Qwen3-ASR-0.6B-kk-ru-en
A trilingual Kazakh / Russian / English speech-recognition model, fine-tuned
from Qwen/Qwen3-ASR-0.6B. It adds
Kazakh (which the base model does not recognize) while preserving strong
Russian and English transcription, in a single 0.6B model with streaming/offline
unified inference.
- Languages: Kazakh (
kk), Russian (ru), English (en) - Base model: Qwen/Qwen3-ASR-0.6B (audio encoder → Qwen3 decoder)
- Numbers are emitted as digits (e.g.
1990,25), not spelled out — no inverse-text-normalization step is needed downstream. - License: Apache-2.0
Performance
Word/character error rate (%) on held-out public test sets, lower is better.
Text is scored with the standard multilingual-Whisper normalization protocol
(EnglishTextNormalizer for English, BasicTextNormalizer for Kazakh/Russian).
| Test set | Lang | WER | CER |
|---|---|---|---|
FLEURS (kk_kz, test) |
kk | 17.80 | 7.38 |
FLEURS (ru_ru, test) |
ru | 12.77 | 5.38 |
FLEURS (en_us, test) |
en | 5.92 | 3.22 |
| KSC2 (ISSAI, test) | kk | 20.42 | 6.97 |
FLEURS/KSC2 numbers are measured on a fixed seeded subset (500 utts per FLEURS
language; 1000 utts for KSC2). Language is auto-detected for Kazakh and forced to
"Russian" / "English" for the other two. See Benchmark below to reproduce.
Installation
The model runs with the official qwen-asr
toolkit. Use a fresh environment to avoid dependency conflicts.
pip install -U qwen-asr # transformers backend
# pip install -U "qwen-asr[vllm]" # add the vLLM backend (faster + streaming)
Usage
import torch
from qwen_asr import Qwen3ASRModel
model = Qwen3ASRModel.from_pretrained(
"nur-dev/Qwen3-ASR-0.6B-kk-ru-en",
dtype=torch.bfloat16,
device_map="cuda:0",
max_new_tokens=256, # raise for long audio
)
# audio can be a local path, URL, base64 string, or an (np.ndarray, sample_rate) tuple
results = model.transcribe(
audio="speech.wav",
language=None, # auto-detect (recommended for Kazakh)
)
print(results[0].language, results[0].text)
Language selection. language=None auto-detects and is recommended for
Kazakh. For Russian or English you may force the label to skip detection:
# Batch inference; pass one language per clip (or None to auto-detect each)
results = model.transcribe(
audio=["kk.wav", "ru.wav", "en.wav"],
language=[None, "Russian", "English"],
)
for r in results:
print(r.text)
Serve
OpenAI-compatible server (vLLM)
qwen-asr-serve wraps vllm serve and accepts any vllm serve argument:
pip install -U "qwen-asr[vllm]"
qwen-asr-serve nur-dev/Qwen3-ASR-0.6B-kk-ru-en \
--gpu-memory-utilization 0.8 --host 0.0.0.0 --port 8000
Send requests to the /v1/chat/completions endpoint:
import requests
from qwen_asr import parse_asr_output
data = {"messages": [{"role": "user", "content": [
{"type": "audio_url", "audio_url": {"url": "https://example.com/speech.wav"}},
]}]}
resp = requests.post("http://localhost:8000/v1/chat/completions", json=data, timeout=300)
content = resp.json()["choices"][0]["message"]["content"]
language, text = parse_asr_output(content)
print(language, text)
vLLM from Python
import torch
from qwen_asr import Qwen3ASRModel
if __name__ == "__main__": # required by vLLM multiprocessing
model = Qwen3ASRModel.LLM(
model="nur-dev/Qwen3-ASR-0.6B-kk-ru-en",
gpu_memory_utilization=0.7,
max_new_tokens=256,
)
out = model.transcribe(audio="speech.wav", language=None)
print(out[0].text)
Benchmark
Self-contained WER/CER on the full FLEURS test split for each language. Uses the same Whisper normalization protocol as the reported numbers.
pip install -U qwen-asr datasets jiwer transformers
import re, unicodedata, torch, jiwer
from datasets import load_dataset
from qwen_asr import Qwen3ASRModel
from transformers.models.whisper.english_normalizer import (
BasicTextNormalizer, EnglishTextNormalizer,
)
basic, english = BasicTextNormalizer(), EnglishTextNormalizer({})
def norm(t, lang):
t = unicodedata.normalize("NFKC", t or "")
t = english(t) if lang == "en" else basic(t)
return re.sub(r"\s+", " ", t).strip()
# FLEURS config name + language to force ("Kazakh" is auto-detected → None)
CFG = {"kk": ("kk_kz", None), "ru": ("ru_ru", "Russian"), "en": ("en_us", "English")}
model = Qwen3ASRModel.from_pretrained(
"nur-dev/Qwen3-ASR-0.6B-kk-ru-en",
dtype=torch.bfloat16, device_map="cuda:0", max_new_tokens=256,
)
for lang, (cfg, force) in CFG.items():
ds = load_dataset("google/fleurs", cfg, split="test")
refs = [ex["transcription"] for ex in ds]
auds = [(ex["audio"]["array"].astype("float32"), ex["audio"]["sampling_rate"]) for ex in ds]
hyps = []
for i in range(0, len(auds), 32):
batch = auds[i:i + 32]
hyps += [o.text for o in model.transcribe(audio=batch, language=[force] * len(batch))]
R = [norm(r, lang) for r in refs]
H = [norm(h, lang) for h in hyps]
pairs = [(r, h) for r, h in zip(R, H) if r]
R, H = [p[0] for p in pairs], [p[1] for p in pairs]
print(f"{lang}: WER={100 * jiwer.wer(R, H):.2f} CER={100 * jiwer.cer(R, H):.2f} n={len(R)}")
The headline table uses a fixed 500-utterance seeded subset per language; this script runs the full FLEURS test split, so expect results within ~1 WER point.
Acknowledgements
Built on Qwen3-ASR by the Qwen team, and evaluated with FLEURS and the ISSAI Kazakh Speech Corpus 2 (KSC2). Released under Apache-2.0, following the base model's license.
- Downloads last month
- 8
Model tree for nur-dev/Qwen3-ASR-0.6B-kk-ru-en
Base model
Qwen/Qwen3-ASR-0.6BEvaluation results
- Test WER on FLEURS (kk_kz)test set self-reported17.800
- Test CER on FLEURS (kk_kz)test set self-reported7.380
- Test WER on FLEURS (ru_ru)test set self-reported12.770
- Test CER on FLEURS (ru_ru)test set self-reported5.380
- Test WER on FLEURS (en_us)test set self-reported5.920
- Test CER on FLEURS (en_us)test set self-reported3.220