Spaces:
Running on Zero
Running on Zero
Download app.py from hugging-apps/ema-lightning: direct link, hf CLI and curl.
- Browser
- Download file 6.82 kB
-
https://huggingface.co/spaces/hugging-apps/ema-lightning/resolve/main/app.py
- Command line
-
hf download hf://spaces/hugging-apps/ema-lightning/app.py
-
curl -L -o app.py https://huggingface.co/spaces/hugging-apps/ema-lightning/resolve/main/app.py
6.82 kB
| """EMA Lightning — tiny, fast and accurate Turkish text-to-speech on ZeroGPU. | |
| The model authors' own pip package (`ema-lightning`) provides the whole | |
| inference path: the text frontend (normalizer-tr), the 5.6M-parameter DiT | |
| acoustic model and the 3M-parameter decoder, both read from the Apache-2.0 | |
| checkpoint at canberkkkkkk/ema-lightning. 48 kHz speech in four ODE steps. | |
| """ | |
| import os | |
| os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") # conv decoder hits transient alloc spikes | |
| import spaces # MUST come before torch / any CUDA-touching import | |
| import time | |
| import gradio as gr | |
| import numpy as np | |
| import torch | |
| from ema_lightning import EMA | |
| MAX_CHARS = 1200 # keeps the whole request well inside a few seconds of GPU | |
| # Module-scope load: ZeroGPU intercepts .to("cuda") and packs the ~34 MB of | |
| # weights to disk, streaming them into VRAM on the first @spaces.GPU call. | |
| tts = EMA() | |
| SAMPLE_RATE = 48000 | |
| # The package normally probes the GPU for its best batch size on first use | |
| # (benching batches up to 128). On ZeroGPU the worker's VRAM is shared with | |
| # the platform and the probe has OOM'd on allocator state; a fixed batch of | |
| # one sentence per pass is plenty for a single-user demo of an 87x-real-time | |
| # model, so pin it and skip the probe entirely. | |
| tts._batch_size = 1 | |
| # The package loads checkpoints as .to(device).requires_grad_(False), but the | |
| # ZeroGPU hijack snapshots requires_grad at .to() time, so the decoder's | |
| # weights arrive in the worker flagged as grad-tracking. Playhead decodes in a | |
| # background thread (engine.decode is the one stage without @torch.no_grad()), | |
| # which makes the audio require grad and kills .numpy(). Wrap it. | |
| _engine_decode = tts._engine.decode | |
| def _decode_nograd(*args, **kwargs): | |
| return _engine_decode(*args, **kwargs) | |
| tts._engine.decode = _decode_nograd | |
| def synthesize(text: str, speed: float = 1.0, seed: int = 0, sample_rate: int = 48000): | |
| """Synthesize Turkish speech from text with EMA Lightning. | |
| Args: | |
| text: Turkish text to read. Digits, dates, times, currency and | |
| percentages are expanded to spoken Turkish automatically; any | |
| written Turkish is accepted and text never raises. | |
| speed: Speaking rate, from 0.25 (four times slower) to 4 (four | |
| times faster). 1.0 is the natural pace. | |
| seed: Fixes the noise draw for a reproducible result. The voice is | |
| fixed regardless of seed. | |
| sample_rate: Output audio rate: 48000, 24000, 16000 or 8000 Hz. | |
| Returns: | |
| A tuple of (sample_rate, waveform) for Gradio's Audio component. | |
| """ | |
| text = (text or "").strip() | |
| if not text: | |
| raise gr.Error("Lütfen okunacak bir metin girin. / Please enter some text.") | |
| if len(text) > MAX_CHARS: | |
| raise gr.Error( | |
| f"Metin çok uzun ({len(text)} karakter). Lütfen {MAX_CHARS} karakterin " | |
| f"altına indirin. / Text too long ({len(text)} chars); keep it under " | |
| f"{MAX_CHARS}." | |
| ) | |
| speed = float(speed) | |
| if not 0.25 <= speed <= 4: | |
| speed = 1.0 | |
| seed = max(0, int(seed)) | |
| sample_rate = int(sample_rate) if int(sample_rate) in (48000, 24000, 16000, 8000) else 48000 | |
| t0 = time.perf_counter() | |
| speech = tts.say(text, speed=speed, seed=seed, sample_rate=sample_rate) | |
| print( | |
| f"[ema-lightning] {len(text)} chars, speed={speed}, seed={seed} -> " | |
| f"{time.perf_counter() - t0:.2f}s synth, {speech.duration:.2f}s audio", | |
| flush=True, | |
| ) | |
| return (speech.sample_rate, np.asarray(speech.audio, dtype=np.float32)) | |
| CSS = """ | |
| #col-container { max-width: 900px; margin: 0 auto; } | |
| .dark .gradio-container { color: var(--body-text-color); } | |
| """ | |
| with gr.Blocks() as demo: | |
| gr.Markdown( | |
| "# ⚡ EMA Lightning — Turkish Text-to-Speech\n" | |
| "An 8.6M-parameter (34 MB) flow-matching TTS that reads Turkish " | |
| "characters directly and synthesizes 48 kHz speech in four ODE steps — " | |
| "**0.92% WER on " | |
| "[Freya-TR-Eval](https://huggingface.co/datasets/freyavoice/freya-tr-eval)**, " | |
| "the lowest of every system measured, including ElevenLabs v4, Gemini 3.8 " | |
| "and the 2.38B-parameter Trendyol-TTS. One fixed voice, no cloning, " | |
| "Apache-2.0.\n\n" | |
| "[Model card](https://huggingface.co/canberkkkkkk/ema-lightning) · " | |
| "[PyPI package](https://pypi.org/project/ema-lightning/) · " | |
| "Numbers, dates, times and currency are expanded to spoken Turkish " | |
| "before the model reads them." | |
| ) | |
| with gr.Column(elem_id="col-container"): | |
| text_input = gr.Textbox( | |
| label="Turkish text", | |
| placeholder="Merhaba, size nasıl yardımcı olabilirim?", | |
| lines=4, | |
| value="Merhaba, size nasıl yardımcı olabilirim?", | |
| ) | |
| run_btn = gr.Button("Generate Speech", variant="primary") | |
| audio_output = gr.Audio(label="Generated speech", type="numpy") | |
| with gr.Accordion("Advanced settings", open=False): | |
| speed_slider = gr.Slider( | |
| minimum=0.25, maximum=4.0, value=1.0, step=0.05, | |
| label="Speed", | |
| info="Speaking rate. 1.0 is the natural pace.", | |
| ) | |
| seed_input = gr.Number( | |
| label="Seed", | |
| value=0, precision=0, | |
| info="Fixes the noise draw for a reproducible result. " | |
| "The voice is fixed regardless of seed.", | |
| ) | |
| rate_dropdown = gr.Dropdown( | |
| choices=[48000, 24000, 16000, 8000], | |
| value=48000, | |
| label="Sample rate (Hz)", | |
| info="The model generates at 48 kHz and resamples cleanly.", | |
| ) | |
| gr.Examples( | |
| examples=[ | |
| ["Model sadece sekiz 8.6 milyon parametre ve çok hızlı.", 1.0, 0, 48000], | |
| ["15 Ekim 2026 Çarşamba günü saat 14:30'da, 1.250.000 TL " | |
| "tutarındaki ödemeniz hesabınıza yatırılacak.", 1.0, 0, 48000], | |
| ["Sabahın erken saatlerinde liman henüz uyanmamıştı. " | |
| "Balıkçılar ağlarını sessizce topluyor, martılar ise " | |
| "teknelerin etrafında dönerek şanslarını deniyordu.", 1.0, 0, 48000], | |
| ], | |
| inputs=[text_input, speed_slider, seed_input, rate_dropdown], | |
| outputs=[audio_output], | |
| fn=synthesize, | |
| cache_examples=True, | |
| cache_mode="lazy", | |
| ) | |
| run_btn.click( | |
| fn=synthesize, | |
| inputs=[text_input, speed_slider, seed_input, rate_dropdown], | |
| outputs=[audio_output], | |
| api_name="synthesize", | |
| ) | |
| demo.launch(mcp_server=True, theme=gr.themes.Citrus(), css=CSS) |