10 KiB
Projektueberblick: Qwen3-TTS
Dieses Repository beschreibt und betreibt einen Qwen3-TTS-Service auf Basis von
vllm/vllm-omni. Es enthaelt die Containerkonfiguration, Startlogik,
Deployment-Parameter und einfache Testclients fuer REST- und WebSocket-Zugriffe.
Der eigentliche Modellserver kommt aus dem vLLM-Omni-Image; dieses Repo kapselt
vor allem Betrieb, Konfiguration und API-Nutzung.
Zweck
Der Service stellt Text-to-Speech auf Port 8091 bereit. Standard ist
Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice; fuer dialogweite feste
geklonte Stimmen wird Qwen/Qwen3-TTS-12Hz-1.7B-Base gestartet. Er unterstuetzt
zwei Nutzungsarten:
- REST-Vollausgabe ueber
POST /v1/audio/speech - inkrementelles Streaming ueber
WS /v1/audio/speech/stream
Das Streaming-Protokoll ist fuer Token-Streams aus LLMs gedacht: Textfragmente werden fortlaufend gesendet, der Server segmentiert an Satz- oder Teilsatzgrenzen und gibt Audio pro Segment zurueck.
Architektur
Der Container startet vllm-omni serve mit Omni-Modus und einer
Deployment-Konfiguration aus deploy/qwen3_tts.yaml. Die Pipeline besteht aus
zwei Stages:
- Stage 0: Talker-LLM fuer die sprachliche Token-/Codec-Erzeugung
- Stage 1: Code2Wav-Decoder fuer die Audiosynthese
Beide Stages laufen auf GPU 0 und kommunizieren ueber einen
Shared-Memory-Connector. async_chunk: true und codec_streaming: true
aktivieren chunked Audioausgabe. Die Konfiguration ist auf eine geteilte
DGX-Spark-Maschine mit begrenzter GPU-Speichernutzung ausgelegt
(gpu_memory_utilization: 0.08 pro Stage).
Wichtige Dateien
docker-compose.yml: definiert den Serviceqwen3-tts, nutztvllm/vllm-omni:latest-aarch64, bindet Port8091fuer die API und8092fuer die Browser-Oberflaeche, mountet Hugging-Face-Daten sowie Deployment-, Entrypoint- und UI-Dateien.entrypoint.sh: startet die Voice-Cloning-Oberflaeche im Container, startet den vLLM-Omni-Server und fuehrt nach erfolgreichem Healthcheck einen Warmup-Request aus, damit CUDA-Graph-/Compile-Kosten nicht den ersten echten Nutzerrequest treffen.start.sh: startet den Compose-Service, filtert relevante Containerlogs in lesbare Statusmeldungen und wartet auf/health.deploy/qwen3_tts.yaml: aktive Deployment-Konfiguration fuer die zweistufige TTS-Pipeline, Streaming-Connectoren, Sampling-Parameter und Speicherlimits.deploy/qwen3_tts.streaming.yaml: aktuell identisch zur aktiven Deployment-Konfiguration; offenbar als explizite Streaming-Variante abgelegt.STREAMING_API.md: detaillierte API-Dokumentation fuer REST, WebSocket, Session-Protokoll, Audioformate, Segmentierung und bekannte Latenzwerte.test_http.py: einfacher REST-Test gegenhttp://localhost:8091, schreibttest_output.wav.test_ws.py: WebSocket-Streaming-Test gegenws://localhost:8091/v1/audio/speech/stream, sammelt PCM-Chunks und schreibttest_stream_output.wav.ui/voice-cloning.html: einfache Browser-Oberflaeche fuer Voice Cloning mit Referenzaudio, Referenztranskript, Zieltext und Ausgabeplayer.voice_clone_ui.py: kleiner lokaler Webserver, der die HTML-Oberflaeche ausliefert und REST-Anfragen als Same-Origin-Proxy an den TTS-Service weiterleitet.
Betrieb
Der normale TTS/UI-Service wird gestartet und gestoppt ueber:
./start_tts.sh
./stop_tts.sh
./start.sh bleibt als kompatibler Wrapper auf ./start_tts.sh erhalten. Beim
Start des TTS-Service wird ein eventuell laufender Clone-Container automatisch
gestoppt. Das Skript startet danach qwen3-tts, beobachtet die Logs und beendet
sich erfolgreich, sobald http://localhost:8091/health antwortet. Die UI ist
unter http://localhost:8092/ erreichbar. Der WebSocket-Logging-Proxy laeuft auf ws://localhost:8094/v1/audio/speech/stream.
Voraussetzungen sind ein Docker-Setup mit NVIDIA-Runtime, Zugriff auf GPU 0
und ein lokaler Modell-/Cache-Mount unter /home/guru/vllm/data/root, der in den
Container als /root eingebunden wird.
Dialogbetrieb mit fester geklonter Stimme
Die Qwen-Hugging-Face-Modelcard listet Qwen/Qwen3-TTS-12Hz-1.7B-Base
als Base-Modell fuer 3-Sekunden-Voice-Cloning und beschreibt wiederverwendbare
Voice-Clone-Prompts. Im lokalen vLLM-Omni-Code wird ein gespeicherter oder aus
ref_audio extrahierter Speaker-Anker als ref_spk_embedding an Qwen3-TTS
uebergeben. Fuer einen Voice-Dialog ist deshalb der richtige Betriebsmodus:
./download_qwen3_tts_base.sh # einmalig, falls das Base-Modell noch fehlt
./start_dialog_tts.sh # startet qwen3-tts mit Qwen3-TTS-12Hz-1.7B-Base
Danach unter http://localhost:8092/ die Referenzstimme speichern und fuer
Ausgaben Gespeicherte Stimme mit Task Base verwenden. Der Server nutzt dann
bei jeder Synthese dieselbe gespeicherte Referenzstimme; intern wird daraus der
Qwen3-TTS-Speaker-Anker erzeugt und gecacht. Fuer harte Persistenz ohne
Referenzaudio kann alternativ ein passender 2048-dim speaker_embedding ueber
/api/voice-vectors gespeichert und bei jeder Ausgabe mitgesendet werden.
seed bleibt zusaetzlich sinnvoll, ersetzt aber keinen Speaker-Anker. Der
Runtime-Patch patch_qwen3_tts_runtime.py sorgt dafuer, dass seed im
WebSocket-Pfad tatsaechlich an jedes Segment weitergereicht wird und die
relevanten Werte im Containerlog sichtbar sind.
API-Nutzung
REST eignet sich fuer komplette Eingabetexte und liefert standardmaessig eine WAV-Antwort. Der Testclient nutzt:
python3 test_http.py
WebSocket eignet sich fuer Low-Latency-Szenarien, in denen Text schrittweise
eintrifft. Eine Session beginnt mit session.config, nimmt danach beliebig viele
input.text-Frames an und wird mit input.done abgeschlossen. Audio kommt als
PCM-Binaerframes oder WAV-Bloecke zurueck. Der Testclient nutzt:
python3 test_ws.py
Details zu Nachrichtentypen, Timeouts, Audioformaten und externem Zugriff stehen
in STREAMING_API.md.
Voice-Cloning-Oberflaeche
Die Browser-Oberflaeche laeuft im selben Container wie der TTS-Service. Beim
Start ueber ./start.sh wird neben der API auf Port 8091 auch die UI auf Port
8092 veroeffentlicht:
http://localhost:8092/
Der im Container gestartete Proxy leitet POST /api/v1/audio/speech intern an
http://localhost:8091/v1/audio/speech weiter. Dadurch muss der Browser keine
direkten Cross-Origin-Requests an die TTS-API senden. Zusaetzlich verwaltet der
Proxy persistente Speaker-Vektoren unter /root/.cache/qwen3-tts-ui/vectors
und stellt dafuer diese lokalen Routen bereit:
GET /api/voice-vectorsPOST /api/voice-vectorsmitname,description,embeddingGET /api/voice-vectors/{name}DELETE /api/voice-vectors/{name}POST /api/voice-vectors/{name}/speechfuer Synthese mitspeaker_embedding
Die Oberflaeche unterstuetzt zwei Testpfade:
Gespeicherte Stimme: listet Stimmen ueberGET /api/v1/audio/voices, erzeugt Testaudio mitvoice: <name>und kann hochgeladene Stimmen loeschen.Ad-hoc Referenz: sendetref_audioundref_textdirekt im Speech-Request, ohne die Stimme dauerhaft zu speichern.
Neue Stimmen koennen in der UI mit Referenzaudio, Referenztranskript, Name,
Consent-ID und Beschreibung ueber POST /api/v1/audio/voices gespeichert
werden. Der Proxy leitet diese Route im Container an /v1/audio/voices weiter.
ref_audio kann beim Ad-hoc-Test als Data-URL oder reine Base64-Zeichenkette
gesendet werden. Welche Variante akzeptiert wird, haengt vom
vLLM-Omni/Qwen3-TTS-API-Schema des laufenden Images ab. Falls der aktuell
gestartete Modellpfad Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice kein Base-Cloning
unterstuetzt, meldet die API einen Fehler; dann muss im UI-Feld Modell ein
klonfaehiges Qwen3-TTS-Modell eingetragen und der Container entsprechend
gestartet werden.
Optionaler Clone-Service
Fuer die Trennung von produktiver TTS-Ausgabe und Voice-Cloning gibt es einen
optionalen zweiten Container qwen3-tts-clone im Compose-Profil clone. Er
laeuft auf Port 8093 und wird nur bei Bedarf gestartet:
QWEN3_TTS_CLONE_MODEL=<lokal-verfuegbares-clone/base-modell> ./start_clone.sh
Beim Start des Clone-Service wird der normale TTS/UI-Container automatisch
gestoppt. Damit laufen TTS und Clone nicht gleichzeitig auf derselben GPU.
clone_model.sh bleibt fuer manuelles status, logs und unload erhalten:
./stop_clone.sh
./clone_model.sh status
./clone_model.sh logs
./clone_model.sh unload
Default fuer den Clone-Service ist jetzt Qwen/Qwen3-TTS-12Hz-1.7B-Base.
Wenn das Modell noch nicht im lokalen Hugging-Face-Cache liegt, zuerst
./download_qwen3_tts_base.sh ausfuehren oder dem Container funktionierenden
Internet-/DNS-Zugriff geben.
Der UI-Proxy kennt dafuer diese optionalen Routen:
GET /api/clone/health->qwen3-tts-clone:8093/healthPOST /api/clone/speech->qwen3-tts-clone:8093/v1/audio/speech
Die normale TTS-Ausgabe kann bereits gespeicherte Speaker-Vektoren verwenden.
Dafuer sendet POST /api/voice-vectors/{name}/speech den gespeicherten Vektor
als speaker_embedding mit task_type: Base und x_vector_only_mode: true an
den Haupt-TTS-Service.
Bekannte Eigenschaften und Risiken
- Das Repo enthaelt keine eigene Serverimplementierung; Verhalten und API werden vom verwendeten vLLM-Omni-Image und Modellcode bestimmt.
- Die Compose-Konfiguration pinnt das Image nicht auf einen Digest. Updates von
vllm/vllm-omni:latest-aarch64koennen Verhalten oder Kompatibilitaet aendern. deploy/qwen3_tts.yamlunddeploy/qwen3_tts.streaming.yamlsind derzeit doppelt vorhanden. Wenn beide Varianten dauerhaft gebraucht werden, sollte der Unterschied dokumentiert oder eine Datei entfernt werden.test_ws.pyinstalliertwebsocketsbei fehlendem Import automatisch per pip. Das ist praktisch fuer lokale Tests, aber fuer reproduzierbare Umgebungen weniger kontrolliert als eine explizite Requirements-Datei.- Die Voice-Cloning-Oberflaeche ist ein Client fuer vorhandene API-Felder. Sie extrahiert oder trainiert keine Sprechervektoren selbst.
- Die Streaming-Doku weist auf moegliche Stimm-Drift zwischen Segmenten hin, weil jedes Segment separat synthetisiert wird.
Kurzfazit
Das Projekt ist ein schlankes Betriebs- und Integrationsrepo fuer einen Qwen3-TTS-Service. Sein Schwerpunkt liegt auf GPU-Deployment, Warmup, Low-Latency-Streaming und pruefbaren Clientbeispielen, nicht auf eigener Modellentwicklung oder einer selbst implementierten API-Schicht.