# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved. # SCAIL model integration for WanGP import torch from typing import Optional def build_scail_pose_tokens(model, pose_latents: torch.Tensor, target_dtype: Optional[torch.dtype] = None) -> torch.Tensor: """ Build SCAIL pose tokens to be concatenated after image tokens. Upstream SCAIL treats pose as an additional token sequence (typically at half spatial resolution), embedded by a dedicated Conv3d (`pose_patch_embedding`) and concatenated to the main token stream. Args: model: WanModel instance with `pose_patch_embedding` (Conv3d). pose_latents: VAE-encoded pose video (B, 16, T, H, W). target_dtype: Optional dtype to cast pose_latents before embedding. Returns: Pose tokens tensor (B, S_pose, dim). """ if target_dtype is not None and pose_latents.dtype != target_dtype: pose_latents = pose_latents.to(dtype=target_dtype) # Upstream uses an all-ones pose mask concatenated to pose latents (16 + 4 = 20 channels). pose_mask = torch.ones( pose_latents.shape[0], 4, *pose_latents.shape[2:], device=pose_latents.device, dtype=pose_latents.dtype, ) pose_input = torch.cat([pose_latents, pose_mask], dim=1) pose_embed = model.pose_patch_embedding(pose_input.to(model.pose_patch_embedding.weight.dtype)) # (B, dim, T', H', W') return pose_embed.flatten(2).transpose(1, 2) def after_patch_embedding_scail( model, x: torch.Tensor, pose_latents: torch.Tensor, mask: Optional[torch.Tensor] = None ): """ SCAIL-specific pose embedding injection. Unlike Animate, SCAIL doesn't need motion_encoder or face_encoder. It concatenates the mask with pose_latents (16 + 4 = 20 channels) and adds the pose embeddings to the latent representation. Args: model: WanModel instance with pose_patch_embedding x: Main latent tensor after patch embedding (B, seq_len, dim) pose_latents: VAE-encoded pose video (B, 16, T, H, W) mask: Conditioning mask (B, 4, T, H, W) to concatenate with pose_latents Returns: Modified x tensor with pose conditioning """ # Concatenate pose_latents with mask to match in_dim=20 if mask is not None: # pose_latents: (B, 16, T, H, W), mask: (B, 4, T, H, W) -> (B, 20, T, H, W) pose_input = torch.cat([pose_latents, mask], dim=1) else: # Fallback: pad with zeros if no mask available pad = torch.zeros( pose_latents.shape[0], 4, *pose_latents.shape[2:], device=pose_latents.device, dtype=pose_latents.dtype ) pose_input = torch.cat([pose_latents, pad], dim=1) # Embed pose latents through Conv3d # Output shape: (B, dim, T', H', W') - same as x pose_embed = model.pose_patch_embedding(pose_input) # Add pose embeddings to x (both have shape B, dim, T', H', W') # SCAIL doesn't have a reference frame in the latent input, so add to all positions x = x + pose_embed return x