Skip to content

[modular] SequentialPipelineBlocks gets required wrong in both inputs and required_inputs #15009

Open

Description

@yiyixuxu

What happens

A SequentialPipelineBlocks has two views of its inputs, and each gets the required flag wrong in a different way.

1. required_inputs keeps names that an earlier sub-block produces. In Flux2CoreDenoiseStep, prepare_latents declares latents as optional and produces it, and set_timesteps does the same for timesteps and num_inference_steps. The denoise loop then requires all three. inputs says optional, required_inputs says required:

from diffusers.modular_pipelines.flux2.modular_blocks_flux2 import Flux2CoreDenoiseStep

core = Flux2CoreDenoiseStep()
print([(i.name, i.required) for i in core.inputs if i.name in ("latents", "timesteps", "num_inference_steps")])
print(sorted(core.required_inputs))
[('latents', False), ('num_inference_steps', False), ('timesteps', False)]
['batch_size', 'guidance', 'latent_ids', 'latents', 'num_inference_steps', 'prompt_embeds', 'timesteps', 'txt_ids']

Nothing produced those values from the user's point of view either way, but the first view is the right one here: the block fills them in itself.

2. inputs keeps the first declaration's flag even when a later sub-block requires the value and nobody produces it. In StableDiffusion3I2ICoreDenoiseStep, additional_inputs declares image_latents as optional, prepare_img2img_latents requires it, and no sub-block produces it. The user has to pass it, so it is required. inputs says optional, required_inputs says required:

from diffusers.modular_pipelines.stable_diffusion_3.modular_blocks_stable_diffusion_3 import StableDiffusion3I2ICoreDenoiseStep

core = StableDiffusion3I2ICoreDenoiseStep()
print([(i.name, i.required) for i in core.inputs if i.name == "image_latents"])
print("image_latents" in core.required_inputs)
[('image_latents', False)]
True

Here the second view is the right one.

LoopSequentialPipelineBlocks has the same two problems.

Why

inputs walks the sub-blocks in order, skips a name once an earlier sub-block produced it, and otherwise keeps the first declaration as is. It never looks at what later sub-blocks require.

required_inputs is computed separately, as a plain union of every sub-block's required_inputs. It never looks at what earlier sub-blocks produce.

Effect

The docstrings are rendered from inputs, so they are wrong in case 2 directly. In case 1 the wrong flag reaches the docstring through blocks that rebuild required from their sub-blocks' required_inputs, which is why the *optional* marker is missing from latents, timesteps and num_inference_steps in the Flux2 auto blocks and in Anima, Cosmos, Helios, Hunyuan Video 1.5, LTX, LTX-2, MiniMax H3, QwenImage, SD3, SDXL, Wan and Z-Image. Anything that reads required_inputs directly, such as the Mellon node config, gets case 1 wrong too.

Fix

Decide required in the single ordered walk: a name is required if some sub-block requires it, no earlier sub-block produced it, and no earlier declaration gives it a default. Derive required_inputs from that, so there is only one answer. PR to follow.


Posted by Claude Code on behalf of @yiyixuxu, who reviewed and approved this text.

Activity

  1. self-assigned this
    on Oct 11, 2026
  2. sayakpaul commented on Oct 12, 2026

    @sayakpaul
    Member

    Just for my own understanding:

    • First view: core.inputs — input specifications, each with a name and a .required flag.
    • Second view: core.required_inputs — the set of names the block considers mandatory.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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