Memory Profiling: torch.profiler, Nsight Systems, OOM Debugging — Production GPU Memory Yönetimi
GPU memory'sinin gizli yaşamı: aktivasyon vs gradient vs optimizer state breakdown, torch.profiler ile memory snapshot, Nsight Systems timeline analizi, OOM root cause analysis, activation checkpointing, gradient accumulation, fragmentation çözümleri.
Şükrü Yusuf KAYA
55 dakikalık okuma
İleri📊 'OOM' = 'Operasyonel Olarak Müthiş bir gün'
70B LLM training'de en sık karşılaştığın hata: CUDA out of memory. 'Daha büyük GPU al' yanlış cevap (her zaman değil). Doğru cevap: memory'i anla, ona göre optimize et. Bu ders production-grade memory engineering. 55 dakika sonra: hangi tensor'lar memory'i tüketiyor, OOM'u root cause'a kadar takip etme, 70B model'i 80GB GPU'da fit etme teknikleri — hepsini bileceksin.
Ders Haritası#
- GPU memory anatomy — neler olabiliyor
- Memory breakdown: weights, activations, gradients, optimizer
- API'leri
torch.cuda.memory_* - torch.profiler memory snapshot
- Nsight Systems timeline analizi
- OOM root cause patterns
- Activation checkpointing
- Gradient accumulation
- Memory fragmentation
- Production memory budget planning
1. GPU Memory Anatomy#
H100 80GB GPU içinde ne var bir training run'da?
Static (sabit) memory#
- Model weights (BF16): N param × 2 byte
- Master weights (FP32): N × 4 byte
- Optimizer states (Adam: m + v): N × 8 byte (FP32)
- CUDA context: ~1 GB
- PyTorch overhead: ~0.5 GB
Dynamic (training adımına göre değişen) memory#
- Activations: forward pass'te saklananlar (backward için)
- Gradients (BF16): N × 2 byte
- Temporary buffers: matmul intermediate, layer outputs
- KV cache (inference): per-token cache
70B Llama 3 örneği (training)#
Weights BF16: 140 GB Master FP32: 280 GB Optimizer (Adam): 560 GB Gradients BF16: 140 GB Activations: ~50 GB (batch 8, seq 8192)
Toplam ~1.17 TB. 80GB GPU'da fit etmez → FSDP/ZeRO sharding gerek (Modül 17).
Inference scenario (70B)#
Weights BF16: 140 GB KV cache (1 token): ~2 MB/token, 8K context = 16 GB Activations: ~5 GB
~160 GB. 2x H100 80GB ile inference mümkün (model parallelism).
2. torch.cuda.memory_* API'leri#
torch.cuda.memory_*PyTorch'un memory introspection toolkit'i:
import torch # Snapshot allocated = torch.cuda.memory_allocated() # şu an ayrılmış (bytes) reserved = torch.cuda.memory_reserved() # PyTorch'un toplam tuttuğu (bytes) max_allocated = torch.cuda.max_memory_allocated() # peak (training boyunca) max_reserved = torch.cuda.max_memory_reserved() # Reset peak stats torch.cuda.reset_peak_memory_stats() # Stats string print(torch.cuda.memory_summary())
allocated vs reserved#
allocatedreserved- allocated: tensor'ların kullandığı bellek
- reserved: PyTorch'un caching allocator'unun cuda'dan istediği (cache dahil)
reserved - allocatedPratik kullanım#
def report_memory(label): a = torch.cuda.memory_allocated() / 1024**3 r = torch.cuda.memory_reserved() / 1024**3 p = torch.cuda.max_memory_allocated() / 1024**3 print(f"{label}: {a:.2f}GB allocated, {r:.2f}GB reserved, {p:.2f}GB peak") report_memory("Before model load") model = load_model().cuda() report_memory("After model load") x = torch.randn(32, 1024, device="cuda") report_memory("After input creation") out = model(x) report_memory("After forward") out.sum().backward() report_memory("After backward")
3. torch.profiler — Memory Snapshot#
PyTorch'un detaylı memory profiling tool'u.
Basic usage#
from torch.profiler import profile, ProfilerActivity, record_function with profile( activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA], profile_memory=True, record_shapes=True, ) as prof: out = model(x) out.sum().backward() print(prof.key_averages().table(sort_by="self_cuda_memory_usage", row_limit=20))
Memory snapshot — tensor-level detay#
PyTorch 2.0+ feature:
torch.cuda.memory._record_memory_history(enabled="all") # Training step out = model(x) out.sum().backward() optimizer.step() torch.cuda.memory._dump_snapshot("memory_snapshot.pickle") torch.cuda.memory._record_memory_history(enabled=None)
Sonra browser'da:
https://pytorch.org/memory_viz
memory_snapshot.pickleVisualization#
Memory_viz'de görürsün:
- Allocation stack trace — hangi koddan
- Tensor lifetime — başlangıç ve bitiş
- Memory pressure peaks
- Reuse patterns
Bu, optimization'ın altın madeni. OOM olduğunda öncelikle bu görsel.
4. Nsight Systems — Timeline Analizi#
NVIDIA'nın system-wide profiler'ı. Sadece memory değil, kernel timing, communication, multi-GPU dynamics — hepsi.
Kurulum#
NVIDIA developer hesabı → indir + install. Linux'ta:
nsys profile -o profile_out python train.py
profile_out.nsys-repGörselleştirilenler#
- CUDA kernel timeline: hangi kernel ne zaman, ne kadar süre
- CPU activity: Python overhead, data loading
- Memcpy ops: CPU↔GPU transfers
- NCCL collective ops: distributed training'de
- GPU utilization: % busy
- Power consumption
Tipik findings#
Pattern 1: Idle GPU
Timeline'da GPU bazen %0 busy. Sebep: CPU bottleneck (data loading slow, Python sync).
Çözüm: artır, pinned memory.
num_workersPattern 2: Long kernels
Tek bir matmul %30 time alıyor. Çözüm: kernel fusion (torch.compile), better tiling.
Pattern 3: Frequent small kernels
Yüzlerce küçük kernel — launch overhead dominant. Çözüm: fused ops, torch.compile.
Pattern 4: Communication overhead
Multi-GPU'da NCCL allreduce %20+ time. Çözüm: gradient bucketing, overlap.
Modern alternatif: NVIDIA DLProf#
Domain-specific (deep learning) profiler. Nsight'ın üzerine LLM-specific insights.
5. OOM Root Cause Patterns#
CUDA out of memoryPattern 1: Batch size too large#
Naive: batch_size çok yüksek → forward'da activations memory'e sığmıyor.
Tespit: track et, batch küçük → büyük denemeleri.
Çözüm: küçük + gradient accumulation (aşağıda).
max_memory_allocatedmicro_batch_sizePattern 2: Sequence too long#
Long context (örn. 32K, 128K) → attention O(T²) memory.
Tespit: short seq çalışıyor, long seq OOM.
Çözüm: FlashAttention (memory-efficient, O(T)), Modül 33.
Pattern 3: Activation explosion#
Bazı katmanlarda intermediate activations çok büyük (örn. FFN hidden 4x).
Tespit: memory profiler ile hangi node'da peak.
Çözüm: activation checkpointing (aşağıda).
Pattern 4: Gradient retention#
backward()optimizer.step()optimizer.zero_grad(set_to_none=True)Pattern 5: Multiple model copies#
Validation veya ensemble'da ikinci model copy → 2x memory.
Tespit: validation sırasında OOM.
Çözüm: validation , model swap (cpu↔gpu).
with torch.no_grad():Pattern 6: Memory fragmentation#
Total free > requested size ama allocator allocate edemiyor.
Tespit: < limit ama OOM.
Çözüm: (2.1+).
memory_allocatedPYTORCH_CUDA_ALLOC_CONF=expandable_segments:TruePattern 7: Other tensors#
KV cache (inference), data loaders, validation set on GPU.
Tespit: snapshot'ta beklenmeyen büyük tensor'lar.
Çözüm: data CPU'da tutması, KV cache management.
6. Activation Checkpointing — Memory ↔ Compute Trade-off#
Forward pass'te tüm aktivasyonları saklamak yerine, sadece bazılarını sakla. Backward'da gerekirse yeniden hesapla.
Trade-off#
- Memory: O(N) → O(√N) (Chen 2016, gradient checkpointing)
- Compute: forward 1x + backward'da extra forward = ~1.5x compute total
70B model'de activation memory 50GB → 5GB ile %90 tasarruf, %33 daha yavaş training.
PyTorch usage#
from torch.utils.checkpoint import checkpoint class TransformerBlock(nn.Module): def forward(self, x): return checkpoint(self._forward, x, use_reentrant=False) def _forward(self, x): x = self.attention(x) x = self.ffn(x) return x
Hangi katmanlara?#
- Transformer blokları (en yaygın)
- Encoder/decoder ayrı ayrı
- Memory-heavy operations (attention, large FFN)
Selective checkpointing#
# Sadece her N. blok checkpoint class Transformer(nn.Module): def forward(self, x): for i, block in enumerate(self.blocks): if i % 2 == 0: # her ikinci blok checkpoint x = checkpoint(block, x, use_reentrant=False) else: x = block(x) return x
Daha akıllı memory/compute trade-off.
use_reentrant=False niye?#
use_reentrant=FalseModern API. Eski reentrant version backward bug'larına yol açıyordu. False set et.
FSDP ile combine#
from torch.distributed.fsdp import FullyShardedDataParallel, checkpoint_wrapper # Each FSDP module wrapped with checkpoint
Modül 17 (Distributed Training) detayda.
7. Gradient Accumulation — Effective Batch Size Artırma#
GPU memory'sini aşmayan küçük micro-batch'ler ile büyük effective batch simulate et.
Algoritma#
accumulation_steps = 4 optimizer.zero_grad() for i, batch in enumerate(dataloader): with autocast(device_type="cuda", dtype=torch.bfloat16): out = model(batch.input) loss = criterion(out, batch.target) / accumulation_steps # scale loss.backward() if (i + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad()
Mathematics#
- micro_batch=4, accumulation=4 → effective_batch=16
- Aynı loss landscape (batch=16 ile equivalent)
- Memory: micro_batch boyutu (4) kadar, gradient sum cumulative ama tek gradient buffer
Trade-off#
- ✓ Memory tasarruf
- ✗ Speed yavaş (her step birden çok forward+backward)
- ✓ Gradient quality eşit
Pratik#
Llama 3 8B'yi 80GB GPU'da fine-tune:
- Naive batch=8: OOM
- Micro batch=2 + accumulation=4: OK (effective batch=8)
FSDP/ZeRO ile combine#
# FSDP shards parameters across GPUs # Gradient accumulation: micro_batch local # Effective batch: micro_batch × num_gpus × accumulation
Aşırı dengelemek mümkün — Modül 17.
8. Memory Fragmentation#
Total free memory: 30 GB Requested: 5 GB Result: OOM ❌
Niye? Free memory parçalanmış — büyük contiguous chunk yok.
Cause#
PyTorch'un caching allocator: kullanılan + cached tensor'lar bellekte yer alıyor. Free'lenen küçük tensor'lar arasında küçük holes.
Detection#
print(torch.cuda.memory_summary()) # "Active memory" ve "Reserved memory" arasındaki gap # Cached blocks histogram
Çözümler#
1. Expandable segments (PyTorch 2.1+)
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
Allocator daha esnek — fragmentation azalır. Default değil ama 2.2+'de stable.
2. Explicit empty_cache
torch.cuda.empty_cache() # PyTorch'un cached memory'sini CUDA'ya geri ver
Pahalı op (synchronizes), sık çağırma. Critical anlarda kullan.
3. Caching disable (debug)
export PYTORCH_NO_CUDA_MEMORY_CACHING=1
Allocator devre dışı. Yavaş ama deterministic. Debug için.
4. Better allocator
export PYTORCH_CUDA_ALLOC_CONF=garbage_collection_threshold:0.8,max_split_size_mb:512
- : %80'i dolduğunda GC tetikle
garbage_collection_threshold - : 512MB'tan büyük block'lar split edilemez (fragmentation azalır)
max_split_size_mb
Pratik öneri#
LLM training'de:
- always
expandable_segments:True - yardımcı
max_split_size_mb:512 - her N step (sık değil)
empty_cache - Memory snapshot ile fragmentation görsel kontrol
python
import torchfrom torch.profiler import profile, ProfilerActivity device = "cuda"model = MyTransformer().to(device)optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4)criterion = torch.nn.CrossEntropyLoss() # Reset peaktorch.cuda.reset_peak_memory_stats()torch.cuda.empty_cache() # Memory snapshot recording (PyTorch 2.0+)torch.cuda.memory._record_memory_history(enabled="all", context="all", stacks="python") # Single training step with profilingx = torch.randn(8, 1024, 1024, device=device)y = torch.randint(0, 100, (8, 1024), device=device) with profile( activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA], profile_memory=True, record_shapes=True, with_stack=True,) as prof: optimizer.zero_grad() with torch.amp.autocast(device_type="cuda", dtype=torch.bfloat16): out = model(x) loss = criterion(out.view(-1, 100), y.view(-1)) loss.backward() optimizer.step() # Print top memory usersprint(prof.key_averages().table( sort_by="self_cuda_memory_usage", row_limit=15)) # Save snapshot for visualizationtorch.cuda.memory._dump_snapshot("memory_snapshot.pickle")torch.cuda.memory._record_memory_history(enabled=None) # Manual reportingprint(f"\nPeak allocated: {torch.cuda.max_memory_allocated()/1024**3:.2f} GB")print(f"Peak reserved: {torch.cuda.max_memory_reserved()/1024**3:.2f} GB") # Visualization: https://pytorch.org/memory_viz adresinde upload memory_snapshot.pickleFull memory profiling pipeline.
9. Production Memory Budget Planning#
LLM training projesi başlamadan önce memory budget hesapla:
Formül (LLM training)#
Memory ≈ (W × 6) + (G × 2) + A + KV + Overhead W = parameters (number) 6 = bytes/param (BF16 weight + FP32 master + 8-byte AdamW state, divided) G = gradient memory (same N, 2 bytes each) A = activation memory (batch × seq × d × num_layers × ~10-20 bytes) KV = inference-only KV cache Overhead = CUDA context (1GB) + PyTorch (0.5GB) + buffers (1-3GB)
Compact:
M ≈ N × 20 bytes + AExample: 8B Llama 3, batch 8, seq 8192#
- N = 8e9 → W × 6 + G × 2 = 8e9 × 8 = 64 GB
- A ≈ 8 × 8192 × 4096 × 32 × 15 bytes ≈ 130 GB !!
Activation domine ediyor. Activation checkpointing kritik:
- A_with_checkpointing ≈ 30 GB
Total: 64 + 30 + 5 = ~100 GB. 2x H100 80GB (FSDP) gerek.
Optimization sırası (memory tight)#
- Mixed precision (BF16) — already
- Activation checkpointing
- Gradient accumulation (micro batch küçük)
- 8-bit optimizer (memory 38% azaltır)
- FSDP / ZeRO Stage 3 (parameters shard across GPUs)
- CPU offload (last resort)
Bu sırayla deneyerek 80GB GPU bile 70B model'e yetebilir.
Excel template#
| Parameter | Value |
|---|---|
| Model size (N) | 8e9 |
| Bytes/param (W+G+opt) | 20 |
| Activation factor | 15 bytes per token per layer |
| Batch | 8 |
| Sequence | 8192 |
| Layers | 32 |
| Activations | 8×8192×4096×32×15 = 130GB |
| Total before optim | 64 + 130 + 5 = 199GB |
| With checkpoint (act → 30GB) | 99GB |
| Recommended setup | 2× H100 80GB FSDP |
Modül 36 (GPU Hardware) ve 17 (Distributed) bu detayları çoğaltıyor.
10. Production Memory Profiling Checklist#
Yeni training run başlatmadan önce:
- Theoretical budget hesaplandı
- set
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True - start'ta
torch.cuda.empty_cache() - track edilir (wandb)
max_memory_allocated - Activation checkpointing transformer block'larında
- zero_grad'da
set_to_none=True - Memory snapshot ilk batch sonrası
- OOM recovery plan: micro_batch küçültme, checkpoint reload
Training sırasında:
- Memory metric'ler her N step log'la
- Beklenmeyen artış: investigate (memory leak ihtimali)
- Periodic (her 1000 step) eğer fragmentation görüyorsan
empty_cache - OOM olursa: checkpoint reload + batch yarıya düşür
- Memory snapshot OOM öncesi otomatik kaydet
Modül 18 (Mini-LLM Pretrain Atölyesi) bu pattern'leri pratik olarak uyguluyor.
11. Mini Egzersizler#
-
Memory hesabı: 13B model, BF16, AdamW, batch 4, seq 4096, 40 layer. Toplam memory?
-
Activation checkpointing trade-off: Aktivasyon 50GB → 5GB. Compute overhead ne kadar?
-
OOM diagnosis: 80GB GPU'da training, OOM 3000. step'te. Olası sebepler?
-
Fragmentation detect:50GB,
memory_allocated75GB, total free 5GB. Fragmentation problemi var mı?memory_reserved -
Gradient accumulation: GPU 32GB, model batch_size=4'te 30GB. Effective batch=16 nasıl?
Bu Derste Neler Öğrendik?#
✓ GPU memory anatomy — static (weights, optimizer) + dynamic (activations, gradients)
✓ Memory breakdown — 70B Llama 3 detayda
✓ API'leri — allocated vs reserved
✓ torch.profiler + memory snapshot — visual analysis ()
✓ Nsight Systems timeline — multi-GPU + communication analizi
✓ OOM 7 root cause pattern
✓ Activation checkpointing — memory ↔ compute trade-off
✓ Gradient accumulation — effective batch artırma
✓ Memory fragmentation —
✓ Production memory budget planning + Excel template
torch.cuda.memory_*pytorch.org/memory_vizexpandable_segments:TrueSıradaki Ders#
5.4 — CUDA Streams, Events ve NCCL Temelleri: Multi-GPU Communication
Tek GPU'dan multi-GPU'ya geçiş. Stream-level concurrency, event synchronization, NCCL collective operations (allreduce, broadcast). Distributed training'in alt katmanı.
Sık Sorulan Sorular
**Evet, recommended**. Düzenli sıralama: (1) Development sırasında her major change sonrası snapshot al, browse et. (2) Production initial deployment'ta canary run'la snapshot, peak'leri investigate et. (3) OOM olursa otomatik snapshot kaydet (error handler'da). (4) Periodic profiling (haftada bir) regression detect için. Snapshot dosyası küçük (~50MB), browser'da load hızlı. Frontier lab'lar bu pattern'i standard kullanıyor.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
İlgili İçerikler
Modül 0: Kurs Çerçevesi ve Atölye Kurulumu
LLM Engineer Kimdir? Junior'dan Staff'a Yapay Zekâ Mühendisliği Kariyer Haritası
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu
Kurs Felsefesi: Neden Bu Yol, Neden Bu Sıra — 8 Aylık Müfredatın İskeleti
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu