Spaces:
Running on Zero
Running on Zero
Download src/utils/predictive_causal_model.py from hugging-apps/looking-ahead-ti2v: direct link, hf CLI and curl.
- Browser
- Download file 19.2 kB
-
https://huggingface.co/spaces/hugging-apps/looking-ahead-ti2v/resolve/main/src/utils/predictive_causal_model.py
- Command line
-
hf download hf://spaces/hugging-apps/looking-ahead-ti2v/src/utils/predictive_causal_model.py
-
curl -L -o predictive_causal_model.py https://huggingface.co/spaces/hugging-apps/looking-ahead-ti2v/resolve/main/src/utils/predictive_causal_model.py
19.2 kB
| """FCP with shared source streams and per-offset AdaLN mini DiTs. | |
| The causal backbone/cached preview route is shared with TF. Auxiliary modules | |
| retain checkpoint-compatible serialized names and are used only during teacher-forced training. | |
| """ | |
| import copy | |
| import logging | |
| import os | |
| import torch | |
| from torch import nn | |
| from torch.utils.checkpoint import checkpoint | |
| from src.utils.attention import attention | |
| from src.utils.causal_mask import visualize_causal_mask | |
| from src.utils.causal_model import CausalHead, CausalWanModel | |
| from src.utils.distributed import is_main_process | |
| from src.utils.predictive_multi_chunk_config import build_predictive_chunk_layout | |
| from src.wan22.modules.model import WanLayerNorm, WanRMSNorm, rope_apply, sinusoidal_embedding_1d | |
| class PredictiveSourceAdaLNProjection(nn.Linear): | |
| def reset_parameters(self): | |
| nn.init.zeros_(self.weight) | |
| if self.bias is not None: | |
| nn.init.zeros_(self.bias) | |
| class PredictiveMultiChunkSelfAttention(nn.Module): | |
| def __init__(self, dim, num_heads, qk_norm=True, eps=1e-6): | |
| super().__init__() | |
| self.num_heads = num_heads | |
| self.head_dim = dim // num_heads | |
| 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) if qk_norm else nn.Identity() | |
| self.norm_k = WanRMSNorm(dim, eps) if qk_norm else nn.Identity() | |
| def forward(self, x, grid_sizes, freqs): | |
| b, s = x.shape[:2] | |
| n, d = self.num_heads, self.head_dim | |
| 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) | |
| out = attention( | |
| rope_apply(q, grid_sizes, freqs).type_as(v), | |
| rope_apply(k, grid_sizes, freqs).type_as(v), | |
| v, | |
| causal=False, | |
| window_size=(-1, -1), | |
| ) | |
| return self.o(out.type_as(v).flatten(2)) | |
| class PredictiveMultiChunkAttentionBlock(nn.Module): | |
| def __init__(self, dim, ffn_dim, num_heads, qk_norm=True, eps=1e-6): | |
| super().__init__() | |
| self.dim = dim | |
| self.norm1 = WanLayerNorm(dim, eps) | |
| self.self_attn = PredictiveMultiChunkSelfAttention(dim, num_heads, 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.modulation = nn.Parameter(torch.randn(1, 6, dim) / dim**0.5) | |
| self.source_adaln_projection = PredictiveSourceAdaLNProjection(dim, 6 * dim) | |
| def forward(self, x, e, grid_sizes, freqs, source_tokens): | |
| if source_tokens.shape != x.shape: | |
| raise ValueError("FCP AdaLN source and future token shapes must match") | |
| frames, seq = e.shape[1], x.shape[1] // e.shape[1] | |
| source_mod = ( | |
| self.source_adaln_projection(source_tokens) | |
| .unflatten(1, (frames, seq)) | |
| .unflatten(-1, (6, self.dim)) | |
| ) | |
| e = (self.modulation.view(1, 1, 1, 6, self.dim) + e.unsqueeze(2) + source_mod).unbind(3) | |
| y = self.self_attn( | |
| (self.norm1(x).unflatten(1, (frames, seq)) * (1 + e[1]) + e[0]).flatten(1, 2), | |
| grid_sizes, | |
| freqs, | |
| ) | |
| x = x + (y.unflatten(1, (frames, seq)) * e[2]).flatten(1, 2) | |
| y = self.ffn((self.norm2(x).unflatten(1, (frames, seq)) * (1 + e[4]) + e[3]).flatten(1, 2)) | |
| return x + (y.unflatten(1, (frames, seq)) * e[5]).flatten(1, 2) | |
| class PredictiveMultiChunkBranch(nn.Module): | |
| def __init__(self, model, config): | |
| super().__init__() | |
| dim = model.dim | |
| self.hidden_adapter = nn.Sequential( | |
| nn.Linear( | |
| len(config["source_layers"]) * dim, config["source_layer_fusion"]["hidden_dim"] | |
| ), | |
| nn.GELU(), | |
| nn.Linear(config["source_layer_fusion"]["hidden_dim"], dim), | |
| ) | |
| self.blocks = nn.ModuleList( | |
| [ | |
| PredictiveMultiChunkAttentionBlock( | |
| dim, model.ffn_dim, model.num_heads, model.qk_norm, model.eps | |
| ) | |
| for _ in range(config["mini_dit_depth"]) | |
| ] | |
| ) | |
| _init_predictive_module(self) | |
| if config["init_mode"] == "copy": | |
| for block, index in zip(self.blocks, config["init_source"], strict=True): | |
| main = model.blocks[index].state_dict() | |
| target = block.state_dict() | |
| selected = {k: v for k, v in main.items() if k in target} | |
| missing, unexpected = block.load_state_dict(selected, strict=False) | |
| if unexpected or any(not k.startswith("source_adaln_projection.") for k in missing): | |
| raise RuntimeError("FCP mini DiT copy is incompatible with backbone blocks") | |
| block.source_adaln_projection.reset_parameters() | |
| # Fresh private modules preserve source initialization order. Do not | |
| # recopy these modules after a subsequent TF checkpoint warm start. | |
| for name in ("patch_embedding", "time_embedding", "time_projection", "head"): | |
| original = getattr(model, name) | |
| if name == "patch_embedding": | |
| module = nn.Conv3d( | |
| model.in_dim, dim, kernel_size=model.patch_size, stride=model.patch_size | |
| ) | |
| elif name == "time_embedding": | |
| module = nn.Sequential( | |
| nn.Linear(model.freq_dim, dim), nn.SiLU(), nn.Linear(dim, dim) | |
| ) | |
| elif name == "time_projection": | |
| module = nn.Sequential(nn.SiLU(), nn.Linear(dim, dim * 6)) | |
| else: | |
| module = CausalHead(dim, model.out_dim, model.patch_size, model.eps) | |
| reference = next(original.parameters()) | |
| module.to(device=reference.device, dtype=reference.dtype) | |
| if config["init_mode"] == "copy": | |
| module.load_state_dict(original.state_dict(), strict=True) | |
| else: | |
| _init_predictive_module(module) | |
| setattr(self, name, module) | |
| reference = next(model.parameters()) | |
| self.to(device=reference.device, dtype=reference.dtype) | |
| self.freq_dim, self.dim = model.freq_dim, dim | |
| def forward( | |
| self, | |
| hidden_tokens, | |
| future_latents, | |
| future_t, | |
| grid_sizes, | |
| freqs, | |
| gradient_checkpointing=False, | |
| ): | |
| dtype = hidden_tokens.dtype | |
| patch = self.patch_embedding(future_latents.to(self.patch_embedding.weight.dtype)) | |
| if tuple(patch.shape[2:]) != tuple(grid_sizes[0].tolist()): | |
| raise ValueError("FCP future latent patch grid mismatch") | |
| x = patch.flatten(2).transpose(1, 2).to(dtype) | |
| embeds = sinusoidal_embedding_1d(self.freq_dim, future_t.flatten()).to( | |
| self.time_embedding[0].weight.dtype | |
| ) | |
| embeds = self.time_embedding(embeds) | |
| e = ( | |
| self.time_projection(embeds) | |
| .unflatten(1, (6, self.dim)) | |
| .unflatten(0, future_t.shape) | |
| .to(dtype) | |
| ) | |
| head_e = embeds.unflatten(0, future_t.shape).unsqueeze(2).to(dtype) | |
| source = self.hidden_adapter(hidden_tokens).to(dtype) | |
| for block in self.blocks: | |
| if torch.is_grad_enabled() and gradient_checkpointing: | |
| x = checkpoint(block, x, e, grid_sizes, freqs, source, use_reentrant=False) | |
| else: | |
| x = block(x, e, grid_sizes, freqs, source) | |
| return self.head(x.to(self.head.head.weight.dtype), head_e.to(self.head.head.weight.dtype)) | |
| def _init_predictive_module(module): | |
| for child in module.modules(): | |
| if isinstance(child, PredictiveSourceAdaLNProjection): | |
| child.reset_parameters() | |
| elif isinstance(child, nn.Linear): | |
| nn.init.xavier_uniform_(child.weight) | |
| if child.bias is not None: | |
| nn.init.zeros_(child.bias) | |
| class PredictiveCausalWanModel(CausalWanModel): | |
| def configure_predictive_multi_chunk(self, config): | |
| if hasattr(self, "predictive_multi_chunk_branches"): | |
| raise RuntimeError("FCP branches are already configured") | |
| self.predictive_multi_chunk_config = copy.deepcopy(config) | |
| self.predictive_multi_chunk_branches = nn.ModuleDict( | |
| { | |
| f"offset_{offset}": PredictiveMultiChunkBranch(self, config) | |
| for offset in config["future_offsets"] | |
| } | |
| ) | |
| self.register_buffer( | |
| "predictive_multi_chunk_source_layers_metadata", | |
| torch.tensor( | |
| config["source_layers"], dtype=torch.int64, device=next(self.parameters()).device | |
| ), | |
| ) | |
| def _forward_predictive_multi_chunk( | |
| self, sources, grid_sizes, aux_noisy_chunks_by_offset, aux_timestep_chunks_by_offset | |
| ): | |
| cfg = self.predictive_multi_chunk_config | |
| batch = sources[cfg["source_layers"][0]].shape[0] | |
| frames = int(grid_sizes[0, 0]) | |
| layout = build_predictive_chunk_layout( | |
| num_frames=frames, | |
| block_size=self.num_frame_per_block, | |
| independent_first_frame=self.independent_first_frame, | |
| ) | |
| if layout.num_regular_chunks != cfg["num_chunks"]: | |
| raise ValueError("FCP runtime and configured chunk counts differ") | |
| frame_seq = int(grid_sizes[0, 1] * grid_sizes[0, 2]) | |
| noisy_tokens = frames * frame_seq | |
| regular_start = layout.regular_frame_start * frame_seq | |
| hidden = torch.cat([sources[layer] for layer in cfg["source_layers"]], dim=-1) | |
| chunk_tokens = self.num_frame_per_block * frame_seq | |
| source_chunks = { | |
| "clean": hidden[:, regular_start:noisy_tokens], | |
| "noisy": hidden[:, noisy_tokens + regular_start : 2 * noisy_tokens], | |
| } | |
| source_chunks = { | |
| name: value.reshape(batch, layout.num_regular_chunks, chunk_tokens, hidden.shape[-1]) | |
| for name, value in source_chunks.items() | |
| } | |
| chunk_grid = grid_sizes.clone() | |
| chunk_grid[:, 0] = self.num_frame_per_block | |
| outputs = {} | |
| for offset in cfg["future_offsets"]: | |
| valid = layout.valid_pair_count(offset) | |
| if ( | |
| offset not in aux_noisy_chunks_by_offset | |
| or offset not in aux_timestep_chunks_by_offset | |
| ): | |
| raise ValueError(f"Missing independent FCP inputs for offset {offset}") | |
| future = aux_noisy_chunks_by_offset[offset] | |
| future_t = aux_timestep_chunks_by_offset[offset] | |
| expected = (batch, self.in_dim, valid, self.num_frame_per_block) | |
| if tuple(future.shape[:4]) != expected or tuple(future_t.shape) != ( | |
| batch, | |
| valid, | |
| self.num_frame_per_block, | |
| ): | |
| raise ValueError(f"Invalid FCP auxiliary input shapes for offset {offset}") | |
| future = future.permute(0, 2, 1, 3, 4, 5).reshape( | |
| batch * valid, self.in_dim, self.num_frame_per_block, *future.shape[-2:] | |
| ) | |
| grids = chunk_grid.repeat_interleave(valid, dim=0) | |
| predictions = {} | |
| branch = self.predictive_multi_chunk_branches[f"offset_{offset}"] | |
| for name in cfg["source_context_streams"]: | |
| current = source_chunks[name][:, :valid].reshape( | |
| batch * valid, chunk_tokens, hidden.shape[-1] | |
| ) | |
| head = branch( | |
| current, | |
| future, | |
| future_t.reshape(batch * valid, self.num_frame_per_block), | |
| grids, | |
| self.freqs, | |
| self.gradient_checkpointing, | |
| ) | |
| prediction = torch.stack(self.unpatchify(head, grids)).permute(0, 2, 1, 3, 4) | |
| predictions[name] = prediction.unflatten(0, (batch, valid)) | |
| outputs[offset] = {"flow_pred_by_source": predictions, "valid_chunks": valid} | |
| return outputs | |
| def _forward_train( | |
| self, | |
| x, | |
| t, | |
| context, | |
| clean_x=None, | |
| return_predictive_aux=False, | |
| aux_noisy_chunks_by_offset=None, | |
| aux_timestep_chunks_by_offset=None, | |
| ): | |
| """ | |
| 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, F] | |
| context (List[Tensor]): | |
| List of text embeddings each with shape [L, C] | |
| 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] | |
| """ | |
| if clean_x is None: | |
| raise ValueError("Teacher-forcing forward requires clean_x.") | |
| device = self.patch_embedding.weight.device | |
| if self.freqs.device != device: | |
| self.freqs = self.freqs.to(device) | |
| if self.block_mask is None: | |
| 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, | |
| local_attn_size=self.local_attn_size, | |
| sink_size=self.sink_size, | |
| independent_first_frame=self.independent_first_frame, | |
| ) | |
| _vis_flag = os.environ.get("WAN_VIS_CAUSAL_MASK", "0") | |
| if _vis_flag and _vis_flag != "0": | |
| if is_main_process(): | |
| if not hasattr(self, "_causal_mask_vis_done"): | |
| self._causal_mask_vis_done = set() | |
| mode = "TF" | |
| vis_key = (mode, int(x.shape[2]), bool(self.independent_first_frame)) | |
| if vis_key not in self._causal_mask_vis_done: | |
| out_dir = os.environ.get( | |
| "WAN_VIS_CAUSAL_MASK_DIR", os.path.join("outputs", "causal_mask_vis") | |
| ) | |
| try: | |
| saved_path = visualize_causal_mask( | |
| mode=mode, | |
| num_frames=int(x.shape[2]), | |
| num_frame_per_block=self.num_frame_per_block, | |
| local_attn_size=self.local_attn_size, | |
| sink_size=self.sink_size, | |
| independent_first_frame=bool(self.independent_first_frame), | |
| out_dir=out_dir, | |
| ) | |
| logging.info( | |
| "[causal_mask_vis] saved %s mask (num_frames=%d, num_frame_per_block=%d, local_attn_size=%d, sink_size=%d, independent_first_frame=%s) -> %s", | |
| mode, | |
| int(x.shape[2]), | |
| self.num_frame_per_block, | |
| self.local_attn_size, | |
| self.sink_size, | |
| bool(self.independent_first_frame), | |
| saved_path, | |
| ) | |
| except Exception as _e: | |
| logging.warning("[causal_mask_vis] failed: %s", _e) | |
| self._causal_mask_vis_done.add(vis_key) | |
| 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]) | |
| 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) | |
| 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] | |
| ) | |
| 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) | |
| 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] | |
| ) | |
| ) | |
| clean_patch_embedding = self.patch_embedding | |
| clean_x = [clean_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) | |
| 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) | |
| 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) | |
| kwargs = { | |
| "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 | |
| sources, auxiliary = {}, None | |
| selected = ( | |
| self.predictive_multi_chunk_config["source_layers"] if return_predictive_aux else [] | |
| ) | |
| for index, block in enumerate(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) | |
| if index in selected: | |
| sources[index] = x | |
| if index == selected[-1]: | |
| auxiliary = self._forward_predictive_multi_chunk( | |
| sources, | |
| grid_sizes, | |
| aux_noisy_chunks_by_offset, | |
| aux_timestep_chunks_by_offset, | |
| ) | |
| x = x[:, x.shape[1] // 2 :] | |
| x = self.head(x, e.unflatten(dim=0, sizes=t.shape).unsqueeze(2)) | |
| x = self.unpatchify(x, grid_sizes) | |
| output = torch.stack(x) | |
| return (output, auxiliary) if return_predictive_aux else output | |