Raportit

124 kertaa hitaampi: MitÃĪ PyTorch DataLoader todella tekee ytimen tasolla

mm
LisÃĪÃĪ Unite.AI suosikkilÃĪhteisiisi Google-palvelussa
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.

TÃĪmÃĪ artikkeli perustuu havaintoihin, jotka on tehty ytimen tasolla suoritetusta GPU-jÃĪljityksestÃĪ, joka suoritettiin oikealla PyTorch-ongelmalla (#154318) kÃĪyttÃĪen eBPF uprobesia. JÃĪljitys-tietokannat on julkaistu Ingero-avoin lÃĪhdekoodirepositoriossa itsenÃĪisen vahvistamisen vuoksi.

TL;DR

PyTorchin DataLoader voi olla 50-124 kertaa hitaampi kuin suora tensorin indeksointi muistin sisÃĪisille GPU-tyÃķkuormille. Toistimme oikean PyTorch-ongelman RTX 4090:lla ja jÃĪljittÃĪimme jokaisen CUDA API-kutsun ja Linux-ytimen tapahtuman, jotta lÃķydettÃĪisiin ongelman syy. GPU ei ollut hitaampi – se oli nÃĪlkÃĪinen. DataLoader-tyÃķntekijÃĪt loivat 200 000 CPU-kontekstivaihtoa ja 300 000 sivuvarauksen 40 sekunnissa, jÃĪttÃĪen GPU:n odottamaan keskimÃĪÃĪrin 301 ms kunkin datansiirron kohdalla, joka pitÃĪisi kestÃĪÃĪ mikrosekunteja.

Ongelma

PyTorch-kÃĪyttÃĪjÃĪ ilmoitti, ettÃĪ DataLoader oli 7-22 kertaa hitaampi kuin suora tensorin indeksointi yksinkertaiselle MLP-inferenssityÃķkuormalle. Vaikka num_workers=12, pin_memory=True ja prefetch_factor=12, ero oli edelleen valtava. GPU-kÃĪyttÃķaste oli 10-20%.

Toistimme sen. Ero oli vielÃĪ pahempi laitteistollamme:

MenetelmÃĪ Aika Suhteessa suoraan
Suora tensorin indeksointi 0,39 s 1x
DataLoader (shuffle=True) 48,49 s 124 kertaa hitaampi
DataLoader (optimoituna, 4 tyÃķntekijÃĪÃĪ, pin_memory) 43,29 s 111 kertaa hitaampi

TyÃķkuorma on triviaali: 7M nÃĪytettÃĪ, 100 piirrettÃĪ, 2-kerroksinen MLP, erÃĪkoko 1M. Malli prosessoi erÃĪn millisekunteissa. MissÃĪ siis menee aika?

MitÃĪ nvidia-smi nÃĪyttÃĪÃĪ

MitÃĪÃĪn hyÃķdyllistÃĪ. GPU-kÃĪyttÃķaste vaihtelee 0% ja 30% vÃĪlillÃĪ. Muistin kÃĪyttÃķ on vakaa. LÃĪmpÃķtila on ok. GPU on selvÃĪsti alikÃĪytetty, mutta nvidia-smi ei voi kertoa, miksi.

MitÃĪ torch.profiler nÃĪyttÃĪÃĪ

Raportoija yritti PyTorchin sisÃĪistÃĪ profiloijaa ja “hankki ei-merkityksellistÃĪ jÃĪljitystietoa.” TÃĪmÃĪ on yleinen frustraatio – sovelluksen tasolla olevat profiloijat voivat nÃĪyttÃĪÃĪ, mitkÃĪ CUDA-ytimet suoritetaan, mutta ne eivÃĪt voi nÃĪhdÃĪ isÃĪntÃĪpuolen aikataulutusta, muistia ja prosessin elinkaareen liittyviÃĪ tapahtumia, jotka mÃĪÃĪrÃĪÃĪvÃĪt, saapuuko data GPU:lle ajoissa.

MitÃĪ ytimen tasolla oleva jÃĪljitys nÃĪyttÃĪÃĪ

Suoritimme benchmarkin jÃĪljitsemÃĪllÃĪ sekÃĪ CUDA API-kutsuja (eBPF uprobesilla libcudart.so:sta) ettÃĪ Linux-ytimen tapahtumia (aikataulutuskontekstivaihdot, muistisivujen varaus, prosessin forkkaus) samanaikaisesti. Tulokset kertovat koko tarinan.

TÃĪydellinen video-esittely: https://asciinema.org/a/RGwhPeXAPJdhXqxp

Videossa liitimme avoimen painopisteen LLM (MiniMax-M2.7):n jÃĪljitystietokantaan MCP (Model Context Protocol) kautta:

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

JSON-konfiguraatio kertoo MCP-asiakkaalle, missÃĪ Ingero-palvelin ja jÃĪljitystietokanta sijaitsevat:

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

TÃĪmÃĪ antaa LLM:lle suoran pÃĪÃĪsyn jÃĪljitystietoihin 7 tyÃķkalun kautta: get_trace_stats, get_causal_chains, get_per_process_breakdown jne. AI voi kysyÃĪ tietokannasta, korreloida CUDA-tapahtumia ytimen aikataulutustiedon kanssa ja tuottaa selkeÃĪn kielen diagnosin ilman manuaalista analyysiÃĪ.

4 korkean tason kausaalisia ketjuja

Kausaaliset ketjutunnistin havaittiin 4 korkean tason mallia, kaikki samalla juurisyyllÃĪ:

[KORKEA] cudaStreamSync p99=42ms (1,638x p50=25us) - CPU 100% + 1,880 sched_switch -tapahtumaa
Aikajana:
[SYSTEM] CPU 100%
[HOST ] 1,880 kontekstivaihtoa (21s pois-CPU:sta)
[CUDA ] p99=42ms (1,638x p50=25us)
Juurisyy: DataLoader-tyÃķntekijÃĪt taistelevat CPU:sta, massiivinen sivuvarauspaine
[KORKEA] cudaLaunchKernel p99=24.67ms (349x p50=70us) - CPU 100%
Juurisyy: 34 sched_switch -tapahtumaa

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

cudaStreamSync p99 on 1,638 kertaa p50. Se ei ole GPU:n hitaus – se on GPU:n odottaminen dataa, joka ei saavu ajoissa.

Kuva 1: AI-generoitu analyysi suorittamisen jÃĪlkeen /investigate. Malli kÃĪytti Ingeron 7 MCP-tyÃķkalua kysymÃĪÃĪn jÃĪljitystietokannasta ja tuotti selkeÃĪn kielen selityksen toimintavinkkejÃĪ, jotka perustuvat suoraan jÃĪljitystietoihin.

Prosessikohtainen jakautuminen

TÃĪssÃĪ se selviÃĪÃĪ. PÃĪÃĪprosessi ja sen 4 DataLoader-tyÃķntekijÃĪÃĪ ovat nÃĪkyvissÃĪ eri entiteetteinÃĪ:

PÃĪÃĪprosessi:

- cudaMemcpyAsync (isÃĪntÃĪ-laite -siirto): keskimÃĪÃĪrin 301ms, maksimi 2,9 sekuntia
- cudaStreamSync: p99 = 42ms (normaalisti 25us)
- 1,567 kontekstivaihtoa, keskimÃĪÃĪrin 16ms pois-CPU:sta, pahin tukkeutuminen 5 sekuntia
- 799,018 sivuvarauksia
DataLoader-tyÃķntekijÃĪ 1: 52,863 kontekstivaihtoa, 89,338 sivuvarauksia, pahin tukkeutuminen 5s
DataLoader-tyÃķntekijÃĪ 2: 50,638 kontekstivaihtoa, 83,509 sivuvarauksia, pahin tukkeutuminen 5s
DataLoader-tyÃķntekijÃĪ 3: 49,361 kontekstivaihtoa, 70,035 sivuvarauksia, pahin tukkeutuminen 5s
DataLoader-tyÃķntekijÃĪ 4: 38,862 kontekstivaihtoa, 56,354 sivuvarauksia, pahin tukkeutuminen 5s

YhteensÃĪ tyÃķntekijÃķiden kesken: noin 191,000 kontekstivaihtoa ja noin 299,000 sivuvarauksia 40 sekunnissa.

MitÃĪ se tarkoittaa

DataLoader-tyÃķntekijÃĪt tekevÃĪt kolme kallista asiaa, joita suora indeksointi vÃĪlttÃĪÃĪ kokonaan:

  1. Shuffling ja indeksointi: DataLoader shuffle=True luo satunnaisen indeksien peruuttamisen, ja jokainen tyÃķntekijÃĪ valitsee oman osuutensa. TÃĪmÃĪ vaatii satunnaisen muistin pÃĪÃĪsyn koko 7M-nÃĪytteisen tensoriin – huonoa vÃĪlimuistin paikallisuutta ja laukaisee sivuvirheitÃĪ.
  2. Collation ja kopioiminen: Jokainen tyÃķntekijÃĪ kerÃĪÃĪ hajallaan olevat nÃĪytteet yhtenÃĪiseen erÃĪtensoriin. TÃĪmÃĪ tarkoittaa uuden muistin varaus (sivuvarauksia), datan kopioimista satunnaisista sijainneista (vÃĪlimuistin hajotus) ja serialisointia takaisin pÃĪÃĪprosessiin jaettujen muistojen tai jonon kautta.
  3. Kilpailu CPU:sta: NeljÃĪ tyÃķntekijÃĪÃĪ + pÃĪÃĪprosessi 4-vCPU-koneessa tarkoittaa jatkuvaan esiajoitukseen. Jokainen tyÃķntekijÃĪ poistetaan 50,000 kertaa. Pahin tukkeutuminen on 5 sekuntia – jonka aikana GPU:lla ei ole mitÃĪÃĪn prosessoida.

Suoralla indeksoinnilla: X[i:i+batch_size] on nollakopio yhtenÃĪisestÃĪ tensorista, joka on jo muistissa. .to(device) laukaisee yhden DMA-siirron yhdestÃĪ yhtenÃĪisestÃĪ alueesta. Ei tyÃķntekijÃķitÃĪ, ei shufflia, ei collationia, ei ristiinprosessin kopioita, ei kontekstivaihtoja. GPU saa datan mikrosekunteissa, ei satojen millisekuntien aikana.

Korjaus

Muistin sisÃĪisille GPU-tyÃķkuormille, joissa koko tietokanta mahtuu muistiin:

  1. ÄlÃĪ kÃĪytÃĪ DataLoaderia. Suora indeksointi esikÃĪÃĪritetyllÃĪ indeksitaulukolla on yksinkertaisempaa ja 100 kertaa nopeampaa:
    indeksit = torch.randperm(num_samples)
    for i in range(0, num_samples, batch_size):
    batch = X[indeksit[i:i+batch_size]].to(device)
    output = model(batch)
    
  2. Jos sinun on kÃĪytettÃĪvÃĪ DataLoaderia, vastaa num_workers -arvoa oikeasti olemassa olevien CPU-ytimien mÃĪÃĪrÃĪllÃĪ. NeljÃĪn ytimen koneessa num_workers=2 vÃĪhentÃĪÃĪ kilpailua. LisÃĪÃĪ persistent_workers=True vÃĪlttÃĪÃĪksesi forkkauskustannukset.
  3. Isommille kuin muistiin mahtuville tietokannoille joissa DataLoader on vÃĪlttÃĪmÃĪtÃķn, todellinen pullonkaula siirtyy levy-I/O:lle. KÃĪytÃĪ prefetch_factor=2 (ÃĪlÃĪ korkeampaa – enemmÃĪn esihaku tarkoittaa enemmÃĪn muistipainetta) ja varmista, ettÃĪ tallennusvÃĪlineesi pystyy pitÃĪmÃĪÃĪn vauhtia.

Laajempi kuva

TÃĪmÃĪ tutkimus havainnollistaa mallin, jota nÃĪemme jatkuvasti GPU-tyÃķkuormissa: GPU on nopea, isÃĪntÃĪ on pullonkaula, ja GPU-mittaukset eivÃĪt voi nÃĪhdÃĪ sitÃĪ. nvidia-smi raportoi matalan kÃĪyttÃķasteen, mutta ei voinut selittÃĪÃĪ, miksi. torch.profiler tallensi CUDA-ytimiÃĪ, mutta se ei nÃĪhnyt 200,000 kontekstivaihtoa, jotka tapahtuivat kÃĪyttÃĪjÃĪtilassa.

Tapa, jolla nÃĪhdÃĪ koko kuva, oli jÃĪljitÃĪÃĪ sekÃĪ CUDA API-kutsuja ettÃĪ Linux-ytimen aikataulutustapahtumia samanaikaisesti ja korreloida niitÃĪ ajan ja prosessin ID:n perusteella. Kausaalinen ketju “CPU 100% -> 1,880 sched_switch -> cudaMemcpyAsync 301ms -> cudaStreamSync 42ms” kertoo koko tarinan yhdellÃĪ rivillÃĪ. Ilman ristirakenteisen jÃĪljitystÃĪ tÃĪmÃĪ olisi jÃĪÃĪnyt arvoitukseksi – kuten se oli alun perin ilmoittajalle, joka vietti viikkoja debuggaamassa sitÃĪ.

Kuva 2: Kun kysyttiin “mitÃĪ on keskeinen ongelma?”, malli tunnistaa CPU-yliedustuksen aiheuttamat isÃĪntÃĪpuolen aikataulutusviiveet. cudaLaunchKernel siirtyi 73us:sta 25,8ms:iin (356 kertaa hitaampi) koska CPU ei voinut ajoittaa laukaisua ajoissa.

Kokeile itse

Toista benchmark:

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

# Nopea polku
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'Suora: {time.time()-start:.3f}s')

# Hidas polku
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')

JÃĪljitÃĪ Ingerolla, jotta nÃĪet, mitÃĪ tapahtuu alla:

git clone https://github.com/ingero-io/ingero.git
cd ingero && make build
sudo ./bin/ingero trace --duration 60s # toisessa terminaalissa
python3 benchmark.py # toisessa terminaalissa
./bin/ingero explain --since 60s # benchmarkin jÃĪlkeen

Tai ohita toisto ja tutki suoraan meidÃĪn jÃĪljitystietokantamme:

# NÃĪytÃĪ kausaaliset ketjut tutkimuksesta
./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m

# Prosessikohtainen jakautuminen (nÃĪe DataLoader-tyÃķntekijÃĪt vs pÃĪÃĪprosessi)
./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m

# LiitÃĪ AI-avustajasi interaktiiviseen tutkimukseen
./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db

Tutki AI:n avulla (suositellaan). Nopein tapa analysoida jÃĪljitys on liittÃĪÃĪ MCP-yhteensopiva AI suoraan tietokantaan. Ei manuaalista analyysiÃĪ tarvita.

Luo konfiguraatiotiedosto:

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

Sitten liitÃĪ mallisi:

# Ollamalla + MiniMax (mitÃĪ kÃĪytimme videossa)
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json

# Claude Code
claude --mcp-config /tmp/ingero-mcp-dataloader.json

# MillÃĪ tahansa MCP-yhteensopivalla asiakkaalla
# LisÃĪÃĪ konfiguraatio yllÃĪ AI:n MCP-asetuksiin

Kirjoita /investigate laukaiseksesi ohjatun analyysin, tai kysy mitÃĪ tahansa: “MikÃĪ aiheutti GPU:n nÃĪlÃĪn?” AI:lla on pÃĪÃĪsy 7 tyÃķkaluun, jotka kysyvÃĪt jÃĪljitystietokannasta suoraan.

GitHub: github.com/ingero-io/ingero
AlkuperÃĪinen ongelma: pytorch/pytorch#154318
Video-esittely: https://asciinema.org/a/RGwhPeXAPJdhXqxp

Tutkimus suoritettiin TensorDock RTX 4090:lla (24GB), Ubuntu 22.04:llÃĪ, PyTorch 2.10.0+cu128:lla.

David Mail on Ingeron ja avoimen lÃĪhdekoodin eBPF-agentin yllÃĪpitÃĪjÃĪ, joka tarjoaa CUDA-tasoa olevan GPU-havainnollistamisen. HÃĪn on erikoistunut tuotannon AI-tyÃķkuormien ydinlevyn jÃĪljitykseen.