8.5 KiB
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_oppour 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 :
- B. CP ctx réutilisé (½ session, RTF 3.07 → 2.4, bit-exact, faible risque).
- A. Streaming pipeline (1 session, RTF perçu réduit drastiquement, bit-exact).
- F. CP forward sur HMX (1 session, RTF → ~1.5, faible risque, réutilise du travail existant).
- 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_PATHou 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.