File size: 9,652 Bytes
7d0cf9c
 
 
 
 
 
 
 
 
 
 
 
 
 
44e90b5
 
 
143a7d0
 
 
 
 
 
44e90b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d0cf9c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44e90b5
 
 
7d0cf9c
44e90b5
 
 
 
 
7d0cf9c
44e90b5
 
143a7d0
 
44e90b5
 
 
7d0cf9c
143a7d0
44e90b5
7d0cf9c
44e90b5
 
 
 
 
 
 
 
7d0cf9c
 
 
44e90b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d0cf9c
44e90b5
 
7d0cf9c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
---
license: mit
language:
  - vi
library_name: onnx
pipeline_tag: text-to-speech
tags:
  - text-to-speech
  - tts
  - vietnamese
  - onnx
  - onnxruntime
  - zero-shot
  - speech-synthesis
  - voice-cloning
  - vietnamese-tts
  - tieng-viet
# NOTE deliberately NO `datasets:` field. It is the only thing that populates
# the Hub's cross-link, but the Hub renders it as "Models trained or fine-tuned
# on <dataset>" — which for our own held-out benchmark reads as train/test
# contamination. ZeroBench-TTS is EVALUATION data; every voice in it is held
# out of training. The `model-index` block below states that correctly, and the
# body links the benchmark in prose.
metrics:
  - wer
model-index:
  - name: ZeroTTS
    results:
      - task:
          type: text-to-speech
          name: Zero-Shot Text-to-Speech
        dataset:
          type: zeroweight-ai/ZeroBench-TTS
          name: ZeroBench-TTS
          split: test
        metrics:
          - type: wer
            value: 1.03
            name: WER (%)
          - type: utmos
            value: 2.91
            name: UTMOSv2 naturalness MOS
          - type: speaker_similarity
            value: 0.936
            name: Speaker similarity (WavLM-SV cosine)
          - type: excess_silence
            value: 0.029
            name: Excess silence (s)
      - task:
          type: text-to-speech
          name: Zero-Shot TTS  monolingual Vietnamese
        dataset:
          type: zeroweight-ai/ZeroBench-TTS
          name: ZeroBench-TTS (vietnamese)
          config: vietnamese
          split: test
        metrics:
          - type: wer
            value: 0.16
            name: WER (%)
      - task:
          type: text-to-speech
          name: Zero-Shot TTS  Vietnamese/English code-switching
        dataset:
          type: zeroweight-ai/ZeroBench-TTS
          name: ZeroBench-TTS (code_switch)
          config: code_switch
          split: test
        metrics:
          - type: wer
            value: 0.97
            name: WER (%)
      - task:
          type: text-to-speech
          name: Zero-Shot TTS  cross-lingual voice prompt
        dataset:
          type: zeroweight-ai/ZeroBench-TTS
          name: ZeroBench-TTS (cross_lingual)
          config: cross_lingual
          split: test
        metrics:
          - type: wer
            value: 1.42
            name: WER (%)
      - task:
          type: text-to-speech
          name: Zero-Shot TTS  acronyms, dates, numbers
        dataset:
          type: zeroweight-ai/ZeroBench-TTS
          name: ZeroBench-TTS (challenging)
          config: challenging
          split: test
        metrics:
          - type: wer
            value: 1.75
            name: WER (%)
---

# ZeroTTS

Vietnamese text-to-speech. The whole inference path is **numpy + ONNX Runtime** —
no PyTorch, no CUDA — so it runs on a laptop CPU or in a browser.

* Code, examples, browser demo: **https://github.com/zeroweight-ai/ZeroTTS**
* Benchmark dataset: **https://huggingface.co/datasets/zeroweight-ai/ZeroBench-TTS**

```python
pip install zerotts
```

```python
from zerotts import ZeroTTS

tts = ZeroTTS.from_pretrained("zeroweight-ai/ZeroTTS")
audio = tts.synthesize("Xin chào các bạn, mình là ZeroTTS.", voice="arya")
tts.save_audio(audio, "out.wav")
```

Streaming, with first audio in roughly 100 ms:

```python
for chunk in tts.synthesize_stream("Một đoạn văn bản dài hơn…", voice="arya"):
    play(chunk)   # (1, n) float32 at 48 kHz
```

## Benchmarks

Measured on **[ZeroBench-TTS](https://huggingface.co/datasets/zeroweight-ai/ZeroBench-TTS)**
— 137 items, 59 held-out reference voices, 4 subsets — against the two public
Vietnamese XTTS-v2 finetunes. 137/137 scored, 0 empty generations.

Scored by the benchmark's own published scorer (`zerobench_eval`), not by us.
WER is the minimum over two ASRs (`whisper-large-v3` + `PhoWhisper-large`) and
over every acceptable reading of the target text — see the
[benchmark README](https://huggingface.co/datasets/zeroweight-ai/ZeroBench-TTS)
for why both matter.

| | **ZeroTTS** | XTTS-v2-vietnamse | viXTTS |
|---|:-:|:-:|:-:|
| **WER — raw text** ↓ | **1.03%** | 16.42% | 18.40% |
| **WER — pre-normalized text** ↓ | **0.56%** | 7.27% | 8.61% |
| **Naturalness** (UTMOS) ↑ | **2.91** | 2.43 | 2.35 |
| **Voice similarity** (SSIM) ↑ | 0.936 | **0.940** | 0.935 |
| **Dead air** (excess silence) ↓ | **0.029 s** | 0.532 s | 0.233 s |

**16× fewer word errors** on the real task, **13× fewer** even after handing every model a perfect text frontend; ~0.5 MOS more natural, an order of magnitude less
dead air. Median WER is **0.00%** on all four subsets.

WER by subset:

| Subset | what it tests | **ZeroTTS** | XTTS-v2-vietnamse | viXTTS |
|---|---|:-:|:-:|:-:|
| `vietnamese` | plain Vietnamese | **0.16%** | 7.92% | 9.56% |
| `code_switch` | Vietnamese + embedded English | **0.97%** | 10.94% | 9.25% |
| `cross_lingual` | foreign voice prompt → Vietnamese | **1.42%** | 21.37% | 27.27% |
| `challenging` | acronyms, dates, %, currency | **1.75%** | 27.86% | 31.85% |

Reading these fairly:

* **Voice similarity is a tie, not a win** — 0.936 / 0.939 / 0.935 is within
  noise. On `cross_lingual` ZeroTTS is genuinely behind (0.911 vs ~0.935): it
  carries a foreign speaker's timbre into Vietnamese less faithfully than the
  XTTS backbone, while winning that subset's WER by 15×.
* **"The baselines just need text normalization" is testable, and fails.**
  Given the spoken-out text instead of raw orthography, XTTS improves to 7.27%
  and viXTTS to 8.61% — big gains, confirming their tokenizers lack Vietnamese
  number expansion — but ZeroTTS improves to 0.56%, so the gap stays 13–15×.
* **ZeroTTS's own remaining errors are known and listed**, not hidden:
  [evaluation/HIGH_WER_ANALYSIS.md](https://github.com/zeroweight-ai/ZeroTTS/blob/main/evaluation/HIGH_WER_ANALYSIS.md)
  audits every item above 0.00 — mostly voiced leading zeros in dates and
  `W`/`H` acronym letter names.

Score your own model on the same benchmark:

```bash
huggingface-cli download zeroweight-ai/ZeroBench-TTS --repo-type dataset --local-dir ZeroBench-TTS
cd ZeroBench-TTS && pip install -r zerobench_eval/requirements.txt
python -m zerobench_eval manifest --out manifest.jsonl    # what to synthesize
python -m zerobench_eval score --wav_dir my_wavs/ --name MyModel
```

Full tables and reproduction commands:
[evaluation/RESULTS.md](https://github.com/zeroweight-ai/ZeroTTS/blob/main/evaluation/RESULTS.md).

## Voices, and voice cloning

A voice is a small array of speaker latents, `(1, n_voice_queries, d_model)`,
shipped as a `.npz` under `voices/`. That array is the entire speaker
conditioning — no reference transcript, no audio prompt.

> **Voice cloning is not available in this release.** Those latents come from a
> voice encoder that reads a reference clip, and that encoder is not published.
> This repository ships ready-to-use voices; it cannot create new ones from
> audio.
>
> To get latents for your own speaker, see **[zeroweight.ai](https://zeroweight.ai)**
> or get in touch.

Because a voice is just an array, latents obtained that way drop into
`voices/<name>/voice.npz` and work with no code change.

## Repository layout

```
config.json                          runtime config
tokenizer.json                       BPE tokenizer
null_voice_emb.npy                   learned unconditional voice prefix
onnx/text_encoder.onnx               text → encoder states (once per utterance)
onnx/prefix_step.onnx                global transformer step (once per frame)
onnx/local_frame_decode.onnx         frame decode + sampling (once per frame)
onnx/codec/                          MOSS-Audio-Tokenizer-Nano decoder (Apache-2.0)
voices/<name>/voice.npz              speaker latents
```

fp32, not quantized: ~900 MB total. Two ONNX Runtime calls per audio frame;
frames are produced at 12.5 Hz and decoded to 48 kHz.

The model architecture, training code, and the ONNX export script are not
published, and the voice encoder is not included.

## Intended use and limitations

Built for **Vietnamese**. It handles English words embedded in Vietnamese text
(`code_switch`), but it is not an English TTS system and is not evaluated as one.

Do not use it to impersonate a real person, to generate speech attributed to
someone without their consent, or to produce audio intended to deceive. The
shipped voices are for evaluation and demos.

Synthetic speech should be disclosed as synthetic wherever a listener might
reasonably assume otherwise.

## Credits

Speech codec: **MOSS-Audio-Tokenizer-Nano** by the OpenMOSS team, Apache-2.0.
Its ONNX **decoder** graphs are redistributed under `onnx/codec/` so ZeroTTS has
no external runtime dependency; the encoder is not included. See
`onnx/codec/LICENSE-Apache-2.0.txt`.

```bibtex
@misc{gong2026mossaudiotokenizerscalingaudiotokenizers,
  title={MOSS-Audio-Tokenizer: Scaling Audio Tokenizers for Future Audio Foundation Models},
  author={Yitian Gong and Kuangwei Chen and Zhaoye Fei and Xiaogui Yang and Ke Chen
          and Yang Wang and Kexin Huang and Mingshu Chen and Ruixiao Li
          and Qingyuan Cheng and Shimin Li and Xipeng Qiu},
  year={2026}, eprint={2602.10934}, archivePrefix={arXiv}, primaryClass={cs.SD}
}
```

## License

ZeroTTS weights and code: **MIT**.
Bundled MOSS codec decoder: **Apache-2.0**.

The ZeroBench-TTS *dataset* is CC-BY-NC-4.0 because it redistributes reference
audio from VIVOS, viVoice, phoaudiobook and Emilia. That license applies to the
benchmark dataset only — **not** to these weights.