ibrhr commited on
Commit
f532e9a
Β·
verified Β·
1 Parent(s): 9f3c2cf

Initial release: fp16 CoreML .mlpackage MDX-Net stem separation models + model card + export tooling

Browse files

UVR MDX-Net (9482, Voc FT, Inst HQ 3) converted to CoreML mlprogram fp16 for ANE/GPU on iOS. Includes export pipeline, LICENSE, example audio.

.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ example.mp3 filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gyoom
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.
22
+
23
+ ---
24
+
25
+ The model weights are derived from the Ultimate Vocal Remover (UVR) MDX-Net
26
+ models by Anjok07 (UVR-MDXNET 9482) and Kimberley Jensen (UVR-MDX-NET Voc FT),
27
+ distributed under the MIT License via https://github.com/TRvlvr/model_repo and
28
+ mirrored at https://huggingface.co/Politrees/UVR_resources. This repository
29
+ redistributes format-converted (ONNX -> CoreML fp16) copies under the same
30
+ MIT terms.
README.md CHANGED
@@ -1,3 +1,117 @@
1
  ---
2
  license: mit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  license: mit
3
+ tags:
4
+ - coreml
5
+ - apple-silicon
6
+ - apple-neural-engine
7
+ - audio-source-separation
8
+ - music-source-separation
9
+ - mdx-net
10
+ - uvr
11
+ - vocals
12
+ - instrumental
13
+ - fp16
14
+ - mlprogram
15
+ - on-device
16
+ pipeline_tag: audio-to-audio
17
  ---
18
+
19
+ # UVR MDX-Net CoreML β€” on-device music stem separation for iOS
20
+
21
+ On-device **vocals / instrumental (music stem) separation** models, converted from the Ultimate Vocal
22
+ Remover (UVR) MDX-Net checkpoints to **CoreML `.mlpackage`** files (fp16, `mlprogram`) for the Apple
23
+ Neural Engine / GPU / CPU on iOS and macOS.
24
+
25
+ Each `.mlpackage` is the **learned core only** β€” the STFT/iSTFT live in your app's DSP, so the whole
26
+ signal pipeline stays under your control and runs entirely on-device. No cloud, no network.
27
+
28
+ ## Models
29
+
30
+ | Model file | Source (UVR MDX-Net) | Stem | Size | `n_fft` | `hop` | `dim_f` |
31
+ | --- | --- | --- | --- | --- | --- | --- |
32
+ | `UVR_MDXNET_9482.mlpackage` | UVR-MDXNET 9482 | **vocals** | ~15 MB | 4096 | 1024 | 2048 |
33
+ | `UVR-MDX-NET-Voc_FT.mlpackage` | UVR-MDX-NET Voc FT | **vocals** | ~32 MB | 6144 | 1024 | 3072 |
34
+ | `UVR-MDX-NET-Inst_HQ_3.mlpackage` | UVR-MDX-NET Inst HQ 3 | **instrumental** | ~32 MB | 6144 | 1024 | 3072 |
35
+
36
+ All three share the same I/O contract: **NCHW**, static `dim_t = 256`, input `[1, 4, dim_f, 256]`
37
+ complex-as-channels `[L_re, L_im, R_re, R_im]` β†’ output of the same shape = the predicted stem's
38
+ spectrogram. Real **fp16** I/O with fp16 compute, `mlprogram`, `minimum_deployment_target = iOS16`.
39
+
40
+ > ⚠️ **Inst HQ 3 inverts the residual.** It is architecturally identical to Voc FT but predicts the
41
+ > **instrumental**, so the free residual is `vocals = mix βˆ’ model(mix)` β€” the opposite polarity from
42
+ > 9482 / Voc FT. A caller that assumes "output = vocals" will get the stems swapped.
43
+
44
+ ## Download
45
+
46
+ ```bash
47
+ # huggingface_hub (Python)
48
+ pip install huggingface_hub
49
+ python - <<'PY'
50
+ from huggingface_hub import snapshot_download
51
+ snapshot_download("gyoom-sa/UVR-MDX-CoreML", local_dir="./UVR-MDX-CoreML")
52
+ PY
53
+
54
+ # or the CLI
55
+ hf download gyoom-sa/UVR-MDX-CoreML --local-dir ./UVR-MDX-CoreML
56
+ ```
57
+
58
+ The `.mlpackage` folders keep their on-disk structure in this repo, so `git clone` / `hf download`
59
+ yields ready-to-compile packages. Add them to your Xcode target (or call
60
+ `MLModel.compileModel(at:)` at runtime) and they compile to `.mlmodelc`.
61
+
62
+ ## Usage (iOS / Swift)
63
+
64
+ The model operates on a **spectrogram**, not raw audio. Build the STFT in your app (Apple's
65
+ Accelerate / vDSP), feed the complex planes to the model, then iSTFT the output. Per model:
66
+ **9482** `n_fft 4096, hop 1024, dim_f 2048`; **Voc FT / Inst HQ 3** `n_fft 6144, hop 1024, dim_f 3072`.
67
+ Use a periodic Hann window, `center=True` reflect padding, **unnormalized** STFT, drop the Nyquist bin
68
+ (`dim_f = n_fft/2`), and pack planes in order `[L_re, L_im, R_re, R_im]` with flat row-major index
69
+ `((plane*dim_f)+bin)*dim_t + frame`.
70
+
71
+ ```swift
72
+ import CoreML
73
+
74
+ // Accelerator is a LOAD-TIME choice. .all == ANE β†’ GPU β†’ CPU (the baked default).
75
+ let config = MLModelConfiguration()
76
+ config.computeUnits = .all // .cpuAndGPU | .cpuOnly also available
77
+ let model = try MLModel(contentsOf: compiledURL, configuration: config)
78
+
79
+ // Per chunk. dim_f = 2048 (9482) or 3072 (Voc FT / Inst HQ 3); dim_t = 256. Real fp16 I/O.
80
+ let input = try MLMultiArray(shape: [1, 4, dimF as NSNumber, dimT as NSNumber],
81
+ dataType: .float16)
82
+ let p = input.dataPointer.bindMemory(to: Float16.self, capacity: input.count)
83
+ // pack the STFT: p[((plane*dimF)+bin)*dimT + frame] = value (plane 0..3 = L_re,L_im,R_re,R_im)
84
+
85
+ let out = try model.prediction(from: MLDictionaryFeatureProvider(dictionary: ["input": input]))
86
+ let stem = out.featureValue(for: "output")!.multiArrayValue! // fp16 [1,4,dim_f,256] β†’ iSTFT
87
+ ```
88
+
89
+ For the stem models, `vocals = model(mix)`; for **Inst HQ 3**, `instrumental = model(mix)` and
90
+ `vocals = mix βˆ’ instrumental`. The equal-power "shift trick" denoise (`0.5Β·model(x) βˆ’ 0.5Β·model(βˆ’x)`)
91
+ is optional.
92
+
93
+ ## Notes & limitations
94
+
95
+ - **STFT is outside the graph.** These are the learned core only; the app must reproduce the host
96
+ DSP contract above (Hann window, unnormalized STFT, reflect padding, Nyquist bin dropped, plane
97
+ order, flat index). Getting the packing wrong silently degrades or swaps the stems.
98
+ - **fp16 is quality-safe for MDX** β€” peak activations sit far below the 65504 fp16 ceiling (measured
99
+ ~560 for 9482 / ~1384 for Voc FT), and the pipeline normalizes the mix, so the fp16 output floor
100
+ (~73 dB) is well above audible noise.
101
+ - **Residual polarity** differs per model (see the Inst HQ 3 warning).
102
+ - These are **format-converted on-device artifacts**, not models loadable in `transformers` or via
103
+ HF Inference. `pipeline_tag: audio-to-audio` is set for task-based discoverability only.
104
+
105
+ ## Reproducibility
106
+
107
+ The exact conversion pipeline (`export_mdx_coreml.py` + pinned `requirements.txt`) and the full
108
+ technical write-up (precision, compute units, verification SNR, iOS DSP contract) live in the
109
+ [`export/`](./export/README.md) folder of this repo. `example.mp3` is provided for a quick smoke test.
110
+
111
+ ## License & attribution
112
+
113
+ The model weights are derived from the Ultimate Vocal Remover (UVR) MDX-Net models by **Anjok07**
114
+ (UVR-MDXNET 9482) and **Kimberley Jensen** (UVR-MDX-NET Voc FT / Inst HQ 3), distributed under the
115
+ MIT License via https://github.com/TRvlvr/model_repo and mirrored at
116
+ https://huggingface.co/Politrees/UVR_resources. This repository redistributes format-converted
117
+ (ONNX β†’ CoreML fp16) copies under the same MIT terms. See [`LICENSE`](./LICENSE).
UVR-MDX-NET-Inst_HQ_3.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fe4055846ccddb4a04765feb20ec7fe6530624383323782a91d125f760f05ac4
3
+ size 115669
UVR-MDX-NET-Inst_HQ_3.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0a687c627a66e8dc3dc6947e31d35526164f234757208624506ff2aaf47bd1a5
3
+ size 33377792
UVR-MDX-NET-Inst_HQ_3.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "95ff6d20-7277-4643-b3ca-76a3abbfb95a": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "c2c2bdc2-b64a-4519-94aa-8b8cd087b7c5": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "95ff6d20-7277-4643-b3ca-76a3abbfb95a"
18
+ }
UVR-MDX-NET-Voc_FT.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bdd6a5c0b2d3d9eae431f82a58e194c43f08cc3f9da664fdb4daa9d7c4b9bc43
3
+ size 115669
UVR-MDX-NET-Voc_FT.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ee8aeba2251618d8248bfc3bbad49bb5d3b3ee0b6caa47182e08f2e5e3131c22
3
+ size 33377792
UVR-MDX-NET-Voc_FT.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "690b47fa-f6b7-4811-8dc1-b7c5bcc91664": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "8d9d7018-0d95-46da-97b6-330fc9f0f65d": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "690b47fa-f6b7-4811-8dc1-b7c5bcc91664"
18
+ }
UVR_MDXNET_9482.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0512ade072df20bd137e83051cb86dd21d32361bdd90535b94c23224ba569a83
3
+ size 115552
UVR_MDXNET_9482.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c58b40c6767e15c2e31be86eca74ddb72765cc169ca698559812daed0f273a1b
3
+ size 14850048
UVR_MDXNET_9482.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "d57d5b90-d8e8-4c71-a9d2-987177ed2f60": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Weights",
7
+ "name": "weights",
8
+ "path": "com.apple.CoreML/weights"
9
+ },
10
+ "fa44167b-afd1-441a-9edb-1970eee79c0e": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Specification",
13
+ "name": "model.mlmodel",
14
+ "path": "com.apple.CoreML/model.mlmodel"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "fa44167b-afd1-441a-9edb-1970eee79c0e"
18
+ }
example.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1b3783ea92eafc1741e6730bc0dc22b047c56dfcb0defc8af085ec9b5e937f56
3
+ size 2402037
export/README.md ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Export pipeline for the UVR-MDX-CoreML repository
2
+
3
+ This folder is the **one-shot conversion pipeline used to produce the CoreML `.mlpackage` models
4
+ shipped in this repository** (`gyoom-sa/UVR-MDX-CoreML`). It is self-contained: it takes a UVR
5
+ MDX-Net source `.onnx` and emits a GPU/ANE-ready fp16 CoreML `.mlpackage` for iOS β€” nothing here
6
+ depends on anything outside this repo.
7
+
8
+ It reproduces the same **STFT-outside-the-graph** formula for all three models: the `.mlpackage` is
9
+ the learned core only; the STFT/iSTFT stay in the app's own DSP.
10
+
11
+ ## Models
12
+
13
+ Source ONNX (from the [TRvlvr/model_repo](https://github.com/TRvlvr/model_repo) UVR releases):
14
+
15
+ | Source ONNX | Output `.mlpackage` | Size | `dim_f` | Predicted stem |
16
+ | --- | --- | --- | --- | --- |
17
+ | `UVR_MDXNET_9482.onnx` | `UVR_MDXNET_9482.mlpackage` | ~15 MB | 2048 | **vocals** |
18
+ | `UVR-MDX-NET-Voc_FT.onnx` | `UVR-MDX-NET-Voc_FT.mlpackage` | ~32 MB | 3072 | **vocals** |
19
+ | `UVR-MDX-NET-Inst_HQ_3.onnx` | `UVR-MDX-NET-Inst_HQ_3.mlpackage` | ~32 MB | 3072 | **instrumental** |
20
+
21
+ All keep **NCHW** I/O with a static `dim_t = 256`: input `[1, 4, dim_f, 256]`, complex-as-channels
22
+ `[L_re, L_im, R_re, R_im]` β†’ output with the same shape = the predicted stem's spectrogram. **Real
23
+ fp16 I/O**, fp16 compute, `mlprogram`, `minimum_deployment_target = iOS16` (~half the size of an
24
+ fp32 `.mlpackage` because the weights are stored fp16).
25
+
26
+ > **Inst HQ 3 inverts the residual.** It is architecturally identical to Voc FT (same 178 ops, same
27
+ > `dim_f 3072 / n_fft 6144 / hop 1024`, 16.68 M params, no embedded metadata) but it is an *Inst*
28
+ > model: the graph predicts the **instrumental**, so the free residual is `vocals = mix βˆ’ model(mix)`
29
+ > β€” the opposite polarity from 9482/Voc FT. A caller that assumes "output = vocals" gets the stems
30
+ > swapped.
31
+
32
+ ## Pipeline (`export_mdx_coreml.py`)
33
+
34
+ ```
35
+ onnx2torch(onnx) β†’ nn.Module β†’ [SNR-gate vs ONNX Runtime > 100 dB]
36
+ β†’ torch.jit.trace β†’ coremltools.convert(mlprogram, fp16, compute_units, iOS16) β†’ .mlpackage β†’ verify
37
+ ```
38
+
39
+ 1. `onnx2torch(onnx)` β†’ `nn.Module` (a plain conv U-Net: conv / bn / relu / convtranspose / matmul).
40
+ 2. SNR-gate the torch module vs ONNX Runtime (must clear `--min-snr`, default 100 dB).
41
+ 3. `torch.jit.trace` β†’ `coremltools.convert(..., convert_to="mlprogram",
42
+ compute_precision=FLOAT16, compute_units=ALL, minimum_deployment_target=iOS16)`, keeping NCHW I/O
43
+ and real fp16 I/O (`dtype=np.float16`).
44
+ 4. Verify: on-disk spec is an `mlProgram` with input/output `[1,4,dim_f,256]` fp16; on **macOS** also
45
+ a CoreML-vs-ONNX `predict` SNR (see "Verification status").
46
+
47
+ **Why `onnx2torch` (not a direct ONNX→CoreML path).** coremltools dropped its ONNX front-end years
48
+ ago; the supported route is Torch→CoreML. `onnx2torch` bridges our ONNX to a torch `nn.Module`,
49
+ which traces and converts cleanly, keeping the conversion input numerically faithful to the ONNX.
50
+
51
+ ## Precision β€” fp16, and why it's safe
52
+
53
+ `compute_precision=FLOAT16` runs the whole graph in fp16 (the ~2Γ— lever, native to the ANE and GPU).
54
+ MDX has no whole-tensor reduction β€” only per-channel BatchNorm that folds into the conv β€” so nothing
55
+ overflows the 65504 fp16 ceiling (peak activation ~560 for 9482 / ~1384 for Voc FT, measured). **I/O
56
+ is real fp16 too**, which is quality-safe for the same reason: the app's pipeline normalizes the mix,
57
+ so the input spectrogram sits far below the ceiling. Real fp16 I/O is the fastest, most idiomatic
58
+ config for an ANE/GPU model β€” no fp32↔fp16 boundary cast, half the I/O bandwidth on the ~2M-element
59
+ tensors.
60
+
61
+ ## Compute units β€” a LOAD-TIME choice
62
+
63
+ `--compute-units all` bakes `ComputeUnit.ALL` (ANE + GPU + CPU) as the model's default, but on iOS the
64
+ real selector is **load-time**: `MLModelConfiguration.computeUnits`. `.all` lets CoreML place ops on
65
+ the **Apple Neural Engine** first (fastest + most power-efficient on iPhone/iPad), then GPU, then CPU.
66
+ Use `.cpuAndGPU` to force the GPU and `.cpuOnly` as the CPU floor. The `--compute-units` flag here
67
+ only affects the Python `predict` default; the shipping app chooses per its own config (see below).
68
+
69
+ ## Pinned environment
70
+
71
+ ```
72
+ Python 3.11
73
+ coremltools 9.0
74
+ onnx2torch 1.5.15
75
+ torch 2.9.1+cpu (via the pytorch cpu index)
76
+ onnx, onnxruntime, numpy
77
+ ```
78
+
79
+ Rebuild from [`requirements.txt`](requirements.txt):
80
+
81
+ ```bash
82
+ python3.11 -m venv .venv
83
+ .venv/bin/pip install --extra-index-url https://download.pytorch.org/whl/cpu -r requirements.txt
84
+ ```
85
+
86
+ The `--extra-index-url` is REQUIRED: torch is pinned to the `+cpu` wheel and must resolve from the
87
+ pytorch cpu index, otherwise pip pulls the multi-GB CUDA build (and its `nvidia-*` deps). Pinning
88
+ torch in the same resolve also stops pip from silently upgrading it to a CUDA torch.
89
+
90
+ > coremltools 9.0 prints "Torch 2.9.1 has not been tested (2.7.0 is newest tested)" β€” a generic
91
+ > warning; it converts the MDX conv U-Net cleanly regardless (verified, torch-vs-ONNX SNR ~108–112
92
+ > dB).
93
+
94
+ ## Run
95
+
96
+ ```bash
97
+ PY=.venv/bin/python
98
+ $PY export_mdx_coreml.py ../UVR_MDXNET_9482.onnx output/UVR_MDXNET_9482.mlpackage
99
+ $PY export_mdx_coreml.py ../UVR-MDX-NET-Voc_FT.onnx output/UVR-MDX-NET-Voc_FT.mlpackage
100
+ $PY export_mdx_coreml.py ../UVR-MDX-NET-Inst_HQ_3.onnx output/UVR-MDX-NET-Inst_HQ_3.mlpackage --min-snr 90
101
+ # options: --compute-units {all,cpuAndGPU,cpuAndNE,cpuOnly} --deployment-target {iOS15..iOS18} --min-snr
102
+ ```
103
+
104
+ The source `.onnx` files are the TRvlvr UVR releases (see the models table above); point the script
105
+ at your download of each.
106
+
107
+ **Why Inst HQ 3 needs `--min-snr 90`.** Its onnx2torch fidelity is **95.4 dB**, below the 100 dB
108
+ default. That is not a broken conversion: the error is dense and unstructured with a
109
+ *scale-invariant* absolute magnitude (~1.8e-4 max, unchanged from gaussianΓ—1 to gaussianΓ—0.05 inputs)
110
+ β€” plain fp32 accumulation round-off. Inst HQ 3 simply carries ~4Γ— larger activations than Voc FT
111
+ (peak ~9.8 vs ~2.4), so the same relative round-off reads as a lower RMS SNR. A real op-level defect
112
+ (the failure mode this gate exists to catch) lands at 10–30 dB, not 95. For context the **shipped
113
+ artifact is fp16**, whose output-quantization floor is ~73 dB, so a 100 dB gate on the fp32
114
+ intermediate is stricter than the deliverable can ever be; 90 dB still sits ~17 dB above that floor.
115
+
116
+ ## Verification status
117
+
118
+ - **onnx2torch fidelity (any OS):** torch-module vs ONNX Runtime SNR β‰ˆ **108 dB** (9482) / **112 dB**
119
+ (Voc FT) β€” the CoreML conversion input is faithful. Gated at 100 dB.
120
+ - **Structural (any OS):** on-disk `.mlpackage` is an `mlProgram`, I/O `[1,4,dim_f,256]` fp16.
121
+ - **Numeric CoreML fidelity + on-device (macOS-pending):** `model.predict()` needs the CoreML
122
+ runtime, which is **macOS-only** β€” on Linux the script reports it as `MACOS-PENDING`. Re-run
123
+ `export_mdx_coreml.py` on a Mac to get the CoreML-vs-ONNX predict SNR, and measure the ANE/GPU RTF
124
+ on-device.
125
+
126
+ ## iOS integration (the contract to port)
127
+
128
+ The `.mlpackage` is only the learned core β€” an iOS app must reproduce the **same** host DSP the
129
+ shipped Android app uses: periodic Hann window, `center=True` reflect padding, **unnormalized** STFT
130
+ (`torch.stft normalized=False`), **Nyquist bin dropped** (`dim_f = n_fft/2`), plane order
131
+ `[L_re, L_im, R_re, R_im]`, flat row-major index `((plane*dim_f)+bin)*dim_t + frame`. Per model:
132
+ **9482** `n_fft 4096, hop 1024, dim_f 2048`; **Voc FT / Inst HQ 3** `n_fft 6144, hop 1024,
133
+ dim_f 3072`. The pipeline (chunk at 10% overlap β†’ STFT β†’ model β†’ iSTFT β†’ overlap-add;
134
+ instrumental = mix βˆ’ vocals for the stem models, the inverse for Inst HQ 3) is the standard
135
+ MDX separation loop. Port the STFT/iSTFT to Accelerate/vDSP; the model call is:
136
+
137
+ ```swift
138
+ import CoreML
139
+
140
+ // Load with the accelerator policy. `.all` == the baked default (ANE β†’ GPU β†’ CPU). Xcode compiles the
141
+ // .mlpackage into a .mlmodelc when it's added to the target (or MLModel.compileModel(at:) at runtime).
142
+ let config = MLModelConfiguration()
143
+ config.computeUnits = .all // GPU-only: .cpuAndGPU Β· CPU floor: .cpuOnly
144
+ let model = try MLModel(contentsOf: compiledURL, configuration: config)
145
+
146
+ // Per chunk. dim_f = 2048 (9482) or 3072 (Voc FT / Inst HQ 3); dim_t = 256. Real fp16 I/O.
147
+ let input = try MLMultiArray(shape: [1, 4, dimF as NSNumber, dimT as NSNumber], dataType: .float16)
148
+ let p = input.dataPointer.bindMemory(to: Float16.self, capacity: input.count)
149
+ // pack the STFT: p[((plane*dimF)+bin)*dimT + frame] = value (plane 0..3 = L_re,L_im,R_re,R_im)
150
+ let out = try model.prediction(from: MLDictionaryFeatureProvider(dictionary: ["input": input]))
151
+ let stems = out.featureValue(for: "output")!.multiArrayValue! // fp16 [1,4,dim_f,256] β†’ iSTFT
152
+ ```
153
+
154
+ > `MLMultiArray` is row-major for the given shape, so the flat index above matches the packing
155
+ > exactly. The UVR "shift trick" denoise (`0.5Β·model(x) βˆ’ 0.5Β·model(βˆ’x)`) is optional. Remember the
156
+ > **Inst HQ 3 residual inversion** (`vocals = mix βˆ’ model(mix)`) β€” see the models table.
157
+
158
+ The repository root `README.md` is the user-facing entry point; this document is the conversion
159
+ tooling reference for re-exporting or auditing the shipped artifacts.
export/export_mdx_coreml.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """One-shot: a UVR MDX-Net .onnx -> a GPU/ANE-ready fp16 CoreML .mlpackage for iOS.
3
+
4
+ Independent STFT-outside-the-graph export: the STFT/iSTFT stay OUTSIDE the graph (in the app's own
5
+ DSP), so the .mlpackage is the learned core only, with the ONNX's
6
+ NCHW I/O kept intact β€” input [1, 4, dim_f, 256] complex-as-channels [L_re, L_im, R_re, R_im] -> output
7
+ same shape = the vocal spectrogram.
8
+
9
+ Pipeline (each step gated so a bad export fails loudly, not silently):
10
+ 1. onnx2torch(onnx) -> nn.Module
11
+ 2. SNR-gate the torch module vs ONNX Runtime (must be > --min-snr, default 100 dB)
12
+ 3. torch.jit.trace -> coremltools.convert(mlprogram, fp16, compute_units, iOS target) -> .mlpackage
13
+ 4. verify: input shape [1,4,dim_f,256] from the saved spec; on macOS also a CoreML-vs-ONNX predict SNR.
14
+
15
+ Why no op-level surgery. CoreML's converter and its Metal-GPU / Apple-Neural-Engine backends lower
16
+ `ConvTranspose + ReLU` natively, so targeting `mlprogram` is a single clean convert β€” no op-version
17
+ patching, no post-conversion fixes.
18
+
19
+ fp16 (`compute_precision=FLOAT16`) is the ~2x lever and is MDX-safe (peak activation << the 65504 fp16
20
+ ceiling; MDX has no whole-tensor reduction). The Apple Neural Engine is fp16-native.
21
+
22
+ Compute units are a LOAD-TIME choice on-device (Swift `MLModelConfiguration.computeUnits`). Passing
23
+ `--compute-units` here only sets the default the Python `predict` uses on macOS; the shipped iOS app
24
+ picks the accelerator itself. Default is ALL (ANE + GPU + CPU) β€” see README.
25
+
26
+ Env (pinned, see requirements.txt): coremltools 9.0, onnx2torch 1.5.15, torch 2.9.1+cpu, onnx,
27
+ onnxruntime, numpy, Python 3.11. NOTE: converting runs anywhere; `predict()` (the numeric CoreML check
28
+ in step 4) is macOS-only β€” on Linux the .mlpackage is produced and structurally checked, and the
29
+ CoreML-vs-ONNX SNR is reported as macOS-pending.
30
+
31
+ Usage:
32
+ python export_mdx_coreml.py UVR_MDXNET_9482.onnx UVR_MDXNET_9482.mlpackage
33
+ python export_mdx_coreml.py UVR-MDX-NET-Voc_FT.onnx UVR-MDX-NET-Voc_FT.mlpackage
34
+ """
35
+ import argparse
36
+ import os
37
+ import platform
38
+ import sys
39
+ import types
40
+ import warnings
41
+
42
+ warnings.filterwarnings("ignore")
43
+ os.environ.setdefault("GRPC_VERBOSITY", "NONE")
44
+
45
+ # onnx2torch eagerly imports torchvision; MDX uses no vision ops, so stub it out when the real package
46
+ # is missing or ABI-broken in the conversion env.
47
+ try:
48
+ import torchvision # noqa: F401
49
+ except Exception:
50
+ _tv = types.ModuleType("torchvision")
51
+ _tv.__version__ = "0.0.0"
52
+ _ops = types.ModuleType("torchvision.ops")
53
+
54
+ def _stub(*a, **k):
55
+ raise NotImplementedError("torchvision op unavailable (stub)")
56
+
57
+ for _n in ("nms", "batched_nms", "roi_align", "RoIAlign", "DeformConv2d", "deform_conv2d"):
58
+ setattr(_ops, _n, _stub)
59
+ _tv.ops = _ops
60
+ sys.modules["torchvision"] = _tv
61
+ sys.modules["torchvision.ops"] = _ops
62
+
63
+ import numpy as np
64
+ import onnx
65
+ import onnx2torch
66
+ import onnxruntime as ort
67
+ import torch
68
+ import coremltools as ct
69
+
70
+ DIM_T = 256 # native trained segment (~5.92 s @ hop 1024); static in the shipped .mlpackage
71
+
72
+ # --compute-units / --deployment-target string -> coremltools enum.
73
+ COMPUTE_UNITS = {
74
+ "all": ct.ComputeUnit.ALL, # ANE + GPU + CPU (default; best on Apple silicon)
75
+ "cpuAndGPU": ct.ComputeUnit.CPU_AND_GPU,
76
+ "cpuAndNE": ct.ComputeUnit.CPU_AND_NE,
77
+ "cpuOnly": ct.ComputeUnit.CPU_ONLY,
78
+ }
79
+ DEPLOY_TARGETS = {
80
+ "iOS15": ct.target.iOS15,
81
+ "iOS16": ct.target.iOS16,
82
+ "iOS17": ct.target.iOS17,
83
+ "iOS18": ct.target.iOS18,
84
+ }
85
+ IN_NAME = "input" # CaC spectrogram [1, 4, dim_f, 256] (Swift MLFeatureProvider key)
86
+ OUT_NAME = "output" # vocal spectrogram [1, 4, dim_f, 256]
87
+
88
+
89
+ def onnx_dim_f(onnx_path):
90
+ """MDX input is [batch, 4, dim_f, dim_t]; return dim_f (2048 for 9482, 3072 for Voc FT)."""
91
+ dims = onnx.load(onnx_path).graph.input[0].type.tensor_type.shape.dim
92
+ return int(dims[2].dim_value)
93
+
94
+
95
+ def sample(dim_f):
96
+ return np.random.randn(1, 4, dim_f, DIM_T).astype(np.float32)
97
+
98
+
99
+ def snr(ref, got):
100
+ den = np.sqrt((ref ** 2).mean())
101
+ num = np.sqrt(((ref - got) ** 2).mean())
102
+ return 20.0 * np.log10(den / (num + 1e-12))
103
+
104
+
105
+ def module_snr(mod, onnx_path, dim_f, trials=2):
106
+ """Fidelity of the onnx2torch module vs ONNX Runtime β€” the CoreML conversion INPUT. This gate runs
107
+ fully on Linux (no CoreML runtime needed)."""
108
+ sess = ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"])
109
+ iname = sess.get_inputs()[0].name
110
+ worst = 1e9
111
+ for _ in range(trials):
112
+ x = sample(dim_f)
113
+ ref = sess.run(None, {iname: x})[0]
114
+ with torch.no_grad():
115
+ got = mod(torch.from_numpy(x)).numpy()
116
+ worst = min(worst, snr(ref, got))
117
+ return float(worst)
118
+
119
+
120
+ def dir_size_mb(path):
121
+ total = 0
122
+ for root, _, files in os.walk(path):
123
+ for f in files:
124
+ total += os.path.getsize(os.path.join(root, f))
125
+ return total / 1e6
126
+
127
+
128
+ def coreml_predict_snr(mlmodel, onnx_path, dim_f, trials=2):
129
+ """macOS-only: run the compiled CoreML model and compare vs ONNX Runtime. Raises on non-macOS
130
+ (no CoreML runtime), which the caller catches and reports as macOS-pending."""
131
+ sess = ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"])
132
+ iname = sess.get_inputs()[0].name
133
+ worst, oshape = 1e9, None
134
+ for _ in range(trials):
135
+ x = sample(dim_f) # fp32 reference input
136
+ ref = sess.run(None, {iname: x})[0] # fp32 ONNX golden
137
+ # Model I/O is fp16: feed the fp16 view so SNR reflects the true deployed path (fp16 I/O +
138
+ # fp16 compute) vs the fp32 ONNX golden.
139
+ got = mlmodel.predict({IN_NAME: x.astype(np.float16)})[OUT_NAME]
140
+ oshape = tuple(np.asarray(got).shape)
141
+ worst = min(worst, snr(ref, np.asarray(got, dtype=np.float32)))
142
+ return float(worst), oshape
143
+
144
+
145
+ def main():
146
+ ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
147
+ ap.add_argument("onnx", help="source UVR MDX-Net .onnx")
148
+ ap.add_argument("out", help="output .mlpackage")
149
+ ap.add_argument("--min-snr", type=float, default=100.0)
150
+ ap.add_argument("--compute-units", choices=list(COMPUTE_UNITS), default="all",
151
+ help="baked default for Python predict; the iOS app sets this at load time (default: all)")
152
+ ap.add_argument("--deployment-target", choices=list(DEPLOY_TARGETS), default="iOS16")
153
+ args = ap.parse_args()
154
+
155
+ dim_f = onnx_dim_f(args.onnx)
156
+ print(f"[1/4] onnx2torch: {args.onnx} (dim_f={dim_f}, dim_t={DIM_T})", flush=True)
157
+ mod = onnx2torch.convert(args.onnx).eval()
158
+
159
+ msnr = module_snr(mod, args.onnx, dim_f)
160
+ print(f"[2/4] torch-module SNR vs ONNX Runtime = {msnr:.1f} dB", flush=True)
161
+ if msnr < args.min_snr:
162
+ raise SystemExit(f"FAIL: onnx2torch fidelity {msnr:.1f} dB < {args.min_snr} dB (do not ship)")
163
+
164
+ print(f"[3/4] trace + coremltools.convert -> {args.out} "
165
+ f"(mlprogram, fp16, compute_units={args.compute_units}, {args.deployment_target})",
166
+ flush=True)
167
+ example = torch.from_numpy(sample(dim_f))
168
+ with torch.no_grad():
169
+ traced = torch.jit.trace(mod, example)
170
+ # NCHW I/O is kept from the ONNX so the app's CaC packing feeds it directly (no transpose).
171
+ # I/O is real fp16 (dtype `np.float16`, matching the fp16 compute) β€” the fastest, most idiomatic
172
+ # config for an fp16 ANE/GPU model: no fp32<->fp16 boundary cast, half the I/O bandwidth on the
173
+ # ~2M-element tensors per chunk, and the ANE's native precision end to end. It is quality-safe
174
+ # because the pipeline normalizes the mix, so the input spectrogram sits far below the 65504 fp16
175
+ # ceiling (peak activation ~560/1384 measured β€” fp16-safety end to end, extended to
176
+ # the boundary). The Swift side feeds/reads an MLMultiArray(.float16); packing the fp32 host-DSP
177
+ # spectrogram into fp16 (and back) is a trivial element-wise cast. See README "iOS integration".
178
+ mlmodel = ct.convert(
179
+ traced,
180
+ inputs=[ct.TensorType(name=IN_NAME, shape=(1, 4, dim_f, DIM_T), dtype=np.float16)],
181
+ outputs=[ct.TensorType(name=OUT_NAME, dtype=np.float16)],
182
+ convert_to="mlprogram",
183
+ compute_precision=ct.precision.FLOAT16, # fp16: the ~2x lever, MDX-safe; ANE is fp16-native
184
+ compute_units=COMPUTE_UNITS[args.compute_units],
185
+ minimum_deployment_target=DEPLOY_TARGETS[args.deployment_target],
186
+ )
187
+ mlmodel.save(args.out)
188
+
189
+ # [4/4] verify. Input shape comes straight from the saved spec (works on any OS). The numeric
190
+ # CoreML-vs-ONNX SNR needs the CoreML runtime -> macOS only; on Linux report it as pending.
191
+ # Read the ON-DISK artifact's spec (the source of truth for the shippable .mlpackage; the
192
+ # in-memory convert result doesn't finalize the fp16 I/O boundary until serialized).
193
+ spec = ct.models.MLModel(args.out, skip_model_load=True).get_spec()
194
+ in_shape = list(spec.description.input[0].type.multiArrayType.shape)
195
+ out_shape = list(spec.description.output[0].type.multiArrayType.shape)
196
+ # CoreML ArrayFeatureType.ArrayDataType: FLOAT32=65568, FLOAT16=65552, DOUBLE=65600.
197
+ io_dtype = {65568: "fp32", 65552: "fp16", 65600: "fp64"}.get(
198
+ spec.description.input[0].type.multiArrayType.dataType, "?")
199
+ size_mb = dir_size_mb(args.out)
200
+ shape_ok = (in_shape == [1, 4, dim_f, DIM_T]
201
+ and spec.WhichOneof("Type") == "mlProgram"
202
+ and io_dtype == "fp16")
203
+
204
+ csnr, cshape, verified = None, None, False
205
+ try:
206
+ csnr, cshape = coreml_predict_snr(mlmodel, args.onnx, dim_f)
207
+ verified = True
208
+ except Exception as e: # non-macOS (no CoreML runtime) or predict failure
209
+ note = f"{type(e).__name__}: {str(e).splitlines()[0][:120]}" if str(e) else type(e).__name__
210
+
211
+ print(f"[4/4] verify: in={in_shape} out={out_shape or 'unspecified (shape-preserving U-Net)'} | "
212
+ f"io={io_dtype} compute=fp16 | mlprogram | size={size_mb:.1f} MB", flush=True)
213
+ if verified:
214
+ ok = shape_ok and csnr >= args.min_snr and cshape == (1, 4, dim_f, DIM_T)
215
+ print(f" CoreML-vs-ONNX predict SNR = {csnr:.1f} dB (macOS) | out={cshape}", flush=True)
216
+ print(" " + ("PASS β€” GPU/ANE-ready fp16 .mlpackage" if ok else "FAIL β€” do not ship"), flush=True)
217
+ raise SystemExit(0 if ok else 1)
218
+ else:
219
+ ok = shape_ok
220
+ print(f" CoreML predict SNR: MACOS-PENDING (no CoreML runtime here β€” {note})", flush=True)
221
+ print(" re-run this script on macOS to get the on-CoreML fidelity number", flush=True)
222
+ print(" " + ("PASS (structural) β€” .mlpackage produced; numeric CoreML check macOS-pending"
223
+ if ok else "FAIL β€” input shape mismatch"), flush=True)
224
+ raise SystemExit(0 if ok else 1)
225
+
226
+
227
+ if __name__ == "__main__":
228
+ main()
export/requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pinned MDX-Net -> CoreML export toolchain for THIS repository (see README.md).
2
+ # Self-contained: rebuild from this folder, no other repo paths needed.
3
+ #
4
+ # Rebuild (Python 3.11) from the export/ folder:
5
+ # python3.11 -m venv .venv
6
+ # .venv/bin/pip install --extra-index-url https://download.pytorch.org/whl/cpu -r requirements.txt
7
+ #
8
+ # The `--extra-index-url` is REQUIRED: torch is pinned to the +cpu wheel and must resolve from the
9
+ # pytorch cpu index, otherwise pip pulls the multi-GB CUDA build (and its nvidia-* deps). Pinning torch
10
+ # in the same resolve also stops pip from silently upgrading it to a CUDA torch.
11
+ #
12
+ # coremltools 9.0 prints "Torch 2.9.1 has not been tested (2.7.0 is newest tested)" β€” a generic warning;
13
+ # it converts the MDX conv U-Net (conv/bn/relu/convtranspose/matmul) cleanly regardless (verified,
14
+ # torch-vs-ONNX SNR ~108-112 dB).
15
+ #
16
+ # Converting runs on Linux; `model.predict()` (the numeric CoreML-vs-ONNX check in step 4) needs the
17
+ # CoreML runtime and is macOS-only.
18
+ torch==2.9.1+cpu
19
+ coremltools==9.0
20
+ onnx2torch==1.5.15
21
+ onnx
22
+ onnxruntime
23
+ numpy