Spaces:
Running on Zero
Running on Zero
| # 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. | |
| """Compression models or wrapper around existing models. | |
| Also defines the main interface that a model must follow to be usable as an audio tokenizer. | |
| """ | |
| import logging | |
| import contextlib | |
| import os | |
| import math | |
| from pathlib import Path | |
| import typing as tp | |
| import numpy as np | |
| import torch | |
| from torch import nn | |
| # from torchmetrics.functional.audio import scale_invariant_signal_distortion_ratio | |
| import config as cfg | |
| from models.quantization.base import QuantizedResult | |
| from models import quantization as qt | |
| import hyperparameters as hp | |
| # from modules import SEANetDecoder, SEANetEncoder | |
| # audiocraft imports | |
| with contextlib.redirect_stderr(open(os.devnull, "w")): | |
| from models.modules import SEANetEncoder, SEANetDecoder | |
| logger = logging.getLogger() | |
| class EncodecModel(nn.Module): | |
| """Encodec model operating on the raw waveform. | |
| Args: | |
| encoder (nn.Module): Encoder network. | |
| decoder (nn.Module): Decoder network. | |
| quantizer (qt.BaseQuantizer): Quantizer network. | |
| frame_rate (int): Frame rate for the latent representation. | |
| sample_rate (int): Audio sample rate. | |
| channels (int): Number of audio channels. | |
| causal (bool): Whether to use a causal version of the model. | |
| renormalize (bool): Whether to renormalize the audio before running the model. | |
| """ | |
| # we need assignment to override the property in the abstract class, | |
| # I couldn't find a better way... | |
| frame_rate: float = 0 # type: ignore | |
| sample_rate: int = 0 | |
| channels: int = 0 | |
| def __init__(self, | |
| encoder: SEANetEncoder, | |
| decoder: SEANetDecoder, | |
| quantizer: qt.ResidualVectorQuantizer, | |
| sample_rate: int, | |
| channels: int, | |
| causal: bool = True, | |
| renormalize: bool = False): | |
| super().__init__() | |
| self.encoder: SEANetEncoder = encoder | |
| self.decoder: SEANetDecoder = decoder | |
| self.quantizer: qt.ResidualVectorQuantizer = quantizer | |
| # self.frame_rate = frame_rate | |
| self.sample_rate = sample_rate | |
| self.channels = channels | |
| self.renormalize = renormalize | |
| self.causal = causal | |
| self.frame_rate: int = int( | |
| math.ceil(self.sample_rate / | |
| np.prod(self.encoder.ratios))) # type: ignore | |
| if self.causal: | |
| # we force disabling here to avoid handling linear overlap of segments | |
| # as supported in original EnCodec codebase. | |
| assert not self.renormalize, 'Causal model does not support renormalize' | |
| def total_codebooks(self): | |
| """Total number of quantizer codebooks available.""" | |
| return self.quantizer.total_codebooks | |
| def num_codebooks(self): | |
| """Active number of codebooks used by the quantizer.""" | |
| return self.quantizer.num_codebooks | |
| def set_num_codebooks(self, n: int): | |
| """Set the active number of codebooks used by the quantizer.""" | |
| self.quantizer.set_num_codebooks(n) | |
| def cardinality(self): | |
| """Cardinality of each codebook.""" | |
| return self.quantizer.bins | |
| def preprocess( | |
| self, x: torch.Tensor | |
| ) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]: | |
| scale: tp.Optional[torch.Tensor] | |
| if self.renormalize: | |
| mono = x.mean(dim=1, keepdim=True) | |
| volume = mono.pow(2).mean(dim=2, keepdim=True).sqrt() | |
| scale = 1e-8 + volume | |
| x = x / scale | |
| scale = scale.view(-1, 1) | |
| else: | |
| scale = None | |
| return x, scale | |
| def postprocess(self, | |
| x: torch.Tensor, | |
| scale: tp.Optional[torch.Tensor] = None) -> torch.Tensor: | |
| if scale is not None: | |
| assert self.renormalize | |
| x = x * scale.view(-1, 1, 1) | |
| return x | |
| def forward_with_sum_loss( | |
| self, | |
| x: torch.Tensor, | |
| sum_loss_multiplier: float = 1.) -> qt.QuantizedResult: | |
| """ Forward pass enforcing additivity in the latent space: | |
| Q(x1) + Q(x2) = Q(x1 + x2) | |
| """ | |
| raise NotImplementedError() | |
| # if we are using sum_loss, batch size needs to be even | |
| if x.shape[0] % 2 != 0: | |
| raise ValueError("Batch size needs to be even to use sum loss. " | |
| f"Received {x.shape}") | |
| # y = x1 + x2 | |
| y = x.clone() | |
| half_bs: int = x.shape[0] // 2 | |
| y = y[:half_bs] + y[half_bs:] | |
| # encode y: E(y) | |
| y, y_scale = self.preprocess(y) | |
| encoded_y = self.encoder(y) | |
| # encode x: E(x) | |
| length = x.shape[-1] | |
| x, scale = self.preprocess(x) | |
| encoded_x = self.encoder(x) | |
| # quantize x = x1, x2: get Q(x) = Q(x1), Q(x2) | |
| x_quantized: QuantizedResult = self.quantizer(encoded_x, | |
| self.frame_rate) | |
| # quantize y = x1 + x2: get Q(y) = Q(x1 + x2) | |
| y_quantized = self.quantizer(encoded_y, self.frame_rate) # type: ignore | |
| # sum_of_quantized_layers = Q(x1) + Q(x2) | |
| sum_of_quantized_layers = ( | |
| x_quantized.quantized_layers[:half_bs] + # type: ignore | |
| x_quantized.quantized_layers[half_bs:]) # type: ignore | |
| # quantization_of_sum = Q(x1 + x2) = Q(y) | |
| quantization_of_sum_layers = y_quantized.quantized_layers | |
| # compute sum_loss with L2. prediction: Q(x1) + Q(x2) target: Q(x1+x2) | |
| sum_loss = nn.functional.mse_loss(sum_of_quantized_layers, | |
| quantization_of_sum_layers) | |
| sum_loss = sum_loss * sum_loss_multiplier | |
| x_quantized.sum_loss = sum_loss | |
| # decode Q(x) to get D(x) = D(x1), D(x2) | |
| decoded_x = self.decoder(x_quantized.x) | |
| # remove extra padding added by the encoder and decoder | |
| assert decoded_x.shape[-1] >= length, (decoded_x.shape[-1], length) | |
| decoded_x = decoded_x[..., :length] | |
| # put in x_quantized.x the decoded version to return it | |
| x_quantized.x = self.postprocess(decoded_x, scale) | |
| # compute informative losses (metrics) | |
| with torch.no_grad(): | |
| # only take last layer (sum of all layers) | |
| sum_of_quantized = sum_of_quantized_layers[:, -1, ...] | |
| quantization_of_sum = quantization_of_sum_layers[:, -1, ...] | |
| # decode Q(y) to get D(y) | |
| decoded_y = self.decoder(y_quantized.x) | |
| assert decoded_y.shape[-1] >= length, (decoded_y.shape[-1], length) | |
| decoded_y = decoded_y[..., :length] | |
| # decode Q(x1) + Q(x2) to get D(Q(x1) + Q(x2)) | |
| decoded_sum_of_quantized = self.decoder(sum_of_quantized) | |
| assert decoded_sum_of_quantized.shape[-1] >= length, ( | |
| decoded_sum_of_quantized.shape[-1], length) | |
| decoded_sum_of_quantized = decoded_sum_of_quantized[..., :length] | |
| # sum_of_decoded_quantized = D(Q(x1)) + D(Q(x2)) | |
| sum_of_decoded_quantized = decoded_x[:half_bs] + decoded_x[half_bs:] | |
| # variable names recap: | |
| # sum_of_quantized = Q(x1) + Q(x2) | |
| # quantization_of_sum = Q(x1 + x2) | |
| # decoded_sum_of_quantized = D(Q(x1) + Q(x2)) | |
| # sum_of_decoded_quantized = D(Q(x1)) + D(Q(x2)) | |
| # cosine similarity between Q(x1) + Q(x2) and Q(x1 + x2) | |
| cos_sim = torch.nn.functional.cosine_similarity(sum_of_quantized, | |
| quantization_of_sum, | |
| dim=2).mean() | |
| # recon-2 | |
| # sisdr1: prediction: D(Q(x1)) + D(Q(x2)) target: D(y) | |
| sisdr1 = scale_invariant_signal_distortion_ratio( | |
| sum_of_decoded_quantized, decoded_y).mean() | |
| # comp-1 | |
| # sisdr2: prediction: D(Q(x1) + Q(x2)) target: D(y) | |
| sisdr2 = scale_invariant_signal_distortion_ratio( | |
| decoded_sum_of_quantized, decoded_y).mean() | |
| # recon-1 | |
| # sisdr3: prediction: D(y) target: y | |
| sisdr3 = scale_invariant_signal_distortion_ratio(decoded_y, | |
| y).mean() | |
| # comp-2 | |
| # sisdr4: prediction: D(Q(x1) + Q(x2)) target: y | |
| sisdr4 = scale_invariant_signal_distortion_ratio( | |
| decoded_sum_of_quantized, y).mean() | |
| metrics: tp.Dict[str, float] = { | |
| "quantizer/cos1": cos_sim.item(), | |
| "quantizer/recon2": sisdr1.item(), | |
| "quantizer/comp1": sisdr2.item(), | |
| "quantizer/recon1": sisdr3.item(), | |
| "quantizer/comp2": sisdr4.item(), | |
| } | |
| x_quantized.metrics = metrics | |
| return x_quantized | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| sum_loss_amount: float = 0., | |
| ) -> qt.QuantizedResult: | |
| assert x.dim() == 3 | |
| if sum_loss_amount > 0: | |
| return self.forward_with_sum_loss(x, sum_loss_amount) | |
| length = x.shape[-1] | |
| x, scale = self.preprocess(x) | |
| emb = self.encoder(x) | |
| q_res: QuantizedResult = self.quantizer(emb, self.frame_rate) | |
| out = self.decoder(q_res.x) | |
| # remove extra padding added by the encoder and decoder | |
| assert out.shape[-1] >= length, (out.shape[-1], length) | |
| out = out[..., :length] | |
| q_res.x = self.postprocess(out, scale) | |
| return q_res | |
| def quantize_embedding(self, emb: torch.Tensor) -> QuantizedResult: | |
| return self.quantizer(emb, self.frame_rate) | |
| def quantize(self, x: torch.Tensor) -> QuantizedResult: | |
| """ Pass x through encoder and quantizer and return QuantizedResult""" | |
| assert x.dim() == 3 | |
| emb = self.encoder(x) | |
| quantized: QuantizedResult = self.quantizer(emb, self.frame_rate) | |
| return quantized | |
| def dequantize(self, x: torch.Tensor | QuantizedResult) -> torch.Tensor: | |
| if isinstance(x, QuantizedResult): | |
| x = x.x | |
| decoded: torch.Tensor = self.decoder(x) | |
| return decoded | |
| def encode( | |
| self, | |
| x: torch.Tensor, | |
| return_scales: bool = False | |
| # ) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]] | torch.Tensor: | |
| ) -> torch.Tensor: | |
| """Encode the given input tensor to quantized representation along with scale parameter. | |
| Args: | |
| x (torch.Tensor): Float tensor of shape [B, C, T] | |
| Returns: | |
| codes, scale (tuple of torch.Tensor, torch.Tensor): Tuple composed of: | |
| codes a float tensor of shape [B, K, T] with K the number of codebooks used and T the timestep. | |
| scale a float tensor containing the scale for audio renormalizealization. | |
| """ | |
| assert x.dim() == 3 | |
| x, scale = self.preprocess(x) | |
| emb = self.encoder(x) | |
| codes = self.quantizer.encode(emb) | |
| # if return_scales: | |
| # return codes, scale | |
| return codes | |
| def decode(self, | |
| codes: torch.Tensor, | |
| scale: tp.Optional[torch.Tensor] = None): | |
| """Decode the given codes to a reconstructed representation, using the scale to perform | |
| audio denormalization if needed. | |
| Args: | |
| codes (torch.Tensor): Int tensor of shape [B, K, T] | |
| scale (torch.Tensor, optional): Float tensor containing the scale value. | |
| Returns: | |
| out (torch.Tensor): Float tensor of shape [B, C, T], the reconstructed audio. | |
| """ | |
| emb = self.decode_latent(codes) | |
| out = self.decoder(emb) | |
| out = self.postprocess(out, scale) | |
| # out contains extra padding added by the encoder and decoder | |
| return out | |
| def decode_latent(self, codes: torch.Tensor): | |
| """Decode from the discrete codes to continuous latent space.""" | |
| return self.quantizer.decode(codes) | |
| def from_pretrained(name: str): | |
| if name == "facebook/encodec_32khz": | |
| model = torch.load(cfg.weights_dir() / "encodec_32khz.pt") | |
| else: | |
| raise NotImplementedError() | |
| return model | |
| def from_params(params: hp.EncodecParams): | |
| seanet_params = params.seanet_params | |
| qt_params = params.quantizer_params | |
| encoder: SEANetEncoder = SEANetEncoder(**seanet_params.__dict__) | |
| decoder: SEANetDecoder = SEANetDecoder(**seanet_params.__dict__) | |
| quantizer: qt.ResidualVectorQuantizer = qt.ResidualVectorQuantizer( | |
| **qt_params.__dict__) | |
| model = EncodecModel(encoder, | |
| decoder, | |
| quantizer, | |
| params.sample_rate, | |
| channels=1, | |
| causal=params.seanet_params.causal, | |
| renormalize=False) | |
| if params.weights is not None: | |
| weights = torch.load(Path(params.weights), | |
| weights_only=True, | |
| map_location="cpu") | |
| model.load_state_dict(weights) | |
| return model | |
| # @staticmethod | |
| # def _get_model(target_bandwidths: tp.List[float], | |
| # sample_rate: int = 24_000, | |
| # channels: int = 1, | |
| # causal: bool = True, | |
| # model_norm: str = 'weight_norm', | |
| # audio_normalize: bool = False, | |
| # segment: tp.Optional[float] = None, | |
| # name: str = 'unset'): | |
| # encoder = m.SEANetEncoder(channels=channels, | |
| # norm=model_norm, | |
| # causal=causal) | |
| # decoder = m.SEANetDecoder(channels=channels, | |
| # norm=model_norm, | |
| # causal=causal) | |
| # n_q = int(1000 * target_bandwidths[-1] // | |
| # (math.ceil(sample_rate / encoder.hop_length) * 10)) # = 32 | |
| # quantizer = qt.ResidualVectorQuantizer( | |
| # dimension=encoder.dimension, | |
| # n_q=n_q, | |
| # bins=1024, | |
| # ) | |
| # model = EncodecModel( | |
| # encoder, | |
| # decoder, | |
| # quantizer, | |
| # # target_bandwidths, | |
| # sample_rate, | |
| # channels, | |
| # causal=True, | |
| # renormalize=audio_normalize, | |
| # # segment=segment, | |
| # # name=name, | |
| # ) | |
| # return model | |
| # @staticmethod | |
| # def _get_pretrained(checkpoint_name: str, | |
| # repository: tp.Optional[Path] = None): | |
| # if repository is not None: | |
| # if not repository.is_dir(): | |
| # raise ValueError(f"{repository} must exist and be a directory.") | |
| # file = repository / checkpoint_name | |
| # checksum = file.stem.split('-')[1] | |
| # _check_checksum(file, checksum) | |
| # return torch.load(file) | |
| # else: | |
| # url = _get_checkpoint_url(cfg.ROOT_URL, checkpoint_name) | |
| # return torch.hub.load_state_dict_from_url( | |
| # url, map_location=cfg.device, check_hash=True) # type:ignore | |
| # @staticmethod | |
| # def encodec_model_24khz(pretrained: bool = True, | |
| # repository: tp.Optional[Path] = None): | |
| # """Return the pretrained causal 24khz model. | |
| # """ | |
| # if repository: | |
| # assert pretrained | |
| # target_bandwidths = [1.5, 3., 6, 12., 24.] | |
| # checkpoint_name = 'encodec_24khz-d7cc33bc.th' | |
| # sample_rate = 24_000 | |
| # channels = 1 | |
| # model = EncodecModel._get_model( | |
| # target_bandwidths, | |
| # sample_rate, | |
| # channels, | |
| # causal=True, | |
| # model_norm='weight_norm', | |
| # audio_normalize=False, | |
| # name='encodec_24khz' if pretrained else 'unset') | |
| # if pretrained: | |
| # state_dict = EncodecModel._get_pretrained(checkpoint_name, | |
| # repository) | |
| # model.load_state_dict(state_dict) | |
| # model.eval() | |
| # return model | |