# Licensed under the TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://github.com/Tencent-Hunyuan/HunyuanVideo-1.5/blob/main/LICENSE # # Unless and only to the extent required by applicable law, the Tencent Hunyuan works and any # output and results therefrom are provided "AS IS" without any express or implied warranties of # any kind including any warranties of title, merchantability, noninfringement, course of dealing, # usage of trade, or fitness for a particular purpose. You are solely responsible for determining the # appropriateness of using, reproducing, modifying, performing, displaying or distributing any of # the Tencent Hunyuan works or outputs and assume any and all risks associated with your or a # third party's use or distribution of any of the Tencent Hunyuan works or outputs and your exercise # of rights and permissions under this agreement. # See the License for the specific language governing permissions and limitations under the License. import einops import torch from typing import Optional import numpy as np import torch.nn.functional as F from unison.commons.parallel_states import get_parallel_state from unison.utils.communications import ( all_gather, all_to_all_4D, ) from unison.utils.flash_attn_no_pad import ( flash_attn_no_pad, flash_attn_no_pad_v3, ) from unison.commons import maybe_fallback_attn_mode flex_mask_cache = {} from unison.models.transformers.modules.ssta_attention import ssta_3d_attention from unison.commons.infer_state import get_infer_state @torch.compiler.disable def attention( q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, drop_rate: float = 0.0, attn_mask: Optional[torch.Tensor] = None, causal: bool = False, attn_mode: str = "flash", ) -> torch.Tensor: """ Compute attention using flash_attn_no_pad or torch scaled_dot_product_attention. Args: q: Query tensor of shape [B, L, H, D] k: Key tensor of shape [B, L, H, D] v: Value tensor of shape [B, L, H, D] drop_rate: Dropout rate for attention weights. attn_mask: Optional attention mask of shape [B, L]. causal: Whether to apply causal masking. attn_mode: Attention mode, either "flash" or "torch". Defaults to "flash". Returns: Output tensor after attention of shape [B, L, H*D] """ attn_mode = maybe_fallback_attn_mode(attn_mode) if attn_mode == "torch": # transpose q,k,v dim to fit scaled_dot_product_attention query = q.transpose(1, 2) # B * H * L * D key = k.transpose(1, 2) # B * H * L * D value = v.transpose(1, 2) # B * H * L * D if attn_mask is not None: if attn_mask.dtype != torch.bool and attn_mask.dtype in [torch.int64, torch.int32]: assert attn_mask.max() <= 1 and attn_mask.min() >= 0, f'Integer attention mask must be between 0 and 1 for torch attention.' attn_mask = attn_mask.to(torch.bool) elif attn_mask.dtype != torch.bool: attn_mask = attn_mask.to(query.dtype) raise NotImplementedError(f'Float attention mask is not implemented for torch attention.') attn_mask1 = einops.rearrange(attn_mask, 'b l -> b 1 l 1') attn_mask2 = einops.rearrange(attn_mask1, 'b 1 l 1 -> b 1 1 l') attn_mask = attn_mask1 & attn_mask2 x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=drop_rate, is_causal=causal) # transpose back x = x.transpose(1, 2) # B * L * H * D b, s, h, d = x.shape out = x.reshape(b, s, -1) return out else: # flash mode (default) qkv = torch.stack([q, k, v], dim=2) if attn_mask is not None and attn_mask.dtype != torch.bool: attn_mask = attn_mask.bool() x = flash_attn_no_pad(qkv, attn_mask, causal=causal, dropout_p=drop_rate, softmax_scale=None) b, s, a, d = x.shape out = x.reshape(b, s, -1) return out @torch.compiler.disable def parallel_attention(q, k, v, img_q_len, img_kv_len, attn_mode=None, text_mask=None, attn_param=None, block_idx=None, ): return sequence_parallel_attention(q, k, v, img_q_len, img_kv_len, attn_mode, text_mask, attn_param=attn_param, block_idx=block_idx) def sequence_parallel_attention(q, k, v, img_q_len, img_kv_len, attn_mode=None, text_mask=None, attn_param=None, block_idx=None, ): assert attn_mode is not None query, encoder_query = q key, encoder_key = k value, encoder_value = v parallel_dims = get_parallel_state() enable_sp = parallel_dims.sp_enabled if enable_sp: sp_group = parallel_dims.sp_group sp_size = parallel_dims.sp sp_rank = parallel_dims.sp_rank if enable_sp: # batch_size, seq_len, attn_heads, head_dim query = all_to_all_4D(query, sp_group, scatter_dim=2, gather_dim=1) key = all_to_all_4D(key, sp_group, scatter_dim=2, gather_dim=1) value = all_to_all_4D(value, sp_group, scatter_dim=2, gather_dim=1) def shrink_head(encoder_state, dim): local_heads = encoder_state.shape[dim] // sp_size return encoder_state.narrow( dim, sp_rank * local_heads, local_heads ) encoder_query = shrink_head(encoder_query, dim=2) encoder_key = shrink_head(encoder_key, dim=2) encoder_value = shrink_head(encoder_value, dim=2) sequence_length = query.size(1) encoder_sequence_length = encoder_query.size(1) attn_mode = maybe_fallback_attn_mode(attn_mode, get_infer_state(), block_idx) if attn_mode == "sageattn": from sageattention import sageattn query = torch.cat([query, encoder_query], dim=1) key = torch.cat([key, encoder_key], dim=1) value = torch.cat([value, encoder_value], dim=1) hidden_states = sageattn(query, key, value, tensor_layout="NHD", is_causal=False) elif attn_mode == "torch": query = torch.cat([query, encoder_query], dim=1) key = torch.cat([key, encoder_key], dim=1) value = torch.cat([value, encoder_value], dim=1) if text_mask is not None: attn_mask = F.pad(text_mask, (sequence_length, 0), value=True) else: attn_mask = None if attn_mask is not None: if attn_mask.dtype != torch.bool and attn_mask.dtype in [torch.int64, torch.int32]: assert attn_mask.max() <= 1 and attn_mask.min() >= 0, f'Integer attention mask must be between 0 and 1 for torch attention.' attn_mask = attn_mask.to(torch.bool) elif attn_mask.dtype != torch.bool: attn_mask = attn_mask.to(query.dtype) raise NotImplementedError(f'Float attention mask is not implemented for torch attention.') # [B, L] -> [B, 1, L, L]: query token q and key token k must both be # unmasked. attn_mask1 = einops.rearrange(attn_mask, 'b l -> b 1 l 1') attn_mask2 = einops.rearrange(attn_mask1, 'b 1 l 1 -> b 1 1 l') attn_mask = attn_mask1 & attn_mask2 # transpose q,k,v dim to fit scaled_dot_product_attention query = query.transpose(1, 2) # B * Head_num * length * dim key = key.transpose(1, 2) # B * Head_num * length * dim value = value.transpose(1, 2) # B * Head_num * length * dim # cut: flex_attention score_mod path, replaced by plain SDPA below — # flex_attention always traces internally, which crashed on this torch build. # def score_mod(score, b, h, q_idx, kv_idx): # return torch.where(attn_mask[b, q_idx] & attn_mask[b, kv_idx], score, float('-inf')) # hidden_states = flex_attention(query, key, value, score_mod=score_mod) hidden_states = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask) # transpose back hidden_states = hidden_states.transpose(1, 2) elif attn_mode == "flash2": query = torch.cat([query, encoder_query], dim=1) key = torch.cat([key, encoder_key], dim=1) value = torch.cat([value, encoder_value], dim=1) # B, S, 3, H, D qkv = torch.stack([query, key, value], dim=2) attn_mask = F.pad(text_mask, (sequence_length, 0), value=True) hidden_states = flash_attn_no_pad(qkv, attn_mask, causal=False, dropout_p=0.0, softmax_scale=None) elif attn_mode == "flash3": query = torch.cat([query, encoder_query], dim=1) key = torch.cat([key, encoder_key], dim=1) value = torch.cat([value, encoder_value], dim=1) # B, S, 3, H, D qkv = torch.stack([query, key, value], dim=2) attn_mask = F.pad(text_mask, (sequence_length, 0), value=True) hidden_states = flash_attn_no_pad_v3(qkv, attn_mask, causal=False, dropout_p=0.0, softmax_scale=None) elif attn_mode == "flex-block-attn": sparse_type = attn_param["attn_sparse_type"] # sta/block_attn/ssta ssta_threshold = attn_param["ssta_threshold"] ssta_lambda = attn_param["ssta_lambda"] ssta_sampling_type = attn_param["ssta_sampling_type"] ssta_adaptive_pool = attn_param["ssta_adaptive_pool"] attn_pad_type = attn_param["attn_pad_type"] # repeat/zero attn_use_text_mask = attn_param["attn_use_text_mask"] attn_mask_share_within_head = attn_param["attn_mask_share_within_head"] ssta_topk = attn_param["ssta_topk"] thw = attn_param["thw"] tile_size = attn_param["tile_size"] win_size = attn_param["win_size"][0].copy() def get_image_tile(tile_size): block_size = np.prod(tile_size) if block_size == 384: tile_size = (1, 16, 24) elif block_size == 128: tile_size = (1, 16, 8) elif block_size == 64: tile_size = (1, 8, 8) elif block_size == 16: tile_size = (1, 4, 4) else: raise ValueError(f"Error tile_size {tile_size}, only support in [16, 64, 128, 384]") return tile_size if thw[0] == 1: tile_size = get_image_tile(tile_size) win_size = [1, 1, 1] elif thw[0] <= 31: # 16fps: 5 * 16 / 4 + 1 = 21; 24fps: 5 * 24 / 4 + 1 = 31 ssta_topk = ssta_topk // 2 # Concatenate and permute query, key, value to (B, H, S, D) query = torch.cat([query, encoder_query], dim=1).permute(0, 2, 1, 3) key = torch.cat([key, encoder_key], dim=1).permute(0, 2, 1, 3) value = torch.cat([value, encoder_value], dim=1).permute(0, 2, 1, 3) assert ( query.shape[-1] == 128 ), "The last dimension of query, key and value must be 128 for flex-block-attn." hidden_states = ssta_3d_attention( query, key, value, thw, topk=ssta_topk, tile_thw=tile_size, kernel_thw=win_size, text_len=encoder_sequence_length, sparse_type=sparse_type, threshold=ssta_threshold, lambda_=ssta_lambda, pad_type=attn_pad_type, text_mask=text_mask if attn_use_text_mask else None, sampling_type=ssta_sampling_type, adaptive_pool=ssta_adaptive_pool, mask_share_within_head=attn_mask_share_within_head, ) hidden_states, sparse_ratio = hidden_states hidden_states = hidden_states.permute(0, 2, 1, 3) else: raise NotImplementedError( f'Unsupported attention mode: {attn_mode}. Only torch, flash, flash3, sageattn and flex-block-attn are supported.' ) if enable_sp: hidden_states, encoder_hidden_states = hidden_states.split_with_sizes((sequence_length, encoder_sequence_length), dim=1) hidden_states = all_to_all_4D(hidden_states, sp_group, scatter_dim=1, gather_dim=2) encoder_hidden_states = all_gather(encoder_hidden_states, dim=2, group=sp_group).contiguous() hidden_states = hidden_states.to(query.dtype) encoder_hidden_states = encoder_hidden_states.to(query.dtype) hidden_states = torch.cat([hidden_states, encoder_hidden_states], dim=1) b, s, a, d = hidden_states.shape hidden_states = hidden_states.reshape(b, s, -1) return hidden_states