ColabWan / models /longcat /longcat_main.py
1ripon1's picture
Upload folder using huggingface_hub
7344bef verified
Raw
History Blame Contribute Delete
50.2 kB
import json
import math
import os
from typing import Optional, List
import numpy as np
import torch
from accelerate import init_empty_weights
from mmgp import offload
from tqdm import tqdm
import librosa
import pyloudnorm as pyln
import scipy.signal as ss
import torch.nn.functional as F
from transformers import AutoFeatureExtractor, Wav2Vec2FeatureExtractor, WhisperModel
from shared.utils import files_locator as fl
from ..wan.modules.t5 import T5EncoderModel
from .modules.longcat_video_dit import LongCatVideoTransformer3DModel
from .modules.avatar.longcat_video_dit_avatar import LongCatVideoAvatarTransformer3DModel
from .modules.autoencoder_kl_wan import AutoencoderKLWan
from .modules.scheduling_flow_match_euler_discrete import FlowMatchEulerDiscreteScheduler
from .audio_process.wav2vec2 import Wav2Vec2ModelWrapper
from ..qwen.convert_diffusers_qwen_vae import convert_state_dict
from shared.utils.text_encoder_cache import TextEncoderCache
def _load_json_config(path):
with open(path, "r", encoding="utf-8") as f:
cfg = json.load(f)
cfg.pop("_class_name", None)
cfg.pop("_diffusers_version", None)
cfg.pop("architectures", None)
cfg.pop("model_max_length", None)
return cfg
def retrieve_latents(
encoder_output: torch.Tensor,
generator: Optional[torch.Generator] = None,
sample_mode: str = "sample",
):
if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
return encoder_output.latent_dist.sample(generator)
if hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
return encoder_output.latent_dist.mode()
if hasattr(encoder_output, "latents"):
return encoder_output.latents
raise AttributeError("Could not access latents of provided encoder_output")
def optimized_scale(positive_flat, negative_flat):
dot_product = torch.sum(positive_flat * negative_flat, dim=1, keepdim=True)
squared_norm = torch.sum(negative_flat ** 2, dim=1, keepdim=True) + 1e-8
return dot_product / squared_norm
LONGCAT_AVATAR_TYPES = {"longcat_avatar", "longcat_avatar_v1_5"}
class LongCatModel:
def __init__(
self,
checkpoint_dir,
model_filename=None,
model_type=None,
model_def=None,
base_model_type=None,
text_encoder_filename=None,
quantizeTransformer=False,
save_quantized=False,
dtype=torch.bfloat16,
VAE_dtype=torch.float32,
mixed_precision_transformer=False,
**kwargs,
):
self.device = torch.device("cuda")
self.dtype = dtype
self.VAE_dtype = VAE_dtype
self.model_def = model_def or {}
self.base_model_type = base_model_type
self.is_avatar = base_model_type in LONGCAT_AVATAR_TYPES
self.is_avatar_v1_5 = base_model_type == "longcat_avatar_v1_5"
self.audio_encoder_name = None
self.sparse_attention_enabled = bool(self.model_def.get("sparse_attention", False))
self._interrupt = False
self._reference_image = None
text_encoder_path = text_encoder_filename or fl.locate_file(
"umt5-xxl/models_t5_umt5-xxl-enc-bf16.safetensors", True
)
text_encoder_folder = self.model_def.get("text_encoder_folder")
if text_encoder_folder:
tokenizer_path = fl.locate_folder(text_encoder_folder)
else:
tokenizer_path = os.path.dirname(text_encoder_path)
self.text_encoder = T5EncoderModel(
text_len=512,
dtype=dtype,
device=torch.device("cpu"),
checkpoint_path=text_encoder_path,
tokenizer_path=tokenizer_path,
)
self.text_encoder_cache = TextEncoderCache()
transformer_config_path = self.model_def.get("transformer_config")
if not transformer_config_path:
transformer_config_path = (
"models/longcat/configs/longcat_avatar.json"
if self.is_avatar
else "models/longcat/configs/longcat_video.json"
)
transformer_cfg = _load_json_config(transformer_config_path)
if self.sparse_attention_enabled:
transformer_cfg["enable_bsa"] = True
sparse_params = self.model_def.get("sparse_attention_params")
if isinstance(sparse_params, dict) and sparse_params:
bsa_params = dict(transformer_cfg.get("bsa_params") or {})
bsa_params.update(sparse_params)
transformer_cfg["bsa_params"] = bsa_params
transformer_cls = (
LongCatVideoAvatarTransformer3DModel
if self.is_avatar
else LongCatVideoTransformer3DModel
)
with init_empty_weights(include_buffers=True):
transformer = transformer_cls(**transformer_cfg)
model_path = model_filename[0] if isinstance(model_filename, (list, tuple)) else model_filename
if model_path is None:
raise ValueError("Missing LongCat transformer weights path.")
offload.load_model_data(transformer, model_path, writable_tensors=False)
transformer._model_dtype = dtype
transformer.eval().requires_grad_(False)
self.transformer = transformer
if save_quantized:
from wgp import save_quantized_model
save_quantized_model(transformer, model_type, model_path, dtype, transformer_config_path)
vae_cfg_path = "models/longcat/configs/longcat_vae.json"
vae_weights = self.model_def.get("vae_URL")
if vae_weights:
vae_weights = fl.locate_file(vae_weights)
else:
for candidate in ["Wan2.1_VAE_bf16.safetensors", "Wan2.1_VAE.safetensors", "longcat_vae_bf16.safetensors"]:
vae_weights = fl.locate_file(candidate, error_if_none=False)
if vae_weights:
break
if not vae_weights:
raise FileNotFoundError("Unable to locate a compatible VAE weights file for LongCat.")
def preprocess_vae_sd(sd):
return convert_state_dict(sd)
self.vae = offload.fast_load_transformers_model(
vae_weights,
modelClass=AutoencoderKLWan,
defaultConfigPath=vae_cfg_path,
writable_tensors=False,
preprocess_sd=preprocess_vae_sd,
default_dtype=VAE_dtype,
)
self.vae = self.vae.to(dtype=VAE_dtype, device="cpu")
self.vae._model_dtype = VAE_dtype
self.vae._dtype = VAE_dtype
self.vae.eval().requires_grad_(False)
scheduler_cfg = _load_json_config(self.model_def.get("scheduler_config", "models/longcat/configs/longcat_scheduler.json"))
self.scheduler = FlowMatchEulerDiscreteScheduler(**scheduler_cfg)
self.num_timesteps = 1000
self.num_distill_sample_steps = int(self.model_def.get("num_distill_sample_steps", 8 if self.is_avatar_v1_5 else 50))
if self.is_avatar:
if self.is_avatar_v1_5:
whisper_folder_name = self.model_def.get("audio_encoder_folder", "whisper-large-v3")
whisper_folder = fl.locate_folder(whisper_folder_name)
whisper_model_path = fl.locate_file(os.path.join(whisper_folder_name, "model.safetensors"))
whisper_config_path = fl.locate_file(os.path.join(whisper_folder_name, "config.json"))
fl.locate_file(os.path.join(whisper_folder_name, "generation_config.json"))
fl.locate_file(os.path.join(whisper_folder_name, "preprocessor_config.json"))
self.audio_encoder_name = "whisper-large-v3"
self.audio_encoder = offload.fast_load_transformers_model(
whisper_model_path,
modelClass=WhisperModel,
defaultConfigPath=whisper_config_path,
modelPrefix="model",
writable_tensors=False,
default_dtype=dtype,
ignore_unused_weights=True,
)
if hasattr(self.audio_encoder, "decoder"):
del self.audio_encoder.decoder
self.audio_encoder._model_dtype = dtype
self.audio_encoder.eval().requires_grad_(False)
self.audio_feature_extractor = AutoFeatureExtractor.from_pretrained(whisper_folder, local_files_only=True)
else:
wav2vec_folder = fl.locate_folder("chinese-wav2vec2-base")
self.audio_encoder_name = "wav2vec2"
self.audio_encoder = Wav2Vec2ModelWrapper(wav2vec_folder)
self.audio_encoder.eval().requires_grad_(False)
if hasattr(self.audio_encoder, "feature_extractor"):
self.audio_encoder.feature_extractor._freeze_parameters()
self.audio_feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(wav2vec_folder, local_files_only=True)
else:
self.audio_encoder = None
self.audio_feature_extractor = None
self.vae_scale_factor_temporal = getattr(self.vae.config, "scale_factor_temporal", 4)
self.vae_scale_factor_spatial = getattr(self.vae.config, "scale_factor_spatial", 8)
self.transformer._interrupt_check = lambda: self._interrupt
def get_loras_transformer(self, get_model_recursive_prop, **kwargs):
if not self.is_avatar_v1_5:
return [], []
sample_solver = kwargs.get("sample_solver") or self.model_def.get("sample_solver", "distill")
lora_url = self.model_def.get("distill_lora_URL") if sample_solver == "distill" else None
return ([lora_url], ["1.0"]) if lora_url else ([], [])
def _clear_runtime_caches(self):
clear = getattr(self.transformer, "clear_runtime_caches", None)
if clear is not None:
clear()
def prepare_preview_payload(self, latents, preview_meta=None):
if not torch.is_tensor(latents):
return None
return {"latents": latents.float()}
def _apply_vae_tiling(self, VAE_tile_size):
if not hasattr(self.vae, "enable_tiling"):
return
if VAE_tile_size is None or VAE_tile_size == 0:
if hasattr(self.vae, "disable_tiling"):
self.vae.disable_tiling()
return
if isinstance(VAE_tile_size, dict):
tile = VAE_tile_size.get("tile_sample_min_size", None)
else:
tile = int(VAE_tile_size)
if tile and tile > 0:
stride = max(16, int(tile * 0.75))
self.vae.enable_tiling(
tile_sample_min_height=tile,
tile_sample_min_width=tile,
tile_sample_stride_height=stride,
tile_sample_stride_width=stride,
)
def _validate_sparse_attention(self, latents):
if not self.sparse_attention_enabled:
return
bsa_params = getattr(self.transformer.config, "bsa_params", None) or {}
chunk_shape_q = bsa_params.get("chunk_3d_shape_q")
chunk_shape_k = bsa_params.get("chunk_3d_shape_k")
chunk_shape = chunk_shape_q or chunk_shape_k
if not chunk_shape or latents.dim() != 5:
self.transformer.disable_bsa()
self.sparse_attention_enabled = False
print("Sparse attention disabled: missing BSA parameters.")
return
attn_mode = offload.shared_state.get("_attention", "auto")
require_grid_divisible = False
if attn_mode == "flash":
require_grid_divisible = True
elif attn_mode == "auto":
try:
from shared.attention import flash_attn_bsa_3d
except Exception:
flash_attn_bsa_3d = None
require_grid_divisible = flash_attn_bsa_3d is not None
patch_t, patch_h, patch_w = self.transformer.config.patch_size
n_t = latents.shape[2] // patch_t
n_h = latents.shape[3] // patch_h
n_w = latents.shape[4] // patch_w
cp_split_hw = getattr(self.transformer.config, "cp_split_hw", None)
if cp_split_hw:
if n_h % cp_split_hw[0] != 0 or n_w % cp_split_hw[1] != 0:
self.transformer.disable_bsa()
self.sparse_attention_enabled = False
print("Sparse attention disabled: cp_split_hw does not divide token grid.")
return
if require_grid_divisible:
shape_q = chunk_shape_q or chunk_shape
shape_k = chunk_shape_k or chunk_shape
if (
n_t % shape_q[0] != 0
or n_h % shape_q[1] != 0
or n_w % shape_q[2] != 0
or n_t % shape_k[0] != 0
or n_h % shape_k[1] != 0
or n_w % shape_k[2] != 0
):
self.transformer.disable_bsa()
self.sparse_attention_enabled = False
print("Sparse attention disabled: flash BSA needs token grid divisible by chunk shape.")
def _encode_prompt(
self,
prompt,
negative_prompt,
num_videos_per_prompt=1,
max_length=512,
device=None,
dtype=None,
):
device = device or self.device
dtype = dtype or self.dtype
def encode_fn(prompts):
ids, mask = self.text_encoder.tokenizer(
prompts,
return_mask=True,
add_special_tokens=True,
)
ids = ids.to(device)
mask = mask.to(device)
prompt_embeds = self.text_encoder.model(ids, mask).to(dtype)
return list(zip(prompt_embeds, mask))
prompt_list = [prompt] if isinstance(prompt, str) else prompt
batch_size = len(prompt_list)
prompt_contexts = self.text_encoder_cache.encode(
encode_fn,
prompt_list,
device=device,
)
prompt_embeds = torch.stack([ctx[0] for ctx in prompt_contexts], dim=0)
mask = torch.stack([ctx[1] for ctx in prompt_contexts], dim=0)
seq_len = prompt_embeds.shape[1]
prompt_embeds = prompt_embeds.unsqueeze(1)
if num_videos_per_prompt > 1:
prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1, 1)
prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, 1, seq_len, -1)
mask = mask.repeat(num_videos_per_prompt, 1)
neg_embeds = None
neg_mask = None
if negative_prompt is not None:
neg_list = [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt
if len(neg_list) == 1 and batch_size > 1:
neg_list = neg_list * batch_size
neg_contexts = self.text_encoder_cache.encode(
encode_fn,
neg_list,
device=device,
)
neg_embeds = torch.stack([ctx[0] for ctx in neg_contexts], dim=0)
neg_mask = torch.stack([ctx[1] for ctx in neg_contexts], dim=0)
neg_embeds = neg_embeds.unsqueeze(1)
if num_videos_per_prompt > 1:
neg_embeds = neg_embeds.repeat(1, num_videos_per_prompt, 1, 1)
neg_embeds = neg_embeds.view(batch_size * num_videos_per_prompt, 1, seq_len, -1)
neg_mask = neg_mask.repeat(num_videos_per_prompt, 1)
return prompt_embeds, mask, neg_embeds, neg_mask
def _prepare_latents(
self,
batch_size,
num_channels_latents,
height,
width,
num_frames,
dtype,
device,
generator,
latents=None,
image=None,
video=None,
num_cond_frames=0,
):
num_latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1
if latents is None:
shape = (
batch_size,
num_channels_latents,
num_latent_frames,
int(height) // self.vae_scale_factor_spatial,
int(width) // self.vae_scale_factor_spatial,
)
latents = torch.randn(shape, generator=generator, device=device, dtype=dtype)
else:
latents = latents.to(device=device, dtype=dtype)
num_cond_latents = 0
if image is not None or video is not None:
cond_latents = []
for i in range(batch_size):
if image is not None:
encoded_input = image[i].unsqueeze(0).unsqueeze(2)
else:
encoded_input = video[i][:, -num_cond_frames:].unsqueeze(0)
latent = retrieve_latents(
self.vae.encode(encoded_input),
generator,
sample_mode="argmax",
)
cond_latents.append(latent)
cond_latents = torch.cat(cond_latents, dim=0).to(dtype)
cond_latents = self.normalize_latents(cond_latents)
num_cond_latents = 1 + (num_cond_frames - 1) // self.vae_scale_factor_temporal
latents[:, :, :num_cond_latents] = cond_latents[:, :, :num_cond_latents]
return latents, num_cond_latents
def normalize_latents(self, latents):
latents_mean = (
torch.tensor(self.vae.config.latents_mean)
.view(1, self.vae.config.z_dim, 1, 1, 1)
.to(latents.device, latents.dtype)
)
latents_std = (
torch.tensor(self.vae.config.latents_std)
.view(1, self.vae.config.z_dim, 1, 1, 1)
.to(latents.device, latents.dtype)
)
return (latents - latents_mean) / latents_std
def denormalize_latents(self, latents):
latents_mean = (
torch.tensor(self.vae.config.latents_mean)
.view(1, self.vae.config.z_dim, 1, 1, 1)
.to(latents.device, latents.dtype)
)
latents_std = (
torch.tensor(self.vae.config.latents_std)
.view(1, self.vae.config.z_dim, 1, 1, 1)
.to(latents.device, latents.dtype)
)
return latents * latents_std + latents_mean
def _loudness_norm(self, audio_array, sr=16000, lufs=-23, threshold=100):
meter = pyln.Meter(sr)
loudness = meter.integrated_loudness(audio_array)
if not np.isfinite(loudness) or loudness > threshold:
return audio_array
return pyln.normalize.loudness(audio_array, loudness, lufs)
def _add_noise_floor(self, audio_array, noise_level=0.0001):
noise = np.random.normal(0, noise_level, size=audio_array.shape)
return audio_array + noise
def _smooth_transients(self, audio_array, sr=16000):
b, a = ss.butter(3, 3000 / (sr / 2))
return ss.lfilter(b, a, audio_array)
@staticmethod
def _interpolate_audio_state(audio_state, target_len):
if audio_state.shape[0] == target_len:
return audio_state
audio_state = audio_state.transpose(0, 1).unsqueeze(0).float()
audio_state = F.interpolate(audio_state, size=target_len, mode="linear", align_corners=False)
return audio_state.squeeze(0).transpose(0, 1)
@torch.no_grad()
def _get_audio_embedding_wav2vec(self, speech_array, fps=32, device="cpu", sample_rate=16000):
audio_duration = len(speech_array) / sample_rate
video_length = audio_duration * fps
speech_array = self._loudness_norm(speech_array, sample_rate)
speech_array = self._add_noise_floor(speech_array)
speech_array = self._smooth_transients(speech_array, sample_rate)
audio_feature = np.squeeze(
self.audio_feature_extractor(speech_array, sampling_rate=sample_rate).input_values
)
audio_feature = np.nan_to_num(audio_feature, nan=0.0, posinf=0.0, neginf=0.0)
audio_feature = torch.from_numpy(audio_feature).float().to(device=device)
audio_feature = audio_feature.unsqueeze(0)
embeddings = self.audio_encoder(audio_feature, seq_len=int(video_length), output_hidden_states=True)
audio_emb = torch.stack(embeddings.hidden_states[1:], dim=1).squeeze(0)
audio_emb = audio_emb.permute(1, 0, 2).contiguous()
audio_emb = torch.nan_to_num(audio_emb, nan=0.0, posinf=0.0, neginf=0.0)
return audio_emb
@torch.no_grad()
def _get_audio_embedding_whisper(self, speech_array, fps=25, device=None, sample_rate=16000):
device = device or self.device
audio_duration = len(speech_array) / sample_rate
video_length = max(int(round(audio_duration * fps)), 1)
encoder_length = max(int(math.ceil(audio_duration * 50)), 1)
speech_array = self._loudness_norm(speech_array, sample_rate)
speech_array = self._add_noise_floor(speech_array)
speech_array = self._smooth_transients(speech_array, sample_rate)
layer_groups = [(0, 8), (8, 16), (16, 24), (24, 32), (32, 33)]
audio_chunks = [[] for _ in layer_groups]
mel_chunk = 750 * 640
for start in range(0, len(speech_array), mel_chunk):
chunk = speech_array[start : start + mel_chunk]
features = self.audio_feature_extractor(
[chunk],
return_tensors="pt",
return_attention_mask=True,
sampling_rate=sample_rate,
)
input_features = features.input_features.to(device=device, dtype=self.dtype)
attention_mask = getattr(features, "attention_mask", None)
if attention_mask is not None:
attention_mask = attention_mask.to(device=device)
input_features = self.audio_encoder._mask_input_features(input_features, attention_mask=attention_mask)
encoder_dtype = getattr(self.audio_encoder.encoder, "dtype", self.dtype)
outputs = self.audio_encoder.encoder(
input_features.to(encoder_dtype),
head_mask=None,
output_attentions=False,
output_hidden_states=True,
return_dict=True,
)
chunk_states = outputs.hidden_states
for group_idx, (layer_start, layer_end) in enumerate(layer_groups):
layer_state = torch.stack(chunk_states[layer_start:layer_end], dim=0).mean(dim=0).squeeze(0)
audio_chunks[group_idx].append(layer_state.detach().to("cpu"))
del outputs, chunk_states, input_features, attention_mask, features
audio_layers = []
for chunks in audio_chunks:
layer_state = torch.cat(chunks, dim=0)[:encoder_length]
layer_state = self._interpolate_audio_state(layer_state, video_length).to(dtype=self.dtype)
audio_layers.append(layer_state)
audio_emb = torch.stack(audio_layers, dim=1).contiguous()
return torch.nan_to_num(audio_emb, nan=0.0, posinf=0.0, neginf=0.0)
@torch.no_grad()
def _get_audio_embedding(self, speech_array, fps=32, device="cpu", sample_rate=16000):
if self.is_avatar_v1_5:
return self._get_audio_embedding_whisper(speech_array, fps=fps, device=device, sample_rate=sample_rate)
return self._get_audio_embedding_wav2vec(speech_array, fps=fps, device=device, sample_rate=sample_rate)
def _build_audio_windows(self, audio_path, frame_num, fps, window_start_frame_no, audio_stride):
speech_array, sr = librosa.load(audio_path, sr=16000)
target_len = int((window_start_frame_no + frame_num) / fps * sr)
if len(speech_array) < target_len:
pad = target_len - len(speech_array)
speech_array = np.pad(speech_array, (0, pad), mode="constant")
audio_device = self.device if self.is_avatar_v1_5 else "cpu"
full_audio_emb = self._get_audio_embedding(speech_array, fps=fps * audio_stride, device=audio_device, sample_rate=sr)
if torch.isnan(full_audio_emb).any():
raise ValueError("Audio embedding contains NaNs.")
audio_start_idx = window_start_frame_no * audio_stride
audio_end_idx = audio_start_idx + audio_stride * frame_num
window = self.transformer.audio_window if hasattr(self.transformer, "audio_window") else 5
offsets = torch.arange(window, device=full_audio_emb.device) - window // 2
centers = torch.arange(audio_start_idx, audio_end_idx, audio_stride, device=full_audio_emb.device).unsqueeze(1) + offsets.unsqueeze(0)
centers = torch.clamp(centers, min=0, max=full_audio_emb.shape[0] - 1)
audio_emb = full_audio_emb[centers][None, ...].to("cpu")
del full_audio_emb
return audio_emb
def _build_ref_target_masks(self, height, width, speakers_bboxes=None):
if not speakers_bboxes:
speakers_bboxes = {"person1": [5, 10, 45, 90], "person2": [55, 10, 95, 90]}
human_masks = []
background_mask = torch.zeros([height, width])
for _, person_bbox in speakers_bboxes.items():
y_min, x_min, y_max, x_max = person_bbox
x_min, y_min, x_max, y_max = max(x_min, 5), max(y_min, 5), min(x_max, 95), min(y_max, 95)
x_min, y_min, x_max, y_max = (
int(height * x_min / 100),
int(width * y_min / 100),
int(height * x_max / 100),
int(width * y_max / 100),
)
human_mask = torch.zeros([height, width])
human_mask[int(x_min) : int(x_max), int(y_min) : int(y_max)] = 1
background_mask += human_mask
human_masks.append(human_mask)
background_mask = torch.where(background_mask > 0, torch.tensor(0), torch.tensor(1))
human_masks.append(background_mask)
return torch.stack(human_masks, dim=0)
def get_timesteps_sigmas(self, sampling_steps, use_distill=False):
if use_distill:
distill_indices = torch.arange(1, self.num_distill_sample_steps + 1, dtype=torch.float32)
distill_indices = (distill_indices * (self.num_timesteps // self.num_distill_sample_steps)).round().long()
if self.is_avatar_v1_5:
distill_indices = self.num_timesteps - distill_indices
sigmas = torch.flip(torch.linspace(0, 1, self.num_timesteps), [0])
sigmas = torch.flip(sigmas[distill_indices], [0]).float()
if sampling_steps != self.num_distill_sample_steps:
inference_indices = np.linspace(0, self.num_distill_sample_steps, num=sampling_steps, endpoint=False)
sigmas = sigmas[np.floor(inference_indices).astype(np.int64)]
else:
inference_indices = np.linspace(0, self.num_distill_sample_steps, num=sampling_steps, endpoint=False)
inference_indices = np.floor(inference_indices).astype(np.int64)
sigmas = torch.flip(distill_indices, [0])[inference_indices].float() / self.num_timesteps
else:
sigmas = torch.linspace(1, 0.001, sampling_steps, dtype=torch.float32)
return sigmas.to(dtype=torch.float32, device="cpu")
@torch.no_grad()
def generate(
self,
seed=None,
input_prompt="",
n_prompt="",
sampling_steps=50,
input_ref_images=None,
input_frames=None,
input_frames2=None,
input_masks=None,
input_masks2=None,
input_video=None,
image_start=None,
image_end=None,
input_ref_masks=None,
input_faces=None,
input_custom=None,
frame_num=93,
batch_size=1,
height=480,
width=832,
fit_into_canvas=None,
alt_prompt=None,
guide_scale=4.0,
guide2_scale=None,
guide3_scale=None,
shift=None,
audio_cfg_scale=None,
joint_pass=False,
VAE_tile_size=None,
prefix_frames_count=0,
conditioning_latents_size=0,
callback=None,
embedded_guidance_scale=None,
enable_RIFLEx=None,
cfg_star_switch=False,
cfg_zero_step=-1,
apg_switch=None,
perturbation_switch=None,
perturbation_layers=None,
perturbation_start=None,
perturbation_end=None,
switch_threshold=None,
switch2_threshold=None,
guide_phases=None,
model_switch_phase=None,
alt_guide_scale=None,
input_waveform=None,
input_waveform_sample_rate=None,
audio_guide=None,
audio_guide2=None,
audio_prompt_type=None,
audio_proj=None,
audio_scale=None,
audio_context_lens=None,
context_scale=None,
control_scale_alt=None,
alt_scale=None,
motion_amplitude=None,
model_mode=None,
causal_block_size=None,
causal_attention=None,
fps=None,
window_start_frame_no=0,
sample_solver=None,
reference_image_enabled=None,
ref_img_index=None,
mask_frame_range=None,
overlapped_latents=None,
return_latent_slice=None,
speakers_bboxes=None,
window_no=None,
overlap_noise=None,
overlap_size=None,
color_correction_strength=None,
input_video_is_hdr=False,
lora_dir=None,
keep_frames_parsed=None,
model_filename=None,
model_type=None,
loras_slists=None,
NAG_scale=None,
NAG_tau=None,
NAG_alpha=None,
image_mode=None,
video_prompt_type=None,
offloadobj=None,
set_header_text=None,
pre_video_frame=None,
prefix_video=None,
original_input_ref_images=None,
image_refs_relative_size=None,
outpainting_dims=None,
face_arc_embeds=None,
custom_settings=None,
**kwargs,
):
if self._interrupt:
return None
if seed is None or seed == -1:
seed = torch.seed() % (2**32 - 1)
generator = torch.Generator(device=self.device)
generator.manual_seed(seed)
if fps is None or fps == 0:
fps = self.model_def.get("fps", 15 if not self.is_avatar else 16)
if frame_num % self.vae_scale_factor_temporal != 1:
frame_num = frame_num // self.vae_scale_factor_temporal * self.vae_scale_factor_temporal + 1
frame_num = max(frame_num, 1)
sample_solver = self.model_def.get("sample_solver", "auto") if sample_solver is None else sample_solver
if sample_solver in (None, ""):
sample_solver = "default"
prompt_embeds, prompt_mask, neg_embeds, neg_mask = self._encode_prompt(
input_prompt,
n_prompt if n_prompt is not None else "",
device=self.device,
dtype=self.dtype,
)
any_guidance = guide_scale is not None and guide_scale > 1
if self.is_avatar:
if audio_cfg_scale is None:
audio_cfg_scale = 1.0
any_guidance = any_guidance or audio_cfg_scale > 1
if reference_image_enabled is None:
reference_image_enabled = self.model_def.get("reference_image_enabled", True)
reference_image_enabled = self.is_avatar and bool(reference_image_enabled)
reference_features_enabled = reference_image_enabled
ref_img_index = self.model_def.get("ref_img_index", 10) if ref_img_index is None else ref_img_index
mask_frame_range = self.model_def.get("mask_frame_range", 3) if mask_frame_range is None else mask_frame_range
if not reference_features_enabled:
ref_img_index = None
mask_frame_range = None
ref_image = None
if reference_image_enabled:
if input_ref_images is not None:
ref_list = input_ref_images if isinstance(input_ref_images, list) else [input_ref_images]
if len(ref_list) > 0:
ref_image = ref_list[0]
if window_no == 1:
if ref_image is not None:
self._reference_image = (
ref_image.detach().to("cpu") if torch.is_tensor(ref_image) else ref_image
)
else:
self._reference_image = None
if ref_image is None and self._reference_image is not None:
ref_image = self._reference_image
cond_video = None
num_cond_frames = 0
if input_video is not None:
cond_video = input_video
num_cond_frames = max(int(prefix_frames_count or 0), 0)
self._apply_vae_tiling(VAE_tile_size)
if cond_video is not None:
cond_video = cond_video.to(device=self.device, dtype=self.VAE_dtype)
if cond_video.dim() == 4:
cond_video = cond_video.unsqueeze(0)
cond_video_frames = cond_video.shape[2]
if num_cond_frames <= 0:
cond_video = None
num_cond_frames = 0
else:
num_cond_frames = min(num_cond_frames, cond_video_frames)
if sample_solver not in ("auto", "default", "enhance_hf", "distill"):
raise ValueError(f"Unsupported scheduler '{sample_solver}' for LongCat.")
if self.model_def.get("distill_only", False) and sample_solver != "distill":
raise ValueError("LongCat Avatar 1.5 currently supports the distilled scheduler only.")
use_distill = sample_solver == "distill"
enhance_hf = sample_solver == "enhance_hf"
if sample_solver == "auto":
enhance_hf = cond_video is not None and num_cond_frames > 1
if use_distill and enhance_hf:
raise ValueError("distill and enhance_hf schedules cannot both be enabled.")
image_cond = None
ref_latent = None
num_ref_latents = 0
if reference_image_enabled and ref_image is not None:
if not torch.is_tensor(ref_image):
ref_image = torch.from_numpy(np.array(ref_image)).float().div_(127.5).sub_(1.).movedim(-1, 0)
ref_image = ref_image.to(device=self.device, dtype=self.VAE_dtype)
if ref_image.dim() == 3:
ref_image = ref_image.unsqueeze(0)
if ref_image.dim() == 5 and ref_image.shape[2] == 1:
ref_image = ref_image.squeeze(2)
if ref_image.dim() != 4:
raise ValueError("reference image must be CHW or BCHW for LongCat.")
if ref_image.shape[0] == 1 and batch_size > 1:
ref_image = ref_image.repeat(batch_size, 1, 1, 1)
elif ref_image.shape[0] != batch_size:
raise ValueError("reference image batch size does not match prompts.")
if cond_video is None:
image_cond = ref_image
else:
ref_image_5d = ref_image.unsqueeze(2)
ref_latent = retrieve_latents(self.vae.encode(ref_image_5d), generator, sample_mode="argmax")
ref_latent = self.normalize_latents(ref_latent).to(torch.float32)
num_ref_latents = 1
if torch.is_tensor(overlapped_latents):
if overlapped_latents.dim() == 4:
overlapped_latents = overlapped_latents.unsqueeze(0)
if overlapped_latents.dim() != 5:
overlapped_latents = None
else:
overlapped_latents = None
cond_image_frames = 1 if image_cond is not None else num_cond_frames
expected_num_cond_latents = (
1 + (cond_image_frames - 1) // self.vae_scale_factor_temporal if cond_image_frames > 0 else 0
)
use_overlap_latents = (
overlapped_latents is not None and expected_num_cond_latents > 0 and image_cond is None
)
if use_overlap_latents:
lat_h = int(height) // self.vae_scale_factor_spatial
lat_w = int(width) // self.vae_scale_factor_spatial
if (
overlapped_latents.shape[1] != self.transformer.config.in_channels
or overlapped_latents.shape[3] != lat_h
or overlapped_latents.shape[4] != lat_w
):
use_overlap_latents = False
if use_overlap_latents:
num_latent_frames = (frame_num - 1) // self.vae_scale_factor_temporal + 1
shape = (
batch_size,
self.transformer.config.in_channels,
num_latent_frames,
lat_h,
lat_w,
)
latents = torch.randn(shape, generator=generator, device=self.device, dtype=torch.float32)
overlap_latents = overlapped_latents.to(device=self.device, dtype=torch.float32)
if overlap_latents.shape[0] == 1 and batch_size > 1:
overlap_latents = overlap_latents.repeat(batch_size, 1, 1, 1, 1)
if overlap_latents.shape[2] > expected_num_cond_latents:
overlap_latents = overlap_latents[:, :, -expected_num_cond_latents:]
cond_latents = None
if cond_video is not None and overlap_latents.shape[2] < expected_num_cond_latents:
cond_latents_list = []
for i in range(batch_size):
encoded_input = cond_video[i][:, -cond_image_frames:].unsqueeze(0)
latent = retrieve_latents(
self.vae.encode(encoded_input),
generator,
sample_mode="argmax",
)
cond_latents_list.append(latent)
cond_latents = torch.cat(cond_latents_list, dim=0).to(torch.float32)
cond_latents = self.normalize_latents(cond_latents)
overlap_len = min(overlap_latents.shape[2], cond_latents.shape[2])
if overlap_len > 0:
cond_latents[:, :, -overlap_len:] = overlap_latents[:, :, -overlap_len:]
else:
cond_latents = overlap_latents
num_cond_latents = min(cond_latents.shape[2], num_latent_frames) if cond_latents is not None else 0
if num_cond_latents > 0:
latents[:, :, :num_cond_latents] = cond_latents[:, :, -num_cond_latents:]
else:
latents, num_cond_latents = self._prepare_latents(
batch_size=batch_size,
num_channels_latents=self.transformer.config.in_channels,
height=height,
width=width,
num_frames=frame_num,
dtype=torch.float32,
device=self.device,
generator=generator,
latents=None,
image=image_cond,
video=None if image_cond is not None else cond_video,
num_cond_frames=cond_image_frames,
)
if reference_image_enabled and ref_latent is None and self.is_avatar and num_cond_latents > 1:
ref_latent = latents[:, :, :1].clone()
num_ref_latents = 1
if ref_latent is not None:
num_cond_latents += num_ref_latents
latents = torch.cat([ref_latent, latents], dim=2)
self._validate_sparse_attention(latents)
sigmas = self.get_timesteps_sigmas(sampling_steps, use_distill=use_distill)
self.scheduler.set_timesteps(sampling_steps, sigmas=sigmas, device=self.device)
timesteps = self.scheduler.timesteps
if enhance_hf:
num_tail_uniform_steps = max(3, min(15, int(len(timesteps) * 0.2)))
tail_uniform_start = float(timesteps.max()) * 0.5
tail_uniform_end = 0
timesteps_uniform_tail = list(
np.linspace(
tail_uniform_start,
tail_uniform_end,
num_tail_uniform_steps,
dtype=np.float32,
endpoint=(tail_uniform_end != 0),
)
)
timesteps_uniform_tail = [
torch.tensor(t, device=self.device, dtype=torch.float32).unsqueeze(0)
for t in timesteps_uniform_tail
]
filtered_timesteps = [
timestep.unsqueeze(0).to(self.device) for timestep in timesteps if timestep > tail_uniform_start
]
timesteps = torch.cat(filtered_timesteps + timesteps_uniform_tail)
self.scheduler.timesteps = timesteps
self.scheduler.sigmas = torch.cat(
[timesteps / self.num_timesteps, torch.zeros(1, device=timesteps.device)]
)
audio_emb = None
ref_target_masks = None
if self.is_avatar:
if audio_guide is None:
raise ValueError("Audio guide is required for LongCat Avatar.")
audio_stride = int(self.model_def.get("audio_stride", 1 if self.is_avatar_v1_5 else 2))
audio_emb = self._build_audio_windows(
audio_guide, frame_num, fps, window_start_frame_no, audio_stride
)
if audio_guide2 is not None or self.model_def.get("multi_speakers_only", False):
if audio_guide2 is None:
raise ValueError("Second audio guide is required for LongCat Avatar Multi.")
audio_emb2 = self._build_audio_windows(
audio_guide2, frame_num, fps, window_start_frame_no, audio_stride
)
audio_emb = torch.cat([audio_emb, audio_emb2], dim=0)
ref_target_masks = self._build_ref_target_masks(height, width, speakers_bboxes)
if ref_target_masks is not None:
ref_target_masks = ref_target_masks.to(self.device)
if self.is_avatar_v1_5 and offloadobj is not None:
offloadobj.unload_all()
audio_emb = audio_emb.to(self.device, dtype=self.dtype)
latents = latents.to(self.device, dtype=self.dtype)
prompt_embeds = prompt_embeds.to(self.device)
prompt_mask = prompt_mask.to(self.device)
if neg_embeds is None:
neg_embeds = prompt_embeds
neg_mask = prompt_mask
else:
neg_embeds = neg_embeds.to(self.device)
neg_mask = neg_mask.to(self.device)
ref_kwargs = {}
if self.is_avatar and num_ref_latents > 0:
ref_kwargs = {
"num_ref_latents": num_ref_latents,
"ref_img_index": ref_img_index,
"mask_frame_range": mask_frame_range,
}
callback(-1, None, True, override_num_inference_steps = len(timesteps))
with tqdm(total=len(timesteps), desc="Denoising") as progress_bar:
for i, t in enumerate(timesteps):
if self._interrupt:
return None
def _aborted(outputs):
if outputs is None:
return True
if isinstance(outputs, (list, tuple)):
return any(item is None for item in outputs)
return outputs is None
timestep = t.expand(latents.shape[0]).to(self.dtype)
if num_cond_latents > 0:
timestep = timestep[:, None].expand(-1, latents.shape[2]).clone()
timestep[:, :num_cond_latents] = 0
if self.is_avatar and audio_emb is not None and any_guidance:
audio_cond = audio_emb.to(self.device, dtype=self.dtype)
audio_uncond = torch.zeros_like(audio_cond)
x_list = [latents, latents, latents]
ctx_list = [prompt_embeds, neg_embeds, neg_embeds]
mask_list = [prompt_mask, neg_mask, neg_mask]
audio_list = [audio_cond, audio_cond, audio_uncond]
ref_list = [ref_target_masks, ref_target_masks, ref_target_masks]
if joint_pass:
outputs = self.transformer(
hidden_states=x_list,
timestep=[timestep] * len(x_list),
encoder_hidden_states=ctx_list,
encoder_attention_mask=mask_list,
num_cond_latents=[num_cond_latents] * len(x_list),
audio_embs=audio_list,
ref_target_masks=ref_list,
**ref_kwargs,
)
if _aborted(outputs):
return None
else:
outputs = []
for x_i, ctx_i, mask_i, audio_i, ref_i in zip(
x_list, ctx_list, mask_list, audio_list, ref_list
):
output = self.transformer(
hidden_states=x_i,
timestep=timestep,
encoder_hidden_states=ctx_i,
encoder_attention_mask=mask_i,
num_cond_latents=num_cond_latents,
audio_embs=audio_i,
ref_target_masks=ref_i,
**ref_kwargs,
)
if _aborted(output):
return None
outputs.append(output)
noise_pred_cond, noise_pred_uncond_text, noise_pred_uncond = outputs
noise_pred = (
noise_pred_uncond
+ guide_scale * (noise_pred_cond - noise_pred_uncond_text)
+ audio_cfg_scale * (noise_pred_uncond_text - noise_pred_uncond)
)
elif any_guidance:
x_list = [latents, latents]
ctx_list = [prompt_embeds, neg_embeds]
mask_list = [prompt_mask, neg_mask]
if joint_pass:
outputs = self.transformer(
hidden_states=x_list,
timestep=[timestep] * len(x_list),
encoder_hidden_states=ctx_list,
encoder_attention_mask=mask_list,
num_cond_latents=[num_cond_latents] * len(x_list),
**ref_kwargs,
)
if _aborted(outputs):
return None
else:
outputs = []
for x_i, ctx_i, mask_i in zip(x_list, ctx_list, mask_list):
output = self.transformer(
hidden_states=x_i,
timestep=timestep,
encoder_hidden_states=ctx_i,
encoder_attention_mask=mask_i,
num_cond_latents=num_cond_latents,
**ref_kwargs,
)
if _aborted(output):
return None
outputs.append(output)
noise_pred_cond, noise_pred_uncond = outputs
if cfg_star_switch:
positive_flat = noise_pred_cond.view(latents.shape[0], -1)
negative_flat = noise_pred_uncond.view(latents.shape[0], -1)
st_star = optimized_scale(positive_flat, negative_flat).view(latents.shape[0], 1, 1, 1)
if cfg_zero_step >= 0 and i <= cfg_zero_step:
noise_pred = noise_pred_cond * 0.0
else:
noise_pred_uncond = noise_pred_uncond * st_star
noise_pred = noise_pred_uncond + guide_scale * (noise_pred_cond - noise_pred_uncond)
else:
noise_pred = noise_pred_uncond + guide_scale * (noise_pred_cond - noise_pred_uncond)
else:
noise_pred = self.transformer(
hidden_states=latents,
timestep=timestep,
encoder_hidden_states=prompt_embeds,
encoder_attention_mask=prompt_mask,
num_cond_latents=num_cond_latents,
audio_embs=audio_emb if self.is_avatar else None,
ref_target_masks=ref_target_masks if self.is_avatar else None,
**ref_kwargs,
)
if _aborted(noise_pred):
return None
noise_pred = -noise_pred
if num_cond_latents > 0:
latents[:, :, num_cond_latents:] = self.scheduler.step(
noise_pred[:, :, num_cond_latents:],
t,
latents[:, :, num_cond_latents:],
return_dict=False,
)[0]
else:
latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
if callback is not None:
callback(i, latents.squeeze(0))
progress_bar.update()
if num_ref_latents > 0:
latents = latents[:, :, num_ref_latents:]
num_cond_latents -= num_ref_latents
latent_slice = None
if return_latent_slice is not None:
latent_slice = latents[:, :, return_latent_slice].detach().to("cpu")
latents = latents.to(self.vae.dtype)
latents = self.denormalize_latents(latents)
video = self.vae.decode(latents, return_dict=False)[0].clamp(-1, 1)
if video.dim() == 5:
video = video[0]
self._clear_runtime_caches()
if latent_slice is not None:
return {"x": video, "latent_slice": latent_slice}
return video