A Vietnamese text-to-speech model fine-tuned from
pnnbao-ump/VieNeu-TTS on ~80k segments of Vietnamese audiobook speech.
1# System packages (Ubuntu/Debian)
2sudo apt-get install -y build-essential cmake libsndfile1 ffmpeg
3
4# Install Rust (needed for sea-g2p phonemizer)
5curl https://sh.rustup.rs -sSf | sh -s -- -y
6source ~/.cargo/env
7
8# Python packages
9pip install vieneu soundfile numpy
Choose one of the methods below depending on your hardware.
Best for GPU machines. Uses the full BF16 model (1.1 GB).
1from vieneu import Vieneu
2import soundfile as sf
3
4# Load the fine-tuned model (auto-downloads from HF Hub)
5tts = Vieneu(backbone_repo="nguyen-brat/VieNeu-TTS-Vietnamese-Finetuned")
6
7# Generate speech
8audio = tts.infer("Xin chao, day la giong noi tieng Viet duoc tong hop bang AI.")
9sf.write("output.wav", audio, samplerate=24000)
10print("Saved output.wav")
1import torch
2from vieneu import Vieneu
3
4device = "cuda" if torch.cuda.is_available() else "cpu"
5tts = Vieneu(
6 backbone_repo="nguyen-brat/VieNeu-TTS-Vietnamese-Finetuned",
7 backbone_device=device,
8 codec_device=device,
9)
10audio = tts.infer("Xin chao the gioi!")
Best for machines without a GPU, or for faster inference with quantized weights (441 MB).
3x faster than PyTorch on GPU, and runs well on CPU.
1# Extra dependency for GGUF
2pip install llama-cpp-python transformers huggingface-hub
1from huggingface_hub import hf_hub_download
2from llama_cpp import Llama
3from transformers import AutoTokenizer
4from vieneu import Vieneu
5import soundfile as sf
6import numpy as np
7
8# --- Config ---
9REPO = "nguyen-brat/VieNeu-TTS-Vietnamese-Finetuned"
10SPEECH_OFFSET = 151671 # <|speech_0|> token ID
11SPEECH_END = 151670 # <|SPEECH_GENERATION_END|> token ID
12SPEECH_MAX = 65535
13
14# --- Step 1: Load model components ---
15gguf_path = hf_hub_download(repo_id=REPO, filename="vieneu_q4_k_m.gguf")
16
17# n_gpu_layers=-1 for full GPU offload, 0 for CPU-only
18llm = Llama(model_path=gguf_path, n_gpu_layers=0, n_ctx=32768, verbose=False)
19tokenizer = AutoTokenizer.from_pretrained(REPO)
20vieneu = Vieneu(backbone_repo=REPO) # for codec + phonemizer + voices
21
22# --- Step 2: Prepare input ---
23text = "Xin chao, day la giong noi tieng Viet."
24
25# Get a preset voice (or use ref_audio for voice cloning)
26voice = vieneu.get_preset_voice() # uses default voice
27ref_codes = voice["codes"]
28ref_text = voice.get("text", "")
29
30# Normalize and phonemize
31from vieneu_utils.phonemize_text import phonemize_with_dict
32text_normalized = vieneu.normalizer.normalize(text)
33ref_phonemes = vieneu.get_ref_phonemes(ref_text)
34chunk_phonemes = phonemize_with_dict(text_normalized, skip_normalize=True)
35
36# --- Step 3: Build prompt ---
37codes_str = "".join(f"<|speech_{int(c)}|>" for c in ref_codes.flatten())
38prompt = (
39 f"user: Convert the text to speech:"
40 f"<|TEXT_PROMPT_START|>{ref_phonemes} {chunk_phonemes}<|TEXT_PROMPT_END|>\n"
41 f"assistant:<|SPEECH_GENERATION_START|>{codes_str}"
42)
43prompt_ids = tokenizer.encode(prompt, add_special_tokens=False)
44
45# --- Step 4: Generate speech tokens ---
46speech_ids = []
47for token_id in llm.generate(prompt_ids, top_k=50, temp=1.0, reset=True):
48 # Ignore end token until at least 50 speech tokens collected
49 if token_id == SPEECH_END and len(speech_ids) >= 50:
50 break
51 if SPEECH_OFFSET <= token_id <= SPEECH_OFFSET + SPEECH_MAX:
52 speech_ids.append(token_id)
53 if len(speech_ids) >= 16000:
54 break
55
56# --- Step 5: Decode to audio ---
57decode_str = "".join(f"<|speech_{tid - SPEECH_OFFSET}|>" for tid in speech_ids)
58audio = vieneu._decode(decode_str)
59sf.write("output.wav", audio, samplerate=24000)
60print(f"Generated {len(audio)/24000:.1f}s of audio ({len(speech_ids)} tokens)")
1from vieneu import Vieneu
2import soundfile as sf
3
4tts = Vieneu(backbone_repo="nguyen-brat/VieNeu-TTS-Vietnamese-Finetuned")
5
6audio = tts.infer(
7 text="Ngay xua, co mot chu be ten la An song trong mot ngoi lang nho ven song.",
8 ref_audio="reference.wav", # 3-10 second WAV of target speaker
9 ref_text="Transcript of reference.", # optional but improves quality
10)
11sf.write("cloned.wav", audio, samplerate=24000)
1from vieneu import Vieneu
2
3tts = Vieneu(backbone_repo="nguyen-brat/VieNeu-TTS-Vietnamese-Finetuned")
4
5# List available voices
6for display_name, key in tts.list_preset_voices():
7 print(f" {key}: {display_name}")
8
9# Use a specific preset
10voice = tts.get_preset_voice("binh") # binh | tuyen | nguyen | huong | ngoc | doan
11audio = tts.infer(text="Xin chao!", voice=voice)
For texts longer than ~250 characters, split into chunks with automatic pause insertion:
1from vieneu import Vieneu
2import soundfile as sf
3
4tts = Vieneu(backbone_repo="nguyen-brat/VieNeu-TTS-Vietnamese-Finetuned")
5
6story = """Ngay xua, co mot chu be ten la An song trong mot ngoi lang nho ven song.
7
8Moi sang, An thuong day som de giup me ganh nuoc va nau com.
9Cau thich nhat la duoc chay ra bo song, ngoi ngam nhung con ca boi loi duoi lan nuoc trong vat."""
10
11# Synthesize each chunk (max 100 chars) with pauses between sentences/paragraphs
12chunks = []
13for i in range(0, len(story), 200):
14 chunk = story[i:i+200].strip()
15 if chunk:
16 audio = tts.infer(chunk)
17 chunks.append(audio)
18
19import numpy as np
20silence = np.zeros(int(24000 * 0.3)) # 300ms pause
21full_audio = np.concatenate([np.concatenate([c, silence]) for c in chunks])
22sf.write("story.wav", full_audio, samplerate=24000)
Apache 2.0 — see base model
pnnbao-ump/VieNeu-TTS for upstream licensing.