Summary
Serving nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-NVFP4 (NemotronH hybrid Mamba-Transformer MoE, ModelOpt NVFP4, FP8 KV cache) with default backend selection on SM100-family GPUs (tested: B200) produces grammatically fluent but semantically corrupted output — 100% deterministic under greedy decoding. The same checkpoint on the same vLLM version is correct on SM120 (RTX PRO 4500 Blackwell).
An A/B/A experiment on the same B200 isolates the fault to the auto-selected FLASHINFER_TRTLLM monolithic NVFP4 MoE backend: adding --moe-backend=flashinfer_cutlass fully fixes the output on the same GPU with the same checkpoint; reverting to the default reproduces the broken logprobs bit-identically.
Environment
- vLLM v0.27.0 (the relevant dispatch code is byte-identical in v0.26.0; the flashinfer pin changed 0.6.14 → 0.6.16.post3 between the tags)
- flashinfer-python 0.6.16.post3 (per
requirements/cuda.txt)
- Broken: B200 (compute capability 10.0). Clean: RTX PRO 4500 Blackwell (12.0)
- Model:
nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-NVFP4 — non-gated ReLU² MoE, 128 experts, top-6, sigmoid+bias grouped top-k with n_group=1, hidden_size=2688, kv_cache_quant_algo: FP8, group_size: 16
- TP=1, PP=1, otherwise default flags
Reproduction
vllm serve nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-NVFP4 --tensor-parallel-size 1
curl -s localhost:8000/v1/completions -H 'Content-Type: application/json' \
-d '{"model":"nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-NVFP4","prompt":"The capital of France is","max_tokens":16,"temperature":0}'
Expected: Paris… — Actual on B200 with the default backend: **"The Last of Us"** (2013) – a
Other 100%-deterministic failures at temperature 0: Question: What is 2+2? Answer: never contains 4; the model cannot continue 1, 2, 3, 4,.
Evidence
Top-5 logprobs for The capital of France is (greedy, 1 token), same B200, same checkpoint:
| MoE backend |
top-5 logprobs |
default (FLASHINFER_TRTLLM monolithic) |
' **' -2.0724, '.' -2.3224, ' ' -2.5099, ',' -2.7599, '**' -2.8849 — no content token in the top-5 |
--moe-backend=flashinfer_cutlass |
' Paris' -0.0278 — healthy |
Two forensic details:
- A/B/A determinism: switching back to the default reproduces the broken logprobs bit-identically to 10 decimal places across restarts — this is a deterministic contract/kernel defect, not a race.
- The broken logits sit on a 1/16 lattice: the gaps between the top-5 raw logits are exactly 4/16, 3/16, 4/16, 2/16. Healthy bf16 logits don't do that; it suggests the MoE contribution to the residual stream is coarsely mis-scaled/degenerate rather than "wrong but rich" — pointing at scale application rather than e.g. wrong expert selection.
During the healthy flashinfer_cutlass run, every other SM100-default path was still active (trtllm-gen FP8-Q attention, CuTe-DSL dense NVFP4 GEMMs), so those are exonerated; the defect is fully contained in what --moe-backend swaps.
Analysis (v0.27.0 tree)
Selection chain: ModelOptNvFp4FusedMoE → select_nvfp4_moe_backend (vllm/model_executor/layers/fused_moe/oracle/nvfp4.py) tries FLASHINFER_TRTLLM first. TrtLlmNvFp4ExpertsMonolithic._supports_current_device() requires is_device_capability_family(100) — which is why SM120 never runs this path (matching the clean/broken hardware split) — and _supports_no_act_and_mul() returns True with the comment "(i.e. Nemotron-Nano)", making this model the primary user of the non-gated variant.
The monolithic bundle differs from the modular flashinfer_cutlass path in several coupled ways; the defect must live in one of these:
- Hidden-size padding contract: expert weights zero-padded 2688 → 2816 (
round_up(…, 256)), activations padded at runtime and the output sliced back; after nvfp4_block_scale_interleave + epilogue tile shuffle, each row has 168 real + 8 padded scale groups. A scale offset/stride mismatch here best fits the 1/16-lattice logits.
- Non-gated (ReLU²) scale plumbing:
g1_scale_c = a2_gscale.clone(), output1_scale_scalar = 1/a2_scale, output1_scale_gate_scalar = w13_scale_2 * a13_scale — gate-slot scalars reused for a MoE that has no gate half; the contract with the flashinfer kernel is implicit.
- In-kernel routing:
flashinfer.fused_moe.trtllm_fp4_block_scale_moe computes DeepSeekV3-style routing internally (degenerate n_group=1, top-6 of 128, fp32 sigmoid+bias) vs vLLM's own ops.grouped_topk on the modular path.
- Activation scale layout: linear (non-swizzled) — unique to this backend.
routing_replay_out kwarg (new call-site argument in v0.27.0) ties behavior to the installed flashinfer signature.
Suggested immediate mitigation
Until the monolithic non-gated path passes a numeric parity test, TrtLlmNvFp4ExpertsMonolithic._supports_no_act_and_mul() should return False so auto-selection falls back to FLASHINFER_CUTLASS (verified correct for this model on both SM100 and SM120). Longer-term: a CI parity test (monolithic vs modular vs QDQ emulation) on a small non-gated ReLU² MoE with hidden size not divisible by 256 would catch this entire defect class.
User workaround until then: --moe-backend=flashinfer_cutlass.
Summary
Serving
nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-NVFP4(NemotronH hybrid Mamba-Transformer MoE, ModelOpt NVFP4, FP8 KV cache) with default backend selection on SM100-family GPUs (tested: B200) produces grammatically fluent but semantically corrupted output — 100% deterministic under greedy decoding. The same checkpoint on the same vLLM version is correct on SM120 (RTX PRO 4500 Blackwell).An A/B/A experiment on the same B200 isolates the fault to the auto-selected
FLASHINFER_TRTLLMmonolithic NVFP4 MoE backend: adding--moe-backend=flashinfer_cutlassfully fixes the output on the same GPU with the same checkpoint; reverting to the default reproduces the broken logprobs bit-identically.Environment
requirements/cuda.txt)nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-NVFP4— non-gated ReLU² MoE, 128 experts, top-6, sigmoid+bias grouped top-k withn_group=1,hidden_size=2688,kv_cache_quant_algo: FP8,group_size: 16Reproduction
Expected:
Paris…— Actual on B200 with the default backend:**"The Last of Us"** (2013) – aOther 100%-deterministic failures at temperature 0:
Question: What is 2+2? Answer:never contains4; the model cannot continue1, 2, 3, 4,.Evidence
Top-5 logprobs for
The capital of France is(greedy, 1 token), same B200, same checkpoint:FLASHINFER_TRTLLMmonolithic)' **' -2.0724,'.' -2.3224,' ' -2.5099,',' -2.7599,'**' -2.8849— no content token in the top-5--moe-backend=flashinfer_cutlass' Paris' -0.0278— healthyTwo forensic details:
During the healthy
flashinfer_cutlassrun, every other SM100-default path was still active (trtllm-gen FP8-Q attention, CuTe-DSL dense NVFP4 GEMMs), so those are exonerated; the defect is fully contained in what--moe-backendswaps.Analysis (v0.27.0 tree)
Selection chain:
ModelOptNvFp4FusedMoE→select_nvfp4_moe_backend(vllm/model_executor/layers/fused_moe/oracle/nvfp4.py) triesFLASHINFER_TRTLLMfirst.TrtLlmNvFp4ExpertsMonolithic._supports_current_device()requiresis_device_capability_family(100)— which is why SM120 never runs this path (matching the clean/broken hardware split) — and_supports_no_act_and_mul()returns True with the comment "(i.e. Nemotron-Nano)", making this model the primary user of the non-gated variant.The monolithic bundle differs from the modular
flashinfer_cutlasspath in several coupled ways; the defect must live in one of these:round_up(…, 256)), activations padded at runtime and the output sliced back; afternvfp4_block_scale_interleave+ epilogue tile shuffle, each row has 168 real + 8 padded scale groups. A scale offset/stride mismatch here best fits the 1/16-lattice logits.g1_scale_c = a2_gscale.clone(),output1_scale_scalar = 1/a2_scale,output1_scale_gate_scalar = w13_scale_2 * a13_scale— gate-slot scalars reused for a MoE that has no gate half; the contract with the flashinfer kernel is implicit.flashinfer.fused_moe.trtllm_fp4_block_scale_moecomputes DeepSeekV3-style routing internally (degeneraten_group=1, top-6 of 128, fp32 sigmoid+bias) vs vLLM's ownops.grouped_topkon the modular path.routing_replay_outkwarg (new call-site argument in v0.27.0) ties behavior to the installed flashinfer signature.Suggested immediate mitigation
Until the monolithic non-gated path passes a numeric parity test,
TrtLlmNvFp4ExpertsMonolithic._supports_no_act_and_mul()should return False so auto-selection falls back toFLASHINFER_CUTLASS(verified correct for this model on both SM100 and SM120). Longer-term: a CI parity test (monolithic vs modular vs QDQ emulation) on a small non-gated ReLU² MoE with hidden size not divisible by 256 would catch this entire defect class.User workaround until then:
--moe-backend=flashinfer_cutlass.