import math import librosa from typing import List from typing import Union, Optional import numpy as np import torch import torch.nn.functional as F from audiotools import AudioSignal from audiotools.ml import BaseModel from torch import nn from einops import rearrange from rotary_embedding_torch import RotaryEmbedding from .base import CodecMixin from dac.nn.layers import Snake1d from dac.nn.layers import WNConv1d from dac.nn.layers import WNConvTranspose1d from dac.nn.quantize import ( ResidualVectorQuantize, PassthroughQuantize, GroupedResidualVectorQuantize, ) from dac.nn.lfq import LFQ, ResidualLFQ, GroupedLFQ from dac.nn.vae import VAEBottleneck from dac.nn.attend import Attend def init_weights(m): if isinstance(m, nn.Conv1d): nn.init.trunc_normal_(m.weight, std=0.02) nn.init.constant_(m.bias, 0) class ResidualUnit(nn.Module): def __init__(self, dim: int = 16, dilation: int = 1): super().__init__() pad = ((7 - 1) * dilation) // 2 self.block = nn.Sequential( Snake1d(dim), WNConv1d(dim, dim, kernel_size=7, dilation=dilation, padding=pad), Snake1d(dim), WNConv1d(dim, dim, kernel_size=1), ) def forward(self, x): y = self.block(x) pad = (x.shape[-1] - y.shape[-1]) // 2 if pad > 0: x = x[..., pad:-pad] return x + y class SubbandProjection(nn.Module): def __init__(self, bandwidth, out_dim): super(SubbandProjection, self).__init__() self.layer_norm = nn.LayerNorm(bandwidth) self.fc = nn.Linear(bandwidth, out_dim) def forward(self, x): x = rearrange(x, "b f t -> b t f") x = self.layer_norm(x) x = self.fc(x) x = rearrange(x, "b t f -> b f t") return x class NeuralFilterbank(nn.Module): def __init__( self, n_fft: int, n_filterbank: int, sample_rate: int = 48000, out_dim: int = 64 ): super(NeuralFilterbank, self).__init__() self.bandwidth_indices = self.get_bandwidth_indices( sample_rate, n_fft, n_filterbank ) self.projection_modules = self.get_projection_layers(out_dim) def get_bandwidth_indices(self, sample_rate, n_fft, n_filterbank): mel_basis = librosa.filters.mel( sr=sample_rate, n_fft=n_fft, n_mels=n_filterbank ) bandwidth_indices = [np.where(row > 0)[0] for row in mel_basis] return bandwidth_indices def get_projection_layers(self, out_dim): projection_modules = nn.ModuleList([]) for indices in self.bandwidth_indices: indices = indices[: min(64, len(indices))] projection_modules.append(SubbandProjection(len(indices), out_dim)) return projection_modules def forward(self, spec): # spec: [batch, freq, time] emb = [] for indices, layer in zip(self.bandwidth_indices, self.projection_modules): emb.append(layer(spec[:, indices[: min(64, len(indices))], :])) return rearrange(torch.stack(emb), "f b c t -> b c f t") # Frequency attention module def exists(val): return val is not None # norm def l2norm(t): return F.normalize(t, dim=-1, p=2) class RMSNorm(nn.Module): def __init__(self, dim): super().__init__() self.scale = dim**0.5 self.gamma = nn.Parameter(torch.ones(dim)) def forward(self, x): return F.normalize(x, dim=-1) * self.scale * self.gamma # attention class FeedForward(nn.Module): def __init__(self, dim, mult=4, dropout=0.0): super().__init__() dim_inner = int(dim * mult) self.net = nn.Sequential( RMSNorm(dim), nn.Linear(dim, dim_inner), nn.GELU(), nn.Dropout(dropout), nn.Linear(dim_inner, dim), nn.Dropout(dropout), ) def forward(self, x): return self.net(x) class Attention(nn.Module): def __init__( self, dim, heads=8, dim_head=64, dropout=0.0, rotary_embed=None, flash=True ): super().__init__() self.heads = heads self.scale = dim_head**-0.5 dim_inner = heads * dim_head self.rotary_embed = rotary_embed self.attend = Attend(flash=flash, dropout=dropout) self.norm = RMSNorm(dim) self.to_qkv = nn.Linear(dim, dim_inner * 3, bias=False) self.to_gates = nn.Linear(dim, heads) self.to_out = nn.Sequential( nn.Linear(dim_inner, dim, bias=False), nn.Dropout(dropout) ) def forward(self, x): x = self.norm(x) q, k, v = rearrange( self.to_qkv(x), "b n (qkv h d) -> qkv b h n d", qkv=3, h=self.heads ) if exists(self.rotary_embed): q = self.rotary_embed.rotate_queries_or_keys(q) k = self.rotary_embed.rotate_queries_or_keys(k) out = self.attend(q, k, v) gates = self.to_gates(x) out = out * rearrange(gates, "b n h -> b h n 1").sigmoid() out = rearrange(out, "b h n d -> b n (h d)") return self.to_out(out) class Transformer(nn.Module): def __init__( self, *, dim, depth, dim_head=64, heads=8, attn_dropout=0.0, ff_dropout=0.0, ff_mult=4, norm_output=True, rotary_embed=None, flash_attn=True, ): super().__init__() self.layers = nn.ModuleList([]) for _ in range(depth): attn = Attention( dim=dim, dim_head=dim_head, heads=heads, dropout=attn_dropout, rotary_embed=rotary_embed, flash=flash_attn, ) self.layers.append( nn.ModuleList( [attn, FeedForward(dim=dim, mult=ff_mult, dropout=ff_dropout)] ) ) self.norm = RMSNorm(dim) if norm_output else nn.Identity() def forward(self, x): for attn, ff in self.layers: x = attn(x) + x x = ff(x) + x return self.norm(x) class LayerNorm(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 self.normalized_shape = (normalized_shape,) def forward(self, x): if self.data_format == "channels_last": return F.layer_norm( x, self.normalized_shape, self.weight, self.bias, self.eps ) elif self.data_format == "channels_first": u = x.mean(1, keepdim=True) s = (x - u).pow(2).mean(1, keepdim=True) x = (x - u) / torch.sqrt(s + self.eps) x = self.weight[:, None, None] * x + self.bias[:, None, None] return x class Block(nn.Module): r"""ConvNeXt Block. There are two equivalent implementations: (1) DwConv -> LayerNorm (channels_first) -> 1x1 Conv -> GELU -> 1x1 Conv; all in (N, C, H, W) (2) DwConv -> Permute to (N, H, W, C); LayerNorm (channels_last) -> Linear -> GELU -> Linear; Permute back We use (2) as we find it slightly faster in PyTorch Args: dim (int): Number of input channels. drop_path (float): Stochastic depth rate. Default: 0.0 layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6. """ def __init__(self, dim, drop_path=0.0, layer_scale_init_value=1e-6): super().__init__() self.dwconv = WNConv1d( dim, dim, kernel_size=7, padding=3, groups=dim ) # depthwise conv self.norm = LayerNorm(dim, eps=1e-6) self.pwconv1 = nn.Linear( dim, 4 * dim ) # pointwise/1x1 convs, implemented with linear layers self.act = nn.GELU() self.pwconv2 = nn.Linear(4 * dim, dim) self.gamma = ( nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True) if layer_scale_init_value > 0 else None ) self.drop_path = nn.Identity() def forward(self, x): input = x x = self.dwconv(x) x = x.permute(0, 2, 1) # (N, C, H) -> (N, H, C) x = self.norm(x) x = self.pwconv1(x) x = self.act(x) x = self.pwconv2(x) if self.gamma is not None: x = self.gamma * x x = x.permute(0, 2, 1) x = input + self.drop_path(x) return x class ConvNeXtSimple(nn.Module): """No downsampling, just 4 blocks of ConvNeXt""" def __init__(self, dim, depth, drop_path=0.0, layer_scale_init_value=1e-6): super().__init__() self.blocks = nn.ModuleList( [Block(dim, drop_path, layer_scale_init_value) for _ in range(depth)] ) def forward(self, x): for block in self.blocks: x = block(x) return x class ConvNeXtBlock(nn.Module): """ConvNeXt Block adapted from https://github.com/facebookresearch/ConvNeXt to 1D audio signal. Args: dim (int): Number of input channels. intermediate_dim (int): Dimensionality of the intermediate layer. layer_scale_init_value (float, optional): Initial value for the layer scale. None means no scaling. Defaults to None. adanorm_num_embeddings (int, optional): Number of embeddings for AdaLayerNorm. None means non-conditional LayerNorm. Defaults to None. """ def __init__( self, dim: int, intermediate_dim: int, layer_scale_init_value: float, adanorm_num_embeddings: Optional[int] = None, ): super().__init__() self.dwconv = nn.Conv1d( dim, dim, kernel_size=7, padding=3, groups=dim ) # depthwise conv self.adanorm = adanorm_num_embeddings is not None if adanorm_num_embeddings: self.norm = AdaLayerNorm(adanorm_num_embeddings, dim, eps=1e-6) else: self.norm = nn.LayerNorm(dim, eps=1e-6) self.pwconv1 = nn.Linear( dim, intermediate_dim ) # pointwise/1x1 convs, implemented with linear layers self.act = nn.GELU() self.pwconv2 = nn.Linear(intermediate_dim, dim) self.gamma = ( nn.Parameter(layer_scale_init_value * torch.ones(dim), requires_grad=True) if layer_scale_init_value > 0 else None ) def forward( self, x: torch.Tensor, cond_embedding_id: Optional[torch.Tensor] = None ) -> torch.Tensor: residual = x x = self.dwconv(x) x = x.transpose(1, 2) # (B, C, T) -> (B, T, C) if self.adanorm: assert cond_embedding_id is not None x = self.norm(x, cond_embedding_id) else: x = self.norm(x) x = self.pwconv1(x) x = self.act(x) x = self.pwconv2(x) if self.gamma is not None: x = self.gamma * x x = x.transpose(1, 2) # (B, T, C) -> (B, C, T) x = residual + x return x class AdaLayerNorm(nn.Module): """ Adaptive Layer Normalization module with learnable embeddings per `num_embeddings` classes Args: num_embeddings (int): Number of embeddings. embedding_dim (int): Dimension of the embeddings. """ def __init__(self, num_embeddings: int, embedding_dim: int, eps: float = 1e-6): super().__init__() self.eps = eps self.dim = embedding_dim self.scale = nn.Embedding( num_embeddings=num_embeddings, embedding_dim=embedding_dim ) self.shift = nn.Embedding( num_embeddings=num_embeddings, embedding_dim=embedding_dim ) torch.nn.init.ones_(self.scale.weight) torch.nn.init.zeros_(self.shift.weight) def forward(self, x: torch.Tensor, cond_embedding_id: torch.Tensor) -> torch.Tensor: scale = self.scale(cond_embedding_id) shift = self.shift(cond_embedding_id) x = nn.functional.layer_norm(x, (self.dim,), eps=self.eps) x = x * scale + shift return x class Encoder(nn.Module): def __init__( self, d_latent: int = 64, n_fft: int = 2048, hop_size: int = 480, n_bands: int = 64, sample_rate: int = 48000, nf_dim: int = 32, depth: int = 4, n_heads: int = 8, ): super().__init__() d_model = nf_dim * 2 * 2 # stft parameters self.n_fft = n_fft self.hop_size = hop_size self.window = torch.hann_window(n_fft) # neural filterbank self.neural_filterbank = NeuralFilterbank( n_fft=n_fft, n_filterbank=n_bands, sample_rate=sample_rate, out_dim=nf_dim, ) # frequency attention self.depth = depth transformer_kwargs = dict( dim=d_model, heads=n_heads, dim_head=d_model // n_heads, attn_dropout=0.0, ff_dropout=0.0, flash_attn=True, norm_output=False, ) rotary_emb = RotaryEmbedding(dim=d_model // n_heads) freq_attn = [] for _ in range(depth): freq_attn.append( Transformer( depth=1, rotary_embed=rotary_emb, **transformer_kwargs ).bfloat16() ) self.freq_attn = nn.ModuleList(freq_attn) # Convnext self.conv_next = nn.ModuleList( [ ConvNeXtBlock( dim=d_model, intermediate_dim=d_model * 3, layer_scale_init_value=1 / depth, adanorm_num_embeddings=None, ) for _ in range(depth) ] ) # last conv self.prj = [ Snake1d(d_model * n_bands), WNConv1d(d_model * n_bands, d_latent, kernel_size=3, padding=1), ] self.prj = nn.Sequential(*self.prj) self.enc_dim = d_model def forward(self, x): """ einops b: batch f: frequency d: feature dimension s: stereo c: complex """ # reshape audio b, s, _ = x.shape x = rearrange(x, "b s t -> (b s) t") # short-time Fourier transform (b s) t -> (b s) f t c self.window = self.window.to(x.device) spec = torch.stft( x, n_fft=self.n_fft, hop_length=self.hop_size, win_length=self.n_fft, window=self.window, onesided=True, return_complex=True, ) spec = torch.view_as_real(spec) t = spec.shape[-2] # neural filterbank (b s) f t c -> (b s) (c d) f' t) spec = rearrange(spec, "(b s) f t c -> (b s c) f t", b=b, s=s, t=t, c=2) nf_spec = self.neural_filterbank(spec) out = rearrange(nf_spec, "(b s c) d f t -> (b t) f (s c d)", b=b, s=s, t=t, c=2) # frequency attention + convnext for i in range(self.depth): out = self.freq_attn[i](out.bfloat16()) out = rearrange( out.float(), "(b t) f (s c d) -> (b f) (s c d) t", b=b, s=s, t=t, c=2 ).float() out = self.conv_next[i](out) out = rearrange( out, "(b f) (s c d) t -> (b t) f (s c d)", b=b, s=s, t=t, c=2 ) # rearrange out = rearrange(out, "(b t) f (s c d) -> b (s f c d) t", b=b, s=s, t=t, c=2) # projection b (s f c d) t -> b c' t out = self.prj(out) return out class Decoder(nn.Module): def __init__( self, input_channel, channels, n_bands: int = 1025, n_fft: int = 2048, hop_size: int = 480, depth: int = 4, n_heads: int = 8, ): super().__init__() # stft parameters self.n_fft = n_fft self.hop_size = hop_size self.window = torch.hann_window(n_fft) self.n_bands = n_bands self.n_dim = channels # First conv layer d_model = channels * 2 * 2 self.inp_layer = WNConv1d( input_channel, d_model * n_bands, kernel_size=7, padding=3 ) # Convnext self.conv_next = nn.ModuleList( [ ConvNeXtBlock( dim=d_model, intermediate_dim=d_model * 3, layer_scale_init_value=1 / depth, adanorm_num_embeddings=None, ) for _ in range(depth) ] ) # frequency attention self.depth = depth transformer_kwargs = dict( dim=d_model, heads=n_heads, dim_head=d_model // n_heads, attn_dropout=0.0, ff_dropout=0.0, flash_attn=True, norm_output=False, ) rotary_emb = RotaryEmbedding(dim=d_model // n_heads) freq_attn = [] for _ in range(depth): freq_attn.append( Transformer( depth=1, rotary_embed=rotary_emb, **transformer_kwargs ).bfloat16() ) self.freq_attn = nn.ModuleList(freq_attn) # Final conv layer self.prj = [Snake1d(d_model), WNConv1d(d_model, 4, kernel_size=7, padding=3)] self.prj = nn.Sequential(*self.prj) def forward(self, x): # projection b c' t -> (b f) (s c d) t b, _, t = x.shape out = self.inp_layer(x) out = rearrange( out, "b (s f c d) t -> (b f) (s c d) t", s=2, f=self.n_bands, c=2, d=self.n_dim, ) # convnext + frequency attention for i in range(self.depth): out = self.conv_next[i](out) out = rearrange( out, "(b f) (s c d) t -> (b t) f (s c d)", b=b, s=2, t=t, c=2 ) out = self.freq_attn[i](out.bfloat16()) out = rearrange( out.float(), "(b t) f (s c d) -> (b f) (s c d) t", b=b, s=2, t=t, c=2 ).float() # projection out = rearrange(out, "(b f) (s c d) t -> b (s c d) (f t)", b=b, s=2, t=t, c=2) out = self.prj(out) out = rearrange(out, "b (s c) (f t) -> (b s) c f t", b=b, s=2, t=t, c=2) # stft params mag = out[:, 0, :] p = out[:, 1, :] mag = torch.exp(mag) mag = torch.clip( mag, max=1e2 ) # safeguard to prevent excessively large magnitudes # wrapping happens here. These two lines produce real and imaginary value x = torch.cos(p) y = torch.sin(p) # recalculating phase here does not produce anything new # only costs time # phase = torch.atan2(y, x) # S = mag * torch.exp(phase * 1j) # better directly produce the complex value S = mag * (x + 1j * y) # iSTFT self.window = self.window.to(S.device) audio = torch.istft( S, self.n_fft, self.hop_size, self.n_fft, self.window, center=True, ) # reshape audio = rearrange(audio, "(b s) t -> b s t", s=2) return audio class DAC(BaseModel, CodecMixin): def __init__( self, encoder_dim: int = 64, encoder_heads: int = 8, latent_dim: int = None, n_bands: int = 64, decoder_dim: int = 64, decoder_heads: int = 8, n_codebooks: int = 8, n_codebooks_per_level: list[int] = [2, 2, 2, 2], codebook_size: int = 1024, codebook_dim: Union[int, list] = 8, quantizer_dropout: bool = False, sample_rate: int = 48000, hop_size: int = 480, quantizer_type: str = "rvq", use_vae: bool = False, residual_type: str = "subtract", enc_depth: int = 4, dec_depth: int = 4, ): super().__init__() self.encoder_dim = encoder_dim self.decoder_dim = decoder_dim self.sample_rate = sample_rate self.quantizer_type = quantizer_type self.use_vae = use_vae self.residual_type = residual_type if use_vae and quantizer_type != "rvq": raise NotImplementedError self.enc_depth = enc_depth self.dec_depth = dec_depth if latent_dim is None: latent_dim = encoder_dim * (2 ** len(5)) self.latent_dim = latent_dim self.hop_length = hop_size self.encoder = Encoder( d_latent=latent_dim, n_bands=n_bands, nf_dim=encoder_dim, depth=enc_depth, n_heads=encoder_heads, ) self.n_codebooks = n_codebooks self.n_codebooks_per_level = n_codebooks_per_level self.codebook_size = codebook_size self.codebook_dim = codebook_dim if quantizer_type == "rvq" or quantizer_type == "rlfq": self.quantizer = ResidualVectorQuantize( input_dim=latent_dim, n_codebooks=n_codebooks, codebook_size=codebook_size, codebook_dim=codebook_dim, quantizer_dropout=quantizer_dropout, use_vae=use_vae, residual_type=residual_type, use_lfq=quantizer_type == "rlfq", ) elif quantizer_type == "grvq": self.quantizer = GroupedResidualVectorQuantize( input_dim=latent_dim, n_codebooks_per_level=n_codebooks_per_level, codebook_size=codebook_size, codebook_dim=codebook_dim, quantizer_dropout=quantizer_dropout, ) elif quantizer_type == "passthrough": self.quantizer = PassthroughQuantize() elif quantizer_type == "lfq": assert isinstance(codebook_dim, int) self.quantizer = LFQ( codebook_size=codebook_size, num_codebooks=n_codebooks, dim=latent_dim, straight_through_activation=torch.tanh, ) elif quantizer_type == "grouped_lfq": self.quantizer = GroupedLFQ( codebook_size=codebook_size, num_quantizers=n_codebooks, dim=latent_dim, quantize_dropout=quantizer_dropout, ) elif quantizer_type == "vae": self.quantizer = VAEBottleneck(latent_dim=latent_dim, dim=codebook_dim) self.decoder = Decoder( latent_dim, decoder_dim, depth=dec_depth, n_heads=decoder_heads, ) self.sample_rate = sample_rate self.apply(init_weights) self.delay = self.get_delay() def preprocess(self, audio_data, sample_rate): if sample_rate is None: sample_rate = self.sample_rate assert sample_rate == self.sample_rate length = audio_data.shape[-1] right_pad = math.ceil(length / self.hop_length) * self.hop_length - length audio_data = nn.functional.pad(audio_data, (0, right_pad)) return audio_data def encode( self, audio_data: torch.Tensor, n_quantizers: int = None, ): """Encode given audio data and return quantized latent codes Parameters ---------- audio_data : Tensor[B x 1 x T] Audio data to encode n_quantizers : int, optional Number of quantizers to use, by default None If None, all quantizers are used. Returns ------- dict A dictionary with the following keys: "z" : Tensor[B x D x T] Quantized continuous representation of input "codes" : Tensor[B x N x T] Codebook indices for each codebook (quantized discrete representation of input) "latents" : Tensor[B x N*D x T] Projected latents (continuous representation of input before quantization) "vq/commitment_loss" : Tensor[1] Commitment loss to train encoder to predict vectors closer to codebook entries "vq/codebook_loss" : Tensor[1] Codebook loss to update the codebook "length" : int Number of samples in input audio """ z = self.encoder(audio_data) return self.quantizer(z, n_quantizers=n_quantizers) def decode(self, z: torch.Tensor): """Decode given latent codes and return audio data Parameters ---------- z : Tensor[B x D x T] Quantized continuous representation of input length : int, optional Number of samples in output audio, by default None Returns ------- dict A dictionary with the following keys: "audio" : Tensor[B x 1 x length] Decoded audio data. """ return self.decoder(z) def forward( self, audio_data: torch.Tensor, sample_rate: int = None, n_quantizers: int = None, ): """Model forward pass Parameters ---------- audio_data : Tensor[B x 1 x T] Audio data to encode sample_rate : int, optional Sample rate of audio data in Hz, by default None If None, defaults to `self.sample_rate` n_quantizers : int, optional Number of quantizers to use, by default None. If None, all quantizers are used. Returns ------- dict A dictionary with the following keys: "z" : Tensor[B x D x T] Quantized continuous representation of input "codes" : Tensor[B x N x T] Codebook indices for each codebook (quantized discrete representation of input) "latents" : Tensor[B x N*D x T] Projected latents (continuous representation of input before quantization) "vq/commitment_loss" : Tensor[1] Commitment loss to train encoder to predict vectors closer to codebook entries "vq/codebook_loss" : Tensor[1] Codebook loss to update the codebook "length" : int Number of samples in input audio "audio" : Tensor[B x 1 x length] Decoded audio data. """ audio_data = self.preprocess(audio_data, sample_rate) q_res = self.encode(audio_data, n_quantizers) x = self.decode(q_res["z"]) return { "audio": x, **q_res, } if __name__ == "__main__": import numpy as np from functools import partial model = DAC().to("cpu") for n, m in model.named_modules(): o = m.extra_repr() p = sum([np.prod(p.size()) for p in m.parameters()]) fn = lambda o, p: o + f" {p/1e6:<.3f}M params." setattr(m, "extra_repr", partial(fn, o=o, p=p)) print(model) print("Total # of params: ", sum([np.prod(p.size()) for p in model.parameters()])) length = 88200 * 2 x = torch.randn(1, 1, length).to(model.device) x.requires_grad_(True) x.retain_grad() # Make a forward pass out = model(x)["audio"] print("Input shape:", x.shape) print("Output shape:", out.shape) # Create gradient variable grad = torch.zeros_like(out) grad[:, :, grad.shape[-1] // 2] = 1 # Make a backward pass out.backward(grad) # Check non-zero values gradmap = x.grad.squeeze(0) gradmap = (gradmap != 0).sum(0) # sum across features rf = (gradmap != 0).sum() print(f"Receptive field: {rf.item()}") x = AudioSignal(torch.randn(1, 1, 44100 * 60), 44100) model.decompress(model.compress(x, verbose=True), verbose=True)