quant-matmul
W4A8 / W8A8 / FP16-storage GEMV-GEMM for quantized LLM inference on CPUs, aarch64-first. Int4 weights in GPTQ/AWQ-compatible group quantization run against per-token INT8 activations. Companions on the Hub: phanerozoic/bitnet-cpu (ternary linears), phanerozoic/cpu-attn (int8 KV attention), phanerozoic/decode-ops (fused glue and sampling).
Per group g of group_size weights along K:
y[m, n] = sa[m] * sum_g s[n, g] * (dot_g(a_m, w_n) - z[n, g] * asum_g[m])
Group dots are exact in int32 (nibble <= 15, |a| <= 127); groups accumulate in fp32 in fixed order, so every dispatch tier produces bit-identical f32 output. Nibbles are shifted and masked in registers against a one-time even/odd split of the activations; no unpack buffer exists.
| path | instruction | selected when |
|---|---|---|
| AVX-VNNI | vpdpbusd (256-bit) |
x86-64 with AVX-VNNI: Intel 12th-gen+, AMD Zen 4+ |
| AVX2 | vpmaddubsw + vpmaddwd |
x86-64 with AVX2: Haswell and newer |
| SDOT | sdot (dotprod) |
aarch64 with asimddp: Cortex-A76+ (Pi 5), Neoverse, Apple silicon |
| NEON | smull + sadalp |
any other aarch64 (Pi 4, Pi Zero 2) |
| scalar | portable C++ | everything else |
Chosen once at runtime from CPUID / HWCAP; QM_CPU_ISA (scalar, avx2,
avxvnni, neon, neondot) demotes the selection for A/B runs; it never
promotes. Every tier produces bit-identical f32 output.
Usage
import torch
from kernels import get_kernel
qm = get_kernel("phanerozoic/quant-matmul", version=1, trust_remote_code=True)
# From a GPTQ checkpoint's tensors:
packed, s, z, gs, perm = qm.from_gptq(qweight, qzeros, scales, g_idx)
y = qm.w4a8_linear(x, packed, s, z, gs, perm) # [..., N]
# Or as a module:
layer = qm.QuantLinear.from_gptq_tensors(qweight, qzeros, scales, g_idx)
y = layer(x)
version selects the release branch; trust_remote_code is required by
kernels for publishers without the trusted-publisher mark.
API
| Function | Purpose |
|---|---|
pack_w4(intw, scales, zeros, group_size) |
raw nibbles [N, K] -> native packing |
from_gptq(qweight, qzeros, scales, g_idx=None, zero_offset=1) |
GPTQ tensors -> native; act-order returns an activation perm |
from_awq(qweight, qzeros, scales) |
AutoAWQ GEMM tensors -> native |
dequant_w4(packed, scales, zeros, group_size) |
reference f32 dequantization |
quantize_activation(x) |
per-token absmax INT8 |
w4a8_linear(x, packed, scales, zeros, group_size, perm) |
int4 weights x int8 activations |
w8a8_linear(x, w_int8, scale_wt) |
int8 x int8, per-row weight scale |
fp16_gemv(x, w_f16) |
f16-storage weights widened to f32 in registers |
QuantLinear |
nn.Module wrapper |
zero_offset=1 follows the AutoGPTQ storage convention (the stored zero
plus one is subtracted at dequant); pass 0 for checkpoints without it.
Performance
Raspberry Pi 5 (4x Cortex-A76 2.4 GHz, SDOT path) and Raspberry Pi 4 Model B (4x Cortex-A72 1.8 GHz, NEON path), 64-bit Raspberry Pi OS, torch 2.13 CPU, group_size 128:
| op, shape (M, N, K) | Pi 5 | Pi 4 |
|---|---|---|
| w4a8 1, 4096, 4096 | 1.16 ms | 4.08 ms |
| w4a8 1, 11008, 4096 | 2.98 ms | 11.7 ms |
| w4a8 1, 4096, 11008 | 3.57 ms | 10.4 ms |
| w4a8 128, 4096, 4096 | 23.2 ms | 209 ms |
| w8a8 1, 4096, 4096 | 1.46 ms | 5.68 ms |
| fp16 1, 4096, 4096 | 2.80 ms | 13.5 ms |
Decode (M=1) streams packed weights at 8-12 GB/s on the Pi 5, the board's memory ceiling; M=128 runs 185 GOPS on the SDOT tier.
On x86-64 (12th-gen Intel mobile, AVX-VNNI), W4A8 M=1 4096-square runs 0.16 ms (1.6x its scalar path) and the fp16-storage GEMV 0.25 ms (7x), tracking the host's higher memory bandwidth.
Requirements
- K divisible by group_size; group_size a multiple of 32 (64 and 128 tested).
- bf16 or f32 activations; output follows the input dtype.
- GPTQ act-order (
g_idx) is supported through an activation permutation applied per forward. - Fast paths cover aarch64 (NEON; SDOT with dotprod) and x86-64 (AVX2; AVX-VNNI where present). Anything else uses the correct scalar fallback.
Scope
Hub-loadable quantized linears for transformers-on-CPU, aimed at boards where a dedicated llama.cpp runtime was previously the only usable path. llama.cpp remains faster as a standalone runtime; this pack is for models composed in Python.
License
Apache-2.0.
- Downloads last month
- -
- OS
- linux
- Arch
- x86_64aarch64
- Kernel Builder
- 19aaa64