# Analyse perf TTS Kazeia — 28/05 soir État après les 4 chantiers de la session (RTF 4.99 → 3.07). Mesures sur Pad3, CPU 4 threads talker / 8 threads decoder, KZTTS_CP_CACHE=1, phrase « Bonjour je m'appelle Kazeia » → 31 frames audio (2.58s). ## 1. Breakdown précis (mesuré) | Étape | Temps | %total | Par frame | RTF partiel | |---|---:|---:|---:|---:| | Prefill talker | 0.10 s | 1.3% | 3 ms | 0.04 | | **Talker loop** (decode 31×1 token) | 1.00 s | 12.6% | 33 ms | 0.39 | | **CP loop** (31× (1 prefill + 14 decode)) | 3.40 s | 43.0% | 110 ms | 1.32 | | **Decoder** (5 stages) | 3.40 s | 43.0% | — | 1.32 | | TOTAL | 7.90 s | 100% | | **3.06** | **Decoder breakdown** (mesuré via `KZTTS_DECODER_PROFILE=1`) : | Stage | Temps | % decoder | |---|---:|---:| | 1. Quantizer (16 embeddings lookup) | 18 ms | 0.5% | | 2. Pre-conv (k=7) | 16 ms | 0.5% | | 3. Pre-transformer (8 layers attn) | 30 ms | 0.9% | | 4. Upsample (2 stages ×4) | 95 ms | 2.8% | | **5. BigVGAN** (4 blocks + final, total ×480) | **3 240 ms** | **95.3%** | **Conclusion immédiate** : 86% du pipeline est dans deux blocs (CP loop + BigVGAN). Toute perf doit attaquer un de ces deux. Le reste est négligeable. ## 2. Pourquoi c'est lent (diagnostic) ### CP : 110 ms/frame, **BW-bound** sur ggml-cpu 15 sub-forwards par frame (1 prefill 2 tokens + 14 decode 1 token via KV cache). Chaque sub-forward stream ~25 MB de poids f16 par layer × 5 layers = 125 MB. À 77 GB/s LPDDR5x → 1.6 ms BW pur par sub-forward. **Mesuré 7 ms/sub-forward**, soit ~25% compute et ~75% overhead (ggml_init/free, graph build, alloc work buffers). Confirmation BW-bound : thread sweep 4 vs 6 vs 8 → CP plat à ±5%. Le compute ne fait pas la différence, c'est la mémoire qui plafonne. ### BigVGAN : 3.2 s, **compute-bound** sur ggml-cpu NEON Estimation analytique : ~500 Gops sur le forward complet (conv1d k=7 dilatés + convtranspose1d avec stride up_rates=[8,5,4,3]). Sur 8 threads NEON 4-wide ~150 GFlops total → ~3.3 s. **Cohérent avec la mesure (3.2 s)** → on est à ~95% du plafond NEON. Pas de gain à attendre du multi-thread (déjà saturé). Hot inner ops : `ggml_conv_1d` et `ggml_conv_transpose_1d` qui appellent mul_mat NEON après im2col. Pas de path HVX/HMX dans ggml-cpu. ### Talker : 33 ms/frame, presque OK llama.cpp standard decode 1 token. Pas un goulot. La piste HTP prefill talker existe (`use_htp=true`) mais elle ne gagne que sur le prefill (0.1 s) — la boucle decode reste CPU. Gain potentiel : <0.05 s. **Pas un levier.** ## 3. Plafonds théoriques par hardware | Composant | Sur ce qu'on a | Plafond CPU NEON | Plafond HVX V79 | Plafond HMX V79 | |---|---:|---:|---:|---:| | CP loop | 3.4 s | ~3.0 s (BW-saturée) | ~1.5 s (×2.2) | ~0.6 s (×5.7) | | BigVGAN | 3.2 s | ~3.0 s | ~1.7 s (×1.9) | **~0.3 s (×10)** | **HMX (matmul fp16 tile 32×32) gagne énormément sur BigVGAN** parce que les conv1d deviennent des im2col + matmul fp16, exactement ce que les tiles HMX font à 1 TFlops crête par contexte. 4 contextes HMX V79 = ~5 TFlops crête, ~1.5 TFlops réel. ## 4. Leviers triés par ROI (gain × probabilité / effort) ### A. **Streaming pipeline** — gain TTFB, RTF inchangé - Refactorer pour qu'audio commence à sortir dès que les premières frames du talker sont disponibles, plutôt que d'attendre la fin de la phrase. - Le decoder peut traiter par chunks (causal-ish), mais BigVGAN a un kernel k=7 → dépendance gauche sur ~3 frames. - Pipeline talker→CP→decoder en parallèle : temps total ≈ max des trois + overhead de chunking → **4-5 s au lieu de 7.9 s pour la même phrase, et TTFB ~500 ms** au lieu de ~5 s. - **Effort** : 1 session de refactor. - **Risque** : faible. Bit-exact possible. - **Gain RTF perçu** : massif (du « 5× plus lent que temps réel » au « le son commence dans une demi-seconde et continue »). ### B. **CP : ctx ggml réutilisé entre sub-forwards** — réduit overhead - Actuellement 15× ggml_init/free + 15× graph build par frame. - Réutiliser un seul ggml_context + ggml_gallocr persistant → graph build une fois, re-compute 15 fois. - Si overhead = 5 ms/sub-forward × 15 = 75 ms/frame, on espère gagner 50 ms/frame. - **Effort** : ~½ session (~80 lignes de refactor dans cp_forward_cached_step). - **Risque** : faible. Bit-exact attendu (mêmes ops, juste alloc différente). - **Gain mesuré** : CP 110 ms → ~60 ms espérés = **RTF total ~2.4** au lieu de 3.07. ### C. **Decoder : ctx ggml réutilisé + buffer mem 12GB→raisonnable** - BigVGAN alloue 12 GB par appel. Probablement minor mais à vérifier. - Réutilisation entre frames si on chunke (couplé à A). - **Gain estimé** : 5-15% sur decoder, soit 0.2-0.5 s. - **Effort** : ½ session. ### D. **BigVGAN sur backend Vulkan (Adreno)** — gain raisonnable - Adreno 750 + ggml-vulkan : conv1d devient compute shader. - Probable gain ×3-4 sur BigVGAN si bien câblé (mais ggml-vulkan + conv1d a des trous). - **Effort** : ~2 sessions (build vulkan, test, fix les ops manquantes). - **Risque** : moyen (état ggml-vulkan + conv1d mal connu). - **Gain** : BigVGAN 3.2 s → 1 s = RTF total **~2.0**. ### E. **BigVGAN sur HMX V79** — le gros gain frontière - Porter conv1d + convtranspose1d en kernels HMX via im2col + matmul fp16 tile. - Existant : ggml-hexagon backend a déjà MUL_MAT HMX (utilisé pour Qwen3.5 prefill). Manque : `supported_op` pour CONV_1D doit retourner true + le kernel im2col HVX. - **Effort** : 1-2 semaines. Probablement bit-exact perdu (fp16 tile) mais audio est tolérant. - **Gain** : BigVGAN 3.2 s → 0.3-0.5 s = RTF total **~1.0**. - **Risque** : moyen-élevé (kernel HVX bit-exact n'est pas demandé pour l'audio, mais artefacts possibles si quant mal calibrée). ### F. **CP sur HMX V79** — symétrique - Le CP est un transformer dense 5L 1024-dim, exactement le pattern HMX. - Port le forward CP sur HTP. ggml-hexagon supporte déjà les matmul HMX pour Qwen3.5. - **Effort** : 1 session (le forward CP est court, ~200 lignes). - **Gain** : CP 3.4 s → 0.5-1 s = RTF total **~1.5**. - **Risque** : faible (path déjà battu côté Qwen3.5). - **Caveat** : les heads CP (15 × 2048 × 1024 = 30M params) restent CPU côté dot-product. Coûtent ~30 M ops × 15 steps = 0.5 G ops/frame, ~5 ms/frame. OK. ### G. **B + F combinés** — chemin « ship-ready avec RTF<1.5 » - CP overhead réduit (B) + CP sur HMX (F) → CP 3.4 s → ~0.3 s. - Decoder reste CPU 3.2 s, talker 1 s. - Total ≈ 4.5 s, RTF **1.7**. Pas <1 mais cap à dépasser. ### H. **B + E + F combinés** — RTF <1 ambitieux - CP overhead + HMX + BigVGAN HMX → CP 0.3 s + BigVGAN 0.4 s + talker 1 s + prefill 0.1 s = 1.8 s. - **RTF 0.7**. Cible atteinte. - **Effort total** : 3-4 semaines. ## 5. Recommandation Plan ordonné par ROI décroissant : 1. **B. CP ctx réutilisé** (½ session, RTF 3.07 → 2.4, bit-exact, faible risque). 2. **A. Streaming pipeline** (1 session, RTF perçu réduit drastiquement, bit-exact). 3. **F. CP forward sur HMX** (1 session, RTF → ~1.5, faible risque, réutilise du travail existant). 4. **E. BigVGAN HMX** (2-3 semaines, RTF → ~0.7, risque qualité audio à valider). Les étapes 1 et 2 sont des gains certains, courts, sans tester de nouveaux kernels. Elles font la moitié du chemin. L'étape 3 est le prochain saut « propre », l'étape 4 est le sujet R&D qui fait franchir <1. **Points de bascule** : si l'étape 1+2 donne RTF 2 avec streaming TTFB ~500 ms, ça peut suffire pour shipper. RTF<1 n'est pas indispensable si l'audio commence à jouer rapidement et continue plus vite qu'il ne se consomme **après** le démarrage (= la queue se vide en cours de route). ## 6. Choses à vérifier / chantiers parallèles - **HTP prefill talker** : n'a pas marché lors du test (hang 90s). Setup `ADSP_LIBRARY_PATH` ou env supplémentaire requis. Pas urgent — gain mineur (~0.05 s). - **Quant heads/codec_embs CP** (Q8/Q4) : 240 MB de f32 dans s.heads+s.codec_embs. Pourrait diminuer BW si les heads (CPU dot-product, pas ggml) deviennent bw-bound. Mesuré insignifiant (5 ms/frame) → pas un levier. - **Decoder en f16** : à vérifier si poids déjà f16 ou f32. Si f32, bascule en f16 peut diviser BW par 2 si BW-bound. Mais BigVGAN est compute-bound (3.2 s ≈ plafond NEON), donc gain attendu faible. ## Verdict RTF<1 atteignable mais demande ~3-4 semaines de travail kernel hexagon (étape E ou F+E). RTF 2 avec TTFB ~500 ms est atteignable en ~2 sessions de refactor (étapes B+A). Cela suffit pour démo et probablement app, parce que ce qui compte côté UX c'est le démarrage rapide + le rythme soutenu, pas la latence end-to-end pure.