Layer 8Ehrlicher Tech-Support
SoftwareSchweregrad: LästigCUDA out of memory

CUDA out of memory: Der Grafikspeicher ist voll

torch.OutOfMemoryError beim Laden oder Trainieren? Der Grafikspeicher muss mehr fassen als die Gewichte allein. Was wirklich hilft und was nicht.

Aufwand:
ca. 40 Min.
Voraussetzung:
Python, PyTorch mit CUDA, nvidia-smi
Gilt für:
PyTorch 2.13 mit CUDA, Nvidia-Grafikkarte

Das Symptom

Das Modell lädt, der erste Trainingsschritt läuft an – und dann bricht alles mit torch.OutOfMemoryError: CUDA out of memory ab. Die Meldung ist ungewöhnlich gesprächig: Sie nennt, wie viel angefordert wurde, wie viel die Karte insgesamt hat, wie viel bereits belegt ist und wie viel PyTorch reserviert, aber nicht benutzt hat. Beim zweiten Versuch bricht es an einer anderen Stelle ab, beim dritten läuft es plötzlich durch. Und nvidia-smi behauptet die ganze Zeit, die Karte sei voll, obwohl das Skript längst beendet ist.

Die „Layer 8“-Wahrheit

Der Fehler ist keine Schätzung und keine Warnung, sondern eine Tatsache: Die Karte hat den angeforderten Block nicht mehr. Falsch ist fast immer die Rechnung davor. Wer nur die Gewichte kalkuliert, hat den kleineren Posten kalkuliert. Die Zwischenergebnisse jeder Schicht müssen für den Rückwärtsdurchlauf aufgehoben werden und wachsen mit der Batchgröße; beim Training legt der Optimierer noch seine eigenen Zustände dazu, bei Adam pro Parameter zwei weitere Werte in Parametergröße. Der Rest ist Arithmetik statt Meinung: Ein Parameter kostet in 32 Bit vier Byte, in 16 Bit zwei, in 8 Bit eines. Sieben Milliarden Parameter in 16 Bit sind damit vierzehn Milliarden Byte, rund 13 GiB – und das ist die Untergrenze, bevor überhaupt gerechnet wird.

Der zweite Teil der Wahrheit ist unangenehmer: Oft fehlt gar kein Speicher, sondern ein zusammenhängender Block. PyTorch reserviert in Blöcken und gibt sie nicht sofort zurück, deshalb zeigt nvidia-smi belegt, was intern längst frei ist. Genau darum steht in der Fehlermeldung so oft eine große Zahl hinter reserved but unallocated. Das ist Fragmentierung, und dagegen hilft kein Speicherriegel: Der Arbeitsspeicher im Rechner und der Speicher auf der Karte sind zwei getrennte Dinge, und nur eines davon lässt sich nachkaufen. Die ehrliche Reihenfolge lautet deshalb: erst messen, dann Batchgröße senken, dann das Zahlenformat verkleinern, dann den Zuteiler konfigurieren – und erst ganz zum Schluss über neue Hardware nachdenken.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Messen, statt raten: belegt gegen reserviert

    Bevor irgendetwas geändert wird, muss klar sein, wo der Speicher hingeht. memory_allocated() nennt den von Tensoren belegten Speicher, memory_reserved() den gesamten Bestand des zwischenspeichernden Zuteilers. Klaffen die beiden Werte weit auseinander, ist Fragmentierung im Spiel und nicht schlichter Platzmangel. memory_summary() gibt die Aufschlüsselung als Text aus.

    import torch
    
    print(torch.cuda.memory_allocated() / 1024**3, 'GiB in Tensoren')
    print(torch.cuda.memory_reserved() / 1024**3, 'GiB vom Zuteiler gehalten')
    print(torch.cuda.memory_summary())
    
  2. 2.

    Den Bedarf der Gewichte ausrechnen, nicht schätzen

    Der Speicherbedarf der Gewichte folgt direkt aus dem Zahlenformat: 32 Bit sind 4 Byte pro Parameter, 16 Bit sind 2 Byte, 8 Bit ist 1 Byte, 4 Bit sind 0,5 Byte. Für ein Modell mit 7 Milliarden Parametern in 16 Bit sind das 7e9 mal 2 Byte gleich 14 Milliarden Byte, also rund 13,0 GiB – in 32 Bit das Doppelte, in 8 Bit die Hälfte. Diese Zahl ist die Untergrenze für reines Ableiten, ohne Zwischenergebnisse.

    python3 -c "p = 7e9; print([p*b/1024**3 for b in (4, 2, 1, 0.5)])"
    
  3. 3.

    Batchgröße senken und stattdessen Gradienten aufsummieren

    Die Zwischenergebnisse wachsen mit der Batchgröße, die Gewichte nicht. Halbierte Batchgröße heißt also grob halbierter Bedarf an Zwischenergebnissen – das ist der wirksamste Hebel und meist der erste, der ausprobiert werden sollte. Damit die effektive Batchgröße gleich bleibt, werden mehrere kleine Durchläufe aufsummiert, bevor der Optimierer einen Schritt macht. Die PyTorch-Dokumentation beschreibt das als effektive Batchgröße aus Batch pro Durchlauf mal Anzahl der aufsummierten Durchläufe.

    akkumuliere = 4  # effektive Batchgröße = Batchgröße * 4
    
    optimierer.zero_grad(set_to_none=True)
    for i, (x, y) in enumerate(lader):
        verlust = kriterium(modell(x.to(geraet)), y.to(geraet)) / akkumuliere
        verlust.backward()
        if (i + 1) % akkumuliere == 0:
            optimierer.step()
            optimierer.zero_grad(set_to_none=True)
    
  4. 4.

    Das Zahlenformat verkleinern (gemischte Genauigkeit)

    Statt alles in 32 Bit zu rechnen, wählt autocast pro Rechenoperation das passende Format. Die Dokumentation nennt als Grund, dass manche Operationen in niedrigerer Genauigkeit deutlich schneller sind, während andere den Wertebereich von 32 Bit brauchen. Bei float16 gehören kleine Gradientenwerte zu den gefährdeten: Sie fallen laut Dokumentation auf null zurück – deshalb der Skalierer, der den Verlust vor dem Rückwärtsdurchlauf hochskaliert.

    import torch
    
    skalierer = torch.amp.GradScaler('cuda')
    
    with torch.autocast(device_type='cuda', dtype=torch.float16):
        ausgabe = modell(eingabe)
        verlust = kriterium(ausgabe, ziel)
    
    skalierer.scale(verlust).backward()
    skalierer.step(optimierer)
    skalierer.update()
    optimierer.zero_grad(set_to_none=True)
    
  5. 5.

    Fragmentierung angehen, bevor Hardware gekauft wird

    Meldet die Fehlermeldung, dass reichlich reserviert, aber wenig belegt ist, fehlt kein Speicher, sondern ein zusammenhängender Block. Das Verhalten des Zuteilers steuert laut Dokumentation die Umgebungsvariable PYTORCH_ALLOC_CONF. expandable_segments lässt Zuteilungen mitwachsen und hilft besonders bei schwankenden Batchgrößen; max_split_size_mb verhindert, dass große Blöcke zerschnitten werden, was laut Dokumentation die Fragmentierung verringern und Grenzfälle noch durchbringen kann.

    PYTORCH_ALLOC_CONF=expandable_segments:True python3 train.py
    
  6. 6.

    Den Cache leeren – und wissen, was das nicht bringt

    empty_cache() gibt laut Dokumentation ausschließlich den ungenutzten Cache frei, damit andere Anwendungen ihn bekommen. Von Tensoren belegter Speicher wird dabei nicht frei. Als Dauerlösung im Trainingsschritt ist der Aufruf deshalb nutzlos bis schädlich, weil der Zuteiler danach wieder beim Treiber anfragen muss. Sinnvoll ist er genau einmal zwischen zwei Phasen, etwa nach dem Training vor der Auswertung.

    import gc
    
    del zwischenergebnisse
    gc.collect()
    torch.cuda.empty_cache()
    
  7. 7.

    Bei hartnäckigen Fällen eine Momentaufnahme aufzeichnen

    PyTorch kann laut Dokumentation den Zustand des belegten CUDA-Speichers zu jedem Zeitpunkt aufzeichnen. Die entstehende Datei lässt sich in den Betrachter unter pytorch.org/memory_viz ziehen und zeigt als Zeitstrahl, welche Tensoren wann leben. Damit findet man den einen aufgehobenen Tensor, der die ganze Rechnung am Leben hält, statt weiter an der Batchgröße zu drehen.

    import torch
    
    torch.cuda.memory._record_memory_history(max_entries=100000)
    # hier den Durchlauf ausführen, der scheitert
    torch.cuda.memory._dump_snapshot('speicher.pickle')
    torch.cuda.memory._record_memory_history(enabled=None)
    

Häufige Fragen (FAQ)

?Warum reicht der Grafikspeicher nicht, obwohl die Gewichte rechnerisch hineinpassen?

Weil die Gewichte nur ein Posten von mehreren sind. Dazu kommen die Zwischenergebnisse jeder Schicht, die für den Rückwärtsdurchlauf aufgehoben werden müssen und mit der Batchgröße wachsen. Beim Training kommen die Zustände des Optimierers obendrauf: Adam führt laut PyTorch-Dokumentation pro Parameter ein erstes und ein zweites Moment mit, also zwei weitere Werte in Parametergröße. Und schließlich reserviert PyTorch den Speicher nicht Byte für Byte, sondern in Blöcken über einen zwischenspeichernden Zuteiler. Wer nur die Gewichte rechnet, rechnet den kleineren Teil.

?nvidia-smi zeigt belegten Speicher, obwohl mein Skript längst nichts mehr rechnet. Ist das ein Leck?

In aller Regel nein. PyTorch gibt freigewordene Blöcke nicht sofort an den Treiber zurück, sondern behält sie für die nächste Anforderung. Die Dokumentation sagt es wörtlich: nicht genutzter, vom Zuteiler verwalteter Speicher erscheint in nvidia-smi trotzdem als belegt. Deshalb ist nvidia-smi das falsche Messgerät für PyTorch-Prozesse. Aussagekräftig sind memory_allocated() für den von Tensoren wirklich belegten Speicher und memory_reserved() für das, was der Zuteiler insgesamt hält. Die Lücke zwischen beiden Zahlen ist der Cache und oft der Grund, warum eine Anforderung scheitert, obwohl rechnerisch noch Platz wäre.

?Ist Grafikspeicher dasselbe wie Arbeitsspeicher, nur woanders?

Nein, und diese Verwechslung kostet die meisten Nachmittage. Der Arbeitsspeicher hängt an der CPU, der Grafikspeicher sitzt fest auf der Grafikkarte und lässt sich nicht aufrüsten, nicht auslagern und nicht durch mehr RAM ergänzen. 64 GB im Rechner helfen einer Karte mit 8 GB kein Stück. Ein Modell, das nicht in den Grafikspeicher passt, passt nicht – es sei denn, du verkleinerst es über das Zahlenformat oder lässt Teile auf der CPU rechnen, was dann entsprechend langsam wird.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

  1. [1]
    CUDA semantics – PyTorch 2.13 documentation

    docs.pytorch.org · abgerufen am

  2. [2]
    Understanding CUDA Memory Usage – PyTorch 2.13 documentation

    docs.pytorch.org · abgerufen am

  3. [5]
    Adam – PyTorch 2.13 documentation

    docs.pytorch.org · abgerufen am

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWAnfängerfehlerRuntimeError: Expected all tensors to be on the same device

PyTorch: Expected all tensors to be on the same device

Modell auf der GPU, Daten auf der CPU: PyTorch verschiebt nichts von allein. Warum das so ist und wie beide Seiten auf dasselbe Gerät kommen.

SWLästig429

KI-Dienst meldet 429: zu viele Anfragen

Zu viele Anfragen, Kapazität erschöpft, 429: Der KI-Dienst ist nicht ausgefallen, sondern gedeckelt. Wiederholen hilft nur bei einer der Ursachen.