onnxruntime
f7c2c2dd - [MLAS] Add AVX512 (+VNNI) 2-bit weight CPU kernels (#29064)

Commit
31 days ago
[MLAS] Add AVX512 (+VNNI) 2-bit weight CPU kernels (#29064) ### Description Today the only high-performance MLAS kernels for 2-bit weights are the LUT-based kernels. They under-utilize AVX-512 (e.g. they don't issue VNNI dot-product instructions), come with shape constraints (most notably `N` must be a multiple of 128), and — most importantly — under-perform on prefill regimes where the GEMM is compute-bound rather than weight-bandwidth-bound. Some of the shape limitations are being chipped away at incrementally (e.g. [#28528](https://github.com/microsoft/onnxruntime/pull/28528)), but the underlying performance gap remains. This PR addresses the gap directly by adding a "traditional" non-LUT 2-bit weight AVX-512 (+VNNI) GEMM kernel to MLAS, modeled on the existing 4-bit and 8-bit kernels in the same family. It supports `BlkLen` ∈ {32, 64, 128} and runs on any shape (no `N % 128 == 0` constraint). A real customer model heavily benefits from this change. The customer's model has 120 W2 `MatMulNBits` nodes. ~80 of them have shapes the LUT kernel supports (`N % 128 == 0`); the remaining ~40 do not. On `main`, those 40 nodes always fell through to the slow fp32 dequant + SGEMM fallback regardless of the LUT session option. This PR replaces that fallback with a native AVX-512(+VNNI) W2 kernel and adds a fast non-LUT path for all 120 nodes (the new kernel beats W2 LUT on the overlapping 80 nodes at prefill shapes and roughly matches it on decode shapes). ### Per-node routing | Branch | LUT mode | LUT-supported nodes (~80) | LUT-unsupported nodes (~40) | |---|---|---|---| | main | LUT off (default) | fp32 dequant + SGEMM | fp32 dequant + SGEMM | | main | LUT on | LUT kernel | fp32 dequant + SGEMM | | This PR | LUT off (default) | new W2 AVX-512(+VNNI) kernel | new W2 AVX-512(+VNNI) kernel | | This PR | LUT on | LUT kernel | new W2 AVX-512(+VNNI) kernel | ### End-to-end throughput (tokens/sec, higher is better) | Branch | LUT mode | seq=32 | seq=64 | seq=128 | |---|---|---:|---:|---:| | main | LUT on (80 LUT + 40 fp32 fallback) | 587 | 538 | 554 | | This PR | LUT on (80 LUT + 40 new kernel) | 681 | 722 | 764 | | This PR | LUT off (120 new kernel) | 1179 | 1338 | 1487 | | W4 baseline | n/a | 1245 | 1445 | 1647 | **Headline:** With this PR + the non-LUT path, W2 throughput is now within ~5–10% of W4. The remaining gap is the W2 "unpack tax" inherent to 2-bit weight kernels (more bytes-per-output-element to unpack vs. 4-bit). ## Behavior with `mlas.use_lut_gemm` opt-in User-facing dispatch precedence is unchanged. When the user opts into LUT via the session config, LUT is still preferred for the shapes where it is available (`MlasIsLutGemmAvailable` returns true). The new W2 native kernel takes over in two cases: * **Default sessions (no LUT opt-in)** — this is the headline behavior change. Previously these sessions had no fast 2-bit path on AVX-512 and would fall back to dequant + SGEMM; now they run the new native W2 kernel. * **LUT-enabled sessions on shapes where LUT is unavailable** (e.g. `N` not a multiple of 128). Previously these shapes hit the dequant+SGEMM fallback inside the LUT path; now they run the much faster native W2 kernel. Net effect: LUT-enabled users get a faster fallback path, default users get a fast default path, and there is no regression in either case. ## fp16 fallback path for 2-bit weights Semi-related addition in `onnxruntime/contrib_ops/cpu/quantization/matmul_nbits.cc`: * `MatMulNBits<MLFloat16>::ComputeBUnpacked` previously only handled `nbits_ == 4`, with everything else falling into an `ORT_ENFORCE(nbits_ == 8)` branch — meaning fp16-activation 2-bit models would fail at runtime. Added the missing `nbits_ == 2` arm that calls `MlasDequantizeBlockwise<float, 2>` and then runs standard fp32 MatMul (same dequant-then-SGEMM pattern as the existing 4-bit/8-bit fp16 paths). * Added a `!prefer_lut_gemm_` guard in `MatMulNBits<T1>::PrePack` so we don't re-pack scales/zero-points in the W2 layout on top of an already-LUT-packed `packed_b_` buffer (which corrupts the LUT layout and crashes the LUT compute path). The guard is gated to `T1 == float` via the constructor, so the fp16 path is unaffected. ### Motivation and Context Adds a native AVX-512 (+VNNI) 2-bit weight CPU GEMM kernel to MLAS so that 2-bit quantized models have a competitive non-LUT path — closing the prefill perf gap versus 4-bit and removing LUT's shape-multiple constraints — and fixes a missing fp16 fallback so fp16-activation 2-bit models load and run on CPU.
Author
Parents
Loading