scripts/hardware_check.py
scripts/hardware_check.pyBrowse 33 files
4,724 tokens
17,905 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32"""hardware_check.py — Detect whether this machine can realistically run ComfyUI locally.3 4Improvements over v1:5 - Multi-GPU detection: scans all NVIDIA / AMD GPUs, picks the best one (most VRAM)6 - Apple Silicon: detects Rosetta-via-x86_64 false negative; warns instead of misclassifying7 - Apple generation: defaults to None (unknown) instead of mis-tagging as M18 - WSL2 detection: identifies WSL2 + nvidia-smi situation explicitly9 - ROCm: prefers `rocm-smi --json` for new ROCm 6.x output10 - Disk space check: warns if /home or workspace volume has < 25 GB free11 - PyTorch verification (optional): tries to import torch and check device availability12 - Windows: prefers PowerShell `Get-CimInstance` over deprecated `wmic`13 - More accurate VRAM thresholds and verdict reasons14 15Emits a structured JSON report. Exit codes match `verdict`:16 0 → ok17 1 → marginal18 2 → cloud19 20Usage:21 python3 hardware_check.py [--json] [--check-pytorch]22"""23 24from __future__ import annotations25 26import json27import os28import platform29import re30import shutil31import subprocess32import sys33from typing import Any34 35 36# Thresholds (GiB).37MIN_VRAM_GB_USABLE = 638OK_VRAM_GB = 839GREAT_VRAM_GB = 1240MIN_MAC_RAM_GB = 1641OK_MAC_RAM_GB = 3242MIN_FREE_DISK_GB = 25 # ComfyUI core ~5 GB + one model ~5–24 GB43 44_COMFY_CLI_FLAG = {45 "nvidia": "--nvidia",46 "amd": "--amd",47 "apple-silicon": "--m-series",48 "intel": None,49 "comfy-cloud": None,50 "cpu": "--cpu",51}52 53 54def _run(cmd: list[str], timeout: int = 8) -> str:55 try:56 out = subprocess.run(57 cmd, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=timeout, check=False58 )59 return (out.stdout or "") + (out.stderr or "")60 except (FileNotFoundError, subprocess.TimeoutExpired, OSError):61 return ""62 63 64def is_wsl() -> bool:65 """Return True when running under Windows Subsystem for Linux."""66 if platform.system() != "Linux":67 return False68 if "microsoft" in platform.release().lower() or "wsl" in platform.release().lower():69 return True70 try:71 with open("/proc/version", "r", encoding="utf-8") as fh:72 return "microsoft" in fh.read().lower()73 except OSError:74 return False75 76 77def is_rosetta() -> bool:78 """Return True when Python is running translated under Rosetta on Apple Silicon."""79 if platform.system() != "Darwin":80 return False81 if platform.machine() == "arm64":82 return False83 # x86_64 on Darwin — could be Intel Mac or Rosetta. Probe sysctl.84 out = _run(["sysctl", "-in", "sysctl.proc_translated"]).strip()85 return out == "1"86 87 88def detect_nvidia() -> dict | None:89 """Detect NVIDIA GPUs. Returns the GPU with the most VRAM, plus list of all."""90 if not shutil.which("nvidia-smi"):91 return None92 out = _run([93 "nvidia-smi",94 "--query-gpu=index,name,memory.total,driver_version",95 "--format=csv,noheader,nounits",96 ])97 if not out.strip():98 return None99 gpus = []100 for line in out.strip().splitlines():101 parts = [p.strip() for p in line.split(",")]102 if len(parts) < 3:103 continue104 try:105 idx = int(parts[0])106 name = parts[1]107 vram_mb = int(parts[2])108 except ValueError:109 continue110 driver = parts[3] if len(parts) > 3 else ""111 gpus.append({112 "vendor": "nvidia",113 "index": idx,114 "name": name,115 "vram_gb": round(vram_mb / 1024, 1),116 "driver": driver,117 })118 if not gpus:119 return None120 # Pick GPU with most VRAM121 best = max(gpus, key=lambda g: g["vram_gb"])122 if len(gpus) > 1:123 best["all_gpus"] = gpus124 return best125 126 127def detect_rocm() -> dict | None:128 if not shutil.which("rocm-smi"):129 return None130 # Prefer JSON output (new ROCm 6.x)131 out = _run(["rocm-smi", "--showproductname", "--showmeminfo", "vram", "--json"])132 if out.strip().startswith("{"):133 try:134 data = json.loads(out)135 cards = []136 for card_id, info in data.items():137 if not card_id.startswith("card"):138 continue139 name = (info.get("Card series") or info.get("Card model")140 or info.get("Marketing Name") or "AMD GPU")141 vram_b = info.get("VRAM Total Memory (B)") or info.get("vram_total_memory_b") or 0142 try:143 vram_b = int(vram_b)144 except (ValueError, TypeError):145 vram_b = 0146 cards.append({147 "vendor": "amd",148 "name": str(name).strip(),149 "vram_gb": round(vram_b / (1024**3), 1),150 "driver": "rocm",151 })152 if cards:153 best = max(cards, key=lambda c: c["vram_gb"])154 if len(cards) > 1:155 best["all_gpus"] = cards156 return best157 except json.JSONDecodeError:158 pass159 # Fall back to text parsing160 out = _run(["rocm-smi", "--showproductname", "--showmeminfo", "vram"])161 if not out.strip():162 return None163 name_m = re.search(r"Card (?:series|model|Marketing Name):\s*(.+)", out)164 vram_m = re.search(r"VRAM Total Memory \(B\):\s*(\d+)", out)165 vram_gb = round(int(vram_m.group(1)) / (1024**3), 1) if vram_m else 0.0166 return {167 "vendor": "amd",168 "name": name_m.group(1).strip() if name_m else "AMD GPU",169 "vram_gb": vram_gb,170 "driver": "rocm",171 }172 173 174def detect_apple_silicon() -> dict | None:175 if platform.system() != "Darwin":176 return None177 if platform.machine() != "arm64":178 return None179 chip = _run(["sysctl", "-n", "machdep.cpu.brand_string"]).strip()180 m = re.search(r"Apple M(\d+)", chip)181 generation = int(m.group(1)) if m else None182 mem_bytes = 0183 try:184 mem_bytes = int(_run(["sysctl", "-n", "hw.memsize"]).strip() or 0)185 except ValueError:186 pass187 ram_gb = round(mem_bytes / (1024**3), 1) if mem_bytes else 0.0188 189 # Detect chip variant ("Pro", "Max", "Ultra") — affects performance even at same gen190 variant = None191 for v in ("Ultra", "Max", "Pro"):192 if v in chip:193 variant = v194 break195 196 return {197 "vendor": "apple",198 "name": chip or "Apple Silicon",199 "generation": generation,200 "variant": variant,201 "unified_memory_gb": ram_gb,202 }203 204 205def detect_intel_arc() -> dict | None:206 if platform.system() not in {"Linux", "Windows"}:207 return None208 if shutil.which("clinfo"):209 out = _run(["clinfo", "--list"])210 if "Intel" in out and ("Arc" in out or "Xe" in out):211 return {"vendor": "intel", "name": "Intel Arc/Xe", "vram_gb": 0.0}212 # Windows: try Get-CimInstance213 if platform.system() == "Windows" and shutil.which("powershell"):214 out = _run(["powershell", "-NoProfile",215 "Get-CimInstance Win32_VideoController | Select-Object Name | Format-List"])216 if "Intel" in out and ("Arc" in out or "Iris Xe" in out):217 return {"vendor": "intel", "name": "Intel Arc/Iris Xe", "vram_gb": 0.0}218 return None219 220 221def total_system_ram_gb() -> float:222 sysname = platform.system()223 if sysname == "Darwin":224 try:225 return round(int(_run(["sysctl", "-n", "hw.memsize"]).strip() or 0) / (1024**3), 1)226 except ValueError:227 return 0.0228 if sysname == "Linux":229 try:230 with open("/proc/meminfo", "r", encoding="utf-8") as fh:231 for line in fh:232 if line.startswith("MemTotal:"):233 kb = int(line.split()[1])234 return round(kb / (1024**2), 1)235 except OSError:236 return 0.0237 if sysname == "Windows":238 if shutil.which("powershell"):239 out = _run([240 "powershell", "-NoProfile",241 "(Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory",242 ])243 m = re.search(r"(\d{8,})", out)244 if m:245 return round(int(m.group(1)) / (1024**3), 1)246 # Fall back to wmic for older Windows247 out = _run(["wmic", "ComputerSystem", "get", "TotalPhysicalMemory"])248 m = re.search(r"(\d{6,})", out)249 if m:250 return round(int(m.group(1)) / (1024**3), 1)251 return 0.0252 253 254def total_free_disk_gb(path: str = ".") -> float:255 try:256 usage = shutil.disk_usage(path)257 return round(usage.free / (1024**3), 1)258 except OSError:259 return 0.0260 261 262def check_pytorch_cuda() -> dict | None:263 """Optional PyTorch availability check. Only run when --check-pytorch is set."""264 try:265 import torch # type: ignore[import-not-found]266 except Exception as e:267 return {"available": False, "reason": f"torch not importable: {e}"}268 info: dict[str, Any] = {269 "available": True,270 "torch_version": torch.__version__,271 }272 try:273 info["cuda_available"] = bool(torch.cuda.is_available())274 if info["cuda_available"]:275 info["cuda_device_count"] = torch.cuda.device_count()276 info["cuda_device_0"] = torch.cuda.get_device_name(0)277 except Exception:278 info["cuda_available"] = False279 try:280 info["mps_available"] = bool(torch.backends.mps.is_available())281 except Exception:282 info["mps_available"] = False283 return info284 285 286def classify(gpu: dict | None, ram_gb: float, free_disk_gb: float, *, wsl: bool, rosetta: bool) -> tuple[str, str, list[str]]:287 notes: list[str] = []288 289 if rosetta:290 notes.append(291 "Detected Python running under Rosetta on Apple Silicon. "292 "ComfyUI MPS support requires native ARM64 Python — install via "293 "`brew install python` or arm64 Miniforge, then re-run."294 )295 return "cloud", "comfy-cloud", notes296 297 if wsl and gpu and gpu["vendor"] == "nvidia":298 notes.append("Detected WSL2 + NVIDIA — confirm `nvidia-smi` works in your WSL distro before installing.")299 300 if free_disk_gb and free_disk_gb < MIN_FREE_DISK_GB:301 notes.append(302 f"Free disk space ({free_disk_gb} GB) is below the {MIN_FREE_DISK_GB} GB recommended minimum. "303 "ComfyUI core (~5 GB) plus one SDXL model (~6.5 GB) needs space; Flux Dev needs ~24 GB."304 )305 306 # Host RAM matters even for discrete-GPU systems: ComfyUI swaps model307 # weights through CPU RAM when shuffling between text encoders / VAE / UNet.308 # Apple's unified-memory check is handled below so don't double-warn.309 if ram_gb and ram_gb < 8 and gpu and gpu.get("vendor") != "apple":310 notes.append(311 f"System RAM ({ram_gb} GB) is low. ComfyUI swaps model weights through "312 "host RAM; <8 GB causes severe slowdowns. 16+ GB recommended."313 )314 315 if gpu is None:316 notes.append(317 "No supported accelerator found (NVIDIA CUDA / AMD ROCm / Apple Silicon / Intel Arc)."318 )319 notes.append(320 "CPU-only ComfyUI works but is unusably slow for modern models — use Comfy Cloud."321 )322 return "cloud", "comfy-cloud", notes323 324 if gpu["vendor"] == "apple":325 gen = gpu.get("generation")326 variant = gpu.get("variant")327 mem = gpu.get("unified_memory_gb", 0.0)328 gen_str = f"M{gen}" if gen else "Apple Silicon"329 if variant:330 gen_str += f" {variant}"331 if mem < MIN_MAC_RAM_GB:332 notes.append(333 f"{gen_str} with {mem} GB unified memory — below the {MIN_MAC_RAM_GB} GB practical minimum."334 )335 notes.append("SD1.5 may work; SDXL/Flux will swap or OOM. Recommend Comfy Cloud.")336 return "cloud", "comfy-cloud", notes337 if mem < OK_MAC_RAM_GB:338 notes.append(339 f"{gen_str} with {mem} GB — SDXL works but slow. Flux/video likely too tight."340 )341 return "marginal", "apple-silicon", notes342 notes.append(f"{gen_str} with {mem} GB unified memory — good for SDXL/Flux.")343 return "ok", "apple-silicon", notes344 345 if gpu["vendor"] == "intel":346 notes.append("Intel Arc detected — ComfyUI IPEX support is experimental; Comfy Cloud is more reliable.")347 return "marginal", "intel", notes348 349 # Discrete NVIDIA / AMD350 vram = gpu.get("vram_gb", 0.0)351 name = gpu["name"]352 if vram < MIN_VRAM_GB_USABLE:353 notes.append(354 f"{name} has only {vram} GB VRAM — below the {MIN_VRAM_GB_USABLE} GB practical minimum."355 )356 notes.append("Most modern models won't load. Recommend Comfy Cloud.")357 return "cloud", "comfy-cloud", notes358 if vram < OK_VRAM_GB:359 notes.append(360 f"{name} ({vram} GB VRAM) — SD1.5 works, SDXL tight, Flux/video unlikely."361 )362 return "marginal", gpu["vendor"], notes363 if vram < GREAT_VRAM_GB:364 notes.append(f"{name} ({vram} GB VRAM) — SDXL comfortable, Flux possible with optimizations.")365 return "ok", gpu["vendor"], notes366 notes.append(f"{name} ({vram} GB VRAM) — can run everything including Flux/video.")367 return "ok", gpu["vendor"], notes368 369 370def build_report(*, check_pytorch: bool = False) -> dict:371 sysname = platform.system()372 arch = platform.machine()373 ram_gb = total_system_ram_gb()374 free_disk_gb = total_free_disk_gb(os.path.expanduser("~"))375 376 rosetta = is_rosetta()377 wsl = is_wsl()378 379 gpu = (380 detect_nvidia()381 or detect_rocm()382 or detect_apple_silicon()383 or detect_intel_arc()384 )385 386 # Intel Mac: arm64 detect failed AND no other GPU paths387 if gpu is None and sysname == "Darwin" and arch != "arm64" and not rosetta:388 notes = [389 "Intel Mac detected — no MPS backend available.",390 "ComfyUI will fall back to CPU which is unusably slow. Use Comfy Cloud.",391 ]392 report = {393 "os": sysname,394 "arch": arch,395 "system_ram_gb": ram_gb,396 "free_disk_gb": free_disk_gb,397 "wsl": False,398 "rosetta": False,399 "gpu": None,400 "verdict": "cloud",401 "recommended_install_path": "comfy-cloud",402 "comfy_cli_flag": None,403 "notes": notes,404 "install_urls": _install_urls(),405 }406 if check_pytorch:407 report["pytorch"] = check_pytorch_cuda()408 return report409 410 verdict, install_path, notes = classify(411 gpu, ram_gb, free_disk_gb, wsl=wsl, rosetta=rosetta,412 )413 414 report = {415 "os": sysname,416 "arch": arch,417 "system_ram_gb": ram_gb,418 "free_disk_gb": free_disk_gb,419 "wsl": wsl,420 "rosetta": rosetta,421 "gpu": gpu,422 "verdict": verdict,423 "recommended_install_path": install_path,424 "comfy_cli_flag": _COMFY_CLI_FLAG.get(install_path),425 "notes": notes,426 "install_urls": _install_urls(),427 }428 if check_pytorch:429 report["pytorch"] = check_pytorch_cuda()430 return report431 432 433def _install_urls() -> dict:434 return {435 "desktop": "https://docs.comfy.org/installation/desktop",436 "manual": "https://docs.comfy.org/installation/manual_install",437 "comfy_cli": "https://docs.comfy.org/comfy-cli/getting-started",438 "cloud": "https://platform.comfy.org",439 }440 441 442def main(argv: list[str] | None = None) -> int:443 import argparse444 p = argparse.ArgumentParser(description="Check whether this machine can run ComfyUI locally.")445 p.add_argument("--json", action="store_true", help="Emit machine-readable JSON only")446 p.add_argument("--check-pytorch", action="store_true",447 help="Also probe `torch` for CUDA/MPS availability (slower)")448 args = p.parse_args(argv)449 450 report = build_report(check_pytorch=args.check_pytorch)451 452 if args.json:453 print(json.dumps(report, indent=2))454 else:455 print(f"OS: {report['os']} ({report['arch']})")456 if report.get("wsl"):457 print("Env: WSL2")458 if report.get("rosetta"):459 print("Env: Rosetta (x86_64 Python on Apple Silicon)")460 print(f"RAM: {report['system_ram_gb']} GB")461 print(f"Free disk: {report['free_disk_gb']} GB (~/)")462 if report["gpu"]:463 g = report["gpu"]464 if g["vendor"] == "apple":465 print(f"GPU: {g['name']} — {g.get('unified_memory_gb', 0)} GB unified memory")466 else:467 print(f"GPU: {g['name']} — {g.get('vram_gb', 0)} GB VRAM")468 if g.get("all_gpus") and len(g["all_gpus"]) > 1:469 print(f" ({len(g['all_gpus'])} GPUs total; using best by VRAM)")470 else:471 print("GPU: (none detected)")472 print(f"Verdict: {report['verdict']} → {report['recommended_install_path']}")473 if report["comfy_cli_flag"]:474 print(f" run: comfy --skip-prompt install {report['comfy_cli_flag']}")475 if report.get("pytorch"):476 pt = report["pytorch"]477 if pt.get("available"):478 line = f"PyTorch: {pt.get('torch_version')}"479 if pt.get("cuda_available"):480 line += f" + CUDA ({pt.get('cuda_device_0', '?')})"481 if pt.get("mps_available"):482 line += " + MPS"483 print(line)484 else:485 print(f"PyTorch: not available — {pt.get('reason')}")486 for n in report["notes"]:487 print(f" • {n}")488 489 if report["verdict"] == "ok":490 return 0491 if report["verdict"] == "marginal":492 return 1493 return 2494 495 496if __name__ == "__main__":497 sys.exit(main())498 Referenced from SKILL.md
SKILL.mdView in source ↗
Source excerpt starting at line 11.SKILL.mdView in source ↗11setup:12 help: "Run scripts/hardware_check.py FIRST to decide local vs Comfy Cloud; then scripts/comfyui_setup.sh auto-installs locally (or use Cloud API key for platform.comfy.org)."13metadata:
Source excerpt starting at line 110.SKILL.mdView in source ↗110# Can this machine run ComfyUI locally? (GPU/VRAM/disk check)111python scripts/hardware_check.py112```
Source excerpt starting at line 268.268```bash269python scripts/hardware_check.py --json270# Optional: also probe `torch` for actual CUDA/MPS:271python scripts/hardware_check.py --json --check-pytorch272```