From 3be086bc2c30dc80c5a99779148308678df06922 Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Mon, 17 Aug 2026 21:10:58 +0000 Subject: [PATCH 01/10] drop the default of a required InputParam, with a warning Co-Authored-By: Claude Opus 5 --- .../modular_pipelines/cosmos/before_denoise.py | 4 ++-- src/diffusers/modular_pipelines/cosmos/denoise.py | 2 +- src/diffusers/modular_pipelines/krea2/denoise.py | 4 ++-- src/diffusers/modular_pipelines/ltx/before_denoise.py | 4 ++-- src/diffusers/modular_pipelines/ltx/decoders.py | 6 +----- src/diffusers/modular_pipelines/ltx/denoise.py | 10 +++++----- src/diffusers/modular_pipelines/ltx2/decoders.py | 4 ++-- src/diffusers/modular_pipelines/ltx2/denoise.py | 4 ++-- .../modular_pipelines/minimax_h3/before_denoise.py | 2 +- .../modular_pipelines/modular_pipeline_utils.py | 9 +++++++++ src/diffusers/modular_pipelines/qwenimage/denoise.py | 2 +- 11 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/diffusers/modular_pipelines/cosmos/before_denoise.py b/src/diffusers/modular_pipelines/cosmos/before_denoise.py index e6f9dbf05abb..689657d0878a 100644 --- a/src/diffusers/modular_pipelines/cosmos/before_denoise.py +++ b/src/diffusers/modular_pipelines/cosmos/before_denoise.py @@ -979,7 +979,7 @@ def expected_configs(self) -> list[ConfigSpec]: @property def inputs(self) -> list[InputParam]: return [ - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), ] @property @@ -1258,7 +1258,7 @@ def expected_components(self) -> list[ComponentSpec]: @property def inputs(self) -> list[InputParam]: - return [InputParam.template("num_inference_steps", required=True)] + return [InputParam.template("num_inference_steps")] @property def intermediate_outputs(self) -> list[OutputParam]: diff --git a/src/diffusers/modular_pipelines/cosmos/denoise.py b/src/diffusers/modular_pipelines/cosmos/denoise.py index 294213f48f4b..39d7a7e57808 100644 --- a/src/diffusers/modular_pipelines/cosmos/denoise.py +++ b/src/diffusers/modular_pipelines/cosmos/denoise.py @@ -500,7 +500,7 @@ def loop_expected_components(self) -> list[ComponentSpec]: def loop_inputs(self) -> list[InputParam]: return [ InputParam.template("timesteps", required=True), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), InputParam( name="num_warmup_steps", type_hint=int, required=True, description="Number of scheduler warmup steps." ), diff --git a/src/diffusers/modular_pipelines/krea2/denoise.py b/src/diffusers/modular_pipelines/krea2/denoise.py index ccb972ca74c1..bbc7613faaa2 100644 --- a/src/diffusers/modular_pipelines/krea2/denoise.py +++ b/src/diffusers/modular_pipelines/krea2/denoise.py @@ -91,7 +91,7 @@ def expected_components(self) -> list[ComponentSpec]: def inputs(self) -> list[InputParam]: return [ InputParam(name="latents", required=True, type_hint=torch.Tensor, description="Packed image latents."), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), InputParam( name="prompt_embeds", required=True, @@ -264,7 +264,7 @@ def loop_inputs(self) -> list[InputParam]: type_hint=torch.Tensor, description="Denoising timesteps from set_timesteps.", ), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), InputParam.template("attention_kwargs"), ] diff --git a/src/diffusers/modular_pipelines/ltx/before_denoise.py b/src/diffusers/modular_pipelines/ltx/before_denoise.py index 5543742a4d46..470ea7325ec7 100644 --- a/src/diffusers/modular_pipelines/ltx/before_denoise.py +++ b/src/diffusers/modular_pipelines/ltx/before_denoise.py @@ -282,7 +282,7 @@ def inputs(self) -> list[InputParam]: InputParam.template("latents"), InputParam.template("num_images_per_prompt", name="num_videos_per_prompt"), InputParam.template("generator"), - InputParam.template("batch_size", required=True), + InputParam.template("batch_size", required=True, default=None), ] @property @@ -348,7 +348,7 @@ def inputs(self) -> list[InputParam]: InputParam.template("width", default=704), InputParam("num_frames", type_hint=int, default=161), InputParam.template("num_images_per_prompt", name="num_videos_per_prompt"), - InputParam.template("batch_size", required=True), + InputParam.template("batch_size", required=True, default=None), ] @property diff --git a/src/diffusers/modular_pipelines/ltx/decoders.py b/src/diffusers/modular_pipelines/ltx/decoders.py index 2917eca125d0..9343d03eb13f 100644 --- a/src/diffusers/modular_pipelines/ltx/decoders.py +++ b/src/diffusers/modular_pipelines/ltx/decoders.py @@ -131,11 +131,7 @@ def inputs(self) -> list[tuple[str, Any]]: InputParam("decode_noise_scale", default=None), InputParam.template("generator"), InputParam.template("batch_size"), - InputParam.template("dtype", required=True), - # Only read on the deprecated path that still accepts packed `[B, S, C]` latents. - InputParam.template("height", default=512), - InputParam.template("width", default=704), - InputParam("num_frames", type_hint=int, default=161), + InputParam.template("dtype", required=True, default=None), ] @property diff --git a/src/diffusers/modular_pipelines/ltx/denoise.py b/src/diffusers/modular_pipelines/ltx/denoise.py index 44a3c91d471b..446acbdb4bee 100644 --- a/src/diffusers/modular_pipelines/ltx/denoise.py +++ b/src/diffusers/modular_pipelines/ltx/denoise.py @@ -45,7 +45,7 @@ def description(self) -> str: def inputs(self) -> list[InputParam]: return [ InputParam.template("latents", required=True), - InputParam.template("dtype", required=True), + InputParam.template("dtype", required=True, default=None), ] @torch.no_grad() @@ -97,7 +97,7 @@ def description(self) -> str: def inputs(self) -> list[tuple[str, Any]]: inputs = [ InputParam.template("attention_kwargs"), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), InputParam("rope_interpolation_scale", type_hint=tuple), InputParam.template("height"), InputParam.template("width"), @@ -211,7 +211,7 @@ def loop_expected_components(self) -> list[ComponentSpec]: def loop_inputs(self) -> list[InputParam]: return [ InputParam.template("timesteps", required=True), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), ] @torch.no_grad() @@ -277,7 +277,7 @@ def inputs(self) -> list[InputParam]: return [ InputParam.template("latents", required=True), InputParam("conditioning_mask", required=True, type_hint=torch.Tensor), - InputParam.template("dtype", required=True), + InputParam.template("dtype", required=True, default=None), ] @torch.no_grad() @@ -331,7 +331,7 @@ def description(self) -> str: def inputs(self) -> list[tuple[str, Any]]: inputs = [ InputParam.template("attention_kwargs"), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), InputParam("rope_interpolation_scale", type_hint=tuple), InputParam.template("height"), InputParam.template("width"), diff --git a/src/diffusers/modular_pipelines/ltx2/decoders.py b/src/diffusers/modular_pipelines/ltx2/decoders.py index cbeb1a66826d..34a2e9b6a74f 100644 --- a/src/diffusers/modular_pipelines/ltx2/decoders.py +++ b/src/diffusers/modular_pipelines/ltx2/decoders.py @@ -161,7 +161,7 @@ def inputs(self) -> list[tuple[str, Any]]: "num_frames", type_hint=int, default=121, description="The number of frames in the generated video." ), InputParam.template("generator"), - InputParam.template("dtype", required=True), + InputParam.template("dtype", required=True, default=None), ] @property @@ -251,7 +251,7 @@ def inputs(self) -> list[tuple[str, Any]]: ), InputParam.template("generator"), InputParam.template("batch_size"), - InputParam.template("dtype", required=True), + InputParam.template("dtype", required=True, default=None), ] @property diff --git a/src/diffusers/modular_pipelines/ltx2/denoise.py b/src/diffusers/modular_pipelines/ltx2/denoise.py index f83b86a656f6..87ad3516aca6 100644 --- a/src/diffusers/modular_pipelines/ltx2/denoise.py +++ b/src/diffusers/modular_pipelines/ltx2/denoise.py @@ -305,7 +305,7 @@ def inputs(self) -> list[InputParam]: # `audio_num_frames`, `video_coords`, `audio_coords` arrive tagged `denoiser_input_fields` upstream and # are collected from the tagged dict (filtered against the transformer signature) in `__call__`. InputParam.template("denoiser_input_fields"), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), InputParam.template("height", default=512), InputParam.template("width", default=704), InputParam( @@ -654,7 +654,7 @@ def loop_expected_components(self) -> list[ComponentSpec]: def loop_inputs(self) -> list[InputParam]: return [ InputParam("timesteps", type_hint=torch.Tensor, required=True), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), ] @torch.no_grad() diff --git a/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py b/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py index 94395a7c3ebe..2f95a1a2576e 100644 --- a/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py +++ b/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py @@ -1146,7 +1146,7 @@ def expected_components(self) -> list[ComponentSpec]: @property def inputs(self) -> list[InputParam]: return [ - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), InputParam( name="video_indices", type_hint=torch.Tensor, diff --git a/src/diffusers/modular_pipelines/modular_pipeline_utils.py b/src/diffusers/modular_pipelines/modular_pipeline_utils.py index e4a81e398e8c..49253188f9df 100644 --- a/src/diffusers/modular_pipelines/modular_pipeline_utils.py +++ b/src/diffusers/modular_pipelines/modular_pipeline_utils.py @@ -571,6 +571,15 @@ class InputParam: # resolves its own default at runtime in `get_block_state` defaults_by_block: dict[str, Any] = None + def __post_init__(self): + if self.required and self.default is not None: + logger.warning( + f"InputParam '{self.name}' is required and declares a default ({self.default!r}), which cannot both " + f"hold: a required input is never missing, so its default is unreachable. Dropping the default. Drop " + f"`required=True` instead if {self.default!r} is meant to be the real fallback." + ) + self.default = None + def __repr__(self): return f"<{self.name}: {'required' if self.required else 'optional'}, default={self.default}>" diff --git a/src/diffusers/modular_pipelines/qwenimage/denoise.py b/src/diffusers/modular_pipelines/qwenimage/denoise.py index 7f271782f82b..b28206fa4210 100644 --- a/src/diffusers/modular_pipelines/qwenimage/denoise.py +++ b/src/diffusers/modular_pipelines/qwenimage/denoise.py @@ -476,7 +476,7 @@ def loop_inputs(self) -> list[InputParam]: type_hint=torch.Tensor, description="The timesteps to use for the denoising process. Can be generated in set_timesteps step.", ), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), ] @torch.no_grad() From a73e96786d5f80df9e00a4dc480d0d6b2475d738 Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Thu, 22 Oct 2026 18:49:04 +0200 Subject: [PATCH 02/10] rebase follow-ups: new qwenimage21 site, keep MiniMax-H3 steps required, unit test and guide note Co-Authored-By: Claude Fable 5.1 --- .ai/references/modular.md | 10 ++-- .../modular_pipelines/ltx/decoders.py | 4 ++ .../minimax_h3/before_denoise.py | 2 +- .../modular_pipelines/qwenimage21/denoise.py | 2 +- .../test_modular_pipeline_utils.py | 50 +++++++++++++++++++ 5 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 tests/modular_pipelines/test_modular_pipeline_utils.py diff --git a/.ai/references/modular.md b/.ai/references/modular.md index 892d59342d7d..6f00625e0f9a 100644 --- a/.ai/references/modular.md +++ b/.ai/references/modular.md @@ -324,16 +324,18 @@ ComponentSpec( 5. **Using `InputParam.template()` / `OutputParam.template()` when semantics don't match.** Templates carry predefined descriptions — e.g. the `"latents"` output template means "Denoised latents". Don't use it for initial noisy latents from a prepare-latents step. Use a plain `InputParam(...)` / `OutputParam(...)` with an accurate description instead. -6. **Test model paths pointing to contributor repos.** Tiny test models ultimately live under `hf-internal-testing/`, not personal repos like `username/tiny-model`. Developing against a personal repo is fine and not merge-blocking — a maintainer moves the model (before or after merge) and updates the path. +6. **`required=True` on an input that carries a default.** A required input is never missing, so its default is unreachable; `InputParam` drops the default with a warning. This is easy to hit through a template whose entry carries a default (`num_inference_steps`, `dtype`, `batch_size`, ...): `InputParam.template("num_inference_steps", required=True)` does not quietly default to 50. Decide which one you mean: drop `required=True` to keep the template default, or pass `default=None` explicitly to keep the input required without the warning. -7. **Respect the declared IO system.** Components in `expected_components`, fields in `inputs` / `intermediate_outputs` — once declared, the modular framework guarantees them. So: +7. **Test model paths pointing to contributor repos.** Tiny test models ultimately live under `hf-internal-testing/`, not personal repos like `username/tiny-model`. Developing against a personal repo is fine and not merge-blocking — a maintainer moves the model (before or after merge) and updates the path. + +8. **Respect the declared IO system.** Components in `expected_components`, fields in `inputs` / `intermediate_outputs` — once declared, the modular framework guarantees them. So: - **Don't read defensively.** Declared components are always set as attributes (possibly `None`); declared upstream outputs are always populated in `block_state` after the upstream block runs. `getattr(components, "vae", None)`, `hasattr(self, "vae")`, `getattr(block_state, "prompt_embeds", None)` are dead code that hides typos. Use `components.vae` / `block_state.prompt_embeds` directly. Check `is not None` only when nullability is meaningful (a component the user might not have loaded). - **Don't write undeclared.** If a block sets `block_state.foo = ...`, declare `OutputParam("foo", ...)` in `intermediate_outputs`. The declarations are the public contract — undeclared writes can't be wired to downstream blocks. - **Don't call `state.set()` directly inside a block.** Write to state only through declared `intermediate_outputs` via `self.get_block_state(state)` / `self.set_block_state(state, block_state)`. A direct `state.set("foo", value)` bypasses the block's interface entirely — the field never appears as a declared output, so downstream blocks can't see it through the normal wiring and the framework can't generate docs / validate types for it. -8. **No-op skip logic inside an optional block.** If a step is conditional (e.g. an optional prompt enhancer), don't have the block check a flag at the top of `__call__` and `return` early. Wrap it in an `AutoPipelineBlocks` with `block_trigger_inputs = ["use_xxx"]` so the block is only assembled into the pipeline when the trigger input is provided. The block's own `__call__` should always assume its components and inputs are present. +9. **No-op skip logic inside an optional block.** If a step is conditional (e.g. an optional prompt enhancer), don't have the block check a flag at the top of `__call__` and `return` early. Wrap it in an `AutoPipelineBlocks` with `block_trigger_inputs = ["use_xxx"]` so the block is only assembled into the pipeline when the trigger input is provided. The block's own `__call__` should always assume its components and inputs are present. -9. **Serving a checkpoint variant through a config flag in a shared block.** `ConfigSpec(name="is_distilled")` plus `if components.config.is_distilled:` bundles two checkpoints' behavior into one blockset — and it can't change the input surface at all (the distilled variant would still accept `negative_prompt`). Suggest a separate blockset for the variant instead (see Key pattern: Checkpoint variants). +10. **Serving a checkpoint variant through a config flag in a shared block.** `ConfigSpec(name="is_distilled")` plus `if components.config.is_distilled:` bundles two checkpoints' behavior into one blockset — and it can't change the input surface at all (the distilled variant would still accept `negative_prompt`). Suggest a separate blockset for the variant instead (see Key pattern: Checkpoint variants). 10. **Raw `torch.randn(device=...)` for noise.** Use `randn_tensor(...)` from `utils/torch_utils`: it draws on the generator's device and moves the result, so CPU generators (what the test mixins pass) work, and the CUDA-generator path is bit-identical to `torch.randn`. diff --git a/src/diffusers/modular_pipelines/ltx/decoders.py b/src/diffusers/modular_pipelines/ltx/decoders.py index 9343d03eb13f..cbde2effd4cb 100644 --- a/src/diffusers/modular_pipelines/ltx/decoders.py +++ b/src/diffusers/modular_pipelines/ltx/decoders.py @@ -132,6 +132,10 @@ def inputs(self) -> list[tuple[str, Any]]: InputParam.template("generator"), InputParam.template("batch_size"), InputParam.template("dtype", required=True, default=None), + # Only read on the deprecated path that still accepts packed `[B, S, C]` latents. + InputParam.template("height", default=512), + InputParam.template("width", default=704), + InputParam("num_frames", type_hint=int, default=161), ] @property diff --git a/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py b/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py index 2f95a1a2576e..500f33c8ccc4 100644 --- a/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py +++ b/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py @@ -1146,7 +1146,7 @@ def expected_components(self) -> list[ComponentSpec]: @property def inputs(self) -> list[InputParam]: return [ - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), InputParam( name="video_indices", type_hint=torch.Tensor, diff --git a/src/diffusers/modular_pipelines/qwenimage21/denoise.py b/src/diffusers/modular_pipelines/qwenimage21/denoise.py index 67e41ed56966..66044e5c0e85 100644 --- a/src/diffusers/modular_pipelines/qwenimage21/denoise.py +++ b/src/diffusers/modular_pipelines/qwenimage21/denoise.py @@ -386,7 +386,7 @@ def loop_inputs(self) -> list[InputParam]: type_hint=torch.Tensor, description="The timesteps to use for the denoising process. Can be generated in set_timesteps step.", ), - InputParam.template("num_inference_steps", required=True), + InputParam.template("num_inference_steps"), InputParam( name="use_kv_cache", type_hint=bool, diff --git a/tests/modular_pipelines/test_modular_pipeline_utils.py b/tests/modular_pipelines/test_modular_pipeline_utils.py new file mode 100644 index 000000000000..ff5d5dfa5f6f --- /dev/null +++ b/tests/modular_pipelines/test_modular_pipeline_utils.py @@ -0,0 +1,50 @@ +# Copyright 2026 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from diffusers.modular_pipelines.modular_pipeline_utils import InputParam +from diffusers.utils import logging + +from ..testing_utils import CaptureLogger + + +class TestInputParamRequiredDefault: + def test_required_drops_default_with_warning(self): + logger = logging.get_logger("diffusers.modular_pipelines.modular_pipeline_utils") + logger.setLevel(logging.WARNING) + with CaptureLogger(logger) as cap_logger: + param = InputParam("num_inference_steps", required=True, default=50) + + assert param.required + assert param.default is None + assert "'num_inference_steps' is required and declares a default (50)" in cap_logger.out + + def test_required_template_drops_template_default(self): + param = InputParam.template("num_inference_steps", required=True) + assert param.required + assert param.default is None + + def test_optional_template_keeps_default(self): + param = InputParam.template("num_inference_steps") + assert not param.required + assert param.default == 50 + + def test_required_with_explicit_none_default_is_silent(self): + logger = logging.get_logger("diffusers.modular_pipelines.modular_pipeline_utils") + logger.setLevel(logging.WARNING) + with CaptureLogger(logger) as cap_logger: + param = InputParam.template("dtype", required=True, default=None) + + assert param.required + assert param.default is None + assert cap_logger.out == "" From f5c8ae5f4f5f9a77507dcb0084982b0e54c12250 Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Thu, 22 Oct 2026 19:21:45 +0200 Subject: [PATCH 03/10] regenerate auto docstrings Co-Authored-By: Claude Fable 5.1 --- .../modular_pipelines/cosmos/denoise.py | 2 +- .../cosmos/modular_blocks_cosmos3.py | 16 ++++++++-------- src/diffusers/modular_pipelines/krea2/denoise.py | 4 ++-- .../modular_pipelines/ltx/modular_blocks_ltx.py | 4 ++-- .../ltx2/modular_blocks_ltx2.py | 4 ++-- .../ltx2/modular_blocks_ltx25.py | 2 +- .../modular_pipelines/qwenimage/denoise.py | 14 +++++++------- .../qwenimage/modular_blocks_qwenimage.py | 2 +- .../qwenimage/modular_blocks_qwenimage_edit.py | 2 +- .../modular_pipelines/qwenimage21/denoise.py | 8 ++++---- .../qwenimage21/modular_blocks_qwenimage21.py | 4 ++-- .../modular_blocks_qwenimage21_turbo.py | 4 ++-- 12 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/diffusers/modular_pipelines/cosmos/denoise.py b/src/diffusers/modular_pipelines/cosmos/denoise.py index 39d7a7e57808..2a55b8b83716 100644 --- a/src/diffusers/modular_pipelines/cosmos/denoise.py +++ b/src/diffusers/modular_pipelines/cosmos/denoise.py @@ -962,7 +962,7 @@ class Cosmos3TransferDenoiseStep(Cosmos3DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): Timesteps for the denoising process. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. num_warmup_steps (`int`): Number of scheduler warmup steps. diff --git a/src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py b/src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py index ab6ebac2c8cd..50c184c148a4 100644 --- a/src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py +++ b/src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py @@ -394,7 +394,7 @@ class Cosmos3VisionCoreDenoiseStep(SequentialPipelineBlocks): Pre-generated noisy vision latents. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. mixed_precision_format (`str`, *optional*): None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is @@ -474,7 +474,7 @@ class Cosmos3VisionSoundCoreDenoiseStep(SequentialPipelineBlocks): Pre-generated noisy vision latents. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. sound_latents (`Tensor`, *optional*): Pre-generated noisy sound latents. @@ -567,7 +567,7 @@ class Cosmos3VisionActionCoreDenoiseStep(SequentialPipelineBlocks): Pre-generated noisy vision latents. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. action (`CosmosActionCondition`): Action-conditioning metadata. @@ -664,7 +664,7 @@ class Cosmos3VisionSoundActionCoreDenoiseStep(SequentialPipelineBlocks): Pre-generated noisy vision latents. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. sound_latents (`Tensor`, *optional*): Pre-generated noisy sound latents. @@ -782,7 +782,7 @@ class Cosmos3TransferChunkDenoiseStep(SequentialPipelineBlocks): Unconditional text segment. fps (`float`, *optional*, defaults to 24.0): Frame rate of the generated video. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. mixed_precision_format (`str`, *optional*): None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is @@ -937,7 +937,7 @@ class Cosmos3TransferCoreDenoiseStep(SequentialPipelineBlocks): Torch generator for deterministic generation. fps (`float`, *optional*, defaults to 24.0): Frame rate of the generated video. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. mixed_precision_format (`str`, *optional*): None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is @@ -1068,7 +1068,7 @@ class Cosmos3AutoCoreDenoiseStep(ConditionalPipelineBlocks): Torch generator for deterministic generation. fps (`float`, *optional*, defaults to 24.0): Frame rate of the generated video. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. mixed_precision_format (`str`, *optional*): None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is @@ -1280,7 +1280,7 @@ class Cosmos3OmniBlocks(SequentialPipelineBlocks): Number of frames the first chunk reuses from the input video. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. mixed_precision_format (`str`, *optional*): None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is diff --git a/src/diffusers/modular_pipelines/krea2/denoise.py b/src/diffusers/modular_pipelines/krea2/denoise.py index bbc7613faaa2..fe1e6165db68 100644 --- a/src/diffusers/modular_pipelines/krea2/denoise.py +++ b/src/diffusers/modular_pipelines/krea2/denoise.py @@ -296,7 +296,7 @@ class Krea2DenoiseStep(Krea2DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): Denoising timesteps from set_timesteps. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. attention_kwargs (`dict`, *optional*): Additional kwargs for attention processors. @@ -346,7 +346,7 @@ class Krea2TurboDenoiseStep(Krea2DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): Denoising timesteps from set_timesteps. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. attention_kwargs (`dict`, *optional*): Additional kwargs for attention processors. diff --git a/src/diffusers/modular_pipelines/ltx/modular_blocks_ltx.py b/src/diffusers/modular_pipelines/ltx/modular_blocks_ltx.py index 0bdaf6ec8816..19f868971235 100644 --- a/src/diffusers/modular_pipelines/ltx/modular_blocks_ltx.py +++ b/src/diffusers/modular_pipelines/ltx/modular_blocks_ltx.py @@ -293,7 +293,7 @@ class LTXAutoCoreDenoiseStep(AutoPipelineBlocks): negative text embeddings used to guide the image generation. Can be generated from text_encoder step. negative_prompt_attention_mask (`Tensor`): mask for the negative text embeddings. Can be generated from text_encoder step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. @@ -366,7 +366,7 @@ class LTXAutoBlocks(SequentialPipelineBlocks): Torch generator for deterministic generation. num_videos_per_prompt (`int`, *optional*, defaults to 1): The number of images to generate per prompt. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. diff --git a/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx2.py b/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx2.py index aa0990f3cc80..73849354602d 100644 --- a/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx2.py +++ b/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx2.py @@ -997,7 +997,7 @@ class LTX2AutoCoreDenoiseStep(ConditionalPipelineBlocks): Torch generator for deterministic generation. reference_cross_mask (`Tensor`, *optional*): Per-reference-token noisy<->reference attention strengths of shape [1, num_ref_tokens]. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 30): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. @@ -1864,7 +1864,7 @@ class LTX2AutoBlocks(SequentialPipelineBlocks): Custom sigmas for the denoising process. reference_cross_mask (`Tensor`, *optional*): Per-reference-token noisy<->reference attention strengths of shape [1, num_ref_tokens]. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 30): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. diff --git a/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx25.py b/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx25.py index 3280820eab0c..a1297c4f5cd2 100644 --- a/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx25.py +++ b/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx25.py @@ -320,7 +320,7 @@ class LTX25AutoBlocks(SequentialPipelineBlocks): Custom sigmas for the denoising process. reference_cross_mask (`Tensor`, *optional*): Per-reference-token noisy<->reference attention strengths of shape [1, num_ref_tokens]. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 30): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. diff --git a/src/diffusers/modular_pipelines/qwenimage/denoise.py b/src/diffusers/modular_pipelines/qwenimage/denoise.py index b28206fa4210..f386a4a6337a 100644 --- a/src/diffusers/modular_pipelines/qwenimage/denoise.py +++ b/src/diffusers/modular_pipelines/qwenimage/denoise.py @@ -530,7 +530,7 @@ class QwenImageDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -588,7 +588,7 @@ class QwenImageInpaintDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -653,7 +653,7 @@ class QwenImageControlNetDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -719,7 +719,7 @@ class QwenImageInpaintControlNetDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -797,7 +797,7 @@ class QwenImageEditDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -856,7 +856,7 @@ class QwenImageEditInpaintDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -920,7 +920,7 @@ class QwenImageLayeredDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. diff --git a/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage.py b/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage.py index bf87028b2f90..bae0c3330f86 100644 --- a/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage.py +++ b/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage.py @@ -1162,7 +1162,7 @@ class QwenImageAutoBlocks(SequentialPipelineBlocks): mask for the negative text embeddings. Can be generated from text_encoder step. latents (`Tensor`): Pre-generated noisy latents for image generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. diff --git a/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage_edit.py b/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage_edit.py index 37b80b69ec7e..55b64c90179a 100644 --- a/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage_edit.py +++ b/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage_edit.py @@ -754,7 +754,7 @@ class QwenImageEditAutoBlocks(SequentialPipelineBlocks): The processed mask image latents (`Tensor`): Pre-generated noisy latents for image generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. diff --git a/src/diffusers/modular_pipelines/qwenimage21/denoise.py b/src/diffusers/modular_pipelines/qwenimage21/denoise.py index 66044e5c0e85..5d9bffb2e8e0 100644 --- a/src/diffusers/modular_pipelines/qwenimage21/denoise.py +++ b/src/diffusers/modular_pipelines/qwenimage21/denoise.py @@ -450,7 +450,7 @@ class QwenImage21DenoiseStep(QwenImage21DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. use_kv_cache (`bool`, *optional*, defaults to True): Cache the text and condition-image keys and values after the first step. Valid because `causal_condition` @@ -515,7 +515,7 @@ class QwenImage21ImageConditionedDenoiseStep(QwenImage21DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. use_kv_cache (`bool`, *optional*, defaults to True): Cache the text and condition-image keys and values after the first step. Valid because `causal_condition` @@ -581,7 +581,7 @@ class QwenImage21TurboDenoiseStep(QwenImage21DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. use_kv_cache (`bool`, *optional*, defaults to True): Cache the text and condition-image keys and values after the first step. Valid because `causal_condition` @@ -642,7 +642,7 @@ class QwenImage21TurboImageConditionedDenoiseStep(QwenImage21DenoiseLoopWrapper) Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. use_kv_cache (`bool`, *optional*, defaults to True): Cache the text and condition-image keys and values after the first step. Valid because `causal_condition` diff --git a/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21.py b/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21.py index 569ffe530186..700a1da68ed7 100644 --- a/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21.py +++ b/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21.py @@ -446,7 +446,7 @@ class QwenImage21AutoCoreDenoiseStep(AutoPipelineBlocks): Target side length used to derive the output size and to resize condition images. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 40): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. @@ -530,7 +530,7 @@ class QwenImage21AutoBlocks(SequentialPipelineBlocks): Normalized latents of each condition image. Can be generated from vae_encoder step. latents (`Tensor`): Pre-generated noisy latents for image generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 40): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. diff --git a/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21_turbo.py b/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21_turbo.py index e7ba91f01627..bc3341aac007 100644 --- a/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21_turbo.py +++ b/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21_turbo.py @@ -415,7 +415,7 @@ class QwenImage21TurboAutoCoreDenoiseStep(AutoPipelineBlocks): Target side length used to derive the output size and to resize condition images. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 8): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. @@ -501,7 +501,7 @@ class QwenImage21TurboAutoBlocks(SequentialPipelineBlocks): Normalized latents of each condition image. Can be generated from vae_encoder step. latents (`Tensor`): Pre-generated noisy latents for image generation. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 8): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. From 5142760fc7ade12741c69ad4700bb81651ce0b67 Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Thu, 22 Oct 2026 23:03:08 +0200 Subject: [PATCH 04/10] reword the required-plus-default gotcha Co-Authored-By: Claude Fable 5.1 --- .ai/references/modular.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/references/modular.md b/.ai/references/modular.md index 6f00625e0f9a..e9f287521da2 100644 --- a/.ai/references/modular.md +++ b/.ai/references/modular.md @@ -324,7 +324,7 @@ ComponentSpec( 5. **Using `InputParam.template()` / `OutputParam.template()` when semantics don't match.** Templates carry predefined descriptions — e.g. the `"latents"` output template means "Denoised latents". Don't use it for initial noisy latents from a prepare-latents step. Use a plain `InputParam(...)` / `OutputParam(...)` with an accurate description instead. -6. **`required=True` on an input that carries a default.** A required input is never missing, so its default is unreachable; `InputParam` drops the default with a warning. This is easy to hit through a template whose entry carries a default (`num_inference_steps`, `dtype`, `batch_size`, ...): `InputParam.template("num_inference_steps", required=True)` does not quietly default to 50. Decide which one you mean: drop `required=True` to keep the template default, or pass `default=None` explicitly to keep the input required without the warning. +6. **`required=True` on an input that carries a default.** An input cannot be both required and have a default. When that happens, `InputParam` drops the default and logs a warning. This is likely to happen when you reuse a template for its type hint and description but make it required: `InputParam.template("num_inference_steps", required=True)` becomes a required input with no default, plus a warning, rather than an optional input that defaults to 50. Decide which one you mean: drop `required=True` to keep the template default, or pass `default=None` explicitly to keep the input required without the warning. 7. **Test model paths pointing to contributor repos.** Tiny test models ultimately live under `hf-internal-testing/`, not personal repos like `username/tiny-model`. Developing against a personal repo is fine and not merge-blocking — a maintainer moves the model (before or after merge) and updates the path. From 949e55ffde571a10aa99bfd3ce8d86fa6fb65318 Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Thu, 22 Oct 2026 23:35:07 +0200 Subject: [PATCH 05/10] drop the InputParam unit test Co-Authored-By: Claude Fable 5.1 --- .../test_modular_pipeline_utils.py | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 tests/modular_pipelines/test_modular_pipeline_utils.py diff --git a/tests/modular_pipelines/test_modular_pipeline_utils.py b/tests/modular_pipelines/test_modular_pipeline_utils.py deleted file mode 100644 index ff5d5dfa5f6f..000000000000 --- a/tests/modular_pipelines/test_modular_pipeline_utils.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2026 The HuggingFace Team. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from diffusers.modular_pipelines.modular_pipeline_utils import InputParam -from diffusers.utils import logging - -from ..testing_utils import CaptureLogger - - -class TestInputParamRequiredDefault: - def test_required_drops_default_with_warning(self): - logger = logging.get_logger("diffusers.modular_pipelines.modular_pipeline_utils") - logger.setLevel(logging.WARNING) - with CaptureLogger(logger) as cap_logger: - param = InputParam("num_inference_steps", required=True, default=50) - - assert param.required - assert param.default is None - assert "'num_inference_steps' is required and declares a default (50)" in cap_logger.out - - def test_required_template_drops_template_default(self): - param = InputParam.template("num_inference_steps", required=True) - assert param.required - assert param.default is None - - def test_optional_template_keeps_default(self): - param = InputParam.template("num_inference_steps") - assert not param.required - assert param.default == 50 - - def test_required_with_explicit_none_default_is_silent(self): - logger = logging.get_logger("diffusers.modular_pipelines.modular_pipeline_utils") - logger.setLevel(logging.WARNING) - with CaptureLogger(logger) as cap_logger: - param = InputParam.template("dtype", required=True, default=None) - - assert param.required - assert param.default is None - assert cap_logger.out == "" From 53ac3af32b753eec575a5b9da49730aa7e82edca Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Fri, 23 Oct 2026 01:27:44 +0200 Subject: [PATCH 06/10] require num_inference_steps in blocks that run after set_timesteps Co-Authored-By: Claude Fable 5.1 --- .../modular_pipelines/cosmos/denoise.py | 4 ++-- .../cosmos/modular_blocks_cosmos3.py | 4 ++-- src/diffusers/modular_pipelines/krea2/denoise.py | 8 ++++---- src/diffusers/modular_pipelines/ltx/denoise.py | 6 +++--- .../modular_pipelines/ltx/modular_blocks_ltx.py | 4 ++-- src/diffusers/modular_pipelines/ltx2/denoise.py | 4 ++-- .../ltx2/modular_blocks_ltx2.py | 4 ++-- .../ltx2/modular_blocks_ltx25.py | 2 +- .../minimax_h3/before_denoise.py | 2 +- .../minimax_h3/modular_blocks_minimax_h3.py | 10 +++++----- .../modular_pipelines/qwenimage/denoise.py | 16 ++++++++-------- .../qwenimage/modular_blocks_qwenimage.py | 2 +- .../qwenimage/modular_blocks_qwenimage_edit.py | 2 +- .../modular_pipelines/qwenimage21/denoise.py | 10 +++++----- .../qwenimage21/modular_blocks_qwenimage21.py | 4 ++-- .../modular_blocks_qwenimage21_turbo.py | 4 ++-- .../test_modular_pipeline_minimax_h3.py | 15 ++++++++------- 17 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/diffusers/modular_pipelines/cosmos/denoise.py b/src/diffusers/modular_pipelines/cosmos/denoise.py index 2a55b8b83716..2422defe4d46 100644 --- a/src/diffusers/modular_pipelines/cosmos/denoise.py +++ b/src/diffusers/modular_pipelines/cosmos/denoise.py @@ -500,7 +500,7 @@ def loop_expected_components(self) -> list[ComponentSpec]: def loop_inputs(self) -> list[InputParam]: return [ InputParam.template("timesteps", required=True), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), InputParam( name="num_warmup_steps", type_hint=int, required=True, description="Number of scheduler warmup steps." ), @@ -962,7 +962,7 @@ class Cosmos3TransferDenoiseStep(Cosmos3DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): Timesteps for the denoising process. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. num_warmup_steps (`int`): Number of scheduler warmup steps. diff --git a/src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py b/src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py index 50c184c148a4..864e687adad2 100644 --- a/src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py +++ b/src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py @@ -1068,7 +1068,7 @@ class Cosmos3AutoCoreDenoiseStep(ConditionalPipelineBlocks): Torch generator for deterministic generation. fps (`float`, *optional*, defaults to 24.0): Frame rate of the generated video. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. mixed_precision_format (`str`, *optional*): None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is @@ -1280,7 +1280,7 @@ class Cosmos3OmniBlocks(SequentialPipelineBlocks): Number of frames the first chunk reuses from the input video. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. mixed_precision_format (`str`, *optional*): None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is diff --git a/src/diffusers/modular_pipelines/krea2/denoise.py b/src/diffusers/modular_pipelines/krea2/denoise.py index fe1e6165db68..8fc019052a54 100644 --- a/src/diffusers/modular_pipelines/krea2/denoise.py +++ b/src/diffusers/modular_pipelines/krea2/denoise.py @@ -91,7 +91,7 @@ def expected_components(self) -> list[ComponentSpec]: def inputs(self) -> list[InputParam]: return [ InputParam(name="latents", required=True, type_hint=torch.Tensor, description="Packed image latents."), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), InputParam( name="prompt_embeds", required=True, @@ -264,7 +264,7 @@ def loop_inputs(self) -> list[InputParam]: type_hint=torch.Tensor, description="Denoising timesteps from set_timesteps.", ), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), InputParam.template("attention_kwargs"), ] @@ -296,7 +296,7 @@ class Krea2DenoiseStep(Krea2DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): Denoising timesteps from set_timesteps. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. attention_kwargs (`dict`, *optional*): Additional kwargs for attention processors. @@ -346,7 +346,7 @@ class Krea2TurboDenoiseStep(Krea2DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): Denoising timesteps from set_timesteps. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. attention_kwargs (`dict`, *optional*): Additional kwargs for attention processors. diff --git a/src/diffusers/modular_pipelines/ltx/denoise.py b/src/diffusers/modular_pipelines/ltx/denoise.py index 446acbdb4bee..d04c0c7f18d5 100644 --- a/src/diffusers/modular_pipelines/ltx/denoise.py +++ b/src/diffusers/modular_pipelines/ltx/denoise.py @@ -97,7 +97,7 @@ def description(self) -> str: def inputs(self) -> list[tuple[str, Any]]: inputs = [ InputParam.template("attention_kwargs"), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), InputParam("rope_interpolation_scale", type_hint=tuple), InputParam.template("height"), InputParam.template("width"), @@ -211,7 +211,7 @@ def loop_expected_components(self) -> list[ComponentSpec]: def loop_inputs(self) -> list[InputParam]: return [ InputParam.template("timesteps", required=True), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), ] @torch.no_grad() @@ -331,7 +331,7 @@ def description(self) -> str: def inputs(self) -> list[tuple[str, Any]]: inputs = [ InputParam.template("attention_kwargs"), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), InputParam("rope_interpolation_scale", type_hint=tuple), InputParam.template("height"), InputParam.template("width"), diff --git a/src/diffusers/modular_pipelines/ltx/modular_blocks_ltx.py b/src/diffusers/modular_pipelines/ltx/modular_blocks_ltx.py index 19f868971235..0bdaf6ec8816 100644 --- a/src/diffusers/modular_pipelines/ltx/modular_blocks_ltx.py +++ b/src/diffusers/modular_pipelines/ltx/modular_blocks_ltx.py @@ -293,7 +293,7 @@ class LTXAutoCoreDenoiseStep(AutoPipelineBlocks): negative text embeddings used to guide the image generation. Can be generated from text_encoder step. negative_prompt_attention_mask (`Tensor`): mask for the negative text embeddings. Can be generated from text_encoder step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. @@ -366,7 +366,7 @@ class LTXAutoBlocks(SequentialPipelineBlocks): Torch generator for deterministic generation. num_videos_per_prompt (`int`, *optional*, defaults to 1): The number of images to generate per prompt. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. diff --git a/src/diffusers/modular_pipelines/ltx2/denoise.py b/src/diffusers/modular_pipelines/ltx2/denoise.py index 87ad3516aca6..c396d01141be 100644 --- a/src/diffusers/modular_pipelines/ltx2/denoise.py +++ b/src/diffusers/modular_pipelines/ltx2/denoise.py @@ -305,7 +305,7 @@ def inputs(self) -> list[InputParam]: # `audio_num_frames`, `video_coords`, `audio_coords` arrive tagged `denoiser_input_fields` upstream and # are collected from the tagged dict (filtered against the transformer signature) in `__call__`. InputParam.template("denoiser_input_fields"), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), InputParam.template("height", default=512), InputParam.template("width", default=704), InputParam( @@ -654,7 +654,7 @@ def loop_expected_components(self) -> list[ComponentSpec]: def loop_inputs(self) -> list[InputParam]: return [ InputParam("timesteps", type_hint=torch.Tensor, required=True), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), ] @torch.no_grad() diff --git a/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx2.py b/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx2.py index 73849354602d..aa0990f3cc80 100644 --- a/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx2.py +++ b/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx2.py @@ -997,7 +997,7 @@ class LTX2AutoCoreDenoiseStep(ConditionalPipelineBlocks): Torch generator for deterministic generation. reference_cross_mask (`Tensor`, *optional*): Per-reference-token noisy<->reference attention strengths of shape [1, num_ref_tokens]. - num_inference_steps (`int`, *optional*, defaults to 30): + num_inference_steps (`int`): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. @@ -1864,7 +1864,7 @@ class LTX2AutoBlocks(SequentialPipelineBlocks): Custom sigmas for the denoising process. reference_cross_mask (`Tensor`, *optional*): Per-reference-token noisy<->reference attention strengths of shape [1, num_ref_tokens]. - num_inference_steps (`int`, *optional*, defaults to 30): + num_inference_steps (`int`): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. diff --git a/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx25.py b/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx25.py index a1297c4f5cd2..3280820eab0c 100644 --- a/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx25.py +++ b/src/diffusers/modular_pipelines/ltx2/modular_blocks_ltx25.py @@ -320,7 +320,7 @@ class LTX25AutoBlocks(SequentialPipelineBlocks): Custom sigmas for the denoising process. reference_cross_mask (`Tensor`, *optional*): Per-reference-token noisy<->reference attention strengths of shape [1, num_ref_tokens]. - num_inference_steps (`int`, *optional*, defaults to 30): + num_inference_steps (`int`): The number of denoising steps. timesteps (`Tensor`): Timesteps for the denoising process. diff --git a/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py b/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py index 500f33c8ccc4..2f95a1a2576e 100644 --- a/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py +++ b/src/diffusers/modular_pipelines/minimax_h3/before_denoise.py @@ -1146,7 +1146,7 @@ def expected_components(self) -> list[ComponentSpec]: @property def inputs(self) -> list[InputParam]: return [ - InputParam.template("num_inference_steps", required=True, default=None), + InputParam.template("num_inference_steps"), InputParam( name="video_indices", type_hint=torch.Tensor, diff --git a/src/diffusers/modular_pipelines/minimax_h3/modular_blocks_minimax_h3.py b/src/diffusers/modular_pipelines/minimax_h3/modular_blocks_minimax_h3.py index 23e7f1b83bba..e96454a44fa4 100644 --- a/src/diffusers/modular_pipelines/minimax_h3/modular_blocks_minimax_h3.py +++ b/src/diffusers/modular_pipelines/minimax_h3/modular_blocks_minimax_h3.py @@ -261,7 +261,7 @@ class MiniMaxH3CoreDenoiseStep(SequentialPipelineBlocks): instead of the draw. audio_latents (`Tensor`, *optional*): Pre-generated audio noise of shape `(2, 32, num_audio_latents)`. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. prompt_embeds (`Tensor`): text embeddings used to guide the image generation. Can be generated from text_encoder step. @@ -360,7 +360,7 @@ class MiniMaxH3FL2VACoreDenoiseStep(SequentialPipelineBlocks): instead of the draw. audio_latents (`Tensor`, *optional*): Pre-generated audio noise of shape `(2, 32, num_audio_latents)`. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. prompt_embeds (`Tensor`): text embeddings used to guide the image generation. Can be generated from text_encoder step. @@ -459,7 +459,7 @@ class MiniMaxH3Ref2VACoreDenoiseStep(SequentialPipelineBlocks): instead of the draw. audio_latents (`Tensor`, *optional*): Pre-generated audio noise of shape `(2, 32, num_audio_latents)`. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. prompt_embeds (`Tensor`): text embeddings used to guide the image generation. Can be generated from text_encoder step. @@ -564,7 +564,7 @@ class MiniMaxH3AutoDenoiseStep(ConditionalPipelineBlocks): instead of the draw. audio_latents (`Tensor`): Pre-generated audio noise of shape `(2, 32, num_audio_latents)`. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. prompt_embeds (`Tensor`): text embeddings used to guide the image generation. Can be generated from text_encoder step. @@ -721,7 +721,7 @@ class MiniMaxH3Blocks(SequentialPipelineBlocks): instead of the draw. audio_latents (`Tensor`): Pre-generated audio noise of shape `(2, 32, num_audio_latents)`. - num_inference_steps (`int`): + num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. **denoiser_input_fields (`None`, *optional*): The structural description of the packed sequence the transformer reads by name: `token_tags`, diff --git a/src/diffusers/modular_pipelines/qwenimage/denoise.py b/src/diffusers/modular_pipelines/qwenimage/denoise.py index f386a4a6337a..73fbef722b10 100644 --- a/src/diffusers/modular_pipelines/qwenimage/denoise.py +++ b/src/diffusers/modular_pipelines/qwenimage/denoise.py @@ -476,7 +476,7 @@ def loop_inputs(self) -> list[InputParam]: type_hint=torch.Tensor, description="The timesteps to use for the denoising process. Can be generated in set_timesteps step.", ), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), ] @torch.no_grad() @@ -530,7 +530,7 @@ class QwenImageDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -588,7 +588,7 @@ class QwenImageInpaintDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -653,7 +653,7 @@ class QwenImageControlNetDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -719,7 +719,7 @@ class QwenImageInpaintControlNetDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -797,7 +797,7 @@ class QwenImageEditDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -856,7 +856,7 @@ class QwenImageEditInpaintDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. @@ -920,7 +920,7 @@ class QwenImageLayeredDenoiseStep(QwenImageDenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. latents (`Tensor`): The initial latents to use for the denoising process. Can be generated in prepare_latent step. diff --git a/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage.py b/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage.py index bae0c3330f86..bf87028b2f90 100644 --- a/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage.py +++ b/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage.py @@ -1162,7 +1162,7 @@ class QwenImageAutoBlocks(SequentialPipelineBlocks): mask for the negative text embeddings. Can be generated from text_encoder step. latents (`Tensor`): Pre-generated noisy latents for image generation. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. diff --git a/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage_edit.py b/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage_edit.py index 55b64c90179a..37b80b69ec7e 100644 --- a/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage_edit.py +++ b/src/diffusers/modular_pipelines/qwenimage/modular_blocks_qwenimage_edit.py @@ -754,7 +754,7 @@ class QwenImageEditAutoBlocks(SequentialPipelineBlocks): The processed mask image latents (`Tensor`): Pre-generated noisy latents for image generation. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. diff --git a/src/diffusers/modular_pipelines/qwenimage21/denoise.py b/src/diffusers/modular_pipelines/qwenimage21/denoise.py index 5d9bffb2e8e0..be44396e5042 100644 --- a/src/diffusers/modular_pipelines/qwenimage21/denoise.py +++ b/src/diffusers/modular_pipelines/qwenimage21/denoise.py @@ -386,7 +386,7 @@ def loop_inputs(self) -> list[InputParam]: type_hint=torch.Tensor, description="The timesteps to use for the denoising process. Can be generated in set_timesteps step.", ), - InputParam.template("num_inference_steps"), + InputParam.template("num_inference_steps", required=True, default=None), InputParam( name="use_kv_cache", type_hint=bool, @@ -450,7 +450,7 @@ class QwenImage21DenoiseStep(QwenImage21DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. use_kv_cache (`bool`, *optional*, defaults to True): Cache the text and condition-image keys and values after the first step. Valid because `causal_condition` @@ -515,7 +515,7 @@ class QwenImage21ImageConditionedDenoiseStep(QwenImage21DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. use_kv_cache (`bool`, *optional*, defaults to True): Cache the text and condition-image keys and values after the first step. Valid because `causal_condition` @@ -581,7 +581,7 @@ class QwenImage21TurboDenoiseStep(QwenImage21DenoiseLoopWrapper): Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. use_kv_cache (`bool`, *optional*, defaults to True): Cache the text and condition-image keys and values after the first step. Valid because `causal_condition` @@ -642,7 +642,7 @@ class QwenImage21TurboImageConditionedDenoiseStep(QwenImage21DenoiseLoopWrapper) Inputs: timesteps (`Tensor`): The timesteps to use for the denoising process. Can be generated in set_timesteps step. - num_inference_steps (`int`, *optional*, defaults to 50): + num_inference_steps (`int`): The number of denoising steps. use_kv_cache (`bool`, *optional*, defaults to True): Cache the text and condition-image keys and values after the first step. Valid because `causal_condition` diff --git a/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21.py b/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21.py index 700a1da68ed7..569ffe530186 100644 --- a/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21.py +++ b/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21.py @@ -446,7 +446,7 @@ class QwenImage21AutoCoreDenoiseStep(AutoPipelineBlocks): Target side length used to derive the output size and to resize condition images. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`, *optional*, defaults to 40): + num_inference_steps (`int`): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. @@ -530,7 +530,7 @@ class QwenImage21AutoBlocks(SequentialPipelineBlocks): Normalized latents of each condition image. Can be generated from vae_encoder step. latents (`Tensor`): Pre-generated noisy latents for image generation. - num_inference_steps (`int`, *optional*, defaults to 40): + num_inference_steps (`int`): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. diff --git a/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21_turbo.py b/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21_turbo.py index bc3341aac007..e7ba91f01627 100644 --- a/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21_turbo.py +++ b/src/diffusers/modular_pipelines/qwenimage21/modular_blocks_qwenimage21_turbo.py @@ -415,7 +415,7 @@ class QwenImage21TurboAutoCoreDenoiseStep(AutoPipelineBlocks): Target side length used to derive the output size and to resize condition images. generator (`Generator`, *optional*): Torch generator for deterministic generation. - num_inference_steps (`int`, *optional*, defaults to 8): + num_inference_steps (`int`): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. @@ -501,7 +501,7 @@ class QwenImage21TurboAutoBlocks(SequentialPipelineBlocks): Normalized latents of each condition image. Can be generated from vae_encoder step. latents (`Tensor`): Pre-generated noisy latents for image generation. - num_inference_steps (`int`, *optional*, defaults to 8): + num_inference_steps (`int`): The number of denoising steps. sigmas (`list`, *optional*): Custom sigmas for the denoising process. diff --git a/tests/modular_pipelines/minimax_h3/test_modular_pipeline_minimax_h3.py b/tests/modular_pipelines/minimax_h3/test_modular_pipeline_minimax_h3.py index 03239ef82e3d..dd7f04f1c7f1 100644 --- a/tests/modular_pipelines/minimax_h3/test_modular_pipeline_minimax_h3.py +++ b/tests/modular_pipelines/minimax_h3/test_modular_pipeline_minimax_h3.py @@ -119,6 +119,7 @@ # reference or from the bare 16:9 default — so all three declare the rule as pipeline config. _SHARED_CONFIGS = {"canvas_short_edge": 768, "canvas_max_pixels": 768 * 1344} _SHARED_INPUTS = { + "num_inference_steps": 50, "height": None, "width": None, "generator": None, @@ -131,7 +132,7 @@ "t2va": { "components": {**_SHARED_COMPONENTS, "transformer": "MiniMaxH3Transformer3DModel"}, "configs": _SHARED_CONFIGS, - "required_inputs": ["prompt", "num_inference_steps"], + "required_inputs": ["prompt"], "inputs": {**_SHARED_INPUTS, "num_frames": 124}, }, "fl2va": { @@ -141,7 +142,7 @@ "image_processor": "VaeImageProcessor", }, "configs": _SHARED_CONFIGS, - "required_inputs": ["prompt", "num_inference_steps"], + "required_inputs": ["prompt"], "inputs": {**_SHARED_INPUTS, "num_frames": 124, "image": None, "last_image": None}, }, } @@ -157,7 +158,7 @@ "configs": {**_SHARED_CONFIGS, "reference_image_short_edge": 2048}, # `num_frames` cannot default on ref2va: reference soundtracks are truncated to the generated duration, so a # silent 124-frame default would cut them short. It is required instead. - "required_inputs": ["prompt", "references", "num_frames", "num_inference_steps"], + "required_inputs": ["prompt", "references", "num_frames"], "inputs": _SHARED_INPUTS, }, } @@ -174,8 +175,8 @@ class MiniMaxH3ModularPipelineTesterConfig(BaseModularPipelineTesterConfig): # MiniMax-H3 packs one request into one sequence and rejects a list of prompts, so nothing is batched. batch_params = frozenset() # The checkpoint is guidance-distilled and generates a video, so neither `guidance_scale` nor - # `num_images_per_prompt` exists. `num_inference_steps` is declared required, so it is not listed here either. - optional_params = frozenset(["latents", "output_type"]) + # `num_images_per_prompt` exists. + optional_params = frozenset(["num_inference_steps", "latents", "output_type"]) output_name = "videos" expected_workflow_blocks = MINIMAX_H3_WORKFLOWS expected_workflow_defaults = MINIMAX_H3_WORKFLOW_DEFAULTS @@ -614,8 +615,8 @@ class MiniMaxH3Ref2VAModularPipelineTesterConfig(BaseModularPipelineTesterConfig # MiniMax-H3 packs one request into one sequence and rejects a list of prompts, so nothing is batched. batch_params = frozenset() # The checkpoint is guidance-distilled and generates a video, so neither `guidance_scale` nor - # `num_images_per_prompt` exists. `num_inference_steps` is declared required, so it is not listed here either. - optional_params = frozenset(["latents", "output_type"]) + # `num_images_per_prompt` exists. + optional_params = frozenset(["num_inference_steps", "latents", "output_type"]) output_name = "videos" expected_workflow_blocks = MINIMAX_H3_REF2VA_WORKFLOWS expected_workflow_defaults = MINIMAX_H3_REF2VA_WORKFLOW_DEFAULTS From c95198eace78541bf1fae9412863400631323143 Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Fri, 23 Oct 2026 05:23:01 +0200 Subject: [PATCH 07/10] flip the drop the required --- .ai/references/modular.md | 2 +- .ai/references/testing.md | 1 + src/diffusers/modular_pipelines/modular_pipeline_utils.py | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.ai/references/modular.md b/.ai/references/modular.md index e9f287521da2..b3a2b2da501f 100644 --- a/.ai/references/modular.md +++ b/.ai/references/modular.md @@ -324,7 +324,7 @@ ComponentSpec( 5. **Using `InputParam.template()` / `OutputParam.template()` when semantics don't match.** Templates carry predefined descriptions — e.g. the `"latents"` output template means "Denoised latents". Don't use it for initial noisy latents from a prepare-latents step. Use a plain `InputParam(...)` / `OutputParam(...)` with an accurate description instead. -6. **`required=True` on an input that carries a default.** An input cannot be both required and have a default. When that happens, `InputParam` drops the default and logs a warning. This is likely to happen when you reuse a template for its type hint and description but make it required: `InputParam.template("num_inference_steps", required=True)` becomes a required input with no default, plus a warning, rather than an optional input that defaults to 50. Decide which one you mean: drop `required=True` to keep the template default, or pass `default=None` explicitly to keep the input required without the warning. +6. **`required=True` on an input that carries a default.** An input cannot be both required and have a default. When that happens, `InputParam` keeps the default, treats the input as optional and logs a warning. This is likely to happen when you reuse a template for its type hint and description but make it required: `InputParam.template("num_inference_steps", required=True)` stays an optional input that defaults to 50, plus a warning. Decide which one you mean: drop `required=True` to keep the template default without the warning, or pass `default=None` explicitly to make the input required. 7. **Test model paths pointing to contributor repos.** Tiny test models ultimately live under `hf-internal-testing/`, not personal repos like `username/tiny-model`. Developing against a personal repo is fine and not merge-blocking — a maintainer moves the model (before or after merge) and updates the path. diff --git a/.ai/references/testing.md b/.ai/references/testing.md index 640ef6f0bd5a..e6dbdddc27ac 100644 --- a/.ai/references/testing.md +++ b/.ai/references/testing.md @@ -61,6 +61,7 @@ Since [#14268](https://github.com/huggingface/diffusers/pull/14268), a standard - Location: `tests/modular_pipelines//test_modular_pipeline_.py` (one config class + set of test classes per blockset / pipeline variant). - **Define one config class**, `ModularPipelineTesterConfig`, subclassing `BaseModularPipelineTesterConfig` (from `..testing_utils`). Set `pipeline_class`, `pipeline_blocks_class`, `pretrained_model_name_or_path`, `params` / `batch_params`, and implement `get_dummy_inputs(seed=0)`. Set `expected_workflow_blocks` to pin the block name → class ordering per workflow. The config holds the whole testing contract and performs no assertions. +- **Set `expected_workflow_defaults`** to what each workflow is expected to have: every component with its class, every config with its default, and every input — the optional ones with their defaults under `inputs`, the required ones by name under `required_inputs`. The test fails when the code declares anything else, so this table is the pipeline's contract written out in one place, and writing it is also the moment to check that contract. Once it is filled in, read it against the same tables of the other families under `tests/modular_pipelines/`: for example, `prompt` is normally required, `num_inference_steps` is normally optional with a default, `latents` is normally accepted so a run can be reproduced, and so on. An entry that breaks the common pattern needs a reason. If there isn't one, something is wrong: find out what and fix it, rather than writing the odd entry into the table. - **Then one test class per concern**, each composing the config with a tester mixin from `..testing_utils`. Keep them separate — pytest reads class-level markers off the whole MRO, so folding a marked mixin (`@is_memory`, ...) into the same class as the others would tag every test in it: - `ModularPipelineTesterMixin` — call signature, batch consistency, float16, device placement, NaN-free output. Put pipeline-specific tests as methods on this class. - `ModularLoadingTesterMixin` — `save_pretrained`/`from_pretrained` round-trips, `modular_model_index.json` contents, `load_components`/`unload_components`. diff --git a/src/diffusers/modular_pipelines/modular_pipeline_utils.py b/src/diffusers/modular_pipelines/modular_pipeline_utils.py index 49253188f9df..07dd94c5693e 100644 --- a/src/diffusers/modular_pipelines/modular_pipeline_utils.py +++ b/src/diffusers/modular_pipelines/modular_pipeline_utils.py @@ -575,10 +575,10 @@ def __post_init__(self): if self.required and self.default is not None: logger.warning( f"InputParam '{self.name}' is required and declares a default ({self.default!r}), which cannot both " - f"hold: a required input is never missing, so its default is unreachable. Dropping the default. Drop " - f"`required=True` instead if {self.default!r} is meant to be the real fallback." + f"hold: a required input is never missing, so its default is unreachable. Keeping the default and " + f"treating the input as optional. Pass `default=None` explicitly if '{self.name}' is meant to be required." ) - self.default = None + self.required = False def __repr__(self): return f"<{self.name}: {'required' if self.required else 'optional'}, default={self.default}>" From 2bca34340d70caa2f671b5f07bdd19cfd9d0d38d Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Fri, 23 Oct 2026 05:36:08 +0200 Subject: [PATCH 08/10] fix numbering --- .ai/references/modular.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ai/references/modular.md b/.ai/references/modular.md index b3a2b2da501f..8ab88e65cc98 100644 --- a/.ai/references/modular.md +++ b/.ai/references/modular.md @@ -337,9 +337,9 @@ ComponentSpec( 10. **Serving a checkpoint variant through a config flag in a shared block.** `ConfigSpec(name="is_distilled")` plus `if components.config.is_distilled:` bundles two checkpoints' behavior into one blockset — and it can't change the input surface at all (the distilled variant would still accept `negative_prompt`). Suggest a separate blockset for the variant instead (see Key pattern: Checkpoint variants). -10. **Raw `torch.randn(device=...)` for noise.** Use `randn_tensor(...)` from `utils/torch_utils`: it draws on the generator's device and moves the result, so CPU generators (what the test mixins pass) work, and the CUDA-generator path is bit-identical to `torch.randn`. +11. **Raw `torch.randn(device=...)` for noise.** Use `randn_tensor(...)` from `utils/torch_utils`: it draws on the generator's device and moves the result, so CPU generators (what the test mixins pass) work, and the CUDA-generator path is bit-identical to `torch.randn`. -11. **Helpers taking `components`.** Prefer explicit args, for example, `get_xx_prompt_embeds(text_encoder, tokenizer, prompt)` instead of `get_xx_prompt_embeds(components, prompt)`. Helpers and block methods should take only what they use so dependencies are visible and they can run without an assembled pipeline. Leave the full `components` object to `__call__`, which unpacks what its helpers need. See `get_qwen_prompt_embeds` in `qwenimage/encoders.py`. +12. **Helpers taking `components`.** Prefer explicit args, for example, `get_xx_prompt_embeds(text_encoder, tokenizer, prompt)` instead of `get_xx_prompt_embeds(components, prompt)`. Helpers and block methods should take only what they use so dependencies are visible and they can run without an assembled pipeline. Leave the full `components` object to `__call__`, which unpacks what its helpers need. See `get_qwen_prompt_embeds` in `qwenimage/encoders.py`. ## Conversion checklist From e4e81e1f81cab16d4779065e3d98fb349257a235 Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Tue, 27 Oct 2026 00:44:40 +0100 Subject: [PATCH 09/10] [agent docs] call expected_workflow_defaults a dict, not a table Co-Authored-By: Claude Fable 5.1 --- .ai/references/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/references/testing.md b/.ai/references/testing.md index e6dbdddc27ac..2ccbc406a9b8 100644 --- a/.ai/references/testing.md +++ b/.ai/references/testing.md @@ -61,7 +61,7 @@ Since [#14268](https://github.com/huggingface/diffusers/pull/14268), a standard - Location: `tests/modular_pipelines//test_modular_pipeline_.py` (one config class + set of test classes per blockset / pipeline variant). - **Define one config class**, `ModularPipelineTesterConfig`, subclassing `BaseModularPipelineTesterConfig` (from `..testing_utils`). Set `pipeline_class`, `pipeline_blocks_class`, `pretrained_model_name_or_path`, `params` / `batch_params`, and implement `get_dummy_inputs(seed=0)`. Set `expected_workflow_blocks` to pin the block name → class ordering per workflow. The config holds the whole testing contract and performs no assertions. -- **Set `expected_workflow_defaults`** to what each workflow is expected to have: every component with its class, every config with its default, and every input — the optional ones with their defaults under `inputs`, the required ones by name under `required_inputs`. The test fails when the code declares anything else, so this table is the pipeline's contract written out in one place, and writing it is also the moment to check that contract. Once it is filled in, read it against the same tables of the other families under `tests/modular_pipelines/`: for example, `prompt` is normally required, `num_inference_steps` is normally optional with a default, `latents` is normally accepted so a run can be reproduced, and so on. An entry that breaks the common pattern needs a reason. If there isn't one, something is wrong: find out what and fix it, rather than writing the odd entry into the table. +- **Set `expected_workflow_defaults`** to what each workflow is expected to have: every component with its class, every config with its default, and every input — the optional ones with their defaults under `inputs`, the required ones by name under `required_inputs`. The test fails when the code declares anything else, so this dict is the pipeline's contract written out in one place, and filling it in is also the moment to check that contract. Once it is filled in, read it against the same dict in the other families under `tests/modular_pipelines/`: for example, `prompt` is normally required, `num_inference_steps` is normally optional with a default, `latents` is normally accepted so a run can be reproduced, and so on. An entry that breaks the common pattern needs a reason. If there isn't one, something is wrong: find out what and fix it, rather than writing the odd entry into the dict. - **Then one test class per concern**, each composing the config with a tester mixin from `..testing_utils`. Keep them separate — pytest reads class-level markers off the whole MRO, so folding a marked mixin (`@is_memory`, ...) into the same class as the others would tag every test in it: - `ModularPipelineTesterMixin` — call signature, batch consistency, float16, device placement, NaN-free output. Put pipeline-specific tests as methods on this class. - `ModularLoadingTesterMixin` — `save_pretrained`/`from_pretrained` round-trips, `modular_model_index.json` contents, `load_components`/`unload_components`. From bcc2f90c81faadaaad604c7d89026256f9636e1d Mon Sep 17 00:00:00 2001 From: yiyixuxu Date: Tue, 27 Oct 2026 00:52:57 +0100 Subject: [PATCH 10/10] [modular tests] every pipeline builds without a required-with-default warning Co-Authored-By: Claude Fable 5.1 --- tests/modular_pipelines/testing_utils/common.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/modular_pipelines/testing_utils/common.py b/tests/modular_pipelines/testing_utils/common.py index 545ca0f31ef0..3ea912a6db5c 100644 --- a/tests/modular_pipelines/testing_utils/common.py +++ b/tests/modular_pipelines/testing_utils/common.py @@ -24,6 +24,7 @@ from diffusers.utils import logging from ...testing_utils import ( + CaptureLogger, backend_empty_cache, numpy_cosine_similarity_distance, require_accelerator, @@ -223,6 +224,16 @@ def _check_for_parameters(parameters, expected_parameters, param_type): f"Parameters declared in `not_params` unexpectedly present in the pipeline inputs: {unsupported_parameters}" ) + def test_no_input_is_both_required_and_has_a_default(self): + # `InputParam` warns and drops `required` when a declaration is both required and has a default (see the + # pitfalls list in `.ai/references/modular.md`). Building the blocks and reading their `inputs` constructs + # every `InputParam` of every sub-block, so a clean log means no block in this pipeline declares one + logger = logging.get_logger("diffusers.modular_pipelines.modular_pipeline_utils") + with CaptureLogger(logger) as cap_logger: + blocks = self.pipeline_blocks_class() + blocks.inputs + assert "is required and declares a default" not in cap_logger.out, cap_logger.out + def test_inference_batch_consistent(self, batch_sizes=[2], batch_generator=True): pipe = self.get_pipeline().to(torch_device)