Repository navigation
Stage GGUF MoE experts on Apple9 GPUs of 10 cores or fewer - #578
Merged
Merged
Conversation
Apple9 ran its GGUF MoE experts on the register tile in decode and
prefill, but for experts mostly in the grid-lookup formats
(apple9StagesFormat), because a 40-core M3 Max decoded Q4_K/Q5_K, Q2_K
and IQ4_XS experts 3-34% faster there than staged. That staged tile ran
gate and up as two passes and padded an expert's lone tile of one or two
rows to an 8-row matmul. It now runs gate and up in one pass and such a
tile on the column tile, and on a 10-core M4 nearly every case measured
runs faster staged. So Apple9 GPUs of 10 cores or fewer, the base M3 and
M4, now stage experts in every format, in both phases; of the two, only
the M4 was measured. Larger Apple9 GPUs keep the register tile, and
Apple10 is unchanged.
One MoE layer on the M4 (gguf-moe-benchmark), register -> staged ms,
rows routed at random or each lane's rows routed among few experts
(pooled), in gate/up, down and shared expert formats; medians of four
runs, main's and this change's in ABBA order, which time the two tiles
in opposite orders:
B1 random B1 pooled B4 random B4 pooled 2048-row chunk
35B Q4_K/Q5_K/Q8_0 1.406 1.163 0.599 0.555 3.943 3.167 1.973 1.786 46.20 39.02
35B Q5_K/Q5_K/Q8_0 1.548 1.300 0.671 0.611 4.381 3.571 2.181 1.958 49.34 39.41
FN Q4_K/IQ4_NL/Q8_0 2.610 2.138 1.063 1.024 8.329 6.550 3.706 3.471 88.00 78.25
FN IQ4_XS/IQ4_NL/Q8_0 2.479 2.061 1.030 1.026 7.940 6.327 3.549 3.474 87.59 77.43
(FN: Qwen3.8-Flash-Next.)
At the 35B's shape in each of the 25 formats the register tile takes
(the K-quants, Q8_0, Q4_0, Q4_1, Q5_0, Q5_1, IQ4_XS, IQ4_NL, IQ3_S,
MXFP4, NVFP4, FP8, Q2_0, PQ2_0, BF16, and MLX 2-, 3-, 4- (in groups of
32 and 64), 6- and 8-bit), staged takes 12-45% less time at B1-B4 with
random routes, up to 17% less with pooled ones and 7-28% less on a
2048-row chunk. It takes longer only with pooled routes in IQ4_NL at B1
and B2 (1.3%, 0.1%) and on some 128- to 512-row chunks in IQ4_NL,
IQ4_XS, MXFP4 and MLX 2- and 4-bit (at most 3.8%). With the 122B's
shape (hidden 3072, top 8 of 256 experts, intermediate 1024) edited into
the benchmark, its Q4_K/Q5_K/Q8_0 and MLX 4-bit layers take 17-21% less
time staged with random routes, 4-10% less with pooled ones and 12-15%
less on a 2048-row chunk. Flash-Next's 640-input down projection takes
the register tile only in formats of one group per meta unit
(quant_register_tensor), so its K-quant and Q2_0 checkpoints were
already staged.
Prefill keeps following decode, as the register tile's prefill did to
keep the decode numerics: a GPU that decodes its experts staged
prefills them staged, on 32-row tiles past one route per expert as
Apple10 does.
The M3 Max's measurement predates the one gate/up pass and the column
tile. Built at its commit (1bf892b), the M4 too decoded Q4_K/Q5_K, Q2_K
and IQ4_XS experts faster on the register tile at B1-B4, staged taking
2-15% longer (that benchmark routed pooled rows only). The ranking
turned with the staged tile, so larger Apple9 GPUs may now stage faster
too, but none was measured, and they keep the register tile.
On Apple9 GPUs that report 10 cores or fewer, the MoE plans of GGUF and
MLX checkpoints (which run the same expert tiles) now run
moe_expert_gguf_m8/m32 with the column tile instead of
moe_expert_gguf_sg_a/_g over Table16 tiles, without moe_prepare_table16,
moe_gather_table16 or the grouped sums. Their outputs change by the two
tiles' rounding (before ed63ffb gave the staged tile its column tile,
the 35B's teacher-forced logits of the two tiles agreed at top-1 95.5%),
and their decode results depend on the rows decoded beside them through
the column tile, as on Apple10. The MoE prefill scratch takes the staged
32-row tiles' bound: 39 MiB more on the 35B, 111 MiB on Flash-Next and
62 MiB on the 122B, while four decode lanes take 1.5-2.4 MiB less. No
kernel changes. Apple10, Apple9 GPUs of more than 10 cores and GPUs that
report no core count (planned as kAssumedGpuCores, 32) plan as before.
Tests: on the 32-core M5 Max (Apple10), make check-native-build,
check-source and test-engine-cpu (execution-plans among it), and
gguf-moe, gguf-moe-e512 and expert-store under MTL_SHADER_VALIDATION=1.
On the 10-core M4 (Apple9), execution-plans, the same three Metal tests
under shader validation, and two gguf-moe-benchmark runs at the 35B's
shape: the M4 now plans that layer staged, which takes 16-18% less time
than the register tile at B1 and B4 with random routes, 8-10% less with
pooled ones and 15% less on a 2048-row chunk, within 2% of the table.
jianc99
force-pushed
the
apple9-moe-staged
branch
2 times, most recently
from
October 25, 2026 07:39
8a591ea to
4f738be
Compare
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.
On Apple9 GPUs that report 10 cores or fewer (the base M3 and M4), GGUF and MLX MoE checkpoints now run their experts on the staged tile in decode and prefill, instead of the register tile.
moeGgufTiletakes the planned core count for this, andExecutionPlanspasses it. Apple10, Apple9 GPUs of more than 10 cores, and GPUs that report no core count (planned askAssumedGpuCores, 32) plan as before. No kernel changes.Apple9 took the register tile for its experts because a 40-core M3 Max decoded Q4_K/Q5_K, Q2_K and IQ4_XS experts 3-34% faster there than staged. The staged tile has since gained a one-pass gate/up and the column tile for an expert's lone tile of one or two rows. On a 10-core Mac mini M4 it is now faster in nearly every case measured. Built at 1bf892b, the commit of the M3 Max's measurement, the same M4 ranked the tiles as the M3 Max did (staged taking 2-15% longer at B1-B4), so the ranking turned with the staged tile.
Measurements
One MoE layer on the Mac mini M4 (Apple9, 10 GPU cores, 16 GB),
gguf-moe-benchmark, register → staged ms. Rows are routed at random, or each lane's rows among few experts (pooled). Medians of four runs (eight for the 122B's MLX 4-bit), main's build and this change's in ABBA order, which time the two tiles in opposite orders.quant_register_tensor).gguf-moe-benchmark 35bon the M4 confirm that it now plans the 35B's layer staged. Staged takes 16-18% less time than the register tile at B1 and B4 with random routes, 8-10% less with pooled routes, and 15% less on a 2048-row chunk (1.169 vs 1.384 ms at B1 random, 39.03 vs 46.14 ms on the chunk), within 2% of the table's row.End to end (an estimate). No MoE checkpoint fits the M4's 16 GB, so no model ran. A one-lane decode cycle of the 35B UD-Q4_K_M runs 40 of these layers, which would take 1.8 ms (pooled) to 9.7 ms (random) less, out of 24 and 56 ms. The rest of the cycle mostly reads weights: 1.2-1.4 GB of the 35B's other tensors and LM head (1.2 GB in its UD-Q2_K_XL GGUF), the LM head again for the draft, and the 1 GB DFlash2 draft. At the 90-100 GB/s these layers reach, that is 26-32 ms. So a one-lane cycle on a base M3 or M4 with the memory for the 35B would take roughly 3-12% less, depending on how widely its verify rows spread over the experts. At B4 the layers would take 7.5-31 ms less per cycle.
Costs
Related PRs
moe_expert_gguf_sg_aand_gdiffer from main's AIR only in the names of lambda types. The M4 times the register expert tile as main does (Q4_0, Q6_K and Q4_K/Q5_K within 1%), and staged still takes 6-22% less time. Either PR can land first.runtime/ops/MoE.hpp,runtime/ops/ExecutionPlans.cpp/.hppandDEVELOPMENT.md. This branch merges into Compile the attention, MoE and stop-token variants Flint runs #573's and Serve Flint (RecursiveSI/flint) from its GGUFs #576's heads without conflicts, and the mergedExecutionPlans.cppandexecution_plans_test.cccompile. Whichever lands second should still rebase and re-run the MoE tests. With both, Flint's experts on a base M3 or M4 run staged as well.Tests
make check-native-build,check-sourceandtest-engine-cpu(execution-plans among it) pass, andgguf-moe,gguf-moe-e512andexpert-storepass underMTL_SHADER_VALIDATION=1.execution-planspasses, andgguf-moe,gguf-moe-e512andexpert-storepass under shader validation. The twogguf-moe-benchmark 35bruns above also ran there.execution-planscovers Apple9 at 10 and 11 reported cores and with no core count (the assumed 32), and Apple10 and Apple11.gguf-moe-benchmarkgave the old ranking.Not covered:
Need help on this PR? Tag
@codesmith-botwith what you need.