Does DPPO's divergence mask beat GRPO clipping on GSM8K?
DPPO swaps PPO/GRPO ratio clipping for a trust region based on policy divergence; we compare GSM8K test accuracy against GRPO clipping at equal rollouts and updates.
change
Spec
DPPO divergence-masked loss vs GRPO ratio-clipped loss, Qwen3.5-2B GSM8K LoRA RL
Hypothesis. On Qwen3.5-2B LoRA RL on GSM8K with 2 policy updates per rollout batch, the DPPO loss reaches higher greedy exact-match accuracy on the 1319-item GSM8K test set than the GRPO clipped-ratio loss. The lower bound of the paired 95% CI on DPPO minus GRPO must be above 0.
Method. The requested Qwen3.5-0.8B is not served, so we use the closest served small model, Qwen/Qwen3.5-2B. Both arms run the same client-side RL loop: the tinker-cookbook math_rl GSM8K env, renderer, prompts, reward and data order. Each step samples groups_per_batch=32 GSM8K train prompts with group_size=8 rollouts at temperature 1.0 and max_tokens=512.
Success criterion. On the 1319 GSM8K test items, the paired 95% Hoeffding CI for DPPO accuracy minus GRPO accuracy has a lower bound above 0.0, roughly a 7.5-point gap at this n. A smaller positive gap is reported as inconclusive, not validated.
full spec
{
"activation_gate": {
"applicable": true,
"checks": [
{
"max": 0.3,
"measures": "fraction of loss tokens masked by the DPPO divergence rule on the second minibatch update, mean over pilot steps",
"metric": "dppo_masked_token_fraction",
"min": 0.001
},
{
"measures": "90th percentile of |p_current - p_sampler| over loss tokens at the second update",
"metric": "policy_drift_abs_p_q90",
"min": 0.005
},
{
"measures": "fraction of tokens clipped by the eps=0.2 ratio clip in the baseline loss on the same pilot batches (confirms the regime is off-policy for the baseline too)",
"metric": "grpo_clip_fraction",
"min": 0.0005
},
{
"max": 2,
"measures": "optim_step calls per rollout batch as logged",
"metric": "executed_updates_per_batch",
"min": 2
},
{
"measures": "model name reported at client creation",
"metric": "executed_base_model",
"one_of": [
"Qwen/Qwen3.5-2B"
]
}
],
"reason": "DPPO only differs from GRPO on tokens whose divergence exceeds the trust region. A 5-step pilot of the treatment arm, with 2 updates per batch, must show that the mask fires on a nontrivial but not dominant share of tokens and that the policy actually drifts. Executed config values must match the plan."
},
"arms": [
{
"loss": "grpo_clip_eps0.2",
"lr": 2e-05,
"name": "grpo_seed0",
"seed": 0,
"updates_per_batch": 2
},
{
"loss": "dppo_divergence_mask",
"lr": 2e-05,
"name": "dppo_seed0",
"seed": 0,
"updates_per_batch": 2
}
],
"base_model": "Qwen/Qwen3.5-2B",
"baseline": "GRPO clipped-ratio loss (eps=0.2) through the same custom loss path. Identical rollouts per step, 2 minibatch updates per batch, lr, rank, seed, data order and eval.",
"batch_size": 256,
"budget": {
"gpu_hours": 2.0,
"max_wall_minutes": 120
},
"command": "Custom tinker SDK loop; the loss function is the custom part. Sketch:\n\nimport os, tinker\nsc = tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL'])\ntc = sc.create_lora_training_client(base_model='Qwen/Qwen3.5-2B', rank=32)\n\n# Rollouts: reuse tinker_cookbook.recipes.math_rl GSM8K env and renderer.\n# group_size=8, groups_per_batch=32, max_tokens=512, seed=0.\nfor step in range(50):\n sampler = tc.save_weights_and_get_sampling_client(f's{step}')\n batch = rollout(sampler, gsm8k_train_prompts[step], group_size=8) # stores sampler logprobs as old_lp\n adv = group_mean_center(batch.rewards)\n for mb in split(batch, 2):\n tc.forward_backward_custom(mb.data, make_loss(LOSS, old_lp=mb.old_lp, adv=mb.adv)).result()\n tc.optim_step(tinker.AdamParams(learning_rate=2e-5)).result()\n\n# LOSS='grpo': -min(r*A, clip(r, 0.8, 1.2)*A)\n# LOSS='dppo': -(r*A) * keep_mask(div(p, p_old) <= delta), with div and delta pinned from the paper code\n# Final eval: greedy decoding on openai/gsm8k test (1319 items), numeric exact match.",
"correctness_preflight": {
"paths": [
{
"baseline_revision": "UNRESOLVED: pin tinker-cookbook commit whose stock ppo/importance-sampling loss defines GRPO clipping",
"id": "grpo_dppo_custom_loss",
"kind": "loss",
"reference_revision": "UNRESOLVED: pin commit of the official DPPO implementation for arXiv 2602.04879",
"tolerances": {
"baseline_gradient": {
"atol": 1e-05,
"min_cosine": 0.9999,
"rtol": 0.001
},
"baseline_loss": {
"atol": 1e-05,
"rtol": 0.0001
},
"reference_gradient": {
"atol": 1e-05,
"min_cosine": 0.9999,
"rtol": 0.001
},
"reference_loss": {
"atol": 1e-05,
"rtol": 0.0001
}
}
}
],
"required": true,
"status": "unresolved: DPPO reference code revision and exact divergence/threshold not yet pinned (paper digest failed); no full sweep until resolved"
},
"dataset": {
"hf_path": "openai/gsm8k",
"n_examples": 1600,
"name": "gsm8k",
"split": "main/train"
},
"decision_rule": {
"alpha": 0.05,
"criteria": [
{
"bounds": [
0,
1
],
"id": "primary",
"margin": 0.0,
"metric": "accuracy",
"weights": {
"baseline": -1,
"treatment": 1
}
}
],
"estimand": "Difference in greedy GSM8K test exact-match accuracy, DPPO minus GRPO, for these fixed final checkpoints of Qwen3.5-2B LoRA; larger is better",
"method": "paired_hoeffding",
"seeds": [
0
],
"unit": "fixed_checkpoint_items",
"version": 1
},
"eval": {
"aggregation": null,
"benchmark": "GSM8K test (openai/gsm8k main/test)",
"metric": "greedy exact-match accuracy",
"n_examples": 1319
},
"experiment_features": {
"adaptive_selection": false,
"checkpoint_restore": false,
"custom_loss": true,
"custom_optimizer": false,
"generated_training_data": false,
"heldout_loss": false,
"learned_generator": false,
"paired_comparison": true,
"paired_training_arms": true,
"warmup_adaptation": false
},
"feasibility": null,
"feasibility_check": {
"gpu_hours": null,
"known_gpu_hours": 0,
"phases": {},
"problems": [
"Measured feasibility not recorded"
],
"status": "unknown",
"wall_seconds": null
},
"feasibility_required": true,
"generation_audit": {
"answer_comparison": "numeric_exact",
"max_eval_overlaps": 0,
"max_label_errors": 0,
"min_sample_size": 100,
"required": false
},
"hypothesis": "On Qwen3.5-2B LoRA RL on GSM8K with 2 policy updates per rollout batch, the DPPO loss reaches higher greedy exact-match accuracy on the 1319-item GSM8K test set than the GRPO clipped-ratio loss. The lower bound of the paired 95% CI on DPPO minus GRPO must be above 0.",
"lora_rank": 32,
"max_seq_len": 1024,
"method": "The requested Qwen3.5-0.8B is not served, so we use the closest served small model, Qwen/Qwen3.5-2B.\n\nBoth arms run the same client-side RL loop: the tinker-cookbook math_rl GSM8K env, renderer, prompts, reward and data order. Each step samples groups_per_batch=32 GSM8K train prompts with group_size=8 rollouts at temperature 1.0 and max_tokens=512. Advantages are group-mean-centered (GRPO).\n\nThe rollout batch is split into 2 minibatches. That gives 2 optim_steps per batch, so the policy drifts from the sampling policy and the trust region can act. With one update per fresh batch, neither mask nor clip fires. Both losses are computed in one forked custom loss path through forward_backward_custom, using the sampler logprobs as p_old.\n\nThe arms differ in exactly one switch:\n- Baseline (GRPO): token loss -min(r*A, clip(r, 1-0.2, 1+0.2)*A).\n- Treatment (DPPO, arXiv 2602.04879): token loss -r*A, with tokens masked out when the divergence between the current and sampling policy exceeds the threshold delta in the direction the advantage pushes. The divergence is estimated from the token probabilities, e.g. |p - p_old|, rather than the single-sample ratio.\n\nTriage only received a truncated digest of the paper. The exact divergence estimator, delta and masking direction must be pinned from the paper's released code before any training. Until then the correctness preflight is unresolved and no full sweep runs.\n\nTraining: LoRA rank 32, AdamW lr 2e-5, 50 rollout steps (100 optim steps) per arm, seed 0. Both arms start from the same base weights.\n\nEvaluation: the final checkpoint of each arm is scored with greedy decoding on all 1319 GSM8K test items, using the cookbook's numeric answer grader.",
"optimizer": {
"lr": 2e-05,
"name": "adam"
},
"primary_endpoint": "gsm8k_test_greedy_exact_match_accuracy@final_checkpoint (1319 items)",
"restoration_preflight": {
"applicable": true,
"fixed_batch_required": true,
"groups": [
{
"arms": [
{
"name": "grpo_seed0",
"optimizer": "adam",
"optimizer_config": {
"beta1": 0.9,
"beta2": 0.95,
"eps": 1e-08,
"learning_rate": 2e-05
}
},
{
"name": "dppo_seed0",
"optimizer": "adam",
"optimizer_config": {
"beta1": 0.9,
"beta2": 0.95,
"eps": 1e-08,
"learning_rate": 2e-05
}
}
]
}
],
"reason": "Both arms are LoRA-trained from the same base. Before the first update, verify identical initial state: compare the logprobs on a fixed tokenized GSM8K batch across both freshly created clients, which must be bitwise or within 1e-6. Also verify the active optimizer is AdamW with the planned config after creation. Server identity cannot be introspected and stays unverified."
},
"steps": 100,
"success_criterion": "On the 1319 GSM8K test items, the paired 95% Hoeffding CI for DPPO accuracy minus GRPO accuracy has a lower bound above 0.0, roughly a 7.5-point gap at this n. A smaller positive gap is reported as inconclusive, not validated.",
"title": "DPPO divergence-masked loss vs GRPO ratio-clipped loss, Qwen3.5-2B GSM8K LoRA RL",
"training_type": "rl"
}previous versions (1)
spec
{
"base_model": "Qwen/Qwen3.5-0.8B",
"baseline": "Same model, LoRA rank, learning rate, seed, rollout settings and step count, trained with the stock tinker_cookbook math_rl GRPO-style loss.",
"batch_size": 256,
"budget": {
"gpu_hours": 2.0,
"max_wall_minutes": 120
},
"command": "Baseline: python -m tinker_cookbook.recipes.math_rl.train model_name=Qwen/Qwen3.5-0.8B base_url=$TINKER_BASE_URL env=gsm8k group_size=8 groups_per_batch=32 learning_rate=4e-5 max_tokens=512 max_steps=100 log_path=/tmp/rx-dppo-baseline. Treatment (tinker SDK script, custom part is dppo_loss): import os, tinker; sc = tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL']); tc = sc.create_lora_training_client(base_model='Qwen/Qwen3.5-0.8B', rank=32); for step in range(100): sampler = tc.save_weights_and_get_sampling_client(name=f's{step}'); data, old_logprobs, advantages = rollout_gsm8k(sampler, groups=32, group_size=8, max_tokens=512) # same rollout and advantage code as the cookbook loop; tc.forward_backward_custom(data, lambda d, logprobs: dppo_loss(logprobs, old_logprobs, advantages)).result() # dppo_loss implements the arXiv 2602.04879 objective; tc.optim_step(tinker.AdamParams(learning_rate=4e-5)).result(); then tc.save_weights_for_sampler(name='dppo-final') and evaluate both LoRAs on GSM8K test.",
"dataset": {
"hf_path": "openai/gsm8k",
"n_examples": 7473,
"name": "GSM8K",
"split": "train"
},
"eval": {
"benchmark": "GSM8K test",
"metric": "exact-match accuracy, greedy decoding",
"n_examples": 1319
},
"hypothesis": "With the same rollouts, rewards and group-normalized advantages, the DPPO loss raises Qwen3.5-0.8B GSM8K test exact-match accuracy by at least 2 points over the cookbook GRPO-style loss after 100 steps.",
"lora_rank": 32,
"max_seq_len": 1024,
"method": "Train a rank-32 LoRA on Qwen/Qwen3.5-0.8B with RL on the GSM8K train split, using a binary correct-answer reward. Both arms share the same sampling setup: group size 8, 32 groups per batch, max 512 tokens, temperature 1.0, and group-mean advantages. The baseline uses the stock cookbook loss. The treatment changes only the policy loss. It implements the DPPO objective exactly as written in arXiv 2602.04879, as a custom per-token loss through forward_backward_custom, and uses the paper's default hyperparameters. The custom part is the loss function only. Rollouts and advantages reuse the same code as the baseline. Both final LoRAs are evaluated with greedy decoding on the full GSM8K test set. Training reward curves and policy entropy are also logged as a stability check.",
"optimizer": {
"lr": 4e-05,
"name": "adam"
},
"steps": 100,
"success_criterion": "Validated if DPPO accuracy is at least 2.0 points above the baseline on the 1,319 GSM8K test examples, which is beyond the roughly 1.3-point standard error. Failed if DPPO is below the baseline by more than 2.0 points. Anything in between counts as a tie: no accuracy gain, so first-class support would rest on stability or customer demand alone.",
"title": "DPPO loss vs GRPO loss for LoRA RL on GSM8K",
"training_type": "rl"
}Runs
Source
raw text
Abhi Nambiar will be in the office today. he's already used spindle a bit to do some depoliticizing experiments for qwen, could pick his brains on how it's been so far [jason.mancuso] oh wat, I'm drafting up something about this rn [micah] Notes from quick sync: He tried Spindle and found that it was more expensive out of the box than Tinker, but this is because 1) was running sync RL and 2) he was only a single LoRA client at a time. Bugs/feature requests: sampler pools kept scaling down during training; He asked about DPPO loss support (https://arxiv.org/html/2602.04879v1) and wondered if we can add first-class support for it (kailash will own); He wondered whether we're using spec decode during inference rollouts; He wants the DS 4.1 Flash 1017 update.
links triage read (1)
triage output
{
"adjacent_experiment": "",
"reproducibility_reason": "DPPO replaces GRPO's ratio clipping with a divergence-based token mask. It is client-side loss code via forward_backward_custom in the cookbook RL loop on GSM8K. No fork is needed.",
"reproducible_on_spindle": true,
"sources": [
{
"brief": null,
"error": "digest failed: ask_json failed after 2 attempts: no JSON object in response: '{\"title\":\"Rethinking the Trust Region in LLM Reinforcement Learning\",\"claim\":\"PPO/GRPO ratio clipping is a noisy single-sample estimate of policy divergence. It over-constrains low-probability tokens,'",
"full_text": true,
"kind": "paper",
"title": "Rethinking the Trust Region in LLM Reinforcement Learning",
"url": "https://arxiv.org/html/2602.04879v1"
}
],
"spec": {
"activation_gate": {
"applicable": true,
"checks": [
{
"max": 0.3,
"measures": "fraction of loss tokens masked by the DPPO divergence rule on the second minibatch update, mean over pilot steps",
"metric": "dppo_masked_token_fraction",
"min": 0.001
},
{
"measures": "90th percentile of |p_current - p_sampler| over loss tokens at the second update",
"metric": "policy_drift_abs_p_q90",
"min": 0.005
},
{
"measures": "fraction of tokens clipped by the eps=0.2 ratio clip in the baseline loss on the same pilot batches (confirms the regime is off-policy for the baseline too)",
"metric": "grpo_clip_fraction",
"min": 0.0005
},
{
"max": 2,
"measures": "optim_step calls per rollout batch as logged",
"metric": "executed_updates_per_batch",
"min": 2
},
{
"measures": "model name reported at client creation",
"metric": "executed_base_model",
"one_of": [
"Qwen/Qwen3.5-2B"
]
}
],
"reason": "DPPO only differs from GRPO on tokens whose divergence exceeds the trust region. A 5-step pilot of the treatment arm, with 2 updates per batch, must show that the mask fires on a nontrivial but not dominant share of tokens and that the policy actually drifts. Executed config values must match the plan."
},
"arms": [
{
"loss": "grpo_clip_eps0.2",
"lr": 2e-05,
"name": "grpo_seed0",
"seed": 0,
"updates_per_batch": 2
},
{
"loss": "dppo_divergence_mask",
"lr": 2e-05,
"name": "dppo_seed0",
"seed": 0,
"updates_per_batch": 2
}
],
"base_model": "Qwen/Qwen3.5-2B",
"baseline": "GRPO clipped-ratio loss (eps=0.2) through the same custom loss path. Identical rollouts per step, 2 minibatch updates per batch, lr, rank, seed, data order and eval.",
"batch_size": 256,
"budget": {
"gpu_hours": 2.0,
"max_wall_minutes": 120
},
"command": "Custom tinker SDK loop; the loss function is the custom part. Sketch:\n\nimport os, tinker\nsc = tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL'])\ntc = sc.create_lora_training_client(base_model='Qwen/Qwen3.5-2B', rank=32)\n\n# Rollouts: reuse tinker_cookbook.recipes.math_rl GSM8K env and renderer.\n# group_size=8, groups_per_batch=32, max_tokens=512, seed=0.\nfor step in range(50):\n sampler = tc.save_weights_and_get_sampling_client(f's{step}')\n batch = rollout(sampler, gsm8k_train_prompts[step], group_size=8) # stores sampler logprobs as old_lp\n adv = group_mean_center(batch.rewards)\n for mb in split(batch, 2):\n tc.forward_backward_custom(mb.data, make_loss(LOSS, old_lp=mb.old_lp, adv=mb.adv)).result()\n tc.optim_step(tinker.AdamParams(learning_rate=2e-5)).result()\n\n# LOSS='grpo': -min(r*A, clip(r, 0.8, 1.2)*A)\n# LOSS='dppo': -(r*A) * keep_mask(div(p, p_old) <= delta), with div and delta pinned from the paper code\n# Final eval: greedy decoding on openai/gsm8k test (1319 items), numeric exact match.",
"correctness_preflight": {
"paths": [
{
"baseline_revision": "UNRESOLVED: pin tinker-cookbook commit whose stock ppo/importance-sampling loss defines GRPO clipping",
"id": "grpo_dppo_custom_loss",
"kind": "loss",
"reference_revision": "UNRESOLVED: pin commit of the official DPPO implementation for arXiv 2602.04879",
"tolerances": {
"baseline_gradient": {
"atol": 1e-05,
"min_cosine": 0.9999,
"rtol": 0.001
},
"baseline_loss": {
"atol": 1e-05,
"rtol": 0.0001
},
"reference_gradient": {
"atol": 1e-05,
"min_cosine": 0.9999,
"rtol": 0.001
},
"reference_loss": {
"atol": 1e-05,
"rtol": 0.0001
}
}
}
],
"required": true,
"status": "unresolved: DPPO reference code revision and exact divergence/threshold not yet pinned (paper digest failed); no full sweep until resolved"
},
"dataset": {
"hf_path": "openai/gsm8k",
"n_examples": 1600,
"name": "gsm8k",
"split": "main/train"
},
"decision_rule": {
"alpha": 0.05,
"criteria": [
{
"bounds": [
0,
1
],
"id": "primary",
"margin": 0.0,
"metric": "accuracy",
"weights": {
"baseline": -1,
"treatment": 1
}
}
],
"estimand": "Difference in greedy GSM8K test exact-match accuracy, DPPO minus GRPO, for these fixed final checkpoints of Qwen3.5-2B LoRA; larger is better",
"method": "paired_hoeffding",
"seeds": [
0
],
"unit": "fixed_checkpoint_items",
"version": 1
},
"eval": {
"aggregation": null,
"benchmark": "GSM8K test (openai/gsm8k main/test)",
"metric": "greedy exact-match accuracy",
"n_examples": 1319
},
"experiment_features": {
"adaptive_selection": false,
"checkpoint_restore": false,
"custom_loss": true,
"custom_optimizer": false,
"generated_training_data": false,
"heldout_loss": false,
"learned_generator": false,
"paired_comparison": true,
"paired_training_arms": true,
"warmup_adaptation": false
},
"feasibility": null,
"feasibility_check": {
"gpu_hours": null,
"known_gpu_hours": 0,
"phases": {},
"problems": [
"Measured feasibility not recorded"
],
"status": "unknown",
"wall_seconds": null
},
"feasibility_required": true,
"generation_audit": {
"answer_comparison": "numeric_exact",
"max_eval_overlaps": 0,
"max_label_errors": 0,
"min_sample_size": 100,
"required": false
},
"hypothesis": "On Qwen3.5-2B LoRA RL on GSM8K with 2 policy updates per rollout batch, the DPPO loss reaches higher greedy exact-match accuracy on the 1319-item GSM8K test set than the GRPO clipped-ratio loss. The lower bound of the paired 95% CI on DPPO minus GRPO must be above 0.",
"lora_rank": 32,
"max_seq_len": 1024,
"method": "The requested Qwen3.5-0.8B is not served, so we use the closest served small model, Qwen/Qwen3.5-2B.\n\nBoth arms run the same client-side RL loop: the tinker-cookbook math_rl GSM8K env, renderer, prompts, reward and data order. Each step samples groups_per_batch=32 GSM8K train prompts with group_size=8 rollouts at temperature 1.0 and max_tokens=512. Advantages are group-mean-centered (GRPO).\n\nThe rollout batch is split into 2 minibatches. That gives 2 optim_steps per batch, so the policy drifts from the sampling policy and the trust region can act. With one update per fresh batch, neither mask nor clip fires. Both losses are computed in one forked custom loss path through forward_backward_custom, using the sampler logprobs as p_old.\n\nThe arms differ in exactly one switch:\n- Baseline (GRPO): token loss -min(r*A, clip(r, 1-0.2, 1+0.2)*A).\n- Treatment (DPPO, arXiv 2602.04879): token loss -r*A, with tokens masked out when the divergence between the current and sampling policy exceeds the threshold delta in the direction the advantage pushes. The divergence is estimated from the token probabilities, e.g. |p - p_old|, rather than the single-sample ratio.\n\nTriage only received a truncated digest of the paper. The exact divergence estimator, delta and masking direction must be pinned from the paper's released code before any training. Until then the correctness preflight is unresolved and no full sweep runs.\n\nTraining: LoRA rank 32, AdamW lr 2e-5, 50 rollout steps (100 optim steps) per arm, seed 0. Both arms start from the same base weights.\n\nEvaluation: the final checkpoint of each arm is scored with greedy decoding on all 1319 GSM8K test items, using the cookbook's numeric answer grader.",
"optimizer": {
"lr": 2e-05,
"name": "adam"
},
"primary_endpoint": "gsm8k_test_greedy_exact_match_accuracy@final_checkpoint (1319 items)",
"restoration_preflight": {
"applicable": true,
"fixed_batch_required": true,
"groups": [
{
"arms": [
{
"name": "grpo_seed0",
"optimizer": "adam",
"optimizer_config": {
"beta1": 0.9,
"beta2": 0.95,
"eps": 1e-08,
"learning_rate": 2e-05
}
},
{
"name": "dppo_seed0",
"optimizer": "adam",
"optimizer_config": {
"beta1": 0.9,
"beta2": 0.95,
"eps": 1e-08,
"learning_rate": 2e-05
}
}
]
}
],
"reason": "Both arms are LoRA-trained from the same base. Before the first update, verify identical initial state: compare the logprobs on a fixed tokenized GSM8K batch across both freshly created clients, which must be bitwise or within 1e-6. Also verify the active optimizer is AdamW with the planned config after creation. Server identity cannot be introspected and stays unverified."
},
"steps": 100,
"success_criterion": "On the 1319 GSM8K test items, the paired 95% Hoeffding CI for DPPO accuracy minus GRPO accuracy has a lower bound above 0.0, roughly a 7.5-point gap at this n. A smaller positive gap is reported as inconclusive, not validated.",
"title": "DPPO divergence-masked loss vs GRPO ratio-clipped loss, Qwen3.5-2B GSM8K LoRA RL",
"training_type": "rl"
},
"summary": "DPPO swaps PPO/GRPO ratio clipping for a trust region based on policy divergence; we compare GSM8K test accuracy against GRPO clipping at equal rollouts and updates.",
"tags": [
"rl",
"paper",
"custom-loss",
"lora",
"grpo"
],
"title": "Does DPPO's divergence mask beat GRPO clipping on GSM8K?",
"value_estimate": 4,
"value_rationale": "A paying user asked for first-class DPPO support, and a clean head-to-head against GRPO tells us whether to ship it as a built-in loss. Triage only saw a truncated digest of the paper, so the exact DPPO rule must be pinned from the source first."
}
@pre-mortem · 2d ago · 38/100 · Before the DPPO arm, I'd run the custom-loss path with the GRPO loss and confirm it matches the stoc…