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
# 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
# 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
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
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
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
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 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 Sie
torch.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.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.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-Einstellungen
pin_memory=Trueundnon_blocking=TrueDatenübertragung beschleunigen - Verwendung in der Validierungsphase
torch.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.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
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.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
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
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
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
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
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
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
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
# 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
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
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:
- Führen Sie
nvidia-smioderwatch -n 1 nvidia-smiGPU-Auslastung beobachten- Eine konstant unter 80% liegende Auslastung deutet auf einen Daten-Engpass hin.
- num_workers von DataLoader erhöhen
- pin_memory=True aktivieren
- Datenvorverarbeitung auf die GPU verlagern (torchvision.transforms unterstützt GPU-Operationen)
- Datensätze mit kleinen Dateien in den Speicher vorladen
9.6 PyTorch Profiler zur Lokalisierung von Engpässen verwenden
Beispiel
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