Files
nvcurve/nvcurve/hal/gpu.py
T
ARIA 930e56bd07 Clean up LSP diagnostics across backend and frontend
Backend (nvcurve/):
- hal/fans.py, hal/limits.py, hal/gpu.py: replace conditional pynvml
  imports with the established 'pynvml: Any = _pynvml_import' pattern
  (fixes ~50 'possibly unbound' errors); type the result dicts; guard
  query_interface() results; explicit uuid/pci-bus parsing (int, hex
  convention documented); modernize Optional[T] -> T | None
- cli.py: fix 'curve_state' possibly-unbound and snap_path None handling
  in cmd_setup; wrap unchecked int()/open()/makedirs() calls in
  try/except with clean CLI errors; add module logger for silent
  except-pass blocks; raise ... from exc; fix unused loop vars and
  set-comprehension
- hal/snapshot.py: filepath: str | None; wrap all file ops; sorted
  imports; remove unused CT_POINTS import
- daemon.py: extract 0o666 to _SOCKET_MODE constant (intentional for
  /run sockets) with nosemgrep
- server.py: nosemgrep for Python 3.7-compat false positive (project
  requires >= 3.12); log previously-swallowed exception
- profiles/native.py, profiles/apply.py: wrap file ops and int(k)
  profile-key parsing; sorted imports; modernize typing

Frontend (frontend/src):
- Add .js extensions to all relative imports (standard TS-ESM; Vite
  resolves .js -> .ts)
- React.FormEvent (deprecated in React 19 types) -> React.SubmitEvent
- catch (e: any) -> catch (e: unknown) + instanceof Error narrowing
- React-hooks: move ref writes from render into effects; convert
  viewport reset to render-phase state adjustment; split
  selectPoint(index, multi) into selectPoint + togglePoint (no flag
  argument); remove non-null assertion
- Static inline styles -> Tailwind classes (dynamic positioning/cursor
  styles kept)
- Remove non-standard 'container' option from scrollIntoView (browsers
  ignore unknown options) which had orphaned a @ts-expect-error
- Object.fromEntries for Map -> Record conversion

Tooling:
- .gitignore: ignore .codegraph/ local tool data

Verified: tsc --noEmit, vite production build, python imports, and
full LSP scan (0 errors/warnings in both projects).
2026-09-08 23:57:30 +02:00

111 lines
3.6 KiB
Python

"""GPU discovery and initialization."""
import contextlib
import ctypes
import logging
import sys
from typing import Any
from ..nvapi.bootstrap import query_interface
from ..nvapi.constants import FUNC
from ..nvapi.types import GpuInfo
log = logging.getLogger("nvcurve.hal.gpu")
def init_nvapi() -> None:
"""Initialize NvAPI. Must be called before any GPU operations."""
init_fn = query_interface(FUNC["Initialize"], nargs=0)
if not init_fn or init_fn() != 0:
raise RuntimeError("NvAPI_Initialize failed")
def enumerate_gpus() -> tuple[ctypes.Array, int]:
"""Return (gpu_handles_array, count). Exits if no GPUs found."""
gpus = (ctypes.c_void_p * 64)()
ngpu = ctypes.c_int32()
enum_fn = query_interface(FUNC["EnumPhysicalGPUs"])
if enum_fn is None:
raise RuntimeError("NvAPI function EnumPhysicalGPUs not available")
enum_fn(ctypes.byref(gpus), ctypes.byref(ngpu))
if ngpu.value == 0:
print("No NVIDIA GPUs found")
sys.exit(1)
return gpus, ngpu.value
def get_gpu_name(gpu) -> str:
"""Return the full name string for a GPU handle."""
name_buf = ctypes.create_string_buffer(256)
fn = query_interface(FUNC["GetFullName"])
if fn is None:
raise RuntimeError("NvAPI function GetFullName not available")
fn(gpu, name_buf)
return name_buf.value.decode(errors="replace")
def discover_gpus() -> list[GpuInfo]:
"""Initialize NvAPI and NVML, and return a list of GpuInfo for all physical GPUs."""
init_nvapi()
gpus, count = enumerate_gpus()
infos = []
try:
import pynvml as _pynvml
_pynvml.nvmlInit()
except Exception:
_pynvml = None
# Aliased as Any so attribute access is not flagged when the import failed.
pynvml: Any = _pynvml
for i in range(count):
name = get_gpu_name(gpus[i])
uuid = None
pci_bus_id = None
if pynvml is not None:
try:
handle = pynvml.nvmlDeviceGetHandleByIndex(i)
raw_uuid = pynvml.nvmlDeviceGetUUID(handle)
# NVML might return bytes
if isinstance(raw_uuid, bytes):
uuid = raw_uuid.decode("utf-8", errors="ignore")
elif raw_uuid is not None:
uuid = str(raw_uuid)
pci_info = pynvml.nvmlDeviceGetPciInfo(handle)
# Parse something like "00000000:01:00.0" -> bus is 1.
# PCI bus numbers are hex by convention (pynvml's field is an
# int; the str/bytes branches are defensive).
bus = pci_info.bus
if isinstance(bus, bytes):
pci_bus_id = int(bus.decode("utf-8", errors="ignore"), 16)
elif isinstance(bus, str):
pci_bus_id = int(bus, 16)
else:
pci_bus_id = int(bus)
except Exception as exc:
log.debug("NVML query for GPU %d failed: %s", i, exc)
infos.append(GpuInfo(name=name, index=i, uuid=uuid, pci_bus_id=pci_bus_id))
if pynvml is not None:
with contextlib.suppress(Exception):
pynvml.nvmlShutdown()
return infos
def get_gpu(index: int = 0):
"""Initialize NvAPI, enumerate GPUs, and return the handle for `index`.
Also returns the GPU name as a convenience.
Returns (handle, name).
"""
init_nvapi()
gpus, count = enumerate_gpus()
if index >= count:
print(f"GPU index {index} out of range (found {count} GPU(s))")
sys.exit(1)
gpu = gpus[index]
name = get_gpu_name(gpu)
return gpu, name