qwen3-tts: add stack + deploy playbook for irv-ml1
Alibaba's open-weight TTS (Apache 2.0, Jan 2026), deployed via groxaxo/Qwen3-TTS-Openai-Fastapi wrapper. Built locally from a pinned git SHA via docker buildx's git context — no source vendored. 1.7B flagship model by default; 0.6B available via QWEN3_TTS_MODEL env override. Why we need a second TTS stack: cosyvoice 3 emits Chinese phonemes for English content per upstream FunAudioLLM/CosyVoice#1790 (unfixed). Qwen3-TTS is from the same Alibaba team but with English first-class in the checkpoint — 10 languages, 97 ms streaming TTFB, instruction-driven emotion. Coexists with cosyvoice on irv-ml1 (port 8191; cosyvoice keeps 8190). Voice cloning shape DIFFERS from cosyvoice: profile-based, not voice-id. Profiles live under voice_library/profiles/<name>/ and are referenced as voice="clone:<name>". Path layout: /worktank/qwen3-tts/{cache,voices}/, with cache excluded from restic (regenerable from HF Hub) and voices included (cloned profiles need original reference audio to recreate). playbooks/deploy-qwen3-tts.yaml: 10 steps + 5 verify, idempotent; the wait step polls /health for up to ~10 min to absorb first-run model download. Stack only — restic profile update for /worktank/qwen3-tts/voices/ to follow when this is empirically validated against the GLaDOS voice (the "did Qwen inherit the Chinese-bias bug?" question).
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# Qwen3-TTS
|
||||
|
||||
Alibaba's open-weight TTS (Apache 2.0, released Jan 2026), deployed
|
||||
via the [groxaxo/Qwen3-TTS-Openai-Fastapi](https://github.com/groxaxo/Qwen3-TTS-Openai-Fastapi)
|
||||
OpenAI-compatible wrapper.
|
||||
|
||||
**Server:** irv-ml1 (Irvine, WireGuard-only)
|
||||
**Port:** 8191 (container 8880)
|
||||
**GPUs:** both exposed (`NVIDIA_VISIBLE_DEVICES=all`); 1.7B model
|
||||
fits on either the RTX 3090 (24 GB) or A6000 (48 GB) with headroom
|
||||
**Image:** `local/qwen3-tts:v1` — built locally from a pinned git SHA
|
||||
of the wrapper repo via docker buildx's git URL context
|
||||
|
||||
## Why this stack alongside cosyvoice
|
||||
|
||||
CosyVoice 3 (the other stack on this host) emits Chinese-flavored
|
||||
phonemes when given English content. Confirmed against upstream
|
||||
issue [FunAudioLLM/CosyVoice#1790](https://github.com/FunAudioLLM/CosyVoice/issues/1790)
|
||||
— closed without a fix as of 2026-04-14.
|
||||
|
||||
Qwen3-TTS is from the same Alibaba team but built English-first into
|
||||
the checkpoint: 10 languages, 97 ms streaming TTFB, instruction-driven
|
||||
emotional expression, voice cloning. It's the better choice for any
|
||||
English-narration use; keep CosyVoice 3 around for Chinese / dialect
|
||||
work where it shines.
|
||||
|
||||
## API endpoints
|
||||
|
||||
| Method + path | Purpose |
|
||||
|---|---|
|
||||
| `POST /v1/audio/speech` | OpenAI-compatible TTS (drop-in for OpenAI clients) |
|
||||
| `GET /v1/voices` | List cloned voice profiles |
|
||||
| `GET /v1/models` | List available model checkpoints |
|
||||
| `GET /health` | Healthcheck |
|
||||
| `GET /` | Web UI |
|
||||
| `GET /voice-studio` | Gradio voice-cloning UI (when `ENABLE_VOICE_STUDIO=true`) |
|
||||
|
||||
## Voice cloning — different shape from cosyvoice
|
||||
|
||||
Profile-based, not voice-id-based:
|
||||
|
||||
1. Clone a voice via the `/voice-studio` web UI (uploads reference
|
||||
audio + transcript, names the profile, e.g. `glados`).
|
||||
2. The wrapper writes
|
||||
`/root/qwen3-tts/voice_library/profiles/<name>/{meta.json,reference.wav}`
|
||||
inside the container (bind-mounted to
|
||||
`/worktank/qwen3-tts/voices/profiles/<name>/` on the host).
|
||||
3. Reference the profile in synthesis requests as
|
||||
`voice="clone:<name>"`.
|
||||
|
||||
```bash
|
||||
# OpenAI-shape, English with emotion via instruction
|
||||
curl -X POST http://10.100.79.3:8191/v1/audio/speech \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"model": "Qwen/Qwen3-TTS-12Hz-1.7B",
|
||||
"voice": "clone:glados",
|
||||
"input": "You should leave now. Visitors are not welcome.",
|
||||
"instructions": "speak with cold contempt",
|
||||
"response_format": "wav"
|
||||
}' \
|
||||
-o glados_en_angry.wav
|
||||
```
|
||||
|
||||
(Note `instructions` field name; OpenAI standard. Whether English
|
||||
instructions actually drive emotion correctly here is the open
|
||||
question this stack exists to test — see the deploy playbook's verify
|
||||
section.)
|
||||
|
||||
## Path layout
|
||||
|
||||
| Host path | Container path | Purpose | Restic? |
|
||||
|---|---|---|---|
|
||||
| `/worktank/qwen3-tts/cache/` | `/root/.cache/huggingface` | Model cache (~5 GB after first run) | excluded |
|
||||
| `/worktank/qwen3-tts/voices/` | `/root/qwen3-tts/voice_library` | Cloned profiles | **included** (precious) |
|
||||
|
||||
## Deploy
|
||||
|
||||
Via elway — see `playbooks/deploy-qwen3-tts.yaml` in the
|
||||
eshpfi-management root. The playbook builds the image, ensures the
|
||||
host dirs, installs compose + .env, brings up. First boot downloads
|
||||
the 1.7B model from HF (~6 GB), which is the slowest step.
|
||||
|
||||
```bash
|
||||
scripts/elway irv-ml1 --playbook playbooks/deploy-qwen3-tts.yaml
|
||||
```
|
||||
|
||||
To pin a different upstream wrapper SHA, set `--var sha=<new-sha>` or
|
||||
edit `.env` on the server and rebuild.
|
||||
|
||||
## Switching to the smaller model
|
||||
|
||||
If 1.7B is too heavy or you need to share GPUs more aggressively:
|
||||
|
||||
```bash
|
||||
ssh irv-ml1 '
|
||||
cd /opt/docker/compose/qwen3-tts && \
|
||||
sed -i "s|^QWEN3_TTS_MODEL=.*|QWEN3_TTS_MODEL=Qwen/Qwen3-TTS-12Hz-0.6B|" .env && \
|
||||
docker compose up -d
|
||||
'
|
||||
```
|
||||
|
||||
The new model auto-downloads on next start (~2–3 GB).
|
||||
Reference in New Issue
Block a user