Add model card, licenses, configs, and ONNX Runtime helper
Browse files- LICENSE +10 -0
- NOTICE.md +31 -0
- README.md +117 -0
- UPSTREAM_MODEL_CARD.md +170 -0
- onnx/fp16/config.json +12 -0
- onnx/w4a16/config.json +19 -0
- runtime/LICENSE +21 -0
- runtime/muscriptor_onnx/__init__.py +6 -0
- runtime/muscriptor_onnx/audio.py +74 -0
- runtime/requirements.txt +4 -0
- runtime/test_onnxruntime.py +153 -0
LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Creative Commons Attribution-NonCommercial 4.0 International
|
| 2 |
+
|
| 3 |
+
The checkpoint weights in this repository are licensed by their original
|
| 4 |
+
rightsholders under CC BY-NC 4.0. The complete legal code is available at:
|
| 5 |
+
|
| 6 |
+
https://creativecommons.org/licenses/by-nc/4.0/legalcode.en
|
| 7 |
+
|
| 8 |
+
This licence applies to the mirrored checkpoint weights and upstream model
|
| 9 |
+
materials. It does not automatically apply to MIDI-ME source code or
|
| 10 |
+
LUSKII-VSTS branding.
|
NOTICE.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MuScriptor ONNX derivative attribution notice
|
| 2 |
+
|
| 3 |
+
This repository contains derivative ONNX Runtime exports of MuScriptor Medium.
|
| 4 |
+
|
| 5 |
+
- Original model: https://huggingface.co/MuScriptor/muscriptor-medium
|
| 6 |
+
- Original project: https://github.com/muscriptor/muscriptor
|
| 7 |
+
- Original developers: Mirelo × Kyutai
|
| 8 |
+
- Original authors: Alexandre Rouard, Michael Krause, Axel Roebel,
|
| 9 |
+
Carl-Johann Simon-Gabriel, and Alexandre Défossez
|
| 10 |
+
- ONNX conversion and quantization repository: happyme531/muscriptor-medium-onnx
|
| 11 |
+
|
| 12 |
+
Changes from the original checkpoint:
|
| 13 |
+
|
| 14 |
+
1. The model was exported to two ONNX graphs: a conditioner and a decoder with
|
| 15 |
+
explicit autoregressive KV-cache inputs and outputs.
|
| 16 |
+
2. An FP16 ONNX variant was produced.
|
| 17 |
+
3. A W4A16 variant was produced with 96 Transformer-backbone MatMul weights
|
| 18 |
+
quantized to UINT4 using HQQ and ONNX Runtime MatMulNBits (block size 128).
|
| 19 |
+
4. Embeddings, the conditioner, and the LM head remain FP16 in the W4A16
|
| 20 |
+
variant. Log-mel audio preprocessing remains outside the ONNX graphs.
|
| 21 |
+
|
| 22 |
+
The original and derivative model weights are licensed under CC BY-NC 4.0.
|
| 23 |
+
Users must also comply with the original model card's supplemental conditions,
|
| 24 |
+
including holding all necessary rights to input audio and avoiding illegal or
|
| 25 |
+
unauthorized use. See `UPSTREAM_MODEL_CARD.md` for the complete upstream terms,
|
| 26 |
+
intended uses, limitations, citation, and disclaimers.
|
| 27 |
+
|
| 28 |
+
This derivative repository is not affiliated with or endorsed by Mirelo,
|
| 29 |
+
Kyutai, or the original authors. The models and generated content are provided
|
| 30 |
+
as-is without warranty.
|
| 31 |
+
|
README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-nc-4.0
|
| 3 |
+
library_name: onnxruntime
|
| 4 |
+
base_model: MuScriptor/muscriptor-medium
|
| 5 |
+
tags:
|
| 6 |
+
- muscriptor
|
| 7 |
+
- music-transcription
|
| 8 |
+
- automatic-music-transcription
|
| 9 |
+
- audio-to-midi
|
| 10 |
+
- onnx
|
| 11 |
+
- onnxruntime
|
| 12 |
+
- int4
|
| 13 |
+
- hqq
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# MuScriptor Medium — ONNX Runtime FP16 and W4A16
|
| 17 |
+
|
| 18 |
+
This is an **unofficial derivative ONNX conversion** of
|
| 19 |
+
[`MuScriptor/muscriptor-medium`](https://huggingface.co/MuScriptor/muscriptor-medium),
|
| 20 |
+
developed by Mirelo × Kyutai. It contains an FP16 export and a smaller W4A16
|
| 21 |
+
export for ONNX Runtime. This repository is not affiliated with or endorsed by
|
| 22 |
+
the MuScriptor authors.
|
| 23 |
+
|
| 24 |
+
The conversion uses upstream revision
|
| 25 |
+
`f32236969308476e01fd3aae67357de5feb05a2d`. The source checkpoint used for the
|
| 26 |
+
export has SHA-256
|
| 27 |
+
`ac80adbdf85d87231735fd948af7013441c0afced316c4e9067fd5d8a7fb97ec`.
|
| 28 |
+
|
| 29 |
+
## Variants
|
| 30 |
+
|
| 31 |
+
| Directory | Transformer backbone | Embeddings | LM head | Activations | Weight files |
|
| 32 |
+
|---|---:|---:|---:|---:|---:|
|
| 33 |
+
| `onnx/fp16` | FP16 | FP16 | FP16 | FP16 | 613,150,680 bytes |
|
| 34 |
+
| `onnx/w4a16` | HQQ UINT4 | FP16 | FP16 | FP16 | 169,649,866 bytes |
|
| 35 |
+
|
| 36 |
+
The W4A16 graph quantizes only the 96 constant-weight MatMul operations in the
|
| 37 |
+
24-layer Transformer backbone. It uses ONNX Runtime `MatMulNBits`, HQQ
|
| 38 |
+
quantization, and block size 128. The token embedding, all other embedding
|
| 39 |
+
tables, conditioner, and LM head deliberately remain FP16.
|
| 40 |
+
|
| 41 |
+
`decoder.onnx.data` is external tensor data and **must remain next to**
|
| 42 |
+
`onnx/w4a16/decoder.onnx`.
|
| 43 |
+
|
| 44 |
+
## ONNX interface
|
| 45 |
+
|
| 46 |
+
The conditioner consumes a 5-second, 16 kHz mono log-mel spectrogram plus
|
| 47 |
+
instrument and dataset IDs. The decoder exposes an autoregressive interface
|
| 48 |
+
with explicit `past_key`/`past_value` inputs and `present_key`/`present_value`
|
| 49 |
+
outputs. Log-mel extraction is intentionally outside the ONNX graph.
|
| 50 |
+
|
| 51 |
+
## Quick smoke test
|
| 52 |
+
|
| 53 |
+
Download this public repository:
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
hf download happyme531/muscriptor-medium-onnx --local-dir muscriptor-medium-onnx
|
| 57 |
+
cd muscriptor-medium-onnx
|
| 58 |
+
uv venv
|
| 59 |
+
source .venv/bin/activate
|
| 60 |
+
uv pip install -r runtime/requirements.txt
|
| 61 |
+
python runtime/test_onnxruntime.py --model-dir onnx/w4a16 --provider cpu
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
Use `--audio path/to/audio.wav` to replace the generated 440 Hz test tone. For
|
| 65 |
+
CUDA, install a matching `onnxruntime-gpu` package instead of `onnxruntime`,
|
| 66 |
+
and pass `--provider cuda`. PyTorch is optional; if installed, the helper
|
| 67 |
+
imports it first so its CUDA/cuDNN libraries can be preloaded.
|
| 68 |
+
|
| 69 |
+
The included script validates conditioner/decoder execution and greedy token
|
| 70 |
+
generation. It is a low-level ONNX Runtime smoke test, not a complete
|
| 71 |
+
audio-to-MIDI application.
|
| 72 |
+
|
| 73 |
+
## Validation
|
| 74 |
+
|
| 75 |
+
Both models pass `onnx.checker.check_model`. The FP16 and W4A16 exports were
|
| 76 |
+
executed with ONNX Runtime on CUDA and x86-64 CPU; on a real audio sample their
|
| 77 |
+
first eight greedy tokens matched. ARM64 execution was not validated and
|
| 78 |
+
depends on whether the target ONNX Runtime build/provider implements the
|
| 79 |
+
`MatMulNBits` graph used by the W4A16 decoder.
|
| 80 |
+
|
| 81 |
+
| File | SHA-256 |
|
| 82 |
+
|---|---|
|
| 83 |
+
| `onnx/fp16/conditioner.onnx` | `cc94f58ba8023f339b4b8eba06a3f3e7623079c8234fa1be075612ee96efcf81` |
|
| 84 |
+
| `onnx/fp16/decoder.onnx` | `6832149920078feb4562c1c211a0dcac52a4ea10d7b715459e2c6fd0aee83b25` |
|
| 85 |
+
| `onnx/w4a16/conditioner.onnx` | `cc94f58ba8023f339b4b8eba06a3f3e7623079c8234fa1be075612ee96efcf81` |
|
| 86 |
+
| `onnx/w4a16/decoder.onnx` | `2d3b8a35c353b581982a1567eea1629ce70d4b899de1d020f4fa9cb85757943e` |
|
| 87 |
+
| `onnx/w4a16/decoder.onnx.data` | `8d92ff62bed61814593222a9fd8fcb731f0aa83cbd56591c56cc845b6e16b61d` |
|
| 88 |
+
|
| 89 |
+
## License, attribution, and conditions
|
| 90 |
+
|
| 91 |
+
The model weights and these derivative ONNX weights are distributed under
|
| 92 |
+
[Creative Commons Attribution-NonCommercial 4.0 International](LICENSE).
|
| 93 |
+
Commercial use is not permitted under that license. You must also comply with
|
| 94 |
+
the upstream supplemental conditions summarized below, including
|
| 95 |
+
having all necessary rights to any audio you transcribe and complying with all
|
| 96 |
+
applicable laws.
|
| 97 |
+
|
| 98 |
+
> **Summary of upstream supplemental conditions of use:** MuScriptor and any generated
|
| 99 |
+
> content by MuScriptor are provided as is without any warranty of any kind,
|
| 100 |
+
> including but not limited to any warranty of non-infringement. Use of
|
| 101 |
+
> MuScriptor and its output must comply with all applicable laws and must not
|
| 102 |
+
> result in, involve, or facilitate any illegal or unauthorized activity.
|
| 103 |
+
> Prohibited uses include, without limitation, inputting music files and
|
| 104 |
+
> transcribing them to MIDI/music sheet without having all the necessary
|
| 105 |
+
> rights, including intellectual property rights, under applicable laws.
|
| 106 |
+
> Accordingly, users undertake and warrant that they have all necessary rights,
|
| 107 |
+
> including intellectual property rights, in connection with their use of
|
| 108 |
+
> MuScriptor and its output. Mirelo and Kyutai disclaim liability for
|
| 109 |
+
> non-compliant use; users shall indemnify, defend, and hold them harmless from
|
| 110 |
+
> claims, damages, losses, liabilities, and expenses arising from failure to
|
| 111 |
+
> comply with CC BY-NC 4.0 and/or these specific conditions. The exact original
|
| 112 |
+
> wording is preserved in `UPSTREAM_MODEL_CARD.md`.
|
| 113 |
+
|
| 114 |
+
See [NOTICE.md](NOTICE.md) for attribution and
|
| 115 |
+
[UPSTREAM_MODEL_CARD.md](UPSTREAM_MODEL_CARD.md) for the original model card,
|
| 116 |
+
intended uses, limitations, supplemental conditions, author list, and citation.
|
| 117 |
+
The small runtime helpers are covered by [their MIT license](runtime/LICENSE).
|
UPSTREAM_MODEL_CARD.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-nc-4.0
|
| 3 |
+
library_name: muscriptor
|
| 4 |
+
extra_gated_prompt: "MuScriptor is the result of a research collaboration between Mirelo and Kyutai whose purpose is to transcribe audio to MIDI/music sheet. It is provided primarily for research purposes under the CC BY-NC 4.0 licence supplemented by the below specific conditions of use.\nSpecific conditions of use: MuScriptor and any generated content by MuScriptor are provided as is without any warranty of any kind, including but not limited to any warranty of non-infringement. Use of MuScriptor and its output must comply with all applicable laws and must not result in, involve, or facilitate any illegal or unauthorized activity. Prohibited uses include, without limitation, inputting music files and transcribing them to MIDI/music sheet without having all the necessary rights, including intellectual property rights, under applicable laws. Accordingly, users of MuScriptor undertake and warrant to have all the necessary rights, including intellectual property rights, in connection with their use of MuScriptor and its output. We disclaim all liability for any non-compliant use and users of MuScriptor shall indemnify, defend, and hold harmless Mirelo and Kyutai from and against any and all claims, damages, losses, liabilities, and expenses (including reasonable attorneys' fees) incurred by Mirelo and/or Kyutai arising out of or resulting from their failure to comply with the terms of the CC BY-NC 4.0 licence and/or these specific conditions of use."
|
| 5 |
+
extra_gated_fields:
|
| 6 |
+
Company or university if applicable: text
|
| 7 |
+
I am a:
|
| 8 |
+
type: select
|
| 9 |
+
options:
|
| 10 |
+
- Musician
|
| 11 |
+
- AI Researcher
|
| 12 |
+
- Other
|
| 13 |
+
tags:
|
| 14 |
+
- music
|
| 15 |
+
- music-transcription
|
| 16 |
+
- automatic-music-transcription
|
| 17 |
+
- amt
|
| 18 |
+
- audio-to-midi
|
| 19 |
+
- midi
|
| 20 |
+
- music-information-retrieval
|
| 21 |
+
- transformer
|
| 22 |
+
- pytorch
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
# MuScriptor — medium (≈300M)
|
| 26 |
+
|
| 27 |
+
**MuScriptor** is an open-weight model for **general-purpose, multi-instrument automatic music transcription (AMT)**: it converts a music recording (any genre, multiple simultaneous instruments) into a stream of notes played. This repository hosts the **medium** variant (≈300M parameters), the default checkpoint downloaded by the `muscriptor` library.
|
| 28 |
+
|
| 29 |
+
`muscriptor-medium` balances quality and footprint. For the best transcription quality use [`muscriptor-large`](https://huggingface.co/MuScriptor/muscriptor-large) (≈1.3B); for the smallest/fastest option use [`muscriptor-small`](https://huggingface.co/MuScriptor/muscriptor-small) (≈100M).
|
| 30 |
+
|
| 31 |
+
- Developed by [Mirelo](https://www.mirelo.ai/) x [kyutai](https://kyutai.org/)
|
| 32 |
+
- 📄 Paper: *MuScriptor: An Open Model for Multi-Instrument Music Transcription* — Rouard, Krause, Roebel, Simon-Gabriel, Défossez (2026). _<!-- TODO: add arXiv link once public; it will auto-cross-link on the Hub -->_
|
| 33 |
+
- 💻 Code: <https://github.com/muscriptor/muscriptor>
|
| 34 |
+
- 🔊 Audio samples: <https://muscriptor.github.io>
|
| 35 |
+
|
| 36 |
+
## Table of contents
|
| 37 |
+
|
| 38 |
+
- [Quickstart](#quickstart)
|
| 39 |
+
- [Model description](#model-description)
|
| 40 |
+
- [Model variants](#model-variants)
|
| 41 |
+
- [Intended uses & limitations](#intended-uses--limitations)
|
| 42 |
+
- [Instrument conditioning](#instrument-conditioning)
|
| 43 |
+
- [Training](#training)
|
| 44 |
+
- [Evaluation](#evaluation)
|
| 45 |
+
- [Citation](#citation)
|
| 46 |
+
- [License](#license)
|
| 47 |
+
|
| 48 |
+
## Quickstart
|
| 49 |
+
|
| 50 |
+
Install the `muscriptor` package (it uses `huggingface_hub` to fetch weights automatically):
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
pip install git+https://github.com/muscriptor/muscriptor.git
|
| 54 |
+
# TODO (PyPI release forthcoming: pip install muscriptor)
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### Python
|
| 58 |
+
|
| 59 |
+
```python
|
| 60 |
+
from pathlib import Path
|
| 61 |
+
from muscriptor import TranscriptionModel
|
| 62 |
+
|
| 63 |
+
# "medium" resolves to hf://MuScriptor/muscriptor-medium and downloads on first use.
|
| 64 |
+
model = TranscriptionModel.load_model("medium")
|
| 65 |
+
|
| 66 |
+
# Get a MIDI file directly:
|
| 67 |
+
Path("out.mid").write_bytes(model.transcribe_to_midi("audio.wav"))
|
| 68 |
+
|
| 69 |
+
# Or stream note events as they are transcribed:
|
| 70 |
+
for event in model.transcribe("audio.wav"):
|
| 71 |
+
print(event) # NoteStartEvent / NoteEndEvent / ProgressEvent
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
`load_model` accepts a size keyword (`"small"`/`"medium"`/`"large"`), a local `.safetensors` path, or an `hf://` / `https://` URL. Weights loaded by size keyword (or any `hf://` URL) are cached in the standard Hugging Face cache (`~/.cache/huggingface/hub`, configurable via `HF_HOME`); weights fetched from a plain `http(s)://` URL are cached under `~/.cache/muscriptor/`. Input audio can be WAV or any format `libsndfile` reads (mp3, flac, ogg, m4a, …); it is resampled to 16 kHz mono internally.
|
| 75 |
+
|
| 76 |
+
### CLI
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
muscriptor transcribe --model medium audio.wav -o out.mid
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Model description
|
| 83 |
+
|
| 84 |
+
MuScriptor performs transcription by **autoregressively predicting a MIDI-like token sequence** given the mel-spectrogram of a short audio segment, following the sequence-to-sequence AMT paradigm (cf. MT3). It deliberately avoids complex architectural tweaks in favor of a simple, decoder-only Transformer.
|
| 85 |
+
|
| 86 |
+
- **Architecture:** decoder-only Transformer (this variant: `dim=1024`, `num_heads=16`, `num_layers=24`).
|
| 87 |
+
- **Input:** raw waveform (16 kHz, mono) of a 5-second segment → mel-spectrogram (STFT `n_fft=2048`, hop 160 → 100 Hz frame rate, 512 mel bins). The spectrogram is projected to the model dimension and used as a prefix condition.
|
| 88 |
+
- **Output tokenization:** MT3-like note events; the 128 MIDI programs are mapped to **36 instrument subgroups** using the `MT3_FULL_PLUS` taxonomy. Decoding is greedy (argmax) by default, with optional classifier-free guidance (CFG).
|
| 89 |
+
- **Inference:** audio is processed in 5-second chunks; note events are emitted in temporal order. Optional **instrument conditioning** stabilizes predictions across chunk boundaries and lets you restrict/customize the transcription (see below).
|
| 90 |
+
|
| 91 |
+
**Note on the representation:** the tokenizer recovers onset/offset timing, pitch, and instrument, but **not velocity**. It also cannot represent two notes of the same pitch and instrument sounding at the same time. Drums are onset-only.
|
| 92 |
+
|
| 93 |
+
## Model variants
|
| 94 |
+
|
| 95 |
+
| Repo | Params | `dim` | heads | layers | Notes |
|
| 96 |
+
|---|---|---|---|---|---|
|
| 97 |
+
| [`muscriptor-small`](https://huggingface.co/MuScriptor/muscriptor-small) | ≈100M | 768 | 12 | 14 | smallest / fastest |
|
| 98 |
+
| [`muscriptor-medium`](https://huggingface.co/MuScriptor/muscriptor-medium) | ≈300M | 1024 | 16 | 24 | **this model** · good trade-off |
|
| 99 |
+
| [`muscriptor-large`](https://huggingface.co/MuScriptor/muscriptor-large) | ≈1.3B | 1536 | 24 | 48 | best quality |
|
| 100 |
+
|
| 101 |
+
All variants share the same input pipeline, tokenizer, and training recipe; they differ only in latent dimension, attention heads, and depth.
|
| 102 |
+
|
| 103 |
+
## Intended uses & limitations
|
| 104 |
+
|
| 105 |
+
**Intended uses**
|
| 106 |
+
- General-purpose transcription of real, multi-instrument music across genres (classical → heavy metal) into MIDI.
|
| 107 |
+
- A building block for music information retrieval (chord/key recognition), musicological analysis, generative-modeling data pipelines, and tools for musicians.
|
| 108 |
+
|
| 109 |
+
**Out of scope / use with care**
|
| 110 |
+
- Not a substitute for a hand-annotated score; expect errors, especially on dense mixes, unusual timbres, and heavily processed audio.
|
| 111 |
+
- Velocity/dynamics are **not** produced (see note above).
|
| 112 |
+
- Onset/offset precision is lower for some styles (e.g. choral music), and exact offsets are inherently harder than onsets.
|
| 113 |
+
|
| 114 |
+
**Limitations & biases**
|
| 115 |
+
- Training data skews toward pop and Western classical music, and the instrument distribution is long-tailed (piano/guitar/bass/drums are most frequent). Rare instruments and underrepresented genres may be transcribed less reliably.
|
| 116 |
+
- The fixed `MT3_FULL_PLUS` 36-group instrument taxonomy limits instrument granularity.
|
| 117 |
+
- Simultaneous same-pitch/same-instrument notes cannot be represented by the tokenizer.
|
| 118 |
+
|
| 119 |
+
## Instrument conditioning
|
| 120 |
+
|
| 121 |
+
The model can be told which instrument groups are present in the track. Supplying the correct set improves quantitative scores and produces more coherent instrument assignments across segments.
|
| 122 |
+
|
| 123 |
+
```python
|
| 124 |
+
from muscriptor.tokenizer.mt3 import MT3_FULL_PLUS_GROUP_NAMES
|
| 125 |
+
|
| 126 |
+
# `instrument_group` is a space-separated string of MT3_FULL_PLUS group IDs.
|
| 127 |
+
# Convert readable group names to IDs:
|
| 128 |
+
names = ["acoustic_piano", "acoustic_guitar", "acoustic_bass"]
|
| 129 |
+
instrument_group = " ".join(str(MT3_FULL_PLUS_GROUP_NAMES[n]) for n in names) # -> "0 4 7"
|
| 130 |
+
|
| 131 |
+
# Only expect piano, acoustic guitar and bass in this track:
|
| 132 |
+
model.transcribe_to_midi("audio.wav", instrument_group=instrument_group)
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
```bash
|
| 136 |
+
muscriptor transcribe --model medium --instruments "acoustic_piano,acoustic_guitar,acoustic_bass" audio.wav -o out.mid
|
| 137 |
+
muscriptor list-instruments # show all available group names
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
## Evaluation
|
| 141 |
+
|
| 142 |
+
Metrics are instrument-agnostic F1 scores computed with [`mir_eval`](https://github.com/craffel/mir_eval) on `D_Test`, the authors' held-out test set of 372 multi-instrument tracks.
|
| 143 |
+
|
| 144 |
+
**Model-size comparison** (F1 ↑; from the paper's scaling study, models trained on `D_Real` only, CFG = 2):
|
| 145 |
+
|
| 146 |
+
| Variant | Params | Onset | Frame | Offset | Drums | Multi |
|
| 147 |
+
|---|---|---|---|---|---|---|
|
| 148 |
+
| `muscriptor-small` | 100M | 51.2 | 67.2 | 38.7 | 41.5 | 38.2 |
|
| 149 |
+
| **`muscriptor-medium`** | **300M** | **52.4** | **68.0** | **40.3** | **42.0** | **39.7** |
|
| 150 |
+
| `muscriptor-large` | 1.3B | 53.2 | 68.7 | 41.0 | 42.5 | 40.5 |
|
| 151 |
+
|
| 152 |
+
These numbers come from the model-size ablation, which trains on real audio **only**. The **released checkpoints additionally use synthetic pre-training and RL post-training**, which improve real-world quality substantially beyond these figures. See [`muscriptor-large`](https://huggingface.co/MuScriptor/muscriptor-large) and the paper for per-dataset results.
|
| 153 |
+
|
| 154 |
+
## Citation
|
| 155 |
+
|
| 156 |
+
```bibtex
|
| 157 |
+
@inproceedings{muscriptor2026,
|
| 158 |
+
title = {MuScriptor: An Open Model for Multi-Instrument Music Transcription},
|
| 159 |
+
author = {Rouard, Simon and Krause, Michael and Roebel, Axel and
|
| 160 |
+
Simon-Gabriel, Carl-Johann and D{\'e}fossez, Alexandre},
|
| 161 |
+
year = {2026},
|
| 162 |
+
note = {Kyutai, Mirelo AI, IRCAM}
|
| 163 |
+
}
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
<!-- TODO: replace with the final published citation (venue / arXiv id) once available. -->
|
| 167 |
+
|
| 168 |
+
## License
|
| 169 |
+
|
| 170 |
+
Code released under the [MIT License](https://github.com/muscriptor/muscriptor/blob/main/LICENSE). Weights released under CC-BY-NC.
|
onnx/fp16/config.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"source_model": "MuScriptor/muscriptor-medium@f32236969308476e01fd3aae67357de5feb05a2d",
|
| 3 |
+
"dtype": "float16",
|
| 4 |
+
"embedding_storage": "float16",
|
| 5 |
+
"num_layers": 24,
|
| 6 |
+
"num_heads": 16,
|
| 7 |
+
"head_dim": 64,
|
| 8 |
+
"card": 1395,
|
| 9 |
+
"initial_token_id": 1395,
|
| 10 |
+
"eos_token_id": 1,
|
| 11 |
+
"first_reserved_token_id": 1393
|
| 12 |
+
}
|
onnx/w4a16/config.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"source_model": "MuScriptor/muscriptor-medium@f32236969308476e01fd3aae67357de5feb05a2d",
|
| 3 |
+
"dtype": "float16",
|
| 4 |
+
"embedding_storage": "float16",
|
| 5 |
+
"num_layers": 24,
|
| 6 |
+
"num_heads": 16,
|
| 7 |
+
"head_dim": 64,
|
| 8 |
+
"card": 1395,
|
| 9 |
+
"initial_token_id": 1395,
|
| 10 |
+
"eos_token_id": 1,
|
| 11 |
+
"first_reserved_token_id": 1393,
|
| 12 |
+
"backbone_weight_storage": "uint4-HQQ",
|
| 13 |
+
"lm_head_weight_storage": "float16",
|
| 14 |
+
"activation_dtype": "float16",
|
| 15 |
+
"quantization": "ORT MatMulNBits HQQ",
|
| 16 |
+
"block_size": 128,
|
| 17 |
+
"quantized_backbone_matmuls": 96,
|
| 18 |
+
"quantized_lm_head_matmuls": 0
|
| 19 |
+
}
|
runtime/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Kyutai x Mirelo
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
runtime/muscriptor_onnx/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ONNX export helpers for MuScriptor."""
|
| 2 |
+
|
| 3 |
+
from .audio import log_mel_spectrogram, load_audio_16k
|
| 4 |
+
|
| 5 |
+
__all__ = ["load_audio_16k", "log_mel_spectrogram"]
|
| 6 |
+
|
runtime/muscriptor_onnx/audio.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""NumPy implementation of MuScriptor's 16 kHz log-mel frontend."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import soundfile as sf
|
| 9 |
+
from scipy.signal import resample_poly
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
SAMPLE_RATE = 16_000
|
| 13 |
+
N_FFT = 2_048
|
| 14 |
+
HOP_LENGTH = 160
|
| 15 |
+
N_MELS = 512
|
| 16 |
+
SEGMENT_SAMPLES = 5 * SAMPLE_RATE
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def load_audio_16k(path: str | Path) -> np.ndarray:
|
| 20 |
+
"""Load an audio file as mono float32 at 16 kHz."""
|
| 21 |
+
audio, sample_rate = sf.read(path, dtype="float32", always_2d=True)
|
| 22 |
+
audio = audio.mean(axis=1)
|
| 23 |
+
if sample_rate != SAMPLE_RATE:
|
| 24 |
+
divisor = np.gcd(sample_rate, SAMPLE_RATE)
|
| 25 |
+
audio = resample_poly(
|
| 26 |
+
audio, SAMPLE_RATE // divisor, sample_rate // divisor
|
| 27 |
+
).astype(np.float32)
|
| 28 |
+
return audio.astype(np.float32, copy=False)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def first_five_second_chunk(audio: np.ndarray) -> np.ndarray:
|
| 32 |
+
"""Crop/pad audio to the five-second chunk used by MuScriptor."""
|
| 33 |
+
audio = np.asarray(audio, dtype=np.float32).reshape(-1)
|
| 34 |
+
if audio.size >= SEGMENT_SAMPLES:
|
| 35 |
+
return audio[:SEGMENT_SAMPLES]
|
| 36 |
+
return np.pad(audio, (0, SEGMENT_SAMPLES - audio.size))
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _hz_to_mel_htk(freq: np.ndarray) -> np.ndarray:
|
| 40 |
+
return 2595.0 * np.log10(1.0 + freq / 700.0)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _mel_to_hz_htk(mel: np.ndarray) -> np.ndarray:
|
| 44 |
+
return 700.0 * (10.0 ** (mel / 2595.0) - 1.0)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def mel_filterbank() -> np.ndarray:
|
| 48 |
+
"""Match the pure-torch HTK filterbank bundled with MuScriptor."""
|
| 49 |
+
all_freqs = np.linspace(0, SAMPLE_RATE // 2, N_FFT // 2 + 1, dtype=np.float32)
|
| 50 |
+
mel_min = _hz_to_mel_htk(np.asarray(0.0, dtype=np.float32))
|
| 51 |
+
mel_max = _hz_to_mel_htk(np.asarray(SAMPLE_RATE / 2, dtype=np.float32))
|
| 52 |
+
mel_points = np.linspace(mel_min, mel_max, N_MELS + 2, dtype=np.float32)
|
| 53 |
+
freq_points = _mel_to_hz_htk(mel_points)
|
| 54 |
+
freq_diff = freq_points[1:] - freq_points[:-1]
|
| 55 |
+
slopes = freq_points[None, :] - all_freqs[:, None]
|
| 56 |
+
down = -slopes[:, :-2] / freq_diff[:-1]
|
| 57 |
+
up = slopes[:, 2:] / freq_diff[1:]
|
| 58 |
+
return np.maximum(0.0, np.minimum(down, up)).astype(np.float32)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def log_mel_spectrogram(audio: np.ndarray) -> np.ndarray:
|
| 62 |
+
"""Return `[1, 501, 512]` log-mel features for a five-second chunk.
|
| 63 |
+
|
| 64 |
+
The reflection padding, periodic Hann window, FFT, HTK mel bank, and log
|
| 65 |
+
epsilon mirror ``muscriptor.modules.mel_spectrogram``.
|
| 66 |
+
"""
|
| 67 |
+
audio = first_five_second_chunk(audio)
|
| 68 |
+
padded = np.pad(audio, (N_FFT // 2, N_FFT // 2), mode="reflect")
|
| 69 |
+
frames = np.lib.stride_tricks.sliding_window_view(padded, N_FFT)[::HOP_LENGTH]
|
| 70 |
+
window = np.hanning(N_FFT + 1)[:-1].astype(np.float32)
|
| 71 |
+
spectrum = np.abs(np.fft.rfft(frames * window, n=N_FFT, axis=-1)).astype(np.float32)
|
| 72 |
+
mel = spectrum @ mel_filterbank()
|
| 73 |
+
return np.log(mel + np.float32(1e-6))[None, ...].astype(np.float32)
|
| 74 |
+
|
runtime/requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy>=1.26
|
| 2 |
+
scipy>=1.11
|
| 3 |
+
soundfile>=0.12
|
| 4 |
+
onnxruntime>=1.26.0
|
runtime/test_onnxruntime.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Run cached autoregressive MuScriptor inference with ONNX Runtime."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
import time
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
# Importing PyTorch first preloads the CUDA/cuDNN shared libraries shipped in
|
| 13 |
+
# the uv environment when available; CPU-only users do not need PyTorch.
|
| 14 |
+
try:
|
| 15 |
+
import torch # noqa: F401
|
| 16 |
+
except ModuleNotFoundError:
|
| 17 |
+
torch = None # type: ignore[assignment]
|
| 18 |
+
import onnxruntime as ort
|
| 19 |
+
|
| 20 |
+
from muscriptor_onnx.audio import SAMPLE_RATE, load_audio_16k, log_mel_spectrogram
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def providers(requested: str, device_id: int) -> list:
|
| 24 |
+
available = ort.get_available_providers()
|
| 25 |
+
if requested == "cuda" or (requested == "auto" and "CUDAExecutionProvider" in available):
|
| 26 |
+
if "CUDAExecutionProvider" not in available:
|
| 27 |
+
raise RuntimeError(f"CUDA EP unavailable; installed providers: {available}")
|
| 28 |
+
return [("CUDAExecutionProvider", {"device_id": device_id}), "CPUExecutionProvider"]
|
| 29 |
+
return ["CPUExecutionProvider"]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def session(path: Path, selected_providers: list) -> ort.InferenceSession:
|
| 33 |
+
options = ort.SessionOptions()
|
| 34 |
+
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
| 35 |
+
result = ort.InferenceSession(path, sess_options=options, providers=selected_providers)
|
| 36 |
+
requested_cuda = bool(selected_providers) and (
|
| 37 |
+
selected_providers[0] == "CUDAExecutionProvider"
|
| 38 |
+
or (
|
| 39 |
+
isinstance(selected_providers[0], tuple)
|
| 40 |
+
and selected_providers[0][0] == "CUDAExecutionProvider"
|
| 41 |
+
)
|
| 42 |
+
)
|
| 43 |
+
if requested_cuda and "CUDAExecutionProvider" not in result.get_providers():
|
| 44 |
+
raise RuntimeError("CUDA EP was requested but session creation fell back to CPU")
|
| 45 |
+
return result
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def causal_mask(query_length: int, past_length: int) -> np.ndarray:
|
| 49 |
+
query_positions = past_length + np.arange(query_length)[:, None]
|
| 50 |
+
key_positions = np.arange(past_length + query_length)[None, :]
|
| 51 |
+
return np.where(key_positions <= query_positions, 0.0, -65504.0).astype(np.float16)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def main() -> None:
|
| 55 |
+
parser = argparse.ArgumentParser()
|
| 56 |
+
parser.add_argument("--model-dir", type=Path, required=True)
|
| 57 |
+
parser.add_argument("--audio", type=Path)
|
| 58 |
+
parser.add_argument("--provider", choices=("auto", "cuda", "cpu"), default="auto")
|
| 59 |
+
parser.add_argument("--device-id", type=int, default=0)
|
| 60 |
+
parser.add_argument("--max-new-tokens", type=int, default=8)
|
| 61 |
+
parser.add_argument(
|
| 62 |
+
"--instrument-id",
|
| 63 |
+
type=int,
|
| 64 |
+
action="append",
|
| 65 |
+
help="Optional MT3_FULL_PLUS group ID; repeat for multiple groups",
|
| 66 |
+
)
|
| 67 |
+
args = parser.parse_args()
|
| 68 |
+
|
| 69 |
+
metadata = json.loads((args.model_dir / "config.json").read_text())
|
| 70 |
+
selected = providers(args.provider, args.device_id)
|
| 71 |
+
conditioner = session(args.model_dir / "conditioner.onnx", selected)
|
| 72 |
+
decoder = session(args.model_dir / "decoder.onnx", selected)
|
| 73 |
+
|
| 74 |
+
if args.audio:
|
| 75 |
+
audio = load_audio_16k(args.audio)
|
| 76 |
+
source = str(args.audio)
|
| 77 |
+
else:
|
| 78 |
+
# Deterministic smoke-test input; it only tests execution, not quality.
|
| 79 |
+
time_axis = np.arange(5 * SAMPLE_RATE, dtype=np.float32) / SAMPLE_RATE
|
| 80 |
+
audio = (0.1 * np.sin(2 * np.pi * 440.0 * time_axis)).astype(np.float32)
|
| 81 |
+
source = "generated 440 Hz sine"
|
| 82 |
+
mel = log_mel_spectrogram(audio).astype(np.float16)
|
| 83 |
+
instrument_ids = np.asarray(
|
| 84 |
+
[args.instrument_id if args.instrument_id is not None else [-1]], dtype=np.int64
|
| 85 |
+
)
|
| 86 |
+
dataset_ids = np.asarray([[-1]], dtype=np.int64)
|
| 87 |
+
|
| 88 |
+
start = time.perf_counter()
|
| 89 |
+
condition = conditioner.run(
|
| 90 |
+
["condition_embeddings"],
|
| 91 |
+
{
|
| 92 |
+
"log_mel": mel,
|
| 93 |
+
"instrument_ids": instrument_ids,
|
| 94 |
+
"dataset_ids": dataset_ids,
|
| 95 |
+
},
|
| 96 |
+
)[0]
|
| 97 |
+
condition_seconds = time.perf_counter() - start
|
| 98 |
+
|
| 99 |
+
layers = metadata["num_layers"]
|
| 100 |
+
heads = metadata["num_heads"]
|
| 101 |
+
head_dim = metadata["head_dim"]
|
| 102 |
+
past_key = np.zeros((layers, 1, heads, 0, head_dim), dtype=np.float16)
|
| 103 |
+
past_value = np.zeros_like(past_key)
|
| 104 |
+
input_ids = np.asarray([[metadata["initial_token_id"]]], dtype=np.int64)
|
| 105 |
+
generated: list[int] = []
|
| 106 |
+
decode_times: list[float] = []
|
| 107 |
+
|
| 108 |
+
for step in range(args.max_new_tokens):
|
| 109 |
+
prefix = condition if step == 0 else np.empty((1, 0, heads * head_dim), np.float16)
|
| 110 |
+
query_length = prefix.shape[1] + input_ids.shape[1]
|
| 111 |
+
mask = causal_mask(query_length, past_key.shape[3])
|
| 112 |
+
tick = time.perf_counter()
|
| 113 |
+
logits, past_key, past_value = decoder.run(
|
| 114 |
+
("logits", "present_key", "present_value"),
|
| 115 |
+
{
|
| 116 |
+
"input_ids": input_ids,
|
| 117 |
+
"condition_embeddings": prefix,
|
| 118 |
+
"past_key": past_key,
|
| 119 |
+
"past_value": past_value,
|
| 120 |
+
"attention_mask": mask,
|
| 121 |
+
},
|
| 122 |
+
)
|
| 123 |
+
decode_times.append(time.perf_counter() - tick)
|
| 124 |
+
if not np.isfinite(logits).all():
|
| 125 |
+
raise RuntimeError("decoder produced non-finite logits")
|
| 126 |
+
logits[:, metadata["first_reserved_token_id"] :] = -np.inf
|
| 127 |
+
token = int(logits.argmax(axis=-1)[0])
|
| 128 |
+
generated.append(token)
|
| 129 |
+
input_ids = np.asarray([[token]], dtype=np.int64)
|
| 130 |
+
if token == metadata["eos_token_id"]:
|
| 131 |
+
break
|
| 132 |
+
|
| 133 |
+
active = decoder.get_providers()
|
| 134 |
+
sizes = {
|
| 135 |
+
path.name: path.stat().st_size
|
| 136 |
+
for path in args.model_dir.iterdir()
|
| 137 |
+
if path.is_file() and (path.suffix == ".onnx" or path.name.endswith(".onnx.data"))
|
| 138 |
+
}
|
| 139 |
+
sizes["total"] = sum(sizes.values())
|
| 140 |
+
print(f"model: {args.model_dir}")
|
| 141 |
+
print(f"audio: {source}")
|
| 142 |
+
print(f"providers: {active}")
|
| 143 |
+
print(f"mel shape: {mel.shape}")
|
| 144 |
+
print(f"condition shape: {condition.shape}")
|
| 145 |
+
print(f"final KV shape: {past_key.shape}")
|
| 146 |
+
print(f"generated tokens: {generated}")
|
| 147 |
+
print(f"condition latency: {condition_seconds * 1000:.1f} ms")
|
| 148 |
+
print(f"decode latency: {[round(x * 1000, 1) for x in decode_times]} ms")
|
| 149 |
+
print(f"ONNX file sizes: {sizes}")
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
if __name__ == "__main__":
|
| 153 |
+
main()
|