GeoNeXt / diffsynth /core /data /custom_model_fn.py
happy0612's picture
Deploy GeoNeXt inference on ZeroGPU
a61978a verified
Raw History Blame Contribute Delete
17.4 kB
import torch
from einops import rearrange
# Import required dependencies while keeping compatibility with upstream logic.
from diffsynth.diffusion import FlowMatchScheduler
from diffsynth.core import ModelConfig, gradient_checkpoint_forward
from diffsynth.diffusion.base_pipeline import BasePipeline, PipelineUnit
from typing import Optional, Any, Dict, List
from diffsynth.models.wan_video_dit import WanModel, sinusoidal_embedding_1d
from diffsynth.models.wan_video_dit_s2v import rope_precompute
from diffsynth.models.wan_video_text_encoder import WanTextEncoder, HuggingfaceTokenizer
from diffsynth.models.wan_video_vae import WanVideoVAE
from diffsynth.models.wan_video_image_encoder import WanImageEncoder
from diffsynth.models.wan_video_vace import VaceWanModel
from diffsynth.models.wan_video_motion_controller import WanMotionControllerModel
from diffsynth.models.wan_video_animate_adapter import WanAnimateAdapter
from diffsynth.models.wan_video_mot import MotWanModel
from diffsynth.models.wav2vec import WanS2VAudioEncoder
from diffsynth.pipelines.wan_video import TeaCache,TemporalTiler_BCTHW, model_fn_wans2v
try:
from diffsynth.models.longcat_video_dit import LongCatVideoTransformer3DModel
from diffsynth.pipelines.wan_video import model_fn_longcat_video
except Exception:
LongCatVideoTransformer3DModel = None
model_fn_longcat_video = None
def model_fn_wan_video_geonext(
dit: WanModel,
motion_controller: WanMotionControllerModel = None,
vace: VaceWanModel = None,
vap: MotWanModel = None,
animate_adapter: WanAnimateAdapter = None,
latents: torch.Tensor = None,
timestep: torch.Tensor = None,
context: torch.Tensor = None,
clip_feature: Optional[torch.Tensor] = None,
y: Optional[torch.Tensor] = None,
reference_latents = None,
vace_context = None,
vace_scale = 1.0,
audio_embeds: Optional[torch.Tensor] = None,
motion_latents: Optional[torch.Tensor] = None,
s2v_pose_latents: Optional[torch.Tensor] = None,
vap_hidden_state = None,
vap_clip_feature = None,
context_vap = None,
drop_motion_frames: bool = True,
tea_cache: TeaCache = None,
use_unified_sequence_parallel: bool = False,
motion_bucket_id: Optional[torch.Tensor] = None,
pose_latents=None,
face_pixel_values=None,
longcat_latents=None,
sliding_window_size: Optional[int] = None,
sliding_window_stride: Optional[int] = None,
cfg_merge: bool = False,
use_gradient_checkpointing: bool = False,
use_gradient_checkpointing_offload: bool = False,
control_camera_latents_input = None,
fuse_vae_embedding_in_latents: bool = False,
# Added argument: clean_reference_latents.
clean_reference_latents: torch.Tensor = None,
rgb_condition_latents: torch.Tensor = None,
rgb_condition_mode: str = "first_frame",
rgb_condition_scale: float = 1.0,
**kwargs,
):
# 1) Sliding-window path (must forward clean_reference_latents).
if sliding_window_size is not None and sliding_window_stride is not None:
model_kwargs = dict(
dit=dit,
motion_controller=motion_controller,
vace=vace,
latents=latents,
timestep=timestep,
context=context,
clip_feature=clip_feature,
y=y,
reference_latents=reference_latents,
vace_context=vace_context,
vace_scale=vace_scale,
tea_cache=tea_cache,
use_unified_sequence_parallel=use_unified_sequence_parallel,
motion_bucket_id=motion_bucket_id,
# Forward through recursive tiled call.
clean_reference_latents=clean_reference_latents,
)
return TemporalTiler_BCTHW().run(
model_fn_wan_video_geonext, # recursive call
sliding_window_size, sliding_window_stride,
latents.device, latents.dtype,
model_kwargs=model_kwargs,
tensor_names=["latents", "y"],
batch_size=2 if cfg_merge else 1
)
# 2) LongCat / S2V compatibility branches.
if LongCatVideoTransformer3DModel is not None and isinstance(dit, LongCatVideoTransformer3DModel):
return model_fn_longcat_video(
dit=dit, latents=latents, timestep=timestep, context=context,
longcat_latents=longcat_latents,
use_gradient_checkpointing=use_gradient_checkpointing,
use_gradient_checkpointing_offload=use_gradient_checkpointing_offload,
)
if audio_embeds is not None:
return model_fn_wans2v(
dit=dit, latents=latents, timestep=timestep, context=context,
audio_embeds=audio_embeds, motion_latents=motion_latents,
s2v_pose_latents=s2v_pose_latents, drop_motion_frames=drop_motion_frames,
use_gradient_checkpointing_offload=use_gradient_checkpointing_offload,
use_gradient_checkpointing=use_gradient_checkpointing,
use_unified_sequence_parallel=use_unified_sequence_parallel,
)
# 3) Distributed-parallel environment setup.
if use_unified_sequence_parallel:
import torch.distributed as dist
from xfuser.core.distributed import (get_sequence_parallel_rank,
get_sequence_parallel_world_size,
get_sp_group)
# If clean reference latents are provided, overwrite the leading frames.
# latents shape: [B, C, F, H, W]
if clean_reference_latents is not None:
# print(f"Injecting clean reference latents with shape {clean_reference_latents.shape} into latents with shape {latents.shape}")
# Ensure clean_reference_latents uses the correct device/dtype.
ref = clean_reference_latents.to(device=latents.device, dtype=latents.dtype)
num_clean_frames = ref.shape[2]
# Force-replace the first N frames with clean latents.
latents[:, :, :num_clean_frames] = ref
if rgb_condition_mode == "repeat_add":
if rgb_condition_latents is None:
raise ValueError("rgb_condition_mode='repeat_add' requires rgb_condition_latents.")
rgb_ref = rgb_condition_latents.to(device=latents.device, dtype=latents.dtype)
if rgb_ref.shape[2] != 1:
rgb_ref = rgb_ref[:, :, :1]
rgb_ref = rgb_ref.repeat(1, 1, latents.shape[2], 1, 1)
latents = latents + float(rgb_condition_scale) * rgb_ref
elif rgb_condition_mode == "concat":
if rgb_condition_latents is None:
raise ValueError("rgb_condition_mode='concat' requires rgb_condition_latents.")
rgb_ref = rgb_condition_latents.to(device=latents.device, dtype=latents.dtype)
if rgb_ref.shape[2] != 1:
rgb_ref = rgb_ref[:, :, :1]
rgb_ref = rgb_ref.repeat(1, 1, latents.shape[2], 1, 1)
latents = torch.cat([latents, float(rgb_condition_scale) * rgb_ref], dim=1)
elif rgb_condition_mode != "first_frame":
raise ValueError(
f"Unsupported rgb_condition_mode: {rgb_condition_mode}. "
"Expected 'first_frame', 'repeat_add', or 'concat'."
)
# =========================================================
# 3) Reworked timestep construction logic.
# Original logic: if dit.seperated_timestep and fuse_vae_embedding_in_latents ...
# New logic: prioritize clean_reference_latents when present.
if dit.seperated_timestep and (clean_reference_latents is not None):
# Compute clean/noisy frame counts dynamically.
num_clean = clean_reference_latents.shape[2]
num_total = latents.shape[2]
num_noisy = num_total - num_clean
tokens_per_frame = (latents.shape[3] // 2) * (latents.shape[4] // 2)
timestep = torch.concat([
torch.zeros((num_clean, tokens_per_frame), dtype=latents.dtype, device=latents.device),
torch.ones((num_noisy, tokens_per_frame), dtype=latents.dtype, device=latents.device) * timestep
]).flatten()
# Build timestep embeddings.
t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep).unsqueeze(0))
# Handle USP sequence parallel path.
if use_unified_sequence_parallel and dist.is_initialized() and dist.get_world_size() > 1:
t_chunks = torch.chunk(t, get_sequence_parallel_world_size(), dim=1)
t_chunks = [torch.nn.functional.pad(chunk, (0, 0, 0, t_chunks[0].shape[1]-chunk.shape[1]), value=0) for chunk in t_chunks]
t = t_chunks[get_sequence_parallel_rank()]
t_mod = dit.time_projection(t).unflatten(2, (6, dit.dim))
# Fallback to the original branch (e.g., Ti2V single-frame style input).
elif dit.seperated_timestep and fuse_vae_embedding_in_latents:
tokens_per_frame = (latents.shape[3] // 2) * (latents.shape[4] // 2)
timestep = torch.concat([
torch.zeros((1, tokens_per_frame), dtype=latents.dtype, device=latents.device),
torch.ones((latents.shape[2] - 1, tokens_per_frame), dtype=latents.dtype, device=latents.device) * timestep
]).flatten()
t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep).unsqueeze(0))
if use_unified_sequence_parallel and dist.is_initialized() and dist.get_world_size() > 1:
t_chunks = torch.chunk(t, get_sequence_parallel_world_size(), dim=1)
t_chunks = [torch.nn.functional.pad(chunk, (0, 0, 0, t_chunks[0].shape[1]-chunk.shape[1]), value=0) for chunk in t_chunks]
t = t_chunks[get_sequence_parallel_rank()]
t_mod = dit.time_projection(t).unflatten(2, (6, dit.dim))
else:
# Standard timestep logic.
t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep))
t_mod = dit.time_projection(t).unflatten(1, (6, dit.dim))
# 4) Motion controller.
if motion_bucket_id is not None and motion_controller is not None:
t_mod = t_mod + motion_controller(motion_bucket_id).unflatten(1, (6, dit.dim))
# 5) Context embedding.
context = dit.text_embedding(context)
# 6) CFG merge.
x = latents
if x.shape[0] != context.shape[0]:
x = torch.concat([x] * context.shape[0], dim=0)
if timestep.shape[0] != context.shape[0]:
timestep = torch.concat([timestep] * context.shape[0], dim=0)
# 7) Image embedding (y / clip_feature).
if y is not None and dit.require_vae_embedding:
x = torch.cat([x, y], dim=1)
if clip_feature is not None and dit.require_clip_embedding:
clip_embdding = dit.img_emb(clip_feature)
context = torch.cat([clip_embdding, context], dim=1)
# 8) Camera control.
# Pass control_camera_latents_input to avoid patchify errors.
x = dit.patchify(x, control_camera_latents_input)
# 9) Animate adapter.
if pose_latents is not None and face_pixel_values is not None:
x, motion_vec = animate_adapter.after_patch_embedding(x, pose_latents, face_pixel_values)
# 10) Flatten / patchify reshape.
f, h, w = x.shape[2:]
x = rearrange(x, 'b c f h w -> b (f h w) c').contiguous()
# 11) Reference image path.
if reference_latents is not None:
if len(reference_latents.shape) == 5:
reference_latents = reference_latents[:, :, 0]
reference_latents = dit.ref_conv(reference_latents).flatten(2).transpose(1, 2)
x = torch.concat([reference_latents, x], dim=1)
f += 1
# 12) RoPE frequency construction.
freqs = torch.cat([
dit.freqs[0][:f].view(f, 1, 1, -1).expand(f, h, w, -1),
dit.freqs[1][:h].view(1, h, 1, -1).expand(f, h, w, -1),
dit.freqs[2][:w].view(1, 1, w, -1).expand(f, h, w, -1)
], dim=-1).reshape(f * h * w, 1, -1).to(x.device)
# 13) VAP logic.
if vap is not None:
# hidden state
x_vap = vap_hidden_state
x_vap = vap.patchify(x_vap)
x_vap = rearrange(x_vap, 'b c f h w -> b (f h w) c').contiguous()
# Timestep
clean_timestep = torch.ones(timestep.shape, device=timestep.device).to(timestep.dtype)
t_vap = vap.time_embedding(sinusoidal_embedding_1d(vap.freq_dim, clean_timestep))
t_mod_vap = vap.time_projection(t_vap).unflatten(1, (6, vap.dim))
# rope
freqs_vap = vap.compute_freqs_mot(f,h,w).to(x.device)
# context
vap_clip_embedding = vap.img_emb(vap_clip_feature)
context_vap = vap.text_embedding(context_vap)
context_vap = torch.cat([vap_clip_embedding, context_vap], dim=1)
# 14) TeaCache logic.
if tea_cache is not None:
tea_cache_update = tea_cache.check(dit, x, t_mod)
else:
tea_cache_update = False
# 15) VACE logic.
if vace_context is not None:
vace_hints = vace(
x, vace_context, context, t_mod, freqs,
use_gradient_checkpointing=use_gradient_checkpointing,
use_gradient_checkpointing_offload=use_gradient_checkpointing_offload
)
# 16) Transformer block loop.
if use_unified_sequence_parallel:
if dist.is_initialized() and dist.get_world_size() > 1:
chunks = torch.chunk(x, get_sequence_parallel_world_size(), dim=1)
pad_shape = chunks[0].shape[1] - chunks[-1].shape[1]
chunks = [torch.nn.functional.pad(chunk, (0, 0, 0, chunks[0].shape[1]-chunk.shape[1]), value=0) for chunk in chunks]
x = chunks[get_sequence_parallel_rank()]
if tea_cache_update:
x = tea_cache.update(x)
else:
def create_custom_forward(module):
def custom_forward(*inputs):
return module(*inputs)
return custom_forward
def create_custom_forward_vap(block, vap):
def custom_forward(*inputs):
return vap(block, *inputs)
return custom_forward
for block_id, block in enumerate(dit.blocks):
# Block logic (including VAP, checkpointing, etc.).
if vap is not None and block_id in vap.mot_layers_mapping:
# VAP branch.
if use_gradient_checkpointing_offload:
with torch.autograd.graph.save_on_cpu():
x, x_vap = torch.utils.checkpoint.checkpoint(
create_custom_forward_vap(block, vap),
x, context, t_mod, freqs, x_vap, context_vap, t_mod_vap, freqs_vap, block_id,
use_reentrant=False,
)
elif use_gradient_checkpointing:
x, x_vap = torch.utils.checkpoint.checkpoint(
create_custom_forward_vap(block, vap),
x, context, t_mod, freqs, x_vap, context_vap, t_mod_vap, freqs_vap, block_id,
use_reentrant=False,
)
else:
x, x_vap = vap(block, x, context, t_mod, freqs, x_vap, context_vap, t_mod_vap, freqs_vap, block_id)
else:
# Standard branch.
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,
)
elif use_gradient_checkpointing:
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)
# VACE Injection
if vace_context is not None and block_id in vace.vace_layers_mapping:
current_vace_hint = vace_hints[vace.vace_layers_mapping[block_id]]
if use_unified_sequence_parallel and dist.is_initialized() and dist.get_world_size() > 1:
current_vace_hint = torch.chunk(current_vace_hint, get_sequence_parallel_world_size(), dim=1)[get_sequence_parallel_rank()]
current_vace_hint = torch.nn.functional.pad(current_vace_hint, (0, 0, 0, chunks[0].shape[1] - current_vace_hint.shape[1]), value=0)
x = x + current_vace_hint * vace_scale
# Animate Injection
if pose_latents is not None and face_pixel_values is not None:
x = animate_adapter.after_transformer_block(block_id, x, motion_vec)
if tea_cache is not None:
tea_cache.store(x)
# 17) Head + unpatchify.
x = dit.head(x, t)
if use_unified_sequence_parallel:
if dist.is_initialized() and dist.get_world_size() > 1:
x = get_sp_group().all_gather(x, dim=1)
x = x[:, :-pad_shape] if pad_shape > 0 else x
# Remove reference latents if added
if reference_latents is not None:
x = x[:, reference_latents.shape[1]:]
f -= 1
x = dit.unpatchify(x, (f, h, w))
return x
# Backward-compatible alias.
model_fn_wan_video_three_frames = model_fn_wan_video_geonext