12. November 2026 · VRAMfarm-Team
Multi-GPU-Training: DDP, FSDP und ZeRO auf einer gemieteten Maschine mit 2–8 GPUs
Vier Karten gemietet, das Skript mit python train.py gestartet, und nvidia-smi zeigt eine Karte bei 100 % und drei bei 0 %. Kaputt ist nichts. Ein Trainingsskript nutzt eine GPU, solange es nicht über einen Launcher gestartet wird, der pro Karte einen Prozess startet, und die Arbeit muss so aufgeteilt werden, dass sie zu Ihrem Modell passt. Dafür gibt es zwei Wege: Jede Karte bekommt eine Kopie des Modells und einen Teil der Daten, oder das Modell selbst wird in Stücke geschnitten. Welchen Weg Sie brauchen, hängt davon ab, ob das Modell auf eine Karte passt.
Zuerst: Brauchen Sie überhaupt mehr als eine Karte?
Es gibt nur zwei Gründe für mehrere GPUs: Der Job passt nicht in den Speicher einer Karte, oder er dauert zu lange auf einer Karte. Bevor Sie zu einer Maschine mit mehreren GPUs greifen, prüfen Sie, ob eine einzelne größere Karte das Problem löst. Eine Karte ist immer einfacher: kein Launcher, keine Kommunikation zwischen Karten, keine NCCL-Fehler.
| Job | Eine Karte, die das schafft | Wann mehrere Karten sinnvoll sind |
|---|---|---|
| LoRA oder QLoRA auf einem Modell bis 70B | 24–80 GB, siehe den VRAM-Ratgeber | Selten; um auf einem großen Datensatz schneller zu trainieren |
| Volles Fine-Tuning eines 7–8B-Modells | Eine 80-GB-Karte mit 8-Bit-Optimizer oder eine H200 (141 GB) | Für den Standard-Optimizer, längere Sequenzen oder einen kürzeren Lauf |
| Volles Fine-Tuning eines 13–70B-Modells | Keine mit dem Standard-Optimizer; ein 13B-Modell passt nur mit 8-Bit-Optimizer auf eine Karte mit 141–192 GB | Fast immer: 4–8 Karten mit FSDP oder ZeRO-3 |
| Training eines kleinen Modells auf vielen Daten | Jede Karte, auf die es passt | Um einen Lauf von 40 Stunden auf 10 zu verkürzen |
| Betrieb eines 70B-Modells in fp8 | H200 | 2 × H100 mit vLLM, siehe H200 oder H100 |
Die größten Einzelkarten im Katalog sind die B200 mit 192 GB, die H200 mit 141 GB und die RTX PRO 6000 mit 96 GB. Wenn eine davon Ihren Job fasst, fangen Sie dort an.
Datenparallelität: dasselbe Modell auf jeder Karte
Datenparalleles Training (PyTorch DDP) startet einen Prozess pro GPU. Jeder Prozess hält eine vollständige Kopie des Modells, nimmt einen anderen Teil jedes Batches, berechnet seine Gradienten, und dann mitteln alle Karten ihre Gradienten vor dem Optimizer-Schritt. Die Karten bleiben identisch, und N Karten verarbeiten pro Schritt N-mal so viele Daten.
Eine Voraussetzung gibt es: Modell, Gradienten, Optimizer-Zustände und Aktivierungen müssen auf eine Karte passen. Ist das so, ist DDP der einfachste Weg zu mehr Tempo. Sie starten das Skript mit einem Launcher statt mit python:
torchrun --nproc_per_node=4 train.py # reines PyTorch, Hugging Face Trainer
accelerate launch --num_processes 4 train.py # Hugging Face Accelerate
Der Hugging Face Trainer und die meisten Fine-Tuning-Werkzeuge (Axolotl, LLaMA-Factory, torchtune) schalten von selbst auf DDP um, wenn sie so gestartet werden. In reinem PyTorch verpacken Sie das Modell in DistributedDataParallel und fügen einen DistributedSampler hinzu.
Der Preis ist die Kommunikation: Nach jedem Schritt tauschen die Karten eine Kopie der Gradienten aus. Bei einem 1B-Modell in bf16 sind das etwa 2 GB pro Schritt, was PCIe schafft, während der Backward-Pass noch läuft. Rechnen Sie damit, dass zwei Karten etwa das 1,7- bis 1,9-Fache einer Karte leisten und vier Karten etwa das 3- bis 3,7-Fache, bei größeren Modellen auf Maschinen ohne NVLink weniger.
Wenn das Modell nicht passt: FSDP und DeepSpeed ZeRO
Mit dem üblichen AdamW-Optimizer in Mixed Precision kostet jeder Parameter rund 16 Bytes: 2 für die bf16-Gewichte, 2 für die Gradienten und 12 für den Optimizer (eine fp32-Masterkopie und zwei Momentschätzungen). Bei einem 8B-Modell sind das 128 GB vor den Aktivierungen, mehr als jede einzelne 80-GB-Karte.
Sharding teilt diese 16 Bytes auf die Karten auf, statt sie zu kopieren. DeepSpeed nennt das ZeRO und kennt drei Stufen; FSDP in PyTorch macht dasselbe unter anderen Namen. Für ein 8B-Modell:
| Methode | Was jede Karte hält | Pro Karte, 4 Karten | Pro Karte, 8 Karten |
|---|---|---|---|
| DDP | Alles | 128 GB | 128 GB |
| ZeRO-1 | Gewichte, Gradienten, 1/N des Optimizers | 56 GB | 44 GB |
ZeRO-2, FSDP SHARD_GRAD_OP |
Gewichte, 1/N der Gradienten und des Optimizers | 44 GB | 30 GB |
ZeRO-3, FSDP FULL_SHARD |
1/N von allem | 32 GB | 16 GB |
Die Aktivierungen kommen hinzu, je nach Sequenzlänge und Batch-Größe von wenigen GB bis zu Dutzenden GB pro Karte; Gradient Checkpointing verringert sie um den Preis von etwa einem Drittel mehr Rechenzeit. So lesen Sie die Tabelle: 4 Karten mit 80 GB schaffen ein volles Fine-Tuning eines 8B-Modells bequem mit ZeRO-2 oder ZeRO-3, 4 Karten mit 48 GB mit ZeRO-3, und 24-GB-Karten nur zu acht oder mit Auslagerung des Optimizers in den Arbeitsspeicher.
FSDP ist Teil von PyTorch; DeepSpeed wird mit einer JSON-Datei konfiguriert. Der Hugging Face Trainer und Accelerate unterstützen beides. CPU-Offload (ZeRO-Offload, FSDP cpu_offload) verschiebt die Optimizer-Zustände in den Arbeitsspeicher und lässt kleine Karten große Modelle trainieren, langsam und nur, wenn die Maschine genug RAM hat.
ZeRO-3 hat einen Preis, der auf gemieteten Maschinen zählt: Die Gewichte selbst wandern bei jedem Schritt zwischen den Karten hin und her. Bei einem 8B-Modell auf vier Karten sind das grob 35–50 GB pro Karte und Schritt. Über NVLink auf einer H100-Maschine dauert das einen Bruchteil einer Sekunde, über PCIe können es Sekunden sein, und die Karten warten. Deshalb ist die Art der Verbindung so wichtig.
Tensor- und Pipeline-Parallelität, und was die Inferenz nutzt
Tensor-Parallelität schneidet jede Gewichtsmatrix in Scheiben, eine pro Karte, und tauscht in jeder Schicht Daten aus. Sie funktioniert deshalb nur über NVLink gut. Pipeline-Parallelität legt verschiedene Schichten auf verschiedene Karten, mit weniger Datenverkehr, aber mit Karten, die warten, während sich die Pipeline füllt. Frameworks fürs Pre-Training wie Megatron kombinieren beides; fürs Fine-Tuning reicht meist FSDP oder ZeRO.
Bei der Inferenz ist es einfacher. vLLM teilt ein Modell mit --tensor-parallel-size 2 (oder 4, 8) auf, llama.cpp und Ollama verteilen die Schichten von selbst auf die Karten. Inferenz schickt wenig Daten zwischen den Karten hin und her und läuft deshalb auch über PCIe ordentlich.
Eine Falle: device_map="auto" in Hugging Face Transformers verteilt die Schichten auf die Karten und führt sie nacheinander aus. Das Modell passt, aber es arbeitet immer nur eine Karte. Zum Ausprobieren eines großen Modells ist das in Ordnung, für schnelles Training nicht.
NVLink oder PCIe auf gemieteten Maschinen
| Verbindung | Geschwindigkeit zwischen Karten, ungefähr | Wo Sie sie finden |
|---|---|---|
| PCIe 4.0 x16 | 25–32 GB/s pro Richtung | RTX 3090, RTX 4090, A6000, die meisten PCIe-Karten |
| PCIe 5.0 x16 | 50–64 GB/s pro Richtung | RTX 5090, RTX PRO 6000, neuere Server |
| NVLink-Brücke, RTX 3090 | etwa 112 GB/s | Auf gemieteten Maschinen selten |
| NVLink, A100 SXM | 600 GB/s | A100-SXM-Boards mit 4 und 8 Karten |
| NVLink, H100 / H200 SXM | 900 GB/s | SXM-Boards mit 4 und 8 Karten |
Consumer-Karten haben eine weitere Grenze: Die RTX 4090 unterstützt keine direkten Peer-to-Peer-Übertragungen, Daten zwischen den Karten laufen also über den Arbeitsspeicher. Maschinen mit mehreren 4090 eignen sich gut für Inferenz und für DDP mit kleinen Modellen, schlecht für ZeRO-3 mit großen.
Der Katalog zeigt den Kartennamen vor dem Start. „SXM“ oder „SXM4“ bedeutet, dass die Karten auf einem Board für NVLink sitzen; PCIe-Karten haben vielleicht Brücken, vielleicht nicht. Der Filter „PCIe“ im Katalog gibt die Busgeschwindigkeit zwischen CPU und GPUs an. Sie zählt beim Laden von Daten und Modellen (eine Karte in einem schmalen x4-Steckplatz lädt langsam), sagt aber nichts über die Verbindung zwischen den Karten. Dafür führen Sie gleich nach dem Start Folgendes aus:
nvidia-smi topo -m
NV4, NV12 oder NV18 zwischen zwei Karten bedeutet NVLink. PIX und PXB stehen für einen PCIe-Switch, PHB für die PCIe-Brücke der CPU, und NODE oder SYS für einen Weg quer über die CPU, den langsamsten.
Eine Maschine mit mehreren GPUs bei VRAMfarm mieten
- Maschine auswählen. Stellen Sie im Serverkatalog „Karten pro Maschine“ auf 2×, 4× oder 8×. Der Preis gilt pro Stunde für die ganze Maschine, etwa die Zahl der Karten mal der Preis einer Karte: ab 2,36 € pro Stunde für 4 × RTX 4090, ab 31,20 € für 8 × H100. Jede Zeile zeigt Kartenname, CPU-Kerne, RAM, Festplatte, Treiber, CUDA-Version und Land.
- Die Filter nutzen, die fürs Training zählen. VRAM pro Maschine, RAM (mindestens der gesamte VRAM), CPU-Kerne (4–8 pro Karte), freie Festplatte (ein vollständiger Checkpoint eines 8B-Modells mit Optimizer-Zuständen hat über 100 GB) und „Mietdauer“, also wie viele Tage am Stück die Maschine gehalten werden kann. Lassen Sie bei langen Läufen Reserve.
- Einen leeren Server wählen. PyTorch mit Jupyter oder Ihr eigenes Docker-Image. Um zu starten, muss Ihr Guthaben eine Stunde der ganzen Maschine abdecken; das ist eine Prüfung, keine Abbuchung, abgerechnet wird minutengenau.
- Die Maschine in den ersten Minuten prüfen. Führen Sie
nvidia-smi,nvidia-smi topo -mundpython -c "import torch; print(torch.cuda.device_count())"aus. Stimmt etwas nicht, stoppen Sie den Server: Sie zahlen das Minimum von 15 Minuten, nicht die Stunde. - Vor dem eigentlichen Job einen kurzen Kommunikationstest laufen lassen:
# nccl_check.py: starten mit torchrun --nproc_per_node=4 nccl_check.py
import os, time, torch, torch.distributed as dist
rank = int(os.environ["LOCAL_RANK"]); torch.cuda.set_device(rank)
dist.init_process_group("nccl")
x = torch.ones(512 * 1024**2, dtype=torch.bfloat16, device="cuda") # 1 GiB
for _ in range(3): dist.all_reduce(x)
torch.cuda.synchronize(); t = time.time()
for _ in range(10): dist.all_reduce(x)
torch.cuda.synchronize()
if rank == 0: print(f"{10 / (time.time() - t):.1f} all-reduces of 1 GiB per second")
dist.destroy_process_group()
Gibt er eine Zahl aus, sprechen die Karten miteinander; eine NVLink-Maschine sollte um ein Vielfaches schneller sein als eine mit PCIe. Hängt er, lesen Sie die erste Stolperfalle unten.
Die API nimmt die Zahl der Karten als count entgegen. Ein Skript kann also für einen Nachtlauf eine Maschine mit 8 Karten starten und sie stoppen, wenn der Job fertig ist.
Häufige Stolperfallen
- NCCL hängt oder meldet „unhandled system error“. Starten Sie mit
NCCL_DEBUG=INFO, um zu sehen, wo es stehen bleibt. Auf Consumer-Karten behebtNCCL_P2P_DISABLE=1Hänger oft. Ist ein Prozess abgestürzt, warten die anderen bis zum Timeout auf ihn; suchen Sie im Log also den ersten Fehler, nicht den letzten. - Zu wenig Shared Memory. In Containern hat
/dev/shmmanchmal nur ein paar Dutzend Megabyte, und dann brechen NCCL und Data Loader mit „bus error“ ab. Prüfen Sie das mitdf -h /dev/shm; senken Sienum_workers, oder versuchen SieNCCL_SHM_DISABLE=1. - Die Batch-Größe hat sich unbemerkt geändert. Globaler Batch = Batch pro Karte × Zahl der Karten × Schritte der Gradientenakkumulation. Wechseln Sie mit derselben Konfiguration von 1 auf 4 Karten, wird der Batch viermal so groß und die Epoche in Schritten viermal so kurz. Senken Sie entweder die Akkumulationsschritte, um den globalen Batch zu halten, oder erhöhen Sie die Lernrate: bei SGD ungefähr linear, bei Adam vorsichtiger (mit der Quadratwurzel), mit Warm-up.
- Jede Karte sieht dieselben Daten. Ohne
DistributedSamplertrainieren alle Karten auf identischen Batches; ohnesampler.set_epoch(epoch)hat jede Epoche dieselbe Reihenfolge. Der Hugging Face Trainer kümmert sich um beides. - Vier Kopien jedes Logs und Checkpoints. Schreiben Sie Dateien nur auf Rank 0. Ein vollständiger FSDP-Checkpoint wird im Arbeitsspeicher eines einzigen Prozesses zusammengesetzt; Sharded Checkpoints vermeiden das.
- „System not yet initialized“ auf einer SXM-Maschine mit 8 Karten. Das kommt vom NVLink-Fabric des Anbieters, nicht aus Ihrem Code. Stoppen Sie den Server, wählen Sie eine andere Maschine und sagen Sie dem Support Bescheid.
Lange Läufe brauchen zwei Gewohnheiten. Starten Sie das Training in tmux, damit es einen zugeklappten Laptop übersteht. Speichern Sie alle 30–60 Minuten Checkpoints in /workspace, behalten Sie nur die letzten zwei, und kopieren Sie sie bei Läufen über einen Tag von der Maschine: Die Serverfestplatte wird beim Stoppen gelöscht, wenn Sie den Ordner nicht speichern (was mit Ihren Dateien passiert). Solange die GPUs beschäftigt sind, lässt die Leerlauferkennung den Server in Ruhe. Endet der Lauf, beginnt sie zu zählen: Nach 2 Stunden ohne GPU-Last, SSH- oder Jupyter-Verbindung bekommen Sie eine E-Mail, eine Stunde später wird der Ordner gespeichert und der Server stoppt. Sie können das abschalten, und Server mit Ihrem eigenen Docker-Image werden nicht automatisch gestoppt. Günstiger ist eine letzte Zeile in Ihrem Skript, die den Server über die API stoppt.
Beginnen Sie mit zwei Karten und einem kurzen Lauf, messen Sie die Zeit pro Schritt und skalieren Sie dann auf vier oder acht. Sind zwei Karten nicht annähernd doppelt so schnell wie eine, helfen auch mehr Karten nicht; sehen Sie sich zuerst die Verbindung zwischen ihnen an.