Skip to content

[CUDA] Add fused decode routing, single-pass finalize, and PDL overlap for NVFP4 QMoE - #33366

Draft
Tianlei Wu (tianleiwu) wants to merge 2 commits into
mainfrom
tlwu/20261014/nvfp4-qmoe-decode-route-pdl
Draft

Tianlei Wu (tianleiwu) wants to merge 2 commits into
mainfrom
tlwu/20261014/nvfp4-qmoe-decode-route-pdl

Conversation

@tianleiwu

Copy link
Copy Markdown
Contributor

Stacked on #33354 (and #33094). The base branch is tlwu/20261013/nvfp4-qmoe-decode-gemv, so the diff below is this change only. Once #33094 and #33354 merge, this PR will be retargeted to main. Opened as a draft until then.

Description

Optimizes the non-GEMV kernels and inter-kernel launch serialization along the offline K-packed (weights_prepacked=1) NVFP4 QMoE decode path on SM90+:

  1. Single-CTA fused decode routing (FusedSoftmaxTopKMapsKernel): Replaces LaunchSoftmaxTopK + fusedBuildExpertMapsSortFirstToken in a single CTA launch for num_rows <= 4, num_experts == 512, and k in {1, 2, 4, 6, 8, 10}. One warp per row reproduces the SoftmaxTopKMergeKernel<T, 128, 4> reduction trees operation for operation and emits the (expert, token)-sorted expert maps bit-identically (ORT_FP4_GEMV_FUSED_ROUTE=0 disables).
  2. Single-pass finalizeMoeRoutingKernel: Sizes the block to one thread per 128-bit column vector (up to 1024 threads instead of a fixed 256) so hidden dimensions up to 4096 (such as Qwen3.8's hidden=2560) reduce in a single pass, and issues batched loads of up to 16 expert rows together while preserving top-k accumulation order (7.7 us -> 4.0 us per layer on H200).
  3. Programmatic dependent launch (PDL) overlap of FC1 -> FC2 -> finalize: Launches FC2 and finalizeMoeRoutingKernel with cudaLaunchAttributeProgrammaticStreamSerialization (ORT_FP4_GEMV_PDL=0 disables; ORT_FP4_GEMV_FC1_PDL=1 optionally enables PDL for FC1 behind fused routing). FC1 triggers dependents at start, FC2 prefetches its first batch of weights/scales before waiting on FC1's output, and finalizeMoeRoutingKernel stages routing metadata into shared memory before waiting on FC2's output (~2.2 us saved per layer on H200, preserved under CUDA Graph capture).

All three optimizations preserve exact floating-point evaluation order and produce bit-identical outputs.

Summary of Changes

File Change
onnxruntime/contrib_ops/cuda/moe/qmoe_fused_route.cuh New FusedSoftmaxTopKMapsKernel single-CTA fused softmax + top-k + expert-map kernel (E=512, rows <= 4, k in {1,2,4,6,8,10})
onnxruntime/contrib_ops/cuda/moe/qmoe_kernels.h / .cu LaunchFusedRouteMaps declarations and launcher
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_kernels.cu / moe_util_kernels.h Single-pass finalizeMoeRoutingKernel (blockDim.x up to 1024, FINALIZE_LOAD_BATCH=16) and early_routing_loads PDL support
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv_fp4.h / .cu griddepcontrol.wait / griddepcontrol.launch_dependents in MoeGemvFp4DecodeKernel and programmatic_dependent_launch attribute in LaunchMoeGemvFp4RawNPacked (ORT_FP4_GEMV_PDL)
onnxruntime/contrib_ops/cuda/moe/moe_quantization.cc Wire LaunchFusedRouteMaps and fc2_finalize_pdl into QMoE::ComputeInternal
docs/contrib_ops/cuda/moe_qmoe.md Document ORT_FP4_GEMV_FUSED_ROUTE, ORT_FP4_GEMV_PDL, and ORT_FP4_GEMV_FC1_PDL

Testing

Validated on NVIDIA H200 (sm_90a, CUDA 13.0):

  • python -m pytest onnxruntime/test/python/transformers/test_qmoe_nvfp4_cuda.py: 178 passed, 8 skipped, 0 failed (including test_nvfp4_offline_row_major_decode_gemv_bit_exact for FP16 and BF16).
  • End-to-end Qwen3.8-Flash-Next NVFP4 (v12b 3-session CUDA-graph + MTP engine, 48 MoE layers + 1 MTP layer):
    • Bit-exactness: engine_gate.py --prefill steps passes 6/6 with 100% identical token sequences (256/256 tokens on text2048 and prose2048) compared against the baseline without fused routing / PDL.
    • End-to-end decode speed (bench_engine_mtp.py, 256 generated tokens, median of 3 runs):
Context Before (#33354 only) ms/cycle With this PR ms/cycle Cycle speedup Decode throughput (tok/s)
2,048 8.274 ms 7.657 ms -0.617 ms (-7.5%) 244.98 tok/s (was 227.64)
8,192 8.164 ms 7.836 ms -0.328 ms (-4.0%) 232.49 tok/s (was 225.81)
32,768 8.352 ms 8.027 ms -0.325 ms (-3.9%) 237.25 tok/s (was 229.97)
  • lintrunner on all touched files: no issues.

Comment thread onnxruntime/contrib_ops/cuda/moe/qmoe_fused_route.cuh
Comment thread onnxruntime/contrib_ops/cuda/moe/moe_quantization.cc Outdated
@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261013/nvfp4-qmoe-decode-gemv branch from 4b4e16e to 60cd1fb Compare October 16, 2026 05:32
@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261014/nvfp4-qmoe-decode-route-pdl branch from 3aa261a to a35a504 Compare October 16, 2026 21:51
@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261013/nvfp4-qmoe-decode-gemv branch from 99626e6 to a20b5c5 Compare October 19, 2026 06:07
Base automatically changed from tlwu/20261013/nvfp4-qmoe-decode-gemv to main October 19, 2026 08:15
@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261014/nvfp4-qmoe-decode-route-pdl branch from 5e08544 to 26ab25a Compare October 19, 2026 08:17
…p for NVFP4 QMoE

For the offline K-packed (weights_prepacked=1) NVFP4 QMoE decode path on SM90+:
- Fused softmax + top-k + expert-map kernel (FusedSoftmaxTopKMapsKernel) for E=512, num_rows <= 4, k in {1, 2, 4, 6, 8, 10}, reproducing LaunchSoftmaxTopK + fusedBuildExpertMapsSortFirstToken bit-exactly in a single CTA launch (ORT_FP4_GEMV_FUSED_ROUTE=0 disables).
- Single-pass finalizeMoeRoutingKernel with one thread per 128-bit column vector (up to 1024 threads) and batched loads of up to 16 expert rows while preserving top-k accumulation order.
- Programmatic dependent launch (PDL) overlap of FC1 -> FC2 -> finalize so FC2 prefetches weights/scales and finalize loads routing metadata before waiting on the producer grid (ORT_FP4_GEMV_PDL=0 disables; ORT_FP4_GEMV_FC1_PDL=1 optionally enables PDL for FC1 behind fused routing).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Guard FusedSoftmaxTopKMapsKernel inline PTX instructions only for __CUDA_ARCH__ >= 900 (with a warp-shuffle fallback for __reduce_max_sync below SM80) and verify compiled SM90 PTX/SASS via cudaFuncGetAttributes in LaunchFusedRouteMapsT.
- Gate fc2_finalize_pdl on sm_ >= 90 (and disable PDL inside the autotune timing loop), and verify SM90 PTX/SASS in finalizeMoeRoutingKernelLauncher before enabling programmaticStreamSerializationAllowed.
- Clamp finalizeMoeRoutingKernel blockDim to the compiled specialization's register-limited maxThreadsPerBlock and check cudaLaunchKernelEx return status.
- Extend test_nvfp4_offline_row_major_decode_gemv_bit_exact to cover 512-expert fused routing, PDL opt-out, and wide hidden_size=5120 finalize.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the tlwu/20261014/nvfp4-qmoe-decode-route-pdl branch from 26ab25a to c9e3dcc Compare October 19, 2026 08:58

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants