kazeia/TTS_REPORT.md

178 lines
8.3 KiB
Markdown

# Rapport complet TTS Qwen3-TTS — Kazeia
## 2026-04-01
---
## 1. Modele choisi
**Qwen3-TTS-12Hz-0.6B-Base** (Alibaba/Qwen)
- 0.6B parametres, 12 Hz codec (12 tokens/seconde audio)
- 16 codebooks par frame audio (hierarchiques)
- Architecture : Talker (28 layers, 1024 dim) + Code Predictor (5 layers, 1024 dim) + Speech Decoder (conv)
- Voice cloning obligatoire via x-vector (le modele Base n'a aucune voix built-in)
---
## 2. Architecture du pipeline
```
Texte → Tokenizer → [Prefill] → Talker → CB0 token
Code Predictor → CB1-CB15
16 codebooks → VQ Decode → Audio PCM
```
### Etapes detaillees :
1. **Prefill** (10 tokens) : `<|im_start|>assistant\n` + 4 tokens controle (think, think_bos, lang_fr, think_eos) + speaker embedding + bos + premier token texte
2. **Generation interleaved** (boucle autoregressive) :
- Talker forward → logits CB0 + hidden state
- Code Predictor (hidden, CB0_emb) → CB1-CB15 autoregressivement (15 steps)
- Somme 16 embeddings codebooks + texte trailing → input suivant du talker
- Sampling (temp=0.9, top_k=50, repetition_penalty=1.05)
- Arret sur EOS (token 2150)
3. **Decodage** : VQ lookup → pre_conv → preprocessor → conv_decoder → audio 24kHz
---
## 3. Reussites
### 3.1 Pipeline fonctionnel complet
- Audio de bonne qualite, voix clonee reconnaissable
- EOS naturel (le modele s'arrete seul)
- Fonctionne pour des phrases de longueur variable
- **RTF 7.1** (28s pour 4s audio)
### 3.2 Export ONNX valide
- **Talker KV-cache** : 1.77 GB, 28 layers, shapes fixes (KV=199), valide identique a PyTorch
- **CP fullseq** : 420 MB, 5 layers, shapes dynamiques, causal mask, 15/15 match vs PyTorch
- **CP KV-cache** : 420 MB, shapes fixes (KV=16), valide 15/15 match vs PyTorch
- **Decoder** (pre_conv + preprocessor + conv_decoder) : fonctionne sur NPU via ONNX Runtime QNN EP
### 3.3 Decodeur sur NPU
- pre_conv, preprocessor, conv_decoder : tous sur QNN NPU
- ~3s pour decoder un chunk de 60 tokens
- Pas de degradation de qualite
### 3.4 Bug critique trouve et corrige : tts_pad
- **Decouverte** : apres epuisement des tokens texte, le modele Python ajoute `tts_pad_embed` (pas des zeros)
- **Impact** : sans tts_pad, le modele ne converge JAMAIS vers EOS (100+ tokens sans arret)
- **Correction** : une ligne changee dans la boucle de generation
- C'etait LE bug qui empechait le pipeline de fonctionner correctement
### 3.5 Bug CP corrige : QK normalization
- Le CP utilise RMSNorm sur Q et K avant le rotary embedding (`q_norm`, `k_norm`)
- Notre premiere implementation manuelle de l'attention les oubliait → divergence totale au step 2
- Correction : ajout de `attn.q_norm()` et `attn.k_norm()` dans le wrapper
### 3.6 Export ExecuTorch .pte
- CP KV-cache exporte en .pte avec QNN fp16 backend (SM8750)
- **55ms pour 17 steps NPU** (vs 5.5s CPU) via le runner C++ standalone
- Pipeline d'export : torch.export → to_edge_transform_and_lower_to_qnn → .pte
- Contournement du bug `WrapWithSetGradEnabled` : pre-calcul des rotary cos/sin
### 3.7 JNI ExecuTorch integre dans l'app
- `libexecutorch.so` (49MB) compile pour arm64 avec QNN backend
- Classes Java ExecuTorch compilees dans un JAR local
- Dependances fbjni + soloader resolues
- **CP NPU via JNI : 79ms/step** (vs 353ms CPU = 4.5x plus rapide)
---
## 4. Echecs et limitations
### 4.1 NPU Talker (ONNX Runtime QNN EP)
- **Quantification par defaut (int8/int16)** : le talker diverge apres ~10 steps, produit du bruit
- **Options fp16** (`htp_precision`, `enable_htp_fp16_precision`) : aucun effet observable, probablement ignorees par le HTP backend
- **Resultat** : EOS premature (1.4-2.2s au lieu de 4s) ou degeneration
- **Cause** : la quantification automatique de ONNX Runtime QNN EP est trop agressive pour un modele autoregistratif
### 4.2 NPU CP (ONNX Runtime QNN EP)
- Meme probleme que le talker : les codebooks secondaires sont corrompus
- Cause une pause audible entre les mots (les embeddings de codebooks sont faux)
- Le modele ne converge pas vers EOS (185 tokens sans arret)
### 4.3 NPU CP (ExecuTorch fp16)
- Le runner standalone produit des codes en 55ms → rapide
- Mais les codes sont **completement differents** du CPU (0/15 match)
- L'audio genere est du bruit
- **Cause** : le fp16 change suffisamment les logits pour que l'argmax donne des codebooks differents, et l'autoregression amplifie
### 4.4 CP KV-cache avec buffer fixe
- Approche : KV padding a 16 positions, shift (drop oldest) a chaque step
- Fonctionne sur PC (valide 15/15 vs PyTorch)
- Sur tablette : degeneration au step 53+ (token 1894 x10)
- **Cause** : apres 16 steps, la position 0 (hidden state du talker) est perdue du cache → le modele perd le contexte initial
- **Solution adoptee** : revenir au CP fullseq (re-run la sequence complete a chaque step)
### 4.5 Subprocess NPU via su
- Le NPU (Hexagon DSP) necessite root (`su`) pour l'acces
- `su -c 'command'` ne transmet pas stdin/stdout au process enfant dans Java
- Named pipes (FIFO) causent un deadlock (blocking open bidirectionnel)
- **Solution** : JNI natif (elimine le besoin de subprocess)
### 4.6 Sampling vs Greedy
- **Greedy** : le modele ne genere JAMAIS EOS (gap logit de -19 a -28 entre EOS et top codec)
- **Sampling** : produit des resultats variables, mais converge vers EOS grace a la stochasticite
- La repetition penalty (1.05x par token unique) n'est pas suffisante seule pour pousser vers EOS
- C'est le sampling + tts_pad qui permet l'EOS naturel
---
## 5. Etat actuel du pipeline
| Composant | Backend | Temps/step | Total (~50 tok) | Statut |
|-----------|---------|-----------|---------|--------|
| Talker (28 layers, KV-cache) | CPU fp32 | 130ms | 6.5s | ✅ Fonctionne |
| CP fullseq (5 layers, seq 2→17) | CPU fp32 | 353ms | 17.7s | ✅ Fonctionne |
| Decoder (VQ + conv) | NPU QNN | — | 3.0s | ✅ Fonctionne |
| **Total** | | **483ms** | **~28s** | **RTF 7.1** |
---
## 6. Fichiers cles
### Sur le PC (/opt/Kazeia/)
- `models_qnn/qwen3-tts-onnx/` — tous les ONNX exports
- `models_qnn/cp_kv_fp16.pte` — CP ExecuTorch NPU (pret mais qualite insuffisante)
- `models_qnn/cp_data/` — embeddings + rotary pour le runner
- `executorch/build-android/` — libs compilees + cp_runner
- `executorch/examples/qualcomm/executor_runner/cp_runner.cpp` — source du runner
### Sur la tablette (/data/local/tmp/kazeia/)
- `models/qwen3-tts-npu/` — modeles ONNX + embeddings + codebooks
- `models/cp_kv_fp16.pte` — CP ExecuTorch
- `cp_runner` — binaire runner C++ (ARM64)
- `cp_data/` — donnees statiques pour le runner
### Dans l'app (kazeia-android/)
- `app/src/main/java/com/kazeia/tts/Qwen3TtsEngine.kt` — moteur TTS complet
- `app/src/main/jniLibs/arm64-v8a/libexecutorch.so` — JNI ExecuTorch (49MB)
- `app/src/main/jniLibs/arm64-v8a/libfbjni.so` — dependance JNI
- `app/src/main/jniLibs/arm64-v8a/libqnn_executorch_backend.so` — backend QNN
- `app/libs/executorch.jar` — classes Java ExecuTorch
---
## 7. Pistes d'optimisation (non explorees)
1. **Quantification calibree** (use_16a8w avec donnees de calibration) — pourrait preserver la qualite sur NPU
2. **Export talker en .pte fp32** — ExecuTorch CPU pourrait etre plus rapide que ONNX Runtime
3. **Streaming decode** — decoder le premier chunk pendant la generation du deuxieme
4. **NNAPI EP** — backend GPU Adreno (pas HTP) pour le talker/CP
5. **Modele TTS plus petit** — SpeechT5 ou VITS pour un RTF < 1 au detriment de la qualite
6. **KV-cache CP correct** augmenter CP_KV_LEN a 17 (au lieu de 16) pour ne pas perdre la position 0
---
## 8. Lecons apprises
1. **Les modeles TTS sont beaucoup plus sensibles a la precision que les LLM** le LLM Qwen3 tourne a 90 tok/s en int4 sur NPU, mais le TTS ne supporte meme pas le fp16
2. **L'autoregression amplifie les erreurs** une petite erreur au step N se propage et s'amplifie aux steps N+1, N+2...
3. **Le debug embedding-level est essentiel** sans comparer tensor par tensor avec PyTorch, impossible de trouver les bugs (tts_pad, q_norm/k_norm)
4. **ONNX Runtime QNN EP != ExecuTorch QNN** deux stacks completement differentes avec des comportements de quantification differents
5. **Le `su` Android est un cauchemar pour l'IPC** stdin/stdout ne passent pas, il faut du JNI natif ou des fichiers