Explainer for a review finding on NVIDIA-NeMo/RL #4124
(paired with NVIDIA-NeMo/Gym #3368).
RL links pin c617a2c; Gym links pin b27e0db.
Nine pages on this PR, one question each. This one: what happens when one prompt group fails.
train_global_batch_size rollouts, or it raises "CC optimizer step requires the full logical sample count" (single_controller.py L3206-3213). It also never re-sends a dropped group, because RL keeps the record of which responses the agent accepted only inside the request that ran it (guide L47). Neither the code nor the guide says why a short step would be wrong. The per-rollout advantage and the token-level loss work for any number of rollouts, so this looks like a limit of the first version, not a math requirement.
Dotted words have a short explanation: hover or tap them.The figure follows one step of 4 prompt groups with 2 rollouts each, and one bad rollout in group g1.
Where the failure happens decides everything. If a model call fails, the agent still finishes and returns a result, and RL masks just that rollout (nemo_gym.py L1114-1120).
If the agent itself raises, Gym answers with an HTTP 500, RL files it as an infrastructure failure, and CC drops the whole group without a retry (rollout_manager.py L2210-2214).
Both ways of handling a dropped group then end the run.
In this PR's built-in client (simple_agent_with_compaction), compaction is not set off by vLLM hitting its limit.
It runs on a schedule. With a context guard set (guards.max_total_tokens, off by default), Gym also counts the prompt before every call and compacts early when it would be too long.
The guard's refusal (a "guard rejection") happens only when compacting early still is not enough. The guard checks the prompt, not the reply: a reply can still run into the limit, and is then cut off (bottom box).
🔴 review comment — context-compaction.md:47
Nothing below is implemented or has been run.
Where the fix lands: RL code and docs (this PR), the Gym agent (Gym #3368), and a follow-up issue for retry. The ordinary path has two safety nets for a failed group: retry it, then train without it. CC turned both off. The fix puts them back, one at a time.
Let a CC step train on the groups it has, the way the ordinary path does. The loss is divided by the token count and the advantage is per rollout, so nothing in the math needs the full count (page 7).
@@ config.py L1437-L1438 · CC config check raise ValueError( "context compaction requires direct routed-expert assembly" ) - if async_config.rollout_failure.min_step_batch_fraction != 1: - raise ValueError("context compaction requires min_step_batch_fraction=1") validate_cc_objective(master_config.grpo, master_config.loss_fn) @@ single_controller.py L3206-L3213 · _train_pump, before finish_train_step if not self._is_ppo: if context_compaction and ( - groups_dispatched != self._algo_cfg.num_prompts_per_step - or len(logical_owners) - != self._master_config.policy["train_global_batch_size"] + # A dropped group shrinks the step, as on the ordinary path. + groups_dispatched != self._target_groups_for_step(version_during_step) + or len(logical_owners) + != groups_dispatched * self._algo_cfg.num_generations_per_prompt ): raise ValueError( - "CC optimizer step requires the full logical sample count" + "CC optimizer step requires every rollout of every kept group" )
_target_groups_for_step already returns the number of groups minus the dropped ones, and still enforces min_step_batch_fraction.
The "gbs" case in test_cc_optimizer_batch.py needs its expected message updated.
Also add one test where a group drops and the step trains with the rest.
With this change, a dropped group in lane B costs one group of data instead of the whole run. That holds once the user sets a drop budget above 0, exactly as on the ordinary path.
simple_agent_with_compaction should catch the guard's refusal, end the episode there, and return what it has, the way lane A already does.
RL then masks that one rollout instead of dropping the whole group.
The ordinary path re-runs a failed group with backoff (rollout_manager.py L2215-2219). CC skips that (L2210-2214), because RL keeps the record of which responses the agent accepted only for that one request. This can land after this PR, but it matters: at moderate scale, timeouts and transport errors are routine. Without retry, each one throws away a group the ordinary path would have recovered.
@@ docs/guides/context-compaction.md L47 · Measurement and lifecycle - Set `async_rl.rollout_failure.nemo_gym.rollout_timeout_s` as a backstop for a hung prompt-group stream; it is not a per-model-call deadline or an acknowledgement that the remote engine stopped. A timeout or transport failure that prevents an agent result drops the group under the existing consecutive-drop budget. This baseline does not redispatch CC groups because selected-response metadata is request-local; checkpoint/retry support is a separate change. Data-integrity errors still raise, and operator cancellation still propagates. + Set `async_rl.rollout_failure.nemo_gym.rollout_timeout_s` as a backstop for a hung prompt-group stream; it is not a per-model-call deadline or an acknowledgement that the remote engine stopped. A timeout, transport failure or agent exception that prevents an agent result drops the group without a retry, and so does a group with no valid owner. A dropped group shrinks that training step, within `max_consecutive_dropped_prompts` and `min_step_batch_fraction`, the same as on the ordinary path; with the default budget of 0, the first drop still stops the run. CC groups are not redispatched because selected-response metadata is request-local; retry is tracked in NVIDIA-NeMo/RL#4557. Data-integrity errors still raise, and operator cancellation still propagates.