Skip to content
ModelListedExternal
Browse files

Moonshine Streaming Small Onnx

ONNX INT8 (dynamic quantization) export of UsefulSensors/moonshine-streaming-small, a fast streaming ASR model designed for real-time on-device speech recognition.

KindOtherBase modelUsefulSensors/moonshine-streaming-smallVersionRevision 36173c1Cgrade

Model source

Kind
Other
File size
71.7–142 MB
Base model
UsefulSensors/moonshine-streaming-small
Version
vRevision 36173c1
Updated
Oct 20, 2026
Downloads
5
Source
Hugging Face

Source description

Moonshine v2 Streaming Small — ONNX INT8

ONNX INT8 (dynamic quantization) export of UsefulSensors/moonshine-streaming-small, a fast streaming ASR model designed for real-time on-device speech recognition.

Based on the paper: Moonshine v2: Ergodic Streaming Encoder ASR

Model Details

Details
ArchitectureEncoder-decoder with sliding-window attention
Parameters~200M
QuantizationDynamic INT8 (weight-only, MatMul/Gemm ops)
Total size341 MB (vs ~1.3 GB FP32)
Input16kHz mono float32 audio + attention_mask
OutputText tokens (BPE, 32768 vocab)

ONNX Files

FileSizeDescription
encoder_model_int8.onnx72 MBAudio → encoder hidden states (620-dim)
decoder_model_int8.onnx142 MBFirst decode step, no KV cache → logits + KV
decoder_with_past_model_int8.onnx127 MBSubsequent steps with KV cache → logits + updated KV

Usage

Requirements

pip install onnxruntime numpy tokenizers
# For real-time mic input:
pip install sounddevice

Basic Inference

import numpy as np
import onnxruntime as ort
from tokenizers import Tokenizer

MODEL_DIR = "moonshine-streaming-onnx"
BOS, EOS = 1, 2

# Load models
opts = ort.SessionOptions()
opts.intra_op_num_threads = 4
providers = ["CPUExecutionProvider"]

encoder = ort.InferenceSession(f"{MODEL_DIR}/encoder_model_int8.onnx", opts, providers=providers)
decoder = ort.InferenceSession(f"{MODEL_DIR}/decoder_model_int8.onnx", opts, providers=providers)
decoder_past = ort.InferenceSession(f"{MODEL_DIR}/decoder_with_past_model_int8.onnx", opts, providers=providers)
tokenizer = Tokenizer.from_file(f"{MODEL_DIR}/tokenizer.json")

# Prepare audio (16kHz float32, padded to multiple of 80)
audio = np.random.randn(16000).astype(np.float32)  # replace with real audio
remainder = len(audio) % 80
if remainder:
    audio = np.pad(audio, (0, 80 - remainder))

audio_input = audio[np.newaxis, :]
attention_mask = np.ones_like(audio_input, dtype=np.int64)

# Encode
(enc_out,) = encoder.run(None, {
    "input_values": audio_input,
    "attention_mask": attention_mask,
})

# Decode first token
outs = decoder.run(None, {
    "decoder_input_ids": np.array([[BOS]], dtype=np.int64),
    "encoder_hidden_states": enc_out,
})
logits, past_kvs = outs[0], outs[1:]
token = int(np.argmax(logits[0, -1, :]))

# Build KV cache mapping (decoder outputs → decoder_with_past inputs)
dec_out_names = [o.name for o in decoder.get_outputs()][1:]
past_in_names = {i.name for i in decoder_past.get_inputs()} - {"decoder_input_ids", "encoder_hidden_states"}

kv_dict = {}
for name, tensor in zip(dec_out_names, past_kvs):
    mapped = name.replace("present_", "past_", 1)
    if mapped in past_in_names:
        kv_dict[mapped] = tensor
    elif name + "_orig" in past_in_names:
        kv_dict[name + "_orig"] = tensor

# Autoregressive decode loop
tokens = [token]
past_out_names = [o.name for o in decoder_past.get_outputs()][1:]

for _ in range(255):
    if token == EOS:
        break
    inputs = {"decoder_input_ids": np.array([[token]], dtype=np.int64), "encoder_hidden_states": enc_out}
    inputs.update(kv_dict)
    outs = decoder_past.run(None, inputs)
    token = int(np.argmax(outs[0][0, -1, :]))
    tokens.append(token)
    # Update KV cache
    kv_dict = {}
    for name, tensor in zip(past_out_names, outs[1:]):
        mapped = name.replace("present_", "past_", 1)
        if mapped in past_in_names:
            kv_dict[mapped] = tensor
        elif name + "_orig" in past_in_names:
            kv_dict[name + "_orig"] = tensor

text = tokenizer.decode(tokens)
print(text)

Important: Attention Mask Required

The encoder requires an attention_mask input (all ones for unpadded audio). Without it, the sliding-window bidirectional attention mask is not constructed and the model produces no transcription.

Performance

Tested on MacBook Air M3, ONNX Runtime CPU:

MetricValue
Encode latency~350-420ms (for ~6s audio)
Decode latency~35-40ms/token
Real-time factor0.22x (4.5x faster than real-time)
Peak RAM~1.4 GB

How It Was Exported

  1. Encoder: Exported with torch.onnx.export(dynamo=True) to handle the vmap-based sliding-window attention masking
  2. Decoders: Exported with torch.onnx.export(dynamo=True) with symbolic dynamic shapes for batch, sequence, and KV cache dimensions
  3. Quantization: onnxruntime.quantization.quantize_dynamic targeting MatMul/Gemm ops, symmetric int8 weights

Key patches applied during export:

  • DynamicLayer.update: Skip torch.cat on first call (empty tensor → rank mismatch fix)
  • Encoder wrapper passes attention_mask through to the encoder

Limitations

  • English only
  • Optimized for short utterances (streaming chunks of 1-5 seconds)
  • INT8 quantization has negligible accuracy impact for greedy decoding; may affect beam search/sampling slightly

Citation

@article{kudlur2025moonshine,
  title={Moonshine v2: Ergodic Streaming Encoder ASR},
  author={Kudlur, Manjunath and King, Evan and Wang, James and Warden, Pete},
  journal={arXiv preprint arXiv:2602.12241},
  year={2025}
}

License

MIT (same as base model)

Sources

1 source
Hugging FacePrimaryVersion 36173c1e9d1643faeff38af056c0120ac60e2e85
huggingface.co/Mer0vin8ian/moonshine-streaming-small-onnx/tree/36173c1e9d1643faeff38af056c0120ac60e2e85

Verified Aug 30

Download files

Model artifacts

3 artifacts

decoder_model_int8.onnx

onnx · 142 MB · SHA-256 8c1a86e1b305…e158 · Hugging Face

Download file

decoder_with_past_model_int8.onnx

onnx · 127 MB · SHA-256 e9bfbc4f2b34…0565 · Hugging Face

Download file

encoder_model_int8.onnx

onnx · 71.7 MB · SHA-256 9bb6562667da…a509 · Hugging Face

Download file

Source excerpts

2 excerpts

--- license: mit base_model: UsefulSensors/moonshine-streaming-small tags: - onnx - int8 - quantized - speech-recognition - asr - streaming - moonshine library_name: onnxruntime pipeline_tag: automatic-speech-recognition language: - en --- # Moonshine v2 Streaming Small — ONNX INT8 ONNX INT8 (dynamic quantization) export of [UsefulSensors/moonshine-streaming-small](https://huggingface.co/UsefulSensors/moonshine-streaming-small), a fast streaming ASR model designed for real-time on-device speech recognition. Based on the paper: [Moonshine v2: Ergodic Streaming Encoder ASR](https://arxiv.org/abs/2602.12241) ## Model Details | | Details | |---|---| | **Architecture** | Encoder-decoder with sliding-window attention | | **Parameters** | ~200M | | **Quantization** | Dynamic INT8 (weight-only, MatMul/Gemm ops) | | **Total size** | **341 MB** (vs ~1.3 GB FP32) | | **Input** | 16kHz mono float32 audio + attention_mask | | **Output** | Text tokens (BPE, 32768 vocab) | ### ONNX Files | File | Size | Description | |------|------|-------------| | `encoder_model_int8.onnx` | 72 MB | Audio → encoder hidden states (620-dim) | | `decoder_model_int8.onnx` | 142 MB | First decode step, no KV cache → logits + KV | | `decoder_with_past_model_int8.onnx` | 127 MB | Subsequent steps with KV cache → logits + updated KV | ## Usage ### Requirements ```bash pip install onnxruntime numpy tokenizers # For real-time mic input: pip install sounddevice ``` ### Basic Inference ```python import numpy as np import onnxruntime as ort from tokenizers import Tokenizer MODEL_DIR = "moonshine-streaming-onnx" BOS, EOS = 1, 2 # Load models opts = ort.SessionOptions() opts.intra_op_num_threads = 4 providers = ["CPUExecutionProvider"] encoder = ort.InferenceSession(f"{MODEL_DIR}/encoder_model_int8.onnx", opts, providers=providers) decoder = ort.InferenceSession(f"{MODEL_DIR}/decoder_model_int8.onnx", opts, providers=providers) decoder_past = ort.InferenceSession(f"{MODEL_DIR}/decoder_with_past_model_int8.onnx", opts, providers=providers) tokenizer = Tokenizer.from_file(f"{MODEL_DIR}/tokenizer.json") # Prepare audio (16kHz float32, padded to multiple of 80) audio = np.random.randn(16000).astype(np.float32) # replace with real audio remainder = len(audio) % 80 if remainder: audio = np.pad(audio, (0, 80 - remainder)) audio_input = audio[np.newaxis, :] attention_mask = np.ones_...

Source context: 5 downloads · 1 likes · Pipeline automatic-speech-recognition · Library onnxruntime · Repo Mer0vin8ian/moonshine-streaming-small-onnx