[Bug] CUTLASS MoE backend unavailable on SM_120/SM_121 (consumer Blackwell / DGX Spark) for tensor/token-scaled FP8 models
Summary
On consumer Blackwell hardware (NVIDIA RTX 50 series = SM_120, NVIDIA GB10 / DGX Spark = SM_121), vLLM's FP8 MoE backend selector picks TRITON instead of VLLM_CUTLASS for tensor/token-scaled FP8 models (e.g. RedHatAI/gemma-4-26B-A4B-it-FP8-Dynamic). This is correct fallback behavior — but the underlying reason is a real upstream gap, not just a configuration mistake. CUTLASS 4.5 does not ship a CollectiveBuilder specialization for tensor/token-scaled FP8 grouped GEMM on SM_120/SM_121. Investigation on real DGX Spark hardware (2026-05-23) confirms this.
Filing this so other GB10/Spark owners stop wasting cycles rebuilding vLLM trying to enable CUTLASS MoE — the path doesn't exist yet at the CUTLASS layer.
Affected hardware
- NVIDIA GB10 (DGX Spark) → SM_121
- NVIDIA RTX 50 series consumer Blackwell → SM_120
- Any future SM_12x consumer Blackwell variant
Symptom
In server logs at startup:
INFO [fp8.py:405] Using TRITON Fp8 MoE backend out of potential backends:
[AITER, FLASHINFER_TRTLLM, FLASHINFER_CUTLASS, DEEPGEMM, VLLM_CUTLASS, TRITON, MARLIN, ...]
Despite VLLM_CUTLASS appearing earlier in the priority list, it is never selected on SM_120/SM_121.
Why VLLM_CUTLASS doesn't get picked — precise diagnosis
The selection in vllm/model_executor/layers/fused_moe/oracle/fp8.py walks the priority list and asks each backend's is_supported_config(...). For VLLM_CUTLASS → TritonOrCutlassExperts → CutlassExpertsFp8 → _supports_current_device() returns the result of cutlass_group_gemm_supported().
That function lives in vllm/_custom_ops.py:844:
def cutlass_group_gemm_supported(cuda_device_capability: int) -> bool:
if cuda_device_capability < 90 or cuda_device_capability >= 110:
return False
try:
return torch.ops._C.cutlass_group_gemm_supported(cuda_device_capability)
except AttributeError:
return False
For SM_121 the Python gate currently returns False because 121 >= 110.
Patching the Python gate to allow SM_12x doesn't help — the underlying C++ entry point torch.ops._C.cutlass_group_gemm_supported is not registered in the compiled _C_stable_libtorch extension on SM_12x builds either, because the source csrc/libtorch_stable/quantization/w8a8/cutlass/moe/grouped_mm_c3x_*.cu only has variants for SM_90 (Hopper) and SM_100 (datacenter Blackwell):
$ ls csrc/libtorch_stable/quantization/w8a8/cutlass/moe/
grouped_mm_c3x.cuh
grouped_mm_c3x_sm100.cu
grouped_mm_c3x_sm90.cu
Writing grouped_mm_c3x_sm120.cu is where you hit the actual upstream wall. See "What we tried" below.
What we tried (so others don't have to)
Revived #32237 (closed, needs-rebase). Adapted to the post-libtorch_stable refactor. Build infrastructure (CMake, scaled_mm_entry.cu dispatch, file path) all wired up correctly. Build chain: TORCH_CUDA_ARCH_LIST="12.0a;12.1a;12.1+PTX", ENABLE_CUTLASS_MOE_SM120=1, gencode compute_120a,code=sm_120a, CUDA 13.0.2, CUTLASS 4.5.x (latest at time of testing). Compile attempts:
| Kernel schedule tried |
Result |
KernelPtrArrayTmaWarpSpecialized1SmSm120 (PR #32237's choice) |
❌ Symbol does not exist in CUTLASS 4.5 (was a pre-release name) |
KernelScheduleSm120Blockwise (from CUTLASS example 87c) |
❌ Expects per-block scale factor layouts tuple<LayoutA*, LayoutSFA*> in mainloop. Our model uses tensor/token-scaled FP8 (scaling in epilogue, plain LayoutA*). Could not build a collective for given parameters. |
KernelPtrArrayTmaWarpSpecializedCooperativeSm120<N> |
❌ Template exists in dispatch_policy.hpp but has zero usages anywhere in CUTLASS (no examples, no tests, no CollectiveBuilder specialization). Compile fails with argument list for class template is missing; supplying a SchedulerPipelineStageCount then hits "Could not build a collective." |
The actual upstream gap
CUTLASS 4.5 ships SM_120 grouped GEMM for:
- ✅ NVFP4 (4-bit float) — example
79d_blackwell_geforce_nvfp4_grouped_gemm.cu
- ✅ Blockwise FP8 (per-block scale factors in mainloop) — example
87c_blackwell_geforce_fp8_bf16_grouped_gemm_groupwise.cu
- ✅ MXFP8 blockscaled —
test/unit/gemm/device/sm120_blockscaled_tensorop_gemm/
Missing: a CollectiveBuilder specialization for tensor or token-scaled FP8 grouped GEMM on SM_120. The dispatch policies (KernelPtrArrayTmaWarpSpecializedCooperativeSm120, KernelPtrArrayTmaWarpSpecializedPingpongSm120) are defined in include/cutlass/gemm/dispatch_policy.hpp but nothing in CUTLASS itself ever instantiates them — they appear to be placeholders for future work.
This is the same code path that grouped_mm_c3x_sm100.cu (datacenter Blackwell) uses successfully via KernelPtrArrayTmaWarpSpecialized1SmSm100 + PtrArrayTmaWarpSpecialized1Sm epilogue. There is no SM_120 equivalent yet.
Reproduction (validates the gap on real GB10/Spark hardware)
# Inside a vLLM container built for SM_121 with full ARM64 + CUDA 13.0 + CUTLASS 4.5:
python3 -c "
import torch
cap = torch.cuda.get_device_capability(0)
cap_int = cap[0]*10 + cap[1]
print(f'Compute capability: {cap_int}')
try:
print(torch.ops._C.cutlass_group_gemm_supported(cap_int))
except AttributeError as e:
print(f'NOT REGISTERED: {e}')
"
# On GB10: prints '121' then 'NOT REGISTERED' — confirms the kernel was never compiled.
What would actually fix this
In order of likelihood / scope:
- CUTLASS adds a
CollectiveBuilder specialization for SM_120 ptr-array FP8 grouped GEMM with tensor/token scaling (i.e. the SM_100 KernelPtrArrayTmaWarpSpecialized*SmSm100 pattern, but for arch::Sm120 and without the 2-CTA cluster variants since consumer Blackwell doesn't have those). This is the right home for the fix.
- Once (1) lands, vLLM adds
grouped_mm_c3x_sm120.cu mirroring grouped_mm_c3x_sm100.cu with arch::Sm120 and the new schedule type, plus the dispatch branch in scaled_mm_entry.cu (we have this work ready locally, can submit a PR the moment CUTLASS ships the specialization).
- Workaround for users today: stay on the
TRITON MoE backend (current behavior; correct).
Workaround / next steps for users on GB10 today
You are already on the right backend (Triton MoE). The "performance might be sub-optimal" warning about device-specific config is real but the backend choice itself is correct. Generating a tuned Triton MoE config for NVIDIA_GB10 via benchmarks/kernels/benchmark_moe.py is a separate, lower-leverage optimization that is possible today (one caveat: the benchmark script as of this writing crashes on Gemma 4 with AttributeError: 'Gemma4TextConfig' object has no attribute 'num_local_experts' — needs config-schema adaptation; separate issue).
Local work product (ready to upstream when CUTLASS ships the missing builder)
Build infrastructure changes that would be reused once the CUTLASS gap closes:
CMakeLists.txt: third branch after grouped_mm_c3x_sm100 for 12.0a;12.1a arches, defining ENABLE_CUTLASS_MOE_SM120=1 and appending grouped_mm_c3x_sm120.cu to VLLM_STABLE_EXT_SRC
csrc/libtorch_stable/quantization/w8a8/cutlass/scaled_mm_entry.cu: extern decl + version_num >= 120 && version_num < 130 dispatch branch (mirrors the existing SM_100 / SM_90 branches)
csrc/libtorch_stable/quantization/w8a8/cutlass/moe/grouped_mm_c3x_sm120.cu: single-config template skeleton, the only blocker for compilation is the missing CollectiveBuilder specialization upstream
Happy to send the rebased-but-blocked-on-CUTLASS PR if a maintainer would like it cached for whenever CUTLASS catches up.
AI assistance disclosure: This investigation was performed with AI (Claude) assistance over a multi-hour session that included reading the failed PR, attempting four rebuild iterations on a physical NVIDIA DGX Spark, and reading CUTLASS upstream source. All compile errors and conclusions are from actual builds on SM_121 hardware. The human submitter (Tyler Merritt) reviewed and validated the diagnosis end-to-end before filing.
[Bug] CUTLASS MoE backend unavailable on SM_120/SM_121 (consumer Blackwell / DGX Spark) for tensor/token-scaled FP8 models
Summary
On consumer Blackwell hardware (NVIDIA RTX 50 series = SM_120, NVIDIA GB10 / DGX Spark = SM_121), vLLM's FP8 MoE backend selector picks
TRITONinstead ofVLLM_CUTLASSfor tensor/token-scaled FP8 models (e.g.RedHatAI/gemma-4-26B-A4B-it-FP8-Dynamic). This is correct fallback behavior — but the underlying reason is a real upstream gap, not just a configuration mistake. CUTLASS 4.5 does not ship aCollectiveBuilderspecialization for tensor/token-scaled FP8 grouped GEMM on SM_120/SM_121. Investigation on real DGX Spark hardware (2026-05-23) confirms this.Filing this so other GB10/Spark owners stop wasting cycles rebuilding vLLM trying to enable CUTLASS MoE — the path doesn't exist yet at the CUTLASS layer.
Affected hardware
Symptom
In server logs at startup:
Despite
VLLM_CUTLASSappearing earlier in the priority list, it is never selected on SM_120/SM_121.Why VLLM_CUTLASS doesn't get picked — precise diagnosis
The selection in
vllm/model_executor/layers/fused_moe/oracle/fp8.pywalks the priority list and asks each backend'sis_supported_config(...). ForVLLM_CUTLASS→TritonOrCutlassExperts→CutlassExpertsFp8→_supports_current_device()returns the result ofcutlass_group_gemm_supported().That function lives in
vllm/_custom_ops.py:844:For SM_121 the Python gate currently returns
Falsebecause121 >= 110.Patching the Python gate to allow SM_12x doesn't help — the underlying C++ entry point
torch.ops._C.cutlass_group_gemm_supportedis not registered in the compiled_C_stable_libtorchextension on SM_12x builds either, because the sourcecsrc/libtorch_stable/quantization/w8a8/cutlass/moe/grouped_mm_c3x_*.cuonly has variants for SM_90 (Hopper) and SM_100 (datacenter Blackwell):Writing
grouped_mm_c3x_sm120.cuis where you hit the actual upstream wall. See "What we tried" below.What we tried (so others don't have to)
Revived #32237 (closed, needs-rebase). Adapted to the post-
libtorch_stablerefactor. Build infrastructure (CMake,scaled_mm_entry.cudispatch, file path) all wired up correctly. Build chain:TORCH_CUDA_ARCH_LIST="12.0a;12.1a;12.1+PTX",ENABLE_CUTLASS_MOE_SM120=1, gencodecompute_120a,code=sm_120a, CUDA 13.0.2, CUTLASS 4.5.x (latest at time of testing). Compile attempts:KernelPtrArrayTmaWarpSpecialized1SmSm120(PR #32237's choice)KernelScheduleSm120Blockwise(from CUTLASS example 87c)tuple<LayoutA*, LayoutSFA*>in mainloop. Our model uses tensor/token-scaled FP8 (scaling in epilogue, plainLayoutA*).Could not build a collective for given parameters.KernelPtrArrayTmaWarpSpecializedCooperativeSm120<N>dispatch_policy.hppbut has zero usages anywhere in CUTLASS (no examples, no tests, no CollectiveBuilder specialization). Compile fails withargument list for class template is missing; supplying a SchedulerPipelineStageCount then hits "Could not build a collective."The actual upstream gap
CUTLASS 4.5 ships SM_120 grouped GEMM for:
79d_blackwell_geforce_nvfp4_grouped_gemm.cu87c_blackwell_geforce_fp8_bf16_grouped_gemm_groupwise.cutest/unit/gemm/device/sm120_blockscaled_tensorop_gemm/Missing: a
CollectiveBuilderspecialization for tensor or token-scaled FP8 grouped GEMM on SM_120. The dispatch policies (KernelPtrArrayTmaWarpSpecializedCooperativeSm120,KernelPtrArrayTmaWarpSpecializedPingpongSm120) are defined ininclude/cutlass/gemm/dispatch_policy.hppbut nothing in CUTLASS itself ever instantiates them — they appear to be placeholders for future work.This is the same code path that
grouped_mm_c3x_sm100.cu(datacenter Blackwell) uses successfully viaKernelPtrArrayTmaWarpSpecialized1SmSm100+PtrArrayTmaWarpSpecialized1Smepilogue. There is no SM_120 equivalent yet.Reproduction (validates the gap on real GB10/Spark hardware)
What would actually fix this
In order of likelihood / scope:
CollectiveBuilderspecialization for SM_120 ptr-array FP8 grouped GEMM with tensor/token scaling (i.e. the SM_100KernelPtrArrayTmaWarpSpecialized*SmSm100pattern, but forarch::Sm120and without the 2-CTA cluster variants since consumer Blackwell doesn't have those). This is the right home for the fix.grouped_mm_c3x_sm120.cumirroringgrouped_mm_c3x_sm100.cuwitharch::Sm120and the new schedule type, plus the dispatch branch inscaled_mm_entry.cu(we have this work ready locally, can submit a PR the moment CUTLASS ships the specialization).TRITONMoE backend (current behavior; correct).Workaround / next steps for users on GB10 today
You are already on the right backend (Triton MoE). The "performance might be sub-optimal" warning about device-specific config is real but the backend choice itself is correct. Generating a tuned Triton MoE config for
NVIDIA_GB10viabenchmarks/kernels/benchmark_moe.pyis a separate, lower-leverage optimization that is possible today (one caveat: the benchmark script as of this writing crashes on Gemma 4 withAttributeError: 'Gemma4TextConfig' object has no attribute 'num_local_experts'— needs config-schema adaptation; separate issue).Local work product (ready to upstream when CUTLASS ships the missing builder)
Build infrastructure changes that would be reused once the CUTLASS gap closes:
CMakeLists.txt: third branch aftergrouped_mm_c3x_sm100for12.0a;12.1aarches, definingENABLE_CUTLASS_MOE_SM120=1and appendinggrouped_mm_c3x_sm120.cutoVLLM_STABLE_EXT_SRCcsrc/libtorch_stable/quantization/w8a8/cutlass/scaled_mm_entry.cu: extern decl +version_num >= 120 && version_num < 130dispatch branch (mirrors the existing SM_100 / SM_90 branches)csrc/libtorch_stable/quantization/w8a8/cutlass/moe/grouped_mm_c3x_sm120.cu: single-config template skeleton, the only blocker for compilation is the missingCollectiveBuilderspecialization upstreamHappy to send the rebased-but-blocked-on-CUTLASS PR if a maintainer would like it cached for whenever CUTLASS catches up.
AI assistance disclosure: This investigation was performed with AI (Claude) assistance over a multi-hour session that included reading the failed PR, attempting four rebuild iterations on a physical NVIDIA DGX Spark, and reading CUTLASS upstream source. All compile errors and conclusions are from actual builds on SM_121 hardware. The human submitter (Tyler Merritt) reviewed and validated the diagnosis end-to-end before filing.