Describe the issue
Single-query (decode-time) mx.fast.scaled_dot_product_attention runs far
below memory-bandwidth speed for a GQA shape with few KV heads and large
head_dim at long context. On an M1 Ultra with mlx 0.32.0:
- Shape:
q=(1, 32, 1, 256), k=v=(1, 4, 100000, 256), bf16, mask=None
- Measured: 2.2-2.4 ms per call
- KV bytes touched: 2 × 4 × 100000 × 256 × 2B ≈ 0.41 GB → ~170-190 GB/s achieved
- The same machine sustains ~680 GB/s on a plain reduction over a large
contiguous array (benchmark below), so the kernel achieves ~25-28% of
practically available bandwidth.
For a 16-full-attention-layer model (Qwen3.6-27B hybrid) this makes decode
attention cost ~39 ms/token at 100k context — about 43% of the total token
time — and is the dominant reason decode throughput trails
bandwidth-proportional expectations at long context.
MLX_SDPA_BLOCKS (new in 0.32.0) was swept at this shape: the default
selection is already optimal (8/16/32/64/128 are all equal or worse), so this
does not look like a block-count tuning issue.
q_len=2 costs 5.39 ms (≈2.2×), so batched-verify speculative decoding at long
context is also gated by the same kernel.
To Reproduce
import time
import mlx.core as mx
B, Hq, Hkv, D, S = 1, 32, 4, 256, 100000
# reference: practically achievable bandwidth on this machine
rows = 8 * 1024**3 // (4 * 65536)
a = mx.random.normal((rows, 65536))
mx.eval(a)
mx.eval(a.sum())
t0 = time.time()
for _ in range(5):
mx.eval(a.sum())
bw = 8 / ((time.time() - t0) / 5)
print(f"reduction bandwidth: {bw:.0f} GB/s")
k = mx.random.normal((B, Hkv, S, D), dtype=mx.bfloat16)
v = mx.random.normal((B, Hkv, S, D), dtype=mx.bfloat16)
q = mx.random.normal((B, Hq, 1, D), dtype=mx.bfloat16)
mx.eval(k, v, q)
scale = D ** -0.5
mx.eval(mx.fast.scaled_dot_product_attention(q, k, v, scale=scale, mask=None))
t0 = time.time()
n = 50
for _ in range(n):
mx.eval(mx.fast.scaled_dot_product_attention(q, k, v, scale=scale, mask=None))
dt = (time.time() - t0) / n
kv_gb = 2 * Hkv * S * D * 2 / 1e9
print(f"sdpa q_len=1: {dt*1000:.2f} ms -> {kv_gb/dt:.0f} GB/s "
f"({kv_gb/dt/bw*100:.0f}% of reduction bandwidth)")
Output on M1 Ultra (128GB), macOS 26.2, mlx 0.32.0 (pip):
reduction bandwidth: 676 GB/s
sdpa q_len=1: 2.17 ms -> 189 GB/s (28% of reduction bandwidth)
Expected behavior
Decode-time SDPA at long context is memory-bound; something closer to
50-80% of achievable bandwidth would be expected (llama.cpp's Metal FA
achieves ~75%+ on comparable GQA shapes on M-series).
Happy to run patches/benchmarks on this machine (M1 Ultra 128GB, real
workload = 27B hybrid model at 80-160k context).
This investigation (bandwidth baseline, kernel benchmarks, and this report)
was performed by Claude (model: claude-fable-5) via Claude Code, operating
directly on the reporter's hardware.
Describe the issue
Single-query (decode-time)
mx.fast.scaled_dot_product_attentionruns farbelow memory-bandwidth speed for a GQA shape with few KV heads and large
head_dim at long context. On an M1 Ultra with mlx 0.32.0:
q=(1, 32, 1, 256),k=v=(1, 4, 100000, 256), bf16,mask=Nonecontiguous array (benchmark below), so the kernel achieves ~25-28% of
practically available bandwidth.
For a 16-full-attention-layer model (Qwen3.6-27B hybrid) this makes decode
attention cost ~39 ms/token at 100k context — about 43% of the total token
time — and is the dominant reason decode throughput trails
bandwidth-proportional expectations at long context.
MLX_SDPA_BLOCKS(new in 0.32.0) was swept at this shape: the defaultselection is already optimal (8/16/32/64/128 are all equal or worse), so this
does not look like a block-count tuning issue.
q_len=2 costs 5.39 ms (≈2.2×), so batched-verify speculative decoding at long
context is also gated by the same kernel.
To Reproduce
Output on M1 Ultra (128GB), macOS 26.2, mlx 0.32.0 (pip):
Expected behavior
Decode-time SDPA at long context is memory-bound; something closer to
50-80% of achievable bandwidth would be expected (llama.cpp's Metal FA
achieves ~75%+ on comparable GQA shapes on M-series).
Happy to run patches/benchmarks on this machine (M1 Ultra 128GB, real
workload = 27B hybrid model at 80-160k context).
This investigation (bandwidth baseline, kernel benchmarks, and this report)
was performed by Claude (model:
claude-fable-5) via Claude Code, operatingdirectly on the reporter's hardware.