mindXtrain / mindxtrain /budget /resource.py
Gregory-L's picture
fork mindXtrain from GitHub (Professor-Codephreak/mindXtrain@661bd41) as the mindX-specific line
dfb775d verified
Raw History Blame Contribute Delete
3.13 kB
"""ResourceBudget — psutil + rocm-smi-derived training-job sizing helper.
Reads host RAM, CPU, GPU memory; emits a recommended `micro_batch_size`
that fits under the budget for a given `seq_len`. psutil + rocm-smi are
both lazy / optional — falls back to conservative defaults if neither is
available.
"""
from __future__ import annotations
import json
import shutil
import subprocess
from typing import cast
from pydantic import BaseModel, ConfigDict, Field
class ResourceBudget(BaseModel):
model_config = ConfigDict(extra="forbid")
host_ram_gb: float = Field(ge=0.0)
cpu_cores: int = Field(ge=1)
gpu_count: int = Field(ge=0)
gpu_mem_gb_each: float = Field(ge=0.0)
def _host_ram_gb() -> float:
try:
import psutil
return float(psutil.virtual_memory().total) / (1024**3)
except ImportError:
return 0.0
def _cpu_cores() -> int:
try:
import psutil
return int(psutil.cpu_count(logical=True) or 1)
except ImportError:
import os
return os.cpu_count() or 1
def _gpu_mem_gb() -> tuple[int, float]:
"""Return (gpu_count, gpu_mem_gb_each)."""
if shutil.which("rocm-smi") is None:
return (0, 0.0)
try:
out = subprocess.run(
["rocm-smi", "--showmeminfo", "vram", "--json"],
capture_output=True,
text=True,
timeout=10.0,
check=False,
)
except (subprocess.TimeoutExpired, OSError):
return (0, 0.0)
if out.returncode != 0:
return (0, 0.0)
try:
data = json.loads(out.stdout)
except json.JSONDecodeError:
return (0, 0.0)
cards = [k for k in data if k.startswith("card")]
if not cards:
return (0, 0.0)
total_bytes = 0.0
for c in cards:
for k, v in (data[c] or {}).items():
if "total" in k.lower():
try:
total_bytes = max(total_bytes, float(v))
break
except ValueError:
continue
if total_bytes == 0.0:
return (len(cards), 0.0)
return (len(cards), total_bytes / (1024**3))
def detect() -> ResourceBudget:
"""Return a `ResourceBudget` snapshot of the current host."""
gpu_count, gpu_mem = _gpu_mem_gb()
return ResourceBudget(
host_ram_gb=_host_ram_gb(),
cpu_cores=_cpu_cores(),
gpu_count=gpu_count,
gpu_mem_gb_each=gpu_mem,
)
def recommend_micro_batch(budget: ResourceBudget, seq_len: int, *, dtype_bytes: int = 2) -> int:
"""Return a conservative `micro_batch_size` that fits under the budget.
Heuristic: each token consumes ~`dtype_bytes` bytes for activations + a
factor for KV cache + gradients. We leave 30% headroom.
"""
if budget.gpu_mem_gb_each <= 0:
return 1
bytes_per_token = dtype_bytes * 16 # rough activations + grads + KV factor
available = budget.gpu_mem_gb_each * 0.7 * (1024**3)
bs = int(available / (seq_len * bytes_per_token))
return cast(int, max(1, bs))
__all__ = ["ResourceBudget", "detect", "recommend_micro_batch"]