Luigi commited on
Commit
a0e4648
·
verified ·
1 Parent(s): 113e732

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +141 -172
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
- voice across Chinese, English, and code-mix through a single frontend (no language routing), with
28
- **entity correctness** — phone numbers, emails, addresses, prices, dates, temperatures, %, serials.
29
 
30
- Two model generations:
31
 
32
- - **`v2_mbistft_16k/` — PrimeTTS v2 (34.7M, 16 kHz) current flagship.** End-to-end
33
- **MB-iSTFT-VITS** targeting the Jetson Nano **GPU** (and any CPU via ONNX). Best quality and
34
- intelligibility of the family; female Mandarin voice ("Xinran").
35
- - **`v1b_16k/` / `v1b_8k/` — PrimeTTS v1 (~5.0M / 4.09M).** FastSpeech + Snake-HiFiGAN, pure-**CPU**,
36
- young-female zh-TW voice; `v1b_8k` reaches **RTF 0.35 on a Jetson Nano CPU** (1 thread). Use v1
37
- when the deployment budget is CPU-only and tight.
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- > 🔊 **Live demo (serves v2 + v1):** https://huggingface.co/spaces/Luigi/PrimeTTS-vs-Inflect-Nano-v1
 
 
40
 
41
- ## PrimeTTS v2 (`v2_mbistft_16k/`)
42
 
43
- | | PrimeTTS v2 |
44
- |---|---|
45
- | **Architecture** | MB-iSTFT-VITS (end-to-end VAE + flow + adversarial; multi-band iSTFT head; conv-only, no LSTM) |
46
- | **Parameters** | 34.7M (generator) |
47
- | **Sample rate** | 16 kHz |
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 on a Jetson Nano gen-1, Tegra X1)
53
 
54
- Two runtime tiers, both real-time. RTF = compute-time / audio-time (lower is faster; <1.0 = real-time).
 
55
 
56
- | Tier | Runtime | Precision | RTF | Quality |
57
  |---|---|---|---|---|
58
- | **GPU** | RapidSpeech.cpp ggml-CUDA, 1 CPU thread | fp32 | **0.42** (2.4× RT) | full (parity 0.9998) |
59
- | **CPU** *(default)* | onnxruntime, 2 threads | **fp16** | **0.77** (1.3× RT) | full — voice-cos 0.915, CER 0.032, 55 MB |
60
- | **CPU** | onnxruntime, 4 threads | fp32 | **0.52** (1.9× RT) | full — voice-cos 0.916, CER 0.033 |
 
 
 
 
 
 
 
61
 
62
- Notes: both tiers are full-fidelity, real-time, and need no GPU. The GPU RTF is launch-overhead-bound on Maxwell (sm_53, no CUDA-graph replay) — 0.42 is the practical floor. **fp16** is the shipped CPU default: half the size, lossless, and leaves cores free (on this ARMv8.0 CPU it casts to fp32 so it's not *faster* than fp32 — the size/headroom is the win). **On int8:** static-int8 was fast but shifted the voice (voice-cos 0.748); dynamic-int8 preserves the voice (0.975) but runs *slower* than fp32 on this core (no dot-product / no FP16 arithmetic on the Cortex-A57). So int8 is offered only as a smaller *download* (`quantize_dynamic`, QUInt8) — not a speed tier. The on-device speed lever is a **smaller model**, not quantization.
 
 
63
 
64
- **Training:** distilled from a **VibeVoice-Large** (MIT) teacher speaking the `zh-Xinran_woman` preset —
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 frontend_bopomofo as F
 
74
 
75
- sess = ort.InferenceSession("PrimeTTS/v2_mbistft_16k/primetts_v2_xinran.onnx",
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) # add_blank=true
79
- wav = sess.run(None, {"x": blank(o["phone_ids"]), "tone": blank(o["tone_ids"]),
80
- "lang": blank(o["lang_ids"]),
81
- "x_lengths": np.array([2*len(o["phone_ids"])+1], np.int64),
82
- "noise_scale": np.array([0.667], np.float32),
83
- "length_scale": np.array([1.0], np.float32)})[0].reshape(-1)
 
 
84
  sf.write("out.wav", wav, 16000)
85
  ```
86
 
87
  ---
88
 
89
- # PrimeTTS v1 (legacy CPU family)
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
- **Frontend.** `g2pw` (Taiwan bopomofo + polyphone disambiguation) + `g2p_en` (arpabet), merged into one phone
155
- sequence with per-phone language ids zh, en, code-mix in a single pass. **88-symbol table.** Entity
156
- normalization (`text_norm.py`) handles numbers / dates / prices / emails / serials, spells ALL-CAPS acronyms
157
- and a small brand lexicon. Text past `max_frames` is auto-chunked at punctuation.
158
 
159
- ## Model files
160
-
161
- ```
162
- v2_mbistft_16k/primetts_v2_xinran.onnx ← PrimeTTS v2 FLAGSHIP fp32 (34.7M, 16 kHz) full quality, the demo serves this
163
- v2_mbistft_16k/primetts_v2_xinran.gguf ← fp32 weights for the ggml-CUDA Jetson-Nano runtime (RapidSpeech.cpp)
164
- v2_mbistft_16k/primetts_v2_xinran_fp16.onnx ← fp16 ONNX (55 MB, half size, lossless) — the shipped CPU default (@2 threads)
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
- ## Quickstart (CPU)
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 # numpy length-regulator
183
 
184
- D = "PrimeTTS/v1b_16k" # the flagship (use v1b_8k for the leanest Nano RTF)
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
- The pipeline — `encoder → numpy length-regulator → decoder → vocoder` is torch-free and runs as-is on a
200
- Jetson Nano CPU. (`scripts/synth_long.py` adds the punctuation auto-chunking for long text.)
201
-
202
- ## Training
203
-
204
- **Distilled from a single teacher voice** so zh / en / code-mix share one timbre and accent:
205
-
206
- - **Reference voice** — a young Taiwan-female speaker from **Mozilla Common Voice zh-TW** (**CC0 / public
207
- domain**, commercial-clear). ~13 s of the cleanest clips fix the accent (it comes from the *reference*, not
208
- prompting) and keep the model shippable.
209
- - **Teacher** — **VoxCPM2** (`openbmb/VoxCPM2`) voice-clones that reference for every line (48 kHz → resampled).
210
- - **Corpus** — Taiwan office / phone / GPS / transit register: diverse Mandarin, general + domain English,
211
- code-mix in varied positions, a large **named-entity bank** (TW + world places / roads / transit / companies
212
- / people / products), plus a **rare-character + brand + email booster** (the latest data lever).
213
- - **ASR gate** Breeze-ASR-25 (zh / mix CER) + Whisper-medium (en WER) keep only clips matching their text;
214
- proper-noun coverage clips are trusted unfiltered.
215
-
216
- The three levers that matter most for a tiny model: **phone-level alignment** (espeak phoneme-CTC +
217
- `torchaudio.forced_align` — sub-syllable boundaries are what separate intelligible speech from fluent babble),
218
- **broad coverage + diverse code-mix**, and the **teacher** (a student's English is only as native as its
219
- teacher's). Pipeline: `teacher corpus ASR gate → align → train vocoder → warm-start + train acoustic →
220
- export`. The 8 kHz on-device model warm-starts the 24 kHz acoustic and adapts to 8 kHz — the trainer resamples
221
- audio and rescales durations internally. Full commands and a one-shot `scripts/rebuild_voice.sh` (swap in your
222
- own ~10 s reference clip) are in the repo.
223
-
224
- ## Known characteristics & limitations
225
-
226
- - **8 kHz checkpoint is telephone-band** (4 kHz ceiling) — use the **16 kHz flagship** for full brightness.
227
- - **Empty-rime syllables** (是 / 十 / 日, the syllabic ㄭ) and **isolated spelled letters** (the leading "A" of a
228
- serial) are the fragile cases at this size: the frontend emits the right phones, but a ~4M acoustic renders
229
- them weakly. Cross-checking a robust (Breeze) vs strict (X-ASR) recognizer exposes this where a single CER
230
- number hides it.
231
- - **Phrase-initial bare vowels** in *ultra-short isolated* inputs ("二月" alone) can garble; fine in normal
232
- sentences.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- · **v2 teacher:** VibeVoice-Large (Microsoft, **MIT**) speaking its `zh-Xinran_woman` preset
238
- (via the MIT [community repo](https://github.com/vibevoice-community/VibeVoice)); synthesized speech,
239
- AI-generated voice mark it as such in products
240
- - **v1 base / trainer:** [`owensong/Inflect-Nano-v1`](https://huggingface.co/owensong/Inflect-Nano-v1) (Apache-2.0)
241
- - **v1 teacher:** [`openbmb/VoxCPM2`](https://huggingface.co/openbmb/VoxCPM2) · **v1 reference voice:**
242
- [Mozilla Common Voice zh-TW](https://commonvoice.mozilla.org/datasets) (**CC0 / public domain**)
243
- - **Gate ASR:** Breeze-ASR-25 (MediaTek Research) · Whisper-medium · **Aligner:**
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**.