Skip to content

tools/microbench: Phase 2 harness for Q1_0_g128 AVX2/AVX-VNNI repack - #11

Merged
kennguy3n merged 3 commits into
prismfrom
devin/1777621102-prism-microbench-q1g128-repack
May 1, 2026
Merged

kennguy3n merged 3 commits into
prismfrom
devin/1777621102-prism-microbench-q1g128-repack

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Phase 2 of the AVX2 / AVX-VNNI repack project (Phase 1c design brief
landed in PR #10).

This PR adds a stand-alone microbenchmark harness at
tools/microbench/q1_g128_repack.cpp that:

  1. Synthesises N deterministic Q1_0_g128 weight blocks and matching
    Q8_0 activations from a fixed RNG seed, so every run produces
    identical inputs.
  2. Implements the four kernel variants from the Phase-1c brief
    (scalar reference, existing single-row AVX2, proposed 4x4 GEMV
    AVX2 [Kernel A], proposed 4x4 GEMV AVX-VNNI [Kernel B]) inside a
    single self-contained translation unit. Nothing in ggml-cpu/ is
    touched; that's Phase 3.
  3. Runs a parity check: every kernel must produce FP32 output that
    matches the scalar baseline within 1e-3 relative tolerance per
    row. Failed parity exits before timing.
  4. Times each kernel via clock_gettime(CLOCK_MONOTONIC_RAW) over a
    warm-up + steady-state loop, reports median ns / 4 rows so all
    kernels can be compared apples-to-apples (the GEMV does 4 rows in
    one call; the single-row kernels are looped 4×).

Why a separate harness, not a tests/ test?

The harness is a CLI you can iterate on in seconds — change a few
intrinsics, rebuild only microbench-q1-g128-repack, run, see
numbers. Once the kernel intrinsics are validated against a parity
check + show real perf, they get ported into
ggml/src/ggml-cpu/arch/x86/repack.cpp (Phase 3) and the dispatcher

  • tensor_traits plumbing follows (Phase 4).

Headline numbers (committed in tools/microbench/baseline-zen3-epyc-7763.txt)

EPYC 7763 / Zen 3 (AVX2 + FMA + F16C, no AVX-512), nb=13312
(~1.7M weights ≈ Bonsai-1.7B-shaped), iters=256, single-threaded:

Kernel ns / 4 rows ns / row vs baseline
scalar reference 34 476 683 8 619 171 0.013×
vec_dot_avx2 (existing) 451 293 112 823 1.00×
gemv_4x4_avx2 (NEW Kernel A) 351 807 87 952 1.28×

Parity passes — every kernel produces bit-identical FP32 output
within FP16 quantisation noise.

Important: actual gain is less than the design brief predicted

The Phase-1c brief estimated 2.5× over the existing AVX2 path on
Zen 3. The measured gain is 1.28×. Root cause: the per-row bit
expansion (broadcast + shuffle + and + cmpeq) is the single most
expensive part of the inner loop, and each row has different
qbits, so this work cannot be shared across rows in this layout.
The savings come only from sharing qy and sum_qy_v across the 4
rows, which is real but smaller than estimated.

This is exactly what Phase 2 was for — the harness produces the data
that gates Phase 3. The 1.28× number changes the conversation:

  • Real-world projection on the demo VM: 23 t/s prefill → ~28–30 t/s.
    That's a ~30% improvement, not the 50–60 t/s the brief targeted.
  • The Q3_K_M alternative (already documented in cv-guard PR docs(avx2-repack-design): drop-unpack experiment write-up (kernel reverted) #15)
    delivers 41 t/s with zero kernel work.
  • AVX-VNNI is the real prize — Kernel B collapses the per-row dot
    to a single vpdpbusd_avx and is expected to push throughput
    close to the AVX-512 fast-path. We can't measure that on Zen 3;
    it needs an Alder Lake P-core or Zen 4 host.

The README in tools/microbench/README.md walks through this
finding in detail and proposes a revised Phase-3-go/no-go criterion.

Build & run

cmake -S . -B build -DLLAMA_CURL=OFF -DGGML_NATIVE=OFF
cmake --build build -j --target microbench-q1-g128-repack

./build/bin/microbench-q1-g128-repack --blocks 13312 --iters 256

For AVX-VNNI hosts:

cmake -S . -B build -DLLAMA_MICROBENCH_AVX_VNNI=ON

Review & Testing Checklist for Human

This is a measurement PR, not a kernel-change PR. The right thing
to look at is the methodology + the actual numbers, not the
intrinsics. Marking yellow because the numbers contradict the brief
and we should re-decide the Phase-3 plan before writing kernel code
in ggml-cpu/.

  • Confirm the block_q1_0_g128x4 packed layout in
    q1_g128_repack.cpp:97 matches the design brief's
    "[d0 d1 d2 d3] [r0_sb0 r1_sb0 r2_sb0 r3_sb0] …" memo (Phase-1c §"Quant byte interleave granularity").
  • Sanity check the AVX2 kernel at q1_g128_repack.cpp:343
    against the Phase-1c pseudocode in docs/avx2-repack-design/03-design-brief.md
    §GEMV-A. The harness matches the post-fix vectorised version
    (no horizontal sums in the inner loop, 2*partial − sum_qy
    in __m256i, hsum_float_8 only at row close).
  • Run the harness on a different x86 host (any AVX2 host) and
    confirm the parity check still passes and the speedup ratio is
    in the same ballpark. The deterministic seed means the FP outputs
    must be byte-identical across runs of the same --seed.

Test Plan

cmake --build build -j --target microbench-q1-g128-repack
./build/bin/microbench-q1-g128-repack --blocks 4096 --iters 256
# parity OK, gemv_4x4_avx2 ~1.24× faster than vec_dot_avx2

./build/bin/microbench-q1-g128-repack --blocks 13312 --iters 256
# parity OK, ~1.28× faster (committed at tools/microbench/baseline-zen3-epyc-7763.txt)

No tests in tests/ are added because the kernel still lives in
the harness, not in ggml-cpu. Phase 3 will add a real
test-quantize-fns-style parity test against the
tensor_traits dispatcher.

Decision needed before Phase 3

Three options based on the new data:

  1. Ship the 1.28× win as Phase 3 (port Kernel A into
    ggml-cpu/arch/x86/repack.cpp + dispatcher). Modest but real,
    transparent to upstream model artifacts.
  2. Pivot to AVX-VNNI only, document Zen 3 as "use Q3_K_M" and
    target the win on Alder Lake / Zen 4 hosts where Kernel B
    should approach AVX-512 perf.
  3. Keep iterating in this harness before integrating: try
    sum_qy precomputation in block_q8_0_sum, or 8-row
    interleave, or shared bit expansion via 64-bit shuffle pairs.
    Probably gets us to 1.5×; whether that's worth another week
    depends on whether (1) is a hard "no".

I have no preference between (1) and (2); (3) is honest engineering
but the diminishing returns are real.

Link to Devin session: https://uney.devinenterprise.com/sessions/576d78b01f9b4389b375033162f7b7b4
Requested by: @kennguy3n

Adds a stand-alone microbenchmark harness at
tools/microbench/q1_g128_repack.cpp implementing the four kernel
variants from docs/avx2-repack-design/03-design-brief.md:

  (a) scalar reference (parity baseline)
  (b) existing single-row AVX2 vec_dot
  (c) proposed AVX2 4x4 GEMV (Kernel A)
  (d) proposed AVX-VNNI 4x4 GEMV (Kernel B, gated on __AVXVNNI__)

The harness is a CLI tool (microbench-q1-g128-repack) that
synthesises deterministic inputs from a fixed seed, repacks 4
rows into the proposed block_q1_0_g128x4 layout, runs all four
kernels on the same data, asserts bit-identical FP32 output
within FP16 tolerance, then reports median ns/row throughput.

It does NOT touch ggml/src/ggml-cpu/. The validated kernels
will be ported to repack.cpp + the dispatcher in Phase 3 once
the timing data justifies that work.

Baseline run on the EPYC 7763 / Zen 3 demo VM committed at
tools/microbench/baseline-zen3-epyc-7763.txt:
  vec_dot_avx2          451293 ns / 4 rows
  gemv_4x4_avx2 (NEW)   351807 ns / 4 rows  (1.28x speedup)

Honest finding: the design brief estimated 2.5x; the measured
gain on AVX2-only Zen 3 is 1.28x because the per-row bit
expansion is the dominant cost and cannot be shared across
rows in this layout. AVX-VNNI is the real prize and needs an
Alder Lake or Zen 4 host to validate. README.md documents the
gap and the realistic prefill projection (23 t/s -> ~30 t/s).
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

Two correctness fixes:

* fp16_to_fp32 fallback: subnormal exponent initialised at e=-1
  was off by 14 (FP16 subnormals have true exponent 1-15=-14, so
  e=-15 is the right starting value). Dead path on F16C hosts;
  would have produced 16384x values on a non-F16C build. Reported
  by Devin Review on PR #11.

* run_parity per-row 'OK'/'MISMATCH' string only checked (a && b)
  — when __AVXVNNI__ was defined, a VNNI-only mismatch would have
  printed 'OK' even though the overall parity check correctly
  returned failure. Refactored to use a shared row_ok variable
  that picks up the VNNI result inside the conditional. Reported
  by Devin Review on PR #11.

Plus AVX-VNNI parity verification:

* Built a -DLLAMA_MICROBENCH_AVX_VNNI=ON variant and ran it under
  Intel SDE 9.48 with the -adl (Alder Lake) chip profile. Parity
  passes — Kernel B produces row-by-row identical FP32 output to
  Kernel A and the scalar baseline. Output committed at
  tools/microbench/parity-alderlake-sde.txt with a header
  explaining that SDE timing is meaningless (only parity is).
  README updated with the SDE recipe.

  qemu-user 6.2 was tried first; its TCG does not implement
  vpdpbusd_avx and the binary segfaults on the first call.
devin-ai-integration[bot]

This comment was marked as resolved.

Reject nb <= 0 and iters <= 0 with a clear error message before
parity/timing run. Without this, median_ns(... iters=0) accesses
samples[0] on an empty std::vector, which is UB (likely segv on
release builds, abort on debug). Reported by Devin Review on PR
#11.

Tested manually:

  $ ./build/bin/microbench-q1-g128-repack --iters 0
  error: --iters must be > 0 (got 0)
  $ ./build/bin/microbench-q1-g128-repack --blocks 0
  error: --blocks must be > 0 (got 0)

Default args path unchanged.
@kennguy3n
kennguy3n merged commit 2ec023a into prism May 1, 2026
1 check passed
kennguy3n added a commit that referenced this pull request May 1, 2026
…kernel-b

Adds AVX-VNNI variants of the 4-row GEMV and true 4x4 GEMM kernels
that PR #13 introduced for AVX2-only hosts. Same `block_q1_0_g128x4`
repack format (no on-disk format change); the dispatcher in
`ggml_gemv_q1_0_g128_4x4_q8_0` and `ggml_gemm_q1_0_g128_4x4_q8_0`
prefers Kernel B over Kernel A whenever `__AVXVNNI__` is defined.

Per (r_a, r_w) per sub-block, the inner body collapses

  masked  = blendv(zero, qy, mask_w[r_w])
  partial = madd(maddubs(ones, masked), ones)
  row_dot = 2*partial - sum_qy_v

(5 ops + 2 shared per qy load) into a single signed-byte dot

  signed_qy = blendv(neg_qy, qy, mask_w[r_w])
  int_acc   = dpbusd(zero, ones_b, signed_qy)

(2 ops + 1 shared per qy load). Per-sub-block hot-path op count
drops from 22 to 9 for the GEMM kernel; same 16-accumulator
register pressure profile as the AVX2 path.

Validation:

* Default Zen 3 / AVX2-only build (`-march=native`): unchanged.
  Kernel B is excluded at compile time; Kernel A from PR #13
  remains the fast path. ctest 50/50 pass.

* `-DGGML_AVX_VNNI=ON` build under Intel SDE 9.48 `-adl` (Alder
  Lake): GEMV (nr=1) + GEMM (nr=4) + GEMM (nr=8) all match the
  single-row `vec_dot` reference with diff = 0.000000e+00 on every
  element. End-to-end smoke (`llama-cli` with Bonsai-1.7B) under
  SDE generates "Paris" correctly for "The capital of France is".
  Disassembly: 506 `vpdpbusd` instructions in libggml-cpu.so.

* No real-hardware perf measurement -- demo VM is AMD EPYC 7763
  (Zen 3, no native AVX-VNNI). SDE timing is meaningless. Whoever
  has Alder Lake / Sapphire Rapids / Zen 4 can bench against the
  PR #13 Zen 3 baseline of 30.87 t/s pp512 and commit the result
  next to phase3-end-to-end-zen3-epyc-7763.txt.

This change:

* docs/avx2-repack-design/06-avx-vnni-kernel-b.md  (design + bench
  recipe + projected uplift discussion)
* docs/avx2-repack-design/parity-alderlake-sde-kernel-b.txt  (SDE
  output verbatim with build/run recipe in header)
* ggml/src/ggml-cpu/arch/x86/repack.cpp  (kernels + dispatcher)

AI authorship disclosure: AI-assisted port from PR #11's Phase 2
microbench draft into the repack file, under explicit authorisation
from @kennguy3n on the prism private fork (per AGENTS.md /
CONTRIBUTING.md preamble added in PR #16). Not intended for
upstream `ggml-org/llama.cpp`.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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