İçeriğe geç

Storage I/O Engineering: Dataset'in Eğitimi Yavaşlatma Sanatı (ve Önleme)

Dataset bottleneck: GPU %30 idle bekliyor çünkü disk yetmiyor. NVMe Gen3/Gen4/Gen5 throughput, dataset format seçimi (parquet vs arrow vs webdataset), HuggingFace datasets caching, num_workers tuning, prefetch_factor, persistent_workers, pinned memory, FSx vs S3 vs local — RTX 4090 + 50K Türkçe dataset'i 0 idle çalıştırma reçetesi.

Şükrü Yusuf KAYA
31 dakikalık okuma
İleri
Storage I/O Engineering: Dataset'in Eğitimi Yavaşlatma Sanatı (ve Önleme)
🎯 Bu ders
Bir Llama 3.1 8B QLoRA Lab'ı tek 4090'da 2.5 step/s koşmalı — eğer 1.5 step/s alıyorsan büyük olasılıkla dataloader CPU-bound ya da disk-bound. Bu ders o farkı kapatmak için.

1. NVMe Bandwidth Hızlı Bakış#

TipSequential ReadRandom 4K ReadML için
SATA SSD0.55 GB/s95K IOPSNO
NVMe Gen3 x43.5 GB/s600K IOPSOK kıt
NVMe Gen4 x4 (cookbook)7.4 GB/s1M IOPS✅ baseline
NVMe Gen5 x412 GB/s1.5M IOPSpremium
Kontrol komutu:
sudo hdparm -Tt /dev/nvme0n1 # Buffered: ~6800 MB/s (NVMe Gen4) # Random 4K sudo fio --filename=/data/test --size=4G --direct=1 --rw=randread --bs=4k --numjobs=4 --runtime=30

2. Dataset Format Karşılaştırma#

FormatRandom accessStreamingCompressionCookbook için
JSONL (raw)yavaş (line scan)iyiyok (büyük)spike
Parquetiyi (row group)iyisnappy/zstdbüyük tabular
Arrow IPC (HF cache)çok hızlı (mmap)iyiyokHF datasets default
WebDataset (.tar)sequential onlyen iyitar + content compressçok büyük (TB+)
MosaicML Streaming (MDS)randomen iyizstdTB+ + S3
Cookbook tercih kuralı:
  • < 5 GB dataset → HF
    datasets
    (Arrow IPC, mmap'ten random access)
  • 5-100 GB → WebDataset (
    .tar
    shards) veya MDS
  • 100 GB → MDS + S3 (training-time streaming)
HF datasets'in en güzel yanı: ilk yükleme
load_dataset(...)
sırasında Arrow IPC dosyalarına convert edip
~/.cache/huggingface/datasets
'e koyar. Sonraki yüklemeler
mmap
ile sıfır kopya.
python
# === Storage I/O bench — DataLoader'ın gerçek throughput'unu ölç ===
import time, torch
from torch.utils.data import DataLoader, IterableDataset
from datasets import load_dataset
 
dataset = load_dataset("malhajar/alpaca-gpt4-tr", split="train")
# Tokenize ve format
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3.1-8B")
 
def tokenize(ex):
return tok(ex["text"], truncation=True, max_length=2048, padding=False)
 
dataset = dataset.map(tokenize, batched=True, num_proc=8)
dataset.set_format("torch", columns=["input_ids", "attention_mask"])
 
# Bench farklı config'ler
def bench(loader, n=200):
it = iter(loader)
next(it) # warmup
t0 = time.perf_counter()
n_tokens = 0
for i in range(n):
batch = next(it)
n_tokens += batch["input_ids"].numel()
elapsed = time.perf_counter() - t0
return n_tokens / elapsed # tokens/sec
 
for nw, pf, pin in [(0, 2, False), (2, 2, True), (4, 2, True), (4, 4, True), (8, 4, True)]:
loader = DataLoader(dataset, batch_size=8,
num_workers=nw, prefetch_factor=pf,
pin_memory=pin, persistent_workers=(nw > 0))
tps = bench(loader)
print(f"num_workers={nw:2d} prefetch={pf} pin={pin} → {tps/1e6:5.2f} M tokens/s")
 
# Typical RTX 4090 + Ryzen 7950X output:
# num_workers=0 prefetch=2 pin=False → 0.42 M tokens/s ← CPU-bound
# num_workers=2 prefetch=2 pin=True → 2.10 M tokens/s
# num_workers=4 prefetch=2 pin=True → 3.85 M tokens/s
# num_workers=4 prefetch=4 pin=True → 4.20 M tokens/s ← sweet spot
# num_workers=8 prefetch=4 pin=True → 3.90 M tokens/s ← context switch overhead
DataLoader tuning bench — sweet spot bulma

3. Tuning Kuralları (RTX 4090 + Ryzen 7950X reference)#

SettingCookbook defaultNiye
num_workers
min(8, cpu_threads // 2)her worker bir CPU thread'i tutar
prefetch_factor
4her worker 4 batch buffer
pin_memory
TrueDMA-direct GPU transfer
persistent_workers
Trueepoch arası worker recreate'i atla
shuffle
True (train)gradient noise için
drop_last
Trueson batch'in unstable size'ı atla
num_workers
aşırı yüksek olursa
(örn. 16+): CPU context-switching overhead, prefetch buffer RAM yer, bazen yavaşlar.
pin_memory=True
ile
GPU transfer asenkron olur —
tensor.to("cuda", non_blocking=True)
ile pipelined. Bu olmadan transfer her step'te blocking.

4. WebDataset: Büyük Dataset İçin Standart#

50 GB+ dataset varsa cookbook tercih: WebDataset
.tar
shard'ları.
# Dataset'i shard'la (her shard ~512MB-2GB tar) python -m webdataset.shardify \ --output 'shards/tr-corpus-{000000..000050}.tar' \ --maxcount 10000 --maxsize 1e9 \ raw_data/*.jsonl
Loading:
import webdataset as wds url = "shards/tr-corpus-{000000..000050}.tar" ds = ( wds.WebDataset(url, shardshuffle=100) .shuffle(1000) .decode("torch") .to_tuple("input_ids", "attention_mask") .batched(8) ) loader = wds.WebLoader(ds, num_workers=4, batch_size=None)
Faydaları:
  • Sequential tar read → NVMe full throughput (7.4 GB/s Gen4'te)
  • Cloud (S3) ile native —
    wds.WebDataset("pipe:aws s3 cp s3://...")
  • Shard-level shuffle yeterli (intra-shard shuffle de yapılır)
Cookbook'un kuralı: > 10K samples ve > 5 GB → WebDataset.
🐛 FMD — 'GPU %25 idle, dataset bottleneck'
`nvtop` ya da `nvidia-smi dmon -s u` ile GPU utilization'ı izle. %85'in altında uzun süre kalıyorsa dataloader sorunu. Hipotezler: (a) num_workers=0 → tek thread tokenize. Çözüm: num_workers=4+. (b) On-the-fly tokenization eğitim sırasında → pre-tokenize batch öncesi. Çözüm: `dataset.map(tokenize, batched=True, num_proc=8)` ile pre-tokenize ve `set_format("torch")` ile mmap-able yap. (c) pin_memory kapalı → her step CPU→GPU transfer blocking. Çözüm: `pin_memory=True` + `to(...non_blocking=True)`. (d) Dataset NFS'te → random read ms gecikme. Çözüm: local NVMe'ye kopyala (50 GB'a kadar). Drill: nvtop'ta GPU idle gözlemle, 4 hipotezi sırayla elimine et.

5. Bench (RTX 4090 + Ryzen 7950X + NVMe Gen4)#

Llama 3.1 8B QLoRA, batch=2, seq=4096, packing on.
DataLoader configtokens/s (loader)step/s (training)GPU utilBottleneck
nw=0, no pin420K0.6228%CPU
nw=2, pin2.1M1.5078%borderline
nw=4, prefetch=4, pin (default)4.2M1.7896%GPU ✅
nw=8, prefetch=4, pin3.9M1.7495%overhead
WebDataset 4 shards5.1M1.7896%GPU (eşit)
Sweet spot: num_workers=4, prefetch=4, pin_memory=True, persistent_workers=True.
✅ Teslim
  1. Yukarıdaki bench script'ini koş. 2) GPU utilization'ı `nvtop` ile izle — %95+ olmalı. 3) Cookbook default'unu Lab'larında otomatik kullan. 4) Sonraki ders: 1.7 — Profiling Stack: torch.profiler + nsys + ncu.

Sık Sorulan Sorular

Default: \`~/.cache/huggingface/datasets\` ve \`~/.cache/huggingface/hub\`. Cookbook NVMe Gen4'te tutmayı önerir. Başka diske taşımak için \`HF_HOME=/data/hf-cache\` env var. Önemli: HDD'ye koyma — ilk \`load_dataset\`'te Arrow IPC dosyaları yazılır ve sonra mmap'tan okunur, HDD'de yavaş.

Yorumlar & Soru-Cevap

(0)
Yorum yazmak için giriş yap.
Yorumlar yükleniyor...

İlgili İçerikler