Views
No views yet
Conv/ConvTranspose (full fp16 degrades the
drums and bass stems badly; with convolutions kept fp32 every stem stays at correlation
= 0.9998 against the fp32 reference). Constants are folded before conversion. ~2.4x faster than fp32 and 126MB total instead of 173MB.
htdemucs_p00.onnx ... htdemucs_p20.onnx), cut at encoder /
transformer-layer / decoder sub-block boundaries so each piece is a ~10-30ms GPU
submission. Draining the queue between pieces lets a 30-60Hz UI keep every frame while
separation runs. The fp32 piece chain reproduces the monolithic model bit-exactly;
the shipped fp16 chain measures per-stem correlation >= 0.9998 on real music.htdemucs_split_manifest.json: ordered piece list. Each entry: {file, inputs, outputs}
(ONNX tensor names). Top level also has outputs: {"freq": "x", "time": "xt"}.htdemucs_p00.onnx ... htdemucs_p20.onnx: the pieces. Run strictly in manifest order.mix [1, 2, 343980] fp32 waveform segment (44.1kHz stereo),
mag [1, 4, 2048, 336] fp32 spectrogram (STFT real/imag interleaved per channel,
matching the standard demucs-web preprocessing). Final outputs: x
[1, 4, 4, 2048, 336] (frequency branch) and xt [1, 4, 2, 343980] (time branch),
combined by the usual demucs post-processing (mask/iSTFT + overlap-add).mix/mag, run pieces in order,
merge each piece's outputs back into the map. All boundary tensors are fp32
(keep_io_types). With preferredOutputLocation: 'gpu-buffer' the boundaries never leave
the GPU; dispose them after each segment.1const CPU_NODES = ['/ReduceMean','/Sub','/Pow','/ReduceMean_1','/Clip','/Sqrt','/Add','/Div',
2 '/ReduceMean_2','/Sub_1','/Pow_1','/ReduceMean_3','/Clip_1','/Sqrt_1','/Add_1','/Div_1'];
3const session0 = await ort.InferenceSession.create(piece0, {
4 executionProviders: [{ name: 'webgpu', forceCpuNodeNames: CPU_NODES }],
5 graphOptimizationLevel: 'all',
6 preferredOutputLocation: 'gpu-buffer',
7});
8// pieces 1..20: executionProviders: ['webgpu'], NO forceCpuNodeNamesmix/mag as GPU buffers, create those buffers with COPY_SRC usage
(the CPU-pinned nodes read them back host-side).await device.queue.onSubmittedWorkDone() plus a few ms of
sleep after each piece. For maximum throughput: run pieces back to back (~7% overhead vs
a monolithic session). Never have two run() calls in flight at once.htdemucs weights (single model, not the ft ensemble) via torch.onnx with an fp16-safe
normalization wrapper; the fp32 export is bit-identical in output to
timcsy/demucs-web-onnx htdemucs_embedded.onnx.
Then: ONNX Runtime offline BASIC optimization while still fp32 (constant folding),
convert_float_to_float16(op_block_list=['Conv','ConvTranspose']) per piece, and a
dependency-bounded graph cut into 21 pieces validated bit-exact against the monolith in fp32.