"""Make Windows able to find the CUDA / cuDNN DLLs that CTranslate2 needs. The single most common failure when running faster-whisper / WhisperX on Windows is: RuntimeError: Library cudnn_ops64_9.dll is not found ... (or: Could not locate cudnn_ops_infer64_8.dll) It happens because CTranslate2 loads cuDNN/cuBLAS by name via the OS loader, but the DLLs live inside pip wheels (``nvidia-cudnn-cu12`` / ``nvidia-cublas-cu12``) or inside ``torch/lib`` (the cu128 wheel bundles them) — directories that are NOT on the default DLL search path. Importing this module **first** — before importing ``ctranslate2``, ``faster_whisper`` or ``whisperx`` — registers those directories with the OS loader using ``os.add_dll_directory`` (and prepends them to PATH as a belt-and-suspenders fallback). Usage:: from app import _cuda_bootstrap # noqa: F401 (must come first) from faster_whisper import WhisperModel # now finds cuDNN This is a no-op on non-Windows platforms. """ from __future__ import annotations import importlib.util import os import sys # cuDNN must come last so its directory is searched first (add_dll_directory is LIFO-ish # in practice via PATH ordering); the order here is the order we *append* candidates. _NVIDIA_PKGS = ( "nvidia.cuda_runtime", "nvidia.cuda_nvrtc", "nvidia.cublas", "nvidia.cudnn", ) def _add_dll_dir(path: str) -> bool: if not path or not os.path.isdir(path): return False try: os.add_dll_directory(path) # type: ignore[attr-defined] (Windows only) except (OSError, AttributeError): return False # Some libraries resolve via LoadLibrary(name) which consults PATH, not the # add_dll_directory list — so prepend to PATH too. if path not in os.environ.get("PATH", ""): os.environ["PATH"] = path + os.pathsep + os.environ.get("PATH", "") return True def _candidate_dirs() -> list[str]: dirs: list[str] = [] for pkg in _NVIDIA_PKGS: try: spec = importlib.util.find_spec(pkg) except (ImportError, ValueError, ModuleNotFoundError): spec = None if spec and spec.submodule_search_locations: base = list(spec.submodule_search_locations)[0] # Windows wheels put DLLs in /bin, Linux in /lib — add both, harmless if absent. dirs.append(os.path.join(base, "bin")) dirs.append(os.path.join(base, "lib")) # torch's bundled CUDA libs (the cu128 wheel ships cudnn/cublas DLLs here). try: import torch # noqa: WPS433 (local import on purpose) dirs.append(os.path.join(os.path.dirname(torch.__file__), "lib")) except Exception: # noqa: BLE001 — torch may not be importable yet; ignore. pass return dirs def setup_cuda_dll_path() -> list[str]: """Register CUDA/cuDNN DLL directories with the Windows loader. Returns dirs added.""" if sys.platform != "win32": return [] added: list[str] = [] for d in _candidate_dirs(): if _add_dll_dir(d): added.append(d) return added # Run on import. Safe to import multiple times. ADDED_DLL_DIRS = setup_cuda_dll_path()