anilegin's picture
Upload inference-only latent diffusion model
a04730e verified
Raw
History Blame Contribute Delete
11 kB
from __future__ import annotations
import torch
from torch import nn
import torch.nn.functional as F
from src.models.diffusion.attention import SpatialTransformer
def normalization(
channels: int,
num_groups: int = 32,
) -> nn.GroupNorm:
"""
GroupNorm
"""
num_groups = min(num_groups, channels)
while channels % num_groups != 0:
num_groups -= 1
return nn.GroupNorm(
num_groups=num_groups,
num_channels=channels,
eps=1e-6,
affine=True,
)
class TimeResBlock(nn.Module):
"""
Residual block conditioned on timestep embedding.
Time embedding is projected and added after the first conv.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
time_embed_dim: int,
dropout: float = 0.0,
):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.time_embed_dim = time_embed_dim
self.norm1 = normalization(in_channels)
self.conv1 = nn.Conv2d(
in_channels,
out_channels,
kernel_size=3,
stride=1,
padding=1,
)
self.time_proj = nn.Linear(
time_embed_dim,
out_channels,
)
self.norm2 = normalization(out_channels)
self.dropout = nn.Dropout(dropout)
self.conv2 = nn.Conv2d(
out_channels,
out_channels,
kernel_size=3,
stride=1,
padding=1,
)
if in_channels != out_channels:
self.shortcut = nn.Conv2d(
in_channels,
out_channels,
kernel_size=1,
stride=1,
padding=0,
)
else:
self.shortcut = nn.Identity()
def forward(
self,
x: torch.Tensor,
time_emb: torch.Tensor,
) -> torch.Tensor:
residual = self.shortcut(x)
h = self.norm1(x)
h = F.silu(h)
h = self.conv1(h)
time_out = self.time_proj(
F.silu(time_emb),
)
h = h + time_out[:, :, None, None]
h = self.norm2(h)
h = F.silu(h)
h = self.dropout(h)
h = self.conv2(h)
return h + residual
class Downsample(nn.Module):
"""
Downsample by factor 2 using strided convolution
"""
def __init__(
self,
channels: int,
):
super().__init__()
self.conv = nn.Conv2d(
channels,
channels,
kernel_size=3,
stride=2,
padding=1,
)
def forward(
self,
x: torch.Tensor,
) -> torch.Tensor:
return self.conv(x)
class Upsample(nn.Module):
"""
Upsample by factor 2 using nearest-neighbor + conv
"""
def __init__(
self,
channels: int,
):
super().__init__()
self.conv = nn.Conv2d(
channels,
channels,
kernel_size=3,
stride=1,
padding=1,
)
def forward(
self,
x: torch.Tensor,
) -> torch.Tensor:
x = F.interpolate(
x,
scale_factor=2.0,
mode="nearest",
)
x = self.conv(x)
return x
class AttentionBlock(nn.Module):
"""
Optional text-conditioned attention block.
If use_attention=True:
applies SpatialTransformer.
If use_attention=False:
identity.
"""
def __init__(
self,
channels: int,
context_dim: int,
num_heads: int,
head_dim: int,
transformer_depth: int = 1,
dropout: float = 0.0,
use_attention: bool = True,
):
super().__init__()
if use_attention:
self.block = SpatialTransformer(
channels=channels,
context_dim=context_dim,
num_heads=num_heads,
head_dim=head_dim,
depth=transformer_depth,
dropout=dropout,
)
else:
self.block = nn.Identity()
def forward(
self,
x: torch.Tensor,
context: torch.Tensor | None = None,
attention_mask: torch.Tensor | None = None,
) -> torch.Tensor:
if isinstance(self.block, nn.Identity):
return x
if context is None:
raise ValueError("AttentionBlock requires context when use_attention=True.")
return self.block(
x,
context=context,
attention_mask=attention_mask,
)
class DownBlock(nn.Module):
"""
U-Net down block.
Contains:
ResBlock(s)
optional SpatialTransformer(s)
optional downsample
Returns:
x
skip features
"""
def __init__(
self,
in_channels: int,
out_channels: int,
time_embed_dim: int,
num_res_blocks: int,
context_dim: int,
num_heads: int,
head_dim: int,
transformer_depth: int = 1,
dropout: float = 0.0,
use_attention: bool = False,
add_downsample: bool = True,
):
super().__init__()
self.resblocks = nn.ModuleList()
self.attentions = nn.ModuleList()
current_channels = in_channels
for _ in range(num_res_blocks):
self.resblocks.append(
TimeResBlock(
in_channels=current_channels,
out_channels=out_channels,
time_embed_dim=time_embed_dim,
dropout=dropout,
)
)
self.attentions.append(
AttentionBlock(
channels=out_channels,
context_dim=context_dim,
num_heads=num_heads,
head_dim=head_dim,
transformer_depth=transformer_depth,
dropout=dropout,
use_attention=use_attention,
)
)
current_channels = out_channels
if add_downsample:
self.downsample = Downsample(out_channels)
else:
self.downsample = nn.Identity()
self.out_channels = out_channels
self.add_downsample = add_downsample
def forward(
self,
x: torch.Tensor,
time_emb: torch.Tensor,
context: torch.Tensor,
attention_mask: torch.Tensor | None = None,
) -> tuple[torch.Tensor, list[torch.Tensor]]:
skips = []
for resblock, attention in zip(self.resblocks, self.attentions):
x = resblock(
x,
time_emb,
)
x = attention(
x,
context=context,
attention_mask=attention_mask,
)
skips.append(x)
x = self.downsample(x)
return x, skips
class UpBlock(nn.Module):
"""
U-Net up block.
Takes skip features from encoder path.
"""
def __init__(
self,
in_channels: int,
skip_channels: int,
out_channels: int,
time_embed_dim: int,
num_res_blocks: int,
context_dim: int,
num_heads: int,
head_dim: int,
transformer_depth: int = 1,
dropout: float = 0.0,
use_attention: bool = False,
add_upsample: bool = True,
):
super().__init__()
self.resblocks = nn.ModuleList()
self.attentions = nn.ModuleList()
current_channels = in_channels
for _ in range(num_res_blocks):
self.resblocks.append(
TimeResBlock(
in_channels=current_channels + skip_channels,
out_channels=out_channels,
time_embed_dim=time_embed_dim,
dropout=dropout,
)
)
self.attentions.append(
AttentionBlock(
channels=out_channels,
context_dim=context_dim,
num_heads=num_heads,
head_dim=head_dim,
transformer_depth=transformer_depth,
dropout=dropout,
use_attention=use_attention,
)
)
current_channels = out_channels
if add_upsample:
self.upsample = Upsample(out_channels)
else:
self.upsample = nn.Identity()
self.out_channels = out_channels
self.add_upsample = add_upsample
def forward(
self,
x: torch.Tensor,
skips: list[torch.Tensor],
time_emb: torch.Tensor,
context: torch.Tensor,
attention_mask: torch.Tensor | None = None,
) -> torch.Tensor:
for resblock, attention in zip(self.resblocks, self.attentions):
if len(skips) == 0:
raise RuntimeError("Not enough skip connections for UpBlock.")
skip = skips.pop()
x = torch.cat(
[x, skip],
dim=1,
)
x = resblock(
x,
time_emb,
)
x = attention(
x,
context=context,
attention_mask=attention_mask,
)
x = self.upsample(x)
return x
class MiddleBlock(nn.Module):
"""
U-Net bottleneck block
"""
def __init__(
self,
channels: int,
time_embed_dim: int,
context_dim: int,
num_heads: int,
head_dim: int,
transformer_depth: int = 1,
dropout: float = 0.0,
use_attention: bool = True,
):
super().__init__()
self.res1 = TimeResBlock(
in_channels=channels,
out_channels=channels,
time_embed_dim=time_embed_dim,
dropout=dropout,
)
self.attn = AttentionBlock(
channels=channels,
context_dim=context_dim,
num_heads=num_heads,
head_dim=head_dim,
transformer_depth=transformer_depth,
dropout=dropout,
use_attention=use_attention,
)
self.res2 = TimeResBlock(
in_channels=channels,
out_channels=channels,
time_embed_dim=time_embed_dim,
dropout=dropout,
)
def forward(
self,
x: torch.Tensor,
time_emb: torch.Tensor,
context: torch.Tensor,
attention_mask: torch.Tensor | None = None,
) -> torch.Tensor:
x = self.res1(
x,
time_emb,
)
x = self.attn(
x,
context=context,
attention_mask=attention_mask,
)
x = self.res2(
x,
time_emb,
)
return x