├── .gitignore ├── CogVideoX ├── inference.py └── pipeline_stg_cogvideox.py ├── HunyuanVideo ├── inference.py └── pipeline_stg_hunyuan_video.py ├── LTXVideo ├── inference.py ├── pipeline_stg_ltx.py └── pipeline_stg_ltx_image2video.py ├── Mochi ├── inference.py └── pipeline_stg_mochi.py ├── README.md ├── Wan2.1 ├── inference.py ├── inference_low_memory.py ├── multiple_inference.py ├── pipeline.py └── prompts.txt └── assets ├── hunyuan1.mp4 ├── hunyuan2.mp4 └── tmp /.gitignore: -------------------------------------------------------------------------------- 1 | weights/ 2 | *.safetensors 3 | .cache 4 | .venv 5 | .venv_test 6 | dist 7 | __pycache__ 8 | mochi.egg-info 9 | genmo.egg-info 10 | outputs 11 | build 12 | .ruff_cache 13 | todo/LTX-Video/weights 14 | todo/LTX-Video/env 15 | mochi/latents.pt 16 | *.pt 17 | README.md 18 | **/ckpts/ 19 | **/__pycache__/ 20 | **/results/ 21 | **/unet_info.txt 22 | **/data/ 23 | samples/ 24 | *.mp4 -------------------------------------------------------------------------------- /CogVideoX/inference.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from diffusers import CogVideoXPipeline 3 | from pipeline_stg_cogvideox import CogVideoXSTGPipeline 4 | from diffusers.utils import export_to_video 5 | import os 6 | import re 7 | 8 | # Ensure the samples directory exists 9 | os.makedirs("samples", exist_ok=True) 10 | 11 | ckpt_path = "THUDM/CogVideoX-2b" 12 | # Load the pipeline 13 | pipe = CogVideoXSTGPipeline.from_pretrained(ckpt_path, torch_dtype=torch.bfloat16).to("cuda") # or "THUDM/CogVideoX-2b" 14 | 15 | # Define parameters 16 | prompt = ( 17 | "A father and son building a treehouse together, their hands covered in sawdust and smiles on their faces, realistic style." 18 | ) 19 | 20 | # prompt = re.sub(r'[^a-zA-Z0-9_\- ]', '_', prompt.strip()) # sanitize prompt 21 | 22 | #---------Option---------# 23 | stg_mode = "STG" 24 | stg_applied_layers_idx = [11] #0 ~ 41 25 | stg_scale = 1.0 # 0.0 for CFG (default) 26 | do_rescaling = False # False (default) 27 | #------------------------# 28 | 29 | guidance_scale = 6 30 | num_inference_steps = 50 31 | 32 | pipe.transformer.to(memory_format=torch.channels_last) 33 | # pipe.transformer = torch.compile(pipe.transformer, mode="max-autotune", fullgraph=True) 34 | 35 | # Generate video frames 36 | frames = pipe( 37 | height=480, 38 | width=480, 39 | prompt=prompt, 40 | guidance_scale=guidance_scale, 41 | num_inference_steps=num_inference_steps, 42 | stg_applied_layers_idx=stg_applied_layers_idx, 43 | stg_scale=stg_scale, 44 | do_rescaling=do_rescaling, 45 | generator=torch.Generator(device="cuda").manual_seed(42), 46 | ).frames[0] 47 | 48 | # Construct the video filename 49 | if stg_scale == 0: 50 | video_name = f"CFG_rescale_{do_rescaling}.mp4" 51 | else: 52 | layers_str = "_".join(map(str, stg_applied_layers_idx)) 53 | video_name = f"{stg_mode}_scale_{stg_scale}_layers_{layers_str}_rescale_{do_rescaling}.mp4" 54 | 55 | # Save video to samples directory 56 | video_path = os.path.join("samples", video_name) 57 | export_to_video(frames, video_path, fps=8) 58 | 59 | print(f"Video saved to {video_path}") 60 | -------------------------------------------------------------------------------- /CogVideoX/pipeline_stg_cogvideox.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CogVideoX team, Tsinghua University & ZhipuAI and The HuggingFace Team. 2 | # All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import types 17 | import inspect 18 | import math 19 | from typing import Any, Callable, Dict, List, Optional, Tuple, Union 20 | 21 | import torch 22 | from transformers import T5EncoderModel, T5Tokenizer 23 | 24 | from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback 25 | from diffusers.loaders import CogVideoXLoraLoaderMixin 26 | from diffusers.models import AutoencoderKLCogVideoX, CogVideoXTransformer3DModel 27 | from diffusers.models.embeddings import get_3d_rotary_pos_embed 28 | from diffusers.pipelines.pipeline_utils import DiffusionPipeline 29 | from diffusers.schedulers import CogVideoXDDIMScheduler, CogVideoXDPMScheduler 30 | from diffusers.utils import is_torch_xla_available, logging, replace_example_docstring 31 | from diffusers.utils.torch_utils import randn_tensor 32 | from diffusers.video_processor import VideoProcessor 33 | from diffusers.pipelines.cogvideo.pipeline_output import CogVideoXPipelineOutput 34 | 35 | 36 | if is_torch_xla_available(): 37 | import torch_xla.core.xla_model as xm 38 | 39 | XLA_AVAILABLE = True 40 | else: 41 | XLA_AVAILABLE = False 42 | 43 | logger = logging.get_logger(__name__) # pylint: disable=invalid-name 44 | 45 | 46 | EXAMPLE_DOC_STRING = """ 47 | Examples: 48 | ```python 49 | >>> import torch 50 | >>> from diffusers import CogVideoXPipeline 51 | >>> from diffusers.utils import export_to_video 52 | 53 | >>> # Models: "THUDM/CogVideoX-2b" or "THUDM/CogVideoX-5b" 54 | >>> pipe = CogVideoXPipeline.from_pretrained("THUDM/CogVideoX-2b", torch_dtype=torch.float16).to("cuda") 55 | >>> prompt = ( 56 | ... "A panda, dressed in a small, red jacket and a tiny hat, sits on a wooden stool in a serene bamboo forest. " 57 | ... "The panda's fluffy paws strum a miniature acoustic guitar, producing soft, melodic tunes. Nearby, a few other " 58 | ... "pandas gather, watching curiously and some clapping in rhythm. Sunlight filters through the tall bamboo, " 59 | ... "casting a gentle glow on the scene. The panda's face is expressive, showing concentration and joy as it plays. " 60 | ... "The background includes a small, flowing stream and vibrant green foliage, enhancing the peaceful and magical " 61 | ... "atmosphere of this unique musical performance." 62 | ... ) 63 | >>> video = pipe(prompt=prompt, guidance_scale=6, num_inference_steps=50).frames[0] 64 | >>> export_to_video(video, "output.mp4", fps=8) 65 | ``` 66 | """ 67 | 68 | 69 | def forward_with_stg( 70 | self, 71 | hidden_states: torch.Tensor, 72 | encoder_hidden_states: torch.Tensor, 73 | temb: torch.Tensor, 74 | image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, 75 | ) -> torch.Tensor: 76 | 77 | num_prompt = hidden_states.size(0) // 3 78 | hidden_states_ptb = hidden_states[2*num_prompt:] 79 | encoder_hidden_states_ptb = encoder_hidden_states[2*num_prompt:] 80 | 81 | text_seq_length = encoder_hidden_states.size(1) 82 | 83 | # norm & modulate 84 | norm_hidden_states, norm_encoder_hidden_states, gate_msa, enc_gate_msa = self.norm1( 85 | hidden_states, encoder_hidden_states, temb 86 | ) 87 | 88 | # attention 89 | attn_hidden_states, attn_encoder_hidden_states = self.attn1( 90 | hidden_states=norm_hidden_states, 91 | encoder_hidden_states=norm_encoder_hidden_states, 92 | image_rotary_emb=image_rotary_emb, 93 | ) 94 | 95 | hidden_states = hidden_states + gate_msa * attn_hidden_states 96 | encoder_hidden_states = encoder_hidden_states + enc_gate_msa * attn_encoder_hidden_states 97 | 98 | # norm & modulate 99 | norm_hidden_states, norm_encoder_hidden_states, gate_ff, enc_gate_ff = self.norm2( 100 | hidden_states, encoder_hidden_states, temb 101 | ) 102 | 103 | # feed-forward 104 | norm_hidden_states = torch.cat([norm_encoder_hidden_states, norm_hidden_states], dim=1) 105 | ff_output = self.ff(norm_hidden_states) 106 | 107 | hidden_states = hidden_states + gate_ff * ff_output[:, text_seq_length:] 108 | encoder_hidden_states = encoder_hidden_states + enc_gate_ff * ff_output[:, :text_seq_length] 109 | 110 | hidden_states[2*num_prompt:] = hidden_states_ptb 111 | encoder_hidden_states[2*num_prompt:] = encoder_hidden_states_ptb 112 | 113 | return hidden_states, encoder_hidden_states 114 | 115 | 116 | # Similar to diffusers.pipelines.hunyuandit.pipeline_hunyuandit.get_resize_crop_region_for_grid 117 | def get_resize_crop_region_for_grid(src, tgt_width, tgt_height): 118 | tw = tgt_width 119 | th = tgt_height 120 | h, w = src 121 | r = h / w 122 | if r > (th / tw): 123 | resize_height = th 124 | resize_width = int(round(th / h * w)) 125 | else: 126 | resize_width = tw 127 | resize_height = int(round(tw / w * h)) 128 | 129 | crop_top = int(round((th - resize_height) / 2.0)) 130 | crop_left = int(round((tw - resize_width) / 2.0)) 131 | 132 | return (crop_top, crop_left), (crop_top + resize_height, crop_left + resize_width) 133 | 134 | 135 | # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps 136 | def retrieve_timesteps( 137 | scheduler, 138 | num_inference_steps: Optional[int] = None, 139 | device: Optional[Union[str, torch.device]] = None, 140 | timesteps: Optional[List[int]] = None, 141 | sigmas: Optional[List[float]] = None, 142 | **kwargs, 143 | ): 144 | r""" 145 | Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles 146 | custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. 147 | 148 | Args: 149 | scheduler (`SchedulerMixin`): 150 | The scheduler to get timesteps from. 151 | num_inference_steps (`int`): 152 | The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps` 153 | must be `None`. 154 | device (`str` or `torch.device`, *optional*): 155 | The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. 156 | timesteps (`List[int]`, *optional*): 157 | Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed, 158 | `num_inference_steps` and `sigmas` must be `None`. 159 | sigmas (`List[float]`, *optional*): 160 | Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed, 161 | `num_inference_steps` and `timesteps` must be `None`. 162 | 163 | Returns: 164 | `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the 165 | second element is the number of inference steps. 166 | """ 167 | if timesteps is not None and sigmas is not None: 168 | raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values") 169 | if timesteps is not None: 170 | accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) 171 | if not accepts_timesteps: 172 | raise ValueError( 173 | f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" 174 | f" timestep schedules. Please check whether you are using the correct scheduler." 175 | ) 176 | scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) 177 | timesteps = scheduler.timesteps 178 | num_inference_steps = len(timesteps) 179 | elif sigmas is not None: 180 | accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) 181 | if not accept_sigmas: 182 | raise ValueError( 183 | f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" 184 | f" sigmas schedules. Please check whether you are using the correct scheduler." 185 | ) 186 | scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs) 187 | timesteps = scheduler.timesteps 188 | num_inference_steps = len(timesteps) 189 | else: 190 | scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) 191 | timesteps = scheduler.timesteps 192 | return timesteps, num_inference_steps 193 | 194 | 195 | class CogVideoXSTGPipeline(DiffusionPipeline, CogVideoXLoraLoaderMixin): 196 | r""" 197 | Pipeline for text-to-video generation using CogVideoX. 198 | 199 | This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the 200 | library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.) 201 | 202 | Args: 203 | vae ([`AutoencoderKL`]): 204 | Variational Auto-Encoder (VAE) Model to encode and decode videos to and from latent representations. 205 | text_encoder ([`T5EncoderModel`]): 206 | Frozen text-encoder. CogVideoX uses 207 | [T5](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5EncoderModel); specifically the 208 | [t5-v1_1-xxl](https://huggingface.co/PixArt-alpha/PixArt-alpha/tree/main/t5-v1_1-xxl) variant. 209 | tokenizer (`T5Tokenizer`): 210 | Tokenizer of class 211 | [T5Tokenizer](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5Tokenizer). 212 | transformer ([`CogVideoXTransformer3DModel`]): 213 | A text conditioned `CogVideoXTransformer3DModel` to denoise the encoded video latents. 214 | scheduler ([`SchedulerMixin`]): 215 | A scheduler to be used in combination with `transformer` to denoise the encoded video latents. 216 | """ 217 | 218 | _optional_components = [] 219 | model_cpu_offload_seq = "text_encoder->transformer->vae" 220 | 221 | _callback_tensor_inputs = [ 222 | "latents", 223 | "prompt_embeds", 224 | "negative_prompt_embeds", 225 | ] 226 | 227 | def __init__( 228 | self, 229 | tokenizer: T5Tokenizer, 230 | text_encoder: T5EncoderModel, 231 | vae: AutoencoderKLCogVideoX, 232 | transformer: CogVideoXTransformer3DModel, 233 | scheduler: Union[CogVideoXDDIMScheduler, CogVideoXDPMScheduler], 234 | ): 235 | super().__init__() 236 | 237 | self.register_modules( 238 | tokenizer=tokenizer, text_encoder=text_encoder, vae=vae, transformer=transformer, scheduler=scheduler 239 | ) 240 | self.vae_scale_factor_spatial = ( 241 | 2 ** (len(self.vae.config.block_out_channels) - 1) if getattr(self, "vae", None) else 8 242 | ) 243 | self.vae_scale_factor_temporal = ( 244 | self.vae.config.temporal_compression_ratio if getattr(self, "vae", None) else 4 245 | ) 246 | self.vae_scaling_factor_image = self.vae.config.scaling_factor if getattr(self, "vae", None) else 0.7 247 | 248 | self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor_spatial) 249 | 250 | def _get_t5_prompt_embeds( 251 | self, 252 | prompt: Union[str, List[str]] = None, 253 | num_videos_per_prompt: int = 1, 254 | max_sequence_length: int = 226, 255 | device: Optional[torch.device] = None, 256 | dtype: Optional[torch.dtype] = None, 257 | ): 258 | device = device or self._execution_device 259 | dtype = dtype or self.text_encoder.dtype 260 | 261 | prompt = [prompt] if isinstance(prompt, str) else prompt 262 | batch_size = len(prompt) 263 | 264 | text_inputs = self.tokenizer( 265 | prompt, 266 | padding="max_length", 267 | max_length=max_sequence_length, 268 | truncation=True, 269 | add_special_tokens=True, 270 | return_tensors="pt", 271 | ) 272 | text_input_ids = text_inputs.input_ids 273 | untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids 274 | 275 | if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids): 276 | removed_text = self.tokenizer.batch_decode(untruncated_ids[:, max_sequence_length - 1 : -1]) 277 | logger.warning( 278 | "The following part of your input was truncated because `max_sequence_length` is set to " 279 | f" {max_sequence_length} tokens: {removed_text}" 280 | ) 281 | 282 | prompt_embeds = self.text_encoder(text_input_ids.to(device))[0] 283 | prompt_embeds = prompt_embeds.to(dtype=dtype, device=device) 284 | 285 | # duplicate text embeddings for each generation per prompt, using mps friendly method 286 | _, seq_len, _ = prompt_embeds.shape 287 | prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1) 288 | prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1) 289 | 290 | return prompt_embeds 291 | 292 | def encode_prompt( 293 | self, 294 | prompt: Union[str, List[str]], 295 | negative_prompt: Optional[Union[str, List[str]]] = None, 296 | do_classifier_free_guidance: bool = True, 297 | num_videos_per_prompt: int = 1, 298 | prompt_embeds: Optional[torch.Tensor] = None, 299 | negative_prompt_embeds: Optional[torch.Tensor] = None, 300 | max_sequence_length: int = 226, 301 | device: Optional[torch.device] = None, 302 | dtype: Optional[torch.dtype] = None, 303 | ): 304 | r""" 305 | Encodes the prompt into text encoder hidden states. 306 | 307 | Args: 308 | prompt (`str` or `List[str]`, *optional*): 309 | prompt to be encoded 310 | negative_prompt (`str` or `List[str]`, *optional*): 311 | The prompt or prompts not to guide the image generation. If not defined, one has to pass 312 | `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is 313 | less than `1`). 314 | do_classifier_free_guidance (`bool`, *optional*, defaults to `True`): 315 | Whether to use classifier free guidance or not. 316 | num_videos_per_prompt (`int`, *optional*, defaults to 1): 317 | Number of videos that should be generated per prompt. torch device to place the resulting embeddings on 318 | prompt_embeds (`torch.Tensor`, *optional*): 319 | Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not 320 | provided, text embeddings will be generated from `prompt` input argument. 321 | negative_prompt_embeds (`torch.Tensor`, *optional*): 322 | Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt 323 | weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input 324 | argument. 325 | device: (`torch.device`, *optional*): 326 | torch device 327 | dtype: (`torch.dtype`, *optional*): 328 | torch dtype 329 | """ 330 | device = device or self._execution_device 331 | 332 | prompt = [prompt] if isinstance(prompt, str) else prompt 333 | if prompt is not None: 334 | batch_size = len(prompt) 335 | else: 336 | batch_size = prompt_embeds.shape[0] 337 | 338 | if prompt_embeds is None: 339 | prompt_embeds = self._get_t5_prompt_embeds( 340 | prompt=prompt, 341 | num_videos_per_prompt=num_videos_per_prompt, 342 | max_sequence_length=max_sequence_length, 343 | device=device, 344 | dtype=dtype, 345 | ) 346 | 347 | if do_classifier_free_guidance and negative_prompt_embeds is None: 348 | negative_prompt = negative_prompt or "" 349 | negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt 350 | 351 | if prompt is not None and type(prompt) is not type(negative_prompt): 352 | raise TypeError( 353 | f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" 354 | f" {type(prompt)}." 355 | ) 356 | elif batch_size != len(negative_prompt): 357 | raise ValueError( 358 | f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" 359 | f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" 360 | " the batch size of `prompt`." 361 | ) 362 | 363 | negative_prompt_embeds = self._get_t5_prompt_embeds( 364 | prompt=negative_prompt, 365 | num_videos_per_prompt=num_videos_per_prompt, 366 | max_sequence_length=max_sequence_length, 367 | device=device, 368 | dtype=dtype, 369 | ) 370 | 371 | return prompt_embeds, negative_prompt_embeds 372 | 373 | def prepare_latents( 374 | self, batch_size, num_channels_latents, num_frames, height, width, dtype, device, generator, latents=None 375 | ): 376 | if isinstance(generator, list) and len(generator) != batch_size: 377 | raise ValueError( 378 | f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" 379 | f" size of {batch_size}. Make sure the batch size matches the length of the generators." 380 | ) 381 | 382 | shape = ( 383 | batch_size, 384 | (num_frames - 1) // self.vae_scale_factor_temporal + 1, 385 | num_channels_latents, 386 | height // self.vae_scale_factor_spatial, 387 | width // self.vae_scale_factor_spatial, 388 | ) 389 | 390 | if latents is None: 391 | latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) 392 | else: 393 | latents = latents.to(device) 394 | 395 | # scale the initial noise by the standard deviation required by the scheduler 396 | latents = latents * self.scheduler.init_noise_sigma 397 | return latents 398 | 399 | def decode_latents(self, latents: torch.Tensor) -> torch.Tensor: 400 | latents = latents.permute(0, 2, 1, 3, 4) # [batch_size, num_channels, num_frames, height, width] 401 | latents = 1 / self.vae_scaling_factor_image * latents 402 | 403 | frames = self.vae.decode(latents).sample 404 | return frames 405 | 406 | # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs 407 | def prepare_extra_step_kwargs(self, generator, eta): 408 | # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature 409 | # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. 410 | # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 411 | # and should be between [0, 1] 412 | 413 | accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) 414 | extra_step_kwargs = {} 415 | if accepts_eta: 416 | extra_step_kwargs["eta"] = eta 417 | 418 | # check if the scheduler accepts generator 419 | accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) 420 | if accepts_generator: 421 | extra_step_kwargs["generator"] = generator 422 | return extra_step_kwargs 423 | 424 | # Copied from diffusers.pipelines.latte.pipeline_latte.LattePipeline.check_inputs 425 | def check_inputs( 426 | self, 427 | prompt, 428 | height, 429 | width, 430 | negative_prompt, 431 | callback_on_step_end_tensor_inputs, 432 | prompt_embeds=None, 433 | negative_prompt_embeds=None, 434 | ): 435 | if height % 8 != 0 or width % 8 != 0: 436 | raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.") 437 | 438 | if callback_on_step_end_tensor_inputs is not None and not all( 439 | k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs 440 | ): 441 | raise ValueError( 442 | f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}" 443 | ) 444 | if prompt is not None and prompt_embeds is not None: 445 | raise ValueError( 446 | f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" 447 | " only forward one of the two." 448 | ) 449 | elif prompt is None and prompt_embeds is None: 450 | raise ValueError( 451 | "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." 452 | ) 453 | elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): 454 | raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") 455 | 456 | if prompt is not None and negative_prompt_embeds is not None: 457 | raise ValueError( 458 | f"Cannot forward both `prompt`: {prompt} and `negative_prompt_embeds`:" 459 | f" {negative_prompt_embeds}. Please make sure to only forward one of the two." 460 | ) 461 | 462 | if negative_prompt is not None and negative_prompt_embeds is not None: 463 | raise ValueError( 464 | f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:" 465 | f" {negative_prompt_embeds}. Please make sure to only forward one of the two." 466 | ) 467 | 468 | if prompt_embeds is not None and negative_prompt_embeds is not None: 469 | if prompt_embeds.shape != negative_prompt_embeds.shape: 470 | raise ValueError( 471 | "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" 472 | f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" 473 | f" {negative_prompt_embeds.shape}." 474 | ) 475 | 476 | def fuse_qkv_projections(self) -> None: 477 | r"""Enables fused QKV projections.""" 478 | self.fusing_transformer = True 479 | self.transformer.fuse_qkv_projections() 480 | 481 | def unfuse_qkv_projections(self) -> None: 482 | r"""Disable QKV projection fusion if enabled.""" 483 | if not self.fusing_transformer: 484 | logger.warning("The Transformer was not initially fused for QKV projections. Doing nothing.") 485 | else: 486 | self.transformer.unfuse_qkv_projections() 487 | self.fusing_transformer = False 488 | 489 | def _prepare_rotary_positional_embeddings( 490 | self, 491 | height: int, 492 | width: int, 493 | num_frames: int, 494 | device: torch.device, 495 | ) -> Tuple[torch.Tensor, torch.Tensor]: 496 | grid_height = height // (self.vae_scale_factor_spatial * self.transformer.config.patch_size) 497 | grid_width = width // (self.vae_scale_factor_spatial * self.transformer.config.patch_size) 498 | 499 | p = self.transformer.config.patch_size 500 | p_t = self.transformer.config.patch_size_t 501 | 502 | base_size_width = self.transformer.config.sample_width // p 503 | base_size_height = self.transformer.config.sample_height // p 504 | 505 | if p_t is None: 506 | # CogVideoX 1.0 507 | grid_crops_coords = get_resize_crop_region_for_grid( 508 | (grid_height, grid_width), base_size_width, base_size_height 509 | ) 510 | freqs_cos, freqs_sin = get_3d_rotary_pos_embed( 511 | embed_dim=self.transformer.config.attention_head_dim, 512 | crops_coords=grid_crops_coords, 513 | grid_size=(grid_height, grid_width), 514 | temporal_size=num_frames, 515 | device=device, 516 | ) 517 | else: 518 | # CogVideoX 1.5 519 | base_num_frames = (num_frames + p_t - 1) // p_t 520 | 521 | freqs_cos, freqs_sin = get_3d_rotary_pos_embed( 522 | embed_dim=self.transformer.config.attention_head_dim, 523 | crops_coords=None, 524 | grid_size=(grid_height, grid_width), 525 | temporal_size=base_num_frames, 526 | grid_type="slice", 527 | max_size=(base_size_height, base_size_width), 528 | device=device, 529 | ) 530 | 531 | return freqs_cos, freqs_sin 532 | 533 | @property 534 | def guidance_scale(self): 535 | return self._guidance_scale 536 | 537 | @property 538 | def do_spatio_temporal_guidance(self): 539 | return self._stg_scale > 0.0 540 | 541 | @property 542 | def num_timesteps(self): 543 | return self._num_timesteps 544 | 545 | @property 546 | def attention_kwargs(self): 547 | return self._attention_kwargs 548 | 549 | @property 550 | def current_timestep(self): 551 | return self._current_timestep 552 | 553 | @property 554 | def interrupt(self): 555 | return self._interrupt 556 | 557 | @torch.no_grad() 558 | @replace_example_docstring(EXAMPLE_DOC_STRING) 559 | def __call__( 560 | self, 561 | prompt: Optional[Union[str, List[str]]] = None, 562 | negative_prompt: Optional[Union[str, List[str]]] = None, 563 | height: Optional[int] = None, 564 | width: Optional[int] = None, 565 | num_frames: Optional[int] = None, 566 | num_inference_steps: int = 50, 567 | timesteps: Optional[List[int]] = None, 568 | guidance_scale: float = 6, 569 | use_dynamic_cfg: bool = False, 570 | num_videos_per_prompt: int = 1, 571 | eta: float = 0.0, 572 | generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, 573 | latents: Optional[torch.FloatTensor] = None, 574 | prompt_embeds: Optional[torch.FloatTensor] = None, 575 | negative_prompt_embeds: Optional[torch.FloatTensor] = None, 576 | output_type: str = "pil", 577 | return_dict: bool = True, 578 | attention_kwargs: Optional[Dict[str, Any]] = None, 579 | callback_on_step_end: Optional[ 580 | Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks] 581 | ] = None, 582 | callback_on_step_end_tensor_inputs: List[str] = ["latents"], 583 | max_sequence_length: int = 226, 584 | stg_applied_layers_idx: Optional[List[int]] = [11], 585 | stg_scale: Optional[float] = 0.0, 586 | do_rescaling: Optional[bool] = False, 587 | ) -> Union[CogVideoXPipelineOutput, Tuple]: 588 | """ 589 | Function invoked when calling the pipeline for generation. 590 | 591 | Args: 592 | prompt (`str` or `List[str]`, *optional*): 593 | The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. 594 | instead. 595 | negative_prompt (`str` or `List[str]`, *optional*): 596 | The prompt or prompts not to guide the image generation. If not defined, one has to pass 597 | `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is 598 | less than `1`). 599 | height (`int`, *optional*, defaults to self.transformer.config.sample_height * self.vae_scale_factor_spatial): 600 | The height in pixels of the generated image. This is set to 480 by default for the best results. 601 | width (`int`, *optional*, defaults to self.transformer.config.sample_height * self.vae_scale_factor_spatial): 602 | The width in pixels of the generated image. This is set to 720 by default for the best results. 603 | num_frames (`int`, defaults to `48`): 604 | Number of frames to generate. Must be divisible by self.vae_scale_factor_temporal. Generated video will 605 | contain 1 extra frame because CogVideoX is conditioned with (num_seconds * fps + 1) frames where 606 | num_seconds is 6 and fps is 8. However, since videos can be saved at any fps, the only condition that 607 | needs to be satisfied is that of divisibility mentioned above. 608 | num_inference_steps (`int`, *optional*, defaults to 50): 609 | The number of denoising steps. More denoising steps usually lead to a higher quality image at the 610 | expense of slower inference. 611 | timesteps (`List[int]`, *optional*): 612 | Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument 613 | in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is 614 | passed will be used. Must be in descending order. 615 | guidance_scale (`float`, *optional*, defaults to 7.0): 616 | Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). 617 | `guidance_scale` is defined as `w` of equation 2. of [Imagen 618 | Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > 619 | 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, 620 | usually at the expense of lower image quality. 621 | num_videos_per_prompt (`int`, *optional*, defaults to 1): 622 | The number of videos to generate per prompt. 623 | generator (`torch.Generator` or `List[torch.Generator]`, *optional*): 624 | One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html) 625 | to make generation deterministic. 626 | latents (`torch.FloatTensor`, *optional*): 627 | Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image 628 | generation. Can be used to tweak the same generation with different prompts. If not provided, a latents 629 | tensor will ge generated by sampling using the supplied random `generator`. 630 | prompt_embeds (`torch.FloatTensor`, *optional*): 631 | Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not 632 | provided, text embeddings will be generated from `prompt` input argument. 633 | negative_prompt_embeds (`torch.FloatTensor`, *optional*): 634 | Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt 635 | weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input 636 | argument. 637 | output_type (`str`, *optional*, defaults to `"pil"`): 638 | The output format of the generate image. Choose between 639 | [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`. 640 | return_dict (`bool`, *optional*, defaults to `True`): 641 | Whether or not to return a [`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] instead 642 | of a plain tuple. 643 | attention_kwargs (`dict`, *optional*): 644 | A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under 645 | `self.processor` in 646 | [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). 647 | callback_on_step_end (`Callable`, *optional*): 648 | A function that calls at the end of each denoising steps during the inference. The function is called 649 | with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int, 650 | callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by 651 | `callback_on_step_end_tensor_inputs`. 652 | callback_on_step_end_tensor_inputs (`List`, *optional*): 653 | The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list 654 | will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the 655 | `._callback_tensor_inputs` attribute of your pipeline class. 656 | max_sequence_length (`int`, defaults to `226`): 657 | Maximum sequence length in encoded prompt. Must be consistent with 658 | `self.transformer.config.max_text_seq_length` otherwise may lead to poor results. 659 | 660 | Examples: 661 | 662 | Returns: 663 | [`~pipelines.cogvideo.pipeline_cogvideox.CogVideoXPipelineOutput`] or `tuple`: 664 | [`~pipelines.cogvideo.pipeline_cogvideox.CogVideoXPipelineOutput`] if `return_dict` is True, otherwise a 665 | `tuple`. When returning a tuple, the first element is a list with the generated images. 666 | """ 667 | 668 | if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)): 669 | callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs 670 | 671 | height = height or self.transformer.config.sample_height * self.vae_scale_factor_spatial 672 | width = width or self.transformer.config.sample_width * self.vae_scale_factor_spatial 673 | num_frames = num_frames or self.transformer.config.sample_frames 674 | 675 | num_videos_per_prompt = 1 676 | 677 | # 1. Check inputs. Raise error if not correct 678 | self.check_inputs( 679 | prompt, 680 | height, 681 | width, 682 | negative_prompt, 683 | callback_on_step_end_tensor_inputs, 684 | prompt_embeds, 685 | negative_prompt_embeds, 686 | ) 687 | self._stg_scale = stg_scale 688 | self._guidance_scale = guidance_scale 689 | self._attention_kwargs = attention_kwargs 690 | self._current_timestep = None 691 | self._interrupt = False 692 | 693 | if self.do_spatio_temporal_guidance: 694 | for i in stg_applied_layers_idx: 695 | self.transformer.transformer_blocks[i].forward = types.MethodType(forward_with_stg, self.transformer.transformer_blocks[i]) 696 | 697 | # 2. Default call parameters 698 | if prompt is not None and isinstance(prompt, str): 699 | batch_size = 1 700 | elif prompt is not None and isinstance(prompt, list): 701 | batch_size = len(prompt) 702 | else: 703 | batch_size = prompt_embeds.shape[0] 704 | 705 | device = self._execution_device 706 | 707 | # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) 708 | # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` 709 | # corresponds to doing no classifier free guidance. 710 | do_classifier_free_guidance = guidance_scale > 1.0 711 | 712 | # 3. Encode input prompt 713 | prompt_embeds, negative_prompt_embeds = self.encode_prompt( 714 | prompt, 715 | negative_prompt, 716 | do_classifier_free_guidance, 717 | num_videos_per_prompt=num_videos_per_prompt, 718 | prompt_embeds=prompt_embeds, 719 | negative_prompt_embeds=negative_prompt_embeds, 720 | max_sequence_length=max_sequence_length, 721 | device=device, 722 | ) 723 | if do_classifier_free_guidance and not self.do_spatio_temporal_guidance: 724 | prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0) 725 | elif do_classifier_free_guidance and self.do_spatio_temporal_guidance: 726 | prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds, prompt_embeds], dim=0) 727 | 728 | # 4. Prepare timesteps 729 | timesteps, num_inference_steps = retrieve_timesteps(self.scheduler, num_inference_steps, device, timesteps) 730 | self._num_timesteps = len(timesteps) 731 | 732 | # 5. Prepare latents 733 | latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1 734 | 735 | # For CogVideoX 1.5, the latent frames should be padded to make it divisible by patch_size_t 736 | patch_size_t = self.transformer.config.patch_size_t 737 | additional_frames = 0 738 | if patch_size_t is not None and latent_frames % patch_size_t != 0: 739 | additional_frames = patch_size_t - latent_frames % patch_size_t 740 | num_frames += additional_frames * self.vae_scale_factor_temporal 741 | 742 | latent_channels = self.transformer.config.in_channels 743 | latents = self.prepare_latents( 744 | batch_size * num_videos_per_prompt, 745 | latent_channels, 746 | num_frames, 747 | height, 748 | width, 749 | prompt_embeds.dtype, 750 | device, 751 | generator, 752 | latents, 753 | ) 754 | 755 | # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline 756 | extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) 757 | 758 | # 7. Create rotary embeds if required 759 | image_rotary_emb = ( 760 | self._prepare_rotary_positional_embeddings(height, width, latents.size(1), device) 761 | if self.transformer.config.use_rotary_positional_embeddings 762 | else None 763 | ) 764 | 765 | # 8. Denoising loop 766 | num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) 767 | 768 | with self.progress_bar(total=num_inference_steps) as progress_bar: 769 | # for DPM-solver++ 770 | old_pred_original_sample = None 771 | for i, t in enumerate(timesteps): 772 | if self.interrupt: 773 | continue 774 | 775 | self._current_timestep = t 776 | if do_classifier_free_guidance and not self.do_spatio_temporal_guidance: 777 | latent_model_input = torch.cat([latents] * 2) 778 | elif do_classifier_free_guidance and self.do_spatio_temporal_guidance: 779 | latent_model_input = torch.cat([latents] * 3) 780 | else: 781 | latent_model_input = latents 782 | latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) 783 | 784 | # broadcast to batch dimension in a way that's compatible with ONNX/Core ML 785 | timestep = t.expand(latent_model_input.shape[0]) 786 | 787 | # predict noise model_output 788 | noise_pred = self.transformer( 789 | hidden_states=latent_model_input, 790 | encoder_hidden_states=prompt_embeds, 791 | timestep=timestep, 792 | image_rotary_emb=image_rotary_emb, 793 | attention_kwargs=attention_kwargs, 794 | return_dict=False, 795 | )[0] 796 | noise_pred = noise_pred.float() 797 | 798 | # perform guidance 799 | if use_dynamic_cfg: 800 | self._guidance_scale = 1 + guidance_scale * ( 801 | (1 - math.cos(math.pi * ((num_inference_steps - t.item()) / num_inference_steps) ** 5.0)) / 2 802 | ) 803 | if do_classifier_free_guidance and not self.do_spatio_temporal_guidance: 804 | noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) 805 | noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond) 806 | elif do_classifier_free_guidance and self.do_spatio_temporal_guidance: 807 | noise_pred_uncond, noise_pred_text, noise_pred_perturb = noise_pred.chunk(3) 808 | noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond) \ 809 | + self._stg_scale * (noise_pred_text - noise_pred_perturb) 810 | 811 | if do_rescaling: 812 | rescaling_scale = 0.7 813 | factor = noise_pred_text.std() / noise_pred.std() 814 | factor = rescaling_scale * factor + (1 - rescaling_scale) 815 | noise_pred = noise_pred * factor 816 | 817 | # compute the previous noisy sample x_t -> x_t-1 818 | if not isinstance(self.scheduler, CogVideoXDPMScheduler): 819 | latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] 820 | else: 821 | latents, old_pred_original_sample = self.scheduler.step( 822 | noise_pred, 823 | old_pred_original_sample, 824 | t, 825 | timesteps[i - 1] if i > 0 else None, 826 | latents, 827 | **extra_step_kwargs, 828 | return_dict=False, 829 | ) 830 | latents = latents.to(prompt_embeds.dtype) 831 | 832 | # call the callback, if provided 833 | if callback_on_step_end is not None: 834 | callback_kwargs = {} 835 | for k in callback_on_step_end_tensor_inputs: 836 | callback_kwargs[k] = locals()[k] 837 | callback_outputs = callback_on_step_end(self, i, t, callback_kwargs) 838 | 839 | latents = callback_outputs.pop("latents", latents) 840 | prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds) 841 | negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds", negative_prompt_embeds) 842 | 843 | if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): 844 | progress_bar.update() 845 | 846 | if XLA_AVAILABLE: 847 | xm.mark_step() 848 | 849 | self._current_timestep = None 850 | 851 | if not output_type == "latent": 852 | # Discard any padding frames that were added for CogVideoX 1.5 853 | latents = latents[:, additional_frames:] 854 | video = self.decode_latents(latents) 855 | video = self.video_processor.postprocess_video(video=video, output_type=output_type) 856 | else: 857 | video = latents 858 | 859 | # Offload all models 860 | self.maybe_free_model_hooks() 861 | 862 | if not return_dict: 863 | return (video,) 864 | 865 | return CogVideoXPipelineOutput(frames=video) -------------------------------------------------------------------------------- /HunyuanVideo/inference.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | from diffusers import HunyuanVideoPipeline, HunyuanVideoTransformer3DModel 4 | from pipeline_stg_hunyuan_video import HunyuanVideoSTGPipeline 5 | from diffusers.utils import export_to_video 6 | 7 | model_id = "tencent/HunyuanVideo" 8 | transformer = HunyuanVideoTransformer3DModel.from_pretrained( 9 | model_id, subfolder="transformer", torch_dtype=torch.bfloat16, revision='refs/pr/18' 10 | ) 11 | pipe = HunyuanVideoSTGPipeline.from_pretrained(model_id, transformer=transformer, revision='refs/pr/18', torch_dtype=torch.float16) 12 | pipe.vae.enable_tiling() 13 | pipe.to("cuda") 14 | 15 | #--------Option--------# 16 | stg_mode = "STG" 17 | stg_applied_layers_idx = [2] 18 | stg_scale = 1.0 19 | do_rescaling = False 20 | #----------------------# 21 | 22 | output = pipe( 23 | prompt="A wolf howling at the moon, with the moon subtly resembling a giant clock face, realistic style.", 24 | height=320, 25 | width=512, 26 | num_frames=61, 27 | num_inference_steps=30, 28 | stg_applied_layers_idx=stg_applied_layers_idx, 29 | stg_scale=stg_scale, 30 | do_rescaling=do_rescaling, 31 | generator=torch.Generator(device="cuda").manual_seed(42), 32 | ).frames[0] 33 | 34 | if stg_scale == 0: 35 | video_name = f"CFG_rescale_{do_rescaling}.mp4" 36 | else: 37 | layers_str = "_".join(map(str, stg_applied_layers_idx)) 38 | video_name = f"{stg_mode}_scale_{stg_scale}_layers_{layers_str}_rescale_{do_rescaling}.mp4" 39 | 40 | # Save video to samples directory 41 | sample_dir = "samples" 42 | os.makedirs(sample_dir, exist_ok=True) 43 | video_path = os.path.join(sample_dir, video_name) 44 | export_to_video(output, video_path, fps=15) 45 | 46 | print(f"Video saved to {video_path}") 47 | -------------------------------------------------------------------------------- /HunyuanVideo/pipeline_stg_hunyuan_video.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The HunyuanVideo Team and The HuggingFace Team. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import types 16 | import inspect 17 | from typing import Any, Callable, Dict, List, Optional, Tuple, Union 18 | 19 | import numpy as np 20 | import torch 21 | from transformers import CLIPTextModel, CLIPTokenizer, LlamaModel, LlamaTokenizerFast 22 | 23 | from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback 24 | from diffusers.loaders import HunyuanVideoLoraLoaderMixin 25 | from diffusers.models import AutoencoderKLHunyuanVideo, HunyuanVideoTransformer3DModel 26 | from diffusers.schedulers import FlowMatchEulerDiscreteScheduler 27 | from diffusers.utils import is_torch_xla_available, logging, replace_example_docstring 28 | from diffusers.utils.torch_utils import randn_tensor 29 | from diffusers.video_processor import VideoProcessor 30 | from diffusers.pipelines.pipeline_utils import DiffusionPipeline 31 | from diffusers.pipelines.hunyuan_video.pipeline_output import HunyuanVideoPipelineOutput 32 | 33 | 34 | if is_torch_xla_available(): 35 | import torch_xla.core.xla_model as xm 36 | 37 | XLA_AVAILABLE = True 38 | else: 39 | XLA_AVAILABLE = False 40 | 41 | logger = logging.get_logger(__name__) # pylint: disable=invalid-name 42 | 43 | 44 | EXAMPLE_DOC_STRING = """ 45 | Examples: 46 | ```python 47 | >>> import torch 48 | >>> from diffusers import HunyuanVideoPipeline, HunyuanVideoTransformer3DModel 49 | >>> from diffusers.utils import export_to_video 50 | 51 | >>> model_id = "hunyuanvideo-community/HunyuanVideo" 52 | >>> transformer = HunyuanVideoTransformer3DModel.from_pretrained( 53 | ... model_id, subfolder="transformer", torch_dtype=torch.bfloat16 54 | ... ) 55 | >>> pipe = HunyuanVideoPipeline.from_pretrained(model_id, transformer=transformer, torch_dtype=torch.float16) 56 | >>> pipe.vae.enable_tiling() 57 | >>> pipe.to("cuda") 58 | 59 | >>> output = pipe( 60 | ... prompt="A cat walks on the grass, realistic", 61 | ... height=320, 62 | ... width=512, 63 | ... num_frames=61, 64 | ... num_inference_steps=30, 65 | ... ).frames[0] 66 | >>> export_to_video(output, "output.mp4", fps=15) 67 | ``` 68 | """ 69 | 70 | 71 | DEFAULT_PROMPT_TEMPLATE = { 72 | "template": ( 73 | "<|start_header_id|>system<|end_header_id|>\n\nDescribe the video by detailing the following aspects: " 74 | "1. The main content and theme of the video." 75 | "2. The color, shape, size, texture, quantity, text, and spatial relationships of the objects." 76 | "3. Actions, events, behaviors temporal relationships, physical movement changes of the objects." 77 | "4. background environment, light, style and atmosphere." 78 | "5. camera angles, movements, and transitions used in the video:<|eot_id|>" 79 | "<|start_header_id|>user<|end_header_id|>\n\n{}<|eot_id|>" 80 | ), 81 | "crop_start": 95, 82 | } 83 | 84 | 85 | def forward_with_stg( 86 | self, 87 | hidden_states: torch.Tensor, 88 | encoder_hidden_states: torch.Tensor, 89 | temb: torch.Tensor, 90 | attention_mask: Optional[torch.Tensor] = None, 91 | freqs_cis: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, 92 | ) -> Tuple[torch.Tensor, torch.Tensor]: 93 | return hidden_states, encoder_hidden_states 94 | 95 | 96 | def forward_without_stg( 97 | self, 98 | hidden_states: torch.Tensor, 99 | encoder_hidden_states: torch.Tensor, 100 | temb: torch.Tensor, 101 | attention_mask: Optional[torch.Tensor] = None, 102 | freqs_cis: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, 103 | ) -> Tuple[torch.Tensor, torch.Tensor]: 104 | # 1. Input normalization 105 | norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1(hidden_states, emb=temb) 106 | norm_encoder_hidden_states, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.norm1_context( 107 | encoder_hidden_states, emb=temb 108 | ) 109 | 110 | # 2. Joint attention 111 | attn_output, context_attn_output = self.attn( 112 | hidden_states=norm_hidden_states, 113 | encoder_hidden_states=norm_encoder_hidden_states, 114 | attention_mask=attention_mask, 115 | image_rotary_emb=freqs_cis, 116 | ) 117 | 118 | # 3. Modulation and residual connection 119 | hidden_states = hidden_states + attn_output * gate_msa.unsqueeze(1) 120 | encoder_hidden_states = encoder_hidden_states + context_attn_output * c_gate_msa.unsqueeze(1) 121 | 122 | norm_hidden_states = self.norm2(hidden_states) 123 | norm_encoder_hidden_states = self.norm2_context(encoder_hidden_states) 124 | 125 | norm_hidden_states = norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None] 126 | norm_encoder_hidden_states = norm_encoder_hidden_states * (1 + c_scale_mlp[:, None]) + c_shift_mlp[:, None] 127 | 128 | # 4. Feed-forward 129 | ff_output = self.ff(norm_hidden_states) 130 | context_ff_output = self.ff_context(norm_encoder_hidden_states) 131 | 132 | hidden_states = hidden_states + gate_mlp.unsqueeze(1) * ff_output 133 | encoder_hidden_states = encoder_hidden_states + c_gate_mlp.unsqueeze(1) * context_ff_output 134 | 135 | return hidden_states, encoder_hidden_states 136 | 137 | 138 | # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps 139 | def retrieve_timesteps( 140 | scheduler, 141 | num_inference_steps: Optional[int] = None, 142 | device: Optional[Union[str, torch.device]] = None, 143 | timesteps: Optional[List[int]] = None, 144 | sigmas: Optional[List[float]] = None, 145 | **kwargs, 146 | ): 147 | r""" 148 | Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles 149 | custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. 150 | 151 | Args: 152 | scheduler (`SchedulerMixin`): 153 | The scheduler to get timesteps from. 154 | num_inference_steps (`int`): 155 | The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps` 156 | must be `None`. 157 | device (`str` or `torch.device`, *optional*): 158 | The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. 159 | timesteps (`List[int]`, *optional*): 160 | Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed, 161 | `num_inference_steps` and `sigmas` must be `None`. 162 | sigmas (`List[float]`, *optional*): 163 | Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed, 164 | `num_inference_steps` and `timesteps` must be `None`. 165 | 166 | Returns: 167 | `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the 168 | second element is the number of inference steps. 169 | """ 170 | if timesteps is not None and sigmas is not None: 171 | raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values") 172 | if timesteps is not None: 173 | accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) 174 | if not accepts_timesteps: 175 | raise ValueError( 176 | f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" 177 | f" timestep schedules. Please check whether you are using the correct scheduler." 178 | ) 179 | scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) 180 | timesteps = scheduler.timesteps 181 | num_inference_steps = len(timesteps) 182 | elif sigmas is not None: 183 | accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) 184 | if not accept_sigmas: 185 | raise ValueError( 186 | f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" 187 | f" sigmas schedules. Please check whether you are using the correct scheduler." 188 | ) 189 | scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs) 190 | timesteps = scheduler.timesteps 191 | num_inference_steps = len(timesteps) 192 | else: 193 | scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) 194 | timesteps = scheduler.timesteps 195 | return timesteps, num_inference_steps 196 | 197 | 198 | class HunyuanVideoSTGPipeline(DiffusionPipeline, HunyuanVideoLoraLoaderMixin): 199 | r""" 200 | Pipeline for text-to-video generation using HunyuanVideo. 201 | 202 | This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods 203 | implemented for all pipelines (downloading, saving, running on a particular device, etc.). 204 | 205 | Args: 206 | text_encoder ([`LlamaModel`]): 207 | [Llava Llama3-8B](https://huggingface.co/xtuner/llava-llama-3-8b-v1_1-transformers). 208 | tokenizer (`LlamaTokenizer`): 209 | Tokenizer from [Llava Llama3-8B](https://huggingface.co/xtuner/llava-llama-3-8b-v1_1-transformers). 210 | transformer ([`HunyuanVideoTransformer3DModel`]): 211 | Conditional Transformer to denoise the encoded image latents. 212 | scheduler ([`FlowMatchEulerDiscreteScheduler`]): 213 | A scheduler to be used in combination with `transformer` to denoise the encoded image latents. 214 | vae ([`AutoencoderKLHunyuanVideo`]): 215 | Variational Auto-Encoder (VAE) Model to encode and decode videos to and from latent representations. 216 | text_encoder_2 ([`CLIPTextModel`]): 217 | [CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically 218 | the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant. 219 | tokenizer_2 (`CLIPTokenizer`): 220 | Tokenizer of class 221 | [CLIPTokenizer](https://huggingface.co/docs/transformers/en/model_doc/clip#transformers.CLIPTokenizer). 222 | """ 223 | 224 | model_cpu_offload_seq = "text_encoder->text_encoder_2->transformer->vae" 225 | _callback_tensor_inputs = ["latents", "prompt_embeds"] 226 | 227 | def __init__( 228 | self, 229 | text_encoder: LlamaModel, 230 | tokenizer: LlamaTokenizerFast, 231 | transformer: HunyuanVideoTransformer3DModel, 232 | vae: AutoencoderKLHunyuanVideo, 233 | scheduler: FlowMatchEulerDiscreteScheduler, 234 | text_encoder_2: CLIPTextModel, 235 | tokenizer_2: CLIPTokenizer, 236 | ): 237 | super().__init__() 238 | 239 | self.register_modules( 240 | vae=vae, 241 | text_encoder=text_encoder, 242 | tokenizer=tokenizer, 243 | transformer=transformer, 244 | scheduler=scheduler, 245 | text_encoder_2=text_encoder_2, 246 | tokenizer_2=tokenizer_2, 247 | ) 248 | 249 | self.vae_scale_factor_temporal = self.vae.temporal_compression_ratio if getattr(self, "vae", None) else 4 250 | self.vae_scale_factor_spatial = self.vae.spatial_compression_ratio if getattr(self, "vae", None) else 8 251 | self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor_spatial) 252 | 253 | def _get_llama_prompt_embeds( 254 | self, 255 | prompt: Union[str, List[str]], 256 | prompt_template: Dict[str, Any], 257 | num_videos_per_prompt: int = 1, 258 | device: Optional[torch.device] = None, 259 | dtype: Optional[torch.dtype] = None, 260 | max_sequence_length: int = 256, 261 | num_hidden_layers_to_skip: int = 2, 262 | ) -> Tuple[torch.Tensor, torch.Tensor]: 263 | device = device or self._execution_device 264 | dtype = dtype or self.text_encoder.dtype 265 | 266 | prompt = [prompt] if isinstance(prompt, str) else prompt 267 | batch_size = len(prompt) 268 | 269 | prompt = [prompt_template["template"].format(p) for p in prompt] 270 | 271 | crop_start = prompt_template.get("crop_start", None) 272 | if crop_start is None: 273 | prompt_template_input = self.tokenizer( 274 | prompt_template["template"], 275 | padding="max_length", 276 | return_tensors="pt", 277 | return_length=False, 278 | return_overflowing_tokens=False, 279 | return_attention_mask=False, 280 | ) 281 | crop_start = prompt_template_input["input_ids"].shape[-1] 282 | # Remove <|eot_id|> token and placeholder {} 283 | crop_start -= 2 284 | 285 | max_sequence_length += crop_start 286 | text_inputs = self.tokenizer( 287 | prompt, 288 | max_length=max_sequence_length, 289 | padding="max_length", 290 | truncation=True, 291 | return_tensors="pt", 292 | return_length=False, 293 | return_overflowing_tokens=False, 294 | return_attention_mask=True, 295 | ) 296 | text_input_ids = text_inputs.input_ids.to(device=device) 297 | prompt_attention_mask = text_inputs.attention_mask.to(device=device) 298 | 299 | prompt_embeds = self.text_encoder( 300 | input_ids=text_input_ids, 301 | attention_mask=prompt_attention_mask, 302 | output_hidden_states=True, 303 | ).hidden_states[-(num_hidden_layers_to_skip + 1)] 304 | prompt_embeds = prompt_embeds.to(dtype=dtype) 305 | 306 | if crop_start is not None and crop_start > 0: 307 | prompt_embeds = prompt_embeds[:, crop_start:] 308 | prompt_attention_mask = prompt_attention_mask[:, crop_start:] 309 | 310 | # duplicate text embeddings for each generation per prompt, using mps friendly method 311 | _, seq_len, _ = prompt_embeds.shape 312 | prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1) 313 | prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1) 314 | prompt_attention_mask = prompt_attention_mask.repeat(1, num_videos_per_prompt) 315 | prompt_attention_mask = prompt_attention_mask.view(batch_size * num_videos_per_prompt, seq_len) 316 | 317 | return prompt_embeds, prompt_attention_mask 318 | 319 | def _get_clip_prompt_embeds( 320 | self, 321 | prompt: Union[str, List[str]], 322 | num_videos_per_prompt: int = 1, 323 | device: Optional[torch.device] = None, 324 | dtype: Optional[torch.dtype] = None, 325 | max_sequence_length: int = 77, 326 | ) -> torch.Tensor: 327 | device = device or self._execution_device 328 | dtype = dtype or self.text_encoder_2.dtype 329 | 330 | prompt = [prompt] if isinstance(prompt, str) else prompt 331 | batch_size = len(prompt) 332 | 333 | text_inputs = self.tokenizer_2( 334 | prompt, 335 | padding="max_length", 336 | max_length=max_sequence_length, 337 | truncation=True, 338 | return_tensors="pt", 339 | ) 340 | 341 | text_input_ids = text_inputs.input_ids 342 | untruncated_ids = self.tokenizer_2(prompt, padding="longest", return_tensors="pt").input_ids 343 | if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids): 344 | removed_text = self.tokenizer_2.batch_decode(untruncated_ids[:, max_sequence_length - 1 : -1]) 345 | logger.warning( 346 | "The following part of your input was truncated because CLIP can only handle sequences up to" 347 | f" {max_sequence_length} tokens: {removed_text}" 348 | ) 349 | 350 | prompt_embeds = self.text_encoder_2(text_input_ids.to(device), output_hidden_states=False).pooler_output 351 | 352 | # duplicate text embeddings for each generation per prompt, using mps friendly method 353 | prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt) 354 | prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, -1) 355 | 356 | return prompt_embeds 357 | 358 | def encode_prompt( 359 | self, 360 | prompt: Union[str, List[str]], 361 | prompt_2: Union[str, List[str]] = None, 362 | prompt_template: Dict[str, Any] = DEFAULT_PROMPT_TEMPLATE, 363 | num_videos_per_prompt: int = 1, 364 | prompt_embeds: Optional[torch.Tensor] = None, 365 | pooled_prompt_embeds: Optional[torch.Tensor] = None, 366 | prompt_attention_mask: Optional[torch.Tensor] = None, 367 | device: Optional[torch.device] = None, 368 | dtype: Optional[torch.dtype] = None, 369 | max_sequence_length: int = 256, 370 | ): 371 | if prompt_embeds is None: 372 | prompt_embeds, prompt_attention_mask = self._get_llama_prompt_embeds( 373 | prompt, 374 | prompt_template, 375 | num_videos_per_prompt, 376 | device=device, 377 | dtype=dtype, 378 | max_sequence_length=max_sequence_length, 379 | ) 380 | 381 | if pooled_prompt_embeds is None: 382 | if prompt_2 is None and pooled_prompt_embeds is None: 383 | prompt_2 = prompt 384 | pooled_prompt_embeds = self._get_clip_prompt_embeds( 385 | prompt, 386 | num_videos_per_prompt, 387 | device=device, 388 | dtype=dtype, 389 | max_sequence_length=77, 390 | ) 391 | 392 | return prompt_embeds, pooled_prompt_embeds, prompt_attention_mask 393 | 394 | def check_inputs( 395 | self, 396 | prompt, 397 | prompt_2, 398 | height, 399 | width, 400 | prompt_embeds=None, 401 | callback_on_step_end_tensor_inputs=None, 402 | prompt_template=None, 403 | ): 404 | if height % 16 != 0 or width % 16 != 0: 405 | raise ValueError(f"`height` and `width` have to be divisible by 16 but are {height} and {width}.") 406 | 407 | if callback_on_step_end_tensor_inputs is not None and not all( 408 | k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs 409 | ): 410 | raise ValueError( 411 | f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}" 412 | ) 413 | 414 | if prompt is not None and prompt_embeds is not None: 415 | raise ValueError( 416 | f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" 417 | " only forward one of the two." 418 | ) 419 | elif prompt_2 is not None and prompt_embeds is not None: 420 | raise ValueError( 421 | f"Cannot forward both `prompt_2`: {prompt_2} and `prompt_embeds`: {prompt_embeds}. Please make sure to" 422 | " only forward one of the two." 423 | ) 424 | elif prompt is None and prompt_embeds is None: 425 | raise ValueError( 426 | "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." 427 | ) 428 | elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): 429 | raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") 430 | elif prompt_2 is not None and (not isinstance(prompt_2, str) and not isinstance(prompt_2, list)): 431 | raise ValueError(f"`prompt_2` has to be of type `str` or `list` but is {type(prompt_2)}") 432 | 433 | if prompt_template is not None: 434 | if not isinstance(prompt_template, dict): 435 | raise ValueError(f"`prompt_template` has to be of type `dict` but is {type(prompt_template)}") 436 | if "template" not in prompt_template: 437 | raise ValueError( 438 | f"`prompt_template` has to contain a key `template` but only found {prompt_template.keys()}" 439 | ) 440 | 441 | def prepare_latents( 442 | self, 443 | batch_size: int, 444 | num_channels_latents: 32, 445 | height: int = 720, 446 | width: int = 1280, 447 | num_frames: int = 129, 448 | dtype: Optional[torch.dtype] = None, 449 | device: Optional[torch.device] = None, 450 | generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, 451 | latents: Optional[torch.Tensor] = None, 452 | ) -> torch.Tensor: 453 | if latents is not None: 454 | return latents.to(device=device, dtype=dtype) 455 | 456 | shape = ( 457 | batch_size, 458 | num_channels_latents, 459 | num_frames, 460 | int(height) // self.vae_scale_factor_spatial, 461 | int(width) // self.vae_scale_factor_spatial, 462 | ) 463 | if isinstance(generator, list) and len(generator) != batch_size: 464 | raise ValueError( 465 | f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" 466 | f" size of {batch_size}. Make sure the batch size matches the length of the generators." 467 | ) 468 | 469 | latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) 470 | return latents 471 | 472 | def enable_vae_slicing(self): 473 | r""" 474 | Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to 475 | compute decoding in several steps. This is useful to save some memory and allow larger batch sizes. 476 | """ 477 | self.vae.enable_slicing() 478 | 479 | def disable_vae_slicing(self): 480 | r""" 481 | Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to 482 | computing decoding in one step. 483 | """ 484 | self.vae.disable_slicing() 485 | 486 | def enable_vae_tiling(self): 487 | r""" 488 | Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to 489 | compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow 490 | processing larger images. 491 | """ 492 | self.vae.enable_tiling() 493 | 494 | def disable_vae_tiling(self): 495 | r""" 496 | Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to 497 | computing decoding in one step. 498 | """ 499 | self.vae.disable_tiling() 500 | 501 | @property 502 | def guidance_scale(self): 503 | return self._guidance_scale 504 | 505 | @property 506 | def do_spatio_temporal_guidance(self): 507 | return self._stg_scale > 0.0 508 | 509 | @property 510 | def num_timesteps(self): 511 | return self._num_timesteps 512 | 513 | @property 514 | def attention_kwargs(self): 515 | return self._attention_kwargs 516 | 517 | @property 518 | def current_timestep(self): 519 | return self._current_timestep 520 | 521 | @property 522 | def interrupt(self): 523 | return self._interrupt 524 | 525 | @torch.no_grad() 526 | @replace_example_docstring(EXAMPLE_DOC_STRING) 527 | def __call__( 528 | self, 529 | prompt: Union[str, List[str]] = None, 530 | prompt_2: Union[str, List[str]] = None, 531 | height: int = 720, 532 | width: int = 1280, 533 | num_frames: int = 129, 534 | num_inference_steps: int = 50, 535 | sigmas: List[float] = None, 536 | guidance_scale: float = 6.0, 537 | num_videos_per_prompt: Optional[int] = 1, 538 | generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, 539 | latents: Optional[torch.Tensor] = None, 540 | prompt_embeds: Optional[torch.Tensor] = None, 541 | pooled_prompt_embeds: Optional[torch.Tensor] = None, 542 | prompt_attention_mask: Optional[torch.Tensor] = None, 543 | output_type: Optional[str] = "pil", 544 | return_dict: bool = True, 545 | attention_kwargs: Optional[Dict[str, Any]] = None, 546 | callback_on_step_end: Optional[ 547 | Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks] 548 | ] = None, 549 | callback_on_step_end_tensor_inputs: List[str] = ["latents"], 550 | prompt_template: Dict[str, Any] = DEFAULT_PROMPT_TEMPLATE, 551 | max_sequence_length: int = 256, 552 | stg_applied_layers_idx: Optional[List[int]] = [2], 553 | stg_scale: Optional[float] = 0.0, 554 | do_rescaling: Optional[bool] = False, 555 | ): 556 | r""" 557 | The call function to the pipeline for generation. 558 | 559 | Args: 560 | prompt (`str` or `List[str]`, *optional*): 561 | The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. 562 | instead. 563 | prompt_2 (`str` or `List[str]`, *optional*): 564 | The prompt or prompts to be sent to `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is 565 | will be used instead. 566 | height (`int`, defaults to `720`): 567 | The height in pixels of the generated image. 568 | width (`int`, defaults to `1280`): 569 | The width in pixels of the generated image. 570 | num_frames (`int`, defaults to `129`): 571 | The number of frames in the generated video. 572 | num_inference_steps (`int`, defaults to `50`): 573 | The number of denoising steps. More denoising steps usually lead to a higher quality image at the 574 | expense of slower inference. 575 | sigmas (`List[float]`, *optional*): 576 | Custom sigmas to use for the denoising process with schedulers which support a `sigmas` argument in 577 | their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is passed 578 | will be used. 579 | guidance_scale (`float`, defaults to `6.0`): 580 | Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). 581 | `guidance_scale` is defined as `w` of equation 2. of [Imagen 582 | Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > 583 | 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, 584 | usually at the expense of lower image quality. Note that the only available HunyuanVideo model is 585 | CFG-distilled, which means that traditional guidance between unconditional and conditional latent is 586 | not applied. 587 | num_videos_per_prompt (`int`, *optional*, defaults to 1): 588 | The number of images to generate per prompt. 589 | generator (`torch.Generator` or `List[torch.Generator]`, *optional*): 590 | A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make 591 | generation deterministic. 592 | latents (`torch.Tensor`, *optional*): 593 | Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image 594 | generation. Can be used to tweak the same generation with different prompts. If not provided, a latents 595 | tensor is generated by sampling using the supplied random `generator`. 596 | prompt_embeds (`torch.Tensor`, *optional*): 597 | Pre-generated text embeddings. Can be used to easily tweak text inputs (prompt weighting). If not 598 | provided, text embeddings are generated from the `prompt` input argument. 599 | output_type (`str`, *optional*, defaults to `"pil"`): 600 | The output format of the generated image. Choose between `PIL.Image` or `np.array`. 601 | return_dict (`bool`, *optional*, defaults to `True`): 602 | Whether or not to return a [`HunyuanVideoPipelineOutput`] instead of a plain tuple. 603 | attention_kwargs (`dict`, *optional*): 604 | A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under 605 | `self.processor` in 606 | [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). 607 | clip_skip (`int`, *optional*): 608 | Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that 609 | the output of the pre-final layer will be used for computing the prompt embeddings. 610 | callback_on_step_end (`Callable`, `PipelineCallback`, `MultiPipelineCallbacks`, *optional*): 611 | A function or a subclass of `PipelineCallback` or `MultiPipelineCallbacks` that is called at the end of 612 | each denoising step during the inference. with the following arguments: `callback_on_step_end(self: 613 | DiffusionPipeline, step: int, timestep: int, callback_kwargs: Dict)`. `callback_kwargs` will include a 614 | list of all tensors as specified by `callback_on_step_end_tensor_inputs`. 615 | callback_on_step_end_tensor_inputs (`List`, *optional*): 616 | The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list 617 | will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the 618 | `._callback_tensor_inputs` attribute of your pipeline class. 619 | 620 | Examples: 621 | 622 | Returns: 623 | [`~HunyuanVideoPipelineOutput`] or `tuple`: 624 | If `return_dict` is `True`, [`HunyuanVideoPipelineOutput`] is returned, otherwise a `tuple` is returned 625 | where the first element is a list with the generated images and the second element is a list of `bool`s 626 | indicating whether the corresponding generated image contains "not-safe-for-work" (nsfw) content. 627 | """ 628 | 629 | if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)): 630 | callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs 631 | 632 | # 1. Check inputs. Raise error if not correct 633 | self.check_inputs( 634 | prompt, 635 | prompt_2, 636 | height, 637 | width, 638 | prompt_embeds, 639 | callback_on_step_end_tensor_inputs, 640 | prompt_template, 641 | ) 642 | 643 | self._stg_scale = stg_scale 644 | self._guidance_scale = guidance_scale 645 | self._attention_kwargs = attention_kwargs 646 | self._current_timestep = None 647 | self._interrupt = False 648 | 649 | device = self._execution_device 650 | 651 | # 2. Define call parameters 652 | if prompt is not None and isinstance(prompt, str): 653 | batch_size = 1 654 | elif prompt is not None and isinstance(prompt, list): 655 | batch_size = len(prompt) 656 | else: 657 | batch_size = prompt_embeds.shape[0] 658 | 659 | # 3. Encode input prompt 660 | prompt_embeds, pooled_prompt_embeds, prompt_attention_mask = self.encode_prompt( 661 | prompt=prompt, 662 | prompt_2=prompt_2, 663 | prompt_template=prompt_template, 664 | num_videos_per_prompt=num_videos_per_prompt, 665 | prompt_embeds=prompt_embeds, 666 | pooled_prompt_embeds=pooled_prompt_embeds, 667 | prompt_attention_mask=prompt_attention_mask, 668 | device=device, 669 | max_sequence_length=max_sequence_length, 670 | ) 671 | 672 | transformer_dtype = self.transformer.dtype 673 | prompt_embeds = prompt_embeds.to(transformer_dtype) 674 | prompt_attention_mask = prompt_attention_mask.to(transformer_dtype) 675 | if pooled_prompt_embeds is not None: 676 | pooled_prompt_embeds = pooled_prompt_embeds.to(transformer_dtype) 677 | 678 | # 4. Prepare timesteps 679 | sigmas = np.linspace(1.0, 0.0, num_inference_steps + 1)[:-1] if sigmas is None else sigmas 680 | timesteps, num_inference_steps = retrieve_timesteps( 681 | self.scheduler, 682 | num_inference_steps, 683 | device, 684 | sigmas=sigmas, 685 | ) 686 | 687 | # 5. Prepare latent variables 688 | num_channels_latents = self.transformer.config.in_channels 689 | num_latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1 690 | latents = self.prepare_latents( 691 | batch_size * num_videos_per_prompt, 692 | num_channels_latents, 693 | height, 694 | width, 695 | num_latent_frames, 696 | torch.float32, 697 | device, 698 | generator, 699 | latents, 700 | ) 701 | 702 | # 6. Prepare guidance condition 703 | guidance = torch.tensor([guidance_scale] * latents.shape[0], dtype=transformer_dtype, device=device) * 1000.0 704 | 705 | # 7. Denoising loop 706 | num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order 707 | self._num_timesteps = len(timesteps) 708 | 709 | with self.progress_bar(total=num_inference_steps) as progress_bar: 710 | for i, t in enumerate(timesteps): 711 | if self.interrupt: 712 | continue 713 | 714 | self._current_timestep = t 715 | latent_model_input = latents.to(transformer_dtype) 716 | # broadcast to batch dimension in a way that's compatible with ONNX/Core ML 717 | timestep = t.expand(latents.shape[0]).to(latents.dtype) 718 | 719 | if self.do_spatio_temporal_guidance: 720 | for i in stg_applied_layers_idx: 721 | self.transformer.transformer_blocks[i].forward = types.MethodType(forward_without_stg, self.transformer.transformer_blocks[i]) 722 | 723 | noise_pred = self.transformer( 724 | hidden_states=latent_model_input, 725 | timestep=timestep, 726 | encoder_hidden_states=prompt_embeds, 727 | encoder_attention_mask=prompt_attention_mask, 728 | pooled_projections=pooled_prompt_embeds, 729 | guidance=guidance, 730 | attention_kwargs=attention_kwargs, 731 | return_dict=False, 732 | )[0] 733 | 734 | if self.do_spatio_temporal_guidance: 735 | for i in stg_applied_layers_idx: 736 | self.transformer.transformer_blocks[i].forward = types.MethodType(forward_with_stg, self.transformer.transformer_blocks[i]) 737 | 738 | noise_pred_perturb = self.transformer( 739 | hidden_states=latent_model_input, 740 | timestep=timestep, 741 | encoder_hidden_states=prompt_embeds, 742 | encoder_attention_mask=prompt_attention_mask, 743 | pooled_projections=pooled_prompt_embeds, 744 | guidance=guidance, 745 | attention_kwargs=attention_kwargs, 746 | return_dict=False, 747 | )[0] 748 | noise_pred = noise_pred + self._stg_scale * (noise_pred - noise_pred_perturb) 749 | 750 | if do_rescaling: 751 | rescaling_scale = 0.7 752 | factor = noise_pred_text.std() / noise_pred.std() 753 | factor = rescaling_scale * factor + (1 - rescaling_scale) 754 | noise_pred = noise_pred * factor 755 | 756 | # compute the previous noisy sample x_t -> x_t-1 757 | latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0] 758 | 759 | if callback_on_step_end is not None: 760 | callback_kwargs = {} 761 | for k in callback_on_step_end_tensor_inputs: 762 | callback_kwargs[k] = locals()[k] 763 | callback_outputs = callback_on_step_end(self, i, t, callback_kwargs) 764 | 765 | latents = callback_outputs.pop("latents", latents) 766 | prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds) 767 | 768 | # call the callback, if provided 769 | if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): 770 | progress_bar.update() 771 | 772 | if XLA_AVAILABLE: 773 | xm.mark_step() 774 | 775 | self._current_timestep = None 776 | 777 | if not output_type == "latent": 778 | latents = latents.to(self.vae.dtype) / self.vae.config.scaling_factor 779 | video = self.vae.decode(latents, return_dict=False)[0] 780 | video = self.video_processor.postprocess_video(video, output_type=output_type) 781 | else: 782 | video = latents 783 | 784 | # Offload all models 785 | self.maybe_free_model_hooks() 786 | 787 | if not return_dict: 788 | return (video,) 789 | 790 | return HunyuanVideoPipelineOutput(frames=video) -------------------------------------------------------------------------------- /LTXVideo/inference.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | from diffusers import LTXPipeline 4 | from pipeline_stg_ltx import LTXSTGPipeline 5 | from diffusers.utils import export_to_video 6 | 7 | ckpt_path = "Lightricks/LTX-Video" 8 | pipe = LTXSTGPipeline.from_pretrained(ckpt_path, torch_dtype=torch.bfloat16) 9 | pipe.to("cuda") 10 | 11 | prompt = "A woman with light skin, wearing a blue jacket and a black hat with a veil, looks down and to her right, then back up as she speaks; she has brown hair styled in an updo, light brown eyebrows, and is wearing a white collared shirt under her jacket; the camera remains stationary on her face as she speaks; the background is out of focus, but shows trees and people in period clothing; the scene is captured in real-life footage." 12 | negative_prompt = "worst quality, inconsistent motion, blurry, jittery, distorted" 13 | 14 | stg_mode = "STG" 15 | stg_applied_layers_idx = [19] # 0~27 16 | stg_scale = 1.0 # 0.0 for CFG 17 | do_rescaling = False 18 | 19 | video = pipe( 20 | prompt=prompt, 21 | negative_prompt=negative_prompt, 22 | width=480, 23 | height=480, 24 | num_frames=81, 25 | num_inference_steps=50, 26 | generator=torch.manual_seed(42), 27 | stg_applied_layers_idx=stg_applied_layers_idx, 28 | stg_scale=stg_scale, 29 | do_rescaling=do_rescaling, 30 | ).frames[0] 31 | 32 | if stg_scale == 0: 33 | video_name = f"CFG_rescale_{do_rescaling}.mp4" 34 | else: 35 | layers_str = "_".join(map(str, stg_applied_layers_idx)) 36 | video_name = f"{stg_mode}_scale_{stg_scale}_layers_{layers_str}_rescale_{do_rescaling}.mp4" 37 | 38 | # Save video to samples directory 39 | sample_dir = "samples" 40 | os.makedirs(sample_dir, exist_ok=True) 41 | video_path = os.path.join(sample_dir, video_name) 42 | export_to_video(video, video_path, fps=24) 43 | 44 | print(f"Video saved to {video_path}") 45 | -------------------------------------------------------------------------------- /LTXVideo/pipeline_stg_ltx.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Lightricks and The HuggingFace Team. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import types 16 | import inspect 17 | from typing import Any, Callable, Dict, List, Optional, Union, Tuple 18 | 19 | import numpy as np 20 | import torch 21 | from transformers import T5EncoderModel, T5TokenizerFast 22 | 23 | from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback 24 | from diffusers.loaders import FromSingleFileMixin, LTXVideoLoraLoaderMixin 25 | from diffusers.models.autoencoders import AutoencoderKLLTXVideo 26 | from diffusers.models.transformers import LTXVideoTransformer3DModel 27 | from diffusers.schedulers import FlowMatchEulerDiscreteScheduler 28 | from diffusers.utils import is_torch_xla_available, logging, replace_example_docstring 29 | from diffusers.utils.torch_utils import randn_tensor 30 | from diffusers.video_processor import VideoProcessor 31 | from diffusers.pipelines.pipeline_utils import DiffusionPipeline 32 | from diffusers.pipelines.ltx.pipeline_output import LTXPipelineOutput 33 | 34 | 35 | if is_torch_xla_available(): 36 | import torch_xla.core.xla_model as xm 37 | 38 | XLA_AVAILABLE = True 39 | else: 40 | XLA_AVAILABLE = False 41 | 42 | logger = logging.get_logger(__name__) # pylint: disable=invalid-name 43 | 44 | EXAMPLE_DOC_STRING = """ 45 | Examples: 46 | ```py 47 | >>> import torch 48 | >>> from diffusers import LTXPipeline 49 | >>> from diffusers.utils import export_to_video 50 | 51 | >>> pipe = LTXPipeline.from_pretrained("Lightricks/LTX-Video", torch_dtype=torch.bfloat16) 52 | >>> pipe.to("cuda") 53 | 54 | >>> prompt = "A woman with long brown hair and light skin smiles at another woman with long blonde hair. The woman with brown hair wears a black jacket and has a small, barely noticeable mole on her right cheek. The camera angle is a close-up, focused on the woman with brown hair's face. The lighting is warm and natural, likely from the setting sun, casting a soft glow on the scene. The scene appears to be real-life footage" 55 | >>> negative_prompt = "worst quality, inconsistent motion, blurry, jittery, distorted" 56 | 57 | >>> video = pipe( 58 | ... prompt=prompt, 59 | ... negative_prompt=negative_prompt, 60 | ... width=704, 61 | ... height=480, 62 | ... num_frames=161, 63 | ... num_inference_steps=50, 64 | ... ).frames[0] 65 | >>> export_to_video(video, "output.mp4", fps=24) 66 | ``` 67 | """ 68 | 69 | 70 | def forward_with_stg( 71 | self, 72 | hidden_states: torch.Tensor, 73 | encoder_hidden_states: torch.Tensor, 74 | temb: torch.Tensor, 75 | image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, 76 | encoder_attention_mask: Optional[torch.Tensor] = None, 77 | ) -> torch.Tensor: 78 | 79 | num_prompt = hidden_states.size(0) // 3 80 | hidden_states_ptb = hidden_states[2*num_prompt:] 81 | encoder_hidden_states_ptb = encoder_hidden_states[2*num_prompt:] 82 | 83 | batch_size = hidden_states.size(0) 84 | norm_hidden_states = self.norm1(hidden_states) 85 | 86 | num_ada_params = self.scale_shift_table.shape[0] 87 | ada_values = self.scale_shift_table[None, None] + temb.reshape(batch_size, temb.size(1), num_ada_params, -1) 88 | shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = ada_values.unbind(dim=2) 89 | norm_hidden_states = norm_hidden_states * (1 + scale_msa) + shift_msa 90 | 91 | attn_hidden_states = self.attn1( 92 | hidden_states=norm_hidden_states, 93 | encoder_hidden_states=None, 94 | image_rotary_emb=image_rotary_emb, 95 | ) 96 | hidden_states = hidden_states + attn_hidden_states * gate_msa 97 | 98 | attn_hidden_states = self.attn2( 99 | hidden_states, 100 | encoder_hidden_states=encoder_hidden_states, 101 | image_rotary_emb=None, 102 | attention_mask=encoder_attention_mask, 103 | ) 104 | hidden_states = hidden_states + attn_hidden_states 105 | norm_hidden_states = self.norm2(hidden_states) * (1 + scale_mlp) + shift_mlp 106 | 107 | ff_output = self.ff(norm_hidden_states) 108 | hidden_states = hidden_states + ff_output * gate_mlp 109 | 110 | hidden_states[2*num_prompt:] = hidden_states_ptb 111 | encoder_hidden_states[2*num_prompt:] = encoder_hidden_states_ptb 112 | 113 | return hidden_states 114 | 115 | 116 | # Copied from diffusers.pipelines.flux.pipeline_flux.calculate_shift 117 | def calculate_shift( 118 | image_seq_len, 119 | base_seq_len: int = 256, 120 | max_seq_len: int = 4096, 121 | base_shift: float = 0.5, 122 | max_shift: float = 1.16, 123 | ): 124 | m = (max_shift - base_shift) / (max_seq_len - base_seq_len) 125 | b = base_shift - m * base_seq_len 126 | mu = image_seq_len * m + b 127 | return mu 128 | 129 | 130 | # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps 131 | def retrieve_timesteps( 132 | scheduler, 133 | num_inference_steps: Optional[int] = None, 134 | device: Optional[Union[str, torch.device]] = None, 135 | timesteps: Optional[List[int]] = None, 136 | sigmas: Optional[List[float]] = None, 137 | **kwargs, 138 | ): 139 | r""" 140 | Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles 141 | custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. 142 | 143 | Args: 144 | scheduler (`SchedulerMixin`): 145 | The scheduler to get timesteps from. 146 | num_inference_steps (`int`): 147 | The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps` 148 | must be `None`. 149 | device (`str` or `torch.device`, *optional*): 150 | The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. 151 | timesteps (`List[int]`, *optional*): 152 | Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed, 153 | `num_inference_steps` and `sigmas` must be `None`. 154 | sigmas (`List[float]`, *optional*): 155 | Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed, 156 | `num_inference_steps` and `timesteps` must be `None`. 157 | 158 | Returns: 159 | `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the 160 | second element is the number of inference steps. 161 | """ 162 | if timesteps is not None and sigmas is not None: 163 | raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values") 164 | if timesteps is not None: 165 | accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) 166 | if not accepts_timesteps: 167 | raise ValueError( 168 | f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" 169 | f" timestep schedules. Please check whether you are using the correct scheduler." 170 | ) 171 | scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) 172 | timesteps = scheduler.timesteps 173 | num_inference_steps = len(timesteps) 174 | elif sigmas is not None: 175 | accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) 176 | if not accept_sigmas: 177 | raise ValueError( 178 | f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" 179 | f" sigmas schedules. Please check whether you are using the correct scheduler." 180 | ) 181 | scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs) 182 | timesteps = scheduler.timesteps 183 | num_inference_steps = len(timesteps) 184 | else: 185 | scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) 186 | timesteps = scheduler.timesteps 187 | return timesteps, num_inference_steps 188 | 189 | 190 | class LTXSTGPipeline(DiffusionPipeline, FromSingleFileMixin, LTXVideoLoraLoaderMixin): 191 | r""" 192 | Pipeline for text-to-video generation. 193 | 194 | Reference: https://github.com/Lightricks/LTX-Video 195 | 196 | Args: 197 | transformer ([`LTXVideoTransformer3DModel`]): 198 | Conditional Transformer architecture to denoise the encoded video latents. 199 | scheduler ([`FlowMatchEulerDiscreteScheduler`]): 200 | A scheduler to be used in combination with `transformer` to denoise the encoded image latents. 201 | vae ([`AutoencoderKLLTXVideo`]): 202 | Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations. 203 | text_encoder ([`T5EncoderModel`]): 204 | [T5](https://huggingface.co/docs/transformers/en/model_doc/t5#transformers.T5EncoderModel), specifically 205 | the [google/t5-v1_1-xxl](https://huggingface.co/google/t5-v1_1-xxl) variant. 206 | tokenizer (`CLIPTokenizer`): 207 | Tokenizer of class 208 | [CLIPTokenizer](https://huggingface.co/docs/transformers/en/model_doc/clip#transformers.CLIPTokenizer). 209 | tokenizer (`T5TokenizerFast`): 210 | Second Tokenizer of class 211 | [T5TokenizerFast](https://huggingface.co/docs/transformers/en/model_doc/t5#transformers.T5TokenizerFast). 212 | """ 213 | 214 | model_cpu_offload_seq = "text_encoder->transformer->vae" 215 | _optional_components = [] 216 | _callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"] 217 | 218 | def __init__( 219 | self, 220 | scheduler: FlowMatchEulerDiscreteScheduler, 221 | vae: AutoencoderKLLTXVideo, 222 | text_encoder: T5EncoderModel, 223 | tokenizer: T5TokenizerFast, 224 | transformer: LTXVideoTransformer3DModel, 225 | ): 226 | super().__init__() 227 | 228 | self.register_modules( 229 | vae=vae, 230 | text_encoder=text_encoder, 231 | tokenizer=tokenizer, 232 | transformer=transformer, 233 | scheduler=scheduler, 234 | ) 235 | 236 | self.vae_spatial_compression_ratio = ( 237 | self.vae.spatial_compression_ratio if getattr(self, "vae", None) is not None else 32 238 | ) 239 | self.vae_temporal_compression_ratio = ( 240 | self.vae.temporal_compression_ratio if getattr(self, "vae", None) is not None else 8 241 | ) 242 | self.transformer_spatial_patch_size = ( 243 | self.transformer.config.patch_size if getattr(self, "transformer", None) is not None else 1 244 | ) 245 | self.transformer_temporal_patch_size = ( 246 | self.transformer.config.patch_size_t if getattr(self, "transformer") is not None else 1 247 | ) 248 | 249 | self.video_processor = VideoProcessor(vae_scale_factor=self.vae_spatial_compression_ratio) 250 | self.tokenizer_max_length = ( 251 | self.tokenizer.model_max_length if getattr(self, "tokenizer", None) is not None else 128 252 | ) 253 | 254 | def _get_t5_prompt_embeds( 255 | self, 256 | prompt: Union[str, List[str]] = None, 257 | num_videos_per_prompt: int = 1, 258 | max_sequence_length: int = 128, 259 | device: Optional[torch.device] = None, 260 | dtype: Optional[torch.dtype] = None, 261 | ): 262 | device = device or self._execution_device 263 | dtype = dtype or self.text_encoder.dtype 264 | 265 | prompt = [prompt] if isinstance(prompt, str) else prompt 266 | batch_size = len(prompt) 267 | 268 | text_inputs = self.tokenizer( 269 | prompt, 270 | padding="max_length", 271 | max_length=max_sequence_length, 272 | truncation=True, 273 | add_special_tokens=True, 274 | return_tensors="pt", 275 | ) 276 | text_input_ids = text_inputs.input_ids 277 | prompt_attention_mask = text_inputs.attention_mask 278 | prompt_attention_mask = prompt_attention_mask.bool().to(device) 279 | 280 | untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids 281 | 282 | if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids): 283 | removed_text = self.tokenizer.batch_decode(untruncated_ids[:, max_sequence_length - 1 : -1]) 284 | logger.warning( 285 | "The following part of your input was truncated because `max_sequence_length` is set to " 286 | f" {max_sequence_length} tokens: {removed_text}" 287 | ) 288 | 289 | prompt_embeds = self.text_encoder(text_input_ids.to(device))[0] 290 | prompt_embeds = prompt_embeds.to(dtype=dtype, device=device) 291 | 292 | # duplicate text embeddings for each generation per prompt, using mps friendly method 293 | _, seq_len, _ = prompt_embeds.shape 294 | prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1) 295 | prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1) 296 | 297 | prompt_attention_mask = prompt_attention_mask.view(batch_size, -1) 298 | prompt_attention_mask = prompt_attention_mask.repeat(num_videos_per_prompt, 1) 299 | 300 | return prompt_embeds, prompt_attention_mask 301 | 302 | # Copied from diffusers.pipelines.mochi.pipeline_mochi.MochiPipeline.encode_prompt with 256->128 303 | def encode_prompt( 304 | self, 305 | prompt: Union[str, List[str]], 306 | negative_prompt: Optional[Union[str, List[str]]] = None, 307 | do_classifier_free_guidance: bool = True, 308 | num_videos_per_prompt: int = 1, 309 | prompt_embeds: Optional[torch.Tensor] = None, 310 | negative_prompt_embeds: Optional[torch.Tensor] = None, 311 | prompt_attention_mask: Optional[torch.Tensor] = None, 312 | negative_prompt_attention_mask: Optional[torch.Tensor] = None, 313 | max_sequence_length: int = 128, 314 | device: Optional[torch.device] = None, 315 | dtype: Optional[torch.dtype] = None, 316 | ): 317 | r""" 318 | Encodes the prompt into text encoder hidden states. 319 | 320 | Args: 321 | prompt (`str` or `List[str]`, *optional*): 322 | prompt to be encoded 323 | negative_prompt (`str` or `List[str]`, *optional*): 324 | The prompt or prompts not to guide the image generation. If not defined, one has to pass 325 | `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is 326 | less than `1`). 327 | do_classifier_free_guidance (`bool`, *optional*, defaults to `True`): 328 | Whether to use classifier free guidance or not. 329 | num_videos_per_prompt (`int`, *optional*, defaults to 1): 330 | Number of videos that should be generated per prompt. torch device to place the resulting embeddings on 331 | prompt_embeds (`torch.Tensor`, *optional*): 332 | Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not 333 | provided, text embeddings will be generated from `prompt` input argument. 334 | negative_prompt_embeds (`torch.Tensor`, *optional*): 335 | Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt 336 | weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input 337 | argument. 338 | device: (`torch.device`, *optional*): 339 | torch device 340 | dtype: (`torch.dtype`, *optional*): 341 | torch dtype 342 | """ 343 | device = device or self._execution_device 344 | 345 | prompt = [prompt] if isinstance(prompt, str) else prompt 346 | if prompt is not None: 347 | batch_size = len(prompt) 348 | else: 349 | batch_size = prompt_embeds.shape[0] 350 | 351 | if prompt_embeds is None: 352 | prompt_embeds, prompt_attention_mask = self._get_t5_prompt_embeds( 353 | prompt=prompt, 354 | num_videos_per_prompt=num_videos_per_prompt, 355 | max_sequence_length=max_sequence_length, 356 | device=device, 357 | dtype=dtype, 358 | ) 359 | 360 | if do_classifier_free_guidance and negative_prompt_embeds is None: 361 | negative_prompt = negative_prompt or "" 362 | negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt 363 | 364 | if prompt is not None and type(prompt) is not type(negative_prompt): 365 | raise TypeError( 366 | f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" 367 | f" {type(prompt)}." 368 | ) 369 | elif batch_size != len(negative_prompt): 370 | raise ValueError( 371 | f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" 372 | f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" 373 | " the batch size of `prompt`." 374 | ) 375 | 376 | negative_prompt_embeds, negative_prompt_attention_mask = self._get_t5_prompt_embeds( 377 | prompt=negative_prompt, 378 | num_videos_per_prompt=num_videos_per_prompt, 379 | max_sequence_length=max_sequence_length, 380 | device=device, 381 | dtype=dtype, 382 | ) 383 | 384 | return prompt_embeds, prompt_attention_mask, negative_prompt_embeds, negative_prompt_attention_mask 385 | 386 | def check_inputs( 387 | self, 388 | prompt, 389 | height, 390 | width, 391 | callback_on_step_end_tensor_inputs=None, 392 | prompt_embeds=None, 393 | negative_prompt_embeds=None, 394 | prompt_attention_mask=None, 395 | negative_prompt_attention_mask=None, 396 | ): 397 | if height % 32 != 0 or width % 32 != 0: 398 | raise ValueError(f"`height` and `width` have to be divisible by 32 but are {height} and {width}.") 399 | 400 | if callback_on_step_end_tensor_inputs is not None and not all( 401 | k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs 402 | ): 403 | raise ValueError( 404 | f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}" 405 | ) 406 | 407 | if prompt is not None and prompt_embeds is not None: 408 | raise ValueError( 409 | f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" 410 | " only forward one of the two." 411 | ) 412 | elif prompt is None and prompt_embeds is None: 413 | raise ValueError( 414 | "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." 415 | ) 416 | elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): 417 | raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") 418 | 419 | if prompt_embeds is not None and prompt_attention_mask is None: 420 | raise ValueError("Must provide `prompt_attention_mask` when specifying `prompt_embeds`.") 421 | 422 | if negative_prompt_embeds is not None and negative_prompt_attention_mask is None: 423 | raise ValueError("Must provide `negative_prompt_attention_mask` when specifying `negative_prompt_embeds`.") 424 | 425 | if prompt_embeds is not None and negative_prompt_embeds is not None: 426 | if prompt_embeds.shape != negative_prompt_embeds.shape: 427 | raise ValueError( 428 | "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" 429 | f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" 430 | f" {negative_prompt_embeds.shape}." 431 | ) 432 | if prompt_attention_mask.shape != negative_prompt_attention_mask.shape: 433 | raise ValueError( 434 | "`prompt_attention_mask` and `negative_prompt_attention_mask` must have the same shape when passed directly, but" 435 | f" got: `prompt_attention_mask` {prompt_attention_mask.shape} != `negative_prompt_attention_mask`" 436 | f" {negative_prompt_attention_mask.shape}." 437 | ) 438 | 439 | @staticmethod 440 | def _pack_latents(latents: torch.Tensor, patch_size: int = 1, patch_size_t: int = 1) -> torch.Tensor: 441 | # Unpacked latents of shape are [B, C, F, H, W] are patched into tokens of shape [B, C, F // p_t, p_t, H // p, p, W // p, p]. 442 | # The patch dimensions are then permuted and collapsed into the channel dimension of shape: 443 | # [B, F // p_t * H // p * W // p, C * p_t * p * p] (an ndim=3 tensor). 444 | # dim=0 is the batch size, dim=1 is the effective video sequence length, dim=2 is the effective number of input features 445 | batch_size, num_channels, num_frames, height, width = latents.shape 446 | post_patch_num_frames = num_frames // patch_size_t 447 | post_patch_height = height // patch_size 448 | post_patch_width = width // patch_size 449 | latents = latents.reshape( 450 | batch_size, 451 | -1, 452 | post_patch_num_frames, 453 | patch_size_t, 454 | post_patch_height, 455 | patch_size, 456 | post_patch_width, 457 | patch_size, 458 | ) 459 | latents = latents.permute(0, 2, 4, 6, 1, 3, 5, 7).flatten(4, 7).flatten(1, 3) 460 | return latents 461 | 462 | @staticmethod 463 | def _unpack_latents( 464 | latents: torch.Tensor, num_frames: int, height: int, width: int, patch_size: int = 1, patch_size_t: int = 1 465 | ) -> torch.Tensor: 466 | # Packed latents of shape [B, S, D] (S is the effective video sequence length, D is the effective feature dimensions) 467 | # are unpacked and reshaped into a video tensor of shape [B, C, F, H, W]. This is the inverse operation of 468 | # what happens in the `_pack_latents` method. 469 | batch_size = latents.size(0) 470 | latents = latents.reshape(batch_size, num_frames, height, width, -1, patch_size_t, patch_size, patch_size) 471 | latents = latents.permute(0, 4, 1, 5, 2, 6, 3, 7).flatten(6, 7).flatten(4, 5).flatten(2, 3) 472 | return latents 473 | 474 | @staticmethod 475 | def _normalize_latents( 476 | latents: torch.Tensor, latents_mean: torch.Tensor, latents_std: torch.Tensor, scaling_factor: float = 1.0 477 | ) -> torch.Tensor: 478 | # Normalize latents across the channel dimension [B, C, F, H, W] 479 | latents_mean = latents_mean.view(1, -1, 1, 1, 1).to(latents.device, latents.dtype) 480 | latents_std = latents_std.view(1, -1, 1, 1, 1).to(latents.device, latents.dtype) 481 | latents = (latents - latents_mean) * scaling_factor / latents_std 482 | return latents 483 | 484 | @staticmethod 485 | def _denormalize_latents( 486 | latents: torch.Tensor, latents_mean: torch.Tensor, latents_std: torch.Tensor, scaling_factor: float = 1.0 487 | ) -> torch.Tensor: 488 | # Denormalize latents across the channel dimension [B, C, F, H, W] 489 | latents_mean = latents_mean.view(1, -1, 1, 1, 1).to(latents.device, latents.dtype) 490 | latents_std = latents_std.view(1, -1, 1, 1, 1).to(latents.device, latents.dtype) 491 | latents = latents * latents_std / scaling_factor + latents_mean 492 | return latents 493 | 494 | def prepare_latents( 495 | self, 496 | batch_size: int = 1, 497 | num_channels_latents: int = 128, 498 | height: int = 512, 499 | width: int = 704, 500 | num_frames: int = 161, 501 | dtype: Optional[torch.dtype] = None, 502 | device: Optional[torch.device] = None, 503 | generator: Optional[torch.Generator] = None, 504 | latents: Optional[torch.Tensor] = None, 505 | ) -> torch.Tensor: 506 | if latents is not None: 507 | return latents.to(device=device, dtype=dtype) 508 | 509 | height = height // self.vae_spatial_compression_ratio 510 | width = width // self.vae_spatial_compression_ratio 511 | num_frames = (num_frames - 1) // self.vae_temporal_compression_ratio + 1 512 | 513 | shape = (batch_size, num_channels_latents, num_frames, height, width) 514 | 515 | if isinstance(generator, list) and len(generator) != batch_size: 516 | raise ValueError( 517 | f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" 518 | f" size of {batch_size}. Make sure the batch size matches the length of the generators." 519 | ) 520 | 521 | latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) 522 | latents = self._pack_latents( 523 | latents, self.transformer_spatial_patch_size, self.transformer_temporal_patch_size 524 | ) 525 | return latents 526 | 527 | @property 528 | def guidance_scale(self): 529 | return self._guidance_scale 530 | 531 | @property 532 | def do_classifier_free_guidance(self): 533 | return self._guidance_scale > 1.0 534 | 535 | @property 536 | def do_spatio_temporal_guidance(self): 537 | return self._stg_scale > 0.0 538 | 539 | @property 540 | def num_timesteps(self): 541 | return self._num_timesteps 542 | 543 | @property 544 | def attention_kwargs(self): 545 | return self._attention_kwargs 546 | 547 | @property 548 | def interrupt(self): 549 | return self._interrupt 550 | 551 | @torch.no_grad() 552 | @replace_example_docstring(EXAMPLE_DOC_STRING) 553 | def __call__( 554 | self, 555 | prompt: Union[str, List[str]] = None, 556 | negative_prompt: Optional[Union[str, List[str]]] = None, 557 | height: int = 512, 558 | width: int = 704, 559 | num_frames: int = 161, 560 | frame_rate: int = 25, 561 | num_inference_steps: int = 50, 562 | timesteps: List[int] = None, 563 | guidance_scale: float = 3, 564 | num_videos_per_prompt: Optional[int] = 1, 565 | generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, 566 | latents: Optional[torch.Tensor] = None, 567 | prompt_embeds: Optional[torch.Tensor] = None, 568 | prompt_attention_mask: Optional[torch.Tensor] = None, 569 | negative_prompt_embeds: Optional[torch.Tensor] = None, 570 | negative_prompt_attention_mask: Optional[torch.Tensor] = None, 571 | decode_timestep: Union[float, List[float]] = 0.0, 572 | decode_noise_scale: Optional[Union[float, List[float]]] = None, 573 | output_type: Optional[str] = "pil", 574 | return_dict: bool = True, 575 | attention_kwargs: Optional[Dict[str, Any]] = None, 576 | callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None, 577 | callback_on_step_end_tensor_inputs: List[str] = ["latents"], 578 | max_sequence_length: int = 128, 579 | stg_applied_layers_idx: Optional[List[int]] = [19], 580 | stg_scale: Optional[float] = 1.0, 581 | do_rescaling: Optional[bool] = False, 582 | ): 583 | r""" 584 | Function invoked when calling the pipeline for generation. 585 | 586 | Args: 587 | prompt (`str` or `List[str]`, *optional*): 588 | The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. 589 | instead. 590 | height (`int`, defaults to `512`): 591 | The height in pixels of the generated image. This is set to 480 by default for the best results. 592 | width (`int`, defaults to `704`): 593 | The width in pixels of the generated image. This is set to 848 by default for the best results. 594 | num_frames (`int`, defaults to `161`): 595 | The number of video frames to generate 596 | num_inference_steps (`int`, *optional*, defaults to 50): 597 | The number of denoising steps. More denoising steps usually lead to a higher quality image at the 598 | expense of slower inference. 599 | timesteps (`List[int]`, *optional*): 600 | Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument 601 | in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is 602 | passed will be used. Must be in descending order. 603 | guidance_scale (`float`, defaults to `3 `): 604 | Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). 605 | `guidance_scale` is defined as `w` of equation 2. of [Imagen 606 | Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > 607 | 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, 608 | usually at the expense of lower image quality. 609 | num_videos_per_prompt (`int`, *optional*, defaults to 1): 610 | The number of videos to generate per prompt. 611 | generator (`torch.Generator` or `List[torch.Generator]`, *optional*): 612 | One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html) 613 | to make generation deterministic. 614 | latents (`torch.Tensor`, *optional*): 615 | Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image 616 | generation. Can be used to tweak the same generation with different prompts. If not provided, a latents 617 | tensor will ge generated by sampling using the supplied random `generator`. 618 | prompt_embeds (`torch.Tensor`, *optional*): 619 | Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not 620 | provided, text embeddings will be generated from `prompt` input argument. 621 | prompt_attention_mask (`torch.Tensor`, *optional*): 622 | Pre-generated attention mask for text embeddings. 623 | negative_prompt_embeds (`torch.FloatTensor`, *optional*): 624 | Pre-generated negative text embeddings. For PixArt-Sigma this negative prompt should be "". If not 625 | provided, negative_prompt_embeds will be generated from `negative_prompt` input argument. 626 | negative_prompt_attention_mask (`torch.FloatTensor`, *optional*): 627 | Pre-generated attention mask for negative text embeddings. 628 | decode_timestep (`float`, defaults to `0.0`): 629 | The timestep at which generated video is decoded. 630 | decode_noise_scale (`float`, defaults to `None`): 631 | The interpolation factor between random noise and denoised latents at the decode timestep. 632 | output_type (`str`, *optional*, defaults to `"pil"`): 633 | The output format of the generate image. Choose between 634 | [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`. 635 | return_dict (`bool`, *optional*, defaults to `True`): 636 | Whether or not to return a [`~pipelines.ltx.LTXPipelineOutput`] instead of a plain tuple. 637 | attention_kwargs (`dict`, *optional*): 638 | A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under 639 | `self.processor` in 640 | [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). 641 | callback_on_step_end (`Callable`, *optional*): 642 | A function that calls at the end of each denoising steps during the inference. The function is called 643 | with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int, 644 | callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by 645 | `callback_on_step_end_tensor_inputs`. 646 | callback_on_step_end_tensor_inputs (`List`, *optional*): 647 | The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list 648 | will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the 649 | `._callback_tensor_inputs` attribute of your pipeline class. 650 | max_sequence_length (`int` defaults to `128 `): 651 | Maximum sequence length to use with the `prompt`. 652 | 653 | Examples: 654 | 655 | Returns: 656 | [`~pipelines.ltx.LTXPipelineOutput`] or `tuple`: 657 | If `return_dict` is `True`, [`~pipelines.ltx.LTXPipelineOutput`] is returned, otherwise a `tuple` is 658 | returned where the first element is a list with the generated images. 659 | """ 660 | 661 | if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)): 662 | callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs 663 | 664 | # 1. Check inputs. Raise error if not correct 665 | self.check_inputs( 666 | prompt=prompt, 667 | height=height, 668 | width=width, 669 | callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs, 670 | prompt_embeds=prompt_embeds, 671 | negative_prompt_embeds=negative_prompt_embeds, 672 | prompt_attention_mask=prompt_attention_mask, 673 | negative_prompt_attention_mask=negative_prompt_attention_mask, 674 | ) 675 | 676 | self._stg_scale = stg_scale 677 | self._guidance_scale = guidance_scale 678 | self._attention_kwargs = attention_kwargs 679 | self._interrupt = False 680 | 681 | if self.do_spatio_temporal_guidance: 682 | for i in stg_applied_layers_idx: 683 | self.transformer.transformer_blocks[i].forward = types.MethodType(forward_with_stg, self.transformer.transformer_blocks[i]) 684 | 685 | # 2. Define call parameters 686 | if prompt is not None and isinstance(prompt, str): 687 | batch_size = 1 688 | elif prompt is not None and isinstance(prompt, list): 689 | batch_size = len(prompt) 690 | else: 691 | batch_size = prompt_embeds.shape[0] 692 | 693 | device = self._execution_device 694 | 695 | # 3. Prepare text embeddings 696 | ( 697 | prompt_embeds, 698 | prompt_attention_mask, 699 | negative_prompt_embeds, 700 | negative_prompt_attention_mask, 701 | ) = self.encode_prompt( 702 | prompt=prompt, 703 | negative_prompt=negative_prompt, 704 | do_classifier_free_guidance=self.do_classifier_free_guidance, 705 | num_videos_per_prompt=num_videos_per_prompt, 706 | prompt_embeds=prompt_embeds, 707 | negative_prompt_embeds=negative_prompt_embeds, 708 | prompt_attention_mask=prompt_attention_mask, 709 | negative_prompt_attention_mask=negative_prompt_attention_mask, 710 | max_sequence_length=max_sequence_length, 711 | device=device, 712 | ) 713 | if self.do_classifier_free_guidance and not self.do_spatio_temporal_guidance: 714 | prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0) 715 | prompt_attention_mask = torch.cat([negative_prompt_attention_mask, prompt_attention_mask], dim=0) 716 | elif self.do_classifier_free_guidance and self.do_spatio_temporal_guidance: 717 | prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds, prompt_embeds], dim=0) 718 | prompt_attention_mask = torch.cat([negative_prompt_attention_mask, prompt_attention_mask, prompt_attention_mask], dim=0) 719 | 720 | # 4. Prepare latent variables 721 | num_channels_latents = self.transformer.config.in_channels 722 | latents = self.prepare_latents( 723 | batch_size * num_videos_per_prompt, 724 | num_channels_latents, 725 | height, 726 | width, 727 | num_frames, 728 | torch.float32, 729 | device, 730 | generator, 731 | latents, 732 | ) 733 | 734 | # 5. Prepare timesteps 735 | latent_num_frames = (num_frames - 1) // self.vae_temporal_compression_ratio + 1 736 | latent_height = height // self.vae_spatial_compression_ratio 737 | latent_width = width // self.vae_spatial_compression_ratio 738 | video_sequence_length = latent_num_frames * latent_height * latent_width 739 | sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps) 740 | mu = calculate_shift( 741 | video_sequence_length, 742 | self.scheduler.config.get("base_image_seq_len", 256), 743 | self.scheduler.config.get("max_image_seq_len", 4096), 744 | self.scheduler.config.get("base_shift", 0.5), 745 | self.scheduler.config.get("max_shift", 1.16), 746 | ) 747 | timesteps, num_inference_steps = retrieve_timesteps( 748 | self.scheduler, 749 | num_inference_steps, 750 | device, 751 | timesteps, 752 | sigmas=sigmas, 753 | mu=mu, 754 | ) 755 | num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) 756 | self._num_timesteps = len(timesteps) 757 | 758 | # 6. Prepare micro-conditions 759 | latent_frame_rate = frame_rate / self.vae_temporal_compression_ratio 760 | rope_interpolation_scale = ( 761 | 1 / latent_frame_rate, 762 | self.vae_spatial_compression_ratio, 763 | self.vae_spatial_compression_ratio, 764 | ) 765 | 766 | # 7. Denoising loop 767 | with self.progress_bar(total=num_inference_steps) as progress_bar: 768 | for i, t in enumerate(timesteps): 769 | if self.interrupt: 770 | continue 771 | 772 | if self.do_classifier_free_guidance and not self.do_spatio_temporal_guidance: 773 | latent_model_input = torch.cat([latents] * 2) 774 | elif self.do_classifier_free_guidance and self.do_spatio_temporal_guidance: 775 | latent_model_input = torch.cat([latents] * 3) 776 | else: 777 | latent_model_input = latents 778 | 779 | latent_model_input = latent_model_input.to(prompt_embeds.dtype) 780 | 781 | # broadcast to batch dimension in a way that's compatible with ONNX/Core ML 782 | timestep = t.expand(latent_model_input.shape[0]) 783 | 784 | noise_pred = self.transformer( 785 | hidden_states=latent_model_input, 786 | encoder_hidden_states=prompt_embeds, 787 | timestep=timestep, 788 | encoder_attention_mask=prompt_attention_mask, 789 | num_frames=latent_num_frames, 790 | height=latent_height, 791 | width=latent_width, 792 | rope_interpolation_scale=rope_interpolation_scale, 793 | attention_kwargs=attention_kwargs, 794 | return_dict=False, 795 | )[0] 796 | noise_pred = noise_pred.float() 797 | 798 | if self.do_classifier_free_guidance and not self.do_spatio_temporal_guidance: 799 | noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) 800 | noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond) 801 | elif self.do_classifier_free_guidance and self.do_spatio_temporal_guidance: 802 | noise_pred_uncond, noise_pred_text, noise_pred_perturb = noise_pred.chunk(3) 803 | noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond) \ 804 | + self._stg_scale * (noise_pred_text - noise_pred_perturb) 805 | 806 | if do_rescaling: 807 | rescaling_scale = 0.7 808 | factor = noise_pred_text.std() / noise_pred.std() 809 | factor = rescaling_scale * factor + (1 - rescaling_scale) 810 | noise_pred = noise_pred * factor 811 | 812 | # compute the previous noisy sample x_t -> x_t-1 813 | latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0] 814 | 815 | if callback_on_step_end is not None: 816 | callback_kwargs = {} 817 | for k in callback_on_step_end_tensor_inputs: 818 | callback_kwargs[k] = locals()[k] 819 | callback_outputs = callback_on_step_end(self, i, t, callback_kwargs) 820 | 821 | latents = callback_outputs.pop("latents", latents) 822 | prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds) 823 | 824 | # call the callback, if provided 825 | if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): 826 | progress_bar.update() 827 | 828 | if XLA_AVAILABLE: 829 | xm.mark_step() 830 | 831 | if output_type == "latent": 832 | video = latents 833 | else: 834 | latents = self._unpack_latents( 835 | latents, 836 | latent_num_frames, 837 | latent_height, 838 | latent_width, 839 | self.transformer_spatial_patch_size, 840 | self.transformer_temporal_patch_size, 841 | ) 842 | latents = self._denormalize_latents( 843 | latents, self.vae.latents_mean, self.vae.latents_std, self.vae.config.scaling_factor 844 | ) 845 | latents = latents.to(prompt_embeds.dtype) 846 | 847 | if not self.vae.config.timestep_conditioning: 848 | timestep = None 849 | else: 850 | noise = randn_tensor(latents.shape, generator=generator, device=device, dtype=latents.dtype) 851 | if not isinstance(decode_timestep, list): 852 | decode_timestep = [decode_timestep] * batch_size 853 | if decode_noise_scale is None: 854 | decode_noise_scale = decode_timestep 855 | elif not isinstance(decode_noise_scale, list): 856 | decode_noise_scale = [decode_noise_scale] * batch_size 857 | 858 | timestep = torch.tensor(decode_timestep, device=device, dtype=latents.dtype) 859 | decode_noise_scale = torch.tensor(decode_noise_scale, device=device, dtype=latents.dtype)[ 860 | :, None, None, None, None 861 | ] 862 | latents = (1 - decode_noise_scale) * latents + decode_noise_scale * noise 863 | 864 | video = self.vae.decode(latents, timestep, return_dict=False)[0] 865 | video = self.video_processor.postprocess_video(video, output_type=output_type) 866 | 867 | # Offload all models 868 | self.maybe_free_model_hooks() 869 | 870 | if not return_dict: 871 | return (video,) 872 | 873 | return LTXPipelineOutput(frames=video) -------------------------------------------------------------------------------- /Mochi/inference.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from diffusers import MochiPipeline 3 | from pipeline_stg_mochi import MochiSTGPipeline 4 | from diffusers.utils import export_to_video 5 | import os 6 | 7 | # Ensure the samples directory exists 8 | os.makedirs("samples", exist_ok=True) 9 | 10 | ckpt_path = "genmo/mochi-1-preview" 11 | # Load the pipeline 12 | pipe = MochiSTGPipeline.from_pretrained(ckpt_path, variant="bf16", torch_dtype=torch.bfloat16) 13 | 14 | # Enable memory savings 15 | # pipe.enable_model_cpu_offload() 16 | # pipe.enable_vae_tiling() 17 | pipe = pipe.to("cuda") 18 | 19 | #--------Option--------# 20 | prompt = "A close-up of a beautiful woman's face with colored powder exploding around her, creating an abstract splash of vibrant hues, realistic style." 21 | stg_applied_layers_idx = [34] 22 | stg_mode = "STG" 23 | stg_scale = 1.0 # 0.0 for CFG (default) 24 | do_rescaling = False # False (default) 25 | #----------------------# 26 | 27 | # Generate video frames 28 | frames = pipe( 29 | prompt, 30 | height=480, 31 | width=480, 32 | num_frames=81, 33 | stg_applied_layers_idx=stg_applied_layers_idx, 34 | stg_scale=stg_scale, 35 | generator = torch.Generator().manual_seed(42), 36 | do_rescaling=do_rescaling, 37 | ).frames[0] 38 | 39 | # Construct the video filename 40 | if stg_scale == 0: 41 | video_name = f"CFG_rescale_{do_rescaling}.mp4" 42 | else: 43 | layers_str = "_".join(map(str, stg_applied_layers_idx)) 44 | video_name = f"{stg_mode}_scale_{stg_scale}_layers_{layers_str}_rescale_{do_rescaling}.mp4" 45 | 46 | # Save video to samples directory 47 | video_path = os.path.join("samples", video_name) 48 | export_to_video(frames, video_path, fps=30) 49 | 50 | print(f"Video saved to {video_path}") 51 | -------------------------------------------------------------------------------- /Mochi/pipeline_stg_mochi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Genmo and The HuggingFace Team. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import types 16 | import inspect 17 | from typing import Any, Callable, Dict, List, Optional, Union, Tuple 18 | 19 | import numpy as np 20 | import torch 21 | from transformers import T5EncoderModel, T5TokenizerFast 22 | 23 | from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback 24 | from diffusers.loaders import Mochi1LoraLoaderMixin 25 | from diffusers.models import AutoencoderKLMochi, MochiTransformer3DModel 26 | from diffusers.schedulers import FlowMatchEulerDiscreteScheduler 27 | from diffusers.utils import ( 28 | is_torch_xla_available, 29 | logging, 30 | replace_example_docstring, 31 | ) 32 | from diffusers.utils.torch_utils import randn_tensor 33 | from diffusers.video_processor import VideoProcessor 34 | from diffusers.pipelines.pipeline_utils import DiffusionPipeline 35 | from diffusers.pipelines.mochi.pipeline_output import MochiPipelineOutput 36 | 37 | 38 | 39 | if is_torch_xla_available(): 40 | import torch_xla.core.xla_model as xm 41 | 42 | XLA_AVAILABLE = True 43 | else: 44 | XLA_AVAILABLE = False 45 | 46 | 47 | logger = logging.get_logger(__name__) # pylint: disable=invalid-name 48 | 49 | EXAMPLE_DOC_STRING = """ 50 | Examples: 51 | ```py 52 | >>> import torch 53 | >>> from diffusers import MochiPipeline 54 | >>> from diffusers.utils import export_to_video 55 | 56 | >>> pipe = MochiPipeline.from_pretrained("genmo/mochi-1-preview", torch_dtype=torch.bfloat16) 57 | >>> pipe.enable_model_cpu_offload() 58 | >>> pipe.enable_vae_tiling() 59 | >>> prompt = "Close-up of a chameleon's eye, with its scaly skin changing color. Ultra high resolution 4k." 60 | >>> frames = pipe(prompt, num_inference_steps=28, guidance_scale=3.5).frames[0] 61 | >>> export_to_video(frames, "mochi.mp4") 62 | ``` 63 | """ 64 | 65 | 66 | def forward_with_stg( 67 | self, 68 | hidden_states: torch.Tensor, 69 | encoder_hidden_states: torch.Tensor, 70 | temb: torch.Tensor, 71 | encoder_attention_mask: torch.Tensor, 72 | image_rotary_emb: Optional[torch.Tensor] = None, 73 | ) -> Tuple[torch.Tensor, torch.Tensor]: 74 | 75 | num_prompt = hidden_states.size(0) // 3 76 | hidden_states_ptb = hidden_states[2*num_prompt:] 77 | encoder_hidden_states_ptb = encoder_hidden_states[2*num_prompt:] 78 | 79 | norm_hidden_states, gate_msa, scale_mlp, gate_mlp = self.norm1(hidden_states, temb) 80 | 81 | if not self.context_pre_only: 82 | norm_encoder_hidden_states, enc_gate_msa, enc_scale_mlp, enc_gate_mlp = self.norm1_context( 83 | encoder_hidden_states, temb 84 | ) 85 | else: 86 | norm_encoder_hidden_states = self.norm1_context(encoder_hidden_states, temb) 87 | 88 | attn_hidden_states, context_attn_hidden_states = self.attn1( 89 | hidden_states=norm_hidden_states, 90 | encoder_hidden_states=norm_encoder_hidden_states, 91 | image_rotary_emb=image_rotary_emb, 92 | attention_mask=encoder_attention_mask, 93 | ) 94 | 95 | hidden_states = hidden_states + self.norm2(attn_hidden_states, torch.tanh(gate_msa).unsqueeze(1)) 96 | norm_hidden_states = self.norm3(hidden_states, (1 + scale_mlp.unsqueeze(1).to(torch.float32))) 97 | ff_output = self.ff(norm_hidden_states) 98 | hidden_states = hidden_states + self.norm4(ff_output, torch.tanh(gate_mlp).unsqueeze(1)) 99 | 100 | if not self.context_pre_only: 101 | encoder_hidden_states = encoder_hidden_states + self.norm2_context( 102 | context_attn_hidden_states, torch.tanh(enc_gate_msa).unsqueeze(1) 103 | ) 104 | norm_encoder_hidden_states = self.norm3_context( 105 | encoder_hidden_states, (1 + enc_scale_mlp.unsqueeze(1).to(torch.float32)) 106 | ) 107 | context_ff_output = self.ff_context(norm_encoder_hidden_states) 108 | encoder_hidden_states = encoder_hidden_states + self.norm4_context( 109 | context_ff_output, torch.tanh(enc_gate_mlp).unsqueeze(1) 110 | ) 111 | 112 | hidden_states[2*num_prompt:] = hidden_states_ptb 113 | encoder_hidden_states[2*num_prompt:] = encoder_hidden_states_ptb 114 | 115 | return hidden_states, encoder_hidden_states 116 | 117 | # from: https://github.com/genmoai/models/blob/075b6e36db58f1242921deff83a1066887b9c9e1/src/mochi_preview/infer.py#L77 118 | def linear_quadratic_schedule(num_steps, threshold_noise, linear_steps=None): 119 | if linear_steps is None: 120 | linear_steps = num_steps // 2 121 | linear_sigma_schedule = [i * threshold_noise / linear_steps for i in range(linear_steps)] 122 | threshold_noise_step_diff = linear_steps - threshold_noise * num_steps 123 | quadratic_steps = num_steps - linear_steps 124 | quadratic_coef = threshold_noise_step_diff / (linear_steps * quadratic_steps**2) 125 | linear_coef = threshold_noise / linear_steps - 2 * threshold_noise_step_diff / (quadratic_steps**2) 126 | const = quadratic_coef * (linear_steps**2) 127 | quadratic_sigma_schedule = [ 128 | quadratic_coef * (i**2) + linear_coef * i + const for i in range(linear_steps, num_steps) 129 | ] 130 | sigma_schedule = linear_sigma_schedule + quadratic_sigma_schedule 131 | sigma_schedule = [1.0 - x for x in sigma_schedule] 132 | return sigma_schedule 133 | 134 | 135 | # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps 136 | def retrieve_timesteps( 137 | scheduler, 138 | num_inference_steps: Optional[int] = None, 139 | device: Optional[Union[str, torch.device]] = None, 140 | timesteps: Optional[List[int]] = None, 141 | sigmas: Optional[List[float]] = None, 142 | **kwargs, 143 | ): 144 | r""" 145 | Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles 146 | custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. 147 | 148 | Args: 149 | scheduler (`SchedulerMixin`): 150 | The scheduler to get timesteps from. 151 | num_inference_steps (`int`): 152 | The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps` 153 | must be `None`. 154 | device (`str` or `torch.device`, *optional*): 155 | The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. 156 | timesteps (`List[int]`, *optional*): 157 | Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed, 158 | `num_inference_steps` and `sigmas` must be `None`. 159 | sigmas (`List[float]`, *optional*): 160 | Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed, 161 | `num_inference_steps` and `timesteps` must be `None`. 162 | 163 | Returns: 164 | `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the 165 | second element is the number of inference steps. 166 | """ 167 | if timesteps is not None and sigmas is not None: 168 | raise Value_orgError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom value") 169 | if timesteps is not None: 170 | accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) 171 | if not accepts_timesteps: 172 | raise Value_orgError( 173 | f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" 174 | f" timestep schedules. Please check whether you are using the correct scheduler." 175 | ) 176 | scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) 177 | timesteps = scheduler.timesteps 178 | num_inference_steps = len(timesteps) 179 | elif sigmas is not None: 180 | accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) 181 | if not accept_sigmas: 182 | raise Value_orgError( 183 | f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" 184 | f" sigmas schedules. Please check whether you are using the correct scheduler." 185 | ) 186 | scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs) 187 | timesteps = scheduler.timesteps 188 | num_inference_steps = len(timesteps) 189 | else: 190 | scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) 191 | timesteps = scheduler.timesteps 192 | return timesteps, num_inference_steps 193 | 194 | 195 | class MochiSTGPipeline(DiffusionPipeline, Mochi1LoraLoaderMixin): 196 | r""" 197 | The mochi pipeline for text-to-video generation. 198 | 199 | Reference: https://github.com/genmoai/models 200 | 201 | Args: 202 | transformer ([`MochiTransformer3DModel`]): 203 | Conditional Transformer architecture to denoise the encoded video latents. 204 | scheduler ([`FlowMatchEulerDiscreteScheduler`]): 205 | A scheduler to be used in combination with `transformer` to denoise the encoded image latents. 206 | vae ([`AutoencoderKLMochi`]): 207 | Variational Auto-Encoder (VAE) Model to encode and decode videos to and from latent representations. 208 | text_encoder ([`T5EncoderModel`]): 209 | [T5](https://huggingface.co/docs/transformers/en/model_doc/t5#transformers.T5EncoderModel), specifically 210 | the [google/t5-v1_1-xxl](https://huggingface.co/google/t5-v1_1-xxl) variant. 211 | tokenizer (`CLIPTokenizer`): 212 | Tokenizer of class 213 | [CLIPTokenizer](https://huggingface.co/docs/transformers/en/model_doc/clip#transformers.CLIPTokenizer). 214 | tokenizer (`T5TokenizerFast`): 215 | Second Tokenizer of class 216 | [T5TokenizerFast](https://huggingface.co/docs/transformers/en/model_doc/t5#transformers.T5TokenizerFast). 217 | """ 218 | 219 | model_cpu_offload_seq = "text_encoder->transformer->vae" 220 | _optional_components = [] 221 | _callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"] 222 | 223 | def __init__( 224 | self, 225 | scheduler: FlowMatchEulerDiscreteScheduler, 226 | vae: AutoencoderKLMochi, 227 | text_encoder: T5EncoderModel, 228 | tokenizer: T5TokenizerFast, 229 | transformer: MochiTransformer3DModel, 230 | force_zeros_for_empty_prompt: bool = False, 231 | ): 232 | super().__init__() 233 | 234 | self.register_modules( 235 | vae=vae, 236 | text_encoder=text_encoder, 237 | tokenizer=tokenizer, 238 | transformer=transformer, 239 | scheduler=scheduler, 240 | ) 241 | # TODO: determine these scaling factors from model parameters 242 | self.vae_spatial_scale_factor = 8 243 | self.vae_temporal_scale_factor = 6 244 | self.patch_size = 2 245 | 246 | self.video_processor = VideoProcessor(vae_scale_factor=self.vae_spatial_scale_factor) 247 | self.tokenizer_max_length = ( 248 | self.tokenizer.model_max_length if hasattr(self, "tokenizer") and self.tokenizer is not None else 256 249 | ) 250 | self.default_height = 480 251 | self.default_width = 848 252 | self.register_to_config(force_zeros_for_empty_prompt=force_zeros_for_empty_prompt) 253 | 254 | def _get_t5_prompt_embeds( 255 | self, 256 | prompt: Union[str, List[str]] = None, 257 | num_videos_per_prompt: int = 1, 258 | max_sequence_length: int = 256, 259 | device: Optional[torch.device] = None, 260 | dtype: Optional[torch.dtype] = None, 261 | ): 262 | device = device or self._execution_device 263 | dtype = dtype or self.text_encoder.dtype 264 | 265 | prompt = [prompt] if isinstance(prompt, str) else prompt 266 | batch_size = len(prompt) 267 | 268 | text_inputs = self.tokenizer( 269 | prompt, 270 | padding="max_length", 271 | max_length=max_sequence_length, 272 | truncation=True, 273 | add_special_tokens=True, 274 | return_tensors="pt", 275 | ) 276 | 277 | text_input_ids = text_inputs.input_ids 278 | prompt_attention_mask = text_inputs.attention_mask 279 | prompt_attention_mask = prompt_attention_mask.bool().to(device) 280 | 281 | # The original Mochi implementation zeros out empty negative prompts 282 | # but this can lead to overflow when placing the entire pipeline under the autocast context 283 | # adding this here so that we can enable zeroing prompts if necessary 284 | if self.config.force_zeros_for_empty_prompt and (prompt == "" or prompt[-1] == ""): 285 | text_input_ids = torch.zeros_like(text_input_ids, device=device) 286 | prompt_attention_mask = torch.zeros_like(prompt_attention_mask, dtype=torch.bool, device=device) 287 | 288 | untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids 289 | 290 | if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids): 291 | removed_text = self.tokenizer.batch_decode(untruncated_ids[:, max_sequence_length - 1 : -1]) 292 | logger.warning( 293 | "The following part of your input was truncated because `max_sequence_length` is set to " 294 | f" {max_sequence_length} tokens: {removed_text}" 295 | ) 296 | 297 | prompt_embeds = self.text_encoder(text_input_ids.to(device), attention_mask=prompt_attention_mask)[0] 298 | prompt_embeds = prompt_embeds.to(dtype=dtype, device=device) 299 | 300 | # duplicate text embeddings for each generation per prompt, using mps friendly method 301 | _, seq_len, _ = prompt_embeds.shape 302 | prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1) 303 | prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1) 304 | 305 | prompt_attention_mask = prompt_attention_mask.view(batch_size, -1) 306 | prompt_attention_mask = prompt_attention_mask.repeat(num_videos_per_prompt, 1) 307 | 308 | return prompt_embeds, prompt_attention_mask 309 | 310 | # Adapted from diffusers.pipelines.cogvideo.pipeline_cogvideox.CogVideoXPipeline.encode_prompt 311 | def encode_prompt( 312 | self, 313 | prompt: Union[str, List[str]], 314 | negative_prompt: Optional[Union[str, List[str]]] = None, 315 | do_classifier_free_guidance: bool = True, 316 | num_videos_per_prompt: int = 1, 317 | prompt_embeds: Optional[torch.Tensor] = None, 318 | negative_prompt_embeds: Optional[torch.Tensor] = None, 319 | prompt_attention_mask: Optional[torch.Tensor] = None, 320 | negative_prompt_attention_mask: Optional[torch.Tensor] = None, 321 | max_sequence_length: int = 256, 322 | device: Optional[torch.device] = None, 323 | dtype: Optional[torch.dtype] = None, 324 | ): 325 | r""" 326 | Encodes the prompt into text encoder hidden states. 327 | 328 | Args: 329 | prompt (`str` or `List[str]`, *optional*): 330 | prompt to be encoded 331 | negative_prompt (`str` or `List[str]`, *optional*): 332 | The prompt or prompts not to guide the image generation. If not defined, one has to pass 333 | `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is 334 | less than `1`). 335 | do_classifier_free_guidance (`bool`, *optional*, defaults to `True`): 336 | Whether to use classifier free guidance or not. 337 | num_videos_per_prompt (`int`, *optional*, defaults to 1): 338 | Number of videos that should be generated per prompt. torch device to place the resulting embeddings on 339 | prompt_embeds (`torch.Tensor`, *optional*): 340 | Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not 341 | provided, text embeddings will be generated from `prompt` input argument. 342 | negative_prompt_embeds (`torch.Tensor`, *optional*): 343 | Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt 344 | weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input 345 | argument. 346 | device: (`torch.device`, *optional*): 347 | torch device 348 | dtype: (`torch.dtype`, *optional*): 349 | torch dtype 350 | """ 351 | device = device or self._execution_device 352 | 353 | prompt = [prompt] if isinstance(prompt, str) else prompt 354 | if prompt is not None: 355 | batch_size = len(prompt) 356 | else: 357 | batch_size = prompt_embeds.shape[0] 358 | 359 | if prompt_embeds is None: 360 | prompt_embeds, prompt_attention_mask = self._get_t5_prompt_embeds( 361 | prompt=prompt, 362 | num_videos_per_prompt=num_videos_per_prompt, 363 | max_sequence_length=max_sequence_length, 364 | device=device, 365 | dtype=dtype, 366 | ) 367 | 368 | if do_classifier_free_guidance and negative_prompt_embeds is None: 369 | negative_prompt = negative_prompt or "" 370 | negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt 371 | 372 | if prompt is not None and type(prompt) is not type(negative_prompt): 373 | raise TypeError( 374 | f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" 375 | f" {type(prompt)}." 376 | ) 377 | elif batch_size != len(negative_prompt): 378 | raise Value_orgError( 379 | f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" 380 | f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" 381 | " the batch size of `prompt`." 382 | ) 383 | 384 | negative_prompt_embeds, negative_prompt_attention_mask = self._get_t5_prompt_embeds( 385 | prompt=negative_prompt, 386 | num_videos_per_prompt=num_videos_per_prompt, 387 | max_sequence_length=max_sequence_length, 388 | device=device, 389 | dtype=dtype, 390 | ) 391 | 392 | return prompt_embeds, prompt_attention_mask, negative_prompt_embeds, negative_prompt_attention_mask 393 | 394 | def check_inputs( 395 | self, 396 | prompt, 397 | height, 398 | width, 399 | callback_on_step_end_tensor_inputs=None, 400 | prompt_embeds=None, 401 | negative_prompt_embeds=None, 402 | prompt_attention_mask=None, 403 | negative_prompt_attention_mask=None, 404 | ): 405 | if height % 8 != 0 or width % 8 != 0: 406 | raise Value_orgError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.") 407 | 408 | if callback_on_step_end_tensor_inputs is not None and not all( 409 | k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs 410 | ): 411 | raise Value_orgError( 412 | f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}" 413 | ) 414 | 415 | if prompt is not None and prompt_embeds is not None: 416 | raise Value_orgError( 417 | f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" 418 | " only forward one of the two." 419 | ) 420 | elif prompt is None and prompt_embeds is None: 421 | raise Value_orgError( 422 | "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." 423 | ) 424 | elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): 425 | raise Value_orgError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") 426 | 427 | if prompt_embeds is not None and prompt_attention_mask is None: 428 | raise Value_orgError("Must provide `prompt_attention_mask` when specifying `prompt_embeds`.") 429 | 430 | if negative_prompt_embeds is not None and negative_prompt_attention_mask is None: 431 | raise Value_orgError("Must provide `negative_prompt_attention_mask` when specifying `negative_prompt_embeds`.") 432 | 433 | if prompt_embeds is not None and negative_prompt_embeds is not None: 434 | if prompt_embeds.shape != negative_prompt_embeds.shape: 435 | raise Value_orgError( 436 | "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" 437 | f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" 438 | f" {negative_prompt_embeds.shape}." 439 | ) 440 | if prompt_attention_mask.shape != negative_prompt_attention_mask.shape: 441 | raise Value_orgError( 442 | "`prompt_attention_mask` and `negative_prompt_attention_mask` must have the same shape when passed directly, but" 443 | f" got: `prompt_attention_mask` {prompt_attention_mask.shape} != `negative_prompt_attention_mask`" 444 | f" {negative_prompt_attention_mask.shape}." 445 | ) 446 | 447 | def enable_vae_slicing(self): 448 | r""" 449 | Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to 450 | compute decoding in several steps. This is useful to save some memory and allow larger batch sizes. 451 | """ 452 | self.vae.enable_slicing() 453 | 454 | def disable_vae_slicing(self): 455 | r""" 456 | Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to 457 | computing decoding in one step. 458 | """ 459 | self.vae.disable_slicing() 460 | 461 | def enable_vae_tiling(self): 462 | r""" 463 | Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to 464 | compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow 465 | processing larger images. 466 | """ 467 | self.vae.enable_tiling() 468 | 469 | def disable_vae_tiling(self): 470 | r""" 471 | Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to 472 | computing decoding in one step. 473 | """ 474 | self.vae.disable_tiling() 475 | 476 | def prepare_latents( 477 | self, 478 | batch_size, 479 | num_channels_latents, 480 | height, 481 | width, 482 | num_frames, 483 | dtype, 484 | device, 485 | generator, 486 | latents=None, 487 | ): 488 | height = height // self.vae_spatial_scale_factor 489 | width = width // self.vae_spatial_scale_factor 490 | num_frames = (num_frames - 1) // self.vae_temporal_scale_factor + 1 491 | 492 | shape = (batch_size, num_channels_latents, num_frames, height, width) 493 | 494 | if latents is not None: 495 | return latents.to(device=device, dtype=dtype) 496 | if isinstance(generator, list) and len(generator) != batch_size: 497 | raise Value_orgError( 498 | f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" 499 | f" size of {batch_size}. Make sure the batch size matches the length of the generators." 500 | ) 501 | 502 | latents = randn_tensor(shape, generator=generator, device=device, dtype=torch.float32) 503 | latents = latents.to(dtype) 504 | return latents 505 | 506 | @property 507 | def guidance_scale(self): 508 | return self._guidance_scale 509 | 510 | @property 511 | def do_classifier_free_guidance(self): 512 | return self._guidance_scale > 1.0 513 | 514 | @property 515 | def do_spatio_temporal_guidance(self): 516 | return self._stg_scale > 0.0 517 | 518 | @property 519 | def num_timesteps(self): 520 | return self._num_timesteps 521 | 522 | @property 523 | def attention_kwargs(self): 524 | return self._attention_kwargs 525 | 526 | @property 527 | def current_timestep(self): 528 | return self._current_timestep 529 | 530 | @property 531 | def interrupt(self): 532 | return self._interrupt 533 | 534 | @torch.no_grad() 535 | @replace_example_docstring(EXAMPLE_DOC_STRING) 536 | def __call__( 537 | self, 538 | prompt: Union[str, List[str]] = None, 539 | negative_prompt: Optional[Union[str, List[str]]] = None, 540 | height: Optional[int] = None, 541 | width: Optional[int] = None, 542 | num_frames: int = 19, 543 | num_inference_steps: int = 64, 544 | timesteps: List[int] = None, 545 | guidance_scale: float = 4.5, 546 | num_videos_per_prompt: Optional[int] = 1, 547 | generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, 548 | latents: Optional[torch.Tensor] = None, 549 | prompt_embeds: Optional[torch.Tensor] = None, 550 | prompt_attention_mask: Optional[torch.Tensor] = None, 551 | negative_prompt_embeds: Optional[torch.Tensor] = None, 552 | negative_prompt_attention_mask: Optional[torch.Tensor] = None, 553 | output_type: Optional[str] = "pil", 554 | return_dict: bool = True, 555 | attention_kwargs: Optional[Dict[str, Any]] = None, 556 | callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None, 557 | callback_on_step_end_tensor_inputs: List[str] = ["latents"], 558 | max_sequence_length: int = 256, 559 | stg_applied_layers_idx: Optional[List[int]] = [34], 560 | stg_scale: Optional[float] = 0.0, 561 | do_rescaling: Optional[bool] = False, 562 | ): 563 | r""" 564 | Function invoked when calling the pipeline for generation. 565 | 566 | Args: 567 | prompt (`str` or `List[str]`, *optional*): 568 | The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. 569 | instead. 570 | height (`int`, *optional*, defaults to `self.default_height`): 571 | The height in pixels of the generated image. This is set to 480 by default for the best results. 572 | width (`int`, *optional*, defaults to `self.default_width`): 573 | The width in pixels of the generated image. This is set to 848 by default for the best results. 574 | num_frames (`int`, defaults to `19`): 575 | The number of video frames to generate 576 | num_inference_steps (`int`, *optional*, defaults to 50): 577 | The number of denoising steps. More denoising steps usually lead to a higher quality image at the 578 | expense of slower inference. 579 | timesteps (`List[int]`, *optional*): 580 | Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument 581 | in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is 582 | passed will be used. Must be in descending order. 583 | guidance_scale (`float`, defaults to `4.5`): 584 | Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). 585 | `guidance_scale` is defined as `w` of equation 2. of [Imagen 586 | Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > 587 | 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, 588 | usually at the expense of lower image quality. 589 | num_videos_per_prompt (`int`, *optional*, defaults to 1): 590 | The number of videos to generate per prompt. 591 | generator (`torch.Generator` or `List[torch.Generator]`, *optional*): 592 | One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html) 593 | to make generation deterministic. 594 | latents (`torch.Tensor`, *optional*): 595 | Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image 596 | generation. Can be used to tweak the same generation with different prompts. If not provided, a latents 597 | tensor will ge generated by sampling using the supplied random `generator`. 598 | prompt_embeds (`torch.Tensor`, *optional*): 599 | Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not 600 | provided, text embeddings will be generated from `prompt` input argument. 601 | prompt_attention_mask (`torch.Tensor`, *optional*): 602 | Pre-generated attention mask for text embeddings. 603 | negative_prompt_embeds (`torch.FloatTensor`, *optional*): 604 | Pre-generated negative text embeddings. For PixArt-Sigma this negative prompt should be "". If not 605 | provided, negative_prompt_embeds will be generated from `negative_prompt` input argument. 606 | negative_prompt_attention_mask (`torch.FloatTensor`, *optional*): 607 | Pre-generated attention mask for negative text embeddings. 608 | output_type (`str`, *optional*, defaults to `"pil"`): 609 | The output format of the generate image. Choose between 610 | [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`. 611 | return_dict (`bool`, *optional*, defaults to `True`): 612 | Whether or not to return a [`~pipelines.mochi.MochiPipelineOutput`] instead of a plain tuple. 613 | attention_kwargs (`dict`, *optional*): 614 | A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under 615 | `self.processor` in 616 | [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). 617 | callback_on_step_end (`Callable`, *optional*): 618 | A function that calls at the end of each denoising steps during the inference. The function is called 619 | with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int, 620 | callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by 621 | `callback_on_step_end_tensor_inputs`. 622 | callback_on_step_end_tensor_inputs (`List`, *optional*): 623 | The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list 624 | will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the 625 | `._callback_tensor_inputs` attribute of your pipeline class. 626 | max_sequence_length (`int` defaults to `256`): 627 | Maximum sequence length to use with the `prompt`. 628 | 629 | Examples: 630 | 631 | Returns: 632 | [`~pipelines.mochi.MochiPipelineOutput`] or `tuple`: 633 | If `return_dict` is `True`, [`~pipelines.mochi.MochiPipelineOutput`] is returned, otherwise a `tuple` 634 | is returned where the first element is a list with the generated images. 635 | """ 636 | 637 | if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)): 638 | callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs 639 | 640 | height = height or self.default_height 641 | width = width or self.default_width 642 | 643 | # 1. Check inputs. Raise error if not correct 644 | self.check_inputs( 645 | prompt=prompt, 646 | height=height, 647 | width=width, 648 | callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs, 649 | prompt_embeds=prompt_embeds, 650 | negative_prompt_embeds=negative_prompt_embeds, 651 | prompt_attention_mask=prompt_attention_mask, 652 | negative_prompt_attention_mask=negative_prompt_attention_mask, 653 | ) 654 | 655 | self._guidance_scale = guidance_scale 656 | self._stg_scale = stg_scale 657 | self._attention_kwargs = attention_kwargs 658 | self._current_timestep = None 659 | self._interrupt = False 660 | 661 | if self.do_spatio_temporal_guidance: 662 | for i in stg_applied_layers_idx: 663 | self.transformer.transformer_blocks[i].forward = types.MethodType(forward_with_stg, self.transformer.transformer_blocks[i]) 664 | 665 | # 2. Define call parameters 666 | if prompt is not None and isinstance(prompt, str): 667 | batch_size = 1 668 | elif prompt is not None and isinstance(prompt, list): 669 | batch_size = len(prompt) 670 | else: 671 | batch_size = prompt_embeds.shape[0] 672 | 673 | device = self._execution_device 674 | # 3. Prepare text embeddings 675 | ( 676 | prompt_embeds, 677 | prompt_attention_mask, 678 | negative_prompt_embeds, 679 | negative_prompt_attention_mask, 680 | ) = self.encode_prompt( 681 | prompt=prompt, 682 | negative_prompt=negative_prompt, 683 | do_classifier_free_guidance=self.do_classifier_free_guidance, 684 | num_videos_per_prompt=num_videos_per_prompt, 685 | prompt_embeds=prompt_embeds, 686 | negative_prompt_embeds=negative_prompt_embeds, 687 | prompt_attention_mask=prompt_attention_mask, 688 | negative_prompt_attention_mask=negative_prompt_attention_mask, 689 | max_sequence_length=max_sequence_length, 690 | device=device, 691 | ) 692 | # 4. Prepare latent variables 693 | num_channels_latents = self.transformer.config.in_channels 694 | latents = self.prepare_latents( 695 | batch_size * num_videos_per_prompt, 696 | num_channels_latents, 697 | height, 698 | width, 699 | num_frames, 700 | prompt_embeds.dtype, 701 | device, 702 | generator, 703 | latents, 704 | ) 705 | 706 | if self.do_classifier_free_guidance and not self.do_spatio_temporal_guidance: 707 | prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0) 708 | prompt_attention_mask = torch.cat([negative_prompt_attention_mask, prompt_attention_mask], dim=0) 709 | elif self.do_classifier_free_guidance and self.do_spatio_temporal_guidance: 710 | prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds, prompt_embeds], dim=0) 711 | prompt_attention_mask = torch.cat([negative_prompt_attention_mask, prompt_attention_mask, prompt_attention_mask], dim=0) 712 | 713 | # 5. Prepare timestep 714 | # from https://github.com/genmoai/models/blob/075b6e36db58f1242921deff83a1066887b9c9e1/src/mochi_preview/infer.py#L77 715 | threshold_noise = 0.025 716 | sigmas = linear_quadratic_schedule(num_inference_steps, threshold_noise) 717 | sigmas = np.array(sigmas) 718 | 719 | timesteps, num_inference_steps = retrieve_timesteps( 720 | self.scheduler, 721 | num_inference_steps, 722 | device, 723 | timesteps, 724 | sigmas, 725 | ) 726 | num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) 727 | self._num_timesteps = len(timesteps) 728 | 729 | # 6. Denoising loop 730 | with self.progress_bar(total=num_inference_steps) as progress_bar: 731 | for i, t in enumerate(timesteps): 732 | if self.interrupt: 733 | continue 734 | 735 | # Note: Mochi uses reversed timesteps. To ensure compatibility with methods like FasterCache, we need 736 | # to make sure we're using the correct non-reversed timestep value. 737 | self._current_timestep = 1000 - t 738 | if self.do_classifier_free_guidance and not self.do_spatio_temporal_guidance: 739 | latent_model_input = torch.cat([latents] * 2) 740 | elif self.do_classifier_free_guidance and self.do_spatio_temporal_guidance: 741 | latent_model_input = torch.cat([latents] * 3) 742 | else: 743 | latent_model_input = latents 744 | # broadcast to batch dimension in a way that's compatible with ONNX/Core ML 745 | timestep = t.expand(latent_model_input.shape[0]).to(latents.dtype) 746 | 747 | noise_pred = self.transformer( 748 | hidden_states=latent_model_input, 749 | encoder_hidden_states=prompt_embeds, 750 | timestep=timestep, 751 | encoder_attention_mask=prompt_attention_mask, 752 | attention_kwargs=attention_kwargs, 753 | return_dict=False, 754 | )[0] 755 | # Mochi CFG + Sampling runs in FP32 756 | noise_pred = noise_pred.to(torch.float32) 757 | 758 | if self.do_classifier_free_guidance and not self.do_spatio_temporal_guidance: 759 | noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) 760 | noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond) 761 | elif self.do_classifier_free_guidance and self.do_spatio_temporal_guidance: 762 | noise_pred_uncond, noise_pred_text, noise_pred_perturb = noise_pred.chunk(3) 763 | noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond) \ 764 | + self._stg_scale * (noise_pred_text - noise_pred_perturb) 765 | 766 | if do_rescaling: 767 | rescaling_scale = 0.7 768 | factor = noise_pred_text.std() / noise_pred.std() 769 | factor = rescaling_scale * factor + (1 - rescaling_scale) 770 | noise_pred = noise_pred * factor 771 | 772 | # compute the previous noisy sample x_t -> x_t-1 773 | latents_dtype = latents.dtype 774 | latents = self.scheduler.step(noise_pred, t, latents.to(torch.float32), return_dict=False)[0] 775 | latents = latents.to(latents_dtype) 776 | 777 | if latents.dtype != latents_dtype: 778 | if torch.backends.mps.is_available(): 779 | # some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272 780 | latents = latents.to(latents_dtype) 781 | 782 | if callback_on_step_end is not None: 783 | callback_kwargs = {} 784 | for k in callback_on_step_end_tensor_inputs: 785 | callback_kwargs[k] = locals()[k] 786 | callback_outputs = callback_on_step_end(self, i, t, callback_kwargs) 787 | 788 | latents = callback_outputs.pop("latents", latents) 789 | prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds) 790 | 791 | # call the callback, if provided 792 | if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): 793 | progress_bar.update() 794 | 795 | if XLA_AVAILABLE: 796 | xm.mark_step() 797 | 798 | self._current_timestep = None 799 | 800 | if output_type == "latent": 801 | video = latents 802 | else: 803 | # unscale/denormalize the latents 804 | # denormalize with the mean and std if available and not None 805 | has_latents_mean = hasattr(self.vae.config, "latents_mean") and self.vae.config.latents_mean is not None 806 | has_latents_std = hasattr(self.vae.config, "latents_std") and self.vae.config.latents_std is not None 807 | if has_latents_mean and has_latents_std: 808 | latents_mean = ( 809 | torch.tensor(self.vae.config.latents_mean).view(1, 12, 1, 1, 1).to(latents.device, latents.dtype) 810 | ) 811 | latents_std = ( 812 | torch.tensor(self.vae.config.latents_std).view(1, 12, 1, 1, 1).to(latents.device, latents.dtype) 813 | ) 814 | latents = latents * latents_std / self.vae.config.scaling_factor + latents_mean 815 | else: 816 | latents = latents / self.vae.config.scaling_factor 817 | 818 | video = self.vae.decode(latents, return_dict=False)[0] 819 | video = self.video_processor.postprocess_video(video, output_type=output_type) 820 | 821 | # Offload all models 822 | self.maybe_free_model_hooks() 823 | 824 | if not return_dict: 825 | return (video,) 826 | 827 | return MochiPipelineOutput(frames=video) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀[CVPR 2025] Spatiotemporal Skip Guidance for Enhanced Video Diffusion Sampling✨ 2 | 3 | ## 📑Paper 4 | - Arxiv: [Spatiotemporal Skip Guidance for Enhanced Video Diffusion Sampling](https://arxiv.org/abs/2411.18664) 5 | 6 | ## 🌐Project Page 7 | - [STG Project Page](https://junhahyung.github.io/STGuidance) 8 | 9 | ## 📰 News 10 | - **[2025.03.08]** 🚀 **STG is now integrated into the Diffusers community pipeline!** 11 | 👉 [**Check it out on Hugging Face**](https://github.com/huggingface/diffusers/tree/main/examples/community) 12 | 13 | - **[2025.02.07]** 🏆 **STG officially accepted to CVPR 2025!** 14 | 🎤 Stay tuned for our presentation at the conference. 15 | 16 | - **[2024.12.20]** 🔥 **STG added to LTXVideo’s official repository!** 17 | 📂 Now part of [**LTXVideo’s main repository**](https://github.com/Lightricks/ComfyUI-LTXVideo). 18 | 19 | - **[2024.12.19]** 🖥️ **ComfyUI STG support for LTXVideo!** 20 | 🎬 Implemented in [**ComfyUI**](https://github.com/Lightricks/ComfyUI-LTXVideo), enhancing **LTXVideo** support. 21 | 22 | ## 🎥Video Examples 23 | Below are example videos showcasing the enhanced video quality achieved through STG: 24 | 25 | ### Mochi 26 | 27 | 28 | https://github.com/user-attachments/assets/b8795d10-b7dd-4928-84b0-1335fac1af03 29 | 30 | 31 | 32 | 33 | https://github.com/user-attachments/assets/7eb5391c-f655-4e42-b704-df9b6125dea1 34 | 35 | ### HunyuanVideo 36 | 37 | 38 | https://github.com/user-attachments/assets/3ccd4a63-15e6-4473-b693-8b757b3ae6b1 39 | 40 | 41 | 42 | https://github.com/user-attachments/assets/492f43d0-c1bd-4941-b90b-8fe3d22a2e6b 43 | 44 | 45 | 46 | 47 | ### CogVideoX 48 | 49 | 50 | https://github.com/user-attachments/assets/adc5af40-e50d-4b00-b98b-8e88ee04bae8 51 | 52 | 53 | https://github.com/user-attachments/assets/fcb8a078-58a5-4e62-a55e-662a0b08216b 54 | 55 | 56 | ### SVD (Stable Video Diffusion) 57 | 58 | 59 | 60 | https://github.com/user-attachments/assets/5d11b8dc-e63d-4ac9-80d8-c81735fcf181 61 | 62 | 63 | 64 | https://github.com/user-attachments/assets/29afec1b-f137-48d4-b237-e2058431ccee 65 | 66 | 67 | ### LTX-Video 68 | 69 | 70 | https://github.com/user-attachments/assets/4cd722cd-c6e8-428d-8183-65e5954a930b 71 | 72 | 73 | 74 | 75 | 76 | ## 🗺️Start Guide 77 | 🧪**Diffusers-based codes** 78 | To run the test script, refer to the `inference.py` file in each folder. Below is an example using Mochi: 79 | 80 | ```python 81 | # inference.py 82 | import torch 83 | from diffusers import MochiPipeline 84 | from pipeline_stg_mochi import MochiSTGPipeline 85 | from diffusers.utils import export_to_video 86 | import os 87 | 88 | # Ensure the samples directory exists 89 | os.makedirs("samples", exist_ok=True) 90 | 91 | ckpt_path = "genmo/mochi-1-preview" 92 | # Load the pipeline 93 | pipe = MochiSTGPipeline.from_pretrained(ckpt_path, variant="bf16", torch_dtype=torch.bfloat16) 94 | 95 | # Enable memory savings 96 | # pipe.enable_model_cpu_offload() 97 | # pipe.enable_vae_tiling() 98 | pipe = pipe.to("cuda") 99 | 100 | #--------Option--------# 101 | prompt = "A close-up of a beautiful woman's face with colored powder exploding around her, creating an abstract splash of vibrant hues, realistic style." 102 | stg_applied_layers_idx = [34] 103 | stg_mode = "STG" 104 | stg_scale = 1.0 # 0.0 for CFG (default) 105 | do_rescaling = False # False (default) 106 | #----------------------# 107 | 108 | # Generate video frames 109 | frames = pipe( 110 | prompt, 111 | height=480, 112 | width=480, 113 | num_frames=81, 114 | stg_applied_layers_idx=stg_applied_layers_idx, 115 | stg_scale=stg_scale, 116 | generator = torch.Generator().manual_seed(42), 117 | do_rescaling=do_rescaling, 118 | ).frames[0] 119 | 120 | # Construct the video filename 121 | if stg_scale == 0: 122 | video_name = f"CFG_rescale_{do_rescaling}.mp4" 123 | else: 124 | layers_str = "_".join(map(str, stg_applied_layers_idx)) 125 | video_name = f"{stg_mode}_scale_{stg_scale}_layers_{layers_str}_rescale_{do_rescaling}.mp4" 126 | 127 | # Save video to samples directory 128 | video_path = os.path.join("samples", video_name) 129 | export_to_video(frames, video_path, fps=30) 130 | 131 | print(f"Video saved to {video_path}") 132 | ``` 133 | For details on memory efficiency, inference acceleration, and more, refer to the original pages below: 134 | - [Mochi](https://huggingface.co/genmo/mochi-1-preview) 135 | - [CogVideoX](https://huggingface.co/docs/diffusers/en/api/pipelines/cogvideox) 136 | - [HunyuanVideo](https://huggingface.co/docs/diffusers/main/api/pipelines/hunyuan_video) 137 | - [StableVideoDiffusion](https://huggingface.co/docs/diffusers/en/using-diffusers/svd) 138 | 139 | 140 | ## 🙏Acknowledgements 141 | This project is built upon the following works: 142 | - [Mochi](https://github.com/genmoai/mochi?tab=readme-ov-file) 143 | - [HunyuanVideo](https://github.com/Tencent/HunyuanVideo) 144 | - [LTX-Video](https://github.com/Lightricks/LTX-Video) 145 | - [diffusers](https://github.com/huggingface/diffusers) 146 | 147 | ## 📖 BibTeX 148 | 149 | ```bibtex 150 | @article{hyung2024spatiotemporal, 151 | title={Spatiotemporal Skip Guidance for Enhanced Video Diffusion Sampling}, 152 | author={Hyung, Junha and Kim, Kinam and Hong, Susung and Kim, Min-Jung and Choo, Jaegul}, 153 | journal={arXiv preprint arXiv:2411.18664}, 154 | year={2024} 155 | } 156 | 157 | 158 | -------------------------------------------------------------------------------- /Wan2.1/inference.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from diffusers.utils import export_to_video 3 | from diffusers import AutoencoderKLWan, WanPipeline 4 | from diffusers.schedulers.scheduling_unipc_multistep import UniPCMultistepScheduler 5 | 6 | # Available models: Wan-AI/Wan2.1-T2V-14B-Diffusers, Wan-AI/Wan2.1-T2V-1.3B-Diffusers 7 | model_id = "/home/nas4_user/kinamkim/checkpoint/Wan2.1-T2V-14B" 8 | vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32) 9 | pipe = WanPipeline.from_pretrained(model_id, vae=vae, torch_dtype=torch.bfloat16) 10 | flow_shift = 5.0 # 5.0 for 720P, 3.0 for 480P 11 | pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config, flow_shift=flow_shift) 12 | pipe.to("cuda") 13 | 14 | prompt = "A cat and a dog baking a cake together in a kitchen. The cat is carefully measuring flour, while the dog is stirring the batter with a wooden spoon. The kitchen is cozy, with sunlight streaming through the window." 15 | negative_prompt = "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards" 16 | 17 | output = pipe( 18 | prompt=prompt, 19 | negative_prompt=negative_prompt, 20 | height=720, 21 | width=1280, 22 | num_frames=81, 23 | guidance_scale=5.0, 24 | ).frames[0] 25 | export_to_video(output, "output.mp4", fps=16) -------------------------------------------------------------------------------- /Wan2.1/inference_low_memory.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from diffusers.utils import export_to_video 3 | from diffusers import AutoencoderKLWan 4 | from diffusers.schedulers.scheduling_unipc_multistep import UniPCMultistepScheduler 5 | from pipeline import WanSTGPipeline 6 | 7 | # Available models: Wan-AI/Wan2.1-T2V-14B-Diffusers, Wan-AI/Wan2.1-T2V-1.3B-Diffusers 8 | model_id = "/home/nas4_user/kinamkim/checkpoint/Wan2.1-T2V-1.3B-Diffusers" 9 | vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32) 10 | pipe = WanSTGPipeline.from_pretrained(model_id, vae=vae, torch_dtype=torch.bfloat16) 11 | flow_shift = 3.0 # 5.0 for 720P, 3.0 for 480P 12 | pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config, flow_shift=flow_shift) 13 | pipe.to("cuda") 14 | 15 | prompt = "A cat and a dog baking a cake together in a kitchen. The cat is carefully measuring flour, while the dog is stirring the batter with a wooden spoon. The kitchen is cozy, with sunlight streaming through the window." 16 | negative_prompt = "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards" 17 | 18 | output = pipe( 19 | prompt=prompt, 20 | negative_prompt=negative_prompt, 21 | height=480, 22 | width=480, 23 | num_frames=49, 24 | guidance_scale=5.0, 25 | skip_guidance_scale=2.0, 26 | skip_guidance_block_idxs=[2], # 30 27 | ).frames[0] 28 | export_to_video(output, "output.mp4", fps=16) -------------------------------------------------------------------------------- /Wan2.1/multiple_inference.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import re 4 | from diffusers.utils import export_to_video 5 | from diffusers import AutoencoderKLWan 6 | from diffusers.schedulers.scheduling_unipc_multistep import UniPCMultistepScheduler 7 | from pipeline import WanSTGPipeline 8 | 9 | #-------Option--------# 10 | # Model options 11 | model_id = "/home/nas4_user/kinamkim/checkpoint/Wan2.1-T2V-1.3B-Diffusers" 12 | prompt_file = "prompts.txt" # Path to a file containing prompts (one per line) 13 | negative_prompt = "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards" 14 | 15 | # Generation options 16 | height = 480 17 | width = 720 18 | num_frames = 49 19 | guidance_scale = 5.0 20 | flow_shift = 3.0 # 5.0 for 720P, 3.0 for 480P 21 | fps = 16 22 | #-----------------------# 23 | 24 | def sanitize_filename(text): 25 | """Convert text to a valid filename by removing or replacing invalid characters.""" 26 | # Replace invalid characters with underscores 27 | sanitized = re.sub(r'[\\/*?:"<>|]', "_", text) 28 | # Limit length to avoid too long filenames 29 | return sanitized[:100] 30 | 31 | def main(): 32 | # Load model and setup pipeline 33 | vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32) 34 | pipe = WanSTGPipeline.from_pretrained(model_id, vae=vae, torch_dtype=torch.bfloat16) 35 | pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config, flow_shift=flow_shift) 36 | pipe.to("cuda") 37 | 38 | # Read prompts from file 39 | with open(prompt_file, 'r') as f: 40 | prompts = [line.strip() for line in f.readlines() if line.strip()] 41 | 42 | # Process each prompt 43 | for prompt in prompts: 44 | print(f"Processing prompt: {prompt}") 45 | sanitized_prompt = sanitize_filename(prompt) 46 | 47 | # Create scales to iterate through 48 | scales = [0.0, 1.0, 2.0, 3.0] 49 | 50 | for scale in scales: 51 | if scale == 0.0: 52 | # For scale 0.0, use CFG only (no STG) 53 | output_dir = f"outputs/{sanitized_prompt}/CFG" 54 | os.makedirs(output_dir, exist_ok=True) 55 | output_path = f"{output_dir}/output.mp4" 56 | if os.path.exists(output_path): 57 | print(f"Skipping {output_path} because it already exists") 58 | continue 59 | 60 | print(f"Generating with CFG only (scale={scale})") 61 | output = pipe( 62 | prompt=prompt, 63 | negative_prompt=negative_prompt, 64 | height=height, 65 | width=width, 66 | num_frames=num_frames, 67 | guidance_scale=guidance_scale, 68 | skip_guidance_scale=0.0, # No STG 69 | skip_guidance_block_idxs=[], # No blocks 70 | generator=torch.Generator(device="cuda").manual_seed(42), 71 | ).frames[0] 72 | 73 | export_to_video(output, output_path, fps=fps) 74 | print(f"Saved to {output_path}") 75 | else: 76 | # For other scales, iterate through block indices 77 | for block_idx in range(30): # 0 to 29 78 | output_dir = f"outputs/{sanitized_prompt}/STG/scale_{scale}" 79 | os.makedirs(output_dir, exist_ok=True) 80 | output_path = f"{output_dir}/block_{block_idx}.mp4" 81 | if os.path.exists(output_path): 82 | print(f"Skipping {output_path} because it already exists") 83 | continue 84 | 85 | print(f"Generating with STG: scale={scale}, block_idx={block_idx}") 86 | output = pipe( 87 | prompt=prompt, 88 | negative_prompt=negative_prompt, 89 | height=height, 90 | width=width, 91 | num_frames=num_frames, 92 | guidance_scale=guidance_scale, 93 | skip_guidance_scale=scale, 94 | skip_guidance_block_idxs=[block_idx], 95 | generator=torch.Generator(device="cuda").manual_seed(42), 96 | ).frames[0] 97 | 98 | export_to_video(output, output_path, fps=fps) 99 | print(f"Saved to {output_path}") 100 | 101 | if __name__ == "__main__": 102 | main() -------------------------------------------------------------------------------- /Wan2.1/pipeline.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 The Wan Team and The HuggingFace Team. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import types 16 | import html 17 | from typing import Any, Callable, Dict, List, Optional, Union 18 | 19 | import ftfy 20 | import regex as re 21 | import torch 22 | from transformers import AutoTokenizer, UMT5EncoderModel 23 | 24 | from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback 25 | from diffusers.loaders import WanLoraLoaderMixin 26 | from diffusers.models import AutoencoderKLWan, WanTransformer3DModel 27 | from diffusers.schedulers import FlowMatchEulerDiscreteScheduler 28 | from diffusers.utils import is_torch_xla_available, logging, replace_example_docstring 29 | from diffusers.utils.torch_utils import randn_tensor 30 | from diffusers.video_processor import VideoProcessor 31 | from diffusers.pipelines.pipeline_utils import DiffusionPipeline 32 | from diffusers.pipelines.wan.pipeline_output import WanPipelineOutput 33 | 34 | 35 | if is_torch_xla_available(): 36 | import torch_xla.core.xla_model as xm 37 | 38 | XLA_AVAILABLE = True 39 | else: 40 | XLA_AVAILABLE = False 41 | 42 | logger = logging.get_logger(__name__) # pylint: disable=invalid-name 43 | 44 | 45 | EXAMPLE_DOC_STRING = """ 46 | Examples: 47 | ```python 48 | >>> import torch 49 | >>> from diffusers.utils import export_to_video 50 | >>> from diffusers import AutoencoderKLWan, WanPipeline 51 | >>> from diffusers.schedulers.scheduling_unipc_multistep import UniPCMultistepScheduler 52 | 53 | >>> # Available models: Wan-AI/Wan2.1-T2V-14B-Diffusers, Wan-AI/Wan2.1-T2V-1.3B-Diffusers 54 | >>> model_id = "Wan-AI/Wan2.1-T2V-14B-Diffusers" 55 | >>> vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32) 56 | >>> pipe = WanPipeline.from_pretrained(model_id, vae=vae, torch_dtype=torch.bfloat16) 57 | >>> flow_shift = 5.0 # 5.0 for 720P, 3.0 for 480P 58 | >>> pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config, flow_shift=flow_shift) 59 | >>> pipe.to("cuda") 60 | 61 | >>> prompt = "A cat and a dog baking a cake together in a kitchen. The cat is carefully measuring flour, while the dog is stirring the batter with a wooden spoon. The kitchen is cozy, with sunlight streaming through the window." 62 | >>> negative_prompt = "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards" 63 | 64 | >>> output = pipe( 65 | ... prompt=prompt, 66 | ... negative_prompt=negative_prompt, 67 | ... height=720, 68 | ... width=1280, 69 | ... num_frames=81, 70 | ... guidance_scale=5.0, 71 | ... ).frames[0] 72 | >>> export_to_video(output, "output.mp4", fps=16) 73 | ``` 74 | """ 75 | 76 | def forward_with_stg( 77 | self, 78 | hidden_states: torch.Tensor, 79 | encoder_hidden_states: torch.Tensor, 80 | temb: torch.Tensor, 81 | rotary_emb: torch.Tensor, 82 | ) -> torch.Tensor: 83 | return hidden_states 84 | 85 | # Copied from diffusers.models.transformers.transformer_wan.py 86 | def forward_without_stg( 87 | self, 88 | hidden_states: torch.Tensor, 89 | encoder_hidden_states: torch.Tensor, 90 | temb: torch.Tensor, 91 | rotary_emb: torch.Tensor, 92 | ) -> torch.Tensor: 93 | shift_msa, scale_msa, gate_msa, c_shift_msa, c_scale_msa, c_gate_msa = ( 94 | self.scale_shift_table + temb.float() 95 | ).chunk(6, dim=1) 96 | 97 | # 1. Self-attention 98 | norm_hidden_states = (self.norm1(hidden_states.float()) * (1 + scale_msa) + shift_msa).type_as(hidden_states) 99 | attn_output = self.attn1(hidden_states=norm_hidden_states, rotary_emb=rotary_emb) 100 | hidden_states = (hidden_states.float() + attn_output * gate_msa).type_as(hidden_states) 101 | 102 | # 2. Cross-attention 103 | norm_hidden_states = self.norm2(hidden_states.float()).type_as(hidden_states) 104 | attn_output = self.attn2(hidden_states=norm_hidden_states, encoder_hidden_states=encoder_hidden_states) 105 | hidden_states = hidden_states + attn_output 106 | 107 | # 3. Feed-forward 108 | norm_hidden_states = (self.norm3(hidden_states.float()) * (1 + c_scale_msa) + c_shift_msa).type_as( 109 | hidden_states 110 | ) 111 | ff_output = self.ffn(norm_hidden_states) 112 | hidden_states = (hidden_states.float() + ff_output.float() * c_gate_msa).type_as(hidden_states) 113 | 114 | return hidden_states 115 | 116 | def basic_clean(text): 117 | text = ftfy.fix_text(text) 118 | text = html.unescape(html.unescape(text)) 119 | return text.strip() 120 | 121 | 122 | def whitespace_clean(text): 123 | text = re.sub(r"\s+", " ", text) 124 | text = text.strip() 125 | return text 126 | 127 | 128 | def prompt_clean(text): 129 | text = whitespace_clean(basic_clean(text)) 130 | return text 131 | 132 | 133 | class WanSTGPipeline(DiffusionPipeline, WanLoraLoaderMixin): 134 | r""" 135 | Pipeline for text-to-video generation using Wan. 136 | 137 | This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods 138 | implemented for all pipelines (downloading, saving, running on a particular device, etc.). 139 | 140 | Args: 141 | tokenizer ([`T5Tokenizer`]): 142 | Tokenizer from [T5](https://huggingface.co/docs/transformers/en/model_doc/t5#transformers.T5Tokenizer), 143 | specifically the [google/umt5-xxl](https://huggingface.co/google/umt5-xxl) variant. 144 | text_encoder ([`T5EncoderModel`]): 145 | [T5](https://huggingface.co/docs/transformers/en/model_doc/t5#transformers.T5EncoderModel), specifically 146 | the [google/umt5-xxl](https://huggingface.co/google/umt5-xxl) variant. 147 | transformer ([`WanTransformer3DModel`]): 148 | Conditional Transformer to denoise the input latents. 149 | scheduler ([`UniPCMultistepScheduler`]): 150 | A scheduler to be used in combination with `transformer` to denoise the encoded image latents. 151 | vae ([`AutoencoderKLWan`]): 152 | Variational Auto-Encoder (VAE) Model to encode and decode videos to and from latent representations. 153 | """ 154 | 155 | model_cpu_offload_seq = "text_encoder->transformer->vae" 156 | _callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"] 157 | 158 | def __init__( 159 | self, 160 | tokenizer: AutoTokenizer, 161 | text_encoder: UMT5EncoderModel, 162 | transformer: WanTransformer3DModel, 163 | vae: AutoencoderKLWan, 164 | scheduler: FlowMatchEulerDiscreteScheduler, 165 | ): 166 | super().__init__() 167 | 168 | self.register_modules( 169 | vae=vae, 170 | text_encoder=text_encoder, 171 | tokenizer=tokenizer, 172 | transformer=transformer, 173 | scheduler=scheduler, 174 | ) 175 | 176 | self.vae_scale_factor_temporal = 2 ** sum(self.vae.temperal_downsample) if getattr(self, "vae", None) else 4 177 | self.vae_scale_factor_spatial = 2 ** len(self.vae.temperal_downsample) if getattr(self, "vae", None) else 8 178 | self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor_spatial) 179 | 180 | def _get_t5_prompt_embeds( 181 | self, 182 | prompt: Union[str, List[str]] = None, 183 | num_videos_per_prompt: int = 1, 184 | max_sequence_length: int = 226, 185 | device: Optional[torch.device] = None, 186 | dtype: Optional[torch.dtype] = None, 187 | ): 188 | device = device or self._execution_device 189 | dtype = dtype or self.text_encoder.dtype 190 | 191 | prompt = [prompt] if isinstance(prompt, str) else prompt 192 | prompt = [prompt_clean(u) for u in prompt] 193 | batch_size = len(prompt) 194 | 195 | text_inputs = self.tokenizer( 196 | prompt, 197 | padding="max_length", 198 | max_length=max_sequence_length, 199 | truncation=True, 200 | add_special_tokens=True, 201 | return_attention_mask=True, 202 | return_tensors="pt", 203 | ) 204 | text_input_ids, mask = text_inputs.input_ids, text_inputs.attention_mask 205 | seq_lens = mask.gt(0).sum(dim=1).long() 206 | 207 | prompt_embeds = self.text_encoder(text_input_ids.to(device), mask.to(device)).last_hidden_state 208 | prompt_embeds = prompt_embeds.to(dtype=dtype, device=device) 209 | prompt_embeds = [u[:v] for u, v in zip(prompt_embeds, seq_lens)] 210 | prompt_embeds = torch.stack( 211 | [torch.cat([u, u.new_zeros(max_sequence_length - u.size(0), u.size(1))]) for u in prompt_embeds], dim=0 212 | ) 213 | 214 | # duplicate text embeddings for each generation per prompt, using mps friendly method 215 | _, seq_len, _ = prompt_embeds.shape 216 | prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1) 217 | prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1) 218 | 219 | return prompt_embeds 220 | 221 | def encode_prompt( 222 | self, 223 | prompt: Union[str, List[str]], 224 | negative_prompt: Optional[Union[str, List[str]]] = None, 225 | do_classifier_free_guidance: bool = True, 226 | num_videos_per_prompt: int = 1, 227 | prompt_embeds: Optional[torch.Tensor] = None, 228 | negative_prompt_embeds: Optional[torch.Tensor] = None, 229 | max_sequence_length: int = 226, 230 | device: Optional[torch.device] = None, 231 | dtype: Optional[torch.dtype] = None, 232 | ): 233 | r""" 234 | Encodes the prompt into text encoder hidden states. 235 | 236 | Args: 237 | prompt (`str` or `List[str]`, *optional*): 238 | prompt to be encoded 239 | negative_prompt (`str` or `List[str]`, *optional*): 240 | The prompt or prompts not to guide the image generation. If not defined, one has to pass 241 | `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is 242 | less than `1`). 243 | do_classifier_free_guidance (`bool`, *optional*, defaults to `True`): 244 | Whether to use classifier free guidance or not. 245 | num_videos_per_prompt (`int`, *optional*, defaults to 1): 246 | Number of videos that should be generated per prompt. torch device to place the resulting embeddings on 247 | prompt_embeds (`torch.Tensor`, *optional*): 248 | Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not 249 | provided, text embeddings will be generated from `prompt` input argument. 250 | negative_prompt_embeds (`torch.Tensor`, *optional*): 251 | Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt 252 | weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input 253 | argument. 254 | device: (`torch.device`, *optional*): 255 | torch device 256 | dtype: (`torch.dtype`, *optional*): 257 | torch dtype 258 | """ 259 | device = device or self._execution_device 260 | 261 | prompt = [prompt] if isinstance(prompt, str) else prompt 262 | if prompt is not None: 263 | batch_size = len(prompt) 264 | else: 265 | batch_size = prompt_embeds.shape[0] 266 | 267 | if prompt_embeds is None: 268 | prompt_embeds = self._get_t5_prompt_embeds( 269 | prompt=prompt, 270 | num_videos_per_prompt=num_videos_per_prompt, 271 | max_sequence_length=max_sequence_length, 272 | device=device, 273 | dtype=dtype, 274 | ) 275 | 276 | if do_classifier_free_guidance and negative_prompt_embeds is None: 277 | negative_prompt = negative_prompt or "" 278 | negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt 279 | 280 | if prompt is not None and type(prompt) is not type(negative_prompt): 281 | raise TypeError( 282 | f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" 283 | f" {type(prompt)}." 284 | ) 285 | elif batch_size != len(negative_prompt): 286 | raise ValueError( 287 | f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" 288 | f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" 289 | " the batch size of `prompt`." 290 | ) 291 | 292 | negative_prompt_embeds = self._get_t5_prompt_embeds( 293 | prompt=negative_prompt, 294 | num_videos_per_prompt=num_videos_per_prompt, 295 | max_sequence_length=max_sequence_length, 296 | device=device, 297 | dtype=dtype, 298 | ) 299 | 300 | return prompt_embeds, negative_prompt_embeds 301 | 302 | def check_inputs( 303 | self, 304 | prompt, 305 | negative_prompt, 306 | height, 307 | width, 308 | prompt_embeds=None, 309 | negative_prompt_embeds=None, 310 | callback_on_step_end_tensor_inputs=None, 311 | ): 312 | if height % 16 != 0 or width % 16 != 0: 313 | raise ValueError(f"`height` and `width` have to be divisible by 16 but are {height} and {width}.") 314 | 315 | if callback_on_step_end_tensor_inputs is not None and not all( 316 | k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs 317 | ): 318 | raise ValueError( 319 | f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}" 320 | ) 321 | 322 | if prompt is not None and prompt_embeds is not None: 323 | raise ValueError( 324 | f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" 325 | " only forward one of the two." 326 | ) 327 | elif negative_prompt is not None and negative_prompt_embeds is not None: 328 | raise ValueError( 329 | f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`: {negative_prompt_embeds}. Please make sure to" 330 | " only forward one of the two." 331 | ) 332 | elif prompt is None and prompt_embeds is None: 333 | raise ValueError( 334 | "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." 335 | ) 336 | elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): 337 | raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") 338 | elif negative_prompt is not None and ( 339 | not isinstance(negative_prompt, str) and not isinstance(negative_prompt, list) 340 | ): 341 | raise ValueError(f"`negative_prompt` has to be of type `str` or `list` but is {type(negative_prompt)}") 342 | 343 | def prepare_latents( 344 | self, 345 | batch_size: int, 346 | num_channels_latents: int = 16, 347 | height: int = 480, 348 | width: int = 832, 349 | num_frames: int = 81, 350 | dtype: Optional[torch.dtype] = None, 351 | device: Optional[torch.device] = None, 352 | generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, 353 | latents: Optional[torch.Tensor] = None, 354 | ) -> torch.Tensor: 355 | if latents is not None: 356 | return latents.to(device=device, dtype=dtype) 357 | 358 | num_latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1 359 | shape = ( 360 | batch_size, 361 | num_channels_latents, 362 | num_latent_frames, 363 | int(height) // self.vae_scale_factor_spatial, 364 | int(width) // self.vae_scale_factor_spatial, 365 | ) 366 | if isinstance(generator, list) and len(generator) != batch_size: 367 | raise ValueError( 368 | f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" 369 | f" size of {batch_size}. Make sure the batch size matches the length of the generators." 370 | ) 371 | 372 | latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) 373 | return latents 374 | 375 | @property 376 | def guidance_scale(self): 377 | return self._guidance_scale 378 | 379 | @property 380 | def do_classifier_free_guidance(self): 381 | return self._guidance_scale > 1.0 382 | 383 | @property 384 | def do_skip_guidance(self): 385 | return self._skip_guidance_scale > 0.0 386 | 387 | @property 388 | def num_timesteps(self): 389 | return self._num_timesteps 390 | 391 | @property 392 | def current_timestep(self): 393 | return self._current_timestep 394 | 395 | @property 396 | def interrupt(self): 397 | return self._interrupt 398 | 399 | @property 400 | def attention_kwargs(self): 401 | return self._attention_kwargs 402 | 403 | @torch.no_grad() 404 | @replace_example_docstring(EXAMPLE_DOC_STRING) 405 | def __call__( 406 | self, 407 | prompt: Union[str, List[str]] = None, 408 | negative_prompt: Union[str, List[str]] = None, 409 | height: int = 480, 410 | width: int = 832, 411 | num_frames: int = 81, 412 | num_inference_steps: int = 50, 413 | guidance_scale: float = 5.0, 414 | num_videos_per_prompt: Optional[int] = 1, 415 | generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, 416 | latents: Optional[torch.Tensor] = None, 417 | prompt_embeds: Optional[torch.Tensor] = None, 418 | negative_prompt_embeds: Optional[torch.Tensor] = None, 419 | output_type: Optional[str] = "np", 420 | return_dict: bool = True, 421 | attention_kwargs: Optional[Dict[str, Any]] = None, 422 | callback_on_step_end: Optional[ 423 | Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks] 424 | ] = None, 425 | callback_on_step_end_tensor_inputs: List[str] = ["latents"], 426 | max_sequence_length: int = 512, 427 | skip_guidance_scale: float = 0.0, 428 | skip_guidance_block_idxs: List[int] = [2], 429 | ): 430 | r""" 431 | The call function to the pipeline for generation. 432 | 433 | Args: 434 | prompt (`str` or `List[str]`, *optional*): 435 | The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. 436 | instead. 437 | height (`int`, defaults to `480`): 438 | The height in pixels of the generated image. 439 | width (`int`, defaults to `832`): 440 | The width in pixels of the generated image. 441 | num_frames (`int`, defaults to `81`): 442 | The number of frames in the generated video. 443 | num_inference_steps (`int`, defaults to `50`): 444 | The number of denoising steps. More denoising steps usually lead to a higher quality image at the 445 | expense of slower inference. 446 | guidance_scale (`float`, defaults to `5.0`): 447 | Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). 448 | `guidance_scale` is defined as `w` of equation 2. of [Imagen 449 | Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > 450 | 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, 451 | usually at the expense of lower image quality. 452 | num_videos_per_prompt (`int`, *optional*, defaults to 1): 453 | The number of images to generate per prompt. 454 | generator (`torch.Generator` or `List[torch.Generator]`, *optional*): 455 | A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make 456 | generation deterministic. 457 | latents (`torch.Tensor`, *optional*): 458 | Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image 459 | generation. Can be used to tweak the same generation with different prompts. If not provided, a latents 460 | tensor is generated by sampling using the supplied random `generator`. 461 | prompt_embeds (`torch.Tensor`, *optional*): 462 | Pre-generated text embeddings. Can be used to easily tweak text inputs (prompt weighting). If not 463 | provided, text embeddings are generated from the `prompt` input argument. 464 | output_type (`str`, *optional*, defaults to `"pil"`): 465 | The output format of the generated image. Choose between `PIL.Image` or `np.array`. 466 | return_dict (`bool`, *optional*, defaults to `True`): 467 | Whether or not to return a [`WanPipelineOutput`] instead of a plain tuple. 468 | attention_kwargs (`dict`, *optional*): 469 | A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under 470 | `self.processor` in 471 | [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). 472 | callback_on_step_end (`Callable`, `PipelineCallback`, `MultiPipelineCallbacks`, *optional*): 473 | A function or a subclass of `PipelineCallback` or `MultiPipelineCallbacks` that is called at the end of 474 | each denoising step during the inference. with the following arguments: `callback_on_step_end(self: 475 | DiffusionPipeline, step: int, timestep: int, callback_kwargs: Dict)`. `callback_kwargs` will include a 476 | list of all tensors as specified by `callback_on_step_end_tensor_inputs`. 477 | callback_on_step_end_tensor_inputs (`List`, *optional*): 478 | The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list 479 | will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the 480 | `._callback_tensor_inputs` attribute of your pipeline class. 481 | autocast_dtype (`torch.dtype`, *optional*, defaults to `torch.bfloat16`): 482 | The dtype to use for the torch.amp.autocast. 483 | 484 | Examples: 485 | 486 | Returns: 487 | [`~WanPipelineOutput`] or `tuple`: 488 | If `return_dict` is `True`, [`WanPipelineOutput`] is returned, otherwise a `tuple` is returned where 489 | the first element is a list with the generated images and the second element is a list of `bool`s 490 | indicating whether the corresponding generated image contains "not-safe-for-work" (nsfw) content. 491 | """ 492 | 493 | if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)): 494 | callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs 495 | 496 | # 1. Check inputs. Raise error if not correct 497 | self.check_inputs( 498 | prompt, 499 | negative_prompt, 500 | height, 501 | width, 502 | prompt_embeds, 503 | negative_prompt_embeds, 504 | callback_on_step_end_tensor_inputs, 505 | ) 506 | 507 | if num_frames % self.vae_scale_factor_temporal != 1: 508 | logger.warning( 509 | f"`num_frames - 1` has to be divisible by {self.vae_scale_factor_temporal}. Rounding to the nearest number." 510 | ) 511 | num_frames = num_frames // self.vae_scale_factor_temporal * self.vae_scale_factor_temporal + 1 512 | num_frames = max(num_frames, 1) 513 | 514 | self._guidance_scale = guidance_scale 515 | self._skip_guidance_scale = skip_guidance_scale 516 | self._attention_kwargs = attention_kwargs 517 | self._current_timestep = None 518 | self._interrupt = False 519 | 520 | device = self._execution_device 521 | 522 | # 2. Define call parameters 523 | if prompt is not None and isinstance(prompt, str): 524 | batch_size = 1 525 | elif prompt is not None and isinstance(prompt, list): 526 | batch_size = len(prompt) 527 | else: 528 | batch_size = prompt_embeds.shape[0] 529 | 530 | # 3. Encode input prompt 531 | prompt_embeds, negative_prompt_embeds = self.encode_prompt( 532 | prompt=prompt, 533 | negative_prompt=negative_prompt, 534 | do_classifier_free_guidance=self.do_classifier_free_guidance, 535 | num_videos_per_prompt=num_videos_per_prompt, 536 | prompt_embeds=prompt_embeds, 537 | negative_prompt_embeds=negative_prompt_embeds, 538 | max_sequence_length=max_sequence_length, 539 | device=device, 540 | ) 541 | 542 | transformer_dtype = self.transformer.dtype 543 | prompt_embeds = prompt_embeds.to(transformer_dtype) 544 | if negative_prompt_embeds is not None: 545 | negative_prompt_embeds = negative_prompt_embeds.to(transformer_dtype) 546 | 547 | # 4. Prepare timesteps 548 | self.scheduler.set_timesteps(num_inference_steps, device=device) 549 | timesteps = self.scheduler.timesteps 550 | 551 | # 5. Prepare latent variables 552 | num_channels_latents = self.transformer.config.in_channels 553 | latents = self.prepare_latents( 554 | batch_size * num_videos_per_prompt, 555 | num_channels_latents, 556 | height, 557 | width, 558 | num_frames, 559 | torch.float32, 560 | device, 561 | generator, 562 | latents, 563 | ) 564 | 565 | # 6. Denoising loop 566 | num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order 567 | self._num_timesteps = len(timesteps) 568 | 569 | with self.progress_bar(total=num_inference_steps) as progress_bar: 570 | for i, t in enumerate(timesteps): 571 | if self.interrupt: 572 | continue 573 | 574 | self._current_timestep = t 575 | latent_model_input = latents.to(transformer_dtype) 576 | timestep = t.expand(latents.shape[0]) 577 | 578 | noise_pred = self.transformer( 579 | hidden_states=latent_model_input, 580 | timestep=timestep, 581 | encoder_hidden_states=prompt_embeds, 582 | attention_kwargs=attention_kwargs, 583 | return_dict=False, 584 | )[0] 585 | 586 | if self.do_classifier_free_guidance: 587 | noise_uncond = self.transformer( 588 | hidden_states=latent_model_input, 589 | timestep=timestep, 590 | encoder_hidden_states=negative_prompt_embeds, 591 | attention_kwargs=attention_kwargs, 592 | return_dict=False, 593 | )[0] 594 | noise_pred = noise_uncond + guidance_scale * (noise_pred - noise_uncond) 595 | 596 | if self.do_skip_guidance: 597 | for i in skip_guidance_block_idxs: 598 | self.transformer.blocks[i].forward = types.MethodType(forward_with_stg, self.transformer.blocks[i]) 599 | noise_perturb = self.transformer( 600 | hidden_states=latent_model_input, 601 | timestep=timestep, 602 | encoder_hidden_states=negative_prompt_embeds, 603 | attention_kwargs=attention_kwargs, 604 | return_dict=False, 605 | )[0] 606 | noise_pred = noise_pred + skip_guidance_scale * (noise_pred - noise_perturb) 607 | for i in skip_guidance_block_idxs: 608 | self.transformer.blocks[i].forward = types.MethodType(forward_without_stg, self.transformer.blocks[i]) 609 | 610 | # compute the previous noisy sample x_t -> x_t-1 611 | latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0] 612 | 613 | if callback_on_step_end is not None: 614 | callback_kwargs = {} 615 | for k in callback_on_step_end_tensor_inputs: 616 | callback_kwargs[k] = locals()[k] 617 | callback_outputs = callback_on_step_end(self, i, t, callback_kwargs) 618 | 619 | latents = callback_outputs.pop("latents", latents) 620 | prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds) 621 | negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds", negative_prompt_embeds) 622 | 623 | # call the callback, if provided 624 | if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): 625 | progress_bar.update() 626 | 627 | if XLA_AVAILABLE: 628 | xm.mark_step() 629 | 630 | self._current_timestep = None 631 | 632 | if not output_type == "latent": 633 | latents = latents.to(self.vae.dtype) 634 | latents_mean = ( 635 | torch.tensor(self.vae.config.latents_mean) 636 | .view(1, self.vae.config.z_dim, 1, 1, 1) 637 | .to(latents.device, latents.dtype) 638 | ) 639 | latents_std = 1.0 / torch.tensor(self.vae.config.latents_std).view(1, self.vae.config.z_dim, 1, 1, 1).to( 640 | latents.device, latents.dtype 641 | ) 642 | latents = latents / latents_std + latents_mean 643 | video = self.vae.decode(latents, return_dict=False)[0] 644 | video = self.video_processor.postprocess_video(video, output_type=output_type) 645 | else: 646 | video = latents 647 | 648 | # Offload all models 649 | self.maybe_free_model_hooks() 650 | 651 | if not return_dict: 652 | return (video,) 653 | 654 | return WanPipelineOutput(frames=video) -------------------------------------------------------------------------------- /Wan2.1/prompts.txt: -------------------------------------------------------------------------------- 1 | A cat and a dog baking a cake together in a kitchen. The cat is carefully measuring flour, while the dog is stirring the batter with a wooden spoon. The kitchen is cozy, with sunlight streaming through the window. -------------------------------------------------------------------------------- /assets/hunyuan1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junhahyung/STGuidance/df04da05d5631e577fedb7e58eff6ddb4ffa4984/assets/hunyuan1.mp4 -------------------------------------------------------------------------------- /assets/hunyuan2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junhahyung/STGuidance/df04da05d5631e577fedb7e58eff6ddb4ffa4984/assets/hunyuan2.mp4 -------------------------------------------------------------------------------- /assets/tmp: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------