Tutorial
Speedup für lokale KI: Qwen & llama.cpp, wie sie wirklich schnell laufen
Ein lokales System mit AMD R9700, Qwen 3.8 und zwei Agent-Harnesses – und die beiden Fixes, die es endlich flüssig machen.
Zwei Probleme machen das Tempo kaputt: Ein Bug in llama.cpp verliert den MTP-Träger beim Cache-Checkpoint – er tritt auch ohne Parallelität auf. Und wenn pi und Hermes parallel laufen, vergleicht llama.cpp die Anfragen im Prompt-Cache manchmal falsch: Es erkennt nicht, dass der Prompt schon im Cache liegt, und verarbeitet ihn komplett neu. Genau dafür gibt es die beiden Begleit-Artikel:
Das Problem in Kürze
Qwen 3.8 (27B) läuft auf der R9700 ohne Weiteres – aber zwei Dinge bremsen das System:
- Der MTP-Bug: Beim Speichern von Checkpoints verliert llama.cpp den MTP-Träger des Prompt-Caches – die Multi-Token-Prediction schlägt danach still fehl. Der Fehler ist unabhängig von der Last und tritt auch bei einer einzelnen, solo laufenden Anfrage auf.
- Das Parallelitäts-Problem: Sobald zwei Agent-Harnesses (pi und Hermes) gleichzeitig gegen denselben llama-server laufen, vergleicht llama.cpp die neuen Anfragen im Prompt-Cache manchmal nicht korrekt – es kapiert in dem Moment nicht, dass der Prompt bereits im Cache liegt, und verarbeitet ihn vollständig neu. Genau hier setzt der Session-Slot-Router an: Er weist jeder Anfrage vor dem Cache-Vergleich einen eindeutigen Session-Slot zu, damit der Cache-Treffer sicher ist und kein erneutes Prompt-Processing stattfindet.
Beide Probleme brauchen jeweils ihre eigene Lösung – die steht in den beiden Begleit-Artikeln: dem MTP-Fix für llama.cpp und dem Session-Slot-Router.
Mein Setup
- GPU: AMD Radeon AI PRO R9700 – weil Nvidia-Karten unbezahlbar sind
- Harnesses: pi und Hermes
- Haupt-LLM: Qwen 3.8 (27B, Q4_K_S, 150k Kontext)
- Fast-LLM: Tiel Coder 1.5 – ein MoE-Modell für schnelle Aufgaben
- Server: llama-server als lokale OpenAI-kompatible API
Was die Fixes bringen
Mit stabilem MTP und sauber vorab zugewiesenen Sessions kommt das System auf, was die Hardware hergibt: Der Cache hält, was er verspricht, auch bei Checkpoint-Restores – und die beiden Harnesses laufen parallel, ohne dass einer die Arbeit des anderen erneut ausführen muss. Dieser Artikel bündelt das Ganze; die Fixes selbst stehen in den beiden Begleit-Artikeln.
Testergebnisse
Alle Modelle auf derselben Hardware (R9700, llama-server, Hermes) getestet. „Haupt" und „Fast" markieren meine aktuelle Wahl.
| Modell | Typ / Rolle | Tempo | Tool-Calls | Fazit |
|---|---|---|---|---|
| Qwen 3.8 (27B) | Dense, Hauptmodell | 30 t/s | sehr zuverlässig | Sehr intelligent durch sehr viel Thinking; trägt das System im Alltag. |
| Qwen3.6-35B-A3B-MTP (UD-Q4_K_XL & FP8-Q4_K_M) | MoE, MTP | top | OK | Einfache, kurze geführte Aufgaben laufen sauber; für größere, längere selbständige Aufgaben unzuverlässig. |
| Qwen3-Coder-30B-A3B-MTP | MoE, MTP | top, aber schwächer | OK | Wie Qwen3.6, bloß noch etwas schwächer. |
| Gemma 4 26B A4B (MoE, MTP) | MoE | – | instabil | Instabil und schwach – rechnet nicht einmal 17+25. |
| North-Mini-Code-70B-A10B-MTP | MoE | – | – | Läuft nicht – der GPU-Speicher reicht dafür nicht. |
| Ministral-3-14B-MTP | Dense | – | – | Zu schwach – Hermes-CLI wird nicht ausgeführt. |
| K2-Horizon-MoVA-36B-A4B | MoE | gut | Loop-Fehler | Qualität ähnlich wie Tiel-Coder, evtl. etwas besser; aber KV-Cache ~8× so groß, Tool-Call-Loop, 74k Kontext nicht erreichbar. |
| Tiel-Coder-35B-A3B (UD-Q4_K_XL) | MoE, MTP, Fast-LLM | 60–100 t/s, MTP-Akzeptanz 0,75–0,83 | sehr zuverlässig | Mein Fast-LLM und die Vergleichsbasis für die Speed-Tests. |
| Xing4.0-29B-A4B-1T-MTP | MoE, MTP | langsam | unzuverlässig | Regression OK, aber 57 s für 122 Tokens; gibt nach ein paar Antworten Hieroglyphen aus; Pi- und Tool-Tests unzuverlässig. |
llama-server-Startskript (Thinking & MTP)
#!/usr/bin/env bash
set -euo pipefail
model_name=Qwen3.8-27B-UD-Q4_K_S
draft_file=mtp-Qwen3.8-27B-Q4_0.gguf
proj_file=mmproj-F16.gguf
# ######################### parallel #########################
if [[ $# -ne 1 || ! "$1" =~ ^[1-9][0-9]*$ ]]; then
echo "Verwendung: $0 <parallel>" >&2
exit 2
fi
parallel="$1"
if [[ "$parallel" -eq 1 ]]; then
ctx_single=262144
elif [[ "$parallel" -eq 2 ]]; then
ctx_single=150000
else
echo "Erlaubt sind nur 1 oder 2 als <parallel>." >&2
exit 2
fi
# ######################### path #########################
model_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
model_file="$model_name.gguf"
model_path="$model_dir/$model_file"
if [[ -n "$proj_file" ]]; then
proj_path="$model_dir/$proj_file"
else
proj_path=""
fi
if [[ -n "$draft_file" ]]; then
draft_path="$model_dir/$draft_file"
else
draft_path=""
fi
# ######################### start #########################
nohup llama-server \
--model "$model_path" \
--alias "$model_name" \
--mmproj "$proj_path" \
--spec-draft-model "$draft_path" \
--spec-type draft-mtp \
--spec-draft-n-max 4 \
--spec-draft-ngl 999 \
--spec-draft-type-k q8_0 \
--spec-draft-type-v q8_0 \
--host 0.0.0.0 \
--port 8080 \
--parallel "$parallel" \
--kv-unified-per-slot "$ctx_single" \
--kv-unified \
--no-cache-idle-slots \
--n-predict 32768 \
--gpu-layers 999 \
--cache-type-k q8_0 \
--cache-type-v q8_0 \
--flash-attn auto \
--temperature 1.0 \
--top-p 0.95 \
--top-k 20 \
--min-p 0.0 \
--presence-penalty 0.0 \
--repeat-penalty 1.0 \
--jinja \
--reasoning-preserve \
--reasoning-budget -1 \
--verbosity 4 \
> "$model_dir/llama-server.log" 2>&1 &
echo "llama-server gestartet: PID $!"
# --chat-template-file "$model_dir/chat_template(sharp).jinja" \
Bericht
Meine Kommentare