Commit Graph

2 Commits

Author SHA1 Message Date
Richard Loyer 4ad3b3716d chantier B STT #6 : 2 bugs critiques fixés grâce à l'A/B in-app du dev
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.
2026-06-01 12:19:33 +02:00
Richard Loyer 4b43d9e3d7 chantier B STT #1 : S1 unification engine (mel partagé + skeleton stt + VAD C++)
S1.1 mel extractor unifié (kazeia_mel.{h,cpp}) :
 - Paramétrable : window (sym/periodic), compression (log/log10), post-norm
   (none/whisper), FFT auto radix-2 (zero-pad N_FFT pas puissance de 2)
 - Configs pré-faites : config_qwen3_tts_speaker() + config_whisper()
 - speaker_encoder.cpp migré dessus : régression damien cos=0.9997 et bit-exact
   (max abs diff = 0) vs avant. E2E clonage in-process intact (RTF inchangé).

S1.2 audit ORT C++ :
 - libonnxruntime.so + headers C++ trouvés dans cache Gradle (AAR Maven
   onnxruntime-android-qnn:1.24.3). API_VERSION=24, QNN EP intégré.
 - headers/ copiés dans dist/include/onnxruntime/ (versionné, ~1 MB)
 - libonnxruntime.so copiée dans dist/lib/ (gitignore, 20 MB, README pour
   reproduire l'extraction).

S1.3 skeleton stt_engine :
 - stt_engine.{h,cpp} : API parallèle à tts_engine.h
   (SttEngineLoadCfg / SttTranscribeCfg / SttTranscribeResult)
 - VAD RMS C++ complet (port de VadStage.kt prod : frame=1600, seuil=150,
   3 frames speech, 8 frames silence)
 - stt_engine_load + stt_engine_transcribe : stubs err=-99 jusqu'au port ORT
   QNN en S2 (encoder NPU + decoder loop KV-cache + tokenizer BPE)
 - Binaire kazeia_stt_test (1.2 MB statique) valide mel Whisper [80,3000] +
   VAD sur audio FR 5s, plage de valeurs cohérente.

Reste S2 : port encoder/decoder NPU via Ort::Session + QNN EP, port tokenizer
BPE byte-level, override transcribe. Quand S2 done : S3 = JNI + façade Kotlin
+ suppression libmel_extractor.so / VadStage.kt / WhisperHybridEngine.kt.
2026-05-31 19:19:28 +02:00