Skip to content

[bugfix] Wan I2V: CLIP image conditioning silently dropped when passed as a tensor during training - #1673

Merged
SolitaryThinker merged 1 commit into
mainfrom
shao/wan-i2v-clip-image-cond-dropped
Aug 5, 2026
Merged

[bugfix] Wan I2V: CLIP image conditioning silently dropped when passed as a tensor during training#1673
SolitaryThinker merged 1 commit into
mainfrom
shao/wan-i2v-clip-image-cond-dropped

Conversation

@shaoxiongduan

Copy link
Copy Markdown
Collaborator

Purpose

WanTransformer3DModel.forward (and the causal/dreamx/lingbotworld variants) unwrapped encoder_hidden_states_image only when it was a list, and set it to None otherwise — discarding a bare tensor even though the signature accepts torch.Tensor | list[torch.Tensor] | None.

The inference pipelines store image_embeds as a list (image_encoding.py), so they were unaffected. Every I2V training pipeline passes the CLIP features directly as a tensor (wan_i2v_training_pipeline.py:129, wan_i2v_distillation_pipeline.py:170, self_forcing_distillation_pipeline.py), so those trained with no image cross-attention at all, silently and without error.

Models with image_dim=None (Wan2.2 I2V/TI2V, LingBotWorld) build no image embedder and are unaffected either way.

Changes

Unwrap a list, leave a tensor alone. Matches the handling of encoder_hidden_states two lines above, and the form already used in matrixgame2/causal_model.py and matrixgame2/model.py.

Touches wanvideo.py, causal_wanvideo.py (2 sites), dreamx_world.py, lingbotworld/model.py.

Test Plan

Wan2.1-I2V-14B-720P, one image, 480x832, 121 frames, 30 steps, guidance 3.0, seed 1000.

The inference stack passes a list, so it never hits the bug. To exercise the training convention end-to-end, denoising.py was temporarily patched to hand the DiT a bare tensor — this patch is not part of the PR:

# DenoisingStage.forward, right after:
#   image_embeds = [image_embed.to(target_dtype) for image_embed in image_embeds]
image_embeds = image_embeds[0]   # training convention: bare tensor

Then generate twice on the same node/seed: once with wanvideo.py from main, once with this branch.

Test Results

std = per-video pixel standard deviation (contrast/detail proxy).

mean std last-frame std
before — tensor silently nulled 120.1 60.5 57.5
after — tensor kept 80.7 63.7 66.3
unmodified inference (list, reference) 80.7 63.8 66.8

After the fix, passing a tensor reproduces the untouched list path exactly. Before it, the output is brighter and washed out, degrading over the clip.

Same fix measured through a training-side validation callback (Wan2.1-I2V-14B, stock weights), against ground truth:

clip before after GT
720p, clip A 19.3 70.3 64.7
720p, clip B 26.8 57.2 58.4
480p, clip A 32.0 70.3 64.6

Checklist

  • pre-commit run — all four files live under fastvideo/models/, which the config's global exclude skips, so no hooks apply to this diff
  • I added or updated tests for my changes — none added. A regression test asserting forward(..., encoder_hidden_states_image=tensor) != forward(..., encoder_hidden_states_image=None) would prevent this from recurring; happy to add if wanted.
  • I updated documentation if needed — n/a
  • I considered GPU memory impact — none

For model/pipeline changes, also check:

  • I verified SSIM regression tests pass — not run locally; inference output is unchanged (list path untouched), so SSIM should be unaffected
  • I updated the support matrix — n/a

WanTransformer3DModel.forward (and the causal/dreamx/lingbotworld variants)
unwrapped encoder_hidden_states_image only when it was a list, and set it to
None otherwise -- discarding a bare tensor even though the signature accepts
torch.Tensor | list[torch.Tensor] | None.

The inference pipelines store image_embeds as a list (image_encoding.py), so
they were unaffected. Every I2V *training* pipeline passes the CLIP features
directly as a tensor (wan_i2v_training_pipeline.py:129,
wan_i2v_distillation_pipeline.py:170, self_forcing_distillation_pipeline.py),
so those trained with no image cross-attention at all, silently and without
error.

Unwrap a list, leave a tensor alone. Matches the handling of
encoder_hidden_states two lines above, and the form already used in
matrixgame2/causal_model.py and matrixgame2/model.py.

Models with image_dim=None (Wan2.2 I2V/TI2V, LingBotWorld) build no image
embedder and are unaffected either way.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@mergify mergify Bot added type: bugfix Bug fix scope: model Model architecture (DiTs, encoders, VAEs) labels Aug 3, 2026
@mergify

mergify Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 1 of 1 protections blocking · waiting on 👀 reviews and 🤖 CI

Protection Waiting on
🔴 PR merge requirements 👀 reviews and 🤖 CI

🔴 PR merge requirements

Waiting for

  • #approved-reviews-by>=1
  • check-success=fastcheck-passed
  • check-success=full-suite-passed
This rule is failing.
  • #approved-reviews-by>=1
  • check-success=fastcheck-passed
  • check-success=full-suite-passed
  • check-success~=pre-commit
  • title~=(?i)^\[(feat|feature|bugfix|fix|refactor|perf|ci|doc|docs|misc|chore|kernel|new.?model|skill|skills|infra)\]

@shaoxiongduan shaoxiongduan changed the title [bugfix] Wan I2V: CLIP image conditioning silently dropped when passed as a tensor [bugfix] Wan I2V: CLIP image conditioning silently dropped when passed as a tensor during training Aug 3, 2026
@shaoxiongduan

Copy link
Copy Markdown
Collaborator Author

/merge

@github-actions github-actions Bot added the ready PR is ready to merge label Aug 5, 2026
@SolitaryThinker
SolitaryThinker merged commit 9f1e7c1 into main Aug 5, 2026
5 of 8 checks passed
@SolitaryThinker
SolitaryThinker deleted the shao/wan-i2v-clip-image-cond-dropped branch August 5, 2026 08:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready PR is ready to merge scope: model Model architecture (DiTs, encoders, VAEs) type: bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants