Skip to content

ggml-cpu(repack): AVX-VNNI Kernel B for Q1_0_g128 4×4 repack (SDE-validated, no real-hardware perf) - #17

Merged
kennguy3n merged 1 commit into
prismfrom
devin/1777645610-prism-avxvnni-kernel-b
May 1, 2026
Merged

kennguy3n merged 1 commit into
prismfrom
devin/1777645610-prism-avxvnni-kernel-b

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Make sure to read the contributing guidelines before submitting a PR

Summary

Adds AVX-VNNI variants of the 4-row GEMV and true 4×4 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.

This is Phase 7 of the multi-phase Q1_0_g128 AVX2 repack project (Phase 1 = PR #10, Phase 2 = PR #11, Phase 3 = PR #12 + #13, Phase 5 = PR #14, drop-unpack experiment = PR #15). It lands the AVX-VNNI Kernel B that was drafted in the Phase 2 microbench (PR #11) and deferred at the end of the Phase 3 follow-up plan.

Inner-loop op-count change

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

const __m256i masked  = _mm256_blendv_epi8(zero, qy, mask_w[r_w]);
const __m256i partial = _mm256_madd_epi16(_mm256_maddubs_epi16(ones_b, masked), ones_w);
const __m256i row_dot = _mm256_sub_epi32(_mm256_slli_epi32(partial, 1), sum_qy_v);

(5 vector ops + 2 shared per qy load for sum_qy_v) into:

const __m256i signed_qy = _mm256_blendv_epi8(neg_qy, qy, mask_w[r_w]);
const __m256i int_acc   = _mm256_dpbusd_avx_epi32(zero, ones_b, signed_qy);

(2 vector ops + 1 shared per qy load for neg_qy).

Per-sub-block GEMM inner-loop op count: 22 → 9 ops (Kernel A → Kernel B). Register-pressure profile (16 __m256 accumulators) and memory-traffic profile (one block_q1_0_g128x4 load per y-group, one block_q8_0x4 unpack) are both unchanged.

Validation

Default Zen 3 / AVX2-only build (host)

No code change for AVX2-only callers: Kernel B is omitted at compile time when __AVXVNNI__ is undefined, and the dispatcher falls back to Kernel A from PR #13.

$ cd build && ctest -j4
100% tests passed, 0 tests failed out of 50

tests/test-q1-g128-repack-parity passes nr=1 / nr=4 / nr=8 with diff = 0.000000e+00 on every element — same as before this PR.

-DGGML_AVX_VNNI=ON build under Intel SDE 9.48 -adl

$ cmake -B build-vnni -G Ninja -DCMAKE_BUILD_TYPE=Release \
      -DGGML_NATIVE=OFF \
      -DGGML_AVX2=ON -DGGML_FMA=ON -DGGML_F16C=ON \
      -DGGML_BMI2=ON -DGGML_AVX_VNNI=ON \
      -DLLAMA_BUILD_TESTS=ON
$ cmake --build build-vnni -j --target test-q1-g128-repack-parity llama-cli
$ sde64 -adl -- ./build-vnni/bin/test-q1-g128-repack-parity
PASS: GEMV (nr=1) + GEMM (nr=4) + GEMM (nr=8) all match single-row vec_dot within FP16 tolerance

Full SDE output committed at docs/avx2-repack-design/parity-alderlake-sde-kernel-b.txt with the build/run recipe in the file header.

End-to-end smoke under SDE

$ sde64 -adl -- ./build-vnni/bin/llama-cli -m Bonsai-1.7B.gguf \
      -p "The capital of France is" -n 8 -t 4 --no-warmup
The capital of France is **Paris**
[ Prompt: 1.3 t/s | Generation: 1.2 t/s ]

Token output is correct under SDE. The 1.3 t/s is SDE's dynamic-translation overhead, not a real measurement (SDE translates vpdpbusd_avx into host-native sequences and timing under emulation is meaningless).

Disassembly cross-check

$ objdump -d build-vnni/bin/libggml-cpu.so | grep -c vpdpbusd
506

Both GEMV + GEMM 4-rows variants are inlined into the public dispatchers by GCC 11.4 at -O3. No vpdpbusd instructions in the default Zen 3 build (kernel correctly excluded at compile time).

What this change does NOT include

Files changed (this PR's net diff)

  • ggml/src/ggml-cpu/arch/x86/repack.cpp — Kernel B GEMV + GEMM 4-rows + dispatcher branch (+234 LOC, gated #if defined(__AVXVNNI__)).
  • docs/avx2-repack-design/06-avx-vnni-kernel-b.md — design + op-count table + bench recipe + projected uplift discussion (+204 LOC, new).
  • docs/avx2-repack-design/parity-alderlake-sde-kernel-b.txt — verbatim SDE Alder Lake parity output (+42 LOC, new).

Review & Testing Checklist for Human

  • Read docs/avx2-repack-design/06-avx-vnni-kernel-b.md. Sanity-check the inner-loop op-count diff (5 ops → 2 ops per (r_a, r_w) per sub-block).
  • Verify the default Zen 3 / AVX2-only build is unchanged: cmake --build build && ./build/bin/test-q1-g128-repack-parity should still produce diff = 0 on every element. objdump -d build/bin/libggml-cpu.so | grep -c vpdpbusd should be 0.
  • Optional: rebuild with -DGGML_AVX_VNNI=ON and re-run sde64 -adl -- ./build-vnni/bin/test-q1-g128-repack-parity to reproduce the parity output committed in this PR.
  • Optional: if you have an Alder Lake / Zen 4 host, run llama-bench -t 4 -p 256,512,1024 -n 32 -r 3 and commit the numbers as phase7-end-to-end-<host>.txt next to the Phase 3 numbers.

Notes

Link to Devin session: https://uney.devinenterprise.com/sessions/576d78b01f9b4389b375033162f7b7b4

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`.
@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 devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@kennguy3n
kennguy3n merged commit e9d8d7e into prism May 1, 2026
1 check passed
kennguy3n added a commit that referenced this pull request May 1, 2026
…ispatcher

* docs(avx2-repack-design): Phase 4 dispatcher closeout (no new code)

Phase 4 was originally scoped to add three pieces:

1. Full dispatcher (runtime ISA selection)
2. On-disk repack converter (--repack-q1-g128-avx2 flag in
   llama-quantize, new GGML_TYPE_Q1_0_G128_AVX2 enum)
3. Cross-arch fallback testing

Item (3) was completed in PR #14. This doc closes out (1) and (2).

(1) is already infrastructure-complete via GGML_CPU_ALL_VARIANTS=ON
+ GGML_BACKEND_DL=ON. The build produces per-ISA shared libraries
(ggml-cpu-haswell.so, ggml-cpu-alderlake.so, ggml-cpu-skylakex.so,
etc.) and the runtime backend loader picks the most capable variant
the host CPU supports. Verified via disassembly:

  $ objdump -d build-variants/bin/libggml-cpu-haswell.so   | grep -c vpdpbusd
  0
  $ objdump -d build-variants/bin/libggml-cpu-alderlake.so | grep -c vpdpbusd
  506

Kernel A from PR #13 lands in the haswell variant (no vpdpbusd);
Kernel B from PR #17 lands in the alderlake variant (506 vpdpbusd
inlined into the public dispatchers). The runtime gate at
repack.cpp:3702 (avx2 && !avx512) routes correctly per variant.

(2) is intentionally not implemented. The equivalent upstream
pattern (GGML_TYPE_Q4_0_4_4 / _4_8 / _8_8) was deprecated and
removed upstream because: (a) on-disk size is identical to the
unrepacked format -- no storage win; (b) the runtime repack is
~1 ms per tensor, negligible vs model load; (c) each enum entry
forces every backend (cuda, metal, vk, ...) to add a code path for
a CPU-only optimisation. Adopting the deprecated pattern on prism
purely to mirror the original Phase 4 plan would re-introduce the
costs upstream already paid down. Decision #1 of 03-design-brief.md
("no on-disk GGUF format changes") was right at the start.

This closes the AVX2 repack project. Status table at the bottom of
the doc summarises all 7+1 phases / PRs.

Note: a full GGML_CPU_ALL_VARIANTS build hits a pre-existing GCC
11.4 break in arch/x86/quants.c:682 (_mm512_setr_epi8 not declared
in <immintrin.h> for GCC < 12) that blocks the AVX-512 variants
(skylakex / cascadelake / icelake / cannonlake / cooperlake /
zen4 / sapphirerapids). This is unrelated to the repack work and
already tracked in 04-cross-arch-validation.md. The verification
above was constrained to the haswell + alderlake variants (where
my work lands); the rest of the dispatcher infrastructure is
upstream code that has been in production for years.

* docs(avx2-repack-design): fix variant→kernel mapping in recommended-build paragraph

Devin Review caught that lines 201-205 of 07-phase4-dispatcher.md
contradicted the routing table earlier in the same doc (lines 58-65
and 86-94). Skylake-X has AVX-512, so it routes to the existing
single-row AVX-512 vec_dot, not Kernel A. Zen 4 also has AVX-512,
so it also routes to vec_dot, not Kernel B.

Corrected mapping:
  Kernel A → Zen 3 (haswell variant)
  Kernel B → Alder Lake (alderlake variant)
  vec_dot  → Skylake-X / Cascade Lake / Zen 4 / Sapphire Rapids

This matches the per-variant routing table at the top of the doc
and the analysis at lines 86-94.

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
kennguy3n added a commit that referenced this pull request May 2, 2026
…vx512-vnni-kernel-c

Adds an experimental AVX-512BW+VL+VNNI variant of the Q1_0_g128 4x4
GEMV ("Kernel C") to the microbench harness. Same outer skeleton
as Kernel B (in PR #17), but exploits two AVX-512BW+VL features
that AVX-VNNI alone does not have:

  1. _mm256_mask_blend_epi8 consumes the 32-bit qbits register
     directly as a __mmask32, eliminating the 5-instruction
     shuffle/and/cmpeq sequence Kernel B needs to expand qbits to a
     byte mask. Net: -4 ops per (sub-block, weight-row) for the mask
     path.

  2. EVEX-encoded _mm256_dpbusd_epi32 replaces Kernel B's VEX
     _mm256_dpbusd_avx_epi32. Same throughput where both are
     available; the new path is reachable on Cascade Lake / Ice Lake
     which have AVX-512+VNNI but not AVX-VNNI.

Validated under Intel SDE 9.48 in two configs:

  * SDE -icx (Ice Lake): five-way parity. Scalar reference =
    single-row AVX2 vec_dot = single-row AVX-512 VNNI vec_dot =
    Kernel A 4x4 GEMV = Kernel C 4x4 GEMV (bit-identical FP32 across
    all four AVX paths).
  * SDE -clx (Cascade Lake): same parity. Cascade Lake is the
    canonical host class for Kernel C since it has AVX-512+VNNI but
    not AVX-VNNI -- Kernel B cannot compile here.

Structural-perf measurement via SDE -mix (dynamic instruction count
for the same workload, both functions process bit-identical input):

  vec_dot_avx512_vnni (single-row, in-tree floor)  4587920 ICOUNT
  gemv_4x4_avx512_vnni (Kernel C, microbench)      3768655 ICOUNT
  -> Kernel C executes 17.9% fewer instructions for the same work,
     a 1.22x structural speedup over the existing AVX-512 path.

  gemv_4x4_avx2 (Kernel A, in-tree on AVX2 hosts)  5898600 ICOUNT
  -> Kernel C is 1.56x ahead of the AVX2 4x4 GEMM that PR #13 ships,
     entirely from the two AVX-512 features above.

Decision: doc-only landing. The dispatcher gate at
ggml/src/ggml-cpu/repack.cpp:3702-3712 is intentionally unchanged.
Reasons documented in 08-avx512-vnni-kernel-c.md:

  * Decode-side memory amplification: once the tensor is repacked to
    block_q1_0_g128x4, single-row vec_dot is no longer reachable, so
    decode (nrows=1) reads 4x more weight bytes than necessary. For
    Bonsai-1.7B-Q1_0_g128 the decode rate is the user-perceived
    latency, so a regression there would be felt directly.
  * No real-hardware data: the demo VM is Zen 3 / AVX2-only. Wiring
    the dispatcher to enable repack on AVX-512 hosts should be backed
    by a prefill-vs-decode wall-clock comparison on at least one of
    {Cascade Lake, Ice Lake, Sapphire Rapids, Zen 4}.

The kernel and its evidence are preserved here so whoever next has
hardware access can pick up the experiment without re-deriving the
design. The doc spells out the workflow for them.

AI authorship disclosed (per AGENTS.md / CONTRIBUTING.md private-fork
preamble, PR #16). Authorised by @kennguy3n in this session.

Co-authored-by: ken <kennguy3n@gmail.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