Repository navigation
Conversation
Add TRL's `top_entropy_quantile` (high-entropy token masking from
"Beyond the 80/20 Rule", arXiv 2506.01939) to `LigerFusedLinearGRPOLoss`.
TRL's Liger path used to raise for `top_entropy_quantile < 1.0` because the
fused loss never materializes logits, so the per-token entropy now comes
from inside the chunked path:
- `_selective_entropy_forward`: streams over sequence and vocab chunks like
the selective-logprob forward, carrying a second online accumulator
`sum exp(z - m) * z` next to the running `(m, sum_exp)` (both rescaled on
a max update), so H = log_z - sum_exp_z / sum_exp without full logits.
- `LigerFusedLinearPPOBase.forward(top_entropy_quantile=1.0)`: when < 1.0,
one extra no-grad pre-pass over the chunks collects the batch entropies,
the threshold is `quantile(active entropies, 1 - q)` over the whole batch
(all-gathered across ranks when distributed, as in TRL), and the (B, T)
mask is chunked and handed to `ppo_loss_fn(entropy_mask=...)`.
`_quantile_large_safe` falls back to a sort past torch.quantile's 2**24
element cap.
- `LigerFusedLinearGRPOFunction.ppo_loss_fn`: multiplies the policy term by
the mask in TRL's position, after the loss_type branch and before the vLLM
IS correction and the KL penalty, for every loss type including vespo.
- `top_entropy_quantile` is appended at the end of the Function signature so
existing positional callers and `liger_fused_linear_grpo` are unchanged;
the default (1.0) path is bit-identical to before. Validated to [0, 1].
- `LigerFusedLinearPPOBase.backward` now returns one gradient slot per
forward input (the four vespo_* slots were missing).
Tests: entropy accumulator vs `Categorical.entropy()` across chunk
boundaries (fp32/bf16, bias, temperature), quantile fallback vs
torch.quantile, mask vs a port of TRL's `get_high_entropy_mask`, fused loss
vs a full-logits reference for q in {0.2, 0.5, 0.8} over grpo/bnpo/dapo/
cispo/sapo/vespo/luspo with beta in {0, 0.04}, default no-op bit-identity,
range validation, and chunk_size invariance (the quantile is batch-global).
Addresses the remaining item of linkedin#1082.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Resolve docstring conflicts with linkedin#1556 by keeping both the upstream parameter docs and the new top_entropy_quantile parameter docs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@vaibhavjindal when you have a moment, could you take a look at this one? It adds Status: rebased on current main after #1556 landed, checkstyle is green, and |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Adds TRL's
top_entropy_quantile(high-entropy token masking, "Beyond the 80/20 Rule") toLigerFusedLinearGRPOLoss. This addresses the remaining open item of #1082 (item 6, asked about again in August): TRL's Liger path had to raise fortop_entropy_quantile < 1.0because the fused loss never materializes logits, so per-token entropy had nowhere to come from. It now comes from inside the chunked path.Details
_selective_entropy_forwardstreams over sequence x vocab chunks like the selective-logprob forward, carrying a second online accumulatorsum exp(z - m) * znext to the running(m, sum exp);H = log_z - sum_exp_z / sum_exp. No full-vocab logits are materialized, and the existing logprob fast path is untouched.LigerFusedLinearPPOBase.forward(top_entropy_quantile=1.0): when< 1.0, one extra no-grad pre-pass over the batch chunks collects entropies, the threshold isquantile(active entropies, 1 - q)over the whole batch (all-gathered across ranks when distributed, as in TRL'sget_high_entropy_mask), and the(B, T)mask is chunked and passed toppo_loss_fn(entropy_mask=...)._quantile_large_safefalls back to a sort beyondtorch.quantile's 2^24-element cap.ppo_loss_fnmultiplies the policy term by the mask in TRL's position: after the loss-type branch, before the vLLM IS correction and before the KL penalty, for every loss type (including vespo). Loss normalization and the metrics layout are unchanged.top_entropy_quantileis appended at the end of theFunctionsignature, so positional callers andliger_fused_linear_grpoare unaffected; the default (1.0) path is bit-identical to before. Validated to[0, 1].LigerFusedLinearPPOBase.backwardnow returns one gradient slot per forward input (the fourvespo_*slots were missing). feat(grpo): add off-policy sequence masking (OPSM) to fused linear GRPO loss #1557 touches the same tuple; whichever lands second needs a trivial rebase.Cost: one additional no-grad lm_head forward per step when the feature is on; nothing when off.
Follow-up (out of scope): the Triton
_selective_log_softmax_kernelcould carry the same second accumulator in its vocab loop to bring this totriton_grpo_loss.Testing Done
_selective_entropy_forwardvstorch.distributions.Categorical(...).entropy()(fp32/bf16, bias, temperature, shapes crossing both the 2048-row and 4096-vocab chunk boundaries)._quantile_large_safevstorch.quantile, with the sort fallback forced._compute_high_entropy_maskvs a single-process port of TRL'sget_high_entropy_mask(incl. the empty-mask case).Fused loss vs a full-logits reference for
q in {0.2, 0.5, 0.8}x grpo/bnpo/dapo/cispo/sapo/vespo/luspo x token/sequence IS xbeta in {0, 0.04}x bias: loss, metrics, input/weight/bias gradients. The reference asserts the mask drops some but not all tokens, so the test cannot pass vacuously.Default
1.0is bit-identical to omitting the argument; out-of-range values raise;chunk_size=1andchunk_size=Bagree (the quantile is batch-global, not per chunk; the test also shows a per-chunk quantile would differ).pytest test/chunked_loss/test_grpo_loss.py -k "not 1024-4096"on a CPU-only machine withtorch.compiledisabled: 696 passed, 338 skipped. The twotest_triton_grpo_loss_matches_referencecases fail identically onmainin this environment (Triton unavailable locally) and are not regressions.ruff checkandruff format --checkclean on the changed files.Not verified locally: the distributed all-gather path (single process only), the
compiled=Truepath (ran eager here), GPU/bf16, and the H=1024/V=4096 parametrizations. Please let GPU CI confirm.Hardware Type: CPU only (Apple M-series, no GPU)
run
make testto ensure correctnessrun
make checkstyleto ensure code stylerun
make test-convergenceto ensure convergence🤖 Generated with Claude Code