234 lines
10 KiB
Markdown
234 lines
10 KiB
Markdown
|
|
# 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 Service `qwen3-tts`, nutzt
|
||
|
|
`vllm/vllm-omni:latest-aarch64`, bindet Port `8091` fuer die API und `8092`
|
||
|
|
fuer 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 gegen `http://localhost:8091`, schreibt
|
||
|
|
`test_output.wav`.
|
||
|
|
- `test_ws.py`: WebSocket-Streaming-Test gegen
|
||
|
|
`ws://localhost:8091/v1/audio/speech/stream`, sammelt PCM-Chunks und schreibt
|
||
|
|
`test_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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
./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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
./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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```text
|
||
|
|
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-vectors`
|
||
|
|
- `POST /api/voice-vectors` mit `name`, `description`, `embedding`
|
||
|
|
- `GET /api/voice-vectors/{name}`
|
||
|
|
- `DELETE /api/voice-vectors/{name}`
|
||
|
|
- `POST /api/voice-vectors/{name}/speech` fuer Synthese mit `speaker_embedding`
|
||
|
|
|
||
|
|
|
||
|
|
Die Oberflaeche unterstuetzt zwei Testpfade:
|
||
|
|
|
||
|
|
- `Gespeicherte Stimme`: listet Stimmen ueber `GET /api/v1/audio/voices`,
|
||
|
|
erzeugt Testaudio mit `voice: <name>` und kann hochgeladene Stimmen loeschen.
|
||
|
|
- `Ad-hoc Referenz`: sendet `ref_audio` und `ref_text` direkt 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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
./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/health`
|
||
|
|
- `POST /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-aarch64` koennen Verhalten oder Kompatibilitaet aendern.
|
||
|
|
- `deploy/qwen3_tts.yaml` und `deploy/qwen3_tts.streaming.yaml` sind derzeit
|
||
|
|
doppelt vorhanden. Wenn beide Varianten dauerhaft gebraucht werden, sollte der
|
||
|
|
Unterschied dokumentiert oder eine Datei entfernt werden.
|
||
|
|
- `test_ws.py` installiert `websockets` bei 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.
|