From a0de5f6d49ec54d15c5059f8d4223668dea66f38 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 11:04:16 -0700 Subject: [PATCH 01/13] Add a reusable pathfinder driver info helper. Break out CUDA driver version querying into a standalone internal utility so it can be reused independently from compatibility checks, and cover the ctypes loader paths with focused tests. Made-with: Cursor --- .../cuda/pathfinder/_utils/driver_info.py | 36 ++++++ .../tests/test_utils_driver_info.py | 106 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py create mode 100644 cuda_pathfinder/tests/test_utils_driver_info.py diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py new file mode 100644 index 00000000000..e032e0bb2b1 --- /dev/null +++ b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import ctypes +from collections.abc import Callable +from typing import cast + +from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import ( + load_nvidia_dynamic_lib as _load_nvidia_dynamic_lib, +) +from cuda.pathfinder._utils.platform_aware import IS_WINDOWS + + +def _query_driver_version() -> int: + """Return the CUDA driver version from ``cuDriverGetVersion()``.""" + loaded_cuda = _load_nvidia_dynamic_lib("cuda") + if loaded_cuda.abs_path is None: + raise RuntimeError('Could not determine an absolute path for the driver library "cuda".') + if IS_WINDOWS: + loader_cls_obj = vars(ctypes).get("WinDLL") + if loader_cls_obj is None: + raise RuntimeError("ctypes.WinDLL is unavailable on this platform.") + loader_cls = cast(Callable[[str], ctypes.CDLL], loader_cls_obj) + else: + loader_cls = ctypes.CDLL + driver_lib = loader_cls(loaded_cuda.abs_path) + cu_driver_get_version = driver_lib.cuDriverGetVersion + cu_driver_get_version.argtypes = [ctypes.POINTER(ctypes.c_int)] + cu_driver_get_version.restype = ctypes.c_int + version = ctypes.c_int() + status = cu_driver_get_version(ctypes.byref(version)) + if status != 0: + raise RuntimeError(f"Failed to query CUDA driver version via cuDriverGetVersion() (status={status}).") + return version.value diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py new file mode 100644 index 00000000000..7cbbc2d62bb --- /dev/null +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import ctypes + +import pytest + +from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL +from cuda.pathfinder._utils import driver_info + + +class _FakeCuDriverGetVersion: + def __init__(self, *, status: int, version: int): + self.argtypes = None + self.restype = None + self._status = status + self._version = version + + def __call__(self, version_ptr) -> int: + ctypes.cast(version_ptr, ctypes.POINTER(ctypes.c_int)).contents.value = self._version + return self._status + + +class _FakeDriverLib: + def __init__(self, *, status: int, version: int): + self.cuDriverGetVersion = _FakeCuDriverGetVersion(status=status, version=version) + + +def _loaded_cuda(abs_path: str | None) -> LoadedDL: + return LoadedDL( + abs_path=abs_path, + was_already_loaded_from_elsewhere=False, + _handle_uint=0xBEEF, + found_via="system-search", + ) + + +def test_query_driver_version_uses_cdll_on_non_windows(monkeypatch): + fake_driver_lib = _FakeDriverLib(status=0, version=13020) + loaded_paths: list[str] = [] + + monkeypatch.setattr(driver_info, "IS_WINDOWS", False) + monkeypatch.setattr(driver_info, "_load_nvidia_dynamic_lib", lambda _libname: _loaded_cuda("/usr/lib/libcuda.so.1")) + + def fake_cdll(abs_path: str): + loaded_paths.append(abs_path) + return fake_driver_lib + + monkeypatch.setattr(driver_info.ctypes, "CDLL", fake_cdll) + + assert driver_info._query_driver_version() == 13020 + assert loaded_paths == ["/usr/lib/libcuda.so.1"] + assert fake_driver_lib.cuDriverGetVersion.argtypes == [ctypes.POINTER(ctypes.c_int)] + assert fake_driver_lib.cuDriverGetVersion.restype is ctypes.c_int + + +def test_query_driver_version_uses_windll_on_windows(monkeypatch): + fake_driver_lib = _FakeDriverLib(status=0, version=12080) + loaded_paths: list[str] = [] + + monkeypatch.setattr(driver_info, "IS_WINDOWS", True) + monkeypatch.setattr( + driver_info, + "_load_nvidia_dynamic_lib", + lambda _libname: _loaded_cuda(r"C:\Windows\System32\nvcuda.dll"), + ) + + def fake_windll(abs_path: str): + loaded_paths.append(abs_path) + return fake_driver_lib + + monkeypatch.setattr(driver_info.ctypes, "WinDLL", fake_windll, raising=False) + + assert driver_info._query_driver_version() == 12080 + assert loaded_paths == [r"C:\Windows\System32\nvcuda.dll"] + + +def test_query_driver_version_raises_when_cuda_abs_path_is_missing(monkeypatch): + monkeypatch.setattr(driver_info, "_load_nvidia_dynamic_lib", lambda _libname: _loaded_cuda(None)) + + with pytest.raises(RuntimeError, match='Could not determine an absolute path for the driver library "cuda"'): + driver_info._query_driver_version() + + +def test_query_driver_version_raises_when_windll_is_unavailable(monkeypatch): + monkeypatch.setattr(driver_info, "IS_WINDOWS", True) + monkeypatch.setattr( + driver_info, + "_load_nvidia_dynamic_lib", + lambda _libname: _loaded_cuda(r"C:\Windows\System32\nvcuda.dll"), + ) + monkeypatch.delattr(driver_info.ctypes, "WinDLL", raising=False) + + with pytest.raises(RuntimeError, match="ctypes.WinDLL is unavailable on this platform"): + driver_info._query_driver_version() + + +def test_query_driver_version_raises_when_cuda_call_fails(monkeypatch): + fake_driver_lib = _FakeDriverLib(status=1, version=0) + + monkeypatch.setattr(driver_info, "IS_WINDOWS", False) + monkeypatch.setattr(driver_info, "_load_nvidia_dynamic_lib", lambda _libname: _loaded_cuda("/usr/lib/libcuda.so.1")) + monkeypatch.setattr(driver_info.ctypes, "CDLL", lambda _abs_path: fake_driver_lib) + + with pytest.raises(RuntimeError, match=r"cuDriverGetVersion\(\) \(status=1\)"): + driver_info._query_driver_version() From 91d38ffa434b57aeec2c9f126ea501fefb72e9be Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 11:36:53 -0700 Subject: [PATCH 02/13] Refine pathfinder driver info loader checks. Treat the Windows WinDLL path as the normal runtime case and keep the focused tests aligned with the stricter driver-loader invariants. Made-with: Cursor --- .../cuda/pathfinder/_utils/driver_info.py | 10 +++------ .../tests/test_utils_driver_info.py | 22 +------------------ 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py index e032e0bb2b1..a5274f1c5fe 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py @@ -5,7 +5,6 @@ import ctypes from collections.abc import Callable -from typing import cast from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import ( load_nvidia_dynamic_lib as _load_nvidia_dynamic_lib, @@ -16,13 +15,10 @@ def _query_driver_version() -> int: """Return the CUDA driver version from ``cuDriverGetVersion()``.""" loaded_cuda = _load_nvidia_dynamic_lib("cuda") - if loaded_cuda.abs_path is None: - raise RuntimeError('Could not determine an absolute path for the driver library "cuda".') if IS_WINDOWS: - loader_cls_obj = vars(ctypes).get("WinDLL") - if loader_cls_obj is None: - raise RuntimeError("ctypes.WinDLL is unavailable on this platform.") - loader_cls = cast(Callable[[str], ctypes.CDLL], loader_cls_obj) + # `ctypes.WinDLL` exists on Windows at runtime. The ignore is only for + # Linux mypy runs, where the platform stubs do not define that attribute. + loader_cls: Callable[[str], ctypes.CDLL] = ctypes.WinDLL # type: ignore[attr-defined] else: loader_cls = ctypes.CDLL driver_lib = loader_cls(loaded_cuda.abs_path) diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py index 7cbbc2d62bb..6b6e7faeec3 100644 --- a/cuda_pathfinder/tests/test_utils_driver_info.py +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -26,7 +26,7 @@ def __init__(self, *, status: int, version: int): self.cuDriverGetVersion = _FakeCuDriverGetVersion(status=status, version=version) -def _loaded_cuda(abs_path: str | None) -> LoadedDL: +def _loaded_cuda(abs_path: str) -> LoadedDL: return LoadedDL( abs_path=abs_path, was_already_loaded_from_elsewhere=False, @@ -75,26 +75,6 @@ def fake_windll(abs_path: str): assert loaded_paths == [r"C:\Windows\System32\nvcuda.dll"] -def test_query_driver_version_raises_when_cuda_abs_path_is_missing(monkeypatch): - monkeypatch.setattr(driver_info, "_load_nvidia_dynamic_lib", lambda _libname: _loaded_cuda(None)) - - with pytest.raises(RuntimeError, match='Could not determine an absolute path for the driver library "cuda"'): - driver_info._query_driver_version() - - -def test_query_driver_version_raises_when_windll_is_unavailable(monkeypatch): - monkeypatch.setattr(driver_info, "IS_WINDOWS", True) - monkeypatch.setattr( - driver_info, - "_load_nvidia_dynamic_lib", - lambda _libname: _loaded_cuda(r"C:\Windows\System32\nvcuda.dll"), - ) - monkeypatch.delattr(driver_info.ctypes, "WinDLL", raising=False) - - with pytest.raises(RuntimeError, match="ctypes.WinDLL is unavailable on this platform"): - driver_info._query_driver_version() - - def test_query_driver_version_raises_when_cuda_call_fails(monkeypatch): fake_driver_lib = _FakeDriverLib(status=1, version=0) From 14450c1d788411ecabf4e37a0e05e5b347cbbeaa Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 11:45:29 -0700 Subject: [PATCH 03/13] Add parsed pathfinder driver version metadata. Wrap the encoded cuDriverGetVersion() result in a DriverVersion dataclass so callers can use major and minor fields directly while retaining a low-level integer helper for loader-focused tests. Made-with: Cursor --- .../cuda/pathfinder/_utils/driver_info.py | 22 +++++++++++++++++-- .../tests/test_utils_driver_info.py | 18 +++++++++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py index a5274f1c5fe..bbdb5643ea6 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py @@ -5,6 +5,7 @@ import ctypes from collections.abc import Callable +from dataclasses import dataclass from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import ( load_nvidia_dynamic_lib as _load_nvidia_dynamic_lib, @@ -12,8 +13,25 @@ from cuda.pathfinder._utils.platform_aware import IS_WINDOWS -def _query_driver_version() -> int: - """Return the CUDA driver version from ``cuDriverGetVersion()``.""" +@dataclass(frozen=True, slots=True) +class DriverVersion: + encoded: int + major: int + minor: int + + +def query_driver_version() -> DriverVersion: + """Return the CUDA driver version parsed into its major/minor components.""" + encoded = _query_driver_version_int() + return DriverVersion( + encoded=encoded, + major=encoded // 1000, + minor=(encoded % 1000) // 10, + ) + + +def _query_driver_version_int() -> int: + """Return the encoded CUDA driver version from ``cuDriverGetVersion()``.""" loaded_cuda = _load_nvidia_dynamic_lib("cuda") if IS_WINDOWS: # `ctypes.WinDLL` exists on Windows at runtime. The ignore is only for diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py index 6b6e7faeec3..f7c9c2981b6 100644 --- a/cuda_pathfinder/tests/test_utils_driver_info.py +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -48,7 +48,7 @@ def fake_cdll(abs_path: str): monkeypatch.setattr(driver_info.ctypes, "CDLL", fake_cdll) - assert driver_info._query_driver_version() == 13020 + assert driver_info._query_driver_version_int() == 13020 assert loaded_paths == ["/usr/lib/libcuda.so.1"] assert fake_driver_lib.cuDriverGetVersion.argtypes == [ctypes.POINTER(ctypes.c_int)] assert fake_driver_lib.cuDriverGetVersion.restype is ctypes.c_int @@ -71,11 +71,21 @@ def fake_windll(abs_path: str): monkeypatch.setattr(driver_info.ctypes, "WinDLL", fake_windll, raising=False) - assert driver_info._query_driver_version() == 12080 + assert driver_info._query_driver_version_int() == 12080 assert loaded_paths == [r"C:\Windows\System32\nvcuda.dll"] -def test_query_driver_version_raises_when_cuda_call_fails(monkeypatch): +def test_query_driver_version_returns_parsed_dataclass(monkeypatch): + monkeypatch.setattr(driver_info, "_query_driver_version_int", lambda: 12080) + + assert driver_info.query_driver_version() == driver_info.DriverVersion( + encoded=12080, + major=12, + minor=8, + ) + + +def test_query_driver_version_int_raises_when_cuda_call_fails(monkeypatch): fake_driver_lib = _FakeDriverLib(status=1, version=0) monkeypatch.setattr(driver_info, "IS_WINDOWS", False) @@ -83,4 +93,4 @@ def test_query_driver_version_raises_when_cuda_call_fails(monkeypatch): monkeypatch.setattr(driver_info.ctypes, "CDLL", lambda _abs_path: fake_driver_lib) with pytest.raises(RuntimeError, match=r"cuDriverGetVersion\(\) \(status=1\)"): - driver_info._query_driver_version() + driver_info._query_driver_version_int() From 329d952ad1821bd68b9d150606f07b41b6e362b7 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 12:06:26 -0700 Subject: [PATCH 04/13] Add a real pathfinder driver version test. Cover query_driver_version() alongside the driver library loading tests and reuse the existing strictness mode so host-specific failures still surface cleanly in all_must_work mode. Made-with: Cursor --- .../tests/test_driver_lib_loading.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/cuda_pathfinder/tests/test_driver_lib_loading.py b/cuda_pathfinder/tests/test_driver_lib_loading.py index bf62a17d703..36ba376764d 100644 --- a/cuda_pathfinder/tests/test_driver_lib_loading.py +++ b/cuda_pathfinder/tests/test_driver_lib_loading.py @@ -25,6 +25,7 @@ _load_lib_no_cache, ) from cuda.pathfinder._dynamic_libs.subprocess_protocol import STATUS_NOT_FOUND, parse_dynamic_lib_subprocess_payload +from cuda.pathfinder._utils import driver_info from cuda.pathfinder._utils.platform_aware import IS_WINDOWS, quote_for_shell STRICTNESS = os.environ.get("CUDA_PATHFINDER_TEST_LOAD_NVIDIA_DYNAMIC_LIB_STRICTNESS", "see_what_works") @@ -157,3 +158,21 @@ def raise_child_process_failed(): assert abs_path is not None info_summary_append(f"abs_path={quote_for_shell(abs_path)}") assert os.path.isfile(abs_path) + + +def test_real_query_driver_version(info_summary_append): + driver_info._load_nvidia_dynamic_lib.cache_clear() + try: + version = driver_info.query_driver_version() + except Exception as exc: + if STRICTNESS == "all_must_work": + raise + info_summary_append(f"driver version unavailable: {exc.__class__.__name__}: {exc}") + return + finally: + driver_info._load_nvidia_dynamic_lib.cache_clear() + + info_summary_append(f"driver_version={version.major}.{version.minor} (encoded={version.encoded})") + assert version.encoded > 0 + assert version.major == version.encoded // 1000 + assert version.minor == (version.encoded % 1000) // 10 From 0bcbd23e4f335e690aec1440b66831dfb6fe4e43 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 12:22:28 -0700 Subject: [PATCH 05/13] Reduce redundant pathfinder driver info mocks. Drop the non-Windows loader mock now that a real driver-version test covers the Linux success path, while keeping the Windows branch and failure-path unit coverage. Made-with: Cursor --- .../tests/test_utils_driver_info.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py index f7c9c2981b6..37ebc6cd4c9 100644 --- a/cuda_pathfinder/tests/test_utils_driver_info.py +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -35,25 +35,6 @@ def _loaded_cuda(abs_path: str) -> LoadedDL: ) -def test_query_driver_version_uses_cdll_on_non_windows(monkeypatch): - fake_driver_lib = _FakeDriverLib(status=0, version=13020) - loaded_paths: list[str] = [] - - monkeypatch.setattr(driver_info, "IS_WINDOWS", False) - monkeypatch.setattr(driver_info, "_load_nvidia_dynamic_lib", lambda _libname: _loaded_cuda("/usr/lib/libcuda.so.1")) - - def fake_cdll(abs_path: str): - loaded_paths.append(abs_path) - return fake_driver_lib - - monkeypatch.setattr(driver_info.ctypes, "CDLL", fake_cdll) - - assert driver_info._query_driver_version_int() == 13020 - assert loaded_paths == ["/usr/lib/libcuda.so.1"] - assert fake_driver_lib.cuDriverGetVersion.argtypes == [ctypes.POINTER(ctypes.c_int)] - assert fake_driver_lib.cuDriverGetVersion.restype is ctypes.c_int - - def test_query_driver_version_uses_windll_on_windows(monkeypatch): fake_driver_lib = _FakeDriverLib(status=0, version=12080) loaded_paths: list[str] = [] From f72c96385be8a56fd875ec745be8e6492cf71118 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 12:42:04 -0700 Subject: [PATCH 06/13] Rename the pathfinder CUDA driver version dataclass. Use DriverCudaVersion for clearer pairing with the planned release-version type while keeping the existing driver info API behavior unchanged. Made-with: Cursor --- cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py | 6 +++--- cuda_pathfinder/tests/test_utils_driver_info.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py index bbdb5643ea6..d83f180d677 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py @@ -14,16 +14,16 @@ @dataclass(frozen=True, slots=True) -class DriverVersion: +class DriverCudaVersion: encoded: int major: int minor: int -def query_driver_version() -> DriverVersion: +def query_driver_version() -> DriverCudaVersion: """Return the CUDA driver version parsed into its major/minor components.""" encoded = _query_driver_version_int() - return DriverVersion( + return DriverCudaVersion( encoded=encoded, major=encoded // 1000, minor=(encoded % 1000) // 10, diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py index 37ebc6cd4c9..21855339736 100644 --- a/cuda_pathfinder/tests/test_utils_driver_info.py +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -59,7 +59,7 @@ def fake_windll(abs_path: str): def test_query_driver_version_returns_parsed_dataclass(monkeypatch): monkeypatch.setattr(driver_info, "_query_driver_version_int", lambda: 12080) - assert driver_info.query_driver_version() == driver_info.DriverVersion( + assert driver_info.query_driver_version() == driver_info.DriverCudaVersion( encoded=12080, major=12, minor=8, From 4eade17f56cc9a45ffa5ef20156553b8075a258d Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 13:46:38 -0700 Subject: [PATCH 07/13] Add a pathfinder NVML driver release version helper. Query nvmlSystemGetDriverVersion() through pathfinder's driver library loading path and add a minimal real test so the implementation is preserved as a future reference. Made-with: Cursor --- .../cuda/pathfinder/_utils/driver_info.py | 44 +++++++++++++++++++ .../tests/test_utils_driver_info.py | 14 ++++++ 2 files changed, 58 insertions(+) diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py index d83f180d677..3d85e7349d0 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py @@ -12,6 +12,9 @@ ) from cuda.pathfinder._utils.platform_aware import IS_WINDOWS +_NVML_SUCCESS = 0 +_NVML_SYSTEM_DRIVER_VERSION_BUFFER_LENGTH = 80 + @dataclass(frozen=True, slots=True) class DriverCudaVersion: @@ -48,3 +51,44 @@ def _query_driver_version_int() -> int: if status != 0: raise RuntimeError(f"Failed to query CUDA driver version via cuDriverGetVersion() (status={status}).") return version.value + + +def _query_driver_release_version_text() -> str: + """Return the graphics driver release version from ``nvmlSystemGetDriverVersion()``.""" + loaded_nvml = _load_nvidia_dynamic_lib("nvml") + nvml_lib = ctypes.CDLL(loaded_nvml.abs_path) + + nvml_init_v2 = nvml_lib.nvmlInit_v2 + nvml_init_v2.argtypes = [] + nvml_init_v2.restype = ctypes.c_int + + nvml_system_get_driver_version = nvml_lib.nvmlSystemGetDriverVersion + nvml_system_get_driver_version.argtypes = [ctypes.POINTER(ctypes.c_char), ctypes.c_uint] + nvml_system_get_driver_version.restype = ctypes.c_int + + nvml_shutdown = nvml_lib.nvmlShutdown + nvml_shutdown.argtypes = [] + nvml_shutdown.restype = ctypes.c_int + + init_status = nvml_init_v2() + if init_status != _NVML_SUCCESS: + raise RuntimeError(f"Failed to initialize NVML via nvmlInit_v2() (status={init_status}).") + + try: + version_buffer = ctypes.create_string_buffer(_NVML_SYSTEM_DRIVER_VERSION_BUFFER_LENGTH) + status = nvml_system_get_driver_version(version_buffer, _NVML_SYSTEM_DRIVER_VERSION_BUFFER_LENGTH) + if status != _NVML_SUCCESS: + raise RuntimeError( + f"Failed to query driver release version via nvmlSystemGetDriverVersion() (status={status})." + ) + release_version = version_buffer.value.decode() + except BaseException as exc: + shutdown_status = nvml_shutdown() + if shutdown_status != _NVML_SUCCESS: + raise RuntimeError(f"Failed to shut down NVML via nvmlShutdown() (status={shutdown_status}).") from exc + raise + + shutdown_status = nvml_shutdown() + if shutdown_status != _NVML_SUCCESS: + raise RuntimeError(f"Failed to shut down NVML via nvmlShutdown() (status={shutdown_status}).") + return release_version diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py index 21855339736..e8eb0e6de42 100644 --- a/cuda_pathfinder/tests/test_utils_driver_info.py +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -35,6 +35,20 @@ def _loaded_cuda(abs_path: str) -> LoadedDL: ) +def test_query_driver_release_version_text(): + driver_info._load_nvidia_dynamic_lib.cache_clear() + try: + release_version = driver_info._query_driver_release_version_text() + finally: + driver_info._load_nvidia_dynamic_lib.cache_clear() + + components = tuple(int(component) for component in release_version.split(".")) + assert len(components) in (2, 3) + assert 400 <= components[0] < 1000 + for component in components[1:]: + assert component >= 0 + + def test_query_driver_version_uses_windll_on_windows(monkeypatch): fake_driver_lib = _FakeDriverLib(status=0, version=12080) loaded_paths: list[str] = [] From 32e814fedd518ac266482dde2037ebf587cad64d Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 13:46:52 -0700 Subject: [PATCH 08/13] Revert the pathfinder NVML driver release version helper. Step back from the exploratory NVML-based release-version query for now because it adds non-trivial complexity and a new dependency surface without a current pathfinder need, while keeping the reference implementation in history if we need it later. Made-with: Cursor --- .../cuda/pathfinder/_utils/driver_info.py | 44 ------------------- .../tests/test_utils_driver_info.py | 14 ------ 2 files changed, 58 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py index 3d85e7349d0..d83f180d677 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py @@ -12,9 +12,6 @@ ) from cuda.pathfinder._utils.platform_aware import IS_WINDOWS -_NVML_SUCCESS = 0 -_NVML_SYSTEM_DRIVER_VERSION_BUFFER_LENGTH = 80 - @dataclass(frozen=True, slots=True) class DriverCudaVersion: @@ -51,44 +48,3 @@ def _query_driver_version_int() -> int: if status != 0: raise RuntimeError(f"Failed to query CUDA driver version via cuDriverGetVersion() (status={status}).") return version.value - - -def _query_driver_release_version_text() -> str: - """Return the graphics driver release version from ``nvmlSystemGetDriverVersion()``.""" - loaded_nvml = _load_nvidia_dynamic_lib("nvml") - nvml_lib = ctypes.CDLL(loaded_nvml.abs_path) - - nvml_init_v2 = nvml_lib.nvmlInit_v2 - nvml_init_v2.argtypes = [] - nvml_init_v2.restype = ctypes.c_int - - nvml_system_get_driver_version = nvml_lib.nvmlSystemGetDriverVersion - nvml_system_get_driver_version.argtypes = [ctypes.POINTER(ctypes.c_char), ctypes.c_uint] - nvml_system_get_driver_version.restype = ctypes.c_int - - nvml_shutdown = nvml_lib.nvmlShutdown - nvml_shutdown.argtypes = [] - nvml_shutdown.restype = ctypes.c_int - - init_status = nvml_init_v2() - if init_status != _NVML_SUCCESS: - raise RuntimeError(f"Failed to initialize NVML via nvmlInit_v2() (status={init_status}).") - - try: - version_buffer = ctypes.create_string_buffer(_NVML_SYSTEM_DRIVER_VERSION_BUFFER_LENGTH) - status = nvml_system_get_driver_version(version_buffer, _NVML_SYSTEM_DRIVER_VERSION_BUFFER_LENGTH) - if status != _NVML_SUCCESS: - raise RuntimeError( - f"Failed to query driver release version via nvmlSystemGetDriverVersion() (status={status})." - ) - release_version = version_buffer.value.decode() - except BaseException as exc: - shutdown_status = nvml_shutdown() - if shutdown_status != _NVML_SUCCESS: - raise RuntimeError(f"Failed to shut down NVML via nvmlShutdown() (status={shutdown_status}).") from exc - raise - - shutdown_status = nvml_shutdown() - if shutdown_status != _NVML_SUCCESS: - raise RuntimeError(f"Failed to shut down NVML via nvmlShutdown() (status={shutdown_status}).") - return release_version diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py index e8eb0e6de42..21855339736 100644 --- a/cuda_pathfinder/tests/test_utils_driver_info.py +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -35,20 +35,6 @@ def _loaded_cuda(abs_path: str) -> LoadedDL: ) -def test_query_driver_release_version_text(): - driver_info._load_nvidia_dynamic_lib.cache_clear() - try: - release_version = driver_info._query_driver_release_version_text() - finally: - driver_info._load_nvidia_dynamic_lib.cache_clear() - - components = tuple(int(component) for component in release_version.split(".")) - assert len(components) in (2, 3) - assert 400 <= components[0] < 1000 - for component in components[1:]: - assert component >= 0 - - def test_query_driver_version_uses_windll_on_windows(monkeypatch): fake_driver_lib = _FakeDriverLib(status=0, version=12080) loaded_paths: list[str] = [] From 1369c175780e620ee2adae2f9755c825ca31d51d Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 13:54:26 -0700 Subject: [PATCH 09/13] Clarify the pathfinder CUDA driver version naming. Document that DriverCudaVersion matches the CUDA Version shown by nvidia-smi rather than the graphics driver release, so the dataclass name reads clearly in context. Made-with: Cursor --- .../cuda/pathfinder/_utils/driver_info.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py index d83f180d677..c7de1a7b36e 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py @@ -15,6 +15,24 @@ @dataclass(frozen=True, slots=True) class DriverCudaVersion: + """ + CUDA-facing driver version reported by ``cuDriverGetVersion()``. + + The name ``DriverCudaVersion`` is intentionally specific: this dataclass + models the version shown as ``CUDA Version`` in ``nvidia-smi``, not the + graphics driver release shown as ``Driver Version``. + + Example ``nvidia-smi`` output:: + + +---------------------------------------------------------------------+ + | NVIDIA-SMI 595.58.03 Driver Version: 595.58.03 CUDA Version: 13.2 | + +---------------------------------------------------------------------+ + + For the example above, ``DriverCudaVersion(encoded=13020, major=13, + minor=2)`` corresponds to ``CUDA Version: 13.2``. It does not correspond + to ``Driver Version: 595.58.03``. + """ + encoded: int major: int minor: int From 772451b13cb61e2ca48cf2ff96bb5f0c4a36c2c2 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 14:16:24 -0700 Subject: [PATCH 10/13] Finalize the pathfinder CUDA driver version query API. Expose DriverCudaVersion, QueryDriverCudaVersionError, and query_driver_cuda_version publicly, and align the internal naming, caching, docs, and test coverage around the CUDA-specific driver version query. Made-with: Cursor --- cuda_pathfinder/cuda/pathfinder/__init__.py | 3 ++ .../cuda/pathfinder/_utils/driver_info.py | 25 ++++++++---- cuda_pathfinder/docs/source/api.rst | 4 ++ .../tests/test_driver_lib_loading.py | 8 ++-- .../tests/test_utils_driver_info.py | 38 +++++++++++++++---- 5 files changed, 60 insertions(+), 18 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/__init__.py b/cuda_pathfinder/cuda/pathfinder/__init__.py index dc818dfd08f..89becb1834f 100644 --- a/cuda_pathfinder/cuda/pathfinder/__init__.py +++ b/cuda_pathfinder/cuda/pathfinder/__init__.py @@ -59,6 +59,9 @@ from cuda.pathfinder._static_libs.find_static_lib import ( locate_static_lib as locate_static_lib, ) +from cuda.pathfinder._utils.driver_info import DriverCudaVersion as DriverCudaVersion +from cuda.pathfinder._utils.driver_info import QueryDriverCudaVersionError as QueryDriverCudaVersionError +from cuda.pathfinder._utils.driver_info import query_driver_cuda_version as query_driver_cuda_version from cuda.pathfinder._utils.env_vars import get_cuda_path_or_home as get_cuda_path_or_home from cuda.pathfinder._version import __version__ # isort: skip diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py index c7de1a7b36e..141b3465d49 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py @@ -4,6 +4,7 @@ from __future__ import annotations import ctypes +import functools from collections.abc import Callable from dataclasses import dataclass @@ -13,6 +14,10 @@ from cuda.pathfinder._utils.platform_aware import IS_WINDOWS +class QueryDriverCudaVersionError(RuntimeError): + """Raised when ``query_driver_cuda_version()`` cannot determine the CUDA driver version.""" + + @dataclass(frozen=True, slots=True) class DriverCudaVersion: """ @@ -38,17 +43,21 @@ class DriverCudaVersion: minor: int -def query_driver_version() -> DriverCudaVersion: +@functools.cache +def query_driver_cuda_version() -> DriverCudaVersion: """Return the CUDA driver version parsed into its major/minor components.""" - encoded = _query_driver_version_int() - return DriverCudaVersion( - encoded=encoded, - major=encoded // 1000, - minor=(encoded % 1000) // 10, - ) + try: + encoded = _query_driver_cuda_version_int() + return DriverCudaVersion( + encoded=encoded, + major=encoded // 1000, + minor=(encoded % 1000) // 10, + ) + except Exception as exc: + raise QueryDriverCudaVersionError("Failed to query the CUDA driver version.") from exc -def _query_driver_version_int() -> int: +def _query_driver_cuda_version_int() -> int: """Return the encoded CUDA driver version from ``cuDriverGetVersion()``.""" loaded_cuda = _load_nvidia_dynamic_lib("cuda") if IS_WINDOWS: diff --git a/cuda_pathfinder/docs/source/api.rst b/cuda_pathfinder/docs/source/api.rst index e49478c09ec..7fe9ea54e9d 100644 --- a/cuda_pathfinder/docs/source/api.rst +++ b/cuda_pathfinder/docs/source/api.rst @@ -18,6 +18,10 @@ CUDA bitcode and static libraries. get_cuda_path_or_home + DriverCudaVersion + QueryDriverCudaVersionError + query_driver_cuda_version + SUPPORTED_NVIDIA_LIBNAMES load_nvidia_dynamic_lib LoadedDL diff --git a/cuda_pathfinder/tests/test_driver_lib_loading.py b/cuda_pathfinder/tests/test_driver_lib_loading.py index 36ba376764d..b97453c9b5a 100644 --- a/cuda_pathfinder/tests/test_driver_lib_loading.py +++ b/cuda_pathfinder/tests/test_driver_lib_loading.py @@ -160,17 +160,19 @@ def raise_child_process_failed(): assert os.path.isfile(abs_path) -def test_real_query_driver_version(info_summary_append): +def test_real_query_driver_cuda_version(info_summary_append): driver_info._load_nvidia_dynamic_lib.cache_clear() + driver_info.query_driver_cuda_version.cache_clear() try: - version = driver_info.query_driver_version() - except Exception as exc: + version = driver_info.query_driver_cuda_version() + except driver_info.QueryDriverCudaVersionError as exc: if STRICTNESS == "all_must_work": raise info_summary_append(f"driver version unavailable: {exc.__class__.__name__}: {exc}") return finally: driver_info._load_nvidia_dynamic_lib.cache_clear() + driver_info.query_driver_cuda_version.cache_clear() info_summary_append(f"driver_version={version.major}.{version.minor} (encoded={version.encoded})") assert version.encoded > 0 diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py index 21855339736..21948dadafe 100644 --- a/cuda_pathfinder/tests/test_utils_driver_info.py +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -9,6 +9,13 @@ from cuda.pathfinder._utils import driver_info +@pytest.fixture(autouse=True) +def _clear_driver_cuda_version_query_cache(): + driver_info.query_driver_cuda_version.cache_clear() + yield + driver_info.query_driver_cuda_version.cache_clear() + + class _FakeCuDriverGetVersion: def __init__(self, *, status: int, version: int): self.argtypes = None @@ -35,7 +42,7 @@ def _loaded_cuda(abs_path: str) -> LoadedDL: ) -def test_query_driver_version_uses_windll_on_windows(monkeypatch): +def test_query_driver_cuda_version_uses_windll_on_windows(monkeypatch): fake_driver_lib = _FakeDriverLib(status=0, version=12080) loaded_paths: list[str] = [] @@ -52,21 +59,38 @@ def fake_windll(abs_path: str): monkeypatch.setattr(driver_info.ctypes, "WinDLL", fake_windll, raising=False) - assert driver_info._query_driver_version_int() == 12080 + assert driver_info._query_driver_cuda_version_int() == 12080 assert loaded_paths == [r"C:\Windows\System32\nvcuda.dll"] -def test_query_driver_version_returns_parsed_dataclass(monkeypatch): - monkeypatch.setattr(driver_info, "_query_driver_version_int", lambda: 12080) +def test_query_driver_cuda_version_returns_parsed_dataclass(monkeypatch): + monkeypatch.setattr(driver_info, "_query_driver_cuda_version_int", lambda: 12080) - assert driver_info.query_driver_version() == driver_info.DriverCudaVersion( + assert driver_info.query_driver_cuda_version() == driver_info.DriverCudaVersion( encoded=12080, major=12, minor=8, ) -def test_query_driver_version_int_raises_when_cuda_call_fails(monkeypatch): +def test_query_driver_cuda_version_wraps_internal_failures(monkeypatch): + root_cause = RuntimeError("low-level query failed") + + def fail_query_driver_cuda_version_int() -> int: + raise root_cause + + monkeypatch.setattr(driver_info, "_query_driver_cuda_version_int", fail_query_driver_cuda_version_int) + + with pytest.raises( + driver_info.QueryDriverCudaVersionError, + match="Failed to query the CUDA driver version", + ) as exc_info: + driver_info.query_driver_cuda_version() + + assert exc_info.value.__cause__ is root_cause + + +def test_query_driver_cuda_version_int_raises_when_cuda_call_fails(monkeypatch): fake_driver_lib = _FakeDriverLib(status=1, version=0) monkeypatch.setattr(driver_info, "IS_WINDOWS", False) @@ -74,4 +98,4 @@ def test_query_driver_version_int_raises_when_cuda_call_fails(monkeypatch): monkeypatch.setattr(driver_info.ctypes, "CDLL", lambda _abs_path: fake_driver_lib) with pytest.raises(RuntimeError, match=r"cuDriverGetVersion\(\) \(status=1\)"): - driver_info._query_driver_version_int() + driver_info._query_driver_cuda_version_int() From 6ec081c75f64855b67914fe48744ecd02456dbd5 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 19 Apr 2026 14:32:18 -0700 Subject: [PATCH 11/13] Add a public pathfinder driver info regression test. Protect the new top-level driver-info re-exports so internal-only test coverage does not miss a broken `cuda.pathfinder` plumbing layer. Made-with: Cursor --- cuda_pathfinder/tests/test_utils_driver_info.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py index 21948dadafe..9ebb98e4fb6 100644 --- a/cuda_pathfinder/tests/test_utils_driver_info.py +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -5,6 +5,15 @@ import pytest +from cuda.pathfinder import ( + DriverCudaVersion as PublicDriverCudaVersion, +) +from cuda.pathfinder import ( + QueryDriverCudaVersionError as PublicQueryDriverCudaVersionError, +) +from cuda.pathfinder import ( + query_driver_cuda_version as public_query_driver_cuda_version, +) from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL from cuda.pathfinder._utils import driver_info @@ -42,6 +51,12 @@ def _loaded_cuda(abs_path: str) -> LoadedDL: ) +def test_driver_cuda_version_public_api_exports(): + assert PublicDriverCudaVersion is driver_info.DriverCudaVersion + assert PublicQueryDriverCudaVersionError is driver_info.QueryDriverCudaVersionError + assert public_query_driver_cuda_version is driver_info.query_driver_cuda_version + + def test_query_driver_cuda_version_uses_windll_on_windows(monkeypatch): fake_driver_lib = _FakeDriverLib(status=0, version=12080) loaded_paths: list[str] = [] From a450104ec6e0ec3f903678b3ae8c5c19291199c5 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Tue, 21 Apr 2026 15:19:23 -0700 Subject: [PATCH 12/13] Remove pathfinder driver info public re-exports Stop exposing the new driver info helper through cuda.pathfinder while keeping the internal implementation and internal test coverage in place. Made-with: Cursor --- cuda_pathfinder/cuda/pathfinder/__init__.py | 3 --- cuda_pathfinder/docs/source/api.rst | 4 ---- cuda_pathfinder/tests/test_utils_driver_info.py | 15 --------------- 3 files changed, 22 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/__init__.py b/cuda_pathfinder/cuda/pathfinder/__init__.py index 89becb1834f..dc818dfd08f 100644 --- a/cuda_pathfinder/cuda/pathfinder/__init__.py +++ b/cuda_pathfinder/cuda/pathfinder/__init__.py @@ -59,9 +59,6 @@ from cuda.pathfinder._static_libs.find_static_lib import ( locate_static_lib as locate_static_lib, ) -from cuda.pathfinder._utils.driver_info import DriverCudaVersion as DriverCudaVersion -from cuda.pathfinder._utils.driver_info import QueryDriverCudaVersionError as QueryDriverCudaVersionError -from cuda.pathfinder._utils.driver_info import query_driver_cuda_version as query_driver_cuda_version from cuda.pathfinder._utils.env_vars import get_cuda_path_or_home as get_cuda_path_or_home from cuda.pathfinder._version import __version__ # isort: skip diff --git a/cuda_pathfinder/docs/source/api.rst b/cuda_pathfinder/docs/source/api.rst index 7fe9ea54e9d..e49478c09ec 100644 --- a/cuda_pathfinder/docs/source/api.rst +++ b/cuda_pathfinder/docs/source/api.rst @@ -18,10 +18,6 @@ CUDA bitcode and static libraries. get_cuda_path_or_home - DriverCudaVersion - QueryDriverCudaVersionError - query_driver_cuda_version - SUPPORTED_NVIDIA_LIBNAMES load_nvidia_dynamic_lib LoadedDL diff --git a/cuda_pathfinder/tests/test_utils_driver_info.py b/cuda_pathfinder/tests/test_utils_driver_info.py index 9ebb98e4fb6..21948dadafe 100644 --- a/cuda_pathfinder/tests/test_utils_driver_info.py +++ b/cuda_pathfinder/tests/test_utils_driver_info.py @@ -5,15 +5,6 @@ import pytest -from cuda.pathfinder import ( - DriverCudaVersion as PublicDriverCudaVersion, -) -from cuda.pathfinder import ( - QueryDriverCudaVersionError as PublicQueryDriverCudaVersionError, -) -from cuda.pathfinder import ( - query_driver_cuda_version as public_query_driver_cuda_version, -) from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL from cuda.pathfinder._utils import driver_info @@ -51,12 +42,6 @@ def _loaded_cuda(abs_path: str) -> LoadedDL: ) -def test_driver_cuda_version_public_api_exports(): - assert PublicDriverCudaVersion is driver_info.DriverCudaVersion - assert PublicQueryDriverCudaVersionError is driver_info.QueryDriverCudaVersionError - assert public_query_driver_cuda_version is driver_info.query_driver_cuda_version - - def test_query_driver_cuda_version_uses_windll_on_windows(monkeypatch): fake_driver_lib = _FakeDriverLib(status=0, version=12080) loaded_paths: list[str] = [] From 0d7c8f831bb730140cc861686bcee27d022903b8 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 24 Apr 2026 13:55:37 -0700 Subject: [PATCH 13/13] Clarify DriverCudaVersion docstring terminology. Spell out that `cuDriverGetVersion()` reports the CUDA-facing user-mode driver (UMD) version rather than the kernel-mode driver (KMD) package version so the `nvidia-smi` comparison is less ambiguous. Made-with: Cursor --- cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py index 141b3465d49..a5d4d167d33 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/driver_info.py @@ -25,7 +25,10 @@ class DriverCudaVersion: The name ``DriverCudaVersion`` is intentionally specific: this dataclass models the version shown as ``CUDA Version`` in ``nvidia-smi``, not the - graphics driver release shown as ``Driver Version``. + graphics driver release shown as ``Driver Version``. More specifically, + it reflects the CUDA user-mode driver (UMD) interface version reported by + ``cuDriverGetVersion()``, not the kernel-mode driver (KMD) package + version. Example ``nvidia-smi`` output::