docs: add CHANGELOG.md + comprehensive inline code comments

This commit is contained in:
2026-06-09 00:40:16 +02:00
parent cbd51745b8
commit c11241b6a8
4 changed files with 260 additions and 19 deletions
+148
View File
@@ -0,0 +1,148 @@
# Changelog — XPUSYS-Monitor-NG
All notable changes for the Windows-native AMD ROCm port are documented here.
This fork is based on [ComfyUI-XPUSYS-Monitor](https://github.com/allanmeng/ComfyUI-XPUSYS-Monitor)
v1.0.3 by allanmeng. The version below tracks deviations from that baseline.
---
## v0.1.0 — 2026-06-08
### Intent
The upstream AMD provider (`providers/amd.py`) relied on `rocm_smi_lib` — a
Linux-only Python package that wraps `librocm_smi64.so`. On Windows with AMD
ROCm, `pip install rocm_smi_lib` fails because the native `.so` library does
not exist. This port replaces every `rocm_smi` call with a Windows-native
alternative.
### Detection: `providers/__init__.py`
**Problem:** The upstream detection function `_is_amd_rocme()` accessed
`torch.version.roc` directly. On Windows AMD ROCm builds (tested with PyTorch
2.9.1+rocm7.2.1), the `roc` attribute **does not exist** on the
`torch.version` module — raising `AttributeError`. The outer `try/except`
caught it and returned `False`, causing the auto-detector to load
`NvidiaProvider` instead of `AMDProvider`. The GPU name fallback code after
the `roc` check was unreachable.
**Fix:** Replaced bare attribute access with `getattr(torch.version, 'roc',
None)`. Added a secondary signal `getattr(torch.version, 'hip', None)` for
HIP-based detection. Added a tertiary fallback scanning the GPU device name
(via `torch.cuda.get_device_name(0)`) for the markers `"amd"`, `"radeon"`,
or `"advanced micro devices"`.
### VRAM: `providers/amd.py` → `_read_vram()`
**Problem:** The upstream AMD provider used `rocm_smi.getMemFreeVdev(0)`,
`rocm_smi.getMemSizeVdev(0)`, and `rocm_smi.getMemUsedVdev(0)` for
driver-level VRAM reads. Without `rocm_smi_lib`, the fallback returned only
total VRAM from `torch.cuda.get_device_properties(0).total_memory`, leaving
`free` and `driver_used` as `0.0` — making the PRED predictor and VRAM
capsule unusable.
**Fix:** Replaced all three `rocm_smi` VRAM calls with
`torch.cuda.mem_get_info(device_index)`, which returns `(free_bytes,
total_bytes)` from the AMD driver on ROCm 6+ for Windows. Moved the call
inside a `try/except` with a fallback to `get_device_properties().total_memory`
if `mem_get_info` is unavailable. Added `torch.cuda.synchronize()` before
reads to force CUDA/HIP context creation (some ROCm builds defer context
init until the first GPU operation, returning zeros otherwise).
### GPU Load: `providers/amd.py` → `_read_gpu_load()`
**Problem:** The upstream used `rocm_smi.getGpuBusyVdev(0)`. No standard
Python-accessible equivalent exists on Windows AMD.
**Solution attempted — PDH (ctypes):** Added `_PdhQuery` to
`providers/_utils.py` using `ctypes` wrappers around `pdh.dll` to query
`\GPU Engine(*)\Utilization Percentage`. The wildcard counter path does not
aggregate correctly with `PdhGetFormattedCounterValue` (returns only the
first matching instance). This approach was disabled for AMD in favour of
typeperf.
**Solution adopted — typeperf:** Added `_TypeperfGpuQuery` to
`providers/_utils.py`. Uses Windows built-in `typeperf.exe` (available since
Vista) with the same counter path `\GPU Engine(*)\Utilization Percentage`.
Output is CSV; we parse columns after the timestamp and take `max()` across
all engine instances. Averaging would dilute the signal (hundreds of engine
columns including idle video/copy/timer). `max()` correctly reflects the
busiest engine (typically 3D or Compute during a ComfyUI workflow). At idle
all engines report ~0%, so the capsule drops cleanly.
**Attempted — amdsmi:** Added `_AmdSmiGpuQuery` to `providers/_utils.py`
using the official AMD SMI Python library (`pip install amdsmi`). On Windows
the library searches for `libamd_smi.so` (a Linux shared object) at
`D:\opt\rocm\lib\`, which does not exist on the tested configuration. The
class logs a single info line and gracefully skips if `amdsmi` is not
installed or fails to load.
### GPU Frequency / Temperature / Power: `providers/amd.py`
**Problem:** The upstream used `rocm_smi.getSingleClockSpeed(0)`,
`rocm_smi.getTempVdev(0)`, `rocm_smi.getPowerVdev(0)`, and
`rocm_smi.getPowerCapVdev(0)` for clock speed, temperature, and power draw.
The AMD Windows WDDM driver on the RX 9070 XT does not register these
performance counters through any standard Python-accessible interface.
**Resolution:** All three return sentinel values matching the `GPUSnapshot`
contract defaults — `0.0` for frequency, `-1.0` for temperature,
`(-1.0, 0.0, False)` for power. The frontend displays these as unavailable
(`--` / greyed out), identical behaviour to when the Intel provider cannot
open Level Zero handles or the NVIDIA provider cannot reach pynvml.
### Shared Utilities: `providers/_utils.py` (new file)
**Problem:** The upstream AMD provider imported system-level CPU and RAM
utility functions (`_get_cpu_info`, `_read_cpu_ram_stats`,
`_read_commit_charge`) from `providers/intel.py`. This created a spurious
dependency on the Intel Level Zero provider code for non-Intel users.
**Fix:** Extracted these three functions plus `_is_admin()` into a new shared
module `providers/_utils.py`. Also relocated `_PdhQuery`, `_TypeperfGpuQuery`,
and `_AmdSmiGpuQuery` into the same module. Both `amd.py` and `nvidia.py`
now import from `_utils.py` instead of `intel.py`. The `intel.py` module is
no longer needed unless the Intel provider is loaded (auto-detection
fallback path).
### NVIDIA Provider: `providers/nvidia.py`
**Change:** Updated import path from `from .intel import ...` to
`from ._utils import ...`. No functional change — identical utility
functions.
### Frontend: `web/xpu_monitor.js`
**Problem:** The `__init__.py` declares `WEB_DIRECTORY = "./web"` which
tells ComfyUI to serve the JavaScript toolbar extension from a `web/`
subdirectory. This directory was not included in the initial workspace,
causing the toolbar capsules to not render.
**Fix:** Added `web/xpu_monitor.js` (56 KB, identical to upstream v1.0.3).
The file is the full JavaScript frontend that renders the seven-capsule
status bar, handles WebSocket updates from the backend, and provides the
VRAM predictor UI. No modifications were made.
### Dependencies: `requirements.txt`
**Change:** `rocm_smi_lib` commented out with an explanatory note. No
replacement dependency added — VRAM reads use `torch.cuda` (bundled with
the ROCm PyTorch installation), GPU load reads use `typeperf` (Windows
built-in), and CPU/RAM reads use `psutil` (already required by the upstream).
### Project Metadata: `pyproject.toml`
**Changes:**
- Repository URL updated to `https://github.com/forkless/XPUSYS-Monitor-NG`
- Display name set to `XPUSYS-Monitor-NG`
- Description updated to reflect the POC nature
- Publisher ID set to `forkless`
### Documentation
- `README.md` — rewritten for the fork with POC context, status table,
relationship to upstream, tested hardware, support disclaimer, MIT license
- `AMD.md` — detailed technical summary of every change, intent, and end
result table
- `LICENSE.md` — MIT license (matches upstream)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 forkless
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+43 -13
View File
@@ -277,18 +277,34 @@ class _PdhQuery:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# typeperf-based GPU utilisation fallback # typeperf-based GPU utilisation fallback
# #
# Uses Windows built-in typeperf.exe (available since Vista) to query # ADDED for Windows-native AMD ROCm support (no rocm_smi_lib).
# the \GPU Engine(*)\Utilization Percentage performance counter.
# #
# typeperf avoids the quoting/escaping headaches of PowerShell -Command # Two approaches were attempted before settling on typeperf:
# and is available on every Windows system with WDDM drivers.
# #
# Output format (CSV): # 1. PDH ctypes — _PdhQuery (above). Uses PdhAddEnglishCounterW with
# "(PDH-CSV 4.0) (...)", "\\COMPUTER\GPU Engine(*)\Utilization Percentage" # wildcard path "\GPU Engine(*)\Utilization Percentage". The wildcard
# "date time", "val1,val2,val3,..." # expands to hundreds of per-process engine instances, but
# PdhGetFormattedCounterValue on a wildcard handle returns only the
# first matching instance, not an aggregate. This approach is disabled
# for AMD in favour of typeperf.
# #
# We parse the second line, split the comma-separated values, and # 2. typeperf — This class (below). Windows built-in CLI tool that
# average them to get total GPU utilisation. # accepts the same counter path and returns CSV with one column per
# engine instance. Every column after the timestamp is a separate
# engine value. We parse all columns and take max()—not average—because
# with hundreds of engines (video decode, copy, timer, security, etc.)
# all reporting 0 at idle, averaging dilutes the real signal from the
# few active 3D/Compute engines during a workflow.
#
# 3. amdsmi — _AmdSmiGpuQuery (further below). Official AMD SMI
# library. Gracefully skipped on Windows because the PyPI package
# searches for libamd_smi.so (Linux-only).
#
# Output format (typeperf CSV):
# Line 1: "(PDH-CSV 4.0)","\\PC\GPU Engine(pid_..._engtype_3D)\...", ...
# Line 2: "date time","0.000000","1.299634","0.000000", ...
#
# REPLACES: upstream rocm_smi.getGpuBusyVdev(0)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
import csv as _csv import csv as _csv
@@ -381,12 +397,26 @@ class _TypeperfGpuQuery:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# amdsmi-based GPU utilisation (official AMD SMI library) # amdsmi-based GPU utilisation (official AMD SMI library)
# #
# Uses the AMD SMI Python package which ships with ROCm. Talks directly # ADDED for Windows-native AMD ROCm support.
# to the AMD driver — not through WDDM. Reports real GPU engine utilisation #
# (GFX, MM, MEM) as percentages 0100%. # The official AMD SMI Python package (pip install amdsmi) provides direct
# driver-level GPU metrics — engine utilisation (GFX, MM, MEM), temperature,
# power, clock speed — without going through WDDM performance counters.
#
# On Windows, the PyPI package's ctypes wrapper searches for the native
# library at a hardcoded Linux path (libamd_smi.so via ctypes.CDLL).
# Windows DLLs use different filenames and search paths, so the import
# fails with KeyError: 'libamd_smi.so' on a standard Windows ROCm install.
#
# This class uses try/except ImportError to gracefully skip when the
# package is not installed or the native library cannot be loaded. No
# crash, no stack trace — just a single info-line in the log.
#
# If AMD releases an official Windows-compatible amdsmi wheel in the
# future, this class will activate automatically without code changes.
# #
# Install: pip install amdsmi # Install: pip install amdsmi
# Requires: ROCm 6+ (user has ROCm 7.2) # Requires: ROCm 6+ (ROCm 7.2 on the tested configuration)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
class _AmdSmiGpuQuery: class _AmdSmiGpuQuery:
+48 -6
View File
@@ -122,8 +122,20 @@ class AMDProvider(BaseGPUProvider):
""" """
Return (free_gb, total_gb, driver_used_gb) via torch.cuda.mem_get_info. Return (free_gb, total_gb, driver_used_gb) via torch.cuda.mem_get_info.
REPLACES: upstream rocm_smi.getMemFreeVdev(0),
rocm_smi.getMemSizeVdev(0),
rocm_smi.getMemUsedVdev(0)
torch.cuda.mem_get_info() returns (free_bytes, total_bytes) from
the AMD HIP driver on ROCm 6+ for Windows. This is the same function
used by NVIDIA CUDA — AMD ROCm's HIP runtime implements the same
CUDA API surface, so it works without any AMD-specific library.
Falls back to get_device_properties if mem_get_info is unavailable. Falls back to get_device_properties if mem_get_info is unavailable.
Forces CUDA context init to ensure device queries succeed. Forces CUDA context init (torch.cuda.synchronize) before reading;
some ROCm builds defer HIP context creation until the first GPU
operation, and mem_get_info() returns (0, 0) without an active
context.
""" """
if not self._torch_ok: if not self._torch_ok:
return 0.0, 0.0, 0.0 return 0.0, 0.0, 0.0
@@ -187,8 +199,20 @@ class AMDProvider(BaseGPUProvider):
""" """
Return GPU utilisation %. Return GPU utilisation %.
Tries amdsmi (official AMD SMI, bypasses WDDM). REPLACES: upstream rocm_smi.getGpuBusyVdev(0)
Falls back to typeperf (WDDM counters, best-effort).
Two-layer fallback chain:
1. amdsmi — official AMD SMI library (pip install amdsmi).
Bypasses WDDM, talks directly to the AMD driver.
Gracefully skipped on Windows because the PyPI
package searches for libamd_smi.so (Linux-only).
2. typeperf — Windows built-in (available since Vista).
Reads \\GPU Engine(*)\\Utilization Percentage
via WDDM performance counters. Returns CSV with
one column per engine instance (3D, Compute,
Copy, Video, Timer, etc.). We use max() across
all engines — averaging dilutes the signal
across hundreds of idle engine types.
""" """
if self._as_gpu_ok: if self._as_gpu_ok:
return self._as_gpu.read_gpu_utilization() return self._as_gpu.read_gpu_utilization()
@@ -200,7 +224,12 @@ class AMDProvider(BaseGPUProvider):
""" """
GPU core frequency in MHz. GPU core frequency in MHz.
Unavailable on Windows without vendor driver API. REPLACES: upstream rocm_smi.getSingleClockSpeed(0)
The AMD WDDM driver on Windows does not expose GPU core clock
through any standard Python-accessible interface (no PDH counter,
no WMI class, no torch.cuda equivalent). Returns 0 (unavailable
sentinel matching the GPUSnapshot contract default).
""" """
return 0.0 return 0.0
@@ -208,7 +237,14 @@ class AMDProvider(BaseGPUProvider):
""" """
GPU core temperature in C. GPU core temperature in C.
Unavailable on Windows without vendor driver API. REPLACES: upstream rocm_smi.getTempVdev(0)
The AMD WDDM driver on the tested configuration (RX 9070 XT,
ROCm 7.2, Windows) does not register a GPU temperature performance
counter. Tested: typeperf -q "GPU Adapter" returned "object not
found". Returns -1 (unavailable sentinel). Some AMD cards on newer
driver versions or different Windows builds may expose this through
WMI or PDH — this is hardware/driver-dependent.
""" """
return -1.0 return -1.0
@@ -216,7 +252,13 @@ class AMDProvider(BaseGPUProvider):
""" """
Return (power_w, tgp_w, power_available). Return (power_w, tgp_w, power_available).
Unavailable on Windows without vendor driver API. REPLACES: upstream rocm_smi.getPowerVdev(0) and
rocm_smi.getPowerCapVdev(0)
GPU power monitoring is not exposed through Windows standard APIs
on the tested AMD driver. Returns (-1.0, 0.0, False) — the
GPUSnapshot power_available=False tells the frontend to grey out
the PWR capsule.
""" """
return -1.0, 0.0, False return -1.0, 0.0, False