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.jsonJSON-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:
- 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ÃĪ.
- 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.
- 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:
- Ã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)
- 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.
- 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.













