PRISM-1 / model.py
brunobellot2939's picture
Upload 11 files (#1)
7c325a4
Raw History Blame Contribute Delete
10.1 kB
"""
PRISM Custom DiT (Diffusion Transformer) Architecture Definition.
Implements a modular Diffusion Transformer with AdaLN-Zero conditioning,
2D spatial patchification, and cross-attention text guidance.
"""
import math
from typing import Optional, Tuple
import torch
import torch.nn as nn
import torch.nn.functional as F
def modulate(x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
"""Modulates input tensor using affine scale and shift parameters."""
return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
class TimestepEmbedder(nn.Module):
"""
Embeds scalar timesteps into vector representations via sinusoidal embeddings
followed by a two-layer MLP.
"""
def __init__(self, hidden_size: int, frequency_embedding_size: int = 256):
super().__init__()
self.mlp = nn.Sequential(
nn.Linear(frequency_embedding_size, hidden_size, bias=True),
nn.SiLU(),
nn.Linear(hidden_size, hidden_size, bias=True),
)
self.frequency_embedding_size = frequency_embedding_size
@staticmethod
def sinusoidal_embedding(t: torch.Tensor, dim: int, max_period: int = 10000) -> torch.Tensor:
half = dim // 2
freqs = torch.exp(
-math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32, device=t.device) / half
)
args = t[:, None].float() * freqs[None]
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
if dim % 2 == 1:
embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
return embedding
def forward(self, t: torch.Tensor) -> torch.Tensor:
t_freq = self.sinusoidal_embedding(t, self.frequency_embedding_size)
return self.mlp(t_freq)
class PatchEmbed(nn.Module):
"""2D Image/Latent to Patch Embedding."""
def __init__(self, patch_size: int = 2, in_channels: int = 16, embed_dim: int = 1152):
super().__init__()
self.patch_size = patch_size
self.proj = nn.Conv2d(
in_channels, embed_dim, kernel_size=patch_size, stride=patch_size, bias=True
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x: (B, C, H, W) -> proj -> (B, D, H/p, W/p) -> flatten -> (B, N, D)
x = self.proj(x)
x = x.flatten(2).transpose(1, 2)
return x
class PrismDiTBlock(nn.Module):
"""
DiT Block with AdaLN-Zero conditioning and Cross-Attention for text conditioning.
"""
def __init__(self, hidden_size: int, num_heads: int, context_dim: int = 1024, mlp_ratio: float = 4.0):
super().__init__()
self.hidden_size = hidden_size
self.num_heads = num_heads
self.head_dim = hidden_size // num_heads
# Normalization layers
self.norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.norm_cross = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
# Self-Attention
self.self_attn = nn.MultiheadAttention(hidden_size, num_heads, batch_first=True)
# Cross-Attention for text embeddings
self.cross_attn = nn.MultiheadAttention(
embed_dim=hidden_size,
kdim=context_dim,
vdim=context_dim,
num_heads=num_heads,
batch_first=True,
)
# Feed-Forward Network
mlp_hidden_dim = int(hidden_size * mlp_ratio)
self.mlp = nn.Sequential(
nn.Linear(hidden_size, mlp_hidden_dim, bias=True),
nn.GELU(approximate="tanh"),
nn.Linear(mlp_hidden_dim, hidden_size, bias=True),
)
# AdaLN modulation table: 6 modulation scales (shift, scale, gate for self-attn and mlp)
self.adaLN_modulation = nn.Sequential(
nn.SiLU(),
nn.Linear(hidden_size, 6 * hidden_size, bias=True),
)
def forward(
self,
x: torch.Tensor,
c: torch.Tensor,
context: Optional[torch.Tensor] = None,
context_mask: Optional[torch.Tensor] = None,
) -> torch.Tensor:
# Modulation vectors from timestep / conditioning vector c
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (
self.adaLN_modulation(c).chunk(6, dim=1)
)
# 1. Self Attention with AdaLN modulation
normed_x = modulate(self.norm1(x), shift_msa, scale_msa)
attn_out, _ = self.self_attn(normed_x, normed_x, normed_x)
x = x + gate_msa.unsqueeze(1) * attn_out
# 2. Cross Attention with text context (if provided)
if context is not None:
normed_cross = self.norm_cross(x)
cross_out, _ = self.cross_attn(
query=normed_cross,
key=context,
value=context,
key_padding_mask=context_mask,
)
x = x + cross_out
# 3. Feed Forward with AdaLN modulation
normed_mlp = modulate(self.norm2(x), shift_mlp, scale_mlp)
mlp_out = self.mlp(normed_mlp)
x = x + gate_mlp.unsqueeze(1) * mlp_out
return x
class FinalLayer(nn.Module):
"""Final AdaLN layer and linear unpatchify projection."""
def __init__(self, hidden_size: int, patch_size: int, out_channels: int):
super().__init__()
self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.linear = nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True)
self.adaLN_modulation = nn.Sequential(
nn.SiLU(),
nn.Linear(hidden_size, 2 * hidden_size, bias=True),
)
def forward(self, x: torch.Tensor, c: torch.Tensor) -> torch.Tensor:
shift, scale = self.adaLN_modulation(c).chunk(2, dim=1)
x = modulate(self.norm_final(x), shift, scale)
return self.linear(x)
class PrismDiT(nn.Module):
"""
PRISM Diffusion Transformer Backbone.
Supports:
- Flow-matching or v-prediction / epsilon diffusion
- Flexible latent channels (default: 16 channels for high-fidelity VAE)
- Full multimodal context cross-attention
"""
def __init__(
self,
input_size: int = 64,
patch_size: int = 2,
in_channels: int = 16,
hidden_size: int = 1152,
depth: int = 28,
num_heads: int = 16,
mlp_ratio: float = 4.0,
context_dim: int = 1024,
):
super().__init__()
self.in_channels = in_channels
self.out_channels = in_channels
self.patch_size = patch_size
self.num_heads = num_heads
self.hidden_size = hidden_size
self.x_embedder = PatchEmbed(patch_size, in_channels, hidden_size)
self.t_embedder = TimestepEmbedder(hidden_size)
# 2D Positional embeddings (learnable or fixed sinusoidal)
num_patches = (input_size // patch_size) ** 2
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches, hidden_size))
# Transformer blocks
self.blocks = nn.ModuleList([
PrismDiTBlock(hidden_size, num_heads, context_dim=context_dim, mlp_ratio=mlp_ratio)
for _ in range(depth)
])
# Final projection layer
self.final_layer = FinalLayer(hidden_size, patch_size, self.out_channels)
self.initialize_weights()
def initialize_weights(self):
# Initialize patch embedding like linear projection
w = self.x_embedder.proj.weight.data
nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
nn.init.constant_(self.x_embedder.proj.bias, 0)
# Initialize pos_embed
nn.init.normal_(self.pos_embed, std=0.02)
# Zero-out AdaLN modulation layers for identity start
for block in self.blocks:
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0)
nn.init.constant_(self.final_layer.adaLN_modulation[-1].bias, 0)
nn.init.constant_(self.final_layer.linear.weight, 0)
nn.init.constant_(self.final_layer.linear.bias, 0)
def unpatchify(self, x: torch.Tensor, h: int, w: int) -> torch.Tensor:
"""
x: (B, N, patch_size**2 * C)
Returns: (B, C, H, W)
"""
c = self.out_channels
p = self.patch_size
ph, pw = h // p, w // p
assert ph * pw == x.shape[1]
x = x.reshape(shape=(x.shape[0], ph, pw, p, p, c))
x = torch.einsum("nhwpqc->nchpwq", x)
imgs = x.reshape(shape=(x.shape[0], c, ph * p, pw * p))
return imgs
def forward(
self,
x: torch.Tensor,
t: torch.Tensor,
context: Optional[torch.Tensor] = None,
context_mask: Optional[torch.Tensor] = None,
) -> torch.Tensor:
"""
Forward pass for PRISM DiT.
x: (B, C, H, W) Latent tensor
t: (B,) Timestep tensor
context: (B, L, context_dim) Text/multimodal conditioning sequence
"""
b, c, h, w = x.shape
x = self.x_embedder(x)
# Add positional embedding (interpolating if resolution differs)
if x.shape[1] == self.pos_embed.shape[1]:
x = x + self.pos_embed
else:
# Dynamic positional interpolation for arbitrary aspect ratios
pos_embed = F.interpolate(
self.pos_embed.transpose(1, 2).reshape(1, self.hidden_size, int(math.sqrt(self.pos_embed.shape[1])), -1),
size=(h // self.patch_size, w // self.patch_size),
mode="bicubic",
align_corners=False,
).flatten(2).transpose(1, 2)
x = x + pos_embed
c_emb = self.t_embedder(t)
for block in self.blocks:
x = block(x, c_emb, context=context, context_mask=context_mask)
x = self.final_layer(x, c_emb)
x = self.unpatchify(x, h, w)
return x