Qwen3.8-27B dense · Apple M4 Max 128 GB · October 2026

Dense Qwen3.8-27B on an M4 Max: which server runs it fastest?

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.

Glossary

TTFT
time to first token: seconds from sending a request until the first token comes back.
Cold vs warm
cold is the first request on an empty cache, so the server reads the whole prompt; warm is a later request that reuses the cached start of the conversation.
Prefill (prompt processing)
reading the prompt before the first token; here prompt tokens ÷ cold TTFT.
Decode (token generation)
how fast the reply comes out, in tokens per second; the median over the warm requests.
Prefix cache and hit rate
the server keeps the work done on the previous prompt; the hit rate is the share of the new prompt served from that cache.
Context 32K / 128K
the test conversation fills a 32K (32,768-token) or 128K (131,072-token) context window.
Draft model (DFlash2) and speculative decoding
a small draft model proposes several tokens and the 27B checks them in one pass; accepted tokens come almost free.
MTP
multi-token prediction: extra heads inside the model propose the next tokens, with no separate draft.
Thinking effort
the reasoning_effort value of the request (xhigh or medium); more effort means more reasoning tokens and lower draft acceptance, so lower decode.

How 11 candidate setups came down to Splash

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.

Selection funnel: 11 → 5 → 3 → 1 setups11candidate setups5 runtimes: mlx-dspark, Splash, oMLX,mlx-serve, llama.cppnever ran, cut by choice:oMLX 0.7.1 · oQ8e refit, 8-bitmlx-serve 26.10.2 · Swift 1.5 + DFlash2mlx-serve 26.10.2 · Swift 1.5, MTP onlyoMLX 0.7.1 · Swift 1.5, 4-bitllama.cpp b11569 · Q8_0, no speculation58K smoke test (decode)the baseline skips it: it is the referencedecode at 8K, thinking effort medium:Splash 1.3.1 + DFlash2decode 92.3 tok/s at 8KoMLX 0.7.1 · oQ4e refit, 4-bitdecode 81.4 tok/s at 8Kmlx-serve 26.10.2 + DFlash2decode 80.6 tok/s at 8Kmlx-serve 26.10.2 · MTP head onlydecode 51.5 tok/s at 8Kllama.cpp b11569 · Q8_0 + MTP draftdecode 35.3 tok/s at 8K3full speed test at 32K and 128Kagent turn time at 32K / 128K, xhigh:mlx-dspark 0.20.3 + DFlash2 (baseline)17.0 / 25.7 s per agent turndecode 44.0 / 32.3 tok/sthe reference for the other twoSplash 1.3.1 + DFlash213.3 / 17.7 s per agent turndecode 54.7 / 49.8 tok/s0.78× / 0.69× the baselinemlx-serve 26.10.2 + DFlash219.9 / 44.7 s per agent turndecode 36.3 / 15.1 tok/s1.17× / 1.74× the baseline1fastest: Splash 1.3.1 + DFlash2Splash 1.3.1 + DFlash213.3 / 17.7 s per agent turndecode 54.7 / 49.8 tok/s0.78× / 0.69× the baseline

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.

Agent turn time at 32K and 128K

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).

Context
Thinking effort

32K context · agent turn time, shorter is better

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

SetupThinking effortAgent turn time (s)Warm TTFT, tool turn (s)Cold TTFT (s)Prefill (tok/s)Decode (tok/s)Cache hit, tool turnDraft / MTP acceptancePeak memory (GB)
mlx-dspark 0.20.3 + DFlash2 (baseline)xhigh17.05.4109.924844.00.96—43.3
mlx-dspark 0.20.3 + DFlash2 (baseline)medium13.55.5109.524963.80.96—41.5
Splash 1.3.1 + DFlash2xhigh13.33.976.335854.70.96—42.2
Splash 1.3.1 + DFlash2medium10.43.976.135879.30.96—42.0
mlx-serve 26.10.2 + DFlash2xhigh19.95.8109.025036.30.960.4659.3
mlx-serve 26.10.2 + DFlash2medium15.75.8108.425251.90.960.7963.0
Qwen3.8-Flash-Next · Swift 1.5 quant (our daily model)xhigh (reference)7.91.834.579184.60.960.7796.1
Qwen3.8-Flash-Next · ddalcu iQ quantxhigh (reference)8.11.834.579182.30.960.8496.0

128K context · agent turn time, shorter is better

mlx-dspark 0.20.3 + DFlash2 (baseline) effort: mediumnot measured—

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

SetupThinking effortAgent turn time (s)Warm TTFT, tool turn (s)Cold TTFT (s)Prefill (tok/s)Decode (tok/s)Cache hit, tool turnDraft / MTP acceptancePeak memory (GB)
mlx-dspark 0.20.3 + DFlash2 (baseline)xhigh25.79.8712.617632.30.99—55.4
mlx-dspark 0.20.3 + DFlash2 (baseline)mediumnot measured
Splash 1.3.1 + DFlash2xhigh17.77.4535.123549.80.99—53.8
Splash 1.3.1 + DFlash2medium15.97.4534.423560.50.99—53.5
mlx-serve 26.10.2 + DFlash2xhigh44.710.8717.817515.10.990.5470.4
mlx-serve 26.10.2 + DFlash2medium34.810.8716.817521.30.990.7769.5
Qwen3.8-Flash-Next · Swift 1.5 quant (our daily model)xhigh (reference)8.61.9165.575976.70.990.67111.2
Qwen3.8-Flash-Next · ddalcu iQ quantxhigh (reference)8.91.9165.476073.20.990.79110.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.

Run it yourself

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.

# 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 --offline
curl -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.

Hardware and software

Limitations

Raw data and reproduction

Internal 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).

CodePublic nameCampaign nameRuntimePackRevisionPortPhase
s27rmlx-dspark 0.20.3 + DFlash2 (baseline)mlx-dspark 0.20.3 + DFlash2mlx-dspark 0.20.3mlx-community 8bit815b83c08484P0
k27Splash 1.3.1 + DFlash2Splash 1.3.1 + DFlash2, KV int8Splash 1.3.1mlx-community 8bit (Hub cache) + local DFlash2 draft815b83c08585P1
o27q8oMLX 0.7.1 · oQ8e refit, 8-bitoMLX oQ8e refitoMLX 0.7.1.dev1Jundot oQ8e-mtp6c76ddfb8000P2
o27q4oMLX 0.7.1 · oQ4e refit, 4-bitoMLX oQ4e refitoMLX 0.7.1.dev1Jundot oQ4e-mtp49c1d6038000P2
r27dmlx-serve 26.10.2 + DFlash2mlx-serve 26.10.2 DFlash2 drafter only (--no-mtp)mlx-serve 26.10.2ddalcu 8bit7775059e11234P3
r27mmlx-serve 26.10.2 · MTP head onlymlx-serve 26.10.2 MTP head only (--no-drafter)mlx-serve 26.10.2ddalcu 8bit7775059e11234P8
w27dmlx-serve 26.10.2 · Swift 1.5 + DFlash2mlx-serve 26.10.2 Swift 1.5, DFlash2 drafter only (--no-mtp)mlx-serve 26.10.2scottlowry Swift-1.5 oQ4e-mtp881dc19111234P7
w27mmlx-serve 26.10.2 · Swift 1.5, MTP onlymlx-serve 26.10.2 Swift 1.5, MTP head only (--no-drafter)mlx-serve 26.10.2scottlowry Swift-1.5 oQ4e-mtp881dc19111234P8
o27swoMLX 0.7.1 · Swift 1.5, 4-bitoMLX Swift 1.5 oQ4eoMLX 0.7.1.dev1scottlowry Swift-1.5 oQ4e-mtp881dc1918000P4
l27llama.cpp b11569 · Q8_0 + MTP draftllama.cpp b11569 draft-mtpllama.cpp b11569ggml-org Q8_0 + mtp71bc7b628080P6
l27nllama.cpp b11569 · Q8_0, no speculationllama.cpp b11569 no specllama.cpp b11569ggml-org Q8_071bc7b628080P6
w1Qwen3.8-Flash-Next · Swift 1.5 quant (our daily model)Flash-Next reference (xhigh)mlx-serve 26.10.1
n2Qwen3.8-Flash-Next · ddalcu iQ quantFlash-Next reference (xhigh)mlx-serve 26.10.1

Raw gates from results/gates.env:

GateValueMeaning
P1_PASS1Splash (k27): T_turno ≤ 0.75 × s27r at 128K and gates
P1_BESTk27Best arm of P1
P3_PASS0mlx-serve 26.10.2: T_turno ≤ s27r
P7_PASSnot_runw27d at or under 1.03 × r27d on both bands
CAMPAIGN_DONE2026-10-14T06:35Z (stopped by user after P3; P7 cancelled)Campaign finished (UTC)
P5_GO0Driver gate: fastest arm within 1.25 × w1 on both bands (0 = none)

8K smokes (warm decode as in summarize_driver.py):

Setups not run (cut on 2026-10-13/14), by user scope:

Thinking-turn probe at 128K:

Run 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.