--- license: cc-by-nc-4.0 language: - kk - ru - en base_model: Qwen/Qwen3-ASR-0.6B pipeline_tag: automatic-speech-recognition tags: - automatic-speech-recognition - asr - speech - kazakh - russian - english - qwen3-asr model-index: - name: Qwen3-ASR-0.6B-kk-ru-en results: - task: type: automatic-speech-recognition name: Automatic Speech Recognition dataset: type: google/fleurs name: FLEURS (kk_kz) config: kk_kz split: test metrics: - type: wer value: 17.8 name: Test WER - type: cer value: 7.38 name: Test CER - task: type: automatic-speech-recognition name: Automatic Speech Recognition dataset: type: google/fleurs name: FLEURS (ru_ru) config: ru_ru split: test metrics: - type: wer value: 12.77 name: Test WER - type: cer value: 5.38 name: Test CER - task: type: automatic-speech-recognition name: Automatic Speech Recognition dataset: type: google/fleurs name: FLEURS (en_us) config: en_us split: test metrics: - type: wer value: 5.92 name: Test WER - type: cer value: 3.22 name: Test CER --- # Qwen3-ASR-0.6B-kk-ru-en A trilingual **Kazakh / Russian / English** speech-recognition model, fine-tuned from [`Qwen/Qwen3-ASR-0.6B`](https://huggingface.co/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`](https://github.com/QwenLM/Qwen3-ASR) toolkit. Use a fresh environment to avoid dependency conflicts. ```bash pip install -U qwen-asr # transformers backend # pip install -U "qwen-asr[vllm]" # add the vLLM backend (faster + streaming) ``` ## Usage ```python 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: ```python # 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: ```bash 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: ```python 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 ```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. ```bash pip install -U qwen-asr datasets jiwer transformers ``` ```python 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](https://github.com/QwenLM/Qwen3-ASR) by the Qwen team, and evaluated with [FLEURS](https://huggingface.co/datasets/google/fleurs) and the ISSAI Kazakh Speech Corpus 2 (KSC2). Released under Apache-2.0, following the base model's license.