# Guide GPU Adreno pour TTS Qwen3-TTS ## ONNX Runtime QNN GPU Backend — Audio parfait, tokens identiques au CPU --- ## 1. Résumé Le GPU Adreno 830 produit un audio TTS **parfait** via ONNX Runtime QNN avec le backend GPU (`libQnnGpu.so`). Contrairement au NPU (HTP) qui quantifie et détruit la qualité, le GPU fait du **fp32/fp16 natif IEEE-754** sans quantification. **Résultat :** tokens identiques au CPU (1995, 215, 212...), EOS naturel, audio impeccable. **Vitesse :** 124-131ms/step (identique au CPU — pas de gain de vitesse dû à l'overhead de transfert mémoire par token). --- ## 2. Changement de code (1 ligne) ### Dans `Qwen3TtsEngine.kt`, charger le talker avec le GPU backend : ```kotlin // AVANT (CPU) val talkerOpts = OrtSession.SessionOptions() talkerKv = ortEnv!!.createSession(cpuOnnx.absolutePath, talkerOpts) talkerUsesInt64Pos = true // APRÈS (GPU Adreno) val gpuPath = "$nativeLibDir/libQnnGpu.so" val opts = OrtSession.SessionOptions() opts.addQnn(mapOf("backend_path" to gpuPath)) talkerKv = ortEnv!!.createSession(cpuOnnx.absolutePath, opts) talkerUsesInt64Pos = false // CRITIQUE: GPU QNN exige int32, pas int64 ``` ### Point critique : `talkerUsesInt64Pos = false` Le GPU QNN backend n'accepte pas les tenseurs `int64` pour `position_ids`. L'erreur sinon : ``` ORT_INVALID_ARGUMENT: Unexpected input data type. Actual: (tensor(int64)), expected: (tensor(int32)) ``` Le CPU ONNX accepte `int64`, le HTP aussi, mais le GPU non. Il faut envoyer `int32`. ### Code de création du tenseur position (dans `runTalkerStep`) : ```kotlin val posTensor = if (talkerUsesInt64Pos) { OnnxTensor.createTensor(env, LongBuffer.wrap(longArrayOf(pos.toLong())), longArrayOf(1)) } else { OnnxTensor.createTensor(env, IntBuffer.wrap(intArrayOf(pos)), longArrayOf(1)) } ``` --- ## 3. Bibliothèques nécessaires ### Dans `app/src/main/jniLibs/arm64-v8a/` : | Fichier | Source | Taille | |---------|--------|--------| | `libQnnGpu.so` | QNN SDK `lib/aarch64-android/` | 6.1 MB | | `libQnnGpuNetRunExtensions.so` | QNN SDK `lib/aarch64-android/` | ~1 MB | | `libQnnGpuProfilingReader.so` | QNN SDK `lib/aarch64-android/` | ~0.5 MB | ### Commande pour copier depuis le QNN SDK : ```bash QNN_SDK=/opt/Kazeia/qnn_sdk_242/qairt/2.42.0.251225 cp $QNN_SDK/lib/aarch64-android/libQnnGpu*.so \ kazeia-android/app/src/main/jniLibs/arm64-v8a/ ``` ### Dépendances système (déjà présentes sur Android) : - `libEGL.so` — OpenGL ES context - `libGLESv2.so` — OpenGL ES 2.0 - `libOpenCL.so` — déjà dans `/vendor/etc/public.libraries.txt` sur OnePlus Pad 2 --- ## 4. Modèle ONNX **Aucune re-exportation nécessaire.** Le même modèle ONNX CPU fonctionne sur GPU : - `talker_kv_cpu/model.onnx` (1.77 GB) — utilisé tel quel - Le GPU backend d'ONNX Runtime QNN compile le graph ONNX à la volée ### Pas de cache GPU Le context caching (`qnn_context_cache_enable`) ne fonctionne PAS avec le backend GPU d'ONNX Runtime (contrairement au HTP). Chaque session recompile le graph (~2.5s de chargement). --- ## 5. Pourquoi le GPU fonctionne mais pas le NPU | Aspect | NPU (HTP) | GPU (Adreno) | |--------|-----------|--------------| | Précision | INT8/INT16 quantifié | FP16/FP32 natif IEEE-754 | | Quantification | Automatique, destructive | Aucune | | Codebook argmax | Changé par la quantification | Identique au CPU | | Audio TTS | Bruit / silence / inintelligible | **Parfait** | | Vitesse | ~20ms/step (inutilisable) | ~130ms/step | Le TTS sélectionne des codebooks par `argmax` sur 2048 valeurs. La moindre erreur de quantification (NPU) change le codebook sélectionné et cascade dans l'autoregression. Le GPU fait du vrai fp32 → mêmes codebooks → même audio. --- ## 6. Performance | Métrique | CPU fp32 | GPU fp16/fp32 | |----------|---------|---------------| | Chargement | 2.5s | 2.8s (+graph compile) | | Talker/step | 130ms | 124-131ms | | Audio | Parfait | Parfait | | RTF | 7.0 | 7.0 | **Pas de gain de vitesse.** Le GPU est memory-bound pour les petits batch sizes (1 token). L'overhead de transfert CPU→GPU→CPU par step annule le gain de calcul parallèle du GPU. **Utilité :** le GPU libère les cœurs CPU pour d'autres tâches (CP, UI, audio playback). En mode streaming, le talker sur GPU + CP sur CPU fonctionneraient en parallèle. --- ## 7. Dépendances Gradle ```kotlin // Déjà présent pour le reste du pipeline TTS implementation("com.microsoft.onnxruntime:onnxruntime-android-qnn:1.24.3") ``` Pas de dépendance supplémentaire. Le QNN GPU backend est inclus dans l'AAR ONNX Runtime. --- ## 8. Erreurs courantes ### `Unexpected input data type. Actual: (tensor(int64)), expected: (tensor(int32))` → Mettre `talkerUsesInt64Pos = false` pour envoyer `position_ids` en `int32`. ### `Cannot Open QNN library libQnnGpu.so` → Copier `libQnnGpu.so` dans `jniLibs/arm64-v8a/`. ### `Execution failed for method: forward` (ExecuTorch) → Le JNI ExecuTorch ne fonctionne PAS avec le GPU QNN depuis l'app (problème de contexte GPU). Utiliser ONNX Runtime à la place. ### Pas de cache GPU → Normal. Le QNN GPU backend ne supporte pas le context caching. Le graph est recompilé à chaque session (~2.5s). --- ## 9. Architecture finale recommandée ``` LLM Qwen3-0.6B → NPU HTP (93 tok/s, INT4 calibré) — ExecuTorch Whisper STT → NPU HTP (INT8 calibré) — ONNX Runtime QNN TTS Talker → GPU Adreno (fp32 natif) — ONNX Runtime QNN TTS CP → CPU fp32 — ONNX Runtime TTS Decoder → NPU HTP — ONNX Runtime QNN Silero VAD → CPU (1.8 Mo) — ONNX Runtime ``` Chaque composant sur le backend optimal : NPU pour ce qui tolère la quantification, GPU pour le TTS qui exige la précision, CPU pour le reste.