Repository navigation
[CUDA] Add Tensor-Core BF16 INT2 QMoE GEMV kernel and 3-mask BF16 converter - #33786
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The undefined TryParseSignedInt64 calls block compilation, and the tests lack coverage for the new odd-step single-K-warp SwiGLU variant.
2 open issues
What changed in this PR
Adds a BF16 INT2 Tensor-Core GEMV path to CUDA QMoE, accelerating grouped expert projections while retaining the existing fallback paths.
Changes:
- Introduces grouped Tensor-Core GEMV kernels and optimizes BF16 INT2 conversion.
- Adds dispatch controls and expands initializer-scale prepacking.
- Adds parity and extreme-scale tests, updates profiling setup, and documents configuration options.
| File | Description |
|---|---|
onnxruntime/test/contrib_ops/moe_test.cc |
Adds BF16 INT2 parity and extreme-scale tests; starts profiling after initialization. |
onnxruntime/contrib_ops/cuda/moe/moe_quantization.h |
Stores the Tensor-Core dispatch threshold. |
onnxruntime/contrib_ops/cuda/moe/moe_quantization.cc |
Parses dispatch controls, passes thresholds, and expands scale prepacking. |
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv.h |
Adds threshold parameters to symmetric GEMV launch interfaces. |
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv.cu |
Routes eligible BF16 INT2 launches to Tensor-Core kernels. |
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv_tc.cuh |
Implements grouped Tensor-Core GEMV and fused SwiGLU execution. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/interleaved_numeric_conversion.h |
Uses three masks to reduce BF16 INT2 conversion shifts. |
docs/contrib_ops/cuda/moe_qmoe.md |
Documents the new kernel path and configuration controls. |
Review effort: Balanced
3 tasks done
…verter
Add MoeGemvTcInt2Kernel (mma.sync.m16n8k16) and TryLaunchInt2Bf16 for
symmetric INT2 QMoE projections (block_size in {32, 64, 128}), optimize
Int2NumericArrayConverter<bfloat16_t, N> using three 2-bit masks inside
BF16's 7-bit mantissa, and cache transposed INT2 scales in QMoE::PrePack.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tianlei Wu (tianleiwu)
force-pushed
the
tlwu/20261031/qmoe-cuda-int2-tc
branch
from
November 1, 2026 07:44
36a4284 to
9ae9283
Compare
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The INT2 test leaves second-chunk routing and output indexing untested, while the scale-caching gate adds an unusable GPU allocation and transpose when weights are not prepacked.
2 open issues
2 resolved since last review
Review effort: Max
…MV test helper Remove redundant int_scale_supports_packed_execution predicate in QMoE::PrePack so runtime weights with initializer scales do not allocate unused transposed scale buffers, and reuse RunQMoEBf16TensorCoreGemvParityCase(/*bits=*/2, ...) in the INT2 Tensor-Core GEMV tests to cover multi-chunk (>8 routes) expert routing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Akshay Sonawane (apsonawane)
approved these changes
Nov 2, 2026
Baiju Meswani (baijumeswani)
approved these changes
Nov 2, 2026
Tianlei Wu (tianleiwu)
enabled stacked auto-merge (squash)
November 2, 2026 23:06
Tianlei Wu (tianleiwu)
added a commit
that referenced
this pull request
Nov 3, 2026
… for SM12x and 256K context (#33802) ## Summary Stacked on #33786 ( lwu/20261031/qmoe-cuda-int2-tc). This PR optimizes CUDA prefill and decode kernels for **Qwen3.8-Flash-Next (RC5)** on **Blackwell SM 12.x (100 KB shared memory/SM, RTX Spark N1X)** from short/medium contexts (2K–32K) up to **256K (262,144) context length**: 1. **DynamicSparseAttention (dynamic_sparse_attention.cc, dynamic_sparse_attention_impl.cu, dynamic_sparse_attention_prefill_int8.cuh, dynamic_sparse_attention_helper.h)**: - Enable DynamicSparseAttentionInt8TcKernel prefill & split-K decode by default on SM >= 8.0. - Add a 2-stage shared-memory pipeline (kTwoBufferSmem, 38.1 KB / 40.4 KB smem) on 100 KB/SM architectures (SM 8.6/8.9/12.0/12.1) to double occupancy from 1 to 2 CTAs/SM (**2.83x faster DSA prefill**, **7.5x faster DSA decode**). - Clamp otal_sequence_length to past_cache_capacity when a smaller runtime past_key cache is bound via untime_profiles. 2. **PackedSparseAttentionIndexer (packed_sparse_attention_indexer.cc, packed_sparse_attention_indexer_impl.cu)**: - Tile **8 query tokens per CTA** (4 in registers + 4 in shared memory) in QsaRaggedPrefillScoreKernel<T, TState, true> and replace shared-memory reduction barriers with a bit-exact 19-shuffle 16-head butterfly reduction, cutting present_key_state DRAM reads by 2x and speeding up 128K prefill scoring by **1.96x (11.67 s -> 5.94 s)**. - Avoid zero-filling inactive 2048-key chunks in QsaScoreTileTopKKernel beyond element-0 sentinels, eliminating the 256K state_capacity padding tax on short/medium prompts. - Aggregate radix histogram updates in QsaPartialTopKKernel with warp-level __match_any_sync and extend single-launch QsaFinalMergeAndEmitKernel up to 64 chunks (256K context). - Allow runtime past_key_state capacity to be sized dynamically from dynamic_batching / untime_profiles. 3. **HyperConnectionLowRankMix (hyper_connection_low_rank_mix_impl.cu)**: - Tune kWeightSlots to 5 on __CUDA_ARCH__ >= 1000 (30.8 KB in HcDownKernel, 23.6 KB in HcUpPreKernel), increasing 100 KB/SM occupancy from 2 to 3–4 CTAs/SM (-34% to -43% kernel time). 4. **QMoE Decode & Prefill (moe_gemv_tc.cuh, qmoe_int3_tc.cuh, qmoe_kernels.cu, int8_tc_gemv.cuh)**: - Increase kRouteChunks to 3 (38.7 KB smem -> 2 CTAs/SM) for otal_routes <= 96 and enable kM = 2 2-route FC2 tile sharing in MoeGemvTcInt2Kernel. - Enable kRemMode = 1 FC2 remainder prefetch in Int3TcCoalescedKernel. ## Validation - End-to-end smoke.py and 10-item MMLU-Pro smoke evaluation across all 3 Qwen3.8-Flash-Next RC5 variants (2b375, 3b3_16, all_int4). - 2K / 8K / 32K and 8K / 128K / 255K + 1K (262,144 total tokens) TTFT & decode benchmarks on RTX Spark N1X (SM 12.1): - 2b375: 32K TTFT 24.85s -> 19.63s (**+26.6%**), 128K TTFT 85.96s -> 75.06s (**-10.91s**), 255K TTFT 205.73s -> 164.07s (**-41.66s, +25.4%**), 128K decode **97.13 tok/s**. - 3b3_16: 32K TTFT 29.96s -> 24.47s (**+22.4%**), 128K TTFT 101.07s -> 86.50s (**-14.57s**), 255K TTFT 234.31s -> 197.38s (**-36.94s, +18.7%**). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tianlei Wu (tianleiwu)
added a commit
that referenced
this pull request
Nov 4, 2026
…akeup (#33787) ## Description Stacked on #33731 (and includes #33785 and #33786 so the branch builds and passes CI standalone). Enables portable runtime CPU/GPU offload (`session.moe.offload.*` / `int2_bank_mode_`) for SM80-prepacked `INT2` (`expert_weight_bits = 2, weights_prepacked = 1, block_size ∈ {32, 64, 128}`) and mixed `INT4 + INT3 + INT2` `com.microsoft::QMoE` models (such as the 2.375-bit 48-layer Qwen3.8-Flash-Next RC5 candidate `qmoe_2b375_x02_y14_z32_dp23_b64` with `2` INT4 + `14` INT3 + `32` INT2 layers), while optimizing `SharedSpinPool` inter-step spin duration and `SessionPromotionBudget` pinned bounce allocation. ## Summary of Changes ### 1. `INT2` Offload Admission & `int2_bank_mode_` Execution (`Commit 3`) | File | Change | |------|--------| | `onnxruntime/core/framework/session_state.cc` | Admits prepacked `INT2` (`weight_bits == 2 && prepacked == 1 && block_size ∈ {32, 64, 128}`) in `SessionState::ValidateNodeForMoeOffload`. | | `onnxruntime/contrib_ops/cuda/moe/moe_quantization.cc` | Admits `int2_sm80_prepacked` `QMoE` nodes into the v4 offload planner using `MakeInt2ExpertBlockLayout` for `cpu_slot_stride`. | | `onnxruntime/contrib_ops/cuda/moe/qmoe_offload.h` & `.cc` | Adds `int2_bank_mode_` and `int2_block_size_` across GPU resident slab sizing (`1,382,408 B/slot` at `H=2560, I=640, b64`), `FileExpertStore` bounded host cache (`1,382,912 B/slot`), scale transposition (`CopyOrTransposeScaleBankToHost`), CPU doorbell cold-expert execution (`Int2Fc1SwiGluRange` / `Int2Fc2Range`), GPU hot resident decode (`launch_moe_gemv_int_symmetric*<T, cutlass::uint2b_t>`), and GPU wave streaming prefill. | | `onnxruntime/contrib_ops/cuda/moe/qmoe_offload.cc` | Caps per-slot promotion bounce allocation in `SessionPromotionBudget::EnsureCapacity` to `pinned_limit / 6` while preserving `decode_reserve = 24 MiB` so all 48 layers can promote experts under a `128 MiB` `pinned_bytes` budget without exhausting persistent decode pinned reservations. | | `onnxruntime/contrib_ops/cuda/moe/qmoe_offload.cc` | Extends the default decode spin window (`ORT_QMOE_SHARED_POOL_SPIN_US` and `DoorbellService::Loop`) from `2,000 us` to `15,000 us` so `SharedSpinPool` workers stay awake across the `~7.6 ms` DFlash2 7-draft generation window between target verification steps, eliminating a `~4.0 ms` Layer 0 futex wakeup stall per step. | | `onnxruntime/test/python/test_qmoe_offload_v4.py` | Extends `test_v4_int4_and_int3_offload_modes_and_numerical_parity` with `INT2` (`block_size ∈ {32, 64}`) and 3-way mixed `INT4 + INT3 + INT2` offload numerical parity tests across `adaptive` and `cpu_execution=disabled` streaming modes. | | `docs/moe-cpu-offload/qmoe_offload.md` | Updates offload overview for `INT4/INT3/INT2` models. | ### 2. Prerequisite Commits Included for Standalone CI - **Commit 1 (#33785)**: `[CPU/MLAS] Add QMoE shared layout INT2 AVX-512/AVX2 kernels and direct CPU QMoE` - **Commit 2 (#33786)**: `[CUDA] Add Tensor-Core BF16 INT2 QMoE GEMV kernel and 3-mask BF16 converter` ## Testing - **Unit & Integration Tests**: - `./build/direct_cuda/Release/onnxruntime_provider_test --gtest_filter="QMoESharedLayoutTest.*:MoETest.*"` (`153/153` passed). - `python3 onnxruntime/test/python/test_qmoe_offload_v4.py TestQMoEOffloadV4.test_v4_int4_and_int3_offload_modes_and_numerical_parity -v` (`OK`). - **End-to-End H200 Offload Qualification (`qmoe_2b375_x02_y14_z32_dp23_b64`, `< 30 GB` VRAM / `< 40 GB` Anon RAM, `device_expert_bytes = 13.5 GB`, `host_expert_bytes = 34.0 GB`)**: - Fits **`8,267–8,363` resident expert slots** on GPU (`33.6%–34.0%` of 24,576 experts) and maps the entire `26.31 GB` cold expert complement in host memory with **`ssd_misses = 0`** and **`Peak Anon RAM = 3.70–4.47 GB`**, **`Peak VRAM = 25.61–29.28 GB`**. - `smoke.py`: `PASS` (`0.223 s` TTFT, `57.9 tok/s` decode, `16.92 ms/step` CPU time). - `engine_behaviours.py`: `PASS` (`26/26` checks, `11.15 ms/step` CPU time). - `bench_engine_mtp.py` (`128` new tokens, `S = 8`): **`87.49 tok/s` median (`91.49 tok/s` peak)** at `ctx = 2048`, **`137.56 tok/s` median** at `ctx = 8192` (**`+25.3%`** vs `top16_44_33`), and **`102.99 tok/s` median (`150.60 tok/s` peak, `36.27 ms/cycle`)** at `ctx = 32768` (**`-35.0%`** cycle latency vs `top16_44_33`). - `bench_ttft_engine.py`: **`0.438 s`** (`L=128`), **`0.918 s`** (`L=512`), **`1.096 s`** (`L=1024`, **`2.43x`** faster), **`2.734 s`** (`L=2048`), **`3.336 s`** (`L=4096`), **`4.177 s`** (`L=8192`, **`1,961 tok/s`**, **`1.67x`** faster). ## Checklist - [x] Tests added/updated - [x] No breaking changes (or documented in description) - [x] Documentation updated (if applicable) --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Description
Adds a Tensor-Core (
mma.sync.m16n8k16, BF16 inputs, FP32 accumulation) grouped GEMV kernel (MoeGemvTcInt2Kernel/TryLaunchInt2Bf16) for symmetricINT2(cutlass::uint2b_t,block_size ∈ {32, 64, 128})com.microsoft::QMoEprojections, and optimizesInt2NumericArrayConverter<bfloat16_t, N>ininterleaved_numeric_conversion.husing a 3-mask BF16 mantissa trick. On NVIDIA H200 (S = 8, k = 10,80routes,H = 2560, I = 640, block_size = 64), Nsight Compute (ncu) profiling shows 2.02x faster FC1+SwiGLU (70.56 us -> 35.01 us) and 1.95x faster FC2 (47.01 us -> 24.13 us) compared to the scalar SIMTmoe_gemvkernel.Summary of Changes
Tensor-Core BF16 INT2 QMoE GEMV & 3-Mask BF16 Converter
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv_tc.cuhMoeGemvTcInt2Kernel,TryLaunchInt2Bf16,DequantPairInt2Masked,ComputeWordInt2, andDiscoverExpertRouteChunk(grouping up to 8 routes per expert into anmma.sync.m16n8k16warp tile with software-pipelined activation and streaming weight/scale loads, supportingblock_size ∈ {32, 64, 128}and sorted route discovery beyond 256 routes).onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv.cucutlass::uint2b_tlaunches tofiv::moe_tc::TryLaunchInt2Bf16. Thetensor_core_min_rowsparameter (default33) of the two launchers already exists on main and is reused as-is.onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/interleaved_numeric_conversion.hInt2NumericArrayConverter<bfloat16_t, N>to extract three 2-bit fields (0x00030003,0x000C000C,0x00300030) inside BF16's 7-bit mantissa with only two right-shifts (>> 6and>> 12) per 32-bit packed word instead of 8 shifts.onnxruntime/contrib_ops/cuda/moe/moe_quantization.ccint_scale_supports_packed_executioninQMoE::PrePackso initializer scales are transposed once whenenable_int2_gemv_orenable_int2_prefill_is active. Theep.cuda.moe_gemv_tc_min_rows/ep.cuda.disable_moe_gemv_tccontrols (ORT_MOE_GEMV_TC_MIN_ROWS/ORT_DISABLE_MOE_GEMV_TC) are already parsed on main and are reused unchanged.docs/contrib_ops/cuda/moe_qmoe.mdINT2GEMV path and session/environment controls.onnxruntime/test/contrib_ops/moe_test.ccQMoETest_Bf16Int2TensorCoreGemvParity(acrossblock_size ∈ {32, 64, 128}and 6 shapes, including the odd-stephidden_size = 640, inter_size = 512case that exercises the single-K-warp SwiGLU kernel) andQMoETest_Bf16Int2TensorCoreGemvExtremeScales, and starts CUPTI profiling aftersession.Initialize()inQMoETest_Int2CudaCachedScalesDecodeThenPrefill.Testing
onnxruntime_provider_testand ran./build/direct_cuda/Release/onnxruntime_provider_test --gtest_filter="MoETest.*"on NVIDIA H200 (123/123passed, includingQMoETest_Bf16Int2TensorCoreGemvParity,QMoETest_Bf16Int2TensorCoreGemvExtremeScales, andQMoETest_Int2CudaCachedScalesDecodeThenPrefill).9ae9283with a CUDA-EP-as-plugin build (CUDA 13.0, NVIDIA H200,sm_90):MoETest.*gives96passed,17skipped (CPU-only tests and routing-statistics tests that the CUDA plugin EP does not support),0failed. Nsight Systems shows the single-K-warp SwiGLU variantMoeGemvTcInt2Kernel<true, 1, 1, G>launched once per block size for the odd-step{hidden_size = 640, inter_size = 512}shape, andcompute-sanitizer --tool memcheckreports0errors onQMoETest_Bf16Int2TensorCoreGemvParity.MoeGemvTcInt2Kernelvsmoe_gemv_interleaved_swiglu_kernel/moe_gemv_kernelwith Nsight Compute (ncu) on NVIDIA H200 (1.99xtotal layer speedup atT = 8, k = 10).clang-format --dry-run --Werrorandgit diff --check.Checklist