104 lines
4.5 KiB
Markdown
104 lines
4.5 KiB
Markdown
# 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<Pair<String, ShortArray>> = 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.
|