| | |
| | |
| |
|
| | import functools |
| | import struct |
| | import sys |
| |
|
| | from cuda.pathfinder._dynamic_libs.find_nvidia_dynamic_lib import _FindNvidiaDynamicLib |
| | from cuda.pathfinder._dynamic_libs.load_dl_common import DynamicLibNotFoundError, LoadedDL, load_dependencies |
| | from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import ( |
| | SUPPORTED_LINUX_SONAMES, |
| | SUPPORTED_WINDOWS_DLLS, |
| | ) |
| | from cuda.pathfinder._utils.platform_aware import IS_WINDOWS |
| |
|
| | if IS_WINDOWS: |
| | from cuda.pathfinder._dynamic_libs.load_dl_windows import ( |
| | check_if_already_loaded_from_elsewhere, |
| | load_with_abs_path, |
| | load_with_system_search, |
| | ) |
| | else: |
| | from cuda.pathfinder._dynamic_libs.load_dl_linux import ( |
| | check_if_already_loaded_from_elsewhere, |
| | load_with_abs_path, |
| | load_with_system_search, |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | _ALL_SUPPORTED_LIBNAMES: frozenset[str] = frozenset( |
| | (SUPPORTED_WINDOWS_DLLS if IS_WINDOWS else SUPPORTED_LINUX_SONAMES).keys() |
| | ) |
| |
|
| | |
| | |
| | |
| | _DRIVER_ONLY_LIBNAMES = frozenset(("cuda", "nvml")) |
| |
|
| |
|
| | def _load_driver_lib_no_cache(libname: str) -> LoadedDL: |
| | """Load an NVIDIA driver library (system-search only). |
| | |
| | Driver libs (libcuda, libnvidia-ml) are part of the display driver, not |
| | the CUDA Toolkit. They are always on the system linker path, so the |
| | full CTK search cascade (site-packages, conda, CUDA_HOME, canary) is |
| | unnecessary. |
| | """ |
| | loaded = check_if_already_loaded_from_elsewhere(libname, False) |
| | if loaded is not None: |
| | return loaded |
| | loaded = load_with_system_search(libname) |
| | if loaded is not None: |
| | return loaded |
| | raise DynamicLibNotFoundError( |
| | f'"{libname}" is an NVIDIA driver library and can only be found via' |
| | f" system search. Ensure the NVIDIA display driver is installed." |
| | ) |
| |
|
| |
|
| | def _load_lib_no_cache(libname: str) -> LoadedDL: |
| | if libname in _DRIVER_ONLY_LIBNAMES: |
| | return _load_driver_lib_no_cache(libname) |
| |
|
| | finder = _FindNvidiaDynamicLib(libname) |
| | abs_path = finder.try_site_packages() |
| | if abs_path is not None: |
| | found_via = "site-packages" |
| | else: |
| | abs_path = finder.try_with_conda_prefix() |
| | if abs_path is not None: |
| | found_via = "conda" |
| |
|
| | |
| | |
| | |
| | loaded = check_if_already_loaded_from_elsewhere(libname, abs_path is not None) |
| |
|
| | |
| | |
| | |
| | load_dependencies(libname, load_nvidia_dynamic_lib) |
| |
|
| | if loaded is not None: |
| | return loaded |
| |
|
| | if abs_path is None: |
| | loaded = load_with_system_search(libname) |
| | if loaded is not None: |
| | return loaded |
| | abs_path = finder.try_with_cuda_home() |
| | if abs_path is None: |
| | finder.raise_not_found_error() |
| | else: |
| | found_via = "CUDA_HOME" |
| |
|
| | return load_with_abs_path(libname, abs_path, found_via) |
| |
|
| |
|
| | @functools.cache |
| | def load_nvidia_dynamic_lib(libname: str) -> LoadedDL: |
| | """Load an NVIDIA dynamic library by name. |
| | |
| | Args: |
| | libname (str): The short name of the library to load (e.g., ``"cudart"``, |
| | ``"nvvm"``, etc.). |
| | |
| | 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: |
| | ValueError: If ``libname`` is not a recognized library name. |
| | DynamicLibNotFoundError: If the library cannot be found or loaded. |
| | RuntimeError: If Python is not 64-bit. |
| | |
| | Search order: |
| | 0. **Already loaded in the current process** |
| | |
| | - If a matching library is already loaded by some other component, |
| | return its absolute path and handle and skip the rest of the search. |
| | |
| | 1. **NVIDIA Python wheels** |
| | |
| | - Scan installed distributions (``site-packages``) to find libraries |
| | shipped in NVIDIA wheels. |
| | |
| | 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()`` |
| | |
| | - CUDA Toolkit (CTK) system installs with system config updates are often |
| | discovered via: |
| | |
| | - Linux: ``/etc/ld.so.conf.d/*cuda*.conf`` |
| | |
| | - Windows: ``C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\vX.Y\\bin`` |
| | on the system ``PATH``. |
| | |
| | 4. **Environment variables** |
| | |
| | - If set, use ``CUDA_HOME`` or ``CUDA_PATH`` (in that order). |
| | |
| | **Driver libraries** (``"cuda"``, ``"nvml"``): |
| | |
| | These are part of the NVIDIA display driver (not the CUDA Toolkit) and |
| | are always on the system linker path. For these libraries the search |
| | is simplified to: |
| | |
| | 0. Already loaded in the current process |
| | 1. OS default mechanisms (``dlopen`` / ``LoadLibraryW``) |
| | |
| | The CTK-specific steps (site-packages, conda, ``CUDA_HOME``, canary |
| | probe) are skipped entirely. |
| | |
| | Notes: |
| | The search is performed **per library**. There is currently no mechanism to |
| | guarantee that multiple libraries are all resolved from the same location. |
| | |
| | """ |
| | pointer_size_bits = struct.calcsize("P") * 8 |
| | if pointer_size_bits != 64: |
| | raise RuntimeError( |
| | f"cuda.pathfinder.load_nvidia_dynamic_lib() requires 64-bit Python." |
| | f" Currently running: {pointer_size_bits}-bit Python" |
| | f" {sys.version_info.major}.{sys.version_info.minor}" |
| | ) |
| | if libname not in _ALL_SUPPORTED_LIBNAMES: |
| | raise ValueError(f"Unsupported library name: {libname!r}. Supported names: {sorted(_ALL_SUPPORTED_LIBNAMES)}") |
| | return _load_lib_no_cache(libname) |
| |
|