Skip to content

kokoro_tts: skip phonemes the vocab has no id for, as KModel does - #8

Closed
christopherthompson81 wants to merge 1 commit into
mainfrom
kokoro-drop-unknown-phonemes
Closed

christopherthompson81 wants to merge 1 commit into
mainfrom
kokoro-drop-unknown-phonemes

Conversation

@christopherthompson81

Copy link
Copy Markdown
Owner

Review copy on the fork. Upstream issue: 0xShug0#556.

encode_input_ids_and_count threw on any symbol missing from Kokoro's 114-entry vocab — including symbols the model's own G2P had just produced — which aborted the whole request.

The reference implementation does not. hexgrad/Kokoro's KModel tokenizes with input_ids = list(filter(None, map(vocab.get, phonemes))): it drops phonemes with no token id. Dropping gives a correct reading; throwing gives no audio at all.

Malformed UTF-8 above still throws — that is a real error. An unknown but well-formed phoneme is not. Each skip is recorded as a debug trace (kokoro.skipped_phoneme), so the information is kept without failing the request.

What it costs today, measured

40 real corpus sentences per language through the release kokoro_82m_q8_0, with the throw still in place:

language result symbol
en-us / en-gb 40 ok / 0 threw
es 35 ok / 5 threw « U+00AB
fr-fr 34 ok / 6 threw « U+00AB
it 39 ok / 1 threw ̪ U+032A
hi / pt-br 40 ok / 0 threw

So roughly 12% of Spanish and 15% of French prose failed outright — the vocab carries ( and ) but not the guillemets. English is affected too, just more rarely: eSpeak glottalises /t/ before a syllabic nasal, so button, kitten, written and forgotten produce a U+0329 the vocab lacks (100 of the first 40,000 dictionary words — but one anywhere aborts the request).

Verification

Every previously-failing case renders, with durations in line with words that always worked (button 1.00 s beside hidden 1.07 s), and 114/114 distinct English golden sentences render.

gate result
C# bindings model test ran=4 skipped=1 failures=0
audiocpp_c_api_path / _exports Passed
audiocpp_c_api_model Passed (146 s)
audiocpp_c_api_parity PARITY OK across 5 families
kokoro_tts audio MATCH cli=79800@24000Hz peak=0.3383 api=79800@24000Hz peak=0.3383

79800 frames at 24 kHz is 3.325 s — byte-identical to what the unpatched engine produced for that sentence, and identical between the CLI and the C API. The fix is inert on text that never had an out-of-vocab symbol.

Notes for the reviewer

  • Developed against pin 3eccab5, cherry-picked onto b46fe6b. The touched file and every dependent header are byte-identical between the two bases.
  • The per-language numbers came from restoring the throw in a patched build and running real corpus text — not from diffing espeak-ng --ipa against the vocab. That proxy is misleading in both directions: it predicts 507 hyphen failures in French that never occur (espeak_text() ends with replace(out, "-", "")), and misses the guillemets that actually fail.
  • A separate PR follows for the external-phonemizer option; it wants an ABI addition and is kept apart from this fix deliberately.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EkxqpYvUbjCpRDnFiNiVfx

eSpeak-ng glottalises /t/ before a syllabic nasal, so the built-in G2P produces
a U+0329 syllabic mark for ordinary words -- "button", "kitten", "written",
"forgotten" -- and encode_input_ids_and_count then threw on the very symbol it
had just produced, losing the whole request.

The reference implementation does not: hexgrad/Kokoro's KModel tokenizes with
filter(None, map(vocab.get, phonemes)), dropping any phoneme the 114-entry
vocab has no id for. Dropping the mark gives b'V?n for "button", a correct
reading; throwing gives no audio at all.

Malformed UTF-8 above still throws. An unknown but well-formed phoneme does not.
@christopherthompson81

Copy link
Copy Markdown
Owner Author

Folded into #9 as one PR, per review. Both commits are there unchanged — the bug fix is still the first commit and still reviewable on its own.

The reason the split stopped being worth it: the ABI addition turned out to be completing something already half-designed rather than proposing a new concept. string_list / float_list / path_list / audio_path_list have been in the spec's type vocabulary since schema v1, and the schema validates their declarations — there was simply no transport, and no spec had ever used one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant