Repository navigation
metal : skip the empty half of the mul_mm_id token tile, load iq2/iq3 codebooks as uint32 - #28301
Conversation
|
Hi @masterFoad, 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. |
Let's split the loading changes in a separate PR. |
|
Looks good to me overall. I think we're in good shape once the loading changes are split out. |
1f20843 to
bd351fc
Compare
| // restage every row unconditionally, as upstream does (out-of-range rows read | ||
| // clamped-safe duplicate addresses and are discarded by the final store loop) | ||
| { | ||
| if (FC_mul_mm_bc_inp) { | ||
| for (short i = 0; i < 8; ++i) { | ||
| const short sx = (tiitg%NL1); | ||
| const short sy = (tiitg/NL1)/8; | ||
|
|
||
| const short lx = i; | ||
| const short ly = (tiitg/NL1)%8; | ||
| //const short lx = (tiitg/NL1)%8; | ||
| //const short ly = i; | ||
|
|
||
| *(sb + NK*(8*sy + ly) + 8*sx + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0; | ||
| } | ||
| } else { | ||
| const short sx = (tiitg%NL1); | ||
| const short sy = (tiitg/NL1)/8; | ||
|
|
||
| const short lx = i; | ||
| //const short lx = i; | ||
| const short ly = (tiitg/NL1)%8; | ||
| //const short lx = (tiitg/NL1)%8; | ||
| //const short ly = i; | ||
|
|
||
| *(sb + NK*(8*sy + ly) + 8*sx + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0; | ||
| *(threadgroup S1_2x4 *)(sb + NK*(8*sy + ly) + 8*sx) = (S1_2x4)(*((device T1_2x4 *) y)); | ||
| } | ||
| } else { | ||
| const short sx = (tiitg%NL1); | ||
| const short sy = (tiitg/NL1)/8; | ||
|
|
||
| //const short lx = i; | ||
| const short ly = (tiitg/NL1)%8; | ||
| //const short lx = (tiitg/NL1)%8; | ||
| //const short ly = i; | ||
|
|
||
| *(threadgroup S1_2x4 *)(sb + NK*(8*sy + ly) + 8*sx) = (S1_2x4)(*((device T1_2x4 *) y)); | ||
| } |
There was a problem hiding this comment.
This comment + indentation is not really needed - let's keep the code block as it is on master
ggerganov
left a comment
There was a problem hiding this comment.
After addressing the comment, we can merge
kernel_mul_mm_id splits its NR1 = 32 token tile into two 16-row halves and skips the upper half when the expert did not fill it, on both the tensor and simdgroup paths. The tB extents are corrected to (NK, NR1H) for the [NR1][NK] row-major tile. The B tile is staged unconditionally, as on master: rows past nr1 restage a clamped duplicate of a valid row, lie in the output-row dimension so they never contribute to a valid row, and are dropped by the final store loop. test-backend-ops: re-draw the expert ids between perf iterations of test_mul_mat_id so MoE perf numbers are not warm-cache, and add token-tile boundary coverage using n_used == n_mats, which routes every token to every expert so each expert receives exactly n rows; n = 32, 33, 47, 48, 49 reach mul_mm_id and leave a last tile of 32, 1, 15, 16 and 17 rows.
bd351fc to
e80a322
Compare
|
@ggerganov Done, Thanks! |
…-org#28301)" This reverts commit 5bda51b.
kernel_mul_mm_id splits its NR1 = 32 token tile into two 16-row halves and skips the upper half when the expert did not fill it, on both the tensor and simdgroup paths. The tB extents are corrected to (NK, NR1H) for the [NR1][NK] row-major tile. The B tile is staged unconditionally, as on master: rows past nr1 restage a clamped duplicate of a valid row, lie in the output-row dimension so they never contribute to a valid row, and are dropped by the final store loop. test-backend-ops: re-draw the expert ids between perf iterations of test_mul_mat_id so MoE perf numbers are not warm-cache, and add token-tile boundary coverage using n_used == n_mats, which routes every token to every expert so each expert receives exactly n rows; n = 32, 33, 47, 48, 49 reach mul_mm_id and leave a last tile of 32, 1, 15, 16 and 17 rows.
kernel_mul_mm_id splits its NR1 = 32 token tile into two 16-row halves and skips the upper half when the expert did not fill it, on both the tensor and simdgroup paths. The tB extents are corrected to (NK, NR1H) for the [NR1][NK] row-major tile. The B tile is staged unconditionally, as on master: rows past nr1 restage a clamped duplicate of a valid row, lie in the output-row dimension so they never contribute to a valid row, and are dropped by the final store loop. test-backend-ops: re-draw the expert ids between perf iterations of test_mul_mat_id so MoE perf numbers are not warm-cache, and add token-tile boundary coverage using n_used == n_mats, which routes every token to every expert so each expert receives exactly n rows; n = 32, 33, 47, 48, 49 reach mul_mm_id and leave a last tile of 32, 1, 15, 16 and 17 rows.
kernel_mul_mm_id splits its NR1 = 32 token tile into two 16-row halves and skips the upper half when the expert did not fill it, on both the tensor and simdgroup paths. The tB extents are corrected to (NK, NR1H) for the [NR1][NK] row-major tile. The B tile is staged unconditionally, as on master: rows past nr1 restage a clamped duplicate of a valid row, lie in the output-row dimension so they never contribute to a valid row, and are dropped by the final store loop. test-backend-ops: re-draw the expert ids between perf iterations of test_mul_mat_id so MoE perf numbers are not warm-cache, and add token-tile boundary coverage using n_used == n_mats, which routes every token to every expert so each expert receives exactly n rows; n = 32, 33, 47, 48, 49 reach mul_mm_id and leave a last tile of 32, 1, 15, 16 and 17 rows.
kernel_mul_mm_id splits its NR1 = 32 token tile into two 16-row halves and skips the upper half when the expert did not fill it, on both the tensor and simdgroup paths. The tB extents are corrected to (NK, NR1H) for the [NR1][NK] row-major tile. The B tile is staged unconditionally, as on master: rows past nr1 restage a clamped duplicate of a valid row, lie in the output-row dimension so they never contribute to a valid row, and are dropped by the final store loop. test-backend-ops: re-draw the expert ids between perf iterations of test_mul_mat_id so MoE perf numbers are not warm-cache, and add token-tile boundary coverage using n_used == n_mats, which routes every token to every expert so each expert receives exactly n rows; n = 32, 33, 47, 48, 49 reach mul_mm_id and leave a last tile of 32, 1, 15, 16 and 17 rows.
Overview
kernel_mul_mm_idworks on 32 routed token rows at a time. An expert often receives fewer than 32 rows, but the unused rows are still multiplied and discarded.This splits the token tile into two
NR1H = 16halves and skips the upper half whennr1 <= NR1H.On the tensor-ops path, each half gets its own
matmul2dcall. On the simdgroup path, the two simdgroups responsible for rows 16..31 are disabled throughsg_activewhen the upper half is not needed.nr1is the number of rows remaining in the current tile. An expert with 33 routed rows therefore gets one full tile and a 1-row tail, and the tail skips its upper half.The
tBextents are also corrected for the split tile. They were(NR1, NK)for a[NR1][NK]row-major tile, which was harmless while both dimensions were 32.For reference, mean rows per expert are:
A 256-expert top-8 model therefore averages 8 rows per expert at
-ub 256and 16 at-ub 512.mul_mm_idis used from 32 tokens up. Below that, MoE usesmul_mv_id, so this kernel is not reached.The IQ codebook and padding-staging changes from the first revision have been removed. This PR now contains only the tile split, the
tBextent fix, and the related tests/benchmark hook.Performance
Apple M5, against master
5a4d0feca. Four paired runs per case. Negative is faster.Tensor ops
./build/bin/test-backend-ops perf -o MUL_MAT_ID -b MTL0 \ -p "type_a=(q4_0|q4_K|iq2_xs|iq2_s|iq3_xxs),.*,n=(64|128|256|512|1024|2048),"The largest regression is +1.2% for
q4_Katn = 2048. At smaller batch sizes, where experts receive fewer rows, the gain is larger.iq2_s,iq3_xxs, andn > 512are not in the upstream perf set. I added them locally for these measurements; the test change is not part of this PR.Measurement-only test-backend-ops change
@@ static std::vector<std::unique_ptr<test_case>> make_test_cases_perf() { // qwen3-30b-a3b - for (int bs : {1, 4, 8, 32, 64, 128, 256, 512}) { - for (ggml_type type_a : {GGML_TYPE_F32, GGML_TYPE_F16, GGML_TYPE_Q4_0, GGML_TYPE_Q8_0, GGML_TYPE_Q4_K, GGML_TYPE_Q6_K, GGML_TYPE_IQ2_XS}) { + for (int bs : {1, 4, 8, 32, 64, 128, 256, 512, 1024, 2048}) { + for (ggml_type type_a : {GGML_TYPE_F32, GGML_TYPE_F16, GGML_TYPE_Q4_0, GGML_TYPE_Q8_0, GGML_TYPE_Q4_K, GGML_TYPE_Q6_K, GGML_TYPE_IQ2_XS, GGML_TYPE_IQ2_S, GGML_TYPE_IQ3_XXS}) { for (ggml_type type_b : {GGML_TYPE_F32}) { test_cases.emplace_back(new test_mul_mat_id(type_a, type_b, 128, 8, false, 768, bs, 2048));The same change was made to the
32, 4, false, 1792loop below it.Simdgroup path
GGML_METAL_TENSOR_DISABLE=1 ./build/bin/test-backend-ops perf -o MUL_MAT_ID -b MTL0 \ -p "type_a=(q4_0|q4_K|iq2_xs),.*,n=(32|64|128|256|512),"Median over
q4_0,q4_K, andiq2_xs.All 30 case medians improve. 119 of 120 paired runs are faster.
The gain is larger on this path because all four simdgroups otherwise run
simdgroup_multiply_accumulateover the full tile regardless ofnr1.End to end
Tiel-Coder-35B-A3BIQ3_XXS, 256 experts top-8, Apple M5, against the same master.-ub 256-ub 512npl = 8npl = 32npl = 64Positive is faster.
Batch-1 decode is unchanged, as expected, because it uses
mul_mv_id.The batched cases start using
mul_mm_idat 32 tokens, which is also where the gain appears.Testing
On
5bda51bf:MUL_MAT_ID: 843/843 passed, 78 not supportedMUL_MAT: 1265/1265 passed, 417 not supportedBoth pass with tensor ops enabled and disabled.
This PR adds 33 test cases across
q4_K,iq2_xs, andf16.Thirty use
n_used == n_mats == 4, so every token routes to every expert and each expert receives exactlynrows.The new
mul_mm_idcases cover the split boundary and tail sizes directly:The 16- and 17-row cases cover the point where the upper half starts running again.
n = 1, 15, 16, 17, 31cover themul_mv_idpath. Three additional cases use eight experts with one selected to cover empty experts.test_mul_mat_idalso redraws expert IDs between timed perf iterations throughreinit_perf_iter, so perf runs do not repeatedly use the same routing assignment.Related
#25377, #27370 and #26223 touch nearby code. #26223 changes the same B-tile staging code, so whichever lands second will need a small merge.
Requirements