Repository navigation
[modular] SequentialPipelineBlocks gets required wrong in both inputs and required_inputs #15009
Description
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
sayakpaul commentedon Oct 12, 2026
Just for my own understanding:
core.inputs— input specifications, each with a name and a .required flag.core.required_inputs— the set of names the block considers mandatory.