Instructions to use Rabe3/VoxCPM_mid_najdi with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- VoxCPM
How to use Rabe3/VoxCPM_mid_najdi with VoxCPM:
import soundfile as sf from voxcpm import VoxCPM model = VoxCPM.from_pretrained("Rabe3/VoxCPM_mid_najdi") wav = model.generate( text="VoxCPM is an innovative end-to-end TTS model from ModelBest, designed to generate highly expressive speech.", prompt_wav_path=None, # optional: path to a prompt speech for voice cloning prompt_text=None, # optional: reference text cfg_value=2.0, # LM guidance on LocDiT, higher for better adherence to the prompt, but maybe worse inference_timesteps=10, # LocDiT inference timesteps, higher for better result, lower for fast speed normalize=True, # enable external TN tool denoise=True, # enable external Denoise tool retry_badcase=True, # enable retrying mode for some bad cases (unstoppable) retry_badcase_max_times=3, # maximum retrying times retry_badcase_ratio_threshold=6.0, # maximum length restriction for bad case detection (simple but effective), it could be adjusted for slow pace speech ) sf.write("output.wav", wav, 16000) print("saved: output.wav") - Notebooks
- Google Colab
- Kaggle
VoxCPM2 Najdi Arabic TTS (diacritized) โ final
Full fine-tune of VoxCPM2 (2.29B) for Saudi Najdi Arabic TTS, trained on diacritized text so that harakat control pronunciation at inference time.
This checkpoint: step 18264 โ final, end of epoch 1 (val loss 0.7848).
Why diacritics, and why a full fine-tune
VoxCPM2's tokenizer has no diacritic entries, so every harakat falls through to a
byte-fallback pair (<0xD9><0x8E> = fatha) whose embedding rows carry a
fusha/Quranic association from pretraining. Token count inflates x1.96 on marked
text.
A LoRA run on this data could not fix that: apply_lora_to_named_linear_modules
wraps Linear only (q/k/v/o_proj) and freezes everything else, so
nn.Embedding is structurally untouchable โ attention can only reinterpret
those rows in context, never change them. Full fine-tuning unfreezes everything
except the AudioVAE, so the rows themselves retrain.
Training data
292,217 rows / 515.8 h, derived from a 377 h / ~1,032-speaker Najdi YouTube corpus:
| portion | rows | note |
|---|---|---|
| diacritized | 204,552 | coverage mean 0.804 |
| long-form (>25 s), bare | 3,835 | diacritization degrades past ~25 s |
| bare duplicates of diacritized clips | 83,830 | same audio, marks stripped |
70.5% of rows carry marks; 45.0% carry a same-speaker ref_audio.
The bare duplicates are the load-bearing part. If bare and marked text sat on disjoint audio, the model could satisfy both by keying on the voice and learn nothing about marks. Identical audio carrying both forms leaves one consistent reading: marks are optional modifiers of the same speech.
Diacritics come from NAMAA-Space/Cohere-Speech-Tashkeel-2B, an acoustic diacritizer, so harakat reflect how each word was actually said rather than what a text model guesses โ it stays dialectal (ููุชูุณููููู, ุญูุชูููุงูููุง, ุญูููุงุฎูุฐ). Because it runs ~10% letter WER its output is not used as the transcript: the corpus transcript is the letter backbone and only marks transfer word-by-word, leaving misheard words bare. Word-final marks (i'rab / pausal) are stripped; shadda is kept, being gemination rather than a case ending.
Transcripts are Cohere ASR text, not raw auto-captions.
Training
Stock openbmb/VoxCPM2, one epoch, 18,264 steps at effective batch 16
(4 x 4 grad-accum), lr 1e-5, weight_decay 0.01, warmup 100, max_grad_norm 1.0,
lambdas 1.0/1.0. Single H200.
Validation loss: 0.9689 (step 0) -> 0.8033 (2k) -> 0.7852 (8k) -> 0.7834 (15k, best) -> 0.7848 (18k). Essentially flat past step 8,000.
Inference
Output is 48 kHz (model.tts_model.sample_rate), not 16 kHz โ writing it
with a 16 kHz header plays ~3x slow and about an octave and a half low.
Short text works through the standard CLI:
voxcpm clone --model-path <this-repo> \
--text "ููุด ุฑูุงููู ููุชูููุงุจูู ุจูุงููุฑ ุจูุงููู
ูููููู ุงููููู ุฌูููุจ ุงููุจูููุช" \
--reference-audio ref.wav --no-denoiser --output out.wav
Long text must be chunked. A single autoregressive pass past ~20 s degenerates
into syllable babble in its tail. Measured on a 47 s single-shot generation:
transcribe-back WER 0.41-0.48, with output like
ุจู ุจู ุจููุณูุดุงูุดุงู ... ุจูุทุง ุจูุทุง ููุทุจู ุทุจูุทุจูุฑ. Insertions and deletions were
both zero, so this is accumulated drift, not a stop-head failure.
Splitting on sentence boundaries fixes it:
| split | chunks | transcribe-back WER |
|---|---|---|
| single-shot 47 s | 1 | 0.410-0.476 |
| comma + word fallback | 8 | 0.105 |
| full stops / question marks only | 16 | 0.069 |
Chunks up to ~15 s measured clean (WER 0.040-0.100). cfg_value 2.0 beat 1.5 and
1.3 (0.250 vs 0.500) โ do not lower it.
Known limitations
- Inference text must be diacritized in the same convention as training: pausal, word-final marks stripped, dialectal vowels. Text carrying standard MSA i'rab endings is off-distribution. There is no text-side diacritizer in this stack โ the one used to build the data is acoustic and needs audio.
- Mark coverage is ~80%, so sentences are internally mixed (some words bare). That is realistic input, not a defect.
- Reference audio in training is capped at 10.0 s; longer references are untested territory.
- Trained on Najdi. The gaf (ู pronounced /g/) is a deliberate register target, not an error.
- Downloads last month
- -
Model tree for Rabe3/VoxCPM_mid_najdi
Base model
openbmb/VoxCPM2