
深入 Diffusers 模块化管线之 LoopSequentialPipelineBlocks构建可迭代去噪循环的循环包装器【免费下载链接】diffusers Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusersLoopSequentialPipelineBlocks是 Diffusers 模块化管线Modular Pipelines体系中的核心多块类型之一它把一组 [~modular_pipelines.ModularPipelineBlocks] 组织成循环结构让数据通过intermediate_inputs/intermediate_outputs在每次迭代中循环流动是构建默认就是迭代式去噪循环这类管线的标准手段。本指南将带你从零创建一个循环包装器与循环块并用from_blocks_dict组装出可运行的LoopSequentialPipelineBlocks最后结合仓库中 Wan 视频模型的真实去噪循环源码理解循环结构在生成管线中的实际落地方式。从多块类型说起顺序 vs 循环在模块化管线的世界观里管道是由一个个 [~modular_pipelines.ModularPipelineBlocks] 拼装而成的而多块类型multi-block types负责定义这些块之间的编排方式SequentialPipelineBlocks将多个块按顺序串接数据线性地从上一个块流向下一个块LoopSequentialPipelineBlocks将多个块以循环方式组合数据在块之间循环流动每个块被迭代执行多次此外还有ConditionalPipelineBlocks/AutoPipelineBlocks这类根据输入条件选择执行分支的多块类型。LoopSequentialPipelineBlocks之所以重要是因为扩散模型的去噪过程本身就是一次迭代同一组预测噪声 → 更新 latent的操作要在num_inference_steps个时间步上反复执行。如果只用顺序块你就不得不把去噪循环手写进某个单一块内部而循环块则把循环体拆成多个可独立替换的子块循环逻辑与每轮具体做什么被彻底解耦。本文配套的模块化管线总览与快速上手可以帮你先建立整体认知本文聚焦LoopSequentialPipelineBlocks这一个点。循环包装器定义循环结构与迭代逻辑[~modular_pipelines.LoopSequentialPipelineBlocks] 也被称为循环包装器loop wrapper因为它负责定义三件事循环结构、迭代变量和配置。你不需要像写普通块那样覆写inputs/intermediate_outputs属性而是覆写一套以loop_前缀命名的等价属性。循环包装器内需要定义的变量如下表所示变量作用对应普通块的属性loop_inputs用户提供的值例如迭代步数num_steps[~modular_pipelines.ModularPipelineBlocks.inputs]loop_intermediate_inputs来自 [~modular_pipelines.PipelineState] 的中间变量供循环体消费[~modular_pipelines.ModularPipelineBlocks.intermediate_inputs]loop_intermediate_outputs由循环内块创建、最终写回 [~modular_pipelines.PipelineState] 的新中间变量[~modular_pipelines.ModularPipelineBlocks.intermediate_outputs]__call__定义循环结构和迭代逻辑是整个包装器的核心——下面的示例定义了一个名为LoopWrapper的最小循环包装器它声明了唯一的循环输入num_steps并在__call__中用一个for循环迭代每次迭代调用self.loop_step(...)按顺序执行所有注册的子块import torch from diffusers.modular_pipelines import LoopSequentialPipelineBlocks, ModularPipelineBlocks, InputParam, OutputParam class LoopWrapper(LoopSequentialPipelineBlocks): model_name test property def description(self): return Im a loop!! property def loop_inputs(self): return [InputParam(namenum_steps)] torch.no_grad() def __call__(self, components, state): block_state self.get_block_state(state) # 循环结构 - 可以根据您的需求定制 for i in range(block_state.num_steps): # loop_step 按顺序执行所有注册的块 components, block_state self.loop_step(components, block_state, ii) self.set_block_state(state, block_state) return components, state注意几个关键细节loop_step与set_block_state的分工loop_step内部已经遍历并执行了sub_blocks中的全部子块但整个循环结束后包装器还要显式调用self.set_block_state(state, block_state)把循环体内累积的BlockState写回全局的PipelineState这是循环包装器与普通块__call__结构上最大的不同。迭代参数可以透传循环包装器可以把额外的参数如当前迭代索引i通过loop_step(components, block_state, ii)传给循环块。从源码可以看到loop_step的实现是依次调用每个子块并把**kwargs原样透传def loop_step(self, components, state: PipelineState, **kwargs): for block_name, block in self.sub_blocks.items(): try: components, state block(components, state, **kwargs) except Exception as e: ... return components, state因此循环块只要在__call__签名里声明i: int甚至t: torch.Tensor就能拿到每次迭代的上下文。循环块每次迭代执行的叶子单元循环块本质上仍然是一个 [~modular_pipelines.ModularPipelineBlocks]但与普通块相比它的__call__方法行为有三个显著区别它从循环包装器接收调用而不是从顺序容器接收调用它直接与 [~modular_pipelines.BlockState] 协作而不是与 [~modular_pipelines.PipelineState] 协作它不需要自行检索或更新 [~modular_pipelines.BlockState] —— 该状态由循环包装器统一管理。循环块共享同一个 [~modular_pipelines.BlockState]这正是值可以在循环的每次迭代中累积和变化的机制来源WanLoopBeforeDenoiser准备本轮输入、WanLoopDenoiser算出noise_pred、WanLoopAfterDenoiser更新latents同一个BlockState在迭代间被反复读写。class LoopBlock(ModularPipelineBlocks): model_name test property def inputs(self): return [InputParam(namex)] property def intermediate_outputs(self): # 这个块产生的输出 return [OutputParam(namex)] property def description(self): return 我是一个在LoopWrapper类内部使用的块 def __call__(self, components, block_state, i: int): block_state.x 1 return components, block_state上面的LoopBlock每被调用一次就把block_state.x加一。注意它的__call__签名是(self, components, block_state, i: int)接收的是block_state而不是state且直接返回(components, block_state)——这正是它与普通块的__call__结构检索BlockState→ 计算 → 写回PipelineState的差异所在。组装用 from_blocks_dict 注册循环块要得到一个可运行的 [~modular_pipelines.LoopSequentialPipelineBlocks]使用类方法 [~modular_pipelines.LoopSequentialPipelineBlocks.from_blocks_dict] 把循环块注册进循环包装器即可。blocks_dict的键是块名值可以是块类会自动实例化也可以是块实例loop LoopWrapper.from_blocks_dict({block1: LoopBlock})如果想要在每次迭代中运行多个块往字典里继续添加即可。这允许你在完全不改动循环逻辑本身的前提下自由增删、替换循环体内的步骤loop LoopWrapper.from_blocks_dict({block1: LoopBlock(), block2: LoopBlock})从源码实现看from_blocks_dict会先实例化传入的类再统一设置block_classes、block_names与sub_blocksclassmethod def from_blocks_dict(cls, blocks_dict: dict[str, Any]) - LoopSequentialPipelineBlocks: instance cls() sub_blocks InsertableDict() for name, block in blocks_dict.items(): if inspect.isclass(block): sub_blocks[name] block() else: sub_blocks[name] block instance.block_classes [block.__class__ for block in blocks_dict.values()] instance.block_names list(blocks_dict.keys()) instance.sub_blocks blocks_dict return instance一个值得注意的约束是LoopSequentialPipelineBlocks.__init__会校验所有子块必须是叶子块leaf blocks即不允许子块自身再嵌套sub_blocks否则直接抛出ValueError。这是因为循环体内的每一步应当是原子操作嵌套容器会让循环语义变得不可控。因此循环块只能是普通的ModularPipelineBlocks不能是SequentialPipelineBlocks或另一个ConditionalPipelineBlocks。源码视角LoopSequentialPipelineBlocks 的完整契约回到src/diffusers/modular_pipelines/modular_pipeline.py中LoopSequentialPipelineBlocks类的实现可以提炼出它区别于SequentialPipelineBlocks的完整行为契约输入合并_get_inputs()先并入loop_inputs再按顺序并入各子块的输入跳过那些已被中间输出覆盖的变量required_inputs则取首个子块必填 ∪ 循环必填 ∪ 其余子块必填的并集。中间输出合并intermediate_outputs先合并所有子块的输出再补充loop_intermediate_outputs中未出现的新变量outputs直接取最后一个子块的intermediate_outputs。组件与配置透传expected_components/expected_configs在聚合全部子块的声明之外还会追加循环包装器自己声明的loop_expected_components/loop_expected_configs——这使得调度器、guider 等循环级组件可以挂在包装器上而不是重复声明在每个子块里。__call__必须由子类实现基类只抛出NotImplementedError__call__method needs to be implemented by the subclass也就是说循环的具体迭代方式如何遍历 timesteps、是否显示进度条、warmup 步数如何计算完全留给你的包装器类定制。进度条工具基类提供了progress_bar(iterableNone, totalNone)与set_progress_bar_config(**kwargs)方便在长循环中渲染 tqdm 进度条并已用torch.compiler.disable标记避免与 torch.compile 冲突。真实案例Wan 视频模型的迭代去噪循环仓库中最能说明LoopSequentialPipelineBlocks实战价值的案例是 Wan 系列视频生成模型的去噪步骤位于src/diffusers/modular_pipelines/wan/denoise.py。首先定义一个循环包装器WanDenoiseLoopWrapper它声明了循环输入timesteps与num_inference_steps并定制了循环逻辑遍历每个 timestept把i和t同时透传给循环体同时维护进度条与 warmup 步数class WanDenoiseLoopWrapper(LoopSequentialPipelineBlocks): model_name wan property def loop_inputs(self): return [ InputParam(timesteps, requiredTrue, type_hinttorch.Tensor, description...), InputParam(num_inference_steps, requiredTrue, type_hintint, description...), ] torch.no_grad() def __call__(self, components, state): block_state self.get_block_state(state) block_state.num_warmup_steps max( len(block_state.timesteps) - block_state.num_inference_steps * components.scheduler.order, 0 ) with self.progress_bar(totalblock_state.num_inference_steps) as progress_bar: for i, t in enumerate(block_state.timesteps): components, block_state self.loop_step(components, block_state, ii, tt) if i len(block_state.timesteps) - 1 or ( (i 1) block_state.num_warmup_steps and (i 1) % components.scheduler.order 0 ): progress_bar.update() self.set_block_state(state, block_state) return components, state然后在包装器的子类里通过block_classesblock_names声明循环体的三个步骤把每轮迭代做什么配置化class WanDenoiseStep(WanDenoiseLoopWrapper): block_classes [ WanLoopBeforeDenoiser, # 准备 latent_model_input WanLoopDenoiser( # 带 guidance 调用 transformer 预测噪声 guider_input_fields{ encoder_hidden_states: (prompt_embeds, negative_prompt_embeds), } ), WanLoopAfterDenoiser, # 用 scheduler.step 更新 latents ] block_names [before_denoiser, denoiser, after_denoiser]三个子块正是叶子块 共享BlockState的典范WanLoopBeforeDenoiser.__call__(self, components, block_state, i, t)计算latent_model_inputWanLoopDenoiser通过 guider 拆分条件/无条件 batch 后调用transformer得到noise_predWanLoopAfterDenoiser调用components.scheduler.step(...)更新latents。同一个block_state.latents在数百次迭代中被反复读出、更新、写回最终得到去噪完成的 latent。Wan2.2 版本Wan22DenoiseStep只需替换其中的 denoiser 子块就能在同一循环逻辑下切换到双 transformer / 双 guider 的高噪声与低噪声两阶段去噪loop_expected_configs中的boundary_ratio即用于切分这两个阶段。除 Wan 之外仓库中 FLUX、FLUX2、SDXL、SD3、LTX、HunyuanVideo1.5、QwenImage、Krea2、Ideogram4、MiniMax 等多个模型的denoise.py也都采用了同样的循环包装器模式如src/diffusers/modular_pipelines/flux/denoise.py印证了循环包装器定义迭代结构、循环块定义每步操作这一设计在图像与视频生成管线中的普适性。与顺序块的分工总结维度SequentialPipelineBlocksLoopSequentialPipelineBlocks数据流向线性一次执行循环多次迭代执行子块状态管理每个子块自行get/set_block_state包装器统一管理共享BlockState子块直接使用子块__call__签名(components, state)(components, block_state, i, ...)子块约束无特殊约束必须是叶子块不能再嵌套sub_blocks典型用途编码 → 去噪 → 解码 的单遍步骤逐 timestep 迭代的去噪循环对于大多数一次性通过的步骤如文本编码、latent 准备、VAE 解码选择SequentialPipelineBlocks对于天然迭代的计算去噪、扩散反向过程则应该使用LoopSequentialPipelineBlocks。两者的组合使用方式——顺序块把循环包装器当作其中一步嵌入整条管线——正是模块化 Diffusers 用统一而可组合的积木搭建完整生成工作流的核心理念。相关测试可参考tests/modular_pipelines/test_modular_pipelines_custom_blocks.py中对from_blocks_dict与循环块的验证用例。【免费下载链接】diffusers Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考