Commit Graph

6 Commits

Author SHA1 Message Date
Richard Loyer 101d1cd7ed tests infra HTP/HMX : verdict, le HTP n'est pas le bon outil pour TTS
5 tests systématiques sur les hypothèses non-explorées :

A. USE_HMX=0 vs USE_HMX=1 sur sched CP :
   - HVX 20% plus rapide que HMX à batch=1 (tile 32×32 sous-utilisé)
   - Mais aucun ne bat baseline CPU (110 ms/frame)
   - HVX et HMX bit-exact entre eux ; les deux divergent de CPU (codes
     différents -> trajectoire talker diverge -> N=64 vs 33).

B. op_offload=true vs false : false -16% sur CP HMX (placement strict
   moins coûteux). Quick win retenu (env KZTTS_CP_OFFLOAD).

C. Profile fin (KZTTS_CP_PROFILE=1) par sub-step :
   - sched_reset+alloc : 0.1 ms (négligeable !)
   - tensor_set/get : 0.0 ms (opt_hostbuf=1 = ION partagé)
   - compute : 6-11 ms (95 % du temps !)
   -> L'overhead sched n'est PAS le problème. Le compute HTP
      lui-même est plus lent que CPU pour batch=1. Hypothèse #1
      (sched_reserve + ctx persistant) INVALIDÉE.

D. Talker option C (use_htp=true, GGML_HEXAGON_GDN_PREFILL=1) :
   - Prefill : 87 ms -> 185 ms (+112%)
   - Talker per-frame : 32 ms -> 81 ms (+154%)
   -> Pour un petit modèle (0.6B), l'overhead routing HTP par token
      dépasse le gain compute. Le pattern option C documenté
      marche pour Qwen3.5-4B, PAS pour Qwen3-TTS-Talker.

VERDICT GLOBAL : le HTP est non-rentable sur TOUS nos workloads TTS.
- BigVGAN : bloqué (nrows > 1024)
- CP : régresse (batch=1)
- Talker : régresse (trop petit pour amortir routing)

CLAUDE.md le notait partiellement ('CPU NEON bat HVX 3.4× sur M=1') mais
on n'avait pas tiré la conséquence globale : notre TTS n'a pas de
workload HTP-friendly.

LEVIERS RESTANTS (sans HTP) :
1. Streaming pipeline (1 session) : RTF inchangé mais TTFB 5s->500ms
2. Réduction overhead CP CPU (½ session) : ctx réutilisé entre sub-forwards
3. Decoder optim NEON conv1d : R&D long
4. Quantization Q8/Q4 CP : compromis qualité

Code committé reste opt-in (KZTTS_CP_HTP, KZTTS_DECODER_HTP) et neutre
au runtime. Le path par défaut RTF=3.0 est intact.

Détails complets dans dist/PERF_INFRA_TESTS.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 16:40:56 +02:00
Richard Loyer 48c66c03e1 chantier F (CP HMX) : path sched livré, gain absent (overhead per-call)
cp_inference.h/.cpp : nouveau cp_load_with_backends + sched path dans
cp_forward_cached_step. KV cache aussi alloué sur backend buffer en mode sched
(no_alloc=true + ggml_backend_alloc_ctx_tensors_from_buft). Inputs (x, pos_ids,
mask) marqués ggml_set_input ; output ggml_set_output. Compute via sched_reset
+ alloc + tensor_set + sched_compute + tensor_get. Path legacy CPU pur (sans
sched) reste intact via détection s.sched.

tts_engine.cpp : KZTTS_CP_HTP=1 + KZTTS_CP_SCHED=1 active le path sched HMX.
KZTTS_CP_HTP=1 seul = poids HTP, compute CPU pur via opt_hostbuf=1 (baseline).

Mesures Pad3 (KZTTS_CP_CACHE=1, seed=42, 'Bonjour Kazeia') :
  CPU baseline                  : CP 111 ms/frame, RTF 2.95, N=33
  CP poids HTP, compute CPU     : CP 108 ms/frame, RTF 2.93, N=33  (~baseline)
  CP sched HMX (HTP-routé)      : CP 181 ms/frame, RTF 3.87, N=64  REGRESSION

GGML_SCHED_DEBUG=2 confirme que ~95% des MUL_MAT CP tombent bien sur HTP0
(blk.0.attn_q.weight : 360 HTP / 21 CPU). Donc HMX est techniquement actif.

Cause de la régression :
  - Overhead par sub-forward sched_reset + sched_alloc_graph + tensor_set/get
    × 15 sub-forwards par frame. ~5 ms/appel × 15 = 75 ms ajoutés par frame
    (cohérent avec 111 -> 181 ms).
  - N=64 vs N=33 = trajectoire talker diverge à cause des codes CP qui
    diffèrent (perte précision HMX f16 tile vs CPU NEON f32).

Pour livrer un gain CP HMX réel, il faudrait :
  - (a) ctx persistant entre sub-forwards (~refactor architectural)
  - (b) batcher les 15 sub-forwards en 1 graph autoregressif (~lourd)

Le path sched CP est laissé en opt-in (KZTTS_CP_SCHED=1, désactivé par défaut)
pour itérer dessus ultérieurement. KZTTS_CP_HTP=1 seul est neutre (~baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 16:03:41 +02:00
Richard Loyer 1203d7ce5d chantier B TTS #9: CP weights f16 conservés, CP -52% cumulé, bit-exact
cp_load: les tenseurs F16 du gguf restent F16 en mémoire (sauf
*_norm.weight qui restent F32 — ggml_mul exige même type que xn).
ggml_mul_mat gère mul_mat(W_f16, x_f32) -> out_f32 nativement, donc
aucun changement dans cp_forward_cached_step / cp_forward_lastpos.

Mesure tablette Pad3 (KZTTS_CP_CACHE=1, seed=42, 31 frames):
  baseline f32 oracle   : CP 227.9 ms/frame, RTF 4.99
  baseline f32 cached   : CP 137.8 ms/frame, RTF 3.45
  f16 weights oracle    : CP 223.8 ms/frame, RTF 4.58  (peu de gain : oracle compute-heavy)
  f16 weights cached    : CP 108.3 ms/frame, RTF 3.07  (-52% CP cumulé vs baseline)
  codes_ref.bin == codes_cache.bin (cmp) + out_*.wav md5 identiques

Toujours BIT-EXACT contre l'oracle f32 : les sums f16->f32 dans mul_mat
restent suffisamment stables pour que les argmax post-softmax tombent
sur les mêmes indices. Bonus inattendu.

Restants pour <1 :
- Decoder RTF 1.33 dominant (~50% du temps total maintenant)
- Fusion graphes CP multi-step : incertain, ~lourd

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:14:21 +02:00
Richard Loyer 7626131d50 chantier B TTS #8: KV cache CP -40% par frame, bit-exact
cp_inference: nouvelle voie cp_predict_cached (1 prefill 2 tokens +
14 decode 1 token via KV cache f32 persistant) à côté de cp_predict
(oracle recompute conservé pour A/B). cp_forward_cached_step écrit le
K/V post-RoPE dans s.K_cache/V_cache via ggml_view_3d + ggml_cpy, et
lit le full cache [0..T_full) pour l'attention. Ordre topo garanti
par build_forward_expand(cpy) AVANT build_forward_expand(x).

tts_pipeline: switch KZTTS_CP_CACHE=0/1 (défaut 0 = oracle bit-exact).
build_tts_pipeline.sh: rebuild reproductible (CMakeLists ne couvrait
que libkazeia_engine.so).

Mesure tablette Pad3 (talker_f32+tts_dump, CPU 6t, seed=42, 31 frames):
  oracle  : CP 227.9 ms/frame, total 12.90s, RTF 4.99
  cached  : CP 137.8 ms/frame, total  8.91s, RTF 3.45
  codes_ref.bin == codes_cache.bin (cmp), out_ref.wav md5 == out_cache.wav

Speedup CP ×1.65, pas le ×8 espéré : le forward est BW-bound (~100 MB
de poids f32 streamés par sub-forward, on lit toujours 15 fois). Le
gain est sur la part compute (sum L=2..16 -> 16 token-fwd au lieu de
135). Pour aller plus loin : (a) cp_load en f16 conservé (×2 BW),
(b) fusion graphe multi-step. Decoder RTF 1.37 reste aussi à attaquer.

Bit-exactness : f32 mul_mat stable entre L=k+1 et L=1 à threads=6,
les sommations donnent les mêmes bits. Math identique, ordre identique
de fait.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:51:22 +02:00
Richard Loyer d20f2b297b chantier B TTS #7: sampler HF-style top_k+top_p+rep_penalty (générique)
sampler.{h,cpp} : module isolé reproduisant la chaine HF
LogitsProcessor :
  1. RepetitionPenaltyLogitsProcessor (logit/penalty si >0, logit*penalty sinon)
  2. TemperatureLogitsWarper (logit /= temp)
  3. TopKLogitsWarper (garde top_k, autres -> -inf)
  4. TopPLogitsWarper (nucleus : garde la masse cumulee jusqu'a top_p)
  5. multinomial sample (xorshift32, deterministe via sampler_seed)
PRNG xorshift32 != torch.mt19937 donc meme seed != audio Python, mais stable
sur n'importe quel texte/voix (pas d'attracteur greedy).

Plumbing :
- CPState.sampler (defaut greedy top_k=1, temp=0 -> cp_validate continue a sortir
  495/495 codes match). Pipeline override pour temp=0.9 top_k=50 rep_penalty=1.05
  rep_window=16 (intra-frame, 15 codebooks max). CB1..15 utilisent
  sampler_sample_local() qui prend les tokens deja samples CE step pour penaliser
  repetition intra-frame.
- tts_pipeline : Sampler talker_sampler avec rep_window=64 (historique long sur
  CB0). Override via env KZTTS_TEMP/TOPK/TOPP/REPP + KZTTS_CP_TEMP/TOPK/TOPP/REPP.

Validation sur 5 seeds (1, 42, 100, 777, 12345), phrase 'Bonjour je m'appelle Kazeia' :
  - Tous EOS naturel (step 31-44, 2.48-3.67s audio @12Hz, proche du PY_REF 2.08s)
  - silence_pct : 25-36% (vs > 60% avec greedy/sampler simple)
  - cp_validate continue a sortir 495/495 codes match (greedy par defaut)

Vs WAV pipeline_v3 user-confirme 'parfait' (seed=123 temp=0.8 sans rep_penalty),
le sampler v4 est generique sur tout seed/texte, plus de besoin de chercher un
seed favorable. Trade-off : on n'a plus le PRNG torch donc l'audio diffère de
PyTorch a chaque run (different trajectoire), mais reste audio TTS coherent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 16:55:05 +02:00
Richard Loyer 746bbe77cd chantier B TTS #5: P3.3 = vrai CP runner intégré (bit-exact)
cp_inference.h/cpp : extrait de cp_runner.cpp standalone (kazeia-tts-decoder-ggml/),
adapté en module réutilisable. Architecture identique (qwen3 5L, GQA 16/8, head_dim
128, RoPE NEOX theta=1e6, q/k-norm AVANT RoPE, recompute T<=16 sans KV cache).
API : cp_load() + cp_predict(hidden[1024], cb0_emb[1024]) -> int32[15] = CB1..CB15.

cp_validate.cpp : reproduit sur tablette les conditions du standalone cp_runner
-> 495/495 codes match (33/33 frames parfaits) vs cp.generate(do_sample=False)
greedy golden, sur les 33 frames du dump 'Bonjour' historique. CP intégré =
bit-exact au standalone, qui est lui-même bit-exact à PyTorch.

tts_orchestrate.cpp mis à jour : si cp_f16.gguf/cp_heads/cp_codec_embs présents
dans <dump_dir>, le CP est ENABLED et remplace le teacher-forcing CB1..15.
Hidden state Talker capturé via llama_get_embeddings_ith(ctx,-1) après chaque
decode_embeds (pas besoin de re-instrumenter le forward Talker).

Mesure sur 'Bonjour je m'appelle Kazeia' (N=26 frames, audio 2.17s) tablette CPU 4t :
  Talker prefill (T=19) : 0.124 s
  Loop (N=26 steps)     : 7.705 s   talker_decode=0.763, cp=6.942
    per-step talker     : 29.3 ms
    per-step CP (15 pass): 267.0 ms
  Total Talker+CP       : 7.829 s -> RTF talker+cp = 3.61

Le CP est le nouveau goulot (267ms × 15 passes/step). Levier P3.5 = KV cache CP
(recompute -> incremental), threads 8, ou Q8_0 du CP.

CB0 match golden = 3/26 = trajectoire stochastique Python diverge rapidement vs
notre greedy ; PAS un bug CP (cp_validate prouve la conformité bit-exact).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:11:07 +02:00