# Copyright 2025 StepFun Inc. All Rights Reserved. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # ============================================================================== import torch from einops import rearrange, repeat from torch import nn from fastvideo.v1.attention import DistributedAttention, LocalAttention from fastvideo.v1.configs.models.dits import StepVideoConfig from fastvideo.v1.distributed.parallel_state import ( get_sequence_model_parallel_world_size) from fastvideo.v1.layers.layernorm import LayerNormScaleShift from fastvideo.v1.layers.linear import ReplicatedLinear from fastvideo.v1.layers.mlp import MLP from fastvideo.v1.layers.rotary_embedding import (_apply_rotary_emb, get_rotary_pos_embed) from fastvideo.v1.layers.visual_embedding import TimestepEmbedder from fastvideo.v1.models.dits.base import BaseDiT from fastvideo.v1.platforms import _Backend class PatchEmbed2D(nn.Module): """2D Image to Patch Embedding Image to Patch Embedding using Conv2d A convolution based approach to patchifying a 2D image w/ embedding projection. Based on the impl in https://github.com/google-research/vision_transformer Hacked together by / Copyright 2020 Ross Wightman Remove the _assert function in forward function to be compatible with multi-resolution images. """ def __init__(self, patch_size=16, in_chans=3, embed_dim=768, norm_layer=None, flatten=True, bias=True, dtype=None, prefix: str = ""): super().__init__() # Convert patch_size to 2-tuple if isinstance(patch_size, list | tuple): if len(patch_size) == 1: patch_size = (patch_size[0], patch_size[0]) else: patch_size = (patch_size, patch_size) self.patch_size = patch_size self.flatten = flatten self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size, bias=bias, dtype=dtype) self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity() def forward(self, x): x = self.proj(x) if self.flatten: x = x.flatten(2).transpose(1, 2) # BCHW -> BNC x = self.norm(x) return x class StepVideoRMSNorm(nn.Module): def __init__( self, dim: int, elementwise_affine=True, eps: float = 1e-6, device=None, dtype=None, ): """ Initialize the RMSNorm normalization layer. Args: dim (int): The dimension of the input tensor. eps (float, optional): A small value added to the denominator for numerical stability. Default is 1e-6. Attributes: eps (float): A small value added to the denominator for numerical stability. weight (nn.Parameter): Learnable scaling parameter. """ factory_kwargs = {"device": device, "dtype": dtype} super().__init__() self.eps = eps if elementwise_affine: self.weight = nn.Parameter(torch.ones(dim, **factory_kwargs)) def _norm(self, x) -> torch.Tensor: """ Apply the RMSNorm normalization to the input tensor. Args: x (torch.Tensor): The input tensor. Returns: torch.Tensor: The normalized tensor. """ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) def forward(self, x): """ Forward pass through the RMSNorm layer. Args: x (torch.Tensor): The input tensor. Returns: torch.Tensor: The output tensor after applying RMSNorm. """ output = self._norm(x.float()).type_as(x) if hasattr(self, "weight"): output = output * self.weight return output class SelfAttention(nn.Module): def __init__(self, hidden_dim, head_dim, rope_split: tuple[int, int, int] = (64, 32, 32), bias: bool = False, with_rope: bool = True, with_qk_norm: bool = True, attn_type: str = "torch", supported_attention_backends=(_Backend.FLASH_ATTN, _Backend.TORCH_SDPA)): super().__init__() self.head_dim = head_dim self.hidden_dim = hidden_dim self.rope_split = list(rope_split) self.n_heads = hidden_dim // head_dim self.wqkv = ReplicatedLinear(hidden_dim, hidden_dim * 3, bias=bias) self.wo = ReplicatedLinear(hidden_dim, hidden_dim, bias=bias) self.with_rope = with_rope self.with_qk_norm = with_qk_norm if self.with_qk_norm: self.q_norm = StepVideoRMSNorm(head_dim, elementwise_affine=True) self.k_norm = StepVideoRMSNorm(head_dim, elementwise_affine=True) # self.core_attention = self.attn_processor(attn_type=attn_type) self.parallel = attn_type == 'parallel' self.attn = DistributedAttention( num_heads=self.n_heads, head_size=head_dim, causal=False, supported_attention_backends=supported_attention_backends) def _apply_rope(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor): """ x: [B, S, H, D] cos: [S, D/2] where D = head_dim = sum(self.rope_split) sin: [S, D/2] returns x with rotary applied exactly as v0 did """ B, S, H, D = x.shape # 1) split cos/sin per chunk half_splits = [c // 2 for c in self.rope_split] # [32,16,16] for [64,32,32] cos_splits = cos.split(half_splits, dim=1) sin_splits = sin.split(half_splits, dim=1) outs = [] idx = 0 for (chunk_size, cos_i, sin_i) in zip(self.rope_split, cos_splits, sin_splits, strict=False): # slice the corresponding channels x_chunk = x[..., idx:idx + chunk_size] # [B,S,H,chunk_size] idx += chunk_size # flatten to [S, B*H, chunk_size] x_flat = rearrange(x_chunk, 'b s h d -> s (b h) d') # apply rotary on *that* chunk out_flat = _apply_rotary_emb(x_flat, cos_i, sin_i, is_neox_style=True) # restore [B,S,H,chunk_size] out = rearrange(out_flat, 's (b h) d -> b s h d', b=B, h=H) outs.append(out) # concatenate back to [B,S,H,D] return torch.cat(outs, dim=-1) def forward(self, x, cu_seqlens=None, max_seqlen=None, rope_positions=None, cos_sin=None, attn_mask=None, mask_strategy=None): B, S, _ = x.shape xqkv, _ = self.wqkv(x) xqkv = xqkv.view(*x.shape[:-1], self.n_heads, 3 * self.head_dim) q, k, v = torch.split(xqkv, [self.head_dim] * 3, dim=-1) # [B,S,H,D] if self.with_qk_norm: q = self.q_norm(q) k = self.k_norm(k) if self.with_rope: if rope_positions is not None: F, Ht, W = rope_positions assert F * Ht * W == S, "rope_positions mismatches sequence length" cos, sin = cos_sin cos = cos.to(x.device, dtype=x.dtype) sin = sin.to(x.device, dtype=x.dtype) q = self._apply_rope(q, cos, sin) k = self._apply_rope(k, cos, sin) output, _ = self.attn(q, k, v) # [B,heads,S,D] output = rearrange(output, 'b s h d -> b s (h d)') output, _ = self.wo(output) return output class CrossAttention(nn.Module): def __init__( self, hidden_dim, head_dim, bias=False, with_qk_norm=True, supported_attention_backends=(_Backend.FLASH_ATTN, _Backend.TORCH_SDPA) ) -> None: super().__init__() self.head_dim = head_dim self.n_heads = hidden_dim // head_dim self.wq = ReplicatedLinear(hidden_dim, hidden_dim, bias=bias) self.wkv = ReplicatedLinear(hidden_dim, hidden_dim * 2, bias=bias) self.wo = ReplicatedLinear(hidden_dim, hidden_dim, bias=bias) self.with_qk_norm = with_qk_norm if self.with_qk_norm: self.q_norm = StepVideoRMSNorm(head_dim, elementwise_affine=True) self.k_norm = StepVideoRMSNorm(head_dim, elementwise_affine=True) self.attn = LocalAttention( num_heads=self.n_heads, head_size=head_dim, causal=False, supported_attention_backends=supported_attention_backends) def forward(self, x: torch.Tensor, encoder_hidden_states: torch.Tensor, attn_mask=None) -> torch.Tensor: xq, _ = self.wq(x) xq = xq.view(*xq.shape[:-1], self.n_heads, self.head_dim) xkv, _ = self.wkv(encoder_hidden_states) xkv = xkv.view(*xkv.shape[:-1], self.n_heads, 2 * self.head_dim) xk, xv = torch.split(xkv, [self.head_dim] * 2, dim=-1) ## seq_len, n, dim if self.with_qk_norm: xq = self.q_norm(xq) xk = self.k_norm(xk) output = self.attn(xq, xk, xv) output = rearrange(output, 'b s h d -> b s (h d)') output, _ = self.wo(output) return output class AdaLayerNormSingle(nn.Module): r""" Norm layer adaptive layer norm single (adaLN-single). As proposed in PixArt-Alpha (see: https://arxiv.org/abs/2310.00426; Section 2.3). Parameters: embedding_dim (`int`): The size of each embedding vector. use_additional_conditions (`bool`): To use additional conditions for normalization or not. """ def __init__(self, embedding_dim: int, time_step_rescale=1000): super().__init__() self.emb = TimestepEmbedder(embedding_dim) self.silu = nn.SiLU() self.linear = ReplicatedLinear(embedding_dim, 6 * embedding_dim, bias=True) self.time_step_rescale = time_step_rescale ## timestep usually in [0, 1], we rescale it to [0,1000] for stability def forward( self, timestep: torch.Tensor, added_cond_kwargs: dict[str, torch.Tensor] | None = None, ) -> tuple[torch.Tensor, torch.Tensor]: embedded_timestep = self.emb(timestep * self.time_step_rescale) out, _ = self.linear(self.silu(embedded_timestep)) return out, embedded_timestep class StepVideoTransformerBlock(nn.Module): r""" A basic Transformer block. Parameters: dim (`int`): The number of channels in the input and output. num_attention_heads (`int`): The number of heads to use for multi-head attention. attention_head_dim (`int`): The number of channels in each head. dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use. cross_attention_dim (`int`, *optional*): The size of the encoder_hidden_states vector for cross attention. activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward. num_embeds_ada_norm (: obj: `int`, *optional*): The number of diffusion steps used during training. See `Transformer2DModel`. attention_bias (: obj: `bool`, *optional*, defaults to `False`): Configure if the attentions should contain a bias parameter. only_cross_attention (`bool`, *optional*): Whether to use only cross-attention layers. In this case two cross attention layers are used. double_self_attention (`bool`, *optional*): Whether to use two self-attention layers. In this case no cross attention layers are used. upcast_attention (`bool`, *optional*): Whether to upcast the attention computation to float32. This is useful for mixed precision training. norm_elementwise_affine (`bool`, *optional*, defaults to `True`): Whether to use learnable elementwise affine parameters for normalization. norm_type (`str`, *optional*, defaults to `"layer_norm"`): The normalization layer to use. Can be `"layer_norm"`, `"ada_norm"` or `"ada_norm_zero"`. final_dropout (`bool` *optional*, defaults to False): Whether to apply a final dropout after the last feed-forward layer. positional_embeddings (`str`, *optional*, defaults to `None`): The type of positional embeddings to apply to. num_positional_embeddings (`int`, *optional*, defaults to `None`): The maximum number of positional embeddings to apply. """ def __init__(self, dim: int, attention_head_dim: int, norm_eps: float = 1e-5, ff_inner_dim: int | None = None, ff_bias: bool = False, attention_type: str = 'torch'): super().__init__() self.dim = dim self.norm1 = LayerNormScaleShift(dim, norm_type="layer", elementwise_affine=True, eps=norm_eps) self.attn1 = SelfAttention( dim, attention_head_dim, bias=False, with_rope=True, with_qk_norm=True, ) self.norm2 = LayerNormScaleShift(dim, norm_type="layer", elementwise_affine=True, eps=norm_eps) self.attn2 = CrossAttention(dim, attention_head_dim, bias=False, with_qk_norm=True) self.ff = MLP(input_dim=dim, mlp_hidden_dim=dim * 4 if ff_inner_dim is None else ff_inner_dim, act_type="gelu_pytorch_tanh", bias=ff_bias) self.scale_shift_table = nn.Parameter(torch.randn(6, dim) / dim**0.5) @torch.no_grad() def forward(self, q: torch.Tensor, kv: torch.Tensor, t_expand: torch.LongTensor, attn_mask=None, rope_positions: list | None = None, cos_sin=None, mask_strategy=None) -> torch.Tensor: shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = ( torch.clone(chunk) for chunk in (self.scale_shift_table[None] + t_expand.reshape(-1, 6, self.dim)).chunk(6, dim=1)) scale_shift_q = self.norm1(q, scale=scale_msa.squeeze(1), shift=shift_msa.squeeze(1)) attn_q = self.attn1(scale_shift_q, rope_positions=rope_positions, cos_sin=cos_sin, mask_strategy=mask_strategy) q = attn_q * gate_msa + q attn_q = self.attn2(q, kv, attn_mask) q = attn_q + q scale_shift_q = self.norm2(q, scale=scale_mlp.squeeze(1), shift=shift_mlp.squeeze(1)) ff_output = self.ff(scale_shift_q) q = ff_output * gate_mlp + q return q class StepVideoModel(BaseDiT): # (Optional) Keep the same attribute for compatibility with splitting, etc. _fsdp_shard_conditions = [ lambda n, m: "transformer_blocks" in n and n.split(".")[-1].isdigit(), # lambda n, m: "pos_embed" in n # If needed for the patch embedding. ] _param_names_mapping = StepVideoConfig()._param_names_mapping _supported_attention_backends = StepVideoConfig( )._supported_attention_backends def __init__(self, config: StepVideoConfig) -> None: super().__init__(config=config) self.num_attention_heads = config.num_attention_heads self.attention_head_dim = config.attention_head_dim self.in_channels = config.in_channels self.out_channels = config.out_channels self.num_layers = config.num_layers self.dropout = config.dropout self.patch_size = config.patch_size self.norm_type = config.norm_type self.norm_elementwise_affine = config.norm_elementwise_affine self.norm_eps = config.norm_eps self.use_additional_conditions = config.use_additional_conditions self.caption_channels = config.caption_channels self.attention_type = config.attention_type self.num_channels_latents = config.num_channels_latents # Compute inner dimension. self.hidden_size = config.hidden_size # Image/video patch embedding. self.pos_embed = PatchEmbed2D( patch_size=self.patch_size, in_chans=self.in_channels, embed_dim=self.hidden_size, ) self._rope_cache: dict[tuple, tuple[torch.Tensor, torch.Tensor]] = {} # Transformer blocks. self.transformer_blocks = nn.ModuleList([ StepVideoTransformerBlock( dim=self.hidden_size, attention_head_dim=self.attention_head_dim, attention_type=self.attention_type) for _ in range(self.num_layers) ]) # Output blocks. self.norm_out = LayerNormScaleShift( self.hidden_size, norm_type="layer", eps=self.norm_eps, elementwise_affine=self.norm_elementwise_affine) self.scale_shift_table = nn.Parameter( torch.randn(2, self.hidden_size) / (self.hidden_size**0.5)) self.proj_out = ReplicatedLinear( self.hidden_size, self.patch_size * self.patch_size * self.out_channels) # Time modulation via adaptive layer norm. self.adaln_single = AdaLayerNormSingle(self.hidden_size) # Set up caption conditioning. if isinstance(self.caption_channels, int): caption_channel = self.caption_channels else: caption_channel, clip_channel = self.caption_channels self.clip_projection = ReplicatedLinear(clip_channel, self.hidden_size) self.caption_norm = nn.LayerNorm( caption_channel, eps=self.norm_eps, elementwise_affine=self.norm_elementwise_affine) self.caption_projection = MLP(input_dim=caption_channel, mlp_hidden_dim=self.hidden_size, act_type="gelu_pytorch_tanh") # Flag to indicate if using parallel attention. self.parallel = (self.attention_type == "parallel") self.__post_init__() def patchfy(self, hidden_states) -> torch.Tensor: hidden_states = rearrange(hidden_states, 'b f c h w -> (b f) c h w') hidden_states = self.pos_embed(hidden_states) return hidden_states def prepare_attn_mask(self, encoder_attention_mask, encoder_hidden_states, q_seqlen) -> tuple[torch.Tensor, torch.Tensor]: kv_seqlens = encoder_attention_mask.sum(dim=1).int() mask = torch.zeros([len(kv_seqlens), q_seqlen, max(kv_seqlens)], dtype=torch.bool, device=encoder_attention_mask.device) encoder_hidden_states = encoder_hidden_states[:, :max(kv_seqlens)] for i, kv_len in enumerate(kv_seqlens): mask[i, :, :kv_len] = 1 return encoder_hidden_states, mask def block_forward(self, hidden_states, encoder_hidden_states=None, t_expand=None, rope_positions=None, cos_sin=None, attn_mask=None, parallel=True, mask_strategy=None) -> torch.Tensor: for i, block in enumerate(self.transformer_blocks): hidden_states = block(hidden_states, encoder_hidden_states, t_expand=t_expand, attn_mask=attn_mask, rope_positions=rope_positions, cos_sin=cos_sin, mask_strategy=mask_strategy[i]) return hidden_states def _get_rope(self, rope_positions: tuple[int, int, int], dtype: torch.dtype, device: torch.device): F, Ht, W = rope_positions key = (F, Ht, W, dtype) if key not in self._rope_cache: cos, sin = get_rotary_pos_embed( rope_sizes=(F * get_sequence_model_parallel_world_size(), Ht, W), hidden_size=self.hidden_size, heads_num=self.hidden_size // self.attention_head_dim, rope_dim_list=(64, 32, 32), # same split you used rope_theta=1.0e4, dtype=torch.float32 # build once in fp32 ) # move & cast once self._rope_cache[key] = (cos.to(device, dtype=dtype), sin.to(device, dtype=dtype)) return self._rope_cache[key] @torch.inference_mode() def forward( self, hidden_states: torch.Tensor, encoder_hidden_states: torch.Tensor | None = None, t_expand: torch.LongTensor | None = None, encoder_hidden_states_2: torch.Tensor | None = None, added_cond_kwargs: dict[str, torch.Tensor] | None = None, encoder_attention_mask: torch.Tensor | None = None, fps: torch.Tensor | None = None, return_dict: bool = True, mask_strategy=None, guidance=None, ): assert hidden_states.ndim == 5 "hidden_states's shape should be (bsz, f, ch, h ,w)" frame = hidden_states.shape[2] hidden_states = rearrange(hidden_states, 'b c f h w -> b f c h w', f=frame) if mask_strategy is None: mask_strategy = [None, None] bsz, frame, _, height, width = hidden_states.shape height, width = height // self.patch_size, width // self.patch_size hidden_states = self.patchfy(hidden_states) len_frame = hidden_states.shape[1] t_expand, embedded_timestep = self.adaln_single(t_expand) encoder_hidden_states = self.caption_projection( self.caption_norm(encoder_hidden_states)) if encoder_hidden_states_2 is not None and hasattr( self, 'clip_projection'): clip_embedding, _ = self.clip_projection(encoder_hidden_states_2) encoder_hidden_states = torch.cat( [clip_embedding, encoder_hidden_states], dim=1) hidden_states = rearrange(hidden_states, '(b f) l d-> b (f l) d', b=bsz, f=frame, l=len_frame).contiguous() encoder_hidden_states, attn_mask = self.prepare_attn_mask( encoder_attention_mask, encoder_hidden_states, q_seqlen=frame * len_frame) cos_sin = self._get_rope((frame, height, width), hidden_states.dtype, hidden_states.device) hidden_states = self.block_forward( hidden_states, encoder_hidden_states, t_expand=t_expand, rope_positions=[frame, height, width], cos_sin=cos_sin, attn_mask=attn_mask, parallel=self.parallel, mask_strategy=mask_strategy) hidden_states = rearrange(hidden_states, 'b (f l) d -> (b f) l d', b=bsz, f=frame, l=len_frame) embedded_timestep = repeat(embedded_timestep, 'b d -> (b f) d', f=frame).contiguous() shift, scale = (self.scale_shift_table[None] + embedded_timestep[:, None]).chunk(2, dim=1) hidden_states = self.norm_out(hidden_states, shift=shift.squeeze(1), scale=scale.squeeze(1)) # Modulation hidden_states, _ = self.proj_out(hidden_states) # unpatchify hidden_states = hidden_states.reshape(shape=(-1, height, width, self.patch_size, self.patch_size, self.out_channels)) hidden_states = rearrange(hidden_states, 'n h w p q c -> n c h p w q') output = hidden_states.reshape(shape=(-1, self.out_channels, height * self.patch_size, width * self.patch_size)) output = rearrange(output, '(b f) c h w -> b c f h w', f=frame) return output