HTDemucs ONNX — INT8 变体与低内存打包
个人实验项目。 对 StemSplitio/htdemucs-onnx
(revision d54ed9e)做的选择性静态 INT8 量化 + FP16 权重存储 + STFT/iSTFT 换成 ONNX DFT,
外加两处针对 onnxruntime-node 运行时内存的等价改写。
生成脚本、技术报告和校准/评测数据暂不提供,这里只发模型文件。接口可能变。
接口
输入 mix (1, 2, 343980) float32,44.1 kHz 立体声(一段 7.8 s);
输出 stems (1, 4, 2, 343980),顺序 drums, bass, other, vocals。与上游一致。
Segment 长度是固定的。长音频自行切段、逐段串行跑;峰值内存只由单段决定, 30 s 和 5 min 一样。
该用哪个
| 文件 | 何时用 | ORT(arena 关)峰值 / 耗时 | OpenVINO 峰值 / 耗时 |
|---|---|---|---|
htdemucs-dft-int8-fp16-final.onnx |
ORT 基准版 | 2.6–3.9 GB / 1.67–1.89 s | 读不了 |
htdemucs-dft-int8-fp16-chunked.onnx |
ORT,注意力分块 | 2.24 GB / 1.86–1.90 s | 读不了 |
split3/ + parts.json |
ORT 下内存最省 | 1.10–1.24 GB / 1.85–2.13 s | 不适用 |
htdemucs-dft-int8-fp16-portable.onnx |
OpenVINO 首选,ORT 也能跑 | 2.6–3.9 GB / 1.84 s | 0.98 GB / 1.22 s |
htdemucs-dft-fp16-transparent.onnx |
零量化风险(不含 INT8),ORT 专用 | 2.6 GB / 1.78 s | 读不了 |
htdemucs-dft-fp16-transparent-portable.onnx |
同上,另可被 OpenVINO 读 | 3.7 GB / 2.02 s | 1.17 GB / 1.54 s |
测量环境:i7-12800H,4 线程,一个 7.8 s segment,onnxruntime-node 1.27 /
openvino-node 2026.3,Linux。ORT 那列全部是 enableCpuMemArena: false;
不关这个开关的话稳态就等于峰值 5 GB。耗时跑间波动约 ±10 %,给区间处即实测跨度。
chunked / split3 / portable 在 ONNX Runtime 下与 final 逐比特一致
(3 个评测片段,0/2751840 个样本有差异);-transparent-portable 与 -transparent
同理。带 -portable 的只是在每个 DFT 后固定了一次输出形状 —— 对 ORT 是空操作,
但 OpenVINO 的 ONNX 前端对 DFT 的形状推断和规范不一致,不做这一步就读不进去。
用法
// onnxruntime-node
const opts = {
executionProviders: ["cpu"],
graphOptimizationLevel: "all",
intraOpNumThreads: 4,
interOpNumThreads: 1,
enableCpuMemArena: false, // 必须:不关的话稳态就是峰值 5 GB
};
const sess = await ort.InferenceSession.create("htdemucs-dft-int8-fp16-final.onnx", opts);
const out = await sess.run({ mix }); // out.stems
// split3:按 parts.json 顺序跑三段,每段只保留下一段声明要的张量
const parts = JSON.parse(fs.readFileSync("split3/parts.json", "utf8"));
const sess3 = [];
for (const p of parts) sess3.push(await ort.InferenceSession.create(`split3/${p.file}`, opts));
let vals = { mix };
for (let i = 0; i < sess3.length; i++) {
const feed = {};
for (const k of parts[i].inputs) feed[k] = vals[k];
const o = await sess3[i].run(feed);
vals = {};
for (const k of parts[i].outputs) vals[k] = o[k];
}
const stems = vals.stems;
// openvino-node
const core = new ov.Core();
core.setProperty({ CACHE_DIR: "./ov-cache" }); // 加载 1.4 s -> 0.63 s
const compiled = await core.compileModel(
await core.readModel("htdemucs-dft-int8-fp16-portable.onnx"),
"AUTO", { PERFORMANCE_HINT: "LATENCY" });
const req = compiled.createInferRequest();
const res = await req.inferAsync([new ov.Tensor(ov.element.f32, [1, 2, 343980], mix)]);
OpenVINO 编译完常驻约 0.95 GB 不归还(丢掉 infer request 也不还);空闲内存敏感的话 ORT 关 arena 的 0.33 GB 仍是最低的。
质量与限制
| 档位 | MUSDB18 平均 SDR | SDR vs 原 FP32 模型 |
|---|---|---|
| 原始 FP32 | 8.37 dB | — |
INT8 档(-final / -chunked / split3 / -portable) |
8.34 dB(−0.03) | 28–39 dB(5 个评测片段:30.2 / 35.9 / 28.2 / 33.2 / 39.4) |
| transparent 档 | 8.37 dB(Δ 0.00) | 123.6 dB —— 数值上等同原模型 |
- 试听测试尚未完成。 SDR 只掉 0.03 dB,但 INT8 会引入一层噪声底;对质量零容忍就用 transparent 档(代价:文件 130 MB、慢约 25 %、内存更高)。
- 量化范围:静态 QDQ,int8 激活 + per-channel int8 权重,只作用于 cross-transformer
相关的
Conv/MatMul/Gemm。频域分支、STFT/iSTFT 和敏感的解码器路径保持 FP32 —— 全量量化会直接摧毁分离质量。120 条排除节点及每条理由见htdemucs-dft-int8-fp16-final.meta.json。 - 校准:percentile 99.99,24 个片段,取自一个私有音乐库,与评测片段来源互不相交。 校准集不随模型发布,所以量化过程无法逐位复现。
- OpenVINO 的输出不是逐比特一致(INT8 执行路径不同),但相对 FP32 参考的误差与 ORT 同量级:30.5 / 36.2 / 28.3 dB vs ORT 的 30.2 / 35.9 / 28.2 dB。换运行时上线前建议自己 再跑一遍评测。
chunked和split3是绕开 ONNX Runtime 分配器行为的手段,换到别的运行时没有收益 (OV 上分块反而更慢更费内存)。- 所有内存/延迟数字都来自单台 x86 CPU 机器,别的 CPU、别的线程数会不一样。
校验
sha256sum -c SHA256SUMS
许可与出处
权重派生自 StemSplitio/htdemucs-onnx
(MIT),其上游为 Meta 的 Demucs(MIT)。
本仓库的转换与量化产物同样以 MIT 发布,见 LICENSE。转发请保留出处。
English summary
Personal experiment. Selective static INT8 quantization of
StemSplitio/htdemucs-onnx
(revision d54ed9e) with FP16 weight storage, the exported STFT/iSTFT Fourier
convolutions replaced by ONNX DFT, plus two rewrites that cut onnxruntime-node
runtime memory. Model files only — the scripts, the technical report and the
calibration/evaluation data are not published.
Input mix (1, 2, 343980) float32 at 44.1 kHz stereo (one 7.8 s segment);
output stems (1, 4, 2, 343980) ordered drums, bass, other, vocals. Segment
length is fixed: split longer audio yourself and run segments sequentially.
Pick a file from the table above. Short version: -portable for OpenVINO (0.98 GB
peak, 1.22 s), split3/ for the lowest memory under ONNX Runtime (1.1 GB), the
transparent pair if you want no quantization risk at all (output numerically
identical to the original FP32 model, 123.6 dB SDR against it). Always set
enableCpuMemArena: false under ONNX Runtime.
Quality: INT8 costs 0.03 dB of MUSDB18 average SDR (8.34 vs 8.37) and sits 28–39 dB below the FP32 model per segment. Listening tests have not been done. Calibration used 24 segments from a private music library that is not published, so the quantization step is not bit-reproducible. Memory and latency were measured on one x86 machine (i7-12800H, 4 threads); expect different numbers elsewhere.
Model tree for itamiArika/htdemucs-int8-memory
Base model
StemSplitio/htdemucs-onnx