You need to agree to share your Hugging Face information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

Model Card for LFM2.5-350M-TTS

This model is our pretrained TTS model from LiquidAI/LFM2.5-350M, pretrained on Bambara.

The LiquidAI/LFM2.5-350M was a general-purpose text-only model that we re-designed to be a TTS model by training it to predict audios tokens instead of text tokens.

Usage

NOTE

This is a pretrained model, if you want more precision or to use it on new languages you should finetune it, and we recommend you to finetune it, especially on african languages.

Installation

pip install unsloth snac
pip install git+https://github.com/sudoping01/bambara-text-normalization.git

Using unsloth

from unsloth import FastLanguageModel
import torch

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="Panga-Azazia/LFM2.5-350M-TTS",
    max_seq_length=2048,
    dtype=None,
    load_in_4bit=False,
)

FastLanguageModel.for_inference(model)

Using transformers

tokenizer = AutoTokenizer.from_pretrained("Panga-Azazia/LFM2.5-350M-TTS", trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    "Panga-Azazia/LFM2.5-350M-TTS",
    trust_remote_code=True
)

Normalisation du Bambara

The bambara_normalizer module is used only for Bambara. It is used to normalize certain parts of the text before generation.

For more information on how to use it, see: bambara-text-normalizer


import torch
from snac import SNAC
import soundfile as sf
import numpy as np
from unsloth import FastLanguageModel
from bambara_normalizer import normalize_dates_in_text, normalize_measurements_in_text, normalize_numbers_in_text, normalize_times_in_text


CODE_END_TOKEN_ID = 7

SNAC_MIN_ID = 64402
CODE_TOKEN_OFFSET = 64402
SNAC_MAX_ID = 93073
SNAC_TOKENS_PER_FRAME = 7



def build_prompt(text: str, lang : str) -> str:

  text = normalize_dates_in_text(text)
  text = normalize_times_in_text(text)
  text = normalize_measurements_in_text(text)
  text = normalize_numbers_in_text(text)

  messages = [
      #{"role" : "system", "content" : prompt},
      {"role": "user", "content": f"<description=Read the following text in {lang}> {text}"}
      ]

  return messages



def extract_snac_codes(token_ids: list) -> list:
    """Extract SNAC codes from generated tokens."""
    try:
        eos_idx = token_ids.index(CODE_END_TOKEN_ID)
    except ValueError:
        eos_idx = len(token_ids)

    snac_codes = [
        token_id for token_id in token_ids[:eos_idx]
        if SNAC_MIN_ID <= token_id <= SNAC_MAX_ID
    ]

    return snac_codes


def unpack_snac_from_7(snac_tokens: list) -> list:
    """Unpack 7-token SNAC frames to 3 hierarchical levels."""

    if snac_tokens and snac_tokens[-1] == CODE_END_TOKEN_ID:
      snac_tokens = snac_tokens[:-1]

    frames = len(snac_tokens) // SNAC_TOKENS_PER_FRAME
    snac_tokens = snac_tokens[:frames * SNAC_TOKENS_PER_FRAME]

    if frames == 0:
        return [[], [], []]

    l1, l2, l3 = [], [], []

    for i in range(frames):
        slots = snac_tokens[i*7:(i+1)*7]
        l1.append((slots[0] - CODE_TOKEN_OFFSET) % 4096)
        l2.extend([
            (slots[1] - CODE_TOKEN_OFFSET) % 4096,
            (slots[4] - CODE_TOKEN_OFFSET) % 4096,
        ])
        l3.extend([
            (slots[2] - CODE_TOKEN_OFFSET) % 4096,
            (slots[3] - CODE_TOKEN_OFFSET) % 4096,
            (slots[5] - CODE_TOKEN_OFFSET) % 4096,
            (slots[6] - CODE_TOKEN_OFFSET) % 4096,
        ])

    return [l1, l2, l3]


snac_model = SNAC.from_pretrained("hubertsiuzdak/snac_24khz").eval()

def main(text = "An me kɛrɛlamana dɔw don a tigilamɔgɔ la." ,lang = "Bambara", temp = .1, top_p = 1.05, top_k = 50,
         max_tokens = 2048, min_tokens = 28, rp = 1.25):

    messages = build_prompt(text, lang)

    chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)

    print(chat)

    inputs = tokenizer(chat, return_tensors="pt").to("cuda")

    print(inputs)


    with torch.inference_mode():
        outputs = model.generate(
            **inputs,
            max_new_tokens=max_tokens, 
            min_new_tokens=min_tokens, 
            temperature=temp,
            top_p=top_p,
            top_k = top_k,
            repetition_penalty=rp,  
            do_sample=True,
            eos_token_id=tokenizer.eos_token_id,  
            pad_token_id=tokenizer.pad_token_id,
            use_cache=True,
        )


    generated_ids = outputs[0, inputs['input_ids'].shape[1]:].tolist()

    print(generated_ids)
    print(len(generated_ids))
    generated_ids = generated_ids[1:-1]
    print(len(generated_ids))


    # Unpack SNAC tokens to 3 hierarchical levels
    levels = unpack_snac_from_7(generated_ids)
    frames = len(levels[0])

    print(f"Unpacked to {frames} frames")
    print(f"   L1: {len(levels[0])} codes")
    print(f"   L2: {len(levels[1])} codes")
    print(f"   L3: {len(levels[2])} codes")

    # Convert to tensors
    device = "cuda" if torch.cuda.is_available() else "cpu"
    codes_tensor = [
        torch.tensor(level, dtype=torch.long, device=device).unsqueeze(0)
        for level in levels
    ]

    # Generate final audio with SNAC decoder
    print("\n[4/4] Decoding to audio...")
    with torch.inference_mode():
        z_q = snac_model.quantizer.from_codes(codes_tensor)
        audio = snac_model.decoder(z_q)[0, 0].cpu().numpy()

    # Trim warmup samples (first 2048 samples)
    if len(audio) > max_tokens:
        audio = audio[max_tokens:]

    duration_sec = len(audio) / 24000
    print(f"Audio generated: {len(audio)} samples ({duration_sec:.2f}s)")

    # Save your emotional voice output
    output_file = f"{text}-{lang}.wav"
    sf.write(output_file, audio, 24000)
main(
    lang="Bambara",
    text="Ayiwa, an jigi bɛ fɛn minnu kan, dannaya ye olu jɛyali ye. Dannaya bɛ fɛn yebaliw seereya kɛ.",
    #temp = .5, top_p = .9, rp = 1.25
)

Ressources

If you want to finetune or to know how to transform an LLM to a TTS model you can use notebooks bellow.

Name Description Notebook
PT (Unsloth) Pre-Training using Unsloth for TTS. Colab link
SFT (Unsloth) Supervised Fine-Tuning with LoRA using Unsloth. Colab link
Downloads last month
-
Safetensors
Model size
0.4B params
Tensor type
F32
·
BF16
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for Panga-Azazia/LFM2.5-350M-TTS

Finetuned
(112)
this model
Finetunes
2 models

Datasets used to train Panga-Azazia/LFM2.5-350M-TTS