--- license: other license_name: cc-by-nc-4.0-and-apache-2.0 license_link: https://huggingface.co/afrispeech/Akiti-TTS language: - tw library_name: phoonnx pipeline_tag: text-to-speech tags: - onnx - tts - twi - akan - neutts - neucodec base_model: - afrispeech/Akiti-TTS - neuphonic/neucodec-onnx-decoder-int8 --- # phoonnx-neutts — Akiti-TTS (Asante Twi), ONNX ONNX conversion of [afrispeech/Akiti-TTS](https://huggingface.co/afrispeech/Akiti-TTS), an Asante Twi text-to-speech model, packaged for [phoonnx](https://github.com/TigreGotico/phoonnx). This repository holds converted weights only — no new training was done. Akiti-TTS is a LoRA fine-tune of [pnnbao-ump/VieNeu-TTS-0.3B](https://huggingface.co/pnnbao-ump/VieNeu-TTS-0.3B), which comes from Neuphonic's NeuTTS Air family: a Qwen3 causal LM that emits [NeuCodec](https://huggingface.co/neuphonic/neucodec) audio tokens. ## Files (`akiti-twi-onnx/`) | File | What it is | |---|---| | `neutts_lm.onnx` + `neutts_lm.onnx.data` | Qwen3 backbone, fp32, KV-cached. **The graph needs its `.data` sidecar next to it.** | | `neutts_lm_int8.onnx` | The same graph, dynamically quantized to int8. Smaller, but measurably lower quality — it puts the end-of-speech token in the top 5 at the first step, where fp32 does not. | | `neucodec_decoder.onnx` | Copy of [neuphonic/neucodec-onnx-decoder-int8](https://huggingface.co/neuphonic/neucodec-onnx-decoder-int8) (Apache-2.0), unmodified. | | `tokenizer.json` | The checkpoint's own BPE, copied from upstream. | | `voices.json` | The nine voice presets, copied from [AfriSpeech/akiti-tts](https://github.com/AfriSpeech/akiti-tts) (MIT). | | `neutts_onnx_meta.json` | Architecture summary written by the export script. | ## Graph contract `neutts_lm.onnx` serves prefill and decode: the same graph with a different past length. ``` inputs input_ids int64 [1, S] prompt tokens, or 1 token per step attention_mask int64 [1, P + S] ones over past and current tokens position_ids int64 [1, S] absolute positions, P .. P+S-1 past_key_ fp32 [1, 4, P, 64] i in 0..27 past_value_ fp32 [1, 4, P, 64] outputs logits fp32 [1, 66938] last position only present_key_ / present_value_ fp32 [1, 4, P + S, 64] ``` `neucodec_decoder.onnx` takes `codes` int32 `[1, 1, N]` and returns `audio` float32 `[1, 1, 480 * (N - 1)]` at 24 kHz — 50 codec tokens per second of audio. ## Prompt format The LM is prompted with phonemes, not letters. Text is phonemized by espeak-ng using the **`lfn`** (Lingua Franca Nova) voice, which is what the checkpoint was trained with; espeak-ng has no Twi voice, and `lfn`'s five-vowel orthography reads Twi spelling closely. ``` <|TEXT_PROMPT_START|>{reference phones} {target phones}<|TEXT_PROMPT_END|> <|SPEECH_GENERATION_START|>{<|speech_c|> for c in reference codes} ``` Generation continues that speech-token run until `<|SPEECH_GENERATION_END|>` or EOS. ## Fidelity Exported with `scripts/conversion/neutts/export_neutts_onnx.py` in phoonnx. Against the torch model on a fixed prompt, max absolute logit difference: | | fp32 ONNX | |---|---| | prefill | 4.10e-05 | | decode (8 steps) | 2.96e-05 | The fp32 graph's top-5 next tokens on a real prompt are identical to torch's, in the same order and to two decimal places. ## Licensing The pieces carry different licenses, and one of them is inconsistent upstream. Read this before using the model. - **`afrispeech/Akiti-TTS` weights** — the Hugging Face model card declares **CC BY-NC 4.0** (non-commercial). The [GitHub README](https://github.com/AfriSpeech/akiti-tts) instead states the weights are Apache-2.0. These two statements disagree. This mirror records both and resolves neither; treat the stricter of the two (**non-commercial**) as binding until AfriSpeech clarifies. The GitHub repository's *code* is MIT, which is not in dispute. - **`neucodec_decoder.onnx`** — Apache-2.0, from Neuphonic. - **`voices.json`** — from the MIT-licensed AfriSpeech/akiti-tts repository. Attribution: AfriSpeech / Ghana NLP (Akiti-TTS), pnnbao-ump (VieNeu-TTS-0.3B), Neuphonic (NeuTTS Air, NeuCodec).