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 clone your own voice, head to platform.zeroweight.ai/audio, upload a reference clip, and download the resulting latents.

The boundary is narrower than it sounds: latents obtained that way are just a .npz. The platform's voice library hands you one zip per voice, and every surface here takes that file as it came down — no unzipping, no copying:

tts.add_voices("my-voice.zip")   # once, ever
tts.synthesize("Xin chào.", voice="my-voice")

Both demos have it as the voice picker's second tab, Nhập giọng của bạn — drop the zip on it and the voice is installed, selected, and there again next time. The Gradio server can start with one: python webui/app.py --voice ~/Downloads/my-voice.zip. Details and the pack format: docs/VOICES.md.

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.

Metadata

Release files for zerotts 0.1.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zerotts 0.1.4
File Size Uploaded Provenance
zerotts-0.1.4.tar.gz 78.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for zerotts 0.1.4
File Interpreter ABI Platform
zerotts-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 146.0 kB

Release files / zerotts-0.1.4.tar.gz

Download URL zerotts-0.1.4.tar.gz
Size 78.6 kB
Tags Source
SHA-256 checksum
How to use checksums
0a591ae3bd0298af389eb6d9ddfedebafc372431f6b9954b4e7c25a444078a32
BLAKE2b-256 checksum
How to use checksums
9f7254a7b95bf0e1d2173534105809ffa35c3f44f400e2bb9275b6b364be4e8e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / zerotts-0.1.4-py3-none-any.whl

Download URL zerotts-0.1.4-py3-none-any.whl
Size 67.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
66c275640fa9405171e38b69cc2d3fa247b1f104346dd1f9133e6fff6366693a
BLAKE2b-256 checksum
How to use checksums
d7c5b63573fe24f3b854c9ae85b16eb4d13b2bb8ad336b4a9d945233179c391a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Security for zerotts 0.1.4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for zerotts-0.1.4.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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