Files
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

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