Copy the files in the new repo for public release

This commit is contained in:
gabrieldemarmiesse
2026-01-12 11:25:16 +01:00
commit f5fa328a58
54 changed files with 6411 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Modules used for building the models."""
+161
View File
@@ -0,0 +1,161 @@
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
previous = torch.zeros(batch_size, self.conv.in_channels, kernel - stride)
first = torch.ones(batch_size, dtype=torch.bool)
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
return dict(partial=torch.zeros(batch_size, self.convtr.out_channels, K - S))
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
+18
View File
@@ -0,0 +1,18 @@
import torch
from torch import nn
class DummyQuantizer(nn.Module):
"""Simplified quantizer that only provides output projection for TTS.
This removes all unnecessary quantization logic since we don't use actual quantization.
"""
def __init__(self, dimension: int, output_dimension: int):
super().__init__()
self.dimension = dimension
self.output_dimension = output_dimension
self.output_proj = torch.nn.Conv1d(self.dimension, self.output_dimension, 1, bias=False)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.output_proj(x)
+11
View File
@@ -0,0 +1,11 @@
import torch
import torch.nn as nn
class LayerScale(nn.Module):
def __init__(self, channels: int, init: float):
super().__init__()
self.scale = nn.Parameter(torch.full((channels,), init))
def forward(self, x: torch.Tensor):
return self.scale * x
+285
View File
@@ -0,0 +1,285 @@
from typing import NamedTuple
import torch
import torch.nn as nn
from einops import rearrange
from torch.nn import functional as F
from typing_extensions import Self
from pocket_tts.modules.layer_scale import LayerScale
from pocket_tts.modules.rope import RotaryEmbedding
from pocket_tts.modules.stateful_module import StatefulModule
from pocket_tts.modules.transformer import StreamingMultiheadAttention
from pocket_tts.utils.config import FlowLMTransformerConfig
class KVCacheResult(NamedTuple):
keys: torch.Tensor
values: torch.Tensor
positions: torch.Tensor
@staticmethod
def from_kv(keys: torch.Tensor, values: torch.Tensor) -> "KVCacheResult":
B, H, T, D = keys.shape
assert tuple(values.shape[:-1]) == (B, H, T)
positions = torch.arange(T, device=keys.device, dtype=torch.long)
return KVCacheResult(keys, values, positions.expand(B, -1))
def complete(
cache: torch.Tensor, end_offset: torch.Tensor, k: torch.Tensor, v: torch.Tensor
) -> KVCacheResult:
capacity = cache.shape[3]
assert k.shape[:-1] == v.shape[:-1], (k.shape, v.shape)
B, H, T, D = k.shape
assert T > 0
indexes = torch.arange(T, device=end_offset.device, dtype=end_offset.dtype)
indexes = indexes + end_offset.view(-1, 1)
indexes = indexes % capacity
# indexes is [B, T]
# k is [B, H, T, D]
# cache is [B, H, T', D]
this_indexes = indexes.view(B, 1, T, 1)
this_indexes = this_indexes.expand(-1, H, T, D)
cache[0].scatter_(2, this_indexes, k)
cache[1].scatter_(2, this_indexes, v)
keys = cache[0]
values = cache[1]
indexes = torch.arange(capacity, device=end_offset.device, dtype=torch.long)
# end_index correspond to the actual index where the last value was written.
last_offset = end_offset.view(-1, 1) + T - 1
end_index = last_offset % capacity
delta = indexes - end_index
positions = torch.where(delta <= 0, last_offset + delta, last_offset + delta - capacity)
end_offset[:] = end_offset + T
invalid = indexes >= end_offset.view(-1, 1)
positions = torch.where(invalid, torch.full_like(positions, -1), positions)
return KVCacheResult(keys, values, positions)
class MimiStreamingMultiheadAttention(StatefulModule):
def __init__(self, embed_dim: int, num_heads: int, context: int, rope: RotaryEmbedding):
super().__init__()
self.embed_dim = embed_dim
self.context = context
self.rope = rope
self.num_heads = num_heads
out_dim = 3 * embed_dim
self.out_proj = nn.Linear(embed_dim, embed_dim, bias=False)
self.in_proj = nn.Linear(embed_dim, out_dim, bias=False)
def init_state(self, batch_size: int, sequence_length: int) -> dict[str, torch.Tensor]:
dim_per_head = self.embed_dim // self.num_heads
state = {}
state["offset"] = torch.zeros(batch_size, dtype=torch.long)
state["cache"] = torch.zeros((2, batch_size, self.num_heads, sequence_length, dim_per_head))
state["end_offset"] = torch.zeros(batch_size, dtype=torch.long)
return state
def increment_step(self, state, increment: int = 1):
state["offset"] += increment
def _complete_kv(self, k, v, model_state: dict | None) -> KVCacheResult:
if model_state is None:
return KVCacheResult.from_kv(k, v)
else:
layer_state = self.get_state(model_state)
return complete(layer_state["cache"], layer_state["end_offset"], k, v)
def forward(self, query: torch.Tensor, model_state: dict | None) -> torch.Tensor:
B, T = query.shape[:2]
if model_state is None:
offset = torch.zeros(B, device=query.device, dtype=torch.long)
else:
offset = self.get_state(model_state)["offset"]
projected = self.in_proj(query)
q, k, v = rearrange(projected, "b t (p h d) -> p b h t d", p=3, h=self.num_heads)
# Permute from [b, h, t, d] to [b, t, h, d] for rope
q = q.permute(0, 2, 1, 3)
k = k.permute(0, 2, 1, 3)
q, k = self.rope(q, k, offset)
# Permute back from [b, t, h, d] to [b, h, t, d]
q = q.permute(0, 2, 1, 3)
k = k.permute(0, 2, 1, 3)
k, v, pos_k = self._complete_kv(k, v, model_state)
pos_k = pos_k[:, None]
pos_q = offset.view(-1, 1, 1) + torch.arange(T, device=q.device, dtype=torch.long).view(
-1, 1
)
delta = pos_q - pos_k
attn_bias = (pos_k >= 0) & (delta >= 0)
attn_bias = attn_bias & (delta < self.context)
attn_bias = attn_bias[:, None]
x = F.scaled_dot_product_attention(q, k, v, attn_bias, dropout_p=0.0)
x = rearrange(x, "b h t d -> b t (h d)")
x = self.out_proj(x)
return x
class StreamingTransformerLayer(nn.Module):
def __init__(
self,
d_model: int,
num_heads: int,
dim_feedforward: int,
context: int | None,
rope: RotaryEmbedding,
layer_scale: float | None = None,
attention_kind: str = "mimi",
):
super().__init__()
# Redefine self_attn to our streaming multi-head attention
if attention_kind == "mimi":
# TODO: we should actually use StreamingMultiheadAttention here and add context window
# support. And we should then delete MimiStreamingMultiheadAttention.
# The implementation is really close.
self.self_attn = MimiStreamingMultiheadAttention(
context=context, rope=rope, embed_dim=d_model, num_heads=num_heads
)
else:
self.self_attn = StreamingMultiheadAttention(
rope=rope, embed_dim=d_model, num_heads=num_heads
)
self.norm1 = nn.LayerNorm(d_model, eps=1e-5)
self.norm2 = nn.LayerNorm(d_model, eps=1e-5)
self.linear1 = nn.Linear(d_model, dim_feedforward, bias=False)
self.linear2 = nn.Linear(dim_feedforward, d_model, bias=False)
if layer_scale is None:
self.layer_scale_1 = nn.Identity()
self.layer_scale_2 = nn.Identity()
else:
self.layer_scale_1 = LayerScale(d_model, layer_scale)
self.layer_scale_2 = LayerScale(d_model, layer_scale)
def _ff_block(self, x: torch.Tensor) -> torch.Tensor:
x_orig = x
x = self.norm2(x)
update = self.linear2(F.gelu(self.linear1(x)))
return x_orig.to(update) + self.layer_scale_2(update)
def _sa_block(self, x: torch.Tensor, model_state: dict | None) -> torch.Tensor:
x_orig = x
x = self.norm1(x)
update = self.self_attn(x, model_state)
return x_orig.to(update) + self.layer_scale_1(update)
def forward(self, x: torch.Tensor, model_state: dict | None) -> torch.Tensor:
x = self._sa_block(x, model_state)
x = self._ff_block(x)
return x
class StreamingTransformer(nn.Module):
def __init__(
self,
d_model: int,
num_heads: int,
num_layers: int,
layer_scale: float | None = None,
dim_feedforward: int | list[int] = 2048,
context: int | None = None,
max_period: float = 10_000.0,
kind: str = "mimi",
):
super().__init__()
assert d_model % num_heads == 0
self.max_period = max_period
self.rope = RotaryEmbedding(max_period=max_period)
self.layers = nn.ModuleList()
for _ in range(num_layers):
self.layers.append(
StreamingTransformerLayer(
d_model=d_model,
num_heads=num_heads,
dim_feedforward=dim_feedforward,
context=context,
rope=self.rope,
layer_scale=layer_scale,
attention_kind=kind,
)
)
@classmethod
def from_pydantic_config(cls, config: FlowLMTransformerConfig) -> Self:
dim_feedforward = int(config.d_model * config.hidden_scale)
return cls(
d_model=config.d_model,
num_heads=config.num_heads,
num_layers=config.num_layers,
dim_feedforward=dim_feedforward,
max_period=float(config.max_period),
kind="flow_lm",
)
def forward(self, x: torch.Tensor, model_state: dict | None):
for layer in self.layers:
x = layer(x, model_state)
return x
class ProjectedTransformer(nn.Module):
def __init__(
self,
input_dimension: int,
output_dimensions: tuple[int, ...],
d_model: int,
num_heads: int,
num_layers: int,
layer_scale: float,
context: int,
max_period: float,
dim_feedforward: int,
):
super().__init__()
self.transformer = StreamingTransformer(
d_model=d_model,
num_heads=num_heads,
num_layers=num_layers,
layer_scale=layer_scale,
context=context,
max_period=max_period,
dim_feedforward=dim_feedforward,
)
self.input_dimension = input_dimension
self.output_dimensions = output_dimensions
self.input_proj = None
if d_model != input_dimension:
self.input_proj = nn.Linear(input_dimension, d_model, bias=False)
self.output_projs = nn.ModuleList()
for output_dimension in output_dimensions:
if d_model == output_dimension:
self.output_projs.append(nn.Identity())
else:
self.output_projs.append(nn.Linear(d_model, output_dimension, bias=False))
def forward(self, x, model_state: dict | None):
x = x.transpose(1, 2)
if self.input_proj is not None:
x = self.input_proj(x)
z = self.transformer(x, model_state)
ys = []
for output_proj in self.output_projs:
y = output_proj(z)
y = y.transpose(1, 2)
ys.append(y)
return ys
+215
View File
@@ -0,0 +1,215 @@
"""
Taken from
https://github.com/LTH14/mar/blob/fe470ac24afbee924668d8c5c83e9fec60af3a73/models/diffloss.py
"""
import math
import torch
import torch.nn as nn
from typing_extensions import Self
from pocket_tts.utils.config import FlowLMConfig
def modulate(x, shift, scale):
return x * (1 + scale) + shift
def _rms_norm(x: torch.Tensor, alpha: torch.Tensor, eps: float):
assert x.dim() >= alpha.dim()
x_dtype = x.dtype
var = eps + x.var(dim=-1, keepdim=True)
y = (x * (alpha.to(var) * torch.rsqrt(var))).to(x_dtype)
return y
class RMSNorm(nn.Module):
def __init__(self, dim: int, eps: float = 1e-5):
super().__init__()
self.eps = eps
alpha_shape = (dim,)
self.alpha = nn.Parameter(torch.full(alpha_shape, 1.0, requires_grad=True))
def forward(self, x: torch.Tensor):
return _rms_norm(x, self.alpha, self.eps)
class LayerNorm(nn.Module):
"""Reimplementation of LayerNorm because the default one doesn't support jvp."""
def __init__(self, channels, eps=1e-6, elementwise_affine=True):
super().__init__()
self.eps = eps
if elementwise_affine:
self.weight = nn.Parameter(torch.ones(channels))
self.bias = nn.Parameter(torch.zeros(channels))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, unbiased=False, keepdim=True)
x = (x - mean) / torch.sqrt(var + self.eps)
if hasattr(self, "weight"):
x = x * self.weight + self.bias
return x
class TimestepEmbedder(nn.Module):
"""Embeds scalar timesteps into vector representations."""
def __init__(
self, hidden_size: int, frequency_embedding_size: int = 256, max_period: int = 10000
):
super().__init__()
blocks = [
nn.Linear(frequency_embedding_size, hidden_size, bias=True),
nn.SiLU(),
nn.Linear(hidden_size, hidden_size, bias=True),
]
blocks.append(RMSNorm(hidden_size))
self.mlp = nn.Sequential(*blocks)
self.frequency_embedding_size = frequency_embedding_size
half = frequency_embedding_size // 2
self.register_buffer(
"freqs", torch.exp(-math.log(max_period) * torch.arange(start=0, end=half) / half)
)
def forward(self, t):
args = t * self.freqs.to(t.dtype)
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
assert not (self.frequency_embedding_size % 2)
t_emb = self.mlp(embedding)
return t_emb
class ResBlock(nn.Module):
"""
A residual block that can optionally change the number of channels.
:param channels: the number of input channels.
"""
def __init__(self, channels):
super().__init__()
self.channels = channels
self.in_ln = LayerNorm(channels, eps=1e-6)
self.mlp = nn.Sequential(
nn.Linear(channels, channels, bias=True),
nn.SiLU(),
nn.Linear(channels, channels, bias=True),
)
self.adaLN_modulation = nn.Sequential(
nn.SiLU(), nn.Linear(channels, 3 * channels, bias=True)
)
def forward(self, x, y):
shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(y).chunk(3, dim=-1)
h = modulate(self.in_ln(x), shift_mlp, scale_mlp)
h = self.mlp(h)
return x + gate_mlp * h
class FinalLayer(nn.Module):
"""
The final layer adopted from DiT.
"""
def __init__(self, model_channels, out_channels):
super().__init__()
self.norm_final = LayerNorm(model_channels, elementwise_affine=False, eps=1e-6)
self.linear = nn.Linear(model_channels, out_channels, bias=True)
self.adaLN_modulation = nn.Sequential(
nn.SiLU(), nn.Linear(model_channels, 2 * model_channels, bias=True)
)
def forward(self, x, c):
shift, scale = self.adaLN_modulation(c).chunk(2, dim=-1)
x = modulate(self.norm_final(x), shift, scale)
x = self.linear(x)
return x
class SimpleMLPAdaLN(nn.Module):
"""Taken from https://arxiv.org/abs/2406.11838.
The MLP for Diffusion Loss.
:param in_channels: channels in the input Tensor.
:param model_channels: base channel count for the model.
:param out_channels: channels in the output Tensor.
:param cond_channels: channels in the condition.
:param num_res_blocks: number of residual blocks per downsample.
"""
def __init__(
self,
in_channels,
model_channels,
out_channels,
cond_channels,
num_res_blocks,
num_time_conds=1,
):
super().__init__()
self.in_channels = in_channels
self.model_channels = model_channels
self.out_channels = out_channels
self.num_res_blocks = num_res_blocks
self.num_time_conds = num_time_conds
assert num_time_conds != 1
self.time_embed = nn.ModuleList(
[TimestepEmbedder(model_channels) for _ in range(num_time_conds)]
)
self.cond_embed = nn.Linear(cond_channels, model_channels)
self.input_proj = nn.Linear(in_channels, model_channels)
res_blocks = []
for i in range(num_res_blocks):
res_blocks.append(ResBlock(model_channels))
self.res_blocks = nn.ModuleList(res_blocks)
self.final_layer = FinalLayer(model_channels, out_channels)
@classmethod
def from_pydantic_config(cls, cfg: FlowLMConfig, latent_dim: int, cond_dim: int) -> Self:
config = cfg.flow
flow_dim = config.dim
flow_depth = config.depth
num_time_conds = 2
return SimpleMLPAdaLN(
latent_dim, flow_dim, latent_dim, cond_dim, flow_depth, num_time_conds=num_time_conds
)
def forward(
self, c: torch.Tensor, s: torch.Tensor, t: torch.Tensor, x: torch.Tensor
) -> torch.Tensor:
"""
Apply the model to an input batch.
:param c: conditioning from AR transformer.
:param s: start time tensor.
:param t: target time tensor.
:param x: an [N x C] Tensor of inputs.
:return: an [N x C] Tensor of outputs.
"""
# Combine time conditions
ts = [s, t]
x = self.input_proj(x)
assert len(ts) == self.num_time_conds, (
f"Expected {self.num_time_conds} time conditions, got {len(ts)}"
)
assert self.num_time_conds != 1
t_combined = (
sum(self.time_embed[i](ts[i]) for i in range(self.num_time_conds)) / self.num_time_conds
)
c = self.cond_embed(c)
y = t_combined + c
for block in self.res_blocks:
x = block(x, y)
return self.final_layer(x, y)
+46
View File
@@ -0,0 +1,46 @@
import torch
from torch import nn
from pocket_tts.modules.conv import StreamingConv1d, StreamingConvTranspose1d
class ConvDownsample1d(nn.Module):
"""
Downsampling by some integer amount `stride` using convolutions
with a kernel size of twice the stride.
"""
def __init__(self, stride: int, dimension: int):
super().__init__()
self.conv = StreamingConv1d(
dimension,
dimension,
kernel_size=2 * stride,
stride=stride,
groups=1,
bias=False,
pad_mode="replicate",
)
def forward(self, x: torch.Tensor, model_state: dict | None):
return self.conv(x, model_state)
class ConvTrUpsample1d(nn.Module):
"""
Upsample by some integer amount `stride` using transposed convolutions.
"""
def __init__(self, stride: int, dimension: int):
super().__init__()
self.convtr = StreamingConvTranspose1d(
dimension,
dimension,
kernel_size=2 * stride,
stride=stride,
groups=dimension,
bias=False,
)
def forward(self, x: torch.Tensor, model_state: dict | None):
return self.convtr(x, model_state)
+74
View File
@@ -0,0 +1,74 @@
import math
import torch
from torch import nn
def apply_rope(
q: torch.Tensor,
k: torch.Tensor,
offset: int | torch.Tensor = 0,
max_period: int | float = 10_000,
):
"""
Args:
q (torch.Tensor): Queries, shape `[B, T, H, D]`.
k (torch.Tensor): Keys, shape `[B, T, H, D]`.
offset (int): Current offset, e.g. when streaming.
max_period (float): Maximum period for the cos and sin.
"""
B, T, H, D = q.shape
Bk, Tk, Hk, Dk = k.shape
assert (B, T, D) == (Bk, Tk, Dk)
assert D > 0
assert D % 2 == 0
assert max_period > 0
ds = torch.arange(D // 2, device=q.device, dtype=torch.float32)
freqs = torch.exp(ds * (-math.log(max_period) * 2 / D))
# could be optimized in one call
ts = torch.arange(T, device=q.device, dtype=torch.float32)
ts += offset
ts = ts.view(-1, 1, 1)
q = q.view(B, T, H, D // 2, 2)
k = k.view(B, T, Hk, D // 2, 2)
# convention is `r` suffix is real part, `i` is imaginary.
qr = q[..., 0].float()
qi = q[..., 1].float()
kr = k[..., 0].float()
ki = k[..., 1].float()
rotr = torch.cos(freqs * ts)
roti = torch.sin(freqs * ts)
qor = qr * rotr - qi * roti
qoi = qr * roti + qi * rotr
kor = kr * rotr - ki * roti
koi = kr * roti + ki * rotr
dtype = q.dtype
qo = torch.stack([qor.to(dtype), qoi.to(dtype)], dim=-1)
ko = torch.stack([kor.to(dtype), koi.to(dtype)], dim=-1)
return qo.view(B, T, H, D), ko.view(B, T, Hk, D)
class RotaryEmbedding(nn.Module):
"""Rotary positional embedding (RoPE) from [Su et al 2022](https://arxiv.org/abs/2104.09864).
Args:
max_period (float): Maximum period of the rotation frequencies.
"""
def __init__(self, max_period: float | int = 10000.0):
super().__init__()
self.max_period = max_period
def forward(self, q: torch.Tensor, k: torch.Tensor, offset: torch.Tensor | int):
"""Apply rope rotation to query or key tensor."""
return apply_rope(q, k, offset, self.max_period)
+180
View File
@@ -0,0 +1,180 @@
import numpy as np
import torch.nn as nn
from .conv import StreamingConv1d, StreamingConvTranspose1d
class SEANetResnetBlock(nn.Module):
def __init__(
self,
dim: int,
kernel_sizes: list[int] = [3, 1],
dilations: list[int] = [1, 1],
pad_mode: str = "reflect",
compress: int = 2,
):
super().__init__()
assert len(kernel_sizes) == len(dilations), (
"Number of kernel sizes should match number of dilations"
)
hidden = dim // compress
block = nn.ModuleList([])
for i, (kernel_size, dilation) in enumerate(zip(kernel_sizes, dilations)):
in_chs = dim if i == 0 else hidden
out_chs = dim if i == len(kernel_sizes) - 1 else hidden
block += [
nn.ELU(alpha=1.0),
StreamingConv1d(
in_chs, out_chs, kernel_size=kernel_size, dilation=dilation, pad_mode=pad_mode
),
]
self.block = block
def forward(self, x, model_state: dict | None):
v = x
for layer in self.block:
if isinstance(layer, StreamingConv1d):
v = layer(v, model_state)
else:
v = layer(v)
assert x.shape == v.shape, (x.shape, v.shape, x.shape)
return x + v
class SEANetEncoder(nn.Module):
def __init__(
self,
channels: int = 1,
dimension: int = 128,
n_filters: int = 32,
n_residual_layers: int = 3,
ratios: list[int] = [8, 5, 4, 2],
kernel_size: int = 7,
last_kernel_size: int = 7,
residual_kernel_size: int = 3,
dilation_base: int = 2,
pad_mode: str = "reflect",
compress: int = 2,
):
super().__init__()
self.channels = channels
self.dimension = dimension
self.n_filters = n_filters
self.ratios = list(reversed(ratios))
del ratios
self.n_residual_layers = n_residual_layers
self.hop_length = int(np.prod(self.ratios))
self.n_blocks = len(self.ratios) + 2 # first and last conv + residual blocks
mult = 1
model = nn.ModuleList(
[StreamingConv1d(channels, mult * n_filters, kernel_size, pad_mode=pad_mode)]
)
# Downsample to raw audio scale
for i, ratio in enumerate(self.ratios):
# Add residual layers
for j in range(n_residual_layers):
model += [
SEANetResnetBlock(
mult * n_filters,
kernel_sizes=[residual_kernel_size, 1],
dilations=[dilation_base**j, 1],
pad_mode=pad_mode,
compress=compress,
)
]
# Add downsampling layers
model += [
nn.ELU(alpha=1.0),
StreamingConv1d(
mult * n_filters,
mult * n_filters * 2,
kernel_size=ratio * 2,
stride=ratio,
pad_mode=pad_mode,
),
]
mult *= 2
model += [
nn.ELU(alpha=1.0),
StreamingConv1d(mult * n_filters, dimension, last_kernel_size, pad_mode=pad_mode),
]
self.model = model
def forward(self, x, model_state: dict | None):
for layer in self.model:
if isinstance(layer, (StreamingConv1d, SEANetResnetBlock)):
x = layer(x, model_state)
else:
x = layer(x)
return x
class SEANetDecoder(nn.Module):
def __init__(
self,
channels: int = 1,
dimension: int = 128,
n_filters: int = 32,
n_residual_layers: int = 3,
ratios: list[int] = [8, 5, 4, 2],
kernel_size: int = 7,
last_kernel_size: int = 7,
residual_kernel_size: int = 3,
dilation_base: int = 2,
pad_mode: str = "reflect",
compress: int = 2,
):
super().__init__()
self.dimension = dimension
self.channels = channels
self.n_filters = n_filters
self.ratios = ratios
del ratios
self.n_residual_layers = n_residual_layers
self.hop_length = int(np.prod(self.ratios))
self.n_blocks = len(self.ratios) + 2 # first and last conv + residual blocks
mult = int(2 ** len(self.ratios))
model = nn.ModuleList(
[StreamingConv1d(dimension, mult * n_filters, kernel_size, pad_mode=pad_mode)]
)
# Upsample to raw audio scale
for _, ratio in enumerate(self.ratios):
# Add upsampling layers
model += [
nn.ELU(alpha=1.0),
StreamingConvTranspose1d(
mult * n_filters, mult * n_filters // 2, kernel_size=ratio * 2, stride=ratio
),
]
# Add residual layers
for j in range(n_residual_layers):
model += [
SEANetResnetBlock(
mult * n_filters // 2,
kernel_sizes=[residual_kernel_size, 1],
dilations=[dilation_base**j, 1],
pad_mode=pad_mode,
compress=compress,
)
]
mult //= 2
# Add final layers
model += [
nn.ELU(alpha=1.0),
StreamingConv1d(n_filters, channels, last_kernel_size, pad_mode=pad_mode),
]
self.model = model
def forward(self, z, model_state: dict | None):
for layer in self.model:
if isinstance(layer, (StreamingConvTranspose1d, SEANetResnetBlock, StreamingConv1d)):
z = layer(z, model_state)
else:
z = layer(z)
return z
+45
View File
@@ -0,0 +1,45 @@
from abc import ABC, abstractmethod
import torch
from torch import nn
def init_states(
model: nn.Module, batch_size: int, sequence_length: int
) -> dict[str, dict[str, torch.Tensor]]:
result = {}
for module_name, module in model.named_modules():
if not isinstance(module, StatefulModule):
continue
module._module_absolute_name = module_name
module_state = module.init_state(batch_size, sequence_length=sequence_length)
result[module_name] = module_state
return result
def increment_steps(
module: nn.Module, model_state: dict[str, dict[str, torch.Tensor]], increment: int = 1
):
# print("incrementing steps by", increment)
for module_name, module in module.named_modules():
if not isinstance(module, StatefulModule):
continue
module.increment_step(model_state[module_name], increment)
class StatefulModule(ABC, nn.Module):
def __init__(self, *args, **kwds):
self._module_absolute_name = None
return super().__init__(*args, **kwds)
@abstractmethod
def init_state(self, batch_size: int, sequence_length: int):
"""Initialize the state."""
raise NotImplementedError
def increment_step(self, state: dict, increment: int = 1):
pass
def get_state(self, model_state: dict[str, dict[str, torch.Tensor]]) -> dict[str, torch.Tensor]:
"""Get the state for this module from the model state."""
return model_state[self._module_absolute_name]
+126
View File
@@ -0,0 +1,126 @@
import torch
import torch.nn as nn
from torch.nn import functional as F
from pocket_tts.modules.rope import RotaryEmbedding
from pocket_tts.modules.stateful_module import StatefulModule
def complete_kv(
cache: torch.Tensor, current_end: torch.Tensor, k: torch.Tensor, v: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]:
current_end = current_end.shape[0]
cache[0, :, current_end : current_end + k.shape[1]] = k
cache[1, :, current_end : current_end + v.shape[1]] = v
valid = cache[:, :, : current_end + k.shape[1]]
return valid[0], valid[1]
def _materialize_causal_mask(
shape: tuple[int, ...], shift: int, device: str | torch.device = "cpu"
) -> torch.Tensor:
dtype = torch.float32
num_queries, num_keys = shape[-2:]
shift = num_keys - num_queries
tensor = torch.full(shape, dtype=dtype, fill_value=1, device=device)
mask = torch.tril(tensor, diagonal=shift).to(dtype)
mask = torch.log(mask)
return mask.to(dtype)
class StreamingMultiheadAttention(StatefulModule):
"""Similar to `nn.MultiheadAttention` but with support for streaming.
Args:
embed_dim (int): Dimension to project to.
num_heads (int): Number of heads.
context (int, optional): Number of time steps the attention can access to.
Can access `context` time steps into the past.
rope (`RotaryEmbedding`, optional): Rope embedding to use.
device (torch.device, optional): Device on which to initialize.
dtype (torch.dtype, optional): dtype to use.
"""
def __init__(self, embed_dim: int, num_heads: int, rope: RotaryEmbedding):
super().__init__()
self.embed_dim = embed_dim
self.rope = rope
self.num_heads = num_heads
out_dim = embed_dim
num_kv = num_heads
kv_dim = (embed_dim // num_heads) * num_kv
out_dim += 2 * kv_dim
mult = 1
in_proj = nn.Linear(embed_dim, mult * out_dim, bias=False)
# We try to follow the default PyTorch MHA convention, to easily compare results.
self.in_proj_weight = in_proj.weight
self.out_proj = nn.Linear(embed_dim, mult * embed_dim, bias=False)
def _get_mask(self, shape: tuple[int, int], shift: int, device: torch.device) -> torch.Tensor:
return _materialize_causal_mask(shape, shift=shift, device=device)
def init_state(self, batch_size: int, sequence_length: int) -> dict[str, torch.Tensor]:
dim_per_head = self.embed_dim // self.num_heads
initial_current_end = torch.zeros((0,)).to(self.in_proj_weight.device)
return dict(
current_end=initial_current_end,
cache=torch.full(
(2, batch_size, sequence_length, self.num_heads, dim_per_head),
float("NaN"),
device=self.in_proj_weight.device,
dtype=self.in_proj_weight.dtype,
),
)
def increment_step(self, state: dict, increment: int = 1):
new_size = state["current_end"].shape[0] + increment
state["current_end"] = torch.zeros((new_size,)).to(state["current_end"].device)
def _complete_kv(self, k, v, state: dict | None):
k, v = complete_kv(state["cache"], state["current_end"], k, v)
return k, v
def _apply_rope(self, query: torch.Tensor, key: torch.Tensor, state: dict | None):
# Apply rope embeddings to query and key tensors.
streaming_offset = self._streaming_offset(state)
return self.rope(query, key, offset=streaming_offset)
def _streaming_offset(self, state: dict | None) -> torch.Tensor | int:
return state["current_end"].shape[0]
def check_model_state(self, model_state: dict):
if model_state is None:
raise ValueError("model_state must be provided")
return self.get_state(model_state)
def forward(self, query: torch.Tensor, model_state: dict | None):
state = self.check_model_state(model_state)
projected = nn.functional.linear(query, self.in_proj_weight)
# Reshape from (b, t, p*h*d) to (b, t, p, h, d) where p=3, h=num_heads
b, t, _ = projected.shape
d = self.embed_dim // self.num_heads
packed = projected.view(b, t, 3, self.num_heads, d)
q, k, v = torch.unbind(packed, dim=2)
q, k = self._apply_rope(q, k, state)
k, v = self._complete_kv(k, v, state)
mask_shape = (query.shape[1], query.shape[1] + state["current_end"].shape[0])
shift = state["current_end"].shape[0]
attn_mask = self._get_mask(mask_shape, shift=shift, device=q.device)
q, k, v = [x.transpose(1, 2) for x in (q, k, v)]
x = F.scaled_dot_product_attention(q, k, v, attn_mask)
x = x.transpose(1, 2)
# Reshape from (b, t, h, d) to (b, t, h*d)
b, t, h, d = x.shape
x = x.reshape(b, t, h * d)
x = self.out_proj(x)
return x