CoolFace
Apppublic

ASesYusuf1/SESA_Audio_Separation

sourceHugging Facemitupdated 6mo agoView on Hugging Face
14likes
inference_pytorch.py391 linesDownload Raw Back to root
1# coding: utf-82__author__ = 'PyTorch Optimized Inference Implementation'3 4import argparse5import time6import librosa7from tqdm.auto import tqdm8import sys9import os10import glob11import torch12import soundfile as sf13import torch.nn as nn14import numpy as np15import pickle16from assets.i18n.i18n import I18nAuto17 18# Set inference path for compatibility19INFERENCE_PATH = os.path.abspath(__file__)20 21i18n = I18nAuto()22 23current_dir = os.path.dirname(os.path.abspath(__file__))24sys.path.append(current_dir)25 26from utils import get_model_from_config, normalize_audio, denormalize_audio27from utils import prefer_target_instrument, load_start_checkpoint, apply_tta, demix28from pytorch_backend import PyTorchBackend, PyTorchOptimizer, create_inference_session29 30import warnings31warnings.filterwarnings("ignore")32 33 34def shorten_filename(filename, max_length=30):35    """Dosya adını belirtilen maksimum uzunluğa kısaltır."""36    base, ext = os.path.splitext(filename)37    if len(base) <= max_length:38        return filename39    shortened = base[:15] + "..." + base[-10:] + ext40    return shortened41 42 43def get_soundfile_subtype(pcm_type, is_float=False):44    """PCM türüne göre uygun soundfile alt türünü belirler."""45    if is_float:46        return 'FLOAT'47    subtype_map = {48        'PCM_16': 'PCM_16',49        'PCM_24': 'PCM_24',50        'FLOAT': 'FLOAT'51    }52    return subtype_map.get(pcm_type, 'FLOAT')53 54 55def demix_pytorch_optimized(56    config,57    backend: PyTorchBackend,58    mix: np.ndarray,59    device: torch.device,60    pbar: bool = False61) -> dict:62    """63    Optimized PyTorch backend ile audio source separation.64    65    Parameters:66    ----------67    config : ConfigDict68        Configuration object69    backend : PyTorchBackend70        PyTorch backend with optimized model71    mix : np.ndarray72        Input audio array73    device : torch.device74        Computation device75    pbar : bool76        Show progress bar77        78    Returns:79    -------80    dict81        Dictionary of separated sources82    """83    mix = torch.tensor(mix, dtype=torch.float32)84    85    chunk_size = config.audio.chunk_size86    num_instruments = len(prefer_target_instrument(config))87    num_overlap = config.inference.num_overlap88    89    fade_size = chunk_size // 1090    step = chunk_size // num_overlap91    border = chunk_size - step92    length_init = mix.shape[-1]93    94    # Windowing array95    fadein = torch.linspace(0, 1, fade_size)96    fadeout = torch.linspace(1, 0, fade_size)97    windowing_array = torch.ones(chunk_size)98    windowing_array[-fade_size:] = fadeout99    windowing_array[:fade_size] = fadein100    101    # Add padding102    if length_init > 2 * border and border > 0:103        mix = nn.functional.pad(mix, (border, border), mode="reflect")104    105    batch_size = config.inference.batch_size106    use_amp = getattr(config.training, 'use_amp', True)107    108    with torch.cuda.amp.autocast(enabled=use_amp):109        with torch.inference_mode():110            # Initialize result and counter tensors111            req_shape = (num_instruments,) + mix.shape112            result = torch.zeros(req_shape, dtype=torch.float32)113            counter = torch.zeros(req_shape, dtype=torch.float32)114            115            i = 0116            batch_data = []117            batch_locations = []118            119            # Progress reporting for GUI (no terminal tqdm)120            total_samples = mix.shape[1]121            last_reported_percent = -1122            123            while i < mix.shape[1]:124                # Extract chunk125                part = mix[:, i:i + chunk_size].to(device)126                chunk_len = part.shape[-1]127                128                if chunk_len > chunk_size // 2:129                    pad_mode = "reflect"130                else:131                    pad_mode = "constant"132                    133                part = nn.functional.pad(134                    part, 135                    (0, chunk_size - chunk_len), 136                    mode=pad_mode, 137                    value=0138                )139                140                batch_data.append(part)141                batch_locations.append((i, chunk_len))142                i += step143                144                # Process batch145                if len(batch_data) >= batch_size or i >= mix.shape[1]:146                    arr = torch.stack(batch_data, dim=0)147                    148                    # Use optimized PyTorch backend for inference149                    x = backend(arr)150                    151                    window = windowing_array.clone()152                    if i - step == 0:  # First chunk153                        window[:fade_size] = 1154                    elif i >= mix.shape[1]:  # Last chunk155                        window[-fade_size:] = 1156                    157                    for j, (start, seg_len) in enumerate(batch_locations):158                        result[..., start:start + seg_len] += x[j, ..., :seg_len].cpu() * window[..., :seg_len]159                        counter[..., start:start + seg_len] += window[..., :seg_len]160                    161                    batch_data.clear()162                    batch_locations.clear()163                164                # Report real progress percentage for GUI capture (every 1% for smooth updates)165                # Use unique prefix [SESA_PROGRESS] to avoid confusion with other log messages166                current_percent = int((i / total_samples) * 100)167                if current_percent > last_reported_percent:168                    last_reported_percent = current_percent169                    print(f"[SESA_PROGRESS]{current_percent}", flush=True)170            171            print("[SESA_PROGRESS]100", flush=True)172            173            # Compute final estimated sources174            estimated_sources = result / counter175            estimated_sources = estimated_sources.cpu().numpy()176            np.nan_to_num(estimated_sources, copy=False, nan=0.0)177            178            # Remove padding179            if length_init > 2 * border and border > 0:180                estimated_sources = estimated_sources[..., border:-border]181    182    # Return as dictionary183    instruments = prefer_target_instrument(config)184    ret_data = {k: v for k, v in zip(instruments, estimated_sources)}185    186    return ret_data187 188 189def run_folder_pytorch_optimized(backend, args, config, device, model=None, verbose: bool = False):190    """191    PyTorch backend ile klasör işleme.192    """193    start_time = time.time()194    195    mixture_paths = sorted(glob.glob(os.path.join(args.input_folder, '*.*')))196    sample_rate = getattr(config.audio, 'sample_rate', 44100)197    198    print(f"PyTorch Backend | {len(mixture_paths)} dosya | SR: {sample_rate}")199    200    instruments = prefer_target_instrument(config)[:]201    202    # Çıktı klasörünü kullan203    store_dir = args.store_dir204    os.makedirs(store_dir, exist_ok=True)205    206    # Progress is reported via print statements for GUI capture (no terminal tqdm)207    total_files = len(mixture_paths)208    detailed_pbar = not args.disable_detailed_pbar209    print(i18n("detailed_pbar_enabled").format(detailed_pbar))210    211    for file_idx, path in enumerate(mixture_paths):212        try:213            mix, sr = librosa.load(path, sr=sample_rate, mono=False)214            print(i18n("loaded_audio").format(path, mix.shape))215        except Exception as e:216            print(i18n("cannot_read_track").format(path))217            print(i18n("error_message").format(str(e)))218            continue219        220        mix_orig = mix.copy()221        if 'normalize' in config.inference:222            if config.inference['normalize'] is True:223                mix, norm_params = normalize_audio(mix)224        225        # Use optimized PyTorch backend226        waveforms_orig = demix_pytorch_optimized(config, backend, mix, device, pbar=detailed_pbar)227        228        if args.use_tta and model is not None:229            waveforms_orig = apply_tta(config, model, mix, waveforms_orig, device, args.model_type)230 231        if args.demud_phaseremix_inst and model is not None:232            print(f"DemudPhaseRemix: {path}")233            instr = 'vocals' if 'vocals' in instruments else instruments[0]234            instruments.append('instrumental_phaseremix')235            if 'instrumental' not in instruments and 'Instrumental' not in instruments:236                mix_modified = mix_orig - 2 * waveforms_orig[instr]237                mix_modified_ = mix_modified.copy()238                waveforms_modified = demix(config, model, mix_modified, device, model_type=args.model_type)239                if args.use_tta:240                    waveforms_modified = apply_tta(config, model, mix_modified, waveforms_modified, device, args.model_type)241                waveforms_orig['instrumental_phaseremix'] = mix_orig + waveforms_modified[instr]242            else:243                mix_modified = 2 * waveforms_orig[instr] - mix_orig244                mix_modified_ = mix_modified.copy()245                waveforms_modified = demix(config, model, mix_modified, device, model_type=args.model_type)246                if args.use_tta:247                    waveforms_modified = apply_tta(config, model, mix_modified, waveforms_orig, device, args.model_type)248                waveforms_orig['instrumental_phaseremix'] = mix_orig + mix_modified_ - waveforms_modified[instr]249 250        if args.extract_instrumental:251            instr = 'vocals' if 'vocals' in instruments else instruments[0]252            waveforms_orig['instrumental'] = mix_orig - waveforms_orig[instr]253            if 'instrumental' not in instruments:254                instruments.append('instrumental')255        256        for instr in instruments:257            estimates = waveforms_orig[instr]258            if 'normalize' in config.inference:259                if config.inference['normalize'] is True:260                    estimates = denormalize_audio(estimates, norm_params)261            262            is_float = getattr(args, 'export_format', '').startswith('wav FLOAT')263            codec = 'flac' if getattr(args, 'flac_file', False) else 'wav'264            if codec == 'flac':265                subtype = get_soundfile_subtype(args.pcm_type, is_float)266            else:267                subtype = get_soundfile_subtype('FLOAT', is_float)268            269            shortened_filename = shorten_filename(os.path.basename(path))270            output_filename = f"{shortened_filename}_{instr}.{codec}"271            output_path = os.path.join(store_dir, output_filename)272            sf.write(output_path, estimates.T, sr, subtype=subtype)273    274    print(i18n("elapsed_time").format(time.time() - start_time))275 276 277def proc_folder_pytorch_optimized(args):278    """279    PyTorch ile inference işleme fonksiyonu.280    """281    parser = argparse.ArgumentParser(description="PyTorch Inference for Music Source Separation")282    parser.add_argument("--model_type", type=str, default='mdx23c', help="Model type")283    parser.add_argument("--config_path", type=str, help="Config path")284    parser.add_argument("--start_check_point", type=str, default='', help="Checkpoint path (.ckpt)")285    parser.add_argument("--input_folder", type=str, help="Input folder path")286    parser.add_argument("--store_dir", type=str, default="", help="Output directory")287    parser.add_argument("--device_ids", nargs='+', type=int, default=0, help="Device IDs")288    parser.add_argument("--extract_instrumental", action='store_true', help="Extract instrumental")289    parser.add_argument("--disable_detailed_pbar", action='store_true', help="Disable detailed progress bar")290    parser.add_argument("--flac_file", action='store_true', help="Output as FLAC")291    parser.add_argument("--export_format", type=str, choices=['wav FLOAT', 'flac PCM_16', 'flac PCM_24'], 292                        default='flac PCM_24', help="Export format")293    parser.add_argument("--pcm_type", type=str, choices=['PCM_16', 'PCM_24'], default='PCM_24', help="PCM type")294    parser.add_argument("--chunk_size", type=int, default=1000000, help="Inference chunk size")295    parser.add_argument("--overlap", type=int, default=4, help="Inference overlap factor")296    parser.add_argument("--optimize_mode", type=str, choices=['channels_last', 'compile', 'jit', 'default'], 297                        default='channels_last', help="PyTorch optimization mode (channels_last recommended)")298    parser.add_argument("--enable_amp", action='store_true', help="Enable automatic mixed precision (2x faster)")299    parser.add_argument("--enable_tf32", action='store_true', help="Enable TF32 for RTX 30xx+ (faster)")300    parser.add_argument("--enable_cudnn_benchmark", action='store_true', help="Enable cuDNN benchmark (faster after warmup)")301    parser.add_argument("--lora_checkpoint", type=str, default='', help="Initial checkpoint to LoRA weights")302    parser.add_argument("--use_tta", action='store_true', help="Test Time Augmentation (flips + polarity)")303    parser.add_argument("--demud_phaseremix_inst", action='store_true', help="DemudPhaseRemix instrumental extraction")304    305    if args is None:306        args = parser.parse_args()307    else:308        args = parser.parse_args(args)309    310    # Device setup311    device = "cpu"312    if torch.cuda.is_available():313        print(i18n("cuda_available"))314        device = f'cuda:{args.device_ids[0]}' if type(args.device_ids) == list else f'cuda:{args.device_ids}'315    elif torch.backends.mps.is_available():316        device = "mps"317        print("Using MPS (Metal) backend")318    319    print(i18n("using_device").format(device))320    321    # Load model322    model_load_start_time = time.time()323    324    model, config = get_model_from_config(args.model_type, args.config_path)325    326    if args.start_check_point != '':327        try:328            checkpoint = torch.load(args.start_check_point, map_location=device, weights_only=False)329        except (pickle.UnpicklingError, RuntimeError, EOFError) as e:330            error_details = f"""331CHECKPOINT FILE CORRUPTED332 333Error: {str(e)}334 335The checkpoint file appears to be corrupted or was not downloaded correctly.336File: {args.start_check_point}337 338Common causes:339  - File is an HTML page (wrong download URL, e.g., HuggingFace /blob/ instead of /resolve/)340  - Incomplete or interrupted download341  - Network issues during download342  - File system corruption343 344Solution:345  1. Delete the corrupted checkpoint file:346     {args.start_check_point}347  2. Re-run the application - it will automatically re-download the model348  3. If the problem persists, check that your model URL uses /resolve/ not /blob/349     Example: https://huggingface.co/user/repo/resolve/main/model.ckpt350"""351            print(error_details)352            import sys353            sys.exit(1)354        355        # Handle different checkpoint formats356        if isinstance(checkpoint, dict):357            if 'state_dict' in checkpoint:358                state_dict = checkpoint['state_dict']359            elif 'model' in checkpoint:360                state_dict = checkpoint['model']361            elif 'state' in checkpoint:362                state_dict = checkpoint['state']363            else:364                state_dict = checkpoint365        else:366            state_dict = checkpoint367        368        model.load_state_dict(state_dict, strict=False)369        model = model.eval().to(device)370    371    print(i18n("instruments_print").format(config.training.instruments))372    373    # Create optimized PyTorch backend374    backend = create_inference_session(375        model=model,376        device=device,377        optimize_mode=args.optimize_mode,378        enable_amp=args.enable_amp,379        enable_tf32=args.enable_tf32,380        enable_cudnn_benchmark=args.enable_cudnn_benchmark381    )382    383    print(i18n("model_load_time").format(time.time() - model_load_start_time))384    385    # Run inference (pass raw model for TTA/demud support)386    run_folder_pytorch_optimized(backend, args, config, device, model=model, verbose=False)387 388 389if __name__ == "__main__":390    proc_folder_pytorch_optimized(None)391