Repository navigation
[CUDA] Add 2-bit fpA_intB GEMM and GEMV support - #32699
Merged
Tianlei Wu (tianleiwu) merged 6 commits intoSep 22, 2026
Merged
Tianlei Wu (tianleiwu) merged 6 commits into
Tianlei Wu (tianleiwu) merged 6 commits into
Conversation
3 tasks done
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The fpA_intB layout tests can exercise fallback paths instead, and one production-shape test introduces excessive reference-computation cost.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 3
Open (3)
What changed in this PR
Adds CUDA 2-bit MatMulNBits support across fused GEMV, CUTLASS fpA_intB GEMM, preprocessing, fallback dequantization, tests, and documentation.
Changes:
- Implements FP16/BF16 INT2 CUTLASS GEMM/GEMV dispatch and layouts.
- Adds generic 2-bit CUDA kernels and dequantization fallback.
- Expands validation, tests, compact-build configuration, and documentation.
| File | Description |
|---|---|
onnxruntime/test/contrib_ops/matmul_nbits_fpa_intb_layout_test.cc |
Adds exact weight-layout tests. |
onnxruntime/test/contrib_ops/matmul_2bits_test.cc |
Adds CUDA INT2 end-to-end coverage. |
onnxruntime/test/contrib_ops/cuda_kernels/fpA_intB_gemm_kernel_test.cc |
Extends kernel tests to INT2. |
onnxruntime/contrib_ops/cuda/quantization/matmul_nbits.h |
Adds INT2 validation and dispatch. |
onnxruntime/contrib_ops/cuda/quantization/matmul_nbits.cuh |
Declares INT2 fast-path dispatch. |
onnxruntime/contrib_ops/cuda/quantization/matmul_nbits.cc |
Integrates preprocessing, profiling, and fallback. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits.cu |
Dispatches INT2 fused kernels. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_m1.cuh |
Declares M=1 kernel entry point. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_m1_impl.cuh |
Implements M=1 GEMV. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_half.cu |
Instantiates FP16 M=1 support. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_float.cu |
Instantiates FP32 M=1 support. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_common.cuh |
Adds shared INT2 conversion primitives. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_bfloat16.cu |
Instantiates BF16 M=1 support. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_batched.cuh |
Declares small-M dispatch. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_batched_impl.cuh |
Implements batched INT2 kernels. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_batched_half.cu |
Instantiates FP16 batched support. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_batched_float.cu |
Instantiates FP32 batched support. |
onnxruntime/contrib_ops/cuda/quantization/matmul_2bits_batched_bfloat16.cu |
Instantiates BF16 batched support. |
onnxruntime/contrib_ops/cuda/quantization/dequantize_blockwise.cuh |
Adds generic INT2 dispatch. |
onnxruntime/contrib_ops/cuda/quantization/dequantize_blockwise_2bits.cu |
Implements INT2 fallback dequantization. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemv/fpA_intB_gemv.h |
Adds INT2 kernel types. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemv/fpA_intB_gemv.cu |
Adds INT2 launch and support checks. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemv/dispatcher.h |
Updates reduction and INT2 dispatch geometry. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemv/dispatcher_fp16_int2.cu |
Instantiates FP16 INT2 GEMV. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemv/dispatcher_bf16_int2.cu |
Instantiates BF16 INT2 GEMV. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemv/details.h |
Defines INT2 type and conversion traits. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm/fpA_intB_gemm_template.h |
Routes INT2 through SM80-compatible GEMM. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm/fp16_int2_gemm_scaleonly.cu |
Instantiates FP16 scale-only GEMM. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm/fp16_int2_gemm_scale_zeros.cu |
Instantiates FP16 affine GEMM. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm/bf16_int2_gemm_scaleonly.cu |
Instantiates BF16 scale-only GEMM. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm/bf16_int2_gemm_scale_zeros.cu |
Instantiates BF16 affine GEMM. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm_profiler.h |
Defines INT2 profiling width. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm_profiler.cc |
Adds INT2 profiler sizing and execution. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm_preprocessors.h |
Adds the W2_A16 quantization type. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm_preprocessors_impl.h |
Defines INT2 layouts and permutations. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm_preprocessors_impl.cu |
Implements INT2 transpose and interleave. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm_adaptor.h |
Generalizes packed zero-point handling. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemm_adaptor.cu |
Implements INT2 unpacking and zero points. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/interleaved_numeric_conversion.h |
Adds UINT2 FP16/BF16 converters. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/gemm/threadblock/dq_mma_pipelined_percol.h |
Fixes odd B-fragment pipelining. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/gemm/threadblock/dq_mma_pipelined_finegrained.h |
Fixes fine-grained pipelining. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/gemm/threadblock/dq_mma_multistage_percol.h |
Fixes multistage per-column pipelining. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/gemm/threadblock/dq_mma_multistage_finegrained.h |
Fixes multistage fine-grained pipelining. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/gemm/threadblock/default_mma.h |
Adds FP16/UINT2 MMA specializations. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/gemm/threadblock/default_mma_bf16.h |
Adds BF16/UINT2 MMA specializations. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/gemm/threadblock/default_dq_mma_pipelined.h |
Permits UINT2 pipelined MMA. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/gemm/threadblock/default_dq_mma_multistage.h |
Permits UINT2 multistage MMA. |
onnxruntime/contrib_ops/cuda/llm/cutlass_extensions/gemm/kernel/mixed_gemm_B_layout.h |
Defines the INT2 interleaved layout. |
onnxruntime/contrib_ops/cpu/quantization/matmul_nbits_helper.h |
Validates INT2 block-size limits. |
docs/contrib_ops/cuda/matmul_nbits.md |
Documents INT2 routing and layouts. |
cmake/onnxruntime_cuda_source_filters.cmake |
Includes INT2 kernels in compact builds. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Tianlei Wu (tianleiwu)
force-pushed
the
tlwu/matmul-nbits-2bit-fpa-intb
branch
from
September 21, 2026 22:37
1b6be66 to
5a3d5e3
Compare
Tianlei Wu (tianleiwu)
requested review from
Akshay Sonawane (apsonawane),
David Fan (jiafatom) and
kunal-vaishnavi
September 21, 2026 23:12
Extends the CUTLASS mixed-input weight-only path (fpA_intB) to uint2b_t
weights so MatMulNBits with bits=2 can use the interleaved fast kernels
instead of dequantize+cuBLAS.
- interleaved_numeric_conversion: Int2NumericArrayConverter for half_t
(4 masks + 1 shift, fma.rn.f16x2) and bfloat16_t (shift per output,
since bf16's 7 mantissa bits cannot hold a field above shift 4).
- mixed_gemm_B_layout / default_mma{,_bf16} / default_dq_mma_*: uint2b_t
layout details (ColumnsInterleaved = 8) and DefaultMma specializations.
- dq_mma_{multistage,pipelined}_{finegrained,percol}: the warp_frag_B[2]
double buffer was indexed by the within-tile load index, which is only
a valid ping-pong when kWarpGemmIterationsForB is even. For 2-bit it is
1, so every load wrote slot 1 and every MMA read slot 0, silently
computing B[n][k % 64]. Replaced with a running read cursor that flips
after the last k-iteration of each B load. Bit-identical for 4/8-bit.
- Prepack: QuantType::W2_A16, 64-entry permutation map, generalized
sub-byte transpose over BITS_PER_ELT, int2 bias+interleave kernel, and
unpack_uint2_transposed_to_int8_direct_cuda. Zero-point packing is now
templated on ZeroPointBits (0/4/2).
- GEMV: Int2 details/traits, generalized warp_reduce_sum for interleave 8,
CtaN tuned for kStepK == 64.
- MatMulNBits: eligibility requires N % 128 == 0 and block_size >= 64 for
2-bit (one B fragment spans a full 64-element K tile), and rejects the
Hopper prepacked layout.
Adds an exact (A = I) layout round-trip regression test for 2- and 4-bit,
2-bit fpA_intB shape/production-shape tests, and kernel-level Int2 tests.
…ursor
The two-deep warp B register pipeline is indexed by the within-tile load
index, which only ping-pongs when kWarpGemmIterationsForB is even. 2-bit
weights make it 1, so the previous commit added a running read cursor.
That cursor is correct but flips an odd number of times per mainloop
iteration, so it is not loop-invariant: warp_frag_B becomes dynamically
indexed and ptxas gives the CtaShape128x128x64 kernel a 64-byte per-thread
stack frame (note the tell-tale "0 bytes spill stores, 0 bytes spill loads"
-- indexed local memory, not spilling). Rotating the two buffers instead
keeps both indices compile-time constants.
Measured on H200 (fp16, block_size=128, bonsai2-27B shapes), 2-bit GEMM:
-4.2% to -7.1% at M in {128, 512, 2048}, e.g. N=17408 K=5120 M=2048 goes
1811 -> 1703 us. 4- and 8-bit generated PTX is byte-identical to the
pre-2-bit baseline (verified by diffing ptx for both widths).
Also records in the docs that converting the B fragment one MMA k-step at a
time -- the previously suggested fix for the large-M gap -- was implemented
and measured as a 4-8% regression: it saves 6 registers but occupancy is
1 CTA/SM either way, so the gather is pure added ALU.
…l set 2-bit was reachable only in a full build: the compact source filter dropped every int2 translation unit, select_gs instantiated GroupSize 32 only, and the eligibility gate required block_size == 32 -- which 2-bit can never satisfy, since one B fragment spans a whole 64-element K tile. Adds the FP16 scale-only int2 variant at block_size 64, the smallest group 2-bit supports: - source filter keeps fp16_int2_gemm_scaleonly.cu and dispatcher_fp16_int2.cu - select_gs instantiates GroupSize 64, gated on Details::kStepK >= 64 so the int4/int8 units are unchanged - CheckFpAIntBEligibility and InitGemmProfiler accept bits=2 at block_size 64 with N % 128 == 0 Cost in a compact build: 4 -> 6 translation units, +1.5 MB of objects (dispatcher_fp16_int2 276 KB, fp16_int2_gemm_scaleonly 1259 KB, versus 283 KB / 1272 KB for the int4 pair already present). BF16, block_size 128 and zero-points still require onnxruntime_USE_FPA_INTB_GEMM_FULL=ON. Note that 2-bit models quantised with block_size 128 and zero-points -- the common case -- therefore still need the full build; this only covers the scale-only block_size 64 contract.
Records the analysis behind kPerm_W2_A16: it compensates for ldmatrix moving 16-bit elements when the weights are narrower, so it composes with the fixed ldmatrix pattern and converter shuffle to the identity and has no free parameters. Two checks back this up -- ncu measures 0 shared-memory bank conflicts in the 2-bit CtaShape128x128x64 kernel (4-bit is also 0, and the swizzled ColumnMajorTensorOpMultiplicandCrosswise smem layout is what handles conflicts), and perturbing the map fails the exact A=I round-trip test. Also corrects the sliced-conversion regression figure: 9-15% against the shipped rotation kernel, not 4-8% against the superseded cursor version.
Tianlei Wu (tianleiwu)
force-pushed
the
tlwu/matmul-nbits-2bit-fpa-intb
branch
from
September 22, 2026 11:35
5a3d5e3 to
7e698e3
Compare
David Fan (jiafatom)
approved these changes
Sep 22, 2026
3 tasks done
Tianlei Wu (tianleiwu)
added a commit
to microsoft/onnxruntime-genai
that referenced
this pull request
Sep 26, 2026
## Summary - Add canonical 2-bit `MatMulNBits` weight packing to the model builder. - Allow DFlash 2 body weights to use `int2` and select FP16 or BF16 compute through the structured `drafter_options.quant_config` schema introduced by #2592. - Support either offline SM80 prepacking through `format.matmulnbits_weights_prepacked` or CUDA runtime prepacking through `runtime_config.model.dflash2.session_options["ep.cuda.fpa_intb_gemm"]`. - Keep the target model and adopted embedding/LM-head tensors at their independently configured precision and block size. This PR is stacked on #2592 and should be reviewed as `171e3ea5...HEAD`. ## Configuration ```json { "builder_config_version": 2, "target_options": { "attention": {"implementation": "paged"} }, "drafter_options": { "drafter_type": "dflash2", "path": "Qwen3.8-27B-DFlash2", "num_draft_tokens": 7, "quant_config": { "io_dtype": "bf16", "weights": {"type": "int2", "block_size": 64} } }, "runtime_config": { "model": { "dflash2": { "session_options": {"ep.cuda.fpa_intb_gemm": "1"} } }, "speculative": {"max_draft_tokens": 7} } } ``` The DFlash body can use the standard raw QOperator layout for runtime prepacking or an offline SM80-prepacked layout. INT2 prepacking requires block size 64 or 128; BF16 x INT2 also requires a full fpA-intB kernel build. ## Runtime Dependencies - microsoft/onnxruntime#32693: base 2-bit `MatMulNBits` support. - microsoft/onnxruntime#32699: INT2 fpA-intB GEMM/GEMV support, including BF16 in the full kernel build. ## Validation - Configuration/precision: 180 passed. - DFlash/quantization: 190 passed, 3 skipped. - Full structured-config export of Qwen3.8-27B with 401 target INT4 block-32 nodes, 46 DFlash INT2 block-64 nodes, and one adopted INT4 block-32 LM head. - Debug-only dispatch check: all 230 observed INT2 calls used `runtime-prepacked(SM80 layout)` with `packing_sm=80`; no fallback/error lines. - Clean timing runs explicitly had `ORT_FPA_INTB_DEBUG` absent. | Context | INT2 block-64 fpA | Same artifact, fpA off | fpA gain | INT4 control | | ---: | ---: | ---: | ---: | ---: | | 2,048 | 289.8 tok/s | 226.4 tok/s | +28.0% | 298.1 tok/s | | 8,192 | 279.3 tok/s | 218.4 tok/s | +27.9% | 294.2 tok/s | fpA reduces acceptance-independent target-step time by about 21.8%. The remaining 2.8-5.1% throughput gap versus INT4 is primarily lower block-64 draft acceptance. MMLU-Pro 800 in thinking mode: INT2 block-64 fpA scored 652/800 (81.50%) versus 653/800 (81.625%) for the byte-identical-target INT4 control. The paired comparison had 14 INT2-only and 15 INT4-only correct answers (McNemar exact p=1.0), with zero unextracted answers. --------- 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 2-bit weight support to the CUDA CUTLASS fpA_intB GEMM and GEMV paths used by MatMulNBits. Eligible FP16 and BF16
bits=2workloads can use fused weight-only kernels instead of dequantize+cuBLAS while preserving existing 4-bit and 8-bit code generation.The branch is rebased onto
48795e0281, which enables BF16 in the compact fpA_intB build by default. Compact mode now includes FP16 and BF16 INT2 scale-only kernels atblock_size=64.Summary of Changes
INT2 fpA_intB kernels
N % 128 == 0,block_size64 or 128, and standard or SM80-prepacked weights. Native SM90-prepacked INT2 weights remain unsupported.Compact build
block_size=64.block_size=32.block_size=128, zero-point, bias, and native SM90-layout variants behindonnxruntime_USE_FPA_INTB_GEMM_FULL=ON.Coverage and documentation
A = Ilayout round-trip tests for INT2 and INT4 weights.Performance
Measured on H200 with FP16 activations and
block_size=128, the fused fpA_intB GEMV is fastest for every tested production shape atM=1(5.41-17.46 us versus 5.67-22.23 us for the hand-written 2-bit GEMV and 16.19-141.11 us for dequantize+cuBLAS). The register-buffer rotation improves INT2 GEMM by 4.2-7.1% forMin {128, 512, 2048}.Testing
onnxruntime_providers_cuda,onnxruntime_providers_cuda_ut,onnxruntime_provider_test, andonnxruntime_test_allwith CUDA 13.0, compact fpA_intB enabled, SM80/SM90 targets, and CUDA EP internal tests enabled.MatMul2BitsCuda.BFloat16_FpAIntBon A100 across four compact BF16/INT2 shapes.clang-format --dry-run --Werrorfor all changed C++/CUDA files andgit diff --check.bf16_int2_gemm_scaleonly.cuanddispatcher_bf16_int2.cu.Checklist