You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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
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.
GEMM level — GemmR4TiledQ8_0 at realistic prefill shapes (m = hidden dim, n = 256/512), where L2 tiling and thread partitioning show up and the microbenchmark will not.
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.
Observation
ComputeRowsQ8_0Interleaved(src/DotLLM.Cpu/Kernels/MatMul.cs:324) is the kernel every Q8_0 prefill token goes through after #400 routed prefill toGemmR4TiledQ8_0. Its entire ISA dispatch is:There is no AVX-512 tier and no VNNI tier.
VecDotQ8_0Avx2_4RowsR4isVector256throughout —Avx2.Sign,vpmaddubsw,vpmaddwd(ones), four separateVector256<float>accumulators, per row per block.The VNNI work from #399 went into
ComputeRows(MatMul.cs:134), gated onAvx512BW.IsSupported && AvxVnni.IsSupported. That is the row-major path.GemmR4TiledQ8_0reaches it only for thetailRowsremainder (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 ofProcessVnniDualBlock/VecDotQ8_0Vnni_4Rowsonto the R4-interleaved layout — and giveComputeRowsQ8_0Interleavedthe same tiered dispatchComputeRowsalready has:Avx512BW && AvxVnniVecDotQ8_0Vnni_4RowsR4(new)Avx512BWAvx2VecDotQ8_0Avx2_4RowsR4(existing)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->Vector512inserts 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+maddpair 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
VecDotQ8_0ScalarR4overblockCountin {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.GemmR4TiledQ8_0at realistic prefill shapes (m = hidden dim, n = 256/512), where L2 tiling and thread partitioning show up and the microbenchmark will not.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_4RowsR4matchesVecDotQ8_0ScalarR4across the discriminating block counts above.ComputeRowsQ8_0Interleaveddispatches tiered, with the AVX2 path retained as fallback and still covered by tests.References
src/DotLLM.Cpu/Kernels/MatMul.cs—ComputeRowsQ8_0Interleaved(:324),VecDotQ8_0Avx2_4RowsR4(:235),VecDotQ8_0ScalarR4(:204),GemmR4TiledQ8_0(:380),ComputeRows(:130),VecDotQ8_0Vnni_4Rows(:883),ProcessVnniDualBlock(:933)vpdpbusdfor the row-major Q8_0 4-row dot product