Adds two zero-shot voice-cloning TTS engines requested in #498, both opt-in and subprocess-isolated with their own dedicated venv — the same pattern as IndexTTS-2. The dedicated venv is forced, not just chosen: each upstream pins a transformers version that conflicts with the parent's >=5.3 (MOSS-TTS-v1.5 ==5.0.0, dots.tts ==4.57.0), so they cannot share the parent interpreter. Because they use the clone+venv bootstrap (env var -> clone -> uv venv), this touches no pyproject.toml / uv.lock / bun.lock — `uv sync --all-extras` and Docker's `bun install --frozen-lockfile` are unchanged, so main's CI/Docker matrix stays green. Engines: - moss-tts-v15: 8B, 31 langs, ~16 GB weights, 24 kHz. AutoModel/ AutoProcessor via trust_remote_code. gpu_compat=(cuda,cpu) — MPS is undocumented/untested upstream so it is never claimed; on a Mac it runs on CPU. Apache-2.0, no license gate. - dots-tts: 2B, 24 langs, ~9 GB weights, 48 kHz. DotsTtsRuntime; continuation cloning (prompt_audio_path+prompt_text). Upstream is Linux/macOS-only, so is_available() gates it off cleanly on Windows (cross-platform parity rule — it is opt-in, never a broken default). Wiring: registered in _LAZY_REGISTRY + _INSTALL_HINTS. list_backends() surfaces both as subprocess/[cuda,cpu]/available-until-installed; the data-driven Settings engine picker needs no frontend change. Tests (19, fail-before/pass-after): registry resolution, subprocess marker, no-MPS gpu_compat, the Windows gate, not-installed honesty, and the parent-side generate() kwarg arbitration. Existing engine suite still 55 passed / 5 skipped. Sidecar inference follows the upstream-documented APIs but, like IndexTTS/Supertonic, can't be executed in CI without the multi-GB model clones. Docs (same-PR per docs-sync rule): README + README_CN engine tables, new docs/engines/moss-tts-v15.md + dots-tts.md, disk-usage.md (torch-dedup note), CHANGELOG. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.7 KiB
Engine venvs & disk usage
Most engines run in-process in OmniVoice's main environment. A few
(IndexTTS2, MOSS-TTS-v1.5, dots.tts, and any engine whose
dependencies conflict with the parent's torch/transformers pins) run in a
dedicated sidecar venv so their pins can't break the rest of the app. Those
sidecars are where disk adds up — this page explains why, and how the on-disk
cost is kept down.
Why a sidecar needs its own venv
IndexTTS2 pins transformers<5, but OmniVoice requires transformers>=5.3.
You can't have both in one environment, so IndexTTS2 gets its own venv created
on first use (uv venv + uv pip install, see
backend/engines/indextts/bootstrap.py). The cost is a second copy of the
heavy ML stack — most of which is torch + the bundled CUDA libraries.
How big is "a second torch"?
Measured (2026-06):
| Platform | torch CUDA wheel | + bundled CUDA libs |
|---|---|---|
| Linux (cu128) | ~0.83 GiB | several GiB of nvidia-* packages on top |
| Windows (cu128) | ~3.2 GiB (DLLs bundled in the wheel) | — |
So a sidecar that pins a different torch version than the parent is a multi-GB add. A sidecar that pins the same torch + CUDA build shares almost all of it (see below).
uv dedupes identical wheels — for free, with one condition
uv installs packages by linking from a global wheel cache into each venv's
site-packages. The link mode:
- macOS + Linux —
clone(copy-on-write reflink). N venvs that install the same wheel share the bytes until one is modified — effectively one copy on disk. - Windows —
hardlink. Same effect on a single volume.
The one condition: the cache and the venv must be on the same
filesystem. If UV_CACHE_DIR lives on a different drive than the engine
venvs, uv falls back to a full copy (no dedup, slower). Keep them together.
Dedup is per identical wheel. torch==2.6.0+cu124 and torch==2.8.0+cu128
are different wheels → zero sharing → a full extra multi-GB copy. The single
biggest disk decision for a sidecar is therefore: pin the same torch build as
the parent whenever the engine allows it. When it doesn't (IndexTTS2's
transformers<5 forces an older torch line), the second copy is the
unavoidable price of isolation — not a bug.
The opt-in #498 engines illustrate both sides: dots.tts pins
torch==2.8.0 — the same build the parent constrains to — so it shares
almost all of torch with the main venv and only its transformers==4.57 +
model deps are new. MOSS-TTS-v1.5 pins torch==2.9.1+cu128, a different
build, so it pays a full extra multi-GB torch copy on CUDA hosts (the price of
running an 8B model whose stack pins transformers==5.0).
On Linux, the
nvidia-*CUDA packages are separate wheels, so even across different torch versions anynvidia-*whose pinned version happens to match is still shared. On Windows the CUDA DLLs live inside the one torch wheel, so nothing is shared across torch versions.
Practical guidance
- Keep
UV_CACHE_DIRand the engine venvs on one filesystem (the default — both under your home dir — already satisfies this). - On Linux ext4 (no reflink),
export UV_LINK_MODE=hardlinkguarantees dedup on any single filesystem; the defaultcloneonly dedupes on reflink-capable filesystems (XFS-with-reflink, btrfs, APFS). - Reclaiming space: deleting a sidecar venv (
backend/engines/<id>/.venv/) frees its unique files; shared cache bytes stay untiluv cache prune. - Forward-looking: PyTorch's experimental wheel variants (shipped in 2.8)
will eventually let
uv install torchauto-pick the right CUDA build, and uv already exposes--torch-backend=auto.