Commit Graph

4 Commits

Author SHA1 Message Date
Richard Loyer 3775bbc37c chantier TTS CosyVoice3 + verdict R&D NPU
dist/cosyvoice/ : engine streaming chunk-causal (RTF 0.88, cache prompt block-causal,
  HiFT F16) + clonage vocal ECAPA + JNI + libs + exemple voix (damien.cvps)
dist/distill/ : pipeline distillation Reflow 2-NFE (train, export ONNX, eval)
dist/jni/ : CosyVoiceTtsEngine.kt + kazeia_cosyvoice_jni + maj engine/tts/speaker_encoder
dist/lib-cpu/ : libs ggml/llama CPU-only (contournement SELinux FastRPC untrusted_app)
dist/outetts/ : runner OuteTTS (écarté au profit de CosyVoice3)

R&D NPU (détail en mémoire) : QNN HTP exécute MAL le flow DiT de façon irréductible
hors-root (CPU-backend fidèle 0.74 vs HTP 0.14, même DLC ; chirurgie ONNX canonicalisée,
opt-level sans effet). Hexagon-MLIR open exécute GPT-2 correctement sur V79 -> QNN
backend coupable, pas le silicium. Ship = CPU RTF 0.88 ; bug-report Qualcomm = vrai levier.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 21:48:30 +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
Richard Loyer 82d206137f chantier B TTS #8 : speaker encoder embedded + clonage in-process
SE.3+SE.4+SE.5 : ECAPA-TDNN ggml C++ bit-exact + intégration TtsEngine.

speaker_encoder.{h,cpp} :
 - API publique : speaker_encoder_load / encode_wav / encode_waveform / free
 - mel C++ (FFT radix-2, Hann, reflect pad, librosa basis) bit-exact Python
 - ECAPA-TDNN forward : conv0 + 3 SE-Res2Net + MFA + ASP + FC -> x_vector[1024]
 - validation : cos=0.9997 vs Python ref (damien_5s.wav)
 - SPK_STANDALONE -> binaire CLI kazeia_speaker_encode

tts_engine :
 - TtsEngineLoadCfg.speaker_encoder_gguf + mel_basis_path (load-time opt-in)
 - TtsSynthesizeCfg.xvector_override (per-call, RAII restore)
 - tts_engine_encode_speaker_wav / _waveform exposés

tts_pipeline : KZTTS_SPK_GGUF + KZTTS_MEL_BASIS + KZTTS_REF_WAV -> clonage
in-process (un seul binaire, plus de subprocess swap manuel).

Pièges trouvés en route (documentés CLAUDE.md project_tts_*) :
 - ggml_conv_1d exige weights F16 -> cast au call site
 - input layout : data[c*T+t] (ggml ne[0]=T fastest) pas memcpy [T,C]
 - refs Python en bytes [C,T] = ggml [T,C] côté cpp
 - RIFF reader chunk-parsing (ffmpeg = LIST/JUNK avant data)

Test E2E : 4 voix (damien/amir/elodie/zelda) clonées à partir de WAV ref 5s,
toutes audibles et distinctes.

Reste : JNI nativeSynthesizeWithReference (refactor mécanique, prochaine session).
2026-05-31 15:54:29 +02:00
Richard Loyer e42e8f8464 SE.1+SE.2+SE.3 partiel : speaker encoder pipeline POC bout-en-bout
Chantier Option C : embarquer le speaker encoder Qwen3-TTS dans Kazeia-Engine
pour permettre le clonage vocal depuis n'importe quel WAV (5-10s) en
tout-tablette sans étape Python offline.

SE.1 Audit : speaker_encoder_weights.pt = ECAPA-TDNN classique
  - 76 tenseurs, 8.85M params (33.8 MB f32 -> 17 MB f16)
  - blocks.0 Conv1d(128->512 k=5) -> 3x SE-Res2Net (tdnn1+res2net+tdnn2+se)
    -> MFA concat(1536) -> ASP(stats pooling) -> FC(3072->1024)
  - Toutes ops supportées par ggml-cpu (Conv1d via im2col+matmul)

SE.2 Convert : dist/scripts/convert_speaker_encoder_to_gguf.py
  produit speaker_encoder.gguf (17 MB f16) + metadata mel config :
    n_mels=128, sr=24000, n_fft=1024, hop=256, win=1024, fmin=0, fmax=12000
  (bit-exact qwen_tts.core.models.modeling_qwen3_tts.mel_spectrogram)

SE.3 partiel : dist/jni/speaker_encoder.cpp + build_speaker_encoder.sh
  POC bout-en-bout sur tablette :
    WAV damien_5s (24kHz mono, 120k samples = 5s)
      -> mel 128 x 468 frames (FFT radix-2 + Hann + reflect pad + librosa basis)
      -> GGUF loaded (76 tensors)
      -> [TODO session suivante : forward ECAPA-TDNN complet, ~300-400 lignes]
      -> stub x_vector[1024] zeros écrit pour valider l'I/O

  Binaire : 53 KB statique, dépend juste de libggml dynamic.

  mel_basis librosa pré-calculé (24kHz/128 mel/0-12000Hz) dumpé en
  dist/models/mel_basis_qwen3tts.bin (262 KB f32 [128,513]) pour
  reproduire à l'identique torch.stft + librosa filter_bank.

À faire next session :
  - Valider mel C++ bit-exact vs torch.stft Python (sanity)
  - Implémenter forward ECAPA-TDNN : ggml_conv_1d, SE block (squeeze/excite),
    ASP (Attentive Statistics Pooling = stats globaux + tdnn attention),
    FC final.
  - Bit-match damien_xvector.bin référence Python.
  - JNI bridge + Kotlin cloneVoice(refWavPath): FloatArray

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 11:37:14 +02:00