Files
PaintPlus/backend/app/services/gpu_detect.py
T
Claude 46b9066bba Handle old/low-VRAM GPUs and document nvidia-container-toolkit requirement
GPU tier table extended:
  ultra   ≥16 GB → SDXL (unchanged)
  high    8-16 GB → SDXL (unchanged)
  medium  4-8 GB → SD 2.x (unchanged)
  legacy  2-4 GB → SD 1.5 (~1.7 GB fp16)  ← new: GTX 970/1060/RX 580 etc.
  minimal <2 GB  → SD 1.5 + sequential CPU offload  ← new: very old/integrated GPUs

gpu_detect.py:
- Detects CUDA compute capability (CC); fp16 disabled for CC < 6.0 (pre-Pascal)
- GpuInfo gains compute_capability and warnings fields
- _make_warnings() emits human-readable warnings for low VRAM and old CC
- model tier fallback updated from 'low' to 'legacy'

local_diffusion.py:
- minimal/legacy tiers use enable_sequential_cpu_offload() + enable_attention_slicing(1)
- target resolution per tier: ultra/high=1024, medium=768, legacy/minimal=512
- .to(device) skipped when sequential CPU offload is active

gpu_status.py:
- Response now includes compute_capability and warnings

docker-compose.gpu.yml:
- Full nvidia-container-toolkit install instructions in header comment
- nvidia-docker2 (legacy) fallback documented as comment block inline
- AMD ROCm swap-in instructions added
- GPU tier table documented in header

scripts/gpu_setup.py:
- Prints compute capability, fp16 status, tier, and model selection at startup
- Prints per-tier warnings (old CC, low VRAM)

https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
2026-06-13 15:19:01 +00:00

191 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
GPU detection and capability tiering.
Detects CUDA (NVIDIA/AMD-ROCm), MPS (Apple Silicon), or CPU fallback.
Called once at startup; result is cached for the process lifetime.
"""
from __future__ import annotations
import subprocess
from dataclasses import dataclass, field
from typing import Optional
# Model IDs per VRAM tier — all publicly available on HuggingFace, no auth needed.
#
# Tier selection by VRAM:
# ultra ≥16 GB → SDXL (best quality)
# high 816 GB → SDXL
# medium 48 GB → SD 2.x
# legacy 24 GB → SD 1.5 (older / budget GPUs like GTX 970/1060/RX 580)
# minimal <2 GB → SD 1.5 with heavy memory offloading (very slow, but functional)
#
# SD 1.5 uses ~1.7 GB VRAM in fp16; SD 2.x uses ~3.5 GB; SDXL uses ~6.5 GB.
_MODEL_TIERS: dict[str, dict[str, str]] = {
"ultra": {
"inpaint": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
"txt2img": "stabilityai/stable-diffusion-xl-base-1.0",
"img2img": "stabilityai/stable-diffusion-xl-base-1.0",
"upscale": "stabilityai/stable-diffusion-x4-upscaler",
},
"high": {
"inpaint": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
"txt2img": "stabilityai/stable-diffusion-xl-base-1.0",
"img2img": "stabilityai/stable-diffusion-xl-base-1.0",
"upscale": "stabilityai/stable-diffusion-x4-upscaler",
},
"medium": {
"inpaint": "stabilityai/stable-diffusion-2-inpainting",
"txt2img": "stabilityai/stable-diffusion-2-1",
"img2img": "stabilityai/stable-diffusion-2-1",
"upscale": None,
},
# GTX 970 / GTX 1060 6 GB / RX 580 / etc. — 24 GB VRAM
"legacy": {
"inpaint": "runwayml/stable-diffusion-inpainting",
"txt2img": "stable-diffusion-v1-5/stable-diffusion-v1-5",
"img2img": "stable-diffusion-v1-5/stable-diffusion-v1-5",
"upscale": None,
},
# Very old / integrated GPUs with <2 GB — runs but slowly; warns user.
"minimal": {
"inpaint": "runwayml/stable-diffusion-inpainting",
"txt2img": "stable-diffusion-v1-5/stable-diffusion-v1-5",
"img2img": "stable-diffusion-v1-5/stable-diffusion-v1-5",
"upscale": None,
},
}
@dataclass
class GpuInfo:
backend: str # cuda | mps | cpu
device_name: str = "CPU"
vram_gb: float = 0.0
compute_capability: str = "" # e.g. "8.6" for RTX 3070
tier: str = "legacy" # ultra | high | medium | legacy | minimal
fp16: bool = False
warnings: list[str] = field(default_factory=list)
capabilities: list[str] = field(default_factory=list)
def detect_gpu() -> GpuInfo:
"""Detect available compute backend, VRAM, compute capability, and assign tier."""
try:
import torch
if torch.cuda.is_available():
props = torch.cuda.get_device_properties(0)
vram_gb = props.total_memory / (1024 ** 3)
cc = f"{props.major}.{props.minor}"
# fp16 inference is reliable on Pascal (6.0) and newer.
# Maxwell (5.x) technically works but is slower in fp16 than fp32 on some ops.
use_fp16 = props.major >= 6
tier = _vram_to_tier(vram_gb)
warnings = _make_warnings(tier, vram_gb, cc, use_fp16)
return GpuInfo(
backend="cuda",
device_name=props.name,
vram_gb=round(vram_gb, 1),
compute_capability=cc,
tier=tier,
fp16=use_fp16,
warnings=warnings,
capabilities=_caps_for_tier(tier),
)
if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
vram_gb = _apple_usable_gb()
tier = _vram_to_tier(vram_gb)
return GpuInfo(
backend="mps",
device_name="Apple Silicon",
vram_gb=round(vram_gb, 1),
compute_capability="mps",
tier=tier,
fp16=False, # MPS diffusion is more stable with fp32
capabilities=_caps_for_tier(tier),
)
except ImportError:
pass
return GpuInfo(
backend="cpu",
device_name="CPU (no GPU detected)",
vram_gb=0.0,
tier="minimal",
fp16=False,
warnings=["No GPU found — running on CPU. Inference will be very slow (minutes per image)."],
capabilities=["txt2img", "inpaint", "img2img", "outpaint"],
)
def _vram_to_tier(vram_gb: float) -> str:
if vram_gb >= 16:
return "ultra"
if vram_gb >= 8:
return "high"
if vram_gb >= 4:
return "medium"
if vram_gb >= 2:
return "legacy"
return "minimal"
def _make_warnings(tier: str, vram_gb: float, cc: str, fp16: bool) -> list[str]:
"""Generate human-readable warnings for suboptimal GPU configurations."""
warns = []
if tier == "minimal":
warns.append(
f"Very low VRAM ({vram_gb:.1f} GB) — inference will be slow and may OOM. "
"Sequential CPU offloading will be enabled automatically."
)
elif tier == "legacy":
warns.append(
f"Limited VRAM ({vram_gb:.1f} GB) — using SD 1.5 models (smaller, lower quality "
"than SD 2.x/SDXL). Still fully functional."
)
if not fp16:
warns.append(
f"GPU compute capability {cc} is below 6.0 — using fp32 (doubles VRAM use). "
"Consider upgrading to a Pascal-era (GTX 1000) or newer GPU for fp16 support."
)
return warns
def _apple_usable_gb() -> float:
"""Estimate GPU-usable unified memory on Apple Silicon (≈ half of total RAM)."""
try:
r = subprocess.run(
["sysctl", "-n", "hw.memsize"],
capture_output=True, text=True, timeout=5,
)
if r.returncode == 0:
return int(r.stdout.strip()) / (1024 ** 3) / 2
except Exception:
pass
return 8.0
def _caps_for_tier(tier: str) -> list[str]:
base = ["txt2img", "inpaint", "img2img", "outpaint"]
if tier in ("ultra", "high"):
return base + ["upscale_diffusion"]
return base
def get_model_ids(tier: str) -> dict[str, Optional[str]]:
"""Return the model-ID map for a given tier."""
return dict(_MODEL_TIERS.get(tier, _MODEL_TIERS["legacy"]))
# Process-level singleton — detect once, reuse everywhere.
_cached: Optional[GpuInfo] = None
def get_cached_gpu_info() -> GpuInfo:
global _cached
if _cached is None:
_cached = detect_gpu()
return _cached