Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/diffusers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,8 @@
"WanImage2VideoAutoBlocks",
"WanImage2VideoModularPipeline",
"WanModularPipeline",
"WanVideoToVideoBlocks",
"WanVideoToVideoModularPipeline",
"ZImageAutoBlocks",
"ZImageModularPipeline",
]
Expand Down Expand Up @@ -1435,6 +1437,8 @@
WanImage2VideoAutoBlocks,
WanImage2VideoModularPipeline,
WanModularPipeline,
WanVideoToVideoBlocks,
WanVideoToVideoModularPipeline,
ZImageAutoBlocks,
ZImageModularPipeline,
)
Expand Down
4 changes: 4 additions & 0 deletions src/diffusers/modular_pipelines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@
"WanBlocks",
"Wan22Blocks",
"WanImage2VideoAutoBlocks",
"WanVideoToVideoBlocks",
"Wan22Image2VideoBlocks",
"Wan22VaceBlocks",
"WanModularPipeline",
"Wan22ModularPipeline",
"WanImage2VideoModularPipeline",
"WanVideoToVideoModularPipeline",
"Wan22Image2VideoModularPipeline",
"Wan22VaceModularPipeline",
]
Expand Down Expand Up @@ -234,6 +236,8 @@
WanImage2VideoAutoBlocks,
WanImage2VideoModularPipeline,
WanModularPipeline,
WanVideoToVideoBlocks,
WanVideoToVideoModularPipeline,
)
from .wan_animate_2 import (
WanAnimate2Blocks,
Expand Down
5 changes: 5 additions & 0 deletions src/diffusers/modular_pipelines/modular_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ def _wan_i2v_map_fn(config_dict=None):
return "WanImage2VideoModularPipeline"


def _wan_v2v_map_fn(config_dict=None):
return "WanVideoToVideoModularPipeline"


def _krea2_map_fn(config_dict=None):
if config_dict is None:
return "Krea2ModularPipeline"
Expand Down Expand Up @@ -135,6 +139,7 @@ def _helios_pyramid_map_fn(config_dict=None):
("wan-animate-2", _create_default_map_fn("WanAnimate2ModularPipeline")),
("wan-animate-2-distilled", _create_default_map_fn("WanAnimate2DistilledModularPipeline")),
("wan-i2v", _wan_i2v_map_fn),
("wan-v2v", _wan_v2v_map_fn),
("wan-vace", _create_default_map_fn("Wan22VaceModularPipeline")),
("flux", _create_default_map_fn("FluxModularPipeline")),
("flux-kontext", _create_default_map_fn("FluxKontextModularPipeline")),
Expand Down
4 changes: 4 additions & 0 deletions src/diffusers/modular_pipelines/wan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
_import_structure["modular_blocks_wan22"] = ["Wan22Blocks"]
_import_structure["modular_blocks_wan22_i2v"] = ["Wan22Image2VideoBlocks"]
_import_structure["modular_blocks_wan_i2v"] = ["WanImage2VideoAutoBlocks"]
_import_structure["modular_blocks_wan_v2v"] = ["WanVideoToVideoBlocks"]
_import_structure["modular_blocks_wan_vace"] = ["Wan22VaceBlocks"]
_import_structure["modular_pipeline"] = [
"Wan22Image2VideoModularPipeline",
"Wan22ModularPipeline",
"Wan22VaceModularPipeline",
"WanImage2VideoModularPipeline",
"WanModularPipeline",
"WanVideoToVideoModularPipeline",
]

if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
Expand All @@ -45,13 +47,15 @@
from .modular_blocks_wan22 import Wan22Blocks
from .modular_blocks_wan22_i2v import Wan22Image2VideoBlocks
from .modular_blocks_wan_i2v import WanImage2VideoAutoBlocks
from .modular_blocks_wan_v2v import WanVideoToVideoBlocks
from .modular_blocks_wan_vace import Wan22VaceBlocks
from .modular_pipeline import (
Wan22Image2VideoModularPipeline,
Wan22ModularPipeline,
Wan22VaceModularPipeline,
WanImage2VideoModularPipeline,
WanModularPipeline,
WanVideoToVideoModularPipeline,
)
else:
import sys
Expand Down
144 changes: 143 additions & 1 deletion src/diffusers/modular_pipelines/wan/before_denoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def expected_components(self) -> list[ComponentSpec]:
@property
def inputs(self) -> list[InputParam]:
return [
InputParam("num_videos_per_prompt", default=1),
InputParam("num_videos_per_prompt", default=1, description="The number of videos to generate per prompt."),
InputParam(
"prompt_embeds",
required=True,
Expand Down Expand Up @@ -595,3 +595,145 @@ def __call__(self, components: WanModularPipeline, state: PipelineState) -> Pipe
self.set_block_state(state, block_state)

return components, state


class WanVideoToVideoSetTimestepsStep(ModularPipelineBlocks):
model_name = "wan-v2v"

@property
def expected_components(self) -> list[ComponentSpec]:
return [
ComponentSpec("scheduler", UniPCMultistepScheduler),
]

@property
def description(self) -> str:
return "Set the scheduler timesteps and select the video-to-video denoising schedule from strength."

@property
def inputs(self) -> list[InputParam]:
return [
InputParam("num_inference_steps", default=50, description="The number of denoising steps."),
InputParam("timesteps", description="Custom timesteps for the denoising process."),
InputParam("sigmas", description="Custom sigmas for the denoising process."),
InputParam(
"strength",
default=0.8,
type_hint=float,
description="The amount of noise added to the input video latents.",
),
InputParam("batch_size", required=True, type_hint=int),
InputParam(
"num_videos_per_prompt",
default=1,
type_hint=int,
description="The number of videos to generate per prompt.",
),
]

@property
def intermediate_outputs(self) -> list[OutputParam]:
return [
OutputParam("timesteps", type_hint=torch.Tensor, description="The selected denoising timesteps."),
OutputParam(
"num_inference_steps",
type_hint=int,
description="The number of selected denoising steps.",
),
OutputParam(
"latent_timestep",
type_hint=torch.Tensor,
description="The timestep used to add noise to the input video latents.",
),
]

@torch.no_grad()
def __call__(self, components: WanModularPipeline, state: PipelineState) -> PipelineState:
block_state = self.get_block_state(state)
device = components._execution_device

timesteps, num_inference_steps = retrieve_timesteps(
components.scheduler,
block_state.num_inference_steps,
device,
block_state.timesteps,
block_state.sigmas,
)
init_timestep = min(int(num_inference_steps * block_state.strength), num_inference_steps)
t_start = max(num_inference_steps - init_timestep, 0)
block_state.timesteps = timesteps[t_start * components.scheduler.order :]
block_state.num_inference_steps = num_inference_steps - t_start
block_state.latent_timestep = block_state.timesteps[:1].repeat(
block_state.batch_size * block_state.num_videos_per_prompt
)

self.set_block_state(state, block_state)
return components, state


class WanVideoToVideoPrepareLatentsStep(ModularPipelineBlocks):
model_name = "wan-v2v"

@property
def expected_components(self) -> list[ComponentSpec]:
return [
ComponentSpec("scheduler", UniPCMultistepScheduler),
]

@property
def description(self) -> str:
return "Add noise at the selected timestep to the encoded input video latents."

@property
def inputs(self) -> list[InputParam]:
return [
InputParam(
"video_latents",
required=True,
type_hint=torch.Tensor,
description="Normalized VAE latents of the input video.",
),
InputParam("latent_timestep", required=True, type_hint=torch.Tensor),
InputParam(
"latents",
type_hint=torch.Tensor | None,
description="Pre-generated noisy video latents to use instead of adding noise to the input video.",
),
InputParam("generator", description="Torch generator for deterministic noise generation."),
]

@property
def intermediate_outputs(self) -> list[OutputParam]:
return [
OutputParam(
"latents",
type_hint=torch.Tensor,
description="Noisy video latents used to start the denoising process.",
)
]

@torch.no_grad()
def __call__(self, components: WanModularPipeline, state: PipelineState) -> PipelineState:
block_state = self.get_block_state(state)
device = components._execution_device

if block_state.latents is None:
noise = randn_tensor(
block_state.video_latents.shape,
generator=block_state.generator,
device=device,
dtype=torch.float32,
)
if hasattr(components.scheduler, "add_noise"):
block_state.latents = components.scheduler.add_noise(
block_state.video_latents, noise, block_state.latent_timestep
)
else:
block_state.latents = components.scheduler.scale_noise(
block_state.video_latents, block_state.latent_timestep, noise
)
else:
block_state.latents = block_state.latents.to(device)

self.set_block_state(state, block_state)
return components, state
2 changes: 1 addition & 1 deletion src/diffusers/modular_pipelines/wan/denoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def description(self) -> str:
@property
def inputs(self) -> list[tuple[str, Any]]:
inputs = [
InputParam("attention_kwargs"),
InputParam("attention_kwargs", description="Additional kwargs for attention processors."),
InputParam(
"num_inference_steps",
required=True,
Expand Down
74 changes: 71 additions & 3 deletions src/diffusers/modular_pipelines/wan/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ def expected_components(self) -> list[ComponentSpec]:
@property
def inputs(self) -> list[InputParam]:
return [
InputParam("prompt"),
InputParam("negative_prompt"),
InputParam("max_sequence_length", default=512),
InputParam("prompt", description="The prompt or prompts to guide video generation."),
InputParam("negative_prompt", description="The prompt or prompts not to guide video generation."),
InputParam("max_sequence_length", default=512, description="Maximum sequence length for prompt encoding."),
]

@property
Expand Down Expand Up @@ -877,6 +877,74 @@ def __call__(self, components: WanModularPipeline, state: PipelineState) -> Pipe
return components, state


class WanVideoVaeEncoderStep(ModularPipelineBlocks):
model_name = "wan-v2v"

@property
def description(self) -> str:
return "Preprocess and encode the input video into normalized VAE latents for video-to-video generation."

@property
def expected_components(self) -> list[ComponentSpec]:
return [
ComponentSpec("vae", AutoencoderKLWan),
ComponentSpec(
"video_processor",
VideoProcessor,
config=FrozenDict({"vae_scale_factor": 8}),
default_creation_method="from_config",
),
]

@property
def inputs(self) -> list[InputParam]:
return [
InputParam("video", required=True, description="The input video to transform."),
InputParam("height", type_hint=int, description="The height in pixels of the generated video."),
InputParam("width", type_hint=int, description="The width in pixels of the generated video."),
InputParam("generator", description="Torch generator for deterministic latent generation."),
]

@property
def intermediate_outputs(self) -> list[OutputParam]:
return [
OutputParam(
"video_latents",
type_hint=torch.Tensor,
description="Normalized VAE latents of the input video.",
)
]

@staticmethod
def check_inputs(height: int, width: int):
if height % 16 != 0 or width % 16 != 0:
raise ValueError(f"`height` and `width` have to be divisible by 16 but are {height} and {width}.")

@torch.no_grad()
def __call__(self, components: WanModularPipeline, state: PipelineState) -> PipelineState:
block_state = self.get_block_state(state)

block_state.height = block_state.height or components.default_height
block_state.width = block_state.width or components.default_width
self.check_inputs(block_state.height, block_state.width)

device = components._execution_device
video = components.video_processor.preprocess_video(
block_state.video, height=block_state.height, width=block_state.width
).to(device=device, dtype=torch.float32)
block_state.video_latents = encode_vae_image(
video_tensor=video,
vae=components.vae,
generator=block_state.generator,
device=device,
dtype=components.vae.dtype,
latent_channels=components.num_channels_latents,
).to(torch.float32)

self.set_block_state(state, block_state)
return components, state


class WanPrepareFirstFrameLatentsStep(ModularPipelineBlocks):
model_name = "wan"

Expand Down
Loading
Loading