multimodalart's picture
multimodalart HF Staff
Bernini-Diffusers-v2 r2v demo
fed6c68 verified
Raw
History Blame Contribute Delete
49.2 kB
# Copyright (c) 2026 Bytedance Ltd. and/or its affiliate
# Copyright 2025 The Wan Team and The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Wan2.2 dual-expert diffusion sampler with APG / chained guidance."""
import json
import os
from typing import List, Optional
import torch
import torch.nn as nn
import torch.nn.functional as F
from diffusers.schedulers.scheduling_unipc_multistep import UniPCMultistepScheduler
from diffusers.utils.torch_utils import randn_tensor
from einops import rearrange
from tqdm import tqdm
from transformers.utils import logging
from .scheduler import FlowMatchScheduler
from .transformer_wan import WanTransformer3DModel
logger = logging.get_logger(__name__)
def _load_json_config(config_path: Optional[str]):
if config_path is None:
return None
if os.path.isfile(config_path):
with open(config_path, "r") as f:
return json.load(f)
return None
def _build_wan_transformer_from_config(config_dict, *, use_src_id_rotary_emb: bool):
config_dict = dict(config_dict or {})
config_dict["use_src_id_rotary_emb"] = use_src_id_rotary_emb
default_dtype = torch.get_default_dtype()
try:
torch.set_default_dtype(torch.bfloat16)
return WanTransformer3DModel.from_config(config_dict)
finally:
torch.set_default_dtype(default_dtype)
# --------------------------------------------------------------------------- #
# Adaptive Projected Guidance (https://arxiv.org/pdf/2410.02416)
# --------------------------------------------------------------------------- #
def apg_delta(
delta: torch.Tensor,
ref: torch.Tensor,
parallel_scale: float = 0.2,
orthogonal_scale: float = 1.0,
eps: float = 1e-8,
) -> torch.Tensor:
"""Apply the same APG delta projection used by veomni_editing Wan2.2."""
b = delta.shape[0]
delta_f = delta.reshape(b, -1)
ref_f = ref.reshape(b, -1)
ref_norm_sq = (ref_f * ref_f).sum(dim=1, keepdim=True).clamp_min(eps)
proj_coeff = (delta_f * ref_f).sum(dim=1, keepdim=True) / ref_norm_sq
delta_parallel_f = proj_coeff * ref_f
delta_orthogonal_f = delta_f - delta_parallel_f
return (
parallel_scale * delta_parallel_f.reshape_as(delta)
+ orthogonal_scale * delta_orthogonal_f.reshape_as(delta)
)
class MomentumBuffer:
def __init__(self, momentum: float):
self.momentum = momentum
self.running_average = 0
def update(self, update_value: torch.Tensor):
self.running_average = update_value + self.momentum * self.running_average
def _normalize_diff(diff, base_pred, momentum_buffer, eta, norm_threshold):
"""Project `diff` onto / off `base_pred` and recombine with weight `eta`."""
if momentum_buffer is not None:
momentum_buffer.update(diff)
diff = momentum_buffer.running_average
if norm_threshold > 0:
ones = torch.ones_like(diff)
diff_norm = diff.norm(p=2, dim=[-1, -2, -4], keepdim=True)
scale_factor = torch.minimum(ones, norm_threshold / diff_norm)
diff = diff * scale_factor
v0, v1 = diff.double(), base_pred.double()
v1 = F.normalize(v1, dim=[-1, -2, -4])
v0_parallel = (v0 * v1).sum(dim=[-1, -2, -4], keepdim=True) * v1
v0_orthogonal = v0 - v0_parallel
diff_parallel, diff_orthogonal = v0_parallel.to(diff.dtype), v0_orthogonal.to(diff.dtype)
return diff_orthogonal + eta * diff_parallel
def normalized_guidance(
pred_cond, pred_uncond, guidance_scale, momentum_buffer=None, eta=1.0, norm_threshold=0.0
):
"""Single-condition APG."""
nd = _normalize_diff(pred_cond - pred_uncond, pred_cond, momentum_buffer, eta, norm_threshold)
return pred_uncond + guidance_scale * nd
def normalized_guidance_chain(pred_uncond, preds, scales, momentum_buffers, eta, norm_thresholds):
"""Chained APG: each condition's diff is taken against the previous one."""
bases = [pred_uncond] + list(preds)
result = pred_uncond
for i, cond in enumerate(preds):
nd = _normalize_diff(cond - bases[i], cond, momentum_buffers[i], eta, norm_thresholds[i])
result = result + scales[i] * nd
return result
_PACK = "b (t h w) (pt ph pw c) -> b c (t pt) (h ph) (w pw)"
_UNPACK = "b c (t pt) (h ph) (w pw) -> b (t h w) (pt ph pw c)"
def _to_spatial(x, shape):
return rearrange(x, _PACK, t=shape[2], h=shape[3] // 2, w=shape[4] // 2, pt=1, ph=2, pw=2)
def _to_packed(x, shape):
return rearrange(x, _UNPACK, t=shape[2], h=shape[3] // 2, w=shape[4] // 2, pt=1, ph=2, pw=2)
class GEN_Wanx22(nn.Module):
"""Dual-expert (high-noise / low-noise) Wan2.2 transformer with guidance."""
def __init__(self, config):
super().__init__()
self.config = config
self.switch_dit_boundary = config.switch_dit_boundary
self.model_id_or_path = getattr(config, "wan22_base", None) or getattr(config, "base_dir", None)
self.transformer_config_path = getattr(config, "transformer_config_path", None)
self.transformer_2_config_path = getattr(config, "transformer_2_config_path", None)
# Load every submodule at the same dtype as the parent model so FSDP2 sees
# a uniform parameter dtype. With mixed precision the model is built in fp32
# (config.dtype) and FSDP casts to bf16 at compute time.
model_dtype = getattr(config, "dtype", None) or torch.bfloat16
common = dict(
use_src_id_rotary_emb=config.use_src_id_rotary_emb,
torch_dtype=model_dtype,
)
scratch = getattr(config, "scratch", False)
if config.skip_transformer_1:
self.transformer = None
else:
if getattr(config, "scratch", False):
transformer_cfg = _load_json_config(self.transformer_config_path)
self.transformer = _build_wan_transformer_from_config(
transformer_cfg,
use_src_id_rotary_emb=config.use_src_id_rotary_emb,
)
else:
self.transformer = WanTransformer3DModel.from_pretrained(
self.model_id_or_path, subfolder="transformer", **common
)
self.config.text_dim = self.transformer.config.text_dim
self.rope = self.transformer.rope
if config.skip_transformer_2:
self.transformer_2 = None
else:
if getattr(config, "scratch", False):
transformer_2_cfg = _load_json_config(self.transformer_2_config_path)
self.transformer_2 = _build_wan_transformer_from_config(
transformer_2_cfg,
use_src_id_rotary_emb=config.use_src_id_rotary_emb,
)
else:
self.transformer_2 = WanTransformer3DModel.from_pretrained(
self.model_id_or_path, subfolder="transformer_2", **common
)
self.config.text_dim = self.transformer_2.config.text_dim
self.rope = self.transformer_2.rope
self.use_unipc = config.use_unipc
if self.use_unipc:
self.scheduler = UniPCMultistepScheduler.from_pretrained(
self.model_id_or_path,
subfolder="scheduler",
flow_shift=config.shift,
)
else:
self.scheduler = FlowMatchScheduler(shift=config.shift, sigma_min=0.0, extra_one_step=False)
self.vae_scale_factor_temporal = 4
self.vae_scale_factor_spatial = 8
def shared_step(self, model_id, noisy_latents, timesteps, cond_embeds, rotary_embs,
batch_vae_seqlen=None, batch_text_seqlen=None, **kwargs):
cur_transformer = self.transformer if model_id == "transformer_1" else self.transformer_2
if cur_transformer is None:
cur_transformer = self.transformer
assert cur_transformer is not None
if batch_vae_seqlen is None:
batch_vae_seqlen = [noisy_latents.shape[1]]
if batch_text_seqlen is None:
batch_text_seqlen = [cond_embeds.shape[1]]
return cur_transformer(
noisy_latents,
timesteps,
encoder_hidden_states=cond_embeds,
rotary_emb=rotary_embs,
batch_image_vae_seqlen=batch_vae_seqlen,
text_features_length=batch_text_seqlen,
).sample
def forward(
self,
input_vae_latents,
input_vae_rope,
vae_latents_mask,
vae_seqlen,
text_embs,
batch_text_seqlen,
timesteps,
target_velocity,
):
# Training trains a single expert: which one is selected by the
# skip_transformer_1/skip_transformer_2 config (the skipped expert is
# None). Do not route by the per-batch mean timestep, which would
# mis-route packed samples spanning the noise boundary.
if self.transformer is not None and self.transformer_2 is not None:
raise ValueError(
"Dual-expert training expects exactly one expert; skip the other "
"via skip_transformer_1 or skip_transformer_2 in the model config."
)
if self.transformer_2 is None:
model_id = "transformer_1"
cur_transformer = self.transformer
else:
model_id = "transformer_2"
cur_transformer = self.transformer_2
input_vae_latents = input_vae_latents.unsqueeze(0)
input_vae_latents = cur_transformer.patch_embedding(input_vae_latents.squeeze(0)).flatten(1).unsqueeze(0)
input_vae_rope = input_vae_rope.permute(1, 0, 2).unsqueeze(0)
target_velocity = rearrange(target_velocity.unsqueeze(0), "b n c pt ph pw -> b n (pt ph pw c)")
target_indices = vae_latents_mask.squeeze(0).nonzero().squeeze(-1)
model_pred = self.shared_step(
model_id=model_id,
noisy_latents=input_vae_latents,
timesteps=timesteps.squeeze(0),
cond_embeds=text_embs,
rotary_embs=input_vae_rope,
batch_vae_seqlen=vae_seqlen.squeeze(0).tolist(),
batch_text_seqlen=batch_text_seqlen,
)[:, target_indices, :]
return (model_pred - target_velocity) ** 2
def _apg_sigma(self, t_idx: int):
"""Noise level at the current step, for converting v-pred to x-pred."""
if hasattr(self.scheduler, "step_index"):
idx = 0 if self.scheduler.step_index is None else self.scheduler.step_index
return self.scheduler.sigmas[idx]
return self.scheduler.sigmas[t_idx]
@torch.no_grad()
def sample(
self,
prompt_embeds=None,
prompt_embeds_t2=None,
uncond_prompt_embeds=None,
uncond_embeds_t2=None,
num_frames=1,
width=832,
height=480,
image_vae_latents=None,
multi_video_vae_latents=None,
multi_image_vae_latents=None,
num_inference_steps=50,
guidance_mode="rv2v",
omega_vid=3.0,
omega_img=3.0,
omega_txt=4.0,
omega_scale=0.75,
flow_shift=5.0,
seed=42,
device="cuda",
eta=1.0,
norm_threshold=(50.0, 50.0),
momentum=0.0,
):
"""Run guided sampling and return the predicted VAE latent `[B,C,T,H,W]`.
guidance_mode:
- ``rv2v`` : reference + video editing (chained, 4 forwards)
- ``v2v`` : video editing, plain CFG (2 forwards)
- ``v2v_chain`` : video editing, chained CFG (3 forwards)
- ``t2v`` : text-to-video, plain CFG (2 forwards)
- ``r2v_apg`` : reference-to-video, APG chained (3 forwards)
- ``v2v_apg`` : video editing, single-condition APG (2 forwards)
- ``t2v_apg`` : text-to-video, single-condition APG (2 forwards)
"""
if self.use_unipc:
self.scheduler.set_timesteps(num_inference_steps)
else:
self.scheduler.set_timesteps(num_inference_steps, shift=flow_shift)
num_frames = num_frames // self.vae_scale_factor_temporal * self.vae_scale_factor_temporal + 1
num_frames = max(num_frames, 1)
prompt_embeds_t1 = prompt_embeds
if prompt_embeds_t2 is None:
prompt_embeds_t2 = prompt_embeds
uncond_embeds_t1 = uncond_prompt_embeds
if uncond_embeds_t2 is None:
uncond_embeds_t2 = uncond_prompt_embeds
timesteps = self.scheduler.timesteps.to(device)
boundary_timestep = self.switch_dit_boundary * self.scheduler.num_train_timesteps
num_channels_latents = (
self.transformer.config.in_channels
if self.transformer is not None
else self.transformer_2.config.in_channels
)
num_latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1
shape = (
1,
num_channels_latents,
num_latent_frames,
int(height) // self.vae_scale_factor_spatial,
int(width) // self.vae_scale_factor_spatial,
)
gen = torch.Generator(device="cpu").manual_seed(seed)
noise = randn_tensor(shape, device=device, dtype=torch.float32, generator=gen)
noisy_vae_latent = rearrange(noise, "b c t (h ph) (w pw) -> b (t h w) (ph pw c)", ph=2, pw=2)
noisy_vae_latent = noisy_vae_latent.to(device)
self.transformer.to(device)
if self.transformer_2 is not None:
self.transformer_2.to("cpu")
torch.cuda.empty_cache()
switched = False
# APG momentum buffers / per-condition norm thresholds.
if guidance_mode == "r2v_apg":
if isinstance(norm_threshold, (int, float)):
norm_threshold = [norm_threshold, norm_threshold]
elif len(norm_threshold) == 1:
norm_threshold = [norm_threshold[0], norm_threshold[0]]
momentum_buffer1 = MomentumBuffer(momentum)
momentum_buffer2 = MomentumBuffer(momentum)
elif guidance_mode in ("v2v_apg", "t2v_apg"):
momentum_buffer = MomentumBuffer(momentum)
nt0 = norm_threshold[0] if isinstance(norm_threshold, (list, tuple)) else norm_threshold
progress_bar = tqdm(timesteps)
for t_idx, t in enumerate(timesteps):
model_id = "transformer_1" if t >= boundary_timestep else "transformer_2"
cond_text = prompt_embeds_t1 if t >= boundary_timestep else prompt_embeds_t2
uncond_text = uncond_embeds_t1 if t >= boundary_timestep else uncond_embeds_t2
if t < boundary_timestep and not switched and self.transformer_2 is not None:
self.transformer.to("cpu")
torch.cuda.empty_cache()
self.transformer_2.to(device)
switched = True
omega_vid *= omega_scale
omega_img *= omega_scale
omega_txt *= omega_scale
cur_transformer = self.transformer_2 if switched else self.transformer
# ----------------------------------------------------------------
# Build conditioning combos. Each combo = condition tokens + the
# shared noisy target latent (source_id 0).
# V : video only I : reference image(s) only
# VI : video + image(s) none : no conditioning
# ----------------------------------------------------------------
v_latents, v_rotary, v_masks, v_len = [], [], [], 0
i_latents, i_rotary, i_masks, i_len = [], [], [], 0
vi_latents, vi_rotary, vi_masks, vi_len = [], [], [], 0
target_video_latents = []
if multi_video_vae_latents is not None:
if isinstance(multi_video_vae_latents, torch.Tensor):
target_video_latents = [multi_video_vae_latents]
else:
target_video_latents = multi_video_vae_latents
# ----------------------------------------------------------------
# Assign a source_id to every conditioning source. Ids start at 1
# (the noisy target keeps 0). When a combo has more sources than the
# model saw in training (`max_trained_src_id`), evenly spread the ids
# across the trained range [1, max_trained_src_id] so the rotary
# phases stay inside the trained manifold instead of extrapolating.
# ----------------------------------------------------------------
num_videos = len(target_video_latents)
num_images = 0
if image_vae_latents is not None:
num_images += image_vae_latents.shape[2]
if multi_image_vae_latents is not None:
num_images += len(multi_image_vae_latents)
interp = getattr(self.config, "interpolate_src_id", True)
max_trained = getattr(self.config, "max_trained_src_id", 5)
def _make_sids(n):
if n <= 0:
return []
if interp and n > max_trained:
return torch.linspace(1.0, float(max_trained), n).tolist()
return [float(i) for i in range(1, n + 1)]
# VI combo holds videos then images on a shared id axis; the
# image-only combo holds just the images on its own axis.
vi_sids = _make_sids(num_videos + num_images)
i_sids = _make_sids(num_images)
vi_ptr = 0 # cursor into vi_sids
i_ptr = 0 # cursor into i_sids
for idx, video_latent in enumerate(target_video_latents):
cur_latent, rotary_emb = cur_transformer.patch_vae_latent(
video_latent.to(dtype=cur_transformer.dtype), source_id=vi_sids[vi_ptr]
)
vi_ptr += 1
mask = torch.zeros(cur_latent.shape[1], device=device, dtype=torch.bool)
if idx == 0: # only the first video joins the V combo
v_latents.append(cur_latent)
v_rotary.append(rotary_emb)
v_masks.append(mask)
v_len += cur_latent.shape[1]
vi_latents.append(cur_latent)
vi_rotary.append(rotary_emb)
vi_masks.append(mask)
vi_len += cur_latent.shape[1]
def _add_image(img_vae):
nonlocal vi_ptr, i_ptr, vi_len, i_len
cur_latent, rotary_emb = cur_transformer.patch_vae_latent(
img_vae.to(dtype=cur_transformer.dtype), source_id=vi_sids[vi_ptr]
)
vi_ptr += 1
vi_latents.append(cur_latent)
vi_rotary.append(rotary_emb)
vi_masks.append(torch.zeros(cur_latent.shape[1], device=device, dtype=torch.bool))
vi_len += cur_latent.shape[1]
cur_latent_i, rotary_emb_i = cur_transformer.patch_vae_latent(
img_vae.to(dtype=cur_transformer.dtype), source_id=i_sids[i_ptr]
)
i_ptr += 1
i_latents.append(cur_latent_i)
i_rotary.append(rotary_emb_i)
i_masks.append(torch.zeros(cur_latent_i.shape[1], device=device, dtype=torch.bool))
i_len += cur_latent_i.shape[1]
if image_vae_latents is not None:
for idx in range(image_vae_latents.shape[2]):
_add_image(image_vae_latents[:, :, idx : idx + 1, :, :])
if multi_image_vae_latents is not None:
for img_vae in multi_image_vae_latents:
_add_image(img_vae)
# Noisy target latent, shared across all combos (source_id 0).
unpacked_noisy_latent = _to_spatial(noisy_vae_latent, shape).to(cur_transformer.dtype)
noisy_latent, noisy_rotary = cur_transformer.patch_vae_latent(unpacked_noisy_latent, source_id=0)
noisy_len = noisy_latent.shape[1]
noisy_mask = torch.ones(noisy_len, device=device, dtype=torch.bool)
def _assemble(cond_lats, cond_rots, cond_msks, cond_len):
return (
torch.cat(cond_lats + [noisy_latent], dim=1).to(cur_transformer.dtype),
torch.cat(cond_rots + [noisy_rotary], dim=2),
torch.cat(cond_msks + [noisy_mask], dim=0),
cond_len + noisy_len,
)
none_inp, none_rot, none_msk, none_total = _assemble([], [], [], 0)
v_inp, v_rot, v_msk, v_total = _assemble(v_latents, v_rotary, v_masks, v_len)
i_inp, i_rot, i_msk, i_total = _assemble(i_latents, i_rotary, i_masks, i_len)
vi_inp, vi_rot, vi_msk, vi_total = _assemble(vi_latents, vi_rotary, vi_masks, vi_len)
timestep = t.expand(1)
def _fwd(lat_inp, rot, msk, total, text_emb):
pred = self.shared_step(
model_id=model_id,
noisy_latents=lat_inp,
timesteps=timestep,
cond_embeds=text_emb,
rotary_embs=rot,
batch_vae_seqlen=[total],
batch_text_seqlen=[text_emb.shape[1]],
)
return pred[:, msk, :]
# ----------------------------------------------------------------
# Guidance.
# ----------------------------------------------------------------
if guidance_mode == "rv2v":
# ε̂ = ε_∅ + ω_V(ε_V-ε_∅) + ω_I(ε_VI-ε_V) + ω_TI(ε_VTI-ε_VI)
eps_uncond = _fwd(none_inp, none_rot, none_msk, none_total, uncond_text)
eps_V = _fwd(v_inp, v_rot, v_msk, v_total, uncond_text)
eps_VI = _fwd(vi_inp, vi_rot, vi_msk, vi_total, uncond_text)
eps_VTI = _fwd(vi_inp, vi_rot, vi_msk, vi_total, cond_text)
noise_pred = (
eps_uncond
+ omega_vid * (eps_V - eps_uncond)
+ omega_img * (eps_VI - eps_V)
+ omega_txt * (eps_VTI - eps_VI)
)
elif guidance_mode == "v2v":
# Video editing, plain CFG over text with the V+I condition
# fixed: ε̂ = ε_VI + ω_TI(ε_VTI - ε_VI)
eps_uncond = _fwd(vi_inp, vi_rot, vi_msk, vi_total, uncond_text)
eps_VTI = _fwd(vi_inp, vi_rot, vi_msk, vi_total, cond_text)
noise_pred = eps_uncond + omega_txt * (eps_VTI - eps_uncond)
elif guidance_mode == "v2v_chain":
# Video editing, chained CFG: ε̂ = ε_∅ + ω_V(ε_V-ε_∅) + ω_TI(ε_VTI-ε_V)
eps_uncond = _fwd(none_inp, none_rot, none_msk, none_total, uncond_text)
eps_V = _fwd(v_inp, v_rot, v_msk, v_total, uncond_text)
eps_VTI = _fwd(vi_inp, vi_rot, vi_msk, vi_total, cond_text)
noise_pred = (
eps_uncond
+ omega_vid * (eps_V - eps_uncond)
+ omega_txt * (eps_VTI - eps_V)
)
elif guidance_mode == "t2v":
# Text-to-video, plain CFG: ε̂ = ε_∅ + ω_TI(ε_T-ε_∅)
eps_uncond = _fwd(none_inp, none_rot, none_msk, none_total, uncond_text)
eps_T = _fwd(none_inp, none_rot, none_msk, none_total, cond_text)
noise_pred = eps_uncond + omega_txt * (eps_T - eps_uncond)
elif guidance_mode == "r2v_apg":
# Reference-to-video: no source video. Chained APG over ∅ / I / TI.
eps_uncond = _fwd(none_inp, none_rot, none_msk, none_total, uncond_text)
eps_I = _fwd(i_inp, i_rot, i_msk, i_total, uncond_text)
eps_TI = _fwd(i_inp, i_rot, i_msk, i_total, cond_text)
sigma_apg = self._apg_sigma(t_idx)
noisy_r = _to_spatial(noisy_vae_latent, shape)
eps_uncond_r = noisy_r - sigma_apg * _to_spatial(eps_uncond, shape)
eps_I_r = noisy_r - sigma_apg * _to_spatial(eps_I, shape)
eps_TI_r = noisy_r - sigma_apg * _to_spatial(eps_TI, shape)
x_guided = normalized_guidance_chain(
pred_uncond=eps_uncond_r,
preds=[eps_I_r, eps_TI_r],
scales=[omega_img, omega_txt],
momentum_buffers=[momentum_buffer1, momentum_buffer2],
eta=eta,
norm_thresholds=norm_threshold,
)
noise_pred = _to_packed((noisy_r - x_guided) / sigma_apg, shape)
elif guidance_mode == "v2v_apg":
# Video editing: single-condition APG between ∅ and VTI.
eps_uncond = _fwd(vi_inp, vi_rot, vi_msk, vi_total, uncond_text)
eps_VTI = _fwd(vi_inp, vi_rot, vi_msk, vi_total, cond_text)
sigma_apg = self._apg_sigma(t_idx)
noisy_r = _to_spatial(noisy_vae_latent, shape)
eps_uncond_r = noisy_r - sigma_apg * _to_spatial(eps_uncond, shape)
eps_VTI_r = noisy_r - sigma_apg * _to_spatial(eps_VTI, shape)
x_guided = normalized_guidance(
pred_cond=eps_VTI_r,
pred_uncond=eps_uncond_r,
guidance_scale=omega_txt,
momentum_buffer=momentum_buffer,
eta=eta,
norm_threshold=nt0,
)
noise_pred = _to_packed((noisy_r - x_guided) / sigma_apg, shape)
elif guidance_mode == "t2v_apg":
# Text-to-video: single-condition APG between ∅ and T.
eps_uncond = _fwd(none_inp, none_rot, none_msk, none_total, uncond_text)
eps_T = _fwd(none_inp, none_rot, none_msk, none_total, cond_text)
sigma_apg = self._apg_sigma(t_idx)
noisy_r = _to_spatial(noisy_vae_latent, shape)
eps_uncond_r = noisy_r - sigma_apg * _to_spatial(eps_uncond, shape)
eps_T_r = noisy_r - sigma_apg * _to_spatial(eps_T, shape)
x_guided = normalized_guidance(
pred_cond=eps_T_r,
pred_uncond=eps_uncond_r,
guidance_scale=omega_txt,
momentum_buffer=momentum_buffer,
eta=eta,
norm_threshold=nt0,
)
noise_pred = _to_packed((noisy_r - x_guided) / sigma_apg, shape)
else:
raise ValueError(
f"Unknown guidance_mode='{guidance_mode}'. Expected one of: "
f"rv2v, v2v, v2v_chain, t2v, r2v_apg, v2v_apg, t2v_apg."
)
if isinstance(self.scheduler, FlowMatchScheduler):
noisy_vae_latent = self.scheduler.step(noise_pred, t, noisy_vae_latent, return_dict=False)
else:
noisy_vae_latent = self.scheduler.step(noise_pred, t, noisy_vae_latent, return_dict=False)[0]
progress_bar.update(1)
return _to_spatial(noisy_vae_latent, shape)
@torch.no_grad()
def sample_bernini_wvitcfg(
self,
prompt_embeds_wtxt_wvit=None,
prompt_embeds_wtxt_wovit=None,
prompt_embeds_wotxt_wvit=None,
prompt_embeds_wotxt_wovit=None,
num_frames=1,
width=832,
height=480,
source_image_vae_latents=None,
source_image_vae_rope=None,
source_video_vae_latents=None,
source_video_vae_rope=None,
# Infer settings
guidance_mode="default",
num_inference_steps=50,
omega_txt=1.0,
omega_img=1.0,
omega_vid=1.0,
omega_tgt=1.0,
omega_scale=1.0,
flow_shift=5.0,
seed=42,
device='cuda',
**kwargs,
):
# only support batchsize=1
weight_dtype = torch.bfloat16
if self.use_unipc:
self.scheduler = UniPCMultistepScheduler.from_config(self.config.scheduler_config_path, flow_shift=flow_shift)
self.scheduler.set_timesteps(num_inference_steps)
else:
self.scheduler.set_timesteps(num_inference_steps, training=False, shift=flow_shift)
num_frames = num_frames // self.vae_scale_factor_temporal * self.vae_scale_factor_temporal + 1
num_frames = max(num_frames, 1)
timesteps = self.scheduler.timesteps.to(device)
boundary_timestep = self.switch_dit_boundary * self.scheduler.num_train_timesteps
num_channels_latents = (
self.transformer.config.in_channels
if self.transformer is not None
else self.transformer_2.config.in_channels
)
num_latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1
shape = (1,
num_channels_latents,
num_latent_frames,
int(height) // self.vae_scale_factor_spatial,
int(width) // self.vae_scale_factor_spatial)
gen = torch.Generator(device='cpu').manual_seed(seed)
noise = randn_tensor(shape, device=device, dtype=torch.float32, generator=gen)
noisy_vae_latent = rearrange(noise, 'b c t (h ph) (w pw) -> b (t h w) (ph pw c)', ph=2, pw=2)
noisy_vae_latent = noisy_vae_latent.to(device) #.to(weight_dtype)
def _module_device(module):
if module is None:
return None
try:
return next(module.parameters()).device
except StopIteration:
return None
local_device_moves = _module_device(self.transformer) == torch.device("cpu")
if local_device_moves:
if self.transformer_2 is not None:
self.transformer_2.to('cpu')
self.transformer.to(device)
torch.cuda.empty_cache()
switched = False
cur_omega_txt = omega_txt
cur_omega_tgt = omega_tgt
cur_omega_img = omega_img
cur_omega_vid = omega_vid
logger.info(f"{guidance_mode=} {cur_omega_txt=} {cur_omega_tgt=} {cur_omega_img=}")
progress_bar = tqdm(timesteps)
for t_idx, t in enumerate(timesteps):
model_id = "transformer_1" if t >= boundary_timestep else "transformer_2"
if t < boundary_timestep and not switched and self.transformer_2 is not None:
if local_device_moves:
self.transformer.to('cpu')
self.transformer_2.to(device)
torch.cuda.empty_cache()
switched = True
cur_omega_txt = omega_txt * omega_scale
cur_omega_tgt = omega_tgt * omega_scale
cur_omega_img = omega_img * omega_scale
cur_omega_vid = omega_vid * omega_scale
logger.info(
f"After CFG SCALE: {omega_scale} {cur_omega_txt=} {cur_omega_tgt=} {cur_omega_img=} {cur_omega_vid=}"
)
cur_transformer = self.transformer_2 if switched else self.transformer
target_vae_latent_masks = []
target_img_vae_latent_masks, target_vid_vae_latent_masks = [], []
latent_model_inputs_wimgvae, latent_model_inputs_wvidvae = [], []
latent_model_inputs_wvae, latent_model_inputs_wovae = [], []
rotary_embeds_wimgvae, rotary_embeds_wvidvae = [], []
rotary_embeds_wvae, rotary_embeds_wovae = [], []
if source_image_vae_latents is not None and len(source_image_vae_latents) > 0:
cur_latent = cur_transformer.patch_vae_embedding(source_image_vae_latents.to(dtype=weight_dtype)).unsqueeze(0)
rotary_emb = source_image_vae_rope.permute(1, 0, 2).unsqueeze(0)
rotary_embeds_wvae.append(rotary_emb)
latent_model_inputs_wvae.append(cur_latent)
rotary_embeds_wimgvae.append(rotary_emb)
latent_model_inputs_wimgvae.append(cur_latent)
vae_latent_mask = torch.zeros(cur_latent.shape[1], device=device, dtype=torch.bool)
target_vae_latent_masks.append(vae_latent_mask)
target_img_vae_latent_masks.append(vae_latent_mask)
if source_video_vae_latents is not None and len(source_video_vae_latents) > 0:
cur_latent = cur_transformer.patch_vae_embedding(source_video_vae_latents.to(dtype=weight_dtype)).unsqueeze(0)
rotary_emb = source_video_vae_rope.permute(1, 0, 2).unsqueeze(0)
rotary_embeds_wvae.append(rotary_emb)
latent_model_inputs_wvae.append(cur_latent)
rotary_embeds_wvidvae.append(rotary_emb)
latent_model_inputs_wvidvae.append(cur_latent)
vae_latent_mask = torch.zeros(cur_latent.shape[1], device=device, dtype=torch.bool)
target_vae_latent_masks.append(vae_latent_mask)
target_vid_vae_latent_masks.append(vae_latent_mask)
unpacked_noisy_latent = rearrange(
noisy_vae_latent,
'b (t h w) (pt ph pw c) -> b c (t pt) (h ph) (w pw)',
t=shape[2],
h=shape[3]//2,
w=shape[4]//2,
pt=1,
ph=2,
pw=2,
).to(dtype=weight_dtype)
noisy_latent, rotary_emb = cur_transformer.patch_vae_latent(unpacked_noisy_latent, source_id=0)
rotary_embeds_wvae.append(rotary_emb)
rotary_embeds_wimgvae.append(rotary_emb)
rotary_embeds_wvidvae.append(rotary_emb)
rotary_embeds_wovae.append(rotary_emb)
latent_model_inputs_wvae.append(noisy_latent)
latent_model_inputs_wimgvae.append(noisy_latent)
latent_model_inputs_wvidvae.append(noisy_latent)
latent_model_inputs_wovae.append(noisy_latent)
vae_latent_mask = torch.ones(noisy_vae_latent.shape[1], device=device, dtype=torch.bool)
target_vae_latent_masks.append(vae_latent_mask)
target_img_vae_latent_masks.append(vae_latent_mask)
target_vid_vae_latent_masks.append(vae_latent_mask)
rotary_embeds_wvae = torch.cat(rotary_embeds_wvae, dim=2)
rotary_embeds_wimgvae = torch.cat(rotary_embeds_wimgvae, dim=2)
rotary_embeds_wvidvae = torch.cat(rotary_embeds_wvidvae, dim=2)
rotary_embeds_wovae = torch.cat(rotary_embeds_wovae, dim=2)
latent_model_inputs_wvae = torch.cat(latent_model_inputs_wvae, dim=1).to(weight_dtype)
latent_model_inputs_wimgvae = torch.cat(latent_model_inputs_wimgvae, dim=1).to(weight_dtype)
latent_model_inputs_wvidvae = torch.cat(latent_model_inputs_wvidvae, dim=1).to(weight_dtype)
latent_model_inputs_wovae = torch.cat(latent_model_inputs_wovae, dim=1).to(weight_dtype)
target_vae_latent_masks = torch.cat(target_vae_latent_masks, dim=0)
target_img_vae_latent_masks = torch.cat(target_img_vae_latent_masks, dim=0)
target_vid_vae_latent_masks = torch.cat(target_vid_vae_latent_masks, dim=0)
timestep = t.expand(latent_model_inputs_wovae.shape[0])
# (Clip_cond, null, null)
shared_kwargs = dict(
model_id=model_id,
timesteps=timestep,
self_attn_mask=None,
cross_attn_mask=None,
need_patch_hidden_states=False,
)
noise_pred = self.sample_one_step(
shared_kwargs=shared_kwargs,
guidance_mode=guidance_mode,
rotary_embeds_wvae=rotary_embeds_wvae,
rotary_embeds_wovae=rotary_embeds_wovae,
rotary_embeds_wimgvae=rotary_embeds_wimgvae,
rotary_embeds_wvidvae=rotary_embeds_wvidvae,
latent_model_inputs_wvae=latent_model_inputs_wvae,
latent_model_inputs_wimgvae=latent_model_inputs_wimgvae,
latent_model_inputs_wvidvae=latent_model_inputs_wvidvae,
latent_model_inputs_wovae=latent_model_inputs_wovae,
prompt_embeds_wtxt_wvit=prompt_embeds_wtxt_wvit,
prompt_embeds_wtxt_wovit=prompt_embeds_wtxt_wovit,
prompt_embeds_wotxt_wvit=prompt_embeds_wotxt_wvit,
prompt_embeds_wotxt_wovit=prompt_embeds_wotxt_wovit,
cur_omega_txt=cur_omega_txt,
cur_omega_tgt=cur_omega_tgt,
cur_omega_img=cur_omega_img,
cur_omega_vid=cur_omega_vid,
target_vae_latent_masks=target_vae_latent_masks,
target_imgvae_latent_masks=target_img_vae_latent_masks,
target_vidvae_latent_masks=target_vid_vae_latent_masks,
noisy_vae_latent=noisy_vae_latent,
shape=shape
)
if isinstance(self.scheduler, FlowMatchScheduler):
noisy_vae_latent = self.scheduler.step(noise_pred, t, noisy_vae_latent, return_dict=False)
else:
noisy_vae_latent = self.scheduler.step(noise_pred, t, noisy_vae_latent, return_dict=False)[0]
progress_bar.update(1)
if local_device_moves:
self.transformer.to('cpu')
if self.transformer_2 is not None:
self.transformer_2.to('cpu')
torch.cuda.empty_cache()
pred_vae_latent = rearrange(
noisy_vae_latent,
'b (t h w) (pt ph pw c) -> b c (t pt) (h ph) (w pw)',
t=shape[2],
h=shape[3]//2,
w=shape[4]//2,
pt=1,
ph=2,
pw=2,
)
return pred_vae_latent
def sample_one_step(
self,
shared_kwargs,
guidance_mode,
rotary_embeds_wvae,
rotary_embeds_wimgvae,
rotary_embeds_wvidvae,
rotary_embeds_wovae,
latent_model_inputs_wimgvae,
latent_model_inputs_wvidvae,
latent_model_inputs_wvae,
latent_model_inputs_wovae,
prompt_embeds_wtxt_wvit,
prompt_embeds_wtxt_wovit,
prompt_embeds_wotxt_wvit,
prompt_embeds_wotxt_wovit,
cur_omega_txt,
cur_omega_tgt,
cur_omega_img,
cur_omega_vid,
target_vae_latent_masks,
target_imgvae_latent_masks,
target_vidvae_latent_masks,
shape,
noisy_vae_latent,
norm_threshold=[50., 50., 50.],
):
def _seq_lens_kwargs(latent_inputs: torch.Tensor, cond_embeds: torch.Tensor):
return dict(
batch_vae_seqlen=torch.tensor(
[latent_inputs.shape[1]], dtype=torch.int32, device=latent_inputs.device
),
batch_text_seqlen=torch.tensor(
[cond_embeds.shape[1]], dtype=torch.int32, device=cond_embeds.device
),
)
# shared conditional results
cond_pred_wtxt_wvit_wvae = self.shared_step(
noisy_latents=latent_model_inputs_wvae,
cond_embeds=prompt_embeds_wtxt_wvit,
rotary_embs=rotary_embeds_wvae,
**_seq_lens_kwargs(latent_model_inputs_wvae, prompt_embeds_wtxt_wvit),
**shared_kwargs
)[:, target_vae_latent_masks, :]
# shared unconditional baseline
cond_pred_wotxt_wovit_wovae = self.shared_step(
noisy_latents=latent_model_inputs_wovae,
rotary_embs=rotary_embeds_wovae,
cond_embeds=prompt_embeds_wotxt_wovit,
**_seq_lens_kwargs(latent_model_inputs_wovae, prompt_embeds_wotxt_wovit),
**shared_kwargs
)
if guidance_mode in ["rv2v_wapg"]:
if cur_omega_vid > 0.0:
eps_V = self.shared_step(
noisy_latents=latent_model_inputs_wvidvae,
rotary_embs=rotary_embeds_wvidvae,
cond_embeds=prompt_embeds_wotxt_wovit,
**_seq_lens_kwargs(latent_model_inputs_wvidvae, prompt_embeds_wotxt_wovit),
**shared_kwargs
)[:, target_vidvae_latent_masks, :]
else:
eps_V = cond_pred_wotxt_wovit_wovae
if cur_omega_img > 0.0:
eps_VI = self.shared_step(
noisy_latents=latent_model_inputs_wvae,
rotary_embs=rotary_embeds_wvae,
cond_embeds=prompt_embeds_wotxt_wovit,
**_seq_lens_kwargs(latent_model_inputs_wvae, prompt_embeds_wotxt_wovit),
**shared_kwargs
)[:, target_vae_latent_masks, :]
else:
eps_VI = eps_V
if cur_omega_txt > 0.0:
eps_VTI = self.shared_step(
noisy_latents=latent_model_inputs_wvae,
rotary_embs=rotary_embeds_wvae,
cond_embeds=prompt_embeds_wtxt_wovit,
**_seq_lens_kwargs(latent_model_inputs_wvae, prompt_embeds_wtxt_wovit),
**shared_kwargs
)[:, target_vae_latent_masks, :]
else:
eps_VTI = eps_VI
if cur_omega_tgt > 0.0:
eps_VTIC = self.shared_step(
noisy_latents=latent_model_inputs_wvae,
rotary_embs=rotary_embeds_wvae,
cond_embeds=prompt_embeds_wtxt_wvit,
**_seq_lens_kwargs(latent_model_inputs_wvae, prompt_embeds_wtxt_wvit),
**shared_kwargs
)[:, target_vae_latent_masks, :]
else:
eps_VTIC = eps_VTI
if guidance_mode == "r2v_wapg":
base = cond_pred_wotxt_wovit_wovae
delta_vid_vae_apg = apg_delta(eps_V - base, ref=base)
delta_img_vae_apg = apg_delta(eps_VI - eps_V, ref=eps_V)
delta_txt_apg = apg_delta(eps_VTI - eps_VI, ref=eps_VI)
delta_vit_apg = apg_delta(eps_VTIC - eps_VTI, ref=eps_VTI)
else:
base = cond_pred_wotxt_wovit_wovae
delta_vid_vae_apg = eps_V - base
delta_img_vae_apg = eps_VI - eps_V
delta_txt_apg = eps_VTI - eps_VI
delta_vit_apg = eps_VTIC - eps_VTI
noise_pred = (
base
+ cur_omega_vid * delta_vid_vae_apg
+ cur_omega_img * delta_img_vae_apg
+ cur_omega_txt * delta_txt_apg
+ cur_omega_tgt * delta_vit_apg
)
return noise_pred
elif guidance_mode == "v2v_apg":
momentum_buffer = MomentumBuffer(momentum=0.0)
if hasattr(self.scheduler, "step_index") and self.scheduler.step_index is None:
sigma_apg = self.scheduler.sigmas[0]
else:
sigma_apg = self.scheduler.sigmas[self.scheduler.step_index]
# Get v_preds
eps_uncond = cond_pred_wotxt_wovit_wovae # ε_∅
eps_T = cond_pred_wtxt_wvit_wvae # ε_T
def rearrange_eps(pred, pred_shape):
return rearrange(
pred,
'b (t h w) (pt ph pw c) -> b c (t pt) (h ph) (w pw)',
t=pred_shape[2], h=pred_shape[3]//2, w=pred_shape[4]//2,
pt=1, ph=2, pw=2,
)
# Compute x_preds: x = noisy_vae_latent - sigma * v
# Rearrange to spatial layout for guidance calculation
noisy_latents_r = rearrange_eps(noisy_vae_latent, shape)
eps_uncond_r = noisy_latents_r - sigma_apg * rearrange_eps(eps_uncond, shape)
eps_T_r = noisy_latents_r - sigma_apg * rearrange_eps(eps_T, shape)
noise_pred = normalized_guidance(
pred_uncond=eps_uncond_r,
pred_cond=eps_T_r,
guidance_scale=cur_omega_txt,
momentum_buffer=momentum_buffer,
eta=1.0,
norm_threshold=norm_threshold[0] if isinstance(norm_threshold, list) else norm_threshold,
)
noise_pred = (noisy_latents_r - noise_pred) / sigma_apg
# Rearrange back
noise_pred = rearrange(
noise_pred,
'b c (t pt) (h ph) (w pw) -> b (t h w) (pt ph pw c)',
t=shape[2], h=shape[3]//2, w=shape[4]//2,
pt=1, ph=2, pw=2
)
elif guidance_mode == "vae_txt_vit":
if cur_omega_img > 0.0:
cond_pred_wotxt_wovit_wvae = self.shared_step(
noisy_latents=latent_model_inputs_wvae,
rotary_embs=rotary_embeds_wvae,
cond_embeds=prompt_embeds_wotxt_wovit,
**_seq_lens_kwargs(latent_model_inputs_wvae, prompt_embeds_wotxt_wovit),
**shared_kwargs
)[:, target_vae_latent_masks, :]
else:
cond_pred_wotxt_wovit_wvae = cond_pred_wotxt_wovit_wovae
if cur_omega_txt > 0.0:
cond_pred_wtxt_wovit_wvae = self.shared_step(
noisy_latents=latent_model_inputs_wvae,
rotary_embs=rotary_embeds_wvae,
cond_embeds=prompt_embeds_wtxt_wovit,
**_seq_lens_kwargs(latent_model_inputs_wvae, prompt_embeds_wtxt_wovit),
**shared_kwargs
)[:, target_vae_latent_masks, :]
else:
cond_pred_wtxt_wovit_wvae = cond_pred_wotxt_wovit_wvae
noise_pred = (
cond_pred_wotxt_wovit_wovae
+ cur_omega_img * (cond_pred_wotxt_wovit_wvae - cond_pred_wotxt_wovit_wovae)
+ cur_omega_txt * (cond_pred_wtxt_wovit_wvae - cond_pred_wotxt_wovit_wvae)
+ cur_omega_tgt * (cond_pred_wtxt_wvit_wvae - cond_pred_wtxt_wovit_wvae)
)
elif guidance_mode == "vae_txt_vit_wapg":
if cur_omega_img > 0.0:
cond_pred_wotxt_wovit_wvae = self.shared_step(
noisy_latents=latent_model_inputs_wvae,
rotary_embs=rotary_embeds_wvae,
cond_embeds=prompt_embeds_wotxt_wovit,
**_seq_lens_kwargs(latent_model_inputs_wvae, prompt_embeds_wotxt_wovit),
**shared_kwargs
)[:, target_vae_latent_masks, :]
else:
cond_pred_wotxt_wovit_wvae = cond_pred_wotxt_wovit_wovae
if cur_omega_txt > 0.0:
cond_pred_wtxt_wovit_wvae = self.shared_step(
noisy_latents=latent_model_inputs_wvae,
rotary_embs=rotary_embeds_wvae,
cond_embeds=prompt_embeds_wtxt_wovit,
**_seq_lens_kwargs(latent_model_inputs_wvae, prompt_embeds_wtxt_wovit),
**shared_kwargs
)[:, target_vae_latent_masks, :]
else:
cond_pred_wtxt_wovit_wvae = cond_pred_wotxt_wovit_wvae
base = cond_pred_wotxt_wovit_wovae
delta_img = cond_pred_wotxt_wovit_wvae - cond_pred_wotxt_wovit_wovae
delta_txt = cond_pred_wtxt_wovit_wvae - cond_pred_wotxt_wovit_wvae
delta_vit = cond_pred_wtxt_wvit_wvae - cond_pred_wtxt_wovit_wvae
delta_img_apg = apg_delta(
delta_img,
ref=cond_pred_wotxt_wovit_wvae,
parallel_scale=0.2,
orthogonal_scale=1.0,
)
delta_txt_apg = apg_delta(
delta_txt,
ref=cond_pred_wtxt_wovit_wvae,
parallel_scale=0.2,
orthogonal_scale=1.0,
)
delta_vit_apg = apg_delta(
delta_vit,
ref=cond_pred_wtxt_wvit_wvae,
parallel_scale=0.2,
orthogonal_scale=1.0,
)
noise_pred = (
base
+ cur_omega_img * delta_img_apg
+ cur_omega_txt * delta_txt_apg
+ cur_omega_tgt * delta_vit_apg
)
else:
raise ValueError(f"Unknown guidance mode: {guidance_mode}")
return noise_pred