1008 lines
41 KiB
Python
1008 lines
41 KiB
Python
#
|
|
# SPDX-FileCopyrightText: Copyright (c) 1993-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# 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.
|
|
#
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import inspect
|
|
import os
|
|
import random
|
|
import time
|
|
import warnings
|
|
from typing import Any, List
|
|
|
|
import numpy as np
|
|
import tensorrt as trt
|
|
import torch
|
|
from cuda.bindings import runtime as cudart
|
|
from diffusers.video_processor import VideoProcessor
|
|
from flux.content_filters import PixtralContentFilter
|
|
from tqdm import tqdm
|
|
|
|
from demo_diffusion import path as path_module
|
|
from demo_diffusion.model import (
|
|
AutoencoderKLWanEncoderModel,
|
|
AutoencoderKLWanModel,
|
|
CosmosTransformerModel,
|
|
T5Model,
|
|
make_tokenizer,
|
|
)
|
|
from demo_diffusion.pipeline.diffusion_pipeline import DiffusionPipeline
|
|
from demo_diffusion.pipeline.type import PIPELINE_TYPE
|
|
|
|
TRT_LOGGER = trt.Logger(trt.Logger.ERROR)
|
|
|
|
|
|
class CosmosPipeline(DiffusionPipeline):
|
|
"""
|
|
Application showcasing the acceleration of Cosmos pipelines using Nvidia TensorRT.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
version="cosmos-predict2-2b",
|
|
pipeline_type=PIPELINE_TYPE.TXT2IMG,
|
|
guidance_scale=6.0,
|
|
max_sequence_length=512,
|
|
t5_weight_streaming_budget_percentage=None,
|
|
transformer_weight_streaming_budget_percentage=None,
|
|
**kwargs,
|
|
):
|
|
"""
|
|
Initializes the Cosmos pipeline.
|
|
|
|
Args:
|
|
version (`str`, defaults to `cosmos-1.0-7B`)
|
|
Version of the underlying Cosmos model.
|
|
guidance_scale (`float`, defaults to 3.5):
|
|
Guidance scale is enabled by setting as > 1.
|
|
Higher guidance scale encourages to generate images that are closely linked to the text prompt, usually at the expense of lower image quality.
|
|
max_sequence_length (`int`, defaults to 512):
|
|
Maximum sequence length to use with the `prompt`.
|
|
t5_weight_streaming_budget_percentage (`int`, defaults to None):
|
|
Weight streaming budget as a percentage of the size of total streamable weights for the T5 model.
|
|
transformer_weight_streaming_budget_percentage (`int`, defaults to None):
|
|
Weight streaming budget as a percentage of the size of total streamable weights for the Transformer model.
|
|
"""
|
|
super().__init__(
|
|
version=version,
|
|
pipeline_type=pipeline_type,
|
|
text_encoder_weight_streaming_budget_percentage=t5_weight_streaming_budget_percentage,
|
|
denoiser_weight_streaming_budget_percentage=transformer_weight_streaming_budget_percentage,
|
|
**kwargs,
|
|
)
|
|
self.guidance_scale = guidance_scale
|
|
self.max_sequence_length = max_sequence_length
|
|
self.do_classifier_free_guidance = self.guidance_scale > 1
|
|
|
|
# WAR ONNX export error: Exporting the operator 'aten::_upsample_nearest_exact2d' to ONNX opset version 19 is not supported
|
|
self.config["vae_torch_fallback"] = True
|
|
self.config["vae_encoder_torch_fallback"] = True
|
|
|
|
@classmethod
|
|
def FromArgs(cls, args: argparse.Namespace, pipeline_type: PIPELINE_TYPE) -> CosmosPipeline:
|
|
"""Factory method to construct a `CosmosPipeline` object from parsed arguments.
|
|
|
|
Overrides:
|
|
DiffusionPipeline.FromArgs
|
|
"""
|
|
MAX_BATCH_SIZE = 4
|
|
DEVICE = "cuda"
|
|
DO_RETURN_LATENTS = False
|
|
|
|
# Resolve all paths.
|
|
dd_path = path_module.resolve_path(
|
|
cls.get_model_names(pipeline_type), args, pipeline_type, cls._get_pipeline_uid(args.version)
|
|
)
|
|
|
|
return cls(
|
|
dd_path=dd_path,
|
|
version=args.version,
|
|
pipeline_type=pipeline_type,
|
|
guidance_scale=args.guidance_scale,
|
|
max_sequence_length=args.max_sequence_length,
|
|
bf16=args.bf16,
|
|
low_vram=args.low_vram,
|
|
torch_fallback=args.torch_fallback,
|
|
weight_streaming=args.ws,
|
|
t5_weight_streaming_budget_percentage=args.t5_ws_percentage,
|
|
transformer_weight_streaming_budget_percentage=args.transformer_ws_percentage,
|
|
max_batch_size=MAX_BATCH_SIZE,
|
|
denoising_steps=args.denoising_steps,
|
|
scheduler=args.scheduler,
|
|
device=DEVICE,
|
|
output_dir=args.output_dir,
|
|
hf_token=args.hf_token,
|
|
verbose=args.verbose,
|
|
nvtx_profile=args.nvtx_profile,
|
|
use_cuda_graph=args.use_cuda_graph,
|
|
framework_model_dir=args.framework_model_dir,
|
|
return_latents=DO_RETURN_LATENTS,
|
|
torch_inference=args.torch_inference,
|
|
)
|
|
|
|
@classmethod
|
|
def get_model_names(cls, pipeline_type: PIPELINE_TYPE, controlnet_type: str = None) -> List[str]:
|
|
"""Return a list of model names used by this pipeline.
|
|
|
|
Overrides:
|
|
DiffusionPipeline.get_model_names
|
|
"""
|
|
if pipeline_type.is_video2world():
|
|
return ["vae_encoder", "t5", "transformer", "vae"]
|
|
return ["t5", "transformer", "vae"]
|
|
|
|
def download_onnx_models(self, model_name: str, model_config: dict[str, Any]) -> None:
|
|
raise ValueError("ONNX models download is not supported for the Cosmos Pipeline")
|
|
|
|
def _initialize_models(self, framework_model_dir, int8, fp8, fp4):
|
|
# Load text tokenizer(s)
|
|
self.tokenizer = make_tokenizer(
|
|
self.version, self.pipeline_type, self.hf_token, framework_model_dir, tokenizer_type="t5"
|
|
)
|
|
|
|
# Load pipeline models
|
|
models_args = {
|
|
"version": self.version,
|
|
"pipeline": self.pipeline_type,
|
|
"device": self.device,
|
|
"hf_token": self.hf_token,
|
|
"verbose": self.verbose,
|
|
"framework_model_dir": framework_model_dir,
|
|
"max_batch_size": self.max_batch_size,
|
|
}
|
|
|
|
self.fp16 = True if not self.bf16 else False
|
|
self.tf32 = True
|
|
if "t5" in self.stages:
|
|
# Known accuracy issues with FP16
|
|
self.models["t5"] = T5Model(
|
|
**models_args,
|
|
fp16=self.fp16,
|
|
tf32=self.tf32,
|
|
bf16=self.bf16,
|
|
text_maxlen=self.max_sequence_length,
|
|
use_attention_mask=True,
|
|
)
|
|
|
|
if "transformer" in self.stages:
|
|
self.models["transformer"] = CosmosTransformerModel(
|
|
**models_args,
|
|
bf16=self.bf16,
|
|
fp16=self.fp16,
|
|
int8=int8,
|
|
fp8=fp8,
|
|
tf32=self.tf32,
|
|
text_maxlen=self.max_sequence_length,
|
|
weight_streaming=self.weight_streaming,
|
|
weight_streaming_budget_percentage=self.denoiser_weight_streaming_budget_percentage,
|
|
)
|
|
|
|
if "vae" in self.stages:
|
|
self.models["vae"] = AutoencoderKLWanModel(**models_args, fp16=False, tf32=self.tf32, bf16=self.bf16)
|
|
|
|
if "vae_encoder" in self.stages:
|
|
self.models["vae_encoder"] = AutoencoderKLWanEncoderModel(
|
|
**models_args, fp16=False, tf32=self.tf32, bf16=self.bf16
|
|
)
|
|
|
|
self.vae_scale_factor_temporal = (
|
|
2 ** sum(self.models["vae"].config["temperal_downsample"])
|
|
if "vae" in self.stages and self.models["vae"] is not None
|
|
else 4
|
|
)
|
|
self.vae_scale_factor_spatial = (
|
|
2 ** len(self.models["vae"].config["temperal_downsample"])
|
|
if "vae" in self.stages and self.models["vae"] is not None
|
|
else 8
|
|
)
|
|
|
|
self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor_spatial)
|
|
|
|
def encode_video(self, video):
|
|
self.profile_start("vae_encoder", color="red")
|
|
cast_to = (
|
|
torch.float16
|
|
if self.models["vae_encoder"].fp16
|
|
else torch.bfloat16 if self.models["vae_encoder"].bf16 else torch.float32
|
|
)
|
|
video = video.to(dtype=cast_to)
|
|
if self.torch_inference:
|
|
image_latents = self.torch_models["vae_encoder"](video)
|
|
else:
|
|
image_latents = self.run_engine("vae_encoder", {"images": video})["latent"]
|
|
self.profile_stop("vae_encoder")
|
|
return image_latents
|
|
|
|
def initialize_latents_text2image(
|
|
self,
|
|
batch_size,
|
|
num_channels_latents,
|
|
num_latent_frames,
|
|
latent_height,
|
|
latent_width,
|
|
latents_dtype=torch.float32,
|
|
):
|
|
latents_shape = (batch_size, num_channels_latents, num_latent_frames, latent_height, latent_width)
|
|
latents = torch.randn(
|
|
latents_shape,
|
|
device=self.device,
|
|
dtype=latents_dtype,
|
|
generator=self.generator,
|
|
)
|
|
|
|
return latents * self.scheduler.config.sigma_max
|
|
|
|
def initialize_latents_video2world(
|
|
self,
|
|
video,
|
|
batch_size,
|
|
num_channels_latents,
|
|
num_frames,
|
|
latent_height,
|
|
latent_width,
|
|
latents_dtype=torch.float32,
|
|
do_classifier_free_guidance=False,
|
|
):
|
|
num_cond_frames = video.size(2)
|
|
if num_cond_frames >= num_frames:
|
|
# Take the last `num_frames` frames for conditioning
|
|
num_cond_latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1
|
|
video = video[:, :, -num_frames:]
|
|
else:
|
|
num_cond_latent_frames = (num_cond_frames - 1) // self.vae_scale_factor_temporal + 1
|
|
num_padding_frames = num_frames - num_cond_frames
|
|
last_frame = video[:, :, -1:]
|
|
padding = last_frame.repeat(1, 1, num_padding_frames, 1, 1)
|
|
video = torch.cat([video, padding], dim=2)
|
|
|
|
# Encode video
|
|
with self.model_memory_manager(["vae_encoder"], low_vram=self.low_vram):
|
|
video_latents = self.encode_video(
|
|
video=video,
|
|
)
|
|
|
|
latents_mean = (
|
|
torch.tensor(self.models["vae"].config["latents_mean"])
|
|
.view(1, self.models["vae"].config["z_dim"], 1, 1, 1)
|
|
.to(self.device, latents_dtype)
|
|
)
|
|
latents_std = (
|
|
torch.tensor(self.models["vae"].config["latents_std"])
|
|
.view(1, self.models["vae"].config["z_dim"], 1, 1, 1)
|
|
.to(self.device, latents_dtype)
|
|
)
|
|
init_latents = (video_latents - latents_mean) / latents_std * self.scheduler.config.sigma_data
|
|
|
|
num_latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1
|
|
shape = (batch_size, num_channels_latents, num_latent_frames, latent_height, latent_width)
|
|
|
|
latents = torch.randn(
|
|
shape,
|
|
device=self.device,
|
|
dtype=latents_dtype,
|
|
generator=self.generator,
|
|
)
|
|
|
|
latents = latents * self.scheduler.config.sigma_max
|
|
|
|
padding_shape = (batch_size, 1, num_latent_frames, latent_height, latent_width)
|
|
ones_padding = latents.new_ones(padding_shape)
|
|
zeros_padding = latents.new_zeros(padding_shape)
|
|
|
|
cond_indicator = latents.new_zeros(1, 1, latents.size(2), 1, 1)
|
|
cond_indicator[:, :, :num_cond_latent_frames] = 1.0
|
|
cond_mask = cond_indicator * ones_padding + (1 - cond_indicator) * zeros_padding
|
|
|
|
uncond_indicator = uncond_mask = None
|
|
if do_classifier_free_guidance:
|
|
uncond_indicator = latents.new_zeros(1, 1, latents.size(2), 1, 1)
|
|
uncond_indicator[:, :, :num_cond_latent_frames] = 1.0
|
|
uncond_mask = uncond_indicator * ones_padding + (1 - uncond_indicator) * zeros_padding
|
|
|
|
return latents, init_latents, cond_indicator, uncond_indicator, cond_mask, uncond_mask
|
|
|
|
# Copied from https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/flux/pipeline_flux_img2img.py#L416C1
|
|
def get_timesteps(self, num_inference_steps, strength):
|
|
# get the original timestep using init_timestep
|
|
init_timestep = min(num_inference_steps * strength, num_inference_steps)
|
|
|
|
t_start = int(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, num_inference_steps - t_start
|
|
|
|
def _duplicate_text_embeddings(self, batch_size, text_embeddings, num_outputs_per_prompt):
|
|
# duplicate text embeddings for each generation per prompt, using mps friendly method
|
|
_, seq_len, _ = text_embeddings.shape
|
|
text_embeddings = text_embeddings.repeat(1, num_outputs_per_prompt, 1)
|
|
text_embeddings = text_embeddings.view(batch_size * num_outputs_per_prompt, seq_len, -1)
|
|
return text_embeddings
|
|
|
|
def _prepare_timesteps(self, num_inference_steps):
|
|
"""Prepare timesteps for the scheduler."""
|
|
sigmas_dtype = torch.float32 if torch.backends.mps.is_available() else torch.float64
|
|
sigmas = torch.linspace(0, 1, num_inference_steps, dtype=sigmas_dtype)
|
|
accept_sigmas = "sigmas" in set(inspect.signature(self.scheduler.set_timesteps).parameters.keys())
|
|
if not accept_sigmas:
|
|
raise ValueError(
|
|
f"The current scheduler class {self.scheduler.__class__}'s `set_timesteps` does not support custom"
|
|
f" sigmas schedules. Please check whether you are using the correct scheduler."
|
|
)
|
|
self.scheduler.set_timesteps(sigmas=sigmas, device=self.device)
|
|
timesteps = self.scheduler.timesteps
|
|
num_inference_steps = len(timesteps)
|
|
if self.scheduler.config.get("final_sigmas_type", "zero") == "sigma_min":
|
|
# Replace the last sigma (which is zero) with the minimum sigma value
|
|
self.scheduler.sigmas[-1] = self.scheduler.sigmas[-2]
|
|
return timesteps, num_inference_steps
|
|
|
|
def _encode_text_prompts(self, prompt, negative_prompt, batch_size, num_outputs_per_prompt):
|
|
"""Encode text prompts using T5 encoder."""
|
|
with self.model_memory_manager(["t5"], low_vram=self.low_vram):
|
|
text_embeddings = self.encode_prompt(prompt)
|
|
text_embeddings = self._duplicate_text_embeddings(batch_size, text_embeddings, num_outputs_per_prompt)
|
|
negative_text_embeddings = None
|
|
if self.do_classifier_free_guidance:
|
|
negative_text_embeddings = self.encode_prompt(negative_prompt)
|
|
negative_text_embeddings = self._duplicate_text_embeddings(
|
|
batch_size, negative_text_embeddings, num_outputs_per_prompt
|
|
)
|
|
return text_embeddings, negative_text_embeddings
|
|
|
|
def _get_latents_normalization_params(self, device, dtype):
|
|
"""Get latents normalization parameters from VAE config."""
|
|
latents_mean = (
|
|
torch.tensor(self.models["vae"].config["latents_mean"])
|
|
.view(1, self.models["vae"].config["z_dim"], 1, 1, 1)
|
|
.to(device, dtype)
|
|
)
|
|
latents_std = (
|
|
torch.tensor(self.models["vae"].config["latents_std"])
|
|
.view(1, self.models["vae"].config["z_dim"], 1, 1, 1)
|
|
.to(device, dtype)
|
|
)
|
|
return latents_mean, latents_std
|
|
|
|
def _normalize_and_decode_latents(self, latents, is_video2world=False):
|
|
"""Normalize latents and decode using VAE."""
|
|
latents_mean, latents_std = self._get_latents_normalization_params(latents.device, latents.dtype)
|
|
|
|
if is_video2world:
|
|
# For video2world: latents * std / sigma_data + mean
|
|
latents = latents * latents_std / self.scheduler.config.sigma_data + latents_mean
|
|
else:
|
|
# For text2image: latents / (1/std) / sigma_data + mean
|
|
latents_std_inv = 1.0 / latents_std
|
|
latents = latents / latents_std_inv / self.scheduler.config.sigma_data + latents_mean
|
|
|
|
with self.model_memory_manager(["vae"], low_vram=self.low_vram):
|
|
video = self.decode_latent(latents)
|
|
|
|
return video
|
|
|
|
def encode_prompt(self, prompt, encoder="t5"):
|
|
self.profile_start(encoder, color="green")
|
|
|
|
def tokenize(prompt):
|
|
text_inputs = self.tokenizer(
|
|
prompt,
|
|
padding="max_length",
|
|
max_length=self.max_sequence_length,
|
|
truncation=True,
|
|
return_overflowing_tokens=False,
|
|
return_length=False,
|
|
return_tensors="pt",
|
|
)
|
|
text_input_ids = text_inputs.input_ids.to(self.device)
|
|
attention_mask = text_inputs.attention_mask.bool().to(self.device)
|
|
|
|
untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids.to(self.device)
|
|
if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(
|
|
text_input_ids, untruncated_ids
|
|
):
|
|
removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.max_sequence_length - 1 : -1])
|
|
warnings.warn(
|
|
"The following part of your input was truncated because `max_sequence_length` is set to "
|
|
f"{self.max_sequence_length} tokens: {removed_text}"
|
|
)
|
|
|
|
if self.torch_inference or self.torch_fallback[encoder]:
|
|
text_encoder_output = self.torch_models[encoder](
|
|
text_input_ids, attention_mask=attention_mask
|
|
).last_hidden_state
|
|
else:
|
|
# NOTE: output tensor for the encoder must be cloned because it will be overwritten when called again for prompt2
|
|
text_encoder_output = self.run_engine(
|
|
encoder, {"input_ids": text_input_ids, "attention_mask": attention_mask}
|
|
)["text_embeddings"]
|
|
|
|
lengths = attention_mask.sum(dim=1).cpu()
|
|
for i, length in enumerate(lengths):
|
|
text_encoder_output[i, length:] = 0
|
|
return text_encoder_output
|
|
|
|
# Tokenize prompt
|
|
text_encoder_output = tokenize(prompt)
|
|
|
|
self.profile_stop(encoder)
|
|
return (
|
|
text_encoder_output.to(torch.float16)
|
|
if self.fp16
|
|
else text_encoder_output.to(torch.bfloat16) if self.bf16 else text_encoder_output.to(torch.float32)
|
|
)
|
|
|
|
def denoise_latent(
|
|
self,
|
|
latents,
|
|
timesteps,
|
|
text_embeddings,
|
|
negative_text_embeddings,
|
|
padding_mask,
|
|
denoiser="transformer",
|
|
):
|
|
do_autocast = self.torch_inference != "" and self.models[denoiser].fp16
|
|
with torch.autocast("cuda", enabled=do_autocast, dtype=torch.float32):
|
|
self.profile_start(denoiser, color="blue")
|
|
|
|
for step_index, timestep in tqdm(enumerate(timesteps), total=len(timesteps), desc="Denoising"):
|
|
# Prepare latents
|
|
cast_to = (
|
|
torch.float16
|
|
if self.models[denoiser].fp16
|
|
else torch.bfloat16 if self.models[denoiser].bf16 else torch.float32
|
|
)
|
|
current_sigma = self.scheduler.sigmas[step_index]
|
|
current_t = current_sigma / (current_sigma + 1)
|
|
c_in = 1 - current_t
|
|
c_skip = 1 - current_t
|
|
c_out = -current_t
|
|
timestep_inp = current_t.expand(latents.shape[0]).to(cast_to) # [B, 1, T, 1, 1]
|
|
latents_input = (latents * c_in).to(cast_to)
|
|
# prepare inputs
|
|
params = {
|
|
"hidden_states": latents_input,
|
|
"timestep": timestep_inp,
|
|
"encoder_hidden_states": text_embeddings,
|
|
"padding_mask": padding_mask,
|
|
}
|
|
|
|
if self.torch_inference or self.torch_fallback[denoiser]:
|
|
noise_pred = self.torch_models[denoiser](**params)["sample"]
|
|
else:
|
|
noise_pred = self.run_engine(denoiser, params)["latent"].clone()
|
|
|
|
noise_pred = (c_skip * latents + c_out * noise_pred.float()).to(cast_to)
|
|
if self.do_classifier_free_guidance:
|
|
params = {
|
|
"hidden_states": latents_input,
|
|
"timestep": timestep_inp,
|
|
"encoder_hidden_states": negative_text_embeddings,
|
|
"padding_mask": padding_mask,
|
|
}
|
|
|
|
# Predict the noise residual
|
|
if self.torch_inference or self.torch_fallback[denoiser]:
|
|
noise_pred_uncond = self.torch_models[denoiser](**params)["sample"]
|
|
else:
|
|
noise_pred_uncond = self.run_engine(denoiser, params)["latent"].clone()
|
|
|
|
noise_pred_uncond = (c_skip * latents + c_out * noise_pred_uncond.float()).to(cast_to)
|
|
noise_pred = noise_pred + self.guidance_scale * (noise_pred - noise_pred_uncond)
|
|
|
|
noise_pred = (latents - noise_pred) / current_sigma
|
|
latents = self.scheduler.step(noise_pred, timestep, latents, return_dict=False)[0]
|
|
|
|
self.profile_stop(denoiser)
|
|
return latents.to(dtype=torch.bfloat16) if self.bf16 else latents.to(dtype=torch.float32)
|
|
|
|
def denoise_latent_video2world(
|
|
self,
|
|
latents,
|
|
timesteps,
|
|
text_embeddings,
|
|
negative_text_embeddings,
|
|
padding_mask,
|
|
fps,
|
|
cond_mask,
|
|
uncond_mask,
|
|
t_conditioning,
|
|
cond_indicator,
|
|
conditioning_latents,
|
|
uncond_indicator,
|
|
unconditioning_latents,
|
|
denoiser="transformer",
|
|
):
|
|
do_autocast = self.torch_inference != "" and self.models[denoiser].fp16
|
|
with torch.autocast("cuda", enabled=do_autocast, dtype=torch.float32):
|
|
self.profile_start(denoiser, color="blue")
|
|
|
|
for step_index, timestep in tqdm(enumerate(timesteps), total=len(timesteps), desc="Denoising"):
|
|
# Prepare latents
|
|
cast_to = (
|
|
torch.float16
|
|
if self.models[denoiser].fp16
|
|
else torch.bfloat16 if self.models[denoiser].bf16 else torch.float32
|
|
)
|
|
current_sigma = self.scheduler.sigmas[step_index]
|
|
current_t = current_sigma / (current_sigma + 1)
|
|
c_in = 1 - current_t
|
|
c_skip = 1 - current_t
|
|
c_out = -current_t
|
|
timestep_inp = current_t.view(1, 1, 1, 1, 1).expand(
|
|
latents.size(0), -1, latents.size(2), -1, -1
|
|
) # [B, 1, T, 1, 1]
|
|
latents_input = latents * c_in
|
|
latents_input = (cond_indicator * conditioning_latents + (1 - cond_indicator) * latents_input).to(
|
|
cast_to
|
|
)
|
|
timestep_inp = (cond_indicator * t_conditioning + (1 - cond_indicator) * timestep_inp).to(cast_to)
|
|
|
|
# prepare inputs
|
|
params = {
|
|
"hidden_states": latents_input,
|
|
"timestep": timestep_inp,
|
|
"encoder_hidden_states": text_embeddings,
|
|
"padding_mask": padding_mask,
|
|
"fps": fps,
|
|
"condition_mask": cond_mask,
|
|
}
|
|
|
|
if self.torch_inference or self.torch_fallback[denoiser]:
|
|
noise_pred = self.torch_models[denoiser](**params)["sample"]
|
|
else:
|
|
noise_pred = self.run_engine(denoiser, params)["latent"].clone()
|
|
|
|
noise_pred = (c_skip * latents + c_out * noise_pred.float()).to(cast_to)
|
|
noise_pred = cond_indicator * conditioning_latents + (1 - cond_indicator) * noise_pred
|
|
if self.do_classifier_free_guidance:
|
|
latents_input = latents * c_in
|
|
latents_input = (
|
|
uncond_indicator * unconditioning_latents + (1 - uncond_indicator) * latents_input
|
|
).to(cast_to)
|
|
timestep_inp = (uncond_indicator * t_conditioning + (1 - uncond_indicator) * timestep_inp).to(
|
|
cast_to
|
|
)
|
|
params = {
|
|
"hidden_states": latents_input,
|
|
"timestep": timestep_inp,
|
|
"encoder_hidden_states": negative_text_embeddings,
|
|
"padding_mask": padding_mask,
|
|
"fps": fps,
|
|
"condition_mask": uncond_mask,
|
|
}
|
|
|
|
# Predict the noise residual
|
|
if self.torch_inference or self.torch_fallback[denoiser]:
|
|
noise_pred_uncond = self.torch_models[denoiser](**params)["sample"]
|
|
else:
|
|
noise_pred_uncond = self.run_engine(denoiser, params)["latent"].clone()
|
|
|
|
noise_pred_uncond = (c_skip * latents + c_out * noise_pred_uncond.float()).to(cast_to)
|
|
noise_pred_uncond = (
|
|
uncond_indicator * unconditioning_latents + (1 - uncond_indicator) * noise_pred_uncond
|
|
)
|
|
noise_pred = noise_pred + self.guidance_scale * (noise_pred - noise_pred_uncond)
|
|
|
|
noise_pred = (latents - noise_pred) / current_sigma
|
|
latents = self.scheduler.step(noise_pred, timestep, latents, return_dict=False)[0]
|
|
|
|
self.profile_stop(denoiser)
|
|
return latents.to(dtype=torch.bfloat16) if self.bf16 else latents.to(dtype=torch.float32)
|
|
|
|
def decode_latent(self, latents, decoder="vae"):
|
|
self.profile_start(decoder, color="red")
|
|
cast_to = (
|
|
torch.float16
|
|
if self.models[decoder].fp16
|
|
else torch.bfloat16 if self.models[decoder].bf16 else torch.float32
|
|
)
|
|
latents = latents.to(dtype=cast_to)
|
|
|
|
if self.torch_inference or self.torch_fallback[decoder]:
|
|
video = self.torch_models[decoder](latents, return_dict=False)[0]
|
|
else:
|
|
video = self.run_engine(decoder, {"latent": latents})["frames"]
|
|
|
|
self.profile_stop(decoder)
|
|
return video
|
|
|
|
def post_process_video(self, video):
|
|
# Post-process video
|
|
video = self.video_processor.postprocess_video(video, output_type="np")
|
|
video = (video * 255).astype(np.uint8)
|
|
video_batch = []
|
|
for vid in video:
|
|
# vid = self.safety_checker.check_video_safety(vid)
|
|
video_batch.append(vid)
|
|
video = np.stack(video_batch).astype(np.float32) / 255.0 * 2 - 1
|
|
video = torch.from_numpy(video).permute(0, 4, 1, 2, 3)
|
|
video = self.video_processor.postprocess_video(video, output_type="pil")
|
|
|
|
if self.pipeline_type.is_video2world():
|
|
return video
|
|
|
|
image = [batch[0] for batch in video]
|
|
if isinstance(video, torch.Tensor):
|
|
image = torch.stack(image)
|
|
elif isinstance(video, np.ndarray):
|
|
image = np.stack(image)
|
|
|
|
return image
|
|
|
|
def _finalize_generation(self, video, walltime_ms, num_inference_steps, batch_size, warmup, save_output):
|
|
"""Handle post-processing, saving, and performance reporting."""
|
|
if not warmup:
|
|
self.print_summary(num_inference_steps, walltime_ms, batch_size)
|
|
if not self.return_latents and save_output:
|
|
# post-process video
|
|
processed_output = self.post_process_video(video)
|
|
|
|
# save output
|
|
if self.pipeline_type.is_video2world():
|
|
return (processed_output[0], walltime_ms)
|
|
return (np.array(processed_output), walltime_ms)
|
|
|
|
def _check_integrity(self, images):
|
|
integrity_checker = PixtralContentFilter(self.device)
|
|
for image in images:
|
|
image_ = np.array(image) / 255.0
|
|
image_ = 2 * image_ - 1
|
|
image_ = torch.from_numpy(image_).to(self.device, dtype=torch.float32).permute(0, 3, 1, 2)
|
|
if integrity_checker.test_image(image_):
|
|
raise ValueError("Your image has been flagged. Choose another prompt/image or try again.")
|
|
|
|
def save_images(self, prompt, images, check_integrity=False):
|
|
if check_integrity:
|
|
self._check_integrity(images)
|
|
for image in images:
|
|
self.save_image(image, self.pipeline_type.name.lower(), prompt, self.seed)
|
|
|
|
def save_video(
|
|
self,
|
|
prompt,
|
|
videos,
|
|
check_integrity=False,
|
|
):
|
|
for frames in videos:
|
|
if check_integrity:
|
|
self._check_integrity([frames])
|
|
prompt_prefix = "".join(set([prompt[i].replace(" ", "_")[:10] for i in range(len(prompt))]))
|
|
video_name_prefix = "-".join(
|
|
[self.pipeline_type.name.lower(), "fp16", str(self.seed), str(random.randint(1000, 9999))]
|
|
)
|
|
video_name_suffix = "torch" if self.torch_inference else "trt"
|
|
video_path = prompt_prefix + "-" + video_name_prefix + "-" + video_name_suffix + ".gif"
|
|
print(f"Saving video to: {video_path}")
|
|
frames[0].save(
|
|
os.path.join(self.output_dir, video_path),
|
|
save_all=True,
|
|
optimize=False,
|
|
append_images=frames[1:],
|
|
loop=0,
|
|
)
|
|
|
|
def print_summary(self, denoising_steps, walltime_ms, batch_size):
|
|
print("|-----------------|--------------|")
|
|
print("| {:^15} | {:^12} |".format("Module", "Latency"))
|
|
print("|-----------------|--------------|")
|
|
for stage in self.stages:
|
|
print(
|
|
"| {:^15} | {:>9.2f} ms |".format(
|
|
stage + " x " + str(denoising_steps) if stage == "transformer" else stage,
|
|
cudart.cudaEventElapsedTime(self.events[stage][0], self.events[stage][1])[1],
|
|
)
|
|
)
|
|
print("|-----------------|--------------|")
|
|
print("| {:^15} | {:>9.2f} ms |".format("Pipeline", walltime_ms))
|
|
print("|-----------------|--------------|")
|
|
print("Throughput: {:.2f} image/s".format(batch_size * 1000.0 / walltime_ms))
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
negative_prompt,
|
|
image_height,
|
|
image_width,
|
|
num_frames=1,
|
|
num_images_per_prompt=1,
|
|
save_image=True,
|
|
warmup=False,
|
|
):
|
|
batch_size = len(prompt)
|
|
|
|
# Spatial dimensions of latent tensor
|
|
num_latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1
|
|
latent_height = image_height // self.vae_scale_factor_spatial
|
|
latent_width = image_width // self.vae_scale_factor_spatial
|
|
|
|
num_inference_steps = self.denoising_steps
|
|
|
|
with torch.inference_mode(), trt.Runtime(TRT_LOGGER):
|
|
torch.cuda.synchronize()
|
|
e2e_tic = time.perf_counter()
|
|
|
|
# Prepare timesteps
|
|
timesteps, num_inference_steps = self._prepare_timesteps(num_inference_steps)
|
|
|
|
# T5 text encoder
|
|
text_embeddings, negative_text_embeddings = self._encode_text_prompts(
|
|
prompt, negative_prompt, batch_size, num_images_per_prompt
|
|
)
|
|
|
|
num_channels_latents = self.models["transformer"].config["in_channels"]
|
|
latents_dtype = torch.float16 if self.fp16 else torch.bfloat16 if self.bf16 else torch.float32
|
|
|
|
# Initialize latents
|
|
latents = self.initialize_latents_text2image(
|
|
batch_size=batch_size,
|
|
num_channels_latents=num_channels_latents,
|
|
num_latent_frames=num_latent_frames,
|
|
latent_height=latent_height,
|
|
latent_width=latent_width,
|
|
latents_dtype=latents_dtype,
|
|
)
|
|
padding_mask = latents.new_zeros(1, 1, image_height, image_width, dtype=latents_dtype)
|
|
|
|
# denoiser
|
|
with self.model_memory_manager(["transformer"], low_vram=self.low_vram):
|
|
latents = self.denoise_latent(
|
|
latents,
|
|
timesteps,
|
|
text_embeddings,
|
|
negative_text_embeddings,
|
|
padding_mask,
|
|
)
|
|
|
|
# VAE decode latent
|
|
video = self._normalize_and_decode_latents(latents, is_video2world=False)
|
|
|
|
torch.cuda.synchronize()
|
|
e2e_toc = time.perf_counter()
|
|
|
|
walltime_ms = (e2e_toc - e2e_tic) * 1000.0
|
|
return self._finalize_generation(
|
|
video,
|
|
walltime_ms,
|
|
num_inference_steps,
|
|
batch_size,
|
|
warmup,
|
|
save_image,
|
|
)
|
|
|
|
def generate_video(
|
|
self,
|
|
prompt,
|
|
negative_prompt,
|
|
image_height,
|
|
image_width,
|
|
input_image=None,
|
|
input_video=None,
|
|
num_frames=1,
|
|
fps=16,
|
|
num_videos_per_prompt=1,
|
|
sigma_conditioning=0.0001,
|
|
save_video=True,
|
|
warmup=False,
|
|
):
|
|
batch_size = len(prompt)
|
|
|
|
# Spatial dimensions of latent tensor
|
|
latent_height = image_height // self.vae_scale_factor_spatial
|
|
latent_width = image_width // self.vae_scale_factor_spatial
|
|
|
|
num_inference_steps = self.denoising_steps
|
|
|
|
with torch.inference_mode(), trt.Runtime(TRT_LOGGER):
|
|
torch.cuda.synchronize()
|
|
e2e_tic = time.perf_counter()
|
|
|
|
# Prepare timesteps
|
|
timesteps, num_inference_steps = self._prepare_timesteps(num_inference_steps)
|
|
|
|
# T5 text encoder
|
|
text_embeddings, negative_text_embeddings = self._encode_text_prompts(
|
|
prompt, negative_prompt, batch_size, num_videos_per_prompt
|
|
)
|
|
|
|
num_channels_latents = self.models["transformer"].config["in_channels"] - 1
|
|
latents_dtype = torch.float16 if self.fp16 else torch.bfloat16 if self.bf16 else torch.float32
|
|
|
|
# Process input conditioning
|
|
if input_image is not None:
|
|
video = (
|
|
self.video_processor.preprocess(input_image, image_height, image_width)
|
|
.unsqueeze(2)
|
|
.to(device=self.device, dtype=latents_dtype)
|
|
)
|
|
elif input_video is not None:
|
|
video = self.video_processor.preprocess_video(input_video, image_height, image_width).to(
|
|
device=self.device, dtype=latents_dtype
|
|
)
|
|
else:
|
|
raise ValueError("Video2world pipeline requires either input_image or input_video to be provided")
|
|
|
|
# Initialize latents
|
|
latents, conditioning_latents, cond_indicator, uncond_indicator, cond_mask, uncond_mask = (
|
|
self.initialize_latents_video2world(
|
|
video,
|
|
batch_size=batch_size,
|
|
num_channels_latents=num_channels_latents,
|
|
num_frames=num_frames,
|
|
latent_height=latent_height,
|
|
latent_width=latent_width,
|
|
latents_dtype=latents_dtype,
|
|
do_classifier_free_guidance=self.do_classifier_free_guidance,
|
|
)
|
|
)
|
|
unconditioning_latents = None
|
|
|
|
cond_mask = cond_mask.to(latents_dtype)
|
|
if self.do_classifier_free_guidance:
|
|
uncond_mask = uncond_mask.to(latents_dtype)
|
|
unconditioning_latents = conditioning_latents
|
|
|
|
padding_mask = latents.new_zeros(1, 1, image_height, image_width, dtype=latents_dtype)
|
|
sigma_conditioning = torch.tensor(sigma_conditioning, dtype=torch.float32, device=self.device)
|
|
t_conditioning = sigma_conditioning / (sigma_conditioning + 1)
|
|
|
|
# denoiser
|
|
with self.model_memory_manager(["transformer"], low_vram=self.low_vram):
|
|
latents = self.denoise_latent_video2world(
|
|
latents,
|
|
timesteps,
|
|
text_embeddings,
|
|
negative_text_embeddings,
|
|
padding_mask,
|
|
fps,
|
|
cond_mask,
|
|
uncond_mask,
|
|
t_conditioning,
|
|
cond_indicator,
|
|
conditioning_latents,
|
|
uncond_indicator,
|
|
unconditioning_latents,
|
|
)
|
|
|
|
# VAE decode latent
|
|
video = self._normalize_and_decode_latents(latents, is_video2world=True)
|
|
|
|
torch.cuda.synchronize()
|
|
e2e_toc = time.perf_counter()
|
|
|
|
walltime_ms = (e2e_toc - e2e_tic) * 1000.0
|
|
return self._finalize_generation(
|
|
video,
|
|
walltime_ms,
|
|
num_inference_steps,
|
|
batch_size,
|
|
warmup,
|
|
save_video,
|
|
)
|
|
|
|
def infer(
|
|
self,
|
|
prompt,
|
|
negative_prompt,
|
|
image_height,
|
|
image_width,
|
|
input_image=None,
|
|
input_video=None,
|
|
num_frames=1,
|
|
fps=16,
|
|
num_images_per_prompt=1,
|
|
num_videos_per_prompt=1,
|
|
sigma_conditioning=0.0001,
|
|
warmup=False,
|
|
save_output=True,
|
|
):
|
|
"""
|
|
Run the diffusion pipeline.
|
|
|
|
Args:
|
|
prompt (str):
|
|
The text prompt to guide image generation.
|
|
negative_prompt (str):
|
|
The prompt not to guide the image generation. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
|
|
less than `1`).
|
|
input_image (image):
|
|
Input image used to initialize the latents.
|
|
input_video (video):
|
|
Input video used to initialize the latents.
|
|
image_height (int):
|
|
Height (in pixels) of the image to be generated. Must be a multiple of 8.
|
|
image_width (int):
|
|
Width (in pixels) of the image to be generated. Must be a multiple of 8.
|
|
num_frames (int):
|
|
The number of frames in the generated video.
|
|
fps (int):
|
|
The frames per second of the generated video.
|
|
num_images_per_prompt (int):
|
|
The number of images to generate per prompt.
|
|
num_videos_per_prompt (int):
|
|
The number of videos to generate per prompt.
|
|
sigma_conditioning (`float`, defaults to `0.0001`):
|
|
The sigma value used for scaling conditioning latents. Ideally, it should not be changed or should be
|
|
set to a small value close to zero.
|
|
warmup (bool):
|
|
Indicate if this is a warmup run.
|
|
save_output (bool):
|
|
Save the generated image or video (if applicable)
|
|
"""
|
|
if self.pipeline_type.is_txt2img():
|
|
return self.generate_image(
|
|
prompt,
|
|
negative_prompt,
|
|
image_height,
|
|
image_width,
|
|
num_frames,
|
|
num_images_per_prompt,
|
|
save_output,
|
|
warmup,
|
|
)
|
|
elif self.pipeline_type.is_video2world():
|
|
return self.generate_video(
|
|
prompt,
|
|
negative_prompt,
|
|
image_height,
|
|
image_width,
|
|
input_image,
|
|
input_video,
|
|
num_frames,
|
|
fps,
|
|
num_videos_per_prompt,
|
|
sigma_conditioning,
|
|
save_output,
|
|
warmup,
|
|
)
|
|
else:
|
|
raise ValueError(f"Invalid pipeline type: {self.pipeline_type}")
|
|
|
|
def run(
|
|
self,
|
|
prompt,
|
|
negative_prompt,
|
|
height,
|
|
width,
|
|
batch_count,
|
|
num_warmup_runs,
|
|
use_cuda_graph,
|
|
**kwargs,
|
|
):
|
|
if self.low_vram and self.use_cuda_graph:
|
|
print("[W] Using low_vram, use_cuda_graph will be disabled")
|
|
self.use_cuda_graph = False
|
|
num_warmup_runs = max(1, num_warmup_runs) if use_cuda_graph else num_warmup_runs
|
|
if num_warmup_runs > 0:
|
|
print("[I] Warming up ..")
|
|
for _ in range(num_warmup_runs):
|
|
self.infer(prompt, negative_prompt, height, width, warmup=True, **kwargs)
|
|
|
|
outputs = []
|
|
for _ in range(batch_count):
|
|
print("[I] Running Cosmos pipeline")
|
|
if self.nvtx_profile:
|
|
cudart.cudaProfilerStart()
|
|
output, _ = self.infer(prompt, negative_prompt, height, width, warmup=False, **kwargs)
|
|
outputs.append(output)
|
|
if self.nvtx_profile:
|
|
cudart.cudaProfilerStop()
|
|
|
|
return outputs
|