Files
vllm-omni/vllm_omni/diffusion/models/sdxl/sdxl_unet.py
T
2026-06-16 22:51:11 +08:00

872 lines
32 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import math
from collections.abc import Iterable
import torch
import torch.nn as nn
import torch.nn.functional as F
from vllm.logger import init_logger
from vllm.model_executor.layers.linear import (
ColumnParallelLinear,
MergedColumnParallelLinear,
QKVParallelLinear,
RowParallelLinear,
)
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
from vllm_omni.diffusion.attention.layer import Attention
from vllm_omni.diffusion.data import OmniDiffusionConfig
from vllm_omni.diffusion.distributed.sp_plan import (
SequenceParallelInput,
SequenceParallelOutput,
)
logger = init_logger(__name__)
def get_timestep_embedding(
timesteps: torch.Tensor,
embedding_dim: int,
max_period: int = 10000,
) -> torch.Tensor:
half_dim = embedding_dim // 2
exponent = (
-math.log(max_period) * torch.arange(0, half_dim, dtype=torch.float32, device=timesteps.device) / half_dim
)
emb = timesteps[:, None].float() * torch.exp(exponent)[None, :]
emb = torch.cat([torch.cos(emb), torch.sin(emb)], dim=-1)
if embedding_dim % 2 == 1:
emb = F.pad(emb, (0, 1))
return emb
class SDXLTimestepEmbedding(nn.Module):
def __init__(self, in_channels: int, time_embed_dim: int):
super().__init__()
self.linear_1 = nn.Linear(in_channels, time_embed_dim)
self.act = nn.SiLU()
self.linear_2 = nn.Linear(time_embed_dim, time_embed_dim)
def forward(self, sample: torch.Tensor) -> torch.Tensor:
sample = self.linear_1(sample)
sample = self.act(sample)
sample = self.linear_2(sample)
return sample
class SDXLAddTimestepEmbedding(nn.Module):
def __init__(self, addition_time_embed_dim: int, text_embed_dim: int, time_embed_dim: int):
super().__init__()
self.linear_1 = nn.Linear(text_embed_dim + addition_time_embed_dim * 6, time_embed_dim)
self.act = nn.SiLU()
self.linear_2 = nn.Linear(time_embed_dim, time_embed_dim)
def forward(self, text_embeds: torch.Tensor, time_ids: torch.Tensor) -> torch.Tensor:
time_embeds = get_timestep_embedding(time_ids.flatten(), 256)
time_embeds = time_embeds.reshape(text_embeds.shape[0], -1)
add_embeds = torch.cat([text_embeds, time_embeds], dim=-1)
add_embeds = add_embeds.to(dtype=self.linear_1.weight.dtype)
add_embeds = self.linear_1(add_embeds)
add_embeds = self.act(add_embeds)
add_embeds = self.linear_2(add_embeds)
return add_embeds
class SDXLResnetBlock2D(nn.Module):
def __init__(
self,
in_channels: int,
out_channels: int,
time_embed_dim: int,
groups: int = 32,
):
super().__init__()
self.norm1 = nn.GroupNorm(groups, in_channels, eps=1e-5)
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)
self.time_emb_proj = nn.Linear(time_embed_dim, out_channels)
self.norm2 = nn.GroupNorm(groups, out_channels, eps=1e-5)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
self.nonlinearity = nn.SiLU()
self.conv_shortcut = None
if in_channels != out_channels:
self.conv_shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1)
def forward(self, hidden_states: torch.Tensor, temb: torch.Tensor) -> torch.Tensor:
residual = hidden_states
hidden_states = self.norm1(hidden_states)
hidden_states = self.nonlinearity(hidden_states)
hidden_states = self.conv1(hidden_states)
temb = self.nonlinearity(temb)
temb = self.time_emb_proj(temb)
hidden_states = hidden_states + temb[:, :, None, None]
hidden_states = self.norm2(hidden_states)
hidden_states = self.nonlinearity(hidden_states)
hidden_states = self.conv2(hidden_states)
if self.conv_shortcut is not None:
residual = self.conv_shortcut(residual)
return hidden_states + residual
class SDXLGEGLU(nn.Module):
def __init__(self, dim_in: int, dim_out: int):
super().__init__()
self.proj = MergedColumnParallelLinear(dim_in, [dim_out, dim_out], bias=True)
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
hidden_states, _ = self.proj(hidden_states)
hidden_states, gate = hidden_states.chunk(2, dim=-1)
return hidden_states * F.gelu(gate)
class SDXLFeedForward(nn.Module):
def __init__(self, dim: int, inner_dim: int):
super().__init__()
self.geglu = SDXLGEGLU(dim, inner_dim)
self.out_proj = RowParallelLinear(inner_dim, dim, bias=True)
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
hidden_states = self.geglu(hidden_states)
hidden_states, _ = self.out_proj(hidden_states)
return hidden_states
class SDXLSelfAttention(nn.Module):
def __init__(self, dim: int, num_attention_heads: int, attention_head_dim: int, prefix: str = ""):
super().__init__()
inner_dim = num_attention_heads * attention_head_dim
self.to_qkv = QKVParallelLinear(
hidden_size=dim,
head_size=attention_head_dim,
total_num_heads=num_attention_heads,
bias=False,
)
self.to_out = nn.ModuleList(
[
RowParallelLinear(inner_dim, dim, bias=True),
]
)
self.attention = Attention(
num_heads=num_attention_heads,
head_size=attention_head_dim,
causal=False,
softmax_scale=1.0 / math.sqrt(attention_head_dim),
role="self",
prefix=prefix,
)
class SDXLCrossAttention(nn.Module):
def __init__(
self, dim: int, cross_attention_dim: int, num_attention_heads: int, attention_head_dim: int, prefix: str = ""
):
super().__init__()
inner_dim = num_attention_heads * attention_head_dim
self.to_q = ColumnParallelLinear(dim, inner_dim, bias=False)
self.to_k = ColumnParallelLinear(cross_attention_dim, inner_dim, bias=False)
self.to_v = ColumnParallelLinear(cross_attention_dim, inner_dim, bias=False)
self.to_out = nn.ModuleList(
[
RowParallelLinear(inner_dim, dim, bias=True),
]
)
self.attention = Attention(
num_heads=num_attention_heads,
head_size=attention_head_dim,
causal=False,
softmax_scale=1.0 / math.sqrt(attention_head_dim),
role="cross",
prefix=prefix,
skip_sequence_parallel=True,
)
class SDXLBasicTransformerBlock(nn.Module):
def __init__(
self,
dim: int,
num_attention_heads: int,
attention_head_dim: int,
cross_attention_dim: int,
prefix: str = "",
):
super().__init__()
self.num_attention_heads = num_attention_heads
self.attention_head_dim = attention_head_dim
self.norm1 = nn.LayerNorm(dim, elementwise_affine=True)
self.norm2 = nn.LayerNorm(dim, elementwise_affine=True)
self.norm3 = nn.LayerNorm(dim, elementwise_affine=True)
self.attn1 = SDXLSelfAttention(
dim=dim,
num_attention_heads=num_attention_heads,
attention_head_dim=attention_head_dim,
prefix=f"{prefix}.attn1",
)
self.attn2 = SDXLCrossAttention(
dim=dim,
cross_attention_dim=cross_attention_dim,
num_attention_heads=num_attention_heads,
attention_head_dim=attention_head_dim,
prefix=f"{prefix}.attn2",
)
# Feed-forward
self.ff = SDXLFeedForward(dim=dim, inner_dim=dim * 4)
def forward(
self,
hidden_states: torch.Tensor,
encoder_hidden_states: torch.Tensor,
temb: None = None,
) -> torch.Tensor:
local_num_heads = self.attn1.to_qkv.num_heads
head_dim = self.attention_head_dim
# Self-attention
norm_hidden = self.norm1(hidden_states)
qkv, _ = self.attn1.to_qkv(norm_hidden)
q, k, v = qkv.chunk(3, dim=-1)
q = q.unflatten(-1, (local_num_heads, head_dim))
k = k.unflatten(-1, (local_num_heads, head_dim))
v = v.unflatten(-1, (local_num_heads, head_dim))
attn_out = self.attn1.attention(q, k, v)
attn_out = attn_out.flatten(-2)
attn_out, _ = self.attn1.to_out[0](attn_out)
hidden_states = hidden_states + attn_out
# Cross-attention
norm_hidden = self.norm2(hidden_states)
q, _ = self.attn2.to_q(norm_hidden)
k, _ = self.attn2.to_k(encoder_hidden_states)
v, _ = self.attn2.to_v(encoder_hidden_states)
q = q.unflatten(-1, (local_num_heads, head_dim))
k = k.unflatten(-1, (local_num_heads, head_dim))
v = v.unflatten(-1, (local_num_heads, head_dim))
attn_out = self.attn2.attention(q, k, v)
attn_out = attn_out.flatten(-2)
attn_out, _ = self.attn2.to_out[0](attn_out)
hidden_states = hidden_states + attn_out
# Feed-forward
norm_hidden = self.norm3(hidden_states)
ff_out = self.ff(norm_hidden)
hidden_states = hidden_states + ff_out
return hidden_states
class SDXLTransformer2DModel(nn.Module):
def __init__(
self,
num_attention_heads: int,
attention_head_dim: int,
in_channels: int,
num_layers: int,
cross_attention_dim: int,
prefix: str = "",
):
super().__init__()
inner_dim = num_attention_heads * attention_head_dim
self.norm = nn.GroupNorm(32, in_channels, eps=1e-6, affine=True)
self.proj_in = nn.Linear(in_channels, inner_dim)
self.transformer_blocks = nn.ModuleList(
[
SDXLBasicTransformerBlock(
dim=inner_dim,
num_attention_heads=num_attention_heads,
attention_head_dim=attention_head_dim,
cross_attention_dim=cross_attention_dim,
prefix=f"{prefix}.transformer_blocks.{i}",
)
for i in range(num_layers)
]
)
self.proj_out = nn.Linear(inner_dim, in_channels)
def forward(
self,
hidden_states: torch.Tensor,
encoder_hidden_states: torch.Tensor,
) -> torch.Tensor:
batch, channels, height, width = hidden_states.shape
residual = hidden_states
hidden_states = self.norm(hidden_states)
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * width, channels)
hidden_states = self.proj_in(hidden_states)
for block in self.transformer_blocks:
hidden_states = block(hidden_states, encoder_hidden_states)
hidden_states = self.proj_out(hidden_states)
hidden_states = hidden_states.reshape(batch, height, width, channels).permute(0, 3, 1, 2)
return hidden_states + residual
class SDXLDownsample2D(nn.Module):
def __init__(self, channels: int):
super().__init__()
self.conv = nn.Conv2d(channels, channels, kernel_size=3, stride=2, padding=1)
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
return self.conv(hidden_states)
class SDXLUpsample2D(nn.Module):
def __init__(self, channels: int):
super().__init__()
self.conv = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
hidden_states = F.interpolate(hidden_states, scale_factor=2.0, mode="nearest")
return self.conv(hidden_states)
class SDXLDownBlock2D(nn.Module):
def __init__(
self,
in_channels: int,
out_channels: int,
time_embed_dim: int,
num_layers: int = 2,
add_downsample: bool = True,
):
super().__init__()
self.resnets = nn.ModuleList()
for i in range(num_layers):
res_in = in_channels if i == 0 else out_channels
self.resnets.append(SDXLResnetBlock2D(res_in, out_channels, time_embed_dim))
self.downsamplers = None
if add_downsample:
self.downsamplers = nn.ModuleList([SDXLDownsample2D(out_channels)])
def forward(
self,
hidden_states: torch.Tensor,
temb: torch.Tensor,
) -> tuple[torch.Tensor, list[torch.Tensor]]:
output_states = []
for resnet in self.resnets:
hidden_states = resnet(hidden_states, temb)
output_states.append(hidden_states)
if self.downsamplers is not None:
hidden_states = self.downsamplers[0](hidden_states)
output_states.append(hidden_states)
return hidden_states, output_states
class SDXLCrossAttnDownBlock2D(nn.Module):
def __init__(
self,
in_channels: int,
out_channels: int,
time_embed_dim: int,
num_layers: int = 2,
num_attention_heads: int = 10,
cross_attention_dim: int = 2048,
transformer_layers_per_block: int = 1,
add_downsample: bool = True,
prefix: str = "",
):
super().__init__()
attention_head_dim = out_channels // num_attention_heads
self.resnets = nn.ModuleList()
self.attentions = nn.ModuleList()
for i in range(num_layers):
res_in = in_channels if i == 0 else out_channels
self.resnets.append(SDXLResnetBlock2D(res_in, out_channels, time_embed_dim))
self.attentions.append(
SDXLTransformer2DModel(
num_attention_heads=num_attention_heads,
attention_head_dim=attention_head_dim,
in_channels=out_channels,
num_layers=transformer_layers_per_block,
cross_attention_dim=cross_attention_dim,
prefix=f"{prefix}.attentions.{i}",
)
)
self.downsamplers = None
if add_downsample:
self.downsamplers = nn.ModuleList([SDXLDownsample2D(out_channels)])
def forward(
self,
hidden_states: torch.Tensor,
temb: torch.Tensor,
encoder_hidden_states: torch.Tensor,
) -> tuple[torch.Tensor, list[torch.Tensor]]:
output_states = []
for resnet, attn in zip(self.resnets, self.attentions):
hidden_states = resnet(hidden_states, temb)
hidden_states = attn(hidden_states, encoder_hidden_states)
output_states.append(hidden_states)
if self.downsamplers is not None:
hidden_states = self.downsamplers[0](hidden_states)
output_states.append(hidden_states)
return hidden_states, output_states
class SDXLUNetMidBlock2DCrossAttn(nn.Module):
def __init__(
self,
in_channels: int,
time_embed_dim: int,
num_attention_heads: int = 20,
cross_attention_dim: int = 2048,
transformer_layers_per_block: int = 10,
prefix: str = "",
):
super().__init__()
attention_head_dim = in_channels // num_attention_heads
self.resnets = nn.ModuleList(
[
SDXLResnetBlock2D(in_channels, in_channels, time_embed_dim),
SDXLResnetBlock2D(in_channels, in_channels, time_embed_dim),
]
)
self.attentions = nn.ModuleList(
[
SDXLTransformer2DModel(
num_attention_heads=num_attention_heads,
attention_head_dim=attention_head_dim,
in_channels=in_channels,
num_layers=transformer_layers_per_block,
cross_attention_dim=cross_attention_dim,
prefix=f"{prefix}.attentions.0",
)
]
)
def forward(
self,
hidden_states: torch.Tensor,
temb: torch.Tensor,
encoder_hidden_states: torch.Tensor,
) -> torch.Tensor:
hidden_states = self.resnets[0](hidden_states, temb)
hidden_states = self.attentions[0](hidden_states, encoder_hidden_states)
hidden_states = self.resnets[1](hidden_states, temb)
return hidden_states
class SDXLUpBlock2D(nn.Module):
def __init__(
self,
in_channels: int,
prev_output_channel: int,
out_channels: int,
time_embed_dim: int,
num_layers: int = 3,
add_upsample: bool = True,
resnet_in_channels_list: list[int] | None = None,
):
super().__init__()
self.resnets = nn.ModuleList()
for i in range(num_layers):
if resnet_in_channels_list is not None:
res_in = resnet_in_channels_list[i]
else:
skip_channels = prev_output_channel if i == 0 else out_channels
res_in = (in_channels if i == 0 else out_channels) + skip_channels
self.resnets.append(SDXLResnetBlock2D(res_in, out_channels, time_embed_dim))
self.upsamplers = None
if add_upsample:
self.upsamplers = nn.ModuleList([SDXLUpsample2D(out_channels)])
def forward(
self,
hidden_states: torch.Tensor,
temb: torch.Tensor,
res_hidden_states_tuple: tuple[torch.Tensor, ...],
) -> torch.Tensor:
for resnet in self.resnets:
res_hidden = res_hidden_states_tuple[-1]
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
hidden_states = torch.cat([hidden_states, res_hidden], dim=1)
hidden_states = resnet(hidden_states, temb)
if self.upsamplers is not None:
hidden_states = self.upsamplers[0](hidden_states)
return hidden_states
class SDXLCrossAttnUpBlock2D(nn.Module):
def __init__(
self,
in_channels: int,
prev_output_channel: int,
out_channels: int,
time_embed_dim: int,
num_layers: int = 3,
num_attention_heads: int = 10,
cross_attention_dim: int = 2048,
transformer_layers_per_block: int = 1,
add_upsample: bool = True,
prefix: str = "",
resnet_in_channels_list: list[int] | None = None,
):
super().__init__()
attention_head_dim = out_channels // num_attention_heads
self.resnets = nn.ModuleList()
self.attentions = nn.ModuleList()
for i in range(num_layers):
if resnet_in_channels_list is not None:
res_in = resnet_in_channels_list[i]
else:
skip_channels = prev_output_channel if i == 0 else out_channels
res_in = (in_channels if i == 0 else out_channels) + skip_channels
self.resnets.append(SDXLResnetBlock2D(res_in, out_channels, time_embed_dim))
self.attentions.append(
SDXLTransformer2DModel(
num_attention_heads=num_attention_heads,
attention_head_dim=attention_head_dim,
in_channels=out_channels,
num_layers=transformer_layers_per_block,
cross_attention_dim=cross_attention_dim,
prefix=f"{prefix}.attentions.{i}",
)
)
self.upsamplers = None
if add_upsample:
self.upsamplers = nn.ModuleList([SDXLUpsample2D(out_channels)])
def forward(
self,
hidden_states: torch.Tensor,
temb: torch.Tensor,
encoder_hidden_states: torch.Tensor,
res_hidden_states_tuple: tuple[torch.Tensor, ...],
) -> torch.Tensor:
for resnet, attn in zip(self.resnets, self.attentions):
res_hidden = res_hidden_states_tuple[-1]
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
hidden_states = torch.cat([hidden_states, res_hidden], dim=1)
hidden_states = resnet(hidden_states, temb)
hidden_states = attn(hidden_states, encoder_hidden_states)
if self.upsamplers is not None:
hidden_states = self.upsamplers[0](hidden_states)
return hidden_states
def _build_sdxl_sp_plan() -> dict:
"""Build _sp_plan for all attention modules in the SDXL UNet.
Shards hidden_states at the first transformer block input and gathers
at proj_out for each SDXLTransformer2DModel instance.
"""
plan: dict = {}
attn_paths = [
# down_blocks[1]: 2 attention modules
"down_blocks.1.attentions.0",
"down_blocks.1.attentions.1",
# down_blocks[2]: 2 attention modules
"down_blocks.2.attentions.0",
"down_blocks.2.attentions.1",
# mid_block: 1 attention module
"mid_block.attentions.0",
# up_blocks[0]: 3 attention modules
"up_blocks.0.attentions.0",
"up_blocks.0.attentions.1",
"up_blocks.0.attentions.2",
# up_blocks[1]: 3 attention modules
"up_blocks.1.attentions.0",
"up_blocks.1.attentions.1",
"up_blocks.1.attentions.2",
]
for path in attn_paths:
plan[f"{path}.transformer_blocks.0"] = {
"hidden_states": SequenceParallelInput(split_dim=1, expected_dims=3, auto_pad=True),
}
plan[f"{path}.proj_out"] = SequenceParallelOutput(gather_dim=1, expected_dims=3)
return plan
class SDXLUNet2DConditionModel(nn.Module):
_repeated_blocks = ["SDXLBasicTransformerBlock", "SDXLResnetBlock2D"]
_layerwise_offload_blocks_attrs = ["down_blocks", "up_blocks"]
_sp_plan = _build_sdxl_sp_plan()
@staticmethod
def _is_shardable_block(name: str, module) -> bool:
"""Match transformer blocks and ResNet blocks for HSDP sharding."""
if not name.split(".")[-1].isdigit():
return False
return "transformer_blocks" in name or "resnets" in name
_hsdp_shard_conditions = [_is_shardable_block]
def __init__(self, *, od_config: OmniDiffusionConfig):
super().__init__()
self.od_config = od_config
# SDXL Base configuration
model_channels = 320
time_embed_dim = model_channels * 4 # 1280
block_out_channels = [320, 640, 1280]
layers_per_block = 2
transformer_layers_per_block = [1, 2, 10]
cross_attention_dim = 2048
addition_time_embed_dim = 256
num_head_channels = 64
self.in_channels = 4
self.out_channels = 4
# Input convolution
self.conv_in = nn.Conv2d(4, model_channels, kernel_size=3, padding=1)
# Time embedding
self.time_proj_dim = model_channels
self.time_embedding = SDXLTimestepEmbedding(model_channels, time_embed_dim)
# Addition embedding (for pooled text + crop/size conditioning)
self.add_embedding = SDXLAddTimestepEmbedding(
addition_time_embed_dim=addition_time_embed_dim,
text_embed_dim=1280,
time_embed_dim=time_embed_dim,
)
# Down blocks
self.down_blocks = nn.ModuleList()
# Level 0: DownBlock2D (no attention)
self.down_blocks.append(
SDXLDownBlock2D(
in_channels=model_channels,
out_channels=block_out_channels[0],
time_embed_dim=time_embed_dim,
num_layers=layers_per_block,
add_downsample=True,
)
)
# Level 1: CrossAttnDownBlock2D
self.down_blocks.append(
SDXLCrossAttnDownBlock2D(
in_channels=block_out_channels[0],
out_channels=block_out_channels[1],
time_embed_dim=time_embed_dim,
num_layers=layers_per_block,
num_attention_heads=block_out_channels[1] // num_head_channels,
cross_attention_dim=cross_attention_dim,
transformer_layers_per_block=transformer_layers_per_block[1],
add_downsample=True,
prefix="down_blocks.1",
)
)
# Level 2: CrossAttnDownBlock2D (no downsampler)
self.down_blocks.append(
SDXLCrossAttnDownBlock2D(
in_channels=block_out_channels[1],
out_channels=block_out_channels[2],
time_embed_dim=time_embed_dim,
num_layers=layers_per_block,
num_attention_heads=block_out_channels[2] // num_head_channels,
cross_attention_dim=cross_attention_dim,
transformer_layers_per_block=transformer_layers_per_block[2],
add_downsample=False,
prefix="down_blocks.2",
)
)
# Mid block
self.mid_block = SDXLUNetMidBlock2DCrossAttn(
in_channels=block_out_channels[2],
time_embed_dim=time_embed_dim,
num_attention_heads=block_out_channels[2] // num_head_channels,
cross_attention_dim=cross_attention_dim,
transformer_layers_per_block=transformer_layers_per_block[2],
prefix="mid_block",
)
# Up blocks (reversed, with skip connections)
# Skip connection channels consumed from down_block_res_samples (right to left):
# down_block_res_samples channels = [320, 320, 320, 320, 640, 640, 640, 1280, 1280]
# up_blocks[0] consumes last 3: skips are [1280, 1280, 640] (consumed from right)
# up_blocks[1] consumes next 3: skips are [640, 640, 320] (consumed from right)
# up_blocks[2] consumes next 3: skips are [320, 320, 320] (consumed from right)
self.up_blocks = nn.ModuleList()
# Level 0: CrossAttnUpBlock2D (mirrors down_blocks[2])
# Input from mid: 1280, skips (consumed right-to-left): 1280, 1280, 640
self.up_blocks.append(
SDXLCrossAttnUpBlock2D(
in_channels=block_out_channels[2],
prev_output_channel=block_out_channels[2],
out_channels=block_out_channels[2],
time_embed_dim=time_embed_dim,
num_layers=layers_per_block + 1,
num_attention_heads=block_out_channels[2] // num_head_channels,
cross_attention_dim=cross_attention_dim,
transformer_layers_per_block=transformer_layers_per_block[2],
add_upsample=True,
prefix="up_blocks.0",
resnet_in_channels_list=[1280 + 1280, 1280 + 1280, 1280 + 640],
)
)
# Level 1: CrossAttnUpBlock2D (mirrors down_blocks[1])
# Input from up_blocks[0]: 1280, skips (consumed right-to-left): 640, 640, 320
self.up_blocks.append(
SDXLCrossAttnUpBlock2D(
in_channels=block_out_channels[2],
prev_output_channel=block_out_channels[1],
out_channels=block_out_channels[1],
time_embed_dim=time_embed_dim,
num_layers=layers_per_block + 1,
num_attention_heads=block_out_channels[1] // num_head_channels,
cross_attention_dim=cross_attention_dim,
transformer_layers_per_block=transformer_layers_per_block[1],
add_upsample=True,
prefix="up_blocks.1",
resnet_in_channels_list=[1280 + 640, 640 + 640, 640 + 320],
)
)
# Level 2: UpBlock2D (mirrors down_blocks[0], no attention, no upsample)
# Input from up_blocks[1]: 640, skips (consumed right-to-left): 320, 320, 320
self.up_blocks.append(
SDXLUpBlock2D(
in_channels=block_out_channels[1],
prev_output_channel=block_out_channels[0],
out_channels=block_out_channels[0],
time_embed_dim=time_embed_dim,
num_layers=layers_per_block + 1,
add_upsample=False,
resnet_in_channels_list=[640 + 320, 320 + 320, 320 + 320],
)
)
# Output
self.conv_norm_out = nn.GroupNorm(32, block_out_channels[0], eps=1e-5)
self.conv_act = nn.SiLU()
self.conv_out = nn.Conv2d(block_out_channels[0], 4, kernel_size=3, padding=1)
def forward(
self,
hidden_states: torch.Tensor,
timestep: torch.Tensor,
encoder_hidden_states: torch.Tensor,
added_cond_kwargs: dict,
return_dict: bool = False,
) -> tuple[torch.Tensor]:
# Time embedding
dtype = hidden_states.dtype
t_emb = get_timestep_embedding(timestep, self.time_proj_dim).to(dtype=dtype)
emb = self.time_embedding(t_emb)
# Addition embedding (pooled text + size/crop)
text_embeds = added_cond_kwargs["text_embeds"].to(dtype=dtype)
time_ids = added_cond_kwargs["time_ids"]
add_emb = self.add_embedding(text_embeds, time_ids)
emb = emb + add_emb
# Input conv
hidden_states = self.conv_in(hidden_states)
# Down blocks
down_block_res_samples = [hidden_states]
for down_block in self.down_blocks:
if isinstance(down_block, SDXLCrossAttnDownBlock2D):
hidden_states, res_samples = down_block(hidden_states, emb, encoder_hidden_states)
else:
hidden_states, res_samples = down_block(hidden_states, emb)
down_block_res_samples.extend(res_samples)
# Mid block
hidden_states = self.mid_block(hidden_states, emb, encoder_hidden_states)
# Up blocks
for up_block in self.up_blocks:
# Determine how many skip connections this block needs
n_resnets = len(up_block.resnets)
res_samples = tuple(down_block_res_samples[-n_resnets:])
down_block_res_samples = down_block_res_samples[:-n_resnets]
if isinstance(up_block, SDXLCrossAttnUpBlock2D):
hidden_states = up_block(hidden_states, emb, encoder_hidden_states, res_samples)
else:
hidden_states = up_block(hidden_states, emb, res_samples)
# Output
hidden_states = self.conv_norm_out(hidden_states)
hidden_states = self.conv_act(hidden_states)
hidden_states = self.conv_out(hidden_states)
return (hidden_states,)
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
stacked_params_mapping = [
# Self-attention QKV fusion
(".attn1.to_qkv", ".attn1.to_q", "q"),
(".attn1.to_qkv", ".attn1.to_k", "k"),
(".attn1.to_qkv", ".attn1.to_v", "v"),
]
self.stacked_params_mapping = stacked_params_mapping
params_dict = dict(self.named_parameters())
loaded_params: set[str] = set()
for name, loaded_weight in weights:
# Handle GEGLU: diffusers stores as ff.net.0.proj.weight/bias
# MergedColumnParallelLinear needs the two halves loaded separately
if ".ff.net.0.proj." in name:
name = name.replace(".ff.net.0.proj.", ".ff.geglu.proj.")
if name in params_dict:
param = params_dict[name]
weight_loader = param.weight_loader
half = loaded_weight.shape[0] // 2
weight_loader(param, loaded_weight[:half], 0)
weight_loader(param, loaded_weight[half:], 1)
loaded_params.add(name)
continue
elif ".ff.net.2." in name:
name = name.replace(".ff.net.2.", ".ff.out_proj.")
handled = False
for param_name, weight_name, shard_id in stacked_params_mapping:
if weight_name not in name:
continue
name = name.replace(weight_name, param_name)
if name not in params_dict:
break
param = params_dict[name]
weight_loader = param.weight_loader
weight_loader(param, loaded_weight, shard_id)
loaded_params.add(name)
handled = True
break
if not handled:
if name in params_dict:
param = params_dict[name]
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight)
loaded_params.add(name)
return loaded_params