Zprávy

124x Pomalejší: Co PyTorch DataLoader Skutečně Dělá na Úrovni Jádru

mm
Přidejte Unite.AI mezi své preferované zdroje na 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.

Tento článek je založen na výsledcích vyšetřování provedeného na úrovni jádra GPU pomocí eBPF uprobes na skutečném problému PyTorch (#154318). Databáze stop jsou zveřejněny v otevřeném repozitáři Ingero pro nezávislé ověření.

TL;DR

PyTorchův DataLoader může být 50-124x pomalejší než přímé indexování tensorů pro úkoly v paměti GPU. Reprodukovali jsme skutečný problém PyTorch na RTX 4090 a stopovali jsme každý volání CUDA API a události Linuxového jádra, abychom našli kořenové příčiny. GPU nebylo pomalé – hladovělo. Pracovníci DataLoaderu vygenerovali 200 000 přepnutí kontextu CPU a 300 000 alokací stránek v 40 sekundách, což způsobilo, že GPU čekalo v průměru 301 ms na každou operaci přenosu dat, která by měla trvat mikrosekundy.

Problém

Uživatel PyTorch nahlásil, že DataLoader je 7-22x pomalejší než přímé indexování tensorů pro jednoduchou úlohu inference MLP. I s num_workers=12, pin_memory=True a prefetch_factor=12 zůstal rozdíl obrovský. Využití GPU se pohybovalo kolem 10-20%.

Reprodukovali jsme to. Rozdíl byl ještě horší na našem hardwaru:

Metoda Čas Vs. Přímé
Přímé indexování tensorů 0,39 s 1x
DataLoader (shuffle=True) 48,49 s 124x pomalejší
DataLoader (optimalizovaný, 4 pracovníci, pin_memory) 43,29 s 111x pomalejší

Úloha je triviální: 7M vzorků, 100 funkcí, 2-vrstvá MLP, velikost dávky 1M. Model zpracuje dávku za milisekundy. Tak kde se bere čas?

Co ukazuje nvidia-smi

Nic užitečného. Využití GPU kolísá mezi 0% a 30%. Použití paměti je stabilní. Teplota je v pořádku. GPU je zjevně nevyužité, ale nvidia-smi nemůže říci proč.

Co ukazuje torch.profiler

Nahlásil se pokusil PyTorchův vestavěný profiler a “získal žádná významná data stop”. To je běžná frustrace – profily na úrovni aplikace mohou ukázat, které CUDA jádra běží, ale nevidí hostitelské plánování, události paměti a životní cyklus procesů, které určují, zda data dorazí do GPU včas.

Co ukazuje stopování na úrovni jádra

Během běhu benchmarku jsme stopovali jak volání CUDA API (pomocí eBPF uprobes na libcudart.so), tak události Linuxového jádra (přepnutí kontextu, alokace stránek, fork procesů) současně. Výsledky vyprávějí kompletní příběh.

Plný video průběh: https://asciinema.org/a/RGwhPeXAPJdhXqxp

V videu jsme připojili otevřenou váhu LLM (MiniMax-M2.7) k databázi stop prostřednictvím MCP (Model Context Protocol):

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

Konfigurační soubor JSON říká klientovi MCP, kde najít server Ingero a které databáze stop načíst:

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

To dává LLM přímý přístup k datům stop prostřednictvím 7 nástrojů: get_trace_stats, get_causal_chains, get_per_process_breakdown a dalších. AI může dotazovat databázi, korelovat CUDA události s daty plánování jádra a produkovat vysvětlení v běžném jazyce bez jakéhokoli manuálního análýzy.

4 Vysoké závažnosti kauzální řetězce

Engine kauzálních řetězců detekoval 4 vysoké závažnosti vzorce, všechny se stejnou kořenovou příčinou:

[VYSOKÉ] cudaStreamSync p99=42ms (1 638x p50=25us) - CPU 100% + 1 880 přepnutí kontextu
Časová osa:
[SYSTEM] CPU 100%
[HOST ] 1 880 přepnutí kontextu (21 s mimo CPU)
[CUDA ] p99=42ms (1 638x p50=25us)
Kořenové příčiny: Pracovníci DataLoaderu bojují o CPU, masivní tlak na alokaci stránek
[VYSOKÉ] cudaLaunchKernel p99=24,67ms (349x p50=70us) - CPU 100%
Kořen: 34 přepnutí kontextu

[VYSOKÉ] cuMemAlloc p99=627us (4,0x p50) - CPU 100%
[VYSOKÉ] cuLaunchKernel p99=106us (4,0x p50) - CPU 100%

cudaStreamSync p99 je 1 638krát p50. To není pomalost GPU – to je GPU čekající na data, která nikdy nedojdou včas.

Obrázek 1: AI-generovaná analýza po spuštění /investigate. Model používal 7 nástrojů MCP Ingero k dotazování databáze stop a produkoval vysvětlení v běžném jazyce s akčními doporučeními odvozenými přímo z dat stop.

Rozdělení podle procesu

Tohle je místo, kde se věci vyjasňují. Hlavní proces a jeho 4 pracovníci DataLoaderu jsou viditelní jako samostatné entity:

Hlavní proces:

- cudaMemcpyAsync (přenos hostitelské stránky na zařízení): prům 301 ms, max 2,9 sekundy
- cudaStreamSync: p99 = 42 ms (obvykle 25 us)
- 1 567 přepnutí kontextu, prům 16 ms mimo CPU, nejhorší zdržení 5 sekund
- 799 018 alokací stránek
Pracovník DataLoaderu 1: 52 863 přepnutí kontextu, 89 338 alokací stránek, nejhorší zdržení 5 s
Pracovník DataLoaderu 2: 50 638 přepnutí kontextu, 83 509 alokací stránek, nejhorší zdržení 5 s
Pracovník DataLoaderu 3: 49 361 přepnutí kontextu, 70 035 alokací stránek, nejhorší zdržení 5 s
Pracovník DataLoaderu 4: 38 862 přepnutí kontextu, 56 354 alokací stránek, nejhorší zdržení 5 s

Celkem přes pracovníky: ~191 000 přepnutí kontextu a ~299 000 alokací stránek v 40 sekundách.

Co to znamená

Pracovníci DataLoaderu dělají tři drahé věci, které přímé indexování zcela vynechává:

  1. Shuffling a indexování: DataLoader s shuffle=True generuje náhodnou permutaci indexů, poté každý pracovník vybírá svou část. To vyžaduje náhodný přístup k paměti napříč celým 7M-vzorkem tensoru – špatné pro lokalitu cache a spouští chyby stránek.
  2. Kolace a kopírování: Každý pracovník shromažďuje rozptýlené vzorky do kontinuální dávky tensoru. To znamená alokovat novou paměť (alokace stránek), kopírovat data z náhodných míst (chyby cache), a serializovat výsledek zpět do hlavního procesu prostřednictvím sdílené paměti nebo fronty.
  3. Soutěž o CPU: Čtyři pracovníci + hlavní proces na 4-vCPU stroj znamená stálé přerušování. Každý pracovník je odhlášen 50 000krát. Nejhorší zdržení je 5 sekund – během kterého GPU nemá nic k zpracování.

S přímým indexováním: X[i:i+velikost_dávky] je zero-copy pohled na kontinuální tensor již v paměti. .to(device) spouští jeden DMA přenos z jedné kontinuální oblasti. Žádní pracovníci, žádné shuffling, žádná kolace, žádný cross-procesní kopírování, žádná přepnutí kontextu. GPU získá data v mikrosekundách, ne v desítkách milisekund.

Řešení

Pro úkoly v paměti GPU, kde celý dataset vejde do RAM:

  1. Nepoužívejte DataLoader. Přímé indexování s předem promíchaným polem indexů je jednodušší a 100x rychlejší:
    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. Pokud musíte použít DataLoader, nastavte num_workers na počet vašich skutečných jader CPU minus 1. Na 4-jádrovém stroji num_workers=2 snižuje konkurenci. Přidejte persistent_workers=True pro vyhnutí se fork overhead.
  3. Pro větší datové sady, kde DataLoader je nutný, skutečná úzká místa se přesouvají na vstup/výstup dat. Použijte prefetch_factor=2 (ne vyšší – více prefetchingu znamená více tlaku na paměť) a zajistěte, aby vaše úložiště mohlo držet krok.

Širší pohled

Toto vyšetřování ilustruje vzorec, který vidíme neustále v úlohách GPU: GPU je rychlé, hostitelská strana je úzkým místem, a metriky GPU nemohou vidět proč. nvidia-smi hlásil nízké využití, ale nemohl říci proč. torch.profiler zachytil CUDA jádra, ale vynechal 200 000 přepnutí kontextu, ke kterým dochází v uživatelském prostoru.

Jejíž jediný způsob, jak vidět kompletní obraz, bylo stopovat obě strany současně – volání CUDA API na úrovni knihovny a události Linuxového jádra – a korelovat je podle času a ID procesu. Kauzální řetězec “CPU 100% -> 1 880 přepnutí kontextu -> cudaMemcpyAsync 301 ms -> cudaStreamSync 42 ms” vypráví kompletní příběh v jednom řádku. Bez cross-stack stopování by to zůstalo tajemstvím – jako to bylo pro původního nahlásitele, který strávil týdny laděním.

Obrázek 2: Když se zeptáte “co je hlavní problém?”, model identifikuje přetížení CPU, které způsobuje zpoždění hostitelské strany. cudaLaunchKernel šel z 73 us na 25,8 ms (356x pomalejší) protože CPU nemohlo naplánovat spuštění včas.

Zkuste to sami

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

# Rychlá cesta 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'Přímé: {time.time()-start:.3f}s')

# Pomalá cesta 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')

Stopujte s Ingero, abyste viděli, co se děje pod kapotou:

git clone https://github.com/ingero-io/ingero.git
cd ingero && make build
sudo ./bin/ingero trace --duration 60s # v jednom terminálu
python3 benchmark.py # v jiném terminálu
./bin/ingero explain --since 60s # po dokončení benchmarku

Nebo přeskočte reprodukci a prozkoumejte naše data stop přímo. Databáze vyšetřování (764 KB) je v repozitáři:

# Zobrazte kauzální řetězce z vyšetřování
./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m

# Rozdělení podle procesu (viz pracovníci DataLoaderu vs hlavní proces) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m

# Připojte svého AI asistenta pro interaktivní vyšetřování ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db

Vyšetřujte s AI (doporučeno). Nejrychlejší způsob, jak analyzovat data stop, je připojit libovolného kompatibilního AI přímo k databázi. Žádná manuální analýza není nutná.

Vytvořte konfigurační soubor:

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

Pak připojte svůj model:

# S Ollama + MiniMax (co jsme použili ve videu)
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json

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

# S libovolným kompatibilním klientem # Přidejte konfiguraci výše do nastavení MCP vašeho AI

Zadejte /investigate, aby se spustila řízená analýza, nebo zeptejte se na jakoukoli otázku: “Co způsobilo hladovění GPU?” AI má přístup k 7 nástrojům, které dotazují databázi stop přímo.

GitHub: github.com/ingero-io/ingero
Původní problém: pytorch/pytorch#154318
Video průběh: https://asciinema.org/a/RGwhPeXAPJdhXqxp

Vyšetřování provedeno na TensorDock RTX 4090 (24 GB), Ubuntu 22.04, PyTorch 2.10.0+cu128.

David Mail je spoluautor a správce Ingera, open-source eBPF agenta pro CUDA-level GPU observability. Specializuje se na kernel-level tracing produkčních AI pracovních zátěží.