mirror of https://github.com/razor-ai/soup.git
207 lines
6.5 KiB
Python
207 lines
6.5 KiB
Python
"""soup profile — estimate memory, speed, and GPU requirements before training."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import typer
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.table import Table
|
|
|
|
from soup_cli.utils.gpu import model_size_from_name
|
|
from soup_cli.utils.profiler import (
|
|
GPU_MEMORY,
|
|
estimate_speed,
|
|
estimate_total,
|
|
recommend_batch_size,
|
|
recommend_gpu,
|
|
)
|
|
|
|
console = Console()
|
|
|
|
|
|
def profile(
|
|
config: str = typer.Option(
|
|
"soup.yaml", "--config", "-c", help="Path to soup.yaml config file"
|
|
),
|
|
gpu: str = typer.Option(
|
|
None, "--gpu", "-g",
|
|
help=(
|
|
"Target GPU for recommendations "
|
|
"(e.g., rtx3090, rtx4090, a100, h100). Auto-detects if not set."
|
|
),
|
|
),
|
|
json_output: bool = typer.Option(
|
|
False, "--json", help="Output as JSON for scripting"
|
|
),
|
|
):
|
|
"""Estimate memory, speed, and GPU requirements BEFORE training."""
|
|
from soup_cli.config.loader import load_config
|
|
|
|
config_path = Path(config)
|
|
if not config_path.exists():
|
|
console.print(f"[red]Config file not found:[/] {config}")
|
|
raise typer.Exit(1)
|
|
|
|
cfg = load_config(config_path)
|
|
|
|
# Determine model size
|
|
model_params_b = model_size_from_name(cfg.base)
|
|
|
|
# Determine batch size (use 4 as default estimate for "auto")
|
|
batch_size = cfg.training.batch_size
|
|
if batch_size == "auto":
|
|
batch_size = 4
|
|
else:
|
|
batch_size = int(batch_size)
|
|
|
|
# Resolve GPU memory
|
|
gpu_memory_gb = _resolve_gpu_memory(gpu)
|
|
|
|
# Compute profile
|
|
result = estimate_total(
|
|
model_name=cfg.base,
|
|
model_params_b=model_params_b,
|
|
quantization=cfg.training.quantization,
|
|
lora_r=cfg.training.lora.r,
|
|
lora_alpha=cfg.training.lora.alpha,
|
|
batch_size=batch_size,
|
|
seq_len=cfg.data.max_length,
|
|
optimizer=cfg.training.optimizer,
|
|
gradient_checkpointing=cfg.training.gradient_checkpointing,
|
|
)
|
|
|
|
# Speed estimates
|
|
tokens_per_sec = estimate_speed(
|
|
model_params_b, cfg.training.quantization, batch_size
|
|
)
|
|
samples_per_sec = tokens_per_sec / max(cfg.data.max_length, 1)
|
|
|
|
# Batch size recommendation
|
|
recommended_bs = recommend_batch_size(result["total_memory_gb"], gpu_memory_gb)
|
|
|
|
# GPU recommendations
|
|
compatible_gpus = recommend_gpu(result["total_memory_gb"])
|
|
|
|
# Add speed/time to result
|
|
result["tokens_per_sec"] = round(tokens_per_sec, 1)
|
|
result["samples_per_sec"] = round(samples_per_sec, 2)
|
|
result["recommended_batch_size"] = recommended_bs
|
|
result["compatible_gpus"] = compatible_gpus
|
|
result["gpu_memory_gb"] = gpu_memory_gb
|
|
|
|
if json_output:
|
|
console.print(json.dumps(result, indent=2))
|
|
return
|
|
|
|
_render_profile(result, cfg, gpu_memory_gb)
|
|
|
|
|
|
def _resolve_gpu_memory(gpu: str | None) -> float:
|
|
"""Resolve GPU memory in GB from flag or auto-detection."""
|
|
if gpu is not None:
|
|
gpu_key = gpu.lower().replace(" ", "").replace("-", "")
|
|
if gpu_key not in GPU_MEMORY:
|
|
valid = ", ".join(sorted(GPU_MEMORY.keys()))
|
|
console.print(
|
|
f"[red]Unknown GPU:[/] {gpu}\n"
|
|
f"[dim]Valid options: {valid}[/]"
|
|
)
|
|
raise typer.Exit(1)
|
|
return float(GPU_MEMORY[gpu_key])
|
|
|
|
# Auto-detect
|
|
try:
|
|
from soup_cli.utils.gpu import get_gpu_info
|
|
|
|
info = get_gpu_info()
|
|
mem_bytes = info.get("memory_total_bytes", 0)
|
|
if mem_bytes > 0:
|
|
return mem_bytes / (1024**3)
|
|
except (ImportError, RuntimeError, OSError):
|
|
pass
|
|
|
|
# Default to 24 GB (common consumer GPU)
|
|
return 24.0
|
|
|
|
|
|
def _render_profile(result: dict, cfg, gpu_memory_gb: float) -> None:
|
|
"""Render Rich profile output."""
|
|
# Model info
|
|
model_info = (
|
|
f"Model: [bold]{cfg.base}[/]\n"
|
|
f"Params: [bold]{result['model_params_b']:.1f}B[/] "
|
|
f"(trainable: {result['trainable_params']:,.0f} with LoRA r={cfg.training.lora.r})\n"
|
|
f"Quantization: [bold]{result['quantization']}[/]"
|
|
)
|
|
if result["gradient_checkpointing"]:
|
|
model_info += "\nGradient checkpointing: [bold green]enabled[/]"
|
|
|
|
# Memory breakdown table
|
|
mem_table = Table(show_header=False, box=None, padding=(0, 2))
|
|
mem_table.add_column("Component", style="bold")
|
|
mem_table.add_column("Memory", justify="right")
|
|
mem_table.add_row("Model", f"~{result['model_memory_gb']:.1f} GB")
|
|
mem_table.add_row("LoRA", f"~{result['lora_memory_gb']:.1f} GB")
|
|
mem_table.add_row("Optimizer", f"~{result['optimizer_memory_gb']:.1f} GB")
|
|
mem_table.add_row(
|
|
f"Activations (bs={result['batch_size']}, seq={result['seq_len']})",
|
|
f"~{result['activation_memory_gb']:.1f} GB",
|
|
)
|
|
mem_table.add_row("Overhead", f"~{result['overhead_gb']:.1f} GB")
|
|
mem_table.add_row("-" * 20, "-" * 10)
|
|
mem_table.add_row("[bold]Total[/]", f"[bold]~{result['total_memory_gb']:.1f} GB[/]")
|
|
|
|
# Speed info
|
|
speed_info = (
|
|
f"Tokens/sec: ~{result['tokens_per_sec']:,.0f}\n"
|
|
f"Samples/sec: ~{result['samples_per_sec']:.1f}"
|
|
)
|
|
|
|
# Recommendations
|
|
recs = []
|
|
fits = result["total_memory_gb"] <= gpu_memory_gb
|
|
if fits:
|
|
recs.append(
|
|
f"[green]OK[/] Fits in {gpu_memory_gb:.0f} GB VRAM"
|
|
)
|
|
else:
|
|
recs.append(
|
|
f"[red]X[/] Does NOT fit in {gpu_memory_gb:.0f} GB VRAM "
|
|
f"(need ~{result['total_memory_gb']:.0f} GB)"
|
|
)
|
|
|
|
recs.append(
|
|
f"[green]OK[/] Recommended batch_size: {result['recommended_batch_size']}"
|
|
)
|
|
|
|
if result["total_memory_gb"] > 24 and not result["gradient_checkpointing"]:
|
|
recs.append(
|
|
"[yellow]![/] Consider gradient_checkpointing: true for memory savings"
|
|
)
|
|
|
|
if result["total_memory_gb"] > 40:
|
|
recs.append(
|
|
"[yellow]![/] Consider DeepSpeed ZeRO-3 or FSDP for distributed training"
|
|
)
|
|
|
|
# Compatible GPUs (show top 5)
|
|
gpu_list = result["compatible_gpus"][:5]
|
|
|
|
console.print(Panel(model_info, title="[bold]Training Profile[/]"))
|
|
console.print()
|
|
console.print("[bold]GPU Memory Estimate:[/]")
|
|
console.print(mem_table)
|
|
console.print()
|
|
console.print(Panel(speed_info, title="Speed Estimate"))
|
|
console.print()
|
|
console.print(Panel("\n".join(recs), title="Recommendations"))
|
|
console.print()
|
|
|
|
if gpu_list:
|
|
console.print("[bold]Compatible GPUs:[/]")
|
|
for gpu_name in gpu_list:
|
|
console.print(f" - {gpu_name}")
|