Moonshine EOU Detector
End-of-utterance (EOU) confirmation gate for streaming voice pipelines β runs alongside Silero VAD to decide when a caller's turn is truly finished.
All-ONNX inference, no PyTorch at runtime.
Files
| File | Size | Description |
|---|---|---|
moonshine_enc_fp32.onnx |
0.9 MB | Moonshine-tiny encoder graph (7.7M params, 288D hidden) |
moonshine_enc_fp32.onnx.data |
30 MB | Encoder weights (external data) |
eou_head.onnx |
2.2 MB | Trained binary classification head |
silero_vad.onnx |
2.2 MB | Silero VAD v5 (bundled for convenience) |
Usage
import numpy as np
import onnxruntime as ort
# Load sessions
enc = ort.InferenceSession("moonshine_enc_fp32.onnx")
head = ort.InferenceSession("eou_head.onnx")
# Predict P(turn complete) from 16 kHz float32 audio
wav = np.zeros(16000, dtype=np.float32).reshape(1, -1) # 1 second
features = enc.run(None, {"input_values": wav})[0]
frame_len = np.array([features.shape[1]], dtype=np.int64)
logit = head.run(None, {"encoder_features": features, "frame_lengths": frame_len})[0]
prob = float(1.0 / (1.0 + np.exp(-logit[0])))
EOU Schedule
The model is consulted at up to 4 checkpoints during caller silence, each with a relaxing gate:
| Silence | Threshold | Rationale |
|---|---|---|
| 96 ms | 0.85 | Very confident β fast commit |
| 192 ms | 0.65 | Moderate confidence |
| 320 ms | model default (0.50) | Calibrated threshold |
| 480 ms | 0.40 | Lenient β long pause likely means done |
| 576 ms | (timeout) | Hard backstop, commit regardless |
Silero VAD ONNX β Context Prefix
The bundled silero_vad.onnx requires 64 samples of context prepended to each 512-sample window (576 total input). Without this, the model produces near-zero probabilities and speech detection silently fails.
The official Silero OnnxWrapper and PyTorch model handle this internally. Custom ONNX code must replicate it:
_CONTEXT_SIZE = 64
context = np.zeros(64, dtype=np.float32)
def process_chunk(chunk_512):
x = np.concatenate([context, chunk_512.astype(np.float32)])
context[:] = x[-_CONTEXT_SIZE:]
out, state = session.run(None, {
"input": x.reshape(1, -1),
"state": state,
"sr": np.array(16000, dtype=np.int64),
})
return float(out[0, 0])
Architecture
- Encoder: Moonshine-tiny (7.7M params), passthrough feature extractor β feed raw 16 kHz audio directly as
(1, seq)float32 - Head: Linear projection from pooled encoder features to binary logit, trained on turn-boundary data
- VAD: Silero v5 ONNX with per-session state
(2, 1, 128)(~1 KB) + 64-sample context buffer
Inference Providers NEW
This model isn't deployed by any Inference Provider. π Ask for provider support