gpu: scope per-engine VRAM to the engine's own card (fix Radeon showing NVIDIA)

The context-free VRAM query used gpu_memory(), which (via NVML/nvidia-smi)
reports every physical NVIDIA card regardless of CUDA_VISIBLE_DEVICES. So the
Vulkan/Radeon engine (CUDA_VISIBLE_DEVICES="") showed the NVIDIA 3090's VRAM in
its status instead of its own AMD card.

Switch the per-engine status poll (api/app.py) and the capability probe
(broker/capabilities.py) to visible_gpu_memory(), which honours
CUDA_VISIBLE_DEVICES (index OR UUID; empty -> no CUDA cards). Add
gpu_query.amd_gpu_memory() (amdgpu sysfs, driver-free) as the fallback so the
Radeon engine reports its real card; capabilities keeps its torch-loaded guard so
the torch-free front still enumerates the whole node. Bumps to 0.1.29.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RdMufYvtTbtGDWsiZVoXce
parent 9c150b3e
......@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here.
__version__ = "0.1.28"
__version__ = "0.1.29"
# Configure the CUDA caching allocator BEFORE torch is imported anywhere.
# expandable_segments lets the allocator return freed pages to the driver even
......
......@@ -250,11 +250,14 @@ def _cached_engine_vram():
return _ENGINE_VRAM_CACHE["vram"]
vram = None
try:
# Query VRAM context-free (pynvml → nvidia-smi → torch-if-inited) so an
# engine that never loads a torch model (the GGUF/llama.cpp engine) doesn't
# pin a ~256 MiB CUDA context just to answer this health poll.
from codai.models.gpu_query import gpu_memory
gpus = gpu_memory()
# Query VRAM context-free so an engine that never loads a torch model (the
# GGUF/llama.cpp engine) doesn't pin a ~256 MiB CUDA context just to answer
# this poll. Scope to THIS engine's cards: visible_gpu_memory() honours
# CUDA_VISIBLE_DEVICES, so the Vulkan/Radeon engine (CUDA_VISIBLE_DEVICES="")
# reports nothing here and falls back to its amdgpu card via sysfs — instead
# of leaking the NVIDIA card's VRAM.
from codai.models.gpu_query import visible_gpu_memory, amd_gpu_memory
gpus = visible_gpu_memory() or amd_gpu_memory()
if gpus:
used = free = total = 0
devs = []
......@@ -267,7 +270,7 @@ def _cached_engine_vram():
devs.append({"index": i, "name": _names[i],
"free": round(f / 1e9, 2), "total": round(t / 1e9, 2)})
n = len(devs)
label = (_names.get(0, "CUDA") if n == 1 else f"{n}× CUDA")
label = (devs[0]["name"] if n == 1 else f"{n}× GPU")
vram = {"used": round(used / 1e9, 2), "free": round(free / 1e9, 2),
"total": round(total / 1e9, 2), "gpu": label,
"devices": devs, "device_count": n}
......
......@@ -49,14 +49,18 @@ def build_hardware_summary() -> Dict[str, Any]:
total_vram_mb = 0
available_vram_mb = 0
# Prefer a context-free query (pynvml → nvidia-smi → torch-if-inited). This
# never creates a CUDA primary context, so probing capabilities in an engine
# that hasn't loaded a torch model (the GGUF/llama.cpp engine) doesn't pin a
# ~256 MiB context. It also works in the torch-free front (NVML/nvidia-smi
# only — it imports torch solely if a context already exists in-process).
# In an ENGINE (torch already loaded), report THIS engine's cards via a
# context-free, CUDA_VISIBLE_DEVICES-scoped query — so the probe never pins a
# ~256 MiB CUDA context (the old torch.cuda.mem_get_info path did). The nvidia
# engine yields its NVIDIA card; the Vulkan/Radeon engine (CUDA_VISIBLE_DEVICES
# ="") yields nothing and falls through to the whole-node path below, as before.
# The torch-free FRONT also falls through to the whole-node path (it must stay
# torch-free and report every physical card).
import sys as _sys
try:
from codai.models.gpu_query import gpu_memory
_gpus = gpu_memory()
if "torch" in _sys.modules:
from codai.models.gpu_query import visible_gpu_memory
_gpus = visible_gpu_memory()
if _gpus:
for d in _gpus:
device_total_mb = int(d["total"] / (1024 * 1024))
......
......@@ -149,6 +149,36 @@ def gpu_memory() -> Optional[List[Dict[str, Any]]]:
return _via_pynvml() or _via_nvidia_smi() or _via_torch_if_inited()
def amd_gpu_memory() -> Optional[List[Dict[str, Any]]]:
"""Per-device VRAM for amdgpu cards via sysfs — driver-free, no context.
Reads ``/sys/class/drm/card*/device/mem_info_vram_{total,used}`` (present only
for amdgpu). Returns ``{index, name, uuid, free, total}`` (bytes) keyed by the
sysfs card index, or ``None`` when no amdgpu card is present. Used by the
Vulkan/Radeon engine (which has ``CUDA_VISIBLE_DEVICES=""``, so the NVML/CUDA
queries report nothing for it)."""
import glob
import re
out: List[Dict[str, Any]] = []
for total_path in sorted(glob.glob('/sys/class/drm/card*/device/mem_info_vram_total')):
used_path = total_path.replace('mem_info_vram_total', 'mem_info_vram_used')
try:
with open(total_path) as f:
total = int(f.read().strip())
except Exception:
continue
try:
with open(used_path) as f:
used = int(f.read().strip())
except Exception:
used = 0
m = re.search(r'card(\d+)', total_path)
idx = int(m.group(1)) if m else len(out)
out.append({"index": idx, "name": "AMD GPU", "uuid": None,
"free": max(0, total - used), "total": total})
return out or None
def _visible_tokens() -> Optional[Set[str]]:
"""Parse ``CUDA_VISIBLE_DEVICES`` into a set of selector tokens (physical
index strings and/or ``GPU-<uuid>`` strings).
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment