Sprache auswählen

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:

MTP-Fix für llama.cpp →

Session-Slot-Router →

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.

ModellTyp / RolleTempoTool-CallsFazit
Qwen 3.8 (27B)Dense, Hauptmodell30 t/ssehr zuverlässigSehr 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, MTPtopOKEinfache, kurze geführte Aufgaben laufen sauber; für größere, längere selbständige Aufgaben unzuverlässig.
Qwen3-Coder-30B-A3B-MTPMoE, MTPtop, aber schwächerOKWie Qwen3.6, bloß noch etwas schwächer.
Gemma 4 26B A4B (MoE, MTP)MoE–instabilInstabil und schwach – rechnet nicht einmal 17+25.
North-Mini-Code-70B-A10B-MTPMoE––Läuft nicht – der GPU-Speicher reicht dafür nicht.
Ministral-3-14B-MTPDense––Zu schwach – Hermes-CLI wird nicht ausgeführt.
K2-Horizon-MoVA-36B-A4BMoEgutLoop-FehlerQualitä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-LLM60–100 t/s, MTP-Akzeptanz 0,75–0,83sehr zuverlässigMein Fast-LLM und die Vergleichsbasis für die Speed-Tests.
Xing4.0-29B-A4B-1T-MTPMoE, MTPlangsamunzuverlässigRegression 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" \

1000 Zeichen übrig