Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,14 +494,16 @@ test-only PR. Docker Hub overview edits still publish through the small
timed out, no retries) and keeps PyPI on any doubt
(`electron/src/main/pypi-mirror.ts`); and Hugging Face model downloads
(gated on install state or an explicit user action).
- **Windows ROCm setup the user explicitly selects** (owner-approved
2026-10-26): the matched AMD SDK and PyTorch wheels from `repo.radeon.com`,
and the pinned HIP CTranslate2 archive from OpenNMT/CTranslate2 GitHub
- **Windows ROCm setup the user explicitly selects:** the matched AMD SDK
and PyTorch wheels from `repo.radeon.com` (owner-approved 2026-10-26) or
`stable.repo.amd.com` (RDNA 1 and 2), and — for the 7.2.1 recipe only — the
pinned HIP CTranslate2 archive from OpenNMT/CTranslate2 GitHub
Releases. Every artifact is SHA-256 pinned in
`scripts/windows-rocm-recipe.json` and verified before use. This opt-in
installer keeps every other package at its installed (locked) version and
uses the usual package indexes only for anything new; verification
and the optional local-model smoke test never acquire models or use the network.
The ROCm 10 recipe installs no CTranslate2, so it downloads nothing from GitHub.
The VC++ Redistributable is a manual prerequisite; opted-in Windows ROCm
source setup checks it locally and stops when it is missing.
- **Galleries the user opens or enables:** the community gallery manifest
Expand Down
169 changes: 169 additions & 0 deletions .github/workflows/windows-rocm-recipe.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Recipe contracts for the opt-in Windows ROCm installer.
#
# What this can and cannot prove, stated up front so a green run is never read
# as hardware validation:
#
# * It CAN prove, on a plain Linux runner, that `scripts/windows-rocm-recipe.json`
# still parses under the same loader the installer uses, that every AMD
# artifact a selected overlay would install carries a 64-hex SHA-256 pin,
# that overlay sources stay on the two approved AMD hosts, and that a GPU
# PCI id maps to the architecture whose device wheels it names.
# * It CANNOT prove that any GPU runs a kernel. There is no Windows runner with
# an AMD GPU in hosted CI, so `--verify-only`, the model-free probe and the
# real TTS render stay a documented local gate (docs/install/windows-rocm.md),
# not a CI claim.
#
# The reason this exists at all is failure mode #1 for this file: a device wheel
# added without its hash, or a recipe whose `gpu_architectures` no longer matches
# the `devices` it ships. Both are platform-independent data errors that would
# otherwise only surface on a user's Windows machine after a ~1.5 GB download.

name: Windows ROCm recipe

on:
pull_request:
branches: [main]
paths:
- scripts/windows-rocm-recipe.json
- backend/core/windows_rocm/**
- backend/core/device_caps.py
- tests/test_windows_rocm_*.py
- docs/install/windows-rocm.md
- .github/workflows/windows-rocm-recipe.yml
push:
branches: [main]
paths:
- scripts/windows-rocm-recipe.json
- backend/core/windows_rocm/**
- backend/core/device_caps.py
- tests/test_windows_rocm_*.py
- docs/install/windows-rocm.md
- .github/workflows/windows-rocm-recipe.yml
workflow_dispatch:

permissions:
contents: read

concurrency:
group: windows-rocm-recipe-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
contracts:
name: Recipe contracts (no GPU)
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false

- name: Setup Python 3.12
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"

# Only pytest (and PyYAML, which some guards import) is needed. These
# tests import the recipe/installer modules and never import torch, so no
# ROCm or CUDA wheel is installed on the runner. The two ignored modules
# need a real torch/numpy and are covered by the main CI job instead.
- name: Install test dependencies
run: python -m pip install --disable-pip-version-check pytest pyyaml numpy

# An explicit file list, not a glob: the exclusions are the modules that
# need a real torch/numpy/soundfile stack or the whole app import graph
# (`asr_runtime`, `runtime_boundaries`, `indextts_optin`). The main CI job
# runs those with the project's real dependencies, so nothing is dropped
# from CI overall — this job only owns the recipe and probe contracts.
- name: Windows ROCm recipe tests
env:
PYTHONPATH: backend
PYTHONUTF8: "1"
run: |
python -m pytest \
tests/test_windows_rocm_gfx1010.py \
tests/test_windows_rocm_rocm10.py \
tests/test_windows_rocm_install.py \
tests/test_windows_rocm_provision.py \
tests/test_windows_rocm_verification.py \
tests/test_windows_rocm_native_integrity.py \
tests/test_windows_rocm_sdk_integrity.py \
tests/test_windows_rocm_failure_policy.py \
tests/test_windows_rocm_guards.py \
tests/test_windows_rocm_heavy_gates.py \
tests/test_windows_rocm_import_exceptions.py \
tests/test_windows_rocm_native_imports.py \
tests/test_windows_rocm_prerequisites.py \
tests/test_windows_rocm_smoke_local.py \
-q --tb=short

# A separate, explicit assertion rather than only the test suite, so the
# invariant is visible in the job log when it breaks: every overlay the
# recipe declares must be fully hash-pinned and internally consistent.
- name: Every declared overlay is hash-pinned and consistent
env:
PYTHONPATH: backend
PYTHONUTF8: "1"
run: |
python - <<'PY'
import json
import re
import sys

from core.windows_rocm import install, recipe as recipes

data = json.loads(recipes.RECIPE_PATH.read_text(encoding="utf-8"))
variant = data["rocm10"]
archs = set(variant["devices"])
failures = []
if not archs:
failures.append("rocm10 overlay declares no devices")

for arch in sorted(archs):
selected = recipes.load_recipe(architecture=arch)
if selected["gpu_architectures"] != [arch]:
failures.append(f"{arch}: selection returned {selected['gpu_architectures']}")
for line in install.hashed_requirements(selected).splitlines():
url, digest = line.split(" --hash=sha256:")
if not re.fullmatch(r"[0-9a-f]{64}", digest):
failures.append(f"{arch}: unpinned artifact {url}")
if not url.startswith("https://stable.repo.amd.com/rocm/whl-next/"):
failures.append(f"{arch}: unexpected source {url}")
foreign = [
pin for pin in selected["runtime"]["packages"]
if re.search(r"(rocm-sdk-device|amd-torch-device|amd-torchvision-device)-", pin)
and f"-{arch}==" not in pin
]
if foreign:
failures.append(f"{arch}: foreign device packages {foreign}")
print(f"{arch}: {len(selected['runtime']['packages'])} pinned runtime packages, "
f"ct2={recipes.ct2_supported(selected)}")

# The stock 7.2.1 matrix must not silently gain an overlay architecture.
# Read it from the JSON, not through load_recipe(None): that call
# auto-detects the host GPU, so on an RDNA 1 machine it would return
# the overlay and the check would compare the overlay with itself.
overlap = archs & set(data["gpu_architectures"])
if overlap:
failures.append(f"overlay architectures also in the 7.2.1 matrix: {sorted(overlap)}")

if failures:
print("\n".join(failures), file=sys.stderr)
raise SystemExit(1)
print("every declared overlay is hash-pinned, single-architecture and sourced from AMD")
PY

- name: Docs mention the recipes they describe
run: |
set -euo pipefail
doc=docs/install/windows-rocm.md
test -f "$doc"
# A recipe change that leaves the install guide describing another
# architecture is the drift this repo has hit before.
for arch in gfx1010 gfx1030 gfx1031 gfx1032; do
grep -q "$arch" "$doc" || { echo "::error::$doc does not mention $arch"; exit 1; }
done
grep -q "ct2_supported" "$doc" || { echo "::error::$doc must state the CTranslate2 boundary"; exit 1; }
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ licence/catalog data. CLA, identity and secret checks remain enabled.
## Change rules (see CLAUDE.md for full text)
- Root-cause the class, not the instance; fail-before/pass-after regression test; smallest correct change.
- Default behavior identical on macOS/Windows/Linux; platform-only features go behind explicit opt-in. Divergent default = P0.
- Local-first: no new required network calls; any HF download gated on installed-ness or explicit user action; all synthetic audio through the `mark_synthetic` chokepoint. Sanctioned calls are listed in `.github/CONTRIBUTING.md` → Quality gates: user-started first-run setup (ffmpeg from GitHub `zackees/ffmpeg_bins`, the huggingface.co/hf-mirror.com reachability probe, `uv.lock`-pinned package downloads from their locked URLs or the chosen/auto-selected regional mirror incl. the parallel wheel prefetch, the Auto region's one-wheel PyPI vs Aliyun speed check, HF model downloads), opening/enabling the galleries (jsDelivr manifest, GitHub Releases), packaged update checks, user-clicked yt-dlp updates from PyPI, user-selected Windows ROCm setup (owner-approved 2026-10-26, hash-pinned: `repo.radeon.com` wheels and the OpenNMT/CTranslate2 GitHub release), consent-gated analytics, prefilled-URL bug reports, phone content reports sent from a result's Report sheet, the Pro licence check after a key is entered, and VoiceStudio Cloud sign-in/account calls (and settings sync once cloud sync is on) only after the user turns them on (off by default; switching off stops them at once). Owner-approved exception (2026-09-28): the public GitHub star count may refresh automatically every 20 minutes, without credentials/referrer or user content; offline use remains unaffected.
- Local-first: no new required network calls; any HF download gated on installed-ness or explicit user action; all synthetic audio through the `mark_synthetic` chokepoint. Sanctioned calls are listed in `.github/CONTRIBUTING.md` → Quality gates: user-started first-run setup (ffmpeg from GitHub `zackees/ffmpeg_bins`, the huggingface.co/hf-mirror.com reachability probe, `uv.lock`-pinned package downloads from their locked URLs or the chosen/auto-selected regional mirror incl. the parallel wheel prefetch, the Auto region's one-wheel PyPI vs Aliyun speed check, HF model downloads), opening/enabling the galleries (jsDelivr manifest, GitHub Releases), packaged update checks, user-clicked yt-dlp updates from PyPI, user-selected Windows ROCm setup (owner-approved 2026-10-26, hash-pinned: `repo.radeon.com` or `stable.repo.amd.com` wheels and the OpenNMT/CTranslate2 GitHub release), consent-gated analytics, prefilled-URL bug reports, phone content reports sent from a result's Report sheet, the Pro licence check after a key is entered, and VoiceStudio Cloud sign-in/account calls (and settings sync once cloud sync is on) only after the user turns them on (off by default; switching off stops them at once). Owner-approved exception (2026-09-28): the public GitHub star count may refresh automatically every 20 minutes, without credentials/referrer or user content; offline use remains unaffected.
- Every user-facing string via i18n, present in ALL 21 `electron/src/renderer/src/i18n/locales/*.json` files (the catalog the app loads) with real translations; `tests/test_locale_parity.py` and Electron `locale:check` enforce it. The legacy `electron/src/shared/i18n/` catalog is loaded only by shared-module tests.
- Docs-sync in the same PR. Changelog: one quiet one-liner per PR in `changelog.d/<PR#>.<type>.md` (not `CHANGELOG.md`), ending `(#N)` + `— thanks @user!` for community work; the release script folds fragments into Unreleased.
- Tagged release announcements lead with the biggest user-visible change; redesigns need real UI screenshots and migrations need installer links and steps. Verify all contributor credits from the tag comparison and included PRs; list authors and bug reporters separately (see `docs/RELEASING.md`).
Expand Down
Loading