Qwen3.8-27B · GGUF IQ3_S · llama.cpp
Qwen3.8-27B GSQ IQ3_S GGUF on llama.cpp, 44.9 t/s warm, same model as EXL3 runs but 6.3× slower prefill.
Based on: https://huggingface.co/Greatjedi/Qwen3.8-27B-Intel-Arc-Tuned-GGUF
Third engine in the journal: llama.cpp on the same ASROCK B70 (card2/renderD129), same model family as runs #2/#3 (Qwen3.8-27B) but GGUF quant (IQ3_S GSQ "Intel Arc Tuned") with an embedded MTP draft head (Q4_K). Dual speculative decoding: ngram-mod + draft-mtp.
Status: awaiting launch, blocker: b70-exl3 currently holds port 9932 AND card2; stop it first.
Config
| Field | Value |
|---|---|
| Model | Greatjedi/Qwen3.8-27B-Intel-Arc-Tuned-GGUF → /models/Qwen3.8-27B-GSQ-RCO-IQ3_S-Intel-Arc-Tuned-MTP-Q4_K.gguf (IQ3_S ≈3.5 bpw, MTP draft head Q4_K embedded) |
| Inference platform | llama.cpp ghcr.io/ggml-org/llama.cpp:full-intel, SYCL/Level-Zero backend, single B70 (SYCL_DEVICE_FILTER=level_zero:0) |
| GPU env | GGML_SYCL_ENABLE_GRAPH=1, GGML_SYCL_F16=1, GGML_SYCL_ENABLE_COPY=1, ZES_ENABLE_SYSMAN=1, SYCL_USE_XMX=1, ZE_AFFINITY_MASK=0; SYCL_CACHE_PERSISTENT=0 (recompiles every start) |
| Port / host | your-LAN-IP:9932 |
| Context | --ctx-size 163840 (alternatives tried per comments: 262144/204256/196608/180000/131072) |
| Layers | --n-gpu-layers all, --fit-target 256 (256 MiB margin/device for auto-fit) |
| Flash attn | on |
| KV cache | --cache-type-k q4_0 --cache-type-v q4_0, --kv-unified --kv-offload (offload default-on, watch startup log for the actual split) |
| Speculative decoding | --spec-type ngram-mod,draft-mtp: ngram-mod n-match 24, n-min 1, n-max 3; draft-mtp n-max 3, p-min 0.5 |
| Prefill batching | --batch-size 16384 --ubatch-size 2048 |
| CPU | --threads 8 --threads-batch 12 (7900X = 12C); OMP_NUM_THREADS=12 |
| Prompt caching | --cache-prompt --cache-reuse 2048 --cache-ram 2048 (disk prompt cache, 2 GiB RAM budget, reuse chunks ≥2048 tokens via KV shifting) |
| Concurrency | --parallel 1, --n-predict -1 |
| Sampling defaults | temp 0.7, top-p 0.9, top-k 40, repeat-penalty 1.05 (last-n 256), presence 0.0, dry-penalty last-n 512 |
| Reasoning / tools | --reasoning on; tools: read_file, file_glob_search, grep_search, write_file, edit_file, get_info; --jinja + /models/qwen-sharp.jinja (from the model repo's chat_templates/) template; alias Qwen3.8-27B |
| Monitoring | --metrics --slots --warmup --perf |
Benchmark protocol note: server sampling defaults (temp 0.7, penalties) differ from runs #1–#3. For comparability, the client request overrides to greedy-equivalent: temperature=0, top_p=1.0, top_k=1, repeat_penalty=1.0, presence_penalty=0.0. Prompt + question identical (64,388 corpus tokens).
Startup stats
- 20.4 GB of 32 GB at startup (user), large headroom: ctx→262k (+~2 GiB q4_0 KV) and f16 KV quality variant both fit comfortably
- Model source:
Greatjedi/Qwen3.8-27B-Intel-Arc-Tuned-GGUF - SYCL kernels compiled at startup (
SYCL_CACHE_PERSISTENT=0), watch post-test logs for mid-inference re-JIT
Stats
| Metric | Cold (req #1) | Warm (APC hit) |
|---|---|---|
| Prompt tokens | 64,501 | 64,501 |
| TTFT (s) | client 85.74 / server prompt eval 63.64 s | client 23.45 / server prompt eval 0.35 s (only 4 tokens re-prefilled) |
| Prefill (tok/s) | 1,013.59 (server; no JIT warnings in log, real speed, not an artifact) | cache-served (cached_tokens 64,497/64,501) |
| Decode (tok/s, generated) | client 30.1 (1,026 tok / 34.14 s); server 22.81 incl. spec overhead | client 44.9 (1,406 tok / 31.30 s); server 25.87 incl. spec overhead |
| Completion tokens | 1,026 | 1,406 |
| E2E (s) | 119.88 (client) / 108.57 (server total) | 54.75 (client) / 54.67 (server total) |
| VRAM (GB) | 20.4 at start (user) | 20.9 peak (user) |
| Spec decode | draft accept 0.693 (580/837), mean len 2.61 | draft accept 0.759 (869/1145), mean len 2.87; cumulative 73.1 % |
Baselines to compare (same corpus): vLLM MoE run #1 warm decode 105.8 tok/s; EXL3 runs #2/#3 warm decode ~57 tok/s, prefill ~6,439 tok/s.
Findings
- Cold prefill is genuinely slow: 1,013.59 tok/s (server-measured, no JIT warnings in the log), 6.3× slower than EXL3/vLLM's ~6,439 tok/s on the same model family. Not a JIT artifact. Prime suspects:
--ubatch-size 2048too small for this hybrid model, and/or--kv-offloadpushing KV writes over PCIe. Both are A/B candidates. - Warm cache reuse works perfectly server-side: only 4 tokens re-prefilled in 351 ms (f_sim_best = 1.000). Yet the client saw TTFT 23.45 s, and cold shows the same constant gap (client TTFT 85.74 vs server prompt eval 63.64 ≈ +22 s; warm ≈ +23 s). A steady ~22–23 s first-token delivery delay independent of server work points at SSE/streaming buffering, not server compute. Follow up with
curl -Nraw timing. - Same-model engine A/B (dense Qwen3.8-27B): llama.cpp warm decode 44.9 vs EXL3 57.0 tok/s. EXL3 is ~27% faster on decode. (Run #1's 105.8 was the MoE A3B, not comparable.)
- Draft acceptance is strong: 69–76 % per request (mean len 2.61/2.87), better than EXL3's MTP (~51–54 %), partly because ngram-mod exploits repetitive prose.
- VRAM barely moves under load (20.4 → 20.9 GB for 65k tokens of q4_0 KV), huge headroom confirmed for ctx→262k and f16 KV.
- Serving in ~1 min after container start (vs EXL3's 3.7 min); SYCL graphs reused (167 cold / 381 warm).
- Tokenizer overhead: 64,501 prompt tokens here vs 64,388 on vLLM/exl3xpu (tool definitions + jinja template).
Recommendations
Quality variant
- KV dtype q4_0 → f16/bf16 (
--cache-type-k/v), quantized KV is the main long-context recall tax; ~2× KV VRAM (~+3–4 GiB at 163k, still fits in 32 GB). - Higher-quant GGUF (IQ4_XS/IQ5) if IQ3_S quality disappoints, fits with room on one B70.
- Draft KV already f16 (default), leave it.
Speed variant
- ubatch-size 2048 → 4096, prefill is the headline weakness (1,014 tok/s vs ~6,439 on other engines); bigger chunks are the first lever.
--no-kv-offloadA/B, offload is default-on; if KV writes are crossing PCIe during prefill, disabling it (everything fits on GPU) could close most of the prefill gap.- ctx-size 163840 → 262144, headroom confirmed: only +0.5 GB VRAM for 65k tokens of q4_0 KV (~8 B/token), so 262k ctx costs ~+2 GiB. No wall in sight.
SYCL_CACHE_PERSISTENT=0 → 1, sycl host volume already mapped but persistence off; recompiles every start. A/B after baseline.- Spec decode isolation, ngram-mod and draft-mtp both on (acceptance 69–76 %); A/B each alone to see net gain per type.
--spec-draft-p-min 0.5is conservative. - Follow-up: SSE first-token delay, constant ~22–23 s gap between server prompt-eval completion and client TTFT on both requests; verify with
curl -Nraw timing before trusting llama.cpp TTFT numbers.
Appendix: docker-compose (verbatim, as provided 2026-10-21)
services:
llamacpp:
container_name: b70
image: ghcr.io/ggml-org/llama.cpp:full-intel
restart: unless-stopped
ports:
- "9932:9932"
volumes:
- ./models:/models
- ./cache/llamacpp:/root/.cache/llamacpp:Z
- ./cache/sycl:/root/.cache/sycl:Z
- ./cache/huggingface:/root/.cache/huggingface:Z
devices:
- /dev/dri
group_add:
- "992"
environment:
GGML_SYCL_ENABLE_GRAPH: 1
UR_LOADER_USE_LEVEL_ZERO_V2: 0
ZE_AFFINITY_MASK: 0
SYCL_DEVICE_FILTER: level_zero:0
ONEAPI_DEVICE_SELECTOR: level_zero:0
SYCL_PI_LEVEL_ZERO_USE_IMMEDIATE_COMMANDLISTS: "1"
GGML_SYCL_ENABLE_COPY: "1"
GGML_SYCL_F16: "1"
ZES_ENABLE_SYSMAN: "1"
OMP_NUM_THREADS: "12"
HF_HOME: /root/.cache/huggingface
HUGGINGFACE_HUB_CACHE: /root/.cache/huggingface
LLAMA_CPP_CACHE: /root/.cache/llamacpp
SYCL_CACHE_PERSISTENT: "0"
SYCL_CACHE_PATH: /root/.cache/sycl
SYCL_USE_XMX: 1
command:
- --server
- --model
- /models/Qwen3.8-27B-GSQ-RCO-IQ3_S-Intel-Arc-Tuned-MTP-Q4_K.gguf
- --spec-type
- "ngram-mod,draft-mtp"
- --spec-ngram-mod-n-match
- "24"
- --spec-ngram-mod-n-min
- "1"
- --spec-ngram-mod-n-max
- "3"
- --spec-draft-n-max
- "3"
- --spec-draft-p-min
- "0.5"
- --jinja
- --chat-template-file
- /models/qwen-sharp.jinja
- --alias
- "Qwen3.8-27B"
- --host
- 0.0.0.0
- --port
- "9932"
- --n-gpu-layers
- "all"
- --flash-attn
- "on"
- --fit-target
- "256"
- --ctx-size
- "163840"
- --n-predict
- "-1"
- --temp
- "0.7"
- --top-p
- "0.9"
- --top-k
- "40"
- --repeat-penalty
- "1.05"
- --repeat-last-n
- "256"
- --presence-penalty
- "0.0"
- --dry-penalty-last-n
- "512"
- --threads
- "8"
- --threads-batch
- "12"
- --batch-size
- "16384"
- --ubatch-size
- "2048"
- --cors-origins
- "*"
- --cache-type-k
- "q4_0"
- --cache-type-v
- "q4_0"
- --cache-prompt
- --cache-reuse
- "2048"
- --cache-ram
- "2048"
- --parallel
- "1"
- --kv-unified
- --kv-offload
- --reasoning
- "on"
- --tools
- "read_file,file_glob_search,grep_search,write_file,edit_file,get_info"
- --metrics
- --slots
- --warmup
- --perf