While benchmarking speculative-decoding verify shapes we found a sharp cliff in
fp16 mx.fast.scaled_dot_product_attention when the number of query rows
crosses from 8 to 12. Latency then stays flat until L≈48 and only recovers
efficiency at L≥64 — so the L∈[12, 48] window pays ~L=48 cost regardless of L.
GQA 32 q-heads / 8 kv-heads, head_dim 128, fp16, no mask, M5 Max:
| S (kv len) |
L=8 |
L=12 |
L=16 |
L=32 |
L=48 |
L=64 |
| 16384 |
0.813 ms |
1.906 ms |
1.903 ms |
1.904 ms |
1.904 ms |
1.588 ms |
| 32768 |
1.319 ms |
3.633 ms |
3.646 ms |
3.643 ms |
3.630 ms |
2.992 ms |
- L=8→12 is a 2.34× (16k) / 2.75× (32k) jump for 1.5× the work.
- Per-query-row cost at L=12 is ~1.6–1.9× the L=8 cost and worse than L=1.
- The flat plateau L=12..48 suggests the vector kernel's small-L path caps at
L≤8 and everything above falls to a path tuned for much larger L (steel /
full attention), with nothing covering the 12–48 window.
Why it matters: multi-token verify steps in speculative decoding (MTP,
prompt-lookup / n-gram, draft-model) live exactly in L≈4–16, and batched short
continuations hit 12–48. The window between "decode-shaped" and "prefill-shaped"
is becoming the hot path for spec-decode workloads.
Observed on the affine-quantized side too: the decomposed quantized attention
(qmm→softmax→qmm) beats fp16 SDPA at L=16 at these S (0.35–0.48× its
latency), which is only possible because of this cliff.
Repro (self-contained):
import time, mlx.core as mx
NQ, NKV, D = 32, 8, 128
for S in (16384, 32768):
k = mx.random.normal((1, NKV, S, D)).astype(mx.float16)
v = mx.random.normal((1, NKV, S, D)).astype(mx.float16)
mx.eval(k, v)
for L in (1, 2, 4, 8, 12, 16, 24, 32, 48, 64):
q = mx.random.normal((1, NQ, L, D)).astype(mx.float16); mx.eval(q)
f = lambda: mx.fast.scaled_dot_product_attention(q, k, v, scale=D**-0.5)
for _ in range(10): mx.eval(f())
mx.synchronize(); t0 = time.perf_counter()
for _ in range(40): mx.eval(f())
mx.synchronize()
print(S, L, f"{(time.perf_counter()-t0)/40*1e3:.3f} ms")
Happy to run additional shapes (head dims, GQA ratios, masks) if useful.
While benchmarking speculative-decoding verify shapes we found a sharp cliff in
fp16
mx.fast.scaled_dot_product_attentionwhen the number of query rowscrosses from 8 to 12. Latency then stays flat until L≈48 and only recovers
efficiency at L≥64 — so the L∈[12, 48] window pays ~L=48 cost regardless of L.
GQA 32 q-heads / 8 kv-heads, head_dim 128, fp16, no mask, M5 Max:
L≤8 and everything above falls to a path tuned for much larger L (steel /
full attention), with nothing covering the 12–48 window.
Why it matters: multi-token verify steps in speculative decoding (MTP,
prompt-lookup / n-gram, draft-model) live exactly in L≈4–16, and batched short
continuations hit 12–48. The window between "decode-shaped" and "prefill-shaped"
is becoming the hot path for spec-decode workloads.
Observed on the affine-quantized side too: the decomposed quantized attention
(qmm→softmax→qmm) beats fp16 SDPA at L=16 at these S (0.35–0.48× its
latency), which is only possible because of this cliff.
Repro (self-contained):
Happy to run additional shapes (head dims, GQA ratios, masks) if useful.