The base model files (S3Gen, VoiceEncoder, etc.) are from ResembleAI and must be downloaded separately:
python
1import os, requests
2from tqdm import tqdm
34DEST_DIR ="./pretrained_models"5os.makedirs(DEST_DIR, exist_ok=True)67BASE_FILES ={8"ve.safetensors":"https://huggingface.co/ResembleAI/chatterbox/resolve/main/ve.safetensors?download=true",9"t3_cfg.safetensors":"https://huggingface.co/ResembleAI/chatterbox/resolve/main/t3_cfg.safetensors?download=true",10"s3gen.safetensors":"https://huggingface.co/ResembleAI/chatterbox/resolve/main/s3gen.safetensors?download=true",11"conds.pt":"https://huggingface.co/ResembleAI/chatterbox/resolve/main/conds.pt?download=true",12}1314for fname, url in BASE_FILES.items():15 dest = os.path.join(DEST_DIR, fname)16ifnot os.path.exists(dest):17 r = requests.get(url, stream=True)18withopen(dest,"wb")as f:19for chunk in r.iter_content(1024*1024):20 f.write(chunk)21print(f"Downloaded {fname}")2223# Copy the Bangla tokenizer into pretrained_models24import shutil
25shutil.copy(tokenizer_path, os.path.join(DEST_DIR,"tokenizer.json"))26print("Tokenizer ready")
Run inference
python
1import torch
2import soundfile as sf
3import numpy as np
4from safetensors.torch import load_file
5from chatterbox.tts import ChatterboxTTS
6from chatterbox.models.t3.t3 import T3
78DEVICE ="cuda"if torch.cuda.is_available()else"cpu"9BASE_MODEL_DIR ="./pretrained_models"10FINETUNED_WEIGHTS = weights_path # from hf_hub_download11AUDIO_PROMPT ="./your_reference.wav"# 3–6 sec clean Bangla speech12NEW_VOCAB_SIZE =42401314# Load base engine15tts = ChatterboxTTS.from_local(BASE_MODEL_DIR, device="cpu")1617# Rebuild T3 with extended Bangla vocab18t3_cfg = tts.t3.hp
19t3_cfg.text_tokens_dict_size = NEW_VOCAB_SIZE
20new_t3 = T3(hp=t3_cfg)2122# Load fine-tuned weights (strip HF Trainer wrapper prefix if present)23state_dict = load_file(FINETUNED_WEIGHTS, device="cpu")24ifany(k.startswith("t3.")for k in state_dict):25 state_dict ={k[len("t3."):]: v for k, v in state_dict.items()if k.startswith("t3.")}26new_t3.load_state_dict(state_dict, strict=True)2728# Swap T3 into engine and move to device29tts.t3 = new_t3
30tts.t3.to(DEVICE).eval()31tts.s3gen.to(DEVICE).eval()32tts.ve.to(DEVICE).eval()33tts.device = DEVICE
3435# Generate speech36text ="আমাদের গ্রাহক সেবায় আপনাকে স্বাগতম। আপনার যেকোনো সমস্যায় আমরা সাহায্য করতে প্রস্তুত।"3738wav = tts.generate(39 text=text,40 audio_prompt_path=AUDIO_PROMPT,41 temperature=0.3,42 exaggeration=0.5,43 cfg_weight=0.5,44 repetition_penalty=1.2,45 min_new_tokens=150,46)4748sf.write("output.wav", wav.squeeze().cpu().numpy(), tts.sr)49print("Saved to output.wav")
Splitting long text into sentences (better quality)
python
1import re, numpy as np, soundfile as sf
23defsynthesize_long_text(tts, text, audio_prompt, output_path,**params):4# Split on sentence boundaries including Bengali danda (।)5 sentences = re.split(r'(?<=[.?!।])\s*', text.strip())6 sentences =[s.strip()for s in sentences if s.strip()]78 chunks =[]9 sr = tts.sr
1011for i, sent inenumerate(sentences):12print(f"[{i+1}/{len(sentences)}] {sent}")13 wav = tts.generate(text=sent, audio_prompt_path=audio_prompt,**params)14 chunks.append(wav.squeeze().cpu().numpy())15 chunks.append(np.zeros(int(sr *0.25)))# 250ms pause between sentences1617 final = np.concatenate(chunks)18 sf.write(output_path, final, sr)19print(f"Saved to {output_path}")2021synthesize_long_text(22 tts,23 text="আজকের আবহাওয়া বেশ সুন্দর। আমি বাজার থেকে তাজা সবজি কিনে এনেছি। রাতের খাবারে ভাত আর মাছের তরকারি রান্না হবে।",24 audio_prompt=AUDIO_PROMPT,25 output_path="output_long.wav",26 temperature=0.3,27 exaggeration=0.5,28 cfg_weight=0.5,29 repetition_penalty=1.2,30 min_new_tokens=150,31)
Reference audio tips:
Use a 3–6 second clean Bangla speech clip from the target speaker
Mono WAV, no background noise or music
The model clones the voice style from this reference — quality depends heavily on it
Full Training Pipeline
This section covers how to reproduce this fine-tuned model from scratch, or adapt it further for your own Bangla dataset.
bn_001|আমি বাংলায় কথা বলছি।|আমি বাংলায় কথা বলছি।
bn_002|আজকের আবহাওয়া সুন্দর।|আজকের আবহাওয়া সুন্দর।
bn_003|আপনাকে স্বাগতম।|আপনাকে স্বাগতম।
Format: ID|RawText|NormalizedText
Audio requirements:
Format: WAV, mono
Sample rate: 22050 Hz or 24000 Hz (auto-resampled during preprocessing)
Duration per clip: 2–12 seconds (shorter clips train better)
Clean speech, no background noise or music
Recommended dataset size: 2–10 hours
Recommended: normalize your audio first:
bash
1# Normalize loudness to -23 LUFS using ffmpeg2forfin MyTTSDataset/wavs/*.wav;do3 ffmpeg -i "$f" -af loudnorm=I=-23:LRA=7:TP=-2 "${f%.wav}_norm.wav" -y
4done
Step 4: Bangla Tokenizer Adaptation
The base Chatterbox tokenizer does not contain Bangla Unicode characters. This step extends it.
You need an XTTS vocab.json that already contains Bangla tokens. You can get one from a pre-existing XTTS Bangla model, or use the one in this repo.
bash
1# Place your XTTS vocab.json at ./xtts_vocab.json, then run:2python add_bangla_tokens.py
What this script does:
Loads the existing Chatterbox tokenizer.json
Extracts all Bangla Unicode characters (U+0980–U+09FF) and BPE subwords from xtts_vocab.json
Appends them to the Chatterbox tokenizer with new sequential IDs
Adds Bengali dari । as a punctuation token
Adds BPE merge rules for Bengali subwords
Saves the extended tokenizer back to pretrained_models/tokenizer.json
Output:
SUCCESS! Added 1240 new tokens
New vocab size: 4240
*** IMPORTANT: Update new_vocab_size in src/config.py to: 4240 ***
Update src/config.py with the printed vocab size:
new_vocab_size: int = 4240 # <- update this to match the printed value
Step 5: Configure Training
Edit src/config.py:
python
1from dataclasses import dataclass
23@dataclass4classTrainConfig:5# --- Paths ---6 model_dir:str="./pretrained_models"7 csv_path:str="./MyTTSDataset/metadata.csv"8 wav_dir:str="./MyTTSDataset/wavs"9 preprocessed_dir:str="./MyTTSDataset/preprocess"10 output_dir:str="./chatterbox_output"1112# --- Mode ---13 ljspeech:bool=True# True = LJSpeech CSV format14 json_format:bool=False# True = JSON format15 preprocess:bool=True# Set False after first run16 is_turbo:bool=False# False = normal Chatterbox, True = Turbo1718# --- Vocab (must match add_bangla_tokens.py output) ---19 new_vocab_size:int=42402021# --- Hyperparameters ---22 batch_size:int=4# adjust for your GPU VRAM23 grad_accum:int=2# effective batch = batch_size × grad_accum24 learning_rate:float=5e-6# keep low — T3 is sensitive25 num_epochs:int=502627 save_steps:int=200028 save_total_limit:int=32930# --- Constraints ---31 max_text_len:int=25632 max_speech_len:int=850# truncates clips longer than ~8s33 prompt_duration:float=3.0# reference audio duration (seconds)
Batch size guide by VRAM:
VRAM
batch_size
grad_accum
Effective batch
8 GB
2
4
8
16 GB
4
2
8
24 GB
8
1
8
40 GB
16
1
16
80 GB (H100)
24
1
24
Step 6: Preprocess Dataset
Preprocessing encodes every audio clip into discrete speech tokens (S3 codes) and saves them as .pt files. This only needs to be run once — subsequent training runs skip it.
bash
1# First run — preprocessing is ON by default (preprocess=True in config)2python train.py
The preprocessor will:
Load each WAV file from wav_dir
Resample to 24000 Hz if needed
Extract a 3-second voice conditioning prompt from the start
Encode audio → S3 speech tokens using S3Gen
Tokenize text → token IDs using the extended tokenizer
Save each sample as a .pt file in preprocessed_dir
After preprocessing completes, set preprocess = False in src/config.py to skip it on future runs.
Step 7: Train the Model
bash
1# Make sure preprocess=False if you've already preprocessed2python train.py
What train.py does internally:
python
1# 1. Load original Chatterbox T3 weights2tts = ChatterboxTTS.from_local(cfg.model_dir, device="cpu")34# 2. Create a new T3 with the extended Bangla vocab size5t3_cfg = tts.t3.hp
6t3_cfg.text_tokens_dict_size = cfg.new_vocab_size # e.g. 42407new_t3 = T3(hp=t3_cfg)89# 3. Transfer all original weights; randomly init only the new embedding rows10new_t3 = resize_and_load_t3_weights(new_t3, tts.t3.state_dict())1112# 4. Freeze S3Gen and VoiceEncoder — only T3 trains13for param in tts.s3gen.parameters(): param.requires_grad =False14for param in tts.ve.parameters(): param.requires_grad =False15for param in new_t3.parameters(): param.requires_grad =True1617# 5. HuggingFace Trainer with cosine LR, weight decay, gradient checkpointing18trainer = Trainer(19 model=ChatterboxTrainerWrapper(new_t3),20 args=TrainingArguments(21 learning_rate=5e-6,22 lr_scheduler_type="cosine",23 warmup_ratio=0.05,24 weight_decay=0.01,25 bf16=True,26 gradient_checkpointing=True,27...28),29)3031# 6. Auto-resume from latest checkpoint if one exists32trainer.train(resume_from_checkpoint=last_ckpt)
Monitoring training with TensorBoard:
tensorboard --logdir ./chatterbox_output
Training auto-resumes from the latest checkpoint in ./chatterbox_output/ if you stop and restart.
Step 8: Train on Modal (Cloud — Recommended)
For H100 training (~10× faster than a 24GB GPU):
Install Modal:
bash
1pip install modal
2modal setup # opens browser for authentication
1# Upload metadata CSV2modal volume put xtts-finetune-data ./MyTTSDataset/metadata.csv dataset/metadata_train.csv
34# Upload WAV files (use a loop for large datasets)5modal volume put xtts-finetune-data ./MyTTSDataset/wavs/ dataset/wavs/
1# Launch (detached — runs in background)2python -m modal run --detach train_modal.py
34# Monitor logs5modal app logs <app-id>
Download a checkpoint after training:
bash
1# List available checkpoints2python -m modal volume ls chatterbox-v2-output
34# Download a specific checkpoint's weights5python -m modal volume get chatterbox-v2-output \6 checkpoint-456000/model.safetensors \7 ./chatterbox_output/checkpoint-456000_model.safetensors
H100 training speed reference:
Dataset size
Batch
Steps/epoch
Time to 500k steps
5h (~8k clips)
24
~333
~12 hours
10h (~16k clips)
24
~667
~24 hours
Step 9: Export Checkpoint for Inference
HuggingFace Trainer saves full checkpoints as checkpoint-XXXXXX/model.safetensors inside output_dir. These are the T3 weights wrapped with a t3. key prefix.
The inference script handles this automatically:
python
1state_dict = load_file(weights_path, device="cpu")2# Strip HF Trainer wrapper prefix3ifany(k.startswith("t3.")for k in state_dict):4 state_dict ={k[len("t3."):]: v for k, v in state_dict.items()if k.startswith("t3.")}5new_t3.load_state_dict(state_dict, strict=True)
You can also flatten the checkpoint for distribution:
python
1from safetensors.torch import load_file, save_file
23state_dict = load_file("./chatterbox_output/checkpoint-456000/model.safetensors")4# Strip prefix5state_dict ={k[len("t3."):]: v for k, v in state_dict.items()if k.startswith("t3.")}6save_file(state_dict,"./t3_bangla_456k_clean.safetensors")
Hyperparameter Reference
Training
Parameter
Value used
Notes
learning_rate
5e-6
Lower than typical LLM fine-tuning — T3 is sensitive
lr_scheduler_type
cosine
Smooth decay, better than constant LR
warmup_ratio
0.05
5% of total steps as warmup
weight_decay
0.01
L2 regularization against overfitting
bf16
True
Faster on A100/H100; use fp16=True on older GPUs
gradient_checkpointing
True
Saves ~40% VRAM at ~20% speed cost
batch_size
24 (H100)
Scale down for smaller GPUs
Inference
Parameter
Recommended
Notes
temperature
0.3
Lower = more stable Bangla; higher = more expressive
exaggeration
0.5
Voice style intensity (0 = neutral, 1 = strong)
cfg_weight
0.5
Classifier-free guidance strength
repetition_penalty
1.2
Reduces token repetition loops
min_new_tokens
150
Prevents early truncation of speech
Troubleshooting
Garbage audio / no speech from a later checkpoint:
This is overfitting. Quality typically peaks around 400k–500k steps for a 5h dataset. Beyond that the model degrades.
Use an earlier checkpoint (checkpoint-456000 recommended over checkpoint-888000 for this training run).
To prevent this: add load_best_model_at_end=True with a validation split in TrainingArguments.
KeyError or size mismatch when loading weights:
Ensure new_vocab_size in src/config.py exactly matches the number printed by add_bangla_tokens.py.
If using HF Trainer checkpoint, make sure the t3. prefix stripping code is applied.
Out of memory (OOM) during training:
Reduce batch_size by half and double grad_accum to keep effective batch size the same.
Enable gradient_checkpointing=True (already on by default).
Preprocessing is very slow:
Normal — encoding audio to S3 codes runs on CPU by default if no GPU is available.
On GPU it takes ~1–2 hours for 10h of audio; on CPU expect 4–8 hours.
Set preprocess=False after the first run to skip it.
Reference audio not matching voice:
Reference audio must be clean and at least 3 seconds long.
The speaker in the reference should ideally match the training speaker.
Try recording a new reference with the same mic/conditions as your training data.