Skip to page content
SAVRN
Search Plan a project

Open-weight model · Speech recognition

whisper-large-v3-turbo-us-atc-v2

by Thomas Eibner thomaseibner/whisper-large-v3-turbo-us-atc-v2

whisper-large-v3-turbo-us-atc-v2 is an open-weight model for speech recognition from Thomas Eibner, released under MIT License. Its published files total 13.2 MB. It draws 35 downloads a month.

A 13 MB LoRA adapter that fine-tunes OpenAI's whisper-large-v3-turbo for US air-traffic-control radio. It is not a full model: the stock turbo weights (~1.6 GB) come from Hugging Face and the adapter is applied on top. MIT licensed.

Parameters—
Context—
Weights13.1 MB
Licensemit
AccessOpen weights
Monthly Downloads35

Model Card

By Thomas Eibner, published under mit, revision 4db502156d9d.

A 13 MB LoRA adapter that fine-tunes OpenAI's whisper-large-v3-turbo for US air-traffic-control radio. It is not a full model: the stock turbo weights (~1.6 GB) come from Hugging Face and the adapter is applied on top. MIT licensed. On 156 held-out ATC clips it takes word error rate from 56% to 32% and identifier recall (callsign digits, airline names, runway sides) from You need Python 3.10+ and ffmpeg on your PATH. The first run downloads the base model. The script uses CUDA, Apple MPS or the CPU, whichever it finds (--device overrides), and prints one file transcript line per input. Feed it one radio transmission per file, cut at the squelch or by a voice-activity detector. That is what…

Read Thomas Eibner's full model card

US ATC fine-tune for Whisper large-v3-turbo — LoRA adapter (v2)

A 13 MB LoRA adapter that fine-tunes OpenAI's whisper-large-v3-turbo for US air-traffic-control radio. It is not a full model: the stock turbo weights (~1.6 GB) come from Hugging Face and the adapter is applied on top. MIT licensed.

On 156 held-out ATC clips it takes word error rate from 56% to 32% and identifier recall (callsign digits, airline names, runway sides) from 53% to 83%.

Quick start

You need Python 3.10+ and ffmpeg on your PATH.

pip install -U huggingface_hub                  # for the `hf` command
hf download thomaseibner/whisper-large-v3-turbo-us-atc-v2 --local-dir whisper-atc   # or unpack the tarball
cd whisper-atc
pip install -r requirements.txt                 # torch, transformers, peft, numpy
python transcribe.py clip1.mp3 clip2.wav

The first run downloads the base model. The script uses CUDA, Apple MPS or the CPU, whichever it finds (--device overrides), and prints one file<TAB>transcript line per input.

Feed it one radio transmission per file, cut at the squelch or by a voice-activity detector. That is what it was trained on.

Applying the adapter in your own code

It is a standard PEFT adapter, so it is one extra line on top of ordinary Whisper code:

import torch
from peft import PeftModel
from transformers import WhisperForConditionalGeneration, WhisperProcessor
from transcribe import load_audio        # from this repo: ffmpeg decode + the training audio chain

base = "openai/whisper-large-v3-turbo"
processor = WhisperProcessor.from_pretrained(base)
model = WhisperForConditionalGeneration.from_pretrained(base, torch_dtype=torch.float32)
model = PeftModel.from_pretrained(model, "thomaseibner/whisper-large-v3-turbo-us-atc-v2").merge_and_unload()   # <- the fine-tune
model = model.to("cuda", torch.float16).eval()

audio = load_audio("clip.mp3", radio_filter=True)      # 16 kHz mono float32
features = processor(audio, sampling_rate=16000, return_tensors="pt").input_features
ids = model.generate(features.to("cuda", torch.float16), language="en", task="transcribe",
                     max_new_tokens=96, repetition_penalty=1.1)
print(processor.batch_decode(ids, skip_special_tokens=True)[0])

PeftModel.from_pretrained takes the repo id or a local directory holding adapter_config.json. Merge in float32 and cast afterwards: merging at half precision rounds every adapted weight twice.

Results

156 clips from days held out of training entirely, scored against human transcriptions:

word error rate identifier recall
stock whisper-large-v3-turbo 56.2% 52.8%
with this adapter 32.3% 82.8%

The eval set deliberately over-samples clips our previous pipeline struggled with. By pool:

pool clips what is in it stock WER adapter WER
control 63 clips the previous pipeline handled fine 41.9% 24.6%
hard 68 previous transcript was low-confidence, or no callsign could be extracted from it 60.8% 38.0%
fail 25 previous pipeline returned 10 characters or fewer 92.9% 39.3%
  • WER is word-level edit distance after normalising both sides: lower-cased, punctuation stripped (decimal points kept), 30L → 30 left, FL380 → flight level 380, ICAO airline codes → spoken names.
  • Identifier recall is the share of the reference's identifiers that appear in the transcript, counted as a bag (order ignored): digit groups, airline names, runway side (left/right/center) and wake category (heavy/super). These are the tokens you need to match a transmission to an aircraft, and on a seven-word clip WER is dominated by filler.

Getting good results

  • Match the audio chain. Every training and eval clip went through the same chain: 8 kHz AM airband audio resampled to 16 kHz mono, a 6th-order 300–3400 Hz bandpass, then EBU R128 loudness normalisation to −16 LUFS. transcribe.py applies it by default (--no-radio-filter skips it). To pre-filter files for another runtime:

bash ffmpeg -i in.mp3 -af "aresample=16000,highpass=f=300:poles=2,highpass=f=300:poles=2,highpass=f=300:poles=2,lowpass=f=3400:poles=2,lowpass=f=3400:poles=2,lowpass=f=3400:poles=2,loudnorm=I=-16:TP=-1.5:LRA=11" -ar 16000 -ac 1 out.wav

Our own production pipeline feeds raw clips without the filter and does fine; it matters more the less your audio sounds like ours. - Force English, and do not prompt. Pass language="en", and no initial_prompt / prompt_ids. Every prompt we tested made results worse: a static ATC-phraseology prompt cost 16.7 WER points on stock Whisper, and on our first fine-tune a prompt listing the callsigns in the area took repetition loops from 1.7% to 13.3% of clips while recovering none of those callsigns. - Drop repetition loops. On silence or static the model can loop (Point Point Point…). transcribe.py flags these with the rule our production pipeline uses (8+ words, and either a run of 5 identical words or under 40% unique words). Discard whatever it flags. - Keep clips short. Whisper sees 30 s at a time. Split long recordings into transmissions rather than relying on long-form decoding. - fp16 is fine for inference; it is what we run in production. Avoid int8 quantisation (see faster-whisper below). bf16 weights produced repetition loops during training; bf16 inference is untested.

What the output looks like:

  • Numbers come out as digits, grouped the way they were spoken: Delta 2261, flight level 380, 12 right, and frequencies such as 124 85 for 124.85. Normalise downstream if you need canonical forms.
  • Waypoint and fix names come out as they sound (near Pagaz for PAGOZ). That is deliberate: the training transcripts wrote what was said rather than the resolved identifier, so the model does not learn Minneapolis-only names. Resolve names against your own fix data.

Other runtimes

The published numbers come from the transformers decode in transcribe.py. faster-whisper and mlx-whisper have their own decoders and feature extraction, so their transcripts differ slightly: mostly punctuation, and occasionally a word on the hardest clips (see Verification).

Both conversions start from a merged model:

python merge.py --out whisper-atc-turbo-v2-hf      # ~3.2 GB, float32

The merged directory can be deleted once converted.

faster-whisper (CTranslate2) — NVIDIA GPUs and CPUs

pip install ctranslate2 faster-whisper
ct2-transformers-converter --model whisper-atc-turbo-v2-hf --output_dir whisper-atc-turbo-v2-ct2 \
    --quantization float16 --copy_files tokenizer.json preprocessor_config.json
from faster_whisper import WhisperModel
from transcribe import load_audio

model = WhisperModel("whisper-atc-turbo-v2-ct2", device="cuda", compute_type="float16")
# on a CPU: WhisperModel(..., device="cpu", compute_type="float32")
segments, _ = model.transcribe(load_audio("clip.mp3", radio_filter=True), language="en", beam_size=5)
print(" ".join(s.text.strip() for s in segments))

--copy_files is not optional. faster-whisper reads the tokenizer and the feature-extractor settings from the model directory, and without them it quietly falls back to defaults that do not match this model.

Avoid compute_type="int8", even though it is the usual advice for CPUs. In our check it made errors that float32 did not: Alaska 216 came out as Alaska 266, and Clear to land as Slow land.

mlx-whisper — Apple Silicon

pip install mlx-whisper safetensors
python to_mlx.py --hf whisper-atc-turbo-v2-hf --out whisper-atc-turbo-v2-mlx
mlx_whisper --model whisper-atc-turbo-v2-mlx --language en clip.mp3

or from Python:

import mlx_whisper
from transcribe import load_audio

result = mlx_whisper.transcribe(load_audio("clip.mp3", radio_filter=True),
                                path_or_hf_repo="whisper-atc-turbo-v2-mlx", language="en")
print(result["text"])

to_mlx.py exists because mlx-whisper ships no converter for Hugging Face checkpoints. It checks every tensor against the stock mlx-community/whisper-large-v3-turbo and refuses to write a model that does not match.

Limitations

  • One area. All training audio came from receivers near Minneapolis–St Paul: KMSP tower, approach and departure, Minneapolis TRACON, St Paul tower, and about a dozen Minneapolis Center sectors. On the center frequencies it is mostly the pilot side, because the controllers' transmitters are out of range. It has not been scored anywhere else. The phraseology should carry over; local accents, the airline mix and fix names will not.
  • Small corpus. About 440 transcribed clips, 31 minutes of speech. Callsign digits are still wrong on a fair share of clips (Delta 766 came out as Delta 762).
  • Thin vocabulary. Terms that were rare in training come out as similar-sounding English: we have seen ILS as "island" and "honest", and VFR as "BFR". v2 added examples, but coverage of these, of squawk and of emergency phraseology is still thin.
  • Not for operational use.

Training details

base model openai/whisper-large-v3-turbo; tested against revision 41f01f3f
method LoRA r=16, α=32, dropout 0.05, on q_proj/v_proj in every attention block: 3.3 M trainable parameters, 0.4% of the model
data 442 transcribed clips (30.7 min), each plus 4 augmented copies = 2,210 training files
augmentation tempo 0.85–1.15×, pitch ±4 semitones, gain ±6 dB, white noise 1–8%; bandpass and loudness chain re-applied after augmenting
schedule 3 epochs, learning rate 1e-3, batch 2 × 4 gradient accumulation
precision fp32 weights, bf16 autocast, gradient checkpointing
hardware one RTX 4090, about 12 minutes
eval 156 clips from days that contributed nothing to training

Verification

Checked on 2026-09-10 against 8 of the held-out clips, whose transcripts from the evaluation run are on record:

what result
transcribe.py (torch 2.13, transformers 5.16.1, peft 0.20.0) 8/8 identical to the evaluation transcripts, on Apple MPS (fp16) and on CPU (fp32)
merge.py writes a complete model directory: weights, config, tokenizer, preprocessor config
to_mlx.py reproduced all 586 weight tensors of the MLX build we run in production, bit for bit. mlx-whisper 0.4.3 with default settings matched 5/8; the other 3 differed in punctuation or on the two hardest clips
faster-whisper (ctranslate2 4.8.2, faster-whisper 1.2.1), float16 conversion float32 on CPU with beam 5 matched 3/8; the rest differed in punctuation, one fix-name spelling (Pegaz for Pagaz) and the two hardest clips. int8 added the two errors above

Files

README.md                      this file
LICENSE                        MIT
adapter_config.json            PEFT config
adapter_model.safetensors      the weights (13 MB)
transcribe.py                  apply the adapter and transcribe
merge.py                       write a standalone merged model
to_mlx.py                      merged model -> mlx-whisper format
requirements.txt
SHA256SUMS                     tarball only; check with: shasum -a 256 -c SHA256SUMS

License

MIT — see LICENSE. The base model, openai/whisper-large-v3-turbo, is MIT-licensed by OpenAI.

Identity and Version

Repository
thomaseibner/whisper-large-v3-turbo-us-atc-v2
Publisher
Thomas Eibner
Task
Speech recognition
Modality
Audio
Library
peft
Parameters
Not stated by the source
Languages
en
Revision
4db502156d9de03d44a1985ad4777447ad339e22
First published
2026-09-11
Last updated
2026-09-22

Files and Weights

9 files, 13.2 MB in total. The weights are 1 file totalling 13.1 MB in safetensors.

Weights1 file · 13.1 MB
Configuration4 files · 16.4 KB
Documentation2 files · 13.0 KB
Other1 file · 440 B
Repository1 file · 1.5 KB
Every file
FileTypeSizeSHA-256
adapter_model.safetensorsWeights13.1 MB eb92ffa1d43a
adapter_config.jsonConfiguration1.2 KB —
merge.pyConfiguration3.1 KB —
to_mlx.pyConfiguration6.1 KB —
transcribe.pyConfiguration6.0 KB —
LICENSEDocumentation1.1 KB —
README.mdDocumentation11.9 KB —
requirements.txtOther440 B —
.gitattributesRepository1.5 KB —

License and Download

License
mit
Access
Open weights, no gate
Download size
13.1 MB
Download from Thomas Eibner

Released by Thomas Eibner through its official repository on Hugging Face. Read the license.

Built From

Memory Requirements

PrecisionWeights in memory
As published13.1 MB

Weights only, from the published parameter count; the key-value cache and runtime add to this.

Questions About whisper-large-v3-turbo-us-atc-v2

Can I use whisper-large-v3-turbo-us-atc-v2 commercially?

Yes. whisper-large-v3-turbo-us-atc-v2 is released under MIT License. The MIT License is a short permissive license. It permits commercial use, modification and redistribution, provided the copyright notice and permission notice are included.

Similar Models

Model · Speech recognition

wav2vec2-large-xlsr-53-japanese

Jonatas Grosman

Fine-tuned facebook/wav2vec2-large-xlsr-53 on Japanese using the train and validation splits of Common Voice 6.1, CSS10 and JSUT. When using this model, make sure that your speech input is sampled at 16kHz. This model has been fine-tuned thanks to the GPU credits generously given by the OVHcloud:) The script used for training can be found here: https://github.com/jonatasgrosman/wav2vec2-sprint The model can be used directly (without a language model) as follows... Using the HuggingSound library: The model can be evaluated as follows on the Japanese test data of Common Voice. In the table below I report the Word Error Rate (WER) and the Character Error Rate (CER) of the model. I ran the…

Open weights apache-2.0 transformers

Model · Speech recognition

whisperkit-coreml

Argmax

WhisperKit is part of Argmax OSS, an On-device Speech AI SDK for Apple Silicon: https://github.com/argmaxinc/argmax-oss-swift Check out the WhisperKit paper and presentation from ICML 2025: https://icml.cc/virtual/2025/47854 For real-time transcription with speakers and custom vocabulary, check out Argmax Pro SDK: https://www.argmaxinc.com/blog/argmax-sdk-2

Open weights mit whisperkit

Model · Speech recognition

speaker-diarization-3.1

Pyannote

Using this open-source model in production? Consider switching to pyannoteAI for better and faster options. This pipeline is the same as pyannote/speaker-diarization-3.0 except it removes the problematic use of onnxruntime. Both speaker segmentation and embedding now run in pure PyTorch. This should ease deployment and possibly speed up inference. It requires pyannote.audio version 3.1 or higher. It ingests mono audio sampled at 16kHz and outputs speaker diarization as an Annotation instance: - stereo or multi-channel audio files are automatically downmixed to mono by averaging the channels. - audio files sampled at a different rate are resampled to 16kHz automatically upon loading. 1.…

Access requested at publisher mit pyannote-audio

Model · Speech recognition

speaker-diarization-community-1

Pyannote

This pipeline ingests mono audio sampled at 16kHz and outputs speaker diarization. - stereo or multi-channel audio files are automatically downmixed to mono by averaging the channels. - audio files sampled at a different rate are resampled to 16kHz automatically upon loading. The main improvements brought by Community-1 are: - improved speaker assignment and counting - simpler reconciliation with transcription timestamps with exclusive speaker diarization - easy offline use (i.e. without internet connection) - (optionally) hosted on pyannoteAI cloud 1. pip install pyannote.audio 3. Create access token at hf.co/settings/tokens. Out of the box, Community-1 is much better than…

Access requested at publisher cc-by-4.0 pyannote-audio

Model · Speech recognition

faster-whisper-small

Systran

This repository contains the conversion of openai/whisper-small to the CTranslate2 model format. This model can be used in CTranslate2 or projects based on CTranslate2 such as faster-whisper. The original model was converted with the following command: Note that the model weights are saved in FP16. This type can be changed when the model is loaded using the computetype option in CTranslate2. For more information about the original model, see its model card.

Open weights mit ctranslate2

Fine-tuned facebook/wav2vec2-large-xlsr-53 on Portuguese using the train and validation splits of Common Voice 6.1. When using this model, make sure that your speech input is sampled at 16kHz. This model has been fine-tuned thanks to the GPU credits generously given by the OVHcloud:) The script used for training can be found here: https://github.com/jonatasgrosman/wav2vec2-sprint The model can be used directly (without a language model) as follows... Using the HuggingSound library: 1. To evaluate on mozilla-foundation/commonvoice60 with split test 2. To evaluate on speech-recognition-community-v2/devdata If you want to cite this model you can use this

Open weights apache-2.0 transformers