# Lucy TTS — Optimierungs- & Strategieplan > Ziel: **Lucys Stimme läuft 100 % lokal & on-device** (kein Box-Zwang, keine Cloud). > Primärengine: **Kyutai pocket-tts 2.1.0** (CPU). Strategische Alternative: **F5-TTS** (GPU/ONNX). > Stand: 2026-06-30. Resume-fähig im Stil von `docs/STATUS.md`. ## Leitentscheidung: Hardware - **pocket-tts ist CPU-gebunden** — Kyutai bestätigt: *kein* GPU-Speedup (Batch 1, 100M Params). → **RDNA2 vs. RDNA4 ist für pocket irrelevant.** Lucy läuft dort, wo der beste CPU steht. - **GPU zählt nur für F5-TTS** (non-autoregressiv, große Matmuls). Dort gewinnt **RDNA4 (9070 XT) + DirectML** deutlich gegen RDNA2 → falls F5 die Lucy-Stimme wird, läuft sie auf der RDNA4-Maschine. - **Konsequenz:** `oute n_gpu_layers=999` und „pocket auf ROCm/Vulkan" werden **eingestellt**. --- ## Phase A — Sofort-Wins (risikoarm, pocket_server.py) **A1 — Voice → safetensors exportieren (einmalig)** - Statt `get_state_for_audio_prompt(ref)` bei jedem Boot: einmal `export_model_state(...)` → `lucy_voice.safetensors`. - Server lädt beim Start nur noch die safetensors (liest kvcache, keine Klon-Rechnung) → schnellerer Start. - Fallback behalten: wenn safetensors fehlt → aus `ref.mp3` klonen + direkt exportieren. **A2 — Referenz säubern** - Kyutai: Sample-Qualität wird *mitreproduziert*. Sauber entrauschte/normalisierte `ref` → weniger Artefakte. - Schritt in `_prep_ref()` ergänzen (Denoise/Highpass/Lautheit), Ergebnis cachen. **A3 — temp-Sweep gegen Kollaps** - Hypothese: `temp=0.9` treibt die best-of-N-Regenerationen. Niedriger testen (0.7–0.85). - Akzeptanz: gleiche/bessere Natürlichkeit bei **messbar weniger Regenerationen** (= weniger Latenz). *Gate A:* A1–A3 live, Kollaps-Rate & TTFB protokolliert. --- ## Phase B — Benchmark-Harness (datenbasiert tunen) **B1 — `bench_lucy.py`** (lokal, CPU) - Misst je Konfig: **RTF**, **TTFB**, **Kollaps-/Regenerations-Rate**, Whisper-Rücktranskription (Verständlichkeit). - Sweep-Achsen: `lsd_decode_steps` (6/8/10/12), `temp`, `noise_clamp`, `quantize` on/off, `frames_after_eos`. - Feste Testsätze (kurz/mittel/lang, wie in `ptts_test.py`). **B2 — CPU-Parallelität** - pocket nutzt nur **2 Kerne** → mehrere **Satz-Worker parallel** statt globalem `LOCK`. - Messen: Wall-Clock langer Antworten bei N Workern (1/2/3/4) vs. Qualität/Last. *Gate B:* dokumentierte Best-Config (RTF + Kollaps-Rate) als neue Defaults in `pocket_server.py`. --- ## Phase C — Runtime-Eval (lokal schneller + Python-3.14-frei) **C1 — ONNX-Pfade testen** - Kandidaten: **PocketTTS.cpp** (Single-File C++/ONNX, CLI+HTTP+FFI) und **sherpa-onnx** (Windows, viele Bindings). - Ziel: torch-CPU schlagen **und** das Python-3.14-Packaging-Problem umgehen. - Vergleich gegen Phase-B-Baseline (gleicher Benchmark). **C2 — Server-Vertrag prüfen** - Fertige **OpenAI-kompatible Streaming-Server** (teddybear082 / ai-joe-git) gegen den aktuellen Custom-Proxy halten. - Nur übernehmen, wenn Stimm-Wächter (F0 + Fingerabdruck) erhalten/abbildbar bleiben. *Gate C:* Entscheidung „torch-CPU behalten" vs. „auf ONNX-Runtime wechseln" (mit Zahlen). --- ## Phase D — Strategische Weiche: pocket vs. F5 **D1 — F5 fair gegen pocket messen** - `lucy-f5/` (F5-TTS DE, ONNX + DirectML, **RDNA4/9070 XT**) ist **non-autoregressiv → strukturell kein Kollaps/Männerstimme/Wiederholung** (genau pockets Schmerz). - Gleicher Benchmark wie Phase B: RTF, TTFB, Natürlichkeit, Stabilität. **D2 — Entscheidung** - **pocket gewinnt** (gut genug stabil, CPU, „nur lokal"-Ideal): pocket = Lucy-Stimme, F5 verworfen/geparkt. - **F5 gewinnt** (Stabilität schlägt den Latenz-Overhead der Wächter): F5 = Lucy-Stimme auf RDNA4, **pocket bleibt schneller CPU-Fallback** (z. B. wenn keine GPU verfügbar). **Beobachtungsposten (extern):** distilliertes **`german`** (statt `german_24l`) ist noch nicht released (Kyutai fixt Distillations-Datenqualität). Sobald da → größter Einzel-Win (Tempo + Stabilität), dann `german_24l → german` swappen. Release-Feed im Blick behalten. --- ## Reihenfolge & Quick-Start 1. **A1 + A2 + A3** (heute) — sofort spürbar, kein Risiko. 2. **B1 + B2** — Zahlen sammeln, Defaults härten. 3. **C1/C2** — Runtime-Wechsel nur wenn Benchmark es trägt. 4. **D1/D2** — finale Stimm-Architektur entscheiden. ## Mess-Ergebnisse & finale Defaults (30.06., 9700X, german_24l) Alles auf der lokalen Maschine gemessen (Logs: `sweep_b2.log`, `ttfb_test.log`, `sweep_b2_result.json`). **B2-Sweep (6-Satz-Antwort, ~21s Audio):** | Konfig | Wall | TTFB | RTF | |---|---|---|---| | seriell (1×alle Kerne) | 15,8s | **3,6s** | 0,74 | | 2w×3t | 12,9s | 6,2s | 0,62 | | 3w×2t | 10,3s | 6,8s | 0,50 | | 4w×2t | 9,7s | 8,2s | **0,44** | Erkenntnis: pocket ist **speicherbandbreiten-gebunden**. Der Pool verbessert nur die Gesamt-Wall-Clock (Batch), verschlechtert aber die **TTFB** deutlich. Seriell liefert RTF 0,74 < 1 → generiert schneller als Echtzeit → Streaming spielt **lückenlos** und startet am schnellsten. **Für Lucys Live-Stimme gewinnt seriell.** Pool bleibt Opt-in für Batch (`/tts` ganze Datei): Sweet Spot **4w×2t** / **3w×2t**. **B3 (FP-Drift-Gate überspringt kurze Audios) + kurzer erster Chunk:** Der MFCC-Fingerabdruck ist auf <2s Audio unzuverlässig (sim ~0,84 < 0,94) → löste 3× Fehlalarm- Regenerierung aus. B3 prüft den Drift erst ab `LUCY_FP_MIN_S=2.0`s stimmhafter Dauer; der F0- Männerstimmen-Wächter bleibt immer aktiv. Effekt (Drift-Warnungen pro Lauf: ~6 → 1): | | TTFB | Wall | |---|---|---| | kurzer 1. Chunk, ohne B3 | 4,71s | 19,9s | | gebündelt (alt) | 3,74s | 15,8s | | **kurzer 1. Chunk, mit B3** | **1,31s** | 16,6s | → Lucy spricht nach **1,3s** statt 3,7s, Wall ~gleich, weiter lückenlos. **Finale Defaults in `pocket_server.py`:** `LUCY_WORKERS=0` (seriell) · `LUCY_FAST_FIRST=1` (kurzer 1. Chunk) · `LUCY_FP_MIN_S=2.0` (B3) · `LUCY_REF_CLEAN=1` (A2) · Voice aus `lucy_voice.safetensors` (A1). Pool-Opt-in für Batch: `LUCY_WORKERS=4 LUCY_WORKER_THREADS=2`. **Offen / nächster Hebel:** distilliertes `german`-Modell (statt `german_24l`) abwarten — größter Qualität/Tempo-Sprung. Optional: TTFB weiter drücken über kürzeres erstes Wort / `lsd`-Tuning nur für den ersten Chunk. ## Umlaute (ae/oe/ue) — Fix (30.06.) Symptom: Lucy spricht manchmal „u-e" statt „ü". Ursache: das LLM (Hermes/Qwen) gibt gelegentlich ASCII-Ersatzschreibweisen (ueber/schoen/maerz) aus; pocket liest sie wörtlich. (ß vs ss ist akustisch identisch -> ignoriert.) **Wurzel-Fix (empfohlen, auf der Box im Hermes-System-Prompt/Persona ergänzen):** > „Schreibe ausschließlich korrektes Deutsch mit echten Umlauten (ä, ö, ü) und ß. Verwende niemals > die Ersatzschreibweisen ae, oe, ue oder ss anstelle von Umlauten." **Schutznetz (in `pocket_server.py`, Default an `LUCY_UMLAUT_FIX=1`):** `_fix_umlauts()` wandelt NUR bekannte deutsche Umlaut-Ganzwörter zurück (Ganzwort + gängige Flexionsendungen, case-erhaltend). Verifiziert (`umlaut_test.py`): konvertiert über/für/größe/natürlich/mögliche/Gespräche; lässt neue/aktuell/Steuer/Quelle/Feuer/Frauen/genau/blaue unverändert. Grenzen: reine Wortliste, deckt nicht jedes seltene Wort — erweiterbar via `LUCY_UMLAUT_EXTRA="wort1,wort2"`. Der Wurzel-Fix bleibt die zuverlässige Lösung. ## Referenzen - pocket-tts README (GPU-kein-Speedup, export-voice, Sprachen): github.com/kyutai-labs/pocket-tts - Multilingual-Ankündigung (DE = `german_24l`, undistilliert): kyutai.org/blog/2026-05-04-pocket-tts-multilingual - Tech-Report / Performance: kyutai.org/blog/2026-01-13-pocket-tts · deepwiki.com/kyutai-labs/pocket-tts