[Bugfix] Fix wan2.2 ti2v (#1221)

Signed-off-by: mxuax <mxuax@connect.ust.hk>
Signed-off-by: XU Mingshi <91017482+mxuax@users.noreply.github.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
XU MingshiandHongsheng Liu authored and GitHub committed 2026-02-06 17:28:49 +08:00
1 parent 285d71b26a
commit 11a5a914ca
2 files changed
+76 -14

No files matched your search

@@ -63,6 +63,11 @@ def sp_shard(
f"world_size ({world_size}) for sequence parallel sharding."
)
if size < world_size:
raise ValueError(
f"Tensor size along dim {dim} ({size}) must be >= world_size ({world_size}). Tensor shape: {tensor.shape}"
)
return tensor.chunk(world_size, dim=dim)[rank]
@@ -296,6 +296,47 @@ class WanTimeTextImageEmbedding(nn.Module):
return temb, timestep_proj, encoder_hidden_states, encoder_hidden_states_image
class TimestepProjPrepare(nn.Module):
"""Prepares timestep_proj for sequence parallel in TI2V models.
Encapsulates the unflatten operation for timestep_proj to enable _sp_plan sharding.
"""
def forward(
self,
timestep_proj: torch.Tensor,
ts_seq_len: int | None,
) -> torch.Tensor:
if ts_seq_len is not None:
# TI2V mode: [batch, seq_len, 6, inner_dim]
timestep_proj = timestep_proj.unflatten(2, (6, -1))
else:
# T2V mode: [batch, 6, inner_dim]
timestep_proj = timestep_proj.unflatten(1, (6, -1))
return timestep_proj
class OutputScaleShiftPrepare(nn.Module):
"""Prepares output scale/shift for SP sharding in TI2V models."""
def __init__(self, inner_dim: int):
super().__init__()
self.scale_shift_table = nn.Parameter(torch.randn(1, 2, inner_dim) / inner_dim**0.5)
def forward(self, temb: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
if temb.ndim == 3:
# TI2V: [B, seq, D] -> 3D outputs for SP sharding
shift, scale = (self.scale_shift_table.unsqueeze(0).to(temb.device) + temb.unsqueeze(2)).chunk(2, dim=2)
shift = shift.squeeze(2)
scale = scale.squeeze(2)
else:
# T2V: [B, D] -> 2D outputs (skip SP sharding via expected_dims=3)
shift, scale = (self.scale_shift_table.to(temb.device) + temb.unsqueeze(1)).chunk(2, dim=1)
shift = shift.squeeze(1)
scale = scale.squeeze(1)
return shift, scale
class WanSelfAttention(nn.Module):
"""
Optimized self-attention module using vLLM layers.
@@ -679,6 +720,7 @@ class WanTransformer3DModel(nn.Module):
#
# The _sp_plan specifies sharding/gathering at module boundaries:
# - rope: Split both RoPE outputs (freqs_cos, freqs_sin) via split_output=True
# - timestep_proj_prepare: Split timestep_proj for TI2V models (4D tensor)
# - blocks.0: Split hidden_states input at the first transformer block
# - proj_out: Gather outputs after the final projection layer
#
@@ -689,11 +731,22 @@ class WanTransformer3DModel(nn.Module):
0: SequenceParallelInput(split_dim=1, expected_dims=4, split_output=True), # freqs_cos [1, seq, 1, dim]
1: SequenceParallelInput(split_dim=1, expected_dims=4, split_output=True), # freqs_sin [1, seq, 1, dim]
},
# Shard timestep_proj for TI2V models (4D tensor: [batch, seq_len, 6, inner_dim])
# This is only active when ts_seq_len is not None (TI2V mode)
# Output is a single tensor, shard along dim=1 (sequence dimension)
"timestep_proj_prepare": {
0: SequenceParallelInput(split_dim=1, expected_dims=4, split_output=True), # [B, seq, 6, dim]
},
# Shard hidden_states at first transformer block input
# (after patch_embedding + flatten + transpose)
"blocks.0": {
"hidden_states": SequenceParallelInput(split_dim=1, expected_dims=3), # [B, seq, dim]
},
# Shard output scale/shift for TI2V (3D); T2V outputs 2D and skips sharding
"output_scale_shift_prepare": {
0: SequenceParallelInput(split_dim=1, expected_dims=3, split_output=True),
1: SequenceParallelInput(split_dim=1, expected_dims=3, split_output=True),
},
# Gather at proj_out (final linear projection before unpatchify)
"proj_out": SequenceParallelOutput(gather_dim=1, expected_dims=3),
}
@@ -774,7 +827,10 @@ class WanTransformer3DModel(nn.Module):
# 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)
# SP helper modules
self.timestep_proj_prepare = TimestepProjPrepare()
self.output_scale_shift_prepare = OutputScaleShiftPrepare(inner_dim)
@property
def dtype(self) -> torch.dtype:
@@ -814,10 +870,10 @@ class WanTransformer3DModel(nn.Module):
temb, timestep_proj, encoder_hidden_states, encoder_hidden_states_image = self.condition_embedder(
timestep, encoder_hidden_states, encoder_hidden_states_image, 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))
# Prepare timestep_proj via TimestepProjPrepare module
# _sp_plan will shard timestep_proj via split_output=True (when ts_seq_len is not None)
# This ensures timestep_proj sequence dimension matches sharded hidden_states
timestep_proj = self.timestep_proj_prepare(timestep_proj, ts_seq_len)
if encoder_hidden_states_image is not None:
encoder_hidden_states = torch.concat([encoder_hidden_states_image, encoder_hidden_states], dim=1)
@@ -827,15 +883,12 @@ class WanTransformer3DModel(nn.Module):
hidden_states = block(hidden_states, encoder_hidden_states, timestep_proj, rotary_emb)
# Output norm, projection & unpatchify
if temb.ndim == 3:
shift, scale = (self.scale_shift_table.unsqueeze(0).to(temb.device) + temb.unsqueeze(2)).chunk(2, dim=2)
shift = shift.squeeze(2)
scale = scale.squeeze(2)
else:
shift, scale = (self.scale_shift_table.to(temb.device) + temb.unsqueeze(1)).chunk(2, dim=1)
shift, scale = self.output_scale_shift_prepare(temb)
shift = shift.to(hidden_states.device)
scale = scale.to(hidden_states.device)
if shift.ndim == 2: # T2V mode: unsqueeze for broadcasting
shift = shift.unsqueeze(1)
scale = scale.unsqueeze(1)
hidden_states = (self.norm_out(hidden_states.float()) * (1 + scale) + shift).type_as(hidden_states)
hidden_states = self.proj_out(hidden_states)
@@ -867,8 +920,6 @@ class WanTransformer3DModel(nn.Module):
tp_rank = get_tensor_model_parallel_rank()
tp_size = get_tensor_model_parallel_world_size()
# Stacked params mapping for self-attention QKV fusion
# Format: (param_name, shard_name, shard_id)
# Note: Only fuse attn1 (self-attention), NOT attn2 (cross-attention)
stacked_params_mapping = [
# self-attention QKV fusion
(".attn1.to_qkv", ".attn1.to_q", "q"),
@@ -876,10 +927,16 @@ class WanTransformer3DModel(nn.Module):
(".attn1.to_qkv", ".attn1.to_v", "v"),
]
# Remap scale_shift_table to new module location
weight_name_remapping = {
"scale_shift_table": "output_scale_shift_prepare.scale_shift_table",
}
params_dict = dict(self.named_parameters())
loaded_params: set[str] = set()
for name, loaded_weight in weights:
name = weight_name_remapping.get(name, name)
original_name = name
lookup_name = name