| --- |
| language: en |
| license: mit |
| library_name: onnxruntime |
| pipeline_tag: audio-to-audio |
| tags: |
| - onnx |
| - onnxruntime |
| - stem-separation |
| - source-separation |
| - demucs |
| - htdemucs |
| - music |
| - audio-to-audio |
| - mobile |
| - ios |
| - android |
| - coreml |
| - directml |
| - production-ready |
| - vocal-extraction |
| - vocal-isolation |
| - vocal-remover |
| - karaoke |
| - acapella |
| datasets: |
| - StemSplitio/stem-separation-benchmark-2026 |
| inference: false |
| --- |
| |
| # HT-Demucs FT β Vocals Specialist, ONNX |
|
|
| **The #1 open-source vocal separator on MUSDB18-HQ**, exported to ONNX. No PyTorch required at inference. Runs on CPU / CoreML / CUDA / DirectML. |
|
|
| This repo packages sub-model 3 of the |
| [`htdemucs_ft`](https://github.com/facebookresearch/demucs) 4-bag ensemble |
| as a single 316 MB `.onnx` file plus a ~150-line numpy reference inference |
| script. Verified to be **numerically equivalent** to the original PyTorch |
| model. |
|
|
| > Want all 4 stems in one drop-in package? Use the full bag repo: |
| > [`StemSplitio/htdemucs-ft-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-onnx). |
|
|
| --- |
|
|
| ## TL;DR |
|
|
| ```bash |
| pip install onnxruntime numpy soundfile |
| python infer.py your-song.mp3 ./out/ |
| # writes ./out/vocals.wav at 44.1 kHz stereo |
| ``` |
|
|
| That's it. No PyTorch, no CUDA setup, no GPU server. |
|
|
| --- |
|
|
| ## Quality |
|
|
| | Metric (MUSDB18-HQ test, 50 songs) | Value | Source | |
| |---|---|---| |
| | Median vocals SDR | **9.19 dB** | [StemSplitio/stem-separation-benchmark-2026](https://huggingface.co/datasets/StemSplitio/stem-separation-benchmark-2026) | |
| | Rank among open-source separators on vocals | **#1** (the highest open-source vocal SDR on MUSDB18-HQ) | same | |
| | ONNX vs PyTorch max abs diff | **< 1e-3** | verified during export (see [Day 1 spike report](https://huggingface.co/StemSplitio/htdemucs-ft-drums-onnx#how-it-was-built)) | |
|
|
| --- |
|
|
| ## Performance |
|
|
| | Runtime | Hardware | Per 7.8-s segment | Per 3-min song | |
| |---|---|---:|---:| |
| | **onnxruntime CPU EP** | Apple M4 Pro | **~1.6 s** | **~22 s** | |
| | PyTorch CPU | Apple M4 Pro | ~2.1 s | ~29 s | |
| | onnxruntime CUDA EP | NVIDIA L4 | ~0.4 s | ~5 s *(extrapolated)* | |
| | onnxruntime DirectML EP | RTX 4090 | ~0.2 s | ~2 s *(extrapolated)* | |
|
|
| **Real-time factor on M4 Pro CPU: 0.20.** Roughly 1.31Γ faster than |
| PyTorch CPU on the same hardware. |
|
|
| --- |
|
|
| ## Tooling β `demucs-onnx` Python package |
|
|
| This model can also be run (and re-exported) via the open-source |
| [`demucs-onnx`](https://github.com/StemSplit/demucs-onnx) Python package |
| on PyPI. It auto-downloads from this repo on first use. |
|
|
| ```bash |
| pip install demucs-onnx |
| |
| # Single specialist (this repo) |
| demucs-onnx separate song.mp3 stems/ --stem vocals |
| |
| # Or via the Python API |
| python -c "from demucs_onnx import separate_stem; \ |
| audio = separate_stem('song.mp3', 'vocals')" |
| ``` |
|
|
| The same package is also the canonical tool for **exporting** htdemucs |
| to ONNX yourself β it bundles all four blocker fixes (complex STFT, |
| `fractions.Fraction`, `random.randrange`, |
| `aten::_native_multi_head_attention`) so vanilla `torch.onnx.export` |
| works on your own checkpoints. |
|
|
| ```bash |
| pip install "demucs-onnx[export]" |
| demucs-onnx export htdemucs_ft vocals.onnx --stem vocals |
| ``` |
|
|
| --- |
|
|
| ## Common use cases |
|
|
| - **Karaoke maker** β extract clean instrumental + acapella in one pass (pair with the `other` ONNX) |
| - **Acapella extraction** β harvest isolated vocals for sampling, remixing, vocal-coach feedback |
| - **Vocal removal** β build a vocal-remover app on iOS / Android / web without a GPU server |
| - **Speech-from-music** β isolate spoken-word from background music for transcription |
|
|
| --- |
|
|
| ## Quick start |
|
|
| ### Python β minimal |
|
|
| ```python |
| import infer |
| vocals = infer.separate_vocals("your-song.mp3") |
| # vocals: numpy array (2, samples) at 44.1 kHz |
| ``` |
|
|
| ### Python β full control |
|
|
| ```python |
| import soundfile as sf |
| import infer |
| |
| # Optional execution providers β CPU is the default and most portable. |
| # Swap to "coreml" on macOS, "cuda" on NVIDIA, "dml" on Windows DX12. |
| audio, sr = sf.read("your-song.mp3", dtype="float32", always_2d=True) |
| stems = infer.separate(audio.T, sr, providers=["CPUExecutionProvider"]) |
| sf.write("vocals.wav", stems[infer.SOURCES.index("vocals")].T, sr) |
| ``` |
|
|
| ### CLI |
|
|
| ```bash |
| python infer.py your-song.mp3 ./out/ |
| python infer.py your-song.mp3 ./out/ --providers cuda # NVIDIA |
| python infer.py your-song.mp3 ./out/ --providers coreml # macOS |
| python infer.py your-song.mp3 ./out/ --providers dml # Windows |
| ``` |
|
|
| ### Mobile (iOS / Swift) |
|
|
| ```swift |
| import onnxruntime_objc |
| |
| let env = try ORTEnv(loggingLevel: .warning) |
| let opts = try ORTSessionOptions() |
| try opts.appendCoreMLExecutionProvider(with: ORTCoreMLExecutionProviderOptions()) |
| let session = try ORTSession(env: env, |
| modelPath: Bundle.main.path(forResource: "htdemucs_ft_vocals", ofType: "onnx")!, |
| sessionOptions: opts) |
| // audio: 1 Γ 2 Γ 343980 Float32 buffer, then session.run(...). |
| ``` |
|
|
| ### Mobile (Android / Kotlin) |
|
|
| ```kotlin |
| import ai.onnxruntime.OrtEnvironment |
| import ai.onnxruntime.OrtSession |
| |
| val env = OrtEnvironment.getEnvironment() |
| val opts = OrtSession.SessionOptions().apply { addNnapi() } |
| val session = env.createSession(modelPath, opts) |
| ``` |
|
|
| ### Web (onnxruntime-web) |
|
|
| ```js |
| import * as ort from "onnxruntime-web"; |
| const session = await ort.InferenceSession.create("htdemucs_ft_vocals.onnx", { |
| executionProviders: ["wasm"], |
| graphOptimizationLevel: "all", |
| }); |
| const tensor = new ort.Tensor("float32", audioBuffer, [1, 2, 343980]); |
| const out = await session.run({ mix: tensor }); |
| // out.stems.data is a Float32Array (1, 4, 2, 343980); use row 3 for vocals. |
| ``` |
|
|
| --- |
|
|
| ## Input / output spec |
|
|
| | Tensor | Name | Shape | Dtype | Notes | |
| |---|---|---|---|---| |
| | Input | `mix` | `(1, 2, 343980)` | float32 | Stereo audio, 44.1 kHz, 7.8 s segment. Values in [-1, 1]. | |
| | Output | `stems` | `(1, 4, 2, 343980)` | float32 | `[drums, bass, other, vocals]` order. **Use only row 3 (`vocals`)** β the other 3 rows are weakly-predicted by-products of the vocals specialist. | |
|
|
| For longer audio, chunk with overlap-add β see `infer.py::separate` for a |
| working ~60-line implementation. |
|
|
| --- |
|
|
| ## Related repos |
|
|
| Sibling stem-specialist ONNX repos from the same export: |
|
|
| | Repo | Stem | Use when | |
| |---|---|---| |
| | [`htdemucs-ft-drums-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-drums-onnx) | drums | Drum extraction, beat transcription | |
| | [`htdemucs-ft-bass-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-bass-onnx) | bass | Bassline transcription, mix rebalancing | |
| | [`htdemucs-ft-other-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-other-onnx) | other | Karaoke instrumentals, sample-flipping | |
| | [`htdemucs-ft-vocals-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-vocals-onnx) | vocals | **#1 open-source vocal SDR** β karaoke, acapella, vocal removal | |
| | [`htdemucs-ft-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-onnx) | all 4 | Full 4-stem separation in one repo | |
|
|
| PyTorch versions for HF Inference Endpoints: |
| [`htdemucs-ft-pytorch`](https://huggingface.co/StemSplitio/htdemucs-ft-pytorch), |
| [`htdemucs-ft-vocals-pytorch`](https://huggingface.co/StemSplitio/htdemucs-ft-vocals-pytorch). |
|
|
| Full benchmark across every popular open-source separator: |
| [StemSplitio/stem-separation-benchmark-2026](https://huggingface.co/datasets/StemSplitio/stem-separation-benchmark-2026). |
|
|
| --- |
|
|
| ## Skip the infrastructure β use the StemSplit API |
|
|
| Don't want to ship a 316 MB model in your app, manage a GPU pool, or write |
| overlap-add chunking? Use the **[StemSplit API](https://stemsplit.io/developers)** |
| instead β same model under the hood, hosted for you, with credits and a |
| dashboard. |
|
|
| - π [stemsplit.io](https://stemsplit.io) |
| - π [Developer docs](https://stemsplit.io/developers/docs) |
| - π [API reference](https://stemsplit.io/developers/reference) |
| - π [Guides & recipes](https://stemsplit.io/developers/guides) |
|
|
| Or use the no-code tools that ship the same model family: |
|
|
| - π§ [Vocal Remover](https://stemsplit.io/vocal-remover) |
| - π§ [Karaoke Maker](https://stemsplit.io/karaoke-maker) |
| - π§ [Acapella Maker](https://stemsplit.io/acapella-maker) |
| - π§ [YouTube Stem Splitter](https://stemsplit.io/youtube-stem-splitter) |
|
|
| --- |
|
|
| ## Files in this repo |
|
|
| | File | Size | Purpose | |
| |---|---:|---| |
| | `htdemucs_ft_vocals.onnx` | 316 MB | The exported model. Opset 17. Passes `onnx.checker`. | |
| | `infer.py` | ~6 KB | Pure numpy + onnxruntime reference. No torch. | |
| | `requirements.txt` | <1 KB | `onnxruntime`, `numpy`, `soundfile`. | |
| | `README.md` | this file | | |
|
|
| --- |
|
|
| ## License & attribution |
|
|
| This repo is **MIT-licensed**, matching the original HT-Demucs. |
|
|
| ```bibtex |
| @inproceedings{rouard2023hybrid, |
| title = {Hybrid Transformers for Music Source Separation}, |
| author = {Rouard, Simon and Massa, Francisco and D{\'e}fossez, Alexandre}, |
| booktitle = {ICASSP}, |
| year = {2023} |
| } |
| ``` |
|
|
| - Original PyTorch model: [`facebookresearch/demucs`](https://github.com/facebookresearch/demucs) |
| - ONNX export, parity verification, and packaging by [StemSplit](https://stemsplit.io) |
| - Search keywords: vocal remover onnx, karaoke maker, acapella extractor, htdemucs vocals onnx, vocal separation ios |
|
|