perf(speculative): gate B=1 MTP on GPU generation, not Neural Accelerator (#1217) - #1254
Merged
Conversation
…ator The static per-hardware gate for the singleton MTP burst, `mtp_b1_default`, ran batch-capable targets only where `has_neural_accelerator` held, which is M5 and nothing else. That rested on two numbers from the founding measurement (#165): about 1.2 to 1.4x on M5 Max against a 0.75 to 0.96x regression on M1 Ultra. Both predate #1194, #1199, #1203, #1208 and #1215, and M3 Ultra had never been run on the pairing at all, so the binary Neural Accelerator proxy lumped a generation-15 part in with generation 13. Re-measured on M3 Ultra, 2026-08-20, on current main, under the #1215 protocol. The batch-capable Gemma 4 31B target with its bf16 assistant measures 2.65x on enumeration, 2.41x on source code and 1.95x on prose, every row inside a 1.0% spread, on a host the gate declined. It beats the M5 Max figure the gate was built to enable. Single-stream acceptance is 0.66 to 1.00, not the too-low value the old comment asserted. The gate now reads `AppleSiliconGen::wide_quantized_projections`, the `use_qmv_wide` split at Apple GPU generation 15: an affine-quantized projection at `M >= 2` runs as one wide pass from M3 up, and as K narrow passes on M1 and M2. That is the mechanism the round-cost model already attributed the host ordering to, and it is finer than the M5-only proxy it replaces. Generation 13 keeps declining, and the width sweep is why that is a conclusion rather than an omission. Round cost fits `0.83 + 0.170 K` classic steps for this pairing on M3 Ultra against `1.14 + 0.090 K` for the 12B pairing on the same host, so the bf16 drafter costs about 1.9x as much per extra block position and the two lines merely cross at K = 4. Carrying that slope ratio onto generation 13's `1.35 + 0.346 K` puts a block-4 round near 3.6 classic steps there, which the 2.96 to 3.99 tokens a round emits would only just cover, consistent with the founding regression. No M1 Ultra host was available to settle it, so the estimate is stated in falsifiable form and generation 13 is left alone. M4 is grouped with M3 by the shared dispatch and is labelled as inferred. The new predicate is strictly more permissive than the one it replaced, so no host loses a path it previously had; a unit test pins that property so the safety argument cannot rot. Verified through the real dispatch path, not only the pure seam: on this M3 Ultra with `MLXCEL_MTP_ADAPTIVE=0` and the real checkpoints, `main` declines the burst, this branch runs it (block 4, 80 tokens over 21 rounds, acceptance 0.921), and `MLXCEL_ENABLE_MTP_B1=0` still declines. Both bench scripts gained a `gemma31b` case. Neither could reach the pairing the gate governs before, which is most of why the founding numbers went stale unnoticed. Also recorded: the offline `generate` path never consults this gate (its only caller is `Scheduler::mtp_b1_should_run`), so the harness measures the burst unconditionally, which is what makes it the right instrument here. Refs #1217. The M1 Ultra rows and an M5 Max re-measurement that issue also asks for remain outstanding for want of the hardware.
Bilingual technical report for the gate change: why the Neural Accelerator proxy went stale, what M3 Ultra measured, why generation 13 keeps declining on the strength of the width sweep rather than by omission, and the two corrections made during the work (a round cost wrongly transferred between pairings, and a PR body whose own disclaimer matched GitHub's closing-keyword parser).
5 tasks
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 this does
Re-measures the batch-capable B=1 MTP pairing on M3 Ultra and moves
mtp_b1_defaultoff thehas_neural_acceleratorproxy onto the mechanism the round-cost model already attributed the host ordering to: theuse_qmv_widesplit at Apple GPU generation 15.Why the old gate was wrong
The gate ran batch-capable targets only where
has_neural_acceleratorheld, which is M5 and nothing else. That rested on two numbers from #165: about 1.2 to 1.4x on M5 Max against a 0.75 to 0.96x regression on M1 Ultra. Both predate #1194, #1199, #1203, #1208 and #1215, and M3 Ultra had never been run on this pairing at all, so a generation-15 part was grouped with generation 13 by a binary proxy.Neither bench script could reach the pairing the gate governs.
bench_speculative.shcovered the 12B and Qwen pairings andbench_block_width.shthe same two, so the founding numbers were not reproducible through the #1215 protocol. That is most of why they went stale without anyone noticing. Both scripts gain agemma31bcase here.Measured
Gemma 4 31B + bf16 assistant, M3 Ultra (512 GB), 2026-08-20, current main, block 4, greedy, under
with_indexers_paused.sh. Round cost isemitted per verify / speedup, in this host's own classic decode steps.Spreads were 0.5% to 1.0% on both arms of every row against the harness limit of 4%. Three prompts with nothing in common agree on the round cost to within 0.7%, and the pairing clears its break-even by roughly double on all of them. Single-stream acceptance is 0.66 to 1.00, not the too-low value the old code comment asserted.
Width sweep on the code row, 8 interleaved rounds: peak at width 5 with width 4 tied inside its 1.7% spread, everything from 3 to 8 gaining more than 2x. Fit
round cost = 0.83 + 0.170 Kclassic steps, largest residual 0.06.Why generation 13 still declines
This is the part the sweep changed my mind on, so it is worth stating plainly. The tempting move is to take M1 Ultra's published block-4 round cost of 2.71, note this pairing emits 2.96 to 3.99 tokens per verify, and conclude post-#1203 M1 Ultra would now clear break-even. That is not sound: 2.71 belongs to the 12B pairing with a 4-bit drafter, and the sweep shows this pairing's slope is 1.9x steeper (
0.83 + 0.170 Kagainst the 12B pairing's1.14 + 0.090 Kon the same host). The two lines merely cross at K = 4, which is the only reason their block-4 costs match.Carrying that slope ratio onto generation 13's
1.35 + 0.346 Kputs a block-4 round near 3.6 classic steps there, which the emitted tokens would only just cover, consistent with the founding 0.75 to 0.96x. So the generation-13 decline reads as sound rather than merely stale and it stays. The estimate extrapolates across a pairing and a generation at once, so it is a reason to leave M1/M2 alone, not a result; it is written down in falsifiable form so an M1 Ultra run can settle it.The new predicate is strictly more permissive than the one it replaced, so no host loses a path it previously had. A unit test pins that property so the safety argument cannot rot.
Verified through the real dispatch path
Unit tests cover
mtp_b1_defaultas a pure function, which is not the same as showing the running scheduler consults it. Same host, same checkpoints, server rather than the offline CLI,MLXCEL_MTP_ADAPTIVE=0so the static gate decides:MLXCEL_ENABLE_MTP_B1mainat9e2c66750Worth recording separately: the offline
mlxcel generatepath never consults this gate.mtp_b1_defaulthas exactly one caller,Scheduler::mtp_b1_should_run, andMtpPolicyis built only in the server worker, so the bench harness runs the burst unconditionally on every host. That is what makes it the right instrument for deciding the gate, and it is also whyMLXCEL_ENABLE_MTP_B1=1andMLXCEL_MTP_ADAPTIVE=0are inert there.Checks
cargo fmt --checkclean,cargo clippy --all-targetsclean, 361server::batchtests pass, plus the newhardwareandmtp_b1_defaultunit tests.Not done here
Issue #1217 deliberately stays open, because two of its acceptance criteria need hardware this host is not. No closing keyword is used anywhere in this PR or its commit.
docs/benchmarks.md(1.67x at block 3, full width sweep). Not re-run.Full record and method:
docs/benchmark_results/mtp-b1-gate-m3ultra-2026-08-20.md.Refs #1217.