b805075bdf
- parakeet/compose.yaml: healthcheck was using curl which isn't in the image (only wget is, via apt). 2,190 failing checks — switched to `wget -q -O /dev/null`, container went healthy on recreate. - qwen3-tts/.env.example: variant annotation was reversed. The upstream wrapper's runtime error is unambiguous: voice cloning requires the -Base variant, not -CustomVoice. Corrected the comment block and flipped the default to Qwen/Qwen3-TTS-12Hz-1.7B-Base. - qwen3-tts/README.md: 0.6B switch snippet now suffixes -Base too, since plain `Qwen/Qwen3-TTS-12Hz-0.6B` isn't published on HF.
104 lines
3.7 KiB
Markdown
104 lines
3.7 KiB
Markdown
# 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-Base|" .env && \
|
||
docker compose up -d
|
||
'
|
||
```
|
||
|
||
The new model auto-downloads on next start (~2–3 GB).
|