Hi everyone,
I’ve been running NVFP4/MXFP4 models on a single DGX Spark (GB10, SM121, 128GB unified memory) for the past few weeks and wanted to share what I found — both the root causes of the !!!!! garbage output and working serve scripts for gpt-oss-120B (59 tok/s) and Qwen3.5-35B (59 tok/s).
Acknowledgments first: This work builds on eugr’s SM121 MXFP4 patches, christopher_owen’s SM121 technical research, and namake-taro’s vLLM fork which fixed the PTX and Marlin race bugs. What follows are additional issues I found when applying these patches to stock vLLM 0.17.1 and serving different model formats.
Part 1: Why any NVFP4 model outputs !!! on SM121
Four independent bugs, all need to be fixed. Fixing three out of four still gives garbage.
Bug 1: cutlass_fp4_supported() false positive
In nvfp4_utils.py, the function passes capability_int = 121 (for SM 12.1) to cutlass_scaled_mm_supports_fp4(). Since 121 exceeds any reasonable threshold, it returns True. But cutlass_scaled_mm_supports_fp4(device_id=0) correctly returns False on GB10. CUTLASS FP4 gets selected for all linear layers — and produces row-identical garbage on SM121.
The diagnostic signature: every element in an output row has the same wrong value (e.g., [-28.625, -28.625, -28.625, ...]). Not zeros, not random noise — same value repeated. This is how you confirm it’s CUTLASS FP4 and not something else.
Bug 2: CutlassExpertsFp4 matches SM121
is_device_capability_family(120) in cutlass_moe.py returns True for any SM12x, including SM121. MoE expert GEMMs also get the broken kernel. Important: VLLM_NVFP4_GEMM_BACKEND=marlin only affects linear layers. The MoE path ignores this env var entirely.
Fix:
python
# cutlass_moe.py
@staticmethod
def _supports_current_device() -> bool:
p = current_platform
cap = p.get_device_capability()
if cap is not None and cap.major == 12 and cap.minor >= 1:
return False # SM121 produces garbage with CUTLASS FP4
return p.is_cuda() and (
p.is_device_capability_family(100)
or p.is_device_capability_family(110)
or p.is_device_capability_family(120)
)
Bug 3: SupportsQuant missing on Qwen3.5 model class
This one is Qwen3.5-122B specific. The hybrid GDN (SSM) layers must stay BF16, but Qwen3_5ForConditionalGeneration doesn’t inherit SupportsQuant, so apply_vllm_mapper() is never called, and the exclude_modules list uses wrong-format weight names. All GDN layers get NVFP4-quantized silently.
Fix: class Qwen3_5ForConditionalGeneration(Qwen3VLForConditionalGeneration, IsHybrid, SupportsQuant):
Bug 4: PTX + Marlin race — already fixed in namake-taro’s fork and eugr’s patches.
Environment variables needed (all four):
bash
export VLLM_NVFP4_GEMM_BACKEND=marlin
export VLLM_MXFP4_USE_MARLIN=1
export VLLM_USE_FLASHINFER_MOE_FP4=0
export VLLM_MARLIN_USE_ATOMIC_ADD=1
Full root cause analysis with detailed diagnostics: https://ai-muninn.com/en/blog/part1-why-your-dgx-spark-says-exclamation-marks
Part 2: gpt-oss-120B at 59 tok/s — 6 additional pitfalls
After fixing the NVFP4 bugs above, gpt-oss-120B has its own set of landmines:
1. eugr patch import path mismatch — mxfp4.py imports from quant_utils (eugr fork path), stock vLLM 0.17.1 uses nvfp4_utils. Server crashes on startup. One-line fix.
2. --enforce-eager in serve script — debug flag that disables CUDAGraph. 26 tok/s → 59 tok/s just by removing it. Check your scripts.
3. tiktoken vocab download on air-gapped machines — gpt-oss uses openai_harmony tokenizer. On internal networks, download fails. The cache uses SHA1 hashes as filenames (undocumented — had to read tiktoken-rs source):
bash
mkdir -p ~/models/tiktoken_cache
wget "https://openaipublic.blob.core.windows.net/encodings/o200k_base.tiktoken" \
-O ~/models/tiktoken_cache/fb374d419588a4632f3f557e76b4b70aebbca790
export TIKTOKEN_RS_CACHE_DIR=~/models/tiktoken_cache
(I saw someone asking about this exact error in the previous gpt-oss thread — this is the fix.)
4. content: null with reasoning parser — --reasoning-parser openai_gptoss puts everything in reasoning field. Standard clients get empty responses.
5. System message bypasses harmony encoding — vLLM PR #31607 (unmerged). Clients sending system prompts hit malformed token sequences. Requires patching serving_chat.py + setting VLLM_GPT_OSS_HARMONY_SYSTEM_INSTRUCTIONS=1.
6. Wrong env var name (the hardest to find) — VLLM_NVFP4_GEMM_BACKEND does NOT exist in vLLM 0.17.1. It’s silently ignored. Correct var: VLLM_MXFP4_BACKEND=marlin. Without this, vLLM auto-selects CUTLASS_FP4, which causes repetition loops on SM121.
How to verify: Check startup log. You want to see:
[MXFP4] Using backend: marlin (VLLM_MXFP4_BACKEND=marlin)
Not:
[MXFP4] Auto-selected: CUTLASS_FP4 (vLLM native SM120 FP4 grouped GEMM for SM12x)
Working gpt-oss-120B serve script:
bash
#!/bin/bash
export VLLM_MXFP4_BACKEND=marlin
export VLLM_MARLIN_USE_ATOMIC_ADD=1
export FLASHINFER_DISABLE_VERSION_CHECK=1
export TIKTOKEN_RS_CACHE_DIR=~/models/tiktoken_cache
export VLLM_GPT_OSS_HARMONY_SYSTEM_INSTRUCTIONS=1
vllm serve ~/models/gpt-oss-120b \
--served-model-name gpt-oss-120b --host 0.0.0.0 --port 8001 \
--quantization mxfp4 --kv-cache-dtype fp8 \
--max-model-len 131072 --max-num-batched-tokens 8192 \
--gpu-memory-utilization 0.90 --attention-backend TRITON_ATTN \
--moe-backend marlin
Result: 59 tok/s decode, 131K context, single GB10. Close to the 273 GB/s bandwidth ceiling.
Full writeup with all 6 bugs explained: https://ai-muninn.com/en/blog/part2-gpt-oss-120b-serve-script
Performance summary (single DGX Spark, 128GB)
| Model | Quantization | Speed | Max Context |
|---|---|---|---|
| Qwen3.5-35B (BF16→MXFP4 online) | MXFP4 | 59 tok/s | 200K |
| gpt-oss-120B | MXFP4 | 57-59 tok/s | 131K |
| Qwen3.5-122B NVFP4 | Marlin W4A16 | ~15 tok/s | 200K |
Note: both 35B and 120B can’t run simultaneously — each takes 65-75GB, total exceeds 128GB.
The 122B at 15 tok/s is Marlin W4A16 (bandwidth-bound). True W4A4 CUTLASS would be 2-3x faster but there’s no working SM121 kernel yet. Writeup on the 122B GDN bottleneck: https://ai-muninn.com/en/blog/part2-qwen-122b-14-toks-gdn-kernel-gap
Happy to answer questions. All detailed writeups are on my blog at https://ai-muninn.com — also available in Chinese (zh-TW).