From aeeea8a170f084d132fb342e115068951a0a7259 Mon Sep 17 00:00:00 2001 From: forkless Date: Tue, 9 Jun 2026 00:15:01 +0200 Subject: [PATCH] feat: add amdsmi probe + typeperf max fix --- providers/_utils.py | 95 +++++++++++++++++++++++++++++++++++++++++++++ providers/amd.py | 19 ++++----- 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/providers/_utils.py b/providers/_utils.py index 19006fc..613166e 100644 --- a/providers/_utils.py +++ b/providers/_utils.py @@ -378,6 +378,100 @@ class _TypeperfGpuQuery: return None +# --------------------------------------------------------------------------- +# amdsmi-based GPU utilisation (official AMD SMI library) +# +# Uses the AMD SMI Python package which ships with ROCm. Talks directly +# to the AMD driver — not through WDDM. Reports real GPU engine utilisation +# (GFX, MM, MEM) as percentages 0–100%. +# +# Install: pip install amdsmi +# Requires: ROCm 6+ (user has ROCm 7.2) +# --------------------------------------------------------------------------- + +class _AmdSmiGpuQuery: + """GPU utilisation reader via official AMD SMI Python library.""" + + def __init__(self): + self._handle = None + self._initialized = False + self._ok = False + + def init(self) -> bool: + try: + import amdsmi as _smi + _smi.amdsmi_init() + self._smi = _smi + handles = _smi.amdsmi_get_processor_handles() + if not handles: + logger.warning("XPUSYSMonitor: amdsmi — no processor handles.") + _smi.amdsmi_shut_down() + return False + + self._handle = handles[0] + self._initialized = True + + # Test read to confirm it works + try: + activity = _smi.amdsmi_get_gpu_activity(self._handle) + logger.info( + f"XPUSYSMonitor: amdsmi GPU activity test — " + f"{activity!r}" + ) + except Exception as exc: + logger.warning( + f"XPUSYSMonitor: amdsmi activity test failed — {exc}" + ) + + self._ok = True + logger.info("XPUSYSMonitor: amdsmi GPU counters OK.") + return True + + except ImportError: + logger.info( + "XPUSYSMonitor: amdsmi not installed — " + "run `pip install amdsmi` to enable AMD SMI monitoring." + ) + return False + except Exception as exc: + logger.warning(f"XPUSYSMonitor: amdsmi init error — {exc}") + try: + self._smi.amdsmi_shut_down() + except Exception: + pass + return False + + def read_gpu_utilization(self) -> float: + """Return GPU utilisation % via amdsmi (GFX engine).""" + if not self._ok or self._handle is None: + return 0.0 + try: + activity = self._smi.amdsmi_get_gpu_activity(self._handle) + + # amdsmi_get_gpu_activity returns engine utilisation. + # The exact return type depends on the version. Try common + # access patterns: attribute, dict key, or index. + if hasattr(activity, 'gfx'): + return float(activity.gfx) + if isinstance(activity, dict): + return float(activity.get('gfx', activity.get('GFX', 0.0))) + if isinstance(activity, (list, tuple)): + return float(activity[0]) if activity else 0.0 + + # Last resort: try treating it as a number directly + return float(activity) + except Exception: + return 0.0 + + def close(self) -> None: + if self._initialized: + try: + self._smi.amdsmi_shut_down() + except Exception: + pass + self._ok = False + + __all__ = [ "_is_admin", "_get_cpu_info", @@ -385,4 +479,5 @@ __all__ = [ "_read_commit_charge", "_PdhQuery", "_TypeperfGpuQuery", + "_AmdSmiGpuQuery", ] diff --git a/providers/amd.py b/providers/amd.py index d4d6167..de91a8f 100644 --- a/providers/amd.py +++ b/providers/amd.py @@ -18,7 +18,7 @@ import sys from typing import Tuple from .base import BaseGPUProvider, GPUSnapshot -from ._utils import _get_cpu_info, _read_cpu_ram_stats, _TypeperfGpuQuery, _is_admin +from ._utils import _get_cpu_info, _read_cpu_ram_stats, _TypeperfGpuQuery, _AmdSmiGpuQuery, _is_admin logger = logging.getLogger("XPUSYSMonitor") @@ -52,11 +52,13 @@ class AMDProvider(BaseGPUProvider): self._check_torch() self._check_psutil() - # Windows GPU utilisation — typeperf (primary, reliable CSV output) + # Windows GPU utilisation — amdsmi (official) -> typeperf (fallback) # PDH has wildcard-counter issues with AMD drivers, so skip it. - self._pdh_ok = False # PDH disabled for AMD (wildcard handling unreliable) + self._pdh_ok = False + self._as_gpu = _AmdSmiGpuQuery() + self._as_gpu_ok = self._as_gpu.init() self._tp_gpu = _TypeperfGpuQuery() - self._tp_gpu_ok = self._tp_gpu.init() + self._tp_gpu_ok = self._tp_gpu.init() if not self._as_gpu_ok else False # BaseGPUProvider.__init__ starts the polling thread — call last super().__init__(interval_ms=interval_ms) @@ -185,12 +187,11 @@ class AMDProvider(BaseGPUProvider): """ Return GPU utilisation %. - Tries PDH API first (sub-millisecond, ctypes). - Falls back to PowerShell Get-Counter if PDH is unavailable - (slower ~100-300ms but works on any Windows WDDM driver). + Tries amdsmi (official AMD SMI, bypasses WDDM). + Falls back to typeperf (WDDM counters, best-effort). """ - if self._pdh_ok: - return self._pdh.read_gpu_utilization() + if self._as_gpu_ok: + return self._as_gpu.read_gpu_utilization() if self._tp_gpu_ok: return self._tp_gpu.read_gpu_utilization() return 0.0