Skip to content

Add MuScriptor audio-to-MIDI transcription (model + example)#3738

Open
sinkingsugar wants to merge 4 commits into
huggingface:mainfrom
shards-lang:muscriptor
Open

Add MuScriptor audio-to-MIDI transcription (model + example)#3738
sinkingsugar wants to merge 4 commits into
huggingface:mainfrom
shards-lang:muscriptor

Conversation

@sinkingsugar

Copy link
Copy Markdown

This adds MuScriptor (Kyutai/Mirelo lineage), an audio→MIDI music transcription model, plus two small build fixes it surfaced.

Model

A decoder-only prefix-LM in the audiocraft/MusicGen style: audio is processed in 5-second chunks, each chunk's log-mel spectrogram is projected and prepended as prefix tokens (together with instrument-group/dataset class embeddings), then MIDI event tokens are generated autoregressively over a single stream and decoded into notes via an MT3-style tokenizer.

  • candle-transformers/src/models/muscriptor/: transformer + LM + conditioners; a pure-Rust STFT/mel front-end (the Hann window and HTK filterbank are loaded from checkpoint buffers, so they match the reference by construction); the token vocabulary and the streaming token→note-event decoder, with unit tests for the pure-logic parts.
  • candle-examples/examples/muscriptor/: CLI — symphonia decode, a julius-style sinc resampler, chunked/batched generation with streaming note output, and a MIDI (SMF format 0) writer. No new dependencies.

Verified against the PyTorch reference implementation (large checkpoint, greedy decoding): MIDI output is byte-identical on CPU f32 and Metal f32, and event-identical on Metal f16, on both synthetic and real-music inputs.

Note the published weights are CC BY-NC 4.0 (small/medium/large variants; gated repos).

cargo run --example muscriptor --release --features symphonia -- song.mp3 --model large --notes

Build fixes included

  • fix(cpu): gate the unstable neon fp16 kernel tier behind nightly toolchainsfloat16x8_t requires the unstable stdarch_neon_f16 feature (Tracking Issue for NEON fp16 intrinsics rust-lang/rust#136306) and target_feature=fp16 is enabled by default on aarch64-apple-darwin, so cargo check on stable currently fails with E0658 on Apple Silicon. A small build.rs sets cfg(candle_nightly) on nightly compilers; stable builds fall back to the existing FCVTL-widening kernels.
  • fix(examples): pin symphonia to 0.5 — the bump to 0.6 (a full API rework) broke all examples behind the symphonia/mimi/encodec/snac features; CI doesn't build those features so it went unnoticed.

Happy to split the two fixes into separate PRs if preferred.

…chains

float16x8_t needs the stdarch_neon_f16 library feature, which is still
unstable (rust-lang/rust#136306), and target_feature=fp16 is on by default
on aarch64-apple-darwin — so stable Apple Silicon builds failed with E0658.
build.rs now detects a nightly compiler and sets cfg(candle_nightly); only
then are the fp16-accumulating kernels compiled, with stable falling back
to the FCVTL widening tier.
The dependabot bump to symphonia 0.6 broke every example behind the
symphonia/mimi/encodec/snac features: 0.6 is a full API rework
(formats::probe, audio::sample, ...) while the examples still use the 0.5
API. CI never builds those features, so it went unnoticed.
Port of the Kyutai/Mirelo MuScriptor inference (decoder-only prefix-LM,
audiocraft lineage): log-mel conditioning prepended as prefix tokens, MIDI
event tokens generated autoregressively over a single stream, then decoded
to notes through the MT3 tokenizer's tie-prologue state machine.

- candle-transformers/src/models/muscriptor: transformer + LM + mel/class
  conditioners (mel STFT in pure Rust, window and HTK filterbank loaded
  from the checkpoint), token vocabulary, streaming note-event decoder,
  note cleanup passes; unit tests for the pure-logic parts.
- candle-examples/examples/muscriptor: CLI (symphonia decode, julius-style
  sinc resampler, chunked batched generation, mido-compatible SMF writer).

Verified against the PyTorch reference (large checkpoint, greedy): MIDI
output is byte-identical on CPU f32 and Metal f32, event-identical on
Metal f16. Weights: hf.co/MuScriptor (CC BY-NC 4.0).
- Use the fused Metal SDPA (vector kernel for single-token decode, causal
  full kernel for prefill) instead of manual matmul/softmax/matmul; the
  kernels read the KV cache's narrowed views through their strides.
- Use the fused layer-norm kernel on Metal instead of the decomposed
  mean/variance op chain (2 norms x 48 layers of small launches).
- One batched argmax and a single device-to-host transfer per decode step
  instead of one sync per batch row.

Large model, 30 s of music, batch 4, M-series Metal f16: 38 s -> 16.6 s
generation (PyTorch MPS f16 reference: 11.9 s). Verification gates hold:
MIDI byte-identical to the PyTorch reference on CPU f32 and Metal f32,
event-identical on Metal f16.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant