Skip to content

Unify policy-loss metrics config and add GSPO metrics#555

Merged
jlamypoirier merged 4 commits into
mainfrom
jlp_gspo_metrics
Jul 8, 2026
Merged

Unify policy-loss metrics config and add GSPO metrics#555
jlamypoirier merged 4 commits into
mainfrom
jlp_gspo_metrics

Conversation

@jlamypoirier

Copy link
Copy Markdown
Collaborator

Summary

Extracted from #553 as an independent, wire-change-free PR (pure Fast-LLM). Gives GSPO the diagnostic metrics GRPO already has, and unifies the metrics config so both losses share it.

  • Config unify (A1): rename GRPOMetricsLevelPolicyLossMetrics and lift the metrics field (none / basic / with_entropy) from the GRPO config up to the shared LanguageModelPolicyGradientLossConfig, so type: gspo and type: grpo both carry it. The pipeline_parallel == 1 guard (both metric paths use a second full-vocab softmax) moves to the shared base.
  • GSPO metrics (A2): compute_gspo_metrics computes segment-level statistics (GSPO clips per segment), reusing the segment aggregation from the loss kernel — segment clipped-ratio fraction, ratio mean + sum/squared-sum, KL(new‖old), advantage mean/max/min, segment/token counts, and (with_entropy) token-level entropy. Metric suffixes mirror GRPO's where the meaning lines up so runs are comparable (noting GSPO's are segment-level, GRPO's token-level). The extra softmax pass is skipped entirely when metrics: none.
  • num_tokens parity: GSPO now also logs the labeled-token count, so mean response length and ratio variance are derivable as for GRPO.

Tests

  • test_gspo_metrics / test_grpo_metrics single-process (reference = plain-Python segment loop), plus a GSPO-metrics subtest added to _run_lm_loss_distributed (and the test_lm_loss_distributed reporting list) so the vocab-parallel group all-reduce path is exercised.
  • Verified locally: import, collection (603 tests), 80 single-process metrics tests pass on CPU, and type: gspo/grpo configs validate for all three metric levels. The distributed group path runs under torchrun in CI.

🤖 Generated with Claude Code — authored by Claude Opus 4.8.

jlamypoirier and others added 4 commits July 8, 2026 11:02
Rename `GRPOMetricsLevel` to `PolicyLossMetrics` and lift the `metrics`
field from the GRPO config to the shared `LanguageModelPolicyGradientLossConfig`,
so GSPO gains the same diagnostic levels (none/basic/with_entropy). The
`pipeline_parallel == 1` guard moves to the shared
`LanguageModelPolicyGradientLoss.__init__`.

Add `compute_gspo_metrics` (segment-level analog of `compute_grpo_metrics`)
and GSPO's `_register_extra_metrics`/`get_loss_definitions`. GSPO clips per
segment, so its statistics (ratio, KL, clip fraction) are per-segment; they
are accumulated as token-weighted sums (weight `mask / num_labels_in_seq`,
which sums to 1 per document across SDP/SP ranks) so they partition and
reduce exactly like the per-token loss. Metric names match GRPO where the
meaning aligns (`_num_segments` replaces `_num_tokens`).

Tested via `compute_gspo_metrics` against an independent plain-Python
segment-loop reference (`reference_gspo_metrics` + `test_gspo_metrics`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Log GSPO num_tokens (labeled-token count) so mean response length and
  ratio variance are derivable, matching GRPO.
- Extract the shared GRPO/GSPO metric LossDef list into
  LanguageModelPolicyGradientLoss._policy_metric_definitions(*extra); GSPO
  passes its num_segments def. Order-preserving.
- Add a GSPO-metrics subtest to _run_lm_loss_distributed (and to the
  test_lm_loss_distributed loss_type list) so compute_gspo_metrics'
  vocab-parallel group all-reduce path is exercised and reported.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The two metric paths differed only in how the importance ratio is formed
(per-token for GRPO, per-segment geometric mean for GSPO). Collapse the
duplication:

- One `PolicyMetrics` tuple replaces `GRPOMetrics`/`GSPOMetrics`.
- `_policy_metrics_log_ratio` holds the shared softmax -> log-ratio -> entropy
  pass; `_policy_metrics_reduce` holds the shared weighted-sum reduction,
  parameterized by the ratio-variance weight (token mask vs document weight).
- One `_register_policy_metrics` replaces the two near-identical registrations.

`compute_grpo_metrics` / `compute_gspo_metrics` now only derive the per-token
`(ratio, effective_log_ratio)` -- their actual difference. GSPO's token-level
`exp`-after-gather is bit-identical to the previous per-segment-then-broadcast.
Behavior-preserving: the independent test references (repackaged into
`PolicyMetrics`, math unchanged) still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rename enum `PolicyLossMetrics` -> `PolicyMetricsLevel` to disambiguate from
  the `PolicyMetrics` value tuple (restores the `Level` suffix).
- `compute_gspo_metrics` label-count parameter `num_labels_in_seq` -> `label_counts`
  to match the sibling `compute_grpo_metrics` and the kwarg source.
- Tighten `losses: dict | None` -> `dict` in both `_register_extra_metrics`
  (only reached under `losses is not None`).
- Name the `num_segments = 4` local in the distributed GSPO-metrics subtest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant