CoolFace
Modelpublic

ThreadAbort/IndexTTS-Rust

sourceHugging Facemitupdated 10mo agoView on Hugging Face
7likes7downloads
convert_to_onnx.py380 linesDownload Raw Back to tools
1#!/usr/bin/env python32"""3Convert IndexTTS-2 PyTorch models to ONNX format for Rust inference!4 5This script converts the three main models:61. GPT model (gpt.pth) - Autoregressive text-to-semantic generation72. S2Mel model (s2mel.pth) - Semantic-to-mel spectrogram conversion83. BigVGAN - Mel-to-waveform vocoder (already available as ONNX from NVIDIA)9 10Usage:11    python tools/convert_to_onnx.py12 13Output:14    models/gpt.onnx15    models/s2mel.onnx16    models/bigvgan.onnx (if needed, otherwise use NVIDIA's)17 18Why ONNX?19    - Cross-platform: Works on Windows, Linux, macOS, M1/M2 Macs20    - Fast: ONNX Runtime is highly optimized21    - Rust-native: ort crate provides excellent ONNX Runtime bindings22    - No Python: Production inference without Python dependency hell!23 24Author: Aye & Hue @ 8b.is25"""26 27import os28import sys29 30# Setup paths31script_dir = os.path.dirname(os.path.abspath(__file__))32project_root = os.path.dirname(script_dir)33os.chdir(project_root)34 35# Set HF cache36os.environ['HF_HUB_CACHE'] = './checkpoints/hf_cache'37 38print("=" * 70)39print("  IndexTTS-2 PyTorch to ONNX Converter")40print("  For Rust inference with ort crate!")41print("=" * 70)42print()43 44# Check for models45if not os.path.exists("checkpoints/gpt.pth"):46    print("ERROR: Models not found!")47    print("Run: python tools/download_files.py -s huggingface")48    sys.exit(1)49 50import torch51import torch.onnx52import numpy as np53from pathlib import Path54 55# Add reference code to path56sys.path.insert(0, "indextts - REMOVING - REF ONLY")57 58# Create output directory59output_dir = Path("models")60output_dir.mkdir(exist_ok=True)61 62print(f"PyTorch version: {torch.__version__}")63print(f"Output directory: {output_dir}")64print()65 66 67def export_speaker_encoder():68    """69    Export the CAM++ speaker encoder to ONNX.70 71    This model extracts speaker embeddings from reference audio.72    Input: mel spectrogram [batch, n_mels, time]73    Output: speaker embedding [batch, 192]74    """75    print("\n" + "=" * 50)76    print("Exporting Speaker Encoder (CAM++)")77    print("=" * 50)78 79    try:80        from omegaconf import OmegaConf81        from indextts.s2mel.modules.campplus.DTDNN import CAMPPlus82 83        # Load config84        cfg = OmegaConf.load("checkpoints/config.yaml")85 86        # Create model87        model = CAMPPlus(feat_dim=80, embedding_size=192)88 89        # Load weights90        weights_path = "./checkpoints/hf_cache/models--funasr--campplus/snapshots/fb71fe990cbf6031ae6987a2d76fe64f94377b7e/campplus_cn_common.bin"91        if os.path.exists(weights_path):92            state_dict = torch.load(weights_path, map_location='cpu')93            model.load_state_dict(state_dict)94            print(f"Loaded weights from: {weights_path}")95 96        model.eval()97 98        # CAMPPlus expects [batch, time, n_mels] NOT [batch, n_mels, time]!99        # This is the key insight - the model processes time-series of mel features100        dummy_input = torch.randn(1, 100, 80)  # [batch, time, features]101 102        # Verify forward pass works before export103        with torch.no_grad():104            test_output = model(dummy_input)105            print(f"Forward pass works! Output shape: {test_output.shape}")106 107        # Export to ONNX108        output_path = output_dir / "speaker_encoder.onnx"109        torch.onnx.export(110            model,111            dummy_input,112            str(output_path),113            input_names=['mel_spectrogram'],114            output_names=['speaker_embedding'],115            dynamic_axes={116                'mel_spectrogram': {0: 'batch', 1: 'time'},  # time is dim 1!117                'speaker_embedding': {0: 'batch'}118            },119            opset_version=18,  # Use 18+ for latest features120            do_constant_folding=True,121        )122 123        # Verify the export124        import onnx125        onnx_model = onnx.load(str(output_path))126        onnx.checker.check_model(onnx_model)127 128        print(f"✓ Exported: {output_path}")129        print(f"  Input: mel_spectrogram [batch, time, 80]")  # Corrected!130        print(f"  Output: speaker_embedding [batch, 192]")131        print(f"✓ ONNX model verified!")132        return True133 134    except Exception as e:135        print(f"✗ Failed to export speaker encoder: {e}")136        import traceback137        traceback.print_exc()138        return False139 140 141def export_gpt_model():142    """143    Export the GPT autoregressive model to ONNX.144 145    This is the most complex model - generates semantic tokens from text.146    We may need to export it in parts due to KV caching.147 148    Input: text_tokens [batch, seq_len], speaker_embedding [batch, 192]149    Output: semantic_codes [batch, code_len]150    """151    print("\n" + "=" * 50)152    print("Exporting GPT Model (Autoregressive)")153    print("=" * 50)154 155    try:156        from omegaconf import OmegaConf157 158        # Load the full model config159        cfg = OmegaConf.load("checkpoints/config.yaml")160 161        # This is tricky - GPT models with KV caching are hard to export162        # We might need to:163        # 1. Export just the forward pass without caching164        # 2. Or export separate encoder/decoder parts165 166        print("GPT model export is complex due to:")167        print("  - Autoregressive generation with KV caching")168        print("  - Dynamic sequence lengths")169        print("  - Multiple internal components")170        print()171        print("Options:")172        print("  A) Export without KV cache (slower but simpler)")173        print("  B) Export encoder + single-step decoder (efficient)")174        print("  C) Use torch.compile + ONNX tracing")175        print()176 177        # For now, let's try the simpler approach178        from infer_v2 import IndexTTS2179 180        # Load model181        tts = IndexTTS2(182            cfg_path="checkpoints/config.yaml",183            model_dir="checkpoints",184            use_fp16=False,185            device="cpu"186        )187 188        # Get the GPT component189        gpt = tts.gpt190        gpt.eval()191 192        print(f"GPT model loaded: {type(gpt)}")193        print(f"Parameters: {sum(p.numel() for p in gpt.parameters()):,}")194 195        # The GPT model architecture:196        # - Text encoder (embeddings + transformer)197        # - Speaker conditioning198        # - Autoregressive decoder199 200        # Let's export the text encoder first201        output_path = output_dir / "gpt_encoder.onnx"202 203        # Create dummy inputs204        text_tokens = torch.randint(0, 30000, (1, 32), dtype=torch.int64)205 206        # This will likely fail due to complex control flow207        # but let's try!208        print(f"Attempting GPT export (may require modifications)...")209 210        # For now, just report what we learned211        print()212        print("Note: Full GPT export requires modifying the model code")213        print("to remove dynamic control flow. Creating a wrapper...")214 215        return False216 217    except Exception as e:218        print(f"✗ Failed to export GPT: {e}")219        import traceback220        traceback.print_exc()221        return False222 223 224def export_s2mel_model():225    """226    Export the Semantic-to-Mel model (flow matching).227 228    This converts semantic codes to mel spectrograms.229    Input: semantic_codes [batch, code_len], speaker_embedding [batch, 192]230    Output: mel_spectrogram [batch, 80, mel_len]231    """232    print("\n" + "=" * 50)233    print("Exporting S2Mel Model (Flow Matching)")234    print("=" * 50)235 236    try:237        from omegaconf import OmegaConf238 239        cfg = OmegaConf.load("checkpoints/config.yaml")240 241        print("S2Mel model (Diffusion/Flow Matching) is also complex:")242        print("  - Multiple denoising steps (iterative)")243        print("  - CFM (Conditional Flow Matching) requires ODE solving")244        print()245        print("Export strategy:")246        print("  1. Export the single denoising step")247        print("  2. Run iteration loop in Rust")248        print()249 250        return False251 252    except Exception as e:253        print(f"✗ Failed to export S2Mel: {e}")254        import traceback255        traceback.print_exc()256        return False257 258 259def export_bigvgan():260    """261    Export BigVGAN vocoder to ONNX.262 263    Good news: NVIDIA provides pre-trained BigVGAN models!264    Even better: They're designed for easy ONNX export.265 266    Input: mel_spectrogram [batch, 80, mel_len]267    Output: waveform [batch, 1, wave_len]268    """269    print("\n" + "=" * 50)270    print("Exporting BigVGAN Vocoder")271    print("=" * 50)272 273    try:274        # BigVGAN from NVIDIA is easier to export275        # Let's check if we already have it276 277        print("BigVGAN options:")278        print("  1. Use NVIDIA's pre-exported ONNX (recommended)")279        print("     https://github.com/NVIDIA/BigVGAN")280        print()281        print("  2. Export from PyTorch weights (we'll do this)")282        print()283 284        # Try to load BigVGAN285        try:286            from bigvgan import bigvgan287            model = bigvgan.BigVGAN.from_pretrained(288                'nvidia/bigvgan_v2_22khz_80band_256x',289                use_cuda_kernel=False290            )291            model.eval()292            model.remove_weight_norm()  # Important for ONNX!293 294            print(f"BigVGAN loaded from HuggingFace")295            print(f"Parameters: {sum(p.numel() for p in model.parameters()):,}")296 297            # Create dummy input298            dummy_mel = torch.randn(1, 80, 100)299 300            # Export301            output_path = output_dir / "bigvgan.onnx"302            torch.onnx.export(303                model,304                dummy_mel,305                str(output_path),306                input_names=['mel_spectrogram'],307                output_names=['waveform'],308                dynamic_axes={309                    'mel_spectrogram': {0: 'batch', 2: 'mel_length'},310                    'waveform': {0: 'batch', 2: 'wave_length'}311                },312                opset_version=18,  # Use 18+ for latest features313                do_constant_folding=True,314            )315 316            print(f"✓ Exported: {output_path}")317            print(f"  Input: mel_spectrogram [batch, 80, mel_len]")318            print(f"  Output: waveform [batch, 1, wave_len]")319 320            # Verify the export321            import onnx322            onnx_model = onnx.load(str(output_path))323            onnx.checker.check_model(onnx_model)324            print(f"✓ ONNX model verified!")325 326            return True327 328        except ImportError:329            print("bigvgan package not installed, installing...")330            os.system("pip install bigvgan")331            print("Please re-run the script.")332            return False333 334    except Exception as e:335        print(f"✗ Failed to export BigVGAN: {e}")336        import traceback337        traceback.print_exc()338        return False339 340 341def main():342    print("\nStarting ONNX conversion...\n")343 344    results = {}345 346    # Export each component347    results['speaker_encoder'] = export_speaker_encoder()348    results['gpt'] = export_gpt_model()349    results['s2mel'] = export_s2mel_model()350    results['bigvgan'] = export_bigvgan()351 352    # Summary353    print("\n" + "=" * 70)354    print("  CONVERSION SUMMARY")355    print("=" * 70)356 357    for name, success in results.items():358        status = "✓ SUCCESS" if success else "✗ NEEDS WORK"359        print(f"  {name:20} {status}")360 361    print()362 363    if all(results.values()):364        print("All models converted! Ready for Rust inference.")365    else:366        print("Some models need manual intervention.")367        print()368        print("For complex models (GPT, S2Mel), consider:")369        print("  1. Modifying the Python code to remove dynamic control flow")370        print("  2. Using torch.jit.trace with concrete inputs")371        print("  3. Exporting subcomponents separately")372        print("  4. Using ONNX Runtime's transformer optimizations")373 374    print()375    print("Output directory:", output_dir.absolute())376 377 378if __name__ == "__main__":379    main()380