Instructions to use ufakai/ufakzeka-kulak with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- NeMo
How to use ufakai/ufakzeka-kulak with NeMo:
import nemo.collections.asr as nemo_asr asr_model = nemo_asr.models.ASRModel.from_pretrained("ufakai/ufakzeka-kulak") transcriptions = asr_model.transcribe(["file.wav"]) - Transformers
How to use ufakai/ufakzeka-kulak with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("automatic-speech-recognition", model="ufakai/ufakzeka-kulak")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("ufakai/ufakzeka-kulak", device_map="auto") - MLX
How to use ufakai/ufakzeka-kulak with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download ufakai/ufakzeka-kulak --local-dir ufakzeka-kulak
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
ufakzeka-kulak
ufakzeka-kulak ("kulak" is Turkish for ear) is a small Turkish speech-to-text model by ufak AI, trained from scratch and released under Apache-2.0. It runs offline on a CPU, and the same weights ship for NeMo, transformers, sherpa-onnx, parakeet.cpp (GGUF) and parakeet-mlx (MLX).
Read this before you try it
The model is meant for transcribing Turkish speech offline on a laptop, a server or in a browser page, where a licence that allows commercial use and a known list of training sources matter. Identifying, tracking or profiling the people who speak is out of scope, and so is acting on a transcript about a person without someone checking it.
Limits:
- Turkish only. Other languages, and Turkish mixed with them, were not measured.
- One utterance at a time. The released model is not a streaming model. Cut long recordings into utterances with voice activity detection and keep a few hundred milliseconds of audio around each one, as the demo does; a segment cut tight to the speech can come out without capitals or punctuation.
- The model does not say who is speaking. Word timestamps were not evaluated.
- Numbers come out as words. An application that needs digits must convert them.
- Commas are often missed, with a comma recall of 0.359 on FLEURS Turkish test and 0.612 on Common Voice 27.0 Turkish test, against 0.839 and 0.939 for full stops.
- Conversational speech is much harder. On Real-TurnTurk, a conversational test set with 6 speakers, normalised WER is 0.3807, against 0.0921 on FLEURS Turkish test; with so few speakers its interval is wide.
- Most of the training text of the YODAS recordings (4,326.6 of 4,696.2 hours) was written by one speech recogniser and checked by another, wherever no uploader subtitle agreed with that transcript (see Training data). Errors on which the two agreed remain in the training text.
- Accuracy by speaker gender, age or regional accent was not measured.
- Each runtime has its own limits, listed under Known limitations.
Model summary
| Property | Value |
|---|---|
| Developed by | ufak AI |
| Model | ufakzeka-kulak |
| Licence | Apache-2.0 |
| Language | Turkish |
| Size | 31,564,935 parameters; the NeMo file is 126,955,520 bytes |
| Architecture | FastConformer encoder (16 layers, width 256, 4 attention heads, time reduction 8) feeding two outputs, a token-and-duration transducer (TDT) decoder with durations 0, 1, 2, 3, 4 and a CTC head |
| Vocabulary | SentencePiece BPE, 1,024 pieces, trained only on the training transcripts after decontamination |
| Input | mono audio at 16,000 Hz, 80 log-mel features, one utterance at a time |
| Output | written Turkish, described below |
| Training | from scratch, on the sources listed under Training data; the training of this model ran on A100 80 GB GPUs for 52.3 GPU session hours, counting setup, validation passes and checkpoint saves as well as training steps |
Output. The model writes Turkish with capital letters, full stops, commas, question marks, apostrophes and hyphens inside words. Other punctuation in the training text was mapped to these (an exclamation mark to a full stop, a semicolon or a colon to a comma). Numbers are written as words, so the model writes "iki bin yirmi ikide", not 2022'de. Circumflex letters are written without the circumflex.
Files and how to run them
| Path in the repo | Runtime | Precision | Checked against NeMo on the development sets |
|---|---|---|---|
ufakzeka-kulak.nemo |
NeMo 3.0.0 | float32 | the reference |
transformers-tdt/, transformers-ctc/ |
transformers 5.20.1 | float32 | the same token ids as NeMo on every utterance (TDT yes, CTC yes) |
sherpa-onnx-tdt/, sherpa-onnx-ctc/ |
sherpa-onnx 1.13.8 | float32 and int8 | every shipped file, at input gain 8; CTC with the caller pad |
gguf/ |
parakeet.cpp v0.6.1 | f32 and q8_0 | both files, TDT and CTC |
mlx/ |
parakeet-mlx 0.5.3 with mlx 0.32.4, and our feature function | float32 weights, run at bfloat16 | TDT, with our feature function |
LICENSE, ATTRIBUTION.md |
the Apache-2.0 licence of the weights, and the credits of every data source |
"Checked" means that the file was decoded on Common Voice dev and FLEURS Turkish dev and its normalised WER stayed within 0.0050 of NeMo's on both sets, with no failed utterance; a reduced copy (int8, q8_0, or the MLX weights run at bfloat16) was also held to its full-precision copy by the same margin. The sherpa-onnx, parakeet.cpp and parakeet-mlx checks ran under Linux x86-64; parakeet.cpp and parakeet-mlx ran on the CPU. One SHA256SUMS file at the top level lists every file of the release, LICENSE and ATTRIBUTION.md included, except this card, SHA256SUMS itself and the Hub's .gitattributes. Add "SHA256SUMS" to the allow_patterns below, then sha256sum -c --ignore-missing SHA256SUMS (GNU coreutils) in the download folder checks the files you fetched.
Everything is in this one repo. The NeMo file sits at the top level, where NeMo's from_pretrained looks for it, and NeMo downloads only that file; each other runtime has its own folder, and the examples below download only the folder they use.
The examples below read speech.wav, one utterance of mono audio at 16,000 Hz. The install lines name the versions the files were checked with.
NeMo
The checkpoint holds both the TDT decoder and the CTC head; TDT is the default.
pip install "nemo_toolkit[asr]==3.0.0"
from nemo.collections.asr.models import ASRModel
model = ASRModel.from_pretrained("ufakai/ufakzeka-kulak")
print(model.transcribe(["speech.wav"])[0].text)
model.change_decoding_strategy(decoder_type="ctc") # the CTC head
print(model.transcribe(["speech.wav"])[0].text)
transformers
pip install transformers==5.20.1 torch soundfile==0.14.0 huggingface_hub==1.33.0
import soundfile as sf
import torch
from huggingface_hub import snapshot_download
from transformers import ParakeetForTDT, ParakeetProcessor
root = snapshot_download("ufakai/ufakzeka-kulak", allow_patterns=["transformers-tdt/*"])
folder = f"{root}/transformers-tdt"
processor = ParakeetProcessor.from_pretrained(folder)
model = ParakeetForTDT.from_pretrained(folder, dtype=torch.float32).eval()
audio, rate = sf.read("speech.wav", dtype="float32")
if audio.ndim > 1: # a stereo file: average the channels
audio = audio.mean(axis=1)
inputs = processor(audio=audio, sampling_rate=rate, return_tensors="pt")
with torch.no_grad():
out = model.generate(**inputs)
print(processor.decode(out.sequences[0], skip_special_tokens=True))
ParakeetForCTC with the transformers-ctc folder works the same way (generate returns the token ids; decode them with processor.batch_decode). pipeline("automatic-speech-recognition", model=folder) also runs both folders; its text matched generate on 10 of 10 utterances of each development set, for both folders.
sherpa-onnx
Two settings belong to the use of these files. The input gain multiplies the samples before decoding; it is 8 for the shipped files. A gain above one is a workaround for a difference between the sherpa-onnx front end and NeMo's (see Known limitations), and it holds at normal recording levels, not on quiet audio. For the CTC file, the caller pad appends zeros after the gain so that the runtime keeps the last encoder frame, which often carries the final punctuation mark. The pad is defined for audio at 16,000 Hz, the model's input rate; resample other audio first.
pip install sherpa-onnx==1.13.8 numpy soundfile==0.14.0 huggingface_hub==1.33.0
import numpy as np
import sherpa_onnx
import soundfile as sf
from huggingface_hub import snapshot_download
GAIN = 8 # the input gain of the shipped files
def caller_pad(samples, hop=160, multiple=8):
"""Zeros up to the next multiple of 8 feature frames (CTC file only, 16 kHz audio)."""
frames = (len(samples) + hop // 2) // hop
target = -(-frames // multiple) * multiple
pad = max(0, hop * target - hop // 2 - len(samples))
return np.concatenate([samples, np.zeros(pad, dtype=np.float32)])
root = snapshot_download("ufakai/ufakzeka-kulak", allow_patterns=["sherpa-onnx-tdt/*", "sherpa-onnx-ctc/*"])
audio, rate = sf.read("speech.wav", dtype="float32")
if audio.ndim > 1: # a stereo file: average the channels
audio = audio.mean(axis=1)
assert rate == 16000, "resample to 16 kHz first: the model and the caller pad expect it"
samples = (audio * np.float32(GAIN)).astype(np.float32)
tdt = sherpa_onnx.OfflineRecognizer.from_transducer(
encoder=f"{root}/sherpa-onnx-tdt/encoder.int8.onnx",
decoder=f"{root}/sherpa-onnx-tdt/decoder.int8.onnx",
joiner=f"{root}/sherpa-onnx-tdt/joiner.int8.onnx",
tokens=f"{root}/sherpa-onnx-tdt/tokens.txt",
sample_rate=16000, feature_dim=80,
decoding_method="greedy_search", model_type="nemo_transducer",
)
s = tdt.create_stream()
s.accept_waveform(rate, samples)
tdt.decode_stream(s)
print(s.result.text)
ctc = sherpa_onnx.OfflineRecognizer.from_nemo_ctc(
model=f"{root}/sherpa-onnx-ctc/model.int8.onnx",
tokens=f"{root}/sherpa-onnx-ctc/tokens.txt",
sample_rate=16000, feature_dim=80, decoding_method="greedy_search",
)
s = ctc.create_stream()
s.accept_waveform(rate, caller_pad(samples))
ctc.decode_stream(s)
print(s.result.text)
The files without .int8 are the float32 copies. The browser demo runs the int8 TDT files at gain 8 in the sherpa-onnx WebAssembly build; the Android and iOS builds were not tried. In any build, apply the gain and, for CTC, the pad before decoding.
GGUF with parakeet.cpp
One GGUF file holds both the TDT decoder and the CTC head. Download it, build the command-line tool from the tag named above and choose the decoder with --decoder. The build switches off the parts of parakeet.cpp that need other submodules, as the checked build did.
pip install huggingface_hub==1.33.0
hf download ufakai/ufakzeka-kulak gguf/ufakzeka-kulak-f32.gguf --local-dir .
git clone --branch v0.6.1 https://github.com/mudler/parakeet.cpp
cd parakeet.cpp
git submodule update --init third_party/ggml
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DPARAKEET_BUILD_CLI=ON -DPARAKEET_BUILD_SERVER=OFF \
-DPARAKEET_BUILD_TESTS=OFF -DPARAKEET_WITH_CED=OFF -DPARAKEET_WITH_VOICEDETECT=OFF
cmake --build build --target parakeet-cli
./build/examples/cli/parakeet-cli transcribe --model ../gguf/ufakzeka-kulak-f32.gguf --input ../speech.wav
./build/examples/cli/parakeet-cli transcribe --model ../gguf/ufakzeka-kulak-f32.gguf --input ../speech.wav --decoder ctc
The q8_0 file, gguf/ufakzeka-kulak-q8_0.gguf, is used the same way.
MLX with parakeet-mlx
parakeet-mlx computes its input features differently from NeMo. With its own transcribe at bfloat16, Common Voice dev WER is 0.0915 against NeMo's 0.0830, outside the check's margin. This folder therefore ships its own feature function, mlx_frontend.py, which computes NeMo's features with numpy; compute the features with it and pass them to the model's generate. The weights are stored in float32. parakeet-mlx runs them at bfloat16 by default, and bfloat16 is the checked setting. Two full runs at bfloat16 gave identical token ids on every development utterance and both passed the check.
pip install parakeet-mlx==0.5.3 mlx==0.32.4 huggingface_hub==1.33.0
import json
import sys
import mlx.core as mx
import numpy as np
from huggingface_hub import snapshot_download
from parakeet_mlx import from_pretrained
from parakeet_mlx.audio import load_audio
folder = snapshot_download("ufakai/ufakzeka-kulak", allow_patterns=["mlx/*"]) + "/mlx"
sys.path.insert(0, folder)
from mlx_frontend import features
model = from_pretrained(folder) # runs at bfloat16, the checked setting
with open(f"{folder}/config.json", encoding="utf-8") as f:
preprocessor = json.load(f)["preprocessor"]
samples = np.array(load_audio("speech.wav", preprocessor["sample_rate"]))
mel = mx.array(features(samples, preprocessor)[None])
print(model.generate(mel)[0].text)
parakeet-mlx decodes the TDT head only. load_audio needs ffmpeg.
Training data
Every training input has a licence that allows commercial use. Hours count each recording once; the speed-perturbed copies made during training are not counted again.
| Source | Licence | Hours | Training text |
|---|---|---|---|
| Mozilla Common Voice Scripted Speech 27.0 Turkish, every clip whose sentence or speaker is in its development or test split removed | CC0 1.0 | 67.9 | the sentences the contributors read |
| Mozilla Common Voice Spontaneous Speech 5.0 Turkish | CC0 1.0 | 0.1 | Common Voice's own transcriptions |
| antalia-voice-corpus, one professional voice actor | CC BY 4.0 | 4.2 | the scripts the voice actor read |
| Turkish Speech Corpus (TSC) of ISSAI, Nazarbayev University, train split | CC BY 4.0 | 209.1 | the corpus transcripts |
| YODAS (espnet/yodas_owsmv4, yodas0.10 and yodas0.00) and YODAS v3 (espnet/yodas3), Turkish | CC BY 3.0 | 4,696.2 | uploader subtitles (369.6 hours) or checked transcripts (4,326.6 hours), see below |
The YODAS recordings are YouTube audio collected by YODAS and YODAS v3, whose authors say they took only videos published under a Creative Commons licence; they release the audio under CC BY 3.0. On YouTube the one Creative Commons licence an uploader can choose is the Creative Commons Attribution licence (CC BY). The three parts are the Turkish yodas0.10 subset of ESPnet's OWSM v4 data, the yodas0.00 recordings not in it, and Turkish YODAS v3 audio cut along the spans of Alania Turkish Speech Style Captions (CC BY 4.0). The Alania dataset supplied only those span boundaries; its transcript column was never read. The training text is chosen per utterance. Where the utterance has an uploader subtitle whose WER against the Whisper large-v3-turbo transcript is at most 0.2000, the subtitle is the training text. Otherwise (no subtitle, or one that disagrees) it is the Whisper large-v3-turbo transcript, kept only where Omnilingual ASR CTC 7B v2 reads the same audio within a character error rate of 0.1000. YouTube's automatic captions are never training text.
ISSAI TSC. ISSAI publishes the corpus under CC BY 4.0 (LICENSE.md of https://github.com/IS2AI/TurkicASR); the Hugging Face copy is tagged MIT. CC BY 4.0 is the licence followed here.
Models used as data tools. Whisper large-v3-turbo (MIT) wrote the transcripts. Omnilingual ASR CTC 7B v2 (Apache-2.0) checked them and chose the spoken reading of number forms. Both were chosen on the ISSAI TSC development split, which was never trained on, against Whisper large-v3 (MIT), Omnilingual ASR CTC 3B v2 (Apache-2.0) and Qwen3-ASR-1.7B (Apache-2.0). The makers of these models do not fully disclose their training data. No output of these models other than the transcripts and readings above is training text.
Augmentation. Noise from the MUSAN noise subset (CC BY 4.0; only files that MUSAN's licence files mark as public domain or CC BY) was mixed into training audio at random, reverberation was added at random with simulated room responses from the OpenSLR room impulse response database (Apache-2.0), the input level was varied at random, and noise clips with an empty transcript were added as examples of non-speech. The YODAS2 recording lists (CC BY 3.0) were read as metadata only.
Decontamination. One exclusion set holds the normalised references of every development and test set used here. It was matched against every training transcript and every tokenizer line, by exact match and by shared word n-grams, and audio fingerprints matched the training audio against the evaluation audio. The matches removed 432 training utterances, 206 of them ISSAI TSC clips matched by their audio. FLEURS (any split), the Common Voice development and test splits, Real-TurnTurk and the 2,511 MediaSpeech clips reported here were never trained on.
Evaluation
How the numbers were produced
The recipe, the checkpoint, the decoder settings and the export files with their settings were chosen on the development sets, Common Voice 27.0 Turkish dev and FLEURS Turkish dev. The labelling choices (the transcribing model, the checking model and its character error rate threshold) were made on the ISSAI TSC development split. The test sets were scored once, for one model, after those choices; the first release-gate attempt passed. NeMo decoding is greedy, float32, TDT with at most 10 tokens per frame. Normalised WER applies our Turkish normaliser to reference and hypothesis (bracketed spans removed, digits written as words, Turkish lower-casing, in-word apostrophes and punctuation removed, circumflex folded, filler words such as ı, ee, hmm and aa dropped); raw-cased WER compares the texts as written.
The intervals in square brackets come from resampling the test sets (a blockwise percentile bootstrap with 2,000 resamples, seed 0, 95 percent intervals), in blocks of speakers for Common Voice and Real-TurnTurk, of sentences for FLEURS (FLEURS publishes no speaker ids, so its interval ignores speaker correlation) and of clips for MediaSpeech. A difference between two systems is paired, with both resampled by the same draws. The model was trained once, with one seed, so the intervals do not cover training variance. Whisper base, Whisper small and Whisper large-v3 (MIT; their maker does not fully disclose their training data) were scored by us with the same normaliser, with greedy decoding and, for base and small, also with the Whisper package's decoding settings (beam search with 5 beams and temperature fallback) as transformers implements them. Each comparison uses the better of the two on that set, which on every set reported here is the Whisper package's settings.
Release gate
The release required the model to beat Whisper base on FLEURS Turkish test and on Common Voice 27.0 Turkish test, each by a paired interval that lies wholly below zero. The model passed on both test sets.
| Set | Utterances | Hours | ufakzeka-kulak WER | Whisper base WER | Ours minus Whisper base | Ours minus Whisper small |
|---|---|---|---|---|---|---|
| FLEURS Turkish test | 743 | 2.6 | 0.0921 [0.0792, 0.1055] | 0.2461 [0.2303, 0.2628] | -0.1541 [-0.1653, -0.1430] | -0.0421 [-0.0506, -0.0340] |
| Common Voice 27.0 Turkish test | 11,874 | 14.5 | 0.0916 [0.0873, 0.0958] | 0.3287 [0.3190, 0.3380] | -0.2372 [-0.2440, -0.2302] | -0.0885 [-0.0930, -0.0843] |
| Real-TurnTurk, conversational, reported, not gating | 1,532 | 1.7 | 0.3807 [0.3270, 0.4640] | 0.7242 | -0.3435 [-0.4258, -0.3080] | -0.1367 [-0.1544, -0.1164] |
On FLEURS Turkish test and Common Voice 27.0 Turkish test, Whisper large-v3 (greedy) scores 0.0604 and 0.0767, lower than ufakzeka-kulak; its difference was not tested.
Normalised CER is 0.0335 [0.0258, 0.0426] on FLEURS Turkish test and 0.0242 [0.0228, 0.0258] on Common Voice 27.0 Turkish test. On the development sets normalised WER is 0.0830 on Common Voice dev and 0.1071 on FLEURS dev.
Punctuation and casing
The model writes capitals and punctuation, so the text as written is scored too, on the three test sets, against references whose punctuation is mapped to the model's marks. PC-WER counts marks and capitals; PuncER and CaseER are the parts of it that come from marks and from capitals (case and punctuation rates; the count of reference words with a capital is our reading of a term that paper does not define); PER is the punctuation error rate of a second paper, computed as NeMo's own implementation does.
| Set | PC-WER | WER without marks or casing | PuncER | CaseER | PER | Capital F-score | Share of references with a final mark |
|---|---|---|---|---|---|---|---|
| FLEURS Turkish test | 0.1508 | 0.0890 | 0.4223 | 0.1878 | 0.4134 | 0.8634 | 0.9960 |
| Common Voice 27.0 Turkish test | 0.1132 | 0.0963 | 0.1385 | 0.0530 | 0.1379 | 0.9691 | 0.9832 |
| Real-TurnTurk | 0.4960 | 0.3838 | 0.8337 | 0.3817 | 0.8647 | 0.3116 | 1.0000 |
On conversational speech the model writes few marks and often misplaces capitals. On Real-TurnTurk its full-stop recall is 0.171 and its comma recall 0.031, and its capitals have a precision of 0.365 and a recall of 0.272. The intervals, the per-mark counts, precision and recall and an apostrophe rate are in the result file. The WER without marks or casing removes only the marks and the capitals, with the numbers in the references written as the model writes them, so it differs from the normalised WER of the test results. The last column describes the references, not the model, since a set whose references seldom end in a mark penalises a model that writes one.
MediaSpeech Turkish
MediaSpeech (NTR Lab, CC BY 4.0) is media speech. Its makers say that part of its utterances come from Mozilla Common Voice, so every clip was checked against the Common Voice 27.0 sentences, the Common Voice development and test audio and our training audio before scoring. Of 2,513 clips, 2 matched training audio and were removed, and no clip matched Common Voice; 2,511 utterances, 10.0 hours, are reported. The references are not edited before normalisation. They join compound numbers into one word, so a CER with spaces removed is given as well.
| Metric | ufakzeka-kulak | Ours minus Whisper base | Ours minus Whisper small |
|---|---|---|---|
| WER | 0.1042 [0.1013, 0.1073] | -0.2240 | -0.1248 |
| CER | 0.0422 | -0.1113 | -0.0935 |
| CER without spaces | 0.0420 |
The dataset has no speaker or video ids, so the interval treats every clip as independent and may be too narrow. Whisper's training data is not fully disclosed, so this set may not be unseen for Whisper.
Low-bitrate, telephone and level copies
Three test sets, FLEURS Turkish test, Common Voice 27.0 Turkish test and Real-TurnTurk, were also scored on stored copies of their audio; MediaSpeech has no copies. The copies are the audio compressed with the Opus audio codec at 6,000 and 12,000 bit/s, the telephone band at 8,000 Hz with A-law coding, and the original audio with its level changed by -20 and -30 dB. The table gives the WER change against the same model on the original audio (copy minus original; the paired intervals are in the result file).
| Copy | FLEURS Turkish test | Common Voice 27.0 Turkish test | Real-TurnTurk |
|---|---|---|---|
| Opus, 6,000 bit/s | +0.0517 | +0.0752 | +0.0803 |
| Opus, 12,000 bit/s | +0.0043 | +0.0110 | +0.0135 |
| Telephone band, A-law | +0.0373 | +0.0491 | +0.0377 |
| Level -20 dB | +0.0045 | +0.0009 | +0.0022 |
| Level -30 dB | +0.0205 | +0.0051 | +0.0040 |
Per shipped file
The NeMo numbers above are the reference. The int8 sherpa-onnx files the examples load were scored at gain 8 on the same test sets and copies, paired on the same utterances with NeMo's greedy TDT decoding (file minus NeMo); the float32 files and the other copies are in the result file. For the CTC file that difference also holds the gap between the two heads; against NeMo's own CTC decoding the CTC file differs by +0.0032 on Common Voice dev.
| File | FLEURS Turkish test WER | Minus NeMo | Common Voice 27.0 Turkish test WER | Minus NeMo | Level -30 dB, FLEURS Turkish test, minus its original |
|---|---|---|---|---|---|
| sherpa-onnx TDT, int8, gain 8 | 0.0921 | 0.0000 | 0.0932 | +0.0016 | +0.0369 |
| sherpa-onnx CTC, int8, gain 8, caller pad | 0.1128 | +0.0207 | 0.1200 | +0.0284 | +0.0427 |
The other files were checked on the development sets only, against NeMo's own token ids.
| File | Common Voice dev, utterances with NeMo's token ids | FLEURS dev, utterances with NeMo's token ids | FLEURS dev WER minus NeMo |
|---|---|---|---|
| transformers TDT | 11,864 of 11,864 | 338 of 338 | 0.0000 |
| transformers CTC | 11,864 of 11,864 | 338 of 338 | 0.0000 |
| parakeet.cpp (GGUF) TDT, f32 | 11,748 of 11,864 | 306 of 338 | +0.0007 |
| parakeet.cpp (GGUF) TDT, q8_0 | 11,555 of 11,864 | 297 of 338 | +0.0018 |
| parakeet.cpp (GGUF) CTC, f32 | 11,697 of 11,864 | 295 of 338 | +0.0009 |
| parakeet.cpp (GGUF) CTC, q8_0 | 11,426 of 11,864 | 272 of 338 | +0.0002 |
| parakeet-mlx, our features, run at bfloat16 | 11,744 of 11,864 | 331 of 338 | 0.0000 |
Each runtime is compared with the NeMo decoding that follows its own rule at the per-frame token cap.
Speed
The shipped sherpa-onnx files were timed with sherpa-onnx 1.13.8 on 60 development utterances from Common Voice dev and FLEURS Turkish dev, the same number in each of three length buckets, short (two to five seconds), medium (five to fifteen seconds) and long (fifteen to thirty seconds). Each row ran in a fresh process. The recognizer was built from the files (cold load), the sample was decoded once without timing and then five times with timing, each utterance in its own stream, at input gain 8 and, for the CTC file, with the caller pad. The real-time factor (RTF) is decode time divided by audio time over a bucket, the median of the five timed passes; below one is faster than real time. A decode cell gives the median and the ninetieth percentile of the per-utterance decode time in seconds. With voice activity detection, the text of a segment appears after the detector's minimum silence (0.5 s in the sherpa-onnx WebAssembly default) plus that decode time. Peak memory is the peak resident set size of the measuring process. The result files hold every row with its smallest and largest pass and its provenance, and each Python row with the load average just before and after it.
Apple M1, sherpa-onnx Python:
| File | Threads | RTF short | RTF medium | RTF long | Decode short (s) | Decode medium (s) | Decode long (s) | Cold load (s) | Peak memory (MB) |
|---|---|---|---|---|---|---|---|---|---|
| TDT, float32 | 1 | 0.0151 | 0.0153 | 0.0155 | 0.046 / 0.058 | 0.104 / 0.149 | 0.262 / 0.329 | 0.529 | 617.2 |
| TDT, int8 | 1 | 0.0096 | 0.0096 | 0.0101 | 0.029 / 0.037 | 0.065 / 0.096 | 0.170 / 0.214 | 0.410 | 633.7 |
| CTC, float32, caller pad | 1 | 0.0137 | 0.0136 | 0.0140 | 0.041 / 0.053 | 0.091 / 0.134 | 0.233 / 0.290 | 0.414 | 742.6 |
| CTC, int8, caller pad | 1 | 0.0091 | 0.0091 | 0.0097 | 0.027 / 0.035 | 0.062 / 0.092 | 0.161 / 0.203 | 0.403 | 524.7 |
Chrome on the same Apple M1 (Chrome 155.0.8059.84, headless), the demo page's sherpa-onnx WebAssembly build with its int8 TDT files, single-threaded. Here the memory column is the JavaScript heap after the timed passes (performance.memory.usedJSHeapSize), which leaves out the WebAssembly memory.
| File | Threads | RTF short | RTF medium | RTF long | Decode short (s) | Decode medium (s) | Decode long (s) | Cold load (s) | Peak memory (MB) |
|---|---|---|---|---|---|---|---|---|---|
| TDT, int8 | 1 | 0.0306 | 0.0308 | 0.0311 | 0.092 / 0.118 | 0.209 / 0.300 | 0.528 / 0.664 | 0.586 | 110.1 |
A 2-vCPU x86-64 Linux cloud server (Intel(R) Xeon(R) CPU @ 2.20GHz), sherpa-onnx Python, at one and at two threads. The server is a shared virtual machine whose CPU model was recorded, not chosen. The load averages and steal around each row are in the result file. On this server every two-thread row had a higher median real-time factor than the same file at one thread.
| File | Threads | RTF short | RTF medium | RTF long | Decode short (s) | Decode medium (s) | Decode long (s) | Cold load (s) | Peak memory (MB) |
|---|---|---|---|---|---|---|---|---|---|
| TDT, float32 | 1 | 0.0411 | 0.0415 | 0.0412 | 0.123 / 0.153 | 0.278 / 0.388 | 0.663 / 0.863 | 1.998 | 700.4 |
| TDT, int8 | 1 | 0.0322 | 0.0321 | 0.0364 | 0.095 / 0.124 | 0.223 / 0.327 | 0.561 / 0.842 | 1.673 | 606.8 |
| CTC, float32, caller pad | 1 | 0.0257 | 0.0240 | 0.0276 | 0.076 / 0.097 | 0.161 / 0.242 | 0.417 / 0.539 | 1.493 | 674.4 |
| CTC, int8, caller pad | 1 | 0.0281 | 0.0277 | 0.0311 | 0.084 / 0.106 | 0.188 / 0.279 | 0.486 / 0.602 | 1.477 | 603.5 |
| TDT, float32 | 2 | 0.0656 | 0.0558 | 0.0483 | 0.191 / 0.238 | 0.377 / 0.453 | 0.729 / 0.934 | 1.499 | 701.0 |
| TDT, int8 | 2 | 0.0579 | 0.0536 | 0.0424 | 0.171 / 0.196 | 0.382 / 0.540 | 0.681 / 0.977 | 1.465 | 604.4 |
| CTC, float32, caller pad | 2 | 0.0268 | 0.0248 | 0.0299 | 0.077 / 0.097 | 0.163 / 0.242 | 0.420 / 0.538 | 1.519 | 677.8 |
| CTC, int8, caller pad | 2 | 0.0331 | 0.0313 | 0.0358 | 0.096 / 0.122 | 0.208 / 0.324 | 0.536 / 0.651 | 1.471 | 600.5 |
Known limitations
- sherpa-onnx front end. The sherpa-onnx feature front end differs from NeMo's in six options. A comparison on an earlier model of the same architecture, with its own data and tokenizer, found that of the differing options only the small constant added before the logarithm, which weighs most on low-energy frames, closed the gap; the others moved WER slightly. The input gain the sherpa-onnx section names is a workaround that holds at normal levels; the level copies above show what is left on quiet audio. The upstream change is pull request 3999, merged on 21 October 2026; until a release includes it, use the gain.
- sherpa-onnx CTC. The sherpa-onnx NeMo CTC runtime sets the output length to the feature frame count divided by the time reduction, rounded down, instead of the encoder's own length, so the last encoder frame is dropped whenever the frame count is not a multiple of it. That frame can carry the final punctuation mark. Normalised WER does not see the loss; raw-cased WER and the punctuation rates do. The caller pad in the sherpa-onnx section restores the frame. On Common Voice dev, of 11,864 hypotheses, 1,788 end in a mark without the pad, 11,637 with it and 11,644 with NeMo's own CTC decoding. The TDT files are not affected.
- Quiet audio. The model was trained with random input levels; the level copies in the Evaluation section measure how much accuracy it still loses on quiet recordings, for NeMo and for the two int8 sherpa-onnx files.
- MediaSpeech reference style. Its references join compound numbers into one word, so part of its WER comes from the reference style; the CER without spaces does not count those joins. Its interval may be too narrow (see MediaSpeech Turkish).
- Apple silicon. The parakeet-mlx files were checked on Linux CPU only. They were not run on Apple silicon or on mlx's Metal backend; the bfloat16 behaviour there may differ.
- transformers decodes TDT without NeMo's per-frame token cap; on the development sets this changed no utterance (11,864 of 11,864 Common Voice dev and 338 of 338 FLEURS dev utterances give NeMo's token ids).
Attribution
ATTRIBUTION.md in this repo lists every source with its licence. In short:
Mozilla Common Voice contributors (CC0 1.0); Antalia (Patientdesk.ai), antalia-voice-corpus (CC BY 4.0); the Institute of Smart Systems and Artificial Intelligence (ISSAI), Nazarbayev University, Turkish Speech Corpus (CC BY 4.0); YODAS, the OWSM v4 data and YODAS v3 by ESPnet (CC BY 3.0); Alania Turkish Speech Style Captions by PatientDesk AI (https://huggingface.co/datasets/cloud0day3/alania-speech-captions-tr), licensed under CC BY 4.0, © 2026 PatientDesk AI; MUSAN (CC BY 4.0) and the OpenSLR simulated room responses (Apache-2.0) for augmentation. The evaluation sets are FLEURS (Google, CC BY 4.0), Common Voice 27.0 Turkish dev and test (CC0 1.0), Real-TurnTurk (Apache-2.0) and MediaSpeech (NTR Lab, CC BY 4.0).
The antalia-voice-corpus card asks that its audio, and anything trained on it, not be used to impersonate the speaker or any real person or to imply someone said something they did not, nor for fraud, scams, social engineering or political robocalls, and that synthetic speech made from it be disclosed. ufakzeka-kulak produces no speech; users of this model are asked to honour these requests.
Citation
The technical report describes the method and the results; the second entry cites the released model.
@techreport{teke2026ufakzekakulak,
title = {ufakzeka-kulak: Building and Evaluating a 31.6M-Parameter {Turkish} Speech Recognizer from Scratch},
author = {Teke, Sait Furkan},
institution = {ufak AI},
year = {2026},
url = {https://ufakai.com/reports/ufakzeka-kulak.pdf}
}
@misc{ufakzekakulak2026,
title = {ufakzeka-kulak},
author = {{ufak AI}},
year = {2026},
howpublished = {\url{https://huggingface.co/ufakai/ufakzeka-kulak}}
}
Links
- Model: https://huggingface.co/ufakai/ufakzeka-kulak
- Code: https://github.com/ufakai/ufakzeka-kulak
- Demo: https://kulak.ufakzeka.com
- Write-up: https://ufakai.com/research/ufakzeka-kulak
- Contact: furkan@ufakai.com
Türkçe özet
ufakzeka-kulak, ufak AI’ın sıfırdan eğittiği küçük bir Türkçe konuşma tanıma modelidir. Apache-2.0 lisansıyla yayımlanmaktadır ve internet bağlantısı olmadan işlemcide çalışır. Aynı ağırlıklar NeMo, transformers, sherpa-onnx, parakeet.cpp (GGUF) ve parakeet-mlx (MLX) için bu repoda bulunmaktadır. Model büyük ve küçük harfleri ayırarak yazar ve noktalama işareti olarak nokta, virgül, soru işareti, kesme işareti ile kelime içindeki kısa çizgiyi kullanır; sayıları rakamla değil yazıyla verir. Yalnızca Türkçe konuşmayla eğitilmiştir ve tek seferde tek bir konuşma parçasını yazıya döker. Akış (streaming) modunda çalışmaz, yani konuşma sürerken metni kelime kelime yazmaz; konuşmacıları da birbirinden ayırmaz. Uzun kayıtların önce ses etkinliği algılamayla (voice activity detection, VAD) konuşma parçalarına bölünmesi ve her parçanın başında ve sonunda birkaç yüz milisaniyelik pay bırakılması gerekir; konuşmanın tam başladığı ve bittiği yerden kesilen bir parçada model büyük harfleri ve noktalama işaretlerini atlayabilir. Eğitim verisindeki her kaynağın lisansı ticari kullanıma izin vermektedir. YODAS kayıtlarında eğitim metinlerinin çoğunu bir konuşma tanıma modeli yazmış, ikinci bir model denetlemiştir; iki modelin üzerinde uyuştuğu hatalar eğitim metninde kalmaktadır. Yayın öncesi kabul testinde normalize edilmiş kelime hata oranı (WER) FLEURS Türkçe testinde 0,0921, Common Voice 27.0 Türkçe testinde 0,0916 olarak ölçülmüştür. Whisper base’in aynı kümelerdeki kelime hata oranları sırasıyla 0,2461 ve 0,3287’dir. İki model aynı ses kayıtları üzerinde karşılaştırıldığında farkın bootstrap güven aralığı iki kümede de tamamen sıfırın altında kalmış ve model kabul testini geçmiştir. Whisper large-v3’ün açgözlü kod çözmeyle kelime hata oranı ise 0,0604 ve 0,0767’dir ve ufakzeka-kulak’ınkinden düşüktür; bu fark için güven aralığı hesaplanmamıştır. Gündelik konuşmalardan oluşan Real-TurnTurk test kümesinde (6 konuşmacı) kelime hata oranı 0,3807 olarak ölçülmüştür. sherpa-onnx dosyalarında sesin örnek değerleri modele verilmeden önce 8 ile çarpılmalı, CTC dosyasında ayrıca sesin sonuna sıfır değerli örnekler eklenmelidir; ayrıntılar yukarıdaki sherpa-onnx bölümündedir. Apple M1 işlemcide sherpa-onnx ile tek iş parçacığında çalışan int8 TDT dosyasının on beş ile otuz saniyelik konuşma parçalarındaki medyan gerçek zaman faktörü (real-time factor) 0,0101 olarak ölçülmüştür, yani model bir saniyelik sesi saniyenin yaklaşık yüzde birinde yazıya döker; aynı bilgisayardaki Chrome’da, WebAssembly ile çalışan demo sayfasında bu değer 0,0311’dir. Doğruluk, konuşmacının cinsiyetine, yaşına ya da yöresel ağzına göre ayrı ayrı ölçülmemiştir. Konuşanların kimliğini belirlemek, onları izlemek, haklarında profil çıkarmak ya da bir kişiyle ilgili transkripti biri denetlemeden o kişi hakkında bir işlem için kullanmak bu modelin kullanım amacı dışındadır. Sonuçların tamamı, dosyalar ve bilinen sınırlar yukarıdaki İngilizce bölümlerde yer almaktadır.
- Downloads last month
- 150
Datasets used to train ufakai/ufakzeka-kulak
espnet/yodas2
espnet/yodas_owsmv4
Papers for ufakai/ufakzeka-kulak
End-to-end Joint Punctuated and Normalized ASR with a Limited Amount of Punctuated Training Data
LibriSpeech-PC: Benchmark for Evaluation of Punctuation and Capitalization Capabilities of end-to-end ASR Models
Evaluation results
- WER (normalised) on FLEURS Turkish (test)test set self-reported0.092
- CER (normalised) on FLEURS Turkish (test)test set self-reported0.034
- WER (normalised) on Common Voice Scripted Speech 27.0 Turkish (test)test set self-reported0.092
- CER (normalised) on Common Voice Scripted Speech 27.0 Turkish (test)test set self-reported0.024