Qwen3.8-27B dense · Apple M4 Max 128 GB · October 2026
Three local servers measured with the same agent-turn test at 32K and 128K context, against Qwen3.8-Flash-Next, the model we use every day.
TL;DR
Fastest setup: Splash 1.3.1 + DFlash2 (8-bit weights). Its agent turn time (time to the first token of a turn that carries a tool result, plus 512 generated tokens) at 128K context is 17.7 s at thinking effort xhigh and 15.9 s at thinking effort medium.
That is 0.69× the baseline, mlx-dspark 0.20.3 + DFlash2 with the same weights and draft (25.7 s at xhigh; same effort on both sides).
It is still ~2× Qwen3.8-Flash-Next, the model we use daily (8.6 s at 128K; 2.06× at xhigh, 1.85× at medium): for agent loops, Flash-Next is still faster.
Each step keeps the setups that can still be the fastest way to run the dense 27B. 5 setups never ran: they were cut by choice to save rig time. 3 ran the 8K smoke and were not taken further after it, so their smoke decode is all we have for them. The comparison with Flash-Next is in the TL;DR and the chart below.
keptreference (baseline)cut by scope (not measured)failed a measurement
Grey means the setup never got the full 32K / 128K test. 3 of them ran the 8K smoke first and were not taken further after it; 2 of those decoded slower than every setup that went on (under 80.6 tok/s). Smoke decode = the median of the identical, append and tool-turn requests at 8K (the same warm-decode definition as every decode number on this page), thinking effort medium, one run. Agent turn times in the funnel are at thinking effort xhigh, the only effort the baseline has at 128K; failed = slower than the baseline.
Each bar is one agent turn: time to the first token of a turn that carries a tool result, plus 512 generated tokens, median of 3 repetitions. Thinking effort is the reasoning_effort value sent with each request. More effort means more reasoning tokens and lower draft acceptance, so a lower decode speed. Only thinking effort xhigh shows by default; tick medium to add those rows. The Flash-Next rows are the reference we compare against (thinking effort xhigh, on mlx-serve 26.10.1).
warm TTFT of the tool-result turn512 tokens at the median decode speedhover a bar for decode, TTFT and memory; the table below has the same numbers
| Setup | Thinking effort | Agent turn time (s) | Warm TTFT, tool turn (s) | Cold TTFT (s) | Prefill (tok/s) | Decode (tok/s) | Cache hit, tool turn | Draft / MTP acceptance | Peak memory (GB) |
|---|---|---|---|---|---|---|---|---|---|
| mlx-dspark 0.20.3 + DFlash2 (baseline) | xhigh | 17.0 | 5.4 | 109.9 | 248 | 44.0 | 0.96 | — | 43.3 |
| mlx-dspark 0.20.3 + DFlash2 (baseline) | medium | 13.5 | 5.5 | 109.5 | 249 | 63.8 | 0.96 | — | 41.5 |
| Splash 1.3.1 + DFlash2 | xhigh | 13.3 | 3.9 | 76.3 | 358 | 54.7 | 0.96 | — | 42.2 |
| Splash 1.3.1 + DFlash2 | medium | 10.4 | 3.9 | 76.1 | 358 | 79.3 | 0.96 | — | 42.0 |
| mlx-serve 26.10.2 + DFlash2 | xhigh | 19.9 | 5.8 | 109.0 | 250 | 36.3 | 0.96 | 0.46 | 59.3 |
| mlx-serve 26.10.2 + DFlash2 | medium | 15.7 | 5.8 | 108.4 | 252 | 51.9 | 0.96 | 0.79 | 63.0 |
| Qwen3.8-Flash-Next · Swift 1.5 quant (our daily model) | xhigh (reference) | 7.9 | 1.8 | 34.5 | 791 | 84.6 | 0.96 | 0.77 | 96.1 |
| Qwen3.8-Flash-Next · ddalcu iQ quant | xhigh (reference) | 8.1 | 1.8 | 34.5 | 791 | 82.3 | 0.96 | 0.84 | 96.0 |
warm TTFT of the tool-result turn512 tokens at the median decode speedhover a bar for decode, TTFT and memory; the table below has the same numbers
| Setup | Thinking effort | Agent turn time (s) | Warm TTFT, tool turn (s) | Cold TTFT (s) | Prefill (tok/s) | Decode (tok/s) | Cache hit, tool turn | Draft / MTP acceptance | Peak memory (GB) |
|---|---|---|---|---|---|---|---|---|---|
| mlx-dspark 0.20.3 + DFlash2 (baseline) | xhigh | 25.7 | 9.8 | 712.6 | 176 | 32.3 | 0.99 | — | 55.4 |
| mlx-dspark 0.20.3 + DFlash2 (baseline) | medium | not measured | |||||||
| Splash 1.3.1 + DFlash2 | xhigh | 17.7 | 7.4 | 535.1 | 235 | 49.8 | 0.99 | — | 53.8 |
| Splash 1.3.1 + DFlash2 | medium | 15.9 | 7.4 | 534.4 | 235 | 60.5 | 0.99 | — | 53.5 |
| mlx-serve 26.10.2 + DFlash2 | xhigh | 44.7 | 10.8 | 717.8 | 175 | 15.1 | 0.99 | 0.54 | 70.4 |
| mlx-serve 26.10.2 + DFlash2 | medium | 34.8 | 10.8 | 716.8 | 175 | 21.3 | 0.99 | 0.77 | 69.5 |
| Qwen3.8-Flash-Next · Swift 1.5 quant (our daily model) | xhigh (reference) | 8.6 | 1.9 | 165.5 | 759 | 76.7 | 0.99 | 0.67 | 111.2 |
| Qwen3.8-Flash-Next · ddalcu iQ quant | xhigh (reference) | 8.9 | 1.9 | 165.4 | 760 | 73.2 | 0.99 | 0.79 | 110.6 |
Peak memory (GB) is the highest wired memory of the whole machine sampled during the run (model weights, KV cache and anything else the system pinned), not the server process alone. Prefill is prompt tokens of the cold request ÷ cold TTFT, read from the per-request results. The harness reads an acceptance rate only from mlx-serve (draft) and the Flash-Next references (MTP); the other cells are empty.
Quality and per-task time were not measured for the 27B.
The exact Splash 1.3.1 launch of the fastest setup, rebuilt from the campaign launcher. KV cache: int8, the Splash 1.3.1 default (--kv-format, not passed). --idle-release off keeps the model loaded; --language-only skips the vision tower.
mlx-community/Qwen3.8-27B-8bit @ 815b83c0 (full revision 815b83c0df8ffd1d1b5244cf75fd6ef14fca9ef9).incoai/Qwen3.8-27B-DFlash2 @ dedf8df6 (full revision dedf8df68adfb1afeaf7b7480c0a0243108177b4), passed as a local directory.reasoning_effort at the top level of the request body; medium gave the shorter turns here.# once: fill the Hugging Face cache (Splash wants a Hub ID for the target and --offline reads the cache)
hf download mlx-community/Qwen3.8-27B-8bit --revision 815b83c0df8ffd1d1b5244cf75fd6ef14fca9ef9
DRAFT_DIR="$HOME/models/Qwen3.8-27B-DFlash2" # any absolute directory you choose
hf download incoai/Qwen3.8-27B-DFlash2 --revision dedf8df68adfb1afeaf7b7480c0a0243108177b4 --local-dir "$DRAFT_DIR"
# serve (Splash 1.3.1)
splash serve --model mlx-community/Qwen3.8-27B-8bit --host 127.0.0.1 --port 8585 \
--max-context 131072 \
--revision 815b83c0df8ffd1d1b5244cf75fd6ef14fca9ef9 \
--draft-model "$DRAFT_DIR" \
--idle-release off --language-only --no-webui --offlinecurl -s http://localhost:8585/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model": "mlx-community/Qwen3.8-27B-8bit", "messages": [{"role": "user", "content": "..."}], "reasoning_effort": "medium", "temperature": 1.0, "top_p": 0.95, "top_k": 20}'Do not resend the reasoning. At 128K, Splash 1.3.1 returned HTTP 400 on every tool turn that sent the previous reasoning_content back (6 of 6, thinking effort medium and xhigh). Without it, 6 of 6 worked, with a TTFT of 7.4 s. Configure your client to drop reasoning_content from earlier assistant messages.
mlx-community/Qwen3.8-27B-8bit @ 815b83c0mlx-community/Qwen3.8-27B-8bit @ 815b83c0ddalcu/Qwen3.8-27B-MLX-Serve-8bit @ 7775059eInternal codes used in the campaign files. "T_turno" is the agent turn time; p<N>x / p<N>m file tags mark the phase and the effort (xhigh / medium).
| Code | Public name | Campaign name | Runtime | Pack | Revision | Port | Phase |
|---|---|---|---|---|---|---|---|
| s27r | mlx-dspark 0.20.3 + DFlash2 (baseline) | mlx-dspark 0.20.3 + DFlash2 | mlx-dspark 0.20.3 | mlx-community 8bit | 815b83c0 | 8484 | P0 |
| k27 | Splash 1.3.1 + DFlash2 | Splash 1.3.1 + DFlash2, KV int8 | Splash 1.3.1 | mlx-community 8bit (Hub cache) + local DFlash2 draft | 815b83c0 | 8585 | P1 |
| o27q8 | oMLX 0.7.1 · oQ8e refit, 8-bit | oMLX oQ8e refit | oMLX 0.7.1.dev1 | Jundot oQ8e-mtp | 6c76ddfb | 8000 | P2 |
| o27q4 | oMLX 0.7.1 · oQ4e refit, 4-bit | oMLX oQ4e refit | oMLX 0.7.1.dev1 | Jundot oQ4e-mtp | 49c1d603 | 8000 | P2 |
| r27d | mlx-serve 26.10.2 + DFlash2 | mlx-serve 26.10.2 DFlash2 drafter only (--no-mtp) | mlx-serve 26.10.2 | ddalcu 8bit | 7775059e | 11234 | P3 |
| r27m | mlx-serve 26.10.2 · MTP head only | mlx-serve 26.10.2 MTP head only (--no-drafter) | mlx-serve 26.10.2 | ddalcu 8bit | 7775059e | 11234 | P8 |
| w27d | mlx-serve 26.10.2 · Swift 1.5 + DFlash2 | mlx-serve 26.10.2 Swift 1.5, DFlash2 drafter only (--no-mtp) | mlx-serve 26.10.2 | scottlowry Swift-1.5 oQ4e-mtp | 881dc191 | 11234 | P7 |
| w27m | mlx-serve 26.10.2 · Swift 1.5, MTP only | mlx-serve 26.10.2 Swift 1.5, MTP head only (--no-drafter) | mlx-serve 26.10.2 | scottlowry Swift-1.5 oQ4e-mtp | 881dc191 | 11234 | P8 |
| o27sw | oMLX 0.7.1 · Swift 1.5, 4-bit | oMLX Swift 1.5 oQ4e | oMLX 0.7.1.dev1 | scottlowry Swift-1.5 oQ4e-mtp | 881dc191 | 8000 | P4 |
| l27 | llama.cpp b11569 · Q8_0 + MTP draft | llama.cpp b11569 draft-mtp | llama.cpp b11569 | ggml-org Q8_0 + mtp | 71bc7b62 | 8080 | P6 |
| l27n | llama.cpp b11569 · Q8_0, no speculation | llama.cpp b11569 no spec | llama.cpp b11569 | ggml-org Q8_0 | 71bc7b62 | 8080 | P6 |
| w1 | Qwen3.8-Flash-Next · Swift 1.5 quant (our daily model) | Flash-Next reference (xhigh) | mlx-serve 26.10.1 | ||||
| n2 | Qwen3.8-Flash-Next · ddalcu iQ quant | Flash-Next reference (xhigh) | mlx-serve 26.10.1 |
Raw gates from results/gates.env:
| Gate | Value | Meaning |
|---|---|---|
P1_PASS | 1 | Splash (k27): T_turno ≤ 0.75 × s27r at 128K and gates |
P1_BEST | k27 | Best arm of P1 |
P3_PASS | 0 | mlx-serve 26.10.2: T_turno ≤ s27r |
P7_PASS | not_run | w27d at or under 1.03 × r27d on both bands |
CAMPAIGN_DONE | 2026-10-14T06:35Z (stopped by user after P3; P7 cancelled) | Campaign finished (UTC) |
P5_GO | 0 | Driver gate: fastest arm within 1.25 × w1 on both bands (0 = none) |
8K smokes (warm decode as in summarize_driver.py):
k27: warm decode 92.3 tok/s, T_turno 8K 8.6 sl27: warm decode 35.3 tok/s, T_turno 8K 19.0 so27q4: warm decode 81.4 tok/s, T_turno 8K 10.7 sr27d: warm decode 80.6 tok/s, T_turno 8K 10.8 sr27m: warm decode 51.5 tok/s, T_turno 8K 14.5 sSetups not run (cut on 2026-10-13/14), by user scope:
o27q8: oMLX with the oQ8e refit pack. Cut by scope B (stop after P1 unless Splash passes at 128K).o27q4: oMLX with the oQ4e refit pack. Only the 8K smoke ran. Cut by scope B.o27sw: Swift 1.5 on oMLX. Cut with the rest of P4.w27d: Swift 1.5 on mlx-serve with the drafter: cancelled after r27d failed on the same runtime and drafter.r27m: MTP head only on mlx-serve. Only the 8K smoke ran (slow decode). Cut.w27m: Swift 1.5 MTP only on mlx-serve. Cut with r27m.l27: llama.cpp draft-mtp. Only the 8K smoke ran (slow decode). Cut.l27n: llama.cpp without speculation. Cut with l27.Thinking-turn probe at 128K:
k27-medium: resend 3/3 errors, drop 0/3 errors, drop TTFT 7.4 sk27-xhigh: resend 3/3 errors, drop 0/3 errors, drop TTFT 7.4 sr27d-xhigh: resend 0/1 errors, drop 0/1 errors, drop TTFT 94.5 sRun the campaign on the rig with bash bench/qwen38-27b-next-steps-2026-10/scripts/run-campaign.sh, then render this page with python3 bench/qwen38-27b-next-steps-2026-10/scripts/render_27b_page.py --png. Scripts and per-request results: github.com/snagnever/macstudio-local-llm/tree/main/bench/qwen38-27b-next-steps-2026-10.