ColabWan / models /magi_human /magi_human_model.py
1ripon1's picture
Upload folder using huggingface_hub
7344bef verified
Raw
History Blame Contribute Delete
37 kB
import math
import os
import sys
from dataclasses import dataclass
from pathlib import Path
import numpy as np
import torch
import torch.nn.functional as F
import torchaudio.functional as taF
from accelerate import init_empty_weights
from diffusers.video_processor import VideoProcessor
from mmgp import offload
from PIL import Image
from tqdm import tqdm
from transformers import AutoTokenizer
from transformers.models.t5gemma import T5GemmaEncoderModel
from shared.utils import files_locator as fl
from shared.utils.loras_mutipliers import update_loras_slists
from shared.utils.text_encoder_cache import TextEncoderCache
from shared.utils.utils import calculate_new_dimensions
from models.wan.modules.vae2_2 import Wan2_2_VAE
_UPSTREAM_ROOT = Path(__file__).resolve().parent / "upstream"
if str(_UPSTREAM_ROOT) not in sys.path:
sys.path.insert(0, str(_UPSTREAM_ROOT))
from inference.model.dit.dit_module import DiTModel, MagiAbortRequested # noqa: E402
from inference.model.sa_audio import SAAudioFeatureExtractor # noqa: E402
from inference.model.turbo_vaed import get_turbo_vaed # noqa: E402
from inference.pipeline.data_proxy import MagiDataProxy # noqa: E402
from inference.pipeline.scheduler_unipc import FlowUniPCMultistepScheduler # noqa: E402
from inference.pipeline.video_process import load_audio_and_encode, resample_audio_sinc, resizecrop # noqa: E402
MODEL_CONFIG = {
"num_layers": 40,
"hidden_size": 5120,
"head_dim": 128,
"num_query_groups": 8,
"video_in_channels": 48 * 4,
"audio_in_channels": 64,
"text_in_channels": 3584,
"checkpoint_qk_layernorm_rope": False,
"params_dtype": torch.bfloat16,
"tread_config": {"selection_rate": 0.5, "start_layer_idx": 2, "end_layer_idx": 25},
"mm_layers": [0, 1, 2, 3, 36, 37, 38, 39],
"local_attn_layers": [],
"enable_attn_gating": True,
"activation_type": "swiglu7",
"gelu7_layers": [0, 1, 2, 3],
"post_norm_layers": [],
}
DEFAULT_NEGATIVE_PROMPT = (
"Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, "
"worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, "
"deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, "
"walking backwards, low quality, worst quality, poor quality, noise, background noise, hiss, hum, buzz, crackle, static, "
"compression artifacts, MP3 artifacts, digital clipping, distortion, muffled, muddy, unclear, echo, reverb, room echo, "
"over-reverberated, hollow sound, distant, washed out, harsh, shrill, piercing, grating, tinny, thin sound, boomy, bass-heavy, "
"flat EQ, over-compressed, abrupt cut, jarring transition, sudden silence, looping artifact, music, instrumental, sirens, alarms, "
"crowd noise, unrelated sound effects, chaotic, disorganized, messy, cheap sound, emotionless, flat delivery, deadpan, lifeless, "
"apathetic, robotic, mechanical, monotone, flat intonation, undynamic, boring, reading from a script, AI voice, synthetic, "
"text-to-speech, TTS, insincere, fake emotion, exaggerated, overly dramatic, melodramatic, cheesy, cringey, hesitant, unconfident, "
"tired, weak voice, stuttering, stammering, mumbling, slurred speech, mispronounced, bad articulation, lisp, vocal fry, creaky voice, "
"mouth clicks, lip smacks, wet mouth sounds, heavy breathing, audible inhales, plosives, p-pops, coughing, clearing throat, sneezing, "
"speaking too fast, rushed, speaking too slow, dragged out, unnatural pauses, awkward silence, choppy, disjointed, multiple speakers, "
"two voices, background talking, out of tune, off-key, autotune artifacts"
)
@dataclass
class _EvalInput:
x_t: torch.Tensor
audio_x_t: torch.Tensor
audio_feat_len: list[int]
txt_feat: torch.Tensor
txt_feat_len: list[int]
class _ConfigObject:
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
class _ZeroSNRDDPMDiscretization:
def __init__(
self,
linear_start=0.00085,
linear_end=0.0120,
num_timesteps=1000,
shift_scale=1.0,
keep_start=False,
post_shift=False,
):
if keep_start and not post_shift:
linear_start = linear_start / (shift_scale + (1 - shift_scale) * linear_start)
self.num_timesteps = num_timesteps
betas = torch.linspace(linear_start**0.5, linear_end**0.5, num_timesteps, dtype=torch.float64).square().cpu().numpy()
alphas_cumprod = np.cumprod(1.0 - betas, axis=0)
if not post_shift:
alphas_cumprod = alphas_cumprod / (shift_scale + (1 - shift_scale) * alphas_cumprod)
self.alphas_cumprod = alphas_cumprod
self.post_shift = post_shift
self.shift_scale = shift_scale
def get_sigmas(self, n=None, device="cpu"):
n = self.num_timesteps if n is None else int(n)
if n < self.num_timesteps:
timesteps = np.linspace(self.num_timesteps - 1, 0, n, endpoint=False).astype(int)[::-1]
alphas_cumprod = self.alphas_cumprod[timesteps]
elif n == self.num_timesteps:
alphas_cumprod = self.alphas_cumprod
else:
raise ValueError
alphas_cumprod = torch.tensor(alphas_cumprod, dtype=torch.float32, device=device)
alphas_cumprod_sqrt = alphas_cumprod.sqrt()
alphas_cumprod_sqrt_0 = alphas_cumprod_sqrt[0].clone()
alphas_cumprod_sqrt_T = alphas_cumprod_sqrt[-1].clone()
alphas_cumprod_sqrt -= alphas_cumprod_sqrt_T
alphas_cumprod_sqrt *= alphas_cumprod_sqrt_0 / (alphas_cumprod_sqrt_0 - alphas_cumprod_sqrt_T)
if self.post_shift:
alphas_cumprod_sqrt = (alphas_cumprod_sqrt**2 / (self.shift_scale + (1 - self.shift_scale) * alphas_cumprod_sqrt**2)) ** 0.5
return torch.flip(alphas_cumprod_sqrt, dims=(0,))
def __call__(self, n=None, do_append_zero=True, device="cpu", flip=False):
sigmas = self.get_sigmas(n=n, device=device)
if do_append_zero:
sigmas = torch.cat([sigmas, sigmas.new_zeros((1,))])
return sigmas if not flip else torch.flip(sigmas, dims=(0,))
class MagiHumanTextEncoder:
def __init__(self, checkpoint_path: str, tokenizer_path: str, dtype: torch.dtype):
self.checkpoint_path = checkpoint_path
self.tokenizer_path = tokenizer_path
self.dtype = dtype
self.device = torch.device("cpu")
self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_path, local_files_only=True)
model_prefix = "encoder" if "quanto" in os.path.basename(checkpoint_path).lower() else "model.encoder"
self.model = offload.fast_load_transformers_model(
checkpoint_path,
writable_tensors=False,
modelClass=T5GemmaEncoderModel,
defaultConfigPath=os.path.join(tokenizer_path, "config.json"),
modelPrefix=model_prefix,
configKwargs={"is_encoder_decoder": False},
default_dtype=dtype,
)
self.model.eval().requires_grad_(False)
@torch.inference_mode()
def encode(self, prompts):
if isinstance(prompts, str):
prompts = [prompts]
inputs = self.tokenizer(prompts, return_tensors="pt", padding=True).to(self.device)
outputs = self.model(**inputs)
return outputs.last_hidden_state.to(self.dtype)
class MagiHumanModel:
def __init__(
self,
model_filename,
model_type,
base_model_type,
model_def,
text_encoder_filename=None,
quantizeTransformer=False,
dtype=torch.bfloat16,
VAE_dtype=torch.float32,
mixed_precision_transformer=False,
save_quantized=False,
**kwargs,
):
self.device = torch.device("cuda")
self.dtype = dtype
self.VAE_dtype = VAE_dtype
self.model_def = model_def or {}
self.model_type = model_type
self.base_model_type = base_model_type
self._interrupt = False
text_encoder_folder = self.model_def.get("text_encoder_folder", "t5gemma-9b-9b-ul2")
text_encoder_path = text_encoder_filename or fl.locate_file(
os.path.join(text_encoder_folder, "t5gemma-9b-9b-ul2_bf16.safetensors")
)
tokenizer_path = fl.locate_folder(text_encoder_folder)
self.text_encoder = MagiHumanTextEncoder(text_encoder_path, tokenizer_path, dtype)
transformer_paths = list(model_filename) if isinstance(model_filename, (list, tuple)) else [model_filename]
transformer_path = transformer_paths[0]
self.transformer_config_path = fl.locate_file(self.model_def.get("config_file", "models/magi_human/configs/magi_human_distill.json"))
model_cfg = self._build_model_config()
self.transformer = self._load_transformer(transformer_path, model_cfg, quantizeTransformer, save_quantized, dtype)
self.transformer2 = None
if len(transformer_paths) > 1:
self.transformer2 = self._load_transformer(transformer_paths[1], model_cfg, quantizeTransformer, save_quantized, dtype)
if save_quantized:
from wgp import save_quantized_model
save_quantized_model(self.transformer, model_type, transformer_path, dtype, self.transformer_config_path)
if self.transformer2 is not None:
save_quantized_model(self.transformer2, model_type, transformer_paths[1], dtype, self.transformer_config_path, submodel_no=2)
self.data_proxy = self._build_data_proxy("v2")
self.sr_data_proxy = self._build_data_proxy("v1")
self.fps = int(self.model_def.get("fps", 25))
self.vae_stride = (4, 16, 16)
self.latent_channels = 48
self.audio_channels = 64
self.text_target_length = 640
self.text_encoder_cache = TextEncoderCache()
self._default_negative_prompt_embeds = None
self._default_negative_prompt_len = None
self._turbo_vae_decode_max_pixels = 832 * 480
self.sr_num_inference_steps = int(self.model_def.get("sr_num_inference_steps", 5))
self.sr_cfg_number = int(self.model_def.get("sr_cfg_number", 1))
self.sr_noise_value = int(self.model_def.get("sr_noise_value", 220))
self.sr_video_txt_guidance_scale = float(self.model_def.get("sr_video_txt_guidance_scale", 3.5))
self.use_cfg_trick = bool(self.model_def.get("use_cfg_trick", True))
self.cfg_trick_start_frame = int(self.model_def.get("cfg_trick_start_frame", 13))
self.cfg_trick_value = float(self.model_def.get("cfg_trick_value", 2.0))
self.using_sde_flag = bool(self.model_def.get("using_sde_flag", False))
self.sr_audio_noise_scale = float(self.model_def.get("sr_audio_noise_scale", 0.7))
self._sr_sigmas = _ZeroSNRDDPMDiscretization()(1000, do_append_zero=False, flip=True)
self.video_processor = VideoProcessor(vae_scale_factor=16)
self.vae = Wan2_2_VAE(vae_pth=fl.locate_file("Wan2.2_VAE.safetensors"), dtype=VAE_dtype, device="cpu")
self.vae.device = self.device
self.audio_vae = SAAudioFeatureExtractor(device="cpu", model_path=fl.locate_folder("stable-audio-open-1.0"))
self.turbo_vae = get_turbo_vaed(
fl.locate_file("turbo_vae/TurboV3-Wan22-TinyShallow_7_7.json"),
fl.locate_file("turbo_vae/TurboV3-Wan22-TinyShallow_7_7.safetensors"),
device="cpu",
weight_dtype=VAE_dtype,
)
def _build_model_config(self):
cfg = dict(MODEL_CONFIG)
cfg["num_heads_q"] = cfg["hidden_size"] // cfg["head_dim"]
cfg["num_heads_kv"] = cfg["num_query_groups"]
return _ConfigObject(**cfg)
def _build_data_proxy(self, coords_style: str):
return MagiDataProxy(
_ConfigObject(
t_patch_size=1,
patch_size=2,
frame_receptive_field=11,
spatial_rope_interpolation="extra",
ref_audio_offset=1000,
text_offset=0,
coords_style=coords_style,
)
)
def _load_transformer(self, checkpoint_path: str, model_cfg, quantizeTransformer: bool, save_quantized: bool, dtype: torch.dtype):
with init_empty_weights():
transformer = DiTModel(model_cfg)
offload.load_model_data(
transformer,
checkpoint_path,
do_quantize=quantizeTransformer and not save_quantized,
writable_tensors=False,
default_dtype=dtype,
)
transformer.eval().requires_grad_(False)
transformer._interrupt_check = self._interrupt_requested
transformer.block._interrupt_check = self._interrupt_requested
return transformer
def _interrupt_requested(self):
return bool(self._interrupt)
def request_early_stop(self):
self._interrupt = True
def _run_transformer(self, transformer, *inputs):
try:
return transformer(*inputs)
except MagiAbortRequested:
self._interrupt = True
return None
def prepare_preview_payload(self, latents, preview_meta=None):
if not torch.is_tensor(latents):
return None
return {"latents": latents.float()}
def _pad_or_trim(self, tensor: torch.Tensor, target_size: int):
current_size = tensor.shape[1]
if current_size < target_size:
pad = torch.zeros((tensor.shape[0], target_size - current_size, tensor.shape[2]), device=tensor.device, dtype=tensor.dtype)
return torch.cat([tensor, pad], dim=1), current_size
return tensor[:, :target_size], target_size
def _pad_or_trim_prompt(self, tensor: torch.Tensor, target_size: int):
current_size = tensor.shape[0]
if current_size < target_size:
pad = torch.zeros((target_size - current_size, tensor.shape[1]), device=tensor.device, dtype=tensor.dtype)
return torch.cat([tensor, pad], dim=0), current_size
return tensor[:target_size], target_size
def _normalize_prompt_batch(self, prompt, batch_size: int):
if isinstance(prompt, str):
return [prompt] * batch_size
prompt = list(prompt)
if len(prompt) == 1 and batch_size > 1:
return prompt * batch_size
if len(prompt) != batch_size:
raise ValueError("Prompt batch size does not match Magi Human batch size.")
return prompt
def _encode_prompt(self, prompt, batch_size: int):
prompts = self._normalize_prompt_batch(prompt, batch_size)
encode_fn = lambda prompt_batch: list(self.text_encoder.encode(prompt_batch))
encoded_prompts = self.text_encoder_cache.encode(encode_fn, prompts, device=self.device, parallel=True)
embeddings = []
prompt_lens = []
for encoded_prompt in encoded_prompts:
padded_prompt, prompt_len = self._pad_or_trim_prompt(encoded_prompt, self.text_target_length)
embeddings.append(padded_prompt)
prompt_lens.append(int(prompt_len))
stacked = torch.stack(embeddings, dim=0).to(self.device)
unique_lens = set(prompt_lens)
return stacked, prompt_lens[0] if len(unique_lens) == 1 else prompt_lens
def _get_uncond_prompt(self, negative_prompt: str):
if negative_prompt:
embeds, prompt_len = self._encode_prompt(negative_prompt, 1)
return embeds, [int(prompt_len)]
if self._default_negative_prompt_embeds is None:
embeds, prompt_len = self._encode_prompt(DEFAULT_NEGATIVE_PROMPT, 1)
self._default_negative_prompt_embeds = embeds
self._default_negative_prompt_len = [int(prompt_len)]
return self._default_negative_prompt_embeds, self._default_negative_prompt_len
def _coerce_image_batch(self, image, batch_size: int):
if image is None:
return None
if isinstance(image, list):
image = image[0] if image else None
if image is None:
return None
if not torch.is_tensor(image):
raise ValueError("Magi Human expects image tensors prepared by WanGP.")
if image.dim() == 3:
image = image.unsqueeze(0)
if image.dim() == 5 and image.shape[2] == 1:
image = image.squeeze(2)
if image.dim() != 4:
raise ValueError(f"Unexpected Magi Human image shape: {tuple(image.shape)}")
image = image.to(device=self.device, dtype=self.VAE_dtype)
if image.shape[0] == 1 and batch_size > 1:
image = image.repeat(batch_size, 1, 1, 1)
return image
def _coerce_last_video_frame_batch(self, video, batch_size: int):
if video is None:
return None
if not torch.is_tensor(video):
raise ValueError("Magi Human expects video tensors prepared by WanGP.")
if video.dim() == 4:
return self._coerce_image_batch(video[:, -1], batch_size)
if video.dim() == 5:
return self._coerce_image_batch(video[:, :, -1], batch_size)
raise ValueError(f"Unexpected Magi Human video shape: {tuple(video.shape)}")
def _encode_image_latent(self, image_batch: torch.Tensor, height: int, width: int, tile_size: int):
latents = []
for image in image_batch:
image_uint8 = image.float().clamp(-1, 1).add(1).mul(127.5).to(torch.uint8).permute(1, 2, 0).cpu().numpy()
image_pil = resizecrop(Image.fromarray(image_uint8), height, width)
image_tensor = self.video_processor.preprocess(image_pil, height=height, width=width)[0].to(device=self.device, dtype=self.VAE_dtype)
latents.append(self.vae.encode([image_tensor.unsqueeze(1)], tile_size=tile_size)[0])
return torch.stack(latents, dim=0).to(device=self.device, dtype=torch.float32)
def _resolve_vae_tile_size(self, VAE_tile_size, height: int, width: int):
if isinstance(VAE_tile_size, dict):
tile_size = int(VAE_tile_size.get("tile_sample_min_size", VAE_tile_size.get("tile_latent_min_size", 0)) or 0)
elif isinstance(VAE_tile_size, (list, tuple)):
if not VAE_tile_size:
tile_size = 0
elif len(VAE_tile_size) >= 2 and isinstance(VAE_tile_size[0], bool):
tile_size = int(VAE_tile_size[1] if VAE_tile_size[0] else 0)
else:
tile_size = int(VAE_tile_size[-1] or 0)
else:
tile_size = int(VAE_tile_size or 0)
if height * width >= 1280 * 704:
print(
f"[Magi][VAE] resolved tile_size={tile_size} for {width}x{height} "
f"(input={VAE_tile_size})",
flush=True,
)
return tile_size
def _load_audio_latent(self, audio_path: str, frame_num: int, fps: int):
seconds = max(1, math.ceil((frame_num - 1) / max(fps, 1)))
audio_latent = load_audio_and_encode(self.audio_vae, audio_path, seconds=seconds).permute(0, 2, 1).to(torch.float32)
return self._finalize_audio_latent(audio_latent, frame_num)
def _finalize_audio_latent(self, audio_latent: torch.Tensor, frame_num: int):
if audio_latent.shape[1] < frame_num:
pad = torch.zeros((audio_latent.shape[0], frame_num - audio_latent.shape[1], audio_latent.shape[2]), device=audio_latent.device, dtype=audio_latent.dtype)
audio_latent = torch.cat([audio_latent, pad], dim=1)
return audio_latent[:, :frame_num].to(self.device)
def _encode_audio_waveform_latent(self, input_waveform, input_waveform_sample_rate: int, frame_num: int, fps: int):
if input_waveform_sample_rate is None or int(input_waveform_sample_rate) <= 0:
raise ValueError("Magi Human requires a valid input_waveform_sample_rate when input_waveform is provided.")
waveform = torch.from_numpy(input_waveform) if isinstance(input_waveform, np.ndarray) else input_waveform
if not torch.is_tensor(waveform):
raise ValueError("Magi Human expects input_waveform as a numpy array or torch tensor.")
waveform = waveform.to(dtype=torch.float32)
if waveform.ndim == 1:
waveform = waveform.unsqueeze(0)
elif waveform.ndim == 2:
waveform = waveform.T
elif waveform.ndim == 3 and waveform.shape[0] == 1:
waveform = waveform.squeeze(0).T
if waveform.ndim != 2:
raise ValueError(f"Unexpected Magi Human waveform shape: {tuple(waveform.shape)}")
target_sample_rate = 51200
if int(input_waveform_sample_rate) != target_sample_rate:
waveform = taF.resample(waveform, int(input_waveform_sample_rate), target_sample_rate)
seconds = max(1, math.ceil((frame_num - 1) / max(fps, 1)))
waveform = waveform[:, : min(waveform.shape[-1], int(seconds * target_sample_rate))]
if waveform.shape[0] == 1:
waveform = waveform.expand(2, -1)
elif waveform.shape[0] > 2:
waveform = waveform[:2]
encode_device = next(self.audio_vae.vae_model.parameters()).device
waveform = waveform.to(device=encode_device)
audio_latent = self.audio_vae.encode(waveform).permute(0, 2, 1).to(torch.float32)
return self._finalize_audio_latent(audio_latent, frame_num)
def _decode_video(self, latents: torch.Tensor, tile_size: int):
turbo_dtype = next(self.turbo_vae.parameters()).dtype
sample_h = int(latents.shape[-2]) * self.vae_stride[1]
sample_w = int(latents.shape[-1]) * self.vae_stride[2]
output_offload = int(tile_size) > 0 or sample_h * sample_w > self._turbo_vae_decode_max_pixels
temporal_chunk_size = 0
# if sample_h * sample_w >= 1920 * 1088:
# temporal_chunk_size = 3
# elif sample_h * sample_w >= 1280 * 704 and latents.shape[2] > self.turbo_vae.step_size:
# temporal_chunk_size = 5
# print(
# f"[Magi][VAE] decode {sample_w}x{sample_h} latent_shape={tuple(latents[:1].shape)} "
# f"tile_size={tile_size} temporal_chunk_size={temporal_chunk_size} output_offload={output_offload}",
# flush=True,
# )
decoded = self.turbo_vae.decode(
latents[:1].to(device=self.device, dtype=turbo_dtype),
output_offload=output_offload,
tile_size=tile_size,
temporal_chunk_size=temporal_chunk_size,
)
with torch.inference_mode(False):
return decoded[0].float().clamp(-1, 1).clone()
def _decode_audio(self, latent_audio: torch.Tensor):
audio_dtype = next(self.audio_vae.vae_model.parameters()).dtype
audio_output = self.audio_vae.decode(latent_audio.squeeze(0).T.to(audio_dtype))
audio_output = audio_output.float().squeeze(0).T.detach().cpu().numpy()
return resample_audio_sinc(audio_output, 441 / 512)
def _prepare_proxy_input(self, eval_input: _EvalInput, data_proxy: MagiDataProxy):
packed = data_proxy.process_input(eval_input)
return packed, dict(data_proxy._saved_data)
def _process_proxy_output(self, pred: torch.Tensor, saved_state: dict, data_proxy: MagiDataProxy):
data_proxy._saved_data = dict(saved_state)
return data_proxy.process_output(pred)
def _resolve_base_phase_dimensions(self, height: int, width: int):
# return 256, 448
phase_height, phase_width = calculate_new_dimensions(256, 448, height, width, 0, block_size=self.model_def.get("vae_block_size", 32))
return int(phase_height), int(phase_width)
def _run_diffusion_phase(
self,
transformer,
data_proxy,
latent_video,
latent_audio,
image_latent,
prompt_embeds,
prompt_lens,
sampling_steps,
shift,
guide_scale,
audio_cfg_scale,
use_audio_guide,
callback,
pass_no,
total_passes,
joint_pass,
uncond_prompt_embeds=None,
uncond_prompt_lens=None,
update_audio=True,
use_sr_model=False,
sr_cfg_scale=None,
):
video_scheduler = FlowUniPCMultistepScheduler()
audio_scheduler = FlowUniPCMultistepScheduler()
video_scheduler.set_timesteps(int(sampling_steps), device=self.device, shift=float(shift))
audio_scheduler.set_timesteps(int(sampling_steps), device=self.device, shift=float(shift))
timesteps = video_scheduler.timesteps
cfg_number = int(self.sr_cfg_number if use_sr_model else 2)
if not use_sr_model:
cfg_number = 1 if float(guide_scale) == 1.0 and (not update_audio or float(audio_cfg_scale) == 1.0) else 2
sr_guidance = None
if use_sr_model and cfg_number == 2:
latent_frames = latent_video.shape[2]
sr_guidance = torch.full((latent_video.shape[0], 1, latent_frames, 1, 1), float(self.sr_video_txt_guidance_scale), device=self.device, dtype=latent_video.dtype)
if self.use_cfg_trick:
sr_guidance[:, :, : self.cfg_trick_start_frame] = min(self.cfg_trick_value, self.sr_video_txt_guidance_scale)
if sr_cfg_scale is not None:
sr_guidance.mul_(float(sr_cfg_scale) / float(self.sr_video_txt_guidance_scale))
if callback is not None:
callback(-1, None, True, override_num_inference_steps=len(timesteps), pass_no=pass_no)
progress_bar = tqdm(timesteps, desc=f"Phase {pass_no}/{total_passes}" if total_passes > 1 else None)
for step_idx, t in enumerate(progress_bar):
if self._interrupt:
return None, None
latent_video[:, :, :1] = image_latent[:, :, :1]
eval_input = _EvalInput(
x_t=latent_video,
audio_x_t=latent_audio,
audio_feat_len=[latent_audio.shape[1]] * latent_audio.shape[0],
txt_feat=prompt_embeds,
txt_feat_len=prompt_lens,
)
packed_inputs, packed_state = self._prepare_proxy_input(eval_input, data_proxy)
if cfg_number == 2:
uncond_eval_input = _EvalInput(
x_t=latent_video,
audio_x_t=latent_audio,
audio_feat_len=[latent_audio.shape[1]] * latent_audio.shape[0],
txt_feat=uncond_prompt_embeds,
txt_feat_len=uncond_prompt_lens,
)
uncond_inputs, uncond_state = self._prepare_proxy_input(uncond_eval_input, data_proxy)
if joint_pass:
pred_pair = self._run_transformer(transformer, *[[packed_inputs[i], uncond_inputs[i]] for i in range(len(packed_inputs))])
if pred_pair is None:
return None, None
pred_cond, pred_uncond = pred_pair
else:
pred_cond = self._run_transformer(transformer, *packed_inputs)
if pred_cond is None:
return None, None
pred_uncond = self._run_transformer(transformer, *uncond_inputs)
if pred_uncond is None:
return None, None
pred_video, pred_audio = self._process_proxy_output(pred_cond, packed_state, data_proxy)
pred_video_uncond, pred_audio_uncond = self._process_proxy_output(pred_uncond, uncond_state, data_proxy)
current_video_guidance = sr_guidance if use_sr_model else float(guide_scale if t > 500 else 2.0)
pred_video = pred_video_uncond + current_video_guidance * (pred_video - pred_video_uncond)
pred_audio = pred_audio_uncond + float(audio_cfg_scale) * (pred_audio - pred_audio_uncond)
latent_video = video_scheduler.step(pred_video, t, latent_video, return_dict=False)[0]
if update_audio and not use_audio_guide:
latent_audio = audio_scheduler.step(pred_audio, t, latent_audio, return_dict=False)[0]
else:
pred = self._run_transformer(transformer, *packed_inputs)
if pred is None:
return None, None
pred_video, pred_audio = self._process_proxy_output(pred, packed_state, data_proxy)
if use_sr_model:
latent_video = video_scheduler.step(pred_video, t, latent_video, return_dict=False)[0]
else:
latent_video = video_scheduler.step_ddim(pred_video, step_idx, latent_video)
if update_audio and not use_audio_guide:
latent_audio = audio_scheduler.step_ddim(pred_audio, step_idx, latent_audio)
if callback is not None:
callback(step_idx, latent_video[0].detach(), pass_no=pass_no)
latent_video[:, :, :1] = image_latent[:, :, :1]
return latent_video, latent_audio
@torch.inference_mode()
def generate(
self,
seed=None,
input_prompt="",
alt_prompt="",
n_prompt="",
sampling_steps=8,
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,
frame_num=101,
batch_size=1,
height=256,
width=448,
guide_scale=1.0,
guide2_scale=1.0,
guide3_scale=1.0,
switch_threshold=0.0,
switch2_threshold=0.0,
guide_phases=1,
model_switch_phase=0,
embedded_guidance_scale=0.0,
shift=5.0,
sample_solver="unipc",
denoising_strength=1.0,
masking_strength=1.0,
callback=None,
VAE_tile_size=None,
joint_pass=True,
audio_cfg_scale=1.0,
prefix_video=None,
prefix_frames_count=0,
input_video_strength=1.0,
input_waveform=None,
input_waveform_sample_rate=None,
audio_guide=None,
audio_guide2=None,
audio_prompt_type="",
fps=None,
offloadobj=None,
set_header_text=None,
loras_slists=None,
**kwargs,
):
self._interrupt = False
if seed is None or seed == -1:
seed = torch.seed() % (2**32 - 1)
if fps is None or fps <= 0:
fps = self.fps
if batch_size < 1:
raise ValueError("Magi Human batch_size must be positive.")
if batch_size != 1:
raise ValueError("Magi Human currently supports batch_size=1 only.")
generator = torch.Generator(device=self.device)
generator.manual_seed(seed)
prompt = alt_prompt or input_prompt
prompt_embeds, prompt_len = self._encode_prompt(prompt, batch_size)
if isinstance(prompt_len, int):
prompt_lens = [prompt_len] * batch_size
else:
prompt_lens = [int(prompt_len)] * batch_size
if self.base_model_type == "magi_human_distill":
guide_scale = 1.0
audio_cfg_scale = 1.0
need_cfg = float(guide_scale) != 1.0 or float(audio_cfg_scale) != 1.0 or (self.transformer2 is not None and self.sr_cfg_number == 2)
if need_cfg:
uncond_prompt_embeds, uncond_prompt_lens = self._get_uncond_prompt(n_prompt)
else:
uncond_prompt_embeds, uncond_prompt_lens = None, None
image_batch = input_video[:, -1].unsqueeze(0)
# image_batch = self._coerce_last_video_frame_batch(input_video, batch_size)
# if image_batch is None:
# image_batch = self._coerce_last_video_frame_batch(prefix_video, batch_size)
# if image_batch is None:
# source_image = image_start
# if source_image is None and input_ref_images is not None:
# source_image = input_ref_images[0] if isinstance(input_ref_images, list) and input_ref_images else input_ref_images
# image_batch = self._coerce_image_batch(source_image, batch_size)
# if image_batch is None:
# raise ValueError("Magi Human requires a start image.")
total_passes = 2 if self.transformer2 is not None else 1
phase1_height, phase1_width = self._resolve_base_phase_dimensions(height, width) if self.transformer2 is not None else (height, width)
tile_size = self._resolve_vae_tile_size(VAE_tile_size, phase1_height, phase1_width)
image_latent = self._encode_image_latent(image_batch, phase1_height, phase1_width, tile_size)
latent_frames = (frame_num - 1) // self.vae_stride[0] + 1
latent_h = phase1_height // self.vae_stride[1]
latent_w = phase1_width // self.vae_stride[2]
latent_video = torch.randn(
(batch_size, self.latent_channels, latent_frames, latent_h, latent_w),
generator=generator,
device=self.device,
dtype=torch.float32,
)
latent_video[:, :, :1] = image_latent[:, :, :1]
use_audio_guide = bool("A" in (audio_prompt_type or "") and (input_waveform is not None or audio_guide))
if "A" in (audio_prompt_type or "") and input_waveform is not None:
latent_audio = self._encode_audio_waveform_latent(input_waveform, input_waveform_sample_rate, frame_num, fps)
elif use_audio_guide:
latent_audio = self._load_audio_latent(audio_guide, frame_num, fps)
else:
latent_audio = torch.randn((batch_size, frame_num, self.audio_channels), generator=generator, device=self.device, dtype=torch.float32)
if loras_slists is not None:
update_loras_slists(self.transformer, loras_slists, int(sampling_steps), phase_switch_step=int(sampling_steps), phase_switch_step2=int(sampling_steps))
latent_video, latent_audio = self._run_diffusion_phase(
self.transformer,
self.data_proxy,
latent_video,
latent_audio,
image_latent,
prompt_embeds,
prompt_lens,
sampling_steps,
shift,
guide_scale,
audio_cfg_scale,
use_audio_guide,
callback,
1,
total_passes,
joint_pass,
uncond_prompt_embeds=uncond_prompt_embeds,
uncond_prompt_lens=uncond_prompt_lens,
update_audio=True,
use_sr_model=False,
)
if latent_video is None or latent_audio is None:
return None
if self._interrupt:
return None
final_video_latent = latent_video
final_audio_latent = latent_audio
final_tile_size = tile_size
if self.transformer2 is not None:
final_tile_size = self._resolve_vae_tile_size(VAE_tile_size, height, width)
sr_image_latent = self._encode_image_latent(image_batch, height, width, final_tile_size)
sr_latent_h = height // self.vae_stride[1]
sr_latent_w = width // self.vae_stride[2]
sr_video_latent = F.interpolate(latent_video, size=(latent_frames, sr_latent_h, sr_latent_w), mode="trilinear", align_corners=True)
if self.sr_noise_value > 0:
sigma = self._sr_sigmas.to(sr_video_latent.device)[self.sr_noise_value]
sr_video_latent = sr_video_latent * sigma + torch.randn_like(sr_video_latent) * (1 - sigma**2).sqrt()
sr_audio_latent = torch.randn_like(latent_audio) * self.sr_audio_noise_scale + latent_audio * (1 - self.sr_audio_noise_scale)
if loras_slists is not None:
update_loras_slists(self.transformer2, loras_slists, self.sr_num_inference_steps, phase_switch_step=0, phase_switch_step2=self.sr_num_inference_steps)
final_video_latent, _ = self._run_diffusion_phase(
self.transformer2,
self.sr_data_proxy,
sr_video_latent,
sr_audio_latent,
sr_image_latent,
prompt_embeds,
prompt_lens,
self.sr_num_inference_steps,
shift,
1.0,
1.0,
True,
callback,
2,
total_passes,
False,
uncond_prompt_embeds=uncond_prompt_embeds,
uncond_prompt_lens=uncond_prompt_lens,
update_audio=False,
use_sr_model=True,
)
if final_video_latent is None:
return None
video = self._decode_video(final_video_latent, final_tile_size)
audio = self._decode_audio(final_audio_latent[:1])
return {"x": video, "audio": audio, "audio_sampling_rate": 44100}