Add Rust API for Moonshine v2 (#3247)

This pull request integrates the latest Moonshine v2 model architecture into the sherpa-onnx Rust API, enhancing its capabilities for offline speech recognition. The changes include extending the model configuration to support Moonshine v2's merged decoder, providing a new example to showcase its usage, and updating related scripts and crate versions. This allows Rust developers to leverage the improved performance and features of Moonshine v2 models.
This commit is contained in:
Fangjun Kuang authored and GitHub committed 2026-02-28 15:51:28 +08:00
1 parent 13ab9970b5
commit a579b72bb6
9 files changed
+143 -10

No files matched your search

+2
View File
@@ -6,6 +6,8 @@ cd rust-api-examples
./run-version.sh
./run-moonshine-v2.sh
./run-fire-red-asr-ctc.sh
./run-silero-vad-remove-silence.sh
+5 -5
View File
@@ -553,7 +553,7 @@ dependencies = [
[[package]]
name = "rust-api-examples"
version = "0.1.8"
version = "0.1.9"
dependencies = [
"anyhow",
"clap",
@@ -621,9 +621,9 @@ dependencies = [
[[package]]
name = "sherpa-onnx"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5bc662a40347268c9088f8e52f47e39268c0c5da80c4d30c38e40464e6f6fef"
checksum = "2cb3cd528fb1ab43135fd7a68d45720910da8d6c036b97808125a456f1b86ffb"
dependencies = [
"serde",
"serde_json",
@@ -632,9 +632,9 @@ dependencies = [
[[package]]
name = "sherpa-onnx-sys"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5d16f9c5d442c96ca2fdfc5f5344fd58c93da7b9182707e5885f98a67f3682"
checksum = "50748be67c68ff33d4c21c934f8d7c7067bb4ca766583864f05b03219a55d4e9"
[[package]]
name = "slab"
+2 -2
View File
@@ -1,12 +1,12 @@
[package]
name = "rust-api-examples"
version = "0.1.8"
version = "0.1.9"
edition = "2021"
[dependencies]
anyhow = "1.0"
clap = { version = "4.5", features = ["derive"] }
sherpa-onnx = "0.1.8"
sherpa-onnx = "0.1.9"
# sherpa-onnx = { path = "../sherpa-onnx/rust/sherpa-onnx" }
cpal = { version = "0.16", optional = true } # cross-platform audio I/O
+106
View File
@@ -0,0 +1,106 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// This file demonstrates how to use a Moonshine v2 model with sherpa-onnx's Rust API
// for offline speech recognition.
//
// See ../README.md for how to run it.
use clap::Parser;
use sherpa_onnx::{OfflineRecognizer, OfflineRecognizerConfig, Wave};
use std::time::Instant;
/// Moonshine v2 offline example
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Path to WAV file
#[arg(long)]
wav: String,
/// Path to the encoder model
#[arg(long)]
encoder: String,
/// Path to the decoder model
#[arg(long)]
decoder: String,
/// Path to tokens file
#[arg(long)]
tokens: String,
/// Provider (default: cpu)
#[arg(long, default_value = "cpu")]
provider: String,
/// Enable debug logs
#[arg(long, default_value_t = false)]
debug: bool,
/// Number of threads
#[arg(long, default_value_t = 2)]
num_threads: i32,
}
fn main() {
let args = Args::parse();
let wave = Wave::read(&args.wav).expect("Failed to read WAV file");
let audio_duration = wave.samples().len() as f64 / wave.sample_rate() as f64;
let mut recognizer_config = OfflineRecognizerConfig::default();
recognizer_config.model_config.moonshine.encoder = Some(args.encoder.clone());
recognizer_config.model_config.moonshine.merged_decoder = Some(args.decoder.clone());
recognizer_config.model_config.tokens = Some(args.tokens.clone());
recognizer_config.model_config.provider = Some(args.provider.clone());
recognizer_config.model_config.debug = args.debug;
recognizer_config.model_config.num_threads = args.num_threads;
// Measure recognizer creation time
println!("Creating recognizer ...");
let start_creation = Instant::now();
let recognizer =
OfflineRecognizer::create(&recognizer_config).expect("Failed to create OfflineRecognizer");
let creation_elapsed = start_creation.elapsed().as_secs_f64();
println!("Recognizer created in {:.3} seconds.", creation_elapsed);
let stream = recognizer.create_stream();
// Measure recognition time
let start_recognition = Instant::now();
stream.accept_waveform(wave.sample_rate(), wave.samples());
recognizer.decode(&stream);
let recognition_elapsed = start_recognition.elapsed().as_secs_f64();
// Get recognition result
if let Some(result) = stream.get_result() {
println!("Decoded text: {}", result.text);
let total_time = creation_elapsed + recognition_elapsed;
let rtf = recognition_elapsed / audio_duration;
println!("\n=== Performance Summary ===");
println!("Audio duration : {:.3} seconds", audio_duration);
println!("Recognizer creation time: {:.3} seconds", creation_elapsed);
println!(
"Recognition time : {:.3} seconds",
recognition_elapsed
);
println!("Total elapsed time : {:.3} seconds", total_time);
// Detailed RTF computation log
println!(
"Real-Time Factor (RTF) : {:.3} (recognition_elapsed / audio_duration = {:.3} / {:.3})",
rtf, recognition_elapsed, audio_duration
);
println!(
"Number of threads : {}",
recognizer_config.model_config.num_threads
);
} else {
eprintln!("Failed to get recognition result");
}
}
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -ex
# see
# https://k2-fsa.github.io/sherpa/onnx/moonshine
if [ ! -f ./sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27/encoder_model.ort ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2
tar xvf sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2
rm sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2
fi
cargo run --example moonshine_v2 -- \
--wav ./sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27/test_wavs/0.wav \
--encoder ./sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27/encoder_model.ort \
--decoder ./sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27/decoder_model_merged.ort \
--tokens ./sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27/tokens.txt \
--num-threads 2
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "sherpa-onnx-sys"
version = "0.1.8"
version = "0.1.9"
edition = "2021"
description = "Raw FFI bindings to the sherpa-onnx C API"
license = "Apache-2.0"
@@ -60,6 +60,7 @@ pub struct OfflineMoonshineModelConfig {
pub encoder: *const c_char,
pub uncached_decoder: *const c_char,
pub cached_decoder: *const c_char,
pub merged_decoder: *const c_char,
}
#[repr(C)]
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "sherpa-onnx"
version = "0.1.8"
version = "0.1.9"
edition = "2021"
description = "Safe Rust wrapper for sherpa-onnx speech recognition toolkit"
license = "Apache-2.0"
@@ -20,6 +20,6 @@ include = [
]
[dependencies]
sherpa-onnx-sys = { path = "../sherpa-onnx-sys", version = "0.1.8" }
sherpa-onnx-sys = { path = "../sherpa-onnx-sys", version = "0.1.9" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
@@ -107,12 +107,18 @@ impl OfflineFireRedAsrModelConfig {
}
}
/// For Moonshine v1, you need 4 models:
/// - preprocessor, encoder, uncached_decoder, cached_decoder
///
/// For Moonshine v2, you need 2 models:
/// - encoder, merged_decoder
#[derive(Clone, Debug, Default)]
pub struct OfflineMoonshineModelConfig {
pub preprocessor: Option<String>,
pub encoder: Option<String>,
pub uncached_decoder: Option<String>,
pub cached_decoder: Option<String>,
pub merged_decoder: Option<String>,
}
impl OfflineMoonshineModelConfig {
@@ -122,6 +128,7 @@ impl OfflineMoonshineModelConfig {
encoder: to_c_ptr(&self.encoder, cstrings),
uncached_decoder: to_c_ptr(&self.uncached_decoder, cstrings),
cached_decoder: to_c_ptr(&self.cached_decoder, cstrings),
merged_decoder: to_c_ptr(&self.merged_decoder, cstrings),
}
}
}