Moonshine Base DE β€” ONNX

This is a browser-ready ONNX export of fidoriel/moonshine-base-de, a 62M-parameter German speech-recognition model. It is the base variant of Moonshine, the tiny ASR family from Useful Sensors that is purpose-built for on-device inference.

The intended consumer is πŸ€— Transformers.js v4 running in the browser via WebAssembly / WebGPU.

Files

onnx/encoder_model.onnx              (~77 MB)
onnx/decoder_model_onnx             (~210 MB, no-cache variant)
onnx/decoder_model_merged.onnx      (~211 MB, with past_key_values + use_cache_branch If-node)
onnx/decoder_with_past_model.onnx   (~200 MB, cache-only variant)
config.json
preprocessor_config.json
generation_config.json
tokenizer.json + tokenizer_config.json + special_tokens_map.json

The merged decoder has a use_cache_branch boolean input that switches between two subgraphs. We set this to False in the shipped decoder_model_merged.onnx (constant initializer), so the If-node always picks the no-cache branch. See Known issues below for why.

How it was built

Two scripts, both checked into this repo under scripts/:

  1. scripts/export_no_attn.py β€” runs optimum.exporters.onnx on the fidoriel/moonshine-base-de PyTorch snapshot, after monkey-patching MoonshineOnnxConfig.inputs to omit the attention_mask input. With single-batch inference and no padding, the attention mask is a no-op; dropping it simplifies the exported graph considerably.

  2. scripts/patch_decoder.py β€” post-processes the merged decoder: removes use_cache_branch from the inputs and replaces it with a constant initializer of value False. This forces the no-cache branch which is the only one that produces correct tokens in our pipeline.

The exact export command is:

python -m optimum.exporters.onnx \
  -m fidoriel/moonshine-base-de \
  --task automatic-speech-recognition-with-past \
  --library-name transformers \
  --device cpu --dtype fp32 --opset 14 \
  out_dir
python scripts/patch_decoder.py out_dir/decoder_model_merged.onnx

Reproducible via the included Dockerfile (python scripts/export_no_attn.py).

Browser demo

moonshine-de.html is a single-file demo that runs the model entirely in the browser using Transformers.js. To run it locally:

python scripts/server_threaded.py 8765
# open http://localhost:8765/moonshine-de.html

The threaded server is required because python -m http.server is single-threaded and Transformers.js opens several concurrent requests (e.g. for cache lookup + actual fetch) which trips ConnectionResetError.

Test audio: guten morgen, das ist ein test der lokalen spracherkennung mit moonshine Expected transcription: a clean German sentence starting with Guten Morgen, das ist ein Test der lokalen Spracherkennung ....

Known issues

1. use_cache_branch is hard-coded to False

There is a known bug in the optimum-exported If-node that branches between the cache and no-cache decoder subgraphs: the cache branch produces wrong tokens through the transformers.js + onnxruntime-web combination. Specifically, the encoder-attention MatMul fails with right operand cannot broadcast on dim 0 once the cached encoder K/V has seq_len > 0.

Until that bug is fixed upstream, we set use_cache_branch_const=False in the shipped decoder_model_merged.onnx. The downside is that every decoder step recomputes K/V from encoder_hidden_states from scratch β€” slow but correct. Typical decode time for a 5-second clip is around 6 seconds on a desktop CPU.

2. Custom JS decoder loop in the demo

moonshine-de.html does not use the standard transformers.js > pipeline > pipe(audio) path. The standard path goes through the same If-node cache branch described above and produces token 1768 'β€ž' instead of the expected token 402 'G'. The HTML therefore loads the encoder and decoder sessions directly and runs a manual greedy decode loop, feeding the full token history to the decoder on every step (no caching). This is the same workaround as the use_cache_branch_const hack, applied at runtime instead of in the model.

Planned v2

A clean v2 is in progress that addresses the items above:

  • Investigate the If-node bug (could be an optimum export issue with newer opset, or a transformers.js cache-construction issue).
  • Try the official onnx-community/moonshine-base-ONNX export path adapted for the DE weights to see if it sidesteps the bug.
  • If the bug is unfixable, fall back to a documented "no-cache" decoder loop in pure Transformers.js, without monkey-patching the ONNX initializer.

License

Apache 2.0, inherited from the upstream fidoriel/moonshine-base-de model and the Moonshine base architecture.

Citation

@software{moonshine_base_de_onnx,
  title = {Moonshine Base DE β€” ONNX},
  author = {Phreak87},
  year = {2026},
  url = {https://huggingface.co/Phreak87/moonshine-base-de-onnx},
}
Downloads last month
38
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support