Files
vllm-omni/recipes/m-a-p/SheetSage2-H200.md
T

259 lines
12 KiB
Markdown
Raw Normal View History

# SheetSage2 audio-to-score preprocessing — H200
## Summary
- Vendor: m-a-p
- Model: `m-a-p/SheetSage2` with the `m-a-p/MERT-v2-FullSong` parent
- Task: recording → ABC score, MIDI and timed music annotations
- Mode: standalone official Transformers inference; optional YuE2 request export
- Hardware: one NVIDIA H200 (141 GB)
- Maintainer: [@princepride](https://github.com/princepride)
## When to use this recipe
Use a recording as the musical score for a YuE2 cover, or inspect its melodies,
chords and structure. The tool calls SheetSage2's official `transcribe()` API.
It does not register SheetSage2 as a native vLLM model or start an Omni server.
The transcription tool works independently of YuE2. For score-conditioned
generation, use an Omni checkout containing the merged
[PR #7886](https://github.com/vllm-project/vllm-omni/pull/7886)
(`423f34326ed420e5acf0b1fb862a1b5ffb7e0fa7` or later). See the
[YuE2 recipe](YuE2-3B.md) for the generation model's runtime contract.
## Supported model contract
| Item | Contract |
| --- | --- |
| Input | One local recording decodable by FFmpeg; upstream converts it to mono, 24 kHz |
| Duration | Entire recording by default; `--max-seconds` limits preprocessing to a prefix |
| Full score | Default: melody voices and chords; exported request uses `cot=full` |
| Melody score | `--melody-only`: vocal and instrumental melodies, without score/playback chords; request uses `cot=melody` |
| Outputs | `score.abc`, `transcription.mid`, upstream annotations and `manifest.json` |
| YuE2 handoff | `--lyrics-file` and `--style` together also write `yue2_request.json` |
| Output directory | Must be new or empty; failed exports produce no YuE2 request |
| Deployment profile | One process, one H200, FP32 weight loading and BF16 inference autocast |
`--melody-only` does not isolate or clone a singer. The upstream model still
decodes its task annotations and retains those raw annotation files. YuE2
uses the exported score to condition newly generated audio; a transcription
does not guarantee an exact reproduction of the recording.
## References
- [Official SheetSage2 model card](https://huggingface.co/m-a-p/SheetSage2)
- [Pinned SheetSage2 source](https://huggingface.co/m-a-p/SheetSage2/tree/cafc0df1021e14f49e928c4b345f5959d414ef64)
- [Official YuE2 model card and cover documentation](https://huggingface.co/m-a-p/YuE2-3B)
- Tool: [`tools/sheetsage2_transcribe.py`](../../tools/sheetsage2_transcribe.py)
- Offline generation: [`examples/offline_inference/yue2/end2end.py`](../../examples/offline_inference/yue2/end2end.py)
- [Supported generation models](../../docs/models/supported_models.md)
## Hardware
- Accelerator: one NVIDIA H200, 141 GB; no multi-device interconnect required.
- Qualification: local M4A transcription, melody-only prefix, full-score
whole-song export and one offline YuE2 cover. Run transcription and generation
sequentially in separate processes. Other devices, CPU inference and
performance scaling are not qualified by this recipe.
## Software environment
- Ubuntu 22.04, Python 3.10, NVIDIA driver 590.48.01.
- PyTorch/torchaudio 2.8.0 (CUDA 12.8 wheels), Transformers 4.45.2.
- FFmpeg 4.4.2 was used for the local M4A check; upstream recommends FFmpeg 6.1.
- vLLM: not required in the preprocessing environment.
- vLLM-Omni: tool developed against main `a038b38179e9c788d3af6a8e73f94652afb247e4`.
- YuE2 qualification: vLLM 0.30.0, PyTorch 2.13.0+cu130, Transformers 5.14.1;
#7886 at `5cc8acc942e76fa6f12f8b2f2558c2663abc2aa5` with the FP32 VAE-loading
and scalar `truncated` fixes subsequently included in the merged integration.
## Command
### Set up transcription
Run from this repository's root. Create a **separate environment**: the
official transcriber's Transformers version differs from Omni's serving
dependencies. FFmpeg must be on `PATH`.
```bash
uv venv --python 3.10 .venv-sheetsage2
uv pip install --python .venv-sheetsage2/bin/python \
-r tools/requirements/sheetsage2.txt
.venv-sheetsage2/bin/python tools/sheetsage2_transcribe.py reference.wav \
--trust-remote-code --device cuda:0 --melody-only \
--output-dir outputs/reference-score
```
The default Hub checkpoint and its custom Python code are pinned to
`cafc0df1021e14f49e928c4b345f5959d414ef64`. Its configuration pins the MERT-v2
parent to `d8ba1c745e733b3908ce6ad16ebeb17ac7600a42`. Review that code before
passing `--trust-remote-code`. `--revision` can override the default pin;
custom Hub IDs use their default revision unless one is supplied.
Local snapshots must preserve this pinned source layout: `modeling_sheetsage2.py`
and its transitive relative-import dependencies are sibling `.py` files in the
snapshot root. The local module-cache workaround targets that flat layout;
reorganized snapshots with nested Python packages are not supported by it.
For offline use, download **both** model snapshots, including their Python,
JSON and safetensors files, before disconnecting:
```bash
.venv-sheetsage2/bin/hf download m-a-p/SheetSage2 \
--revision cafc0df1021e14f49e928c4b345f5959d414ef64 \
--local-dir models/SheetSage2
.venv-sheetsage2/bin/hf download m-a-p/MERT-v2-FullSong \
--revision d8ba1c745e733b3908ce6ad16ebeb17ac7600a42 \
--local-dir models/MERT-v2-FullSong
```
### Transcribe a full recording for a cover
Supply target lyrics in `lyrics.txt`, including YuE2 section tags such as
`[Verse]` and `[Chorus]`. Match the lyric phrasing to the reference melody.
This example retains the melody and chords of the whole recording:
```bash
.venv-sheetsage2/bin/python tools/sheetsage2_transcribe.py reference.wav \
--model models/SheetSage2 --base-model-path models/MERT-v2-FullSong \
--local-files-only --trust-remote-code --device cuda:0 \
--lyrics-file lyrics.txt --style 'Chinese folk, guzheng, gentle vocals' \
--seed 831001 --output-dir outputs/cover-score
```
Add `--melody-only` for a new arrangement without the original chords;
this also changes the exported request from `cot=full` to `cot=melody`.
For a short transcription smoke test, add `--max-seconds 45` and use lyrics
appropriate to that excerpt. Use a fresh output directory for each run.
### Generate offline
Switch to your **Omni environment** for generation. Download `m-a-p/YuE2-3B`
and `m-a-p/YuE2-Vae` to `models/YuE2-3B` and `models/YuE2-Vae`, respectively.
The offline entrypoint needs a local YuE2 directory containing `qwen.tiktoken`.
From the repository root, pass the exported ABC to the existing entrypoint:
```bash
CUDA_VISIBLE_DEVICES=0 python examples/offline_inference/yue2/end2end.py \
--model models/YuE2-3B --vae models/YuE2-Vae \
--lyrics "$(cat lyrics.txt)" \
--style 'Chinese folk, guzheng, gentle vocals' \
--abc-file outputs/cover-score/score.abc --cot full \
--seed 831001 --max-frames 9000 --gpu-memory-utilization 0.25 \
--output cover.wav
```
Use `--cot melody` if the score was transcribed with `--melody-only`.
Changing `--cot` alone does not remove chords from an existing score.
The 9,000-frame budget permits up to 360 seconds at 25 frames/s; it does
not force that duration. The entrypoint's default 200-frame budget caps
audio at 8 seconds. Check that the final report says `truncated=False`.
The memory fraction above was used on one H200; it is not a measured
minimum memory requirement.
### Submit over HTTP (configuration-only)
The exported JSON can also be submitted to YuE2's speech endpoint. These
commands have not been qualified with an HTTP synthesis run in this recipe.
Start YuE2 in its **Omni environment**:
```bash
vllm serve m-a-p/YuE2-3B --omni --port 8091
```
Submit the exported file:
```bash
curl --fail-with-body http://localhost:8091/v1/audio/speech \
-H 'Content-Type: application/json' \
--data-binary @outputs/cover-score/yue2_request.json \
--output cover.wav
```
The JSON contains the score itself, not a server-local file path. It sets
`stream=false`, requests WAV, and preserves an optional generation seed.
Use `--yue2-model` to match a custom served model name. The tool does not
submit the request or set a generation token budget; the server's own
duration and context limits apply.
## Verification
After a successful transcription, set `output` below to its output directory
(`outputs/reference-score` for the transcription-only example). The YuE2
request is checked when exported with `--lyrics-file` and `--style`:
```bash
.venv-sheetsage2/bin/python - <<'PY'
import json
from pathlib import Path
import mido
output = Path("outputs/cover-score")
abc = (output / "score.abc").read_text(encoding="utf-8")
manifest = json.loads((output / "manifest.json").read_text(encoding="utf-8"))
assert abc.strip()
request_path = output / "yue2_request.json"
if request_path.exists():
request = json.loads(request_path.read_text(encoding="utf-8"))
assert request["extra_params"]["abc"] == abc
assert request["extra_params"]["cot"] == ("melody" if manifest["melody_only"] else "full")
print("YuE2 request verified")
midi = mido.MidiFile(output / "transcription.mid")
assert any(msg.type == "note_on" and msg.velocity for track in midi.tracks for msg in track)
print("ABC, MIDI and manifest verified")
PY
```
Run the CPU CLI contract tests in the repository's normal test
environment (with pytest and pytest-mock installed):
```bash
python -m pytest tests/tools/test_sheetsage2_transcribe.py -m 'core_model and cpu' -q
```
These tests cover request construction, loading options, validation and
failure handling with a mocked backend. They do not measure transcription
accuracy or generated cover similarity.
Local H200 checks with the pinned backend:
| Input / mode | Result |
| --- | --- |
| 45 s M4A prefix, melody-only | 579 ABC characters; parseable MIDI with 118 note-on events |
| 242.219 s M4A, full score | 3,033 ABC characters; parseable MIDI with 1,087 note-on events |
| Default Hub ID, 10 s prefix | Successful ABC and MIDI export |
| Local snapshots, fresh Transformers module cache | Successful offline full-score export |
| Both generated JSON requests, #7886 adapter at `5cc8acc942e76fa6f12f8b2f2558c2663abc2aa5` | Validation and prompt construction passed (919 / 2,821 prompt tokens) |
| Full score + new Mandarin lyrics, offline YuE2, `cot=full`, seed 831001, 9,000-frame budget | 241.319 s, 48 kHz stereo WAV; 6,034 generated tokens; `truncated=False` |
The offline cover used a 108 BPM Chinese traditional pop style and one H200
with `--gpu-memory-utilization 0.25`. The saved waveform contained finite
samples, peak amplitude 0.9501, RMS 0.1232 and no full-scale samples. This is
one functional run, not an accuracy or performance benchmark. It does not
establish perceptual equivalence to the recording. HTTP synthesis and
recordings longer than 300 seconds were not exercised. The local recording,
lyrics, generated scores and cover audio are not included in the repository.
## Notes
- Weights load in FP32 because upstream merges MERT adapters before inference
autocast. `--dtype fp32` disables BF16 autocast.
- For local snapshots the tool prepares transitive Python dependencies in
Transformers' module cache. This avoids the pinned Transformers version's
direct-import-only copy behavior without editing upstream files.
- SheetSage2 manages long recordings with its own overlapping windows. This
tool leaves windowing and notation generation to the official implementation.
- Failed runs may retain upstream diagnostic annotations. Choose a new output
directory when retrying; no incomplete request is published for synthesis.
- Model weights have upstream license terms (CC BY-NC 4.0); this repository
does not bundle weights or recordings.
## Supported features
| Feature | Status |
| --- | --- |
| Audio-to-score preprocessing | Official backend; local file input |
| YuE2 score-conditioned generation | Offline ABC handoff qualified on H200; HTTP request export validated, HTTP synthesis configuration-only |
| Native Omni inference, continuous batching, TP/PP/SP | Not implemented for SheetSage2 |
| Streaming transcription or cover audio | Not supported by this tool |