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
Model card: rewrite for current on-device v1b (8k, 4.09M, snake_8k_lite) + vocoder-family/param table; synthesized + shortened (424->155 lines)
Browse files
README.md
CHANGED
|
@@ -18,407 +18,163 @@ library_name: onnxruntime
|
|
| 18 |
pipeline_tag: text-to-speech
|
| 19 |
---
|
| 20 |
|
| 21 |
-
# PrimeTTS — tiny
|
| 22 |
|
| 23 |
-
A **4.
|
| 24 |
-
and
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
dates, temperatures, percentages, serial numbers, and a broad bank of Taiwan/world named entities.
|
| 28 |
|
| 29 |
-
> 🔊 **Live demo:** https://huggingface.co/spaces/Luigi/PrimeTTS-vs-Inflect-Nano-v1
|
| 30 |
|
| 31 |
| | |
|
| 32 |
|---|---|
|
| 33 |
-
| **Parameters** |
|
| 34 |
-
| **Sample rate** | 24 kHz |
|
| 35 |
-
| **Runtime** | `onnxruntime`, **CPU
|
| 36 |
-
| **Languages** | zh
|
| 37 |
-
| **Voice** | young female,
|
| 38 |
-
| **
|
| 39 |
-
| **License** | Apache
|
| 40 |
-
|
| 41 |
-
##
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
¹ `CER(generic ASR) − CER(Taiwan‑tuned Breeze‑ASR‑25)` per zh clip; `>0` ⇒ a Taiwan‑tuned recognizer
|
| 62 |
-
understands it better ⇒ genuine Taiwan accent present.
|
| 63 |
-
|
| 64 |
-
> **v3 pronunciation fix:** corrected the forced-aligner's bopomofo→IPA map (ㄜ/ㄟ/ㄡ were mapped to `ɤ`/`ei`/`ou`, absent from the aligner vocab, so those vowels were starved to ~2 frames and dropped) and added the syllabic-vowel symbol **ㄭ** for empty-rime syllables (是/十/日/司/資…, previously rendered as a bare consonant). Both classes — 額/給/走 and 是/司 — now render correctly; zh‑CER 0.106→**0.087**. 88 phone symbols.
|
| 65 |
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
##
|
| 70 |
-
|
| 71 |
-
The entity-dense diagnostic sentence was synthesized by the **teacher**, **PrimeTTS v1 (4.63M)** and
|
| 72 |
-
**PrimeTTS v2 (6.85M)**, then transcribed by two recognizers that treat **empty-rime syllables** (a bare
|
| 73 |
-
retroflex/dental sibilant + the syllabic vowel ㄭ — 十/日/之…) very differently:
|
| 74 |
-
- **Breeze-ASR-25** — Taiwan-tuned, robust; tends to **over-read** short empty-rimes.
|
| 75 |
-
- **X-ASR** — a fine-tuned zh-TW/en streaming zipformer2 transducer; **stricter** on those syllables.
|
| 76 |
-
|
| 77 |
-
*(All evidence below is ASR transcripts only — no listening judgment is implied.)*
|
| 78 |
-
|
| 79 |
-
> Diagnostic sentence: `Anderson 先生您好,您 2024年3月15日 訂的 3 件商品總共 NT$1,299,序號 AB1234CD,…,降雨機率 70%,謝謝。`
|
| 80 |
-
|
| 81 |
-
| token | Breeze-ASR-25 (robust) | X-ASR (strict) |
|
| 82 |
-
|---|---|---|
|
| 83 |
-
| **3月15日** — teacher | `3 月 15 日` ✓ | date span lost (`…二零二四年 YING 总共…`) |
|
| 84 |
-
| **3月15日** — v1 4.63M | `3 月 15 日` ✓ | `三月十五日` ✓ |
|
| 85 |
-
| **3月15日** — v2 6.85M | `3 月 15 日` ✓ | `Y 号` — lost |
|
| 86 |
-
| **序號 AB1234CD** | teacher `AB1234CD` ✓ · v1 `1B1234City` · v2 `PB1234CD` | teacher `A B 1 2 3 4 C D` ✓ · v1 serial dropped · v2 `DB … C D` |
|
| 87 |
-
| **降雨機率 70%** | numeral `70%` for all three | `百分之七` (teacher) · `百分之` (v1, v2) — 十 blurred |
|
| 88 |
-
| **松高路11號5樓櫃台** | ✓ all three | v2 `松高路十一号五楼柜台` ✓ · v1 dropped · teacher garbled (`LL ZU Y 号`) |
|
| 89 |
-
|
| 90 |
-
**Findings**
|
| 91 |
-
1. **Empty-rime 日 (3月15日).** Robust Breeze renders the date for all three (it over-reads the syllabic ㄭ).
|
| 92 |
-
The stricter X-ASR yields it **only for v1 (4.63M)**; **v2 (6.85M)** and the **teacher** both lose it. So the
|
| 93 |
-
syllable is fragile across the board and the two ASRs disagree — v2 shows **no X-ASR gain on 日, a soft
|
| 94 |
-
regression vs v1, but not vs the teacher**. *Caveat:* v1's X-ASR transcript is otherwise the **most degraded**
|
| 95 |
-
of the three (it also drops the serial, the address tail, and 七十), and `三月十五日` is a high-frequency date
|
| 96 |
-
the strict ASR may be pattern-completing — so treat v1's 日 as a **soft win**, not proof of an acoustic edge.
|
| 97 |
-
2. **Serial `AB1234CD`.** Only the **teacher** is clean on both ASRs. Both students mis-render the **leading
|
| 98 |
-
letter "A"**; even with the frontend emitting the correct letter name, a 4–7M acoustic renders an isolated
|
| 99 |
-
spelled letter weakly. (The frontend letter-name fix lands the right *phoneme*; the *acoustic* is the limit.)
|
| 100 |
-
3. **降雨機率 70% (百分之七十).** **X-ASR** blurs the final 十 **even on the teacher** (`百分之七`); **Breeze
|
| 101 |
-
sidesteps it** by emitting the numeral `70%` for all three. A hard empty-rime (十 = ㄕㄭ), present on the
|
| 102 |
-
teacher and both students — **not a v2-specific regression** (under X-ASR v2 drops both 七 and 十, slightly
|
| 103 |
-
worse than the teacher here, consistent with a hard syllable).
|
| 104 |
-
4. **Address tail (松高路11號5樓櫃台).** v2 renders it cleanly (X-ASR ✓); v1 drops it and the teacher is itself
|
| 105 |
-
garbled there — so **v2 beats both v1 and the teacher on the long tail**.
|
| 106 |
-
|
| 107 |
-
**Takeaway.** The two ASRs disagree precisely on the empty-rime syllables, and that disagreement *is* the
|
| 108 |
-
method: a robust ASR (Breeze) over-reads them, so its CER **understates** empty-rime fragility; a strict ASR
|
| 109 |
-
(X-ASR) exposes it. Net, **v2 (6.85M) trades a clearer, more complete long tail** (address/temperature — beating
|
| 110 |
-
both v1 *and* the teacher) **for no gain on the empty-rime 日/十 family and a soft regression vs v1 on 日.**
|
| 111 |
-
Catching this required cross-checking a robust against a strict ASR — a single CER number hides it.
|
| 112 |
|
| 113 |
-
--
|
|
|
|
|
|
|
| 114 |
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
reflects the *model*, not out-of-distribution text.
|
| 121 |
-
|
| 122 |
-
| Pass | Coverage | Utterance |
|
| 123 |
-
|---|---|---|
|
| 124 |
-
| **1 · zh-TW** | all 37 bopomofo + ㄭ = **38/38** | 小明知道今天是好日子,他喝了四次熱湯。女兒給媽媽買肥皂、青菜、綠茶和八顆雞蛋,婆婆很歡喜。二月的雨不停,風很涼,我們走回家。 |
|
| 125 |
-
| **2 · English** | all 39 arpabet = **39/39** | On a beige autumn morning, she shyly measured both choices with joy and pleasure; he laughed, thought it through, and quickly chose the rough path. Good vision, you know, can bring change now. |
|
| 126 |
-
| **3 · entities** | date · price (NT$) · % · °C · phone · alphanumeric serial · ordinal | 訂單 AB1234CD,三月十五日,NT$299,折扣 70%,氣溫 28 度,請撥 0918,第3名,謝謝。 |
|
| 127 |
-
|
| 128 |
-
Each was synthesized by the **teacher** (VoxCPM2), **v1 (4.63M)** and **v2 (6.85M)**, then transcribed by
|
| 129 |
-
**Breeze-ASR-25** (robust) and **X-ASR** (a stricter zh-TW/en zipformer2). Raw results below — **bold = error**,
|
| 130 |
-
✓ = clean — so you can judge for yourself.
|
| 131 |
-
|
| 132 |
-
**Pass 1 (zh)** — ref: 小明知道今天是好日子,…,二月的雨不停,風很涼,我們走回家。
|
| 133 |
-
|
| 134 |
-
| model | Breeze (robust) | X-ASR (strict) |
|
| 135 |
-
|---|---|---|
|
| 136 |
-
| teacher | ✓ | ✓ |
|
| 137 |
-
| v1 4.63M | ✓ | ✓ |
|
| 138 |
-
| v2 6.85M | …**與兒**給媽媽…(女兒→與兒) | …八顆雞蛋**QUIP'N'月**的雨…風很**亮**…回家**家**(婆婆很歡喜二月→Latin bleed) |
|
| 139 |
-
|
| 140 |
-
**Pass 2 (English)** — ref: …she shyly measured both choices…he laughed…chose the rough path. Good vision, you know, can bring change now.
|
| 141 |
-
|
| 142 |
-
| model | Breeze | X-ASR |
|
| 143 |
-
|---|---|---|
|
| 144 |
-
| teacher | ✓ | drops "**morning**" |
|
| 145 |
-
| v1 4.63M | ✓ | she shyly→**XI SHAILY**, laughed→**LOVED** |
|
| 146 |
-
| v2 6.85M | ✓ | Good→**QUOTE** |
|
| 147 |
-
|
| 148 |
-
**Pass 3 (entities)** — ref: 訂單 AB1234CD,三月十五日,NT$299,折扣 70%,氣溫 28 度,請撥 0918,第3名。
|
| 149 |
-
|
| 150 |
-
| model | Breeze | X-ASR |
|
| 151 |
-
|---|---|---|
|
| 152 |
-
| teacher | 訂單→**DingDang**, else ✓ (AB1234CD ✓) | serial ✓ but tail **garbled** |
|
| 153 |
-
| v1 4.63M | serial→**AB1234C** (drops D), else ✓ | A→**EIGHT**, 三月→**先月** |
|
| 154 |
-
| v2 6.85M | AB1234CD ✓, 70%→**77%**, else ✓ | A→**AV**(B→V), 三月→**现月** |
|
| 155 |
-
|
| 156 |
-
**Diagnosis**
|
| 157 |
-
1. **Dense zh (P1): v1 ≥ v2.** teacher and v1 are clean on both ASRs; **v2 still degrades on a dense zh run**
|
| 158 |
-
(婆婆很歡喜二月 → Latin "QUIP'N'" under X-ASR, 女兒→與兒 under Breeze). This persists on *natural* text, so
|
| 159 |
-
it is a real v2 weakness, not OOD noise — the larger, more code-mix-exposed model over-triggers English on
|
| 160 |
-
dense zh.
|
| 161 |
-
2. **English (P2): on natural prose all three are good.** v2 and the teacher are clean on Breeze; X-ASR shows
|
| 162 |
-
only minor slips (v1 "she shyly"→"XI SHAILY"/laughed→loved; v2 Good→"QUOTE"). **This corrects an earlier
|
| 163 |
-
pangram result** that made the students look far worse than the teacher — that gap was largely an
|
| 164 |
-
out-of-distribution artifact of dense, unnatural English.
|
| 165 |
-
3. **Entities (P3): the serial letter "A" is the hard case.** Under robust Breeze, teacher and **v2 render the
|
| 166 |
-
full serial AB1234CD correctly** (v1 drops a digit); v2's only number error is 70%→77%. Under the strict
|
| 167 |
-
X-ASR the leading **A** garbles for all (→EIGHT/AV) and even the teacher's tail breaks up — i.e. an isolated
|
| 168 |
-
spelled letter is near the capacity limit for a 4–7M model. The text-norm expansions themselves are correct;
|
| 169 |
-
the failures are acoustic.
|
| 170 |
-
|
| 171 |
-
**Net:** naturalizing the test removed an OOD penalty on English (P2) — students are close to the teacher
|
| 172 |
-
there. The weaknesses that survive natural text are **v2's dense-zh Latin-bleed** and the **serial letter "A"**;
|
| 173 |
-
on entity-dense, long-form text v2 (6.85M) is otherwise the strongest student.
|
| 174 |
|
| 175 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
```bash
|
| 180 |
pip install onnxruntime numpy soundfile g2pw g2p_en cn2an
|
| 181 |
huggingface-cli download Luigi/PrimeTTS --local-dir PrimeTTS
|
| 182 |
```
|
| 183 |
```python
|
| 184 |
-
|
| 185 |
-
import sys; sys.path.insert(0, "scripts")
|
| 186 |
import json, numpy as np, onnxruntime as ort, soundfile as sf
|
| 187 |
import frontend_bopomofo as F
|
| 188 |
-
from synth_from_text import host_regulate
|
| 189 |
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
|
|
|
| 194 |
|
| 195 |
-
o = F.text_to_ids("您好,歡迎使用 PrimeTTS。Thank you for calling.")
|
| 196 |
ph, tn, lg = (np.array([o[k]], np.int64) for k in ("phone_ids", "tone_ids", "lang_ids"))
|
| 197 |
-
cond, dur, pitch = enc.run(None, {"phone": ph, "tone": tn,
|
| 198 |
-
"lang": lg, "speaker": np.zeros(1, np.int64)})
|
| 199 |
reg = host_regulate(cond, dur, pitch, meta["abs_frame_bins"], meta["max_frames"])
|
| 200 |
mel = dec.run(None, {k: reg[k] for k in
|
| 201 |
["frames","frame_meta","local_ctx_raw","abs_pos","pitch_frame","frame_mask"]})[0]
|
| 202 |
wav = voc.run(None, {"mel": mel.astype(np.float32)})[0].reshape(-1)
|
| 203 |
sf.write("out.wav", wav, meta["sample_rate"])
|
| 204 |
```
|
| 205 |
-
The
|
| 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 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
| **total** | **29,547** |
|
| 240 |
-
|
| 241 |
-
The corpus is assembled from **32,500** teacher clips; the generic subset passes the ASR gate (≈21%
|
| 242 |
-
dropped), the named‑entity coverage subset is trusted unfiltered, and English rows are upsampled ×2
|
| 243 |
-
(~27% exposure) to protect English quality. English phones additionally carry v1's native pronunciation
|
| 244 |
-
via the warm‑start.
|
| 245 |
-
|
| 246 |
-
---
|
| 247 |
-
|
| 248 |
-
## How it was trained — the levers
|
| 249 |
-
|
| 250 |
-
The recipe was established on the **4.63M v3** (kept in `v3_4.6M/`) and grown into the **6.85M v4
|
| 251 |
-
default**. Three data/alignment levers carry across both and matter most for a tiny model:
|
| 252 |
-
|
| 253 |
-
1. **Phone‑level alignment** (`scripts/align_durations_v4.py`) — true per‑phone durations (espeak
|
| 254 |
-
phoneme‑CTC + `torchaudio.forced_align`) instead of crude char/letter CTC. Sub‑syllable boundary
|
| 255 |
-
accuracy is what separates intelligible speech from fluent babble; skipping this makes tiny TTS garble.
|
| 256 |
-
2. **Vocabulary coverage + diverse code‑mix** — broad character coverage and a code‑mix **frame bank**
|
| 257 |
-
(varied syntax, English in varied positions) so the model isn't overfit to a few templates.
|
| 258 |
-
3. **Teacher choice** — the English a tiny model learns is only as native as the teacher's; **VoxCPM2**
|
| 259 |
-
gives clean, natural zh *and* en in one voice.
|
| 260 |
-
|
| 261 |
-
**What changed from v3 (4.63M) → v4 (6.85M, current default):**
|
| 262 |
-
- **More capacity** — hidden 168→184, decoder 6→7 layers, ff×3→×4, and **contextual prosody predictors**
|
| 263 |
-
on (a Conv‑FFN refinement block + per‑phone duration/energy/bright/pitch deltas). Latency is unchanged
|
| 264 |
-
(vocoder + host length‑regulation dominate) — still CPU real‑time, incl. **1.3× real‑time on 2 cores of
|
| 265 |
-
a Jetson Nano**.
|
| 266 |
-
- **Multi‑resolution‑STFT clarity loss** added alongside the 2D mel‑GAN (both ramp in after a 25k pure‑
|
| 267 |
-
reconstruction warmup) to sharpen the predicted mel.
|
| 268 |
-
- **Trained from scratch** (the architecture changed, so the v1 warm‑start no longer applies). v3 was
|
| 269 |
-
warm‑started from Inflect‑Nano‑v1's English‑native checkpoint, which is why v3 still edges v4 on
|
| 270 |
-
**code‑mixed** text; v4 matches v3 on pure Mandarin (zh‑CER 0.083) and was chosen for clearer, more
|
| 271 |
-
naturally‑timed delivery (preferred by ear).
|
| 272 |
-
|
| 273 |
-
The shipped v4 checkpoint is the **held‑out best (50k step)** — past that the mel‑GAN keeps sharpening the
|
| 274 |
-
train mel but held‑out intelligibility drifts, so sweep the held‑out set and pick the optimum rather than
|
| 275 |
-
the last step. **A v1 warm‑start of the v4 architecture is the clear next lever** for closing the code‑mix
|
| 276 |
-
gap, since pure‑zh is already at parity.
|
| 277 |
-
|
| 278 |
-
---
|
| 279 |
-
|
| 280 |
-
## Architecture
|
| 281 |
-
|
| 282 |
-
- **Acoustic** — `MicroFastSpeech` (v4: **5.43M**): depthwise Conv‑FFN, **no attention**, external
|
| 283 |
-
durations + length regulator, frame‑pitch, BiGRU, postnet, plus **contextual prosody predictors**.
|
| 284 |
-
Exact v4 config (read from the checkpoint; the trainer builds from these flags):
|
| 285 |
-
|
| 286 |
-
```json
|
| 287 |
-
{ "vocab_size": 256, "tone_size": 16, "lang_size": 4, "n_mels": 80,
|
| 288 |
-
"hidden": 184, "encoder_layers": 6, "decoder_layers": 7, "decoder_ff_mult": 4,
|
| 289 |
-
"kernel_size": 7, "speaker_count": 2, "speaker_dim": 64, "dropout": 0.08,
|
| 290 |
-
"sample_rate": 24000, "max_frames": 1400, "postnet_scale": 0.1,
|
| 291 |
-
"use_frame_pitch": true, "use_frame_pitch_refiner": true, "abs_frame_bins": 512,
|
| 292 |
-
"use_contextual_predictors": true, "use_group_duration_planner": true }
|
| 293 |
-
```
|
| 294 |
-
> The **group‑duration planner** is trained but **disabled at export** (it uses a non‑ONNX‑able host
|
| 295 |
-
> loop and only adjusts inference‑time durations); `scripts/export_8k.py` sets it to `None`
|
| 296 |
-
> automatically, so the ONNX uses the plain per‑phone durations (with the contextual delta).
|
| 297 |
-
> The v3 4.63M config (`hidden 168, enc 5 / dec 6, ff×3`, predictors off) is preserved in `v3_4.6M/`.
|
| 298 |
-
|
| 299 |
-
- **Vocoder** — Snake‑HiFiGAN (~1.17M), **24 kHz variant** `snake_v2mid` (sr 24000, n_fft 1024, hop 256,
|
| 300 |
-
80 mels, fmax 12000), retrained on the teacher corpus. **Shared by v3 and v4.**
|
| 301 |
-
- **Frontend** — `g2pw` (Taiwan bopomofo + polyphone disambiguation) + `g2p_en` (arpabet), merged into
|
| 302 |
-
one phone sequence with per‑phone language ids → handles zh, en, and code‑mix in a single pass.
|
| 303 |
-
**88‑symbol table** (`symbol_table.json`), identical for v3 and v4.
|
| 304 |
-
- **Long text** — the absolute positional code saturates past `max_frames` (~1400 frames ≈ 15 s), so
|
| 305 |
-
utterances longer than that are **auto‑chunked at punctuation** (`scripts/synth_long.py`); the live
|
| 306 |
-
Space does this transparently.
|
| 307 |
-
|
| 308 |
-
---
|
| 309 |
-
|
| 310 |
-
## Reproduce / fine‑tune your own
|
| 311 |
-
|
| 312 |
-
Pipeline: **teacher corpus → ASR gate → align → train vocoder → warm‑start + train acoustic → export**.
|
| 313 |
-
Repo layout:
|
| 314 |
-
|
| 315 |
-
```
|
| 316 |
-
acoustic_encoder.onnx acoustic_decoder.onnx vocoder.onnx meta.json symbol_table.json ← DEFAULT = v4 6.85M (24 kHz)
|
| 317 |
-
v3_4.6M/{acoustic_encoder,acoustic_decoder,vocoder}.onnx v3_4.6M/meta.json ← prior 4.63M default, for record/rollback
|
| 318 |
-
checkpoints/inflect-micro-fastspeech-v4-50000.pt ← v4 acoustic (shipped)
|
| 319 |
-
checkpoints/inflect-micro-fastspeech-v3-30000.pt ← v3 acoustic (4.63M)
|
| 320 |
-
checkpoints/hifigan-snake_v2mid-final.pt ← vocoder (shared by v3 & v4)
|
| 321 |
-
scripts/ frontend, aligner, corpus‑gen, train/export (export_8k.py), long‑text chunking (synth_long.py), eval
|
| 322 |
-
inflect_nano/ the trainer (acoustic.py + vocoder.py), forked from Inflect‑Nano‑v1 (LICENSE included)
|
| 323 |
-
```
|
| 324 |
-
|
| 325 |
-
**Prerequisites:** Python 3.12, a GPU for training; `pip install torch torchaudio transformers
|
| 326 |
-
onnxruntime soundfile librosa g2pw g2p_en cn2an opencc faster-whisper edge-tts`.
|
| 327 |
-
|
| 328 |
-
### 1 · Teacher corpus (one cloned voice)
|
| 329 |
-
```bash
|
| 330 |
-
# make a Taiwan‑female reference, then VoxCPM2‑clone every line in that voice
|
| 331 |
-
edge-tts --voice zh-TW-HsiaoChenNeural --text "<ref sentence>" --write-media ref.mp3
|
| 332 |
-
python gen_voxcpm_corpus.py --texts texts.jsonl --ref ref.wav --ref-text ref.txt \
|
| 333 |
-
--out-dir corpus --manifest manifest.jsonl
|
| 334 |
-
```
|
| 335 |
-
|
| 336 |
-
### 2 · ASR quality gate (Taiwan‑tuned)
|
| 337 |
-
```bash
|
| 338 |
-
python asr_filter.py --manifest manifest.jsonl --out manifest \
|
| 339 |
-
--device cuda # Breeze‑ASR‑25 (zh/mix) + Whisper‑medium (en) → manifest.clean.jsonl
|
| 340 |
-
```
|
| 341 |
-
|
| 342 |
-
### 3 · Phone‑level alignment ⭐ *the key step*
|
| 343 |
-
```bash
|
| 344 |
-
python scripts/align_durations_v4.py --manifest manifest.clean.jsonl --out align.jsonl
|
| 345 |
-
```
|
| 346 |
-
|
| 347 |
-
### 4 · Train the 24 kHz vocoder
|
| 348 |
-
```bash
|
| 349 |
-
PYTHONPATH=. python -m inflect_nano.vocoder --train-jsonl voc_rows.jsonl \
|
| 350 |
-
--out-dir vocoder_24k --variant snake_v2mid --steps 40000 --segment-size 16384 --stft-weight 2.5
|
| 351 |
-
```
|
| 352 |
-
|
| 353 |
-
### 5 · Train the acoustic model
|
| 354 |
-
|
| 355 |
-
**v4 (current default, 6.85M, from scratch)** — capacity + contextual prosody + mel‑GAN + MR‑STFT clarity:
|
| 356 |
-
```bash
|
| 357 |
-
PYTHONPATH=. PITCH_CACHE_DIR=pitch_cache python -m inflect_nano.acoustic \
|
| 358 |
-
--durations-jsonl align.jsonl --out-dir acoustic_24k_v4 \
|
| 359 |
-
--hidden 184 --encoder-layers 6 --decoder-layers 7 --decoder-ff-mult 4 \
|
| 360 |
-
--contextual-predictors --group-duration-planner --group-duration-weight 0.05 \
|
| 361 |
-
--vocoder-variant snake_v2mid --sample-rate 24000 \
|
| 362 |
-
--vocoder-checkpoint vocoder_24k/hifigan-snake_v2mid-final.pt --vocoder-mel-weight 1.0 \
|
| 363 |
-
--vocoder-mrstft-weight 1.0 --mrstft-warmup-steps 25000 \
|
| 364 |
-
--mel-gan-weight 0.1 --gan-2d --gan-fm-auto --gan-r1-gamma 1.0 --gan-crop 128 --gan-warmup-steps 25000 \
|
| 365 |
-
--frame-pitch-weight 1.0 --duration-weight 0.08 --pitch-weight 0.04 \
|
| 366 |
-
--steps 60000 --batch-size 8 --lr 2e-4 --max-frames 1400 --en-upsample 2 \
|
| 367 |
-
--save-interval 5000 --preload-features --device cuda
|
| 368 |
-
# ~5 h on one 24 GB GPU. Ship the HELD-OUT best (step 50000 here), not the last step.
|
| 369 |
-
```
|
| 370 |
-
|
| 371 |
-
**v3 (4.63M, warm‑started)** — the prior default, reproduced for record:
|
| 372 |
-
```bash
|
| 373 |
-
PYTHONPATH=. python -m inflect_nano.acoustic --durations-jsonl align.jsonl \
|
| 374 |
-
--out-dir acoustic_24k_v3 --vocoder-variant snake_v2mid --sample-rate 24000 \
|
| 375 |
-
--vocoder-checkpoint vocoder_24k/hifigan-snake_v2mid-final.pt --vocoder-mel-weight 1.0 \
|
| 376 |
-
--init-checkpoint inflect_nano_v1_acoustic.pt \
|
| 377 |
-
--mel-gan-weight 0.1 --gan-2d --gan-fm-auto --gan-r1-gamma 1.0 --gan-crop 128 --gan-warmup-steps 25000 \
|
| 378 |
-
--steps 60000 --batch-size 8 --max-frames 1400 --en-upsample 2 # ship step 30000
|
| 379 |
-
```
|
| 380 |
-
|
| 381 |
-
### 6 · Export to ONNX + evaluate
|
| 382 |
-
```bash
|
| 383 |
-
python scripts/export_8k.py --acoustic-ckpt acoustic_8k/…pt --vocoder-ckpt vocoder_8k/…pt --out-dir onnx/
|
| 384 |
-
python scripts/synth_from_text.py --onnx-dir onnx --out-dir syn --texts eval.jsonl
|
| 385 |
-
python scripts/assess_big.py --synth-dir syn # offline CER/WER
|
| 386 |
-
```
|
| 387 |
-
Evaluate on **≥30 held‑out sentences** — small eval sets are too noisy to trust. Sweep checkpoints and
|
| 388 |
-
pick the **held‑out** sweet spot (the GAN keeps improving train‑set sharpness past the held‑out optimum).
|
| 389 |
-
|
| 390 |
-
---
|
| 391 |
-
|
| 392 |
-
## Train on your OWN voice — one command
|
| 393 |
-
|
| 394 |
-
Swap the reference voice; everything else (text pools, ASR gate, alignment, recipe) is fixed. Both
|
| 395 |
-
vocoder and acoustic are retrained (both are voice-specific). Text pools + eval sets are bundled in
|
| 396 |
-
`data/` and at the repo root, so it reproduces exactly.
|
| 397 |
-
|
| 398 |
-
```bash
|
| 399 |
-
# 0. one venv with the deps (see prereqs in scripts/rebuild_voice.sh), PYTHONPATH=repo root,
|
| 400 |
-
# and inflect_nano_v1_acoustic.pt from owensong/Inflect-Nano-v1 for the warm-start.
|
| 401 |
-
huggingface-cli download Luigi/PrimeTTS --local-dir PrimeTTS && cd PrimeTTS
|
| 402 |
-
cp data/*.jsonl data/*.txt . # text pools at root
|
| 403 |
-
|
| 404 |
-
# 1. a ~10 s clip of your voice. For a commercial-clear reference, use a CC0 source such as
|
| 405 |
-
# Mozilla Common Voice zh-TW (the shipped model uses a young-female Common Voice speaker). Or synth one:
|
| 406 |
-
edge-tts --voice zh-TW-HsiaoYuNeural --text "您好,歡迎來電。Thank you for calling." --write-media ref.mp3
|
| 407 |
-
ffmpeg -y -i ref.mp3 -ar 24000 -ac 1 ref.wav ; printf '%s' "您好,歡迎來電。Thank you for calling." > ref.txt
|
| 408 |
-
|
| 409 |
-
# 2. ONE command -> corpus -> gate -> align -> vocoder -> acoustic -> export
|
| 410 |
-
PY=/path/to/venv/bin/python ./scripts/rebuild_voice.sh ref.wav ref.txt myvoice
|
| 411 |
-
# -> pick best corpus_myvoice/onnx_<K>/ (~35k is the usual held-out sweet spot)
|
| 412 |
-
```
|
| 413 |
-
**Time on dual RTX 5090:** ≈ 9 h end-to-end (~6.5 h to a shippable 35k checkpoint) — synth ~2 h,
|
| 414 |
-
gate+align ~25 min, then vocoder (~3 h) ∥ acoustic (~4–7 h) in parallel, export ~15 min.
|
| 415 |
|
| 416 |
## Credits & licenses
|
| 417 |
|
| 418 |
-
- **Base
|
| 419 |
-
- **Teacher
|
| 420 |
-
|
| 421 |
-
- **
|
| 422 |
-
-
|
| 423 |
|
| 424 |
-
This repository: **Apache
|
|
|
|
| 18 |
pipeline_tag: text-to-speech
|
| 19 |
---
|
| 20 |
|
| 21 |
+
# PrimeTTS — tiny on-device zh-TW + English TTS
|
| 22 |
|
| 23 |
+
A **4.09M-parameter** Taiwan-Mandarin + English text-to-speech model that runs **entirely on CPU** — small
|
| 24 |
+
and fast enough for **Jetson-class on-device** use (contact-centre, GPS, transit). One model, **one
|
| 25 |
+
young-female voice**: Chinese, English, and code-mix through a single frontend (no language routing), built
|
| 26 |
+
for **entity correctness** — phone numbers, emails, addresses, prices, dates, temperatures, %, serials.
|
|
|
|
| 27 |
|
| 28 |
+
> 🔊 **Live demo:** https://huggingface.co/spaces/Luigi/PrimeTTS-vs-Inflect-Nano-v1
|
| 29 |
|
| 30 |
| | |
|
| 31 |
|---|---|
|
| 32 |
+
| **Parameters** | **4.09M** = 3.56M acoustic *(incl. 97K pitch refiner)* + 0.53M vocoder |
|
| 33 |
+
| **Sample rate** | 8 kHz *(on-device; a 24 kHz variant is in-repo)* |
|
| 34 |
+
| **Runtime** | `onnxruntime`, **CPU-only**, torch-free at inference |
|
| 35 |
+
| **Languages** | zh-TW (Traditional) + English + code-mix — single voice |
|
| 36 |
+
| **Voice** | young female, Taiwan-Mandarin accent |
|
| 37 |
+
| **Measured** | zh-CER 0.109 · en-WER 0.083 · **Jetson Nano RTF 0.35** (1 thread) |
|
| 38 |
+
| **License** | Apache-2.0 |
|
| 39 |
+
|
| 40 |
+
## Highlights
|
| 41 |
+
|
| 42 |
+
- **Tiny + CPU-only** — ~4M params, ONNX, torch-free; real-time on a Jetson Nano (**RTF 0.35, single thread**).
|
| 43 |
+
- **One voice, three modes** — zh / en / code-mix share one timbre and accent through a single frontend; no
|
| 44 |
+
language tag needed.
|
| 45 |
+
- **Mandarin tones via a frame-pitch refiner** — a 97K-param module turns coarse per-phoneme pitch into a
|
| 46 |
+
per-frame F0 contour (the tone carrier). Ablating it costs **+18% relative zh-CER** (zh-only; English is
|
| 47 |
+
unaffected — no lexical tone).
|
| 48 |
+
- **Entity-correct** — a normalization layer reads numbers, dates, prices, emails, addresses, serials, and
|
| 49 |
+
spells acronyms/letters (VIP → V-I-P), applied identically in training and at inference.
|
| 50 |
+
|
| 51 |
+
## Performance — held-out (36 unseen phone-attendant sentences)
|
| 52 |
+
|
| 53 |
+
| metric | value |
|
| 54 |
+
|---|---|
|
| 55 |
+
| zh-CER (Breeze-ASR-25) | **0.109** (pure-zh 0.119 · code-mix 0.098) |
|
| 56 |
+
| en-WER (Whisper) | **0.083** |
|
| 57 |
+
| Taiwan-accent gap¹ | **+0.033** (genuine TW accent) |
|
| 58 |
+
| SQUIM PESQ / STOI | 2.22 / 0.90 |
|
| 59 |
+
| **On Jetson Nano** (ORT-CPU, 1 thread) | **RTF 0.347** · on-device CER ≈ 0.117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
+
¹ `CER(generic ASR) − CER(Taiwan-tuned Breeze-ASR-25)` per zh clip; `>0` ⇒ a Taiwan recognizer understands
|
| 62 |
+
it better ⇒ a real Taiwan accent is present.
|
| 63 |
|
| 64 |
+
> **On audio quality:** at 8 kHz the band ceiling is 4 kHz (Nyquist), which discards the brightness/sibilance
|
| 65 |
+
> above it — clear and intelligible, but *telephone-band*. A **16 kHz variant (0–8 kHz, ~5.0M) is in
|
| 66 |
+
> progress** for higher fidelity while staying on-device; the **24 kHz** files (below) are the fullest today.
|
| 67 |
|
| 68 |
+
## Architecture
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
+
**Acoustic — `MicroFastSpeech` (3.56M).** FastSpeech-style, **no attention**: depthwise gated Conv-FFN,
|
| 71 |
+
external durations + length regulator, frame-pitch, BiGRU, postnet — plus the **frame-pitch refiner**
|
| 72 |
+
(`Conv1d → SiLU → Conv1d(groups=4) → SiLU → Conv1d`, 97K) that builds the per-frame F0 contour = Mandarin tones.
|
| 73 |
|
| 74 |
+
```json
|
| 75 |
+
{ "vocab_size": 256, "tone_size": 16, "lang_size": 4, "n_mels": 80,
|
| 76 |
+
"hidden": 168, "encoder_layers": 5, "decoder_layers": 6, "decoder_ff_mult": 3,
|
| 77 |
+
"sample_rate": 8000, "max_frames": 1000, "use_frame_pitch_refiner": true }
|
| 78 |
+
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
+
**Vocoder — Snake-HiFiGAN.** The on-device model uses the lightweight `snake_8k_lite`. The family — same
|
| 81 |
+
architecture, different width / sample rate — is **why a model's headline param count varies**:
|
| 82 |
+
|
| 83 |
+
| variant | used by | params | rate | band | PESQ² |
|
| 84 |
+
|---|---|---|---|---|---|
|
| 85 |
+
| **`snake_8k_lite`** | **PrimeTTS (on-device)** | **0.53M** | 8 kHz | 0–4 kHz | 2.34 |
|
| 86 |
+
| `snake_8k` | 8 kHz, full width | 1.15M | 8 kHz | 0–4 kHz | 2.60 |
|
| 87 |
+
| `snake_v2mid` | 24 kHz variant | 1.17M | 24 kHz | 0–12 kHz | 3.23 |
|
| 88 |
+
| `snake_16k` | 16 kHz *(roadmap)* | 1.43M | 16 kHz | 0–8 kHz | — |
|
| 89 |
+
|
| 90 |
+
² SQUIM-PESQ, 5-utterance mean. The big 8 → 24 kHz jump is the **sample rate** (band), *not* the vocoder;
|
| 91 |
+
`_lite` trades ~0.26 PESQ vs the full `snake_8k` for ~2.2× less compute (the lower Nano RTF). So the **4.09M
|
| 92 |
+
on-device total** = 3.56M acoustic + the **0.53M lite vocoder**; the 24 kHz files pair the same-class acoustic
|
| 93 |
+
with the heavier 1.17M `snake_v2mid`.
|
| 94 |
|
| 95 |
+
**Frontend.** `g2pw` (Taiwan bopomofo + polyphone disambiguation) + `g2p_en` (arpabet), merged into one phone
|
| 96 |
+
sequence with per-phone language ids — zh, en, code-mix in a single pass. **88-symbol table.** Entity
|
| 97 |
+
normalization (`text_norm.py`) handles numbers / dates / prices / emails / serials, spells ALL-CAPS acronyms
|
| 98 |
+
and a small brand lexicon. Text past `max_frames` is auto-chunked at punctuation.
|
| 99 |
+
|
| 100 |
+
## Model files
|
| 101 |
+
|
| 102 |
+
```
|
| 103 |
+
v1b_8k/{acoustic_encoder,acoustic_decoder,vocoder}.onnx + meta.json ← RECOMMENDED on-device (4.09M, 8 kHz) — the demo serves this
|
| 104 |
+
{acoustic_encoder,acoustic_decoder,vocoder}.onnx + meta.json ← 24 kHz higher-fidelity variant (6.85M)
|
| 105 |
+
v3_4.6M/… ← legacy 24 kHz (4.63M), kept for record
|
| 106 |
+
scripts/ frontend, aligner, corpus-gen, train / export, eval
|
| 107 |
+
inflect_nano/ the trainer (acoustic.py + vocoder.py), forked from Inflect-Nano-v1 (LICENSE included)
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
## Quickstart (CPU)
|
| 111 |
|
| 112 |
```bash
|
| 113 |
pip install onnxruntime numpy soundfile g2pw g2p_en cn2an
|
| 114 |
huggingface-cli download Luigi/PrimeTTS --local-dir PrimeTTS
|
| 115 |
```
|
| 116 |
```python
|
| 117 |
+
import sys; sys.path.insert(0, "PrimeTTS/scripts")
|
|
|
|
| 118 |
import json, numpy as np, onnxruntime as ort, soundfile as sf
|
| 119 |
import frontend_bopomofo as F
|
| 120 |
+
from synth_from_text import host_regulate # numpy length-regulator
|
| 121 |
|
| 122 |
+
D = "PrimeTTS/v1b_8k" # the on-device model
|
| 123 |
+
meta = json.load(open(f"{D}/meta.json"))
|
| 124 |
+
enc = ort.InferenceSession(f"{D}/acoustic_encoder.onnx", providers=["CPUExecutionProvider"])
|
| 125 |
+
dec = ort.InferenceSession(f"{D}/acoustic_decoder.onnx", providers=["CPUExecutionProvider"])
|
| 126 |
+
voc = ort.InferenceSession(f"{D}/vocoder.onnx", providers=["CPUExecutionProvider"])
|
| 127 |
|
| 128 |
+
o = F.text_to_ids("您好,歡迎使用 PrimeTTS。Thank you for calling.")
|
| 129 |
ph, tn, lg = (np.array([o[k]], np.int64) for k in ("phone_ids", "tone_ids", "lang_ids"))
|
| 130 |
+
cond, dur, pitch = enc.run(None, {"phone": ph, "tone": tn, "lang": lg, "speaker": np.zeros(1, np.int64)})
|
|
|
|
| 131 |
reg = host_regulate(cond, dur, pitch, meta["abs_frame_bins"], meta["max_frames"])
|
| 132 |
mel = dec.run(None, {k: reg[k] for k in
|
| 133 |
["frames","frame_meta","local_ctx_raw","abs_pos","pitch_frame","frame_mask"]})[0]
|
| 134 |
wav = voc.run(None, {"mel": mel.astype(np.float32)})[0].reshape(-1)
|
| 135 |
sf.write("out.wav", wav, meta["sample_rate"])
|
| 136 |
```
|
| 137 |
+
The pipeline — `encoder → numpy length-regulator → decoder → vocoder` — is torch-free and runs as-is on a
|
| 138 |
+
Jetson Nano CPU. (`scripts/synth_long.py` adds the punctuation auto-chunking for long text.)
|
| 139 |
+
|
| 140 |
+
## Training
|
| 141 |
+
|
| 142 |
+
**Distilled from a single teacher voice** so zh / en / code-mix share one timbre and accent:
|
| 143 |
+
|
| 144 |
+
- **Reference voice** — a young Taiwan-female speaker from **Mozilla Common Voice zh-TW** (**CC0 / public
|
| 145 |
+
domain**, commercial-clear). ~13 s of the cleanest clips fix the accent (it comes from the *reference*, not
|
| 146 |
+
prompting) and keep the model shippable.
|
| 147 |
+
- **Teacher** — **VoxCPM2** (`openbmb/VoxCPM2`) voice-clones that reference for every line (48 kHz → resampled).
|
| 148 |
+
- **Corpus** — Taiwan office / phone / GPS / transit register: diverse Mandarin, general + domain English,
|
| 149 |
+
code-mix in varied positions, a large **named-entity bank** (TW + world places / roads / transit / companies
|
| 150 |
+
/ people / products), plus a **rare-character + brand + email booster** (the latest data lever).
|
| 151 |
+
- **ASR gate** — Breeze-ASR-25 (zh / mix CER) + Whisper-medium (en WER) keep only clips matching their text;
|
| 152 |
+
proper-noun coverage clips are trusted unfiltered.
|
| 153 |
+
|
| 154 |
+
The three levers that matter most for a tiny model: **phone-level alignment** (espeak phoneme-CTC +
|
| 155 |
+
`torchaudio.forced_align` — sub-syllable boundaries are what separate intelligible speech from fluent babble),
|
| 156 |
+
**broad coverage + diverse code-mix**, and the **teacher** (a student's English is only as native as its
|
| 157 |
+
teacher's). Pipeline: `teacher corpus → ASR gate → align → train vocoder → warm-start + train acoustic →
|
| 158 |
+
export`. The 8 kHz on-device model warm-starts the 24 kHz acoustic and adapts to 8 kHz — the trainer resamples
|
| 159 |
+
audio and rescales durations internally. Full commands and a one-shot `scripts/rebuild_voice.sh` (swap in your
|
| 160 |
+
own ~10 s reference clip) are in the repo.
|
| 161 |
+
|
| 162 |
+
## Known characteristics & limitations
|
| 163 |
+
|
| 164 |
+
- **8 kHz band ceiling** — telephone-band brightness (the 16 kHz variant in progress addresses it).
|
| 165 |
+
- **Empty-rime syllables** (是 / 十 / 日, the syllabic ㄭ) and **isolated spelled letters** (the leading "A" of a
|
| 166 |
+
serial) are the fragile cases at this size: the frontend emits the right phones, but a ~4M acoustic renders
|
| 167 |
+
them weakly. Cross-checking a robust (Breeze) vs strict (X-ASR) recognizer exposes this where a single CER
|
| 168 |
+
number hides it.
|
| 169 |
+
- **Phrase-initial bare vowels** in *ultra-short isolated* inputs ("二月" alone) can garble; fine in normal
|
| 170 |
+
sentences.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
## Credits & licenses
|
| 173 |
|
| 174 |
+
- **Base / trainer:** [`owensong/Inflect-Nano-v1`](https://huggingface.co/owensong/Inflect-Nano-v1) (Apache-2.0)
|
| 175 |
+
- **Teacher:** [`openbmb/VoxCPM2`](https://huggingface.co/openbmb/VoxCPM2) · **Reference voice:**
|
| 176 |
+
[Mozilla Common Voice zh-TW](https://commonvoice.mozilla.org/datasets) (**CC0 / public domain**)
|
| 177 |
+
- **Gate ASR:** Breeze-ASR-25 (MediaTek Research) · Whisper-medium · **Aligner:**
|
| 178 |
+
`facebook/wav2vec2-lv-60-espeak-cv-ft` + `torchaudio.forced_align` · **Eval:** sherpa-onnx X-ASR
|
| 179 |
|
| 180 |
+
This repository: **Apache-2.0**.
|