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:
@@ -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 ~ 6–8 (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")
|
||||
Reference in New Issue
Block a user