File size: 7,034 Bytes
94004a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#!/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

if sys.platform == "win32" and hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8")


# ── 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:
        subprocess.run(
            [
                sys.executable, "-m", "pip", "install",
                "--no-deps", "--target", compat_dir,
                "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()