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
-
apache-2.0
OS
linux
Arch
x86_64aarch64
Kernel Builder
19aaa64