Uh oh!
There was an error while loading. Please reload this page.
[Modular]improve loop block - #14159
Conversation
…tate scopes Adds a loop composite whose sub-blocks are ordinary state blocks, so loops can nest and compose freely (e.g. an autoregressive chunk loop containing a timestep denoise loop). Loop variables like the current timestep are provided through a loop-local scope on PipelineState (`loop_scope()` / `set_local`): they resolve sub-blocks' declared inputs while the loop runs and are discarded when it exits, so they never surface as pipeline inputs. Sub-blocks declare everything they consume; declared outputs persist as usual. The loop block declares its own surface symmetrically to LoopSequentialPipelineBlocks: loop_inputs, loop_locals (names it provides via the scope), loop_intermediate_outputs, loop_expected_components/configs. Subclasses hand-write `__call__` around `loop_step()`, same idiom as leaf blocks around `get_block_state`. Ports the flux2 denoise loops (flux2, klein, klein-base) as the reference example, moves `progress_bar` to the ModularPipelineBlocks base, and treats the new class as a leaf in workflow traversal like LoopSequential. Adds structure/execution/nesting tests modeled on the helios chunk-loop use case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HuggingFaceDocBuilderDev
commented
Jul 10, 2026
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. |
Loop variables (i, t, k, ...) now ride the call signature: leaf sub-blocks of an IterativePipelineBlocks accept them after (components, state), the loop declares their names in `loop_variables`, and `loop_step` validates every leaf's signature against it before the first iteration. Assembled sub-blocks (nested loops, sequential/conditional groups) are called with the regular (components, state) interface and pass their own loop variables to their own sub-blocks. This removes the PipelineState scope machinery entirely — PipelineState and get/set_block_state are unchanged from main — and loop sub-blocks keep the familiar LoopSequentialPipelineBlocks authoring style, now with full composability. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove the composite exemption in loop_step: every sub-block of an IterativePipelineBlocks — including a nested loop — must accept the loop variables after (components, state), validated before the first iteration. A nested loop accepts the outer variables in its hand-written __call__ (ignoring or forwarding them) and passes its own loop_variables to its own sub-blocks. Plain Sequential/Conditional groups are not supported as loop sub-blocks (flatten instead). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The abstract __call__ placeholder now accepts **kwargs and the docstring shows the nested case: a loop nested inside another accepts the outer loop's variables in its hand-written __call__. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ModularPipelineBlocks now defines an abstract __call__ raising a clear NotImplementedError. Loop steps get their own base class, ModularLoopPipelineBlocks, whose only difference is the __call__ contract (accepts the enclosing loop's variables after (components, state)). IterativePipelineBlocks validates at construction that every sub-block is a ModularLoopPipelineBlocks or a nested IterativePipelineBlocks, in addition to the signature validation before the first iteration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove loop_inputs / loop_intermediate_outputs / loop_expected_components / loop_expected_configs from IterativePipelineBlocks — when the loop logic in __call__ consumes inputs or components beyond what sub-blocks declare, the subclass overrides the aggregated inputs / expected_components properties directly (see Flux2DenoiseLoopWrapper). Sub-block validation (type + loop-variable signature) now runs at construction (__init__ and from_blocks_dict) instead of lazily in loop_step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… every loop iteration - `stream(components, state)` generator on every block type; leaves yield nothing, composites re-yield sub-block events with the sub-block name prepended to `event.path` - `IterativePipelineBlocks`: streaming is opt-in — `__call__`/`loop_step` unchanged; a loop that supports it also implements `stream` as a generator over the new `stream_step`; base `stream` raises a clear NotImplementedError - `StreamEvent(path, block, loop_kwargs, state)` dataclass, exported - `supports_streaming` property on blocks (legacy `LoopSequentialPipelineBlocks` sets it to False; to be deprecated once all pipelines are ported) - `ModularPipeline.stream(state=None, **kwargs)` — same seeding as `__call__`, no_grad held only while blocks run; error logging mirrors `__call__` at every level - flux2: `Flux2DenoiseLoopWrapper.stream` next to its `__call__` - tests: `test_stream_matches_call` on `ModularPipelineTesterMixin` (auto-skips when blocks don't support streaming) - docs: Streaming section on the ModularPipeline page; regenerate dummy_pt_objects Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conflict resolutions: - flux2/denoise.py: keep this branch's Flux2LoopAfterDenoiser — main's #14278 removed its unused inputs/intermediate_inputs, but the class is rewritten here as a ModularLoopPipelineBlocks that declares real inputs (latents, noise_pred) - test_modular_pipelines_common.py: deleted on main by #14444 (tests split up); test_stream_matches_call moved to its new home on ModularPipelineTesterMixin in tests/modular_pipelines/testing_utils/common.py Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- WanAnimate2SegmentLoopWrapper is an IterativePipelineBlocks (loop variable `k`); the per-segment steps become ModularLoopPipelineBlocks operating on the shared PipelineState - the hand-rolled timestep loop in WanAnimate2SegmentDenoiseInner becomes a nested loop: WanAnimate2LoopDenoiser + WanAnimate2LoopAfterDenoiser under WanAnimate2DenoiseLoopWrapper (loop variables `i`, `t`) - loop-carried state made explicit: `out_frames` is a declared input on the prev-frames step but filtered from the wrapper's aggregated inputs; `segment_frames` accumulation moves from the decode step into the segment loop's own logic - both loops implement `stream`: events per denoise step and per segment, so test_stream_matches_call now runs for wan-animate-2 (stream == call) - regenerate stale flux2 modular_blocks docstrings; doc-builder reflow in modular_pipeline.py Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- the 7 loop steps become ModularLoopPipelineBlocks operating on the shared PipelineState; the intra-iteration dataflow is now declared (before-denoiser outputs latent_model_input/timesteps, denoiser outputs noise_pred_video/ noise_pred_audio, after-denoisers output latents/audio_latents) and stays satisfied within the loop, so pipeline inputs are unchanged - LTX2DenoiseLoopWrapper is an IterativePipelineBlocks (loop variables `i`, `t`) and implements `stream`, so LTX-2 and LTX-2.5 stream with both video and audio latents live in every event; test_stream_matches_call now runs for all 8 ltx2/ltx25 testers - regenerate ltx2 modular_blocks docstrings (timesteps correctly optional at the core-step level, produced by set_timesteps) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ck state to the loop logic Re-add `loop_inputs` / `loop_intermediate_outputs` on IterativePipelineBlocks. The base class merges them into the aggregated `inputs` / `intermediate_outputs`, and `get_block_state` / `set_block_state` on a loop block now read and write only these — sub-block values live in the pipeline state, not in the loop's (pre-loop) snapshot. This lets a loop write its own outputs through `set_block_state` like a leaf, and removes the hand-written inputs merge from every wrapper. - flux2 / ltx2 / wan-animate-2 wrappers and test fixtures move their loop inputs to `loop_inputs`; the wan segment loop declares `segment_frames` in `loop_intermediate_outputs` and writes it via `set_block_state`. - wan-animate-2: `out_frames` (loop-carried, previous segment's frames) is seeded as `None` by the prepare-segments step instead of being filtered out of the loop's inputs, so the sequential aggregation hides it at pipeline level on its own. - tests: loop outputs aggregate; loop block state holds only `loop_inputs`; a loop output written through `set_block_state` leaves sub-block outputs untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
…op-blocks # Conflicts: # src/diffusers/modular_pipelines/ltx2/denoise.py
…oop steps take only the loop variables they use (plus **kwargs), update docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS
…ourself section Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS
…ce/diffusers into refactor-iterative-loop-blocks
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS
This PR adds
IterativePipelineblockswhich is an improved version ofLoopSequentialPipelineBlocks:(1) loop steps receive the full
PipelineStateand declare their ownblock_statelike regular blocks. Previously, the loop wrapper extracted one flattenedBlockStatebefore the loop, and all steps share that(2)it can be nested and composed: e.g. you can nest a
IterativePipelineblocksinside another to created a nested loop (autoregressive chunk loop)(3) added first-class streaming support: for example, you can render a preview of the latent at each step like this (also see the complete space demo here
For more details please refer to the updated docs in the PR
Notes
example to run a real-time world model (under #14539)