huseinzolkepliscicom commited on
Commit
259eeac
·
verified ·
1 Parent(s): 4aa51fc

Add neucodec package for self-contained inference

Browse files
neucodec/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .model import NeuCodec
neucodec/activations.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Implementation adapted from https://github.com/EdwardDixon/snake under the MIT license.
2
+ # LICENSE is in incl_licenses directory.
3
+
4
+ import torch
5
+ from torch import nn, sin, pow
6
+ from torch.nn import Parameter
7
+
8
+
9
+ class Snake(nn.Module):
10
+ """
11
+ Implementation of a sine-based periodic activation function
12
+ Shape:
13
+ - Input: (B, C, T)
14
+ - Output: (B, C, T), same shape as the input
15
+ Parameters:
16
+ - alpha - trainable parameter
17
+ References:
18
+ - This activation function is from this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda:
19
+ https://arxiv.org/abs/2006.08195
20
+ Examples:
21
+ >>> a1 = snake(256)
22
+ >>> x = torch.randn(256)
23
+ >>> x = a1(x)
24
+ """
25
+
26
+ def __init__(
27
+ self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False
28
+ ):
29
+ """
30
+ Initialization.
31
+ INPUT:
32
+ - in_features: shape of the input
33
+ - alpha: trainable parameter
34
+ alpha is initialized to 1 by default, higher values = higher-frequency.
35
+ alpha will be trained along with the rest of your model.
36
+ """
37
+ super(Snake, self).__init__()
38
+ self.in_features = in_features
39
+
40
+ # initialize alpha
41
+ self.alpha_logscale = alpha_logscale
42
+ if self.alpha_logscale: # log scale alphas initialized to zeros
43
+ self.alpha = Parameter(torch.zeros(in_features) * alpha)
44
+ else: # linear scale alphas initialized to ones
45
+ self.alpha = Parameter(torch.ones(in_features) * alpha)
46
+
47
+ self.alpha.requires_grad = alpha_trainable
48
+
49
+ self.no_div_by_zero = 0.000000001
50
+
51
+ def forward(self, x):
52
+ """
53
+ Forward pass of the function.
54
+ Applies the function to the input elementwise.
55
+ Snake ∶= x + 1/a * sin^2 (xa)
56
+ """
57
+ alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # line up with x to [B, C, T]
58
+ if self.alpha_logscale:
59
+ alpha = torch.exp(alpha)
60
+ x = x + (1.0 / (alpha + self.no_div_by_zero)) * pow(sin(x * alpha), 2)
61
+
62
+ return x
63
+
64
+
65
+ class SnakeBeta(nn.Module):
66
+ """
67
+ A modified Snake function which uses separate parameters for the magnitude of the periodic components
68
+ Shape:
69
+ - Input: (B, C, T)
70
+ - Output: (B, C, T), same shape as the input
71
+ Parameters:
72
+ - alpha - trainable parameter that controls frequency
73
+ - beta - trainable parameter that controls magnitude
74
+ References:
75
+ - This activation function is a modified version based on this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda:
76
+ https://arxiv.org/abs/2006.08195
77
+ Examples:
78
+ >>> a1 = snakebeta(256)
79
+ >>> x = torch.randn(256)
80
+ >>> x = a1(x)
81
+ """
82
+
83
+ def __init__(
84
+ self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False
85
+ ):
86
+ """
87
+ Initialization.
88
+ INPUT:
89
+ - in_features: shape of the input
90
+ - alpha - trainable parameter that controls frequency
91
+ - beta - trainable parameter that controls magnitude
92
+ alpha is initialized to 1 by default, higher values = higher-frequency.
93
+ beta is initialized to 1 by default, higher values = higher-magnitude.
94
+ alpha will be trained along with the rest of your model.
95
+ """
96
+ super(SnakeBeta, self).__init__()
97
+ self.in_features = in_features
98
+
99
+ # initialize alpha
100
+ self.alpha_logscale = alpha_logscale
101
+ if self.alpha_logscale: # log scale alphas initialized to zeros
102
+ self.alpha = Parameter(torch.zeros(in_features) * alpha)
103
+ self.beta = Parameter(torch.zeros(in_features) * alpha)
104
+ else: # linear scale alphas initialized to ones
105
+ self.alpha = Parameter(torch.ones(in_features) * alpha)
106
+ self.beta = Parameter(torch.ones(in_features) * alpha)
107
+
108
+ self.alpha.requires_grad = alpha_trainable
109
+ self.beta.requires_grad = alpha_trainable
110
+
111
+ self.no_div_by_zero = 0.000000001
112
+
113
+ def forward(self, x):
114
+ """
115
+ Forward pass of the function.
116
+ Applies the function to the input elementwise.
117
+ SnakeBeta ∶= x + 1/b * sin^2 (xa)
118
+ """
119
+ alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # line up with x to [B, C, T]
120
+ beta = self.beta.unsqueeze(0).unsqueeze(-1)
121
+ if self.alpha_logscale:
122
+ alpha = torch.exp(alpha)
123
+ beta = torch.exp(beta)
124
+ x = x + (1.0 / (beta + self.no_div_by_zero)) * pow(sin(x * alpha), 2)
125
+
126
+ return x
neucodec/alias_free_torch/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
2
+ # LICENSE is in incl_licenses directory.
3
+
4
+ from .filter import *
5
+ from .resample import *
6
+ from .act import *
neucodec/alias_free_torch/act.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
2
+ # LICENSE is in incl_licenses directory.
3
+
4
+ import torch.nn as nn
5
+ from .resample import UpSample1d, DownSample1d
6
+
7
+
8
+ class Activation1d(nn.Module):
9
+ def __init__(
10
+ self,
11
+ activation,
12
+ up_ratio: int = 2,
13
+ down_ratio: int = 2,
14
+ up_kernel_size: int = 12,
15
+ down_kernel_size: int = 12,
16
+ ):
17
+ super().__init__()
18
+ self.up_ratio = up_ratio
19
+ self.down_ratio = down_ratio
20
+ self.act = activation
21
+ self.upsample = UpSample1d(up_ratio, up_kernel_size)
22
+ self.downsample = DownSample1d(down_ratio, down_kernel_size)
23
+
24
+ # x: [B,C,T]
25
+ def forward(self, x):
26
+ x = self.upsample(x)
27
+ x = self.act(x)
28
+ x = self.downsample(x)
29
+
30
+ return x
neucodec/alias_free_torch/filter.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
2
+ # LICENSE is in incl_licenses directory.
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ import math
8
+
9
+ if "sinc" in dir(torch):
10
+ sinc = torch.sinc
11
+ else:
12
+ # This code is adopted from adefossez's julius.core.sinc under the MIT License
13
+ # https://adefossez.github.io/julius/julius/core.html
14
+ # LICENSE is in incl_licenses directory.
15
+ def sinc(x: torch.Tensor):
16
+ """
17
+ Implementation of sinc, i.e. sin(pi * x) / (pi * x)
18
+ __Warning__: Different to julius.sinc, the input is multiplied by `pi`!
19
+ """
20
+ return torch.where(
21
+ x == 0,
22
+ torch.tensor(1.0, device=x.device, dtype=x.dtype),
23
+ torch.sin(math.pi * x) / math.pi / x,
24
+ )
25
+
26
+
27
+ # This code is adopted from adefossez's julius.lowpass.LowPassFilters under the MIT License
28
+ # https://adefossez.github.io/julius/julius/lowpass.html
29
+ # LICENSE is in incl_licenses directory.
30
+ def kaiser_sinc_filter1d(
31
+ cutoff, half_width, kernel_size
32
+ ): # return filter [1,1,kernel_size]
33
+ even = kernel_size % 2 == 0
34
+ half_size = kernel_size // 2
35
+
36
+ # For kaiser window
37
+ delta_f = 4 * half_width
38
+ A = 2.285 * (half_size - 1) * math.pi * delta_f + 7.95
39
+ if A > 50.0:
40
+ beta = 0.1102 * (A - 8.7)
41
+ elif A >= 21.0:
42
+ beta = 0.5842 * (A - 21) ** 0.4 + 0.07886 * (A - 21.0)
43
+ else:
44
+ beta = 0.0
45
+ window = torch.kaiser_window(kernel_size, beta=beta, periodic=False)
46
+
47
+ # ratio = 0.5/cutoff -> 2 * cutoff = 1 / ratio
48
+ if even:
49
+ time = torch.arange(-half_size, half_size) + 0.5
50
+ else:
51
+ time = torch.arange(kernel_size) - half_size
52
+ if cutoff == 0:
53
+ filter_ = torch.zeros_like(time)
54
+ else:
55
+ filter_ = 2 * cutoff * window * sinc(2 * cutoff * time)
56
+ # Normalize filter to have sum = 1, otherwise we will have a small leakage
57
+ # of the constant component in the input signal.
58
+ filter_ /= filter_.sum()
59
+ filter = filter_.view(1, 1, kernel_size)
60
+
61
+ return filter
62
+
63
+
64
+ class LowPassFilter1d(nn.Module):
65
+ def __init__(
66
+ self,
67
+ cutoff=0.5,
68
+ half_width=0.6,
69
+ stride: int = 1,
70
+ padding: bool = True,
71
+ padding_mode: str = "replicate",
72
+ kernel_size: int = 12,
73
+ ):
74
+ # kernel_size should be even number for stylegan3 setup,
75
+ # in this implementation, odd number is also possible.
76
+ super().__init__()
77
+ if cutoff < -0.0:
78
+ raise ValueError("Minimum cutoff must be larger than zero.")
79
+ if cutoff > 0.5:
80
+ raise ValueError("A cutoff above 0.5 does not make sense.")
81
+ self.kernel_size = kernel_size
82
+ self.even = kernel_size % 2 == 0
83
+ self.pad_left = kernel_size // 2 - int(self.even)
84
+ self.pad_right = kernel_size // 2
85
+ self.stride = stride
86
+ self.padding = padding
87
+ self.padding_mode = padding_mode
88
+ filter = kaiser_sinc_filter1d(cutoff, half_width, kernel_size)
89
+ self.register_buffer("filter", filter)
90
+
91
+ # input [B, C, T]
92
+ def forward(self, x):
93
+ _, C, _ = x.shape
94
+
95
+ if self.padding:
96
+ x = F.pad(x, (self.pad_left, self.pad_right), mode=self.padding_mode)
97
+ out = F.conv1d(x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C)
98
+
99
+ return out
neucodec/alias_free_torch/resample.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
2
+ # LICENSE is in incl_licenses directory.
3
+
4
+ import torch.nn as nn
5
+ from torch.nn import functional as F
6
+ from .filter import LowPassFilter1d
7
+ from .filter import kaiser_sinc_filter1d
8
+
9
+
10
+ class UpSample1d(nn.Module):
11
+ def __init__(self, ratio=2, kernel_size=None):
12
+ super().__init__()
13
+ self.ratio = ratio
14
+ self.kernel_size = (
15
+ int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size
16
+ )
17
+ self.stride = ratio
18
+ self.pad = self.kernel_size // ratio - 1
19
+ self.pad_left = self.pad * self.stride + (self.kernel_size - self.stride) // 2
20
+ self.pad_right = (
21
+ self.pad * self.stride + (self.kernel_size - self.stride + 1) // 2
22
+ )
23
+ filter = kaiser_sinc_filter1d(
24
+ cutoff=0.5 / ratio, half_width=0.6 / ratio, kernel_size=self.kernel_size
25
+ )
26
+ self.register_buffer("filter", filter)
27
+
28
+ # x: [B, C, T]
29
+ def forward(self, x):
30
+ _, C, _ = x.shape
31
+
32
+ x = F.pad(x, (self.pad, self.pad), mode="replicate")
33
+ x = self.ratio * F.conv_transpose1d(
34
+ x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C
35
+ )
36
+ x = x[..., self.pad_left : -self.pad_right]
37
+
38
+ return x
39
+
40
+
41
+ class DownSample1d(nn.Module):
42
+ def __init__(self, ratio=2, kernel_size=None):
43
+ super().__init__()
44
+ self.ratio = ratio
45
+ self.kernel_size = (
46
+ int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size
47
+ )
48
+ self.lowpass = LowPassFilter1d(
49
+ cutoff=0.5 / ratio,
50
+ half_width=0.6 / ratio,
51
+ stride=ratio,
52
+ kernel_size=self.kernel_size,
53
+ )
54
+
55
+ def forward(self, x):
56
+ xx = self.lowpass(x)
57
+
58
+ return xx
neucodec/bs_roformer5.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from einops import rearrange
4
+ from torchtune.modules import RotaryPositionalEmbeddings
5
+
6
+
7
+ class RMSNorm(torch.nn.Module):
8
+ def __init__(self, dim: int, eps: float = 1e-6):
9
+ r"""https://github.com/meta-llama/llama/blob/main/llama/model.py"""
10
+ super().__init__()
11
+ self.eps = eps
12
+ self.weight = nn.Parameter(torch.ones(dim))
13
+
14
+ def forward(self, x):
15
+ norm_x = torch.mean(x**2, dim=-1, keepdim=True)
16
+ output = x * torch.rsqrt(norm_x + self.eps) * self.weight
17
+ return output
18
+
19
+
20
+ class MLP(nn.Module):
21
+ def __init__(self, dim: int) -> None:
22
+ super().__init__()
23
+
24
+ self.fc1 = nn.Linear(dim, 4 * dim, bias=False)
25
+ self.silu = nn.SiLU()
26
+ self.fc2 = nn.Linear(4 * dim, dim, bias=False)
27
+
28
+ def forward(self, x):
29
+ x = self.fc1(x)
30
+ x = self.silu(x)
31
+ x = self.fc2(x)
32
+ return x
33
+
34
+
35
+ class Attention(nn.Module):
36
+ def __init__(
37
+ self, dim: int, n_heads: int, rotary_embed: RotaryPositionalEmbeddings
38
+ ):
39
+ super().__init__()
40
+
41
+ assert dim % n_heads == 0
42
+
43
+ self.n_heads = n_heads
44
+ self.dim = dim
45
+ self.rotary_embed = rotary_embed
46
+
47
+ self.flash = hasattr(torch.nn.functional, "scaled_dot_product_attention")
48
+ assert self.flash, "Must have flash attention."
49
+
50
+ self.c_attn = nn.Linear(dim, 3 * dim, bias=False)
51
+ self.c_proj = nn.Linear(dim, dim, bias=False)
52
+
53
+ def forward(self, x):
54
+ r"""
55
+ Args:
56
+ x: (b, t, h*d)
57
+
58
+ Constants:
59
+ b: batch_size
60
+ t: time steps
61
+ r: 3
62
+ h: heads_num
63
+ d: heads_dim
64
+ """
65
+ B, T, C = x.size()
66
+
67
+ q, k, v = rearrange(
68
+ self.c_attn(x), "b t (r h d) -> r b h t d", r=3, h=self.n_heads
69
+ )
70
+ # q, k, v: (b, h, t, d)
71
+
72
+ q = self.rotary_embed(q)
73
+ k = self.rotary_embed(k)
74
+
75
+ if self.flash:
76
+ y = torch.nn.functional.scaled_dot_product_attention(
77
+ q, k, v, attn_mask=None, dropout_p=0, is_causal=False
78
+ )
79
+
80
+ y = rearrange(y, "b h t d -> b t (h d)")
81
+
82
+ y = self.c_proj(y)
83
+ # shape: (b, t, h*d)
84
+
85
+ return y
86
+
87
+
88
+ class TransformerBlock(nn.Module):
89
+ def __init__(
90
+ self, dim: int, n_heads: int, rotary_embed: RotaryPositionalEmbeddings
91
+ ):
92
+ super().__init__()
93
+ self.dim = dim
94
+ self.n_heads = n_heads
95
+
96
+ self.att_norm = RMSNorm(dim)
97
+ self.ffn_norm = RMSNorm(dim)
98
+ self.att = Attention(dim=dim, n_heads=n_heads, rotary_embed=rotary_embed)
99
+ self.mlp = MLP(dim=dim)
100
+
101
+ def forward(
102
+ self,
103
+ x: torch.Tensor,
104
+ ):
105
+ x = x + self.att(self.att_norm(x))
106
+ x = x + self.mlp(self.ffn_norm(x))
107
+ return x
108
+
109
+
110
+ if __name__ == "__main__":
111
+ rotary_embed_128 = RotaryPositionalEmbeddings(dim=128)
112
+ transformer_block = TransformerBlock(
113
+ dim=1024, n_heads=8, rotary_embed=rotary_embed_128
114
+ )
115
+ x = torch.randn(2, 128, 1024)
116
+ y = transformer_block(x)
117
+ print(y.shape)
118
+ c = 1
neucodec/codec_decoder_vocos.py ADDED
@@ -0,0 +1,431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+ from typing import List
5
+ from torchtune.modules import RotaryPositionalEmbeddings
6
+ from vector_quantize_pytorch import ResidualFSQ
7
+
8
+ from .bs_roformer5 import TransformerBlock
9
+
10
+
11
+ class ISTFT(nn.Module):
12
+ """
13
+ Custom implementation of ISTFT since torch.istft doesn't allow custom padding (other than `center=True`) with
14
+ windowing. This is because the NOLA (Nonzero Overlap Add) check fails at the edges.
15
+ See issue: https://github.com/pytorch/pytorch/issues/62323
16
+ Specifically, in the context of neural vocoding we are interested in "same" padding analogous to CNNs.
17
+ The NOLA constraint is met as we trim padded samples anyway.
18
+
19
+ Args:
20
+ n_fft (int): Size of Fourier transform.
21
+ hop_length (int): The distance between neighboring sliding window frames.
22
+ win_length (int): The size of window frame and STFT filter.
23
+ padding (str, optional): Type of padding. Options are "center" or "same". Defaults to "same".
24
+ """
25
+
26
+ def __init__(
27
+ self, n_fft: int, hop_length: int, win_length: int, padding: str = "same"
28
+ ):
29
+ super().__init__()
30
+ if padding not in ["center", "same"]:
31
+ raise ValueError("Padding must be 'center' or 'same'.")
32
+ self.padding = padding
33
+ self.n_fft = n_fft
34
+ self.hop_length = hop_length
35
+ self.win_length = win_length
36
+ window = torch.hann_window(win_length)
37
+ self.register_buffer("window", window)
38
+
39
+ def forward(self, spec: torch.Tensor) -> torch.Tensor:
40
+ """
41
+ Compute the Inverse Short Time Fourier Transform (ISTFT) of a complex spectrogram.
42
+
43
+ Args:
44
+ spec (Tensor): Input complex spectrogram of shape (B, N, T), where B is the batch size,
45
+ N is the number of frequency bins, and T is the number of time frames.
46
+
47
+ Returns:
48
+ Tensor: Reconstructed time-domain signal of shape (B, L), where L is the length of the output signal.
49
+ """
50
+ if self.padding == "center":
51
+ # Fallback to pytorch native implementation
52
+ return torch.istft(
53
+ spec,
54
+ self.n_fft,
55
+ self.hop_length,
56
+ self.win_length,
57
+ self.window,
58
+ center=True,
59
+ )
60
+ elif self.padding == "same":
61
+ pad = (self.win_length - self.hop_length) // 2
62
+ else:
63
+ raise ValueError("Padding must be 'center' or 'same'.")
64
+
65
+ assert spec.dim() == 3, "Expected a 3D tensor as input"
66
+ B, N, T = spec.shape
67
+
68
+ # Inverse FFT
69
+ ifft = torch.fft.irfft(spec, self.n_fft, dim=1, norm="backward")
70
+ ifft = ifft * self.window[None, :, None]
71
+
72
+ # Overlap and Add
73
+ output_size = (T - 1) * self.hop_length + self.win_length
74
+ y = torch.nn.functional.fold(
75
+ ifft,
76
+ output_size=(1, output_size),
77
+ kernel_size=(1, self.win_length),
78
+ stride=(1, self.hop_length),
79
+ )[:, 0, 0, pad:-pad]
80
+
81
+ # Window envelope
82
+ window_sq = self.window.square().expand(1, T, -1).transpose(1, 2)
83
+ window_envelope = torch.nn.functional.fold(
84
+ window_sq,
85
+ output_size=(1, output_size),
86
+ kernel_size=(1, self.win_length),
87
+ stride=(1, self.hop_length),
88
+ ).squeeze()[pad:-pad]
89
+
90
+ # Normalize
91
+ assert (window_envelope > 1e-11).all()
92
+ y = y / window_envelope
93
+
94
+ return y
95
+
96
+
97
+ class FourierHead(nn.Module):
98
+ """Base class for inverse fourier modules."""
99
+
100
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
101
+ """
102
+ Args:
103
+ x (Tensor): Input tensor of shape (B, L, H), where B is the batch size,
104
+ L is the sequence length, and H denotes the model dimension.
105
+
106
+ Returns:
107
+ Tensor: Reconstructed time-domain audio signal of shape (B, T), where T is the length of the output signal.
108
+ """
109
+ raise NotImplementedError("Subclasses must implement the forward method.")
110
+
111
+
112
+ class ISTFTHead(FourierHead):
113
+ """
114
+ ISTFT Head module for predicting STFT complex coefficients.
115
+
116
+ Args:
117
+ dim (int): Hidden dimension of the model.
118
+ n_fft (int): Size of Fourier transform.
119
+ hop_length (int): The distance between neighboring sliding window frames, which should align with
120
+ the resolution of the input features.
121
+ padding (str, optional): Type of padding. Options are "center" or "same". Defaults to "same".
122
+ """
123
+
124
+ def __init__(self, dim: int, n_fft: int, hop_length: int, padding: str = "same"):
125
+ super().__init__()
126
+ out_dim = n_fft + 2
127
+ self.out = torch.nn.Linear(dim, out_dim)
128
+ self.istft = ISTFT(
129
+ n_fft=n_fft, hop_length=hop_length, win_length=n_fft, padding=padding
130
+ )
131
+
132
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
133
+ """
134
+ Forward pass of the ISTFTHead module.
135
+
136
+ Args:
137
+ x (Tensor): Input tensor of shape (B, L, H), where B is the batch size,
138
+ L is the sequence length, and H denotes the model dimension.
139
+
140
+ Returns:
141
+ Tensor: Reconstructed time-domain audio signal of shape (B, T), where T is the length of the output signal.
142
+ """
143
+ x_pred = self.out(x)
144
+ # x_pred = x
145
+ x_pred = x_pred.transpose(1, 2)
146
+ mag, p = x_pred.chunk(2, dim=1)
147
+ mag = torch.exp(mag)
148
+ mag = torch.clip(
149
+ mag, max=1e2
150
+ ) # safeguard to prevent excessively large magnitudes
151
+ # wrapping happens here. These two lines produce real and imaginary value
152
+ x = torch.cos(p)
153
+ y = torch.sin(p)
154
+ # recalculating phase here does not produce anything new
155
+ # only costs time
156
+ # phase = torch.atan2(y, x)
157
+ # S = mag * torch.exp(phase * 1j)
158
+ # better directly produce the complex value
159
+ S = mag * (x + 1j * y)
160
+ audio = self.istft(S)
161
+ return audio.unsqueeze(1), x_pred
162
+
163
+
164
+ def nonlinearity(x):
165
+ # swish
166
+ return x * torch.sigmoid(x)
167
+
168
+
169
+ def Normalize(in_channels, num_groups=32):
170
+ return torch.nn.GroupNorm(
171
+ num_groups=num_groups, num_channels=in_channels, eps=1e-6, affine=True
172
+ )
173
+
174
+
175
+ class ResnetBlock(nn.Module):
176
+ def __init__(
177
+ self,
178
+ *,
179
+ in_channels,
180
+ out_channels=None,
181
+ conv_shortcut=False,
182
+ dropout,
183
+ temb_channels=512,
184
+ ):
185
+ super().__init__()
186
+ self.in_channels = in_channels
187
+ out_channels = in_channels if out_channels is None else out_channels
188
+ self.out_channels = out_channels
189
+ self.use_conv_shortcut = conv_shortcut
190
+
191
+ self.norm1 = Normalize(in_channels)
192
+ self.conv1 = torch.nn.Conv1d(
193
+ in_channels, out_channels, kernel_size=3, stride=1, padding=1
194
+ )
195
+ if temb_channels > 0:
196
+ self.temb_proj = torch.nn.Linear(temb_channels, out_channels)
197
+ self.norm2 = Normalize(out_channels)
198
+ self.dropout = torch.nn.Dropout(dropout)
199
+ self.conv2 = torch.nn.Conv1d(
200
+ out_channels, out_channels, kernel_size=3, stride=1, padding=1
201
+ )
202
+ if self.in_channels != self.out_channels:
203
+ if self.use_conv_shortcut:
204
+ self.conv_shortcut = torch.nn.Conv1d(
205
+ in_channels, out_channels, kernel_size=3, stride=1, padding=1
206
+ )
207
+ else:
208
+ self.nin_shortcut = torch.nn.Conv1d(
209
+ in_channels, out_channels, kernel_size=1, stride=1, padding=0
210
+ )
211
+
212
+ def forward(self, x, temb=None):
213
+ h = x
214
+ h = self.norm1(h)
215
+ h = nonlinearity(h)
216
+ h = self.conv1(h)
217
+
218
+ if temb is not None:
219
+ h = h + self.temb_proj(nonlinearity(temb))[:, :, None, None]
220
+
221
+ h = self.norm2(h)
222
+ h = nonlinearity(h)
223
+ h = self.dropout(h)
224
+ h = self.conv2(h)
225
+
226
+ if self.in_channels != self.out_channels:
227
+ if self.use_conv_shortcut:
228
+ x = self.conv_shortcut(x)
229
+ else:
230
+ x = self.nin_shortcut(x)
231
+
232
+ return x + h
233
+
234
+
235
+ class Backbone(nn.Module):
236
+ """Base class for the generator's backbone. It preserves the same temporal resolution across all layers."""
237
+
238
+ def forward(self, x: torch.Tensor, **kwargs) -> torch.Tensor:
239
+ """
240
+ Args:
241
+ x (Tensor): Input tensor of shape (B, C, L), where B is the batch size,
242
+ C denotes output features, and L is the sequence length.
243
+
244
+ Returns:
245
+ Tensor: Output of shape (B, L, H), where B is the batch size, L is the sequence length,
246
+ and H denotes the model dimension.
247
+ """
248
+ raise NotImplementedError("Subclasses must implement the forward method.")
249
+
250
+
251
+ class VocosBackbone(Backbone):
252
+ """
253
+ Vocos backbone module built with ConvNeXt blocks. Supports additional conditioning with Adaptive Layer Normalization
254
+
255
+ Args:
256
+ input_channels (int): Number of input features channels.
257
+ dim (int): Hidden dimension of the model.
258
+ intermediate_dim (int): Intermediate dimension used in ConvNeXtBlock.
259
+ num_layers (int): Number of ConvNeXtBlock layers.
260
+ layer_scale_init_value (float, optional): Initial value for layer scaling. Defaults to `1 / num_layers`.
261
+ adanorm_num_embeddings (int, optional): Number of embeddings for AdaLayerNorm.
262
+ None means non-conditional model. Defaults to None.
263
+ """
264
+
265
+ def __init__(self, hidden_dim=1024, depth=12, heads=16, pos_meb_dim=64):
266
+ super().__init__()
267
+
268
+ self.embed = nn.Conv1d(hidden_dim, hidden_dim, kernel_size=7, padding=3)
269
+
270
+ self.temb_ch = 0
271
+ block_in = hidden_dim
272
+ dropout = 0.1
273
+
274
+ prior_net: List[nn.Module] = [
275
+ ResnetBlock(
276
+ in_channels=block_in,
277
+ out_channels=block_in,
278
+ temb_channels=self.temb_ch,
279
+ dropout=dropout,
280
+ ),
281
+ ResnetBlock(
282
+ in_channels=block_in,
283
+ out_channels=block_in,
284
+ temb_channels=self.temb_ch,
285
+ dropout=dropout,
286
+ ),
287
+ ]
288
+ self.prior_net = nn.Sequential(*prior_net)
289
+
290
+ depth = depth
291
+ time_rotary_embed = RotaryPositionalEmbeddings(dim=pos_meb_dim)
292
+
293
+ transformer_blocks = [
294
+ TransformerBlock(
295
+ dim=hidden_dim, n_heads=heads, rotary_embed=time_rotary_embed
296
+ )
297
+ for _ in range(depth)
298
+ ]
299
+
300
+ self.transformers = nn.Sequential(*transformer_blocks)
301
+ self.final_layer_norm = nn.LayerNorm(hidden_dim, eps=1e-6)
302
+ post_net: List[nn.Module] = [
303
+ ResnetBlock(
304
+ in_channels=block_in,
305
+ out_channels=block_in,
306
+ temb_channels=self.temb_ch,
307
+ dropout=dropout,
308
+ ),
309
+ ResnetBlock(
310
+ in_channels=block_in,
311
+ out_channels=block_in,
312
+ temb_channels=self.temb_ch,
313
+ dropout=dropout,
314
+ ),
315
+ ]
316
+ self.post_net = nn.Sequential(*post_net)
317
+
318
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
319
+ x = x.transpose(1, 2)
320
+ x = self.embed(x)
321
+ x = self.prior_net(x)
322
+ x = x.transpose(1, 2)
323
+ x = self.transformers(x)
324
+ x = x.transpose(1, 2)
325
+ x = self.post_net(x)
326
+ x = x.transpose(1, 2)
327
+ x = self.final_layer_norm(x)
328
+ return x
329
+
330
+
331
+ def init_weights(m):
332
+ if isinstance(m, nn.Conv1d):
333
+ nn.init.trunc_normal_(m.weight, std=0.02)
334
+ nn.init.constant_(m.bias, 0)
335
+
336
+
337
+ class CodecDecoderVocos(nn.Module):
338
+ def __init__(
339
+ self,
340
+ hidden_dim=1024,
341
+ depth=12,
342
+ heads=16,
343
+ pos_meb_dim=64,
344
+ hop_length=320,
345
+ vq_num_quantizers=1,
346
+ vq_dim=2048, # 1024 2048
347
+ vq_commit_weight=0.25,
348
+ vq_weight_init=False,
349
+ vq_full_commit_loss=False,
350
+ codebook_size=16384,
351
+ codebook_dim=16,
352
+ ):
353
+ super().__init__()
354
+ self.hop_length = hop_length
355
+
356
+ self.quantizer = ResidualFSQ(
357
+ dim=vq_dim, levels=[4, 4, 4, 4, 4, 4, 4, 4], num_quantizers=1
358
+ )
359
+
360
+ self.backbone = VocosBackbone(
361
+ hidden_dim=hidden_dim, depth=depth, heads=heads, pos_meb_dim=pos_meb_dim
362
+ )
363
+
364
+ self.head = ISTFTHead(
365
+ dim=hidden_dim,
366
+ n_fft=self.hop_length * 4,
367
+ hop_length=self.hop_length,
368
+ padding="same",
369
+ )
370
+
371
+ self.reset_parameters()
372
+
373
+ def forward(self, x, vq=True):
374
+ if vq is True:
375
+ # x, q, commit_loss = self.quantizer(x)
376
+ x = x.permute(0, 2, 1)
377
+ x, q = self.quantizer(x)
378
+ x = x.permute(0, 2, 1)
379
+ q = q.permute(0, 2, 1)
380
+ return x, q, None
381
+ x = self.backbone(x)
382
+ x, _ = self.head(x)
383
+
384
+ return x, _
385
+
386
+ def vq2emb(self, vq):
387
+ self.quantizer = self.quantizer.eval()
388
+ x = self.quantizer.vq2emb(vq)
389
+ return x
390
+
391
+ def get_emb(self):
392
+ self.quantizer = self.quantizer.eval()
393
+ embs = self.quantizer.get_emb()
394
+ return embs
395
+
396
+ def inference_vq(self, vq):
397
+ x = vq[None, :, :]
398
+ x = self.model(x)
399
+ return x
400
+
401
+ def inference_0(self, x):
402
+ x, q, loss, perp = self.quantizer(x)
403
+ x = self.model(x)
404
+ return x, None
405
+
406
+ def inference(self, x):
407
+ x = self.model(x)
408
+ return x, None
409
+
410
+ def remove_weight_norm(self):
411
+ """Remove weight normalization module from all of the layers."""
412
+
413
+ def _remove_weight_norm(m):
414
+ try:
415
+ torch.nn.utils.remove_weight_norm(m)
416
+ except ValueError: # this module didn't have weight norm
417
+ return
418
+
419
+ self.apply(_remove_weight_norm)
420
+
421
+ def apply_weight_norm(self):
422
+ """Apply weight normalization module from all of the layers."""
423
+
424
+ def _apply_weight_norm(m):
425
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d):
426
+ torch.nn.utils.weight_norm(m)
427
+
428
+ self.apply(_apply_weight_norm)
429
+
430
+ def reset_parameters(self):
431
+ self.apply(init_weights)
neucodec/codec_encoder.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+
4
+ from torch import nn
5
+
6
+ from .module import WNConv1d, EncoderBlock
7
+ from .alias_free_torch import Activation1d
8
+ from . import activations
9
+
10
+
11
+ def init_weights(m):
12
+ if isinstance(m, nn.Conv1d):
13
+ nn.init.trunc_normal_(m.weight, std=0.02)
14
+ nn.init.constant_(m.bias, 0)
15
+
16
+
17
+ class CodecEncoder(nn.Module):
18
+ def __init__(
19
+ self,
20
+ ngf=48,
21
+ up_ratios=[2, 2, 4, 4, 5],
22
+ dilations=(1, 3, 9),
23
+ hidden_dim=1024,
24
+ depth=12,
25
+ heads=12,
26
+ pos_meb_dim=64,
27
+ ):
28
+ super().__init__()
29
+ self.hop_length = np.prod(up_ratios)
30
+ self.ngf = ngf
31
+ self.up_ratios = up_ratios
32
+
33
+ d_model = ngf
34
+ self.conv_blocks = [WNConv1d(1, d_model, kernel_size=7, padding=3)]
35
+
36
+ for i, stride in enumerate(up_ratios):
37
+ d_model *= 2
38
+ self.conv_blocks += [
39
+ EncoderBlock(d_model, stride=stride, dilations=dilations)
40
+ ]
41
+
42
+ self.conv_blocks = nn.Sequential(*self.conv_blocks)
43
+
44
+ self.conv_final_block = [
45
+ Activation1d(
46
+ activation=activations.SnakeBeta(d_model, alpha_logscale=True)
47
+ ),
48
+ WNConv1d(d_model, hidden_dim, kernel_size=3, padding=1),
49
+ ]
50
+ self.conv_final_block = nn.Sequential(*self.conv_final_block)
51
+
52
+ self.reset_parameters()
53
+
54
+ def forward(self, x):
55
+ x = self.conv_blocks(x)
56
+ x = self.conv_final_block(x)
57
+ x = x.permute(0, 2, 1)
58
+ return x
59
+
60
+ def inference(self, x):
61
+ return self.block(x)
62
+
63
+ def remove_weight_norm(self):
64
+ """Remove weight normalization module from all of the layers."""
65
+
66
+ def _remove_weight_norm(m):
67
+ try:
68
+ torch.nn.utils.remove_weight_norm(m)
69
+ except ValueError: # this module didn't have weight norm
70
+ return
71
+
72
+ self.apply(_remove_weight_norm)
73
+
74
+ def apply_weight_norm(self):
75
+ """Apply weight normalization module from all of the layers."""
76
+
77
+ def _apply_weight_norm(m):
78
+ if isinstance(m, nn.Conv1d):
79
+ torch.nn.utils.weight_norm(m)
80
+
81
+ self.apply(_apply_weight_norm)
82
+
83
+ def reset_parameters(self):
84
+ self.apply(init_weights)
neucodec/codec_encoder_distill.py ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import Tensor
3
+ from torch import nn
4
+ from local_attention.transformer import DynamicPositionBias, LocalMHA, FeedForward
5
+ from .distill_layers import ChannelNorm, Conv1d, Linear, GRN, Snake1d
6
+ from .tconv.t_first import FirstBlock
7
+
8
+
9
+ class LocalTrans(nn.Module):
10
+ def __init__(
11
+ self,
12
+ dim=512,
13
+ depth=6,
14
+ causal=True,
15
+ local_attn_window_size=512,
16
+ dim_head=64,
17
+ heads=8,
18
+ ff_mult=4,
19
+ attn_dropout=0.0,
20
+ ff_dropout=0.0,
21
+ use_dynamic_pos_bias=False,
22
+ qk_rmsnorm=False,
23
+ ):
24
+ super().__init__()
25
+
26
+ self.layers = nn.ModuleList([])
27
+
28
+ self.window_size = local_attn_window_size
29
+ self.use_rotary_pos_emb = not use_dynamic_pos_bias
30
+ self.dynamic_pos_bias = (
31
+ None
32
+ if self.use_rotary_pos_emb
33
+ else DynamicPositionBias(dim=dim // 2, heads=heads)
34
+ )
35
+
36
+ for _ in range(depth):
37
+ self.layers.append(
38
+ nn.ModuleList(
39
+ [
40
+ LocalMHA(
41
+ dim=dim,
42
+ dim_head=dim_head,
43
+ heads=heads,
44
+ dropout=attn_dropout,
45
+ causal=causal,
46
+ window_size=self.window_size,
47
+ use_xpos=False,
48
+ xpos_scale_base=None,
49
+ use_rotary_pos_emb=self.use_rotary_pos_emb,
50
+ prenorm=True,
51
+ qk_rmsnorm=qk_rmsnorm,
52
+ exact_windowsize=False,
53
+ ),
54
+ FeedForward(dim=dim, mult=ff_mult, dropout=ff_dropout),
55
+ ]
56
+ )
57
+ )
58
+
59
+ def forward(self, x, mask=None):
60
+ attn_bias = (
61
+ None
62
+ if self.use_rotary_pos_emb
63
+ else self.dynamic_pos_bias(self.window_size, self.window_size * 2)
64
+ )
65
+ for attn, ff in self.layers:
66
+ x = attn(x, mask=mask, attn_bias=attn_bias) + x
67
+ x = ff(x) + x
68
+
69
+ return x
70
+
71
+ @classmethod
72
+ def builder(
73
+ cls, feature_dim=128, depth=2, local_window_size=200, use_dynamic_pos_bias=False
74
+ ):
75
+ return cls(
76
+ dim=feature_dim,
77
+ depth=depth,
78
+ dim_head=feature_dim // 4,
79
+ heads=6,
80
+ ff_mult=4,
81
+ causal=True,
82
+ local_attn_window_size=local_window_size,
83
+ use_dynamic_pos_bias=use_dynamic_pos_bias,
84
+ )
85
+
86
+
87
+ class LocalEncoder(nn.Module):
88
+ def __init__(
89
+ self,
90
+ feature_dim=128,
91
+ depth=2,
92
+ local_window_size=200,
93
+ use_dynamic_pos_bias=False,
94
+ ):
95
+ super().__init__()
96
+ self.local_trans = LocalTrans.builder(
97
+ feature_dim=feature_dim,
98
+ depth=depth,
99
+ local_window_size=local_window_size,
100
+ use_dynamic_pos_bias=use_dynamic_pos_bias,
101
+ )
102
+
103
+ def forward(self, feature):
104
+ """
105
+ Args:
106
+ feature: (B, C, T)
107
+ Returns:
108
+ local_feature: (B, T, C)
109
+ """
110
+ feature = feature.permute(0, 2, 1)
111
+ feature = self.local_trans(feature)
112
+ return feature
113
+
114
+
115
+ class DownTrans(nn.Module):
116
+ def __init__(
117
+ self, feature_dim=128, window_size=200, compress_rate=2, depth=2, **kwargs
118
+ ):
119
+ super().__init__()
120
+ assert window_size % compress_rate == 0
121
+ self.feature_dim = feature_dim
122
+ self.compress_rate = compress_rate
123
+ self.trans = LocalTrans.builder(
124
+ feature_dim, local_window_size=window_size, depth=depth, **kwargs
125
+ )
126
+ self.down_layer = Conv1d(
127
+ feature_dim, feature_dim, kernel_size=compress_rate, stride=compress_rate
128
+ )
129
+
130
+ def forward(self, x):
131
+ x = self.trans(x)
132
+ # x = x[:, ::self.compress_rate, :] # v1
133
+ x = self.down_layer(x.permute(0, 2, 1)).permute(0, 2, 1) # v2
134
+ return x
135
+
136
+
137
+ class CompressedLocalEncoderWithCache(nn.Module):
138
+ def __init__(
139
+ self,
140
+ feature_dim=128,
141
+ local_window_size=200,
142
+ compress_rate=2,
143
+ cache_size=3,
144
+ depth=4,
145
+ **kwargs,
146
+ ):
147
+ super().__init__()
148
+ self.local_window_size = local_window_size
149
+ self.cache_size = cache_size
150
+ self.compress_rate = compress_rate
151
+ self.trans_window_size = local_window_size + cache_size
152
+
153
+ self.cache_token = nn.Parameter(
154
+ torch.randn(1, self.cache_size * self.compress_rate, feature_dim)
155
+ )
156
+
157
+ self.down_trans = DownTrans(
158
+ feature_dim,
159
+ window_size=self.trans_window_size * compress_rate,
160
+ compress_rate=compress_rate,
161
+ depth=2,
162
+ **kwargs,
163
+ )
164
+
165
+ self.local_trans = LocalTrans.builder(
166
+ feature_dim,
167
+ local_window_size=self.trans_window_size,
168
+ depth=depth - 2,
169
+ **kwargs,
170
+ )
171
+
172
+ def forward(self, feature):
173
+ feature = feature.permute(0, 2, 1)
174
+ split_feature = torch.split(
175
+ feature, self.local_window_size * self.compress_rate, dim=1
176
+ )
177
+ cache_token = self.cache_token.expand(feature.shape[0], -1, -1)
178
+ feature = torch.cat(
179
+ [
180
+ f
181
+ for fs in split_feature
182
+ for f in (
183
+ cache_token,
184
+ fs,
185
+ )
186
+ ],
187
+ dim=1,
188
+ )
189
+ # assert feature[:, self.down_trans_window_size: 2*self.down_trans_window_size, :].equal(
190
+ # feature.reshape(B, -1, self.down_trans_window_size, C)[:, 1, :, :])
191
+ feature = self.down_trans(feature)
192
+ feature = self.local_trans(feature)
193
+ return feature
194
+
195
+
196
+ class ConvUnit(nn.Module):
197
+ """
198
+ Args:
199
+ dim (int): Number of input channels.
200
+ """
201
+
202
+ def __init__(self, dim, snake_act=True, norm=False, dilation=1, kernel_size=7):
203
+ super().__init__()
204
+ total_pad = (kernel_size - 1) * dilation
205
+ self.dw_conv = Conv1d(
206
+ dim,
207
+ dim,
208
+ kernel_size=kernel_size,
209
+ dilation=dilation,
210
+ padding=total_pad // 2,
211
+ groups=dim,
212
+ ) # depth-wise conv
213
+
214
+ self.norm = (
215
+ ChannelNorm(dim, data_format="channels_last") if norm else nn.Identity()
216
+ )
217
+ self.pw_conv1 = Linear(
218
+ dim, 4 * dim
219
+ ) # point-wise/1x1 conv, implemented with linear layer
220
+
221
+ if snake_act:
222
+ self.act = Snake1d(4 * dim, data_format="channels_last")
223
+ else:
224
+ self.act = nn.GELU()
225
+ self.grn = GRN(4 * dim)
226
+ self.pw_conv2 = Linear(4 * dim, dim)
227
+
228
+ def forward(self, x):
229
+ x = self.dw_conv(x)
230
+ x = x.permute(0, 2, 1) # (N, C, T) -> (N, T, C)
231
+ x = self.norm(x)
232
+ x = self.pw_conv1(x)
233
+ x = self.act(x)
234
+ x = self.grn(x)
235
+ x = self.pw_conv2(x)
236
+ x = x.permute(0, 2, 1) # (N, T, C) -> (N, C, T)
237
+ return x
238
+
239
+
240
+ class Residual(nn.Module):
241
+ def __init__(
242
+ self, module: nn.Module, drop_prob: float = 0.0, scale_by_keep: bool = True
243
+ ):
244
+ super().__init__()
245
+ assert 0 <= drop_prob < 1
246
+ self.module = module
247
+ self.drop_prob = drop_prob
248
+ self.scale_by_keep = scale_by_keep
249
+
250
+ def drop_path(self, x_side: Tensor):
251
+ if self.drop_prob == 0.0 or not self.training:
252
+ return x_side
253
+ keep_prob = 1 - self.drop_prob
254
+ shape = (x_side.shape[0],) + (1,) * (x_side.ndim - 1)
255
+ keep_mask = x_side.new_empty(shape).bernoulli_(keep_prob)
256
+ if self.scale_by_keep:
257
+ keep_mask.div_(keep_prob)
258
+ return x_side * keep_mask
259
+
260
+ def forward(self, x: Tensor):
261
+ x_side = self.module(x)
262
+ x_side = self.drop_path(x_side)
263
+ return x + x_side
264
+
265
+
266
+ ResidualUnit = lambda *args, drop_rate=0.0, **kwargs: Residual(
267
+ ConvUnit(*args, **kwargs), drop_prob=drop_rate
268
+ )
269
+
270
+
271
+ class LegacyUnit(nn.Module):
272
+ def __init__(self, dim, snake_act=True, norm=False, dilation=1, kernel_size=7):
273
+ super().__init__()
274
+ assert snake_act, "LegacyUnit only supports snake_act=True"
275
+ assert norm == False, "LegacyUnit only supports norm=False"
276
+ total_pad = (kernel_size - 1) * dilation
277
+ self.block = nn.Sequential(
278
+ Snake1d(dim),
279
+ Conv1d(
280
+ dim,
281
+ dim,
282
+ kernel_size=kernel_size,
283
+ dilation=dilation,
284
+ padding=total_pad // 2,
285
+ ),
286
+ Snake1d(dim),
287
+ Conv1d(dim, dim, kernel_size=1),
288
+ )
289
+
290
+ def forward(self, x):
291
+ return self.block(x)
292
+
293
+
294
+ ResidualLegacyUnit = lambda *args, **kwargs: Residual(
295
+ LegacyUnit(*args, **kwargs), drop_prob=0.0
296
+ )
297
+
298
+ BaseUnit = ResidualUnit
299
+
300
+
301
+ class Encoder(nn.Module):
302
+ def __init__(
303
+ self,
304
+ feature_dim: int = 512,
305
+ strides: tuple = (2, 2, 2, 2),
306
+ depths: tuple = (1, 1, 1, 1, 1),
307
+ dims: tuple = (32, 64, 128, 256, 512),
308
+ drop_path_rate: float = 0.0,
309
+ use_norm=False,
310
+ use_snake_act=True,
311
+ ):
312
+ super().__init__()
313
+ # Create first convolution
314
+ blocks = [
315
+ # Conv1d(1, dims[0], kernel_size=7, padding=3),
316
+ FirstBlock(dims[0]),
317
+ ]
318
+
319
+ drop_path_rates = [
320
+ x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))
321
+ ]
322
+ cur = 0
323
+ for i_d, o_d, stride, depth in zip(dims[:-1], dims[1:], strides, depths):
324
+ stage = nn.Sequential(
325
+ *[
326
+ BaseUnit(
327
+ dim=i_d,
328
+ drop_rate=drop_path_rates[cur + j],
329
+ snake_act=use_snake_act,
330
+ norm=use_norm,
331
+ )
332
+ for j in range(depth)
333
+ ]
334
+ )
335
+ down_layer = nn.Sequential(
336
+ Conv1d(i_d, o_d, kernel_size=stride, stride=stride),
337
+ ChannelNorm(o_d, data_format="channels_first")
338
+ if use_norm
339
+ else nn.Identity(),
340
+ )
341
+ blocks += [stage, down_layer]
342
+ cur += depth
343
+
344
+ # Create last convolution
345
+ blocks += [
346
+ nn.Sequential(
347
+ *[
348
+ BaseUnit(
349
+ dim=dims[-1],
350
+ drop_rate=drop_path_rates[cur + j],
351
+ snake_act=use_snake_act,
352
+ norm=use_norm,
353
+ )
354
+ for j in range(depths[-1])
355
+ ]
356
+ ),
357
+ # Snake1d(dims[-1]),
358
+ Conv1d(dims[-1], feature_dim, kernel_size=3, padding=1),
359
+ ]
360
+
361
+ self.blocks = nn.Sequential(*blocks)
362
+
363
+ def forward(self, x):
364
+ return self.blocks(x)
365
+
366
+
367
+ class DistillCodecEncoder(nn.Module):
368
+ def __init__(self):
369
+ super().__init__()
370
+ self.encoder = Encoder(
371
+ feature_dim=512,
372
+ strides=(4, 4, 4, 4),
373
+ depths=(1, 1, 1, 2),
374
+ dims=(32, 64, 128, 256),
375
+ )
376
+ self.en_encoder = CompressedLocalEncoderWithCache(
377
+ feature_dim=512,
378
+ local_window_size=300,
379
+ compress_rate=5,
380
+ cache_size=0,
381
+ depth=5,
382
+ use_dynamic_pos_bias=True,
383
+ )
384
+
385
+ def forward(self, x):
386
+ x = self.encoder(x)
387
+ x = self.en_encoder(x)
388
+ return x
neucodec/distill_layers.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from torch import Tensor
5
+ from torch.nn.utils.parametrizations import weight_norm
6
+
7
+
8
+ def get_eps(data_type):
9
+ return torch.finfo(data_type).eps
10
+
11
+
12
+ EPS = get_eps(torch.float32)
13
+
14
+
15
+ def nn_wrapper(nn_class, norm_weight=True, init_weight=True):
16
+ def nn_builder(*args, **kwargs):
17
+ nn_instance = nn_class(*args, **kwargs)
18
+ if init_weight:
19
+ nn.init.trunc_normal_(nn_instance.weight, std=0.02)
20
+ nn.init.constant_(nn_instance.bias, 0)
21
+ if norm_weight:
22
+ nn_instance = weight_norm(nn_instance)
23
+ return nn_instance
24
+
25
+ return nn_builder
26
+
27
+
28
+ Conv1d = nn_wrapper(nn.Conv1d, norm_weight=True, init_weight=True)
29
+ Linear = nn_wrapper(nn.Linear, norm_weight=True, init_weight=True)
30
+
31
+
32
+ class Residual(nn.Module):
33
+ def __init__(
34
+ self, module: nn.Module, drop_prob: float = 0.0, scale_by_keep: bool = True
35
+ ):
36
+ super().__init__()
37
+ assert 0 <= drop_prob < 1
38
+ self.module = module
39
+ self.drop_prob = drop_prob
40
+ self.scale_by_keep = scale_by_keep
41
+
42
+ def drop_path(self, x_side: Tensor):
43
+ if self.drop_prob == 0.0 or not self.training:
44
+ return x_side
45
+ keep_prob = 1 - self.drop_prob
46
+ shape = (x_side.shape[0],) + (1,) * (x_side.ndim - 1)
47
+ keep_mask = x_side.new_empty(shape).bernoulli_(keep_prob)
48
+ if self.scale_by_keep:
49
+ keep_mask.div_(keep_prob)
50
+ return x_side * keep_mask
51
+
52
+ def forward(self, x: Tensor):
53
+ x_side = self.module(x)
54
+ x_side = self.drop_path(x_side)
55
+ return x + x_side
56
+
57
+
58
+ class GRN(nn.Module):
59
+ """GRN (Global Response Normalization) layer
60
+ Which supports two data formats: channels_last (default) or channels_first.
61
+ Channels_last corresponds to inputs with shape (batch_size, Sequence, channels)
62
+ while channels_first corresponds to inputs with shape (batch_size, channels, Sequence).
63
+ """
64
+
65
+ def __init__(self, n_channels, eps=EPS, data_format="channels_last"):
66
+ super().__init__()
67
+ self.n_channels = n_channels
68
+ self.data_format = data_format
69
+ if data_format == "channels_last":
70
+ self.gamma = nn.Parameter(torch.zeros(1, n_channels))
71
+ self.beta = nn.Parameter(torch.zeros(1, n_channels))
72
+ self.channel_dim = -1
73
+ elif data_format == "channels_first":
74
+ self.gamma = nn.Parameter(torch.zeros(n_channels, 1))
75
+ self.beta = nn.Parameter(torch.zeros(n_channels, 1))
76
+ self.channel_dim = 1
77
+ else:
78
+ raise ValueError(f"Unsupported data_format: {data_format}")
79
+ self.eps = torch.tensor(eps)
80
+
81
+ def forward(self, x):
82
+ g_x = torch.norm(x, p=2, dim=[1, 2], keepdim=True)
83
+ n_x = g_x / (g_x.mean(dim=self.channel_dim, keepdim=True) + self.eps)
84
+ return self.gamma * (x * n_x) + self.beta + x
85
+
86
+ def __repr__(self):
87
+ return f"{self.__class__.__name__}(n_channels={self.n_channels}, {self.data_format})"
88
+
89
+
90
+ # Scripting this brings model speed up 1.4x
91
+ @torch.jit.script
92
+ def snake(x, alpha):
93
+ # torch.clamp_(alpha, 0.05, 50.)
94
+ eps = 1.1920928955078125e-07
95
+ x = x + (alpha + eps).reciprocal() * torch.sin(alpha * x).pow(2)
96
+ return x
97
+
98
+
99
+ class Snake1d(nn.Module):
100
+ def __init__(self, channels, data_format="channels_first"):
101
+ super().__init__()
102
+ if data_format == "channels_first":
103
+ self.alpha = nn.Parameter(torch.ones(1, channels, 1))
104
+ elif data_format == "channels_last":
105
+ self.alpha = nn.Parameter(torch.ones(1, 1, channels))
106
+ else:
107
+ raise NotImplementedError
108
+
109
+ def forward(self, x):
110
+ return snake(x, self.alpha)
111
+
112
+
113
+ @torch.jit.script
114
+ def channel_norm(x, weight, bias, eps):
115
+ u = x.mean(1, keepdim=True)
116
+ s = (x - u).pow(2).mean(1, keepdim=True)
117
+ x = (x - u) / torch.sqrt(s + eps)
118
+ x = weight * x + bias
119
+ return x
120
+
121
+
122
+ class ChannelNorm(nn.Module):
123
+ """ChannelNorm that supports two data formats: channels_last (default) or channels_first.
124
+ Channels_last corresponds to inputs with shape (batch_size, ..., channels)
125
+ while channels_first corresponds to inputs with shape (batch_size, channels, ...).
126
+ """
127
+
128
+ def __init__(self, n_channels, eps=EPS, data_format="channels_last"):
129
+ super().__init__()
130
+ self.n_channels = n_channels
131
+ self.data_format = data_format
132
+ self.weight = nn.Parameter(torch.ones(n_channels))
133
+ self.bias = nn.Parameter(torch.zeros(n_channels))
134
+ self.eps = torch.tensor(eps)
135
+
136
+ def forward(self, x):
137
+ if self.data_format == "channels_first":
138
+ extend_dims = (1,) * len(x.shape[2:])
139
+ return channel_norm(
140
+ x,
141
+ self.weight.view(-1, *extend_dims),
142
+ self.bias.view(-1, *extend_dims),
143
+ self.eps,
144
+ )
145
+
146
+ elif self.data_format == "channels_last":
147
+ return F.layer_norm(
148
+ x, (self.n_channels,), self.weight, self.bias, self.eps.item()
149
+ )
150
+
151
+ else:
152
+ raise NotImplementedError
153
+
154
+ def __repr__(self):
155
+ return f"{self.__class__.__name__}(n_channels={self.n_channels}, {self.data_format})"
neucodec/model.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Dict
2
+ from pathlib import Path
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ import torchaudio
8
+ from torchaudio import transforms as T
9
+ from huggingface_hub import PyTorchModelHubMixin, ModelHubMixin, hf_hub_download
10
+ from transformers import AutoFeatureExtractor, HubertModel, Wav2Vec2BertModel
11
+
12
+ from .codec_encoder import CodecEncoder
13
+ from .codec_encoder_distill import DistillCodecEncoder
14
+ from .codec_decoder_vocos import CodecDecoderVocos
15
+ from .module import SemanticEncoder
16
+
17
+
18
+ class NeuCodec(
19
+ nn.Module,
20
+ PyTorchModelHubMixin,
21
+ repo_url="https://github.com/neuphonic/neucodec",
22
+ license="apache-2.0",
23
+ ):
24
+
25
+ def __init__(self, sample_rate: int, hop_length: int, decoder_depth: int = 12):
26
+ super().__init__()
27
+ self.sample_rate = sample_rate
28
+ self.hop_length = hop_length
29
+ self.semantic_model = Wav2Vec2BertModel.from_pretrained(
30
+ "facebook/w2v-bert-2.0", output_hidden_states=True
31
+ )
32
+ self.feature_extractor = AutoFeatureExtractor.from_pretrained(
33
+ "facebook/w2v-bert-2.0"
34
+ )
35
+ self.SemanticEncoder_module = SemanticEncoder(1024, 1024, 1024)
36
+ self.CodecEnc = CodecEncoder()
37
+ self.generator = CodecDecoderVocos(hop_length=hop_length, depth=decoder_depth)
38
+ self.fc_prior = nn.Linear(2048, 2048)
39
+ self.fc_post_a = nn.Linear(2048, 1024)
40
+
41
+ @property
42
+ def device(self):
43
+ return next(self.parameters()).device
44
+
45
+ @classmethod
46
+ def _from_pretrained(
47
+ cls,
48
+ *,
49
+ model_id: str = None,
50
+ revision: Optional[str] = None,
51
+ cache_dir: Optional[str] = None,
52
+ force_download: bool = False,
53
+ proxies: Optional[Dict] = None,
54
+ resume_download: bool = False,
55
+ local_files_only: bool = False,
56
+ token: Optional[str] = None,
57
+ map_location: str = "cpu",
58
+ strict: bool = False,
59
+ local_ckpt_path: str = None,
60
+ **model_kwargs,
61
+ ):
62
+ if model_id == "neuphonic/neucodec":
63
+ ignore_keys = ["fc_post_s", "SemanticDecoder"]
64
+ elif model_id == "neuphonic/distill-neucodec":
65
+ ignore_keys = []
66
+ else:
67
+ ignore_keys = []
68
+
69
+ if model_id is not None:
70
+ ckpt_path = hf_hub_download(
71
+ repo_id=model_id,
72
+ filename="pytorch_model.bin",
73
+ revision=revision,
74
+ cache_dir=cache_dir,
75
+ force_download=force_download,
76
+ proxies=proxies,
77
+ resume_download=resume_download,
78
+ local_files_only=local_files_only,
79
+ token=token,
80
+ )
81
+ else:
82
+ # incase we interpolate the weight to become 960 instead train from scratch
83
+ ckpt_path = local_ckpt_path
84
+
85
+ # initialize model
86
+ decoder_depth = model_kwargs.pop('decoder_depth', 12)
87
+ model = cls(44_100, 882, decoder_depth=decoder_depth)
88
+
89
+ # load weights
90
+ state_dict = torch.load(ckpt_path, map_location)
91
+ contains_list = lambda s, l: any(i in s for i in l)
92
+ state_dict = {
93
+ k:v for k, v in state_dict.items()
94
+ if not contains_list(k, ignore_keys)
95
+ }
96
+
97
+ # Filter out keys with shape mismatches (e.g. 48k model vs 24k checkpoint)
98
+ model_state = model.state_dict()
99
+ state_dict = {
100
+ k: v for k, v in state_dict.items()
101
+ if k in model_state and v.shape == model_state[k].shape
102
+ }
103
+
104
+ model.load_state_dict(state_dict, strict=False)
105
+
106
+ return model
107
+
108
+ def _prepare_audio(self, audio_or_path: torch.Tensor | Path | str):
109
+
110
+ # load from file
111
+ if isinstance(audio_or_path, (Path, str)):
112
+ y, sr = torchaudio.load(audio_or_path)
113
+ if sr != 16_000:
114
+ y, sr = (T.Resample(sr, 16_000)(y), 16_000)
115
+ y = y[None, :] # [1, T] -> [B, 1, T]
116
+
117
+ # ensure input tensor is of correct shape
118
+ elif isinstance(audio_or_path, torch.Tensor):
119
+ y = audio_or_path
120
+ if len(y.shape) == 3:
121
+ y = audio_or_path
122
+ else:
123
+ raise ValueError(
124
+ f"NeuCodec expects tensor audio input to be of shape [B, 1, T] -- received shape: {y.shape}"
125
+ )
126
+
127
+ # pad audio
128
+ pad_for_wav = 320 - (y.shape[-1] % 320)
129
+ y = torch.nn.functional.pad(y, (0, pad_for_wav))
130
+
131
+ return y
132
+
133
+ def encode_code(self, audio_or_path: torch.Tensor | Path | str) -> torch.Tensor:
134
+ """
135
+ Args:
136
+ audio_or_path: torch.Tensor [B, 1, T] | Path | str, input audio
137
+
138
+ Returns:
139
+ fsq_codes: torch.Tensor [B, 1, F], 50hz FSQ codes
140
+ """
141
+
142
+ # prepare inputs
143
+ y = self._prepare_audio(audio_or_path)
144
+ semantic_features = self.feature_extractor(
145
+ [w for w in y.squeeze(1).cpu()], sampling_rate=16_000, return_tensors="pt"
146
+ ).input_features.to(self.device)
147
+
148
+ # acoustic encoding
149
+ acoustic_emb = self.CodecEnc(y.to(self.device))
150
+ acoustic_emb = acoustic_emb.transpose(1, 2)
151
+
152
+ # semantic encoding
153
+ semantic_output = (
154
+ self.semantic_model(semantic_features).hidden_states[16].transpose(1, 2)
155
+ )
156
+ semantic_encoded = self.SemanticEncoder_module(semantic_output)
157
+
158
+ # concatenate embeddings
159
+ if acoustic_emb.shape[-1] != semantic_encoded.shape[-1]:
160
+ min_len = min(acoustic_emb.shape[-1], semantic_encoded.shape[-1])
161
+ acoustic_emb = acoustic_emb[:, :, :min_len]
162
+ semantic_encoded = semantic_encoded[:, :, :min_len]
163
+ concat_emb = torch.cat([semantic_encoded, acoustic_emb], dim=1)
164
+ concat_emb = self.fc_prior(concat_emb.transpose(1, 2)).transpose(1, 2)
165
+
166
+ # quantize
167
+ _, fsq_codes, _ = self.generator(concat_emb, vq=True)
168
+ return fsq_codes
169
+
170
+ def encode_code_from_features(self, audio: torch.Tensor, semantic_features: torch.Tensor) -> torch.Tensor:
171
+ """Encode using pre-computed semantic features, avoiding CPU feature extraction.
172
+
173
+ Args:
174
+ audio: torch.Tensor [B, 1, T], 16kHz input audio
175
+ semantic_features: torch.Tensor [B, seq_len, feat_dim], pre-computed features
176
+
177
+ Returns:
178
+ fsq_codes: torch.Tensor [B, 1, F], 50hz FSQ codes
179
+ """
180
+ y = self._prepare_audio(audio)
181
+ semantic_features = semantic_features.to(self.device)
182
+
183
+ # acoustic encoding
184
+ acoustic_emb = self.CodecEnc(y.to(self.device))
185
+ acoustic_emb = acoustic_emb.transpose(1, 2)
186
+
187
+ # semantic encoding
188
+ semantic_output = (
189
+ self.semantic_model(semantic_features).hidden_states[16].transpose(1, 2)
190
+ )
191
+ semantic_encoded = self.SemanticEncoder_module(semantic_output)
192
+
193
+ # concatenate embeddings
194
+ if acoustic_emb.shape[-1] != semantic_encoded.shape[-1]:
195
+ min_len = min(acoustic_emb.shape[-1], semantic_encoded.shape[-1])
196
+ acoustic_emb = acoustic_emb[:, :, :min_len]
197
+ semantic_encoded = semantic_encoded[:, :, :min_len]
198
+ concat_emb = torch.cat([semantic_encoded, acoustic_emb], dim=1)
199
+ concat_emb = self.fc_prior(concat_emb.transpose(1, 2)).transpose(1, 2)
200
+
201
+ # quantize
202
+ _, fsq_codes, _ = self.generator(concat_emb, vq=True)
203
+ return fsq_codes
204
+
205
+ def decode_code(self, fsq_codes: torch.Tensor) -> torch.Tensor:
206
+ """
207
+ Args:
208
+ fsq_codes: torch.Tensor [B, 1, F], 50hz FSQ codes
209
+
210
+ Returns:
211
+ recon: torch.Tensor [B, 1, T], reconstructed 48kHz audio
212
+ """
213
+
214
+ fsq_post_emb = self.generator.quantizer.get_output_from_indices(fsq_codes.transpose(1, 2))
215
+ fsq_post_emb = fsq_post_emb.transpose(1, 2)
216
+ fsq_post_emb = self.fc_post_a(fsq_post_emb.transpose(1, 2)).transpose(1, 2)
217
+ recon = self.generator(fsq_post_emb.transpose(1, 2), vq=False)[0]
218
+ return recon
neucodec/module.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+
3
+ from torch.nn.utils import weight_norm
4
+
5
+ from .activations import SnakeBeta
6
+ from .alias_free_torch import Activation1d
7
+
8
+
9
+ def WNConv1d(*args, **kwargs):
10
+ return weight_norm(nn.Conv1d(*args, **kwargs))
11
+
12
+
13
+ class ResidualUnit(nn.Module):
14
+ def __init__(self, dim: int = 16, dilation: int = 1):
15
+ super().__init__()
16
+ pad = ((7 - 1) * dilation) // 2
17
+ self.block = nn.Sequential(
18
+ Activation1d(activation=SnakeBeta(dim, alpha_logscale=True)),
19
+ WNConv1d(dim, dim, kernel_size=7, dilation=dilation, padding=pad),
20
+ Activation1d(activation=SnakeBeta(dim, alpha_logscale=True)),
21
+ WNConv1d(dim, dim, kernel_size=1),
22
+ )
23
+
24
+ def forward(self, x):
25
+ return x + self.block(x)
26
+
27
+
28
+ class EncoderBlock(nn.Module):
29
+ def __init__(self, dim: int = 16, stride: int = 1, dilations=(1, 3, 9)):
30
+ super().__init__()
31
+ runits = [ResidualUnit(dim // 2, dilation=d) for d in dilations]
32
+ self.block = nn.Sequential(
33
+ *runits,
34
+ Activation1d(activation=SnakeBeta(dim // 2, alpha_logscale=True)),
35
+ WNConv1d(
36
+ dim // 2,
37
+ dim,
38
+ kernel_size=2 * stride,
39
+ stride=stride,
40
+ padding=stride // 2 + stride % 2,
41
+ ),
42
+ )
43
+
44
+ def forward(self, x):
45
+ return self.block(x)
46
+
47
+
48
+ class SemanticEncoder(nn.Module):
49
+ def __init__(
50
+ self,
51
+ input_channels: int,
52
+ code_dim: int,
53
+ encode_channels: int,
54
+ kernel_size: int = 3,
55
+ bias: bool = True,
56
+ ):
57
+ super(SemanticEncoder, self).__init__()
58
+
59
+ self.initial_conv = nn.Conv1d(
60
+ in_channels=input_channels,
61
+ out_channels=encode_channels,
62
+ kernel_size=kernel_size,
63
+ stride=1,
64
+ padding=(kernel_size - 1) // 2,
65
+ bias=False,
66
+ )
67
+
68
+ self.residual_blocks = nn.Sequential(
69
+ nn.ReLU(inplace=True),
70
+ nn.Conv1d(
71
+ encode_channels,
72
+ encode_channels,
73
+ kernel_size=kernel_size,
74
+ stride=1,
75
+ padding=(kernel_size - 1) // 2,
76
+ bias=bias,
77
+ ),
78
+ nn.ReLU(inplace=True),
79
+ nn.Conv1d(
80
+ encode_channels,
81
+ encode_channels,
82
+ kernel_size=kernel_size,
83
+ stride=1,
84
+ padding=(kernel_size - 1) // 2,
85
+ bias=bias,
86
+ ),
87
+ )
88
+
89
+ self.final_conv = nn.Conv1d(
90
+ in_channels=encode_channels,
91
+ out_channels=code_dim,
92
+ kernel_size=kernel_size,
93
+ stride=1,
94
+ padding=(kernel_size - 1) // 2,
95
+ bias=False,
96
+ )
97
+
98
+ def forward(self, x):
99
+ x = self.initial_conv(x) # (Batch, Encode_channels, Length)
100
+ x = self.residual_blocks(x) + x # 残差连接
101
+ x = self.final_conv(x) # (Batch, Code_dim, Length)
102
+ return x
neucodec/tconv/__init__.py ADDED
File without changes
neucodec/tconv/base.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import einops
2
+ import torch
3
+ from torch import nn
4
+ import torch.nn.functional as F
5
+
6
+ from ..distill_layers import Conv1d
7
+
8
+
9
+ def trend_pool(x, kernel_size):
10
+ if kernel_size > 1:
11
+ pool_args = dict(kernel_size=kernel_size, stride=1, padding=kernel_size // 2)
12
+ return F.avg_pool1d(F.max_pool1d(x.abs(), **pool_args), **pool_args)
13
+ # return F.avg_pool1d(F.max_pool1d(x, **pool_args), **pool_args) # woabs
14
+ else:
15
+ return x
16
+
17
+
18
+ class TrendPool(nn.Module):
19
+ def __init__(self, kernel_size=5):
20
+ super().__init__()
21
+ self.kernel_size = kernel_size
22
+
23
+ def forward(self, x):
24
+ return trend_pool(x, self.kernel_size)
25
+
26
+
27
+ class FirstBlock(nn.Module):
28
+ def __init__(
29
+ self,
30
+ target_dim,
31
+ conv_kernels=(7, 7, 7, 7),
32
+ pool_kernels=(1, 3, 5, 9),
33
+ dilation_rate=2,
34
+ ):
35
+ super().__init__()
36
+ assert target_dim % len(pool_kernels) == 0
37
+ each_dim = target_dim // len(pool_kernels)
38
+ blocks = []
39
+ for conv_kernel, pool_kernel in zip(conv_kernels, pool_kernels):
40
+ conv_dilation = pool_kernel // dilation_rate + 1
41
+ conv_padding = (conv_kernel - 1) * conv_dilation // 2
42
+ blocks.append(
43
+ nn.Sequential(
44
+ TrendPool(pool_kernel),
45
+ Conv1d(
46
+ 1,
47
+ each_dim,
48
+ kernel_size=conv_kernel,
49
+ dilation=conv_dilation,
50
+ padding=conv_padding,
51
+ ),
52
+ )
53
+ )
54
+ self.blocks = nn.ModuleList(blocks)
55
+
56
+ def forward(self, x):
57
+ return torch.cat([block(x) for block in self.blocks], dim=1)
58
+
59
+
60
+ class EnhanceBlock(FirstBlock):
61
+ def __init__(self, dim):
62
+ super().__init__(4, conv_kernels=(7, 7, 7, 7), pool_kernels=(1, 3, 5, 9))
63
+ self.dim = dim
64
+ self.merge_layer = nn.Sequential(
65
+ # nn.LeakyReLU(), # ! if active or use InstanceNorm1d
66
+ nn.InstanceNorm1d(4, affine=True),
67
+ nn.Conv1d(4, 1, kernel_size=1),
68
+ )
69
+
70
+ def forward(self, x):
71
+ x = einops.rearrange(x, "b c t -> (b c) 1 t", c=self.dim)
72
+ y = super().forward(x)
73
+ y = self.merge_layer(y)
74
+ y = einops.rearrange(y, "(b c) 1 t -> b c t", c=self.dim)
75
+ return y # ! x + y or x + y * x
76
+
77
+
78
+ class SimpleEnhanceBlock(FirstBlock):
79
+ def __init__(self, dim):
80
+ super().__init__(4, conv_kernels=(7, 7, 7, 7), pool_kernels=(1, 3, 5, 9))
81
+ self.dim = dim
82
+ self.merge_layer = nn.Sequential(
83
+ # nn.LeakyReLU(), # ! if active or use InstanceNorm1d
84
+ nn.InstanceNorm1d(4, affine=True),
85
+ nn.Conv1d(4, self.dim, kernel_size=1),
86
+ )
87
+
88
+ def forward(self, x):
89
+ xi = x[:, :1, :]
90
+ yi = super().forward(xi)
91
+ y = self.merge_layer(yi)
92
+ return x + y * x
neucodec/tconv/t_first.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import nn
3
+
4
+ from ..distill_layers import Conv1d
5
+ from . import base
6
+
7
+
8
+ class V3FirstBlock(base.FirstBlock): # (1, 5, 11, 21, 45)
9
+ def __init__(
10
+ self,
11
+ target_dim,
12
+ conv_kernels=(7, 7, 7, 7, 7),
13
+ pool_kernels=(1, 5, 11, 21, 45),
14
+ dilation_rate=7,
15
+ ):
16
+ h_dim = len(pool_kernels) * 4
17
+ super().__init__(h_dim, conv_kernels, pool_kernels, dilation_rate=dilation_rate)
18
+ self.conv_1 = Conv1d(h_dim, h_dim * 4, kernel_size=1)
19
+ self.act = nn.GELU()
20
+ self.conv_2 = Conv1d(h_dim * 4 + 1, target_dim, kernel_size=1)
21
+
22
+ def forward(self, x):
23
+ h = super().forward(x)
24
+ h = self.conv_1(h)
25
+ h = self.act(h)
26
+ y = torch.cat([h, x], dim=1)
27
+ y = self.conv_2(y)
28
+ return y
29
+
30
+
31
+ FirstBlock = lambda dim: (
32
+ V3FirstBlock(
33
+ dim,
34
+ conv_kernels=(7, 7, 7, 7, 7),
35
+ pool_kernels=(1, 5, 11, 21, 45),
36
+ dilation_rate=99,
37
+ ) # fv36
38
+ )
neucodec/token_interpolator.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TokenInterpolator: Upsample low-rate (25/12 TPS) token embeddings back to 50 TPS.
3
+
4
+ The original 50 TPS NeuCodec codebook is completely frozen and unchanged.
5
+ This module operates purely in embedding space (after quantizer lookup).
6
+
7
+ Encode at 25 TPS:
8
+ audio -> NeuCodec.encode_code() -> 50 TPS codes [B, 1, T]
9
+ -> take every 2nd token -> 25 TPS codes [B, 1, T//2]
10
+
11
+ Decode from 25 TPS:
12
+ 25 TPS codes -> quantizer.get_output_from_indices -> 25 TPS embeddings [B, T//2, 1024]
13
+ -> TokenInterpolator(factor=2) -> 50 TPS embeddings [B, T, 1024]
14
+ -> NeuCodec decoder backbone + ISTFT -> audio
15
+
16
+ Training:
17
+ Freeze entire NeuCodec. Only train TokenInterpolator.
18
+ Loss: MSE on the predicted (odd-position) embeddings vs the true 50 TPS embeddings.
19
+ Optional: reconstruction loss via frozen decoder.
20
+ """
21
+
22
+ import torch
23
+ import torch.nn as nn
24
+ from torchtune.modules import RotaryPositionalEmbeddings
25
+ from .bs_roformer5 import TransformerBlock
26
+
27
+
28
+ class TokenInterpolator(nn.Module):
29
+ """
30
+ Upsamples from low-rate token embeddings to 50 TPS embeddings.
31
+
32
+ Args:
33
+ dim: embedding dimension (1024, matching fc_post_a output)
34
+ factor: upsample factor — 2 for 25->50 TPS, 4 for 12->50 TPS
35
+ depth: number of transformer layers
36
+ heads: attention heads
37
+ """
38
+
39
+ def __init__(self, dim: int = 1024, factor: int = 2, depth: int = 4, heads: int = 8):
40
+ super().__init__()
41
+ assert factor in (2, 4), "factor must be 2 (25 TPS) or 4 (12 TPS)"
42
+ self.factor = factor
43
+ self.dim = dim
44
+
45
+ # Learned sub-position embeddings to distinguish slots within each group.
46
+ # e.g. factor=2: slot 0 = known token, slot 1 = to be predicted.
47
+ self.sub_pos_embed = nn.Embedding(factor, dim)
48
+
49
+ rotary_embed = RotaryPositionalEmbeddings(dim=64)
50
+ self.transformer = nn.Sequential(*[
51
+ TransformerBlock(dim=dim, n_heads=heads, rotary_embed=rotary_embed)
52
+ for _ in range(depth)
53
+ ])
54
+ self.norm = nn.LayerNorm(dim)
55
+
56
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
57
+ """
58
+ Args:
59
+ x: [B, T_low, dim] — embeddings at 25 or 12 TPS
60
+
61
+ Returns:
62
+ out: [B, T_low * factor, dim] — embeddings at 50 TPS
63
+ """
64
+ B, T, D = x.shape
65
+
66
+ # Repeat each embedding `factor` times along time axis
67
+ # [B, T, D] -> [B, T, factor, D] -> [B, T*factor, D]
68
+ x = x.unsqueeze(2).expand(B, T, self.factor, D).reshape(B, T * self.factor, D)
69
+
70
+ # Add sub-position embedding so the model knows which slot it's filling.
71
+ # sub_idx: [0,1,0,1,...] for factor=2; [0,1,2,3,0,1,2,3,...] for factor=4
72
+ sub_idx = torch.arange(self.factor, device=x.device).repeat(T) # [T*factor]
73
+ x = x + self.sub_pos_embed(sub_idx) # broadcast over batch
74
+
75
+ x = self.transformer(x)
76
+ x = self.norm(x)
77
+ return x # [B, T*factor, D]
78
+
79
+
80
+ def encode_low_rate(neucodec, audio, factor: int = 2) -> torch.Tensor:
81
+ """
82
+ Encode audio to low-rate codes.
83
+
84
+ Returns:
85
+ codes: [B, 1, T//factor] integer token indices
86
+ """
87
+ codes = neucodec.encode_code(audio) # [B, 1, T] at 50 TPS
88
+ codes = codes[:, :, ::factor] # [B, 1, T//factor]
89
+ return codes
90
+
91
+
92
+ def decode_low_rate(neucodec, interpolator: TokenInterpolator, codes: torch.Tensor) -> torch.Tensor:
93
+ """
94
+ Decode low-rate codes back to 48kHz audio via interpolation.
95
+
96
+ Args:
97
+ codes: [B, 1, T_low] — 25 or 12 TPS codes
98
+
99
+ Returns:
100
+ audio: [B, 1, T_audio] — 48kHz audio
101
+ """
102
+ # 1. Lookup embeddings for the known tokens [B, T_low, 2048]
103
+ emb = neucodec.generator.quantizer.get_output_from_indices(codes.transpose(1, 2))
104
+ # 2. Project to 1024-dim space [B, T_low, 1024]
105
+ emb = neucodec.fc_post_a(emb)
106
+
107
+ # 3. Interpolate to 50 TPS [B, T_high, 1024]
108
+ emb = interpolator(emb)
109
+
110
+ # 4. Decode with existing frozen backbone + ISTFT
111
+ audio, _ = neucodec.generator(emb, vq=False)
112
+ return audio