You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add CUDA support for affine 2-bit MatMulNBits weights. The change adds 2-bit dequantization and specialized GEMV/batched kernels, dispatches fp16, fp32, and bf16 inputs, and makes packed weight and zero-point offsets depend on the configured bit width.
The exported Bonsai graph contract was verified directly: 401 MatMulNBits nodes use bits=2, block_size=128, fp16 activations, packed uint8 affine zero points, and no g_idx input. CUDA supports 2-bit block sizes from 16 through 256, matching MLAS, and rejects g_idx explicitly.
Validation on CUDA 13.0 / A100:
onnxruntime_provider_test --gtest_filter='MatMul2BitsCuda.*:*GatherBlockQuantized*Cuda*'
[==========] 24 tests from 2 test suites ran.
[ PASSED ] 24 tests.
This includes all 17 MatMul2BitsCuda tests, typed and packed zero-point fallback coverage, forced chunked fallback with and without packed zero points, validation failures for unsupported g_idx and oversized blocks, and the production QKV dimensions at M=1 and the fallback boundary M=32, N=10240, K=5120, block_size=128.
Motivation and Context
Models with native ternary weights export four 2-bit values per byte through the existing MatMulNBits affine ABI. CUDA previously accepted this schema but lacked a correct 2-bit execution path. This change implements that path and rejects unsupported widths and input combinations instead of interpreting them with an incompatible packed stride.
The reason will be displayed to describe this comment to others. Learn more.
π‘ Changes recommended
The implementation has SM52 build compatibility and reorder correctness defects, while CPU fallback and missing chunked coverage weaken the CUDA tests.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds CUDA support for affine 2-bit MatMulNBits, including fused small-M kernels and generic dequantization fallback.
Changes:
Adds FP16, FP32, and BF16 2-bit GEMV/batched kernels.
Extends dequantization, packed offsets, and bit-width validation.
Adds CUDA correctness, ternary-weight, and production-shape tests.
This direct CUDA test also leaves CPU fallback enabled, so it can validate the CPU implementation instead of the new CUDA path if CUDA does not claim the node. Disable CPU EP fallback in the session options before running it.
This negative test can pass through CPU fallback because the CPU kernel also rejects bits=3 with an error containing bits; it therefore does not prove that the new CUDA constructor guard ran. Disable CPU EP fallback before asserting the CUDA-specific failure.
Akshay Sonawane (@apsonawane) Updated docs/contrib_ops/cuda/matmul_nbits.md with the 2-bit CUDA support details, the MLAS-aligned 16β256 block-size range, unsupported g_idx, and the relevant implementation/test references.
Fix indentation of entries in section 4 nested list
docs/βcontrib_ops/βcuda/βmatmul_nbits.md:33
These three entries are indented by only two spaces, unlike the adjacent 4.1 entry, so Markdown renders them outside the section 4 nested list. Align their indentation to keep the table of contents hierarchy intact.
## 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
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
Add CUDA support for affine 2-bit
MatMulNBitsweights. The change adds 2-bit dequantization and specialized GEMV/batched kernels, dispatches fp16, fp32, and bf16 inputs, and makes packed weight and zero-point offsets depend on the configured bit width.The exported Bonsai graph contract was verified directly: 401
MatMulNBitsnodes usebits=2,block_size=128, fp16 activations, packeduint8affine zero points, and nog_idxinput. CUDA supports 2-bit block sizes from 16 through 256, matching MLAS, and rejectsg_idxexplicitly.Validation on CUDA 13.0 / A100:
This includes all 17
MatMul2BitsCudatests, typed and packed zero-point fallback coverage, forced chunked fallback with and without packed zero points, validation failures for unsupportedg_idxand oversized blocks, and the production QKV dimensions atM=1and the fallback boundaryM=32, N=10240, K=5120, block_size=128.Motivation and Context
Models with native ternary weights export four 2-bit values per byte through the existing
MatMulNBitsaffine ABI. CUDA previously accepted this schema but lacked a correct 2-bit execution path. This change implements that path and rejects unsupported widths and input combinations instead of interpreting them with an incompatible packed stride.