Skip to content

MTP + NVFP4 blocked on sm120: unquantized MTP head forces flashinfer_cutlass/trtllm → Sm120_SafeFP4 MoE kernel crash (marlin rejects unquantized head) #17

Description

@mabry1985

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)

  1. 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)
  2. Upstream vLLM/flashinfer Sm120_SafeFP4 kernel fix so flashinfer_cutlass/trtllm NVFP4 MoE
    runs on sm120. Out of our hands.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions