Eine Zahl, mehrere Speicherrollen
Ein Parameter kann während des Trainings als Gewicht, Gradient und in zwei Adam-Momentzuständen vorkommen. Mit allen vier Arrays in Float32 sind das grob 16 Bytes pro Parameter. Für 15 Millionen Parameter ergibt das etwa 240 Millionen Bytes, rund 229 MiB. Aktivierungen und temporäre Arbeitsbereiche sind darin noch nicht enthalten.
Klassisches Mixed-Precision-Training mit Adam braucht ebenfalls etwa 16 Bytes pro Parameter: 2 für 16-Bit-Gewichte, 2 für Gradienten, 4 für FP32-Mastergewichte und 8 für die beiden Momente (ZeRO, Rajbhandari et al.). Es spart vor allem Aktivierungsspeicher und Rechenzeit, nicht Optimizer-Zustand. Sage bei jeder Rechnung ausdrücklich, welche Zustände welchen Dtype besitzen.
Warum Aktivierungen teuer sind
Beim Backward benötigt Autograd bestimmte Zwischenergebnisse aus dem Forward. Diese hängen von Batchgröße, Sequenzlänge, Breite und Tiefe ab. Eine naive Attentionmatrix fügt einen quadratischen Term in T hinzu. Ein effizienter Kernel kann bestimmte große Zwischenmatrizen vermeiden, aber nicht sämtliche Aktivierungen verschwinden lassen.
Wenn der Speicher knapp ist, reduziere zuerst den Microbatch und prüfe danach Kontextlänge und Modellgröße. Gradient Accumulation kann die effektive Batchgröße erhalten, erzeugt aber nicht dieselbe Laufzeit wie ein größerer gleichzeitig gerechneter Batch.
FP32, FP16 und BF16
FP32 hat 32 Bits und ist ein einfacher robuster Ausgangspunkt. FP16 besitzt weniger Exponentenbereich; große oder sehr kleine Werte können schneller problematisch werden. BF16 besitzt einen breiteren Exponentenbereich als FP16, aber eine gröbere Mantisse. Keiner dieser Dtypes ist „immer genauer genug“.
Große Läufe nutzen inzwischen auch FP8 (8 Bit) für viele Matrixmultiplikationen, mit feinkörnigen Skalierungsfaktoren, höherpräzisen Akkumulatoren und Mastergewichten (DeepSeek-V3); für die Inferenz sind auch 4-Bit-Formate verbreitet. Das ist Ingenieursarbeit für spezielle Hardware, kein Schalter für dein Lehrprojekt.
Der Referenztrainer beginnt bewusst in Float32. Das reduziert die Zahl gleichzeitig zu lernender Mechanismen und unterstützt CPU, MPS und CUDA mit einem gemeinsamen klaren Weg. Mixed Precision ist eine anschließende Erweiterung, nicht ein stiller Anspruch des Downloads.
Autocast bewusst einsetzen
Für geeignete CUDA-Hardware kann ein Training mit Autocast und BF16 bestimmte Operationen in niedrigerer Präzision ausführen. Andere Operationen bleiben je nach Regeln in Float32. Eine mögliche Erweiterung lautet:
with torch.autocast(device_type='cuda', dtype=torch.bfloat16):
logits = model(x)
loss = F.cross_entropy(logits.reshape(-1, V), y.reshape(-1))
loss.backward()Prüfe die Unterstützung deiner Hardware. Für FP16 wird häufig ein Gradient Scaler verwendet, der den Loss skaliert und vor dem Optimizer-Schritt wieder korrekt zurückskaliert. Clipping gehört dann nach dem Unscale. Benutze nicht einfach denselben Ausschnitt ungetestet auf jedem Backend. PyTorch AMP-Dokumentation.
Quantisierung ist ein anderer Eingriff
Eine 4-Bit-Gewichtsdatei verringert den Speicher der dargestellten Gewichte. Sie macht nicht automatisch jedes Trainingsarray 4-Bit und nicht alle Operationen viermal schneller. Skalen, Gruppierungsmetadaten, Aktivierungen, Cache und Dequantisierung gehören zur tatsächlichen Rechnung.
Bei Quantisierung gruppierst du beispielsweise Gewichte, wählst eine Skala und approximierst Fließkommazahlen durch begrenzte Integerwerte. Das verursacht Fehler. Wie stark die Ausgabe beeinträchtigt wird, hängt von Verfahren, Daten, Modell und empfindlichen Schichten ab. „Vier Bit“ allein beschreibt nicht die gesamte Qualität.
Aktivierungscheckpointing
Beim Checkpointing speicherst du weniger Forward-Zwischenwerte und berechnest sie im Backward erneut. Das spart Aktivierungsspeicher gegen zusätzliche Rechenzeit. Es ist nicht dasselbe wie ein Modellcheckpoint auf der Festplatte.
from torch.utils.checkpoint import checkpoint
for block in model.blocks:
x = checkpoint(block, x, use_reentrant=False)Für ein kleines Modell kann der Mehraufwand größer sein als der Nutzen. Führe diese Erweiterung erst ein, wenn du einen gemessenen Speicherengpass hast. Prüfe zudem Zufallsoperationen, Dtypes und die Kompatibilität mit deinem Backend.
Eine ehrliche Speichermessung
Auf CUDA kannst du nach Aufwärmen die Spitzenallokation von PyTorch messen. Das entspricht nicht exakt der Gesamtspeichernutzung des Geräts: Bibliotheken und andere Programme können zusätzlichen Speicher beanspruchen. Auf Apple Silicon ist der gemeinsam genutzte Speicher wieder anders zu betrachten.
if device.type == 'cuda':
torch.cuda.reset_peak_memory_stats()
# Ein echter Forward/Backward/Optimizer-Schritt
print(torch.cuda.max_memory_allocated() / 2**20, 'MiB')Ein Modell, das beim Forward passt, kann beim ersten Adam-Schritt scheitern, weil Momentzustände erst dann angelegt werden. Miss deshalb einen vollständigen Trainingsschritt.
Dein Optimierungsprotokoll
Vergleiche Baseline-Float32, kleinere Microbatches plus Accumulation und danach eventuell Mixed Precision. Dokumentiere Validierungs-Loss, Durchsatz und Spitzenverbrauch. Eine Beschleunigung, die den Loss verändert oder instabil wird, benötigt eine Qualitätsentscheidung. Optimierung ist nicht bloß die Suche nach der kleinsten Zahl im Speichermonitor.