Skip to content

Re-sync gspo_token _compute_loss with GRPOTrainer - #6856

Open
behroozazarkhalili wants to merge 13 commits into
mainfrom
fix/6807-gspo-token-resync
Open

Re-sync gspo_token _compute_loss with GRPOTrainer#6856
behroozazarkhalili wants to merge 13 commits into
mainfrom
fix/6807-gspo-token-resync

Conversation

@behroozazarkhalili

@behroozazarkhalili behroozazarkhalili commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Re-syncs trl/experimental/gspo_token/grpo_trainer.py::_compute_loss against the current GRPOTrainer._compute_loss, as reported in #6807.

Credit to @mmjerge, who found this and offered to send the patch.

What was wrong

The override is a copy of GRPOTrainer._compute_loss plus one branch. It stopped tracking its source, so four GRPOConfig options, four VLM forward kwargs and one metric silently did nothing on this trainer. All of it reproduces at 47d20ec6:

Setting State in the copy
use_bias_correction_kl KL never multiplied by coef_1
off_policy_mask_threshold get_off_policy_mask call and its application both absent
entropy_coef and the adaptive controller the whole _entropy_bonus_enabled block absent
router_aux_loss_coef compute_aux_loss kwarg and the aux term absent
VLM forward kwargs mm_token_type_ids, image_position_ids, spatial_shapes, num_tiles absent
the sequence_token error message omitted the value the file itself accepts

The issue did not list the policy_loss metric, which GRPOTrainer logs and this copy does not.

use_bias_correction_kl failed differently from the rest. It multiplies the per-token KL by coef_1, and the copy computed the KL forty lines before coef_1 existed, so the option was unreachable rather than missing. Restoring canonical statement order is what fixes it.

What this changes

_compute_loss is now the canonical method at 47d20ec6, lines 3109 to 3379, with two divergences: the sequence_token branch of importance_sampling_level, and the ValueError message naming that third value. Checking that claim takes one command, and it returns those two hunks and nothing else:

SRC=trl/trainer/grpo_trainer.py
GSPO=trl/experimental/gspo_token/grpo_trainer.py
diff <(git show "47d20ec6:$SRC" | sed -n '3109,3379p') <(sed -n '22,297p' "$GSPO")

The diff is large relative to the bug because the method was rebuilt from its source instead of patched feature by feature. Patching is how it drifted: #6024, #6380, #6439 and #6083 each touched this file and each left drift behind.

Verification

  • Tests (experimental) green in CI, and the 9 gspo_token tests pass on a compute node against the branch. test_matches_grpo_trainer_outside_the_sequence_token_branch compares the subclass with the unbound GRPOTrainer._compute_loss on the same trainer and inputs at importance_sampling_level="token", in loss, gradient and logged metrics, once per restored option (bias-corrected KL, off-policy mask, entropy bonus); the pre-resync file fails all three rows, while the earlier gradient test passes on it. Check code quality green, which is ruff@0.13.3 plus the pinned doc-builder; I ran both locally too, the doc-builder one with a negative control confirming it can still fail.
  • The GSPO-token math is untouched. The branch is byte-identical to the one it replaces, torch.equal on the weight tensor returns True, and the stop-gradient identity d/dlogp exp(w) == exp(seq_weight) holds.
  • Each defect was quantified by driving both code paths on identical synthetic tensors under importance_sampling_level="sequence_token":
option                      stale       re-synced      rel
use_bias_correction_kl    0.00516653   0.00515584    -0.21%
entropy bonus 0.01        0.00516653  -0.00414616  -180.25%
MoE aux loss 2.0          0.00516653   0.00716653   +38.71%
OPSM threshold 0.05       0.00516653   0.09534809  +1745.49%
  • Metamorphic control: with every drifted option off, the two paths agree exactly at all three importance_sampling_level values, so the table isolates the options rather than the rewrite.
  • sequence_token yields (B, T), the same shape as "token", so the newly reachable cispo, sapo, vespo and luspo branches broadcast correctly.

Two things reported rather than fixed

gmpo has the same gaps. trl/experimental/gmpo/gmpo_trainer.py is also missing the bias-correction KL, OPSM, the entropy bonus, the aux loss, and spatial_shapes/num_tiles. It is not a mechanical re-sync: GMPO's objective is sequence-level, coef is (B,), and there is no loss_type branching, so use_bias_correction_kl and OPSM have no direct translation. Happy to open a separate PR once you decide what those should mean there.

vespo ignores importance_sampling_level. get_gamma_weights receives log_ratio_per_token=log_ratio, so phi_seq never sees log_importance_weights or coef_1. Measured across all three levels the vespo loss is identical to twelve decimal places. GRPOTrainer behaves the same way for "sequence", so per the AGENTS.md consistency rule I reproduced it rather than adding a guard here that would diverge from the source.

Note on conflicts

#6637 and #6667 both touch this file's KL block, and this re-sync moves that block. Whichever merges second will conflict. I will rebase mine.

Resolves #6807


Note

Medium Risk
Large rewrite of RL training loss logic affects gradients and optional regularizers; risk is mitigated by parity tests against GRPOTrainer and unchanged sequence_token math.

Overview
Re-aligns GSPOTokenTrainer._compute_loss with the current GRPOTrainer implementation so drifted GRPOConfig behavior works again, while keeping the GSPO-token-only importance_sampling_level="sequence_token" branch unchanged.

The override again applies bias-corrected KL (use_bias_correction_kl), off-policy masking (off_policy_mask_threshold), entropy regularization (static and adaptive), MoE router aux loss, extra VLM forward kwargs, and the full set of loss types and metrics (e.g. policy_loss, CISPO/VESPO clip logging) that the stale copy had dropped or computed in the wrong order.

Tests add gradient checks that sequence_token routes per-token advantages differently from sequence, and a parametrized parity test that GSPOTokenTrainer matches GRPOTrainer on loss, gradients, and logged metrics when importance_sampling_level="token" and each restored option is enabled.

Reviewed by Cursor Bugbot for commit 3cb6e52. Bugbot is set up for automated code reviews on this repo. Configure here.

`trl/experimental/gspo_token` overrides `_compute_loss` with a copy of
`GRPOTrainer._compute_loss` plus one branch. The copy stopped tracking its
source, so four `GRPOConfig` options, four VLM forward kwargs and one metric
silently did nothing on this trainer.

`use_bias_correction_kl` failed differently from the rest. It multiplies the
per-token KL by `coef_1`, and the copy computed the KL forty lines before
`coef_1` existed, so the option was unreachable rather than missing. Restoring
the canonical statement order is what fixes it.

The others were absences: the OPSM mask and its application, the entropy bonus
with its adaptive controller, `compute_aux_loss` and the router auxiliary term,
the `mm_token_type_ids`, `image_position_ids`, `spatial_shapes` and `num_tiles`
kwargs that `_generate_and_score_completions` already puts in `inputs`, and the
`policy_loss` metric. Four newer `loss_type` values raised instead, which at
least failed loudly.

The method is now the canonical one at 47d20ec, lines 3109 to 3379, with two
divergences: the `sequence_token` branch, and an error message that names it.
Reviewers can check that with:

    SRC=trl/trainer/grpo_trainer.py
    GSPO=trl/experimental/gspo_token/grpo_trainer.py
    diff <(git show "47d20ec6:$SRC" | sed -n '3109,3379p') \
         <(sed -n '22,297p' "$GSPO")

Resolves #6807
@mmjerge

mmjerge commented Aug 21, 2026

Copy link
Copy Markdown

@behroozazarkhalili looks good thanks!

@behroozazarkhalili
behroozazarkhalili marked this pull request as ready for review August 21, 2026 15:23
The job is gated on `draft == false` and the PR was opened as a draft, so it
was skipped. Marking the PR ready does not re-trigger it, because
`ready_for_review` is not one of the default `pull_request` event types. An
empty commit fires `synchronize`, which does.

No code change: the tree is identical to 4520d7b.
@bot-ci-comment

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

The branch was 8 commits behind and merged with no conflicts. None of the eight
touched trl/trainer/grpo_trainer.py or trl/experimental/gspo_token/, so the reference
this PR syncs against did not move; they are CI and test changes only.

Re-checked the invariant on the merged tree by comparing the two _compute_loss bodies
as parsed ASTs: they differ in exactly 2 hunks, the sequence_token branch and the
error message that lists it, which are the two intended divergences.
…om sequence

The only test of the re-synced `_compute_loss` trains with one advantage
per sequence. Under that input `sequence_token` and `sequence` agree in
value and in gradient, so the test would pass even if the branch had
collapsed to plain sequence-level GSPO.

The new test calls `_compute_loss` directly with hand-built inputs. With
one advantage per sequence it confirms the two levels match; with
token-varying advantages it confirms the loss values still match while
the parameter gradients differ, which is the whole point of the token
variant: the detached sequence weight routes each token's own advantage
into its gradient. Replacing the branch by the sequence branch makes the
gradient assertion fail.
…not only to the other levels

The gradient test compared sequence_token against sequence, so any branch that merely differed from sequence would pass, including one that forgot the stop-gradient on the sequence weight. With old_per_token_logps equal to the live log-probs the sequence weight is exactly 1 and must carry no gradient, so sequence_token has to reproduce the token-level gradient exactly. The test now asserts that equality. Removing the detach makes it fail while the relation checks still pass.
…e_token branch

The existing gradient test runs with beta=0 and only exercises the sequence_token branch, which the
file already had before the resync, so it passes against the pre-resync implementation and does not
guard what this PR restores. The new test calls the subclass and the unbound GRPOTrainer._compute_loss
on the same trainer and inputs at importance_sampling_level="token" and asserts the same loss, the same
gradient and the same logged metrics, once per restored option: the bias-corrected KL, the off-policy
mask and the entropy bonus. The pre-resync file fails all three rows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gspo_token: _compute_loss is a stale copy of GRPO's — use_bias_correction_kl, OPSM, entropy bonus, and MoE aux loss silently ignored

2 participants