Denne artikel er baseret på fund fra en kernel-niveau GPU-sporingsundersøgelse udført på et rigtigt PyTorch-problem (#154318) ved hjælp af eBPF uprobes. Spordatabaser er offentliggjort i Ingero open-source-repositoriet til uafhængig verificering.
TL;DR
PyTorch’s DataLoader kan være 50-124 gange langsommere end direkte tensor-indexering for in-memory GPU-arbejdslaster. Vi genskabte et rigtigt PyTorch-problem på en RTX 4090 og spored hver CUDA-API-opkald og Linux-kernel-hændelse for at finde rodårsagen. GPU’en var ikke langsom – den sultede. DataLoader-arbejdere genererede 200.000 CPU-kontekstskift og 300.000 sideallokeringer i 40 sekunder, hvilket fik GPU’en til at vente i gennemsnit 301 ms pr. dataoverførsel, der burde tage mikrosekunder.
Problemet
En PyTorch-bruger rapporterede, at DataLoader var 7-22 gange langsommere end direkte tensor-indexering for en simpel MLP-inferensarbejdslast. Selv med num_workers=12, pin_memory=True og prefetch_factor=12, forblev gapet massivt. GPU-udnyttelse lå på 10-20%.
Vi genskabte det. Gapet var endda værre på vores hardware:
| Metode | Tid | vs Direkte |
|---|---|---|
| Direkte tensor-indexering | 0,39 s | 1x |
| DataLoader (shuffle=True) | 48,49 s | 124 gange langsommere |
| DataLoader (optimeret, 4 arbejdere, pin_memory) | 43,29 s | 111 gange langsommere |
Arbejdslasten er trivial: 7M eksempler, 100 funktioner, 2-lagdelt MLP, batch-størrelse 1M. Modellen behandler en batch på få millisekunder. Så hvor går tiden hen?
Hvad nvidia-smi viser
Ingenting nyttigt. GPU-udnyttelse flimrer mellem 0% og 30%. Hukommelsesbrug er stabil. Temperatur er fin. GPU’en er tydelig underudnyttet, men nvidia-smi kan ikke fortælle, hvorfor.
Hvad torch.profiler viser
Rapporteren prøvede PyTorch’s indbyggede profiler og “fik ingen meningsfulde sporfiler.” Dette er en almindelig frustration – applikationsniveau-profilere kan vise, hvilke CUDA-kerner der kører, men de kan ikke se host-siden scheduling, hukommelse og proces-livscyklus-hændelser, der bestemmer, om data ankommer til GPU’en på tid.
Hvad kernel-niveau-sporing viser
Vi kørte benchmarket, mens vi spored både CUDA-API-opkald (via eBPF uprobes på libcudart.so) og Linux-kernel-hændelser (scheduler-kontekstskift, hukommelsesside-allokeringer, proces-fork) samtidigt. Resultaterne fortæller den komplette historie.
Full video-gennemgang: https://asciinema.org/a/RGwhPeXAPJdhXqxp
I videoen forbinder vi en åben-vægt LLM (MiniMax-M2.7) til spordatabasen via MCP (Model Context Protocol):
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.jsonJSON-konfigurationen fortæller MCP-klienten, hvor den kan finde Ingero-serveren og hvilken spordatabase, der skal indlæses:
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
Dette giver LLM direkte adgang til sporfilerne gennem 7 værktøjer: get_trace_stats, get_causal_chains, get_per_process_breakdown og andre. AI’en kan forespørge databasen, korrelerer CUDA-hændelser med kernel-scheduling-data og producerer en plain-language-diagnose uden nogen manuel analyse.
4 HIGH-severity årsagskæder
Årsagskæde-motoren detekterede 4 high-severity-mønstre, alle med samme rodårsag:
[HIGH] cudaStreamSync p99=42ms (1,638x p50=25us) - CPU 100% + 1,880 sched_switch-hændelser Tidslinje: [SYSTEM] CPU 100% [HOST ] 1,880 kontekstskift (21s off-CPU) [CUDA ] p99=42ms (1,638x p50=25us) Rodårsag: DataLoader-arbejdere kæmper om CPU, massiv sideallokeringstryk
[HIGH] cudaLaunchKernel p99=24.67ms (349x p50=70us) - CPU 100% Rod: 34 sched_switch-hændelser [HIGH] cuMemAlloc p99=627us (4.0x p50) - CPU 100% [HIGH] cuLaunchKernel p99=106us (4.0x p50) - CPU 100%
cudaStreamSync p99 er 1.638 gange p50. Det er ikke GPU-langsomhed – det er GPU’en, der venter på data, der aldrig ankommer på tid.
Figur 1: AI-genereret analyse efter kørsel af /investigate. Modellen brugte Ingero’s 7 MCP-værktøjer til at forespørge spordatabasen og producerede en plain-language-forklaring med handlelige anbefalinger, der er direkte afledt fra sporfilerne.

Den pr. proces-breakdown
Dette er, hvor det bliver klart. Hovedprocessen og dens 4 DataLoader-arbejdere er synlige som separate enheder:
Hovedprocessen:
- cudaMemcpyAsync (host-to-device-overførsel): gennemsnit 301ms, maks 2,9 sekunder - cudaStreamSync: p99 = 42ms (normalt 25us) - 1,567 kontekstskift, gennemsnit 16ms off-CPU, værst stall 5 sekunder - 799,018 sideallokeringer
DataLoader-arbejder 1: 52,863 kontekstskift, 89,338 sideallokeringer, værst stall 5s DataLoader-arbejder 2: 50,638 kontekstskift, 83,509 sideallokeringer, værst stall 5s DataLoader-arbejder 3: 49,361 kontekstskift, 70,035 sideallokeringer, værst stall 5s DataLoader-arbejder 4: 38,862 kontekstskift, 56,354 sideallokeringer, værst stall 5s
I alt over arbejdere: ~191.000 kontekstskift og ~299.000 sideallokeringer i 40 sekunder.
Hvad dette betyder
DataLoader-arbejderne gør tre dyre ting, som direkte indexering undgår helt:
- Shuffling og indexering: DataLoader med shuffle=True genererer en tilfældig permutation af indeks, derefter vælger hver arbejder sin del. Dette kræver tilfældig hukommelsesadgang på tværs af den fulde 7M-eksemplar-tensor – frygteligt for cache-lokalitet og udløser sidefejl.
- Collation og kopiering: Hver arbejder samler spredte eksempler i en kontinuert batch-tensor. Dette betyder at allokerer ny hukommelse (sideallokeringer), kopiere data fra tilfældige placeringer (cache-miss), og serialisere resultatet tilbage til hovedprocessen via delt hukommelse eller en kø.
- Konkurrerende om CPU: Fire arbejdere + hovedprocessen på en 4-vCPU-maskine betyder konstant preempt. Hver arbejder bliver afbrudt 50.000 gange. Den værste stall er 5 sekunder – under hvilken GPU’en intet har at proces.
Med direkte indexering: X[i:i+batch_size] er en zero-copy-visning af en kontinuert tensor, der allerede er i hukommelse. .to(device) udløser en enkelt DMA-overførsel fra en enkelt kontinuert region. Ingen arbejdere, ingen shuffling, ingen collation, ingen cross-process-kopier, ingen kontekstskift. GPU’en får data på mikrosekunder, ikke hundredvis af millisekunder.
Løsningen
Til in-memory GPU-arbejdslaster, hvor hele datasettet passer i RAM:
- Brug ikke DataLoader. Direkte indexering med en forhåndsshufflet indeksarray er simpelt og 100 gange hurtigere:
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)
- Hvis du må bruge DataLoader, match num_workers til dine faktiske CPU-kerner minus 1. På en 4-kernet maskine, num_workers=2 reducerer konkurrence. Tilføj persistent_workers=True for at undgå fork-overhead.
- Til større-end-hukommelse-datasæt hvor DataLoader er nødvendig, skifter den virkelige flaskehals til disk-I/O. Brug prefetch_factor=2 (ikke højere – mere prefetching betyder mere hukommelsespres) og sikr, at din lagring kan følge med.
Det større billede
Denne undersøgelse illustrerer en mønster, vi ser konstant i GPU-arbejdslaster: GPU’en er hurtig, hosten er flaskehalsen, og GPU-målinger kan ikke se det. nvidia-smi rapporterede lav udnyttelse, men kunne ikke forklare, hvorfor. torch.profiler fanget CUDA-kerner, men missede de 200.000 kontekstskift, der skete i brugerens rum.
Den eneste måde at se det fulde billede var at spore både sider samtidigt – CUDA-API-opkald på biblioteks-niveau og Linux-kernel-scheduling-hændelser – og korrelerer dem efter tid og proces-ID. Årsagskæden “CPU 100% -> 1,880 sched_switch -> cudaMemcpyAsync 301ms -> cudaStreamSync 42ms” fortæller den komplette historie på en linje. Uden cross-stack-sporing ville dette være blevet en mysterium – som det var for den oprindelige rapport, der tilbragte uger med at fejlfinde det.
Figur 2: Når man beder om “hvad er det centrale problem?”, identificerer modellen CPU-over-subscription, der forårsager host-side-scheduling-forsinkelser. cudaLaunchKernel gik fra 73us til 25,8ms (356 gange langsommere), fordi CPU’en ikke kunne planlægge lanceringen på tid.

Prøv det selv
Reproducer benchmarket:
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()
# Hurtig sti 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'Direkte: {time.time()-start:.3f}s')
# Langsom sti 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')
Spor med Ingero for at se, hvad der sker under kørsel:
git clone https://github.com/ingero-io/ingero.git cd ingero && make build sudo ./bin/ingero trace --duration 60s # i en terminal python3 benchmark.py # i en anden terminal ./bin/ingero explain --since 60s # efter benchmark er færdig
Eller spring reproduction over og udforsk vores spordata direkte. Undersøgelsesdatabasen (764KB) er i repositoriet:
# Vis årsagskæder fra undersøgelsen ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m# Pr. proces-breakdown (se DataLoader-arbejdere vs hovedprocessen) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m
# Forbind din AI-assistent til interaktiv undersøgelse ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db
Undersøg med AI (anbefales). Den hurtigste måde at analysere sporfilerne er at forbinde enhver MCP-kompatibel AI direkte til databasen. Ingen manuel analyse nødvendig.
Opret en konfigurationsfil:
cat > /tmp/ingero-mcp-dataloader.json << 'EOF'
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
EOF
Så forbind din model:
# Med Ollama + MiniMax (hvad vi brugte i videoen) ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json# Med Claude Code claude --mcp-config /tmp/ingero-mcp-dataloader.json
# Med enhver MCP-kompatibel klient # Tilføj konfigurationen ovenfor til din AI's MCP-indstillinger
Skriv /investigate for at udløse den guidede analyse eller still et spørgsmål: “Hvad forårsagede GPU-sulten?” AI’en har adgang til 7 værktøjer, der forespørger spordatabasen direkte.
GitHub: github.com/ingero-io/ingero
Original issue: pytorch/pytorch#154318
Video-gennemgang: https://asciinema.org/a/RGwhPeXAPJdhXqxp
Undersøgelse udført på TensorDock RTX 4090 (24GB), Ubuntu 22.04, PyTorch 2.10.0+cu128.













