Skip to content

Wan I2V expand_timesteps: why hard-clamp only first frame (no last-frame clamp)? #13167

Open

Description

@emirks

I have a question on expand_timesteps option within WanImageToVideoPipeline. I see that the mechanism hard-clamps only the first frame. For FLF (first+last) conditioning, is there a reason we can’t (or shouldn’t) apply the same mechanism to both endpoints?

The related code is in:

  • diffusers/src/diffusers/pipelines/wan/pipeline_wan_i2v.py

In prepare_latents, when expand_timesteps is enabled, video_condition ignores last_image and first_frame_mask is defined only for frame 0:

# prepare_latents
if self.config.expand_timesteps:
    video_condition = image

...
if self.config.expand_timesteps:
    first_frame_mask = torch.ones(1, 1, num_latent_frames, latent_height, latent_width, ...)
    first_frame_mask[:, :, 0] = 0
    return latents, latent_condition, first_frame_mask

In the denoising loop, the clamp/mix uses only first_frame_mask:

if self.config.expand_timesteps:
    latent_model_input = (1 - first_frame_mask) * condition + first_frame_mask * latents
    temp_ts = (first_frame_mask[0][0][:, ::2, ::2] * t).flatten()

Question
Is it a limitation of the Wan2.2 I2V checkpoint training (only first-frame conditioning)?
Or a design choice because per-token timestep masking doesn’t extend cleanly to a last-frame constraint?

I see that the corresponding part was written by @yiyixuxu, thanks!

Activity

  1. chinmayirm commented on Sep 22, 2026

    @chinmayirm

    Hi @emirks, the first-frame-only clamp is what that branch is doing, and the last frame is handled on the other path.

    Root cause

    prepare_latents forks on self.config.expand_timesteps.

    When it is true, video_condition is only image, last_image is not encoded, and first_frame_mask[:, :, 0] = 0. The denoising loop then builds latent_model_input from that mask and sets per-token timesteps with temp_ts = (first_frame_mask[0][0][:, ::2, ::2] * t). Frame 0 stays at timestep 0. Every other frame, including the last, stays at t. That is the image-to-video schedule this checkpoint was trained with.

    When it is false and last_image is set, the condition is [first, zeros, last] and the mask keeps both endpoints. First-and-last-frame conditioning already goes through that branch.

    Clamping the last frame inside expand_timesteps would change the timestep layout of a model that was not trained that way. The two schedules are not interchangeable.

    Proposed approach

    Leave expand_timesteps as first-frame-only. A docstring on prepare_latents should say that this flag ignores last_image, and that first-and-last-frame conditioning is the expand_timesteps=False path. I would not add a last-frame clamp to this branch.

    I would be glad to open a PR for that docstring if it would help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions