# Qwen3-TTS — Alibaba's open-weight TTS, deployed via the # groxaxo/Qwen3-TTS-Openai-Fastapi wrapper. # # Why this stack exists alongside cosyvoice: CosyVoice 3 emits # Chinese phonemes for non-Chinese inputs (upstream issue # FunAudioLLM/CosyVoice#1790, no fix). Qwen3-TTS is from the same # Alibaba team but with English first-class — 10 languages, 97 ms # streaming TTFB, voice cloning, instruction-driven emotional # expression. Released Jan 2026, Apache 2.0. # # Build: no prebuilt image; pinned to a SHA via docker buildx's git # context URL so subsequent rebuilds are reproducible. ~5–10 min on # first build (CUDA torch + transformers). # # Model: 1.7B flagship (~6–8 GB VRAM with bfloat16) by default; the # host has plenty of VRAM. Switch to the 0.6B in .env if you ever # need more headroom. # # Voice cloning shape DIFFERS from cosyvoice: profile-based, not # voice-id. Profiles live under voice_library/profiles// with # meta.json + reference.wav, and are referenced as # `voice="clone:"` in /v1/audio/speech requests. # # All tunables live in .env — edit that, not this file. services: qwen3-tts: image: local/qwen3-tts:${QWEN3_TTS_TAG} build: context: https://github.com/groxaxo/Qwen3-TTS-Openai-Fastapi.git#${QWEN3_TTS_SHA} dockerfile: Dockerfile # Upstream Dockerfile defines five stages — without an explicit # target, buildkit picks the LAST named stage (`cpu-base`) and # produces a CPU-only image with no GPU torch + onnxruntime # (not -gpu). Pin to `production` to get the CUDA stack with # flash-attn that the model actually needs. target: production container_name: qwen3-tts restart: unless-stopped runtime: nvidia # Upstream image declares `USER appuser` (uid 1000) but writes # state under /root (mode 0700) — appuser can't traverse it, so # /v1/voices and any FS-touching endpoint 500s. The model loads # only because warmup happens in an early root phase. Pin to root # so all paths are reachable. Files written into the host bind # mounts become root-owned; that's fine for restic + sudo reads. user: "0:0" ports: - "${QWEN3_TTS_BIND:-0.0.0.0}:${QWEN3_TTS_PORT}:8880" environment: - NVIDIA_VISIBLE_DEVICES=all - PORT=8880 - TTS_BACKEND=${QWEN3_TTS_BACKEND:-official} - TTS_MODEL_NAME=${QWEN3_TTS_MODEL:-Qwen/Qwen3-TTS-12Hz-1.7B} # Only used when TTS_BACKEND=optimized — points the optimized backend # at the upstream config.yaml (which defines the model registry + # default model). Without this, the optimized backend looks at # ~/qwen3-tts/config.yaml inside the container, which doesn't exist # at runtime, leaving the model registry empty and synthesis fails # with "Unknown model key". - TTS_CONFIG=/app/config.yaml - TTS_WARMUP_ON_START=${QWEN3_TTS_WARMUP:-true} - TTS_MAX_CONCURRENT=${QWEN3_TTS_MAX_CONCURRENT:-1} - ENABLE_VOICE_STUDIO=${QWEN3_TTS_VOICE_STUDIO:-true} - VOICE_LIBRARY_DIR=/root/qwen3-tts/voice_library - HF_HOME=/root/.cache/huggingface volumes: - ${QWEN3_TTS_CACHE_DIR}:/root/.cache/huggingface - ${QWEN3_TTS_VOICES_DIR}:/root/qwen3-tts/voice_library healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8880/health >/dev/null || exit 1"] interval: 30s timeout: 10s retries: 3 # First boot pulls torch + Qwen3-TTS-12Hz-1.7B (~6 GB) and # optionally warms the model — give it a generous budget. start_period: 600s labels: - homepage.group=AI Systems - homepage.name=Qwen3-TTS - homepage.icon=mdi-account-voice - homepage.description=Multilingual TTS with English-first emotion (irv-ml1) - homepage.href=http://10.100.79.3:${QWEN3_TTS_PORT}