Ten artykuÅ oparty jest na wynikach Åledzenia na poziomie jÄ dra GPU przeprowadzonego na rzeczywistym problemie PyTorch (#154318) z uÅžyciem eBPF uprobes. Bazy danych Åledzenia sÄ publikowane w repozytorium open-source Ingero do niezaleÅžnej weryfikacji.
TL;DR
DataLoader PyTorch moÅže byÄ 50-124 razy wolniejszy niÅž bezpoÅrednie indeksowanie tensorÃģw dla obciÄ ÅžeÅ pamiÄci GPU. OdtworzyliÅmy rzeczywisty problem PyTorch na RTX 4090 i ÅledziliÅmy kaÅžde wywoÅanie interfejsu API CUDA i zdarzenia jÄ dra Linux, aby znaleÅšÄ przyczynÄ. GPU nie byÅ wolny â gÅodowaÅ. Pracownicy DataLoader wygenerowali 200 000 przeÅÄ czeÅ kontekstu CPU i 300 000 alokacji stron w 40 sekund, pozostawiajÄ c GPU w oczekiwaniu Årednio 301 ms na transfer danych, ktÃģry powinien trwaÄ mikrosekundy.
Problem
UÅžytkownik PyTorch zgÅosiÅ, Åže DataLoader jest 7-22 razy wolniejszy niÅž bezpoÅrednie indeksowanie tensorÃģw dla prostego obciÄ Åženia inferencyjnego MLP. Nawet z num_workers=12, pin_memory=True i prefetch_factor=12, rÃģÅžnica byÅa nadal ogromna. Wykorzystanie GPU wynosiÅo 10-20%.
OdtworzyliÅmy to. RÃģÅžnica byÅa jeszcze gorsza na naszym sprzÄcie:
| Metoda | Czas | vs BezpoÅrednie |
|---|---|---|
| BezpoÅrednie indeksowanie tensorÃģw | 0,39 s | 1x |
| DataLoader (shuffle=True) | 48,49 s | 124 razy wolniejszy |
| DataLoader (zoptymalizowany, 4 pracownicy, pin_memory) | 43,29 s | 111 razy wolniejszy |
ObciÄ Åženie jest trywialne: 7M prÃģbek, 100 cech, 2-warstwowy MLP, rozmiar partii 1M. Model przetwarza partiÄ w milisekundach. Gdzie wiÄc idzie czas?
Co nvidia-smi Pokazuje
Nic przydatnego. Wykorzystanie GPU migocze miÄdzy 0% a 30%. UÅžycie pamiÄci jest stabilne. Temperatura jest w porzÄ dku. GPU jest wyraÅšnie niedoÅžywiony, ale nvidia-smi nie moÅže powiedzieÄ, dlaczego.
Co torch.profiler Pokazuje
RaportujÄ cy sprÃģbowaÅ wbudowanego profilera PyTorch i ânie uzyskaÅ Åžadnych przydatnych danych Åledzeniaâ. Jest to powszechna frustracja â profilery na poziomie aplikacji mogÄ pokazaÄ, ktÃģre jÄ dra CUDA sÄ uruchomione, ale nie mogÄ zobaczyÄ host-side scheduling, zdarzeÅ pamiÄci i procesu cyklu Åžycia, ktÃģre determinujÄ , czy dane docierajÄ do GPU na czas.
Co Åledzenie na Poziomie JÄ dra Pokazuje
UruchomiliÅmy benchmark, ÅledzÄ c jednoczeÅnie wywoÅania interfejsu API CUDA (za pomocÄ eBPF uprobes na libcudart.so) i zdarzenia jÄ dra Linux (przeÅÄ czenia kontekstu planisty, alokacje stron pamiÄci, fork procesÃģw). Wyniki opowiadajÄ caÅÄ historiÄ.
PeÅne wideo: https://asciinema.org/a/RGwhPeXAPJdhXqxp
W filmie poÅÄ czyliÅmy otwarty model LLM (MiniMax-M2.7) z bazÄ danych Åledzenia za pomocÄ MCP (Model Context Protocol):
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.jsonPlik konfiguracyjny JSON informuje klienta MCP, gdzie znaleÅšÄ serwer Ingero i jakÄ bazÄ danych Åledzenia zaÅadowaÄ:
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
To daje LLM bezpoÅredni dostÄp do danych Åledzenia za pomocÄ 7 narzÄdzi: get_trace_stats, get_causal_chains, get_per_process_breakdown i innych. AI moÅže wysyÅaÄ zapytania do bazy danych, korelowaÄ zdarzenia CUDA z danymi planowania jÄ dra i generowaÄ wyjaÅnienie w jÄzyku naturalnym bez Åžadnej rÄcznej analizy.
4 ÅaÅcuchy Przyczynowe o Wysokim Poziomie ZagroÅženia
Silnik ÅaÅcuchÃģw przyczynowych wykryÅ 4 wzorce o wysokim poziomie zagroÅženia, wszystkie z tÄ samÄ przyczynÄ :
[WYSOKI] cudaStreamSync p99=42ms (1,638x p50=25us) - CPU 100% + 1,880 przeÅÄ czeÅ kontekstu Czas: [SYSTEM] CPU 100% [HOST] 1,880 przeÅÄ czeÅ kontekstu (21s poza CPU) [CUDA] p99=42ms (1,638x p50=25us) Przyczyna: Pracownicy DataLoader walczÄ o CPU, ogromne ciÅnienie alokacji stron
[WYSOKI] cudaLaunchKernel p99=24.67ms (349x p50=70us) - CPU 100% KorzeÅ: 34 przeÅÄ czenia kontekstu [WYSOKI] cuMemAlloc p99=627us (4.0x p50) - CPU 100% [WYSOKI] cuLaunchKernel p99=106us (4.0x p50) - CPU 100%
cudaStreamSync p99 jest 1,638 razy wiÄkszy niÅž p50. To nie jest powolnoÅÄ GPU â to GPU czekajÄ ce na dane, ktÃģre nigdy nie przychodzÄ na czas.
Rysunek 1: Analiza wygenerowana przez AI po uruchomieniu /investigate. Model uÅžyÅ 7 narzÄdzi MCP Ingero do wysÅania zapytaÅ do bazy danych Åledzenia i wygenerowaÅ wyjaÅnienie w jÄzyku naturalnym z zaleceniami pochodzÄ cymi bezpoÅrednio z danych Åledzenia.

Rozbicie na Poziomie Procesu
To staje siÄ jasne. GÅÃģwny proces i jego 4 pracownicy DataLoader sÄ
widoczne jako oddzielne jednostki:
GÅÃģwny proces:
- cudaMemcpyAsync (transfer z hosta do urzÄ dzenia): Årednio 301ms, maksymalnie 2,9 sekundy - cudaStreamSync: p99 = 42ms (zwykle 25us) - 1,567 przeÅÄ czeÅ kontekstu, Årednio 16ms poza CPU, najgorsza zwÅoka 5 sekund - 799,018 alokacji stron
Pracownik DataLoader 1: 52,863 przeÅÄ czenia kontekstu, 89,338 alokacji stron, najgorsza zwÅoka 5s Pracownik DataLoader 2: 50,638 przeÅÄ czeÅ kontekstu, 83,509 alokacji stron, najgorsza zwÅoka 5s Pracownik DataLoader 3: 49,361 przeÅÄ czeÅ kontekstu, 70,035 alokacji stron, najgorsza zwÅoka 5s Pracownik DataLoader 4: 38,862 przeÅÄ czenia kontekstu, 56,354 alokacji stron, najgorsza zwÅoka 5s
ÅÄ cznie na pracownikÃģw: ~191,000 przeÅÄ czeÅ kontekstu i ~299,000 alokacji stron w 40 sekund.
Co to Znaczy
Pracownicy DataLoader robiÄ trzy drogie rzeczy, ktÃģrych unika bezpoÅrednie indeksowanie:
- Mieszanie i indeksowanie: DataLoader z shuffle=True generuje losowÄ permutacjÄ indeksÃģw, a nastÄpnie kaÅždy pracownik wybiera swÃģj kawaÅek. To wymaga losowego dostÄpu do pamiÄci w caÅym tensory 7M-prÃģbek â straszne dla lokalnoÅci pamiÄci i wyzwala bÅÄdy stron.
- Kolekcja i kopiowanie: KaÅždy pracownik gromadzi rozproszone prÃģbki w ciÄ gÅy tensor partii. To oznacza alokacjÄ nowej pamiÄci (alokacje stron), kopiowanie danych z losowych lokalizacji (bÅÄdy cache) i serializacjÄ wyniku z powrotem do procesu gÅÃģwnego za pomocÄ pamiÄci wspÃģÅdzielonej lub kolejki.
- WspÃģÅzawodnictwo o CPU: Czterech pracownikÃģw + proces gÅÃģwny na maszynie 4-vCPU oznacza staÅe przerwanie. KaÅždy pracownik jest odÅÄ czany 50,000 razy. Najgorsza zwÅoka wynosi 5 sekund â podczas ktÃģrej GPU nie ma nic do przetworzenia.
Z bezpoÅrednim indeksowaniem: X[i:i+rozmiar_partii] jest widokiem zero-kopiowym ciÄ gÅego tensora juÅž w pamiÄci. .to(device) wyzwala jeden transfer DMA z jednej ciÄ gÅej czÄÅci. Åŧadnych pracownikÃģw, Åžadnego mieszania, Åžadnej kolekcji, Åžadnych kopii miÄdzy procesami, Åžadnych przeÅÄ czeÅ kontekstu. GPU otrzymuje dane w mikrosekundach, a nie setkach milisekund.
Naprawa
Dla obciÄ ÅžeÅ pamiÄci GPU, gdzie caÅy zestaw danych mieÅci siÄ w pamiÄci RAM:
- Nie uÅžywaj DataLoader. BezpoÅrednie indeksowanie z pre-mieszaniem tablicy indeksÃģw jest prostsze i 100x szybsze:
indeksy = torch.randperm(num_samples) dla i w zakresie(0, num_samples, batch_size): partia = X[indeksy[i:i+batch_size]].to(device) wynik = model(partia)
- JeÅli musisz uÅžywaÄ DataLoader, dopasuj num_workers do Twoich rzeczywistych rdzeni CPU minus 1. Na maszynie 4-rdzeniowej num_workers=2 zmniejsza konflikty. Dodaj persistent_workers=True, aby uniknÄ Ä nakÅadu pracy fork.
- Dla zestawÃģw danych wiÄkszych niÅž pamiÄÄ gdzie DataLoader jest konieczny, prawdziwa wÄ ska garÅÄ przechodzi do wejÅcia/wyjÅcia dysku. UÅžyj prefetch_factor=2 (nie wiÄcej â wiÄcej prefetchingu oznacza wiÄcej ciÅnienia pamiÄci) i upewnij siÄ, Åže Twoje przechowywanie moÅže dotrzymaÄ tempa.
Szerszy Kontekst
To Åledzenie ilustruje wzorzec, ktÃģry widzimy ciÄ gle w obciÄ Åženiach GPU: GPU jest szybkie, host jest wÄ skÄ garÅciÄ , a metryki GPU nie mogÄ tego zobaczyÄ. nvidia-smi zgÅaszaÅ niskie wykorzystanie, ale nie mÃģgÅ powiedzieÄ, dlaczego. torch.profiler przechwyciÅ jÄ dra CUDA, ale przegapiÅ 200,000 przeÅÄ czeÅ kontekstu wystÄpujÄ cych w przestrzeni uÅžytkownika.
Jedynym sposobem, aby zobaczyÄ peÅny obraz, byÅo Åledzenie obu stron jednoczeÅnie â wywoÅaÅ interfejsu API CUDA na poziomie biblioteki i zdarzeÅ jÄ dra Linux â i skorelowanie ich w czasie i wedÅug ID procesu. ÅaÅcuch przyczynowy âCPU 100% -> 1,880 przeÅÄ czeÅ kontekstu -> cudaMemcpyAsync 301ms -> cudaStreamSync 42msâ opowiada caÅÄ historiÄ w jednej linii. Bez Åledzenia cross-stack tego pozostaÅoby tajemnicÄ â tak jak dla oryginalnego raportujÄ cego, ktÃģry spÄdziÅ tygodnie debugujÄ c to.
Rysunek 2: Kiedy zapytano âjaka jest podstawowa przyczyna?â, model identyfikuje nadmiar CPU powodujÄ cy opÃģÅšnienia w host-side scheduling. cudaLaunchKernel przeszedÅ od 73us do 25.8ms (356x wolniejszy) dlatego, Åže CPU nie mÃģgÅ zaplanowaÄ uruchomienia na czas.

SprÃģbuj Sam
OdtwÃģrz 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()
# Szybka ÅcieÅžka
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'BezpoÅrednie: {time.time()-start:.3f}s')
# Wolna ÅcieÅžka
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')
ÅledÅš z Ingero, aby zobaczyÄ, co dzieje siÄ pod spodem:
git clone https://github.com/ingero-io/ingero.git cd ingero && make build sudo ./bin/ingero trace --duration 60s # w jednej konsoli python3 benchmark.py # w innej konsoli ./bin/ingero explain --since 60s # po zakoÅczeniu benchmarku
Lub pomiÅ odtworzenie i przejdÅš bezpoÅrednio do naszych danych Åledzenia:
# WyÅwietl ÅaÅcuchy przyczynowe z Åledzenia ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m # Rozbicie na poziomie procesu (zobacz pracownikÃģw DataLoader vs proces gÅÃģwny) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m # PoÅÄ cz swÃģj asystent AI dla interaktywnego Åledzenia ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db
ÅledÅš z AI (zalecane). Najszybszym sposobem analizy Åledzenia jest poÅÄ czenie dowolnego kompatybilnego AI z bazÄ danych bezpoÅrednio. Nie jest wymagana rÄczna analiza.
UtwÃģrz plik konfiguracyjny:
cat > /tmp/ingero-mcp-dataloader.json << 'EOF'
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
EOF
NastÄpnie poÅÄ cz swÃģj model:
# Z Ollama + MiniMax (co uÅžyliÅmy w filmie) ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json # Z Claude Code claude --mcp-config /tmp/ingero-mcp-dataloader.json # Z dowolnym kompatybilnym klientem # Dodaj powyÅžszÄ konfiguracjÄ do ustawieÅ MCP Twojego AI
Wpisz /investigate, aby uruchomiÄ przewodzonÄ analizÄ lub zapytaj o coÅ: âCo spowodowaÅo gÅodowanie GPU?â AI ma dostÄp do 7 narzÄdzi, ktÃģre wysyÅajÄ zapytania do bazy danych Åledzenia bezpoÅrednio.
GitHub: github.com/ingero-io/ingero
Oryginalny problem: pytorch/pytorch#154318
Przewodnik wideo: https://asciinema.org/a/RGwhPeXAPJdhXqxp
Åledzenie wykonane na TensorDock RTX 4090 (24GB), Ubuntu 22.04, PyTorch 2.10.0+cu128.













