Skip to content

Decode speed vs context depth — M1 Max 32 GB

Every benchmark here answers one question: how fast does the model decode when the context is actually full? A near-empty prompt says 62 tok/s; the same config in a real coding session ran 1.7 tok/s at 135K used tokens. That one observation created this test, and it runs first in the stack because everything else depends on its answer: the harness compaction threshold, the "gated by" verdict, the published max context, and which seat (main agent, sub-agent, background) a config can hold. The community measures the same axis as llama.cpp's llama-bench token-generation rate at depth. And it is only the first gate: a config that flies here but scores low on EvalPlus or Mendel gets dropped anyway.

Two rules to read the tables by:

  • The floor is 8 tok/s — below it, a config is unusable for interactive work, whatever its window says.
  • Used tokens, not allocated. Allocation is storage. Every allocated-context table this project once published is retired on the historical page.

Latest per model and backend

best curvetok/s (shallow → deep)atgated by
Ternary-Bonsai-27B 2-bitprism-ml,mlx_lm.serverf16on24.5† → 17.3†53kmem
Ternary-Bonsai-27B Q2_g64prism-ml,prism-llamaq4_0+biason14.8† → 7.9†33kspeed
Gemma-4-12B Q4_K_XLunsloth,llama-serverf16off24.64† → 8.86†245kmem
Gemma-4-26B-A4B UD-Q4_K_XLunsloth,llama-servermtp/2f16on60.3† → 17.3†197kmem
Gemma-4-26B-A4B 4-bitmlx-community,mlx_lm.serverf16on51† → 12.8†66kmem
Qwen3.6-35B-A3B UD-Q4_K_XLunsloth,llama-servermtp/3q8_0on43.7 → 13.082kspeed
Qwen3.6-35B-A3B 4-bitmlx-community,mlx_lm.serverf16on55.1† → 37.4†37kmem
Qwen3.8-27B Q4_K_Mbartowski,llama-servermtp/3f16xhigh11.8 → 8.672kmem
Qwen3.8-27B 4-bitmlx-community,mlx_lm.serverf16low17† → 15.3†25kmem

† from an earlier serving config or method; re-run pending.

MLX-side engines: flat curves, hard memory ceilings

MLX runtimes barely slow down with depth — then die of memory, fast and without warning. The curve ends in a Metal OOM, never at the floor. Every row below runs at f16 KV, the only cache type these servers offer, so the columns leave it out.

used tokensQwen3.6 MLXGemma-26B MLXGemma-12B LM StudioBonsai MLXQwen3.8 MLX
4K53.351.134.224.5
8K24.217.1
16K49.643.532.122.916.4
24-25K39.622.014.8
28K15.3 — last stable
32-33K42.235.630.620.5OOM ~30K
37K42.0 — last stable
41-42KOOM ~41K18.7
49K28.818.4
57-58K17.3 — last stable
65K27.1OOM ~60K
70K12.8 — last stable
74KOOM ~72K
98K24.5
131K23.2 — last stable

Gemma-12B is the outlier twice over: the flattest curve of the project, and the only MLX config that does not end in an OOM — its engine leans on macOS memory compression, so its ceiling is where the wired cap fills and swap starts, not where the process dies. It is also the only column here that a quality test rules out for tool work: it loops on the thought channel in multi-turn sessions, whatever the thinking setting.

llama-side (GGUF): faster decay, but never an OOM

llama runtimes creep down steadily and cross the floor while memory stays comfortable. The curve ends at the floor or at the window — never in a crash.

used tokensQwen3.6 +MTP, f16 KVQwen3.6 f16 KV, no drafterQwen3.6 +MTP, q8_0 KVGemma-26B +MTP, f16 KVQwen3.8 4-bit +MTP, f16 KVQwen3.8 ISTA 3-bit, f16 KV, no drafterGemma-12B f16 KV, no drafterGemma-12B f16 KV, no drafter, 1 of 2 slotsGemma-12B +MTP, q8_0 KVBonsai fork, f16 KV, no drafterBonsai fork, q4_0 KV + bias
4K69.149.843.760.311.814.124.625.013.815.014.8
8K71.344.118.213.824.124.18.716.313.2
16K65.731.256.516.113.322.722.86.5 — floor at 16K15.610.8
24-25K61.024.217.212.821.621.515.19.1
32-33K56.519.645.916.412.420.620.614.57.9 — floor at 33K
41K52.6 — window end38.3 — window end16.615.612.019.519.513.9
49K19.245.915.011.518.818.613.4
65K11.28.6 — last clean, swap past it10.917.416.912.5
82K13.0 — last above the floor10.215.715.7 — last clean per slot, swap past it11.5
98K7.9 — floor9.714.910.8
115K26.49.213.610.2
131K8.713.09.7 — window end, no floor found
147K8.3 — last above the floor
164K7.9 — floor
180K10.7
197K17.3 — last step, -c 212992 the largest that loads
213K9.7
245K8.9 — window end

The Qwen3.6 no-drafter column, the Qwen3.6 q8_0 cells at 4K, 49K and 82K, and the Qwen3.8 4-bit cells at 4K and 65K were read 2026-09-11 with llama-benchy on real code text at the server's own sampling, with draft acceptance recorded beside each cell on the model pages. Every other cell is a creep reading.

The Qwen3.6, Qwen3.8 GGUF, Bonsai f16 and Gemma-12B two-slot columns ran at wired limit 25000 between 2026-09-06 and 2026-09-09; the Gemma-26B, Gemma-12B one-slot and Bonsai q4_0 columns at 24000 between 2026-08-30 and 2026-09-05.

Four configs measured here never cross the floor inside their whole window: Qwen3.6 on llama at f16 KV, Gemma-26B on llama, the Bonsai fork at f16 KV, and Gemma-12B on llama with f16 KV and no drafter, which holds 8.9 tok/s at the model's own 245K. The same Gemma-12B server with q8_0 KV floors at 16K, a 3.2x gap at 16K between two KV types of one config, and the same Bonsai fork at q4_0 KV floors at 33K.

What this test caught

  • Speculative decoding costs depth, and on one build it costs speed too. On the Bonsai fork, the DSpark drafter lifts shallow decode but drops the floor from ~30K to ~20-23K and adds 4-5 GB. On the Qwen3.8 ISTA 3-bit build the MTP drafter loses at every depth, 12.4 against 14.4 tok/s at depth 256 and 7.9 against 9.5 at 98K, and dropping it moved the served window from 131072 to 163840. Measure the drafter per build, at two depths.
  • A fake 44K OOM. mlx_lm.server pools several multi-GB KV caches and acts like a memory leak; --prompt-cache-size 2 removed the fake ceiling. A server can also keep answering /health 200 after its generation thread died — the sweep scripts watch the server log, not the endpoint.
  • Fast sweeps understate ceilings. Pausing ~25 s between depth steps (as a real agent would) lets macOS compress memory and raised measured ceilings by ~2K tokens; the pause rule retired three earlier ceilings (Gemma-26B 82K → 70K among them).
  • Not every bad reading is real. A 12 tok/s dip at 44-48K on Bonsai and a 7 tok/s crash at 98K on Gemma-12B both vanished on watched re-runs — transient system episodes. The sweep samples memory on every step for exactly this reason.
  • The KV type can be the ceiling. On Gemma-12B, q8_0 KV falls under the floor by 16K while f16 holds to the model's window — 3.2x apart at 16K, and f16 still fits inside the wired limit. The KV policy's "~1% speed edge" is model-dependent, and this model retired its own published ceiling.

Fast is a ticket, not a win

This test decides whether a config is usable, not whether it is chosen. The quality tiers come after: EvalPlus gates, Mendel tests real agentic work and ranks the survivors — and a config that loses there is dropped no matter how good its curve was. The live example is Gemma-26B: the fastest MLX depth curve on this page (51 tok/s shallow, 70K deep), parked anyway after scoring 0.713 on the gate and failing the agentic tier. Read this page as the entry requirement, and the comparison for who actually wins seats.

Method, in one breath

Grow one prompt append-only, ~25 s pause per step, memory counters read into every step row, read the server's own timings, stop only at the floor, an OOM, or the trained window. Full procedure: context creep.


Complete curves for every config and era, including retired ones: Qwen3.6-35B-A3B · Qwen3.8-27B · Gemma-4-26B-A4B · Gemma-4-12B · Ternary-Bonsai-27B