fix(vllm): support native BF16 FlashInfer TRTLLM refit - #3545
Conversation
Signed-off-by: seonjinn <sna@nvidia.com> (cherry picked from commit 707968a)
Signed-off-by: seonjinn <sna@nvidia.com> (cherry picked from commit cbd4138)
Signed-off-by: seonjinn <sna@nvidia.com> (cherry picked from commit bda8858)
Signed-off-by: seonjinn <sna@nvidia.com> (cherry picked from commit 2aa1570)
Signed-off-by: seonjinn <sna@nvidia.com> (cherry picked from commit 4aece4b)
Signed-off-by: seonjinn <sna@nvidia.com> (cherry picked from commit 1292dbe)
Signed-off-by: seonjinn <sna@nvidia.com> (cherry picked from commit 56e27a8)
Signed-off-by: seonjinn <sna@nvidia.com> (cherry picked from commit 0c33c30)
…e-refit-pr-20260807 Signed-off-by: seonjinn <sna@nvidia.com>
Signed-off-by: seonjinn <sna@nvidia.com>
…e-refit-pr-20260807 Signed-off-by: seonjinn <sna@nvidia.com>
Signed-off-by: seonjinn <sna@nvidia.com>
|
/ok to test a73421e |
Signed-off-by: seonjinn <sna@nvidia.com>
|
/ok to test 7180bff |
yuki-97
left a comment
There was a problem hiding this comment.
hi @youngeunkwon0405 , could you help review this PR? thanks!
…e-refit-pr-20260807
Signed-off-by: seonjinn <sna@nvidia.com>
|
/ok to test 95452e9 |
@seonjinn, there was an error processing your request: See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/2/ |
|
/ok to test 95452e9 |
|
Self-review completed with
Local validation completed:
The full vLLM unit suite is left to CI because the local host cannot create the repository Linux-only locked vLLM environment. Follow-up PRs #3659 and #3669 should be restacked after this PR lands. |
Signed-off-by: seonjinn <sna@nvidia.com>
youngeunkwon0405
left a comment
There was a problem hiding this comment.
Team review (5 agents: RL-code, vLLM-API, tests, bug-finder, devil's advocate; every vLLM claim verified against the pinned v0.25.1 wheel source).
This is a well-built change — the lifecycle design, the realized-backend gating (including auto), the failure latching, and especially the buffer-overwrite regression test all held up under adversarial verification. The detach-necessity claim, the FlashInfer TRTLLM finalize semantics, and the HPC-pass gap were each confirmed in vLLM source. The earlier FP8-KV-cache concern was re-checked and is indeed covered: the layerwise finalizer re-runs layer.process_weights_after_loading(model_config.dtype) and reloads streamed k/v scales, equivalent to the skipped generic pass.
6 inline comments: 1 confirmed concurrency bug on the collective path (cross-stream RAW race introduced by the deferral+detach), 1 fail-loud gap (checkpoint-engine/sparse-delta transports), 1 upstream-issue suggestion, 1 test gap, 1 lint failure (repo-pinned ruff 0.9.9), 1 docstring nit.
Lower-confidence notes, not requesting changes:
- The detach helper relies on private
reload.layerwiseinternals (LayerReloadingInfo.loaded_weightsshape, theloaded_weightarg name); imports are function-local so older vLLM fails loudly only when the feature runs. A version-pinning comment would help future upgrades. except Exceptionin_weight_update_lifecyclemissesBaseException(e.g. a cancelled refit): the worker isn't marked unusable and the next refit would silently merge stale buffered clones (vLLM skips still-initialized layers). Low reachability;except BaseExceptioncloses it if you think it's worth it.- Question: does BF16-TRTLLM +
nccl_reshardfail loudly today, or run the generic finalize silently until #3659 lands? Our read is the reshard mapping most plausibly errors at prepare time on the 4-D block layout, but a confirmation would help. - Given the per-refit init/detach/finalize overhead, one before/after refit-latency number in the description would be a nice-to-have (perf work itself is understood to be #3669).
Generated by Claude Code
youngeunkwon0405
left a comment
There was a problem hiding this comment.
I did a thorough review of this PR. The logic is hard to understand, but I think the problem itself is just complex. I think the implementation functionally makes sense and is reasonable. Please address the comments. Thank you!
|
@guyueh1 I studied this PR today and learned that this is also using the layer-wise reload feature of vLLM, which is I didn't realize before. I am adding you because you have been working on the reload PR. Maybe you might also want to check an overlap with your PR? I will also review your PR soon. |
…e path Signed-off-by: seonjinn <sna@nvidia.com>
Thank you so much for your thorugh review Youngeun, I'll try to fix codes for clear understanding. |
Signed-off-by: seonjinn <sna@nvidia.com>
|
/ok to test 86c94f1 |
|
Superseding my 08-18 comment ("close in favor of #3659"): that had the dependency direction backwards — #3659 explicitly extends this PR's native layerwise refit to the On the overlap question with @guyueh1's #3651 (native |
seonjinn
left a comment
There was a problem hiding this comment.
Self-review summary (supplemental to youngeunkwon0405's review, whose 8 comments were addressed in e81aba7 with per-thread replies).
Method: five review agents over the post-fix diff, then an adversarial verification pass that re-checked every citation, verified vLLM internals against a real vLLM tree, and re-ran CI queries live. No inline code findings survived adversarial verification -- the lifecycle state machine (fail-closed on every failure window including finalize), the transport-buffer detach, the single-buffer race fix, and the mock fidelity of the new tests were all confirmed sound. Two follow-up fixes were pushed as 86c94f1: a version-guard on the fused_moe.oracle import (on vLLM builds without that module layout, every unquantized-model refit would have raised ImportError instead of skipping the TRTLLM check) and a missing num_buffers kwarg on a pre-existing test fake that would have failed the vllm-gated test lane.
Merge gates (process, not code):
- CI has not run on any recent head. The last /ok to test targeted f0c23ae (08-18), fourteen commits and a 169-file upstream merge ago. A fresh /ok to test on the final head is required; posted above.
- PR body needs a refresh. Three statements are stale relative to the code: "Checkpoint-engine transports are unchanged" (they are now rejected for the unquantized TRTLLM backend, by design); the CI claim references a stale SHA; and the 48-refit smoke predates the cross-stream race fix -- a clean smoke was consistent with the race being present (narrow-window silent corruption), so it should be cited as evidence for the lifecycle, not for stream-safety. Worth adding while editing: the native path now runs the collective consumer single-buffer, trading broadcast/load overlap for stream-safety on this backend only.
- Sibling-PR order is clarified in the comment above: #3545 -> #3659 (nccl_reshard extension) -> #3669 (perf), with #3651 (opt-in reload_weights API) complementary and rebased on top.
Signed-off-by: seonjinn <sna@nvidia.com>
Signed-off-by: seonjinn <sna@nvidia.com>
Signed-off-by: seonjinn <sna@nvidia.com>
Signed-off-by: seonjinn <sna@nvidia.com>
Signed-off-by: seonjinn <sna@nvidia.com>
|
/ok to test e450256 |
|
/ok to test 87e9e98 |
Adapt to the NVIDIA-NeMo#3545 API merged on main (renamed validator/refresh helpers, transport-aware native layerwise refit) and fold in review fixes: expert-count divisibility guard for the staged grouped-expert path, loaded-name verification after staged expert loads, fail-closed expert placement detection, and updated unit tests. Signed-off-by: seonjinn <sna@nvidia.com>
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>
TL;DR for reviewers
FlashInfer TRTLLM repacks BF16 MoE expert weights into a backend-private layout at load time, so NeMo-RL's generic refit (write canonical tensors into live params, run generic post-processing) silently corrupts the runtime weights on this backend. This PR makes the affected refit paths follow vLLM's native layerwise reload lifecycle instead. The three things to review: (1) the lifecycle context manager and its fail-closed semantics in
vllm_backend.py; (2) the transport-buffer detach that keeps deferred reload tensors alive after NeMo-RL reuses NCCL/IPC buffers; (3) the single-buffer collective path that removes a cross-stream read-after-write hazard.Problem
FlashInfer TRTLLM does not execute incoming BF16 expert weights in their original layout — vLLM repacks them into backend-specific runtime storage at model load. Two failure modes follow from the generic refit path:
weight_loadercalls until finalization, and those deferred calls hold references into NCCL/IPC transport buffers that NeMo-RL reuses for the next transfer — so by the time they replay, the data may have been overwritten.How the fix works
For this backend only, each refit follows vLLM's native lifecycle:
Backend detection inspects the realized model, so
autorecipes that resolve to FlashInfer TRTLLM at runtime are covered.Stream safety: with the native lifecycle active, vLLM replays buffered
weight_loaders eagerly when a layer's last shard arrives — potentially on a different CUDA stream than the one that produced the clones. The collective consumer therefore runs single-buffer on this path (trading broadcast/load overlap for a closed race window, this backend only).Fail-closed: any failure after reload initialization marks the worker permanently unusable (
_refit_unusable_reason); subsequent refit attempts return False instead of running against a partially updated model.Scope
nccl_reshardRuntime support only; recipe changes follow separately. Independent of #3478 (MXFP8 refit performance). Sibling ordering: this PR → #3659 (extends the lifecycle to
nccl_reshard) → #3669 (perf); #3651 (opt-inreload_weightsAPI) is complementary and rebases on top.Validation
autobackend selection, transport-buffer ownership, failure handling (including public-method fatal-raise and worker poisoning), unaffected backend paths, static MTP draft loading.5950903). Note: the smoke predates the single-buffer stream fix and validates the lifecycle, not stream safety — the race window was narrow enough that a clean smoke was consistent with it./ok to testissued on the current head; earlierCI:Lfastruns targeted a pre-review SHA.