Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 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
3e7ca0e
Merge branch 'main' into load_nvidia_dynamic_lib_conda_specific
rwgk Sep 25, 2025
917803e
Add issue 1011 notes to the load_nvidia_dynamic_lib() docstring and t…
rwgk Sep 25, 2025
f1d39d9
Merge branch 'main' into load_nvidia_dynamic_lib_conda_specific
rwgk Sep 25, 2025
37414a5
Change release date to Sep 29, 2025
rwgk Sep 25, 2025
1bde30e
Remove dead code (noticed in passing)
rwgk Sep 25, 2025
5056e46
Move the `.try_site_packages()`, `.try_with_conda_prefix()` calls int…
rwgk Sep 25, 2025
2c336c9
Bug fix: `.try_with_conda_prefix()` only if `.try_site_packages()` di…
rwgk Sep 25, 2025
735ca85
Merge branch 'main' into load_nvidia_dynamic_lib_conda_specific
rwgk Sep 25, 2025
3d6ea31
Move `self.lib_searched_for` assignment from `.try_site_packages()` t…
rwgk Sep 25, 2025
ab2a349
Remove `self.abs_path` from `_FindNvidiaDynamicLib`
rwgk Sep 25, 2025
44ed3e6
Systematically replace `.. module:: cuda.pathfinder` → `.. py:current…
rwgk Sep 25, 2025
45fb2cd
Fix indentation issues in contribute.rst, mainly to resolve docutils …
rwgk Sep 25, 2025
d19d3ec
Resolve last remaining sphinx warning:
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
@@ -1,21 +1,20 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import functools
import glob
import os
from collections.abc import Sequence
from typing import Optional

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 +79,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 @@ -144,55 +156,55 @@ def _find_dll_using_lib_dir(
class _FindNvidiaDynamicLib:
def __init__(self, libname: str):
self.libname = libname
if IS_WINDOWS:
self.lib_searched_for = f"{libname}*.dll"
else:
self.lib_searched_for = f"lib{libname}.so"
self.error_messages: list[str] = []
self.attachments: list[str] = []
self.abs_path = None
self.abs_path: Optional[str] = None

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

def retry_with_cuda_home_priority_last(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,
)

def raise_if_abs_path_is_None(self) -> str: # noqa: N802
if self.abs_path:
return self.abs_path
return _find_so_using_nvidia_lib_dirs(
self.libname,
self.lib_searched_for,
self.error_messages,
self.attachments,
)

def try_with_conda_prefix(self) -> Optional[str]:
return self._find_using_lib_dir(_find_lib_dir_using_conda_prefix(self.libname))

def try_with_cuda_home(self) -> Optional[str]:
return self._find_using_lib_dir(_find_lib_dir_using_cuda_home(self.libname))

def _find_using_lib_dir(self, lib_dir: Optional[str]) -> Optional[str]:
if lib_dir is None:
return None
if IS_WINDOWS:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is pre-existing, but it seems like this is begging for some sort of interface that you can isolate into modules and then import at the top level based on platform, similar to how to the os and pathlib modules work. This would likely avoid a lot of in-method or in-function branching.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this twice before but backed off both times after seeing where it's going, and determining it's a net loss in terms of code organization and readability.

In contrast, it was clearly a net win for the rest of the code underneath load_nvidia_dynamic_lib.py, which also started out as interleaved code.

return _find_dll_using_lib_dir(
lib_dir,
self.libname,
self.error_messages,
self.attachments,
)
else:
return _find_so_using_lib_dir(
lib_dir,
self.lib_searched_for,
self.error_messages,
self.attachments,
)

def raise_not_found_error(self) -> None:
err = ", ".join(self.error_messages)
att = "\n".join(self.attachments)
raise DynamicLibNotFoundError(f'Failure finding "{self.lib_searched_for}": {err}\n{att}')


@functools.cache
def find_nvidia_dynamic_lib(libname: str) -> str:
return _FindNvidiaDynamicLib(libname).raise_if_abs_path_is_None()
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 All @@ -24,13 +24,15 @@


def _load_lib_no_cache(libname: str) -> LoadedDL:
found = _FindNvidiaDynamicLib(libname)
have_abs_path = found.abs_path is not None
finder = _FindNvidiaDynamicLib(libname)
abs_path = finder.try_site_packages()
if abs_path is None:
abs_path = finder.try_with_conda_prefix()

# If the library was already loaded by someone else, reproduce any OS-specific
# side-effects we would have applied on a direct absolute-path load (e.g.,
# AddDllDirectory on Windows for libs that require it).
loaded = check_if_already_loaded_from_elsewhere(libname, have_abs_path)
loaded = check_if_already_loaded_from_elsewhere(libname, abs_path is not None)

# Load dependencies regardless of who loaded the primary lib first.
# Doing this *after* the side-effect ensures dependencies resolve consistently
Expand All @@ -40,15 +42,15 @@ def _load_lib_no_cache(libname: str) -> LoadedDL:
if loaded is not None:
return loaded

if not have_abs_path:
if abs_path is None:
loaded = load_with_system_search(libname)
if loaded is not None:
return loaded
found.retry_with_cuda_home_priority_last()
found.raise_if_abs_path_is_None()
abs_path = finder.try_with_cuda_home()
if abs_path is None:
finder.raise_not_found_error()

assert found.abs_path is not None # for mypy
return load_with_abs_path(libname, found.abs_path)
return load_with_abs_path(libname, abs_path)


@functools.cache
Expand All @@ -62,6 +64,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 +91,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 +113,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
2 changes: 1 addition & 1 deletion cuda_pathfinder/docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_static_path = [] # ["_static"] does not exist in our environment

# skip cmdline prompts
copybutton_exclude = ".linenos, .gp"
Expand Down
Loading
Loading