--- license: apache-2.0 language: - en pipeline_tag: text-to-speech tags: - tts - kokoro - onnx - onnxruntime - text-to-speech - speech - audio - voice base_model: oddadmix/Kokoro-7M-Distill --- # Kokoro-7M ONNX Models & Voice Pack Ultra-lightweight (7.48M parameters) Text-to-Speech bundle for **Kokoro-7M-Distill** (`oddadmix/Kokoro-7M-Distill`), containing both **Full FP32** and **INT8 Quantized** ONNX models, fully optimized for CPU execution with **ONNX Runtime** and paired with the official conditioned voice **`af_msa.onnx`**. - **Sample Rate**: 24 kHz - **Conditioned Voice**: `af_msa.onnx` - **CPU Speed**: **17.6x–18.4x faster than real-time** (~330 ms for 6.1s speech) - **Zero Weights at Inference**: 100% pure ONNX Runtime execution. --- ## Files in this Folder | File | Size | Type | Description | | :--- | :---: | :---: | :--- | | **`kokoro_7m.onnx`** | 28.82 MB | **Full Model** | Full-precision FP32 model with Level 1–3 CPU graph optimizations. | | **`kokoro_7m_int8.onnx`** | 25.23 MB | **INT8 Model** | Dynamic INT8 quantized model for reduced footprint and max CPU speed. | | **`af_msa.onnx`** | 0.52 MB | **Voice Model** | Standalone ONNX voice model (evaluates style embeddings via ONNX Runtime). | | **`test.py`** | 5.9 KB | **Test Script** | Self-contained test runner supporting both Full Model and INT8. | | **`README.md`** | — | **Readme** | Documentation and usage guide. | > [!IMPORTANT] > **Why `af_msa`?** > The student model was distilled and conditioned specifically on the `af_msa` voice pack. Distillation benchmarks show using `af_heart` severely degrades naturalness and clarity (UTMOS drops from 4.14 to 3.57). Always pair Kokoro-7M with `af_msa`. --- ## Quickstart: How to Test ### 1. Requirements Install minimal inference dependencies: ```bash pip install onnxruntime soundfile numpy kokoro ``` ### 2. Run Test **Test Full Precision Model (Default):** ```bash python test.py ``` **Test INT8 Quantized Model:** ```bash python test.py --int8 ``` **Test Custom Text:** ```bash python test.py --text "Hello world! Testing Kokoro seven million on CPU." python test.py --int8 --text "Testing INT8 quantized speech synthesis." ``` Generated audio is saved to `out.wav` (24 kHz, 16-bit PCM). --- ## Performance (CPU Benchmarks) Benchmarked on an Intel Core CPU with standard 4-thread budget: | Model | File Size | Latency (6.1s speech) | Real-Time Factor (RTF) | Speed | | :--- | :---: | :---: | :---: | :---: | | **Full Model (`kokoro_7m.onnx`)** | 28.82 MB | 349 ms | 0.0568 | **17.6x real-time** | | **INT8 Model (`kokoro_7m_int8.onnx`)** | 25.23 MB | 331 ms | 0.0543 | **18.4x real-time** | --- ## Python Usage (100% Pure ONNX Runtime) ```python import numpy as np import onnxruntime as ort import soundfile as sf from kokoro import KPipeline # 1. Choose model: "kokoro_7m.onnx" (full) or "kokoro_7m_int8.onnx" (int8) model_path = "kokoro_7m.onnx" # or "kokoro_7m_int8.onnx" sess_opts = ort.SessionOptions() sess_opts.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL model_sess = ort.InferenceSession(model_path, sess_opts, providers=["CPUExecutionProvider"]) voice_sess = ort.InferenceSession("af_msa.onnx", sess_opts, providers=["CPUExecutionProvider"]) # 2. Phonemize text pipeline = KPipeline(lang_code="a", repo_id="oddadmix/Kokoro-7M-Distill", model=False) text = "Hello! Kokoro-7M is running with an ONNX voice on CPU." _, tokens = pipeline.g2p(text) # 114-symbol vocabulary mapping VOCAB = { ';': 1, ':': 2, ',': 3, '.': 4, '!': 5, '?': 6, '—': 9, '…': 10, '"': 11, '(': 12, ')': 13, '“': 14, '”': 15, ' ': 16, '̃': 17, 'ʣ': 18, 'ʥ': 19, 'ʦ': 20, 'ʨ': 21, 'ᵝ': 22, 'ꭧ': 23, 'A': 24, 'I': 25, 'O': 31, 'Q': 33, 'S': 35, 'T': 36, 'W': 39, 'Y': 41, 'ᵊ': 42, 'a': 43, 'b': 44, 'c': 45, 'd': 46, 'e': 47, 'f': 48, 'h': 50, 'i': 51, 'j': 52, 'k': 53, 'l': 54, 'm': 55, 'n': 56, 'o': 57, 'p': 58, 'q': 59, 'r': 60, 's': 61, 't': 62, 'u': 63, 'v': 64, 'w': 65, 'x': 66, 'y': 67, 'z': 68, 'ɑ': 69, 'ɐ': 70, 'ɒ': 71, 'æ': 72, 'β': 75, 'ɔ': 76, 'ɕ': 77, 'ç': 78, 'ɖ': 80, 'ð': 81, 'ʤ': 82, 'ə': 83, 'ɚ': 85, 'ɛ': 86, 'ɜ': 87, 'ɟ': 90, 'ɡ': 92, 'ɥ': 99, 'ɨ': 101, 'ɪ': 102, 'ʝ': 103, 'ɯ': 110, 'ɰ': 111, 'ŋ': 112, 'ɳ': 113, 'ɲ': 114, 'ɴ': 115, 'ø': 116, 'ɸ': 118, 'θ': 119, 'œ': 120, 'ɹ': 123, 'ɾ': 125, 'ɻ': 126, 'ʁ': 128, 'ɽ': 129, 'ʂ': 130, 'ʃ': 131, 'ʈ': 132, 'ʧ': 133, 'ʊ': 135, 'ʋ': 136, 'ʌ': 138, 'ɣ': 139, 'ɤ': 140, 'χ': 142, 'ʎ': 143, 'ʒ': 147, 'ʔ': 148, 'ˈ': 156, 'ˌ': 157, 'ː': 158, 'ʰ': 162, 'ʲ': 164, '↓': 169, '→': 171, '↗': 172, '↘': 173, 'ᵻ': 177 } # 3. Synthesize chunks = [] silence = np.zeros(int(24000 * 0.15), dtype=np.float32) for _, ps, _ in pipeline.en_tokenize(tokens): if not ps: continue input_ids = np.array([[0] + [VOCAB[p] for p in ps if p in VOCAB] + [0]], dtype=np.int64) # Query style vector from voice ONNX model idx = np.array([min(input_ids.shape[1] - 1, 509)], dtype=np.int64) style_vec = voice_sess.run(None, {"index": idx})[0] # Generate audio chunk from model ONNX chunk = model_sess.run(None, { "input_ids": input_ids, "style": style_vec.astype(np.float32), "speed": np.array([1.0], dtype=np.float32) })[0].squeeze() chunks.append(chunk) chunks.append(silence) full_audio = np.concatenate(chunks[:-1]) if len(chunks) > 1 else chunks[0] sf.write("out.wav", full_audio, 24000) print(f"Generated {len(full_audio)/24000:.2f}s of audio saved to out.wav!") ```