CoolFace
Apppublic

GAASH-Lab/Matcha-TTS-Kashmiri-Demo

sourceHugging Faceapache-2.0updated 7mo agoView on Hugging Face
4likes
app.py268 linesDownload Raw Back to root
1 2import gradio as gr3from pathlib import Path4import torch5import urllib.request6import os  7 8# HuggingFace Spaces GPU support9try:10    import spaces11    SPACES_AVAILABLE = True12except ImportError:13    SPACES_AVAILABLE = False14    print("[!] spaces module not available, running without GPU decorator")15import soundfile as sf16import traceback17import huggingface_hub18from huggingface_hub import hf_hub_download19 20# Patch for older diffusers compatibility with newer huggingface_hub21if not hasattr(huggingface_hub, "cached_download"):22    huggingface_hub.cached_download = hf_hub_download23 24from transformers import AutoTokenizer, AutoModelForCausalLM25from peft import PeftModel26from matcha.models.matcha_tts import MatchaTTS27from matcha.hifigan.models import Generator as HiFiGAN28from matcha.hifigan.config import v129from matcha.hifigan.env import AttrDict30from matcha.text import text_to_sequence31from matcha.utils.utils import intersperse32 33HF_TOKEN = os.getenv("HF_TOKEN")34 35DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")36MODEL_REPO = "GAASH-Lab/Matcha-TTS-Kashmiri"37 38def load_models():39    print("[*] Downloading GAASH-Lab checkpoint...")40    ckpt = hf_hub_download(repo_id=MODEL_REPO, filename="model.ckpt", token=HF_TOKEN)41    model = MatchaTTS.load_from_checkpoint(ckpt, map_location=DEVICE, weights_only=False)42    model.eval()43    44    print("[*] Loading HiFi-GAN vocoder...")45    # The file 'generator_v1' is what the code calls 'hifigan_T2_v1'46    # We download it from the official GitHub release if not found locally47    vocoder_path = Path("hifigan_T2_v1")48    if not vocoder_path.exists():49        url = "https://github.com/shivammehta25/Matcha-TTS-checkpoints/releases/download/v1.0/generator_v1"50        urllib.request.urlretrieve(url, vocoder_path)51 52    vocoder = HiFiGAN(AttrDict(v1)).to(DEVICE)53    state_dict = torch.load(vocoder_path, map_location=DEVICE)54    vocoder.load_state_dict(state_dict['generator'])55    vocoder.eval()56    vocoder.remove_weight_norm()57    58    return model, vocoder59 60# Translation Config61TRANSLATION_BASE_MODEL = "sarvamai/sarvam-translate"62TRANSLATION_ADAPTER = "GAASH-Lab/Sarvam-Kashmiri-finetuned"63 64# Global cache for translation model (loaded lazily when GPU is available)65_trans_cache = {"tokenizer": None, "model": None, "loaded": False}66 67def load_translation_models():68    """Load translation model lazily on first use (CPU deployment)."""69    global _trans_cache70    71    if _trans_cache["loaded"]:72        return _trans_cache["tokenizer"], _trans_cache["model"]73    74    print("[*] Loading Sarvam Translate Adapter (CPU mode)...")75    try:76        # Load the tokenizer with left padding (required for causal LM)77        tokenizer = AutoTokenizer.from_pretrained(TRANSLATION_BASE_MODEL, trust_remote_code=True)78        tokenizer.padding_side = "left"79        if tokenizer.pad_token is None:80            tokenizer.pad_token = tokenizer.eos_token81        82        # Load the base model on CPU with bfloat16 to reduce memory83        # bfloat16 is better supported on CPU than float1684        print("[*] Loading base model on CPU (bfloat16)...")85        base_model = AutoModelForCausalLM.from_pretrained(86            TRANSLATION_BASE_MODEL,87            torch_dtype=torch.bfloat16,88            device_map="cpu",89            low_cpu_mem_usage=True,90            trust_remote_code=True91        )92        93        # Load the LoRA adapter94        print("[*] Loading LoRA adapter...")95        model = PeftModel.from_pretrained(base_model, TRANSLATION_ADAPTER)96        97        # Merge LoRA weights into base model for faster inference98        # This eliminates adapter overhead during generation99        print("[*] Merging LoRA weights for faster inference...")100        model = model.merge_and_unload()101        model.eval()102        103        print(f"[+] Translation model loaded and merged successfully on CPU.")104        _trans_cache["tokenizer"] = tokenizer105        _trans_cache["model"] = model106        _trans_cache["loaded"] = True107        return tokenizer, model108    except Exception as e:109        print(f"[-] Error loading translation model: {e}")110        traceback.print_exc()111        return None, None112 113# Load TTS models at startup (they're smaller)114model, vocoder = load_models()115# Translation model will be loaded lazily when GPU is available116 117def _translate_impl(text):118    """Internal translation implementation - matching evaluate_model.py approach."""119    # Load model lazily (will be cached after first load)120    trans_tokenizer, trans_model = load_translation_models()121    122    if trans_model is None:123        return "Translation model unavailable."124    125    # Build chat messages (matching evaluate_model.py)126    messages = [127        {"role": "system", "content": "Translate the text below to Kashmiri."},128        {"role": "user", "content": text},129    ]130    131    try:132        # Apply chat template (matching evaluate_model.py)133        prompt = trans_tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)134        inputs = trans_tokenizer(prompt, padding=True, truncation=True, max_length=512, return_tensors="pt")135        136        # Move inputs to model's device137        inputs = {k: v.to(trans_model.device) for k, v in inputs.items()}138        139        print(f"[DEBUG] Input tokens: {inputs['input_ids'].shape[1]}")140        141    except Exception as e:142        print(f"Chat template error: {e}")143        traceback.print_exc()144        return "Error in translation template."145 146    try:147        import time148        start_time = time.time()149        print("[DEBUG] Starting generation...")150        151        # Generation settings optimized for CPU inference152        # - Greedy decoding (do_sample=False) is faster than sampling153        # - Same quality as temp=0.01 which was near-greedy anyway154        with torch.no_grad():155            generated = trans_model.generate(156                **inputs,157                max_new_tokens=256,  # Keep full length for long texts158                do_sample=False,     # Greedy decoding for speed159                num_beams=1,         # No beam search overhead160            )161        162        elapsed = time.time() - start_time163        print(f"[DEBUG] Generation completed in {elapsed:.2f}s")164        165        # Decode only the new tokens (matching evaluate_model.py)166        input_len = inputs['input_ids'].shape[1]167        output_ids = generated[0][input_len:]168        decoded = trans_tokenizer.decode(output_ids, skip_special_tokens=True).replace("\n", "")169        170        print(f"[DEBUG] New tokens: {len(output_ids)}")171        print(f"[DEBUG] Decoded: '{decoded}'")172        173        return decoded.strip()174        175    except Exception as e:176        print(f"Generation error: {e}")177        traceback.print_exc()178        return "Error during translation generation."179 180# Simple wrapper function for CPU deployment181def translate(text):182    return _translate_impl(text)183 184 185# --- Update the function signature to accept two arguments ---186@torch.inference_mode()187def process(text, speaker_id, n_timesteps=10):188    # 1. Kashmiri script normalization189    text = text.replace("ي", "ی").replace("ك", "ک").replace("۔", "").strip()190    191    # 2. Text to Sequence192    cleaner = "basic_cleaners" 193    sequence, _ = text_to_sequence(text, [cleaner])194    195    # Filter out any non-integer values (unknown characters not in vocabulary)196    # This happens when text contains characters not supported by the TTS model197    filtered_sequence = [s for s in sequence if isinstance(s, int)]198       199    x = torch.tensor(intersperse(filtered_sequence, 0), dtype=torch.long, device=DEVICE)[None]200    x_lengths = torch.tensor([x.shape[-1]], dtype=torch.long, device=DEVICE)201    202    # 3. Use the Speaker ID from the interface203    # Even if you only use one voice, the model requires this tensor204    spks = torch.tensor([int(speaker_id)], device=DEVICE, dtype=torch.long)205    206    # 4. Generate Mel-spectrogram207    output = model.synthesise(208        x, 209        x_lengths, 210        n_timesteps=n_timesteps, 211        temperature=0.667, 212        spks=spks,213        length_scale=1.0214    )215    216    # 5. Generate Waveform217    audio = vocoder(output['mel']).clamp(-1, 1).cpu().squeeze().numpy()218    output_path = "out.wav"219    sf.write(output_path, audio, 22050)220    return output_path221 222# --- Gradio UI with Translation Option ---223with gr.Blocks(title="GAASH-Lab: Kashmiri TTS & Translation") as demo:224    gr.Markdown("# GAASH-Lab: Kashmiri TTS & Translation")225    gr.Markdown("Enter text in English (check the box) or Kashmiri directly.")226    227    with gr.Row():228        with gr.Column():229            input_text = gr.Textbox(label="Input Text", placeholder="Type here...")230            is_english = gr.Checkbox(label="Input is English (Translate first)", value=False)231            speaker_radio = gr.Radio(choices=["Male", "Female"], value="Male", label="Speaker Voice")232            quality_radio = gr.Radio(233                choices=["Low (fast)", "Medium", "High"], 234                value="Low (fast)", 235                label="Quality"236            )237            gen_btn = gr.Button("Generate Speech", variant="primary")238        239        with gr.Column():240            trans_view = gr.Textbox(label="Processed/Translated Kashmiri Text", interactive=False)241            audio_output = gr.Audio(label="Audio", type="filepath")242 243    def pipeline(text, is_eng, spk_voice, quality):244        spk_id = 422 if spk_voice == "Male" else 423245        246        if "Low" in quality:247            steps = 10248        elif "Medium" in quality:249            steps = 50250        else:251            steps = 500252 253        processed_text = text254        if is_eng:255            print(f"Translating input: {text}")256            processed_text = translate(text)257        258        print(f"Synthesizing for: {processed_text}")259        audio_path = process(processed_text, spk_id, steps)260        return processed_text, audio_path261 262    gen_btn.click(263        pipeline, 264        inputs=[input_text, is_english, speaker_radio, quality_radio], 265        outputs=[trans_view, audio_output]266    )267 268demo.launch(ssr_mode=False)