diffusers 0.33.1__py3-none-any.whl → 0.35.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (551) hide show
  1. diffusers/__init__.py +145 -1
  2. diffusers/callbacks.py +35 -0
  3. diffusers/commands/__init__.py +1 -1
  4. diffusers/commands/custom_blocks.py +134 -0
  5. diffusers/commands/diffusers_cli.py +3 -1
  6. diffusers/commands/env.py +1 -1
  7. diffusers/commands/fp16_safetensors.py +2 -2
  8. diffusers/configuration_utils.py +11 -2
  9. diffusers/dependency_versions_check.py +1 -1
  10. diffusers/dependency_versions_table.py +3 -3
  11. diffusers/experimental/rl/value_guided_sampling.py +1 -1
  12. diffusers/guiders/__init__.py +41 -0
  13. diffusers/guiders/adaptive_projected_guidance.py +188 -0
  14. diffusers/guiders/auto_guidance.py +190 -0
  15. diffusers/guiders/classifier_free_guidance.py +141 -0
  16. diffusers/guiders/classifier_free_zero_star_guidance.py +152 -0
  17. diffusers/guiders/frequency_decoupled_guidance.py +327 -0
  18. diffusers/guiders/guider_utils.py +309 -0
  19. diffusers/guiders/perturbed_attention_guidance.py +271 -0
  20. diffusers/guiders/skip_layer_guidance.py +262 -0
  21. diffusers/guiders/smoothed_energy_guidance.py +251 -0
  22. diffusers/guiders/tangential_classifier_free_guidance.py +143 -0
  23. diffusers/hooks/__init__.py +17 -0
  24. diffusers/hooks/_common.py +56 -0
  25. diffusers/hooks/_helpers.py +293 -0
  26. diffusers/hooks/faster_cache.py +9 -8
  27. diffusers/hooks/first_block_cache.py +259 -0
  28. diffusers/hooks/group_offloading.py +332 -227
  29. diffusers/hooks/hooks.py +58 -3
  30. diffusers/hooks/layer_skip.py +263 -0
  31. diffusers/hooks/layerwise_casting.py +5 -10
  32. diffusers/hooks/pyramid_attention_broadcast.py +15 -12
  33. diffusers/hooks/smoothed_energy_guidance_utils.py +167 -0
  34. diffusers/hooks/utils.py +43 -0
  35. diffusers/image_processor.py +7 -2
  36. diffusers/loaders/__init__.py +10 -0
  37. diffusers/loaders/ip_adapter.py +260 -18
  38. diffusers/loaders/lora_base.py +261 -127
  39. diffusers/loaders/lora_conversion_utils.py +657 -35
  40. diffusers/loaders/lora_pipeline.py +2778 -1246
  41. diffusers/loaders/peft.py +78 -112
  42. diffusers/loaders/single_file.py +2 -2
  43. diffusers/loaders/single_file_model.py +64 -15
  44. diffusers/loaders/single_file_utils.py +395 -7
  45. diffusers/loaders/textual_inversion.py +3 -2
  46. diffusers/loaders/transformer_flux.py +10 -11
  47. diffusers/loaders/transformer_sd3.py +8 -3
  48. diffusers/loaders/unet.py +24 -21
  49. diffusers/loaders/unet_loader_utils.py +6 -3
  50. diffusers/loaders/utils.py +1 -1
  51. diffusers/models/__init__.py +23 -1
  52. diffusers/models/activations.py +5 -5
  53. diffusers/models/adapter.py +2 -3
  54. diffusers/models/attention.py +488 -7
  55. diffusers/models/attention_dispatch.py +1218 -0
  56. diffusers/models/attention_flax.py +10 -10
  57. diffusers/models/attention_processor.py +113 -667
  58. diffusers/models/auto_model.py +49 -12
  59. diffusers/models/autoencoders/__init__.py +2 -0
  60. diffusers/models/autoencoders/autoencoder_asym_kl.py +4 -4
  61. diffusers/models/autoencoders/autoencoder_dc.py +17 -4
  62. diffusers/models/autoencoders/autoencoder_kl.py +5 -5
  63. diffusers/models/autoencoders/autoencoder_kl_allegro.py +4 -4
  64. diffusers/models/autoencoders/autoencoder_kl_cogvideox.py +6 -6
  65. diffusers/models/autoencoders/autoencoder_kl_cosmos.py +1110 -0
  66. diffusers/models/autoencoders/autoencoder_kl_hunyuan_video.py +2 -2
  67. diffusers/models/autoencoders/autoencoder_kl_ltx.py +3 -3
  68. diffusers/models/autoencoders/autoencoder_kl_magvit.py +4 -4
  69. diffusers/models/autoencoders/autoencoder_kl_mochi.py +3 -3
  70. diffusers/models/autoencoders/autoencoder_kl_qwenimage.py +1070 -0
  71. diffusers/models/autoencoders/autoencoder_kl_temporal_decoder.py +4 -4
  72. diffusers/models/autoencoders/autoencoder_kl_wan.py +626 -62
  73. diffusers/models/autoencoders/autoencoder_oobleck.py +1 -1
  74. diffusers/models/autoencoders/autoencoder_tiny.py +3 -3
  75. diffusers/models/autoencoders/consistency_decoder_vae.py +1 -1
  76. diffusers/models/autoencoders/vae.py +13 -2
  77. diffusers/models/autoencoders/vq_model.py +2 -2
  78. diffusers/models/cache_utils.py +32 -10
  79. diffusers/models/controlnet.py +1 -1
  80. diffusers/models/controlnet_flux.py +1 -1
  81. diffusers/models/controlnet_sd3.py +1 -1
  82. diffusers/models/controlnet_sparsectrl.py +1 -1
  83. diffusers/models/controlnets/__init__.py +1 -0
  84. diffusers/models/controlnets/controlnet.py +3 -3
  85. diffusers/models/controlnets/controlnet_flax.py +1 -1
  86. diffusers/models/controlnets/controlnet_flux.py +21 -20
  87. diffusers/models/controlnets/controlnet_hunyuan.py +2 -2
  88. diffusers/models/controlnets/controlnet_sana.py +290 -0
  89. diffusers/models/controlnets/controlnet_sd3.py +1 -1
  90. diffusers/models/controlnets/controlnet_sparsectrl.py +2 -2
  91. diffusers/models/controlnets/controlnet_union.py +5 -5
  92. diffusers/models/controlnets/controlnet_xs.py +7 -7
  93. diffusers/models/controlnets/multicontrolnet.py +4 -5
  94. diffusers/models/controlnets/multicontrolnet_union.py +5 -6
  95. diffusers/models/downsampling.py +2 -2
  96. diffusers/models/embeddings.py +36 -46
  97. diffusers/models/embeddings_flax.py +2 -2
  98. diffusers/models/lora.py +3 -3
  99. diffusers/models/model_loading_utils.py +233 -1
  100. diffusers/models/modeling_flax_utils.py +1 -2
  101. diffusers/models/modeling_utils.py +203 -108
  102. diffusers/models/normalization.py +4 -4
  103. diffusers/models/resnet.py +2 -2
  104. diffusers/models/resnet_flax.py +1 -1
  105. diffusers/models/transformers/__init__.py +7 -0
  106. diffusers/models/transformers/auraflow_transformer_2d.py +70 -24
  107. diffusers/models/transformers/cogvideox_transformer_3d.py +1 -1
  108. diffusers/models/transformers/consisid_transformer_3d.py +1 -1
  109. diffusers/models/transformers/dit_transformer_2d.py +2 -2
  110. diffusers/models/transformers/dual_transformer_2d.py +1 -1
  111. diffusers/models/transformers/hunyuan_transformer_2d.py +2 -2
  112. diffusers/models/transformers/latte_transformer_3d.py +4 -5
  113. diffusers/models/transformers/lumina_nextdit2d.py +2 -2
  114. diffusers/models/transformers/pixart_transformer_2d.py +3 -3
  115. diffusers/models/transformers/prior_transformer.py +1 -1
  116. diffusers/models/transformers/sana_transformer.py +8 -3
  117. diffusers/models/transformers/stable_audio_transformer.py +5 -9
  118. diffusers/models/transformers/t5_film_transformer.py +3 -3
  119. diffusers/models/transformers/transformer_2d.py +1 -1
  120. diffusers/models/transformers/transformer_allegro.py +1 -1
  121. diffusers/models/transformers/transformer_chroma.py +641 -0
  122. diffusers/models/transformers/transformer_cogview3plus.py +5 -10
  123. diffusers/models/transformers/transformer_cogview4.py +353 -27
  124. diffusers/models/transformers/transformer_cosmos.py +586 -0
  125. diffusers/models/transformers/transformer_flux.py +376 -138
  126. diffusers/models/transformers/transformer_hidream_image.py +942 -0
  127. diffusers/models/transformers/transformer_hunyuan_video.py +12 -8
  128. diffusers/models/transformers/transformer_hunyuan_video_framepack.py +416 -0
  129. diffusers/models/transformers/transformer_ltx.py +105 -24
  130. diffusers/models/transformers/transformer_lumina2.py +1 -1
  131. diffusers/models/transformers/transformer_mochi.py +1 -1
  132. diffusers/models/transformers/transformer_omnigen.py +2 -2
  133. diffusers/models/transformers/transformer_qwenimage.py +645 -0
  134. diffusers/models/transformers/transformer_sd3.py +7 -7
  135. diffusers/models/transformers/transformer_skyreels_v2.py +607 -0
  136. diffusers/models/transformers/transformer_temporal.py +1 -1
  137. diffusers/models/transformers/transformer_wan.py +316 -87
  138. diffusers/models/transformers/transformer_wan_vace.py +387 -0
  139. diffusers/models/unets/unet_1d.py +1 -1
  140. diffusers/models/unets/unet_1d_blocks.py +1 -1
  141. diffusers/models/unets/unet_2d.py +1 -1
  142. diffusers/models/unets/unet_2d_blocks.py +1 -1
  143. diffusers/models/unets/unet_2d_blocks_flax.py +8 -7
  144. diffusers/models/unets/unet_2d_condition.py +4 -3
  145. diffusers/models/unets/unet_2d_condition_flax.py +2 -2
  146. diffusers/models/unets/unet_3d_blocks.py +1 -1
  147. diffusers/models/unets/unet_3d_condition.py +3 -3
  148. diffusers/models/unets/unet_i2vgen_xl.py +3 -3
  149. diffusers/models/unets/unet_kandinsky3.py +1 -1
  150. diffusers/models/unets/unet_motion_model.py +2 -2
  151. diffusers/models/unets/unet_stable_cascade.py +1 -1
  152. diffusers/models/upsampling.py +2 -2
  153. diffusers/models/vae_flax.py +2 -2
  154. diffusers/models/vq_model.py +1 -1
  155. diffusers/modular_pipelines/__init__.py +83 -0
  156. diffusers/modular_pipelines/components_manager.py +1068 -0
  157. diffusers/modular_pipelines/flux/__init__.py +66 -0
  158. diffusers/modular_pipelines/flux/before_denoise.py +689 -0
  159. diffusers/modular_pipelines/flux/decoders.py +109 -0
  160. diffusers/modular_pipelines/flux/denoise.py +227 -0
  161. diffusers/modular_pipelines/flux/encoders.py +412 -0
  162. diffusers/modular_pipelines/flux/modular_blocks.py +181 -0
  163. diffusers/modular_pipelines/flux/modular_pipeline.py +59 -0
  164. diffusers/modular_pipelines/modular_pipeline.py +2446 -0
  165. diffusers/modular_pipelines/modular_pipeline_utils.py +672 -0
  166. diffusers/modular_pipelines/node_utils.py +665 -0
  167. diffusers/modular_pipelines/stable_diffusion_xl/__init__.py +77 -0
  168. diffusers/modular_pipelines/stable_diffusion_xl/before_denoise.py +1874 -0
  169. diffusers/modular_pipelines/stable_diffusion_xl/decoders.py +208 -0
  170. diffusers/modular_pipelines/stable_diffusion_xl/denoise.py +771 -0
  171. diffusers/modular_pipelines/stable_diffusion_xl/encoders.py +887 -0
  172. diffusers/modular_pipelines/stable_diffusion_xl/modular_blocks.py +380 -0
  173. diffusers/modular_pipelines/stable_diffusion_xl/modular_pipeline.py +365 -0
  174. diffusers/modular_pipelines/wan/__init__.py +66 -0
  175. diffusers/modular_pipelines/wan/before_denoise.py +365 -0
  176. diffusers/modular_pipelines/wan/decoders.py +105 -0
  177. diffusers/modular_pipelines/wan/denoise.py +261 -0
  178. diffusers/modular_pipelines/wan/encoders.py +242 -0
  179. diffusers/modular_pipelines/wan/modular_blocks.py +144 -0
  180. diffusers/modular_pipelines/wan/modular_pipeline.py +90 -0
  181. diffusers/pipelines/__init__.py +68 -6
  182. diffusers/pipelines/allegro/pipeline_allegro.py +11 -11
  183. diffusers/pipelines/amused/pipeline_amused.py +7 -6
  184. diffusers/pipelines/amused/pipeline_amused_img2img.py +6 -5
  185. diffusers/pipelines/amused/pipeline_amused_inpaint.py +6 -5
  186. diffusers/pipelines/animatediff/pipeline_animatediff.py +6 -6
  187. diffusers/pipelines/animatediff/pipeline_animatediff_controlnet.py +6 -6
  188. diffusers/pipelines/animatediff/pipeline_animatediff_sdxl.py +16 -15
  189. diffusers/pipelines/animatediff/pipeline_animatediff_sparsectrl.py +6 -6
  190. diffusers/pipelines/animatediff/pipeline_animatediff_video2video.py +5 -5
  191. diffusers/pipelines/animatediff/pipeline_animatediff_video2video_controlnet.py +5 -5
  192. diffusers/pipelines/audioldm/pipeline_audioldm.py +8 -7
  193. diffusers/pipelines/audioldm2/modeling_audioldm2.py +1 -1
  194. diffusers/pipelines/audioldm2/pipeline_audioldm2.py +22 -13
  195. diffusers/pipelines/aura_flow/pipeline_aura_flow.py +48 -11
  196. diffusers/pipelines/auto_pipeline.py +23 -20
  197. diffusers/pipelines/blip_diffusion/modeling_blip2.py +1 -1
  198. diffusers/pipelines/blip_diffusion/modeling_ctx_clip.py +2 -2
  199. diffusers/pipelines/blip_diffusion/pipeline_blip_diffusion.py +11 -10
  200. diffusers/pipelines/chroma/__init__.py +49 -0
  201. diffusers/pipelines/chroma/pipeline_chroma.py +949 -0
  202. diffusers/pipelines/chroma/pipeline_chroma_img2img.py +1034 -0
  203. diffusers/pipelines/chroma/pipeline_output.py +21 -0
  204. diffusers/pipelines/cogvideo/pipeline_cogvideox.py +17 -16
  205. diffusers/pipelines/cogvideo/pipeline_cogvideox_fun_control.py +17 -16
  206. diffusers/pipelines/cogvideo/pipeline_cogvideox_image2video.py +18 -17
  207. diffusers/pipelines/cogvideo/pipeline_cogvideox_video2video.py +17 -16
  208. diffusers/pipelines/cogview3/pipeline_cogview3plus.py +9 -9
  209. diffusers/pipelines/cogview4/pipeline_cogview4.py +23 -22
  210. diffusers/pipelines/cogview4/pipeline_cogview4_control.py +7 -7
  211. diffusers/pipelines/consisid/consisid_utils.py +2 -2
  212. diffusers/pipelines/consisid/pipeline_consisid.py +8 -8
  213. diffusers/pipelines/consistency_models/pipeline_consistency_models.py +1 -1
  214. diffusers/pipelines/controlnet/pipeline_controlnet.py +7 -7
  215. diffusers/pipelines/controlnet/pipeline_controlnet_blip_diffusion.py +11 -10
  216. diffusers/pipelines/controlnet/pipeline_controlnet_img2img.py +7 -7
  217. diffusers/pipelines/controlnet/pipeline_controlnet_inpaint.py +7 -7
  218. diffusers/pipelines/controlnet/pipeline_controlnet_inpaint_sd_xl.py +14 -14
  219. diffusers/pipelines/controlnet/pipeline_controlnet_sd_xl.py +10 -6
  220. diffusers/pipelines/controlnet/pipeline_controlnet_sd_xl_img2img.py +13 -13
  221. diffusers/pipelines/controlnet/pipeline_controlnet_union_inpaint_sd_xl.py +226 -107
  222. diffusers/pipelines/controlnet/pipeline_controlnet_union_sd_xl.py +12 -8
  223. diffusers/pipelines/controlnet/pipeline_controlnet_union_sd_xl_img2img.py +207 -105
  224. diffusers/pipelines/controlnet/pipeline_flax_controlnet.py +1 -1
  225. diffusers/pipelines/controlnet_hunyuandit/pipeline_hunyuandit_controlnet.py +8 -8
  226. diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet.py +7 -7
  227. diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet_inpainting.py +7 -7
  228. diffusers/pipelines/controlnet_xs/pipeline_controlnet_xs.py +12 -10
  229. diffusers/pipelines/controlnet_xs/pipeline_controlnet_xs_sd_xl.py +9 -7
  230. diffusers/pipelines/cosmos/__init__.py +54 -0
  231. diffusers/pipelines/cosmos/pipeline_cosmos2_text2image.py +673 -0
  232. diffusers/pipelines/cosmos/pipeline_cosmos2_video2world.py +792 -0
  233. diffusers/pipelines/cosmos/pipeline_cosmos_text2world.py +664 -0
  234. diffusers/pipelines/cosmos/pipeline_cosmos_video2world.py +826 -0
  235. diffusers/pipelines/cosmos/pipeline_output.py +40 -0
  236. diffusers/pipelines/dance_diffusion/pipeline_dance_diffusion.py +5 -4
  237. diffusers/pipelines/ddim/pipeline_ddim.py +4 -4
  238. diffusers/pipelines/ddpm/pipeline_ddpm.py +1 -1
  239. diffusers/pipelines/deepfloyd_if/pipeline_if.py +10 -10
  240. diffusers/pipelines/deepfloyd_if/pipeline_if_img2img.py +10 -10
  241. diffusers/pipelines/deepfloyd_if/pipeline_if_img2img_superresolution.py +10 -10
  242. diffusers/pipelines/deepfloyd_if/pipeline_if_inpainting.py +10 -10
  243. diffusers/pipelines/deepfloyd_if/pipeline_if_inpainting_superresolution.py +10 -10
  244. diffusers/pipelines/deepfloyd_if/pipeline_if_superresolution.py +10 -10
  245. diffusers/pipelines/deprecated/alt_diffusion/pipeline_alt_diffusion.py +8 -8
  246. diffusers/pipelines/deprecated/alt_diffusion/pipeline_alt_diffusion_img2img.py +5 -5
  247. diffusers/pipelines/deprecated/audio_diffusion/mel.py +1 -1
  248. diffusers/pipelines/deprecated/audio_diffusion/pipeline_audio_diffusion.py +3 -3
  249. diffusers/pipelines/deprecated/latent_diffusion_uncond/pipeline_latent_diffusion_uncond.py +1 -1
  250. diffusers/pipelines/deprecated/pndm/pipeline_pndm.py +2 -2
  251. diffusers/pipelines/deprecated/repaint/pipeline_repaint.py +4 -3
  252. diffusers/pipelines/deprecated/score_sde_ve/pipeline_score_sde_ve.py +1 -1
  253. diffusers/pipelines/deprecated/spectrogram_diffusion/continuous_encoder.py +1 -1
  254. diffusers/pipelines/deprecated/spectrogram_diffusion/midi_utils.py +1 -1
  255. diffusers/pipelines/deprecated/spectrogram_diffusion/notes_encoder.py +1 -1
  256. diffusers/pipelines/deprecated/spectrogram_diffusion/pipeline_spectrogram_diffusion.py +1 -1
  257. diffusers/pipelines/deprecated/stable_diffusion_variants/pipeline_cycle_diffusion.py +8 -8
  258. diffusers/pipelines/deprecated/stable_diffusion_variants/pipeline_onnx_stable_diffusion_inpaint_legacy.py +9 -9
  259. diffusers/pipelines/deprecated/stable_diffusion_variants/pipeline_stable_diffusion_inpaint_legacy.py +10 -10
  260. diffusers/pipelines/deprecated/stable_diffusion_variants/pipeline_stable_diffusion_model_editing.py +10 -8
  261. diffusers/pipelines/deprecated/stable_diffusion_variants/pipeline_stable_diffusion_paradigms.py +5 -5
  262. diffusers/pipelines/deprecated/stable_diffusion_variants/pipeline_stable_diffusion_pix2pix_zero.py +18 -18
  263. diffusers/pipelines/deprecated/stochastic_karras_ve/pipeline_stochastic_karras_ve.py +1 -1
  264. diffusers/pipelines/deprecated/versatile_diffusion/modeling_text_unet.py +2 -2
  265. diffusers/pipelines/deprecated/versatile_diffusion/pipeline_versatile_diffusion.py +6 -6
  266. diffusers/pipelines/deprecated/versatile_diffusion/pipeline_versatile_diffusion_dual_guided.py +5 -5
  267. diffusers/pipelines/deprecated/versatile_diffusion/pipeline_versatile_diffusion_image_variation.py +5 -5
  268. diffusers/pipelines/deprecated/versatile_diffusion/pipeline_versatile_diffusion_text_to_image.py +5 -5
  269. diffusers/pipelines/deprecated/vq_diffusion/pipeline_vq_diffusion.py +1 -1
  270. diffusers/pipelines/dit/pipeline_dit.py +4 -2
  271. diffusers/pipelines/easyanimate/pipeline_easyanimate.py +4 -4
  272. diffusers/pipelines/easyanimate/pipeline_easyanimate_control.py +4 -4
  273. diffusers/pipelines/easyanimate/pipeline_easyanimate_inpaint.py +7 -6
  274. diffusers/pipelines/flux/__init__.py +4 -0
  275. diffusers/pipelines/flux/modeling_flux.py +1 -1
  276. diffusers/pipelines/flux/pipeline_flux.py +37 -36
  277. diffusers/pipelines/flux/pipeline_flux_control.py +9 -9
  278. diffusers/pipelines/flux/pipeline_flux_control_img2img.py +7 -7
  279. diffusers/pipelines/flux/pipeline_flux_control_inpaint.py +7 -7
  280. diffusers/pipelines/flux/pipeline_flux_controlnet.py +7 -7
  281. diffusers/pipelines/flux/pipeline_flux_controlnet_image_to_image.py +31 -23
  282. diffusers/pipelines/flux/pipeline_flux_controlnet_inpainting.py +3 -2
  283. diffusers/pipelines/flux/pipeline_flux_fill.py +7 -7
  284. diffusers/pipelines/flux/pipeline_flux_img2img.py +40 -7
  285. diffusers/pipelines/flux/pipeline_flux_inpaint.py +12 -7
  286. diffusers/pipelines/flux/pipeline_flux_kontext.py +1134 -0
  287. diffusers/pipelines/flux/pipeline_flux_kontext_inpaint.py +1460 -0
  288. diffusers/pipelines/flux/pipeline_flux_prior_redux.py +2 -2
  289. diffusers/pipelines/flux/pipeline_output.py +6 -4
  290. diffusers/pipelines/free_init_utils.py +2 -2
  291. diffusers/pipelines/free_noise_utils.py +3 -3
  292. diffusers/pipelines/hidream_image/__init__.py +47 -0
  293. diffusers/pipelines/hidream_image/pipeline_hidream_image.py +1026 -0
  294. diffusers/pipelines/hidream_image/pipeline_output.py +35 -0
  295. diffusers/pipelines/hunyuan_video/__init__.py +2 -0
  296. diffusers/pipelines/hunyuan_video/pipeline_hunyuan_skyreels_image2video.py +8 -8
  297. diffusers/pipelines/hunyuan_video/pipeline_hunyuan_video.py +26 -25
  298. diffusers/pipelines/hunyuan_video/pipeline_hunyuan_video_framepack.py +1114 -0
  299. diffusers/pipelines/hunyuan_video/pipeline_hunyuan_video_image2video.py +71 -15
  300. diffusers/pipelines/hunyuan_video/pipeline_output.py +19 -0
  301. diffusers/pipelines/hunyuandit/pipeline_hunyuandit.py +8 -8
  302. diffusers/pipelines/i2vgen_xl/pipeline_i2vgen_xl.py +10 -8
  303. diffusers/pipelines/kandinsky/pipeline_kandinsky.py +6 -6
  304. diffusers/pipelines/kandinsky/pipeline_kandinsky_combined.py +34 -34
  305. diffusers/pipelines/kandinsky/pipeline_kandinsky_img2img.py +19 -26
  306. diffusers/pipelines/kandinsky/pipeline_kandinsky_inpaint.py +7 -7
  307. diffusers/pipelines/kandinsky/pipeline_kandinsky_prior.py +11 -11
  308. diffusers/pipelines/kandinsky2_2/pipeline_kandinsky2_2.py +6 -6
  309. diffusers/pipelines/kandinsky2_2/pipeline_kandinsky2_2_combined.py +35 -35
  310. diffusers/pipelines/kandinsky2_2/pipeline_kandinsky2_2_controlnet.py +6 -6
  311. diffusers/pipelines/kandinsky2_2/pipeline_kandinsky2_2_controlnet_img2img.py +17 -39
  312. diffusers/pipelines/kandinsky2_2/pipeline_kandinsky2_2_img2img.py +17 -45
  313. diffusers/pipelines/kandinsky2_2/pipeline_kandinsky2_2_inpainting.py +7 -7
  314. diffusers/pipelines/kandinsky2_2/pipeline_kandinsky2_2_prior.py +10 -10
  315. diffusers/pipelines/kandinsky2_2/pipeline_kandinsky2_2_prior_emb2emb.py +10 -10
  316. diffusers/pipelines/kandinsky3/pipeline_kandinsky3.py +7 -7
  317. diffusers/pipelines/kandinsky3/pipeline_kandinsky3_img2img.py +17 -38
  318. diffusers/pipelines/kolors/pipeline_kolors.py +10 -10
  319. diffusers/pipelines/kolors/pipeline_kolors_img2img.py +12 -12
  320. diffusers/pipelines/kolors/text_encoder.py +3 -3
  321. diffusers/pipelines/kolors/tokenizer.py +1 -1
  322. diffusers/pipelines/latent_consistency_models/pipeline_latent_consistency_img2img.py +2 -2
  323. diffusers/pipelines/latent_consistency_models/pipeline_latent_consistency_text2img.py +2 -2
  324. diffusers/pipelines/latent_diffusion/pipeline_latent_diffusion.py +1 -1
  325. diffusers/pipelines/latent_diffusion/pipeline_latent_diffusion_superresolution.py +3 -3
  326. diffusers/pipelines/latte/pipeline_latte.py +12 -12
  327. diffusers/pipelines/ledits_pp/pipeline_leditspp_stable_diffusion.py +13 -13
  328. diffusers/pipelines/ledits_pp/pipeline_leditspp_stable_diffusion_xl.py +17 -16
  329. diffusers/pipelines/ltx/__init__.py +4 -0
  330. diffusers/pipelines/ltx/modeling_latent_upsampler.py +188 -0
  331. diffusers/pipelines/ltx/pipeline_ltx.py +64 -18
  332. diffusers/pipelines/ltx/pipeline_ltx_condition.py +117 -38
  333. diffusers/pipelines/ltx/pipeline_ltx_image2video.py +63 -18
  334. diffusers/pipelines/ltx/pipeline_ltx_latent_upsample.py +277 -0
  335. diffusers/pipelines/lumina/pipeline_lumina.py +13 -13
  336. diffusers/pipelines/lumina2/pipeline_lumina2.py +10 -10
  337. diffusers/pipelines/marigold/marigold_image_processing.py +2 -2
  338. diffusers/pipelines/mochi/pipeline_mochi.py +15 -14
  339. diffusers/pipelines/musicldm/pipeline_musicldm.py +16 -13
  340. diffusers/pipelines/omnigen/pipeline_omnigen.py +13 -11
  341. diffusers/pipelines/omnigen/processor_omnigen.py +8 -3
  342. diffusers/pipelines/onnx_utils.py +15 -2
  343. diffusers/pipelines/pag/pag_utils.py +2 -2
  344. diffusers/pipelines/pag/pipeline_pag_controlnet_sd.py +12 -8
  345. diffusers/pipelines/pag/pipeline_pag_controlnet_sd_inpaint.py +7 -7
  346. diffusers/pipelines/pag/pipeline_pag_controlnet_sd_xl.py +10 -6
  347. diffusers/pipelines/pag/pipeline_pag_controlnet_sd_xl_img2img.py +14 -14
  348. diffusers/pipelines/pag/pipeline_pag_hunyuandit.py +8 -8
  349. diffusers/pipelines/pag/pipeline_pag_kolors.py +10 -10
  350. diffusers/pipelines/pag/pipeline_pag_pixart_sigma.py +11 -11
  351. diffusers/pipelines/pag/pipeline_pag_sana.py +18 -12
  352. diffusers/pipelines/pag/pipeline_pag_sd.py +8 -8
  353. diffusers/pipelines/pag/pipeline_pag_sd_3.py +7 -7
  354. diffusers/pipelines/pag/pipeline_pag_sd_3_img2img.py +7 -7
  355. diffusers/pipelines/pag/pipeline_pag_sd_animatediff.py +6 -6
  356. diffusers/pipelines/pag/pipeline_pag_sd_img2img.py +5 -5
  357. diffusers/pipelines/pag/pipeline_pag_sd_inpaint.py +8 -8
  358. diffusers/pipelines/pag/pipeline_pag_sd_xl.py +16 -15
  359. diffusers/pipelines/pag/pipeline_pag_sd_xl_img2img.py +18 -17
  360. diffusers/pipelines/pag/pipeline_pag_sd_xl_inpaint.py +12 -12
  361. diffusers/pipelines/paint_by_example/image_encoder.py +1 -1
  362. diffusers/pipelines/paint_by_example/pipeline_paint_by_example.py +8 -7
  363. diffusers/pipelines/pia/pipeline_pia.py +8 -6
  364. diffusers/pipelines/pipeline_flax_utils.py +5 -6
  365. diffusers/pipelines/pipeline_loading_utils.py +113 -15
  366. diffusers/pipelines/pipeline_utils.py +127 -48
  367. diffusers/pipelines/pixart_alpha/pipeline_pixart_alpha.py +14 -12
  368. diffusers/pipelines/pixart_alpha/pipeline_pixart_sigma.py +31 -11
  369. diffusers/pipelines/qwenimage/__init__.py +55 -0
  370. diffusers/pipelines/qwenimage/pipeline_output.py +21 -0
  371. diffusers/pipelines/qwenimage/pipeline_qwenimage.py +726 -0
  372. diffusers/pipelines/qwenimage/pipeline_qwenimage_edit.py +882 -0
  373. diffusers/pipelines/qwenimage/pipeline_qwenimage_img2img.py +829 -0
  374. diffusers/pipelines/qwenimage/pipeline_qwenimage_inpaint.py +1015 -0
  375. diffusers/pipelines/sana/__init__.py +4 -0
  376. diffusers/pipelines/sana/pipeline_sana.py +23 -21
  377. diffusers/pipelines/sana/pipeline_sana_controlnet.py +1106 -0
  378. diffusers/pipelines/sana/pipeline_sana_sprint.py +23 -19
  379. diffusers/pipelines/sana/pipeline_sana_sprint_img2img.py +981 -0
  380. diffusers/pipelines/semantic_stable_diffusion/pipeline_semantic_stable_diffusion.py +7 -6
  381. diffusers/pipelines/shap_e/camera.py +1 -1
  382. diffusers/pipelines/shap_e/pipeline_shap_e.py +1 -1
  383. diffusers/pipelines/shap_e/pipeline_shap_e_img2img.py +1 -1
  384. diffusers/pipelines/shap_e/renderer.py +3 -3
  385. diffusers/pipelines/skyreels_v2/__init__.py +59 -0
  386. diffusers/pipelines/skyreels_v2/pipeline_output.py +20 -0
  387. diffusers/pipelines/skyreels_v2/pipeline_skyreels_v2.py +610 -0
  388. diffusers/pipelines/skyreels_v2/pipeline_skyreels_v2_diffusion_forcing.py +978 -0
  389. diffusers/pipelines/skyreels_v2/pipeline_skyreels_v2_diffusion_forcing_i2v.py +1059 -0
  390. diffusers/pipelines/skyreels_v2/pipeline_skyreels_v2_diffusion_forcing_v2v.py +1063 -0
  391. diffusers/pipelines/skyreels_v2/pipeline_skyreels_v2_i2v.py +745 -0
  392. diffusers/pipelines/stable_audio/modeling_stable_audio.py +1 -1
  393. diffusers/pipelines/stable_audio/pipeline_stable_audio.py +5 -5
  394. diffusers/pipelines/stable_cascade/pipeline_stable_cascade.py +8 -8
  395. diffusers/pipelines/stable_cascade/pipeline_stable_cascade_combined.py +13 -13
  396. diffusers/pipelines/stable_cascade/pipeline_stable_cascade_prior.py +9 -9
  397. diffusers/pipelines/stable_diffusion/__init__.py +0 -7
  398. diffusers/pipelines/stable_diffusion/clip_image_project_model.py +1 -1
  399. diffusers/pipelines/stable_diffusion/convert_from_ckpt.py +11 -4
  400. diffusers/pipelines/stable_diffusion/pipeline_flax_stable_diffusion.py +1 -1
  401. diffusers/pipelines/stable_diffusion/pipeline_flax_stable_diffusion_img2img.py +1 -1
  402. diffusers/pipelines/stable_diffusion/pipeline_flax_stable_diffusion_inpaint.py +1 -1
  403. diffusers/pipelines/stable_diffusion/pipeline_onnx_stable_diffusion.py +12 -11
  404. diffusers/pipelines/stable_diffusion/pipeline_onnx_stable_diffusion_img2img.py +10 -10
  405. diffusers/pipelines/stable_diffusion/pipeline_onnx_stable_diffusion_inpaint.py +11 -11
  406. diffusers/pipelines/stable_diffusion/pipeline_onnx_stable_diffusion_upscale.py +10 -10
  407. diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion.py +10 -9
  408. diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_depth2img.py +5 -5
  409. diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_image_variation.py +5 -5
  410. diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_img2img.py +5 -5
  411. diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_inpaint.py +5 -5
  412. diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_instruct_pix2pix.py +5 -5
  413. diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_latent_upscale.py +4 -4
  414. diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_upscale.py +5 -5
  415. diffusers/pipelines/stable_diffusion/pipeline_stable_unclip.py +7 -7
  416. diffusers/pipelines/stable_diffusion/pipeline_stable_unclip_img2img.py +5 -5
  417. diffusers/pipelines/stable_diffusion/safety_checker.py +1 -1
  418. diffusers/pipelines/stable_diffusion/safety_checker_flax.py +1 -1
  419. diffusers/pipelines/stable_diffusion/stable_unclip_image_normalizer.py +1 -1
  420. diffusers/pipelines/stable_diffusion_3/pipeline_stable_diffusion_3.py +13 -12
  421. diffusers/pipelines/stable_diffusion_3/pipeline_stable_diffusion_3_img2img.py +7 -7
  422. diffusers/pipelines/stable_diffusion_3/pipeline_stable_diffusion_3_inpaint.py +7 -7
  423. diffusers/pipelines/stable_diffusion_attend_and_excite/pipeline_stable_diffusion_attend_and_excite.py +12 -8
  424. diffusers/pipelines/stable_diffusion_diffedit/pipeline_stable_diffusion_diffedit.py +15 -9
  425. diffusers/pipelines/stable_diffusion_gligen/pipeline_stable_diffusion_gligen.py +11 -9
  426. diffusers/pipelines/stable_diffusion_gligen/pipeline_stable_diffusion_gligen_text_image.py +11 -9
  427. diffusers/pipelines/stable_diffusion_k_diffusion/pipeline_stable_diffusion_k_diffusion.py +18 -12
  428. diffusers/pipelines/stable_diffusion_k_diffusion/pipeline_stable_diffusion_xl_k_diffusion.py +11 -8
  429. diffusers/pipelines/stable_diffusion_ldm3d/pipeline_stable_diffusion_ldm3d.py +11 -8
  430. diffusers/pipelines/stable_diffusion_panorama/pipeline_stable_diffusion_panorama.py +15 -12
  431. diffusers/pipelines/stable_diffusion_safe/pipeline_stable_diffusion_safe.py +8 -6
  432. diffusers/pipelines/stable_diffusion_safe/safety_checker.py +1 -1
  433. diffusers/pipelines/stable_diffusion_sag/pipeline_stable_diffusion_sag.py +15 -11
  434. diffusers/pipelines/stable_diffusion_xl/pipeline_flax_stable_diffusion_xl.py +1 -1
  435. diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl.py +16 -15
  436. diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_img2img.py +18 -17
  437. diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_inpaint.py +12 -12
  438. diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_instruct_pix2pix.py +16 -15
  439. diffusers/pipelines/stable_video_diffusion/pipeline_stable_video_diffusion.py +3 -3
  440. diffusers/pipelines/t2i_adapter/pipeline_stable_diffusion_adapter.py +12 -12
  441. diffusers/pipelines/t2i_adapter/pipeline_stable_diffusion_xl_adapter.py +18 -17
  442. diffusers/pipelines/text_to_video_synthesis/pipeline_text_to_video_synth.py +12 -7
  443. diffusers/pipelines/text_to_video_synthesis/pipeline_text_to_video_synth_img2img.py +12 -7
  444. diffusers/pipelines/text_to_video_synthesis/pipeline_text_to_video_zero.py +15 -13
  445. diffusers/pipelines/text_to_video_synthesis/pipeline_text_to_video_zero_sdxl.py +24 -21
  446. diffusers/pipelines/unclip/pipeline_unclip.py +4 -3
  447. diffusers/pipelines/unclip/pipeline_unclip_image_variation.py +4 -3
  448. diffusers/pipelines/unclip/text_proj.py +2 -2
  449. diffusers/pipelines/unidiffuser/modeling_text_decoder.py +2 -2
  450. diffusers/pipelines/unidiffuser/modeling_uvit.py +1 -1
  451. diffusers/pipelines/unidiffuser/pipeline_unidiffuser.py +8 -7
  452. diffusers/pipelines/visualcloze/__init__.py +52 -0
  453. diffusers/pipelines/visualcloze/pipeline_visualcloze_combined.py +444 -0
  454. diffusers/pipelines/visualcloze/pipeline_visualcloze_generation.py +952 -0
  455. diffusers/pipelines/visualcloze/visualcloze_utils.py +251 -0
  456. diffusers/pipelines/wan/__init__.py +2 -0
  457. diffusers/pipelines/wan/pipeline_wan.py +91 -30
  458. diffusers/pipelines/wan/pipeline_wan_i2v.py +145 -45
  459. diffusers/pipelines/wan/pipeline_wan_vace.py +975 -0
  460. diffusers/pipelines/wan/pipeline_wan_video2video.py +14 -16
  461. diffusers/pipelines/wuerstchen/modeling_paella_vq_model.py +1 -1
  462. diffusers/pipelines/wuerstchen/modeling_wuerstchen_diffnext.py +1 -1
  463. diffusers/pipelines/wuerstchen/modeling_wuerstchen_prior.py +1 -1
  464. diffusers/pipelines/wuerstchen/pipeline_wuerstchen.py +8 -8
  465. diffusers/pipelines/wuerstchen/pipeline_wuerstchen_combined.py +16 -15
  466. diffusers/pipelines/wuerstchen/pipeline_wuerstchen_prior.py +6 -6
  467. diffusers/quantizers/__init__.py +3 -1
  468. diffusers/quantizers/base.py +17 -1
  469. diffusers/quantizers/bitsandbytes/bnb_quantizer.py +4 -0
  470. diffusers/quantizers/bitsandbytes/utils.py +10 -7
  471. diffusers/quantizers/gguf/gguf_quantizer.py +13 -4
  472. diffusers/quantizers/gguf/utils.py +108 -16
  473. diffusers/quantizers/pipe_quant_config.py +202 -0
  474. diffusers/quantizers/quantization_config.py +18 -16
  475. diffusers/quantizers/quanto/quanto_quantizer.py +4 -0
  476. diffusers/quantizers/torchao/torchao_quantizer.py +31 -1
  477. diffusers/schedulers/__init__.py +3 -1
  478. diffusers/schedulers/deprecated/scheduling_karras_ve.py +4 -3
  479. diffusers/schedulers/deprecated/scheduling_sde_vp.py +1 -1
  480. diffusers/schedulers/scheduling_consistency_models.py +1 -1
  481. diffusers/schedulers/scheduling_cosine_dpmsolver_multistep.py +10 -5
  482. diffusers/schedulers/scheduling_ddim.py +8 -8
  483. diffusers/schedulers/scheduling_ddim_cogvideox.py +5 -5
  484. diffusers/schedulers/scheduling_ddim_flax.py +6 -6
  485. diffusers/schedulers/scheduling_ddim_inverse.py +6 -6
  486. diffusers/schedulers/scheduling_ddim_parallel.py +22 -22
  487. diffusers/schedulers/scheduling_ddpm.py +9 -9
  488. diffusers/schedulers/scheduling_ddpm_flax.py +7 -7
  489. diffusers/schedulers/scheduling_ddpm_parallel.py +18 -18
  490. diffusers/schedulers/scheduling_ddpm_wuerstchen.py +2 -2
  491. diffusers/schedulers/scheduling_deis_multistep.py +16 -9
  492. diffusers/schedulers/scheduling_dpm_cogvideox.py +5 -5
  493. diffusers/schedulers/scheduling_dpmsolver_multistep.py +18 -12
  494. diffusers/schedulers/scheduling_dpmsolver_multistep_flax.py +22 -20
  495. diffusers/schedulers/scheduling_dpmsolver_multistep_inverse.py +11 -11
  496. diffusers/schedulers/scheduling_dpmsolver_sde.py +2 -2
  497. diffusers/schedulers/scheduling_dpmsolver_singlestep.py +19 -13
  498. diffusers/schedulers/scheduling_edm_dpmsolver_multistep.py +13 -8
  499. diffusers/schedulers/scheduling_edm_euler.py +20 -11
  500. diffusers/schedulers/scheduling_euler_ancestral_discrete.py +3 -3
  501. diffusers/schedulers/scheduling_euler_discrete.py +3 -3
  502. diffusers/schedulers/scheduling_euler_discrete_flax.py +3 -3
  503. diffusers/schedulers/scheduling_flow_match_euler_discrete.py +20 -5
  504. diffusers/schedulers/scheduling_flow_match_heun_discrete.py +1 -1
  505. diffusers/schedulers/scheduling_flow_match_lcm.py +561 -0
  506. diffusers/schedulers/scheduling_heun_discrete.py +2 -2
  507. diffusers/schedulers/scheduling_ipndm.py +2 -2
  508. diffusers/schedulers/scheduling_k_dpm_2_ancestral_discrete.py +2 -2
  509. diffusers/schedulers/scheduling_k_dpm_2_discrete.py +2 -2
  510. diffusers/schedulers/scheduling_karras_ve_flax.py +5 -5
  511. diffusers/schedulers/scheduling_lcm.py +3 -3
  512. diffusers/schedulers/scheduling_lms_discrete.py +2 -2
  513. diffusers/schedulers/scheduling_lms_discrete_flax.py +1 -1
  514. diffusers/schedulers/scheduling_pndm.py +4 -4
  515. diffusers/schedulers/scheduling_pndm_flax.py +4 -4
  516. diffusers/schedulers/scheduling_repaint.py +9 -9
  517. diffusers/schedulers/scheduling_sasolver.py +15 -15
  518. diffusers/schedulers/scheduling_scm.py +1 -2
  519. diffusers/schedulers/scheduling_sde_ve.py +1 -1
  520. diffusers/schedulers/scheduling_sde_ve_flax.py +2 -2
  521. diffusers/schedulers/scheduling_tcd.py +3 -3
  522. diffusers/schedulers/scheduling_unclip.py +5 -5
  523. diffusers/schedulers/scheduling_unipc_multistep.py +21 -12
  524. diffusers/schedulers/scheduling_utils.py +3 -3
  525. diffusers/schedulers/scheduling_utils_flax.py +2 -2
  526. diffusers/schedulers/scheduling_vq_diffusion.py +1 -1
  527. diffusers/training_utils.py +91 -5
  528. diffusers/utils/__init__.py +15 -0
  529. diffusers/utils/accelerate_utils.py +1 -1
  530. diffusers/utils/constants.py +4 -0
  531. diffusers/utils/doc_utils.py +1 -1
  532. diffusers/utils/dummy_pt_objects.py +432 -0
  533. diffusers/utils/dummy_torch_and_transformers_objects.py +480 -0
  534. diffusers/utils/dynamic_modules_utils.py +85 -8
  535. diffusers/utils/export_utils.py +1 -1
  536. diffusers/utils/hub_utils.py +33 -17
  537. diffusers/utils/import_utils.py +151 -18
  538. diffusers/utils/logging.py +1 -1
  539. diffusers/utils/outputs.py +2 -1
  540. diffusers/utils/peft_utils.py +96 -10
  541. diffusers/utils/state_dict_utils.py +20 -3
  542. diffusers/utils/testing_utils.py +195 -17
  543. diffusers/utils/torch_utils.py +43 -5
  544. diffusers/video_processor.py +2 -2
  545. {diffusers-0.33.1.dist-info → diffusers-0.35.0.dist-info}/METADATA +72 -57
  546. diffusers-0.35.0.dist-info/RECORD +703 -0
  547. {diffusers-0.33.1.dist-info → diffusers-0.35.0.dist-info}/WHEEL +1 -1
  548. diffusers-0.33.1.dist-info/RECORD +0 -608
  549. {diffusers-0.33.1.dist-info → diffusers-0.35.0.dist-info}/LICENSE +0 -0
  550. {diffusers-0.33.1.dist-info → diffusers-0.35.0.dist-info}/entry_points.txt +0 -0
  551. {diffusers-0.33.1.dist-info → diffusers-0.35.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,2446 @@
1
+ # Copyright 2025 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import importlib
15
+ import inspect
16
+ import os
17
+ import traceback
18
+ import warnings
19
+ from collections import OrderedDict
20
+ from copy import deepcopy
21
+ from dataclasses import dataclass, field
22
+ from typing import Any, Dict, List, Optional, Tuple, Union
23
+
24
+ import torch
25
+ from huggingface_hub import create_repo
26
+ from huggingface_hub.utils import validate_hf_hub_args
27
+ from tqdm.auto import tqdm
28
+ from typing_extensions import Self
29
+
30
+ from ..configuration_utils import ConfigMixin, FrozenDict
31
+ from ..pipelines.pipeline_loading_utils import _fetch_class_library_tuple, simple_get_class_obj
32
+ from ..utils import PushToHubMixin, is_accelerate_available, logging
33
+ from ..utils.dynamic_modules_utils import get_class_from_dynamic_module, resolve_trust_remote_code
34
+ from ..utils.hub_utils import load_or_create_model_card, populate_model_card
35
+ from .components_manager import ComponentsManager
36
+ from .modular_pipeline_utils import (
37
+ ComponentSpec,
38
+ ConfigSpec,
39
+ InputParam,
40
+ InsertableDict,
41
+ OutputParam,
42
+ format_components,
43
+ format_configs,
44
+ make_doc_string,
45
+ )
46
+
47
+
48
+ if is_accelerate_available():
49
+ import accelerate
50
+
51
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
52
+
53
+
54
+ MODULAR_PIPELINE_MAPPING = OrderedDict(
55
+ [
56
+ ("stable-diffusion-xl", "StableDiffusionXLModularPipeline"),
57
+ ("wan", "WanModularPipeline"),
58
+ ("flux", "FluxModularPipeline"),
59
+ ]
60
+ )
61
+
62
+ MODULAR_PIPELINE_BLOCKS_MAPPING = OrderedDict(
63
+ [
64
+ ("StableDiffusionXLModularPipeline", "StableDiffusionXLAutoBlocks"),
65
+ ("WanModularPipeline", "WanAutoBlocks"),
66
+ ("FluxModularPipeline", "FluxAutoBlocks"),
67
+ ]
68
+ )
69
+
70
+
71
+ @dataclass
72
+ class PipelineState:
73
+ """
74
+ [`PipelineState`] stores the state of a pipeline. It is used to pass data between pipeline blocks.
75
+ """
76
+
77
+ values: Dict[str, Any] = field(default_factory=dict)
78
+ kwargs_mapping: Dict[str, List[str]] = field(default_factory=dict)
79
+
80
+ def set(self, key: str, value: Any, kwargs_type: str = None):
81
+ """
82
+ Add a value to the pipeline state.
83
+
84
+ Args:
85
+ key (str): The key for the value
86
+ value (Any): The value to store
87
+ kwargs_type (str): The kwargs_type with which the value is associated
88
+ """
89
+ self.values[key] = value
90
+
91
+ if kwargs_type is not None:
92
+ if kwargs_type not in self.kwargs_mapping:
93
+ self.kwargs_mapping[kwargs_type] = [key]
94
+ else:
95
+ self.kwargs_mapping[kwargs_type].append(key)
96
+
97
+ def get(self, keys: Union[str, List[str]], default: Any = None) -> Union[Any, Dict[str, Any]]:
98
+ """
99
+ Get one or multiple values from the pipeline state.
100
+
101
+ Args:
102
+ keys (Union[str, List[str]]): Key or list of keys for the values
103
+ default (Any): The default value to return if not found
104
+
105
+ Returns:
106
+ Union[Any, Dict[str, Any]]: Single value if keys is str, dictionary of values if keys is list
107
+ """
108
+ if isinstance(keys, str):
109
+ return self.values.get(keys, default)
110
+ return {key: self.values.get(key, default) for key in keys}
111
+
112
+ def get_by_kwargs(self, kwargs_type: str) -> Dict[str, Any]:
113
+ """
114
+ Get all values with matching kwargs_type.
115
+
116
+ Args:
117
+ kwargs_type (str): The kwargs_type to filter by
118
+
119
+ Returns:
120
+ Dict[str, Any]: Dictionary of values with matching kwargs_type
121
+ """
122
+ value_names = self.kwargs_mapping.get(kwargs_type, [])
123
+ return self.get(value_names)
124
+
125
+ def to_dict(self) -> Dict[str, Any]:
126
+ """
127
+ Convert PipelineState to a dictionary.
128
+ """
129
+ return {**self.__dict__}
130
+
131
+ def __repr__(self):
132
+ def format_value(v):
133
+ if hasattr(v, "shape") and hasattr(v, "dtype"):
134
+ return f"Tensor(dtype={v.dtype}, shape={v.shape})"
135
+ elif isinstance(v, list) and len(v) > 0 and hasattr(v[0], "shape") and hasattr(v[0], "dtype"):
136
+ return f"[Tensor(dtype={v[0].dtype}, shape={v[0].shape}), ...]"
137
+ else:
138
+ return repr(v)
139
+
140
+ values_str = "\n".join(f" {k}: {format_value(v)}" for k, v in self.values.items())
141
+ kwargs_mapping_str = "\n".join(f" {k}: {v}" for k, v in self.kwargs_mapping.items())
142
+
143
+ return f"PipelineState(\n values={{\n{values_str}\n }},\n kwargs_mapping={{\n{kwargs_mapping_str}\n }}\n)"
144
+
145
+
146
+ @dataclass
147
+ class BlockState:
148
+ """
149
+ Container for block state data with attribute access and formatted representation.
150
+ """
151
+
152
+ def __init__(self, **kwargs):
153
+ for key, value in kwargs.items():
154
+ setattr(self, key, value)
155
+
156
+ def __getitem__(self, key: str):
157
+ # allows block_state["foo"]
158
+ return getattr(self, key, None)
159
+
160
+ def __setitem__(self, key: str, value: Any):
161
+ # allows block_state["foo"] = "bar"
162
+ setattr(self, key, value)
163
+
164
+ def as_dict(self):
165
+ """
166
+ Convert BlockState to a dictionary.
167
+
168
+ Returns:
169
+ Dict[str, Any]: Dictionary containing all attributes of the BlockState
170
+ """
171
+ return dict(self.__dict__.items())
172
+
173
+ def __repr__(self):
174
+ def format_value(v):
175
+ # Handle tensors directly
176
+ if hasattr(v, "shape") and hasattr(v, "dtype"):
177
+ return f"Tensor(dtype={v.dtype}, shape={v.shape})"
178
+
179
+ # Handle lists of tensors
180
+ elif isinstance(v, list):
181
+ if len(v) > 0 and hasattr(v[0], "shape") and hasattr(v[0], "dtype"):
182
+ shapes = [t.shape for t in v]
183
+ return f"List[{len(v)}] of Tensors with shapes {shapes}"
184
+ return repr(v)
185
+
186
+ # Handle tuples of tensors
187
+ elif isinstance(v, tuple):
188
+ if len(v) > 0 and hasattr(v[0], "shape") and hasattr(v[0], "dtype"):
189
+ shapes = [t.shape for t in v]
190
+ return f"Tuple[{len(v)}] of Tensors with shapes {shapes}"
191
+ return repr(v)
192
+
193
+ # Handle dicts with tensor values
194
+ elif isinstance(v, dict):
195
+ formatted_dict = {}
196
+ for k, val in v.items():
197
+ if hasattr(val, "shape") and hasattr(val, "dtype"):
198
+ formatted_dict[k] = f"Tensor(shape={val.shape}, dtype={val.dtype})"
199
+ elif (
200
+ isinstance(val, list)
201
+ and len(val) > 0
202
+ and hasattr(val[0], "shape")
203
+ and hasattr(val[0], "dtype")
204
+ ):
205
+ shapes = [t.shape for t in val]
206
+ formatted_dict[k] = f"List[{len(val)}] of Tensors with shapes {shapes}"
207
+ else:
208
+ formatted_dict[k] = repr(val)
209
+ return formatted_dict
210
+
211
+ # Default case
212
+ return repr(v)
213
+
214
+ attributes = "\n".join(f" {k}: {format_value(v)}" for k, v in self.__dict__.items())
215
+ return f"BlockState(\n{attributes}\n)"
216
+
217
+
218
+ class ModularPipelineBlocks(ConfigMixin, PushToHubMixin):
219
+ """
220
+ Base class for all Pipeline Blocks: PipelineBlock, AutoPipelineBlocks, SequentialPipelineBlocks,
221
+ LoopSequentialPipelineBlocks
222
+
223
+ [`ModularPipelineBlocks`] provides method to load and save the defination of pipeline blocks.
224
+
225
+ <Tip warning={true}>
226
+
227
+ This is an experimental feature and is likely to change in the future.
228
+
229
+ </Tip>
230
+ """
231
+
232
+ config_name = "modular_config.json"
233
+ model_name = None
234
+
235
+ @classmethod
236
+ def _get_signature_keys(cls, obj):
237
+ parameters = inspect.signature(obj.__init__).parameters
238
+ required_parameters = {k: v for k, v in parameters.items() if v.default == inspect._empty}
239
+ optional_parameters = set({k for k, v in parameters.items() if v.default != inspect._empty})
240
+ expected_modules = set(required_parameters.keys()) - {"self"}
241
+
242
+ return expected_modules, optional_parameters
243
+
244
+ def __init__(self):
245
+ self.sub_blocks = InsertableDict()
246
+
247
+ @property
248
+ def description(self) -> str:
249
+ """Description of the block. Must be implemented by subclasses."""
250
+ return ""
251
+
252
+ @property
253
+ def expected_components(self) -> List[ComponentSpec]:
254
+ return []
255
+
256
+ @property
257
+ def expected_configs(self) -> List[ConfigSpec]:
258
+ return []
259
+
260
+ @property
261
+ def inputs(self) -> List[InputParam]:
262
+ """List of input parameters. Must be implemented by subclasses."""
263
+ return []
264
+
265
+ def _get_required_inputs(self):
266
+ input_names = []
267
+ for input_param in self.inputs:
268
+ if input_param.required:
269
+ input_names.append(input_param.name)
270
+
271
+ return input_names
272
+
273
+ @property
274
+ def required_inputs(self) -> List[InputParam]:
275
+ return self._get_required_inputs()
276
+
277
+ @property
278
+ def intermediate_outputs(self) -> List[OutputParam]:
279
+ """List of intermediate output parameters. Must be implemented by subclasses."""
280
+ return []
281
+
282
+ def _get_outputs(self):
283
+ return self.intermediate_outputs
284
+
285
+ @property
286
+ def outputs(self) -> List[OutputParam]:
287
+ return self._get_outputs()
288
+
289
+ @classmethod
290
+ def from_pretrained(
291
+ cls,
292
+ pretrained_model_name_or_path: str,
293
+ trust_remote_code: Optional[bool] = None,
294
+ **kwargs,
295
+ ):
296
+ hub_kwargs_names = [
297
+ "cache_dir",
298
+ "force_download",
299
+ "local_files_only",
300
+ "proxies",
301
+ "resume_download",
302
+ "revision",
303
+ "subfolder",
304
+ "token",
305
+ ]
306
+ hub_kwargs = {name: kwargs.pop(name) for name in hub_kwargs_names if name in kwargs}
307
+
308
+ config = cls.load_config(pretrained_model_name_or_path)
309
+ has_remote_code = "auto_map" in config and cls.__name__ in config["auto_map"]
310
+ trust_remote_code = resolve_trust_remote_code(
311
+ trust_remote_code, pretrained_model_name_or_path, has_remote_code
312
+ )
313
+ if not (has_remote_code and trust_remote_code):
314
+ raise ValueError(
315
+ "Selected model repository does not happear to have any custom code or does not have a valid `config.json` file."
316
+ )
317
+
318
+ class_ref = config["auto_map"][cls.__name__]
319
+ module_file, class_name = class_ref.split(".")
320
+ module_file = module_file + ".py"
321
+ block_cls = get_class_from_dynamic_module(
322
+ pretrained_model_name_or_path,
323
+ module_file=module_file,
324
+ class_name=class_name,
325
+ **hub_kwargs,
326
+ **kwargs,
327
+ )
328
+ expected_kwargs, optional_kwargs = block_cls._get_signature_keys(block_cls)
329
+ block_kwargs = {
330
+ name: kwargs.pop(name) for name in kwargs if name in expected_kwargs or name in optional_kwargs
331
+ }
332
+
333
+ return block_cls(**block_kwargs)
334
+
335
+ def save_pretrained(self, save_directory, push_to_hub=False, **kwargs):
336
+ # TODO: factor out this logic.
337
+ cls_name = self.__class__.__name__
338
+
339
+ full_mod = type(self).__module__
340
+ module = full_mod.rsplit(".", 1)[-1].replace("__dynamic__", "")
341
+ parent_module = self.save_pretrained.__func__.__qualname__.split(".", 1)[0]
342
+ auto_map = {f"{parent_module}": f"{module}.{cls_name}"}
343
+
344
+ self.register_to_config(auto_map=auto_map)
345
+ self.save_config(save_directory=save_directory, push_to_hub=push_to_hub, **kwargs)
346
+ config = dict(self.config)
347
+ self._internal_dict = FrozenDict(config)
348
+
349
+ def init_pipeline(
350
+ self,
351
+ pretrained_model_name_or_path: Optional[Union[str, os.PathLike]] = None,
352
+ components_manager: Optional[ComponentsManager] = None,
353
+ collection: Optional[str] = None,
354
+ ) -> "ModularPipeline":
355
+ """
356
+ create a ModularPipeline, optionally accept modular_repo to load from hub.
357
+ """
358
+ pipeline_class_name = MODULAR_PIPELINE_MAPPING.get(self.model_name, ModularPipeline.__name__)
359
+ diffusers_module = importlib.import_module("diffusers")
360
+ pipeline_class = getattr(diffusers_module, pipeline_class_name)
361
+
362
+ modular_pipeline = pipeline_class(
363
+ blocks=deepcopy(self),
364
+ pretrained_model_name_or_path=pretrained_model_name_or_path,
365
+ components_manager=components_manager,
366
+ collection=collection,
367
+ )
368
+ return modular_pipeline
369
+
370
+ def get_block_state(self, state: PipelineState) -> dict:
371
+ """Get all inputs and intermediates in one dictionary"""
372
+ data = {}
373
+ state_inputs = self.inputs
374
+
375
+ # Check inputs
376
+ for input_param in state_inputs:
377
+ if input_param.name:
378
+ value = state.get(input_param.name)
379
+ if input_param.required and value is None:
380
+ raise ValueError(f"Required input '{input_param.name}' is missing")
381
+ elif value is not None or (value is None and input_param.name not in data):
382
+ data[input_param.name] = value
383
+
384
+ elif input_param.kwargs_type:
385
+ # if kwargs_type is provided, get all inputs with matching kwargs_type
386
+ if input_param.kwargs_type not in data:
387
+ data[input_param.kwargs_type] = {}
388
+ inputs_kwargs = state.get_by_kwargs(input_param.kwargs_type)
389
+ if inputs_kwargs:
390
+ for k, v in inputs_kwargs.items():
391
+ if v is not None:
392
+ data[k] = v
393
+ data[input_param.kwargs_type][k] = v
394
+
395
+ return BlockState(**data)
396
+
397
+ def set_block_state(self, state: PipelineState, block_state: BlockState):
398
+ for output_param in self.intermediate_outputs:
399
+ if not hasattr(block_state, output_param.name):
400
+ raise ValueError(f"Intermediate output '{output_param.name}' is missing in block state")
401
+ param = getattr(block_state, output_param.name)
402
+ state.set(output_param.name, param, output_param.kwargs_type)
403
+
404
+ for input_param in self.inputs:
405
+ if input_param.name and hasattr(block_state, input_param.name):
406
+ param = getattr(block_state, input_param.name)
407
+ # Only add if the value is different from what's in the state
408
+ current_value = state.get(input_param.name)
409
+ if current_value is not param: # Using identity comparison to check if object was modified
410
+ state.set(input_param.name, param, input_param.kwargs_type)
411
+
412
+ elif input_param.kwargs_type:
413
+ # if it is a kwargs type, e.g. "guider_input_fields", it is likely to be a list of parameters
414
+ # we need to first find out which inputs are and loop through them.
415
+ intermediate_kwargs = state.get_by_kwargs(input_param.kwargs_type)
416
+ for param_name, current_value in intermediate_kwargs.items():
417
+ if param_name is None:
418
+ continue
419
+
420
+ if not hasattr(block_state, param_name):
421
+ continue
422
+
423
+ param = getattr(block_state, param_name)
424
+ if current_value is not param: # Using identity comparison to check if object was modified
425
+ state.set(param_name, param, input_param.kwargs_type)
426
+
427
+ @staticmethod
428
+ def combine_inputs(*named_input_lists: List[Tuple[str, List[InputParam]]]) -> List[InputParam]:
429
+ """
430
+ Combines multiple lists of InputParam objects from different blocks. For duplicate inputs, updates only if
431
+ current default value is None and new default value is not None. Warns if multiple non-None default values
432
+ exist for the same input.
433
+
434
+ Args:
435
+ named_input_lists: List of tuples containing (block_name, input_param_list) pairs
436
+
437
+ Returns:
438
+ List[InputParam]: Combined list of unique InputParam objects
439
+ """
440
+ combined_dict = {} # name -> InputParam
441
+ value_sources = {} # name -> block_name
442
+
443
+ for block_name, inputs in named_input_lists:
444
+ for input_param in inputs:
445
+ if input_param.name is None and input_param.kwargs_type is not None:
446
+ input_name = "*_" + input_param.kwargs_type
447
+ else:
448
+ input_name = input_param.name
449
+ if input_name in combined_dict:
450
+ current_param = combined_dict[input_name]
451
+ if (
452
+ current_param.default is not None
453
+ and input_param.default is not None
454
+ and current_param.default != input_param.default
455
+ ):
456
+ warnings.warn(
457
+ f"Multiple different default values found for input '{input_name}': "
458
+ f"{current_param.default} (from block '{value_sources[input_name]}') and "
459
+ f"{input_param.default} (from block '{block_name}'). Using {current_param.default}."
460
+ )
461
+ if current_param.default is None and input_param.default is not None:
462
+ combined_dict[input_name] = input_param
463
+ value_sources[input_name] = block_name
464
+ else:
465
+ combined_dict[input_name] = input_param
466
+ value_sources[input_name] = block_name
467
+
468
+ return list(combined_dict.values())
469
+
470
+ @staticmethod
471
+ def combine_outputs(*named_output_lists: List[Tuple[str, List[OutputParam]]]) -> List[OutputParam]:
472
+ """
473
+ Combines multiple lists of OutputParam objects from different blocks. For duplicate outputs, keeps the first
474
+ occurrence of each output name.
475
+
476
+ Args:
477
+ named_output_lists: List of tuples containing (block_name, output_param_list) pairs
478
+
479
+ Returns:
480
+ List[OutputParam]: Combined list of unique OutputParam objects
481
+ """
482
+ combined_dict = {} # name -> OutputParam
483
+
484
+ for block_name, outputs in named_output_lists:
485
+ for output_param in outputs:
486
+ if (output_param.name not in combined_dict) or (
487
+ combined_dict[output_param.name].kwargs_type is None and output_param.kwargs_type is not None
488
+ ):
489
+ combined_dict[output_param.name] = output_param
490
+
491
+ return list(combined_dict.values())
492
+
493
+ @property
494
+ def input_names(self) -> List[str]:
495
+ return [input_param.name for input_param in self.inputs]
496
+
497
+ @property
498
+ def intermediate_output_names(self) -> List[str]:
499
+ return [output_param.name for output_param in self.intermediate_outputs]
500
+
501
+ @property
502
+ def output_names(self) -> List[str]:
503
+ return [output_param.name for output_param in self.outputs]
504
+
505
+ @property
506
+ def doc(self):
507
+ return make_doc_string(
508
+ self.inputs,
509
+ self.outputs,
510
+ self.description,
511
+ class_name=self.__class__.__name__,
512
+ expected_components=self.expected_components,
513
+ expected_configs=self.expected_configs,
514
+ )
515
+
516
+
517
+ class AutoPipelineBlocks(ModularPipelineBlocks):
518
+ """
519
+ A Pipeline Blocks that automatically selects a block to run based on the inputs.
520
+
521
+ This class inherits from [`ModularPipelineBlocks`]. Check the superclass documentation for the generic methods the
522
+ library implements for all the pipeline blocks (such as loading or saving etc.)
523
+
524
+ <Tip warning={true}>
525
+
526
+ This is an experimental feature and is likely to change in the future.
527
+
528
+ </Tip>
529
+
530
+ Attributes:
531
+ block_classes: List of block classes to be used
532
+ block_names: List of prefixes for each block
533
+ block_trigger_inputs: List of input names that trigger specific blocks, with None for default
534
+ """
535
+
536
+ block_classes = []
537
+ block_names = []
538
+ block_trigger_inputs = []
539
+
540
+ def __init__(self):
541
+ sub_blocks = InsertableDict()
542
+ for block_name, block_cls in zip(self.block_names, self.block_classes):
543
+ sub_blocks[block_name] = block_cls()
544
+ self.sub_blocks = sub_blocks
545
+ if not (len(self.block_classes) == len(self.block_names) == len(self.block_trigger_inputs)):
546
+ raise ValueError(
547
+ f"In {self.__class__.__name__}, the number of block_classes, block_names, and block_trigger_inputs must be the same."
548
+ )
549
+ default_blocks = [t for t in self.block_trigger_inputs if t is None]
550
+ # can only have 1 or 0 default block, and has to put in the last
551
+ # the order of blocks matters here because the first block with matching trigger will be dispatched
552
+ # e.g. blocks = [inpaint, img2img] and block_trigger_inputs = ["mask", "image"]
553
+ # as long as mask is provided, it is inpaint; if only image is provided, it is img2img
554
+ if len(default_blocks) > 1 or (len(default_blocks) == 1 and self.block_trigger_inputs[-1] is not None):
555
+ raise ValueError(
556
+ f"In {self.__class__.__name__}, exactly one None must be specified as the last element "
557
+ "in block_trigger_inputs."
558
+ )
559
+
560
+ # Map trigger inputs to block objects
561
+ self.trigger_to_block_map = dict(zip(self.block_trigger_inputs, self.sub_blocks.values()))
562
+ self.trigger_to_block_name_map = dict(zip(self.block_trigger_inputs, self.sub_blocks.keys()))
563
+ self.block_to_trigger_map = dict(zip(self.sub_blocks.keys(), self.block_trigger_inputs))
564
+
565
+ @property
566
+ def model_name(self):
567
+ return next(iter(self.sub_blocks.values())).model_name
568
+
569
+ @property
570
+ def description(self):
571
+ return ""
572
+
573
+ @property
574
+ def expected_components(self):
575
+ expected_components = []
576
+ for block in self.sub_blocks.values():
577
+ for component in block.expected_components:
578
+ if component not in expected_components:
579
+ expected_components.append(component)
580
+ return expected_components
581
+
582
+ @property
583
+ def expected_configs(self):
584
+ expected_configs = []
585
+ for block in self.sub_blocks.values():
586
+ for config in block.expected_configs:
587
+ if config not in expected_configs:
588
+ expected_configs.append(config)
589
+ return expected_configs
590
+
591
+ @property
592
+ def required_inputs(self) -> List[str]:
593
+ if None not in self.block_trigger_inputs:
594
+ return []
595
+ first_block = next(iter(self.sub_blocks.values()))
596
+ required_by_all = set(getattr(first_block, "required_inputs", set()))
597
+
598
+ # Intersect with required inputs from all other blocks
599
+ for block in list(self.sub_blocks.values())[1:]:
600
+ block_required = set(getattr(block, "required_inputs", set()))
601
+ required_by_all.intersection_update(block_required)
602
+
603
+ return list(required_by_all)
604
+
605
+ # YiYi TODO: add test for this
606
+ @property
607
+ def inputs(self) -> List[Tuple[str, Any]]:
608
+ named_inputs = [(name, block.inputs) for name, block in self.sub_blocks.items()]
609
+ combined_inputs = self.combine_inputs(*named_inputs)
610
+ # mark Required inputs only if that input is required by all the blocks
611
+ for input_param in combined_inputs:
612
+ if input_param.name in self.required_inputs:
613
+ input_param.required = True
614
+ else:
615
+ input_param.required = False
616
+ return combined_inputs
617
+
618
+ @property
619
+ def intermediate_outputs(self) -> List[str]:
620
+ named_outputs = [(name, block.intermediate_outputs) for name, block in self.sub_blocks.items()]
621
+ combined_outputs = self.combine_outputs(*named_outputs)
622
+ return combined_outputs
623
+
624
+ @property
625
+ def outputs(self) -> List[str]:
626
+ named_outputs = [(name, block.outputs) for name, block in self.sub_blocks.items()]
627
+ combined_outputs = self.combine_outputs(*named_outputs)
628
+ return combined_outputs
629
+
630
+ @torch.no_grad()
631
+ def __call__(self, pipeline, state: PipelineState) -> PipelineState:
632
+ # Find default block first (if any)
633
+
634
+ block = self.trigger_to_block_map.get(None)
635
+ for input_name in self.block_trigger_inputs:
636
+ if input_name is not None and state.get(input_name) is not None:
637
+ block = self.trigger_to_block_map[input_name]
638
+ break
639
+
640
+ if block is None:
641
+ logger.warning(f"skipping auto block: {self.__class__.__name__}")
642
+ return pipeline, state
643
+
644
+ try:
645
+ logger.info(f"Running block: {block.__class__.__name__}, trigger: {input_name}")
646
+ return block(pipeline, state)
647
+ except Exception as e:
648
+ error_msg = (
649
+ f"\nError in block: {block.__class__.__name__}\n"
650
+ f"Error details: {str(e)}\n"
651
+ f"Traceback:\n{traceback.format_exc()}"
652
+ )
653
+ logger.error(error_msg)
654
+ raise
655
+
656
+ def _get_trigger_inputs(self):
657
+ """
658
+ Returns a set of all unique trigger input values found in the blocks. Returns: Set[str] containing all unique
659
+ block_trigger_inputs values
660
+ """
661
+
662
+ def fn_recursive_get_trigger(blocks):
663
+ trigger_values = set()
664
+
665
+ if blocks is not None:
666
+ for name, block in blocks.items():
667
+ # Check if current block has trigger inputs(i.e. auto block)
668
+ if hasattr(block, "block_trigger_inputs") and block.block_trigger_inputs is not None:
669
+ # Add all non-None values from the trigger inputs list
670
+ trigger_values.update(t for t in block.block_trigger_inputs if t is not None)
671
+
672
+ # If block has sub_blocks, recursively check them
673
+ if block.sub_blocks:
674
+ nested_triggers = fn_recursive_get_trigger(block.sub_blocks)
675
+ trigger_values.update(nested_triggers)
676
+
677
+ return trigger_values
678
+
679
+ trigger_inputs = set(self.block_trigger_inputs)
680
+ trigger_inputs.update(fn_recursive_get_trigger(self.sub_blocks))
681
+
682
+ return trigger_inputs
683
+
684
+ @property
685
+ def trigger_inputs(self):
686
+ return self._get_trigger_inputs()
687
+
688
+ def __repr__(self):
689
+ class_name = self.__class__.__name__
690
+ base_class = self.__class__.__bases__[0].__name__
691
+ header = (
692
+ f"{class_name}(\n Class: {base_class}\n" if base_class and base_class != "object" else f"{class_name}(\n"
693
+ )
694
+
695
+ if self.trigger_inputs:
696
+ header += "\n"
697
+ header += " " + "=" * 100 + "\n"
698
+ header += " This pipeline contains blocks that are selected at runtime based on inputs.\n"
699
+ header += f" Trigger Inputs: {[inp for inp in self.trigger_inputs if inp is not None]}\n"
700
+ header += " " + "=" * 100 + "\n\n"
701
+
702
+ # Format description with proper indentation
703
+ desc_lines = self.description.split("\n")
704
+ desc = []
705
+ # First line with "Description:" label
706
+ desc.append(f" Description: {desc_lines[0]}")
707
+ # Subsequent lines with proper indentation
708
+ if len(desc_lines) > 1:
709
+ desc.extend(f" {line}" for line in desc_lines[1:])
710
+ desc = "\n".join(desc) + "\n"
711
+
712
+ # Components section - focus only on expected components
713
+ expected_components = getattr(self, "expected_components", [])
714
+ components_str = format_components(expected_components, indent_level=2, add_empty_lines=False)
715
+
716
+ # Configs section - use format_configs with add_empty_lines=False
717
+ expected_configs = getattr(self, "expected_configs", [])
718
+ configs_str = format_configs(expected_configs, indent_level=2, add_empty_lines=False)
719
+
720
+ # Blocks section - moved to the end with simplified format
721
+ blocks_str = " Sub-Blocks:\n"
722
+ for i, (name, block) in enumerate(self.sub_blocks.items()):
723
+ # Get trigger input for this block
724
+ trigger = None
725
+ if hasattr(self, "block_to_trigger_map"):
726
+ trigger = self.block_to_trigger_map.get(name)
727
+ # Format the trigger info
728
+ if trigger is None:
729
+ trigger_str = "[default]"
730
+ elif isinstance(trigger, (list, tuple)):
731
+ trigger_str = f"[trigger: {', '.join(str(t) for t in trigger)}]"
732
+ else:
733
+ trigger_str = f"[trigger: {trigger}]"
734
+ # For AutoPipelineBlocks, add bullet points
735
+ blocks_str += f" • {name} {trigger_str} ({block.__class__.__name__})\n"
736
+ else:
737
+ # For SequentialPipelineBlocks, show execution order
738
+ blocks_str += f" [{i}] {name} ({block.__class__.__name__})\n"
739
+
740
+ # Add block description
741
+ desc_lines = block.description.split("\n")
742
+ indented_desc = desc_lines[0]
743
+ if len(desc_lines) > 1:
744
+ indented_desc += "\n" + "\n".join(" " + line for line in desc_lines[1:])
745
+ blocks_str += f" Description: {indented_desc}\n\n"
746
+
747
+ # Build the representation with conditional sections
748
+ result = f"{header}\n{desc}"
749
+
750
+ # Only add components section if it has content
751
+ if components_str.strip():
752
+ result += f"\n\n{components_str}"
753
+
754
+ # Only add configs section if it has content
755
+ if configs_str.strip():
756
+ result += f"\n\n{configs_str}"
757
+
758
+ # Always add blocks section
759
+ result += f"\n\n{blocks_str})"
760
+
761
+ return result
762
+
763
+ @property
764
+ def doc(self):
765
+ return make_doc_string(
766
+ self.inputs,
767
+ self.outputs,
768
+ self.description,
769
+ class_name=self.__class__.__name__,
770
+ expected_components=self.expected_components,
771
+ expected_configs=self.expected_configs,
772
+ )
773
+
774
+
775
+ class SequentialPipelineBlocks(ModularPipelineBlocks):
776
+ """
777
+ A Pipeline Blocks that combines multiple pipeline block classes into one. When called, it will call each block in
778
+ sequence.
779
+
780
+ This class inherits from [`ModularPipelineBlocks`]. Check the superclass documentation for the generic methods the
781
+ library implements for all the pipeline blocks (such as loading or saving etc.)
782
+
783
+ <Tip warning={true}>
784
+
785
+ This is an experimental feature and is likely to change in the future.
786
+
787
+ </Tip>
788
+
789
+ Attributes:
790
+ block_classes: List of block classes to be used
791
+ block_names: List of prefixes for each block
792
+ """
793
+
794
+ block_classes = []
795
+ block_names = []
796
+
797
+ @property
798
+ def description(self):
799
+ return ""
800
+
801
+ @property
802
+ def model_name(self):
803
+ return next((block.model_name for block in self.sub_blocks.values() if block.model_name is not None), None)
804
+
805
+ @property
806
+ def expected_components(self):
807
+ expected_components = []
808
+ for block in self.sub_blocks.values():
809
+ for component in block.expected_components:
810
+ if component not in expected_components:
811
+ expected_components.append(component)
812
+ return expected_components
813
+
814
+ @property
815
+ def expected_configs(self):
816
+ expected_configs = []
817
+ for block in self.sub_blocks.values():
818
+ for config in block.expected_configs:
819
+ if config not in expected_configs:
820
+ expected_configs.append(config)
821
+ return expected_configs
822
+
823
+ @classmethod
824
+ def from_blocks_dict(cls, blocks_dict: Dict[str, Any]) -> "SequentialPipelineBlocks":
825
+ """Creates a SequentialPipelineBlocks instance from a dictionary of blocks.
826
+
827
+ Args:
828
+ blocks_dict: Dictionary mapping block names to block classes or instances
829
+
830
+ Returns:
831
+ A new SequentialPipelineBlocks instance
832
+ """
833
+ instance = cls()
834
+
835
+ # Create instances if classes are provided
836
+ sub_blocks = InsertableDict()
837
+ for name, block in blocks_dict.items():
838
+ if inspect.isclass(block):
839
+ sub_blocks[name] = block()
840
+ else:
841
+ sub_blocks[name] = block
842
+
843
+ instance.block_classes = [block.__class__ for block in sub_blocks.values()]
844
+ instance.block_names = list(sub_blocks.keys())
845
+ instance.sub_blocks = sub_blocks
846
+ return instance
847
+
848
+ def __init__(self):
849
+ sub_blocks = InsertableDict()
850
+ for block_name, block_cls in zip(self.block_names, self.block_classes):
851
+ sub_blocks[block_name] = block_cls()
852
+ self.sub_blocks = sub_blocks
853
+
854
+ def _get_inputs(self):
855
+ inputs = []
856
+ outputs = set()
857
+
858
+ # Go through all blocks in order
859
+ for block in self.sub_blocks.values():
860
+ # Add inputs that aren't in outputs yet
861
+ for inp in block.inputs:
862
+ if inp.name not in outputs and inp.name not in {input.name for input in inputs}:
863
+ inputs.append(inp)
864
+
865
+ # Only add outputs if the block cannot be skipped
866
+ should_add_outputs = True
867
+ if hasattr(block, "block_trigger_inputs") and None not in block.block_trigger_inputs:
868
+ should_add_outputs = False
869
+
870
+ if should_add_outputs:
871
+ # Add this block's outputs
872
+ block_intermediate_outputs = [out.name for out in block.intermediate_outputs]
873
+ outputs.update(block_intermediate_outputs)
874
+
875
+ return inputs
876
+
877
+ # YiYi TODO: add test for this
878
+ @property
879
+ def inputs(self) -> List[Tuple[str, Any]]:
880
+ return self._get_inputs()
881
+
882
+ @property
883
+ def required_inputs(self) -> List[str]:
884
+ # Get the first block from the dictionary
885
+ first_block = next(iter(self.sub_blocks.values()))
886
+ required_by_any = set(getattr(first_block, "required_inputs", set()))
887
+
888
+ # Union with required inputs from all other blocks
889
+ for block in list(self.sub_blocks.values())[1:]:
890
+ block_required = set(getattr(block, "required_inputs", set()))
891
+ required_by_any.update(block_required)
892
+
893
+ return list(required_by_any)
894
+
895
+ @property
896
+ def intermediate_outputs(self) -> List[str]:
897
+ named_outputs = []
898
+ for name, block in self.sub_blocks.items():
899
+ inp_names = {inp.name for inp in block.inputs}
900
+ # so we only need to list new variables as intermediate_outputs, but if user wants to list these they modified it's still fine (a.k.a we don't enforce)
901
+ # filter out them here so they do not end up as intermediate_outputs
902
+ if name not in inp_names:
903
+ named_outputs.append((name, block.intermediate_outputs))
904
+ combined_outputs = self.combine_outputs(*named_outputs)
905
+ return combined_outputs
906
+
907
+ # YiYi TODO: I think we can remove the outputs property
908
+ @property
909
+ def outputs(self) -> List[str]:
910
+ # return next(reversed(self.sub_blocks.values())).intermediate_outputs
911
+ return self.intermediate_outputs
912
+
913
+ @torch.no_grad()
914
+ def __call__(self, pipeline, state: PipelineState) -> PipelineState:
915
+ for block_name, block in self.sub_blocks.items():
916
+ try:
917
+ pipeline, state = block(pipeline, state)
918
+ except Exception as e:
919
+ error_msg = (
920
+ f"\nError in block: ({block_name}, {block.__class__.__name__})\n"
921
+ f"Error details: {str(e)}\n"
922
+ f"Traceback:\n{traceback.format_exc()}"
923
+ )
924
+ logger.error(error_msg)
925
+ raise
926
+ return pipeline, state
927
+
928
+ def _get_trigger_inputs(self):
929
+ """
930
+ Returns a set of all unique trigger input values found in the blocks. Returns: Set[str] containing all unique
931
+ block_trigger_inputs values
932
+ """
933
+
934
+ def fn_recursive_get_trigger(blocks):
935
+ trigger_values = set()
936
+
937
+ if blocks is not None:
938
+ for name, block in blocks.items():
939
+ # Check if current block has trigger inputs(i.e. auto block)
940
+ if hasattr(block, "block_trigger_inputs") and block.block_trigger_inputs is not None:
941
+ # Add all non-None values from the trigger inputs list
942
+ trigger_values.update(t for t in block.block_trigger_inputs if t is not None)
943
+
944
+ # If block has sub_blocks, recursively check them
945
+ if block.sub_blocks:
946
+ nested_triggers = fn_recursive_get_trigger(block.sub_blocks)
947
+ trigger_values.update(nested_triggers)
948
+
949
+ return trigger_values
950
+
951
+ return fn_recursive_get_trigger(self.sub_blocks)
952
+
953
+ @property
954
+ def trigger_inputs(self):
955
+ return self._get_trigger_inputs()
956
+
957
+ def _traverse_trigger_blocks(self, trigger_inputs):
958
+ # Convert trigger_inputs to a set for easier manipulation
959
+ active_triggers = set(trigger_inputs)
960
+
961
+ def fn_recursive_traverse(block, block_name, active_triggers):
962
+ result_blocks = OrderedDict()
963
+
964
+ # sequential(include loopsequential) or PipelineBlock
965
+ if not hasattr(block, "block_trigger_inputs"):
966
+ if block.sub_blocks:
967
+ # sequential or LoopSequentialPipelineBlocks (keep traversing)
968
+ for sub_block_name, sub_block in block.sub_blocks.items():
969
+ blocks_to_update = fn_recursive_traverse(sub_block, sub_block_name, active_triggers)
970
+ blocks_to_update = fn_recursive_traverse(sub_block, sub_block_name, active_triggers)
971
+ blocks_to_update = {f"{block_name}.{k}": v for k, v in blocks_to_update.items()}
972
+ result_blocks.update(blocks_to_update)
973
+ else:
974
+ # PipelineBlock
975
+ result_blocks[block_name] = block
976
+ # Add this block's output names to active triggers if defined
977
+ if hasattr(block, "outputs"):
978
+ active_triggers.update(out.name for out in block.outputs)
979
+ return result_blocks
980
+
981
+ # auto
982
+ else:
983
+ # Find first block_trigger_input that matches any value in our active_triggers
984
+ this_block = None
985
+ for trigger_input in block.block_trigger_inputs:
986
+ if trigger_input is not None and trigger_input in active_triggers:
987
+ this_block = block.trigger_to_block_map[trigger_input]
988
+ break
989
+
990
+ # If no matches found, try to get the default (None) block
991
+ if this_block is None and None in block.block_trigger_inputs:
992
+ this_block = block.trigger_to_block_map[None]
993
+
994
+ if this_block is not None:
995
+ # sequential/auto (keep traversing)
996
+ if this_block.sub_blocks:
997
+ result_blocks.update(fn_recursive_traverse(this_block, block_name, active_triggers))
998
+ else:
999
+ # PipelineBlock
1000
+ result_blocks[block_name] = this_block
1001
+ # Add this block's output names to active triggers if defined
1002
+ # YiYi TODO: do we need outputs here? can it just be intermediate_outputs? can we get rid of outputs attribute?
1003
+ if hasattr(this_block, "outputs"):
1004
+ active_triggers.update(out.name for out in this_block.outputs)
1005
+
1006
+ return result_blocks
1007
+
1008
+ all_blocks = OrderedDict()
1009
+ for block_name, block in self.sub_blocks.items():
1010
+ blocks_to_update = fn_recursive_traverse(block, block_name, active_triggers)
1011
+ all_blocks.update(blocks_to_update)
1012
+ return all_blocks
1013
+
1014
+ def get_execution_blocks(self, *trigger_inputs):
1015
+ trigger_inputs_all = self.trigger_inputs
1016
+
1017
+ if trigger_inputs is not None:
1018
+ if not isinstance(trigger_inputs, (list, tuple, set)):
1019
+ trigger_inputs = [trigger_inputs]
1020
+ invalid_inputs = [x for x in trigger_inputs if x not in trigger_inputs_all]
1021
+ if invalid_inputs:
1022
+ logger.warning(
1023
+ f"The following trigger inputs will be ignored as they are not supported: {invalid_inputs}"
1024
+ )
1025
+ trigger_inputs = [x for x in trigger_inputs if x in trigger_inputs_all]
1026
+
1027
+ if trigger_inputs is None:
1028
+ if None in trigger_inputs_all:
1029
+ trigger_inputs = [None]
1030
+ else:
1031
+ trigger_inputs = [trigger_inputs_all[0]]
1032
+ blocks_triggered = self._traverse_trigger_blocks(trigger_inputs)
1033
+ return SequentialPipelineBlocks.from_blocks_dict(blocks_triggered)
1034
+
1035
+ def __repr__(self):
1036
+ class_name = self.__class__.__name__
1037
+ base_class = self.__class__.__bases__[0].__name__
1038
+ header = (
1039
+ f"{class_name}(\n Class: {base_class}\n" if base_class and base_class != "object" else f"{class_name}(\n"
1040
+ )
1041
+
1042
+ if self.trigger_inputs:
1043
+ header += "\n"
1044
+ header += " " + "=" * 100 + "\n"
1045
+ header += " This pipeline contains blocks that are selected at runtime based on inputs.\n"
1046
+ header += f" Trigger Inputs: {[inp for inp in self.trigger_inputs if inp is not None]}\n"
1047
+ # Get first trigger input as example
1048
+ example_input = next(t for t in self.trigger_inputs if t is not None)
1049
+ header += f" Use `get_execution_blocks()` with input names to see selected blocks (e.g. `get_execution_blocks('{example_input}')`).\n"
1050
+ header += " " + "=" * 100 + "\n\n"
1051
+
1052
+ # Format description with proper indentation
1053
+ desc_lines = self.description.split("\n")
1054
+ desc = []
1055
+ # First line with "Description:" label
1056
+ desc.append(f" Description: {desc_lines[0]}")
1057
+ # Subsequent lines with proper indentation
1058
+ if len(desc_lines) > 1:
1059
+ desc.extend(f" {line}" for line in desc_lines[1:])
1060
+ desc = "\n".join(desc) + "\n"
1061
+
1062
+ # Components section - focus only on expected components
1063
+ expected_components = getattr(self, "expected_components", [])
1064
+ components_str = format_components(expected_components, indent_level=2, add_empty_lines=False)
1065
+
1066
+ # Configs section - use format_configs with add_empty_lines=False
1067
+ expected_configs = getattr(self, "expected_configs", [])
1068
+ configs_str = format_configs(expected_configs, indent_level=2, add_empty_lines=False)
1069
+
1070
+ # Blocks section - moved to the end with simplified format
1071
+ blocks_str = " Sub-Blocks:\n"
1072
+ for i, (name, block) in enumerate(self.sub_blocks.items()):
1073
+ # Get trigger input for this block
1074
+ trigger = None
1075
+ if hasattr(self, "block_to_trigger_map"):
1076
+ trigger = self.block_to_trigger_map.get(name)
1077
+ # Format the trigger info
1078
+ if trigger is None:
1079
+ trigger_str = "[default]"
1080
+ elif isinstance(trigger, (list, tuple)):
1081
+ trigger_str = f"[trigger: {', '.join(str(t) for t in trigger)}]"
1082
+ else:
1083
+ trigger_str = f"[trigger: {trigger}]"
1084
+ # For AutoPipelineBlocks, add bullet points
1085
+ blocks_str += f" • {name} {trigger_str} ({block.__class__.__name__})\n"
1086
+ else:
1087
+ # For SequentialPipelineBlocks, show execution order
1088
+ blocks_str += f" [{i}] {name} ({block.__class__.__name__})\n"
1089
+
1090
+ # Add block description
1091
+ desc_lines = block.description.split("\n")
1092
+ indented_desc = desc_lines[0]
1093
+ if len(desc_lines) > 1:
1094
+ indented_desc += "\n" + "\n".join(" " + line for line in desc_lines[1:])
1095
+ blocks_str += f" Description: {indented_desc}\n\n"
1096
+
1097
+ # Build the representation with conditional sections
1098
+ result = f"{header}\n{desc}"
1099
+
1100
+ # Only add components section if it has content
1101
+ if components_str.strip():
1102
+ result += f"\n\n{components_str}"
1103
+
1104
+ # Only add configs section if it has content
1105
+ if configs_str.strip():
1106
+ result += f"\n\n{configs_str}"
1107
+
1108
+ # Always add blocks section
1109
+ result += f"\n\n{blocks_str})"
1110
+
1111
+ return result
1112
+
1113
+ @property
1114
+ def doc(self):
1115
+ return make_doc_string(
1116
+ self.inputs,
1117
+ self.outputs,
1118
+ self.description,
1119
+ class_name=self.__class__.__name__,
1120
+ expected_components=self.expected_components,
1121
+ expected_configs=self.expected_configs,
1122
+ )
1123
+
1124
+
1125
+ class LoopSequentialPipelineBlocks(ModularPipelineBlocks):
1126
+ """
1127
+ A Pipeline blocks that combines multiple pipeline block classes into a For Loop. When called, it will call each
1128
+ block in sequence.
1129
+
1130
+ This class inherits from [`ModularPipelineBlocks`]. Check the superclass documentation for the generic methods the
1131
+ library implements for all the pipeline blocks (such as loading or saving etc.)
1132
+
1133
+ <Tip warning={true}>
1134
+
1135
+ This is an experimental feature and is likely to change in the future.
1136
+
1137
+ </Tip>
1138
+
1139
+ Attributes:
1140
+ block_classes: List of block classes to be used
1141
+ block_names: List of prefixes for each block
1142
+ """
1143
+
1144
+ model_name = None
1145
+ block_classes = []
1146
+ block_names = []
1147
+
1148
+ @property
1149
+ def description(self) -> str:
1150
+ """Description of the block. Must be implemented by subclasses."""
1151
+ raise NotImplementedError("description method must be implemented in subclasses")
1152
+
1153
+ @property
1154
+ def loop_expected_components(self) -> List[ComponentSpec]:
1155
+ return []
1156
+
1157
+ @property
1158
+ def loop_expected_configs(self) -> List[ConfigSpec]:
1159
+ return []
1160
+
1161
+ @property
1162
+ def loop_inputs(self) -> List[InputParam]:
1163
+ """List of input parameters. Must be implemented by subclasses."""
1164
+ return []
1165
+
1166
+ @property
1167
+ def loop_required_inputs(self) -> List[str]:
1168
+ input_names = []
1169
+ for input_param in self.loop_inputs:
1170
+ if input_param.required:
1171
+ input_names.append(input_param.name)
1172
+ return input_names
1173
+
1174
+ @property
1175
+ def loop_intermediate_outputs(self) -> List[OutputParam]:
1176
+ """List of intermediate output parameters. Must be implemented by subclasses."""
1177
+ return []
1178
+
1179
+ # modified from SequentialPipelineBlocks to include loop_expected_components
1180
+ @property
1181
+ def expected_components(self):
1182
+ expected_components = []
1183
+ for block in self.sub_blocks.values():
1184
+ for component in block.expected_components:
1185
+ if component not in expected_components:
1186
+ expected_components.append(component)
1187
+ for component in self.loop_expected_components:
1188
+ if component not in expected_components:
1189
+ expected_components.append(component)
1190
+ return expected_components
1191
+
1192
+ # modified from SequentialPipelineBlocks to include loop_expected_configs
1193
+ @property
1194
+ def expected_configs(self):
1195
+ expected_configs = []
1196
+ for block in self.sub_blocks.values():
1197
+ for config in block.expected_configs:
1198
+ if config not in expected_configs:
1199
+ expected_configs.append(config)
1200
+ for config in self.loop_expected_configs:
1201
+ if config not in expected_configs:
1202
+ expected_configs.append(config)
1203
+ return expected_configs
1204
+
1205
+ def _get_inputs(self):
1206
+ inputs = []
1207
+ inputs.extend(self.loop_inputs)
1208
+ outputs = set()
1209
+
1210
+ for name, block in self.sub_blocks.items():
1211
+ # Add inputs that aren't in outputs yet
1212
+ for inp in block.inputs:
1213
+ if inp.name not in outputs and inp not in inputs:
1214
+ inputs.append(inp)
1215
+
1216
+ # Only add outputs if the block cannot be skipped
1217
+ should_add_outputs = True
1218
+ if hasattr(block, "block_trigger_inputs") and None not in block.block_trigger_inputs:
1219
+ should_add_outputs = False
1220
+
1221
+ if should_add_outputs:
1222
+ # Add this block's outputs
1223
+ block_intermediate_outputs = [out.name for out in block.intermediate_outputs]
1224
+ outputs.update(block_intermediate_outputs)
1225
+
1226
+ for input_param in inputs:
1227
+ if input_param.name in self.required_inputs:
1228
+ input_param.required = True
1229
+ else:
1230
+ input_param.required = False
1231
+
1232
+ return inputs
1233
+
1234
+ @property
1235
+ # Copied from diffusers.modular_pipelines.modular_pipeline.SequentialPipelineBlocks.inputs
1236
+ def inputs(self):
1237
+ return self._get_inputs()
1238
+
1239
+ # modified from SequentialPipelineBlocks, if any additionan input required by the loop is required by the block
1240
+ @property
1241
+ def required_inputs(self) -> List[str]:
1242
+ # Get the first block from the dictionary
1243
+ first_block = next(iter(self.sub_blocks.values()))
1244
+ required_by_any = set(getattr(first_block, "required_inputs", set()))
1245
+
1246
+ required_by_loop = set(getattr(self, "loop_required_inputs", set()))
1247
+ required_by_any.update(required_by_loop)
1248
+
1249
+ # Union with required inputs from all other blocks
1250
+ for block in list(self.sub_blocks.values())[1:]:
1251
+ block_required = set(getattr(block, "required_inputs", set()))
1252
+ required_by_any.update(block_required)
1253
+
1254
+ return list(required_by_any)
1255
+
1256
+ # YiYi TODO: this need to be thought about more
1257
+ # modified from SequentialPipelineBlocks to include loop_intermediate_outputs
1258
+ @property
1259
+ def intermediate_outputs(self) -> List[str]:
1260
+ named_outputs = [(name, block.intermediate_outputs) for name, block in self.sub_blocks.items()]
1261
+ combined_outputs = self.combine_outputs(*named_outputs)
1262
+ for output in self.loop_intermediate_outputs:
1263
+ if output.name not in {output.name for output in combined_outputs}:
1264
+ combined_outputs.append(output)
1265
+ return combined_outputs
1266
+
1267
+ # YiYi TODO: this need to be thought about more
1268
+ @property
1269
+ def outputs(self) -> List[str]:
1270
+ return next(reversed(self.sub_blocks.values())).intermediate_outputs
1271
+
1272
+ def __init__(self):
1273
+ sub_blocks = InsertableDict()
1274
+ for block_name, block_cls in zip(self.block_names, self.block_classes):
1275
+ sub_blocks[block_name] = block_cls()
1276
+ self.sub_blocks = sub_blocks
1277
+
1278
+ @classmethod
1279
+ def from_blocks_dict(cls, blocks_dict: Dict[str, Any]) -> "LoopSequentialPipelineBlocks":
1280
+ """
1281
+ Creates a LoopSequentialPipelineBlocks instance from a dictionary of blocks.
1282
+
1283
+ Args:
1284
+ blocks_dict: Dictionary mapping block names to block instances
1285
+
1286
+ Returns:
1287
+ A new LoopSequentialPipelineBlocks instance
1288
+ """
1289
+ instance = cls()
1290
+
1291
+ # Create instances if classes are provided
1292
+ sub_blocks = InsertableDict()
1293
+ for name, block in blocks_dict.items():
1294
+ if inspect.isclass(block):
1295
+ sub_blocks[name] = block()
1296
+ else:
1297
+ sub_blocks[name] = block
1298
+
1299
+ instance.block_classes = [block.__class__ for block in blocks_dict.values()]
1300
+ instance.block_names = list(blocks_dict.keys())
1301
+ instance.sub_blocks = blocks_dict
1302
+ return instance
1303
+
1304
+ def loop_step(self, components, state: PipelineState, **kwargs):
1305
+ for block_name, block in self.sub_blocks.items():
1306
+ try:
1307
+ components, state = block(components, state, **kwargs)
1308
+ except Exception as e:
1309
+ error_msg = (
1310
+ f"\nError in block: ({block_name}, {block.__class__.__name__})\n"
1311
+ f"Error details: {str(e)}\n"
1312
+ f"Traceback:\n{traceback.format_exc()}"
1313
+ )
1314
+ logger.error(error_msg)
1315
+ raise
1316
+ return components, state
1317
+
1318
+ def __call__(self, components, state: PipelineState) -> PipelineState:
1319
+ raise NotImplementedError("`__call__` method needs to be implemented by the subclass")
1320
+
1321
+ @property
1322
+ def doc(self):
1323
+ return make_doc_string(
1324
+ self.inputs,
1325
+ self.outputs,
1326
+ self.description,
1327
+ class_name=self.__class__.__name__,
1328
+ expected_components=self.expected_components,
1329
+ expected_configs=self.expected_configs,
1330
+ )
1331
+
1332
+ # modified from SequentialPipelineBlocks,
1333
+ # (does not need trigger_inputs related part so removed them,
1334
+ # do not need to support auto block for loop blocks)
1335
+ def __repr__(self):
1336
+ class_name = self.__class__.__name__
1337
+ base_class = self.__class__.__bases__[0].__name__
1338
+ header = (
1339
+ f"{class_name}(\n Class: {base_class}\n" if base_class and base_class != "object" else f"{class_name}(\n"
1340
+ )
1341
+
1342
+ # Format description with proper indentation
1343
+ desc_lines = self.description.split("\n")
1344
+ desc = []
1345
+ # First line with "Description:" label
1346
+ desc.append(f" Description: {desc_lines[0]}")
1347
+ # Subsequent lines with proper indentation
1348
+ if len(desc_lines) > 1:
1349
+ desc.extend(f" {line}" for line in desc_lines[1:])
1350
+ desc = "\n".join(desc) + "\n"
1351
+
1352
+ # Components section - focus only on expected components
1353
+ expected_components = getattr(self, "expected_components", [])
1354
+ components_str = format_components(expected_components, indent_level=2, add_empty_lines=False)
1355
+
1356
+ # Configs section - use format_configs with add_empty_lines=False
1357
+ expected_configs = getattr(self, "expected_configs", [])
1358
+ configs_str = format_configs(expected_configs, indent_level=2, add_empty_lines=False)
1359
+
1360
+ # Blocks section - moved to the end with simplified format
1361
+ blocks_str = " Sub-Blocks:\n"
1362
+ for i, (name, block) in enumerate(self.sub_blocks.items()):
1363
+ # For SequentialPipelineBlocks, show execution order
1364
+ blocks_str += f" [{i}] {name} ({block.__class__.__name__})\n"
1365
+
1366
+ # Add block description
1367
+ desc_lines = block.description.split("\n")
1368
+ indented_desc = desc_lines[0]
1369
+ if len(desc_lines) > 1:
1370
+ indented_desc += "\n" + "\n".join(" " + line for line in desc_lines[1:])
1371
+ blocks_str += f" Description: {indented_desc}\n\n"
1372
+
1373
+ # Build the representation with conditional sections
1374
+ result = f"{header}\n{desc}"
1375
+
1376
+ # Only add components section if it has content
1377
+ if components_str.strip():
1378
+ result += f"\n\n{components_str}"
1379
+
1380
+ # Only add configs section if it has content
1381
+ if configs_str.strip():
1382
+ result += f"\n\n{configs_str}"
1383
+
1384
+ # Always add blocks section
1385
+ result += f"\n\n{blocks_str})"
1386
+
1387
+ return result
1388
+
1389
+ @torch.compiler.disable
1390
+ def progress_bar(self, iterable=None, total=None):
1391
+ if not hasattr(self, "_progress_bar_config"):
1392
+ self._progress_bar_config = {}
1393
+ elif not isinstance(self._progress_bar_config, dict):
1394
+ raise ValueError(
1395
+ f"`self._progress_bar_config` should be of type `dict`, but is {type(self._progress_bar_config)}."
1396
+ )
1397
+
1398
+ if iterable is not None:
1399
+ return tqdm(iterable, **self._progress_bar_config)
1400
+ elif total is not None:
1401
+ return tqdm(total=total, **self._progress_bar_config)
1402
+ else:
1403
+ raise ValueError("Either `total` or `iterable` has to be defined.")
1404
+
1405
+ def set_progress_bar_config(self, **kwargs):
1406
+ self._progress_bar_config = kwargs
1407
+
1408
+
1409
+ # YiYi TODO:
1410
+ # 1. look into the serialization of modular_model_index.json, make sure the items are properly ordered like model_index.json (currently a mess)
1411
+ # 2. do we need ConfigSpec? the are basically just key/val kwargs
1412
+ # 3. imnprove docstring and potentially add validator for methods where we accpet kwargs to be passed to from_pretrained/save_pretrained/load_default_components(), load_components()
1413
+ class ModularPipeline(ConfigMixin, PushToHubMixin):
1414
+ """
1415
+ Base class for all Modular pipelines.
1416
+
1417
+ <Tip warning={true}>
1418
+
1419
+ This is an experimental feature and is likely to change in the future.
1420
+
1421
+ </Tip>
1422
+
1423
+ Args:
1424
+ blocks: ModularPipelineBlocks, the blocks to be used in the pipeline
1425
+ """
1426
+
1427
+ config_name = "modular_model_index.json"
1428
+ hf_device_map = None
1429
+
1430
+ # YiYi TODO: add warning for passing multiple ComponentSpec/ConfigSpec with the same name
1431
+ def __init__(
1432
+ self,
1433
+ blocks: Optional[ModularPipelineBlocks] = None,
1434
+ pretrained_model_name_or_path: Optional[Union[str, os.PathLike]] = None,
1435
+ components_manager: Optional[ComponentsManager] = None,
1436
+ collection: Optional[str] = None,
1437
+ **kwargs,
1438
+ ):
1439
+ """
1440
+ Initialize a ModularPipeline instance.
1441
+
1442
+ This method sets up the pipeline by:
1443
+ - creating default pipeline blocks if not provided
1444
+ - gather component and config specifications based on the pipeline blocks's requirement (e.g.
1445
+ expected_components, expected_configs)
1446
+ - update the loading specs of from_pretrained components based on the modular_model_index.json file from
1447
+ huggingface hub if `pretrained_model_name_or_path` is provided
1448
+ - create defaultfrom_config components and register everything
1449
+
1450
+ Args:
1451
+ blocks: `ModularPipelineBlocks` instance. If None, will attempt to load
1452
+ default blocks based on the pipeline class name.
1453
+ pretrained_model_name_or_path: Path to a pretrained pipeline configuration. If provided,
1454
+ will load component specs (only for from_pretrained components) and config values from the saved
1455
+ modular_model_index.json file.
1456
+ components_manager:
1457
+ Optional ComponentsManager for managing multiple component cross different pipelines and apply
1458
+ offloading strategies.
1459
+ collection: Optional collection name for organizing components in the ComponentsManager.
1460
+ **kwargs: Additional arguments passed to `load_config()` when loading pretrained configuration.
1461
+
1462
+ Examples:
1463
+ ```python
1464
+ # Initialize with custom blocks
1465
+ pipeline = ModularPipeline(blocks=my_custom_blocks)
1466
+
1467
+ # Initialize from pretrained configuration
1468
+ pipeline = ModularPipeline(blocks=my_blocks, pretrained_model_name_or_path="my-repo/modular-pipeline")
1469
+
1470
+ # Initialize with components manager
1471
+ pipeline = ModularPipeline(
1472
+ blocks=my_blocks, components_manager=ComponentsManager(), collection="my_collection"
1473
+ )
1474
+ ```
1475
+
1476
+ Notes:
1477
+ - If blocks is None, the method will try to find default blocks based on the pipeline class name
1478
+ - Components with default_creation_method="from_config" are created immediately, its specs are not included
1479
+ in config dict and will not be saved in `modular_model_index.json`
1480
+ - Components with default_creation_method="from_pretrained" are set to None and can be loaded later with
1481
+ `load_default_components()`/`load_components()`
1482
+ - The pipeline's config dict is populated with component specs (only for from_pretrained components) and
1483
+ config values, which will be saved as `modular_model_index.json` during `save_pretrained`
1484
+ - The pipeline's config dict is also used to store the pipeline blocks's class name, which will be saved as
1485
+ `_blocks_class_name` in the config dict
1486
+ """
1487
+ if blocks is None:
1488
+ blocks_class_name = MODULAR_PIPELINE_BLOCKS_MAPPING.get(self.__class__.__name__)
1489
+ if blocks_class_name is not None:
1490
+ diffusers_module = importlib.import_module("diffusers")
1491
+ blocks_class = getattr(diffusers_module, blocks_class_name)
1492
+ blocks = blocks_class()
1493
+ else:
1494
+ logger.warning(f"`blocks` is `None`, no default blocks class found for {self.__class__.__name__}")
1495
+
1496
+ self.blocks = blocks
1497
+ self._components_manager = components_manager
1498
+ self._collection = collection
1499
+ self._component_specs = {spec.name: deepcopy(spec) for spec in self.blocks.expected_components}
1500
+ self._config_specs = {spec.name: deepcopy(spec) for spec in self.blocks.expected_configs}
1501
+
1502
+ # update component_specs and config_specs from modular_repo
1503
+ if pretrained_model_name_or_path is not None:
1504
+ config_dict = self.load_config(pretrained_model_name_or_path, **kwargs)
1505
+
1506
+ for name, value in config_dict.items():
1507
+ # all the components in modular_model_index.json are from_pretrained components
1508
+ if name in self._component_specs and isinstance(value, (tuple, list)) and len(value) == 3:
1509
+ library, class_name, component_spec_dict = value
1510
+ component_spec = self._dict_to_component_spec(name, component_spec_dict)
1511
+ component_spec.default_creation_method = "from_pretrained"
1512
+ self._component_specs[name] = component_spec
1513
+
1514
+ elif name in self._config_specs:
1515
+ self._config_specs[name].default = value
1516
+
1517
+ register_components_dict = {}
1518
+ for name, component_spec in self._component_specs.items():
1519
+ if component_spec.default_creation_method == "from_config":
1520
+ component = component_spec.create()
1521
+ else:
1522
+ component = None
1523
+ register_components_dict[name] = component
1524
+ self.register_components(**register_components_dict)
1525
+
1526
+ default_configs = {}
1527
+ for name, config_spec in self._config_specs.items():
1528
+ default_configs[name] = config_spec.default
1529
+ self.register_to_config(**default_configs)
1530
+
1531
+ self.register_to_config(_blocks_class_name=self.blocks.__class__.__name__ if self.blocks is not None else None)
1532
+
1533
+ @property
1534
+ def default_call_parameters(self) -> Dict[str, Any]:
1535
+ """
1536
+ Returns:
1537
+ - Dictionary mapping input names to their default values
1538
+ """
1539
+ params = {}
1540
+ for input_param in self.blocks.inputs:
1541
+ params[input_param.name] = input_param.default
1542
+ return params
1543
+
1544
+ def load_default_components(self, **kwargs):
1545
+ """
1546
+ Load from_pretrained components using the loading specs in the config dict.
1547
+
1548
+ Args:
1549
+ **kwargs: Additional arguments passed to `from_pretrained` method, e.g. torch_dtype, cache_dir, etc.
1550
+ """
1551
+ names = [
1552
+ name
1553
+ for name in self._component_specs.keys()
1554
+ if self._component_specs[name].default_creation_method == "from_pretrained"
1555
+ ]
1556
+ self.load_components(names=names, **kwargs)
1557
+
1558
+ @classmethod
1559
+ @validate_hf_hub_args
1560
+ def from_pretrained(
1561
+ cls,
1562
+ pretrained_model_name_or_path: Optional[Union[str, os.PathLike]],
1563
+ trust_remote_code: Optional[bool] = None,
1564
+ components_manager: Optional[ComponentsManager] = None,
1565
+ collection: Optional[str] = None,
1566
+ **kwargs,
1567
+ ):
1568
+ """
1569
+ Load a ModularPipeline from a huggingface hub repo.
1570
+
1571
+ Args:
1572
+ pretrained_model_name_or_path (`str` or `os.PathLike`, optional):
1573
+ Path to a pretrained pipeline configuration. If provided, will load component specs (only for
1574
+ from_pretrained components) and config values from the modular_model_index.json file.
1575
+ trust_remote_code (`bool`, optional):
1576
+ Whether to trust remote code when loading the pipeline, need to be set to True if you want to create
1577
+ pipeline blocks based on the custom code in `pretrained_model_name_or_path`
1578
+ components_manager (`ComponentsManager`, optional):
1579
+ ComponentsManager instance for managing multiple component cross different pipelines and apply
1580
+ offloading strategies.
1581
+ collection (`str`, optional):`
1582
+ Collection name for organizing components in the ComponentsManager.
1583
+ """
1584
+ from ..pipelines.pipeline_loading_utils import _get_pipeline_class
1585
+
1586
+ try:
1587
+ blocks = ModularPipelineBlocks.from_pretrained(
1588
+ pretrained_model_name_or_path, trust_remote_code=trust_remote_code, **kwargs
1589
+ )
1590
+ except EnvironmentError:
1591
+ blocks = None
1592
+
1593
+ cache_dir = kwargs.pop("cache_dir", None)
1594
+ force_download = kwargs.pop("force_download", False)
1595
+ proxies = kwargs.pop("proxies", None)
1596
+ token = kwargs.pop("token", None)
1597
+ local_files_only = kwargs.pop("local_files_only", False)
1598
+ revision = kwargs.pop("revision", None)
1599
+
1600
+ load_config_kwargs = {
1601
+ "cache_dir": cache_dir,
1602
+ "force_download": force_download,
1603
+ "proxies": proxies,
1604
+ "token": token,
1605
+ "local_files_only": local_files_only,
1606
+ "revision": revision,
1607
+ }
1608
+
1609
+ try:
1610
+ config_dict = cls.load_config(pretrained_model_name_or_path, **load_config_kwargs)
1611
+ pipeline_class = _get_pipeline_class(cls, config=config_dict)
1612
+ except EnvironmentError:
1613
+ pipeline_class = cls
1614
+ pretrained_model_name_or_path = None
1615
+
1616
+ pipeline = pipeline_class(
1617
+ blocks=blocks,
1618
+ pretrained_model_name_or_path=pretrained_model_name_or_path,
1619
+ components_manager=components_manager,
1620
+ collection=collection,
1621
+ **kwargs,
1622
+ )
1623
+ return pipeline
1624
+
1625
+ def save_pretrained(self, save_directory: Union[str, os.PathLike], push_to_hub: bool = False, **kwargs):
1626
+ """
1627
+ Save the pipeline to a directory. It does not save components, you need to save them separately.
1628
+
1629
+ Args:
1630
+ save_directory (`str` or `os.PathLike`):
1631
+ Path to the directory where the pipeline will be saved.
1632
+ push_to_hub (`bool`, optional):
1633
+ Whether to push the pipeline to the huggingface hub.
1634
+ **kwargs: Additional arguments passed to `save_config()` method
1635
+ """
1636
+ if push_to_hub:
1637
+ commit_message = kwargs.pop("commit_message", None)
1638
+ private = kwargs.pop("private", None)
1639
+ create_pr = kwargs.pop("create_pr", False)
1640
+ token = kwargs.pop("token", None)
1641
+ repo_id = kwargs.pop("repo_id", save_directory.split(os.path.sep)[-1])
1642
+ repo_id = create_repo(repo_id, exist_ok=True, private=private, token=token).repo_id
1643
+
1644
+ # Create a new empty model card and eventually tag it
1645
+ model_card = load_or_create_model_card(repo_id, token=token, is_pipeline=True)
1646
+ model_card = populate_model_card(model_card)
1647
+ model_card.save(os.path.join(save_directory, "README.md"))
1648
+
1649
+ # YiYi TODO: maybe order the json file to make it more readable: configs first, then components
1650
+ self.save_config(save_directory=save_directory)
1651
+
1652
+ if push_to_hub:
1653
+ self._upload_folder(
1654
+ save_directory,
1655
+ repo_id,
1656
+ token=token,
1657
+ commit_message=commit_message,
1658
+ create_pr=create_pr,
1659
+ )
1660
+
1661
+ @property
1662
+ def doc(self):
1663
+ """
1664
+ Returns:
1665
+ - The docstring of the pipeline blocks
1666
+ """
1667
+ return self.blocks.doc
1668
+
1669
+ def register_components(self, **kwargs):
1670
+ """
1671
+ Register components with their corresponding specifications.
1672
+
1673
+ This method is responsible for:
1674
+ 1. Sets component objects as attributes on the loader (e.g., self.unet = unet)
1675
+ 2. Updates the config dict, which will be saved as `modular_model_index.json` during `save_pretrained` (only
1676
+ for from_pretrained components)
1677
+ 3. Adds components to the component manager if one is attached (only for from_pretrained components)
1678
+
1679
+ This method is called when:
1680
+ - Components are first initialized in __init__:
1681
+ - from_pretrained components not loaded during __init__ so they are registered as None;
1682
+ - non from_pretrained components are created during __init__ and registered as the object itself
1683
+ - Components are updated with the `update_components()` method: e.g. loader.update_components(unet=unet) or
1684
+ loader.update_components(guider=guider_spec)
1685
+ - (from_pretrained) Components are loaded with the `load_default_components()` method: e.g.
1686
+ loader.load_default_components(names=["unet"])
1687
+
1688
+ Args:
1689
+ **kwargs: Keyword arguments where keys are component names and values are component objects.
1690
+ E.g., register_components(unet=unet_model, text_encoder=encoder_model)
1691
+
1692
+ Notes:
1693
+ - When registering None for a component, it sets attribute to None but still syncs specs with the config
1694
+ dict, which will be saved as `modular_model_index.json` during `save_pretrained`
1695
+ - component_specs are updated to match the new component outside of this method, e.g. in
1696
+ `update_components()` method
1697
+ """
1698
+ for name, module in kwargs.items():
1699
+ # current component spec
1700
+ component_spec = self._component_specs.get(name)
1701
+ if component_spec is None:
1702
+ logger.warning(f"ModularPipeline.register_components: skipping unknown component '{name}'")
1703
+ continue
1704
+
1705
+ # check if it is the first time registration, i.e. calling from __init__
1706
+ is_registered = hasattr(self, name)
1707
+ is_from_pretrained = component_spec.default_creation_method == "from_pretrained"
1708
+
1709
+ if module is not None:
1710
+ # actual library and class name of the module
1711
+ library, class_name = _fetch_class_library_tuple(module) # e.g. ("diffusers", "UNet2DConditionModel")
1712
+ else:
1713
+ # if module is None, e.g. self.register_components(unet=None) during __init__
1714
+ # we do not update the spec,
1715
+ # but we still need to update the modular_model_index.json config based on component spec
1716
+ library, class_name = None, None
1717
+
1718
+ # extract the loading spec from the updated component spec that'll be used as part of modular_model_index.json config
1719
+ # e.g. {"repo": "stabilityai/stable-diffusion-2-1",
1720
+ # "type_hint": ("diffusers", "UNet2DConditionModel"),
1721
+ # "subfolder": "unet",
1722
+ # "variant": None,
1723
+ # "revision": None}
1724
+ component_spec_dict = self._component_spec_to_dict(component_spec)
1725
+
1726
+ register_dict = {name: (library, class_name, component_spec_dict)}
1727
+
1728
+ # set the component as attribute
1729
+ # if it is not set yet, just set it and skip the process to check and warn below
1730
+ if not is_registered:
1731
+ if is_from_pretrained:
1732
+ self.register_to_config(**register_dict)
1733
+ setattr(self, name, module)
1734
+ if module is not None and is_from_pretrained and self._components_manager is not None:
1735
+ self._components_manager.add(name, module, self._collection)
1736
+ continue
1737
+
1738
+ current_module = getattr(self, name, None)
1739
+ # skip if the component is already registered with the same object
1740
+ if current_module is module:
1741
+ logger.info(
1742
+ f"ModularPipeline.register_components: {name} is already registered with same object, skipping"
1743
+ )
1744
+ continue
1745
+
1746
+ # warn if unregister
1747
+ if current_module is not None and module is None:
1748
+ logger.info(
1749
+ f"ModularPipeline.register_components: setting '{name}' to None "
1750
+ f"(was {current_module.__class__.__name__})"
1751
+ )
1752
+ # same type, new instance → replace but send debug log
1753
+ elif (
1754
+ current_module is not None
1755
+ and module is not None
1756
+ and isinstance(module, current_module.__class__)
1757
+ and current_module != module
1758
+ ):
1759
+ logger.debug(
1760
+ f"ModularPipeline.register_components: replacing existing '{name}' "
1761
+ f"(same type {type(current_module).__name__}, new instance)"
1762
+ )
1763
+
1764
+ # update modular_model_index.json config
1765
+ if is_from_pretrained:
1766
+ self.register_to_config(**register_dict)
1767
+ # finally set models
1768
+ setattr(self, name, module)
1769
+ # add to component manager if one is attached
1770
+ if module is not None and is_from_pretrained and self._components_manager is not None:
1771
+ self._components_manager.add(name, module, self._collection)
1772
+
1773
+ @property
1774
+ def device(self) -> torch.device:
1775
+ r"""
1776
+ Returns:
1777
+ `torch.device`: The torch device on which the pipeline is located.
1778
+ """
1779
+ modules = self.components.values()
1780
+ modules = [m for m in modules if isinstance(m, torch.nn.Module)]
1781
+
1782
+ for module in modules:
1783
+ return module.device
1784
+
1785
+ return torch.device("cpu")
1786
+
1787
+ @property
1788
+ # Modified from diffusers.pipelines.pipeline_utils.DiffusionPipeline._execution_device
1789
+ def _execution_device(self):
1790
+ r"""
1791
+ Returns the device on which the pipeline's models will be executed. After calling
1792
+ [`~DiffusionPipeline.enable_sequential_cpu_offload`] the execution device can only be inferred from
1793
+ Accelerate's module hooks.
1794
+ """
1795
+ for name, model in self.components.items():
1796
+ if not isinstance(model, torch.nn.Module):
1797
+ continue
1798
+
1799
+ if not hasattr(model, "_hf_hook"):
1800
+ return self.device
1801
+ for module in model.modules():
1802
+ if (
1803
+ hasattr(module, "_hf_hook")
1804
+ and hasattr(module._hf_hook, "execution_device")
1805
+ and module._hf_hook.execution_device is not None
1806
+ ):
1807
+ return torch.device(module._hf_hook.execution_device)
1808
+ return self.device
1809
+
1810
+ @property
1811
+ def dtype(self) -> torch.dtype:
1812
+ r"""
1813
+ Returns:
1814
+ `torch.dtype`: The torch dtype on which the pipeline is located.
1815
+ """
1816
+ modules = self.components.values()
1817
+ modules = [m for m in modules if isinstance(m, torch.nn.Module)]
1818
+
1819
+ for module in modules:
1820
+ return module.dtype
1821
+
1822
+ return torch.float32
1823
+
1824
+ @property
1825
+ def null_component_names(self) -> List[str]:
1826
+ """
1827
+ Returns:
1828
+ - List of names for components that needs to be loaded
1829
+ """
1830
+ return [name for name in self._component_specs.keys() if hasattr(self, name) and getattr(self, name) is None]
1831
+
1832
+ @property
1833
+ def component_names(self) -> List[str]:
1834
+ """
1835
+ Returns:
1836
+ - List of names for all components
1837
+ """
1838
+ return list(self.components.keys())
1839
+
1840
+ @property
1841
+ def pretrained_component_names(self) -> List[str]:
1842
+ """
1843
+ Returns:
1844
+ - List of names for from_pretrained components
1845
+ """
1846
+ return [
1847
+ name
1848
+ for name in self._component_specs.keys()
1849
+ if self._component_specs[name].default_creation_method == "from_pretrained"
1850
+ ]
1851
+
1852
+ @property
1853
+ def config_component_names(self) -> List[str]:
1854
+ """
1855
+ Returns:
1856
+ - List of names for from_config components
1857
+ """
1858
+ return [
1859
+ name
1860
+ for name in self._component_specs.keys()
1861
+ if self._component_specs[name].default_creation_method == "from_config"
1862
+ ]
1863
+
1864
+ @property
1865
+ def components(self) -> Dict[str, Any]:
1866
+ """
1867
+ Returns:
1868
+ - Dictionary mapping component names to their objects (include both from_pretrained and from_config
1869
+ components)
1870
+ """
1871
+ # return only components we've actually set as attributes on self
1872
+ return {name: getattr(self, name) for name in self._component_specs.keys() if hasattr(self, name)}
1873
+
1874
+ def get_component_spec(self, name: str) -> ComponentSpec:
1875
+ """
1876
+ Returns:
1877
+ - a copy of the ComponentSpec object for the given component name
1878
+ """
1879
+ return deepcopy(self._component_specs[name])
1880
+
1881
+ def update_components(self, **kwargs):
1882
+ """
1883
+ Update components and configuration values and specs after the pipeline has been instantiated.
1884
+
1885
+ This method allows you to:
1886
+ 1. Replace existing components with new ones (e.g., updating `self.unet` or `self.text_encoder`)
1887
+ 2. Update configuration values (e.g., changing `self.requires_safety_checker` flag)
1888
+
1889
+ In addition to updating the components and configuration values as pipeline attributes, the method also
1890
+ updates:
1891
+ - the corresponding specs in `_component_specs` and `_config_specs`
1892
+ - the `config` dict, which will be saved as `modular_model_index.json` during `save_pretrained`
1893
+
1894
+ Args:
1895
+ **kwargs: Component objects, ComponentSpec objects, or configuration values to update:
1896
+ - Component objects: Only supports components we can extract specs using
1897
+ `ComponentSpec.from_component()` method i.e. components created with ComponentSpec.load() or
1898
+ ConfigMixin subclasses that aren't nn.Modules (e.g., `unet=new_unet, text_encoder=new_encoder`)
1899
+ - ComponentSpec objects: Only supports default_creation_method == "from_config", will call create()
1900
+ method to create a new component (e.g., `guider=ComponentSpec(name="guider",
1901
+ type_hint=ClassifierFreeGuidance, config={...}, default_creation_method="from_config")`)
1902
+ - Configuration values: Simple values to update configuration settings (e.g.,
1903
+ `requires_safety_checker=False`)
1904
+
1905
+ Raises:
1906
+ ValueError: If a component object is not supported in ComponentSpec.from_component() method:
1907
+ - nn.Module components without a valid `_diffusers_load_id` attribute
1908
+ - Non-ConfigMixin components without a valid `_diffusers_load_id` attribute
1909
+
1910
+ Examples:
1911
+ ```python
1912
+ # Update multiple components at once
1913
+ pipeline.update_components(unet=new_unet_model, text_encoder=new_text_encoder)
1914
+
1915
+ # Update configuration values
1916
+ pipeline.update_components(requires_safety_checker=False)
1917
+
1918
+ # Update both components and configs together
1919
+ pipeline.update_components(unet=new_unet_model, requires_safety_checker=False)
1920
+
1921
+ # Update with ComponentSpec objects (from_config only)
1922
+ pipeline.update_components(
1923
+ guider=ComponentSpec(
1924
+ name="guider",
1925
+ type_hint=ClassifierFreeGuidance,
1926
+ config={"guidance_scale": 5.0},
1927
+ default_creation_method="from_config",
1928
+ )
1929
+ )
1930
+ ```
1931
+
1932
+ Notes:
1933
+ - Components with trained weights must be created using ComponentSpec.load(). If the component has not been
1934
+ shared in huggingface hub and you don't have loading specs, you can upload it using `push_to_hub()`
1935
+ - ConfigMixin objects without weights (e.g., schedulers, guiders) can be passed directly
1936
+ - ComponentSpec objects with default_creation_method="from_pretrained" are not supported in
1937
+ update_components()
1938
+ """
1939
+
1940
+ # extract component_specs_updates & config_specs_updates from `specs`
1941
+ passed_component_specs = {
1942
+ k: kwargs.pop(k) for k in self._component_specs if k in kwargs and isinstance(kwargs[k], ComponentSpec)
1943
+ }
1944
+ passed_components = {
1945
+ k: kwargs.pop(k) for k in self._component_specs if k in kwargs and not isinstance(kwargs[k], ComponentSpec)
1946
+ }
1947
+ passed_config_values = {k: kwargs.pop(k) for k in self._config_specs if k in kwargs}
1948
+
1949
+ for name, component in passed_components.items():
1950
+ current_component_spec = self._component_specs[name]
1951
+
1952
+ # warn if type changed
1953
+ if current_component_spec.type_hint is not None and not isinstance(
1954
+ component, current_component_spec.type_hint
1955
+ ):
1956
+ logger.warning(
1957
+ f"ModularPipeline.update_components: adding {name} with new type: {component.__class__.__name__}, previous type: {current_component_spec.type_hint.__name__}"
1958
+ )
1959
+ # update _component_specs based on the new component
1960
+ new_component_spec = ComponentSpec.from_component(name, component)
1961
+ if new_component_spec.default_creation_method != current_component_spec.default_creation_method:
1962
+ logger.warning(
1963
+ f"ModularPipeline.update_components: changing the default_creation_method of {name} from {current_component_spec.default_creation_method} to {new_component_spec.default_creation_method}."
1964
+ )
1965
+
1966
+ self._component_specs[name] = new_component_spec
1967
+
1968
+ if len(kwargs) > 0:
1969
+ logger.warning(f"Unexpected keyword arguments, will be ignored: {kwargs.keys()}")
1970
+
1971
+ created_components = {}
1972
+ for name, component_spec in passed_component_specs.items():
1973
+ if component_spec.default_creation_method == "from_pretrained":
1974
+ raise ValueError(
1975
+ "ComponentSpec object with default_creation_method == 'from_pretrained' is not supported in update_components() method"
1976
+ )
1977
+ created_components[name] = component_spec.create()
1978
+ current_component_spec = self._component_specs[name]
1979
+ # warn if type changed
1980
+ if current_component_spec.type_hint is not None and not isinstance(
1981
+ created_components[name], current_component_spec.type_hint
1982
+ ):
1983
+ logger.warning(
1984
+ f"ModularPipeline.update_components: adding {name} with new type: {created_components[name].__class__.__name__}, previous type: {current_component_spec.type_hint.__name__}"
1985
+ )
1986
+ # update _component_specs based on the user passed component_spec
1987
+ self._component_specs[name] = component_spec
1988
+ self.register_components(**passed_components, **created_components)
1989
+
1990
+ config_to_register = {}
1991
+ for name, new_value in passed_config_values.items():
1992
+ # e.g. requires_aesthetics_score = False
1993
+ self._config_specs[name].default = new_value
1994
+ config_to_register[name] = new_value
1995
+ self.register_to_config(**config_to_register)
1996
+
1997
+ # YiYi TODO: support map for additional from_pretrained kwargs
1998
+ # YiYi/Dhruv TODO: consolidate load_components and load_default_components?
1999
+ def load_components(self, names: Union[List[str], str], **kwargs):
2000
+ """
2001
+ Load selected components from specs.
2002
+
2003
+ Args:
2004
+ names: List of component names to load; by default will not load any components
2005
+ **kwargs: additional kwargs to be passed to `from_pretrained()`.Can be:
2006
+ - a single value to be applied to all components to be loaded, e.g. torch_dtype=torch.bfloat16
2007
+ - a dict, e.g. torch_dtype={"unet": torch.bfloat16, "default": torch.float32}
2008
+ - if potentially override ComponentSpec if passed a different loading field in kwargs, e.g. `repo`,
2009
+ `variant`, `revision`, etc.
2010
+ """
2011
+
2012
+ if isinstance(names, str):
2013
+ names = [names]
2014
+ elif not isinstance(names, list):
2015
+ raise ValueError(f"Invalid type for names: {type(names)}")
2016
+
2017
+ components_to_load = {name for name in names if name in self._component_specs}
2018
+ unknown_names = {name for name in names if name not in self._component_specs}
2019
+ if len(unknown_names) > 0:
2020
+ logger.warning(f"Unknown components will be ignored: {unknown_names}")
2021
+
2022
+ components_to_register = {}
2023
+ for name in components_to_load:
2024
+ spec = self._component_specs[name]
2025
+ component_load_kwargs = {}
2026
+ for key, value in kwargs.items():
2027
+ if not isinstance(value, dict):
2028
+ # if the value is a single value, apply it to all components
2029
+ component_load_kwargs[key] = value
2030
+ else:
2031
+ if name in value:
2032
+ # if it is a dict, check if the component name is in the dict
2033
+ component_load_kwargs[key] = value[name]
2034
+ elif "default" in value:
2035
+ # check if the default is specified
2036
+ component_load_kwargs[key] = value["default"]
2037
+ try:
2038
+ components_to_register[name] = spec.load(**component_load_kwargs)
2039
+ except Exception as e:
2040
+ logger.warning(f"Failed to create component '{name}': {e}")
2041
+
2042
+ # Register all components at once
2043
+ self.register_components(**components_to_register)
2044
+
2045
+ # Copied from diffusers.pipelines.pipeline_utils.DiffusionPipeline._maybe_raise_error_if_group_offload_active
2046
+ def _maybe_raise_error_if_group_offload_active(
2047
+ self, raise_error: bool = False, module: Optional[torch.nn.Module] = None
2048
+ ) -> bool:
2049
+ from ..hooks.group_offloading import _is_group_offload_enabled
2050
+
2051
+ components = self.components.values() if module is None else [module]
2052
+ components = [component for component in components if isinstance(component, torch.nn.Module)]
2053
+ for component in components:
2054
+ if _is_group_offload_enabled(component):
2055
+ if raise_error:
2056
+ raise ValueError(
2057
+ "You are trying to apply model/sequential CPU offloading to a pipeline that contains components "
2058
+ "with group offloading enabled. This is not supported. Please disable group offloading for "
2059
+ "components of the pipeline to use other offloading methods."
2060
+ )
2061
+ return True
2062
+ return False
2063
+
2064
+ # Modified from diffusers.pipelines.pipeline_utils.DiffusionPipeline.to
2065
+ def to(self, *args, **kwargs) -> Self:
2066
+ r"""
2067
+ Performs Pipeline dtype and/or device conversion. A torch.dtype and torch.device are inferred from the
2068
+ arguments of `self.to(*args, **kwargs).`
2069
+
2070
+ <Tip>
2071
+
2072
+ If the pipeline already has the correct torch.dtype and torch.device, then it is returned as is. Otherwise,
2073
+ the returned pipeline is a copy of self with the desired torch.dtype and torch.device.
2074
+
2075
+ </Tip>
2076
+
2077
+
2078
+ Here are the ways to call `to`:
2079
+
2080
+ - `to(dtype, silence_dtype_warnings=False) → DiffusionPipeline` to return a pipeline with the specified
2081
+ [`dtype`](https://pytorch.org/docs/stable/tensor_attributes.html#torch.dtype)
2082
+ - `to(device, silence_dtype_warnings=False) → DiffusionPipeline` to return a pipeline with the specified
2083
+ [`device`](https://pytorch.org/docs/stable/tensor_attributes.html#torch.device)
2084
+ - `to(device=None, dtype=None, silence_dtype_warnings=False) → DiffusionPipeline` to return a pipeline with the
2085
+ specified [`device`](https://pytorch.org/docs/stable/tensor_attributes.html#torch.device) and
2086
+ [`dtype`](https://pytorch.org/docs/stable/tensor_attributes.html#torch.dtype)
2087
+
2088
+ Arguments:
2089
+ dtype (`torch.dtype`, *optional*):
2090
+ Returns a pipeline with the specified
2091
+ [`dtype`](https://pytorch.org/docs/stable/tensor_attributes.html#torch.dtype)
2092
+ device (`torch.Device`, *optional*):
2093
+ Returns a pipeline with the specified
2094
+ [`device`](https://pytorch.org/docs/stable/tensor_attributes.html#torch.device)
2095
+ silence_dtype_warnings (`str`, *optional*, defaults to `False`):
2096
+ Whether to omit warnings if the target `dtype` is not compatible with the target `device`.
2097
+
2098
+ Returns:
2099
+ [`DiffusionPipeline`]: The pipeline converted to specified `dtype` and/or `dtype`.
2100
+ """
2101
+ from ..pipelines.pipeline_utils import _check_bnb_status
2102
+ from ..utils import is_accelerate_available, is_accelerate_version, is_hpu_available, is_transformers_version
2103
+
2104
+ dtype = kwargs.pop("dtype", None)
2105
+ device = kwargs.pop("device", None)
2106
+ silence_dtype_warnings = kwargs.pop("silence_dtype_warnings", False)
2107
+
2108
+ dtype_arg = None
2109
+ device_arg = None
2110
+ if len(args) == 1:
2111
+ if isinstance(args[0], torch.dtype):
2112
+ dtype_arg = args[0]
2113
+ else:
2114
+ device_arg = torch.device(args[0]) if args[0] is not None else None
2115
+ elif len(args) == 2:
2116
+ if isinstance(args[0], torch.dtype):
2117
+ raise ValueError(
2118
+ "When passing two arguments, make sure the first corresponds to `device` and the second to `dtype`."
2119
+ )
2120
+ device_arg = torch.device(args[0]) if args[0] is not None else None
2121
+ dtype_arg = args[1]
2122
+ elif len(args) > 2:
2123
+ raise ValueError("Please make sure to pass at most two arguments (`device` and `dtype`) `.to(...)`")
2124
+
2125
+ if dtype is not None and dtype_arg is not None:
2126
+ raise ValueError(
2127
+ "You have passed `dtype` both as an argument and as a keyword argument. Please only pass one of the two."
2128
+ )
2129
+
2130
+ dtype = dtype or dtype_arg
2131
+
2132
+ if device is not None and device_arg is not None:
2133
+ raise ValueError(
2134
+ "You have passed `device` both as an argument and as a keyword argument. Please only pass one of the two."
2135
+ )
2136
+
2137
+ device = device or device_arg
2138
+ device_type = torch.device(device).type if device is not None else None
2139
+ pipeline_has_bnb = any(any((_check_bnb_status(module))) for _, module in self.components.items())
2140
+
2141
+ # throw warning if pipeline is in "offloaded"-mode but user tries to manually set to GPU.
2142
+ def module_is_sequentially_offloaded(module):
2143
+ if not is_accelerate_available() or is_accelerate_version("<", "0.14.0"):
2144
+ return False
2145
+
2146
+ _, _, is_loaded_in_8bit_bnb = _check_bnb_status(module)
2147
+
2148
+ if is_loaded_in_8bit_bnb:
2149
+ return False
2150
+
2151
+ return hasattr(module, "_hf_hook") and (
2152
+ isinstance(module._hf_hook, accelerate.hooks.AlignDevicesHook)
2153
+ or hasattr(module._hf_hook, "hooks")
2154
+ and isinstance(module._hf_hook.hooks[0], accelerate.hooks.AlignDevicesHook)
2155
+ )
2156
+
2157
+ def module_is_offloaded(module):
2158
+ if not is_accelerate_available() or is_accelerate_version("<", "0.17.0.dev0"):
2159
+ return False
2160
+
2161
+ return hasattr(module, "_hf_hook") and isinstance(module._hf_hook, accelerate.hooks.CpuOffload)
2162
+
2163
+ # .to("cuda") would raise an error if the pipeline is sequentially offloaded, so we raise our own to make it clearer
2164
+ pipeline_is_sequentially_offloaded = any(
2165
+ module_is_sequentially_offloaded(module) for _, module in self.components.items()
2166
+ )
2167
+
2168
+ is_pipeline_device_mapped = self.hf_device_map is not None and len(self.hf_device_map) > 1
2169
+ if is_pipeline_device_mapped:
2170
+ raise ValueError(
2171
+ "It seems like you have activated a device mapping strategy on the pipeline which doesn't allow explicit device placement using `to()`. You can call `reset_device_map()` to remove the existing device map from the pipeline."
2172
+ )
2173
+
2174
+ if device_type in ["cuda", "xpu"]:
2175
+ if pipeline_is_sequentially_offloaded and not pipeline_has_bnb:
2176
+ raise ValueError(
2177
+ "It seems like you have activated sequential model offloading by calling `enable_sequential_cpu_offload`, but are now attempting to move the pipeline to GPU. This is not compatible with offloading. Please, move your pipeline `.to('cpu')` or consider removing the move altogether if you use sequential offloading."
2178
+ )
2179
+ # PR: https://github.com/huggingface/accelerate/pull/3223/
2180
+ elif pipeline_has_bnb and is_accelerate_version("<", "1.1.0.dev0"):
2181
+ raise ValueError(
2182
+ "You are trying to call `.to('cuda')` on a pipeline that has models quantized with `bitsandbytes`. Your current `accelerate` installation does not support it. Please upgrade the installation."
2183
+ )
2184
+
2185
+ # Display a warning in this case (the operation succeeds but the benefits are lost)
2186
+ pipeline_is_offloaded = any(module_is_offloaded(module) for _, module in self.components.items())
2187
+ if pipeline_is_offloaded and device_type in ["cuda", "xpu"]:
2188
+ logger.warning(
2189
+ f"It seems like you have activated model offloading by calling `enable_model_cpu_offload`, but are now manually moving the pipeline to GPU. It is strongly recommended against doing so as memory gains from offloading are likely to be lost. Offloading automatically takes care of moving the individual components {', '.join(self.components.keys())} to GPU when needed. To make sure offloading works as expected, you should consider moving the pipeline back to CPU: `pipeline.to('cpu')` or removing the move altogether if you use offloading."
2190
+ )
2191
+
2192
+ # Enable generic support for Intel Gaudi accelerator using GPU/HPU migration
2193
+ if device_type == "hpu" and kwargs.pop("hpu_migration", True) and is_hpu_available():
2194
+ os.environ["PT_HPU_GPU_MIGRATION"] = "1"
2195
+ logger.debug("Environment variable set: PT_HPU_GPU_MIGRATION=1")
2196
+
2197
+ import habana_frameworks.torch # noqa: F401
2198
+
2199
+ # HPU hardware check
2200
+ if not (hasattr(torch, "hpu") and torch.hpu.is_available()):
2201
+ raise ValueError("You are trying to call `.to('hpu')` but HPU device is unavailable.")
2202
+
2203
+ os.environ["PT_HPU_MAX_COMPOUND_OP_SIZE"] = "1"
2204
+ logger.debug("Environment variable set: PT_HPU_MAX_COMPOUND_OP_SIZE=1")
2205
+
2206
+ modules = self.components.values()
2207
+ modules = [m for m in modules if isinstance(m, torch.nn.Module)]
2208
+
2209
+ is_offloaded = pipeline_is_offloaded or pipeline_is_sequentially_offloaded
2210
+ for module in modules:
2211
+ _, is_loaded_in_4bit_bnb, is_loaded_in_8bit_bnb = _check_bnb_status(module)
2212
+ is_group_offloaded = self._maybe_raise_error_if_group_offload_active(module=module)
2213
+
2214
+ if (is_loaded_in_4bit_bnb or is_loaded_in_8bit_bnb) and dtype is not None:
2215
+ logger.warning(
2216
+ f"The module '{module.__class__.__name__}' has been loaded in `bitsandbytes` {'4bit' if is_loaded_in_4bit_bnb else '8bit'} and conversion to {dtype} is not supported. Module is still in {'4bit' if is_loaded_in_4bit_bnb else '8bit'} precision."
2217
+ )
2218
+
2219
+ if is_loaded_in_8bit_bnb and device is not None:
2220
+ logger.warning(
2221
+ f"The module '{module.__class__.__name__}' has been loaded in `bitsandbytes` 8bit and moving it to {device} via `.to()` is not supported. Module is still on {module.device}."
2222
+ )
2223
+
2224
+ # Note: we also handle this at the ModelMixin level. The reason for doing it here too is that modeling
2225
+ # components can be from outside diffusers too, but still have group offloading enabled.
2226
+ if (
2227
+ self._maybe_raise_error_if_group_offload_active(raise_error=False, module=module)
2228
+ and device is not None
2229
+ ):
2230
+ logger.warning(
2231
+ f"The module '{module.__class__.__name__}' is group offloaded and moving it to {device} via `.to()` is not supported."
2232
+ )
2233
+
2234
+ # This can happen for `transformer` models. CPU placement was added in
2235
+ # https://github.com/huggingface/transformers/pull/33122. So, we guard this accordingly.
2236
+ if is_loaded_in_4bit_bnb and device is not None and is_transformers_version(">", "4.44.0"):
2237
+ module.to(device=device)
2238
+ elif not is_loaded_in_4bit_bnb and not is_loaded_in_8bit_bnb and not is_group_offloaded:
2239
+ module.to(device, dtype)
2240
+
2241
+ if (
2242
+ module.dtype == torch.float16
2243
+ and str(device) in ["cpu"]
2244
+ and not silence_dtype_warnings
2245
+ and not is_offloaded
2246
+ ):
2247
+ logger.warning(
2248
+ "Pipelines loaded with `dtype=torch.float16` cannot run with `cpu` device. It"
2249
+ " is not recommended to move them to `cpu` as running them will fail. Please make"
2250
+ " sure to use an accelerator to run the pipeline in inference, due to the lack of"
2251
+ " support for`float16` operations on this device in PyTorch. Please, remove the"
2252
+ " `torch_dtype=torch.float16` argument, or use another device for inference."
2253
+ )
2254
+ return self
2255
+
2256
+ @staticmethod
2257
+ def _component_spec_to_dict(component_spec: ComponentSpec) -> Any:
2258
+ """
2259
+ Convert a ComponentSpec into a JSON‐serializable dict for saving as an entry in `modular_model_index.json`. If
2260
+ the `default_creation_method` is not `from_pretrained`, return None.
2261
+
2262
+ This dict contains:
2263
+ - "type_hint": Tuple[str, str]
2264
+ Library name and class name of the component. (e.g. ("diffusers", "UNet2DConditionModel"))
2265
+ - All loading fields defined by `component_spec.loading_fields()`, typically:
2266
+ - "repo": Optional[str]
2267
+ The model repository (e.g., "stabilityai/stable-diffusion-xl").
2268
+ - "subfolder": Optional[str]
2269
+ A subfolder within the repo where this component lives.
2270
+ - "variant": Optional[str]
2271
+ An optional variant identifier for the model.
2272
+ - "revision": Optional[str]
2273
+ A specific git revision (commit hash, tag, or branch).
2274
+ - ... any other loading fields defined on the spec.
2275
+
2276
+ Args:
2277
+ component_spec (ComponentSpec):
2278
+ The spec object describing one pipeline component.
2279
+
2280
+ Returns:
2281
+ Dict[str, Any]: A mapping suitable for JSON serialization.
2282
+
2283
+ Example:
2284
+ >>> from diffusers.pipelines.modular_pipeline_utils import ComponentSpec >>> from diffusers import
2285
+ UNet2DConditionModel >>> spec = ComponentSpec(
2286
+ ... name="unet", ... type_hint=UNet2DConditionModel, ... config=None, ... repo="path/to/repo", ...
2287
+ subfolder="subfolder", ... variant=None, ... revision=None, ...
2288
+ default_creation_method="from_pretrained",
2289
+ ... ) >>> ModularPipeline._component_spec_to_dict(spec) {
2290
+ "type_hint": ("diffusers", "UNet2DConditionModel"), "repo": "path/to/repo", "subfolder": "subfolder",
2291
+ "variant": None, "revision": None,
2292
+ }
2293
+ """
2294
+ if component_spec.default_creation_method != "from_pretrained":
2295
+ return None
2296
+
2297
+ if component_spec.type_hint is not None:
2298
+ lib_name, cls_name = _fetch_class_library_tuple(component_spec.type_hint)
2299
+ else:
2300
+ lib_name = None
2301
+ cls_name = None
2302
+ load_spec_dict = {k: getattr(component_spec, k) for k in component_spec.loading_fields()}
2303
+ return {
2304
+ "type_hint": (lib_name, cls_name),
2305
+ **load_spec_dict,
2306
+ }
2307
+
2308
+ @staticmethod
2309
+ def _dict_to_component_spec(
2310
+ name: str,
2311
+ spec_dict: Dict[str, Any],
2312
+ ) -> ComponentSpec:
2313
+ """
2314
+ Reconstruct a ComponentSpec from a loading specdict.
2315
+
2316
+ This method converts a dictionary representation back into a ComponentSpec object. The dict should contain:
2317
+ - "type_hint": Tuple[str, str]
2318
+ Library name and class name of the component. (e.g. ("diffusers", "UNet2DConditionModel"))
2319
+ - All loading fields defined by `component_spec.loading_fields()`, typically:
2320
+ - "repo": Optional[str]
2321
+ The model repository (e.g., "stabilityai/stable-diffusion-xl").
2322
+ - "subfolder": Optional[str]
2323
+ A subfolder within the repo where this component lives.
2324
+ - "variant": Optional[str]
2325
+ An optional variant identifier for the model.
2326
+ - "revision": Optional[str]
2327
+ A specific git revision (commit hash, tag, or branch).
2328
+ - ... any other loading fields defined on the spec.
2329
+
2330
+ Args:
2331
+ name (str):
2332
+ The name of the component.
2333
+ specdict (Dict[str, Any]):
2334
+ A dictionary containing the component specification data.
2335
+
2336
+ Returns:
2337
+ ComponentSpec: A reconstructed ComponentSpec object.
2338
+
2339
+ Example:
2340
+ >>> spec_dict = { ... "type_hint": ("diffusers", "UNet2DConditionModel"), ... "repo":
2341
+ "stabilityai/stable-diffusion-xl", ... "subfolder": "unet", ... "variant": None, ... "revision": None, ...
2342
+ } >>> ModularPipeline._dict_to_component_spec("unet", spec_dict) ComponentSpec(
2343
+ name="unet", type_hint=UNet2DConditionModel, config=None, repo="stabilityai/stable-diffusion-xl",
2344
+ subfolder="unet", variant=None, revision=None, default_creation_method="from_pretrained"
2345
+ )
2346
+ """
2347
+ # make a shallow copy so we can pop() safely
2348
+ spec_dict = spec_dict.copy()
2349
+ # pull out and resolve the stored type_hint
2350
+ lib_name, cls_name = spec_dict.pop("type_hint")
2351
+ if lib_name is not None and cls_name is not None:
2352
+ type_hint = simple_get_class_obj(lib_name, cls_name)
2353
+ else:
2354
+ type_hint = None
2355
+
2356
+ # re‐assemble the ComponentSpec
2357
+ return ComponentSpec(
2358
+ name=name,
2359
+ type_hint=type_hint,
2360
+ **spec_dict,
2361
+ )
2362
+
2363
+ def set_progress_bar_config(self, **kwargs):
2364
+ for sub_block_name, sub_block in self.blocks.sub_blocks.items():
2365
+ if hasattr(sub_block, "set_progress_bar_config"):
2366
+ sub_block.set_progress_bar_config(**kwargs)
2367
+
2368
+ def __call__(self, state: PipelineState = None, output: Union[str, List[str]] = None, **kwargs):
2369
+ """
2370
+ Execute the pipeline by running the pipeline blocks with the given inputs.
2371
+
2372
+ Args:
2373
+ state (`PipelineState`, optional):
2374
+ PipelineState instance contains inputs and intermediate values. If None, a new `PipelineState` will be
2375
+ created based on the user inputs and the pipeline blocks's requirement.
2376
+ output (`str` or `List[str]`, optional):
2377
+ Optional specification of what to return:
2378
+ - None: Returns the complete `PipelineState` with all inputs and intermediates (default)
2379
+ - str: Returns a specific intermediate value from the state (e.g. `output="image"`)
2380
+ - List[str]: Returns a dictionary of specific intermediate values (e.g. `output=["image",
2381
+ "latents"]`)
2382
+
2383
+
2384
+ Examples:
2385
+ ```python
2386
+ # Get complete pipeline state
2387
+ state = pipeline(prompt="A beautiful sunset", num_inference_steps=20)
2388
+ print(state.intermediates) # All intermediate outputs
2389
+
2390
+ # Get specific output
2391
+ image = pipeline(prompt="A beautiful sunset", output="image")
2392
+
2393
+ # Get multiple specific outputs
2394
+ results = pipeline(prompt="A beautiful sunset", output=["image", "latents"])
2395
+ image, latents = results["image"], results["latents"]
2396
+
2397
+ # Continue from previous state
2398
+ state = pipeline(prompt="A beautiful sunset")
2399
+ new_state = pipeline(state=state, output="image") # Continue processing
2400
+ ```
2401
+
2402
+ Returns:
2403
+ - If `output` is None: Complete `PipelineState` containing all inputs and intermediates
2404
+ - If `output` is str: The specific intermediate value from the state (e.g. `output="image"`)
2405
+ - If `output` is List[str]: Dictionary mapping output names to their values from the state (e.g.
2406
+ `output=["image", "latents"]`)
2407
+ """
2408
+ if state is None:
2409
+ state = PipelineState()
2410
+
2411
+ # Make a copy of the input kwargs
2412
+ passed_kwargs = kwargs.copy()
2413
+
2414
+ # Add inputs to state, using defaults if not provided in the kwargs or the state
2415
+ # if same input already in the state, will override it if provided in the kwargs
2416
+ for expected_input_param in self.blocks.inputs:
2417
+ name = expected_input_param.name
2418
+ default = expected_input_param.default
2419
+ kwargs_type = expected_input_param.kwargs_type
2420
+ if name in passed_kwargs:
2421
+ state.set(name, passed_kwargs.pop(name), kwargs_type)
2422
+ elif name not in state.values:
2423
+ state.set(name, default, kwargs_type)
2424
+
2425
+ # Warn about unexpected inputs
2426
+ if len(passed_kwargs) > 0:
2427
+ warnings.warn(f"Unexpected input '{passed_kwargs.keys()}' provided. This input will be ignored.")
2428
+ # Run the pipeline
2429
+ with torch.no_grad():
2430
+ try:
2431
+ _, state = self.blocks(self, state)
2432
+ except Exception:
2433
+ error_msg = f"Error in block: ({self.blocks.__class__.__name__}):\n"
2434
+ logger.error(error_msg)
2435
+ raise
2436
+
2437
+ if output is None:
2438
+ return state
2439
+
2440
+ if isinstance(output, str):
2441
+ return state.get(output)
2442
+
2443
+ elif isinstance(output, (list, tuple)):
2444
+ return state.get(output)
2445
+ else:
2446
+ raise ValueError(f"Output '{output}' is not a valid output type")