Spaces:
Running on Zero
Running on Zero
| from wan.modules.attention import attention | |
| from wan.modules.model import ( | |
| WanRMSNorm, | |
| rope_apply, | |
| WanLayerNorm, | |
| WAN_CROSSATTENTION_CLASSES, | |
| rope_params, | |
| MLPProj, | |
| sinusoidal_embedding_1d | |
| ) | |
| from torch.nn.attention.flex_attention import create_block_mask, flex_attention | |
| from diffusers.configuration_utils import ConfigMixin, register_to_config | |
| from torch.nn.attention.flex_attention import BlockMask | |
| from diffusers.models.modeling_utils import ModelMixin | |
| import torch.nn as nn | |
| import torch | |
| import math | |
| import torch.distributed as dist | |
| import os | |
| # wan 1.3B model has a weird channel / head configurations and require max-autotune to work with flexattention | |
| # see https://github.com/pytorch/pytorch/issues/133254 | |
| # change to default for other models | |
| flex_attention = torch.compile( | |
| flex_attention, dynamic=False, mode="max-autotune-no-cudagraphs") | |
| def causal_rope_apply(x, grid_sizes, freqs, start_frame=0): | |
| n, c = x.size(2), x.size(3) // 2 | |
| # split freqs | |
| freqs = freqs.split([c - 2 * (c // 3), c // 3, c // 3], dim=1) | |
| # loop over samples | |
| output = [] | |
| for i, (f, h, w) in enumerate(grid_sizes.tolist()): | |
| seq_len = f * h * w | |
| # precompute multipliers | |
| x_i = torch.view_as_complex(x[i, :seq_len].to(torch.float64).reshape( | |
| seq_len, n, -1, 2)) | |
| freqs_i = torch.cat([ | |
| freqs[0][start_frame:start_frame + f].view(f, 1, 1, -1).expand(f, h, w, -1), | |
| freqs[1][:h].view(1, h, 1, -1).expand(f, h, w, -1), | |
| freqs[2][:w].view(1, 1, w, -1).expand(f, h, w, -1) | |
| ], | |
| dim=-1).reshape(seq_len, 1, -1) | |
| # apply rotary embedding | |
| x_i = torch.view_as_real(x_i * freqs_i).flatten(2) | |
| x_i = torch.cat([x_i, x[i, seq_len:]]) | |
| # append to collection | |
| output.append(x_i) | |
| return torch.stack(output).type_as(x) | |
| def causal_rope_apply_pruned(x, grid_sizes, freqs, pruning_info, start_frame=0): | |
| """ | |
| 对pruned tokens应用RoPE,使用kept_indices的原始位置信息 | |
| 现在每个frame保留相同数量的tokens,可以正常reshape | |
| Args: | |
| x: [B, F*N_kept, num_heads, head_dim] pruned tokens(每个frame N_kept个) | |
| grid_sizes: [B, 3] 原始的(F, H, W) | |
| freqs: RoPE频率参数 | |
| pruning_info: dict 包含kept_indices等信息 | |
| start_frame: 起始帧索引 | |
| Returns: | |
| [B, F*N_kept, num_heads, head_dim] 应用RoPE后的tokens | |
| """ | |
| B, total_len, n, head_dim = x.shape | |
| c = head_dim // 2 | |
| kept_indices_list = pruning_info['kept_indices'] | |
| F = pruning_info['num_frames'] | |
| tokens_per_frame_kept = pruning_info['tokens_per_frame_kept'] | |
| tokens_per_frame_original = pruning_info['tokens_per_frame_original'] | |
| # split freqs | |
| freqs = freqs.split([c - 2 * (c // 3), c // 3, c // 3], dim=1) | |
| output = [] | |
| for i, (f, h, w) in enumerate(grid_sizes.tolist()): | |
| assert f == F and h * w == tokens_per_frame_original | |
| kept_idx = kept_indices_list[i] # [F*tokens_per_frame_kept] | |
| # 将kept_idx转换为3D坐标(frame, height, width) | |
| frame_indices = kept_idx // tokens_per_frame_original | |
| spatial_idx = kept_idx % tokens_per_frame_original | |
| height_indices = spatial_idx // w | |
| width_indices = spatial_idx % w | |
| # 获取对应位置的频率 | |
| freqs_f = freqs[0][start_frame + frame_indices] # [F*tokens_per_frame_kept, c1] | |
| freqs_h = freqs[1][height_indices] # [F*tokens_per_frame_kept, c2] | |
| freqs_w = freqs[2][width_indices] # [F*tokens_per_frame_kept, c3] | |
| freqs_i = torch.cat([freqs_f, freqs_h, freqs_w], dim=1) # [F*tokens_per_frame_kept, c] | |
| # 应用RoPE | |
| num_tokens = len(kept_idx) # F*tokens_per_frame_kept | |
| x_i = x[i].to(torch.float64) # [num_tokens, n, head_dim] | |
| x_i = torch.view_as_complex(x_i.reshape(num_tokens, n, c, 2)) | |
| freqs_i = freqs_i.unsqueeze(1) # [num_tokens, 1, c] | |
| # 应用旋转 | |
| x_i = torch.view_as_real(x_i * freqs_i).flatten(2) | |
| output.append(x_i) | |
| return torch.stack(output).type_as(x) | |
| class CausalWanSelfAttention(nn.Module): | |
| def __init__(self, | |
| dim, | |
| num_heads, | |
| local_attn_size=-1, | |
| sink_size=0, | |
| qk_norm=True, | |
| eps=1e-6, | |
| block_id=None): | |
| assert dim % num_heads == 0 | |
| super().__init__() | |
| self.dim = dim | |
| self.num_heads = num_heads | |
| self.head_dim = dim // num_heads | |
| self.local_attn_size = local_attn_size | |
| self.sink_size = sink_size | |
| self.qk_norm = qk_norm | |
| self.eps = eps | |
| self.max_attention_size = 32760 if local_attn_size == -1 else local_attn_size * 1560 | |
| # layers | |
| self.q = nn.Linear(dim, dim) | |
| self.k = nn.Linear(dim, dim) | |
| self.v = nn.Linear(dim, dim) | |
| self.o = nn.Linear(dim, dim) | |
| self.norm_q = WanRMSNorm(dim, eps=eps) if qk_norm else nn.Identity() | |
| self.norm_k = WanRMSNorm(dim, eps=eps) if qk_norm else nn.Identity() | |
| self.block_id = block_id | |
| def forward( | |
| self, | |
| x, | |
| seq_lens, | |
| grid_sizes, | |
| freqs, | |
| block_mask, | |
| kv_cache=None, | |
| current_start=0, | |
| cache_start=None, | |
| debug_dict=None, | |
| pruning_info=None # 🆕 接收pruning信息 | |
| ): | |
| r""" | |
| Args: | |
| x(Tensor): Shape [B, L, num_heads, C / num_heads] | |
| seq_lens(Tensor): Shape [B] | |
| grid_sizes(Tensor): Shape [B, 3], the second dimension contains (F, H, W) | |
| freqs(Tensor): Rope freqs, shape [1024, C / num_heads / 2] | |
| block_mask (BlockMask) | |
| """ | |
| b, s, n, d = *x.shape[:2], self.num_heads, self.head_dim | |
| if cache_start is None: | |
| cache_start = current_start | |
| # query, key, value function | |
| def qkv_fn(x): | |
| q = self.norm_q(self.q(x)).view(b, s, n, d) | |
| k = self.norm_k(self.k(x)).view(b, s, n, d) | |
| v = self.v(x).view(b, s, n, d) | |
| return q, k, v | |
| q, k, v = qkv_fn(x) | |
| if kv_cache is None: | |
| # if it is teacher forcing training? | |
| is_tf = (s == seq_lens[0].item() * 2) | |
| if is_tf: | |
| q_chunk = torch.chunk(q, 2, dim=1) | |
| k_chunk = torch.chunk(k, 2, dim=1) | |
| roped_query = [] | |
| roped_key = [] | |
| # rope should be same for clean and noisy parts | |
| for ii in range(2): | |
| rq = rope_apply(q_chunk[ii], grid_sizes, freqs).type_as(v) | |
| rk = rope_apply(k_chunk[ii], grid_sizes, freqs).type_as(v) | |
| roped_query.append(rq) | |
| roped_key.append(rk) | |
| roped_query = torch.cat(roped_query, dim=1) | |
| roped_key = torch.cat(roped_key, dim=1) | |
| padded_length = math.ceil(q.shape[1] / 128) * 128 - q.shape[1] | |
| padded_roped_query = torch.cat( | |
| [roped_query, | |
| torch.zeros([q.shape[0], padded_length, q.shape[2], q.shape[3]], | |
| device=q.device, dtype=v.dtype)], | |
| dim=1 | |
| ) | |
| padded_roped_key = torch.cat( | |
| [roped_key, torch.zeros([k.shape[0], padded_length, k.shape[2], k.shape[3]], | |
| device=k.device, dtype=v.dtype)], | |
| dim=1 | |
| ) | |
| padded_v = torch.cat( | |
| [v, torch.zeros([v.shape[0], padded_length, v.shape[2], v.shape[3]], | |
| device=v.device, dtype=v.dtype)], | |
| dim=1 | |
| ) | |
| x = flex_attention( | |
| query=padded_roped_query.transpose(2, 1), | |
| key=padded_roped_key.transpose(2, 1), | |
| value=padded_v.transpose(2, 1), | |
| block_mask=block_mask | |
| )[:, :, :-padded_length].transpose(2, 1) | |
| else: | |
| # 🎯 根据pruning_info选择RoPE函数 | |
| if pruning_info and pruning_info.get('pruned', False): | |
| # 使用pruned RoPE,保持原始位置信息 | |
| roped_query = causal_rope_apply_pruned( | |
| q, grid_sizes, freqs, pruning_info, start_frame=0 | |
| ).type_as(v) | |
| roped_key = causal_rope_apply_pruned( | |
| k, grid_sizes, freqs, pruning_info, start_frame=0 | |
| ).type_as(v) | |
| else: | |
| # 正常的RoPE | |
| roped_query = rope_apply(q, grid_sizes, freqs).type_as(v) | |
| roped_key = rope_apply(k, grid_sizes, freqs).type_as(v) | |
| padded_length = math.ceil(q.shape[1] / 128) * 128 - q.shape[1] | |
| padded_roped_query = torch.cat( | |
| [roped_query, | |
| torch.zeros([q.shape[0], padded_length, q.shape[2], q.shape[3]], | |
| device=q.device, dtype=v.dtype)], | |
| dim=1 | |
| ) | |
| padded_roped_key = torch.cat( | |
| [roped_key, torch.zeros([k.shape[0], padded_length, k.shape[2], k.shape[3]], | |
| device=k.device, dtype=v.dtype)], | |
| dim=1 | |
| ) | |
| padded_v = torch.cat( | |
| [v, torch.zeros([v.shape[0], padded_length, v.shape[2], v.shape[3]], | |
| device=v.device, dtype=v.dtype)], | |
| dim=1 | |
| ) | |
| x = flex_attention( | |
| query=padded_roped_query.transpose(2, 1), | |
| key=padded_roped_key.transpose(2, 1), | |
| value=padded_v.transpose(2, 1), | |
| block_mask=block_mask | |
| )[:, :, :-padded_length].transpose(2, 1) | |
| else: | |
| frame_seqlen = math.prod(grid_sizes[0][1:]).item() | |
| current_start_frame = current_start // frame_seqlen | |
| # 🎯 根据pruning_info选择RoPE函数(kv_cache分支) | |
| if pruning_info and pruning_info.get('pruned', False): | |
| roped_query = causal_rope_apply_pruned( | |
| q, grid_sizes, freqs, pruning_info, | |
| start_frame=current_start_frame | |
| ).type_as(v) | |
| roped_key = causal_rope_apply_pruned( | |
| k, grid_sizes, freqs, pruning_info, | |
| start_frame=current_start_frame | |
| ).type_as(v) | |
| else: | |
| roped_query = causal_rope_apply( | |
| q, grid_sizes, freqs, start_frame=current_start_frame).type_as(v) | |
| roped_key = causal_rope_apply( | |
| k, grid_sizes, freqs, start_frame=current_start_frame).type_as(v) | |
| current_end = current_start + roped_query.shape[1] | |
| sink_tokens = self.sink_size * frame_seqlen | |
| # If we are using local attention and the current KV cache size is larger than the local attention size, we need to truncate the KV cache | |
| kv_cache_size = kv_cache["k"].shape[1] | |
| num_new_tokens = roped_query.shape[1] | |
| if self.local_attn_size != -1 and (current_end > kv_cache["global_end_index"].item()) and ( | |
| num_new_tokens + kv_cache["local_end_index"].item() > kv_cache_size): | |
| # Calculate the number of new tokens added in this step | |
| # Shift existing cache content left to discard oldest tokens | |
| # Clone the source slice to avoid overlapping memory error | |
| num_evicted_tokens = num_new_tokens + kv_cache["local_end_index"].item() - kv_cache_size | |
| num_rolled_tokens = kv_cache["local_end_index"].item() - num_evicted_tokens - sink_tokens | |
| kv_cache["k"][:, sink_tokens:sink_tokens + num_rolled_tokens] = \ | |
| kv_cache["k"][:, sink_tokens + num_evicted_tokens:sink_tokens + num_evicted_tokens + num_rolled_tokens].clone() | |
| kv_cache["v"][:, sink_tokens:sink_tokens + num_rolled_tokens] = \ | |
| kv_cache["v"][:, sink_tokens + num_evicted_tokens:sink_tokens + num_evicted_tokens + num_rolled_tokens].clone() | |
| # Insert the new keys/values at the end | |
| local_end_index = kv_cache["local_end_index"].item() + current_end - \ | |
| kv_cache["global_end_index"].item() - num_evicted_tokens | |
| local_start_index = local_end_index - num_new_tokens | |
| # if debug_dict["block_index"] == 0 : | |
| # print(f"[KVCache DEBUG] {local_start_index} to {local_end_index}, {sink_tokens=}") | |
| # print(f"[KVCache DEBUG] {roped_key.shape=}") | |
| # print(f"[KVCache DEBUG] {v.shape=}") | |
| kv_cache["k"][:, local_start_index:local_end_index] = roped_key | |
| kv_cache["v"][:, local_start_index:local_end_index] = v | |
| else: | |
| # Assign new keys/values directly up to current_end | |
| local_end_index = kv_cache["local_end_index"].item() + current_end - kv_cache["global_end_index"].item() | |
| local_start_index = local_end_index - num_new_tokens | |
| # if debug_dict is not None and debug_dict["block_index"] == 0 : | |
| # print(f"[KVCache DEBUG] {local_start_index} to {local_end_index}, {sink_tokens=}") | |
| # print(f"[KVCache DEBUG] {roped_key.shape=}") | |
| # print(f"[KVCache DEBUG] {v.shape=}") | |
| kv_cache["k"][:, local_start_index:local_end_index] = roped_key | |
| kv_cache["v"][:, local_start_index:local_end_index] = v | |
| # print(f"{max(0, local_end_index - self.max_attention_size)} to {local_end_index}") | |
| # | |
| x = attention( # 对应wan/modules/attention.py中的attention函数 | |
| roped_query, | |
| kv_cache["k"][:, max(0, local_end_index - self.max_attention_size):local_end_index], | |
| kv_cache["v"][:, max(0, local_end_index - self.max_attention_size):local_end_index], | |
| debug_dict=debug_dict | |
| ) | |
| kv_cache["global_end_index"].fill_(current_end) | |
| kv_cache["local_end_index"].fill_(local_end_index) | |
| # output | |
| x = x.flatten(2) | |
| x = self.o(x) | |
| return x | |
| class CausalWanAttentionBlock(nn.Module): | |
| def __init__(self, | |
| cross_attn_type, | |
| dim, | |
| ffn_dim, | |
| num_heads, | |
| local_attn_size=-1, | |
| sink_size=0, | |
| qk_norm=True, | |
| cross_attn_norm=False, | |
| eps=1e-6, | |
| block_id=None): | |
| super().__init__() | |
| self.dim = dim | |
| self.ffn_dim = ffn_dim | |
| self.num_heads = num_heads | |
| self.local_attn_size = local_attn_size | |
| self.qk_norm = qk_norm | |
| self.cross_attn_norm = cross_attn_norm | |
| self.eps = eps | |
| # layers | |
| self.norm1 = WanLayerNorm(dim, eps) | |
| self.self_attn = CausalWanSelfAttention(dim, num_heads, local_attn_size, sink_size, qk_norm, eps, block_id) | |
| self.norm3 = WanLayerNorm( | |
| dim, eps, | |
| elementwise_affine=True) if cross_attn_norm else nn.Identity() | |
| self.cross_attn = WAN_CROSSATTENTION_CLASSES[cross_attn_type](dim, | |
| num_heads, | |
| (-1, -1), | |
| qk_norm, | |
| eps) | |
| self.norm2 = WanLayerNorm(dim, eps) | |
| self.ffn = nn.Sequential( | |
| nn.Linear(dim, ffn_dim), nn.GELU(approximate='tanh'), | |
| nn.Linear(ffn_dim, dim)) | |
| self.ffn_hidden_dim = ffn_dim | |
| # modulation | |
| self.modulation = nn.Parameter(torch.randn(1, 6, dim) / dim**0.5) | |
| self.block_id = block_id | |
| def _block_internal_prune(self, x, kept_indices_per_frame, num_frames): | |
| """ | |
| Block内部pruning:在计算delta时对输入进行prune | |
| Args: | |
| x: [B, F*H*W, C] 完整的输入tokens | |
| kept_indices_per_frame: List[Tensor], 每个frame的kept indices | |
| num_frames: int, 帧数 | |
| Returns: | |
| x_pruned: [B, total_kept, C] pruned后的tokens | |
| flat_kept_indices: [total_kept] 扁平化的全局indices | |
| """ | |
| B, L, C = x.shape | |
| tokens_per_frame = L // num_frames | |
| # 收集所有kept indices(转换为全局索引) | |
| flat_kept_indices = [] | |
| for f_idx in range(num_frames): | |
| frame_kept = kept_indices_per_frame[f_idx] # [kept_count] | |
| global_indices = frame_kept + f_idx * tokens_per_frame | |
| flat_kept_indices.append(global_indices) | |
| flat_kept_indices = torch.cat(flat_kept_indices, dim=0) # [total_kept] | |
| # Gather pruned tokens | |
| x_pruned = x[:, flat_kept_indices, :] # [B, total_kept, C] | |
| return x_pruned, flat_kept_indices | |
| def _block_internal_restore(self, delta_pruned, x_full, flat_kept_indices, block_idx=None, | |
| first_chunk_cache=None, cache_item="x", fill_strategy="prev_step", | |
| num_frames=None, frame_seqlen=None, prev_step_deltas=None, source_latent=None): | |
| """ | |
| Block内部restore:将pruned delta恢复到完整维度 | |
| Args: | |
| delta_pruned: [B, total_kept, C] pruned计算的delta | |
| x_full: [B, F*H*W, C] 完整的x(用于获取shape) | |
| flat_kept_indices: [total_kept] 全局kept indices | |
| block_idx: int, 当前block的索引 | |
| first_chunk_cache: dict, first chunk的缓存 | |
| cache_item: str, 缓存内容类型(当fill_strategy="first_chunk"时使用) | |
| - "x": 缓存block输入x | |
| - "delta": 缓存delta(y_full) | |
| fill_strategy: str, 未pruning位置的填充策略 | |
| - "identity": delta=0,保持当前x不变 | |
| - "prev_step": 使用前一个全量计算step的delta(推荐) | |
| - "first_chunk": 使用first chunk的值(根据cache_item) | |
| - "source_latent": 使用source_latent (y_input),delta = source - current_x | |
| - "avg_delta": 使用保留token的平均delta | |
| - "interpolate": 从周围保留token插值 | |
| - "zero": 填充0(等同identity) | |
| num_frames: int, 帧数(插值时需要) | |
| frame_seqlen: int, 每帧token数(插值时需要) | |
| prev_step_deltas: dict, 上一个step的delta缓存 {block_idx: delta} | |
| source_latent: Tensor, 源latent (y_input),用于source_latent策略 | |
| Returns: | |
| delta_full: [B, F*H*W, C] 完整维度的delta | |
| - 保留位置:使用计算得到的delta | |
| - 未保留位置:根据fill_strategy填充 | |
| """ | |
| B, L, C = x_full.shape | |
| delta_full = torch.zeros_like(x_full) # [B, L, C] | |
| # Scatter pruned delta到保留位置 | |
| delta_full[:, flat_kept_indices, :] = delta_pruned | |
| # 创建未保留位置的mask | |
| all_indices = torch.arange(L, device=x_full.device) | |
| kept_mask = torch.zeros(L, dtype=torch.bool, device=x_full.device) | |
| kept_mask[flat_kept_indices] = True | |
| unkept_indices = all_indices[~kept_mask] | |
| if len(unkept_indices) == 0: | |
| # 没有未保留位置,直接返回 | |
| return delta_full | |
| # 🎨 根据fill_strategy填充未保留位置 | |
| if fill_strategy == "zero" or fill_strategy == "identity": | |
| # 策略1&2: delta=0,保持当前x不变(已经是0了,不需要操作) | |
| pass | |
| elif fill_strategy == "prev_step": | |
| # 策略: 使用前一个全量计算step的delta(⭐推荐) | |
| # if block_idx == 0: | |
| # print(f"[Debug Fill] prev_step_deltas is not None: {prev_step_deltas is not None}") | |
| # print(f"[Debug Fill] block_idx is not None: {block_idx is not None}") | |
| # if prev_step_deltas is not None: | |
| # print(f"[Debug Fill] block_idx in prev_step_deltas: {block_idx in prev_step_deltas}") | |
| # print(f"[Debug Fill] prev_step_deltas.keys(): {list(prev_step_deltas.keys())}") | |
| # print(f"[Debug Fill] not self.training: {not self.training}") | |
| if (prev_step_deltas is not None and block_idx is not None and \ | |
| block_idx in prev_step_deltas and not self.training): | |
| prev_delta = prev_step_deltas[block_idx] | |
| # 使用上一个step的delta填充未保留位置 | |
| delta_full[:, unkept_indices, :] = prev_delta[:, unkept_indices, :] | |
| # if block_idx == 0: | |
| # print(f"[Debug Fill] Successfully filled {len(unkept_indices)} unkept positions from prev_step") | |
| elif fill_strategy == "debug": | |
| # 策略: 使用前一个全量计算step的delta 直接覆盖 | |
| if (prev_step_deltas is not None and block_idx is not None and | |
| block_idx in prev_step_deltas and not self.training): | |
| prev_delta = prev_step_deltas[block_idx] | |
| # 使用上一个step的delta填充未保留位置 | |
| delta_full = prev_delta | |
| elif fill_strategy == "first_chunk": | |
| # 策略: 使用first chunk的值 | |
| if (first_chunk_cache is not None and block_idx is not None and | |
| block_idx in first_chunk_cache and not self.training): | |
| if cache_item == "x": | |
| # 缓存的是x:delta = first_chunk_x - current_x | |
| first_chunk_x = first_chunk_cache[block_idx] | |
| delta_full[:, unkept_indices, :] = first_chunk_x[:, unkept_indices, :] - x_full[:, unkept_indices, :] | |
| elif cache_item == "delta": | |
| # 缓存的是delta:直接使用 | |
| first_chunk_delta = first_chunk_cache[block_idx] | |
| delta_full[:, unkept_indices, :] = first_chunk_delta[:, unkept_indices, :] | |
| elif fill_strategy == "source_latent": | |
| # 策略: 使用source_latent (y_input) | |
| # delta = source - current_x | |
| if source_latent is not None: | |
| delta_full[:, unkept_indices, :] = source_latent[:, unkept_indices, :] - x_full[:, unkept_indices, :] | |
| elif fill_strategy == "avg_delta": | |
| # 策略: 使用保留token的平均delta | |
| if len(flat_kept_indices) > 0: | |
| avg_delta = delta_pruned.mean(dim=1, keepdim=True) # [B, 1, C] | |
| delta_full[:, unkept_indices, :] = avg_delta.expand(B, len(unkept_indices), C) | |
| elif fill_strategy == "interpolate": | |
| # 策略: 从周围保留token插值(2D空间插值) | |
| if num_frames is not None and frame_seqlen is not None: | |
| # 需要2D grid信息进行插值 | |
| # 这里实现一个简单的最近邻插值 | |
| H = int(frame_seqlen ** 0.5) | |
| W = frame_seqlen // H | |
| for b in range(B): | |
| for f_idx in range(num_frames): | |
| # 当前帧的范围 | |
| frame_start = f_idx * frame_seqlen | |
| frame_end = (f_idx + 1) * frame_seqlen | |
| # 当前帧的kept和unkept indices(转为局部索引) | |
| frame_kept_global = flat_kept_indices[(flat_kept_indices >= frame_start) & (flat_kept_indices < frame_end)] | |
| frame_kept_local = frame_kept_global - frame_start | |
| frame_unkept_global = unkept_indices[(unkept_indices >= frame_start) & (unkept_indices < frame_end)] | |
| frame_unkept_local = frame_unkept_global - frame_start | |
| if len(frame_kept_local) == 0 or len(frame_unkept_local) == 0: | |
| continue | |
| # 转换为2D坐标 | |
| kept_y = frame_kept_local // W | |
| kept_x = frame_kept_local % W | |
| unkept_y = frame_unkept_local // W | |
| unkept_x = frame_unkept_local % W | |
| # 对每个unkept位置,找最近的kept位置 | |
| for i, unk_idx in enumerate(frame_unkept_local): | |
| uy, ux = unkept_y[i].item(), unkept_x[i].item() | |
| # 计算距离 | |
| dist = (kept_y - uy) ** 2 + (kept_x - ux) ** 2 | |
| nearest_idx = dist.argmin() | |
| # 使用最近邻的delta | |
| nearest_global_idx = frame_kept_global[nearest_idx] | |
| delta_full[b, frame_start + unk_idx] = delta_full[b, nearest_global_idx] | |
| return delta_full | |
| def forward( | |
| self, | |
| x, | |
| e, | |
| seq_lens, | |
| grid_sizes, | |
| freqs, | |
| context, | |
| context_lens, | |
| block_mask, | |
| kv_cache=None, | |
| crossattn_cache=None, | |
| current_start=0, | |
| cache_start=None, | |
| debug_dict=None, | |
| pruning_info=None, | |
| block_idx=None # 🆕 用于保存/读取first chunk缓存 | |
| ): | |
| r""" | |
| Args: | |
| x(Tensor): Shape [B, L, C] - 保持完整维度 | |
| e(Tensor): Shape [B, F, 6, C] | |
| seq_lens(Tensor): Shape [B], length of each sequence in batch | |
| grid_sizes(Tensor): Shape [B, 3], the second dimension contains (F, H, W) | |
| freqs(Tensor): Rope freqs, shape [1024, C / num_heads / 2] | |
| pruning_info(dict): {'internal_pruning': bool, 'kept_indices_per_frame': List[Tensor]} | |
| """ | |
| num_frames = e.shape[1] | |
| e = (self.modulation.unsqueeze(1) + e).chunk(6, dim=2) | |
| # ✨ Block内部Pruning模式判断 | |
| use_block_internal_pruning = ( | |
| pruning_info is not None and | |
| pruning_info.get('internal_pruning', False) and | |
| 'kept_indices_per_frame' in pruning_info and | |
| pruning_info.get('use_pruning', False) # 🆕 新增:检查是否启用pruning | |
| ) | |
| # 🆕 层级控制:检查是否在指定层进行pruning | |
| pruning_layers = pruning_info.get('pruning_layers', set()) if pruning_info else set() | |
| use_self_attn_pruning = use_block_internal_pruning and "self_attn" in pruning_layers | |
| use_ffn_pruning = use_block_internal_pruning and "ffn" in pruning_layers | |
| # 🆕 提前提取first_chunk_cache、cache_item、fill_strategy和prev_step_deltas(供所有分支使用) | |
| first_chunk_cache = pruning_info.get('first_chunk_block_inputs', {}) if pruning_info else {} | |
| cache_item = pruning_info.get('cache_item', 'x') if pruning_info else 'x' | |
| fill_strategy = pruning_info.get('fill_strategy', 'prev_step') if pruning_info else 'prev_step' | |
| use_mean_alignment = pruning_info.get('use_mean_alignment', True) if pruning_info else True # 🆕 均值对齐开关 | |
| source_latent = pruning_info.get('source_latent', None) if pruning_info else None # 🆕 源latent (y_input) | |
| # ⚠️ 关键:直接使用pruning_info中的引用,如果不存在则用空字典(会导致保存失败) | |
| # 这样修改局部变量会直接影响pruning_info中的字典 | |
| prev_step_self_attn_deltas = pruning_info.get('prev_step_self_attn_deltas', {}) if pruning_info else {} | |
| prev_step_ffn_deltas = pruning_info.get('prev_step_ffn_deltas', {}) if pruning_info else {} | |
| # 🐛 Debug: 打印引用检查 | |
| # if block_idx == 0 and pruning_info and 'prev_step_self_attn_deltas' in pruning_info: | |
| # print(f"[Block Debug] prev_step_self_attn_deltas is pruning_info ref: {prev_step_self_attn_deltas is pruning_info['prev_step_self_attn_deltas']}") | |
| # print(f"[Block Debug] prev_step_self_attn_deltas id: {id(prev_step_self_attn_deltas)}, pruning_info id: {id(pruning_info['prev_step_self_attn_deltas'])}") | |
| # 原始frame_seqlen(x始终保持完整) | |
| frame_seqlen = x.shape[1] // num_frames | |
| # ========== Self-Attention with Block Internal Pruning ========== | |
| if use_self_attn_pruning: | |
| # 💾 保存first chunk(根据cache_item决定保存x还是delta) | |
| should_cache = (block_idx is not None and block_idx not in first_chunk_cache and not self.training) | |
| if should_cache and cache_item == "x": | |
| # 缓存x:在计算前保存 | |
| first_chunk_cache[block_idx] = x.detach().clone() | |
| # 1. Norm + Modulation (完整维度) | |
| x_norm = (self.norm1(x).unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * (1 + e[1]) + e[0]).flatten(1, 2) | |
| # 2. Prune输入 | |
| x_pruned, flat_kept_indices = self._block_internal_prune( | |
| x_norm, | |
| pruning_info['kept_indices_per_frame'], | |
| num_frames | |
| ) | |
| # 3. Self-Attn计算pruned delta | |
| # 🔑 构造兼容旧格式的pruning_info(用于RoPE) | |
| pruned_info = { | |
| 'pruned': True, | |
| 'tokens_per_frame_kept': len(pruning_info['kept_indices_per_frame'][0]), | |
| 'tokens_per_frame_original': frame_seqlen, | |
| 'num_frames': num_frames, | |
| 'kept_indices': [flat_kept_indices] # List of [total_kept] for batch | |
| } | |
| y_pruned = self.self_attn( | |
| x_pruned, seq_lens, grid_sizes, freqs, block_mask, | |
| kv_cache, current_start, cache_start, debug_dict, pruned_info | |
| ) | |
| # 🔧 对齐y_pruned与prev_step delta的均值(如果使用prev_step填充策略且启用均值对齐) | |
| # print("DEBUG: use_mean_alignment", use_mean_alignment, "fill_strategy", fill_strategy, "block_idx", block_idx) | |
| if use_mean_alignment and (fill_strategy in ["prev_step", "debug"]) and block_idx in prev_step_self_attn_deltas: | |
| prev_delta = prev_step_self_attn_deltas[block_idx] # [B, L, D] | |
| # 计算y_pruned的均值 | |
| y_pruned_mean = y_pruned.mean(dim=(0, 1), keepdim=True) # [1, 1, D] | |
| # 计算kept位置对应的prev_delta均值 | |
| prev_delta_kept = prev_delta[:, flat_kept_indices, :] # [B, kept, D] | |
| prev_delta_kept_mean = prev_delta_kept.mean(dim=(0, 1), keepdim=True) # [1, 1, D] | |
| # 对齐:y_pruned -= (y_pruned_mean - prev_delta_kept_mean) | |
| y_pruned = y_pruned - (y_pruned_mean - prev_delta_kept_mean) | |
| if block_idx == 0: | |
| print(f"[Mean Align] Self-Attn: y_pruned_mean={y_pruned_mean[0,0,:3].tolist()}, " | |
| f"prev_kept_mean={prev_delta_kept_mean[0,0,:3].tolist()}") | |
| # 4. Restore delta到完整维度(使用first chunk填充未保留位置) | |
| y_full = self._block_internal_restore( | |
| y_pruned, x, flat_kept_indices, block_idx, first_chunk_cache, cache_item, fill_strategy, | |
| num_frames, frame_seqlen, prev_step_self_attn_deltas, source_latent # ✅ 传递source_latent | |
| ) | |
| # if block_idx == 0: | |
| # print("[Reuse] Self Attn delta in forward pass") | |
| # 💾 保存当前step的self-attn delta(用于下一个pruning step填充) | |
| # 注意:保存未经modulation的delta,使用时需要应用当前step的modulation | |
| # if block_idx is not None and not self.training: | |
| # # prev_step_self_attn_deltas[block_idx] = y_full.detach().clone() | |
| # if block_idx == 0: | |
| # # 🔍 打印填充后的完整delta统计(包含保留+填充部分) | |
| # filled_mean = y_full.mean().item() | |
| # filled_std = y_full.std().item() | |
| # # 🔍 打印pruned delta统计(仅保留部分,真实计算的delta) | |
| # pruned_mean = y_pruned.mean().item() | |
| # pruned_std = y_pruned.std().item() | |
| # print(f"[Reuse] Filled delta: mean={filled_mean:.6f}, std={filled_std:.6f}") | |
| # print(f"[Reuse] Pruned delta (computed): mean={pruned_mean:.6f}, std={pruned_std:.6f}") | |
| # 5. 残差连接(x保持完整) | |
| x = x + (y_full.unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * e[2]).flatten(1, 2) | |
| else: | |
| # 原始流程(无pruning,全量计算) | |
| y = self.self_attn( | |
| (self.norm1(x).unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * (1 + e[1]) + e[0]).flatten(1, 2), | |
| seq_lens, grid_sizes, freqs, block_mask, kv_cache, current_start, cache_start, debug_dict, None | |
| ) | |
| # print(f"[Shapes] self-attn {y.shape=}") | |
| # 💾 全量计算时也保存self-attn delta(用于下一个pruning step填充) | |
| if block_idx is not None and not self.training: | |
| # y是完整的delta,直接保存 | |
| prev_step_self_attn_deltas[block_idx]=y | |
| # if block_idx == 0: | |
| # delta_mean = y.mean().item() | |
| # delta_std = y.std().item() | |
| # print(f"[Cache] Saving Self-Attn delta: mean={delta_mean:.6f}, std={delta_std:.6f} in block {block_idx}") | |
| x = x + (y.unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * e[2]).flatten(1, 2) | |
| # ========== Cross-Attention & FFN ========== | |
| def cross_attn_ffn(x, context, context_lens, e, crossattn_cache=None, debug_dict=None, | |
| block_idx=None, first_chunk_cache=None, use_ffn_pruning=False, cache_item="x", | |
| fill_strategy="prev_step", prev_step_ffn_deltas=None): | |
| # Cross-Attention (不prune,因为涉及外部context) | |
| x = x + self.cross_attn(self.norm3(x), context, context_lens, | |
| crossattn_cache=crossattn_cache, debug_dict=debug_dict) | |
| # FFN with Block Internal Pruning | |
| if use_ffn_pruning: | |
| # 💾 保存first chunk(如果cache_item="x"且还没缓存) | |
| # 注意:如果self-attention已经缓存了,这里不重复缓存 | |
| should_cache_ffn = (block_idx is not None and block_idx not in first_chunk_cache and not self.training) | |
| if should_cache_ffn and cache_item == "x": | |
| first_chunk_cache[block_idx] = x.detach().clone() | |
| # print(f"[Block {block_idx} FFN] 💾 Saved first chunk x: {x.shape}") | |
| # 1. Norm + Modulation | |
| x_norm = (self.norm2(x).unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * (1 + e[4]) + e[3]).flatten(1, 2) | |
| # 2. Prune输入 | |
| x_pruned, flat_kept_indices = self._block_internal_prune( | |
| x_norm, | |
| pruning_info['kept_indices_per_frame'], | |
| num_frames | |
| ) | |
| # 3. FFN计算pruned delta | |
| x_norm_for_ffn = x_pruned | |
| # 先正常计算 | |
| y_pruned = self.ffn(x_norm_for_ffn) | |
| # 🔍 保存FFN中间激活(如果启用debug) | |
| if debug_dict is not None and debug_dict.get('visualize_ffn', False) and block_idx == 0: | |
| with torch.no_grad(): | |
| ffn_hidden = self.ffn[0](x_norm_for_ffn) | |
| ffn_act = self.ffn[1](ffn_hidden) | |
| import numpy as np | |
| save_dir = debug_dict.get("save_path", "./visual-output").replace(os.path.basename(debug_dict.get("save_path", "")), "") | |
| os.makedirs(save_dir, exist_ok=True) | |
| chunk_idx = debug_dict.get('current_chunk', 0) | |
| step_idx = debug_dict.get('current_step', 0) | |
| np.save(os.path.join(save_dir, f'ffn_hidden_{chunk_idx}_{step_idx}.npy'), | |
| ffn_hidden.cpu().float().numpy()) | |
| np.save(os.path.join(save_dir, f'ffn_act_{chunk_idx}_{step_idx}.npy'), | |
| ffn_act.cpu().float().numpy()) | |
| np.save(os.path.join(save_dir, f'ffn_output_{chunk_idx}_{step_idx}.npy'), | |
| y_pruned.cpu().float().numpy()) | |
| print(f"[Debug] Saved FFN activations: hidden={ffn_hidden.shape}, act={ffn_act.shape}, output={y_pruned.shape}") | |
| # 🔧 对齐y_pruned与prev_step delta的均值(如果使用prev_step填充策略且启用均值对齐) | |
| if use_mean_alignment and (fill_strategy in ["prev_step", "debug"]) and block_idx in prev_step_ffn_deltas: | |
| prev_delta = prev_step_ffn_deltas[block_idx] # [B, L, D] | |
| # 计算y_pruned的均值 | |
| y_pruned_mean = y_pruned.mean(dim=(0, 1), keepdim=True) # [1, 1, D] | |
| # 计算kept位置对应的prev_delta均值 | |
| prev_delta_kept = prev_delta[:, flat_kept_indices, :] # [B, kept, D] | |
| prev_delta_kept_mean = prev_delta_kept.mean(dim=(0, 1), keepdim=True) # [1, 1, D] | |
| # 对齐:y_pruned -= (y_pruned_mean - prev_delta_kept_mean) | |
| y_pruned = y_pruned - (y_pruned_mean - prev_delta_kept_mean) | |
| if block_idx == 0: | |
| print(f"[Mean Align] FFN: y_pruned_mean={y_pruned_mean[0,0,:3].tolist()}, " | |
| f"prev_kept_mean={prev_delta_kept_mean[0,0,:3].tolist()}") | |
| # 4. Restore delta(使用first chunk填充未保留位置) | |
| y_full = self._block_internal_restore( | |
| y_pruned, x, flat_kept_indices, block_idx, first_chunk_cache, cache_item, fill_strategy, | |
| num_frames, frame_seqlen, prev_step_ffn_deltas, source_latent # ✅ 传递source_latent | |
| ) | |
| # if block_idx == 0: | |
| # print("[Reuse] FFN delta in cross_attn_ffn") | |
| # 5. 残差连接 | |
| x = x + (y_full.unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * e[5]).flatten(1, 2) | |
| else: | |
| # 原始流程(无pruning,全量计算) | |
| x_norm_input = (self.norm2(x).unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * (1 + e[4]) + e[3]).flatten(1, 2) | |
| # 先正常计算 | |
| y = self.ffn(x_norm_input) | |
| # 🔍 保存FFN中间激活(如果启用debug) | |
| if debug_dict is not None and debug_dict.get('visualize_ffn', False) and block_idx == 0: | |
| with torch.no_grad(): | |
| ffn_hidden = self.ffn[0](x_norm_input) | |
| ffn_act = self.ffn[1](ffn_hidden) | |
| import numpy as np | |
| save_dir = debug_dict.get("save_path", "./visual-output").replace(os.path.basename(debug_dict.get("save_path", "")), "") | |
| os.makedirs(save_dir, exist_ok=True) | |
| chunk_idx = debug_dict.get('current_chunk', 0) | |
| step_idx = debug_dict.get('current_step', 0) | |
| np.save(os.path.join(save_dir, f'ffn_hidden_{chunk_idx}_{step_idx}.npy'), | |
| ffn_hidden.cpu().float().numpy()) | |
| np.save(os.path.join(save_dir, f'ffn_act_{chunk_idx}_{step_idx}.npy'), | |
| ffn_act.cpu().float().numpy()) | |
| np.save(os.path.join(save_dir, f'ffn_output_{chunk_idx}_{step_idx}.npy'), | |
| y.cpu().float().numpy()) | |
| print(f"[Debug] Saved FFN activations: hidden={ffn_hidden.shape}, act={ffn_act.shape}, output={y.shape}") | |
| # print(f"[Shapes] ffn {y.shape=}") | |
| # 💾 全量计算时也保存FFN delta(用于下一个pruning step填充) | |
| if block_idx is not None and not self.training: | |
| prev_step_ffn_deltas[block_idx] = y #.detach().clone() | |
| # if block_idx == 0: | |
| # print("[Cache] Saving FFN delta in cross_attn_ffn") | |
| x = x + (y.unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * e[5]).flatten(1, 2) | |
| return x | |
| x = cross_attn_ffn(x, context, context_lens, e, crossattn_cache, debug_dict, | |
| block_idx, first_chunk_cache, use_ffn_pruning, cache_item, fill_strategy, prev_step_ffn_deltas) | |
| return x | |
| class CausalHead(nn.Module): | |
| def __init__(self, dim, out_dim, patch_size, eps=1e-6): | |
| super().__init__() | |
| self.dim = dim | |
| self.out_dim = out_dim | |
| self.patch_size = patch_size | |
| self.eps = eps | |
| # layers | |
| out_dim = math.prod(patch_size) * out_dim | |
| self.norm = WanLayerNorm(dim, eps) | |
| self.head = nn.Linear(dim, out_dim) | |
| # modulation | |
| self.modulation = nn.Parameter(torch.randn(1, 2, dim) / dim**0.5) | |
| def forward(self, x, e): | |
| r""" | |
| Args: | |
| x(Tensor): Shape [B, L1, C] | |
| e(Tensor): Shape [B, F, 1, C] | |
| """ | |
| # assert e.dtype == torch.float32 | |
| # with amp.autocast(dtype=torch.float32): | |
| num_frames, frame_seqlen = e.shape[1], x.shape[1] // e.shape[1] | |
| e = (self.modulation.unsqueeze(1) + e).chunk(2, dim=2) | |
| x = (self.head(self.norm(x).unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * (1 + e[1]) + e[0])) | |
| return x | |
| class CausalWanModel(ModelMixin, ConfigMixin): | |
| r""" | |
| Wan diffusion backbone supporting both text-to-video and image-to-video. | |
| """ | |
| ignore_for_config = [ | |
| 'patch_size', 'cross_attn_norm', 'qk_norm', 'text_dim' | |
| ] | |
| _no_split_modules = ['WanAttentionBlock'] | |
| _supports_gradient_checkpointing = True | |
| def __init__(self, | |
| model_type='t2v', | |
| patch_size=(1, 2, 2), | |
| text_len=512, | |
| in_dim=16, | |
| dim=2048, | |
| ffn_dim=8192, | |
| freq_dim=256, | |
| text_dim=4096, | |
| out_dim=16, | |
| num_heads=16, | |
| num_layers=32, | |
| local_attn_size=-1, | |
| sink_size=0, | |
| qk_norm=True, | |
| cross_attn_norm=True, | |
| eps=1e-6): | |
| r""" | |
| Initialize the diffusion model backbone. | |
| Args: | |
| model_type (`str`, *optional*, defaults to 't2v'): | |
| Model variant - 't2v' (text-to-video) or 'i2v' (image-to-video) | |
| patch_size (`tuple`, *optional*, defaults to (1, 2, 2)): | |
| 3D patch dimensions for video embedding (t_patch, h_patch, w_patch) | |
| text_len (`int`, *optional*, defaults to 512): | |
| Fixed length for text embeddings | |
| in_dim (`int`, *optional*, defaults to 16): | |
| Input video channels (C_in) | |
| dim (`int`, *optional*, defaults to 2048): | |
| Hidden dimension of the transformer | |
| ffn_dim (`int`, *optional*, defaults to 8192): | |
| Intermediate dimension in feed-forward network | |
| freq_dim (`int`, *optional*, defaults to 256): | |
| Dimension for sinusoidal time embeddings | |
| text_dim (`int`, *optional*, defaults to 4096): | |
| Input dimension for text embeddings | |
| out_dim (`int`, *optional*, defaults to 16): | |
| Output video channels (C_out) | |
| num_heads (`int`, *optional*, defaults to 16): | |
| Number of attention heads | |
| num_layers (`int`, *optional*, defaults to 32): | |
| Number of transformer blocks | |
| local_attn_size (`int`, *optional*, defaults to -1): | |
| Window size for temporal local attention (-1 indicates global attention) | |
| sink_size (`int`, *optional*, defaults to 0): | |
| Size of the attention sink, we keep the first `sink_size` frames unchanged when rolling the KV cache | |
| qk_norm (`bool`, *optional*, defaults to True): | |
| Enable query/key normalization | |
| cross_attn_norm (`bool`, *optional*, defaults to False): | |
| Enable cross-attention normalization | |
| eps (`float`, *optional*, defaults to 1e-6): | |
| Epsilon value for normalization layers | |
| """ | |
| super().__init__() | |
| assert model_type in ['t2v', 'i2v'] | |
| self.model_type = model_type | |
| self.patch_size = patch_size | |
| self.text_len = text_len | |
| self.in_dim = in_dim | |
| self.dim = dim | |
| self.ffn_dim = ffn_dim | |
| self.freq_dim = freq_dim | |
| self.text_dim = text_dim | |
| self.out_dim = out_dim | |
| self.num_heads = num_heads | |
| self.num_layers = num_layers | |
| self.local_attn_size = local_attn_size | |
| self.qk_norm = qk_norm | |
| self.cross_attn_norm = cross_attn_norm | |
| self.eps = eps | |
| # embeddings | |
| self.patch_embedding = nn.Conv3d( | |
| in_dim, dim, kernel_size=patch_size, stride=patch_size) | |
| self.source_patch_embedding = None | |
| self.text_embedding = nn.Sequential( | |
| nn.Linear(text_dim, dim), nn.GELU(approximate='tanh'), | |
| nn.Linear(dim, dim)) | |
| self.time_embedding = nn.Sequential( | |
| nn.Linear(freq_dim, dim), nn.SiLU(), nn.Linear(dim, dim)) | |
| self.time_projection = nn.Sequential( | |
| nn.SiLU(), nn.Linear(dim, dim * 6)) | |
| # blocks | |
| cross_attn_type = 't2v_cross_attn' if model_type == 't2v' else 'i2v_cross_attn' | |
| self.blocks = nn.ModuleList([ | |
| CausalWanAttentionBlock(cross_attn_type, dim, ffn_dim, num_heads, | |
| local_attn_size, sink_size, qk_norm, cross_attn_norm, eps, block_id) | |
| for block_id in range(num_layers) | |
| ]) | |
| # head | |
| self.head = CausalHead(dim, out_dim, patch_size, eps) | |
| # buffers (don't use register_buffer otherwise dtype will be changed in to()) | |
| assert (dim % num_heads) == 0 and (dim // num_heads) % 2 == 0 | |
| d = dim // num_heads | |
| self.freqs = torch.cat([ | |
| rope_params(1024, d - 4 * (d // 6)), | |
| rope_params(1024, 2 * (d // 6)), | |
| rope_params(1024, 2 * (d // 6)) | |
| ], | |
| dim=1) | |
| if model_type == 'i2v': | |
| self.img_emb = MLPProj(1280, dim) | |
| # initialize weights | |
| self.init_weights() | |
| self.gradient_checkpointing = False | |
| self.block_mask = None | |
| self.num_frame_per_block = 1 | |
| self.independent_first_frame = False | |
| # Token Pruning配置(在patch_embedding后进行) | |
| self.enable_internal_pruning = False | |
| self.internal_pruning_layers = set() # 🆕 控制哪些层进行pruning: {"self_attn", "ffn"} | |
| self.first_chunk_cache_item = "x" | |
| self.unpruned_fill_strategy = "first_chunk" | |
| self.use_mean_alignment = False | |
| self.first_chunk_block_inputs = {} | |
| self.prev_step_self_attn_deltas = {} | |
| for i in range(self.num_layers): | |
| self.prev_step_self_attn_deltas[i] = torch.zeros([1, 4680, 1536]) | |
| self.prev_step_ffn_deltas = {} # {block_idx: [B, F*H*W, D]} | |
| for i in range(self.num_layers): | |
| self.prev_step_ffn_deltas[i] = torch.zeros([1, 4680, 1536]) | |
| def _initialize_source_patch_embedding(self, model_path=None): | |
| """ | |
| 初始化source_patch_embedding(只在unpruned_fill_strategy="source_latent"时调用) | |
| 从预训练模型加载patch_embedding权重 | |
| Args: | |
| model_path: 模型路径,如果为None则使用默认路径 | |
| """ | |
| if self.source_patch_embedding is None: | |
| # 获取主patch_embedding的device和dtype | |
| device = self.patch_embedding.weight.device | |
| dtype = self.patch_embedding.weight.dtype | |
| self.source_patch_embedding = nn.Conv3d( | |
| self.in_dim, self.dim, | |
| kernel_size=self.patch_size, | |
| stride=self.patch_size | |
| ).to(device=device, dtype=dtype) | |
| # 如果没有指定路径,使用默认路径 | |
| if model_path is None: | |
| model_path = "wan_models/Wan2.1-T2V-1.3B/" | |
| # 从预训练模型加载patch_embedding权重 | |
| try: | |
| # 尝试加载safetensors格式 | |
| from safetensors.torch import load_file | |
| checkpoint_path = f"{model_path}/diffusion_pytorch_model.safetensors" | |
| state_dict = load_file(checkpoint_path) | |
| print(f"[Source Patch Embedding] Loading from safetensors: {checkpoint_path}") | |
| except Exception as e: | |
| try: | |
| # 如果safetensors失败,尝试.pth格式 | |
| checkpoint_path = f"{model_path}/diffusion_pytorch_model.pth" | |
| state_dict = torch.load(checkpoint_path, map_location='cpu', weights_only=False) | |
| print(f"[Source Patch Embedding] Loading from pth: {checkpoint_path}") | |
| except Exception as e2: | |
| # 如果都失败,则复制当前模型的权重 | |
| print(f"[Source Patch Embedding] Warning: Failed to load checkpoint, copying from current model") | |
| print(f" Safetensors error: {e}") | |
| print(f" PTH error: {e2}") | |
| with torch.no_grad(): | |
| self.source_patch_embedding.weight.copy_(self.patch_embedding.weight) | |
| if self.patch_embedding.bias is not None: | |
| self.source_patch_embedding.bias.copy_(self.patch_embedding.bias) | |
| # 设置为eval模式 | |
| self.source_patch_embedding.eval() | |
| self.source_patch_embedding.requires_grad_(False) | |
| print(f"[Source Patch Embedding] Initialized by copying from current model") | |
| return | |
| # 提取patch_embedding的权重 | |
| patch_weight_key = 'patch_embedding.weight' | |
| patch_bias_key = 'patch_embedding.bias' | |
| if patch_weight_key in state_dict: | |
| with torch.no_grad(): | |
| # 转换到正确的dtype和device | |
| weight = state_dict[patch_weight_key].to(device=device, dtype=dtype) | |
| self.source_patch_embedding.weight.copy_(weight) | |
| if patch_bias_key in state_dict and self.source_patch_embedding.bias is not None: | |
| bias = state_dict[patch_bias_key].to(device=device, dtype=dtype) | |
| self.source_patch_embedding.bias.copy_(bias) | |
| print(f"[Source Patch Embedding] Loaded weights: {self.source_patch_embedding.weight.shape}, dtype={dtype}") | |
| else: | |
| # 如果找不到,则复制当前模型的权重 | |
| print(f"[Source Patch Embedding] Warning: 'patch_embedding' not found in checkpoint, copying from current model") | |
| with torch.no_grad(): | |
| self.source_patch_embedding.weight.copy_(self.patch_embedding.weight) | |
| if self.patch_embedding.bias is not None: | |
| self.source_patch_embedding.bias.copy_(self.patch_embedding.bias) | |
| # 设置为eval模式,不参与训练 | |
| self.source_patch_embedding.eval() | |
| self.source_patch_embedding.requires_grad_(False) | |
| print(f"[Source Patch Embedding] Initialized successfully") | |
| def _set_gradient_checkpointing(self, module, value=False): | |
| self.gradient_checkpointing = value | |
| def _prepare_blockwise_causal_attn_mask( | |
| device: torch.device | str, num_frames: int = 21, | |
| frame_seqlen: int = 1560, num_frame_per_block=1, local_attn_size=-1 | |
| ) -> BlockMask: | |
| """ | |
| we will divide the token sequence into the following format | |
| [1 latent frame] [1 latent frame] ... [1 latent frame] | |
| We use flexattention to construct the attention mask | |
| """ | |
| total_length = num_frames * frame_seqlen | |
| # we do right padding to get to a multiple of 128 | |
| padded_length = math.ceil(total_length / 128) * 128 - total_length | |
| ends = torch.zeros(total_length + padded_length, | |
| device=device, dtype=torch.long) | |
| # Block-wise causal mask will attend to all elements that are before the end of the current chunk | |
| frame_indices = torch.arange( | |
| start=0, | |
| end=total_length, | |
| step=frame_seqlen * num_frame_per_block, | |
| device=device | |
| ) | |
| for tmp in frame_indices: | |
| ends[tmp:tmp + frame_seqlen * num_frame_per_block] = tmp + \ | |
| frame_seqlen * num_frame_per_block | |
| def attention_mask(b, h, q_idx, kv_idx): | |
| if local_attn_size == -1: | |
| return (kv_idx < ends[q_idx]) | (q_idx == kv_idx) | |
| else: | |
| return ((kv_idx < ends[q_idx]) & (kv_idx >= (ends[q_idx] - local_attn_size * frame_seqlen))) | (q_idx == kv_idx) | |
| # return ((kv_idx < total_length) & (q_idx < total_length)) | (q_idx == kv_idx) # bidirectional mask | |
| block_mask = create_block_mask(attention_mask, B=None, H=None, Q_LEN=total_length + padded_length, | |
| KV_LEN=total_length + padded_length, _compile=False, device=device) | |
| import torch.distributed as dist | |
| if not dist.is_initialized() or dist.get_rank() == 0: | |
| print( | |
| f" cache a block wise causal mask with block size of {num_frame_per_block} frames") | |
| print(block_mask) | |
| # import imageio | |
| # import numpy as np | |
| # from torch.nn.attention.flex_attention import create_mask | |
| # mask = create_mask(attention_mask, B=None, H=None, Q_LEN=total_length + | |
| # padded_length, KV_LEN=total_length + padded_length, device=device) | |
| # import cv2 | |
| # mask = cv2.resize(mask[0, 0].cpu().float().numpy(), (1024, 1024)) | |
| # imageio.imwrite("mask_%d.jpg" % (0), np.uint8(255. * mask)) | |
| return block_mask | |
| def _prepare_teacher_forcing_mask( | |
| device: torch.device | str, num_frames: int = 21, | |
| frame_seqlen: int = 1560, num_frame_per_block=1 | |
| ) -> BlockMask: | |
| """ | |
| we will divide the token sequence into the following format | |
| [1 latent frame] [1 latent frame] ... [1 latent frame] | |
| We use flexattention to construct the attention mask | |
| """ | |
| # debug | |
| DEBUG = False | |
| if DEBUG: | |
| num_frames = 9 | |
| frame_seqlen = 256 | |
| total_length = num_frames * frame_seqlen * 2 | |
| # we do right padding to get to a multiple of 128 | |
| padded_length = math.ceil(total_length / 128) * 128 - total_length | |
| clean_ends = num_frames * frame_seqlen | |
| # for clean context frames, we can construct their flex attention mask based on a [start, end] interval | |
| context_ends = torch.zeros(total_length + padded_length, device=device, dtype=torch.long) | |
| # for noisy frames, we need two intervals to construct the flex attention mask [context_start, context_end] [noisy_start, noisy_end] | |
| noise_context_starts = torch.zeros(total_length + padded_length, device=device, dtype=torch.long) | |
| noise_context_ends = torch.zeros(total_length + padded_length, device=device, dtype=torch.long) | |
| noise_noise_starts = torch.zeros(total_length + padded_length, device=device, dtype=torch.long) | |
| noise_noise_ends = torch.zeros(total_length + padded_length, device=device, dtype=torch.long) | |
| # Block-wise causal mask will attend to all elements that are before the end of the current chunk | |
| attention_block_size = frame_seqlen * num_frame_per_block | |
| frame_indices = torch.arange( | |
| start=0, | |
| end=num_frames * frame_seqlen, | |
| step=attention_block_size, | |
| device=device, dtype=torch.long | |
| ) | |
| # attention for clean context frames | |
| for start in frame_indices: | |
| context_ends[start:start + attention_block_size] = start + attention_block_size | |
| noisy_image_start_list = torch.arange( | |
| num_frames * frame_seqlen, total_length, | |
| step=attention_block_size, | |
| device=device, dtype=torch.long | |
| ) | |
| noisy_image_end_list = noisy_image_start_list + attention_block_size | |
| # attention for noisy frames | |
| for block_index, (start, end) in enumerate(zip(noisy_image_start_list, noisy_image_end_list)): | |
| # attend to noisy tokens within the same block | |
| noise_noise_starts[start:end] = start | |
| noise_noise_ends[start:end] = end | |
| # attend to context tokens in previous blocks | |
| # noise_context_starts[start:end] = 0 | |
| noise_context_ends[start:end] = block_index * attention_block_size | |
| def attention_mask(b, h, q_idx, kv_idx): | |
| # first design the mask for clean frames | |
| clean_mask = (q_idx < clean_ends) & (kv_idx < context_ends[q_idx]) | |
| # then design the mask for noisy frames | |
| # noisy frames will attend to all clean preceeding clean frames + itself | |
| C1 = (kv_idx < noise_noise_ends[q_idx]) & (kv_idx >= noise_noise_starts[q_idx]) | |
| C2 = (kv_idx < noise_context_ends[q_idx]) & (kv_idx >= noise_context_starts[q_idx]) | |
| noise_mask = (q_idx >= clean_ends) & (C1 | C2) | |
| eye_mask = q_idx == kv_idx | |
| return eye_mask | clean_mask | noise_mask | |
| block_mask = create_block_mask(attention_mask, B=None, H=None, Q_LEN=total_length + padded_length, | |
| KV_LEN=total_length + padded_length, _compile=False, device=device) | |
| if DEBUG: | |
| print(block_mask) | |
| import imageio | |
| import numpy as np | |
| from torch.nn.attention.flex_attention import create_mask | |
| mask = create_mask(attention_mask, B=None, H=None, Q_LEN=total_length + | |
| padded_length, KV_LEN=total_length + padded_length, device=device) | |
| import cv2 | |
| mask = cv2.resize(mask[0, 0].cpu().float().numpy(), (1024, 1024)) | |
| imageio.imwrite("mask_%d.jpg" % (0), np.uint8(255. * mask)) | |
| return block_mask | |
| def _prepare_blockwise_causal_attn_mask_i2v( | |
| device: torch.device | str, num_frames: int = 21, | |
| frame_seqlen: int = 1560, num_frame_per_block=4, local_attn_size=-1 | |
| ) -> BlockMask: | |
| """ | |
| we will divide the token sequence into the following format | |
| [1 latent frame] [N latent frame] ... [N latent frame] | |
| The first frame is separated out to support I2V generation | |
| We use flexattention to construct the attention mask | |
| """ | |
| total_length = num_frames * frame_seqlen | |
| # we do right padding to get to a multiple of 128 | |
| padded_length = math.ceil(total_length / 128) * 128 - total_length | |
| ends = torch.zeros(total_length + padded_length, | |
| device=device, dtype=torch.long) | |
| # special handling for the first frame | |
| ends[:frame_seqlen] = frame_seqlen | |
| # Block-wise causal mask will attend to all elements that are before the end of the current chunk | |
| frame_indices = torch.arange( | |
| start=frame_seqlen, | |
| end=total_length, | |
| step=frame_seqlen * num_frame_per_block, | |
| device=device | |
| ) | |
| for idx, tmp in enumerate(frame_indices): | |
| ends[tmp:tmp + frame_seqlen * num_frame_per_block] = tmp + \ | |
| frame_seqlen * num_frame_per_block | |
| def attention_mask(b, h, q_idx, kv_idx): | |
| if local_attn_size == -1: | |
| return (kv_idx < ends[q_idx]) | (q_idx == kv_idx) | |
| else: | |
| return ((kv_idx < ends[q_idx]) & (kv_idx >= (ends[q_idx] - local_attn_size * frame_seqlen))) | \ | |
| (q_idx == kv_idx) | |
| block_mask = create_block_mask(attention_mask, B=None, H=None, Q_LEN=total_length + padded_length, | |
| KV_LEN=total_length + padded_length, _compile=False, device=device) | |
| if not dist.is_initialized() or dist.get_rank() == 0: | |
| print( | |
| f" cache a block wise causal mask with block size of {num_frame_per_block} frames") | |
| print(block_mask) | |
| # import imageio | |
| # import numpy as np | |
| # from torch.nn.attention.flex_attention import create_mask | |
| # mask = create_mask(attention_mask, B=None, H=None, Q_LEN=total_length + | |
| # padded_length, KV_LEN=total_length + padded_length, device=device) | |
| # import cv2 | |
| # mask = cv2.resize(mask[0, 0].cpu().float().numpy(), (1024, 1024)) | |
| # imageio.imwrite("mask_%d.jpg" % (0), np.uint8(255. * mask)) | |
| return block_mask | |
| def _forward_inference( | |
| self, | |
| x, | |
| t, | |
| context, | |
| seq_len, | |
| clip_fea=None, | |
| kv_cache=None, | |
| crossattn_cache=None, | |
| current_start=None, | |
| cache_start=None, | |
| debug_dict=None, | |
| y=None, # 🆕 source latent for unpruned filling | |
| kept_indices_per_frame=None, # 🆕 Block Internal Pruning使用 | |
| importance_mask=None, # Old Per-frame Pruning使用(废弃) | |
| use_pruning=False, # 🆕 是否在当前step应用pruning | |
| ): | |
| # 🐛 Debug: 检查进入forward时的状态 | |
| # if hasattr(self, 'prev_step_self_attn_deltas') and not self.training: | |
| # none_count = sum(1 for v in self.prev_step_self_attn_deltas.values() if v is None) | |
| # total_count = len(self.prev_step_self_attn_deltas) | |
| # print(f"[Forward Entry] prev_step_self_attn_deltas: {none_count}/{total_count} blocks are None, id={id(self.prev_step_self_attn_deltas)}") | |
| device = self.patch_embedding.weight.device | |
| if self.freqs.device != device: | |
| self.freqs = self.freqs.to(device) | |
| # 🆕 如果有y,并且使用source_latent填充策略,单独处理source | |
| source_latent_embedded = None | |
| if y is not None: | |
| # 原有训练逻辑:concat后一起patch_embedding | |
| x = [torch.cat([u, v], dim=0) for u, v in zip(x, y)] | |
| # 只在使用source_latent填充策略时才处理source | |
| if self.unpruned_fill_strategy == "source_latent": | |
| # 初始化source_patch_embedding(如果还没初始化) | |
| if self.source_patch_embedding is None: | |
| self._initialize_source_patch_embedding() | |
| # 对source单独做patch_embedding | |
| with torch.no_grad(): | |
| source_embed = [self.source_patch_embedding(v.unsqueeze(0)) for v in y] | |
| source_latent_embedded = [u.flatten(2).transpose(1, 2) for u in source_embed] | |
| source_latent_embedded = torch.cat(source_latent_embedded) # [B, L, C] | |
| # 💾 保存latent空间的grid_sizes(patch_embedding前) | |
| # x: List[Tensor], 每个shape=[C, F, H, W](如果有y则C=32,否则C=16) | |
| latent_grid_sizes = torch.stack( | |
| [torch.tensor(u.shape[1:], dtype=torch.long) for u in x]) # [B, 3] (F, H_latent, W_latent) | |
| # embeddings (保持原有训练逻辑) | |
| x = [self.patch_embedding(u.unsqueeze(0)) for u in x] | |
| grid_sizes = torch.stack( | |
| [torch.tensor(u.shape[2:], dtype=torch.long) for u in x]) # [B, 3] (F, H_patch, W_patch) | |
| x = [u.flatten(2).transpose(1, 2) for u in x] | |
| seq_lens = torch.tensor([u.size(1) for u in x], dtype=torch.long) | |
| assert seq_lens.max() <= seq_len | |
| x = torch.cat(x) | |
| pruning_info = {'pruned': False} | |
| original_seq_lens = seq_lens | |
| original_grid_sizes = grid_sizes | |
| if kept_indices_per_frame is not None: | |
| # print("🎯 Block Internal Pruning (新实现)") | |
| # Pipeline传来的kept_indices是latent空间的(60×104) | |
| # 需要转换到patch_embedding后空间的indices (30×52) | |
| # 1. 使用latent空间的grid_sizes重建mask | |
| B = x.shape[0] | |
| F, H_latent, W_latent = latent_grid_sizes[0].tolist() # ✅ 使用latent空间尺寸 | |
| patch_t, patch_h, patch_w = self.patch_size | |
| H_patch = H_latent // patch_h | |
| W_patch = W_latent // patch_w | |
| tokens_per_frame_latent = H_latent * W_latent | |
| tokens_per_frame_patch = H_patch * W_patch | |
| # 重建latent空间mask | |
| latent_mask = torch.zeros(F, H_latent, W_latent, dtype=torch.bool, device=x.device) | |
| for f_idx in range(F): | |
| kept_idx_latent = kept_indices_per_frame[f_idx] | |
| # 🔧 添加边界检查和裁剪 | |
| valid_mask = (kept_idx_latent >= 0) & (kept_idx_latent < H_latent * W_latent) | |
| kept_idx_latent = kept_idx_latent[valid_mask] | |
| h_coords = kept_idx_latent // W_latent | |
| w_coords = kept_idx_latent % W_latent | |
| # 再次确保索引在范围内 | |
| h_coords = torch.clamp(h_coords, 0, H_latent - 1) | |
| w_coords = torch.clamp(w_coords, 0, W_latent - 1) | |
| latent_mask[f_idx, h_coords, w_coords] = True | |
| # 2. 下采样到patch空间(average pooling + threshold) | |
| # [F, H_latent, W_latent] → [F, H_patch, W_patch] | |
| latent_mask_float = latent_mask.float().unsqueeze(0).unsqueeze(0) # [1, 1, F, H, W] | |
| latent_mask_2d = latent_mask_float.squeeze(1) # [1, F, H, W] | |
| # 对每个frame单独average pooling | |
| patch_masks = [] | |
| for f_idx in range(F): | |
| frame_mask = latent_mask_2d[:, f_idx:f_idx+1] # [1, 1, H, W] | |
| # Average pooling: 计算每个patch内True的比例 | |
| pooled = torch.nn.functional.avg_pool2d( | |
| frame_mask, | |
| kernel_size=(patch_h, patch_w), | |
| stride=(patch_h, patch_w) | |
| ) | |
| # 阈值:patch内>50%的像素为True → 该patch为True | |
| patch_mask_bool = (pooled > 0.5).squeeze() # [H_patch, W_patch] | |
| patch_masks.append(patch_mask_bool) | |
| patch_mask = torch.stack(patch_masks) # [F, H_patch, W_patch] | |
| # 3. 从patch mask提取indices | |
| kept_indices_per_frame_patch = [] | |
| for f_idx in range(F): | |
| frame_mask_flat = patch_mask[f_idx].flatten() # [H_patch * W_patch] | |
| kept_idx = torch.nonzero(frame_mask_flat, as_tuple=True)[0] | |
| kept_indices_per_frame_patch.append(kept_idx) | |
| # 4. Per-frame统一长度 | |
| min_kept = min(len(idx) for idx in kept_indices_per_frame_patch) | |
| kept_indices_per_frame_patch = [idx[:min_kept] for idx in kept_indices_per_frame_patch] | |
| # x保持完整,pruning在每个Block内部进行 | |
| pruning_info = { | |
| 'internal_pruning': True, | |
| 'use_pruning': use_pruning, # 🆕 是否在当前step应用pruning | |
| 'kept_indices_per_frame': kept_indices_per_frame_patch, # ✅ 使用patch空间的indices | |
| 'first_chunk_block_inputs': self.first_chunk_block_inputs, # 🆕 传递缓存引用 | |
| 'pruning_layers': self.internal_pruning_layers, # 🆕 传递层级配置 | |
| 'cache_item': self.first_chunk_cache_item, # 🆕 传递缓存内容配置: "x" or "delta" | |
| 'fill_strategy': self.unpruned_fill_strategy, # 🆕 传递填充策略 | |
| 'prev_step_self_attn_deltas': self.prev_step_self_attn_deltas, # 🆕 self-attn的delta缓存 | |
| 'prev_step_ffn_deltas': self.prev_step_ffn_deltas, # 🆕 FFN的delta缓存 | |
| 'use_mean_alignment': self.use_mean_alignment, # 🆕 均值对齐开关 | |
| 'source_latent': source_latent_embedded # 🆕 源latent (patch_embedding后的y) | |
| } | |
| total_kept = F * min_kept | |
| reduction = (1 - total_kept / (F * tokens_per_frame_patch)) * 100 | |
| # 🆕 显示层级配置 | |
| layers_enabled = [] | |
| if "self_attn" in self.internal_pruning_layers: | |
| layers_enabled.append("Self-Attn") | |
| if "ffn" in self.internal_pruning_layers: | |
| layers_enabled.append("FFN") | |
| layers_str = " + ".join(layers_enabled) if layers_enabled else "None" | |
| importance_mask = None | |
| """ | |
| torch.cat([ | |
| torch.cat([u, u.new_zeros(1, seq_len - u.size(1), u.size(2))], | |
| dim=1) for u in x | |
| ]) | |
| """ | |
| # time embeddings | |
| # with amp.autocast(dtype=torch.float32): | |
| e = self.time_embedding( | |
| sinusoidal_embedding_1d(self.freq_dim, t.flatten()).type_as(x)) | |
| e0 = self.time_projection(e).unflatten( | |
| 1, (6, self.dim)).unflatten(dim=0, sizes=t.shape) | |
| # assert e.dtype == torch.float32 and e0.dtype == torch.float32 | |
| # context | |
| context_lens = None | |
| context = self.text_embedding( | |
| torch.stack([ | |
| torch.cat( | |
| [u, u.new_zeros(self.text_len - u.size(0), u.size(1))]) | |
| for u in context | |
| ])) | |
| if clip_fea is not None: | |
| context_clip = self.img_emb(clip_fea) # bs x 257 x dim | |
| context = torch.concat([context_clip, context], dim=1) | |
| # arguments | |
| kwargs = dict( | |
| e=e0, | |
| seq_lens=seq_lens, | |
| grid_sizes=grid_sizes, | |
| freqs=self.freqs, | |
| context=context, | |
| context_lens=context_lens, | |
| block_mask=self.block_mask, | |
| pruning_info=pruning_info # 🆕 传递pruning信息给attention层 | |
| ) | |
| def create_custom_forward(module): | |
| def custom_forward(*inputs, **kwargs): | |
| return module(*inputs, **kwargs) | |
| return custom_forward | |
| for block_index, block in enumerate(self.blocks): | |
| if torch.is_grad_enabled() and self.gradient_checkpointing: | |
| kwargs.update( | |
| { | |
| "kv_cache": kv_cache[block_index], | |
| "current_start": current_start, | |
| "cache_start": cache_start | |
| } | |
| ) | |
| x = torch.utils.checkpoint.checkpoint( | |
| create_custom_forward(block), | |
| x, **kwargs, | |
| use_reentrant=False, | |
| ) | |
| else: | |
| if debug_dict is not None: | |
| debug_dict["block_index"] = block_index | |
| kwargs.update( | |
| { | |
| "kv_cache": kv_cache[block_index], | |
| "crossattn_cache": crossattn_cache[block_index], | |
| "current_start": current_start, | |
| "cache_start": cache_start, | |
| "debug_dict": debug_dict, | |
| "block_idx": block_index # 🆕 传递block索引 | |
| } | |
| ) | |
| x = block(x, **kwargs) # 对应CausalWanAttentionBlock | |
| x = self.head(x, e.unflatten(dim=0, sizes=t.shape).unsqueeze(2)) | |
| # unpatchify | |
| x = self.unpatchify(x, grid_sizes) | |
| return torch.stack(x) | |
| def _forward_train( | |
| self, | |
| x, | |
| t, | |
| context, | |
| seq_len, | |
| clean_x=None, | |
| aug_t=None, | |
| clip_fea=None, | |
| y=None, | |
| ): | |
| r""" | |
| Forward pass through the diffusion model | |
| Args: | |
| x (List[Tensor]): | |
| List of input video tensors, each with shape [C_in, F, H, W] | |
| t (Tensor): | |
| Diffusion timesteps tensor of shape [B] | |
| context (List[Tensor]): | |
| List of text embeddings each with shape [L, C] | |
| seq_len (`int`): | |
| Maximum sequence length for positional encoding | |
| clip_fea (Tensor, *optional*): | |
| CLIP image features for image-to-video mode | |
| y (List[Tensor], *optional*): | |
| Conditional video inputs for image-to-video mode, same shape as x | |
| Returns: | |
| List[Tensor]: | |
| List of denoised video tensors with original input shapes [C_out, F, H / 8, W / 8] | |
| """ | |
| # print(f"{[u.shape for u in x]=} {t.shape=} {[u.shape for u in context]=} {seq_len=}") | |
| if self.model_type == 'i2v': | |
| assert clip_fea is not None and y is not None | |
| # params | |
| device = self.patch_embedding.weight.device | |
| if self.freqs.device != device: | |
| self.freqs = self.freqs.to(device) | |
| # Construct blockwise causal attn mask | |
| if self.block_mask is None: | |
| if clean_x is not None: | |
| if self.independent_first_frame: | |
| raise NotImplementedError() | |
| else: | |
| self.block_mask = self._prepare_teacher_forcing_mask( | |
| device, num_frames=x.shape[2], | |
| frame_seqlen=x.shape[-2] * x.shape[-1] // (self.patch_size[1] * self.patch_size[2]), | |
| num_frame_per_block=self.num_frame_per_block | |
| ) | |
| else: | |
| if self.independent_first_frame: | |
| self.block_mask = self._prepare_blockwise_causal_attn_mask_i2v( | |
| device, num_frames=x.shape[2], | |
| frame_seqlen=x.shape[-2] * x.shape[-1] // (self.patch_size[1] * self.patch_size[2]), | |
| num_frame_per_block=self.num_frame_per_block, | |
| local_attn_size=self.local_attn_size | |
| ) | |
| else: | |
| self.block_mask = self._prepare_blockwise_causal_attn_mask( | |
| device, num_frames=x.shape[2], | |
| frame_seqlen=x.shape[-2] * x.shape[-1] // (self.patch_size[1] * self.patch_size[2]), | |
| num_frame_per_block=self.num_frame_per_block, | |
| local_attn_size=self.local_attn_size | |
| ) | |
| if y is not None: # channel-wise | |
| x = [torch.cat([u, v[:, -u.shape[1]:] if u.shape[1] != v.shape[1] else v], dim=0) | |
| for u, v in zip(x, y)] | |
| # embeddings | |
| x = [self.patch_embedding(u.unsqueeze(0)) for u in x] | |
| # print(f"after patch embedding, {[u.shape for u in x]=}") | |
| grid_sizes = torch.stack( | |
| [torch.tensor(u.shape[2:], dtype=torch.long) for u in x]) | |
| x = [u.flatten(2).transpose(1, 2) for u in x] | |
| # print(f"after flatten, {[u.shape for u in x]=}") | |
| seq_lens = torch.tensor([u.size(1) for u in x], dtype=torch.long) | |
| assert seq_lens.max() <= seq_len | |
| x = torch.cat([ | |
| torch.cat([u, u.new_zeros(1, seq_lens[0] - u.size(1), u.size(2))], | |
| dim=1) for u in x | |
| ]) | |
| # time embeddings | |
| # with amp.autocast(dtype=torch.float32): | |
| # print(f"{x.shape=} {self.freq_dim=} {t.flatten().shape=}") | |
| e = self.time_embedding( | |
| sinusoidal_embedding_1d(self.freq_dim, t.flatten()).type_as(x)) | |
| e0 = self.time_projection(e).unflatten( | |
| 1, (6, self.dim)).unflatten(dim=0, sizes=t.shape) | |
| # assert e.dtype == torch.float32 and e0.dtype == torch.float32 | |
| # print(f"{x.shape=} {e.shape=} {e0.shape=} {seq_lens=} {grid_sizes=}") | |
| # context | |
| context_lens = None | |
| context = self.text_embedding( | |
| torch.stack([ | |
| torch.cat( | |
| [u, u.new_zeros(self.text_len - u.size(0), u.size(1))]) | |
| for u in context | |
| ])) | |
| if clip_fea is not None: | |
| context_clip = self.img_emb(clip_fea) # bs x 257 x dim | |
| context = torch.concat([context_clip, context], dim=1) | |
| if clean_x is not None: | |
| clean_x = [self.patch_embedding(u.unsqueeze(0)) for u in clean_x] | |
| clean_x = [u.flatten(2).transpose(1, 2) for u in clean_x] | |
| seq_lens_clean = torch.tensor([u.size(1) for u in clean_x], dtype=torch.long) | |
| assert seq_lens_clean.max() <= seq_len | |
| clean_x = torch.cat([ | |
| torch.cat([u, u.new_zeros(1, seq_lens_clean[0] - u.size(1), u.size(2))], dim=1) for u in clean_x | |
| ]) | |
| x = torch.cat([clean_x, x], dim=1) | |
| if aug_t is None: | |
| aug_t = torch.zeros_like(t) | |
| e_clean = self.time_embedding( | |
| sinusoidal_embedding_1d(self.freq_dim, aug_t.flatten()).type_as(x)) | |
| e0_clean = self.time_projection(e_clean).unflatten( | |
| 1, (6, self.dim)).unflatten(dim=0, sizes=t.shape) | |
| e0 = torch.cat([e0_clean, e0], dim=1) | |
| # arguments | |
| kwargs = dict( | |
| e=e0, | |
| seq_lens=seq_lens, | |
| grid_sizes=grid_sizes, | |
| freqs=self.freqs, | |
| context=context, | |
| context_lens=context_lens, | |
| block_mask=self.block_mask) | |
| def create_custom_forward(module): | |
| def custom_forward(*inputs, **kwargs): | |
| return module(*inputs, **kwargs) | |
| return custom_forward | |
| for block in self.blocks: | |
| if torch.is_grad_enabled() and self.gradient_checkpointing: | |
| x = torch.utils.checkpoint.checkpoint( | |
| create_custom_forward(block), | |
| x, **kwargs, | |
| use_reentrant=False, | |
| ) | |
| else: | |
| x = block(x, **kwargs) | |
| # print(f"in training forward, after block {block}, {x.shape=}") | |
| if clean_x is not None: | |
| x = x[:, x.shape[1] // 2:] | |
| # head | |
| x = self.head(x, e.unflatten(dim=0, sizes=t.shape).unsqueeze(2)) | |
| # unpatchify | |
| x = self.unpatchify(x, grid_sizes) | |
| return torch.stack(x) | |
| def forward( | |
| self, | |
| *args, | |
| **kwargs | |
| ): | |
| if kwargs.get('kv_cache', None) is not None: | |
| return self._forward_inference(*args, **kwargs) | |
| else: | |
| return self._forward_train(*args, **kwargs) | |
| def unpatchify(self, x, grid_sizes): | |
| r""" | |
| Reconstruct video tensors from patch embeddings. | |
| Args: | |
| x (List[Tensor]): | |
| List of patchified features, each with shape [L, C_out * prod(patch_size)] | |
| grid_sizes (Tensor): | |
| Original spatial-temporal grid dimensions before patching, | |
| shape [B, 3] (3 dimensions correspond to F_patches, H_patches, W_patches) | |
| Returns: | |
| List[Tensor]: | |
| Reconstructed video tensors with shape [C_out, F, H / 8, W / 8] | |
| """ | |
| c = self.out_dim | |
| out = [] | |
| for u, v in zip(x, grid_sizes.tolist()): | |
| u = u[:math.prod(v)].view(*v, *self.patch_size, c) | |
| u = torch.einsum('fhwpqrc->cfphqwr', u) | |
| u = u.reshape(c, *[i * j for i, j in zip(v, self.patch_size)]) | |
| out.append(u) | |
| return out | |
| def init_weights(self): | |
| r""" | |
| Initialize model parameters using Xavier initialization. | |
| """ | |
| # basic init | |
| for m in self.modules(): | |
| if isinstance(m, nn.Linear): | |
| nn.init.xavier_uniform_(m.weight) | |
| if m.bias is not None: | |
| nn.init.zeros_(m.bias) | |
| # init embeddings | |
| nn.init.xavier_uniform_(self.patch_embedding.weight.flatten(1)) | |
| for m in self.text_embedding.modules(): | |
| if isinstance(m, nn.Linear): | |
| nn.init.normal_(m.weight, std=.02) | |
| for m in self.time_embedding.modules(): | |
| if isinstance(m, nn.Linear): | |
| nn.init.normal_(m.weight, std=.02) | |
| # init output layer | |
| nn.init.zeros_(self.head.head.weight) | |