Instructions to use Splintir/speecht5_tts-pld-ceb-v2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Splintir/speecht5_tts-pld-ceb-v2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-to-speech", model="Splintir/speecht5_tts-pld-ceb-v2")# Load model directly from transformers import AutoProcessor, AutoModelForTextToSpectrogram processor = AutoProcessor.from_pretrained("Splintir/speecht5_tts-pld-ceb-v2") model = AutoModelForTextToSpectrogram.from_pretrained("Splintir/speecht5_tts-pld-ceb-v2", device_map="auto") - Notebooks
- Google Colab
- Kaggle
speecht5_tts-pld-ceb-v2
sapinsapin/speecht5_tts-pld-ceb
continue-finetuned on every usable Cebuano clip in
sapinsapin/pld β 14,007 of
them, against the ~1,800 the published checkpoint saw.
It was never a shortage of data. PLD holds ~50,900 Cebuano rows; 15,163 pass the
TTS filter (read speech, not a prompt, β₯3 words) and 14,007 survive the digit
and length caps β 19 hours. The original run stopped at 1,847 because
--max-samples 3000 was a default nobody revisited. This gives the same model
the rest of its own language.
"Continue", not "resume": the published repo ships model.safetensors and
training_args.bin and nothing else β no optimizer moments, no scheduler state
β so this warm-starts from the released weights with a fresh optimizer and a
fresh linear schedule.
| training clips | 13,807 train + 200 eval (14,007 total) |
| steps | 4,000 (β9 epochs) |
| batch | 8 Γ 4 accumulation |
| learning rate | 1e-05 |
| precision | fp32 + gradient checkpointing (fp16 NaNs SpeechT5's mel loss) |
| eval loss | 0.3687, against 0.4070 for the base checkpoint |
| hardware | one free-tier Colab T4, ~3 h |
What it bought
There is no way to measure a synthetic voice directly, so the test is indirect: play each clip to a speech recognizer that never saw the original sentence, and count how much of the text it fails to recover. Character error rate (CER) is the share of characters it gets wrong. Lower is better. This measures whether speech is intelligible to a machine listener β not whether it sounds natural or authentically Cebuano, which still needs human ears.
Ten held-out pld test lines, transcribed by whisper-large-v3-turbo:
| base | v2 | |
|---|---|---|
CER, arctic:slt |
0.141 | 0.093 |
WER, arctic:slt |
0.497 | 0.388 |
CER, native pld:ceb:12 |
0.210 | 0.148 |
WER, native pld:ceb:12 |
0.637 | 0.524 |
A third of the character errors, gone. At 0.093 this scores below mms-tts-ceb
on the same lines (0.153).
Four listeners, not one
A single judge is a single opinion, and every recognizer is deaf in its own way. Whisper has no Cebuano in its training data at all, so some share of every score above is the judge's failure rather than the model's β a real Cebuano speaker reading these same lines scores 0.081, not 0. Rescoring the same audio with four different recognizers separates the two:
| judge | base | v2 | what it is |
|---|---|---|---|
sapinsapin/whisper-small-pld-ceb |
0.139 | 0.065 | small Whisper, finetuned on Cebuano |
whisper-large-v3-turbo |
0.146 | 0.123 | large, zero-shot, no Cebuano |
whisper-turbo + Filipino LoRA |
0.126 | 0.092 | large, nudged toward Philippine speech |
facebook/mms-1b-all (ceb adapter) |
0.125 | 0.067 | CTC model, 491k hours, ceb head |
CER, arctic:slt, ten lines. This is a different bench run from the table
above β which is why turbo reads 0.123 here and 0.093 there, with no change to
the weights. See the determinism note at the bottom; it is the same lesson.
The pattern is the finding. The judge that actually knows Cebuano sees the retrain cut errors by 53%; the judge that does not sees 16%. Zero-shot Whisper is measuring partly its own ignorance, and that noise compresses the distance between a good model and a poor one. Read down a column, never across: a CTC model and a sequence-to-sequence model make different kinds of mistakes, so their absolute numbers are not on the same scale.
One caveat on the in-domain judge: whisper-small-pld-ceb was finetuned on the
same corpus this model trained on. It is the most informed listener available,
and also the most likely to reward speech that sounds specifically like PLD.
Treat it as the sharpest instrument, not the neutral one.
What it did not fix
Speaker conditioning. Handed a native Cebuano x-vector instead of the American
arctic:slt, the decoder still collapses on most voices: 3 of 16 native
candidates survive, against 1 of 16 for the base checkpoint. Tripled, still 13
of 16 broken β one renders a 4-second line as 17 seconds at rms 0.003.
Seven-fold data was not the fix, because the bottleneck is not clip count but clip distribution: those 19 hours are 139 speakers, and the best-covered has 15.5 minutes. PLD is broad and shallow β built for ASR coverage, not TTS depth. Averaging harder over 139 people does not produce one person.
If you need a single reliable voice, use
Splintir/speecht5_tts-pld-ceb-solo,
which continue-trains this checkpoint on one speaker alone.
Usage
SpeechT5 holds no voice of its own β every call needs a 512-d x-vector. None
ships with the original PLD checkpoints, which is why they are usually run with
an American speaker from the HuggingFace tutorial. speaker.npy in this repo
is a real ceb speaker from the training data, so you can skip that.
import numpy as np, torch, soundfile as sf
from huggingface_hub import hf_hub_download
from transformers import SpeechT5ForTextToSpeech, SpeechT5HifiGan, SpeechT5Processor
REPO = "Splintir/speecht5_tts-pld-ceb-v2"
processor = SpeechT5Processor.from_pretrained(REPO)
model = SpeechT5ForTextToSpeech.from_pretrained(REPO).eval()
vocoder = SpeechT5HifiGan.from_pretrained("microsoft/speecht5_hifigan").eval()
# speaker.npy is stored as (512,); the model wants (1, 512). Without the
# unsqueeze this raises "The first dimension of speaker_embeddings must be
# either 1 or the same as batch size".
speaker = torch.from_numpy(np.load(hf_hub_download(REPO, "speaker.npy"))).float().unsqueeze(0)
ids = processor(text="Maayong buntag sa imong tanan.", return_tensors="pt")["input_ids"]
with torch.inference_mode():
speech = model.generate_speech(ids, speaker, vocoder=vocoder)
sf.write("out.wav", speech.numpy(), 16000) # 16 kHz mono
# In a notebook (Colab, Jupyter), play it inline instead of saving:
from IPython.display import Audio, display
display(Audio(speech.numpy(), rate=16000))
Runs unmodified on a stock Colab CPU runtime β every dependency is preinstalled,
including the sentencepiece the tokenizer needs β at roughly 1.8Γ real time.
For GPU, .to("cuda") the model, the vocoder and ids/speaker, then .cpu()
the result before sf.write.
The display(Audio(...)) line renders a play button in a notebook cell. It
produces no sound over a terminal or SSH session; there, write the wav and fetch
it (from google.colab import files; files.download("out.wav")).
Spell numbers out: the tokenizer is character-level Latin and drops digits silently.
Two things to know before trusting a number
Generation is not deterministic. SpeechT5's decoder prenet keeps dropout
active during inference, by design, as Tacotron2's does. Five renders of one
line from one checkpoint gave five different durations (3.17β3.30 s) and rms
0.062β0.074. This checkpoint scored CER 0.093 in one bench run and 0.123 in
another with nothing changed β so compare systems within a run, never across
runs. In production, cache audio by hash(text + voice) to freeze one
known-good render.
An out-of-distribution x-vector produces 12 seconds of quiet mumbling, not an error. If output is long and near-silent, the speaker embedding is the suspect, not the text.
Trained with scripts/train_tts.py;
preprocessing matches finetune_tts.py from the
halohalo pipeline β one x-vector per
clip, never averaged.
- Downloads last month
- 208