Repository navigation
Conversation
…ading DeepGemmMegaMoEExperts combined NVFP4 global scales inside the weight loader: the L2 activation scale was a running max over a CPU copy of every expert's w2_input_scale, w1 and w3 weight_scale_2 were paired through loader state and both written into one slot, and block scales went through repeat_interleave and clone even when nothing is expanded. A load-plan trace (vllm-project#64418) cannot replay any of that. Each checkpoint tensor now lands in its own slot with a view and copy_: w13_weight_scale_2 is (2, experts) for w1 and w3, so its w1 row is the contiguous L1 alpha the kernel reads; w2_input_scale holds every logical expert on every rank; block scales are a view chain unless the checkpoint granularity is coarser than the kernel's. A new process_weights_after_loading checks the w1/w3 pair and sets l2_activation_scale from the max; it reruns on every reload. _l2_alpha is marked weight_loader_numel = 0 so layerwise reload does not wait for it. Passing the L2 activation scale to the kernels as a tensor stays vllm-project#54477 F16. Kernel-facing tensors match base at cold start when the w1 and w3 global scales are equal. Part of vllm-project#64418 (L2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Aaron Hao <hao-aaron@users.noreply.github.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Part of #64418 (load plan), item L2. MegaMoE NVFP4 experts now follow the loader contract (§5):
load_weightscalls;copy_s;Before this PR,
DeepGemmMegaMoEExperts(MiniMax-M3 NVFP4 ondeep_gemm_mega_moe) did three things a load-plan trace cannot replay:w2_input_scale;weight_scale_2through loader state and wrote both into one slot;repeat_interleave+cloneeven when nothing is expanded.Changes
In
deep_gemm_mega_moe.py:w13_weight_scale_2is now(2, local experts), rows w1 and w3. Each shard writes its own slot. Row 0 is a contiguous(e,)view, and the kernel reads it directly asl1_alpha. EPLB gets the two rows as separate expert tensors.w2_input_scaleis now(logical experts,). It is written for every expert on every rank, as the CPU copy was, so all ranks agree on the L2 activation scale. It is not an EPLB expert tensor.process_weights_after_loading. It checks that w1 and w3 match (same error as before, now raised after loading), then setsl2_activation_scale = max(w2_input_scale) or 1.0. Direct, layerwise and IPC weight-cache loads all rerun it. A side effect on the IPC path: base leftl2_activation_scaleat 1.0 there, and it is now computed._l2_alphabuffer is markedweight_loader_numel = 0. No loader writes it, so layerwise reload doesn't wait for it._pack_scaleexpands or copies only when it has to. NVFP4 1x16 and MX 1x32 block scales become a pure view chain (byte→int32 reinterpretation plus interleave/UTCCP views). The DeepSeek-V4/Kimi-K3 MXFP4 scales get this too. Bytes are unchanged.w13_weight_scale_2gets1e-3, as ModelOpt NVFP4 MoE does, so w1 and w3 match.w2_input_scalegets1.0, which keeps the dummy L2 scale at 1.0 as before.Block scales stay in the kernel layout. That layout is a permutation of the checkpoint bytes, so it is already a view (§4). Packing them in PWAL instead would cost a second copy of the scales or a non-idempotent in-place transform.
Not in this PR:
l2_activation_scale, but graphs captured earlier keep the old value.1/x, fixed 1/16). They are per tensor, but they are not views, so those names stay fallbacks.This does not duplicate other work: #60096 (merged) added the kernel-layout loading this builds on, and no open PR touches these scales.
Test plan
Cold-start equivalence, using a one-off script that is not in this PR. A tiny EP layer with one replica loads random NVFP4/MXFP4 bytes (with and without a fused MXFP8 shared expert) one shard per call, in shuffled order. Every tensor the kernels read is compared against base.
New CPU tests in
tests/kernels/moe/test_deep_gemm_mega_moe.py:direct_reload_matches_cold_start(MXFP4 and NVFP4)layerwise_reload_does_not_buffer_until_finalizerouted_loaders_only_view_and_copy(traced withFakeRDTTensor)eplb_views_move_whole_expertsloads_and_processes_meta_tensorsExisting tests were updated to run post-load processing.
GB200:
tests/kernels/moe/test_deep_gemm_mega_moe.pyandtests/models/test_minimax_m3_mega_moe.py. These check the kernel layout against DeepGEMM's transform, run MegaMoE against a dequantized reference for every format, and check the MiniMax-M3.1 block against its QAT reference.Test result
weight_scale_2to be bitwise equal. Within the 1e-5 tolerance the check allows, base used whichever was loaded last; this PR always uses w1.CUDA_VISIBLE_DEVICES=""): 27 passed; the GPU-only kernel-layout tests are covered by the GB200 run.tests/kernels/moe/test_deep_gemm_mega_moe.py+tests/models/test_minimax_m3_mega_moe.py, 74 passed (prepare-kernel bitwise tests deselected; that code is unchanged).A/B
Setup: A = main
4a0dfc7b2f, B = this PR on the same commit. Both are Python overlays on the4a0dfc7b2fnightly wheel. Each row ran on one node, with the arms back to back.GSM8K: 5-shot, greedy, 1319 questions. Throughput:
vllm bench serve, random 1024 in / 256 out, concurrency 64, about 2.5 min.Every arm's server log has a probe line showing that
DeepGemmMegaMoEExpertswas built, with the expected format (nvfp4ormxfp4) on every rank. The probe is the same in both arms.VLLM_RELOAD_MODE=direct(GB200, TP2 EP)--gpu-memory-utilization 0.85(GB200, TP2 EP)nvidia-smiduring sync: 169.6 GiB → 169.6 GiB--gpu-memory-utilization 0.9(GB200, TP2 EP)Notes:
How to read the noise.
Sync rows. Both arms use the same protocol:
Device digests of every registered and off-registry tensor confirm, in both arms and both modes:
The real-weights sync time in the direct row (72.9 s → 101.9 s) is a single sample. The scrambled step took 91 s → 95 s, and the layerwise row has 73 s → 72 s, so it looks like trainer-side checkpoint read noise.
Layerwise at the default
--gpu-memory-utilization 0.9, TP2. Both arms OOM at the same point, with the same allocation:So this PR neither causes nor fixes it. With 0.85 (the row above) both arms complete. Direct mode needs no extra memory and completes at 0.9.
Layerwise buffering is unchanged by this PR here. In both arms, layerwise reload logs a 4080 MB buffer for one
DeepGemmMegaMoEExperts+GateLinearpair. That buffer comes from the checkpoint's tensor order across layers, not from_l2_alpha. The other buffer warnings (12 per rank) are also identical, as is the peak memory during the sync.Serve args.
--enable-expert-parallel --max-model-len 4096 --kernel-config '{"moe_backend":"deep_gemm_mega_moe","linear_backend_per_quant":{"mxfp8":"flashinfer_trtllm"}}'. GSM8K uses chat completions withreasoning_effort=lowandmax_tokens2048. Plain completions degenerate on this chat model (0.08).--enable-expert-parallel --moe-backend deep_gemm_mega_moe --kv-cache-dtype fp8 --block-size 256 --attention_config.indexer_kv_dtype=mxfp4 --tokenizer-mode deepseek_v4, the CI MegaMoE config without MTP, plain completions.Main bugs found along the way. Both are in A too and unrelated to this PR.
QuantizedActivationto the sequence-parallel all-gather ([Perf][MiniMax-M3] Sequence parallelism and GEMM-RS for TP + MegaMoE #64659):'QuantizedActivation' object has no attribute 'contiguous'insp_all_gather. The TRT-LLM MXFP8 linear avoids it.--max-model-len 4096 --max-num-batched-tokens 16384 --max-num-seqs 128crashes during CUDA graph capture:max() iterable argument is emptyinsparse_attn_indexer. TP2 with the CI args works.Skipped.
Mapika/MiniMax-M3-NVFP4: the cache entry is empty, and its ModelOpt NVFP4 config has nomoe_quant_algo, so--moe-backend deep_gemm_mega_moerejects it. MiniMax-M3.1-Flash (MXFP8 + NVFP4 experts, the format MegaMoE targets) was used instead._pack_scaleview path.AI assistance (Claude Code) was used; the submitter reviews every changed line.
🤖 Generated with Claude Code