Artikel ini berdasarkan temuan dari penyelidikan jejak tingkat kernel GPU yang dilakukan pada masalah PyTorch yang nyata (#154318) menggunakan eBPF uprobes. Basis data jejak diterbitkan di repositori Ingero open-source untuk verifikasi independen.
TL;DR
DataLoader PyTorch dapat 50-124x lebih lambat daripada indeks tensor langsung untuk pekerjaan memori GPU. Kami mereproduksi masalah PyTorch yang nyata pada RTX 4090 dan melacak setiap panggilan API CUDA dan peristiwa kernel Linux untuk menemukan penyebab akar. GPU tidak lambat – itu kelaparan. Pekerja DataLoader menghasilkan 200.000 switch konteks CPU dan 300.000 alokasi halaman dalam 40 detik, meninggalkan GPU menunggu rata-rata 301ms per transfer data yang seharusnya memakan waktu mikrodetik.
Masalahnya
Pengguna PyTorch melaporkan bahwa DataLoader 7-22x lebih lambat daripada indeks tensor langsung untuk pekerjaan inferensi MLP sederhana. Bahkan dengan num_workers=12, pin_memory=True, dan prefetch_factor=12, celahnya tetap besar. Utilisasi GPU berada pada 10-20%.
Kami mereproduksi masalahnya. Celahnya bahkan lebih buruk pada perangkat keras kami:
| Metode | Waktu | vs Langsung |
|---|---|---|
| Indeks tensor langsung | 0,39s | 1x |
| DataLoader (acak=True) | 48,49s | 124x lebih lambat |
| DataLoader (dioptimalkan, 4 pekerja, pin_memory) | 43,29s | 111x lebih lambat |
Pekerjaan ini sangat sederhana: 7M sampel, 100 fitur, 2-lapis MLP, ukuran batch 1M. Model memproses batch dalam milidetik. Jadi, di mana waktu itu pergi?
Apa yang Ditunjukkan nvidia-smi
Tidak ada yang berguna. Utilisasi GPU berfluktuasi antara 0% dan 30%. Penggunaan memori stabil. Suhu baik. GPU jelas tidak terpakai, tetapi nvidia-smi tidak bisa memberitahu Anda mengapa.
Apa yang Ditunjukkan torch.profiler
Pelapor mencoba profiler bawaan PyTorch dan “tidak mendapatkan data jejak yang berarti.” Ini adalah frustrasi umum – profiler aplikasi dapat menunjukkan kernel CUDA yang berjalan, tetapi tidak bisa melihat penjadwalan host, memori, dan peristiwa siklus proses yang menentukan apakah data tiba di GPU tepat waktu.
Apa yang Ditunjukkan Jejak Tingkat Kernel
Kami menjalankan benchmark sambil melacak panggilan API CUDA (melalui eBPF uprobes pada libcudart.so) dan peristiwa kernel Linux (switch konteks penjadwal, alokasi halaman memori, fork proses) secara bersamaan. Hasilnya menceritakan kisah lengkap.
Video walkthrough lengkap: https://asciinema.org/a/RGwhPeXAPJdhXqxp
Dalam video, kami menghubungkan LLM terbuka (MiniMax-M2.7) ke basis data jejak melalui MCP (Model Context Protocol):
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.jsonFile konfigurasi JSON memberitahu klien MCP di mana menemukan server Ingero dan basis data jejak mana yang harus dimuat:
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
Ini memberikan LLM akses langsung ke data jejak melalui 7 alat: get_trace_stats, get_causal_chains, get_per_process_breakdown, dan lain-lain. AI dapat mengquery basis data, mengorelasikan peristiwa CUDA dengan data penjadwalan kernel, dan menghasilkan diagnosis bahasa sederhana tanpa analisis manual.
4 Rantai Penyebab Tingkat Tinggi
Mesin rantai penyebab mendeteksi 4 pola tingkat tinggi, semua dengan penyebab akar yang sama:
[TINGGI] cudaStreamSync p99=42ms (1.638x p50=25us) - CPU 100% + 1.880 switch konteks Timeline: [SYSTEM] CPU 100% [HOST ] 1.880 switch konteks (21s off-CPU) [CUDA ] p99=42ms (1.638x p50=25us) Penyebab akar: Pekerja DataLoader berebut CPU, tekanan alokasi halaman yang besar
[TINGGI] cudaLaunchKernel p99=24,67ms (349x p50=70us) - CPU 100% Root: 34 switch konteks [TINGGI] cuMemAlloc p99=627us (4,0x p50) - CPU 100% [TINGGI] cuLaunchKernel p99=106us (4,0x p50) - CPU 100%
cudaStreamSync p99 adalah 1.638 kali p50. Itu bukan lambatnya GPU – itu GPU menunggu data yang tidak pernah tiba tepat waktu.
Gambar 1: Analisis yang dihasilkan AI setelah menjalankan /investigate. Model menggunakan 7 alat MCP Ingero untuk mengquery basis data jejak dan menghasilkan penjelasan bahasa sederhana dengan rekomendasi yang dapat dilakukan yang berasal langsung dari data jejak.

Pembagian Proses per Proses
Ini adalah tempat di mana semuanya menjadi jelas. Proses utama dan 4 pekerja DataLoader-nya terlihat sebagai entitas terpisah:
Proses Utama:
- cudaMemcpyAsync (transfer host-ke-perangkat): rata-rata 301ms, maksimum 2,9 detik - cudaStreamSync: p99 = 42ms (biasanya 25us) - 1.567 switch konteks, rata-rata 16ms off-CPU, stall terburuk 5 detik - 799.018 alokasi halaman
Pekerja DataLoader 1: 52.863 switch konteks, 89.338 alokasi halaman, stall terburuk 5s Pekerja DataLoader 2: 50.638 switch konteks, 83.509 alokasi halaman, stall terburuk 5s Pekerja DataLoader 3: 49.361 switch konteks, 70.035 alokasi halaman, stall terburuk 5s Pekerja DataLoader 4: 38.862 switch konteks, 56.354 alokasi halaman, stall terburuk 5s
Total di seluruh pekerja: ~191.000 switch konteks dan ~299.000 alokasi halaman dalam 40 detik.
Apa yang Dimaksud
Pekerja DataLoader melakukan tiga hal yang mahal yang dihindari oleh indeks langsung:
- Mengacak dan mengindeks: DataLoader dengan shuffle=True menghasilkan permutasi acak dari indeks, lalu setiap pekerja memilih bagian mereka. Ini memerlukan akses memori acak di seluruh tensor 7M-sampel – buruk untuk lokalitas cache dan memicu page faults.
- Menggabungkan dan menyalin: Setiap pekerja mengumpulkan sampel yang tersebar ke dalam tensor batch kontinu. Ini berarti mengalokasikan memori baru (alokasi halaman), menyalin data dari lokasi acak (cache misses), dan meng serialisasi hasilnya kembali ke proses utama melalui memori bersama atau antrian.
- Berebut CPU: Empat pekerja + proses utama pada mesin 4-vCPU berarti preemption konstan. Setiap pekerja dijadwalkan ulang 50.000 kali. Stall terburuk adalah 5 detik – selama itu GPU tidak memiliki apa-apa untuk diproses.
Dengan indeks langsung: X[i:i+batch_size] adalah tampilan tanpa salinan dari tensor kontinu yang sudah ada di memori. .to(device) memicu satu transfer DMA dari wilayah kontinu tunggal. Tidak ada pekerja, tidak ada pengacakan, tidak ada penggabungan, tidak ada salinan antar proses, tidak ada switch konteks. GPU mendapatkan data dalam mikrodetik, bukan ratusan milidetik.
Perbaikan
Untuk pekerjaan memori GPU di mana seluruh dataset muat di RAM:
- Jangan gunakan DataLoader. Indeks langsung dengan array indeks yang telah diacak sebelumnya lebih sederhana dan 100x lebih cepat:
indeks = torch.randperm(num_samples) for i in range(0, num_samples, batch_size): batch = X[indeks[i:i+batch_size]].to(device) output = model(batch)
- Jika Anda harus menggunakan DataLoader, sesuaikan num_workers dengan inti CPU Anda yang sebenarnya minus 1. Pada mesin 4-core, num_workers=2 mengurangi konten. Tambahkan persistent_workers=True untuk menghindari overhead fork.
- Untuk dataset yang lebih besar dari memori di mana DataLoader diperlukan, bottleneck yang sebenarnya bergeser ke I/O disk. Gunakan prefetch_factor=2 (bukan lebih tinggi – prefetching lebih banyak berarti tekanan memori lebih banyak) dan pastikan penyimpanan Anda dapat mengimbanginya.
Gambaran Besar
Penyelidikan ini menggambarkan pola yang kami lihat secara konstan dalam pekerjaan GPU: GPU cepat, host adalah bottleneck, dan metrik GPU tidak bisa melihatnya. nvidia-smi melaporkan utilisasi yang rendah tetapi tidak bisa menjelaskan mengapa. torch.profiler menangkap kernel CUDA tetapi melewatkan 200.000 switch konteks yang terjadi di ruang pengguna.
Cara satu-satunya untuk melihat gambaran lengkap adalah dengan melacak kedua sisi secara bersamaan – panggilan API CUDA dan peristiwa penjadwalan kernel Linux – dan mengorelasikannya berdasarkan waktu dan ID proses. Rantai penyebab “CPU 100% -> 1.880 switch konteks -> cudaMemcpyAsync 301ms -> cudaStreamSync 42ms” menceritakan kisah lengkap dalam satu baris. Tanpa jejak lintas-tumpukan, ini akan tetap menjadi misteri – seperti yang dialami oleh pelapor asli yang menghabiskan beberapa minggu untuk memecahkannya.
Gambar 2: Ketika ditanya “apa inti masalahnya?”, model mengidentifikasi oversubscription CPU yang menyebabkan keterlambatan penjadwalan host. cudaLaunchKernel berubah dari 73us menjadi 25,8ms (356x lebih lambat) karena CPU tidak bisa menjadwalkan peluncuran tepat waktu.

Coba Sendiri
Reproduksi benchmark:
import torch, time from torch.utils.data import DataLoaderX = 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()
# Jalur cepat 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'Direct: {time.time()-start:.3f}s')
# Jalur lambat 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')
Jejak dengan Ingero untuk melihat apa yang terjadi di bawah tudung:
git clone https://github.com/ingero-io/ingero.git cd ingero && make build sudo ./bin/ingero trace --duration 60s # di terminal lain python3 benchmark.py # di terminal lain ./bin/ingero explain --since 60s # setelah benchmark selesai
Atau lewati reproduksi dan jelajahi data jejak kami langsung. Basis data investigasi (764KB) ada di repo:
# Lihat rantai penyebab dari investigasi ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m# Pembagian proses per proses (lihat pekerja DataLoader vs proses utama) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m
# Hubungkan asisten AI Anda untuk penyelidikan interaktif ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db
Investigasi dengan AI (direkomendasikan). Cara tercepat untuk menganalisis jejak adalah dengan menghubungkan AI yang kompatibel dengan MCP langsung ke basis data. Tidak ada analisis manual yang diperlukan.
Buat file konfigurasi:
cat > /tmp/ingero-mcp-dataloader.json << 'EOF'
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
EOF
Kemudian hubungkan model Anda:
# Dengan Ollama + MiniMax (apa yang kami gunakan di video) ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json# Dengan Claude Code claude --mcp-config /tmp/ingero-mcp-dataloader.json
# Dengan klien MCP yang kompatibel # Tambahkan konfigurasi di atas ke pengaturan MCP AI Anda
Tulis /investigate untuk memicu analisis yang dipandu, atau tanyakan pertanyaan apa pun: “Apa yang menyebabkan kelaparan GPU?” AI memiliki akses ke 7 alat yang mengquery basis data jejak langsung.
GitHub: github.com/ingero-io/ingero
Masalah Asli: pytorch/pytorch#154318
Video Walkthrough: https://asciinema.org/a/RGwhPeXAPJdhXqxp
Penyelidikan dilakukan pada TensorDock RTX 4090 (24GB), Ubuntu 22.04, PyTorch 2.10.0+cu128.













