Skip to content

perf(speculative): gate B=1 MTP on GPU generation, not Neural Accelerator (#1217) - #1254

Merged
inureyes merged 2 commits into
mainfrom
update/issue-1217-mtp-b1-gate
Aug 20, 2026
Merged

perf(speculative): gate B=1 MTP on GPU generation, not Neural Accelerator (#1217)#1254
inureyes merged 2 commits into
mainfrom
update/issue-1217-mtp-b1-gate

Conversation

@inureyes

@inureyes inureyes commented Aug 19, 2026

Copy link
Copy Markdown
Member

What this does

Re-measures the batch-capable B=1 MTP pairing on M3 Ultra and moves mtp_b1_default off the has_neural_accelerator proxy onto the mechanism the round-cost model already attributed the host ordering to: the use_qmv_wide split at Apple GPU generation 15.

Why the old gate was wrong

The gate ran batch-capable targets only where has_neural_accelerator held, 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.sh covered the 12B and Qwen pairings and bench_block_width.sh the 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 a gemma31b case 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 is emitted per verify / speedup, in this host's own classic decode steps.

Output acceptance emitted/verify classic MTP speedup round cost
enumeration 1.000 3.990 31.5 83.6 2.65x 1.51
source code 0.882 3.646 31.8 76.6 2.41x 1.51
prose 0.656 2.956 31.7 61.9 1.95x 1.52

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 K classic 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 K against the 12B pairing's 1.14 + 0.090 K on 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 K puts 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_default as 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=0 so the static gate decides:

Binary MLXCEL_ENABLE_MTP_B1 Scheduler outcome
main at 9e2c6675 unset declined, classic decode
this branch unset ran the burst (block 4, 80 tokens over 21 rounds, acceptance 0.921)
this branch 0 declined, classic decode

Worth recording separately: the offline mlxcel generate path never consults this gate. mtp_b1_default has exactly one caller, Scheduler::mtp_b1_should_run, and MtpPolicy is 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 why MLXCEL_ENABLE_MTP_B1=1 and MLXCEL_MTP_ADAPTIVE=0 are inert there.

Checks

cargo fmt --check clean, cargo clippy --all-targets clean, 361 server::batch tests pass, plus the new hardware and mtp_b1_default unit 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.

Full record and method: docs/benchmark_results/mtp-b1-gate-m3ultra-2026-08-20.md.

Refs #1217.

…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.
@inureyes inureyes added type:performance Performance improvements priority:medium Medium priority area:inference Generation, sampling, decoding (incl. speculative, DRY) platform:macos macOS (Apple Silicon) specific area:benchmark Benchmark harness and performance measurement (bench_*.sh, /update-benchmarks) status:review Under review labels Aug 19, 2026
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).
@inureyes inureyes added status:done Completed and removed status:review Under review labels Aug 20, 2026
@inureyes
inureyes merged commit 2766abd into main Aug 20, 2026
8 checks passed
@inureyes
inureyes deleted the update/issue-1217-mtp-b1-gate branch August 20, 2026 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:benchmark Benchmark harness and performance measurement (bench_*.sh, /update-benchmarks) area:inference Generation, sampling, decoding (incl. speculative, DRY) platform:macos macOS (Apple Silicon) specific priority:medium Medium priority status:done Completed type:performance Performance improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant