Rapporter

124x Slower: Hva PyTorch DataLoader faktisk gjÃļr pÃĨ kernelnivÃĨ

mm
Legg til Unite.AI blant 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 artikkelen er basert pÃĨ funn fra en kernel-nivÃĨ GPU-sporing undersÃļkelse utfÃļrt pÃĨ en ekte PyTorch-problem (#154318) ved hjelp av eBPF uprobes. Spordatabaser er publisert i Ingero ÃĨpen kilde-repositorium for uavhengig verifisering.

TL;DR

PyTorchs DataLoader kan vÃĶre 50-124x langsommere enn direkte tensor-indeksning for arbeidsbelastninger i minnet pÃĨ GPU. Vi gjentok et ekte PyTorch-problem pÃĨ en RTX 4090 og sporet hver CUDA API-kall og Linux-kernel-hendelse for ÃĨ finne ÃĨrsaken. GPU-en var ikke langsom – den sultet. DataLoader-arbeidere genererte 200 000 CPU-kontekstskifter og 300 000 sideallokeringer pÃĨ 40 sekunder, og etterlot GPU-en ventende i gjennomsnitt 301 ms per datatransfer som skulle ta mikrosekunder.

Problemet

En PyTorch-bruker rapporterte at DataLoader var 7-22x langsommere enn direkte tensor-indeksning for en enkel MLP-inferensarbeidsbelastning. Selv med num_workers=12, pin_memory=True og prefetch_factor=12, forble gapet enormt. GPU-utnyttelse satt pÃĨ 10-20%.

Vi gjentok det. Gapet var enda verre pÃĨ vÃĨr maskinvare:

Metode Tid vs Direkte
Direkte tensor-indeksning 0,39 s 1x
DataLoader (shuffle=True) 48,49 s 124x langsommere
DataLoader (optimalisert, 4 arbeidere, pin_memory) 43,29 s 111x langsommere

Arbeidsbelastningen er trivial: 7M eksempler, 100 funksjoner, 2-lags MLP, batch-stÃļrrelse 1M. Modellen prosesserer en batch pÃĨ millisekunder. SÃĨ hvor gÃĨr tiden?

Hva nvidia-smi viser

Ingenting nyttig. GPU-utnyttelse blinker mellom 0% og 30%. Minnebruk er stabilt. Temperatur er fin. GPU-en er tydelig underutnyttet, men nvidia-smi kan ikke fortelle hvorfor.

Hva torch.profiler viser

Rapporten prÃļvde PyTorchs innebygde profiler og “fikk ingen meningsfulle spor-data.” Dette er en vanlig frustrasjon – applikasjonsnivÃĨ-profiler kan vise deg hvilke CUDA-kjerner som kjÃļrer, men de kan ikke se host-siden scheduling, minne og prosess-livslÃļpshendelser som bestemmer om data ankommer pÃĨ GPU pÃĨ tide.

Hva kernel-nivÃĨ-sporing viser

Vi kjÃļrte benchmarket mens vi sporet bÃĨde CUDA API-kall (via eBPF uprobes pÃĨ libcudart.so) og Linux-kernel-hendelser (scheduler-kontekstskifter, minneside-allokeringer, prosess-fork) samtidig. Resultatene forteller den komplette historien.

Full video-gjennomgang: https://asciinema.org/a/RGwhPeXAPJdhXqxp

I videoen koblet vi en ÃĨpen-vekt LLM (MiniMax-M2.7) til spordatabasen via MCP (Model Context Protocol):

ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json

JSON-konfigurasjonen forteller MCP-klienten hvor den skal finne Ingero-serveren og hvilken spordatabase som skal lastes:

{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}

Dette gir LLM direkte tilgang til spor-data gjennom 7 verktÃļy: get_trace_stats, get_causal_chains, get_per_process_breakdown og andre. AI-en kan spÃļrre databasen, korrelere CUDA-hendelser med kernel-scheduling-data og produsere en ren-sprÃĨklig diagnose uten noen manuell analyse.

4 HIGH-severity ÃĨrsakskjeder

Årsakskjede-motoren detekterte 4 high-severity-mÃļnster, alle med samme rotÃĨrsak:

[HIGH] cudaStreamSync p99=42ms (1,638x p50=25us) - CPU 100% + 1,880 sched_switch events
Tidslinje:
[SYSTEM] CPU 100%
[HOST ] 1,880 kontekstskifter (21s off-CPU)
[CUDA ] p99=42ms (1,638x p50=25us)
RotÃĨrsak: DataLoader-arbeidere kjemper om CPU, massiv sideallokeringstrykk
[HIGH] cudaLaunchKernel p99=24.67ms (349x p50=70us) - CPU 100%
Rot: 34 sched_switch events

[HIGH] cuMemAlloc p99=627us (4.0x p50) - CPU 100%
[HIGH] cuLaunchKernel p99=106us (4.0x p50) - CPU 100%

cudaStreamSync p99 er 1,638 ganger p50. Det er ikke GPU-lengsel – det er GPU-en som venter pÃĨ data som aldri ankommer pÃĨ tide.

Figur 1: AI-generert analyse etter ÃĨ ha kjÃļrt /investigate. Modellen brukte Ingeros 7 MCP-verktÃļy til ÃĨ spÃļrre spordatabasen og produserte en ren-sprÃĨklig forklaring med handlebarske anbefalinger direkte fra spor-data.

Prosessen per-prosess

Dette er hvor det blir tydelig. Hovedprosessen og dens 4 DataLoader-arbeidere er synlige som separate enheter:
Hovedprosess:

- cudaMemcpyAsync (host-til-enhet-overfÃļring): gjennomsnitt 301ms, maks 2,9 sekunder
- cudaStreamSync: p99 = 42ms (normalt 25us)
- 1,567 kontekstskifter, gjennomsnitt 16ms off-CPU, verst stall 5 sekunder
- 799,018 sideallokeringer
DataLoader-arbeider 1: 52,863 kontekstskifter, 89,338 sideallokeringer, verst stall 5s
DataLoader-arbeider 2: 50,638 kontekstskifter, 83,509 sideallokeringer, verst stall 5s
DataLoader-arbeider 3: 49,361 kontekstskifter, 70,035 sideallokeringer, verst stall 5s
DataLoader-arbeider 4: 38,862 kontekstskifter, 56,354 sideallokeringer, verst stall 5s

Totalt over arbeidere: ~191 000 kontekstskifter og ~299 000 sideallokeringer pÃĨ 40 sekunder.

Hva dette betyr

DataLoader-arbeiderne gjÃļr tre dyre ting som direkte indeksning unngÃĨr helt:

  1. Shuffling og indeksning: DataLoader med shuffle=True genererer en tilfeldig permutasjon av indekser, sÃĨ hver arbeider velger sin del. Dette krever tilfeldig minneaksess over hele 7M-eksemplaret – dÃĨrlig for cache-lokalitet og utlÃļser sidefeil.
  2. Kollasjon og kopiering: Hver arbeider samler spredte eksempler i en sammenhengende batch-tensor. Dette innebÃĶrer ÃĨ allokere ny minne (sideallokeringer), kopiere data fra tilfeldige lokasjoner (cache-miss), og serialisere resultatet tilbake til hovedprosessen via delt minne eller en kÃļ.
  3. Konkurrerende om CPU: Fire arbeidere + hovedprosessen pÃĨ en 4-vCPU-maskin betyr konstant forhÃĨndtering. Hver arbeider blir descheduled 50 000 ganger. Verst-case-stall er 5 sekunder – under hvilken GPU-en har ingenting ÃĨ prosessere.

Med direkte indeksning: X[i:i+batch_size] er en null-kopi-utsikt over en sammenhengende tensor allerede i minnet. .to(device) utlÃļser en enkelt DMA-overfÃļring fra en enkelt sammenhengende region. Ingen arbeidere, ingen shuffling, ingen kollasjon, ingen cross-prosess-kopier, ingen kontekstskifter. GPU-en fÃĨr data pÃĨ mikrosekunder, ikke hundredvis av millisekunder.

LÃļsningen

For arbeidsbelastninger i minnet pÃĨ GPU hvor hele datasettet passer i RAM:

  1. Bruk ikke DataLoader. Direkte indeksning med en forhÃĨnds-shufflet indeks-array er enklere og 100x raskere:
    indekser = torch.randperm(num_samples)
    for i in range(0, num_samples, batch_size):
    batch = X[indekser[i:i+batch_size]].to(device)
    output = model(batch)
    
  2. Hvis du mÃĨ bruke DataLoader, match num_workers med dine faktiske CPU-kjerner minus 1. PÃĨ en 4-kjerne-maskin, num_workers=2 reduserer konflikten. Legg til persistent_workers=True for ÃĨ unngÃĨ fork-overhead.
  3. For stÃļrre enn minne-datasett hvor DataLoader er nÃļdvendig, flytter den virkelige flaskehalen til disk-I/O. Bruk prefetch_factor=2 (ikke hÃļyere – mer prefetching betyr mer minne-trykk) og sikre at ditt lagringsutstyr kan holde pace.

Det stÃļrre bildet

Denne undersÃļkelsen illustrerer en mÃļnster vi ser konstant i GPU-arbeidsbelastninger: GPU-en er rask, hosten er flaskehalen, og GPU-mÃĨlinger kan ikke se det. nvidia-smi rapporterte lav utnyttelse, men kunne ikke forklare hvorfor. torch.profiler fanget CUDA-kjerner, men gikk glipp av de 200 000 kontekstskiftene som skjedde i brukerrommet.

Den eneste mÃĨten ÃĨ se det fullstendige bildet var ÃĨ spore bÃĨde sider samtidig – CUDA API-kall pÃĨ biblioteknivÃĨ og Linux-kernel-scheduling-hendelser – og korrelere dem med tid og prosess-ID. Årsakskjeden “CPU 100% -> 1,880 sched_switch -> cudaMemcpyAsync 301ms -> cudaStreamSync 42ms” forteller den komplette historien pÃĨ ÃĐn linje. Uten cross-stack-sporing ville dette ha forblet et mysterium – som det var for den opprinnelige rapportÃļren som brukte uker pÃĨ ÃĨ feilsÃļke det.

Figur 2: NÃĨr du blir bedt om “hva er det grunnleggende problemet?”, identifiserer modellen CPU-overskripping som ÃĨrsak til host-siden-scheduling-forsinkelser. cudaLaunchKernel gikk fra 73us til 25,8ms (356x langsommere) fordi CPU-en ikke kunne planlegge lanseringen i tide.

PrÃļv det selv

Reproduser 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()

# Rask vei 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 vei 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 ÃĨ se hva som skjer under panseret:

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 annen terminal
./bin/ingero explain --since 60s # etter benchmark er fullfÃļrt

Eller hopp over reproduksjonen og utforsk vÃĨre spor-data direkte. UndersÃļkelsesdatabasen (764KB) er i repoet:

# Vis ÃĨrsakskjeder fra undersÃļkelsen
./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m

# Per-prosess-breakdown (se DataLoader-arbeidere vs hovedprosess) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m

# Kobler din AI-assistent for interaktiv undersÃļkelse ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db

UndersÃļk med AI (anbefalt). Den raskeste mÃĨten ÃĨ analysere sporet er ÃĨ koble en MCP-kompatibel AI direkte til databasen. Ingen manuell analyse nÃļdvendig.

Lag en konfigurasjonsfil:

cat > /tmp/ingero-mcp-dataloader.json << 'EOF'
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
EOF

SÃĨ kobler du modellen din:

# Med Ollama + MiniMax (hva vi brukte 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 en hvilken som helst MCP-kompatibel klient # Legg til konfigurasjonen over i MCP-innstillingene dine

Skriv /investigate for ÃĨ utlÃļse guidet analyse, eller spÃļr noen spÃļrsmÃĨl: “Hva forÃĨrsaket GPU-sulten?” AI-en har tilgang til 7 verktÃļy som spÃļr databasen direkte.

GitHub: github.com/ingero-io/ingero
Opprinnelig problem: pytorch/pytorch#154318
Video-gjennomgang: https://asciinema.org/a/RGwhPeXAPJdhXqxp

UndersÃļkelse utfÃļrt pÃĨ TensorDock RTX 4090 (24GB), Ubuntu 22.04, PyTorch 2.10.0+cu128.

David Mail er medforfatter og vedlikeholder av Ingero, en ÃĨpen kildekode-eBPF-agent for CUDA-nivÃĨ GPU-observabilitet. Han spesialiserer seg pÃĨ kernel-nivÃĨ sporingsproduksjons AI-arbeidsbelastninger.