mindXtrain / tests /test_hw_diagnostics.py
Gregory-L's picture
fork mindXtrain from GitHub (Professor-Codephreak/mindXtrain@661bd41) as the mindX-specific line
dfb775d verified
Raw History Blame Contribute Delete
9.57 kB
"""Hardware diagnostics — CPU/AMD/NVIDIA detection.
Probes shell out to rocm-smi / nvidia-smi when available; tests
monkey-patch shutil.which and subprocess.run so the suite stays
offline and reproducible regardless of the host hardware.
"""
from __future__ import annotations
import subprocess
import pytest
from mindxtrain.operator.coach import hw_diagnostics as hw
# ---- CPU --------------------------------------------------------------------
def test_probe_cpu_returns_available():
"""Every host has a CPU; probe never returns available=False."""
info = hw.probe_cpu()
assert info.available is True
assert info.cores >= 1
assert info.threads >= 1
def test_probe_cpu_detects_ryzen_when_model_name_matches(monkeypatch):
"""Test the Ryzen detection branch with a synthetic /proc/cpuinfo."""
monkeypatch.setattr(hw, "_parse_cpuinfo",
lambda: ("AMD Ryzen 5 5600U", "AuthenticAMD"))
monkeypatch.setattr(hw, "_parse_meminfo", lambda: (16.0, 8.0))
info = hw.probe_cpu()
assert info.vendor == "amd"
assert info.is_ryzen is True
assert info.ram_total_gb == 16.0
def test_probe_cpu_detects_intel(monkeypatch):
monkeypatch.setattr(hw, "_parse_cpuinfo",
lambda: ("Intel(R) Core(TM) i7-12700K", "GenuineIntel"))
monkeypatch.setattr(hw, "_parse_meminfo", lambda: (32.0, 16.0))
info = hw.probe_cpu()
assert info.vendor == "intel"
assert info.is_ryzen is False
def test_probe_cpu_unknown_vendor_no_model(monkeypatch):
"""When /proc/cpuinfo is missing, surface a friendly note."""
monkeypatch.setattr(hw, "_parse_cpuinfo", lambda: ("", ""))
monkeypatch.setattr(hw, "_parse_meminfo", lambda: (0.0, 0.0))
info = hw.probe_cpu()
assert info.model_name == "CPU"
assert info.vendor == ""
assert info.is_ryzen is False
assert info.note is not None
assert "cpuinfo" in info.note.lower()
def test_probe_cpu_amd_non_ryzen_carries_friendly_note(monkeypatch):
"""An AMD chip that isn't Ryzen / EPYC still gets a usable note."""
monkeypatch.setattr(hw, "_parse_cpuinfo",
lambda: ("AMD Athlon X4 760K", "AuthenticAMD"))
monkeypatch.setattr(hw, "_parse_meminfo", lambda: (8.0, 4.0))
info = hw.probe_cpu()
assert info.vendor == "amd"
assert info.is_ryzen is False
assert info.note is not None
assert "amd" in info.note.lower()
# ---- AMD GPU ----------------------------------------------------------------
def test_probe_amd_unavailable_when_rocm_smi_missing(monkeypatch):
monkeypatch.setattr(hw, "_which", lambda _: None)
info = hw.probe_amd()
assert info.available is False
assert info.gpus == []
assert info.note is not None
assert "rocm-smi" in info.note
def test_probe_amd_parses_json_output(monkeypatch):
"""Synthesise a rocm-smi --json response with one MI300X."""
monkeypatch.setattr(hw, "_which", lambda _: "/usr/bin/rocm-smi")
fake_json = (
'{"card0": {"Card series": "AMD Instinct MI300X", '
'"VRAM Total Memory (B)": "206158430208"}, '
'"system": {"Driver version": "7.2.1"}}'
)
monkeypatch.setattr(subprocess, "run",
lambda *a, **kw: subprocess.CompletedProcess(
args=a, returncode=0, stdout=fake_json, stderr=""))
info = hw.probe_amd()
assert info.available is True
assert len(info.gpus) == 1
assert "MI300X" in info.gpus[0].name
# 206158430208 bytes / 1024^3 ≈ 191.97 GB → rounds to 191.97
assert info.gpus[0].vram_gb == pytest.approx(191.97, abs=0.05)
assert info.rocm_version == "7.2.1"
def test_probe_amd_handles_rocm_smi_failure(monkeypatch):
"""A non-zero rc → unavailable with the stderr surfaced."""
monkeypatch.setattr(hw, "_which", lambda _: "/usr/bin/rocm-smi")
monkeypatch.setattr(subprocess, "run",
lambda *a, **kw: subprocess.CompletedProcess(
args=a, returncode=1, stdout="",
stderr="no AMD GPUs detected"))
info = hw.probe_amd()
assert info.available is False
assert info.note is not None
assert "rc=1" in info.note
def test_probe_amd_handles_timeout(monkeypatch):
monkeypatch.setattr(hw, "_which", lambda _: "/usr/bin/rocm-smi")
def _raise(*a, **kw):
raise subprocess.TimeoutExpired(cmd="rocm-smi", timeout=4)
monkeypatch.setattr(subprocess, "run", _raise)
info = hw.probe_amd()
assert info.available is False
assert info.note is not None
def test_probe_amd_handles_non_json_output(monkeypatch):
"""Older rocm-smi versions print plain text — graceful soft signal."""
monkeypatch.setattr(hw, "_which", lambda _: "/usr/bin/rocm-smi")
monkeypatch.setattr(subprocess, "run",
lambda *a, **kw: subprocess.CompletedProcess(
args=a, returncode=0,
stdout="GPU[0]: AMD MI300X\n", stderr=""))
info = hw.probe_amd()
# We say "available" because rocm-smi ran cleanly; just couldn't parse.
assert info.available is True
assert info.note is not None
assert "json" in info.note.lower()
# ---- NVIDIA GPU -------------------------------------------------------------
def test_probe_nvidia_unavailable_when_nvidia_smi_missing(monkeypatch):
monkeypatch.setattr(hw, "_which", lambda _: None)
info = hw.probe_nvidia()
assert info.available is False
assert info.gpus == []
def test_probe_nvidia_parses_csv_output(monkeypatch):
monkeypatch.setattr(hw, "_which", lambda _: "/usr/bin/nvidia-smi")
fake_csv = "NVIDIA H100 80GB HBM3, 81920, 560.35"
# Two subprocess.run calls happen: one for csv, one for the header CUDA line.
call_count = {"n": 0}
def fake_run(*a, **kw):
call_count["n"] += 1
if call_count["n"] == 1:
return subprocess.CompletedProcess(
args=a, returncode=0, stdout=fake_csv, stderr="",
)
return subprocess.CompletedProcess(
args=a, returncode=0, stdout="CUDA Version: 12.4\n", stderr="",
)
monkeypatch.setattr(subprocess, "run", fake_run)
info = hw.probe_nvidia()
assert info.available is True
assert len(info.gpus) == 1
assert "H100" in info.gpus[0].name
# 81920 MB / 1024 = 80 GB
assert info.gpus[0].vram_gb == pytest.approx(80.0, abs=0.01)
assert info.driver_version == "560.35"
assert info.cuda_version == "12.4"
def test_probe_nvidia_multiple_gpus(monkeypatch):
monkeypatch.setattr(hw, "_which", lambda _: "/usr/bin/nvidia-smi")
fake_csv = (
"NVIDIA H100 80GB HBM3, 81920, 560.35\n"
"NVIDIA H100 80GB HBM3, 81920, 560.35\n"
)
monkeypatch.setattr(subprocess, "run",
lambda *a, **kw: subprocess.CompletedProcess(
args=a, returncode=0, stdout=fake_csv, stderr=""))
info = hw.probe_nvidia()
assert len(info.gpus) == 2
def test_probe_nvidia_handles_rc_nonzero(monkeypatch):
monkeypatch.setattr(hw, "_which", lambda _: "/usr/bin/nvidia-smi")
monkeypatch.setattr(subprocess, "run",
lambda *a, **kw: subprocess.CompletedProcess(
args=a, returncode=9, stdout="",
stderr="NVIDIA-SMI has failed"))
info = hw.probe_nvidia()
assert info.available is False
# ---- recommended lane -------------------------------------------------------
def test_recommend_lane_prefers_amd_when_available():
cpu = hw.CPUInfo()
amd = hw.AMDInfo(available=True, gpus=[hw.AMDGPU(name="MI300X", vram_gb=192)])
nv = hw.NVIDIAInfo(available=True, gpus=[hw.NVIDIAGPU(name="H100", vram_gb=80)])
assert hw.recommend_lane(cpu, amd, nv) == "axolotl_amd"
def test_recommend_lane_consumer_radeon_uses_trl_local():
"""A consumer Radeon (not Instinct/MI-class) → the in-process local lane."""
cpu = hw.CPUInfo()
amd = hw.AMDInfo(
available=True, gpus=[hw.AMDGPU(name="Radeon RX 7900 XTX", vram_gb=24)],
)
nv = hw.NVIDIAInfo(available=False)
assert hw.recommend_lane(cpu, amd, nv) == "trl_local"
def test_recommend_lane_local_nvidia_uses_trl_local():
"""Any local NVIDIA card → trl_local (no CUDA-axolotl subprocess path exists)."""
cpu = hw.CPUInfo()
amd = hw.AMDInfo(available=False)
nv = hw.NVIDIAInfo(available=True, gpus=[hw.NVIDIAGPU(name="RTX 4090", vram_gb=24)])
assert hw.recommend_lane(cpu, amd, nv) == "trl_local"
def test_recommend_lane_falls_back_to_cpu():
"""The default — CPU is always the universal fallback."""
cpu = hw.CPUInfo()
amd = hw.AMDInfo(available=False)
nv = hw.NVIDIAInfo(available=False)
assert hw.recommend_lane(cpu, amd, nv) == "trl_cpu"
def test_recommend_lane_treats_empty_gpu_list_as_unavailable():
"""available=True with no GPUs should still skip the lane."""
cpu = hw.CPUInfo()
amd = hw.AMDInfo(available=True, gpus=[])
nv = hw.NVIDIAInfo(available=True, gpus=[])
assert hw.recommend_lane(cpu, amd, nv) == "trl_cpu"
# ---- probe_all (composite) --------------------------------------------------
def test_probe_all_returns_profile():
profile = hw.probe_all()
assert isinstance(profile, hw.HardwareProfile)
# The CPU half is always populated on a real host.
assert profile.cpu.available is True
# On a CPU-only laptop, the recommendation is trl_cpu.
assert profile.recommended_lane in {"trl_cpu", "axolotl_amd", "axolotl_cuda"}