Repository navigation
feat: pause SingleController refits and enable KV recomputation by default - #3929
youngeunkwon0405 wants to merge 25 commits into
Conversation
|
/ok to test e16a857 |
|
Real-GPU validation completed at exact PR head I ran the full-size native SingleController vLLM GRPO recipe (
The only traceback in each log was a post-success W&B service |
e16a857 to
daa045d
Compare
|
/ok to test daa045d |
|
Addressed the The failure was PR-caused but test-only: Validation:
|
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
daa045d to
15074e4
Compare
|
Addressed the remaining The failure was PR-caused but test-only: Validation:
|
|
/ok to test 15074e4 |
youngeunkwon0405
left a comment
There was a problem hiding this comment.
Team review: 4 agents (rl-expert, test-agent, bug-finder, devil's advocate) analyzed this PR; every finding below was independently re-verified against source, including the pinned vLLM 0.25.1 wheel.
Verified clean (no action): clear_cache-at-pause genuinely subsumes post-refit invalidation (vLLM keep-mode pause_scheduler resets caches at pause); _refit_leader_workers() targets exactly the set the old run_rank_0_only_axes dispatch selected (incl. pp>1) and matches the existing refit dispatches; interface default hooks keep unsupported backends safe; fail-stop resume semantics mirror trajectory_collector; removing the drain-gate TODO is consistent (docs + issue #2625 still track it). Ruff 0.9.9 check+format clean. The A/B W&B evidence (preserve vs recompute, 11 pause/refit/resume cycles each) is solid.
Findings: 1 hardening suggestion, 1 docs note, 2 optional test suggestions (both verified passing locally on CPU) — all inline.
Generated by Claude Code
…roller-refit-pause Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test 1f68285 |
|
Post-review validation for exact head 1f68285:
Claude review: all four threads were addressed in 1f68285, replied to, and resolved: bounded lifecycle calls, timeout-budget documentation, recovery ordering coverage, and pause-failure coverage. Exact-head CI was retriggered; branch-up-to-date, lint, docs, and script-coverage checks are passing, with container builds still running. |
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test 463f247 |
|
Simplified the post-review implementation at exact head
Validation at this exact head:
The only post-success traceback was the same W&B service |
|
/ok to test 463f247 |
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test df75b8c |
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test cbf5d3b |
mehraakash
left a comment
There was a problem hiding this comment.
Review (review-pr-team): PR #3929 at cbf5d3be9
2 action items, neither blocking, posted inline: a docs regression and two test gaps (with verified tests). Six agents (RL expert, bug finder, test reviewer, design reviewer, comment reviewer, devil's advocate) re-verified every finding against head and against the pinned Ray 2.58.0 / vLLM 0.29.0 sources.
Both earlier blocking items are resolved at head. A pause-origin RefitAborted no longer marks survivors partial (refit_started gates it, pinned by the ledger snapshot in test_failed_pause_recovers_and_pauses_survivors_before_refit), and a confirmed ActorDiedError on one leader at resume is recorded and the run continues (pinned at both the vLLM and controller level). All other "Addressed in" threads verify fixed. The reconcile_communicator missing-argument crash from the September GPU runs is fixed on main by #4233 and is in this head.
Fault-injection results on this head, 4×GB300, 2 train + 2 generation shards, matched against main at the merge-base 9c8778b14:
| Variant | main @ merge-base |
PR head |
|---|---|---|
step-boundary kill, nccl_reshard |
PASS 24/24 steps | PASS 24/24 steps; 25 pause / 25 resume requests across the rebuild |
step-boundary kill, nccl |
FAIL: NCCLError: unhandled system error in MegatronPolicyWorker.init_collective during the rebuild |
FAIL, identical traceback |
kill during refit, nccl |
not run | FAIL, same rebuild error |
restart dead shards, nccl |
not run | FAIL, same rebuild error |
The nccl collective-transport recovery failure reproduces on main with the same traceback, so it is pre-existing on this platform and not introduced here. It is worth tracking separately. The reshard transport recovers with the pause/resume lifecycle in place.
Unit tests at head on the same node: 288 passed in the SingleController bucket; 11/12 in the vLLM bucket (the one failure is test_vllm_policy_generation_with_fastokens_enabled, an nixl_ep ABI import error in the test environment, unrelated to this PR); the two suggested tests pass.
PR description nits (no code change): it cites Ray 2.56.1 as the lockfile version, but the lock at head pins Ray 2.58.0 and vLLM 0.29.0; and the sentence saying the None-reduction in invalidate_kv_cache was fixed does not match the diff, which leaves that function untouched (the async path simply no longer calls it).
Still open from earlier threads, not re-raised: the resume-timeout design discussion with @asolergi-nv, a KILL_DURING_PAUSE functional variant, and setup-time validation of backend_timeout_s against the pause budget.
Generated by Claude Code
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test 70f3b39 |
|
Could we gate pause/resume behind an |
I would like to double check about this with you. I personally think not pausing during refit is a bug and this PR is sort of a bug fix. Do you prefer to make it configurable? |
|
1 action item. TL;DR — now that Before this PR the flag was only a prefix-cache reset that could leave running-request KV untouched, so a stale-KV default was the safe choice. With native keep-mode pause plus Flipping it touches three defaults that should move together so the two loops agree:
Two things to handle alongside it:
If you would rather keep this PR to the pause mechanics and do the default flip in a follow-up, say so here and I will open an issue to track it. |
|
/ok to test 0f623c1 |
|
/ok to test 2505b20 |
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
2505b20 to
7de9e6d
Compare
|
/ok to test 7de9e6d |
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test a1677bc |
|
Hi @mehraakash, I updated the PR based on your comment. Mostly just changing default setting of recompute_kv_cache to true.
|
|
Hi @terrykong, @mehraakash asked me to ask for the final approval from you. Thanks! |
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test a5052e4 |
terrykong
left a comment
There was a problem hiding this comment.
Thanks for this. The pause/clear fix is right and needed: before it, recompute_kv_cache_after_weight_updates: true on the single controller silently did nothing, because vLLM refuses a prefix-cache reset while requests hold blocks. Two blockers come from the default flip in 7de9e6d, not from the pause code: 36 failing unit tests in test_checkpointing.py, and the GB200 L1 video script that now fails at setup (comment on grpo.py#L299). Please fix both and re-run CI. The green CI in the description is from before the flip.
All three of my earlier comments are fixed at a5052e4: the pause is bounded and inside the recovery try, the Gym test axis has a real assertion, and the drain-gate TODO is back. Reusing the shared pause/resume hooks from your vLLM pause PR (#3839) and the refit_started flag are clean.
Behavior change. The title and description never mention the default flip. Please add a "Behavior change" section that says:
- the default is now
truefor legacy async GRPO/PPO and for the single controller; - Megatron generation needs
kv_cache_management_mode: recompute, or an explicitfalsewithpersist/offload; - how to opt out;
- the sync Megatron-generation nightlies move to
recompute; - TRT-LLM recipes pass
recomputethrough interpolation.
Consider widening the title scope to match.
Numbers. Please put median step time and generation tokens/s from the linked preserve vs recompute runs in the description. Also run the H100 nightly grpo-llama3.2-1b-instruct-1n8g-megatron_generation on this head; it gates median step time < 12 s (L41).
Router. Pause → refit → resume works with async_rl.generation_router.enabled, and the new docs paragraph (single-controller.md#L427) already covers sizing backend_timeout_s for the pause. Please run the full-mode L1 SingleController lane that turns the router on (L1_Functional_Tests_SingleController_2.sh#L52-L54) on this head before merge: Lfast skips it, and recompute is now on by default.
Not for this PR. Two router issues (a restarted engine never rejoins the router; router timeouts during a hung refit can mark a healthy shard as suspect) will be filed separately. Keeping running requests' KV while stopping new requests from reusing old prefix blocks (a cache_salt per weight version) is also a separate follow-up. No separate pause flag is needed: pausing during refit is the fix, and recompute_kv_cache_after_weight_updates stays the one setting, now default true, as agreed.
Overlapping PRs. #4018 and #4647 touch the same flag and reset path; they will be asked to rebase on this one.
Generated by Claude Code
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test d001be0 |
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test 9c15e56 |
Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
|
/ok to test 064a49e |
Summary
Implementing the same feature as #3839 for the single controller path.
recompute_kv_cache_after_weight_updateseffective for active vLLM requests by clearing their KV state during native pause; the previous post-refit prefix-cache reset could leave running-request KV untouched while reporting successBehavior change
This PR also enables KV-cache recomputation by default, beyond the SingleController pause/resume lifecycle:
grpo.async_grpo.recompute_kv_cache_after_weight_updates(legacy async GRPO),ppo.async_ppo.recompute_kv_cache_after_weight_updates(legacy async PPO), andasync_rl.recompute_kv_cache_after_weight_updates(SingleController) now default totrue. For vLLM's native async path this clears paused requests' KV, repeating prefill after refit; it can reduce throughput.false. For legacy async GRPO with Megatron generation and for SingleController, pairtruewithpolicy.generation.mcore_generation_config.kv_cache_management_mode: recompute, orfalsewithpersist/offload. Setup rejects inconsistent pairs.kv_cache_management_mode: recomputeinstead ofpersist; explicitly selectingpersistoroffloadremains valid.falsepropagates the opt-out.true/recompute.Implementation
SingleControllerActor._sync_weights()continues to close the rollout-dispatch gate and cancel sampler-stale native requests first. After refit membership reconciliation and optional FP8 KV-scale calibration, it asks the generation backend to pause throughpause_generation_for_refit(clear_cache=...), runs the existingWeightSynchronizerrefit (including its one-shot recovery retry), and callsresume_generation_after_refit()before reopening dispatch.SingleController passes the existing refit timeout-plus-grace budget to pause and resume, with a 360-second fallback when
refit_timeout_s=None. The collective's existing disabled-watchdog behavior and_sync_weights_withinhelper/docstring remain unchanged. Pause and refit failures enter the existing one-shot fleet recovery, but survivors are marked as holding partial weights only if a transfer actually started. After rebuilding membership, the controller pauses every retry participant, including restarted engines, before transferring weights again.Resume waits for all leader RPCs under one shared timeout. With fleet health enabled, confirmed
ActorDiedErrorfailures remove only the dead engines; dispatch reopens if enough survivors successfully resumed. A late error from an old actor does not condemn its replacement. Timeouts, temporary actor unavailability, false acknowledgements, and exhausted fleets still stop the run with dispatch closed. Resume never retries the weight transfer.vLLM preserves active request state with native keep-mode pause. With
clear_cache=True, vLLM clears the KV state owned by those paused requests so they recompute it after refit. This fixes a pre-existing correctness gap: the former asyncreset_prefix_cachepath did not clear KV blocks held by running requests, and itsNoneworker results could still be reduced to success. This PR does not change theNone-reduction ininvalidate_kv_cache; the native async pause path instead no longer calls that function. Backends without native pause keep their existing in-flight refit behavior and receive the shared warning; when recompute is requested, they must confirm equivalent engine-side invalidation or the controller raisesNotImplementedErrorinstead of silently claiming recomputation.The lifecycle is in the shared SingleController
_sync_weights()path, so it applies to GRPO and PPO and to native and NeMo-Gym rollouts.Historical performance snapshot
For the previously linked Llama 3.1 8B SingleController runs at
e16a857503c0843df20d3b58f737be6bcde6bf76(not the current head), the mediantiming/train/total_step_timeover optimizer steps 1–10 was:Recompute was 3.16% slower by this median. This includes step 1 and is one historical pair, not a steady-state estimate, statistical result, or current-head performance claim. These runs did not record generation-tokens/s, so that requested metric cannot be supplied from their history. The logged valid-token throughput includes training behavior and must not be relabeled as generation throughput.
Validation
Current review fixes (
d001be04e610f3a1b2d5c5900606cb8d7fde1114):--noconftest, not the repository GPU test environment. Existing dependency/marker/timeout-thread warnings and mock-object shutdown messages remain; no GPU engine or distributed training runtime is validated by these CPU checks.git diff --checkpassed.Historical validation
The following evidence is tied to its stated revisions and does not establish current-head CI, performance, or GPU recovery readiness.
Review follow-up (
70f3b3967c8026863b4ffe4bd108a5803f7a1d45):refit_timeout_s=null.cbf5d3be97d3e64f4d2a03585c61a0137d9f67adpassed: 63 successful jobs, 9 skipped.cbf5d3be97d3e64f4d2a03585c61a0137d9f67adagainst main9c8778b1404e84c64bc73f671427234ba304e41c:nccl_reshardstep-boundary kill passed 24/24 on both;ncclstep-boundary recovery failed with the same rebuild traceback on both. PR-only kill-during-refit and restart variants also failed with that rebuild error. These are the reviewer's reported results, not independently rerun here. They do not replace the unresolved historical manual GPU evidence or constitute a complete nine-variant suite on the follow-up SHA.Review fixes (
bad0cb1691e9e20f0130219ddaee22b76a7a07b4):git diff --checkpass.RefitAbortedwithin its deadline. The controller/backend methods were source-extracted; no vLLM engine or GPU was exercised. The first attempt stopped during automatic uv workspace packaging; disabling that local-only packaging allowed the test to run.Previous merge (
d8e713dbbf6ba3d08f481c53d25e0a3e9fff41f1):mainatfeff70f5b6239d90dc703ae79b973ea809550732, including feat(sc): restart dead generation shards and re-admit them at the next refit #3592. Preserved participant-aware refit completion and shard re-admission. Recovery retries now re-pause the rebuilt fleet, covering both returning shards and replacement engines that reuse a shard index.18608218failed during environment setup (/opt/nemo_rl_venv/bin/pythonwas absent from the selected image), before pytest started. It provides no runtime validation.Earlier validation:
git diff --checkpassed17015948innemo_rl.0825.sqsh: 92 focused tests passedtest_single_controller_actor.pytest_refit_recovery.py2134in the same image/account ran the fulltest_checkpointing.py: 62 passedgen_handle=None, CW-DFW Slurm job17510464in the same image/account ran the fulltest_train_pump_e2e.pyunder--hf-gated --mcore-onlywith a forced worker-environment rebuild: 1 passed, 66 warnings in 123.19s; SlurmCOMPLETED/0:0in 6:27e16a857503c0843df20d3b58f737be6bcde6bf76. These runs predate the subsequent merges frommainand do not validate the current PR head.grpo-llama3.1-8b-instruct-2n8g-async-1off-single-controller-streaming2.yamlworkload: 2 nodes / 16 H100 GPUs, 64 prompts x 32 generations, global batch size 2048, max sequence length 409617016712,COMPLETED/0:0, 10 optimizer steps and 11 successful pause/refit/resume cycles; W&B17016711,COMPLETED/0:0, 10 optimizer steps and 11 successful pause/refit/resume cycles; W&B