

8 adet NVIDIA A100 80GB GPU barındıran bir node üzerinde Llama-3 70B modelini fine-tune ederken, eğitim sürecinin 3450. adımında aniden karşılaşılan CUDA Out of Memory (OOM) hatası, dağıtık sistem mimarisinin en karanlık noktalarından biridir. Logları incelediğinizde GPU’ların 76GB VRAM kullandığını, ancak PyTorch’un sadece 128MB’lık bir bellek bloğu tahsis edemediği için tüm torch.distributed process grubunu çökerttiğini görürsünüz. Bu senaryo, bir kapasite eksikliğinden ziyade, bellek parçalanması (memory fragmentation) ve FSDP (Fully Sharded Data Parallel) senkronizasyon bariyerlerinin yanlış konfigürasyonundan kaynaklanır.
PyTorch 2.2 sürümü ile birlikte QLoRA (Quantized Low-Rank Adaptation) ve FSDP entegrasyonu stabil hale geldi. Ancak 4-bit NormalFloat (NF4) taban ağırlıklarının FSDP ile shard edilmesi, ileri-geri yayılım (forward-backward pass) sırasında aktivasyon belleklerinde öngörülemeyen sıçramalara neden oluyor. Bu mimaride OOM hatalarını izole etmek, sadece batch boyutunu düşürmekle çözülmez; FSDP wrapping kurallarını, NCCL iletişim maliyetlerini ve PyTorch CUDA bellek tahsis edicisinin (allocator) davranışlarını mikroskobik düzeyde analiz etmeyi gerektirir.
Aşağıda, 70 milyar parametre üstü modelleri cluster ortamında eğitirken karşılaştığımız bellek darboğazlarının anatomisini, PyTorch 2.2’nin getirdiği API değişikliklerini ve production ortamında OOM hatalarını sıfıra indiren izole etme stratejilerini inceleyeceğiz.
Geleneksel FSDP, modelin ağırlıklarını, gradyanlarını ve optimizasyon durumlarını (optimizer states) rank’ler (GPU’lar) arasında böler. PyTorch 2.2 öncesinde, bitsandbytes kütüphanesi tarafından sağlanan 4-bit Linear katmanlarını FSDP ile doğrudan shard etmek mümkün değildi, çünkü FSDP, uint8 veya özel veri tiplerine sahip indislenmiş tensörleri bölüştürürken pointer hataları üretiyordu. PyTorch 2.2’de use_orig_params=True parametresi PEFT modelleriyle tam uyumlu hale getirildi ve QLoRA katmanlarının FSDP FULL_SHARD stratejisi altında çalışmasına olanak tanındı.
Bu mimaride, taban model (base model) ağırlıkları 4-bit NF4 formatında bellekte tutulurken, sadece LoRA adaptörleri (A ve B matrisleri) bfloat16 formatında tutulur ve eğitilir. Sistem her forward pass öncesi 4-bit ağırlıkları geçici olarak 16-bit’e dequantize eder (all-gather işlemi ile toplanan shard’lar üzerinde). Bu işlem, VRAM tüketimini 70B model için 140GB seviyelerinden 42.4GB seviyelerine çeker ancak hesaplama maliyetini artırır.
FSDP’nin bellek taşması yaratmadan çalışması için, özellikle Transformer bloklarında her bir decoder katmanının ayrı bir FSDP birimi olarak sarmalanması şarttır. Aksi takdirde, modelin tamamı tek bir FSDP bloğu olarak algılanır ve bellek tüketimi DDP (Distributed Data Parallel) ile aynı seviyeye çıkarak anında OOM ile sonuçlanır.
import torch
import functools
from torch.distributed.fsdp import (
FullyShardedDataParallel as FSDP,
MixedPrecision,
BackwardPrefetch,
ShardingStrategy,
)
from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy
from transformers.models.llama.modeling_llama import LlamaDecoderLayer
# FSDP Mixed Precision konfigürasyonu
mp_policy = MixedPrecision(
param_dtype=torch.bfloat16,
reduce_dtype=torch.bfloat16,
buffer_dtype=torch.bfloat16,
)
# Sadece Decoder katmanlarını sarmalama kuralı
llama_auto_wrap_policy = functools.partial(
transformer_auto_wrap_policy,
transformer_layer_cls={LlamaDecoderLayer},
)
# Modeli FSDP ile sarmalama
fsdp_model = FSDP(
peft_model, # get_peft_model ile oluşturulmuş QLoRA modeli
auto_wrap_policy=llama_auto_wrap_policy,
mixed_precision=mp_policy,
sharding_strategy=ShardingStrategy.FULL_SHARD,
backward_prefetch=BackwardPrefetch.BACKWARD_PRE,
use_orig_params=True,
device_id=torch.cuda.current_device(),
limit_all_gathers=True # OOM engellemek için kritik parametre
)
Buradaki limit_all_gathers=True parametresi, CPU thread’inin GPU’dan senkronizasyon kopukluğu yaşayarak gereksiz all-gather isteklerini VRAM’e yığmasını engeller. 64 GPU’lu bir cluster mimarisinde bu parametrenin aktif edilmesi, backward pass sırasındaki tepe noktası (peak) bellek tüketimini GPU başına ortalama 4.2GB düşürür.
Yakın zamanda 8x H100 80GB üzerinde Mixtral 8x7B MoE (Mixture of Experts) modelini QLoRA ile fine-tune ederken, eğitim istikrarlı şekilde ilerleyip tam %15 seviyesine geldiğinde Rank 3 ve Rank 7 GPU’larında düzenli olarak OOM meydana geliyordu. Hatayı izole etmek için torch.cuda.memory._dump_snapshot() API’sini kullandık.
Snapshot analizinde şu gerçek ortaya çıktı: VRAM’in %34’ü aktivasyon tensörleri tarafından tutuluyor, %28’i ağırlıklara tahsis edilmişti. Geri kalan 30.4GB’lık alan boş olmasına rağmen %82 oranında parçalanmış (fragmented) durumdaydı. FSDP, backward pass sırasında önceki katmanın gradyanlarını serbest bırakmadan önce, bir sonraki katmanın ağırlıklarını all_gather ile çağırıyor ve contiguous (bitişik) bir bellek bloğuna ihtiyaç duyuyordu. Parçalanmış VRAM tablosu, PyTorch’un 8GB’lık tek parça bir tensör yaratmasına izin vermediği için süreç çöküyordu.
Bu tür parçalanma kaynaklı OOM hatalarını izole edip çözmek için üç seviyeli bir yaklaşım uyguluyoruz:
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True atanır. Bu ayar, allocator’ın VRAM sayfalarını işletim sistemi seviyesinde de-commit etmesine olanak tanıyarak bellek parçalanmasını %21 oranında azaltır.apply_activation_checkpointing fonksiyonunu FSDP wrapper’ından sonra uygulamak zorunludur. Aksi senaryoda, yeniden hesaplanan aktivasyonlar FSDP bloğunun dışında kalarak VRAM’de asılı (dangling) tensörler oluşturur.BackwardPrefetch.BACKWARD_PRE, TFLOPS oranını artırır ancak bellek sınırlarını zorlar. Eğer bellek snapshot’ında OOM tam olarak prefetch aşamasında görünüyorsa, bu parametreyi BackwardPrefetch.BACKWARD_POST olarak değiştirmek veya None ile tamamen kapatmak, throughput’ta %8.4 düşüşe sebep olurken hatayı tamamen ortadan kaldırır.Production sistemlerinde dağıtım mimarisi kararları, bellek tüketimi (Memory) ve işlem hacmi (Throughput – Token/Saniye) arasındaki bir trade-off denklemidir. Aşağıdaki tablo, 8x A100 80GB (400GB/s NVLink) altyapısında Llama-3 70B modeli için farklı yaklaşımların ölçümlerini göstermektedir.
| Dağıtım Stratejisi & Optimizasyon | GPU Başına Peak VRAM (GB) | Eğitim Hızı (Token/Saniye) | İletişim Yükü (Ağ Kullanımı) |
|---|---|---|---|
| DDP + 8-bit LoRA | OOM (Tek GPU’ya sığmaz) | – | – |
| DeepSpeed Zero-3 + 16-bit LoRA | 78.4 (Sınırda) | 1,420 | %85 NCCL Bant Genişliği |
| FSDP (FULL_SHARD) + 4-bit QLoRA | 52.1 | 1,150 | %45 NCCL Bant Genişliği |
| FSDP (SHARD_GRAD_OP) + 4-bit QLoRA | 68.3 | 1,310 | %25 NCCL Bant Genişliği |
Karar Matrisi: FULL_SHARD kullanımı GPU belleğinde 16.2GB ek alan yaratırken, eğitim hızında SHARD_GRAD_OP stratejisine kıyasla %12.2 civarında bir performans kaybı yaratır. Bunun nedeni, FULL_SHARD konfigürasyonunda ağırlıkların ileri ve geri yayılım adımlarında ağ üzerinden sürekli olarak GPU’lar arasında taşınma (all-gather) zorunluluğudur.
Eğer modeliniz VRAM’in %75’inden azını tüketiyorsa (örneğin 80GB limitli kartta 50GB civarı), SHARD_GRAD_OP kullanmak ağ iletişimini sadece gradyan senkronizasyonuna (reduce-scatter) indirger ve doğrudan işlem hacmini artırır. Ancak context length’i 8192 token ve üzerine çıkardığınız senaryolarda aktivasyon bellekleri doğrusal olmayan şekilde büyüyeceğinden FULL_SHARD kullanmak mutlak bir gerekliliğe dönüşür.
Teoride hatasız çalışan bir FSDP-QLoRA mimarisinin, 7/24 operasyonel bir production cluster’ında çökmeden ilerlemesi için aşağıdaki kontrol listesi eksiksiz uygulanmalıdır.
torch.save kullanmak ana process üzerinde System Memory (CPU RAM) OOM hatasına sebep olur. 70B bir modelin state_dict yapısı tek node’da toplanamayacak kadar büyüktür. PyTorch 2.2’nin Distributed Checkpoint (DCP) API’sini kullanarak paralel kayıt mekanizması kurgulanmalıdır: FSDP.set_state_dict_type(fsdp_model, StateDictType.SHARDED_STATE_DICT).TORCH_NCCL_ASYNC_ERROR_HANDLING=1 ve NCCL_TIMEOUT=7200 (2 saat) ortam değişkenleri eklenmelidir.nvidia-smi üzerinden VRAM kapasitesini izlemek yanıltıcıdır. Prometheus & Grafana stack’i üzerinden NVIDIA DCGM exporter kullanılarak DCGM_FI_DEV_FB_USED ve özellikle DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL metrikleri milisaniye bazında takip edilmelidir. NVLink darboğazı, FSDP senkronizasyon sürelerini uzatarak tensörlerin VRAM’de gereğinden fazla kalmasına ve dolaylı OOM’lere neden olur.Mimari Not: Production ortamlarında “Eğitim başladı, ilk step’te OOM yok, mimari sağlamdır” yanılgısına sıklıkla düşülür. Optimizatör (örneğin AdamW) iterasyonları ilerledikçe ve
gradient_accumulation_stepsdöngüsü tamamlanıp asıl ağırlık güncellemesi yapıldığında, momentum tensörlerinin devreye girmesi %15-18 arasında ani bir VRAM sıçramasına yol açar. Gerçek stabilite onayı ilk 10 optimizer adımından sonra verilir.
FSDP ortamında logger mekanizmalarının (W&B, MLflow veya TensorBoard) her adımda gradyan normlarını ana process’e (Rank 0) çekmeye çalışması bu sorunun temel kaynağıdır. Loglanan tensörler CPU tarafında çöp toplayıcı (Garbage Collector) tarafından zamanında temizlenmez. Çözüm olarak gradyan norm hesaplamalarını torch.no_grad() bloğu içerisinde yapın ve GPU’dan CPU’ya veri kopyalarken mutlak suretle .item() çağrısı kullanarak sadece skaler değerleri aktarın.
PyTorch 2.2’de FSDP sarmalaması öncesi, taban modelin katmanlarına requires_grad = False atanmalıdır. PEFT’nin get_peft_model fonksiyonu bunu LoRA adaptörleri dışında tüm parametreler için otomatik olarak uygular. use_orig_params=True kullanıldığında FSDP bu dondurulmuş parametreleri shard eder ancak optimizer state tahsis etmez ve gradyan hesaplamaz. Bellek tasarrufunun kaynağı da bu gradyan hesaplama (autograd) grafiğinin dışında kalmalarıdır.
Dequantization yükünü izole etmek için bitsandbytes konfigürasyonunda bnb_4bit_use_double_quant=True (ikincil kuantizasyon) ve bnb_4bit_compute_dtype=torch.bfloat16 ayarlarını eşzamanlı kullanmak gerekir. Ek olarak, PyTorch 2.2’nin yerleşik Scaled Dot Product Attention (SDPA) fonksiyonu ile Flash Attention 2 entegrasyonu sağlandığında, bellek okuma/yazma (Memory Bound) işlemleri ciddi oranda azalır ve dequantization maliyeti TFLOPS bazında maskelenmiş olur.
Eğer modelin toplam parametre boyutu GPU sayısına bölündüğünde VRAM limitlerinin altında kalıyorsa, sorun tekil katman (layer) boyutundadır. FSDP, hesaplama esnasında en azından bir tam Transformer katmanının birleştirilmiş (de-shard) versiyonunu bellekte tutmak zorundadır. Llama-3 70B’nin tek bir katman aktivasyonu ve ağırlıkları mevcut GPU’nuzun (örneğin RTX 4090 24GB) sınırlarını aşıyorsa, FSDP tek başına yeterli olmaz; Tensor Parallelism (TP) veya Pipeline Parallelism (PP) mimarilerine geçiş yapmanız gerekir.
PyTorch 2.2 ekosisteminde FSDP ve QLoRA entegrasyonu, milyar parametreli LLM’lerin dağıtık eğitimini yüzlerce GPU’luk süper bilgisayarlardan çıkarıp, 8-16 GPU’luk lokal cluster yapılarına indirgeyen dönüm noktasıdır. Ancak bu mimari, donanım seviyesindeki NVLink bant genişliğinden, işletim sistemi seviyesindeki bellek tahsis algoritmalarına kadar uzanan hatasız bir konfigürasyon dizilimi gerektirir. OOM hatalarını salt bir bellek eksikliği olarak sınıflandırmak yerine, FSDP bariyerlerindeki senkronizasyon asimetrisi ve VRAM parçalanması perspektifiyle ele almak, kalıcı çözümün tek yoludur.
Bir sonraki fine-tuning pipeline’ınızı production’a almadan önce; PYTORCH_CUDA_ALLOC_CONF ortam değişkenlerini revize edin, Transformer wrapping stratejisini otomasyona bırakmak yerine manuel hiyerarşi ile tanımlayın ve DCP (Distributed Checkpoint) kayıt mekanizmasını ilk epoch tamamlanmadan önce simüle edin. Doğru izolasyon ve metrik takibi uygulandığında, altyapınızın hesaplama kapasitesini (TFLOPS) sınır noktasına kadar zorlayıp kesintisiz bir eğitim süreci elde edebilirsiniz.

Duyarlı/Responsive tasarım nedir? İlk olarak, responsive tasarımın ve nasıl çalıştığının hızlı bir açıklamasını yapalım. Esasen, responsive tasarım, bir web sitesini, içeriğini ve öğelerini görüntülendiği ekran…