neuro-symbolic-coder-13M / memory_utils.py
DexopT's picture
Upload 13 files
5337457 verified
Raw
History Blame Contribute Delete
6.97 kB
# file: memory_utils.py
"""Memory management utilities for NVIDIA RTX 4050 (6GB VRAM)."""
from __future__ import annotations
import gc
import logging
from typing import Any, Callable, Dict, Tuple
import torch
import torch.nn as nn
from torch.optim import Optimizer
logger = logging.getLogger(__name__)
# ===================================================================
# MemoryManager
# ===================================================================
class MemoryManager:
"""CUDA memory management for constrained VRAM (6GB)."""
# ------------------------------------------------------------------
# cache clearing
# ------------------------------------------------------------------
@staticmethod
def clear_memory() -> None:
"""Clear CUDA cache and run garbage collection."""
if torch.cuda.is_available():
torch.cuda.empty_cache()
torch.cuda.synchronize()
gc.collect()
# ------------------------------------------------------------------
# diagnostics
# ------------------------------------------------------------------
@staticmethod
def get_memory_usage() -> Dict[str, Any]:
"""Return CUDA memory stats."""
if torch.cuda.is_available():
allocated = torch.cuda.memory_allocated() / 1024**2
reserved = torch.cuda.memory_reserved() / 1024**2
max_allocated = torch.cuda.max_memory_allocated() / 1024**2
return {
"allocated_mb": round(allocated, 1),
"reserved_mb": round(reserved, 1),
"max_allocated_mb": round(max_allocated, 1),
"device": torch.cuda.get_device_name(0),
}
return {"status": "cuda_unavailable"}
# ------------------------------------------------------------------
# gradient checkpointing
# ------------------------------------------------------------------
@staticmethod
def enable_gradient_checkpointing(model: nn.Module) -> nn.Module:
"""Model already uses checkpoint in forward — no-op here."""
return model
# ------------------------------------------------------------------
# mixed precision
# ------------------------------------------------------------------
@staticmethod
def enable_mixed_precision() -> torch.amp.autocast:
"""Return bfloat16 autocast for RTX 40-series (native bfloat16 support)."""
return torch.autocast(device_type='cuda', dtype=torch.bfloat16)
# ------------------------------------------------------------------
# model memory estimation
# ------------------------------------------------------------------
@staticmethod
def estimate_model_memory(model: nn.Module) -> float:
"""Estimate model memory in MiB (params only, bfloat16)."""
total = sum(p.numel() * 2 for p in model.parameters()) # bfloat16 = 2 bytes
total += sum(p.numel() * 2 for p in model.buffers())
return total / 1024**2
# ===================================================================
# Model optimisation helper
# ===================================================================
def optimize_for_rtx4050(model: nn.Module, _config: Any = None) -> nn.Module:
"""Apply RTX 40-series optimizations.
- torch.compile for Ada Lovelace speedup
- Log model size and VRAM estimate
- Verify forward pass fits in 6GB
Note: torch.compile won't compile on first call (requires CUDA warmup).
If it fails, falls back to eager mode gracefully.
"""
param_count = sum(p.numel() for p in model.parameters())
logger.info("Model params: %.2fM", param_count / 1e6)
mem_mb = MemoryManager.estimate_model_memory(model)
logger.info("Model memory (bf16): %.1f MiB", mem_mb)
if torch.cuda.is_available():
free, total = torch.cuda.mem_get_info()
logger.info("VRAM: %.1f GiB free / %.1f GiB total", free / 1024**3, total / 1024**3)
if mem_mb > free / 1024**2 * 0.8:
logger.warning("Model may exceed 80%% of free VRAM — consider reducing batch size")
# torch.compile handled by model's create_small_model factory
return model
def verify_cuda_compatibility(
model: nn.Module,
batch_size: int = 2,
seq_len: int = 128,
vocab_size: int = 5000,
) -> bool:
"""Run dummy forward+backward to verify CUDA compatibility and VRAM fit.
Returns True if the model fits and gradients flow correctly.
"""
if not torch.cuda.is_available():
logger.warning("CUDA not available — skipping verification")
return True
try:
model = model.cuda()
model.train()
x = torch.randint(0, vocab_size, (batch_size, seq_len), device='cuda')
with torch.autocast(device_type='cuda', dtype=torch.bfloat16):
logits = model(x)
loss = logits.mean()
loss.backward()
MemoryManager.clear_memory()
logger.info("CUDA verification passed — forward+backward OK")
return True
except torch.cuda.OutOfMemoryError:
logger.error("OOM during verification — reduce batch size or model size")
return False
except Exception as e:
logger.error("CUDA verification failed: %s", e)
return False
# ===================================================================
# Logging helper
# ===================================================================
def log_memory_usage(logger_instance: logging.Logger) -> None:
"""Log current CUDA memory usage."""
if torch.cuda.is_available():
stats = MemoryManager.get_memory_usage()
logger_instance.debug(
"VRAM: allocated=%(allocated_mb).1fMB reserved=%(reserved_mb).1fMB max=%(max_allocated_mb).1fMB",
stats,
)
# ===================================================================
# Gradient accumulation
# ===================================================================
def create_gradient_accumulator(
accumulation_steps: int,
) -> Tuple[Callable[[Optimizer], None], Callable[[int], bool]]:
"""Return (step_fn, should_step_fn) for gradient accumulation.
Usage::
step_fn, should_step = create_gradient_accumulator(4)
for i, (x, y) in enumerate(dataloader):
loss = model(x, y)
loss.backward()
if should_step(i):
step_fn(optimizer) # optimizer.step() + zero_grad()
"""
def step_fn(optimizer: Optimizer) -> None:
optimizer.step()
optimizer.zero_grad()
MemoryManager.clear_memory()
def should_step_fn(batch_idx: int) -> bool:
return (batch_idx + 1) % accumulation_steps == 0
return step_fn, should_step_fn
# ===================================================================
# Backward compat aliases
# ===================================================================
optimize_for_m2 = optimize_for_rtx4050