Skip to main content
ZeroTTS — Vietnamese zero-shot text-to-speech

ZeroTTS

Vietnamese Zero-Shot Text-to-Speech (TTS) with real-time streaming and voice cloning from seconds of audio. Fast, natural, and optimised for CPU inference.

PyPI License ONNX HuggingFace Blog

The most accurate open Vietnamese TTS we know of — 4× fewer word errors than the next open model, and it runs faster than real time on a laptop CPU.

  • 🗣️ Zero-shot voice cloning — cloned from as little as 3 seconds of reference audio (up to 30 seconds). No fine-tuning, no per-speaker training.
  • Real-time on CPU, streaming — ~2× faster than real time (RTF 0.5×), first audio chunk in ~70 ms. No GPU required.
  • 🇻🇳 Built for Vietnamese — tones, code-switched English, and reads words like 31/12/2026 and ZeroTTS without text normalizer.

Samples

Two-speaker conversation Long-form narration News read, code-switched English
▶ Two-speaker conversation (mp3) ▶ Long-form narration (mp3) ▶ News read, code-switched English (mp3)

Contents

  1. Samples
  2. Install
  3. Usage
  4. Voices — and voice cloning
  5. Benchmarks
  6. Web UI
  7. Browser demo
  8. How it works
  9. Credits

Install

pip install zerotts

That pulls numpy, onnxruntime, tokenizers, huggingface_hub, soundfile, scipy — and nothing else. Weights download from the Hub on first use (~900 MB, cached under HF_HOME).

Optional extras:

pip install "zerotts[webui]"   # Gradio demo
pip install "zerotts[eval]"    # benchmark scorers — these DO need torch

The eval extra is the only thing in this repo that installs PyTorch, and it is for measuring quality, not for generating audio.

Usage

Web UI

pip install "zerotts[webui]"
python webui/app.py                       # http://localhost:7860
python webui/app.py --model ./local_dir   # a local model directory

Voice picker, streaming playback, long-form segmentation, and the generation settings above.

Browser demo

js/ runs the same model client-side with onnxruntime-web — no server, no upload. See docs/BROWSER.md.

Note the download: the weights are fp32 and not quantized, so the demo fetches ~900 MB once and persists it (OPFS/Cache API). That is a deliberate quality-over-size choice; it targets desktop broadband, not mobile data.

Python

from zerotts import ZeroTTS

tts = ZeroTTS.from_pretrained("zeroweight-ai/ZeroTTS")

print(tts.list_voices())

# One shot
audio = tts.synthesize("Hôm nay trời đẹp quá.", voice="maichi")
tts.save_audio(audio, "out.wav")

# Streaming — first chunk arrives in ~70 ms
import queue

import numpy as np
import sounddevice as sd   # pip install sounddevice

TEXT = ("Đây là chế độ phát trực tuyến. Âm thanh được tạo ra và phát ngay lập tức, "
        "không cần chờ toàn bộ đoạn văn hoàn thành. Nhờ vậy, người nghe chỉ mất "
        "khoảng 70 mili giây là đã nghe thấy câu đầu tiên, ngay cả khi mô "
        "hình đang chạy trên CPU của một chiếc laptop bình thường.")

pending, tail = queue.Queue(), np.zeros(0, dtype="float32")

def feed(outdata, frames, _time, _status):
    global tail
    while len(tail) < frames and not pending.empty():
        tail = np.concatenate([tail, pending.get_nowait()])
    n = min(frames, len(tail))
    outdata[:n, 0] = tail[:n]
    outdata[n:] = 0
    tail = tail[n:]

with sd.OutputStream(samplerate=tts.sample_rate, channels=1,
                     dtype="float32", callback=feed):
    for chunk in tts.synthesize_stream(TEXT, voice="maichi"):
        pending.put(chunk.reshape(-1))   # chunk is (1, n) float32 at 48 kHz
    while not pending.empty() or len(tail):
        sd.sleep(50)                     # let the buffer drain before closing

Dates, clock times, fractions and acronyms are expanded to spoken Vietnamese before synthesis:

from zerotts import normalize_vi_text

normalize_vi_text("Ngày 23/8/2024 lúc 15h30, giá 1.250.000")
# 'Ngày hai mươi ba tháng tám năm hai nghìn không trăm hai mươi tư lúc
#  mười lăm giờ ba mươi phút, giá một triệu hai trăm năm mươi nghìn'

synthesize() does not apply it — it is a separate step so you stay in control (the expansions are Vietnamese words, so they are wrong for English text). The CLI and web UI apply it by default; zerotts say --no_text_norm turns it off.

Long input should be segmented — the model is trained on utterances, not paragraphs:

from zerotts.chunking import chunk_text, clean_segment_punctuation, normalize_punctuation

segments = [clean_segment_punctuation(s)
            for s in chunk_text(normalize_punctuation(long_text), max_chunk_sec=15)]

Command line

zerotts voices
zerotts say "Xin chào các bạn." --voice maichi -o hello.wav
zerotts say "$(cat article.txt)" --voice maichi --chunk -o article.wav
zerotts bench --voice maichi

Generation settings

Argument Default Effect
voice None Voice pack name. None = the model's unconditional voice, which is not stable across runs.
cfg_scale 1.0 >1 guides toward the voice's identity, at 2× the per-frame cost.
audio_temperature 0.8
audio_topk / audio_topp 25 / 0.95
audio_repetition_penalty 1.2 Benchmarked default. 1.0 measurably raises WER and leaves more dead air.
eoa_extra_frames 1 Frames of trailing audio kept after the model signals stop. 0 clips the last phone's release.

Defaults are the exact settings the benchmark numbers were produced with, so out-of-the-box output matches the published scores.

Voices — and voice cloning

A voice in ZeroTTS is a small array of speaker latents, shape (1, n_voice_queries, d_model). That array is the entire speaker conditioning — there is no reference transcript, no in-context audio prompt, no teacher-forced frames. It ships as a .npz inside the weights repo.

Voice cloning is not available in this release

Those latents are produced by a voice encoder that reads a reference clip, and the voice encoder is not published. This package can load voices; it cannot create them from audio. There is no flag that turns this on.

To get latents for your own speaker, visit zeroweight.ai or get in touch.

The boundary is narrower than it sounds: latents obtained that way are just a .npz, so they drop into voices/<name>/voice.npz and work with no code change.

Eight presets ship with the weights, each tagged by gender, age and register so you can pick one by ear or by filter — maichi (Mai Chi) is the default used throughout this README. Full list, tags, and preview clips: docs/VOICES.md.

tts.list_voices()                       # ['maichi', 'baotrang', ...]
v = tts.load_voice("maichi")
v.emb.shape                             # (1, 10, 768)
v.display_name, v.gender, v.tags        # 'Mai Chi', 'nữ', ['nữ', 'trẻ', 'kể chuyện', ...]

# A latent array from anywhere works directly
audio = tts.synthesize("…", voice=my_latents)

Benchmarks

Measured on ZeroBench-TTS

Every system reads raw text — dates, numbers and acronyms verbatim, exactly as they appear in the wild, with no text frontend in front of the model.

ZeroTTS OmniVoice XTTS-v2-vietnamse viXTTS
WER 1.03 % 4.13 % 16.42 % 18.40 %
Naturalness (UTMOS) ↑ 2.91 2.76 2.43 2.35
Voice similarity (SSIM) ↑ 0.936 0.950 0.940 0.935
Dead air (excess silence) ↓ 0.029 s 0.340 s 0.532 s 0.233 s
RTF, CPU 0.50× 6.12× 0.71× 0.73×
Time to first audio, CPU ~70 ms ~34 s ~6.1 s ~5.1 s
Parameters 202 M 775 M 467 M 467 M

4× fewer word errors than the next-best system, and the fastest of the four on CPU. The gap is much wider in latency than in throughput: the two XTTS fine-tunes also beat real time (0.71×) but need seconds to emit their first sample, while OmniVoice is 6× slower than real time. All three are sized and tuned for a GPU, and it shows.

Full comparison tables, per-subset breakdowns, and CPU speed methodology: docs/BENCHMARKS.md

Speed — CPU

RTF (realtime factor, wall-clock synthesis time ÷ output audio duration — lower is faster; below 1× is faster than real time) and time-to-first-audio, all measured on CPU, single request, 8 inference threads pinned to a dedicated core pool (no other synthesis running concurrently). Three Vietnamese samples — short (26 chars), medium (77 chars), long (227 chars) — each run 6 times with the first 2 (cold-cache) discarded; figures below are the mean of the remaining 4.

ZeroTTS OmniVoice XTTS-v2-vietnamse viXTTS
RTF — short 0.51× 10.87× 0.70× 0.71×
RTF — medium 0.47× 4.82× 0.70× 0.70×
RTF — long 0.53× 2.67× 0.71× 0.78×
TTFA — short 53 ms 21.7 s 4.02 s 2.45 s
TTFA — medium 66 ms 28.9 s 4.02 s 3.72 s
TTFA — long 89 ms 52.3 s 10.3 s 9.22 s

ZeroTTS's time-to-first-audio comes from its real streaming path — first audio frame, not first full utterance. The three baselines have no working CPU streaming path, so their TTFA is the time to the complete utterance.

Credits

Speech codec: MOSS-Audio-Tokenizer-Nano by the OpenMOSS team, Apache-2.0. ZeroTTS bundles its ONNX decoder graphs in the weights repo so there is no external runtime dependency; see NOTICE and LICENSES/.

@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}
}

Vietnamese text normalization adapts the expansion rules and abbreviation table of soe-vinorm (MIT), reimplemented as pure stdlib regex so the inference path keeps its no-torch, no-download guarantee. See NOTICE.

Benchmark reference audio comes from VIVOS, viVoice, phoaudiobook and Emilia. ASR scoring uses PhoWhisper (VinAI).

License

Code and weights: MIT. The bundled MOSS codec decoder is Apache-2.0. The ZeroBench-TTS dataset is CC-BY-NC-4.0 (it redistributes audio from the corpora above) — that applies to the benchmark, not to ZeroTTS.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zerotts-0.1.2.tar.gz (72.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zerotts-0.1.2-py3-none-any.whl (63.8 kB view details)

Uploaded Python 3

File details

Details for the file zerotts-0.1.2.tar.gz.

File metadata

  • Download URL: zerotts-0.1.2.tar.gz
  • Upload date:
  • Size: 72.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for zerotts-0.1.2.tar.gz
Algorithm Hash digest
SHA256 5ade0b13d498b24decd815dc51ccc3a7d0bf45e44dd6b6e359a9e1fbe18f97c1
MD5 736ad8facc014bbade11f4481f397ece
BLAKE2b-256 20b290ae91d13c1e999a42d112196b47d884595cb7c6903b8cd6c87e6073e866

See more details on using hashes here.

Provenance

The following attestation bundles were made for zerotts-0.1.2.tar.gz:

Publisher: publish-pypi.yml on zeroweight-ai/ZeroTTS

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zerotts-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: zerotts-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 63.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for zerotts-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7779bf2a12156d2dfe0c47046adbb4d98e08695e848ef69ecc46ebbe9ecc89d8
MD5 2c45c714223f020f5d3fc9dbd5bc5563
BLAKE2b-256 e8cdebce268dd3aeb0bf0d4d9ff1f41771a800ba8f53eb1b8baf3bf1c6589025

See more details on using hashes here.

Provenance

The following attestation bundles were made for zerotts-0.1.2-py3-none-any.whl:

Publisher: publish-pypi.yml on zeroweight-ai/ZeroTTS

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page