Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 7 additions & 2 deletions cuda_pathfinder/cuda/pathfinder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
find_nvidia_binary_utility as find_nvidia_binary_utility,
)
from cuda.pathfinder._binaries.supported_nvidia_binaries import SUPPORTED_BINARIES as _SUPPORTED_BINARIES
from cuda.pathfinder._dynamic_libs.find_nvidia_dynamic_lib import LocatedDynamicLib as LocatedDynamicLib
from cuda.pathfinder._dynamic_libs.find_nvidia_dynamic_lib import find_nvidia_dynamic_lib as find_nvidia_dynamic_lib
from cuda.pathfinder._dynamic_libs.find_nvidia_dynamic_lib import (
locate_nvidia_dynamic_lib as locate_nvidia_dynamic_lib,
)
from cuda.pathfinder._dynamic_libs.load_dl_common import (
DynamicLibNotAvailableError as DynamicLibNotAvailableError,
)
Expand Down Expand Up @@ -76,8 +81,8 @@
SUPPORTED_BINARY_UTILITIES = _SUPPORTED_BINARIES

#: Tuple of CUDA Toolkit dynamic library names supported by
#: :func:`load_nvidia_dynamic_lib` for the current operating system and
#: interpreter architecture.
#: :func:`load_nvidia_dynamic_lib` and :func:`locate_nvidia_dynamic_lib` for
#: the current operating system and interpreter architecture.
SUPPORTED_NVIDIA_LIBNAMES = _SUPPORTED_NVIDIA_LIBNAMES

#: Tuple of supported bitcode library names that can be resolved
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import functools
import os
from dataclasses import dataclass

from cuda.pathfinder._dynamic_libs.lib_descriptor import LIB_DESCRIPTORS
from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import (
_DRIVER_ONLY_LIBNAMES,
_resolve_system_loaded_abs_path_in_subprocess,
_try_ctk_root_canary,
check_libname,
raise_driver_lib_not_found,
)
from cuda.pathfinder._dynamic_libs.search_steps import (
EARLY_FIND_STEPS,
LATE_FIND_STEPS,
FindResult,
SearchContext,
run_find_steps,
)


@dataclass(frozen=True)
class LocatedDynamicLib:
"""Information about a located dynamic library."""

name: str
abs_path: str
filename: str
found_via: str


def _find_with_system_search(libname: str) -> FindResult | None:
# The native loader search can only be observed by loading, so it runs in
# a fresh subprocess and the caller's process stays untouched.
abs_path = _resolve_system_loaded_abs_path_in_subprocess(libname)
Comment thread
srimanachanta marked this conversation as resolved.
if abs_path is None:
return None
return FindResult(abs_path, "system-search")


def _locate_lib(libname: str) -> FindResult:
desc = LIB_DESCRIPTORS[libname]

if libname in _DRIVER_ONLY_LIBNAMES:
find = _find_with_system_search(libname)
if find is None:
raise_driver_lib_not_found(desc)
return find

ctx = SearchContext(desc)
find = run_find_steps(ctx, EARLY_FIND_STEPS)

# load_nvidia_dynamic_lib loads the required dependencies at this point and
# fails if one of them cannot be found.
for dep in desc.dependencies:
locate_nvidia_dynamic_lib(dep)

if find is not None:
return find
Comment thread
coderabbitai[bot] marked this conversation as resolved.

find = _find_with_system_search(libname)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Major · Data Integrity & Integration · Heavy lift

Supported by static analysis

Script executed:

f1=cuda_pathfinder/cuda/pathfinder/_dynamic_libs/find_nvidia_dynamic_lib.py
f2=cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_nvidia_dynamic_lib.py
ls -l "$f1" "$f2"
ast-grep outline "$f1" --view expanded
ast-grep outline "$f2" --view expanded
rg -n -F -e '_find_with_system_search' -e 'dependenc' -e 'LOADER' -- "$f1" "$f2" || test "$?" -eq 1
nl -ba "$f1" | sed -n '1,140p'

Repository: NVIDIA/cuda-python

Length of output: 10018


important: Locate the required dependencies in the probe subprocess before the system search.

_locate_lib locates each required dependency with locate_nvidia_dynamic_lib(dep), but it does not load it. The system-search probe then calls _resolve_system_loaded_abs_path_in_subprocess(libname), which takes only the library name and loads only that library in a fresh process. load_nvidia_dynamic_lib loads the same dependencies before its native-loader search.

When the target is reached through the system search and a required dependency exists only at a wheel path, the probe's native loader may not resolve that dependency. The probe then returns None, and _locate_lib can raise DynamicLibNotFoundError. load_nvidia_dynamic_lib can load the same target in that environment. The locate_nvidia_dynamic_lib docstring says the result is the file that load_nvidia_dynamic_lib would load in a fresh process. Its Notes list only already-loaded libraries and RPATH as sources of difference, not dependency preloading.

Pass the located dependency paths to the probe subprocess. The child should load them by absolute path before it loads the target. This keeps the caller's process unloaded. If the native-loader result is cached by library name alone, include the dependency paths in the cache key. Otherwise a cached result can be reused with a different dependency state.

if find is not None:
return find

find = run_find_steps(ctx, LATE_FIND_STEPS)
if find is not None:
return find

if desc.ctk_root_canary_anchor_libnames:
canary_abs_path = _try_ctk_root_canary(ctx)
if canary_abs_path is not None:
return FindResult(canary_abs_path, "system-ctk-root")

ctx.raise_not_found()


def locate_nvidia_dynamic_lib(libname: str) -> LocatedDynamicLib:
"""Locate an NVIDIA dynamic library by name without loading it.

Returns the file that :func:`load_nvidia_dynamic_lib` would load in a fresh
process. Use it where a path is needed but the library must not be loaded
into the current process, for example to pass a library to a build system.

Like :func:`load_nvidia_dynamic_lib`, it fails if a library that ``libname``
requires cannot be found. Optional dependencies are not checked.

Args:
libname (str): The short name of the library to locate (e.g.,
``"cudart"``, ``"nvvm"``, etc.).

Returns:
LocatedDynamicLib: The absolute path of the library file and how it
was found.

Raises:
DynamicLibUnknownError: If ``libname`` is not a recognized library name.
DynamicLibNotAvailableError: If ``libname`` is recognized but not
supported on this platform.
DynamicLibNotFoundError: If the library cannot be found.

Search order:
The steps of :func:`load_nvidia_dynamic_lib`, in the same order, except
that a library already loaded in the current process is not taken into
account:

1. NVIDIA Python wheels (``site-packages``)
2. Conda environment (``CONDA_PREFIX``)
3. OS default mechanisms (``dlopen()`` / ``LoadLibraryExW()``), run in
a fresh Python subprocess
4. Environment variables (library-specific roots, then ``CUDA_PATH`` /
``CUDA_HOME``)
5. Windows Program Files (configured libraries only)
6. CTK root canary probe (discoverable libs only)

Driver libraries (``"cuda"``, ``"nvml"``) use step 3 only.

Notes:
The result can differ from what :func:`load_nvidia_dynamic_lib` returns
in the current process when another component has already loaded the
library, or influences the native loader search (for example through
its ``RPATH``). A file that is found but fails to load is still
returned, since nothing is loaded.

The result of the native loader search (step 3) is cached for the life
of the process and shared with the CTK root canary probe, because each
search starts a Python subprocess.
"""
check_libname(libname)
find = _locate_lib(libname)
return LocatedDynamicLib(
name=libname,
abs_path=find.abs_path,
filename=os.path.basename(find.abs_path),
found_via=find.found_via,
)


@functools.cache
def find_nvidia_dynamic_lib(libname: str) -> str:
"""Find the absolute path of an NVIDIA dynamic library without loading it.

See :func:`locate_nvidia_dynamic_lib` for the search order.

Raises:
DynamicLibUnknownError: If ``libname`` is not a recognized library name.
DynamicLibNotAvailableError: If ``libname`` is recognized but not
supported on this platform.
DynamicLibNotFoundError: If the library cannot be found.
"""
return locate_nvidia_dynamic_lib(libname).abs_path
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import struct
import subprocess
import sys
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, NoReturn

from cuda.pathfinder._dynamic_libs.lib_descriptor import LIB_DESCRIPTORS
from cuda.pathfinder._dynamic_libs.load_dl_common import (
Expand Down Expand Up @@ -66,12 +66,27 @@ def _load_driver_lib_no_cache(desc: LibDescriptor) -> LoadedDL:
loaded = LOADER.load_with_system_search(desc)
if loaded is not None:
return loaded
raise_driver_lib_not_found(desc)


def raise_driver_lib_not_found(desc: LibDescriptor) -> NoReturn:
raise DynamicLibNotFoundError(
f'"{desc.name}" is an NVIDIA driver library and can only be found via'
f" system search. Ensure the NVIDIA display driver is installed."
)


def check_libname(libname: str) -> None:
"""Raise if ``libname`` is unknown or not available on this platform."""
if libname not in _ALL_KNOWN_LIBNAMES:
raise DynamicLibUnknownError(f"Unknown library name: {libname!r}. Known names: {sorted(_ALL_KNOWN_LIBNAMES)}")
if libname not in ALL_AVAILABLE_LIBNAMES:
raise DynamicLibNotAvailableError(
f"Library name {libname!r} is known but not available on {_PLATFORM_NAME}. "
f"Supported names on {_PLATFORM_NAME}: {sorted(ALL_AVAILABLE_LIBNAMES)}"
)


def _coerce_subprocess_output(output: str | bytes | None) -> str:
if isinstance(output, bytes):
return output.decode(errors="replace")
Expand Down Expand Up @@ -320,11 +335,5 @@ def load_nvidia_dynamic_lib(libname: str) -> LoadedDL:
f" Currently running: {pointer_size_bits}-bit Python"
f" {sys.version_info.major}.{sys.version_info.minor}"
)
if libname not in _ALL_KNOWN_LIBNAMES:
raise DynamicLibUnknownError(f"Unknown library name: {libname!r}. Known names: {sorted(_ALL_KNOWN_LIBNAMES)}")
if libname not in ALL_AVAILABLE_LIBNAMES:
raise DynamicLibNotAvailableError(
f"Library name {libname!r} is known but not available on {_PLATFORM_NAME}. "
f"Supported names on {_PLATFORM_NAME}: {sorted(ALL_AVAILABLE_LIBNAMES)}"
)
check_libname(libname)
return _load_lib_no_cache(libname)
7 changes: 5 additions & 2 deletions cuda_pathfinder/docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
``cuda.pathfinder`` API Reference
=================================

The ``cuda.pathfinder`` module provides utilities for loading NVIDIA dynamic libraries,
locating NVIDIA C/C++ header directories, finding CUDA binary utilities, and locating
The ``cuda.pathfinder`` module provides utilities for loading and locating NVIDIA dynamic
libraries, locating NVIDIA C/C++ header directories, finding CUDA binary utilities, and locating
CUDA bitcode and static libraries.

.. NOTE: The source of truth for public APIs is cuda_pathfinder/cuda/pathfinder/__init__.py.
Expand All @@ -21,6 +21,9 @@ CUDA bitcode and static libraries.
SUPPORTED_NVIDIA_LIBNAMES
load_nvidia_dynamic_lib
LoadedDL
find_nvidia_dynamic_lib
locate_nvidia_dynamic_lib
LocatedDynamicLib
DynamicLibNotFoundError
DynamicLibUnknownError
DynamicLibNotAvailableError
Expand Down
Loading
Loading