Kokoro-82M β ONNX export for Vernacula
Re-packaged ONNX export of hexgrad/Kokoro-82M
(v1.0, StyleTTS2 / iSTFTNet, 24 kHz mono) plus its English voice packs in a flat binary
layout, for use as the Kokoro text-to-speech engine in
Vernacula.
- Conversion script:
scripts/kokoro_export/ - Vernacula: github.com/christopherthompson81/vernacula
- Upstream model:
hexgrad/Kokoro-82M
Highlights
- Our own export pipeline, not the
onnx-communityartifacts: we control the opset, the I/O contract and the validation metric. There is no upstream export script to reproduce theirs from. - Exported with
disable_complex=True. The model's default complex-valued STFT cannot be exported at all (the TorchScript exporter dies onUnknown number type: complex); the real-valued STFT that replaces it is not bit-identical in the waveform domain (vocoder phase is not uniquely determined) but sits at0.37 log-spectral L1 against PyTorch β below a single frame of jitter (0.77) and inaudible in A/B listening. The full argument, with numbers, is indocs/kokoro_onnx_investigation.md. - G2P stays outside the graph. The model takes token ids; in Vernacula the phonemes come from vernacula-phonemizer rendered into Kokoro's vocabulary, so the same frontend serves every engine.
- Voice packs as flat float32, indexed by phoneme-string length, readable without a tensor library.
- A variable-length batched graph (
kokoro_batched.onnx) that renders several texts of different lengths in one call, at the model's own fidelity. Kokoro is batch=1 upstream, and naive padding corrupts the shorter items β AdaIN normalises over time, so padding frames pollute the per-item statistics, and the bidirectional LSTMs read padding backwards into real tokens and shift the predicted durations. Masking the statistics, packing the LSTMs and re-zeroing the padding after eachAdaIN1dcloses all three. It replaces the old single-item graph outright β at batch 1 it is ~1.08x faster than that graph was, so there is nothing to trade off.
Contents
| File | Purpose |
|---|---|
kokoro_batched.onnx |
The whole model, with a dynamic batch axis: token ids + style vectors + speed β 24 kHz waveforms (fp32, ~311 MB, weights inlined). Batch 1 is just the batch=1 case, and is faster than the old single-item graph, so this is the only model here |
voices/<name>.bin |
One voice pack per voice: 510 Γ 256 float32, little-endian β row n is the style vector for a phoneme string of length n + 1 |
manifest.json |
Per-file MD5 hashes for integrity checks |
ONNX contract
| Name | Shape | dtype | Description |
|---|---|---|---|
input_ids (in) |
[batch, tokens] |
int64 | Right-padded token ids, one row per text |
ref_s (in) |
[batch, 256] |
float32 | Style vector per item (each indexed by its own phoneme-string length) |
speed (in) |
[1] |
float32 | Speech-rate multiplier, shared by the batch |
input_lengths (in) |
[batch] |
int64 | Real token count per item β required; padding is masked from it |
audio (out) |
[batch, samples] |
float32 | 24 kHz waveform, padded to the batch's longest item |
pred_dur (out) |
[batch, tokens] |
int64 | Per-token frames, 0 on padded tokens |
tokens, batch and samples are dynamic. The context window is 510 tokens per item; split
longer text on sentence boundaries first.
Each item is valid for its own pred_dur.sum() * 600 samples; the rest of its row is batch
padding, so trim before use. pred_dur is identical to what the item gets rendered alone, at any
batch size β so word alignment never depends on which texts share a batch.
A batch is padded to its longest item, so grouping texts of similar length matters for throughput: an unsorted mix of one-line headings and long paragraphs fills only ~37% of the batch and is no faster than rendering one at a time. Sorting by phoneme length first roughly doubles it. Fidelity does not depend on the grouping β the padding error is a step function (two frames of padding cost as much as two hundred) and is masked out either way.
Voices
The 28 English voices of Kokoro v1.0 β American (af_* / am_*) and British
(bf_* / bm_*), the prefix selecting the accent's phonemization:
af_alloy af_aoede af_bella af_heart af_jessica af_kore af_nicole af_nova af_river af_sarah af_sky
am_adam am_echo am_eric am_fenrir am_liam am_michael am_onyx am_puck am_santa
bf_alice bf_emma bf_isabella bf_lily bm_daniel bm_fable bm_george bm_lewis
Upstream also ships voices for other languages (export_voices.py --all); they are not
included here because Vernacula's Kokoro frontend is English-only.
Export provenance
Exported via scripts/kokoro_export/
in the Vernacula repo: export_kokoro.py exports KModel.forward_with_tokens at opset 17
with disable_complex=True and validates it against the PyTorch reference on a real
(input_ids, ref_s, speed) capture using log-spectral L1 (waveform SNR and random
token ids both give meaningless verdicts here β see the investigation doc);
export_voices.py flattens the voices/*.pt packs.
License
Apache-2.0, inherited from
hexgrad/Kokoro-82M. The voice packs are
upstream's, redistributed unchanged in layout only.
Using these files
In Vernacula, point Settings β Text-to-Speech β Kokoro-82M at a folder holding these files (or use its Download button). Outside Vernacula:
from huggingface_hub import snapshot_download
import numpy as np, onnxruntime as ort
path = snapshot_download(repo_id="christopherthompson81/kokoro-82m-onnx")
sess = ort.InferenceSession(f"{path}/kokoro_batched.onnx")
# ids: Kokoro vocabulary ids for the phoneme string (see upstream / misaki)
ids = np.array([[0, *phoneme_ids, 0]], dtype=np.int64)
pack = np.fromfile(f"{path}/voices/af_heart.bin", dtype="<f4").reshape(510, 256)
style = pack[len(phoneme_ids) - 1][None, :]
audio, = sess.run(None, {"input_ids": ids, "style": style, "speed": np.array([1.0], np.float32)})
# audio: float32 mono at 24 kHz
Limitations
English only, as packaged here. Inherits Kokoro-82M's own limits (see the upstream model card); the ONNX export adds the phase difference described above and nothing else. Word timing in Vernacula comes from the model's predicted durations, which is exact to the frame.
Citation
See the upstream model card and the StyleTTS 2 paper.
Acknowledgments
- Original model: hexgrad (Kokoro-82M), building on StyleTTS 2 and iSTFTNet
- ONNX repackaging: Chris Thompson for Vernacula
Issues with the ONNX export specifically: open an issue on the Vernacula repo. Issues with the underlying model: see the upstream model card.
See also
- Vernacula on GitHub β the speech pipeline app this package is built for
- Conversion script (
scripts/kokoro_export/) hexgrad/Kokoro-82Mβ upstream model card- Other Vernacula model packages