Repository navigation
feat(sglang): megatron backend weight refit for sglang rollouts - #3612
Conversation
Kh4L
left a comment
There was a problem hiding this comment.
Self-review (/review-pr-team), per the contributor self-review policy
Ran the four reviewer roles the skill specifies — rl-expert, bug-finder, design-reviewer, test-agent — plus the mandatory devil's-advocate challenge, and the repo's own pre-commit suite.
39 raw findings → 21 CONFIRMED, 8 DOWNGRADED, 4 DISPUTED. The adversarial pass earned its place: it killed a flashinfer-cubin finding by checking PyPI (no 0.6.14 exists, so the removal from the sglang extra was forced, not a divergence), corrected two sets of wrong GPU-hour arithmetic, and upgraded one dependency finding after reading the lockfile.
Already fixed in b5356f0
grpo.pyraisedNameErroron every SGLang setup. It still referencedworker_init_timing_metrics, whichmainreplaced with theSetupTimingMetricsdataclass. It was the only surviving reference in the file, inside theelif backend == "sglang"branch. It survived the rebase precisely because that hunk merged cleanly while its two neighbours conflicted and got fixed. Routed tosetup_timing_metrics.extras, the documented overflow bucket the vLLM sparse transports already use.- A failed refit left every engine paused forever.
begin_weight_update()andinvalidate_kv_cache()sat outside thetrywhosefinallycallscontinue_generation(). Each acquired state now has its own guard, andend_weight_update()only runs for a session that actually opened. - The refit driver-import guard was blind to
from ...workers.megatron_policy_worker import X—ast.ImportFromputs the module onnode.moduleand only the symbol onalias.name. Checked against all four import spellings: the old comprehension caught 2 of 4. sglang_weight_synchronizer.pywas missing frompyrefly.toml, so the largest new module in the PR escaped type-checking.pre-commithygiene the PR shipped red: taplo formatting and three unminimized recipe YAMLs.
Worth stating plainly: the only guaranteed crash was found by pre-commit, not by any of the four agents. That is an argument for running the real linter as part of the review rather than trusting the agent pass alone.
Environment caveats
- No GPU on the review machine, so nothing runtime-dependent was executed; every such finding is marked unverified rather than asserted. A GPU re-run would strengthen the test findings.
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1was not set, so the roles ran without inter-agent messaging. Devil's-advocate verified findings itself instead of interrogating the reporting agent across its usual two challenge rounds.
The five comments below are what survived and is not fixed — left for the reviewer's judgement rather than changed unilaterally.
Generated by Claude Code
b5356f0 to
6cc4448
Compare
|
Addendum to the self-review above — a rationale in this PR's own commit message is wrong, and it hides how big the change is.
Both halves are false against
To be clear about what I am not saying: the deletion itself is correct and is the point of the PR. Replacing the HTTP transport with the IPC/broadcast synchronizers is exactly what this change is for, it is done coherently (no dangling imports — What I am asking for is that the PR stop describing it as removing dead code. A maintainer who reads "it was dead (never constructed)" will not scrutinize what is in fact a swap of the live SGLang weight-transfer transport, and the PR body doesn't mention the swap at all. Anyone currently on SGLang gets a different transport after this merges. Action: reword that paragraph of the commit body to say it replaces the transport rather than removing dead code, and add a line to the PR description noting the behavioural change for existing SGLang users. Found while rebasing the stack onto current Generated by Claude Code |
1d5b657 to
b30bb85
Compare
yuki-97
left a comment
There was a problem hiding this comment.
Re-cut of #3190, so I re-checked all 34 of my review threads there against this tree. This review is the #3190 carry-over only — comments on the new code will follow separately.
Reuse of the vLLM / TRT-LLM transfer functions — settled offline, no action needed. SGLang's refit differs enough that it can't easily reuse them as-is: it runs its own Ray CUDA-IPC NCCL group, needs an HF-weight conversion step and SGL-specific begin/apply/commit hooks, and has different pause / KV-cache / recovery semantics after a partial update. So the three threads asking for reuse (r3689520162, r3689584423, r3689651312) are not carried over here; please treat them as closed.
Carried over from #3190, details inline: 4 unresolved threads that still apply to this tree, and 2 asks that had no natural line to sit on, anchored to the nearest relevant file.
Everything else is either the reuse family above or genuinely addressed here.
b30bb85 to
8cd3a35
Compare
8cd3a35 to
b86aa8e
Compare
yuki-97
left a comment
There was a problem hiding this comment.
Follow-up round on the re-cut. The fix commits stacked on top of the original seven (dffdc88b9 through b86aa8e4b) address 16 of the 20 open threads, which are now resolved.
Still open:
quantizationschema — declaring the TypedDict is good, but see my reply: let's keep.get("scheme", "bf16")rather than addingget_sglang_quantization_scheme.- NCCL_CUMEM_ENABLE placement — see my reply on
megatron/setup.py. - Convergence evidence (#3612 (comment)) — the 2n8g non-colocated nightly is back at 450 steps / 150 min, but the description still says this tree has not been run on hardware. A reward + loss +
token_mult_prob_errorcurve from a longer run would close it.
The rest is inline.
Two things this review does not cover: no CI has run on this tree, and every shipped SGLang recipe is PP=1, so the multi-rank bucket path is not exercised by anything in the suite.
|
/ok to test b86aa8e |
b86aa8e to
f4d36ef
Compare
|
/ok to test f4d36ef |
terrykong
left a comment
There was a problem hiding this comment.
Team review of the SGLang Megatron refit path, run across six agents with an adversarial verification pass (12 candidate findings were dropped as unreachable, pre-existing, or already covered by a test). Reviewed at b86aa8e; swept against the rebased head f4d36ef — comments below say individually what that push already fixed.
On structure — this does follow the weight-synchronizer pattern, and it is the right direction. The PR does not add a parallel mechanism: it replaces HTTPWeightSynchronizer, which was already one. The two new classes subclass the same ABC, are built by create_weight_synchronizer, use the same timer key as all five siblings, mirror IPCWeightSynchronizer's is_stale semantics and buffer sizing, add their tests to the existing test_weight_synchronizer.py, and delete the SGLang isinstance branches that used to live inside refit_policy_generation. Two composition asks remain on the Megatron worker (constructor block, duplicated iterator) — both have small in-PR fixes spelled out inline, with the longer-term shape sketched separately.
Status after the f4d36ef push: the 9 pyrefly errors are fixed and the rebase cleared the staleness gate, but no head of this PR has ever produced test evidence — the only /ok to test died at lint. main has moved again (2 commits) and GitHub currently reports the branch CONFLICTING, so it likely needs one more rebase before a meaningful CI run.
Dependency changes in this PR, at a glance
Verified against uv.lock on this branch vs main (unchanged by the rebase — pyproject.toml is byte-identical between the two heads):
| package | main |
this PR |
|---|---|---|
transformers base range |
>=5.5.0,<5.9.0 |
>=5.5.0,<5.13.0 |
transformers sglang extra |
==5.6.0 |
==5.12.1 |
transformers resolved for the mcore venv |
5.8.1 | 5.12.1 |
transformers resolved for automodel/fsdp |
5.5.0 | 5.5.0 (unchanged) |
sglang |
==0.5.12.post1 (PyPI release) |
git rev 3003d70f, unreleased sglang-miles branch |
sglang-kernel |
0.4.2.post2 | 0.4.5 |
kernels |
>=0.12.0,<0.13 |
>=0.14.1,<0.15 |
flashinfer-python |
0.6.11.post1 | 0.6.14 |
flashinfer-cubin / flashinfer-jit-cache |
0.6.11.post1 | deleted |
llguidance override |
>=1.3.0,<1.4.0 |
>=1.3.0 (cap dropped) |
transformers bump is not contained to sglang. The lock resolves 5.12.1 for the mcore extra on its own, so every Megatron training venv moves 5.8.1 → 5.12.1 — four minor versions — in a PR whose test plan only covers the sglang recipes. The new comment in policy/__init__.py claims "the driver and policy venvs stay on 5.5.x", which the lock contradicts. Resolution: this PR should wait for #3498 to land and rebase on it. That PR (Automodel r0.6.0) makes the same <5.9.0 → <5.13.0 move with an actual transformers-5.12 migration and its own test surface, so the bump arrives already exercised and a transformers regression bisects to one PR instead of two upgrades landing together.
Findings already raised in @yuki-97's open threads (weight_transfer_mode — since deleted outright at f4d36ef — the vLLM pause_generation stubs, and the automodel NCCL_CUMEM_ENABLE guard) are deliberately not duplicated here; our independent checks agreed with all three.
The remaining comments, in severity order: CI status, dependency blast radius, two try/finally placement issues, the ColocatablePolicyInterface regression, setup-time vs refit-time validation, an error-message regression against main, test coverage for the new pure helpers, two stale --deselect lines, the two composition asks on the Megatron worker, and a bundled block of low-severity nits. All were re-verified against f4d36ef; each one still present at that head says so.
Generated by Claude Code
…he shared HF iterator Demonstrates the review asks on NVIDIA-NeMo#3612: 1. Both policy-worker constructors lose their generation-backend branch. Transport state lives in a base-owned per-transport dict created on first use (AbstractPolicyWorker._refit_transport_state), and the torch reductions monkey patch installs inside connect_sglang_rollout_engines -- the same lazy shape as maybe_init_zmq on the ZMQ path. 2. megatron_sglang_weight_iterator.py is deleted. The Megatron SGLang path now composes the same two pieces every other transport uses: _iter_params_with_optional_kv_scales (with include_draft=False; SGLang keeps draft weights engine-side) and the shared iter_named_tensor_buckets packing -- identical in shape to the FSDP2 SGLang path. Also aligns weight-version bookkeeping with the DTensor path (the IPC impl already tracks it in worker_state). Signed-off-by: Terry Kong <terryk@nvidia.com>
f4d36ef to
f322100
Compare
|
/ok to test f322100 |
Signed-off-by: Serge Panev <spanev@nvidia.com>
Signed-off-by: Serge Panev <spanev@nvidia.com>
…he shared HF iterator Demonstrates the review asks on NVIDIA-NeMo#3612: 1. Both policy-worker constructors lose their generation-backend branch. Transport state lives in a base-owned per-transport dict created on first use (AbstractPolicyWorker._refit_transport_state), and the torch reductions monkey patch installs inside connect_sglang_rollout_engines -- the same lazy shape as maybe_init_zmq on the ZMQ path. 2. megatron_sglang_weight_iterator.py is deleted. The Megatron SGLang path now composes the same two pieces every other transport uses: _iter_params_with_optional_kv_scales (with include_draft=False; SGLang keeps draft weights engine-side) and the shared iter_named_tensor_buckets packing -- identical in shape to the FSDP2 SGLang path. Also aligns weight-version bookkeeping with the DTensor path (the IPC impl already tracks it in worker_state). Signed-off-by: Terry Kong <terryk@nvidia.com>
…plicit weight version - _refit_transport_state now uses a plain hasattr guard instead of __dict__.setdefault; identical behavior, readable at a glance. - The colocated path keeps passing weight_version explicitly (now stored in the transport dict), so the demo changes nothing beyond the two review asks. Signed-off-by: Terry Kong <terryk@nvidia.com>
Harden refit cleanup and setup validation, expose the policy interface, clean up lock and subgroup lifecycles, and install matching FlashInfer artifacts. Signed-off-by: Serge Panev <spanev@nvidia.com>
Signed-off-by: Serge Panev <spanev@nvidia.com>
Signed-off-by: Serge Panev <spanev@nvidia.com>
Signed-off-by: Serge Panev <spanev@nvidia.com>
Signed-off-by: Serge Panev <spanev@nvidia.com>
Signed-off-by: Serge Panev <spanev@nvidia.com>
Signed-off-by: Serge Panev <spanev@nvidia.com>
The pinned SGLang weight updater rejects tensor updates outside an explicit begin/end session. Wrap both real CUDA-IPC round-trip streams in that lifecycle so the tests exercise the same engine contract as the production synchronizer. Signed-off-by: Serge Panev <spanev@nvidia.com>
9336ee7 to
3b75899
Compare
|
/ok to test 3b75899 |
Signed-off-by: Yuki Huang <yukih@nvidia.com>
|
/ok to test 09c4efc |
14 upstream commits; four of them produced seven conflicts. Each resolution below. #3612 feat(sglang): megatron backend weight refit for sglang rollouts - weight_sync/factory.py: it rewrote the train_cluster/inference_cluster/ refit_buffer_size_gb docstrings (SGLang owns its own process group, so it needs neither cluster handle). Took its wording and kept our refit_timeout_s entry, which it never saw. refit_timeout_s still reaches NcclReshardWeightSynchronizer and CollectiveWeightSynchronizer; the new SGLang synchronizer does not take it, which is correct -- our watchdog bounds a JOINT communicator and SGLang does not build one. - base_policy_worker.py: it added _refit_transport_state and connect_sglang_rollout_engines at the same insertion point as our stand_down_refit_watchdog. Disjoint additions; kept both. - pyrefly.toml: it swapped http_weight_synchronizer for sglang_weight_synchronizer. Corroborated by the merge deleting http_weight_synchronizer.py outright. #3773 feat(sc): support PPO in single controller - single_controller_utils/setup.py: the SC path is no longer GRPO-only, so it renamed grpo_config to algo_cfg. Kept our nccl_reshard precondition guard and applied the rename to the val-period line inside it; grpo_config no longer appears anywhere. - L1_Functional_Tests_SingleController.sh: it added a ppo_async run_test and padded every non-fast entry to align with "run_test fast". Kept our annotation -- it says which of skip-vs-pass a green lane actually means, which its one-line version does not -- and adopted the alignment, including on our seven recovery entries, so the file does not end up half-converted. - pyrefly.toml: it re-sorted the list, moving vllm_remote_sparse_weight_synchronizer to its correct alphabetical slot. Our side had added membership.py AND held that entry in the old position, so taking our block verbatim would have duplicated it. Kept membership.py only; verified the result is sorted and has no duplicates. #3545 fix(vllm): support native BF16 FlashInfer TRTLLM refit - vllm_backend.py: its _nrl_layerwise_reload_* class attributes landed where our model_update_group declaration is. Disjoint; kept both. - tests/unit/models/generation/test_vllm_backend.py: its layerwise-reload suite against our init_collective release tests plus the _RecordingGroup fixture. Disjoint; kept both. 53 tests collect. #3768 feat: add MOPD to single-controller text path - Touched setup.py alongside #3773; no separate resolution needed. Submodule: the merge advances Megatron-Bridge to d352aced (#3824). Verified the STAGED pointer is upstream's and not our stale 8c46dc42 -- staging the local one is what breaks the fast-forward check and `uv lock --check` together. Gym is untouched by the merge. Verified after resolving: no conflict markers remain, all four lint hooks clean (the one pyrefly error is the pre-existing unrelated transfer_queue import), and 841 unit tests pass across single_controller, refit_watchdog, worker_refit_signatures and weight_sync -- up from 725, because #3773 brings a large new SC suite that passes alongside ours. Signed-off-by: asolergibert <asolergibert@nvidia.com>
- vllm_worker.py/vllm_backend.py: gate the draft module-sharing disable and the strict-cotraining/full-manifest-validation paths on the actual full-vs-partial stream signal (_draft_full_refit config flag / embed_tokens key presence via the renamed _is_full_draft_stream), not on the dspark/dflash method name -- method names alone don't imply a DTensor-v2 full stream, so a future headless Megatron block-drafter using the same method name would otherwise hit a spurious "missing draft keys" refit failure. - integration.py: shift input_ids/teacher_logits (and next_token_position_mask) left by one in Eagle3Runtime.compute_loss so the TTT forward trains h[t] + token[t+1] -> teacher_logits[t+1], matching vLLM's eagle proposer contract (previously trained the easier, misaligned h[t] + token[t] -> teacher_logits[t] task, which would look healthy in the loss curve while degrading refit acceptance). - algorithms/utils.py: move finalize_draft_ratio_metrics out of the nemo_automodel-gated draft/integration.py into this dependency-free module and call it directly (no more try/except ImportError) -- driver processes essentially never have nemo_automodel installed, so this was silently no-op'ing and leaving raw *_num/*_den keys in wandb instead of the finalized ratios. Also anchor the regex to draft_* so it can't rewrite unrelated *_num/*_den metric pairs. - dtensor_policy_worker_v2.py: drop the dead stream_weights_via_http method (its stream_weights_via_http_impl callee was removed from utils.py in NVIDIA-NeMo#3612; this would ImportError the moment anything called it). - policy/__init__.py, lm_policy.py, automodel/setup.py, dtensor_policy_worker_v2.py: drop DEFAULT_DRAFT_ALGO and read policy.draft.algo as a required key once draft is known to be enabled, instead of the forbidden cfg.get(key, MODULE_CONSTANT) pattern at 3 call sites (config-conventions) -- the exemplar YAML already supplies the default for every shipped recipe. Added the missing algo field to ppo_math_1B_megatron_single_controller.yaml's (disabled) draft block for consistency. - automodel/setup.py, dtensor_policy_worker_v2.py: hoist the Eagle3DraftOptions/DSparkDraftOptions imports to module top (both files already import nemo_rl.models.policy there; no circular-import or heavy-dep reason to defer them). - eagle3_llama.py: drop the unused (production-dead, only test-exercised) _kl_div_per_position checkpointed-chunking wrapper and correct the header/docstring claim -- the TTT loop's actual per-chunk compute_logits+KL loop caps the transient peak but, since no chunk is checkpointed, does not reduce the memory retained for backward across the step. - dtensor_policy_worker_v2.py: fix a backwards comment about which path (sequence packing, not dynamic batching) can make microbatch counts differ across DP ranks. - Test coverage: new parametrized table test for _draft_module_sharing_disable_required (all 3 cotrained methods x full/partial stream x load_format), rewrote the KL chunking test against the actual production chunking pattern instead of the deleted wrapper, and moved the ratio-metrics tests to tests/unit/algorithms/ (no longer nemo_automodel-gated). Not addressed here: the lm_policy.py rebase conflict (main replaced the blanket sequence-packing guard with Megatron-scoped guards in Signed-off-by: Yuekai Zhang <zhangyuekai@foxmail.com> NVIDIA-NeMo#3463) and the cross-PR config-schema/nightly-budget discussion with NVIDIA-NeMo#3712/NVIDIA-NeMo#3713, both left for separate follow-up.
Original work: #3190 by @xiuhu17. This is a re-cut opened at his request so the change can complete review. His commits retain their original authorship and
Signed-off-bytrailers.Summary
begin_weight_update/end_weight_updatesessions so derived quantized layouts are finalized once after the last bucket.main; this PR supersedes it with the IPC/NCCL transports.Runtime dependencies
0.5.17.dev30+g3003d70f6from pinned commit3003d70f680d41c59d1b7acbf65cb47795dfd19e0.4.50.6.145.12.1The pinned SGLang revision contains sglang#27948, replacing the local TMS CUDA-graph patch removed here. The matching FlashInfer cubin and JIT-cache wheels are explicit so engine startup does not fall back to compiling kernels.
Dependency versions are isolated per extra: only SGLang uses Transformers
5.12.1and TileLang0.1.11; Megatron/vLLM stay on Transformers5.8.1, and base/non-SGLang extras stay on TileLang0.1.9where the package's x86_64 marker applies. This PR no longer depends on #3498.Review fixes included
ColocatablePolicyInterface.messageerror text.NCCL_CUMEM_ENABLEselection before the first NCCL process group.Validation
Current head:
9336ee7af808556dc884f65462a4d025f50a8f87Local/current-head checks:
uv lock --check: pass.minimize-check.TEST_DRYRUN=1.pytest --collect-onlysucceeds with all 57 weight-synchronizer tests collected./ok to test 9336ee7af.HSG hardware validation used immutable image digest
sha256:3ef02100ebceb15790ad0e1e32728feb3d2d144f8e6da97daf954758acc74891, built by GitLab job411470249from469bd63927650b00fca5ed4399926b11f46baa21.6518683:COMPLETED (0:0), 450/450 training steps and 450/450 successful refits, four SGLang engines, validator passed, mediantoken_mult_prob_error=1.003821.6518682:COMPLETED (0:0), 4/4 training steps and 4/4 successful refits, positive gradients on steps 1–2, minimum 4043 valid tokens, validator passed, mediantoken_mult_prob_error=1.015443.--disable-cuda-graphwas not used.6526299printedPR3612_RUNTIME_VERSIONS_OKfor the immutable image on GB200/ARM64: the SGLang environment has SGLang0.5.17.dev30+g3003d70f6, kernel0.4.5, FlashInfer0.6.14, Transformers5.12.1, TileLang0.1.11, and Torch2.11.0+cu130; the Megatron environment has Transformers5.8.1, Transformer Engine2.15.0+42b84005, and Torch2.11.0+cu130. Megatron's x86_64-only TileLang dependency is correctly absent on ARM64. The wrapper was cancelled after the success marker when container teardown remained stuck, releasing the node.Qwen 50-step window means from the 450-step run:
token_mult_prob_errorThe only change after the hardware-tested commit is
9336ee7af, which replaces the invalidbreakableprefill backend in shipped/test configs withdisabled. That is the same prefill setting used by both successful HSG runs; runtime code and dependencies are unchanged.Stack
This is the base of the linear follow-up stack:
KV-cache preservation parity is tracked separately in #3747.