Files
rustytorch/scripts/export_demucs_onnx.py
T
osobhandClaude Opus 4.6 85b77d49f2 Add audio neural layers and model architectures for ClawSample integration
New nn layers:
- ConvTranspose1d with stride, padding, output_padding (9 tests)
- LSTM/BiLSTM with multi-layer support and hidden state (10 tests)

Audio source separation:
- Demucs ONNX inference with segmented overlap-add processing
- Native HtDemucs architecture (encoder/decoder with BiLSTM bottleneck)
- StemType enum: vocals, drums, bass, other, piano, guitar

Audio generation:
- Stable Audio Open ONNX inference scaffold
- GenerationParams (prompt, duration, steps, cfg_scale, seed)

ONNX export scripts:
- export_demucs_onnx.py — Demucs v4 to ONNX with segment chunking
- export_stable_audio_onnx.py — Stable Audio Open components
- export_mert_onnx.py — MERT music understanding transformer

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-17 12:27:12 -07:00

83 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Export Meta's HTDemucs model to ONNX format for use with RustyTorch++.
Usage:
pip install torch demucs onnx
python export_demucs_onnx.py --stems 4 --output models/htdemucs.onnx
python export_demucs_onnx.py --stems 6 --output models/htdemucs_6s.onnx
The exported model expects:
Input: "mix" shape [1, 2, segment_length] (stereo audio at 44100 Hz)
Output: "stems" shape [1, num_stems, 2, segment_length]
Segment length is fixed at export time (default: 441000 = 10 seconds at 44.1 kHz).
The Rust inference code handles longer audio via segmented overlap-add.
"""
import argparse
import os
import torch
import torch.onnx
def export_demucs(num_stems: int, output_path: str, segment_length: int = 441000):
# Import demucs and load the pretrained model
from demucs.pretrained import get_model
model_name = "htdemucs" if num_stems == 4 else "htdemucs_6s"
print(f"Loading {model_name} ({num_stems} stems)...")
model = get_model(model_name)
model.eval()
# Create dummy input: [batch=1, channels=2, samples=segment_length]
dummy_input = torch.randn(1, 2, segment_length)
print(f"Exporting to ONNX: {output_path}")
print(f" Input shape: [1, 2, {segment_length}]")
print(f" Output shape: [1, {num_stems}, 2, {segment_length}]")
os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
torch.onnx.export(
model,
dummy_input,
output_path,
input_names=["mix"],
output_names=["stems"],
opset_version=17,
do_constant_folding=True,
dynamic_axes=None, # Fixed shapes for reliable ONNX inference
)
# Verify the exported model
import onnx
onnx_model = onnx.load(output_path)
onnx.checker.check_model(onnx_model)
file_size_mb = os.path.getsize(output_path) / (1024 * 1024)
print(f"Export complete: {output_path} ({file_size_mb:.1f} MB)")
print(f"Model verified successfully.")
def main():
parser = argparse.ArgumentParser(description="Export HTDemucs to ONNX")
parser.add_argument("--stems", type=int, default=4, choices=[4, 6],
help="Number of stems (4 or 6)")
parser.add_argument("--output", type=str, default=None,
help="Output ONNX file path")
parser.add_argument("--segment-length", type=int, default=441000,
help="Segment length in samples (default: 441000 = 10s at 44.1kHz)")
args = parser.parse_args()
if args.output is None:
name = "htdemucs" if args.stems == 4 else "htdemucs_6s"
args.output = f"models/{name}.onnx"
export_demucs(args.stems, args.output, args.segment_length)
if __name__ == "__main__":
main()