Skip to content

perf(cpu): the R4 Q8_0 prefill kernel has no AVX-512 or VNNI tier #414

Description

@kkokosa

Observation

ComputeRowsQ8_0Interleaved (src/DotLLM.Cpu/Kernels/MatMul.cs:324) is the kernel every Q8_0 prefill token goes through after #400 routed prefill to GemmR4TiledQ8_0. Its entire ISA dispatch is:

if (Avx2.IsSupported)  -> VecDotQ8_0Avx2_4RowsR4
else                   -> VecDotQ8_0ScalarR4

There is no AVX-512 tier and no VNNI tier. VecDotQ8_0Avx2_4RowsR4 is Vector256 throughout — Avx2.Sign, vpmaddubsw, vpmaddwd(ones), four separate Vector256<float> accumulators, per row per block.

The VNNI work from #399 went into ComputeRows (MatMul.cs:134), gated on Avx512BW.IsSupported && AvxVnni.IsSupported. That is the row-major path. GemmR4TiledQ8_0 reaches it only for the tailRows remainder (at most 3 rows per matrix).

So on an AVX-512 host the Q8_0 prefill matmul runs entirely on 256-bit AVX2 integer ops. #399 gave VNNI to the decode path; #400 then routed prefill away from it.

Proposed change

Add VecDotQ8_0Vnni_4RowsR4 — port the dual-block structure of ProcessVnniDualBlock / VecDotQ8_0Vnni_4Rows onto the R4-interleaved layout — and give ComputeRowsQ8_0Interleaved the same tiered dispatch ComputeRows already has:

Tier Guard Kernel
VNNI Avx512BW && AvxVnni VecDotQ8_0Vnni_4RowsR4 (new)
AVX-512 Avx512BW 512-bit R4 variant (new, or fall through to AVX2 initially)
AVX2 Avx2 VecDotQ8_0Avx2_4RowsR4 (existing)
scalar VecDotQ8_0ScalarR4 (existing)

The R4 layout should suit VNNI better than row-major does: the four rows' blocks for a given column are contiguous within 136 bytes, and dual-blocking runs along K. That directly addresses the "blocks aren't contiguous 64 bytes, so the V512 path pays two extra Vector256->Vector512 inserts per cell" problem recorded in #322.

Bit-exactness argument carries over from #399 unchanged: both forms group 4 consecutive byte products into one int32 lane, the i16 intermediate saturates only above 32767, and |x|,|w| <= 127 bounds each lane at 2127127 = 32258.

This may not be a win

#322 comment [4699772011 thread] records the counter-hypothesis directly: "Zen5 executes the 256-bit maddubs+madd pair with more throughput/ILP than one serial 512-bit VPDPBUSD." Four independent 256-bit accumulators is reasonable ILP and going wider can serialize. This is an experiment, not a presumed fix — a negative result is a useful outcome and should be recorded as one.

Measurement plan

  1. Parity first — vs VecDotQ8_0ScalarR4 over blockCount in {1, 2, 3, 8, 17, 18, 48, 128} (covers the odd-block tail, the single-block case, and multi-tile K), plus a discrimination self-check: perturb one cell and confirm the test actually fails, so a skipped ISA guard cannot masquerade as a pass.
  2. Microbenchmark — new vs existing R4 kernel at K=4096, matching the shape used in perf(cpu/matmul): AVX-512 VNNI for Q8_0 outer-product (AvxVnni.V512 / VPDPBUSD-512) — requires .NET 11 bump #322 so numbers are comparable to that table.
  3. GEMM levelGemmR4TiledQ8_0 at realistic prefill shapes (m = hidden dim, n = 256/512), where L2 tiling and thread partitioning show up and the microbenchmark will not.
  4. End-to-end — Llama-3.2-1B Q8_0 pp256/pp512 through bench_compare, with llama.cpp as the external anchor. This is the number that decides whether it mattered.

Why this before anything else in the Q8_0 backlog

Acceptance criteria

  • VecDotQ8_0Vnni_4RowsR4 matches VecDotQ8_0ScalarR4 across the discriminating block counts above.
  • Discrimination self-check confirms the tests fail on a deliberately perturbed cell.
  • ComputeRowsQ8_0Interleaved dispatches tiered, with the AVX2 path retained as fallback and still covered by tests.
  • Microbenchmark and end-to-end prefill numbers recorded in the PR, whichever direction they point.
  • If VNNI is not a win on this hardware, the AVX2 path stays the default and the result is documented rather than shipped.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions