Commit Graph

4 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 c9e198332d chantier B STT #4 : audit dev pris en compte — options QNN explicites + bench apples-to-apples
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.
2026-06-01 09:08:22 +02:00
Richard Loyer 970f731e01 chantier B STT #3 : decoder zero-copy + sanity vocab + checklist dev
Optim decoder identifiée après bench sur audio FR continu (39 tokens) :
le `std::vector<>::emplace_back(data)` à chaque step recopiait 12 MB de
self KV inutilement = 2.4 GB de bande passante mémoire sur le loop entier.

Refactor zero-copy :
 - Pré-allocation UNE FOIS des buffers persistants (self_k/v, mask, input_ids,
   position_ids) au début de transcribe()
 - Pré-création UNE FOIS des Ort::Value pointant sur ces buffers (les Ort::Value
   sont des "borrowing views" — ORT lit les buffers au Run())
 - Cache des indices k_self_out / v_self_out / logits_out (parsing 1 fois,
   pas N fois dans le loop)
 - Update self KV : memcpy direct depuis dec_outputs vers nos buffers persistants
   (qui sont déjà les inputs du step suivant)
 - mask_buf updaté à 1 fp16 par step (juste le slot R-to-L), pas 200

Bench zero-copy sur Snapdragon 8 Elite :
 - Decoder/token : 98 ms -> 36 ms (-63%)
 - Decoder total 39 tokens : 3831 ms -> 1390 ms (-64%)
 - RTF audio 10s FR continu : 0.42 -> 0.18 (mieux que prod 0.51)
 - RAM peak : 378 MB inchangée (zero-copy n'augmente pas la peak)

Sanity vérifié au runtime :
 - Logits decoder shape = 51865 elements (= constante VOCAB_SIZE OK,
   pas de buffer over-read sur argmax). Le vocab.json contient 50258 tokens
   texte, les 1607 manquants sont les tokens langue/timestamp non décodés.

Doc STT_INTEGRATION.md :
 - Chiffres perf à jour (RTF 0.18, decoder 36 ms/token)
 - Nouvelle section "Checklist d'intégration" (6 points à vérifier en premier
   côté app : System.loadLibrary, transcribe vs WhisperHybridEngine, null/edge,
   VAD drop-in, threading IO, cycle de vie release)
 - Roadmap optim future via Ort::IoBinding (gain estimé 30% sur decoder restant)
2026-05-31 22:26:40 +02:00
Richard Loyer 8f6253204c chantier B STT #2 : S2 + S3 livrés (stt_engine ORT QNN + JNI + façade Kotlin + doc)
S2 port complet WhisperHybridEngine.kt -> C++ :
 - stt_engine.cpp impl complète (ORT 1.24.3 + QNN EP HTP V79, decoder KV-cache
   autoregressif 200 steps, override translate->transcribe, parsers JSON inline
   pour mel_filters et vocab.json, tokenizer BPE byte-level GPT-2/Whisper)
 - kazeia_stt_cli : binaire test (1.3 MB statique)
 - Bench bit-correct vs prod : transcription FR identique sur 3 runs déterministes
   ("Elle mat est un casque de chantier connecté et modulable.")
 - Perf SM8750 : RTF 0.41-0.43 (vs prod 0.51), RAM peak 378 MB (vs 545 MB)
 - Mel C++ 60 ms (FFT) vs prod 189 ms (DFT). Decoder per-token 100 ms vs 23 ms
   prod (overhead memcpy KV; optim IO bindings ORT non-bloquant)

S3 JNI + façade + doc :
 - kazeia_stt_jni.cpp : 7 fonctions JNI exportées (nativeLoad/Transcribe/Free
   + nativeVadNew/Push/Reset/Free). Sortie pipe-séparée parseable côté Kotlin.
 - libkazeia_stt.so : 168 KB SHARED, prête pour jniLibs/arm64-v8a/
 - SttEngine.kt : façade Kotlin minimale (~100 l) parallèle à TtsEngine.kt
   (SttJni / SttEngine / SttVad / SttResult)
 - Suppression côté app : WhisperHybridEngine.kt (527L), MelExtractor.kt,
   VadStage.kt, mel_extractor.cpp, libmel_extractor.so (documentée)

Doc livrable dev :
 - STT_INTEGRATION.md (10 KB) : guide complet migration + perf + erreurs +
   bench rapide + roadmap optim
 - KAZEIA_ENGINE_OVERVIEW.md : vue globale moteur unifié (LLM + TTS + STT),
   architecture des libs, API Kotlin unifiée, empreinte cascade complète
 - CLAUDE.md : section "STT unifié engine (31/05)" + verdict A invalidé

Runtime ORT 1.24.3 + QNN libs : extraites cache Gradle Maven (cf README_ORT.md
+ README_QNN.md dans dist/lib/). Pas de tarball ext. Modèles whisper-small-sm8750
prod inchangés (545 MB QAIRT contexte conservé).
2026-05-31 22:15:58 +02:00