Files
mission-control-v2/docs/LUCY_TTS_PLAN.md

7.7 KiB
Raw Permalink Blame History

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.70.85).
  • Akzeptanz: gleiche/bessere Natürlichkeit bei messbar weniger Regenerationen (= weniger Latenz).

Gate A: A1A3 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.0s 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