Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5174ea2
Bump pathfinder version to 1.2.4a0
rwgk Sep 20, 2025
8da27a7
Update load_nvidia_dynamic_lib docstring first (to define what we're …
rwgk Sep 20, 2025
37cb325
Rename .retry_with_cuda_home_priority_last() → .try_with_cuda_home()
rwgk Sep 20, 2025
3e5aa1d
Add in ._try_with_conda_prefix()
rwgk Sep 20, 2025
5b3c5eb
Fix anchor_point if IS_WINDOWS
rwgk Sep 22, 2025
233ad08
Untangle CTK/Conda subdirs_list
rwgk Sep 22, 2025
2642d35
Bug fix (in test code only): logical or/and mixup that leads to silen…
rwgk Sep 22, 2025
edff6ec
Make interaction between test_load_nvidia_dynamic_lib.py and child_lo…
rwgk Sep 22, 2025
5ba032c
Show abs_path suitable for copy-pasting in shell
rwgk Sep 22, 2025
cc7d84d
Merge branch 'main' into load_nvidia_dynamic_lib_conda_specific
rwgk Sep 22, 2025
894c6b5
Standardize on `os.environ.get` under cuda_pathfinder/
rwgk Sep 22, 2025
bd5e2ae
predefined → defined automatically
rwgk Sep 22, 2025
ce2ca30
Move quote_for_shell() to new _utils/platform_aware.py
rwgk Sep 22, 2025
9e0b4a8
Universally use cuda.pathfinder._utils.platform_aware.IS_WINDOWS
rwgk Sep 22, 2025
9cd31bb
Merge branch 'main' into load_nvidia_dynamic_lib_conda_specific
rwgk Sep 23, 2025
5fd4801
Bump pathfinder version to 1.3.0 (for release) and add release notes.
rwgk Sep 23, 2025
7498392
Merge branch 'main' into load_nvidia_dynamic_lib_conda_specific
rwgk Sep 23, 2025
2841734
Replace `find_sub_dirs` with `glob.glob` for simplicity. Functionally…
rwgk Sep 23, 2025
c83fa0d
Replace sys.stdout.write → print
rwgk Sep 23, 2025
22999d1
Merge branch 'main' into load_nvidia_dynamic_lib_conda_specific
rwgk Sep 24, 2025
4509626
Add issue 1011 notes to the load_nvidia_dynamic_lib() docstring and t…
rwgk Sep 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

from cuda.pathfinder._dynamic_libs.load_dl_common import DynamicLibNotFoundError
from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import (
IS_WINDOWS,
SITE_PACKAGES_LIBDIRS_LINUX,
SITE_PACKAGES_LIBDIRS_WINDOWS,
is_suppressed_dll_file,
)
from cuda.pathfinder._utils.env_vars import get_cuda_home_or_path
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs, find_sub_dirs_all_sitepackages
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_all_sitepackages
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS


def _no_such_file_in_sub_dirs(
Expand Down Expand Up @@ -80,37 +80,50 @@ def _find_dll_using_nvidia_bin_dirs(
return None


def _find_lib_dir_using_cuda_home(libname: str) -> Optional[str]:
cuda_home = get_cuda_home_or_path()
if cuda_home is None:
return None
subdirs_list: tuple[tuple[str, ...], ...]
def _find_lib_dir_using_anchor_point(libname: str, anchor_point: str, linux_lib_dir: str) -> Optional[str]:
# Resolve paths for the four cases:
# Windows/Linux x nvvm yes/no
if IS_WINDOWS:
if libname == "nvvm": # noqa: SIM108
subdirs_list = (
("nvvm", "bin", "*"), # CTK 13
("nvvm", "bin"), # CTK 12
)
rel_paths = [
"nvvm/bin/*", # CTK 13
"nvvm/bin", # CTK 12
]
else:
subdirs_list = (
("bin", "x64"), # CTK 13
("bin",), # CTK 12
)
rel_paths = [
"bin/x64", # CTK 13
"bin", # CTK 12
]
else:
if libname == "nvvm": # noqa: SIM108
subdirs_list = (("nvvm", "lib64"),)
rel_paths = ["nvvm/lib64"]
else:
subdirs_list = (
("lib64",), # CTK
("lib",), # Conda
)
for sub_dirs in subdirs_list:
dirname: str # work around bug in mypy
for dirname in find_sub_dirs((cuda_home,), sub_dirs):
return dirname
rel_paths = [linux_lib_dir]

for rel_path in rel_paths:
for dirname in sorted(glob.glob(os.path.join(anchor_point, rel_path))):
if os.path.isdir(dirname):
return dirname

return None


def _find_lib_dir_using_cuda_home(libname: str) -> Optional[str]:
cuda_home = get_cuda_home_or_path()
if cuda_home is None:
return None
return _find_lib_dir_using_anchor_point(libname, anchor_point=cuda_home, linux_lib_dir="lib64")


def _find_lib_dir_using_conda_prefix(libname: str) -> Optional[str]:
conda_prefix = os.environ.get("CONDA_PREFIX")
if not conda_prefix:
return None
return _find_lib_dir_using_anchor_point(
libname, anchor_point=os.path.join(conda_prefix, "Library") if IS_WINDOWS else conda_prefix, linux_lib_dir="lib"
)


def _find_so_using_lib_dir(
lib_dir: str, so_basename: str, error_messages: list[str], attachments: list[str]
) -> Optional[str]:
Expand Down Expand Up @@ -146,44 +159,56 @@ def __init__(self, libname: str):
self.libname = libname
self.error_messages: list[str] = []
self.attachments: list[str] = []
self.abs_path = None
self.abs_path: Optional[str] = None

self._try_site_packages()
self._try_with_conda_prefix()

def _try_site_packages(self) -> None:
if IS_WINDOWS:
self.lib_searched_for = f"{libname}*.dll"
self.lib_searched_for = f"{self.libname}*.dll"
if self.abs_path is None:
self.abs_path = _find_dll_using_nvidia_bin_dirs(
libname,
self.libname,
self.lib_searched_for,
self.error_messages,
self.attachments,
)
else:
self.lib_searched_for = f"lib{libname}.so"
self.lib_searched_for = f"lib{self.libname}.so"
if self.abs_path is None:
self.abs_path = _find_so_using_nvidia_lib_dirs(
libname,
self.libname,
self.lib_searched_for,
self.error_messages,
self.attachments,
)

def retry_with_cuda_home_priority_last(self) -> None:
def _try_with_conda_prefix(self) -> None:
conda_lib_dir = _find_lib_dir_using_conda_prefix(self.libname)
if conda_lib_dir is not None:
self._find_using_lib_dir(conda_lib_dir)

def try_with_cuda_home(self) -> None:
cuda_home_lib_dir = _find_lib_dir_using_cuda_home(self.libname)
if cuda_home_lib_dir is not None:
if IS_WINDOWS:
self.abs_path = _find_dll_using_lib_dir(
cuda_home_lib_dir,
self.libname,
self.error_messages,
self.attachments,
)
else:
self.abs_path = _find_so_using_lib_dir(
cuda_home_lib_dir,
self.lib_searched_for,
self.error_messages,
self.attachments,
)
self._find_using_lib_dir(cuda_home_lib_dir)

def _find_using_lib_dir(self, lib_dir: str) -> None:
if IS_WINDOWS:
self.abs_path = _find_dll_using_lib_dir(
lib_dir,
self.libname,
self.error_messages,
self.attachments,
)
else:
self.abs_path = _find_so_using_lib_dir(
lib_dir,
self.lib_searched_for,
self.error_messages,
self.attachments,
)

def raise_if_abs_path_is_None(self) -> str: # noqa: N802
if self.abs_path:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from cuda.pathfinder._dynamic_libs.find_nvidia_dynamic_lib import _FindNvidiaDynamicLib
from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL, load_dependencies
from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import IS_WINDOWS
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS

if IS_WINDOWS:
from cuda.pathfinder._dynamic_libs.load_dl_windows import (
Expand Down Expand Up @@ -44,7 +44,7 @@ def _load_lib_no_cache(libname: str) -> LoadedDL:
loaded = load_with_system_search(libname)
if loaded is not None:
return loaded
found.retry_with_cuda_home_priority_last()
found.try_with_cuda_home()
found.raise_if_abs_path_is_None()

assert found.abs_path is not None # for mypy
Expand All @@ -62,6 +62,18 @@ def load_nvidia_dynamic_lib(libname: str) -> LoadedDL:
Returns:
LoadedDL: Object containing the OS library handle and absolute path.

**Important:**

**Never close the returned handle.** Do **not** call ``dlclose`` (Linux) or
``FreeLibrary`` (Windows) on the ``LoadedDL._handle_uint``.

**Why:** the return value is cached (``functools.cache``) and shared across the
process. Closing the handle can unload the module while other code still uses
it, leading to crashes or subtle failures.

This applies to Linux and Windows. For context, see issue #1011:
https://github.com/NVIDIA/cuda-python/issues/1011

Raises:
DynamicLibNotFoundError: If the library cannot be found or loaded.
RuntimeError: If Python is not 64-bit.
Expand All @@ -77,22 +89,20 @@ def load_nvidia_dynamic_lib(libname: str) -> LoadedDL:
- Scan installed distributions (``site-packages``) to find libraries
shipped in NVIDIA wheels.

2. **OS default mechanisms / Conda environments**
2. **Conda environment**

- Conda installations are discovered via ``CONDA_PREFIX``, which is
defined automatically in activated conda environments (see
https://docs.conda.io/projects/conda-build/en/stable/user-guide/environment-variables.html).

3. **OS default mechanisms**

- Fall back to the native loader:

- Linux: ``dlopen()``

- Windows: ``LoadLibraryW()``

- Conda installations are commonly discovered via:

- Linux: ``$ORIGIN/../lib`` in the ``RPATH`` of the ``python`` binary
(note: this can take precedence over ``LD_LIBRARY_PATH`` and
``/etc/ld.so.conf.d/``).

- Windows: ``%CONDA_PREFIX%\\Library\\bin`` on the system ``PATH``.

- CUDA Toolkit (CTK) system installs with system config updates are often
discovered via:

Expand All @@ -101,7 +111,7 @@ def load_nvidia_dynamic_lib(libname: str) -> LoadedDL:
- Windows: ``C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\vX.Y\\bin``
on the system ``PATH``.

3. **Environment variables**
4. **Environment variables**

- If set, use ``CUDA_HOME`` or ``CUDA_PATH`` (in that order).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
# SUPPORTED_WINDOWS_DLLS
# SUPPORTED_LINUX_SONAMES

import sys

IS_WINDOWS = sys.platform == "win32"
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS

SUPPORTED_LIBNAMES_COMMON = (
# Core CUDA Runtime and Compiler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from typing import Optional

from cuda.pathfinder._headers import supported_nvidia_headers
from cuda.pathfinder._headers.supported_nvidia_headers import IS_WINDOWS
from cuda.pathfinder._utils.env_vars import get_cuda_home_or_path
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_all_sitepackages
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS


def _abs_norm(path: Optional[str]) -> Optional[str]:
Expand Down Expand Up @@ -90,7 +90,7 @@ def _find_ctk_header_directory(libname: str) -> Optional[str]:
if _joined_isfile(hdr_dir, h_basename):
return hdr_dir

conda_prefix = os.getenv("CONDA_PREFIX")
conda_prefix = os.environ.get("CONDA_PREFIX")
if conda_prefix: # noqa: SIM102
if result := _find_based_on_conda_layout(libname, h_basename, conda_prefix):
return result
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import sys
from typing import Final

IS_WINDOWS = sys.platform == "win32"
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS

SUPPORTED_HEADERS_CTK_COMMON = {
"cccl": "cuda/std/version",
Expand Down
18 changes: 18 additions & 0 deletions cuda_pathfinder/cuda/pathfinder/_utils/platform_aware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import sys

IS_WINDOWS = sys.platform == "win32"


def quote_for_shell(s: str) -> str:
if IS_WINDOWS:
# This is a relatively heavy import; keep pathfinder if possible.
from subprocess import list2cmdline # nosec B404

return list2cmdline([s])
else:
import shlex

return shlex.quote(s)
2 changes: 1 addition & 1 deletion cuda_pathfinder/cuda/pathfinder/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

__version__ = "1.2.3"
__version__ = "1.3.0"
4 changes: 4 additions & 0 deletions cuda_pathfinder/docs/nv-versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"version": "latest",
"url": "https://nvidia.github.io/cuda-python/cuda-pathfinder/latest/"
},
{
"version": "1.3.0",
"url": "https://nvidia.github.io/cuda-python/cuda-pathfinder/1.3.0/"
},
{
"version": "1.2.3",
"url": "https://nvidia.github.io/cuda-python/cuda-pathfinder/1.2.3/"
Expand Down
1 change: 1 addition & 0 deletions cuda_pathfinder/docs/source/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Release Notes
.. toctree::
:maxdepth: 3

1.3.0 <release/1.3.0-notes>
1.2.3 <release/1.2.3-notes>
1.2.2 <release/1.2.2-notes>
1.2.1 <release/1.2.1-notes>
Expand Down
33 changes: 33 additions & 0 deletions cuda_pathfinder/docs/source/release/1.3.0-notes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.. SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
.. SPDX-License-Identifier: Apache-2.0

.. module:: cuda.pathfinder

``cuda-pathfinder`` 1.3.0 Release notes
=======================================

Released on Sep 25, 2025

Important update to the ``load_nvidia_dynamic_lib`` documentation
-----------------------------------------------------------------

The documentation for :func:`cuda.pathfinder.load_nvidia_dynamic_lib` now calls out
an important behavior:

* **Do not close** the OS handle returned (``LoadedDL._handle_uint``). Avoid
``dlclose`` (Linux) and ``FreeLibrary`` (Windows).

* **Reason:** the function's result is cached (``functools.cache``) and shared across
the process. Closing the handle can unload the module while other code still holds
a reference, which may cause crashes or latent errors.

This note is documentation‑only; there is no API change in 1.3.0. See issue #1011
for background and discussion:
https://github.com/NVIDIA/cuda-python/issues/1011

Highlights
----------

* Add a Conda-specific search into ``load_nvidia_dynamic_lib()``.
This makes the search more intuitive and predictable.
(`PR #1003 <https://github.com/NVIDIA/cuda-python/pull/1003>`_)
Loading