| 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 |
| from inference.model.sa_audio import SAAudioFeatureExtractor |
| from inference.model.turbo_vaed import get_turbo_vaed |
| from inference.pipeline.data_proxy import MagiDataProxy |
| from inference.pipeline.scheduler_unipc import FlowUniPCMultistepScheduler |
| from inference.pipeline.video_process import load_audio_and_encode, resample_audio_sinc, resizecrop |
|
|
|
|
| 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 |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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): |
| |
| 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) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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} |
|
|