mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 13:20:39 -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]>
131 lines
4.5 KiB
Python
131 lines
4.5 KiB
Python
"""
|
|
Phase 1.1 Test: ROCm requirements installation.
|
|
|
|
Validates that requirements-rocm.txt correctly installs ROCm-enabled PyTorch
|
|
and that torch.cuda.is_available() returns True on AMD hardware.
|
|
|
|
Usage:
|
|
python -m pytest backend/tests/test_rocm_requirements.py -v
|
|
"""
|
|
|
|
import os
|
|
import platform
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
def _has_amd_hardware():
|
|
"""Check if AMD GPU hardware is present on Windows."""
|
|
if platform.system() != "Windows":
|
|
return False
|
|
try:
|
|
result = subprocess.run(
|
|
[
|
|
"powershell",
|
|
"-Command",
|
|
"Get-WmiObject Win32_VideoController | "
|
|
"Where-Object {$_.AdapterCompatibility -like '*AMD*'} | "
|
|
"Measure-Object | Select-Object -ExpandProperty Count",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
return int(result.stdout.strip()) > 0
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
@pytest.fixture()
|
|
def backend_dir():
|
|
return Path(__file__).parent.parent
|
|
|
|
|
|
class TestRocmRequirements:
|
|
"""Validate requirements-rocm.txt content and installation."""
|
|
|
|
def test_requirements_file_exists(self, backend_dir):
|
|
req_file = backend_dir / "requirements-rocm.txt"
|
|
assert req_file.exists(), "requirements-rocm.txt must exist"
|
|
|
|
def test_requirements_file_content(self, backend_dir):
|
|
import re
|
|
req_file = backend_dir / "requirements-rocm.txt"
|
|
content = req_file.read_text()
|
|
assert "rocm7.2" in content, "Must point to ROCm 7.2 extra index"
|
|
# Parse exact package names to avoid false positives from URL substrings
|
|
package_names = re.findall(r"^([A-Za-z][A-Za-z0-9_-]*)", content, re.MULTILINE)
|
|
assert "torch" in package_names, "Must include torch package"
|
|
assert "torchaudio" in package_names, "Must include torchaudio package"
|
|
assert "torchvision" in package_names, "Must include torchvision package"
|
|
|
|
@pytest.mark.timeout(900)
|
|
@pytest.mark.skipif(
|
|
not os.environ.get("VOICEBOX_TEST_ROCM_INSTALL"),
|
|
reason="Set VOICEBOX_TEST_ROCM_INSTALL=1 to run the heavy install test",
|
|
)
|
|
def test_rocm_torch_installs_and_detects_amd(self, backend_dir):
|
|
"""
|
|
Create a temporary venv, install requirements-rocm.txt, and verify
|
|
torch.cuda.is_available() returns True on AMD hardware.
|
|
"""
|
|
req_file = backend_dir / "requirements-rocm.txt"
|
|
has_amd = _has_amd_hardware()
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
venv_dir = Path(tmpdir) / "venv"
|
|
subprocess.run(
|
|
[sys.executable, "-m", "venv", str(venv_dir)],
|
|
check=True,
|
|
)
|
|
|
|
if sys.platform == "win32":
|
|
venv_python = venv_dir / "Scripts" / "python.exe"
|
|
else:
|
|
venv_python = venv_dir / "bin" / "python"
|
|
|
|
# Upgrade pip to avoid resolver issues
|
|
subprocess.run(
|
|
[str(venv_python), "-m", "pip", "install", "--upgrade", "pip"],
|
|
check=True,
|
|
)
|
|
|
|
# Install ROCm requirements
|
|
subprocess.run(
|
|
[str(venv_python), "-m", "pip", "install", "-r", str(req_file)],
|
|
check=True,
|
|
)
|
|
|
|
# Verify torch imports and cuda availability
|
|
result = subprocess.run(
|
|
[
|
|
str(venv_python),
|
|
"-c",
|
|
"import torch; print(torch.__version__); print(torch.cuda.is_available())",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
|
|
lines = result.stdout.strip().splitlines()
|
|
assert len(lines) >= 2, f"Unexpected output: {result.stdout}"
|
|
torch_version = lines[0]
|
|
cuda_available = lines[1] == "True"
|
|
|
|
# The honest test: on AMD hardware ROCm torch should report cuda available
|
|
if has_amd:
|
|
assert cuda_available, (
|
|
f"AMD hardware detected but torch.cuda.is_available() returned False. "
|
|
f"torch version: {torch_version}, stderr: {result.stderr}"
|
|
)
|
|
else:
|
|
assert not cuda_available, (
|
|
f"No AMD hardware detected but torch.cuda.is_available() returned True. "
|
|
f"torch version: {torch_version}"
|
|
)
|