Repository navigation
tools/microbench: Phase 2 harness for Q1_0_g128 AVX2/AVX-VNNI repack - #11
Merged
Merged
Conversation
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).
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
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.
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.
4 tasks
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>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.cppthat:Q1_0_g128weight blocks and matchingQ8_0activations from a fixed RNG seed, so every run producesidentical inputs.
(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/istouched; that's Phase 3.
matches the scalar baseline within 1e-3 relative tolerance per
row. Failed parity exits before timing.
clock_gettime(CLOCK_MONOTONIC_RAW)over awarm-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, seenumbers. 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 dispatchertensor_traitsplumbing 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:
vec_dot_avx2(existing)gemv_4x4_avx2(NEW Kernel A)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
qyandsum_qy_vacross the 4rows, 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:
That's a ~30% improvement, not the 50–60 t/s the brief targeted.
delivers 41 t/s with zero kernel work.
to a single
vpdpbusd_avxand is expected to push throughputclose 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.mdwalks through thisfinding 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 256For AVX-VNNI hosts:
cmake -S . -B build -DLLAMA_MICROBENCH_AVX_VNNI=ONReview & 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/.block_q1_0_g128x4packed layout inq1_g128_repack.cpp:97matches the design brief's"[d0 d1 d2 d3] [r0_sb0 r1_sb0 r2_sb0 r3_sb0] …" memo (Phase-1c §"Quant byte interleave granularity").
q1_g128_repack.cpp:343against 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_qyin
__m256i,hsum_float_8only at row close).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
No tests in
tests/are added because the kernel still lives inthe harness, not in
ggml-cpu. Phase 3 will add a realtest-quantize-fns-style parity test against thetensor_traitsdispatcher.Decision needed before Phase 3
Three options based on the new data:
ggml-cpu/arch/x86/repack.cpp+ dispatcher). Modest but real,transparent to upstream model artifacts.
target the win on Alder Lake / Zen 4 hosts where Kernel B
should approach AVX-512 perf.
sum_qy precomputation in
block_q8_0_sum, or 8-rowinterleave, 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