canberkkkkkk/ema-lightning

Hugging Face Models Trending Models

Summary

EMA Lightning is a tiny, fast Turkish text-to-speech model with 8.6M total parameters (~34 MB) that claims the lowest WER (0.92%) on Freya-TR-Eval, beating ElevenLabs v4 and Gemini 3.8, with streaming generation and fully offline operation under Apache 2.0.

Task: text-to-speech Tags: ema-lightning, text-to-speech, tts, turkish, speech-synthesis, flow-matching, tr, arxiv:2405.14867, license:apache-2.0, region:us
Original Article
View Cached Full Text

Cached at: 10/08/26, 12:27 PM

canberkkkkkk/ema-lightning · Hugging Face

Source: https://huggingface.co/canberkkkkkk/ema-lightning EMA Lightning

https://huggingface.co/canberkkkkkk/ema-lightning#%E2%9A%A1-ema-lightning⚡ EMA Lightning

**Tiny, fast and accurate Turkish text to speech.**A 5.6M-parameter DiT acoustic model and a 3M-parameter vocoder: 8.6M in total, about 34 MB. Apache 2.0.

  • **Most accurate on Freya-TR-Eval:**0.92% WER, the lowest of every system measured, including ElevenLabs v4, Gemini 3.8 and the 2.38B-parameter Trendyol-TTS.
  • **Fast:**first audio in 3.86 ms and 440× faster than real time on an RTX 4090; 1,316× with batching.
  • **Streams:**audio starts while the rest of the text is still being generated.
  • **Cheap:**about $0.0085 per million characters on a rented RTX 4090.
  • **Runs anywhere:**GPU or CPU, fully offline. No API key, and no text or audio leaves the machine.
  • **One model serves many callers:**call it from as many threads as you like; its scheduler, Playhead, runs everyone’s work together on the GPU.
  • Code:EMA Lightning on GitHub
  • Package:pip install ema\-lightning
  • Text frontend:normalizer-tr, by Erdem Tuna

EMA Lightning architecture

**Figure 1.**Text is encoded per letter, placed on a word–letter timeline by a duration predictor, turned into one condition per frame by a windowed aligner, generated as latents in four steps by a DiT, and decoded to 48 kHz audio. Details underArchitecture.

https://huggingface.co/canberkkkkkk/ema-lightning#listenListen

Every clip below is made withsay\(\)at seed 0, straight from the text shown.

Model sadece 8.6 milyon parametre ve çok hızlı.

15 Ekim 2026 Çarşamba günü saat 14:30’da, 1.250.000 TL tutarındaki ödemeniz hesabınıza yatırılacak.

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.

https://huggingface.co/canberkkkkkk/ema-lightning#quickstartQuickstart

pip install ema-lightning

Make oneEMAand keep it for the whole program. There are two ways to get speech out of it:

say\(\)``stream\(\)Gives youthe whole audio, once it’s readythe audio piece by piece, as it’s madeTakesone text, or a list of textsone text per callUse it forfiles, voiceovers, batch jobsplaying as you go: speakers, phone calls, live apps

https://huggingface.co/canberkkkkkk/ema-lightning#say-a-whole-text-back-when-its-readysay\(\): a whole text, back when it’s ready

from ema_lightning import EMA

tts = EMA()                                     # downloads ema.pt and decoder.pt, uses the GPU if there is one
speech = tts.say("Merhaba, size nasıl yardımcı olabilirim?", path="merhaba.wav")
print(speech.duration, speech.sample_rate)      # seconds of audio, 48000

say\(\)returns aSpeech: theaudio(a float32 NumPy array, mono, between -1 and 1), itssample\_rate, itsdurationin seconds, and theseedthat made it. Withpath, it also writes a WAV file. A list is made in shared batches, oneSpeechper text, in order; withpath, the clips go into that folder as0\.wav,1\.wav, …:

speeches = tts.say(["Günaydın.", "Siparişiniz yola çıktı.", "İyi günler dileriz."], path="clips")

https://huggingface.co/canberkkkkkk/ema-lightning#stream-hear-it-while-its-being-madestream\(\): hear it while it’s being made

import sounddevice as sd                        # pip install sounddevice

with sd.OutputStream(samplerate=48000, channels=1, dtype="float32") as speaker:
    for chunk in tts.stream("Merhaba! Bu ses siz dinlerken üretiliyor. İlk kelimeyi duyduğunuzda, "
                            "cümlenin geri kalanı çoktan hazır."):
        speaker.write(chunk)

The first chunk is one second of audio and is ready in about 4 ms on a GPU. The rest follows four seconds at a time, far faster than it plays, so playback never waits. Leave the loop early and the rest of that text’s work is dropped. Joined together, the chunks are the same audiosay\(\)gives.

stream\(\)takes one text per call. To stream many texts at once, call it once per text, each from its own thread. All the calls share the model, and Playhead runs them together on the GPU:

from concurrent.futures import ThreadPoolExecutor

texts = ["Merhaba, size nasıl yardımcı olabilirim?", "Siparişiniz yola çıktı.", "Randevunuz onaylandı, görüşmek üzere."]

def stream_one(text):
    chunks = []
    for chunk in tts.stream(text):
        chunks.append(chunk)                    # in a real app: send it to this caller right away
    return chunks

with ThreadPoolExecutor(max_workers=len(texts)) as pool:
    results = list(pool.map(stream_one, texts))  # one list of chunks per text, in the same order

https://huggingface.co/canberkkkkkk/ema-lightning#lightning-the-fast-path-on-nvidia-gpus\.lightning\(\): the fast path on NVIDIA GPUs

tts = EMA().lightning()

Call it once at startup. It measures the best batch size for your GPU (once, then cached), compiles the model, records CUDA graphs, checks them against the plain path and prints when it’s ready, with its first-audio time. Without it, or on a CPU, everything works the same, just slower.

OptionValuesDefaultspeed0.25 to 41.0seednon-negative integer; the same seed gives the same audiorandom (returned inspeech\.seed)sample\_rate48000, 24000, 16000, 800048000path``say\(\)only: a\.wavfile for one text, a folder for a listnone Any text is accepted, and text never raises. Invalid settings raiseValueErrorbefore any work starts.

https://huggingface.co/canberkkkkkk/ema-lightning#one-model-many-callersOne model, many callers

Playhead: callers, the two queues and the GPU, and one turn of the loop

Everysay\(\)andstream\(\)call goes through Playhead, a scheduler insideEMA. A GPU making one sentence at a time is mostly idle, so Playhead collects what every caller needs at that moment and runs it together. It keeps two first-come-first-served queues: sentences waiting for the model, and audio windows waiting for the decoder. Each turn, it takes up to one batch from the front of each, makes them in one pass each on the GPU, and hands every window straight back to the call that asked for it, in order. Nobody’s sentences are overtaken, a caller who hangs up leaves the queues, and a batch that fails fails only its own callers.

On one RTX PRO 6000, 64 streams started at the same instant made 887 seconds of audio in 0.74 seconds, and every one matched its text said alone.

https://huggingface.co/canberkkkkkk/ema-lightning#benchmarksBenchmarks

Freya-TR-Eval, all 495 sentences, speed 1.0:

#SystemParamsSize vs EMAWER %CER %RTF× real timex real time (batch 64)GPU memory1EMA Lightning (compiled)5.6M DiT + 3M decoder1×0.92 ± 0.130.180.0023440×****1,316×—1EMA Lightning (plain PyTorch)5.6M DiT + 3M decoder1×0.92 ± 0.130.180.011587×****952×****0.28 GB; 1.39 GB at batch 642Trendyol-TTS2.38B277×0.98 ± 0.150.19 ± 0.030.3602.8×——3Gemini 3.8 Flash-Lite——1.31 ± 0.130.28 ± 0.02————4Gemini 3.8 Flash——1.35 ± 0.230.28 ± 0.06————5ElevenLabs v4——1.43 ± 0.120.27 ± 0.03————6ElevenLabs v4 Turbo——1.54 ± 0.220.29 ± 0.04————7Anka TTS †336M39×1.73 ± 0.050.41 ± 0.050.1606.3×——8Chatterbox MTL †500M58×1.84 ± 0.200.41 ± 0.080.3792.6×——9Piper (tr, dfki) †16M1.9×3.220.710.06515×——10XTTS-v2 (base) †470M55×3.34 ± 0.381.33 ± 0.200.1407.1×——11MMS-TTS (tr) †36M4.2×6.411.520.01567×——12Antalia 1 ‡305M35×6.88 ± 0.342.72 ± 0.210.3283.0×——13Coqui GlowTTS (tr) †28M3.3×10.752.920.01663×——14FreyaTTS †183M21×12.024.950.1198.4×——15F5 base (no Turkish) †336M39×77.84 ± 0.1332.58 ± 0.540.1466.8×——

  • Our runs(every row without †): all 495 sentences, 3 seeds or takes, raw text in, an 8 kHz band, Whisper large-v3 with beam 5, and Freya’s text normalisation. ElevenLabs uses its default voice (George) with default settings. Gemini uses the voice Kore.
  • † marks numbers as reported on the Anka TTS model card.
  • All RTFs are measured on an RTX 4090, as total generation time divided by total audio length, three passes after a warm-up, middle pass reported. Cloud APIs have no RTF or parameter count.
  • **“±” is the spread across the three seeds.**EMA Lightning’s spread comes from its lowest, highest and average result (0.79 to 1.05%); its CER has no spread.
  • ‡“Antalia 1” by Sezgin Saygili, Emre Kaplaner, Oncel Ozgul and Fikri San Koktas (Patientdesk.ai):huggingface.co/cloud0day3/antalia-1. Three of its takes failed inside its own code and count as silence.

https://huggingface.co/canberkkkkkk/ema-lightning#speed-and-costSpeed and cost

All on an RTX 4090 with\.lightning\(\):

One request440× real time (RTF 0.0023)First audio, typical3.86 ms, from raw text, normalisation includedBatch of 641,316× real time; plain PyTorch 952×A 6 h 7 min audiobook, batchedabout 17 secondsCost at $0.74/hourabout $0.0085 per million characters (24,091 characters a second)CPUabout 6× real time (RTF 0.166, measured on a cloud-container CPU)

https://huggingface.co/canberkkkkkk/ema-lightning#architectureArchitecture

**Figure 1 (top of this card).****(a)Overview. Normalized Turkish text is encoded per letter; a duration predictor and a word–letter timeline place every output frame at a word and a position within it; a windowed aligner builds one condition per frame; a latent generator produces 64-dimensional latents at 25 Hz in four steps; a decoder upsamples them ×1920 to 48 kHz audio.(b)Windowed Gaussian aligner. Each frame attends only to letters of its own word and the adjacent words, with a Gaussian bias around its position.(c)**DiT block with shared timestep modulation. One linear layer maps the timestep embedding to all modulation parameters; each of the six blocks adds a learned offset.

PartFileParametersAcoustic model: text encoder, durations, windowed aligner and the DiT latent generatorema\.pt5.6MVocoder: the HiFi-GAN-style decoder from latents to 48 kHz audiodecoder\.pt3.0MTotal****8.6M The acoustic model stays at 5.6M partly through shared modulation (Fig. 1c, below): one projection serves all six DiT blocks, which takes the modulation parameters from 1.81M to 0.31M.

Text encoder.Input text is normalized to spoken-form Turkish withnormalizer-trand lowercased. Letters are embedded ind= 224 dimensions and encoded by four ConvNeXt blocks followed by two self-attention blocks with RoPE, givingh∈ ℝL×d.

Word–letter timeline.A convolutional duration predictor estimates the number of framesd̂ℓfor each letter ℓ. Durations are grouped by word: each word receives the frames of its letters, and each letter receives a positionπℓ∈ [0, 1] within its wordwℓ. Each output framefis likewise assigned a word*wfand a positionπf*. The duration model is trained without external alignments.

**Windowed Gaussian aligner (Fig. 1b).**Withp=w+π, framefattends to letter ℓ through

αfℓ=softmax⁡ℓ(τqf⊤kℓd−β(pf−pℓ)22σ2),∣wf−wℓ∣≤1,(1)\alpha_{f\ell} = \operatorname{softmax}_{\ell}\left( \tau \, \frac{\mathbf{q}_f^{\top} \mathbf{k}_\ell}{\sqrt{d}} - \beta \, \frac{(p_f - p_\ell)^2}{2\sigma^2} \right), \qquad |w_f - w_\ell| \le 1, \tag{1}

cf=Wo∑ℓαfℓvℓ,(2)\mathbf{c}_f = \mathbf{W}_o \sum_{\ell} \alpha_{f\ell} \, \mathbf{v}_\ell, \tag{2}

whereqfandkℓare rotated by RoPE atp**fandpℓ, andτ,βandσare learned per head. Letters outside the window are masked. The conditionsc∈ ℝT×dare computed once per utterance.

Latent generator with shared modulation (Fig. 1c).Six DiT blocks (8 heads, RoPE over frames, SwiGLU feed-forward) predict a flow-matching velocity on latentsz∈ ℝT×64. Instead of one adaptive-norm projection per block, a single projectionWis shared by all blocks:

[γ1,β1,α1,γ2,β2,α2]k=WSiLU⁡(e(t))+δk,(3)[\boldsymbol{\gamma}_1, \boldsymbol{\beta}_1, \boldsymbol{\alpha}_1, \boldsymbol{\gamma}_2, \boldsymbol{\beta}_2, \boldsymbol{\alpha}_2]_k = \mathbf{W} \, \operatorname{SiLU}(\mathbf{e}(t)) + \boldsymbol{\delta}_k, \tag{3}

Mod⁡(x)=LN⁡(x)⊙(1+γ)+β,(4)\operatorname{Mod}(\mathbf{x}) = \operatorname{LN}(\mathbf{x}) \odot (1 + \boldsymbol{\gamma}) + \boldsymbol{\beta}, \tag{4}

with a learned offsetδk∈ ℝ6×dper block. This reduces the modulation parameters from 1.81M to 0.31M.

**Sampling: four steps, distilled with DMD2.**The latent generator was first trained as an ordinary flow-matching model. That teacher has the same architecture and the same 5.6M parameters, and samples with 16 midpoint steps: 32 passes of the network per sentence. The shipped generator is a student distilled from it with DMD2 (Yin et al., 2024), and it needs four steps, att∈ {0, ¼, ½, ¾}. Each step predicts the clean latents

z^1=zt+(1−t)vθ(zt,t,c),(5)\hat{\mathbf{z}}_1 = \mathbf{z}_t + (1 - t)\, v_\theta(\mathbf{z}_t, t, \mathbf{c}), \tag{5}

**Decoder.**A HiFi-GAN-style generator with upsampling factors (8, 6, 5, 2, 2, 2) maps the 25 Hz latents to 48 kHz audio. Latents are decoded in 4 s windows with 8 frames of context on each side, so the joined output is identical to decoding the whole utterance at once; a stream’s first window is 1 s, so its first audio comes early.

Inference.\.lightning\(\)enables cuDNN benchmark mode, compiles the model once for any batch size, and records CUDA graphs at batch sizes 1, 2, 4 and every multiple of 8 up to the measured batch size, for length buckets of 32, 64, 128 and 256 letters and 40, 80, 160 and 320 frames, and decoder windows of 48 and 120 frames. Each batch is padded only to the nearest recorded size. The compiled path is verified against the eager path at startup.

https://huggingface.co/canberkkkkkk/ema-lightning#filesFiles

FileWhat it isema\.ptacoustic model: text encoder, durations, aligner, latent generator (5.6M)decoder\.ptdecoder, latents to 48 kHz audio (3M)assets/architecture\.pngthe architecture figureassets/playhead\.pngthe Playhead figuresamples/the example clips underListen, made withsay\(\)at seed 0

https://huggingface.co/canberkkkkkk/ema-lightning#training-dataTraining data

An internal corpus of about 1000 hours of single-speaker Turkish speech.

https://huggingface.co/canberkkkkkk/ema-lightning#limitationsLimitations

  • **One voice.**No voice cloning and no emotion control.
  • **Turkish only.**Foreign words are read with Turkish spelling rules.
  • **Naturalness is good, not the best.**UTMOS is about 3.30 on Freya-TR-Eval: level with ElevenLabs v4 (3.30), below Gemini 3.8 (3.52 to 3.61) and Trendyol-TTS (3.83).
  • **WER is measured by Whisper,**so some of the counted errors are the recogniser’s.

https://huggingface.co/canberkkkkkk/ema-lightning#responsible-useResponsible use

EMA Lightning makes synthetic speech, and people who hear it should know that. Tell listeners they are hearing an AI-generated voice, for example at the start of a call (“Bu görüşmede yapay zekâ ile üretilmiş bir ses kullanılmaktadır.”), and label the audio you publish the same way (“Bu ses yapay zekâ ile üretilmiştir.”); laws in many places, including the EU AI Act, require this disclosure. Do not present it as a real person’s voice, use it in scams or impersonation, or make it say things a real person did not say. Numbers, dates and abbreviations are read through automatic text normalization, which can misread unusual input, so for banking, health, legal or emergency messages, have a person check the text and the audio before it reaches anyone.

https://huggingface.co/canberkkkkkk/ema-lightning#disclaimerDisclaimer

EMA Lightning is provided “as is”, without warranty of any kind, under the Apache License 2.0. The authors are not liable for how it is used or for any damage arising from its use. Whoever deploys it is responsible for using it lawfully, including the disclosure and consent rules that apply where it is used. This section is not legal advice.

https://huggingface.co/canberkkkkkk/ema-lightning#licenseLicense

Apache 2.0, for the weights and the code. Commercial use included.

https://huggingface.co/canberkkkkkk/ema-lightning#acknowledgementsAcknowledgements

Special thanks to**Erdem Tuna**, who builtnormalizer-tr, the Turkish text normalizer behind EMA Lightning. Every number, date, amount and symbol you hear read aloud goes through his work.

Thanks also toFreyafor the Freya-TR-Eval benchmark, and tothe Anka TTS authorsfor the published baselines marked †.

https://huggingface.co/canberkkkkkk/ema-lightning#citationCitation

@misc{aslan2026emalightning,
  title        = {EMA Lightning: Tiny, Fast and Accurate Turkish Text to Speech},
  author       = {Aslan, Canberk},
  year         = {2026},
  howpublished = {\url{https://huggingface.co/canberkkkkkk/ema-lightning}}
}

Similar Articles

FreyaTTS Technical Report

arXiv cs.CL

FreyaTTS is a compact, tokenizer-free Turkish-first text-to-speech model based on a non-autoregressive conditional flow-matching Diffusion Transformer, achieving state-of-the-art performance with a fraction of the parameters of larger systems and released under Apache-2.0.

Edge0/Audio8-TTS-Realistic-0.6b

Hugging Face Models Trending

Edge0 released Audio8-TTS-Realistic-0.6B, a compact 0.6B DualAR text-to-speech model with improved speech realism, zero-shot voice cloning, and support for 23 languages (12 more than the Preview version), bundled with a 44.1 kHz neural audio codec and an SGLang serving adapter.

Edge0/Audio8-TTS-Realistic-Air

Hugging Face Models Trending

Edge0/Audio8-TTS-Realistic-Air is an Apache-2.0 text-to-speech model finetuned from Edge0/Audio8-TTS-Preview-0.6b, supporting zero-shot voice cloning and multilingual synthesis across 25+ languages (zh, en, ja, ko, de, fr, es, and more).