Benchmarks¶
Measured results, mostly on a 64 GB M4 Max, plus a 16 GB M4 Mac mini and a 48 GB M5 Pro where noted. For a download list by Mac memory, see Choose a model; each model's Hugging Face card has its full measurements.
Weight compression¶
| Model | Method | Bits | PPL | Size | Gen Speed (M4 Max) |
|---|---|---|---|---|---|
| Qwen2.5-7B | TurboQuant | 3 | 8.92 | 3.5 GB | — |
| Qwen2.5-7B | Affine | 3 | 13.37 | 3.3 GB | — |
| GPT-OSS-20B | Affine (mlx-lm) | 4 | — | 11.2 GB | 148 tok/s |
| GPT-OSS-20B | MXFP4 (original) | 4 | 83.04 | 12.8 GB | — |
| GPT-OSS-20B | TurboQuant | 4 | 72.63 | 11.2 GB | — |
| GPT-OSS-20B | TurboQuant | 3 | 78.60 | 9.3 GB | 73 tok/s |
| GPT-OSS-120B | Affine 4-bit (mlx-community) | 4 | — | 65.8 GB | Doesn't fit 64GB |
| GPT-OSS-120B | MXFP4 (original) | 4 | — | 63.5 GB | Doesn't fit 64GB |
| GPT-OSS-120B | TurboQuant | 3 | — | 48 GB | 44 tok/s |
| GPT-OSS-120B (hybrid for 48GB) | TQ 3-attn / 2-experts, gs=32 | 2/3 mix | — | ~35 GB | 42–50 tok/s |
| GPT-OSS-120B | TurboQuant | 2 | — | 32 GB | 51 tok/s (poor quality) |
| Qwen3.5-122B-A10B | BF16 (original) | 16 | — | ~240 GB | Doesn't fit 64GB |
| Qwen3.5-122B-A10B | TurboQuant | 3 | — | ~50 GB | 26.5 tok/s (64 GB) · streams on a 16 GB Mac mini |
| Qwen3.5-122B-A10B (ternary experts) | TQ 3-attn / ternary trit-packed experts, gs=64 | 1.6/3 mix | — | 30.9 GB | ~25 tok/s* (0.29.0: ~13) · 32.5 GB peak, resident on 48 GB Macs without sysctl · 6/6 stress |
| Qwen3.6-35B-A3B | TurboQuant, gs=32 | 3 | — | ~16 GB | ~60 tok/s (resident) · runs in <4 GB via streaming |
| Qwen3.6-35B-A3B (ternary experts) | TQ 3-attn / ternary trit-packed experts, gs=64 | 1.6/3 mix | — | 9.4 GB | ~17 tok/s* fully resident on a 16 GB Mac mini (0.29.0: ~10.4) · ~55 tok/s* M4 Max (0.29.0: ~33) · chat; not for agent harnesses |
| Qwen3.6-35B-A3B (ternary + 4-bit expert-down) | TQ 3-attn / ternary experts / 4-bit expert-down, gs=64 | 1.6/3/4 mix | — | 12.6 GB | ~17 tok/s* on a 16 GB Mac mini (sysctl; 0.29.0: 15.2–15.6) · Opencode agentic 3/3 |
| Qwen3.6-27B (dense coder) | TurboQuant, gs=32 | 3 | — | ~13 GB | ~14 tok/s (resident) · fits 48 GB, SWE-bench coder |
| Qwen3-235B-A22B-Instruct-2507 (hybrid) | TQ 3-attn / 2-experts, gs=32 | 2/3 mix | — | 70.5 GB | ~4–6 tok/s (64 GB, 40 GB cache) · converts + streams on a 16 GB Mac mini |
| Qwen3-235B-A22B-Instruct-2507 (full 3-bit) | TurboQuant, gs=32 | 3 | — | 103 GB | ~1.3 tok/s (64 GB, 40 GB cache) · recall-critical sibling, passes 6/6 stress |
| Qwen3-235B-A22B-Instruct-2507 (ternary experts, 1.58-bit) | TQ 3-attn / ternary trit-packed experts, gs=64 | 1.6/3 mix | — | 53 GB | ~9–12 tok/s* (64 GB; 0.29.0: 5.6) · fully resident, no streaming |
| Nemotron-3-Nano-4B | TurboQuant | 3 | — | ~2.2 GB | 75.6 tok/s |
| Nemotron-3-Super-120B-A12B | BF16 (original) | 16 | — | ~240 GB | Doesn't fit 64GB |
| Nemotron-3-Super-120B-A12B | TurboQuant | 3 | — | ~50 GB | 18.7 tok/s |
| Nemotron-3-Super-120B-A12B (hybrid for 48GB) | TQ 3-attn / 2-experts, gs=32 | 2/3 mix | — | ~36 GB | ~27.2 tok/s |
| Laguna-XS.2 (33B agentic coder) | TurboQuant, gs=64 | 3 | — | 13.8 GB | 46.8 tok/s · 32 GB Mac · Opencode pass |
| Laguna-S-2.1 (118B) | BF16 (original) | 16 | — | ~219 GB | Doesn't fit 64GB |
| Laguna-S-2.1 (118B) | Affine 4-bit (mlx-community; since removed from Hugging Face) | 4 | — | ~64 GB | Exceeds Metal's ~56 GB working set — won't load |
| Laguna-S-2.1 (118B, ternary experts) | TQ 3-attn / ternary trit-packed experts, gs=64 | 1.6/3 mix | — | ~27 GB | ~20–28 tok/s* (0.29.0: ~12.5) · fully resident on 64 GB with ~25 GB spare · Opencode pass |
| Kimi K3 (2.8T MoE) | TQ 3-attn / ternary experts / 4-bit expert-down, gs=64 | 1.6/3/4 mix | — | 931 GB | 2.3 tok/s (Mac Studio 512 GB, top-8) · 1.2 tok/s at native top-16 · streams experts from disk |
| DiffusionGemma-26B-A4B (block-diffusion MoE) | Affine 4-bit (mlx-community) | 4 | — | 15.0 GB | ~35 tok/s |
| DiffusionGemma-26B-A4B | TurboQuant, gs=32 | 3 | — | 13.8 GB | ~4.6 tok/s · experimental: slower than 4-bit |
| DiffusionGemma-26B-A4B (16 GB build) | TQ 3-attn / 2-experts, 6 layers protected at 3-bit / 8-bit extras, gs=32 | 2/3 mix | — | 9.79 GB | ~1.5 tok/s on a 16 GB Mac mini (sysctl) · experimental |
| Muse Glimmer (30B dense VLM) | Affine 4-bit | 4 | 4.3798 | 19.88 GiB | 26.1 tok/s · leaves embedding + vision tower at bf16 |
| Muse Glimmer (30B dense VLM) | TurboQuant, gs=64 | 4 | 4.3315 | 14.88 GiB | 9.4 tok/s · better PPL than affine 4-bit in 25% less space |
| Muse Glimmer (30B dense VLM) | TurboQuant, gs=64 | 3 | 5.0454 | 12.53 GiB | 10.9 tok/s · smallest coherent build |
| Qwen3.8-27B (dense 27.8B hybrid-GDN VLM) | BF16 (original) | 16 | — | 55.6 GB | Needs a 64 GB Mac just to load |
| Qwen3.8-27B | Affine 4-bit (mlx-community) | 4 | 7.2685 | 14.95 GiB | 29.7 tok/s — fastest 4-bit, but peaks 16.16 GiB: won't fit 16 GB |
| Qwen3.8-27B | TurboQuant, gs=64 | 4 | 7.2496 | 15.15 GiB | 11.3 tok/s · best PPL of the three · Opencode agentic pass · vision 4/4 |
| Qwen3.8-27B (16 GB build) | TurboQuant, gs=64 | 3 | 8.0052 | 11.55 GiB | 12–13.6 tok/s (M4 Max) · verified on a real 16 GB Mac mini: 13.61 GiB peak, 3.7 tok/s, vision passes |
| Qwen3.8-Flash-Next (180B MoE, 512 experts) | Affine oQ2 (Vontra) | 2 | — | 63.03 GiB | Too big to load resident on a 64 GB Mac |
| Qwen3.8-Flash-Next | imatrix iQ-MLX 3.3 bpw (ddalcu, for mlx-serve) | 3.3 | — | 50.6 GiB | Fits 64 GB per its card; not measured here |
| Qwen3.8-Flash-Next | TQ 4-attn / 2-experts / 2-bit affine n-gram table, data-free | 2/4 mix | 9.31 | 52.00 GiB + 0.84 GiB vision | 16–18 tok/s · fully resident on 64 GB · vision passes |
| NVIDIA Nemotron 3.5 Super VL 120B (VLM, 512 experts, top-22) | BF16 (original) | 16 | — | 249 GB | Doesn't fit 64GB |
| Nemotron 3.5 Super VL 120B | NVFP4 (NVIDIA) | 4 | — | 63.0 GiB | Doesn't fit resident on a 64 GB Mac |
| Nemotron 3.5 Super VL 120B (ternary experts) | TQ 3-attn / ternary trit-packed experts / 8-bit affine extras, gs=64 | 1.6/3 mix | — | 29.29 GiB | ~16–21 tok/s, measured on 0.30.0 (2× 0.29.0 on the same prompts) · fully resident on 64 GB at default settings · 36K context · text 6/6, vision 3/3 |
| Nemotron 3.5 Super VL 120B (3-bit) | TurboQuant, gs=64 | 3 | — | 50.22 GiB | ~19–21 tok/s · 64 GB with sysctl + --prefill-step-size 256 |
* Estimated for 0.30.0, not measured. 0.30.0 makes ternary experts decode faster (the kernels no longer do an integer division per weight). These figures come from kernel timings at each model's expert shapes, calibrated against the model's measured speeds; the same estimate predicted 20.4 tok/s for Nemotron 3.5 VL, which measured 21.0. The 0.29.0 figures in parentheses were measured.
KV cache compression¶
| Model | KV cache config | KV size | Speed | Notes |
|---|---|---|---|---|
| GPT-OSS-20B (FP16 weights) | FP16 KV | 27.0 MB | 90.6 tok/s | baseline |
| GPT-OSS-20B (FP16 weights) | TQ 3-bit KV | 7.79 MB | 29.9 tok/s | 3.5x cache savings |
| GPT-OSS-120B (TQ 3-bit weights) | FP16 KV | 45.0 MB | 6.4 tok/s | baseline |
| GPT-OSS-120B (TQ 3-bit weights) | TQ 3-bit KV | 11.83 MB | 8.7 tok/s | 3.8x cache savings — and faster than FP16 |
| GPT-OSS-120B (TQ 3-bit weights) | TQ 4-bit KV | 12.21 MB | 16.0 tok/s | also clean |
| Qwen3.5-122B (TQ 3-bit weights) | FP16 KV | 161.06 MB | 5.4 tok/s | baseline |
| Qwen3.5-122B (TQ 3-bit weights) | TQ 3-bit KV | 150.17 MB | 5.7 tok/s | output identical to FP16 |
KV cache compression projects to ~7 GB RAM saved at 131K context on GPT-OSS-120B and ~5 GB at 262K on Qwen3.5-122B. Roundtrip cosine similarity vs FP16: 0.983 at 3-bit, 0.995 at 4-bit.
On the Qwen3.5-122B KV rows: these were measured with symmetric 3-bit KV (
demo_kv.py --tq-bits 3, short prompt), and the rates sit well below the model's fully-resident decode. With the Metal wired cap raised so the ~50 GB model stays fully resident, decode with the recommended mixed K8/V3 cache is ~24–25 t/s — and there fp16 edges out compression at short-to-moderate context, the gap widening as context grows. On this model KV compression is a memory win, not a speed win; the genuine decode speed-up is GPT-OSS-120B-specific. See the resident long-context sweep in #19.
Apple M5 Pro (48 GB)¶
First Metal4 / MTLGPUFamilyApple10 data point, contributed by @sbayer2 (#14, #16) — a new 48 GB tier between the 16 GB Mac mini and the 64 GB M4 Max. Reproduce with benchmarks/bench_m5_pro.py.
| Model | Mode | Gen t/s | Peak Memory | Notes |
|---|---|---|---|---|
| Qwen3.6-35B-A3B tq3-g32 | resident | 52.0 | 18.1 GB | fp16 KV |
| Nemotron-3-Super-120B-A12B tq3a/tq2e-g32 | resident | 20.2 | 41.1 GB | needs sudo sysctl iogpu.wired_limit_mb=49152 |
| Qwen3.5-122B-A10B tq3 | streaming (30 GB cache) | 9.4 (7.3 e2e) | 34.2 GB | 89.9% expert hit-rate |
KV cache sweep on the 35B (resident, 3-run averages):
| KV config | Prompt t/s | Gen t/s | Peak |
|---|---|---|---|
| fp16 baseline | 47.8 | 52.0 | 18.131 GB |
| K8 / V3 | 75.5 | 45.7 | 18.123 GB |
| K8 / V3 + sink128 | 76.1 | 45.7 | 18.124 GB |
| K3 / V3 | 75.6 | 45.2 | 18.122 GB |
KV cache against context on the 35B (256 generated tokens; 128 at 63K):
| Context | KV config | Prompt t/s | Decode t/s | Peak Metal | Memory saved |
|---|---|---|---|---|---|
| ~65 tok | fp16 | 47.8 | 52.0 | 18.13 GB | — |
| ~65 tok | K8/V3 | 76.1 | 45.7 | 18.12 GB | 0.01 GB |
| ~2.5K tok | fp16 | 131.9 | 51.5 | 20.50 GB | — |
| ~2.5K tok | K8/V3 | 133.4 | 38.2 | 20.55 GB | -0.05 GB |
| ~14.5K tok | fp16 | 132.8 | 46.0 | 22.50 GB | — |
| ~14.5K tok | K8/V3 | 132.6 | 16.7 | 21.94 GB | 0.56 GB |
| ~63K tok | fp16 | 124.5 | 34.5 | 30.79 GB | — |
| ~63K tok | K8/V3 (no sink) | 123.6 | 5.2 | 28.71 GB | 2.08 GB |
FP16 decodes 1.14× faster at 65 tokens, 1.35× at 2.5K, 2.75× at 14.5K and 6.6× at 63K, so on this model KV compression saves memory, not time. Of the models measured, only GPT-OSS-120B decodes faster with a compressed cache (KV cache compression, 8.7 vs 6.4 tok/s), and not because of its size: Qwen3.5-122B, run resident on a 64 GB M4 Max, decodes faster with FP16 at every context (1.07× at 256 tokens, 1.20× at 4096; #19). The difference is GPT-OSS-120B's KV geometry.
122B expert streaming — parallel prefetch (--cache-budget-gb 30, 256 tokens):
--prefetch-workers |
Gen t/s | E2E t/s | Disk read | Hit rate |
|---|---|---|---|---|
| 1 (serial) | 7.4 | 5.7 | 44.3 GB | 89.5% |
| 8 (parallel) | 9.1 | 7.6 | 41.9 GB | 90.1% |
| Speedup | 1.23× | 1.33× |
A 1.23× decode speedup from --prefetch-workers 8, landing between the Mac mini (1.3×) and the M4 Max (1.67×). The M5 Pro MacBook Pro SSD is the limiter — parallel prefetch helps but doesn't saturate the way the M4 Max's higher-bandwidth SSD does.
122B expert streaming — cache-budget sweep (--prefetch-workers 8, 256 tokens):
| Budget | Hit rate | Gen t/s | E2E t/s | Peak Metal | Disk read |
|---|---|---|---|---|---|
| 20 GB | 80.9% | 7.1 | 6.1 | 24.2 GB | 80.7 GB |
| 30 GB | 90.3% | 9.1 | 7.6 | 34.2 GB | 40.9 GB |
| 38 GB | 91.0% | 8.9 | 7.5 | 42.2 GB | 38.0 GB |
The hit-rate curve flattens hard past 30 GB (only +0.7% for +8 GB), and throughput actually dips at 38 GB as peak Metal (42.2 GB) crowds the wired cap. 30 GB is the sweet spot on the 48 GB tier — 90%+ hit rate with ~14 GB of headroom for OS stability; pushing to 38 GB gives negligible gain while peaking uncomfortably close to the wired limit.
KV compression gives a consistent ~1.6× prompt-processing speedup for a ~12% decode cost (long-context decode behavior is in The speed flip). Expert-streaming hit-rate scales with the cache budget — 44.6% at 4 GB (16 GB mini) → 89.9% at 30 GB (48 GB), a ~7× throughput jump that fills the gap between the 16 GB and 64 GB tiers.
Stability near the memory ceiling: long-context prompt prefill close to the wired cap can starve the kernel watchdog (a
watchdogd/AppleARMWatchdogTimerpanic). A rapidly-growing KV cache makes Metal commit pages continuously (AGXG17XFamilyResidencySet _commitAddedAllocations), so the binding limit is allocation rate, not peak — a static 41 GB resident model (Nemotron) is stable, while a growing ~30 GB KV during a 63K-token prefill can panic. Practical limits on the 48 GB tier (#14): contexts up to ~14.5K are safe on the 35B with either KV config; 63K is feasible with K8/V3 (28.7 GB peak) but not fp16 (30.8 GB → panic). Keep headroom and close other apps for long-context runs near the cap.