captainspock arisha07 commited on
Commit
b56f9e6
·
0 Parent(s):

Duplicate from arisha07/stem-separator

Browse files

Co-authored-by: Arisha Kumar <arisha07@users.noreply.huggingface.co>

.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz 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
+ onnx/htdemucs_fwd.onnx.data filter=lfs diff=lfs merge=lfs -text
LICENSE.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # License
2
+
3
+ This model is derived from [facebookresearch/demucs](https://github.com/facebookresearch/demucs),
4
+ licensed under the MIT License. No training or fine-tuning was performed — this repository
5
+ contains an ONNX format conversion of the original pretrained `htdemucs.th` checkpoint.
6
+
7
+ ## MIT License (original demucs project)
8
+
9
+ Copyright (c) Facebook, Inc. and its affiliates.
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ > Note: verify this text against the exact `LICENSE` file in the upstream
30
+ > `facebookresearch/demucs` repository before publishing, and update the copyright
31
+ > line if it differs.
README.md ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - onnx
5
+ - webnn
6
+ - audio-source-separation
7
+ - htdemucs
8
+ pipeline_tag: audio-to-audio
9
+ ---
10
+
11
+ # Stem Separator (HTDemucs v4, fwd-only ONNX export) - WebNN
12
+
13
+ ONNX export of Meta's pretrained **HTDemucs v4** (`htdemucs.th`, base/non-fine-tuned checkpoint)
14
+ used by the [WebNN Developer Preview](https://github.com/microsoft/webnn-developer-preview) Stem
15
+ Separator demo. Splits a stereo mix into **drums / bass / other / vocals**, running entirely
16
+ client-side via ONNX Runtime Web with WebNN (NPU/GPU) acceleration, cascading to WebGPU/WASM.
17
+
18
+ ## Files
19
+
20
+ | File | Size | Description |
21
+ |---|---|---|
22
+ | `onnx/htdemucs_fwd.onnx` | ~2.3 MB | Model graph |
23
+ | `onnx/htdemucs_fwd.onnx.data` | ~168 MB | External weights |
24
+
25
+ ## Provenance
26
+
27
+ - **Source checkpoint**: Meta Research's pretrained `htdemucs.th` base checkpoint (signature
28
+ `955717e8`, filename `955717e8-8726e21a.th`) from the
29
+ [facebookresearch/demucs](https://github.com/facebookresearch/demucs) project (MIT license),
30
+ [checkpoint link](https://dl.fbaipublicfiles.com/demucs/hybrid_transformer/955717e8-8726e21a.th).
31
+ No training or fine-tuning was performed. This is a format conversion of an already-trained model.
32
+ - **Conversion**: exported via `convert_htdemucs_fwd_only.py`, a script modeled on the HTDemucs
33
+ conversion utilities from the
34
+ [RyanMetcalfeInt8/Music-Source-Separation-Training](https://github.com/RyanMetcalfeInt8/Music-Source-Separation-Training)
35
+ fork, purpose-built to produce a clean, opset-pinned ONNX file for web/WebNN consumption rather
36
+ than an OpenVINO IR intermediate.
37
+ - **Why fwd-only**: `torch.onnx.export` cannot trace the complex-tensor ops used by HTDemucs's
38
+ STFT/iSTFT stages. Those stages are reimplemented client-side in JavaScript instead, matching the
39
+ original PyTorch implementation within floating-point tolerance.
40
+ - **Conversion command**:
41
+ ```
42
+ python convert_htdemucs_fwd_only.py \
43
+ --config configs/config_musdb18_htdemucs.yaml \
44
+ --checkpoint 955717e8-8726e21a.th \
45
+ --output htdemucs_fwd.onnx
46
+ ```
47
+ The exact script used is included in this repository at
48
+ [`tools/convert_htdemucs_fwd_only.py`](./tools/convert_htdemucs_fwd_only.py) as reproducibility
49
+ collateral (adapted from the fork above, MIT License).
50
+
51
+ ## Input / Output shapes
52
+
53
+ | Tensor | Shape | Description |
54
+ |---|---|---|
55
+ | `x` (input) | `[1, 4, 2048, 336]` | Normalized CAC spectrogram (freq-domain branch) |
56
+ | `xt` (input) | `[1, 2, 343980]` | Normalized stereo waveform (time-domain branch), ~7.8s @ 44.1kHz |
57
+ | `x_out` (output) | `[1, 16, 2048, 336]` | 4 stems × 4-channel CAC spectrogram |
58
+ | `xt_out` (output) | `[1, 8, 343980]` | 4 stems × stereo waveform |
59
+
60
+ Segments are processed with 50% overlap (`SEGMENT_LENGTH=343980`, `OVERLAP=171990`) and
61
+ reassembled with triangular-weighted overlap-add.
62
+
63
+ ## Validation
64
+
65
+ Compared against a Python fp32 reference pipeline (sine-wave input, every intermediate stage dumped):
66
+
67
+ | Stage | Max diff |
68
+ |---|---|
69
+ | STFT | 9.5e-7 |
70
+ | pre_forward | 7.6e-6 |
71
+ | ONNX `fwd` (fp16) | MAE 9e-4 |
72
+ | post_forward | 1.8e-7 |
73
+
74
+ ## Usage
75
+
76
+ See the [Stem Separator demo](https://github.com/microsoft/webnn-developer-preview/tree/main/demos/stem-separator)
77
+ in the WebNN Developer Preview repository for the full client-side pre/post-processing pipeline.
78
+
79
+ ## License
80
+
81
+ MIT - see [LICENSE.md](./LICENSE.md). Derived from
82
+ [facebookresearch/demucs](https://github.com/facebookresearch/demucs) (MIT).
onnx/htdemucs_fwd.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:555f511fb653348a1c9495ab6b6091767d1920798f3fba68629a1fe5b135a45f
3
+ size 2385507
onnx/htdemucs_fwd.onnx.data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:23a7c68669c041363caaef94641e961dc8c4e16b9891cb87f58f0e199a5e8820
3
+ size 168361984
tools/convert_htdemucs_fwd_only.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Export ONLY the fwd(x, xt) branch of HTDemucs to ONNX.
2
+
3
+ This matches what Intel's OpenVINO plugin does: the ONNX contains only the
4
+ transformer/convolution inner network. Pre-processing (STFT, magnitude reshape,
5
+ normalization) and post-processing (iSTFT, mask, de-norm, sum, trim) remain
6
+ outside the ONNX and must be done at inference time.
7
+
8
+ Usage:
9
+ python convert_htdemucs_fwd_only.py \
10
+ --config <repo>/configs/config_musdb18_htdemucs.yaml \
11
+ --checkpoint 955717e8-8726e21a.th \
12
+ --output htdemucs_fwd.onnx
13
+
14
+ Source: modeled on the HTDemucs conversion utilities from the `RyanMetcalfeInt8`
15
+ fork of Music-Source-Separation-Training (https://github.com/RyanMetcalfeInt8/Music-Source-Separation-Training),
16
+ itself forked from https://github.com/ZFTurbo/Music-Source-Separation-Training
17
+ (MIT License, Copyright (c) 2024 Roman Solovyev (ZFTurbo)). Purpose-built to
18
+ produce a clean, opset-pinned ONNX file for web/WebNN consumption. Included here
19
+ as reproducibility collateral for the htdemucs_fwd.onnx model in this repository.
20
+ """
21
+ import argparse
22
+ import sys
23
+ from pathlib import Path
24
+
25
+ import torch
26
+ import torch.nn as nn
27
+ import yaml
28
+ from omegaconf import OmegaConf
29
+
30
+ THIS_DIR = Path(__file__).resolve().parent
31
+ REPO_ROOT = THIS_DIR.parent
32
+ sys.path.insert(0, str(REPO_ROOT))
33
+
34
+ from models.demucs4ht import HTDemucs # noqa: E402
35
+
36
+
37
+ def patch_demucs_for_export() -> None:
38
+ from demucs import hdemucs as hd
39
+ import torch.nn.functional as F
40
+ import typing as tp
41
+
42
+ def pad1d_no_assert(x, paddings: tp.Tuple[int, int], mode: str = 'constant', value: float = 0.):
43
+ length = x.shape[-1]
44
+ padding_left, padding_right = paddings
45
+ if mode == 'reflect':
46
+ max_pad = max(padding_left, padding_right)
47
+ if length <= max_pad:
48
+ extra_pad = max_pad - length + 1
49
+ extra_pad_right = min(padding_right, extra_pad)
50
+ extra_pad_left = extra_pad - extra_pad_right
51
+ paddings = (padding_left - extra_pad_left, padding_right - extra_pad_right)
52
+ x = F.pad(x, (extra_pad_left, extra_pad_right))
53
+ return F.pad(x, paddings, mode, value)
54
+
55
+ hd.pad1d = pad1d_no_assert
56
+ import models.demucs4ht as dmht
57
+ dmht.pad1d = pad1d_no_assert
58
+
59
+
60
+ def build_model_from_config(config_path: Path) -> HTDemucs:
61
+ with open(config_path, 'r', encoding='utf-8') as handle:
62
+ raw = yaml.safe_load(handle)
63
+ numeric_fields = ['dconv_init', 'freq_emb', 'emb_scale', 'rescale',
64
+ 't_dropout', 't_weight_decay', 't_sparsity', 't_hidden_scale',
65
+ 't_weight_pos_embed']
66
+ if 'htdemucs' in raw:
67
+ for key in numeric_fields:
68
+ if key in raw['htdemucs'] and isinstance(raw['htdemucs'][key], str):
69
+ try:
70
+ raw['htdemucs'][key] = float(raw['htdemucs'][key])
71
+ except ValueError:
72
+ pass
73
+ args = OmegaConf.create(raw)
74
+ extra = {
75
+ 'sources': list(args.training.instruments),
76
+ 'audio_channels': args.training.channels,
77
+ 'samplerate': args.training.samplerate,
78
+ 'segment': args.training.segment,
79
+ }
80
+ kw = OmegaConf.to_container(args.htdemucs, resolve=True)
81
+ return HTDemucs(**extra, **kw)
82
+
83
+
84
+ def load_checkpoint(model: nn.Module, ckpt_path: Path) -> None:
85
+ state_dict = torch.load(str(ckpt_path), map_location='cpu', weights_only=False)
86
+ if isinstance(state_dict, dict) and 'state' in state_dict:
87
+ state_dict = state_dict['state']
88
+ missing, unexpected = model.load_state_dict(state_dict, strict=False)
89
+ if missing:
90
+ print(f" [load] missing keys ({len(missing)}): {missing[:5]}")
91
+ if unexpected:
92
+ print(f" [load] unexpected keys ({len(unexpected)}): {unexpected[:5]}")
93
+
94
+
95
+ class FwdWrapper(nn.Module):
96
+ def __init__(self, model: HTDemucs) -> None:
97
+ super().__init__()
98
+ self.model = model
99
+
100
+ def forward(self, x: torch.Tensor, xt: torch.Tensor):
101
+ return self.model.fwd(x, xt)
102
+
103
+
104
+ def main() -> None:
105
+ parser = argparse.ArgumentParser(description=__doc__)
106
+ parser.add_argument('--config', type=Path, required=True)
107
+ parser.add_argument('--checkpoint', type=Path, required=True)
108
+ parser.add_argument('--output', type=Path, required=True)
109
+ parser.add_argument('--segment', type=int, default=343980)
110
+ parser.add_argument('--no-dynamo', action='store_true',
111
+ help='Use the legacy (scripting) exporter instead of dynamo')
112
+ parser.add_argument('--opset', type=int, default=17)
113
+ args = parser.parse_args()
114
+
115
+ print(f"== HTDemucs fwd-only ONNX export ==")
116
+ print(f" config: {args.config}")
117
+ print(f" checkpoint: {args.checkpoint}")
118
+ print(f" output: {args.output}")
119
+ print(f" segment: {args.segment}")
120
+
121
+ patch_demucs_for_export()
122
+
123
+ print("[1/4] Building model + loading checkpoint...")
124
+ model = build_model_from_config(args.config)
125
+ model.eval()
126
+ load_checkpoint(model, args.checkpoint)
127
+
128
+ print("[2/4] Running pre_forward on dummy input to get (x, xt) example tensors...")
129
+ dummy_mix = torch.zeros([1, 2, args.segment], dtype=torch.float32)
130
+ with torch.inference_mode():
131
+ x, xt, std, mean, meant, stdt, z, length, length_pre_pad = model.pre_forward(dummy_mix)
132
+ print(f" [shapes] x={tuple(x.shape)} dtype={x.dtype}")
133
+ print(f" [shapes] xt={tuple(xt.shape)} dtype={xt.dtype}")
134
+
135
+ wrapped = FwdWrapper(model)
136
+ wrapped.eval()
137
+
138
+ print("[3/4] Python forward check...")
139
+ with torch.inference_mode():
140
+ x_out, xt_out = wrapped(x, xt)
141
+ print(f" [shapes] x_out={tuple(x_out.shape)} dtype={x_out.dtype}")
142
+ print(f" [shapes] xt_out={tuple(xt_out.shape)} dtype={xt_out.dtype}")
143
+
144
+ use_dynamo = not args.no_dynamo
145
+ print(f"[4/4] Exporting to ONNX (dynamo={use_dynamo}, opset={args.opset})...")
146
+ args.output.parent.mkdir(parents=True, exist_ok=True)
147
+ with torch.inference_mode():
148
+ if use_dynamo:
149
+ torch.onnx.export(
150
+ wrapped,
151
+ (x, xt),
152
+ str(args.output),
153
+ input_names=['x', 'xt'],
154
+ output_names=['x_out', 'xt_out'],
155
+ dynamo=True,
156
+ report=False,
157
+ opset_version=args.opset,
158
+ )
159
+ else:
160
+ torch.onnx.export(
161
+ wrapped,
162
+ (x, xt),
163
+ str(args.output),
164
+ input_names=['x', 'xt'],
165
+ output_names=['x_out', 'xt_out'],
166
+ opset_version=args.opset,
167
+ do_constant_folding=True,
168
+ )
169
+ size_mb = args.output.stat().st_size / (1024 * 1024)
170
+ print(f" [export] done: {args.output} ({size_mb:.1f} MB)")
171
+
172
+ print("DONE.")
173
+
174
+
175
+ if __name__ == '__main__':
176
+ main()