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
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ repos:
- id: cython-lint
args: [--no-pycodestyle]
exclude: ^cuda_bindings/
additional_dependencies:
- Cython==3.2.9


default_language_version:
Expand Down
63 changes: 48 additions & 15 deletions cuda_pathfinder/cuda/pathfinder/_dynamic_libs/descriptor_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,19 @@ class DescriptorSpec:
packaged_with: PackagedWith
linux_sonames: tuple[str, ...] = ()
windows_dlls: tuple[str, ...] = ()
windows_dll_fallback_globs: tuple[str, ...] = ()
supported_windows_arch: tuple[WindowsArch, ...] = ()
site_packages_linux: tuple[str, ...] = ()
site_packages_windows: WindowsSearchDirs = WindowsSearchDirs()
dependencies: tuple[str, ...] = ()
optional_dependencies: tuple[str, ...] = ()
anchor_rel_dirs_linux: tuple[str, ...] = ("lib64", "lib")
anchor_rel_dirs_windows: WindowsSearchDirs = DEFAULT_WINDOWS_CTK_ANCHOR_DIRS
install_root_env_vars_linux: tuple[str, ...] = ()
install_root_env_rel_dirs_linux: tuple[str, ...] = ()
install_root_env_vars_windows: tuple[str, ...] = ()
install_root_env_rel_dirs_windows: WindowsSearchDirs = WindowsSearchDirs()
program_files_root_globs_windows: WindowsSearchDirs = WindowsSearchDirs()
ctk_root_canary_anchor_libnames: tuple[str, ...] = ()
requires_add_dll_directory: bool = False
requires_rtld_deepbind: bool = False
Expand Down Expand Up @@ -353,23 +360,24 @@ class DescriptorSpec:
packaged_with="ctk",
linux_sonames=("libcupti.so.12", "libcupti.so.13"),
windows_dlls=(
"cupti64_2026.3.0.dll",
"cupti64_2026.2.1.dll",
"cupti64_2026.2.0.dll",
"cupti64_2026.1.1.dll",
"cupti64_2026.1.0.dll",
"cupti64_2025.4.1.dll",
"cupti64_2025.3.1.dll",
"cupti64_2025.2.1.dll",
"cupti64_2025.1.1.dll",
"cupti64_2024.3.2.dll",
"cupti64_2024.2.1.dll",
"cupti64_2024.1.1.dll",
"cupti64_2023.3.1.dll",
"cupti64_2023.2.2.dll",
"cupti64_2023.1.1.dll",
"cupti64_2022.4.1.dll",
"cupti64_2023.1.1.dll",
"cupti64_2023.2.2.dll",
"cupti64_2023.3.1.dll",
"cupti64_2024.1.1.dll",
"cupti64_2024.2.1.dll",
"cupti64_2024.3.2.dll",
"cupti64_2025.1.1.dll",
"cupti64_2025.2.1.dll",
"cupti64_2025.3.1.dll",
"cupti64_2025.4.1.dll",
"cupti64_2026.1.0.dll",
"cupti64_2026.1.1.dll",
"cupti64_2026.2.0.dll",
"cupti64_2026.2.1.dll",
"cupti64_2026.3.0.dll",
),
windows_dll_fallback_globs=("cupti64_*.dll",),
supported_windows_arch=("x64", "arm64"),
site_packages_linux=("nvidia/cu13/lib", "nvidia/cuda_cupti/lib"),
site_packages_windows=_ctk_windows_wheel_dirs("nvidia/cu13/bin", "nvidia/cuda_cupti/bin"),
Expand Down Expand Up @@ -411,6 +419,29 @@ class DescriptorSpec:
dependencies=("nvshmem_host",),
requires_rtld_deepbind=True,
),
DescriptorSpec(
name="cudnn",
packaged_with="other",
linux_sonames=("libcudnn.so.9",),
windows_dlls=("cudnn64_9.dll",),
supported_windows_arch=("x64", "arm64"),
site_packages_linux=("nvidia/cudnn/lib",),
site_packages_windows=WindowsSearchDirs.x64_only("nvidia/cudnn/bin"),
dependencies=("cublasLt",),
optional_dependencies=("nvrtc",),
install_root_env_vars_linux=("CUDNN_PATH",),
install_root_env_rel_dirs_linux=("lib", "lib64"),
# The ARM64 layout is verified only for the standalone archive rooted
# at CUDNN_PATH, not for conda, CUDA_PATH, or Program Files installs.
anchor_rel_dirs_windows=WindowsSearchDirs.x64_only("bin/x64", "bin"),
install_root_env_vars_windows=("CUDNN_PATH",),
install_root_env_rel_dirs_windows=WindowsSearchDirs(
x64=("bin/x64", "bin"),
arm64=("bin/arm64",),
),
program_files_root_globs_windows=WindowsSearchDirs.x64_only("NVIDIA/CUDNN/v9.*"),
requires_add_dll_directory=True,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non-blocking edge case: this side effect is skipped when cudnn64_9.dll was already loaded from a late root such as CUDNN_PATH or Program Files. _load_lib_no_cache() passes find is not None to the already-loaded check, but find contains only the early wheel/conda result; the function then returns before late-root discovery. That can leave cuDNN's lazily loaded component DLLs undiscoverable. Could the already-loaded path register its resolved module directory whenever requires_add_dll_directory is set, independent of the early-find boolean, with a regression test for a preloaded standalone install?

),
DescriptorSpec(
name="cusolverMp",
packaged_with="other",
Expand Down Expand Up @@ -530,6 +561,8 @@ class DescriptorSpec:
packaged_with="other",
linux_sonames=("libnccl.so.2",),
site_packages_linux=("nvidia/nccl/lib",),
install_root_env_vars_linux=("NCCL_HOME",),
install_root_env_rel_dirs_linux=("lib", "lib64", "build/lib"),
),
DescriptorSpec(
name="nvpl_fftw",
Expand Down
15 changes: 15 additions & 0 deletions cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,20 @@ class LoadedDL:


def load_dependencies(desc: LibDescriptor, load_func: Callable[[str], LoadedDL]) -> None:
"""Load required dependencies, then best-effort runtime dependencies.

A plain ``DynamicLibNotFoundError`` from an optional dependency is
suppressed. More specific contract errors and failures while loading a
dependency that was found remain errors.
"""
for dep in desc.dependencies:
load_func(dep)
for dep in desc.optional_dependencies:
try:
load_func(dep)
except DynamicLibNotFoundError as exc:
# Both public contract errors inherit DynamicLibNotFoundError, but
# neither an unknown descriptor nor platform incompatibility means
# that an optional runtime component is simply absent.
if type(exc) is not DynamicLibNotFoundError:
raise
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def _candidate_sonames(desc: LibDescriptor) -> list[str]:

if sys.platform == "linux":

def check_if_already_loaded_from_elsewhere(desc: LibDescriptor, _have_abs_path: bool) -> LoadedDL | None:
def check_if_already_loaded_from_elsewhere(desc: LibDescriptor) -> LoadedDL | None:
for soname in _candidate_sonames(desc):
try:
handle = ctypes.CDLL(soname, mode=os.RTLD_NOLOAD)
Expand All @@ -160,7 +160,7 @@ def _load_lib(desc: LibDescriptor, filename: str) -> ctypes.CDLL:
return ctypes.CDLL(filename, cdll_mode)
else:

def check_if_already_loaded_from_elsewhere(_desc: LibDescriptor, _have_abs_path: bool) -> LoadedDL | None:
def check_if_already_loaded_from_elsewhere(_desc: LibDescriptor) -> LoadedDL | None:
raise RuntimeError(f"check_if_already_loaded_from_elsewhere() is not supported on platform {sys.platform!r}")

def _load_lib(_desc: LibDescriptor, _filename: str) -> ctypes.CDLL:
Expand Down
20 changes: 12 additions & 8 deletions cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import struct
import sys
import warnings
from collections.abc import Iterator
from typing import TYPE_CHECKING

from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL
Expand Down Expand Up @@ -120,15 +121,19 @@ def abs_path_for_dynamic_library(libname: str, handle: ctypes.wintypes.HMODULE)
return buffer.value


def check_if_already_loaded_from_elsewhere(desc: LibDescriptor, have_abs_path: bool) -> LoadedDL | None:
for dll_name in desc.windows_dlls:
def _candidate_dll_names(desc: LibDescriptor) -> Iterator[str]:
"""Yield tabulated DLL names from newest to oldest."""
return reversed(desc.windows_dlls)


def check_if_already_loaded_from_elsewhere(desc: LibDescriptor) -> LoadedDL | None:
for dll_name in _candidate_dll_names(desc):
handle = kernel32.GetModuleHandleW(dll_name)
if handle:
abs_path = abs_path_for_dynamic_library(desc.name, handle)
if have_abs_path and desc.requires_add_dll_directory:
# This is a side-effect if the pathfinder loads the library via
# load_with_abs_path(). To make the side-effect more deterministic,
# activate it even if the library was already loaded from elsewhere.
if desc.requires_add_dll_directory:
# Match load_with_abs_path(): lazy component DLLs need the directory
# of the module that is actually loaded, regardless of how it arrived.
add_dll_directory(abs_path)
return LoadedDL(abs_path, True, ctypes_handle_to_unsigned_int(handle), "was-already-loaded-from-elsewhere")
return None
Expand All @@ -149,8 +154,7 @@ def load_with_system_search(desc: LibDescriptor) -> LoadedDL | None:
Returns:
A LoadedDL object if successful, None if the library cannot be loaded
"""
# Reverse tabulated names to achieve new -> old search order.
for dll_name in reversed(desc.windows_dlls):
for dll_name in _candidate_dll_names(desc):
handle = kernel32.LoadLibraryExW(dll_name, None, 0)
if handle:
abs_path = abs_path_for_dynamic_library(desc.name, handle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _load_driver_lib_no_cache(desc: LibDescriptor) -> LoadedDL:
native loader mechanisms, so the full CTK search cascade (site-packages,
conda, CUDA_PATH, canary) is unnecessary.
"""
loaded = LOADER.check_if_already_loaded_from_elsewhere(desc, False)
loaded = LOADER.check_if_already_loaded_from_elsewhere(desc)
if loaded is not None:
return loaded
loaded = LOADER.load_with_system_search(desc)
Expand Down Expand Up @@ -174,9 +174,7 @@ def _load_lib_no_cache(libname: str) -> LoadedDL:
find = run_find_steps(ctx, EARLY_FIND_STEPS)

# Phase 2: Cross-cutting — already-loaded check and dependency loading.
# The already-loaded check on Windows uses the "have we found a path?"
# flag to decide whether to apply AddDllDirectory side-effects.
loaded = LOADER.check_if_already_loaded_from_elsewhere(desc, find is not None)
loaded = LOADER.check_if_already_loaded_from_elsewhere(desc)
load_dependencies(desc, load_nvidia_dynamic_lib)
if loaded is not None:
return loaded
Expand Down Expand Up @@ -275,12 +273,21 @@ def load_nvidia_dynamic_lib(libname: str) -> LoadedDL:

4. **Environment variables**

- If set, use ``CUDA_PATH`` or ``CUDA_HOME`` (in that order).
On Windows, this is the typical way system-installed CTK DLLs are
located. Note that the NVIDIA CTK installer automatically
- First search library-specific roots declared by the descriptor,
such as ``CUDNN_PATH`` and ``NCCL_HOME``, using their
platform-specific product layouts. Then use ``CUDA_PATH`` or
``CUDA_HOME`` (in that order).
On Windows, ``CUDA_PATH`` is the typical way system-installed CTK
DLLs are located. Note that the NVIDIA CTK installer automatically
adds ``CUDA_PATH`` to the system-wide environment.

5. **CTK root canary probe (discoverable libs only)**
5. **Windows Program Files (configured libraries only)**

- Search descriptor-configured standalone installation roots, such
as versioned x64 cuDNN directories under ``ProgramFiles``, using
the general per-library anchor layout.

6. **CTK root canary probe (discoverable libs only)**

- For selected libraries whose shared object doesn't reside on the
standard linker path (currently ``nvvm``), attempt to derive CTK
Expand All @@ -298,8 +305,8 @@ def load_nvidia_dynamic_lib(libname: str) -> LoadedDL:
0. Already loaded in the current process
1. OS default mechanisms (``dlopen`` / ``LoadLibraryExW``)

The CTK-specific steps (site-packages, conda, ``CUDA_PATH``, canary
probe) are skipped entirely.
The non-driver steps (site-packages, conda, environment roots,
``ProgramFiles``, and canary probe) are skipped entirely.

Notes:
The search is performed **per library**. There is currently no mechanism to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@


class PlatformLoader(Protocol):
def check_if_already_loaded_from_elsewhere(self, desc: LibDescriptor, have_abs_path: bool) -> LoadedDL | None: ...
def check_if_already_loaded_from_elsewhere(self, desc: LibDescriptor) -> LoadedDL | None: ...

def load_with_system_search(self, desc: LibDescriptor) -> LoadedDL | None: ...

Expand Down
Loading
Loading