Dieser Artikel basiert auf den Ergebnissen einer kernelbasierten GPU-Spurinvestigation, die an einem realen PyTorch-Issue (#154318) mit eBPF-Uprobes durchgefÞhrt wurde. Die Spurdatenbanken werden im Ingero-Open-Source-Repository fÞr unabhÃĪngige ÃberprÞfung verÃķffentlicht.
TL;DR
PyTorchs DataLoader kann bis zu 50-124-mal langsamer sein als direktes Tensor-Indexing fÞr GPU-Arbeitslasten im Speicher. Wir reproduzierten ein reales PyTorch-Issue auf einem RTX 4090 und verfolgten jeden CUDA-API-Aufruf und jedes Linux-Kernel-Ereignis, um die Ursache zu finden. Die GPU war nicht langsam â sie hungerte. DataLoader-Worker erzeugten 200.000 CPU-Kontextwechsel und 300.000 Seitenzuweisungen in 40 Sekunden, was die GPU im Durchschnitt 301 ms pro DatenÞbertragung warten lieÃ, die normalerweise Mikrosekunden dauern sollte.
Das Problem
Ein PyTorch-Benutzer berichtete, dass DataLoader 7-22-mal langsamer war als direktes Tensor-Indexing fÞr eine einfache MLP-SchÃĪtzung. Selbst mit num_workers=12, pin_memory=True und prefetch_factor=12 blieb die LÞcke enorm. Die GPU-Auslastung lag bei 10-20%.
Wir reproduzierten es. Die LÞcke war auf unserem Hardware sogar noch schlimmer:
| Methode | Zeit | vs Direkt |
|---|---|---|
| Direktes Tensor-Indexing | 0,39 s | 1x |
| DataLoader (shuffle=True) | 48,49 s | 124-mal langsamer |
| DataLoader (optimiert, 4 Worker, pin_memory) | 43,29 s | 111-mal langsamer |
Die Arbeitslast ist trivial: 7 Mio. Proben, 100 Merkmale, 2-Schichten-MLP, Batch-GrÃķÃe 1 Mio. Das Modell verarbeitet einen Batch in Millisekunden. Wo geht also die Zeit hin?
Was nvidia-smi zeigt
Nichts NÞtzliches. Die GPU-Auslastung fluktuiert zwischen 0% und 30%. Der Speicherbedarf ist stabil. Die Temperatur ist in Ordnung. Die GPU ist offensichtlich unterausgelastet, aber nvidia-smi kann nicht erklÃĪren, warum.
Was torch.profiler zeigt
Der Berichterstatter versuchte PyTorchs integrierten Profiler und âerhielt keine nÞtzlichen Spurdatenâ. Dies ist eine hÃĪufige Frustration â Anwendungsprofiler kÃķnnen zeigen, welche CUDA-Kernel ausgefÞhrt werden, aber sie kÃķnnen nicht die Host-Seite-Scheduling-, Speicher- und Prozesslebenszyklus-Ereignisse sehen, die bestimmen, ob Daten rechtzeitig bei der GPU ankommen.
Was kernelbasiertes Tracing zeigt
Wir fÞhrten den Benchmark durch, wÃĪhrend wir sowohl CUDA-API-Aufrufe (Þber eBPF-Uprobes auf libcudart.so) als auch Linux-Kernel-Ereignisse (Scheduler-Kontextwechsel, Speicherseitenzuweisungen, Prozessforks) gleichzeitig verfolgten. Die Ergebnisse erzÃĪhlen die ganze Geschichte.
VollstÃĪndige Video-Anleitung: https://asciinema.org/a/RGwhPeXAPJdhXqxp
In dem Video verbanden wir einen offenen LLM (MiniMax-M2.7) mit der Spurdatenbank Þber MCP (Model Context Protocol):
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.jsonDie JSON-Konfiguration sagt dem MCP-Client, wo er den Ingero-Server und welche Spurdatenbank finden kann:
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
Dies gibt dem LLM direkten Zugriff auf die Spurdaten Þber 7 Tools: get_trace_stats, get_causal_chains, get_per_process_breakdown und andere. Der AI kann die Datenbank abfragen, CUDA-Ereignisse mit Kernel-Scheduling-Daten korrelieren und eine einfache Sprachdiagnose ohne manuelle Analyse erstellen.
4 HIGH-severity-Kausalketten
Der Kausalketten-Motor erkannte 4 High-SeveritÃĪts-Muster, alle mit der gleichen Ursache:
[HIGH] cudaStreamSync p99=42ms (1.638x p50=25us) - CPU 100% + 1.880 sched_switch-Ereignisse Zeitplan: [SYSTEM] CPU 100% [HOST ] 1.880 Kontextwechsel (21s off-CPU) [CUDA ] p99=42ms (1.638x p50=25us) Ursache: DataLoader-Worker kÃĪmpfen um CPU, massiver Speicherdruck
[HIGH] cudaLaunchKernel p99=24,67ms (349x p50=70us) - CPU 100% Wurzel: 34 sched_switch-Ereignisse [HIGH] cuMemAlloc p99=627us (4,0x p50) - CPU 100% [HIGH] cuLaunchKernel p99=106us (4,0x p50) - CPU 100%
Die cudaStreamSync p99 ist 1.638-mal die p50. Das ist nicht GPU-Langsamkeit â das ist die GPU, die auf Daten wartet, die nie rechtzeitig ankommen.
Abbildung 1: AI-erzeugte Analyse nach dem AusfÞhren von /investigate. Das Modell verwendete Ingeros 7 MCP-Tools, um die Spurdatenbank abzufragen und eine einfache SpracherklÃĪrung mit handlungsfÃĪhigen Empfehlungen direkt aus den Spurdaten zu erstellen.

Die prozessbasierte AufschlÞsselung
Hier wird es klar. Der Hauptprozess und seine 4 DataLoader-Worker sind als separate EntitÃĪten sichtbar:
Hauptprozess:
- cudaMemcpyAsync (Host-zu-GerÃĪt-Ãbertragung): Durchschnitt 301ms, Max 2,9 Sekunden - cudaStreamSync: p99 = 42ms (normalerweise 25us) - 1.567 Kontextwechsel, Durchschnitt 16ms off-CPU, schlimmster Stau 5 Sekunden - 799.018 Seitenzuweisungen
DataLoader-Worker 1: 52.863 Kontextwechsel, 89.338 Seitenzuweisungen, schlimmster Stau 5s DataLoader-Worker 2: 50.638 Kontextwechsel, 83.509 Seitenzuweisungen, schlimmster Stau 5s DataLoader-Worker 3: 49.361 Kontextwechsel, 70.035 Seitenzuweisungen, schlimmster Stau 5s DataLoader-Worker 4: 38.862 Kontextwechsel, 56.354 Seitenzuweisungen, schlimmster Stau 5s
Insgesamt Þber die Worker: ~191.000 Kontextwechsel und ~299.000 Seitenzuweisungen in 40 Sekunden.
Was dies bedeutet
Die DataLoader-Worker tun drei teure Dinge, die direktes Indexing vollstÃĪndig vermeidet:
- Shuffling und Indexing: DataLoader mit shuffle=True generiert eine zufÃĪllige Permutation von Indizes, dann wÃĪhlt jeder Worker seinen Anteil. Dies erfordert zufÃĪlligen Speicherzugriff Þber den gesamten 7-Mio.-Proben-Tensor â schlecht fÞr Cache-LokalitÃĪt und lÃķst Seitenfehler aus.
- Kollation und Kopie: Jeder Worker sammelt verstreute Proben in einem kontinuierlichen Batch-Tensor. Dies bedeutet die Zuweisung neuer Speicher (Seitenzuweisungen), das Kopieren von Daten aus zufÃĪlligen Orten (Cache-Verfehlungen) und die Serialisierung des Ergebnisses zurÞck zum Hauptprozess Þber gemeinsamen Speicher oder eine Warteschlange.
- Wettbewerb um CPU: Vier Worker plus der Hauptprozess auf einem 4-vCPU-System bedeuten stÃĪndige Unterbrechung. Jeder Worker wird 50.000-mal unterbrochen. Der schlimmste Stau ist 5 Sekunden â wÃĪhrenddessen hat die GPU nichts zu verarbeiten.
Mit direktem Indexing: X[i:i+batch_size] ist eine Zero-Copy-Ansicht eines kontinuierlichen Tensors, der bereits im Speicher ist. .to(device) lÃķst einen einzigen DMA-Transfer von einer einzigen kontinuierlichen Region aus. Keine Worker, kein Shuffling, keine Kollation, keine Cross-Prozess-Kopien, keine Kontextwechsel. Die GPU erhÃĪlt Daten in Mikrosekunden, nicht in Hunderten von Millisekunden.
Die LÃķsung
FÞr GPU-Arbeitslasten im Speicher, bei denen die gesamte Datenmenge in den RAM passt:
- Verwenden Sie keinen DataLoader. Direktes Indexing mit einem vorhergehenden Index-Array ist einfacher und 100-mal schneller:
indices = torch.randperm(num_samples) for i in range(0, num_samples, batch_size): batch = X[indices[i:i+batch_size]].to(device) output = model(batch)
- Wenn Sie DataLoader verwenden mÞssen, passen Sie num_workers an Ihre tatsÃĪchlichen CPU-Kerne minus 1 an. Auf einem 4-Kern-System reduziert num_workers=2 den Wettbewerb. FÞgen Sie persistent_workers=True hinzu, um den Fork-Overhead zu vermeiden.
- FÞr grÃķÃere Datenmengen als der Speicher, bei denen DataLoader notwendig ist, verschiebt sich die eigentliche Flaschenhals auf die Disk-I/O. Verwenden Sie prefetch_factor=2 (nicht hÃķher â mehr Prefetching bedeutet mehr Speicherdruck) und stellen Sie sicher, dass Ihr Speicher mithalten kann.
Das grÃķÃere Bild
Diese Untersuchung veranschaulicht ein Muster, das wir stÃĪndig in GPU-Arbeitslasten sehen: Die GPU ist schnell, der Host ist die Flasche, und GPU-Metriken kÃķnnen es nicht sehen. nvidia-smi berichtete eine geringe Auslastung, aber konnte nicht erklÃĪren, warum. torch.profiler erfasste CUDA-Kernel, aber verpasste die 200.000 Kontextwechsel, die in Benutzerbereichen stattfanden.
Der einzige Weg, das vollstÃĪndige Bild zu sehen, bestand darin, beide Seiten gleichzeitig zu verfolgen â CUDA-API-Aufrufe auf Bibliotheksebene und Linux-Kernel-Scheduling-Ereignisse â und sie nach Zeit und Prozess-ID zu korrelieren. Die Kausalkette âCPU 100% -> 1.880 sched_switch -> cudaMemcpyAsync 301ms -> cudaStreamSync 42msâ erzÃĪhlt die vollstÃĪndige Geschichte in einer Zeile. Ohne cross-stack-Tracing wÃĪre dies ein RÃĪtsel geblieben â wie es fÞr den ursprÞnglichen Berichterstatter der Fall war, der Wochen damit verbrachte, es zu debuggen.
Abbildung 2: Wenn man den Model fragt âWas ist das Kernproblem?â, identifiziert es die CPU-Ãberzeichnung als Ursache fÞr Host-Seite-Scheduling-VerzÃķgerungen. cudaLaunchKernel ging von 73us auf 25,8ms (356-mal langsamer) zurÞck, weil die CPU den Start nicht rechtzeitig einplanen konnte.

Probieren Sie es selbst aus
Reproduzieren Sie den Benchmark:
import torch, time from torch.utils.data import DataLoaderX = torch.randn(7_000_000, 100) model = torch.nn.Sequential( torch.nn.Linear(100, 512), torch.nn.ReLU(), torch.nn.Linear(512, 512), torch.nn.ReLU(), torch.nn.Linear(512, 10) ).cuda()
# Schneller Pfad start = time.time() with torch.no_grad(): for i in range(0, len(X), 1_048_576): model(X[i:i+1_048_576].cuda()) torch.cuda.synchronize() print(f'Direkt: {time.time()-start:.3f}s')
# Langsamer Pfad loader = DataLoader(X, batch_size=1_048_576, shuffle=True) start = time.time() with torch.no_grad(): for batch in loader: model(batch.cuda()) torch.cuda.synchronize() print(f'DataLoader: {time.time()-start:.3f}s')
Verfolgen Sie mit Ingero, um zu sehen, was unter der Haube passiert:
git clone https://github.com/ingero-io/ingero.git cd ingero && make build sudo ./bin/ingero trace --duration 60s # in einem Terminal python3 benchmark.py # in einem anderen Terminal ./bin/ingero explain --since 60s # nach Benchmark-Abschluss
Oder Þberspringen Sie die Reproduktion und erkunden Sie unsere Spurdaten direkt. Die Untersuchungsdatenbank (764KB) befindet sich im Repository:
# Kausalketten aus der Untersuchung anzeigen ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m# Prozessbasierte AufschlÞsselung (siehe DataLoader-Worker vs Hauptprozess) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m
# Verbinden Sie Ihren AI-Assistenten fÞr interaktive Untersuchung ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db
Untersuchen Sie mit AI (empfohlen). Der schnellste Weg, die Spur zu analysieren, besteht darin, jeden MCP-kompatiblen AI direkt mit der Datenbank zu verbinden. Keine manuelle Analyse erforderlich.
Erstellen Sie eine Konfigurationsdatei:
cat > /tmp/ingero-mcp-dataloader.json << 'EOF'
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
EOF
Dann verbinden Sie Ihr Modell:
# Mit Ollama + MiniMax (was wir im Video verwendet haben) ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json# Mit Claude Code claude --mcp-config /tmp/ingero-mcp-dataloader.json
# Mit jedem MCP-kompatiblen Client # FÞgen Sie die Konfiguration oben zu den MCP-Einstellungen Ihres AI hinzu
Geben Sie /investigate ein, um die gefÞhrte Analyse auszulÃķsen, oder stellen Sie eine Frage: âWas verursachte den GPU-Hunger?â Der AI hat Zugriff auf 7 Tools, die die Spurdatenbank direkt abfragen.
GitHub: github.com/ingero-io/ingero
UrsprÞngliches Issue: pytorch/pytorch#154318
Video-Anleitung: https://asciinema.org/a/RGwhPeXAPJdhXqxp
Untersuchung durchgefÞhrt auf TensorDock RTX 4090 (24GB), Ubuntu 22.04, PyTorch 2.10.0+cu128.













