Files
kijai-ComfyUI-HunyuanVideoW…/hyvideo/diffusion/pipelines/pipeline_hunyuan_video.py
T

947 lines
46 KiB
Python

# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
#
# Modified from diffusers==0.29.2
#
# ==============================================================================
import inspect
from typing import Any, Callable, Dict, List, Optional, Union, Tuple
import torch
from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback
from diffusers.schedulers import KarrasDiffusionSchedulers
from diffusers.utils import (
logging,
replace_example_docstring
)
from diffusers.utils.torch_utils import randn_tensor
from diffusers.pipelines.pipeline_utils import DiffusionPipeline
from diffusers.schedulers import DPMSolverMultistepScheduler
from ...modules import HYVideoDiffusionTransformer
from comfy.utils import ProgressBar
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
EXAMPLE_DOC_STRING = """"""
from ...modules.posemb_layers import get_nd_rotary_pos_embed
from ....enhance_a_video.globals import enable_enhance, disable_enhance, set_enhance_weight
def get_rotary_pos_embed(transformer, latent_video_length, height, width, k=0):
target_ndim = 3
ndim = 5 - 2
rope_theta = 225
patch_size = transformer.patch_size
rope_dim_list = transformer.rope_dim_list
hidden_size = transformer.hidden_size
heads_num = transformer.heads_num
head_dim = hidden_size // heads_num
# 884
latents_size = [latent_video_length, height // 8, width // 8]
if isinstance(patch_size, int):
assert all(s % patch_size == 0 for s in latents_size), (
f"Latent size(last {ndim} dimensions) should be divisible by patch size({patch_size}), "
f"but got {latents_size}."
)
rope_sizes = [s // patch_size for s in latents_size]
elif isinstance(patch_size, list):
assert all(
s % patch_size[idx] == 0
for idx, s in enumerate(latents_size)
), (
f"Latent size(last {ndim} dimensions) should be divisible by patch size({patch_size}), "
f"but got {latents_size}."
)
rope_sizes = [
s // patch_size[idx] for idx, s in enumerate(latents_size)
]
if len(rope_sizes) != target_ndim:
rope_sizes = [1] * (target_ndim - len(rope_sizes)) + rope_sizes # time axis
if rope_dim_list is None:
rope_dim_list = [head_dim // target_ndim for _ in range(target_ndim)]
assert (
sum(rope_dim_list) == head_dim
), "sum(rope_dim_list) should equal to head_dim of attention layer"
freqs_cos, freqs_sin = get_nd_rotary_pos_embed(
rope_dim_list,
rope_sizes,
theta=rope_theta,
use_real=True,
theta_rescale_factor=1,
num_frames=latent_video_length,
k=k,
)
return freqs_cos, freqs_sin
def retrieve_timesteps(
scheduler,
num_inference_steps: Optional[int] = None,
device: Optional[Union[str, torch.device]] = None,
timesteps: Optional[List[int]] = None,
sigmas: Optional[List[float]] = None,
**kwargs,
):
"""
Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles
custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.
Args:
scheduler (`SchedulerMixin`):
The scheduler to get timesteps from.
num_inference_steps (`int`):
The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
must be `None`.
device (`str` or `torch.device`, *optional*):
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
timesteps (`List[int]`, *optional*):
Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
`num_inference_steps` and `sigmas` must be `None`.
sigmas (`List[float]`, *optional*):
Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
`num_inference_steps` and `timesteps` must be `None`.
Returns:
`Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
second element is the number of inference steps.
"""
if timesteps is not None and sigmas is not None:
raise ValueError(
"Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values"
)
if timesteps is not None:
accepts_timesteps = "timesteps" in set(
inspect.signature(scheduler.set_timesteps).parameters.keys()
)
if not accepts_timesteps:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" timestep schedules. Please check whether you are using the correct scheduler."
)
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
timesteps = scheduler.timesteps
num_inference_steps = len(timesteps)
elif sigmas is not None:
accept_sigmas = "sigmas" in set(
inspect.signature(scheduler.set_timesteps).parameters.keys()
)
if not accept_sigmas:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" sigmas schedules. Please check whether you are using the correct scheduler."
)
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
timesteps = scheduler.timesteps
num_inference_steps = len(timesteps)
else:
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
timesteps = scheduler.timesteps
return timesteps, num_inference_steps
class HunyuanVideoPipeline(DiffusionPipeline):
r"""
Pipeline for text-to-video generation using HunyuanVideo.
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods
implemented for all pipelines (downloading, saving, running on a particular device, etc.).
Args:
transformer ([`HYVideoDiffusionTransformer`]):
A `HYVideoDiffusionTransformer` to denoise the encoded video latents.
scheduler ([`SchedulerMixin`]):
A scheduler to be used in combination with `unet` to denoise the encoded image latents.
"""
def __init__(
self,
transformer: HYVideoDiffusionTransformer,
scheduler: KarrasDiffusionSchedulers,
comfy_model = None,
progress_bar_config: Dict[str, Any] = None,
base_dtype = torch.bfloat16,
):
super().__init__()
# ==========================================================================================
if progress_bar_config is None:
progress_bar_config = {}
if not hasattr(self, "_progress_bar_config"):
self._progress_bar_config = {}
self._progress_bar_config.update(progress_bar_config)
self.base_dtype = base_dtype
self.comfy_model = comfy_model
# ==========================================================================================
self.register_modules(
transformer=transformer,
scheduler=scheduler
)
self.vae_scale_factor = 8
def prepare_extra_func_kwargs(self, func, kwargs):
# prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
# eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
# eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
# and should be between [0, 1]
extra_step_kwargs = {}
for k, v in kwargs.items():
accepts = k in set(inspect.signature(func).parameters.keys())
if accepts:
extra_step_kwargs[k] = v
return extra_step_kwargs
def get_timesteps(self, num_inference_steps, strength, device):
# get the original timestep using init_timestep
init_timestep = min(int(num_inference_steps * strength), num_inference_steps)
t_start = max(num_inference_steps - init_timestep, 0)
timesteps = self.scheduler.timesteps[t_start * self.scheduler.order :]
if hasattr(self.scheduler, "set_begin_index"):
self.scheduler.set_begin_index(t_start * self.scheduler.order)
return timesteps.to(device), num_inference_steps - t_start
def prepare_latents(
self,
batch_size,
num_channels_latents,
num_inference_steps,
height,
width,
video_length,
device,
timesteps,
generator,
latents=None,
denoise_strength=1.0,
freenoise=False,
context_size=None,
context_overlap=None,
i2v_condition_type=None,
image_cond_latents=None,
i2v_stability=True,
):
shape = (
batch_size,
num_channels_latents,
video_length,
int(height) // self.vae_scale_factor,
int(width) // self.vae_scale_factor,
)
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
if latents is not None:
latents = latents.to(device)
noise = randn_tensor(shape, generator=generator, device=device, dtype=self.base_dtype)
if freenoise:
logger.info("Applying FreeNoise")
# code and comments from AnimateDiff-Evolved by Kosinkadink (https://github.com/Kosinkadink/ComfyUI-AnimateDiff-Evolved)
#video_length = video_length // 4
delta = context_size - context_overlap
for start_idx in range(0, video_length-context_size, delta):
# start_idx corresponds to the beginning of a context window
# goal: place shuffled in the delta region right after the end of the context window
# if space after context window is not enough to place the noise, adjust and finish
place_idx = start_idx + context_size
# if place_idx is outside the valid indexes, we are already finished
if place_idx >= video_length:
break
end_idx = place_idx - 1
#print("video_length:", video_length, "start_idx:", start_idx, "end_idx:", end_idx, "place_idx:", place_idx, "delta:", delta)
# if there is not enough room to copy delta amount of indexes, copy limited amount and finish
if end_idx + delta >= video_length:
final_delta = video_length - place_idx
# generate list of indexes in final delta region
list_idx = torch.tensor(list(range(start_idx,start_idx+final_delta)), device=torch.device("cpu"), dtype=torch.long)
# shuffle list
list_idx = list_idx[torch.randperm(final_delta, generator=generator)]
# apply shuffled indexes
noise[:, :, place_idx:place_idx + final_delta, :, :] = noise[:, :, list_idx, :, :]
break
# otherwise, do normal behavior
# generate list of indexes in delta region
list_idx = torch.tensor(list(range(start_idx,start_idx+delta)), device=torch.device("cpu"), dtype=torch.long)
# shuffle list
list_idx = list_idx[torch.randperm(delta, generator=generator)]
# apply shuffled indexes
#print("place_idx:", place_idx, "delta:", delta, "list_idx:", list_idx)
noise[:, :, place_idx:place_idx + delta, :, :] = noise[:, :, list_idx, :, :]
logger.info(f"image_cond_latents shape {image_cond_latents.shape} ")
i2v_mask = None
if image_cond_latents is not None:
if i2v_condition_type == "latent_concat":
# Create mask
i2v_mask = torch.zeros(shape[0], 1, shape[2], shape[3], shape[4], device=device)
i2v_mask[:, :, 0, ...] = 1.0
if image_cond_latents.shape[2] == 1:
padding = torch.zeros(shape, device=device)
padding[:, :, 0:1, :, :] = image_cond_latents
image_cond_latents = padding
if denoise_strength < 1.0:
if i2v_condition_type == "latent_concat":
latents = torch.cat((latents[:,:,0].unsqueeze(2), latents), dim=2)
timesteps, num_inference_steps = self.get_timesteps(num_inference_steps, denoise_strength, device)
latent_timestep = timesteps[:1]
frames_needed = noise.shape[2]
current_frames = latents.shape[2]
if frames_needed > current_frames:
repeat_factor = frames_needed - current_frames
additional_frame = torch.randn((latents.shape[0], latents.shape[1], repeat_factor, latents.shape[3], latents.shape[4]), dtype=latents.dtype, device=latents.device)
latents = torch.cat((additional_frame, latents), dim=2)
logger.info(f"Frames needed more than current frames, adding {repeat_factor} frames")
elif frames_needed < current_frames:
latents = latents[:, :, :frames_needed, :, :]
logger.info(f"Frames needed less than current frames, cutting down to {frames_needed}")
latents = latents * (1 - latent_timestep / 1000) + latent_timestep / 1000 * noise
print("latents shape:", latents.shape)
elif image_cond_latents is not None and i2v_stability:
if image_cond_latents.shape[2] == 1:
img_latents = image_cond_latents.repeat(1, 1, video_length, 1, 1)
else:
img_latents = image_cond_latents
t = torch.tensor([0.999]).to(device=device)
latents = noise * t + img_latents * (1 - t)
latents = latents.to(dtype=self.base_dtype)
else:
logger.info("Using random noise only")
latents = noise
# Check existence to make it compatible with FlowMatchEulerDiscreteScheduler
if hasattr(self.scheduler, "init_noise_sigma"):
# scale the initial noise by the standard deviation required by the scheduler
latents = latents * self.scheduler.init_noise_sigma
return latents.to(device), timesteps, i2v_mask, image_cond_latents
# Copied from diffusers.pipelines.latent_consistency_models.pipeline_latent_consistency_text2img.LatentConsistencyModelPipeline.get_guidance_scale_embedding
def get_guidance_scale_embedding(
self,
w: torch.Tensor,
embedding_dim: int = 512,
dtype: torch.dtype = torch.float32,
) -> torch.Tensor:
"""
See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298
Args:
w (`torch.Tensor`):
Generate embedding vectors with a specified guidance scale to subsequently enrich timestep embeddings.
embedding_dim (`int`, *optional*, defaults to 512):
Dimension of the embeddings to generate.
dtype (`torch.dtype`, *optional*, defaults to `torch.float32`):
Data type of the generated embeddings.
Returns:
`torch.Tensor`: Embedding vectors with shape `(len(w), embedding_dim)`.
"""
assert len(w.shape) == 1
w = w * 1000.0
half_dim = embedding_dim // 2
emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1)
emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb)
emb = w.to(dtype)[:, None] * emb[None, :]
emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1)
if embedding_dim % 2 == 1: # zero pad
emb = torch.nn.functional.pad(emb, (0, 1))
assert emb.shape == (w.shape[0], embedding_dim)
return emb
@property
def guidance_scale(self):
return self._guidance_scale
@property
def guidance_rescale(self):
return self._guidance_rescale
@property
def clip_skip(self):
return self._clip_skip
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
# corresponds to doing no classifier free guidance.
@property
def do_classifier_free_guidance(self):
# return self._guidance_scale > 1 and self.transformer.config.time_cond_proj_dim is None
return self._guidance_scale > 1
@property
def do_spatio_temporal_guidance(self):
# return self._guidance_scale > 1 and self.transformer.config.time_cond_proj_dim is None
return self._stg_scale > 0
@property
def cross_attention_kwargs(self):
return self._cross_attention_kwargs
@property
def num_timesteps(self):
return self._num_timesteps
@property
def interrupt(self):
return self._interrupt
@torch.no_grad()
@replace_example_docstring(EXAMPLE_DOC_STRING)
def __call__(
self,
height: int,
width: int,
video_length: int,
prompt_embed_dict: dict,
num_inference_steps: int = 50,
timesteps: List[int] = None,
sigmas: List[float] = None,
guidance_scale: float = 1.0,
cfg_start_percent: float = 0.0,
cfg_end_percent: float = 1.0,
batched_cfg: bool = True,
num_videos_per_prompt: Optional[int] = 1,
eta: float = 0.0,
denoise_strength: float = 1.0,
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
latents: Optional[torch.Tensor] = None,
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
guidance_rescale: float = 0.0,
clip_skip: Optional[int] = None,
callback_on_step_end: Optional[
Union[
Callable[[int, int, Dict], None],
PipelineCallback,
MultiPipelineCallbacks,
]
] = None,
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
embedded_guidance_scale: Optional[float] = None,
stg_mode: Optional[str] = None,
stg_block_idx: Optional[int] = -1,
stg_scale: Optional[float] = 0.0,
stg_start_percent: Optional[float] = 0.0,
stg_end_percent: Optional[float] = 1.0,
context_options: Optional[Dict[str, Any]] = None,
feta_args: Optional[Dict] = None,
leapfusion_img2vid: Optional[bool] = False,
image_cond_latents: Optional[torch.Tensor] = None,
riflex_freq_index: Optional[int] = None,
i2v_stability=True,
**kwargs,
):
r"""
The call function to the pipeline for generation.
Args:
height (`int`):
The height in pixels of the generated image.
width (`int`):
The width in pixels of the generated image.
video_length (`int`):
The number of frames in the generated video.
num_inference_steps (`int`, *optional*, defaults to 50):
The number of denoising steps. More denoising steps usually lead to a higher quality image at the
expense of slower inference.
timesteps (`List[int]`, *optional*):
Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument
in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is
passed will be used. Must be in descending order.
sigmas (`List[float]`, *optional*):
Custom sigmas to use for the denoising process with schedulers which support a `sigmas` argument in
their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is passed
will be used.
guidance_scale (`float`, *optional*, defaults to 7.5):
A higher guidance scale value encourages the model to generate images closely linked to the text
`prompt` at the expense of lower image quality. Guidance scale is enabled when `guidance_scale > 1`.
num_videos_per_prompt (`int`, *optional*, defaults to 1):
The number of images to generate per prompt.
eta (`float`, *optional*, defaults to 0.0):
Corresponds to parameter eta (η) from the [DDIM](https://arxiv.org/abs/2010.02502) paper. Only applies
to the [`~schedulers.DDIMScheduler`], and is ignored in other schedulers.
generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make
generation deterministic.
latents (`torch.Tensor`, *optional*):
Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image
generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
tensor is generated by sampling using the supplied random `generator`.
cross_attention_kwargs (`dict`, *optional*):
A kwargs dictionary that if specified is passed along to the [`AttentionProcessor`] as defined in
[`self.processor`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
guidance_rescale (`float`, *optional*, defaults to 0.0):
Guidance rescale factor from [Common Diffusion Noise Schedules and Sample Steps are
Flawed](https://arxiv.org/pdf/2305.08891.pdf). Guidance rescale factor should fix overexposure when
using zero terminal SNR.
callback_on_step_end (`Callable`, `PipelineCallback`, `MultiPipelineCallbacks`, *optional*):
A function or a subclass of `PipelineCallback` or `MultiPipelineCallbacks` that is called at the end of
each denoising step during the inference. with the following arguments: `callback_on_step_end(self:
DiffusionPipeline, step: int, timestep: int, callback_kwargs: Dict)`. `callback_kwargs` will include a
list of all tensors as specified by `callback_on_step_end_tensor_inputs`.
callback_on_step_end_tensor_inputs (`List`, *optional*):
The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list
will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the
`._callback_tensor_inputs` attribute of your pipeline class.
Examples:
Returns:
[`~HunyuanVideoPipelineOutput`] or `tuple`:
If `return_dict` is `True`, [`HunyuanVideoPipelineOutput`] is returned,
otherwise a `tuple` is returned where the first element is a list with the generated images and the
second element is a list of `bool`s indicating whether the corresponding generated image contains
"not-safe-for-work" (nsfw) content.
"""
callback = kwargs.pop("callback", None)
callback_steps = kwargs.pop("callback_steps", None)
if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)):
callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs
# 0. Default height and width to unet
# height = height or self.transformer.config.sample_size * self.vae_scale_factor
# width = width or self.transformer.config.sample_size * self.vae_scale_factor
# to deal with lora scaling and other possible forward hooks
self._guidance_scale = guidance_scale
self._guidance_rescale = guidance_rescale
self._clip_skip = clip_skip
self._cross_attention_kwargs = cross_attention_kwargs
self._interrupt = False
self._stg_scale = stg_scale
# 2. Define call parameters
batch_size = 1
device = self._execution_device
prompt_embeds = prompt_embed_dict["prompt_embeds"]
negative_prompt_embeds = prompt_embed_dict["negative_prompt_embeds"]
prompt_mask = prompt_embed_dict["attention_mask"]
negative_prompt_mask = prompt_embed_dict["negative_attention_mask"]
prompt_embeds_2 = prompt_embed_dict["prompt_embeds_2"]
negative_prompt_embeds_2 = prompt_embed_dict["negative_prompt_embeds_2"]
# For classifier free guidance, we need to do two forward passes.
# Here we concatenate the unconditional and text embeddings into a single batch
# to avoid doing two forward passes
if self.do_classifier_free_guidance and not self.do_spatio_temporal_guidance:
prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])
if prompt_mask is not None:
prompt_mask = torch.cat([negative_prompt_mask, prompt_mask])
if prompt_embeds_2 is not None:
prompt_embeds_2 = torch.cat([negative_prompt_embeds_2, prompt_embeds_2])
elif self.do_classifier_free_guidance and self.do_spatio_temporal_guidance:
prompt_embeds = torch.cat(
[negative_prompt_embeds, prompt_embeds, prompt_embeds]
)
if prompt_mask is not None:
prompt_mask = torch.cat([negative_prompt_mask, prompt_mask, prompt_mask])
if prompt_embeds_2 is not None:
prompt_embeds_2 = torch.cat(
[negative_prompt_embeds_2, prompt_embeds_2, prompt_embeds_2]
)
elif self.do_spatio_temporal_guidance:
prompt_embeds = torch.cat([prompt_embeds, prompt_embeds])
if prompt_mask is not None:
prompt_mask = torch.cat([prompt_mask, prompt_mask])
if prompt_embeds_2 is not None:
prompt_embeds_2 = torch.cat([prompt_embeds_2, prompt_embeds_2])
prompt_embeds = prompt_embeds.to(device = device, dtype = self.base_dtype)
prompt_mask = prompt_mask.to(device)
if prompt_embeds_2 is not None:
prompt_embeds_2 = prompt_embeds_2.to(device = device, dtype = self.base_dtype)
# 4. Prepare timesteps
extra_set_timesteps_kwargs = self.prepare_extra_func_kwargs(
self.scheduler.set_timesteps, {}
)
if hasattr(self.scheduler, "set_begin_index") and denoise_strength == 1.0:
self.scheduler.set_begin_index(begin_index=0)
timesteps, num_inference_steps = retrieve_timesteps(
self.scheduler,
num_inference_steps,
device,
timesteps,
sigmas,
**extra_set_timesteps_kwargs,
)
latent_video_length = (video_length - 1) // 4 + 1
original_image_latents = image_cond_latents
i2v_condition_type = self.transformer.i2v_condition_type
#if i2v_condition_type == "latent_concat":
#latent_video_length += 1
if feta_args is not None:
set_enhance_weight(feta_args["weight"])
feta_start_percent = feta_args["start_percent"]
feta_end_percent = feta_args["end_percent"]
enable_enhance(feta_args["single_blocks"], feta_args["double_blocks"])
else:
disable_enhance()
# context windows
use_context_schedule = False
freenoise = False
context_stride = 1
context_overlap = 1
context_frames = 65
if context_options is not None:
context_schedule = context_options["context_schedule"]
context_frames = (context_options["context_frames"] - 1) // 4 + 1
context_stride = context_options["context_stride"] // 4
context_overlap = context_options["context_overlap"] // 4
freenoise = context_options["freenoise"]
logger.info(f"Context schedule enabled: {context_frames} frames, {context_stride} stride, {context_overlap} overlap")
use_context_schedule = True
from ....context import get_context_scheduler
context = get_context_scheduler(context_schedule)
freqs_cos, freqs_sin = get_rotary_pos_embed(
self.transformer, context_frames, height, width
)
else:
# rotary embeddings
freqs_cos, freqs_sin = get_rotary_pos_embed(
self.transformer, latent_video_length, height, width, k=riflex_freq_index
)
if not self.transformer.upcast_rope:
freqs_cos = freqs_cos.to(self.base_dtype).to(device)
freqs_sin = freqs_sin.to(self.base_dtype).to(device)
else:
freqs_cos = freqs_cos.to(device)
freqs_sin = freqs_sin.to(device)
if leapfusion_img2vid:
logger.info("Single input latent frame detected, LeapFusion img2vid enabled")
original_latents = latents
# 5. Prepare latent variables
#num_channels_latents = self.transformer.config.in_channels
num_channels_latents = 16
latents, timesteps, i2v_mask, image_cond_latents = self.prepare_latents(
batch_size * num_videos_per_prompt,
num_channels_latents,
num_inference_steps,
height,
width,
latent_video_length,
device,
timesteps,
generator,
latents,
denoise_strength=denoise_strength,
freenoise=freenoise,
context_size=context_frames,
context_overlap=context_overlap,
i2v_condition_type=i2v_condition_type,
i2v_stability=i2v_stability,
image_cond_latents=image_cond_latents,
)
# 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline
extra_step_kwargs = self.prepare_extra_func_kwargs(
self.scheduler.step,
{"generator": generator, "eta": eta},
)
# 7. Denoising loop
num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
self._num_timesteps = len(timesteps)
# 8. Preview callback
from latent_preview import prepare_callback
callback = prepare_callback(self.comfy_model, num_inference_steps)
#print(self.scheduler.sigmas)
logger.info(f"Sampling {video_length} frames in {latents.shape[2]} latents at {width}x{height} with {len(timesteps)} inference steps")
comfy_pbar = ProgressBar(len(timesteps))
with self.progress_bar(total=len(timesteps)) as progress_bar:
for i, t in enumerate(timesteps):
if self.interrupt:
continue
if image_cond_latents is not None and i2v_condition_type == "token_replace":
latents = torch.concat([original_image_latents, latents[:, :, 1:, :, :]], dim=2)
latent_model_input = latents
input_prompt_embeds = prompt_embeds
input_prompt_mask = prompt_mask
input_prompt_embeds_2 = prompt_embeds_2
cfg_enabled = False
stg_enabled = False
current_step_percentage = i / len(timesteps)
if self.do_spatio_temporal_guidance:
if stg_start_percent <= current_step_percentage <= stg_end_percent:
stg_enabled = True
if self.do_classifier_free_guidance:
latent_model_input = torch.cat([latents] * 3)
else:
latent_model_input = torch.cat([latents] * 2)
else:
stg_mode = None
stg_block_idx = -1
input_prompt_embeds = prompt_embeds[0].unsqueeze(0)
input_prompt_mask = prompt_mask[0].unsqueeze(0)
input_prompt_embeds_2 = prompt_embeds_2[0].unsqueeze(0)
latent_model_input = latents
else:
stg_enabled = False
# expand the latents if we are doing classifier free guidance
if self.do_classifier_free_guidance:
if cfg_start_percent <= current_step_percentage <= cfg_end_percent:
#print("applying CFG at step", i + 1, "with strength", guidance_scale)
latent_model_input = torch.cat([latents] * 2)
cfg_enabled = True
else:
input_prompt_embeds = prompt_embeds[1].unsqueeze(0)
input_prompt_mask = prompt_mask[1].unsqueeze(0)
input_prompt_embeds_2 = prompt_embeds_2[1].unsqueeze(0)
if feta_args is not None:
if feta_start_percent <= current_step_percentage <= feta_end_percent:
enable_enhance(feta_args["single_blocks"], feta_args["double_blocks"])
else:
disable_enhance()
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
t_expand = t.repeat(latent_model_input.shape[0])
if leapfusion_img2vid:
latent_model_input[:, :, [0], :, :] = original_latents[:, :, [0], :, :].to(latent_model_input)
if image_cond_latents is not None and not use_context_schedule:
if i2v_condition_type == "latent_concat":
latent_image_input = (torch.cat([image_cond_latents] * 2) if cfg_enabled else image_cond_latents)
if self.transformer.in_channels == 33:
i2v_mask = torch.cat([i2v_mask] * 2) if cfg_enabled else i2v_mask
latent_image_input = torch.cat([latent_image_input, i2v_mask], dim=1)
latent_model_input = torch.cat([latent_model_input, latent_image_input], dim=1)
if self.transformer.guidance_embed:
if cfg_enabled:
guidance_expand = (
torch.tensor([embedded_guidance_scale] * latents.shape[0] * 2, dtype=self.base_dtype, device=device)
* 1000.0
)
else:
guidance_expand = (
torch.tensor([embedded_guidance_scale] * latents.shape[0], dtype=self.base_dtype, device=device)
* 1000.0
)
else:
guidance_expand = None
if use_context_schedule:
counter = torch.zeros_like(latent_model_input)[:, :16]
noise_pred = torch.zeros_like(latent_model_input)[:, :16]
print("noise_pred", noise_pred.shape)
print("counter", counter.shape)
context_queue = list(context(
i, num_inference_steps, latents.shape[2], context_frames, context_stride, context_overlap,
))
if image_cond_latents is not None:
latent_image_input = (
torch.cat([image_cond_latents] * 2) if cfg_enabled else image_cond_latents
)
if i2v_mask is not None:
i2v_mask = torch.cat([i2v_mask] * 2) if cfg_enabled else i2v_mask
for c in context_queue:
partial_latent_model_input = latent_model_input[:, :, c]
if i2v_mask is not None:
#doesn't work properly
new_mask = torch.zeros_like(i2v_mask)
new_mask[..., 0, :, :] = 1.0
new_image_input = torch.zeros_like(latent_image_input)
new_image_input[..., 0, :, :] = latent_image_input[..., 0, :, :]
partial_latent_image_input = torch.cat([new_image_input[..., c, :, :], new_mask[..., c, :, :]], dim=1)
partial_latent_model_input = torch.cat([partial_latent_model_input, partial_latent_image_input], dim=1)
#print("partial_latent_model_input", partial_latent_model_input.shape)
with torch.autocast(
device_type="cuda", dtype=self.base_dtype, enabled=True):
noise_pred_context = self.transformer(
partial_latent_model_input,
t_expand,
text_states=input_prompt_embeds,
text_mask=input_prompt_mask,
text_states_2=input_prompt_embeds_2,
freqs_cos=freqs_cos,
freqs_sin=freqs_sin,
guidance=guidance_expand,
stg_block_idx=stg_block_idx,
stg_mode=stg_mode,
return_dict=True,
)["x"]
window_mask = torch.ones_like(noise_pred_context)
# Apply left-side blending for all except first chunk
if min(c) > 0:
ramp_up = torch.linspace(0, 1, context_overlap, device=noise_pred_context.device)
ramp_up = ramp_up.view(1, 1, -1, 1, 1)
window_mask[:, :, :context_overlap] = ramp_up
# Apply right-side blending for all except last chunk
if max(c) < latent_video_length - 1:
ramp_down = torch.linspace(1, 0, context_overlap, device=noise_pred_context.device)
ramp_down = ramp_down.view(1, 1, -1, 1, 1)
window_mask[:, :, -context_overlap:] = ramp_down
noise_pred[:, :, c, :, :] += noise_pred_context * window_mask
counter[:, :, c, :, :] += window_mask
noise_pred = noise_pred.float()
noise_pred /= counter
else:
# predict the noise residual
with torch.autocast(
device_type="cuda", dtype=self.base_dtype, enabled=True
):
if batched_cfg or not cfg_enabled:
noise_pred = self.transformer( # For an input image (129, 192, 336) (1, 256, 256)
latent_model_input, # [2, 16, 33, 24, 42]
t_expand, # [2]
text_states=input_prompt_embeds, # [2, 256, 4096]
text_mask=input_prompt_mask, # [2, 256]
text_states_2=input_prompt_embeds_2, # [2, 768]
freqs_cos=freqs_cos, # [seqlen, head_dim]
freqs_sin=freqs_sin, # [seqlen, head_dim]
guidance=guidance_expand,
stg_block_idx=stg_block_idx,
stg_mode=stg_mode,
return_dict=True,
)["x"]
else:
uncond = self.transformer(
latent_model_input[0].unsqueeze(0),
t_expand[0].unsqueeze(0),
text_states=input_prompt_embeds[0].unsqueeze(0),
text_mask=input_prompt_mask[0].unsqueeze(0),
text_states_2=input_prompt_embeds_2[0].unsqueeze(0),
freqs_cos=freqs_cos,
freqs_sin=freqs_sin,
guidance=guidance_expand[0].unsqueeze(0),
stg_block_idx=stg_block_idx,
stg_mode=stg_mode,
return_dict=True,
)["x"]
cond = self.transformer(
latent_model_input[1].unsqueeze(0),
t_expand[1].unsqueeze(0),
text_states=input_prompt_embeds[1].unsqueeze(0),
text_mask=input_prompt_mask[1].unsqueeze(0),
text_states_2=input_prompt_embeds_2[1].unsqueeze(0),
freqs_cos=freqs_cos,
freqs_sin=freqs_sin,
guidance=guidance_expand[1].unsqueeze(0),
stg_block_idx=stg_block_idx,
stg_mode=stg_mode,
return_dict=True,
)["x"]
# perform guidance
if cfg_enabled and not self.do_spatio_temporal_guidance:
if batched_cfg:
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + self.guidance_scale * (
noise_pred_text - noise_pred_uncond
)
else:
noise_pred = uncond + self.guidance_scale * (cond - uncond)
elif self.do_classifier_free_guidance and self.do_spatio_temporal_guidance:
raise NotImplementedError
noise_pred_uncond, noise_pred_text, noise_pred_perturb = noise_pred.chunk(3)
noise_pred = noise_pred_uncond + self.guidance_scale * (
noise_pred_text - noise_pred_uncond
) + self._stg_scale * (
noise_pred_text - noise_pred_perturb
)
elif self.do_spatio_temporal_guidance and stg_enabled:
noise_pred_text, noise_pred_perturb = noise_pred.chunk(2)
noise_pred = noise_pred_text + self._stg_scale * (
noise_pred_text - noise_pred_perturb
)
# compute the previous noisy sample x_t -> x_t-1
if image_cond_latents is not None and i2v_condition_type == "token_replace":
latents = self.scheduler.step(
noise_pred[:, :, 1:, :, :], t, latents[:, :, 1:, :, :], **extra_step_kwargs, return_dict=False
)[0]
latents = torch.concat(
[original_image_latents, latents], dim=2
)
else:
latents = self.scheduler.step(
noise_pred, t, latents, **extra_step_kwargs, return_dict=False
)[0]
if callback_on_step_end is not None:
callback_kwargs = {}
for k in callback_on_step_end_tensor_inputs:
callback_kwargs[k] = locals()[k]
callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
latents = callback_outputs.pop("latents", latents)
prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
negative_prompt_embeds = callback_outputs.pop(
"negative_prompt_embeds", negative_prompt_embeds
)
# call the callback, if provided
if i == len(timesteps) - 1 or (
(i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0
):
if progress_bar is not None:
progress_bar.update()
if callback is not None:
if leapfusion_img2vid or i2v_condition_type == "token_replace":
callback_latent = (latent_model_input[:, :, 1:, :, :] - noise_pred[:, :, 1:, :, :] * t / 1000).detach()[0].permute(1,0,2,3)
else:
callback_latent = (latent_model_input[:, :16, :, :, :] - noise_pred * t / 1000).detach()[0].permute(1,0,2,3)
callback(
i,
callback_latent,
None,
num_inference_steps
)
else:
comfy_pbar.update(1)
if leapfusion_img2vid or i2v_condition_type == "latent_concat":
latents = latents[:, :, 1:, :, :]
return latents