#9 · Drei Modelle, acht GPUs: Wie wir 192 GB VRAM sinnvoll aufteilen
Bei lokaler Inferenz dreht sich vieles um eine Frage: Wie bekommen wir dieses eine große Modell zum Laufen? Ein Modell, alle acht GPUs, TP=8, fertig. Für das erste Foto vom laufenden Server ist das eine gute Geschichte.
Unser Alltag sieht anders aus. Für schwere Coding-Aufgaben brauchen wir ein großes Modell, für interaktive Anfragen ein schnelles und für Office, Sprache oder Bildverarbeitung womöglich noch ein drittes. Damit wird aus der Frage, ob ein Modell irgendwie in 192 GB VRAM passt, eine Aufteilungsaufgabe: Welche Karten bekommt welches Modell, wie viel Kontext bleibt dabei übrig und welche Kombination übersteht mehr als einen kurzen Testlauf?
Dieser Artikel handelt deshalb von dem vermutlich unspektakulärsten, betrieblich aber nützlichsten Teil des Rigs: der Speicherbuchhaltung. Alle Zahlen stammen aus Messungen auf unserem System. Hinzu kamen zwei Hardwarefehler, die uns bei der Diagnose auf sehr unterschiedliche Weise beschäftigten: eine überhitzte Karte, deren Kerntemperatur völlig harmlos aussah, und ein Netzwerkkabel, das monatelang überzeugend den Gigabit-Link spielte.
Das Rig in Kurzform
Unser lokales LLM-Rig besteht aus sieben RTX 3090 und einer RTX 3090 Ti. Für den Multimodellbetrieb sind vor allem diese Eckdaten relevant:
- 8x Ampere mit je 24 GB, zusammen rund 192 GB VRAM. Alle Karten sind SM 8.6 und beherrschen daher kein hardwareseitiges FP8 oder FP4; bei INT4 kommt lediglich die Dekomprimierung der Gewichte durch Marlin zum Einsatz.
- EPYC 7443P, 256 GB DDR4 und ein Supermicro H12SSL-i, ohne NVLink und mit vier PCIe-Root-Complexes in den Domains
00,40,80undc0. - Der proprietäre Treiber
nvidia-driver-580in Version580.126.20. P2P haben wir bewusst abgeschaltet, weil es bei GeForce-Karten über getrennte Root-Complexes in unserem Aufbau ohnehin nur Nullen zurückliefert.
Die 192 GB bilden keinen gemeinsamen Speicherblock. Es sind acht physisch getrennte Bereiche zu je 24 GB. Jedes Modell erhält eine feste Gruppe von Karten und muss innerhalb des VRAM-Budgets jeder einzelnen Karte auskommen.
Device-ID und Steckplatz sind nicht dasselbe
Die Angabe --gpus '"device=N"' in Docker bezeichnet die CUDA-Device-ID, nicht den physischen Steckplatz. Wer beides verwechselt, untersucht bei einem thermischen Problem sehr zuverlässig die falsche Karte. Für unser Rig gilt diese Zuordnung:
| Device | PCI-BDF | Steckplatz | Karte |
|---|---|---|---|
| 0 | 01:00.0 | SLOT6 | 3090 |
| 1 | 41:00.0 | SLOT2 | 3090 |
| 2 | 42:00.0 | SLOT4 | 3090 |
| 3 | 81:00.0 | SLOT7 | 3090 |
| 4 | 82:00.0 | SLOT1bif | 3090 Ti |
| 5 | 83:00.0 | SLOT1bif | 3090 |
| 6 | c1:00.0 | SLOT3 | 3090 |
| 7 | c2:00.0 | SLOT5 | 3090 |
Besonders wichtig wurde für uns die erste Zeile: Device 0 steckt in SLOT6. Diese Karte kommt später noch einmal vor.
Laguna bekommt vier GPUs, vier bleiben verfügbar
Unsere Zielaufteilung ließ sich leicht formulieren: Das große Hauptmodell läuft auf vier GPUs, die übrigen vier stehen für ein oder zwei kleinere Modelle bereit.
Das große Modell ist Poolside Laguna S 2.1 INT4, unser Daily Driver für Coding-Aufgaben. Es läuft mit TP=4 auf den Devices 3, 4, 6 und 7 und ist auf Port 8005 erreichbar. Diese vier Karten tragen Laguna mit vollem Kontext von 262.144 Tokens, BF16-KV und einer brauchbaren KV-Reserve. Zugleich bleibt Device 0 in SLOT6 unbelegt, also genau die Karte, deren Kühlung unter Dauerlast problematisch ist.
Für Laguna haben wir folgende Werte gemessen:
| Topologie | Kontext | kurzer Decode | Decode bei 100k aktivem Kontext | KV-Kapazität |
|---|---|---|---|---|
| TP=4, BF16-KV, Utilization 0,96 | 262144 | 109,1 tok/s | 85,6 tok/s | 394.028 Tokens (1,50x bei 262k) |
| TP=4, FP8-KV, Utilization 0,92 | 262144 | 105,8 tok/s | 97,2 tok/s | 377.571 Tokens (1,44x bei 262k) |
BF16-KV war beim kurzen Decode um 3,1 Prozent schneller als FP8-KV, fiel bei einem aktiven Kontext von 100.000 Tokens jedoch von 109,1 auf 85,6 tok/s und damit hinter die 97,2 tok/s der FP8-Variante zurück. Außerdem blieben pro GPU nur rund 0,85 GiB Reserve. Für den produktiven Betrieb verwendeten wir trotzdem BF16-KV, weil FP8-KV auf diesem Ampere-Stack eigene und wesentlich unangenehmere Grenzen hat.
Der vollständige Startbefehl für genau diese vier GPUs lautet:
docker rm -f laguna_s21_int4 2>/dev/null || true
docker run -d --name laguna_s21_int4 --entrypoint vllm \
--gpus '"device=3,4,6,7"' --ipc host --shm-size 64g \
-e NVIDIA_DISABLE_REQUIRE=1 -e CUDA_DEVICE_ORDER=PCI_BUS_ID \
-e NCCL_P2P_DISABLE=1 -e NCCL_IB_DISABLE=1 -e NCCL_CUMEM_ENABLE=0 \
-e VLLM_ALLREDUCE_USE_SYMM_MEM=0 -e VLLM_WORKER_MULTIPROC_METHOD=spawn \
-e VLLM_ENABLE_CUDA_COMPATIBILITY=0 \
-v /bigData/hf-cache/Laguna-S-2.1-INT4:/models/Laguna-S-2.1-INT4 \
-v /bigData/vllm/data/torch-extensions-v024:/root/.cache/torch_extensions \
-v /bigData/vllm/data/triton-v024:/root/.triton \
-v /bigData/vllm/data/cuda-cache-v024:/root/.cache/cuda \
-v /bigData/vllm/data/vllm-cache-v024:/root/.cache/vllm \
-p 8005:8000 \
vllm/vllm-openai:v0.25.1 \
serve /models/Laguna-S-2.1-INT4 --served-model-name laguna-s-2.1-int4 \
--tensor-parallel-size 4 --max-model-len 262144 \
--kv-cache-dtype bfloat16 --gpu-memory-utilization 0.96 \
--disable-custom-all-reduce --enable-prefix-caching --generation-config auto \
--reasoning-parser poolside_v1 --enable-auto-tool-choice --tool-call-parser poolside_v1 \
--default-chat-template-kwargs '{"enable_thinking":true}' --trust-remote-code \
--max-num-seqs 1 --max-num-batched-tokens 4096 \
--host 0.0.0.0 --port 8000
until curl -fsS http://127.0.0.1:8005/health; do sleep 3; done
Die Wand aus NCCL_*– und VLLM_*-Variablen ist kein über Jahre angesammelter Kommandozeilen-Aberglaube. Sie schaltet genau die P2P-Pfade ab, die über unsere vier getrennten Root-Complexes nicht funktionieren. Ohne --disable-custom-all-reduce führt vLLM eine eigene P2P-Prüfung durch und ignoriert dabei NCCL_P2P_DISABLE=1. Eine frühere Fehlersuche hat uns dafür einen Abend gekostet; seitdem gehört der Schalter zum Startrezept.
Nach dem Start von Laguna bleiben die Devices 0, 1, 2 und 5 frei.
Was auf die übrigen vier GPUs passt
Qwen3.6-35B-A3B-FP8: rund 171 tok/s auf TP=4
Der naheliegende Kandidat ist Qwen3.6-35B-A3B-FP8. Das MoE besitzt insgesamt 35 Milliarden Parameter, aktiviert pro Token aber nur rund 3 Milliarden. Auf den vier freien GPUs, also den Devices 0, 1, 2 und 5, erreicht es mit TP=4 ungefähr 171 tok/s bei kurzem Kontext. Der volle Kontext von 262.144 Tokens passt ebenfalls. vLLM meldet eine KV-Kapazität von 1.182.514 Tokens, entsprechend 4,51 parallelen 262k-Kontexten. Die Gewichte belegen pro GPU lediglich 8,82 GiB.
docker rm -f qwen36_35b_a3b 2>/dev/null || true
docker run -d --name qwen36_35b_a3b --entrypoint vllm \
--gpus '"device=0,1,2,5"' --ipc host --shm-size 64g \
-e NVIDIA_DISABLE_REQUIRE=1 -e CUDA_DEVICE_ORDER=PCI_BUS_ID \
-e NCCL_P2P_DISABLE=1 -e NCCL_IB_DISABLE=1 -e NCCL_CUMEM_ENABLE=0 \
-e VLLM_ALLREDUCE_USE_SYMM_MEM=0 -e VLLM_WORKER_MULTIPROC_METHOD=spawn \
-e VLLM_ENABLE_CUDA_COMPATIBILITY=0 \
-v /bigData/hf-cache/Qwen3.6-35B-A3B-FP8:/models/Qwen3.6-35B-A3B-FP8 \
-v /bigData/vllm/data/torch-extensions:/root/.cache/torch_extensions \
-v /bigData/vllm/data/triton:/root/.triton \
-v /bigData/vllm/data/cuda-cache:/root/.cache/cuda \
-v /bigData/vllm/data/vllm-cache:/root/.cache/vllm \
-p 8007:8000 \
vllm/vllm-openai:latest \
serve /models/Qwen3.6-35B-A3B-FP8 --served-model-name qwen3.6-35b-a3b \
--tensor-parallel-size 4 --max-model-len 262144 --gpu-memory-utilization 0.92 \
--max-num-seqs 1 --disable-custom-all-reduce --enable-prefix-caching \
--reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser qwen3_coder \
--trust-remote-code --host 0.0.0.0 --port 8000
until curl -fsS http://127.0.0.1:8007/health; do sleep 3; done
An dieser Stelle stießen wir auf eine harte Architekturgrenze, die in einer üblichen Modellübersicht leicht untergeht: TP=8 ist mit diesem FP8-Block-Quant mathematisch unmöglich.
Die Blockquantisierung verwendet ein festes block_k=128, während die Hidden-Dimension 512 beträgt. Bei TP=8 entfielen auf jede Partition nur 512 / 8 = 64 Werte, also weniger als ein vollständiger 128er-Block. vLLM beendet den Start deshalb mit ValueError: Weight input_size_per_partition = 64 is not divisible by weight quantization block_k = 128. Das ist weder ein Speicherproblem noch ein Konfigurationsfehler. Für unsere Aufteilung spielt es keine praktische Rolle, weil das Modell ohnehin genau vier GPUs erhalten sollte. Mit TP=4 passt die Partitionierung.
Qwen3.6-27B-FP8: kleiner, aber deutlich langsamer
Das dichte Qwen3.6-27B-FP8 ist nominell kleiner. Auf denselben vier GPUs war es bei kalter Hardware trotzdem erheblich langsamer:
| Modell | aktive Parameter | Aufbau | kurzer Decode |
|---|---|---|---|
| Qwen3.6-35B-A3B | rund 3B | TP=4, Devices 0/1/2/5 | rund 171 tok/s |
| Qwen3.6-27B | dichtes Modell, entsprechend mehr aktive Parameter | TP=4, Devices 0/1/2/5 | rund 65 bis 67 tok/s, kalt |
Das 35B-Modell war damit mehr als 2,5-mal so schnell wie das 27B-Modell. Beim Decode zählt vor allem, wie viele Parameter für jedes Token tatsächlich durch die Speicherpipeline bewegt werden. Das A3B-MoE liest ungefähr 3 Milliarden aktive Parameter; das dichte 27B-Modell muss seine Gewichte vollständig verarbeiten. Die Gesamtgröße im Modellnamen verrät darüber wenig.
Der Zusatz „kalt“ in der Tabelle ist wichtig. Nach längerer Dauerlast brach dieser Messwert wegen eines thermischen Problems ein, das wir weiter unten beschreiben.
Beide Qwen-Varianten belegen in diesem Aufbau dieselben vier GPUs. Sie können daher nicht gleichzeitig laufen. Ports lassen sich beliebig vergeben; der VRAM einer bereits belegten Karte verdoppelt sich dadurch nicht.
Drei Modelle gleichzeitig: Aufteilung in GPU-Paare
Bei zwei Modellen ist die Rechnung noch einfach: vier GPUs für Laguna, vier für Qwen. Ein dritter Endpunkt zwingt uns, die zweite Hälfte des Rigs in zwei Paare aufzuteilen. TP=2 spart Karten, verkleinert aber den gemeinsam verfügbaren VRAM und damit vor allem das Budget für den KV-Cache.
Der gleichzeitig betriebene Aufbau sah so aus:
| Modell | GPUs | Topologie und KV | Kontext | Port | kurzer Decode |
|---|---|---|---|---|---|
| Laguna S 2.1 INT4 | 3, 4, 6, 7 | TP=4, BF16-KV, Utilization 0,96 | 262144 | 8005 | 109,1 tok/s |
| Qwen3.6-27B-FP8 | 0, 2 | TP=2, BF16-KV, Utilization 0,96 | 200000 | 8007 | 45,17 tok/s |
| Gemma 4 31B FP8-block | 1, 5 | TP=2, BF16-KV, Utilization 0,95 | 76000 | 8008 | 40,6 tok/s |
Damit belegt Laguna die große Vierergruppe, Qwen das Paar 0/2 und Gemma das Paar 1/5. Alle acht GPUs sind belegt, und jedes Modell besitzt einen eigenen OpenAI-kompatiblen Endpunkt. Die 46,2 tok/s, die wir für Qwen auf dem Paar 1/5 gemessen haben, gehören zu einem separaten Vergleichslauf; 1/5 kann nicht gleichzeitig von Qwen und Gemma verwendet werden.
Die entscheidende Grenze zeigte sich beim Kontext. 262.144 Tokens passten auf TP=2 bei beiden dichten Modellen nicht mehr. vLLM berechnete die Maxima beim Start bis auf das Token genau.
Weshalb Qwen27B auf TP=2 bei 200.000 Tokens endet
Ein direkter TP=2-Start mit 262.144 Tokens brach nach dem Profiling ab:
262144 benoetigt: 8.16 GiB KV pro GPU
verfuegbar: 6.33 GiB KV pro GPU
berechnetes Maximum: 202272 Tokens
Auf TP=4 hatte Qwen27B noch eine KV-Kapazität von 823.332 Tokens. Mit TP=2 verteilt sich das Modell nur über zwei statt vier Karten. Die Gewichte benötigen pro GPU entsprechend mehr Platz, während die gesamte VRAM-Basis halbiert wird. Für den KV-Cache bleiben in dieser Konfiguration noch rund 202.000 Tokens. Wir setzten den Kontext daher direkt auf 200000 und ließen eine kleine Reserve, statt uns über eine Reihe von Zwischenwerten heranzutasten, die garantiert zu groß gewesen wären.
Der folgende Startbefehl dokumentiert den separat gemessenen Lauf auf dem GPU-Paar 1/5. Für den gleichzeitigen Dreimodellbetrieb wurde Qwen stattdessen auf 0/2 gestartet.
docker rm -f qwen36_27b_fp8 2>/dev/null || true
docker run -d --name qwen36_27b_fp8 --entrypoint vllm \
--gpus '"device=1,5"' --ipc host --shm-size 64g \
-e NVIDIA_DISABLE_REQUIRE=1 -e CUDA_DEVICE_ORDER=PCI_BUS_ID \
-e NCCL_P2P_DISABLE=1 -e NCCL_IB_DISABLE=1 -e NCCL_CUMEM_ENABLE=0 \
-e VLLM_ALLREDUCE_USE_SYMM_MEM=0 -e VLLM_WORKER_MULTIPROC_METHOD=spawn \
-e VLLM_ENABLE_CUDA_COMPATIBILITY=0 \
-v /bigData/hf-cache/Qwen3.6-27B-FP8:/models/Qwen3.6-27B-FP8 \
-v /bigData/vllm/data/torch-extensions:/root/.cache/torch_extensions \
-v /bigData/vllm/data/triton:/root/.triton \
-v /bigData/vllm/data/cuda-cache:/root/.cache/cuda \
-v /bigData/vllm/data/vllm-cache:/root/.cache/vllm \
-p 8007:8000 \
vllm/vllm-openai:latest \
serve /models/Qwen3.6-27B-FP8 --served-model-name qwen3.6-27b-fp8 \
--tensor-parallel-size 2 --max-model-len 200000 --gpu-memory-utilization 0.96 \
--max-num-seqs 1 --disable-custom-all-reduce --enable-prefix-caching \
--reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser qwen3_coder \
--trust-remote-code --host 0.0.0.0 --port 8000
until curl -fsS http://127.0.0.1:8007/health; do sleep 3; done
Weshalb Gemma bei 76.000 Tokens endet
Gemma 4 31B ist ebenfalls dicht und benötigt einen großen KV-Cache. Der Versuch mit 262.144 Tokens scheiterte noch deutlicher:
262144 benoetigt: 11.96 GiB KV pro GPU
verfuegbar: 5.16 GiB KV pro GPU
berechnetes Maximum: 83936 Tokens
Ein erster Start mit 80.000 Tokens und einer gpu-memory-utilization von 0,96 funktionierte zunächst. vLLM meldete 82.397 KV-Tokens. Beim nächsten Start scheiterte jedoch das Aufzeichnen des CUDA-Graphen an einer gerade einmal 20 MiB großen Allokation, weil nur noch ungefähr 3 MiB frei waren. Das rechnerische Maximum war also kein belastbarer Betriebswert.
Mit 76.000 Tokens, einer Utilization von 0,95 und einer gemeldeten KV-Kapazität von 77.050 Tokens startete Gemma wiederholt sauber. Drei freie MiB sind keine Reserve, sondern eine Einladung an den nächsten Neustart, die Fehlersuche zu übernehmen.
docker rm -f gemma4_31b 2>/dev/null || true
docker run -d --name gemma4_31b --entrypoint vllm \
--gpus '"device=1,5"' --ipc host --shm-size 64g \
-e NVIDIA_DISABLE_REQUIRE=1 -e CUDA_DEVICE_ORDER=PCI_BUS_ID \
-e NCCL_P2P_DISABLE=1 -e NCCL_IB_DISABLE=1 -e NCCL_CUMEM_ENABLE=0 \
-e VLLM_ALLREDUCE_USE_SYMM_MEM=0 -e VLLM_WORKER_MULTIPROC_METHOD=spawn \
-e VLLM_ENABLE_CUDA_COMPATIBILITY=0 \
-v /bigData/hf-cache/gemma-4-31B-it-FP8-block:/models/gemma-4-31B-it-FP8-block \
-v /bigData/vllm/data/torch-extensions:/root/.cache/torch_extensions \
-v /bigData/vllm/data/triton:/root/.triton \
-v /bigData/vllm/data/cuda-cache:/root/.cache/cuda \
-v /bigData/vllm/data/vllm-cache:/root/.cache/vllm \
-p 8008:8000 \
vllm/vllm-openai:latest \
serve /models/gemma-4-31B-it-FP8-block --served-model-name gemma-4-31b-fp8 \
--tensor-parallel-size 2 --max-model-len 76000 --gpu-memory-utilization 0.95 \
--max-num-seqs 1 --max-num-batched-tokens 4096 \
--disable-custom-all-reduce --enable-prefix-caching \
--enable-auto-tool-choice --tool-call-parser gemma4 --reasoning-parser gemma4 \
--trust-remote-code --host 0.0.0.0 --port 8000
until curl -fsS http://127.0.0.1:8008/health; do sleep 3; done
FP8-KV funktioniert auf diesem Ampere-Stack nicht
FP8 für den KV-Cache wäre der naheliegende Hebel, um mehr Kontext in dieselbe Speichermenge zu bekommen. Gegenüber BF16 halbiert es den Speicherbedarf. Auf Hopper-Hardware lässt sich diese Möglichkeit sinnvoll nutzen. Unsere Ampere-Karten mit SM 8.6 lehnten dagegen jede verfügbare Variante auf ihre eigene Weise ab:
--kv-cache-dtype fp8verwendet E4M3. Der gewählte Triton-Pfad unterstützt diesen Typ auf SM86 nicht und endet mitfp8e4nv not supported.- Bei
--kv-cache-dtype fp8_e5m2wäre der Kernel-Typ vorhanden, vLLM blockiert die Kombination jedoch mit FP8-Weight-Checkpoints. --kv-cache-dtype fp8_incist in unserer installierten vLLM-Version ausschließlich für Intel Gaudi beziehungsweise HPU vorgesehen.
Für Gemma und Qwen blieb auf diesem Stack damit nur BF16-KV. Das knappe KV-Budget und die zu kleinen TP=2-Maxima für 262k sind unmittelbare Folgen dieser Hardware- und Softwaregrenze.
Pipeline Parallelism hielt eine weitere Falle bereit. Laguna erzeugte mit TP=2, PP=3 und --kv-cache-dtype fp8 flüssigen Unsinn. Wegen des verdoppelten Attention-Präfixes wurden die Werte für k_scale und v_scale nicht geladen. --calculate-kv-scales half nicht und ist in vLLM 0.25.1 ohnehin als veraltet markiert. Bei Pipeline Parallelism war deshalb BF16-KV erforderlich, damit die Ausgabe inhaltlich gültig blieb. Ein sauberer Start und grammatisch korrekte Sätze sind keine ausreichende Funktionsprüfung, wenn die Zahlen im Cache falsch skaliert werden.
Für die Speicherplanung auf Ampere ergaben sich daraus vier Regeln:
| Situation | Konsequenz |
|---|---|
| FP8-KV auf SM86 | E4M3, E5M2 und fp8_inc sind auf diesem Stack blockiert; BF16-KV verwenden |
| PP mit FP8-KV | Ausgabe wird unbrauchbar; BF16-KV ist erforderlich |
| TP=2 mit 262k | passt bei den getesteten dichten Modellen nicht; Qwen erreicht rund 202k, Gemma zuverlässig rund 76k |
| A3B-MoE mit TP=8 | unmöglich, weil die Partition nicht durch block_k teilbar ist |
Was TP=2 bei Qwen27B kostet
Qwen3.6-27B ließ sich sowohl in der Vierer- als auch in der Paaraufteilung betreiben. Dadurch wurde der Preis des zusätzlichen Endpunkts direkt sichtbar:
| Aufbau | GPUs | Kontext | kurzer Decode | Anmerkung |
|---|---|---|---|---|
| TP=4 | 4 | 262k | rund 65 bis 67 tok/s, kalt | KV-Kapazität 823.332 Tokens |
| TP=2 | 2 | 200k | 46,2 tok/s | halbe VRAM-Basis, lässt Platz für ein weiteres Modell |
Der Wechsel von TP=4 auf TP=2 kostete rund 30 Prozent Geschwindigkeit und etwa ein Viertel des konfigurierbaren Kontexts, von 262k auf 200k. Dafür beansprucht Qwen nur zwei Karten, sodass zwei weitere für Gemma frei bleiben. Im Multimodellbetrieb sind freie GPU-Paare die eigentliche Währung.
Bei einem dichten Modell ist dieser Preis spürbar. 46 tok/s sind brauchbar, aber nicht üppig. Ein A3B-MoE bewegt beim Decode viel weniger aktive Gewichte und wäre als Bewohner eines kleinen GPU-Paars genügsamer.
Die PCIe-Breite entschied den Decode nicht
Für TP=2 schien zunächst das GPU-Paar mit der breitesten PCIe-Anbindung die offensichtliche Wahl zu sein. Wir starteten denselben Qwen27B-Container auf drei Paaren und änderten nur die Devices:
| GPU-Paar | PCIe-Anbindung | kurzer Decode |
|---|---|---|
| 1/5 | x8 + x8 | 46,20 tok/s |
| 6/7 | x16 + x16 | 45,30 tok/s |
| 0/2 | x16 + x4 | 45,17 tok/s |
Ausgerechnet das Paar mit x8+x8 war am schnellsten. x16+x16 lag 1,9 Prozent dahinter, die gemischte Verbindung aus x16 und x4 noch knapp darunter. Über alle drei Paare betrug die Streuung wenig mehr als zwei Prozent und war praktisch vernachlässigbar.
Beim Batch-1-Decode begrenzt in diesem Aufbau vor allem die Bandbreite des lokalen VRAM. Zwischen zwei TP-Ranks werden Reduktionen ausgetauscht, doch deren Umfang ist klein gegenüber der Datenmenge, die jede GPU pro Token aus ihrem eigenen Speicher liest. Die PCIe-Breite wirkt sich stärker auf den Prefill eines langen Prompts aus. Für die Decode-Rate konnten wir die Paare dagegen nach Temperatur und Topologie auswählen, ohne x16-Verbindungen um jeden Preis freihalten zu müssen.
Wenn die Hardware überzeugender lügt als die Software
SLOT6 drosselte wegen des Speichers, nicht wegen des GPU-Kerns
Die kalten 65 bis 67 tok/s von Qwen3.6-27B auf TP=4 hielten unter längerer Dauerlast nicht. Nach einiger Zeit fiel der Durchsatz auf 17 bis 20 tok/s. Es gab keinen Absturz, keine Fehlermeldung und keinen OOM. Das Modell wurde lediglich ungefähr dreimal langsamer.
Ursache war Device 0 in SLOT6. Der GDDR6X-Speicher beziehungsweise seine Memory Junction wurde unter Dauerlast zu heiß. Daraufhin meldete die Karte einen SW Thermal Slowdown und senkte den Kerntakt auf 300 MHz. Die angezeigte Kerntemperatur betrug zu diesem Zeitpunkt nur 76 Grad Celsius. Wer in nvidia-smi lediglich diesen Wert beobachtet, sieht keinen offensichtlichen Grund zur Sorge. Die Notbremse kam vom Speicher.
Tensor Parallelism machte aus einer langsamen Karte vier langsame Karten. Jeder Rank wartet auf den langsamsten Teilnehmer; eine gedrosselte GPU zieht deshalb den gesamten TP-Verbund herunter.
Ein Power-Limit von 200 W half nicht. Es verringerte sogar die verfügbare Bandbreite, weil nun auch der Kern unter regulärer Last unnötig drosselte. Softwareparameter konnten die Ursache ebenfalls nicht beheben. Die Karten stehen zu dicht beieinander, SLOT6 erhält zu wenig Luft und die Wärmeleitpads des VRAM arbeiten an ihrer Grenze. Die passende Reparatur besteht aus neuen Pads und mehr Luftstrom, nicht aus einer anderen --gpu-memory-utilization.
Deshalb halten wir Device 0 aus dem großen Laguna-Aufbau heraus und behandeln die Karte als den Teil des Systems, der unter Dauerlast besondere Aufmerksamkeit braucht. Bei kurzen Chat-Anfragen mit Pausen blieb sie unauffällig und kühlte zwischen den Requests wieder ab. Erst anhaltender Decode brachte das Problem zuverlässig hervor.
Während der Suche nach der Wärmequelle gelang uns außerdem eine besonders menschliche Fehldiagnose. Eine GPU lief weiter unter Volllast, obwohl das produktive Modell laut Dashboard längst nichts mehr zu tun hatte. Wir verdächtigten eine ganze Weile das falsche Modell. Tatsächlich hatte ein Client die Verbindung getrennt, während vLLM den Request im Hintergrund weiter generierte. Diese verwaiste Anfrage heizte die Karte, ohne dass jemand ihre Ausgabe noch entgegennahm. Bleibt eine GPU heiß, obwohl vermeintlich nichts läuft, prüfen wir seitdem zuerst aktive und hängende Requests. Das geht schneller als ein spontaner Umbau der Kühlung.
Ein defektes Kabel verhandelte die Link-Geschwindigkeit
Ein zweiter Fehler bremste Modelldownloads und Kopien zwischen unseren Rigs wochenlang auf ungefähr 12 MB/s, also das Niveau einer 100-Mbit-Verbindung. Ein 150-GiB-Modell zu kopieren dauerte damit gut dreieinhalb Stunden, obwohl alle beteiligten Komponenten als Gigabit ausgewiesen waren.
Die Schnittstelle eno1 hing nicht stabil bei 100 Mbit fest. Sie sprang zwischen 1000 und 100 Mbit, kehrte zeitweise auf 1000 zurück und fiel später erneut ab. Genau dieses Flapping zog die Diagnose in die Länge. Ein dauerhaft defekter Link ist schnell gefunden. Ein sporadischer lässt nach einem Neustart kurz Hoffnung aufkommen und lenkt die Suche anschließend zu Treibern, Switch-Ports und Kernel-Einstellungen.
Am Ende war das Kabel defekt. Nach dem Tausch gegen ein Cat6-Kabel blieb eno1 stabil bei 1000 Mbit, und die Kopierrate stieg auf 112 MB/s, also fast das Zehnfache. Die lange Suche nach dem vermeintlichen Downloadproblem endete bei einem Kabel für drei Euro.
Das Muster kannten wir bereits aus unserer Fehlersuche zu verschwindenden GPUs und heruntergestuften PCIe-Links im Artikel „Wenn die GPU vom Bus überfahren wird“. Wird ein System ohne klare Fehlermeldung plötzlich langsam, prüfen wir zuerst die physische Ebene: Temperaturen, Takte, Kabel, Riser und Link-Breite. Software ist komplex, Kupfer bleibt trotzdem Teil des Stacks.
Unsere Regeln für den Multimodellbetrieb
Aus den Messungen und Fehlersuchen haben wir sechs betriebliche Regeln abgeleitet:
- Wir planen in Karten, nicht in Gesamt-VRAM. 192 GB sind acht getrennte 24-GB-Bereiche. Praktische Aufteilungen entstehen aus GPU-Paaren und Vierergruppen.
- MoE-Modelle mit wenigen aktiven Parametern sind besonders gut teilbar. Das A3B-Modell verliert auf einer kleinen Topologie weniger Decode-Leistung als ein dichtes 27B- oder 31B-Modell.
- Auf unserem Ampere-Stack kalkulieren wir mit BF16-KV. Die drei getesteten FP8-KV-Varianten scheiterten aus unterschiedlichen Gründen, und 262k passten deshalb auf TP=2 nicht.
- Pipeline Parallelism und FP8-KV prüfen wir nicht nur auf einen erfolgreichen Start. Laguna produzierte in dieser Kombination flüssigen Unsinn. Für den dokumentierten PP-Pfad war BF16-KV erforderlich.
- PCIe-Breite ist für Batch-1-Decode ein nachgeordnetes Kriterium. Zwischen x8+x8, x16+x16 und x16+x4 lagen in unserem Qwen27B-Test nur gut zwei Prozent. Temperatur und eine sinnvolle Kartenaufteilung waren wichtiger.
- Bei unerklärlicher Langsamkeit untersuchen wir Hardware und laufende Requests vor dem Modell. Heißer GDDR6X-Speicher, ein verwaister Request und ein flappendes Netzwerkkabel erzeugten Fehlerbilder, die sich durch keine Änderung am Quant beheben ließen.
Drei Modelle gleichzeitig auf einem gebrauchten 8×3090-Rig zu betreiben, ist am Ende eine Frage genauer Zuordnungen, realistischer KV-Reserven und der Disziplin, eine rechnerisch freie GPU nicht automatisch für eine thermisch gesunde zu halten. So laufen bei uns drei eigenständige OpenAI-kompatible Endpunkte auf einem System. Der Rest ist Buchhaltung, bis ein Kabel beschließt, ebenfalls mitzurechnen.