ggml-cpu: AVX-512-VNNI dot-products for Q1_0/Q2_0 - #37
Conversation
Q1_0/Q2_0 had no x86 vec_dot path (arch-fallback routed the generic functions to a scalar loop). Add an AVX-512-VNNI/AVX-512VL fast path guarded by __AVX512VNNI__ && __AVX512VL__, scalar fallback otherwise: - helper ggml_hsum_i32_8_vnni to reduce _mm256_dpbusd_epi32 accumulators - Q1_0: build a sign mask from the bit field, blend +qy/-qy, accumulate with dpbusd(ones, sel) - Q2_0: vectorized 2-bit unpack (replicate-4 + 16-bit shift/mask + pack), then dpbusd(codes, qy) - dpbusd(ones, qy) = sum((code-1)*qy) Q2_0 prefill ~3.9x / decode ~3.0x vs scalar on EPYC 9655; Q1_0 ~parity (the +/-1 scalar loop already auto-vectorizes). Bit-exact vs scalar (test-quantize-fns + standalone unit test); KL-divergence vs FP16 unchanged between scalar and VNNI builds.
|
@khosravipasha Why are the avx-512 specific changes being put into the generic quants file instead of x86 specific one? Also it seems that q1 part might be not called as x86 dot routes to generic only if neither AVX2/AVX/SSSE3 are available, am I right? |
|
@pl752 I think you might be right that wrong file is being modified, did not realize in the initial pass reviewing. For the second part will need to double check. Probably need to undo the commit and redo the PR. |
|
In order to add x86 specific implementation it is needed to remove alias in x86 section of arch_fallback.h for q2 and define function with impls and call to generic fallback |
|
Also I will benchmark vnni for zen 4 as I tried to use it, but encountered lower speed than xor-sub (though it's likely that I have used it wrong), also for 256 bit variants there is |
|
Thanks @pl752 — good catch, you're right about the q1_0 path. q1_0: correct. On x86, q2_0 is the reason the change is in the generic file — and there it's on the live path. q2_0 has no x86-specific Summary: the q2_0 placement is intentional and on the hot path; the q1_0 VNNI hunk is dead on AVX2 x86 and isn't what's producing the parity number. I'll put up a follow-up to remove the q1_0 block (and can add a q1_0 VNNI tier in |
Q1_0/Q2_0 had no x86 vec_dot path (arch-fallback routed the generic functions to a scalar loop). Add an AVX-512-VNNI/AVX-512VL fast path guarded by __AVX512VNNI__ && __AVX512VL__, scalar fallback otherwise: - helper ggml_hsum_i32_8_vnni to reduce _mm256_dpbusd_epi32 accumulators - Q1_0: build a sign mask from the bit field, blend +qy/-qy, accumulate with dpbusd(ones, sel) - Q2_0: vectorized 2-bit unpack (replicate-4 + 16-bit shift/mask + pack), then dpbusd(codes, qy) - dpbusd(ones, qy) = sum((code-1)*qy) Q2_0 prefill ~3.9x / decode ~3.0x vs scalar on EPYC 9655; Q1_0 ~parity (the +/-1 scalar loop already auto-vectorizes). Bit-exact vs scalar (test-quantize-fns + standalone unit test); KL-divergence vs FP16 unchanged between scalar and VNNI builds. Co-authored-by: Brian <brian@Brians-MacBook-Pro.local>
Port the fork's Q2_0 AVX-512-VNNI dot-product to the group-128 type (142). Wired ONLY to Q2_0_g128 (fork format); upstream's Q2_0 g64 stays scalar (a g64 VNNI dot would be an upstream contribution — tracked separately). Generic scalar kept as the fallback via arch-fallback. Verified: test-quantize-fns q2_0_g128 PASS, CPU MUL_MAT q2_0_g128 45 OK / 0 FAIL (VNNI == reference). Original fork work by Brian / THT / bri-prism (#37, AVX-VNNI fast path). Co-authored-by: THT <tht@prismml.com> Co-authored-by: bri-prism <bri-prism@users.noreply.github.com>
What
Adds an AVX-512-VNNI / AVX-512VL fast path for the
Q1_0andQ2_0CPU dot products. Both formats previously had no x86vec_dotpath —arch-fallbackrouted the_genericfunctions to a scalar loop. The scalar path is preserved as the#elsefallback (guarded by__AVX512VNNI__ && __AVX512VL__).Why
The low-bit CPU dot product is the hot kernel for Bonsai inference on CPU backends.
Q2_0in particular was leaving a large amount of x86 throughput on the table.How
ggml_hsum_i32_8_vnnito reduce_mm256_dpbusd_epi32accumulators+qy/-qy, accumulate withdpbusd(ones, sel)dpbusd(codes, qy) - dpbusd(ones, qy)=sum((code-1)*qy)Performance (EPYC 9655, AVX-512-VNNI)
Scales: holds 3.4–4× through
-d 2048; scalar Q2_0 times out at-d 8192.Correctness
test-quantize-fnsdot-product error identical with/without the path.packed_models_KL_validationflow): Q2_0 mean KLD 0.000135, top-1 99.28%, PPL +0.14% — passes thresholds. Scalar and VNNI builds produce byte-identical KLD/top-1, confirming the kernel preserves outputs end-to-end.prismtree (above numbers reproduced here).Notes
This is the public-fork counterpart of the approved internal PR (
llama.cpp-private#1), landed onprismper review. The one red CI signal —test-quantize-fnsonq2_0— is pre-existing onprism(the{−1,0,1,2}2-bit format exceeds the generic 2-bit threshold); it reports identical numbers with and without this change.