Skip to content

perf(cpu): AVX2 beats both 512-bit tiers in the Q8_0 row-major dot product, but ComputeRows prefers them #415

Description

@kkokosa

Observation

The row-major A/B added in #414's benchmark (RowMajorVnniBenchmarks) compares all three tiers ComputeRows dispatches between. On an AVX-512 host (AVX-512F+CD+BW+DQ+VL+VBMI, .NET 10.0.10, BDN 0.14.0, 3 warmup / 10 iterations, single-threaded), baseline is the AVX-512 maddubs kernel that #399's VNNI kernel displaced:

Kernel K=512 ratio K=4096 ratio
VecDotQ8_0Avx512_4Rows (maddubs, pre-#399) 162.08 ns 1.01 816.83 ns 1.00
VecDotQ8_0Vnni_4Rows (#399, currently shipped) 108.78 ns 0.67 795.41 ns 0.97
VecDotQ8_0Avx2_4Rows (AVX2 maddubs) 89.62 ns 0.56 695.54 ns 0.85

Two things follow:

  1. perf(cpu): use AVX-VNNI vpdpbusd for the Q8_0 4-row dot product #399 was a genuine improvement over what it replaced — 0.67x at K=512, 0.97x at K=4096 against the AVX-512 maddubs tier.
  2. But plain AVX2 beats both, by 1.21x over VNNI at K=4096 and 1.21x at K=512. And ComputeRows (src/DotLLM.Cpu/Kernels/MatMul.cs:130) tiers Avx512BW && AvxVnni -> Avx512BW -> Avx2, so on this hardware the fastest of the three is the one tier that never gets selected.

This is the same shape as the #414 result on the R4 kernel, where the 256-bit AVX2 kernel beat the VNNI one by 1.8-1.9x. Two independent kernel families, same ordering: the 512-bit dual-block variants lose to 256-bit single-block. Consistent with the mechanism — Q8_0's per-block fp16 scale forces a convert-scale-FMA every 32 elements, and the Vector256->Vector512 assembly plus scale broadcast-and-insert costs more than the wider FMA saves.

Why this is not simply "flip the dispatch order"

ComputeRows is the decode path (GEMV, n == 1), and decode is memory-bandwidth-bound, not kernel-throughput-bound. A 1.21x microkernel win may not survive end-to-end at all. Measured earlier in the same investigation: decode on Llama-3.2-1B Q8_0 runs well under this machine's memory ceiling, so the kernel may not be the limiter.

So the microbenchmark justifies investigating, not changing. The change needs an end-to-end decode measurement before it is defensible.

Proposed work

  1. Measure end-to-end decode tok/s with the dispatch preferring AVX2, against main, on Llama-3.2-1B Q8_0 — the number that decides this.
  2. If it moves: reorder the tiers, with a comment recording the measurement, and keep the wider kernels reachable for hardware that orders them differently.
  3. If it does not move: document that the tier ordering is performance-neutral for decode and close. That result is worth having written down, because it tells us decode work belongs in traffic reduction rather than kernel width.
  4. Either way, re-run RowMajorVnniBenchmarks on a second microarchitecture before concluding anything general. One CPU is one CPU — @jamesburton's perf(cpu/matmul): AVX-512 VNNI for Q8_0 outer-product (AvxVnni.V512 / VPDPBUSD-512) — requires .NET 11 bump #322 numbers are Zen5 and ordered these differently.

Acceptance criteria

  • End-to-end decode measured for both dispatch orders on the same build and model.
  • Any dispatch change carries the measurement in a comment, and parity tests still pass on every reachable tier.
  • The negative case is documented rather than silently dropped.

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