888ba6a714
Host service (runs as llmuser, owns /opt/fluxgym + GPU access) that runs
sd-scripts SDXL LoRA training on demand for arbo — the infra-ops half of the
in-arbo LoRA training Phase 1 ownership split (vh/arbo
docs/contracts/in-arbo-lora-training-phase1.contract.md §4.1/§2).
- Fixed-invocation only (INV-T7): bounded params -> one sd-scripts command
shape; every param range/allowlist/path-containment checked before spawn;
bad request = 422, never a silent downgrade. 14 unit tests green.
- Thin supervisor: never imports torch; subprocesses the fluxgym venv's
accelerate. 1-job-at-a-time (arbo lease is the serializer, 409 is backstop).
Durable job records + boot reconciliation (§4.3).
- API: POST /train, GET /train/{id}[/log], POST /train/{id}/cancel,
GET /gpu-status (per-device VRAM + tts_on_3090 co-OOM signal), GET /healthz.
- Wire-shape (§7 resolved with comfy-dev): shared /worktank/arbo/train handoff
(group arbotrain, setgid 2770); worker binds 0.0.0.0:8203, arbo reaches via
host.docker.internal:host-gateway (reachability proven on 172.20.0.1:8203);
device-aware TTS steering via /gpu-status.
Deployed to irv-ml1 via playbooks/deploy-lora-training-worker.yaml (elway,
idempotent); systemd unit active; /healthz + /gpu-status verified live.
123 lines
4.9 KiB
Python
123 lines
4.9 KiB
Python
"""GPU status for arbo's device-aware scheduler (GET /gpu-status).
|
|
|
|
arbo steers a lean train OFF the 3090 when TTS is live there (the 3090/TTS co-OOM concern,
|
|
contract §7). The worker can't decide that — it just reports the raw per-device VRAM plus a
|
|
`tts_on_3090` boolean derived from nvidia-smi compute-apps. Everything is nvidia-smi CSV
|
|
parsing; no torch import.
|
|
|
|
Device indices are reported under CUDA_DEVICE_ORDER=PCI_BUS_ID (index 0 = RTX 3090,
|
|
index 1 = RTX A6000) — the same ordering the training recipe pins, so `device_index` in a
|
|
/train request and the indices here mean the same physical card.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
|
|
from . import config
|
|
|
|
_SMI_ENV = {"CUDA_DEVICE_ORDER": "PCI_BUS_ID"}
|
|
|
|
|
|
def _run(args: list[str]) -> str:
|
|
return subprocess.run(
|
|
["nvidia-smi", *args], capture_output=True, text=True, timeout=15, env={**_SMI_ENV}
|
|
).stdout
|
|
|
|
|
|
def _query_devices() -> list[dict]:
|
|
# index,name,memory.total,memory.used,memory.free (MiB) under PCI_BUS_ID ordering
|
|
out = _run(["--query-gpu=index,name,memory.total,memory.used,memory.free",
|
|
"--format=csv,noheader,nounits"])
|
|
devices = []
|
|
for line in out.strip().splitlines():
|
|
parts = [p.strip() for p in line.split(",")]
|
|
if len(parts) != 5:
|
|
continue
|
|
idx, name, total, used, free = parts
|
|
devices.append({
|
|
"index": int(idx), "name": name,
|
|
"total_mb": int(total), "used_mb": int(used), "free_mb": int(free),
|
|
"procs": [],
|
|
})
|
|
return devices
|
|
|
|
|
|
def _query_compute_apps() -> list[dict]:
|
|
# pid,used_memory,gpu_bus_id — map each compute app to a device by bus id
|
|
out = _run(["--query-compute-apps=pid,used_memory,gpu_bus_id", "--format=csv,noheader,nounits"])
|
|
apps = []
|
|
for line in out.strip().splitlines():
|
|
parts = [p.strip() for p in line.split(",")]
|
|
if len(parts) != 3:
|
|
continue
|
|
pid, mem, bus = parts
|
|
if not pid.isdigit():
|
|
continue
|
|
apps.append({"pid": int(pid), "used_mb": int(mem) if mem.isdigit() else 0, "bus_id": bus})
|
|
return apps
|
|
|
|
|
|
def _bus_id_by_index() -> dict[int, str]:
|
|
out = _run(["--query-gpu=index,gpu_bus_id", "--format=csv,noheader"])
|
|
mapping = {}
|
|
for line in out.strip().splitlines():
|
|
parts = [p.strip() for p in line.split(",")]
|
|
if len(parts) == 2 and parts[0].isdigit():
|
|
mapping[int(parts[0])] = parts[1]
|
|
return mapping
|
|
|
|
|
|
def _cmdline(pid: int) -> str:
|
|
try:
|
|
with open(f"/proc/{pid}/cmdline", "rb") as fh:
|
|
return fh.read().replace(b"\x00", b" ").decode("utf-8", "replace").lower()
|
|
except (OSError, ValueError):
|
|
return ""
|
|
|
|
|
|
def gpu_status() -> dict:
|
|
"""Return `{devices: [...], tts_on_3090: bool}`. Degrades to an error field on nvidia-smi failure."""
|
|
try:
|
|
devices = _query_devices()
|
|
apps = _query_compute_apps()
|
|
bus_by_idx = _bus_id_by_index()
|
|
except (subprocess.SubprocessError, OSError, ValueError) as exc:
|
|
return {"devices": [], "tts_on_3090": False, "error": f"nvidia-smi failed: {exc}"}
|
|
|
|
idx_by_bus = {bus: idx for idx, bus in bus_by_idx.items()}
|
|
dev_by_idx = {d["index"]: d for d in devices}
|
|
|
|
# Attach compute apps (with a marker flag) to their device.
|
|
for app in apps:
|
|
idx = idx_by_bus.get(app["bus_id"])
|
|
cmd = _cmdline(app["pid"])
|
|
app["is_tts"] = any(m in cmd for m in config.TTS_CMDLINE_MARKERS)
|
|
if idx is not None and idx in dev_by_idx:
|
|
dev_by_idx[idx]["procs"].append(
|
|
{"pid": app["pid"], "used_mb": app["used_mb"], "is_tts": app["is_tts"]}
|
|
)
|
|
|
|
# tts_on_3090 — the co-OOM-risk signal for arbo's device-aware steering. Honest derivation
|
|
# (transparent via `reason`), because the worker can't reliably fingerprint TTS: the TTS
|
|
# containers run generic cmdlines (`uvicorn app:app`, `python app.py`) so only the container
|
|
# NAME identifies them, and the worker's llmuser isn't in the docker group. So:
|
|
# (a) proc_marker — a compute app on device 0 whose cmdline matches a TTS marker (best effort), else
|
|
# (b) used_mb_floor — device 0 used >= floor (a busy 3090 = lean-train co-OOM risk, TTS/STT/audio alike).
|
|
# The PRECISE lever arbo should key on is `devices[0].free_mb`; `tts_on_3090` is the convenience boolean.
|
|
dev0 = dev_by_idx.get(config.DEVICE_3090)
|
|
tts_on_3090, reason = False, "idle"
|
|
free_3090 = None
|
|
if dev0 is not None:
|
|
free_3090 = dev0["free_mb"]
|
|
if any(p["is_tts"] for p in dev0["procs"]):
|
|
tts_on_3090, reason = True, "proc_marker"
|
|
elif dev0["used_mb"] >= config.TTS_3090_USED_MB_FLOOR:
|
|
tts_on_3090, reason = True, "used_mb_floor"
|
|
return {
|
|
"devices": devices,
|
|
"tts_on_3090": tts_on_3090,
|
|
"tts_on_3090_reason": reason,
|
|
"gpu3090_free_mb": free_3090, # the precise co-OOM lever; steer lean off the 3090 when low
|
|
}
|