Repository navigation
Add support for Meta Omnilingual ASR v2 models - #3138
Conversation
Summary of ChangesHello @Edison2ST, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request integrates support for the latest Meta Omnilingual ASR v2 models, enhancing the project's multilingual speech recognition capabilities without requiring modifications to the core C++ codebase. It introduces a dedicated Python example to facilitate usage and demonstrates the model's performance and compatibility. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
📝 WalkthroughWalkthroughA new Python example script is added that demonstrates non-streaming Omnilingual ASR CTC v2 decoding using sherpa-onnx, including functions for recognizer initialization, audio loading, single and batch file decoding, with timing metrics and error handling. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request adds a Python example for the new Meta Omnilingual ASR v2 models. The changes are clear and the example is helpful. I've provided a few suggestions to improve code style and maintainability, mainly around refactoring hardcoded paths and adhering to standard Python import conventions. These changes will make the example script cleaner and more aligned with best practices. Great first contribution!
| model = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/model.int8.onnx" | ||
| tokens = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/tokens.txt" | ||
| test_wav_en = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/test_wavs/en.wav" | ||
| test_wav_de = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/test_wavs/de.wav" | ||
| test_wav_fr = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/test_wavs/fr.wav" | ||
| test_wav_es = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/test_wavs/es.wav" |
There was a problem hiding this comment.
The file paths for the model, tokens, and test waves are long and repetitive. To improve readability and maintainability, you can define a base directory path using pathlib.Path. This makes it easier to update the model version in the future.
| model = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/model.int8.onnx" | |
| tokens = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/tokens.txt" | |
| test_wav_en = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/test_wavs/en.wav" | |
| test_wav_de = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/test_wavs/de.wav" | |
| test_wav_fr = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/test_wavs/fr.wav" | |
| test_wav_es = "./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05/test_wavs/es.wav" | |
| repo_dir = Path("./sherpa-onnx-omnilingual-asr-1600-languages-300M-ctc-v2-int8-2026-02-05") | |
| model = str(repo_dir / "model.int8.onnx") | |
| tokens = str(repo_dir / "tokens.txt") | |
| test_wav_en = str(repo_dir / "test_wavs/en.wav") | |
| test_wav_de = str(repo_dir / "test_wavs/de.wav") | |
| test_wav_fr = str(repo_dir / "test_wavs/fr.wav") | |
| test_wav_es = str(repo_dir / "test_wavs/es.wav") |
| raise ValueError("""Please download model files from | ||
| https://huggingface.co/Edison2ST/sherpa-onnx-omnilingual-asr-1600-languages-ctc-v2 | ||
| """) |
There was a problem hiding this comment.
The multi-line string for the error message includes leading whitespace from the source code indentation, which might not be intended. Using standard string literal concatenation results in a cleaner error message.
raise ValueError(
"Please download model files from "
"https://huggingface.co/Edison2ST/sherpa-onnx-omnilingual-asr-1600-languages-ctc-v2"
)| if sample_rate != 16000: | ||
| import librosa | ||
|
|
||
| audio = librosa.resample(audio, orig_sr=sample_rate, target_sr=16000) |
There was a problem hiding this comment.
According to PEP 8, imports should be at the top of the file. This makes dependencies clear and avoids re-importing librosa every time load_audio is called. Please move import librosa to the top of the script with the other imports.
if sample_rate != 16000:
audio = librosa.resample(audio, orig_sr=sample_rate, target_sr=16000)There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@python-api-examples/offline-omnilingual-asr-ctc-v2-decode-files.py`:
- Around line 65-85: The decode_single_file function can divide by zero when the
loaded audio has zero samples; modify decode_single_file to check samples (or
audio_duration) after load_audio and handle the empty case—e.g., if len(samples)
== 0: log/print a clear message including filename and return early (or set
audio_duration to a safe nonzero value) so computing real_time_factor =
elapsed_seconds / audio_duration and the formatted prints cannot raise
ZeroDivisionError; ensure the guard covers both the real_time_factor computation
and any prints that use audio_duration or derived values.
- Around line 88-119: The decode_multiple_files function can divide by zero if
audio_duration is 0 and should use strict zip to catch mismatched lists; update
decode_multiple_files to guard the real_time_factor calculation by checking if
audio_duration > 0 (or set real_time_factor to float("inf")/0.0-safe value)
before computing elapsed_seconds / audio_duration, and change the for loop that
iterates filenames and streams to use zip(filenames, streams, strict=True) so
length mismatches raise immediately; reference symbols: decode_multiple_files,
audio_duration, real_time_factor, filenames, streams, and the zip(...) call.
| def decode_single_file(recognizer, filename): | ||
| samples = load_audio(filename) | ||
|
|
||
| start_time = time.time() | ||
|
|
||
| stream = recognizer.create_stream() | ||
| stream.accept_waveform(sample_rate=16000, waveform=samples) | ||
| recognizer.decode_stream(stream) | ||
|
|
||
| end_time = time.time() | ||
| elapsed_seconds = end_time - start_time | ||
| audio_duration = len(samples) / 16000 | ||
| real_time_factor = elapsed_seconds / audio_duration | ||
|
|
||
| print("---") | ||
| print(filename) | ||
| print(stream.result) | ||
| print(f"Elapsed seconds: {elapsed_seconds:.3f}") | ||
| print(f"Audio duration in seconds: {audio_duration:.3f}") | ||
| print(f"RTF: {elapsed_seconds:.3f}/{audio_duration:.3f} = {real_time_factor:.3f}") | ||
| print() |
There was a problem hiding this comment.
Potential division by zero if audio file is empty.
If the audio file exists but contains no samples, audio_duration will be 0, causing a ZeroDivisionError on line 77. For an example script this is a minor edge case, but consider adding a guard.
🛡️ Suggested fix
audio_duration = len(samples) / 16000
- real_time_factor = elapsed_seconds / audio_duration
+ real_time_factor = elapsed_seconds / audio_duration if audio_duration > 0 else float('inf')🤖 Prompt for AI Agents
In `@python-api-examples/offline-omnilingual-asr-ctc-v2-decode-files.py` around
lines 65 - 85, The decode_single_file function can divide by zero when the
loaded audio has zero samples; modify decode_single_file to check samples (or
audio_duration) after load_audio and handle the empty case—e.g., if len(samples)
== 0: log/print a clear message including filename and return early (or set
audio_duration to a safe nonzero value) so computing real_time_factor =
elapsed_seconds / audio_duration and the formatted prints cannot raise
ZeroDivisionError; ensure the guard covers both the real_time_factor computation
and any prints that use audio_duration or derived values.
| def decode_multiple_files(recognizer, filenames): | ||
| streams = [] | ||
|
|
||
| start_time = time.time() | ||
|
|
||
| audio_duration = 0 | ||
|
|
||
| for filename in filenames: | ||
| samples = load_audio(filename) | ||
| audio_duration += len(samples) / 16000 | ||
|
|
||
| stream = recognizer.create_stream() | ||
| stream.accept_waveform(sample_rate=16000, waveform=samples) | ||
| streams.append(stream) | ||
|
|
||
| recognizer.decode_streams(streams) | ||
|
|
||
| end_time = time.time() | ||
| elapsed_seconds = end_time - start_time | ||
| real_time_factor = elapsed_seconds / audio_duration | ||
|
|
||
| for name, stream in zip(filenames, streams): | ||
| print("---") | ||
| print(name) | ||
| print(stream.result) | ||
| print() | ||
|
|
||
| print(f"Elapsed seconds: {elapsed_seconds:.3f}") | ||
| print(f"Audio duration in seconds: {audio_duration:.3f}") | ||
| print(f"RTF: {elapsed_seconds:.3f}/{audio_duration:.3f} = {real_time_factor:.3f}") | ||
| print() | ||
| print() |
There was a problem hiding this comment.
Same division by zero risk; consider strict=True for zip().
-
Line 107 has the same potential division by zero issue as
decode_single_file()if all audio files are empty. -
Per static analysis, adding
strict=Truetozip()(Python 3.10+) helps catch length mismatches early.
,
♻️ Suggested improvements
- real_time_factor = elapsed_seconds / audio_duration
+ real_time_factor = elapsed_seconds / audio_duration if audio_duration > 0 else float('inf')
- for name, stream in zip(filenames, streams):
+ for name, stream in zip(filenames, streams, strict=True):🧰 Tools
🪛 Ruff (0.14.14)
[warning] 109-109: zip() without an explicit strict= parameter
Add explicit value for parameter strict=
(B905)
🤖 Prompt for AI Agents
In `@python-api-examples/offline-omnilingual-asr-ctc-v2-decode-files.py` around
lines 88 - 119, The decode_multiple_files function can divide by zero if
audio_duration is 0 and should use strict zip to catch mismatched lists; update
decode_multiple_files to guard the real_time_factor calculation by checking if
audio_duration > 0 (or set real_time_factor to float("inf")/0.0-safe value)
before computing elapsed_seconds / audio_duration, and change the for loop that
iterates filenames and streams to use zip(filenames, streams, strict=True) so
length mismatches raise immediately; reference symbols: decode_multiple_files,
audio_duration, real_time_factor, filenames, streams, and the zip(...) call.
|
Thank you for your contribution! Regarding 4-bit Quantization: Is it compatible with the current ONNX Runtime version v1.23.2? Do users need to take any special steps to run 4-bit quantized ONNX models? |
|
@csukuangfj It works just as good as the int8 or fp32 versions, meaning it doesn't require any extensions or modifications to the C++ core, just change the path from I updated the model source with the .tar.bz2 and the scripts files to generate those models, I used the native BashONNX Runtime Versionpython -c "import onnxruntime; print(f'onnxruntime version: {onnxruntime.__version__}')"
onnxruntime version: 1.23.2sherpa-onnx-offlinesherpa-onnx-offline --omnilingual-asr-model=model.int4.onnx --tokens=tokens.txt --num-threads=4 ./test_wavs/de.wav ./test_wavs/en.wav ./test_wavs/es.wav ./test_wavs/fr.wav
D:\a\sherpa-onnx\sherpa-onnx\sherpa-onnx\csrc\parse-options.cc:Read:373 sherpa-onnx-offline --omnilingual-asr-model=model.int4.onnx --tokens=tokens.txt --num-threads=4 ./test_wavs/de.wav ./test_wavs/en.wav ./test_wavs/es.wav ./test_wavs/fr.wav
OfflineRecognizerConfig(feat_config=FeatureExtractorConfig(sampling_rate=16000, feature_dim=80, low_freq=20, high_freq=-400, dither=0, normalize_samples=True, snip_edges=False), model_config=OfflineModelConfig(transducer=OfflineTransducerModelConfig(encoder_filename="", decoder_filename="", joiner_filename=""), paraformer=OfflineParaformerModelConfig(model=""), nemo_ctc=OfflineNemoEncDecCtcModelConfig(model=""), whisper=OfflineWhisperModelConfig(encoder="", decoder="", language="", task="transcribe", tail_paddings=-1), fire_red_asr=OfflineFireRedAsrModelConfig(encoder="", decoder=""), tdnn=OfflineTdnnModelConfig(model=""), zipformer_ctc=OfflineZipformerCtcModelConfig(model=""), wenet_ctc=OfflineWenetCtcModelConfig(model=""), sense_voice=OfflineSenseVoiceModelConfig(model="", language="auto", use_itn=False), moonshine=OfflineMoonshineModelConfig(preprocessor="", encoder="", uncached_decoder="", cached_decoder=""), dolphin=OfflineDolphinModelConfig(model=""), canary=OfflineCanaryModelConfig(encoder="", decoder="", src_lang="", tgt_lang="", use_pnc=True), omnilingual=OfflineOmnilingualAsrCtcModelConfig(model="model.int4.onnx"), funasr_nano=OfflineFunASRNanoModelConfig(encoder_adaptor="", llm="", embedding="", tokenizer="", system_prompt="You are a helpful assistant.", user_prompt="Þ»¡Úƒ│Þ¢¼ÕåÖ´╝Ü", max_new_tokens=512, temperature=1e-06, top_p=0.8, seed=42), medasr=OfflineMedAsrCtcModelConfig(model=""), telespeech_ctc="", tokens="tokens.txt", num_threads=4, debug=False, provider="cpu", model_type="", modeling_unit="cjkchar", bpe_vocab=""), lm_config=OfflineLMConfig(model="", scale=0.5, lodr_scale=0.01, lodr_fst="", lodr_backoff_id=-1), ctc_fst_decoder_config=OfflineCtcFstDecoderConfig(graph="", max_active=3000), decoding_method="greedy_search", max_active_paths=4, hotwords_file="", hotwords_score=1.5, blank_penalty=0, rule_fsts="", rule_fars="", hr=HomophoneReplacerConfig(lexicon="", rule_fsts=""))
Creating recognizer ...
recognizer created in 0.758 s
Started
D:\a\sherpa-onnx\sherpa-onnx\sherpa-onnx\csrc\offline-stream.cc:AcceptWaveformImpl:133 Creating a resampler:
in_sample_rate: 22050
output_sample_rate: 16000
D:\a\sherpa-onnx\sherpa-onnx\sherpa-onnx\csrc\offline-stream.cc:AcceptWaveformImpl:133 Creating a resampler:
in_sample_rate: 24000
output_sample_rate: 16000
D:\a\sherpa-onnx\sherpa-onnx\sherpa-onnx\csrc\offline-stream.cc:AcceptWaveformImpl:133 Creating a resampler:
in_sample_rate: 22050
output_sample_rate: 16000
D:\a\sherpa-onnx\sherpa-onnx\sherpa-onnx\csrc\offline-stream.cc:AcceptWaveformImpl:133 Creating a resampler:
in_sample_rate: 22050
output_sample_rate: 16000
Done!
./test_wavs/de.wav
{"lang": "", "emotion": "", "event": "", "text": "alles hat ein ende nur die worst hat zwei", "timestamps": [0.08, 0.10, 0.16, 0.20, 0.28, 0.36, 0.38, 0.42, 0.50, 0.58, 0.62, 0.66, 0.70, 0.80, 0.86, 0.94, 1.00, 1.08, 1.28, 1.34, 1.38, 1.44, 1.52, 1.54, 1.60, 1.64, 1.68, 1.72, 1.78, 1.82, 1.90, 1.96, 1.98, 2.02, 2.06, 2.12, 2.20, 2.26, 2.34, 2.44, 2.52], "durations": [], "tokens":["a", "l", "l", "e", "s", " ", "h", "a", "t", " ", "e", "i", "n", " ", "e", "n", "d", "e", " ", "n", "u", "r", " ", "d", "i", "e", " ", "w", "o", "r", "s", "t", " ", "h", "a", "t", " ", "z", "w", "e", "i"], "ys_log_probs": [], "words": []}
----
./test_wavs/en.wav
{"lang": "", "emotion": "", "event": "", "text": "ask not what your country can do for you ask what you can do for your country", "timestamps": [0.16, 0.28, 0.34, 0.46, 0.50, 0.54, 0.58, 0.62, 0.64, 0.66, 0.68, 0.72, 0.74, 0.76, 0.78, 0.82, 0.84, 0.88, 0.92, 0.96, 0.98, 1.02, 1.06, 1.12, 1.16, 1.22, 1.28, 1.30, 1.36, 1.40, 1.44, 1.48, 1.56, 1.60, 1.64, 1.66, 1.72, 1.74, 1.78, 1.80, 2.04, 2.16, 2.26, 2.30, 2.40, 2.42, 2.46, 2.48, 2.50, 2.52, 2.56, 2.58, 2.60, 2.66, 2.70, 2.72, 2.76, 2.82, 2.86, 2.90, 2.98, 3.00, 3.04, 3.06, 3.10, 3.12, 3.16, 3.18, 3.20, 3.24, 3.30, 3.34, 3.36, 3.40, 3.46, 3.54, 3.60], "durations": [], "tokens":["a", "s", "k", " ", "n", "o", "t", " ", "w", "h", "a", "t", " ", "y", "o", "u", "r", " ", "c", "o", "u", "n", "t", "r", "y", " ", "c", "a", "n", " ", "d", "o", " ", "f", "o", "r", " ", "y", "o", "u", " ", "a", "s", "k", " ", "w", "h", "a", "t", " ", "y", "o", "u", " ", "c", "a", "n", " ", "d", "o", " ", "f", "o", "r", " ", "y", "o", "u", "r", " ", "c", "o", "u", "n", "t", "r", "y"], "ys_log_probs": [], "words": []}
----
./test_wavs/es.wav
{"lang": "", "emotion": "", "event": "", "text": "no preguntes que puede hacer tu país por ti pregunta que puedes hacer te un puerto país", "timestamps": [0.12, 0.16, 0.22, 0.26, 0.30, 0.34, 0.40, 0.46, 0.50, 0.56, 0.62, 0.70, 0.78, 0.84, 0.86, 0.90, 0.98, 1.02, 1.04, 1.10, 1.16, 1.20, 1.26, 1.28, 1.32, 1.36, 1.40, 1.46, 1.52, 1.56, 1.60, 1.66, 1.72, 1.78, 1.88, 1.96, 2.04, 2.08, 2.14, 2.18, 2.26, 2.30, 2.38, 2.72, 2.78, 2.82, 2.86, 2.92, 2.96, 3.02, 3.08, 3.16, 3.20, 3.24, 3.26, 3.30, 3.36, 3.40, 3.44, 3.46, 3.52, 3.56, 3.64, 3.66, 3.70, 3.74, 3.78, 3.84, 3.90, 3.96, 4.00, 4.04, 4.08, 4.10, 4.14, 4.18, 4.22, 4.26, 4.30, 4.36, 4.46, 4.54, 4.56, 4.66, 4.76, 4.90, 5.02], "durations": [], "tokens":["n", "o", " ", "p", "r", "e", "g", "u", "n", "t", "e", "s", " ", "q", "u", "e", " ", "p", "u", "e", "d", "e", " ", "h", "a", "c", "e", "r", " ", "t", "u", " ", "p", "a", "í", "s", " ", "p", "o", "r", " ", "t", "i", " ", "p", "r", "e", "g", "u", "n", "t", "a", " ", "q", "u", "e", " ", "p", "u", "e", "d", "e", "s", " ", "h", "a", "c", "e", "r", " ", "t", "e", " ", "u", "n", " ", "p", "u", "e", "r", "t", "o", " ", "p", "a", "í", "s"], "ys_log_probs": [], "words": []}
----
./test_wavs/fr.wav
{"lang": "", "emotion": "", "event": "", "text": "ne vous demandez pas ce que votre pays peut faire pour vous demandez-vous plut├┤t ce que vous pouvez faire pour lui", "timestamps": [0.10, 0.14, 0.18, 0.22, 0.24, 0.26, 0.28, 0.32, 0.36, 0.38, 0.44, 0.46, 0.48, 0.52, 0.58, 0.64, 0.68, 0.70, 0.72, 0.76, 0.78, 0.80, 0.82, 0.84, 0.88, 0.90, 0.92, 0.96, 0.98, 1.02, 1.08, 1.10, 1.14, 1.18, 1.22, 1.26, 1.34, 1.36, 1.40, 1.44, 1.48, 1.50, 1.54, 1.60, 1.64, 1.74, 1.76, 1.80, 1.84, 1.90, 1.94, 1.98, 2.00, 2.06, 2.14, 2.18, 2.22, 2.24, 2.28, 2.60, 2.70, 2.72, 2.82, 2.84, 2.88, 2.92, 2.98, 3.02, 3.06, 3.08, 3.12, 3.14, 3.16, 3.22, 3.26, 3.30, 3.34, 3.40, 3.46, 3.50, 3.54, 3.58, 3.60, 3.64, 3.68, 3.70, 3.72, 3.76, 3.80, 3.82, 3.84, 3.86, 3.90, 3.94, 3.96, 3.98, 4.02, 4.06, 4.14, 4.22, 4.26, 4.34, 4.36, 4.40, 4.44, 4.50, 4.54, 4.56, 4.58, 4.62, 4.68, 4.72, 4.76, 4.80], "durations": [], "tokens":["n", "e", " ", "v", "o", "u", "s", " ", "d", "e", "m", "a", "n", "d", "e", "z", " ", "p", "a", "s", " ", "c", "e", " ", "q", "u", "e", " ", "v", "o", "t", "r", "e", " ", "p", "a", "y", "s", " ", "p", "e", "u", "t", " ", "f", "a", "i", "r", "e", " ", "p", "o", "u", "r", " ", "v", "o", "u", "s", " ", "d", "e", "m", "a", "n", "d", "e", "z", "-", "v", "o", "u", "s", " ", "p", "l", "u", "t", "├┤", "t", " ", "c", "e", " ", "q", "u", "e", " ", "v", "o", "u", "s", " ", "p", "o", "u", "v", "e", "z", " ", "f", "a", "i", "r", "e", " ", "p", "o", "u", "r", " ", "l", "u", "i"], "ys_log_probs": [], "words": []}
----
num threads: 4
decoding method: greedy_search
Elapsed seconds: 1.725 s
Real time factor (RTF): 1.725 / 16.895 = 0.102pythonpython offline-omnilingual-asr-ctc-v2-decode-files.py
---
test_wavs/en.wav
{"lang": "", "emotion": "", "event": "", "text": "ask not what your country can do for you ask what you can do for your country", "timestamps": [0.16, 0.28, 0.34, 0.46, 0.50, 0.54, 0.58, 0.62, 0.64, 0.66, 0.68, 0.72, 0.74, 0.76, 0.78, 0.82, 0.84, 0.88, 0.92, 0.96, 0.98, 1.02, 1.06, 1.12, 1.16, 1.22, 1.28, 1.30, 1.36, 1.40, 1.44, 1.48, 1.56, 1.60, 1.64, 1.66, 1.72, 1.76, 1.78, 1.80, 2.04, 2.16, 2.26, 2.30, 2.40, 2.42, 2.46, 2.48, 2.50, 2.52, 2.56, 2.58, 2.60, 2.66, 2.70, 2.72, 2.76, 2.82, 2.86, 2.90, 2.98, 3.00, 3.04, 3.06, 3.10, 3.12, 3.16, 3.18, 3.20, 3.24, 3.30, 3.34, 3.36, 3.40, 3.46, 3.54, 3.60], "durations": [], "tokens":["a", "s", "k", " ", "n", "o", "t", " ", "w", "h", "a", "t", " ", "y", "o", "u", "r", " ", "c", "o", "u", "n", "t", "r", "y", " ", "c", "a", "n", " ", "d", "o", " ", "f", "o", "r", " ", "y", "o", "u", " ", "a", "s", "k", " ", "w", "h", "a", "t", " ", "y", "o", "u", " ", "c", "a", "n", " ", "d", "o", " ", "f", "o", "r", " ", "y", "o", "u", "r", " ", "c", "o", "u", "n", "t", "r", "y"], "ys_log_probs": [], "words": []}
Elapsed seconds: 1.355
Audio duration in seconds: 3.845
RTF: 1.355/3.845 = 0.352
---
test_wavs/de.wav
{"lang": "", "emotion": "", "event": "", "text": "alles hat ein ende nur die worst hat zwei", "timestamps": [0.08, 0.10, 0.16, 0.20, 0.28, 0.34, 0.38, 0.42, 0.50, 0.58, 0.62, 0.66, 0.70, 0.80, 0.86, 0.94, 1.00, 1.08, 1.28, 1.34, 1.38, 1.44, 1.52, 1.54, 1.60, 1.64, 1.68, 1.72, 1.78, 1.82, 1.90, 1.96, 1.98, 2.02, 2.06, 2.12, 2.20, 2.26, 2.34, 2.44, 2.52], "durations": [], "tokens":["a", "l", "l", "e", "s", " ", "h", "a", "t", " ", "e", "i", "n", " ", "e", "n", "d", "e", " ", "n", "u", "r", " ", "d", "i", "e", " ", "w", "o", "r", "s", "t", " ", "h", "a", "t", " ", "z", "w", "e", "i"], "ys_log_probs": [], "words": []}
Elapsed seconds: 0.974
Audio duration in seconds: 2.752
RTF: 0.974/2.752 = 0.354
---
test_wavs/fr.wav
{"lang": "", "emotion": "", "event": "", "text": "ne vous demandez pas ce que votre pays peut faire pour vous demandez-vous plutôt ce que vous pouvez faire pour lui", "timestamps": [0.10, 0.14, 0.18, 0.22, 0.24, 0.26, 0.28, 0.32, 0.36, 0.38, 0.44, 0.46, 0.48, 0.52, 0.58, 0.64, 0.68, 0.70, 0.72, 0.76, 0.78, 0.80, 0.82, 0.84, 0.88, 0.90, 0.92, 0.96, 0.98, 1.02, 1.08, 1.10, 1.14, 1.18, 1.22, 1.26, 1.34, 1.36, 1.42, 1.44, 1.48, 1.50, 1.54, 1.60, 1.64, 1.74, 1.76, 1.80, 1.84, 1.90, 1.94, 1.98, 2.00, 2.06, 2.14, 2.18, 2.22, 2.24, 2.28, 2.60, 2.70, 2.72, 2.82, 2.84, 2.88, 2.92, 2.98, 3.02, 3.06, 3.08, 3.10, 3.14, 3.16, 3.22, 3.26, 3.30, 3.34, 3.40, 3.46, 3.50, 3.52, 3.58, 3.60, 3.64, 3.68, 3.70, 3.72, 3.76, 3.80, 3.82, 3.84, 3.86, 3.90, 3.94, 3.96, 3.98, 4.02, 4.06, 4.14, 4.22, 4.26, 4.34, 4.36, 4.40, 4.44, 4.50, 4.54, 4.56, 4.58, 4.62, 4.68, 4.72, 4.76, 4.80], "durations": [], "tokens":["n", "e", " ", "v", "o", "u", "s", " ", "d", "e", "m", "a", "n", "d", "e", "z", " ", "p", "a", "s", " ", "c", "e", " ", "q", "u", "e", " ", "v", "o", "t", "r", "e", " ", "p", "a", "y", "s", " ", "p", "e", "u", "t", " ", "f", "a", "i", "r", "e", " ", "p", "o", "u", "r", " ", "v", "o", "u", "s", " ", "d", "e", "m", "a", "n", "d", "e", "z", "-", "v", "o", "u", "s", " ", "p", "l", "u", "t", "ô", "t", " ", "c", "e", " ", "q", "u", "e", " ", "v", "o", "u", "s", " ", "p", "o", "u", "v", "e", "z", " ", "f", "a", "i", "r", "e", " ", "p", "o", "u", "r", " ", "l", "u", "i"], "ys_log_probs": [], "words": []}
---
test_wavs/es.wav
{"lang": "", "emotion": "", "event": "", "text": "no preguntes que puede hacer tu país por ti pregunta que puedes hacer te un puerto país", "timestamps": [0.12, 0.16, 0.22, 0.26, 0.30, 0.34, 0.40, 0.46, 0.50, 0.56, 0.62, 0.70, 0.78, 0.84, 0.86, 0.90, 0.98, 1.02, 1.04, 1.10, 1.16, 1.20, 1.26, 1.28, 1.32, 1.36, 1.40, 1.44, 1.52, 1.56, 1.60, 1.68, 1.72, 1.78, 1.88, 1.96, 2.04, 2.08, 2.14, 2.18, 2.26, 2.30, 2.38, 2.74, 2.78, 2.82, 2.88, 2.92, 2.96, 3.02, 3.08, 3.16, 3.20, 3.24, 3.26, 3.30, 3.36, 3.40, 3.44, 3.46, 3.52, 3.56, 3.64, 3.66, 3.70, 3.74, 3.78, 3.84, 3.90, 3.96, 4.00, 4.04, 4.08, 4.10, 4.14, 4.18, 4.22, 4.26, 4.30, 4.36, 4.46, 4.54, 4.56, 4.66, 4.76, 4.88, 5.02], "durations": [], "tokens":["n", "o", " ", "p", "r", "e", "g", "u", "n", "t", "e", "s", " ", "q", "u", "e", " ", "p", "u", "e", "d", "e", " ", "h", "a", "c", "e", "r", " ", "t", "u", " ", "p", "a", "í", "s", " ", "p", "o", "r", " ", "t", "i", " ", "p", "r", "e", "g", "u", "n", "t", "a", " ", "q", "u", "e", " ", "p", "u", "e", "d", "e", "s", " ", "h", "a", "c", "e", "r", " ", "t", "e", " ", "u", "n", " ", "p", "u", "e", "r", "t", "o", " ", "p", "a", "í", "s"], "ys_log_probs": [], "words": []}
Elapsed seconds: 3.608
Audio duration in seconds: 10.298
RTF: 3.608/10.298 = 0.350 |
Thank you for sharing the detailed information! I found your export script at Will try it with our other models. |
This pull request integrates support for the latest Meta Omnilingual ASR v2 models, enhancing the project's multilingual speech recognition capabilities without requiring modifications to the core C++ codebase. It introduces a dedicated Python example to facilitate usage and demonstrates the model's performance and compatibility.
Description
This model uses the existing CTC architecture, so no changes to the C++ core or CMakeLists are required. It is fully compatible with the current implementation. The only difference: going from Omnilingual v1 to Omnilingual v2 (December 2025 update).
Tested it on Python and with sherpa-onnx-offline, it works.
This is my first PR to the project, so please let me know if I've missed any specific formatting or project conventions!
Model Source
https://huggingface.co/Edison2ST/sherpa-onnx-omnilingual-asr-1600-languages-ctc-v2
4-bit Quantization (MatMulNBits) Investigation
I have also developed a quantization pipeline for these V2 models using
MatMulNBits(4-bit). Usedblock_size=32(due to the 128-bit registers and it aligns with the cache line and SIMD processing of 4-bit values) andis_symmetric=Trueto balance accuracy and performance on consumer CPUs/ARM, and the script bypasses anAssertionErrorin_infer_Reshape.I didn't include these models because I'm leading with the INT8 version to match the current project standards for accuracy (they're not on the v1 nor saw any Q4 model on releases). Would you be interested in these Q4 versions to further reduce the memory footprint for mobile/edge deployments?
The 300M model had low CER (1.77%) against clean LibriSpeech and 0.5x RTF on a Dimensity 6300. And more importantly, it doesn't break the C++ core at all. The 1B Q4 model is about 641 MB (ONNX file), so roughly 30% reduction.
Note: I measured it using a logit mask to constrain decoding to the English alphabet/space tokens, demonstrating high stability for English-specific tasks. But it seems to be good in other languages as well, I just haven't benchmarked it.
Bash
300M, int8, AMD Ryzen 7 7435HS. RTF 0.111.
Click to expand full bash logs (Note: show some encoding artifacts due to Windows console environment)
Summary by CodeRabbit