Skip to content

Conversation

@vraspar
Copy link
Contributor

@vraspar vraspar commented Dec 1, 2025

Description

This PR introduces a new experimental lookup-table(LUT) based matrix multiplication method for 2-bit MatMulNBits on x64 AVX2 inspired from T-MAC paper and T-MAC repository to speed up low bit LLM inference.

Unlike the existing quant-dequant methods, the LUT-based method directly supports mixed-precision-GEMM without dequantization. It uses bit-wise table lookup to eliminate multiplications and reduce additions required in matrix multiplication.

image

This PR:

  • Add mlas.use_lut_gemm session option allowing use of LUT GEMM inside matmulnbits when it is available (2-bit, BlkLen multiple of 32, K multiple of 32, N multiple of 128, AVX2 present).
  • Introduces LUT packing + kernel config cache (packs bitplanes, scales, ZP) and the main MlasLUTGemm entry that generates per-row LUTs and calls the AVX2 kernel.
  • Implements AVX2 LUT generation GenerateLUT_avx2 and GEMM compute TMACComputeGemm_avx2 and wires dispatch in MLAS platform init.
  • Updates MatMulNBits PrePack/Compute to use LUT packing/compute when opted-in; keeps existing quant-dequant path as fallback.
  • Extends Python quant bindings with 2-bit QDQ helper for parity with the new path.
  • Adds MLAS unit tests covering LUT GEMM across symmetric/asymmetric quant and multiple shapes/block sizes.

Main components:

  • MlasInitLUTGemmKernelConfig: Config for LUT kernels

  • MlasLUTGemmPackQuantBData: Pre Packing of quantized weight

  • MlasLUTPackScalesAndZeroPoints: Pre Packing of qunatized scales and zero points

  • MlasLUTGemm: Main Entry point

  • GenerateLUT_avx2: LUT construction from activations

  • TMACComputeGemm_avx2: AVX2 LUT GEMM kernel

  • Session option: mlas.use_lut_gemm

How to test

  • MLAS LUT GEMM unit tests: see test_sqlutgemm.cpp
  • Run MatMulNBits models with session option mlas.use_lut_gemm=1 on AVX2 machines; expect fallback to existing path if availability checks fail.

Perf

Focus of this PR is functional + kernel bring-up; perf to be reported separately once broader profiling is done.

Future Work

  • Support MLFloat16 (FP16 scales and zero points)
  • Add neon kernel for ARM.
  • Add kernels for 4 bit weights and bitnet kernels
  • Broader batch (N>1) support and additional shape coverage.

liqunfu and others added 30 commits January 29, 2025 19:11
Signed-off-by: Liqun Fu <liqun.fu@microsoft.com>
Signed-off-by: Liqun Fu <liqun.fu@microsoft.com>
Signed-off-by: Liqun Fu <liqun.fu@microsoft.com>
Signed-off-by: Liqun Fu <liqun.fu@microsoft.com>
Signed-off-by: Liqun Fu <liqun.fu@microsoft.com>
Signed-off-by: Liqun Fu <liqun.fu@microsoft.com>
Signed-off-by: Liqun Fu <liqun.fu@microsoft.com>
…as kernel not implemented for fp32. Also, I need to write the packing logic for the scales as well.
…ssert issue with the data shuffling in prepack
@vraspar vraspar marked this pull request as ready for review January 3, 2026 03:26
@vraspar vraspar requested a review from edgchen1 January 3, 2026 03:30
}

// Create a temporary threadpool for parallel packing
// This is used during model load time to speed up weight prepacking
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the overhead like for creating a new threadpool in each call to PrePack()?

I wonder if we should make an existing threadpool available to this code. perhaps we can pass in the threadpool from SessionState. something to consider, and maybe for a future PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, passing thread pool to PrePack would be clean. I am planning to create second PR improving Prepacking logic in general, I will include this along with this :)

auto scale_ptr = scales ? scales->DataRaw() : nullptr;
packed_b_ = IAllocator::MakeUniquePtr<void>(alloc, packed_b_size_, true);
MlasQNBitGemmPackQuantBData(N_, K_, nbits_, block_size_, compute_type_, qptr, packed_b_.get(), scale_ptr,
has_zp_input_, nullptr, threadpool_ptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC - The usage of threadpool in the existing non-LUT path seems like a new addition - is that intentaional (and come with apprioriate tests) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, I thought tests in test_sqnbitgemm.cpp should suffice since they already test it with thread pool. I applied changes to only use thread pool for LUT path now.

Once we add tests, I think it might be beneficial to use thread pool for pre packing for other paths

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closing comment for now to merge as discussed offline

}

// Conditional pragma unroll for compiler compatibility
#if defined(__INTEL_COMPILER) || defined(__clang__)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this complier dependent ? Is this implementation from the T-MAC library as is ?

// Each iteration processes one row of the activation matrix
// TODO(vraspar): Ideally we have to do block parallelism here

MlasTrySimpleParallel(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If M == 1, can we parallelize on N ?

}

size_t n_div = 0;
switch (BlkBitWidth) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have this switch if BlkBitWidth is guaranteed to be 2 at this stage ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to have it generalized for when we add int4 kernels

@jambayk jambayk merged commit 8e050d1 into main Jan 15, 2026
90 checks passed
@jambayk jambayk deleted the vraspar/lut-gemm branch January 15, 2026 18:56
* @brief Parameters for TMAC kernel
*/
struct MlasTMACKernelParams {
size_t g;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A brief comment describing what each config is and what it is used for will help

)
{
const MlasTMACKernelParams& tmac_params = MlasGetLutGemmKernelParams(N, K, BlkBitWidth, BlkLen, HasZeroPoint);
const size_t PackedQuantBDataSize = (N * BlkBitWidth) * (K / tmac_params.g / tmac_params.ngroups_per_elem);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an alignment requirement for the packed weights ?

assert(bm % mgroup == 0);
assert(bm % bits == 0);

std::unique_ptr<uint8_t[]> buf(new uint8_t[N * bits * (K / g)]);
Copy link
Member

@hariharans29 hariharans29 Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the purpose of what is being done here, a standard RAII containers like vector would do, do we really need a unique_ptr here ?

alex-spacemit pushed a commit to spacemit-com/onnxruntime that referenced this pull request Jan 20, 2026
…TMAC) (microsoft#26695)

### Description

This PR introduces a new experimental lookup-table(LUT) based matrix
multiplication method for 2-bit MatMulNBits on x64 AVX2 inspired from
[T-MAC paper](https://arxiv.org/abs/2407.00088) and [T-MAC
repository](https://github.com/microsoft/T-MAC) to speed up low bit LLM
inference.

Unlike the existing quant-dequant methods, the LUT-based method directly
supports mixed-precision-GEMM without dequantization. It uses bit-wise
table lookup to eliminate multiplications and reduce additions required
in matrix multiplication.

<img width="1910" height="759" alt="image"
src="https://github.com/user-attachments/assets/3e3f2ced-eba4-4d4e-a63c-fec479943202"
/>
 

This PR:
- Add` mlas.use_lut_gemm` session option allowing use of LUT GEMM inside
matmulnbits when it is available (2-bit, BlkLen multiple of 32, K
multiple of 32, N multiple of 128, AVX2 present).
- Introduces LUT packing + kernel config cache (packs bitplanes, scales,
ZP) and the main `MlasLUTGemm` entry that generates per-row LUTs and
calls the AVX2 kernel.
- Implements AVX2 LUT generation `GenerateLUT_avx2` and GEMM compute
`TMACComputeGemm_avx2` and wires dispatch in MLAS platform init.
- Updates MatMulNBits PrePack/Compute to use LUT packing/compute when
opted-in; keeps existing quant-dequant path as fallback.
- Extends Python quant bindings with 2-bit QDQ helper for parity with
the new path.
- Adds MLAS unit tests covering LUT GEMM across symmetric/asymmetric
quant and multiple shapes/block sizes.

 
 ### Main components:
 
 - `MlasInitLUTGemmKernelConfig`: Config for LUT kernels
 - `MlasLUTGemmPackQuantBData`: Pre Packing of quantized weight
- `MlasLUTPackScalesAndZeroPoints`: Pre Packing of qunatized scales and
zero points
 
 - `MlasLUTGemm`: Main Entry point
 - `GenerateLUT_avx2`:  LUT construction from activations
 - `TMACComputeGemm_avx2`: AVX2 LUT GEMM kernel
 - Session option: mlas.use_lut_gemm


### How to test
- MLAS LUT GEMM unit tests: see `test_sqlutgemm.cpp`
- Run MatMulNBits models with session option `mlas.use_lut_gemm=1` on
AVX2 machines; expect fallback to existing path if availability checks
fail.

### Perf
Focus of this PR is functional + kernel bring-up; perf to be reported
separately once broader profiling is done.


### Future Work
- Support MLFloat16 (FP16 scales and zero points)
- Add neon kernel for ARM.
- Add kernels for 4 bit weights and bitnet kernels
- Broader batch (N>1) support and additional shape coverage.

---------

Signed-off-by: Liqun Fu <liqun.fu@microsoft.com>
Co-authored-by: Liqun Fu <liqun.fu@microsoft.com>
Co-authored-by: carzh <wolfivyaura@gmail.com>
Co-authored-by: Hector Li <hecli@microsoft.com>
Co-authored-by: carzh <carolinezhu@microsoft.com>
Co-authored-by: Vrajang Parikh <vrparikh@microsoft.com>
tianleiwu pushed a commit that referenced this pull request Jan 21, 2026
…TMAC) (#26695)

### Description

This PR introduces a new experimental lookup-table(LUT) based matrix
multiplication method for 2-bit MatMulNBits on x64 AVX2 inspired from
[T-MAC paper](https://arxiv.org/abs/2407.00088) and [T-MAC
repository](https://github.com/microsoft/T-MAC) to speed up low bit LLM
inference.

Unlike the existing quant-dequant methods, the LUT-based method directly
supports mixed-precision-GEMM without dequantization. It uses bit-wise
table lookup to eliminate multiplications and reduce additions required
in matrix multiplication.

<img width="1910" height="759" alt="image"
src="https://github.com/user-attachments/assets/3e3f2ced-eba4-4d4e-a63c-fec479943202"
/>

This PR:
- Add` mlas.use_lut_gemm` session option allowing use of LUT GEMM inside
matmulnbits when it is available (2-bit, BlkLen multiple of 32, K
multiple of 32, N multiple of 128, AVX2 present).
- Introduces LUT packing + kernel config cache (packs bitplanes, scales,
ZP) and the main `MlasLUTGemm` entry that generates per-row LUTs and
calls the AVX2 kernel.
- Implements AVX2 LUT generation `GenerateLUT_avx2` and GEMM compute
`TMACComputeGemm_avx2` and wires dispatch in MLAS platform init.
- Updates MatMulNBits PrePack/Compute to use LUT packing/compute when
opted-in; keeps existing quant-dequant path as fallback.
- Extends Python quant bindings with 2-bit QDQ helper for parity with
the new path.
- Adds MLAS unit tests covering LUT GEMM across symmetric/asymmetric
quant and multiple shapes/block sizes.

 ### Main components:

 - `MlasInitLUTGemmKernelConfig`: Config for LUT kernels
 - `MlasLUTGemmPackQuantBData`: Pre Packing of quantized weight
- `MlasLUTPackScalesAndZeroPoints`: Pre Packing of qunatized scales and
zero points

 - `MlasLUTGemm`: Main Entry point
 - `GenerateLUT_avx2`:  LUT construction from activations
 - `TMACComputeGemm_avx2`: AVX2 LUT GEMM kernel
 - Session option: mlas.use_lut_gemm

### How to test
- MLAS LUT GEMM unit tests: see `test_sqlutgemm.cpp`
- Run MatMulNBits models with session option `mlas.use_lut_gemm=1` on
AVX2 machines; expect fallback to existing path if availability checks
fail.

### Perf
Focus of this PR is functional + kernel bring-up; perf to be reported
separately once broader profiling is done.

### Future Work
- Support MLFloat16 (FP16 scales and zero points)
- Add neon kernel for ARM.
- Add kernels for 4 bit weights and bitnet kernels
- Broader batch (N>1) support and additional shape coverage.

---------

Signed-off-by: Liqun Fu <liqun.fu@microsoft.com>
Co-authored-by: Liqun Fu <liqun.fu@microsoft.com>
Co-authored-by: carzh <wolfivyaura@gmail.com>
Co-authored-by: Hector Li <hecli@microsoft.com>
Co-authored-by: carzh <carolinezhu@microsoft.com>
Co-authored-by: Vrajang Parikh <vrparikh@microsoft.com>
(cherry picked from commit 8e050d1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants