Architecture pour exploiter le gain chraac sans casser le talker :
- kazeia_decoder_chraac : binaire static contre libqwen3tts-decoder.a chraac
+ ggml chraac statiques (9.8 MB autonome, pas de dép .so chraac).
Args : <decoder.gguf> <codes.bin> <T> <out.wav>. Lit codes int32 time-major,
transpose codebook-major, forward, écrit WAV.
- tts_engine : si KZTTS_DECODER_SUBPROC=path + KZTTS_DECODER_GGUF=path posés,
fork+execl au lieu de dec.forward intégré. Codes via /data/local/tmp tmpfile,
WAV via rename. Le talker+CP continuent sur ql/ggml (qualcomm) qui marche.
Mesures Pad3 KZTTS_THREADS=6 GGML_NUM_THREADS=8 seed=42 :
Phrase 'Bonjour Kazeia' (33 frames) :
A baseline qualcomm : decoder 3.29s, total 6.61s, RTF 2.40
B chraac subproc : decoder 2.63s, total 6.06s, RTF 2.20
-> decoder -20%, total -8.3%, audio RMS 1743 identique
Phrase 'Bonsoir, comment tu te sens ce soir ?' (41 frames) :
A baseline : decoder 3.95s, total 8.16s, RTF 2.39
B subproc : decoder 3.15s, total 7.38s, RTF 2.16
-> decoder -20%, total -9.6%
Audio cohérent au baseline (range, RMS quasi identiques), imperceptible
à l'oreille (cf cas 'Bonsoir' envoyé).
Le gain chraac historique sur decoder seul est désormais accessible SANS
casser le talker.
Code committé opt-in (KZTTS_DECODER_SUBPROC absent par défaut = baseline intact).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Concept validé : decoder appelé incrementalement sur codes accumulés tous
les K frames. Premier appel donne TTFB rapide.
TtsSynthesizeResult ajoute ttfb_s + n_chunks. tts_engine_synthesize
détecte KZTTS_STREAM_CHUNK > 0 et appelle decoder.forward(codes_so_far)
tous les K codes, accumule la portion nouvelle dans wav_stream.
Mesure Pad3 phrase 'Bonjour Kazeia' (33 frames) :
baseline : TTFB 6.80s, RTF 2.47
KZTTS_STREAM_CHUNK=8 : TTFB 1.90s (-72%), 5 chunks, total RTF 8.67 (×3.5 cost)
KZTTS_STREAM_CHUNK=4 : TTFB 1.13s (-83%), 9 chunks, total RTF 13.82 (×5.6 cost)
Limitations connues du POC :
1. Total time explose : recompute decoder sur N_so_far à chaque chunk =
somme des coûts O(N_chunks × N_avg) au lieu de O(N_total).
2. WAV pas bit-exact vs baseline : decoder.forward(K) ne match pas le
prefix de decoder.forward(N). Probable conv_transpose_1d qui regarde
au-delà du strict causal kernel/stride. À comparer à l'oreille.
POC opt-in (KZTTS_STREAM_CHUNK=0 par défaut = path baseline intact).
Pour rendre utilisable en prod (sessions suivantes) :
- Refactor decoder avec state KV cache (pre_transformer en KV cache style
comme cp_forward_cached_step + BigVGAN avec context buffer ~5 frames input)
- Threading 2-thread : generator (talker+CP) en parallèle de decoder
- Cible : TTFB ~500ms, total time ≈ baseline + overhead minimal
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-analyse globale plateforme : le GPU n'avait JAMAIS été testé. Build
ggml-vulkan pour Android arm64 (NDK r27d + SPIRV-Headers + vulkan.hpp
téléchargés depuis github, libomp.so poussé sur tablette).
Adreno 830 détecté : uma=1 fp16=1 matrix cores=NONE (compute shaders seuls).
3 patterns testés :
(1) Decoder weights Vulkan buft + CPU compute : NEUTRE
RTF 2.45 -> 2.40-2.47 (bruit), WAV bit-exact
Cause : UMA = même DRAM, pas de gain BW
(2) Decoder + sched (GPU compute attendu) : tout CPU
sched route UMA buffer -> CPU (logique is_host)
Cause : sched ne déclenche pas l'offload pour buffers UMA
(3) Talker offload 29/29 layers via llama.cpp : REGRESSION +128%
Prefill 87->775 ms, talker 32->45 ms, decoder 3.3->6.6s
Cause : 0.6B + batch=1 + bus contention GPU/CPU sur DRAM partagée
Verdict cumulé HTP + Vulkan : sur SM8750 ce TTS spécifique, ni HTP ni
GPU n'apportent gain net. NEON CPU optimisé reste la voie unique
réaliste sur ce hardware.
Conso énergie : Vulkan talker = pipeline ×2.3 plus long, conso ~×2.
NEON CPU = meilleure efficience.
Verdict : RESTE SUR NEON CPU pour la prod.
Code committé opt-in (KZTTS_VULKAN_LIB, KZTTS_DECODER_VULKAN,
KZTTS_TALKER_VULKAN), désactivé par défaut. Path principal RTF 2.45 intact.
Détails complets dans dist/PERF_VULKAN_TESTS.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>