Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
15074e4
feat(single-controller): pause generation during refit
youngeunkwon0405 Aug 31, 2026
21bd668
Merge remote-tracking branch 'origin/main' into youngeunk/single-cont…
youngeunkwon0405 Aug 31, 2026
1f68285
fix(single-controller): enforce refit cache correctness
youngeunkwon0405 Aug 31, 2026
463f247
refactor(single-controller): simplify refit pause lifecycle
youngeunkwon0405 Aug 31, 2026
df75b8c
merge main into single-controller refit pause
youngeunkwon0405 Sep 1, 2026
c6a082b
docs(sc): remove refit timeout guidance
youngeunkwon0405 Sep 1, 2026
40ca90a
Merge branch 'main' into youngeunk/single-controller-refit-pause
youngeunkwon0405 Sep 1, 2026
ae0ca27
Merge remote-tracking branch 'origin/main' into youngeunk/single-cont…
youngeunkwon0405 Sep 2, 2026
efa7099
Merge remote-tracking branch 'origin/main' into youngeunk/single-cont…
youngeunkwon0405 Sep 9, 2026
13fd140
Merge remote-tracking branch 'origin/main' into youngeunk/single-cont…
youngeunkwon0405 Sep 14, 2026
c877a8e
refactor(single-controller): remove duplicate refit warning
youngeunkwon0405 Sep 14, 2026
558ceab
Bound single-controller generation pause and resume waits
youngeunkwon0405 Sep 14, 2026
d8e713d
Merge main and preserve refit pause through shard re-admission
youngeunkwon0405 Sep 15, 2026
bad0cb1
fix: preserve fleet health across refit pause and resume failures
youngeunkwon0405 Sep 15, 2026
8036c3e
Merge main and collect refit resume regression tests in CI
youngeunkwon0405 Sep 19, 2026
72f82b0
Merge main and resolve vLLM test imports
youngeunkwon0405 Sep 21, 2026
75e9352
chore: merge main and preserve generation control hooks
youngeunkwon0405 Sep 28, 2026
cbf5d3b
chore: merge main and preserve streaming PPO refit docs
youngeunkwon0405 Oct 12, 2026
70f3b39
test: cover pause recovery failures and restore timeout guidance
youngeunkwon0405 Oct 12, 2026
7de9e6d
fix: default async refit KV recomputation on
youngeunkwon0405 Oct 16, 2026
a1677bc
chore: refresh main and minimize inherited recipe defaults
youngeunkwon0405 Oct 16, 2026
a5052e4
test: align colocated Megatron smoke test with KV recomputation
youngeunkwon0405 Oct 16, 2026
d001be0
fix: address async KV refit review regressions
youngeunkwon0405 Oct 17, 2026
9c15e56
test: resolve functional recipe paths from repository root
youngeunkwon0405 Oct 17, 2026
064a49e
test: bind refit shard helper in reshard rebuild fixture
youngeunkwon0405 Oct 18, 2026
61dfb29
chore: merge main to satisfy L1 CI freshness
youngeunkwon0405 Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/guides/async-grpo.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ grpo:
max_trajectory_age_steps: 1 # Maximum age, in training steps, for trajectories
max_generation_failures: 0 # Consecutive worker failures to tolerate
in_flight_weight_updates: false # Enable for faster weight synchronization
recompute_kv_cache_after_weight_updates: false # Invalidates kv cache after weight-updates
recompute_kv_cache_after_weight_updates: true # Recompute KV after weight updates (default)
```

### Complete Example Config
Expand Down Expand Up @@ -72,7 +72,7 @@ grpo:
max_trajectory_age_steps: 1
max_generation_failures: 0 # Consecutive worker failures to tolerate
in_flight_weight_updates: false # Enable for faster weight synchronization
recompute_kv_cache_after_weight_updates: false # Invalidates kv cache after weight-updates
recompute_kv_cache_after_weight_updates: true # Recompute KV after weight updates (default)

cluster:
num_nodes: 2
Expand Down Expand Up @@ -221,9 +221,9 @@ If no `replay_buffer.pt` file is found in the latest checkpoint directory, train

3. **Resource Allocation**: Ensure sufficient GPU memory for both the training and generation clusters

4. **In-Flight Weight Updates**: Enable `in_flight_weight_updates: true` to refit without waiting for the longest in-flight generation to finish. Except for managed Dynamo, the collector requests a generation pause and resume from every async backend around the weight transfer. Async vLLM implements this contract while preserving request state. A backend that does not implement the hook emits a warning once per backend type per process and refits without a collector-side pause or drain; SGLang is in this group today and instead relies on the pause its own weight synchronizer performs around the transfer. Managed Dynamo always drains active trajectories before refit. vLLM requires `async_engine: true`; the Megatron backend is always async-engine.
4. **In-Flight Weight Updates**: Enable `in_flight_weight_updates: true` to refit without waiting for the longest in-flight generation to finish. Except for managed Dynamo, the collector requests a generation pause and resume from every async backend around the weight transfer. Async vLLM implements this contract while preserving request state. SGLang acknowledges these hooks through its weight synchronizer, which pauses, flushes KV, transfers weights, and resumes generation; its configured pause mode determines whether requests are requeued or aborted. A backend that does not implement the hook emits a warning once per backend type per process and refits without a collector-side pause or drain. Managed Dynamo always drains active trajectories before refit. vLLM requires `async_engine: true`; the Megatron backend is always async-engine.

5. **Recompute KV Cache After Weight Updates**: Set `recompute_kv_cache_after_weight_updates: true` to invalidate reusable KV/prefix caches when weights change. On the native async vLLM in-flight path, caches are cleared while generation is paused, so preserved requests recompute their KV after resuming. Other refit paths keep their existing post-update invalidation behavior. When false, in-flight requests retain their pre-update KV cache. On the Megatron generation backend, this must agree with `policy.generation.mcore_generation_config.kv_cache_management_mode`; setup errors on a mismatch.
5. **Recompute KV Cache After Weight Updates**: `recompute_kv_cache_after_weight_updates` defaults to `true` to invalidate reusable KV/prefix caches when weights change. On the native async vLLM in-flight path, caches are cleared while generation is paused, so preserved requests recompute their KV after resuming. This repeats prefill for those requests and can reduce throughput. SGLang always flushes KV inside its weight synchronizer before the transfer, regardless of this flag; on its in-flight path, the collector skips a second flush after generation resumes. Other refit paths keep their existing post-update invalidation behavior. Set the flag to `false` to retain pre-update KV caches and avoid this recomputation cost on backends that support doing so. On the Megatron generation backend, the flag must agree with `policy.generation.mcore_generation_config.kv_cache_management_mode`: use `true` with `recompute`, or explicitly set `false` with `persist`/`offload`; setup errors on a mismatch.

## Why Importance Sampling Correction Is Required for Async

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/ppo.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ ppo:
max_trajectory_age_steps: 1
warmup_generation_lead_steps: null
in_flight_weight_updates: false
recompute_kv_cache_after_weight_updates: false
recompute_kv_cache_after_weight_updates: true # Default; false avoids repeated prefill but retains old KV.
drop_incomplete_targets_on_restore: false

adv_estimator:
Expand Down
6 changes: 4 additions & 2 deletions docs/guides/single-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,9 @@ The SC path splits the async-GRPO loop across a rollout pump and a train pump th
2. **Actor startup**: `SingleControllerActor` launches `_rollout_pump` and `_train_pump` concurrently as asyncio tasks; both share the same `TQReplayBuffer` and `StalenessSampler`.
3. **Rollout pump loop**: `sampler.admit` gates dispatch against the current trainer version (returning a `target_step` for `in_order`); the pump then reserves a buffer slot, drives `RolloutManager.generate_and_push`, and commits with the observed `start_weight` / `end_weight`.
4. **Train pump loop**: `sampler.evict` removes out-of-window groups, and `sampler.select` chooses the next chunk. For PPO, `_value_stage` computes critic values and `_advantage_stage` computes advantages. TQPolicy then updates the policy: full-batch PPO uses `ppo.ppo_epochs` updates, while streaming PPO accumulates gradients across chunks for one update. Streaming PPO refits the updated policy before `_value_train_epochs` trains the critic on the assembled full batch for `ppo.critic_ppo_epochs`. GRPO uses one TQPolicy optimizer step per RL step.
5. **Weight sync**: after each optimizer step the pump bumps the trainer version, clears rollout permission, calls the weight synchronizer, and re-opens the rollout pump for the next version. In streaming PPO, it does the weight sync as soon as the policy training is finished, which is before the critic model training starts. This allows new batch generation with the updated policy weight overlapping with the critic training epochs.
5. **Weight sync**: after each optimizer step the pump bumps the trainer version, clears rollout permission, asks the generation backend to pause in-flight work, calls the weight synchronizer, resumes generation, and re-opens the rollout pump for the next version. vLLM's native pause preserves active request state; backends without pause support warn and retain their existing refit behavior. In streaming PPO, it does the weight sync as soon as the policy training is finished, which is before the critic model training starts. This allows new batch generation with the updated policy weight overlapping with the critic training epochs.
Comment thread
youngeunkwon0405 marked this conversation as resolved.

For non-colocated vLLM, freezing a request does not stop its end-to-end generation timeout. Size `NRL_VLLM_ASYNC_TIMEOUT_SECONDS` and, when the generation router is enabled, `async_rl.generation_router.backend_timeout_s` to cover the longest expected generation plus time spent paused for refit, including membership recovery and a possible retry. With a finite `async_rl.generation_fleet_health.refit_timeout_s`, pause, each transfer attempt, and resume each use that timeout plus a 60 s grace; these are separate call budgets, not a single total refit budget. When `refit_timeout_s` is `null`, pause and resume retain a 360 s budget each, but the collective transfer wait is unbounded, so no finite worst-case total pause duration is guaranteed.

## Relation to Legacy Async GRPO

Expand All @@ -449,7 +451,7 @@ Do not carry `max_num_epochs: -1` across either. [ppo.md](./ppo.md#asynchronous-
| `enabled: true` | Implicit — SC is always async; use `sampler.max_lookahead_versions: 0` for sync semantics, `>= 1` for async |
| `max_trajectory_age_steps: N` | `sampler.name: in_order` with `sampler.max_lookahead_versions: N` |
| `warmup_generation_lead_steps` (PPO) | `sampler.warmup_lookahead_versions` — the lookahead to use while critic warmup is in progress |
| `recompute_kv_cache_after_weight_updates` | `recompute_kv_cache_after_weight_updates` (same) |
| `recompute_kv_cache_after_weight_updates` | `recompute_kv_cache_after_weight_updates` (same, defaults to `true` in both loops); when vLLM pauses natively, preserved requests recompute their KV cache after refit, repeating prefill. Set `false` to retain pre-update KV. For Megatron generation, pair `true` with `kv_cache_management_mode: recompute`, or `false` with `persist`/`offload`. |
| `in_flight_weight_updates` | Always effectively true; `false`-equivalent behavior is not yet supported (drain-gate tracked in [issue #2625](https://github.com/NVIDIA-NeMo/RL/issues/2625)) |
Comment thread
youngeunkwon0405 marked this conversation as resolved.
| *(no legacy equivalent — matches legacy full-batch train semantics)* | `min_groups_for_streaming_train: ${grpo.num_prompts_per_step}`, or `${ppo.num_prompts_per_step}` on a PPO run |
| *(no legacy equivalent — matches legacy `max_trajectory_age + 1` batches in flight)* | `max_inflight_prompts: num_prompts_per_step × (max_lookahead_versions + 1)` |
Expand Down
4 changes: 2 additions & 2 deletions examples/configs/grpo_math_1B.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ grpo:
# when transient generation errors are expected and acceptable to drop.
max_generation_failures: 0
in_flight_weight_updates: false # Set to true to enable in-flight weight updates
recompute_kv_cache_after_weight_updates: false # Set to true to recompute kv cache after weight updates.
recompute_kv_cache_after_weight_updates: true # Set false to retain pre-update KV caches (avoids repeated prefill).
Comment thread
youngeunkwon0405 marked this conversation as resolved.

# Reward-zeroing penalties applied to NeMo-Gym rollout results.
reward_penalties:
Expand Down Expand Up @@ -506,7 +506,7 @@ policy:
prefix_cache_ttl_seconds: 300.0 # MCore default coordinator cache-entry lifetime.
prefix_caching_routing_alpha: 1.0 # MCore default load penalty for prefix-affinity routing.
max_tokens: 16384 # Maximum number of tokens to use in a single step. Analogous to vllm's max_num_batched_tokens
kv_cache_management_mode: "persist" # KV cache lifecycle across suspend/resume. Options: "persist", "offload", "recompute".
kv_cache_management_mode: "recompute" # Must agree with recompute_kv_cache_after_weight_updates. Options: "persist", "offload", "recompute".
Comment thread
youngeunkwon0405 marked this conversation as resolved.
async_sched_mode: "async" # Dynamic-batching scheduler mode. Options: "legacy", "async". "async" hides scheduler CPU time behind GPU compute.
materialize_only_last_token_logits: true
num_speculative_tokens: 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async_rl:
# How far generation may run ahead of the trainer.
max_lookahead_versions: 1
# Recompute generation KV caches after each weight update.
recompute_kv_cache_after_weight_updates: false
recompute_kv_cache_after_weight_updates: true
# Min ready groups the streaming trainer waits for before dispatching a batch.
min_groups_for_streaming_train: ${grpo.num_prompts_per_step}
# Cap on in-flight generate_and_push calls in the rollout pump.
Expand Down
2 changes: 1 addition & 1 deletion examples/configs/ppo_math_1B.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ ppo:
# Requires policy_training_start_step > 0. null uses max_trajectory_age_steps.
warmup_generation_lead_steps: null
in_flight_weight_updates: false
recompute_kv_cache_after_weight_updates: false
recompute_kv_cache_after_weight_updates: true
# true discards partial restored rows and fills from subsequent prompts.
drop_incomplete_targets_on_restore: false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async_rl:
# throughout. Buffer capacity below must cover the wider of the two.
warmup_lookahead_versions: null
# Recompute generation KV caches after each weight update.
recompute_kv_cache_after_weight_updates: false
recompute_kv_cache_after_weight_updates: true
# Full-batch default. Reduce this to stream forwards, GAE, and policy backward;
# requires ppo_epochs=1. normalize_advantages=true then normalizes per chunk.
# Value training stays full batch.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ policy:
backend: megatron
refit_transport: mcore
mcore_generation_config:
kv_cache_management_mode: recompute
refit_backend: nccl
tensor_model_parallel_size: 4
expert_model_parallel_size: 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ policy:
backend: megatron
refit_transport: mcore
mcore_generation_config:
kv_cache_management_mode: recompute
refit_backend: nccl
expert_model_parallel_size: 4
colocated:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ policy:
refit_transport: mcore
mcore_generation_config:
refit_backend: nccl
kv_cache_management_mode: recompute
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ policy:
refit_transport: mcore
mcore_generation_config:
refit_backend: nccl
kv_cache_management_mode: recompute
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ cluster:
async_rl:
sampler:
warmup_lookahead_versions: 2
recompute_kv_cache_after_weight_updates: true
max_inflight_prompts: ${mul:${ppo.num_prompts_per_step}, 3}
max_buffered_rollouts: ${mul:${ppo.num_prompts_per_step}, 3}
checkpointing:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ grpo:
async_grpo:
enabled: true
in_flight_weight_updates: true
recompute_kv_cache_after_weight_updates: false
policy:
model_name: nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-BF16
is_vlm: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async_rl:
sampler:
name: in_order
max_lookahead_versions: 1
recompute_kv_cache_after_weight_updates: false
recompute_kv_cache_after_weight_updates: true
min_groups_for_streaming_train: ${grpo.num_prompts_per_step}
max_inflight_prompts: ${mul:${grpo.num_prompts_per_step}, 2}
max_buffered_rollouts: ${mul:${grpo.num_prompts_per_step}, 2}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ policy:
backend: "megatron"
refit_transport: "mcore"
mcore_generation_config:
kv_cache_management_mode: recompute
refit_backend: nccl
# NeMo-Gym drives rollouts through the engine's OpenAI server.
expose_http_server: true
Expand Down
18 changes: 10 additions & 8 deletions nemo_rl/algorithms/grpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ class AsyncGRPOConfig(BaseModel, extra="allow"):
# without waiting for the pending generations to finish.
in_flight_weight_updates: bool = False
# Recomputes the KV cache after weight updates.
recompute_kv_cache_after_weight_updates: bool = False
recompute_kv_cache_after_weight_updates: bool = True
Comment thread
youngeunkwon0405 marked this conversation as resolved.


class RewardPenaltyTokenIdsConfig(BaseModel, extra="allow"):
Expand Down Expand Up @@ -1352,13 +1352,15 @@ def _spinup_nemo_gym(base_urls, model_name, policy_api_key=None):
policy_config["megatron_cfg"]["train_iters"] = total_train_iters

# Megatron generation expresses recompute-after-refit engine-side via
# `kv_cache_management_mode="recompute"`; the loop-level flag must agree.
if generation_config["backend"] == "megatron":
async_grpo_config = grpo_config.async_grpo
recompute_kv_cache = bool(
async_grpo_config is not None
and async_grpo_config.recompute_kv_cache_after_weight_updates
)
# `kv_cache_management_mode="recompute"`; the async loop's flag must agree.
# Synchronous GRPO does not consume this flag.
async_grpo_config = grpo_config.async_grpo
if (
generation_config["backend"] == "megatron"
and async_grpo_config is not None
and async_grpo_config.enabled
):
recompute_kv_cache = async_grpo_config.recompute_kv_cache_after_weight_updates
kv_cache_mode = generation_config["mcore_generation_config"][
"kv_cache_management_mode"
]
Expand Down
2 changes: 1 addition & 1 deletion nemo_rl/algorithms/ppo.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class AsyncPPOConfig(BaseModel, extra="allow"):
# Allows weight updates while rollout requests are still in flight.
in_flight_weight_updates: bool = False
# Recomputes the KV cache after weight updates.
recompute_kv_cache_after_weight_updates: bool = False
recompute_kv_cache_after_weight_updates: bool = True
# Drops partial restored targets; replacement rollouts use subsequent prompts.
drop_incomplete_targets_on_restore: bool = False

Expand Down
Loading
Loading