L'A/B in-app du dev a révélé un bug grave que mon harness standalone ratait :
3/6 audios FR (elodie, richard, zelda) sortaient vides (0 tokens, EOT immédiat).
Investigation : reproduit en standalone (chaque audio dans son propre process)
- DONC PAS un bug d'enchaînement KV (mon code reset déjà self_k/v à chaque
transcribe). Le dev avait une fausse piste qui m'a poussé à investiguer
en profondeur côté algo decoder, ce qui a révélé les vraies causes.
Bug #1 : FFT zero-pad 512 vs DFT exact 400
- kazeia_mel utilisait next_pow2(N_FFT=400)=512 + FFT radix-2 zero-padded
- Conséquence : bins 0..200 du FFT 512 sont à fs/512=31.25 Hz/bin, alors
que mel_filters.json est calibré pour fs/400=40 Hz/bin du DFT exact
- Spectre désaligné en fréquences -> mel cohérent sur voix graves (damien,
jerome, sid) mais cassé sur voix avec hautes harmoniques (elodie,
richard, zelda)
- Fix : DFT directe N=n_fft quand n_fft pas puissance de 2, FFT radix-2
quand pow2 (TTS speaker encoder N_FFT=1024 reste rapide)
- Coût : mel 60 ms -> 220-320 ms (incontournable pour N_FFT=400)
- Fixe elodie après ce bug, mais richard/zelda restaient vides
Bug #2 : Pas de forced decoder prompt
- Sur audios borderline (énergie spectrale ambiguë), Whisper saute la
prédiction de langue (top-1 step 0 = 50362 NOTIMESTAMPS direct au lieu
de 50265 FR) et atteint EOT au step 1
- Trace step-0 logits richard : [50362:+25.1] [50265:+24.7] (0.4 diff)
- Solution standard HF whisper : forcer le prompt <SOT, |lang|, |task|>
au lieu de laisser le modèle prédire les 3 tokens spéciaux
- IMPORTANT : NE PAS forcer <|notimestamps|> ; le decoder QAIRT pred
timestamp_begin (50363) après <|transcribe|> et veut le mode timestamps.
Forcer NOTIMESTAMPS cause un step 3 = EOT immédiat sur TOUS les audios.
- Fix : forced_prompt = [SOT, lang_tok, TRANSCRIBE_TOK], modèle décide
timestamps vs notimestamps après. lang="auto" garde juste SOT.
Résultat après les 2 fixes : 6/6 audios FR transcrivent en texte cohérent
(damien/elodie/jerome/richard/sid/zelda).
Chiffres honnêtes (warm path, après fixes) :
- Mel : 220-320 ms (vs prod 189) — DFT plus coûteux mais correct
- Encoder : 60-86 ms (vs prod 125) — gain options QNN burst/v79/fp16
- Decoder/token : 12-13 ms (vs prod 23) — gain zero-copy + options QNN
- Total 21 tokens / 3s : ~620 ms (mesuré)
- Total estimé 22 tokens / 1.6s : ~546 ms (vs prod ~820, -33 %)
Les chiffres précédents (-45 %, mel 60 ms) étaient FAUX à cause du bug #1.
Le -33 % réel reste meilleur que prod mais l'écart s'est réduit.
A/B accuracy CER/WER vs WhisperHybridEngine reste à mesurer in-app par
le dev sur SES audios fixtures.
Modifs :
- kazeia_mel.cpp : ajout DFT directe pour n_fft non pow2
- stt_engine.cpp : forced decoder prompt + lang_to_token + flags KZSTT_QNN_*
réglables runtime pour debug
- stt_cli.cpp : multi-audio sequence + KZSTT_DEBUG_STEPS pour trace logits
Crédit : sans le retour précis du dev sur les vides observés, ces bugs
étaient passé en checklist §9 et fait crash en prod.
Critique du dev Kazeia juste : la comparaison RTF 0.18 (10s) vs 0.51 (1.6s) prod
était pommes/oranges (encoder fixe amorti sur audio long), et l'encoder +152 %
non investigué cachait un vrai problème de config QNN par défaut.
Investigation : options QNN ExecutionProvider explicites manquaient
- htp_performance_mode = "burst" (au lieu de default = balanced)
- htp_arch = "79" (au lieu de auto-detect)
- enable_htp_fp16_precision = "1" (NPU fp16 natif)
- SetGraphOptimizationLevel(ORT_ENABLE_ALL) + DisableMemPattern()
Impact mesuré (5 runs warm, audio FR continu, 12 tokens / 1.6s) :
- Encoder : 309 ms -> 62 ms (-80 %)
- Decoder/token : 48 ms -> 14 ms (-71 %)
- Total : 962 ms -> 320 ms (-67 %)
- RAM peak : 377 MB inchangée
Cas réel Kazeia (1.6s, extrapolé 22 tokens) :
- Prod Kotlin : 820 ms (mel 189 + enc 125 + 22*23)
- C++ unifié : ~450 ms (mel 80 + enc 62 + 22*14)
- Gain réel : -45 % sur le workload réel (pas amortissement)
Audio long (15s, 71 tokens) : decoder 10 ms/tok, RTF 0.06.
Mode warm bench (KZSTT_RUNS=N) : exerce N transcribes successifs sur le même
engine pour distinguer cold-start (1er run) vs warm path (suivants).
Recommandation prod Kotlin : ajouter les mêmes options QNN (encOpts.addQnn
multimap) pour gagner sans migration. Documenté dans STT_INTEGRATION.md §7bis.
A/B accuracy : 10 utterances FR transcrites sortent du texte FR cohérent
(avec erreurs typiques Whisper-Small sur mots rares : "Vagons" vs "Wagons",
"zéfière" vs "zéphyr"). La comparaison CER/WER vs WhisperHybridEngine sur
les mêmes fichiers reste à faire côté app (checklist §9.2) — non faisable
standalone sans la prod Kotlin en CLI.
Remerciements : critique du dev a permis de trouver une optim qui sert
TANT la version C++ unifiée QUE la prod Kotlin actuelle.