# Patch QNN options pour la prod gelée — sans migration **Action 1 isolée** de la décision migration `libkazeia_stt`. Ce patch ne touche pas le chemin code, juste les options ORT passées à QNN. Réversible en 4 lignes. ## Constat Dans `WhisperHybridEngine.kt:101-102` actuel : ```kotlin val encOpts = OrtSession.SessionOptions() encOpts.addQnn(mapOf("backend_path" to htpPath)) ``` Ces options QNN par défaut donnent : - `htp_performance_mode = balanced` (scheduler équilibré, pas freq max NPU) - pas de `htp_arch` explicite → ORT auto-détecte, fallback générique sur certains paths - pas de `enable_htp_fp16_precision` → conversion implicite parfois sub-optimale - pas de `SetGraphOptimizationLevel(ALL_OPT)` activé explicitement **Coût mesuré sur SD8 Elite (SM8750, Whisper-Small)** : encoder 309 ms au lieu de 62 ms, decoder 48 ms/tok au lieu de 14 ms/tok. ## Patch (4 lignes Kotlin) Dans `WhisperHybridEngine.kt`, remplacer les blocs encOpts/decOpts : ```kotlin val encOpts = OrtSession.SessionOptions() encOpts.addQnn(mapOf( "backend_path" to htpPath, "htp_performance_mode" to "burst", "htp_arch" to "79", "enable_htp_fp16_precision" to "1", )) encOpts.setOptimizationLevel(OrtSession.SessionOptions.OptLevel.ALL_OPT) ``` Idem pour `decOpts`. Et symmetric pour le fallback `encoder_npu` path si présent (ligne 134 environ). ## Gain mesuré attendu Sur le port C++ avec exactement ces options, mesures stables 5 runs warm (audio FR continu, 12 tokens / 1.6 s) : | Étape | Sans options | Avec burst+v79+fp16 | |---|---:|---:| | Encoder | 309 ms | 62 ms | | Decoder/token | 48 ms | 14 ms | | Total (12 tok) | 962 ms | 320 ms | Hypothèse cohérente : sur la prod Kotlin actuelle (820 ms / 22 tok), avec patch : - Encoder 125 → ~62 ms - Decoder 22 × 23 → 22 × ~14 ms = 308 ms - Total ≈ **560 ms (-32 %)** **À valider sur audio Pierre réel.** L'extrapolation est mathématique, pas un bench prod direct. ## Script de bench audio Pierre Pour valider le gain sur les audios prod typiques (phrases courtes thérapeutiques) : ```kotlin // Dans un mode debug de l'app, ou un test Android instrumenté : val engine = WhisperHybridEngine(nativeLibDir) engine.load(modelPath = WHISPER_DIR) // Charge audio Pierre déjà capturé en prod (ShortArray PCM16 16kHz mono) val samples: List> = loadPierrAudioSamples() // 10-20 samples samples.forEach { (name, pcm) -> val t0 = System.currentTimeMillis() val r = engine.transcribe(pcm, "fr") val dt = System.currentTimeMillis() - t0 Log.i("BENCH", "$name : ${pcm.size / 16000.0}s audio, $dt ms total, text='${r.text}'") } ``` Compare le `dt` total avec et sans patch sur **les mêmes audios** : - Si gain ≥ 25 % en moyenne : patch validé, à merger en l'état - Si gain < 25 % : enquêter (probable que l'app n'utilise pas les bonnes libs QNN, vérifier `jniLibs/arm64-v8a/libQnnHtp*.so` et version `qnn-runtime`) ## Pourquoi c'est sans risque 1. **Code path identique** : c'est de la config ORT, pas un changement de modèle ni de pipeline. 2. **Réversible** : 4 lignes à retirer si jamais ça régresse. 3. **Compatibilité** : `htp_arch=79` est explicite mais valide sur SD8 Elite. Sur autre device (Gen3 = V75), il faudrait conditionner sur `Build.SOC_MANUFACTURER`/`SOC_MODEL`. Pour Kazeia mono-tablette (Pad3 = SM8750 = V79), c'est sûr. 4. **Pas de dégel STT** : le contexte QAIRT est inchangé, le modèle est inchangé, la classe Kotlin est inchangée hors les 4 lignes. ## Après ce patch, la décision migration `libkazeia_stt` Le delta restant entre prod patchée et lib unifiée : | | Prod + patch QNN | Lib unifiée | |---|---:|---:| | Total (22 tok / 1.6 s) | ~560 ms | ~450 ms | | Delta | — | **-110 ms** | | RAM | 545 MB | **377 MB (-168 MB)** | | Code | îlot STT (527 l Kotlin + 203 l C++ mel + 197 l VAD) | unifié dans `libkazeia_stt.so` + `SttEngine.kt` | Décision migration **conditionnée à** : 1. Patch QNN posé et mesuré sur audio Pierre ✓ 2. A/B accuracy CER/WER lib unifiée vs prod patchée sur les mêmes audios — **à mesurer in-app** 3. Si #2 passe (CER ≤ +5 % relatif vs prod), la migration vaut le coup pour les -168 MB et la cohérence code 4. Si #2 dégrade, on garde la prod patchée et la lib unifiée reste un POC L'A/B accuracy reste le seul point dur non mesurable en standalone. Le dev offre un patch derrière l'interface existante (`WhisperHybridEngine` → `SttEngine` swap derrière `SttStage`) — c'est la bonne façon de mesurer sans s'engager.