mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-29 15:15:27 -07:00
* feat(windows): add native ROCm support for AMD GPUs Implements native ROCm architecture for Windows. - Adds backend build pipeline for voicebox-server-rocm.exe - Detects AMD GPUs dynamically and routes PyTorch allocations - Adds automatic download and update logic for ROCm dependencies - Refactors UI in GpuPage.tsx and GpuAcceleration.tsx to add AMD flows - Fixes 'Switch to CPU' lock on Windows via Tauri backend_override state - Resolves PyInstaller/rocm_sdk UnboundLocalError silent crashes - Resolves Numba/NumPy 2.x incompatibilities during Qwen3-TTS load - Resolves HF_HUB_OFFLINE Catch-22 for CustomVoice processor caching * fix(rocm): host libs archive under the app release tag, drop offline-load regression Align the ROCm libs download with the CUDA pattern: both the server core and the libs archive are published under the app-version release tag, with the libs content version encoded in the filename only. The previous code fetched libs from a separate rocm7.2-v1 tag, which disagreed with the download test. Also revert the unrelated Qwen CustomVoice changes that wrapped model loading in force_offline_if_cached (not imported — a NameError on load for every platform) and re-added a Base-model cache gate. The inference-path offline guard was deliberately removed previously. * feat(rocm): gate download on AMD detection and persist the backend variant The ROCm download section now only shows when the backend reports an AMD GPU on Windows (new supports_rocm health field, backed by the memoized is_amd_gpu_windows detection that was previously unused), or when ROCm is already downloaded/active. Make the backend override honor a pinned variant: set_backend_override persists the choice to disk so it survives an app restart, start_server reads it back, and a cuda/rocm pin now actually selects that variant instead of always preferring ROCm. A stale pin to a deleted backend self-heals to the default order rather than forcing CPU. Add the web no-op stub for the new method. * chore(rocm): drop incomplete vitest harness for the unused GpuAcceleration component GpuAcceleration.tsx is not routed anywhere (GpuPage is the live settings view), and the added vitest setup referenced testing-library/vitest deps that were not in the lockfile, breaking the web typecheck. Remove the dead component's test and its scaffolding to keep this PR scoped to the ROCm feature. * ci(rocm): add ROCm release-artifact pipeline Mirror the CUDA packaging path for ROCm so the runtime download has artifacts to fetch. scripts/package_rocm.py splits the PyInstaller --rocm onedir into voicebox-server-rocm.tar.gz (core) + rocm-libs-rocm7.2-v1.tar.gz (AMD runtime: HIP DLLs, rocBLAS Tensile data, MIOpen kernel DBs) + rocm-libs.json, matching the names services/rocm.py expects, both under the app-version release tag. The new build-rocm-windows job in release.yml builds on windows-latest/cp312 and lets build_binary.py --rocm pull the official AMD Radeon wheels. The file classifier can't be validated against a real AMD build on CI, so it has unit coverage (test_package_rocm.py) against a synthetic onedir layout. The prefixes/dir markers may need a tweak after the first real build on AMD hardware — the packager hard-fails loudly if it classifies zero ROCm files. --------- Co-authored-by: Jamie Pine <[email protected]>
This commit is contained in:
co-authored by
Jamie Pine
parent
c2282b256a
commit
e766c7cbfb
@@ -20,6 +20,7 @@ def register_routers(app: FastAPI) -> None:
|
||||
from .settings import router as settings_router
|
||||
from .tasks import router as tasks_router
|
||||
from .cuda import router as cuda_router
|
||||
from .rocm import router as rocm_router
|
||||
from .speak import router as speak_router
|
||||
from .mcp_bindings import router as mcp_bindings_router
|
||||
from .events import router as events_router
|
||||
@@ -39,6 +40,7 @@ def register_routers(app: FastAPI) -> None:
|
||||
app.include_router(settings_router)
|
||||
app.include_router(tasks_router)
|
||||
app.include_router(cuda_router)
|
||||
app.include_router(rocm_router)
|
||||
app.include_router(speak_router)
|
||||
app.include_router(mcp_bindings_router)
|
||||
app.include_router(events_router)
|
||||
|
||||
@@ -13,7 +13,7 @@ from sqlalchemy.orm import Session
|
||||
from .. import config, models
|
||||
from ..services import tts
|
||||
from ..database import get_db
|
||||
from ..utils.platform_detect import get_backend_type
|
||||
from ..utils.platform_detect import get_backend_type, is_amd_gpu_windows
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -103,7 +103,10 @@ async def health():
|
||||
|
||||
gpu_type = None
|
||||
if has_cuda:
|
||||
gpu_type = f"CUDA ({torch.cuda.get_device_name(0)})"
|
||||
if hasattr(torch.version, "hip") and torch.version.hip:
|
||||
gpu_type = f"ROCm ({torch.cuda.get_device_name(0)})"
|
||||
else:
|
||||
gpu_type = f"CUDA ({torch.cuda.get_device_name(0)})"
|
||||
elif has_mps:
|
||||
gpu_type = "MPS (Apple Silicon)"
|
||||
elif backend_type == "mlx":
|
||||
@@ -164,6 +167,15 @@ async def health():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
default_variant = "cpu"
|
||||
if has_cuda:
|
||||
if hasattr(torch.version, "hip") and torch.version.hip:
|
||||
default_variant = "rocm"
|
||||
else:
|
||||
default_variant = "cuda"
|
||||
elif has_xpu:
|
||||
default_variant = "xpu"
|
||||
|
||||
return models.HealthResponse(
|
||||
status="healthy",
|
||||
model_loaded=model_loaded,
|
||||
@@ -173,10 +185,8 @@ async def health():
|
||||
gpu_type=gpu_type,
|
||||
vram_used_mb=vram_used,
|
||||
backend_type=backend_type,
|
||||
backend_variant=os.environ.get(
|
||||
"VOICEBOX_BACKEND_VARIANT",
|
||||
"cuda" if torch.cuda.is_available() else ("xpu" if has_xpu else "cpu"),
|
||||
),
|
||||
backend_variant=os.environ.get("VOICEBOX_BACKEND_VARIANT", default_variant),
|
||||
supports_rocm=is_amd_gpu_windows(),
|
||||
gpu_compatibility_warning=gpu_compat_warning,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""ROCm backend management endpoints."""
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from ..services.task_queue import create_background_task
|
||||
from ..utils.progress import get_progress_manager
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.get("/backend/rocm-status")
|
||||
async def get_rocm_status():
|
||||
"""Get ROCm backend download/availability status."""
|
||||
from ..services import rocm
|
||||
|
||||
return rocm.get_rocm_status()
|
||||
|
||||
|
||||
@router.post("/backend/download-rocm")
|
||||
async def download_rocm_backend():
|
||||
"""Download the ROCm backend binary."""
|
||||
from ..services import rocm
|
||||
|
||||
progress_manager = get_progress_manager()
|
||||
existing = progress_manager.get_progress(rocm.PROGRESS_KEY)
|
||||
if existing and existing.get("status") in {"downloading", "extracting"}:
|
||||
raise HTTPException(status_code=409, detail="ROCm backend download already in progress")
|
||||
|
||||
async def _download():
|
||||
try:
|
||||
await rocm.download_rocm_binary()
|
||||
except Exception as e:
|
||||
logger.error("ROCm download failed: %s", e)
|
||||
|
||||
create_background_task(_download())
|
||||
return {"message": "ROCm backend download started", "progress_key": rocm.PROGRESS_KEY}
|
||||
|
||||
|
||||
@router.delete("/backend/rocm")
|
||||
async def delete_rocm_backend():
|
||||
"""Delete the downloaded ROCm backend binary."""
|
||||
from ..services import rocm
|
||||
|
||||
if rocm.is_rocm_active():
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="Cannot delete ROCm backend while it is active. Switch to CPU first.",
|
||||
)
|
||||
|
||||
deleted = await rocm.delete_rocm_binary()
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=404, detail="No ROCm backend found to delete")
|
||||
|
||||
return {"message": "ROCm backend deleted"}
|
||||
|
||||
|
||||
@router.get("/backend/rocm-progress")
|
||||
async def get_rocm_download_progress():
|
||||
"""Get ROCm backend download progress via Server-Sent Events."""
|
||||
progress_manager = get_progress_manager()
|
||||
|
||||
async def event_generator():
|
||||
async for event in progress_manager.subscribe("rocm-backend"):
|
||||
yield event
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user