Summary
MTP speculative decoding cannot be enabled for Nemotron-3-Puzzle-75B-A9B-NVFP4 (and, by
extension, any NVFP4 model whose MTP draft head is shipped unquantized) on our sm120 Blackwell
cards, because the only MoE backend that can serve both the NVFP4 main model and the unquantized
MTP head routes the NVFP4 MoE through the Sm120_SafeFP4 cutlass FP4-MoE kernel, which crashes on
sm120. Tracking for a later fix; not blocking eval (we run NVFP4 + CUDA graphs at ~116 tok/s).
The wall (verified 2026-07-08, vLLM 0.22.1 / flashinfer 0.6.11 / CUDA 13)
--moe-backend is a single global flag, but the model has mixed quantization: the main MoE is
NVFP4, the MTP draft head's MoE is unquantized (bf16) — NVIDIA ships it that way.
--moe-backend marlin NVFP4 MoE ✓ MTP head ✗
→ ValueError: moe_backend='marlin' is not supported for unquantized MoE
(nemotron_h_mtp.py:140, NemotronHMTPMoEDecoderLayer)
--moe-backend flashinfer_cutlass both accepted ✓ but NVFP4 MoE crashes at runtime ✗
--moe-backend flashinfer_trtllm both accepted ✓ same crash ✗
→ tensorrt_llm::kernels::cutlass_kernels_oss::...kernelLauncher_Sm120_SafeFP4_SafeFP4...
MoeGemmRunner<__nv_fp4_e2m1, __nv_fp4_e2m1, __nv_bfloat16...>::runMoe(...)
Sm120_SafeFP4 is the known-bad sm120 FP4-MoE kernel (already documented in CLAUDE.md as why the
daily driver uses --moe-backend marlin). So the two valid-for-both backends both hit it.
Red herring ruled out along the way: the flashinfer_cutlass first-compile OOM-kills the engine
on our 61GB host (ninja -j defaults high; each cutlass cicc spikes ~12GB). Fixed with MAX_JOBS=2
(ninja respects it; FLASHINFER_NVCC_THREADS defaults to 1). After the compile succeeded, it crashed
at runtime on the Sm120_SafeFP4 kernel — so the block is the kernel, not the build.
What works today (no MTP)
--moe-backend marlin + CUDA graphs (drop --enforce-eager) + the [sm120 flashinfer recipe] env
= ~116 tok/s single-stream, coherent, stable. Graph capture works fine on the Nemotron-H Mamba
hybrid (the earlier hang was cutlass-JIT vs graph-capture RAM contention, not Mamba). This is our
eval + serving config.
Unlock paths (later)
- Re-quantize the MTP head to NVFP4 so both MoEs are quantized →
marlin serves everything
incl. MTP. Uses our quant pipeline; uncertain draft-quality payoff; not yet attempted. (most
promising, in our control)
- Upstream vLLM/flashinfer
Sm120_SafeFP4 kernel fix so flashinfer_cutlass/trtllm NVFP4 MoE
runs on sm120. Out of our hands.
- A vLLM feature to set per-quant / per-submodel MoE backends (marlin for the NVFP4 main model,
triton for the unquantized MTP head) instead of one global --moe-backend.
Refs
- Serving finding + config: memory
reference_nemotron3_puzzle_75b
- sm120 flashinfer recipe:
experiments/quantize/FLASHINFER-SM120-RECIPE.md
- Model:
nvidia/NVIDIA-Nemotron-Labs-3-Puzzle-75B-A9B-NVFP4
Summary
MTP speculative decoding cannot be enabled for Nemotron-3-Puzzle-75B-A9B-NVFP4 (and, by
extension, any NVFP4 model whose MTP draft head is shipped unquantized) on our sm120 Blackwell
cards, because the only MoE backend that can serve both the NVFP4 main model and the unquantized
MTP head routes the NVFP4 MoE through the
Sm120_SafeFP4cutlass FP4-MoE kernel, which crashes onsm120. Tracking for a later fix; not blocking eval (we run NVFP4 + CUDA graphs at ~116 tok/s).
The wall (verified 2026-07-08, vLLM 0.22.1 / flashinfer 0.6.11 / CUDA 13)
--moe-backendis a single global flag, but the model has mixed quantization: the main MoE isNVFP4, the MTP draft head's MoE is unquantized (bf16) — NVIDIA ships it that way.
Sm120_SafeFP4is the known-bad sm120 FP4-MoE kernel (already documented in CLAUDE.md as why thedaily driver uses
--moe-backend marlin). So the two valid-for-both backends both hit it.Red herring ruled out along the way: the flashinfer_cutlass first-compile OOM-kills the engine
on our 61GB host (ninja
-jdefaults high; each cutlass cicc spikes ~12GB). Fixed withMAX_JOBS=2(ninja respects it;
FLASHINFER_NVCC_THREADSdefaults to 1). After the compile succeeded, it crashedat runtime on the Sm120_SafeFP4 kernel — so the block is the kernel, not the build.
What works today (no MTP)
--moe-backend marlin+ CUDA graphs (drop--enforce-eager) + the [sm120 flashinfer recipe] env= ~116 tok/s single-stream, coherent, stable. Graph capture works fine on the Nemotron-H Mamba
hybrid (the earlier hang was cutlass-JIT vs graph-capture RAM contention, not Mamba). This is our
eval + serving config.
Unlock paths (later)
marlinserves everythingincl. MTP. Uses our quant pipeline; uncertain draft-quality payoff; not yet attempted. (most
promising, in our control)
Sm120_SafeFP4kernel fix so flashinfer_cutlass/trtllm NVFP4 MoEruns on sm120. Out of our hands.
triton for the unquantized MTP head) instead of one global
--moe-backend.Refs
reference_nemotron3_puzzle_75bexperiments/quantize/FLASHINFER-SM120-RECIPE.mdnvidia/NVIDIA-Nemotron-Labs-3-Puzzle-75B-A9B-NVFP4