Skip to content

Commit ee54a19

Browse files
committed
update
1 parent ea7e3cd commit ee54a19

3 files changed

Lines changed: 83 additions & 33 deletions

File tree

docs/source/en/api/pipelines/hunyuan_video.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ The following models are available for the image-to-video pipeline:
5252
| [`Skywork/SkyReels-V1-Hunyuan-I2V`](https://huggingface.co/Skywork/SkyReels-V1-Hunyuan-I2V) | Skywork's custom finetune of HunyuanVideo (de-distilled). Performs best with `97x544x960` resolution. Performs best at `97x544x960` resolution, `guidance_scale=1.0`, `true_cfg_scale=6.0` and a negative prompt. |
5353
| [`hunyuanvideo-community/HunyuanVideo-I2V-33ch`](https://huggingface.co/hunyuanvideo-community/HunyuanVideo-I2V) | Tecent's official HunyuanVideo 33-channel I2V model. Performs best at resolutions of 480, 720, 960, 1280. A higher `shift` value when initializing the scheduler is recommended (good values are between 7 and 20). |
5454
| [`hunyuanvideo-community/HunyuanVideo-I2V`](https://huggingface.co/hunyuanvideo-community/HunyuanVideo-I2V) | Tecent's official HunyuanVideo 16-channel I2V model. Performs best at resolutions of 480, 720, 960, 1280. A higher `shift` value when initializing the scheduler is recommended (good values are between 7 and 20) |
55+
- [`lllyasviel/FramePackI2V_HY`](https://huggingface.co/lllyasviel/FramePackI2V_HY) | lllyasviel's paper introducing a new technique for long-context video generation called [Framepack](https://arxiv.org/abs/2504.12626). |
5556

5657
## Quantization
5758

src/diffusers/models/transformers/transformer_hunyuan_video_framepack.py

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@
2222
from ...loaders import FromOriginalModelMixin, PeftAdapterMixin
2323
from ...utils import USE_PEFT_BACKEND, get_logger, scale_lora_layers, unscale_lora_layers
2424
from ..cache_utils import CacheMixin
25-
from ..embeddings import CombinedTimestepGuidanceTextProjEmbeddings, get_1d_rotary_pos_embed
25+
from ..embeddings import get_1d_rotary_pos_embed
2626
from ..modeling_outputs import Transformer2DModelOutput
2727
from ..modeling_utils import ModelMixin
2828
from ..normalization import AdaLayerNormContinuous
2929
from .transformer_hunyuan_video import (
30+
HunyuanVideoConditionEmbedding,
3031
HunyuanVideoPatchEmbed,
3132
HunyuanVideoSingleTransformerBlock,
3233
HunyuanVideoTokenRefiner,
@@ -48,11 +49,16 @@ def __init__(self, patch_size: int, patch_size_t: int, rope_dim: List[int], thet
4849

4950
def forward(self, frame_indices: torch.Tensor, height: int, width: int, device: torch.device):
5051
frame_indices = frame_indices.unbind(0)
51-
freqs = [self._forward(f, height, width, device) for f in frame_indices]
52-
freqs_cos, freqs_sin = zip(*freqs)
53-
freqs_cos = torch.stack(freqs_cos, dim=0) # [B, W * H * T, D / 2]
54-
freqs_sin = torch.stack(freqs_sin, dim=0) # [B, W * H * T, D / 2]
55-
return freqs_cos, freqs_sin
52+
# This is from the original code. We don't call _forward for each batch index because we know that
53+
# each batch has the same frame indices. However, it may be possible that the frame indices don't
54+
# always be the same for every item in a batch (such as in training). We cannot use the original
55+
# implementation because our `apply_rotary_emb` function broadcasts across the batch dim.
56+
# freqs = [self._forward(f, height, width, device) for f in frame_indices]
57+
# freqs_cos, freqs_sin = zip(*freqs)
58+
# freqs_cos = torch.stack(freqs_cos, dim=0) # [B, W * H * T, D / 2]
59+
# freqs_sin = torch.stack(freqs_sin, dim=0) # [B, W * H * T, D / 2]
60+
# return freqs_cos, freqs_sin
61+
return self._forward(frame_indices[0], height, width, device)
5662

5763
def _forward(self, frame_indices, height, width, device):
5864
height = height // self.patch_size
@@ -89,7 +95,7 @@ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
8995
return hidden_states
9096

9197

92-
class HunyuanVideoPatchEmbedForCleanLatents(nn.Module):
98+
class HunyuanVideoHistoryPatchEmbed(nn.Module):
9399
def __init__(self, inner_dim: int):
94100
super().__init__()
95101
self.proj = nn.Conv3d(16, inner_dim, kernel_size=(1, 2, 2), stride=(1, 2, 2))
@@ -147,6 +153,7 @@ def __init__(
147153
pooled_projection_dim: int = 768,
148154
rope_theta: float = 256.0,
149155
rope_axes_dim: Tuple[int] = (16, 56, 56),
156+
image_condition_type: Optional[str] = None,
150157
has_image_proj: int = False,
151158
image_proj_dim: int = 1152,
152159
has_clean_x_embedder: int = False,
@@ -161,7 +168,9 @@ def __init__(
161168
self.context_embedder = HunyuanVideoTokenRefiner(
162169
text_embed_dim, num_attention_heads, attention_head_dim, num_layers=num_refiner_layers
163170
)
164-
self.time_text_embed = CombinedTimestepGuidanceTextProjEmbeddings(inner_dim, pooled_projection_dim)
171+
self.time_text_embed = HunyuanVideoConditionEmbedding(
172+
inner_dim, pooled_projection_dim, guidance_embeds, image_condition_type
173+
)
165174

166175
# 2. RoPE
167176
self.rope = HunyuanVideoFramepackRotaryPosEmbed(patch_size, patch_size_t, rope_axes_dim, rope_theta)
@@ -195,7 +204,7 @@ def __init__(
195204

196205
self.clean_x_embedder = None
197206
if has_clean_x_embedder:
198-
self.clean_x_embedder = HunyuanVideoPatchEmbedForCleanLatents(inner_dim)
207+
self.clean_x_embedder = HunyuanVideoHistoryPatchEmbed(inner_dim)
199208

200209
self.use_gradient_checkpointing = False
201210

@@ -238,19 +247,20 @@ def forward(
238247
post_patch_num_frames = num_frames // p_t
239248
post_patch_height = height // p
240249
post_patch_width = width // p
250+
original_context_length = post_patch_num_frames * post_patch_height * post_patch_width
241251

242252
hidden_states, image_rotary_emb = self._pack_history_states(
243253
hidden_states,
244254
indices_latents,
245255
latents_clean,
246256
latents_history_2x,
247-
indices_latents_history_2x,
248-
indices_latents_clean,
249257
latents_history_4x,
258+
indices_latents_clean,
259+
indices_latents_history_2x,
250260
indices_latents_history_4x,
251261
)
252262

253-
temb, token_replace_emb = self.time_text_embed(timestep, pooled_projections, guidance)
263+
temb, _ = self.time_text_embed(timestep, pooled_projections, guidance)
254264
encoder_hidden_states = self.context_embedder(encoder_hidden_states, timestep, encoder_attention_mask)
255265

256266
encoder_hidden_states_image = self.image_projection(image_embeds)
@@ -298,6 +308,7 @@ def forward(
298308
)
299309

300310
# 5. Output projection
311+
hidden_states = hidden_states[:, -original_context_length:]
301312
hidden_states = self.norm_out(hidden_states, temb)
302313
hidden_states = self.proj_out(hidden_states)
303314

@@ -331,44 +342,75 @@ def _pack_history_states(
331342
indices_latents = torch.arange(0, num_frames).unsqueeze(0).expand(batch_size, -1)
332343

333344
hidden_states = self.x_embedder(hidden_states)
334-
hidden_states = hidden_states.flatten(2).transpose(1, 2)
335345
image_rotary_emb = self.rope(
336346
frame_indices=indices_latents, height=height, width=width, device=hidden_states.device
337347
)
348+
image_rotary_emb = list(image_rotary_emb) # convert tuple to list for in-place modification
349+
pph, ppw = height // self.config.patch_size, width // self.config.patch_size
338350

339351
latents_clean, latents_history_2x, latents_history_4x = self.clean_x_embedder(
340352
latents_clean, latents_history_2x, latents_history_4x
341353
)
342354

343355
if latents_clean is not None:
356+
hidden_states = torch.cat([latents_clean, hidden_states], dim=1)
357+
344358
image_rotary_emb_clean = self.rope(
345359
frame_indices=indices_latents_clean, height=height, width=width, device=latents_clean.device
346360
)
347-
hidden_states = torch.cat([latents_clean, hidden_states], dim=1)
348-
image_rotary_emb = torch.cat([image_rotary_emb_clean, image_rotary_emb], dim=1)
361+
image_rotary_emb[0] = torch.cat([image_rotary_emb_clean[0], image_rotary_emb[0]], dim=0)
362+
image_rotary_emb[1] = torch.cat([image_rotary_emb_clean[1], image_rotary_emb[1]], dim=0)
349363

350364
if latents_history_2x is not None and indices_latents_history_2x is not None:
365+
hidden_states = torch.cat([latents_history_2x, hidden_states], dim=1)
366+
351367
image_rotary_emb_history_2x = self.rope(
352368
frame_indices=indices_latents_history_2x, height=height, width=width, device=latents_history_2x.device
353369
)
354-
image_rotary_emb_history_2x = _pad_for_3d_conv(image_rotary_emb_history_2x, (2, 2, 2))
355-
image_rotary_emb_history_2x = _center_down_sample_3d(image_rotary_emb_history_2x, (2, 2, 2))
356-
hidden_states = torch.cat([latents_history_2x, hidden_states], dim=1)
357-
image_rotary_emb = torch.cat([image_rotary_emb_history_2x, image_rotary_emb], dim=1)
370+
image_rotary_emb_history_2x = self._pad_rotary_emb(
371+
image_rotary_emb_history_2x, indices_latents_history_2x.size(1), pph, ppw, (2, 2, 2)
372+
)
373+
image_rotary_emb[0] = torch.cat([image_rotary_emb_history_2x[0], image_rotary_emb[0]], dim=0)
374+
image_rotary_emb[1] = torch.cat([image_rotary_emb_history_2x[1], image_rotary_emb[1]], dim=0)
358375

359376
if latents_history_4x is not None and indices_latents_history_4x is not None:
377+
hidden_states = torch.cat([latents_history_4x, hidden_states], dim=1)
378+
360379
image_rotary_emb_history_4x = self.rope(
361380
frame_indices=indices_latents_history_4x, height=height, width=width, device=latents_history_4x.device
362381
)
363-
image_rotary_emb_history_4x = _pad_for_3d_conv(image_rotary_emb_history_4x, (4, 4, 4))
364-
image_rotary_emb_history_4x = _center_down_sample_3d(image_rotary_emb_history_4x, (4, 4, 4))
365-
hidden_states = torch.cat([latents_history_4x, hidden_states], dim=1)
366-
image_rotary_emb = torch.cat([image_rotary_emb_history_4x, image_rotary_emb], dim=1)
382+
image_rotary_emb_history_4x = self._pad_rotary_emb(
383+
image_rotary_emb_history_4x, indices_latents_history_4x.size(1), pph, ppw, (4, 4, 4)
384+
)
385+
image_rotary_emb[0] = torch.cat([image_rotary_emb_history_4x[0], image_rotary_emb[0]], dim=0)
386+
image_rotary_emb[1] = torch.cat([image_rotary_emb_history_4x[1], image_rotary_emb[1]], dim=0)
367387

368388
return hidden_states, image_rotary_emb
369389

390+
def _pad_rotary_emb(
391+
self,
392+
image_rotary_emb: Tuple[torch.Tensor],
393+
num_frames: int,
394+
height: int,
395+
width: int,
396+
kernel_size: Tuple[int, int, int],
397+
):
398+
# freqs_cos, freqs_sin have shape [W * H * T, D / 2], where D is attention head dim
399+
freqs_cos, freqs_sin = image_rotary_emb
400+
freqs_cos = freqs_cos.unsqueeze(0).permute(0, 2, 1).unflatten(2, (num_frames, height, width))
401+
freqs_sin = freqs_sin.unsqueeze(0).permute(0, 2, 1).unflatten(2, (num_frames, height, width))
402+
freqs_cos = _pad_for_3d_conv(freqs_cos, kernel_size)
403+
freqs_sin = _pad_for_3d_conv(freqs_sin, kernel_size)
404+
freqs_cos = _center_down_sample_3d(freqs_cos, kernel_size)
405+
freqs_sin = _center_down_sample_3d(freqs_sin, kernel_size)
406+
freqs_cos = freqs_cos.flatten(2).permute(0, 2, 1).squeeze(0)
407+
freqs_sin = freqs_sin.flatten(2).permute(0, 2, 1).squeeze(0)
408+
return freqs_cos, freqs_sin
409+
370410

371411
def _pad_for_3d_conv(x, kernel_size):
412+
if isinstance(x, (tuple, list)):
413+
return tuple(_pad_for_3d_conv(i, kernel_size) for i in x)
372414
b, c, t, h, w = x.shape
373415
pt, ph, pw = kernel_size
374416
pad_t = (pt - (t % pt)) % pt
@@ -378,4 +420,6 @@ def _pad_for_3d_conv(x, kernel_size):
378420

379421

380422
def _center_down_sample_3d(x, kernel_size):
423+
if isinstance(x, (tuple, list)):
424+
return tuple(_center_down_sample_3d(i, kernel_size) for i in x)
381425
return torch.nn.functional.avg_pool3d(x, kernel_size, stride=kernel_size)

src/diffusers/pipelines/hunyuan_video/pipeline_hunyuan_video_framepack.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -705,14 +705,10 @@ def __call__(
705705
image = self.video_processor.preprocess(image, height, width)
706706
image_embeds = self.encode_image(image, device=device).to(transformer_dtype)
707707

708-
# 4. Prepare timesteps
709-
sigmas = np.linspace(1.0, 0.0, num_inference_steps + 1)[:-1] if sigmas is None else sigmas
710-
timesteps, num_inference_steps = retrieve_timesteps(self.scheduler, num_inference_steps, device, sigmas=sigmas)
711-
712708
# 5. Prepare latent variables
713709
num_channels_latents = self.transformer.config.in_channels
714-
window_size = (latent_window_size - 1) * self.vae_scale_factor_temporal + 1
715-
num_latent_sections = max(1, (num_frames + window_size - 1) // window_size)
710+
window_num_frames = (latent_window_size - 1) * self.vae_scale_factor_temporal + 1
711+
num_latent_sections = max(1, (num_frames + window_num_frames - 1) // window_num_frames)
716712
# Specific to the released checkpoint: https://huggingface.co/lllyasviel/FramePackI2V_HY
717713
# TODO: find a more generic way in future if there are more checkpoints
718714
history_sizes = [1, 2, 16]
@@ -739,10 +735,15 @@ def __call__(
739735
guidance = torch.tensor([guidance_scale] * batch_size, dtype=transformer_dtype, device=device) * 1000.0
740736

741737
# 7. Denoising loop
742-
num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
743-
self._num_timesteps = len(timesteps)
738+
sigmas = np.linspace(1.0, 0.0, num_inference_steps + 1)[:-1] if sigmas is None else sigmas
744739

745740
for i in range(num_latent_sections):
741+
timesteps, num_inference_steps = retrieve_timesteps(
742+
self.scheduler, num_inference_steps, device, sigmas=sigmas
743+
)
744+
num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
745+
self._num_timesteps = len(timesteps)
746+
746747
current_latent_padding = latent_paddings[i]
747748
is_last_section = current_latent_padding == 0
748749
latent_padding_size = current_latent_padding * latent_window_size
@@ -771,7 +772,7 @@ def __call__(
771772
num_channels_latents,
772773
height,
773774
width,
774-
num_frames,
775+
window_num_frames,
775776
dtype=torch.float32,
776777
device=device,
777778
generator=generator,
@@ -877,7 +878,11 @@ def __call__(
877878
self._current_timestep = None
878879

879880
if not output_type == "latent":
880-
history_video = history_video[:, :, :num_frames]
881+
generated_frames = history_video.size(2)
882+
generated_frames = (
883+
generated_frames - 1
884+
) // self.vae_scale_factor_temporal * self.vae_scale_factor_temporal + 1
885+
history_video = history_video[:, :, :generated_frames]
881886
video = self.video_processor.postprocess_video(history_video, output_type=output_type)
882887
else:
883888
video = history_video

0 commit comments

Comments
 (0)