Skip to content

move unpack out of the flux, krea2 and ltx decoders into the core denoise groups - #14737

Merged
yiyixuxu merged 13 commits into
huggingface:mainfrom
akshan-main:modular-latents-form
Oct 20, 2026
Merged

yiyixuxu merged 13 commits into
huggingface:mainfrom
akshan-main:modular-latents-form

Conversation

@akshan-main

@akshan-main akshan-main commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #14730.

Adds a test to the modular tester mixin that checks the shape of the latents each pipeline leaves in the state after denoising, and every tester now declares expected_latents_shape. Running it across all families catches the three from the issue, plus ltx2 (being moved in #14612, so it's declared at its current packed shape until then) and wan_animate_2, which decodes inside its segment loop and never leaves latents behind, so it opts out with latents_output_name = None.

For flux, krea2 and ltx the unpack now happens at the end of the core denoise group, same shape as Flux2UnpackLatentsStep. The decoders just denormalize and decode. They still accept packed latents with a deprecation warning, which is why height/width (and num_frames for ltx) are still on them, and there's a test per family for that path.

Outputs are identical to main on the test repos and on the real checkpoints (images in akshan-main/latents-form-verification). Loading LTX-Video with ModularPipeline.from_pretrained needs #14736. Also wrote the principle down in .ai/references/modular.md in a separate commit; feel free to drop it or change it.

Before submitting

Who can review?

@yiyixuxu

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@yiyixuxu yiyixuxu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks a lot! really nice work
I left some feedbacks

@@ -28,6 +28,64 @@


# auto_docstring

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh I think this #auto_docstring maker is in wrong place

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you update the description here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +48 to +49
"the VAE takes (still normalized). Closes the core denoise group, so the blocks that follow take the same "
"form the VAE produces and need no geometry inputs."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"the VAE takes (still normalized). Closes the core denoise group, so the blocks that follow take the same "
"form the VAE produces and need no geometry inputs."
"the VAE takes (still normalized)."

Comment on lines +56 to +57
"VAE takes (still normalized). Closes the core denoise group, so the blocks that follow take the same "
"form the VAE encoder produces and need no geometry inputs."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"VAE takes (still normalized). Closes the core denoise group, so the blocks that follow take the same "
"form the VAE encoder produces and need no geometry inputs."
"VAE takes (still normalized)."

type_hint=torch.Tensor,
description="The denoised latents from the denoising step, packed, of shape `[B, S, C]`.",
),
InputParam("height", default=1024),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

height/width should be required with no default

Comment on lines +49 to +50
"the VAE takes (still normalized). Closes the core denoise group, so the blocks that follow take the same "
"form the VAE encoder produces and need no geometry inputs."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"the VAE takes (still normalized). Closes the core denoise group, so the blocks that follow take the same "
"form the VAE encoder produces and need no geometry inputs."
"the VAE takes (still normalized)."

Comment on lines +107 to +108
# Still the packed `[B, S, C]` form: #14612 moves ltx2 to the VAE form and updates this.
expected_latents_shape = (1, 768, 4)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Still the packed `[B, S, C]` form: #14612 moves ltx2 to the VAE form and updates this.
expected_latents_shape = (1, 768, 4)

let's not give the wrong "expected_latents_shape" just so that the test pass - it's misleading

we can skip the test explicitly with something like this

  @pytest.mark.skip(reason="ltx2 still leaves packed `[B, S, C]` latents in the state; #14612 moves it to the VAE form.")
  def test_latents_output_in_canonical_form(self):
      pass

Comment on lines +393 to +394
if self.latents_output_name is None:
pytest.skip("This family decodes inside its denoise loop and leaves no latents in the state.")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.latents_output_name is None:
pytest.skip("This family decodes inside its denoise loop and leaves no latents in the state.")

the subclass can skip it with `@pytest.mark.skip(...)

Comment thread tests/modular_pipelines/ltx/test_modular_pipeline_ltx.py
@yiyixuxu
yiyixuxu requested a review from sayakpaul October 17, 2026 23:04
@yiyixuxu yiyixuxu added this to the Release 0.42.0 milestone Oct 17, 2026

@sayakpaul sayakpaul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the PR. I think it aims at a standardization that we have been discussing in other threads. Thank you!

Comment thread .ai/references/modular.md

Each should be applied and undone in mirrored pairs, and the core denoise group hands latents back in the same form it received them. The `latents` output of the denoise group should always be in one form: unpacked and still normalized, the same form its VAE encoder blocks produce. Whatever runs next (a decoder, a latent upsampler, a second denoise group) consumes that form, and no block outside the group needs `height`/`width`/`num_frames` just to interpret tokens.

The pattern: `encode -> norm -> pack -> denoise -> unpack -> denorm -> decode`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should provide a reference from the codebase here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +146 to +147
"packed latents",
"1.0.0",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can deprecate before. Like give it ~5 releases? I would also add a test to ensure the deprecation is actually raised (if not already added).

Comment on lines +61 to +62
InputParam.template("height", default=1024),
InputParam.template("width", default=1024),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pipeline_blocks_class = LTX25AutoBlocks
pretrained_model_name_or_path = LTX25_REPO_ID
# Still the packed `[B, S, C]` form: #14612 moves ltx2 to the VAE form and updates this.
expected_latents_shape = (1, 768, 4)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we explicitly skip this one too?

@yiyixuxu

Copy link
Copy Markdown
Collaborator

/diffusers-bot pytest tests/modular_pipelines/flux tests/modular_pipelines/krea2 tests/modular_pipelines/ltx -k test_decode_accepts_packed_latents_with_deprecation

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

❌ pytest tests/modular_pipelines/flux tests/modular_pipelines/krea2 tests/modular_pipelines/ltx -k test_decode_accepts_packed_latents_with_deprecation failed on GPU — view logs.

output_name = "images"
# State name of the latents the denoise group hands to the decoder. Chunked families that hand over a list of
# per-chunk latents (e.g. `latent_chunks`) are checked chunk by chunk. Families that decode inside their
# denoise loop and leave no latents in the state set this to `None`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you update the comment here? better to skip explicitly with a reason

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this
State name of the latents the denoise group hands to the decoder. Chunked families that hand over a list of per-chunk latents (e.g. latent_chunks) are checked chunk by chunk. A family that leaves no latents in the state, or not yet in its canonical form, skips test_latents_output_in_canonical_form with a reason.

decoder.to(torch_device)

# The 9-frame 32x32 dummy video is a `[1, 8, 5, 16, 16]` latent, packed to 1280 tokens of 8 channels.
packed_latents = torch.randn(1, 1280, 8, generator=self.get_generator())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
packed_latents = torch.randn(1, 1280, 8, generator=self.get_generator())
packed_latents = torch.randn(1, 1280, 8, generator=self.get_generator()).to(torch_device)

can you update the other 2 tests too? see https://github.com/huggingface/diffusers/actions/runs/39362689598/job/118432333376#step:8:58

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@yiyixuxu yiyixuxu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks a lot for working on this!

@yiyixuxu
yiyixuxu merged commit 6184688 into huggingface:main Oct 20, 2026
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[modular] move unpack out of the decoder blocks for flux, krea2 and ltx

4 participants