from __future__ import annotations import os import sys from dataclasses import asdict, dataclass from importlib.metadata import PackageNotFoundError, version from typing import Any from .constants import ( HARDWARE_VERIFIED_ARCHITECTURES, SUPPORTED_ARCHITECTURES, VERIFIED_HIP_VERSION, VERIFIED_PACKAGE_VERSIONS, ) from .errors import RuntimeEnvironmentError @dataclass(frozen=True) class DeviceInfo: index: int name: str architecture: str | None uuid: str | None @dataclass(frozen=True) class RuntimeInfo: torch_version: str hip_version: str | None cuda_available: bool bf16_supported: bool visible_devices: int devices: tuple[DeviceInfo, ...] package_versions: dict[str, str | None] accepted_architecture: bool hardware_verified: bool software_verified: bool warnings: tuple[str, ...] def to_dict(self) -> dict[str, Any]: return asdict(self) def select_device(device: str | None) -> None: if device is None: return if "torch" in sys.modules: raise RuntimeEnvironmentError("GPU selection must happen before importing PyTorch") existing_rocr = os.environ.get("ROCR_VISIBLE_DEVICES") if existing_rocr is not None and existing_rocr != device: raise RuntimeEnvironmentError( f"--device {device!r} conflicts with existing ROCR_VISIBLE_DEVICES={existing_rocr!r}; unset one explicitly" ) conflicts = [name for name in ("HIP_VISIBLE_DEVICES", "CUDA_VISIBLE_DEVICES") if name in os.environ] if conflicts: raise RuntimeEnvironmentError( f"--device cannot be combined with existing {', '.join(conflicts)}; " "unset the conflicting visibility variable" ) os.environ["ROCR_VISIBLE_DEVICES"] = device def _installed_versions() -> dict[str, str | None]: installed: dict[str, str | None] = {} for package in VERIFIED_PACKAGE_VERSIONS: try: installed[package] = version(package) except PackageNotFoundError: installed[package] = None return installed def runtime_issues(info: RuntimeInfo, *, require_single: bool) -> tuple[str, ...]: issues: list[str] = [] if info.hip_version is None or not info.cuda_available: issues.append("PyTorch does not see a ROCm GPU; a CUDA-only wheel is not sufficient") if require_single and info.visible_devices != 1: issues.append(f"expected exactly one visible GPU, found {info.visible_devices}; pass --device INDEX_OR_UUID") if not info.accepted_architecture: architectures = ", ".join(device.architecture or "unknown" for device in info.devices) or "none" issues.append(f"expected RDNA 4 (gfx1200/gfx1201), found: {architectures}") if info.cuda_available and not info.bf16_supported: issues.append("the selected GPU/runtime does not report BF16 support") return tuple(issues) def inspect_runtime(*, device: str | None = None, require_single: bool = False, validate: bool = True) -> RuntimeInfo: select_device(device) try: import torch except ImportError as exc: raise RuntimeEnvironmentError( "ROCm PyTorch is not installed. Run scripts/bootstrap-rocm.sh from the repository checkout." ) from exc hip_version = getattr(torch.version, "hip", None) cuda_available = bool(torch.cuda.is_available()) count = torch.cuda.device_count() if cuda_available else 0 devices: list[DeviceInfo] = [] for index in range(count): properties = torch.cuda.get_device_properties(index) devices.append( DeviceInfo( index=index, name=properties.name, architecture=getattr(properties, "gcnArchName", None), uuid=str(getattr(properties, "uuid", "")) or None, ) ) accepted_architecture = bool(devices) and all(device.architecture in SUPPORTED_ARCHITECTURES for device in devices) hardware_verified = bool(devices) and all( device.architecture in HARDWARE_VERIFIED_ARCHITECTURES for device in devices ) package_versions = _installed_versions() software_verified = hip_version == VERIFIED_HIP_VERSION and all( package_versions[name] == expected for name, expected in VERIFIED_PACKAGE_VERSIONS.items() ) warnings: list[str] = [] if accepted_architecture and not hardware_verified: warnings.append( "gfx1200 is accepted by the RDNA 4 guard but has not completed this project's GPU acceptance gate" ) if not software_verified: warnings.append("the installed software stack differs from the exact validated versions") info = RuntimeInfo( torch_version=torch.__version__, hip_version=hip_version, cuda_available=cuda_available, bf16_supported=bool(torch.cuda.is_bf16_supported()) if cuda_available else False, visible_devices=count, devices=tuple(devices), package_versions=package_versions, accepted_architecture=accepted_architecture, hardware_verified=hardware_verified, software_verified=software_verified, warnings=tuple(warnings), ) issues = runtime_issues(info, require_single=require_single) if validate and issues: raise RuntimeEnvironmentError("; ".join(issues)) return info