Lucy-TTS/F5: Skripte + Batches versionieren, schwere Assets ignoriert

- pocket_server.py (Produktions-TTS mit Stimmen-Waechter), text_norm, Bench-/Diag-Skripte
- lucy-f5: f5_server/f5_test/bench_dml (DirectML-Experiment, Phase C/D offen)
- .gitignore: venvs/Modelle/Audio/Logs der beiden Ordner + box_recon/gemma_swap-Scratch

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-02 10:29:33 +02:00
parent aff0105700
commit 09a1c98514
49 changed files with 8231 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
"""sweep_b2.py — findet den B2-Sweet-Spot auf DIESER Maschine (Ryzen 9700X, 8C/16T).
Variiert WORKERS × THREADS-pro-Worker und misst je Konfig auf einer langen, mehrsätzigen Antwort:
- wall : Gesamt-Wall-Clock (Generierung der ganzen Antwort)
- ttfb : Time-to-first-audio (Reaktionszeit = erster Satz fertig)
- audio : erzeugte Audiolänge (variiert leicht, da temp>0 stochastisch)
- RTF : wall/audio (längen-normalisiert -> fairer Durchsatz-Vergleich; <1 = schneller als Echtzeit)
Nutzt die ECHTEN pocket_server-Funktionen (_worker_init/_warmup/_gen_sentences_ordered).
Schreibt sweep_b2_result.json + eine Tabelle und nennt am Ende den Sweet Spot.
"""
import os, time, json, statistics as st
import numpy as np, librosa
from concurrent.futures import ProcessPoolExecutor
from pocket_tts import TTSModel
import pocket_server as ps
BASE = ps.BASE
LONG = ("Guten Morgen, Commander. "
"Das nächtliche Backup ist sauber durchgelaufen und es gab keine Fehler. "
"Der Dienst läuft stabil und die Engine antwortet zügig. "
"Ich habe die Modelle vorgewärmt und die Latenz im Blick behalten. "
"Die Protokolle zeigen keine Auffälligkeiten in den letzten Stunden. "
"Wenn du möchtest, fasse ich die offenen Punkte für heute zusammen. "
"Danach kümmere ich mich um die anstehenden Updates und melde mich wieder.")
# (workers, threads_pro_worker). Ziel: workers*threads ~ 68 (8 physische Kerne).
CONFIGS = [("seriell", 0, 0), ("pool", 2, 4), ("pool", 2, 3),
("pool", 3, 2), ("pool", 3, 3), ("pool", 4, 2)]
REPS = 3
def _measure(sents):
"""Eine Durchführung: (wall, ttfb, audio_s) über den aktuell in STATE gesetzten Pfad."""
t0 = time.time(); first = None; total = 0
for i, a in enumerate(ps._gen_sentences_ordered(sents)):
if i == 0:
first = time.time() - t0
total += a.size
return time.time() - t0, first, total / 24000.0
def run_serial(reps):
m = TTSModel.load_model(language=ps.LANG, lsd_decode_steps=ps.LSD, temp=ps.TEMP,
noise_clamp=ps.NOISE_CLAMP, quantize=ps.QUANTIZE)
ref = ps._prep_ref()
try:
vs = m.get_state_for_audio_prompt(ps.VOICE_ST)
except Exception:
vs = m.get_state_for_audio_prompt(ref)
ra, _ = librosa.load(ref, sr=m.sample_rate, mono=True)
ps.STATE.clear()
ps.STATE.update(m=m, vs=vs, sr=m.sample_rate, ref_fp=ps._fingerprint(ra, m.sample_rate))
sents = ps._split_sentences(LONG)
rows = [_measure(sents) for _ in range(reps)]
ps.STATE.clear(); del m
return rows, len(sents)
def run_pool(workers, threads, reps):
os.environ["LUCY_WORKER_THREADS"] = str(threads) # vom Worker beim Spawn gelesen
pool = ProcessPoolExecutor(max_workers=workers, initializer=ps._worker_init)
list(pool.map(ps._warmup, range(workers)))
ref = ps._prep_ref(); ra, _ = librosa.load(ref, sr=24000, mono=True)
ps.STATE.clear()
ps.STATE.update(sr=24000, ref_fp=ps._fingerprint(ra, 24000), pool=pool)
sents = ps._split_sentences(LONG)
rows = [_measure(sents) for _ in range(reps)]
pool.shutdown(wait=True); ps.STATE.clear()
return rows, len(sents)
if __name__ == "__main__":
results = []
print(f"Text: {len(LONG)} Zeichen, REPS={REPS}\n")
print(f"{'config':>14} | {'wall':>6} {'ttfb':>6} {'audio':>6} {'RTF':>5}")
print("-" * 48)
for kind, w, t in CONFIGS:
try:
rows, nsent = (run_serial(REPS) if kind == "seriell" else run_pool(w, t, REPS))
except Exception as e:
print(f"{kind} w{w} t{t}: FEHLER {e}")
continue
wall = st.median([r[0] for r in rows])
ttfb = st.median([r[1] for r in rows])
audio = st.median([r[2] for r in rows])
rtf = wall / max(audio, 0.01)
label = "seriell(1×all)" if kind == "seriell" else f"{w}w×{t}t"
print(f"{label:>14} | {wall:6.2f} {ttfb:6.2f} {audio:6.2f} {rtf:5.2f}")
results.append(dict(label=label, kind=kind, workers=(1 if kind == "seriell" else w),
threads=t, wall=wall, ttfb=ttfb, audio=audio, rtf=rtf, sents=nsent))
json.dump(results, open(os.path.join(BASE, "sweep_b2_result.json"), "w"), indent=2)
pool_rows = [r for r in results if r["kind"] == "pool"]
if pool_rows:
best_tput = min(pool_rows, key=lambda r: r["rtf"])
best_ttfb = min(pool_rows, key=lambda r: r["ttfb"])
# Sweet Spot: bester Durchsatz, aber TTFB nicht >20% über dem TTFB-Sieger (Reaktion zählt)
cand = [r for r in pool_rows if r["ttfb"] <= best_ttfb["ttfb"] * 1.20]
sweet = min(cand, key=lambda r: r["rtf"]) if cand else best_tput
print("\n>> bester Durchsatz :", best_tput["label"], f"(RTF {best_tput['rtf']:.2f})")
print(">> beste Reaktion :", best_ttfb["label"], f"(TTFB {best_ttfb['ttfb']:.2f}s)")
print(">> SWEET SPOT :", sweet["label"],
f"-> LUCY_WORKERS={sweet['workers']} LUCY_WORKER_THREADS={sweet['threads']}")
print("SWEEP_DONE")