Files
XPUSYS-Monitor-NG/providers/__init__.py
T

174 lines
6.1 KiB
Python

"""
providers/__init__.py — Provider registry and auto-detection factory.
Usage:
from .providers import auto_detect_provider, BaseGPUProvider, GPUSnapshot
provider = auto_detect_provider(interval_ms=1000)
snap = provider.get_snapshot()
Detection strategy — align with ComfyUI's own device selection:
ComfyUI uses torch.cuda.is_available() for NVIDIA and torch.xpu.is_available()
for Intel. We follow the same signals so the monitor always tracks whichever
device ComfyUI is actually running on.
Detection order:
1. torch.cuda.is_available() + torch.version.roc → AMDProvider (ROCm)
2. torch.cuda.is_available() → NvidiaProvider (NVIDIA)
3. torch.xpu.is_available() → IntelProvider (Intel Arc)
4. ze_loader.dll present → IntelProvider (fallback)
5. pynvml available → NvidiaProvider (fallback)
6. Last resort → IntelProvider (limited)
Fallback tiers 4-5 cover edge cases where torch is not yet imported or the
user is running a non-standard environment without torch.
"""
import logging
from .base import BaseGPUProvider, GPUSnapshot
logger = logging.getLogger("XPUSYSMonitor")
def auto_detect_provider(interval_ms: int = 1000) -> BaseGPUProvider:
"""
Detect the available GPU hardware and return the appropriate provider.
Primary strategy: mirror ComfyUI's own torch-based device selection so the
monitor always tracks the same device that ComfyUI is running inference on.
Fallback strategy: raw driver/library probing for non-standard environments.
"""
# --- Primary: follow torch (mirrors ComfyUI model_management.py) ---
if _detect_nvidia_torch():
# torch.cuda available — determine if NVIDIA or AMD via torch.version.roc
if _is_amd_rocme():
# torch.version.roc is not None → AMD ROCm
logger.info("XPUSYSMonitor: torch.cuda + ROCm — using AMDProvider.")
from .amd import AMDProvider
return AMDProvider(interval_ms=interval_ms)
else:
# torch.version.roc is None → NVIDIA
logger.info("XPUSYSMonitor: torch.cuda (NVIDIA) — using NvidiaProvider.")
from .nvidia import NvidiaProvider
return NvidiaProvider(interval_ms=interval_ms)
if _detect_intel_torch():
logger.info("XPUSYSMonitor: torch.xpu available — using IntelProvider.")
from .intel import IntelProvider
return IntelProvider(interval_ms=interval_ms)
# --- Fallback: raw driver probing (torch not imported yet / non-std env) ---
if _detect_intel_driver():
logger.info(
"XPUSYSMonitor: ze_loader.dll found (torch unavailable) — "
"using IntelProvider."
)
from .intel import IntelProvider
return IntelProvider(interval_ms=interval_ms)
if _detect_nvidia_driver():
logger.info(
"XPUSYSMonitor: NVIDIA driver found (torch unavailable) — "
"using NvidiaProvider."
)
from .nvidia import NvidiaProvider
return NvidiaProvider(interval_ms=interval_ms)
# --- Last resort ---
logger.warning(
"XPUSYSMonitor: no supported GPU detected — "
"falling back to IntelProvider (limited functionality)."
)
from .intel import IntelProvider
return IntelProvider(interval_ms=interval_ms)
# ---------------------------------------------------------------------------
# Primary detectors — torch-based (align with ComfyUI)
# ---------------------------------------------------------------------------
def _detect_nvidia_torch() -> bool:
"""Return True if torch has a working CUDA backend (mirrors ComfyUI)."""
try:
import torch
return torch.cuda.is_available()
except Exception:
return False
def _detect_intel_torch() -> bool:
"""Return True if torch has a working XPU backend (mirrors ComfyUI)."""
try:
import torch
return torch.xpu.is_available()
except Exception:
return False
# ---------------------------------------------------------------------------
# Fallback detectors — raw driver / library probing
# Used when torch is not yet available or in non-standard environments.
# ---------------------------------------------------------------------------
def _detect_intel_driver() -> bool:
"""Return True if Intel Level Zero runtime (ze_loader.dll) is present."""
import ctypes
try:
ctypes.WinDLL("ze_loader.dll")
return True
except OSError:
return False
def _detect_nvidia_driver() -> bool:
"""Return True if pynvml is installed and NVIDIA driver is reachable."""
try:
import pynvml
pynvml.nvmlInit()
count = pynvml.nvmlDeviceGetCount()
pynvml.nvmlShutdown()
return count > 0
except Exception:
return False
def _is_amd_rocme() -> bool:
"""
Check if torch.cuda is backed by AMD ROCm.
Primary signal: torch.version.roc is set (AMD ROCm PyTorch).
Secondary signal: torch.version.hip is set (HIP backend).
Fallback signal: GPU device name contains AMD/Radeon markers
(catches Windows ROCm builds that don't set version.roc).
Uses getattr() so missing attributes are handled cleanly
without raising AttributeError (some Windows ROCm builds
lack the `roc` attribute entirely).
Returns False for NVIDIA or standard CUDA.
"""
try:
import torch
# Primary: ROCm version string (may not exist on some Windows ROCm builds)
if getattr(torch.version, 'roc', None) is not None:
return True
# Secondary: HIP version string (alternative ROCm signal)
if getattr(torch.version, 'hip', None) is not None:
return True
# Fallback: check GPU name for AMD identifiers
name = torch.cuda.get_device_name(0).lower()
for marker in ["amd", "radeon", "advanced micro devices"]:
if marker in name:
return True
return False
except Exception:
return False
__all__ = [
"BaseGPUProvider",
"GPUSnapshot",
"auto_detect_provider",
]