Warn when truncated sampling biases the vLLM importance-sampling ratio - #6880
Warn when truncated sampling biases the vLLM importance-sampling ratio#6880behroozazarkhalili wants to merge 5 commits into
Conversation
…mpling ratio
GRPO asks vLLM for `processed_logprobs`, which are renormalized over the support
that survives top_p/top_k/min_p, while the trainer takes a full-vocab log-softmax.
`grpo_trainer.py:2680` differences the two, so the result carries log(S), the log
of the surviving probability mass, on top of the train/inference mismatch the
correction exists to measure.
Measured on one H100 with GRPOTrainer in colocate mode,
`sampling/sampling_logp_difference/mean` reads |log(top_p)| exactly:
top_p predicted measured
1.0 0.0000 0.0007
0.9 0.1054 0.1054
0.8 0.2231 0.2232
The sequence-level `sampling/importance_sampling_ratio/mean` confirms the same
mechanism through a different quantity, reading S^16 for 16-token completions:
0.1855 against 0.185302 at top_p=0.9, and 0.02814 against 0.028147 at top_p=0.8.
The correction therefore scales the policy gradient by a factor the user did not
ask for, with no error and no log line. This warns at config time instead of
changing the math, because a correct fix needs the kept-token set from the sampler
and vLLM only returns it from a release outside the current vllm<=0.27.1 pin.
The guard is in `GRPOConfig.__post_init__`, so it reaches `GRPOWithReplayBufferConfig`
and the gspo_token variant through inheritance. RLOO is unaffected because it has no
importance-sampling correction.
|
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. |
|
Nice work — the measured-vs-predicted One gap: this only guards Given AsyncGRPO is arguably the more exposed case (no correction flag to turn off), might be worth either duplicating this guard into |
The guard added for #6789 lists top_p, top_k and min_p, but vLLM computes `processed_logprobs` after its whole sampler pass rather than after truncation alone. `repetition_penalty` is applied in that same pass, at `vllm/v1/sample/sampler.py:403`, before `sample()` produces the logprobs the importance-sampling correction consumes. A run with `repetition_penalty != 1.0` and otherwise default sampling therefore biases `sampling/sampling_logp_difference` the same way truncation does, and the warning stayed silent for it. `temperature` is the case that needs no warning: both trainers divide their own logits by it before the log-softmax (`grpo_trainer.py:1539`, and `utils.py:1336` for the chunked head), so it cancels in the difference. The trigger list is renamed to `reshaping` because it no longer describes only truncation, and the message says "reshapes the sampled distribution" instead.
|
Confirmed, all three, and the gap turns out to be worse than a missing warning. Checked against this PR's head and vLLM 0.22.0:
You hedged on whether it has a What I had not appreciated until working through it is that the async bias lands in a different place than GRPO's. GRPO computes its own
Below Two things came out of it. #6944 adds the warning on the AsyncGRPO side, and its CI is green. #6945 raises the design question underneath, which is that TRL imposes Your comment also caught something in this PR itself. On the offer to help, the useful piece is measurement. That table is derived from the two normalizations and computed exactly, not observed on hardware, and confirming it end to end needs a two-GPU async run against a real vLLM server that I have not managed to schedule. If you have a lane for that, #6945 is the place to put it. |
…of vLLM logprobs
The warning read the config fields, but `generation_kwargs` is merged
over them when the request is built, in both server and colocate mode.
`generation_kwargs={"top_p": 0.9}` with the field at 1.0 stayed silent,
and `top_p=0.9` with an override back to 1.0 warned about nothing. The
check now evaluates the effective value of each field, and the message
says which of the two the user set.
It also gated on `vllm_importance_sampling_correction` alone. The
trainer requests vLLM's logprobs unconditionally and stores them in
the inputs whenever they arrive, and the off-policy mask reads them
whenever `off_policy_mask_threshold` is set, correction or not. So the
bias reached the keep/drop decision with the correction off, and the
message's advice to turn the correction off did not remove it. The gate
now covers either consumer, and the advice names both.
`temperature` was excluded on the claim that it cancels. It does, as
long as vLLM samples at the trainer's value; a `generation_kwargs`
override of it breaks that and is now checked. Two limits are stated in
the comment rather than checked: a temperature below vLLM's greedy
threshold (1e-5), where vLLM skips temperature scaling while the
trainer still divides, because that constant is vLLM's; and `top_k` at
or above the vocabulary size, which vLLM disables but the config cannot
recognise. The `generation_kwargs` docstring says that other
sampler-side modifiers passed through it (`logit_bias`, `bad_words`,
`frequency_penalty`, ...) bias the difference the same way and are not
checked.
The message listed several settings as "top_p and top_k and min_p
reshapes"; it now reads "Setting `top_p`, `top_k` and `min_p`
reshapes". The default-row test comment claimed the two distributions
agree; the PR's own measurement puts the default at 0.001, so it now
says only the train/inference mismatch remains.
Tests: a `generation_kwargs` matrix (override alone warns, override
back to the default is silent, temperature override warns, matching
temperature is silent), an off-policy-mask case with the correction
off, and the existing silent case renamed to say what it covers. On
the previous commit four of the new cases fail: the override alone, the
override back to the default, the temperature override and the
off-policy mask. The matching-temperature and no-consumer cases pass on
both.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort 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 d51684c. Configure here.
| "`off_policy_mask_threshold=None`. See https://github.com/huggingface/trl/issues/6789.", | ||
| UserWarning, | ||
| stacklevel=3, | ||
| ) |
There was a problem hiding this comment.
AsyncGRPO misses truncation warning
Medium Severity
The new truncation-bias warning lives only in GRPOConfig.__post_init__, but AsyncGRPOConfig extends _BaseConfig directly and never inherits it. AsyncGRPOTrainer still differences trainer log-softmax against vLLM old_log_probs in log_ratio, so top_p, top_k, min_p, and repetition_penalty bias that ratio the same way with no warning. Repository guidance requires duplicated trainer logic to stay aligned across copies.
Triggered by project rule: ../.ai/AGENTS.md
Reviewed by Cursor Bugbot for commit d51684c. Configure here.
There was a problem hiding this comment.
Correct that AsyncGRPOConfig does not inherit this block. That is deliberate: the async trainer has no vllm_importance_sampling_correction, so the message here ("biases sampling/sampling_logp_difference and the correction built on it") would be false for it, and its own guard needs different wording. That half is #6944, which adds the warning to AsyncGRPOConfig.__post_init__ with the async-specific message and covers top_p, top_k, min_p and repetition_penalty; #6945 tracks the design question of whether AsyncGRPO should get the correction itself. Keeping the two PRs separate lets each be reviewed against its own trainer's semantics.


What this does
Warns when something in GRPO consumes vLLM's logprobs, the importance-sampling correction or the off-policy mask, and the sampling vLLM runs with reshapes the distribution:
top_p < 1,top_k > 0,min_p > 0,repetition_penalty != 1, or ageneration_kwargsoverride oftemperature. The check reads the effective values, so ageneration_kwargsoverride of any of these counts and an override back to the default does not.Refs #6789. This is direction 4 from that issue, the warning, not the math fix. It forecloses none of the other directions.
Why
vLLM is asked for
processed_logprobs, which are renormalized over the support that survives truncation, while the trainer takes a full-vocab log-softmax.grpo_trainer.py:2680differences the two:so the result is
log p_hf - log p_vllm + log S, whereSis the surviving mass. The first two terms are the train/inference mismatch the correction exists to measure.log Sis not, and it flows straight intovllm_importance_sampling_ratio, which multiplies the per-token loss.Measured on one H100, GRPOTrainer in colocate mode,
trl-internal-testing/small-Qwen2ForCausalLM-2.5,max_completion_length=16. The predicted values were fixed before the run:abs(log S)A second logged quantity confirms the same mechanism independently. With 16 completion tokens the sequence-level correction is
S^16, andsampling/importance_sampling_ratio/meanreads 0.1855 against a predicted 0.185302 attop_p=0.9, and 0.02814 against 0.028147 attop_p=0.8. Both agree to four decimals, which fixes the per-token surviving mass at exactlyS = top_p.At
top_p=0.8that weights a 16-token completion at 2.8 percent of its uncorrected value, with no error and no log line.Why warn rather than fix the math
A correct fix needs the kept-token set the sampler actually used. Both frameworks the issue cites do exactly that: prime-rl #3235 transports kept ids out of vLLM's engine-core worker, and slime v0.3.1 requires
rollout_top_p_token_idswheneverrollout_top_pis not 1.0. vLLM exposes the set only in #49577, which is merged but lands after 0.27.1 and so falls outside the currentvllm>=0.18.0,<=0.27.1pin.Recomputing the mask from training-side logits is possible today but is an approximation, and it has a sharp edge: the sampled token can fall outside the recomputed nucleus, giving
-infand a NaN batch unless it is force-included. I left that for the design discussion on the issue.Scope
The guard is in
GRPOConfig.__post_init__, soGRPOWithReplayBufferConfiginherits it and the gspo_token variant picks it up through the shared config. RLOO is untouched because it requests no vLLM logprobs at all. AsyncGRPO has the same bias with no correction to gate on; #6944 covers it.The gate covers the off-policy mask as well as the correction because the trainer requests vLLM's logprobs unconditionally (
grpo_trainer.py:1117), stores them whenever they arrive (:2931), andget_off_policy_maskreads them wheneveroff_policy_mask_thresholdis set (:3162). Turning the correction off therefore does not remove the bias from the keep/drop decision, and the message no longer suggests it does on its own.Three limits, stated in the code comment rather than checked. A temperature below vLLM's greedy threshold (1e-5 in its
SamplingParams) makes vLLM skip temperature scaling while the trainer still divides, so the cancellation the check relies on breaks there; the constant is vLLM's, so it is not hardcoded here.top_kat or above the vocabulary size is disabled by vLLM but still warns, since the config does not know the vocabulary size. Other sampler-side modifiers passed throughgeneration_kwargs(logit_bias,bad_words,frequency_penalty, ...) reshape the logits the same way and are not checked; thegeneration_kwargsdocstring says so.Verification
should_warn=Truecases fail before the guard, all six pass after. On the previous commit four of the new cases fail (override alone, override back to the default, temperature override, off-policy mask with the correction off) and pass after; the matching-temperature and no-consumer cases pass on both.top_p,top_k, andmin_pconditions, and removing theuse_vllm and correctiongate, each kills a test. The file is byte-identical after restore.ruff0.13.3 check and format clean, version read from.pre-commit-config.yaml.doc-builderat the pinned2430c1ewith--max_len 119clean on both files, withtrl/trainer/sft_trainer.pyas a positive control.Note
Low Risk
Config-time warnings only; no training math or runtime sampling behavior changes beyond alerting users to a known metric bias.
Overview
Adds a
GRPOConfig.__post_init__guard that emits aUserWarningwhen vLLM is used and something actually consumes vLLM’s returned logprobs—vllm_importance_sampling_correctionor a setoff_policy_mask_threshold—while effective sampling settings would reshape those logprobs relative to the trainer’s full-vocab normalization.The check uses merged effective values from config fields and
generation_kwargsoverrides fortop_p,top_k,min_p,repetition_penalty, andtemperature(warns when an override disagrees with the config temperature). Other sampler modifiers ingeneration_kwargsare called out in docs as unchecked but similarly biasing. The warning stays silent when neither consumer is enabled, even under truncated sampling.Tests cover default vs truncated sampling,
generation_kwargsoverrides, off-policy masking without correction, and the no-consumer case.generation_kwargsdocstring is expanded to describe this limitation (refs #6789).Reviewed by Cursor Bugbot for commit d51684c. Bugbot is set up for automated code reviews on this repo. Configure here.