Skip to content

Add W8A8 int8 MatMul with optional rotations - #1003

Open
Mikyx-1 wants to merge 4 commits into
google:devfrom
Mikyx-1:w8a8-int8-matmul-dev
Open

Add W8A8 int8 MatMul with optional rotations#1003
Mikyx-1 wants to merge 4 commits into
google:devfrom
Mikyx-1:w8a8-int8-matmul-dev

Conversation

@Mikyx-1

@Mikyx-1 Mikyx-1 commented Aug 31, 2026

Copy link
Copy Markdown

Summary

  • add a symmetric W8A8 MatMul kernel with per-token activation scales, per-output-channel weight scales, and int32 accumulation
  • consume packed int8 weights directly through Highway integer dot products instead of repeatedly decompressing weight tiles to BF16
  • use the native signed encoding on Arm-like targets and an x86 biased-weight encoding with per-K-range correction
  • add an optional block-128 Rademacher-Hadamard rotation to reduce quantization outlier error
  • add model-level experimental routing, lazy weight caching, selection controls, kernel benchmarks, correctness tests, and generic model-comparison tooling

Evaluation

The complete 83-question repository MMLU fixture was run on Gemma 3 270M IT and 1B IT. Full methodology, hardware/software details, flips, KL, RAM, and raw-artifact descriptions are in issue #1002.

Model Configuration Accuracy Speedup Answer changes Correctness flips Mean KL Peak RSS
270M naive W8A8 19/83 1.432x 13/83 5/83 0.544739 1037.2 MiB
270M rotated W8A8 18/83 1.313x 3/83 2/83 0.048856 1039.4 MiB
1B naive W8A8 23/83 1.561x 17/83 8/83 0.327469 2728.4 MiB
1B rotated W8A8 25/83 1.494x 6/83 0/83 0.055162 2786.6 MiB

Rotation reduced mean KL versus naive W8A8 by 91.0% on 270M and 83.2% on 1B while retaining most of the speedup. The fixture is small, so one- or two-question accuracy differences are not statistically conclusive.

Validation

  • signed int8, rotation off: PASS
  • signed int8, rotation on: PASS
  • biased uint8, rotation off: PASS
  • biased uint8, rotation on: PASS
  • rotation dot-product relative error: 1.270e-07
  • compare_models Python tests: 6 passed
  • full 270M and 1B evaluations completed without OOM or disk pressure

Prototype scope

The model integration is intentionally experimental. It lazily quantizes from the loaded representation, retains a process-wide cache, and currently shares shape-only MatMul autotune keys with the original path. A production integration should load weights quantized directly from the original checkpoint and use separate tuning identities.

Closes #1002

@google-cla

google-cla Bot commented Aug 31, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Prototype of the int8*int8 path suggested in google#560. Unlike the existing I8Stream support, which dequantizes B to BF16 per tile via MMDecompress::DecompressB, this consumes B directly and multiplies with hn::SumOfMulQuadAccumulate (vpdpbusd on x86 VNNI, sdot/usdot on NEON, svdot on SVE), accumulating in int32.

Quantization is symmetric throughout: per-token scales for A, computed on the fly in place of DecompressA, and per-output-channel scales for B baked in at pack time. No zero points, so C[r,c] = a_scale[r] * b_scale[c] * dot(qa[r], qb[c]) and int32 accumulation runs over a whole kc range before a single scaling step.

MMLoops is now generic over the kernel and B type, so the int8 path reuses the existing blocking, parallelization and autotuning rather than duplicating the loop nest. The BF16 path is unchanged; matmul_test still passes on all attainable targets.

On x86 the 4-way dot needs one unsigned operand. B is biased by 128 there and the 128*sum_k(qa) term is subtracted per kc range using prefix sums of the quantized A. Biasing B rather than A is what makes that per-range correction cheap, and it keeps the values written to C close to the true partial sums: correcting once over the whole K would inflate the intermediates that MMAddC accumulates through C, which is unrecoverable when C is BF16 and the weight channels are not zero-mean.

matmul_i8_test is built twice, once per encoding, so the x86 path is covered on non-x86 hosts. bench_matmul_i8 reports throughput against the BF16 and SFP kernels plus accuracy against an F64 reference.

matmul_i8_model-inl.h routes the model's MatMuls through the kernel behind GEMMA_MM_I8=1, for end-to-end measurement only. It quantizes lazily from whatever the weights file holds, so it stacks a second quantization on top of e.g. SFP; a production path would quantize the original checkpoint and would not share MatMulEnv's shape-only autotune keys between the two kernels.

Fixes #1
@Mikyx-1
Mikyx-1 force-pushed the w8a8-int8-matmul-dev branch from 7353b06 to f4034f4 Compare August 31, 2026 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant