CoolFace
Modelpublic

kristijonas/paprika-whisper-lt-v3

sourceHugging Facecc-by-4.0updated 13d agoView on Hugging Face
4likes1.8kdownloads
Model Card

paprika-whisper-lt-v3 - 🌶️ Lithuanian ASR (LIEPA-3 fine-tune)

Third generation of a Lithuanian fine-tune of whisper-large-v3-turbo. Trained on ~3,281 h of LIEPA-3, warm-started from `paprika-whisper-lt`.

Ready-to-run pipelines for real-time subtitles and long-file transcription with punctuation and speaker labels: https://github.com/kristijonasatpro/paprika

Read this before you use it

Use long-form decoding. Do not use `chunk_length_s`.

The chunked pipeline cuts audio at a fixed stride, transcribes each window independently, then merges by matching text in the overlaps — and where the two sides disagree it discards the span it cannot align. Measured 2026-08-15 on clean audio, it silently dropped 30 words from one recording and 52 from another, both at a seam. The output reads perfectly. There is no marker that anything is missing.

It also invents text on non-speech. On 60 s of digital silence, 60 s of faint hiss and 60 s of room tone, the chunked pipeline produced 24–164 characters of confident Lithuanian; native long-form produced zero characters on all three. This is a property of the chunked decoder, not of the weights — the previous generation behaves the same way.

python
import torch
from transformers import WhisperForConditionalGeneration, WhisperProcessor

m = "kristijonas/paprika-whisper-lt-v3"
proc = WhisperProcessor.from_pretrained(m, language="lithuanian", task="transcribe")
model = WhisperForConditionalGeneration.from_pretrained(m, dtype=torch.float16).to("mps").eval()

feats = proc(audio, sampling_rate=16000, return_tensors="pt",
             truncation=False, padding="longest", return_attention_mask=True)
ids = model.generate(feats.input_features.to("mps", torch.float16),
                     attention_mask=feats.attention_mask.to("mps"),
                     language="lithuanian", task="transcribe",
                     return_timestamps=True, condition_on_prev_tokens=False,
                     temperature=(0.0, 0.2, 0.4, 0.6, 0.8, 1.0),
                     logprob_threshold=-1.0, compression_ratio_threshold=1.35,
                     no_speech_threshold=0.6)
print(proc.batch_decode(ids, skip_special_tokens=True)[0])

Native long-form holds the whole feature sequence in memory (~18 GB for 22 minutes with word timestamps). For long recordings, cut into pause-aligned blocks under 30 s and decode each independently — chunk_longform.py in the repo above does this in bounded memory (5.4 GB flat regardless of duration).

Third-party tools take the chunked path. whisper-standalone-win, faster-whisper and WhisperX do their own fixed-stride segmentation and do not run a punctuation tagger, so you get ~30-second blocks of unpunctuated lowercase text. That is the path described above, not a property of the weights. Measured on a 57-minute press event: this model produced 6,681 words against stock large-v3's 6,341, so 5.4% MORE content, but in 132 rigid ~30 s blocks with zero punctuation, which reads far worse side by side. Use transcribe_file.py from the repo for long files.

Output has no punctuation and no casing. That is by design: the LIEPA-3 labels have neither, and a separate tagger does the job better. The repo ships one with a word-preservation contract (comma 84.7 / period 88.5 / casing 91.7 F1).

Results

If you want the lowest WER, use [`akisviete/azuolas-whisper-lt`](https://huggingface.co/akisviete/azuolas-whisper-lt) instead. Its authors benchmarked all three Lithuanian models through this project's own harness and normalisation, which makes it the fairest comparison available: on FLEURS LT, Ąžuolas 9.05 against this model's 12.06; on Common Voice LT, 4.60 against 8.60. This model is a 0.81 B turbo against their 1.55 B large-v3, and runs about 3x faster (83x vs 26x realtime), which is the trade it exists to make.

The table below compares this model only against its own earlier versions.

v1v2**v3**
gold-11 WER (chunked)15.8715.5015.29
gold-11 WER (long-form)17.9417.6817.25
heldout-39 WER (chunked)5.455.135.16
heldout-39 WER (long-form)8.447.676.42
valid timestamp share——1.00

The long-form column is where this generation earns its keep. v1's card advised chunked-only inference because its long-form decoding was broken (+23 WER gap); v3's gap is +1.26 to +1.96, so long-form is now the correct default.

Both benchmarks are in-domain — same sources as training. There is no valid out-of-domain number: the sealed FLEURS set built for it turned out to be 44/44 digital silence, and every figure derived from it was withdrawn. The honest out-of-domain evidence is A/B comparison against a commercial API on real recordings (press conference, two-person call, phone recordings), where v3 was competitive and visibly better on dialect speech.

Training

Warm-started from v2 (itself from v1, from svogunas/whisper-large-v3-turbo-lt). 37,500 steps, effective batch 32, one L40S, ~29 GPU-hours.

Mix: 50% spontaneous, 30% read, 12% phonetic, 5% dialect, 3% VoxPopuli LT. The dialect slice spans all four regions (Aukštaitija, Žemaitija, Dzūkija, Suvalkija). Its transcripts carry stress marks and non-standard vowels (ɜ ə ɘ), which would teach the model to emit them, so they were normalised to standard orthography before training — conservatively, leaving any word the normaliser could not confidently map. That dialect data, not the raw volume, is what this generation actually bought: tripling the hours moved WER ~0.2 points.

Limitations

  • —Lowercase, unpunctuated output (see above).
  • —Realized-speech convention: transcribes turim, not normative turime, because that is what LIEPA-3 labels do.
  • —16 kHz mono. Parliamentary and spontaneous speech dominate the training mix.
  • —No out-of-domain benchmark. Test on your own audio before relying on it.

Attribution

  • —Data: LIEPA-3 garsynas (CC BY 4.0, VU / raštija.lt) — dėkojame. VoxPopuli (Meta AI).
  • —Base lineage: svogunas/whisper-large-v3-turbo-lt (CC BY 4.0).
  • —Built for kalamo.ai — Lithuanian speech tooling.