Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d97a2cc

Browse files
authored
cuda.pathfinder._find_nvidia_header_directory(): add support for CTK libs (#956)
* New supported_nvidia_headers.py, starting with just SUPPORTED_HEADERS_CTK, SUPPORTED_SITE_PACKAGE_HEADER_DIRS_CTK dicts. * Add _find_ctk_header_directory(), currently for site-packages only. * Factor out get_cuda_home_or_path() into new cuda/pathfinder/_utils/env_vars.py * Add CUDA_HOME code in find_nvidia_headers.py * Formalize supported_nvidia_headers.CCCL_LIBNAMES * Add CONDA_PREFIX code in find_nvidia_headers.py * Add `shutil.which("nvcc")` code in find_nvidia_headers.py * Cleanup: add _joined_isfile() helper * find_nvidia_header_directory(): return _abs_norm() * Bump pathfinder version to 1.2.3a0 * Replace libcudacxx,cub,thrust with cccl. Add cuda-toolkit[cccl] to nvidia_wheels_cu12, nvidia_wheels_cu13 * SUPPORTED_HEADERS_CTK_LINUX_ONLY etc. (for cufile) * Insert _find_based_on_conda_layout() * Remove `shutil.which("nvcc")` code (it finds all includes on Windows with conda) * Remove cccl code * conda windows support * Replace cusolver_common.h → cusolverDn.h * UserWarning: Both CUDA_HOME and CUDA_PATH are set but differ * Remove `cccl` again in pyproject.toml * Revert "Remove `cccl` again in pyproject.toml" This reverts commit 826398d. * Revert "Remove cccl code" This reverts commit 4d4ff77. * Remove `cuda-cccl` include path in SUPPORTED_SITE_PACKAGE_HEADER_DIRS_CTK * Apply reviewer suggestion: #956 (comment) * Add find_nvidia_header_directory docstring. * Add _SUPPORTED_HEADERS_CTK to public API * Add cuda_pathfinder 1.2.3 Release notes * Remove leading underscores: _SUPPORTED_HEADERS_CTK, _find_nvidia_header_directory * docstring in __init__.py, using `#: ` Sphinx-specific markup A triple-quoted docstring worked for Sphinx but tripped up the check-docstring-first pre-commit check. * Bump pathfinder version to 1.2.3 (for release) and change release date to Sep 17 * Make comment less ambiguous. * Remove subtitle as suggested by reviewer.
1 parent 365bf07 commit d97a2cc

File tree

14 files changed

+484
-33
lines changed

14 files changed

+484
-33
lines changed
Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4+
"""cuda.pathfinder public APIs"""
5+
46
from cuda.pathfinder._dynamic_libs.load_dl_common import DynamicLibNotFoundError as DynamicLibNotFoundError
57
from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL as LoadedDL
68
from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import load_nvidia_dynamic_lib as load_nvidia_dynamic_lib
79
from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import (
810
SUPPORTED_LIBNAMES as SUPPORTED_NVIDIA_LIBNAMES, # noqa: F401
911
)
10-
from cuda.pathfinder._headers.find_nvidia_headers import (
11-
find_nvidia_header_directory as _find_nvidia_header_directory, # noqa: F401
12-
)
12+
from cuda.pathfinder._headers.find_nvidia_headers import find_nvidia_header_directory as find_nvidia_header_directory
13+
from cuda.pathfinder._headers.supported_nvidia_headers import SUPPORTED_HEADERS_CTK as _SUPPORTED_HEADERS_CTK
1314
from cuda.pathfinder._version import __version__ as __version__
15+
16+
# Indirection to help Sphinx find the docstring.
17+
#: Mapping from short CUDA Toolkit (CTK) library names to their canonical
18+
#: header basenames (used to validate a discovered include directory).
19+
#: Example: ``"cublas" → "cublas.h"``. The key set is platform-aware
20+
#: (e.g., ``"cufile"`` may be Linux-only).
21+
SUPPORTED_HEADERS_CTK = _SUPPORTED_HEADERS_CTK
22+
23+
# Backward compatibility: _find_nvidia_header_directory was added in release 1.2.2.
24+
# It will be removed in release 1.2.4.
25+
_find_nvidia_header_directory = find_nvidia_header_directory

cuda_pathfinder/cuda/pathfinder/_dynamic_libs/find_nvidia_dynamic_lib.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
SITE_PACKAGES_LIBDIRS_WINDOWS,
1515
is_suppressed_dll_file,
1616
)
17+
from cuda.pathfinder._utils.env_vars import get_cuda_home_or_path
1718
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs, find_sub_dirs_all_sitepackages
1819

1920

@@ -79,15 +80,8 @@ def _find_dll_using_nvidia_bin_dirs(
7980
return None
8081

8182

82-
def _get_cuda_home() -> Optional[str]:
83-
cuda_home = os.environ.get("CUDA_HOME")
84-
if cuda_home is None:
85-
cuda_home = os.environ.get("CUDA_PATH")
86-
return cuda_home
87-
88-
8983
def _find_lib_dir_using_cuda_home(libname: str) -> Optional[str]:
90-
cuda_home = _get_cuda_home()
84+
cuda_home = get_cuda_home_or_path()
9185
if cuda_home is None:
9286
return None
9387
subdirs_list: tuple[tuple[str, ...], ...]

cuda_pathfinder/cuda/pathfinder/_headers/find_nvidia_headers.py

Lines changed: 120 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,145 @@
66
import os
77
from typing import Optional
88

9-
from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import IS_WINDOWS
9+
from cuda.pathfinder._headers import supported_nvidia_headers
10+
from cuda.pathfinder._headers.supported_nvidia_headers import IS_WINDOWS
11+
from cuda.pathfinder._utils.env_vars import get_cuda_home_or_path
1012
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_all_sitepackages
1113

1214

13-
@functools.cache
14-
def find_nvidia_header_directory(libname: str) -> Optional[str]:
15-
if libname != "nvshmem":
16-
raise RuntimeError(f"UNKNOWN {libname=}")
15+
def _abs_norm(path: Optional[str]) -> Optional[str]:
16+
if path:
17+
return os.path.normpath(os.path.abspath(path))
18+
return None
19+
20+
21+
def _joined_isfile(dirpath: str, basename: str) -> bool:
22+
return os.path.isfile(os.path.join(dirpath, basename))
1723

18-
if libname == "nvshmem" and IS_WINDOWS:
24+
25+
def _find_nvshmem_header_directory() -> Optional[str]:
26+
if IS_WINDOWS:
1927
# nvshmem has no Windows support.
2028
return None
2129

2230
# Installed from a wheel
2331
nvidia_sub_dirs = ("nvidia", "nvshmem", "include")
2432
hdr_dir: str # help mypy
2533
for hdr_dir in find_sub_dirs_all_sitepackages(nvidia_sub_dirs):
26-
nvshmem_h_path = os.path.join(hdr_dir, "nvshmem.h")
27-
if os.path.isfile(nvshmem_h_path):
34+
if _joined_isfile(hdr_dir, "nvshmem.h"):
2835
return hdr_dir
2936

3037
conda_prefix = os.environ.get("CONDA_PREFIX")
3138
if conda_prefix and os.path.isdir(conda_prefix):
3239
hdr_dir = os.path.join(conda_prefix, "include")
33-
nvshmem_h_path = os.path.join(hdr_dir, "nvshmem.h")
34-
if os.path.isfile(nvshmem_h_path):
40+
if _joined_isfile(hdr_dir, "nvshmem.h"):
3541
return hdr_dir
3642

3743
for hdr_dir in sorted(glob.glob("/usr/include/nvshmem_*"), reverse=True):
38-
nvshmem_h_path = os.path.join(hdr_dir, "nvshmem.h")
39-
if os.path.isfile(nvshmem_h_path):
44+
if _joined_isfile(hdr_dir, "nvshmem.h"):
4045
return hdr_dir
4146

4247
return None
48+
49+
50+
def _find_based_on_ctk_layout(libname: str, h_basename: str, anchor_point: str) -> Optional[str]:
51+
parts = [anchor_point]
52+
if libname == "nvvm":
53+
parts.append(libname)
54+
parts.append("include")
55+
idir = os.path.join(*parts)
56+
if libname == "cccl":
57+
cdir = os.path.join(idir, "cccl") # CTK 13
58+
if _joined_isfile(cdir, h_basename):
59+
return cdir
60+
if _joined_isfile(idir, h_basename):
61+
return idir
62+
return None
63+
64+
65+
def _find_based_on_conda_layout(libname: str, h_basename: str, conda_prefix: str) -> Optional[str]:
66+
if IS_WINDOWS:
67+
anchor_point = os.path.join(conda_prefix, "Library")
68+
if not os.path.isdir(anchor_point):
69+
return None
70+
else:
71+
targets_include_path = glob.glob(os.path.join(conda_prefix, "targets", "*", "include"))
72+
if not targets_include_path:
73+
return None
74+
if len(targets_include_path) != 1:
75+
# Conda does not support multiple architectures.
76+
# QUESTION(PR#956): Do we want to issue a warning?
77+
return None
78+
anchor_point = os.path.dirname(targets_include_path[0])
79+
return _find_based_on_ctk_layout(libname, h_basename, anchor_point)
80+
81+
82+
def _find_ctk_header_directory(libname: str) -> Optional[str]:
83+
h_basename = supported_nvidia_headers.SUPPORTED_HEADERS_CTK[libname]
84+
candidate_dirs = supported_nvidia_headers.SUPPORTED_SITE_PACKAGE_HEADER_DIRS_CTK[libname]
85+
86+
# Installed from a wheel
87+
for cdir in candidate_dirs:
88+
hdr_dir: str # help mypy
89+
for hdr_dir in find_sub_dirs_all_sitepackages(tuple(cdir.split("/"))):
90+
if _joined_isfile(hdr_dir, h_basename):
91+
return hdr_dir
92+
93+
conda_prefix = os.getenv("CONDA_PREFIX")
94+
if conda_prefix: # noqa: SIM102
95+
if result := _find_based_on_conda_layout(libname, h_basename, conda_prefix):
96+
return result
97+
98+
cuda_home = get_cuda_home_or_path()
99+
if cuda_home: # noqa: SIM102
100+
if result := _find_based_on_ctk_layout(libname, h_basename, cuda_home):
101+
return result
102+
103+
return None
104+
105+
106+
@functools.cache
107+
def find_nvidia_header_directory(libname: str) -> Optional[str]:
108+
"""Locate the header directory for a supported NVIDIA library.
109+
110+
Args:
111+
libname (str): The short name of the library whose headers are needed
112+
(e.g., ``"nvrtc"``, ``"cusolver"``, ``"nvshmem"``).
113+
114+
Returns:
115+
str or None: Absolute path to the discovered header directory, or ``None``
116+
if the headers cannot be found.
117+
118+
Raises:
119+
RuntimeError: If ``libname`` is not in the supported set.
120+
121+
Search order:
122+
1. **NVIDIA Python wheels**
123+
124+
- Scan installed distributions (``site-packages``) for header layouts
125+
shipped in NVIDIA wheels (e.g., ``cuda-toolkit[nvrtc]``).
126+
127+
2. **Conda environments**
128+
129+
- Check Conda-style installation prefixes, which use platform-specific
130+
include directory layouts.
131+
132+
3. **CUDA Toolkit environment variables**
133+
134+
- Use ``CUDA_HOME`` or ``CUDA_PATH`` (in that order).
135+
136+
Notes:
137+
- The ``SUPPORTED_HEADERS_CTK`` dictionary maps each supported CUDA Toolkit
138+
(CTK) library to the name of its canonical header (e.g., ``"cublas" →
139+
"cublas.h"``). This is used to verify that the located directory is valid.
140+
141+
- The only supported non-CTK library at present is ``nvshmem``.
142+
"""
143+
144+
if libname == "nvshmem":
145+
return _abs_norm(_find_nvshmem_header_directory())
146+
147+
if libname in supported_nvidia_headers.SUPPORTED_HEADERS_CTK:
148+
return _abs_norm(_find_ctk_header_directory(libname))
149+
150+
raise RuntimeError(f"UNKNOWN {libname=}")
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import sys
5+
from typing import Final
6+
7+
IS_WINDOWS = sys.platform == "win32"
8+
9+
SUPPORTED_HEADERS_CTK_COMMON = {
10+
"cccl": "cuda/std/version",
11+
"cublas": "cublas.h",
12+
"cudart": "cuda_runtime.h",
13+
"cufft": "cufft.h",
14+
"curand": "curand.h",
15+
"cusolver": "cusolverDn.h",
16+
"cusparse": "cusparse.h",
17+
"npp": "npp.h",
18+
"nvcc": "fatbinary_section.h",
19+
"nvfatbin": "nvFatbin.h",
20+
"nvjitlink": "nvJitLink.h",
21+
"nvjpeg": "nvjpeg.h",
22+
"nvrtc": "nvrtc.h",
23+
"nvvm": "nvvm.h",
24+
}
25+
26+
SUPPORTED_HEADERS_CTK_LINUX_ONLY = {
27+
"cufile": "cufile.h",
28+
}
29+
SUPPORTED_HEADERS_CTK_LINUX = SUPPORTED_HEADERS_CTK_COMMON | SUPPORTED_HEADERS_CTK_LINUX_ONLY
30+
31+
SUPPORTED_HEADERS_CTK_WINDOWS_ONLY: dict[str, str] = {}
32+
SUPPORTED_HEADERS_CTK_WINDOWS = SUPPORTED_HEADERS_CTK_COMMON | SUPPORTED_HEADERS_CTK_WINDOWS_ONLY
33+
34+
SUPPORTED_HEADERS_CTK_ALL = (
35+
SUPPORTED_HEADERS_CTK_COMMON | SUPPORTED_HEADERS_CTK_LINUX_ONLY | SUPPORTED_HEADERS_CTK_WINDOWS_ONLY
36+
)
37+
SUPPORTED_HEADERS_CTK: Final[dict[str, str]] = (
38+
SUPPORTED_HEADERS_CTK_WINDOWS if IS_WINDOWS else SUPPORTED_HEADERS_CTK_LINUX
39+
)
40+
41+
SUPPORTED_SITE_PACKAGE_HEADER_DIRS_CTK = {
42+
"cccl": (
43+
"nvidia/cu13/include/cccl", # cuda-toolkit[cccl]==13.*
44+
"nvidia/cuda_cccl/include", # cuda-toolkit[cccl]==12.*
45+
),
46+
"cublas": ("nvidia/cu13/include", "nvidia/cublas/include"),
47+
"cudart": ("nvidia/cu13/include", "nvidia/cuda_runtime/include"),
48+
"cufft": ("nvidia/cu13/include", "nvidia/cufft/include"),
49+
"cufile": ("nvidia/cu13/include", "nvidia/cufile/include"),
50+
"curand": ("nvidia/cu13/include", "nvidia/curand/include"),
51+
"cusolver": ("nvidia/cu13/include", "nvidia/cusolver/include"),
52+
"cusparse": ("nvidia/cu13/include", "nvidia/cusparse/include"),
53+
"npp": ("nvidia/cu13/include", "nvidia/npp/include"),
54+
"nvcc": ("nvidia/cu13/include", "nvidia/cuda_nvcc/include"),
55+
"nvfatbin": ("nvidia/cu13/include", "nvidia/nvfatbin/include"),
56+
"nvjitlink": ("nvidia/cu13/include", "nvidia/nvjitlink/include"),
57+
"nvjpeg": ("nvidia/cu13/include", "nvidia/nvjpeg/include"),
58+
"nvrtc": ("nvidia/cu13/include", "nvidia/cuda_nvrtc/include"),
59+
"nvvm": ("nvidia/cu13/include", "nvidia/cuda_nvcc/nvvm/include"),
60+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import os
5+
import warnings
6+
from typing import Optional
7+
8+
9+
def _paths_differ(a: str, b: str) -> bool:
10+
"""
11+
Return True if paths are observably different.
12+
13+
Strategy:
14+
1) Compare os.path.normcase(os.path.normpath(...)) for quick, robust textual equality.
15+
- Handles trailing slashes and case-insensitivity on Windows.
16+
2) If still different AND both exist, use os.path.samefile to resolve symlinks/junctions.
17+
3) Otherwise (nonexistent paths or samefile unavailable), treat as different.
18+
"""
19+
norm_a = os.path.normcase(os.path.normpath(a))
20+
norm_b = os.path.normcase(os.path.normpath(b))
21+
if norm_a == norm_b:
22+
return False
23+
24+
try:
25+
if os.path.exists(a) and os.path.exists(b):
26+
# samefile raises on non-existent paths; only call when both exist.
27+
return not os.path.samefile(a, b)
28+
except OSError:
29+
# Fall through to "different" if samefile isn't applicable/available.
30+
pass
31+
32+
# If normalized strings differ and we couldn't prove they're the same entry, treat as different.
33+
return True
34+
35+
36+
def get_cuda_home_or_path() -> Optional[str]:
37+
cuda_home = os.environ.get("CUDA_HOME")
38+
cuda_path = os.environ.get("CUDA_PATH")
39+
40+
if cuda_home and cuda_path and _paths_differ(cuda_home, cuda_path):
41+
warnings.warn(
42+
"Both CUDA_HOME and CUDA_PATH are set but differ:\n"
43+
f" CUDA_HOME={cuda_home}\n"
44+
f" CUDA_PATH={cuda_path}\n"
45+
"Using CUDA_HOME (higher priority).",
46+
UserWarning,
47+
stacklevel=2,
48+
)
49+
50+
if cuda_home is not None:
51+
return cuda_home
52+
return cuda_path
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
__version__ = "1.2.2"
4+
__version__ = "1.2.3"

cuda_pathfinder/docs/nv-versions.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"version": "latest",
44
"url": "https://nvidia.github.io/cuda-python/cuda-pathfinder/latest/"
55
},
6+
{
7+
"version": "1.2.3",
8+
"url": "https://nvidia.github.io/cuda-python/cuda-pathfinder/1.2.3/"
9+
},
610
{
711
"version": "1.2.2",
812
"url": "https://nvidia.github.io/cuda-python/cuda-pathfinder/1.2.2/"

cuda_pathfinder/docs/source/api.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
``cuda.pathfinder`` API Reference
77
=================================
88

9-
The ``cuda.pathfinder`` module provides utilities for loading NVIDIA dynamic libraries.
10-
11-
Public API
12-
-----------
9+
The ``cuda.pathfinder`` module provides utilities for loading NVIDIA dynamic libraries,
10+
and experimental APIs for locating NVIDIA C/C++ header directories.
1311

1412
.. autosummary::
1513
:toctree: generated/
@@ -18,3 +16,6 @@ Public API
1816
load_nvidia_dynamic_lib
1917
LoadedDL
2018
DynamicLibNotFoundError
19+
20+
SUPPORTED_HEADERS_CTK
21+
find_nvidia_header_directory

cuda_pathfinder/docs/source/release.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Release Notes
77
.. toctree::
88
:maxdepth: 3
99

10+
1.2.3 <release/1.2.3-notes>
1011
1.2.2 <release/1.2.2-notes>
1112
1.2.1 <release/1.2.1-notes>
1213
1.2.0 <release/1.2.0-notes>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.. SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
.. SPDX-License-Identifier: Apache-2.0
3+
4+
.. module:: cuda.pathfinder
5+
6+
``cuda-pathfinder`` 1.2.3 Release notes
7+
=======================================
8+
9+
Released on Sep 17, 2025
10+
11+
12+
Highlights
13+
----------
14+
15+
* Extend experimental ``cuda.pathfinder._find_nvidia_headers`` API
16+
to support CTK library headers
17+
(`PR #956 <https://github.com/NVIDIA/cuda-python/pull/956>`_)

0 commit comments

Comments
 (0)