Coverage for wrapper/hunyuanframepack/hunyuanframepack_xfuser.py: 21%
173 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 04:47 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 04:47 +0000
1# mypy: ignore-errors
2"""
3xFuser FramePack for Hunyuan Video.
4Extends the Hunyuan Video Framepack Pipeline to support xFuser's long context attention and sequence parallelism.
5Sources:
6* https://github.com/lllyasviel/FramePack
7* https://github.com/xdit-project/xDiT
8"""
9import torch
10import functools
11import einops
13from typing import Tuple
14from typing import Any
15from typing import Optional
16from typing import Dict
18from diffusers import HunyuanVideoFramepackPipeline
20from diffusers.models.attention import Attention
21from diffusers.models.transformers.transformer_hunyuan_video import HunyuanVideoAttnProcessor2_0
22from diffusers.models.transformers.transformer_2d import Transformer2DModelOutput
24from flash_attn import flash_attn_varlen_func
26from xfuser.envs import PACKAGES_CHECKER
27from xfuser.core.cache_manager.cache_manager import get_cache_manager
28from xfuser.core.distributed import get_sp_group
29from xfuser.core.distributed import get_sequence_parallel_world_size
30from xfuser.core.distributed import get_sequence_parallel_rank
31from xfuser.core.distributed import get_classifier_free_guidance_world_size
32from xfuser.core.distributed import get_classifier_free_guidance_rank
33from xfuser.core.distributed import get_runtime_state
34from xfuser.core.long_ctx_attention import xFuserLongContextAttention
35from xfuser.model_executor.layers.attention_processor import xFuserAttentionProcessorRegister
38# Class for the attention extending:
39# https://github.com/xdit-project/xDiT/blob/main/xfuser/model_executor/layers/attention_processor.py
40env_info = PACKAGES_CHECKER.get_packages_info()
41HAS_LONG_CTX_ATTN = env_info["has_long_ctx_attn"]
42HAS_FLASH_ATTN = env_info["has_flash_attn"]
45@xFuserAttentionProcessorRegister.register(HunyuanVideoAttnProcessor2_0)
46class xFuserFramepackSingleHunyuanVideoAttnProcessor2_0(HunyuanVideoAttnProcessor2_0):
47 def __init__(self) -> None:
48 super().__init__()
50 assert get_sequence_parallel_world_size() > 1
51 assert HAS_LONG_CTX_ATTN is True
52 assert HAS_FLASH_ATTN is True
54 self.hybrid_seq_parallel_attn = xFuserLongContextAttention(use_kv_cache=True)
56 # HunyuanAttnProcessorFlashAttnSingle
57 def __call__(
58 self,
59 attn: HunyuanVideoAttnProcessor2_0,
60 hidden_states: torch.Tensor,
61 encoder_hidden_states: torch.Tensor,
62 attention_mask: torch.Tensor,
63 image_rotary_emb: torch.Tensor
64 ) -> Tuple[torch.Tensor, torch.Tensor]:
65 cu_seqlens_q, cu_seqlens_kv, max_seqlen_q, max_seqlen_kv = attention_mask
66 hidden_states = torch.cat([hidden_states, encoder_hidden_states], dim=1)
68 query = attn.to_q(hidden_states)
69 key = attn.to_k(hidden_states)
70 value = attn.to_v(hidden_states)
72 query = query.unflatten(2, (attn.heads, -1))
73 key = key.unflatten(2, (attn.heads, -1))
74 value = value.unflatten(2, (attn.heads, -1))
76 query = attn.norm_q(query)
77 key = attn.norm_k(key)
79 txt_length = encoder_hidden_states.shape[1]
80 query = torch.cat([apply_rotary_emb_transposed(query[:, :-txt_length],
81 image_rotary_emb), query[:, -txt_length:]], dim=1)
82 key = torch.cat([apply_rotary_emb_transposed(key[:, :-txt_length],
83 image_rotary_emb), key[:, -txt_length:]], dim=1)
84 hidden_states = attn_varlen_func(query, key, value, cu_seqlens_q, cu_seqlens_kv, max_seqlen_q, max_seqlen_kv,
85 self.hybrid_seq_parallel_attn, attn)
86 hidden_states = hidden_states.flatten(-2)
87 hidden_states, encoder_hidden_states = hidden_states[:, :-txt_length], hidden_states[:, -txt_length:]
88 return hidden_states, encoder_hidden_states
91def attn_varlen_func(
92 q: torch.Tensor,
93 k: torch.Tensor,
94 v: torch.Tensor,
95 cu_seqlens_q: torch.Tensor,
96 cu_seqlens_kv: torch.Tensor,
97 max_seqlen_q: int,
98 max_seqlen_kv: int,
99 hybrid_seq_parallel_attn: Any = None,
100 attn: Any = None
101) -> torch.Tensor:
102 if cu_seqlens_q is None and cu_seqlens_kv is None and max_seqlen_q is None and max_seqlen_kv is None:
103 # Needed for XDiT
104 x = hybrid_seq_parallel_attn(attn, q, k, v, dropout_p=0.0, causal=False, joint_strategy="none")
105 # Original
106 # x = flash_attn_func(q, k, v)
107 return x
109 B, L, H, C = q.shape
111 q = q.flatten(0, 1)
112 k = k.flatten(0, 1)
113 v = v.flatten(0, 1)
115 x = flash_attn_varlen_func(q, k, v, cu_seqlens_q, cu_seqlens_kv, max_seqlen_q, max_seqlen_kv)
117 x = x.unflatten(0, (B, L))
119 return x
122def apply_rotary_emb_transposed(
123 x: torch.Tensor,
124 freqs_cis: torch.Tensor
125) -> torch.Tensor:
126 # https://github.com/lllyasviel/FramePack/blob/c5d375661a2557383f0b8da9d11d14c23b0c4eaf/diffusers_helper/models/hunyuan_video_packed.py#L190
127 cos, sin = freqs_cis.unsqueeze(-2).chunk(2, dim=-1)
128 x_real, x_imag = x.unflatten(-1, (-1, 2)).unbind(-1)
129 x_rotated = torch.stack([-x_imag, x_real], dim=-1).flatten(3)
130 out = x.float() * cos + x_rotated.float() * sin
131 out = out.to(x)
132 return out
135@xFuserAttentionProcessorRegister.register(HunyuanVideoAttnProcessor2_0)
136class xFuserFramepackDoubleHunyuanVideoAttnProcessor2_0(HunyuanVideoAttnProcessor2_0):
137 def __init__(self) -> None:
138 super().__init__()
140 assert get_sequence_parallel_world_size() > 1
141 assert HAS_LONG_CTX_ATTN is True
142 assert HAS_FLASH_ATTN is True
144 self.hybrid_seq_parallel_attn = xFuserLongContextAttention(use_kv_cache=True)
146 # HunyuanAttnProcessorFlashAttnDouble
147 def __call__(
148 self,
149 attn: HunyuanVideoAttnProcessor2_0,
150 hidden_states: torch.Tensor,
151 encoder_hidden_states: torch.Tensor,
152 attention_mask: torch.Tensor,
153 image_rotary_emb: torch.Tensor
154 ) -> Tuple[torch.Tensor, torch.Tensor]:
155 cu_seqlens_q, cu_seqlens_kv, max_seqlen_q, max_seqlen_kv = attention_mask
156 query = attn.to_q(hidden_states)
157 key = attn.to_k(hidden_states)
158 value = attn.to_v(hidden_states)
160 query = query.unflatten(2, (attn.heads, -1))
161 key = key.unflatten(2, (attn.heads, -1))
162 value = value.unflatten(2, (attn.heads, -1))
164 query = attn.norm_q(query)
165 key = attn.norm_k(key)
167 query = apply_rotary_emb_transposed(query, image_rotary_emb)
168 key = apply_rotary_emb_transposed(key, image_rotary_emb)
170 encoder_query = attn.add_q_proj(encoder_hidden_states)
171 encoder_key = attn.add_k_proj(encoder_hidden_states)
172 encoder_value = attn.add_v_proj(encoder_hidden_states)
174 encoder_query = encoder_query.unflatten(2, (attn.heads, -1))
175 encoder_key = encoder_key.unflatten(2, (attn.heads, -1))
176 encoder_value = encoder_value.unflatten(2, (attn.heads, -1))
178 encoder_query = attn.norm_added_q(encoder_query)
179 encoder_key = attn.norm_added_k(encoder_key)
181 query = torch.cat([query, encoder_query], dim=1)
182 key = torch.cat([key, encoder_key], dim=1)
183 value = torch.cat([value, encoder_value], dim=1)
184 hidden_states = attn_varlen_func(query, key, value, cu_seqlens_q, cu_seqlens_kv, max_seqlen_q, max_seqlen_kv,
185 self.hybrid_seq_parallel_attn, attn)
186 hidden_states = hidden_states.flatten(-2)
187 txt_length = encoder_hidden_states.shape[1]
188 hidden_states, encoder_hidden_states = hidden_states[:, :-txt_length], hidden_states[:, -txt_length:]
189 hidden_states = attn.to_out[0](hidden_states)
190 hidden_states = attn.to_out[1](hidden_states)
191 encoder_hidden_states = attn.to_add_out(encoder_hidden_states)
192 return hidden_states, encoder_hidden_states
195def parallelize_transformer(pipe_hunyuan: HunyuanVideoFramepackPipeline) -> HunyuanVideoFramepackPipeline:
196 transformer = pipe_hunyuan.transformer
198 """
199 Parallelize the transformer.
200 """
201 @functools.wraps(transformer.__class__.forward)
202 def new_forward(
203 self: Any,
204 hidden_states: torch.Tensor, # shape: [1, 16, 9, 80, 76]
205 timestep: Any,
206 encoder_hidden_states: torch.Tensor, # shape: [1, 512, 4096]
207 encoder_attention_mask: torch.Tensor, # shape: [1, 512]
208 pooled_projections: Any,
209 guidance: Any,
210 latent_indices: Any = None,
211 clean_latents: Optional[torch.Tensor] = None, # shape: [1, 16, 2, 80, 76]
212 clean_latent_indices: Optional[torch.Tensor] = None, # shape: [1, 2]
213 clean_latents_2x: Optional[torch.Tensor] = None, # shape: [1, 16, 2, 80, 76]
214 clean_latent_2x_indices: Optional[torch.Tensor] = None, # shape: [1, 2]
215 clean_latents_4x: Optional[torch.Tensor] = None, # shape: [1, 16, 16, 80, 76]
216 clean_latent_4x_indices: Optional[torch.Tensor] = None, # shape: [1, 16]
217 image_embeddings: Optional[torch.Tensor] = None,
218 attention_kwargs: Optional[Dict] = None,
219 return_dict: bool = True
220 ) -> Any:
221 """
222 Started from https://github.com/lllyasviel/FramePack/blob/main/diffusers_helper/models/hunyuan_video_packed.py
223 Aligned with https://github.com/xdit-project/xDiT/blob/main/examples/hunyuan_video_usp_example.py
224 """
225 if attention_kwargs is None:
226 attention_kwargs = {}
228 batch_size, num_channels, num_frames, height, width = hidden_states.shape
230 p, p_t = self.config['patch_size'], self.config['patch_size_t'] # 2, 1
231 post_patch_num_frames = num_frames // p_t
232 post_patch_height = height // p
233 post_patch_width = width // p
235 original_context_length = post_patch_num_frames * post_patch_height * post_patch_width
237 # 1. FramePack + RoPE
238 # image_rotary_emb ~= rope_freqs
239 # hidden_states: [1, 16, 9, 80, 76] -> [1, 17500, 3072] = [1, 7*5*5*5*5*4, 3*2*512]
240 hidden_states, rope_freqs = self.process_input_hidden_states(
241 hidden_states, latent_indices,
242 clean_latents, clean_latent_indices,
243 clean_latents_2x, clean_latent_2x_indices,
244 clean_latents_4x, clean_latent_4x_indices
245 )
247 # 2. Conditional embeddings
248 temb = self.time_text_embed(timestep, guidance, pooled_projections)
249 # [1, 512, 4096] -> [1, 512, 3072]
250 encoder_hidden_states = self.context_embedder(encoder_hidden_states, timestep, encoder_attention_mask)
252 extra_encoder_hidden_states = self.image_projection(image_embeddings)
253 extra_attention_mask = torch.ones(
254 (batch_size, extra_encoder_hidden_states.shape[1]),
255 dtype=encoder_attention_mask.dtype,
256 device=encoder_attention_mask.device
257 )
258 encoder_hidden_states = torch.cat([extra_encoder_hidden_states, encoder_hidden_states], dim=1)
259 encoder_attention_mask = torch.cat([extra_attention_mask, encoder_attention_mask], dim=1)
261 text_len = encoder_attention_mask.sum().item()
262 # 1, 1241, 3072 -> 1, text_len(742), 3072
263 encoder_hidden_states = encoder_hidden_states[:, :text_len]
264 attention_mask = None, None, None, None
266 # Sequence-parallel chunking
267 sp_world = get_sequence_parallel_world_size()
268 sp_rank = get_sequence_parallel_rank()
269 cfg_world = get_classifier_free_guidance_world_size()
270 cfg_rank = get_classifier_free_guidance_rank()
272 # Chunk hidden_states and rope_freqs identically along the sequence dimension
273 # [1, 17226, 3072] -> [1, 17226 / sp_world, 3072]
274 # [1, 17500, 3072] -> [1, 17500 / sp_world, 3072]
275 if hidden_states.shape[-2] % sp_world != 0:
276 # This will cause a noisy output after chunking
277 raise RuntimeError(f"hidden_states {hidden_states.shape} is not divisible by sp_world {sp_world}.")
278 hidden_states = torch.chunk(hidden_states, sp_world, dim=-2)[sp_rank]
280 # [1, 17226, 256] -> [1, 17226 / sp_world, 256]
281 if rope_freqs.shape[-2] % sp_world != 0:
282 # This will cause a noisy output after chunking
283 raise RuntimeError(f"rope_freqs {rope_freqs.shape} is not divisible by sp_world {sp_world}.")
284 rope_freqs = torch.chunk(rope_freqs, sp_world, dim=-2)[sp_rank]
286 # Chunk encoder_hidden_states [1, 742, 3072] -> [1, 742 / sp_world, 3072]
287 if encoder_hidden_states.shape[-2] % sp_world != 0:
288 get_runtime_state().split_text_embed_in_sp = False
289 else:
290 get_runtime_state().split_text_embed_in_sp = True
292 encoder_hidden_states = torch.chunk(
293 encoder_hidden_states,
294 cfg_world,
295 dim=0)[cfg_rank]
297 if get_runtime_state().split_text_embed_in_sp:
298 encoder_hidden_states = torch.chunk(
299 encoder_hidden_states,
300 sp_world,
301 dim=-2)[sp_rank]
303 # 3. Transformer blocks
304 # https://github.com/lllyasviel/FramePack/blob/main/diffusers_helper/models/hunyuan_video_packed.py#HunyuanVideoTransformerBlock
305 for block in self.transformer_blocks + self.single_transformer_blocks:
306 # HunyuanVideoSingleTransformerBlock
307 hidden_states, encoder_hidden_states = block(
308 hidden_states,
309 encoder_hidden_states,
310 temb,
311 attention_mask,
312 rope_freqs
313 )
315 # Output projection
316 # This is torch.Size([1, 8613, 3072])
317 hidden_states = self.norm_out(hidden_states, temb)
318 # Ensure dtype matches proj_out weights
319 hidden_states = hidden_states.to(self.proj_out.weight.dtype)
320 hidden_states = self.proj_out(hidden_states)
321 # shape: ([1, 8613, 64])
322 # Gather and reshape like xDiT
323 hidden_states = get_sp_group().all_gather(hidden_states, dim=-2)
324 hidden_states = hidden_states[:, -original_context_length:, :]
325 hidden_states = einops.rearrange(hidden_states, 'b (t h w) (c pt ph pw) -> b c (t pt) (h ph) (w pw)',
326 t=post_patch_num_frames, h=post_patch_height, w=post_patch_width,
327 pt=p_t, ph=p, pw=p)
328 # Rearranged hidden_states [1, 16, 9, 80, 76]
330 if return_dict:
331 return Transformer2DModelOutput(sample=hidden_states)
333 return hidden_states,
335 new_forward = new_forward.__get__(transformer) # type: ignore[attr-defined]
336 transformer.forward = new_forward
338 # Apply the xFuser attention processor to all transformer blocks
339 for block in transformer.transformer_blocks:
340 block.attn.processor = xFuserFramepackDoubleHunyuanVideoAttnProcessor2_0()
341 for block in transformer.single_transformer_blocks:
342 block.attn.processor = xFuserFramepackSingleHunyuanVideoAttnProcessor2_0()
344 # Register the attention layers to the cache manager
345 # This is needed for sequence parallelism to work correctly and allow each chunk to attend to the entire sequence
346 for block in transformer.transformer_blocks:
347 for submodule in block.modules():
348 if isinstance(submodule, Attention):
349 get_cache_manager().register_cache_entry(submodule, "attn", "sequence_parallel_attn_cache")
350 for block in transformer.single_transformer_blocks:
351 for submodule in block.modules():
352 if isinstance(submodule, Attention):
353 get_cache_manager().register_cache_entry(submodule, "attn", "sequence_parallel_attn_cache")
355 return pipe_hunyuan