Co-authored-by: gabrieldemarmiesse <gabrieldemarmiesse@gmail.com>
164 lines
5.6 KiB
Python
164 lines
5.6 KiB
Python
import math
|
|
import warnings
|
|
|
|
import torch
|
|
from torch import nn
|
|
from torch.nn import functional as F
|
|
|
|
from pocket_tts.modules.stateful_module import StatefulModule
|
|
|
|
|
|
def get_extra_padding_for_conv1d(
|
|
x: torch.Tensor, kernel_size: int, stride: int, padding_total: int = 0
|
|
) -> int:
|
|
"""See `pad_for_conv1d`."""
|
|
length = x.shape[-1]
|
|
n_frames = (length - kernel_size + padding_total) / stride + 1
|
|
ideal_length = (math.ceil(n_frames) - 1) * stride + (kernel_size - padding_total)
|
|
return ideal_length - length
|
|
|
|
|
|
def pad_for_conv1d(x: torch.Tensor, kernel_size: int, stride: int, padding_total: int = 0):
|
|
"""Pad for a convolution to make sure that the last window is full.
|
|
Extra padding is added at the end. This is required to ensure that we can rebuild
|
|
an output of the same length, as otherwise, even with padding, some time steps
|
|
might get removed.
|
|
For instance, with total padding = 4, kernel size = 4, stride = 2:
|
|
0 0 1 2 3 4 5 0 0 # (0s are padding)
|
|
1 2 3 # (output frames of a convolution, last 0 is never used)
|
|
0 0 1 2 3 4 5 0 # (output of tr. conv., but pos. 5 is going to get removed as padding)
|
|
1 2 3 4 # once you removed padding, we are missing one time step !
|
|
"""
|
|
extra_padding = get_extra_padding_for_conv1d(x, kernel_size, stride, padding_total)
|
|
return F.pad(x, (0, extra_padding))
|
|
|
|
|
|
class StreamingConv1d(StatefulModule):
|
|
"""Conv1d with some builtin handling of asymmetric or causal padding
|
|
and normalization.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
in_channels: int,
|
|
out_channels: int,
|
|
kernel_size: int,
|
|
stride: int = 1,
|
|
dilation: int = 1,
|
|
groups: int = 1,
|
|
bias: bool = True,
|
|
pad_mode: str = "constant",
|
|
):
|
|
super().__init__()
|
|
assert pad_mode in ["constant", "replicate"], pad_mode
|
|
self.pad_mode = pad_mode
|
|
# warn user on unusual setup between dilation and stride
|
|
if stride > 1 and dilation > 1:
|
|
warnings.warn(
|
|
"StreamingConv1d has been initialized with stride > 1 and dilation > 1"
|
|
f" (kernel_size={kernel_size} stride={stride}, dilation={dilation})."
|
|
)
|
|
self.conv = nn.Conv1d(
|
|
in_channels,
|
|
out_channels,
|
|
kernel_size,
|
|
stride,
|
|
dilation=dilation,
|
|
groups=groups,
|
|
bias=bias,
|
|
)
|
|
|
|
@property
|
|
def _stride(self) -> int:
|
|
return self.conv.stride[0]
|
|
|
|
@property
|
|
def _kernel_size(self) -> int:
|
|
return self.conv.kernel_size[0]
|
|
|
|
@property
|
|
def _effective_kernel_size(self) -> int:
|
|
dilation = self.conv.dilation[0]
|
|
return (self._kernel_size - 1) * dilation + 1 # effective kernel size with dilations
|
|
|
|
def init_state(self, batch_size: int, sequence_length: int) -> dict[str, torch.Tensor]:
|
|
stride = self._stride
|
|
# Effective kernel size accounting for dilation.
|
|
kernel = self._effective_kernel_size
|
|
device = self.conv.weight.device
|
|
previous = torch.zeros(batch_size, self.conv.in_channels, kernel - stride, device=device)
|
|
first = torch.ones(batch_size, dtype=torch.bool, device=device)
|
|
return dict(previous=previous, first=first)
|
|
|
|
def forward(self, x, model_state: dict | None):
|
|
B, C, T = x.shape
|
|
S = self._stride
|
|
assert T > 0 and T % S == 0, "Steps must be multiple of stride"
|
|
if model_state is None:
|
|
state = self.init_state(B, 0)
|
|
else:
|
|
state = self.get_state(model_state)
|
|
TP = state["previous"].shape[-1]
|
|
if TP and self.pad_mode == "replicate":
|
|
assert T >= TP, "Not enough content to pad streaming."
|
|
init = x[..., :1]
|
|
state["previous"][:] = torch.where(
|
|
state["first"].view(-1, 1, 1), init, state["previous"]
|
|
)
|
|
if TP:
|
|
x = torch.cat([state["previous"], x], dim=-1)
|
|
y = self.conv(x)
|
|
if TP:
|
|
state["previous"][:] = x[..., -TP:]
|
|
if self.pad_mode == "replicate":
|
|
state["first"] = torch.zeros_like(state["first"])
|
|
return y
|
|
|
|
|
|
class StreamingConvTranspose1d(StatefulModule):
|
|
"""ConvTranspose1d with some builtin handling of asymmetric or causal padding
|
|
and normalization.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
in_channels: int,
|
|
out_channels: int,
|
|
kernel_size: int,
|
|
stride: int = 1,
|
|
groups: int = 1,
|
|
bias: bool = True,
|
|
):
|
|
super().__init__()
|
|
self.convtr = nn.ConvTranspose1d(
|
|
in_channels, out_channels, kernel_size, stride, groups=groups, bias=bias
|
|
)
|
|
|
|
@property
|
|
def _stride(self) -> int:
|
|
return self.convtr.stride[0]
|
|
|
|
@property
|
|
def _kernel_size(self) -> int:
|
|
return self.convtr.kernel_size[0]
|
|
|
|
def init_state(self, batch_size: int, sequence_length: int) -> dict[str, torch.Tensor]:
|
|
K = self._kernel_size
|
|
S = self._stride
|
|
device = self.convtr.weight.device
|
|
return dict(partial=torch.zeros(batch_size, self.convtr.out_channels, K - S, device=device))
|
|
|
|
def forward(self, x, mimi_state: dict):
|
|
layer_state = self.get_state(mimi_state)["partial"]
|
|
y = self.convtr(x)
|
|
PT = layer_state.shape[-1]
|
|
if PT > 0:
|
|
y[..., :PT] += layer_state
|
|
bias = self.convtr.bias
|
|
for_partial = y[..., -PT:]
|
|
if bias is not None:
|
|
for_partial -= bias[:, None]
|
|
layer_state[:] = for_partial
|
|
y = y[..., :-PT]
|
|
return y
|