Optimize AVX2 ggml_vec_dot_q4_0_q8_0: two FMA accumulator chains - #31
Open
codspeed-hq[bot] wants to merge 1 commit into
Conversation
… chains Process two Q4_0 blocks per iteration into two independent __m256 accumulators (acc0/acc1) instead of one, keeping two _mm256_fmadd_ps chains in flight to hide FMA latency and halving loop-control overhead. A trailing scalar-style loop handles an odd final block, and the two partial sums are combined with a single _mm256_add_ps before the existing hsum_float_8 reduction. The per-block computation is unchanged; only the accumulator that receives each block's product changes. The result is mathematically equivalent (same float reassociation the kernel already performs across its SIMD lanes). Only the __AVX2__ branch is touched.
Author
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
Optimizes the AVX2 implementation of
ggml_vec_dot_q4_0_q8_0(ggml/src/ggml-cpu/arch/x86/quants.c), the per-row dot-product kernel used by every Q4_0 matrix multiplication.ggml_compute_forward_mul_matcalls it once per output element, so it dominates every Q4_0 linear/projection layer of transformer inference (prompt processing, decode and the LM head).Analysis
On the CodSpeed micro suite,
mul_mat[q4_0](~102 ms in CPU simulation) spends essentially all of its time in this kernel. The AVX2 inner loop processed one 32-element block per iteration and accumulated every block into a single__m256 accvia_mm256_fmadd_ps. That forms a serial dependency chain — each block's FMA must wait for the previous block's FMA to retire — so the loop is FMA-latency bound, and the per-iteration loop-control work is paid on every block.Change
Process two blocks per iteration into two independent accumulators (
acc0,acc1), each with its own scale and nibble/maddubschain, then combine them with a single_mm256_add_psbefore the existinghsum_float_8reduction. A trailing scalar-style loop handles an odd final block. The per-block computation (bytes_from_nibbles_32, the-8offset,mul_sum_i8_pairs_float, the FMA) is byte-for-byte the same as before — only the accumulator that receives it changes. This keeps two FMA chains in flight to hide latency and halves the loop-control overhead. The change is confined to the#if defined(__AVX2__)branch; the AVX/SSSE3/scalar paths are untouched.Correctness
The result is mathematically equivalent: the two accumulators sum the same per-block products, just partitioned across two lanes, and are added before the horizontal reduce — the same class of float-reassociation the kernel already performs across its 8 SIMD lanes.
Performance (CodSpeed, CPU simulation)
Measured locally through the CodSpeed CLI on the
mul_matmicro benchmarks:mul_mat[q4_0]mul_mat[q4_0]mul_mat[q4_0]mul_mat[q4_0]The cache and memory components are byte-for-byte identical — the win is a pure, deterministic instruction-count reduction.
mul_mat[f32]andmul_mat[q4_k]are unchanged (no regressions). The kernel also feeds the walltime macro benchmarks' Q4_0 layers (prompt_layer[q4_0],decode_layer[q4_0]), which exercise the same hot path.