Skip to content

feat(vllm): refit-idempotent BF16 FlashInfer-TRTLLM MoE (vLLM 0.20.x stopgap) - #3296

Closed
seonjinn wants to merge 5 commits into
NVIDIA-NeMo:mainfrom
seonjinn:sna/pr-bf16-trtllm-refit
Closed

feat(vllm): refit-idempotent BF16 FlashInfer-TRTLLM MoE (vLLM 0.20.x stopgap)#3296
seonjinn wants to merge 5 commits into
NVIDIA-NeMo:mainfrom
seonjinn:sna/pr-bf16-trtllm-refit

Conversation

@seonjinn

@seonjinn seonjinn commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

vLLM 0.20's BF16 FlashInfer-TRTLLM MoE path rebinds w13/w2 to 4D block-layout Parameters on first processing, so the second RL refit's load_weights fails (ValueError: shard_dim=0 is not a valid data dimension for a 3D tensor) - the reason the shipped GRPO recipes pin moe_backend: triton for BF16.

This PR keeps checkpoint-layout parameters permanently and exposes the TRTLLM layout as 4D views aliasing the same storage (zero duplicate weight memory), recomputing the re-layout each refit as one batched gather.

  • NRL_BF16_TRTLLM_VERIFY=1: first-refit bit-equality assert vs the stock vLLM path.
  • NRL_BF16_TRTLLM_REFIT=0: kill switch (patch fully inert).
  • Fail-loud guard (RuntimeError with remediation) for load paths that skip reprocessing (ModelOpt real-quant, MTP draft reload).
  • Composes with the MXFP8 fp8 patches when both are active; builds standalone otherwise.

Version applicability

The rebind-on-reprocess issue this patch works around is handled natively in vLLM from v0.21.0 (is_weight_update/prefer_copy in _setup_kernel; verified at tags v0.21.0-v0.24.0 and main - the upstream comment explicitly cites "RL weight updates that re-trigger process_weights_after_loading"). This patch is therefore a stopgap for the currently pinned vLLM 0.20.x only and should be removed at the next vLLM bump; the kill switch plus the isolated module make removal a two-line revert.

Scope

Applies to SiLU-gated, 128-aligned MoE models on SM100 (B200/GB200). Validated on Qwen3-30B-A3B. The 128-alignment constraint applies to the per-partition intermediate size: Qwen3-235B-A22B at the recipe's vLLM TP=8 shards moe_intermediate_size=1536 to 192 per rank, which FlashInfer's Bf16MoeLauncher::check_moe rejects (intermediate_size % 128 == 0 fails) at engine build - before any refit, i.e. stock vLLM 0.20 with moe_backend=flashinfer_trtllm fails identically on that shape. Qwen3-235B is therefore N/A at TP=8 and stays on triton. Dense models (Qwen3-32B, Llama) are N/A - the TRTLLM MoE backend is MoE-only. Nemotron Nano (RELU2 + non-aligned dims) excluded pending kernel support.

Benchmark setup

  • 20 training steps per run (impact numbers below from an 8-step probe window; 20-step confirmation in flight)
  • Qwen3-30B-A3B, BF16, B200, 2 nodes x 8 GPU

Impact (BF16, triton -> flashinfer_trtllm)

Metric Value Delta
Generation time (s) 70.5 -> 63.4 -10.1%
Step time (s) 235.7 -> 229.1 -2.8%
Reward 0.52 -> 0.52 unchanged

Trade-offs

  • ~1.2 GB persistent scratch per vLLM worker.
  • Unsupported load paths convert from silent risk to loud RuntimeError.

Correctness

  • Bit-exact verify on GPU across 16 workers; 8+ refit cycles on the exact configuration that previously crashed at refit Test script to verify new models compatibility with RL pipeline #2.
  • CPU bit-equality + two-cycle idempotency unit tests (tests/unit/models/generation/test_bf16_trtllm_moe.py).
  • Known deviation: pyrefly exclusion and getattr side-channel patterns mirror the sibling fp8.py module in the same directory.

seonjinn added 3 commits July 20, 2026 11:51
Unquantized vLLM with the FlashInfer TRTLLM MoE backend rebinds the
fused-MoE weights to a 4D block layout on the first
process_weights_after_loading pass, which breaks every subsequent
refit load. Install idempotent replacements that keep
checkpoint-layout params as permanent load targets, re-deriving the
kernel layout from scratch buffers on every pass, and fail loudly if
a layer is loaded without a full reprocessing pass. Kill switch:
NRL_BF16_TRTLLM_REFIT=0.

Also installs the patches for precision=fp8 runs that keep some MoE
layers unquantized (num_first/last_layers_in_bf16, ignored-layer
keywords). The engine-core compose hook chains to the fp8 patches
only when they are present (guarded by hasattr on nrl_fp8_cfg), so
the module builds and runs standalone without the fp8 refit patches.

Squashed from 1cbf401b0, 4377fb223 (module/utils/worker wiring), and
adb2b8c09; launcher hunks dropped.

Signed-off-by: sna <sna@nvidia.com>
Validate the torch-only permutation/layout helpers in
bf16_trtllm_moe_utils against the per-expert vLLM reference pipeline
(bit equality), alias write-through, and two-cycle idempotency, all
on CPU without vLLM or flashinfer.

Squashed from 4377fb223 (test portion).

Signed-off-by: sna <sna@nvidia.com>
The new torch-only module type-checks clean, so CI's zero-error
whitelist check requires it in project-includes. Also add the missing
docstring on swap_w13_to_w31_row_indices.

Signed-off-by: sna <sna@nvidia.com>
@seonjinn
seonjinn requested review from a team as code owners July 20, 2026 19:32
@copy-pr-bot

copy-pr-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@seonjinn

Copy link
Copy Markdown
Contributor Author

Isolated-branch reproduction (follow-up promised in the PR body).

Setup: Qwen3-30B-A3B GRPO (OpenMathInstruct-2), 2 nodes x 8 B200, BF16 recipe grpo-qwen3-30ba3b-4n4g.yaml, 20 steps, means over steps 3-20. Both arms run this branch; the only difference is the recipe's moe_backend: triton vs policy.generation.vllm_kwargs.moe_backend=flashinfer_trtllm with NRL_BF16_TRTLLM_VERIFY=1.

steps 3-20 mean triton (recipe workaround) flashinfer_trtllm + this PR delta
generation (s) 69.4 62.7 -9.5%
total step (s) 239.9 220.3 -8.1%*
refits survived 20/20 20/20 crash at #2 without this patch
train reward 0.527 0.527 =

*Generation is the causal saving; the total-step delta also absorbed 12 s of run-to-run variance in the untouched logprob/training phases, so -9.5% gen (-3% E2E) is the conservative claim. The bit-exact verify assert (NRL_BF16_TRTLLM_VERIFY=1) passed on all 16 workers; the reference crash without the patch is reproduced in the PR body's probe history.

shuyixiong pushed a commit that referenced this pull request Aug 13, 2026
Squash of NVIDIA-NeMo/RL PR #3545 (head 7180bff, 2026-08-11) onto
main@d5fb8d04 with patches 0001-0003 applied. Replaces the carried PR #3296
module (bf16_trtllm_moe.py): the vllm_backend refit path now natively
reloads unquantized FlashInfer TRTLLM MoE weights, detects the realized
refit backend, constrains native refit scope, defaults injected reload
state safely, and hardens layerwise-refit failures; the ModelOpt quant
backend opts out explicitly. Retire when the upstream PR merges.
@seonjinn

Copy link
Copy Markdown
Contributor Author

Contributor self-review status: this vLLM 0.20 compatibility stopgap is obsolete on the current vLLM stack, and the remaining BF16 TRTLLM refit requirement is covered by #3659. Recommendation: close this PR rather than merge current main into obsolete compatibility code; do not retrigger CI.

…20260817

Signed-off-by: seonjinn <sna@nvidia.com>
@seonjinn seonjinn added the CI:L1 Run doctests, unit tests, and functional tests label Aug 18, 2026
@seonjinn

Copy link
Copy Markdown
Contributor Author

/ok to test d5165a1

@seonjinn seonjinn closed this Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI:L1 Run doctests, unit tests, and functional tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant