Raporlar

124 Katı Dahili Veri Yükleyici: PyTorch DataLoader’in Kernel Düzeyinde Gerçekten Ne Yaptığını Gösteriyor

mm
Unite.AI sitesini Google'daki tercih ettiğiniz kaynaklara ekleyin
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.

Bu makale, eBPF uprobes kullanarak gerçek bir PyTorch sorunu (#154318) üzerinde gerçekleştirilen bir kernel düzeyinde GPU izleme araştırmasının sonuçlarına dayanmaktadır. İzleme veritabanları, bağımsız doğrulama için Ingero açık kaynak deposunda yayımlanmıştır.

Özet

PyTorch’un DataLoader’i, doğrudan tensör indekslemesine kıyasla bellekteki GPU iş yükleri için 50-124 kat daha yavaş olabilir. Bir RTX 4090’da gerçek bir PyTorch sorununu yeniden ürettik ve her CUDA API çağrısı ve Linux kernel olayını izleyerek kök nedeni bulduk. GPU yavaş değildi – açydı. DataLoader işçiler, 40 saniye içinde 200.000 CPU bağlam değişikliği ve 300.000 sayfa tahsisi üretti, GPU’nun her veri aktarımı için ortalama 301 ms beklemesine neden oldu.

Sorun

Bir PyTorch kullanıcısı, basit bir MLP çıkarımı iş yükü için DataLoader’in doğrudan tensör indekslemesine kıyasla 7-22 kat daha yavaş olduğunu bildirdi. num_workers=12, pin_memory=True ve prefetch_factor=12 ile bile, fark hala çok büyüktü. GPU kullanımı %10-20’de kaldı.

Bunu yeniden ürettik. Aralık bizim donanımımızda daha da kötüydü:

Yöntem Süre Doğrudan Karşılaştırma
Doğrudan tensör indekslemesi 0.39s 1x
DataLoader (karıştırma=True) 48.49s 124 kat daha yavaş
DataLoader (optimize edilmiş, 4 işçi, pin_memory) 43.29s 111 kat daha yavaş

İş yükü basittir: 7M örnek, 100 özellik, 2 katmanlı MLP, toplu iş boyutu 1M. Model bir partiyi milisaniyede işler. Zaman nereye gider?

nvidia-smi’nin Gösterdiği

Hiçbir şey faydalı değil. GPU kullanımı %0 ile %30 arasında değişir. Bellek kullanımı sabittir. Sıcaklık iyidir. GPU açıkça underutilized, ancak nvidia-smi nedenini söyleyemez.

torch.profiler’ın Gösterdiği

Raporcu, PyTorch’un yerleşik profilini denedi ve “anlamlı izleme verisi alamadı.” Bu, bir uygulama düzeyinde profil için ortak bir hayal kırıklığıdır – CUDA çekirdeklerinin ne çalıştığını gösterebilir, ancak veri zamanında GPU’ya ulaşıp ulaşmadığını belirleyen host-side scheduling, bellek ve işlem yaşam döngüsü olaylarını göremez.

Kernel Düzeyinde İzlemenin Gösterdiği

Hem CUDA API çağrılarını (libcudart.so üzerinde eBPF uprobes kullanarak) hem de Linux kernel olaylarını (scheduler bağlam değişiklikleri, bellek sayfa tahsisleri, işlem çatalları) aynı anda izledik. Sonuçlar, hikayenin tamamını anlatıyor.

Tam video yürüyüşü: https://asciinema.org/a/RGwhPeXAPJdhXqxp

Videoda, bir açık ağırlıklı LLM (MiniMax-M2.7) ile izleme veritabanına MCP (Model Context Protocol) aracılığıyla bağlandık:

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

JSON yapılandırma dosyası, MCP istemcisine Ingero sunucusunun nerede bulunduğunu ve hangi izleme veritabanını yükleyeceğini söyler:

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

Bu, LLM’ye izleme verilerine 7 araç aracılığıyla doğrudan erişim sağlar: get_trace_stats, get_causal_chains, get_per_process_breakdown ve diğerleri. AI, veritabanını sorgulayabilir, CUDA olaylarını kernel scheduling verileriyle ilişkilendirebilir ve herhangi bir manuel analiz olmadan düzgün bir dille tanı koyabilir.

4 Yüksek Şiddetli Neden Zincirleri

Neden zinciri motoru, aynı kök neden olan 4 yüksek şiddetli modeli tespit etti:

[YÜKSEK] cudaStreamSync p99=42ms (1,638x p50=25us) - CPU 100% + 1,880 sched_switch olayı
Zaman çizelgesi:
[SYSTEM] CPU 100%
[HOST ] 1,880 bağlam değişikliği (21s off-CPU)
[CUDA ] p99=42ms (1,638x p50=25us)
Kök neden: DataLoader işçiler CPU için savaşıyor, büyük sayfa tahsisi baskısı
[YÜKSEK] cudaLaunchKernel p99=24.67ms (349x p50=70us) - CPU 100%
Kök: 34 sched_switch olayı

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

cudaStreamSync p99, p50’nin 1,638 katı. Bu, GPU’nun yavaş olduğu anlamına gelmez – bu, GPU’nun zamanında gelmeyen veri için beklediği anlamına gelir.

Şekil 1: /investigate çalıştırdıktan sonra AI tarafından oluşturulan analiz. Model, izleme veritabanına Ingero’nun 7 MCP aracını kullanarak sorguladı ve izleme verilerine dayanan eyleme geçirilebilir önerilerle birlikte düzgün bir dille açıklama üretti.

Süreç Başına Ayrıştırma

Burada açıkça görülüyor. Ana işlem ve 4 DataLoader işçisi ayrı varlıklar olarak görünür:

Ana işlem:

- cudaMemcpyAsync (host-to-device transfer): ortalama 301ms, maksimum 2.9 saniye
- cudaStreamSync: p99 = 42ms (normalde 25us)
- 1,567 bağlam değişikliği, ortalama 16ms off-CPU, en kötü duraklama 5 saniye
- 799,018 sayfa tahsisi
DataLoader işçi 1: 52,863 bağlam değişikliği, 89,338 sayfa tahsisi, en kötü duraklama 5s
DataLoader işçi 2: 50,638 bağlam değişikliği, 83,509 sayfa tahsisi, en kötü duraklama 5s
DataLoader işçi 3: 49,361 bağlam değişikliği, 70,035 sayfa tahsisi, en kötü duraklama 5s
DataLoader işçi 4: 38,862 bağlam değişikliği, 56,354 sayfa tahsisi, en kötü duraklama 5s

Toplam işçiler arasında: yaklaşık 191,000 bağlam değişikliği ve 299,000 sayfa tahsisi 40 saniye içinde.

Bu Ne Anlama Geliyor

DataLoader işçiler, doğrudan indekslemenin tamamen kaçındığı üç pahalı işlemi yapıyor:

  1. Karıştırma ve indeksleme: DataLoader ile shuffle=True, 7M örneklik tensör boyunca rasgele bir indeks permütasyonu oluşturur, her işçi kendi parçasını seçer. Bu, önbellek yerelliği için kötü olan ve sayfa hatalarını tetikleyen tam tensör boyunca rasgele bellek erişimini gerektirir.
  2. Toplama ve kopyalama: Her işçi, dağınık örnekleri bir dizi tensörüne toplar. Bu, yeni bellek tahsisi (sayfa tahsisleri), rasgele konumlardan veri kopyalaması (önbellek kaçırması) ve sonucu ana işleme geri aktarmayı gerektirir.
  3. CPU için rekabet: Dört işçi + ana işlem bir 4-vCPU makinesinde sürekli önyükleme anlamına gelir. Her işçi 50,000 kez planlanmaz. En kötü duraklama 5 saniyedir – bu süre zarfında GPU hiçbir şey işlemiyor.

Doğrudan indeksleme ile: X[i:i+batch_size] zaten bellekte olan bir dizi tensörünün sıfır kopya görünümüdür. .to(device) tek bir DMA transferini tetikler. Hiçbir işçi, karıştırma, toplama, kopyalama, işler arası kopyalama, bağlam değişikliği veya GPU’ya veri gelmesi yüzlerce milisaniye değil, mikrosaniye sürer.

Çözüm

Bellekteki GPU iş yükleri için, tüm veri kümesinin RAM’de sığdığı durumlarda:

  1. DataLoader’i kullanmayın. Önceden karıştırılmış bir indeks dizisi ile doğrudan indeksleme 100 kat daha hızlıdır:
    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. DataLoader’i kullanmanız gerekiyorsa, num_workers’ı gerçek CPU çekirdeklerinize göre ayarlayın. 4 çekirdekli bir makinede num_workers=2, çatışmayı azaltır. persistent_workers=True ekleyin để fork overhead’ini önleyin.
  3. Bellekten büyük veri kümeleri için DataLoader gerekli olduğunda, gerçek tıkanma noktası disk I/O’ya kayar. prefetch_factor=2 (daha yüksek değil – daha fazla prefetching daha fazla bellek baskısı anlamına gelir) kullanın ve depolamanızın yetişebildiğinden emin olun.

Daha Büyük Resim

Bu araştırma, GPU iş yüklerinde gördüğümüz bir modeli示示 ediyor: GPU hızlı, host tıkanma noktası ve GPU metrikleri nedenini göremez. nvidia-smi düşük kullanım raporu verdi ancak nedenini açıklayamadı. torch.profiler, CUDA çekirdeklerini yakaladı ancak kullanıcı alanında meydana gelen 200,000 bağlam değişikliğini kaçırdı.

Bu resmin tamamını görmek için, hem CUDA API çağrılarını hem de Linux kernel olaylarını aynı anda izlemek ve bunları zaman ve işlem kimliği ile ilişkilendirmek gerekiyordu. “CPU 100% -> 1,880 sched_switch -> cudaMemcpyAsync 301ms -> cudaStreamSync 42ms” neden zinciri, tek bir satırda hikayenin tamamını anlatıyor. Çapraz yığın izleme olmadan, bu bir gizem olarak kalacaktı – orijinal raporcu için de öyle oldu, hafta boyunca bunu halletmeye çalıştı.

Şekil 2: “Temel sorun nedir?” sorusuna yanıt olarak model, CPU aşırı aboneliğinin neden olduğu host-side scheduling gecikmelerini tanımlar. cudaLaunchKernel, CPU’nun zamanında başlatamaması nedeniyle 73us’den 25.8ms’ye (356 kat daha yavaş) çıktı.

Deneyin

Ölçeği yeniden üretin:

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

# Hızlı yol 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')

# Yavaş yol 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')

Ingero ile izleyin ve ne olduğu görün:

git clone https://github.com/ingero-io/ingero.git
cd ingero && make build
sudo ./bin/ingero trace --duration 60s # bir terminalde
python3 benchmark.py # başka bir terminalde
./bin/ingero explain --since 60s # benchmark tamamlandıktan sonra

Ya da ölçeklendirmeyi atlayın ve doğrudan izleme verilerini keşfedin. Araştırma veritabanı (764KB) depoda bulunmaktadır:

# Araştırma için neden zincirlerini görün
./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m

# Süreç başına ayrıştırma (DataLoader işçiler ve ana işlem) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m

# AI asistanınızı etkileşimli bir araştırma için bağlayın ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db

AI ile araştırın (önerilir). İzlemeyi analiz etmenin en hızlı yolu, herhangi bir MCP uyumlu AI’ı doğrudan veritabanına bağlamaktır. Hiçbir manuel analiz gerekmez.

Bir yapılandırma dosyası oluşturun:

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

Sonra modelinizi bağlayın:

# Ollama + MiniMax ile (videodaki gibi)
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json

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

# Herhangi bir MCP uyumlu istemci ile # Yapılandırmanızı AI'nizin MCP ayarlarına ekleyin

/investigate yazarak kılavuzlu analizi tetikleyin veya herhangi bir soru sorun: “GPU açlığını ne gây etti?” AI, izleme veritabanına 7 araç aracılığıyla doğrudan erişim sahiptir.

GitHub: github.com/ingero-io/ingero
Orijinal sorun: pytorch/pytorch#154318
Video yürüyüşü: https://asciinema.org/a/RGwhPeXAPJdhXqxp

Araştırma, TensorDock RTX 4090 (24GB), Ubuntu 22.04, PyTorch 2.10.0+cu128 üzerinde gerçekleştirilmiştir.

David Mail, Ingero'nun açık kaynaklı bir eBPF ajanı olan CUDA düzeyinde GPU gözlemlenebilirliği için ortak yazar ve bakım sorumlusudur. Üretim AI iş yüklerinin çekirdek düzeyinde izlenmesinde uzmanlaşmıştır.