Rapporter

124 gange langsommere: Hvad PyTorch DataLoader faktisk gÃļr pÃĨ kernelniveau

mm
FÃļj Unite.AI til dine foretrukne kilder pÃĨ Google
A conceptual widescreen illustration of an hourglass containing glowing digital data streams and circuit patterns, with the bottom half featuring a large data block and a GPU hardware component sitting idle to the side, representing data processing delays and GPU starvation.

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.json

JSON-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:

  1. 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.
  2. 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Ãļ.
  3. 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:

  1. 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)
    
  2. 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.
  3. 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 DataLoader

X = 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.

David Mail er medforfatter og vedligeholder af Ingero, en open-source eBPF-agent til CUDA-niveau GPU-overvÃĨgning. Han specialiserer sig i kernel-niveau sporingsaf production AI-workloads.