# SPDX-License-Identifier: Apache-2.0 # Copyright (c) 2026 SandAI. All Rights Reserved. # Ported from daVinci-MagiHuman inference/pipeline/video_generate.py # Adapted for vllm-omni: single-GPU, diffusers VAE, configurable dit_subfolder. from __future__ import annotations import json import logging import math import os from collections.abc import Iterable from dataclasses import dataclass from pathlib import Path from typing import Any, ClassVar, Literal import numpy as np import torch import torch.nn as nn import whisper from diffusers.configuration_utils import ConfigMixin, register_to_config from diffusers.schedulers.scheduling_utils import ( KarrasDiffusionSchedulers, SchedulerMixin, SchedulerOutput, ) from diffusers.utils import deprecate, load_image from diffusers.utils.torch_utils import randn_tensor from diffusers.video_processor import VideoProcessor from einops import rearrange from PIL import Image from safetensors.torch import load_file from torch.nn import functional as F from torch.nn.utils import weight_norm from transformers import AutoTokenizer from transformers.models.t5gemma import T5GemmaEncoderModel from vllm.distributed import ( get_tensor_model_parallel_world_size, ) from vllm.model_executor.model_loader.weight_utils import default_weight_loader from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig from vllm_omni.diffusion.distributed.autoencoders.autoencoder_kl_wan import ( DistributedAutoencoderKLWan, ) from vllm_omni.diffusion.model_loader.diffusers_loader import ( DiffusersPipelineLoader, ) from vllm_omni.diffusion.models.interface import SupportsComponentDiscovery from vllm_omni.diffusion.models.progress_bar import ProgressBarMixin from vllm_omni.diffusion.models.t5_encoder.t5_gemma_encoder import T5GemmaEncoderModelTP from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import ( DiffusionPipelineProfilerMixin, ) from vllm_omni.diffusion.request import OmniDiffusionRequest from vllm_omni.diffusion.worker.request_batch import DiffusionRequestBatch from .magi_human_dit import ( DiTModel, FFAHandler, MagiHumanDiTConfig, Modality, VarlenHandler, ) logger = logging.getLogger(__name__) # =========================================================================== # Scheduler (ported from daVinci-MagiHuman inference/pipeline/scheduler_unipc.py) # =========================================================================== class FlowUniPCMultistepScheduler(SchedulerMixin, ConfigMixin): _compatibles = [e.name for e in KarrasDiffusionSchedulers] order = 1 @register_to_config def __init__( self, num_train_timesteps: int = 1000, solver_order: int = 2, prediction_type: str = "flow_prediction", shift: float = 1.0, use_dynamic_shifting=False, thresholding: bool = False, dynamic_thresholding_ratio: float = 0.995, sample_max_value: float = 1.0, predict_x0: bool = True, solver_type: str = "bh2", lower_order_final: bool = True, disable_corrector: list[int] = [], solver_p: SchedulerMixin = None, timestep_spacing: str = "linspace", steps_offset: int = 0, final_sigmas_type: str | None = "zero", ): if solver_type not in ["bh1", "bh2"]: if solver_type in ["midpoint", "heun", "logrho"]: self.register_to_config(solver_type="bh2") else: raise NotImplementedError(f"{solver_type} is not implemented for {self.__class__}") self.predict_x0 = predict_x0 self.num_inference_steps = None alphas = np.linspace(1, 1 / num_train_timesteps, num_train_timesteps)[::-1].copy() sigmas = 1.0 - alphas sigmas = torch.from_numpy(sigmas).to(dtype=torch.float32) if not use_dynamic_shifting: sigmas = shift * sigmas / (1 + (shift - 1) * sigmas) self.sigmas = sigmas self.timesteps = sigmas * num_train_timesteps self.model_outputs = [None] * solver_order self.timestep_list = [None] * solver_order self.lower_order_nums = 0 self.disable_corrector = disable_corrector self.solver_p = solver_p self.last_sample = None self._step_index: int | None = None self._begin_index: int | None = None self.sigmas = self.sigmas.to("cpu") self.sigma_min = self.sigmas[-1].item() self.sigma_max = self.sigmas[0].item() @property def step_index(self): return self._step_index @property def begin_index(self): return self._begin_index def set_begin_index(self, begin_index: int = 0): self._begin_index = begin_index def set_timesteps( self, num_inference_steps: int | None = None, device: str | torch.device = None, sigmas: list[float] | None = None, mu: float | None | None = None, shift: float | None | None = None, ): if self.config.use_dynamic_shifting and mu is None: raise ValueError(" you have to pass a value for `mu` when `use_dynamic_shifting` is set to be `True`") if sigmas is None: sigmas = np.linspace(self.sigma_max, self.sigma_min, num_inference_steps + 1).copy()[:-1] if self.config.use_dynamic_shifting: sigmas = self.time_shift(mu, 1.0, sigmas) else: if shift is None: shift = self.config.shift sigmas = shift * sigmas / (1 + (shift - 1) * sigmas) if self.config.final_sigmas_type == "sigma_min": sigma_last = ((1 - self.alphas_cumprod[0]) / self.alphas_cumprod[0]) ** 0.5 elif self.config.final_sigmas_type == "zero": sigma_last = 0 else: raise ValueError( f"`final_sigmas_type` must be one of 'zero', or 'sigma_min', but got {self.config.final_sigmas_type}" ) timesteps = sigmas * self.config.num_train_timesteps sigmas = np.concatenate([sigmas, [sigma_last]]).astype(np.float32) self.sigmas = torch.from_numpy(sigmas) self.timesteps = torch.from_numpy(timesteps).to(device=device, dtype=torch.int64) self.num_inference_steps = len(timesteps) self.model_outputs = [None] * self.config.solver_order self.lower_order_nums = 0 self.last_sample = None if self.solver_p: self.solver_p.set_timesteps(self.num_inference_steps, device=device) self._step_index = None self._begin_index = None self.sigmas = self.sigmas.to("cpu") def _threshold_sample(self, sample: torch.Tensor) -> torch.Tensor: dtype = sample.dtype batch_size, channels, *remaining_dims = sample.shape if dtype not in (torch.float32, torch.float64): sample = sample.float() sample = sample.reshape(batch_size, channels * np.prod(remaining_dims)) abs_sample = sample.abs() s = torch.quantile(abs_sample, self.config.dynamic_thresholding_ratio, dim=1) s = torch.clamp(s, min=1, max=self.config.sample_max_value) s = s.unsqueeze(1) sample = torch.clamp(sample, -s, s) / s sample = sample.reshape(batch_size, channels, *remaining_dims) return sample.to(dtype) def _sigma_to_t(self, sigma): return sigma * self.config.num_train_timesteps def _sigma_to_alpha_sigma_t(self, sigma): return 1 - sigma, sigma def time_shift(self, mu: float, sigma: float, t: torch.Tensor): return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma) def convert_model_output( self, model_output: torch.Tensor, *args, sample: torch.Tensor = None, **kwargs ) -> torch.Tensor: timestep = args[0] if len(args) > 0 else kwargs.pop("timestep", None) if sample is None: if len(args) > 1: sample = args[1] else: raise ValueError("missing `sample` as a required keyword argument") if timestep is not None: deprecate( "timesteps", "1.0.0", "Passing `timesteps` is deprecated and has no effect as model output " "conversion is now handled via an internal counter `self.step_index`", ) sigma = self.sigmas[self.step_index] alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma) if self.predict_x0: if self.config.prediction_type == "flow_prediction": sigma_t = self.sigmas[self.step_index] x0_pred = sample - sigma_t * model_output else: raise ValueError( f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`," " `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler." ) if self.config.thresholding: x0_pred = self._threshold_sample(x0_pred) return x0_pred else: if self.config.prediction_type == "flow_prediction": sigma_t = self.sigmas[self.step_index] epsilon = sample - (1 - sigma_t) * model_output else: raise ValueError( f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`," " `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler." ) if self.config.thresholding: sigma_t = self.sigmas[self.step_index] x0_pred = sample - sigma_t * model_output x0_pred = self._threshold_sample(x0_pred) epsilon = model_output + x0_pred return epsilon def multistep_uni_p_bh_update( self, model_output: torch.Tensor, *args, sample: torch.Tensor | None = None, order: int | None = None, **kwargs, ) -> torch.Tensor: prev_timestep = args[0] if len(args) > 0 else kwargs.pop("prev_timestep", None) if sample is None: if len(args) > 1: sample = args[1] else: raise ValueError(" missing `sample` as a required keyword argument") if order is None: if len(args) > 2: order = args[2] else: raise ValueError(" missing `order` as a required keyword argument") if prev_timestep is not None: deprecate("prev_timestep", "1.0.0", "Passing `prev_timestep` is deprecated and has no effect.") model_output_list = self.model_outputs s0 = self.timestep_list[-1] m0 = model_output_list[-1] x = sample if self.solver_p: return self.solver_p.step(model_output, s0, x).prev_sample sigma_t, sigma_s0 = self.sigmas[self.step_index + 1], self.sigmas[self.step_index] alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t) alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0) lambda_t = torch.log(alpha_t) - torch.log(sigma_t) lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0) h = lambda_t - lambda_s0 device = sample.device rks = [] D1s: list[Any] | None = [] for i in range(1, order): si = self.step_index - i mi = model_output_list[-(i + 1)] alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si]) lambda_si = torch.log(alpha_si) - torch.log(sigma_si) rk = (lambda_si - lambda_s0) / h rks.append(rk) D1s.append((mi - m0) / rk) rks.append(1.0) rks = torch.tensor(rks, device=device) R = [] b = [] hh = -h if self.predict_x0 else h h_phi_1 = torch.expm1(hh) h_phi_k = h_phi_1 / hh - 1 factorial_i = 1 if self.config.solver_type == "bh1": B_h = hh elif self.config.solver_type == "bh2": B_h = torch.expm1(hh) else: raise NotImplementedError() for i in range(1, order + 1): R.append(torch.pow(rks, i - 1)) b.append(h_phi_k * factorial_i / B_h) factorial_i *= i + 1 h_phi_k = h_phi_k / hh - 1 / factorial_i R = torch.stack(R) b = torch.tensor(b, device=device) if len(D1s) > 0: D1s = torch.stack(D1s, dim=1) if order == 2: rhos_p = torch.tensor([0.5], dtype=x.dtype, device=device) else: rhos_p = torch.linalg.solve(R[:-1, :-1], b[:-1]).to(device).to(x.dtype) else: D1s = None if self.predict_x0: x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0 pred_res = torch.einsum("k,bkc...->bc...", rhos_p, D1s) if D1s is not None else 0 x_t = x_t_ - alpha_t * B_h * pred_res else: x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0 pred_res = torch.einsum("k,bkc...->bc...", rhos_p, D1s) if D1s is not None else 0 x_t = x_t_ - sigma_t * B_h * pred_res return x_t.to(x.dtype) def multistep_uni_c_bh_update( self, this_model_output: torch.Tensor, *args, last_sample: torch.Tensor = None, this_sample: torch.Tensor = None, order: int | None = None, **kwargs, ) -> torch.Tensor: this_timestep = args[0] if len(args) > 0 else kwargs.pop("this_timestep", None) if last_sample is None: if len(args) > 1: last_sample = args[1] else: raise ValueError(" missing`last_sample` as a required keyword argument") if this_sample is None: if len(args) > 2: this_sample = args[2] else: raise ValueError(" missing`this_sample` as a required keyword argument") if order is None: if len(args) > 3: order = args[3] else: raise ValueError(" missing`order` as a required keyword argument") if this_timestep is not None: deprecate("this_timestep", "1.0.0", "Passing `this_timestep` is deprecated and has no effect.") model_output_list = self.model_outputs m0 = model_output_list[-1] x = last_sample x_t = this_sample model_t = this_model_output sigma_t, sigma_s0 = self.sigmas[self.step_index], self.sigmas[self.step_index - 1] alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t) alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0) lambda_t = torch.log(alpha_t) - torch.log(sigma_t) lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0) h = lambda_t - lambda_s0 device = this_sample.device rks = [] D1s: list[Any] | None = [] for i in range(1, order): si = self.step_index - (i + 1) mi = model_output_list[-(i + 1)] alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si]) lambda_si = torch.log(alpha_si) - torch.log(sigma_si) rk = (lambda_si - lambda_s0) / h rks.append(rk) D1s.append((mi - m0) / rk) rks.append(1.0) rks = torch.tensor(rks, device=device) R = [] b = [] hh = -h if self.predict_x0 else h h_phi_1 = torch.expm1(hh) h_phi_k = h_phi_1 / hh - 1 factorial_i = 1 if self.config.solver_type == "bh1": B_h = hh elif self.config.solver_type == "bh2": B_h = torch.expm1(hh) else: raise NotImplementedError() for i in range(1, order + 1): R.append(torch.pow(rks, i - 1)) b.append(h_phi_k * factorial_i / B_h) factorial_i *= i + 1 h_phi_k = h_phi_k / hh - 1 / factorial_i R = torch.stack(R) b = torch.tensor(b, device=device) if len(D1s) > 0: D1s = torch.stack(D1s, dim=1) else: D1s = None if order == 1: rhos_c = torch.tensor([0.5], dtype=x.dtype, device=device) else: rhos_c = torch.linalg.solve(R, b).to(device).to(x.dtype) if self.predict_x0: x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0 corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s) if D1s is not None else 0 D1_t = model_t - m0 x_t = x_t_ - alpha_t * B_h * (corr_res + rhos_c[-1] * D1_t) else: x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0 corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s) if D1s is not None else 0 D1_t = model_t - m0 x_t = x_t_ - sigma_t * B_h * (corr_res + rhos_c[-1] * D1_t) return x_t.to(x.dtype) def index_for_timestep(self, timestep, schedule_timesteps=None): if schedule_timesteps is None: schedule_timesteps = self.timesteps indices = (schedule_timesteps == timestep).nonzero() pos = 1 if len(indices) > 1 else 0 return indices[pos].item() def _init_step_index(self, timestep): if self.begin_index is None: if isinstance(timestep, torch.Tensor): timestep = timestep.to(self.timesteps.device) self._step_index = self.index_for_timestep(timestep) else: self._step_index = self._begin_index def step( self, model_output: torch.Tensor, timestep: int | torch.Tensor, sample: torch.Tensor, return_dict: bool = True, generator=None, ) -> SchedulerOutput | tuple: if self.num_inference_steps is None: raise ValueError( "Number of inference steps is 'None', you need to run 'set_timesteps' after creating the scheduler" ) if self.step_index is None: self._init_step_index(timestep) use_corrector = ( self.step_index > 0 and self.step_index - 1 not in self.disable_corrector and self.last_sample is not None ) model_output_convert = self.convert_model_output(model_output, sample=sample) if use_corrector: sample = self.multistep_uni_c_bh_update( this_model_output=model_output_convert, last_sample=self.last_sample, this_sample=sample, order=self.this_order, ) for i in range(self.config.solver_order - 1): self.model_outputs[i] = self.model_outputs[i + 1] self.timestep_list[i] = self.timestep_list[i + 1] self.model_outputs[-1] = model_output_convert self.timestep_list[-1] = timestep if self.config.lower_order_final: this_order = min(self.config.solver_order, len(self.timesteps) - self.step_index) else: this_order = self.config.solver_order self.this_order = min(this_order, self.lower_order_nums + 1) assert self.this_order > 0 self.last_sample = sample prev_sample = self.multistep_uni_p_bh_update(model_output=model_output, sample=sample, order=self.this_order) if self.lower_order_nums < self.config.solver_order: self.lower_order_nums += 1 self._step_index += 1 if not return_dict: return (prev_sample,) return SchedulerOutput(prev_sample=prev_sample) def step_ddim( self, velocity: torch.FloatTensor, t: int, curr_state: torch.FloatTensor, prev_state: torch.FloatTensor | None = None, generator: torch.Generator | None = None, ): device = curr_state.device curr_t = self.sigmas[t] prev_t = self.sigmas[t + 1] variance_noise = randn_tensor(curr_state.shape, generator=generator, device=device, dtype=curr_state.dtype) cur_clean_ = curr_state - curr_t * velocity return prev_t * variance_noise + (1 - prev_t) * cur_clean_ def step_sde( self, velocity: torch.FloatTensor, t: int, curr_state: torch.FloatTensor, noise_theta: float = 1.0, prev_state: torch.FloatTensor | None = None, generator: torch.Generator | None = None, ): device = curr_state.device curr_t = self.sigmas[t] prev_t = self.sigmas[t + 1] cos = torch.cos(torch.tensor(noise_theta) * torch.pi / 2).to(device) sin = torch.sin(torch.tensor(noise_theta) * torch.pi / 2).to(device) prev_sample_mean = (1 - prev_t + prev_t * cos) * (curr_state - curr_t * velocity) + prev_t * cos * velocity std_dev_t = prev_t * sin std_dev_t = torch.ones((1, 1)).to(curr_state) * std_dev_t if prev_state is None: variance_noise = randn_tensor(curr_state.shape, generator=generator, device=device, dtype=curr_state.dtype) prev_state = prev_sample_mean + std_dev_t * variance_noise else: prev_state = prev_sample_mean + (prev_state - prev_sample_mean.detach()) return prev_state def scale_model_input(self, sample: torch.Tensor, *args, **kwargs) -> torch.Tensor: return sample def add_noise( self, original_samples: torch.Tensor, noise: torch.Tensor, timesteps: torch.IntTensor ) -> torch.Tensor: sigmas = self.sigmas.to(device=original_samples.device, dtype=original_samples.dtype) if original_samples.device.type == "mps" and torch.is_floating_point(timesteps): schedule_timesteps = self.timesteps.to(original_samples.device, dtype=torch.float32) timesteps = timesteps.to(original_samples.device, dtype=torch.float32) else: schedule_timesteps = self.timesteps.to(original_samples.device) timesteps = timesteps.to(original_samples.device) if self.begin_index is None: step_indices = [self.index_for_timestep(t, schedule_timesteps) for t in timesteps] elif self.step_index is not None: step_indices = [self.step_index] * timesteps.shape[0] else: step_indices = [self.begin_index] * timesteps.shape[0] sigma = sigmas[step_indices].flatten() while len(sigma.shape) < len(original_samples.shape): sigma = sigma.unsqueeze(-1) alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma) return alpha_t * original_samples + sigma_t * noise def __len__(self): return self.config.num_train_timesteps # =========================================================================== # Audio VAE (ported from daVinci-MagiHuman inference/model/sa_audio/) # =========================================================================== def _snake_beta(x, alpha, beta): return x + (1.0 / (beta + 1e-9)) * torch.pow(torch.sin(x * alpha), 2) class _SnakeBeta(nn.Module): def __init__(self, in_features: int, alpha: float = 1.0, alpha_trainable: bool = True, alpha_logscale: bool = True): super().__init__() self.alpha_logscale = alpha_logscale if self.alpha_logscale: self.alpha = nn.Parameter(torch.zeros(in_features) * alpha) self.beta = nn.Parameter(torch.zeros(in_features) * alpha) else: self.alpha = nn.Parameter(torch.ones(in_features) * alpha) self.beta = nn.Parameter(torch.ones(in_features) * alpha) self.alpha.requires_grad = alpha_trainable self.beta.requires_grad = alpha_trainable def forward(self, x): alpha = self.alpha.unsqueeze(0).unsqueeze(-1) beta = self.beta.unsqueeze(0).unsqueeze(-1) if self.alpha_logscale: alpha = torch.exp(alpha) beta = torch.exp(beta) return _snake_beta(x, alpha, beta) def _vae_sample(mean, scale): stdev = F.softplus(scale) + 1e-4 var = stdev * stdev logvar = torch.log(var) latents = torch.randn_like(mean) * stdev + mean kl = (mean * mean + var - logvar - 1).sum(1).mean() return latents, kl class _VAEBottleneck(nn.Module): def encode(self, x, return_info=False, **kwargs): info = {} mean, scale = x.chunk(2, dim=1) x, kl = _vae_sample(mean, scale) info["kl"] = kl return (x, info) if return_info else x def decode(self, x): return x def _WNConv1d(*args, **kwargs): return weight_norm(nn.Conv1d(*args, **kwargs)) def _WNConvTranspose1d(*args, **kwargs): return weight_norm(nn.ConvTranspose1d(*args, **kwargs)) def _checkpoint(function, *args, **kwargs): kwargs.setdefault("use_reentrant", False) return torch.utils.checkpoint.checkpoint(function, *args, **kwargs) def _get_activation(activation: Literal["elu", "snake", "none"], antialias: bool = False, channels=None) -> nn.Module: if antialias: raise NotImplementedError("antialias activation not supported") if activation == "elu": return nn.ELU() if activation == "snake": return _SnakeBeta(channels) if activation == "none": return nn.Identity() raise ValueError(f"Unknown activation {activation}") class _ResidualUnit(nn.Module): def __init__(self, in_channels, out_channels, dilation, use_snake=False, antialias_activation=False): super().__init__() padding = (dilation * (7 - 1)) // 2 self.layers = nn.Sequential( _get_activation("snake" if use_snake else "elu", antialias=antialias_activation, channels=out_channels), _WNConv1d(in_channels, out_channels, kernel_size=7, dilation=dilation, padding=padding), _get_activation("snake" if use_snake else "elu", antialias=antialias_activation, channels=out_channels), _WNConv1d(out_channels, out_channels, kernel_size=1), ) def forward(self, x): return (_checkpoint(self.layers, x) if self.training else self.layers(x)) + x class _EncoderBlock(nn.Module): def __init__(self, in_channels, out_channels, stride, use_snake=False, antialias_activation=False): super().__init__() self.layers = nn.Sequential( _ResidualUnit(in_channels, in_channels, 1, use_snake=use_snake), _ResidualUnit(in_channels, in_channels, 3, use_snake=use_snake), _ResidualUnit(in_channels, in_channels, 9, use_snake=use_snake), _get_activation("snake" if use_snake else "elu", antialias=antialias_activation, channels=in_channels), _WNConv1d(in_channels, out_channels, kernel_size=2 * stride, stride=stride, padding=math.ceil(stride / 2)), ) def forward(self, x): return self.layers(x) class _DecoderBlock(nn.Module): def __init__( self, in_channels, out_channels, stride, use_snake=False, antialias_activation=False, use_nearest_upsample=False ): super().__init__() if use_nearest_upsample: upsample_layer = nn.Sequential( nn.Upsample(scale_factor=stride, mode="nearest"), _WNConv1d(in_channels, out_channels, kernel_size=2 * stride, stride=1, bias=False, padding="same"), ) else: upsample_layer = _WNConvTranspose1d( in_channels, out_channels, kernel_size=2 * stride, stride=stride, padding=math.ceil(stride / 2) ) self.layers = nn.Sequential( _get_activation("snake" if use_snake else "elu", antialias=antialias_activation, channels=in_channels), upsample_layer, _ResidualUnit(out_channels, out_channels, 1, use_snake=use_snake), _ResidualUnit(out_channels, out_channels, 3, use_snake=use_snake), _ResidualUnit(out_channels, out_channels, 9, use_snake=use_snake), ) def forward(self, x): return self.layers(x) class _OobleckEncoder(nn.Module): def __init__( self, in_channels=2, channels=128, latent_dim=32, c_mults=[1, 2, 4, 8], strides=[2, 4, 8, 8], use_snake=False, antialias_activation=False, ): super().__init__() c_mults = [1] + c_mults depth = len(c_mults) layers = [_WNConv1d(in_channels, c_mults[0] * channels, kernel_size=7, padding=3)] for i in range(depth - 1): layers.append( _EncoderBlock(c_mults[i] * channels, c_mults[i + 1] * channels, strides[i], use_snake=use_snake) ) layers.extend( [ _get_activation( "snake" if use_snake else "elu", antialias=antialias_activation, channels=c_mults[-1] * channels ), _WNConv1d(c_mults[-1] * channels, latent_dim, kernel_size=3, padding=1), ] ) self.layers = nn.Sequential(*layers) def forward(self, x): return self.layers(x) class _OobleckDecoder(nn.Module): def __init__( self, out_channels=2, channels=128, latent_dim=32, c_mults=[1, 2, 4, 8], strides=[2, 4, 8, 8], use_snake=False, antialias_activation=False, use_nearest_upsample=False, final_tanh=True, ): super().__init__() c_mults = [1] + c_mults depth = len(c_mults) layers = [_WNConv1d(latent_dim, c_mults[-1] * channels, kernel_size=7, padding=3)] for i in range(depth - 1, 0, -1): layers.append( _DecoderBlock( c_mults[i] * channels, c_mults[i - 1] * channels, strides[i - 1], use_snake=use_snake, antialias_activation=antialias_activation, use_nearest_upsample=use_nearest_upsample, ) ) layers.extend( [ _get_activation( "snake" if use_snake else "elu", antialias=antialias_activation, channels=c_mults[0] * channels ), _WNConv1d(c_mults[0] * channels, out_channels, kernel_size=7, padding=3, bias=False), nn.Tanh() if final_tanh else nn.Identity(), ] ) self.layers = nn.Sequential(*layers) def forward(self, x): return self.layers(x) class _AudioAutoencoder(nn.Module): def __init__( self, encoder, decoder, latent_dim, downsampling_ratio, sample_rate, io_channels=2, bottleneck=None, in_channels=None, out_channels=None, soft_clip=False, ): super().__init__() self.downsampling_ratio = downsampling_ratio self.sample_rate = sample_rate self.latent_dim = latent_dim self.io_channels = io_channels self.in_channels = in_channels if in_channels is not None else io_channels self.out_channels = out_channels if out_channels is not None else io_channels self.bottleneck = bottleneck self.encoder = encoder self.decoder = decoder self.soft_clip = soft_clip def encode(self, audio, skip_bottleneck=False, return_info=False, **kwargs): info = {} latents = self.encoder(audio) info["pre_bottleneck_latents"] = latents if self.bottleneck is not None and not skip_bottleneck: latents, bottleneck_info = self.bottleneck.encode(latents, return_info=True, **kwargs) info.update(bottleneck_info) return (latents, info) if return_info else latents def decode(self, latents, skip_bottleneck=False, **kwargs): if self.bottleneck is not None and not skip_bottleneck: latents = self.bottleneck.decode(latents) decoded = self.decoder(latents, **kwargs) if self.soft_clip: decoded = torch.tanh(decoded) return decoded def _create_encoder_from_config(cfg: dict[str, Any]): assert cfg.get("type") == "oobleck", f"Only 'oobleck' encoder supported, got: {cfg.get('type')}" enc = _OobleckEncoder(**cfg["config"]) if not cfg.get("requires_grad", True): for p in enc.parameters(): p.requires_grad = False return enc def _create_decoder_from_config(cfg: dict[str, Any]): assert cfg.get("type") == "oobleck", f"Only 'oobleck' decoder supported, got: {cfg.get('type')}" dec = _OobleckDecoder(**cfg["config"]) if not cfg.get("requires_grad", True): for p in dec.parameters(): p.requires_grad = False return dec def _create_bottleneck_from_config(cfg: dict[str, Any]): assert cfg.get("type") == "vae", f"Only 'vae' bottleneck supported, got: {cfg.get('type')}" bn = _VAEBottleneck() if not cfg.get("requires_grad", True): for p in bn.parameters(): p.requires_grad = False return bn def _create_autoencoder_from_config(config: dict[str, Any]): ae_config = config["model"] if ae_config.get("pretransform") is not None: raise NotImplementedError("Nested pretransform not supported") encoder = _create_encoder_from_config(ae_config["encoder"]) decoder = _create_decoder_from_config(ae_config["decoder"]) bottleneck_cfg = ae_config.get("bottleneck") bottleneck = _create_bottleneck_from_config(bottleneck_cfg) if bottleneck_cfg else None return _AudioAutoencoder( encoder=encoder, decoder=decoder, latent_dim=ae_config["latent_dim"], downsampling_ratio=ae_config["downsampling_ratio"], sample_rate=config["sample_rate"], io_channels=ae_config["io_channels"], bottleneck=bottleneck, in_channels=ae_config.get("in_channels"), out_channels=ae_config.get("out_channels"), soft_clip=ae_config["decoder"].get("soft_clip", False), ) class SAAudioFeatureExtractor: def __init__(self, device, model_path): self.device = device self.vae_model, self.sample_rate = self._load_vae(model_path) self.resampler = None def _load_vae(self, model_path): if not (isinstance(model_path, str) and Path(model_path).is_dir()): raise ValueError("model_path must be a local directory") model_config_path = os.path.join(model_path, "model_config.json") with open(model_config_path) as f: full_config = json.load(f) vae_config = full_config["model"]["pretransform"]["config"] sample_rate = full_config["sample_rate"] autoencoder_config = { "model_type": "autoencoder", "sample_rate": sample_rate, "model": vae_config, } vae_model = _create_autoencoder_from_config(autoencoder_config) weights_path = Path(model_path) / "model.safetensors" if not weights_path.exists(): raise FileNotFoundError(f"Weight file does not exist: {weights_path}") full_state_dict = load_file(weights_path, device=str(self.device)) vae_state_dict = {} for key, value in full_state_dict.items(): if key.startswith("pretransform.model."): vae_state_dict[key[len("pretransform.model.") :]] = value model_keys = set(vae_model.state_dict().keys()) vae_keys = set(vae_state_dict.keys()) missing = model_keys - vae_keys extra = vae_keys - model_keys if missing: logger.warning("Audio VAE missing keys (%d): %s", len(missing), list(missing)[:5]) if extra: logger.warning("Audio VAE unexpected keys (%d): %s", len(extra), list(extra)[:5]) vae_model.load_state_dict(vae_state_dict) vae_model.to(self.device) return vae_model, sample_rate def decode(self, latents): with torch.no_grad(): return self.vae_model.decode(latents) def encode(self, waveform): with torch.no_grad(): return self.vae_model.encode(waveform) # =========================================================================== # Audio utilities (ported from daVinci-MagiHuman inference/pipeline/video_process.py) # =========================================================================== _SAMPLE_RATE = 51200 _AUDIO_CHUNK_DURATION = 29 _OVERLAP_RATIO = 0.5 def _merge_overlapping_vae_features(audio_feats: list[torch.Tensor], overlap_ratio: float = 0.5) -> torch.Tensor | None: if not audio_feats: return None if len(audio_feats) == 1: return audio_feats[0] batch_size, total_frames, feature_dim = audio_feats[0].shape overlap_frames = int(total_frames * overlap_ratio) step_frames = total_frames - overlap_frames final_length = (len(audio_feats) - 1) * step_frames + total_frames output_feat = torch.zeros( batch_size, final_length, feature_dim, device=audio_feats[0].device, dtype=audio_feats[0].dtype ) for block_idx, current_feat in enumerate(audio_feats): output_start = block_idx * step_frames if block_idx == 0: output_feat[:, output_start : output_start + total_frames, :] = current_feat continue non_overlap_start = output_start + overlap_frames non_overlap_end = output_start + total_frames output_feat[:, non_overlap_start:non_overlap_end, :] = current_feat[:, overlap_frames:, :] for frame_idx in range(overlap_frames): output_pos = output_start + frame_idx prev_weight = (overlap_frames - frame_idx) / overlap_frames curr_weight = frame_idx / overlap_frames output_feat[:, output_pos, :] = ( prev_weight * output_feat[:, output_pos, :] + curr_weight * current_feat[:, frame_idx, :] ) return output_feat def load_audio_and_encode(audio_vae, audio_path: str, seconds: int | None = None) -> torch.Tensor: """Load audio from file and encode to latent space using the Stable Audio VAE.""" audio_full = whisper.load_audio(audio_path, sr=_SAMPLE_RATE) if seconds is not None: audio_full = audio_full[: min(int(seconds * _SAMPLE_RATE), audio_full.shape[0])] total_samples = audio_full.shape[0] window_size = int(_AUDIO_CHUNK_DURATION * _SAMPLE_RATE) step_size = int(window_size * (1 - _OVERLAP_RATIO)) if total_samples <= window_size: audio = torch.from_numpy(audio_full).cuda() audio = audio.unsqueeze(0).expand(2, -1) return audio_vae.vae_model.encode(audio) encoded_chunks = [] latent_to_audio_ratio = None for offset_start in range(0, total_samples, step_size): offset_end = min(offset_start + window_size, total_samples) chunk = whisper.pad_or_trim(audio_full[offset_start:offset_end], length=window_size) chunk_tensor = torch.from_numpy(chunk).cuda().unsqueeze(0).expand(2, -1) encoded_chunk = audio_vae.vae_model.encode(chunk_tensor) if latent_to_audio_ratio is None: latent_to_audio_ratio = encoded_chunk.shape[-1] / window_size encoded_chunks.append(encoded_chunk.permute(0, 2, 1)) if offset_end >= total_samples: break final_feat = _merge_overlapping_vae_features(encoded_chunks, overlap_ratio=_OVERLAP_RATIO).permute(0, 2, 1) final_target_len = math.ceil(total_samples * latent_to_audio_ratio) return final_feat[:, :, :final_target_len] # =========================================================================== # Data proxy (ported from daVinci-MagiHuman inference/pipeline/data_proxy.py) # =========================================================================== def _unfold_3d(x: torch.Tensor, kernel_size: tuple[int, int, int], stride: tuple[int, int, int]) -> torch.Tensor: """Pure-PyTorch 3D unfold matching UnfoldAnd behavior. After N unfold ops the shape is (batch, C, oD, oH, oW, kD, kH, kW). UnfoldAnd permutes kernel dims next to channel before reshape so that the col_dim axis is ordered as (C, kD, kH, kW) -- matching F.unfold semantics. Without this permute, .view() interleaves spatial and kernel positions. Args: x: (N, C, D, H, W) kernel_size: (kD, kH, kW) stride: (sD, sH, sW) Returns: (N, C*kD*kH*kW, L) where L = product of output spatial dims. """ ndim = len(kernel_size) for d in range(ndim): x = x.unfold(d + 2, kernel_size[d], stride[d]) perm = [0, 1] + list(range(ndim + 2, 2 * ndim + 2)) + list(range(2, ndim + 2)) x = x.permute(*perm).contiguous() batch_size = x.shape[0] col_dim = 1 for i in range(1, ndim + 2): col_dim *= x.shape[i] spatial = 1 for i in range(ndim + 2, 2 * ndim + 2): spatial *= x.shape[i] return x.view(batch_size, col_dim, spatial) def _calc_local_qk_range(num_video_tokens, num_audio_and_txt_tokens, num_frames, frame_receptive_field): token_per_frame = num_video_tokens // num_frames total_tokens = num_video_tokens + num_audio_and_txt_tokens q_range_list = [] k_range_list = [] for i in range(num_frames): q_range_list.append(torch.tensor([i * token_per_frame, (i + 1) * token_per_frame])) k_range_list.append( torch.tensor( [ (i - frame_receptive_field) * token_per_frame, (i + frame_receptive_field + 1) * token_per_frame, ] ) ) local_q_range = torch.stack(q_range_list, dim=0) local_k_range = torch.stack(k_range_list, dim=0) local_k_range[local_k_range < 0] = 0 local_k_range[local_k_range > num_video_tokens] = num_video_tokens video_q_range = torch.tensor([[0, num_video_tokens]]) video_k_range = torch.tensor([[num_video_tokens, num_video_tokens + num_audio_and_txt_tokens]]) at_q_ranges = torch.tensor([[num_video_tokens, total_tokens]]) at_k_ranges = torch.tensor([[0, total_tokens]]) q_ranges = ( torch.cat([local_q_range, video_q_range, at_q_ranges], dim=0).to(torch.int32).to("cuda", non_blocking=True) ) k_ranges = ( torch.cat([local_k_range, video_k_range, at_k_ranges], dim=0).to(torch.int32).to("cuda", non_blocking=True) ) return q_ranges, k_ranges def _calc_local_attn_ffa_handler(num_video_tokens, num_audio_and_txt_tokens, num_frames, frame_receptive_field): q_ranges, k_ranges = _calc_local_qk_range( num_video_tokens, num_audio_and_txt_tokens, num_frames, frame_receptive_field ) total = num_video_tokens + num_audio_and_txt_tokens return FFAHandler( q_ranges=q_ranges, k_ranges=k_ranges, max_seqlen_q=total, max_seqlen_k=total, attn_type_map=torch.zeros([q_ranges.shape[0]], device="cuda", dtype=torch.int32), softmax_scale=None, ) def _get_coords( shape: list[int], ref_feat_shape: list[int], offset_thw: list[int] | None = None, device: torch.device = torch.device("cpu"), dtype: torch.dtype = torch.float32, ): if offset_thw is None: offset_thw = [0, 0, 0] ori_t, ori_h, ori_w = shape ref_t, ref_h, ref_w = ref_feat_shape offset_t, offset_h, offset_w = offset_thw time_rng = torch.arange(ori_t, device=device, dtype=dtype) + offset_t height_rng = torch.arange(ori_h, device=device, dtype=dtype) + offset_h width_rng = torch.arange(ori_w, device=device, dtype=dtype) + offset_w time_grid, height_grid, width_grid = torch.meshgrid(time_rng, height_rng, width_rng, indexing="ij") coords_flat = torch.stack([time_grid, height_grid, width_grid], dim=-1).reshape(-1, 3) meta = torch.tensor([ori_t, ori_h, ori_w, ref_t, ref_h, ref_w], device=device, dtype=dtype) meta_expanded = meta.expand(coords_flat.size(0), -1) return torch.cat([coords_flat, meta_expanded], dim=-1) @dataclass class _SingleData: video_x_t: torch.Tensor audio_x_t: torch.Tensor audio_feat_len: int txt_feat: torch.Tensor txt_feat_len: int t: int h: int w: int patch_size: int t_patch_size: int spatial_rope_interpolation: Literal["inter", "extra"] ref_audio_offset: int text_offset: int coords_style: Literal["v1", "v2"] = "v1" def __post_init__(self): self.video_token_num = self.video_x_t.shape[0] self.audio_x_t = self.audio_x_t[: self.audio_feat_len] self.txt_feat = self.txt_feat[: self.txt_feat_len] self.video_channel = self.video_x_t.shape[-1] self.audio_channel = self.audio_x_t.shape[-1] self.txt_channel = self.txt_feat.shape[-1] @property def device(self): return self.video_x_t.device @property def default_dtype(self): return self.video_x_t.dtype @property def total_token_num(self): return self.video_token_num + self.audio_feat_len + self.txt_feat_len @property def token_sequence(self): tensors = [self.video_x_t, self.audio_x_t, self.txt_feat] max_channel = max(t.shape[-1] for t in tensors) padded = [F.pad(t, (0, max_channel - t.shape[-1])) for t in tensors] return torch.cat(padded, dim=0) @property def modality_mapping(self): v_map = torch.full((self.video_token_num,), Modality.VIDEO, dtype=torch.int64, device=self.device) a_map = torch.full((self.audio_feat_len,), Modality.AUDIO, dtype=torch.int64, device=self.device) t_map = torch.full((self.txt_feat_len,), Modality.TEXT, dtype=torch.int64, device=self.device) return torch.cat([v_map, a_map, t_map], dim=0) def _default_coords(self, shape, ref_feat_shape, offset_thw=None): if offset_thw is None: offset_thw = [0, 0, 0] return _get_coords( shape=shape, ref_feat_shape=ref_feat_shape, offset_thw=offset_thw, device=self.device, dtype=self.default_dtype, ) @property def coords_mapping(self): if self.spatial_rope_interpolation == "inter": video_ref_feat_shape = (self.t // self.t_patch_size, 32, 32) else: video_ref_feat_shape = (self.t // self.t_patch_size, self.h // self.patch_size, self.w // self.patch_size) video_coords = self._default_coords( shape=(self.t // self.t_patch_size, self.h // self.patch_size, self.w // self.patch_size), ref_feat_shape=video_ref_feat_shape, ) if self.coords_style == "v1": audio_coords = self._default_coords( shape=(self.audio_feat_len, 1, 1), ref_feat_shape=(self.t // self.t_patch_size, 1, 1), ) text_coords = self._default_coords( shape=(self.txt_feat_len, 1, 1), ref_feat_shape=(2, 1, 1), offset_thw=[self.text_offset, 0, 0], ) elif self.coords_style == "v2": magic_audio_ref_t = (self.audio_feat_len - 1) // 4 + 1 audio_coords = self._default_coords( shape=(self.audio_feat_len, 1, 1), ref_feat_shape=(magic_audio_ref_t // self.t_patch_size, 1, 1), ) text_coords = self._default_coords( shape=(self.txt_feat_len, 1, 1), ref_feat_shape=(1, 1, 1), offset_thw=[-self.txt_feat_len, 0, 0], ) else: raise ValueError(f"Unknown coords_style: {self.coords_style}") return torch.cat([video_coords, audio_coords, text_coords], dim=0) def depack_token_sequence(self, token_sequence): video_x_t = token_sequence[: self.video_token_num, : self.video_channel] video_x_t = rearrange( video_x_t, "(T H W) (pT pH pW C) -> C (T pT) (H pH) (W pW)", H=self.h // self.patch_size, W=self.w // self.patch_size, pT=self.t_patch_size, pH=self.patch_size, pW=self.patch_size, ).contiguous() audio_x_t = token_sequence[ self.video_token_num : self.video_token_num + self.audio_feat_len, : self.audio_channel ] return video_x_t, audio_x_t @dataclass class _SimplePackedData: items: list[_SingleData] @property def token_sequence(self): return torch.cat([item.token_sequence for item in self.items], dim=0) @property def modality_mapping(self): return torch.cat([item.modality_mapping for item in self.items], dim=0) @property def coords_mapping(self): return torch.cat([item.coords_mapping for item in self.items], dim=0) @property def total_token_num(self): return sum(item.total_token_num for item in self.items) def __getitem__(self, index): return self.items[index] @property def cu_seqlen(self): cu = torch.cumsum(torch.tensor([item.total_token_num for item in self.items]), dim=0) return F.pad(cu, (1, 0)) @property def max_seqlen(self): return torch.tensor(max(item.total_token_num for item in self.items)) def depack_token_sequence(self, token_sequence): video_list, audio_list = [], [] parts = torch.split(token_sequence, [item.total_token_num for item in self.items], dim=0) for item, part in zip(self.items, parts): v, a = item.depack_token_sequence(part) video_list.append(v) audio_list.append(a) return torch.stack(video_list, dim=0), torch.stack(audio_list, dim=0) class MagiDataProxy: def __init__( self, patch_size: int = 2, t_patch_size: int = 1, frame_receptive_field: int = 11, spatial_rope_interpolation: str = "extra", ref_audio_offset: int = 1000, text_offset: int = 0, coords_style: str = "v2", ): self.patch_size = patch_size self.t_patch_size = t_patch_size self.frame_receptive_field = frame_receptive_field self.spatial_rope_interpolation = spatial_rope_interpolation self.ref_audio_offset = ref_audio_offset self.text_offset = text_offset self.coords_style = coords_style self._kernel = (t_patch_size, patch_size, patch_size) self._stride = (t_patch_size, patch_size, patch_size) self._saved_data: dict[str, Any] = {} def saved_for_output(self, **kwargs): self._saved_data.update(kwargs) def get_saved_data(self, key: str): return self._saved_data[key] def img2tokens(self, x_t: torch.Tensor): x_t_unfolded = _unfold_3d(x_t, self._kernel, self._stride) return rearrange(x_t_unfolded, "N col_dim num_tokens -> N num_tokens col_dim").contiguous() def process_input(self, transported_data: EvalInput): batch_size, _, t, h, w = transported_data.x_t.shape x_t = self.img2tokens(transported_data.x_t) audio_x_t = transported_data.audio_x_t.contiguous() text_in = transported_data.txt_feat.contiguous() simple_packed_data = _SimplePackedData(items=[]) for i in range(batch_size): single_data = _SingleData( video_x_t=x_t[i], audio_x_t=audio_x_t[i], audio_feat_len=transported_data.audio_feat_len[i], txt_feat=text_in[i], txt_feat_len=transported_data.txt_feat_len[i], t=t, h=h, w=w, patch_size=self.patch_size, t_patch_size=self.t_patch_size, spatial_rope_interpolation=self.spatial_rope_interpolation, ref_audio_offset=self.ref_audio_offset, text_offset=self.text_offset, coords_style=self.coords_style, ) simple_packed_data.items.append(single_data) if self.frame_receptive_field != -1: assert batch_size == 1, "local attention only supports batch size 1" local_attn_handler = _calc_local_attn_ffa_handler( num_video_tokens=simple_packed_data[0].video_token_num, num_audio_and_txt_tokens=simple_packed_data[0].audio_feat_len + simple_packed_data[0].txt_feat_len, num_frames=t, frame_receptive_field=self.frame_receptive_field, ) if isinstance(local_attn_handler.max_seqlen_k, torch.Tensor): local_attn_handler.max_seqlen_k = local_attn_handler.max_seqlen_k.item() if isinstance(local_attn_handler.max_seqlen_q, torch.Tensor): local_attn_handler.max_seqlen_q = local_attn_handler.max_seqlen_q.item() else: local_attn_handler = None varlen_handler = VarlenHandler( cu_seqlens_q=simple_packed_data.cu_seqlen.to(torch.int32).cuda(), cu_seqlens_k=simple_packed_data.cu_seqlen.to(torch.int32).cuda(), max_seqlen_q=simple_packed_data.max_seqlen.to(torch.int32).cuda(), max_seqlen_k=simple_packed_data.max_seqlen.to(torch.int32).cuda(), ) self.saved_for_output(simple_packed_data=simple_packed_data) x = simple_packed_data.token_sequence coords_mapping = simple_packed_data.coords_mapping modality_mapping = simple_packed_data.modality_mapping return (x, coords_mapping, modality_mapping, varlen_handler, local_attn_handler) def process_output(self, x: torch.Tensor): simple_packed_data: _SimplePackedData = self.get_saved_data("simple_packed_data") return simple_packed_data.depack_token_sequence(x) # =========================================================================== # Pipeline helpers # =========================================================================== @dataclass class EvalInput: x_t: torch.Tensor audio_x_t: torch.Tensor audio_feat_len: torch.Tensor | list[int] txt_feat: torch.Tensor txt_feat_len: torch.Tensor | list[int] class _T5GemmaEncoder: def __init__(self, model_path: str, device: str, weight_dtype: torch.dtype, subfolder: str | None = None): from vllm.distributed import get_tensor_model_parallel_world_size self.device = device hf_kwargs: dict[str, Any] = {} if subfolder is not None: hf_kwargs["subfolder"] = subfolder self.tokenizer = AutoTokenizer.from_pretrained(model_path, **hf_kwargs) tp_size = get_tensor_model_parallel_world_size() if tp_size > 1: from transformers.models.t5gemma.configuration_t5gemma import T5GemmaConfig config = T5GemmaConfig.from_pretrained(model_path, **hf_kwargs) # The config we need is the encoder config config_encoder = config.encoder # Propagate some outer config values config_encoder.vocab_size = config.vocab_size config_encoder.rms_norm_eps = getattr(config, "rms_norm_eps", config_encoder.rms_norm_eps) self.model = T5GemmaEncoderModelTP(config_encoder).to(device).to(weight_dtype) self.is_tp = True else: self.model = T5GemmaEncoderModel.from_pretrained( model_path, is_encoder_decoder=False, dtype=weight_dtype, **hf_kwargs ).to(device) self.is_tp = False @torch.inference_mode() def encode(self, prompt: str) -> torch.Tensor: inputs = self.tokenizer([prompt], return_tensors="pt").to(self.device) outputs = self.model(**inputs) if self.is_tp: # T5GemmaEncoderModelTP just returns the hidden states tensor return outputs.half() else: # HF model returns BaseModelOutput return outputs["last_hidden_state"].half() def _pad_or_trim(tensor: torch.Tensor, target_size: int, dim: int, pad_value: float = 0.0) -> tuple[torch.Tensor, int]: current_size = tensor.size(dim) if current_size < target_size: padding_amount = target_size - current_size padding_tuple = [0] * (2 * tensor.dim()) padding_dim_index = tensor.dim() - 1 - dim padding_tuple[2 * padding_dim_index + 1] = padding_amount return F.pad(tensor, tuple(padding_tuple), "constant", pad_value), current_size slicing = [slice(None)] * tensor.dim() slicing[dim] = slice(0, target_size) return tensor[tuple(slicing)], target_size def _get_padded_t5_gemma_embedding( prompt: str, encoder: _T5GemmaEncoder, target_length: int, ) -> tuple[torch.Tensor, int]: txt_feat = encoder.encode(prompt) txt_feat, original_len = _pad_or_trim(txt_feat, target_size=target_length, dim=1) return txt_feat.to(torch.float32), original_len def _resizecrop(img: Image.Image, target_height: int, target_width: int) -> Image.Image: """Centre-crop resize keeping aspect ratio then letterbox to target.""" pil_image = img.convert("RGB") original_width, original_height = pil_image.size scale_x = target_width / original_width scale_y = target_height / original_height scale = max(scale_x, scale_y) new_width = int(round(original_width * scale)) new_height = int(round(original_height * scale)) resized_image = pil_image.resize((new_width, new_height), Image.LANCZOS) left = (new_width - target_width) // 2 top = (new_height - target_height) // 2 return resized_image.crop((left, top, left + target_width, top + target_height)) class ZeroSNRDDPMDiscretization: """ZeroSNR DDPM sigma schedule, ported from daVinci-MagiHuman. Used to compute sigma values for SR noise injection. """ def __init__( self, linear_start: float = 0.00085, linear_end: float = 0.0120, num_timesteps: int = 1000, shift_scale: float = 1.0, keep_start: bool = False, post_shift: bool = False, ): from functools import partial 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) ** 2 alphas = 1.0 - betas.cpu().numpy() self.alphas_cumprod = np.cumprod(alphas, axis=0) self.to_torch = partial(torch.tensor, dtype=torch.float32) if not post_shift: self.alphas_cumprod = self.alphas_cumprod / (shift_scale + (1 - shift_scale) * self.alphas_cumprod) self.post_shift = post_shift self.shift_scale = shift_scale def __call__( self, n: int, do_append_zero: bool = True, device: str = "cpu", flip: bool = False, return_idx: bool = False, ): from functools import partial 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(f"n={n} > num_timesteps={self.num_timesteps}") to_torch = partial(torch.tensor, dtype=torch.float32, device=device) alphas_cumprod = to_torch(alphas_cumprod) 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 sigmas = torch.flip(alphas_cumprod_sqrt, (0,)) sigmas = torch.cat([sigmas, sigmas.new_zeros([1])]) if do_append_zero else sigmas if return_idx: return sigmas if not flip else torch.flip(sigmas, (0,)), timesteps return sigmas if not flip else torch.flip(sigmas, (0,)) def _schedule_latent_step( *, video_scheduler: FlowUniPCMultistepScheduler, audio_scheduler: FlowUniPCMultistepScheduler, latent_video: torch.Tensor, latent_audio: torch.Tensor, t, idx: int, steps, v_cfg_video: torch.Tensor, v_cfg_audio: torch.Tensor, is_a2v: bool, cfg_number: int, using_sde_flag: bool, use_sr_model: bool = False, ): # Fast DDIM path for cfg_number==1, only used during the BR stage if cfg_number == 1 and not use_sr_model: latent_video = video_scheduler.step_ddim(v_cfg_video, idx, latent_video) latent_audio = audio_scheduler.step_ddim(v_cfg_audio, idx, latent_audio) return latent_video, latent_audio if using_sde_flag: if use_sr_model: # SR stage with SDE: only update video, keep audio unchanged latent_video = video_scheduler.step(v_cfg_video, t, latent_video, return_dict=False)[0] return latent_video, latent_audio if idx < int(len(steps) * (3 / 4)): noise_theta = 1.0 if (idx + 1) % 2 == 0 else 0.0 else: noise_theta = 1.0 if idx % 3 == 0 else 0.0 latent_video = video_scheduler.step_sde(v_cfg_video, idx, latent_video, noise_theta=noise_theta) if not is_a2v: latent_audio = audio_scheduler.step_sde(v_cfg_audio, idx, latent_audio, noise_theta=noise_theta) return latent_video, latent_audio latent_video = video_scheduler.step(v_cfg_video, t, latent_video, return_dict=False)[0] # Do not update audio latent during the SR stage if not is_a2v and not use_sr_model: latent_audio = audio_scheduler.step(v_cfg_audio, t, latent_audio, return_dict=False)[0] return latent_video, latent_audio _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" ) # =========================================================================== # Pre/post process funcs (registered in registry) # =========================================================================== def get_magi_human_pre_process_func(*args, **kwargs): def pre_process(request: OmniDiffusionRequest): return request return pre_process def get_magi_human_post_process_func(*args, **kwargs): def post_process(output): if isinstance(output, tuple) and len(output) == 2: video, audio = output return { "video": video, "audio": audio, "audio_sample_rate": 44100, "fps": 25, } return output return post_process # =========================================================================== # HF Hub / local path helpers # =========================================================================== def _load_json(model_path: str, filename: str, local_files_only: bool = True) -> dict: """Load a JSON config file from a local path or HuggingFace Hub repo.""" if local_files_only: path = os.path.join(model_path, *filename.split("/")) with open(path) as f: return json.load(f) else: from huggingface_hub import hf_hub_download cached = hf_hub_download(repo_id=model_path, filename=filename) with open(cached) as f: return json.load(f) def _resolve_subdir( model_path: str, subfolder: str, local_files_only: bool = True, required_files: list[str] | None = None, ) -> str: """Resolve a model subfolder to a local directory path. For HF Hub repos, downloads all ``required_files`` (default: ``["config.json"]``) into the HF cache and returns the parent directory. """ if local_files_only: return os.path.join(model_path, subfolder) from huggingface_hub import hf_hub_download files = required_files or ["config.json"] last_cached: str | None = None for fname in files: last_cached = hf_hub_download(repo_id=model_path, filename=f"{subfolder}/{fname}") return os.path.dirname(last_cached) # =========================================================================== # Main Pipeline # =========================================================================== class MagiHumanPipeline(nn.Module, ProgressBarMixin, SupportsComponentDiscovery, DiffusionPipelineProfilerMixin): _dit_modules: ClassVar[list[str]] = ["dit", "sr_dit"] _encoder_modules: ClassVar[list[str]] = ["text_encoder"] _vae_modules: ClassVar[list[str]] = ["vae", "audio_vae"] def __init__(self, od_config: OmniDiffusionConfig, **kwargs): super().__init__() model_path = od_config.model local_files_only = os.path.exists(model_path) device = f"cuda:{torch.accelerator.current_device_index()}" self.device_str = device self.dtype = od_config.dtype or torch.bfloat16 model_index = _load_json(model_path, "model_index.json", local_files_only) eval_cfg = model_index dp_cfg = model_index.get("data_proxy", {}) dit_subfolder = "transformer" dit_json = _load_json(model_path, f"{dit_subfolder}/config.json", local_files_only) dit_model_config = MagiHumanDiTConfig(**dit_json) self.dit = DiTModel(dit_model_config) self.dit.eval() self.vae = DistributedAutoencoderKLWan.from_pretrained(model_path, subfolder="vae") self.vae.to(device) self.vae.eval() vae_cfg = _load_json(model_path, "vae/config.json", local_files_only) self.vae_latent_mean = torch.tensor(vae_cfg["latents_mean"], dtype=torch.float32) self.vae_latent_std = torch.tensor(vae_cfg["latents_std"], dtype=torch.float32) self.audio_vae = SAAudioFeatureExtractor( device=device, model_path=_resolve_subdir( model_path, "audio_vae", local_files_only, required_files=["config.json", "model_config.json", "model.safetensors"], ), ) logger.info("Loading T5Gemma text encoder from %s (subfolder=text_encoder)", model_path) if local_files_only: txt_enc_path = os.path.join(model_path, "text_encoder") txt_enc_subfolder = None else: txt_enc_path = model_path txt_enc_subfolder = "text_encoder" self.text_encoder = _T5GemmaEncoder( model_path=txt_enc_path, device=device, weight_dtype=self.dtype, subfolder=txt_enc_subfolder, ) self.data_proxy = MagiDataProxy( patch_size=dp_cfg.get("patch_size", 2), t_patch_size=dp_cfg.get("t_patch_size", 1), frame_receptive_field=dp_cfg.get("frame_receptive_field", 11), spatial_rope_interpolation=dp_cfg.get("spatial_rope_interpolation", "extra"), ref_audio_offset=dp_cfg.get("ref_audio_offset", 1000), text_offset=dp_cfg.get("text_offset", 0), coords_style=dp_cfg.get("coords_style", "v2"), ) # SR DataProxy forces v1 coordinate style (consistent with the original) self.sr_data_proxy = MagiDataProxy( patch_size=dp_cfg.get("patch_size", 2), t_patch_size=dp_cfg.get("t_patch_size", 1), frame_receptive_field=dp_cfg.get("frame_receptive_field", 11), spatial_rope_interpolation=dp_cfg.get("spatial_rope_interpolation", "extra"), ref_audio_offset=dp_cfg.get("ref_audio_offset", 1000), text_offset=dp_cfg.get("text_offset", 0), coords_style="v1", ) self.fps = eval_cfg.get("fps", 25) self.num_inference_steps_default = eval_cfg.get("num_inference_steps", 32) self.video_txt_guidance_scale = eval_cfg.get("video_txt_guidance_scale", 5.0) self.audio_txt_guidance_scale = eval_cfg.get("audio_txt_guidance_scale", 5.0) self.shift = eval_cfg.get("shift", 5.0) self.cfg_number = eval_cfg.get("cfg_number", 2) self.use_cfg_trick = eval_cfg.get("use_cfg_trick", True) self.cfg_trick_start_frame = eval_cfg.get("cfg_trick_start_frame", 13) self.cfg_trick_value = eval_cfg.get("cfg_trick_value", 2.0) self.using_sde_flag = eval_cfg.get("using_sde_flag", False) self.t5_gemma_target_length = eval_cfg.get("t5_gemma_target_length", 640) self.vae_stride = eval_cfg.get("vae_stride", [4, 16, 16]) self.z_dim = eval_cfg.get("z_dim", 48) self.patch_size = eval_cfg.get("patch_size", [1, 2, 2]) # SR-specific hyperparameters self.sr_num_inference_steps_default = eval_cfg.get("sr_num_inference_steps", 5) self.sr_cfg_number = eval_cfg.get("sr_cfg_number", 2) self.sr_video_txt_guidance_scale = eval_cfg.get("sr_video_txt_guidance_scale", 3.5) self.noise_value = eval_cfg.get("noise_value", 220) self.sr_audio_noise_scale = eval_cfg.get("sr_audio_noise_scale", 0.7) # ZeroSNR sigma schedule for SR noise injection (flip=True, high to low) self.zerosnr_sigmas = ZeroSNRDDPMDiscretization()(1000, do_append_zero=False, flip=True) self.context_null, self.original_context_null_len = _get_padded_t5_gemma_embedding( _NEGATIVE_PROMPT, self.text_encoder, self.t5_gemma_target_length, ) self.video_processor = VideoProcessor(vae_scale_factor=16) # SR DiT model (loaded from the sr/ subdirectory) sr_dit_subfolder = "sr" sr_dit_json = _load_json(model_path, f"{sr_dit_subfolder}/config.json", local_files_only) sr_dit_model_config = MagiHumanDiTConfig(**sr_dit_json) self.sr_dit = DiTModel(sr_dit_model_config) self.sr_dit.eval() self.weights_sources = [ DiffusersPipelineLoader.ComponentSource( model_or_path=model_path, subfolder=dit_subfolder, revision=None, prefix="dit.", fall_back_to_pt=True, ), DiffusersPipelineLoader.ComponentSource( model_or_path=model_path, subfolder=sr_dit_subfolder, revision=None, prefix="sr_dit.", fall_back_to_pt=True, ), ] if getattr(self.text_encoder, "is_tp", False): self.weights_sources.append( DiffusersPipelineLoader.ComponentSource( model_or_path=model_path, subfolder="text_encoder", revision=None, prefix="text_encoder.", fall_back_to_pt=True, ), ) def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]: # Weight loading for MagiHuman DiT with TP support. # # The checkpoint stores weights with these naming patterns: # - attention.linear_qkv.weight: fused [Q, K, V, G] for shared layers, # or stacked per-expert [expert0_Q|K|V|G, expert1_..., expert2_...] for MoE. # - attention.linear_proj.weight: single for shared, stacked per-expert for MoE. # - mlp.up_gate_proj.weight / mlp.down_proj.weight: similarly stacked for MoE. # # The model now uses per-expert vLLM parallel layers for MoE blocks: # attention.linear_qkv.experts.{i}.weight (QKVParallelLinear per expert) # attention.linear_gating.experts.{i}.weight (ColumnParallelLinear per expert) # attention.linear_proj.experts.{i}.weight (RowParallelLinear per expert) # mlp.up_gate_proj.experts.{i}.weight (ColumnParallelLinear per expert) # mlp.down_proj.experts.{i}.weight (RowParallelLinear per expert) # # Shared layers keep the same naming (no .experts.). params_dict = dict(self.named_parameters()) modules_dict = dict(self.named_modules()) loaded_params: set[str] = set() for name, loaded_weight in weights: # ── Text Encoder weights ── if name.startswith("text_encoder."): if getattr(self.text_encoder, "is_tp", False): # Strip "text_encoder." prefix for the T5Gemma TP model # The T5GemmaEncoderModelTP load_weights handles the "encoder." prefix itself sub_name = name[len("text_encoder.") :] loaded_params.update( f"text_encoder.{k}" for k in self.text_encoder.model.load_weights([(sub_name, loaded_weight)]) ) else: loaded_params.add(name) continue # ── Shared attention QKV + Gating split ── # Checkpoint: attention.linear_qkv.weight = [Q, K, V, G] fused. # Model: attention.linear_qkv.weight (QKVParallelLinear) + attention.linear_gating.weight. if "attention.linear_qkv.weight" in name: gating_name = name.replace("attention.linear_qkv.weight", "attention.linear_gating.weight") # Check if this is a shared layer (direct param exists, no .experts.) if name in params_dict and gating_name in params_dict: qkv_param = params_dict[name] gating_param = params_dict[gating_name] mod_path = name[: -len(".weight")] qkv_mod = modules_dict.get(mod_path) if qkv_mod is not None and hasattr(qkv_mod, "total_num_heads"): total_heads_q = qkv_mod.total_num_heads total_heads_kv = qkv_mod.total_num_kv_heads head_dim = qkv_mod.head_size else: head_dim = 128 tp_size = get_tensor_model_parallel_world_size() total_heads_q = gating_param.data.shape[0] * tp_size total_heads_kv = (loaded_weight.shape[0] - total_heads_q * head_dim - total_heads_q) // ( 2 * head_dim ) q_size = total_heads_q * head_dim kv_size = total_heads_kv * head_dim q_w = loaded_weight[:q_size] k_w = loaded_weight[q_size : q_size + kv_size] v_w = loaded_weight[q_size + kv_size : q_size + 2 * kv_size] g_w = loaded_weight[q_size + 2 * kv_size :] qkv_loader = getattr(qkv_param, "weight_loader", default_weight_loader) qkv_loader(qkv_param, q_w, "q") qkv_loader(qkv_param, k_w, "k") qkv_loader(qkv_param, v_w, "v") gating_loader = getattr(gating_param, "weight_loader", default_weight_loader) gating_loader(gating_param, g_w) loaded_params.add(name) loaded_params.add(gating_name) continue # ── MoE attention QKV + Gating split ── # Checkpoint: attention.linear_qkv.weight = stacked [expert0_QKVG, expert1_QKVG, ...]. # Model: attention.linear_qkv.experts.{i}.weight (QKVParallelLinear per expert) # + attention.linear_gating.experts.{i}.weight (ColumnParallelLinear per expert). expert0_name = name.replace("attention.linear_qkv.weight", "attention.linear_qkv.experts.0.weight") if expert0_name in params_dict: # Determine num_experts by checking which expert indices exist. moe_qkv_mod_path = name[: -len(".weight")] moe_qkv_mod = modules_dict.get(moe_qkv_mod_path) num_experts = moe_qkv_mod.num_experts if moe_qkv_mod is not None else 3 # Get head info from the first expert's QKVParallelLinear. expert0_mod_path = name.replace("attention.linear_qkv.weight", "attention.linear_qkv.experts.0") expert0_mod = modules_dict.get(expert0_mod_path) if expert0_mod is not None and hasattr(expert0_mod, "total_num_heads"): total_heads_q = expert0_mod.total_num_heads total_heads_kv = expert0_mod.total_num_kv_heads head_dim = expert0_mod.head_size else: head_dim = 128 # Infer from checkpoint weight shape. # We'll get exact sizes from model config below. total_heads_q = 40 # fallback for default config total_heads_kv = 8 q_size = total_heads_q * head_dim kv_size = total_heads_kv * head_dim # Check if gating is present. gating_expert0_name = name.replace( "attention.linear_qkv.weight", "attention.linear_gating.experts.0.weight" ) has_gating = gating_expert0_name in params_dict # Split stacked checkpoint weight into per-expert chunks. expert_weights = loaded_weight.chunk(num_experts, dim=0) for i in range(num_experts): expert_w = expert_weights[i] # Each expert chunk: [Q, K, V, G (optional)]. q_w = expert_w[:q_size] k_w = expert_w[q_size : q_size + kv_size] v_w = expert_w[q_size + kv_size : q_size + 2 * kv_size] expert_param_name = name.replace( "attention.linear_qkv.weight", f"attention.linear_qkv.experts.{i}.weight", ) expert_param = params_dict[expert_param_name] expert_loader = getattr(expert_param, "weight_loader", default_weight_loader) expert_loader(expert_param, q_w, "q") expert_loader(expert_param, k_w, "k") expert_loader(expert_param, v_w, "v") loaded_params.add(expert_param_name) if has_gating: g_w = expert_w[q_size + 2 * kv_size :] gating_param_name = name.replace( "attention.linear_qkv.weight", f"attention.linear_gating.experts.{i}.weight", ) gating_param = params_dict[gating_param_name] gating_loader = getattr(gating_param, "weight_loader", default_weight_loader) gating_loader(gating_param, g_w) loaded_params.add(gating_param_name) continue # ── MoE stacked weight splitting for proj / MLP layers ── # Checkpoint: x.y.weight (stacked [expert0, expert1, ...]). # Model: x.y.experts.{i}.weight. if name not in params_dict: # Check if this is a stacked MoE weight by looking for .experts.0. base, _, suffix = name.rpartition(".") expert0_name = f"{base}.experts.0.{suffix}" if base else None if expert0_name and expert0_name in params_dict: # Determine num_experts. moe_mod = modules_dict.get(base) num_experts = getattr(moe_mod, "num_experts", 3) if moe_mod is not None else 3 # Split stacked weight into per-expert chunks. expert_weights = loaded_weight.chunk(num_experts, dim=0) for i in range(num_experts): expert_param_name = f"{base}.experts.{i}.{suffix}" if expert_param_name not in params_dict: continue expert_param = params_dict[expert_param_name] expert_loader = getattr(expert_param, "weight_loader", default_weight_loader) expert_loader(expert_param, expert_weights[i]) loaded_params.add(expert_param_name) continue # Truly unknown weight — skip. continue # ── Standard weight loading (shared layers + non-MoE params) ── param = params_dict[name] weight_loader = getattr(param, "weight_loader", default_weight_loader) weight_loader(param, loaded_weight) loaded_params.add(name) if getattr(self.text_encoder, "is_tp", False): self.context_null, self.original_context_null_len = _get_padded_t5_gemma_embedding( _NEGATIVE_PROMPT, self.text_encoder, self.t5_gemma_target_length, ) return loaded_params def _dit_forward(self, eval_input: EvalInput) -> tuple[torch.Tensor, torch.Tensor]: packed = self.data_proxy.process_input(eval_input) noise_pred = self.dit(*packed) return self.data_proxy.process_output(noise_pred) def _sr_dit_forward(self, eval_input: EvalInput) -> tuple[torch.Tensor, torch.Tensor]: """SR stage uses sr_data_proxy (coords_style=v1) and sr_dit model.""" packed = self.sr_data_proxy.process_input(eval_input) noise_pred = self.sr_dit(*packed) return self.sr_data_proxy.process_output(noise_pred) @torch.inference_mode() def _evaluate_with_latent( self, context: torch.Tensor, original_context_len: int, latent_image: torch.Tensor | None, latent_video: torch.Tensor, latent_audio: torch.Tensor, num_inference_steps: int, is_a2v: bool = False, use_sr_model: bool = False, ) -> tuple[torch.Tensor, torch.Tensor]: # Select cfg_number and guidance_scale based on BR/SR stage cfg_number = self.sr_cfg_number if use_sr_model else self.cfg_number video_guidance = self.sr_video_txt_guidance_scale if use_sr_model else self.video_txt_guidance_scale forward_fn = self._sr_dit_forward if use_sr_model else self._dit_forward video_scheduler = FlowUniPCMultistepScheduler() audio_scheduler = FlowUniPCMultistepScheduler() video_scheduler.set_timesteps(num_inference_steps, device=self.device_str, shift=self.shift) audio_scheduler.set_timesteps(num_inference_steps, device=self.device_str, shift=self.shift) timesteps = video_scheduler.timesteps latent_length = latent_video.shape[2] cfg_trick_guidance = ( torch.tensor(video_guidance, device=self.device_str).expand(1, 1, latent_length, 1, 1).clone() ) if self.use_cfg_trick: cfg_trick_guidance[:, :, : self.cfg_trick_start_frame] = min(self.cfg_trick_value, video_guidance) with self.progress_bar(total=len(timesteps)) as pbar: for idx, t in enumerate(timesteps): if latent_image is not None: latent_video[:, :, :1] = latent_image[:, :, :1] # Reduce guidance when t<=500 during BR stage (original behavior) cur_video_guidance = video_guidance if (use_sr_model or t > 500) else 2.0 eval_input_cond = EvalInput( x_t=latent_video, audio_x_t=latent_audio, audio_feat_len=[latent_audio.shape[1]], txt_feat=context, txt_feat_len=[original_context_len], ) v_cond_video, v_cond_audio = forward_fn(eval_input_cond) if cfg_number == 1: v_cfg_video = v_cond_video v_cfg_audio = v_cond_audio elif cfg_number == 2: eval_input_uncond = EvalInput( x_t=latent_video, audio_x_t=latent_audio, audio_feat_len=[latent_audio.shape[1]], txt_feat=self.context_null, txt_feat_len=[self.original_context_null_len], ) v_uncond_video, v_uncond_audio = forward_fn(eval_input_uncond) v_cfg_video = v_uncond_video + cur_video_guidance * (v_cond_video - v_uncond_video) v_cfg_audio = v_uncond_audio + self.audio_txt_guidance_scale * (v_cond_audio - v_uncond_audio) else: raise ValueError(f"Invalid cfg_number: {cfg_number}") latent_video, latent_audio = _schedule_latent_step( video_scheduler=video_scheduler, audio_scheduler=audio_scheduler, latent_video=latent_video, latent_audio=latent_audio, t=t, idx=idx, steps=timesteps, v_cfg_video=v_cfg_video, v_cfg_audio=v_cfg_audio, is_a2v=is_a2v, cfg_number=cfg_number, using_sde_flag=self.using_sde_flag, use_sr_model=use_sr_model, ) pbar.update() if latent_image is not None: latent_video[:, :, :1] = latent_image[:, :, :1] return latent_video, latent_audio def _encode_image(self, image: Image.Image, height: int, width: int) -> torch.Tensor: image = load_image(image) image = _resizecrop(image, height, width) image = self.video_processor.preprocess(image, height=height, width=width) image = image.to(device=self.device_str, dtype=self.dtype).unsqueeze(2) vae_out = self.vae.encode(image) if hasattr(vae_out, "latent_dist"): return vae_out.latent_dist.mode().to(torch.float32) return vae_out.to(torch.float32) def _decode_video(self, latent: torch.Tensor) -> list[np.ndarray]: mean = self.vae_latent_mean.to(latent.device, dtype=latent.dtype).view(1, -1, 1, 1, 1) std = self.vae_latent_std.to(latent.device, dtype=latent.dtype).view(1, -1, 1, 1, 1) latent = latent * std + mean videos = self.vae.decode(latent.to(self.dtype)) if hasattr(videos, "sample"): videos = videos.sample videos.mul_(0.5).add_(0.5).clamp_(0, 1) videos = [v.float().cpu().permute(1, 2, 3, 0) * 255 for v in videos] return [v.numpy().astype(np.uint8) for v in videos] def _decode_audio(self, latent_audio: torch.Tensor) -> np.ndarray: latent_audio = latent_audio.squeeze(0).to(self.dtype) audio_output = self.audio_vae.decode(latent_audio.T) audio_np = audio_output.squeeze(0).T.float().cpu().numpy() target_len = int(audio_np.shape[0] * 441 / 512) from scipy.signal import resample return resample(audio_np, target_len) def encode_prompt( self, prompt: str, target_length: int | None = None, ) -> tuple[torch.Tensor, int]: """Encode *prompt* with the T5-Gemma text encoder and pad to fixed length. This is the single text-encoder entrypoint so the runner-level prompt-embedding cache (see ``vllm_omni/diffusion/cache/prompt_embed_cache.py``) can transparently memoize results when the same prompt is submitted repeatedly. Returns: ``(context, original_context_len)`` matching :func:`_get_padded_t5_gemma_embedding`. """ return _get_padded_t5_gemma_embedding( prompt, self.text_encoder, target_length if target_length is not None else self.t5_gemma_target_length, ) @torch.inference_mode() def forward( self, req: DiffusionRequestBatch, prompt: str | None = None, height: int = 256, width: int = 448, num_inference_steps: int | None = None, seconds: int = 10, seed: int | None = None, image_path: str | None = None, audio_path: str | None = None, **kwargs, ) -> DiffusionOutput: if len(req.prompts) >= 1: p = req.prompts[0] prompt = p if isinstance(p, str) else p.get("prompt", prompt) if not isinstance(p, str): image_path = p.get("image_path", image_path) audio_path = p.get("audio_path", audio_path) if prompt is None: raise ValueError("prompt is required") height = req.sampling_params.height or height width = req.sampling_params.width or width seed = req.sampling_params.seed if req.sampling_params.seed is not None else seed num_steps = req.sampling_params.num_inference_steps or num_inference_steps or self.num_inference_steps_default sr_height: int | None = None sr_width: int | None = None sr_num_steps: int | None = None if hasattr(req.sampling_params, "extra_args") and req.sampling_params.extra_args: seconds = req.sampling_params.extra_args.get("seconds", seconds) audio_path = req.sampling_params.extra_args.get("audio_path", audio_path) image_path = req.sampling_params.extra_args.get("image_path", image_path) sr_height = req.sampling_params.extra_args.get("sr_height", None) sr_width = req.sampling_params.extra_args.get("sr_width", None) sr_num_steps = req.sampling_params.extra_args.get("sr_num_inference_steps", None) device = self.device_str br_latent_height = height // self.vae_stride[1] // self.patch_size[1] * self.patch_size[1] br_latent_width = width // self.vae_stride[2] // self.patch_size[2] * self.patch_size[2] br_height = br_latent_height * self.vae_stride[1] br_width = br_latent_width * self.vae_stride[2] if seed is not None: torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) if audio_path is not None: latent_audio = load_audio_and_encode(self.audio_vae, audio_path, seconds) latent_audio = latent_audio.permute(0, 2, 1) num_frames = latent_audio.shape[1] is_a2v = True else: num_frames = seconds * self.fps + 1 latent_audio = torch.randn(1, num_frames, 64, dtype=torch.float32, device=device) is_a2v = False latent_length = (num_frames - 1) // 4 + 1 latent_video = torch.randn( 1, self.z_dim, latent_length, br_latent_height, br_latent_width, dtype=torch.float32, device=device, ) context, original_context_len = self.encode_prompt(prompt) if image_path is not None: br_image = self._encode_image(load_image(image_path), br_height, br_width) else: br_image = None # ── BR stage ───────────────────────────────────────────────────────── br_latent_video, br_latent_audio = self._evaluate_with_latent( context, original_context_len, br_image, latent_video.clone(), latent_audio.clone(), num_steps, is_a2v, use_sr_model=False, ) # ── SR stage (optional, triggered when sr_height/sr_width are provided) ── if sr_height is not None and sr_width is not None: sr_latent_height = sr_height // self.vae_stride[1] // self.patch_size[1] * self.patch_size[1] sr_latent_width = sr_width // self.vae_stride[2] // self.patch_size[2] * self.patch_size[2] sr_height = sr_latent_height * self.vae_stride[1] sr_width = sr_latent_width * self.vae_stride[2] # Image condition (at SR resolution) if image_path is not None: sr_image = self._encode_image(load_image(image_path), sr_height, sr_width) else: sr_image = None # Trilinear interpolation of BR latent to SR resolution sr_latent_video = torch.nn.functional.interpolate( br_latent_video, size=(latent_length, sr_latent_height, sr_latent_width), mode="trilinear", align_corners=True, ) # Noise injection: sigma-weighted blend (noise_value indexes the ZeroSNR sigma schedule) if self.noise_value != 0: noise = torch.randn_like(sr_latent_video) sigma = self.zerosnr_sigmas.to(sr_latent_video.device)[self.noise_value] sr_latent_video = sr_latent_video * sigma + noise * (1 - sigma**2) ** 0.5 # Audio: blend with noise (noised version used during SR inference; final audio keeps BR result) sr_latent_audio = torch.randn_like(br_latent_audio) * self.sr_audio_noise_scale + br_latent_audio * ( 1 - self.sr_audio_noise_scale ) torch.accelerator.empty_cache() sr_steps = sr_num_steps or self.sr_num_inference_steps_default final_latent_video, _ = self._evaluate_with_latent( context, original_context_len, sr_image, sr_latent_video.clone(), sr_latent_audio.clone(), sr_steps, is_a2v, use_sr_model=True, ) # SR stage does not update audio; keep the BR result final_latent_video = final_latent_video final_latent_audio = br_latent_audio else: final_latent_video = br_latent_video final_latent_audio = br_latent_audio torch.accelerator.empty_cache() videos_np = self._decode_video(final_latent_video) torch.accelerator.empty_cache() audio_np = self._decode_audio(final_latent_audio) return DiffusionOutput(output=(videos_np, audio_np))