dinac3_96 / dinac3 /trunk.py
data-archetype's picture
dinac3_96 v1.0
75ff4df
Raw History Blame Contribute Delete
4.18 kB
"""The decoder's ViT trunk: RMS-sandwich blocks with 2-D RoPE."""
from __future__ import annotations
import torch
from torch import Tensor, nn
from torch.nn import functional as F
from .layers import Mlp, RMSNorm
ROPE_BASE = 10_000.0
def rope_periods(head_dim: int, device: torch.device) -> Tensor:
"""FP32 periods ``ROPE_BASE ** (2 i / (head_dim / 2))`` of the
``head_dim / 4`` rotation frequencies per axis."""
exponents = (
2.0
* torch.arange(head_dim // 4, device=device, dtype=torch.float32)
/ (head_dim // 2)
)
return torch.tensor(ROPE_BASE, device=device, dtype=torch.float32) ** exponents
def rope_tables(
height: int, width: int, *, head_dim: int, device: torch.device
) -> tuple[Tensor, Tensor]:
"""FP32 ``(sin, cos)`` ``[H * W, head_dim]`` of axial 2-D RoPE on the token
grid: unnormalized patch-index coordinates (row, column), the
:func:`rope_periods`, adjacent-pair layout."""
periods = rope_periods(head_dim, device)
rows = torch.arange(0.0, float(height), device=device, dtype=torch.float32)
cols = torch.arange(0.0, float(width), device=device, dtype=torch.float32)
coords = torch.stack(torch.meshgrid(rows, cols, indexing="ij"), dim=-1).flatten(
0, 1
)
angles = 1.0 * coords[:, :, None] / periods[None, None, :].expand(1, 2, -1)
angles = angles.repeat_interleave(2, dim=-1).flatten(1, 2)
return torch.sin(angles), torch.cos(angles)
def _rotate_pairs(x: Tensor) -> Tensor:
"""Rotate consecutive channel pairs: ``(a, b) -> (-b, a)``."""
pairs = x.reshape(*x.shape[:-1], x.shape[-1] // 2, 2)
return torch.stack((-pairs[..., 1], pairs[..., 0]), dim=-1).reshape_as(x)
def apply_rope(x: Tensor, sin: Tensor, cos: Tensor) -> Tensor:
"""Rotate ``[B, heads, N, head_dim]`` queries or keys in the tables' dtype."""
rotated = x.to(dtype=sin.dtype)
sin_b = sin.view(1, 1, *sin.shape)
cos_b = cos.view(1, 1, *cos.shape)
return ((rotated * cos_b) + (_rotate_pairs(rotated) * sin_b)).to(dtype=x.dtype)
class Attention(nn.Module):
"""Bias-free multi-head self-attention with RMS-normalized queries and keys."""
def __init__(self, width: int, head_dim: int) -> None:
"""Allocate the fused QKV and output projections and the q/k norms."""
super().__init__()
self.heads = width // head_dim
self.head_dim = head_dim
self.qkv = nn.Linear(width, 3 * width, bias=False)
self.proj_out = nn.Linear(width, width, bias=False)
self.q_norm = RMSNorm(head_dim, affine=True)
self.k_norm = RMSNorm(head_dim, affine=True)
def forward(self, x: Tensor, sin: Tensor, cos: Tensor) -> Tensor:
"""Attend over all tokens of ``[B, N, width]``."""
b, n, width = x.shape
q, k, v = self.qkv(x).chunk(3, dim=-1)
shape = (b, n, self.heads, self.head_dim)
q = q.view(shape).transpose(1, 2).contiguous()
k = k.view(shape).transpose(1, 2).contiguous()
v = v.view(shape).transpose(1, 2).contiguous()
q = apply_rope(self.q_norm(q), sin, cos)
k = apply_rope(self.k_norm(k), sin, cos)
out = F.scaled_dot_product_attention(q, k, v, dropout_p=0.0, is_causal=False)
return self.proj_out(out.transpose(1, 2).contiguous().view(b, n, width))
class DitBlock(nn.Module):
"""ViT block: RMSNorm before and after attention and MLP."""
def __init__(self, width: int, head_dim: int, mlp_ratio: float) -> None:
"""Allocate attention, MLP and the four norms."""
super().__init__()
self.attn_norm1 = RMSNorm(width, affine=True)
self.attn_norm2 = RMSNorm(width, affine=True)
self.mlp_norm1 = RMSNorm(width, affine=True)
self.mlp_norm2 = RMSNorm(width, affine=True)
self.attn = Attention(width, head_dim)
self.mlp = Mlp(width, int(mlp_ratio * width))
def forward(self, x: Tensor, sin: Tensor, cos: Tensor) -> Tensor:
"""Residual attention then residual MLP on ``[B, N, width]`` tokens."""
x = x + self.attn_norm2(self.attn(self.attn_norm1(x), sin, cos))
return x + self.mlp_norm2(self.mlp(self.mlp_norm1(x)))