metal : add 64x8 mul_mm tile variant for small batches (q4_0) - #25377
metal : add 64x8 mul_mm tile variant for small batches (q4_0)#25377discobot wants to merge 1 commit into
Conversation
|
Hi @discobot, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
The mat-mv kernels are compute-bound and scale ~linearly with the batch size, while the 64x32 mul_mm tile zero-pads 75% of the batch dimension at bs = 8. Route q4_0 x f32 to a 64x8 tile for bs 5..16: 2x at bs = 8 on M4 Pro (test-backend-ops perf 4096x14336), pp8 -19% end to end on gemma-4-26B-A4B q4_0. bs = 1 decode unaffected. Ref: ggml-org#25250 Assisted-by: Claude Fable 5
|
updated to match the template, rebased on top of fresh main |
|
this enables more efficient speculative decoding on metal |
|
@ggerganov friendly ping |
|
Thanks a lot for tackling this :) the small-batch waste in the 5–16 range is a real pain point and a thin tile is a clean way at it. My main hesitation is maintainability, each new tile ends up being a near-copy of mul_mm gated by another hardcoded predicate like ne1<=16, and those predicates don't really generalize — e.g. I tried the same thin tile on f16 + long K and it was slower than 64×32 at small n, so the q4_0-tuned cutoff wouldn't carry over. So I've been poking at a PoC that makes the tile a template parameter picked from a small shape-indexed table, instead of a new kernel per shape — the optimal tile turns out to be continuously tunable. It's basically the same approach as #25750 , just applied to mul_mm. |
|
Love your approach, and agree that maintaining this zoo would be hard. |
|
@forforever73 End to end, gemma-4-26B-A4B q4_0: pp8 44.7 ms this PR vs 56.7 tile-poc. so you win on f16, I win on q4_0 small bs, the poc tunes the tiles but keeps the old mv_ext -> mm. so best possible solution seems to be your table + per-dtype switch point + thin q4_0 rows |
|
In the parameterized version, those three naturally fold into one. The thin q4_0 tile is simply one row in the table, and the switch point (ne11_mm_min) is another per-dtype parameter (like use_nr8), so both become part of the same shape-indexed lookup rather than separate knobs. PoC only tuned the tile while leaving the switch unchanged, which is why q4_0 at bs 5–8 never reaches the tuned tiles. I've already started working on this and hope to have a draft PR up in a few days. Happy to iterate on it together if you're interested. |
|
thank you, let me know if I can be of any help |
|
I'm just noticing this PR. Will take a look. |
Overview
mat-mv kernels are compute-bound and scale ~linearly with the batch size, while the 64x32 mul_mm tile zero-pads 75% of the batch dimension at bs = 8.
Route q4_0 x f32 to a 64x8 tile for bs 5..16:
bs = 1 decode unaffected
Performance (M4 Pro 64GB)
test-backend-ops perf, MUL_MAT q4_0 x f32, 4096x14336:
llama-bench, gemma-4-26B-A4B q4_0: pp5 -8%, pp8 -19%, pp13 -8%, tg unchanged.
Correctness
test-backend-ops -o MUL_MAT -b MTL0: 1134/1134 vs CPU (includes q4_0 at bs 2..16 routed through the new kernel).
Additional information
Closes #25250
this problem encountered in #24768
Only tested on M4 Pro.
Requirements