Exp 2026 06 13 Hadamard Int4 Accuracy - oRKLLM/ork-driver GitHub Wiki
Experiment — Hadamard rotation for int4 (W4A4) accuracy
Date: 2026-06-13 · Status: ✅ MECHANISM CONFIRMED (matmul-level); go/no-go deferred to perplexity · Path: CPU math probe (no NPU)
Hypothesis
int4 W4A4 runs on the NPU but is incoherent — the same-precision MAC forces 4-bit activations, and activation outliers blow out the per-group absmax scale, so the 16 int4 levels land mostly on near-zero values. A Hadamard rotation Q = H/√K (orthonormal, symmetric) is the QuaRot/SpinQuant fix: A·B = (A·Q)·(Q·B) exactly in fp32, but the rotated A'=A·Q, B'=Q·B have their outliers spread evenly across channels, so per-group int4 quant of A',B' is far more accurate. Hypothesis: a Hadamard pre-rotation materially reduces W4A4 matmul error — the prerequisite for int4 ever being usable.
Method — measure before integrating
Rather than wire Hadamard into the model pipeline first, two pure-CPU probes measure the matmul quant error (RMS rel vs the fp32 reference), plain vs Hadamard-rotated, across bit-widths:
tools/hadamard_int4.c— synthetic GaussianA,B+ injected activation outliers.fwht_norm()(fast Walsh-Hadamard + ×1/√K) is the rotation;q_dequant_vec(x,out,K,G,bits)is per-group symmetric int quant→dequant for any bit-width;matmul_q_err(...,abits,bbits,rotate)is the RMS metric.tools/hadamard_real.c— same harness but the weightBis a real Q4_K tensor dequantized from a GGUF (random data is the worst case for quant; this removes that objection). Activations stay synthetic Gaussian + outliers (real LLM activations are ~Gaussian with heavy tails — that's the thing Hadamard tames).outlier_scaleis a CLI arg so the benefit-vs-severity trend is measurable.
Both print 5 rows: W8A8 (sanity), W4A4 plain / +Hadamard, and W4A8 (act8/wt4 — not HW-supported, included as a diagnostic to isolate which operand's int4 hurts).
Results
Synthetic (hadamard_int4, M=16 K=2048 N=512, outliers ×20):
| quant | G=128 | G=32 |
|---|---|---|
| W8A8 sanity | 2.06% | 1.31% |
| W4A4 plain | 26.4% | 18.8% |
| W4A4 + Hadamard | 16.4% | 13.2% |
| W4A8 plain (act8/wt4) | 11.6% | 9.5% |
| W4A8 + Hadamard | 11.7% | 9.4% |
Real weights (hadamard_real, token_embd Q4_K, K=512 N=512 M=16):
| quant | ×20 G128 | ×20 G64 | ×4 G128 |
|---|---|---|---|
| W8A8 sanity | 3.05% | 2.37% | 1.35% |
| W4A4 plain | 46.8% | 32.1% | 22.9% |
| W4A4 + Hadamard | 18.8% | 17.3% | 13.5% |
| W4A8 plain (act8/wt4) | 20.7% | 17.1% | 15.0% |
| W4A8 + Hadamard | 13.8% | 12.8% | 9.7% |
Three robust facts, consistent across synthetic and real weights:
- Hadamard cuts W4A4 error 40–60% (46→19, 32→17, 26→16). The mechanism works.
- The benefit scales with outlier severity — ×20 outliers: 46→19; ×4 outliers: 23→13.5. This is the QuaRot signature: the rotation buys exactly as much as there is outlier mass to spread. Real LLM activation outliers (often 10–100×) sit at the high end.
- It's the activations. Hadamard helps W4A4 (int4 acts carry the outliers) much more than W4A8 (int8 acts already absorb outliers; the remaining error is the int4 weights, which have no outliers to spread). Confirms activation-int4 is the dominant error source — and W4A8 would be far better, but this NPU can't do W4A8 (same-precision MAC).
Verdict
✅ Mechanism confirmed — Hadamard is the right lever and the only one that addresses the W4A4 outlier problem. But matmul RMS is a diagnostic, not the go/no-go. int4 quant error is unbiased and averages out across a matmul (N reductions) and across residual-connected layers — which is precisely why QuaRot/SpinQuant int4 LLMs are coherent at ~0.5 perplexity cost despite double-digit per-matmul RMS. So an 18% rotated-W4A4 RMS does not mean "broken"; the real verdict needs end-to-end perplexity from a Hadamard-wired int4 path.
Sequencing call: 4b (speed) gates 4a (accuracy). Even if Hadamard makes int4 coherent, int4 today is ~23× slower on prefill / ~1.8× on decode than int8 (the per-group K/G-submit explosion on a submit-floor-bound NPU). int4 is only worth the integration cost if 4b submit-count reduction (per-channel int4 → full-K single submit, + graph fusion) also lands — otherwise int8 strictly wins. The rational order is therefore: prove 4b can get int4 within ~2× of int8, then spend the Hadamard-pipeline integration to make it coherent. This probe de-risks 4a cheaply (mechanism proven) so that decision can be made on the perf result.
Reproduce
make hadamard_int4 && ./hadamard_int4 16 2048 512 128 # synthetic, no model
make hadamard_real && ./hadamard_real model.gguf 16 512 128 20 # real Q4_K weights, outliers ×20
Both are CPU-only (-lm, no NPU) — they run on the build host. hadamard_real finds the first Q4_K 2D tensor and uses the largest power-of-2 channel slice (plain FWHT needs power-of-2 K).