Repository navigation
[CUDA] Extend NVFP4 QMoE decode GEMV envelope and add 256-expert fused routing - #33545
Conversation
…d routing
1. Extends the default SM90 MoeGemvFp4DecodeKernel envelope (ORT_FP4_GEMV_DECODE_KERNEL=1) from (expanded_num_rows <= 20, N, K <= 2560) to (expanded_num_rows <= 64, N, K <= 3072), covering 8-token speculative verification (64 expanded rows at top_k=8) and hidden_size=3072 models such as Ornith-1.5-122B by default.
2. Adds FusedSoftmaxTopKMaps256Kernel<T, K> in qmoe_fused_route.cuh for single-CTA fused softmax + top-k + expert-map construction at num_experts == 256, 1..8 rows, k in {1, 2, 4, 6, 8} (num_rows * k <= 64) with programmatic dependent launch (PDL), bit-identical to SoftmaxTopKWarpTopKKernel + fusedBuildExpertMapsSortFirstToken.
3. Updates docs/contrib_ops/cuda/moe_qmoe.md and adds unit test coverage in test_qmoe_nvfp4_cuda.py.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The default-enabled CUDA routing and dependent-launch behavior need broader regression coverage and final human review of GPU correctness.
1 open issue
What changed in this PR
This PR expands ONNX Runtime’s NVFP4 QMoE decode fast path for larger projections and adds fused routing for 256-expert models.
Changes:
- Raises default decode GEMV limits to 64 expanded rows and N/K dimensions of 3072.
- Adds single-block fused routing for 256 experts and up to eight tokens.
- Updates dispatch documentation and adds bitwise-parity, CUDA Graph, and programmatic dependent launch tests.
| File | Description |
|---|---|
onnxruntime/test/python/transformers/test_qmoe_nvfp4_cuda.py |
Adds larger decode shapes and 256-expert routing tests. |
onnxruntime/contrib_ops/cuda/moe/qmoe_kernels.h |
Documents the expanded fused-routing limits. |
onnxruntime/contrib_ops/cuda/moe/qmoe_kernels.cu |
Dispatches eligible workloads to the 256-expert kernel. |
onnxruntime/contrib_ops/cuda/moe/qmoe_fused_route.cuh |
Implements fused 256-expert routing and expert-map generation. |
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv_fp4.cu |
Expands default decode GEMV limits. |
docs/contrib_ops/cuda/moe_qmoe.md |
Updates documented decode and routing limits. |
Review effort: Balanced
Replaced static test cases with dynamic generation for base_case_256. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…fp4-256e-8row-decode Conflicts resolved in onnxruntime/contrib_ops/cuda/moe/: - qmoe_fused_route.cuh: take main's version (8-row, 256-thread 512-expert kernel and the INT3 groups kernel) and re-add FusedSoftmaxTopKMaps256Kernel with its kFusedRoute256* constants. - qmoe_kernels.cu/.h: keep main's CanUseFusedRouteMaps unchanged (512-expert envelope shared with the generic runner via isFusedMoeRoutingSupported). LaunchFusedRouteMaps additionally accepts the 256-expert envelope through a separate CanUseFusedRouteMaps256, so the generic runner behavior is unchanged and only the NVFP4 GEMV decode path uses the 256-expert kernel. Also updated docs/contrib_ops/cuda/moe_qmoe.md: the ORT_FP4_GEMV_FUSED_ROUTE row now says num_rows <= 8 for 512 experts, matching main's 8-row 512-expert kernel (a semantic conflict git could not detect). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
This PR expands the default NVFP4 decode kernel range and combines routing and expert-map creation into one launch for 256-expert models. The change is focused, keeps the existing fallback and opt-out paths, and adds useful tests for the new shapes, top-k values and CUDA Graphs.[/+]
My independent checks passed 1,600 FP16/BF16 routing cases, with matching scales, selected experts and offsets, and valid forward/inverse maps. Memory, race and synchronization checks were clean. Routing compilation passed SM80/86/90 with CUDA 13.0 on Linux. These checks do not cover Hopper dependent launches or full-model parity. The reported H200 performance numbers are consistent, but I could not independently reproduce them on the available A100 hardware; they describe the QMoE stage, not the whole model.
I would hold approval until the Windows CUDA 12.8 build regression is fixed. The matching builds pass on the exact base commit and fail on this PR head. The blocking issue and one small comment correction are noted inline.
…ting
Replace WarpBitonicTopN<8> and the 8-way conditional store loop in
FusedSoftmaxTopKMaps256Kernel with the 32-bit FusedRouteKey and k-round
warp-wide arg-max (__reduce_max_sync / __shfl_xor_sync fallback) plus single
if (lane < k) store pattern used by FusedSoftmaxTopKMapsKernel, and replace
the nested conditional kernel-pointer deduction in LaunchFusedRouteMapsT
with a LaunchFusedRouteKernel helper. This resolves the Windows CUDA 12.8
cicc 0xC0000005 access violation while preserving bit-identical routing
outputs and aligning the kernel implementation with its comment.
Also extend softmax_topk_kernel_test.cc with C++ unit tests for 256-expert
LaunchFusedRouteMaps across rows in {1, 8} and k in {1, 2, 4, 6, 8}.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Description
Optimizes the offline K-packed (
weights_prepacked=1) NVFP4QMoEdecode path on SM90+ for 256-expert models withhidden_size = 3072and 8-token speculative verification (64expanded rows attop_k = 8, such asOrnith-1.5-122B):MoeGemvFp4DecodeKernelenvelope (ORT_FP4_GEMV_DECODE_KERNEL=1):kDecodeDefaultMaxN = 3072(was2560),kDecodeDefaultMaxK = 3072(was2560), andkDecodeDefaultMaxExpandedRows = 64(was20).MoeGemvFp4DecodeKernel(with shared-memory FP32 activation staging and PDL overlap ofFC1 -> FC2 -> finalize) by default forhidden_size = 3072, inter_size = 1024(FC1 N=2048, K=3072;FC2 N=3072, K=1024) up to 8-token speculative verification (64expanded rows), instead of falling back toMoeGemvFp4RawKPackedKernel.FusedSoftmaxTopKMaps256Kernel):LaunchFusedRouteMaps(ORT_FP4_GEMV_FUSED_ROUTE=1) to supportnum_experts == 256,1..8rows, andk in {1, 2, 4, 6, 8}(num_rows * k <= 64).256threads, one warp per row) and reproducesSoftmaxTopKWarpTopKKernel<T, 8, 4>'sWarpReduceMax/WarpReduceSum/WarpBitonicTopN<8>evaluation order operation-for-operation while emitting(expert, token)-sorted expert maps in the same CTA with programmatic dependent launch (PDL).LaunchSoftmaxTopK+fusedBuildExpertMapsSortFirstToken.Summary of Changes
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv_fp4.cukDecodeDefaultMaxN = 3072,kDecodeDefaultMaxK = 3072,kDecodeDefaultMaxExpandedRows = 64onnxruntime/contrib_ops/cuda/moe/qmoe_fused_route.cuhFusedSoftmaxTopKMaps256Kernel<T, K>(kFusedRoute256Experts = 256,kFusedRoute256MaxRows = 8,kFusedRoute256MaxK = 8,kFusedRoute256Threads = 256)onnxruntime/contrib_ops/cuda/moe/qmoe_kernels.h/.cunum_experts == 256(1..8rows,k in {1, 2, 4, 6, 8}) inLaunchFusedRouteMapsdocs/contrib_ops/cuda/moe_qmoe.mdORT_FP4_GEMV_DECODE_KERNEL,ORT_FP4_GEMV_FUSED_ROUTE) and §9b.2 documentationonnxruntime/test/python/transformers/test_qmoe_nvfp4_cuda.pyN=2048/3072, K=3072/1024, expanded=64decode GEMV dispatch/bit-exactness cases and256-expert8-row fused routing + PDL testsTesting & Performance
Validated on NVIDIA H200 (
sm_90a, CUDA 13.0):pytest onnxruntime/test/python/transformers/test_qmoe_nvfp4_cuda.py -k "test_nvfp4_offline_row_major_decode_gemv": 4/4 passed (FP16 and BF16 bit-exactness acrossORT_FP4_GEMV_DECODE_KERNEL={0, 1, 2}andORT_FP4_GEMV_FUSED_ROUTE={0, 1}with and without CUDA Graph capture).lintrunner: no issues.Ornith-1.5-122B(48NVFP4QMoElayers,num_experts=256, top_k=8, hidden_size=3072, inter_size=1024,64expanded rows/step):ms/stepat 2K Context)MoeGemvFp4RawKPackedKernel+ 2-kernel routing)MoeGemvFp4DecodeKernel+FusedSoftmaxTopKMaps256Kernel)96launches)96->48launches)