ggml-cpu: dequantize each weight row once per matmul tile (4-column Q4_0/Q4_K AVX2 kernels) - #41
Open
codspeed-hq[bot] wants to merge 1 commit into
Conversation
ggml_compute_forward_mul_mat() computes one output element per vec_dot call, so the whole src0 side of the work is redone for every column of a matmul tile even though it only depends on the row: nibble extraction, the -8 offset, the absolute values needed by the unsigned multiply, the 6-bit Q4_K scale/min unpacking and the FP16 block-scale conversions. Add AVX2 kernels computing one src0 row against 4 src1 columns in a single pass (ggml_vec_dot_q4_0_q8_0_4cols, ggml_vec_dot_q4_K_q8_K_4cols) and a fast path in ggml_compute_forward_mul_mat_one_chunk() that walks the tile 4 columns at a time. Leftover columns, batched matmuls, src0 broadcast and every other type keep the existing path. The per-column arithmetic and the order of the accumulation are unchanged, so results are bit-identical: verified over 6480 mul_mat configurations (q4_0/q4_K/f32/q8_0/q6_K, K 256..2560, M 1..512 incl. odd, N 1..64, 1/3/8 threads) with no differing output bits, and clean under valgrind memcheck.
Author
Merging this PR will improve performance by 62.77%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Simulation | mul_mat[q4_0] |
102 ms | 59.6 ms | +71.03% |
| ⚡ | Simulation | mul_mat[q4_k] |
72.4 ms | 46.7 ms | +54.91% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing codspeed-optim-dequantize-each-weight-row-once-per-matmul-tile-4-1785338847703 (7baa65d) with master (46819c9)
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.
What
ggml_compute_forward_mul_mat()computes one output element pervec_dotcall. For quantized weights this means the wholesrc0side of the work — nibble extraction, the-8offset, the|x|needed by the unsigned multiply, the 6-bit Q4_K scale/min unpacking and the FP16 block-scale conversions — is redone for every column of the tile, even though it only depends on the row.Flamegraph analysis of
mul_mat[q4_0]confirmed it:ggml_lookup_fp16_to_fp3212.6%, the nibble/offset/mask intrinsics ~13%,_mm_loadu_si12812%. Formul_mat[q4_k],srli+shuffle+and+memcpy+set_epi32account for ~37%. All of it row-only work, repeated once per column.This PR adds 4-column kernels (
ggml_vec_dot_q4_0_q8_0_4cols,ggml_vec_dot_q4_K_q8_K_4cols, AVX2) that unpack the row once and reuse it for 4src1columns, plus a fast path inggml_compute_forward_mul_mat_one_chunk()that walks the tile 4 columns at a time. Leftover columns and every other case keep the existing path.Why it is safe
ne12 == ne13 == 1, nosrc0broadcast) withnrows == 1; batched attention matmuls, GEMV decode steps (1 column) and all other types fall back unchanged. Thetmpstaging that limits false sharing between threads is preserved.Verification performed on this PR
ggml_mul_matover 6,480 configurations — typesq4_0,q4_K,f32,q8_0,q6_K× K ∈ {256, 512, 1024, 2560} × M ∈ {1, 3, 7, 16, 17, 33, 64, 129, 512} × N ∈ {1, 2, 3, 4, 5, 7, 8, 15, 16, 32, 33, 64} × {1, 3, 8} threads — and hashes the exact bit pattern of every output element. The hashes are identical between the base build and this build for all 6,480 cases (diffis empty).mul_mat[q4_0]/mul_mat[q4_k].N ∈ {1, 4, 5, 9, 33}to cover the 4-column remainder) runs with 0 errors from 0 contexts.test-quantize-fns,test-optandtest-barrierpass.test-backend-ops -o MUL_MATreportsOKbut skips the CPU backend (it is the reference), so it provides no additional coverage for a CPU-only change — hence the bit-identity harness above.Measured impact — CodSpeed CPU simulation
mul_mat[q4_0]mul_mat[q4_k]The other 13 benchmarks of the suite are unchanged (e.g.
mul_mat[f32]346.97 ms → 347.00 ms), no regressions.Notes
ggml_get_vec_dot_4cols()returnsNULLand the code path is unchanged; the NEON equivalents are the natural follow-up.perftooling required by walltime mode is not installable there). The walltime macro job on this PR is the ground-truth validation for that mode; note that if the macro runners are aarch64 they will not exercise these AVX2 kernels.