Im ersten Teil habe ich vLLM auf meinen zwei RTX A6000 installiert und Qwen3.8-27B im vollen BF16 zum Laufen gebracht. Das funktionierte, aber im Alltag hat mich das Ergebnis nicht überzeugt. Beide Karten waren permanent belegt, nur um ein einziges Modell über Tensor Parallel zu bedienen, und beim agentischen Coden mit OpenCode fühlte sich das Decode-Tempo zäh an. Für lange Agenten-Sessions mit vielen Runden war mir das schlicht zu langsam.

Also habe ich mich auf die Suche nach Alternativen gemacht. Ziel war zweierlei. Erstens mehr Tempo beim Generieren für das agentische Arbeiten mit OpenCode. Zweitens wollte ich nicht dauerhaft beide GPUs für ein Modell reservieren, sondern eine Karte wieder frei bekommen, etwa für ComfyUI oder andere Aufgaben. In diesem Teil zeige ich, wie ich dahin gekommen bin. Über quantisierte Modelle, einen gemeinen Multi-GPU-Hänger ohne NVLink, einen sauberen Autostart per systemd und die Anbindung an OpenCode fürs agentische Arbeiten.

Eine quantisierte Version von Qwen 3.8 27B

Für Qwen3.8-27B liegen mehrere geprüfte INT4-Varianten auf HuggingFace. Alle rund 18 bis 20 GB statt der 52 bis 56 GB von BF16. Auf Ampere laufen die Gewichte durch die Marlin W4A16-Kernel. Die Decode-Gewinne kommen vom kleineren Lesen der Gewichte. Und der große Vorteil ist der Platz, den die kleineren Gewichte für den KV-Cache lassen.

Befehl: hf download Avesed/Qwen3.8-27B-INT4-W4A16

Hier kommt der Vorteil, der mir mit meinem Setup am meisten bringt. INT4 passt mit rund 18 GB bequem auf eine einzige A6000, mit reichlich Platz für den KV-Cache. Ich brauche also kein Tensor Parallel mehr. Damit fällt die langsame Kommunikation zwischen den Karten weg, die mich vorher so viel Mühe gekostet hat. Und die zweite Karte wird frei, etwa für ComfyUI.

Befehl: CUDA_VISIBLE_DEVICES=0 vllm serve Avesed/Qwen3.8-27B-INT4-W4A16 --max-model-len 131072 --enable-prefix-caching --enable-auto-tool-choice --tool-call-parser qwen3_coder --reasoning-parser qwen3 --trust-remote-code --default-chat-template-kwargs '{"reasoning_effort": "medium"}'

Wenn du noch mehr Tempo willst

Dann gibt es einen zweiten Hebel obendrauf. Einige INT4-Varianten behalten den MTP-Kopf des Modells und erlauben damit Speculative Decoding. Das verdoppelt den Decode-Durchsatz ungefähr, gemessen plus 98 Prozent. Die Variante Frozenlock/Qwen3.8-27B-int4-AutoRound ist genau darauf ausgelegt. Der genaue --speculative-config Aufruf steht auf deren Modellkarte (https://huggingface.co/Frozenlock/Qwen3.8-27B-int4-AutoRound).

Befehl: hf download Frozenlock/Qwen3.8-27B-int4-AutoRound

Ich habe das Speculative Decoding noch nicht selbst getestet. Den genauen Aufruf und die Konfiguration findest du auf der Modellkarte. Aber hier wie der Befehl aussehen sollte.

Befehl: CUDA_VISIBLE_DEVICES=0 vllm serve Frozenlock/Qwen3.8-27B-int4-AutoRound --quantization auto_round --max-model-len 131072 --kv-cache-dtype fp8 --enable-prefix-caching --enable-auto-tool-choice --tool-call-parser qwen3_coder --reasoning-parser qwen3 --trust-remote-code --speculative-config '{"method":"mtp","num_speculative_tokens":3}'

Zwei Karten ohne NVLink: der Start hängt

Das war bei mir der größte Brocken. Meine beiden A6000 haben keine NVLink-Brücke. Sie reden also über PCIe miteinander. Genau das führt beim Tensor-Parallel-Start zu einem Hänger, den man schwer deutet.

Das Bild sieht so aus. Die Gewichte laden sauber. Die CUDA-Graphen werden aufgenommen. Und dann bleibt es stehen. Beide Karten auf 100 Prozent GPU-Util, die Speicherbelegung wächst nicht weiter, und im Log wiederholt sich im Minutentakt eine Zeile.

No available shared memory broadcast block found in 60 seconds

Der Port 8000 geht nie auf. Der Server kommt also nie ganz hoch. Es ist kein Absturz, sondern ein stiller Deadlock bei der Kommunikation zwischen den Karten.

Zwei Dinge zusammen lösen das. Beide habe ich gebraucht.

Erstens die Umgebungsvariable, die den direkten P2P-Weg über PCIe abschaltet. Damit kommt der Start überhaupt bis zur Graph-Aufnahme.

Befehl: export NCCL_P2P_DISABLE=1

Zweitens, und das ist der eigentliche Fix, das Flag --disable-custom-all-reduce. vLLM nutzt für die Karten-zu-Karten-Summierung einen eigenen, optimierten Weg, der auf direktem P2P aufsetzt. Ohne NVLink hängt genau dieser Weg. Das Flag schaltet ihn ab und zwingt vLLM auf den stabilen NCCL-Pfad.

Der vollständige Startbefehl für zwei Karten ohne NVLink sieht damit so aus.

Befehl: vllm serve Qwen/Qwen3.8-27B --tensor-parallel-size 2 --max-model-len 131072 --disable-custom-all-reduce --reasoning-parser qwen3 --default-chat-template-kwargs '{"reasoning_effort": "medium"}'

Danach lief der Start bei mir sauber durch bis Application startup complete, und der Port 8000 nahm Anfragen an. Hast du NVLink zwischen den Karten, brauchst du beides nicht. Bei zwei A6000 auf reinem PCIe aber schon.

Cache und schnellere Starts

Der erste Start dauert. Danach geht es schnell. vLLM legt zwei Dinge dauerhaft ab. Beides ist ab Werk aktiv. Du musst nichts einstellen.

Zum einen die Modellgewichte. Sie liegen nach dem Download unter ~/.cache/huggingface. Beim nächsten Start lädt vLLM nichts neu. Es liest direkt aus dem Cache.

Zum anderen der Compile-Cache. vLLM übersetzt das Modell beim Start in schnelle Kernel. Das kostet beim ersten Mal Zeit. Das Ergebnis landet unter ~/.cache/vllm. Beim nächsten Start mit gleichem Modell, gleicher Konfiguration und gleicher GPU nutzt vLLM diesen Cache wieder. Der Start ist dann deutlich kürzer.

Wichtig ist nur, dass du den Cache nicht löschst. Ändert sich das Modell, die Konfiguration oder der Treiber, baut vLLM den Compile-Cache automatisch neu auf.

Cache auf ein schnelles Laufwerk auslagern

Ab Werk landet alles unter ~/.cache. Das liegt meist auf dem Systemlaufwerk. Bei mir ist das die langsamere Root-Platte. Die 55 GB Gewichte und der Compile-Cache gehören aber auf eine schnelle NVMe. Bei mir ist das /mnt/temp_03. Dort lege ich einen Ordner vLLM an. Bei Ollama regelt man so etwas über OLLAMA_MODELS. Bei vLLM geht es genauso, nur über zwei Variablen.

Zuerst lege ich den Zielordner an.

Befehl: mkdir -p /mnt/temp_03/vLLM

Die erste Variable lenkt den HuggingFace-Cache, also die Modellgewichte. Das ist das Gegenstück zum Ollama-Weg.

Befehl: export HF_HOME=/mnt/temp_03/vLLM/hf

Die zweite verschiebt den Compile-Cache von vLLM.

Befehl: export VLLM_CACHE_ROOT=/mnt/temp_03/vLLM/cache

Den Pfad /mnt/temp_03 ersetzt du durch deinen eigenen NVMe-Mountpunkt. Welche Laufwerke du hast, zeigt dir df -h.

Damit die Einstellung dauerhaft gilt, schreibe ich beide Zeilen in meine ~/.bashrc. Dann sind sie in jeder neuen Shell gesetzt.

Befehl: echo 'export HF_HOME=/mnt/temp_03/vLLM/hf' >> ~/.bashrc

Befehl: echo 'export VLLM_CACHE_ROOT=/mnt/temp_03/vLLM/cache' >> ~/.bashrc

Ein wichtiger Punkt für den systemd-Dienst. Der liest deine bashrc nicht. Dort trägst du die Variablen direkt in die Unit ein. Wie das geht, steht weiter unten im Autostart-Abschnitt.

vLLM stoppen und deaktivieren

Läuft vLLM im Vordergrund, stoppe ich mit Strg und C. Läuft es im Hintergrund, beende ich den Prozess gezielt.

Befehl: pkill -f "vllm serve"

Danach verlasse ich die Umgebung.

Befehl: deactivate

Zum Schluss prüfe ich, ob der Speicher wieder frei ist.

Befehl: nvidia-smi

Jetzt kann ich Ollama oder andere Dienste wieder starten. Der Platz auf den GPUs gehört ihnen dann wieder.

vLLM automatisch starten, statt Ollama

Ich will, dass vLLM beim Booten hochkommt. Und zwar an der Stelle, an der bisher Ollama lief. Dafür brauche ich keinen Docker-Container. Ein systemd-Dienst reicht. Das ist der native Weg und am einfachsten auf einem einzelnen Server.

Ein Punkt vorweg. vLLM und Ollama teilen sich die GPUs. Beide gleichzeitig wäre ein Kampf um den Speicher. Also lasse ich vLLM starten und nehme Ollama aus dem Autostart.

Zuerst lege ich die Unit an.

Befehl: sudo nano /etc/systemd/system/vllm.service

Inhalt der Datei. Den Benutzer und den Pfad passe ich an mein System an.

[Unit]
Description=vLLM Qwen3.8-27B
After=network-online.target
Wants=network-online.target

[Service]
User=DEINUSER
WorkingDirectory=/home/DEINUSER
Environment=HF_HOME=/mnt/temp_03/vLLM/hf
Environment=VLLM_CACHE_ROOT=/mnt/temp_03/vLLM/cache
Environment=HF_HUB_DISABLE_XET=1
Environment=NCCL_P2P_DISABLE=1
Environment=PATH=/home/DEINUSER/vllm-env/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=/home/DEINUSER/vllm-env/bin/vllm serve Qwen/Qwen3.8-27B --tensor-parallel-size 2 --max-model-len 131072 --disable-custom-all-reduce --enable-prefix-caching --enable-auto-tool-choice --tool-call-parser qwen3_coder --reasoning-parser qwen3 --default-chat-template-kwargs '{"reasoning_effort": "medium"}'
Restart=on-failure
TimeoutStartSec=1800

[Install]
WantedBy=multi-user.target

Diese Unit enthält alle Lehren aus meinem Einrichtungsmarathon. Kurz erklärt:

  • Die Zeilen HF_HOME und VLLM_CACHE_ROOT legen Modell und Compile-Cache auf die schnelle NVMe. Den Pfad passt du an.
  • HF_HUB_DISABLE_XET nutzt den zuverlässigen Downloadweg.
  • NCCL_P2P_DISABLE und das Flag disable-custom-all-reduce in der ExecStart-Zeile lösen den Multi-GPU-Hänger ohne NVLink. Ohne beides startet der Dienst nicht.
  • Die PATH-Zeile sorgt dafür, dass der Dienst die Werkzeuge aus dem venv findet, allen voran ninja.
  • TimeoutStartSec=1800 gibt dem ersten Start Zeit. Sonst hält systemd ihn nach 90 Sekunden fälschlich für gescheitert und killt ihn mitten im Kompilieren.

Brauchst du die Cache-Auslagerung nicht, lässt du die Zeilen HF_HOME und VLLM_CACHE_ROOT weg. Hast du NVLink zwischen den Karten, kannst du NCCL_P2P_DISABLE und disable-custom-all-reduce weglassen.

Ein Tipp zur Reihenfolge. Starte vLLM einmal von Hand, bevor du den Dienst aktivierst. So sind Gewichte und Compile-Cache schon da. Der erste automatische Start ist dann schnell und lädt nicht erst 55 GB.

Dann aktiviere ich den Dienst. So startet er sofort und künftig bei jedem Boot.

Befehl: sudo systemctl daemon-reload

Befehl: sudo systemctl enable --now vllm

Jetzt nehme ich Ollama aus dem Autostart. Der Dienst läuft dann nicht mehr automatisch beim Booten mit.

Befehl: sudo systemctl disable --now ollama

Damit übernimmt vLLM den Platz von Ollama. Den Status prüfe ich bei Bedarf so.

Befehl: systemctl status vllm

Und die Logs sehe ich live mit journalctl. Das ist praktisch, um den Start zu beobachten.

Befehl: journalctl -u vllm -f

Stoppen und wieder aus dem Autostart nehmen geht genauso einfach.

Befehl: sudo systemctl stop vllm

Befehl: sudo systemctl disable vllm

Alternative: vLLM als Docker-Container

Man kann vLLM auch als Container betreiben. Es gibt ein offizielles Image, vllm/vllm-openai. Der Reiz ist klar. Der Container startet immer gleich und lässt sich sauber neu aufsetzen.

Für einen einzelnen Server ist der Aufwand aber höher als der Nutzen. Du brauchst dafür das NVIDIA Container Toolkit, damit der Container auf die GPUs kommt. Und du musst die Caches als Volume einhängen. Sonst lädt der Container das Modell bei jedem Neustart neu und übersetzt es neu.

Wenn du diesen Weg gehen willst, sieht der Start etwa so aus.

Befehl: docker run --gpus all -v ~/.cache/huggingface:/root/.cache/huggingface -v ~/.cache/vllm:/root/.cache/vllm -p 8000:8000 vllm/vllm-openai --model Qwen/Qwen3.8-27B --tensor-parallel-size 2 --max-model-len 131072 --reasoning-parser qwen3 --default-chat-template-kwargs '{"reasoning_effort": "medium"}'

Wichtig sind die beiden Volumes. Das erste hält die Modellgewichte. Das zweite den Compile-Cache. Ohne sie verlierst du genau den Vorteil aus dem Cache-Abschnitt oben.

Für den Dauerbetrieb auf einem Rechner bleibe ich aber bei systemd. Es ist schlanker und braucht das Container Toolkit nicht.

Reasoning steuern

Das Modell denkt gern lange. Ab Werk auf der höchsten Stufe. Das kostet Zeit. Gerade bei einfachen Aufgaben.

Es gibt drei Stufen. low, medium und xhigh. Der Default ist xhigh. Über den Serverstart oben habe ich schon global auf medium gestellt. Das spart spürbar Zeit. Und kostet kaum Qualität.

Für eine einzelne Anfrage überschreibe ich den Default direkt im Request. Dazu setze ich {"chat_template_kwargs": {"reasoning_effort": "xhigh"}}.

Ganz ohne Denken geht auch. Dann nutze ich {"chat_template_kwargs": {"enable_thinking": false}}.

Kleiner Hinweis am Rande. Unter Ollama funktionieren diese Stufen nicht. Ollama ersetzt das Template des Modells. vLLM behält es. Deshalb ist vLLM hier klar im Vorteil.

vLLM für agentisches Arbeiten

Ich binde vLLM an OpenCode an. Dafür braucht der Server zwei Dinge extra. Tool Calling und Prefix Caching.

Tool Calling ist Pflicht. Sonst kann das Modell keine Funktionsaufrufe zurückgeben. Ein Agent lebt aber genau davon.

Prefix Caching ist der große Hebel beim Tempo. Ein Agent schickt bei jedem Schritt die ganze Historie erneut. Der vordere Teil ändert sich kaum. vLLM verwendet den bereits berechneten Cache wieder. Das spart pro Schritt viel Zeit. Hier hilft auch großer Speicher. Mehr Platz heißt, dass mehr Cache erhalten bleibt.

Der Startbefehl für den Agentenbetrieb sieht so aus.

Befehl: vllm serve Qwen/Qwen3.8-27B --tensor-parallel-size 2 --max-model-len 131072 --enable-prefix-caching --enable-auto-tool-choice --tool-call-parser qwen3_coder --reasoning-parser qwen3 --default-chat-template-kwargs '{"reasoning_effort": "medium"}'

Für den Agenten ist medium oder sogar low oft besser. Ein Agent macht viele Runden. Kurze Runden halten die Session flott.

Läufst du auf zwei Karten ohne NVLink, ergänze auch hier --disable-custom-all-reduce und setze NCCL_P2P_DISABLE=1, wie im NVLink-Abschnitt beschrieben.

Den Server testen

Läuft der Server, teste ich mit einem einfachen Aufruf.

Befehl: curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -d '{"model": "Qwen/Qwen3.8-27B", "messages": [{"role": "user", "content": "Nenne drei Primzahlen über 100."}]}'

Kommt eine saubere Antwort zurück, steht der Server. vLLM bietet eine OpenAI-kompatible API. OpenCode und andere Clients binden sich also leicht an.

Fazit

vLLM auf zwei A6000 ist schnell eingerichtet. Ein Stolperstein war bei mir der Treiber. Das aktuelle vLLM will CUDA 13. Also habe ich den Treiber auf 580 gehoben. Danach lief alles glatt.

Der größte Brocken war aber der fehlende NVLink zwischen meinen Karten. Ohne NCCL_P2P_DISABLE=1 und das Flag --disable-custom-all-reduce hängt der Start beim Zusammenschalten der beiden GPUs. Wer zwei A6000 auf reinem PCIe fährt, sollte das von Anfang an setzen. Das spart die halbe Nacht, die es mich gekostet hat.

Der wichtigste Griff ist die Wahl des Modells. Auf Ampere gehört BF16 auf die Karten. Nicht die quantisierten Varianten für neuere Hardware.

Der zweite Griff ist der Speicher. Erst andere Dienste entladen. Dann startet vLLM ohne Ärger. Und wer wie ich einen Agenten anbinden will, aktiviert Tool Calling und Prefix Caching.

vLLM läuft bei mir jetzt als systemd-Dienst und übernimmt den Platz von Ollama. Dank Cache sind die Neustarts schnell. Kein Docker nötig.

Für mich ist die feine Reasoning-Steuerung der eigentliche Gewinn. Ich drehe die Denktiefe genau so weit, wie die Aufgabe es braucht. Das macht den lokalen Betrieb im Alltag richtig angenehm.