Dit artikel is gebaseerd op de resultaten van een kernel-niveau GPU-trace-onderzoek dat is uitgevoerd op een echt PyTorch-probleem (#154318) met behulp van eBPF-uprobes. Trace-databases worden gepubliceerd in de Ingero-open-source-repository voor onafhankelijke verificatie.
TL;DR
PyTorch’s DataLoader kan 50-124x langzamer zijn dan directe tensor-indexering voor in-memory GPU-werkbelasting. We hebben een echt PyTorch-probleem gereproduceerd op een RTX 4090 en elke CUDA-API-aanroep en Linux-kernel-gebeurtenis getraceerd om de oorzaak te vinden. De GPU was niet langzaam – het was hongerig. DataLoader-werkers genereerden 200.000 CPU-context-switches en 300.000 pagina-toewijzingen in 40 seconden, waardoor de GPU gemiddeld 301ms per gegevensoverdracht moest wachten die normaal gesproken microseconden zou duren.
Het Probleem
Een PyTorch-gebruiker rapporteerde dat DataLoader 7-22x langzamer was dan directe tensor-indexering voor een eenvoudige MLP-inferentie-werkbelasting. Zelfs met num_workers=12, pin_memory=True en prefetch_factor=12 bleef de kloof enorm. GPU-gebruik zat op 10-20%.
We hebben het gereproduceerd. De kloof was nog erger op onze hardware:
| Methode | Tijd | vs Direct |
|---|---|---|
| Directe tensor-indexering | 0,39s | 1x |
| DataLoader (shuffle=True) | 48,49s | 124x langzamer |
| DataLoader (geoptimaliseerd, 4 werknemers, pin_memory) | 43,29s | 111x langzamer |
De werkbelasting is triviaal: 7M samples, 100 kenmerken, 2-laags MLP, batchgrootte 1M. Het model verwerkt een batch in milliseconden. Dus waar gaat de tijd heen?
Wat nvidia-smi Laat Zien
Niets nuttigs. GPU-gebruik schiet heen en weer tussen 0% en 30%. Geheugengebruik is stabiel. Temperatuur is in orde. De GPU is duidelijk onderbenut, maar nvidia-smi kan niet vertellen waarom.
Wat torch.profiler Laat Zien
De rapporteur probeerde PyTorch’s ingebouwde profiler en “kreeg geen zinvolle trace-gegevens.” Dit is een veelvoorkomende frustratie – applicatie-niveau-profilers kunnen laten zien welke CUDA-kernels worden uitgevoerd, maar ze kunnen de host-side-scheduling, geheugen- en proces-levensevents die bepalen of gegevens op tijd bij de GPU aankomen, niet zien.
Wat Kernel-Niveau Tracing Laat Zien
We hebben de benchmark uitgevoerd terwijl we zowel CUDA-API-aanroepen (via eBPF-uprobes op libcudart.so) als Linux-kernel-gebeurtenissen (scheduler-context-switches, geheugen-pagina-toewijzingen, proces-forks) tegelijkertijd traceerden. De resultaten vertellen het complete verhaal.
Volledige video-walkthrough: https://asciinema.org/a/RGwhPeXAPJdhXqxp
In de video hebben we een open-weight LLM (MiniMax-M2.7) verbonden met de trace-database via MCP (Model Context Protocol):
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.jsonDe JSON-config vertelt de MCP-client waar de Ingero-server en welke trace-database te vinden zijn:
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
Dit geeft de LLM directe toegang tot de trace-gegevens via 7 tools: get_trace_stats, get_causal_chains, get_per_process_breakdown en anderen. De AI kan de database ondervragen, CUDA-gebeurtenissen correleren met kernel-scheduling-gegevens en een eenvoudige taal-diagnose produceren zonder enige handmatige analyse.
4 HOGE-risico-causale ketens
De causale keten-engine detecteerde 4 hoge-risico-patronen, allemaal met dezelfde oorzaak:
[HOGE] cudaStreamSync p99=42ms (1.638x p50=25us) - CPU 100% + 1.880 sched_switch-gebeurtenissen Tijdlijn: [SYSTEM] CPU 100% [HOST ] 1.880 context-switches (21s off-CPU) [CUDA ] p99=42ms (1.638x p50=25us) Oorzaak: DataLoader-werkers vechten om CPU, massive pagina-toewijzingsdruk
[HOGE] cudaLaunchKernel p99=24,67ms (349x p50=70us) - CPU 100% Root: 34 sched_switch-gebeurtenissen [HOGE] cuMemAlloc p99=627us (4,0x p50) - CPU 100% [HOGE] cuLaunchKernel p99=106us (4,0x p50) - CPU 100%
De cudaStreamSync p99 is 1.638 keer de p50. Dat is geen GPU-trageheid – dat is de GPU die wacht op gegevens die nooit op tijd aankomen.
Figuur 1: AI-gegenereerde analyse na het uitvoeren van /investigate. Het model gebruikte Ingero’s 7 MCP-tools om de trace-database te ondervragen en produceerde een eenvoudige taal-verklaring met actieaanbevelingen die rechtstreeks uit de trace-gegevens zijn afgeleid.

De Per-Process Breakdown
Dit is waar het duidelijk wordt. Het hoofdproces en zijn 4 DataLoader-werkers zijn zichtbaar als afzonderlijke entiteiten:
Hoofdproces:
- cudaMemcpyAsync (host-to-device-overdracht): gem 301ms, max 2,9 seconden - cudaStreamSync: p99 = 42ms (normaal 25us) - 1.567 context-switches, gem 16ms off-CPU, worst-case-stall 5 seconden - 799.018 pagina-toewijzingen
DataLoader-werker 1: 52.863 context-switches, 89.338 pagina-toewijzingen, worst-case-stall 5s DataLoader-werker 2: 50.638 context-switches, 83.509 pagina-toewijzingen, worst-case-stall 5s DataLoader-werker 3: 49.361 context-switches, 70.035 pagina-toewijzingen, worst-case-stall 5s DataLoader-werker 4: 38.862 context-switches, 56.354 pagina-toewijzingen, worst-case-stall 5s
Totaal over werknemers: ~191.000 context-switches en ~299.000 pagina-toewijzingen in 40 seconden.
Wat Dit Betekent
De DataLoader-werkers doen drie dure dingen die directe indexering volledig vermijdt:
- Shuffling en indexering: DataLoader met shuffle=True genereert een willekeurige permutatie van indices, waarna elke werker zijn chunk selecteert. Dit vereist willekeurige geheugen-toegang over de volledige 7M-sample-tensor – verschrikkelijk voor cache-localiteit en triggerpagina-fouten.
- Collatie en kopiëren: Elke werker verzamelt verspreide samples in een aaneengesloten batch-tensor. Dit betekent het toewijzen van nieuw geheugen (pagina-toewijzingen), het kopiëren van gegevens van willekeurige locaties (cache-misses) en het serialiseren van het resultaat terug naar het hoofdproces via gedeelde geheugen of een wachtrij.
- Concurrentie voor CPU: Vier werknemers + het hoofdproces op een 4-vCPU-machine betekent constante pre-emptie. Elke werker wordt 50.000 keer uitgeschakeld. De worst-case-stall is 5 seconden – tijdens welke de GPU niets te verwerken heeft.
Met directe indexering: X[i:i+batch_size] is een zero-copy-weergave van een aaneengesloten tensor die al in het geheugen zit. .to(device) trigger een enkele DMA-overdracht van een enkele aaneengesloten regio. Geen werknemers, geen shuffling, geen collatie, geen cross-proces-kopiëren, geen context-switches. De GPU krijgt gegevens in microseconden, niet in honderden milliseconden.
De Oplossing
Voor in-memory GPU-werkbelastingen waar de hele dataset in het RAM-past:
- Gebruik geen DataLoader. Directe indexering met een vooraf-geshuffelde index-array is eenvoudiger en 100x sneller:
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)
- Als u DataLoader moet gebruiken, moet u num_workers overeenkomen met uw werkelijke CPU-kernen minus 1. Op een 4-core-machine, num_workers=2 vermindert concurrentie. Voeg persistent_workers=True toe om fork-overhead te vermijden.
- Voor grotere-dan-geheugen-datasets waar DataLoader noodzakelijk is, verschuift de echte bottleneck naar schijf-I/O. Gebruik prefetch_factor=2 (niet hoger – meer prefetching betekent meer geheugendruk) en zorg ervoor dat uw opslag kan bijhouden.
Het Grotere Plaatje
Dit onderzoek illustreert een patroon dat we constant zien in GPU-werkbelastingen: de GPU is snel, de host is de bottleneck en GPU-metrieken kunnen het niet zien. nvidia-smi rapporteerde lage utilisatie, maar kon niet vertellen waarom. torch.profiler ving CUDA-kernels, maar miste de 200.000 context-switches die in userspace gebeurden.
De enige manier om het complete plaatje te zien was om zowel CUDA-API-aanroepen als Linux-kernel-scheduling-gebeurtenissen tegelijkertijd te traceren en te correleren door tijd en proces-ID. De causale keten “CPU 100% -> 1.880 sched_switch -> cudaMemcpyAsync 301ms -> cudaStreamSync 42ms” vertelt het complete verhaal in één regel. Zonder cross-stack-tracing zou dit een mysterie zijn gebleven – zoals het voor de oorspronkelijke rapporteur was die wekenlang aan het debuggen was.
Figuur 2: Toen de model werd gevraagd “wat is het kernprobleem?”, identificeerde het CPU-overschrijving als oorzaak van host-side-scheduling-vertragingen. cudaLaunchKernel ging van 73us naar 25,8ms (356x langzamer) omdat de CPU de lancering niet op tijd kon plannen.

Probeer Het Zelf
Reproduceer de 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()
# Snelle weg 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'Direct: {time.time()-start:.3f}s')
# Trage weg 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')
Trace met Ingero om te zien wat er gebeurt onder de motorkap:
git clone https://github.com/ingero-io/ingero.git cd ingero && make build sudo ./bin/ingero trace --duration 60s # in één terminal python3 benchmark.py # in een andere terminal ./bin/ingero explain --since 60s # na benchmark-completie
Of sla de reproduktie over en onderzoek onze trace-gegevens rechtstreeks. De onderzoeksdatabase (764KB) is in de repo:
# Bekijk causale ketens van het onderzoek ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m# Per-proces-breakdown (zie DataLoader-werkers vs hoofdproces) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m
# Verbind uw AI-assistent voor interactief onderzoek ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db
Onderzoek met AI (aanbevolen). De snelste manier om de trace te analyseren is om elke MCP-compatibele AI rechtstreeks te verbinden met de database. Geen handmatige analyse nodig.
Maak een configuratiebestand:
cat > /tmp/ingero-mcp-dataloader.json << 'EOF'
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
EOF
Verbind dan uw model:
# Met Ollama + MiniMax (wat we in de video gebruikten) ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json# Met Claude Code claude --mcp-config /tmp/ingero-mcp-dataloader.json
# Met elke MCP-compatibele client # Voeg de configuratie hierboven toe aan de MCP-instellingen van uw AI
Typ /investigate om de geleide analyse te activeren, of stel een vraag: “Wat veroorzaakte de GPU-sterfte?” De AI heeft toegang tot 7 tools die de trace-database rechtstreeks ondervragen.
GitHub: github.com/ingero-io/ingero
Oorspronkelijk probleem: pytorch/pytorch#154318
Video-walkthrough: https://asciinema.org/a/RGwhPeXAPJdhXqxp
Onderzoek uitgevoerd op TensorDock RTX 4090 (24GB), Ubuntu 22.04, PyTorch 2.10.0+cu128.













