The Anatomy of GPU Memory Budgeting: W + G + O + A + B — Managing the 24GB on RTX 4090 at the Atom Level
The most common phrase in fine-tuning: 'OOM'. This lesson ends random OOMs forever. Break down the Weights/Grads/Optimizer/Activations/Buffers budget; understand mathematically why AdamW needs 8 bytes/param, Lion 4, and NF4 fits at 0.5. Fit Llama 3.1 8B into 24GB with 4 different methods.
Şükrü Yusuf KAYA
42 min read
Advanced🎯 Ders sonunda
Bir model + hyperparam kombinasyonu önünde durunca 'sığar mı?' sorusunu kâğıt-kalem ile cevaplayabileceksin. `nvidia-smi`'ya bakmadan tahmin edeceksin, gerçek peak ile %10 içinde tutturacaksın. OOM yediğinde niye yediğini, hangi terimi azaltacağını saniyede bulacaksın.
1. Bütçe Formülü#
mem_total = W + G + O + A + B
| Sembol | Ne | Birim |
|---|---|---|
| W | Model weights (parameters in GPU memory) | bytes |
| G | Gradient tensörleri (training-only) | bytes |
| O | Optimizer state (AdamW: m + v; Lion: m) | bytes |
| A | Activation memory (forward'da biriken intermediates) | bytes |
| B | Buffers + workspace + fragmentation (~2-4 GB sabit) | bytes |
W ve G kolay — parametre sayısı × bytes/param. Zor olan O ve A — bunlar mühendislik kararı.
2. W — Weights Memory#
W = N_params × bytes_per_param| Precision | bytes/param | Llama 3.1 8B için W |
|---|---|---|
| fp32 | 4 | 32.0 GB |
| fp16 / bf16 | 2 | 16.0 GB |
| fp8 (e4m3) | 1 | 8.0 GB |
| int8 (LLM.int8) | 1 | 8.0 GB |
| nf4 / int4 (QLoRA) | 0.5 | 4.0 GB ✅ |
| 2-bit (Q2_K) | 0.25 | 2.0 GB (inf-only) |
Llama 3.1 8B'nin gerçek parametre sayısı: 8.03B (8,030,261,248). Tablo değerleri ufak farkla yukarı yuvarlanır.
Cookbook'ta kural: fine-tune sırasında bf16 ya da NF4, fp16'dan kaçın (loss scaling complexity + RTX 4090 native bf16 destekler). FP8 training cookbook'ta Part X'te (advanced).
3. G — Gradient Memory#
G = N_trainable_params × bytes_per_grad- Full FT: N_trainable = N_params → G ≈ W.
- LoRA / QLoRA: N_trainable = sadece LoRA matrisleri (A ve B) → (bf16) → milyarın binde-yüzde-biri kadar.
G ≈ trainable × 2 bytes
Llama 3.1 8B QLoRA, rank=32, target=q/k/v/o/gate/up/down:
LoRA params (per layer) = 32 × 2 matrices × (h + h) bytes = 32 × 2 × 4096 × 7 module × 32 layers ≈ 58.7 M params G ≈ 58.7M × 2 = 117 MB
İlginç: full FT'de G ≈ 16 GB, QLoRA'da 0.12 GB — yani G %99.3 azalıyor.
4. O — Optimizer State (En Büyük Sürpriz Kaynağı)#
| Optimizer | State per param | Llama 3.1 8B full FT | Niye |
|---|---|---|---|
| SGD (no momentum) | 0 bytes | 0 GB | sadece grad var |
| SGD + momentum | 4 bytes (fp32 m) | 32 GB | velocity tensor |
| AdamW (fp32) | 8 bytes (fp32 m + fp32 v) | 64 GB ⚠️ | mü ve varyans |
| AdamW (bf16 master) | 8 bytes (bf16 m + bf16 v + fp32 master) | ~48 GB | mixed precision |
| 8-bit AdamW (bnb) | ~2 bytes (8-bit m + 8-bit v + percentile quant) | 16 GB | block-wise quant |
| Lion | 4 bytes (fp32 m) | 32 GB | sadece momentum |
| Adafactor | ~2-4 bytes (factorized v) | 16-32 GB | row+col stats |
| Paged AdamW (bnb) | 8 bytes ama CPU RAM'e taşır | ~8 GB peak | CPU offload |
Cookbook'un kuralı (RTX 4090 üzerinde):
- LoRA / QLoRA → 8-bit AdamW (bitsandbytes )
paged_adamw_8bit - Full FT (<3B model) → 8-bit AdamW veya Lion (Lion özellikle stable)
- DPO / R1-style RL → AdamW bf16 (8-bit instability gözlemleniyor bazen)
Sayı örneği — Llama 3.1 8B QLoRA (rank=32) + 8-bit AdamW:
O = 58.7M × 8 / 4 (8-bit avg) ≈ 117 MB
Karşılaştır: full FT bf16 AdamW → 48 GB. 400x daha az.
5. A — Activation Memory (En Yanıltıcı Terim)#
Forward pass'te her layer'ın çıktısı backward'a kadar saklanır. Bu birikim activation memory.
Naïve formül (gradient checkpointing kapalı):#
A_naive ≈ batch × seq_len × layers × hidden × bytes × multiplier
multiplier- Plain transformer: ~10-14 (attn intermediates + ffn intermediates + norms)
- FlashAttention ile attn intermediates kaybolur → ~8-10
Llama 3.1 8B (32 layer, hidden=4096), batch=1, seq=4096, bf16:
A_naive ≈ 1 × 4096 × 32 × 4096 × 2 × 10 ≈ 10.7 GB
Gradient checkpointing açık:#
A_ckpt ≈ A_naive / sqrt(layers) × c
cAynı modelde:
A_ckpt ≈ 10.7 / sqrt(32) × 2.5 ≈ 4.7 GB
Maliyeti: Backward'da ~%33 daha fazla compute (forward'ın bazı kısımları yeniden hesaplanır). RTX 4090'da pratik throughput cezası %20-30.
6. B — Buffers + Workspace + Fragmentation#
- cuBLAS workspace: ~256-512 MB
- cuDNN workspace: ~256-512 MB
- NCCL buffers (multi-GPU): 0.5-2 GB
- PyTorch caching allocator fragmentation: %5-15 of allocated
- Triton autotune cache (Unsloth/Flash): ~100-200 MB
Toplam: ~2-4 GB baseline. Cookbook tablolarında varsayılır.
B = 3 GB7. Vaka — Llama 3.1 8B'yi RTX 4090'a 4 Ayrı Yolla Sığdır#
| Yol | W | G | O | A | B | Total | Sığar? |
|---|---|---|---|---|---|---|---|
| (a) Full FT bf16 + AdamW | 16 | 16 | 48 | 4.3 | 3 | 87 GB | ❌ |
| (b) Full FT bf16 + 8-bit AdamW + grad-ckpt | 16 | 16 | 16 | 4.3 | 3 | 55 GB | ❌ |
| (c) LoRA r=32 bf16 + 8-bit AdamW + grad-ckpt | 16 | 0.12 | 0.12 | 5.2 | 3 | 24.4 GB | ⚠️ marjinal |
| (d) QLoRA NF4 r=32 + 8-bit AdamW + grad-ckpt | 4 | 0.12 | 0.12 | 5.2 | 3 | 12.4 GB | ✅ |
| (d) + Unsloth optimizations | 4 | 0.12 | 0.12 | 4.4 | 2 | 10.6 GB | ✅✅ |
| (d) + seq_len=8192 (instead of 4096) | 4 | 0.12 | 0.12 | 10.4 | 3 | 17.6 GB | ✅ |
Cookbook'un Llama 3.1 8B baseline'ı (d) konfigürasyonudur: 12.4 GB peak, 11.6 GB headroom = stres-test'e dayanıklı.
(c) ve (d) arasındaki fark: NF4 ile weights'in 16'dan 4 GB'a düşmesi. Bu tek başına kompakt FT'yi mümkün kılıyor.
python
# === Bellek ölçüm helper'ı — cookbook'un her Lab'ında bulunur ===import torch def measure_memory(label: str = ""): """Mevcut GPU bellek breakdown'unu döner.""" torch.cuda.synchronize() allocated = torch.cuda.memory_allocated() / 1024**3 reserved = torch.cuda.memory_reserved() / 1024**3 peak = torch.cuda.max_memory_allocated() / 1024**3 free, total = torch.cuda.mem_get_info() free_gb = free / 1024**3 total_gb = total / 1024**3 print( f"[mem{':' + label if label else ''}] " f"alloc={allocated:6.2f} GB reserved={reserved:6.2f} GB " f"peak={peak:6.2f} GB free={free_gb:6.2f}/{total_gb:.1f} GB" ) return { "alloc_gb": allocated, "reserved_gb": reserved, "peak_gb": peak, "free_gb": free_gb, } def estimate_budget(n_params: int, precision: str = "nf4", optimizer: str = "adamw_8bit", batch: int = 1, seq_len: int = 4096, layers: int = 32, hidden: int = 4096, grad_ckpt: bool = True, trainable_fraction: float = 0.007): """ Cookbook bütçe formülü — kâğıt-kalem tahmin yapar. trainable_fraction: QLoRA r=32 için ~0.7%, full FT için 1.0. """ bpp = {"fp32": 4, "fp16": 2, "bf16": 2, "fp8": 1, "int8": 1, "nf4": 0.5}[precision] opt_mult = {"sgd": 0, "sgd_momentum": 4, "adamw": 8, "adamw_bf16": 8, "adamw_8bit": 2, "lion": 4}[optimizer] W = n_params * bpp G = n_params * trainable_fraction * 2 # grads always bf16 O = n_params * trainable_fraction * opt_mult mult = 10 A_naive = batch * seq_len * layers * hidden * 2 * mult A = A_naive / (layers**0.5) * 2.5 if grad_ckpt else A_naive B = 3 * 1024**3 total = W + G + O + A + B return { "W_gb": W / 1024**3, "G_gb": G / 1024**3, "O_gb": O / 1024**3, "A_gb": A / 1024**3, "B_gb": B / 1024**3, "total_gb": total / 1024**3, } # Llama 3.1 8B QLoRA baselinebudget = estimate_budget(n_params=8_030_000_000)print(budget)# {'W_gb': 3.74, 'G_gb': 0.10, 'O_gb': 0.10, 'A_gb': 5.21, 'B_gb': 3.00, 'total_gb': 12.15}bellek ölçüm + tahmin helper'ı
🐛 Failure Mode Drill — 'Formül 12 GB diyor ama nvidia-smi 19 GB peak gösteriyor'
Hipotezler: (a) Sequence packing on, effective batch artmış → A formülün öngördüğünden büyük. (b) HF Trainer + Trainer.evaluate çağrısı sırasında ek inference batch eval kümesinde sığmıyor → eval_batch_size=1 yap. (c) Tokenizer max_length>4096, gerçek sequence'lar daha uzun → `max_seq_length` zorla kısıtla. (d) Allocator fragmentation, `reserved`/`allocated` oranı 1.5+ → `PYTORCH_CUDA_ALLOC_CONF=expandable_segments
` ekle. Drill: nvidia-smi peak'ten 19 GB olduğunda hangi hipotez geçerli — `torch.cuda.memory_summary()` çıktısını oku.8. Bench — Aynı Model, 5 Konfigürasyon#
50 step warmup, sonra 100 step ölçüm. RTX 4090 + Llama 3.1 8B.
| Config | Peak GB | step/s | tokens/s | TR-MMLU Δ (3 epoch) |
|---|---|---|---|---|
| (c) LoRA bf16 + AdamW (memory borderline) | 23.1 | 1.32 | 5400 | +6.8 |
| (d) QLoRA NF4 r=32 | 12.4 | 1.78 | 7290 | +7.1 |
| (d) + Unsloth | 10.6 | 3.10 | 12700 | +7.0 |
| (d) + r=64 | 13.1 | 1.71 | 7000 | +7.4 |
| (d) + r=128 | 14.3 | 1.65 | 6760 | +7.5 |
Çıkarımlar:
- NF4'ün bf16'ya karşı kalite kaybı çok küçük (Δ TR-MMLU 0.1-0.3 puan).
- Unsloth, aynı output'a 2-3x daha hızlı ulaşır (Triton fused kernel'lar).
- Lora rank'i artırmak (r=128) küçük ama gerçek bir kalite artışı verir; bütçe yetiyorsa tercih edilir.
✅ Bu dersin teslimi
- `estimate_budget` helper'ını çalıştır, kendi favori model + recipe'in için tahmin yap. 2) Gerçek Lab'da peak ölç (`measure_memory("post-step")`). 3) Tahmin vs ölçüm farkın <%15 olmalı — değilse hipotez bul. 4) Sonraki ders: 1.2 — Activation Memory Anatomisi: Niye O(L·s·h)?
Frequently Asked Questions
Marjinal — 70B × 0.5 bytes = 35 GB sadece weights. 24GB'a sığmaz. **Çözümler:** (a) CPU offload (paged_adamw_8bit + bnb 4-bit cpu offload) → 4090'da kısmi inference + train, çok yavaş (~0.1 step/s); (b) **2-bit quant** weights (HQQ / AQLM) + LoRA → 4090'a sığar, kalite kaybı orta. Cookbook Part IV'te detaylı.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
Related Content
Part 0 — Engineering Foundations
Welcome to the Fine-Tuning Cookbook: System, Stage Taxonomy, and the Reproducibility Contract
Start LearningPart 0 — Engineering Foundations
Reproducibility Stack: Seeds, cuDNN Flags, and Deterministic CUDA — End the 'Works on My Machine' Problem
Start LearningPart 0 — Engineering Foundations
Environment Pinning: uv + pyproject.toml, CUDA Version Matrix, and Container Recipes
Start LearningConnected pillar topics