[modular] conditional block triggers should be workflow inputs
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 48/100
- Loại issue
- Tái cấu trúc
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Lĩnh vực
- machine-learning
Hướng nghiên cứu
Start with the modular guide and the ConditionalPipelineBlocks docstring, then inspect workflow maps, conditional triggers, and intermediate outputs in the released blocksets. The issue’s scan_triggers.py example shows how to find triggers that are neither workflow inputs nor intermediates; use it to guide the regression test. Done means documenting the rule, testing all released blocksets against it, and migrating the listed LTX-2, Ernie, SDXL, and Cosmos 3 cases.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
A bit of history on conditional blocks and workflows
Conditional blocks (AutoPipelineBlocks, ConditionalPipelineBlocks) are convenient to use, but they come at a cost of clarity: it is harder to figure out what is actually running. We first addressed that with get_execution_blocks, which works on any blockset with sub-blocks: give it a set of inputs and it returns the blocks that would run for them. Later, #13028 added workflows on a higher level, on the final assembled blockset only: it declares its workflows in _workflow_map, get_workflow(name) returns the blocks of one, and from_pretrained(workflow=...) and load_components(workflow=...) use that to prune blocks and components.
This leaves a gap for a conditional block whose trigger input is not also a workflow input. Let's look at a dummy example:
Three blocks: prompt_enhancer is optional, keyed on enhance_prompt; block1 and block2 each pick a text-to-image or image-to-image branch on image. Two declared workflows, t2i and i2i.
simple_get_workflow.py
from diffusers.modular_pipelines import (
AutoPipelineBlocks,
ConditionalPipelineBlocks,
ModularPipelineBlocks,
SequentialPipelineBlocks,
)
from diffusers.modular_pipelines.modular_pipeline_utils import InputParam, OutputParam
def leaf(name, inputs, outputs):
"""A leaf block that only declares inputs/outputs; it does nothing when run."""
class Block(ModularPipelineBlocks):
model_name = "dummy"
@property
def description(self):
return name
@property
def inputs(self):
return [InputParam(n) for n in inputs]
@property
def intermediate_outputs(self):
return [OutputParam(n) for n in outputs]
def __call__(self, components, state):
return components, state
Block.__name__ = Block.__qualname__ = name
return Block
PromptEnhancer = leaf("prompt_enhancer", ["prompt", "enhance_prompt"], ["prompt"])
Block1T2I = leaf("block1_t2i", ["prompt"], ["latents"])
Block1I2I = leaf("block1_i2i", ["prompt", "image"], ["latents"])
Block2T2I = leaf("block2_t2i", ["latents"], ["images"])
Block2I2I = leaf("block2_i2i", ["latents", "image"], ["images"])
class AutoPromptEnhancer(ConditionalPipelineBlocks):
block_classes = [PromptEnhancer]
block_names = ["enhance"]
block_trigger_inputs = ["enhance_prompt"]
def select_block(self, enhance_prompt=None):
return "enhance" if enhance_prompt else None
class AutoBlock1(AutoPipelineBlocks):
block_classes = [Block1I2I, Block1T2I]
block_names = ["i2i", "t2i"]
block_trigger_inputs = ["image", None]
class AutoBlock2(AutoPipelineBlocks):
block_classes = [Block2I2I, Block2T2I]
block_names = ["i2i", "t2i"]
block_trigger_inputs = ["image", None]
class Blocks(SequentialPipelineBlocks):
model_name = "dummy"
block_classes = [AutoPromptEnhancer, AutoBlock1, AutoBlock2]
block_names = ["prompt_enhancer", "block1", "block2"]
_workflow_map = {"t2i": {"prompt": True}, "i2i": {"prompt": True, "image": True}}
blocks = Blocks()
for name in blocks.available_workflows:
wf = blocks.get_workflow(name)
print(f"get_workflow({name!r}) -> one {type(wf).__name__}: {[type(b).__name__ for b in wf.sub_blocks.values()]}")
print()
print("for comparison, get_execution_blocks with enhance_prompt=True:")
for kwargs in ({"prompt": True}, {"prompt": True, "image": True}):
resolved = blocks.get_execution_blocks(enhance_prompt=True, **kwargs)
print(f" {kwargs} -> {[type(b).__name__ for b in resolved.sub_blocks.values()]}")
get_workflow('t2i') -> one SequentialPipelineBlocks: ['block1_t2i', 'block2_t2i']
get_workflow('i2i') -> one SequentialPipelineBlocks: ['block1_i2i', 'block2_i2i']
for comparison, get_execution_blocks with enhance_prompt=True:
{'prompt': True} -> ['prompt_enhancer', 'block1_t2i', 'block2_t2i']
{'prompt': True, 'image': True} -> ['prompt_enhancer', 'block1_i2i', 'block2_i2i']
block1 and block2 are conditioned on image, a workflow input, so each workflow resolves them to the right branch. prompt_enhancer is conditioned on enhance_prompt, which no workflow entry mentions, so get_workflow resolves it as if the input had not been passed and drops it from both. In other words, t2i actually has two possible paths, with and without prompt_enhancer, and get_workflow returns one of them with no sign of the other. A pipeline loaded with workflow="t2i" therefore has no prompt_enhancer, none of its components, and no enhance_prompt input. But you should be able to enhance the prompt in the text-to-image workflow.
We could technically make get_workflow return both paths, but I think a simpler solution is to not let it happen: do not create conditional blocks on inputs that do not decide a workflow: a block can read the input and branch in its own code, or it can be an optional block that users insert into or pop out of their workflow.
So "workflow" is where I think we should draw the line, as a trade between convenience and clarity.
Proposed rule of thumb: a conditional block's triggers must be workflow inputs, or intermediates derived from them.
With that rule, every workflow entry decides every conditional block in the tree, so each workflow has exactly one path and get_workflow returns the whole truth.
Where we do this today
A scan of the 28 released blocksets for conditional triggers that are neither workflow inputs nor intermediates (scan_triggers.py below) finds these:
- LTX-2 / LTX-2.5
enable_prompt_enhancementand ernieuse_pe: opt-in prompt enhancers.get_workflow("text2video")on LTX-2 drops the block and itsprompt_enhancerandprocessorcomponents. - LTX-2 / LTX-2.5
conditioning_attention_strengthandconditioning_attention_mask: the in-context attention-mask step runs when the strength is below 1 or a mask is given.get_workflow("in_context")drops it, so a pipeline loaded by workflow cannot use either input. - SDXL
padding_mask_crop: the decode step picks the inpaint decoder (which pastes the crop back) onpadding_mask_croprather than onmask_image.get_workflow("inpainting")resolves to the plain decoder. - Cosmos 3
control_videos: thetransferworkflow is deliberately unavailable, so this one is known.
Under the rule the prompt enhancers become plain blocks that users pop out when they do not want them, the SDXL decode is keyed on mask_image and reads padding_mask_crop as a regular input, and the LTX-2 attention mask becomes a regular input of the in-context branch.
scan_triggers.py
"""List conditional-block triggers that are neither workflow inputs nor intermediates, for every released blockset."""
import importlib
import inspect
import pkgutil
import diffusers.modular_pipelines as mp
from diffusers.modular_pipelines.modular_pipeline import ConditionalPipelineBlocks, SequentialPipelineBlocks
def all_blocks(block):
yield block
for sub in getattr(block, "sub_blocks", {}).values():
yield from all_blocks(sub)
seen = set()
for modinfo in pkgutil.walk_packages(mp.__path__, mp.__name__ + "."):
if not modinfo.name.rsplit(".", 1)[-1].startswith("modular_blocks"):
continue
mod = importlib.import_module(modinfo.name)
for name, cls in inspect.getmembers(mod, inspect.isclass):
if cls.__module__ != mod.__name__ or not issubclass(cls, SequentialPipelineBlocks):
continue
if getattr(cls, "_workflow_map", None) is None or cls in seen:
continue
seen.add(cls)
blocks = cls()
keys = set()
for entry in blocks._workflow_map.values():
for d in entry if isinstance(entry, tuple) else (entry,):
keys |= set(d)
produced = {o.name for b in all_blocks(blocks) for o in getattr(b, "intermediate_outputs", [])}
triggers = {
t
for b in all_blocks(blocks)
if isinstance(b, ConditionalPipelineBlocks)
for t in b.block_trigger_inputs
if t
}
bad = sorted(triggers - keys - produced)
if bad:
print(f"{name}: {bad}")
print(f"checked {len(seen)} blocksets")
Follow-ups
- say so in the modular guide and in the
ConditionalPipelineBlocksdocstring; - a test over the released blocksets that every trigger of a conditional block is a workflow-map key or an intermediate output of a block in the tree, which is what the scan above does;
- migrate the cases listed above.
Posted by Claude Code on behalf of @yiyixuxu, who reviewed and approved this text.
- Ngôn ngữ chính
- Python
- Star
- 34.8k
- Fork
- 7.4k
- Merge trung bình
- 2 ngày 6 giờ
- Pull request đã merge (30 ngày)
- 54
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của huggingface/diffusers
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
huggingface/diffusers#15125 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
`encode_video` truncates a fractional `fps`, so the video runs longer than its audioCó thể đã có người làm @guptaishaan đã nhận 3 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 83/100
huggingface/diffusers#15104 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
VaeImageProcessor.denormalize() raises AttributeError for NumPy inputsCó thể đã có người làm @tonycoder-hub đã nhận 14 ngày trước. Đang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
huggingface/diffusers#15011 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
`ModelMixin.from_pretrained` on a sharded checkpoint fails under `HF_HUB_OFFLINE=1` even when fully cached (`_get_checkpoint_shard_files` still calls `model_info`)Có thể đã có người làm @Subodh-17 đã nhận 19 ngày trước. Đang mởbug models
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
huggingface/diffusers#14952 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
UniPCMultistepScheduler fails in torch.linalg.solve under a float64 default dtype: the unit entry of rks takes the default dtypeCó thể đã có người làm @deep1blue đã nhận 7 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
huggingface/diffusers#14888 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của huggingface/diffusers
Issue tương tự
-
request-theme
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 66/100
LizardByte/ThemerrDB#9575 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Author Page: noshin-nawalĐang mởauthor-page correction
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
acl-org/acl-anthology#10599 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
zilliztech/memsearch#818 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[FAQ] Commit ID for ProjectsCó thể đã có người làm @github-actions đã nhận hôm nay. Đang mởfaq-proposal
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 84/100
DataTalksClub/faq#503 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
README.zh-CN/ja/es still hard-code 501 test counts after README.md went count-freeCó thể đã có người làm @Aaqibhafeezkhan đã nhận hôm nay. Đang mởbeginner documentation good first issue
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
BistaDinesh03/critique#164 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày