İçeriğe geç

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
Memory Profiling: torch.profiler, Nsight Systems, OOM Debugging — Production GPU Memory Yönetimi
📊 '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ı#

  1. GPU memory anatomy — neler olabiliyor
  2. Memory breakdown: weights, activations, gradients, optimizer
  3. torch.cuda.memory_*
    API'leri
  4. torch.profiler memory snapshot
  5. Nsight Systems timeline analizi
  6. OOM root cause patterns
  7. Activation checkpointing
  8. Gradient accumulation
  9. Memory fragmentation
  10. 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#

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
#

  • allocated: tensor'ların kullandığı bellek
  • reserved: PyTorch'un caching allocator'unun cuda'dan istediği (cache dahil)
reserved - allocated
= cached but unused. Genelde küçük problem değil, ama fragmentation göstergesi olabilir.

Pratik 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.pickle
'ı yükle → görsel timeline: hangi tensor ne zaman alındı, ne kadar memory.

Visualization#

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-rep
dosyası → Nsight Systems GUI ile aç.

Görselleştirilenler#

  1. CUDA kernel timeline: hangi kernel ne zaman, ne kadar süre
  2. CPU activity: Python overhead, data loading
  3. Memcpy ops: CPU↔GPU transfers
  4. NCCL collective ops: distributed training'de
  5. GPU utilization: % busy
  6. Power consumption

Tipik findings#

Pattern 1: Idle GPU

Timeline'da GPU bazen %0 busy. Sebep: CPU bottleneck (data loading slow, Python sync). Çözüm:
num_workers
artır, pinned memory.

Pattern 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 memory
hatası — yaygın senaryolar:

Pattern 1: Batch size too large#

Naive: batch_size çok yüksek → forward'da activations memory'e sığmıyor. Tespit:
max_memory_allocated
track et, batch küçük → büyük denemeleri. Çözüm:
micro_batch_size
küçük + gradient accumulation (aşağıda).

Pattern 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()
sonrası gradient'ler bellekte kalıyor. Sonraki adıma kadar. Tespit: between training steps memory yüksek kalıyor. Çözüm:
optimizer.step()
sonrası
optimizer.zero_grad(set_to_none=True)
(memory free).

Pattern 5: Multiple model copies#

Validation veya ensemble'da ikinci model copy → 2x memory. Tespit: validation sırasında OOM. Çözüm: validation
with torch.no_grad():
, model swap (cpu↔gpu).

Pattern 6: Memory fragmentation#

Total free > requested size ama allocator allocate edemiyor. Tespit:
memory_allocated
< limit ama OOM. Çözüm:
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
(2.1+).

Pattern 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?#

Modern 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
  • garbage_collection_threshold
    : %80'i dolduğunda GC tetikle
  • max_split_size_mb
    : 512MB'tan büyük block'lar split edilemez (fragmentation azalır)

Pratik öneri#

LLM training'de:
  1. expandable_segments:True
    always
  2. max_split_size_mb:512
    yardımcı
  3. empty_cache
    her N step (sık değil)
  4. Memory snapshot ile fragmentation görsel kontrol
python
import torch
from 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 peak
torch.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 profiling
x = 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 users
print(prof.key_averages().table(
sort_by="self_cuda_memory_usage", row_limit=15
))
 
# Save snapshot for visualization
torch.cuda.memory._dump_snapshot("memory_snapshot.pickle")
torch.cuda.memory._record_memory_history(enabled=None)
 
# Manual reporting
print(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.pickle
Full 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 + A

Example: 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)#

  1. Mixed precision (BF16) — already
  2. Activation checkpointing
  3. Gradient accumulation (micro batch küçük)
  4. 8-bit optimizer (memory 38% azaltır)
  5. FSDP / ZeRO Stage 3 (parameters shard across GPUs)
  6. CPU offload (last resort)
Bu sırayla deneyerek 80GB GPU bile 70B model'e yetebilir.

Excel template#

ParameterValue
Model size (N)8e9
Bytes/param (W+G+opt)20
Activation factor15 bytes per token per layer
Batch8
Sequence8192
Layers32
Activations8×8192×4096×32×15 = 130GB
Total before optim64 + 130 + 5 = 199GB
With checkpoint (act → 30GB)99GB
Recommended setup2× 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ı
  • PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
    set
  • torch.cuda.empty_cache()
    start'ta
  • max_memory_allocated
    track edilir (wandb)
  • Activation checkpointing transformer block'larında
  • set_to_none=True
    zero_grad'da
  • 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
    empty_cache
    (her 1000 step) eğer fragmentation görüyorsan
  • 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#

  1. Memory hesabı: 13B model, BF16, AdamW, batch 4, seq 4096, 40 layer. Toplam memory?
  2. Activation checkpointing trade-off: Aktivasyon 50GB → 5GB. Compute overhead ne kadar?
  3. OOM diagnosis: 80GB GPU'da training, OOM 3000. step'te. Olası sebepler?
  4. Fragmentation detect:
    memory_allocated
    50GB,
    memory_reserved
    75GB, total free 5GB. Fragmentation problemi var mı?
  5. 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 ✓
torch.cuda.memory_*
API'leri
— allocated vs reserved ✓ torch.profiler + memory snapshot — visual analysis (
pytorch.org/memory_viz
) ✓ Nsight Systems timeline — multi-GPU + communication analizi ✓ OOM 7 root cause patternActivation checkpointing — memory ↔ compute trade-off ✓ Gradient accumulation — effective batch artırma ✓ Memory fragmentation
expandable_segments:True
Production memory budget planning + Excel template

Sı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