W8A8 Implementation
Date: 2026-08-31
Tested commit: 7353b0600ab4675f1fc863aaae0f34e90157eb5b
Executive summary
This work adds an experimental W8A8 matrix-multiplication path to gemma.cpp
and evaluates two modes:
- Naive W8A8: symmetric 8-bit weights and 8-bit activations without
outlier mitigation.
- Rotated W8A8: the same W8A8 kernel, preceded by matching block-128
randomized Hadamard transforms on weights and activations.
Both modes accelerate the complete 83-question repository MMLU fixture on the
tested x86 machine. Naive W8A8 is fastest: 1.432x control speed on Gemma 3 270M
and 1.561x on Gemma 3 1B. Rotated W8A8 retains most of that speed and preserves
behavior much better, reducing mean KL(control || W8A8) by 91.0% on 270M and
83.2% on 1B relative to naive W8A8. On 1B, rotated W8A8 exactly preserves the
control's 25/83 correctness outcome; naive W8A8 scores 23/83.
The largest process peak was 2786.6 MiB RSS. Available system memory remained
at least approximately 10 GiB and free disk approximately 244 GiB during
monitoring. No run crashed or approached the safety thresholds.
Implementation
Direction implemented
The implementation targets the weight-side decompression cost in the existing
matrix multiplication. The standard compressed-weight path repeatedly
converts weight tiles to BF16 before multiplication. W8A8 instead retains a
packed 8-bit weight representation and sends those bytes directly to integer
dot-product instructions.
The change has four connected parts:
- W8A8 kernel: consumes quantized activations and weights, accumulates in
int32, applies activation and weight scales, and writes the expected model
output type.
- Model routing: eligible MatMuls are intercepted before the original path
when GEMMA_MM_I8=1; ineligible calls fall back unchanged.
- Lazy packing: each weight tensor is decompressed, quantized once on first
use, and retained in a process-wide cache. Activations are quantized per
call because they change per token.
- Optional rotation: when
GEMMA_MM_I8_ROTATE=1, matching orthonormal
block transforms are applied to activation and transposed-weight rows before
quantization.
Loaded SFP weight row
-> float decompression
-> optional block-128 Rademacher + Hadamard rotation
-> per-output-channel int8 quantization
-> lazy packed-weight cache
Current activation row
-> optional matching block-128 rotation
-> per-token int8 quantization
-> int8 dot products / int32 accumulation
-> multiply by activation_scale * weight_scale
-> BF16/F32 model output
The kernel reuses existing MMLoops blocking, threading, and autotuning. The
fused gated-FFN pair is supported through TwoMatMulI8, allowing both
projections to share one quantized activation.
Quantization method
Both operands use symmetric round-to-nearest int8 quantization with maximum
magnitude 127. For an activation row a and transposed weight row b:
s_a = max(abs(a)) / 127 q_a = round(a / s_a)
s_b = max(abs(b)) / 127 q_b = round(b / s_b)
C[row, column] ~= s_a * s_b * sum_k(q_a[k] * q_b[k])
- Activations use one runtime scale per row/token.
- Weights use one packing-time scale per transposed-B row, equivalent to one
per output channel.
- Dot products accumulate in signed 32-bit integers across a K-range before a
scaling step.
- Quantization has no zero point. It uses no calibration data, learned
clipping, group scales, or mixed precision.
Signed and biased byte encodings
Activations are always signed int8. The weight encoding depends on the target:
- Arm NEON/SVE: signed
int8 x int8, using native signed dot operations.
- x86:
q_biased = q_b + 128 is stored as uint8, enabling efficient
unsigned-by-signed VNNI-style dot products. The kernel subtracts
128 * sum(q_a) for each K-range.
The x86 +128 is an instruction encoding, not an asymmetric quantization zero
point. Both encodings represent the same symmetric values and have separate
correctness binaries.
Rotation method
Rotated mode divides K into blocks of 128. Each block receives:
- A deterministic Rademacher diagonal of fixed
+1/-1 signs.
- A Walsh-Hadamard transform.
- Normalization by
1 / sqrt(128).
The same orthonormal transform R is applied to activations and weights:
(Ra)^T (Rb) = a^T R^T R b = a^T b
Thus the unquantized dot product is preserved. After quantization, isolated
large values have been spread across 128 coordinates, so one outlier is less
likely to set a coarse scale for the entire row. Rotation is used only when K
is divisible by 128.
Runtime controls
| Variable |
Purpose |
GEMMA_MM_I8=1 |
Enable model-level W8A8 routing. |
GEMMA_MM_I8_ROTATE=1 |
Enable block-128 matching rotations. |
GEMMA_MM_I8_MIN_K=<n> |
Keep matrices with K below n on the original path. |
GEMMA_MM_I8_SKIP_ROWS=<n> |
Keep matrices with N at least n on the original path. |
GEMMA_MM_I8_INCLUDE=<list> |
Restrict W8A8 to matching tensor names. |
GEMMA_MM_I8_EXCLUDE=<list> |
Exclude matching tensor names. |
GEMMA_MM_I8_VERBOSE=1 |
Log each lazily packed tensor. |
The reported runs used every eligible matrix and no routing filters.
Prototype boundaries
This is an evaluation harness, not a production weight loader:
- Released SFP weights are decompressed and then quantized again to int8. A
production path should generate W8 from the original checkpoint.
- Original SFP weights and the int8 cache coexist, inflating measured RSS.
- The process-wide cache is not freed during execution.
- W8A8 and original MatMul currently share shape-only autotune keys.
- Rotation covers MatMul A/B operands only, not residual streams or KV cache.
What method? Based on what paper?
Core kernel
The core kernel is repository-specific work motivated by the weight
decompression bottleneck discussed in
google/gemma.cpp issue #560.
That discussion showed weight-tile decompression dominating parts of runtime
while activation conversion was comparatively small. This implementation
therefore consumes packed 8-bit weights directly, invokes Highway's
four-product integer dot primitive, and reuses gemma.cpp's loop nest.
The symmetric per-token-activation/per-output-channel-weight quantizer is a
simple calibration-free post-training W8A8 method. This report does not claim
that the core kernel reproduces one particular paper.
QuaRot-inspired rotation
The optional rotation is QuaRot-style, based on
QuaRot: Outlier-Free 4-Bit Inference in Rotated LLMs,
Ashkboos et al., NeurIPS 2024. QuaRot uses orthogonal rotations to remove hidden
state outliers without changing the exact full-precision computation, making
quantization easier. The paper also reports calibration-free 8-bit LLaMA-2
results using round-to-nearest quantization.
This implementation borrows the randomized-Hadamard outlier-spreading idea but
is not full QuaRot:
| QuaRot |
This implementation |
| Targets end-to-end weights, activations, and KV cache, including 4-bit inference. |
Uses W8A8 MatMul operands only. |
| Applies model-wide invariance across residual, FFN, attention, and KV-cache components. |
Applies local matching transforms to eligible MatMul A/B vectors. |
| Uses its own model conversion and quantization pipeline. |
Reuses gemma.cpp and lazily quantizes released SFP weights. |
| Evaluated on LLaMA-family models and standard benchmarks. |
Evaluated here on Gemma 3 270M/1B and an 83-question fixture. |
The precise description is: a custom W8A8 kernel with optional
QuaRot-inspired block randomized-Hadamard preprocessing.
Evaluation metrics: “Accuracy is Not All You Need”
The benchmark's flips metric follows the definition proposed by
Accuracy is Not All You Need, Dutta,
Krishnan, Kwatra, and Ramjee, NeurIPS 2024. The paper argues that aggregate
accuracy can remain nearly unchanged after model compression even when many
individual answers exchange correctness, and therefore recommends reporting
flips and KL divergence as baseline-relative distance metrics.
For baseline results B and quantized results Q, this implementation uses:
Flips = count(B correct and Q incorrect)
+ count(B incorrect and Q correct)
Flip rate = Flips / number of questions
This matches the paper's definition: incorrect-to-incorrect transitions are
not counted as flips. evals/compare_mmlu.py additionally reports:
correct_to_incorrect and incorrect_to_correct separately;
wrong_to_wrong_changes; and
answer_changes, which includes all three categories when the selected
answer actually changes.
That extra answer_changes field is similar in purpose to the paper's broader
AllFlips analysis, while the report's correctness flips column is the
paper-compatible primary flips metric.
There are two scope differences from the paper:
- The paper compares a higher-precision baseline with compressed variants;
this experiment uses the released SFP model as its control and compares the
additional W8A8 conversion against it.
- The paper's KL definition averages vocabulary-distribution divergence over
tokens of all answer options. This implementation records full-vocabulary
KL(SFP control || W8A8) at the answer token for each MMLU question. The
motivation is paper-based, but the KL sampling protocol is a local,
lower-cost adaptation rather than an exact reproduction.
Result - Comparison
Evaluation scope
| Configuration |
Environment |
Meaning |
| SFP control |
GEMMA_MM_I8=0 GEMMA_MM_I8_ROTATE=0 |
Existing released-weight path. |
| W8A8 naive |
GEMMA_MM_I8=1 GEMMA_MM_I8_ROTATE=0 |
W8A8 without rotation. |
| W8A8 rotated |
GEMMA_MM_I8=1 GEMMA_MM_I8_ROTATE=1 |
W8A8 with block-128 rotation. |
All runs used --num_threads 6 --pin 1 --verbosity 0 and all 83 questions in
gemma/evals/mmlu.json. This repository fixture is not official MMLU, which
has roughly 14,000 questions. Here one question equals 1.20 percentage points,
so one- or two-question accuracy differences are not conclusive.
Hardware specifications
| Component |
Specification |
| Architecture |
x86_64 |
| CPU |
12th Gen Intel Core i5-12400F |
| Topology |
6 physical cores, 12 logical CPUs |
| Relevant SIMD |
AVX2 and AVX-VNNI available; tests selected AVX2 |
| Cache |
L1d 288 KiB total, L2 7.5 MiB total, L3 18 MiB |
| RAM |
15 GiB reported by free -h |
| Swap |
4.0 GiB; about 0.94 GiB occupied before evaluation |
| Filesystem |
449 GiB total; about 244 GiB available during evaluation |
End-to-end results use the x86 biased-weight path. Signed-kernel correctness
was tested, but this machine cannot provide Arm performance data.
Software specifications
| Component |
Version/configuration |
| OS |
Ubuntu 24.04.4 LTS |
| Kernel |
Linux 6.11.0-29-generic x86_64 |
| Compiler |
GCC/G++ 13.3.0 |
| CMake / generator |
3.28.3 / Unix Makefiles |
| Build type |
Release |
| Python |
3.12.9 |
| Highway revision |
9d5b1261 |
| SentencePiece revision |
9045b2f |
| gemma.cpp commit |
7353b0600ab4675f1fc863aaae0f34e90157eb5b |
Combined result
| Model |
Configuration |
Accuracy |
Wall time |
Speedup |
Answer changes |
Correctness flips |
Mean KL |
P95 KL |
Peak RSS |
| 270M |
SFP control |
20/83 (24.10%) |
46.55 s |
1.000x |
- |
- |
- |
- |
766.3 MiB |
| 270M |
W8A8 naive |
19/83 (22.89%) |
32.51 s |
1.432x |
13/83 (15.66%) |
5/83 (6.02%) |
0.544739 |
1.464864 |
1037.2 MiB |
| 270M |
W8A8 rotated |
18/83 (21.69%) |
35.45 s |
1.313x |
3/83 (3.61%) |
2/83 (2.41%) |
0.048856 |
0.141595 |
1039.4 MiB |
| 1B |
SFP control |
25/83 (30.12%) |
285.79 s |
1.000x |
- |
- |
- |
- |
1762.1 MiB |
| 1B |
W8A8 naive |
23/83 (27.71%) |
183.14 s |
1.561x |
17/83 (20.48%) |
8/83 (9.64%) |
0.327469 |
1.060650 |
2728.4 MiB |
| 1B |
W8A8 rotated |
25/83 (30.12%) |
191.24 s |
1.494x |
6/83 (7.23%) |
0/83 (0.00%) |
0.055162 |
0.270375 |
2786.6 MiB |
Definitions:
- Answer change: selected A/B/C/D differs from control, including one wrong
option changing to another wrong option.
- Correctness flip: correct becomes incorrect or incorrect becomes correct.
- KL: full-vocabulary
KL(control || target) at the answer token, in nats.
- Wall time: complete process time, including startup and lazy packing.
Speed
| Model |
Configuration |
Throughput gain |
Wall-time reduction |
Cost vs naive |
| 270M |
W8A8 naive |
+43.19% |
30.16% |
baseline W8A8 |
| 270M |
W8A8 rotated |
+31.30% |
23.84% |
9.06% slower |
| 1B |
W8A8 naive |
+56.05% |
35.92% |
baseline W8A8 |
| 1B |
W8A8 rotated |
+49.44% |
33.08% |
4.43% slower |
Both W8A8 modes materially outperform control. Naive is fastest because it
avoids activation rotation. Rotation retains most of the gain, especially on
1B, where transform overhead is smaller relative to MatMul work.
Flips and answer changes
| Model |
Configuration |
Changes |
Correct -> incorrect |
Incorrect -> correct |
Wrong -> wrong |
Net correct |
| 270M |
W8A8 naive |
13 |
3 |
2 |
8 |
-1 |
| 270M |
W8A8 rotated |
3 |
2 |
0 |
1 |
-2 |
| 1B |
W8A8 naive |
17 |
5 |
3 |
9 |
-2 |
| 1B |
W8A8 rotated |
6 |
0 |
0 |
6 |
0 |
Rotation reduces changes from 13 to 3 on 270M and 17 to 6 on 1B. The 270M
accuracy count is an exception to the strong fidelity trend: two of its three
rotated changes are correct-to-incorrect. With only 83 samples, the difference
between 18, 19, and 20 correct should be interpreted cautiously.
Directly comparing rotated against naive gives 16 changes on 270M (4
correct-to-incorrect, 3 incorrect-to-correct, 9 wrong-to-wrong) and 17 on 1B
(3 correct-to-incorrect, 5 incorrect-to-correct, 9 wrong-to-wrong).
KL divergence
| Model |
Configuration |
Mean |
Median |
P95 |
Maximum |
Mean reduction vs naive |
| 270M |
W8A8 naive |
0.544739 |
0.382031 |
1.464864 |
2.865242 |
- |
| 270M |
W8A8 rotated |
0.048856 |
0.034517 |
0.141595 |
0.219807 |
91.0% |
| 1B |
W8A8 naive |
0.327469 |
0.185496 |
1.060650 |
2.593445 |
- |
| 1B |
W8A8 rotated |
0.055162 |
0.011058 |
0.270375 |
0.601561 |
83.2% |
KL is more sensitive than the final multiple-choice label. Rotation improves
every reported KL statistic for both models. This is the strongest evidence
here that outlier spreading improves per-row int8 quantization fidelity.
RAM and disk usage
| Model |
Configuration |
Peak RSS |
Increase vs control |
| 270M |
SFP control |
766.3 MiB |
- |
| 270M |
W8A8 naive |
1037.2 MiB |
+35.36% |
| 270M |
W8A8 rotated |
1039.4 MiB |
+35.64% |
| 1B |
SFP control |
1762.1 MiB |
- |
| 1B |
W8A8 naive |
2728.4 MiB |
+54.84% |
| 1B |
W8A8 rotated |
2786.6 MiB |
+58.14% |
Runs were serialized and /proc/<pid>/status, free -h, df -h, and result
size were polled. Available RAM never fell below about 10 GiB; free disk stayed
near 244 GiB. Swap use did not increase, falling from about 942 MiB to 921 MiB.
The final result directory is 167 MiB. There were no crashes or OOM events.
Higher prototype RSS is expected because loaded SFP weights and the int8 cache
coexist. A W8-native loader should replace rather than duplicate weights, so
these numbers are not a production W8 memory comparison.
Kernel correctness
| Encoding |
Rotation |
Result |
| Signed int8 |
Off |
PASS, 0 failures |
| Signed int8 |
On |
PASS, 0 failures |
| Biased uint8 |
Off |
PASS, 0 failures |
| Biased uint8 |
On |
PASS, 0 failures |
The rotation-invariance test reported relative dot-product error 1.270e-07.
This proves arithmetic coverage for both encodings on this host, not Arm
end-to-end performance.
Which part is faster? Why is it faster?
Measured ordering
W8A8 naive > W8A8 rotated > SFP control
fastest slowest
Naive W8A8 is 43.19% higher throughput on 270M and 56.05% on 1B. Rotated W8A8
remains 31.30% and 49.44% higher. Rotation costs 9.06% over naive on 270M and
4.43% on 1B.
Why W8A8 beats control
- Repeated BF16 weight-tile decompression is removed. Packed W8 weights are
consumed directly.
- The kernel moves fewer weight bytes. Int8 values reduce cache and memory
traffic relative to decompressed BF16.
- Hardware integer dot instructions do more useful work per instruction.
Highway maps the four-product helper to operations such as x86 vpdpbusd
and Arm signed dot instructions.
- Scaling is delayed. Symmetric quantization permits int32 accumulation
across a K-range before applying the scale product.
- Mature loop machinery is reused. Blocking, threading, fused MatMul, and
autotuning remain in place.
The 1B model benefits more. A reasonable implementation-based explanation is
that larger MatMuls spend more total time in the accelerated kernel and better
amortize packing/startup. No per-layer counters were captured, so this is an
inference rather than measured cycle attribution.
Why naive beats rotated
Rotated W8A8 additionally copies/converts activation rows into temporary float
buffers, performs sign multiplication and O(K log 128) block transforms, and
rotates each weight row during first-use packing. Weight rotation is one-time;
activation rotation repeats every MatMul call. This extra fidelity has a
measurable but modest runtime cost.
Why rotation improves fidelity
One outlier can determine a row's single scale and leave too few effective int8
levels for ordinary values. The normalized randomized Hadamard transform
redistributes the outlier's energy while preserving the exact pre-quantization
dot product. The large reductions in answer changes and KL are consistent with
this mechanism.
Limitations
- Only 83 MMLU questions were evaluated; accuracy claims are low-confidence.
- Each timing configuration was run once, without confidence intervals.
- Only one x86 host was measured; there is no Arm latency or power result.
- SFP-to-int8 double quantization differs from direct-checkpoint W8.
- RSS includes both SFP weights and an int8 cache.
- No cross-entropy, perplexity, or generation benchmark was run.
- No per-layer profiler or hardware counters were captured.
- This is not full QuaRot: residual and KV-cache transformations are absent.
Reproduction
Run from the repository root.
Build
cmake --build build --target \
gemma_mmlu matmul_i8_test matmul_i8_biased_test \
bench_matmul_i8 bench_matmul_i8_biased -j6
Kernel tests
./build/matmul_i8_test
GEMMA_MM_I8_ROTATE=1 ./build/matmul_i8_test
./build/matmul_i8_biased_test
GEMMA_MM_I8_ROTATE=1 ./build/matmul_i8_biased_test
Each should end with PASS (0 failures).
270M comparison
python3 evals/compare_models.py \
--config evals/w8a8_270m_comparison.json \
--build_dir build \
--output_dir evals/results/w8a8_comparison_20260831/270m
1B comparison
python3 evals/compare_models.py \
--config evals/w8a8_1b_comparison.json \
--build_dir build \
--output_dir evals/results/w8a8_comparison_20260831/1b
The tool runs configurations serially. Control writes one full-vocabulary
reference; both W8A8 targets use it for KL.
Resource monitoring
free -h
df -h .
ps -eo pid,ppid,etime,rss,%cpu,comm,args | grep gemma_mmlu
du -sh evals/results/w8a8_comparison_20260831
On a 16 GiB-class host, conservative stops are less than 2 GiB available RAM
or less than 20 GiB disk. Do not run model configurations concurrently.
Artifacts
Reports and structured metrics
evals/results/w8a8_comparison_20260831/REPORT.md: this report.
270m/comparison.md, 1b/comparison.md: generated summary tables.
270m/comparison.json, 1b/comparison.json: exact configuration, accuracy,
timings, flips, KL, and peak RSS.
Raw outputs
Each model result directory contains control, naive, and rotated *.mmlu.out
and *.mmlu.err files plus *-control.root-kl.bin. The two 83 MiB reference
files dominate the 167 MiB artifact directory.
Reproduction inputs
evals/w8a8_270m_comparison.json
evals/w8a8_1b_comparison.json
Implementation files
ops/matmul_i8-inl.h: quantization, rotation, kernel, storage, and entry
points.
ops/matmul_i8_model-inl.h: routing, lazy cache, filters, and environment.
ops/matmul-inl.h: shared four-output integer dot helper.
ops/ops-inl.h: hooks before the standard path.
ops/matmul_i8_test.cc: signed/biased and rotation correctness.
ops/bench_matmul_i8.cc: standalone kernel benchmark.
evals/compare_models.py: end-to-end comparison runner.
W8A8 Implementation
Date: 2026-08-31
Tested commit:
7353b0600ab4675f1fc863aaae0f34e90157eb5bExecutive summary
This work adds an experimental W8A8 matrix-multiplication path to
gemma.cppand evaluates two modes:
outlier mitigation.
randomized Hadamard transforms on weights and activations.
Both modes accelerate the complete 83-question repository MMLU fixture on the
tested x86 machine. Naive W8A8 is fastest: 1.432x control speed on Gemma 3 270M
and 1.561x on Gemma 3 1B. Rotated W8A8 retains most of that speed and preserves
behavior much better, reducing mean
KL(control || W8A8)by 91.0% on 270M and83.2% on 1B relative to naive W8A8. On 1B, rotated W8A8 exactly preserves the
control's 25/83 correctness outcome; naive W8A8 scores 23/83.
The largest process peak was 2786.6 MiB RSS. Available system memory remained
at least approximately 10 GiB and free disk approximately 244 GiB during
monitoring. No run crashed or approached the safety thresholds.
Implementation
Direction implemented
The implementation targets the weight-side decompression cost in the existing
matrix multiplication. The standard compressed-weight path repeatedly
converts weight tiles to BF16 before multiplication. W8A8 instead retains a
packed 8-bit weight representation and sends those bytes directly to integer
dot-product instructions.
The change has four connected parts:
int32, applies activation and weight scales, and writes the expected modeloutput type.
when
GEMMA_MM_I8=1; ineligible calls fall back unchanged.use, and retained in a process-wide cache. Activations are quantized per
call because they change per token.
GEMMA_MM_I8_ROTATE=1, matching orthonormalblock transforms are applied to activation and transposed-weight rows before
quantization.
The kernel reuses existing
MMLoopsblocking, threading, and autotuning. Thefused gated-FFN pair is supported through
TwoMatMulI8, allowing bothprojections to share one quantized activation.
Quantization method
Both operands use symmetric round-to-nearest int8 quantization with maximum
magnitude 127. For an activation row
aand transposed weight rowb:per output channel.
scaling step.
clipping, group scales, or mixed precision.
Signed and biased byte encodings
Activations are always signed int8. The weight encoding depends on the target:
int8 x int8, using native signed dot operations.q_biased = q_b + 128is stored asuint8, enabling efficientunsigned-by-signed VNNI-style dot products. The kernel subtracts
128 * sum(q_a)for each K-range.The x86
+128is an instruction encoding, not an asymmetric quantization zeropoint. Both encodings represent the same symmetric values and have separate
correctness binaries.
Rotation method
Rotated mode divides K into blocks of 128. Each block receives:
+1/-1signs.1 / sqrt(128).The same orthonormal transform
Ris applied to activations and weights:Thus the unquantized dot product is preserved. After quantization, isolated
large values have been spread across 128 coordinates, so one outlier is less
likely to set a coarse scale for the entire row. Rotation is used only when K
is divisible by 128.
Runtime controls
GEMMA_MM_I8=1GEMMA_MM_I8_ROTATE=1GEMMA_MM_I8_MIN_K=<n>non the original path.GEMMA_MM_I8_SKIP_ROWS=<n>non the original path.GEMMA_MM_I8_INCLUDE=<list>GEMMA_MM_I8_EXCLUDE=<list>GEMMA_MM_I8_VERBOSE=1The reported runs used every eligible matrix and no routing filters.
Prototype boundaries
This is an evaluation harness, not a production weight loader:
production path should generate W8 from the original checkpoint.
What method? Based on what paper?
Core kernel
The core kernel is repository-specific work motivated by the weight
decompression bottleneck discussed in
google/gemma.cpp issue #560.
That discussion showed weight-tile decompression dominating parts of runtime
while activation conversion was comparatively small. This implementation
therefore consumes packed 8-bit weights directly, invokes Highway's
four-product integer dot primitive, and reuses gemma.cpp's loop nest.
The symmetric per-token-activation/per-output-channel-weight quantizer is a
simple calibration-free post-training W8A8 method. This report does not claim
that the core kernel reproduces one particular paper.
QuaRot-inspired rotation
The optional rotation is QuaRot-style, based on
QuaRot: Outlier-Free 4-Bit Inference in Rotated LLMs,
Ashkboos et al., NeurIPS 2024. QuaRot uses orthogonal rotations to remove hidden
state outliers without changing the exact full-precision computation, making
quantization easier. The paper also reports calibration-free 8-bit LLaMA-2
results using round-to-nearest quantization.
This implementation borrows the randomized-Hadamard outlier-spreading idea but
is not full QuaRot:
The precise description is: a custom W8A8 kernel with optional
QuaRot-inspired block randomized-Hadamard preprocessing.
Evaluation metrics: “Accuracy is Not All You Need”
The benchmark's flips metric follows the definition proposed by
Accuracy is Not All You Need, Dutta,
Krishnan, Kwatra, and Ramjee, NeurIPS 2024. The paper argues that aggregate
accuracy can remain nearly unchanged after model compression even when many
individual answers exchange correctness, and therefore recommends reporting
flips and KL divergence as baseline-relative distance metrics.
For baseline results
Band quantized resultsQ, this implementation uses:This matches the paper's definition: incorrect-to-incorrect transitions are
not counted as flips.
evals/compare_mmlu.pyadditionally reports:correct_to_incorrectandincorrect_to_correctseparately;wrong_to_wrong_changes; andanswer_changes, which includes all three categories when the selectedanswer actually changes.
That extra
answer_changesfield is similar in purpose to the paper's broaderAllFlips analysis, while the report's
correctness flipscolumn is thepaper-compatible primary flips metric.
There are two scope differences from the paper:
this experiment uses the released SFP model as its control and compares the
additional W8A8 conversion against it.
tokens of all answer options. This implementation records full-vocabulary
KL(SFP control || W8A8)at the answer token for each MMLU question. Themotivation is paper-based, but the KL sampling protocol is a local,
lower-cost adaptation rather than an exact reproduction.
Result - Comparison
Evaluation scope
GEMMA_MM_I8=0 GEMMA_MM_I8_ROTATE=0GEMMA_MM_I8=1 GEMMA_MM_I8_ROTATE=0GEMMA_MM_I8=1 GEMMA_MM_I8_ROTATE=1All runs used
--num_threads 6 --pin 1 --verbosity 0and all 83 questions ingemma/evals/mmlu.json. This repository fixture is not official MMLU, whichhas roughly 14,000 questions. Here one question equals 1.20 percentage points,
so one- or two-question accuracy differences are not conclusive.
Hardware specifications
free -hEnd-to-end results use the x86 biased-weight path. Signed-kernel correctness
was tested, but this machine cannot provide Arm performance data.
Software specifications
9d5b12619045b2f7353b0600ab4675f1fc863aaae0f34e90157eb5bCombined result
Definitions:
option changing to another wrong option.
KL(control || target)at the answer token, in nats.Speed
Both W8A8 modes materially outperform control. Naive is fastest because it
avoids activation rotation. Rotation retains most of the gain, especially on
1B, where transform overhead is smaller relative to MatMul work.
Flips and answer changes
Rotation reduces changes from 13 to 3 on 270M and 17 to 6 on 1B. The 270M
accuracy count is an exception to the strong fidelity trend: two of its three
rotated changes are correct-to-incorrect. With only 83 samples, the difference
between 18, 19, and 20 correct should be interpreted cautiously.
Directly comparing rotated against naive gives 16 changes on 270M (4
correct-to-incorrect, 3 incorrect-to-correct, 9 wrong-to-wrong) and 17 on 1B
(3 correct-to-incorrect, 5 incorrect-to-correct, 9 wrong-to-wrong).
KL divergence
KL is more sensitive than the final multiple-choice label. Rotation improves
every reported KL statistic for both models. This is the strongest evidence
here that outlier spreading improves per-row int8 quantization fidelity.
RAM and disk usage
Runs were serialized and
/proc/<pid>/status,free -h,df -h, and resultsize were polled. Available RAM never fell below about 10 GiB; free disk stayed
near 244 GiB. Swap use did not increase, falling from about 942 MiB to 921 MiB.
The final result directory is 167 MiB. There were no crashes or OOM events.
Higher prototype RSS is expected because loaded SFP weights and the int8 cache
coexist. A W8-native loader should replace rather than duplicate weights, so
these numbers are not a production W8 memory comparison.
Kernel correctness
The rotation-invariance test reported relative dot-product error
1.270e-07.This proves arithmetic coverage for both encodings on this host, not Arm
end-to-end performance.
Which part is faster? Why is it faster?
Measured ordering
Naive W8A8 is 43.19% higher throughput on 270M and 56.05% on 1B. Rotated W8A8
remains 31.30% and 49.44% higher. Rotation costs 9.06% over naive on 270M and
4.43% on 1B.
Why W8A8 beats control
consumed directly.
traffic relative to decompressed BF16.
Highway maps the four-product helper to operations such as x86
vpdpbusdand Arm signed dot instructions.
across a K-range before applying the scale product.
autotuning remain in place.
The 1B model benefits more. A reasonable implementation-based explanation is
that larger MatMuls spend more total time in the accelerated kernel and better
amortize packing/startup. No per-layer counters were captured, so this is an
inference rather than measured cycle attribution.
Why naive beats rotated
Rotated W8A8 additionally copies/converts activation rows into temporary float
buffers, performs sign multiplication and
O(K log 128)block transforms, androtates each weight row during first-use packing. Weight rotation is one-time;
activation rotation repeats every MatMul call. This extra fidelity has a
measurable but modest runtime cost.
Why rotation improves fidelity
One outlier can determine a row's single scale and leave too few effective int8
levels for ordinary values. The normalized randomized Hadamard transform
redistributes the outlier's energy while preserving the exact pre-quantization
dot product. The large reductions in answer changes and KL are consistent with
this mechanism.
Limitations
Reproduction
Run from the repository root.
Build
Kernel tests
Each should end with
PASS (0 failures).270M comparison
1B comparison
The tool runs configurations serially. Control writes one full-vocabulary
reference; both W8A8 targets use it for KL.
Resource monitoring
On a 16 GiB-class host, conservative stops are less than 2 GiB available RAM
or less than 20 GiB disk. Do not run model configurations concurrently.
Artifacts
Reports and structured metrics
evals/results/w8a8_comparison_20260831/REPORT.md: this report.270m/comparison.md,1b/comparison.md: generated summary tables.270m/comparison.json,1b/comparison.json: exact configuration, accuracy,timings, flips, KL, and peak RSS.
Raw outputs
Each model result directory contains control, naive, and rotated
*.mmlu.outand
*.mmlu.errfiles plus*-control.root-kl.bin. The two 83 MiB referencefiles dominate the 167 MiB artifact directory.
Reproduction inputs
evals/w8a8_270m_comparison.jsonevals/w8a8_1b_comparison.jsonImplementation files
ops/matmul_i8-inl.h: quantization, rotation, kernel, storage, and entrypoints.
ops/matmul_i8_model-inl.h: routing, lazy cache, filters, and environment.ops/matmul-inl.h: shared four-output integer dot helper.ops/ops-inl.h: hooks before the standard path.ops/matmul_i8_test.cc: signed/biased and rotation correctness.ops/bench_matmul_i8.cc: standalone kernel benchmark.evals/compare_models.py: end-to-end comparison runner.