wet-stems-hd / make_model.py
Reigningwet's picture
Upload 3 files
47a36a5 verified
Raw History Blame Contribute Delete
2.5 kB
"""Rebuilds htdemucs-wet.onnx (the Wet Studio HD stem split model).
Input: htdemucs_fp16weights.onnx from huggingface.co/StemSplitio/htdemucs-onnx
(revision d54ed9eb60e258ea82131c6ee14578628816456a), an MIT export of Meta's
HT-Demucs v4. Why: that export doesn't build on WebGPU in onnxruntime-web
(a double-typed ConstantOfShape in its iSTFT has no kernel there), and its
24,917 nodes take ~37 s to load. With the input shape fixed, everything that
only depends on shapes folds to constants: 1,427 standard-ONNX nodes, a 3 s
load on WebGPU. Big float tensors are then stored as float16 (+ a Cast), so
the file is 174 MB. Output matches the original to ~90 dB SDR.
pip install onnx onnxruntime numpy
python make_model.py htdemucs_fp16weights.onnx htdemucs-wet.onnx
"""
import sys, onnx, numpy as np, onnxruntime as ort
from onnx import numpy_helper, helper, TensorProto
src, dst = sys.argv[1], sys.argv[2]
so = ort.SessionOptions()
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_BASIC # constant folding only: stays standard ONNX
so.optimized_model_filepath = dst + '.folded'
ort.InferenceSession(src, so, providers=['CPUExecutionProvider'])
m = onnx.load(dst + '.folded'); g = m.graph
users = {}
for n in g.node:
for i in n.input: users.setdefault(i, []).append(n.op_type)
def half(keep): # store the float tensors `keep` picks as float16, cast back to float at load
inits, casts = [], []
for t in g.initializer:
if t.data_type == TensorProto.FLOAT and keep(t):
inits.append(numpy_helper.from_array(numpy_helper.to_array(t).astype(np.float16), t.name + '__fp16'))
casts.append(helper.make_node('Cast', [t.name + '__fp16'], [t.name], to=TensorProto.FLOAT, name=t.name + '__cast'))
else:
inits.append(t)
del g.initializer[:]; g.initializer.extend(inits)
nodes = list(g.node); del g.node[:]; g.node.extend(casts + nodes)
# 1) the network's weights (conv/matmul, not the STFT kernels)
half(lambda t: int(np.prod(t.dims)) >= 1024 and users.get(t.name) and all(u in ('Conv', 'ConvTranspose', 'MatMul') for u in users[t.name]) and 'stft' not in t.name.lower())
# 2) the remaining big tensors: STFT kernels and folded embeddings; the iSTFT window envelope stays float32
half(lambda t: int(np.prod(t.dims)) >= 100000 and 'Cast_output' not in t.name)
ops = [o for o in m.opset_import if o.domain == '']; del m.opset_import[:]; m.opset_import.extend(ops)
onnx.checker.check_model(m)
onnx.save(m, dst)