Text-to-Speech
ONNX
GGUF
Chinese
English
onnxruntime
tts
on-device
jetson
telephony
vits
mb-istft-vits
multi-speaker
mandarin
taiwanese-mandarin
imatrix
conversational
Instructions to use Luigi/PrimeTTS with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use Luigi/PrimeTTS with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf Luigi/PrimeTTS:F32 # Run inference directly in the terminal: llama cli -hf Luigi/PrimeTTS:F32
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf Luigi/PrimeTTS:F32 # Run inference directly in the terminal: llama cli -hf Luigi/PrimeTTS:F32
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf Luigi/PrimeTTS:F32 # Run inference directly in the terminal: ./llama-cli -hf Luigi/PrimeTTS:F32
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf Luigi/PrimeTTS:F32 # Run inference directly in the terminal: ./build/bin/llama-cli -hf Luigi/PrimeTTS:F32
Use Docker
docker model run hf.co/Luigi/PrimeTTS:F32
- LM Studio
- Jan
- Ollama
How to use Luigi/PrimeTTS with Ollama:
ollama run hf.co/Luigi/PrimeTTS:F32
- Unsloth Studio
How to use Luigi/PrimeTTS with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for Luigi/PrimeTTS to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for Luigi/PrimeTTS to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for Luigi/PrimeTTS to start chatting
- Atomic Chat new
- Docker Model Runner
How to use Luigi/PrimeTTS with Docker Model Runner:
docker model run hf.co/Luigi/PrimeTTS:F32
- Lemonade
How to use Luigi/PrimeTTS with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull Luigi/PrimeTTS:F32
Run and chat with the model
lemonade run user.PrimeTTS-F32
List all available models
lemonade list
Upload README.md with huggingface_hub
Browse files
README.md
CHANGED
|
@@ -13,6 +13,7 @@ tags:
|
|
| 13 |
- telephony
|
| 14 |
- vits
|
| 15 |
- mb-istft-vits
|
|
|
|
| 16 |
- mandarin
|
| 17 |
- taiwanese-mandarin
|
| 18 |
base_model: owensong/Inflect-Nano-v1
|
|
@@ -24,223 +25,191 @@ pipeline_tag: text-to-speech
|
|
| 24 |
# PrimeTTS — on-device zh-TW + English TTS
|
| 25 |
|
| 26 |
Taiwan-Mandarin + English text-to-speech built for on-device use (contact-centre, GPS, transit): one
|
| 27 |
-
|
| 28 |
-
|
| 29 |
|
| 30 |
-
Two
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
|
|
|
|
|
|
|
| 40 |
|
| 41 |
-
|
| 42 |
|
| 43 |
-
| |
|
| 44 |
-
|---|---|
|
| 45 |
-
| **
|
| 46 |
-
|
|
| 47 |
-
|
|
| 48 |
-
| **Voice** | female Mandarin, "Xinran" |
|
| 49 |
-
| **Eval (36 held-out zh/mix/en sentences)** | X-ASR CER **0.027** overall — zh 0.033 · code-mix 0.039 · en 0.008 (below its 7B teacher's 0.043 on the same eval) |
|
| 50 |
-
| **Runtime** | single ONNX (`primetts_v2_xinran.onnx`, ORT-CPU) · `primetts_v2_xinran.gguf` for the ggml-CUDA Jetson-Nano runtime ([RapidSpeech.cpp](https://github.com/vieenrose/RapidSpeech.cpp), `mbistft-vits` arch) |
|
| 51 |
|
| 52 |
-
### On-device deployment (measured
|
| 53 |
|
| 54 |
-
|
|
|
|
| 55 |
|
| 56 |
-
| Tier | Runtime | Precision | RTF |
|
| 57 |
|---|---|---|---|---|
|
| 58 |
-
| **GPU** | RapidSpeech.cpp ggml-CUDA, 1 CPU thread | fp32 | **0.42** (2.4× RT) |
|
| 59 |
-
| **CPU** *(default)* | onnxruntime,
|
| 60 |
-
| **CPU** | onnxruntime,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
-
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
|
| 65 |
-
29k utterances over the same entity-rich zh-TW corpus as v1, per-utterance speaker-consistency QC
|
| 66 |
-
(retry-regenerated until >99% of clips match the target voice), trained from scratch at 16 kHz with a
|
| 67 |
-
3-embedding frontend (phone + tone + language, 88 symbols) and deterministic duration predictor.
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
```python
|
| 70 |
-
# v2 quickstart — one session, one call
|
| 71 |
-
import numpy as np, onnxruntime as ort, soundfile as sf
|
| 72 |
import sys; sys.path.insert(0, "PrimeTTS/scripts")
|
| 73 |
-
import
|
|
|
|
| 74 |
|
| 75 |
-
sess = ort.InferenceSession("PrimeTTS/
|
| 76 |
providers=["CPUExecutionProvider"])
|
| 77 |
o = F.text_to_ids("您好,歡迎使用 PrimeTTS。Thank you for calling.")
|
| 78 |
-
blank = lambda s: np.array([[0] + [v for x in s for v in (x, 0)]], np.int64)
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
| 84 |
sf.write("out.wav", wav, 16000)
|
| 85 |
```
|
| 86 |
|
| 87 |
---
|
| 88 |
|
| 89 |
-
# PrimeTTS v1 (
|
| 90 |
-
|
| 91 |
-
| | flagship `v1b_16k/` | on-device `v1b_8k/` |
|
| 92 |
-
|---|---|---|
|
| 93 |
-
| **Parameters** | **~5.0M** (3.56M acoustic + 1.43M vocoder) | **4.09M** (3.56M + 0.53M) |
|
| 94 |
-
| **Sample rate** | **16 kHz** (0–8 kHz) | 8 kHz (0–4 kHz) |
|
| 95 |
-
| **SQUIM PESQ** | **2.70** | 2.22 |
|
| 96 |
-
| **zh-CER / en-WER** | 0.149 / 0.083 | 0.109 / 0.083 |
|
| 97 |
-
| **Jetson Nano RTF** | (heavier) | **0.35** (1 thread) |
|
| 98 |
-
|
| 99 |
-
*Runtime: `onnxruntime`, CPU-only, torch-free. Voice: young female, Taiwan-Mandarin accent. License: Apache-2.0.*
|
| 100 |
-
|
| 101 |
-
## Highlights
|
| 102 |
-
|
| 103 |
-
- **Tiny + CPU-only** — ~4M params, ONNX, torch-free; real-time on a Jetson Nano (**RTF 0.35, single thread**).
|
| 104 |
-
- **One voice, three modes** — zh / en / code-mix share one timbre and accent through a single frontend; no
|
| 105 |
-
language tag needed.
|
| 106 |
-
- **Mandarin tones via a frame-pitch refiner** — a 97K-param module turns coarse per-phoneme pitch into a
|
| 107 |
-
per-frame F0 contour (the tone carrier). Ablating it costs **+18% relative zh-CER** (zh-only; English is
|
| 108 |
-
unaffected — no lexical tone).
|
| 109 |
-
- **Entity-correct** — a normalization layer reads numbers, dates, prices, emails, addresses, serials, and
|
| 110 |
-
spells acronyms/letters (VIP → V-I-P), applied identically in training and at inference.
|
| 111 |
-
|
| 112 |
-
## Performance — held-out (36 unseen phone-attendant sentences)
|
| 113 |
-
|
| 114 |
-
Per-checkpoint headline numbers are in the table at the top. Both checkpoints share the same intelligibility
|
| 115 |
-
and accent (**en-WER 0.083**; Taiwan-accent gap **+0.033**¹ ⇒ a genuine TW accent); they differ in **clarity**
|
| 116 |
-
(16 kHz **PESQ 2.70** vs 8 kHz 2.22) and **footprint** (8 kHz: **RTF 0.347** on a Jetson Nano, 1 thread).
|
| 117 |
-
zh-CER sits in the **0.11–0.15** band depending on checkpoint and recognizer, and **~half of the residual is
|
| 118 |
-
ASR homophone/variant noise**, not synthesis error (see *Known characteristics*).
|
| 119 |
-
|
| 120 |
-
¹ `CER(generic ASR) − CER(Taiwan-tuned Breeze-ASR-25)` per zh clip; `>0` ⇒ a Taiwan recognizer understands
|
| 121 |
-
it better ⇒ a real Taiwan accent is present.
|
| 122 |
-
|
| 123 |
-
> **On sample rate & clarity:** 8 kHz caps the band at 4 kHz (Nyquist), discarding the brightness/sibilance
|
| 124 |
-
> above it — intelligible but *telephone-band*. The **16 kHz flagship** doubles the band to 0–8 kHz
|
| 125 |
-
> (**PESQ 2.70 vs 2.22**, recovering ~22% of the energy 8 kHz throws away) while staying CPU-only. Choose the
|
| 126 |
-
> 8 kHz checkpoint only when you need the absolute tightest on-device RTF.
|
| 127 |
-
|
| 128 |
-
## Architecture
|
| 129 |
-
|
| 130 |
-
**Acoustic — `MicroFastSpeech` (3.56M).** FastSpeech-style, **no attention**: depthwise gated Conv-FFN,
|
| 131 |
-
external durations + length regulator, frame-pitch, BiGRU, postnet — plus the **frame-pitch refiner**
|
| 132 |
-
(`Conv1d → SiLU → Conv1d(groups=4) → SiLU → Conv1d`, 97K) that builds the per-frame F0 contour = Mandarin tones.
|
| 133 |
-
|
| 134 |
-
```json
|
| 135 |
-
{ "vocab_size": 256, "tone_size": 16, "lang_size": 4, "n_mels": 80,
|
| 136 |
-
"hidden": 168, "encoder_layers": 5, "decoder_layers": 6, "decoder_ff_mult": 3,
|
| 137 |
-
"sample_rate": 8000, "max_frames": 1000, "use_frame_pitch_refiner": true }
|
| 138 |
-
```
|
| 139 |
-
|
| 140 |
-
**Vocoder — Snake-HiFiGAN.** The on-device model uses the lightweight `snake_8k_lite`. The family — same
|
| 141 |
-
architecture, different width / sample rate — is **why a model's headline param count varies**:
|
| 142 |
-
|
| 143 |
-
| variant | used by | params | rate | band | PESQ² |
|
| 144 |
-
|---|---|---|---|---|---|
|
| 145 |
-
| **`snake_16k`** | **flagship `v1b_16k/`** | **1.43M** | 16 kHz | 0–8 kHz | **2.70** |
|
| 146 |
-
| `snake_8k_lite` | on-device `v1b_8k/` | 0.53M | 8 kHz | 0–4 kHz | 2.22 |
|
| 147 |
-
| `snake_8k` | 8 kHz, full width | 1.15M | 8 kHz | 0–4 kHz | 2.60 |
|
| 148 |
-
| `snake_v2mid` | 24 kHz (legacy) | 1.17M | 24 kHz | 0–12 kHz | 3.23 |
|
| 149 |
-
|
| 150 |
-
² SQUIM-PESQ. The dominant lever is the **sample rate** (band), not the vocoder: 16 kHz adds the 4–8 kHz
|
| 151 |
-
brightness 8 kHz can't represent. `snake_8k_lite` trades ~0.26 PESQ vs the full `snake_8k` for ~2.2× less
|
| 152 |
-
compute — the right call for the 8 kHz on-device build, where RTF matters more than the last bit of fidelity.
|
| 153 |
|
| 154 |
-
**
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
v1b_16k/{acoustic_encoder,acoustic_decoder,vocoder}.onnx + meta.json ← v1 16 kHz (~5.0M, CPU)
|
| 166 |
-
v1b_8k/ {acoustic_encoder,acoustic_decoder,vocoder}.onnx + meta.json ← v1 leanest on-device (4.09M, 8 kHz, Nano CPU)
|
| 167 |
-
{acoustic_encoder,…}.onnx + meta.json · v3_4.6M/ ← legacy 24 kHz variants (6.85M / 4.63M), for record
|
| 168 |
-
scripts/ frontend, aligner, corpus-gen, train / export, eval
|
| 169 |
-
inflect_nano/ the v1 trainer (acoustic.py + vocoder.py), forked from Inflect-Nano-v1 (LICENSE included)
|
| 170 |
-
```
|
| 171 |
|
| 172 |
-
|
| 173 |
|
| 174 |
-
```bash
|
| 175 |
-
pip install onnxruntime numpy soundfile g2pw g2p_en cn2an
|
| 176 |
-
huggingface-cli download Luigi/PrimeTTS --local-dir PrimeTTS
|
| 177 |
-
```
|
| 178 |
```python
|
| 179 |
import sys; sys.path.insert(0, "PrimeTTS/scripts")
|
| 180 |
import json, numpy as np, onnxruntime as ort, soundfile as sf
|
| 181 |
import frontend_bopomofo as F
|
| 182 |
-
from synth_from_text import host_regulate
|
| 183 |
|
| 184 |
-
D = "PrimeTTS/v1b_16k"
|
| 185 |
meta = json.load(open(f"{D}/meta.json"))
|
| 186 |
enc = ort.InferenceSession(f"{D}/acoustic_encoder.onnx", providers=["CPUExecutionProvider"])
|
| 187 |
dec = ort.InferenceSession(f"{D}/acoustic_decoder.onnx", providers=["CPUExecutionProvider"])
|
| 188 |
voc = ort.InferenceSession(f"{D}/vocoder.onnx", providers=["CPUExecutionProvider"])
|
| 189 |
-
|
| 190 |
o = F.text_to_ids("您好,歡迎使用 PrimeTTS。Thank you for calling.")
|
| 191 |
ph, tn, lg = (np.array([o[k]], np.int64) for k in ("phone_ids", "tone_ids", "lang_ids"))
|
| 192 |
cond, dur, pitch = enc.run(None, {"phone": ph, "tone": tn, "lang": lg, "speaker": np.zeros(1, np.int64)})
|
| 193 |
reg = host_regulate(cond, dur, pitch, meta["abs_frame_bins"], meta["max_frames"])
|
| 194 |
-
mel = dec.run(None, {k: reg[k] for k in
|
| 195 |
-
["frames","frame_meta","local_ctx_raw","abs_pos","pitch_frame","frame_mask"]})[0]
|
| 196 |
wav = voc.run(None, {"mel": mel.astype(np.float32)})[0].reshape(-1)
|
| 197 |
sf.write("out.wav", wav, meta["sample_rate"])
|
| 198 |
```
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
- **
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
## Credits & licenses
|
| 235 |
|
| 236 |
-
- **v2 architecture:** [MB-iSTFT-VITS](https://github.com/MasayaKawamura/MB-iSTFT-VITS) (Kawamura et al., Apache-2.0)
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
AI-generated
|
| 240 |
-
- **v1 base / trainer:** [`owensong/Inflect-Nano-v1`](https://huggingface.co/owensong/Inflect-Nano-v1) (Apache-2.0)
|
| 241 |
-
|
| 242 |
-
[Mozilla Common Voice zh-TW](https://commonvoice.mozilla.org/datasets) (**CC0 / public domain**)
|
| 243 |
-
- **Gate ASR:** Breeze-ASR-25 (MediaTek Research) · Whisper
|
| 244 |
`facebook/wav2vec2-lv-60-espeak-cv-ft` + `torchaudio.forced_align` · **Eval:** sherpa-onnx X-ASR
|
| 245 |
|
| 246 |
This repository: **Apache-2.0**.
|
|
|
|
| 13 |
- telephony
|
| 14 |
- vits
|
| 15 |
- mb-istft-vits
|
| 16 |
+
- multi-speaker
|
| 17 |
- mandarin
|
| 18 |
- taiwanese-mandarin
|
| 19 |
base_model: owensong/Inflect-Nano-v1
|
|
|
|
| 25 |
# PrimeTTS — on-device zh-TW + English TTS
|
| 26 |
|
| 27 |
Taiwan-Mandarin + English text-to-speech built for on-device use (contact-centre, GPS, transit): one
|
| 28 |
+
frontend handles Chinese, English, and **code-mix** with no language routing, and reads **entities**
|
| 29 |
+
correctly — phone numbers, emails, addresses, prices, dates, temperatures, %, serials.
|
| 30 |
|
| 31 |
+
**Two models to know:**
|
| 32 |
|
| 33 |
+
| | **PrimeTTS v2.1** — flagship | **PrimeTTS v1** — leanest CPU |
|
| 34 |
+
|---|---|---|
|
| 35 |
+
| Folder | [`v21_mbistft_16k/`](./v21_mbistft_16k) | [`v1b_16k/`](./v1b_16k) · [`v1b_8k/`](./v1b_8k) |
|
| 36 |
+
| Architecture | MB-iSTFT-VITS (end-to-end, multi-speaker) | FastSpeech + Snake-HiFiGAN (+ pitch refiner) |
|
| 37 |
+
| Params | 37.9M | ~5.0M (16 kHz) / 4.09M (8 kHz) |
|
| 38 |
+
| Voices | **3 selectable** — Xinran ♀, Anchen ♂, Bowen ♂ | 1 — young ♀ zh-TW |
|
| 39 |
+
| Sample rate | 16 kHz | 16 kHz / 8 kHz |
|
| 40 |
+
| Held-out CER | **0.059** (zh/mix/en, 3-voice avg) | 0.11–0.15 (zh) |
|
| 41 |
+
| Best on | Jetson Nano **GPU** (also any CPU) | pure **CPU** — Nano **RTF 0.35** (8 kHz, 1 thread) |
|
| 42 |
+
|
| 43 |
+
Pick **v2.1** for the best quality and multiple voices; pick **v1** when the budget is CPU-only and tight.
|
| 44 |
+
(`v2_mbistft_16k/` is v2.1's single-voice "Xinran" predecessor — same architecture, one speaker; kept for
|
| 45 |
+
reference. `v3_4.6M/` and the top-level `*.onnx` are legacy 24 kHz variants.)
|
| 46 |
+
|
| 47 |
+
> 🔊 **Live demo:** https://huggingface.co/spaces/Luigi/PrimeTTS-vs-Inflect-Nano-v1 — pick a model, pick a voice, type text.
|
| 48 |
+
|
| 49 |
+
---
|
| 50 |
+
|
| 51 |
+
## PrimeTTS v2.1 (`v21_mbistft_16k/`)
|
| 52 |
|
| 53 |
+
End-to-end **MB-iSTFT-VITS** (VAE + normalizing flow + adversarial multi-band iSTFT head; conv-only, no LSTM)
|
| 54 |
+
with **3 selectable Taiwan-Mandarin voices**, chosen by an integer `sid` input (0 = Xinran ♀, 1 = Anchen ♂,
|
| 55 |
+
2 = Bowen ♂). 37.9M generator params, 16 kHz, `gin_channels=256` speaker conditioning.
|
| 56 |
|
| 57 |
+
**Quality** (36 held-out zh / code-mix / en sentences, X-ASR normalized CER):
|
| 58 |
|
| 59 |
+
| voice (`sid`) | CER | note |
|
| 60 |
+
|---|---|---|
|
| 61 |
+
| Xinran ♀ (0) | **0.059** | flagship voice, cleanest teacher |
|
| 62 |
+
| Anchen ♂ (1) | 0.069 | slight accent |
|
| 63 |
+
| Bowen ♂ (2) | 0.066 | slight accent |
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
### On-device deployment (measured, Jetson Nano gen-1 / Tegra X1)
|
| 66 |
|
| 67 |
+
Same runtime profile as the single-voice v2 (identical architecture). RTF = compute-time ÷ audio-time
|
| 68 |
+
(lower is faster; < 1.0 = real-time).
|
| 69 |
|
| 70 |
+
| Tier | Runtime | Precision | RTF | Notes |
|
| 71 |
|---|---|---|---|---|
|
| 72 |
+
| **GPU** | RapidSpeech.cpp ggml-CUDA, 1 CPU thread | fp32 | **0.42** (2.4× RT) | launch-bound floor on Maxwell (sm_53, no CUDA-graph replay) |
|
| 73 |
+
| **CPU** *(default)* | onnxruntime, 4 threads | fp32 | **0.52** (1.9× RT) | full quality, 117 MB |
|
| 74 |
+
| **CPU** | onnxruntime, 2 threads | fp32 | **0.77** (1.3× RT) | fewer cores, leaves headroom |
|
| 75 |
+
|
| 76 |
+
Both tiers are full-fidelity and need no GPU. On this ARMv8.0 Cortex-A57, **fp32 is the fast format**:
|
| 77 |
+
**int8 is not a speed lever** (static-int8 shifts the voice; dynamic-int8 preserves it but runs *slower* than
|
| 78 |
+
fp32 — no dot-product / no FP16 arithmetic on this core), fp16 casts to fp32 (no speedup), and XNNPACK ≈ MLAS.
|
| 79 |
+
The only on-device speed lever is a smaller/faster **architecture**, not quantization.
|
| 80 |
+
|
| 81 |
+
### Files
|
| 82 |
|
| 83 |
+
```
|
| 84 |
+
v21_mbistft_16k/primetts_v21_3voice.onnx 3-voice fp32 (117 MB) — full quality, all runtimes
|
| 85 |
+
```
|
| 86 |
|
| 87 |
+
### Quickstart
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
+
```bash
|
| 90 |
+
pip install onnxruntime numpy soundfile g2pw g2p_en cn2an
|
| 91 |
+
huggingface-cli download Luigi/PrimeTTS --local-dir PrimeTTS
|
| 92 |
+
```
|
| 93 |
```python
|
|
|
|
|
|
|
| 94 |
import sys; sys.path.insert(0, "PrimeTTS/scripts")
|
| 95 |
+
import numpy as np, onnxruntime as ort, soundfile as sf
|
| 96 |
+
import frontend_bopomofo as F # g2pw bopomofo + g2p_en, one pass
|
| 97 |
|
| 98 |
+
sess = ort.InferenceSession("PrimeTTS/v21_mbistft_16k/primetts_v21_3voice.onnx",
|
| 99 |
providers=["CPUExecutionProvider"])
|
| 100 |
o = F.text_to_ids("您好,歡迎使用 PrimeTTS。Thank you for calling.")
|
| 101 |
+
blank = lambda s: np.array([[0] + [v for x in s for v in (x, 0)]], np.int64) # add_blank=true
|
| 102 |
+
sid = 0 # 0 Xinran ♀ · 1 Anchen ♂ · 2 Bowen ♂
|
| 103 |
+
wav = sess.run(None, {
|
| 104 |
+
"x": blank(o["phone_ids"]), "tone": blank(o["tone_ids"]), "lang": blank(o["lang_ids"]),
|
| 105 |
+
"x_lengths": np.array([2*len(o["phone_ids"])+1], np.int64),
|
| 106 |
+
"sid": np.array([sid], np.int64),
|
| 107 |
+
"noise_scale": np.array([0.667], np.float32),
|
| 108 |
+
"length_scale": np.array([1.0], np.float32)})[0].reshape(-1)
|
| 109 |
sf.write("out.wav", wav, 16000)
|
| 110 |
```
|
| 111 |
|
| 112 |
---
|
| 113 |
|
| 114 |
+
## PrimeTTS v1 (`v1b_16k/`, `v1b_8k/`) — tiny, CPU-only
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
+
FastSpeech-style acoustic (**no attention**: depthwise gated Conv-FFN + external durations + length regulator
|
| 117 |
+
+ BiGRU + postnet) with a **97K-param frame-pitch refiner** that turns per-phoneme pitch into the per-frame F0
|
| 118 |
+
contour = Mandarin tones (ablating it costs +18% relative zh-CER), and a **Snake-HiFiGAN** vocoder. Torch-free
|
| 119 |
+
ONNX; runs real-time on a Jetson Nano CPU. One young-female zh-TW voice across zh / en / code-mix.
|
| 120 |
|
| 121 |
+
| | flagship `v1b_16k/` | leanest `v1b_8k/` |
|
| 122 |
+
|---|---|---|
|
| 123 |
+
| Params | **~5.0M** (3.56M acoustic + 1.43M vocoder) | **4.09M** (+ 0.53M vocoder) |
|
| 124 |
+
| Sample rate | 16 kHz (0–8 kHz band) | 8 kHz (telephone band) |
|
| 125 |
+
| Jetson Nano RTF | (heavier) | **0.35** (1 thread) |
|
| 126 |
+
| `.gguf` (ggml) | — | `inflect_combined_v1b.gguf` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
+
Pipeline is `encoder → numpy length-regulator → decoder → vocoder`:
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
```python
|
| 131 |
import sys; sys.path.insert(0, "PrimeTTS/scripts")
|
| 132 |
import json, numpy as np, onnxruntime as ort, soundfile as sf
|
| 133 |
import frontend_bopomofo as F
|
| 134 |
+
from synth_from_text import host_regulate
|
| 135 |
|
| 136 |
+
D = "PrimeTTS/v1b_16k" # or v1b_8k for the leanest Nano RTF
|
| 137 |
meta = json.load(open(f"{D}/meta.json"))
|
| 138 |
enc = ort.InferenceSession(f"{D}/acoustic_encoder.onnx", providers=["CPUExecutionProvider"])
|
| 139 |
dec = ort.InferenceSession(f"{D}/acoustic_decoder.onnx", providers=["CPUExecutionProvider"])
|
| 140 |
voc = ort.InferenceSession(f"{D}/vocoder.onnx", providers=["CPUExecutionProvider"])
|
|
|
|
| 141 |
o = F.text_to_ids("您好,歡迎使用 PrimeTTS。Thank you for calling.")
|
| 142 |
ph, tn, lg = (np.array([o[k]], np.int64) for k in ("phone_ids", "tone_ids", "lang_ids"))
|
| 143 |
cond, dur, pitch = enc.run(None, {"phone": ph, "tone": tn, "lang": lg, "speaker": np.zeros(1, np.int64)})
|
| 144 |
reg = host_regulate(cond, dur, pitch, meta["abs_frame_bins"], meta["max_frames"])
|
| 145 |
+
mel = dec.run(None, {k: reg[k] for k in ["frames","frame_meta","local_ctx_raw","abs_pos","pitch_frame","frame_mask"]})[0]
|
|
|
|
| 146 |
wav = voc.run(None, {"mel": mel.astype(np.float32)})[0].reshape(-1)
|
| 147 |
sf.write("out.wav", wav, meta["sample_rate"])
|
| 148 |
```
|
| 149 |
+
`scripts/synth_long.py` adds punctuation auto-chunking for long text.
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
## Shared frontend
|
| 154 |
+
|
| 155 |
+
`g2pw` (Taiwan bopomofo + polyphone disambiguation) + `g2p_en` (arpabet) merge into one phone sequence with
|
| 156 |
+
per-phone **language ids** — zh / en / code-mix in a single pass, **88-symbol table**. Entity normalization
|
| 157 |
+
(`scripts/text_norm.py`) reads numbers / dates / prices / emails / addresses / serials and spells acronyms
|
| 158 |
+
(VIP → V-I-P), applied identically in training and inference. Both model families consume the *same*
|
| 159 |
+
`frontend_bopomofo.text_to_ids()` output (phone / tone / lang ids).
|
| 160 |
+
|
| 161 |
+
---
|
| 162 |
+
|
| 163 |
+
## Reproduce from this repo
|
| 164 |
+
|
| 165 |
+
Everything needed to rebuild both models is here: the frontend, entity normalizer, aligner, corpus-gen and
|
| 166 |
+
text-selection scripts, the eval sets + scorer, the export scripts, and the v1 trainer.
|
| 167 |
+
|
| 168 |
+
```
|
| 169 |
+
scripts/ frontend_bopomofo.py · text_norm.py · align_durations_v4.py · build_corpus_v3.py
|
| 170 |
+
gen_codemix*.py · gen_entity_texts.py · select_diverse_text.py · asr_filter.py
|
| 171 |
+
synth_from_text.py · synth_long.py · export_8k.py · export_onnx_primetts_v21.py
|
| 172 |
+
xasr_offline.py · assess_big.py · rebuild_voice.sh · symbol_table.json
|
| 173 |
+
data/ codemix_v2.txt · entity_texts.jsonl · voxcpm_texts.jsonl (corpus text sources)
|
| 174 |
+
eval_big.jsonl · eval_entity.jsonl (held-out eval sets)
|
| 175 |
+
inflect_nano/ the v1 trainer (acoustic.py + vocoder.py), forked from Inflect-Nano-v1
|
| 176 |
+
configs/ zhtw_mb_istft_16k_v21b.json (v2.1 3-voice training config)
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
**Common recipe (both models):** `teacher corpus → ASR/CER gate → phone-level align → train → export`.
|
| 180 |
+
The three levers that matter for a tiny model: **phone-level alignment** (espeak phoneme-CTC +
|
| 181 |
+
`torchaudio.forced_align` — sub-syllable boundaries separate speech from fluent babble), **broad coverage +
|
| 182 |
+
diverse code-mix**, and **the teacher** (a student's language is only as good as its teacher's).
|
| 183 |
+
|
| 184 |
+
**v1** (`inflect_nano/` trainer, all in-repo):
|
| 185 |
+
1. Generate corpus text — `scripts/gen_codemix_v2.py`, `gen_entity_texts.py`, `select_diverse_text.py`.
|
| 186 |
+
2. Synthesize with the teacher (VoxCPM2 cloning a CC0 zh-TW reference), gate with `asr_filter.py`.
|
| 187 |
+
3. Align — `scripts/align_durations_v4.py`. Train acoustic + vocoder (`inflect_nano/`). Export — `scripts/export_8k.py`.
|
| 188 |
+
4. One-shot: **`scripts/rebuild_voice.sh`** (swap in your own ~10 s reference clip).
|
| 189 |
+
|
| 190 |
+
**v2.1** (MB-iSTFT-VITS; trainer is the upstream repo — see credits):
|
| 191 |
+
1. Synthesize the corpus with a **VibeVoice-Large** teacher across the 3 zh-capable voices.
|
| 192 |
+
2. **CER-gate the teacher audio** (X-ASR normalized CER < 0.05) — *not* voice-similarity — so only intelligible
|
| 193 |
+
clips train the model. (This is the single most important QC step; ungated multi-voice teacher audio is the
|
| 194 |
+
main failure mode.)
|
| 195 |
+
3. Train the 3-speaker MB-iSTFT-VITS (`configs/zhtw_mb_istft_16k_v21b.json`, `n_speakers=3`, `gin_channels=256`),
|
| 196 |
+
warm-started from the single-voice v2 with **fresh** speaker-conditioning layers.
|
| 197 |
+
4. Export to ONNX — **`scripts/export_onnx_primetts_v21.py`** (opset 17, `dynamo=False`; the tiny gen-head
|
| 198 |
+
iSTFT `n_fft=16, hop=4` is replaced by an exact irFFT + overlap-add matrix, verified vs `torch.istft`).
|
| 199 |
+
5. Score — `scripts/xasr_offline.py` + `assess_big.py` on `eval_big.jsonl`.
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
|
| 203 |
## Credits & licenses
|
| 204 |
|
| 205 |
+
- **v2.1 architecture:** [MB-iSTFT-VITS](https://github.com/MasayaKawamura/MB-iSTFT-VITS) (Kawamura et al., Apache-2.0) ·
|
| 206 |
+
Jetson-Nano ggml-CUDA runtime: [RapidSpeech.cpp](https://github.com/vieenrose/RapidSpeech.cpp) (`mbistft-vits` arch)
|
| 207 |
+
- **v2.1 teacher:** VibeVoice-Large (Microsoft, **MIT**), 3 zh-capable presets (via the MIT
|
| 208 |
+
[community repo](https://github.com/vibevoice-community/VibeVoice)) — synthesized / AI-generated voices; mark as such in products
|
| 209 |
+
- **v1 base / trainer:** [`owensong/Inflect-Nano-v1`](https://huggingface.co/owensong/Inflect-Nano-v1) (Apache-2.0) ·
|
| 210 |
+
**v1 teacher:** [`openbmb/VoxCPM2`](https://huggingface.co/openbmb/VoxCPM2) ·
|
| 211 |
+
**v1 reference voice:** [Mozilla Common Voice zh-TW](https://commonvoice.mozilla.org/datasets) (**CC0 / public domain**)
|
| 212 |
+
- **Gate ASR:** Breeze-ASR-25 (MediaTek Research) · Whisper · **Aligner:**
|
| 213 |
`facebook/wav2vec2-lv-60-espeak-cv-ft` + `torchaudio.forced_align` · **Eval:** sherpa-onnx X-ASR
|
| 214 |
|
| 215 |
This repository: **Apache-2.0**.
|