136 lines
5.8 KiB
Markdown
136 lines
5.8 KiB
Markdown
# Tests Qwen3-TTS sur OnePlus Pad 3 — Journal
|
|
|
|
**Date** : 29 mars 2026
|
|
|
|
---
|
|
|
|
## Environnement
|
|
|
|
- **Tablette** : OnePlus Pad 3 (Snapdragon 8 Elite, 16 GB RAM)
|
|
- **Runtime** : Termux + Python 3.12 + PyTorch 2.9.0 (Termux native ARM)
|
|
- **Modèle** : Qwen3-TTS-12Hz-0.6B-Base (local, `/data/local/tmp/kazeia/models/qwen3-tts/`)
|
|
- **Dépendances** : transformers 4.57.3, torchaudio 2.9.0, soundfile, einops
|
|
- **Mocks** : librosa (soundfile+scipy), soxr (scipy), sox, onnxruntime
|
|
|
|
## Résultats des tests
|
|
|
|
### Test 1 : float32 complet
|
|
- **Résultat** : OOM (killed) — le modèle 1.7 GB + speech tokenizer 651 MB + overhead dépassent la RAM disponible
|
|
- **RAM utilisée** : >10 GB avant crash
|
|
|
|
### Test 2 : float16
|
|
- **Résultat** : NaN dans le code predictor (`RuntimeError: probability tensor contains either inf, nan or element < 0`)
|
|
- **Cause** : float16 n'a pas assez de précision pour le softmax du code predictor (5 couches)
|
|
|
|
### Test 3 : float16 + code predictor float32
|
|
- **Résultat** : dtype mismatch (`RuntimeError: expected m1 and m2 to have the same dtype, but got: float != c10::Half`)
|
|
- **Cause** : le code predictor en float32 reçoit des tenseurs float16 du talker — les types ne sont pas automatiquement castés dans le forward couplé
|
|
|
|
### Test 4 : bfloat16 ✅
|
|
- **Résultat** : **Fonctionne**
|
|
- **"Bonjour."** : 39.5s pour 1.0s d'audio (RTF 39.5x)
|
|
- **"Bonjour, je suis là pour vous écouter."** : 109.4s pour 2.6s d'audio (RTF 41.5x)
|
|
- **Explication** : bfloat16 a le même range que float32 (8 bits d'exposant) mais moins de mantisse. Le code predictor ne produit plus de NaN.
|
|
- **RAM** : ~3.8 GB (modèle) + ~1-2 GB (inference) = ~5-6 GB total
|
|
|
|
### Test 5 : INT8 dynamic quantization
|
|
- **Résultat** : Échec (`NoQEngine` — le backend quantization n'est pas compilé dans la version Termux de PyTorch)
|
|
|
|
### Test 6 : torch.compile
|
|
- **Résultat** : OOM — l'overhead de compilation consomme trop de RAM
|
|
|
|
### Test 7 : Speaker encoder timing
|
|
- **Sur PC** : 2-10s selon la voix
|
|
- **Sur tablette CPU** : **688s (11 min)** — inutilisable
|
|
- **Solution** : Pré-calculer les embeddings sur PC, les stocker en .npy (4 KB chacun), les charger instantanément
|
|
|
|
## Architecture validée
|
|
|
|
```
|
|
[PC - pré-calcul]
|
|
Voix WAV → Speaker Encoder → embedding .npy (1024 floats, 4 KB)
|
|
|
|
[Tablette - runtime]
|
|
embedding .npy (instantané)
|
|
+ texte
|
|
↓
|
|
Talker LM (28 couches, bfloat16 CPU) → speech tokens
|
|
↓
|
|
Code Predictor (5 couches, bfloat16) → 15 codebooks
|
|
↓
|
|
Speech Decoder (Transformer + VQ + ConvNet) → audio WAV
|
|
```
|
|
|
|
## Performances actuelles (CPU bfloat16, 6 threads)
|
|
|
|
| Phrase | Tokens | Temps | Audio | RTF |
|
|
|--------|--------|-------|-------|-----|
|
|
| "Bonjour." | ~20 | 39.5s | 1.0s | 39.5x |
|
|
| "Bonjour, je suis là..." | ~50 | 109.4s | 2.6s | 41.5x |
|
|
|
|
**Goulot d'étranglement** : Le talker (28 couches transformer autorégressif) représente ~90% du temps.
|
|
|
|
## Estimation avec NPU
|
|
|
|
Le talker .pte a été testé à **90.7 tok/s** sur le NPU Hexagon (rapport précédent).
|
|
Sur CPU bfloat16, le talker fait ~0.5 tok/s (estimé d'après les temps).
|
|
|
|
| Composant | CPU actuel | NPU estimé |
|
|
|-----------|-----------|------------|
|
|
| Talker (50 tokens) | ~100s | **~0.6s** |
|
|
| Code predictor | ~3s | ~3s (CPU) |
|
|
| Speech decoder | ~6s | ~6s (CPU) |
|
|
| **Total** | **~109s** | **~10s** |
|
|
|
|
## Blocages pour l'intégration NPU
|
|
|
|
1. **`qnn_llama_runner` incompatible** : Le runner prend du texte brut et utilise un TEXT tokenizer. Le talker TTS attend des embeddings texte pré-calculés (via `text_projection`) + un speaker embedding. Les entrées/sorties ne correspondent pas.
|
|
|
|
2. **ExecuTorch Python pas dispo sur Termux** : Le package pip `executorch` n'a pas de wheel ARM. La compilation locale nécessiterait le NDK + CMake cross-compilation.
|
|
|
|
3. **Couplage talker ↔ code predictor** : Le code predictor est appelé à CHAQUE step du talker (pas après). Ses sorties (15 codebooks) sont ré-injectées dans le talker comme embeddings pour le step suivant.
|
|
|
|
## Solutions en cours d'exploration
|
|
|
|
### A. Service TTS résident (CPU bfloat16)
|
|
Script Python (`tts_service.py`) qui reste en mémoire avec le modèle chargé. L'app Android écrit une requête JSON, le service génère le WAV.
|
|
- **Avantage** : Fonctionne maintenant (validé)
|
|
- **Inconvénient** : ~40-110s par phrase (inutilisable en production)
|
|
|
|
### B. Compiler ExecuTorch Python pour Termux/ARM
|
|
Cross-compiler le binding Python ExecuTorch pour aarch64-android. Permettrait de charger le `.pte` et faire les forward passes sur NPU directement depuis Python.
|
|
- **Avantage** : Garderait le couplage talker ↔ code predictor
|
|
- **Difficulté** : Compilation cross-platform complexe
|
|
|
|
### C. Runner C++ custom pour le talker TTS
|
|
Modifier `qnn_llama_runner` pour accepter des embeddings pré-calculés au lieu de texte, et sortir des token IDs bruts.
|
|
- **Avantage** : Réutilise l'infra ExecuTorch existante
|
|
- **Difficulté** : Modification C++ du runner
|
|
|
|
### D. Pipeline découplé (talker NPU → code predictor CPU)
|
|
Accepter une qualité légèrement réduite en découplant : le talker NPU génère codebook 0, puis le code predictor génère codebooks 1-14 en un seul pass (pas step-by-step).
|
|
- **Avantage** : Plus simple à implémenter
|
|
- **Inconvénient** : Qualité potentiellement dégradée
|
|
|
|
## Fichiers déployés sur la tablette
|
|
|
|
```
|
|
/data/local/tmp/kazeia/
|
|
├── models/qwen3-tts/
|
|
│ ├── config.json, model.safetensors (1.7 GB)
|
|
│ ├── speech_tokenizer/model.safetensors (651 MB)
|
|
│ ├── tokenizer_config.json, vocab.json, merges.txt
|
|
│ └── voice_embeddings/
|
|
│ ├── damien_spk_embedding.npy (4 KB)
|
|
│ ├── elodie_spk_embedding.npy
|
|
│ └── ... (8 voix)
|
|
├── tts_service.py
|
|
├── tts_test.wav (dernier test)
|
|
└── kazeia-et/
|
|
└── hybrid_llama_qnn.pte (286 MB, talker NPU)
|
|
```
|
|
|
|
---
|
|
|
|
*Journal de tests — Claude Code (Opus 4.6)*
|