
Qwen3.8-27B benchmarks on Strix Halo - Q8_0 MTP: 21 t/s, 3x. ROCmFP4 MTP: 26 t/s
TL;DR: On a Strix Halo laptop (Radeon 8060S, 128 GB unified), MTP takes Qwen3.8-27B Q8_0 from 7.1 to 21.9 tok/s. The julianmb/q38rocm FP4 fork gives 26.7 tok/s.
Setup: LlamaStash driving stock llama.cpp b10503 (5112b9738) for ROCm and Vulkan, and the q38rocm fork (server build 246) for ROCmFP4, all built from git today. Greedy, 256 max tokens, wall-clock tok/s from the API usage numbers, best of 2 reps, on AC power. Everything launched through my tool LlamaStash (v0.2.0) so the flags are reproducible.
Qwen3.8-27B decode tok/s (code prompt / prose prompt):
| Build and model | MTP off | MTP on | Draft acceptance |
|---|---|---|---|
| ROCm + Q8_0 (29 GB) | 7.1 | 21.9 (draft 5) | 73% |
| Vulkan + Q8_0 (29 GB) | 7.4 | 21.3 (draft 5) | 73% |
| ROCmFP4 FAST (14.6 GB) | 12.5 | 26.7 (draft 6) | 86% |
Draft length sweep, code prompt tok/s:
| draft n | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|
| Q8_0 | 15.4 | 18.4 | 19.1 | 21.9 | 21.2 | 21.8 | 20.5 |
| FP4 | - | 23.3 | 24.9 | 25.0 | 26.7 | 25.6 | - |
Observations:
- MTP really makes a difference 2.5x to 3.1x on Q8_0, 2.1x on ROCmFP4, at 73-95% draft acceptance. The model verifies every token, so quality does not change on stock llama.cpp.
- Draft length of 5 works best. The backend default is 3; 5 was best for Q8_0 and 6 for FP4.
- FP4 is a double win if you can live with the quant. Half the memory (29 to 14.6 GB), 1.8x faster raw decode without MTP, and 26.7 tok/s with MTP, which is 3.8x my stock Q8_0 baseline.
- Engine choice barely matters for decode at this size. ROCm, Vulkan and the rocWMMA build all land within about 3%. Vulkan does lose roughly half its prompt processing throughput though, so ROCm stays my default.
- Stuff that did not help: KV cache q8_0 (helps at draft 3, slightly worse at 5), flash attention off (minus 5%).
- One caveat on the ROCmFP4 fork: completion lengths drifted a little across draft settings (152-158 tokens for the same prompt) while stock llama.cpp was stable at 164, so the fork's speculative path is not strictly greedy-identical yet.
LlamsStash defaults were used for all runs. The only flags set were to enable disable MTP ( --spec-type draft-mtp), set draft length (--spec-draft-n-max N) and the --fit-ctx 16384 flag was used to ensure the GPU layers were fully utilized.
The below are the default flags that was applied. Some sent by LlamaStash and others are llamacpp defaults.
| Flag | Value | Set by |
|---|---|---|
--host |
127.0.0.1 |
LlamaStash (loopback policy) |
--port |
41100 |
LlamaStash (auto-assigned) |
-m |
<model>.gguf absolute path |
LlamaStash (resolves the name ref) |
--mmproj |
mmproj-F16.gguf |
LlamaStash (auto-paired sidecar; Q8_0 rows only, FP4 has none) |
--jinja |
on | LlamaStash (chat template handling) |
--spec-type |
draft-mtp |
LlamaStash (translated from --mtp on) |
--spec-draft-n-max |
5 (Q8_0) / 6 (FP4 best) / omitted on default rows |
LlamaStash (--mtp-draft-n); llama.cpp default 3 when omitted |
--fit-ctx |
16384 |
LlamaStash (ctx knob = auto, fit hint) |
--cache-type-k / -v |
f16 (q8_0 on kvq8 variant rows) |
llama.cpp default except for kvq8 runs |
--flash-attn |
on (off on the fa-off variant row only) |
llama.cpp default except for fa-off runs |
--n_ctx (final) |
262144 per slot (native 256K) |
llama.cpp fitter |
--n_parallel |
4, kv_unified true |
llama.cpp (auto) |
--n_gpu_layers |
all (-1) |
llama.cpp fitter (llamastash knob was auto) |
--cache-type-k/v (final) |
f16 / f16 |
llama.cpp default |
--n_threads |
16 (of 32 logical) |
llama.cpp default |
--n_batch / --n_ubatch |
server defaults, not overridden | llama.cpp |
| spec internals (fork only) | n_min=0, p_min=0.75, backend_sampling=1 |
llama.cpp (q38rocm fork defaults) |
| draft context (fork only) | all GPU layers, f16 KV |
llama.cpp (fork) |