* fix(bootstrap): port cuDNN 8 (NVIDIA CUDA GPU) + VC++ redist install into ensure_venv_ready() * fix(bootstrap): address #869 review — drop dead VC++ half, cache negative CUDA probe, gate on ROCm, sync docs Per maintainer review on #869: 1. Drop the VC++ Redistributable half: LoadLibraryA("vcruntime140.dll") from the running Tauri exe is a tautology (the exe itself links the MSVC CRT, so the process wouldn't be running without it), and torch's real failure mode is msvcp140.dll inside the venv python process. Dead code removed; a comment records why for future readers. 2. Stop taxing every non-CUDA launch: a negative torch probe (CPU / Intel / AMD — most installs) is now cached in a .venv/.cudnn8_probe_negative marker, so the synchronous `import torch` runs at most once per venv lifetime. Invalidated on every path that can change the torch build (drift sync #307, repair sync, first-run sync, ROCm reinstall) and implicitly by a venv rebuild. A probe that fails to run cleanly is skipped WITHOUT caching so a transient error can't wedge a real CUDA machine. 3. Rewrite docs/install/troubleshooting.md §10 to the actual root cause: packaged installs never had the cudnn8_compat libs (so reinstalling never restored them); the bootstrap now installs them automatically on CUDA machines, with the manual uv pip command as the offline fallback and PyTorch Whisper as the sidestep. 4. Gate the ~700 MB nvidia-cudnn-cu12 download on the venv torch being a real CUDA build: the probe now reports 'hip' before checking cuda.is_available() (which HIP spoofs), so opt-in ROCm installs (#124) never fetch the CUDA wheel. Also reflow the CHANGELOG entry to house style (bold one-line lead, 1-3 lines of why, (#827, #869) refs) and extend the bootstrap unit tests: classify_cuda_probe verdict mapping and the marker write/invalidate round-trip (6 cuDNN tests total, 43 lib tests green). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
194 lines
7.0 KiB
Python
194 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Post-install setup for platform-specific runtime dependencies.
|
|
|
|
1. **Windows: VC++ Redistributable** — PyTorch's native DLLs (c10.dll,
|
|
torch_cpu.dll, etc.) link against vcruntime140.dll and msvcp140.dll from
|
|
the Microsoft Visual C++ 2015-2022 Redistributable. Fresh Windows installs
|
|
(especially debloated/LTSC-style) don't ship it. We detect and auto-install
|
|
it silently before any `import torch` can fail.
|
|
|
|
2. **CUDA: cuDNN 8 compat** — Ensures cuDNN 8 libraries are available for
|
|
CTranslate2 (faster-whisper / WhisperX) alongside PyTorch 2.8+'s cuDNN 9.
|
|
|
|
Run automatically as part of `bun run setup:api` — no user action required.
|
|
|
|
Cross-platform:
|
|
- Linux: cuDNN 8 compat (.so.8 libs)
|
|
- Windows: VC++ Redistributable + cuDNN 8 compat (.dll libs)
|
|
- macOS: skipped (no CUDA)
|
|
"""
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import glob
|
|
|
|
|
|
# ── Windows: VC++ Redistributable ─────────────────────────────────────────
|
|
|
|
def _ensure_vcredist_windows():
|
|
"""Check for and install the VC++ 2015-2022 Redistributable on Windows.
|
|
|
|
PyTorch's native libraries (c10.dll, torch_cpu.dll, etc.) are built with
|
|
MSVC and dynamically link against vcruntime140.dll + msvcp140.dll. These
|
|
ship with Visual Studio / Build Tools but are NOT part of Windows itself.
|
|
On a fresh or debloated install the very first `import torch` crashes with:
|
|
|
|
OSError: [WinError 126] The specified module could not be found.
|
|
Error loading ...\\torch\\lib\\c10.dll or one of its dependencies.
|
|
|
|
This function silently downloads and installs the official x64 redist
|
|
package from Microsoft if the runtime DLLs are missing.
|
|
"""
|
|
if sys.platform != "win32":
|
|
return
|
|
|
|
# Check if vcruntime140.dll is already loadable
|
|
import ctypes
|
|
try:
|
|
ctypes.WinDLL("vcruntime140.dll")
|
|
print("✓ VC++ Redistributable: already installed")
|
|
return
|
|
except OSError:
|
|
pass
|
|
|
|
print("⚙ VC++ Redistributable not found — installing (required for PyTorch)...")
|
|
|
|
import tempfile
|
|
import urllib.request
|
|
|
|
vc_url = "https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
|
installer = os.path.join(tempfile.gettempdir(), "vc_redist.x64.exe")
|
|
|
|
try:
|
|
# Download
|
|
print(" Downloading VC++ Redistributable...")
|
|
urllib.request.urlretrieve(vc_url, installer)
|
|
|
|
# Silent install (/install /quiet /norestart)
|
|
print(" Installing silently...")
|
|
result = subprocess.run(
|
|
[installer, "/install", "/quiet", "/norestart"],
|
|
timeout=120,
|
|
capture_output=True,
|
|
)
|
|
|
|
# Verify it worked
|
|
try:
|
|
ctypes.WinDLL("vcruntime140.dll")
|
|
print("✓ VC++ Redistributable: installed successfully")
|
|
except OSError:
|
|
# Exit code 3010 = success but reboot required
|
|
if result.returncode == 3010:
|
|
print("✓ VC++ Redistributable: installed (reboot recommended)")
|
|
else:
|
|
print(f"⚠ VC++ Redistributable: install may have failed (exit code {result.returncode})")
|
|
print(" Manual install: https://aka.ms/vs/17/release/vc_redist.x64.exe")
|
|
except Exception as e:
|
|
print(f"⚠ VC++ Redistributable: auto-install failed: {e}")
|
|
print(" Manual install: https://aka.ms/vs/17/release/vc_redist.x64.exe")
|
|
finally:
|
|
# Clean up installer
|
|
try:
|
|
os.remove(installer)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
# ── cuDNN 8 compat ────────────────────────────────────────────────────────
|
|
|
|
def _find_compat_dir():
|
|
"""Return the cudnn8_compat target directory, auto-detecting venv layout."""
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
project_root = os.path.dirname(script_dir)
|
|
venv_dir = os.path.join(project_root, ".venv")
|
|
|
|
if not os.path.isdir(venv_dir):
|
|
return None
|
|
|
|
if sys.platform == "win32":
|
|
# Windows: .venv/Lib/site-packages/
|
|
sp = os.path.join(venv_dir, "Lib", "site-packages", "cudnn8_compat")
|
|
else:
|
|
# Linux: .venv/lib/pythonX.Y/site-packages/
|
|
pyver = f"python{sys.version_info.major}.{sys.version_info.minor}"
|
|
sp = os.path.join(venv_dir, "lib", pyver, "site-packages", "cudnn8_compat")
|
|
|
|
return sp
|
|
|
|
|
|
def _cudnn8_lib_dir(compat_dir):
|
|
"""Return the cuDNN lib subdirectory within the compat install."""
|
|
if sys.platform == "win32":
|
|
return os.path.join(compat_dir, "nvidia", "cudnn", "bin")
|
|
return os.path.join(compat_dir, "nvidia", "cudnn", "lib")
|
|
|
|
|
|
def _count_cudnn8_libs(lib_dir):
|
|
"""Count cuDNN 8 shared libraries in the given directory."""
|
|
if sys.platform == "win32":
|
|
return len(glob.glob(os.path.join(lib_dir, "cudnn*64_8.dll")))
|
|
return len(glob.glob(os.path.join(lib_dir, "libcudnn*.so.8")))
|
|
|
|
|
|
def main():
|
|
# ── Step 1: Windows VC++ Redistributable ──────────────────────────────
|
|
_ensure_vcredist_windows()
|
|
|
|
# macOS — no CUDA, nothing to do
|
|
if sys.platform == "darwin":
|
|
return
|
|
|
|
compat_dir = _find_compat_dir()
|
|
if compat_dir is None:
|
|
return
|
|
|
|
lib_dir = _cudnn8_lib_dir(compat_dir)
|
|
|
|
# Already installed?
|
|
if os.path.isdir(lib_dir):
|
|
n = _count_cudnn8_libs(lib_dir)
|
|
if n >= 5:
|
|
print(f"✓ cuDNN 8 compat: {n} libraries ready")
|
|
return
|
|
|
|
# Check if CUDA is available before installing GPU-only libs
|
|
try:
|
|
result = subprocess.run(
|
|
[sys.executable, "-c", "import torch; print(torch.cuda.is_available())"],
|
|
capture_output=True, text=True, timeout=30,
|
|
)
|
|
if result.stdout.strip() != "True":
|
|
print("✓ No CUDA — cuDNN 8 compat not needed")
|
|
return
|
|
except Exception:
|
|
pass # Can't detect CUDA — install anyway, it's harmless on CPU
|
|
|
|
print("⚙ Installing cuDNN 8 compatibility libraries for CTranslate2...")
|
|
try:
|
|
# `uv venv` doesn't seed pip into the venv, so `sys.executable -m pip`
|
|
# fails with "No module named pip". `uv pip install --python` talks to
|
|
# the interpreter directly without needing pip installed inside it.
|
|
subprocess.run(
|
|
[
|
|
"uv", "pip", "install",
|
|
"--no-deps", "--target", compat_dir,
|
|
"--python", sys.executable,
|
|
"nvidia-cudnn-cu12==8.9.7.29",
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=180,
|
|
)
|
|
n = _count_cudnn8_libs(lib_dir)
|
|
print(f"✓ cuDNN 8 installed: {n} libraries")
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"⚠ cuDNN 8 install failed (transcription may not work on CUDA):")
|
|
print(f" {(e.stderr or '')[:300]}")
|
|
except Exception as e:
|
|
print(f"⚠ cuDNN 8 install skipped: {e}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|