← Files AMDARCHIVED FILE
skills/serving-llms-on-instinct/scripts/validate.py
8.25 KB · Sep 30, 2026 · 23:13 UTC
#!/usr/bin/env python3
"""
Validate the environment on an AMD GPU machine before launching vLLM.
Usage:
python scripts/validate.py
python scripts/validate.py --host root@10.0.0.5
python scripts/validate.py --auto-fix # apply safe fixes (NUMA, hipBLASLt)
Checks: /dev/kfd, /dev/dri, Docker, NUMA balancing, hipBLASLt, HF_TOKEN.
Each issue is classified as: error (blocks launch), warning (degrades perf), advisory (info).
Exits 0 if no error-severity issues remain, 1 otherwise.
Env vars:
ROCM_SSH_HOST, ROCM_SSH_USER, ROCM_SSH_PORT
"""
import argparse
import json
import os
import subprocess
import sys
def _is_local(host):
return not host or host in ("local", "localhost", "127.0.0.1")
def _run(cmd, host, user, port, timeout=20):
try:
if _is_local(host):
r = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=timeout)
else:
ssh_target = f"{user}@{host}" if user else host
ssh = [
"ssh",
"-o", "StrictHostKeyChecking=accept-new",
"-o", "ConnectTimeout=15",
"-o", "BatchMode=yes",
"-o", "LogLevel=ERROR",
"-p", str(port),
ssh_target, cmd,
]
r = subprocess.run(ssh, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=timeout)
return r.returncode, r.stdout.strip(), r.stderr.strip()
except subprocess.TimeoutExpired:
target = f"{user}@{host}" if user else host
return 1, "", f"Command timed out after {timeout}s on {target}"
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--host", default="", help="[user@]host (default: local or ROCM_SSH_HOST)")
parser.add_argument("--user", default="", help="SSH user (default: root)")
parser.add_argument("--port", type=int, default=0)
parser.add_argument("--auto-fix", action="store_true", help="Apply safe fixes without prompting")
args = parser.parse_args()
host = args.host
user = args.user
if "@" in host:
user, host = host.split("@", 1)
host = host or os.environ.get("ROCM_SSH_HOST", "")
user = user or os.environ.get("ROCM_SSH_USER", "")
port = args.port or int(os.environ.get("ROCM_SSH_PORT", "22"))
issues = []
fixes_applied = []
# /dev/kfd
rc, out, _ = _run("test -e /dev/kfd && echo exists || echo missing", host, user, port)
if "missing" in out:
issues.append({
"check": "dev_kfd",
"severity": "error",
"message": "/dev/kfd not found. The amdgpu kernel module is not loaded or the driver is not installed.",
"fix": "sudo modprobe amdgpu # or install ROCm driver",
})
else:
rc2, out2, _ = _run("test -r /dev/kfd && echo ok || echo denied", host, user, port)
if "denied" in out2:
# Docker passes --device /dev/kfd directly, so host user permissions
# don't block containerized workloads. Downgrade to warning.
issues.append({
"check": "dev_kfd",
"severity": "warning",
"message": "/dev/kfd exists but current user is not in video/render group. Docker containers will still work.",
"fix": "sudo usermod -aG video,render $USER # then re-login (only needed for non-Docker use)",
})
# /dev/dri
rc, out, _ = _run("ls /dev/dri/renderD* 2>/dev/null | wc -l", host, user, port)
try:
render_count = int(out)
except ValueError:
render_count = 0
if render_count == 0:
issues.append({
"check": "dev_dri",
"severity": "error",
"message": "No /dev/dri/renderD* nodes found. GPU render nodes not present.",
"fix": "Check that the amdgpu driver is loaded: lsmod | grep amdgpu",
})
# Docker
rc, out, err = _run("docker ps -q 2>&1 | head -1", host, user, port)
if rc != 0 or "permission denied" in err.lower() or "cannot connect" in err.lower():
issues.append({
"check": "docker",
"severity": "error",
"message": f"Docker not accessible: {err or 'docker ps failed'}",
"fix": "Start Docker: sudo systemctl start docker | Or add user to docker group: sudo usermod -aG docker $USER",
})
# NUMA balancing
rc, out, _ = _run("cat /proc/sys/kernel/numa_balancing 2>/dev/null || echo 0", host, user, port)
numa_val = out.strip()
if numa_val == "1":
if args.auto_fix:
rc2, _, _ = _run("echo 0 | sudo tee /proc/sys/kernel/numa_balancing > /dev/null", host, user, port)
if rc2 == 0:
fixes_applied.append("NUMA balancing disabled (non-persistent, resets on reboot)")
else:
issues.append({
"check": "numa_balancing",
"severity": "warning",
"message": "NUMA balancing is enabled. Causes latency spikes during GPU inference.",
"fix": "echo 0 | sudo tee /proc/sys/kernel/numa_balancing",
})
else:
issues.append({
"check": "numa_balancing",
"severity": "warning",
"message": "NUMA balancing is enabled. Causes latency spikes during GPU inference.",
"fix": "echo 0 | sudo tee /proc/sys/kernel/numa_balancing (or run with --auto-fix)",
})
# hipBLASLt
rc, out, _ = _run("ls /opt/rocm/lib/libhipblaslt* 2>/dev/null | head -1", host, user, port)
if not out.strip():
issues.append({
"check": "hipblaslt",
"severity": "warning",
"message": "hipBLASLt not found at /opt/rocm/lib/. GEMM performance may be reduced.",
"fix": "Ensure ROCm is fully installed: sudo apt install hipblaslt or reinstall ROCm",
})
# HF_TOKEN
rc, out, _ = _run("printenv HF_TOKEN | head -c 4", host, user, port)
if not out.strip():
issues.append({
"check": "hf_token",
"severity": "advisory",
"message": "HF_TOKEN not set. Required for gated models (Llama, Gemma). Not needed for Qwen3.",
"fix": "export HF_TOKEN=hf_...",
})
# vLLM Docker image
rc, out, _ = _run("docker images vllm/vllm-openai-rocm --format '{{.Tag}}' 2>/dev/null | head -1", host, user, port)
if not out.strip():
issues.append({
"check": "vllm_image",
"severity": "advisory",
"message": "vllm/vllm-openai-rocm image not pulled yet. First launch will download ~20GB.",
"fix": "docker pull vllm/vllm-openai-rocm:latest",
})
# CUDA_VISIBLE_DEVICES footgun -- empty string hides all GPUs, explicit indices are OK
rc, out, _ = _run("env | grep -c '^CUDA_VISIBLE_DEVICES=' || true", host, user, port)
if out.strip() and out.strip() != "0":
rc2, val, _ = _run("printenv CUDA_VISIBLE_DEVICES", host, user, port)
raw_val = val.strip()
if raw_val == "":
issues.append({
"check": "cuda_visible_devices",
"severity": "error",
"message": "CUDA_VISIBLE_DEVICES is set to '' (empty string). This hides all GPUs from the ROCm runtime.",
"fix": "unset CUDA_VISIBLE_DEVICES",
})
else:
issues.append({
"check": "cuda_visible_devices",
"severity": "advisory",
"message": f"CUDA_VISIBLE_DEVICES is set to {raw_val}. ROCm maps this to HIP_VISIBLE_DEVICES. Only the listed GPUs will be visible.",
"fix": "unset CUDA_VISIBLE_DEVICES # to use all GPUs",
})
errors = [i for i in issues if i["severity"] == "error"]
warnings = [i for i in issues if i["severity"] == "warning"]
advisories = [i for i in issues if i["severity"] == "advisory"]
result = {
"ready": len(errors) == 0,
"target": "local" if _is_local(host) else host,
"errors": errors,
"warnings": warnings,
"advisories": advisories,
"fixes_applied": fixes_applied,
}
print(json.dumps(result, indent=2))
sys.exit(0 if len(errors) == 0 else 1)
if __name__ == "__main__":
main()
SHA-256: 7de7b69e9229e64c0353345a31a7627440445759153c6e57e9df52a5f43c5903