multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
ac69be1 verified
Raw
History Blame Contribute Delete
26.4 kB
"""
Modified Wan2.2 Diffusion Transformer for LoomVideo.
Extends the HuggingFace Diffusers Wan transformer to support:
- Separate self-attention and cross-attention execution paths
- Variable-length sequence handling with cu_seq_lens
- Source video conditioning via learnable patch embedding
- Reference image/video conditioning with custom temporal RoPE offsets
Reference: https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/transformers/transformer_wan.py
"""
import math
from typing import Optional, Tuple, List
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from diffusers.configuration_utils import register_to_config
from diffusers.utils import logging
from diffusers.models.attention import FeedForward
from diffusers.models.embeddings import get_1d_rotary_pos_embed
from diffusers.models.normalization import FP32LayerNorm
from diffusers.models.transformers.transformer_wan import (
WanAttention,
WanRotaryPosEmbed,
WanTimeTextImageEmbedding,
WanAttnProcessor,
WanTransformerBlock,
WanTransformer3DModel,
_get_qkv_projections,
)
logger = logging.get_logger(__name__)
class WanAttnProcessor(WanAttnProcessor):
"""Attention processor with QKV extraction for cross-attention in LoomVideo."""
_attention_backend = None
def __init__(self):
super().__init__()
if not hasattr(F, "scaled_dot_product_attention"):
raise ImportError("WanAttnProcessor requires PyTorch 2.0+.")
def get_qkv(
self,
attn: "WanAttention",
hidden_states: torch.Tensor,
encoder_hidden_states: Optional[torch.Tensor] = None,
rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
):
"""
Extract Q, K, V projections with optional rotary embeddings.
Returns:
Tuple of (query, key, value, encoder_hidden_states, encoder_hidden_states_img).
"""
encoder_hidden_states_img = None
if attn.add_k_proj is not None:
# 512 is the context length of the text encoder
image_context_length = encoder_hidden_states.shape[1] - 512
encoder_hidden_states_img = encoder_hidden_states[:, :image_context_length]
encoder_hidden_states = encoder_hidden_states[:, image_context_length:]
query, key, value = _get_qkv_projections(
attn, hidden_states, encoder_hidden_states
)
query = attn.norm_q(query)
key = attn.norm_k(key)
query = query.unflatten(2, (attn.heads, -1))
key = key.unflatten(2, (attn.heads, -1))
value = value.unflatten(2, (attn.heads, -1))
if rotary_emb is not None:
def apply_rotary_emb(
hidden_states: torch.Tensor,
freqs_cos: torch.Tensor,
freqs_sin: torch.Tensor,
):
x1, x2 = hidden_states.unflatten(-1, (-1, 2)).unbind(-1)
cos = freqs_cos[..., 0::2]
sin = freqs_sin[..., 1::2]
out = torch.empty_like(hidden_states)
out[..., 0::2] = x1 * cos - x2 * sin
out[..., 1::2] = x1 * sin + x2 * cos
return out.type_as(hidden_states)
query = apply_rotary_emb(query, *rotary_emb)
key = apply_rotary_emb(key, *rotary_emb)
# [B, L, Num_heads, Head_dims] -> [B, Num_heads, L, Head_dims]
query = query.transpose(1, 2)
key = key.transpose(1, 2)
value = value.transpose(1, 2)
return query, key, value, encoder_hidden_states, encoder_hidden_states_img
class WanTransformerBlock(WanTransformerBlock):
"""
Extended transformer block with separate self-attention and cross-attention paths.
Adds methods for split execution:
- forward_selfattn: self-attention only (for training with flash cross-attn)
- forward_crossattn_later_layer: cross-attn + FFN (paired with forward_selfattn)
"""
def __init__(
self,
dim: int,
ffn_dim: int,
num_heads: int,
qk_norm: str = "rms_norm_across_heads",
cross_attn_norm: bool = False,
eps: float = 1e-6,
added_kv_proj_dim: Optional[int] = None,
):
super().__init__(
dim, ffn_dim, num_heads, qk_norm, cross_attn_norm, eps, added_kv_proj_dim
)
self.dim = dim
self.num_heads = num_heads
self.eps = eps
# 1. Self-attention
self.norm1 = FP32LayerNorm(dim, eps, elementwise_affine=False)
self.attn1 = WanAttention(
dim=dim,
heads=num_heads,
dim_head=dim // num_heads,
eps=eps,
cross_attention_dim_head=None,
processor=WanAttnProcessor(),
)
# 2. Cross-attention
self.attn2 = WanAttention(
dim=dim,
heads=num_heads,
dim_head=dim // num_heads,
eps=eps,
added_kv_proj_dim=added_kv_proj_dim,
cross_attention_dim_head=dim // num_heads,
processor=WanAttnProcessor(),
)
self.norm2 = (
FP32LayerNorm(dim, eps, elementwise_affine=True)
if cross_attn_norm
else nn.Identity()
)
# 3. Feed-forward
self.ffn = FeedForward(dim, inner_dim=ffn_dim, activation_fn="gelu-approximate")
self.norm3 = FP32LayerNorm(dim, eps, elementwise_affine=False)
self.scale_shift_table = nn.Parameter(torch.randn(1, 6, dim) / dim**0.5)
def _parse_temb(self, temb: torch.Tensor):
"""Parse timestep embedding into shift/scale/gate components."""
if temb.ndim == 4:
shift_msa, scale_msa, gate_msa, c_shift_msa, c_scale_msa, c_gate_msa = (
self.scale_shift_table.unsqueeze(0) + temb.float()
).chunk(6, dim=2)
shift_msa = shift_msa.squeeze(2)
scale_msa = scale_msa.squeeze(2)
gate_msa = gate_msa.squeeze(2)
c_shift_msa = c_shift_msa.squeeze(2)
c_scale_msa = c_scale_msa.squeeze(2)
c_gate_msa = c_gate_msa.squeeze(2)
else:
shift_msa, scale_msa, gate_msa, c_shift_msa, c_scale_msa, c_gate_msa = (
self.scale_shift_table + temb.float()
).chunk(6, dim=1)
return shift_msa, scale_msa, gate_msa, c_shift_msa, c_scale_msa, c_gate_msa
def forward_selfattn(
self,
hidden_states: torch.Tensor,
temb: torch.Tensor,
rotary_emb: torch.Tensor,
cu_seq_lens: list = None,
) -> torch.Tensor:
"""
Execute only the self-attention portion of this block.
Args:
hidden_states: Input tensor.
temb: Timestep embedding (adaptive normalization parameters).
rotary_emb: Rotary position embeddings.
cu_seq_lens: Cumulative sequence lengths for per-sample attention.
Returns:
Hidden states after self-attention (before cross-attention and FFN).
"""
shift_msa, scale_msa, gate_msa, _, _, _ = self._parse_temb(temb)
norm_hidden_states = (self.norm1(hidden_states.float()) * (1 + scale_msa) + shift_msa).type_as(hidden_states)
if cu_seq_lens is not None and len(cu_seq_lens) > 2:
# Per-sample self-attention for variable-length sequences
attn_outputs = []
for i in range(len(cu_seq_lens) - 1):
start = int(cu_seq_lens[i])
end = int(cu_seq_lens[i + 1])
sample_norm_hidden = norm_hidden_states[:, start:end, :]
if isinstance(rotary_emb, (list, tuple)):
sample_rotary = [r[:, start:end, ...] if r is not None else None for r in rotary_emb]
elif rotary_emb is not None:
sample_rotary = rotary_emb[:, start:end, ...]
else:
sample_rotary = None
sample_attn_output = self.attn1(sample_norm_hidden, None, None, sample_rotary)
attn_outputs.append(sample_attn_output)
attn_output = torch.cat(attn_outputs, dim=1)
else:
attn_output = self.attn1(norm_hidden_states, None, None, rotary_emb)
hidden_states = (hidden_states.float() + attn_output * gate_msa).type_as(hidden_states)
return hidden_states
def forward_crossattn_later_layer(
self,
hidden_states: torch.Tensor,
attn_output: torch.Tensor,
temb: torch.Tensor,
) -> torch.Tensor:
"""
Execute cross-attention residual addition and feed-forward network.
Args:
hidden_states: Current hidden states (after self-attention).
attn_output: Cross-attention output to be added as residual.
temb: Timestep embedding for adaptive normalization.
Returns:
Hidden states after cross-attention residual and FFN.
"""
_, _, _, c_shift_msa, c_scale_msa, c_gate_msa = self._parse_temb(temb)
hidden_states = hidden_states + attn_output
# Feed-forward network
norm_hidden_states = (self.norm3(hidden_states.float()) * (1 + c_scale_msa) + c_shift_msa).type_as(hidden_states)
ff_output = self.ffn(norm_hidden_states)
hidden_states = (hidden_states.float() + ff_output.float() * c_gate_msa).type_as(hidden_states)
return hidden_states
class WanTransformer3DModel(WanTransformer3DModel):
"""
Extended Wan 3D Transformer with source/reference conditioning support.
Adds:
- Source video conditioning via learnable patch embedding
- Reference conditioning with custom temporal RoPE offsets
- Separate early-layer and output-projection methods for flexible fusion
"""
_supports_gradient_checkpointing = True
_skip_layerwise_casting_patterns = ["patch_embedding", "condition_embedder", "norm"]
_no_split_modules = ["WanTransformerBlock"]
_keep_in_fp32_modules = [
"time_embedder",
"scale_shift_table",
"norm1",
"norm2",
"norm3",
]
_keys_to_ignore_on_load_unexpected = ["norm_added_q"]
_repeated_blocks = ["WanTransformerBlock"]
@register_to_config
def __init__(
self,
patch_size: Tuple[int] = (1, 2, 2),
num_attention_heads: int = 40,
attention_head_dim: int = 128,
in_channels: int = 16,
out_channels: int = 16,
text_dim: int = 4096,
freq_dim: int = 256,
ffn_dim: int = 13824,
num_layers: int = 40,
cross_attn_norm: bool = True,
qk_norm: Optional[str] = "rms_norm_across_heads",
eps: float = 1e-6,
image_dim: Optional[int] = None,
added_kv_proj_dim: Optional[int] = None,
rope_max_seq_len: int = 1024,
pos_embed_seq_len: Optional[int] = None,
):
super().__init__(
patch_size,
num_attention_heads,
attention_head_dim,
in_channels,
out_channels,
text_dim,
freq_dim,
ffn_dim,
num_layers,
cross_attn_norm,
qk_norm,
eps,
image_dim,
added_kv_proj_dim,
rope_max_seq_len,
pos_embed_seq_len,
)
inner_dim = num_attention_heads * attention_head_dim
out_channels = out_channels or in_channels
self.in_channels = in_channels
self.ffn_dim = ffn_dim
self.qk_norm = qk_norm
self.cross_attn_norm = cross_attn_norm
self.attention_head_dim = attention_head_dim
self.num_attention_heads = num_attention_heads
self.eps = eps
self.added_kv_proj_dim = added_kv_proj_dim
self.num_layers = num_layers
self.inner_dim = inner_dim
# 1. Patch & position embedding
self.rope = WanRotaryPosEmbed(attention_head_dim, patch_size, rope_max_seq_len)
self.patch_embedding = nn.Conv3d(
in_channels, inner_dim, kernel_size=patch_size, stride=patch_size
)
# 2. Condition embeddings
self.condition_embedder = WanTimeTextImageEmbedding(
dim=inner_dim,
time_freq_dim=freq_dim,
time_proj_dim=inner_dim * 6,
text_embed_dim=text_dim,
image_embed_dim=image_dim,
pos_embed_seq_len=pos_embed_seq_len,
)
# 3. Transformer blocks
self.blocks = nn.ModuleList(
[
WanTransformerBlock(
inner_dim,
ffn_dim,
num_attention_heads,
qk_norm,
cross_attn_norm,
eps,
added_kv_proj_dim,
)
for _ in range(num_layers)
]
)
# 4. Output norm & projection
self.norm_out = FP32LayerNorm(inner_dim, eps, elementwise_affine=False)
self.proj_out = nn.Linear(inner_dim, out_channels * math.prod(patch_size))
self.scale_shift_table = nn.Parameter(
torch.randn(1, 2, inner_dim) / inner_dim**0.5
)
self.gradient_checkpointing = True
def compute_ref_rotary_emb(
self,
ref_shape: Tuple[int, ...],
time_offset: int,
device: torch.device,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Compute 3D RoPE for a reference latent with a custom temporal offset.
Reference frames share the same temporal position (given by time_offset),
while spatial dimensions use normal indices starting from 0.
Args:
ref_shape: (B, C, T, H, W) shape of the reference latent tensor.
time_offset: Temporal position index for all frames (e.g., -10, -20).
device: Target device.
Returns:
Tuple of (freqs_cos, freqs_sin) tensors.
"""
_, _, num_frames, height, width = ref_shape
p_t, p_h, p_w = self.config.patch_size
ppf = num_frames // p_t
pph = height // p_h
ppw = width // p_w
rope_module = self.rope
freqs_dtype = torch.float64
# Temporal: all frames share the same time_offset
time_pos = np.array([time_offset] * ppf, dtype=np.float64)
freq_cos_t, freq_sin_t = get_1d_rotary_pos_embed(
rope_module.t_dim, time_pos, theta=10000.0,
use_real=True, repeat_interleave_real=True, freqs_dtype=freqs_dtype,
)
# Spatial: normal indices [0, 1, ...]
split_sizes = [rope_module.t_dim, rope_module.h_dim, rope_module.w_dim]
precomputed_cos = rope_module.freqs_cos.split(split_sizes, dim=1)
precomputed_sin = rope_module.freqs_sin.split(split_sizes, dim=1)
freq_cos_h = precomputed_cos[1][:pph]
freq_sin_h = precomputed_sin[1][:pph]
freq_cos_w = precomputed_cos[2][:ppw]
freq_sin_w = precomputed_sin[2][:ppw]
# Broadcast to (ppf, pph, ppw, dim_*)
freq_cos_t = freq_cos_t.to(device).view(ppf, 1, 1, -1).expand(ppf, pph, ppw, -1)
freq_cos_h = freq_cos_h.to(device).view(1, pph, 1, -1).expand(ppf, pph, ppw, -1)
freq_cos_w = freq_cos_w.to(device).view(1, 1, ppw, -1).expand(ppf, pph, ppw, -1)
freq_sin_t = freq_sin_t.to(device).view(ppf, 1, 1, -1).expand(ppf, pph, ppw, -1)
freq_sin_h = freq_sin_h.to(device).view(1, pph, 1, -1).expand(ppf, pph, ppw, -1)
freq_sin_w = freq_sin_w.to(device).view(1, 1, ppw, -1).expand(ppf, pph, ppw, -1)
freqs_cos = torch.cat([freq_cos_t, freq_cos_h, freq_cos_w], dim=-1).reshape(1, ppf * pph * ppw, 1, -1)
freqs_sin = torch.cat([freq_sin_t, freq_sin_h, freq_sin_w], dim=-1).reshape(1, ppf * pph * ppw, 1, -1)
return freqs_cos, freqs_sin
def forward_early_layers(
self,
hidden_states: List[torch.Tensor],
timestep: torch.LongTensor,
encoder_hidden_states: torch.Tensor,
source_hidden_states: Optional[List[torch.Tensor]] = None,
source_scale: Optional[torch.Tensor] = None,
ref_hidden_states: Optional[List[torch.Tensor]] = None,
):
"""
Process patch embedding, timestep conditioning, source/ref concatenation.
Prepares all inputs for the main transformer blocks by:
1. Applying patch embedding to each latent sample
2. Adding source conditioning (scaled by timestep)
3. Concatenating reference latents with custom temporal RoPE
4. Computing timestep projections
Args:
hidden_states: List of latent tensors (None for non-generation samples).
timestep: Diffusion timestep for each generation sample.
encoder_hidden_states: T5 text encoder hidden states.
source_hidden_states: Optional source video latents.
source_scale: Timestep-dependent scale for source conditioning.
ref_hidden_states: Optional list of reference latent lists.
Returns:
Tuple of processed tensors and metadata for the transformer blocks.
"""
hidden_states_list = []
shape_list = []
rotary_emb_cos = []
rotary_emb_sin = []
cu_seq_lens = [0]
sample_index = []
seq_lens = []
gen_seq_lens = []
for index, hidden_state in enumerate(hidden_states):
if hidden_state is not None:
valid_sample_idx = len(seq_lens)
shape = hidden_state.shape
hidden_state = self.patch_embedding(hidden_state)
hidden_state = hidden_state.flatten(2).transpose(1, 2) # [1, L, C]
# Add source video conditioning
if (source_hidden_states is not None
and source_hidden_states[index] is not None
and source_scale is not None
and hasattr(self, 'source_patch_embedding')):
source_input = source_hidden_states[index]
source_encoded = self.source_patch_embedding(source_input)
source_encoded = source_encoded.flatten(2).transpose(1, 2)
source_scale_idx = index if source_scale.shape[0] == len(hidden_states) else valid_sample_idx
hidden_state = hidden_state + source_encoded * source_scale[source_scale_idx]
# Record original sequence length (for loss computation, excluding ref tokens)
original_seq_len = hidden_state.shape[1]
gen_seq_lens.append(original_seq_len)
# Compute rotary embeddings for the main latent
fake_tensor = torch.empty(shape, device=hidden_state.device, dtype=hidden_state.dtype)
rotary_emb = self.rope(fake_tensor)
# Concatenate reference latents with negative temporal offsets
if (ref_hidden_states is not None and ref_hidden_states[index] is not None):
ref_list = ref_hidden_states[index]
for ref_idx, ref_input in enumerate(ref_list):
ref_encoded = self.patch_embedding(ref_input)
ref_encoded = ref_encoded.flatten(2).transpose(1, 2)
hidden_state = torch.cat([hidden_state, ref_encoded], dim=1)
# Negative temporal offset: -10, -20, -30, ... for each ref
ref_time_offset = -10 * (ref_idx + 1)
ref_rotary_emb = self.compute_ref_rotary_emb(
ref_shape=ref_input.shape,
time_offset=ref_time_offset,
device=hidden_state.device,
)
rotary_emb = (
torch.cat([rotary_emb[0], ref_rotary_emb[0]], dim=1),
torch.cat([rotary_emb[1], ref_rotary_emb[1]], dim=1),
)
hidden_states_list.append(hidden_state)
rotary_emb_cos.append(rotary_emb[0])
rotary_emb_sin.append(rotary_emb[1])
cu_seq_lens.append(cu_seq_lens[-1] + hidden_state.shape[1])
sample_index.append(index)
shape_list.append(shape)
seq_lens.append(hidden_state.shape[1])
hidden_states = torch.cat(hidden_states_list, dim=1) # [1, sum(L), C]
rotary_emb_cos = torch.cat(rotary_emb_cos, dim=1)
rotary_emb_sin = torch.cat(rotary_emb_sin, dim=1)
rotary_emb = (rotary_emb_cos, rotary_emb_sin)
# Timestep conditioning
if timestep.ndim == 2:
ts_seq_len = timestep.shape[1]
timestep = timestep.flatten()
else:
ts_seq_len = None
temb, timestep_proj, encoder_hidden_states, _ = (
self.condition_embedder(
timestep,
encoder_hidden_states,
timestep_seq_len=ts_seq_len,
)
)
if ts_seq_len is not None:
timestep_proj = timestep_proj.unflatten(2, (6, -1))
else:
timestep_proj = timestep_proj.unflatten(1, (6, -1))
# Expand timestep projections to match sequence lengths (zero for ref tokens)
num_valid_samples = len(seq_lens)
if timestep_proj.shape[0] == num_valid_samples and num_valid_samples > 0:
expanded_timestep_proj = []
expanded_temb = []
for i, length in enumerate(seq_lens):
gen_len = gen_seq_lens[i]
ref_len = length - gen_len
if ts_seq_len is None:
expanded_timestep_proj.append(timestep_proj[i:i + 1].expand(gen_len, -1, -1))
if temb is not None:
expanded_temb.append(temb[i:i + 1].expand(gen_len, -1))
# Zero timestep embedding for ref tokens
if ref_len > 0:
expanded_timestep_proj.append(
torch.zeros(ref_len, timestep_proj.shape[1], timestep_proj.shape[2],
device=timestep_proj.device, dtype=timestep_proj.dtype)
)
if temb is not None:
expanded_temb.append(
torch.zeros(ref_len, temb.shape[1], device=temb.device, dtype=temb.dtype)
)
else:
expanded_timestep_proj.append(timestep_proj[i])
if temb is not None:
expanded_temb.append(temb[i])
if ts_seq_len is None:
timestep_proj = torch.cat(expanded_timestep_proj, dim=0).unsqueeze(0)
if temb is not None:
temb = torch.cat(expanded_temb, dim=0).unsqueeze(0)
else:
timestep_proj = torch.cat(expanded_timestep_proj, dim=0).unsqueeze(0)
if temb is not None:
temb = torch.cat(expanded_temb, dim=0).unsqueeze(0)
return (
hidden_states,
encoder_hidden_states,
timestep_proj,
rotary_emb,
temb,
shape_list,
cu_seq_lens,
sample_index,
gen_seq_lens,
)
def get_output(self, hidden_states: torch.Tensor, temb: torch.Tensor, cu_seq_lens, shape, gen_seq_lens=None):
"""
Project hidden states back to pixel space via unpatchify.
Args:
hidden_states: Transformer output tensor.
temb: Timestep embedding for final adaptive normalization.
cu_seq_lens: Cumulative sequence lengths.
shape: Original latent shapes per sample.
gen_seq_lens: Original sequence lengths (excluding ref tokens).
If provided, only these tokens are projected (ref tokens discarded).
Returns:
List of output tensors, one per sample.
"""
output_list = []
for index in range(len(cu_seq_lens) - 1):
start_idx = cu_seq_lens[index]
# Only project original tokens (exclude ref tokens)
if gen_seq_lens is not None:
end_idx = cu_seq_lens[index] + gen_seq_lens[index]
else:
end_idx = cu_seq_lens[index + 1]
hidden_state = hidden_states[:, start_idx:end_idx, :]
if temb.ndim == 3 and temb.shape[1] == hidden_states.shape[1]:
sample_temb = temb[:, start_idx:end_idx, :]
elif temb.ndim == 2 and temb.shape[0] > 1:
sample_temb = temb[index:index + 1]
else:
sample_temb = temb
batch_size, num_channels, num_frames, height, width = shape[index]
p_t, p_h, p_w = self.config.patch_size
post_patch_num_frames = num_frames // p_t
post_patch_height = height // p_h
post_patch_width = width // p_w
# Adaptive normalization for output
if sample_temb.ndim == 3:
shift, scale = (
self.scale_shift_table.unsqueeze(0) + sample_temb.unsqueeze(2)
).chunk(2, dim=2)
shift = shift.squeeze(2)
scale = scale.squeeze(2)
else:
shift, scale = (self.scale_shift_table + sample_temb.unsqueeze(1)).chunk(2, dim=1)
shift = shift.to(hidden_state.device)
scale = scale.to(hidden_state.device)
hidden_state = (
self.norm_out(hidden_state.float()) * (1 + scale) + shift
).type_as(hidden_state)
hidden_state = self.proj_out(hidden_state)
# Unpatchify: reshape back to video dimensions
hidden_state = hidden_state.reshape(
batch_size,
post_patch_num_frames,
post_patch_height,
post_patch_width,
p_t,
p_h,
p_w,
-1,
)
hidden_state = hidden_state.permute(0, 7, 1, 4, 2, 5, 3, 6)
output = hidden_state.flatten(6, 7).flatten(4, 5).flatten(2, 3)
output_list.append(output)
return output_list