Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
Expand Up @@ -80,10 +80,7 @@ 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
def _find_lib_dir_using_anchor_point(libname: str, anchor_point: str, linux_lib_dir: str) -> Optional[str]:
subdirs_list: tuple[tuple[str, ...], ...]
if IS_WINDOWS:
if libname == "nvvm": # noqa: SIM108
Expand All @@ -100,17 +97,30 @@ def _find_lib_dir_using_cuda_home(libname: str) -> Optional[str]:
if libname == "nvvm": # noqa: SIM108
subdirs_list = (("nvvm", "lib64"),)
else:
subdirs_list = (
("lib64",), # CTK
("lib",), # Conda
)
subdirs_list = ((linux_lib_dir,),)
for sub_dirs in subdirs_list:
dirname: str # work around bug in mypy
for dirname in find_sub_dirs((cuda_home,), sub_dirs):
for dirname in find_sub_dirs((anchor_point,), sub_dirs):
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.getenv("CONDA_PREFIX")
Comment thread
cpcloud marked this conversation as resolved.
Outdated
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"
)
Comment thread
kkraus14 marked this conversation as resolved.


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 +156,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()
Comment thread
leofang marked this conversation as resolved.
Outdated

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:
Comment thread
rwgk marked this conversation as resolved.
Outdated
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:
Comment thread
rwgk marked this conversation as resolved.
Outdated
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:
Copy link
Copy Markdown
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
Copy Markdown
Contributor 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.

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
Comment thread
leofang marked this conversation as resolved.
Original file line number Diff line number Diff line change
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 Down Expand Up @@ -77,22 +77,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
predefined in activated conda environments (see
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
predefined in activated conda environments (see
defined in activated conda environments (see

I don't think the pre prefix is making a useful distinction beyond just "defined".

Copy link
Copy Markdown
Contributor Author

@rwgk rwgk Sep 22, 2025

Choose a reason for hiding this comment

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

Changed to "defined automatically": commit bd5e2ae

This is for people not-so-familiar with Conda. That one extra word is to reassure them that they don't have to think about defining the variable themselves.

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 +99,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
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.2.4a0"
13 changes: 10 additions & 3 deletions cuda_pathfinder/tests/child_load_nvidia_dynamic_lib_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
# lightweight module. That avoids re-importing the test module (and
# repeating its potentially expensive setup) in every child process.

import json
import os
import sys
import traceback


def build_child_process_failed_for_libname_message(libname, result):
Expand All @@ -24,15 +26,20 @@ def validate_abs_path(abs_path):


def child_process_func(libname):
from cuda.pathfinder import load_nvidia_dynamic_lib
from cuda.pathfinder import DynamicLibNotFoundError, load_nvidia_dynamic_lib
from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import _load_lib_no_cache
from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import (
IS_WINDOWS,
SUPPORTED_LINUX_SONAMES,
SUPPORTED_WINDOWS_DLLS,
)

loaded_dl_fresh = load_nvidia_dynamic_lib(libname)
try:
loaded_dl_fresh = load_nvidia_dynamic_lib(libname)
except DynamicLibNotFoundError:
sys.stdout.write("CHILD_LOAD_NVIDIA_DYNAMIC_LIB_HELPER_DYNAMIC_LIB_NOT_FOUND_ERROR:\n")
Comment thread
leofang marked this conversation as resolved.
Outdated
traceback.print_exc(file=sys.stdout)
return
if loaded_dl_fresh.was_already_loaded_from_elsewhere:
raise RuntimeError("loaded_dl_fresh.was_already_loaded_from_elsewhere")
validate_abs_path(loaded_dl_fresh.abs_path)
Expand All @@ -50,4 +57,4 @@ def child_process_func(libname):
raise RuntimeError(f"not os.path.samefile({loaded_dl_no_cache.abs_path=!r}, {loaded_dl_fresh.abs_path=!r})")
validate_abs_path(loaded_dl_no_cache.abs_path)

sys.stdout.write(f"{loaded_dl_fresh.abs_path!r}\n")
sys.stdout.write(json.dumps(loaded_dl_fresh.abs_path) + "\n")
Comment thread
leofang marked this conversation as resolved.
Outdated
26 changes: 22 additions & 4 deletions cuda_pathfinder/tests/test_load_nvidia_dynamic_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
# SPDX-License-Identifier: Apache-2.0

import functools
import json
import os
import shlex
from subprocess import list2cmdline # nosec B404
from unittest.mock import patch

import pytest
Expand Down Expand Up @@ -90,6 +93,12 @@ def _get_libnames_for_test_load_nvidia_dynamic_lib():
return tuple(result)


def _quote_for_shell_copypaste(s):
Comment thread
leofang marked this conversation as resolved.
Outdated
if os.name == "nt":
return list2cmdline([s])
return shlex.quote(s)
Comment thread
leofang marked this conversation as resolved.
Outdated


@pytest.mark.parametrize("libname", _get_libnames_for_test_load_nvidia_dynamic_lib())
def test_load_nvidia_dynamic_lib(info_summary_append, libname):
# We intentionally run each dynamic library operation in a child process
Expand All @@ -98,9 +107,18 @@ def test_load_nvidia_dynamic_lib(info_summary_append, libname):
# interfere across test cases and lead to nondeterministic or platform-specific failures.
timeout = 120 if supported_nvidia_libs.IS_WINDOWS else 30
result = spawned_process_runner.run_in_spawned_child_process(child_process_func, args=(libname,), timeout=timeout)
if result.returncode == 0:
info_summary_append(f"abs_path={result.stdout.rstrip()}")
elif STRICTNESS == "see_what_works" or "DynamicLibNotFoundError: Failure finding " in result.stderr:

def raise_child_process_failed():
raise RuntimeError(build_child_process_failed_for_libname_message(libname, result))

if result.returncode != 0:
raise_child_process_failed()
assert not result.stderr
if result.stdout.startswith("CHILD_LOAD_NVIDIA_DYNAMIC_LIB_HELPER_DYNAMIC_LIB_NOT_FOUND_ERROR:"):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there some reason not to use stderr here, since that is its intended use (communicating errors)?

Copy link
Copy Markdown
Contributor Author

@rwgk rwgk Sep 22, 2025

Choose a reason for hiding this comment

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

That was a deliberate choice I made while working on commit edff6ec.

See the bug-fix commit right before (2642d35) for why I worked on it.

The requirements on my mind:

  • I didn't want to start fiddling with exit codes (result.returncode).

  • I wanted to make it so that a non-zero error code always leads to an exception.

  • I also wanted to get away from searching for substrings that are not guaranteed to be conclusive: "DynamicLibNotFoundError: Failure finding " in result.stderr

So I introduced a tag that is practically certain to be unique: CHILD_LOAD_NVIDIA_DYNAMIC_LIB_HELPER_DYNAMIC_LIB_NOT_FOUND_ERROR:, to be sent via stdout for the reasons above.

I added assert os.path.isfile(abs_path) # double-check the abs_path to be sure if I miss the tag somehow, it'll not slip through unnoticed.

if STRICTNESS == "all_must_work":
raise_child_process_failed()
info_summary_append(f"Not found: {libname=!r}")
else:
raise RuntimeError(build_child_process_failed_for_libname_message(libname, result))
abs_path = json.loads(result.stdout.rstrip())
Comment thread
leofang marked this conversation as resolved.
info_summary_append(f"abs_path={_quote_for_shell_copypaste(abs_path)}")
assert os.path.isfile(abs_path) # double-check the abs_path
Loading