Pull requests / #2273

#2273 serve: an AMD card's load, VRAM, temperature and power on Windows

open · @unimo-chan · 0 コメント · GitHub で見る

Setup & installServer & APIAMD / HIPModels & quantsWindowsLinux

本文

## What this changes

On Windows an AMD card has no NVML and no amdgpu sysfs, so `serve/telemetry.py` had nothing to read and the
dashboard said *"no GPU load or VRAM readings for AMD cards on Windows yet"* (the `gpu_note` below, from #1380).
This adds a reader built from what the machine does have — all of it through `ctypes`, no package:

- **DXGI** (`CreateDXGIFactory1` → `EnumAdapters1` → `GetDesc1`): the card's name, its dedicated memory total,
  and its LUID. The card is the one whose vendor ID is 0x1002, so an NVIDIA display card in the same PC is not
  mistaken for the card the engine runs on.
- **Performance counters** (`PdhAddEnglishCounterW`, English counter names, instances picked by that LUID) — the
  same counters Task Manager's GPU column reads: `\GPU Adapter Memory(luid_…)\Dedicated Usage` for the VRAM in
  use, `\GPU Engine(*luid_…*)\Utilization Percentage` for load (the busiest engine, which is what Task Manager
  shows; measured on this machine: 93% on the compute engine where the `Running Time` counter beside it computed
  94% over the same window). DXGI's own `QueryVideoMemoryInfo` is per-process and reported 0 while the engine
  held 19 GiB, so it is not used.
- **ADLX** (`amdadlx64.dll`, installed with the Adrenalin driver) for temperature and power: the ADLX services
  reached through their vtable slots (ADLX interfaces are plain C++ vtables, not COM), the GPU found through the
  ADL mapping and matched to the name DXGI reported. On this driver plain `ADLXInitialize` answers an empty GPU
  list, so `ADLXInitializeWithIncompatibleDriver2` is the entry that works; `GPUPower` answers NOT_SUPPORTED, so
  power falls back to `GPUTotalBoardPower`. Initialization is once per process (a second
  `GetPerformanceMonitoringServices` hangs, measured).
- `gpu_reader(amd=True)` returns this on Windows when there is no amdgpu sysfs; the Linux path and the NVIDIA
  path are untouched, and a card none of the three reaches still gets a word why instead of empty tiles.

Enumeration, counters and property reads only — no device, no context: it takes no VRAM from the engine and
cannot stall it.

## What it leaves alone

- **No PCIe readings at all** — neither the link nor the traffic. The card's own PCI devnode describes the switch
  inside an RX 7000 (measured: Gen4 x16 reported by the endpoint while the card sits in a Gen3 x8 slot on a
  B450), and the three devices above it report nothing, so the host-facing link is one Windows cannot name; the
  tile stays blank there rather than showing the card's own capability (#1734, #2137). For the traffic: PDH has
  no PCIe counter set and ADLX 1.5's headers name no bus metric — that half is the engine's own byte counter, in #2272.
- **No new dependency.** `adlxpybind` ships cp310–cp313 `win_amd64` wheels and no sdist, so a `pip install` fails
  on any other Python; `ctypes` needs nothing and the file already uses it for NVML.
- Linux, NVIDIA, the engine, and every other file.

## Where this overlaps

#1871 (dashboard readings for AMD cards), #1152 (Windows AMD telemetry, with an Arabic UI), #1224 (ADLX through
`adlxpybind`) and #1434 are all open in this area, and I read them before writing this. This one is the
ctypes-only route: no install step, and it is the ADLX *fallback chain* (`GPUPower` → `GPUTotalBoardPower`) and
the LUID-picked performance counters that make the numbers appear on a driver where the direct calls answer
NOT_SUPPORTED. If any of those PRs lands first, this should be rebased on it rather than shipped beside it.

## How I checked

RX 7900 XT, driver 32.0.23033.1002, ADLX 1.5.0.124, Windows 11, Python 3.12.10, with the engine holding the
model:

- idle: 42–43 °C, 67–82 W, util 0, 19248 MiB of 20418 MiB in use (the engine's own allocation).
- during one chat request (400 completion tokens in 9.1 s): util 90–95 %, power up to 292 W, 47 °C.
- the ADLX path is the only one that answers temperature and power here: ADL (`atiadlxx`) enumerates the card but
  answers −1/−5 for every sensor call, and no performance counter or WMI class reports them.
- `python -m unittest serve.test_server.AmdTelemetry serve.test_server.TelemetryNoDisks
  serve.test_server.SharingTheGpu serve.test_server.DesktopVramNote serve.test_server.WebApp
  serve.test_server.PrometheusMetrics serve.test_server.LiveRate`: **42 tests, OK** (Python 3.12.10, on main at
  `a0116b2`). Full `serve.test_server` on this branch: **308 tests, OK** in 101 s (Windows 11, Python 3.12.10), nothing skipped.
- Cross-checked against AMD's own client for the same library — the `adlxpybind` 1.4.6 wheel, unpacked rather than
  installed: it answers the same 43.0 °C and the same 19248 MiB, and the same `GPUPower` 0.0 with
  `GPUTotalBoardPower` 64.0. The temperature and VRAM figures are not an artifact of reading the vtable by hand.

One existing test changed: the sysfs link test built its reader with `gpu_reader(0, amd=True)`, which on Windows
is now the DXGI reader; it constructs `telemetry._Amd(0)` directly instead. Its assertions are unchanged.

Not tested: an RX 6000 or an older driver, a machine without `amdadlx64.dll` (the reader returns nothing and the
existing note is shown), and a second card.

関連リンク

インストール・モデル・リリースへの站内リンク。