--- license: apache-2.0 library_name: onnxruntime pipeline_tag: text-to-speech tags: - onnx - onnxruntime - text-to-speech - kokoro - styletts2 - vernacula base_model: hexgrad/Kokoro-82M language: - en --- # Kokoro-82M — ONNX export for Vernacula Re-packaged ONNX export of [`hexgrad/Kokoro-82M`](https://huggingface.co/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](https://github.com/christopherthompson81/vernacula). - **Conversion script:** [`scripts/kokoro_export/`](https://github.com/christopherthompson81/vernacula/tree/main/scripts/kokoro_export) - **Vernacula:** [github.com/christopherthompson81/vernacula](https://github.com/christopherthompson81/vernacula) - **Upstream model:** [`hexgrad/Kokoro-82M`](https://huggingface.co/hexgrad/Kokoro-82M) ## Highlights - **Our own export pipeline**, not the `onnx-community` artifacts: 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 on `Unknown 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 at ~0.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 in [`docs/kokoro_onnx_investigation.md`](https://github.com/christopherthompson81/vernacula/blob/main/docs/kokoro_onnx_investigation.md). - **G2P stays outside the graph.** The model takes token ids; in Vernacula the phonemes come from [vernacula-phonemizer](https://github.com/christopherthompson81/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 each `AdaIN1d` closes 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/.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/`](https://github.com/christopherthompson81/vernacula/tree/main/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](https://www.apache.org/licenses/LICENSE-2.0), inherited from [`hexgrad/Kokoro-82M`](https://huggingface.co/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: ```python 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="