Download tests/test_hw_diagnostics.py from PYTHAI/mindXtrain: direct link, hf CLI and curl.
- Browser
- Download file 9.57 kB
-
https://huggingface.co/PYTHAI/mindXtrain/resolve/main/tests/test_hw_diagnostics.py
- Command line
-
hf download hf://PYTHAI/mindXtrain/tests/test_hw_diagnostics.py
-
curl -L -o test_hw_diagnostics.py https://huggingface.co/PYTHAI/mindXtrain/resolve/main/tests/test_hw_diagnostics.py
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"} | |