Moonshine¶
from_weights(...).
Moonshine is built for live transcription and voice commands, where latency matters more than covering 99 languages. Two choices follow from that.
It eats the raw 16 kHz waveform: a three-layer Conv1D stem replaces the log-mel front end entirely, so there is no spectrogram step. And it has no fixed input window, unlike Whisper's mandatory 30-second pad. A one-second command costs one second of compute, which is the whole point: Whisper pads that same clip to 30 s and pays for the silence.
Positions come from GLM-style partial rotary embeddings, the decoder MLP is gated SiLU, and every LayerNorm is scale-only.
Paper: Moonshine: Speech Recognition for Live Transcription and Voice Commands
API¶
MoonshineSpeechToText¶
MoonshineSpeechToText(hidden_dim=288, encoder_num_layers=6,
decoder_num_layers=6, encoder_attention_heads=8,
decoder_attention_heads=8, encoder_num_kv_heads=None,
decoder_num_kv_heads=None, encoder_ffn_dim=1152,
decoder_ffn_dim=1152, vocab_size=32768,
max_position_embeddings=194, partial_rotary_factor=0.9,
rope_theta=10000.0, encoder_activation="gelu",
decoder_activation="silu", layer_norm_eps=1e-05,
name="MoonshineSpeechToText")
The conv stem, encoder, decoder, and tied LM head, plus a generate that runs the whole
pipeline. This is the class for speech to text.
Parameters
- hidden_dim (
int, optional, defaults to288): model width. Filled in byfrom_weightsfrom the variant config. - encoder_num_layers / decoder_num_layers (
int, optional): blocks per stack, the size lever between tiny and base. - encoder_attention_heads / decoder_attention_heads (
int, optional): attention heads per stack. - encoder_num_kv_heads / decoder_num_kv_heads (
int, optional): grouped-query KV heads.Nonemeans full multi-head. - encoder_ffn_dim / decoder_ffn_dim (
int, optional): MLP inner width. The decoder MLP is gated SiLU, so itsfc1projects to twice this. - vocab_size (
int, optional, defaults to32768): BPE vocabulary. - partial_rotary_factor (
float, optional, defaults to0.9): fraction of each head rotated by RoPE; the rest passes through. - rope_theta (
float, optional, defaults to10000.0): RoPE base frequency. - max_position_embeddings (
int, optional, defaults to194): size of the stored rotary tables. It does not cap the audio you can transcribe, see Audio Format. - encoder_activation / decoder_activation / layer_norm_eps: block-level knobs, set from the variant config.
- name (
str, optional, defaults to"MoonshineSpeechToText"): model name.
Call model({"input_values": ..., "decoder_input_ids": ...}) for a teacher-forced
forward pass. Returns a dict with logits (B, T, vocab_size) and
encoder_hidden_states (B, T', hidden_dim). For transcription use generate.
generate
- audio: a 1-D float32 waveform in
[-1, 1], or a list of them for a batch. - processor (
MoonshineProcessor): supplies the feature extractor and tokenizer. - max_new_tokens (
int, optional, defaults to200): decode budget. - sampling_rate (
int, optional, defaults to16000): sample rate ofaudio. - return_ids (
bool, optional, defaults toFalse): return token ids instead of strings.
Returns a list of strings, one per clip.
MoonshineModel¶
MoonshineModel(hidden_dim=288, encoder_num_layers=6, decoder_num_layers=6, ...,
name="MoonshineModel")
The encoder-decoder without the LM head, for features or a custom head. Same arguments as
MoonshineSpeechToText.
Preprocessing¶
MoonshineFeatureExtractor¶
There is no spectrogram here. The extractor only batches and pads waveforms; the conv stem does the rest.
Parameters
- sampling_rate (
int, optional, defaults to16000): rate the model was trained at. Resample your audio to match; this does not resample for you. - padding_value (
float, optional, defaults to0.0): value used to pad a batch to its longest clip.
feat(raw_audio) returns (B, num_samples) float32.
MoonshineProcessor¶
MoonshineProcessor(variant=None, tokenizer_file=None, sampling_rate=16000,
decoder_start_token_id=1, bos_token_id=1, eos_token_id=2,
unk_token_id=0, tokenizer=None, feature_extractor=None)
Bundles the feature extractor and tokenizer.
Call processor(audio=..., text=...). Returns input_values and, when text is
given, labels.
Prefer
MoonshineProcessor.from_weights(variant). It fetches the vocabulary that matches the checkpoint, which is whatgenerateexpects.
Model Variants¶
| Variant id | Params |
|---|---|
moonshine_tiny |
27 M |
moonshine_base |
61 M |
Both are English only. Even moonshine_base is smaller than whisper_base, and
because there is no 30-second pad the gap in wall-clock time on short clips is far larger
than the parameter counts suggest.
Basic Usage: Transcription¶
The sample below is a LibriSpeech clip, 5.12 s of 16 kHz mono, kept in the repo at
assets/speech_etchings.wav:
Its reference transcript is "AS FOR ETCHINGS THEY ARE OF TWO KINDS BRITISH AND FOREIGN".
import os
os.environ["KERAS_BACKEND"] = "torch" # or "jax" / "tensorflow"
import soundfile as sf
from kerasformers.models.moonshine import MoonshineProcessor, MoonshineSpeechToText
model = MoonshineSpeechToText.from_weights("moonshine_tiny")
processor = MoonshineProcessor.from_weights("moonshine_tiny")
audio, sr = sf.read("assets/speech_etchings.wav", dtype="float32") # 16 kHz mono
text = model.generate(audio, processor)
print(repr(text[0]))
Cased and punctuated, like Whisper and unlike Speech2Text. The reference
reads "they are of two kinds"; tiny hears "there are of", leaving an ungrammatical "are
of". moonshine_base cleans that to 'As for etchings, there are two kinds, British and
foreign.', which reads properly but still swaps "they" for "there".
Short clips are the point¶
Because nothing is padded to a fixed window, cost scales with the audio you actually have:
One second of audio is one second of encoder work; Whisper would pad the same clip to 30 s before the encoder ever ran. The transcript is short because one second of this sentence really is just "As for", which is the honest answer to what was said.
Batching¶
Pass a list of waveforms; the extractor pads them to the longest in the batch:
Padding is per batch, so grouping clips of similar length wastes less compute.
Audio Format¶
A 1-D float32 waveform in [-1, 1] at 16 kHz, fed to the model as-is.
| What it expects | |
|---|---|
generate / processor |
A 1-D float32 array (or a list of them). sampling_rate tells it what rate you are handing over; it does not resample. |
| Models | input_values, the raw (B, num_samples) waveform. No spectrogram step. |
import librosa
import soundfile as sf
audio, sr = sf.read("assets/speech_etchings.wav", dtype="float32")
if audio.ndim > 1:
audio = audio.mean(axis=1) # stereo to mono
if sr != 16000:
audio, sr = librosa.resample(audio, orig_sr=sr, target_sr=16000), 16000
There is no duration cap: the rotary tables are derived for whatever length the encoder
produces, so max_position_embeddings sizes the stored tables but does not limit the
audio. Attention cost still grows with duration, and Moonshine is aimed at utterances and
commands, so cut long recordings into segments rather than feeding a whole meeting.
Loading Fine-tuned and Community Weights¶
Any Hugging Face repo whose model_type is "moonshine" loads with the hf: prefix.
from kerasformers.models.moonshine import MoonshineProcessor, MoonshineSpeechToText
model = MoonshineSpeechToText.from_weights("hf:UsefulSensors/moonshine-tiny")
processor = MoonshineProcessor.from_weights("hf:UsefulSensors/moonshine-tiny")
# Architecture only, randomly initialized
model = MoonshineSpeechToText.from_weights("moonshine_tiny", load_weights=False)
See also Whisper for multilingual coverage, and Speech2Text for the fairseq lineage.