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 |