PyTorch GPU-/CUDA-Beschleunigung

Die Kernoperationen des Deep Learning sind Matrixmultiplikation im großen Maßstab und elementweise Operationen. CPUs sind darauf ausgelegt, komplexe serielle Logik zu verarbeiten, typischerweise mit 8 bis 64 Kernen; GPUs hingegen verfügen über Tausende einfacher paralleler Kerne, was sie natürlicherweise für diese Art hochgradig paralleler numerischer Berechnung geeignet macht. PyTorch ruft über NVIDIAsCUDA(Compute Unified Device Architecture)-Framework die GPU auf, was das Training um Dutzende oder sogar das Hundertfache beschleunigen kann.


1. Unterschiede zwischen CPU und GPU

Die Verbesserung der GPU-Trainingsgeschwindigkeit kommt hauptsächlich aus zwei Aspekten: erstens die parallele Ausführung einer großen Anzahl identischer Berechnungen; zweitens ermöglicht der GPU-Speicher mit hoher Bandbreite einen Datentransfer, der viel schneller als CPU-Speicher ist.

Bei rechenintensiven Operationen wie der Matrixmultiplikation ist der Beschleunigungseffekt besonders deutlich.

Vergleichspunkt CPU GPU(NVIDIA)
Anzahl der Kerne 8-64 große Kerne Tausende kleiner Kerne (CUDA Cores)
Designziel Serielle Verarbeitung mit geringer Latenz Hochdurchsatz-Parallelrechnen
Speicherbandbreite ~50~100 GB/s ~500~3000 GB/s
Matrixmultiplikationsgeschwindigkeit Baseline 10x-100x schneller
PyTorch-Schnittstelle "cpu" "cuda"

Bei Verwendung von Apple Silicon (M1/M2/M3) kann PyTorch über dasmpsBackend, Metal-GPU-Beschleunigung. Die Verwendung ist nahezu identisch mit CUDA; ändern Sie einfach das Gerät auf"mps"。


2. Erkennen der CUDA-Umgebung

Bevor Sie die GPU verwenden, müssen Sie bestätigen, ob in der aktuellen Umgebung ein CUDA-fähiges PyTorch installiert ist.

Prüfen Sie außerdem, ob im System verfügbare GPU-Geräte vorhanden sind.

Beispiel

import torch

# Ob CUDA unterstützt wird
print("CUDA verfügbar:", torch.cuda.is_available())

# Anzahl der GPU-Geräte
print("GPU-Anzahl:", torch.cuda.device_count())

# Index der aktuellen Standard-GPU
print("Aktuelle GPU:", torch.cuda.current_device())

# Name des GPU-Modells
print("GPU-Modell:", torch.cuda.get_device_name(0))

# PyTorch-Version und die CUDA-Version, mit der PyTorch kompiliert wurde
print("PyTorch-Version:", torch.__version__)
print("CUDA-Version:", torch.version.cuda)

Beispielausgabe:

CUDA 可用: True
GPU 数量: 1
当前 GPU: 0
GPU 型号: NVIDIA GeForce RTX 4090
PyTorch 版本: 2.3.0+cu121
CUDA 版本: 12.1

2.1 Dynamische Auswahl eines Geräts (Empfohlene Vorgehensweise)

Hartcodierung im Code"cuda"Dies wird bei Maschinen ohne GPU direkt einen Fehler auslösen.

Beispiel

import torch

# Methode 1: Klassischer Ansatz, beste Kompatibilität
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Methode 2: Empfohlen für PyTorch 2.0+, unterstützt automatisch CUDA / MPS / CPU
device = (
    "cuda" if torch.cuda.is_available()
    else "mps" if torch.backends.mps.is_available()
    else "cpu"
)

print(f"Using device: {device}")

3. Verschieben von Tensoren zwischen Geräten

Tensoren in PyTorch werden standardmäßig auf der CPU erstellt.

Um GPU-Berechnung zu nutzen, müssen Sie Tensoren explizit auf die GPU verschieben oder sie direkt auf der GPU erstellen.

3.1 Grundlegende Verschiebungsmethoden

Beispiel

import torch

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Einen CPU-Tensor erstellen
cpu_tensor = torch.tensor([1.0, 2.0, 3.0])
print(cpu_tensor.device)   # cpu

# Methode 1: .to(device) — empfohlen, am vielseitigsten
gpu_tensor = cpu_tensor.to(device)

# Methode 2: .cuda() — nur in CUDA-Umgebung
gpu_tensor = cpu_tensor.cuda()

# Methode 3: Gerät direkt beim Erstellen angeben
gpu_tensor = torch.tensor([1.0, 2.0, 3.0], device=device)
gpu_tensor = torch.randn(3, 4, device=device)

# Zurück zur CPU verschieben (zum Ausgeben, für numpy-Konvertierung, zum Speichern usw.)
back_to_cpu = gpu_tensor.cpu()

print(gpu_tensor.device)    # cuda:0
print(back_to_cpu.device)   # cpu

Beim Konvertieren eines GPU-Tensors in numpy müssen Sie ihn zuerst zurück auf die CPU verschieben, und wenn der Tensor Gradienten hat, müssen Sie ihn außerdem zuerst mit detach abtrennen:

Beispiel

# Einen gewöhnlichen GPU-Tensor in numpy konvertieren
arr = gpu_tensor.cpu().numpy()

# Einen GPU-Tensor mit Gradienten in numpy konvertieren
arr = gpu_tensor.detach().cpu().numpy()

3.2 Gerätekonsistenz-Bedingung

Tensoren auf verschiedenen Geräten können nicht direkt an derselben Operation teilnehmen.

Andernfalls wird eine Ausnahme ausgelöst:RuntimeError:

Beispiel

a = torch.randn(3).to("cuda")
b = torch.randn(3)           # Auf CPU

# c = a + b # RuntimeError: Erwartet, dass sich alle Tensoren auf demselben Gerät befinden

# Korrekte Vorgehensweise: Zuerst das Gerät vereinheitlichen
c = a + b.to("cuda")

Überprüfen Sie das Gerät, auf dem sich der Tensor befindet:

Beispiel

x = torch.randn(3, 4).to(device)

print(x.device)           # cuda:0
print(x.is_cuda)          # True
print(x.get_device())     # 0 (GPU-Index)

3.3 Überprüfung des Geschwindigkeitsvergleichs

Beispiel

import torch
import time

device = torch.device("cuda")
n = 5000

# CPU-Matrixmultiplikation
a_cpu = torch.randn(n, n)
b_cpu = torch.randn(n, n)
start = time.time()
c_cpu = torch.matmul(a_cpu, b_cpu)
print(f"CPU-Zeit: {time.time() - start:.3f}s")

# GPU-Matrixmultiplikation
a_gpu = a_cpu.to(device)
b_gpu = b_cpu.to(device)
torch.cuda.synchronize()    # Stellen Sie sicher, dass die Datenübertragung abgeschlossen ist, bevor Sie die Zeitmessung starten
start = time.time()
c_gpu = torch.matmul(a_gpu, b_gpu)
torch.cuda.synchronize()    # Warten Sie, bis die GPU die Ausführung beendet hat, bevor Sie die Zeitmessung stoppen
print(f"GPU-Zeit: {time.time() - start:.3f}s")

Beispielausgabe:

CPU 耗时: 1.847s
GPU 耗时: 0.021s

GPU-Berechnung wirdasynchron ausgeführt—nach der Rückkehr des Python-Aufrufs ist die GPU-Operation möglicherweise noch nicht abgeschlossen. Bei der Zeitmessung müssen Sietorch.cuda.synchronize()warten, bis die GPU tatsächlich fertig ist; andernfalls sind die Messergebnisse ungenau.


4. Verschieben des Modells auf die GPU

Alle Parameter des Modells (weight、bias) sind im Wesentlichen Tensoren.

Diese Parameter müssen ebenfalls auf die GPU verschoben werden, um Vorwärts- und Rückwärtspropagation auf der GPU durchzuführen.

Aufruf.to(device)auf das gesamte Modell, und PyTorch wird automatisch alle internen Parameter durchlaufen und verschieben.

Beispiel

import torch
import torch.nn as nn

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(784, 256),
            nn.ReLU(),
            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Linear(128, 10),
        )

    def forward(self, x):
        return self.net(x)

# Verschiebe das Modell auf die GPU, muss nur einmal aufgerufen werden
model = SimpleNet().to(device)

# Überprüfe, dass sich alle Parameter auf der GPU befinden
for name, param in model.named_parameters():
    print(f"{name}: {param.device}")
# net.0.weight: cuda:0
# net.0.bias: cuda:0
# ...

# Eingabedaten müssen sich ebenfalls auf demselben Gerät befinden
x = torch.randn(32, 784).to(device)
output = model(x)
print(output.shape)   # torch.Size([32, 10])

Wenn das Modell auf der GPU liegt, die Eingabedaten sich aber noch auf der CPU befinden, löst der Forward-Pass einen Fehler aus. Achten Sie darauf, nachdem der DataLoader jeden Batch gelesen hat,inputsundlabelsfür alle .to(device) aufzurufen..to(device)。


5. Vollständiger Trainingsworkflow

Das Folgende ist eine Standard-GPU-Trainingsvorlage, die Datenladen, Modelltraining und Validierungsauswertung umfasst.

Beispiel

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# 1. Gerätekonfiguration
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Trainingsgerät: {device}")

# 2. Datenladen
# pin_memory=True: Fixiert Daten im Speicher, um die CPU -> GPU-Übertragung zu beschleunigen
# num_workers: Multiprozess-Vorladen, um die Datenwartezeit zu reduzieren
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,)),
])
train_dataset = datasets.MNIST(root="./data", train=True,
                                download=True, transform=transform)
test_dataset  = datasets.MNIST(root="./data", train=False,
                                download=True, transform=transform)

train_loader = DataLoader(train_dataset, batch_size=256, shuffle=True,
                          num_workers=4, pin_memory=True)
test_loader  = DataLoader(test_dataset,  batch_size=256, shuffle=False,
                          num_workers=4, pin_memory=True)

# 3. Modell definieren
class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(64 * 7 * 7, 256), nn.ReLU(), nn.Dropout(0.5),
            nn.Linear(256, 10),
        )

    def forward(self, x):
        return self.classifier(self.features(x))

model     = CNN().to(device)     # Modell auf GPU verschieben
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)

# 4. Trainingsfunktion
def train_epoch(model, loader, optimizer, criterion):
    model.train()
    total_loss, correct = 0.0, 0
    for inputs, labels in loader:
        # non_blocking=True: Asynchroner Transfer, CPU kann weiterhin den nächsten Datenstapel vorbereiten
        inputs = inputs.to(device, non_blocking=True)
        labels = labels.to(device, non_blocking=True)

        optimizer.zero_grad()
        outputs = model(inputs)
        loss    = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        total_loss += loss.item() * inputs.size(0)
        correct    += (outputs.argmax(1) == labels).sum().item()

    n = len(loader.dataset)
    return total_loss / n, correct / n

# 5. Validierungsfunktion
def eval_epoch(model, loader, criterion):
    model.eval()
    total_loss, correct = 0.0, 0
    with torch.no_grad():
        for inputs, labels in loader:
            inputs = inputs.to(device, non_blocking=True)
            labels = labels.to(device, non_blocking=True)
            outputs = model(inputs)
            loss    = criterion(outputs, labels)
            total_loss += loss.item() * inputs.size(0)
            correct    += (outputs.argmax(1) == labels).sum().item()
    n = len(loader.dataset)
    return total_loss / n, correct / n

# 6. Haupttrainingsschleife
for epoch in range(1, 11):
    train_loss, train_acc = train_epoch(model, train_loader, optimizer, criterion)
    val_loss,   val_acc   = eval_epoch(model,  test_loader,  criterion)
    print(f"Epoche {epoch:02d} | "
          f"Trainingsverlust: {train_loss:.4f}, Genauigkeit: {train_acc:.4f} | "
          f"Validierungsverlust: {val_loss:.4f}, Genauigkeit: {val_acc:.4f}")

Wichtige Punkte:

  • deviceVariablen werden ganz oben im Code deklariert, global referenziert, niemals hartkodiert"cuda"
  • Modell.to(device)muss nur einmal aufgerufen werden
  • Für jeden Batch,inputsundlabelsmuss ebenfalls.to(device)
  • DataLoader-Einstellungenpin_memory=Trueundnon_blocking=TrueDatenübertragung beschleunigen
  • Verwendung in der Validierungsphasetorch.no_grad()Gradienten deaktivieren, um Speicher zu sparen

6. Multi-GPU-Training

Wenn der Speicher einer einzelnen GPU nicht ausreicht, sollten Sie den Einsatz von parallelem Multi-GPU-Training in Betracht ziehen.

Darüber hinaus kann Multi-GPU die Trainingsgeschwindigkeit weiter verbessern. PyTorch bietet zwei Hauptansätze.

6.1 DataParallel

Dies ist der einfachste Multi-GPU-Ansatz.

Es verwendet einen einzelnen Prozess, teilt jeden Batch gleichmäßig auf die GPUs auf, führt die Vorwärtspropagation parallel aus und aggregiert die Gradienten auf der Haupt-GPU für Aktualisierungen. Es ist einfach zu verwenden, aber die Haupt-GPU ist stark ausgelastet, die Auslastung über die GPUs ist ungleichmäßig und es eignet sich für einen schnellen Einstieg.

Beispiel

import torch
import torch.nn as nn

model = CNN()

if torch.cuda.device_count() > 1:
    print(f"Using {torch.cuda.device_count()} GPUs")
    model = nn.DataParallel(model)

    # Sie können auch angeben, welche GPUs verwendet werden sollen
    # model = nn.DataParallel(model, device_ids=[0, 1])

model = model.to("cuda")

# Der nachfolgende Trainingscode ist genau derselbe wie bei Single-GPU
# DataParallel teilt den Batch automatisch gleichmäßig auf die GPUs auf und aggregiert die Ergebnisse

Wenn Sie auf die Attribute des ursprünglichen Modells zugreifen müssen (z. B. benutzerdefinierte Methoden), müssen Sie.modulegreifen Sie wie folgt darauf zu:

Beispiel

# Nachdem model von DataParallel umhüllt wurde, befindet sich das ursprüngliche Modell in model
print(model.module.classifier)

# Beim Speichern des Modells wird empfohlen, model.module zu speichern, damit das Laden mit einer einzelnen GPU möglich ist
torch.save(model.module.state_dict(), "model.pth")

6.2 DistributedDataParallel

Dies ist der Multiprozess-Ansatz.

Jeder Prozess ist an eine GPU gebunden, jede Karte enthält eine vollständige Modellkopie, und Gradienten werden über All-Reduce synchronisiert. Es ist die empfohlene Lösung für Produktion und großskaliges Training, mit weitaus höherer Effizienz als DataParallel.

Beispiel

import torch
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
from torch.utils.data.distributed import DistributedSampler

def main(rank, world_size):
    # Prozessgruppe initialisieren; das NCCL-Backend ist für NVIDIA-GPUs optimiert
    dist.init_process_group(backend="nccl", rank=rank, world_size=world_size)

    # Binde jeden Prozess an seine entsprechende GPU
    torch.cuda.set_device(rank)
    device = torch.device(f"cuda:{rank}")

    # Mit DDP umschließen, nachdem das Modell auf die entsprechende GPU verschoben wurde
    model = CNN().to(device)
    model = DDP(model, device_ids=[rank])

    # DistributedSampler im DataLoader verwenden, um sicherzustellen, dass Daten nicht über GPUs hinweg überlappen
    sampler = DistributedSampler(train_dataset,
                                  num_replicas=world_size, rank=rank)
    loader  = DataLoader(train_dataset, batch_size=64,
                         sampler=sampler, pin_memory=True)

    # Trainingslogik ist identisch mit Single-GPU
    # ...

    dist.destroy_process_group()

# Wie starten (empfohlen: torchrun):
# torchrun --nproc_per_node=4 train_ddp.py
Vergleichspunkt DataParallel DistributedDataParallel
Anzahl der Prozesse Einzelner Prozess Mehrere Prozesse (einer pro GPU)
Kommunikations-Backend Python-GIL-Einschränkung NCCL (effizient)
Haupt-GPU-Last Schwer (Gradientenaggregation) Ausgewogen (All-Reduce)
Codeänderungen Minimal Moderat
Anwendbare Szenarien Schnelle Experimentierung Produktionstraining

7. Mixed-Precision-Training AMP

Training verwendet standardmäßig float32 (FP32)-Präzision.

Automatische gemischte Präzision (AMP)Verwendet float16 (FP16) oder bfloat16 (BF16) für einige Berechnungen, was erhebliche Vorteile bei nahezu keinem Präzisionsverlust bringt:

  • Speicherverbrauch um etwa 50% reduziert
  • Trainingsgeschwindigkeit um 2x–3x verbessert (Hardwarebeschleunigung durch Tensor Core)
  • Sehr wenige Codeänderungen erforderlich; nur drei Änderungen

Beispiel

from torch.cuda.amp import autocast, GradScaler

model     = CNN().to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-3)
scaler    = GradScaler()    # FP16-Gradientenskalierer, um zu verhindern, dass Gradienten auf null unterlaufen

for epoch in range(num_epochs):
    model.train()
    for inputs, labels in train_loader:
        inputs = inputs.to(device, non_blocking=True)
        labels = labels.to(device, non_blocking=True)

        optimizer.zero_grad()

        # Änderung 1: FP16/FP32 automatisch im autocast-Bereich auswählen
        with autocast(device_type="cuda"):
            outputs = model(inputs)
            loss    = criterion(outputs, labels)

        # Änderung 2: den Loss mit scaler vor der Backpropagation skalieren
        scaler.scale(loss).backward()

        # Änderung 3: scaler aktualisiert Parameter und übernimmt die Gradientenskalierung intern automatisch
        scaler.step(optimizer)
        scaler.update()

7.1 Auswahl zwischen FP16 und BF16

Format Präzisionsbits Exponentenbits Geeignete Hardware Eigenschaften
float16 10 Bits 5 Bits RTX 20/30/40-Serie Erfordert GradScaler, um Überlauf zu verhindern
bfloat16 7 Bits 8 Bits A100 / H100 / RTX 4090 Gleicher Bereich wie FP32, stabileres Training

Wenn deine GPU BF16 unterstützt, verwende es bevorzugt; kein GradScaler erforderlich:

Beispiel

# BF16-Verwendung (PyTorch 2.0+)
with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
    outputs = model(inputs)
    loss    = criterion(outputs, labels)

loss.backward()
optimizer.step()

8. Tipps zur Leistungsoptimierung

Dieser Abschnitt stellt mehrere Techniken zur Optimierung der Speichernutzung und der Trainingsgeschwindigkeit vor.

8.1 GPU-Speicherverwaltung

Beispiel

# Aktuelle Speichernutzung anzeigen
print(f"Zugewiesen: {torch.cuda.memory_allocated() / 1024**2:.1f} MB")
print(f"Zwischengespeichert: {torch.cuda.memory_reserved() / 1024**2:.1f} MB")

# Detaillierten Speicherbericht ausgeben
print(torch.cuda.memory_summary())

# Cache-Pool leeren (gibt zugewiesenen Speicher nicht frei)
torch.cuda.empty_cache()

# inference_mode während der Inferenz verwenden (schneller als no_grad, deaktiviert die Gradient-Engine vollständig)
with torch.inference_mode():
    output = model(x)

# Gradient-Checkpointing: Neuberechnung gegen Speicher tauschen (häufig beim Training großer Modelle verwendet)
from torch.utils.checkpoint import checkpoint
output = checkpoint(model_block, x)

8.2 DataLoader-Optimierung

Beispiel

loader = DataLoader(
    dataset,
    batch_size=256,
    num_workers=4,              # Mehrprozess-Prefetching; empfohlen, auf 50% der CPU-Kerne einzustellen
    pin_memory=True,            # Pinned Memory, um den CPU -> GPU-Transfer zu beschleunigen
    persistent_workers=True,    # Worker-Prozesse am Leben halten, um das Neuerstellen von Prozessen in jeder Epoche zu vermeiden
    prefetch_factor=2,          # Anzahl der pro Worker vorab geladenen Batches
)

8.3 torch.compile Modellkompilierung (PyTorch 2.0+)

Eine Codezeile kompiliert und optimiert den Berechnungsgraphen und verbessert die Geschwindigkeit um 30%–200% (je nach Modellarchitektur):

Beispiel

# Modell kompilieren; der erste Lauf hat Kompilierungs-Overhead, nachfolgende Iterationen sind deutlich schneller
model = torch.compile(model)

# Kompromisse zwischen verschiedenen Kompilierungsmodi
model = torch.compile(model, mode="default")          # Ausgewogen, für die meisten Szenarien geeignet
model = torch.compile(model, mode="reduce-overhead")  # Reduziert den Scheduling-Overhead
model = torch.compile(model, mode="max-autotune")     # Maximale Optimierung, längere Kompilierungszeit

8.4 Gradientenakkumulation (Simulation großer Batches)

Wenn der Speicher nicht ausreicht, kann die Gradientenakkumulation eine größere Batch-Größe simulieren, ohne den Speicherverbrauch tatsächlich zu erhöhen:

Beispiel

accumulation_steps = 8    # Parameter alle 8 Batches aktualisieren, entspricht batch_size × 8

for i, (inputs, labels) in enumerate(train_loader):
    inputs = inputs.to(device)
    labels = labels.to(device)

    outputs = model(inputs)
    loss    = criterion(outputs, labels) / accumulation_steps   # Verlust gleichmäßig aufteilen
    loss.backward()    # Gradienten akkumulieren, ohne sie auf null zu setzen

    if (i + 1) % accumulation_steps == 0:
        optimizer.step()
        optimizer.zero_grad()    # Gradienten erst nach Parameter-Update auf null setzen

8.5 Strategien bei unzureichendem GPU-Speicher

Strategie Methode Wirkung
Batch-Größe reduzieren 256 -> 64 Reduziert den Speicher linear
Gemischte Präzision AMP + FP16/BF16 Speicher um etwa 50% reduziert
Gradientenakkumulation Alle N Schritte aktualisieren Großen Batch simulieren, ohne den Speicher zu erhöhen
Gradienten-Checkpointing torch.utils.checkpoint Reduziert den Speicher erheblich, verringert jedoch die Trainingsgeschwindigkeit
Einige Parameter einfrieren Backbone beim Transfer Learning einfrieren Reduziert die Speichernutzung der Backpropagation

9. Häufige Fehler und Fehlerbehebung

Dieser Abschnitt fasst die häufigsten Fehler beim GPU-Training und ihre Lösungen zusammen.

9.1 RuntimeError: Expected all tensors to be on the same device

Ursache: Die an der Operation beteiligten Tensoren befinden sich auf verschiedenen Geräten (einer auf der CPU, einer auf der GPU).

Beispiel

# Fehlerbehebung: Gerät jedes Tensors ausgeben
print(inputs.device, labels.device, next(model.parameters()).device)

# Lösung: sicherstellen, dass .to(device) für jeden Batch ausgeführt wird
inputs = inputs.to(device)
labels = labels.to(device)

9.2 RuntimeError: CUDA out of memory

Ursache: Speichermangel, häufig aufgrund zu großer Batch-Größe, eines zu großen Modells oder versehentlicher Anhäufung von Berechnungsgraphen.

Beispiel

# Häufige Fehlerquelle: loss.item() wird in der Schleife nicht aufgerufen, wodurch sich Berechnungsgraphen ansammeln
# Falscher Code
total_loss += loss           # loss ist ein Tensor, der den gesamten Berechnungsgraphen enthält

# Korrekter Code
total_loss += loss.item()    # .item() extrahiert einen Python-Skalar und gibt den Berechnungsgraphen frei

# Weitere Schritte zur Fehlerbehebung:
# 1. batch_size reduzieren
# 2. Sicherstellen, dass die Validierungsschleife torch.no_grad() verwendet
# 3. torch.cuda.empty_cache() aufrufen, um den Cache zu leeren
# 4. torch.cuda.memory_summary() verwenden, um die Quelle der GPU-Speichernutzung zu lokalisieren

9.3 numpy() kann nicht für einen Tensor aufgerufen werden, der Gradienten benötigt

Grund: Tensoren mit Gradienten können nicht direkt in numpy-Arrays umgewandelt werden.

Beispiel

# Falscher Code
arr = gpu_tensor.numpy()

# Korrekter Code
arr = gpu_tensor.detach().cpu().numpy()

9.4 CUDA-Fehler: geräteseitiger Assert ausgelöst

Grund: Normalerweise sind es Label-Werte außerhalb des gültigen Bereichs (z. B. 10 Klassen, aber der Label-Wert ist gleich 10) oder ein Array-Index außerhalb des gültigen Bereichs. Die Fehlermeldung wird auf der GPU erzeugt, und die standardmäßig angezeigte Stelle ist ungenau.

Beispiel

# Umgebungsvariable setzen, damit Fehler an der exakten Codezeile ausgelöst werden (wird zu synchroner Ausführung, langsamer)
import os
os.environ["CUDA_LAUNCH_BLOCKING"] = "1"

9.5 Trainingsgeschwindigkeit verbessert sich nicht (geringe GPU-Auslastung)

Grund: Das Laden der Daten wird zum Engpass; die GPU verbringt die meiste Zeit damit, darauf zu warten, dass die CPU Daten vorbereitet.

Schritte zur Fehlerbehebung:

  1. Führen Sienvidia-smioderwatch -n 1 nvidia-smiGPU-Auslastung beobachten
    • Eine konstant unter 80% liegende Auslastung deutet auf einen Daten-Engpass hin.
  2. num_workers von DataLoader erhöhen
  3. pin_memory=True aktivieren
  4. Datenvorverarbeitung auf die GPU verlagern (torchvision.transforms unterstützt GPU-Operationen)
  5. Datensätze mit kleinen Dateien in den Speicher vorladen

9.6 PyTorch Profiler zur Lokalisierung von Engpässen verwenden

Beispiel

from torch.profiler import profile, ProfilerActivity

with profile(
    activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
    record_shapes=True,
) as prof:
    for i, (inputs, labels) in enumerate(train_loader):
        if i >= 10:
            break
        inputs = inputs.to(device)
        labels = labels.to(device)
        loss   = criterion(model(inputs), labels)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

# Nach CUDA-Zeit sortieren, die obersten 15 Einträge ausgeben
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=15))

10. API-Kurzreferenz

Die folgende Tabelle ist eine Kurzreferenz für gängige PyTorch-GPU-APIs.

10.1 Geräteverwaltung

Aktion Code
CUDA-Verfügbarkeit erkennen torch.cuda.is_available()
Gerät auswählen torch.device("cuda" if ... else "cpu")
Anzahl der GPUs torch.cuda.device_count()
GPU-Name torch.cuda.get_device_name(0)
Aktuelle GPU festlegen torch.cuda.set_device(0)
Warten, bis die GPU fertig ist torch.cuda.synchronize()

10.2 Tensor-Operationen

Aktion Code
Tensor auf GPU verschieben tensor.to(device)odertensor.cuda()
Tensor auf CPU verschieben tensor.cpu()
Aktuelles Gerät anzeigen tensor.device
Ob es auf der GPU liegt tensor.is_cuda
GPU-Tensor in numpy umwandeln tensor.detach().cpu().numpy()
Asynchroner Transfer tensor.to(device, non_blocking=True)

10.3 Modelloperationen

Aktion Code
Modell auf GPU verschieben model.to(device)
Trainingsmodus aktivieren model.train()
Inferenzmodus aktivieren model.eval()
Kompilierungsbeschleunigung torch.compile(model)
Gradienten deaktivieren with torch.no_grad():
Inferenzmodus with torch.inference_mode():

10.4 GPU-Speicherverwaltung

Aktion Code
Zugewiesener GPU-Speicher torch.cuda.memory_allocated()
Zwischengespeicherter GPU-Speicher torch.cuda.memory_reserved()
GPU-Speicherbericht torch.cuda.memory_summary()
Cache leeren torch.cuda.empty_cache()

10.5 Kurzreferenz der Kernprinzipien

1. 用 device 变量统一管理,不要硬编码 "cuda"
2. 模型和数据必须在同一设备上,每个 batch 都要 .to(device)
3. 验证和推理时一定使用 torch.no_grad() oder  torch.inference_mode()
4. 生产训练推荐开启 AMP 混合精度,几乎免费获得 2x 加速
5. DataLoader 设置 pin_memory=True 和 num_workers >= 4 减少数据瓶颈
6. PyTorch 2.0+ 可以用 torch.compile(model) 一行提速 30%+
7. GPU 计算是异步的,精确计时需要调用 torch.cuda.synchronize()
Weitere Erweiterungen