kristijonas/paprika-whisper-lt-v3
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.
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.
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 normativeturime, 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.
