sorotts / README.md
Shinzmann's picture
Training details from the real B200 run: 97.3M trainable (2.86%), 7894 steps, ~31 min
9dc733d verified
|
Raw
History Blame Contribute Delete
11.3 kB
---
license: cc-by-nc-sa-4.0
language:
- yo
- ha
- ig
- pcm
base_model: hypaai/hypaai_orpheus_v5
base_model_relation: adapter
library_name: peft
pipeline_tag: text-to-speech
datasets:
- naijavoices/naijavoices-dataset
- google/fleurs
- google/WaxalNLP
- asr-nigerian-pidgin/nigerian-pidgin-1.0
tags:
- text-to-speech
- tts
- speech-synthesis
- orpheus
- snac
- lora
- peft
- unsloth
- yoruba
- hausa
- igbo
- nigerian-pidgin
- nigeria
- african-languages
- multilingual
- low-resource
---
# SoroTTS: a natural voice for Yorùbá, Hausa, Igbo, and Nigerian Pidgin
**SoroTTS** (from *sọ̀rọ̀*, "speak" in Yorùbá) is a LoRA fine-tune of **Orpheus-3B** that gives natural, expressive text-to-speech in **four Nigerian languages**, Yorùbá, Hausa, Igbo, and Nigerian Pidgin, from a single adapter under the **4B "Tiny Titan"** line.
It powers [**Naija Solar**](https://huggingface.co/spaces/build-small-hackathon/naija-solar), a voice-first solar-sizing app that reads your plan aloud in your own language, and it was built for the **Build Small Hackathon** (Gradio × Hugging Face).
🔊 **[Listen](#-listen)**  ·  🟢 **[Try it in Naija Solar](https://huggingface.co/spaces/build-small-hackathon/naija-solar)**  ·  🎥 **[Demo video](https://youtu.be/PfQeRfNof8Y)**  ·  💻 **[Training code on GitHub](https://github.com/Mystique1337/naija-solar)**
## 🔊 Listen
Hear each language (cleanest voice per language):
| Language | Voice | Sample |
|---|---|---|
| Yorùbá | `Yor1` | [yor_Yor1.wav](https://huggingface.co/Shinzmann/sorotts/resolve/main/samples/yor_Yor1.wav) |
| Hausa | `Hau1` | [hau_Hau1.wav](https://huggingface.co/Shinzmann/sorotts/resolve/main/samples/hau_Hau1.wav) |
| Igbo | `Ibo1` | [ibo_Ibo1.wav](https://huggingface.co/Shinzmann/sorotts/resolve/main/samples/ibo_Ibo1.wav) |
| Nigerian Pidgin | `NaijaA` | [pcm_NaijaA.wav](https://huggingface.co/Shinzmann/sorotts/resolve/main/samples/pcm_NaijaA.wav) |
## Why it exists
Off-the-shelf TTS for Nigerian languages is either robotic (Meta MMS) or simply absent. Orpheus is one of the most natural open speech LLMs, but it is English-first and speaks no Nigerian language. SoroTTS closes that gap. It keeps Orpheus's natural prosody while speaking Yorùbá, Hausa, Igbo, and the language no public model spoke before, **Nigerian Pidgin**, the everyday tongue of perhaps a hundred million people.
## What it is
- **Architecture:** Orpheus-3B, a Llama-3B backbone that predicts [SNAC](https://huggingface.co/hubertsiuzdak/snac_24khz) 24 kHz neural audio codes. About 3B parameters, under the 4B line.
- **Base:** [`hypaai/hypaai_orpheus_v5`](https://huggingface.co/hypaai/hypaai_orpheus_v5) (Hypa-Orpheus), already adapted to Yorùbá, Hausa, and Igbo. SoroTTS **adds Nigerian Pidgin** and reinforces the other three.
- **This repo:** a single **LoRA adapter** (r=64) covering all four languages, plus the tokenizer. Attach it to the base at inference time (see Quickstart).
## Voices
Each clean data source becomes a named **voice tag** that you prepend to the text. An Orpheus speech LLM conditions on the voice tag, so more clean sources means more coverage. The cleanest source per language is voice `1`:
| Language | Cleanest voice | Other voices |
|---|---|---|
| Yorùbá | `Yor1` | `Yor2`, `Yor3` |
| Hausa | `Hau1` | `Hau2`, ... , `HauBible` (BibleTTS studio) |
| Igbo | `Ibo1` | `Ibo2`, `Ibo3` |
| Nigerian Pidgin | `NaijaA` | `NaijaB`, ... (one per speaker) |
English falls back to a base Orpheus / Hypa voice (e.g. `Eniola`).
> **Tip:** write Yorùbá and Igbo **with diacritics** (tone marks and dots) and Hausa **with hooked letters** (ɓ ɗ ƙ). The model learned diacritised text, so `Ẹ kú àbọ̀` sounds far better than `E ku abo`.
## 🚀 Quickstart
```bash
pip install unsloth snac soundfile peft huggingface_hub
```
```python
import os, torch, soundfile as sf
from huggingface_hub import snapshot_download
from unsloth import FastLanguageModel
from peft import PeftModel
from snac import SNAC
# 1) load the base WITHOUT its bundled adapter, then attach SoroTTS
local = snapshot_download("hypaai/hypaai_orpheus_v5", ignore_patterns=["adapter_*"])
for f in ("adapter_config.json", "adapter_model.safetensors", "adapter_model.bin"):
p = os.path.join(local, f)
if os.path.exists(p):
os.remove(p)
model, tok = FastLanguageModel.from_pretrained(local, max_seq_length=2048, dtype=None, load_in_4bit=False)
model = PeftModel.from_pretrained(model, "Shinzmann/sorotts") # <- the SoroTTS adapter
FastLanguageModel.for_inference(model)
snac = SNAC.from_pretrained("hubertsiuzdak/snac_24khz").to("cuda").eval()
# 2) Orpheus token scheme (must match training)
SOH, EOT, EOH, SOS, EOS, EOAI, OFF = 128259, 128009, 128260, 128257, 128258, 128262, 128266
DEV = next(snac.parameters()).device
def _decode(codes):
cl = lambda v: 0 if v < 0 else (4095 if v > 4095 else v) # clamp so a stray code can't crash SNAC
l1, l2, l3 = [], [], []
for i in range((len(codes) + 1) // 7):
b = 7 * i
l1.append(cl(codes[b])); l2.append(cl(codes[b+1] - 4096))
l3.append(cl(codes[b+2] - 2*4096)); l3.append(cl(codes[b+3] - 3*4096))
l2.append(cl(codes[b+4] - 4*4096))
l3.append(cl(codes[b+5] - 5*4096)); l3.append(cl(codes[b+6] - 6*4096))
c = [torch.tensor(x).unsqueeze(0).to(DEV) for x in (l1, l2, l3)]
with torch.inference_mode():
return snac.decode(c).squeeze().cpu().numpy()
def speak(text, voice="Yor1", max_new_tokens=1024):
ids = tok(f"{voice}: {text}", return_tensors="pt").input_ids
ids = torch.cat([torch.tensor([[SOH]]), ids, torch.tensor([[EOT, EOH]])], dim=1).to(model.device)
out = model.generate(input_ids=ids, attention_mask=torch.ones_like(ids),
max_new_tokens=max_new_tokens, do_sample=True, temperature=0.55,
top_p=0.95, repetition_penalty=1.1,
eos_token_id=[EOS, EOAI], use_cache=True)[0] # stop on EOS or EOAI
sos = (out == SOS).nonzero(as_tuple=True)[0]
seq = out[sos[-1].item() + 1:] if len(sos) else out
seq = seq[seq >= OFF] # keep only SNAC audio codes
n = (seq.size(0) // 7) * 7
return _decode([t.item() - OFF for t in seq[:n]])
wave = speak("Ẹ kú àbọ̀ sí Nàìjíríà, orílẹ̀-èdè wa tó kún fún ìbùkún.", voice="Yor1")
sf.write("out.wav", wave, 24000)
```
**Two gotchas worth knowing** (both handled above):
1. **Stop on `EOS` *or* `EOAI`.** The model often ends a clip with `EOAI` (128262); if you stop only on `EOS` it will run to the token cap and babble.
2. **Keep only audio codes, and clamp them.** Filter to tokens `>= 128266`, then clamp each SNAC value to `[0, 4095]`. A stray control token left in the stream corrupts the 7-token frame alignment and crashes the SNAC decoder.
Generate **one sentence per call** (each call has a roughly 2048-token, about 15 second budget) and stitch sentences with a short silence for longer text.
## Training data
**31,574 SNAC-tokenised clips** (0.7 to 15 seconds each), streamed and ranked cleanest-first, across:
| Corpus | Languages | License | Role |
|---|---|---|---|
| [NaijaVoices](https://huggingface.co/datasets/naijavoices/naijavoices-dataset) | yor / hau / ibo | CC-BY-NC-SA | the bulk (about 600h per language available) |
| [WAXAL TTS](https://huggingface.co/datasets/google/WaxalNLP) | yor / hau / ibo | CC-BY | clean single-speaker |
| [FLEURS](https://huggingface.co/datasets/google/fleurs) | yor / hau / ibo | CC-BY | read speech |
| [BibleTTS](https://huggingface.co/datasets/vpetukhov/bible_tts_hausa) | hau | CC-BY-SA | studio 48 kHz (`HauBible`) |
| [Nigerian Pidgin v1.0](https://huggingface.co/datasets/asr-nigerian-pidgin/nigerian-pidgin-1.0) | pcm | CC-BY | the Pidgin anchor (the new language) |
Because NaijaVoices (non-commercial) is included, the model is stamped **`cc-by-nc-sa-4.0`**. A permissive, commercial variant is possible by training on the CC-BY and CC-BY-SA sources only (the training script has a `--commercial-only` flag).
## How it was trained
- **LoRA** r=64, α=64, dropout 0, on all attention and MLP projections (the Orpheus-TTS standard), via **[Unsloth](https://github.com/unslothai/unsloth)**. Only **97.3M parameters are trained, 2.86%** of the 3.40B-parameter model.
- **2 epochs = 7,894 steps**, bf16, AdamW-8bit, lr 2e-4, 3% warmup, **total batch 8** (8 per device, 1 gradient-accumulation step), on a single **NVIDIA B200**. The run takes about **31 minutes** (1,894 s, roughly 33 samples per second) and converges to a train loss near 3.5.
- Audio is encoded to Orpheus's **7-tokens-per-frame SNAC stream**; each training sequence is
`[SOH] voice: text [EOT][EOH] [SOAI][SOS] <snac codes> [EOS][EOAI]`.
- **Fully reproducible on Modal**: streaming and SNAC-encoding the data, the LoRA training, and the Hub push all run as one serverless job. See [`modal/finetune_orpheus.py`](https://github.com/Mystique1337/naija-solar/blob/main/modal/finetune_orpheus.py) (train), [`serving_tts.py`](https://github.com/Mystique1337/naija-solar/blob/main/modal/serving_tts.py) (serve), and [`test_sorotts.py`](https://github.com/Mystique1337/naija-solar/blob/main/modal/test_sorotts.py) (samples).
## Intended use and limitations
- **Intended use:** accessibility and voice interfaces in Nigerian languages, reading text aloud, IVR, narration. Built for [Naija Solar](https://huggingface.co/spaces/build-small-hackathon/naija-solar).
- **Speed:** Orpheus is autoregressive (roughly 10 to 25 seconds of compute per sentence on an A100 or H200). Great behind a "written-first" UI; for real time, serve via vLLM.
- **English** is the base model's, not specifically fine-tuned here.
- **Pidgin** has the thinnest data (one clean 4 to 5 hour corpus). It works because Pidgin is English-lexified and Orpheus has a strong English prior, but a dedicated single-speaker corpus would sharpen it.
- **Numbers and units:** like most TTS, it reads digits and abbreviations literally. For natural speech, spell numbers as words (Naija Solar does this per language).
- **Ethics:** this is a voice for accessibility. Do not use it to clone a real person's voice without consent.
## License
**CC-BY-NC-SA-4.0**, inherited from the NaijaVoices data. The base model and the other corpora carry their own licenses; see each link above. Please attribute SoroTTS and share derivatives alike.
## Acknowledgements
Built on [Hypa-Orpheus](https://huggingface.co/hypaai/hypaai_orpheus_v5), [Orpheus-TTS](https://github.com/canopyai/Orpheus-TTS), [SNAC](https://huggingface.co/hubertsiuzdak/snac_24khz), and [Unsloth](https://github.com/unslothai/unsloth), with data from NaijaVoices, Google WAXAL and FLEURS, BibleTTS, and the Nigerian Pidgin ASR Corpus.
## Citation
```bibtex
@misc{ashinze2026sorotts,
title = {SoroTTS: a natural voice for Yoruba, Hausa, Igbo, and Nigerian Pidgin},
author = {Ashinze, Emmanuel},
year = {2026},
howpublished = {\url{https://huggingface.co/Shinzmann/sorotts}},
note = {LoRA fine-tune of Orpheus-3B, built for the Build Small Hackathon}
}
```
*By Ashinze Emmanuel, for the Build Small Hackathon.*