Dynamic INT8 quantized ONNX export of
UsefulSensors/moonshine-streaming-medium for fast CPU inference with ONNX Runtime.
1import numpy as np
2import onnxruntime as ort
3from tokenizers import Tokenizer
4
5MODEL_DIR = "moonshine-streaming-medium-onnx"
6
7# Load models
8opts = ort.SessionOptions()
9opts.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
10providers = ["CPUExecutionProvider"]
11
12encoder = ort.InferenceSession(f"{MODEL_DIR}/encoder_model_int8.onnx", opts, providers=providers)
13decoder = ort.InferenceSession(f"{MODEL_DIR}/decoder_model_int8.onnx", opts, providers=providers)
14decoder_past = ort.InferenceSession(f"{MODEL_DIR}/decoder_with_past_model_int8.onnx", opts, providers=providers)
15tokenizer = Tokenizer.from_file(f"{MODEL_DIR}/tokenizer.json")
16
17# Encode audio (16kHz float32)
18audio = np.random.randn(1, 16000).astype(np.float32) # 1 second
19mask = np.ones((1, 16000), dtype=np.int64)
20enc_out = encoder.run(None, {"input_values": audio, "attention_mask": mask})[0]
21
22# First decode step (BOS token = 1)
23bos = np.array([[1]], dtype=np.int64)
24first_out = decoder.run(None, {"decoder_input_ids": bos, "encoder_hidden_states": enc_out})
25logits = first_out[0]
26token_id = int(np.argmax(logits[0, -1, :]))
27
28# Build KV cache mapping for subsequent steps
29dec_out_names = [o.name for o in decoder.get_outputs()][1:]
30dec_past_in_names = {i.name for i in decoder_past.get_inputs() if i.name not in ("decoder_input_ids", "encoder_hidden_states")}
31kv = {}
32for name, tensor in zip(dec_out_names, first_out[1:]):
33 past_name = name.replace("present_", "past_", 1)
34 mapped = past_name if past_name in dec_past_in_names else (name + "_orig" if name + "_orig" in dec_past_in_names else name)
35 kv[mapped] = tensor
36
37# Autoregressive decoding
38tokens = [token_id]
39EOS = 2
40while token_id != EOS and len(tokens) < 256:
41 inputs = {"decoder_input_ids": np.array([[token_id]], dtype=np.int64), "encoder_hidden_states": enc_out}
42 inputs.update(kv)
43 past_out = decoder_past.run(None, inputs)
44 token_id = int(np.argmax(past_out[0][0, -1, :]))
45 tokens.append(token_id)
46 # Update KV cache
47 past_out_names = [o.name for o in decoder_past.get_outputs()][1:]
48 kv = {}
49 for name, tensor in zip(past_out_names, past_out[1:]):
50 past_name = name.replace("present_", "past_", 1)
51 mapped = past_name if past_name in dec_past_in_names else (name + "_orig" if name + "_orig" in dec_past_in_names else name)
52 kv[mapped] = tensor
53
54text = tokenizer.decode(tokens)
55print(text)
The companion CLI tool provides real-time streaming ASR with voice activity detection:
1pip install sounddevice
2python inference_moonshine.py --model-dir moonshine_streaming_medium
-
Encoder — Processes raw audio with causal stride-2 convolutions and sliding-window attention. Outputs 768-dim hidden states at 50Hz (one frame per 20ms of audio).
-
Decoder (first step) — Takes BOS token + encoder states, produces first token logits and initializes 56 KV cache tensors (14 layers × 2 attention types × key+value).
-
Decoder with past — Takes previous token + encoder states + KV cache, produces next token logits and updated cache. Self-attention KV grows each step; cross-attention KV stays constant.
1pip install "transformers>=5.2.0" "huggingface_hub>=0.23" torch onnx onnxruntime
2python export_moonshine_streaming_medium.py
This model inherits the
MIT License from the original Moonshine model by Useful Sensors.
1@article{jeffries2024moonshine,
2 title={Moonshine: Speech Recognition for Live Transcription and Voice Commands},
3 author={Jeffries, Nat and Silent, Evan},
4 journal={arXiv preprint arXiv:2410.15608},
5 year={2024}
6}