Teknik yazı · 14 Ekim 2026

balbal-v1 nasıl yapıldı

15 milyon aktif parametreli bir Türkçe konuşma tanıma modelini tek başıma eğittim. İlk Türkçe model RunPod'da, kiralık bir NVIDIA RTX PRO 4500 Blackwell üzerinde yaklaşık 35,6 saat eğitildi; sonraki aşamaların hepsi tek bir masaüstü makinede. Mimari, veri, eğitim, değerlendirme ve yol boyunca yaptığım hatalar.

Kısaca

  • balbal-v1, 45,4 milyon parametre saklayan ama ses karesi başına yalnızca 15,4 milyonunu kullanan bir CTC modeli.
  • Mu-Bench'in resmi değerlendirmesinde %11,93 kelime hata oranı (WER) aldı. FLEURS Türkçe testinde %6,54.
  • 8 işlemci çekirdeğinde, tek tek kliplerle, gerçek zamanın ~96 katı hızla çalışıyor.
  1. Neden küçük bir model
  2. Mimari
  3. Veri
  4. Eğitim
  5. Çözücü ve dil modeli
  6. Nasıl ölçtüm
  7. Sonuçlar
  8. Hız
  9. Hatalar ve dersler
  10. Sınırlar ve sonraki adımlar
  11. Kaynaklar
  12. Atıf

1Neden küçük bir model

Türkçe konuşma tanımada iki seçenek öne çıkıyor. Birincisi ticari API'ler: iyiler, ama ses dışarıya gidiyor ve dakika başı ücret ödeniyor. İkincisi büyük açık modeller (Whisper large-v3, Qwen3-ASR, omniASR): doğrulukları yüksek, ama bir milyardan fazla parametreleri var ve işlemcide gerçek zamana ancak yetişiyorlar.

Hedefim bu ikisinin arasındaki boşluktu. GPU gerektirmeyen, telefon görüşmesi gibi zor seslerde ticari API'lerle yarışabilen ve bir dizüstü bilgisayarda bile rahat çalışan bir model istedim. Bu yüzden her tasarım kararında doğruluğu hesaplama maliyetiyle birlikte tarttım.

Mu-Bench hata oranı ile işlemcide hız (gerçek zaman katı, logaritmik) grafiği; balbal-v1 sağ altta, hem hızlı hem doğru. Freya ve PatientDesk API'leri de grafikte.
Hız ve doğruluk. Yerel modeller aynı oturumda, 8 işlemci çekirdeğinde ve batch 1 ile ölçüldü. Freya ve PatientDesk (elmaslar) kendi sunucularının bildirdiği işlem süresiyle. Yatay eksen logaritmik; sağ alt köşe en iyisi. omniASR'ın LLM'li modelleri işlemcide bitmek bilmeyeceği için grafikte yok. Duyu-small ayrı bir oturumda 30× ölçüldü (aynı oturumda balbal-v1 108×); grafikte balbal-v1'in iki oturum arasındaki oranıyla ölçeklenmiş hali (27×) var.

2Mimari

Model üç parçadan oluşuyor.

Kodlayıcı: Conformer-CTC

16 kHz ses önce 80 kanallı log-mel spektrogramına çevriliyor. 16 katmanlı bir Conformer bunu işliyor (model boyutu 176, 4 dikkat başı, 31'lik evrişim çekirdeği) ve 40 milisaniyede bir çıktı karesi üretiyor. Başlangıç noktası NVIDIA'nın İngilizce stt_en_conformer_ctc_small modeliydi. Türkçe için 1024 parçalı bir SentencePiece (unigram) sözlüğüyle yeniden eğittim.

İyileştirici ve ürün-anahtarlı bellek

Kodlayıcının üstünde 2 katmanlı, 192 genişliğinde küçük bir iyileştirici var. Aynı ağırlıklarla üç kez çalışıyor; her geçişte çıktı biraz daha düzeltiliyor. İyileştiricinin içinde ürün-anahtarlı bir bellek (product-key memory) bulunuyor. İki adet 500'lük alt anahtar kümesinin çarpımı 250.000 bellek yuvası veriyor; her yuva 120 boyutlu bir vektör tutuyor. Bu tablo 30 milyon parametre ediyor, ama her ses karesi yalnızca 64 yuvaya bakıyor (4 baş × en iyi 16). Böylece model çok şey ezberleyebiliyor, ancak her kare için az hesap yapıyor.

Aktif parametre sayısına karşı Mu-Bench kelime hata oranı grafiği. balbal-v1, 15,4 milyon aktif parametreyle sol altta; büyük açık modeller sağda.
Aktif parametreye karşı hata. Bellek tablosu saklanıyor ama her karede yalnızca küçük bir kısmı okunuyor. Bu yüzden yatay eksende aktif parametre var.
ParçaSaklananKare başına kullanılan
Conformer kodlayıcı (16 katman)12,97 mn12,97 mn
CTC çıkış katmanı0,18 mn0,18 mn
İyileştirici (bellek hariç)2,2 mn2,2 mn
Bellek tablosu (250.000 × 120)30,0 mn≤ 0,02 mn (3 geçişte 23.040 değer)
Toplam45,4 mn15,4 mn

Dil modeli

Çıktı, SentencePiece parçaları üzerinde eğitilmiş 6-gram bir KenLM dil modeliyle çözülüyor. Ayrıntılar 5. bölümde.

Bu mimariye nasıl vardım

Eylül sonunda düz bir Conformer-CTC (13,2 mn parametre) ile başladım. Her fikri aynı küçük düzenekte denedim: 200 saat veri, 3 epoch, aynı dil modeli. Aşağıdaki sayılar FLEURS ve iki başka Türkçe test kümesinin birleşik WER'i (15.610 klip), o dönemki eski puanlama kuralıyla. Bu yüzden yukarıdaki sonuçlarla doğrudan karşılaştırılamaz. Önemli olan aralarındaki farklar.

Deney (200 saat, 3 epoch)Birleşik WERKarar
Düz Conformer-CTC23,78başlangıç
Self-conditioning CTC23,52küçük kazanç
Dikkat tabanlı çözücü (AED)23,90elendi
Yalnızca ürün-anahtarlı bellek (4 baş × 16)22,23umut verici
Döngülü iyileştirici, 3 geçiş22,18kaldı
6 geçiş + derinlik koşullama22,023 geçişe değmedi
Döngü + iki görünüm tutarlılık kaybı20,43kaldı
Döngü + bellek + tutarlılık19,72kaldı
+ Muon/AdamW optimizasyonu18,43temel mimari
+ uzman karışımı (4 uzman, en iyi 2)18,37fark gürültüde, eklenmedi
+ WavLM öğretmenden damıtma18,80elendi

Yol boyunca öğrendiklerim:

Son modelde parçaların katkısını görmek için önceki sürümde (balbal-v2-beta) belleği ve döngüyü çıkarım sırasında kapattım. Bu, yeniden eğitilmiş bir karşılaştırma değil; yalnızca eğitilmiş modelin bu parçalara ne kadar dayandığını gösteriyor.

balbal-v2-beta, WER (balbal-normalizer)Tam modelBellek kapalıDöngü yok
FLEURS6,598,798,81
Mu-Bench13,3916,0515,90

Dil modeliyle birlikte, belleği olmayan döngü neredeyse hiç döngü olmamasıyla aynı sonucu veriyor. Yani iyileştiricinin kazancı büyük ölçüde bellekten geliyor.

3Veri

Model, eğitimin başından sonuna kadar en az bir kez toplam 1.722 saat farklı ses gördü. Tekrarlarla birlikte bu yaklaşık 14.450 saat ediyor; 12.100 saati ilk Türkçe modelin 10 epoch'undan geliyor. Aynı ses birden fazla aşamada kullanıldıysa bir kez sayıldı (kod çözülmüş sesin özeti, yani hash ile). NVIDIA'nın İngilizce başlangıç modelinin ön eğitim verisi tabloda yok.

KaynakSaatPay
Gerçek Türkçe konuşma (açık veri setleri ve web'den derlenmiş doğal konuşma)455,6%26,5
Sentetik Türkçe konuşma (TTS)1.195,7%69,4
Okunan web ve haber metinleri, kısa cümleler714,4%41,5
Gündelik konuşma ve diyaloglar329,4%19,1
Harf harf kodlama, e-posta50,0%2,9
Sayılar, telefon numaraları, tutarlar40,7%2,4
İsimler ve ekler24,5%1,4
Çağrı merkezi diyalogları20,4%1,2
Kısa cevaplar16,2%0,9
Diğer70,5%4,1
İngilizce konuşma (LibriSpeech)61,3%3,6
Konuşma olmayan sesler (gürültü, müzik, sessizlik)9,3%0,5
Toplam: 1.075.680 klip1.721,9%100

Son eğitim aşamasında yeni ses eklemedim. Daha önce görülmüş kayıtlardan 702 saatlik bir karışım kurdum ve gerçek konuşmanın payını %64'e çıkardım.

Sentetik konuşmayı açık TTS modelleriyle ürettim. Gerçek veride az bulunan durumlara ağırlık verdim: harf harf okunan kodlar, telefon numaraları, isimler ve kısa "evet / hayır / tamam" cevapları. Çağrı merkezi konuşmalarının asıl zorluğu bu durumlarda.

Gerçek kayıtları Moonshine gibi kesip birleştirdim

Son eğitim aşamasında gerçek kayıtları Moonshine'ın eğitim verisini hazırlama biçimine göre yeniden düzenledim (Jeffries ve ark., 2024). Moonshine, aynı kayıttaki ardışık parçaları aralarında en fazla 2 saniye boşluk kalacak şekilde birleştirip 4 ile 30 saniye arası örnekler oluşturuyor. Bu yüzden süre dağılımı iki tepeli: bir tepe 3–6 saniyede, diğeri 27–30 saniyede. Ben de gerçek kayıtları böyle birleştirdim. Kısa sentetik klipleri ve aşağıdaki kesitleri bilerek kısa bıraktığım için karışımın yalnızca %5,7'si 25–31 saniye aralığında (Moonshine'da %23).

Ayrıca gerçek kayıtlardan kısa kalıp ifadeler kestim. Eğitim metinlerinde "iyi günler", "kolay gelsin", "rica ederim" gibi 35 sık konuşma ifadesini buldum. Önceki modelin CTC hizalamasıyla bu ifadelerin seste nerede başlayıp bittiğini çıkardım, kelime sınırlarından 80 ms pay bırakarak kestim. Yalnızca modelin kesilmiş sesi tam olarak o ifade diye okuduğu kesitleri tuttum: 11.476 kesit, toplam 2 saat. Nadir ifadeler daha sık tekrarlansın diye her ifadeyi 3 ile 15 kat arasında çoğalttım. Amaç, çağrı merkezlerinde çok geçen ama uzun kayıtların içinde kaybolan kısa cevapları modele ayrı ayrı göstermekti.

Birkaç kural koydum:

4Eğitim

balbal-v1 tek seferde eğitilmedi; birkaç aşamalık bir soy ağacının son halkası.

  1. İngilizce başlangıçNVIDIA stt_en_conformer_ctc_small (Conformer-CTC, ~13 mn parametre).
  2. İlk Türkçe modelRunPod'da, NVIDIA RTX PRO 4500 Blackwell üzerinde 10 epoch, yaklaşık 35,6 saat.
  3. Genel Türkçe modelİlk Türkçe modelden devam: 203 saatlik yeni veriyle 3 epoch.
  4. balbal-v1-betaAğırlık çorbası: genel Türkçe model %75, ilk Türkçe model %25.
  5. balbal-v2-betaİyileştirici ve ürün-anahtarlı bellek eklendi. E-posta ve harf harf kodlama verisiyle 27.024 güncelleme, ardından 0,6 oranlı ağırlık çorbası.
  6. balbal-v1v2-beta'dan başlayarak 702 saatlik karışımla 6000 güncelleme (yaklaşık 0,57 epoch). Yerel iGPU'da 5.024 saniye (84 dakika). Çorba yapılmadı; ham ağırlıklar kullanıldı.

Son aşamanın ayarları: batch 32; öğrenme hızı 1e-4 (bellek tablosu için 5e-4). Matris ağırlıklarında Muon, geri kalan ağırlıklarda AdamW kullandım.

Speedrun yaklaşımı

Eğitim değişikliklerini Keller Jordan'ın modded-nanogpt "speedrun" yaklaşımıyla değerlendirdim. Bir değişikliğin değeri, adım başına süreyle değil, sabit bir kalite hedefine ulaşma süresiyle ölçüldü. Hedef, ayrı tutulan kontrol kümelerinde Türkçe, İngilizce ve konuşma olmayan ses grupları için belirlenen eşiklerdi. Geliştirme kümesinde (DEV) gerileme olmaması şart koşuldu. İki model arasındaki farkları eşleştirilmiş bootstrap ile sınadım. Kontrol noktası seçimi yalnızca DEV ve kontrol kümelerine bakılarak yapıldı; test kümelerine ve Mu-Bench'e bakılmadı.

Donanım

İlk Türkçe model (10 epoch, yaklaşık 35,6 saat) RunPod'da kiraladığım bir NVIDIA RTX PRO 4500 Blackwell (32 GB) GPU'sunda eğitildi. Sonraki aşamaların hepsi kendi makinemde devam etti: tek bir AMD Ryzen AI Max+ 395, Radeon 8060S entegre GPU, ROCm ve 121 GB ortak bellek. Mimari denemelerin ve speedrun ölçümlerinin bir kısmı için de RunPod'dan RTX 5090 ve A40 kiraladım. Radeon'un kendine özgü sorunlarını 9. bölümde anlatıyorum.

5Çözücü ve dil modeli

CTC çıktısı, flashlight'ın sözlüksüz (lexicon-free) ışın aramasıyla çözülüyor. Dil modeli SentencePiece parçaları üzerinde çalışan 6-gram bir KenLM. 45 milyon parça üzerinde eğitildi ve 124 MB tutuyor. Dil modeli metni, bu modelin soy ağacındaki tüm eğitim metinlerinden oluşuyor. DEV, test ve kontrol cümleleri bu metinden çıkarıldı. Her model sürümü için dil modelini o sürümün gördüğü veriyle yeniden kuruyorum.

Çözücü ayarları DEV kümesinde kalibre edildi: dil modeli ağırlığı α = 0,75, kelime bonusu β = 1,5, sıcaklık 1,2, ışın genişliği 64, kare başına en iyi 12 parça, budama eşiği 8. Dil modeli FLEURS'ta WER'i %9,35'ten %6,54'e, Mu-Bench'te %13,86'dan %11,78'e indiriyor.

Çıktıda büyük harf ve noktalama var. Sayılar yazıyla çıkıyor; rakama çeviren bir son işlem (ITN) modelin içinde yok.

6Nasıl ölçtüm

Kendi ölçümlerimde tek bir puanlama kuralı kullandım (balbal-normalizer). Harf büyüklüğü ve noktalama yok sayılıyor. Sayılar rakam rakam karşılaştırılıyor; "doksan", "dokuz sıfır" ve "90" aynı sayılıyor. Bitişik yazılmış kodlar harf ve rakamlarına ayrılıyor. Kural yalnızca aynı kelimelerin farklı yazımlarını affediyor; farklı bir kelime her zaman hata. Tablodaki bütün açık modeller aynı klipler ve aynı kuralla puanlandı. Puanlama kodu GitHub'da: normalizer/metrics_v2.py. Normalizer, çıkarım ve eğitim kodunun tamamı da github.com/erayyap/balbal deposunda.

Mu-Bench için resmi değerlendirme düzeneğini de çalıştırdım (sierra-research/mu-bench, gpt-4.1 hakem). Gönderimden önce yazıyla çıkan sayılar rakama çevrildi (ITN). Ticari API'lerin puanları Mu-Bench'in kendi sonuç tablosundan alındı. Maliyeti azaltmak için bir önlem aldım: 846 cümlenin 342'sinde balbal-v1, balbal-v2-beta ile birebir aynı metni üretiyordu. Bunlar için v2-beta'nın hakem kararlarını tekrar kullandım; kalan 504 cümle yeniden değerlendirildi.

Mu-Bench yalnızca sonuç okumak için kullanıldı. Eğitim verisine, dil modeline ya da kontrol noktası seçimine hiçbir şekilde girmedi.

7Sonuçlar

Mu-Bench: ticari API'lerle karşılaştırma

Mu-Bench (Li ve Ray, 2026), 8 kHz kalitesinde müşteri hizmetleri görüşmelerinden 846 Türkçe cümle içeriyor. Resmi değerlendirmede balbal-v1'in WER'i %11,93, anlamı bozan hata oranı (significant WER) %25,18. Bu sonuçla Azure, Deepgram, OpenAI ve xAI'nin önünde, Google ve ElevenLabs'in arkasında yer alıyor. Anlamı bozan hatalarda Deepgram Nova-3 (%24,35) biraz daha iyi.

Mu-Bench resmi WER çubuk grafiği: balbal-v1 yüzde 11,93 ile ticari API'lerin arasında.
Mu-Bench, resmi değerlendirme. API puanları Mu-Bench tablosundan. balbal-v1 aynı düzenekle, gpt-4.1 hakemle ve ITN uygulanarak puanlandı.

Diğer modellerle karşılaştırma

Açık ağırlıklı modellerin hepsini aynı klipler ve balbal-normalizer kuralıyla kendim çalıştırdım. Mu-Bench'te (balbal-normalizer, ITN'siz) balbal-v1 %11,78 ile hepsinin önünde. En yakın rakipler Qwen3-ASR 1.7B (%13,95) ve Whisper large-v3 (%14,92); ikisi de yüz kat büyük. FLEURS'ta durum farklı: Whisper large-v3 (%3,71) ve turbo (%4,07) açık ara önde. balbal-v1'in %6,54'ü Qwen3-ASR 1.7B (%6,52) ile aynı düzeyde. FLEURS temiz, okunmuş Wikipedia cümlelerinden oluşuyor ve büyük modellerin geniş dünya bilgisi burada daha çok işe yarıyor.

Sonradan bir model daha ekledim: Duyu-small. Türkçe ve İngilizce tanıyan, 19,3 milyon aktif parametreli açık ağırlıklı bir model. Model kartına göre kodlayıcı tasarımı (paylaşımlı geçişler ve seyrek akustik bellek) balbal'dan alınmış; üstüne dikkat tabanlı bir çözücü ve iki dilli bir sözlük eklenmiş. Aynı klipler ve balbal-normalizer kuralıyla Mu-Bench'te %15,39, FLEURS'ta %6,62 hata yapıyor. Resmi Mu-Bench değerlendirmesinde (GPT-4.1 hakemli) kelime hata oranı %15,70, ciddi hata oranı %35,22 (balbal-v1: %11,93 ve %25,18).

Mu-Bench'te diğer modellerin WER karşılaştırması (balbal-normalizer); balbal-v1 en düşük hata oranıyla en üstte.
Mu-Bench, diğer modeller (balbal-normalizer ile puanlama). Her model aynı 846 klip üzerinde aynı kuralla puanlandı.
FLEURS Türkçe test kümesinde diğer modellerin WER karşılaştırması; Whisper large-v3 en iyi, balbal-v1 Qwen3-ASR ile yakın.
FLEURS Türkçe testi, 743 klip. Temiz, okunmuş konuşma; büyük modellerin en güçlü olduğu alan.

MihuAI'nin kendi testi

MihuAI, turkish-stt modeliyle birlikte 81 klipten oluşan bir test yayımladı. Kayıtlar TTS ile üretilmiş, 24 kHz. Bu testi MihuAI'nin kendi puanlama koduyla, hiçbir değişiklik yapmadan çalıştırdım. balbal-v1'in WER'i %7,94. MihuAI turkish-stt, kendi önerdiği ayarlarla aynı testte %15,20 alıyor (yayımladıkları değer %15,37).

Kategori bazında

Kategoriler Mu-Bench'in kendisinden gelmiyor. Freya'nın aynı ses ve metinleri içeren sierra-benchmark veri setindeki kategori etiketlerini kullandım. balbal-v1 telefon numaralarında (%3,96), adreslerde (%4,53) ve kod dizilerinde (%5,34) güçlü. İsimler (%24,84), harf harf okuma (%20,06) ve istek cümleleri (%19,98) hâlâ zayıf noktaları.

Mu-Bench kategorilerine göre WER: balbal-v1, Duyu-small, Qwen3-ASR ve Whisper large-v3 karşılaştırması.
Mu-Bench kategorileri (balbal-normalizer ile puanlama). Kategori etiketleri Freya'nın sierra-benchmark veri setinden. Grafikte yalnızca en az 20 klibi olan kategoriler var; parantez içindeki sayılar klip sayısı. E-posta, adres ve kısa cevaplarda balbal-v1 önde; telefonda Duyu-small ile neredeyse eşit (%3,96'ya %3,76). Kalanların çoğunda Whisper large-v3 önde.
ModelTümü 846Kısa cevap 229Telefon 109E-posta 102İstek 85Kod dizisi 84Sohbet 65Soru 63İsim 55Harf harf 27Adres 21Tutar 5Tarih/saat 1
Google Chirp 38,48,02,115,610,40,97,811,425,514,03,913,60,0
ElevenLabs Scribe v28,712,42,810,412,03,19,514,323,612,22,111,40,0
Google Gemini 3.5 Transcribe8,86,62,713,510,76,49,712,023,012,84,813,60,0
Freya9,76,14,718,611,81,49,712,819,213,44,213,60,0
PatientDesk Duyu10,09,64,317,613,54,111,011,918,010,33,99,10,0
OpenAI GPT-4o Mini Transcribe11,315,73,014,916,07,712,916,030,611,63,64,50,0
balbal-v111,812,24,012,520,05,313,118,624,820,14,518,20,0
Deepgram Nova-312,411,42,727,014,810,07,813,526,114,04,811,40,0
Qwen3-ASR 1.7B (Baseten)13,817,15,521,318,49,011,816,930,415,55,413,60,0
Qwen3-ASR 1.7B (Modal)13,918,05,321,618,98,812,016,929,815,25,113,60,0
Qwen3-ASR 1.7B13,917,55,522,418,18,311,816,531,115,56,313,60,0
Whisper large-v314,913,64,340,014,04,512,216,021,710,95,59,10,0
Duyu-small15,418,03,822,321,811,213,920,932,322,88,418,20,0
xAI Grok STT15,727,95,818,718,912,415,820,028,620,17,215,90,0
omniASR LLM 7B18,451,35,623,617,214,113,517,142,215,510,216,30,0
Microsoft Azure Speech18,613,812,135,816,514,710,517,626,131,112,911,40,0
omniASR LLM 3B19,238,36,922,823,111,214,125,055,318,212,720,40,0
omniASR LLM 1B19,541,76,723,622,018,313,722,742,918,513,211,60,0
MihuAI turkish-stt22,621,67,742,026,021,219,627,025,527,413,215,90,0
omniASR CTC 3B24,050,58,040,826,411,414,925,046,920,714,213,928,6
Whisper small24,728,310,438,128,619,822,232,838,528,017,213,657,1
Whisper large-v3-turbo26,882,48,848,119,317,612,719,744,718,96,418,20,0
omniASR CTC 7B27,650,013,941,324,023,720,224,861,526,422,816,314,3
omniASR CTC 1B29,066,58,039,534,320,821,529,054,725,524,629,60,0
Whisper medium33,521,251,654,423,39,018,425,934,231,912,413,657,1
omniASR CTC 300M45,675,926,349,252,244,141,151,479,935,542,535,714,3
Whisper base55,844,523,250,448,7193,535,747,059,641,595,530,214,3
Whisper tiny94,751,492,7101,363,3311,454,759,664,061,361,050,042,9

Mu-Bench'te puanladığım her model, kategori bazında WER (%, balbal-normalizer). Başlıklardaki küçük sayılar klip sayısı; her sütunun en iyisi altın renkte ve kalın (en küçük iki kategori hariç). Kategori etiketleri Freya'nın sierra-benchmark veri setinden. Tutar (5 klip) ve tarih/saat (1 klip) çok küçük; bu iki sütun gürültülü.

Tüm skorlar

Ölçtüğüm her model, Mu-Bench (846 klip) ve FLEURS Türkçe testi (743 klip) üzerinde. Bizim modellerden yalnızca en iyisini, balbal-v1'i koydum. Satırlar Mu-Bench WER'ine (balbal-normalizer) göre sıralı.

ModelMu-Bench WER (balbal-normalizer)Mu-Bench LLM WERSignificant WERFLEURS
Google Chirp 38,458,03*14,30*—
ElevenLabs Scribe v28,729,43*19,15*—
Google Gemini 3.5 Transcribe8,758,68*18,20*—
Freya9,729,2721,286,61
PatientDesk Duyu10,0210,9425,303,62
OpenAI GPT-4o Mini Transcribe11,3012,82*28,61*—
balbal-v111,7811,9325,186,54
Deepgram Nova-312,3713,37*24,35*—
Qwen3-ASR 1.7B (Baseten)13,8215,03*31,91*—
Qwen3-ASR 1.7B (Modal)13,9114,76*30,61*—
Qwen3-ASR 1.7B13,95——6,52
Whisper large-v314,92——3,71
Duyu-small15,3915,7035,226,62
xAI Grok STT15,6915,94*34,04*—
omniASR LLM 7B18,35——5,31
Microsoft Azure Speech18,6514,02*30,73*—
omniASR LLM 3B19,21——6,49
omniASR LLM 1B19,53——6,95
MihuAI turkish-stt22,5924,5949,538,16
omniASR CTC 3B24,02——8,30
Whisper small24,72——12,17
Whisper large-v3-turbo26,77——4,07
omniASR CTC 7B27,57——7,71
omniASR CTC 1B28,99——11,33
Whisper medium33,51——6,29
omniASR CTC 300M45,56——20,88
Whisper base55,81——25,99
Whisper tiny94,66——42,43
Birim
Kelime hata oranı (WER), yüzde; düşük olan daha iyi. "—": ölçülmedi.
Mu-Bench WER (balbal-normalizer), FLEURS
balbal-normalizer ile puanlama, ITN'siz. balbal-normalizer: harf büyüklüğünü ve noktalamayı yok sayan, sayıları rakam rakam karşılaştıran puanlama kuralı. Her modelin metnini aynı kuralla kendim puanladım. Ticari API'lerin Mu-Bench metinleri Mu-Bench deposundan; Freya ve PatientDesk'i kendim çağırdım.
Mu-Bench LLM WER
Resmi değerlendirme: gpt-4.1 referansı ve çıktıyı normalleştiriyor, WER ondan sonra hesaplanıyor.
Significant WER
gpt-4.1 hakeme göre anlamı bozan en az bir hata içeren cümlelerin oranı.
Kaynak
Son iki sütunu balbal-v1, Freya, PatientDesk Duyu ve MihuAI turkish-stt için resmi düzeneği kendim çalıştırarak hesapladım. * işaretli değerler Mu-Bench'in yayımladığı sonuç tablosundan.

8Hız

Hızı tüm modeller için aynı yöntemle ölçtüm. 100 klip kullandım (50 FLEURS, 50 Mu-Bench). Her model 8 işlemci çekirdeğinde, batch boyutu 1 ile, klipleri tek tek işledi. Ölçülen süre, ham sesin girmesinden metnin çıkmasına kadar geçen süre: öznitelik çıkarma, ağ ve (balbal için) dil modelli ışın araması dahil. Makinenin hızı günden güne değişebildiği için bütün modelleri aynı oturumda ölçtüm.

balbal-v1, ONNX Runtime (fp32) ile gerçek zamanın ~96 katı hızda çalışıyor; dört ayrı ölçüm 95,8× ile 97,3× arasında çıktı. Aynı koşullarda Whisper large-v3 0,91×, Qwen3-ASR 1.7B 0,84× hızda. Yani bu iki model işlemcide gerçek zamana bile yetişemiyor. Karşılaştırmanın tamamı yazının başındaki grafikte.

Duyu-small'yi sonradan, ayrı bir oturumda aynı yöntemle ölçtüm. Modelin önerdiği ayarlarla (fp32, dikkat tabanlı çözücü, ışın 5) gerçek zamanın 30 katı hızda çalışıyor. Aynı oturumda balbal-v1 108× çıktı; yani balbal-v1 yaklaşık 3,6 kat daha hızlı. Grafikte Duyu-small bu oranla diğer ölçümlerin oturumuna taşınmış halde (27×) yer alıyor.

9Hatalar ve dersler

"Doksan" → "9"

İlk karışımlarla eğitilen modeller, Mu-Bench'teki "sıfır doksan kırk beş" gibi telefon numaralarını "0 9 4 5" diye yazıyordu. Sorun etiketlerdeydi. Gerçek veri sayıları rakamla yazıyordu. Sentetik sayı setinde ise etiketler rakamları tek tek yazarken TTS sesi sayıları gruplar halinde okuyordu; bu setteki rakamlı satırların %66'sı yanlış etiketlenmişti. Çözüm iki adımdı: tüm etiketlerde sayıları konuşulduğu gibi yazıyla yazmak ve bir daha eğitime başlamadan önce veriyi sistemli olarak denetlemek.

MIOpen'i eğitimde kapatmak: %41 daha hızlı

Uzunlukları farklı kliplerle eğitim yaparken AMD'nin MIOpen kütüphanesi her yeni giriş boyutu için en iyi çekirdeği yeniden arıyordu. Bir epoch'taki adım süresinin %44'ü 5 saniyeden uzun süren güncellemelere gidiyordu. Eğitim adımlarında MIOpen'i kapatıp PyTorch'un kendi çekirdeklerini kullanınca güncelleme başına süre 2,37 saniyeden 1,40 saniyeye indi. Bu sorun tek bir süreç içinde yapılan karşılaştırmalarda görünmüyor, çünkü MIOpen aynı boyutları önbellekte tutuyor. Hız ölçümlerini bu yüzden her seferinde yeni bir süreçte yaptım.

Gürültü tabanını bilmek

Mu-Bench'te yaklaşık 0,6 puanlık farklar gürültü içinde kalıyor. Bu yüzden her karşılaştırmayı eşleştirilmiş bootstrap güven aralığıyla raporluyorum. Tek bir sayıya bakarak karar vermiyorum.

İşe yaramayanlar

Sinir ağı tabanlı bir dil modeliyle yeniden puanlama, birkaç modelin olasılıklarını birleştirme, ses seviyesi normalizasyonu ve CTC ön olasılık düzeltmesi ölçülebilir bir kazanç getirmedi. Kazanç en çok veriyi düzeltmekten ve ses biçimini test koşullarına uydurmaktan geldi.

10Sınırlar ve sonraki adımlar

Kaynaklar

  1. X. Cheng ve ark. Conditional Memory via Scalable Lookup: A New Axis of Sparsity for Large Language Models (Engram). arXiv:2601.07372, 2026.
  2. G. Lample, A. Sablayrolles, M. Ranzato, L. Denoyer, H. Jégou. Large Memory Layers with Product Keys. NeurIPS, 2019.
  3. Z. Qiu ve ark. On the Design of Qwen3.8-Next Architecture: Evaluation, Efficiency, and Training Stability. arXiv:2608.30320, 2026.
  4. K. Jordan. Muon: An optimizer for hidden layers in neural networks. Blog yazısı, 2024. Ayrıca modded-nanogpt deposu.
  5. Z. Yao ve ark. CR-CTC: Consistency Regularization on CTC for Improved Speech Recognition. ICLR, 2025.
  6. J. Nozaki, T. Komatsu. Relaxing the Conditional Independence Assumption of CTC-based ASR by Conditioning on Intermediate Predictions. Interspeech, 2021.
  7. N. Jeffries, E. King, M. Kudlur, G. Nicholson, J. Wang, P. Warden. Moonshine: Speech Recognition for Live Transcription and Voice Commands. arXiv:2410.15608, 2024.
  8. A. Li, S. Ray. MU-Bench: A Multilingual Transcription Benchmark from Real Phone Calls. 2026. Veri seti, sonuç tablosu.
  9. A. Conneau ve ark. FLEURS: Few-shot Learning Evaluation of Universal Representations of Speech. IEEE SLT, 2022.
  10. Freya. sierra-benchmark veri seti. Mu-Bench kategori etiketleri buradan.
  11. KaanAydinli. Duyu-small. Hugging Face model kartı, 2026.

Atıf

balbal-v1'i bir çalışmada kullanırsan ya da ondan bahsedersen şöyle atıf verebilirsin:

Eray Yapağcı. balbal-v1: A Small Turkish Speech Recognition Model. 2026. erayy.com/balbal-blog

@misc{yapagci2026balbal,
  author       = {Eray Yapa{\u{g}}c{\i}},
  title        = {balbal-v1: A Small Turkish Speech Recognition Model},
  year         = {2026},
  month        = oct,
  howpublished = {\url{https://erayy.com/balbal-blog}},
  note         = {Code: \url{https://github.com/erayyap/balbal}}
}

Kendi kulağınla dene

Ağırlıkları indir (balbal Community License) ya da demo sayfasında kendi kulağınla dene.

Ağırlıkları indir ↗ Demoyu dene ↗ Kod (GitHub) ↗ Tanıtım sayfası

Technical post · 14 October 2026

How balbal-v1 was built

I trained a Turkish speech recognition model with 15 million active parameters on my own. The first Turkish model trained for about 35.6 hours on a rented NVIDIA RTX PRO 4500 Blackwell on RunPod; every later stage ran on a single desktop machine. Architecture, data, training, evaluation and the mistakes I made along the way.

In short

  • balbal-v1 is a CTC model that stores 45.4 million parameters but uses only 15.4 million of them per audio frame.
  • It scored 11.93% word error rate (WER) in Mu-Bench's official evaluation, and 6.54% on the FLEURS Turkish test.
  • It runs at ~96× real time on 8 CPU cores, one clip at a time.
  1. Why a small model
  2. Architecture
  3. Data
  4. Training
  5. Decoder and language model
  6. How I measured
  7. Results
  8. Speed
  9. Mistakes and lessons
  10. Limits and next steps
  11. References
  12. How to cite

1Why a small model

Two options stand out for Turkish speech recognition. The first is commercial APIs: they are good, but the audio leaves your hands and you pay per minute. The second is large open models (Whisper large-v3, Qwen3-ASR, omniASR): they are accurate, but they have over a billion parameters and barely keep up with real time on a CPU.

My goal was the gap between the two. I wanted a model that needs no GPU, competes with commercial APIs on hard audio such as phone calls, and runs comfortably even on a laptop. So I weighed every design decision on accuracy and compute cost together.

Mu-Bench error rate versus CPU speed (multiple of real time, log scale); balbal-v1 sits bottom right, both fast and accurate. The Freya and PatientDesk APIs are also on the chart.
Speed and accuracy. Local models were measured in the same session on 8 CPU cores with batch 1. Freya and PatientDesk (diamonds) use the processing time reported by their own servers. The horizontal axis is logarithmic; bottom right is best. omniASR's LLM models are not on the chart: on a CPU they would take forever. Duyu-small was timed in a separate session at 30× (balbal-v1 ran at 108× there); the chart shows it scaled by balbal-v1's ratio between the two sessions (27×).

2Architecture

The model has three parts.

Encoder: Conformer-CTC

16 kHz audio is first turned into an 80-channel log-mel spectrogram. A 16-layer Conformer processes it (model size 176, 4 attention heads, convolution kernel 31) and produces an output frame every 40 milliseconds. The starting point was NVIDIA's English stt_en_conformer_ctc_small model. I retrained it for Turkish with a 1024-piece SentencePiece (unigram) vocabulary.

Refiner and product-key memory

On top of the encoder sits a small refiner: 2 layers, width 192. It runs three times with the same weights, and each pass corrects the output a little more. Inside the refiner is a product-key memory. The product of two sets of 500 sub-keys gives 250,000 memory slots, and each slot holds a 120-dimensional vector. The table adds up to 30 million parameters, but each audio frame looks at only 64 slots (4 heads × top 16). So the model can memorize a lot while doing little compute per frame.

Mu-Bench word error rate versus active parameter count. balbal-v1 is bottom left with 15.4 million active parameters; large open models are on the right.
Error versus active parameters. The memory table is stored, but only a small part of it is read per frame. That is why the horizontal axis shows active parameters.
PartStoredUsed per frame
Conformer encoder (16 layers)12.97M12.97M
CTC output layer0.18M0.18M
Refiner (excluding memory)2.2M2.2M
Memory table (250,000 × 120)30.0M≤ 0.02M (23,040 values over 3 passes)
Total45.4M15.4M

Language model

The output is decoded with a 6-gram KenLM language model trained on SentencePiece pieces. Details in section 5.

How I arrived at this architecture

At the end of September I started with a plain Conformer-CTC (13.2M parameters). I tried every idea on the same small setup: 200 hours of data, 3 epochs, the same language model. The numbers below are the combined WER of FLEURS and two other Turkish test sets (15,610 clips) under the older scoring rule of the time, so they cannot be compared directly with the results above. What matters is the differences between them.

Experiment (200 h, 3 epochs)Combined WERDecision
Plain Conformer-CTC23.78baseline
Self-conditioning CTC23.52small gain
Attention-based decoder (AED)23.90dropped
Product-key memory only (4 heads × 16)22.23promising
Looped refiner, 3 passes22.18kept
6 passes + depth conditioning22.02not worth it over 3 passes
Loop + two-view consistency loss20.43kept
Loop + memory + consistency19.72kept
+ Muon/AdamW optimization18.43base architecture
+ mixture of experts (4 experts, top 2)18.37within noise, not added
+ distillation from a WavLM teacher18.80dropped

What I learned along the way:

To see what the parts contribute in the final design, I turned the memory and the loop off at inference in the previous release (balbal-v2-beta). This is not a retrained comparison; it only shows how much the trained model relies on these parts.

balbal-v2-beta, WER (balbal-normalizer)Full modelMemory offNo loop
FLEURS6.598.798.81
Mu-Bench13.3916.0515.90

With the language model, the loop without memory gives almost the same result as no loop at all. So the refiner's gain comes mostly from the memory.

3Data

From the start of training to the end, the model saw 1,722 hours of distinct audio at least once. With repeats this comes to about 14,450 hours; 12,100 of them come from the 10 epochs of the first Turkish model. Audio used in more than one stage was counted once (by a hash of the decoded audio). The pre-training data of NVIDIA's English starting model is not in the table.

SourceHoursShare
Real Turkish speech (open datasets and natural speech collected from the web)455.626.5%
Synthetic Turkish speech (TTS)1,195.769.4%
Read web and news text, short sentences714.441.5%
Everyday speech and dialogues329.419.1%
Letter-by-letter codes, email50.02.9%
Numbers, phone numbers, amounts40.72.4%
Names and suffixes24.51.4%
Call-center dialogues20.41.2%
Short answers16.20.9%
Other70.54.1%
English speech (LibriSpeech)61.33.6%
Non-speech audio (noise, music, silence)9.30.5%
Total: 1,075,680 clips1,721.9100%

I added no new audio in the last training stage. I built a 702-hour mix from recordings seen before and raised the share of real speech to 64%.

I generated the synthetic speech with open TTS models. I put weight on cases that are rare in real data: codes read letter by letter, phone numbers, names and short "evet / hayır / tamam" (yes / no / okay) answers. That is where the real difficulty of call-center speech lies.

I cut and merged real recordings the Moonshine way

In the last training stage I rearranged the real recordings following the way Moonshine prepares its training data (Jeffries et al., 2024). Moonshine joins consecutive segments of the same recording, with at most 2 seconds of gap between them, into examples of 4 to 30 seconds. That gives a bimodal duration distribution: one peak at 3–6 seconds, the other at 27–30 seconds. I merged the real recordings the same way. Because I deliberately kept the short synthetic clips and the cuts below short, only 5.7% of the mix falls in the 25–31 second range (23% in Moonshine).

I also cut short stock phrases out of real recordings. In the training text I found 35 common conversational phrases such as "iyi günler" (have a nice day), "kolay gelsin" (roughly "take it easy") and "rica ederim" (you're welcome). With the previous model's CTC alignment I found where each phrase starts and ends in the audio and cut it with an 80 ms margin around the word boundaries. I kept only the cuts that the model read back as exactly that phrase: 11,476 cuts, 2 hours in total. To repeat rare phrases more often, I multiplied each phrase between 3 and 15 times. The goal was to show the model, one by one, the short answers that are common in call centers but get lost inside long recordings.

I set a few rules:

4Training

balbal-v1 was not trained in one go; it is the last link in a lineage of several stages.

  1. English starting pointNVIDIA stt_en_conformer_ctc_small (Conformer-CTC, ~13M parameters).
  2. First Turkish model10 epochs on an NVIDIA RTX PRO 4500 Blackwell on RunPod, about 35.6 hours.
  3. General Turkish modelContinued from the first Turkish model: 3 epochs on 203 hours of new data.
  4. balbal-v1-betaWeight soup: general Turkish model 75%, first Turkish model 25%.
  5. balbal-v2-betaRefiner and product-key memory added. 27,024 updates on email and letter-by-letter code data, then a weight soup at 0.6.
  6. balbal-v1Starting from v2-beta, 6000 updates on the 702-hour mix (about 0.57 epochs). 5,024 seconds (84 minutes) on the local iGPU. No soup; the raw weights are used.

Settings of the last stage: batch 32; learning rate 1e-4 (5e-4 for the memory table). I used Muon on the matrix weights and AdamW on the rest.

The speedrun approach

I evaluated training changes with the "speedrun" approach of Keller Jordan's modded-nanogpt. The value of a change was measured not by time per step but by time to reach a fixed quality target. The target was a set of thresholds for the Turkish, English and non-speech groups on held-out control sets, with the condition of no regression on the development set (DEV). I tested differences between two models with a paired bootstrap. Checkpoints were selected by looking only at DEV and the control sets; the test sets and Mu-Bench were not looked at.

Hardware

The first Turkish model (10 epochs, about 35.6 hours) was trained on an NVIDIA RTX PRO 4500 Blackwell (32 GB) GPU that I rented on RunPod. All later stages continued on my own machine: a single AMD Ryzen AI Max+ 395 with the Radeon 8060S integrated GPU, ROCm and 121 GB of unified memory. For some of the architecture trials and speedrun measurements I also rented RTX 5090 and A40 GPUs on RunPod. I describe the Radeon's own quirks in section 9.

5Decoder and language model

The CTC output is decoded with flashlight's lexicon-free beam search. The language model is a 6-gram KenLM over SentencePiece pieces. It was trained on 45 million pieces and takes 124 MB. The language model text consists of all training text in this model's lineage, with DEV, test and control sentences removed. For each model release I rebuild the language model on the data that release has seen.

The decoder settings were calibrated on the DEV set: language model weight α = 0.75, word bonus β = 1.5, temperature 1.2, beam width 64, top 12 pieces per frame, pruning threshold 8. The language model brings WER down from 9.35% to 6.54% on FLEURS and from 13.86% to 11.78% on Mu-Bench.

The output has capitalization and punctuation. Numbers come out as words; there is no inverse text normalization (ITN) step to turn them into digits inside the model.

6How I measured

In my own measurements I used a single scoring rule (balbal-normalizer). Case and punctuation are ignored. Numbers are compared digit by digit; "doksan" (ninety), "dokuz sıfır" (nine zero) and "90" count as the same. Codes written as one word are split into their letters and digits. The rule only forgives different spellings of the same words; a different word is always an error. All open models in the tables were scored on the same clips with the same rule. The scoring code is on GitHub: normalizer/metrics_v2.py. The full normalizer, inference and training code is in the github.com/erayyap/balbal repository.

For Mu-Bench I also ran the official evaluation harness (sierra-research/mu-bench, gpt-4.1 judge). Before submission, numbers written as words were converted to digits (ITN). Scores for commercial APIs were taken from Mu-Bench's own leaderboard. To cut cost I took one shortcut: for 342 of the 846 sentences, balbal-v1 produced exactly the same text as balbal-v2-beta. For those I reused v2-beta's judge verdicts; the remaining 504 sentences were judged again.

Mu-Bench was used only to read results. It never went into the training data, the language model or checkpoint selection.

7Results

Mu-Bench: against commercial APIs

Mu-Bench (Li and Ray, 2026) contains 846 Turkish sentences from 8 kHz customer-service calls. In the official evaluation balbal-v1 has 11.93% WER and 25.18% significant WER (errors that change the meaning). That puts it ahead of Azure, Deepgram, OpenAI and xAI, and behind Google and ElevenLabs. On meaning-changing errors Deepgram Nova-3 (24.35%) is slightly better.

Mu-Bench official WER bar chart: balbal-v1 at 11.93 percent among the commercial APIs.
Mu-Bench, official evaluation. API scores from the Mu-Bench leaderboard. balbal-v1 was scored with the same harness, the gpt-4.1 judge and ITN applied.

Against other models

I ran all the open-weight models myself on the same clips with the balbal-normalizer rule. On Mu-Bench (balbal-normalizer, no ITN) balbal-v1 leads them all with 11.78%. The closest are Qwen3-ASR 1.7B (13.95%) and Whisper large-v3 (14.92%), both a hundred times larger. FLEURS is a different story: Whisper large-v3 (3.71%) and turbo (4.07%) are clearly ahead. balbal-v1's 6.54% is on par with Qwen3-ASR 1.7B (6.52%). FLEURS consists of clean, read Wikipedia sentences, where the broad world knowledge of large models helps more.

One more model, added later: Duyu-small, an open-weight Turkish + English model with 19.3 million active parameters. Its model card says its encoder design (shared passes and the sparse acoustic memory) is taken from balbal, with an attention decoder and a bilingual vocabulary added on top. On the same clips with the balbal-normalizer rule it scores 15.39% on Mu-Bench and 6.62% on FLEURS. In the official Mu-Bench evaluation (GPT-4.1 judge) it gets 15.70% WER and 35.22% significant WER (balbal-v1: 11.93% and 25.18%).

WER of other models on Mu-Bench (balbal-normalizer); balbal-v1 at the top with the lowest error rate.
Mu-Bench, other models (scored with balbal-normalizer). Every model was scored on the same 846 clips with the same rule.
WER of other models on the FLEURS Turkish test set; Whisper large-v3 best, balbal-v1 close to Qwen3-ASR.
FLEURS Turkish test, 743 clips. Clean, read speech; where large models are strongest.

MihuAI's own test

MihuAI released an 81-clip test together with its turkish-stt model. The recordings are TTS-generated, 24 kHz. I ran this test with MihuAI's own scoring code, without changing anything. balbal-v1's WER is 7.94%. MihuAI turkish-stt, with its own recommended settings, gets 15.20% on the same test (their published figure is 15.37%).

By category

The categories do not come from Mu-Bench itself. I used the category labels from Freya's sierra-benchmark dataset, which contains the same audio and text. balbal-v1 is strong on phone numbers (3.96%), addresses (4.53%) and code sequences (5.34%). Names (24.84%), spelling (20.06%) and instruction requests (19.98%) are still its weak spots.

Mu-Bench WER by category: balbal-v1 versus Duyu-small, Qwen3-ASR and Whisper large-v3.
Mu-Bench categories (scored with balbal-normalizer). Category labels from Freya's sierra-benchmark dataset. Only categories with at least 20 clips are shown; numbers in brackets are clip counts. balbal-v1 leads on email, addresses and short answers and is about even with Duyu-small on phone numbers (3.96% vs 3.76%); Whisper large-v3 leads most of the rest.
ModelAll 846Short response 229Phone number 109Email 102Request 85Code sequence 84Conversational 65Question 63Name 55Spelling 27Address 21Amount 5Date/time 1
Google Chirp 38.48.02.115.610.40.97.811.425.514.03.913.60.0
ElevenLabs Scribe v28.712.42.810.412.03.19.514.323.612.22.111.40.0
Google Gemini 3.5 Transcribe8.86.62.713.510.76.49.712.023.012.84.813.60.0
Freya9.76.14.718.611.81.49.712.819.213.44.213.60.0
PatientDesk Duyu10.09.64.317.613.54.111.011.918.010.33.99.10.0
OpenAI GPT-4o Mini Transcribe11.315.73.014.916.07.712.916.030.611.63.64.50.0
balbal-v111.812.24.012.520.05.313.118.624.820.14.518.20.0
Deepgram Nova-312.411.42.727.014.810.07.813.526.114.04.811.40.0
Qwen3-ASR 1.7B (Baseten)13.817.15.521.318.49.011.816.930.415.55.413.60.0
Qwen3-ASR 1.7B (Modal)13.918.05.321.618.98.812.016.929.815.25.113.60.0
Qwen3-ASR 1.7B13.917.55.522.418.18.311.816.531.115.56.313.60.0
Whisper large-v314.913.64.340.014.04.512.216.021.710.95.59.10.0
Duyu-small15.418.03.822.321.811.213.920.932.322.88.418.20.0
xAI Grok STT15.727.95.818.718.912.415.820.028.620.17.215.90.0
omniASR LLM 7B18.451.35.623.617.214.113.517.142.215.510.216.30.0
Microsoft Azure Speech18.613.812.135.816.514.710.517.626.131.112.911.40.0
omniASR LLM 3B19.238.36.922.823.111.214.125.055.318.212.720.40.0
omniASR LLM 1B19.541.76.723.622.018.313.722.742.918.513.211.60.0
MihuAI turkish-stt22.621.67.742.026.021.219.627.025.527.413.215.90.0
omniASR CTC 3B24.050.58.040.826.411.414.925.046.920.714.213.928.6
Whisper small24.728.310.438.128.619.822.232.838.528.017.213.657.1
Whisper large-v3-turbo26.882.48.848.119.317.612.719.744.718.96.418.20.0
omniASR CTC 7B27.650.013.941.324.023.720.224.861.526.422.816.314.3
omniASR CTC 1B29.066.58.039.534.320.821.529.054.725.524.629.60.0
Whisper medium33.521.251.654.423.39.018.425.934.231.912.413.657.1
omniASR CTC 300M45.675.926.349.252.244.141.151.479.935.542.535.714.3
Whisper base55.844.523.250.448.7193.535.747.059.641.595.530.214.3
Whisper tiny94.751.492.7101.363.3311.454.759.664.061.361.050.042.9

Every model I scored on Mu-Bench, WER (%, balbal-normalizer) per category. Small numbers in the headers are clip counts; the best value in each column is in bold gold (except the two smallest categories). Category labels come from Freya's sierra-benchmark dataset. Amount (5 clips) and date/time (1 clip) are tiny, so those two columns are noisy.

All scores

Every model I measured, on Mu-Bench (846 clips) and the FLEURS Turkish test (743 clips). Of our own models I list only the best one, balbal-v1. Rows are sorted by Mu-Bench WER (balbal-normalizer).

ModelMu-Bench WER (balbal-normalizer)Mu-Bench LLM WERSignificant WERFLEURS
Google Chirp 38.458.03*14.30*—
ElevenLabs Scribe v28.729.43*19.15*—
Google Gemini 3.5 Transcribe8.758.68*18.20*—
Freya9.729.2721.286.61
PatientDesk Duyu10.0210.9425.303.62
OpenAI GPT-4o Mini Transcribe11.3012.82*28.61*—
balbal-v111.7811.9325.186.54
Deepgram Nova-312.3713.37*24.35*—
Qwen3-ASR 1.7B (Baseten)13.8215.03*31.91*—
Qwen3-ASR 1.7B (Modal)13.9114.76*30.61*—
Qwen3-ASR 1.7B13.95——6.52
Whisper large-v314.92——3.71
Duyu-small15.3915.7035.226.62
xAI Grok STT15.6915.94*34.04*—
omniASR LLM 7B18.35——5.31
Microsoft Azure Speech18.6514.02*30.73*—
omniASR LLM 3B19.21——6.49
omniASR LLM 1B19.53——6.95
MihuAI turkish-stt22.5924.5949.538.16
omniASR CTC 3B24.02——8.30
Whisper small24.72——12.17
Whisper large-v3-turbo26.77——4.07
omniASR CTC 7B27.57——7.71
omniASR CTC 1B28.99——11.33
Whisper medium33.51——6.29
omniASR CTC 300M45.56——20.88
Whisper base55.81——25.99
Whisper tiny94.66——42.43
Unit
Word error rate (WER), percent; lower is better. "—": not measured.
Mu-Bench WER (balbal-normalizer), FLEURS
Scored with balbal-normalizer, no ITN. balbal-normalizer is the scoring rule that ignores case and punctuation and compares numbers digit by digit. I scored every model's transcripts myself with the same rule. The commercial APIs' Mu-Bench transcripts come from the Mu-Bench repository; I called Freya and PatientDesk myself.
Mu-Bench LLM WER
The official evaluation: gpt-4.1 normalizes the reference and the output, then WER is computed.
Significant WER
The share of sentences with at least one meaning-changing error according to the gpt-4.1 judge.
Source
I computed the last two columns for balbal-v1, Freya, PatientDesk Duyu and MihuAI turkish-stt by running the official harness myself. Values marked * come from the leaderboard Mu-Bench publishes.

8Speed

I measured speed the same way for every model. I used 100 clips (50 FLEURS, 50 Mu-Bench). Each model processed the clips one by one on 8 CPU cores with batch size 1. The measured time runs from raw audio in to text out: feature extraction, the network and (for balbal) the beam search with the language model included. Because the machine's speed can vary from day to day, I measured all models in the same session.

balbal-v1 runs at ~96× real time with ONNX Runtime (fp32); four separate measurements came out between 95.8× and 97.3×. Under the same conditions Whisper large-v3 runs at 0.91× and Qwen3-ASR 1.7B at 0.84×, so these two models cannot even keep up with real time on a CPU. The full comparison is in the chart at the top of this post.

I measured Duyu-small later, in a separate session, the same way. With its recommended settings (fp32, attention decoder, beam 5) it runs at 30× real time. In the same session balbal-v1 ran at 108×, so balbal-v1 is about 3.6 times faster. In the chart, Duyu-small is moved to the other measurements' session by that ratio (27×).

9Mistakes and lessons

"Doksan" → "9"

Models trained on the first mixes wrote phone numbers in Mu-Bench such as "sıfır doksan kırk beş" (zero ninety forty-five) as "0 9 4 5". The problem was in the labels. The real data wrote numbers as digits. In the synthetic number set, the labels spelled out digits one by one while the TTS audio read the numbers in groups; 66% of the digit rows in that set were mislabeled. The fix had two steps: write numbers out as spoken in all labels, and audit the data systematically before every new training run.

Turning MIOpen off in training: 41% faster

When training on clips of different lengths, AMD's MIOpen library searched for the best kernel again for every new input shape. 44% of the step time in an epoch went to updates that took longer than 5 seconds. Turning MIOpen off in training steps and using PyTorch's own kernels brought the time per update down from 2.37 to 1.40 seconds. The problem does not show up in comparisons made inside a single process, because MIOpen caches shapes it has already seen. That is why I ran every speed measurement in a fresh process.

Knowing the noise floor

On Mu-Bench, differences of about 0.6 points are within noise. So I report every comparison with a paired bootstrap confidence interval. I do not decide based on a single number.

What did not work

Rescoring with a neural language model, combining the probabilities of several models, loudness normalization and CTC prior correction brought no measurable gain. The gains came mostly from fixing the data and matching the audio format to test conditions.

10Limits and next steps

References

  1. X. Cheng et al. Conditional Memory via Scalable Lookup: A New Axis of Sparsity for Large Language Models (Engram). arXiv:2601.07372, 2026.
  2. G. Lample, A. Sablayrolles, M. Ranzato, L. Denoyer, H. Jégou. Large Memory Layers with Product Keys. NeurIPS, 2019.
  3. Z. Qiu et al. On the Design of Qwen3.8-Next Architecture: Evaluation, Efficiency, and Training Stability. arXiv:2608.30320, 2026.
  4. K. Jordan. Muon: An optimizer for hidden layers in neural networks. Blog post, 2024. See also the modded-nanogpt repository.
  5. Z. Yao et al. CR-CTC: Consistency Regularization on CTC for Improved Speech Recognition. ICLR, 2025.
  6. J. Nozaki, T. Komatsu. Relaxing the Conditional Independence Assumption of CTC-based ASR by Conditioning on Intermediate Predictions. Interspeech, 2021.
  7. N. Jeffries, E. King, M. Kudlur, G. Nicholson, J. Wang, P. Warden. Moonshine: Speech Recognition for Live Transcription and Voice Commands. arXiv:2410.15608, 2024.
  8. A. Li, S. Ray. MU-Bench: A Multilingual Transcription Benchmark from Real Phone Calls. 2026. Dataset, leaderboard.
  9. A. Conneau et al. FLEURS: Few-shot Learning Evaluation of Universal Representations of Speech. IEEE SLT, 2022.
  10. Freya. sierra-benchmark dataset, the source of the Mu-Bench category labels.
  11. KaanAydinli. Duyu-small. Hugging Face model card, 2026.

How to cite

If you use or mention balbal-v1 in your work, you can cite it like this:

Eray Yapağcı. balbal-v1: A Small Turkish Speech Recognition Model. 2026. erayy.com/balbal-blog

@misc{yapagci2026balbal,
  author       = {Eray Yapa{\u{g}}c{\i}},
  title        = {balbal-v1: A Small Turkish Speech Recognition Model},
  year         = {2026},
  month        = oct,
  howpublished = {\url{https://erayy.com/balbal-blog}},
  note         = {Code: \url{https://github.com/erayyap/balbal}}
}

Hear it for yourself

Download the weights (balbal Community License), or try it by ear on the demo page.

Download the weights ↗ Try the demo ↗ Code (GitHub) ↗ Launch page