Funktioniert
This commit is contained in:
233
docs/PROJECT_OVERVIEW.md
Normal file
233
docs/PROJECT_OVERVIEW.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# 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.
|
||||
220
docs/VOICE_CLONING.md
Normal file
220
docs/VOICE_CLONING.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Voice Cloning — Qwen3-TTS
|
||||
|
||||
Vollständige Übersicht, wie in diesem Projekt Stimmen geklont werden: welche
|
||||
Modelle das können, welcher Container/Port wofür zuständig ist, welche
|
||||
API-Felder und UI-Pfade es gibt und welche Fallstricke (Embedding-Dimensionen,
|
||||
Stimm-Drift) zu beachten sind.
|
||||
|
||||
> Stand: 2026-06-17. Quelle: Code in diesem Repo (`docker-compose.yml`,
|
||||
> `entrypoint*.sh`, `voice_clone_ui.py`, `ui/voice-cloning.html`,
|
||||
> `patch_qwen3_tts_runtime.py`, `start_*.sh`) + `docs/PROJECT_OVERVIEW.md`.
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
- **Cloning braucht das Base-Modell**, nicht CustomVoice.
|
||||
- `Qwen/Qwen3-TTS-12Hz-1.7B-Base` → hat Speaker-Encoder, kann aus 3 s
|
||||
Referenzaudio klonen.
|
||||
- `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice` (Default-Betrieb, Port 8091) → hat
|
||||
**keinen** Speaker-Encoder (`speaker_encoder_config = None`), nur 9 feste
|
||||
Sprecher. Klon-Versuch crasht mit `Expected size 2048 but got size 1024`.
|
||||
- **Beide Modelle liegen bereits im lokalen HF-Cache** unter
|
||||
`/home/guru/vllm/data/root/.cache/huggingface/hub/`.
|
||||
- **Klon-Workflow:** Base-Modell starten → in der Browser-UI (Port 8092)
|
||||
Referenzaudio + Transkript hochladen → testen oder als Stimme speichern.
|
||||
- **Für stabile Dialogstimmen:** Referenz-WAV bzw. fester Speaker-Vektor
|
||||
(2048-dim) muss bei *jedem* Turn mitgegeben werden, sonst driftet das Timbre.
|
||||
|
||||
---
|
||||
|
||||
## Modellfamilie & Cloning-Fähigkeit
|
||||
|
||||
| Modell | Port/Betrieb | Cloning? | Wie |
|
||||
|---|---|---|---|
|
||||
| `Qwen3-TTS-12Hz-1.7B-CustomVoice` | Default, Port 8091 | ❌ Nein | 9 feste Sprecher (Vivian/Serena/Uncle_Fu/Dylan/Eric/Ryan/Aiden/Ono_Anna/Sohee), `instruct` steuert nur Emotion/Stil |
|
||||
| `Qwen3-TTS-12Hz-1.7B-Base` | via `start_dialog_tts.sh` (Port 8091) oder Clone-Service (Port 8093) | ✅ Ja | 3-Sekunden-Clone aus `ref_audio`, Speaker-Encoder vorhanden |
|
||||
| `Qwen3-TTS-12Hz-1.7B-VoiceDesign` | nicht eingerichtet | ⚠️ Indirekt | erzeugt neue Stimme aus Textbeschreibung (`instruct`), kein Referenzaudio |
|
||||
|
||||
**Embedding-Dimension:** 2048 für die 1.7B-Modelle (1024 für 0.6B-Varianten).
|
||||
Das ist die Ursache des `Expected size 2048 but got size 1024`-Crashs, wenn man
|
||||
CustomVoice ein Clone-Embedding unterschieben will.
|
||||
|
||||
---
|
||||
|
||||
## Betriebsmodi / Container
|
||||
|
||||
Definiert in `docker-compose.yml`. Beide Container teilen sich GPU 0 und den
|
||||
HF-Cache (`/home/guru/vllm/data/root:/root`), laufen aber **nicht gleichzeitig**
|
||||
(Start-Skripte stoppen jeweils den anderen).
|
||||
|
||||
### 1. Haupt-Container `qwen3-tts` (Port 8091/8092/8094)
|
||||
- Startet via `entrypoint.sh`: `vllm-omni serve`, plus
|
||||
- Browser-UI (`voice_clone_ui.py`) auf **8092**
|
||||
- WS-Logging-Proxy (`ws_log_proxy.py`) auf **8094**
|
||||
- Runtime-Patch (`patch_qwen3_tts_runtime.py`) für Seed-Propagation im WS-Pfad
|
||||
- Auto-Warmup-Request nach `/health`
|
||||
- Modell wählbar über `QWEN3_TTS_MODEL`:
|
||||
- `./start_custom_tts.sh` → CustomVoice (kein Cloning, schnell, Standard)
|
||||
- `./start_dialog_tts.sh` → **Base** (Cloning + feste Dialogstimmen)
|
||||
- `./start_tts.sh` / `./start.sh` → respektiert `QWEN3_TTS_MODEL`, Default CustomVoice
|
||||
- `./stop_tts.sh` → stoppt + entfernt den Container
|
||||
|
||||
### 2. Optionaler Clone-Container `qwen3-tts-clone` (Port 8093, Compose-Profil `clone`)
|
||||
- Trennt Cloning von produktiver Ausgabe. Eigenes `entrypoint_clone.sh`,
|
||||
`gpu-memory-utilization 0.10`, `restart: "no"`.
|
||||
- Start: `QWEN3_TTS_CLONE_MODEL=<modell> ./start_clone.sh`
|
||||
(Default-Modell: `Qwen3-TTS-12Hz-1.7B-Base`). Stoppt automatisch `qwen3-tts`.
|
||||
- Verwaltung: `./clone_model.sh {load|unload|status|logs}`, `./stop_clone.sh`.
|
||||
- UI-Proxy-Routen dafür: `GET /api/clone/health`, `POST /api/clone/speech`.
|
||||
|
||||
### Modell-Download (einmalig)
|
||||
```bash
|
||||
./download_qwen3_tts_base.sh # lädt Qwen3-TTS-12Hz-1.7B-Base in den Cache
|
||||
```
|
||||
(Beide Modelle sind aktuell bereits im Cache vorhanden.)
|
||||
|
||||
---
|
||||
|
||||
## Klon-Workflow (empfohlen, über die UI)
|
||||
|
||||
```bash
|
||||
./download_qwen3_tts_base.sh # nur falls Base noch nicht im Cache
|
||||
./start_dialog_tts.sh # startet qwen3-tts mit dem Base-Modell
|
||||
# Browser: http://localhost:8092/
|
||||
```
|
||||
|
||||
In der UI (`ui/voice-cloning.html`) gibt es zwei Klon-Pfade plus Vektor-Verwaltung:
|
||||
|
||||
1. **Ad-hoc Referenz** — Referenzaudio + Transkript direkt im Request mitsenden,
|
||||
Stimme wird nicht gespeichert. Sendet `task_type="Base"`, `ref_audio`
|
||||
(Base64 oder Data-URL), `ref_text`.
|
||||
2. **Gespeicherte Stimme** — Stimme dauerhaft registrieren via
|
||||
`POST /v1/audio/voices` (multipart: audio, ref_text, name, consent,
|
||||
description), danach Synthese mit `voice=<name>`. Auflisten über
|
||||
`GET /v1/audio/voices`, löschen via DELETE.
|
||||
3. **Speaker-Vektoren** — persistente 2048-dim Embeddings, lokal verwaltet vom
|
||||
UI-Proxy unter `/root/.cache/qwen3-tts-ui/vectors`. Synthese mit fixem Vektor
|
||||
über `POST /api/voice-vectors/{name}/speech` (setzt intern `task_type="Base"`,
|
||||
`x_vector_only_mode=true`, `speaker_embedding=<vektor>`).
|
||||
|
||||
> Wichtig: Die UI/der Proxy **extrahiert selbst keine Sprechervektoren** und
|
||||
> trainiert nichts. Er ist ein Client für die vom Modell bereitgestellten
|
||||
> API-Felder. Das Berechnen des x-vectors macht der Modellserver (nur Base).
|
||||
|
||||
---
|
||||
|
||||
## REST-API-Felder für Cloning
|
||||
|
||||
`POST /v1/audio/speech` (Beispiel Ad-hoc-Clone):
|
||||
```json
|
||||
{
|
||||
"model": "Qwen/Qwen3-TTS-12Hz-1.7B-Base",
|
||||
"input": "Der zu sprechende Zieltext.",
|
||||
"task_type": "Base",
|
||||
"ref_audio": "<base64 oder data-URL der Referenz-WAV>",
|
||||
"ref_text": "Transkript der Referenzaufnahme (ICL)",
|
||||
"language": "German",
|
||||
"response_format": "wav"
|
||||
}
|
||||
```
|
||||
|
||||
Synthese mit gespeichertem Speaker-Vektor (vom UI-Proxy erzeugt):
|
||||
```json
|
||||
{
|
||||
"task_type": "Base",
|
||||
"x_vector_only_mode": true,
|
||||
"speaker_embedding": [ /* 2048 floats */ ],
|
||||
"input": "...", "language": "German"
|
||||
}
|
||||
```
|
||||
|
||||
Voice registrieren: `POST /v1/audio/voices` (multipart) — Felder `audio_sample`
|
||||
**oder** `speaker_embedding` (2048-dim), `consent`, `name`, optional `ref_text` /
|
||||
`speaker_description`. `GET /v1/audio/voices` listet, `DELETE
|
||||
/v1/audio/voices/{name}` löscht.
|
||||
|
||||
---
|
||||
|
||||
## UI-Proxy-Routen (`voice_clone_ui.py`, Port 8092)
|
||||
|
||||
Same-Origin-Proxy, vermeidet CORS im Browser:
|
||||
|
||||
| Route | Ziel / Funktion |
|
||||
|---|---|
|
||||
| `GET /` | liefert `ui/voice-cloning.html` |
|
||||
| `GET /health` | → `8091/health` |
|
||||
| `GET /api/config` | aktuelles Modell |
|
||||
| `GET/POST /api/v1/audio/voices` | → `8091/v1/audio/voices` (Liste / registrieren) |
|
||||
| `DELETE /api/v1/audio/voices/{name}` | Voice löschen |
|
||||
| `POST /api/v1/audio/speech` | → `8091/v1/audio/speech` (Synthese) |
|
||||
| `GET/POST /api/voice-vectors` | lokale Speaker-Vektoren auflisten / speichern |
|
||||
| `GET/DELETE /api/voice-vectors/{name}` | einzelnen Vektor holen / löschen |
|
||||
| `POST /api/voice-vectors/{name}/speech` | Synthese mit gespeichertem Vektor |
|
||||
| `GET /api/clone/health` | → `qwen3-tts-clone:8093/health` |
|
||||
| `POST /api/clone/speech` | → `qwen3-tts-clone:8093/v1/audio/speech` |
|
||||
|
||||
Vektor-Speicher: `/root/.cache/qwen3-tts-ui/vectors/<name>.json`, Namen müssen
|
||||
`^[A-Za-z0-9_.-]{1,80}$` matchen, Embedding ≤ 4096 finite Floats.
|
||||
|
||||
---
|
||||
|
||||
## Stimm-Persistenz & Drift (wichtig für Dialoge)
|
||||
|
||||
Eine geklonte/designte Stimme ist **nicht automatisch über mehrere Aufrufe
|
||||
stabil**. Jeder getrennte Call zieht die Sprecher-Realisierung neu aus der
|
||||
Verteilung → Timbre/Tonhöhe driften über Dialog-Turns.
|
||||
|
||||
- Gemessen (CustomVoice, voice=Ryan, seed=42, getrennte Calls): F0 schwankt
|
||||
128–169 Hz (~11 %), Spektralzentroid ~11 %.
|
||||
- **Temperatur/top_k senken hilft nicht** — Drift kommt nicht vom Sampling.
|
||||
- `seed` allein reicht nicht (ersetzt keinen Speaker-Anker), wird aber im
|
||||
WS-Pfad jetzt korrekt pro Segment durchgereicht (`patch_qwen3_tts_runtime.py`).
|
||||
|
||||
**Lösungen:**
|
||||
1. **Fester Speaker-Anker (eigentliche Lösung):** Referenz-WAV bzw. 2048-dim
|
||||
`speaker_embedding` bei *jedem* Turn mitgeben (Base-Pfad). So bleibt das
|
||||
Timbre konstant. Genau dafür sind die gespeicherten Voice-Vektoren da.
|
||||
2. **Sofort-Mitigation (ohne Base):** mehr Text pro Call bündeln (2–4 Sätze) —
|
||||
Drift entsteht *zwischen* Calls, nicht innerhalb. RTF ~0.4, TTFA ~0.16 s
|
||||
erlauben Bündeln ohne große Latenzkosten.
|
||||
|
||||
**„Design-once-then-fixed-voice"-Workflow:** 1× mit VoiceDesign eine Referenz-WAV
|
||||
erzeugen → diese WAV als `ref_audio` mit dem Base-Modell für alle Dialog-Turns
|
||||
nutzen. Braucht VoiceDesign UND Base.
|
||||
|
||||
---
|
||||
|
||||
## Fallstricke
|
||||
|
||||
- **CustomVoice + Clone = Crash:** Server extrahiert 1024-dim, Modell erwartet
|
||||
2048-dim → `RuntimeError: Expected size 2048 but got size 1024`. Container
|
||||
fängt sich via `restart: unless-stopped` + Warmup in Sekunden. Die UI fängt
|
||||
diesen Fall vorab ab und zeigt eine erklärende Fehlermeldung.
|
||||
- Für Cloning **muss** das Base-Modell laufen (`start_dialog_tts.sh` oder
|
||||
Clone-Service), nicht der Default-CustomVoice-Betrieb.
|
||||
- Image ist nicht auf Digest gepinnt (`vllm/vllm-omni:latest-aarch64`); Updates
|
||||
können API-Verhalten ändern.
|
||||
- `ref_audio` als Data-URL **oder** reine Base64 — was akzeptiert wird, hängt
|
||||
vom API-Schema des laufenden Images ab.
|
||||
|
||||
---
|
||||
|
||||
## Schnellreferenz Befehle
|
||||
|
||||
```bash
|
||||
# Cloning-fähigen Betrieb starten (Base-Modell)
|
||||
./start_dialog_tts.sh
|
||||
# UI: http://localhost:8092/ API: http://localhost:8091
|
||||
|
||||
# Zurück auf schnellen Standardbetrieb (keine Klone)
|
||||
./start_custom_tts.sh
|
||||
|
||||
# Getrennter Clone-Service auf Port 8093
|
||||
QWEN3_TTS_CLONE_MODEL=Qwen/Qwen3-TTS-12Hz-1.7B-Base ./start_clone.sh
|
||||
./clone_model.sh status|logs|unload
|
||||
./stop_clone.sh
|
||||
|
||||
# Base-Modell herunterladen (einmalig)
|
||||
./download_qwen3_tts_base.sh
|
||||
```
|
||||
Reference in New Issue
Block a user