mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-29 15:15:27 -07:00
Fix CUDA downloads on unsupported platforms (#770)
* Fix CUDA downloads on unsupported platforms * fix: align CUDA status nullability * fix: require CUDA download support flag
This commit is contained in:
@@ -270,7 +270,8 @@ Use cases: agent dev loops (dictate a question, hear the answer in a cloned voic
|
|||||||
| Platform | Backend | Notes |
|
| Platform | Backend | Notes |
|
||||||
| ------------------------ | -------------- | ---------------------------------------------- |
|
| ------------------------ | -------------- | ---------------------------------------------- |
|
||||||
| macOS (Apple Silicon) | MLX (Metal) | 4-5x faster via Neural Engine |
|
| macOS (Apple Silicon) | MLX (Metal) | 4-5x faster via Neural Engine |
|
||||||
| Windows / Linux (NVIDIA) | PyTorch (CUDA) | Auto-downloads CUDA binary from within the app |
|
| Windows (NVIDIA) | PyTorch (CUDA) | Auto-downloads CUDA binary from within the app |
|
||||||
|
| Linux (NVIDIA) | PyTorch (CUDA) | Use a local/remote Python backend with CUDA PyTorch |
|
||||||
| Linux (AMD) | PyTorch (ROCm) | Auto-configures HSA_OVERRIDE_GFX_VERSION |
|
| Linux (AMD) | PyTorch (ROCm) | Auto-configures HSA_OVERRIDE_GFX_VERSION |
|
||||||
| Windows (any GPU) | DirectML | Universal Windows GPU support |
|
| Windows (any GPU) | DirectML | Universal Windows GPU support |
|
||||||
| Intel Arc | IPEX/XPU | Intel discrete GPU acceleration |
|
| Intel Arc | IPEX/XPU | Intel discrete GPU acceleration |
|
||||||
|
|||||||
@@ -287,7 +287,10 @@ export interface CudaDownloadProgress {
|
|||||||
export interface CudaStatus {
|
export interface CudaStatus {
|
||||||
available: boolean; // CUDA binary exists on disk
|
available: boolean; // CUDA binary exists on disk
|
||||||
active: boolean; // Currently running the CUDA binary
|
active: boolean; // Currently running the CUDA binary
|
||||||
binary_path?: string;
|
binary_path: string | null;
|
||||||
|
cuda_libs_version: string | null;
|
||||||
|
download_supported: boolean; // Platform has a matching release asset
|
||||||
|
unsupported_reason: string | null;
|
||||||
downloading: boolean; // Download in progress
|
downloading: boolean; // Download in progress
|
||||||
download_progress?: CudaDownloadProgress;
|
download_progress?: CudaDownloadProgress;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ async def download_cuda_backend():
|
|||||||
"""Download the CUDA backend binary."""
|
"""Download the CUDA backend binary."""
|
||||||
from ..services import cuda
|
from ..services import cuda
|
||||||
|
|
||||||
|
unsupported_reason = cuda.get_cuda_download_unsupported_reason()
|
||||||
|
if unsupported_reason:
|
||||||
|
raise HTTPException(status_code=409, detail=unsupported_reason)
|
||||||
|
|
||||||
if cuda.get_cuda_binary_path() is not None:
|
if cuda.get_cuda_binary_path() is not None:
|
||||||
raise HTTPException(status_code=409, detail="CUDA backend already downloaded")
|
raise HTTPException(status_code=409, detail="CUDA backend already downloaded")
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ import tarfile
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from .. import __version__
|
||||||
from ..config import get_data_dir
|
from ..config import get_data_dir
|
||||||
from ..utils.progress import get_progress_manager
|
from ..utils.progress import get_progress_manager
|
||||||
from .. import __version__
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -31,6 +31,8 @@ GITHUB_RELEASES_URL = "https://github.com/jamiepine/voicebox/releases/download"
|
|||||||
|
|
||||||
PROGRESS_KEY = "cuda-backend"
|
PROGRESS_KEY = "cuda-backend"
|
||||||
|
|
||||||
|
CUDA_DOWNLOAD_UNSUPPORTED_REASON = "Downloadable CUDA backend releases are currently only published for Windows."
|
||||||
|
|
||||||
# The current expected CUDA libs version. Bump this when we change the
|
# The current expected CUDA libs version. Bump this when we change the
|
||||||
# CUDA toolkit version or torch's CUDA dependency changes (e.g. cu126 -> cu128).
|
# CUDA toolkit version or torch's CUDA dependency changes (e.g. cu126 -> cu128).
|
||||||
CUDA_LIBS_VERSION = "cu128-v1"
|
CUDA_LIBS_VERSION = "cu128-v1"
|
||||||
@@ -63,6 +65,25 @@ def get_cuda_exe_name() -> str:
|
|||||||
return "voicebox-server-cuda"
|
return "voicebox-server-cuda"
|
||||||
|
|
||||||
|
|
||||||
|
def is_cuda_download_supported() -> bool:
|
||||||
|
"""Return whether this platform has a matching CUDA release asset."""
|
||||||
|
return sys.platform == "win32"
|
||||||
|
|
||||||
|
|
||||||
|
def get_cuda_download_unsupported_reason() -> str | None:
|
||||||
|
"""Explain why this platform cannot use the release-download flow."""
|
||||||
|
if is_cuda_download_supported():
|
||||||
|
return None
|
||||||
|
return CUDA_DOWNLOAD_UNSUPPORTED_REASON
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_cuda_download_supported() -> None:
|
||||||
|
"""Raise if downloading would fetch an asset built for another platform."""
|
||||||
|
reason = get_cuda_download_unsupported_reason()
|
||||||
|
if reason:
|
||||||
|
raise RuntimeError(reason)
|
||||||
|
|
||||||
|
|
||||||
def get_cuda_binary_path() -> Optional[Path]:
|
def get_cuda_binary_path() -> Optional[Path]:
|
||||||
"""Return path to the CUDA executable if it exists inside the onedir."""
|
"""Return path to the CUDA executable if it exists inside the onedir."""
|
||||||
p = get_cuda_dir() / get_cuda_exe_name()
|
p = get_cuda_dir() / get_cuda_exe_name()
|
||||||
@@ -103,12 +124,15 @@ def get_cuda_status() -> dict:
|
|||||||
cuda_path = get_cuda_binary_path()
|
cuda_path = get_cuda_binary_path()
|
||||||
progress = progress_manager.get_progress(PROGRESS_KEY)
|
progress = progress_manager.get_progress(PROGRESS_KEY)
|
||||||
cuda_libs_version = get_installed_cuda_libs_version()
|
cuda_libs_version = get_installed_cuda_libs_version()
|
||||||
|
unsupported_reason = get_cuda_download_unsupported_reason()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"available": cuda_path is not None,
|
"available": cuda_path is not None,
|
||||||
"active": is_cuda_active(),
|
"active": is_cuda_active(),
|
||||||
"binary_path": str(cuda_path) if cuda_path else None,
|
"binary_path": str(cuda_path) if cuda_path else None,
|
||||||
"cuda_libs_version": cuda_libs_version,
|
"cuda_libs_version": cuda_libs_version,
|
||||||
|
"download_supported": unsupported_reason is None,
|
||||||
|
"unsupported_reason": unsupported_reason,
|
||||||
"downloading": progress is not None and progress.get("status") == "downloading",
|
"downloading": progress is not None and progress.get("status") == "downloading",
|
||||||
"download_progress": progress,
|
"download_progress": progress,
|
||||||
}
|
}
|
||||||
@@ -257,6 +281,8 @@ async def download_cuda_binary(version: Optional[str] = None):
|
|||||||
|
|
||||||
async def _download_cuda_binary_locked(version: Optional[str] = None):
|
async def _download_cuda_binary_locked(version: Optional[str] = None):
|
||||||
"""Inner implementation of download_cuda_binary, called under _download_lock."""
|
"""Inner implementation of download_cuda_binary, called under _download_lock."""
|
||||||
|
ensure_cuda_download_supported()
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
if version is None:
|
if version is None:
|
||||||
@@ -387,6 +413,11 @@ async def check_and_update_cuda_binary():
|
|||||||
if not cuda_path:
|
if not cuda_path:
|
||||||
return # No CUDA binary installed, nothing to update
|
return # No CUDA binary installed, nothing to update
|
||||||
|
|
||||||
|
unsupported_reason = get_cuda_download_unsupported_reason()
|
||||||
|
if unsupported_reason:
|
||||||
|
logger.info("Skipping CUDA backend auto-update: %s", unsupported_reason)
|
||||||
|
return
|
||||||
|
|
||||||
need_server = _needs_server_download()
|
need_server = _needs_server_download()
|
||||||
need_libs = _needs_cuda_libs_download()
|
need_libs = _needs_cuda_libs_download()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import sys as py_sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from backend.services import cuda
|
||||||
|
|
||||||
|
|
||||||
|
def test_cuda_status_reports_unsupported_linux_download(monkeypatch, tmp_path):
|
||||||
|
monkeypatch.setattr(cuda.sys, "platform", "linux")
|
||||||
|
monkeypatch.setattr(cuda, "get_data_dir", lambda: tmp_path)
|
||||||
|
|
||||||
|
status = cuda.get_cuda_status()
|
||||||
|
|
||||||
|
assert status["available"] is False
|
||||||
|
assert status["download_supported"] is False
|
||||||
|
assert status["unsupported_reason"] == cuda.CUDA_DOWNLOAD_UNSUPPORTED_REASON
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_cuda_download_rejects_linux_before_network(monkeypatch, tmp_path):
|
||||||
|
monkeypatch.setattr(cuda.sys, "platform", "linux")
|
||||||
|
monkeypatch.setattr(cuda, "get_data_dir", lambda: tmp_path)
|
||||||
|
|
||||||
|
class UnexpectedClient:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
raise AssertionError("unsupported platforms should not start a release download")
|
||||||
|
|
||||||
|
monkeypatch.setitem(py_sys.modules, "httpx", types.SimpleNamespace(AsyncClient=UnexpectedClient))
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="currently only published for Windows"):
|
||||||
|
await cuda._download_cuda_binary_locked("v0.5.0")
|
||||||
@@ -23,7 +23,7 @@ This page is for the cases where it doesn't:
|
|||||||
| **Windows + NVIDIA** | PyTorch CUDA (cu128) | Auto-downloads the CUDA backend binary on first use |
|
| **Windows + NVIDIA** | PyTorch CUDA (cu128) | Auto-downloads the CUDA backend binary on first use |
|
||||||
| **Windows + Intel Arc** | PyTorch XPU (IPEX) | New in 0.4 — works with Arc A-series and B-series |
|
| **Windows + Intel Arc** | PyTorch XPU (IPEX) | New in 0.4 — works with Arc A-series and B-series |
|
||||||
| **Windows generic GPU** | DirectML | Universal Windows GPU support; slower than CUDA |
|
| **Windows generic GPU** | DirectML | Universal Windows GPU support; slower than CUDA |
|
||||||
| **Linux + NVIDIA** | PyTorch CUDA (cu128) | Same auto-download flow as Windows |
|
| **Linux + NVIDIA** | PyTorch CUDA (cu128) | Use a local/remote Python backend with CUDA PyTorch |
|
||||||
| **Linux + AMD** | PyTorch ROCm | Auto-configures `HSA_OVERRIDE_GFX_VERSION` |
|
| **Linux + AMD** | PyTorch ROCm | Auto-configures `HSA_OVERRIDE_GFX_VERSION` |
|
||||||
| **Linux + Intel Arc** | PyTorch XPU (IPEX) | |
|
| **Linux + Intel Arc** | PyTorch XPU (IPEX) | |
|
||||||
| **Any (no GPU)** | PyTorch CPU | Works everywhere; expect 5-50x slower than GPU |
|
| **Any (no GPU)** | PyTorch CPU | Works everywhere; expect 5-50x slower than GPU |
|
||||||
@@ -46,7 +46,7 @@ On M-series Macs, Voicebox ships an MLX-optimized backend that uses the Apple Ne
|
|||||||
|
|
||||||
The Whisper Turbo + MLX combo dropped transcription latency from ~20s to ~2-3s on M-series chips (see CHANGELOG entry for v0.1.10).
|
The Whisper Turbo + MLX combo dropped transcription latency from ~20s to ~2-3s on M-series chips (see CHANGELOG entry for v0.1.10).
|
||||||
|
|
||||||
## Windows / Linux + NVIDIA — The CUDA Backend Swap
|
## Windows + NVIDIA — The CUDA Backend Swap
|
||||||
|
|
||||||
Voicebox doesn't bundle CUDA into the main installer (it would balloon downloads to multi-gigabyte territory for users who don't have an NVIDIA GPU). Instead, when you first need it, the app downloads a separate **CUDA backend binary** that contains the PyTorch + CUDA runtime.
|
Voicebox doesn't bundle CUDA into the main installer (it would balloon downloads to multi-gigabyte territory for users who don't have an NVIDIA GPU). Instead, when you first need it, the app downloads a separate **CUDA backend binary** that contains the PyTorch + CUDA runtime.
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ No cloud fallback, no bring-your-own-API-key. Local is the product.
|
|||||||
| Platform | Backend | Notes |
|
| Platform | Backend | Notes |
|
||||||
|----------|---------|-------|
|
|----------|---------|-------|
|
||||||
| macOS (Apple Silicon) | MLX (Metal) | 4-5x faster via Neural Engine |
|
| macOS (Apple Silicon) | MLX (Metal) | 4-5x faster via Neural Engine |
|
||||||
| Windows / Linux (NVIDIA) | PyTorch (CUDA) | Auto-downloads CUDA binary from within the app |
|
| Windows (NVIDIA) | PyTorch (CUDA) | Auto-downloads CUDA binary from within the app |
|
||||||
|
| Linux (NVIDIA) | PyTorch (CUDA) | Use a local/remote Python backend with CUDA PyTorch |
|
||||||
| Linux (AMD) | PyTorch (ROCm) | Auto-configures HSA_OVERRIDE_GFX_VERSION |
|
| Linux (AMD) | PyTorch (ROCm) | Auto-configures HSA_OVERRIDE_GFX_VERSION |
|
||||||
| Windows (any GPU) | DirectML | Universal Windows GPU support |
|
| Windows (any GPU) | DirectML | Universal Windows GPU support |
|
||||||
| Intel Arc | IPEX/XPU | Intel discrete GPU acceleration |
|
| Intel Arc | IPEX/XPU | Intel discrete GPU acceleration |
|
||||||
|
|||||||
Reference in New Issue
Block a user