bachi / models /components.py
vedmistry's picture
initial BACHI deployment
bb92a2a
Raw
History Blame Contribute Delete
12.1 kB
import math
from typing import Any, Dict, Optional, Tuple
import torch
import torch.nn as nn
import torch.nn.functional as F
class PatchEmbedding(nn.Module):
def __init__(self, d_model: int, frames_per_patch: int = 6, expansion: int = 2):
super().__init__()
self.d_model = d_model
self.frames_per_patch = frames_per_patch
# Frame embedding (collapse pitch dim)
self.conv2d = nn.Conv2d(
in_channels=1,
out_channels=d_model,
kernel_size=(88, 1),
stride=(1, 1),
padding=(0, 0),
)
self.norm_frame = nn.LayerNorm(d_model)
# anti-aliasing conv on time axis
self.aa = nn.Conv1d(d_model, d_model, kernel_size=3, stride=1,
padding=1, groups=d_model, bias=False)
# Late temporal pooling (downsample frames -> patches)
self.glu_conv = nn.Conv1d(
in_channels=d_model,
out_channels=d_model * expansion * 2,
kernel_size=frames_per_patch,
stride=frames_per_patch,
padding=0,
bias=True,
)
self.project = nn.Conv1d(
in_channels=d_model * expansion,
out_channels=d_model,
kernel_size=1,
)
self.norm_temporal = nn.LayerNorm(d_model)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x: (B, 1, 88, T)
x = self.conv2d(x) # (B, C, 1, T)
x = x.squeeze(2).transpose(1, 2) # (B, T, C)
x = self.norm_frame(x)
# anti-aliased and temporal pooling
x = x.transpose(1, 2) # (B, C, T)
x = self.aa(x) # (B, C, T)
v, g = self.glu_conv(x).chunk(2, dim=1)
x = self.project(v * torch.sigmoid(g)) # (B, C, T//k)
x = x.transpose(1, 2) # (B, T//k, C)
return self.norm_temporal(x)
def downsample_key_padding_mask(mask: Optional[torch.Tensor], frames_per_patch: int) -> Optional[torch.Tensor]:
if mask is None:
return None
bsz, total_len = mask.shape
if total_len < frames_per_patch:
return mask.new_zeros((bsz, 0), dtype=mask.dtype)
out_len = total_len // frames_per_patch
trimmed = mask[:, : out_len * frames_per_patch]
grouped = trimmed.view(bsz, out_len, frames_per_patch)
return grouped.all(dim=-1)
class RelativePositionBias(nn.Module):
def __init__(self, num_heads: int, max_distance: int) -> None:
super().__init__()
if max_distance < 1:
raise ValueError("max_distance must be >= 1")
self.num_heads = num_heads
self.max_distance = max_distance
self.bias = nn.Parameter(torch.zeros(2 * max_distance - 1, num_heads))
def forward(self, seq_len: int, device: torch.device, dtype: torch.dtype) -> torch.Tensor:
pos = torch.arange(seq_len, device=device)
rel = pos[:, None] - pos[None, :]
rel = rel.clamp(-self.max_distance + 1, self.max_distance - 1)
rel = rel + self.max_distance - 1
bias = self.bias[rel]
return bias.permute(2, 0, 1).to(dtype=dtype)
class RelativeTransformerEncoderLayer(nn.Module):
def __init__(
self,
d_model: int,
nhead: int,
dim_feedforward: int,
dropout: float,
activation: str,
) -> None:
super().__init__()
if d_model % nhead != 0:
raise ValueError("d_model must be divisible by nhead")
self.d_model = d_model
self.nhead = nhead
self.head_dim = d_model // nhead
self.qkv_proj = nn.Linear(d_model, 3 * d_model)
self.attn_dropout = nn.Dropout(dropout)
self.out_proj = nn.Linear(d_model, d_model)
self.resid_dropout = nn.Dropout(dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.linear1 = nn.Linear(d_model, dim_feedforward)
self.linear2 = nn.Linear(dim_feedforward, d_model)
self.ff_dropout = nn.Dropout(dropout)
if activation == "gelu":
self.activation_fn = F.gelu
elif activation == "relu":
self.activation_fn = F.relu
else:
raise ValueError(f"unsupported activation: {activation}")
def forward(
self,
src: torch.Tensor,
src_mask: Optional[torch.Tensor] = None,
src_key_padding_mask: Optional[torch.Tensor] = None,
attn_bias: Optional[torch.Tensor] = None,
) -> torch.Tensor:
bsz, seq_len, _ = src.size()
qkv = self.qkv_proj(src)
q, k, v = qkv.chunk(3, dim=-1)
q = q.view(bsz, seq_len, self.nhead, self.head_dim)
k = k.view(bsz, seq_len, self.nhead, self.head_dim)
v = v.view(bsz, seq_len, self.nhead, self.head_dim)
attn_scores = torch.einsum("bthd,bshd->bhts", q, k) / math.sqrt(self.head_dim)
if src_mask is not None:
if src_mask.dtype == torch.bool:
attn_scores = attn_scores.masked_fill(src_mask.unsqueeze(0), float("-inf"))
else:
attn_scores = attn_scores + src_mask.unsqueeze(0)
if src_key_padding_mask is not None:
key_mask = src_key_padding_mask.unsqueeze(1).unsqueeze(2)
attn_scores = attn_scores.masked_fill(key_mask, float("-inf"))
if attn_bias is not None:
if attn_bias.dim() == 3:
attn_scores = attn_scores + attn_bias.unsqueeze(0)
elif attn_bias.dim() == 4:
attn_scores = attn_scores + attn_bias
else:
raise ValueError("attn_bias must be 3D or 4D tensor")
attn_weights = F.softmax(attn_scores, dim=-1)
attn_weights = self.attn_dropout(attn_weights)
context = torch.einsum("bhts,bshd->bthd", attn_weights, v)
context = context.contiguous().view(bsz, seq_len, self.d_model)
attn_out = self.out_proj(context)
src = self.norm1(src + self.resid_dropout(attn_out))
ff_out = self.linear2(self.ff_dropout(self.activation_fn(self.linear1(src))))
src = self.norm2(src + self.resid_dropout(ff_out))
return src
class RelativeTransformerEncoder(nn.Module):
def __init__(
self,
num_layers: int,
d_model: int,
nhead: int,
dim_feedforward: int,
dropout: float,
activation: str,
relative_position_bias: Optional[RelativePositionBias],
) -> None:
super().__init__()
self.layers = nn.ModuleList(
[
RelativeTransformerEncoderLayer(
d_model=d_model,
nhead=nhead,
dim_feedforward=dim_feedforward,
dropout=dropout,
activation=activation,
)
for _ in range(num_layers)
]
)
self.norm = nn.LayerNorm(d_model)
self.rpb = relative_position_bias
def forward(
self,
src: torch.Tensor,
src_key_padding_mask: Optional[torch.Tensor],
) -> torch.Tensor:
output = src
if self.rpb is not None:
attn_bias = self.rpb(src.size(1), device=src.device, dtype=src.dtype)
else:
attn_bias = None
for layer in self.layers:
output = layer(
output,
src_key_padding_mask=src_key_padding_mask,
attn_bias=attn_bias,
)
output = self.norm(output)
return output
class ChordProjectionHead(nn.Module):
def __init__(self, d_model: int, vocab_sizes: Dict[str, int]) -> None:
super().__init__()
self.boundary_head = nn.Sequential(
nn.Linear(d_model, d_model // 2),
nn.GELU(),
nn.Linear(d_model // 2, 1),
)
projection_heads: Dict[str, nn.Module] = {}
for name, size in vocab_sizes.items():
projection_heads[name] = nn.Sequential(
nn.Linear(d_model, d_model // 2),
nn.GELU(),
nn.Linear(d_model // 2, size),
)
self.projection_heads = nn.ModuleDict(projection_heads)
def forward(self, x: torch.Tensor) -> Dict[str, torch.Tensor]:
boundary_logits = self.boundary_head(x).squeeze(-1)
outputs: Dict[str, torch.Tensor] = {"boundary": boundary_logits}
for comp, head in self.projection_heads.items():
outputs[comp] = head(x)
return outputs
class KTokenDecoderLayer(nn.Module):
def __init__(self, d_model: int, nhead: int, mlp_ratio: int, dropout: float) -> None:
super().__init__()
if d_model % nhead != 0:
raise ValueError("d_model must be divisible by nhead")
self.d_model = d_model
self.nhead = nhead
self.head_dim = d_model // nhead
self.sa_qkv = nn.Linear(d_model, 3 * d_model)
self.sa_out = nn.Linear(d_model, d_model)
self.sa_ln = nn.LayerNorm(d_model)
self.sa_drop = nn.Dropout(dropout)
self.ca_q = nn.Linear(d_model, d_model)
self.ca_kv = nn.Linear(d_model, 2 * d_model)
self.ca_out = nn.Linear(d_model, d_model)
self.ca_ln = nn.LayerNorm(d_model)
self.ca_drop = nn.Dropout(dropout)
hidden = d_model * mlp_ratio
self.ff_ln = nn.LayerNorm(d_model)
self.ff = nn.Sequential(
nn.Linear(d_model, hidden),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(hidden, d_model),
)
self.ff_drop = nn.Dropout(dropout)
def _attn(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
attn = torch.einsum("nlhd,nshd->nhls", q, k) / math.sqrt(q.size(-1))
attn = torch.softmax(attn, dim=-1)
ctx = torch.einsum("nhls,nshd->nlhd", attn, v)
ctx = ctx.contiguous().view(q.size(0), q.size(1), -1)
return ctx
def forward(self, x_tokens: torch.Tensor, context: torch.Tensor) -> torch.Tensor:
bsz, length, k_tokens, d_model = x_tokens.shape
c_len = context.size(2)
batch_tokens = bsz * length
x = x_tokens.view(batch_tokens, k_tokens, d_model)
c = context.view(batch_tokens, c_len, d_model)
x_norm = self.sa_ln(x)
qkv = self.sa_qkv(x_norm)
q, k, v = qkv.chunk(3, dim=-1)
q = q.view(batch_tokens, k_tokens, self.nhead, self.head_dim)
k = k.view(batch_tokens, k_tokens, self.nhead, self.head_dim)
v = v.view(batch_tokens, k_tokens, self.nhead, self.head_dim)
sa_ctx = self._attn(q, k, v)
x = x + self.sa_drop(self.sa_out(sa_ctx))
x_norm = self.ca_ln(x)
q = self.ca_q(x_norm).view(batch_tokens, k_tokens, self.nhead, self.head_dim)
kv = self.ca_kv(c)
k, v = kv.chunk(2, dim=-1)
k = k.view(batch_tokens, c_len, self.nhead, self.head_dim)
v = v.view(batch_tokens, c_len, self.nhead, self.head_dim)
ca_ctx = self._attn(q, k, v)
x = x + self.ca_drop(self.ca_out(ca_ctx))
x_norm = self.ff_ln(x)
x = x + self.ff_drop(self.ff(x_norm))
return x.view(bsz, length, k_tokens, d_model)
def build_rpb(config: Dict[str, Any]) -> RelativePositionBias:
return RelativePositionBias(
num_heads=config["n_head"],
max_distance=config["n_beats"] * config["label_resolution"],
)
def build_encoder(config: Dict[str, Any]) -> RelativeTransformerEncoder:
rpb = build_rpb(config)
return RelativeTransformerEncoder(
num_layers=config["num_encoder_layers"],
d_model=config["d_model"],
nhead=config["n_head"],
dim_feedforward=config["dim_feedforward"],
dropout=config["dropout"],
activation="gelu",
relative_position_bias=rpb,
)
def build_patch_embedding(config: Dict[str, Any]) -> PatchEmbedding:
return PatchEmbedding(
d_model=config["d_model"],
frames_per_patch=config["frames_per_patch"],
expansion=2,
)