[FEAT]Add FastVideo VSA backend for Wan2.2 (#4820)
Signed-off-by: bjf-frz <frz123db@gmail.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
1 parent
3d035bfa19
commit
072bfc02dd
28 files changed
+1316
-107
No files matched your search
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -33,7 +33,7 @@ from vllm_omni.diffusion.models.interface import SupportsComponentDiscovery
|
||||
from vllm_omni.diffusion.models.progress_bar import ProgressBarMixin, _is_rank_zero
|
||||
from vllm_omni.diffusion.models.schedulers import FlowUniPCMultistepScheduler
|
||||
from vllm_omni.diffusion.models.wan2_2.scheduling_wan_euler import WanEulerScheduler
|
||||
from vllm_omni.diffusion.models.wan2_2.wan2_2_transformer import WanTransformer3DModel
|
||||
from vllm_omni.diffusion.models.wan2_2.wan2_2_transformer import WanSelfAttention, WanTransformer3DModel
|
||||
from vllm_omni.diffusion.postprocess import interpolate_video_tensor
|
||||
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import DiffusionPipelineProfilerMixin
|
||||
from vllm_omni.diffusion.request import OmniDiffusionRequest
|
||||
@@ -44,6 +44,8 @@ from vllm_omni.platforms import current_omni_platform
|
||||
logger = logging.getLogger(__name__)
|
||||
DEBUG_PERF = False
|
||||
WAN_SAMPLE_SOLVER_CHOICES = {"unipc", "euler"}
|
||||
FASTWAN_DMD_TIMESTEPS = (1000.0, 757.0, 522.0)
|
||||
FASTWAN_DMD_SCHEDULER_SHIFT = 8.0
|
||||
|
||||
|
||||
def build_wan_scheduler(sample_solver: str, flow_shift: float) -> Any:
|
||||
@@ -64,6 +66,30 @@ def build_wan_scheduler(sample_solver: str, flow_shift: float) -> Any:
|
||||
)
|
||||
|
||||
|
||||
def load_wan_weights_with_optional_gate(model: nn.Module, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
"""Load Wan weights and discard optional VSA gates absent from the checkpoint."""
|
||||
gate_param_names = {name for name, _ in model.named_parameters() if ".to_gate_compress." in name}
|
||||
has_gate_compress_weights = False
|
||||
|
||||
def tracked_weights():
|
||||
nonlocal has_gate_compress_weights
|
||||
for name, weight in weights:
|
||||
if ".to_gate_compress." in name:
|
||||
has_gate_compress_weights = True
|
||||
yield name, weight
|
||||
|
||||
loaded_weights = AutoWeightsLoader(model).load_weights(tracked_weights())
|
||||
setattr(model, "has_gate_compress_weights", has_gate_compress_weights)
|
||||
if not has_gate_compress_weights:
|
||||
for module in model.modules():
|
||||
if isinstance(module, WanSelfAttention):
|
||||
module.to_gate_compress = None
|
||||
|
||||
# Optional gate parameters are absent from public FullAttn/DMD checkpoints.
|
||||
loaded_weights.update(gate_param_names)
|
||||
return loaded_weights
|
||||
|
||||
|
||||
def resolve_wan_sample_solver(req: OmniDiffusionRequest, default: str = "unipc") -> str:
|
||||
extra_args = getattr(req.sampling_params, "extra_args", {}) or {}
|
||||
raw = extra_args.get("sample_solver", default)
|
||||
@@ -314,6 +340,7 @@ class Wan22Pipeline(
|
||||
|
||||
# Read model_index.json to detect expand_timesteps mode (for TI2V-5B)
|
||||
self.expand_timesteps = False
|
||||
self.is_dmd = False
|
||||
self.has_transformer_2 = False
|
||||
if local_files_only:
|
||||
model_index_path = os.path.join(model, "model_index.json")
|
||||
@@ -321,6 +348,7 @@ class Wan22Pipeline(
|
||||
with open(model_index_path) as f:
|
||||
model_index = json.load(f)
|
||||
self.expand_timesteps = model_index.get("expand_timesteps", False)
|
||||
self.is_dmd = model_index.get("_class_name") == "WanDMDPipeline"
|
||||
# Check if this is a two-stage model (MoE with transformer_2)
|
||||
transformer_2_path = os.path.join(model, "transformer_2")
|
||||
self.has_transformer_2 = os.path.exists(transformer_2_path)
|
||||
@@ -333,6 +361,7 @@ class Wan22Pipeline(
|
||||
with open(model_index_path) as f:
|
||||
model_index = json.load(f)
|
||||
self.expand_timesteps = model_index.get("expand_timesteps", False)
|
||||
self.is_dmd = model_index.get("_class_name") == "WanDMDPipeline"
|
||||
# Check transformer_2 from model_index
|
||||
transformer_2_info = model_index.get("transformer_2", [None, None])
|
||||
self.has_transformer_2 = transformer_2_info[0] is not None
|
||||
@@ -422,6 +451,10 @@ class Wan22Pipeline(
|
||||
else:
|
||||
self.transformer_2 = None
|
||||
|
||||
for transformer in (self.transformer, self.transformer_2):
|
||||
if transformer is not None:
|
||||
transformer.preserve_vsa_all_blocks = self.is_dmd
|
||||
|
||||
# Store the active transformer config
|
||||
if load_transformer:
|
||||
self.transformer_config = self.transformer.config
|
||||
@@ -430,8 +463,14 @@ class Wan22Pipeline(
|
||||
else:
|
||||
raise RuntimeError("No transformer loaded")
|
||||
|
||||
self._sample_solver = "unipc"
|
||||
self._flow_shift = od_config.flow_shift if od_config.flow_shift is not None else 5.0
|
||||
self._sample_solver = "euler" if self.is_dmd else "unipc"
|
||||
self._flow_shift = (
|
||||
FASTWAN_DMD_SCHEDULER_SHIFT
|
||||
if self.is_dmd
|
||||
else od_config.flow_shift
|
||||
if od_config.flow_shift is not None
|
||||
else 5.0
|
||||
)
|
||||
self.scheduler = build_wan_scheduler(self._sample_solver, self._flow_shift)
|
||||
|
||||
self.vae_scale_factor_temporal = self.vae.config.scale_factor_temporal if getattr(self, "vae", None) else 4
|
||||
@@ -480,6 +519,7 @@ class Wan22Pipeline(
|
||||
attention_kwargs: dict[str, Any],
|
||||
latent_condition: torch.Tensor | None = None,
|
||||
first_frame_mask: torch.Tensor | None = None,
|
||||
generator: torch.Generator | None = None,
|
||||
) -> torch.Tensor | AsyncLatents:
|
||||
if attention_kwargs is None:
|
||||
attention_kwargs = {}
|
||||
@@ -561,7 +601,20 @@ class Wan22Pipeline(
|
||||
cfg_normalize=False,
|
||||
)
|
||||
|
||||
latents = self.scheduler_step_maybe_with_cfg(noise_pred, t, latents, do_true_cfg)
|
||||
if self.is_dmd:
|
||||
pred_clean = self.scheduler.predict_clean(noise_pred, latents, t).to(noise_pred.dtype)
|
||||
if step_idx + 1 < len(timesteps):
|
||||
noise = randn_tensor(
|
||||
latents.shape,
|
||||
generator=generator,
|
||||
device=latents.device,
|
||||
dtype=pred_clean.dtype,
|
||||
)
|
||||
latents = self.scheduler.add_noise(pred_clean, noise, timesteps[step_idx + 1])
|
||||
else:
|
||||
latents = pred_clean
|
||||
else:
|
||||
latents = self.scheduler_step_maybe_with_cfg(noise_pred, t, latents, do_true_cfg)
|
||||
pbar.update()
|
||||
|
||||
return latents
|
||||
@@ -600,13 +653,21 @@ class Wan22Pipeline(
|
||||
mod_value = self.vae_scale_factor_spatial * patch_size[1] # 16*2=32 for TI2V, 8*2=16 for I2V
|
||||
height = (height // mod_value) * mod_value
|
||||
width = (width // mod_value) * mod_value
|
||||
num_steps = 40 if common.num_inference_steps is None else common.num_inference_steps
|
||||
if self.is_dmd:
|
||||
# The checkpoint was distilled for these three transitions. Ignore
|
||||
# request-level step counts, including the engine's 1-step warmup.
|
||||
num_steps = len(FASTWAN_DMD_TIMESTEPS)
|
||||
else:
|
||||
num_steps = 40 if common.num_inference_steps is None else common.num_inference_steps
|
||||
|
||||
output_type = common.output_type or "np"
|
||||
num_outputs_per_prompt = common.num_outputs_per_prompt or 1
|
||||
attention_kwargs: dict | None = None
|
||||
|
||||
guidance_low, guidance_high = resolve_wan_guidance_scales(common, default_guidance_scale=4.0)
|
||||
guidance_low, guidance_high = resolve_wan_guidance_scales(
|
||||
common,
|
||||
default_guidance_scale=1.0 if self.is_dmd else 4.0,
|
||||
)
|
||||
|
||||
# record guidance for properties
|
||||
self._guidance_scale = guidance_low
|
||||
@@ -685,17 +746,19 @@ class Wan22Pipeline(
|
||||
current_omni_platform.synchronize()
|
||||
_t_text_enc_ms = (time.perf_counter() - _t_text_enc_start) * 1000
|
||||
|
||||
first_request = req.requests[0]
|
||||
sample_solver = resolve_wan_sample_solver(first_request, default=self._sample_solver)
|
||||
flow_shift = resolve_wan_flow_shift(first_request, self.od_config)
|
||||
if sample_solver != self._sample_solver or abs(flow_shift - self._flow_shift) > 1e-6:
|
||||
self.scheduler = build_wan_scheduler(sample_solver, flow_shift)
|
||||
self._sample_solver = sample_solver
|
||||
self._flow_shift = flow_shift
|
||||
if self.is_dmd:
|
||||
timesteps = torch.tensor(FASTWAN_DMD_TIMESTEPS, device=device, dtype=torch.float32)
|
||||
else:
|
||||
first_request = req.requests[0]
|
||||
sample_solver = resolve_wan_sample_solver(first_request, default=self._sample_solver)
|
||||
flow_shift = resolve_wan_flow_shift(first_request, self.od_config)
|
||||
if sample_solver != self._sample_solver or abs(flow_shift - self._flow_shift) > 1e-6:
|
||||
self.scheduler = build_wan_scheduler(sample_solver, flow_shift)
|
||||
self._sample_solver = sample_solver
|
||||
self._flow_shift = flow_shift
|
||||
|
||||
# Timesteps
|
||||
self.scheduler.set_timesteps(num_steps, device=device)
|
||||
timesteps = self.scheduler.timesteps
|
||||
self.scheduler.set_timesteps(num_steps, device=device)
|
||||
timesteps = self.scheduler.timesteps
|
||||
self._num_timesteps = len(timesteps)
|
||||
boundary_timestep = None
|
||||
if boundary_ratio is not None:
|
||||
@@ -817,6 +880,7 @@ class Wan22Pipeline(
|
||||
attention_kwargs=attention_kwargs,
|
||||
latent_condition=latent_condition,
|
||||
first_frame_mask=first_frame_mask,
|
||||
generator=generator,
|
||||
)
|
||||
|
||||
# Wan2.2 is prone to out of memory errors when predicting large videos
|
||||
@@ -1003,9 +1067,7 @@ class Wan22Pipeline(
|
||||
return latents
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
"""Load weights using AutoWeightsLoader for vLLM integration."""
|
||||
loader = AutoWeightsLoader(self)
|
||||
return loader.load_weights(weights)
|
||||
return load_wan_weights_with_optional_gate(self, weights)
|
||||
|
||||
def check_inputs(
|
||||
self,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -16,7 +16,6 @@ import torchvision.transforms.functional as TF
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
from torch import nn
|
||||
from transformers import AutoTokenizer, CLIPImageProcessor, CLIPVisionModel, UMT5EncoderModel
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader
|
||||
from vllm.sequence import IntermediateTensors
|
||||
|
||||
from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
|
||||
@@ -36,6 +35,7 @@ from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
|
||||
build_wan_scheduler,
|
||||
create_transformer_from_config,
|
||||
load_transformer_config,
|
||||
load_wan_weights_with_optional_gate,
|
||||
resolve_wan_flow_shift,
|
||||
resolve_wan_guidance_scales,
|
||||
resolve_wan_sample_solver,
|
||||
@@ -1014,9 +1014,7 @@ class Wan22I2VPipeline(
|
||||
raise ValueError("`guidance_scale_2` is only supported when `boundary_ratio` is set.")
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
"""Load weights using AutoWeightsLoader for vLLM integration."""
|
||||
loader = AutoWeightsLoader(self)
|
||||
return loader.load_weights(weights)
|
||||
return load_wan_weights_with_optional_gate(self, weights)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""
|
||||
Wan2.2 Speech-to-Video (S2V) Pipeline for vLLM-Omni.
|
||||
@@ -22,7 +22,6 @@ from diffusers.utils.torch_utils import randn_tensor
|
||||
from torch import nn
|
||||
from torchvision import transforms
|
||||
from transformers import AutoTokenizer, UMT5Config, UMT5EncoderModel, Wav2Vec2ForCTC, Wav2Vec2Processor
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader
|
||||
from vllm.multimodal.media.audio import load_audio
|
||||
|
||||
from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
|
||||
@@ -36,6 +35,7 @@ from vllm_omni.diffusion.models.progress_bar import ProgressBarMixin
|
||||
from vllm_omni.diffusion.models.schedulers import FlowUniPCMultistepScheduler
|
||||
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
|
||||
load_transformer_config,
|
||||
load_wan_weights_with_optional_gate,
|
||||
retrieve_latents,
|
||||
)
|
||||
from vllm_omni.diffusion.models.wan2_2.wan2_2_s2v_transformer import (
|
||||
@@ -1475,6 +1475,4 @@ class Wan22S2VPipeline(
|
||||
return outputs
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
"""Load weights using AutoWeightsLoader for vLLM integration."""
|
||||
loader = AutoWeightsLoader(self)
|
||||
return loader.load_weights(weights)
|
||||
return load_wan_weights_with_optional_gate(self, weights)
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""
|
||||
VACE (Video Creation and Editing) Pipeline for WAN models.
|
||||
@@ -22,12 +22,12 @@ import PIL.Image
|
||||
import torch
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader
|
||||
|
||||
from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
|
||||
from vllm_omni.diffusion.models.interface import SupportImageInput
|
||||
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
|
||||
Wan22Pipeline,
|
||||
load_wan_weights_with_optional_gate,
|
||||
retrieve_latents,
|
||||
)
|
||||
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
|
||||
@@ -747,6 +747,4 @@ class Wan22VACEPipeline(Wan22Pipeline, SupportImageInput):
|
||||
)
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
"""Load weights using AutoWeightsLoader for vLLM integration."""
|
||||
loader = AutoWeightsLoader(self)
|
||||
return loader.load_weights(weights)
|
||||
return load_wan_weights_with_optional_gate(self, weights)
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -110,6 +110,42 @@ class WanEulerScheduler:
|
||||
self._step_index = None
|
||||
self._begin_index = None
|
||||
|
||||
def sigma_for_timestep(
|
||||
self,
|
||||
timestep: float | torch.Tensor,
|
||||
*,
|
||||
device: torch.device | str | None = None,
|
||||
dtype: torch.dtype = torch.float32,
|
||||
) -> torch.Tensor:
|
||||
"""Return the flow sigma nearest to a (possibly distilled) timestep."""
|
||||
timestep_t = torch.as_tensor(timestep, device=self.timesteps.device, dtype=self.timesteps.dtype)
|
||||
if timestep_t.numel() != 1:
|
||||
raise ValueError(f"Expected a scalar timestep, got shape {tuple(timestep_t.shape)}")
|
||||
index = torch.argmin(torch.abs(self.timesteps - timestep_t))
|
||||
return self.sigmas[index].to(device=device or self.timesteps.device, dtype=dtype)
|
||||
|
||||
def predict_clean(
|
||||
self,
|
||||
model_output: torch.Tensor,
|
||||
sample: torch.Tensor,
|
||||
timestep: float | torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
"""Convert a flow/noise prediction into the corresponding clean latent."""
|
||||
sample_fp32 = sample.to(torch.float32)
|
||||
sigma = self.sigma_for_timestep(timestep, device=sample.device, dtype=torch.float32)
|
||||
return sample_fp32 - sigma * model_output.to(torch.float32)
|
||||
|
||||
def add_noise(
|
||||
self,
|
||||
clean_sample: torch.Tensor,
|
||||
noise: torch.Tensor,
|
||||
timestep: float | torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
"""Re-noise a clean latent at a distilled flow timestep."""
|
||||
sigma = self.sigma_for_timestep(timestep, device=noise.device, dtype=torch.float32)
|
||||
noisy = (1.0 - sigma) * clean_sample.to(torch.float32) + sigma * noise.to(torch.float32)
|
||||
return noisy.to(noise.dtype)
|
||||
|
||||
def scale_model_input(self, sample: torch.Tensor, timestep: int | None = None) -> torch.Tensor: # noqa: ARG002
|
||||
return sample
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
import math
|
||||
from collections.abc import Iterable
|
||||
@@ -419,6 +419,24 @@ class WanSelfAttention(nn.Module):
|
||||
prefix=prefix,
|
||||
)
|
||||
|
||||
# FastVideo VSA checkpoints may add a learned projection that gates
|
||||
# the compressed global branch. Zero initialization makes checkpoints
|
||||
# without these weights sparse-only, with no user-facing mode switch.
|
||||
self.to_gate_compress: ColumnParallelLinear | None = None
|
||||
if self.attn.attn_backend.get_name() == "FASTVIDEO_VSA":
|
||||
self.to_gate_compress = ColumnParallelLinear(
|
||||
dim,
|
||||
self.inner_dim,
|
||||
bias=True,
|
||||
gather_output=False,
|
||||
return_bias=False,
|
||||
quant_config=quant_config,
|
||||
prefix=f"{prefix}.to_gate_compress" if prefix else "to_gate_compress",
|
||||
)
|
||||
nn.init.zeros_(self.to_gate_compress.weight)
|
||||
if self.to_gate_compress.bias is not None:
|
||||
nn.init.zeros_(self.to_gate_compress.bias)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
@@ -441,6 +459,15 @@ class WanSelfAttention(nn.Module):
|
||||
key = key.unflatten(2, (self.num_kv_heads, self.head_dim))
|
||||
value = value.unflatten(2, (self.num_kv_heads, self.head_dim))
|
||||
|
||||
if self.to_gate_compress is not None:
|
||||
gate_result = self.to_gate_compress(hidden_states)
|
||||
gate_compress = gate_result[0] if isinstance(gate_result, tuple) else gate_result
|
||||
gate_compress = gate_compress.unflatten(2, (self.num_heads, self.head_dim))
|
||||
if attn_metadata is None:
|
||||
attn_metadata = AttentionMetadata(extra={"gate_compress": gate_compress})
|
||||
else:
|
||||
attn_metadata.extra["gate_compress"] = gate_compress
|
||||
|
||||
# Apply rotary embeddings
|
||||
if rotary_emb is not None:
|
||||
freqs_cos, freqs_sin = rotary_emb
|
||||
@@ -709,6 +736,8 @@ class WanTransformerBlock(nn.Module):
|
||||
temb: torch.Tensor,
|
||||
rotary_emb: tuple[torch.Tensor, torch.Tensor],
|
||||
hidden_states_mask: torch.Tensor | None = None,
|
||||
vsa_dit_seq_shape: tuple[int, int, int] | None = None,
|
||||
preserve_vsa_all_blocks: bool = False,
|
||||
) -> torch.Tensor:
|
||||
if temb.ndim == 4:
|
||||
# temb: batch_size, seq_len, 6, inner_dim (wan2.2 ti2v)
|
||||
@@ -729,7 +758,12 @@ class WanTransformerBlock(nn.Module):
|
||||
|
||||
# 1. Self-attention
|
||||
norm_hidden_states = self.norm1(hidden_states, scale_msa, shift_msa).type_as(hidden_states)
|
||||
self_attn_metadata = AttentionMetadata(attn_mask=hidden_states_mask)
|
||||
self_attn_extra = {}
|
||||
if vsa_dit_seq_shape is not None:
|
||||
self_attn_extra["vsa_dit_seq_shape"] = vsa_dit_seq_shape
|
||||
if preserve_vsa_all_blocks:
|
||||
self_attn_extra["preserve_vsa_all_blocks"] = True
|
||||
self_attn_metadata = AttentionMetadata(attn_mask=hidden_states_mask, extra=self_attn_extra)
|
||||
attn_output = self.attn1(norm_hidden_states, rotary_emb, self_attn_metadata)
|
||||
hidden_states = (hidden_states + attn_output * gate_msa).type_as(hidden_states)
|
||||
|
||||
@@ -910,6 +944,9 @@ class WanTransformer3DModel(nn.Module):
|
||||
pos_embed_seq_len=pos_embed_seq_len,
|
||||
)
|
||||
|
||||
# DMD/FastVideo checkpoints retain VSA semantics when top-k selects every block.
|
||||
self.preserve_vsa_all_blocks = False
|
||||
|
||||
# 3. Transformer blocks — partitioned across PP stages via vLLM's `make_layers`.
|
||||
# It computes the [start_layer, end_layer) slice for this rank and fills the remaining slots
|
||||
# with PPMissingLayer so that weight names stay globally consistent.
|
||||
@@ -1044,8 +1081,19 @@ class WanTransformer3DModel(nn.Module):
|
||||
)
|
||||
|
||||
# Transformer blocks
|
||||
# Preserve the post-patch (T, H, W) grid so VSA can partition
|
||||
# the flattened DiT sequence into spatiotemporal blocks.
|
||||
vsa_dit_seq_shape = (post_patch_num_frames, post_patch_height, post_patch_width)
|
||||
for block in self.blocks[self.start_layer : self.end_layer]:
|
||||
hidden_states = block(hidden_states, encoder_hidden_states, timestep_proj, rotary_emb, hidden_states_mask)
|
||||
hidden_states = block(
|
||||
hidden_states,
|
||||
encoder_hidden_states,
|
||||
timestep_proj,
|
||||
rotary_emb,
|
||||
hidden_states_mask,
|
||||
vsa_dit_seq_shape,
|
||||
self.preserve_vsa_all_blocks,
|
||||
)
|
||||
|
||||
if not is_pipeline_last_stage():
|
||||
# Non-last PP stage: hand the token sequence to the caller via IntermediateTensors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""VACE variant of WanTransformer3DModel for conditional video generation."""
|
||||
|
||||
@@ -61,6 +61,7 @@ class VaceWanTransformerBlock(WanTransformerBlock):
|
||||
temb: torch.Tensor,
|
||||
rotary_emb: tuple[torch.Tensor, torch.Tensor],
|
||||
hidden_states_mask: torch.Tensor | None = None,
|
||||
vsa_dit_seq_shape: tuple[int, int, int] | None = None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
if self.proj_in is not None:
|
||||
control_hidden_states = self.proj_in(control_hidden_states)
|
||||
@@ -72,6 +73,7 @@ class VaceWanTransformerBlock(WanTransformerBlock):
|
||||
temb,
|
||||
rotary_emb,
|
||||
hidden_states_mask,
|
||||
vsa_dit_seq_shape,
|
||||
)
|
||||
|
||||
conditioning_states = self.proj_out(control_hidden_states)
|
||||
@@ -250,6 +252,7 @@ class WanVACETransformer3DModel(WanTransformer3DModel):
|
||||
)
|
||||
|
||||
# VACE: embed context and run conditioning blocks
|
||||
vsa_dit_seq_shape = (post_patch_num_frames, post_patch_height, post_patch_width)
|
||||
vace_hints = None
|
||||
if vace_context is not None and self.vace_blocks is not None:
|
||||
full_seq_len = hidden_states.shape[1] * sp_size
|
||||
@@ -263,6 +266,7 @@ class WanVACETransformer3DModel(WanTransformer3DModel):
|
||||
timestep_proj,
|
||||
rotary_emb,
|
||||
hidden_states_mask,
|
||||
vsa_dit_seq_shape,
|
||||
)
|
||||
vace_hints.append(conditioning_states)
|
||||
|
||||
@@ -278,6 +282,7 @@ class WanVACETransformer3DModel(WanTransformer3DModel):
|
||||
timestep_proj,
|
||||
rotary_emb,
|
||||
hidden_states_mask,
|
||||
vsa_dit_seq_shape,
|
||||
)
|
||||
if vace_hints is not None and self.vace_layers_mapping is not None and i in self.vace_layers_mapping:
|
||||
vace_idx = self.vace_layers_mapping[i]
|
||||
|
||||
Reference in new issue
Block a user