mlx-audio-separator
MLX-native stem separation for Apple Silicon Macs.
This project ports the inference paths from audio-separator (upstream repo: nomadkaraoke/python-audio-separator) to MLX so separation runs on Apple Silicon without requiring PyTorch or ONNX Runtime at inference time. Core runtime components are powered by mlx-audio-io (audio I/O) and mlx-spectro (spectral transforms).
Requirements
- macOS 13+ (Ventura or later)
- Apple Silicon (M1/M2/M3/M4)
- Python 3.10+
Installation
pip install mlx-audio-separator
If you need first-run conversion from upstream checkpoints (.ckpt/.onnx/Demucs weights), install conversion extras:
pip install "mlx-audio-separator[convert]"
Quick Start
CLI
# Separate with default model
mlx-audio-separator song.mp3
# Use a specific model
mlx-audio-separator song.mp3 -m htdemucs_ft.yaml
# List supported models
mlx-audio-separator --list_models
Python
from mlx_audio_separator import Separator
sep = Separator()
sep.load_model()
outputs = sep.separate("song.mp3")
print(outputs)
Supported Architectures
- Roformer (BS-Roformer and MelBand-Roformer families)
- MDXC (including MDX23C-style checkpoints)
- MDX
- VR
- Demucs
Demucs cache security and migration
Demucs conversion stores MLX weights as <model>.safetensors with a validated
<model>_config.json sidecar. The loader verifies that the two files match
before constructing a model. Legacy <model>_mlx.pkl caches are never
deserialized. When automatic conversion is enabled they are left untouched and
replaced by newly generated safe files from the official Demucs source.
To regenerate a cache explicitly:
pip install "mlx-audio-separator[convert]"
python -m mlx_audio_separator.demucs_mlx.mlx_convert htdemucs \
--output-dir ~/.cache/mlx-audio-separator/demucs
Converted Demucs weights live in ~/.cache/mlx-audio-separator/demucs.
MLX_AUDIO_SEPARATOR_DEMUCS_CACHE_DIR points that elsewhere. Caches left in the
older ~/.cache/demucs-mlx by a release before 0.1.12 are still read, so
upgrading does not cost a reconversion; nothing is written there any more,
because the demucs-mlx package uses that directory for its own differently
shaped cache.
Official Demucs downloads retain their filename hash checks and are loaded with PyTorch's restricted weight-only deserializer. This trusts the installed PyTorch, Demucs, NumPy, and optional DiffQ implementations plus the official model registry; arbitrary checkpoint globals and local pickle caches are not trusted.
Validation Snapshot
Release validation snapshot (2026-02-24 to 2026-02-26):
| Check | Result |
|---|---|
| Full-catalog benchmark gate | 163/163 models ok (0 failures) |
| Unit tests | 167 passed, 1 skipped |
MLX vs audio-separator parity smoke |
4/4 models passed (rel L2 <= 5e-2) |
Scope: Apple Silicon (M4 mini), MUSDB18-HQ test subset, release gate + parity smoke model set.
Detailed evidence and provenance: docs/release-validation.md.
Performance Snapshot
MLX vs audio-separator (ABBA, 12-song MUSDB18-HQ test subset, M4 mini):
| Model | MLX speedup vs PAS |
|---|---|
htdemucs_ft.yaml |
1.40x |
model_bs_roformer_ep_317_sdr_12.9755.ckpt |
2.16x |
mel_band_roformer_instrumental_instv7n_gabox.ckpt |
2.50x |
UVR-MDX-NET-Inst_HQ_3.onnx |
1.53x |
Median speedup across the 4-model overlap set: 1.847x.
These numbers are scoped to the benchmark settings above and are not universal guarantees for all machines, models, or audio inputs.
Stable Runtime Tuning
The defaults are the recommended configuration. Every inference option ships at the value that measured best for both quality and speed, so you should not need to set anything to get good results. See docs/tuning.md for what those values are, the measurements behind them, and the levers that remain for benchmarking or exact-parity work.
Release-facing stable controls:
--cache_clear_policy {aggressive,deferred}— defaults todeferred--write_workers <int>— defaults to2
Both already ship at the value that measured fastest, so the example that used to live here (passing them explicitly) is no longer needed.
--speed_mode is deprecated and ignored. Its latency_safe* profiles now
resolve to the defaults, and it will be removed in the next major version.
Basic benchmark command:
mlx-audio-separator \
--benchmark song.mp3 \
--benchmark_warmup 1 \
--benchmark_repeats 3 \
--benchmark_profile
BS-Roformer-SW Performance (Opt-In)
For BS-Roformer-SW.ckpt, no extra flags are needed:
mlx-audio-separator song.mp3 \
-m BS-Roformer-SW.ckpt \
--output_format FLAC
What --speed_mode latency_safe_v3 used to enable -- deferred cache clearing
plus async stem writes -- is now the default. Measured on a 195 s track to
FLAC it runs ~6-17% faster than the old defaults with bit-identical output.
To avoid repeated checkpoint conversion overhead, pre-convert once to
*.safetensors and exit:
mlx-audio-separator \
-m BS-Roformer-SW.ckpt \
--save_converted_safetensors \
--preconvert_only
safetensors primarily improves model load/startup time. It is not expected to
materially change per-file inference latency.
Validation command (latency + deterministic equivalence):
uv run --with torch python scripts/perf/compare_latency.py \
--corpus-file /tmp/corpus_one.txt \
--baseline-config scripts/perf/configs/bs_roformer_sw_default_baseline.json \
--candidate-config scripts/perf/configs/bs_roformer_sw_latency_safe_v3_candidate.json \
--model-file-dir ~/.cache/mlx-audio-separator/models \
--allow-speed-mode-mismatch \
--target-improvement-demucs-mdxc 10.0 \
--equivalence-check \
--equivalence-threshold-rel-l2 1e-6 \
--output-json /tmp/bs_roformer_sw_latency_safe_v3_compare.json \
--output-markdown /tmp/bs_roformer_sw_latency_safe_v3_compare.md
BS-Roformer-SW Optimization Program (Opt-In Tracks)
As of 0.1.8, what latency_safe_v3 promoted -- deferred cache clearing and
async stem writes -- is the default for every model, and --speed_mode is
deprecated and ignored. Measure candidates against the defaults. All
experimental tracks below remain parked pending new evidence.
Candidate configs for staged exploration live under scripts/perf/configs/:
bs_roformer_sw_cand_grouped_bandmask.jsonbs_roformer_sw_cand_fused_ola.jsonbs_roformer_sw_cand_stream_pipeline.jsonbs_roformer_sw_cand_compile_fullgraph.jsonbs_roformer_sw_cand_flac_fastwrite.json
Corpus manifest templates:
- Quick gate (3 files):
scripts/perf/corpora/bs_roformer_sw_quick.txt - Full gate (12 files):
scripts/perf/corpora/bs_roformer_sw_full.txt
Quick-gate example:
uv run --with torch python scripts/perf/compare_latency.py \
--corpus-file scripts/perf/corpora/bs_roformer_sw_quick.txt \
--baseline-config scripts/perf/configs/bs_roformer_sw_latency_safe_v3_baseline.json \
--candidate-config scripts/perf/configs/bs_roformer_sw_cand_grouped_bandmask.json \
--model-file-dir ~/.cache/mlx-audio-separator/models \
--target-improvement-demucs-mdxc 3.0 \
--equivalence-check \
--equivalence-threshold-rel-l2 1e-6 \
--equivalence-max-files 1 \
--output-json /tmp/bs_roformer_sw_quick_gate.json \
--output-markdown /tmp/bs_roformer_sw_quick_gate.md
Full-gate example:
uv run --with torch python scripts/perf/compare_latency.py \
--corpus-file scripts/perf/corpora/bs_roformer_sw_full.txt \
--baseline-config scripts/perf/configs/bs_roformer_sw_latency_safe_v3_baseline.json \
--candidate-config scripts/perf/configs/bs_roformer_sw_cand_grouped_bandmask.json \
--model-file-dir ~/.cache/mlx-audio-separator/models \
--target-improvement-demucs-mdxc 5.0 \
--equivalence-check \
--equivalence-threshold-rel-l2 1e-6 \
--equivalence-max-files 0 \
--output-json /tmp/bs_roformer_sw_full_gate.json \
--output-markdown /tmp/bs_roformer_sw_full_gate.md
Troubleshooting
Symbol not found: __ZN3mlx4core6astype... when importing, after changing
your MLX version. mlx-audio-io ships a source distribution, so its native
extension is compiled on your machine against whichever MLX was installed at the
time. Upgrading or downgrading MLX afterwards leaves that binary referencing
symbols the new MLX does not export; it fails at import and names the missing
symbol. Rebuild it against the MLX you now run:
# nanobind must match your MLX: 0.31.x -> 2.12.0, 0.32.x -> 2.15.0
pip install "mlx==0.32.2" "nanobind==2.15.0" scikit-build-core cmake ninja delocate
pip install --force-reinstall --no-cache-dir --no-build-isolation \
--no-binary mlx-audio-io mlx-audio-io
cmake and ninja are in that list because --no-build-isolation means pip
installs nothing for the build, and --no-cache-dir matters because pip
otherwise reuses the wheel it built against your previous MLX. The rebuild takes
about ten seconds.
MLX version mismatch: the mlx-audio-io native binary was built against ...
is the same situation caught before it becomes a symbol error. Same fix.
Documentation
| Document | Description |
|---|---|
docs/release-validation.md |
Release evidence snapshot |
docs/release-first.md |
Release execution playbook |
docs/reproducibility.md |
Reproducibility guide |
docs/wave4-opt-in.md |
Wave 4 opt-in/experimental roadmap |
docs/bs-roformer-sw-optimization-program.md |
BS-Roformer-SW candidate gating and promotion table |
CHANGELOG.md |
Changelog |
THIRD_PARTY_NOTICES.md |
Third-party attribution and license notices |
License
This project is MIT licensed.
Acknowledgments
mlx-audio-separator is derived from audio-separator (upstream repo: nomadkaraoke/python-audio-separator, MIT) by beveradb and the nomadkaraoke community. Substantial portions of the architecture, model loading, and separation logic are adapted from that project. If you find this package useful, please also star and support the upstream project.
The models used by this project were trained by the Ultimate Vocal Remover community, primarily @Anjok07 and @aufr33. See THIRD_PARTY_NOTICES.md for full attribution and license details.
Additional references:
Metadata
Release files for mlx-audio-separator 0.1.15
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mlx_audio_separator-0.1.15.tar.gz | 355.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mlx_audio_separator-0.1.15-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 713.5 kB
Release files / mlx_audio_separator-0.1.15.tar.gz
| Download URL | mlx_audio_separator-0.1.15.tar.gz |
|---|---|
| Size | 355.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
386bc952505dd9f242b20a6f400b582d828fc3d90e9b9451c9be4f98b3b349a5
|
|
BLAKE2b-256 checksum How to use checksums |
78f38bf73d26b30a5cc8dadf0d440514ac1971af468e7f7325a02b18a90fa5dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency logRelease files / mlx_audio_separator-0.1.15-py3-none-any.whl
| Download URL | mlx_audio_separator-0.1.15-py3-none-any.whl |
|---|---|
| Size | 357.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4fd608b9294225f9ed87db749cdf9d07025091e22365a730c27639ffedf3dd89
|
|
BLAKE2b-256 checksum How to use checksums |
0b1c6083beb00e5c10c1c16539707de082a4972e3cb8151df5ea8e9b7515af89
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency log