[diffusion] chore: clean MOVA codes (#18107)

This commit is contained in:
Zhang Yiyang (SII)
2026-02-04 21:23:41 +08:00
committed by GitHub
parent 760ae933bb
commit 0c9a0adc53
3 changed files with 76 additions and 166 deletions

View File

@@ -122,8 +122,6 @@ class WanAudioModel(CachableDiT, OffloadableDiTMixin):
num_layers = config.num_layers
has_image_pos_emb = config.has_image_pos_emb
has_ref_conv = config.has_ref_conv
add_control_adapter = config.add_control_adapter
in_dim_control_adapter = config.in_dim_control_adapter
seperated_timestep = config.seperated_timestep
require_vae_embedding = config.require_vae_embedding
require_clip_embedding = config.require_clip_embedding
@@ -180,17 +178,6 @@ class WanAudioModel(CachableDiT, OffloadableDiTMixin):
self.accumulated_rel_l1_distance_even = 0
self.accumulated_rel_l1_distance_odd = 0
self.__post_init__()
if add_control_adapter:
from .wan_video_camera_controller import SimpleAdapter
self.control_adapter = SimpleAdapter(
in_dim_control_adapter,
dim,
kernel_size=patch_size[1:],
stride=patch_size[1:],
)
else:
self.control_adapter = None
def _init_freqs(self):
if self.freqs is not None:
@@ -207,19 +194,6 @@ class WanAudioModel(CachableDiT, OffloadableDiTMixin):
control_camera_latents_input: Optional[torch.Tensor] = None,
):
x = self.patch_embedding(x)
if (
self.control_adapter is not None
and control_camera_latents_input is not None
):
y_camera = self.control_adapter(control_camera_latents_input)
if isinstance(x, list):
x = [u + v for u, v in zip(x, y_camera)]
x = x[0].unsqueeze(0)
else:
if isinstance(y_camera, list):
x = x + y_camera[0]
else:
x = x + y_camera
grid_size = x.shape[2:]
x = rearrange(x, "b c f -> b f c").contiguous()
return x, grid_size # x, grid_size: (f)
@@ -234,11 +208,6 @@ class WanAudioModel(CachableDiT, OffloadableDiTMixin):
hidden_states: torch.Tensor,
encoder_hidden_states: torch.Tensor | list[torch.Tensor],
timestep: torch.LongTensor,
encoder_hidden_states_image: torch.Tensor | list[torch.Tensor] | None = None,
guidance=None,
use_gradient_checkpointing: bool = False,
use_gradient_checkpointing_offload: bool = False,
**kwargs,
) -> torch.Tensor:
# MOVA audio uses x/context naming historically.
x = hidden_states
@@ -268,35 +237,8 @@ class WanAudioModel(CachableDiT, OffloadableDiTMixin):
.to(x.device)
)
def create_custom_forward(module):
def custom_forward(*inputs):
return module(*inputs)
return custom_forward
for block in self.blocks:
if self.training and use_gradient_checkpointing:
if use_gradient_checkpointing_offload:
with torch.autograd.graph.save_on_cpu():
x = torch.utils.checkpoint.checkpoint(
create_custom_forward(block),
x,
context,
t_mod,
freqs,
use_reentrant=False,
)
else:
x = torch.utils.checkpoint.checkpoint(
create_custom_forward(block),
x,
context,
t_mod,
freqs,
use_reentrant=False,
)
else:
x = block(x, context, t_mod, freqs)
x = block(x, context, t_mod, freqs)
x = self.head(x, t)
x = self.unpatchify(x, (f,))

View File

@@ -384,8 +384,6 @@ class WanModel(CachableDiT, OffloadableDiTMixin):
num_layers = config.num_layers
has_image_pos_emb = config.has_image_pos_emb
has_ref_conv = config.has_ref_conv
add_control_adapter = config.add_control_adapter
in_dim_control_adapter = config.in_dim_control_adapter
seperated_timestep = config.seperated_timestep
require_vae_embedding = config.require_vae_embedding
require_clip_embedding = config.require_clip_embedding
@@ -439,17 +437,6 @@ class WanModel(CachableDiT, OffloadableDiTMixin):
self.accumulated_rel_l1_distance_even = 0
self.accumulated_rel_l1_distance_odd = 0
self.__post_init__()
if add_control_adapter:
from .wan_video_camera_controller import SimpleAdapter
self.control_adapter = SimpleAdapter(
in_dim_control_adapter,
dim,
kernel_size=patch_size[1:],
stride=patch_size[1:],
)
else:
self.control_adapter = None
def _init_freqs(self):
if self.freqs is not None:
@@ -463,20 +450,6 @@ class WanModel(CachableDiT, OffloadableDiTMixin):
# NOTE(dhyu): avoid slow_conv
x = x.contiguous(memory_format=torch.channels_last_3d)
x = self.patch_embedding(x)
if (
self.control_adapter is not None
and control_camera_latents_input is not None
):
y_camera = self.control_adapter(control_camera_latents_input)
if isinstance(x, list):
x = [u + v for u, v in zip(x, y_camera)]
x = x[0].unsqueeze(0)
else:
# Some adapters may return a list even when x is a Tensor.
if isinstance(y_camera, list):
x = x + y_camera[0]
else:
x = x + y_camera
grid_size = x.shape[2:]
x = rearrange(x, "b c f h w -> b (f h w) c").contiguous()
return x, grid_size # x, grid_size: (f, h, w)
@@ -498,12 +471,6 @@ class WanModel(CachableDiT, OffloadableDiTMixin):
hidden_states: torch.Tensor,
encoder_hidden_states: torch.Tensor | list[torch.Tensor],
timestep: torch.LongTensor,
encoder_hidden_states_image: torch.Tensor | list[torch.Tensor],
y: torch.Tensor,
guidance=None,
use_gradient_checkpointing: bool = False,
use_gradient_checkpointing_offload: bool = False,
**kwargs,
) -> torch.Tensor:
# MOVA code historically uses x/context/y/clip_feature naming.
x = hidden_states
@@ -532,35 +499,8 @@ class WanModel(CachableDiT, OffloadableDiTMixin):
.to(x.device)
)
def create_custom_forward(module):
def custom_forward(*inputs):
return module(*inputs)
return custom_forward
for block in self.blocks:
if self.training and use_gradient_checkpointing:
if use_gradient_checkpointing_offload:
with torch.autograd.graph.save_on_cpu():
x = torch.utils.checkpoint.checkpoint(
create_custom_forward(block),
x,
context,
t_mod,
freqs,
use_reentrant=False,
)
else:
x = torch.utils.checkpoint.checkpoint(
create_custom_forward(block),
x,
context,
t_mod,
freqs,
use_reentrant=False,
)
else:
x = block(x, context, t_mod, freqs)
x = block(x, context, t_mod, freqs)
x = self.head(x, t)
x = self.unpatchify(x, (f, h, w))

View File

@@ -1,13 +1,11 @@
# Copied and adapted from: https://github.com/OpenMOSS/MOVA/tree/main/mova/diffusion/schedulers/flow_match.py and flow_match_pair.py
# SPDX-License-Identifier: Apache-2.0
# Copied and adapted from: mossVG/mova/diffusion/schedulers/flow_match.py and flow_match_pair.py
from __future__ import annotations
import math
import torch
from diffusers.configuration_utils import ConfigMixin, register_to_config
from diffusers.schedulers.scheduling_utils import SchedulerMixin
from sglang.multimodal_gen.runtime.models.schedulers.base import BaseScheduler
@@ -88,7 +86,7 @@ class FlowMatchScheduler(BaseScheduler):
if self.reverse_sigmas:
self.sigmas = 1 - self.sigmas
self.timesteps = self.sigmas * self.num_train_timesteps
# 第一次设置 train_timesteps
# Initialize train_timesteps on first set.
if self.train_timesteps is None:
self.train_timesteps = self.timesteps
self.train_sigmas = self.sigmas
@@ -160,14 +158,16 @@ class FlowMatchScheduler(BaseScheduler):
return mu
class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
"""
在 FlowMatchScheduler 的基础上,提供便捷的配对接口:
- 默认返回形状为 [num_timesteps, 2] 的张量,每一行为 (t, t)
- 允许通过 set_pair_postprocess(fn) 设置一个后处理函数以修改配对行为
class FlowMatchPairScheduler(FlowMatchScheduler):
"""Pairing scheduler built on FlowMatchScheduler.
Provides a convenient pairing interface for timesteps or sigmas.
Attributes:
pair_timesteps: Cached timestep pairs of shape [num_timesteps, 2].
pair_sigmas: Cached sigma pairs of shape [num_timesteps, 2].
"""
@register_to_config
def __init__(
self,
num_inference_steps=100,
@@ -203,35 +203,47 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
)
def set_pair_postprocess(self, fn):
"""
设置一个后处理函数,用于在默认配对生成后进行自定义修改。
要求:
- fn(pairs: torch.Tensor) -> torch.Tensor
- 返回的张量必须与输入 pairs 形状一致,否则直接 raise。
"""Set a postprocess function to customize pairs after construction.
Args:
fn: Callable with signature fn(pairs: torch.Tensor) -> torch.Tensor.
The returned tensor must have the same shape as input pairs.
Raises:
TypeError: If fn is not callable or None.
RuntimeError: If scheduler is not initialized.
"""
if fn is not None and not callable(fn):
raise TypeError("pair_postprocess 必须是可调用对象或 None")
raise TypeError("pair_postprocess must be callable or None")
self._pair_postprocess_fn = fn
self._pair_postprocess_requires_source = (
False if fn is None else bool(getattr(fn, "_requires_source", False))
)
if self.timesteps is None or self.sigmas is None:
raise RuntimeError("调度器未初始化,请先调用 set_timesteps()")
raise RuntimeError("Scheduler not initialized; call set_timesteps() first")
self._refresh_pair_cache()
def set_pair_postprocess_by_name(self, name: str | None, **kwargs):
"""
通过名称快速配置后处理函数。
支持:
- None/"none"/"off"/"false"/"no": 关闭
- "quadratic_perp_bulge_swap": x2=x+d, y2=x-d其中 d=4*amp*s*(1-s), s=t/T
- "v2a_sequential": 假设原 pairs 为 (t,t)用列0步长2采样一半序列先让列0按该序列变化再让列1按该序列变化
- "a2v_sequential": 同上先列1后列0
- "dual_sigma_shift": 仅使用 timestep 数量,重新按照 FlowMatchScheduler 的 sigma 变换逻辑为两列独立构建调度,可配置 visual_shift/audio_shift
"""Configure a postprocess function by name.
额外参数:
- amp: 浮点幅度,默认 150.0
Supported names:
- None/"none"/"off"/"false"/"no": disable
- "quadratic_perp_bulge_swap": x2=x+d, y2=x-d, where d=4*amp*s*(1-s), s=t/T
- "v2a_sequential": assume pairs are (t,t); sample half sequence from column 0
with stride 2, then let column 0 follow this sequence first, followed by column 1
- "a2v_sequential": same as above, but column 1 first then column 0
- "dual_sigma_shift": use only timestep count; rebuild two columns independently using
FlowMatchScheduler sigma transform logic; configurable visual_shift/audio_shift
Args:
name: Postprocess name or None to disable.
**kwargs: Extra parameters for the named postprocess. For example:
- amp: Float amplitude, default 150.0.
Raises:
ValueError: If name is unknown.
"""
if name is None or str(name).lower() in ("none", "off", "false", "no"):
self.set_pair_postprocess(None)
return
@@ -244,7 +256,7 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
or pairs.ndim != 2
or pairs.shape[1] != 2
):
raise ValueError("pairs 必须是形状 [N, 2] 的 torch.Tensor")
raise ValueError("pairs must be a torch.Tensor of shape [N, 2]")
x = pairs[:, 0]
T = float(self.num_train_timesteps)
s = x / T
@@ -263,7 +275,7 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
or pairs.ndim != 2
or pairs.shape[1] != 2
):
raise ValueError("pairs 必须是形状 [N, 2] 的 torch.Tensor")
raise ValueError("pairs must be a torch.Tensor of shape [N, 2]")
N = pairs.shape[0]
base = pairs[:, 0]
seq_half = base[::2]
@@ -288,7 +300,7 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
or pairs.ndim != 2
or pairs.shape[1] != 2
):
raise ValueError("pairs 必须是形状 [N, 2] 的 torch.Tensor")
raise ValueError("pairs must be a torch.Tensor of shape [N, 2]")
N = pairs.shape[0]
base = pairs[:, 0]
seq_half = base[::2]
@@ -313,7 +325,7 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
or pairs.ndim != 2
or pairs.shape[1] != 2
):
raise ValueError("pairs 必须是形状 [N, 2] 的 torch.Tensor")
raise ValueError("pairs must be a torch.Tensor of shape [N, 2]")
zeros = torch.zeros_like(pairs[:, 0])
return torch.stack([zeros, pairs[:, 1]], dim=1)
@@ -327,7 +339,7 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
or pairs.ndim != 2
or pairs.shape[1] != 2
):
raise ValueError("pairs 必须是形状 [N, 2] 的 torch.Tensor")
raise ValueError("pairs must be a torch.Tensor of shape [N, 2]")
zeros = torch.zeros_like(pairs[:, 1])
return torch.stack([pairs[:, 0], zeros], dim=1)
@@ -351,13 +363,13 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
def _dual_sigma_shift(pairs: torch.Tensor, *, source: str):
if not isinstance(pairs, torch.Tensor):
raise TypeError("pairs 必须是 torch.Tensor")
raise TypeError("pairs must be a torch.Tensor")
if pairs.ndim != 2 or pairs.shape[1] != 2:
raise ValueError("pairs 必须是形状 [N, 2] 的 torch.Tensor")
raise ValueError("pairs must be a torch.Tensor of shape [N, 2]")
if pairs.shape[0] == 0:
raise ValueError("pairs 的长度必须大于 0")
raise ValueError("pairs length must be greater than 0")
if source not in ("timesteps", "sigmas"):
raise ValueError("source 仅支持 'timesteps' 'sigmas'")
raise ValueError("source must be 'timesteps' or 'sigmas'")
num_steps = pairs.shape[0]
device = pairs.device
@@ -367,9 +379,9 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
shift_value: float, denoising_strength: float, mu_override
):
if shift_value <= 0:
raise ValueError("shift 必须为正数")
raise ValueError("shift must be positive")
if denoising_strength <= 0:
raise ValueError("denoising_strength 必须为正数")
raise ValueError("denoising_strength must be positive")
sigma_start = (
self.sigma_min
@@ -399,7 +411,7 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
mu_value = mu_override
if mu_value is None:
raise RuntimeError(
"启用了 exponential_shift 但未提供 exponential_shift_mu"
"exponential_shift enabled but exponential_shift_mu is missing"
)
exp_mu = math.exp(float(mu_value))
base = exp_mu / (exp_mu + (1 / base - 1))
@@ -444,7 +456,14 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
raise ValueError("source must be 'timesteps' or 'sigmas'")
def timestep_to_sigma(self, timestep: torch.Tensor | float) -> torch.Tensor:
"""根据给定的 timestep标量返回对应的 sigma按最近邻在 self.timesteps 中查找)。"""
"""Return sigma for a scalar timestep via nearest neighbor lookup.
Args:
timestep: Scalar timestep value.
Returns:
Sigma corresponding to the nearest timestep.
"""
t_value = float(timestep)
t_cpu = torch.tensor(t_value)
idx = torch.argmin((self.train_timesteps - t_cpu).abs())
@@ -457,10 +476,19 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
timestep_to: torch.Tensor | None,
sample: torch.Tensor,
) -> torch.Tensor:
"""
使用显式给定的 (from, to) timestep 对,按照对应的 sigma 差进行一步更新:
"""Advance one step using an explicit (from, to) timestep pair.
The update rule is:
x_{to} = x_{from} + model_output * (sigma(to) - sigma(from))
该方法可用于两个模态分别沿着它们各自列序列推进。
Args:
model_output: Predicted model output.
timestep_from: Source timestep.
timestep_to: Target timestep or None for terminal.
sample: Current sample at timestep_from.
Returns:
Updated sample at timestep_to.
"""
sigma_from = self.timestep_to_sigma(timestep_from)
if timestep_to is None:
@@ -476,7 +504,7 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
def _refresh_pair_cache(self) -> None:
if self.timesteps is None or self.sigmas is None:
raise RuntimeError("调度器未初始化,请先调用 set_timesteps()")
raise RuntimeError("Scheduler not initialized; call set_timesteps() first")
def _apply_postprocess(pairs: torch.Tensor, source: str) -> torch.Tensor:
if self._pair_postprocess_fn is None:
@@ -486,9 +514,9 @@ class FlowMatchPairScheduler(FlowMatchScheduler, SchedulerMixin, ConfigMixin):
else:
modified = self._pair_postprocess_fn(pairs)
if not isinstance(modified, torch.Tensor):
raise TypeError("pair_postprocess 返回值必须是 torch.Tensor")
raise TypeError("pair_postprocess must return a torch.Tensor")
if modified.shape != pairs.shape:
raise ValueError("pair_postprocess 返回的张量形状必须与输入一致")
raise ValueError("pair_postprocess must return the same shape as input")
return modified
base_pairs_timesteps = self._make_pairs_from_vector(self.timesteps)