Files
spark-pocket-tts/pocket_tts/modules/conv.py
T

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