Repository files navigation

GemLite

Triton Kernels for Efficient Low-Bit Matrix Multiplication

GemLite Logo

Twitter

Made with ❤ by the team at Mobius Labs for 'Aana' (ആന : Elephant) suite of multimodal product.

GemLite is a collection of Triton kernels designed for efficient low-bit matrix multiplication, emphasizing simplicity and reusability. It provides a practical solution for achieving significant performance gains, delivering up to 7-8x faster prefill and 3-6x faster decoding compared to default Torch AO kernels. For more detailed benchmarks, check the Performance section.

GemLite strikes the perfect balance between flexibility and performance, allowing users to easily use and modify the codebase to develop high-performance kernels optimized for their specific hardware. We have included multiple versions of the kernels to maximize performance across different matrix shapes.

The project started with CUDA kernels, but we have switched to Triton for enhanced flexibility. For the old CUDA version, please refer to this branch.

Result Teaser

End-to-end Performance (Llama3 8-bit)Matmul Performance (A16W8)
End to End PerformanceMatmul Performance

Extensive performance results across different bitwidths, batch sizes, and devices are available in the Performance section below.

Table of Contents

Recent Highlights

  • Improved performance with a focus on sm_120.
  • GemLite now supports MXFP4/NVFP4 for Blackwell.
  • GemLite now supports vLLM V1 and is torch.compile compatible.
  • GemLite now supports bfloat16.
  • GemLite is now available in vLLM via the HQQ library.
  • GemLite is now integrated with TorchAO/SGLang for 4-bit quantization. Check out the blog post.
  • Major performance improvements, especially on the A100 and H100.
  • Flexible bit packing: use 8-bit packing for improved batched performance on the A100 and H100 with packed data.
  • Autotune caching: save and load the best autotune configs across all kernels with a single line of code.
  • Helper functions: make it easier to get started, especially for dynamic quantization.
  • New GEMV RevSplit-K algorithm: outperforms GEMM Split-K and GEMV for batch size = 1 with packed data.
  • Channel-wise scaling: added support for channel-wise scaling for weights, activations, or both.
  • Precision support: includes FP16 × Wn, FP8 × FP8, FP8 × Wn, INT8 × INT8, INT8 × Wn, and MXFPn × MXFPn.
  • torch.compile() support.

Getting Started

Installation

Latest (Recommended)

pip install git+https://github.com/dropbox/gemlite/

Latest Stable Version

pip install gemlite

Usage

importgemlitefromgemliteimportDType, GemLiteLineargemlite_linear=GemLiteLinear(
W_nbits, # weight quantization bit width. supported: [8, 4, 2, 1]group_size=group_size, # any group_size divisible by 32 - enable autotune for group_size < 128 (!)in_features=in_features, # input sizeout_features=out_features, # output sizeinput_dtype=DType.FP16, # FP16, BF16, FP8, INT8output_dtype=DType.FP16, # FP16, BF16, FP32, FP8, INT32scaled_activations=False, # whether the activations are scaled
)
# Packing: we follow the HQQ format (W_q - zeros) * scales ~= W# https://github.com/dropbox/hqq/gemlite_linear.pack(W_q, scales, zeros, bias)
# Forwardout=gemlite_linear(x)
Settings
# Set packing width for packed data - recommended to leave this at the default valuegemlite.set_packing_bitwidth(int)
# Set the accumulation dtype - this is configured automatically.# On consumer GPUs, fp16 is used by default.gemlite.set_acc_dtype(DType)
# Enable TMA - disabled by default. Only supported for MXFP/NVFP kernelsgemlite.enable_tma(True)
# Enable Triton warp specialization on the k-loop - disabled by defaultgemlite.enable_warp_specialize(True)
# Enable/disable native bfp16 atomic addition - recommended to leave this at the default valuegemlite.set_native_atomic_bfp16(True)
# Enable optimized PTX FP4 packing in the MXFP4/NVFP4 activation quant kernel - requires CUDA 13 ptxasgemlite.set_ptx_fp4_pack(True)
# Experimental fast mode for NVFP4, using a static meta scale for activationsgemlite.set_fast_nvfp4(True)
# Use CUDA graphs for autotuning - this will slow down autotuninggemlite.enable_cudagraph_autotune(True)
# Enable activation quantization only from a specified batch size onward.# Smaller batch sizes will use weight-only quantization.gemlite.enable_activation_scaling(int)
# Enable kernel caching: makes some GEMV kernels faster,# but might break with some torch.compile settingsgemlite.set_kernel_caching(True)

Helper Functions

Additionally, we offer helper functions that operate as follows:

fromgemlite.helperimport*device, dtype='cuda:0', torch.float16# AxWy: x = activation precision in bits, y = weight precision in bits.# Weight-onlygemlite_linear=A16W8_INT8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_FP8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W4_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W2_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W158_INT(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# 8-bit activation dynamic quant (channelwise; pass block_quant=True for DeepSeek-style 128x128 block quant)gemlite_linear=A8W8_INT8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W8_FP8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W4_HQQ_INT_dynamic(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A8W158_INT_dynamic(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# MXFP weight-onlygemlite_linear=A16W8_MXFP(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W4_MXFP(device=device, dtype=dtype).from_linear(layer)
# MXFP/NVFP dynamic quant - if post_scale=True, uses channel-wise activation quantization.# Support depends on Triton's ability to support native MXFP/NVFP MMA.gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A4W4_MXFP_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A4W4_NVFP_dynamic(device=device, dtype=dtype).from_linear(layer)

You can also patch the whole model, even from CPU, as follows:

fromgemlite.helperimport*patch_model(model, device=device, processor=A8W8_INT8_dynamic())

Config Caching

Triton autotuning can be time-consuming. To accelerate this process, we provide tools to automatically cache and load the optimal autotuning configurations for all kernels:

importgemlitegemlite.reset_config() # resets cached configs for all kernelsgemlite.cache_config('gemlite_config.json') # cachegemlite.load_config('gemlite_config.json') # load

Ensure that you use one JSON cache file per GPU model. When the cache is loaded, the kernels will skip autotuning, leading to faster startup times.

You can warm up specific shapes using the following helper function:

importgemlite# Ignore pre-loaded configs if you want to start from scratch (optional)# gemlite.reset_config()# Set autotune mode: fast or max# gemlite.set_autotune("max")# Autotune with the default batch sizeswarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)])
# You can specify batch sizes toowarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)], batch_sizes=[1, 8, 64, 128])
# If you want to specify the group size for HQQ-style quantizationwarmup(A16W4_HQQ_INT(), shapes=[(4096, 4096), (2048, 4096)], group_size=64)
# Cache your new configgemlite.cache_config('new_config.json')

vLLM

You can use GemLite with vLLM via TorchAO or HQQ as follows:

fromhqq.utils.vllmimportset_vllm_onthefly_hqq_quantskip_modules= ['lm_head', 'visual', 'vision']
# Select one of the following modes:# INT/FP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='int8_weightonly', skip_modules=skip_modules) # A16W8 - INT8 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, group_size=128, quant_mode='int4_weightonly', skip_modules=skip_modules) # A16W4 - HQQ weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='int8_dynamic', skip_modules=skip_modules) # A8W8 - INT8 x INT8 dynamicset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='fp8_dynamic', skip_modules=skip_modules) # A8W8 - FP8 x FP8 dynamic# MXFP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Trueset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=32, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Falseset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_weightonly', skip_modules=skip_modules) # A16W4 - MXFP4 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W4 - MXFP8 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_dynamic', skip_modules=skip_modules) # A4W4 - MXFP4 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='nvfp4_dynamic', skip_modules=skip_modules) # A4W4 - NVFP4 x NVFP4 dynamic# Load your vLLM modelllm=LLM(model="meta-llama/Llama-3.1-8B-Instruct", max_model_len=4096, gpu_memory_utilization=0.80, dtype=torch.float16)

Deep Dive

We implement various versions of Triton kernels:

  • GEMM: This GEMM kernel is implemented similarly to GPTQ-triton. Since it uses tensor cores, activations must be padded with zeros along the batch dimension to at least 16 rows. It supports both float32 and float16 accumulation for fp16 inputs, but only float32 accumulation for bfloat16.

  • GEMM Split-K: This Split-K GEMM kernel is implemented similarly to the GPTQ Split-K version. We build on the GEMM version above and add another grid dimension that splits the K dimension into multiple jobs that calculate partial sums, which are atomically added and then stored. Split-K performs particularly well for batched LLM decoding (batch sizes between 2 and 32).

  • GEMV: This GEMV kernel splits activations into 1D chunks, performs the dot product using tl.sum, and accumulates via atomic addition. It is primarily intended for use with small batch sizes (M == 1).

  • GEMV RevSplit-K: This algorithm, newly introduced in GemLite, operates in contrast to the GEMM Split-K approach, but within a GEMV context. By doubling the workload per Triton program launched in the GEMV kernel, it reduces the frequency of loading scales/zeros and lowers the number of threads needed. As a result, this method delivers the best performance for batch size = 1 decoding.

All kernels are flexible, supporting 8-, 4-, 2-, and 1-bit weight precision, as well as float16, bfloat16, and int8/fp8 activations.

Performance

End-to-End vLLM benchmarks

Make sure to use CUDA 13 ptxas for Blackwell:

export TRITON_PTXAS_BLACKWELL_PATH=/usr/local/cuda-13.0/bin/ptxas

Prefill (in=1024, out=1) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
115.4 ms10.3 ms9.9 ms7.3 ms8.3 ms10.5 ms
832.4 ms23.3 ms23.6 ms20.5 ms20.4 ms22.2 ms
1636.9 ms29.8 ms29.2 ms27.1 ms27.7 ms28.5 ms
3256.7 ms48.1 ms48.0 ms42.6 ms43.9 ms44.4 ms
64104.0 ms86.6 ms93.7 ms87.6 ms87.1 ms75.3 ms
128198.4 ms164.9 ms153.5 ms151.4 ms143.1 ms141.2 ms

Decode (in=1, out=1024) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
111.75s6.75s8.00s4.84s5.94s8.19s
811.92s7.41s7.78s5.19s6.32s8.40s
1612.44s7.89s8.23s5.66s6.77s8.76s
3213.83s8.74s9.53s6.68s7.71s9.38s
6415.69s10.41s11.08s8.96s9.24s10.62s
12819.32s14.71s14.65s12.39s13.34s13.81s

Talks and Resources

Check out the talk by lead author Dr. Hicham Badri about GemLite at GPU MODE. You can also find the slides here.

Please note that GemLite is under active development, and the content discussed in the talk may evolve as the library continues to improve.

Contributing

Contributions are always welcome. Please feel free to raise issues, submit pull requests, or start a discussion.

If you're looking to integrate GemLite with major inference and AI libraries, we'd love to hear from you!

About

Fast low-bit matmul kernels in Triton

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

GemLite

Triton Kernels for Efficient Low-Bit Matrix Multiplication

GemLite Logo

Twitter

Made with ❤ by the team at Mobius Labs for 'Aana' (ആന : Elephant) suite of multimodal product.

GemLite is a collection of Triton kernels designed for efficient low-bit matrix multiplication, emphasizing simplicity and reusability. It provides a practical solution for achieving significant performance gains, delivering up to 7-8x faster prefill and 3-6x faster decoding compared to default Torch AO kernels. For more detailed benchmarks, check the Performance section.

GemLite strikes the perfect balance between flexibility and performance, allowing users to easily use and modify the codebase to develop high-performance kernels optimized for their specific hardware. We have included multiple versions of the kernels to maximize performance across different matrix shapes.

The project started with CUDA kernels, but we have switched to Triton for enhanced flexibility. For the old CUDA version, please refer to this branch.

Result Teaser

End-to-end Performance (Llama3 8-bit)Matmul Performance (A16W8)
End to End PerformanceMatmul Performance

Extensive performance results across different bitwidths, batch sizes, and devices are available in the Performance section below.

Table of Contents

Recent Highlights

  • Improved performance with a focus on sm_120.
  • GemLite now supports MXFP4/NVFP4 for Blackwell.
  • GemLite now supports vLLM V1 and is torch.compile compatible.
  • GemLite now supports bfloat16.
  • GemLite is now available in vLLM via the HQQ library.
  • GemLite is now integrated with TorchAO/SGLang for 4-bit quantization. Check out the blog post.
  • Major performance improvements, especially on the A100 and H100.
  • Flexible bit packing: use 8-bit packing for improved batched performance on the A100 and H100 with packed data.
  • Autotune caching: save and load the best autotune configs across all kernels with a single line of code.
  • Helper functions: make it easier to get started, especially for dynamic quantization.
  • New GEMV RevSplit-K algorithm: outperforms GEMM Split-K and GEMV for batch size = 1 with packed data.
  • Channel-wise scaling: added support for channel-wise scaling for weights, activations, or both.
  • Precision support: includes FP16 × Wn, FP8 × FP8, FP8 × Wn, INT8 × INT8, INT8 × Wn, and MXFPn × MXFPn.
  • torch.compile() support.

Getting Started

Installation

Latest (Recommended)

pip install git+https://github.com/dropbox/gemlite/

Latest Stable Version

pip install gemlite

Usage

importgemlitefromgemliteimportDType, GemLiteLineargemlite_linear=GemLiteLinear(
W_nbits, # weight quantization bit width. supported: [8, 4, 2, 1]group_size=group_size, # any group_size divisible by 32 - enable autotune for group_size < 128 (!)in_features=in_features, # input sizeout_features=out_features, # output sizeinput_dtype=DType.FP16, # FP16, BF16, FP8, INT8output_dtype=DType.FP16, # FP16, BF16, FP32, FP8, INT32scaled_activations=False, # whether the activations are scaled
)
# Packing: we follow the HQQ format (W_q - zeros) * scales ~= W# https://github.com/dropbox/hqq/gemlite_linear.pack(W_q, scales, zeros, bias)
# Forwardout=gemlite_linear(x)
Settings
# Set packing width for packed data - recommended to leave this at the default valuegemlite.set_packing_bitwidth(int)
# Set the accumulation dtype - this is configured automatically.# On consumer GPUs, fp16 is used by default.gemlite.set_acc_dtype(DType)
# Enable TMA - disabled by default. Only supported for MXFP/NVFP kernelsgemlite.enable_tma(True)
# Enable Triton warp specialization on the k-loop - disabled by defaultgemlite.enable_warp_specialize(True)
# Enable/disable native bfp16 atomic addition - recommended to leave this at the default valuegemlite.set_native_atomic_bfp16(True)
# Enable optimized PTX FP4 packing in the MXFP4/NVFP4 activation quant kernel - requires CUDA 13 ptxasgemlite.set_ptx_fp4_pack(True)
# Experimental fast mode for NVFP4, using a static meta scale for activationsgemlite.set_fast_nvfp4(True)
# Use CUDA graphs for autotuning - this will slow down autotuninggemlite.enable_cudagraph_autotune(True)
# Enable activation quantization only from a specified batch size onward.# Smaller batch sizes will use weight-only quantization.gemlite.enable_activation_scaling(int)
# Enable kernel caching: makes some GEMV kernels faster,# but might break with some torch.compile settingsgemlite.set_kernel_caching(True)

Helper Functions

Additionally, we offer helper functions that operate as follows:

fromgemlite.helperimport*device, dtype='cuda:0', torch.float16# AxWy: x = activation precision in bits, y = weight precision in bits.# Weight-onlygemlite_linear=A16W8_INT8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_FP8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W4_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W2_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W158_INT(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# 8-bit activation dynamic quant (channelwise; pass block_quant=True for DeepSeek-style 128x128 block quant)gemlite_linear=A8W8_INT8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W8_FP8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W4_HQQ_INT_dynamic(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A8W158_INT_dynamic(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# MXFP weight-onlygemlite_linear=A16W8_MXFP(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W4_MXFP(device=device, dtype=dtype).from_linear(layer)
# MXFP/NVFP dynamic quant - if post_scale=True, uses channel-wise activation quantization.# Support depends on Triton's ability to support native MXFP/NVFP MMA.gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A4W4_MXFP_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A4W4_NVFP_dynamic(device=device, dtype=dtype).from_linear(layer)

You can also patch the whole model, even from CPU, as follows:

fromgemlite.helperimport*patch_model(model, device=device, processor=A8W8_INT8_dynamic())

Config Caching

Triton autotuning can be time-consuming. To accelerate this process, we provide tools to automatically cache and load the optimal autotuning configurations for all kernels:

importgemlitegemlite.reset_config() # resets cached configs for all kernelsgemlite.cache_config('gemlite_config.json') # cachegemlite.load_config('gemlite_config.json') # load

Ensure that you use one JSON cache file per GPU model. When the cache is loaded, the kernels will skip autotuning, leading to faster startup times.

You can warm up specific shapes using the following helper function:

importgemlite# Ignore pre-loaded configs if you want to start from scratch (optional)# gemlite.reset_config()# Set autotune mode: fast or max# gemlite.set_autotune("max")# Autotune with the default batch sizeswarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)])
# You can specify batch sizes toowarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)], batch_sizes=[1, 8, 64, 128])
# If you want to specify the group size for HQQ-style quantizationwarmup(A16W4_HQQ_INT(), shapes=[(4096, 4096), (2048, 4096)], group_size=64)
# Cache your new configgemlite.cache_config('new_config.json')

vLLM

You can use GemLite with vLLM via TorchAO or HQQ as follows:

fromhqq.utils.vllmimportset_vllm_onthefly_hqq_quantskip_modules= ['lm_head', 'visual', 'vision']
# Select one of the following modes:# INT/FP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='int8_weightonly', skip_modules=skip_modules) # A16W8 - INT8 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, group_size=128, quant_mode='int4_weightonly', skip_modules=skip_modules) # A16W4 - HQQ weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='int8_dynamic', skip_modules=skip_modules) # A8W8 - INT8 x INT8 dynamicset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='fp8_dynamic', skip_modules=skip_modules) # A8W8 - FP8 x FP8 dynamic# MXFP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Trueset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=32, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Falseset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_weightonly', skip_modules=skip_modules) # A16W4 - MXFP4 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W4 - MXFP8 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_dynamic', skip_modules=skip_modules) # A4W4 - MXFP4 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='nvfp4_dynamic', skip_modules=skip_modules) # A4W4 - NVFP4 x NVFP4 dynamic# Load your vLLM modelllm=LLM(model="meta-llama/Llama-3.1-8B-Instruct", max_model_len=4096, gpu_memory_utilization=0.80, dtype=torch.float16)

Deep Dive

We implement various versions of Triton kernels:

  • GEMM: This GEMM kernel is implemented similarly to GPTQ-triton. Since it uses tensor cores, activations must be padded with zeros along the batch dimension to at least 16 rows. It supports both float32 and float16 accumulation for fp16 inputs, but only float32 accumulation for bfloat16.

  • GEMM Split-K: This Split-K GEMM kernel is implemented similarly to the GPTQ Split-K version. We build on the GEMM version above and add another grid dimension that splits the K dimension into multiple jobs that calculate partial sums, which are atomically added and then stored. Split-K performs particularly well for batched LLM decoding (batch sizes between 2 and 32).

  • GEMV: This GEMV kernel splits activations into 1D chunks, performs the dot product using tl.sum, and accumulates via atomic addition. It is primarily intended for use with small batch sizes (M == 1).

  • GEMV RevSplit-K: This algorithm, newly introduced in GemLite, operates in contrast to the GEMM Split-K approach, but within a GEMV context. By doubling the workload per Triton program launched in the GEMV kernel, it reduces the frequency of loading scales/zeros and lowers the number of threads needed. As a result, this method delivers the best performance for batch size = 1 decoding.

All kernels are flexible, supporting 8-, 4-, 2-, and 1-bit weight precision, as well as float16, bfloat16, and int8/fp8 activations.

Performance

End-to-End vLLM benchmarks

Make sure to use CUDA 13 ptxas for Blackwell:

export TRITON_PTXAS_BLACKWELL_PATH=/usr/local/cuda-13.0/bin/ptxas

Prefill (in=1024, out=1) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
115.4 ms10.3 ms9.9 ms7.3 ms8.3 ms10.5 ms
832.4 ms23.3 ms23.6 ms20.5 ms20.4 ms22.2 ms
1636.9 ms29.8 ms29.2 ms27.1 ms27.7 ms28.5 ms
3256.7 ms48.1 ms48.0 ms42.6 ms43.9 ms44.4 ms
64104.0 ms86.6 ms93.7 ms87.6 ms87.1 ms75.3 ms
128198.4 ms164.9 ms153.5 ms151.4 ms143.1 ms141.2 ms

Decode (in=1, out=1024) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
111.75s6.75s8.00s4.84s5.94s8.19s
811.92s7.41s7.78s5.19s6.32s8.40s
1612.44s7.89s8.23s5.66s6.77s8.76s
3213.83s8.74s9.53s6.68s7.71s9.38s
6415.69s10.41s11.08s8.96s9.24s10.62s
12819.32s14.71s14.65s12.39s13.34s13.81s

Talks and Resources

Check out the talk by lead author Dr. Hicham Badri about GemLite at GPU MODE. You can also find the slides here.

Please note that GemLite is under active development, and the content discussed in the talk may evolve as the library continues to improve.

Contributing

Contributions are always welcome. Please feel free to raise issues, submit pull requests, or start a discussion.

If you're looking to integrate GemLite with major inference and AI libraries, we'd love to hear from you!

About

Fast low-bit matmul kernels in Triton

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GemLite

Triton Kernels for Efficient Low-Bit Matrix Multiplication

GemLite Logo

Twitter

Made with ❤ by the team at Mobius Labs for 'Aana' (ആന : Elephant) suite of multimodal product.

GemLite is a collection of Triton kernels designed for efficient low-bit matrix multiplication, emphasizing simplicity and reusability. It provides a practical solution for achieving significant performance gains, delivering up to 7-8x faster prefill and 3-6x faster decoding compared to default Torch AO kernels. For more detailed benchmarks, check the Performance section.

GemLite strikes the perfect balance between flexibility and performance, allowing users to easily use and modify the codebase to develop high-performance kernels optimized for their specific hardware. We have included multiple versions of the kernels to maximize performance across different matrix shapes.

The project started with CUDA kernels, but we have switched to Triton for enhanced flexibility. For the old CUDA version, please refer to this branch.

Result Teaser

End-to-end Performance (Llama3 8-bit)Matmul Performance (A16W8)
End to End PerformanceMatmul Performance

Extensive performance results across different bitwidths, batch sizes, and devices are available in the Performance section below.

Table of Contents

Recent Highlights

  • Improved performance with a focus on sm_120.
  • GemLite now supports MXFP4/NVFP4 for Blackwell.
  • GemLite now supports vLLM V1 and is torch.compile compatible.
  • GemLite now supports bfloat16.
  • GemLite is now available in vLLM via the HQQ library.
  • GemLite is now integrated with TorchAO/SGLang for 4-bit quantization. Check out the blog post.
  • Major performance improvements, especially on the A100 and H100.
  • Flexible bit packing: use 8-bit packing for improved batched performance on the A100 and H100 with packed data.
  • Autotune caching: save and load the best autotune configs across all kernels with a single line of code.
  • Helper functions: make it easier to get started, especially for dynamic quantization.
  • New GEMV RevSplit-K algorithm: outperforms GEMM Split-K and GEMV for batch size = 1 with packed data.
  • Channel-wise scaling: added support for channel-wise scaling for weights, activations, or both.
  • Precision support: includes FP16 × Wn, FP8 × FP8, FP8 × Wn, INT8 × INT8, INT8 × Wn, and MXFPn × MXFPn.
  • torch.compile() support.

Getting Started

Installation

Latest (Recommended)

pip install git+https://github.com/dropbox/gemlite/

Latest Stable Version

pip install gemlite

Usage

importgemlitefromgemliteimportDType, GemLiteLineargemlite_linear=GemLiteLinear(
W_nbits, # weight quantization bit width. supported: [8, 4, 2, 1]group_size=group_size, # any group_size divisible by 32 - enable autotune for group_size < 128 (!)in_features=in_features, # input sizeout_features=out_features, # output sizeinput_dtype=DType.FP16, # FP16, BF16, FP8, INT8output_dtype=DType.FP16, # FP16, BF16, FP32, FP8, INT32scaled_activations=False, # whether the activations are scaled
)
# Packing: we follow the HQQ format (W_q - zeros) * scales ~= W# https://github.com/dropbox/hqq/gemlite_linear.pack(W_q, scales, zeros, bias)
# Forwardout=gemlite_linear(x)
Settings
# Set packing width for packed data - recommended to leave this at the default valuegemlite.set_packing_bitwidth(int)
# Set the accumulation dtype - this is configured automatically.# On consumer GPUs, fp16 is used by default.gemlite.set_acc_dtype(DType)
# Enable TMA - disabled by default. Only supported for MXFP/NVFP kernelsgemlite.enable_tma(True)
# Enable Triton warp specialization on the k-loop - disabled by defaultgemlite.enable_warp_specialize(True)
# Enable/disable native bfp16 atomic addition - recommended to leave this at the default valuegemlite.set_native_atomic_bfp16(True)
# Enable optimized PTX FP4 packing in the MXFP4/NVFP4 activation quant kernel - requires CUDA 13 ptxasgemlite.set_ptx_fp4_pack(True)
# Experimental fast mode for NVFP4, using a static meta scale for activationsgemlite.set_fast_nvfp4(True)
# Use CUDA graphs for autotuning - this will slow down autotuninggemlite.enable_cudagraph_autotune(True)
# Enable activation quantization only from a specified batch size onward.# Smaller batch sizes will use weight-only quantization.gemlite.enable_activation_scaling(int)
# Enable kernel caching: makes some GEMV kernels faster,# but might break with some torch.compile settingsgemlite.set_kernel_caching(True)

Helper Functions

Additionally, we offer helper functions that operate as follows:

fromgemlite.helperimport*device, dtype='cuda:0', torch.float16# AxWy: x = activation precision in bits, y = weight precision in bits.# Weight-onlygemlite_linear=A16W8_INT8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_FP8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W4_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W2_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W158_INT(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# 8-bit activation dynamic quant (channelwise; pass block_quant=True for DeepSeek-style 128x128 block quant)gemlite_linear=A8W8_INT8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W8_FP8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W4_HQQ_INT_dynamic(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A8W158_INT_dynamic(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# MXFP weight-onlygemlite_linear=A16W8_MXFP(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W4_MXFP(device=device, dtype=dtype).from_linear(layer)
# MXFP/NVFP dynamic quant - if post_scale=True, uses channel-wise activation quantization.# Support depends on Triton's ability to support native MXFP/NVFP MMA.gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A4W4_MXFP_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A4W4_NVFP_dynamic(device=device, dtype=dtype).from_linear(layer)

You can also patch the whole model, even from CPU, as follows:

fromgemlite.helperimport*patch_model(model, device=device, processor=A8W8_INT8_dynamic())

Config Caching

Triton autotuning can be time-consuming. To accelerate this process, we provide tools to automatically cache and load the optimal autotuning configurations for all kernels:

importgemlitegemlite.reset_config() # resets cached configs for all kernelsgemlite.cache_config('gemlite_config.json') # cachegemlite.load_config('gemlite_config.json') # load

Ensure that you use one JSON cache file per GPU model. When the cache is loaded, the kernels will skip autotuning, leading to faster startup times.

You can warm up specific shapes using the following helper function:

importgemlite# Ignore pre-loaded configs if you want to start from scratch (optional)# gemlite.reset_config()# Set autotune mode: fast or max# gemlite.set_autotune("max")# Autotune with the default batch sizeswarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)])
# You can specify batch sizes toowarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)], batch_sizes=[1, 8, 64, 128])
# If you want to specify the group size for HQQ-style quantizationwarmup(A16W4_HQQ_INT(), shapes=[(4096, 4096), (2048, 4096)], group_size=64)
# Cache your new configgemlite.cache_config('new_config.json')

vLLM

You can use GemLite with vLLM via TorchAO or HQQ as follows:

fromhqq.utils.vllmimportset_vllm_onthefly_hqq_quantskip_modules= ['lm_head', 'visual', 'vision']
# Select one of the following modes:# INT/FP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='int8_weightonly', skip_modules=skip_modules) # A16W8 - INT8 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, group_size=128, quant_mode='int4_weightonly', skip_modules=skip_modules) # A16W4 - HQQ weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='int8_dynamic', skip_modules=skip_modules) # A8W8 - INT8 x INT8 dynamicset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='fp8_dynamic', skip_modules=skip_modules) # A8W8 - FP8 x FP8 dynamic# MXFP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Trueset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=32, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Falseset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_weightonly', skip_modules=skip_modules) # A16W4 - MXFP4 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W4 - MXFP8 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_dynamic', skip_modules=skip_modules) # A4W4 - MXFP4 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='nvfp4_dynamic', skip_modules=skip_modules) # A4W4 - NVFP4 x NVFP4 dynamic# Load your vLLM modelllm=LLM(model="meta-llama/Llama-3.1-8B-Instruct", max_model_len=4096, gpu_memory_utilization=0.80, dtype=torch.float16)

Deep Dive

We implement various versions of Triton kernels:

  • GEMM: This GEMM kernel is implemented similarly to GPTQ-triton. Since it uses tensor cores, activations must be padded with zeros along the batch dimension to at least 16 rows. It supports both float32 and float16 accumulation for fp16 inputs, but only float32 accumulation for bfloat16.

  • GEMM Split-K: This Split-K GEMM kernel is implemented similarly to the GPTQ Split-K version. We build on the GEMM version above and add another grid dimension that splits the K dimension into multiple jobs that calculate partial sums, which are atomically added and then stored. Split-K performs particularly well for batched LLM decoding (batch sizes between 2 and 32).

  • GEMV: This GEMV kernel splits activations into 1D chunks, performs the dot product using tl.sum, and accumulates via atomic addition. It is primarily intended for use with small batch sizes (M == 1).

  • GEMV RevSplit-K: This algorithm, newly introduced in GemLite, operates in contrast to the GEMM Split-K approach, but within a GEMV context. By doubling the workload per Triton program launched in the GEMV kernel, it reduces the frequency of loading scales/zeros and lowers the number of threads needed. As a result, this method delivers the best performance for batch size = 1 decoding.

All kernels are flexible, supporting 8-, 4-, 2-, and 1-bit weight precision, as well as float16, bfloat16, and int8/fp8 activations.

Performance

End-to-End vLLM benchmarks

Make sure to use CUDA 13 ptxas for Blackwell:

export TRITON_PTXAS_BLACKWELL_PATH=/usr/local/cuda-13.0/bin/ptxas

Prefill (in=1024, out=1) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
115.4 ms10.3 ms9.9 ms7.3 ms8.3 ms10.5 ms
832.4 ms23.3 ms23.6 ms20.5 ms20.4 ms22.2 ms
1636.9 ms29.8 ms29.2 ms27.1 ms27.7 ms28.5 ms
3256.7 ms48.1 ms48.0 ms42.6 ms43.9 ms44.4 ms
64104.0 ms86.6 ms93.7 ms87.6 ms87.1 ms75.3 ms
128198.4 ms164.9 ms153.5 ms151.4 ms143.1 ms141.2 ms

Decode (in=1, out=1024) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
111.75s6.75s8.00s4.84s5.94s8.19s
811.92s7.41s7.78s5.19s6.32s8.40s
1612.44s7.89s8.23s5.66s6.77s8.76s
3213.83s8.74s9.53s6.68s7.71s9.38s
6415.69s10.41s11.08s8.96s9.24s10.62s
12819.32s14.71s14.65s12.39s13.34s13.81s

Talks and Resources

Check out the talk by lead author Dr. Hicham Badri about GemLite at GPU MODE. You can also find the slides here.

Please note that GemLite is under active development, and the content discussed in the talk may evolve as the library continues to improve.

Contributing

Contributions are always welcome. Please feel free to raise issues, submit pull requests, or start a discussion.

If you're looking to integrate GemLite with major inference and AI libraries, we'd love to hear from you!

About

Fast low-bit matmul kernels in Triton

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GemLite

Triton Kernels for Efficient Low-Bit Matrix Multiplication

GemLite Logo

Twitter

Made with ❤ by the team at Mobius Labs for 'Aana' (ആന : Elephant) suite of multimodal product.

GemLite is a collection of Triton kernels designed for efficient low-bit matrix multiplication, emphasizing simplicity and reusability. It provides a practical solution for achieving significant performance gains, delivering up to 7-8x faster prefill and 3-6x faster decoding compared to default Torch AO kernels. For more detailed benchmarks, check the Performance section.

GemLite strikes the perfect balance between flexibility and performance, allowing users to easily use and modify the codebase to develop high-performance kernels optimized for their specific hardware. We have included multiple versions of the kernels to maximize performance across different matrix shapes.

The project started with CUDA kernels, but we have switched to Triton for enhanced flexibility. For the old CUDA version, please refer to this branch.

Result Teaser

End-to-end Performance (Llama3 8-bit)Matmul Performance (A16W8)
End to End PerformanceMatmul Performance

Extensive performance results across different bitwidths, batch sizes, and devices are available in the Performance section below.

Table of Contents

Recent Highlights

  • Improved performance with a focus on sm_120.
  • GemLite now supports MXFP4/NVFP4 for Blackwell.
  • GemLite now supports vLLM V1 and is torch.compile compatible.
  • GemLite now supports bfloat16.
  • GemLite is now available in vLLM via the HQQ library.
  • GemLite is now integrated with TorchAO/SGLang for 4-bit quantization. Check out the blog post.
  • Major performance improvements, especially on the A100 and H100.
  • Flexible bit packing: use 8-bit packing for improved batched performance on the A100 and H100 with packed data.
  • Autotune caching: save and load the best autotune configs across all kernels with a single line of code.
  • Helper functions: make it easier to get started, especially for dynamic quantization.
  • New GEMV RevSplit-K algorithm: outperforms GEMM Split-K and GEMV for batch size = 1 with packed data.
  • Channel-wise scaling: added support for channel-wise scaling for weights, activations, or both.
  • Precision support: includes FP16 × Wn, FP8 × FP8, FP8 × Wn, INT8 × INT8, INT8 × Wn, and MXFPn × MXFPn.
  • torch.compile() support.

Getting Started

Installation

Latest (Recommended)

pip install git+https://github.com/dropbox/gemlite/

Latest Stable Version

pip install gemlite

Usage

importgemlitefromgemliteimportDType, GemLiteLineargemlite_linear=GemLiteLinear(
W_nbits, # weight quantization bit width. supported: [8, 4, 2, 1]group_size=group_size, # any group_size divisible by 32 - enable autotune for group_size < 128 (!)in_features=in_features, # input sizeout_features=out_features, # output sizeinput_dtype=DType.FP16, # FP16, BF16, FP8, INT8output_dtype=DType.FP16, # FP16, BF16, FP32, FP8, INT32scaled_activations=False, # whether the activations are scaled
)
# Packing: we follow the HQQ format (W_q - zeros) * scales ~= W# https://github.com/dropbox/hqq/gemlite_linear.pack(W_q, scales, zeros, bias)
# Forwardout=gemlite_linear(x)
Settings
# Set packing width for packed data - recommended to leave this at the default valuegemlite.set_packing_bitwidth(int)
# Set the accumulation dtype - this is configured automatically.# On consumer GPUs, fp16 is used by default.gemlite.set_acc_dtype(DType)
# Enable TMA - disabled by default. Only supported for MXFP/NVFP kernelsgemlite.enable_tma(True)
# Enable Triton warp specialization on the k-loop - disabled by defaultgemlite.enable_warp_specialize(True)
# Enable/disable native bfp16 atomic addition - recommended to leave this at the default valuegemlite.set_native_atomic_bfp16(True)
# Enable optimized PTX FP4 packing in the MXFP4/NVFP4 activation quant kernel - requires CUDA 13 ptxasgemlite.set_ptx_fp4_pack(True)
# Experimental fast mode for NVFP4, using a static meta scale for activationsgemlite.set_fast_nvfp4(True)
# Use CUDA graphs for autotuning - this will slow down autotuninggemlite.enable_cudagraph_autotune(True)
# Enable activation quantization only from a specified batch size onward.# Smaller batch sizes will use weight-only quantization.gemlite.enable_activation_scaling(int)
# Enable kernel caching: makes some GEMV kernels faster,# but might break with some torch.compile settingsgemlite.set_kernel_caching(True)

Helper Functions

Additionally, we offer helper functions that operate as follows:

fromgemlite.helperimport*device, dtype='cuda:0', torch.float16# AxWy: x = activation precision in bits, y = weight precision in bits.# Weight-onlygemlite_linear=A16W8_INT8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_FP8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W4_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W2_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W158_INT(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# 8-bit activation dynamic quant (channelwise; pass block_quant=True for DeepSeek-style 128x128 block quant)gemlite_linear=A8W8_INT8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W8_FP8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W4_HQQ_INT_dynamic(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A8W158_INT_dynamic(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# MXFP weight-onlygemlite_linear=A16W8_MXFP(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W4_MXFP(device=device, dtype=dtype).from_linear(layer)
# MXFP/NVFP dynamic quant - if post_scale=True, uses channel-wise activation quantization.# Support depends on Triton's ability to support native MXFP/NVFP MMA.gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A4W4_MXFP_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A4W4_NVFP_dynamic(device=device, dtype=dtype).from_linear(layer)

You can also patch the whole model, even from CPU, as follows:

fromgemlite.helperimport*patch_model(model, device=device, processor=A8W8_INT8_dynamic())

Config Caching

Triton autotuning can be time-consuming. To accelerate this process, we provide tools to automatically cache and load the optimal autotuning configurations for all kernels:

importgemlitegemlite.reset_config() # resets cached configs for all kernelsgemlite.cache_config('gemlite_config.json') # cachegemlite.load_config('gemlite_config.json') # load

Ensure that you use one JSON cache file per GPU model. When the cache is loaded, the kernels will skip autotuning, leading to faster startup times.

You can warm up specific shapes using the following helper function:

importgemlite# Ignore pre-loaded configs if you want to start from scratch (optional)# gemlite.reset_config()# Set autotune mode: fast or max# gemlite.set_autotune("max")# Autotune with the default batch sizeswarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)])
# You can specify batch sizes toowarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)], batch_sizes=[1, 8, 64, 128])
# If you want to specify the group size for HQQ-style quantizationwarmup(A16W4_HQQ_INT(), shapes=[(4096, 4096), (2048, 4096)], group_size=64)
# Cache your new configgemlite.cache_config('new_config.json')

vLLM

You can use GemLite with vLLM via TorchAO or HQQ as follows:

fromhqq.utils.vllmimportset_vllm_onthefly_hqq_quantskip_modules= ['lm_head', 'visual', 'vision']
# Select one of the following modes:# INT/FP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='int8_weightonly', skip_modules=skip_modules) # A16W8 - INT8 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, group_size=128, quant_mode='int4_weightonly', skip_modules=skip_modules) # A16W4 - HQQ weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='int8_dynamic', skip_modules=skip_modules) # A8W8 - INT8 x INT8 dynamicset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='fp8_dynamic', skip_modules=skip_modules) # A8W8 - FP8 x FP8 dynamic# MXFP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Trueset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=32, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Falseset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_weightonly', skip_modules=skip_modules) # A16W4 - MXFP4 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W4 - MXFP8 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_dynamic', skip_modules=skip_modules) # A4W4 - MXFP4 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='nvfp4_dynamic', skip_modules=skip_modules) # A4W4 - NVFP4 x NVFP4 dynamic# Load your vLLM modelllm=LLM(model="meta-llama/Llama-3.1-8B-Instruct", max_model_len=4096, gpu_memory_utilization=0.80, dtype=torch.float16)

Deep Dive

We implement various versions of Triton kernels:

  • GEMM: This GEMM kernel is implemented similarly to GPTQ-triton. Since it uses tensor cores, activations must be padded with zeros along the batch dimension to at least 16 rows. It supports both float32 and float16 accumulation for fp16 inputs, but only float32 accumulation for bfloat16.

  • GEMM Split-K: This Split-K GEMM kernel is implemented similarly to the GPTQ Split-K version. We build on the GEMM version above and add another grid dimension that splits the K dimension into multiple jobs that calculate partial sums, which are atomically added and then stored. Split-K performs particularly well for batched LLM decoding (batch sizes between 2 and 32).

  • GEMV: This GEMV kernel splits activations into 1D chunks, performs the dot product using tl.sum, and accumulates via atomic addition. It is primarily intended for use with small batch sizes (M == 1).

  • GEMV RevSplit-K: This algorithm, newly introduced in GemLite, operates in contrast to the GEMM Split-K approach, but within a GEMV context. By doubling the workload per Triton program launched in the GEMV kernel, it reduces the frequency of loading scales/zeros and lowers the number of threads needed. As a result, this method delivers the best performance for batch size = 1 decoding.

All kernels are flexible, supporting 8-, 4-, 2-, and 1-bit weight precision, as well as float16, bfloat16, and int8/fp8 activations.

Performance

End-to-End vLLM benchmarks

Make sure to use CUDA 13 ptxas for Blackwell:

export TRITON_PTXAS_BLACKWELL_PATH=/usr/local/cuda-13.0/bin/ptxas

Prefill (in=1024, out=1) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
115.4 ms10.3 ms9.9 ms7.3 ms8.3 ms10.5 ms
832.4 ms23.3 ms23.6 ms20.5 ms20.4 ms22.2 ms
1636.9 ms29.8 ms29.2 ms27.1 ms27.7 ms28.5 ms
3256.7 ms48.1 ms48.0 ms42.6 ms43.9 ms44.4 ms
64104.0 ms86.6 ms93.7 ms87.6 ms87.1 ms75.3 ms
128198.4 ms164.9 ms153.5 ms151.4 ms143.1 ms141.2 ms

Decode (in=1, out=1024) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
111.75s6.75s8.00s4.84s5.94s8.19s
811.92s7.41s7.78s5.19s6.32s8.40s
1612.44s7.89s8.23s5.66s6.77s8.76s
3213.83s8.74s9.53s6.68s7.71s9.38s
6415.69s10.41s11.08s8.96s9.24s10.62s
12819.32s14.71s14.65s12.39s13.34s13.81s

Talks and Resources

Check out the talk by lead author Dr. Hicham Badri about GemLite at GPU MODE. You can also find the slides here.

Please note that GemLite is under active development, and the content discussed in the talk may evolve as the library continues to improve.

Contributing

Contributions are always welcome. Please feel free to raise issues, submit pull requests, or start a discussion.

If you're looking to integrate GemLite with major inference and AI libraries, we'd love to hear from you!

About

Fast low-bit matmul kernels in Triton

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

GemLite

Triton Kernels for Efficient Low-Bit Matrix Multiplication

GemLite Logo

Twitter

Made with ❤ by the team at Mobius Labs for 'Aana' (ആന : Elephant) suite of multimodal product.

GemLite is a collection of Triton kernels designed for efficient low-bit matrix multiplication, emphasizing simplicity and reusability. It provides a practical solution for achieving significant performance gains, delivering up to 7-8x faster prefill and 3-6x faster decoding compared to default Torch AO kernels. For more detailed benchmarks, check the Performance section.

GemLite strikes the perfect balance between flexibility and performance, allowing users to easily use and modify the codebase to develop high-performance kernels optimized for their specific hardware. We have included multiple versions of the kernels to maximize performance across different matrix shapes.

The project started with CUDA kernels, but we have switched to Triton for enhanced flexibility. For the old CUDA version, please refer to this branch.

Result Teaser

End-to-end Performance (Llama3 8-bit)Matmul Performance (A16W8)
End to End PerformanceMatmul Performance

Extensive performance results across different bitwidths, batch sizes, and devices are available in the Performance section below.

Table of Contents

Recent Highlights

  • Improved performance with a focus on sm_120.
  • GemLite now supports MXFP4/NVFP4 for Blackwell.
  • GemLite now supports vLLM V1 and is torch.compile compatible.
  • GemLite now supports bfloat16.
  • GemLite is now available in vLLM via the HQQ library.
  • GemLite is now integrated with TorchAO/SGLang for 4-bit quantization. Check out the blog post.
  • Major performance improvements, especially on the A100 and H100.
  • Flexible bit packing: use 8-bit packing for improved batched performance on the A100 and H100 with packed data.
  • Autotune caching: save and load the best autotune configs across all kernels with a single line of code.
  • Helper functions: make it easier to get started, especially for dynamic quantization.
  • New GEMV RevSplit-K algorithm: outperforms GEMM Split-K and GEMV for batch size = 1 with packed data.
  • Channel-wise scaling: added support for channel-wise scaling for weights, activations, or both.
  • Precision support: includes FP16 × Wn, FP8 × FP8, FP8 × Wn, INT8 × INT8, INT8 × Wn, and MXFPn × MXFPn.
  • torch.compile() support.

Getting Started

Installation

Latest (Recommended)

pip install git+https://github.com/dropbox/gemlite/

Latest Stable Version

pip install gemlite

Usage

importgemlitefromgemliteimportDType, GemLiteLineargemlite_linear=GemLiteLinear(
W_nbits, # weight quantization bit width. supported: [8, 4, 2, 1]group_size=group_size, # any group_size divisible by 32 - enable autotune for group_size < 128 (!)in_features=in_features, # input sizeout_features=out_features, # output sizeinput_dtype=DType.FP16, # FP16, BF16, FP8, INT8output_dtype=DType.FP16, # FP16, BF16, FP32, FP8, INT32scaled_activations=False, # whether the activations are scaled
)
# Packing: we follow the HQQ format (W_q - zeros) * scales ~= W# https://github.com/dropbox/hqq/gemlite_linear.pack(W_q, scales, zeros, bias)
# Forwardout=gemlite_linear(x)
Settings
# Set packing width for packed data - recommended to leave this at the default valuegemlite.set_packing_bitwidth(int)
# Set the accumulation dtype - this is configured automatically.# On consumer GPUs, fp16 is used by default.gemlite.set_acc_dtype(DType)
# Enable TMA - disabled by default. Only supported for MXFP/NVFP kernelsgemlite.enable_tma(True)
# Enable Triton warp specialization on the k-loop - disabled by defaultgemlite.enable_warp_specialize(True)
# Enable/disable native bfp16 atomic addition - recommended to leave this at the default valuegemlite.set_native_atomic_bfp16(True)
# Enable optimized PTX FP4 packing in the MXFP4/NVFP4 activation quant kernel - requires CUDA 13 ptxasgemlite.set_ptx_fp4_pack(True)
# Experimental fast mode for NVFP4, using a static meta scale for activationsgemlite.set_fast_nvfp4(True)
# Use CUDA graphs for autotuning - this will slow down autotuninggemlite.enable_cudagraph_autotune(True)
# Enable activation quantization only from a specified batch size onward.# Smaller batch sizes will use weight-only quantization.gemlite.enable_activation_scaling(int)
# Enable kernel caching: makes some GEMV kernels faster,# but might break with some torch.compile settingsgemlite.set_kernel_caching(True)

Helper Functions

Additionally, we offer helper functions that operate as follows:

fromgemlite.helperimport*device, dtype='cuda:0', torch.float16# AxWy: x = activation precision in bits, y = weight precision in bits.# Weight-onlygemlite_linear=A16W8_INT8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_FP8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W4_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W2_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W158_INT(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# 8-bit activation dynamic quant (channelwise; pass block_quant=True for DeepSeek-style 128x128 block quant)gemlite_linear=A8W8_INT8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W8_FP8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W4_HQQ_INT_dynamic(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A8W158_INT_dynamic(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# MXFP weight-onlygemlite_linear=A16W8_MXFP(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W4_MXFP(device=device, dtype=dtype).from_linear(layer)
# MXFP/NVFP dynamic quant - if post_scale=True, uses channel-wise activation quantization.# Support depends on Triton's ability to support native MXFP/NVFP MMA.gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A4W4_MXFP_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A4W4_NVFP_dynamic(device=device, dtype=dtype).from_linear(layer)

You can also patch the whole model, even from CPU, as follows:

fromgemlite.helperimport*patch_model(model, device=device, processor=A8W8_INT8_dynamic())

Config Caching

Triton autotuning can be time-consuming. To accelerate this process, we provide tools to automatically cache and load the optimal autotuning configurations for all kernels:

importgemlitegemlite.reset_config() # resets cached configs for all kernelsgemlite.cache_config('gemlite_config.json') # cachegemlite.load_config('gemlite_config.json') # load

Ensure that you use one JSON cache file per GPU model. When the cache is loaded, the kernels will skip autotuning, leading to faster startup times.

You can warm up specific shapes using the following helper function:

importgemlite# Ignore pre-loaded configs if you want to start from scratch (optional)# gemlite.reset_config()# Set autotune mode: fast or max# gemlite.set_autotune("max")# Autotune with the default batch sizeswarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)])
# You can specify batch sizes toowarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)], batch_sizes=[1, 8, 64, 128])
# If you want to specify the group size for HQQ-style quantizationwarmup(A16W4_HQQ_INT(), shapes=[(4096, 4096), (2048, 4096)], group_size=64)
# Cache your new configgemlite.cache_config('new_config.json')

vLLM

You can use GemLite with vLLM via TorchAO or HQQ as follows:

fromhqq.utils.vllmimportset_vllm_onthefly_hqq_quantskip_modules= ['lm_head', 'visual', 'vision']
# Select one of the following modes:# INT/FP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='int8_weightonly', skip_modules=skip_modules) # A16W8 - INT8 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, group_size=128, quant_mode='int4_weightonly', skip_modules=skip_modules) # A16W4 - HQQ weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='int8_dynamic', skip_modules=skip_modules) # A8W8 - INT8 x INT8 dynamicset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='fp8_dynamic', skip_modules=skip_modules) # A8W8 - FP8 x FP8 dynamic# MXFP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Trueset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=32, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Falseset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_weightonly', skip_modules=skip_modules) # A16W4 - MXFP4 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W4 - MXFP8 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_dynamic', skip_modules=skip_modules) # A4W4 - MXFP4 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='nvfp4_dynamic', skip_modules=skip_modules) # A4W4 - NVFP4 x NVFP4 dynamic# Load your vLLM modelllm=LLM(model="meta-llama/Llama-3.1-8B-Instruct", max_model_len=4096, gpu_memory_utilization=0.80, dtype=torch.float16)

Deep Dive

We implement various versions of Triton kernels:

  • GEMM: This GEMM kernel is implemented similarly to GPTQ-triton. Since it uses tensor cores, activations must be padded with zeros along the batch dimension to at least 16 rows. It supports both float32 and float16 accumulation for fp16 inputs, but only float32 accumulation for bfloat16.

  • GEMM Split-K: This Split-K GEMM kernel is implemented similarly to the GPTQ Split-K version. We build on the GEMM version above and add another grid dimension that splits the K dimension into multiple jobs that calculate partial sums, which are atomically added and then stored. Split-K performs particularly well for batched LLM decoding (batch sizes between 2 and 32).

  • GEMV: This GEMV kernel splits activations into 1D chunks, performs the dot product using tl.sum, and accumulates via atomic addition. It is primarily intended for use with small batch sizes (M == 1).

  • GEMV RevSplit-K: This algorithm, newly introduced in GemLite, operates in contrast to the GEMM Split-K approach, but within a GEMV context. By doubling the workload per Triton program launched in the GEMV kernel, it reduces the frequency of loading scales/zeros and lowers the number of threads needed. As a result, this method delivers the best performance for batch size = 1 decoding.

All kernels are flexible, supporting 8-, 4-, 2-, and 1-bit weight precision, as well as float16, bfloat16, and int8/fp8 activations.

Performance

End-to-End vLLM benchmarks

Make sure to use CUDA 13 ptxas for Blackwell:

export TRITON_PTXAS_BLACKWELL_PATH=/usr/local/cuda-13.0/bin/ptxas

Prefill (in=1024, out=1) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
115.4 ms10.3 ms9.9 ms7.3 ms8.3 ms10.5 ms
832.4 ms23.3 ms23.6 ms20.5 ms20.4 ms22.2 ms
1636.9 ms29.8 ms29.2 ms27.1 ms27.7 ms28.5 ms
3256.7 ms48.1 ms48.0 ms42.6 ms43.9 ms44.4 ms
64104.0 ms86.6 ms93.7 ms87.6 ms87.1 ms75.3 ms
128198.4 ms164.9 ms153.5 ms151.4 ms143.1 ms141.2 ms

Decode (in=1, out=1024) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
111.75s6.75s8.00s4.84s5.94s8.19s
811.92s7.41s7.78s5.19s6.32s8.40s
1612.44s7.89s8.23s5.66s6.77s8.76s
3213.83s8.74s9.53s6.68s7.71s9.38s
6415.69s10.41s11.08s8.96s9.24s10.62s
12819.32s14.71s14.65s12.39s13.34s13.81s

Talks and Resources

Check out the talk by lead author Dr. Hicham Badri about GemLite at GPU MODE. You can also find the slides here.

Please note that GemLite is under active development, and the content discussed in the talk may evolve as the library continues to improve.

Contributing

Contributions are always welcome. Please feel free to raise issues, submit pull requests, or start a discussion.

If you're looking to integrate GemLite with major inference and AI libraries, we'd love to hear from you!

About

Fast low-bit matmul kernels in Triton

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GemLite

Triton Kernels for Efficient Low-Bit Matrix Multiplication

GemLite Logo

Twitter

Made with ❤ by the team at Mobius Labs for 'Aana' (ആന : Elephant) suite of multimodal product.

GemLite is a collection of Triton kernels designed for efficient low-bit matrix multiplication, emphasizing simplicity and reusability. It provides a practical solution for achieving significant performance gains, delivering up to 7-8x faster prefill and 3-6x faster decoding compared to default Torch AO kernels. For more detailed benchmarks, check the Performance section.

GemLite strikes the perfect balance between flexibility and performance, allowing users to easily use and modify the codebase to develop high-performance kernels optimized for their specific hardware. We have included multiple versions of the kernels to maximize performance across different matrix shapes.

The project started with CUDA kernels, but we have switched to Triton for enhanced flexibility. For the old CUDA version, please refer to this branch.

Result Teaser

End-to-end Performance (Llama3 8-bit)Matmul Performance (A16W8)
End to End PerformanceMatmul Performance

Extensive performance results across different bitwidths, batch sizes, and devices are available in the Performance section below.

Table of Contents

Recent Highlights

  • Improved performance with a focus on sm_120.
  • GemLite now supports MXFP4/NVFP4 for Blackwell.
  • GemLite now supports vLLM V1 and is torch.compile compatible.
  • GemLite now supports bfloat16.
  • GemLite is now available in vLLM via the HQQ library.
  • GemLite is now integrated with TorchAO/SGLang for 4-bit quantization. Check out the blog post.
  • Major performance improvements, especially on the A100 and H100.
  • Flexible bit packing: use 8-bit packing for improved batched performance on the A100 and H100 with packed data.
  • Autotune caching: save and load the best autotune configs across all kernels with a single line of code.
  • Helper functions: make it easier to get started, especially for dynamic quantization.
  • New GEMV RevSplit-K algorithm: outperforms GEMM Split-K and GEMV for batch size = 1 with packed data.
  • Channel-wise scaling: added support for channel-wise scaling for weights, activations, or both.
  • Precision support: includes FP16 × Wn, FP8 × FP8, FP8 × Wn, INT8 × INT8, INT8 × Wn, and MXFPn × MXFPn.
  • torch.compile() support.

Getting Started

Installation

Latest (Recommended)

pip install git+https://github.com/dropbox/gemlite/

Latest Stable Version

pip install gemlite

Usage

importgemlitefromgemliteimportDType, GemLiteLineargemlite_linear=GemLiteLinear(
W_nbits, # weight quantization bit width. supported: [8, 4, 2, 1]group_size=group_size, # any group_size divisible by 32 - enable autotune for group_size < 128 (!)in_features=in_features, # input sizeout_features=out_features, # output sizeinput_dtype=DType.FP16, # FP16, BF16, FP8, INT8output_dtype=DType.FP16, # FP16, BF16, FP32, FP8, INT32scaled_activations=False, # whether the activations are scaled
)
# Packing: we follow the HQQ format (W_q - zeros) * scales ~= W# https://github.com/dropbox/hqq/gemlite_linear.pack(W_q, scales, zeros, bias)
# Forwardout=gemlite_linear(x)
Settings
# Set packing width for packed data - recommended to leave this at the default valuegemlite.set_packing_bitwidth(int)
# Set the accumulation dtype - this is configured automatically.# On consumer GPUs, fp16 is used by default.gemlite.set_acc_dtype(DType)
# Enable TMA - disabled by default. Only supported for MXFP/NVFP kernelsgemlite.enable_tma(True)
# Enable Triton warp specialization on the k-loop - disabled by defaultgemlite.enable_warp_specialize(True)
# Enable/disable native bfp16 atomic addition - recommended to leave this at the default valuegemlite.set_native_atomic_bfp16(True)
# Enable optimized PTX FP4 packing in the MXFP4/NVFP4 activation quant kernel - requires CUDA 13 ptxasgemlite.set_ptx_fp4_pack(True)
# Experimental fast mode for NVFP4, using a static meta scale for activationsgemlite.set_fast_nvfp4(True)
# Use CUDA graphs for autotuning - this will slow down autotuninggemlite.enable_cudagraph_autotune(True)
# Enable activation quantization only from a specified batch size onward.# Smaller batch sizes will use weight-only quantization.gemlite.enable_activation_scaling(int)
# Enable kernel caching: makes some GEMV kernels faster,# but might break with some torch.compile settingsgemlite.set_kernel_caching(True)

Helper Functions

Additionally, we offer helper functions that operate as follows:

fromgemlite.helperimport*device, dtype='cuda:0', torch.float16# AxWy: x = activation precision in bits, y = weight precision in bits.# Weight-onlygemlite_linear=A16W8_INT8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_FP8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W4_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W2_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W158_INT(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# 8-bit activation dynamic quant (channelwise; pass block_quant=True for DeepSeek-style 128x128 block quant)gemlite_linear=A8W8_INT8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W8_FP8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W4_HQQ_INT_dynamic(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A8W158_INT_dynamic(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# MXFP weight-onlygemlite_linear=A16W8_MXFP(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W4_MXFP(device=device, dtype=dtype).from_linear(layer)
# MXFP/NVFP dynamic quant - if post_scale=True, uses channel-wise activation quantization.# Support depends on Triton's ability to support native MXFP/NVFP MMA.gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A4W4_MXFP_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A4W4_NVFP_dynamic(device=device, dtype=dtype).from_linear(layer)

You can also patch the whole model, even from CPU, as follows:

fromgemlite.helperimport*patch_model(model, device=device, processor=A8W8_INT8_dynamic())

Config Caching

Triton autotuning can be time-consuming. To accelerate this process, we provide tools to automatically cache and load the optimal autotuning configurations for all kernels:

importgemlitegemlite.reset_config() # resets cached configs for all kernelsgemlite.cache_config('gemlite_config.json') # cachegemlite.load_config('gemlite_config.json') # load

Ensure that you use one JSON cache file per GPU model. When the cache is loaded, the kernels will skip autotuning, leading to faster startup times.

You can warm up specific shapes using the following helper function:

importgemlite# Ignore pre-loaded configs if you want to start from scratch (optional)# gemlite.reset_config()# Set autotune mode: fast or max# gemlite.set_autotune("max")# Autotune with the default batch sizeswarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)])
# You can specify batch sizes toowarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)], batch_sizes=[1, 8, 64, 128])
# If you want to specify the group size for HQQ-style quantizationwarmup(A16W4_HQQ_INT(), shapes=[(4096, 4096), (2048, 4096)], group_size=64)
# Cache your new configgemlite.cache_config('new_config.json')

vLLM

You can use GemLite with vLLM via TorchAO or HQQ as follows:

fromhqq.utils.vllmimportset_vllm_onthefly_hqq_quantskip_modules= ['lm_head', 'visual', 'vision']
# Select one of the following modes:# INT/FP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='int8_weightonly', skip_modules=skip_modules) # A16W8 - INT8 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, group_size=128, quant_mode='int4_weightonly', skip_modules=skip_modules) # A16W4 - HQQ weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='int8_dynamic', skip_modules=skip_modules) # A8W8 - INT8 x INT8 dynamicset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='fp8_dynamic', skip_modules=skip_modules) # A8W8 - FP8 x FP8 dynamic# MXFP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Trueset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=32, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Falseset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_weightonly', skip_modules=skip_modules) # A16W4 - MXFP4 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W4 - MXFP8 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_dynamic', skip_modules=skip_modules) # A4W4 - MXFP4 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='nvfp4_dynamic', skip_modules=skip_modules) # A4W4 - NVFP4 x NVFP4 dynamic# Load your vLLM modelllm=LLM(model="meta-llama/Llama-3.1-8B-Instruct", max_model_len=4096, gpu_memory_utilization=0.80, dtype=torch.float16)

Deep Dive

We implement various versions of Triton kernels:

  • GEMM: This GEMM kernel is implemented similarly to GPTQ-triton. Since it uses tensor cores, activations must be padded with zeros along the batch dimension to at least 16 rows. It supports both float32 and float16 accumulation for fp16 inputs, but only float32 accumulation for bfloat16.

  • GEMM Split-K: This Split-K GEMM kernel is implemented similarly to the GPTQ Split-K version. We build on the GEMM version above and add another grid dimension that splits the K dimension into multiple jobs that calculate partial sums, which are atomically added and then stored. Split-K performs particularly well for batched LLM decoding (batch sizes between 2 and 32).

  • GEMV: This GEMV kernel splits activations into 1D chunks, performs the dot product using tl.sum, and accumulates via atomic addition. It is primarily intended for use with small batch sizes (M == 1).

  • GEMV RevSplit-K: This algorithm, newly introduced in GemLite, operates in contrast to the GEMM Split-K approach, but within a GEMV context. By doubling the workload per Triton program launched in the GEMV kernel, it reduces the frequency of loading scales/zeros and lowers the number of threads needed. As a result, this method delivers the best performance for batch size = 1 decoding.

All kernels are flexible, supporting 8-, 4-, 2-, and 1-bit weight precision, as well as float16, bfloat16, and int8/fp8 activations.

Performance

End-to-End vLLM benchmarks

Make sure to use CUDA 13 ptxas for Blackwell:

export TRITON_PTXAS_BLACKWELL_PATH=/usr/local/cuda-13.0/bin/ptxas

Prefill (in=1024, out=1) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
115.4 ms10.3 ms9.9 ms7.3 ms8.3 ms10.5 ms
832.4 ms23.3 ms23.6 ms20.5 ms20.4 ms22.2 ms
1636.9 ms29.8 ms29.2 ms27.1 ms27.7 ms28.5 ms
3256.7 ms48.1 ms48.0 ms42.6 ms43.9 ms44.4 ms
64104.0 ms86.6 ms93.7 ms87.6 ms87.1 ms75.3 ms
128198.4 ms164.9 ms153.5 ms151.4 ms143.1 ms141.2 ms

Decode (in=1, out=1024) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
111.75s6.75s8.00s4.84s5.94s8.19s
811.92s7.41s7.78s5.19s6.32s8.40s
1612.44s7.89s8.23s5.66s6.77s8.76s
3213.83s8.74s9.53s6.68s7.71s9.38s
6415.69s10.41s11.08s8.96s9.24s10.62s
12819.32s14.71s14.65s12.39s13.34s13.81s

Talks and Resources

Check out the talk by lead author Dr. Hicham Badri about GemLite at GPU MODE. You can also find the slides here.

Please note that GemLite is under active development, and the content discussed in the talk may evolve as the library continues to improve.

Contributing

Contributions are always welcome. Please feel free to raise issues, submit pull requests, or start a discussion.

If you're looking to integrate GemLite with major inference and AI libraries, we'd love to hear from you!

About

Fast low-bit matmul kernels in Triton

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GemLite

Triton Kernels for Efficient Low-Bit Matrix Multiplication

GemLite Logo

Twitter

Made with ❤ by the team at Mobius Labs for 'Aana' (ആന : Elephant) suite of multimodal product.

GemLite is a collection of Triton kernels designed for efficient low-bit matrix multiplication, emphasizing simplicity and reusability. It provides a practical solution for achieving significant performance gains, delivering up to 7-8x faster prefill and 3-6x faster decoding compared to default Torch AO kernels. For more detailed benchmarks, check the Performance section.

GemLite strikes the perfect balance between flexibility and performance, allowing users to easily use and modify the codebase to develop high-performance kernels optimized for their specific hardware. We have included multiple versions of the kernels to maximize performance across different matrix shapes.

The project started with CUDA kernels, but we have switched to Triton for enhanced flexibility. For the old CUDA version, please refer to this branch.

Result Teaser

End-to-end Performance (Llama3 8-bit)Matmul Performance (A16W8)
End to End PerformanceMatmul Performance

Extensive performance results across different bitwidths, batch sizes, and devices are available in the Performance section below.

Table of Contents

Recent Highlights

  • Improved performance with a focus on sm_120.
  • GemLite now supports MXFP4/NVFP4 for Blackwell.
  • GemLite now supports vLLM V1 and is torch.compile compatible.
  • GemLite now supports bfloat16.
  • GemLite is now available in vLLM via the HQQ library.
  • GemLite is now integrated with TorchAO/SGLang for 4-bit quantization. Check out the blog post.
  • Major performance improvements, especially on the A100 and H100.
  • Flexible bit packing: use 8-bit packing for improved batched performance on the A100 and H100 with packed data.
  • Autotune caching: save and load the best autotune configs across all kernels with a single line of code.
  • Helper functions: make it easier to get started, especially for dynamic quantization.
  • New GEMV RevSplit-K algorithm: outperforms GEMM Split-K and GEMV for batch size = 1 with packed data.
  • Channel-wise scaling: added support for channel-wise scaling for weights, activations, or both.
  • Precision support: includes FP16 × Wn, FP8 × FP8, FP8 × Wn, INT8 × INT8, INT8 × Wn, and MXFPn × MXFPn.
  • torch.compile() support.

Getting Started

Installation

Latest (Recommended)

pip install git+https://github.com/dropbox/gemlite/

Latest Stable Version

pip install gemlite

Usage

importgemlitefromgemliteimportDType, GemLiteLineargemlite_linear=GemLiteLinear(
W_nbits, # weight quantization bit width. supported: [8, 4, 2, 1]group_size=group_size, # any group_size divisible by 32 - enable autotune for group_size < 128 (!)in_features=in_features, # input sizeout_features=out_features, # output sizeinput_dtype=DType.FP16, # FP16, BF16, FP8, INT8output_dtype=DType.FP16, # FP16, BF16, FP32, FP8, INT32scaled_activations=False, # whether the activations are scaled
)
# Packing: we follow the HQQ format (W_q - zeros) * scales ~= W# https://github.com/dropbox/hqq/gemlite_linear.pack(W_q, scales, zeros, bias)
# Forwardout=gemlite_linear(x)
Settings
# Set packing width for packed data - recommended to leave this at the default valuegemlite.set_packing_bitwidth(int)
# Set the accumulation dtype - this is configured automatically.# On consumer GPUs, fp16 is used by default.gemlite.set_acc_dtype(DType)
# Enable TMA - disabled by default. Only supported for MXFP/NVFP kernelsgemlite.enable_tma(True)
# Enable Triton warp specialization on the k-loop - disabled by defaultgemlite.enable_warp_specialize(True)
# Enable/disable native bfp16 atomic addition - recommended to leave this at the default valuegemlite.set_native_atomic_bfp16(True)
# Enable optimized PTX FP4 packing in the MXFP4/NVFP4 activation quant kernel - requires CUDA 13 ptxasgemlite.set_ptx_fp4_pack(True)
# Experimental fast mode for NVFP4, using a static meta scale for activationsgemlite.set_fast_nvfp4(True)
# Use CUDA graphs for autotuning - this will slow down autotuninggemlite.enable_cudagraph_autotune(True)
# Enable activation quantization only from a specified batch size onward.# Smaller batch sizes will use weight-only quantization.gemlite.enable_activation_scaling(int)
# Enable kernel caching: makes some GEMV kernels faster,# but might break with some torch.compile settingsgemlite.set_kernel_caching(True)

Helper Functions

Additionally, we offer helper functions that operate as follows:

fromgemlite.helperimport*device, dtype='cuda:0', torch.float16# AxWy: x = activation precision in bits, y = weight precision in bits.# Weight-onlygemlite_linear=A16W8_INT8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_FP8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W4_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W2_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W158_INT(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# 8-bit activation dynamic quant (channelwise; pass block_quant=True for DeepSeek-style 128x128 block quant)gemlite_linear=A8W8_INT8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W8_FP8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W4_HQQ_INT_dynamic(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A8W158_INT_dynamic(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# MXFP weight-onlygemlite_linear=A16W8_MXFP(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W4_MXFP(device=device, dtype=dtype).from_linear(layer)
# MXFP/NVFP dynamic quant - if post_scale=True, uses channel-wise activation quantization.# Support depends on Triton's ability to support native MXFP/NVFP MMA.gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A4W4_MXFP_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A4W4_NVFP_dynamic(device=device, dtype=dtype).from_linear(layer)

You can also patch the whole model, even from CPU, as follows:

fromgemlite.helperimport*patch_model(model, device=device, processor=A8W8_INT8_dynamic())

Config Caching

Triton autotuning can be time-consuming. To accelerate this process, we provide tools to automatically cache and load the optimal autotuning configurations for all kernels:

importgemlitegemlite.reset_config() # resets cached configs for all kernelsgemlite.cache_config('gemlite_config.json') # cachegemlite.load_config('gemlite_config.json') # load

Ensure that you use one JSON cache file per GPU model. When the cache is loaded, the kernels will skip autotuning, leading to faster startup times.

You can warm up specific shapes using the following helper function:

importgemlite# Ignore pre-loaded configs if you want to start from scratch (optional)# gemlite.reset_config()# Set autotune mode: fast or max# gemlite.set_autotune("max")# Autotune with the default batch sizeswarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)])
# You can specify batch sizes toowarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)], batch_sizes=[1, 8, 64, 128])
# If you want to specify the group size for HQQ-style quantizationwarmup(A16W4_HQQ_INT(), shapes=[(4096, 4096), (2048, 4096)], group_size=64)
# Cache your new configgemlite.cache_config('new_config.json')

vLLM

You can use GemLite with vLLM via TorchAO or HQQ as follows:

fromhqq.utils.vllmimportset_vllm_onthefly_hqq_quantskip_modules= ['lm_head', 'visual', 'vision']
# Select one of the following modes:# INT/FP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='int8_weightonly', skip_modules=skip_modules) # A16W8 - INT8 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, group_size=128, quant_mode='int4_weightonly', skip_modules=skip_modules) # A16W4 - HQQ weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='int8_dynamic', skip_modules=skip_modules) # A8W8 - INT8 x INT8 dynamicset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='fp8_dynamic', skip_modules=skip_modules) # A8W8 - FP8 x FP8 dynamic# MXFP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Trueset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=32, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Falseset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_weightonly', skip_modules=skip_modules) # A16W4 - MXFP4 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W4 - MXFP8 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_dynamic', skip_modules=skip_modules) # A4W4 - MXFP4 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='nvfp4_dynamic', skip_modules=skip_modules) # A4W4 - NVFP4 x NVFP4 dynamic# Load your vLLM modelllm=LLM(model="meta-llama/Llama-3.1-8B-Instruct", max_model_len=4096, gpu_memory_utilization=0.80, dtype=torch.float16)

Deep Dive

We implement various versions of Triton kernels:

  • GEMM: This GEMM kernel is implemented similarly to GPTQ-triton. Since it uses tensor cores, activations must be padded with zeros along the batch dimension to at least 16 rows. It supports both float32 and float16 accumulation for fp16 inputs, but only float32 accumulation for bfloat16.

  • GEMM Split-K: This Split-K GEMM kernel is implemented similarly to the GPTQ Split-K version. We build on the GEMM version above and add another grid dimension that splits the K dimension into multiple jobs that calculate partial sums, which are atomically added and then stored. Split-K performs particularly well for batched LLM decoding (batch sizes between 2 and 32).

  • GEMV: This GEMV kernel splits activations into 1D chunks, performs the dot product using tl.sum, and accumulates via atomic addition. It is primarily intended for use with small batch sizes (M == 1).

  • GEMV RevSplit-K: This algorithm, newly introduced in GemLite, operates in contrast to the GEMM Split-K approach, but within a GEMV context. By doubling the workload per Triton program launched in the GEMV kernel, it reduces the frequency of loading scales/zeros and lowers the number of threads needed. As a result, this method delivers the best performance for batch size = 1 decoding.

All kernels are flexible, supporting 8-, 4-, 2-, and 1-bit weight precision, as well as float16, bfloat16, and int8/fp8 activations.

Performance

End-to-End vLLM benchmarks

Make sure to use CUDA 13 ptxas for Blackwell:

export TRITON_PTXAS_BLACKWELL_PATH=/usr/local/cuda-13.0/bin/ptxas

Prefill (in=1024, out=1) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
115.4 ms10.3 ms9.9 ms7.3 ms8.3 ms10.5 ms
832.4 ms23.3 ms23.6 ms20.5 ms20.4 ms22.2 ms
1636.9 ms29.8 ms29.2 ms27.1 ms27.7 ms28.5 ms
3256.7 ms48.1 ms48.0 ms42.6 ms43.9 ms44.4 ms
64104.0 ms86.6 ms93.7 ms87.6 ms87.1 ms75.3 ms
128198.4 ms164.9 ms153.5 ms151.4 ms143.1 ms141.2 ms

Decode (in=1, out=1024) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
111.75s6.75s8.00s4.84s5.94s8.19s
811.92s7.41s7.78s5.19s6.32s8.40s
1612.44s7.89s8.23s5.66s6.77s8.76s
3213.83s8.74s9.53s6.68s7.71s9.38s
6415.69s10.41s11.08s8.96s9.24s10.62s
12819.32s14.71s14.65s12.39s13.34s13.81s

Talks and Resources

Check out the talk by lead author Dr. Hicham Badri about GemLite at GPU MODE. You can also find the slides here.

Please note that GemLite is under active development, and the content discussed in the talk may evolve as the library continues to improve.

Contributing

Contributions are always welcome. Please feel free to raise issues, submit pull requests, or start a discussion.

If you're looking to integrate GemLite with major inference and AI libraries, we'd love to hear from you!

About

Fast low-bit matmul kernels in Triton

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

GemLite

Triton Kernels for Efficient Low-Bit Matrix Multiplication

GemLite Logo

Twitter

Made with ❤ by the team at Mobius Labs for 'Aana' (ആന : Elephant) suite of multimodal product.

GemLite is a collection of Triton kernels designed for efficient low-bit matrix multiplication, emphasizing simplicity and reusability. It provides a practical solution for achieving significant performance gains, delivering up to 7-8x faster prefill and 3-6x faster decoding compared to default Torch AO kernels. For more detailed benchmarks, check the Performance section.

GemLite strikes the perfect balance between flexibility and performance, allowing users to easily use and modify the codebase to develop high-performance kernels optimized for their specific hardware. We have included multiple versions of the kernels to maximize performance across different matrix shapes.

The project started with CUDA kernels, but we have switched to Triton for enhanced flexibility. For the old CUDA version, please refer to this branch.

Result Teaser

End-to-end Performance (Llama3 8-bit)Matmul Performance (A16W8)
End to End PerformanceMatmul Performance

Extensive performance results across different bitwidths, batch sizes, and devices are available in the Performance section below.

Table of Contents

Recent Highlights

  • Improved performance with a focus on sm_120.
  • GemLite now supports MXFP4/NVFP4 for Blackwell.
  • GemLite now supports vLLM V1 and is torch.compile compatible.
  • GemLite now supports bfloat16.
  • GemLite is now available in vLLM via the HQQ library.
  • GemLite is now integrated with TorchAO/SGLang for 4-bit quantization. Check out the blog post.
  • Major performance improvements, especially on the A100 and H100.
  • Flexible bit packing: use 8-bit packing for improved batched performance on the A100 and H100 with packed data.
  • Autotune caching: save and load the best autotune configs across all kernels with a single line of code.
  • Helper functions: make it easier to get started, especially for dynamic quantization.
  • New GEMV RevSplit-K algorithm: outperforms GEMM Split-K and GEMV for batch size = 1 with packed data.
  • Channel-wise scaling: added support for channel-wise scaling for weights, activations, or both.
  • Precision support: includes FP16 × Wn, FP8 × FP8, FP8 × Wn, INT8 × INT8, INT8 × Wn, and MXFPn × MXFPn.
  • torch.compile() support.

Getting Started

Installation

Latest (Recommended)

pip install git+https://github.com/dropbox/gemlite/

Latest Stable Version

pip install gemlite

Usage

importgemlitefromgemliteimportDType, GemLiteLineargemlite_linear=GemLiteLinear(
W_nbits, # weight quantization bit width. supported: [8, 4, 2, 1]group_size=group_size, # any group_size divisible by 32 - enable autotune for group_size < 128 (!)in_features=in_features, # input sizeout_features=out_features, # output sizeinput_dtype=DType.FP16, # FP16, BF16, FP8, INT8output_dtype=DType.FP16, # FP16, BF16, FP32, FP8, INT32scaled_activations=False, # whether the activations are scaled
)
# Packing: we follow the HQQ format (W_q - zeros) * scales ~= W# https://github.com/dropbox/hqq/gemlite_linear.pack(W_q, scales, zeros, bias)
# Forwardout=gemlite_linear(x)
Settings
# Set packing width for packed data - recommended to leave this at the default valuegemlite.set_packing_bitwidth(int)
# Set the accumulation dtype - this is configured automatically.# On consumer GPUs, fp16 is used by default.gemlite.set_acc_dtype(DType)
# Enable TMA - disabled by default. Only supported for MXFP/NVFP kernelsgemlite.enable_tma(True)
# Enable Triton warp specialization on the k-loop - disabled by defaultgemlite.enable_warp_specialize(True)
# Enable/disable native bfp16 atomic addition - recommended to leave this at the default valuegemlite.set_native_atomic_bfp16(True)
# Enable optimized PTX FP4 packing in the MXFP4/NVFP4 activation quant kernel - requires CUDA 13 ptxasgemlite.set_ptx_fp4_pack(True)
# Experimental fast mode for NVFP4, using a static meta scale for activationsgemlite.set_fast_nvfp4(True)
# Use CUDA graphs for autotuning - this will slow down autotuninggemlite.enable_cudagraph_autotune(True)
# Enable activation quantization only from a specified batch size onward.# Smaller batch sizes will use weight-only quantization.gemlite.enable_activation_scaling(int)
# Enable kernel caching: makes some GEMV kernels faster,# but might break with some torch.compile settingsgemlite.set_kernel_caching(True)

Helper Functions

Additionally, we offer helper functions that operate as follows:

fromgemlite.helperimport*device, dtype='cuda:0', torch.float16# AxWy: x = activation precision in bits, y = weight precision in bits.# Weight-onlygemlite_linear=A16W8_INT8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_FP8(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W8_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W4_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W2_HQQ_INT(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A16W158_INT(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# 8-bit activation dynamic quant (channelwise; pass block_quant=True for DeepSeek-style 128x128 block quant)gemlite_linear=A8W8_INT8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W8_FP8_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A8W4_HQQ_INT_dynamic(device=device, dtype=dtype).from_hqqlinear(hqq_layer)
gemlite_linear=A8W158_INT_dynamic(device=device, dtype=dtype).from_bitlinear(bitlinear_layer)
# MXFP weight-onlygemlite_linear=A16W8_MXFP(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A16W4_MXFP(device=device, dtype=dtype).from_linear(layer)
# MXFP/NVFP dynamic quant - if post_scale=True, uses channel-wise activation quantization.# Support depends on Triton's ability to support native MXFP/NVFP MMA.gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W8_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=False).from_linear(layer)
gemlite_linear=A8W4_MXFP_dynamic(device=device, dtype=dtype, post_scale=True).from_linear(layer)
gemlite_linear=A4W4_MXFP_dynamic(device=device, dtype=dtype).from_linear(layer)
gemlite_linear=A4W4_NVFP_dynamic(device=device, dtype=dtype).from_linear(layer)

You can also patch the whole model, even from CPU, as follows:

fromgemlite.helperimport*patch_model(model, device=device, processor=A8W8_INT8_dynamic())

Config Caching

Triton autotuning can be time-consuming. To accelerate this process, we provide tools to automatically cache and load the optimal autotuning configurations for all kernels:

importgemlitegemlite.reset_config() # resets cached configs for all kernelsgemlite.cache_config('gemlite_config.json') # cachegemlite.load_config('gemlite_config.json') # load

Ensure that you use one JSON cache file per GPU model. When the cache is loaded, the kernels will skip autotuning, leading to faster startup times.

You can warm up specific shapes using the following helper function:

importgemlite# Ignore pre-loaded configs if you want to start from scratch (optional)# gemlite.reset_config()# Set autotune mode: fast or max# gemlite.set_autotune("max")# Autotune with the default batch sizeswarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)])
# You can specify batch sizes toowarmup(A8W8_INT8_dynamic(), shapes=[(4096, 4096), (2048, 4096)], batch_sizes=[1, 8, 64, 128])
# If you want to specify the group size for HQQ-style quantizationwarmup(A16W4_HQQ_INT(), shapes=[(4096, 4096), (2048, 4096)], group_size=64)
# Cache your new configgemlite.cache_config('new_config.json')

vLLM

You can use GemLite with vLLM via TorchAO or HQQ as follows:

fromhqq.utils.vllmimportset_vllm_onthefly_hqq_quantskip_modules= ['lm_head', 'visual', 'vision']
# Select one of the following modes:# INT/FP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='int8_weightonly', skip_modules=skip_modules) # A16W8 - INT8 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, group_size=128, quant_mode='int4_weightonly', skip_modules=skip_modules) # A16W4 - HQQ weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='int8_dynamic', skip_modules=skip_modules) # A8W8 - INT8 x INT8 dynamicset_vllm_onthefly_hqq_quant(weight_bits=8, quant_mode='fp8_dynamic', skip_modules=skip_modules) # A8W8 - FP8 x FP8 dynamic# MXFP formatset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=None, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Trueset_vllm_onthefly_hqq_quant(weight_bits=8, group_size=32, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W8 - MXFP8 x MXFP8 - post_scale=Falseset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_weightonly', skip_modules=skip_modules) # A16W4 - MXFP4 weight-onlyset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp8_dynamic', skip_modules=skip_modules) # A8W4 - MXFP8 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='mxfp4_dynamic', skip_modules=skip_modules) # A4W4 - MXFP4 x MXFP4 dynamicset_vllm_onthefly_hqq_quant(weight_bits=4, quant_mode='nvfp4_dynamic', skip_modules=skip_modules) # A4W4 - NVFP4 x NVFP4 dynamic# Load your vLLM modelllm=LLM(model="meta-llama/Llama-3.1-8B-Instruct", max_model_len=4096, gpu_memory_utilization=0.80, dtype=torch.float16)

Deep Dive

We implement various versions of Triton kernels:

  • GEMM: This GEMM kernel is implemented similarly to GPTQ-triton. Since it uses tensor cores, activations must be padded with zeros along the batch dimension to at least 16 rows. It supports both float32 and float16 accumulation for fp16 inputs, but only float32 accumulation for bfloat16.

  • GEMM Split-K: This Split-K GEMM kernel is implemented similarly to the GPTQ Split-K version. We build on the GEMM version above and add another grid dimension that splits the K dimension into multiple jobs that calculate partial sums, which are atomically added and then stored. Split-K performs particularly well for batched LLM decoding (batch sizes between 2 and 32).

  • GEMV: This GEMV kernel splits activations into 1D chunks, performs the dot product using tl.sum, and accumulates via atomic addition. It is primarily intended for use with small batch sizes (M == 1).

  • GEMV RevSplit-K: This algorithm, newly introduced in GemLite, operates in contrast to the GEMM Split-K approach, but within a GEMV context. By doubling the workload per Triton program launched in the GEMV kernel, it reduces the frequency of loading scales/zeros and lowers the number of threads needed. As a result, this method delivers the best performance for batch size = 1 decoding.

All kernels are flexible, supporting 8-, 4-, 2-, and 1-bit weight precision, as well as float16, bfloat16, and int8/fp8 activations.

Performance

End-to-End vLLM benchmarks

Make sure to use CUDA 13 ptxas for Blackwell:

export TRITON_PTXAS_BLACKWELL_PATH=/usr/local/cuda-13.0/bin/ptxas

Prefill (in=1024, out=1) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
115.4 ms10.3 ms9.9 ms7.3 ms8.3 ms10.5 ms
832.4 ms23.3 ms23.6 ms20.5 ms20.4 ms22.2 ms
1636.9 ms29.8 ms29.2 ms27.1 ms27.7 ms28.5 ms
3256.7 ms48.1 ms48.0 ms42.6 ms43.9 ms44.4 ms
64104.0 ms86.6 ms93.7 ms87.6 ms87.1 ms75.3 ms
128198.4 ms164.9 ms153.5 ms151.4 ms143.1 ms141.2 ms

Decode (in=1, out=1024) — Llama-3.1-8B · RTX PRO 6000

Batch SizeFP16GemLite FP8RedHat FP8GemLite MXFP4GemLite NVFP4RedHat NVFP4
111.75s6.75s8.00s4.84s5.94s8.19s
811.92s7.41s7.78s5.19s6.32s8.40s
1612.44s7.89s8.23s5.66s6.77s8.76s
3213.83s8.74s9.53s6.68s7.71s9.38s
6415.69s10.41s11.08s8.96s9.24s10.62s
12819.32s14.71s14.65s12.39s13.34s13.81s

Talks and Resources

Check out the talk by lead author Dr. Hicham Badri about GemLite at GPU MODE. You can also find the slides here.

Please note that GemLite is under active development, and the content discussed in the talk may evolve as the library continues to improve.

Contributing

Contributions are always welcome. Please feel free to raise issues, submit pull requests, or start a discussion.

If you're looking to integrate GemLite with major inference and AI libraries, we'd love to hear from you!

About

Fast low-bit matmul kernels in Triton

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages