diff --git a/dist/PERF_ANALYSIS.md b/dist/PERF_ANALYSIS.md new file mode 100644 index 0000000..696f2e4 --- /dev/null +++ b/dist/PERF_ANALYSIS.md @@ -0,0 +1,172 @@ +# 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.