Repository navigation
2, 3, 5, 6 bit quantization exploration - #32657
David Fan (jiafatom) wants to merge 25 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The support matrix incorrectly reports that WebGPU MatMulNBits lacks 2-bit support.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Explores INT2/INT6 weight-only quantization and recommends a scoped CUDA INT2 implementation.
Changes:
- Documents current support, external approaches, packing options, evaluation criteria, and delivery estimates.
- Proposes phased CUDA INT2 development with INT6 as follow-up.
File summaries
| File | Description |
|---|---|
docs/design/2bit-6bit-weight-only-quantization-exploration.md |
Adds the quantization exploration and roadmap. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Thanks for putting this exploration together. From the Olive/Mobius side, we want to clarify the end-to-end model-production dependency for the proposed INT2 CUDA work. Even after ORT implements the CUDA
The current codebase has different gaps for these paths. Olive's PyTorch RTN/GPTQ/KQuant and SelectiveMixedPrecision already accept 2-bit configurations, but the INT2 checkpoint-to-ONNX path still needs end-to-end qualification. Olive's ONNX RTN and built-in quantized-linear export paths also need additional work for complete INT2 support. Mobius can construct We expect both workflows to be supported eventually, but could the document clarify which one is required for the initial CUDA vertical slice? Given the GPTQ, SMP, and mixed-precision quality goals, the PyTorch/Mobius path may be the more relevant first milestone, with ONNX-native INT2 RTN following for ONNX-only workflows. Could the scope also explicitly confirm whether the initial target is limited to dense |
|
Thanks for clarifying the model-production dependency. Updated in f20da36 with two explicit scope decisions:
The committed scope and dated schedule now reflect the export qualification work. |
There was a problem hiding this comment.
🔵 Needs a closer look
The support matrix inaccurately describes existing 2-bit QDQ packing and runtime fusion support.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
docs/design/2bit-6bit-weight-only-quantization-exploration.md:37
- This note misidentifies the remaining QDQ gap. A native 2-bit QDQ packer is already exported as
quantize_qdq_matmul_2bits(onnxruntime/python/onnxruntime_pybind_quant.cc:210-211), and runtime DQ→MatMulNBits(bits=2)fusion is implemented and tested (onnxruntime/core/optimizer/qdq_transformer/selectors_actions/qdq_actions.cc:24-26,296-300;onnxruntime/test/optimizer/qdq_matmulnbits_transformer_test.cc:763-768). What remains 4-bit-only is the high-level Python quantizer's QDQ branch (matmul_nbits_quantizer.py:1032-1037). Please describe that distinction so the roadmap does not plan already-completed native/runtime work.
- Files reviewed: 1/1 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
Added the INT2 QMoE investigation in e751729. The document now:
The November dense |
|
Added a standalone QMoE-first delivery plan in 19a723d: It defines the end-to-end path from Olive calibration and Mobius export through CPU reference, CUDA correctness, packed decode, bounded/native prefill, and Qwen quality/performance qualification. The initial target is mixed FC1 INT2 / FC2 INT4, with backward-compatible schema semantics and explicit separation from IQ2_XS/IQ4_NL. The proposed eight-week milestone is contract + reproducible export + CPU reference + bounded CUDA correctness + packed decode on one architecture. Production grouped-GEMM prefill and broad architecture coverage require follow-up time or additional CUDA staffing. The original exploration document now points to this plan and identifies QMoE as the product priority. |
|
Extended the MatMulNBits exploration in 929ee53 to include INT3 and INT5 alongside INT6. The update adds:
INT2 QMoE remains the product priority. INT3/INT5/INT6 are explicitly a parallel measured research track, with INT3 the first optimization candidate only if the data supports it. |
|
Thanks for making mixed-width QMoE the explicit product priority. We aligned the
The existing From the downstream side, we propose treating the ORT mixed-width QMoE contract
Before that contract is fixed, we will keep work schema-neutral: checkpoint Please also confirm whether native Mobius QMoE construction is sufficient for |
### Description
Add the schema and validation contract needed for mixed-width QMoE
expert weights.
- Add optional `fc1_expert_weight_bits`, `fc2_expert_weight_bits`, and
`fc3_expert_weight_bits` attributes, inheriting from
`expert_weight_bits` when omitted.
- Pass effective bit widths through shared validation and compute packed
byte dimensions as `logical_elements * bits / 8`.
- Require byte-aligned weight rows while allowing zero-point rows to pad
to a whole byte.
- Preserve legacy single-`pack_size` helper overloads and existing 2/4/8
validation diagnostics.
- Wire effective widths through CPU, CUDA, and WebGPU QMoE providers.
- Require matching FC1/FC3 widths for fused SwiGLU.
- Bypass legacy prepacking and return an explicit `NOT_IMPLEMENTED`
status for mixed-width execution. Execution support will follow
separately.
- Preserve the existing uniform-width behavior and schema version.
### Attribute semantics
`expert_weight_bits` remains the legacy global value and the fallback
for each optional projection override:
```text
effective_fc1_bits = fc1_expert_weight_bits if present, otherwise expert_weight_bits
effective_fc2_bits = fc2_expert_weight_bits if present, otherwise expert_weight_bits
effective_fc3_bits = fc3_expert_weight_bits if present, otherwise expert_weight_bits
```
If an override is omitted, it is never left uninitialized: it inherits
`expert_weight_bits`, whose schema default is 4. If all three overrides
are present, each override takes precedence for its corresponding
projection. All effective values are currently restricted to 2, 4, or 8
bits. Fused SwiGLU additionally requires effective FC1 and FC3 widths to
match.
Examples:
- `expert=4`, no overrides -> effective widths `(4, 4, 4)`, existing
uniform 4-bit path.
- `expert=4`, `fc1=2`, `fc2=4`, `fc3=2` -> effective widths `(2, 4, 2)`,
mixed-width contract path.
- `expert=4`, `fc1=2`, `fc2=2`, `fc3=2` -> effective widths `(2, 2, 2)`,
but currently still treated as an override/mixed configuration because
the effective widths differ from the legacy global value.
- For a uniform 2-bit model, use `expert_weight_bits=2` and omit the
overrides.
### Current execution boundary
For this contract-only change, `is_mixed_width` means that at least one
effective projection width differs from `expert_weight_bits`:
```text
is_mixed_width =
effective_fc1_bits != expert_weight_bits ||
effective_fc2_bits != expert_weight_bits ||
effective_fc3_bits != expert_weight_bits
```
This intentionally rejects even `expert=4, fc1=2, fc2=2, fc3=2`. Shape
and zero-point validation use the effective 2-bit widths, while the
existing execution and dispatch paths still use the legacy global width.
Allowing execution in that state could interpret 2-bit data as 4-bit
data. The explicit rejection prevents silent incorrect results.
Follow-up execution support must make prepacking, scale/zero-point
interpretation, packed layouts, workspace sizing, and kernel dispatch
projection-aware. Once dispatch consumes effective widths, uniformly
overridden configurations can be normalized to one effective width,
while genuinely mixed configurations can dispatch FC1/FC2/FC3
independently.
### Testing
- Built `onnxruntime_provider_test` with CUDA enabled.
- Added CPU contract and invalid-shape tests.
- Added row-wise and block-wise mixed-width zero-point validation,
including invalid FC2 shapes.
- Added availability-gated CUDA and WebGPU provider contract tests.
- Preserved the legacy INT2 invalid-hidden-size diagnostic.
- Covered arbitrary 3/5/6-bit packed-byte calculations in the shared
helper.
- Repository lintrunner passes for the changed files.
Related to #32657.
## Summary - qualify synthetic Olive-format uniform INT2 checkpoints through the public local `build()` path - qualify mixed default INT2 with INT4 `down_proj` and INT8 `gate_proj` overrides - save and reload the Mobius package, then verify exact `MatMulNBits` attributes, input wiring, packed bytes, scales, and initializer binding - execute representative emitted INT2/INT4/INT8 projections explicitly on `CPUExecutionProvider` against an independent Olive-compatible decode ## Scope This validates the Olive checkpoint format/import/export contract and CPU operator semantics. It does **not** claim CUDA INT2 kernel readiness or performance, and it does not cover QMoE/fused MoE. No Mobius production changes were required after #733. ## Validation - `python -m pytest src/mobius/_component_quantization_loading_test.py -q --tb=short` (61 passed) - `python -m ruff check src/mobius/_component_quantization_loading_test.py` - `python -m ruff format --check src/mobius/_component_quantization_loading_test.py` - `git diff --check` Related: microsoft/onnxruntime#32657, microsoft/Olive#2669 --------- Signed-off-by: titaiwang <titaiwang@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
929ee53 to
8831424
Compare
## Describe your changes Add offline qualification coverage for the Olive side of the initial dense INT2 model-production workflow discussed in microsoft/onnxruntime#32657. - Verify native PyTorch RTN, GPTQ, and KQuant produce real uniformly packed INT2 weights and preserve them across checkpoint save/reload. - Verify `SelectiveMixedPrecision` drives each quantizer with an INT2 default, an SMP-selected INT4 projection, and an explicit INT8 projection override. - Inspect reloaded `QuantTensor` settings and packed safetensors buffers instead of relying on checkpoint metadata alone. - Check RTN weights against the original floating-point weights within the quantization-scale bound. - Check GPTQ checkpoint reconstruction and module execution using deterministic, varied, fully local calibration sequences. - Check KQuant checkpoint materialization against a directly recomputed KQuant result. - Keep all tests offline with a locally constructed tiny dense Llama model and tokenizer. No production code changes were required: the existing native RTN, GPTQ, KQuant, SMP, serialization, and reload paths already materialize dense INT2 and mixed INT2/INT4/INT8 checkpoints correctly. Mobius export qualification, ONNX-native INT2 RTN, and MoE/QMoE remain separate follow-up work. Validation: ```text python -m pytest -q \ test/passes/pytorch/test_dense_int2.py \ test/passes/pytorch/test_rtn.py::test_rtn_int2_dense_checkpoint_packing_and_roundtrip \ test/passes/pytorch/test_rtn.py::test_selective_mixed_precision_rtn_int2_int4_int8_checkpoint 6 passed python -m ruff format --check \ test/passes/pytorch/test_dense_int2.py \ test/passes/pytorch/quantization_test_utils.py \ test/passes/pytorch/test_rtn.py python -m ruff check --ignore CPY001 \ test/passes/pytorch/test_dense_int2.py \ test/passes/pytorch/quantization_test_utils.py \ test/passes/pytorch/test_rtn.py git diff --check ``` ## Checklist before requesting a review - [x] Add unit tests for this change. - [x] Make sure all targeted tests can pass. - [x] Update documents if necessary. (No user-facing behavior changed.) - [ ] Lint and apply fixes to your code by running `lintrunner -a`. (Targeted Ruff checks pass; the existing `test_rtn.py` has a pre-existing CPY001 finding.) - [x] Is this a user-facing change? If yes, give a description of this change to be included in the release notes. (Tests only; no release note required.) ## (Optional) Issue link Supports the INT2 model-production scope in microsoft/onnxruntime#32657. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2eb9d05 to
1d78f64
Compare
|
Thanks for aligning the downstream tracking. The mixed-width contract is now fixed by #32697, merged as
Native Mobius QMoE construction is sufficient for the first exporter milestone. Dense-graph-to-QMoE fusion should follow separately and should not block the deterministic fused-QMoE fixture/export path. I recorded that milestone boundary and the Olive#2638/Mobius#735 ownership in |
35f76b8 to
83581ec
Compare
### Description Implements Workstream 4, PR 4a of the delivery plan in #32657: an independently testable kernel foundation, not the QMoE prefill integration planned for PR 4b. - Add packed symmetric W2A16 grouped GEMM for SM80, FP16/BF16 input/output, block size 64 and FP32 accumulation, without full global-memory A16 expert-weight materialization or caller-supplied GEMM workspace. - Support explicit CTA 32x128x64 / warp 32x32x64 and CTA 64x128x64 / warp 32x64x64, both with three stages. Keep 32 rows as the default; no automatic tactic selection. - Add scalar-reference parity tests, production W4/W8 regressions, GPT-OSS-20B-shaped balanced/skewed cases, and opt-in CUDA-event benchmarks against W4 and dense FP16 baselines. - Register tests and implementation dependencies for legacy and plugin CUDA builds. Dense runner template instantiation also requires the existing five FP16 SM80 fused launchers. - Fix stale routing_record API usage in packed INT decode, exposed by the clean legacy build, in a separate commit. No QMoE prefill dispatch, bias/activation epilogue, SwiGLU/finalization integration, or additional architecture support is enabled. Mixed FC1/FC2 execution, lifetimes, runtime dispatch and model-level qualification remain PR 4b work. Validation notes are kept in this description rather than a repository Markdown artifact. ### Motivation and Context Provide the native packed INT2 grouped-GEMM building block needed to replace full expert-weight dequantization in mixed INT2/INT4 QMoE prefill. Related design: #32657; packed decode foundation: #32761. ### Kernel Contract For E experts, P routed rows, N output columns and K reduction elements: | Buffer | Layout | | --- | --- | | activations | FP16 or BF16 [P,K], contiguous rows grouped by expert | | packed_weights | E consecutive SM80 fpA_intB prepacked INT2 matrices, K*N/4 bytes each | | block_scales | Same dtype as activations [E,K/64,N] | | expert_row_ends | E device int64 cumulative exclusive row ends | | output | Same dtype as activations [P,N], same grouped row order | N and K must be positive multiples of 64; partial M/N tiles and empty experts are supported. P must be positive and fit in int32, and E must be positive. IsInt2GroupedGemmSupported checks metadata; RunInt2GroupedGemm also requires non-null buffers and checks 16-byte alignment of activations, packed_weights, block_scales, and output before invoking CUTLASS. The caller supplies the actual SM/SM count, keeps buffers alive until stream completion, ensures matching capacities and nonoverlapping output, and guarantees nonnegative, nondecreasing row ends terminating at P. Repeated ends represent empty experts; device offset contents are not validated on the host. Raw unsigned codes represent (code - 2) * scale. Convert raw [E,N,K/4] weights using unpack_uint2_transposed_to_int8_direct_cuda and preprocess_weights_for_mixed_gemm_cuda with SM80/W2_A16. Preprocessing mutates its scratch input. GEMM converts tiles internally and performs no routing or finalization. ### Local Validation Latest alignment fix (2edffb5): rebuilt and validated in jiafa-dev on A100. Both legacy and plugin passed all 20 enabled grouped tests, including FP16/BF16 rejection tests for each of the four buffers at an 8-byte offset and both tile configurations. Both configurations also passed all 20 grouped tests under Compute Sanitizer memcheck with zero errors. Host /tmp inode exhaustion was resolved before these runs. A100-SXM4-80GB, CUDA 12.8.61, GCC 13.3, Release, SM80, internal tests enabled, QUICK_BUILD=OFF; September 29-30, 2026. Separate build trees started empty without old objects or compiler caches, then fixes were rebuilt in those trees. - Legacy (2edffb5): all 20 grouped tests passed; all 20 passed under Compute Sanitizer memcheck with zero errors. Existing QMoE regression: 53 passed, 11 skipped. - Plugin (2edffb5): all 20 grouped tests passed; all 20 passed under Compute Sanitizer memcheck with zero errors. The combined Int2GroupedGemm*:*QMoE* run had 75 passed and 12 skipped (20 grouped tests plus 55 passing QMoE-related tests, including three directly registered FP4-to-FP8 internal tests). - Plugin production compilation used local CMAKE_CUDA_FLAGS="--diag-suppress=970 --diag-suppress=2189" for Abseil/Protobuf diagnostics. This is not evidence that default-toolchain CI passes. - Small cases compare every output against a scalar oracle, covering FP16 and BF16, minimum alignment, empty experts, partial tiles, dense parity, and the 32-row and 64-row candidates. Production W4/W8 block-64 paths are exercised directly. - Model-shaped cases use E=32, top-k=4, K=2880, FC1 N=5760, FC2 N=2880, T=128/512/2048 and P=512/2048/8192. FP16 covers balanced and skewed routing at all three sizes; BF16 covers T=128. They check all outputs for finiteness and sampled first/middle/last expert rows and columns on both sides of 64-column boundaries against the oracle. - Mixtral INT4 skips because its dimensions are too small; it is not counted as CUDA INT4 coverage. Plugin routing-statistics testing also skips because that EP does not support it. CPU-specific skips are not counted as validation. - Full onnxruntime_test_all suite was not run. Three opt-in benchmarks are disabled by default. P is routed rows, not model input tokens T. Run from the appropriate build directory with an available GPU: ```bash # Legacy internal-test loader GTEST_FILTER="Int2GroupedGemm*" ./onnxruntime_provider_test --gtest_filter=CUDA_EP_Unittest.All ./onnxruntime_provider_test --gtest_filter="*QMoE*" GTEST_FILTER="Int2GroupedGemm*" compute-sanitizer --tool memcheck --error-exitcode 99 \ ./onnxruntime_provider_test --gtest_filter=CUDA_EP_Unittest.All # Plugin tests are directly registered ./onnxruntime_provider_test --gtest_filter="Int2GroupedGemm*:*QMoE*" compute-sanitizer --tool memcheck --error-exitcode 99 \ ./onnxruntime_provider_test --gtest_filter="Int2GroupedGemm*" # Opt-in legacy benchmark; use DISABLED_GptOssKernelBenchmark for W4 comparison GTEST_FILTER="Int2GroupedGemmTest.DISABLED_GptOssTacticBenchmark" \ GTEST_ALSO_RUN_DISABLED_TESTS=1 \ ./onnxruntime_provider_test --gtest_filter=CUDA_EP_Unittest.All ``` ### Initial Performance Evidence Five warmups after an initial launch; three trials of 30 launches on a nonblocking stream; median per-launch CUDA-event milliseconds. This measures stream elapsed time around host-submitted launches, not isolated profiler kernel duration. Allocation, packing, H2D/D2H transfers and the reference computation are outside GEMM timing. First run, balanced routing, E=32, top-k=4, K=2880, FC1 N=5760, FC2 N=2880, tile32: | T | P | W2 FC1 | W4 FC1 | W4 FC2 | | --- | --- | --- | --- | --- | | 128 | 512 | 0.403695 | 0.391612 | 0.212548 | | 512 | 2048 | 0.760764 | 0.766020 | 0.323072 | | 2048 | 8192 | 2.476240 | 2.570210 | 1.253720 | W4 uses a different quantized value distribution, not an equal-quality model baseline. W2 and W4 are close; the T=2048 W2 trial range was 2.47624-2.98469 ms, so this does not establish a robust speedup. Second run compares identical INT2 codes, activations, scales and offsets. The dense path uses production DequantizeNBits and MoeGemmRunner<half,half,half>. Cached dense dequantizes once before timing; Dequant+dense dequantizes all experts before every GEMM. Allocations are excluded. This is not an active-experts-only or tiled-dequantization baseline. | Routing | T | W2 tile32 | W2 tile64 | Cached dense tile32 | Cached dense tile64 | Dequant+dense tile32 | Dequant+dense tile64 | | --- | --- | --- | --- | --- | --- | --- | --- | | Balanced | 128 | 0.404 | 0.602 | 0.651 | 0.663 | 1.538 | 1.548 | | Balanced | 512 | 0.761 | 0.605 | 0.884 | 0.702 | 1.765 | 1.586 | | Balanced | 2048 | 2.728 | 1.905 | 2.375 | 1.905 | 3.678 | 2.765 | | Skewed | 128 | 0.233 | 0.228 | 0.217 | 0.189 | 1.097 | 1.071 | | Skewed | 512 | 0.798 | 0.641 | 0.737 | 0.488 | 1.510 | 1.353 | | Skewed | 2048 | 2.819 | 1.979 | 2.532 | 1.700 | 3.139 | 2.585 | Balanced experts receive T/8 rows each. Skewed counts are 1,31,T-32,T,T,T, with the other experts empty; total 4T. Each benchmark case passed sampled parity and finiteness checks. GPU 1 had no competing compute clients at the first measurement start. Clocks were unlocked, execution order fixed, and CPU plugin compilation ran concurrently during the second run. Repeat controlled measurements before selecting dispatch thresholds or asserting stable speedups. Tile64 helps larger per-expert M but is slower at balanced T=128; default remains tile32. Cached dense GEMM can be faster, especially with skew. A tile128 W2 candidate was rejected by CUTLASS thread-map compile assertions and is not exposed. These are not full prefill, routing, activation, finalization or TTFT measurements. ### Prepack and Memory Accounting One-pass prepack event times were 23.1-24.6 ms (W2 FC1), 24.1-24.3 ms (W4 FC1) and 10.2-12.5 ms (W4 FC2), including transpose/preprocessing launches and possible host submission gaps, not steady-state GEMM overhead. | Buffer | W2 FC1 bytes | W4 FC1 bytes | W4 FC2 bytes | | --- | --- | --- | --- | | Packed weights | 132710400 | 265420800 | 132710400 | | A16 scales | 16588800 | 16588800 | 8294400 | | Prepack scratch | 4147456 | 8294656 | 4147456 | Tests also retain a raw GPU copy equal to packed-weight size, activations, outputs and offsets. Dense FC1 adds 1,061,683,200 bytes (1012.5 MiB). These are buffer capacities, not measured peak process memory. W2 halves FC1 packed-weight bytes, not total model memory. ### Remaining Qualification - Supported-platform CI, including the default plugin toolchain configuration, remains separate from local validation. - Controlled timing repeats are needed before automatic tactic selection or performance gates. - Profiler evidence and peak allocation measurements are needed before end-to-end memory/prefill claims. - Current commit 2edffb5 passed FP16/BF16 grouped regression, alignment rejection coverage, and grouped memcheck in both legacy and plugin builds. The full test suite and opt-in benchmarks were not rerun for this update. - PR 4b will integrate routing, mixed FC1/FC2, SwiGLU/finalization, lifetimes and model-level evaluation.
|
Please see onnx/onnx#8576 ... whether it makes sense |
a05da07 to
1340936
Compare
|
G. Ramalingam (@gramalingam) Thank you for the feedback in #32657 (comment) and for pointing me to onnx/onnx#8576 in #32657 (comment). I reviewed the current Pack/Unpack proposal and updated the INT3 QMoE delivery plan in commit 1340936:
This keeps the standard interchange semantics separate from backend execution. CUDA may still fuse decoding with computation or convert the portable bytes into a provider-private prepack; the reference graph does not require production execution to materialize all experts as floating-point weights. Since #8576 is still an open proposal, the plan records the reviewed revision and does not treat its draft operator/opset as a released dependency. Until it is approved and supported, the reference unpack stage can use an equivalent decomposition of existing standard operators. Native INT3/UINT3 datatype standardization remains outside this delivery sequence. These are plan and acceptance-criteria updates, not a claim that INT3 QMoE kernels or the interoperability tests are already implemented. |
## Summary Add projection-specific QMoE export for Olive fused expert checkpoints. FC1 and FC2 independently support INT2, INT3, INT4, and INT8: all 16 pairs are accepted subject to format validation, without an experimental opt-in. This is **export support, not runtime qualification**. INT3 follows the proposed raw-weight direction in microsoft/onnxruntime#32657 and consumes the native Torch INT3 checkpoint format introduced by microsoft/Olive#2716. INT3 output retains draft-format/runtime-warning metadata; this PR does not provide INT3 ORT kernels or claim that released ORT can execute it. ## Changes - Resolve FC1/FC2 widths independently instead of using an explicit pair whitelist. This intentionally includes `(2,3)`, `(3,2)`, `(3,8)`, `(8,3)`, `(2,2)`, and `(2,8)`, in addition to the other valid pairs. - Remove the experimental INT3 flag and opt-in rejection. Export requires an explicitly quantized checkpoint with compatible projection configurations; ordinary unquantized models do not become INT3 automatically. - Require exact Olive expert overrides and a supported model-wide INT4 fallback for projection-specific layouts. Preserve the existing uniform INT4 path. - Preserve row-local, LSB-first packed unsigned INT3 codes with symmetric zero point 4. For pairs containing INT3, interleave FC1 gate/up payloads lazily during serialization in public HF builds; direct preprocessing remains eager by default. Otherwise retain graph-side interleaving. Preserve FC2's K-last layout without requantization. - Validate shared power-of-two group sizes, aligned dimensions, sidecar shapes, packed zero points, and finite nonnegative scales. Reject scale conversion overflow when the adapter supplies the activation dtype. INT3-containing pairs additionally require block size 32/64/128 and FP16/BF16 activation/scale agreement. Non-INT3 layouts retain the standard loader's dtype conversion. - Apply the same numeric scale checks to the legacy uniform INT4 path without imposing the projection-specific shape ABI. Rejecting previously accepted negative, NaN, infinite, or activation-cast-overflowing scales is an intentional validation tightening. - Bind deferred FC1 weights, scales, and asymmetric zero points through uncached lazy tensors, rather than retaining transformed copies for every layer. Keep transform accounting component-scoped when prefix stripping produces identical local initializer names. - Preserve INT2/INT4/INT8 zero-point semantics and validate optional explicit offset-4 INT3 zero points before omitting them from the graph. - Emit projection-width attributes where needed and retain INT3 draft-format metadata without assigning an unapproved schema version. - Cover all 16 pairs across FP16/BF16, independent packing/dequantization oracles, CPU execution of graph-side interleaving, invalid scales, local-checkpoint public builds, and external-data save/reload. - Pass the model activation dtype through all three Qwen3.5-MoE preprocessing paths: text, VL global quantization, and VL decoder component quantization. Add 57 regression cases covering all seven INT3-containing pairs across FP16/BF16, incompatible dtypes, and non-INT3 scale conversion behavior. ## Validation Latest offline CPU validation includes the merged `main`, Qwen3.5-MoE dtype propagation, uniform scale checks, deferred serialization, and component routing: ```bash PYTHONPATH="$PWD/src" HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 \ TOKENIZERS_PARALLELISM=false PYTHONDONTWRITEBYTECODE=1 CUDA_VISIBLE_DEVICES='' \ timeout 300 /home/titaiwang/miniconda3/envs/dev/bin/python -m pytest \ --rootdir="$PWD" -c "$PWD/pyproject.toml" \ src/mobius/models/int3_qmoe_test.py src/mobius/models/qwen35_test.py \ src/mobius/models/moe_test.py \ src/mobius/_weight_utils_test.py src/mobius/_flags_test.py \ src/mobius/components/_moe_test.py \ src/mobius/_model_package_test.py::TestModelPackageApplyWeights \ src/mobius/weights/_adapters_test.py \ src/mobius/integrations/_weight_loading_test.py \ src/mobius/integrations/transformers/_builder_test.py \ -q -m 'not integration and not arch_validation and not golden and not generation' \ --tb=short ``` **Passed: 728 tests, 1 deselected, in 52.36 seconds.** Coverage includes distinct per-layer payloads, source-storage aliasing, release of transformed tensors between callbacks, repeated saves, asymmetric FC1 zero points, and positive/negative component-local-name collision regressions. ```bash lintrunner src/mobius/_model_package.py src/mobius/_model_package_test.py \ src/mobius/_weight_utils.py src/mobius/integrations/_weight_loading.py \ src/mobius/integrations/transformers/_builder.py src/mobius/models/granite.py \ src/mobius/models/int3_qmoe_test.py src/mobius/models/llada.py \ src/mobius/models/moe.py src/mobius/models/qwen35.py \ src/mobius/models/qwen35_test.py src/mobius/weights/_adapters.py git diff --check ``` **Both passed.** Ruff formatting and checking also passed for the same 12 Python files. Scoped mypy reported 77 errors outside changed hunks; no project-wide type-check success or fully established clean baseline is claimed. ## Draft boundaries - All 16 pairs are exporter-supported layouts, not a runtime-qualified matrix. Execution depends on the target ORT version and execution provider. - Source checkpoint loading remains eager. Uncached lazy interleaving bounds additional FC1 payload retention, not total checkpoint or serialization peak memory. No full-checkpoint RSS or concurrent-writer qualification was run. - Direct callers opting into deferred preprocessing must forward the populated transform map when binding the returned weights. Explicit adapters and older preprocessing hooks retain their existing behavior. - No INT3 ORT kernel execution, checkpoint-to-runtime logits parity, real-model quality, or performance qualification was run. The CPU interleave tests exercise standard ONNX graph operations, not QMoE kernels. - Dense INT3 `MatMulNBits`, quantized embeddings, unsupported source layouts, and schema-unapproved tails are not enabled. - Final QMoE schema/version agreement, complete quantization provenance, and runtime qualification remain follow-up gates. This does not complete the end-to-end plan's P5 acceptance gate. - INT2 runtime/CI qualification is separate in #765 and is not included in this PR. --------- Signed-off-by: titaiwang <titaiwang@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fba8a408-cd63-49f0-b8b4-5daa19ed42d4
Description
Documents the design space for 2-, 3-, 5-, and 6-bit weight-only quantization and defines the end-to-end delivery plan for mixed-width INT2 QMoE. The prioritized QMoE recipe uses INT2 expert gate/up weights and INT4 expert down weights while keeping the portable model format separate from provider-specific runtime prepacking.
QMoE Progress (October 6, 2026)
a4d55e2b6f): SM80 FP16/BF16 INT2 grouped GEMM foundation, including the corrected byte-address alignment contract.78c834918d): SM8x test coverage, plugin test build footprint, diagnostic options and requested file headers. The QUICK_BUILD baseline tactic issue was addressed before merge; it is no longer an outstanding delivery task. The redundant quantization fix is already in main via Replace old use ofrouting_recordwithrouting_snapshot_in onnxruntime/contrib_ops/cuda/moe/moe_quantization.cc. #32932.jiafa-dev: actual CUDA 12 legacy provider-test target built successfully; 23 grouped tests passed (3 benchmarks disabled), QMoE regression 53 passed / 11 skipped. Both passed memcheck with zero errors/leaks. Actual plugin internal-test target and fixture compiled; a standalone harness linked from these generated objects passed 16 tests under memcheck. Standalone CUDA 12/13 plugin and CUDA 12 legacy harnesses also passed memcheck/synccheck/initcheck and three focused racecheck cases. These results do not establish SM86/SM89 coverage. That local full CUDA 12 plugin provider build was blocked by third-party-header warning-as-error diagnostics; the historical standalone results are not a full plugin integration pass.2cdff3ca8e): default-enabled SM80+ FP16/BF16 packed INT2 FC1 / INT4 FC2 prefill, symmetric quantization, block size 64 and interleaved fused SwiGLU. SetORT_ENABLE_QMOE_INT2_PREFILL=0to opt out. Packed decode retains priority;ORT_DISABLE_MOE_GEMV=1disables eligible INT2 and existing INT4/INT8 GEMV dispatch. Set environment controls before process startup.0c67e77bb7cb): extends packed prefill to block sizes 32, 64 and 128 and(FC1, FC2)widths(2,4),(2,2)and(4,2), dispatching each projection to the INT2 or INT4 grouped GEMM. Existing(4,4), INT8, zero-point and non-fused paths and width inheritance defaults are unchanged. Block size 32 has no GEMV path. Final-head local validation atdb8a7625b8on A100 with non-plugin Release CUDA 12.8, BF16 and internal tests enabled passed 15 outer tests and 26 internal tests, with no failures/skips (three benchmarks disabled). Coverage includes the 216-invocation FP16/BF16 integration matrix, direct grouped-GEMM block/tile cases, cached-scale decode-to-prefill transition, packed-decode fallback and block32 dense fallback with prefill disabled. Validation details: Extend CUDA QMoE packed INT2 prefill block sizes and weight combinations #33092 (comment) . This is local A100 correctness evidence, not plugin integration, every-GPU coverage or a performance claim.463fa42cb5. Full mixed GPT-OSS-20B validation demonstrated GEMM prefill -> GEMV decode in one session, with finite logits/KV and explicit branch counts (24 prefill and 48 decode calls for a 309-token prompt). These results predate the final default-dispatch and packed-tiling changes and must not be treated as validation of those revisions.Branch and Documentation Update
Rebased onto main at
2cdff3ca8e(including PR4a2 and PR4b), without conflicts. Both design documents record the merged work and distinguish historical estimates from remaining qualification tasks. The October 6 update records merged PR4c (#33092,0c67e77bb7cb) and its final-head local A100 validation in both design documents, including the expanded support boundary and remaining qualification limits. This roadmap update is documentation-only; no new build or runtime tests were run for this update.Motivation and Context
Expert weights dominate storage and memory traffic in the target Qwen MoE models. This plan prioritizes a deployable mixed-width QMoE path while reusing dense
MatMulNBitspacking, validation, and CUDA primitives where useful. It does not treat the bounded dense fallback as production performance support, and it does not claim compatibility with GGUFIQ2_XSorIQ4_NLformats.