Skip to the content.

Qwen3.6-27B on Apple M3 Pro — the llama.cpp parity bar (witnessed)

Goal lane: “complete and prove out working end to end in chat Qwen3.6-27B on this gpu/cpu setup; reach performance parity with llama.cpp (if it can even run it); hybrid cpu/gpu.” This doc records the llama.cpp reference — the answer to “can it even run it” and the speed bar fak’s own engine targets. As of 2026-06-19, fak also has a pure in-kernel Qwen3.6-27B GGUF->Q8 smoke with first-token greedy parity; that row is still not a speed-parity claim.

Hardware

Apple M3 Pro, 12 CPU cores (6P+6E), 18-core GPU, 36 GB unified memory, Metal 4. Native go1.26 (GOTOOLCHAIN=auto). darwin/arm64.

The model is real, new, and architecturally radical

Qwen/Qwen3.6-27B (HF config.json: Qwen3_5ForConditionalGeneration, model_type: qwen3_5; GGUF arch string qwen35). It is not a standard transformer:

Can it even run it? — YES, on llama.cpp b9707 (Metal)

The GDN operators are brand-new; the stock Homebrew llama.cpp (b8200) has no gated_delta_net / ssm_conv kernels and cannot load qwen35. The current release b9707 ships them (ggml_metal_kargs_gated_delta_net, ssm_conv, src/models/qwen3next.cpp, llama-memory-recurrent.cpp in libggml/libllama). Installed locally (no sudo) at ~/.local/llamacpp-b9707/.

Quant: Qwen3.6-27B.q4_k_m.gguf (15.4 GB) from AaryanK/Qwen3.6-27B-GGUF.

It generates coherent chat. Prompt (ChatML): “In one short paragraph, explain what makes the Apple M3 Pro good for local LLM inference.” → model opened a <think> block and correctly began reasoning about unified memory architecture.

Measured results (q4_k_m, 36 GB, this M3 Pro)

Backend Prefill (tok/s) Decode (tok/s) Peak RSS
Metal, full offload (-ngl 99) 51.55 7.29 ~24.5 GB
CPU only (-ngl 0, -t 6) 20.12 6.48 24.5 GB
fak in-kernel GGUF->Q8, cached GDN 0.5 0.1 25.8 GB
fak resident-Q4_K Metal, warm post-#1085 2.6 @P=27 / 7.3 @P=940 (cold first 0.5) 1.2 (2026-06-26 decode; not remeasured post-#1085) ~26 GB
Metal speedup 2.56× 1.13×

(llama.cpp common_perf_print; Metal: load 0.98 s. CPU peak RSS from /usr/bin/time -l. GGUF->Q8 fak row: cmd/fakchat --gguf ... --tokenizer ... --prompt "Say OK." --max-new 1, load 75.51 s, prefill 22 tokens in 40.62 s, one cached decode token in 16.25 s, peak RSS 25,785,204,736 bytes. Resident-Q4_K Metal fak row: owner-witnessed in #63 and archived at experiments/qwen36/metal-fak-q4k-post1085-m3pro-20260628.json; raw prompt strings were not present in this checkout, so the artifact records the tokenized prompt lengths rather than claiming a byte-for-byte rerun command.)

Which fak Qwen3.6-27B decode number is which (reconciliation)

Several fak Qwen3.6-27B decode numbers circulate, and a reader landing on any one doc cannot tell which is “the” number. This is the single table that reconciles them: they are different paths on different hardware, not three rival claims for the same configuration. Every row is single-stream, batch=1, on this M3 Pro, and each value is labelled with where it is cited.

fak decode path Decode (tok/s) What it measures Cited at (path)
GGUF→Q8 cached (CPU) 0.1 one cached decode token through the GGUF→Q8 round-trip path the Measured results table above; BENCHMARK-AUTHORITY.md (“Qwen3.6-27B Q8 decode”); docs/benchmarks/FAK-NATIVE-QWEN35-RESULTS.md
resident-q4k microbench (CPU) 0.9 raw q4_k blocks stay resident (no Q8 round-trip), scalar-f32 GEMV — the model-ladder rung-4b refresh the fak-native status bullet below (“decode 0.9 tok/s … ~9× the Q8 path’s 0.1”); artifact experiments/model-ladder/qwen36-resident-q4k-parity-20260619.json ("decode_tok_s": 0.9); docs/notes/MACBOOK-SERVE-AND-AGENTIC-BENCH-2026-06-24.md
resident-Q4_K Metal (GPU, -tags fakmetal FAK_Q4K=1 FAK_METAL=1) 1.2 the int8-SDOT Metal decode GEMV path; bit-correct (GEMV cosine 1.0) but launch-bound (~336 command-buffer GEMVs/token). Prefill on the same lane refreshed post-#1085 to 2.6 tok/s @P=27 and 7.3 tok/s @P=940 warm; decode is carried forward until #67 remeasures resident-forward decode. BENCHMARK-AUTHORITY.md (“Qwen3.6-27B fak Metal Q4_K”); experiments/qwen36/metal-fak-q4k-post1085-m3pro-20260628.json; docs/notes/MAC-QWEN36-27B-Q4K-METAL-PERF-DIAGNOSIS-2026-06-26.md

So “fak Qwen3.6-27B decode” is 0.1 → 0.9 → 1.2 tok/s along the GGUF→Q8-cached → resident-q4k-CPU → resident-Q4_K-Metal progression — one M3 Pro, all single-stream. It is a measured progression toward the 7.29 tok/s llama.cpp-Metal bar, not a self-contradiction.

Different axis — served throughput (NOT the kernel rows above). fak’s 8-GPU served Qwen3.6-27B figures (single-stream ≈59–93 tok/s, batched peak ≈820–1085 completion tok/s) come from SGLang-serves + fak-adjudicates on 8-GPU datacenter server/GPU server, not fak’s own M3 Pro engine; they live in BENCHMARK-AUTHORITY.md (the two “8-GPU SGLang serving” rows) and docs/benchmarks/QWEN36-27B-GPU-SERVER-RESULTS.md. Do not quote them on the same line as the single-stream kernel rows above.

On the “1.6–1.8 tok/s served-warm” figure. An earlier migrated note cited a “Warm decode ≈ 1.6–1.8 tok/s” from a FAK-NATIVE-CHAT-RESULTS.md that predates the v0.30.0 public squash and is not tracked in this tree; no committed artifact reproduces 1.6–1.8 tok/s for this model. It is therefore not asserted here — the witnessed single-stream figures are the three rows above.

What the numbers say — and why “hybrid CPU/GPU” is the right design

fak-native status against this bar

Witnessed 2026-06-18 on the M3 Pro; fak row refreshed 2026-06-19. llama.cpp numbers are llama.cpp’s own perf counters.