2025-12-10 16:38:05 -10:00
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import argparse
2026-01-20 11:35:48 +08:00
import os
2026-01-27 11:33:38 +08:00
import time
2025-12-10 16:38:05 -10:00
from pathlib import Path
import numpy as np
import torch
2026-01-27 11:33:38 +08:00
from vllm_omni.diffusion.data import DiffusionParallelConfig
2025-12-10 16:38:05 -10:00
from vllm_omni.entrypoints.omni import Omni
2026-01-28 16:07:42 +08:00
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
2025-12-31 22:02:04 +08:00
from vllm_omni.outputs import OmniRequestOutput
2026-01-28 22:12:52 +08:00
from vllm_omni.platforms import current_omni_platform
2025-12-10 16:38:05 -10:00
def parse_args () -> argparse . Namespace :
parser = argparse . ArgumentParser ( description = "Generate a video with Wan2.2 T2V." )
parser . add_argument (
"--model" ,
default = "Wan-AI/Wan2.2-T2V-A14B-Diffusers" ,
help = "Diffusers Wan2.2 model ID or local path." ,
)
parser . add_argument ( "--prompt" , default = "A serene lakeside sunrise with mist over the water." , help = "Text prompt." )
parser . add_argument ( "--negative_prompt" , default = "" , help = "Negative prompt." )
parser . add_argument ( "--seed" , type = int , default = 42 , help = "Random seed." )
parser . add_argument ( "--guidance_scale" , type = float , default = 4.0 , help = "CFG scale (applied to low/high)." )
parser . add_argument ( "--guidance_scale_high" , type = float , default = None , help = "Optional separate CFG for high-noise." )
parser . add_argument ( "--height" , type = int , default = 720 , help = "Video height." )
parser . add_argument ( "--width" , type = int , default = 1280 , help = "Video width." )
parser . add_argument ( "--num_frames" , type = int , default = 81 , help = "Number of frames (Wan default is 81)." )
parser . add_argument ( "--num_inference_steps" , type = int , default = 40 , help = "Sampling steps." )
2026-01-29 22:02:24 +08:00
parser . add_argument (
"--boundary_ratio" ,
type = float ,
default = 0.875 ,
help = "Boundary split ratio for low/high DiT. Default 0.875 uses both transformers for best quality. Set to 1.0 to load only the low-noise transformer (saves noticeable memory with good quality, recommended if memory is limited)." ,
)
2025-12-10 16:38:05 -10:00
parser . add_argument (
"--flow_shift" , type = float , default = 5.0 , help = "Scheduler flow_shift (5.0 for 720p, 12.0 for 480p)."
)
2026-01-28 20:07:50 +08:00
parser . add_argument (
"--cache_backend" ,
type = str ,
default = None ,
choices = [ "cache_dit" ],
help = (
"Cache backend to use for acceleration. "
"Options: 'cache_dit' (DBCache + SCM + TaylorSeer). "
"Default: None (no cache acceleration)."
),
)
parser . add_argument (
"--enable-cache-dit-summary" ,
action = "store_true" ,
help = "Enable cache-dit summary logging after diffusion forward passes." ,
)
2025-12-10 16:38:05 -10:00
parser . add_argument ( "--output" , type = str , default = "wan22_output.mp4" , help = "Path to save the video (mp4)." )
parser . add_argument ( "--fps" , type = int , default = 24 , help = "Frames per second for the output video." )
2026-01-28 22:12:52 +08:00
parser . add_argument (
"--vae_use_slicing" ,
action = "store_true" ,
help = "Enable VAE slicing for memory optimization." ,
)
parser . add_argument (
"--vae_use_tiling" ,
action = "store_true" ,
help = "Enable VAE tiling for memory optimization." ,
)
2026-01-28 20:07:50 +08:00
parser . add_argument (
"--enforce_eager" ,
action = "store_true" ,
help = "Disable torch.compile and force eager execution." ,
)
2026-01-23 22:43:00 +08:00
parser . add_argument (
"--enable-cpu-offload" ,
action = "store_true" ,
help = "Enable CPU offloading for diffusion models." ,
)
2026-01-27 11:33:38 +08:00
parser . add_argument (
"--ulysses_degree" ,
type = int ,
default = 1 ,
help = "Number of GPUs used for ulysses sequence parallelism." ,
)
parser . add_argument (
"--ring_degree" ,
type = int ,
default = 1 ,
help = "Number of GPUs used for ring sequence parallelism." ,
)
2025-12-10 16:38:05 -10:00
return parser . parse_args ()
def main ():
args = parse_args ()
2026-01-28 22:12:52 +08:00
generator = torch . Generator ( device = current_omni_platform . device_type ) . manual_seed ( args . seed )
2025-12-10 16:38:05 -10:00
2026-01-28 20:07:50 +08:00
# Wan2.2 cache-dit tuning (from cache-dit examples and cache_alignment).
cache_config = None
if args . cache_backend == "cache_dit" :
cache_config = {
# DBCache parameters [cache-dit only]
"Fn_compute_blocks" : 1 , # Optimized for single-transformer models
"Bn_compute_blocks" : 0 , # Number of backward compute blocks
"max_warmup_steps" : 4 , # Maximum warmup steps (works for few-step models)
"max_cached_steps" : 20 ,
"residual_diff_threshold" : 0.24 , # Higher threshold for more aggressive caching
"max_continuous_cached_steps" : 3 , # Limit to prevent precision degradation
# TaylorSeer parameters [cache-dit only]
"enable_taylorseer" : False , # Disabled by default (not suitable for few-step models)
"taylorseer_order" : 1 , # TaylorSeer polynomial order
# SCM (Step Computation Masking) parameters [cache-dit only]
"scm_steps_mask_policy" : None , # SCM mask policy: None (disabled), "slow", "medium", "fast", "ultra"
"scm_steps_policy" : "dynamic" , # SCM steps policy: "dynamic" or "static"
}
2026-01-27 11:33:38 +08:00
# Configure parallel settings (only SP is supported for Wan)
# Note: cfg_parallel and tensor_parallel are not implemented for Wan models
parallel_config = DiffusionParallelConfig (
ulysses_degree = args . ulysses_degree ,
ring_degree = args . ring_degree ,
)
2026-01-20 11:35:48 +08:00
# Check if profiling is requested via environment variable
profiler_enabled = bool ( os . getenv ( "VLLM_TORCH_PROFILER_DIR" ))
2025-12-10 16:38:05 -10:00
omni = Omni (
model = args . model ,
2026-01-28 22:12:52 +08:00
vae_use_slicing = args . vae_use_slicing ,
vae_use_tiling = args . vae_use_tiling ,
2025-12-10 16:38:05 -10:00
boundary_ratio = args . boundary_ratio ,
flow_shift = args . flow_shift ,
2026-01-28 20:07:50 +08:00
cache_backend = args . cache_backend ,
cache_config = cache_config ,
enable_cache_dit_summary = args . enable_cache_dit_summary ,
2026-01-23 22:43:00 +08:00
enable_cpu_offload = args . enable_cpu_offload ,
2026-01-27 11:33:38 +08:00
parallel_config = parallel_config ,
enforce_eager = args . enforce_eager ,
2025-12-10 16:38:05 -10:00
)
2026-01-20 11:35:48 +08:00
if profiler_enabled :
print ( "[Profiler] Starting profiling..." )
omni . start_profile ()
2026-01-27 11:33:38 +08:00
# Print generation configuration
print ( f " \n { '=' * 60 } " )
print ( "Generation Configuration:" )
print ( f " Model: { args . model } " )
print ( f " Inference steps: { args . num_inference_steps } " )
print ( f " Frames: { args . num_frames } " )
print ( f " Parallel configuration: ulysses_degree= { args . ulysses_degree } , ring_degree= { args . ring_degree } " )
print ( f " Video size: { args . width } x { args . height } " )
print ( f " { '=' * 60 } \n " )
generation_start = time . perf_counter ()
2025-12-10 16:38:05 -10:00
frames = omni . generate (
2026-01-28 16:07:42 +08:00
{
"prompt" : args . prompt ,
"negative_prompt" : args . negative_prompt ,
},
OmniDiffusionSamplingParams (
height = args . height ,
width = args . width ,
generator = generator ,
guidance_scale = args . guidance_scale ,
guidance_scale_2 = args . guidance_scale_high ,
num_inference_steps = args . num_inference_steps ,
num_frames = args . num_frames ,
),
2025-12-10 16:38:05 -10:00
)
2026-01-27 11:33:38 +08:00
generation_end = time . perf_counter ()
generation_time = generation_end - generation_start
# Print profiling results
print ( f "Total generation time: { generation_time : .4f } seconds ( { generation_time * 1000 : .2f } ms)" )
2025-12-10 16:38:05 -10:00
2025-12-31 22:02:04 +08:00
# Extract video frames from OmniRequestOutput
if isinstance ( frames , list ) and len ( frames ) > 0 :
first_item = frames [ 0 ]
# Check if it's an OmniRequestOutput
if hasattr ( first_item , "final_output_type" ):
if first_item . final_output_type != "image" :
raise ValueError (
f "Unexpected output type ' { first_item . final_output_type } ', expected 'image' for video generation."
)
# Pipeline mode: extract from nested request_output
if hasattr ( first_item , "is_pipeline_output" ) and first_item . is_pipeline_output :
if isinstance ( first_item . request_output , list ) and len ( first_item . request_output ) > 0 :
inner_output = first_item . request_output [ 0 ]
if isinstance ( inner_output , OmniRequestOutput ) and hasattr ( inner_output , "images" ):
frames = inner_output . images [ 0 ] if inner_output . images else None
if frames is None :
raise ValueError ( "No video frames found in output." )
# Diffusion mode: use direct images field
elif hasattr ( first_item , "images" ) and first_item . images :
frames = first_item . images
else :
raise ValueError ( "No video frames found in OmniRequestOutput." )
2025-12-10 16:38:05 -10:00
output_path = Path ( args . output )
output_path . parent . mkdir ( parents = True , exist_ok = True )
try :
from diffusers.utils import export_to_video
except ImportError :
raise ImportError ( "diffusers is required for export_to_video." )
# frames may be np.ndarray (preferred) or torch.Tensor
# export_to_video expects a list of frames with values in [0, 1]
if isinstance ( frames , torch . Tensor ):
video_tensor = frames . detach () . cpu ()
if video_tensor . dim () == 5 :
# [B, C, F, H, W] or [B, F, H, W, C]
if video_tensor . shape [ 1 ] in ( 3 , 4 ):
video_tensor = video_tensor [ 0 ] . permute ( 1 , 2 , 3 , 0 )
else :
video_tensor = video_tensor [ 0 ]
elif video_tensor . dim () == 4 and video_tensor . shape [ 0 ] in ( 3 , 4 ):
video_tensor = video_tensor . permute ( 1 , 2 , 3 , 0 )
# If float, assume [-1,1] and normalize to [0,1]
if video_tensor . is_floating_point ():
video_tensor = video_tensor . clamp ( - 1 , 1 ) * 0.5 + 0.5
video_array = video_tensor . float () . numpy ()
else :
video_array = frames
if hasattr ( video_array , "shape" ) and video_array . ndim == 5 :
video_array = video_array [ 0 ]
# Convert 4D array (frames, H, W, C) to list of frames for export_to_video
if isinstance ( video_array , np . ndarray ) and video_array . ndim == 4 :
video_array = list ( video_array )
export_to_video ( video_array , str ( output_path ), fps = args . fps )
print ( f "Saved generated video to { output_path } " )
2026-01-20 11:35:48 +08:00
if profiler_enabled :
print ( " \n [Profiler] Stopping profiler and collecting results..." )
profile_results = omni . stop_profile ()
if profile_results and isinstance ( profile_results , dict ):
traces = profile_results . get ( "traces" , [])
print ( " \n " + "=" * 60 )
print ( "PROFILING RESULTS:" )
for rank , trace in enumerate ( traces ):
print ( f " \n Rank { rank } :" )
if trace :
print ( f " • Trace: { trace } " )
if not traces :
print ( " No traces collected." )
print ( "=" * 60 )
else :
print ( "[Profiler] No valid profiling data returned." )
2025-12-10 16:38:05 -10:00
if __name__ == "__main__" :
main ()