SPB Git

spb/wp31_uqo

Public
4.7 KB · 107 lines python
1#!/usr/bin/env python2"""Phase 0 environment audit: records hardware, toolchain and framework capabilities.34Writes hardware/device_inventory.json (local machine) and prints a summary.5Never prints secrets. Remote cluster nodes are audited by scripts/cluster_audit.sh6(SSH, read-only commands) and merged into the same JSON under "remote_nodes".7"""8from __future__ import annotations9import argparse, json, os, platform, shutil, subprocess, sys, time10from pathlib import Path111213def sh(cmd: list[str]) -> str:14    try:15        return subprocess.run(cmd, capture_output=True, text=True, timeout=20).stdout.strip()16    except Exception as e:  # noqa: BLE00117        return f"<unavailable: {type(e).__name__}>"181920def main() -> int:21    ap = argparse.ArgumentParser(description=__doc__)22    ap.add_argument("--out", default="hardware/device_inventory.json")23    ap.add_argument("--smoke", action="store_true", help="run a tiny forward/backward on every available backend")24    args = ap.parse_args()2526    inv: dict = {27        "audited_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),28        "hostname": platform.node(),29        "os": f"{platform.system()} {platform.release()} ({platform.mac_ver()[0] or platform.version()})",30        "machine": platform.machine(),31        "python": sys.version.split()[0],32        "python_executable": sys.executable,33        "cpu_brand": sh(["sysctl", "-n", "machdep.cpu.brand_string"]) if sys.platform == "darwin" else platform.processor(),34        "cpu_logical_cores": os.cpu_count(),35        "ram_bytes": int(sh(["sysctl", "-n", "hw.memsize"]) or 0) if sys.platform == "darwin" else None,36        "disk_free_bytes": shutil.disk_usage(os.getcwd()).free,37        "toolchains": {t: shutil.which(t) for t in ["git", "latexmk", "pdflatex", "lualatex", "pandoc", "nvidia-smi", "uv", "cmake", "clang"]},38        "frameworks": {},39        "backends": {},40    }41    if sys.platform == "darwin":42        sp = sh(["system_profiler", "SPDisplaysDataType"])43        gpu_cores = None44        for line in sp.splitlines():45            if "Total Number of Cores" in line:46                gpu_cores = int(line.split(":")[-1].strip())47        inv["gpu_cores"] = gpu_cores48        inv["metal"] = any("Metal" in l for l in sp.splitlines())49    try:50        import torch51        inv["frameworks"]["torch"] = torch.__version__52        inv["backends"]["cpu"] = True53        inv["backends"]["mps"] = bool(torch.backends.mps.is_available())54        inv["backends"]["cuda"] = bool(torch.cuda.is_available())55        if torch.cuda.is_available():56            inv["cuda_devices"] = [torch.cuda.get_device_name(i) for i in range(torch.cuda.device_count())]57    except Exception as e:  # noqa: BLE00158        inv["frameworks"]["torch"] = f"<missing: {e}>"59    try:60        import mlx.core as mx61        inv["frameworks"]["mlx"] = mx.__version__62    except Exception:  # noqa: BLE00163        inv["frameworks"]["mlx"] = None64    for pkg in ["numpy", "tokenizers", "datasets", "safetensors", "scipy", "pandas", "matplotlib", "pytest", "hypothesis", "yaml"]:65        try:66            m = __import__(pkg)67            inv["frameworks"][pkg] = getattr(m, "__version__", "present")68        except Exception:  # noqa: BLE00169            inv["frameworks"][pkg] = None70    inv["network"] = {"note": "outbound HTTPS assumed available for dataset download; verified at data preparation time"}71    inv["secrets_policy"] = "no secret values are recorded in this inventory"7273    if args.smoke:74        import torch75        res = {}76        for dev in [d for d, ok in inv["backends"].items() if ok]:77            try:78                t0 = time.perf_counter()79                x = torch.randn(64, 256, device=dev, requires_grad=True)80                w = torch.randn(256, 256, device=dev, requires_grad=True)81                y = (x @ w).relu().sum()82                y.backward()83                if dev == "mps":84                    torch.mps.synchronize()85                res[dev] = {"ok": bool(torch.isfinite(w.grad).all().item()), "seconds": round(time.perf_counter() - t0, 4)}86            except Exception as e:  # noqa: BLE00187                res[dev] = {"ok": False, "error": repr(e)}88        inv["smoke_forward_backward"] = res8990    out = Path(args.out)91    existing = {}92    if out.exists():93        try:94            existing = json.loads(out.read_text())95        except Exception:  # noqa: BLE00196            existing = {}97    if "remote_nodes" in existing:98        inv["remote_nodes"] = existing["remote_nodes"]99    out.parent.mkdir(parents=True, exist_ok=True)100    out.write_text(json.dumps(inv, indent=2, ensure_ascii=False))101    print(json.dumps({k: v for k, v in inv.items() if k != "remote_nodes"}, indent=2, ensure_ascii=False))102    return 0103104105if __name__ == "__main__":106    raise SystemExit(main())107
Files · a2c46f03643fdd75e8e87595931d35e18c590d57
Menu

Theme