From 4ee6ae0c2e3774f43c40f61c012b533b8ee59e47 Mon Sep 17 00:00:00 2001 From: Omar Atie Date: Thu, 23 Jul 2026 16:06:57 -0700 Subject: [PATCH 1/3] fix(cuda.core): fall back to driver when nvJitLink < 12.3 is installed Stop probing nvJitLink availability via module.version(), which calls the unversioned nvJitLinkVersion symbol missing in nvJitLink 12.0-12.2. Use symbol pointer inspection via _nvjitlink_has_version_symbol() instead, restoring cuda-core 0.6.0 fallback behavior. Fixes #2408 Signed-off-by: Omar Atie Co-authored-by: Cursor --- cuda_core/cuda/core/_linker.pyx | 8 ++--- .../tests/test_optional_dependency_imports.py | 35 +++++++++++++++++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/cuda_core/cuda/core/_linker.pyx b/cuda_core/cuda/core/_linker.pyx index 753fa28b0d3..bbc61508df5 100644 --- a/cuda_core/cuda/core/_linker.pyx +++ b/cuda_core/cuda/core/_linker.pyx @@ -684,10 +684,10 @@ def _decide_nvjitlink_or_driver() -> bool: " For best results, consider upgrading to a recent version of" ) - nvjitlink_module = _optional_cuda_import( - "cuda.bindings.nvjitlink", - probe_function=lambda module: module.version(), # probe triggers nvJitLink runtime load - ) + # Do not probe via module.version(): nvJitLink 12.0-12.2 lacks the unversioned + # nvJitLinkVersion symbol, so calling version() raises FunctionNotFoundError. + # Use _nvjitlink_has_version_symbol() below instead (symbol pointer inspection). + nvjitlink_module = _optional_cuda_import("cuda.bindings.nvjitlink") if nvjitlink_module is None: warn_txt = f"cuda.bindings.nvjitlink is not available, therefore {warn_txt_common} cuda-bindings." else: diff --git a/cuda_core/tests/test_optional_dependency_imports.py b/cuda_core/tests/test_optional_dependency_imports.py index 02edcc9839a..4ec58c9fa2a 100644 --- a/cuda_core/tests/test_optional_dependency_imports.py +++ b/cuda_core/tests/test_optional_dependency_imports.py @@ -78,7 +78,7 @@ def fake__optional_cuda_import(modname, probe_function=None): def test_decide_nvjitlink_or_driver_reraises_nested_module_not_found(monkeypatch): def fake__optional_cuda_import(modname, probe_function=None): assert modname == "cuda.bindings.nvjitlink" - assert probe_function is not None + assert probe_function is None err = ModuleNotFoundError("No module named 'not_a_real_dependency'") err.name = "not_a_real_dependency" raise err @@ -93,7 +93,7 @@ def fake__optional_cuda_import(modname, probe_function=None): def test_decide_nvjitlink_or_driver_falls_back_when_module_missing(monkeypatch): def fake__optional_cuda_import(modname, probe_function=None): assert modname == "cuda.bindings.nvjitlink" - assert probe_function is not None + assert probe_function is None return None monkeypatch.setattr(_linker, "_optional_cuda_import", fake__optional_cuda_import) @@ -103,3 +103,34 @@ def fake__optional_cuda_import(modname, probe_function=None): assert use_driver_backend is True assert _linker._use_nvjitlink_backend is False + + +def test_decide_nvjitlink_or_driver_falls_back_when_nvjitlink_too_old(monkeypatch): + def fake__optional_cuda_import(modname, probe_function=None): + assert modname == "cuda.bindings.nvjitlink" + assert probe_function is None + return object() + + monkeypatch.setattr(_linker, "_optional_cuda_import", fake__optional_cuda_import) + monkeypatch.setattr(_linker, "_nvjitlink_has_version_symbol", lambda _nvjitlink: False) + + with pytest.warns(RuntimeWarning, match="too old \\(<12.3\\)"): + use_driver_backend = _linker._decide_nvjitlink_or_driver() + + assert use_driver_backend is True + assert _linker._use_nvjitlink_backend is False + + +def test_decide_nvjitlink_or_driver_selects_nvjitlink_when_version_symbol_present(monkeypatch): + def fake__optional_cuda_import(modname, probe_function=None): + assert modname == "cuda.bindings.nvjitlink" + assert probe_function is None + return object() + + monkeypatch.setattr(_linker, "_optional_cuda_import", fake__optional_cuda_import) + monkeypatch.setattr(_linker, "_nvjitlink_has_version_symbol", lambda _nvjitlink: True) + + use_driver_backend = _linker._decide_nvjitlink_or_driver() + + assert use_driver_backend is False + assert _linker._use_nvjitlink_backend is True From 7f6a534eb96293c45bfb28bb414fac065f8cc5b3 Mon Sep 17 00:00:00 2001 From: Omar Atie Date: Thu, 23 Jul 2026 16:06:57 -0700 Subject: [PATCH 2/3] test(cuda.core): add coverage for nvJitLink <12.3 driver fallback Add regression tests for Linker.which_backend() and _decide_nvjitlink_or_driver() when the nvJitLinkVersion symbol is missing (nvJitLink 12.0-12.2). Related to #2408 Signed-off-by: Omar Atie Co-authored-by: Cursor --- cuda_core/tests/test_linker.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cuda_core/tests/test_linker.py b/cuda_core/tests/test_linker.py index 9d95b5fd9c3..a8dbce7bcc5 100644 --- a/cuda_core/tests/test_linker.py +++ b/cuda_core/tests/test_linker.py @@ -303,6 +303,24 @@ def fake_decide(): assert result == "nvJitLink" assert called, "_decide_nvjitlink_or_driver was not called" + def test_which_backend_falls_back_when_nvjitlink_too_old(self, monkeypatch): + """Regression test for #2408: old nvJitLink must not crash which_backend().""" + monkeypatch.setattr(_linker, "_use_nvjitlink_backend", None) + monkeypatch.setattr(_linker, "_driver", None) + + def fake__optional_cuda_import(modname, probe_function=None): + assert modname == "cuda.bindings.nvjitlink" + assert probe_function is None + return object() + + monkeypatch.setattr(_linker, "_optional_cuda_import", fake__optional_cuda_import) + monkeypatch.setattr(_linker, "_nvjitlink_has_version_symbol", lambda _nvjitlink: False) + + with pytest.warns(RuntimeWarning, match="too old \\(<12.3\\)"): + assert Linker.which_backend() == "driver" + + assert _linker._use_nvjitlink_backend is False + def test_which_backend_is_classmethod(self): attr = inspect.getattr_static(Linker, "which_backend") assert isinstance(attr, classmethod) From 91a1f4fff4e6679046390ae2deffdf0caf3adcf9 Mon Sep 17 00:00:00 2001 From: Omar Atie Date: Thu, 23 Jul 2026 17:46:28 -0700 Subject: [PATCH 3/3] docs(cuda.core): add 1.2.0 release note for nvJitLink <12.3 fallback fix Document the #2408 regression fix in the cuda.core 1.2.0 release notes. Related to #2408 Signed-off-by: Omar Atie Co-authored-by: Cursor --- cuda_core/docs/source/release/1.2.0-notes.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cuda_core/docs/source/release/1.2.0-notes.rst b/cuda_core/docs/source/release/1.2.0-notes.rst index 6a047c9cfe8..4f561576b4b 100644 --- a/cuda_core/docs/source/release/1.2.0-notes.rst +++ b/cuda_core/docs/source/release/1.2.0-notes.rst @@ -18,6 +18,15 @@ Fixes and enhancements (`#2357 `__, `#2371 `__) +- :meth:`Linker.which_backend` and constructing a :class:`Linker` no longer + raise ``FunctionNotFoundError`` when an nvJitLink older than 12.3 + (12.0–12.2) is installed. These versions do not export the unversioned + ``nvJitLinkVersion`` symbol, so probing the version crashed instead of + falling back. ``cuda.core`` now warns and falls back to the driver + (``cuLink``) backend, restoring the pre-0.7.0 behavior. + (`#2409 `__, + closes `#2408 `__) + Deprecation Notices -------------------