fix(kokoro): voice loading, Synth method selection, padding fixes#943
fix(kokoro): voice loading, Synth method selection, padding fixes#943yocontra wants to merge 2 commits intosoftware-mansion:mainfrom
Conversation
|
Thanks for the contribution, @yocontra! We'll be taking a closer look at this soon. In the meantime, if you're open to sharing your experience using this or any other APIs in the library, we'd love to hear your feedback! |
|
Right now I'm trying to get TTS working well - I was using an onnx based approach and got the quality pretty high (after writing my own phonemizer, because nobody else had theirs quite right). Switched to this library as I need multiple types of things now, and been fixing issues to get the quality to where that other library was. I'm also sending some PRs to the phonemizer this library uses as there are some words it pronounces wrong and that should get it to perfect again :) |
|
I'm very eager for #936 as I'm trying to get a fully local replacement for gemini live/chatgpt realtime by doing Speech to Text -> Llama 3.2 -> Kokoro TTS |
Voice loading: - Read all rows from voice file instead of truncating to kMaxInputTokens. Voice files (hexgrad/Kokoro-82M) have 510 rows; upstream discards 382. - Changed voice_ from fixed std::array to std::vector, sized from file. - voiceID: three-way min(phonemes-1, dpTokens-1, voice_.size()-1) to prevent OOB. Upstream had a latent OOB with voiceID=noTokens on a 128-element array. Synthesizer method selection: - Discover forward_N methods at construction, same pattern DurationPredictor already uses. Falls back to "forward" for older/single-method models. - Uses execute() instead of forward() for named method dispatch. Padding fixes: - Pad indices to inputDurationLimit before Synthesizer to prevent XNNPACK shape mismatch on repeated calls with varying duration predictions. - When DP and Synth use same token count (common case), pass DP tensor directly to Synth instead of copying (~320KB save). Perf: - Use resize() for silence padding instead of allocating temp vectors. - Move-capture audio in streaming callback instead of copying.
241cae9 to
b5ed922
Compare
The Synthesizer's attention drifts on longer sequences (60+ tokens), causing later phonemes to be spoken progressively faster. Cap inputTokensLimit to 60 so the Partitioner splits text into shorter chunks that stay faithful to the Duration Predictor's timing. Also switch tokenize()'s std::partition to std::stable_partition so phoneme token order is preserved when invalid tokens are filtered out.
Fixes for Kokoro TTS native code. Addresses voice data truncation, missing Synthesizer method selection, XNNPACK shape mismatches on repeated inference, progressive speed-up on longer inputs, and phoneme token reordering.
Voice loading reads only 128 of 510 rows
voice_was a fixedstd::array<..., kMaxInputTokens>(128 elements), buthexgrad/Kokoro-82Mvoice files contain 510 rows. The remaining 382 rows were silently dropped.Changed
voice_tostd::vector, sized dynamically from the file. Also fixed an OOB invoiceID— upstream usedstd::min(phonemes.size() - 1, noTokens)wherenoTokenscould equal 128, indexing past the end of a 128-element array. Now uses a three-waystd::min({phonemes.size() - 1, dpTokens - 1, voice_.size() - 1}).Synthesizer doesn't do method selection
DurationPredictordiscovers and selects fromforward_8/forward_32/forward_64/forward_128based on input size, butSynthesizeronly knew aboutforward. Added the same discovery and selection logic. Falls back to"forward"if noforward_Nmethods exist, so older models still work.indicestensor changes size between callsDurationPredictor::generate()returns anindicesvector whose size depends on predicted durations — different per input. XNNPACK caches the execution plan from the first call and errors on shape mismatches.Fixed by padding
indicestocontext_.inputDurationLimitbefore passing to the Synthesizer.Audio progressively speeds up on longer inputs
The Synthesizer's attention mechanism drifts on longer input sequences (60+ tokens), causing later phonemes to be spoken progressively faster than the Duration Predictor intended. The DP's timing predictions are correct, but the Synthesizer compresses later phonemes into fewer samples.
Fixed by capping
inputTokensLimitto 60, which forces the Partitioner to split text into shorter chunks that the Synthesizer can render faithfully. Each chunk is roughly one sentence (~15-20 words).tokenize()scrambles phoneme order on invalid tokensstd::partitionwas used to filter out invalid (unrecognized) phoneme tokens, butpartitiondoes not preserve relative order. When any phonemes fall outside the vocabulary, the remaining valid tokens could be reordered, producing garbled audio.Changed to
std::stable_partitionwhich preserves relative order.Misc perf
durPaddedheap alloc (up to 320KB) when DP and Synth use the same token count, which is the common caseresize()directly on the outputChanges
Kokoro.h—voice_from fixed array to vectorKokoro.cpp—loadVoice(),synthesize(),generate(),stream(), constructor token limit capDurationPredictor.h—getMethodTokenCount()Synthesizer.h—forwardMethods_member,getMethodTokenCount()Synthesizer.cpp— method discovery and selectionUtils.cpp—stable_partitionintokenize()