Repository navigation
refactor(rl): one RL loss (ipo/icepop/reinforce) with shared exact score centering and sampler KL - #4233
Open
faresobeid wants to merge 36 commits into
Open
refactor(rl): one RL loss (ipo/icepop/reinforce) with shared exact score centering and sampler KL#4233faresobeid wants to merge 36 commits into
faresobeid wants to merge 36 commits into
Conversation
…, and #4012 parity; config auto rule
…and per-token ages into the trainer
faresobeid
marked this pull request as ready for review
October 24, 2026 18:33
## Summary - `node_policy_versions` counts sampled tokens with `np.count_nonzero(node.mask)` instead of `sum(node.mask)`. `MessageNode.mask` is a numpy bool array since PrimeIntellect-ai/verifiers#2961, which #4226 pulled into main. Python's `sum()` walks that array one token at a time and returns an `np.int64`. `np.count_nonzero` is vectorized, accepts lists too, and the `int()` cast keeps the metric values as plain ints. - `test_token_policy_ages` builds its masks as numpy arrays, which is the type main now produces. Targets #4141. Checks: `ruff check` and `ruff format --check` pass. I also ran `token_policy_ages` on the test case with list masks and with numpy masks. Both returned `(20, 8, 3)` as plain ints. I did not run pytest, because the import chain needs `torch` and the test machine (macOS) does not have it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small metric-calculation fix for numpy masks; behavior unchanged aside from correct int types and performance. > > **Overview** > **Fixes sampled-token counting** now that `MessageNode.mask` is a numpy bool array (from verifiers): `node_policy_versions` uses `int(np.count_nonzero(node.mask))` instead of `sum(node.mask)`, avoiding slow element-wise summation and keeping metrics as plain Python `int`s rather than `np.int64`. > > The unit test builds trace masks with `np.array(...)` to match production types. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5a96878. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # src/prime_rl/orchestrator/train_sink.py # tests/unit/orchestrator/test_train_sink.py
# Conflicts: # docs/algorithms.md # packages/prime-rl-configs/src/prime_rl/configs/rl.py
…t 1/K) as the untruncated default score_centering_estimator = sampled | topk (default sampled), score_centering_k = 16 (replaces score_centering_topk). Without replay, sampled centers with mean_k w(v_k) grad log p(v_k) and estimates the KL as mean_k (log q - log p) over K i.i.d. draws v_k ~ q; topk keeps the head plus proportional tail. The loss takes per-token support_replayed instead of sampler_tail_mass and derives the per-entry weights and the tail itself. The launcher asks policy sources for the draws via extra_args.prl_sampler_draws.
Replace the padded [N, K] sampling-mask / top-logprobs tensors with one token-major CSR support (TokenSupport: int32 ids, host int64 offsets, replayed flag, sampler logprobs). The fused LM head slices each fixed token chunk's entries from host offsets (no sort, padding or device sync), gathers support logits through flat indices and renormalizes replayed rows with a deterministic Triton segment logsumexp. RLLoss reduces per-entry terms with segment sums; CP shards and gathers entry slices.
kl_estimator = 'exact' (the support sum, default) or 'k1' (log q(a) - log p(a), differentiated directly as in Tinker's stable_reinforce); k1 needs no sampler support. Document the StableReinforce-equivalent config.
…bel and support Replayed tokens normalize over their support, and REINFORCE centered over K draws without a KL has weights that sum to one, so in both cases each token's logit gradient lives on its label and support entries. train.py decides this per micro batch and the fused head then skips the vocab loop in backward: the gradient is one CSR matrix multiplied into the weight and hidden states by a deterministic Triton kernel.
…ged-support-trainer
# Conflicts: # docs/algorithms.md # packages/prime-rl-configs/src/prime_rl/configs/rl.py # src/prime_rl/trainer/rl/data.py # src/prime_rl/trainer/rl/train.py # tests/unit/orchestrator/test_batch.py # tests/unit/test_configs.py
# Conflicts: # src/prime_rl/orchestrator/batch.py # src/prime_rl/orchestrator/train_sink.py # src/prime_rl/trainer/rl/data.py # src/prime_rl/trainer/rl/loss.py # src/prime_rl/trainer/rl/train.py # src/prime_rl/transports/batch/types.py # tests/unit/orchestrator/test_batch.py
…ds for custom losses
… updates Score centering now sets a frozen sampling.sampler_draws; the sampling config's validator derives extra_body.extra_args.prl_sampler_draws from it, so every validation (launch, reload, live control-API update, resume merge) re-derives it and a live extra_body update cannot drop it.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fc8dc13. Configure here.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

One RL loss for
ipo,icepopand the newreinforce, with score centering always on and an optional sampler KL, over one ragged (CSR) per-token support from #4232:sum_v u_v f(v)estimatesE_q[f]with one rule per token:u_v = q_v. Centering is exact.score_centering_k(default 16) i.i.d. sampler draws withu_v = 1/K, captured on the GPU next to the sampled token.What changes
RLLossreplacesIPOLoss/IcePopLoss. Types differ only inw: ipo1(|p-q| <= eps) * min(p/q, max_importance_ratio), icepop1(low <= p/q <= high) * p/q, reinforce1.customis unaffected. Config:score_centering_konly (replacesscore_centering/score_centering_topk). The top-K head + tail estimator and its top-id capture are gone: compact-logprob requests now need replay orprl_sampler_draws.kl_coeff, default 0).kl_estimator = exact | k1;kl_staleness = none | inv_sqrtuses per-token policy age from the engines' weight-version labels (main's per-tokenstalenessstream).RLConfigrequests the support for every policy-sampled train source: replay masks with sampler probabilities when truncated,sampling.sampler_draws = Kwhen not (kept across live config updates). Train sampling needs temperature > 0.TokenSupport(int32 ids, host int64 offsets, replayed flag, sampler logprobs) replaces the padded[N, K]mask / top-k tensors indata.py, CP sharding, the model forwards and the loss. The fused LM head slices fixed token chunks from the CSR, with no padding, sort or device-to-host sync.score_centering/support_mass(E_q[p/q]),score_centering/mask_tv, and with the KLkl,kl/beta,kl/age0.Why
Each token needs the sampler distribution over its support. Padding every support to the micro batch's widest row wasted more than 99% of those tensors on chat with uncapped top-p (about 1.5 GiB at 64k tokens, K=2048). The per-type losses duplicated the same centering math.
Numbers
Head + RLLoss fwd+bwd, H200, H=2048, V≈152k, real chat kept counts at top_p 0.97 (ms / peak GiB):
Validation
pytest tests/unit -m "not gpu": 1024 passed.ruff check,ruff format --check,uv lock --check(uv 0.13.5): clean.test_serving_tokens,test_loss,test_fused_lm_head(incl. sparse vs dense backward),test_batchwith-m gpu: 50 passed.Before merge
main.Note
Medium Risk
Large refactor of RL loss math, batch wire format, and LM-head autograd on the hot training path; truncated runs now center by default, so regressions would show up as wrong gradients or KL rather than silent UI bugs.
Overview
Unifies RL policy losses into one
RLLossforipo,icepop, and newreinforce, all sharing the same per-token objective: importance weightw, optional score centering under the sampler, and optional KL(q‖p) withexact/k1estimators andinv_sqrtstaleness from token policy age.Replaces padded mask/top-k tensors with
TokenSupport(CSR ids, host offsets, replay flag, sampler logprobs) from batch packing through model forward, CP gather, validation, and loss. IcePop-only centering knobs (score_centering_topk, IcePop-only flag) move to shared fields:score_centering(auto/true/false),score_centering_estimator(sampled/topk),score_centering_k.autonow exactly centers replayed (truncated) tokens by default—a behavior change vs IcePop-only centering on main.Inference/orchestrator wiring:
RLConfigauto-requestslogprobsand, for untruncated sampled centering/KL,sampler_draws→prl_sampler_draws; vLLM rejects draws without compact logprobs. Fused LM head computessupport_logprobsover CSR rows and can take a sparse backward (Tritonsegment_reduce/ CSR matmul) when RL gradients lie only on label + support—large memory/time win for replay or REINFORCE+sampled centering without KL.Docs and tests are updated for REINFORCE, StableReinforce mapping, GPU-side CSR validation, and dense-vs-sparse gradient parity.
Reviewed by Cursor Bugbot for commit 4509953. Bugbot is set up for automated code reviews on this repo. Configure here.