You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the MTP exactness probe fails under qmv_wide and passes without it, the gate disables the kernel for the whole process and deliberately never re-enables it (retry_without_qmv_wide in src/models/speculative_exactness.rs: "it is a per-process kernel selection"). That is correct for the MTP verify forward, which is the block the contract is about, but the switch sits on the dispatch path of every quantized matmul in the process (mlxcel_qmv_wide_flag() in the quantized.cpp overlay), so everything else the same server process runs also drops to the narrow kernel:
batched decode at B >= 2, whose per-step projections are exactly the M >= 2 shape qmv_wide exists for;
prefill for other requests admitted while MTP is engaged;
any non-MTP request the scheduler serves alongside the speculative one.
On Apple GPU generation 15+ this fires by default now: the probe fails, the retry passes, and the process is pinned narrow (#1199 for Qwen, #1258 for Gemma 4). The measured verify-side cost is 17 to 20% (Qwen) and ~23% (Gemma 4); what the rest of the process pays has never been measured.
Step 1: measure the tax before designing the scope
Nothing below is worth building until the collateral cost is a number. On one generation 15+ host, mlxcel-server with a batch-capable target:
batched decode throughput at B = 2, 4, 8 with MLXCEL_QMV_WIDE=1 pinned vs =0 pinned (no drafter involved, so the comparison isolates the kernel);
a mixed workload: one MTP stream plus N classic streams, default env (gate flips the process narrow) vs MLXCEL_MTP_ALLOW_INEXACT=1 with the switch left wide, reading the classic streams' throughput only.
If the B-sweep delta is small on production shapes, the right fix may be documenting the tax and stopping there.
Step 2 (if the tax is material): scope the exact kernel to the verify forward
The hard part is recorded in the overlay's own comment: MLX evaluates lazily and dispatch happens at eval time on MLX's scheduler thread, so "set off, run verify, set on" from the caller thread does not bracket the kernels it means to bracket, and a caller-side thread-local never reaches the dispatch site. Candidate shapes, in rough order of invasiveness:
A dispatch-side predicate keyed on something the verify ops carry (a dedicated stream, or an op annotation plumbed through the overlay). More surgery in the overlay, but no stalls.
Per-call kernel selection for just the verify projections via a dedicated entry point in the overlay, leaving the global flag untouched.
Whichever shape wins must keep the probe's contract: the verify block's kernel selection at probe time has to match its selection at serve time, or the probe certifies the wrong thing.
Acceptance criteria
The B = 2/4/8 batched-decode tax of the narrow pin is measured and recorded on a generation 15+ host.
Either the tax is documented as accepted (with the number), or the exact kernel is scoped so non-verify work keeps qmv_wide, with byte-identity of the MTP stream re-verified after the change.
The probe measures the same kernel configuration the verify forward serves with.
Problem
When the MTP exactness probe fails under
qmv_wideand passes without it, the gate disables the kernel for the whole process and deliberately never re-enables it (retry_without_qmv_wideinsrc/models/speculative_exactness.rs: "it is a per-process kernel selection"). That is correct for the MTP verify forward, which is the block the contract is about, but the switch sits on the dispatch path of every quantized matmul in the process (mlxcel_qmv_wide_flag()in thequantized.cppoverlay), so everything else the same server process runs also drops to the narrow kernel:B >= 2, whose per-step projections are exactly theM >= 2shapeqmv_wideexists for;On Apple GPU generation 15+ this fires by default now: the probe fails, the retry passes, and the process is pinned narrow (#1199 for Qwen, #1258 for Gemma 4). The measured verify-side cost is 17 to 20% (Qwen) and ~23% (Gemma 4); what the rest of the process pays has never been measured.
Step 1: measure the tax before designing the scope
Nothing below is worth building until the collateral cost is a number. On one generation 15+ host,
mlxcel-serverwith a batch-capable target:MLXCEL_QMV_WIDE=1pinned vs=0pinned (no drafter involved, so the comparison isolates the kernel);MLXCEL_MTP_ALLOW_INEXACT=1with the switch left wide, reading the classic streams' throughput only.If the B-sweep delta is small on production shapes, the right fix may be documenting the tax and stopping there.
Step 2 (if the tax is material): scope the exact kernel to the verify forward
The hard part is recorded in the overlay's own comment: MLX evaluates lazily and dispatch happens at eval time on MLX's scheduler thread, so "set off, run verify, set on" from the caller thread does not bracket the kernels it means to bracket, and a caller-side thread-local never reaches the dispatch site. Candidate shapes, in rough order of invasiveness:
Whichever shape wins must keep the probe's contract: the verify block's kernel selection at probe time has to match its selection at serve time, or the probe certifies the wrong thing.
Acceptance criteria
qmv_wide, with byte-identity of the MTP stream re-verified after the change.References