diff --git a/configs/astral_quantization/default_2048.yml b/configs/astral_quantization/default_2048.yml new file mode 100644 index 0000000000000000000000000000000000000000..8045f2dfdbfa7e31223cf2e086921e0063944410 --- /dev/null +++ b/configs/astral_quantization/default_2048.yml @@ -0,0 +1,40 @@ +_target_: modules.astral_quantization.default_model.AstralQuantizer +tokenizer_name: "openai/whisper-small" +ssl_model_name: "facebook/hubert-large-ll60k" +ssl_output_layer: 18 +encoder: + _target_: modules.astral_quantization.convnext.ConvNeXtV2Stage + dim: 512 + num_blocks: 12 + intermediate_dim: 1536 + dilation: 1 + input_dim: 1024 +quantizer: + _target_: modules.astral_quantization.bsq.BinarySphericalQuantize + codebook_size: 2048 # codebook size, must be a power of 2 + dim: 512 + entropy_loss_weight: 0.1 + diversity_gamma: 1.0 + spherical: True + enable_entropy_loss: True + soft_entropy_loss: True +decoder: + _target_: modules.astral_quantization.convnext.ConvNeXtV2Stage + dim: 512 + num_blocks: 12 + intermediate_dim: 1536 + dilation: 1 + output_dim: 1024 + gin_channels: 192 +asr_decoder: + _target_: modules.astral_quantization.asr_decoder.ASRDecoder + hidden_dim: 768 + num_heads: 12 + depth: 12 + block_size: 4096 + in_channels: 512 + n_vocab: 51866 + bos_id: 50528 + eos_id: 50527 + dropout_rate: 0.0 + attn_dropout_rate: 0.0 \ No newline at end of file diff --git a/configs/astral_quantization/default_32.yml b/configs/astral_quantization/default_32.yml new file mode 100644 index 0000000000000000000000000000000000000000..7da34ff4a34c6ae5e84b866fdff2d8d4c0a5865c --- /dev/null +++ b/configs/astral_quantization/default_32.yml @@ -0,0 +1,40 @@ +_target_: default_model.AstralQuantizer +tokenizer_name: "openai/whisper-small" +ssl_model_name: "facebook/hubert-large-ll60k" +ssl_output_layer: 18 +encoder: + _target_: modules.convnext.ConvNeXtV2Stage + dim: 512 + num_blocks: 12 + intermediate_dim: 1536 + dilation: 1 + input_dim: 1024 +quantizer: + _target_: modules.bsq.BinarySphericalQuantize + codebook_size: 32 # codebook size, must be a power of 2 + dim: 512 + entropy_loss_weight: 0.1 + diversity_gamma: 1.0 + spherical: True + enable_entropy_loss: True + soft_entropy_loss: True +decoder: + _target_: modules.convnext.ConvNeXtV2Stage + dim: 512 + num_blocks: 12 + intermediate_dim: 1536 + dilation: 1 + output_dim: 1024 + gin_channels: 192 +asr_decoder: + _target_: modules.asr_decoder.ASRDecoder + hidden_dim: 768 + num_heads: 12 + depth: 12 + block_size: 4096 + in_channels: 512 + n_vocab: 51866 + bos_id: 50528 + eos_id: 50527 + dropout_rate: 0.0 + attn_dropout_rate: 0.0 \ No newline at end of file diff --git a/configs/config.json b/configs/config.json new file mode 100644 index 0000000000000000000000000000000000000000..e74f0b4898f6e47e1d198b62cdba989784ce2bb0 --- /dev/null +++ b/configs/config.json @@ -0,0 +1 @@ +{"reference_audio_path": "D:/FAcodec/test_waves/kobe_0.wav", "sg_hostapi": "MME", "sg_wasapi_exclusive": false, "sg_input_device": "\u9ea6\u514b\u98ce (Razer BlackShark V2 HS 2.4", "sg_output_device": "\u626c\u58f0\u5668 (Razer BlackShark V2 HS 2.4", "sr_type": "sr_model", "diffusion_steps": 10.0, "inference_cfg_rate": 0.0, "max_prompt_length": 3.0, "block_time": 0.7, "crossfade_length": 0.04, "extra_time": 0.5, "extra_time_right": 0.02} \ No newline at end of file diff --git a/configs/hifigan.yml b/configs/hifigan.yml new file mode 100644 index 0000000000000000000000000000000000000000..6faee66d988dc54bcc5f747d048c5f7e2a8b759b --- /dev/null +++ b/configs/hifigan.yml @@ -0,0 +1,25 @@ +hift: + in_channels: 80 + base_channels: 512 + nb_harmonics: 8 + sampling_rate: 22050 + nsf_alpha: 0.1 + nsf_sigma: 0.003 + nsf_voiced_threshold: 10 + upsample_rates: [8, 8] + upsample_kernel_sizes: [16, 16] + istft_params: + n_fft: 16 + hop_len: 4 + resblock_kernel_sizes: [3, 7, 11] + resblock_dilation_sizes: [[1, 3, 5], [1, 3, 5], [1, 3, 5]] + source_resblock_kernel_sizes: [7, 11] + source_resblock_dilation_sizes: [[1, 3, 5], [1, 3, 5]] + lrelu_slope: 0.1 + audio_limit: 0.99 +f0_predictor: + num_class: 1 + in_channels: 80 + cond_channels: 512 + +pretrained_model_path: "checkpoints/hift.pt" diff --git a/configs/presets/config_dit_mel_seed_uvit_whisper_base_f0_44k.yml b/configs/presets/config_dit_mel_seed_uvit_whisper_base_f0_44k.yml new file mode 100644 index 0000000000000000000000000000000000000000..150560475fd7dedad8da76e87480dd36c85ee07f --- /dev/null +++ b/configs/presets/config_dit_mel_seed_uvit_whisper_base_f0_44k.yml @@ -0,0 +1,98 @@ +log_dir: "./runs" +save_freq: 1 +log_interval: 10 +save_interval: 1000 +device: "cuda" +epochs: 1000 # number of epochs for first stage training (pre-training) +batch_size: 1 +batch_length: 100 # maximum duration of audio in a batch (in seconds) +max_len: 80 # maximum number of frames +pretrained_model: "DiT_seed_v2_uvit_whisper_base_f0_44k_bigvgan_pruned_ft_ema.pth" +pretrained_encoder: "" +load_only_params: False # set to true if do not want to load epoch numbers and optimizer parameters + +preprocess_params: + sr: 44100 + spect_params: + n_fft: 2048 + win_length: 2048 + hop_length: 512 + n_mels: 128 + fmin: 0 + fmax: "None" + +model_params: + dit_type: "DiT" # uDiT or DiT + reg_loss_type: "l1" # l1 or l2 + + timbre_shifter: + se_db_path: "./modules/openvoice/checkpoints_v2/converter/se_db.pt" + ckpt_path: './modules/openvoice/checkpoints_v2/converter' + + vocoder: + type: "bigvgan" + name: "nvidia/bigvgan_v2_44khz_128band_512x" + + speech_tokenizer: + type: 'whisper' + name: "openai/whisper-small" + + style_encoder: + dim: 192 + campplus_path: "campplus_cn_common.bin" + + DAC: + encoder_dim: 64 + encoder_rates: [2, 5, 5, 6] + decoder_dim: 1536 + decoder_rates: [ 6, 5, 5, 2 ] + sr: 24000 + + length_regulator: + channels: 768 + is_discrete: false + in_channels: 768 + content_codebook_size: 2048 + sampling_ratios: [1, 1, 1, 1] + vector_quantize: false + n_codebooks: 1 + quantizer_dropout: 0.0 + f0_condition: true + n_f0_bins: 256 + + DiT: + hidden_dim: 768 + num_heads: 12 + depth: 17 + class_dropout_prob: 0.1 + block_size: 8192 + in_channels: 128 + style_condition: true + final_layer_type: 'mlp' + target: 'mel' # mel or codec + content_dim: 768 + content_codebook_size: 1024 + content_type: 'discrete' + f0_condition: true + n_f0_bins: 256 + content_codebooks: 1 + is_causal: false + long_skip_connection: false + zero_prompt_speech_token: false # for prompt component, do not input corresponding speech token + time_as_token: false + style_as_token: false + uvit_skip_connection: true + add_resblock_in_transformer: false + + wavenet: + hidden_dim: 768 + num_layers: 8 + kernel_size: 5 + dilation_rate: 1 + p_dropout: 0.2 + style_condition: true + +loss_params: + base_lr: 0.0001 + lambda_mel: 45 + lambda_kl: 1.0 \ No newline at end of file diff --git a/configs/presets/config_dit_mel_seed_uvit_whisper_small_wavenet.yml b/configs/presets/config_dit_mel_seed_uvit_whisper_small_wavenet.yml new file mode 100644 index 0000000000000000000000000000000000000000..7fc8ec4cb659e9d4856e7b9fa1621ba20796fb08 --- /dev/null +++ b/configs/presets/config_dit_mel_seed_uvit_whisper_small_wavenet.yml @@ -0,0 +1,91 @@ +log_dir: "./runs" +save_freq: 1 +log_interval: 10 +save_interval: 1000 +device: "cuda" +epochs: 1000 # number of epochs for first stage training (pre-training) +batch_size: 2 +batch_length: 100 # maximum duration of audio in a batch (in seconds) +max_len: 80 # maximum number of frames +pretrained_model: "DiT_seed_v2_uvit_whisper_small_wavenet_bigvgan_pruned.pth" +pretrained_encoder: "" +load_only_params: False # set to true if do not want to load epoch numbers and optimizer parameters + +preprocess_params: + sr: 22050 + spect_params: + n_fft: 1024 + win_length: 1024 + hop_length: 256 + n_mels: 80 + fmin: 0 + fmax: "None" + +model_params: + dit_type: "DiT" # uDiT or DiT + reg_loss_type: "l1" # l1 or l2 + + timbre_shifter: + se_db_path: "./modules/openvoice/checkpoints_v2/converter/se_db.pt" + ckpt_path: './modules/openvoice/checkpoints_v2/converter' + + speech_tokenizer: + type: 'whisper' + name: "openai/whisper-small" + + style_encoder: + dim: 192 + campplus_path: "campplus_cn_common.bin" + + vocoder: + type: "bigvgan" + name: "nvidia/bigvgan_v2_22khz_80band_256x" + + length_regulator: + channels: 512 + is_discrete: false + in_channels: 768 + content_codebook_size: 2048 + sampling_ratios: [1, 1, 1, 1] + vector_quantize: false + n_codebooks: 1 + quantizer_dropout: 0.0 + f0_condition: false + n_f0_bins: 512 + + DiT: + hidden_dim: 512 + num_heads: 8 + depth: 13 + class_dropout_prob: 0.1 + block_size: 8192 + in_channels: 80 + style_condition: true + final_layer_type: 'wavenet' + target: 'mel' # mel or codec + content_dim: 512 + content_codebook_size: 1024 + content_type: 'discrete' + f0_condition: false + n_f0_bins: 512 + content_codebooks: 1 + is_causal: false + long_skip_connection: true + zero_prompt_speech_token: false # for prompt component, do not input corresponding speech token + time_as_token: false + style_as_token: false + uvit_skip_connection: true + add_resblock_in_transformer: false + + wavenet: + hidden_dim: 512 + num_layers: 8 + kernel_size: 5 + dilation_rate: 1 + p_dropout: 0.2 + style_condition: true + +loss_params: + base_lr: 0.0001 + lambda_mel: 45 + lambda_kl: 1.0 \ No newline at end of file diff --git a/configs/presets/config_dit_mel_seed_uvit_xlsr_tiny.yml b/configs/presets/config_dit_mel_seed_uvit_xlsr_tiny.yml new file mode 100644 index 0000000000000000000000000000000000000000..d1870d89bb8979fca9e89a29a4c5ebb96a9eceb6 --- /dev/null +++ b/configs/presets/config_dit_mel_seed_uvit_xlsr_tiny.yml @@ -0,0 +1,82 @@ +log_dir: "./runs/" +save_freq: 1 +log_interval: 10 +save_interval: 500 +device: "cuda" +epochs: 1000 # number of epochs for first stage training (pre-training) +batch_size: 2 +batch_length: 100 # maximum duration of audio in a batch (in seconds) +max_len: 80 # maximum number of frames +pretrained_model: "DiT_uvit_tat_xlsr_ema.pth" +pretrained_encoder: "" +load_only_params: False # set to true if do not want to load epoch numbers and optimizer parameters + +preprocess_params: + sr: 22050 + spect_params: + n_fft: 1024 + win_length: 1024 + hop_length: 256 + n_mels: 80 + fmin: 0 + fmax: 8000 + +model_params: + dit_type: "DiT" # uDiT or DiT + reg_loss_type: "l1" # l1 or l2 + diffusion_type: "flow" + + timbre_shifter: + se_db_path: "./modules/openvoice/checkpoints_v2/converter/se_db.pt" + ckpt_path: './modules/openvoice/checkpoints_v2/converter' + + vocoder: + type: "hifigan" + + speech_tokenizer: + type: 'xlsr' + output_layer: 12 + name: 'facebook/wav2vec2-xls-r-300m' + + style_encoder: + dim: 192 + campplus_path: "campplus_cn_common.bin" + + length_regulator: + channels: 384 + is_discrete: false + in_channels: 1024 + content_codebook_size: 1024 + sampling_ratios: [1, 1, 1, 1] + vector_quantize: false + n_codebooks: 2 + quantizer_dropout: 0.0 + f0_condition: false + n_f0_bins: 512 + + DiT: + hidden_dim: 384 + num_heads: 6 + depth: 9 + class_dropout_prob: 0.1 + block_size: 8192 + in_channels: 80 + style_condition: true + final_layer_type: 'mlp' + target: 'mel' # mel or betavae + content_dim: 384 + content_codebook_size: 1024 + content_type: 'discrete' + f0_condition: false + n_f0_bins: 512 + content_codebooks: 1 + is_causal: false + long_skip_connection: false + zero_prompt_speech_token: false # for prompt component, do not input corresponding speech token + time_as_token: true + style_as_token: true + uvit_skip_connection: true + add_resblock_in_transformer: false + +loss_params: + base_lr: 0.0001 \ No newline at end of file diff --git a/configs/v2/vc_wrapper.yaml b/configs/v2/vc_wrapper.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9d254d516f969f3fff27f8477fc5026074a2a59e --- /dev/null +++ b/configs/v2/vc_wrapper.yaml @@ -0,0 +1,105 @@ +_target_: modules.v2.vc_wrapper.VoiceConversionWrapper +sr: 22050 +hop_size: 256 +mel_fn: + _target_: modules.audio.mel_spectrogram + _partial_: true + n_fft: 1024 + win_size: 1024 + hop_size: 256 + num_mels: 80 + sampling_rate: 22050 + fmin: 0 + fmax: null + center: False +cfm: + _target_: modules.v2.cfm.CFM + estimator: + _target_: modules.v2.dit_wrapper.DiT + time_as_token: true + style_as_token: true + uvit_skip_connection: false + block_size: 8192 + depth: 13 + num_heads: 8 + hidden_dim: 512 + in_channels: 80 + content_dim: 512 + style_encoder_dim: 192 + class_dropout_prob: 0.1 + dropout_rate: 0.0 + attn_dropout_rate: 0.0 +cfm_length_regulator: + _target_: modules.v2.length_regulator.InterpolateRegulator + channels: 512 + is_discrete: true + codebook_size: 2048 + sampling_ratios: [ 1, 1, 1, 1 ] + f0_condition: false +ar: + _target_: modules.v2.ar.NaiveWrapper + model: + _target_: modules.v2.ar.NaiveTransformer + config: + _target_: modules.v2.ar.NaiveModelArgs + dropout: 0.0 + rope_base: 10000.0 + dim: 768 + head_dim: 64 + n_local_heads: 2 + intermediate_size: 2304 + n_head: 12 + n_layer: 12 + vocab_size: 2049 # 1 + 1 for eos +ar_length_regulator: + _target_: modules.v2.length_regulator.InterpolateRegulator + channels: 768 + is_discrete: true + codebook_size: 32 + sampling_ratios: [ ] + f0_condition: false +style_encoder: + _target_: modules.campplus.DTDNN.CAMPPlus + feat_dim: 80 + embedding_size: 192 +content_extractor_narrow: + _target_: modules.astral_quantization.default_model.AstralQuantizer + tokenizer_name: "openai/whisper-small" + ssl_model_name: "facebook/hubert-large-ll60k" + ssl_output_layer: 18 + skip_ssl: true + encoder: &bottleneck_encoder + _target_: modules.astral_quantization.convnext.ConvNeXtV2Stage + dim: 512 + num_blocks: 12 + intermediate_dim: 1536 + dilation: 1 + input_dim: 1024 + quantizer: + _target_: modules.astral_quantization.bsq.BinarySphericalQuantize + codebook_size: 32 # codebook size, must be a power of 2 + dim: 512 + entropy_loss_weight: 0.1 + diversity_gamma: 1.0 + spherical: True + enable_entropy_loss: True + soft_entropy_loss: True +content_extractor_wide: + _target_: modules.astral_quantization.default_model.AstralQuantizer + tokenizer_name: "openai/whisper-small" + ssl_model_name: "facebook/hubert-large-ll60k" + ssl_output_layer: 18 + encoder: *bottleneck_encoder + quantizer: + _target_: modules.astral_quantization.bsq.BinarySphericalQuantize + codebook_size: 2048 # codebook size, must be a power of 2 + dim: 512 + entropy_loss_weight: 0.1 + diversity_gamma: 1.0 + spherical: True + enable_entropy_loss: True + soft_entropy_loss: True +vocoder: + _target_: modules.bigvgan.bigvgan.BigVGAN.from_pretrained + pretrained_model_name_or_path: "nvidia/bigvgan_v2_22khz_80band_256x" + use_cuda_kernel: false diff --git a/hf_utils.py b/hf_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..4bd88fa6be55f3eaf4d2cd434bb8757a61cdab5f --- /dev/null +++ b/hf_utils.py @@ -0,0 +1,12 @@ +import os +from huggingface_hub import hf_hub_download + + +def load_custom_model_from_hf(repo_id, model_filename="pytorch_model.bin", config_filename=None): + os.makedirs("./checkpoints", exist_ok=True) + model_path = hf_hub_download(repo_id=repo_id, filename=model_filename, cache_dir="./checkpoints") + if config_filename is None: + return model_path + config_path = hf_hub_download(repo_id=repo_id, filename=config_filename, cache_dir="./checkpoints") + + return model_path, config_path \ No newline at end of file diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/astral_quantization/__init__.py b/modules/astral_quantization/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/astral_quantization/bsq.py b/modules/astral_quantization/bsq.py new file mode 100644 index 0000000000000000000000000000000000000000..239d0caaf370afc77ba62961563903ad675b6d98 --- /dev/null +++ b/modules/astral_quantization/bsq.py @@ -0,0 +1,569 @@ +""" +Lookup Free Quantization +Proposed in https://arxiv.org/abs/2310.05737 + +In the simplest setup, each dimension is quantized into {-1, 1}. +An entropy penalty is used to encourage utilization. +""" + +from math import log2, ceil +from functools import partial, cache +from collections import namedtuple +from contextlib import nullcontext + +import torch.distributed as dist +from torch.distributed import nn as dist_nn + +import torch +from torch import nn, einsum +import torch.nn.functional as F +from torch.nn import Module +from torch.amp import autocast + +from einops import rearrange, reduce, pack, unpack + +# constants + +Return = namedtuple('Return', ['quantized', 'indices', 'entropy_aux_loss']) + +LossBreakdown = namedtuple('LossBreakdown', ['per_sample_entropy', 'batch_entropy', 'commitment']) + +# distributed helpers + +@cache +def is_distributed(): + return dist.is_initialized() and dist.get_world_size() > 1 + +def maybe_distributed_mean(t): + if not is_distributed(): + return t + + dist_nn.all_reduce(t) + t = t / dist.get_world_size() + return t + +# helper functions + +def exists(v): + return v is not None + +def identity(t): + return t + +def default(*args): + for arg in args: + if exists(arg): + return arg() if callable(arg) else arg + return None + +def pack_one(t, pattern): + return pack([t], pattern) + +def unpack_one(t, ps, pattern): + return unpack(t, ps, pattern)[0] + +def l2norm(t): + return F.normalize(t, dim = -1) + +# entropy + +def log(t, eps = 1e-5): + return t.clamp(min = eps).log() + +def entropy(prob): + return (-prob * log(prob)).sum(dim=-1) + +# cosine sim linear + +class CosineSimLinear(Module): + def __init__( + self, + dim_in, + dim_out, + scale = 1. + ): + super().__init__() + self.scale = scale + self.weight = nn.Parameter(torch.randn(dim_in, dim_out)) + + def forward(self, x): + x = F.normalize(x, dim = -1) + w = F.normalize(self.weight, dim = 0) + return (x @ w) * self.scale + +def soft_entropy_loss(u, tau=1.0, gamma=1.0): + """ + Compute the soft entropy loss for Binary Spherical Quantization (BSQ). + + Args: + u (torch.Tensor): Input latent embeddings of shape (batch_size, L). + tau (float): Temperature scaling factor. + gamma (float): Weight for the second entropy term. + + Returns: + torch.Tensor: Soft entropy loss. + """ + # Binary quantization: Generate implicit codebook corners + L = u.size(1) # Dimensionality of codebook + corners = torch.tensor([-1.0, 1.0], device=u.device) / (L**0.5) + + # Compute soft quantization probabilities for all dimensions + # q_hat(c|u) for each dimension + prob_matrix = torch.sigmoid(2 * tau * corners.unsqueeze(1) * u.unsqueeze(2)) # Shape: (batch_size, L, 2) + + # Entropy of q_hat(c|u) (independent along each dimension) + entropy_per_dim = -torch.sum(prob_matrix * prob_matrix.log(), dim=-1) # Shape: (batch_size, L) + entropy_term1 = entropy_per_dim.mean() + + # Expected probabilities for dataset entropy (approximation) + expected_probs = prob_matrix.mean(dim=0) # Mean across batch, shape: (L, 2) + entropy_term2 = -torch.sum(expected_probs * expected_probs.log(), dim=-1).mean() + + # Final entropy loss + loss = entropy_term1 - gamma * entropy_term2 + return loss + +# class + +class BinarySphericalQuantize(Module): + def __init__( + self, + *, + dim = None, + codebook_size = None, + entropy_loss_weight = 0.1, + commitment_loss_weight = 0., + diversity_gamma = 1., + straight_through_activation = nn.Identity(), + num_codebooks = 1, + keep_num_codebooks_dim = None, + codebook_scale = 1., # for residual LFQ, codebook scaled down by 2x at each layer + frac_per_sample_entropy = 0.25, # make less than 1. to only use a random fraction of the probs for per sample entropy + has_projections = None, + projection_has_bias = True, + soft_clamp_input_value = None, + cosine_sim_project_in = False, + cosine_sim_project_in_scale = None, + channel_first = None, + experimental_softplus_entropy_loss = False, + entropy_loss_offset = 5., # how much to shift the loss before softplus + spherical = True, # from https://arxiv.org/abs/2406.07548 + force_quantization_f32 = True, # will force the quantization step to be full precision + enable_entropy_loss = True, + soft_entropy_loss = True, + ): + super().__init__() + + # some assert validations + + assert exists(dim) or exists(codebook_size), 'either dim or codebook_size must be specified for LFQ' + assert not exists(codebook_size) or log2(codebook_size).is_integer(), f'your codebook size must be a power of 2 for lookup free quantization (suggested {2 ** ceil(log2(codebook_size))})' + + codebook_size = default(codebook_size, lambda: 2 ** dim) + self.codebook_size = codebook_size + + codebook_dim = int(log2(codebook_size)) + codebook_dims = codebook_dim * num_codebooks + dim = default(dim, codebook_dims) + + has_projections = default(has_projections, dim != codebook_dims) + + if cosine_sim_project_in: + cosine_sim_project_in = default(cosine_sim_project_in_scale, codebook_scale) + project_in_klass = partial(CosineSimLinear, scale = cosine_sim_project_in) + else: + project_in_klass = partial(nn.Linear, bias = projection_has_bias) + + self.project_in = project_in_klass(dim, codebook_dims) if has_projections else nn.Identity() + self.project_out = nn.Linear(codebook_dims, dim, bias = projection_has_bias) if has_projections else nn.Identity() + self.has_projections = has_projections + + self.dim = dim + self.codebook_dim = codebook_dim + self.num_codebooks = num_codebooks + + keep_num_codebooks_dim = default(keep_num_codebooks_dim, num_codebooks > 1) + assert not (num_codebooks > 1 and not keep_num_codebooks_dim) + self.keep_num_codebooks_dim = keep_num_codebooks_dim + + # channel first + + self.channel_first = channel_first + + # straight through activation + + self.activation = straight_through_activation + + # whether to use BSQ (binary spherical quantization) + + self.spherical = spherical + self.maybe_l2norm = (lambda t: l2norm(t) * self.codebook_scale) if spherical else identity + + # entropy aux loss related weights + + assert 0 < frac_per_sample_entropy <= 1. + self.frac_per_sample_entropy = frac_per_sample_entropy + + self.diversity_gamma = diversity_gamma + self.entropy_loss_weight = entropy_loss_weight + + # codebook scale + + self.codebook_scale = codebook_scale + + # commitment loss + + self.commitment_loss_weight = commitment_loss_weight + + # whether to soft clamp the input value from -value to value + + self.soft_clamp_input_value = soft_clamp_input_value + assert not exists(soft_clamp_input_value) or soft_clamp_input_value >= codebook_scale + + # whether to make the entropy loss positive through a softplus (experimental, please report if this worked or not in discussions) + + self.entropy_loss_offset = entropy_loss_offset + self.experimental_softplus_entropy_loss = experimental_softplus_entropy_loss + + # for no auxiliary loss, during inference + + self.register_buffer('mask', 2 ** torch.arange(codebook_dim - 1, -1, -1)) + self.register_buffer('zero', torch.tensor(0.), persistent = False) + + # whether to force quantization step to be f32 + + self.force_quantization_f32 = force_quantization_f32 + + # codes + self.enable_entropy_loss = enable_entropy_loss + self.soft_entropy_loss = soft_entropy_loss + if codebook_size <= 100000: + all_codes = torch.arange(codebook_size) + bits = ((all_codes[..., None].int() & self.mask) != 0).float() + codebook = self.bits_to_codes(bits) + + self.register_buffer('codebook', codebook.float(), persistent = False) + else: + all_codes = torch.arange(pow(2, 16)) + mask = 2 ** torch.arange(16 - 1, -1, -1) + bits = ((all_codes[..., None].int() & mask) != 0).float() + codebook = self.bits_to_codes(bits) + + self.register_buffer('codebook', codebook.float(), persistent = False) + + def bits_to_codes(self, bits): + return bits * self.codebook_scale * 2 - self.codebook_scale + + @property + def dtype(self): + return self.codebook.dtype + + def indices_to_codes( + self, + indices, + project_out = True + ): + is_img_or_video = indices.ndim >= (3 + int(self.keep_num_codebooks_dim)) + should_transpose = default(self.channel_first, is_img_or_video) + + if not self.keep_num_codebooks_dim: + indices = rearrange(indices, '... -> ... 1') + + # indices to codes, which are bits of either -1 or 1 + + bits = ((indices[..., None].int() & self.mask) != 0).to(self.dtype) + + codes = self.bits_to_codes(bits) + + codes = self.maybe_l2norm(codes) + + codes = rearrange(codes, '... c d -> ... (c d)') + + # whether to project codes out to original dimensions + # if the input feature dimensions were not log2(codebook size) + + if project_out: + codes = self.project_out(codes) + + # rearrange codes back to original shape + + if should_transpose: + codes = rearrange(codes, 'b ... d -> b d ...') + + return codes + + def bits_to_z(self, bits): + # assert bits must contain only -1 and 1 + assert torch.all(bits.abs() == 1) + quantized = bits.float() + quantized = self.maybe_l2norm(quantized) + z = self.project_out(quantized) + return z + + def forward( + self, + x, + inv_temperature = 100., + return_loss_breakdown = False, + mask = None, + return_bits = False + ): + """ + einstein notation + b - batch + n - sequence (or flattened spatial dimensions) + d - feature dimension, which is also log2(codebook size) + c - number of codebook dim + """ + + is_img_or_video = x.ndim >= 4 + should_transpose = default(self.channel_first, is_img_or_video) + + # standardize image or video into (batch, seq, dimension) + + if should_transpose: + x = rearrange(x, 'b d ... -> b ... d') + x, ps = pack_one(x, 'b * d') + + assert x.shape[-1] == self.dim, f'expected dimension of {self.dim} but received {x.shape[-1]}' + + x = self.project_in(x) + + # maybe soft clamp + + if exists(self.soft_clamp_input_value): + clamp_value = self.soft_clamp_input_value + x = (x / clamp_value).tanh() * clamp_value + + # split out number of codebooks + + x = rearrange(x, 'b n (c d) -> b n c d', c = self.num_codebooks) + + # maybe l2norm + + x = self.maybe_l2norm(x) + + # whether to force quantization step to be full precision or not + + force_f32 = self.force_quantization_f32 + + quantization_context = partial(autocast, 'cuda', enabled = False) if force_f32 else nullcontext + + with quantization_context(): + + if force_f32: + orig_dtype = x.dtype + x = x.float() + + # quantize by eq 3. + + original_input = x + + codebook_value = torch.ones_like(x) * self.codebook_scale + quantized = torch.where(x > 0, codebook_value, -codebook_value) + if return_bits: + return quantized + + # calculate indices + + indices = reduce((quantized > 0).int() * self.mask.int(), 'b n c d -> b n c', 'sum') + + # maybe l2norm + + quantized = self.maybe_l2norm(quantized) + + # use straight-through gradients (optionally with custom activation fn) if training + + if self.training: + x = self.activation(x) + x = x + (quantized - x).detach() + else: + x = quantized + + # entropy aux loss + if self.soft_entropy_loss: + entropy_aux_loss = soft_entropy_loss(x, tau=1.0, gamma=1.0) + elif self.training and self.enable_entropy_loss: + + if force_f32: + codebook = self.codebook.float() + + codebook = self.maybe_l2norm(codebook) + + # whether to only use a fraction of probs, for reducing memory + + if self.frac_per_sample_entropy < 1.: + # account for mask + if exists(mask): + original_input = original_input[mask] + original_input = rearrange(original_input, 'b n ... -> (b n) ...') + + rand_mask = torch.randn(self.codebook_dim).argsort(dim = -1) < 16 + + sampled_input = original_input[..., rand_mask] + + sampled_distance = -2 * einsum('... i d, j d -> ... i j', sampled_input, codebook) + + sampled_prob = (-sampled_distance * inv_temperature).softmax(dim = -1) + + per_sample_probs = sampled_prob + else: + if exists(mask): + original_input = original_input[mask] + original_input = rearrange(original_input, 'b n ... -> (b n) ...') + # the same as euclidean distance up to a constant + distance = -2 * einsum('... i d, j d -> ... i j', original_input, codebook) + + prob = (-distance * inv_temperature).softmax(dim = -1) + + per_sample_probs = prob + + # calculate per sample entropy + + per_sample_entropy = entropy(per_sample_probs).mean() + + # distribution over all available tokens in the batch + + avg_prob = reduce(per_sample_probs, '... c d -> c d', 'mean') + + avg_prob = maybe_distributed_mean(avg_prob) + + codebook_entropy = entropy(avg_prob).mean() + + # 1. entropy will be nudged to be low for each code, to encourage the network to output confident predictions + # 2. codebook entropy will be nudged to be high, to encourage all codes to be uniformly used within the batch + + entropy_aux_loss = per_sample_entropy - self.diversity_gamma * codebook_entropy + else: + # if not training, just return dummy 0 + entropy_aux_loss = per_sample_entropy = codebook_entropy = self.zero + + # whether to make the entropy loss positive or not through a (shifted) softplus + + if self.training and self.experimental_softplus_entropy_loss: + entropy_aux_loss = F.softplus(entropy_aux_loss + self.entropy_loss_offset) + + # commit loss + + if self.training and self.commitment_loss_weight > 0.: + + commit_loss = F.mse_loss(original_input, quantized.detach(), reduction = 'none') + + if exists(mask): + commit_loss = commit_loss[mask] + + commit_loss = commit_loss.mean() + else: + commit_loss = self.zero + + # input back to original dtype if needed + + if force_f32: + x = x.type(orig_dtype) + + # merge back codebook dim + + x = rearrange(x, 'b n c d -> b n (c d)') + + # project out to feature dimension if needed + + x = self.project_out(x) + + # reconstitute image or video dimensions + + if should_transpose: + x = unpack_one(x, ps, 'b * d') + x = rearrange(x, 'b ... d -> b d ...') + + indices = unpack_one(indices, ps, 'b * c') + + # whether to remove single codebook dim + + if not self.keep_num_codebooks_dim: + indices = rearrange(indices, '... 1 -> ...') + + # complete aux loss + + aux_loss = entropy_aux_loss * self.entropy_loss_weight + commit_loss * self.commitment_loss_weight + + # returns + + ret = Return(x, indices, aux_loss) + + if not return_loss_breakdown: + return ret + + return ret, LossBreakdown(per_sample_entropy, codebook_entropy, commit_loss) + +class GroupedResidualBSQ(Module): + def __init__( + self, + *, + dim, + groups = 1, + accept_image_fmap = False, + **kwargs + ): + super().__init__() + self.dim = dim + self.groups = groups + assert (dim % groups) == 0 + dim_per_group = dim // groups + + self.accept_image_fmap = accept_image_fmap + + self.rvqs = nn.ModuleList([]) + + for _ in range(groups): + self.rvqs.append(LFQ( + dim = dim_per_group, + **kwargs + )) + + self.codebook_size = self.rvqs[0].codebook_size + + @property + def codebooks(self): + return torch.stack(tuple(rvq.codebooks for rvq in self.rvqs)) + + @property + def split_dim(self): + return 1 if self.accept_image_fmap else -1 + + def get_codes_from_indices(self, indices): + codes = tuple(rvq.get_codes_from_indices(chunk_indices) for rvq, chunk_indices in zip(self.rvqs, indices)) + return torch.stack(codes) + + def get_output_from_indices(self, indices): + outputs = tuple(rvq.get_output_from_indices(chunk_indices) for rvq, chunk_indices in zip(self.rvqs, indices)) + return torch.cat(outputs, dim = self.split_dim) + + def forward( + self, + x, + return_all_codes = False + ): + shape, split_dim = x.shape, self.split_dim + assert shape[split_dim] == self.dim + + # split the feature dimension into groups + + x = x.chunk(self.groups, dim = split_dim) + + forward_kwargs = dict( + ) + + # invoke residual vq on each group + + out = tuple(rvq(chunk, **forward_kwargs) for rvq, chunk in zip(self.rvqs, x)) + out = tuple(zip(*out)) + + # otherwise, get all the zipped outputs and combine them + + quantized, all_indices, *maybe_aux_loss = out + + quantized = torch.cat(quantized, dim = split_dim) + all_indices = torch.stack(all_indices) + + ret = (quantized, all_indices, *maybe_aux_loss) + return ret diff --git a/modules/astral_quantization/convnext.py b/modules/astral_quantization/convnext.py new file mode 100644 index 0000000000000000000000000000000000000000..dc7ac97c993ba9b5811f5f57185f5ed5e84547ed --- /dev/null +++ b/modules/astral_quantization/convnext.py @@ -0,0 +1,209 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from typing import List + + +class ConvNextV2LayerNorm(nn.Module): + r"""LayerNorm that supports two data formats: channels_last (default) or channels_first. + The ordering of the dimensions in the inputs. channels_last corresponds to inputs with shape (batch_size, height, + width, channels) while channels_first corresponds to inputs with shape (batch_size, channels, height, width). + """ + + def __init__(self, normalized_shape, eps=1e-6, data_format="channels_last"): + super().__init__() + self.weight = nn.Parameter(torch.ones(normalized_shape)) + self.bias = nn.Parameter(torch.zeros(normalized_shape)) + self.eps = eps + self.data_format = data_format + if self.data_format not in ["channels_last", "channels_first"]: + raise NotImplementedError(f"Unsupported data format: {self.data_format}") + self.normalized_shape = (normalized_shape,) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + if self.data_format == "channels_last": + x = torch.nn.functional.layer_norm( + x, self.normalized_shape, self.weight, self.bias, self.eps + ) + elif self.data_format == "channels_first": + input_dtype = x.dtype + x = x.float() + u = x.mean(1, keepdim=True) + s = (x - u).pow(2).mean(1, keepdim=True) + x = (x - u) / torch.sqrt(s + self.eps) + x = x.to(dtype=input_dtype) + x = self.weight[None, :, None] * x + self.bias[None, :, None] + return x + + +class GRN(nn.Module): + def __init__(self, dim): + super().__init__() + self.gamma = nn.Parameter(torch.zeros(1, 1, dim)) + self.beta = nn.Parameter(torch.zeros(1, 1, dim)) + + def forward(self, x): + Gx = torch.norm(x, p=2, dim=1, keepdim=True) + Nx = Gx / (Gx.mean(dim=-1, keepdim=True) + 1e-6) + return self.gamma * (x * Nx) + self.beta + x + +class InterpolationLayer(nn.Module): + def __init__(self, ): # this is a default of 1 / 50 * (44100 / 512) / 4 + super().__init__() + pass + + def forward(self, x: torch.Tensor, target_len: torch.Tensor, *args, **kwargs) -> torch.Tensor: + x = F.interpolate(x, size=target_len, mode='linear') + return x + +class ConvNeXtV2Stage(nn.Module): + def __init__( + self, + dim: int = 512, + intermediate_dim: int = 2048, + num_blocks: int = 1, + dilation: int = 1, + downsample_layer_indices: List[int] = None, + downsample_factors: List[int] = None, + upsample_layer_indices: List[int] = None, + upsample_factors: List[int] = None, + interpolation_layer_indices: List[int] = None, + input_dim: int = None, + output_dim: int = None, + gin_channels: int = 0, + ): + super().__init__() + # maybe downsample layers + if downsample_layer_indices is not None: + assert downsample_factors is not None + self.downsample_blocks = nn.ModuleList( + [ + nn.Sequential( + ConvNextV2LayerNorm(dim, data_format="channels_first"), + nn.Conv1d( + dim, dim, kernel_size=downsample_factor, stride=downsample_factor + ), + ) for _, downsample_factor in zip(downsample_layer_indices, downsample_factors) + ] + ) + self.downsample_layer_indices = downsample_layer_indices + else: + self.downsample_blocks = nn.ModuleList() + self.downsample_layer_indices = [] + + # maybe upsample layers + if upsample_layer_indices is not None: + assert upsample_factors is not None + self.upsample_blocks = nn.ModuleList( + [ + nn.Sequential( + ConvNextV2LayerNorm(dim, data_format="channels_first"), + nn.ConvTranspose1d( + dim, dim, kernel_size=upsample_factor, stride=upsample_factor + ), + ) for _, upsample_factor in zip(upsample_layer_indices, upsample_factors) + ] + ) + self.upsample_layer_indices = upsample_layer_indices + else: + self.upsample_blocks = nn.ModuleList() + self.upsample_layer_indices = [] + + # maybe interpolation layers + if interpolation_layer_indices is not None: + self.interpolation_blocks = nn.ModuleList( + [ + InterpolationLayer() + for _ in interpolation_layer_indices + ] + ) + self.interpolation_layer_indices = interpolation_layer_indices + else: + self.interpolation_blocks = nn.ModuleList() + self.interpolation_layer_indices = [] + + # main blocks + self.blocks = nn.ModuleList( + [ + ConvNeXtV2Block( + dim=dim, + intermediate_dim=intermediate_dim, + dilation=dilation, + ) + for _ in range(num_blocks) + ] + ) + # maybe input and output projections + if input_dim is not None and input_dim != dim: + self.input_projection = nn.Conv1d(input_dim, dim, kernel_size=1) + else: + self.input_projection = nn.Identity() + if output_dim is not None and output_dim != dim: + self.output_projection = nn.Conv1d(dim, output_dim, kernel_size=1) + else: + self.output_projection = nn.Identity() + + if gin_channels > 0: + self.gin = nn.Conv1d(gin_channels, dim, kernel_size=1) + + def forward(self, x: torch.Tensor, *args, **kwargs) -> torch.Tensor: + x = self.input_projection(x) # B, D, T + if hasattr(self, 'gin'): + g = kwargs['g'] + x = x + self.gin(g) + # pad to a multiple of cumprod(downsample_factors) + if len(self.downsample_blocks) > 0: + downsample_factor = 1 + for factor in self.downsample_blocks: + downsample_factor *= factor[1].stride[0] + pad_len = downsample_factor - x.size(-1) % downsample_factor + if pad_len > 0: + x = torch.cat([x, torch.zeros_like(x[:, :, :pad_len])], dim=-1) + + # main blocks + for layer_idx, block in enumerate(self.blocks): + if layer_idx in self.downsample_layer_indices: + x = self.downsample_blocks[self.downsample_layer_indices.index(layer_idx)](x) + if layer_idx in self.upsample_layer_indices: + x = self.upsample_blocks[self.upsample_layer_indices.index(layer_idx)](x) + if layer_idx in self.interpolation_layer_indices: + x = self.interpolation_blocks[self.interpolation_layer_indices.index(layer_idx)](x, target_len=kwargs['target_len']) + x = block(x) + x = self.output_projection(x) + return x + + def setup_caches(self, *args, **kwargs): + pass + + +class ConvNeXtV2Block(nn.Module): + def __init__( + self, + dim: int, + intermediate_dim: int, + dilation: int = 1, + ): + super().__init__() + padding = (dilation * (7 - 1)) // 2 + self.dwconv = nn.Conv1d( + dim, dim, kernel_size=7, padding=padding, groups=dim, dilation=dilation + ) # depthwise conv + self.norm = ConvNextV2LayerNorm(dim, data_format="channels_first") + self.pwconv1 = nn.Linear( + dim, intermediate_dim + ) # pointwise/1x1 convs, implemented with linear layers + self.act = nn.GELU() + self.grn = GRN(intermediate_dim) + self.pwconv2 = nn.Linear(intermediate_dim, dim) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + residual = x + x = self.dwconv(x) + x = self.norm(x) + x = x.transpose(1, 2) # b d n -> b n d + x = self.pwconv1(x) + x = self.act(x) + x = self.grn(x) + x = self.pwconv2(x) + x = x.transpose(1, 2) # b n d -> b d n + return residual + x \ No newline at end of file diff --git a/modules/astral_quantization/default_model.py b/modules/astral_quantization/default_model.py new file mode 100644 index 0000000000000000000000000000000000000000..259515557fc42c36007099c29e71138c2ddae93a --- /dev/null +++ b/modules/astral_quantization/default_model.py @@ -0,0 +1,73 @@ +import torch +from transformers import AutoTokenizer, AutoModel, Wav2Vec2FeatureExtractor + +class AstralQuantizer(torch.nn.Module): + def __init__( + self, + tokenizer_name: str, + ssl_model_name: str, + ssl_output_layer: int, + encoder: torch.nn.Module, + quantizer: torch.nn.Module, + skip_ssl: bool = False, + ): + super().__init__() + self.encoder = encoder + self.quantizer = quantizer + self.tokenizer_name = tokenizer_name + self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_name) + + # Load SSL model from Huggingface + self.ssl_model_name = ssl_model_name + self.ssl_output_layer = ssl_output_layer + self.ssl_feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(ssl_model_name) + + if skip_ssl: # in case the same SSL model has been loaded somewhere else + self.ssl_model = None + else: + self.ssl_model = AutoModel.from_pretrained(ssl_model_name).eval() + self.ssl_model.encoder.layers = self.ssl_model.encoder.layers[:ssl_output_layer] + self.ssl_model.encoder.layer_norm = torch.nn.Identity() + + def load_separate_checkpoint(self, checkpoint_path): + params = torch.load(checkpoint_path, map_location='cpu')['net'] + for key in params.keys(): + for k in list(params[key].keys()): + if k.startswith("module."): + params[key][k[len("module."):]] = params[key][k] + del params[key][k] + self.encoder.load_state_dict(params['encoder']) + self.quantizer.load_state_dict(params['vq']) + if self.decoder is not None: + self.decoder.load_state_dict(params['decoder']) + if self.asr_decoder is not None: + self.asr_decoder.load_state_dict(params['predictor'], strict=False) + + def forward(self, waves_16k, wave_16k_lens, ssl_model=None): + ssl_fn = self.ssl_model if self.ssl_model else ssl_model + assert ssl_fn is not None, "In case in-class SSL model loading is skipped, external ssl_model must be provided" + waves_16k_input_list = [ + waves_16k[bib, :wave_16k_lens[bib]].cpu().numpy() + for bib in range(len(waves_16k)) + ] + alt_inputs = self.ssl_feature_extractor( + waves_16k_input_list, + return_tensors='pt', + return_attention_mask=True, + padding=True, + sampling_rate=16000 + ).to(waves_16k.device) + feature_lens = alt_inputs.data['attention_mask'].sum(-1) // 320 # frame rate of hubert is 50 Hz + + outputs = ssl_fn( + alt_inputs.input_values, + attention_mask=alt_inputs.attention_mask, + ) + last_hidden_states = outputs.last_hidden_state + last_hidden_states = last_hidden_states[:, :feature_lens.max(), :] + feature_lens = feature_lens.clamp(max=last_hidden_states.size(1)) + last_hidden_states = last_hidden_states.transpose(1, 2) + x_hidden = self.encoder(last_hidden_states, feature_lens) + x_hidden = x_hidden.transpose(1, 2) + x_quantized, indices = self.quantizer(x_hidden)[:2] + return x_quantized, indices, feature_lens \ No newline at end of file diff --git a/modules/astral_quantization/transformer.py b/modules/astral_quantization/transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..015ec417d02e3442d8ea120c3802807c73e89bb4 --- /dev/null +++ b/modules/astral_quantization/transformer.py @@ -0,0 +1,254 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +from dataclasses import dataclass +from typing import Optional + +import torch +import torch.nn as nn +from torch import Tensor +from torch.nn import functional as F +import time + +def find_multiple(n: int, k: int) -> int: + if n % k == 0: + return n + return n + k - (n % k) + +class AdaptiveLayerNorm(nn.Module): + r"""Adaptive Layer Normalization""" + + def __init__(self, d_model, norm) -> None: + super(AdaptiveLayerNorm, self).__init__() + self.project_layer = nn.Linear(d_model, 2 * d_model) + self.norm = norm + self.d_model = d_model + self.eps = self.norm.eps + + def forward(self, input: Tensor, embedding: Tensor = None) -> Tensor: + if embedding is None: + return self.norm(input) + weight, bias = torch.split( + self.project_layer(embedding), + split_size_or_sections=self.d_model, + dim=-1, + ) + return weight * self.norm(input) + bias + + +@dataclass +class ModelArgs: + block_size: int = 2048 + vocab_size: int = 32000 + n_layer: int = 32 + n_head: int = 32 + dim: int = 4096 + intermediate_size: int = None + n_local_heads: int = -1 + head_dim: int = 64 + rope_base: float = 10000 + norm_eps: float = 1e-5 + has_cross_attention: bool = False + context_dim: int = 0 + is_causal: bool = False + dropout_rate: float = 0.1 + attn_dropout_rate: float = 0.1 + + def __post_init__(self): + if self.n_local_heads == -1: + self.n_local_heads = self.n_head + if self.intermediate_size is None: + hidden_dim = 4 * self.dim + n_hidden = int(2 * hidden_dim / 3) + self.intermediate_size = find_multiple(n_hidden, 256) + # self.head_dim = self.dim // self.n_head + +class Transformer(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.config = config + + self.layers = nn.ModuleList(TransformerBlock(config) for _ in range(config.n_layer)) + self.norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + + self.max_batch_size = -1 + self.max_seq_length = config.block_size + freqs_cis = precompute_freqs_cis(self.config.block_size, self.config.head_dim, + self.config.rope_base) + self.register_buffer("freqs_cis", freqs_cis) + + causal_mask = torch.tril( + torch.ones(self.max_seq_length, self.max_seq_length, dtype=torch.bool) + ) + self.register_buffer("causal_mask", causal_mask) + + def forward(self, + x: Tensor, + c: Tensor, + input_pos: Optional[Tensor] = None, + mask: Optional[Tensor] = None, + context: Optional[Tensor] = None, + context_input_pos: Optional[Tensor] = None, + cross_attention_mask: Optional[Tensor] = None, + ) -> Tensor: + if mask is None: + mask = self.causal_mask[:x.size(1), :x.size(1)] + else: + mask = mask[..., input_pos] + freqs_cis = self.freqs_cis[input_pos] + if context is not None: + context_freqs_cis = self.freqs_cis[context_input_pos] + else: + context_freqs_cis = None + skip_in_x_list = [] + for i, layer in enumerate(self.layers): + x = layer(x, c, freqs_cis, mask, context, context_freqs_cis, cross_attention_mask) + x = self.norm(x, c) + return x + + +class TransformerBlock(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.attention = Attention(config) + self.feed_forward = FeedForward(config) + self.ffn_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + self.attention_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + + if config.has_cross_attention: + self.has_cross_attention = True + self.cross_attention = Attention(config, is_cross_attention=True) + self.cross_attention_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + else: + self.has_cross_attention = False + + def forward(self, + x: Tensor, + c: Tensor, + freqs_cis: Tensor, + mask: Tensor, + context: Optional[Tensor] = None, + context_freqs_cis: Optional[Tensor] = None, + cross_attention_mask: Optional[Tensor] = None, + ) -> Tensor: + #time_attn_start = time.time() + h = x + self.attention(self.attention_norm(x, c), freqs_cis, mask) + #print(f"time take for attention of sequence length {x.shape[1]} is {time.time() - time_attn_start}") + if self.has_cross_attention: + h = h + self.cross_attention(self.cross_attention_norm(h, c), freqs_cis, cross_attention_mask, context, context_freqs_cis) + out = h + self.feed_forward(self.ffn_norm(h, c)) + return out + + +class Attention(nn.Module): + def __init__(self, config: ModelArgs, is_cross_attention: bool = False): + super().__init__() + assert config.dim % config.n_head == 0 + + total_head_dim = (config.n_head + 2 * config.n_local_heads) * config.head_dim + # key, query, value projections for all heads, but in a batch + if is_cross_attention: + self.wq = nn.Linear(config.dim, config.n_head * config.head_dim, bias=False) + self.wkv = nn.Linear(config.context_dim, 2 * config.n_local_heads * config.head_dim, bias=False) + else: + self.wqkv = nn.Linear(config.dim, total_head_dim, bias=False) + self.wo = nn.Linear(config.head_dim * config.n_head, config.dim, bias=False) + self.kv_cache = None + + self.n_head = config.n_head + self.head_dim = config.head_dim + self.n_local_heads = config.n_local_heads + self.dim = config.dim + self.attn_dropout_rate = config.attn_dropout_rate + + def forward(self, + x: Tensor, + freqs_cis: Tensor, + mask: Tensor, + context: Optional[Tensor] = None, + context_freqs_cis: Optional[Tensor] = None, + ) -> Tensor: + bsz, seqlen, _ = x.shape + + kv_size = self.n_local_heads * self.head_dim + if context is None: + q, k, v = self.wqkv(x).split([kv_size, kv_size, kv_size], dim=-1) + context_seqlen = seqlen + else: + q = self.wq(x) + k, v = self.wkv(context).split([kv_size, kv_size], dim=-1) + context_seqlen = context.shape[1] + + q = q.view(bsz, seqlen, self.n_head, self.head_dim) + k = k.view(bsz, context_seqlen, self.n_local_heads, self.head_dim) + v = v.view(bsz, context_seqlen, self.n_local_heads, self.head_dim) + + q = apply_rotary_emb(q, freqs_cis) + k = apply_rotary_emb(k, context_freqs_cis if context_freqs_cis is not None else freqs_cis) + + q, k, v = map(lambda x: x.transpose(1, 2), (q, k, v)) + + k = k.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + v = v.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + y = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=self.attn_dropout_rate if self.training else 0.0) + + y = y.transpose(1, 2).contiguous().view(bsz, seqlen, self.head_dim * self.n_head) + + y = self.wo(y) + return y + + +class FeedForward(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.w1 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w3 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w2 = nn.Linear(config.intermediate_size, config.dim, bias=False) + self.dropout = nn.Dropout(config.dropout_rate) + + def forward(self, x: Tensor) -> Tensor: + return self.w2(self.dropout(F.silu(self.w1(x)) * self.w3(x))) + + +class RMSNorm(nn.Module): + def __init__(self, dim: int, eps: float = 1e-5): + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.ones(dim)) + + def _norm(self, x): + return x * torch.rsqrt(torch.mean(x * x, dim=-1, keepdim=True) + self.eps) + + def forward(self, x: Tensor) -> Tensor: + output = self._norm(x.float()).type_as(x) + return output * self.weight + + +def precompute_freqs_cis( + seq_len: int, n_elem: int, base: int = 10000, + dtype: torch.dtype = torch.bfloat16 +) -> Tensor: + freqs = 1.0 / (base ** (torch.arange(0, n_elem, 2)[: (n_elem // 2)].float() / n_elem)) + t = torch.arange(seq_len, device=freqs.device) + freqs = torch.outer(t, freqs) + freqs_cis = torch.polar(torch.ones_like(freqs), freqs) + cache = torch.stack([freqs_cis.real, freqs_cis.imag], dim=-1) + return cache.to(dtype=dtype) + + +def apply_rotary_emb(x: Tensor, freqs_cis: Tensor) -> Tensor: + xshaped = x.float().reshape(*x.shape[:-1], -1, 2) + freqs_cis = freqs_cis.view(1, xshaped.size(1), 1, xshaped.size(3), 2) + x_out2 = torch.stack( + [ + xshaped[..., 0] * freqs_cis[..., 0] - xshaped[..., 1] * freqs_cis[..., 1], + xshaped[..., 1] * freqs_cis[..., 0] + xshaped[..., 0] * freqs_cis[..., 1], + ], + -1, + ) + + x_out2 = x_out2.flatten(3) + return x_out2.type_as(x) + diff --git a/modules/audio.py b/modules/audio.py new file mode 100644 index 0000000000000000000000000000000000000000..abe783b0e0af630319700c931eb51d2ce375282b --- /dev/null +++ b/modules/audio.py @@ -0,0 +1,82 @@ +import numpy as np +import torch +import torch.utils.data +from librosa.filters import mel as librosa_mel_fn +from scipy.io.wavfile import read + +MAX_WAV_VALUE = 32768.0 + + +def load_wav(full_path): + sampling_rate, data = read(full_path) + return data, sampling_rate + + +def dynamic_range_compression(x, C=1, clip_val=1e-5): + return np.log(np.clip(x, a_min=clip_val, a_max=None) * C) + + +def dynamic_range_decompression(x, C=1): + return np.exp(x) / C + + +def dynamic_range_compression_torch(x, C=1, clip_val=1e-5): + return torch.log(torch.clamp(x, min=clip_val) * C) + + +def dynamic_range_decompression_torch(x, C=1): + return torch.exp(x) / C + + +def spectral_normalize_torch(magnitudes): + output = dynamic_range_compression_torch(magnitudes) + return output + + +def spectral_de_normalize_torch(magnitudes): + output = dynamic_range_decompression_torch(magnitudes) + return output + + +mel_basis = {} +hann_window = {} + + +def mel_spectrogram(y, n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax, center=False): + if torch.min(y) < -1.0: + print("min value is ", torch.min(y)) + if torch.max(y) > 1.0: + print("max value is ", torch.max(y)) + + global mel_basis, hann_window # pylint: disable=global-statement + if f"{str(sampling_rate)}_{str(fmax)}_{str(y.device)}" not in mel_basis: + mel = librosa_mel_fn(sr=sampling_rate, n_fft=n_fft, n_mels=num_mels, fmin=fmin, fmax=fmax) + mel_basis[str(sampling_rate) + "_" + str(fmax) + "_" + str(y.device)] = torch.from_numpy(mel).float().to(y.device) + hann_window[str(sampling_rate) + "_" + str(y.device)] = torch.hann_window(win_size).to(y.device) + + y = torch.nn.functional.pad( + y.unsqueeze(1), (int((n_fft - hop_size) / 2), int((n_fft - hop_size) / 2)), mode="reflect" + ) + y = y.squeeze(1) + + spec = torch.view_as_real( + torch.stft( + y, + n_fft, + hop_length=hop_size, + win_length=win_size, + window=hann_window[str(sampling_rate) + "_" + str(y.device)], + center=center, + pad_mode="reflect", + normalized=False, + onesided=True, + return_complex=True, + ) + ) + + spec = torch.sqrt(spec.pow(2).sum(-1) + (1e-9)) + + spec = torch.matmul(mel_basis[str(sampling_rate) + "_" + str(fmax) + "_" + str(y.device)], spec) + spec = spectral_normalize_torch(spec) + + return spec diff --git a/modules/bigvgan/__init__.py b/modules/bigvgan/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/bigvgan/activations.py b/modules/bigvgan/activations.py new file mode 100644 index 0000000000000000000000000000000000000000..61f2808a5466b3cf4d041059700993af5527dd29 --- /dev/null +++ b/modules/bigvgan/activations.py @@ -0,0 +1,120 @@ +# Implementation adapted from https://github.com/EdwardDixon/snake under the MIT license. +# LICENSE is in incl_licenses directory. + +import torch +from torch import nn, sin, pow +from torch.nn import Parameter + + +class Snake(nn.Module): + ''' + Implementation of a sine-based periodic activation function + Shape: + - Input: (B, C, T) + - Output: (B, C, T), same shape as the input + Parameters: + - alpha - trainable parameter + References: + - This activation function is from this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda: + https://arxiv.org/abs/2006.08195 + Examples: + >>> a1 = snake(256) + >>> x = torch.randn(256) + >>> x = a1(x) + ''' + def __init__(self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False): + ''' + Initialization. + INPUT: + - in_features: shape of the input + - alpha: trainable parameter + alpha is initialized to 1 by default, higher values = higher-frequency. + alpha will be trained along with the rest of your model. + ''' + super(Snake, self).__init__() + self.in_features = in_features + + # initialize alpha + self.alpha_logscale = alpha_logscale + if self.alpha_logscale: # log scale alphas initialized to zeros + self.alpha = Parameter(torch.zeros(in_features) * alpha) + else: # linear scale alphas initialized to ones + self.alpha = Parameter(torch.ones(in_features) * alpha) + + self.alpha.requires_grad = alpha_trainable + + self.no_div_by_zero = 0.000000001 + + def forward(self, x): + ''' + Forward pass of the function. + Applies the function to the input elementwise. + Snake ∶= x + 1/a * sin^2 (xa) + ''' + alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # line up with x to [B, C, T] + if self.alpha_logscale: + alpha = torch.exp(alpha) + x = x + (1.0 / (alpha + self.no_div_by_zero)) * pow(sin(x * alpha), 2) + + return x + + +class SnakeBeta(nn.Module): + ''' + A modified Snake function which uses separate parameters for the magnitude of the periodic components + Shape: + - Input: (B, C, T) + - Output: (B, C, T), same shape as the input + Parameters: + - alpha - trainable parameter that controls frequency + - beta - trainable parameter that controls magnitude + References: + - This activation function is a modified version based on this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda: + https://arxiv.org/abs/2006.08195 + Examples: + >>> a1 = snakebeta(256) + >>> x = torch.randn(256) + >>> x = a1(x) + ''' + def __init__(self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False): + ''' + Initialization. + INPUT: + - in_features: shape of the input + - alpha - trainable parameter that controls frequency + - beta - trainable parameter that controls magnitude + alpha is initialized to 1 by default, higher values = higher-frequency. + beta is initialized to 1 by default, higher values = higher-magnitude. + alpha will be trained along with the rest of your model. + ''' + super(SnakeBeta, self).__init__() + self.in_features = in_features + + # initialize alpha + self.alpha_logscale = alpha_logscale + if self.alpha_logscale: # log scale alphas initialized to zeros + self.alpha = Parameter(torch.zeros(in_features) * alpha) + self.beta = Parameter(torch.zeros(in_features) * alpha) + else: # linear scale alphas initialized to ones + self.alpha = Parameter(torch.ones(in_features) * alpha) + self.beta = Parameter(torch.ones(in_features) * alpha) + + self.alpha.requires_grad = alpha_trainable + self.beta.requires_grad = alpha_trainable + + self.no_div_by_zero = 0.000000001 + + def forward(self, x): + ''' + Forward pass of the function. + Applies the function to the input elementwise. + SnakeBeta ∶= x + 1/b * sin^2 (xa) + ''' + alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # line up with x to [B, C, T] + beta = self.beta.unsqueeze(0).unsqueeze(-1) + if self.alpha_logscale: + alpha = torch.exp(alpha) + beta = torch.exp(beta) + x = x + (1.0 / (beta + self.no_div_by_zero)) * pow(sin(x * alpha), 2) + + return x \ No newline at end of file diff --git a/modules/bigvgan/alias_free_activation/cuda/__init__.py b/modules/bigvgan/alias_free_activation/cuda/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/bigvgan/alias_free_activation/cuda/activation1d.py b/modules/bigvgan/alias_free_activation/cuda/activation1d.py new file mode 100644 index 0000000000000000000000000000000000000000..01a25ffc04edbdfe17859653690e3005d441712f --- /dev/null +++ b/modules/bigvgan/alias_free_activation/cuda/activation1d.py @@ -0,0 +1,77 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +import torch +import torch.nn as nn +from ..torch.resample import UpSample1d, DownSample1d + +# load fused CUDA kernel: this enables importing anti_alias_activation_cuda +from ..cuda import load + +anti_alias_activation_cuda = load.load() + + +class FusedAntiAliasActivation(torch.autograd.Function): + """ + Assumes filter size 12, replication padding on upsampling/downsampling, and logscale alpha/beta parameters as inputs. + The hyperparameters are hard-coded in the kernel to maximize speed. + NOTE: The fused kenrel is incorrect for Activation1d with different hyperparameters. + """ + + @staticmethod + def forward(ctx, inputs, up_ftr, down_ftr, alpha, beta): + activation_results = anti_alias_activation_cuda.forward( + inputs, up_ftr, down_ftr, alpha, beta + ) + + return activation_results + + @staticmethod + def backward(ctx, output_grads): + raise NotImplementedError + return output_grads, None, None + + +class Activation1d(nn.Module): + def __init__( + self, + activation, + up_ratio: int = 2, + down_ratio: int = 2, + up_kernel_size: int = 12, + down_kernel_size: int = 12, + fused: bool = True, + ): + super().__init__() + self.up_ratio = up_ratio + self.down_ratio = down_ratio + self.act = activation + self.upsample = UpSample1d(up_ratio, up_kernel_size) + self.downsample = DownSample1d(down_ratio, down_kernel_size) + + self.fused = fused # Whether to use fused CUDA kernel or not + + def forward(self, x): + if not self.fused: + x = self.upsample(x) + x = self.act(x) + x = self.downsample(x) + return x + else: + if self.act.__class__.__name__ == "Snake": + beta = self.act.alpha.data # Snake uses same params for alpha and beta + else: + beta = ( + self.act.beta.data + ) # Snakebeta uses different params for alpha and beta + alpha = self.act.alpha.data + if ( + not self.act.alpha_logscale + ): # Exp baked into cuda kernel, cancel it out with a log + alpha = torch.log(alpha) + beta = torch.log(beta) + + x = FusedAntiAliasActivation.apply( + x, self.upsample.filter, self.downsample.lowpass.filter, alpha, beta + ) + return x diff --git a/modules/bigvgan/alias_free_activation/cuda/anti_alias_activation.cpp b/modules/bigvgan/alias_free_activation/cuda/anti_alias_activation.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c5651f77143bd678169eb11564a7cf7a7969a59e --- /dev/null +++ b/modules/bigvgan/alias_free_activation/cuda/anti_alias_activation.cpp @@ -0,0 +1,23 @@ +/* coding=utf-8 + * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #include + +extern "C" torch::Tensor fwd_cuda(torch::Tensor const &input, torch::Tensor const &up_filter, torch::Tensor const &down_filter, torch::Tensor const &alpha, torch::Tensor const &beta); + +PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { + m.def("forward", &fwd_cuda, "Anti-Alias Activation forward (CUDA)"); +} \ No newline at end of file diff --git a/modules/bigvgan/alias_free_activation/cuda/anti_alias_activation_cuda.cu b/modules/bigvgan/alias_free_activation/cuda/anti_alias_activation_cuda.cu new file mode 100644 index 0000000000000000000000000000000000000000..8c442334869fe72d639ec203fa4fac07f96a0ee1 --- /dev/null +++ b/modules/bigvgan/alias_free_activation/cuda/anti_alias_activation_cuda.cu @@ -0,0 +1,246 @@ +/* coding=utf-8 + * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "type_shim.h" +#include +#include +#include +#include +#include + +namespace +{ + // Hard-coded hyperparameters + // WARP_SIZE and WARP_BATCH must match the return values batches_per_warp and + constexpr int ELEMENTS_PER_LDG_STG = 1; //(WARP_ITERATIONS < 4) ? 1 : 4; + constexpr int BUFFER_SIZE = 32; + constexpr int FILTER_SIZE = 12; + constexpr int HALF_FILTER_SIZE = 6; + constexpr int UPSAMPLE_REPLICATION_PAD = 5; // 5 on each side, matching torch impl + constexpr int DOWNSAMPLE_REPLICATION_PAD_LEFT = 5; // matching torch impl + constexpr int DOWNSAMPLE_REPLICATION_PAD_RIGHT = 6; // matching torch impl + + template + __global__ void anti_alias_activation_forward( + output_t *dst, + const input_t *src, + const input_t *up_ftr, + const input_t *down_ftr, + const input_t *alpha, + const input_t *beta, + int batch_size, + int channels, + int seq_len) + { + // Up and downsample filters + input_t up_filter[FILTER_SIZE]; + input_t down_filter[FILTER_SIZE]; + + // Load data from global memory including extra indices reserved for replication paddings + input_t elements[2 * FILTER_SIZE + 2 * BUFFER_SIZE + 2 * UPSAMPLE_REPLICATION_PAD] = {0}; + input_t intermediates[2 * FILTER_SIZE + 2 * BUFFER_SIZE + DOWNSAMPLE_REPLICATION_PAD_LEFT + DOWNSAMPLE_REPLICATION_PAD_RIGHT] = {0}; + + // Output stores downsampled output before writing to dst + output_t output[BUFFER_SIZE]; + + // blockDim/threadIdx = (128, 1, 1) + // gridDim/blockIdx = (seq_blocks, channels, batches) + int block_offset = (blockIdx.x * 128 * BUFFER_SIZE + seq_len * (blockIdx.y + gridDim.y * blockIdx.z)); + int local_offset = threadIdx.x * BUFFER_SIZE; + int seq_offset = blockIdx.x * 128 * BUFFER_SIZE + local_offset; + + // intermediate have double the seq_len + int intermediate_local_offset = threadIdx.x * BUFFER_SIZE * 2; + int intermediate_seq_offset = blockIdx.x * 128 * BUFFER_SIZE * 2 + intermediate_local_offset; + + // Get values needed for replication padding before moving pointer + const input_t *right_most_pntr = src + (seq_len * (blockIdx.y + gridDim.y * blockIdx.z)); + input_t seq_left_most_value = right_most_pntr[0]; + input_t seq_right_most_value = right_most_pntr[seq_len - 1]; + + // Move src and dst pointers + src += block_offset + local_offset; + dst += block_offset + local_offset; + + // Alpha and beta values for snake activatons. Applies exp by default + alpha = alpha + blockIdx.y; + input_t alpha_val = expf(alpha[0]); + beta = beta + blockIdx.y; + input_t beta_val = expf(beta[0]); + + #pragma unroll + for (int it = 0; it < FILTER_SIZE; it += 1) + { + up_filter[it] = up_ftr[it]; + down_filter[it] = down_ftr[it]; + } + + // Apply replication padding for upsampling, matching torch impl + #pragma unroll + for (int it = -HALF_FILTER_SIZE; it < BUFFER_SIZE + HALF_FILTER_SIZE; it += 1) + { + int element_index = seq_offset + it; // index for element + if ((element_index < 0) && (element_index >= -UPSAMPLE_REPLICATION_PAD)) + { + elements[2 * (HALF_FILTER_SIZE + it)] = 2 * seq_left_most_value; + } + if ((element_index >= seq_len) && (element_index < seq_len + UPSAMPLE_REPLICATION_PAD)) + { + elements[2 * (HALF_FILTER_SIZE + it)] = 2 * seq_right_most_value; + } + if ((element_index >= 0) && (element_index < seq_len)) + { + elements[2 * (HALF_FILTER_SIZE + it)] = 2 * src[it]; + } + } + + // Apply upsampling strided convolution and write to intermediates. It reserves DOWNSAMPLE_REPLICATION_PAD_LEFT for replication padding of the downsampilng conv later + #pragma unroll + for (int it = 0; it < (2 * BUFFER_SIZE + 2 * FILTER_SIZE); it += 1) + { + input_t acc = 0.0; + int element_index = intermediate_seq_offset + it; // index for intermediate + #pragma unroll + for (int f_idx = 0; f_idx < FILTER_SIZE; f_idx += 1) + { + if ((element_index + f_idx) >= 0) + { + acc += up_filter[f_idx] * elements[it + f_idx]; + } + } + intermediates[it + DOWNSAMPLE_REPLICATION_PAD_LEFT] = acc; + } + + // Apply activation function. It reserves DOWNSAMPLE_REPLICATION_PAD_LEFT and DOWNSAMPLE_REPLICATION_PAD_RIGHT for replication padding of the downsampilng conv later + double no_div_by_zero = 0.000000001; + #pragma unroll + for (int it = 0; it < 2 * BUFFER_SIZE + 2 * FILTER_SIZE; it += 1) + { + intermediates[it + DOWNSAMPLE_REPLICATION_PAD_LEFT] += (1.0 / (beta_val + no_div_by_zero)) * sinf(intermediates[it + DOWNSAMPLE_REPLICATION_PAD_LEFT] * alpha_val) * sinf(intermediates[it + DOWNSAMPLE_REPLICATION_PAD_LEFT] * alpha_val); + } + + // Apply replication padding before downsampling conv from intermediates + #pragma unroll + for (int it = 0; it < DOWNSAMPLE_REPLICATION_PAD_LEFT; it += 1) + { + intermediates[it] = intermediates[DOWNSAMPLE_REPLICATION_PAD_LEFT]; + } + #pragma unroll + for (int it = DOWNSAMPLE_REPLICATION_PAD_LEFT + 2 * BUFFER_SIZE + 2 * FILTER_SIZE; it < DOWNSAMPLE_REPLICATION_PAD_LEFT + 2 * BUFFER_SIZE + 2 * FILTER_SIZE + DOWNSAMPLE_REPLICATION_PAD_RIGHT; it += 1) + { + intermediates[it] = intermediates[DOWNSAMPLE_REPLICATION_PAD_LEFT + 2 * BUFFER_SIZE + 2 * FILTER_SIZE - 1]; + } + + // Apply downsample strided convolution (assuming stride=2) from intermediates + #pragma unroll + for (int it = 0; it < BUFFER_SIZE; it += 1) + { + input_t acc = 0.0; + #pragma unroll + for (int f_idx = 0; f_idx < FILTER_SIZE; f_idx += 1) + { + // Add constant DOWNSAMPLE_REPLICATION_PAD_RIGHT to match torch implementation + acc += down_filter[f_idx] * intermediates[it * 2 + f_idx + DOWNSAMPLE_REPLICATION_PAD_RIGHT]; + } + output[it] = acc; + } + + // Write output to dst + #pragma unroll + for (int it = 0; it < BUFFER_SIZE; it += ELEMENTS_PER_LDG_STG) + { + int element_index = seq_offset + it; + if (element_index < seq_len) + { + dst[it] = output[it]; + } + } + + } + + template + void dispatch_anti_alias_activation_forward( + output_t *dst, + const input_t *src, + const input_t *up_ftr, + const input_t *down_ftr, + const input_t *alpha, + const input_t *beta, + int batch_size, + int channels, + int seq_len) + { + if (seq_len == 0) + { + return; + } + else + { + // Use 128 threads per block to maximimize gpu utilization + constexpr int threads_per_block = 128; + constexpr int seq_len_per_block = 4096; + int blocks_per_seq_len = (seq_len + seq_len_per_block - 1) / seq_len_per_block; + dim3 blocks(blocks_per_seq_len, channels, batch_size); + dim3 threads(threads_per_block, 1, 1); + + anti_alias_activation_forward + <<>>(dst, src, up_ftr, down_ftr, alpha, beta, batch_size, channels, seq_len); + } + } +} + +extern "C" torch::Tensor fwd_cuda(torch::Tensor const &input, torch::Tensor const &up_filter, torch::Tensor const &down_filter, torch::Tensor const &alpha, torch::Tensor const &beta) +{ + // Input is a 3d tensor with dimensions [batches, channels, seq_len] + const int batches = input.size(0); + const int channels = input.size(1); + const int seq_len = input.size(2); + + // Output + auto act_options = input.options().requires_grad(false); + + torch::Tensor anti_alias_activation_results = + torch::empty({batches, channels, seq_len}, act_options); + + void *input_ptr = static_cast(input.data_ptr()); + void *up_filter_ptr = static_cast(up_filter.data_ptr()); + void *down_filter_ptr = static_cast(down_filter.data_ptr()); + void *alpha_ptr = static_cast(alpha.data_ptr()); + void *beta_ptr = static_cast(beta.data_ptr()); + void *anti_alias_activation_results_ptr = static_cast(anti_alias_activation_results.data_ptr()); + + DISPATCH_FLOAT_HALF_AND_BFLOAT( + input.scalar_type(), + "dispatch anti alias activation_forward", + dispatch_anti_alias_activation_forward( + reinterpret_cast(anti_alias_activation_results_ptr), + reinterpret_cast(input_ptr), + reinterpret_cast(up_filter_ptr), + reinterpret_cast(down_filter_ptr), + reinterpret_cast(alpha_ptr), + reinterpret_cast(beta_ptr), + batches, + channels, + seq_len);); + return anti_alias_activation_results; +} \ No newline at end of file diff --git a/modules/bigvgan/alias_free_activation/cuda/compat.h b/modules/bigvgan/alias_free_activation/cuda/compat.h new file mode 100644 index 0000000000000000000000000000000000000000..25818b2edf4cb0dc9130e62c7c4de8d16a01baa5 --- /dev/null +++ b/modules/bigvgan/alias_free_activation/cuda/compat.h @@ -0,0 +1,29 @@ +/* coding=utf-8 + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*This code is copied fron NVIDIA apex: + * https://github.com/NVIDIA/apex + * with minor changes. */ + +#ifndef TORCH_CHECK +#define TORCH_CHECK AT_CHECK +#endif + +#ifdef VERSION_GE_1_3 +#define DATA_PTR data_ptr +#else +#define DATA_PTR data +#endif diff --git a/modules/bigvgan/alias_free_activation/cuda/load.py b/modules/bigvgan/alias_free_activation/cuda/load.py new file mode 100644 index 0000000000000000000000000000000000000000..ca5d01de398249e75e9e2298958764acb436edba --- /dev/null +++ b/modules/bigvgan/alias_free_activation/cuda/load.py @@ -0,0 +1,86 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +import os +import pathlib +import subprocess + +from torch.utils import cpp_extension + +""" +Setting this param to a list has a problem of generating different compilation commands (with diferent order of architectures) and leading to recompilation of fused kernels. +Set it to empty stringo avoid recompilation and assign arch flags explicity in extra_cuda_cflags below +""" +os.environ["TORCH_CUDA_ARCH_LIST"] = "" + + +def load(): + # Check if cuda 11 is installed for compute capability 8.0 + cc_flag = [] + _, bare_metal_major, _ = _get_cuda_bare_metal_version(cpp_extension.CUDA_HOME) + if int(bare_metal_major) >= 11: + cc_flag.append("-gencode") + cc_flag.append("arch=compute_80,code=sm_80") + + # Build path + srcpath = pathlib.Path(__file__).parent.absolute() + buildpath = srcpath / "build" + _create_build_dir(buildpath) + + # Helper function to build the kernels. + def _cpp_extention_load_helper(name, sources, extra_cuda_flags): + return cpp_extension.load( + name=name, + sources=sources, + build_directory=buildpath, + extra_cflags=[ + "-O3", + ], + extra_cuda_cflags=[ + "-O3", + "-gencode", + "arch=compute_70,code=sm_70", + "--use_fast_math", + ] + + extra_cuda_flags + + cc_flag, + verbose=True, + ) + + extra_cuda_flags = [ + "-U__CUDA_NO_HALF_OPERATORS__", + "-U__CUDA_NO_HALF_CONVERSIONS__", + "--expt-relaxed-constexpr", + "--expt-extended-lambda", + ] + + sources = [ + srcpath / "anti_alias_activation.cpp", + srcpath / "anti_alias_activation_cuda.cu", + ] + anti_alias_activation_cuda = _cpp_extention_load_helper( + "anti_alias_activation_cuda", sources, extra_cuda_flags + ) + + return anti_alias_activation_cuda + + +def _get_cuda_bare_metal_version(cuda_dir): + raw_output = subprocess.check_output( + [cuda_dir + "/bin/nvcc", "-V"], universal_newlines=True + ) + output = raw_output.split() + release_idx = output.index("release") + 1 + release = output[release_idx].split(".") + bare_metal_major = release[0] + bare_metal_minor = release[1][0] + + return raw_output, bare_metal_major, bare_metal_minor + + +def _create_build_dir(buildpath): + try: + os.mkdir(buildpath) + except OSError: + if not os.path.isdir(buildpath): + print(f"Creation of the build directory {buildpath} failed") diff --git a/modules/bigvgan/alias_free_activation/cuda/type_shim.h b/modules/bigvgan/alias_free_activation/cuda/type_shim.h new file mode 100644 index 0000000000000000000000000000000000000000..5db7e8a397e982d4d30d16ab6060814b98b7ab83 --- /dev/null +++ b/modules/bigvgan/alias_free_activation/cuda/type_shim.h @@ -0,0 +1,92 @@ +/* coding=utf-8 + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "compat.h" + +#define DISPATCH_FLOAT_HALF_AND_BFLOAT(TYPE, NAME, ...) \ + switch (TYPE) \ + { \ + case at::ScalarType::Float: \ + { \ + using scalar_t = float; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::Half: \ + { \ + using scalar_t = at::Half; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::BFloat16: \ + { \ + using scalar_t = at::BFloat16; \ + __VA_ARGS__; \ + break; \ + } \ + default: \ + AT_ERROR(#NAME, " not implemented for '", toString(TYPE), "'"); \ + } + +#define DISPATCH_FLOAT_HALF_AND_BFLOAT_INOUT_TYPES(TYPEIN, TYPEOUT, NAME, ...) \ + switch (TYPEIN) \ + { \ + case at::ScalarType::Float: \ + { \ + using scalar_t_in = float; \ + switch (TYPEOUT) \ + { \ + case at::ScalarType::Float: \ + { \ + using scalar_t_out = float; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::Half: \ + { \ + using scalar_t_out = at::Half; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::BFloat16: \ + { \ + using scalar_t_out = at::BFloat16; \ + __VA_ARGS__; \ + break; \ + } \ + default: \ + AT_ERROR(#NAME, " not implemented for '", toString(TYPEOUT), "'"); \ + } \ + break; \ + } \ + case at::ScalarType::Half: \ + { \ + using scalar_t_in = at::Half; \ + using scalar_t_out = at::Half; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::BFloat16: \ + { \ + using scalar_t_in = at::BFloat16; \ + using scalar_t_out = at::BFloat16; \ + __VA_ARGS__; \ + break; \ + } \ + default: \ + AT_ERROR(#NAME, " not implemented for '", toString(TYPEIN), "'"); \ + } diff --git a/modules/bigvgan/alias_free_activation/torch/__init__.py b/modules/bigvgan/alias_free_activation/torch/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8f756ed83f87f9839e457b240f60469bc187707d --- /dev/null +++ b/modules/bigvgan/alias_free_activation/torch/__init__.py @@ -0,0 +1,6 @@ +# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0 +# LICENSE is in incl_licenses directory. + +from .filter import * +from .resample import * +from .act import * diff --git a/modules/bigvgan/alias_free_activation/torch/act.py b/modules/bigvgan/alias_free_activation/torch/act.py new file mode 100644 index 0000000000000000000000000000000000000000..a6693aac602d7b331d6149522685dd512a26d277 --- /dev/null +++ b/modules/bigvgan/alias_free_activation/torch/act.py @@ -0,0 +1,30 @@ +# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0 +# LICENSE is in incl_licenses directory. + +import torch.nn as nn +from .resample import UpSample1d, DownSample1d + + +class Activation1d(nn.Module): + def __init__( + self, + activation, + up_ratio: int = 2, + down_ratio: int = 2, + up_kernel_size: int = 12, + down_kernel_size: int = 12, + ): + super().__init__() + self.up_ratio = up_ratio + self.down_ratio = down_ratio + self.act = activation + self.upsample = UpSample1d(up_ratio, up_kernel_size) + self.downsample = DownSample1d(down_ratio, down_kernel_size) + + # x: [B,C,T] + def forward(self, x): + x = self.upsample(x) + x = self.act(x) + x = self.downsample(x) + + return x diff --git a/modules/bigvgan/alias_free_activation/torch/filter.py b/modules/bigvgan/alias_free_activation/torch/filter.py new file mode 100644 index 0000000000000000000000000000000000000000..0fa35b0d5ddf8d6cb04cd9d47364ca033cebcd32 --- /dev/null +++ b/modules/bigvgan/alias_free_activation/torch/filter.py @@ -0,0 +1,101 @@ +# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0 +# LICENSE is in incl_licenses directory. + +import torch +import torch.nn as nn +import torch.nn.functional as F +import math + +if "sinc" in dir(torch): + sinc = torch.sinc +else: + # This code is adopted from adefossez's julius.core.sinc under the MIT License + # https://adefossez.github.io/julius/julius/core.html + # LICENSE is in incl_licenses directory. + def sinc(x: torch.Tensor): + """ + Implementation of sinc, i.e. sin(pi * x) / (pi * x) + __Warning__: Different to julius.sinc, the input is multiplied by `pi`! + """ + return torch.where( + x == 0, + torch.tensor(1.0, device=x.device, dtype=x.dtype), + torch.sin(math.pi * x) / math.pi / x, + ) + + +# This code is adopted from adefossez's julius.lowpass.LowPassFilters under the MIT License +# https://adefossez.github.io/julius/julius/lowpass.html +# LICENSE is in incl_licenses directory. +def kaiser_sinc_filter1d( + cutoff, half_width, kernel_size +): # return filter [1,1,kernel_size] + even = kernel_size % 2 == 0 + half_size = kernel_size // 2 + + # For kaiser window + delta_f = 4 * half_width + A = 2.285 * (half_size - 1) * math.pi * delta_f + 7.95 + if A > 50.0: + beta = 0.1102 * (A - 8.7) + elif A >= 21.0: + beta = 0.5842 * (A - 21) ** 0.4 + 0.07886 * (A - 21.0) + else: + beta = 0.0 + window = torch.kaiser_window(kernel_size, beta=beta, periodic=False) + + # ratio = 0.5/cutoff -> 2 * cutoff = 1 / ratio + if even: + time = torch.arange(-half_size, half_size) + 0.5 + else: + time = torch.arange(kernel_size) - half_size + if cutoff == 0: + filter_ = torch.zeros_like(time) + else: + filter_ = 2 * cutoff * window * sinc(2 * cutoff * time) + """ + Normalize filter to have sum = 1, otherwise we will have a small leakage of the constant component in the input signal. + """ + filter_ /= filter_.sum() + filter = filter_.view(1, 1, kernel_size) + + return filter + + +class LowPassFilter1d(nn.Module): + def __init__( + self, + cutoff=0.5, + half_width=0.6, + stride: int = 1, + padding: bool = True, + padding_mode: str = "replicate", + kernel_size: int = 12, + ): + """ + kernel_size should be even number for stylegan3 setup, in this implementation, odd number is also possible. + """ + super().__init__() + if cutoff < -0.0: + raise ValueError("Minimum cutoff must be larger than zero.") + if cutoff > 0.5: + raise ValueError("A cutoff above 0.5 does not make sense.") + self.kernel_size = kernel_size + self.even = kernel_size % 2 == 0 + self.pad_left = kernel_size // 2 - int(self.even) + self.pad_right = kernel_size // 2 + self.stride = stride + self.padding = padding + self.padding_mode = padding_mode + filter = kaiser_sinc_filter1d(cutoff, half_width, kernel_size) + self.register_buffer("filter", filter) + + # Input [B, C, T] + def forward(self, x): + _, C, _ = x.shape + + if self.padding: + x = F.pad(x, (self.pad_left, self.pad_right), mode=self.padding_mode) + out = F.conv1d(x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C) + + return out diff --git a/modules/bigvgan/alias_free_activation/torch/resample.py b/modules/bigvgan/alias_free_activation/torch/resample.py new file mode 100644 index 0000000000000000000000000000000000000000..a35380f5a2b0767069d8e3a64e01e090299ee2ab --- /dev/null +++ b/modules/bigvgan/alias_free_activation/torch/resample.py @@ -0,0 +1,58 @@ +# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0 +# LICENSE is in incl_licenses directory. + +import torch.nn as nn +from torch.nn import functional as F +from .filter import LowPassFilter1d +from .filter import kaiser_sinc_filter1d + + +class UpSample1d(nn.Module): + def __init__(self, ratio=2, kernel_size=None): + super().__init__() + self.ratio = ratio + self.kernel_size = ( + int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size + ) + self.stride = ratio + self.pad = self.kernel_size // ratio - 1 + self.pad_left = self.pad * self.stride + (self.kernel_size - self.stride) // 2 + self.pad_right = ( + self.pad * self.stride + (self.kernel_size - self.stride + 1) // 2 + ) + filter = kaiser_sinc_filter1d( + cutoff=0.5 / ratio, half_width=0.6 / ratio, kernel_size=self.kernel_size + ) + self.register_buffer("filter", filter) + + # x: [B, C, T] + def forward(self, x): + _, C, _ = x.shape + + x = F.pad(x, (self.pad, self.pad), mode="replicate") + x = self.ratio * F.conv_transpose1d( + x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C + ) + x = x[..., self.pad_left : -self.pad_right] + + return x + + +class DownSample1d(nn.Module): + def __init__(self, ratio=2, kernel_size=None): + super().__init__() + self.ratio = ratio + self.kernel_size = ( + int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size + ) + self.lowpass = LowPassFilter1d( + cutoff=0.5 / ratio, + half_width=0.6 / ratio, + stride=ratio, + kernel_size=self.kernel_size, + ) + + def forward(self, x): + xx = self.lowpass(x) + + return xx diff --git a/modules/bigvgan/bigvgan.py b/modules/bigvgan/bigvgan.py new file mode 100644 index 0000000000000000000000000000000000000000..65f0cc4fefdf9e038beed968325da324e67fb565 --- /dev/null +++ b/modules/bigvgan/bigvgan.py @@ -0,0 +1,492 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +import os +import json +from pathlib import Path +from typing import Optional, Union, Dict + +import torch +import torch.nn as nn +from torch.nn import Conv1d, ConvTranspose1d +from torch.nn.utils import weight_norm, remove_weight_norm + +from . import activations +from .utils import init_weights, get_padding +from .alias_free_activation.torch.act import Activation1d as TorchActivation1d +from .env import AttrDict + +from huggingface_hub import PyTorchModelHubMixin, hf_hub_download + + +def load_hparams_from_json(path) -> AttrDict: + with open(path) as f: + data = f.read() + return AttrDict(json.loads(data)) + + +class AMPBlock1(torch.nn.Module): + """ + AMPBlock applies Snake / SnakeBeta activation functions with trainable parameters that control periodicity, defined for each layer. + AMPBlock1 has additional self.convs2 that contains additional Conv1d layers with a fixed dilation=1 followed by each layer in self.convs1 + + Args: + h (AttrDict): Hyperparameters. + channels (int): Number of convolution channels. + kernel_size (int): Size of the convolution kernel. Default is 3. + dilation (tuple): Dilation rates for the convolutions. Each dilation layer has two convolutions. Default is (1, 3, 5). + activation (str): Activation function type. Should be either 'snake' or 'snakebeta'. Default is None. + """ + + def __init__( + self, + h: AttrDict, + channels: int, + kernel_size: int = 3, + dilation: tuple = (1, 3, 5), + activation: str = None, + ): + super().__init__() + + self.h = h + + self.convs1 = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + stride=1, + dilation=d, + padding=get_padding(kernel_size, d), + ) + ) + for d in dilation + ] + ) + self.convs1.apply(init_weights) + + self.convs2 = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + stride=1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ) + for _ in range(len(dilation)) + ] + ) + self.convs2.apply(init_weights) + + self.num_layers = len(self.convs1) + len( + self.convs2 + ) # Total number of conv layers + + # Select which Activation1d, lazy-load cuda version to ensure backward compatibility + if self.h.get("use_cuda_kernel", False): + from .alias_free_activation.cuda.activation1d import ( + Activation1d as CudaActivation1d, + ) + + Activation1d = CudaActivation1d + else: + Activation1d = TorchActivation1d + + # Activation functions + if activation == "snake": + self.activations = nn.ModuleList( + [ + Activation1d( + activation=activations.Snake( + channels, alpha_logscale=h.snake_logscale + ) + ) + for _ in range(self.num_layers) + ] + ) + elif activation == "snakebeta": + self.activations = nn.ModuleList( + [ + Activation1d( + activation=activations.SnakeBeta( + channels, alpha_logscale=h.snake_logscale + ) + ) + for _ in range(self.num_layers) + ] + ) + else: + raise NotImplementedError( + "activation incorrectly specified. check the config file and look for 'activation'." + ) + + def forward(self, x): + acts1, acts2 = self.activations[::2], self.activations[1::2] + for c1, c2, a1, a2 in zip(self.convs1, self.convs2, acts1, acts2): + xt = a1(x) + xt = c1(xt) + xt = a2(xt) + xt = c2(xt) + x = xt + x + + return x + + def remove_weight_norm(self): + for l in self.convs1: + remove_weight_norm(l) + for l in self.convs2: + remove_weight_norm(l) + + +class AMPBlock2(torch.nn.Module): + """ + AMPBlock applies Snake / SnakeBeta activation functions with trainable parameters that control periodicity, defined for each layer. + Unlike AMPBlock1, AMPBlock2 does not contain extra Conv1d layers with fixed dilation=1 + + Args: + h (AttrDict): Hyperparameters. + channels (int): Number of convolution channels. + kernel_size (int): Size of the convolution kernel. Default is 3. + dilation (tuple): Dilation rates for the convolutions. Each dilation layer has two convolutions. Default is (1, 3, 5). + activation (str): Activation function type. Should be either 'snake' or 'snakebeta'. Default is None. + """ + + def __init__( + self, + h: AttrDict, + channels: int, + kernel_size: int = 3, + dilation: tuple = (1, 3, 5), + activation: str = None, + ): + super().__init__() + + self.h = h + + self.convs = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + stride=1, + dilation=d, + padding=get_padding(kernel_size, d), + ) + ) + for d in dilation + ] + ) + self.convs.apply(init_weights) + + self.num_layers = len(self.convs) # Total number of conv layers + + # Select which Activation1d, lazy-load cuda version to ensure backward compatibility + if self.h.get("use_cuda_kernel", False): + from .alias_free_activation.cuda.activation1d import ( + Activation1d as CudaActivation1d, + ) + + Activation1d = CudaActivation1d + else: + Activation1d = TorchActivation1d + + # Activation functions + if activation == "snake": + self.activations = nn.ModuleList( + [ + Activation1d( + activation=activations.Snake( + channels, alpha_logscale=h.snake_logscale + ) + ) + for _ in range(self.num_layers) + ] + ) + elif activation == "snakebeta": + self.activations = nn.ModuleList( + [ + Activation1d( + activation=activations.SnakeBeta( + channels, alpha_logscale=h.snake_logscale + ) + ) + for _ in range(self.num_layers) + ] + ) + else: + raise NotImplementedError( + "activation incorrectly specified. check the config file and look for 'activation'." + ) + + def forward(self, x): + for c, a in zip(self.convs, self.activations): + xt = a(x) + xt = c(xt) + x = xt + x + + def remove_weight_norm(self): + for l in self.convs: + remove_weight_norm(l) + + +class BigVGAN( + torch.nn.Module, + PyTorchModelHubMixin, + library_name="bigvgan", + repo_url="https://github.com/NVIDIA/BigVGAN", + docs_url="https://github.com/NVIDIA/BigVGAN/blob/main/README.md", + pipeline_tag="audio-to-audio", + license="mit", + tags=["neural-vocoder", "audio-generation", "arxiv:2206.04658"], +): + """ + BigVGAN is a neural vocoder model that applies anti-aliased periodic activation for residual blocks (resblocks). + New in BigVGAN-v2: it can optionally use optimized CUDA kernels for AMP (anti-aliased multi-periodicity) blocks. + + Args: + h (AttrDict): Hyperparameters. + use_cuda_kernel (bool): If set to True, loads optimized CUDA kernels for AMP. This should be used for inference only, as training is not supported with CUDA kernels. + + Note: + - The `use_cuda_kernel` parameter should be used for inference only, as training with CUDA kernels is not supported. + - Ensure that the activation function is correctly specified in the hyperparameters (h.activation). + """ + + def __init__(self, h: AttrDict, use_cuda_kernel: bool = False): + super().__init__() + self.h = h + self.h["use_cuda_kernel"] = use_cuda_kernel + + # Select which Activation1d, lazy-load cuda version to ensure backward compatibility + if self.h.get("use_cuda_kernel", False): + from .alias_free_activation.cuda.activation1d import ( + Activation1d as CudaActivation1d, + ) + + Activation1d = CudaActivation1d + else: + Activation1d = TorchActivation1d + + self.num_kernels = len(h.resblock_kernel_sizes) + self.num_upsamples = len(h.upsample_rates) + + # Pre-conv + self.conv_pre = weight_norm( + Conv1d(h.num_mels, h.upsample_initial_channel, 7, 1, padding=3) + ) + + # Define which AMPBlock to use. BigVGAN uses AMPBlock1 as default + if h.resblock == "1": + resblock_class = AMPBlock1 + elif h.resblock == "2": + resblock_class = AMPBlock2 + else: + raise ValueError( + f"Incorrect resblock class specified in hyperparameters. Got {h.resblock}" + ) + + # Transposed conv-based upsamplers. does not apply anti-aliasing + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(h.upsample_rates, h.upsample_kernel_sizes)): + self.ups.append( + nn.ModuleList( + [ + weight_norm( + ConvTranspose1d( + h.upsample_initial_channel // (2 ** i), + h.upsample_initial_channel // (2 ** (i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ] + ) + ) + + # Residual blocks using anti-aliased multi-periodicity composition modules (AMP) + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = h.upsample_initial_channel // (2 ** (i + 1)) + for j, (k, d) in enumerate( + zip(h.resblock_kernel_sizes, h.resblock_dilation_sizes) + ): + self.resblocks.append( + resblock_class(h, ch, k, d, activation=h.activation) + ) + + # Post-conv + activation_post = ( + activations.Snake(ch, alpha_logscale=h.snake_logscale) + if h.activation == "snake" + else ( + activations.SnakeBeta(ch, alpha_logscale=h.snake_logscale) + if h.activation == "snakebeta" + else None + ) + ) + if activation_post is None: + raise NotImplementedError( + "activation incorrectly specified. check the config file and look for 'activation'." + ) + + self.activation_post = Activation1d(activation=activation_post) + + # Whether to use bias for the final conv_post. Default to True for backward compatibility + self.use_bias_at_final = h.get("use_bias_at_final", True) + self.conv_post = weight_norm( + Conv1d(ch, 1, 7, 1, padding=3, bias=self.use_bias_at_final) + ) + + # Weight initialization + for i in range(len(self.ups)): + self.ups[i].apply(init_weights) + self.conv_post.apply(init_weights) + + # Final tanh activation. Defaults to True for backward compatibility + self.use_tanh_at_final = h.get("use_tanh_at_final", True) + + def forward(self, x): + # Pre-conv + x = self.conv_pre(x) + + for i in range(self.num_upsamples): + # Upsampling + for i_up in range(len(self.ups[i])): + x = self.ups[i][i_up](x) + # AMP blocks + xs = None + for j in range(self.num_kernels): + if xs is None: + xs = self.resblocks[i * self.num_kernels + j](x) + else: + xs += self.resblocks[i * self.num_kernels + j](x) + x = xs / self.num_kernels + + # Post-conv + x = self.activation_post(x) + x = self.conv_post(x) + # Final tanh activation + if self.use_tanh_at_final: + x = torch.tanh(x) + else: + x = torch.clamp(x, min=-1.0, max=1.0) # Bound the output to [-1, 1] + + return x + + def remove_weight_norm(self): + try: + print("Removing weight norm...") + for l in self.ups: + for l_i in l: + remove_weight_norm(l_i) + for l in self.resblocks: + l.remove_weight_norm() + remove_weight_norm(self.conv_pre) + remove_weight_norm(self.conv_post) + except ValueError: + print("[INFO] Model already removed weight norm. Skipping!") + pass + + # Additional methods for huggingface_hub support + def _save_pretrained(self, save_directory: Path) -> None: + """Save weights and config.json from a Pytorch model to a local directory.""" + + model_path = save_directory / "bigvgan_generator.pt" + torch.save({"generator": self.state_dict()}, model_path) + + config_path = save_directory / "config.json" + with open(config_path, "w") as config_file: + json.dump(self.h, config_file, indent=4) + + @classmethod + def _from_pretrained( + cls, + *, + model_id: str, + revision: str, + cache_dir: str, + force_download: bool, + proxies: Optional[Dict], + resume_download: bool, + local_files_only: bool, + token: Union[str, bool, None], + map_location: str = "cpu", # Additional argument + strict: bool = False, # Additional argument + use_cuda_kernel: bool = False, + **model_kwargs, + ): + """Load Pytorch pretrained weights and return the loaded model.""" + + # Download and load hyperparameters (h) used by BigVGAN + if os.path.isdir(model_id): + print("Loading config.json from local directory") + config_file = os.path.join(model_id, "config.json") + else: + config_file = hf_hub_download( + repo_id=model_id, + filename="config.json", + revision=revision, + cache_dir=cache_dir, + force_download=force_download, + proxies=proxies, + resume_download=resume_download, + token=token, + local_files_only=local_files_only, + ) + h = load_hparams_from_json(config_file) + + # instantiate BigVGAN using h + if use_cuda_kernel: + print( + f"[WARNING] You have specified use_cuda_kernel=True during BigVGAN.from_pretrained(). Only inference is supported (training is not implemented)!" + ) + print( + f"[WARNING] You need nvcc and ninja installed in your system that matches your PyTorch build is using to build the kernel. If not, the model will fail to initialize or generate incorrect waveform!" + ) + print( + f"[WARNING] For detail, see the official GitHub repository: https://github.com/NVIDIA/BigVGAN?tab=readme-ov-file#using-custom-cuda-kernel-for-synthesis" + ) + model = cls(h, use_cuda_kernel=use_cuda_kernel) + + # Download and load pretrained generator weight + if os.path.isdir(model_id): + print("Loading weights from local directory") + model_file = os.path.join(model_id, "bigvgan_generator.pt") + else: + print(f"Loading weights from {model_id}") + model_file = hf_hub_download( + repo_id=model_id, + filename="bigvgan_generator.pt", + revision=revision, + cache_dir=cache_dir, + force_download=force_download, + proxies=proxies, + resume_download=resume_download, + token=token, + local_files_only=local_files_only, + ) + + checkpoint_dict = torch.load(model_file, map_location=map_location) + + try: + model.load_state_dict(checkpoint_dict["generator"]) + except RuntimeError: + print( + f"[INFO] the pretrained checkpoint does not contain weight norm. Loading the checkpoint after removing weight norm!" + ) + model.remove_weight_norm() + model.load_state_dict(checkpoint_dict["generator"]) + + return model \ No newline at end of file diff --git a/modules/bigvgan/config.json b/modules/bigvgan/config.json new file mode 100644 index 0000000000000000000000000000000000000000..635bd8975629bd6d4b51c409986944a281cfe7be --- /dev/null +++ b/modules/bigvgan/config.json @@ -0,0 +1,63 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 32, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [4,4,2,2,2,2], + "upsample_kernel_sizes": [8,8,4,4,4,4], + "upsample_initial_channel": 1536, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "use_tanh_at_final": false, + "use_bias_at_final": false, + + "activation": "snakebeta", + "snake_logscale": true, + + "use_cqtd_instead_of_mrd": true, + "cqtd_filters": 128, + "cqtd_max_filters": 1024, + "cqtd_filters_scale": 1, + "cqtd_dilations": [1, 2, 4], + "cqtd_hop_lengths": [512, 256, 256], + "cqtd_n_octaves": [9, 9, 9], + "cqtd_bins_per_octaves": [24, 36, 48], + + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "use_multiscale_melloss": true, + "lambda_melloss": 15, + + "clip_grad_norm": 500, + + "segment_size": 65536, + "num_mels": 80, + "num_freq": 1025, + "n_fft": 1024, + "hop_size": 256, + "win_size": 1024, + + "sampling_rate": 22050, + + "fmin": 0, + "fmax": null, + "fmax_for_loss": null, + + "normalize_volume": true, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/modules/bigvgan/env.py b/modules/bigvgan/env.py new file mode 100644 index 0000000000000000000000000000000000000000..b8be238d4db710c8c9a338d336baea0138f18d1f --- /dev/null +++ b/modules/bigvgan/env.py @@ -0,0 +1,18 @@ +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +import os +import shutil + + +class AttrDict(dict): + def __init__(self, *args, **kwargs): + super(AttrDict, self).__init__(*args, **kwargs) + self.__dict__ = self + + +def build_env(config, config_name, path): + t_path = os.path.join(path, config_name) + if config != t_path: + os.makedirs(path, exist_ok=True) + shutil.copyfile(config, os.path.join(path, config_name)) \ No newline at end of file diff --git a/modules/bigvgan/meldataset.py b/modules/bigvgan/meldataset.py new file mode 100644 index 0000000000000000000000000000000000000000..5e89d7384755e725c946aff3884834e15e295a16 --- /dev/null +++ b/modules/bigvgan/meldataset.py @@ -0,0 +1,354 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +import math +import os +import random +import torch +import torch.utils.data +import numpy as np +from librosa.util import normalize +from scipy.io.wavfile import read +from librosa.filters import mel as librosa_mel_fn +import pathlib +from tqdm import tqdm + +MAX_WAV_VALUE = 32767.0 # NOTE: 32768.0 -1 to prevent int16 overflow (results in popping sound in corner cases) + + +def load_wav(full_path, sr_target): + sampling_rate, data = read(full_path) + if sampling_rate != sr_target: + raise RuntimeError( + f"Sampling rate of the file {full_path} is {sampling_rate} Hz, but the model requires {sr_target} Hz" + ) + return data, sampling_rate + + +def dynamic_range_compression(x, C=1, clip_val=1e-5): + return np.log(np.clip(x, a_min=clip_val, a_max=None) * C) + + +def dynamic_range_decompression(x, C=1): + return np.exp(x) / C + + +def dynamic_range_compression_torch(x, C=1, clip_val=1e-5): + return torch.log(torch.clamp(x, min=clip_val) * C) + + +def dynamic_range_decompression_torch(x, C=1): + return torch.exp(x) / C + + +def spectral_normalize_torch(magnitudes): + return dynamic_range_compression_torch(magnitudes) + + +def spectral_de_normalize_torch(magnitudes): + return dynamic_range_decompression_torch(magnitudes) + + +mel_basis_cache = {} +hann_window_cache = {} + + +def mel_spectrogram( + y: torch.Tensor, + n_fft: int, + num_mels: int, + sampling_rate: int, + hop_size: int, + win_size: int, + fmin: int, + fmax: int = None, + center: bool = False, +) -> torch.Tensor: + """ + Calculate the mel spectrogram of an input signal. + This function uses slaney norm for the librosa mel filterbank (using librosa.filters.mel) and uses Hann window for STFT (using torch.stft). + + Args: + y (torch.Tensor): Input signal. + n_fft (int): FFT size. + num_mels (int): Number of mel bins. + sampling_rate (int): Sampling rate of the input signal. + hop_size (int): Hop size for STFT. + win_size (int): Window size for STFT. + fmin (int): Minimum frequency for mel filterbank. + fmax (int): Maximum frequency for mel filterbank. If None, defaults to half the sampling rate (fmax = sr / 2.0) inside librosa_mel_fn + center (bool): Whether to pad the input to center the frames. Default is False. + + Returns: + torch.Tensor: Mel spectrogram. + """ + if torch.min(y) < -1.0: + print(f"[WARNING] Min value of input waveform signal is {torch.min(y)}") + if torch.max(y) > 1.0: + print(f"[WARNING] Max value of input waveform signal is {torch.max(y)}") + + device = y.device + key = f"{n_fft}_{num_mels}_{sampling_rate}_{hop_size}_{win_size}_{fmin}_{fmax}_{device}" + + if key not in mel_basis_cache: + mel = librosa_mel_fn( + sr=sampling_rate, n_fft=n_fft, n_mels=num_mels, fmin=fmin, fmax=fmax + ) + mel_basis_cache[key] = torch.from_numpy(mel).float().to(device) + hann_window_cache[key] = torch.hann_window(win_size).to(device) + + mel_basis = mel_basis_cache[key] + hann_window = hann_window_cache[key] + + padding = (n_fft - hop_size) // 2 + y = torch.nn.functional.pad( + y.unsqueeze(1), (padding, padding), mode="reflect" + ).squeeze(1) + + spec = torch.stft( + y, + n_fft, + hop_length=hop_size, + win_length=win_size, + window=hann_window, + center=center, + pad_mode="reflect", + normalized=False, + onesided=True, + return_complex=True, + ) + spec = torch.sqrt(torch.view_as_real(spec).pow(2).sum(-1) + 1e-9) + + mel_spec = torch.matmul(mel_basis, spec) + mel_spec = spectral_normalize_torch(mel_spec) + + return mel_spec + + +def get_mel_spectrogram(wav, h): + """ + Generate mel spectrogram from a waveform using given hyperparameters. + + Args: + wav (torch.Tensor): Input waveform. + h: Hyperparameters object with attributes n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax. + + Returns: + torch.Tensor: Mel spectrogram. + """ + return mel_spectrogram( + wav, + h.n_fft, + h.num_mels, + h.sampling_rate, + h.hop_size, + h.win_size, + h.fmin, + h.fmax, + ) + + +def get_dataset_filelist(a): + training_files = [] + validation_files = [] + list_unseen_validation_files = [] + + with open(a.input_training_file, "r", encoding="utf-8") as fi: + training_files = [ + os.path.join(a.input_wavs_dir, x.split("|")[0] + ".wav") + for x in fi.read().split("\n") + if len(x) > 0 + ] + print(f"first training file: {training_files[0]}") + + with open(a.input_validation_file, "r", encoding="utf-8") as fi: + validation_files = [ + os.path.join(a.input_wavs_dir, x.split("|")[0] + ".wav") + for x in fi.read().split("\n") + if len(x) > 0 + ] + print(f"first validation file: {validation_files[0]}") + + for i in range(len(a.list_input_unseen_validation_file)): + with open(a.list_input_unseen_validation_file[i], "r", encoding="utf-8") as fi: + unseen_validation_files = [ + os.path.join(a.list_input_unseen_wavs_dir[i], x.split("|")[0] + ".wav") + for x in fi.read().split("\n") + if len(x) > 0 + ] + print( + f"first unseen {i}th validation fileset: {unseen_validation_files[0]}" + ) + list_unseen_validation_files.append(unseen_validation_files) + + return training_files, validation_files, list_unseen_validation_files + + +class MelDataset(torch.utils.data.Dataset): + def __init__( + self, + training_files, + hparams, + segment_size, + n_fft, + num_mels, + hop_size, + win_size, + sampling_rate, + fmin, + fmax, + split=True, + shuffle=True, + n_cache_reuse=1, + device=None, + fmax_loss=None, + fine_tuning=False, + base_mels_path=None, + is_seen=True, + ): + self.audio_files = training_files + random.seed(1234) + if shuffle: + random.shuffle(self.audio_files) + self.hparams = hparams + self.is_seen = is_seen + if self.is_seen: + self.name = pathlib.Path(self.audio_files[0]).parts[0] + else: + self.name = "-".join(pathlib.Path(self.audio_files[0]).parts[:2]).strip("/") + + self.segment_size = segment_size + self.sampling_rate = sampling_rate + self.split = split + self.n_fft = n_fft + self.num_mels = num_mels + self.hop_size = hop_size + self.win_size = win_size + self.fmin = fmin + self.fmax = fmax + self.fmax_loss = fmax_loss + self.cached_wav = None + self.n_cache_reuse = n_cache_reuse + self._cache_ref_count = 0 + self.device = device + self.fine_tuning = fine_tuning + self.base_mels_path = base_mels_path + + print("[INFO] checking dataset integrity...") + for i in tqdm(range(len(self.audio_files))): + assert os.path.exists( + self.audio_files[i] + ), f"{self.audio_files[i]} not found" + + def __getitem__(self, index): + filename = self.audio_files[index] + if self._cache_ref_count == 0: + audio, sampling_rate = load_wav(filename, self.sampling_rate) + audio = audio / MAX_WAV_VALUE + if not self.fine_tuning: + audio = normalize(audio) * 0.95 + self.cached_wav = audio + if sampling_rate != self.sampling_rate: + raise ValueError( + f"{sampling_rate} SR doesn't match target {self.sampling_rate} SR" + ) + self._cache_ref_count = self.n_cache_reuse + else: + audio = self.cached_wav + self._cache_ref_count -= 1 + + audio = torch.FloatTensor(audio) + audio = audio.unsqueeze(0) + + if not self.fine_tuning: + if self.split: + if audio.size(1) >= self.segment_size: + max_audio_start = audio.size(1) - self.segment_size + audio_start = random.randint(0, max_audio_start) + audio = audio[:, audio_start : audio_start + self.segment_size] + else: + audio = torch.nn.functional.pad( + audio, (0, self.segment_size - audio.size(1)), "constant" + ) + + mel = mel_spectrogram( + audio, + self.n_fft, + self.num_mels, + self.sampling_rate, + self.hop_size, + self.win_size, + self.fmin, + self.fmax, + center=False, + ) + else: # Validation step + # Match audio length to self.hop_size * n for evaluation + if (audio.size(1) % self.hop_size) != 0: + audio = audio[:, : -(audio.size(1) % self.hop_size)] + mel = mel_spectrogram( + audio, + self.n_fft, + self.num_mels, + self.sampling_rate, + self.hop_size, + self.win_size, + self.fmin, + self.fmax, + center=False, + ) + assert ( + audio.shape[1] == mel.shape[2] * self.hop_size + ), f"audio shape {audio.shape} mel shape {mel.shape}" + + else: + mel = np.load( + os.path.join( + self.base_mels_path, + os.path.splitext(os.path.split(filename)[-1])[0] + ".npy", + ) + ) + mel = torch.from_numpy(mel) + + if len(mel.shape) < 3: + mel = mel.unsqueeze(0) + + if self.split: + frames_per_seg = math.ceil(self.segment_size / self.hop_size) + + if audio.size(1) >= self.segment_size: + mel_start = random.randint(0, mel.size(2) - frames_per_seg - 1) + mel = mel[:, :, mel_start : mel_start + frames_per_seg] + audio = audio[ + :, + mel_start + * self.hop_size : (mel_start + frames_per_seg) + * self.hop_size, + ] + else: + mel = torch.nn.functional.pad( + mel, (0, frames_per_seg - mel.size(2)), "constant" + ) + audio = torch.nn.functional.pad( + audio, (0, self.segment_size - audio.size(1)), "constant" + ) + + mel_loss = mel_spectrogram( + audio, + self.n_fft, + self.num_mels, + self.sampling_rate, + self.hop_size, + self.win_size, + self.fmin, + self.fmax_loss, + center=False, + ) + + return (mel.squeeze(), audio.squeeze(0), filename, mel_loss.squeeze()) + + def __len__(self): + return len(self.audio_files) diff --git a/modules/bigvgan/utils.py b/modules/bigvgan/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..da98a24cf1447778305563f8e909f30b06e06b26 --- /dev/null +++ b/modules/bigvgan/utils.py @@ -0,0 +1,99 @@ +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +import glob +import os +import matplotlib +import torch +from torch.nn.utils import weight_norm + +matplotlib.use("Agg") +import matplotlib.pylab as plt +from .meldataset import MAX_WAV_VALUE +from scipy.io.wavfile import write + + +def plot_spectrogram(spectrogram): + fig, ax = plt.subplots(figsize=(10, 2)) + im = ax.imshow(spectrogram, aspect="auto", origin="lower", interpolation="none") + plt.colorbar(im, ax=ax) + + fig.canvas.draw() + plt.close() + + return fig + + +def plot_spectrogram_clipped(spectrogram, clip_max=2.0): + fig, ax = plt.subplots(figsize=(10, 2)) + im = ax.imshow( + spectrogram, + aspect="auto", + origin="lower", + interpolation="none", + vmin=1e-6, + vmax=clip_max, + ) + plt.colorbar(im, ax=ax) + + fig.canvas.draw() + plt.close() + + return fig + + +def init_weights(m, mean=0.0, std=0.01): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + m.weight.data.normal_(mean, std) + + +def apply_weight_norm(m): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + weight_norm(m) + + +def get_padding(kernel_size, dilation=1): + return int((kernel_size * dilation - dilation) / 2) + + +def load_checkpoint(filepath, device): + assert os.path.isfile(filepath) + print(f"Loading '{filepath}'") + checkpoint_dict = torch.load(filepath, map_location=device) + print("Complete.") + return checkpoint_dict + + +def save_checkpoint(filepath, obj): + print(f"Saving checkpoint to {filepath}") + torch.save(obj, filepath) + print("Complete.") + + +def scan_checkpoint(cp_dir, prefix, renamed_file=None): + # Fallback to original scanning logic first + pattern = os.path.join(cp_dir, prefix + "????????") + cp_list = glob.glob(pattern) + + if len(cp_list) > 0: + last_checkpoint_path = sorted(cp_list)[-1] + print(f"[INFO] Resuming from checkpoint: '{last_checkpoint_path}'") + return last_checkpoint_path + + # If no pattern-based checkpoints are found, check for renamed file + if renamed_file: + renamed_path = os.path.join(cp_dir, renamed_file) + if os.path.isfile(renamed_path): + print(f"[INFO] Resuming from renamed checkpoint: '{renamed_file}'") + return renamed_path + + return None + + +def save_audio(audio, path, sr): + # wav: torch with 1d shape + audio = audio * MAX_WAV_VALUE + audio = audio.cpu().numpy().astype("int16") + write(path, sr, audio) diff --git a/modules/campplus/DTDNN.py b/modules/campplus/DTDNN.py new file mode 100644 index 0000000000000000000000000000000000000000..490b584e3fd3d2e37c6be382d9853c28eea8f6ce --- /dev/null +++ b/modules/campplus/DTDNN.py @@ -0,0 +1,138 @@ +# Copyright 3D-Speaker (https://github.com/alibaba-damo-academy/3D-Speaker). All Rights Reserved. +# Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + +from collections import OrderedDict + +import torch +from torch import nn +import torch.nn.functional as F + +from modules.campplus.layers import DenseLayer, StatsPool, TDNNLayer, CAMDenseTDNNBlock, TransitLayer, BasicResBlock, get_nonlinear + + +class FCM(nn.Module): + def __init__(self, + block=BasicResBlock, + num_blocks=[2, 2], + m_channels=32, + feat_dim=80): + super(FCM, self).__init__() + self.in_planes = m_channels + self.conv1 = nn.Conv2d(1, m_channels, kernel_size=3, stride=1, padding=1, bias=False) + self.bn1 = nn.BatchNorm2d(m_channels) + + self.layer1 = self._make_layer(block, m_channels, num_blocks[0], stride=2) + self.layer2 = self._make_layer(block, m_channels, num_blocks[1], stride=2) + + self.conv2 = nn.Conv2d(m_channels, m_channels, kernel_size=3, stride=(2, 1), padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(m_channels) + self.out_channels = m_channels * (feat_dim // 8) + + def _make_layer(self, block, planes, num_blocks, stride): + strides = [stride] + [1] * (num_blocks - 1) + layers = [] + for stride in strides: + layers.append(block(self.in_planes, planes, stride)) + self.in_planes = planes * block.expansion + return nn.Sequential(*layers) + + def forward(self, x): + x = x.unsqueeze(1) + out = F.relu(self.bn1(self.conv1(x))) + out = self.layer1(out) + out = self.layer2(out) + out = F.relu(self.bn2(self.conv2(out))) + + shape = out.shape + out = out.reshape(shape[0], shape[1]*shape[2], shape[3]) + return out + +class CAMPPlus(nn.Module): + def __init__(self, + feat_dim=80, + embedding_size=512, + growth_rate=32, + bn_size=4, + init_channels=128, + config_str='batchnorm-relu', + memory_efficient=True): + super(CAMPPlus, self).__init__() + + self.head = FCM(feat_dim=feat_dim) + channels = self.head.out_channels + + self.xvector = nn.Sequential( + OrderedDict([ + + ('tdnn', + TDNNLayer(channels, + init_channels, + 5, + stride=2, + dilation=1, + padding=-1, + config_str=config_str)), + ])) + channels = init_channels + for i, (num_layers, kernel_size, + dilation) in enumerate(zip((12, 24, 16), (3, 3, 3), (1, 2, 2))): + block = CAMDenseTDNNBlock(num_layers=num_layers, + in_channels=channels, + out_channels=growth_rate, + bn_channels=bn_size * growth_rate, + kernel_size=kernel_size, + dilation=dilation, + config_str=config_str, + memory_efficient=memory_efficient) + self.xvector.add_module('block%d' % (i + 1), block) + channels = channels + num_layers * growth_rate + self.xvector.add_module( + 'transit%d' % (i + 1), + TransitLayer(channels, + channels // 2, + bias=False, + config_str=config_str)) + channels //= 2 + + self.xvector.add_module( + 'out_nonlinear', get_nonlinear(config_str, channels)) + + # self.xvector.add_module('stats', StatsPool()) + # self.xvector.add_module( + # 'dense', + # DenseLayer(channels * 2, embedding_size, config_str='batchnorm_')) + self.stats = StatsPool() + self.dense = DenseLayer(channels * 2, embedding_size, config_str='batchnorm_') + + for m in self.modules(): + if isinstance(m, (nn.Conv1d, nn.Linear)): + nn.init.kaiming_normal_(m.weight.data) + if m.bias is not None: + nn.init.zeros_(m.bias) + + def load_state_dict(self, state_dict, strict=True): + """ + Custom load_state_dict that remaps keys from a previous version of the model where + stats and dense layers were part of xvector. + """ + new_state_dict = {} + + # Remap keys for compatibility + for key in state_dict.keys(): + new_key = key + if key.startswith('xvector.stats'): + new_key = key.replace('xvector.stats', 'stats') + elif key.startswith('xvector.dense'): + new_key = key.replace('xvector.dense', 'dense') + new_state_dict[new_key] = state_dict[key] + + # Call the original load_state_dict with the modified state_dict + super(CAMPPlus, self).load_state_dict(new_state_dict, strict) + + def forward(self, x, x_lens=None): + x = x.permute(0, 2, 1) # (B,T,F) => (B,F,T) + x = self.head(x) + x = self.xvector(x) + x = self.stats(x, x_lens) + x = self.dense(x) + return x \ No newline at end of file diff --git a/modules/campplus/__init__.py b/modules/campplus/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/campplus/classifier.py b/modules/campplus/classifier.py new file mode 100644 index 0000000000000000000000000000000000000000..5c709e7da673ac43fdc41c4d5babdde26368f6a4 --- /dev/null +++ b/modules/campplus/classifier.py @@ -0,0 +1,70 @@ +# Copyright 3D-Speaker (https://github.com/alibaba-damo-academy/3D-Speaker). All Rights Reserved. +# Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from modules.campplus.layers import DenseLayer + + +class CosineClassifier(nn.Module): + def __init__( + self, + input_dim, + num_blocks=0, + inter_dim=512, + out_neurons=1000, + ): + + super().__init__() + self.blocks = nn.ModuleList() + + for index in range(num_blocks): + self.blocks.append( + DenseLayer(input_dim, inter_dim, config_str='batchnorm') + ) + input_dim = inter_dim + + self.weight = nn.Parameter( + torch.FloatTensor(out_neurons, input_dim) + ) + nn.init.xavier_uniform_(self.weight) + + def forward(self, x): + # x: [B, dim] + for layer in self.blocks: + x = layer(x) + + # normalized + x = F.linear(F.normalize(x), F.normalize(self.weight)) + return x + +class LinearClassifier(nn.Module): + def __init__( + self, + input_dim, + num_blocks=0, + inter_dim=512, + out_neurons=1000, + ): + + super().__init__() + self.blocks = nn.ModuleList() + + self.nonlinear = nn.ReLU(inplace=True) + for index in range(num_blocks): + self.blocks.append( + DenseLayer(input_dim, inter_dim, bias=True) + ) + input_dim = inter_dim + + self.linear = nn.Linear(input_dim, out_neurons, bias=True) + + def forward(self, x): + # x: [B, dim] + x = self.nonlinear(x) + for layer in self.blocks: + x = layer(x) + x = self.linear(x) + return x \ No newline at end of file diff --git a/modules/campplus/layers.py b/modules/campplus/layers.py new file mode 100644 index 0000000000000000000000000000000000000000..97945979ff7ac8ede4f1f53ff3b4d999fc4829d2 --- /dev/null +++ b/modules/campplus/layers.py @@ -0,0 +1,267 @@ +# Copyright 3D-Speaker (https://github.com/alibaba-damo-academy/3D-Speaker). All Rights Reserved. +# Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + +import torch +import torch.nn.functional as F +import torch.utils.checkpoint as cp +from torch import nn + + +def get_nonlinear(config_str, channels): + nonlinear = nn.Sequential() + for name in config_str.split('-'): + if name == 'relu': + nonlinear.add_module('relu', nn.ReLU(inplace=True)) + elif name == 'prelu': + nonlinear.add_module('prelu', nn.PReLU(channels)) + elif name == 'batchnorm': + nonlinear.add_module('batchnorm', nn.BatchNorm1d(channels)) + elif name == 'batchnorm_': + nonlinear.add_module('batchnorm', + nn.BatchNorm1d(channels, affine=False)) + else: + raise ValueError('Unexpected module ({}).'.format(name)) + return nonlinear + +def statistics_pooling(x, dim=-1, keepdim=False, unbiased=True, eps=1e-2): + mean = x.mean(dim=dim) + std = x.std(dim=dim, unbiased=unbiased) + stats = torch.cat([mean, std], dim=-1) + if keepdim: + stats = stats.unsqueeze(dim=dim) + return stats + +def masked_statistics_pooling(x, x_lens, dim=-1, keepdim=False, unbiased=True, eps=1e-2): + stats = [] + for i, x_len in enumerate(x_lens): + x_i = x[i, :, :x_len] + mean = x_i.mean(dim=dim) + std = x_i.std(dim=dim, unbiased=unbiased) + stats.append(torch.cat([mean, std], dim=-1)) + stats = torch.stack(stats, dim=0) + if keepdim: + stats = stats.unsqueeze(dim=dim) + return stats + + +class StatsPool(nn.Module): + def forward(self, x, x_lens=None): + if x_lens is not None: + return masked_statistics_pooling(x, x_lens) + return statistics_pooling(x) + + +class TDNNLayer(nn.Module): + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + bias=False, + config_str='batchnorm-relu'): + super(TDNNLayer, self).__init__() + if padding < 0: + assert kernel_size % 2 == 1, 'Expect equal paddings, but got even kernel size ({})'.format( + kernel_size) + padding = (kernel_size - 1) // 2 * dilation + self.linear = nn.Conv1d(in_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + self.nonlinear = get_nonlinear(config_str, out_channels) + + def forward(self, x): + x = self.linear(x) + x = self.nonlinear(x) + return x + + +class CAMLayer(nn.Module): + def __init__(self, + bn_channels, + out_channels, + kernel_size, + stride, + padding, + dilation, + bias, + reduction=2): + super(CAMLayer, self).__init__() + self.linear_local = nn.Conv1d(bn_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + self.linear1 = nn.Conv1d(bn_channels, bn_channels // reduction, 1) + self.relu = nn.ReLU(inplace=True) + self.linear2 = nn.Conv1d(bn_channels // reduction, out_channels, 1) + self.sigmoid = nn.Sigmoid() + + def forward(self, x): + y = self.linear_local(x) + context = x.mean(-1, keepdim=True)+self.seg_pooling(x) + context = self.relu(self.linear1(context)) + m = self.sigmoid(self.linear2(context)) + return y*m + + def seg_pooling(self, x, seg_len=100, stype='avg'): + if stype == 'avg': + seg = F.avg_pool1d(x, kernel_size=seg_len, stride=seg_len, ceil_mode=True) + elif stype == 'max': + seg = F.max_pool1d(x, kernel_size=seg_len, stride=seg_len, ceil_mode=True) + else: + raise ValueError('Wrong segment pooling type.') + shape = seg.shape + seg = seg.unsqueeze(-1).expand(*shape, seg_len).reshape(*shape[:-1], -1) + seg = seg[..., :x.shape[-1]] + return seg + + +class CAMDenseTDNNLayer(nn.Module): + def __init__(self, + in_channels, + out_channels, + bn_channels, + kernel_size, + stride=1, + dilation=1, + bias=False, + config_str='batchnorm-relu', + memory_efficient=False): + super(CAMDenseTDNNLayer, self).__init__() + assert kernel_size % 2 == 1, 'Expect equal paddings, but got even kernel size ({})'.format( + kernel_size) + padding = (kernel_size - 1) // 2 * dilation + self.memory_efficient = memory_efficient + self.nonlinear1 = get_nonlinear(config_str, in_channels) + self.linear1 = nn.Conv1d(in_channels, bn_channels, 1, bias=False) + self.nonlinear2 = get_nonlinear(config_str, bn_channels) + self.cam_layer = CAMLayer(bn_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + def bn_function(self, x): + return self.linear1(self.nonlinear1(x)) + + def forward(self, x): + if self.training and self.memory_efficient: + x = cp.checkpoint(self.bn_function, x) + else: + x = self.bn_function(x) + x = self.cam_layer(self.nonlinear2(x)) + return x + + +class CAMDenseTDNNBlock(nn.ModuleList): + def __init__(self, + num_layers, + in_channels, + out_channels, + bn_channels, + kernel_size, + stride=1, + dilation=1, + bias=False, + config_str='batchnorm-relu', + memory_efficient=False): + super(CAMDenseTDNNBlock, self).__init__() + for i in range(num_layers): + layer = CAMDenseTDNNLayer(in_channels=in_channels + i * out_channels, + out_channels=out_channels, + bn_channels=bn_channels, + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + bias=bias, + config_str=config_str, + memory_efficient=memory_efficient) + self.add_module('tdnnd%d' % (i + 1), layer) + + def forward(self, x): + for layer in self: + x = torch.cat([x, layer(x)], dim=1) + return x + + +class TransitLayer(nn.Module): + def __init__(self, + in_channels, + out_channels, + bias=True, + config_str='batchnorm-relu'): + super(TransitLayer, self).__init__() + self.nonlinear = get_nonlinear(config_str, in_channels) + self.linear = nn.Conv1d(in_channels, out_channels, 1, bias=bias) + + def forward(self, x): + x = self.nonlinear(x) + x = self.linear(x) + return x + + +class DenseLayer(nn.Module): + def __init__(self, + in_channels, + out_channels, + bias=False, + config_str='batchnorm-relu'): + super(DenseLayer, self).__init__() + self.linear = nn.Conv1d(in_channels, out_channels, 1, bias=bias) + self.nonlinear = get_nonlinear(config_str, out_channels) + + def forward(self, x): + if len(x.shape) == 2: + x = self.linear(x.unsqueeze(dim=-1)).squeeze(dim=-1) + else: + x = self.linear(x) + x = self.nonlinear(x) + return x + + +class BasicResBlock(nn.Module): + expansion = 1 + + def __init__(self, in_planes, planes, stride=1): + super(BasicResBlock, self).__init__() + self.conv1 = nn.Conv2d(in_planes, + planes, + kernel_size=3, + stride=(stride, 1), + padding=1, + bias=False) + self.bn1 = nn.BatchNorm2d(planes) + self.conv2 = nn.Conv2d(planes, + planes, + kernel_size=3, + stride=1, + padding=1, + bias=False) + self.bn2 = nn.BatchNorm2d(planes) + + self.shortcut = nn.Sequential() + if stride != 1 or in_planes != self.expansion * planes: + self.shortcut = nn.Sequential( + nn.Conv2d(in_planes, + self.expansion * planes, + kernel_size=1, + stride=(stride, 1), + bias=False), + nn.BatchNorm2d(self.expansion * planes)) + + def forward(self, x): + out = F.relu(self.bn1(self.conv1(x))) + out = self.bn2(self.conv2(out)) + out += self.shortcut(x) + out = F.relu(out) + return out \ No newline at end of file diff --git a/modules/commons.py b/modules/commons.py new file mode 100644 index 0000000000000000000000000000000000000000..4af3fecc7fda93d647f49723e1089a791ec52ed2 --- /dev/null +++ b/modules/commons.py @@ -0,0 +1,476 @@ +import math +import numpy as np +import torch +from torch import nn +from torch.nn import functional as F +from munch import Munch +import json +import argparse + +def str2bool(v): + if isinstance(v, bool): + return v + if v.lower() in ("yes", "true", "t", "y", "1"): + return True + elif v.lower() in ("no", "false", "f", "n", "0"): + return False + else: + raise argparse.ArgumentTypeError("Boolean value expected.") + +class AttrDict(dict): + def __init__(self, *args, **kwargs): + super(AttrDict, self).__init__(*args, **kwargs) + self.__dict__ = self + + +def init_weights(m, mean=0.0, std=0.01): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + m.weight.data.normal_(mean, std) + + +def get_padding(kernel_size, dilation=1): + return int((kernel_size * dilation - dilation) / 2) + + +def convert_pad_shape(pad_shape): + l = pad_shape[::-1] + pad_shape = [item for sublist in l for item in sublist] + return pad_shape + + +def intersperse(lst, item): + result = [item] * (len(lst) * 2 + 1) + result[1::2] = lst + return result + + +def kl_divergence(m_p, logs_p, m_q, logs_q): + """KL(P||Q)""" + kl = (logs_q - logs_p) - 0.5 + kl += ( + 0.5 * (torch.exp(2.0 * logs_p) + ((m_p - m_q) ** 2)) * torch.exp(-2.0 * logs_q) + ) + return kl + + +def rand_gumbel(shape): + """Sample from the Gumbel distribution, protect from overflows.""" + uniform_samples = torch.rand(shape) * 0.99998 + 0.00001 + return -torch.log(-torch.log(uniform_samples)) + + +def rand_gumbel_like(x): + g = rand_gumbel(x.size()).to(dtype=x.dtype, device=x.device) + return g + + +def slice_segments(x, ids_str, segment_size=4): + ret = torch.zeros_like(x[:, :, :segment_size]) + for i in range(x.size(0)): + idx_str = ids_str[i] + idx_end = idx_str + segment_size + ret[i] = x[i, :, idx_str:idx_end] + return ret + + +def slice_segments_audio(x, ids_str, segment_size=4): + ret = torch.zeros_like(x[:, :segment_size]) + for i in range(x.size(0)): + idx_str = ids_str[i] + idx_end = idx_str + segment_size + ret[i] = x[i, idx_str:idx_end] + return ret + + +def rand_slice_segments(x, x_lengths=None, segment_size=4): + b, d, t = x.size() + if x_lengths is None: + x_lengths = t + ids_str_max = x_lengths - segment_size + 1 + ids_str = ((torch.rand([b]).to(device=x.device) * ids_str_max).clip(0)).to( + dtype=torch.long + ) + ret = slice_segments(x, ids_str, segment_size) + return ret, ids_str + + +def get_timing_signal_1d(length, channels, min_timescale=1.0, max_timescale=1.0e4): + position = torch.arange(length, dtype=torch.float) + num_timescales = channels // 2 + log_timescale_increment = math.log(float(max_timescale) / float(min_timescale)) / ( + num_timescales - 1 + ) + inv_timescales = min_timescale * torch.exp( + torch.arange(num_timescales, dtype=torch.float) * -log_timescale_increment + ) + scaled_time = position.unsqueeze(0) * inv_timescales.unsqueeze(1) + signal = torch.cat([torch.sin(scaled_time), torch.cos(scaled_time)], 0) + signal = F.pad(signal, [0, 0, 0, channels % 2]) + signal = signal.view(1, channels, length) + return signal + + +def add_timing_signal_1d(x, min_timescale=1.0, max_timescale=1.0e4): + b, channels, length = x.size() + signal = get_timing_signal_1d(length, channels, min_timescale, max_timescale) + return x + signal.to(dtype=x.dtype, device=x.device) + + +def cat_timing_signal_1d(x, min_timescale=1.0, max_timescale=1.0e4, axis=1): + b, channels, length = x.size() + signal = get_timing_signal_1d(length, channels, min_timescale, max_timescale) + return torch.cat([x, signal.to(dtype=x.dtype, device=x.device)], axis) + + +def subsequent_mask(length): + mask = torch.tril(torch.ones(length, length)).unsqueeze(0).unsqueeze(0) + return mask + + +@torch.jit.script +def fused_add_tanh_sigmoid_multiply(input_a, input_b, n_channels): + n_channels_int = n_channels[0] + in_act = input_a + input_b + t_act = torch.tanh(in_act[:, :n_channels_int, :]) + s_act = torch.sigmoid(in_act[:, n_channels_int:, :]) + acts = t_act * s_act + return acts + + +def convert_pad_shape(pad_shape): + l = pad_shape[::-1] + pad_shape = [item for sublist in l for item in sublist] + return pad_shape + + +def shift_1d(x): + x = F.pad(x, convert_pad_shape([[0, 0], [0, 0], [1, 0]]))[:, :, :-1] + return x + + +def sequence_mask(length, max_length=None): + if max_length is None: + max_length = length.max() + x = torch.arange(max_length, dtype=length.dtype, device=length.device) + return x.unsqueeze(0) < length.unsqueeze(1) + + +def avg_with_mask(x, mask): + assert mask.dtype == torch.float, "Mask should be float" + + if mask.ndim == 2: + mask = mask.unsqueeze(1) + + if mask.shape[1] == 1: + mask = mask.expand_as(x) + + return (x * mask).sum() / mask.sum() + + +def generate_path(duration, mask): + """ + duration: [b, 1, t_x] + mask: [b, 1, t_y, t_x] + """ + device = duration.device + + b, _, t_y, t_x = mask.shape + cum_duration = torch.cumsum(duration, -1) + + cum_duration_flat = cum_duration.view(b * t_x) + path = sequence_mask(cum_duration_flat, t_y).to(mask.dtype) + path = path.view(b, t_x, t_y) + path = path - F.pad(path, convert_pad_shape([[0, 0], [1, 0], [0, 0]]))[:, :-1] + path = path.unsqueeze(1).transpose(2, 3) * mask + return path + + +def clip_grad_value_(parameters, clip_value, norm_type=2): + if isinstance(parameters, torch.Tensor): + parameters = [parameters] + parameters = list(filter(lambda p: p.grad is not None, parameters)) + norm_type = float(norm_type) + if clip_value is not None: + clip_value = float(clip_value) + + total_norm = 0 + for p in parameters: + param_norm = p.grad.data.norm(norm_type) + total_norm += param_norm.item() ** norm_type + if clip_value is not None: + p.grad.data.clamp_(min=-clip_value, max=clip_value) + total_norm = total_norm ** (1.0 / norm_type) + return total_norm + + +def log_norm(x, mean=-4, std=4, dim=2): + """ + normalized log mel -> mel -> norm -> log(norm) + """ + x = torch.log(torch.exp(x * std + mean).norm(dim=dim)) + return x + + +def load_F0_models(path): + # load F0 model + from .JDC.model import JDCNet + + F0_model = JDCNet(num_class=1, seq_len=192) + params = torch.load(path, map_location="cpu")["net"] + F0_model.load_state_dict(params) + _ = F0_model.train() + + return F0_model + + +def modify_w2v_forward(self, output_layer=15): + """ + change forward method of w2v encoder to get its intermediate layer output + :param self: + :param layer: + :return: + """ + from transformers.modeling_outputs import BaseModelOutput + + def forward( + hidden_states, + attention_mask=None, + output_attentions=False, + output_hidden_states=False, + return_dict=True, + ): + all_hidden_states = () if output_hidden_states else None + all_self_attentions = () if output_attentions else None + + conv_attention_mask = attention_mask + if attention_mask is not None: + # make sure padded tokens output 0 + hidden_states = hidden_states.masked_fill( + ~attention_mask.bool().unsqueeze(-1), 0.0 + ) + + # extend attention_mask + attention_mask = 1.0 - attention_mask[:, None, None, :].to( + dtype=hidden_states.dtype + ) + attention_mask = attention_mask * torch.finfo(hidden_states.dtype).min + attention_mask = attention_mask.expand( + attention_mask.shape[0], + 1, + attention_mask.shape[-1], + attention_mask.shape[-1], + ) + + hidden_states = self.dropout(hidden_states) + + if self.embed_positions is not None: + relative_position_embeddings = self.embed_positions(hidden_states) + else: + relative_position_embeddings = None + + deepspeed_zero3_is_enabled = False + + for i, layer in enumerate(self.layers): + if output_hidden_states: + all_hidden_states = all_hidden_states + (hidden_states,) + + # add LayerDrop (see https://arxiv.org/abs/1909.11556 for description) + dropout_probability = torch.rand([]) + + skip_the_layer = ( + True + if self.training and (dropout_probability < self.config.layerdrop) + else False + ) + if not skip_the_layer or deepspeed_zero3_is_enabled: + # under deepspeed zero3 all gpus must run in sync + if self.gradient_checkpointing and self.training: + layer_outputs = self._gradient_checkpointing_func( + layer.__call__, + hidden_states, + attention_mask, + relative_position_embeddings, + output_attentions, + conv_attention_mask, + ) + else: + layer_outputs = layer( + hidden_states, + attention_mask=attention_mask, + relative_position_embeddings=relative_position_embeddings, + output_attentions=output_attentions, + conv_attention_mask=conv_attention_mask, + ) + hidden_states = layer_outputs[0] + + if skip_the_layer: + layer_outputs = (None, None) + + if output_attentions: + all_self_attentions = all_self_attentions + (layer_outputs[1],) + + if i == output_layer - 1: + break + + if output_hidden_states: + all_hidden_states = all_hidden_states + (hidden_states,) + + if not return_dict: + return tuple( + v + for v in [hidden_states, all_hidden_states, all_self_attentions] + if v is not None + ) + return BaseModelOutput( + last_hidden_state=hidden_states, + hidden_states=all_hidden_states, + attentions=all_self_attentions, + ) + + return forward + + +MATPLOTLIB_FLAG = False + + +def plot_spectrogram_to_numpy(spectrogram): + global MATPLOTLIB_FLAG + if not MATPLOTLIB_FLAG: + import matplotlib + import logging + + matplotlib.use("Agg") + MATPLOTLIB_FLAG = True + mpl_logger = logging.getLogger("matplotlib") + mpl_logger.setLevel(logging.WARNING) + import matplotlib.pylab as plt + import numpy as np + + fig, ax = plt.subplots(figsize=(10, 2)) + im = ax.imshow(spectrogram, aspect="auto", origin="lower", interpolation="none") + plt.colorbar(im, ax=ax) + plt.xlabel("Frames") + plt.ylabel("Channels") + plt.tight_layout() + + fig.canvas.draw() + data = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep="") + data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,)) + plt.close() + return data + + +def normalize_f0(f0_sequence): + # Remove unvoiced frames (replace with -1) + voiced_indices = np.where(f0_sequence > 0)[0] + f0_voiced = f0_sequence[voiced_indices] + + # Convert to log scale + log_f0 = np.log2(f0_voiced) + + # Calculate mean and standard deviation + mean_f0 = np.mean(log_f0) + std_f0 = np.std(log_f0) + + # Normalize the F0 sequence + normalized_f0 = (log_f0 - mean_f0) / std_f0 + + # Create the normalized F0 sequence with unvoiced frames + normalized_sequence = np.zeros_like(f0_sequence) + normalized_sequence[voiced_indices] = normalized_f0 + normalized_sequence[f0_sequence <= 0] = -1 # Assign -1 to unvoiced frames + + return normalized_sequence + + +def build_model(args, stage="DiT"): + if stage == "DiT": + from modules.flow_matching import CFM + from modules.length_regulator import InterpolateRegulator + + length_regulator = InterpolateRegulator( + channels=args.length_regulator.channels, + sampling_ratios=args.length_regulator.sampling_ratios, + is_discrete=args.length_regulator.is_discrete, + in_channels=args.length_regulator.in_channels if hasattr(args.length_regulator, "in_channels") else None, + codebook_size=args.length_regulator.content_codebook_size, + f0_condition=args.length_regulator.f0_condition if hasattr(args.length_regulator, "f0_condition") else False, + n_f0_bins=args.length_regulator.n_f0_bins if hasattr(args.length_regulator, "n_f0_bins") else 512, + ) + cfm = CFM(args) + nets = Munch( + cfm=cfm, + length_regulator=length_regulator, + ) + else: + raise ValueError(f"Unknown stage: {stage}") + + return nets + + +def load_checkpoint( + model, + optimizer, + path, + load_only_params=True, + ignore_modules=[], + is_distributed=False, + load_ema=False, +): + state = torch.load(path, map_location="cpu") + params = state["net"] + if load_ema and "ema" in state: + print("Loading EMA") + for key in model: + i = 0 + for param_name in params[key]: + if "input_pos" in param_name: + continue + assert params[key][param_name].shape == state["ema"][key][0][i].shape + params[key][param_name] = state["ema"][key][0][i].clone() + i += 1 + for key in model: + if key in params and key not in ignore_modules: + if not is_distributed: + # strip prefix of DDP (module.), create a new OrderedDict that does not contain the prefix + for k in list(params[key].keys()): + if k.startswith("module."): + params[key][k[len("module.") :]] = params[key][k] + del params[key][k] + model_state_dict = model[key].state_dict() + # 过滤出形状匹配的键值对 + filtered_state_dict = { + k: v + for k, v in params[key].items() + if k in model_state_dict and v.shape == model_state_dict[k].shape + } + skipped_keys = set(params[key].keys()) - set(filtered_state_dict.keys()) + if skipped_keys: + print( + f"Warning: Skipped loading some keys due to shape mismatch: {skipped_keys}" + ) + print("%s loaded" % key) + model[key].load_state_dict(filtered_state_dict, strict=False) + _ = [model[key].eval() for key in model] + + if not load_only_params: + epoch = state["epoch"] + 1 + iters = state["iters"] + optimizer.load_state_dict(state["optimizer"]) + optimizer.load_scheduler_state_dict(state["scheduler"]) + + else: + epoch = 0 + iters = 0 + + return model, optimizer, epoch, iters + + +def recursive_munch(d): + if isinstance(d, dict): + return Munch((k, recursive_munch(v)) for k, v in d.items()) + elif isinstance(d, list): + return [recursive_munch(v) for v in d] + else: + return d diff --git a/modules/diffusion_transformer.py b/modules/diffusion_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..0de2aa00b7378423a25e4fcbff6ad0561ee6092c --- /dev/null +++ b/modules/diffusion_transformer.py @@ -0,0 +1,537 @@ +import torch +from torch import nn +import math + +# from modules.torchscript_modules.gpt_fast_model import ModelArgs, Transformer +from modules.wavenet import WN +from modules.commons import sequence_mask + +from torch.nn.utils import weight_norm + +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +from dataclasses import dataclass +from typing import Optional + +import torch +import torch.nn as nn +from torch import Tensor +from torch.nn import functional as F + + +def find_multiple(n: int, k: int) -> int: + if n % k == 0: + return n + return n + k - (n % k) + +class AdaptiveLayerNorm(nn.Module): + r"""Adaptive Layer Normalization""" + + def __init__(self, d_model, norm) -> None: + super(AdaptiveLayerNorm, self).__init__() + self.project_layer = nn.Linear(d_model, 2 * d_model) + self.norm = norm + self.d_model = d_model + self.eps = self.norm.eps + + def forward(self, input: Tensor, embedding: Tensor = None) -> Tensor: + if embedding is None: + return self.norm(input) + weight, bias = torch.split( + self.project_layer(embedding), + split_size_or_sections=self.d_model, + dim=-1, + ) + return weight * self.norm(input) + bias + + +@dataclass +class ModelArgs: + block_size: int = 2048 + vocab_size: int = 32000 + n_layer: int = 32 + n_head: int = 32 + dim: int = 4096 + intermediate_size: int = None + n_local_heads: int = -1 + head_dim: int = 64 + rope_base: float = 10000 + norm_eps: float = 1e-5 + has_cross_attention: bool = False + context_dim: int = 0 + uvit_skip_connection: bool = False + time_as_token: bool = False + + def __post_init__(self): + if self.n_local_heads == -1: + self.n_local_heads = self.n_head + if self.intermediate_size is None: + hidden_dim = 4 * self.dim + n_hidden = int(2 * hidden_dim / 3) + self.intermediate_size = find_multiple(n_hidden, 256) + # self.head_dim = self.dim // self.n_head + +class Transformer(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.config = config + + self.layers = nn.ModuleList(TransformerBlock(config) for _ in range(config.n_layer)) + self.norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + + self.freqs_cis: Optional[Tensor] = None + self.mask_cache: Optional[Tensor] = None + self.max_batch_size = -1 + self.max_seq_length = -1 + + def setup_caches(self, max_batch_size, max_seq_length, use_kv_cache=False): + if self.max_seq_length >= max_seq_length and self.max_batch_size >= max_batch_size: + return + head_dim = self.config.dim // self.config.n_head + max_seq_length = find_multiple(max_seq_length, 8) + self.max_seq_length = max_seq_length + self.max_batch_size = max_batch_size + dtype = self.norm.project_layer.weight.dtype + device = self.norm.project_layer.weight.device + + self.freqs_cis = precompute_freqs_cis(self.config.block_size, self.config.head_dim, + self.config.rope_base, dtype).to(device) + self.causal_mask = torch.tril(torch.ones(self.max_seq_length, self.max_seq_length, dtype=torch.bool)).to(device) + self.use_kv_cache = use_kv_cache + self.uvit_skip_connection = self.config.uvit_skip_connection + if self.uvit_skip_connection: + self.layers_emit_skip = [i for i in range(self.config.n_layer) if i < self.config.n_layer // 2] + self.layers_receive_skip = [i for i in range(self.config.n_layer) if i > self.config.n_layer // 2] + else: + self.layers_emit_skip = [] + self.layers_receive_skip = [] + + def forward(self, + x: Tensor, + c: Tensor, + input_pos: Optional[Tensor] = None, + mask: Optional[Tensor] = None, + context: Optional[Tensor] = None, + context_input_pos: Optional[Tensor] = None, + cross_attention_mask: Optional[Tensor] = None, + ) -> Tensor: + assert self.freqs_cis is not None, "Caches must be initialized first" + if mask is None: # in case of non-causal model + if not self.training and self.use_kv_cache: + mask = self.causal_mask[None, None, input_pos] + else: + mask = self.causal_mask[None, None, input_pos] + mask = mask[..., input_pos] + freqs_cis = self.freqs_cis[input_pos] + if context is not None: + context_freqs_cis = self.freqs_cis[context_input_pos] + else: + context_freqs_cis = None + skip_in_x_list = [] + for i, layer in enumerate(self.layers): + if self.uvit_skip_connection and i in self.layers_receive_skip: + skip_in_x = skip_in_x_list.pop(-1) + else: + skip_in_x = None + x = layer(x, c, input_pos, freqs_cis, mask, context, context_freqs_cis, cross_attention_mask, skip_in_x) + if self.uvit_skip_connection and i in self.layers_emit_skip: + skip_in_x_list.append(x) + x = self.norm(x, c) + return x + + @classmethod + def from_name(cls, name: str): + return cls(ModelArgs.from_name(name)) + + +class TransformerBlock(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.attention = Attention(config) + self.feed_forward = FeedForward(config) + self.ffn_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + self.attention_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + + if config.has_cross_attention: + self.has_cross_attention = True + self.cross_attention = Attention(config, is_cross_attention=True) + self.cross_attention_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + else: + self.has_cross_attention = False + + if config.uvit_skip_connection: + self.skip_in_linear = nn.Linear(config.dim * 2, config.dim) + self.uvit_skip_connection = True + else: + self.uvit_skip_connection = False + + self.time_as_token = config.time_as_token + + def forward(self, + x: Tensor, + c: Tensor, + input_pos: Tensor, + freqs_cis: Tensor, + mask: Tensor, + context: Optional[Tensor] = None, + context_freqs_cis: Optional[Tensor] = None, + cross_attention_mask: Optional[Tensor] = None, + skip_in_x: Optional[Tensor] = None, + ) -> Tensor: + c = None if self.time_as_token else c + if self.uvit_skip_connection and skip_in_x is not None: + x = self.skip_in_linear(torch.cat([x, skip_in_x], dim=-1)) + h = x + self.attention(self.attention_norm(x, c), freqs_cis, mask, input_pos) + if self.has_cross_attention: + h = h + self.cross_attention(self.cross_attention_norm(h, c), freqs_cis, cross_attention_mask, input_pos, context, context_freqs_cis) + out = h + self.feed_forward(self.ffn_norm(h, c)) + return out + + +class Attention(nn.Module): + def __init__(self, config: ModelArgs, is_cross_attention: bool = False): + super().__init__() + assert config.dim % config.n_head == 0 + + total_head_dim = (config.n_head + 2 * config.n_local_heads) * config.head_dim + # key, query, value projections for all heads, but in a batch + if is_cross_attention: + self.wq = nn.Linear(config.dim, config.n_head * config.head_dim, bias=False) + self.wkv = nn.Linear(config.context_dim, 2 * config.n_local_heads * config.head_dim, bias=False) + else: + self.wqkv = nn.Linear(config.dim, total_head_dim, bias=False) + self.wo = nn.Linear(config.head_dim * config.n_head, config.dim, bias=False) + self.kv_cache = None + + self.n_head = config.n_head + self.head_dim = config.head_dim + self.n_local_heads = config.n_local_heads + self.dim = config.dim + # self._register_load_state_dict_pre_hook(self.load_hook) + + # def load_hook(self, state_dict, prefix, *args): + # if prefix + "wq.weight" in state_dict: + # wq = state_dict.pop(prefix + "wq.weight") + # wk = state_dict.pop(prefix + "wk.weight") + # wv = state_dict.pop(prefix + "wv.weight") + # state_dict[prefix + "wqkv.weight"] = torch.cat([wq, wk, wv]) + + def forward(self, + x: Tensor, + freqs_cis: Tensor, + mask: Tensor, + input_pos: Optional[Tensor] = None, + context: Optional[Tensor] = None, + context_freqs_cis: Optional[Tensor] = None, + ) -> Tensor: + bsz, seqlen, _ = x.shape + + kv_size = self.n_local_heads * self.head_dim + if context is None: + q, k, v = self.wqkv(x).split([kv_size, kv_size, kv_size], dim=-1) + context_seqlen = seqlen + else: + q = self.wq(x) + k, v = self.wkv(context).split([kv_size, kv_size], dim=-1) + context_seqlen = context.shape[1] + + q = q.view(bsz, seqlen, self.n_head, self.head_dim) + k = k.view(bsz, context_seqlen, self.n_local_heads, self.head_dim) + v = v.view(bsz, context_seqlen, self.n_local_heads, self.head_dim) + + q = apply_rotary_emb(q, freqs_cis) + k = apply_rotary_emb(k, context_freqs_cis if context_freqs_cis is not None else freqs_cis) + + q, k, v = map(lambda x: x.transpose(1, 2), (q, k, v)) + + if self.kv_cache is not None: + k, v = self.kv_cache.update(input_pos, k, v) + + k = k.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + v = v.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + y = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0) + + y = y.transpose(1, 2).contiguous().view(bsz, seqlen, self.head_dim * self.n_head) + + y = self.wo(y) + return y + + +class FeedForward(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.w1 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w3 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w2 = nn.Linear(config.intermediate_size, config.dim, bias=False) + + def forward(self, x: Tensor) -> Tensor: + return self.w2(F.silu(self.w1(x)) * self.w3(x)) + + +class RMSNorm(nn.Module): + def __init__(self, dim: int, eps: float = 1e-5): + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.ones(dim)) + + def _norm(self, x): + return x * torch.rsqrt(torch.mean(x * x, dim=-1, keepdim=True) + self.eps) + + def forward(self, x: Tensor) -> Tensor: + output = self._norm(x.float()).type_as(x) + return output * self.weight + + +def precompute_freqs_cis( + seq_len: int, n_elem: int, base: int = 10000, + dtype: torch.dtype = torch.bfloat16 +) -> Tensor: + freqs = 1.0 / (base ** (torch.arange(0, n_elem, 2)[: (n_elem // 2)].float() / n_elem)) + t = torch.arange(seq_len, device=freqs.device) + freqs = torch.outer(t, freqs) + freqs_cis = torch.polar(torch.ones_like(freqs), freqs) + cache = torch.stack([freqs_cis.real, freqs_cis.imag], dim=-1) + return cache.to(dtype=dtype) + + +def apply_rotary_emb(x: Tensor, freqs_cis: Tensor) -> Tensor: + xshaped = x.float().reshape(*x.shape[:-1], -1, 2) + freqs_cis = freqs_cis.view(1, xshaped.size(1), 1, xshaped.size(3), 2) + x_out2 = torch.stack( + [ + xshaped[..., 0] * freqs_cis[..., 0] - xshaped[..., 1] * freqs_cis[..., 1], + xshaped[..., 1] * freqs_cis[..., 0] + xshaped[..., 0] * freqs_cis[..., 1], + ], + -1, + ) + + x_out2 = x_out2.flatten(3) + return x_out2.type_as(x) + + +def modulate(x, shift, scale): + return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1) + + +################################################################################# +# Embedding Layers for Timesteps and Class Labels # +################################################################################# + +class TimestepEmbedder(nn.Module): + """ + Embeds scalar timesteps into vector representations. + """ + def __init__(self, hidden_size, frequency_embedding_size=256): + super().__init__() + self.mlp = nn.Sequential( + nn.Linear(frequency_embedding_size, hidden_size, bias=True), + nn.SiLU(), + nn.Linear(hidden_size, hidden_size, bias=True), + ) + self.frequency_embedding_size = frequency_embedding_size + self.max_period = 10000 + self.scale = 1000 + + half = frequency_embedding_size // 2 + freqs = torch.exp( + -math.log(self.max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half + ) + self.register_buffer("freqs", freqs) + + def timestep_embedding(self, t): + """ + Create sinusoidal timestep embeddings. + :param t: a 1-D Tensor of N indices, one per batch element. + These may be fractional. + :param dim: the dimension of the output. + :param max_period: controls the minimum frequency of the embeddings. + :return: an (N, D) Tensor of positional embeddings. + """ + # https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py + + args = self.scale * t[:, None].float() * self.freqs[None] + embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + if self.frequency_embedding_size % 2: + embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1) + return embedding + + def forward(self, t): + t_freq = self.timestep_embedding(t) + t_emb = self.mlp(t_freq) + return t_emb + + +class StyleEmbedder(nn.Module): + """ + Embeds class labels into vector representations. Also handles label dropout for classifier-free guidance. + """ + def __init__(self, input_size, hidden_size, dropout_prob): + super().__init__() + use_cfg_embedding = dropout_prob > 0 + self.embedding_table = nn.Embedding(int(use_cfg_embedding), hidden_size) + self.style_in = weight_norm(nn.Linear(input_size, hidden_size, bias=True)) + self.input_size = input_size + self.dropout_prob = dropout_prob + + def forward(self, labels, train, force_drop_ids=None): + use_dropout = self.dropout_prob > 0 + if (train and use_dropout) or (force_drop_ids is not None): + labels = self.token_drop(labels, force_drop_ids) + else: + labels = self.style_in(labels) + embeddings = labels + return embeddings + +class FinalLayer(nn.Module): + """ + The final layer of DiT. + """ + def __init__(self, hidden_size, patch_size, out_channels): + super().__init__() + self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6) + self.linear = weight_norm(nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True)) + self.adaLN_modulation = nn.Sequential( + nn.SiLU(), + nn.Linear(hidden_size, 2 * hidden_size, bias=True) + ) + + def forward(self, x, c): + shift, scale = self.adaLN_modulation(c).chunk(2, dim=1) + x = modulate(self.norm_final(x), shift, scale) + x = self.linear(x) + return x + +class DiT(torch.nn.Module): + def __init__( + self, + args + ): + super(DiT, self).__init__() + self.time_as_token = args.DiT.time_as_token if hasattr(args.DiT, 'time_as_token') else False + self.style_as_token = args.DiT.style_as_token if hasattr(args.DiT, 'style_as_token') else False + self.uvit_skip_connection = args.DiT.uvit_skip_connection if hasattr(args.DiT, 'uvit_skip_connection') else False + model_args = ModelArgs( + block_size=16384,#args.DiT.block_size, + n_layer=args.DiT.depth, + n_head=args.DiT.num_heads, + dim=args.DiT.hidden_dim, + head_dim=args.DiT.hidden_dim // args.DiT.num_heads, + vocab_size=1024, + uvit_skip_connection=self.uvit_skip_connection, + time_as_token=self.time_as_token, + ) + self.transformer = Transformer(model_args) + self.in_channels = args.DiT.in_channels + self.out_channels = args.DiT.in_channels + self.num_heads = args.DiT.num_heads + + self.x_embedder = weight_norm(nn.Linear(args.DiT.in_channels, args.DiT.hidden_dim, bias=True)) + + self.content_type = args.DiT.content_type # 'discrete' or 'continuous' + self.content_codebook_size = args.DiT.content_codebook_size # for discrete content + self.content_dim = args.DiT.content_dim # for continuous content + self.cond_embedder = nn.Embedding(args.DiT.content_codebook_size, args.DiT.hidden_dim) # discrete content + self.cond_projection = nn.Linear(args.DiT.content_dim, args.DiT.hidden_dim, bias=True) # continuous content + + self.is_causal = args.DiT.is_causal + + self.t_embedder = TimestepEmbedder(args.DiT.hidden_dim) + + input_pos = torch.arange(16384) + self.register_buffer("input_pos", input_pos) + + self.final_layer_type = args.DiT.final_layer_type # mlp or wavenet + if self.final_layer_type == 'wavenet': + self.t_embedder2 = TimestepEmbedder(args.wavenet.hidden_dim) + self.conv1 = nn.Linear(args.DiT.hidden_dim, args.wavenet.hidden_dim) + self.conv2 = nn.Conv1d(args.wavenet.hidden_dim, args.DiT.in_channels, 1) + self.wavenet = WN(hidden_channels=args.wavenet.hidden_dim, + kernel_size=args.wavenet.kernel_size, + dilation_rate=args.wavenet.dilation_rate, + n_layers=args.wavenet.num_layers, + gin_channels=args.wavenet.hidden_dim, + p_dropout=args.wavenet.p_dropout, + causal=False) + self.final_layer = FinalLayer(args.wavenet.hidden_dim, 1, args.wavenet.hidden_dim) + self.res_projection = nn.Linear(args.DiT.hidden_dim, + args.wavenet.hidden_dim) # residual connection from tranformer output to final output + self.wavenet_style_condition = args.wavenet.style_condition + assert args.DiT.style_condition == args.wavenet.style_condition + else: + self.final_mlp = nn.Sequential( + nn.Linear(args.DiT.hidden_dim, args.DiT.hidden_dim), + nn.SiLU(), + nn.Linear(args.DiT.hidden_dim, args.DiT.in_channels), + ) + self.transformer_style_condition = args.DiT.style_condition + + + self.class_dropout_prob = args.DiT.class_dropout_prob + self.content_mask_embedder = nn.Embedding(1, args.DiT.hidden_dim) + + self.long_skip_connection = args.DiT.long_skip_connection + self.skip_linear = nn.Linear(args.DiT.hidden_dim + args.DiT.in_channels, args.DiT.hidden_dim) + + self.cond_x_merge_linear = nn.Linear(args.DiT.hidden_dim + args.DiT.in_channels * 2 + + args.style_encoder.dim * self.transformer_style_condition * (not self.style_as_token), + args.DiT.hidden_dim) + if self.style_as_token: + self.style_in = nn.Linear(args.style_encoder.dim, args.DiT.hidden_dim) + + def setup_caches(self, max_batch_size, max_seq_length): + self.transformer.setup_caches(max_batch_size, max_seq_length, use_kv_cache=False) + def forward(self, x, prompt_x, x_lens, t, style, cond, mask_content=False): + class_dropout = False + if self.training and torch.rand(1) < self.class_dropout_prob: + class_dropout = True + if not self.training and mask_content: + class_dropout = True + # cond_in_module = self.cond_embedder if self.content_type == 'discrete' else self.cond_projection + cond_in_module = self.cond_projection + + B, _, T = x.size() + + + t1 = self.t_embedder(t) # (N, D) + + cond = cond_in_module(cond) + + x = x.transpose(1, 2) + prompt_x = prompt_x.transpose(1, 2) + + x_in = torch.cat([x, prompt_x, cond], dim=-1) + if self.transformer_style_condition and not self.style_as_token: + x_in = torch.cat([x_in, style[:, None, :].repeat(1, T, 1)], dim=-1) + if class_dropout: + x_in[..., self.in_channels:] = x_in[..., self.in_channels:] * 0 + x_in = self.cond_x_merge_linear(x_in) # (N, T, D) + + if self.style_as_token: + style = self.style_in(style) + style = torch.zeros_like(style) if class_dropout else style + x_in = torch.cat([style.unsqueeze(1), x_in], dim=1) + if self.time_as_token: + x_in = torch.cat([t1.unsqueeze(1), x_in], dim=1) + x_mask = sequence_mask(x_lens + self.style_as_token + self.time_as_token).to(x.device).unsqueeze(1) + input_pos = self.input_pos[:x_in.size(1)] # (T,) + x_mask_expanded = x_mask[:, None, :].repeat(1, 1, x_in.size(1), 1) if not self.is_causal else None + x_res = self.transformer(x_in, t1.unsqueeze(1), input_pos, x_mask_expanded) + x_res = x_res[:, 1:] if self.time_as_token else x_res + x_res = x_res[:, 1:] if self.style_as_token else x_res + if self.long_skip_connection: + x_res = self.skip_linear(torch.cat([x_res, x], dim=-1)) + if self.final_layer_type == 'wavenet': + x = self.conv1(x_res) + x = x.transpose(1, 2) + t2 = self.t_embedder2(t) + x = self.wavenet(x, x_mask, g=t2.unsqueeze(2)).transpose(1, 2) + self.res_projection( + x_res) # long residual connection + x = self.final_layer(x, t1).transpose(1, 2) + x = self.conv2(x) + else: + x = self.final_mlp(x_res) + x = x.transpose(1, 2) + return x \ No newline at end of file diff --git a/modules/encodec.py b/modules/encodec.py new file mode 100644 index 0000000000000000000000000000000000000000..9feeadd935dabd6642af6b1844494a51967292c3 --- /dev/null +++ b/modules/encodec.py @@ -0,0 +1,292 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +"""Convolutional layers wrappers and utilities.""" + +import math +import typing as tp +import warnings + +import torch +from torch import nn +from torch.nn import functional as F +from torch.nn.utils import spectral_norm, weight_norm + +import typing as tp + +import einops + + +class ConvLayerNorm(nn.LayerNorm): + """ + Convolution-friendly LayerNorm that moves channels to last dimensions + before running the normalization and moves them back to original position right after. + """ + def __init__(self, normalized_shape: tp.Union[int, tp.List[int], torch.Size], **kwargs): + super().__init__(normalized_shape, **kwargs) + + def forward(self, x): + x = einops.rearrange(x, 'b ... t -> b t ...') + x = super().forward(x) + x = einops.rearrange(x, 'b t ... -> b ... t') + return + + +CONV_NORMALIZATIONS = frozenset(['none', 'weight_norm', 'spectral_norm', + 'time_layer_norm', 'layer_norm', 'time_group_norm']) + + +def apply_parametrization_norm(module: nn.Module, norm: str = 'none') -> nn.Module: + assert norm in CONV_NORMALIZATIONS + if norm == 'weight_norm': + return weight_norm(module) + elif norm == 'spectral_norm': + return spectral_norm(module) + else: + # We already check was in CONV_NORMALIZATION, so any other choice + # doesn't need reparametrization. + return module + + +def get_norm_module(module: nn.Module, causal: bool = False, norm: str = 'none', **norm_kwargs) -> nn.Module: + """Return the proper normalization module. If causal is True, this will ensure the returned + module is causal, or return an error if the normalization doesn't support causal evaluation. + """ + assert norm in CONV_NORMALIZATIONS + if norm == 'layer_norm': + assert isinstance(module, nn.modules.conv._ConvNd) + return ConvLayerNorm(module.out_channels, **norm_kwargs) + elif norm == 'time_group_norm': + if causal: + raise ValueError("GroupNorm doesn't support causal evaluation.") + assert isinstance(module, nn.modules.conv._ConvNd) + return nn.GroupNorm(1, module.out_channels, **norm_kwargs) + else: + return nn.Identity() + + +def get_extra_padding_for_conv1d(x: torch.Tensor, kernel_size: int, stride: int, + padding_total: int = 0) -> int: + """See `pad_for_conv1d`. + """ + length = x.shape[-1] + n_frames = (length - kernel_size + padding_total) / stride + 1 + ideal_length = (math.ceil(n_frames) - 1) * stride + (kernel_size - padding_total) + return ideal_length - length + + +def pad_for_conv1d(x: torch.Tensor, kernel_size: int, stride: int, padding_total: int = 0): + """Pad for a convolution to make sure that the last window is full. + Extra padding is added at the end. This is required to ensure that we can rebuild + an output of the same length, as otherwise, even with padding, some time steps + might get removed. + For instance, with total padding = 4, kernel size = 4, stride = 2: + 0 0 1 2 3 4 5 0 0 # (0s are padding) + 1 2 3 # (output frames of a convolution, last 0 is never used) + 0 0 1 2 3 4 5 0 # (output of tr. conv., but pos. 5 is going to get removed as padding) + 1 2 3 4 # once you removed padding, we are missing one time step ! + """ + extra_padding = get_extra_padding_for_conv1d(x, kernel_size, stride, padding_total) + return F.pad(x, (0, extra_padding)) + + +def pad1d(x: torch.Tensor, paddings: tp.Tuple[int, int], mode: str = 'zero', value: float = 0.): + """Tiny wrapper around F.pad, just to allow for reflect padding on small input. + If this is the case, we insert extra 0 padding to the right before the reflection happen. + """ + length = x.shape[-1] + padding_left, padding_right = paddings + assert padding_left >= 0 and padding_right >= 0, (padding_left, padding_right) + if mode == 'reflect': + max_pad = max(padding_left, padding_right) + extra_pad = 0 + if length <= max_pad: + extra_pad = max_pad - length + 1 + x = F.pad(x, (0, extra_pad)) + padded = F.pad(x, paddings, mode, value) + end = padded.shape[-1] - extra_pad + return padded[..., :end] + else: + return F.pad(x, paddings, mode, value) + + +def unpad1d(x: torch.Tensor, paddings: tp.Tuple[int, int]): + """Remove padding from x, handling properly zero padding. Only for 1d!""" + padding_left, padding_right = paddings + assert padding_left >= 0 and padding_right >= 0, (padding_left, padding_right) + assert (padding_left + padding_right) <= x.shape[-1] + end = x.shape[-1] - padding_right + return x[..., padding_left: end] + + +class NormConv1d(nn.Module): + """Wrapper around Conv1d and normalization applied to this conv + to provide a uniform interface across normalization approaches. + """ + def __init__(self, *args, causal: bool = False, norm: str = 'none', + norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs): + super().__init__() + self.conv = apply_parametrization_norm(nn.Conv1d(*args, **kwargs), norm) + self.norm = get_norm_module(self.conv, causal, norm, **norm_kwargs) + self.norm_type = norm + + def forward(self, x): + x = self.conv(x) + x = self.norm(x) + return x + + +class NormConv2d(nn.Module): + """Wrapper around Conv2d and normalization applied to this conv + to provide a uniform interface across normalization approaches. + """ + def __init__(self, *args, norm: str = 'none', + norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs): + super().__init__() + self.conv = apply_parametrization_norm(nn.Conv2d(*args, **kwargs), norm) + self.norm = get_norm_module(self.conv, causal=False, norm=norm, **norm_kwargs) + self.norm_type = norm + + def forward(self, x): + x = self.conv(x) + x = self.norm(x) + return x + + +class NormConvTranspose1d(nn.Module): + """Wrapper around ConvTranspose1d and normalization applied to this conv + to provide a uniform interface across normalization approaches. + """ + def __init__(self, *args, causal: bool = False, norm: str = 'none', + norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs): + super().__init__() + self.convtr = apply_parametrization_norm(nn.ConvTranspose1d(*args, **kwargs), norm) + self.norm = get_norm_module(self.convtr, causal, norm, **norm_kwargs) + self.norm_type = norm + + def forward(self, x): + x = self.convtr(x) + x = self.norm(x) + return x + + +class NormConvTranspose2d(nn.Module): + """Wrapper around ConvTranspose2d and normalization applied to this conv + to provide a uniform interface across normalization approaches. + """ + def __init__(self, *args, norm: str = 'none', + norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs): + super().__init__() + self.convtr = apply_parametrization_norm(nn.ConvTranspose2d(*args, **kwargs), norm) + self.norm = get_norm_module(self.convtr, causal=False, norm=norm, **norm_kwargs) + + def forward(self, x): + x = self.convtr(x) + x = self.norm(x) + return x + + +class SConv1d(nn.Module): + """Conv1d with some builtin handling of asymmetric or causal padding + and normalization. + """ + def __init__(self, in_channels: int, out_channels: int, + kernel_size: int, stride: int = 1, dilation: int = 1, + groups: int = 1, bias: bool = True, causal: bool = False, + norm: str = 'none', norm_kwargs: tp.Dict[str, tp.Any] = {}, + pad_mode: str = 'reflect', **kwargs): + super().__init__() + # warn user on unusual setup between dilation and stride + if stride > 1 and dilation > 1: + warnings.warn('SConv1d has been initialized with stride > 1 and dilation > 1' + f' (kernel_size={kernel_size} stride={stride}, dilation={dilation}).') + self.conv = NormConv1d(in_channels, out_channels, kernel_size, stride, + dilation=dilation, groups=groups, bias=bias, causal=causal, + norm=norm, norm_kwargs=norm_kwargs) + self.causal = causal + self.pad_mode = pad_mode + + def forward(self, x): + B, C, T = x.shape + kernel_size = self.conv.conv.kernel_size[0] + stride = self.conv.conv.stride[0] + dilation = self.conv.conv.dilation[0] + kernel_size = (kernel_size - 1) * dilation + 1 # effective kernel size with dilations + padding_total = kernel_size - stride + extra_padding = get_extra_padding_for_conv1d(x, kernel_size, stride, padding_total) + if self.causal: + # Left padding for causal + x = pad1d(x, (padding_total, extra_padding), mode=self.pad_mode) + else: + # Asymmetric padding required for odd strides + padding_right = padding_total // 2 + padding_left = padding_total - padding_right + x = pad1d(x, (padding_left, padding_right + extra_padding), mode=self.pad_mode) + return self.conv(x) + + +class SConvTranspose1d(nn.Module): + """ConvTranspose1d with some builtin handling of asymmetric or causal padding + and normalization. + """ + def __init__(self, in_channels: int, out_channels: int, + kernel_size: int, stride: int = 1, causal: bool = False, + norm: str = 'none', trim_right_ratio: float = 1., + norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs): + super().__init__() + self.convtr = NormConvTranspose1d(in_channels, out_channels, kernel_size, stride, + causal=causal, norm=norm, norm_kwargs=norm_kwargs) + self.causal = causal + self.trim_right_ratio = trim_right_ratio + assert self.causal or self.trim_right_ratio == 1., \ + "`trim_right_ratio` != 1.0 only makes sense for causal convolutions" + assert self.trim_right_ratio >= 0. and self.trim_right_ratio <= 1. + + def forward(self, x): + kernel_size = self.convtr.convtr.kernel_size[0] + stride = self.convtr.convtr.stride[0] + padding_total = kernel_size - stride + + y = self.convtr(x) + + # We will only trim fixed padding. Extra padding from `pad_for_conv1d` would be + # removed at the very end, when keeping only the right length for the output, + # as removing it here would require also passing the length at the matching layer + # in the encoder. + if self.causal: + # Trim the padding on the right according to the specified ratio + # if trim_right_ratio = 1.0, trim everything from right + padding_right = math.ceil(padding_total * self.trim_right_ratio) + padding_left = padding_total - padding_right + y = unpad1d(y, (padding_left, padding_right)) + else: + # Asymmetric padding required for odd strides + padding_right = padding_total // 2 + padding_left = padding_total - padding_right + y = unpad1d(y, (padding_left, padding_right)) + return y + +class SLSTM(nn.Module): + """ + LSTM without worrying about the hidden state, nor the layout of the data. + Expects input as convolutional layout. + """ + def __init__(self, dimension: int, num_layers: int = 2, skip: bool = True): + super().__init__() + self.skip = skip + self.lstm = nn.LSTM(dimension, dimension, num_layers) + self.hidden = None + + def forward(self, x): + x = x.permute(2, 0, 1) + if self.training: + y, _ = self.lstm(x) + else: + y, self.hidden = self.lstm(x, self.hidden) + if self.skip: + y = y + x + y = y.permute(1, 2, 0) + return y \ No newline at end of file diff --git a/modules/flow_matching.py b/modules/flow_matching.py new file mode 100644 index 0000000000000000000000000000000000000000..9b250a24d4eed7867ec6b4b08afa70bd1b4a7eb3 --- /dev/null +++ b/modules/flow_matching.py @@ -0,0 +1,167 @@ +from abc import ABC + +import torch +import torch.nn.functional as F + +from modules.diffusion_transformer import DiT +from modules.commons import sequence_mask + +from tqdm import tqdm + +class BASECFM(torch.nn.Module, ABC): + def __init__( + self, + args, + ): + super().__init__() + self.sigma_min = 1e-6 + + self.estimator = None + + self.in_channels = args.DiT.in_channels + + self.criterion = torch.nn.MSELoss() if args.reg_loss_type == "l2" else torch.nn.L1Loss() + + if hasattr(args.DiT, 'zero_prompt_speech_token'): + self.zero_prompt_speech_token = args.DiT.zero_prompt_speech_token + else: + self.zero_prompt_speech_token = False + + @torch.inference_mode() + def inference(self, mu, x_lens, prompt, style, f0, n_timesteps, temperature=1.0, inference_cfg_rate=0.5): + """Forward diffusion + + Args: + mu (torch.Tensor): output of encoder + shape: (batch_size, n_feats, mel_timesteps) + mask (torch.Tensor): output_mask + shape: (batch_size, 1, mel_timesteps) + n_timesteps (int): number of diffusion steps + temperature (float, optional): temperature for scaling noise. Defaults to 1.0. + spks (torch.Tensor, optional): speaker ids. Defaults to None. + shape: (batch_size, spk_emb_dim) + cond: Not used but kept for future purposes + + Returns: + sample: generated mel-spectrogram + shape: (batch_size, n_feats, mel_timesteps) + """ + B, T = mu.size(0), mu.size(1) + z = torch.randn([B, self.in_channels, T], device=mu.device) * temperature + t_span = torch.linspace(0, 1, n_timesteps + 1, device=mu.device) + # t_span = t_span + (-1) * (torch.cos(torch.pi / 2 * t_span) - 1 + t_span) + return self.solve_euler(z, x_lens, prompt, mu, style, f0, t_span, inference_cfg_rate) + + def solve_euler(self, x, x_lens, prompt, mu, style, f0, t_span, inference_cfg_rate=0.5): + """ + Fixed euler solver for ODEs. + Args: + x (torch.Tensor): random noise + t_span (torch.Tensor): n_timesteps interpolated + shape: (n_timesteps + 1,) + mu (torch.Tensor): output of encoder + shape: (batch_size, n_feats, mel_timesteps) + mask (torch.Tensor): output_mask + shape: (batch_size, 1, mel_timesteps) + spks (torch.Tensor, optional): speaker ids. Defaults to None. + shape: (batch_size, spk_emb_dim) + cond: Not used but kept for future purposes + """ + t, _, _ = t_span[0], t_span[-1], t_span[1] - t_span[0] + + # I am storing this because I can later plot it by putting a debugger here and saving it to a file + # Or in future might add like a return_all_steps flag + sol = [] + # apply prompt + prompt_len = prompt.size(-1) + prompt_x = torch.zeros_like(x) + prompt_x[..., :prompt_len] = prompt[..., :prompt_len] + x[..., :prompt_len] = 0 + if self.zero_prompt_speech_token: + mu[..., :prompt_len] = 0 + for step in tqdm(range(1, len(t_span))): + dt = t_span[step] - t_span[step - 1] + if inference_cfg_rate > 0: + # Stack original and CFG (null) inputs for batched processing + stacked_prompt_x = torch.cat([prompt_x, torch.zeros_like(prompt_x)], dim=0) + stacked_style = torch.cat([style, torch.zeros_like(style)], dim=0) + stacked_mu = torch.cat([mu, torch.zeros_like(mu)], dim=0) + stacked_x = torch.cat([x, x], dim=0) + stacked_t = torch.cat([t.unsqueeze(0), t.unsqueeze(0)], dim=0) + + # Perform a single forward pass for both original and CFG inputs + stacked_dphi_dt = self.estimator( + stacked_x, stacked_prompt_x, x_lens, stacked_t, stacked_style, stacked_mu, + ) + + # Split the output back into the original and CFG components + dphi_dt, cfg_dphi_dt = stacked_dphi_dt.chunk(2, dim=0) + + # Apply CFG formula + dphi_dt = (1.0 + inference_cfg_rate) * dphi_dt - inference_cfg_rate * cfg_dphi_dt + else: + dphi_dt = self.estimator(x, prompt_x, x_lens, t.unsqueeze(0), style, mu) + + x = x + dt * dphi_dt + t = t + dt + sol.append(x) + if step < len(t_span) - 1: + dt = t_span[step + 1] - t + x[:, :, :prompt_len] = 0 + + return sol[-1] + def forward(self, x1, x_lens, prompt_lens, mu, style): + """Computes diffusion loss + + Args: + x1 (torch.Tensor): Target + shape: (batch_size, n_feats, mel_timesteps) + mask (torch.Tensor): target mask + shape: (batch_size, 1, mel_timesteps) + mu (torch.Tensor): output of encoder + shape: (batch_size, n_feats, mel_timesteps) + spks (torch.Tensor, optional): speaker embedding. Defaults to None. + shape: (batch_size, spk_emb_dim) + + Returns: + loss: conditional flow matching loss + y: conditional flow + shape: (batch_size, n_feats, mel_timesteps) + """ + b, _, t = x1.shape + + # random timestep + t = torch.rand([b, 1, 1], device=mu.device, dtype=x1.dtype) + # sample noise p(x_0) + z = torch.randn_like(x1) + + y = (1 - (1 - self.sigma_min) * t) * z + t * x1 + u = x1 - (1 - self.sigma_min) * z + + prompt = torch.zeros_like(x1) + for bib in range(b): + prompt[bib, :, :prompt_lens[bib]] = x1[bib, :, :prompt_lens[bib]] + # range covered by prompt are set to 0 + y[bib, :, :prompt_lens[bib]] = 0 + if self.zero_prompt_speech_token: + mu[bib, :, :prompt_lens[bib]] = 0 + + estimator_out = self.estimator(y, prompt, x_lens, t.squeeze(1).squeeze(1), style, mu, prompt_lens) + loss = 0 + for bib in range(b): + loss += self.criterion(estimator_out[bib, :, prompt_lens[bib]:x_lens[bib]], u[bib, :, prompt_lens[bib]:x_lens[bib]]) + loss /= b + + return loss, estimator_out + (1 - self.sigma_min) * z + + + +class CFM(BASECFM): + def __init__(self, args): + super().__init__( + args + ) + if args.dit_type == "DiT": + self.estimator = DiT(args) + else: + raise NotImplementedError(f"Unknown diffusion type {args.dit_type}") diff --git a/modules/hifigan/__init__.py b/modules/hifigan/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/hifigan/f0_predictor.py b/modules/hifigan/f0_predictor.py new file mode 100644 index 0000000000000000000000000000000000000000..36b85f4ed90c3a412cb179f49ccb471132a86550 --- /dev/null +++ b/modules/hifigan/f0_predictor.py @@ -0,0 +1,55 @@ +# Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu, Kai Hu) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import torch +import torch.nn as nn +from torch.nn.utils import weight_norm + + +class ConvRNNF0Predictor(nn.Module): + def __init__(self, + num_class: int = 1, + in_channels: int = 80, + cond_channels: int = 512 + ): + super().__init__() + + self.num_class = num_class + self.condnet = nn.Sequential( + weight_norm( + nn.Conv1d(in_channels, cond_channels, kernel_size=3, padding=1) + ), + nn.ELU(), + weight_norm( + nn.Conv1d(cond_channels, cond_channels, kernel_size=3, padding=1) + ), + nn.ELU(), + weight_norm( + nn.Conv1d(cond_channels, cond_channels, kernel_size=3, padding=1) + ), + nn.ELU(), + weight_norm( + nn.Conv1d(cond_channels, cond_channels, kernel_size=3, padding=1) + ), + nn.ELU(), + weight_norm( + nn.Conv1d(cond_channels, cond_channels, kernel_size=3, padding=1) + ), + nn.ELU(), + ) + self.classifier = nn.Linear(in_features=cond_channels, out_features=self.num_class) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.condnet(x) + x = x.transpose(1, 2) + return torch.abs(self.classifier(x).squeeze(-1)) diff --git a/modules/hifigan/generator.py b/modules/hifigan/generator.py new file mode 100644 index 0000000000000000000000000000000000000000..867894cee55a0746b93c1ba070871b7e5e5eba2b --- /dev/null +++ b/modules/hifigan/generator.py @@ -0,0 +1,454 @@ +# Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu, Kai Hu) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""HIFI-GAN""" + +import typing as tp +import numpy as np +from scipy.signal import get_window +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.nn import Conv1d +from torch.nn import ConvTranspose1d +from torch.nn.utils import remove_weight_norm +from torch.nn.utils import weight_norm +from torch.distributions.uniform import Uniform + +from torch import sin +from torch.nn.parameter import Parameter + + +"""hifigan based generator implementation. + +This code is modified from https://github.com/jik876/hifi-gan + ,https://github.com/kan-bayashi/ParallelWaveGAN and + https://github.com/NVIDIA/BigVGAN + +""" +class Snake(nn.Module): + ''' + Implementation of a sine-based periodic activation function + Shape: + - Input: (B, C, T) + - Output: (B, C, T), same shape as the input + Parameters: + - alpha - trainable parameter + References: + - This activation function is from this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda: + https://arxiv.org/abs/2006.08195 + Examples: + >>> a1 = snake(256) + >>> x = torch.randn(256) + >>> x = a1(x) + ''' + def __init__(self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False): + ''' + Initialization. + INPUT: + - in_features: shape of the input + - alpha: trainable parameter + alpha is initialized to 1 by default, higher values = higher-frequency. + alpha will be trained along with the rest of your model. + ''' + super(Snake, self).__init__() + self.in_features = in_features + + # initialize alpha + self.alpha_logscale = alpha_logscale + if self.alpha_logscale: # log scale alphas initialized to zeros + self.alpha = Parameter(torch.zeros(in_features) * alpha) + else: # linear scale alphas initialized to ones + self.alpha = Parameter(torch.ones(in_features) * alpha) + + self.alpha.requires_grad = alpha_trainable + + self.no_div_by_zero = 0.000000001 + + def forward(self, x): + ''' + Forward pass of the function. + Applies the function to the input elementwise. + Snake ∶= x + 1/a * sin^2 (xa) + ''' + alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # line up with x to [B, C, T] + if self.alpha_logscale: + alpha = torch.exp(alpha) + x = x + (1.0 / (alpha + self.no_div_by_zero)) * pow(sin(x * alpha), 2) + + return x + +def get_padding(kernel_size, dilation=1): + return int((kernel_size * dilation - dilation) / 2) + + +def init_weights(m, mean=0.0, std=0.01): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + m.weight.data.normal_(mean, std) + + + +class ResBlock(torch.nn.Module): + """Residual block module in HiFiGAN/BigVGAN.""" + def __init__( + self, + channels: int = 512, + kernel_size: int = 3, + dilations: tp.List[int] = [1, 3, 5], + ): + super(ResBlock, self).__init__() + self.convs1 = nn.ModuleList() + self.convs2 = nn.ModuleList() + + for dilation in dilations: + self.convs1.append( + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation, + padding=get_padding(kernel_size, dilation) + ) + ) + ) + self.convs2.append( + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1) + ) + ) + ) + self.convs1.apply(init_weights) + self.convs2.apply(init_weights) + self.activations1 = nn.ModuleList([ + Snake(channels, alpha_logscale=False) + for _ in range(len(self.convs1)) + ]) + self.activations2 = nn.ModuleList([ + Snake(channels, alpha_logscale=False) + for _ in range(len(self.convs2)) + ]) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + for idx in range(len(self.convs1)): + xt = self.activations1[idx](x) + xt = self.convs1[idx](xt) + xt = self.activations2[idx](xt) + xt = self.convs2[idx](xt) + x = xt + x + return x + + def remove_weight_norm(self): + for idx in range(len(self.convs1)): + remove_weight_norm(self.convs1[idx]) + remove_weight_norm(self.convs2[idx]) + +class SineGen(torch.nn.Module): + """ Definition of sine generator + SineGen(samp_rate, harmonic_num = 0, + sine_amp = 0.1, noise_std = 0.003, + voiced_threshold = 0, + flag_for_pulse=False) + samp_rate: sampling rate in Hz + harmonic_num: number of harmonic overtones (default 0) + sine_amp: amplitude of sine-wavefrom (default 0.1) + noise_std: std of Gaussian noise (default 0.003) + voiced_thoreshold: F0 threshold for U/V classification (default 0) + flag_for_pulse: this SinGen is used inside PulseGen (default False) + Note: when flag_for_pulse is True, the first time step of a voiced + segment is always sin(np.pi) or cos(0) + """ + + def __init__(self, samp_rate, harmonic_num=0, + sine_amp=0.1, noise_std=0.003, + voiced_threshold=0): + super(SineGen, self).__init__() + self.sine_amp = sine_amp + self.noise_std = noise_std + self.harmonic_num = harmonic_num + self.sampling_rate = samp_rate + self.voiced_threshold = voiced_threshold + + def _f02uv(self, f0): + # generate uv signal + uv = (f0 > self.voiced_threshold).type(torch.float32) + return uv + + @torch.no_grad() + def forward(self, f0): + """ + :param f0: [B, 1, sample_len], Hz + :return: [B, 1, sample_len] + """ + + F_mat = torch.zeros((f0.size(0), self.harmonic_num + 1, f0.size(-1))).to(f0.device) + for i in range(self.harmonic_num + 1): + F_mat[:, i: i + 1, :] = f0 * (i + 1) / self.sampling_rate + + theta_mat = 2 * np.pi * (torch.cumsum(F_mat, dim=-1) % 1) + u_dist = Uniform(low=-np.pi, high=np.pi) + phase_vec = u_dist.sample(sample_shape=(f0.size(0), self.harmonic_num + 1, 1)).to(F_mat.device) + phase_vec[:, 0, :] = 0 + + # generate sine waveforms + sine_waves = self.sine_amp * torch.sin(theta_mat + phase_vec) + + # generate uv signal + uv = self._f02uv(f0) + + # noise: for unvoiced should be similar to sine_amp + # std = self.sine_amp/3 -> max value ~ self.sine_amp + # . for voiced regions is self.noise_std + noise_amp = uv * self.noise_std + (1 - uv) * self.sine_amp / 3 + noise = noise_amp * torch.randn_like(sine_waves) + + # first: set the unvoiced part to 0 by uv + # then: additive noise + sine_waves = sine_waves * uv + noise + return sine_waves, uv, noise + + +class SourceModuleHnNSF(torch.nn.Module): + """ SourceModule for hn-nsf + SourceModule(sampling_rate, harmonic_num=0, sine_amp=0.1, + add_noise_std=0.003, voiced_threshod=0) + sampling_rate: sampling_rate in Hz + harmonic_num: number of harmonic above F0 (default: 0) + sine_amp: amplitude of sine source signal (default: 0.1) + add_noise_std: std of additive Gaussian noise (default: 0.003) + note that amplitude of noise in unvoiced is decided + by sine_amp + voiced_threshold: threhold to set U/V given F0 (default: 0) + Sine_source, noise_source = SourceModuleHnNSF(F0_sampled) + F0_sampled (batchsize, length, 1) + Sine_source (batchsize, length, 1) + noise_source (batchsize, length 1) + uv (batchsize, length, 1) + """ + + def __init__(self, sampling_rate, upsample_scale, harmonic_num=0, sine_amp=0.1, + add_noise_std=0.003, voiced_threshod=0): + super(SourceModuleHnNSF, self).__init__() + + self.sine_amp = sine_amp + self.noise_std = add_noise_std + + # to produce sine waveforms + self.l_sin_gen = SineGen(sampling_rate, harmonic_num, + sine_amp, add_noise_std, voiced_threshod) + + # to merge source harmonics into a single excitation + self.l_linear = torch.nn.Linear(harmonic_num + 1, 1) + self.l_tanh = torch.nn.Tanh() + + def forward(self, x): + """ + Sine_source, noise_source = SourceModuleHnNSF(F0_sampled) + F0_sampled (batchsize, length, 1) + Sine_source (batchsize, length, 1) + noise_source (batchsize, length 1) + """ + # source for harmonic branch + with torch.no_grad(): + sine_wavs, uv, _ = self.l_sin_gen(x.transpose(1, 2)) + sine_wavs = sine_wavs.transpose(1, 2) + uv = uv.transpose(1, 2) + sine_merge = self.l_tanh(self.l_linear(sine_wavs)) + + # source for noise branch, in the same shape as uv + noise = torch.randn_like(uv) * self.sine_amp / 3 + return sine_merge, noise, uv + + +class HiFTGenerator(nn.Module): + """ + HiFTNet Generator: Neural Source Filter + ISTFTNet + https://arxiv.org/abs/2309.09493 + """ + def __init__( + self, + in_channels: int = 80, + base_channels: int = 512, + nb_harmonics: int = 8, + sampling_rate: int = 22050, + nsf_alpha: float = 0.1, + nsf_sigma: float = 0.003, + nsf_voiced_threshold: float = 10, + upsample_rates: tp.List[int] = [8, 8], + upsample_kernel_sizes: tp.List[int] = [16, 16], + istft_params: tp.Dict[str, int] = {"n_fft": 16, "hop_len": 4}, + resblock_kernel_sizes: tp.List[int] = [3, 7, 11], + resblock_dilation_sizes: tp.List[tp.List[int]] = [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + source_resblock_kernel_sizes: tp.List[int] = [7, 11], + source_resblock_dilation_sizes: tp.List[tp.List[int]] = [[1, 3, 5], [1, 3, 5]], + lrelu_slope: float = 0.1, + audio_limit: float = 0.99, + f0_predictor: torch.nn.Module = None, + ): + super(HiFTGenerator, self).__init__() + + self.out_channels = 1 + self.nb_harmonics = nb_harmonics + self.sampling_rate = sampling_rate + self.istft_params = istft_params + self.lrelu_slope = lrelu_slope + self.audio_limit = audio_limit + + self.num_kernels = len(resblock_kernel_sizes) + self.num_upsamples = len(upsample_rates) + self.m_source = SourceModuleHnNSF( + sampling_rate=sampling_rate, + upsample_scale=np.prod(upsample_rates) * istft_params["hop_len"], + harmonic_num=nb_harmonics, + sine_amp=nsf_alpha, + add_noise_std=nsf_sigma, + voiced_threshod=nsf_voiced_threshold) + self.f0_upsamp = torch.nn.Upsample(scale_factor=np.prod(upsample_rates) * istft_params["hop_len"]) + + self.conv_pre = weight_norm( + Conv1d(in_channels, base_channels, 7, 1, padding=3) + ) + + # Up + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)): + self.ups.append( + weight_norm( + ConvTranspose1d( + base_channels // (2**i), + base_channels // (2**(i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ) + + # Down + self.source_downs = nn.ModuleList() + self.source_resblocks = nn.ModuleList() + downsample_rates = [1] + upsample_rates[::-1][:-1] + downsample_cum_rates = np.cumprod(downsample_rates) + for i, (u, k, d) in enumerate(zip(downsample_cum_rates[::-1], source_resblock_kernel_sizes, + source_resblock_dilation_sizes)): + if u == 1: + self.source_downs.append( + Conv1d(istft_params["n_fft"] + 2, base_channels // (2 ** (i + 1)), 1, 1) + ) + else: + self.source_downs.append( + Conv1d(istft_params["n_fft"] + 2, base_channels // (2 ** (i + 1)), u * 2, u, padding=(u // 2)) + ) + + self.source_resblocks.append( + ResBlock(base_channels // (2 ** (i + 1)), k, d) + ) + + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = base_channels // (2**(i + 1)) + for j, (k, d) in enumerate(zip(resblock_kernel_sizes, resblock_dilation_sizes)): + self.resblocks.append(ResBlock(ch, k, d)) + + self.conv_post = weight_norm(Conv1d(ch, istft_params["n_fft"] + 2, 7, 1, padding=3)) + self.ups.apply(init_weights) + self.conv_post.apply(init_weights) + self.reflection_pad = nn.ReflectionPad1d((1, 0)) + self.stft_window = torch.from_numpy(get_window("hann", istft_params["n_fft"], fftbins=True).astype(np.float32)) + self.f0_predictor = f0_predictor + + def _f02source(self, f0: torch.Tensor) -> torch.Tensor: + f0 = self.f0_upsamp(f0[:, None]).transpose(1, 2) # bs,n,t + + har_source, _, _ = self.m_source(f0) + return har_source.transpose(1, 2) + + def _stft(self, x): + spec = torch.stft( + x, + self.istft_params["n_fft"], self.istft_params["hop_len"], self.istft_params["n_fft"], window=self.stft_window.to(x.device), + return_complex=True) + spec = torch.view_as_real(spec) # [B, F, TT, 2] + return spec[..., 0], spec[..., 1] + + def _istft(self, magnitude, phase): + magnitude = torch.clip(magnitude, max=1e2) + real = magnitude * torch.cos(phase) + img = magnitude * torch.sin(phase) + inverse_transform = torch.istft(torch.complex(real, img), self.istft_params["n_fft"], self.istft_params["hop_len"], self.istft_params["n_fft"], window=self.stft_window.to(magnitude.device)) + return inverse_transform + + def forward(self, x: torch.Tensor, f0=None) -> torch.Tensor: + if f0 is None: + f0 = self.f0_predictor(x) + s = self._f02source(f0) + + s_stft_real, s_stft_imag = self._stft(s.squeeze(1)) + s_stft = torch.cat([s_stft_real, s_stft_imag], dim=1) + + x = self.conv_pre(x) + for i in range(self.num_upsamples): + x = F.leaky_relu(x, self.lrelu_slope) + x = self.ups[i](x) + + if i == self.num_upsamples - 1: + x = self.reflection_pad(x) + + # fusion + si = self.source_downs[i](s_stft) + si = self.source_resblocks[i](si) + x = x + si + + xs = None + for j in range(self.num_kernels): + if xs is None: + xs = self.resblocks[i * self.num_kernels + j](x) + else: + xs += self.resblocks[i * self.num_kernels + j](x) + x = xs / self.num_kernels + + x = F.leaky_relu(x) + x = self.conv_post(x) + magnitude = torch.exp(x[:, :self.istft_params["n_fft"] // 2 + 1, :]) + phase = torch.sin(x[:, self.istft_params["n_fft"] // 2 + 1:, :]) # actually, sin is redundancy + + x = self._istft(magnitude, phase) + x = torch.clamp(x, -self.audio_limit, self.audio_limit) + return x + + def remove_weight_norm(self): + print('Removing weight norm...') + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + remove_weight_norm(self.conv_pre) + remove_weight_norm(self.conv_post) + self.source_module.remove_weight_norm() + for l in self.source_downs: + remove_weight_norm(l) + for l in self.source_resblocks: + l.remove_weight_norm() + + @torch.inference_mode() + def inference(self, mel: torch.Tensor, f0=None) -> torch.Tensor: + return self.forward(x=mel, f0=f0) diff --git a/modules/length_regulator.py b/modules/length_regulator.py new file mode 100644 index 0000000000000000000000000000000000000000..a896c6ced97e409ba657f60af59a2f82e1688e65 --- /dev/null +++ b/modules/length_regulator.py @@ -0,0 +1,141 @@ +from typing import Tuple +import torch +import torch.nn as nn +from torch.nn import functional as F +from modules.commons import sequence_mask +import numpy as np +from dac.nn.quantize import VectorQuantize + +# f0_bin = 256 +f0_max = 1100.0 +f0_min = 50.0 +f0_mel_min = 1127 * np.log(1 + f0_min / 700) +f0_mel_max = 1127 * np.log(1 + f0_max / 700) + +def f0_to_coarse(f0, f0_bin): + f0_mel = 1127 * (1 + f0 / 700).log() + a = (f0_bin - 2) / (f0_mel_max - f0_mel_min) + b = f0_mel_min * a - 1. + f0_mel = torch.where(f0_mel > 0, f0_mel * a - b, f0_mel) + # torch.clip_(f0_mel, min=1., max=float(f0_bin - 1)) + f0_coarse = torch.round(f0_mel).long() + f0_coarse = f0_coarse * (f0_coarse > 0) + f0_coarse = f0_coarse + ((f0_coarse < 1) * 1) + f0_coarse = f0_coarse * (f0_coarse < f0_bin) + f0_coarse = f0_coarse + ((f0_coarse >= f0_bin) * (f0_bin - 1)) + return f0_coarse + +class InterpolateRegulator(nn.Module): + def __init__( + self, + channels: int, + sampling_ratios: Tuple, + is_discrete: bool = False, + in_channels: int = None, # only applies to continuous input + vector_quantize: bool = False, # whether to use vector quantization, only applies to continuous input + codebook_size: int = 1024, # for discrete only + out_channels: int = None, + groups: int = 1, + n_codebooks: int = 1, # number of codebooks + quantizer_dropout: float = 0.0, # dropout for quantizer + f0_condition: bool = False, + n_f0_bins: int = 512, + ): + super().__init__() + self.sampling_ratios = sampling_ratios + out_channels = out_channels or channels + model = nn.ModuleList([]) + if len(sampling_ratios) > 0: + self.interpolate = True + for _ in sampling_ratios: + module = nn.Conv1d(channels, channels, 3, 1, 1) + norm = nn.GroupNorm(groups, channels) + act = nn.Mish() + model.extend([module, norm, act]) + else: + self.interpolate = False + model.append( + nn.Conv1d(channels, out_channels, 1, 1) + ) + self.model = nn.Sequential(*model) + self.embedding = nn.Embedding(codebook_size, channels) + self.is_discrete = is_discrete + + self.mask_token = nn.Parameter(torch.zeros(1, channels)) + + self.n_codebooks = n_codebooks + if n_codebooks > 1: + self.extra_codebooks = nn.ModuleList([ + nn.Embedding(codebook_size, channels) for _ in range(n_codebooks - 1) + ]) + self.extra_codebook_mask_tokens = nn.ParameterList([ + nn.Parameter(torch.zeros(1, channels)) for _ in range(n_codebooks - 1) + ]) + self.quantizer_dropout = quantizer_dropout + + if f0_condition: + self.f0_embedding = nn.Embedding(n_f0_bins, channels) + self.f0_condition = f0_condition + self.n_f0_bins = n_f0_bins + self.f0_bins = torch.arange(2, 1024, 1024 // n_f0_bins) + self.f0_mask = nn.Parameter(torch.zeros(1, channels)) + else: + self.f0_condition = False + + if not is_discrete: + self.content_in_proj = nn.Linear(in_channels, channels) + if vector_quantize: + self.vq = VectorQuantize(channels, codebook_size, 8) + + def forward(self, x, ylens=None, n_quantizers=None, f0=None): + # apply token drop + if self.training: + n_quantizers = torch.ones((x.shape[0],)) * self.n_codebooks + dropout = torch.randint(1, self.n_codebooks + 1, (x.shape[0],)) + n_dropout = int(x.shape[0] * self.quantizer_dropout) + n_quantizers[:n_dropout] = dropout[:n_dropout] + n_quantizers = n_quantizers.to(x.device) + # decide whether to drop for each sample in batch + else: + n_quantizers = torch.ones((x.shape[0],), device=x.device) * (self.n_codebooks if n_quantizers is None else n_quantizers) + if self.is_discrete: + if self.n_codebooks > 1: + assert len(x.size()) == 3 + x_emb = self.embedding(x[:, 0]) + for i, emb in enumerate(self.extra_codebooks): + x_emb = x_emb + (n_quantizers > i+1)[..., None, None] * emb(x[:, i+1]) + # add mask token if not using this codebook + # x_emb = x_emb + (n_quantizers <= i+1)[..., None, None] * self.extra_codebook_mask_tokens[i] + x = x_emb + elif self.n_codebooks == 1: + if len(x.size()) == 2: + x = self.embedding(x) + else: + x = self.embedding(x[:, 0]) + else: + x = self.content_in_proj(x) + # x in (B, T, D) + mask = sequence_mask(ylens).unsqueeze(-1) + if self.interpolate: + x = F.interpolate(x.transpose(1, 2).contiguous(), size=ylens.max(), mode='nearest') + else: + x = x.transpose(1, 2).contiguous() + mask = mask[:, :x.size(2), :] + ylens = ylens.clamp(max=x.size(2)).long() + if self.f0_condition: + if f0 is None: + x = x + self.f0_mask.unsqueeze(-1) + else: + #quantized_f0 = torch.bucketize(f0, self.f0_bins.to(f0.device)) # (N, T) + quantized_f0 = f0_to_coarse(f0, self.n_f0_bins) + quantized_f0 = quantized_f0.clamp(0, self.n_f0_bins - 1).long() + f0_emb = self.f0_embedding(quantized_f0) + f0_emb = F.interpolate(f0_emb.transpose(1, 2).contiguous(), size=ylens.max(), mode='nearest') + x = x + f0_emb + out = self.model(x).transpose(1, 2).contiguous() + if hasattr(self, 'vq'): + out_q, commitment_loss, codebook_loss, codes, out, = self.vq(out.transpose(1, 2)) + out_q = out_q.transpose(1, 2) + return out_q * mask, ylens, codes, commitment_loss, codebook_loss + olens = ylens + return out * mask, olens, None, None, None diff --git a/modules/rmvpe.py b/modules/rmvpe.py new file mode 100644 index 0000000000000000000000000000000000000000..8d7811b2f98f2bf6e25f71ad3a3444e6999d9d4a --- /dev/null +++ b/modules/rmvpe.py @@ -0,0 +1,637 @@ +from io import BytesIO +import os +from typing import List, Optional, Tuple +import numpy as np +import torch + +import torch.nn as nn +import torch.nn.functional as F +from librosa.util import normalize, pad_center, tiny +from scipy.signal import get_window + +import logging + +logger = logging.getLogger(__name__) + + +class STFT(torch.nn.Module): + def __init__( + self, filter_length=1024, hop_length=512, win_length=None, window="hann" + ): + """ + This module implements an STFT using 1D convolution and 1D transpose convolutions. + This is a bit tricky so there are some cases that probably won't work as working + out the same sizes before and after in all overlap add setups is tough. Right now, + this code should work with hop lengths that are half the filter length (50% overlap + between frames). + + Keyword Arguments: + filter_length {int} -- Length of filters used (default: {1024}) + hop_length {int} -- Hop length of STFT (restrict to 50% overlap between frames) (default: {512}) + win_length {[type]} -- Length of the window function applied to each frame (if not specified, it + equals the filter length). (default: {None}) + window {str} -- Type of window to use (options are bartlett, hann, hamming, blackman, blackmanharris) + (default: {'hann'}) + """ + super(STFT, self).__init__() + self.filter_length = filter_length + self.hop_length = hop_length + self.win_length = win_length if win_length else filter_length + self.window = window + self.forward_transform = None + self.pad_amount = int(self.filter_length / 2) + fourier_basis = np.fft.fft(np.eye(self.filter_length)) + + cutoff = int((self.filter_length / 2 + 1)) + fourier_basis = np.vstack( + [np.real(fourier_basis[:cutoff, :]), np.imag(fourier_basis[:cutoff, :])] + ) + forward_basis = torch.FloatTensor(fourier_basis) + inverse_basis = torch.FloatTensor(np.linalg.pinv(fourier_basis)) + + assert filter_length >= self.win_length + # get window and zero center pad it to filter_length + fft_window = get_window(window, self.win_length, fftbins=True) + fft_window = pad_center(fft_window, size=filter_length) + fft_window = torch.from_numpy(fft_window).float() + + # window the bases + forward_basis *= fft_window + inverse_basis = (inverse_basis.T * fft_window).T + + self.register_buffer("forward_basis", forward_basis.float()) + self.register_buffer("inverse_basis", inverse_basis.float()) + self.register_buffer("fft_window", fft_window.float()) + + def transform(self, input_data, return_phase=False): + """Take input data (audio) to STFT domain. + + Arguments: + input_data {tensor} -- Tensor of floats, with shape (num_batch, num_samples) + + Returns: + magnitude {tensor} -- Magnitude of STFT with shape (num_batch, + num_frequencies, num_frames) + phase {tensor} -- Phase of STFT with shape (num_batch, + num_frequencies, num_frames) + """ + input_data = F.pad( + input_data, + (self.pad_amount, self.pad_amount), + mode="reflect", + ) + forward_transform = input_data.unfold( + 1, self.filter_length, self.hop_length + ).permute(0, 2, 1) + forward_transform = torch.matmul(self.forward_basis, forward_transform) + cutoff = int((self.filter_length / 2) + 1) + real_part = forward_transform[:, :cutoff, :] + imag_part = forward_transform[:, cutoff:, :] + magnitude = torch.sqrt(real_part**2 + imag_part**2) + if return_phase: + phase = torch.atan2(imag_part.data, real_part.data) + return magnitude, phase + else: + return magnitude + + def inverse(self, magnitude, phase): + """Call the inverse STFT (iSTFT), given magnitude and phase tensors produced + by the ```transform``` function. + + Arguments: + magnitude {tensor} -- Magnitude of STFT with shape (num_batch, + num_frequencies, num_frames) + phase {tensor} -- Phase of STFT with shape (num_batch, + num_frequencies, num_frames) + + Returns: + inverse_transform {tensor} -- Reconstructed audio given magnitude and phase. Of + shape (num_batch, num_samples) + """ + cat = torch.cat( + [magnitude * torch.cos(phase), magnitude * torch.sin(phase)], dim=1 + ) + fold = torch.nn.Fold( + output_size=(1, (cat.size(-1) - 1) * self.hop_length + self.filter_length), + kernel_size=(1, self.filter_length), + stride=(1, self.hop_length), + ) + inverse_transform = torch.matmul(self.inverse_basis, cat) + inverse_transform = fold(inverse_transform)[ + :, 0, 0, self.pad_amount : -self.pad_amount + ] + window_square_sum = ( + self.fft_window.pow(2).repeat(cat.size(-1), 1).T.unsqueeze(0) + ) + window_square_sum = fold(window_square_sum)[ + :, 0, 0, self.pad_amount : -self.pad_amount + ] + inverse_transform /= window_square_sum + return inverse_transform + + def forward(self, input_data): + """Take input data (audio) to STFT domain and then back to audio. + + Arguments: + input_data {tensor} -- Tensor of floats, with shape (num_batch, num_samples) + + Returns: + reconstruction {tensor} -- Reconstructed audio given magnitude and phase. Of + shape (num_batch, num_samples) + """ + self.magnitude, self.phase = self.transform(input_data, return_phase=True) + reconstruction = self.inverse(self.magnitude, self.phase) + return reconstruction + + +from time import time as ttime + + +class BiGRU(nn.Module): + def __init__(self, input_features, hidden_features, num_layers): + super(BiGRU, self).__init__() + self.gru = nn.GRU( + input_features, + hidden_features, + num_layers=num_layers, + batch_first=True, + bidirectional=True, + ) + + def forward(self, x): + return self.gru(x)[0] + + +class ConvBlockRes(nn.Module): + def __init__(self, in_channels, out_channels, momentum=0.01): + super(ConvBlockRes, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + bias=False, + ), + nn.BatchNorm2d(out_channels, momentum=momentum), + nn.ReLU(), + nn.Conv2d( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + bias=False, + ), + nn.BatchNorm2d(out_channels, momentum=momentum), + nn.ReLU(), + ) + # self.shortcut:Optional[nn.Module] = None + if in_channels != out_channels: + self.shortcut = nn.Conv2d(in_channels, out_channels, (1, 1)) + + def forward(self, x: torch.Tensor): + if not hasattr(self, "shortcut"): + return self.conv(x) + x + else: + return self.conv(x) + self.shortcut(x) + + +class Encoder(nn.Module): + def __init__( + self, + in_channels, + in_size, + n_encoders, + kernel_size, + n_blocks, + out_channels=16, + momentum=0.01, + ): + super(Encoder, self).__init__() + self.n_encoders = n_encoders + self.bn = nn.BatchNorm2d(in_channels, momentum=momentum) + self.layers = nn.ModuleList() + self.latent_channels = [] + for i in range(self.n_encoders): + self.layers.append( + ResEncoderBlock( + in_channels, out_channels, kernel_size, n_blocks, momentum=momentum + ) + ) + self.latent_channels.append([out_channels, in_size]) + in_channels = out_channels + out_channels *= 2 + in_size //= 2 + self.out_size = in_size + self.out_channel = out_channels + + def forward(self, x: torch.Tensor): + concat_tensors: List[torch.Tensor] = [] + x = self.bn(x) + for i, layer in enumerate(self.layers): + t, x = layer(x) + concat_tensors.append(t) + return x, concat_tensors + + +class ResEncoderBlock(nn.Module): + def __init__( + self, in_channels, out_channels, kernel_size, n_blocks=1, momentum=0.01 + ): + super(ResEncoderBlock, self).__init__() + self.n_blocks = n_blocks + self.conv = nn.ModuleList() + self.conv.append(ConvBlockRes(in_channels, out_channels, momentum)) + for i in range(n_blocks - 1): + self.conv.append(ConvBlockRes(out_channels, out_channels, momentum)) + self.kernel_size = kernel_size + if self.kernel_size is not None: + self.pool = nn.AvgPool2d(kernel_size=kernel_size) + + def forward(self, x): + for i, conv in enumerate(self.conv): + x = conv(x) + if self.kernel_size is not None: + return x, self.pool(x) + else: + return x + + +class Intermediate(nn.Module): # + def __init__(self, in_channels, out_channels, n_inters, n_blocks, momentum=0.01): + super(Intermediate, self).__init__() + self.n_inters = n_inters + self.layers = nn.ModuleList() + self.layers.append( + ResEncoderBlock(in_channels, out_channels, None, n_blocks, momentum) + ) + for i in range(self.n_inters - 1): + self.layers.append( + ResEncoderBlock(out_channels, out_channels, None, n_blocks, momentum) + ) + + def forward(self, x): + for i, layer in enumerate(self.layers): + x = layer(x) + return x + + +class ResDecoderBlock(nn.Module): + def __init__(self, in_channels, out_channels, stride, n_blocks=1, momentum=0.01): + super(ResDecoderBlock, self).__init__() + out_padding = (0, 1) if stride == (1, 2) else (1, 1) + self.n_blocks = n_blocks + self.conv1 = nn.Sequential( + nn.ConvTranspose2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=(3, 3), + stride=stride, + padding=(1, 1), + output_padding=out_padding, + bias=False, + ), + nn.BatchNorm2d(out_channels, momentum=momentum), + nn.ReLU(), + ) + self.conv2 = nn.ModuleList() + self.conv2.append(ConvBlockRes(out_channels * 2, out_channels, momentum)) + for i in range(n_blocks - 1): + self.conv2.append(ConvBlockRes(out_channels, out_channels, momentum)) + + def forward(self, x, concat_tensor): + x = self.conv1(x) + x = torch.cat((x, concat_tensor), dim=1) + for i, conv2 in enumerate(self.conv2): + x = conv2(x) + return x + + +class Decoder(nn.Module): + def __init__(self, in_channels, n_decoders, stride, n_blocks, momentum=0.01): + super(Decoder, self).__init__() + self.layers = nn.ModuleList() + self.n_decoders = n_decoders + for i in range(self.n_decoders): + out_channels = in_channels // 2 + self.layers.append( + ResDecoderBlock(in_channels, out_channels, stride, n_blocks, momentum) + ) + in_channels = out_channels + + def forward(self, x: torch.Tensor, concat_tensors: List[torch.Tensor]): + for i, layer in enumerate(self.layers): + x = layer(x, concat_tensors[-1 - i]) + return x + + +class DeepUnet(nn.Module): + def __init__( + self, + kernel_size, + n_blocks, + en_de_layers=5, + inter_layers=4, + in_channels=1, + en_out_channels=16, + ): + super(DeepUnet, self).__init__() + self.encoder = Encoder( + in_channels, 128, en_de_layers, kernel_size, n_blocks, en_out_channels + ) + self.intermediate = Intermediate( + self.encoder.out_channel // 2, + self.encoder.out_channel, + inter_layers, + n_blocks, + ) + self.decoder = Decoder( + self.encoder.out_channel, en_de_layers, kernel_size, n_blocks + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x, concat_tensors = self.encoder(x) + x = self.intermediate(x) + x = self.decoder(x, concat_tensors) + return x + + +class E2E(nn.Module): + def __init__( + self, + n_blocks, + n_gru, + kernel_size, + en_de_layers=5, + inter_layers=4, + in_channels=1, + en_out_channels=16, + ): + super(E2E, self).__init__() + self.unet = DeepUnet( + kernel_size, + n_blocks, + en_de_layers, + inter_layers, + in_channels, + en_out_channels, + ) + self.cnn = nn.Conv2d(en_out_channels, 3, (3, 3), padding=(1, 1)) + if n_gru: + self.fc = nn.Sequential( + BiGRU(3 * 128, 256, n_gru), + nn.Linear(512, 360), + nn.Dropout(0.25), + nn.Sigmoid(), + ) + else: + self.fc = nn.Sequential( + nn.Linear(3 * nn.N_MELS, nn.N_CLASS), nn.Dropout(0.25), nn.Sigmoid() + ) + + def forward(self, mel): + # print(mel.shape) + mel = mel.transpose(-1, -2).unsqueeze(1) + x = self.cnn(self.unet(mel)).transpose(1, 2).flatten(-2) + x = self.fc(x) + # print(x.shape) + return x + + +from librosa.filters import mel + + +class MelSpectrogram(torch.nn.Module): + def __init__( + self, + is_half, + n_mel_channels, + sampling_rate, + win_length, + hop_length, + n_fft=None, + mel_fmin=0, + mel_fmax=None, + clamp=1e-5, + ): + super().__init__() + n_fft = win_length if n_fft is None else n_fft + self.hann_window = {} + mel_basis = mel( + sr=sampling_rate, + n_fft=n_fft, + n_mels=n_mel_channels, + fmin=mel_fmin, + fmax=mel_fmax, + htk=True, + ) + mel_basis = torch.from_numpy(mel_basis).float() + self.register_buffer("mel_basis", mel_basis) + self.n_fft = win_length if n_fft is None else n_fft + self.hop_length = hop_length + self.win_length = win_length + self.sampling_rate = sampling_rate + self.n_mel_channels = n_mel_channels + self.clamp = clamp + self.is_half = is_half + + def forward(self, audio, keyshift=0, speed=1, center=True): + factor = 2 ** (keyshift / 12) + n_fft_new = int(np.round(self.n_fft * factor)) + win_length_new = int(np.round(self.win_length * factor)) + hop_length_new = int(np.round(self.hop_length * speed)) + keyshift_key = str(keyshift) + "_" + str(audio.device) + if keyshift_key not in self.hann_window: + self.hann_window[keyshift_key] = torch.hann_window(win_length_new).to( + audio.device + ) + if "privateuseone" in str(audio.device): + if not hasattr(self, "stft"): + self.stft = STFT( + filter_length=n_fft_new, + hop_length=hop_length_new, + win_length=win_length_new, + window="hann", + ).to(audio.device) + magnitude = self.stft.transform(audio) + else: + fft = torch.stft( + audio, + n_fft=n_fft_new, + hop_length=hop_length_new, + win_length=win_length_new, + window=self.hann_window[keyshift_key], + center=center, + return_complex=True, + ) + magnitude = torch.sqrt(fft.real.pow(2) + fft.imag.pow(2)) + if keyshift != 0: + size = self.n_fft // 2 + 1 + resize = magnitude.size(1) + if resize < size: + magnitude = F.pad(magnitude, (0, 0, 0, size - resize)) + magnitude = magnitude[:, :size, :] * self.win_length / win_length_new + mel_output = torch.matmul(self.mel_basis, magnitude) + if self.is_half == True: + mel_output = mel_output.half() + log_mel_spec = torch.log(torch.clamp(mel_output, min=self.clamp)) + return log_mel_spec + + +class RMVPE: + def __init__(self, model_path: str, is_half, device=None, use_jit=False): + self.resample_kernel = {} + self.resample_kernel = {} + self.is_half = is_half + if device is None: + #device = "cuda:0" if torch.cuda.is_available() else "cpu" + if torch.cuda.is_available(): + device = "cuda:0" + elif torch.backends.mps.is_available(): + device = "mps" + else: + device = "cpu" + self.device = device + self.mel_extractor = MelSpectrogram( + is_half, 128, 16000, 1024, 160, None, 30, 8000 + ).to(device) + if "privateuseone" in str(device): + import onnxruntime as ort + + ort_session = ort.InferenceSession( + "%s/rmvpe.onnx" % os.environ["rmvpe_root"], + providers=["DmlExecutionProvider"], + ) + self.model = ort_session + else: + if str(self.device) == "cuda": + self.device = torch.device("cuda:0") + + def get_default_model(): + model = E2E(4, 1, (2, 2)) + ckpt = torch.load(model_path, map_location="cpu") + model.load_state_dict(ckpt) + model.eval() + if is_half: + model = model.half() + else: + model = model.float() + return model + + self.model = get_default_model() + + self.model = self.model.to(device) + cents_mapping = 20 * np.arange(360) + 1997.3794084376191 + self.cents_mapping = np.pad(cents_mapping, (4, 4)) # 368 + + def mel2hidden(self, mel): + with torch.no_grad(): + n_frames = mel.shape[-1] + n_pad = 32 * ((n_frames - 1) // 32 + 1) - n_frames + if n_pad > 0: + mel = F.pad(mel, (0, n_pad), mode="constant") + if "privateuseone" in str(self.device): + onnx_input_name = self.model.get_inputs()[0].name + onnx_outputs_names = self.model.get_outputs()[0].name + hidden = self.model.run( + [onnx_outputs_names], + input_feed={onnx_input_name: mel.cpu().numpy()}, + )[0] + else: + mel = mel.half() if self.is_half else mel.float() + hidden = self.model(mel) + return hidden[:, :n_frames] + + def decode(self, hidden, thred=0.03): + cents_pred = self.to_local_average_cents(hidden, thred=thred) + f0 = 10 * (2 ** (cents_pred / 1200)) + f0[f0 == 10] = 0 + # f0 = np.array([10 * (2 ** (cent_pred / 1200)) if cent_pred else 0 for cent_pred in cents_pred]) + return f0 + + def infer_from_audio(self, audio, thred=0.03): + # torch.cuda.synchronize() + # t0 = ttime() + if not torch.is_tensor(audio): + audio = torch.from_numpy(audio) + mel = self.mel_extractor( + audio.float().to(self.device).unsqueeze(0), center=True + ) + # print(123123123,mel.device.type) + # torch.cuda.synchronize() + # t1 = ttime() + hidden = self.mel2hidden(mel) + # torch.cuda.synchronize() + # t2 = ttime() + # print(234234,hidden.device.type) + if "privateuseone" not in str(self.device): + hidden = hidden.squeeze(0).cpu().numpy() + else: + hidden = hidden[0] + if self.is_half == True: + hidden = hidden.astype("float32") + + f0 = self.decode(hidden, thred=thred) + # torch.cuda.synchronize() + # t3 = ttime() + # print("hmvpe:%s\t%s\t%s\t%s"%(t1-t0,t2-t1,t3-t2,t3-t0)) + return f0 + def infer_from_audio_batch(self, audio, thred=0.03): + # torch.cuda.synchronize() + # t0 = ttime() + if not torch.is_tensor(audio): + audio = torch.from_numpy(audio) + mel = self.mel_extractor( + audio.float().to(self.device), center=True + ) + # print(123123123,mel.device.type) + # torch.cuda.synchronize() + # t1 = ttime() + hidden = self.mel2hidden(mel) + # torch.cuda.synchronize() + # t2 = ttime() + # print(234234,hidden.device.type) + if "privateuseone" not in str(self.device): + hidden = hidden.cpu().numpy() + else: + pass + if self.is_half == True: + hidden = hidden.astype("float32") + + f0s = [] + for bib in range(hidden.shape[0]): + f0s.append(self.decode(hidden[bib], thred=thred)) + f0s = np.stack(f0s) + f0s = torch.from_numpy(f0s).to(self.device) + # torch.cuda.synchronize() + # t3 = ttime() + # print("hmvpe:%s\t%s\t%s\t%s"%(t1-t0,t2-t1,t3-t2,t3-t0)) + return f0s + + def to_local_average_cents(self, salience, thred=0.05): + # t0 = ttime() + center = np.argmax(salience, axis=1) # 帧长#index + salience = np.pad(salience, ((0, 0), (4, 4))) # 帧长,368 + # t1 = ttime() + center += 4 + todo_salience = [] + todo_cents_mapping = [] + starts = center - 4 + ends = center + 5 + for idx in range(salience.shape[0]): + todo_salience.append(salience[:, starts[idx] : ends[idx]][idx]) + todo_cents_mapping.append(self.cents_mapping[starts[idx] : ends[idx]]) + # t2 = ttime() + todo_salience = np.array(todo_salience) # 帧长,9 + todo_cents_mapping = np.array(todo_cents_mapping) # 帧长,9 + product_sum = np.sum(todo_salience * todo_cents_mapping, 1) + weight_sum = np.sum(todo_salience, 1) # 帧长 + devided = product_sum / weight_sum # 帧长 + # t3 = ttime() + maxx = np.max(salience, axis=1) # 帧长 + devided[maxx <= thred] = 0 + # t4 = ttime() + # print("decode:%s\t%s\t%s\t%s" % (t1 - t0, t2 - t1, t3 - t2, t4 - t3)) + return devided diff --git a/modules/v2/__init__.py b/modules/v2/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/v2/ar.py b/modules/v2/ar.py new file mode 100644 index 0000000000000000000000000000000000000000..0611a459c74d1260442855499b03a6885bb4fb04 --- /dev/null +++ b/modules/v2/ar.py @@ -0,0 +1,763 @@ +import dataclasses +import json +import math +from collections import OrderedDict +from functools import partial, wraps +from dataclasses import dataclass +from pathlib import Path +from typing import Optional, Tuple, List +from tqdm import tqdm + +import torch +import torch.nn as nn +from einops import rearrange +from torch import Tensor +from torch.nn import functional as F +from torch.utils.checkpoint import checkpoint + + +def find_multiple(n: int, k: int) -> int: + if n % k == 0: + return n + return n + k - (n % k) + +def l2norm(t, groups = 1): + t = rearrange(t, '... (g d) -> ... g d', g = groups) + t = F.normalize(t, p = 2, dim = -1) + return rearrange(t, '... g d -> ... (g d)') + +@dataclass +class BaseModelArgs: + model_type: str = "base" + + vocab_size: int = 32000 + n_layer: int = 32 + n_head: int = 32 + dim: int = 4096 + intermediate_size: int = None + n_local_heads: int = -1 + head_dim: int = 64 + rope_base: float = 10000 + norm_eps: float = 1e-5 + max_seq_len: int = 4096 + dropout: float = 0.0 + tie_word_embeddings: bool = True + attention_qkv_bias: bool = False + + # Gradient checkpointing + use_gradient_checkpointing: bool = False + + # Initialize the model + initializer_range: float = 0.02 + + qk_norm: bool = False + layerscale: bool = False + + def __post_init__(self): + if self.n_local_heads == -1: + self.n_local_heads = self.n_head + if self.intermediate_size is None: + hidden_dim = 4 * self.dim + n_hidden = int(2 * hidden_dim / 3) + self.intermediate_size = find_multiple(n_hidden, 256) + self.head_dim = self.dim // self.n_head + + def save(self, path: str): + with open(path, "w") as f: + json.dump(self.__dict__, f, indent=4, sort_keys=True, ensure_ascii=False) + + +@dataclass +class NaiveModelArgs(BaseModelArgs): + model_type: str = "naive" + + +class KVCache(nn.Module): + def __init__( + self, max_batch_size, max_seq_len, n_heads, head_dim, dtype=torch.bfloat16 + ): + super().__init__() + cache_shape = (max_batch_size, n_heads, max_seq_len, head_dim) + self.register_buffer("k_cache", torch.zeros(cache_shape, dtype=dtype)) + self.register_buffer("v_cache", torch.zeros(cache_shape, dtype=dtype)) + + def update(self, input_pos, k_val, v_val): + # input_pos: [S], k_val: [B, H, S, D] + assert input_pos.shape[0] == k_val.shape[2] + + k_out = self.k_cache + v_out = self.v_cache + k_out[:, :, input_pos] = k_val + v_out[:, :, input_pos] = v_val + + return k_out, v_out + + +@dataclass +class TransformerForwardResult: + token_logits: Tensor + token_targets: Tensor + + +@dataclass +class BaseTransformerForwardResult: + logits: Tensor + hidden_states: Tensor + + +class BaseTransformer(nn.Module): + def __init__( + self, + config: BaseModelArgs, + init_weights: bool = True, + ) -> None: + super().__init__() + self.config = config + + # Slow transformer + self.embeddings = nn.Embedding( + config.vocab_size, + config.dim, + ) + self.layers = nn.ModuleList( + TransformerBlock(config, use_sdpa=True) for _ in range(config.n_layer) + ) + self.norm = RMSNorm(config.dim, eps=config.norm_eps) + + if self.config.tie_word_embeddings is False: + self.output = nn.Linear( + config.dim, + config.vocab_size, + bias=False, + ) + + self.register_buffer( + "freqs_cis", + precompute_freqs_cis( + config.max_seq_len, + config.dim // config.n_head, + config.rope_base, + ), + persistent=False, + ) + self.register_buffer( + "causal_mask", + torch.tril( + torch.ones( + config.max_seq_len, + config.max_seq_len, + dtype=torch.bool, + ) + ), + persistent=False, + ) + + self.output = nn.Linear( + config.dim, + config.vocab_size, + bias=False, + ) + + # For kv cache + self.max_batch_size = -1 + self.max_seq_len = -1 + + if init_weights: + self.apply(self._init_weights) + + def setup_caches( + self, max_batch_size: int, max_seq_len: int, dtype: torch.dtype = torch.bfloat16, device: torch.device = "cuda" + ): + if self.max_seq_len >= max_seq_len and self.max_batch_size >= max_batch_size: + return + + head_dim = self.config.dim // self.config.n_head + max_seq_len = find_multiple(max_seq_len, 8) + self.max_seq_len = max_seq_len + self.max_batch_size = max_batch_size + + for b in self.layers: + b.attention.kv_cache = KVCache( + max_batch_size, + max_seq_len, + self.config.n_local_heads, + head_dim, + dtype=dtype, + ).to(device) + + def embed_base(self, x: Tensor, x_lens: Tensor) -> Tensor: + for bib in range(x.size(0)): + x[bib, x_lens[bib]:] = self.config.vocab_size - 1 + + x_emb = self.embeddings(x) + return x, x_emb + + def forward( + self, + inp: Tensor, + key_padding_mask: Optional[Tensor] = None, + input_pos: Optional[Tensor] = None, + ) -> BaseTransformerForwardResult: + seq_len = inp.size(1) + + # Here we want to merge the embeddings of the codebooks + # x = self.embed(inp) + x = inp.clone() + + if input_pos is None: + freqs_cis = self.freqs_cis[:seq_len].repeat(inp.size(0), 1, 1, 1) + else: + freqs_cis = self.freqs_cis[input_pos] + + # Not that the causal mask here follows the definition of scaled_dot_product_attention + # That is, FALSE means masked out + # To maintain consistency, key_padding_mask use TRUE to mask out + mask = None + if key_padding_mask is not None: + mask = self.causal_mask[None, None, :seq_len, :seq_len] # (B, N, Q, K) + mask = mask & key_padding_mask[:, None, None, :].logical_not() + + for layer in self.layers: + if self.config.use_gradient_checkpointing and self.training: + x = checkpoint(layer, x, freqs_cis, mask, use_reentrant=True) + else: + x = layer(x, freqs_cis, mask) + + # We got slow_out here + slow_out = self.norm(x) + + if self.config.tie_word_embeddings: + token_logits = F.linear(slow_out, self.embeddings.weight) + else: + token_logits = self.output(slow_out) + + return BaseTransformerForwardResult( + logits=token_logits, + hidden_states=x, + ) + + def forward_generate( + self, + inp: Tensor, + input_pos: Optional[Tensor] = None, + kv_pos: Optional[Tensor] = None, + return_all: bool = False, + ) -> BaseTransformerForwardResult: + # This is used for generation, optimized for torch compile + + x = inp + max_seq_len = self.max_seq_len + + mask = self.causal_mask[None, None, kv_pos, :max_seq_len] # (B, N, Q, K) + freqs_cis = self.freqs_cis[input_pos] + + for layer in self.layers: + x = layer(x, freqs_cis, mask, input_pos=kv_pos) + + x = x[:, -1:] + + # We got slow_out here + slow_out = self.norm(x) + + token_logits = self.output(slow_out) + + return BaseTransformerForwardResult( + logits=token_logits, + hidden_states=x, + ) + + def _init_weights(self, module): + std = self.config.initializer_range + if isinstance(module, nn.Linear): + module.weight.data.normal_(mean=0.0, std=std) + if module.bias is not None: + module.bias.data.zero_() + elif isinstance(module, nn.Embedding): + module.weight.data.normal_(mean=0.0, std=std) + if module.padding_idx is not None: + module.weight.data[module.padding_idx].zero_() + +class NaiveTransformer(BaseTransformer): + def __init__(self, config: NaiveModelArgs) -> None: + super().__init__(config, init_weights=False) + self.apply(self._init_weights) + + def forward( + self, + inp: Tensor, + cond_lens: Tensor, + target: Tensor, + target_lens: Tensor, + key_padding_mask: Optional[Tensor] = None, + input_pos: Optional[Tensor] = None, + ) -> TransformerForwardResult: + parent_result = super().forward( + inp=inp, + key_padding_mask=key_padding_mask, + input_pos=input_pos, + ) + token_logits = parent_result.logits + + # construct targets for token_logits + token_targets = torch.zeros(token_logits.size(0), token_logits.size(1), dtype=torch.long, + device=target.device) - 100 + for bib in range(token_targets.size(0)): + token_targets[bib, cond_lens[bib] + 1:cond_lens[bib] + target_lens[bib] + 1] = target[bib, :target_lens[bib]] + token_targets[bib, cond_lens[bib] + target_lens[bib] + 1] = self.config.vocab_size - 1 + return TransformerForwardResult( + token_logits=token_logits, + token_targets=token_targets, + ) + + def infer_slow(self, inp: Tensor, input_pos: Optional[Tensor] = None): + # no kv cache used + parent_result = super().forward(inp, input_pos=input_pos) + latent = parent_result.hidden_states[:, -1] + base_logits = parent_result.logits[:, -1] + base_sampled, _ = topk_sampling(base_logits, top_k=-1, top_p=1.0) + return base_sampled + + def forward_generate( + self, + x: Tensor, + input_pos: Optional[Tensor] = None, + kv_pos: Optional[Tensor] = None, + vq_masks: Optional[Tensor] = None, + ) -> TransformerForwardResult: + x = super().forward_generate(x, input_pos, kv_pos, vq_masks) + return x + +class NaiveWrapper(nn.Module): + def __init__(self, model: NaiveTransformer) -> None: + super().__init__() + self.model = model + self.sep_token_emb = nn.Parameter(torch.randn(model.config.dim)) + + def setup_caches(self, max_batch_size: int, max_seq_len: int, dtype: torch.dtype = torch.bfloat16, device: torch.device = "cuda"): + self.model.setup_caches(max_batch_size, max_seq_len, dtype, device) + + def forward(self, cond: Tensor, cond_lens: Tensor, x: Tensor, x_lens: Tensor) -> torch.Tensor: + # style_emb = self.style_in(style).unsqueeze(1) # [B, 1, D] + sep_token_emb = self.sep_token_emb.expand(x.size(0), 1, -1) + _, x_emb = self.model.embed_base(x, x_lens) + emb_seq_list = [] + for i in range(x.size(0)): + emb_seq = torch.cat([ + sep_token_emb[i:i + 1], + cond[i:i+1, :cond_lens[i]], + sep_token_emb[i:i+1], + x_emb[i:i+1, :x_lens[i]]], dim=1) + emb_seq_list.append(emb_seq) + max_len = max([emb_seq.size(1) for emb_seq in emb_seq_list]) + emb_seq = torch.cat([ + F.pad(emb_seq, (0, 0, 0, max_len - emb_seq.size(1)), value=0) + for emb_seq in emb_seq_list + ], dim=0) + # input_pos = torch.arange(emb_seq.size(1), device=emb_seq.device).repeat(emb_seq.size(0), 1) + input_pos = torch.zeros(emb_seq.size(0), emb_seq.size(1), device=emb_seq.device, dtype=torch.long) + for i in range(x.size(0)): + input_pos[i, :cond_lens[i] + 1] = torch.arange(cond_lens[i] + 1, device=emb_seq.device) + input_pos[i, cond_lens[i] + 1: cond_lens[i] + x_lens[i] + 2] = torch.arange(x_lens[i] + 1, device=emb_seq.device) + out = self.model(emb_seq, cond_lens, x, x_lens, input_pos=input_pos) + loss = F.cross_entropy(out.token_logits.transpose(1, 2), out.token_targets.long(), ignore_index=-100) + return loss + + @torch.no_grad() + def infer(self, cond: Tensor) -> torch.Tensor: + sep_token_emb = self.sep_token_emb.expand(1, 1, -1) + emb_seq = torch.cat([sep_token_emb, cond, sep_token_emb], dim=1) + pred_codes = [] + input_pos = torch.arange(cond.size(1) + 1, device=cond.device) + for i in tqdm(range(4000)): + input_pos = torch.cat([input_pos, torch.LongTensor([i]).to(cond.device)], dim=0) + base = self.model.infer_slow(emb_seq, input_pos) + if base == self.model.config.vocab_size - 1: + break + new_emb = self.model.embed_base(base, torch.LongTensor([1]).to(base.device))[1] + emb_seq = torch.cat([emb_seq, new_emb], dim=1) + pred_codes.append(base) + return torch.cat(pred_codes, dim=-1) + + @torch.no_grad() + def generate( + self, + prompt_text, + prompt_target, + compiled_decode_fn = None, + **sampling_kwargs, + ): + sep_token_emb = self.sep_token_emb.expand(1, 1, -1) + emb_seq = torch.cat([sep_token_emb, prompt_text, sep_token_emb], dim=1) + input_pos = torch.arange(prompt_text.size(1) + 1, device=emb_seq.device) + input_pos = torch.cat([input_pos, torch.LongTensor([0]).to(emb_seq.device)]) + prompt_target_emb = self.model.embed_base(prompt_target,torch.LongTensor([prompt_target.size(1)]).to(prompt_target.device))[1] + emb_seq = torch.cat([emb_seq, prompt_target_emb], dim=1) + input_pos = torch.cat([input_pos, torch.arange(prompt_target_emb.size(1)).to(input_pos.device) + 1]) + + pred_codes = [] + kv_pos = torch.arange(emb_seq.size(1), device=emb_seq.device) + next_tokens = self.decode_one_token_ar(emb_seq, input_pos, kv_pos, suppress_tokens=[self.model.config.vocab_size - 1], **sampling_kwargs) + pred_base = next_tokens[0] + pred_codes.append(pred_base) + new_emb = self.model.embed_base(pred_base.unsqueeze(0), torch.LongTensor([1]).to(pred_base.device))[1] + emb_seq = torch.cat([emb_seq, new_emb], dim=1) + for _ in tqdm(range(4000)): + suppress_eos = len(pred_codes) < 10 + input_pos = input_pos[-1:] + 1 + kv_pos = kv_pos[-1:] + 1 + next_tokens = self.decode_one_token_ar( + emb_seq[:, -1:].reshape(1, 1, -1), + input_pos.reshape(1), + kv_pos.reshape(1), + previous_tokens=torch.cat(pred_codes), + suppress_tokens=[self.model.config.vocab_size - 1] if suppress_eos else None, + compiled_decode_fn=compiled_decode_fn, + **sampling_kwargs) + pred_base = next_tokens[0] + if pred_base == self.model.config.vocab_size - 1: + break + pred_codes.append(pred_base.clone()) + new_emb = self.model.embed_base(pred_base.unsqueeze(0), torch.LongTensor([1]).to(pred_base.device))[1] + emb_seq = torch.cat([emb_seq, new_emb], dim=1) + return torch.stack(pred_codes, dim=-1) + + def decode_one_token_ar( + self, + x: torch.Tensor, + input_pos: torch.Tensor, + kv_pos: torch.Tensor, + previous_tokens: torch.Tensor = None, + compiled_decode_fn = None, + **sampling_kwargs, + ) -> torch.Tensor: + if compiled_decode_fn is not None: + x = compiled_decode_fn(x, input_pos, kv_pos) + else: + x = self.model.forward_generate(x, input_pos, kv_pos) + + sampling_kwargs_main = sampling_kwargs.copy() + codebooks = [ + sample( + x.logits, + previous_tokens=( + previous_tokens[0] if previous_tokens is not None else None + ), + **sampling_kwargs_main, + )[0] + ] + codebooks = torch.stack(codebooks, dim=0) + return codebooks + +class TransformerBlock(nn.Module): + def __init__(self, config: BaseModelArgs, use_sdpa: bool = True) -> None: + super().__init__() + self.attention = Attention(config, use_sdpa=use_sdpa) + self.feed_forward = FeedForward(config) + self.ffn_norm = RMSNorm(config.dim, config.norm_eps) + self.attention_norm = RMSNorm(config.dim, config.norm_eps) + + def forward( + self, x: Tensor, freqs_cis: Tensor, mask: Tensor, input_pos: Tensor = None + ) -> Tensor: + h = x + self.attention(self.attention_norm(x), freqs_cis, mask, input_pos) + out = h + self.feed_forward(self.ffn_norm(h)) + return out + + +class Attention(nn.Module): + def __init__(self, config: BaseModelArgs, use_sdpa: bool = True): + super().__init__() + assert config.dim % config.n_head == 0 + + total_head_dim = (config.n_head + 2 * config.n_local_heads) * config.head_dim + # key, query, value projections for all heads, but in a batch + self.wqkv = nn.Linear( + config.dim, total_head_dim, bias=config.attention_qkv_bias + ) + self.wo = nn.Linear(config.dim, config.dim, bias=False) + self.kv_cache = None + + self.dropout = config.dropout + self.n_head = config.n_head + self.head_dim = config.head_dim + self.n_local_heads = config.n_local_heads + self.dim = config.dim + self.use_sdpa = use_sdpa + self._register_load_state_dict_pre_hook(self.load_hook) + self.qk_norm = config.qk_norm + self.qk_norm_groups = 1 + self.qk_norm_scale = 10 + self.qk_norm_dim_scale = False + self.qk_norm_q_scale = self.qk_norm_k_scale = 1 + + if self.qk_norm and self.qk_norm_dim_scale: + self.qk_norm_q_scale = nn.Parameter(torch.ones(self.n_head, 1, self.head_dim)) + self.qk_norm_k_scale = nn.Parameter(torch.ones(self.n_head, 1, self.head_dim)) + def load_hook(self, state_dict, prefix, *args): + if prefix + "wq.weight" in state_dict: + wq = state_dict.pop(prefix + "wq.weight") + wk = state_dict.pop(prefix + "wk.weight") + wv = state_dict.pop(prefix + "wv.weight") + state_dict[prefix + "wqkv.weight"] = torch.cat([wq, wk, wv]) + + def forward( + self, + x: Tensor, + freqs_cis: Tensor, + mask: Tensor, + input_pos: Optional[Tensor] = None, + ) -> Tensor: + bsz, seqlen, _ = x.shape + + kv_size = self.n_local_heads * self.head_dim + q, k, v = self.wqkv(x).split([self.dim, kv_size, kv_size], dim=-1) + + q = q.view(bsz, seqlen, self.n_head, self.head_dim) + k = k.view(bsz, seqlen, self.n_local_heads, self.head_dim) + v = v.view(bsz, seqlen, self.n_local_heads, self.head_dim) + + if self.qk_norm: + qk_l2norm = partial(l2norm, groups = self.qk_norm_groups) + q, k = map(qk_l2norm, (q, k)) + scale = self.qk_norm_scale + + q = q * self.qk_norm_q_scale + k = k * self.qk_norm_k_scale + + q = apply_rotary_emb(q, freqs_cis) + k = apply_rotary_emb(k, freqs_cis) + + q, k, v = map(lambda x: x.transpose(1, 2), (q, k, v)) + + if self.kv_cache is not None: + k, v = self.kv_cache.update(input_pos, k, v) + + k = k.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + v = v.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + + if self.use_sdpa: + if mask is None: + y = F.scaled_dot_product_attention( + q, + k, + v, + dropout_p=self.dropout if self.training else 0.0, + is_causal=True, + # No third party attn_mask here to use flash_attention + ) + else: + y = F.scaled_dot_product_attention( + q, + k, + v, + attn_mask=mask, + dropout_p=self.dropout if self.training else 0.0, + ) + else: + y = self.eq_scaled_dot_product_attention( + q, + k, + v, + attn_mask=mask, + dropout_p=self.dropout if self.training else 0.0, + ) + + y = y.transpose(1, 2).contiguous().view(bsz, seqlen, self.dim) + + return self.wo(y) + + def eq_scaled_dot_product_attention( + self, + query, + key, + value, + attn_mask=None, + dropout_p=0.0, + ) -> torch.Tensor: + # This is a standard scaled dot product attention + # It's low efficient, but it doesn't raise cuda error + + L, S = query.size(-2), key.size(-2) + scale_factor = 1 / math.sqrt(query.size(-1)) + attn_bias = torch.zeros(1, 1, L, S, dtype=query.dtype, device=query.device) + + if attn_mask is not None: + if attn_mask.dtype == torch.bool: + attn_bias.masked_fill_(attn_mask.logical_not(), float("-inf")) + else: + attn_bias += attn_mask + + attn_weight = query @ key.transpose(-2, -1) * scale_factor + attn_weight += attn_bias + attn_weight = torch.softmax(attn_weight, dim=-1) + attn_weight = torch.dropout(attn_weight, dropout_p, train=True) + + return attn_weight @ value + + +class FeedForward(nn.Module): + def __init__(self, config: BaseModelArgs) -> None: + super().__init__() + self.w1 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w3 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w2 = nn.Linear(config.intermediate_size, config.dim, bias=False) + self.dropout = nn.Dropout(p=config.dropout) + + def forward(self, x: Tensor) -> Tensor: + return self.w2(self.dropout(F.silu(self.w1(x)) * self.w3(x))) + + +class RMSNorm(nn.Module): + def __init__(self, dim: int, eps: float = 1e-5): + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.ones(dim)) + + def _norm(self, x): + return x * torch.rsqrt(torch.mean(x * x, dim=-1, keepdim=True) + self.eps) + + def forward(self, x: Tensor) -> Tensor: + output = self._norm(x.float()).type_as(x) + return output * self.weight + + +def precompute_freqs_cis(seq_len: int, n_elem: int, base: int = 10000) -> Tensor: + freqs = 1.0 / ( + base ** (torch.arange(0, n_elem, 2)[: (n_elem // 2)].float() / n_elem) + ) + t = torch.arange(seq_len, device=freqs.device) + freqs = torch.outer(t, freqs) + freqs_cis = torch.polar(torch.ones_like(freqs), freqs) + cache = torch.stack([freqs_cis.real, freqs_cis.imag], dim=-1) + return cache.to(dtype=torch.bfloat16) + + +def apply_rotary_emb(x: Tensor, freqs_cis: Tensor) -> Tensor: + xshaped = x.float().reshape(*x.shape[:-1], -1, 2) + freqs_cis = freqs_cis.view(x.size(0), xshaped.size(1), 1, xshaped.size(3), 2) + x_out2 = torch.stack( + [ + xshaped[..., 0] * freqs_cis[..., 0] - xshaped[..., 1] * freqs_cis[..., 1], + xshaped[..., 1] * freqs_cis[..., 0] + xshaped[..., 0] * freqs_cis[..., 1], + ], + -1, + ) + + x_out2 = x_out2.flatten(3) + return x_out2.type_as(x) + +def top_k_top_p_filtering( + logits, top_k=0, top_p=1.0, filter_value=-float("Inf"), min_tokens_to_keep=1 +): + """Filter a distribution of logits using top-k and/or nucleus (top-p) filtering + Args: + logits: logits distribution shape (batch size, vocabulary size) + if top_k > 0: keep only top k tokens with highest probability (top-k filtering). + if top_p < 1.0: keep the top tokens with cumulative probability >= top_p (nucleus filtering). + Nucleus filtering is described in Holtzman et al. (http://arxiv.org/abs/1904.09751) + Make sure we keep at least min_tokens_to_keep per batch example in the output + From: https://gist.github.com/thomwolf/1a5a29f6962089e871b94cbd09daf317 + """ + if top_k > 0: + top_k = min( + max(top_k, min_tokens_to_keep), logits.size(-1) + ) # Safety check + # Remove all tokens with a probability less than the last token of the top-k + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits[indices_to_remove] = filter_value + + if top_p < 1.0: + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum( + F.softmax(sorted_logits, dim=-1), dim=-1 + ) + + # Remove tokens with cumulative probability above the threshold (token with 0 are kept) + sorted_indices_to_remove = cumulative_probs > top_p + if min_tokens_to_keep > 1: + # Keep at least min_tokens_to_keep (set to min_tokens_to_keep-1 because we add the first one below) + sorted_indices_to_remove[..., :min_tokens_to_keep] = 0 + # Shift the indices to the right to keep also the first token above the threshold + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[ + ..., :-1 + ].clone() + sorted_indices_to_remove[..., 0] = 0 + + # scatter sorted tensors to original indexing + indices_to_remove = sorted_indices_to_remove.scatter( + 1, sorted_indices, sorted_indices_to_remove + ) + logits[indices_to_remove] = filter_value + return logits + +def topk_sampling(logits, top_k=10, top_p=1.0, temperature=1.0): + # temperature: (`optional`) float + # The value used to module the next token probabilities. Must be strictly positive. Default to 1.0. + # top_k: (`optional`) int + # The number of highest probability vocabulary tokens to keep for top-k-filtering. Between 1 and infinity. Default to 50. + # top_p: (`optional`) float + # The cumulative probability of parameter highest probability vocabulary tokens to keep for nucleus sampling. Must be between 0 and 1. Default to 1. + + # Temperature (higher temperature => more likely to sample low probability tokens) + if temperature != 1.0: + logits = logits / temperature + # Top-p/top-k filtering + logits = top_k_top_p_filtering(logits, top_k=top_k, top_p=top_p) + # Sample + token = torch.multinomial(F.softmax(logits, dim=-1), num_samples=1) + logprobs = F.log_softmax(logits.float(), dim=-1) + current_logprobs = logprobs[torch.arange(logprobs.shape[0]), token.squeeze(1)] + return token, current_logprobs + +def sample( + logits, + previous_tokens: Optional[torch.Tensor] = None, + **sampling_kwargs, +) -> Tuple[torch.Tensor, torch.Tensor]: + probs = logits_to_probs( + logits=logits[0, -1], previous_tokens=previous_tokens, **sampling_kwargs + ) + idx_next = multinomial_sample_one_no_sync(probs) + return idx_next, probs + +def multinomial_sample_one_no_sync( + probs_sort, +): # Does multinomial sampling without a cuda synchronization + q = torch.empty_like(probs_sort).exponential_(1) + return torch.argmax(probs_sort / q, dim=-1, keepdim=True).to(dtype=torch.int) + + +def logits_to_probs( + logits, + previous_tokens: Optional[torch.Tensor] = None, + suppress_tokens: Optional[List[int]] = None, + temperature: torch.Tensor = 0.7, + top_p: torch.Tensor = 0.7, + repetition_penalty: torch.Tensor = 1.5, +) -> torch.Tensor: + # Apply repetition penalty + if previous_tokens is not None: + previous_tokens = previous_tokens.long() + score = torch.gather(logits, dim=0, index=previous_tokens) + score = torch.where( + score < 0, score * repetition_penalty, score / repetition_penalty + ) + logits.scatter_(dim=0, index=previous_tokens, src=score) + if suppress_tokens is not None: + for token in suppress_tokens: + logits[token] = -float("Inf") + + # Apply top-p sampling + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cum_probs = torch.cumsum(torch.nn.functional.softmax(sorted_logits, dim=-1), dim=-1) + sorted_indices_to_remove = cum_probs > top_p + sorted_indices_to_remove[0] = False # keep at least one option + indices_to_remove = sorted_indices_to_remove.scatter( + dim=0, index=sorted_indices, src=sorted_indices_to_remove + ) + logits = logits.masked_fill(indices_to_remove, -float("Inf")) + + logits = logits / max(temperature, 1e-5) + + probs = torch.nn.functional.softmax(logits, dim=-1) + return probs diff --git a/modules/v2/cfm.py b/modules/v2/cfm.py new file mode 100644 index 0000000000000000000000000000000000000000..929d557b99aaabec19012c810e833aaa922470d5 --- /dev/null +++ b/modules/v2/cfm.py @@ -0,0 +1,173 @@ +import torch +from tqdm import tqdm + +class CFM(torch.nn.Module): + def __init__( + self, + estimator: torch.nn.Module, + ): + super().__init__() + self.sigma_min = 1e-6 + self.estimator = estimator + self.in_channels = estimator.in_channels + self.criterion = torch.nn.L1Loss() + + @torch.inference_mode() + def inference(self, + mu: torch.Tensor, + x_lens: torch.Tensor, + prompt: torch.Tensor, + style: torch.Tensor, + n_timesteps=10, + temperature=1.0, + inference_cfg_rate=[0.5, 0.5], + random_voice=False, + ): + """Forward diffusion + + Args: + mu (torch.Tensor): output of encoder + shape: (batch_size, n_feats, mel_timesteps) + x_lens (torch.Tensor): length of each mel-spectrogram + shape: (batch_size,) + prompt (torch.Tensor): prompt + shape: (batch_size, n_feats, prompt_len) + style (torch.Tensor): style + shape: (batch_size, style_dim) + n_timesteps (int): number of diffusion steps + temperature (float, optional): temperature for scaling noise. Defaults to 1.0. + inference_cfg_rate (float, optional): Classifier-Free Guidance inference introduced in VoiceBox. Defaults to 0.5. + + Returns: + sample: generated mel-spectrogram + shape: (batch_size, n_feats, mel_timesteps) + """ + B, T = mu.size(0), mu.size(1) + z = torch.randn([B, self.in_channels, T], device=mu.device) * temperature + t_span = torch.linspace(0, 1, n_timesteps + 1, device=mu.device) + t_span = t_span + (-1) * (torch.cos(torch.pi / 2 * t_span) - 1 + t_span) + return self.solve_euler(z, x_lens, prompt, mu, style, t_span, inference_cfg_rate, random_voice) + def solve_euler(self, x, x_lens, prompt, mu, style, t_span, inference_cfg_rate=[0.5, 0.5], random_voice=False,): + """ + Fixed euler solver for ODEs. + Args: + x (torch.Tensor): random noise + t_span (torch.Tensor): n_timesteps interpolated + shape: (n_timesteps + 1,) + mu (torch.Tensor): output of encoder + shape: (batch_size, n_feats, mel_timesteps) + x_lens (torch.Tensor): length of each mel-spectrogram + shape: (batch_size,) + prompt (torch.Tensor): prompt + shape: (batch_size, n_feats, prompt_len) + style (torch.Tensor): style + shape: (batch_size, style_dim) + inference_cfg_rate (float, optional): Classifier-Free Guidance inference introduced in VoiceBox. Defaults to 0.5. + sway_sampling (bool, optional): Sway sampling. Defaults to False. + amo_sampling (bool, optional): AMO sampling. Defaults to False. + """ + t, _, dt = t_span[0], t_span[-1], t_span[1] - t_span[0] + + # apply prompt + prompt_len = prompt.size(-1) + prompt_x = torch.zeros_like(x) + prompt_x[..., :prompt_len] = prompt[..., :prompt_len] + x[..., :prompt_len] = 0 + for step in tqdm(range(1, len(t_span))): + if random_voice: + cfg_dphi_dt = self.estimator( + torch.cat([x, x], dim=0), + torch.cat([torch.zeros_like(prompt_x), torch.zeros_like(prompt_x)], dim=0), + torch.cat([x_lens, x_lens], dim=0), + torch.cat([t.unsqueeze(0), t.unsqueeze(0)], dim=0), + torch.cat([torch.zeros_like(style), torch.zeros_like(style)], dim=0), + torch.cat([mu, torch.zeros_like(mu)], dim=0), + ) + cond_txt, uncond = cfg_dphi_dt[0:1], cfg_dphi_dt[1:2] + dphi_dt = ((1.0 + inference_cfg_rate[0]) * cond_txt - inference_cfg_rate[0] * uncond) + elif all(i == 0 for i in inference_cfg_rate): + dphi_dt = self.estimator(x, prompt_x, x_lens, t.unsqueeze(0), style, mu) + elif inference_cfg_rate[0] == 0: + # Classifier-Free Guidance inference introduced in VoiceBox + cfg_dphi_dt = self.estimator( + torch.cat([x, x], dim=0), + torch.cat([prompt_x, torch.zeros_like(prompt_x)], dim=0), + torch.cat([x_lens, x_lens], dim=0), + torch.cat([t.unsqueeze(0), t.unsqueeze(0)], dim=0), + torch.cat([style, torch.zeros_like(style)], dim=0), + torch.cat([mu, mu], dim=0), + ) + cond_txt_spk, cond_txt = cfg_dphi_dt[0:1], cfg_dphi_dt[1:2] + dphi_dt = ((1.0 + inference_cfg_rate[1]) * cond_txt_spk - inference_cfg_rate[1] * cond_txt) + elif inference_cfg_rate[1] == 0: + cfg_dphi_dt = self.estimator( + torch.cat([x, x], dim=0), + torch.cat([prompt_x, torch.zeros_like(prompt_x)], dim=0), + torch.cat([x_lens, x_lens], dim=0), + torch.cat([t.unsqueeze(0), t.unsqueeze(0)], dim=0), + torch.cat([style, torch.zeros_like(style)], dim=0), + torch.cat([mu, torch.zeros_like(mu)], dim=0), + ) + cond_txt_spk, uncond = cfg_dphi_dt[0:1], cfg_dphi_dt[1:2] + dphi_dt = ((1.0 + inference_cfg_rate[0]) * cond_txt_spk - inference_cfg_rate[0] * uncond) + else: + # Multi-condition Classifier-Free Guidance inference introduced in MegaTTS3 + cfg_dphi_dt = self.estimator( + torch.cat([x, x, x], dim=0), + torch.cat([prompt_x, torch.zeros_like(prompt_x), torch.zeros_like(prompt_x)], dim=0), + torch.cat([x_lens, x_lens, x_lens], dim=0), + torch.cat([t.unsqueeze(0), t.unsqueeze(0), t.unsqueeze(0)], dim=0), + torch.cat([style, torch.zeros_like(style), torch.zeros_like(style)], dim=0), + torch.cat([mu, mu, torch.zeros_like(mu)], dim=0), + ) + cond_txt_spk, cond_txt, uncond = cfg_dphi_dt[0:1], cfg_dphi_dt[1:2], cfg_dphi_dt[2:3] + dphi_dt = (1.0 + inference_cfg_rate[0] + inference_cfg_rate[1]) * cond_txt_spk - \ + inference_cfg_rate[0] * uncond - inference_cfg_rate[1] * cond_txt + x = x + dt * dphi_dt + t = t + dt + if step < len(t_span) - 1: + dt = t_span[step + 1] - t + x[:, :, :prompt_len] = 0 + + return x + + def forward(self, x1, x_lens, prompt_lens, mu, style): + """Computes diffusion loss + + Args: + x1 (torch.Tensor): Target + shape: (batch_size, n_feats, mel_timesteps) + mask (torch.Tensor): target mask + shape: (batch_size, 1, mel_timesteps) + mu (torch.Tensor): output of encoder + shape: (batch_size, n_feats, mel_timesteps) + spks (torch.Tensor, optional): speaker embedding. Defaults to None. + shape: (batch_size, spk_emb_dim) + + Returns: + loss: conditional flow matching loss + y: conditional flow + shape: (batch_size, n_feats, mel_timesteps) + """ + b, _, t = x1.shape + + # random timestep + t = torch.rand([b, 1, 1], device=mu.device, dtype=x1.dtype) + # sample noise p(x_0) + z = torch.randn_like(x1) + + y = (1 - (1 - self.sigma_min) * t) * z + t * x1 + u = x1 - (1 - self.sigma_min) * z + prompt = torch.zeros_like(x1) + for bib in range(b): + prompt[bib, :, :prompt_lens[bib]] = x1[bib, :, :prompt_lens[bib]] + # range covered by prompt are set to 0 + y[bib, :, :prompt_lens[bib]] = 0 + + estimator_out = self.estimator(y, prompt, x_lens, t.squeeze(), style, mu) + loss = 0 + for bib in range(b): + loss += self.criterion(estimator_out[bib, :, prompt_lens[bib]:x_lens[bib]], u[bib, :, prompt_lens[bib]:x_lens[bib]]) + loss /= b + + return loss diff --git a/modules/v2/dit_model.py b/modules/v2/dit_model.py new file mode 100644 index 0000000000000000000000000000000000000000..4374ac86a4d4d0869788cdd16087115c4418ba5f --- /dev/null +++ b/modules/v2/dit_model.py @@ -0,0 +1,250 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +from dataclasses import dataclass +from typing import Optional, Union, Tuple, List + +import torch +import torch.nn as nn +from torch import Tensor +from torch.nn import functional as F +import time + +def find_multiple(n: int, k: int) -> int: + if n % k == 0: + return n + return n + k - (n % k) + +class AdaptiveLayerNorm(nn.Module): + r"""Adaptive Layer Normalization""" + + def __init__(self, d_model, norm) -> None: + super(AdaptiveLayerNorm, self).__init__() + self.linear = nn.Linear(d_model, 6 * d_model) + self.act = nn.SiLU() + self.norm = norm + self.d_model = d_model + self.eps = self.norm.eps + + def forward(self, x: Tensor, emb: Tensor) -> Tuple[Tensor]: + emb = self.linear(self.act(emb)) + shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = torch.chunk(emb, 6, dim=-1) + + x = self.norm(x) * (1 + scale_msa) + shift_msa + return x, gate_msa, shift_mlp, scale_mlp, gate_mlp + +class AdaptiveLayerNormFinal(nn.Module): + r"""Adaptive Layer Normalization""" + + def __init__(self, d_model, norm) -> None: + super(AdaptiveLayerNormFinal, self).__init__() + self.linear = nn.Linear(d_model, 2 * d_model) + self.act = nn.SiLU() + self.norm = norm + self.d_model = d_model + self.eps = self.norm.eps + + def forward(self, x: Tensor, emb: Tensor) -> Tuple[Tensor]: + emb = self.linear(self.act(emb)) + scale, shift = torch.chunk(emb, 2, dim=-1) + + x = self.norm(x) * (1 + scale) + shift + return x + +@dataclass +class ModelArgs: + block_size: int = 2048 + vocab_size: int = 32000 + n_layer: int = 32 + n_head: int = 32 + dim: int = 4096 + intermediate_size: int = None + n_local_heads: int = -1 + head_dim: int = 64 + rope_base: float = 10000 + norm_eps: float = 1e-5 + uvit_skip_connection: bool = False + time_as_token: bool = False + dropout_rate: float = 0.1 + attn_dropout_rate: float = 0.1 + + def __post_init__(self): + if self.n_local_heads == -1: + self.n_local_heads = self.n_head + if self.intermediate_size is None: + hidden_dim = 4 * self.dim + n_hidden = int(2 * hidden_dim / 3) + self.intermediate_size = find_multiple(n_hidden, 256) + # self.head_dim = self.dim // self.n_head + +class Transformer(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.config = config + + self.layers = nn.ModuleList(TransformerBlock(config) for _ in range(config.n_layer)) + self.norm = AdaptiveLayerNormFinal(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + + self.max_batch_size = -1 + self.max_seq_length = config.block_size + + self.uvit_skip_connection = self.config.uvit_skip_connection + if self.uvit_skip_connection: + self.layers_emit_skip = [i for i in range(self.config.n_layer) if i < self.config.n_layer // 2] + self.layers_receive_skip = [i for i in range(self.config.n_layer) if i > self.config.n_layer // 2] + else: + self.layers_emit_skip = [] + self.layers_receive_skip = [] + freqs_cis = precompute_freqs_cis(self.config.block_size, self.config.head_dim, + self.config.rope_base) + self.register_buffer("freqs_cis", freqs_cis) + + causal_mask = torch.tril( + torch.ones(self.max_seq_length, self.max_seq_length, dtype=torch.bool) + ) + self.register_buffer("causal_mask", causal_mask) + + def forward(self, + x: Tensor, + c: Tensor, + input_pos: Optional[Tensor] = None, + mask: Optional[Tensor] = None, + ) -> Tensor: + mask = mask[..., input_pos] + freqs_cis = self.freqs_cis[input_pos] + for i, layer in enumerate(self.layers): + x = layer(x, c, freqs_cis, mask) + x = self.norm(x, c) + return x + + +class TransformerBlock(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.attention = Attention(config) + self.feed_forward = FeedForward(config) + self.ffn_norm = RMSNorm(config.dim, eps=config.norm_eps) + self.attention_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + def forward(self, + x: Tensor, + c: Tensor, + freqs_cis: Tensor, + mask: Tensor, + ) -> Tensor: + normed_x, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attention_norm(x, emb=c) + # attention + attn_output = self.attention(normed_x, freqs_cis, mask) + x = x + gate_msa * attn_output + normed_x = self.ffn_norm(x) * (1 + scale_mlp) + shift_mlp + ff_output = self.feed_forward(normed_x) + x = x + gate_mlp * ff_output + return x + + +class Attention(nn.Module): + def __init__(self, config: ModelArgs, is_cross_attention: bool = False): + super().__init__() + assert config.dim % config.n_head == 0 + + total_head_dim = (config.n_head + 2 * config.n_local_heads) * config.head_dim + # key, query, value projections for all heads, but in a batch + if is_cross_attention: + self.wq = nn.Linear(config.dim, config.n_head * config.head_dim, bias=False) + self.wkv = nn.Linear(config.context_dim, 2 * config.n_local_heads * config.head_dim, bias=False) + else: + self.wqkv = nn.Linear(config.dim, total_head_dim, bias=False) + self.wo = nn.Linear(config.head_dim * config.n_head, config.dim, bias=False) + self.kv_cache = None + + self.n_head = config.n_head + self.head_dim = config.head_dim + self.n_local_heads = config.n_local_heads + self.dim = config.dim + self.attn_dropout_rate = config.attn_dropout_rate + + def forward(self, + x: Tensor, + freqs_cis: Tensor, + mask: Tensor, + context: Optional[Tensor] = None, + context_freqs_cis: Optional[Tensor] = None, + ) -> Tensor: + bsz, seqlen, _ = x.shape + + kv_size = self.n_local_heads * self.head_dim + q, k, v = self.wqkv(x).split([kv_size, kv_size, kv_size], dim=-1) + context_seqlen = seqlen + + q = q.view(bsz, seqlen, self.n_head, self.head_dim) + k = k.view(bsz, context_seqlen, self.n_local_heads, self.head_dim) + v = v.view(bsz, context_seqlen, self.n_local_heads, self.head_dim) + + q = apply_rotary_emb(q, freqs_cis) + k = apply_rotary_emb(k, freqs_cis) + + q, k, v = map(lambda x: x.transpose(1, 2), (q, k, v)) + + k = k.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + v = v.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + y = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0) + + y = y.transpose(1, 2).contiguous().view(bsz, seqlen, self.head_dim * self.n_head) + + y = self.wo(y) + return y + + +class FeedForward(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.w1 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w3 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w2 = nn.Linear(config.intermediate_size, config.dim, bias=False) + self.dropout = nn.Dropout(config.dropout_rate) + + def forward(self, x: Tensor) -> Tensor: + return self.w2(self.dropout(F.silu(self.w1(x)) * self.w3(x))) + + +class RMSNorm(nn.Module): + def __init__(self, dim: int, eps: float = 1e-5): + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.ones(dim)) + + def _norm(self, x): + return x * torch.rsqrt(torch.mean(x * x, dim=-1, keepdim=True) + self.eps) + + def forward(self, x: Tensor) -> Tensor: + output = self._norm(x.float()).type_as(x) + return output * self.weight + + +def precompute_freqs_cis( + seq_len: int, n_elem: int, base: int = 10000, + dtype: torch.dtype = torch.bfloat16 +) -> Tensor: + freqs = 1.0 / (base ** (torch.arange(0, n_elem, 2)[: (n_elem // 2)].float() / n_elem)) + t = torch.arange(seq_len, device=freqs.device) + freqs = torch.outer(t, freqs) + freqs_cis = torch.polar(torch.ones_like(freqs), freqs) + cache = torch.stack([freqs_cis.real, freqs_cis.imag], dim=-1) + return cache.to(dtype=dtype) + + +def apply_rotary_emb(x: Tensor, freqs_cis: Tensor) -> Tensor: + xshaped = x.float().reshape(*x.shape[:-1], -1, 2) + freqs_cis = freqs_cis.view(1, xshaped.size(1), 1, xshaped.size(3), 2) + x_out2 = torch.stack( + [ + xshaped[..., 0] * freqs_cis[..., 0] - xshaped[..., 1] * freqs_cis[..., 1], + xshaped[..., 1] * freqs_cis[..., 0] + xshaped[..., 0] * freqs_cis[..., 1], + ], + -1, + ) + + x_out2 = x_out2.flatten(3) + return x_out2.type_as(x) + diff --git a/modules/v2/dit_wrapper.py b/modules/v2/dit_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..3ac611ab9e33114df905a80e3f847e7ab198c72a --- /dev/null +++ b/modules/v2/dit_wrapper.py @@ -0,0 +1,152 @@ +import torch +from torch import nn +import math + +from modules.v2.dit_model import ModelArgs, Transformer +from modules.commons import sequence_mask + +from torch.nn.utils import weight_norm + +def modulate(x, shift, scale): + return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1) + + +################################################################################# +# Embedding Layers for Timesteps and Class Labels # +################################################################################# + +class TimestepEmbedder(nn.Module): + """ + Embeds scalar timesteps into vector representations. + """ + def __init__(self, hidden_size, frequency_embedding_size=256): + super().__init__() + self.mlp = nn.Sequential( + nn.Linear(frequency_embedding_size, hidden_size, bias=True), + nn.SiLU(), + nn.Linear(hidden_size, hidden_size, bias=True), + ) + self.frequency_embedding_size = frequency_embedding_size + + @staticmethod + def timestep_embedding(t, dim, max_period=10000, scale=1000): + """ + Create sinusoidal timestep embeddings. + :param t: a 1-D Tensor of N indices, one per batch element. + These may be fractional. + :param dim: the dimension of the output. + :param max_period: controls the minimum frequency of the embeddings. + :return: an (N, D) Tensor of positional embeddings. + """ + # https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py + half = dim // 2 + freqs = torch.exp( + -math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half + ).to(device=t.device) + args = scale * t[:, None].float() * freqs[None] + embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + if dim % 2: + embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1) + return embedding + + def forward(self, t): + t_freq = self.timestep_embedding(t, self.frequency_embedding_size) + t_emb = self.mlp(t_freq) + return t_emb + + +class DiT(torch.nn.Module): + def __init__( + self, + time_as_token, + style_as_token, + uvit_skip_connection, + block_size, + depth, + num_heads, + hidden_dim, + in_channels, + content_dim, + style_encoder_dim, + class_dropout_prob, + dropout_rate, + attn_dropout_rate, + ): + super(DiT, self).__init__() + self.time_as_token = time_as_token + self.style_as_token = style_as_token + self.uvit_skip_connection = uvit_skip_connection + model_args = ModelArgs( + block_size=block_size, + n_layer=depth, + n_head=num_heads, + dim=hidden_dim, + head_dim=hidden_dim // num_heads, + vocab_size=1, # we don't use this + uvit_skip_connection=self.uvit_skip_connection, + time_as_token=self.time_as_token, + dropout_rate=dropout_rate, + attn_dropout_rate=attn_dropout_rate, + ) + self.transformer = Transformer(model_args) + self.in_channels = in_channels + self.out_channels = in_channels + self.num_heads = num_heads + + self.x_embedder = weight_norm(nn.Linear(in_channels, hidden_dim, bias=True)) + + self.content_dim = content_dim # for continuous content + self.cond_projection = nn.Linear(content_dim, hidden_dim, bias=True) # continuous content + + self.t_embedder = TimestepEmbedder(hidden_dim) + + self.final_mlp = nn.Sequential( + nn.Linear(hidden_dim, hidden_dim), + nn.SiLU(), + nn.Linear(hidden_dim, in_channels), + ) + + self.class_dropout_prob = class_dropout_prob + + self.cond_x_merge_linear = nn.Linear(hidden_dim + in_channels + in_channels, hidden_dim) + self.style_in = nn.Linear(style_encoder_dim, hidden_dim) + + def forward(self, x, prompt_x, x_lens, t, style, cond): + class_dropout = False + content_dropout = False + if self.training and torch.rand(1) < self.class_dropout_prob: + class_dropout = True + if self.training and torch.rand(1) < 0.5: + content_dropout = True + cond_in_module = self.cond_projection + + B, _, T = x.size() + + t1 = self.t_embedder(t) # (N, D) + cond = cond_in_module(cond) + + x = x.transpose(1, 2) + prompt_x = prompt_x.transpose(1, 2) + + x_in = torch.cat([x, prompt_x, cond], dim=-1) + if class_dropout: + x_in[..., self.in_channels:self.in_channels*2] = 0 + if content_dropout: + x_in[..., self.in_channels*2:] = 0 + x_in = self.cond_x_merge_linear(x_in) # (N, T, D) + + style = self.style_in(style) + style = torch.zeros_like(style) if class_dropout else style + if self.style_as_token: + x_in = torch.cat([style.unsqueeze(1), x_in], dim=1) + if self.time_as_token: + x_in = torch.cat([t1.unsqueeze(1), x_in], dim=1) + x_mask = sequence_mask(x_lens + self.style_as_token + self.time_as_token, max_length=x_in.size(1)).to(x.device).unsqueeze(1) + input_pos = torch.arange(x_in.size(1)).to(x.device) + x_mask_expanded = x_mask[:, None, :].repeat(1, 1, x_in.size(1), 1) + x_res = self.transformer(x_in, t1.unsqueeze(1), input_pos, x_mask_expanded) + x_res = x_res[:, 1:] if self.time_as_token else x_res + x_res = x_res[:, 1:] if self.style_as_token else x_res + x = self.final_mlp(x_res) + x = x.transpose(1, 2) + return x diff --git a/modules/v2/length_regulator.py b/modules/v2/length_regulator.py new file mode 100644 index 0000000000000000000000000000000000000000..c8fba22ed62e367ef3abe88b5a04c5ee40658349 --- /dev/null +++ b/modules/v2/length_regulator.py @@ -0,0 +1,105 @@ +from typing import Tuple +import torch +import torch.nn as nn +from torch.nn import functional as F +from modules.commons import sequence_mask +import numpy as np + +# f0_bin = 256 +f0_max = 1100.0 +f0_min = 50.0 +f0_mel_min = 1127 * np.log(1 + f0_min / 700) +f0_mel_max = 1127 * np.log(1 + f0_max / 700) + +def f0_to_coarse(f0, f0_bin): + f0_mel = 1127 * (1 + f0 / 700).log() + a = (f0_bin - 2) / (f0_mel_max - f0_mel_min) + b = f0_mel_min * a - 1. + f0_mel = torch.where(f0_mel > 0, f0_mel * a - b, f0_mel) + # torch.clip_(f0_mel, min=1., max=float(f0_bin - 1)) + f0_coarse = torch.round(f0_mel).long() + f0_coarse = f0_coarse * (f0_coarse > 0) + f0_coarse = f0_coarse + ((f0_coarse < 1) * 1) + f0_coarse = f0_coarse * (f0_coarse < f0_bin) + f0_coarse = f0_coarse + ((f0_coarse >= f0_bin) * (f0_bin - 1)) + return f0_coarse + +class InterpolateRegulator(nn.Module): + def __init__( + self, + channels: int, + sampling_ratios: Tuple, + is_discrete: bool = False, + in_channels: int = None, # only applies to continuous input + codebook_size: int = 1024, # for discrete only + out_channels: int = None, + groups: int = 1, + f0_condition: bool = False, + n_f0_bins: int = 512, + ): + super().__init__() + self.sampling_ratios = sampling_ratios + out_channels = out_channels or channels + model = nn.ModuleList([]) + if len(sampling_ratios) > 0: + self.interpolate = True + for _ in sampling_ratios: + module = nn.Conv1d(channels, channels, 3, 1, 1) + norm = nn.GroupNorm(groups, channels) + act = nn.Mish() + model.extend([module, norm, act]) + else: + self.interpolate = False + model.append( + nn.Conv1d(channels, out_channels, 1, 1) if channels != out_channels else nn.Identity() + ) + self.model = nn.Sequential(*model) + self.embedding = nn.Embedding(codebook_size, channels) + self.is_discrete = is_discrete + + self.mask_token = nn.Parameter(torch.zeros(1, channels)) + + if f0_condition: + self.f0_embedding = nn.Embedding(n_f0_bins, channels) + self.f0_condition = f0_condition + self.n_f0_bins = n_f0_bins + self.f0_bins = torch.arange(2, 1024, 1024 // n_f0_bins) + self.f0_mask = nn.Parameter(torch.zeros(1, channels)) + else: + self.f0_condition = False + + if not is_discrete: + self.content_in_proj = nn.Linear(in_channels, channels) + + def forward(self, x, ylens=None, f0=None): + if self.is_discrete: + if len(x.size()) == 2: + x = self.embedding(x) + else: + x = self.embedding(x[:, 0]) + else: + x = self.content_in_proj(x) + # x in (B, T, D) + + if self.interpolate: + mask = sequence_mask(ylens).unsqueeze(-1) + x = F.interpolate(x.transpose(1, 2).contiguous(), size=ylens.max(), mode='nearest') + else: + x = x.transpose(1, 2).contiguous() + mask = None + # mask = mask[:, :x.size(2), :] + # ylens = ylens.clamp(max=x.size(2)).long() + if self.f0_condition: + if f0 is None: + x = x + self.f0_mask.unsqueeze(-1) + else: + # quantized_f0 = torch.bucketize(f0, self.f0_bins.to(f0.device)) # (N, T) + quantized_f0 = f0_to_coarse(f0, self.n_f0_bins) + quantized_f0 = quantized_f0.clamp(0, self.n_f0_bins - 1).long() + f0_emb = self.f0_embedding(quantized_f0) + f0_emb = F.interpolate(f0_emb.transpose(1, 2).contiguous(), size=ylens.max(), mode='nearest') + x = x + f0_emb + out = self.model(x).transpose(1, 2).contiguous() + out = out * mask if mask is not None else out + olens = ylens + return out, olens diff --git a/modules/v2/model.py b/modules/v2/model.py new file mode 100644 index 0000000000000000000000000000000000000000..c1453ef438c8ef3a22b8ab151db36aa75c93f0ff --- /dev/null +++ b/modules/v2/model.py @@ -0,0 +1,302 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +from dataclasses import dataclass +from typing import Optional + +import torch +import torch.nn as nn +from torch import Tensor +from torch.nn import functional as F + + +def find_multiple(n: int, k: int) -> int: + if n % k == 0: + return n + return n + k - (n % k) + +class AdaptiveLayerNorm(nn.Module): + r"""Adaptive Layer Normalization""" + + def __init__(self, d_model, norm) -> None: + super(AdaptiveLayerNorm, self).__init__() + self.project_layer = nn.Linear(d_model, 2 * d_model) + self.norm = norm + self.d_model = d_model + self.eps = self.norm.eps + + def forward(self, input: Tensor, embedding: Tensor = None) -> Tensor: + if embedding is None: + return self.norm(input) + weight, bias = torch.split( + self.project_layer(embedding), + split_size_or_sections=self.d_model, + dim=-1, + ) + return weight * self.norm(input) + bias + + +@dataclass +class ModelArgs: + block_size: int = 2048 + vocab_size: int = 32000 + n_layer: int = 32 + n_head: int = 32 + dim: int = 4096 + intermediate_size: int = None + n_local_heads: int = -1 + head_dim: int = 64 + rope_base: float = 10000 + norm_eps: float = 1e-5 + has_cross_attention: bool = False + context_dim: int = 0 + uvit_skip_connection: bool = False + time_as_token: bool = False + + def __post_init__(self): + if self.n_local_heads == -1: + self.n_local_heads = self.n_head + if self.intermediate_size is None: + hidden_dim = 4 * self.dim + n_hidden = int(2 * hidden_dim / 3) + self.intermediate_size = find_multiple(n_hidden, 256) + # self.head_dim = self.dim // self.n_head + +class Transformer(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.config = config + + self.layers = nn.ModuleList(TransformerBlock(config) for _ in range(config.n_layer)) + self.norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + + self.freqs_cis: Optional[Tensor] = None + self.mask_cache: Optional[Tensor] = None + self.max_batch_size = -1 + self.max_seq_length = -1 + + def setup_caches(self, max_batch_size, max_seq_length, use_kv_cache=False): + if self.max_seq_length >= max_seq_length and self.max_batch_size >= max_batch_size: + return + head_dim = self.config.dim // self.config.n_head + max_seq_length = find_multiple(max_seq_length, 8) + self.max_seq_length = max_seq_length + self.max_batch_size = max_batch_size + dtype = self.norm.project_layer.weight.dtype + device = self.norm.project_layer.weight.device + + self.freqs_cis = precompute_freqs_cis(self.config.block_size, self.config.head_dim, + self.config.rope_base, dtype).to(device) + self.causal_mask = torch.tril(torch.ones(self.max_seq_length, self.max_seq_length, dtype=torch.bool)).to(device) + self.use_kv_cache = use_kv_cache + self.uvit_skip_connection = self.config.uvit_skip_connection + if self.uvit_skip_connection: + self.layers_emit_skip = [i for i in range(self.config.n_layer) if i < self.config.n_layer // 2] + self.layers_receive_skip = [i for i in range(self.config.n_layer) if i > self.config.n_layer // 2] + else: + self.layers_emit_skip = [] + self.layers_receive_skip = [] + + def forward(self, + x: Tensor, + c: Tensor, + input_pos: Optional[Tensor] = None, + mask: Optional[Tensor] = None, + context: Optional[Tensor] = None, + context_input_pos: Optional[Tensor] = None, + cross_attention_mask: Optional[Tensor] = None, + ) -> Tensor: + assert self.freqs_cis is not None, "Caches must be initialized first" + if mask is None: # in case of non-causal model + if not self.training and self.use_kv_cache: + mask = self.causal_mask[None, None, input_pos] + else: + mask = self.causal_mask[None, None, input_pos] + mask = mask[..., input_pos] + freqs_cis = self.freqs_cis[input_pos] + if context is not None: + context_freqs_cis = self.freqs_cis[context_input_pos] + else: + context_freqs_cis = None + skip_in_x_list = [] + for i, layer in enumerate(self.layers): + if self.uvit_skip_connection and i in self.layers_receive_skip: + skip_in_x = skip_in_x_list.pop(-1) + else: + skip_in_x = None + x = layer(x, c, input_pos, freqs_cis, mask, context, context_freqs_cis, cross_attention_mask, skip_in_x) + if self.uvit_skip_connection and i in self.layers_emit_skip: + skip_in_x_list.append(x) + x = self.norm(x, c) + return x + + @classmethod + def from_name(cls, name: str): + return cls(ModelArgs.from_name(name)) + + +class TransformerBlock(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.attention = Attention(config) + self.feed_forward = FeedForward(config) + self.ffn_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + self.attention_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + + if config.has_cross_attention: + self.has_cross_attention = True + self.cross_attention = Attention(config, is_cross_attention=True) + self.cross_attention_norm = AdaptiveLayerNorm(config.dim, RMSNorm(config.dim, eps=config.norm_eps)) + else: + self.has_cross_attention = False + + if config.uvit_skip_connection: + self.skip_in_linear = nn.Linear(config.dim * 2, config.dim) + self.uvit_skip_connection = True + else: + self.uvit_skip_connection = False + + self.time_as_token = config.time_as_token + + def forward(self, + x: Tensor, + c: Tensor, + input_pos: Tensor, + freqs_cis: Tensor, + mask: Tensor, + context: Optional[Tensor] = None, + context_freqs_cis: Optional[Tensor] = None, + cross_attention_mask: Optional[Tensor] = None, + skip_in_x: Optional[Tensor] = None, + ) -> Tensor: + c = None if self.time_as_token else c + if self.uvit_skip_connection and skip_in_x is not None: + x = self.skip_in_linear(torch.cat([x, skip_in_x], dim=-1)) + h = x + self.attention(self.attention_norm(x, c), freqs_cis, mask, input_pos) + if self.has_cross_attention: + h = h + self.cross_attention(self.cross_attention_norm(h, c), freqs_cis, cross_attention_mask, input_pos, context, context_freqs_cis) + out = h + self.feed_forward(self.ffn_norm(h, c)) + return out + + +class Attention(nn.Module): + def __init__(self, config: ModelArgs, is_cross_attention: bool = False): + super().__init__() + assert config.dim % config.n_head == 0 + + total_head_dim = (config.n_head + 2 * config.n_local_heads) * config.head_dim + # key, query, value projections for all heads, but in a batch + if is_cross_attention: + self.wq = nn.Linear(config.dim, config.n_head * config.head_dim, bias=False) + self.wkv = nn.Linear(config.context_dim, 2 * config.n_local_heads * config.head_dim, bias=False) + else: + self.wqkv = nn.Linear(config.dim, total_head_dim, bias=False) + self.wo = nn.Linear(config.head_dim * config.n_head, config.dim, bias=False) + self.kv_cache = None + + self.n_head = config.n_head + self.head_dim = config.head_dim + self.n_local_heads = config.n_local_heads + self.dim = config.dim + # self._register_load_state_dict_pre_hook(self.load_hook) + + # def load_hook(self, state_dict, prefix, *args): + # if prefix + "wq.weight" in state_dict: + # wq = state_dict.pop(prefix + "wq.weight") + # wk = state_dict.pop(prefix + "wk.weight") + # wv = state_dict.pop(prefix + "wv.weight") + # state_dict[prefix + "wqkv.weight"] = torch.cat([wq, wk, wv]) + + def forward(self, + x: Tensor, + freqs_cis: Tensor, + mask: Tensor, + input_pos: Optional[Tensor] = None, + context: Optional[Tensor] = None, + context_freqs_cis: Optional[Tensor] = None, + ) -> Tensor: + bsz, seqlen, _ = x.shape + + kv_size = self.n_local_heads * self.head_dim + if context is None: + q, k, v = self.wqkv(x).split([kv_size, kv_size, kv_size], dim=-1) + context_seqlen = seqlen + else: + q = self.wq(x) + k, v = self.wkv(context).split([kv_size, kv_size], dim=-1) + context_seqlen = context.shape[1] + + q = q.view(bsz, seqlen, self.n_head, self.head_dim) + k = k.view(bsz, context_seqlen, self.n_local_heads, self.head_dim) + v = v.view(bsz, context_seqlen, self.n_local_heads, self.head_dim) + + q = apply_rotary_emb(q, freqs_cis) + k = apply_rotary_emb(k, context_freqs_cis if context_freqs_cis is not None else freqs_cis) + + q, k, v = map(lambda x: x.transpose(1, 2), (q, k, v)) + + if self.kv_cache is not None: + k, v = self.kv_cache.update(input_pos, k, v) + + k = k.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + v = v.repeat_interleave(self.n_head // self.n_local_heads, dim=1) + y = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0) + + y = y.transpose(1, 2).contiguous().view(bsz, seqlen, self.head_dim * self.n_head) + + y = self.wo(y) + return y + + +class FeedForward(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.w1 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w3 = nn.Linear(config.dim, config.intermediate_size, bias=False) + self.w2 = nn.Linear(config.intermediate_size, config.dim, bias=False) + + def forward(self, x: Tensor) -> Tensor: + return self.w2(F.silu(self.w1(x)) * self.w3(x)) + + +class RMSNorm(nn.Module): + def __init__(self, dim: int, eps: float = 1e-5): + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.ones(dim)) + + def _norm(self, x): + return x * torch.rsqrt(torch.mean(x * x, dim=-1, keepdim=True) + self.eps) + + def forward(self, x: Tensor) -> Tensor: + output = self._norm(x.float()).type_as(x) + return output * self.weight + + +def precompute_freqs_cis( + seq_len: int, n_elem: int, base: int = 10000, + dtype: torch.dtype = torch.bfloat16 +) -> Tensor: + freqs = 1.0 / (base ** (torch.arange(0, n_elem, 2)[: (n_elem // 2)].float() / n_elem)) + t = torch.arange(seq_len, device=freqs.device) + freqs = torch.outer(t, freqs) + freqs_cis = torch.polar(torch.ones_like(freqs), freqs) + cache = torch.stack([freqs_cis.real, freqs_cis.imag], dim=-1) + return cache.to(dtype=dtype) + + +def apply_rotary_emb(x: Tensor, freqs_cis: Tensor) -> Tensor: + xshaped = x.float().reshape(*x.shape[:-1], -1, 2) + freqs_cis = freqs_cis.view(1, xshaped.size(1), 1, xshaped.size(3), 2) + x_out2 = torch.stack( + [ + xshaped[..., 0] * freqs_cis[..., 0] - xshaped[..., 1] * freqs_cis[..., 1], + xshaped[..., 1] * freqs_cis[..., 0] + xshaped[..., 0] * freqs_cis[..., 1], + ], + -1, + ) + + x_out2 = x_out2.flatten(3) + return x_out2.type_as(x) diff --git a/modules/v2/vc_wrapper.py b/modules/v2/vc_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..26e2dd0c5ecbbb0e8c19cea2c8472450148eb97e --- /dev/null +++ b/modules/v2/vc_wrapper.py @@ -0,0 +1,664 @@ +import torch +import librosa +import torchaudio +import numpy as np +from pydub import AudioSegment +from hf_utils import load_custom_model_from_hf + +DEFAULT_REPO_ID = "Plachta/Seed-VC" +DEFAULT_CFM_CHECKPOINT = "v2/cfm_small.pth" +DEFAULT_AR_CHECKPOINT = "v2/ar_base.pth" + +DEFAULT_CE_REPO_ID = "Plachta/ASTRAL-quantization" +DEFAULT_CE_NARROW_CHECKPOINT = "bsq32/bsq32_light.pth" +DEFAULT_CE_WIDE_CHECKPOINT = "bsq2048/bsq2048_light.pth" + +DEFAULT_SE_REPO_ID = "funasr/campplus" +DEFAULT_SE_CHECKPOINT = "campplus_cn_common.bin" + +class VoiceConversionWrapper(torch.nn.Module): + def __init__( + self, + sr: int, + hop_size: int, + mel_fn: callable, + cfm: torch.nn.Module, + cfm_length_regulator: torch.nn.Module, + content_extractor_narrow: torch.nn.Module, + content_extractor_wide: torch.nn.Module, + ar_length_regulator: torch.nn.Module, + ar: torch.nn.Module, + style_encoder: torch.nn.Module, + vocoder: torch.nn.Module, + ): + super(VoiceConversionWrapper, self).__init__() + self.sr = sr + self.hop_size = hop_size + self.mel_fn = mel_fn + self.cfm = cfm + self.cfm_length_regulator = cfm_length_regulator + self.content_extractor_narrow = content_extractor_narrow + self.content_extractor_wide = content_extractor_wide + self.vocoder = vocoder + self.ar_length_regulator = ar_length_regulator + self.ar = ar + self.style_encoder = style_encoder + # Set streaming parameters + self.overlap_frame_len = 16 + self.bitrate = "320k" + self.compiled_decode_fn = None + self.dit_compiled = False + self.dit_max_context_len = 30 # in seconds + self.ar_max_content_len = 1500 # in num of narrow tokens + self.compile_len = 87 * self.dit_max_context_len + + def forward_cfm(self, content_indices_wide, content_lens, mels, mel_lens, style_vectors): + device = content_indices_wide.device + B = content_indices_wide.size(0) + cond, _ = self.cfm_length_regulator(content_indices_wide, ylens=mel_lens) + + # randomly set a length as prompt + prompt_len_max = mel_lens - 1 + prompt_len = (torch.rand([B], device=device) * prompt_len_max).floor().to(dtype=torch.long) + prompt_len[torch.rand([B], device=device) < 0.1] = 0 + + loss = self.cfm(mels, mel_lens, prompt_len, cond, style_vectors) + return loss + + def forward_ar(self, content_indices_narrow, content_indices_wide, content_lens): + device = content_indices_narrow.device + duration_reduced_narrow_tokens = [] + duration_reduced_narrow_lens = [] + for bib in range(content_indices_narrow.size(0)): + reduced, reduced_len = self.duration_reduction_func(content_indices_narrow[bib]) + duration_reduced_narrow_tokens.append(reduced) + duration_reduced_narrow_lens.append(reduced_len) + duration_reduced_narrow_tokens = torch.nn.utils.rnn.pad_sequence(duration_reduced_narrow_tokens, + batch_first=True, padding_value=0).to(device) + duration_reduced_narrow_lens = torch.LongTensor(duration_reduced_narrow_lens).to(device) + + # interpolate speech token to match acoustic feature length + cond, _ = self.ar_length_regulator(duration_reduced_narrow_tokens) + loss = self.ar(cond, duration_reduced_narrow_lens, content_indices_wide, content_lens) + return loss + + def forward(self, waves_16k, mels, wave_lens_16k, mel_lens, forward_ar=False, forward_cfm=True): + """ + Forward pass for the model. + """ + # extract wide content features as both AR and CFM models use them + with torch.no_grad(): + _, content_indices_wide, content_lens = self.content_extractor_wide(waves_16k, wave_lens_16k) + if forward_ar: + # extract narrow content features for AR model + _, content_indices_narrow, _ = self.content_extractor_narrow(waves_16k, wave_lens_16k, ssl_model=self.content_extractor_wide.ssl_model) + loss_ar = self.forward_ar(content_indices_narrow.clone(), content_indices_wide.clone(), content_lens) + else: + loss_ar = torch.tensor(0.0, device=waves_16k.device, dtype=waves_16k.dtype) + if forward_cfm: + style_vectors = self.compute_style(waves_16k, wave_lens_16k) + loss_cfm = self.forward_cfm(content_indices_wide, content_lens, mels, mel_lens, style_vectors) + else: + loss_cfm = torch.tensor(0.0, device=waves_16k.device, dtype=waves_16k.dtype) + return loss_ar, loss_cfm + + def compile_ar(self): + """ + Compile the AR model for inference. + """ + self.compiled_decode_fn = torch.compile( + self.ar.model.forward_generate, + fullgraph=True, + backend="inductor" if torch.cuda.is_available() else "aot_eager", + mode="reduce-overhead" if torch.cuda.is_available() else None, + ) + + def compile_cfm(self): + self.cfm.estimator.transformer = torch.compile( + self.cfm.estimator.transformer, + fullgraph=True, + backend="inductor" if torch.cuda.is_available() else "aot_eager", + mode="reduce-overhead" if torch.cuda.is_available() else None, + ) + self.dit_compiled = True + + @staticmethod + def strip_prefix(state_dict: dict, prefix: str = "module.") -> dict: + """ + Strip the prefix from the state_dict keys. + """ + new_state_dict = {} + for k, v in state_dict.items(): + if k.startswith(prefix): + new_key = k[len(prefix):] + else: + new_key = k + new_state_dict[new_key] = v + return new_state_dict + + @staticmethod + def duration_reduction_func(token_seq, n_gram=1): + """ + Args: + token_seq: (T,) + Returns: + reduced_token_seq: (T') + reduced_token_seq_len: T' + """ + n_gram_seq = token_seq.unfold(0, n_gram, 1) + mask = torch.all(n_gram_seq[1:] != n_gram_seq[:-1], dim=1) + reduced_token_seq = torch.cat( + (n_gram_seq[0, :n_gram], n_gram_seq[1:, -1][mask]) + ) + return reduced_token_seq, len(reduced_token_seq) + + @staticmethod + def crossfade(chunk1, chunk2, overlap): + """Apply crossfade between two audio chunks.""" + fade_out = np.cos(np.linspace(0, np.pi / 2, overlap)) ** 2 + fade_in = np.cos(np.linspace(np.pi / 2, 0, overlap)) ** 2 + if len(chunk2) < overlap: + chunk2[:overlap] = chunk2[:overlap] * fade_in[:len(chunk2)] + (chunk1[-overlap:] * fade_out)[:len(chunk2)] + else: + chunk2[:overlap] = chunk2[:overlap] * fade_in + chunk1[-overlap:] * fade_out + return chunk2 + + def _stream_wave_chunks(self, vc_wave, processed_frames, vc_mel, overlap_wave_len, + generated_wave_chunks, previous_chunk, is_last_chunk, stream_output): + """ + Helper method to handle streaming wave chunks. + + Args: + vc_wave: The current wave chunk + processed_frames: Number of frames processed so far + vc_mel: The mel spectrogram + overlap_wave_len: Length of overlap between chunks + generated_wave_chunks: List of generated wave chunks + previous_chunk: Previous wave chunk for crossfading + is_last_chunk: Whether this is the last chunk + stream_output: Whether to stream the output + + Returns: + Tuple of (processed_frames, previous_chunk, should_break, mp3_bytes, full_audio) + where should_break indicates if processing should stop + mp3_bytes is the MP3 bytes if streaming, None otherwise + full_audio is the full audio if this is the last chunk, None otherwise + """ + mp3_bytes = None + full_audio = None + + if processed_frames == 0: + if is_last_chunk: + output_wave = vc_wave[0].cpu().numpy() + generated_wave_chunks.append(output_wave) + + if stream_output: + output_wave_int16 = (output_wave * 32768.0).astype(np.int16) + mp3_bytes = AudioSegment( + output_wave_int16.tobytes(), frame_rate=self.sr, + sample_width=output_wave_int16.dtype.itemsize, channels=1 + ).export(format="mp3", bitrate=self.bitrate).read() + full_audio = (self.sr, np.concatenate(generated_wave_chunks)) + else: + return processed_frames, previous_chunk, True, None, np.concatenate(generated_wave_chunks) + + return processed_frames, previous_chunk, True, mp3_bytes, full_audio + + output_wave = vc_wave[0, :-overlap_wave_len].cpu().numpy() + generated_wave_chunks.append(output_wave) + previous_chunk = vc_wave[0, -overlap_wave_len:] + processed_frames += vc_mel.size(2) - self.overlap_frame_len + + if stream_output: + output_wave_int16 = (output_wave * 32768.0).astype(np.int16) + mp3_bytes = AudioSegment( + output_wave_int16.tobytes(), frame_rate=self.sr, + sample_width=output_wave_int16.dtype.itemsize, channels=1 + ).export(format="mp3", bitrate=self.bitrate).read() + + elif is_last_chunk: + output_wave = self.crossfade(previous_chunk.cpu().numpy(), vc_wave[0].cpu().numpy(), overlap_wave_len) + generated_wave_chunks.append(output_wave) + processed_frames += vc_mel.size(2) - self.overlap_frame_len + + if stream_output: + output_wave_int16 = (output_wave * 32768.0).astype(np.int16) + mp3_bytes = AudioSegment( + output_wave_int16.tobytes(), frame_rate=self.sr, + sample_width=output_wave_int16.dtype.itemsize, channels=1 + ).export(format="mp3", bitrate=self.bitrate).read() + full_audio = (self.sr, np.concatenate(generated_wave_chunks)) + else: + return processed_frames, previous_chunk, True, None, np.concatenate(generated_wave_chunks) + + return processed_frames, previous_chunk, True, mp3_bytes, full_audio + + else: + output_wave = self.crossfade(previous_chunk.cpu().numpy(), vc_wave[0, :-overlap_wave_len].cpu().numpy(), overlap_wave_len) + generated_wave_chunks.append(output_wave) + previous_chunk = vc_wave[0, -overlap_wave_len:] + processed_frames += vc_mel.size(2) - self.overlap_frame_len + + if stream_output: + output_wave_int16 = (output_wave * 32768.0).astype(np.int16) + mp3_bytes = AudioSegment( + output_wave_int16.tobytes(), frame_rate=self.sr, + sample_width=output_wave_int16.dtype.itemsize, channels=1 + ).export(format="mp3", bitrate=self.bitrate).read() + + return processed_frames, previous_chunk, False, mp3_bytes, full_audio + + def load_checkpoints( + self, + cfm_checkpoint_path = None, + ar_checkpoint_path = None, + ): + if cfm_checkpoint_path is None: + cfm_checkpoint_path = load_custom_model_from_hf( + repo_id=DEFAULT_REPO_ID, + model_filename=DEFAULT_CFM_CHECKPOINT, + ) + else: + print(f"Loading CFM checkpoint from {cfm_checkpoint_path}...") + if ar_checkpoint_path is None: + ar_checkpoint_path = load_custom_model_from_hf( + repo_id=DEFAULT_REPO_ID, + model_filename=DEFAULT_AR_CHECKPOINT, + ) + else: + print(f"Loading AR checkpoint from {ar_checkpoint_path}...") + # cfm + cfm_checkpoint = torch.load(cfm_checkpoint_path, map_location="cpu") + cfm_length_regulator_state_dict = self.strip_prefix(cfm_checkpoint["net"]['length_regulator'], "module.") + cfm_state_dict = self.strip_prefix(cfm_checkpoint["net"]['cfm'], "module.") + missing_keys, unexpected_keys = self.cfm.load_state_dict(cfm_state_dict, strict=False) + missing_keys, unexpected_keys = self.cfm_length_regulator.load_state_dict(cfm_length_regulator_state_dict, strict=False) + + # ar + ar_checkpoint = torch.load(ar_checkpoint_path, map_location="cpu") + ar_length_regulator_state_dict = self.strip_prefix(ar_checkpoint["net"]['length_regulator'], "module.") + ar_state_dict = self.strip_prefix(ar_checkpoint["net"]['ar'], "module.") + missing_keys, unexpected_keys = self.ar.load_state_dict(ar_state_dict, strict=False) + missing_keys, unexpected_keys = self.ar_length_regulator.load_state_dict(ar_length_regulator_state_dict, strict=False) + + # content extractor + content_extractor_narrow_checkpoint_path = load_custom_model_from_hf( + repo_id=DEFAULT_CE_REPO_ID, + model_filename=DEFAULT_CE_NARROW_CHECKPOINT, + ) + content_extractor_narrow_checkpoint = torch.load(content_extractor_narrow_checkpoint_path, map_location="cpu") + self.content_extractor_narrow.load_state_dict( + content_extractor_narrow_checkpoint, strict=False + ) + + content_extractor_wide_checkpoint_path = load_custom_model_from_hf( + repo_id=DEFAULT_CE_REPO_ID, + model_filename=DEFAULT_CE_WIDE_CHECKPOINT, + ) + content_extractor_wide_checkpoint = torch.load(content_extractor_wide_checkpoint_path, map_location="cpu") + self.content_extractor_wide.load_state_dict( + content_extractor_wide_checkpoint, strict=False + ) + + # style encoder + style_encoder_checkpoint_path = load_custom_model_from_hf(DEFAULT_SE_REPO_ID, DEFAULT_SE_CHECKPOINT, config_filename=None) + style_encoder_checkpoint = torch.load(style_encoder_checkpoint_path, map_location="cpu") + self.style_encoder.load_state_dict(style_encoder_checkpoint, strict=False) + + def setup_ar_caches(self, max_batch_size=1, max_seq_len=4096, dtype=torch.float32, device=torch.device("cpu")): + self.ar.setup_caches(max_batch_size=max_batch_size, max_seq_len=max_seq_len, dtype=dtype, device=device) + + @torch.no_grad() + def compute_style(self, waves_16k: torch.Tensor, wave_lens_16k: torch.Tensor = None): + if wave_lens_16k is None: + wave_lens_16k = torch.tensor([waves_16k.size(-1)], dtype=torch.int32).to(waves_16k.device) + feat_list = [] + for bib in range(waves_16k.size(0)): + feat = torchaudio.compliance.kaldi.fbank(waves_16k[bib:bib + 1, :wave_lens_16k[bib]], + num_mel_bins=80, + dither=0, + sample_frequency=16000) + feat = feat - feat.mean(dim=0, keepdim=True) + feat_list.append(feat) + max_feat_len = max([feat.size(0) for feat in feat_list]) + feat_lens = torch.tensor([feat.size(0) for feat in feat_list], dtype=torch.int32).to(waves_16k.device) // 2 + feat_list = [ + torch.nn.functional.pad(feat, (0, 0, 0, max_feat_len - feat.size(0)), value=float(feat.min().item())) + for feat in feat_list + ] + feat = torch.stack(feat_list, dim=0) + style = self.style_encoder(feat, feat_lens) + return style + + @torch.no_grad() + @torch.inference_mode() + def convert_timbre( + self, + source_audio_path: str, + target_audio_path: str, + diffusion_steps: int = 30, + length_adjust: float = 1.0, + inference_cfg_rate: float = 0.5, + use_sway_sampling: bool = False, + use_amo_sampling: bool = False, + device: torch.device = torch.device("cpu"), + dtype: torch.dtype = torch.float32, + ): + source_wave = librosa.load(source_audio_path, sr=self.sr)[0] + target_wave = librosa.load(target_audio_path, sr=self.sr)[0] + source_wave_tensor = torch.tensor(source_wave).unsqueeze(0).to(device) + target_wave_tensor = torch.tensor(target_wave).unsqueeze(0).to(device) + + # get 16khz audio + source_wave_16k = librosa.resample(source_wave, orig_sr=self.sr, target_sr=16000) + target_wave_16k = librosa.resample(target_wave, orig_sr=self.sr, target_sr=16000) + source_wave_16k_tensor = torch.tensor(source_wave_16k).unsqueeze(0).to(device) + target_wave_16k_tensor = torch.tensor(target_wave_16k).unsqueeze(0).to(device) + + # compute mel spectrogram + source_mel = self.mel_fn(source_wave_tensor) + target_mel = self.mel_fn(target_wave_tensor) + source_mel_len = source_mel.size(2) + target_mel_len = target_mel.size(2) + + with torch.autocast(device_type=device.type, dtype=dtype): + # compute content features + _, source_content_indices, _ = self.content_extractor_wide(source_wave_16k_tensor, [source_wave_16k.size]) + _, target_content_indices, _ = self.content_extractor_wide(target_wave_16k_tensor, [target_wave_16k.size]) + + # compute style features + target_style = self.compute_style(target_wave_16k_tensor) + + # Length regulation + cond, _ = self.cfm_length_regulator(source_content_indices, ylens=torch.LongTensor([source_mel_len]).to(device)) + prompt_condition, _, = self.cfm_length_regulator(target_content_indices, ylens=torch.LongTensor([target_mel_len]).to(device)) + + cat_condition = torch.cat([prompt_condition, cond], dim=1) + # generate mel spectrogram + vc_mel = self.cfm.inference( + cat_condition, + torch.LongTensor([cat_condition.size(1)]).to(device), + target_mel, target_style, diffusion_steps, + inference_cfg_rate=inference_cfg_rate, + sway_sampling=use_sway_sampling, + amo_sampling=use_amo_sampling, + ) + vc_mel = vc_mel[:, :, target_mel_len:] + vc_wave = self.vocoder(vc_mel.float()).squeeze()[None] + return vc_wave.cpu().numpy() + + @torch.no_grad() + @torch.inference_mode() + def convert_voice( + self, + source_audio_path: str, + target_audio_path: str, + diffusion_steps: int = 30, + length_adjust: float = 1.0, + inference_cfg_rate: float = 0.5, + top_p: float = 0.7, + temperature: float = 0.7, + repetition_penalty: float = 1.5, + use_sway_sampling: bool = False, + use_amo_sampling: bool = False, + device: torch.device = torch.device("cpu"), + dtype: torch.dtype = torch.float32, + ): + source_wave = librosa.load(source_audio_path, sr=self.sr)[0] + target_wave = librosa.load(target_audio_path, sr=self.sr)[0] + source_wave_tensor = torch.tensor(source_wave).unsqueeze(0).to(device) + target_wave_tensor = torch.tensor(target_wave).unsqueeze(0).to(device) + + # get 16khz audio + source_wave_16k = librosa.resample(source_wave, orig_sr=self.sr, target_sr=16000) + target_wave_16k = librosa.resample(target_wave, orig_sr=self.sr, target_sr=16000) + source_wave_16k_tensor = torch.tensor(source_wave_16k).unsqueeze(0).to(device) + target_wave_16k_tensor = torch.tensor(target_wave_16k).unsqueeze(0).to(device) + + # compute mel spectrogram + source_mel = self.mel_fn(source_wave_tensor) + target_mel = self.mel_fn(target_wave_tensor) + source_mel_len = source_mel.size(2) + target_mel_len = target_mel.size(2) + + with torch.autocast(device_type=device.type, dtype=dtype): + # compute content features + _, source_content_indices, _ = self.content_extractor_wide(source_wave_16k_tensor, [source_wave_16k.size]) + _, target_content_indices, _ = self.content_extractor_wide(target_wave_16k_tensor, [target_wave_16k.size]) + + _, source_narrow_indices, _ = self.content_extractor_narrow(source_wave_16k_tensor, + [source_wave_16k.size], ssl_model=self.content_extractor_wide.ssl_model) + _, target_narrow_indices, _ = self.content_extractor_narrow(target_wave_16k_tensor, + [target_wave_16k.size], ssl_model=self.content_extractor_wide.ssl_model) + + src_narrow_reduced, src_narrow_len = self.duration_reduction_func(source_narrow_indices[0], 1) + tgt_narrow_reduced, tgt_narrow_len = self.duration_reduction_func(target_narrow_indices[0], 1) + + ar_cond = self.ar_length_regulator(torch.cat([tgt_narrow_reduced, src_narrow_reduced], dim=0)[None])[0] + + ar_out = self.ar.generate(ar_cond, target_content_indices, top_p=top_p, temperature=temperature, repetition_penalty=repetition_penalty) + ar_out_mel_len = torch.LongTensor([int(source_mel_len / source_content_indices.size(-1) * ar_out.size(-1) * length_adjust)]).to(device) + # compute style features + target_style = self.compute_style(target_wave_16k_tensor) + + # Length regulation + cond, _ = self.cfm_length_regulator(ar_out, ylens=torch.LongTensor([ar_out_mel_len]).to(device)) + prompt_condition, _, = self.cfm_length_regulator(target_content_indices, ylens=torch.LongTensor([target_mel_len]).to(device)) + + cat_condition = torch.cat([prompt_condition, cond], dim=1) + # generate mel spectrogram + vc_mel = self.cfm.inference( + cat_condition, + torch.LongTensor([cat_condition.size(1)]).to(device), + target_mel, target_style, diffusion_steps, + inference_cfg_rate=inference_cfg_rate, + sway_sampling=use_sway_sampling, + amo_sampling=use_amo_sampling, + ) + vc_mel = vc_mel[:, :, target_mel_len:] + vc_wave = self.vocoder(vc_mel.float()).squeeze()[None] + return vc_wave.cpu().numpy() + + def _process_content_features(self, audio_16k_tensor, is_narrow=False): + """Process audio through Whisper model to extract features.""" + content_extractor_fn = self.content_extractor_narrow if is_narrow else self.content_extractor_wide + if audio_16k_tensor.size(-1) <= 16000 * 30: + # Compute content features + _, content_indices, _ = content_extractor_fn(audio_16k_tensor, [audio_16k_tensor.size(-1)], ssl_model=self.content_extractor_wide.ssl_model) + else: + # Process long audio in chunks + overlapping_time = 5 # 5 seconds + features_list = [] + buffer = None + traversed_time = 0 + while traversed_time < audio_16k_tensor.size(-1): + if buffer is None: # first chunk + chunk = audio_16k_tensor[:, traversed_time:traversed_time + 16000 * 30] + else: + chunk = torch.cat([ + buffer, + audio_16k_tensor[:, traversed_time:traversed_time + 16000 * (30 - overlapping_time)] + ], dim=-1) + _, chunk_content_indices, _ = content_extractor_fn(chunk, [chunk.size(-1)], ssl_model=self.content_extractor_wide.ssl_model) + if traversed_time == 0: + features_list.append(chunk_content_indices) + else: + features_list.append(chunk_content_indices[:, 50 * overlapping_time:]) + buffer = chunk[:, -16000 * overlapping_time:] + traversed_time += 30 * 16000 if traversed_time == 0 else chunk.size(-1) - 16000 * overlapping_time + content_indices = torch.cat(features_list, dim=1) + + return content_indices + + @torch.no_grad() + @torch.inference_mode() + def convert_voice_with_streaming( + self, + source_audio_path: str, + target_audio_path: str, + diffusion_steps: int = 30, + length_adjust: float = 1.0, + intelligebility_cfg_rate: float = 0.7, + similarity_cfg_rate: float = 0.7, + top_p: float = 0.7, + temperature: float = 0.7, + repetition_penalty: float = 1.5, + convert_style: bool = False, + anonymization_only: bool = False, + device: torch.device = torch.device("cuda"), + dtype: torch.dtype = torch.float16, + stream_output: bool = True, + ): + """ + Convert voice with streaming support for long audio files. + + Args: + source_audio_path: Path to source audio file + target_audio_path: Path to target audio file + diffusion_steps: Number of diffusion steps (default: 30) + length_adjust: Length adjustment factor (default: 1.0) + intelligebility_cfg_rate: CFG rate for intelligibility (default: 0.7) + similarity_cfg_rate: CFG rate for similarity (default: 0.7) + top_p: Top-p sampling parameter (default: 0.7) + temperature: Temperature for sampling (default: 0.7) + repetition_penalty: Repetition penalty (default: 1.5) + device: Device to use (default: cpu) + dtype: Data type to use (default: float32) + stream_output: Whether to stream the output (default: True) + + Returns: + If stream_output is True, yields (mp3_bytes, full_audio) tuples + If stream_output is False, returns the full audio as a numpy array + """ + # Load audio + source_wave = librosa.load(source_audio_path, sr=self.sr)[0] + target_wave = librosa.load(target_audio_path, sr=self.sr)[0] + + # Limit target audio to 25 seconds + target_wave = target_wave[:self.sr * (self.dit_max_context_len - 5)] + + source_wave_tensor = torch.tensor(source_wave).unsqueeze(0).float().to(device) + target_wave_tensor = torch.tensor(target_wave).unsqueeze(0).float().to(device) + + # Resample to 16kHz for feature extraction + source_wave_16k = librosa.resample(source_wave, orig_sr=self.sr, target_sr=16000) + target_wave_16k = librosa.resample(target_wave, orig_sr=self.sr, target_sr=16000) + source_wave_16k_tensor = torch.tensor(source_wave_16k).unsqueeze(0).to(device) + target_wave_16k_tensor = torch.tensor(target_wave_16k).unsqueeze(0).to(device) + + # Compute mel spectrograms + source_mel = self.mel_fn(source_wave_tensor) + target_mel = self.mel_fn(target_wave_tensor) + source_mel_len = source_mel.size(2) + target_mel_len = target_mel.size(2) + + # Set up chunk processing parameters + max_context_window = self.sr // self.hop_size * self.dit_max_context_len + overlap_wave_len = self.overlap_frame_len * self.hop_size + + with torch.autocast(device_type=device.type, dtype=dtype): + # Compute content features + source_content_indices = self._process_content_features(source_wave_16k_tensor, is_narrow=False) + target_content_indices = self._process_content_features(target_wave_16k_tensor, is_narrow=False) + # Compute style features + target_style = self.compute_style(target_wave_16k_tensor) + prompt_condition, _, = self.cfm_length_regulator(target_content_indices, + ylens=torch.LongTensor([target_mel_len]).to(device)) + + # prepare for streaming + generated_wave_chunks = [] + processed_frames = 0 + previous_chunk = None + if convert_style: + with torch.autocast(device_type=device.type, dtype=dtype): + source_narrow_indices = self._process_content_features(source_wave_16k_tensor, is_narrow=True) + target_narrow_indices = self._process_content_features(target_wave_16k_tensor, is_narrow=True) + src_narrow_reduced, src_narrow_len = self.duration_reduction_func(source_narrow_indices[0], 1) + tgt_narrow_reduced, tgt_narrow_len = self.duration_reduction_func(target_narrow_indices[0], 1) + # Process src_narrow_reduced in chunks of max 1000 tokens + max_chunk_size = self.ar_max_content_len - tgt_narrow_len + + # Process src_narrow_reduced in chunks + for i in range(0, len(src_narrow_reduced), max_chunk_size): + is_last_chunk = i + max_chunk_size >= len(src_narrow_reduced) + with torch.autocast(device_type=device.type, dtype=dtype): + chunk = src_narrow_reduced[i:i + max_chunk_size] + if anonymization_only: + chunk_ar_cond = self.ar_length_regulator(chunk[None])[0] + chunk_ar_out = self.ar.generate(chunk_ar_cond, torch.zeros([1, 0]).long().to(device), + compiled_decode_fn=self.compiled_decode_fn, + top_p=top_p, temperature=temperature, + repetition_penalty=repetition_penalty) + else: + # For each chunk, we need to include tgt_narrow_reduced as context + chunk_ar_cond = self.ar_length_regulator(torch.cat([tgt_narrow_reduced, chunk], dim=0)[None])[0] + chunk_ar_out = self.ar.generate(chunk_ar_cond, target_content_indices, compiled_decode_fn=self.compiled_decode_fn, + top_p=top_p, temperature=temperature, + repetition_penalty=repetition_penalty) + chunkar_out_mel_len = torch.LongTensor([int(source_mel_len / source_content_indices.size( + -1) * chunk_ar_out.size(-1) * length_adjust)]).to(device) + # Length regulation + chunk_cond, _ = self.cfm_length_regulator(chunk_ar_out, ylens=torch.LongTensor([chunkar_out_mel_len]).to(device)) + cat_condition = torch.cat([prompt_condition, chunk_cond], dim=1) + original_len = cat_condition.size(1) + # pad cat_condition to compile_len + if self.dit_compiled: + cat_condition = torch.nn.functional.pad(cat_condition, + (0, 0, 0, self.compile_len - cat_condition.size(1),), + value=0) + # Voice Conversion + vc_mel = self.cfm.inference( + cat_condition, + torch.LongTensor([original_len]).to(device), + target_mel, target_style, diffusion_steps, + inference_cfg_rate=[intelligebility_cfg_rate, similarity_cfg_rate], + random_voice=anonymization_only, + ) + vc_mel = vc_mel[:, :, target_mel_len:original_len] + vc_wave = self.vocoder(vc_mel).squeeze()[None] + processed_frames, previous_chunk, should_break, mp3_bytes, full_audio = self._stream_wave_chunks( + vc_wave, processed_frames, vc_mel, overlap_wave_len, + generated_wave_chunks, previous_chunk, is_last_chunk, stream_output + ) + + if stream_output and mp3_bytes is not None: + yield mp3_bytes, full_audio + if should_break: + break + else: + cond, _ = self.cfm_length_regulator(source_content_indices, ylens=torch.LongTensor([source_mel_len]).to(device)) + + # Process in chunks for streaming + max_source_window = max_context_window - target_mel.size(2) + + # Generate chunk by chunk and stream the output + while processed_frames < cond.size(1): + chunk_cond = cond[:, processed_frames:processed_frames + max_source_window] + is_last_chunk = processed_frames + max_source_window >= cond.size(1) + cat_condition = torch.cat([prompt_condition, chunk_cond], dim=1) + original_len = cat_condition.size(1) + # pad cat_condition to compile_len + if self.dit_compiled: + cat_condition = torch.nn.functional.pad(cat_condition, + (0, 0, 0, self.compile_len - cat_condition.size(1),), value=0) + with torch.autocast(device_type=device.type, dtype=torch.float32): # force CFM to use float32 + # Voice Conversion + vc_mel = self.cfm.inference( + cat_condition, + torch.LongTensor([original_len]).to(device), + target_mel, target_style, diffusion_steps, + inference_cfg_rate=[intelligebility_cfg_rate, similarity_cfg_rate], + random_voice=anonymization_only, + ) + vc_mel = vc_mel[:, :, target_mel_len:original_len] + vc_wave = self.vocoder(vc_mel).squeeze()[None] + + processed_frames, previous_chunk, should_break, mp3_bytes, full_audio = self._stream_wave_chunks( + vc_wave, processed_frames, vc_mel, overlap_wave_len, + generated_wave_chunks, previous_chunk, is_last_chunk, stream_output + ) + + if stream_output and mp3_bytes is not None: + yield mp3_bytes, full_audio + if should_break: + break diff --git a/modules/wavenet.py b/modules/wavenet.py new file mode 100644 index 0000000000000000000000000000000000000000..2d2c3b862593e2c0f78b2f8cb2d38c4cbbe573eb --- /dev/null +++ b/modules/wavenet.py @@ -0,0 +1,174 @@ +import math +import torch +from torch import nn +from torch.nn import functional as F + +from modules.encodec import SConv1d + +from . import commons +LRELU_SLOPE = 0.1 + +class LayerNorm(nn.Module): + def __init__(self, channels, eps=1e-5): + super().__init__() + self.channels = channels + self.eps = eps + + self.gamma = nn.Parameter(torch.ones(channels)) + self.beta = nn.Parameter(torch.zeros(channels)) + + def forward(self, x): + x = x.transpose(1, -1) + x = F.layer_norm(x, (self.channels,), self.gamma, self.beta, self.eps) + return x.transpose(1, -1) + + +class ConvReluNorm(nn.Module): + def __init__(self, in_channels, hidden_channels, out_channels, kernel_size, n_layers, p_dropout): + super().__init__() + self.in_channels = in_channels + self.hidden_channels = hidden_channels + self.out_channels = out_channels + self.kernel_size = kernel_size + self.n_layers = n_layers + self.p_dropout = p_dropout + assert n_layers > 1, "Number of layers should be larger than 0." + + self.conv_layers = nn.ModuleList() + self.norm_layers = nn.ModuleList() + self.conv_layers.append(nn.Conv1d(in_channels, hidden_channels, kernel_size, padding=kernel_size // 2)) + self.norm_layers.append(LayerNorm(hidden_channels)) + self.relu_drop = nn.Sequential( + nn.ReLU(), + nn.Dropout(p_dropout)) + for _ in range(n_layers - 1): + self.conv_layers.append(nn.Conv1d(hidden_channels, hidden_channels, kernel_size, padding=kernel_size // 2)) + self.norm_layers.append(LayerNorm(hidden_channels)) + self.proj = nn.Conv1d(hidden_channels, out_channels, 1) + self.proj.weight.data.zero_() + self.proj.bias.data.zero_() + + def forward(self, x, x_mask): + x_org = x + for i in range(self.n_layers): + x = self.conv_layers[i](x * x_mask) + x = self.norm_layers[i](x) + x = self.relu_drop(x) + x = x_org + self.proj(x) + return x * x_mask + + +class DDSConv(nn.Module): + """ + Dialted and Depth-Separable Convolution + """ + + def __init__(self, channels, kernel_size, n_layers, p_dropout=0.): + super().__init__() + self.channels = channels + self.kernel_size = kernel_size + self.n_layers = n_layers + self.p_dropout = p_dropout + + self.drop = nn.Dropout(p_dropout) + self.convs_sep = nn.ModuleList() + self.convs_1x1 = nn.ModuleList() + self.norms_1 = nn.ModuleList() + self.norms_2 = nn.ModuleList() + for i in range(n_layers): + dilation = kernel_size ** i + padding = (kernel_size * dilation - dilation) // 2 + self.convs_sep.append(nn.Conv1d(channels, channels, kernel_size, + groups=channels, dilation=dilation, padding=padding + )) + self.convs_1x1.append(nn.Conv1d(channels, channels, 1)) + self.norms_1.append(LayerNorm(channels)) + self.norms_2.append(LayerNorm(channels)) + + def forward(self, x, x_mask, g=None): + if g is not None: + x = x + g + for i in range(self.n_layers): + y = self.convs_sep[i](x * x_mask) + y = self.norms_1[i](y) + y = F.gelu(y) + y = self.convs_1x1[i](y) + y = self.norms_2[i](y) + y = F.gelu(y) + y = self.drop(y) + x = x + y + return x * x_mask + + +class WN(torch.nn.Module): + def __init__(self, hidden_channels, kernel_size, dilation_rate, n_layers, gin_channels=0, p_dropout=0, causal=False): + super(WN, self).__init__() + conv1d_type = SConv1d + assert (kernel_size % 2 == 1) + self.hidden_channels = hidden_channels + self.kernel_size = kernel_size, + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.gin_channels = gin_channels + self.p_dropout = p_dropout + + self.in_layers = torch.nn.ModuleList() + self.res_skip_layers = torch.nn.ModuleList() + self.drop = nn.Dropout(p_dropout) + + if gin_channels != 0: + self.cond_layer = conv1d_type(gin_channels, 2 * hidden_channels * n_layers, 1, norm='weight_norm') + + for i in range(n_layers): + dilation = dilation_rate ** i + padding = int((kernel_size * dilation - dilation) / 2) + in_layer = conv1d_type(hidden_channels, 2 * hidden_channels, kernel_size, dilation=dilation, + padding=padding, norm='weight_norm', causal=causal) + self.in_layers.append(in_layer) + + # last one is not necessary + if i < n_layers - 1: + res_skip_channels = 2 * hidden_channels + else: + res_skip_channels = hidden_channels + + res_skip_layer = conv1d_type(hidden_channels, res_skip_channels, 1, norm='weight_norm', causal=causal) + self.res_skip_layers.append(res_skip_layer) + + def forward(self, x, x_mask, g=None, **kwargs): + output = torch.zeros_like(x) + n_channels_tensor = torch.IntTensor([self.hidden_channels]) + + if g is not None: + g = self.cond_layer(g) + + for i in range(self.n_layers): + x_in = self.in_layers[i](x) + if g is not None: + cond_offset = i * 2 * self.hidden_channels + g_l = g[:, cond_offset:cond_offset + 2 * self.hidden_channels, :] + else: + g_l = torch.zeros_like(x_in) + + acts = commons.fused_add_tanh_sigmoid_multiply( + x_in, + g_l, + n_channels_tensor) + acts = self.drop(acts) + + res_skip_acts = self.res_skip_layers[i](acts) + if i < self.n_layers - 1: + res_acts = res_skip_acts[:, :self.hidden_channels, :] + x = (x + res_acts) * x_mask + output = output + res_skip_acts[:, self.hidden_channels:, :] + else: + output = output + res_skip_acts + return output * x_mask + + def remove_weight_norm(self): + if self.gin_channels != 0: + torch.nn.utils.remove_weight_norm(self.cond_layer) + for l in self.in_layers: + torch.nn.utils.remove_weight_norm(l) + for l in self.res_skip_layers: + torch.nn.utils.remove_weight_norm(l) \ No newline at end of file diff --git a/pipeline/inference.py b/pipeline/inference.py index ca40e5fbb8983410267c8d24b908179b228426ac..a9683ec1a599a88cb8f03cdcc806ff41a86e7787 100644 --- a/pipeline/inference.py +++ b/pipeline/inference.py @@ -1,13 +1,11 @@ """ Voice conversion module using Seed-VC (zero-shot diffusion transformer). -No training needed - just reference audio + source audio. -Uses the singing voice conversion model with F0 conditioning. +Based on the official Seed-VC app_svc.py implementation. """ import os import sys import logging -import argparse import numpy as np import torch import torchaudio @@ -25,8 +23,6 @@ except ImportError: return fn return decorator -from pipeline.setup import SEED_VC_DIR, ensure_seed_vc_path, setup_seed_vc - OUTPUT_DIR = "/tmp/rvc_output" # Cached models (loaded once, reused across calls) @@ -34,17 +30,15 @@ _model_cache = {} def _load_seed_vc_models(device): - """Load Seed-VC singing voice conversion models.""" + """Load Seed-VC singing voice conversion models (following official app_svc.py).""" if "model" in _model_cache: return _model_cache - # Ensure Seed-VC is cloned and on path (critical for ZeroGPU workers) - setup_seed_vc() - - # Import Seed-VC's model loading utilities + import yaml from modules.commons import recursive_munch, build_model, load_checkpoint from hf_utils import load_custom_model_from_hf - import yaml + + logger.info("Loading Seed-VC models on {}...".format(device)) # Load the singing model (F0-conditioned, whisper-base, 44kHz, BigVGAN) dit_checkpoint_path, dit_config_path = load_custom_model_from_hf( @@ -53,12 +47,14 @@ def _load_seed_vc_models(device): "config_dit_mel_seed_uvit_whisper_base_f0_44k.yml", ) - with open(dit_config_path, "r") as f: - config = yaml.safe_load(f) - + config = yaml.safe_load(open(dit_config_path, "r")) model_params = recursive_munch(config["model_params"]) + model_params.dit_type = "DiT" model = build_model(model_params, stage="DiT") + hop_length = config["preprocess_params"]["spect_params"]["hop_length"] + sr = config["preprocess_params"]["sr"] + # Load checkpoint model, _, _, _ = load_checkpoint( model, None, dit_checkpoint_path, @@ -68,99 +64,111 @@ def _load_seed_vc_models(device): model[key].eval() model[key].to(device) - # FP16 for efficiency - for key in model: - if hasattr(model[key], "half"): - model[key] = model[key].half() - - # Load speech tokenizer (Whisper) - from modules.speech_tokenizers.whisper.whisper_enc import WhisperSpeechEncoder - speech_tokenizer_type = config.get("model_params", {}).get( - "speech_tokenizer", {} - ).get("type", "whisper") - - whisper_name = model_params.speech_tokenizer.get("name", "whisper-small") - whisper_model = WhisperSpeechEncoder.load_model(whisper_name).to(device).eval() - if hasattr(whisper_model, "half"): - whisper_model = whisper_model.half() - - def semantic_fn(waves_16k): - wav = waves_16k.to(device).half() if waves_16k.dim() == 1 else waves_16k.to(device).half() - if wav.dim() == 1: - wav = wav.unsqueeze(0) - with torch.no_grad(): - return whisper_model.extract_features(wav) - - # Load vocoder (BigVGAN) - vocoder_type = config.get("model_params", {}).get("vocoder", {}).get("type", "bigvgan") - if vocoder_type == "bigvgan": - from modules.bigvgan import bigvgan - vocoder_path = os.path.join(SEED_VC_DIR, "modules", "bigvgan") - vocoder = bigvgan.BigVGAN.from_pretrained( - "nvidia/bigvgan_v2_44khz_128band_512x", use_cuda_kernel=False - ) - vocoder = vocoder.to(device).eval() - if hasattr(vocoder, "half"): - vocoder = vocoder.half() - - def vocoder_fn(mel): - with torch.no_grad(): - return vocoder(mel.half()) - else: - from modules.vocoder import load_vocoder - vocoder = load_vocoder(vocoder_type, config).to(device).eval() - - def vocoder_fn(mel): - with torch.no_grad(): - return vocoder(mel) + # Setup caches for faster inference + model.cfm.estimator.setup_caches(max_batch_size=1, max_seq_length=8192) # Load CAMPPlus speaker embedding model from modules.campplus.DTDNN import CAMPPlus + campplus_ckpt_path = load_custom_model_from_hf( "funasr/campplus", "campplus_cn_common.bin", config_filename=None ) - if isinstance(campplus_ckpt_path, tuple): - campplus_ckpt_path = campplus_ckpt_path[0] campplus_model = CAMPPlus(feat_dim=80, embedding_size=192) campplus_model.load_state_dict(torch.load(campplus_ckpt_path, map_location="cpu")) - campplus_model = campplus_model.to(device).eval().half() + campplus_model.eval() + campplus_model.to(device) + + # Load BigVGAN vocoder + from modules.bigvgan import bigvgan + bigvgan_name = model_params.vocoder.name + bigvgan_model = bigvgan.BigVGAN.from_pretrained(bigvgan_name, use_cuda_kernel=False) + bigvgan_model.remove_weight_norm() + bigvgan_model = bigvgan_model.eval().to(device) + + # Load Whisper speech tokenizer (using transformers, NOT custom module) + from transformers import AutoFeatureExtractor, WhisperModel + whisper_name = model_params.speech_tokenizer.name + whisper_model = WhisperModel.from_pretrained(whisper_name, torch_dtype=torch.float16).to(device) + del whisper_model.decoder + whisper_feature_extractor = AutoFeatureExtractor.from_pretrained(whisper_name) - # Load F0 extractor (RMVPE) + def semantic_fn(waves_16k): + ori_inputs = whisper_feature_extractor( + [waves_16k.squeeze(0).cpu().numpy()], + return_tensors="pt", + return_attention_mask=True, + ) + ori_input_features = whisper_model._mask_input_features( + ori_inputs.input_features, attention_mask=ori_inputs.attention_mask + ).to(device) + with torch.no_grad(): + ori_outputs = whisper_model.encoder( + ori_input_features.to(whisper_model.encoder.dtype), + head_mask=None, + output_attentions=False, + output_hidden_states=False, + return_dict=True, + ) + S_ori = ori_outputs.last_hidden_state.to(torch.float32) + S_ori = S_ori[:, :waves_16k.size(-1) // 320 + 1] + return S_ori + + # Mel spectrogram + from modules.audio import mel_spectrogram + + mel_fn_args = { + "n_fft": config["preprocess_params"]["spect_params"]["n_fft"], + "win_size": config["preprocess_params"]["spect_params"]["win_length"], + "hop_size": config["preprocess_params"]["spect_params"]["hop_length"], + "num_mels": config["preprocess_params"]["spect_params"]["n_mels"], + "sampling_rate": sr, + "fmin": config["preprocess_params"]["spect_params"].get("fmin", 0), + "fmax": None if config["preprocess_params"]["spect_params"].get("fmax", "None") == "None" else 8000, + "center": False, + } + to_mel = lambda x: mel_spectrogram(x, **mel_fn_args) + + # F0 extractor (RMVPE) from modules.rmvpe import RMVPE + model_path = load_custom_model_from_hf("lj1995/VoiceConversionWebUI", "rmvpe.pt", None) + rmvpe = RMVPE(model_path, is_half=False, device=device) + f0_fn = rmvpe.infer_from_audio - rmvpe_path = load_custom_model_from_hf("lj1995/VoiceConversionWebUI", "rmvpe.pt", config_filename=None) - if isinstance(rmvpe_path, tuple): - rmvpe_path = rmvpe_path[0] - f0_extractor = RMVPE(rmvpe_path, is_half=True, device=device) - - def f0_fn(wav, thred=0.03): - return f0_extractor.infer_from_audio(wav, thred=thred) - - # Mel spectrogram config - from modules.commons import build_mel_fn - mel_fn_args = config["preprocess_params"]["spect_params"] - to_mel = build_mel_fn(mel_fn_args) - sr = config["preprocess_params"]["sr"] - hop_length = mel_fn_args["hop_length"] + max_context_window = sr // hop_length * 30 + overlap_frame_len = 16 + overlap_wave_len = overlap_frame_len * hop_length _model_cache.update({ "model": model, "semantic_fn": semantic_fn, - "vocoder_fn": vocoder_fn, + "vocoder_fn": bigvgan_model, "campplus_model": campplus_model, "f0_fn": f0_fn, "to_mel": to_mel, "sr": sr, "hop_length": hop_length, + "max_context_window": max_context_window, + "overlap_frame_len": overlap_frame_len, + "overlap_wave_len": overlap_wave_len, "device": device, - "max_context_window": model_params.DiT.max_context_window, - "overlap_frame_len": 16, }) - logger.info(f"Seed-VC models loaded (sr={sr}, hop={hop_length})") + logger.info("Seed-VC models loaded (sr={}, hop={})".format(sr, hop_length)) return _model_cache +def adjust_f0_semitones(f0_sequence, n_semitones): + factor = 2 ** (n_semitones / 12) + return f0_sequence * factor + + +def crossfade(chunk1, chunk2, overlap): + fade_out = np.cos(np.linspace(0, np.pi / 2, overlap)) ** 2 + fade_in = np.cos(np.linspace(np.pi / 2, 0, overlap)) ** 2 + chunk2[:overlap] = chunk2[:overlap] * fade_in + chunk1[-overlap:] * fade_out + return chunk2 + + @spaces.GPU(duration=120) def convert_voice( audio_path, @@ -176,14 +184,7 @@ def convert_voice( ): """ Convert voice using Seed-VC zero-shot singing voice conversion. - - Args: - audio_path: Path to source vocals (separated by Demucs) - reference_path: Path to reference voice audio (3-30 sec) - pitch: Semitone shift (-24 to +24) - diffusion_steps: Quality vs speed trade-off (10=fast, 30=quality) - - Returns path to converted audio file. + Based on the official app_svc.py voice_conversion function. """ import soundfile as sf @@ -191,187 +192,172 @@ def convert_voice( base_name = os.path.splitext(os.path.basename(audio_path))[0] output_path = os.path.join(OUTPUT_DIR, "{}_converted.wav".format(base_name)) - device = "cuda" if torch.cuda.is_available() else "cpu" + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") logger.info("Converting voice with Seed-VC on {}".format(device)) - logger.info("Source: {}, Reference: {}, Pitch: {}".format(audio_path, reference_path, pitch)) - - # Ensure Seed-VC is available in this worker process - try: - setup_seed_vc() - except Exception as e: - logger.error("Seed-VC setup failed in GPU worker: {}".format(e)) - raise RuntimeError("Seed-VC setup failed: {}".format(e)) # Load models - try: - cache = _load_seed_vc_models(device) - except ModuleNotFoundError as e: - logger.error("Module not found: {}. sys.path: {}".format(e, sys.path[:5])) - logger.error("SEED_VC_DIR exists: {}, contents: {}".format( - os.path.exists(SEED_VC_DIR), - os.listdir(SEED_VC_DIR) if os.path.exists(SEED_VC_DIR) else "N/A" - )) - raise - model = cache["model"] + cache = _load_seed_vc_models(device) + inference_module = cache["model"] semantic_fn = cache["semantic_fn"] vocoder_fn = cache["vocoder_fn"] campplus_model = cache["campplus_model"] f0_fn = cache["f0_fn"] - to_mel = cache["to_mel"] + mel_fn = cache["to_mel"] sr = cache["sr"] hop_length = cache["hop_length"] max_context_window = cache["max_context_window"] overlap_frame_len = cache["overlap_frame_len"] + overlap_wave_len = cache["overlap_wave_len"] # Load source audio source_audio = librosa.load(audio_path, sr=sr)[0] source_audio = torch.tensor(source_audio).unsqueeze(0).float().to(device) - # Load reference audio + # Load reference audio (clip to 25s as per official code) ref_audio = librosa.load(reference_path, sr=sr)[0] - # Limit reference to 30 seconds - max_ref_samples = 30 * sr - if len(ref_audio) > max_ref_samples: - ref_audio = ref_audio[:max_ref_samples] - ref_audio = torch.tensor(ref_audio).unsqueeze(0).float().to(device) - - # Resample to 16kHz for speech tokenizer - source_16k = torchaudio.functional.resample(source_audio, sr, 16000) - ref_16k = torchaudio.functional.resample(ref_audio, sr, 16000) - - # Extract semantic tokens - S_alt = semantic_fn(source_16k[0]) - S_ori = semantic_fn(ref_16k[0]) - - # Extract mel spectrograms - mel_source = to_mel(source_audio.to(device)) - mel_ref = to_mel(ref_audio.to(device)) - target_lengths = torch.LongTensor([mel_ref.size(2)]).to(device) - - # Extract speaker embedding from reference - feat_ref = torchaudio.compliance.kaldi.fbank( - ref_16k[0].unsqueeze(0) if ref_16k.dim() == 2 else ref_16k, - num_mel_bins=80, sample_frequency=16000, - dither=0, window_type="hamming", - ) - feat_ref = feat_ref - feat_ref.mean(dim=0, keepdim=True) - style_ref = campplus_model(feat_ref.unsqueeze(0).half().to(device)) + ref_audio = torch.tensor(ref_audio[:sr * 25]).unsqueeze(0).float().to(device) - # Extract F0 for singing - F0_ori = f0_fn(ref_16k[0].cpu().numpy(), thred=0.03) - F0_alt = f0_fn(source_16k[0].cpu().numpy(), thred=0.03) + # Resample to 16kHz + ref_waves_16k = torchaudio.functional.resample(ref_audio, sr, 16000) + converted_waves_16k = torchaudio.functional.resample(source_audio, sr, 16000) - F0_ori = torch.tensor(F0_ori).to(device).float() - F0_alt = torch.tensor(F0_alt).to(device).float() - - # Auto-adjust F0 to match reference pitch range - voiced_ori = F0_ori > 1 - voiced_alt = F0_alt > 1 + # Extract semantic tokens with Whisper + # Handle long audio by chunking (>30s) + if converted_waves_16k.size(-1) <= 16000 * 30: + S_alt = semantic_fn(converted_waves_16k) + else: + overlapping_time = 5 # seconds + S_alt_list = [] + buffer = None + traversed_time = 0 + while traversed_time < converted_waves_16k.size(-1): + if buffer is None: + chunk = converted_waves_16k[:, traversed_time:traversed_time + 16000 * 30] + else: + chunk = torch.cat([ + buffer, + converted_waves_16k[:, traversed_time:traversed_time + 16000 * (30 - overlapping_time)] + ], dim=-1) + S_alt = semantic_fn(chunk) + if traversed_time == 0: + S_alt_list.append(S_alt) + else: + S_alt_list.append(S_alt[:, 50 * overlapping_time:]) + buffer = chunk[:, -16000 * overlapping_time:] + traversed_time += 30 * 16000 if traversed_time == 0 else chunk.size(-1) - 16000 * overlapping_time + S_alt = torch.cat(S_alt_list, dim=1) + + S_ori = semantic_fn(ref_waves_16k) + + # Mel spectrograms + mel = mel_fn(source_audio.to(device).float()) + mel2 = mel_fn(ref_audio.to(device).float()) + + target_lengths = torch.LongTensor([mel.size(2)]).to(mel.device) + target2_lengths = torch.LongTensor([mel2.size(2)]).to(mel2.device) + + # Speaker embedding from reference + feat2 = torchaudio.compliance.kaldi.fbank( + ref_waves_16k, + num_mel_bins=80, + dither=0, + sample_frequency=16000, + ) + feat2 = feat2 - feat2.mean(dim=0, keepdim=True) + style2 = campplus_model(feat2.unsqueeze(0)) - log_f0_alt = torch.zeros_like(F0_alt) - log_f0_alt[voiced_alt] = torch.log(F0_alt[voiced_alt]) + # F0 extraction + F0_ori = f0_fn(ref_waves_16k[0], thred=0.03) + F0_alt = f0_fn(converted_waves_16k[0], thred=0.03) - shifted_log_f0_alt = log_f0_alt.clone() + F0_ori = torch.from_numpy(F0_ori).to(device)[None] + F0_alt = torch.from_numpy(F0_alt).to(device)[None] - if voiced_ori.any() and voiced_alt.any(): - median_log_f0_ori = torch.log(F0_ori[voiced_ori]).median() - median_log_f0_alt = log_f0_alt[voiced_alt].median() - shifted_log_f0_alt[voiced_alt] = ( - log_f0_alt[voiced_alt] - median_log_f0_alt + median_log_f0_ori - ) + voiced_F0_ori = F0_ori[F0_ori > 1] + voiced_F0_alt = F0_alt[F0_alt > 1] - shifted_f0_alt = torch.zeros_like(F0_alt) - shifted_f0_alt[voiced_alt] = torch.exp(shifted_log_f0_alt[voiced_alt]) + log_f0_alt = torch.log(F0_alt + 1e-5) + voiced_log_f0_ori = torch.log(voiced_F0_ori + 1e-5) + voiced_log_f0_alt = torch.log(voiced_F0_alt + 1e-5) + median_log_f0_ori = torch.median(voiced_log_f0_ori) + median_log_f0_alt = torch.median(voiced_log_f0_alt) - # Apply semitone pitch shift + # Auto F0 adjust + pitch shift + shifted_log_f0_alt = log_f0_alt.clone() + shifted_log_f0_alt[F0_alt > 1] = log_f0_alt[F0_alt > 1] - median_log_f0_alt + median_log_f0_ori + shifted_f0_alt = torch.exp(shifted_log_f0_alt) if pitch != 0: - factor = 2.0 ** (pitch / 12.0) - shifted_f0_alt[voiced_alt] = shifted_f0_alt[voiced_alt] * factor + shifted_f0_alt[F0_alt > 1] = adjust_f0_semitones(shifted_f0_alt[F0_alt > 1], pitch) - # Process in chunks with crossfading - cond = model["DiT"].prepare_concat(S_alt, mel_source) + # Length regulation + cond, _, codes, commitment_loss, codebook_loss = inference_module.length_regulator( + S_alt, ylens=target_lengths, n_quantizers=3, f0=shifted_f0_alt + ) + prompt_condition, _, codes, commitment_loss, codebook_loss = inference_module.length_regulator( + S_ori, ylens=target2_lengths, n_quantizers=3, f0=F0_ori + ) - # Prepare F0 conditioning - max_source_window = max_context_window - mel_ref.size(2) - overlap_wave_len = overlap_frame_len * hop_length + # Interpolate F0 + interpolated_shifted_f0_alt = torch.nn.functional.interpolate( + shifted_f0_alt.unsqueeze(1), size=cond.size(1), mode="nearest" + ).squeeze(1) - # Interpolate F0 to match mel frames - n_mel_frames = cond.size(1) - if len(shifted_f0_alt) != n_mel_frames: - shifted_f0_alt_interp = torch.nn.functional.interpolate( - shifted_f0_alt.unsqueeze(0).unsqueeze(0), - size=n_mel_frames, mode="nearest", - ).squeeze() - else: - shifted_f0_alt_interp = shifted_f0_alt + max_source_window = max_context_window - mel2.size(2) - # Generate in chunks - generated_wave_chunks = [] + # Generate chunk by chunk with crossfading processed_frames = 0 + generated_wave_chunks = [] while processed_frames < cond.size(1): - chunk_end = min(processed_frames + max_source_window, cond.size(1)) - chunk_cond = cond[:, processed_frames:chunk_end] - chunk_f0 = shifted_f0_alt_interp[processed_frames:chunk_end] - - # Concatenate reference mel with source chunk - cat_condition = torch.cat([mel_ref, chunk_cond], dim=2) - cat_f0 = torch.cat([ - torch.zeros(mel_ref.size(2)).to(device), - chunk_f0, - ]) - - with torch.no_grad(): - vc_target = model["cfm"].inference( - cat_condition.half(), - torch.LongTensor([cat_condition.size(2)]).to(device), - mel_ref.half(), - style_ref, - cat_f0.unsqueeze(0).half(), - diffusion_steps, + chunk_cond = cond[:, processed_frames:processed_frames + max_source_window] + is_last_chunk = processed_frames + max_source_window >= cond.size(1) + cat_condition = torch.cat([prompt_condition, chunk_cond], dim=1) + + with torch.autocast(device_type=device.type, dtype=torch.float16): + vc_target = inference_module.cfm.inference( + cat_condition, + torch.LongTensor([cat_condition.size(1)]).to(mel2.device), + mel2, style2, None, diffusion_steps, inference_cfg_rate=index_rate, ) - vc_target = vc_target[:, :, mel_ref.size(2):] - - # Vocoder - vc_wave = vocoder_fn(vc_target.float()) - - if generated_wave_chunks: - # Crossfade with previous chunk - prev = generated_wave_chunks[-1] - if overlap_wave_len > 0 and len(prev) >= overlap_wave_len: - cross_len = min(overlap_wave_len, vc_wave.size(-1)) - fade_out = np.linspace(1, 0, cross_len) - fade_in = np.linspace(0, 1, cross_len) - prev_np = prev if isinstance(prev, np.ndarray) else prev - new_np = vc_wave[0].cpu().float().numpy() - prev_np[-cross_len:] = ( - prev_np[-cross_len:] * fade_out + new_np[:cross_len] * fade_in - ) - generated_wave_chunks.append(new_np[cross_len:]) - else: - generated_wave_chunks.append(vc_wave[0].cpu().float().numpy()) + vc_target = vc_target[:, :, mel2.size(-1):] + + vc_wave = vocoder_fn(vc_target.float()).squeeze().cpu() + if vc_wave.ndim == 1: + vc_wave = vc_wave.unsqueeze(0) + + if processed_frames == 0: + if is_last_chunk: + generated_wave_chunks.append(vc_wave[0].cpu().numpy()) + break + generated_wave_chunks.append(vc_wave[0, :-overlap_wave_len].cpu().numpy()) + previous_chunk = vc_wave[0, -overlap_wave_len:] + processed_frames += vc_target.size(2) - overlap_frame_len + elif is_last_chunk: + output_wave = crossfade( + previous_chunk.cpu().numpy(), + vc_wave[0].cpu().numpy(), + overlap_wave_len, + ) + generated_wave_chunks.append(output_wave) + break else: - generated_wave_chunks.append(vc_wave[0].cpu().float().numpy()) - - processed_frames = chunk_end - overlap_frame_len - if processed_frames < 0: - processed_frames = chunk_end + output_wave = crossfade( + previous_chunk.cpu().numpy(), + vc_wave[0, :-overlap_wave_len].cpu().numpy(), + overlap_wave_len, + ) + generated_wave_chunks.append(output_wave) + previous_chunk = vc_wave[0, -overlap_wave_len:] + processed_frames += vc_target.size(2) - overlap_frame_len - # Concatenate all chunks + # Concatenate and normalize audio_out = np.concatenate(generated_wave_chunks) - - # Normalize audio_max = np.abs(audio_out).max() if audio_max > 0.01: audio_out = audio_out / audio_max * 0.95 - # Resample to 44.1kHz if needed and save - if sr != 44100: - audio_out = librosa.resample(audio_out, orig_sr=sr, target_sr=44100) - - sf.write(output_path, audio_out, 44100, subtype="PCM_16") - logger.info("Conversion complete: {} ({:.1f}s)".format( - output_path, len(audio_out) / 44100 - )) + # Save + sf.write(output_path, audio_out, sr, subtype="PCM_16") + logger.info("Conversion complete: {} ({:.1f}s)".format(output_path, len(audio_out) / sr)) return output_path diff --git a/pipeline/setup.py b/pipeline/setup.py index f3a2bfe623fa2a4599e02c68a65b495cde41a40a..6e8b5ed4f0ef9288bb010d130a3fc84066d67951 100644 --- a/pipeline/setup.py +++ b/pipeline/setup.py @@ -1,59 +1,17 @@ """ -Setup module: clone Seed-VC repo at startup. -Seed-VC downloads its own pretrained models from HuggingFace on first use. +Setup module: Seed-VC modules are now bundled directly in the repo. +No git clone needed. Models are downloaded from HuggingFace on first use. """ import os import sys -import subprocess import logging logger = logging.getLogger(__name__) -SEED_VC_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "seed-vc") -SEED_VC_REPO = "https://github.com/Plachtaa/seed-vc.git" - - -def clone_seed_vc(): - """Clone Seed-VC repository if not already present.""" - if os.path.exists(os.path.join(SEED_VC_DIR, "inference.py")): - logger.info("Seed-VC already cloned.") - return True - - logger.info("Cloning Seed-VC repository...") - try: - subprocess.run( - ["git", "clone", "--depth", "1", SEED_VC_REPO, SEED_VC_DIR], - check=True, capture_output=True, text=True, - ) - logger.info("Seed-VC cloned successfully.") - return True - except subprocess.CalledProcessError as e: - logger.error(f"Failed to clone Seed-VC: {e.stderr}") - return False - - -def ensure_seed_vc_path(): - """Ensure Seed-VC is on the Python path.""" - if SEED_VC_DIR not in sys.path: - sys.path.insert(0, SEED_VC_DIR) - def setup_seed_vc(): - """Full setup: clone repo + add to path.""" - if not clone_seed_vc(): - raise RuntimeError("Failed to clone Seed-VC") - ensure_seed_vc_path() - - # Install Seed-VC dependencies that might not be in our requirements.txt - try: - subprocess.run( - [sys.executable, "-m", "pip", "install", "-q", - "descript-audio-codec", "vocos", "bigvgan"], - capture_output=True, text=True, timeout=120, - ) - except Exception as e: - logger.warning(f"Some Seed-VC deps may be missing: {e}") - - logger.info("Seed-VC setup complete.") + """Setup: ensure checkpoints directory exists.""" + os.makedirs("./checkpoints", exist_ok=True) + logger.info("Seed-VC setup complete (modules bundled in repo).") return True