iimmortall commited on
Commit
bc275c2
·
verified ·
1 Parent(s): c2f7f7c

Deploy InstantRetouch BILA ZeroGPU Space

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +54 -6
  2. app.py +79 -146
  3. demo_runtime/__init__.py +2 -0
  4. demo_runtime/backends.py +380 -0
  5. demo_runtime/bila_layers.py +301 -0
  6. demo_runtime/image_io.py +71 -0
  7. demo_runtime/ip2p_scheduler.json +26 -0
  8. demo_runtime/manager.py +120 -0
  9. demo_runtime/weights.py +84 -0
  10. model_manifest.json +74 -0
  11. requirements.txt +14 -6
  12. vendor/diffusers/__init__.py +1532 -0
  13. vendor/diffusers/callbacks.py +244 -0
  14. vendor/diffusers/commands/__init__.py +27 -0
  15. vendor/diffusers/commands/custom_blocks.py +134 -0
  16. vendor/diffusers/commands/diffusers_cli.py +45 -0
  17. vendor/diffusers/commands/env.py +180 -0
  18. vendor/diffusers/commands/fp16_safetensors.py +132 -0
  19. vendor/diffusers/configuration_utils.py +769 -0
  20. vendor/diffusers/dependency_versions_check.py +34 -0
  21. vendor/diffusers/dependency_versions_table.py +56 -0
  22. vendor/diffusers/experimental/README.md +5 -0
  23. vendor/diffusers/experimental/__init__.py +1 -0
  24. vendor/diffusers/experimental/rl/__init__.py +1 -0
  25. vendor/diffusers/experimental/rl/value_guided_sampling.py +153 -0
  26. vendor/diffusers/guiders/__init__.py +32 -0
  27. vendor/diffusers/guiders/adaptive_projected_guidance.py +235 -0
  28. vendor/diffusers/guiders/adaptive_projected_guidance_mix.py +297 -0
  29. vendor/diffusers/guiders/auto_guidance.py +196 -0
  30. vendor/diffusers/guiders/classifier_free_guidance.py +154 -0
  31. vendor/diffusers/guiders/classifier_free_zero_star_guidance.py +162 -0
  32. vendor/diffusers/guiders/frequency_decoupled_guidance.py +333 -0
  33. vendor/diffusers/guiders/guider_utils.py +394 -0
  34. vendor/diffusers/guiders/magnitude_aware_guidance.py +159 -0
  35. vendor/diffusers/guiders/perturbed_attention_guidance.py +287 -0
  36. vendor/diffusers/guiders/skip_layer_guidance.py +278 -0
  37. vendor/diffusers/guiders/smoothed_energy_guidance.py +267 -0
  38. vendor/diffusers/guiders/tangential_classifier_free_guidance.py +149 -0
  39. vendor/diffusers/hooks/__init__.py +28 -0
  40. vendor/diffusers/hooks/_common.py +56 -0
  41. vendor/diffusers/hooks/_helpers.py +361 -0
  42. vendor/diffusers/hooks/context_parallel.py +302 -0
  43. vendor/diffusers/hooks/faster_cache.py +654 -0
  44. vendor/diffusers/hooks/first_block_cache.py +259 -0
  45. vendor/diffusers/hooks/group_offloading.py +955 -0
  46. vendor/diffusers/hooks/hooks.py +291 -0
  47. vendor/diffusers/hooks/layer_skip.py +263 -0
  48. vendor/diffusers/hooks/layerwise_casting.py +240 -0
  49. vendor/diffusers/hooks/pyramid_attention_broadcast.py +314 -0
  50. vendor/diffusers/hooks/smoothed_energy_guidance_utils.py +167 -0
README.md CHANGED
@@ -1,12 +1,60 @@
1
  ---
2
  title: InstantRetouch
3
- emoji: 🖼
4
- colorFrom: purple
5
- colorTo: red
6
  sdk: gradio
7
- sdk_version: 6.5.1
8
- app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: InstantRetouch
 
 
 
3
  sdk: gradio
4
+ sdk_version: 4.44.1
5
+ python_version: 3.10
6
  pinned: false
7
+ license: other
8
  ---
9
 
10
+ # InstantRetouch / BILA Space Demo
11
+
12
+ This Space is an isolated Hugging Face ZeroGPU/Gradio demo for direct image editing with two BILA backends:
13
+
14
+ - `ip2p_bila_score1_8_104`: InstructPix2Pix base plus BILA checkpoint.
15
+ - `flux_bila_score1_8_022`: Flux2 Klein base, task LoRA, and BILA checkpoint.
16
+
17
+ The demo does not import or depend on the research repo's `agent/` path. It uses the validation-style direct flow:
18
+
19
+ 1. Load the base model.
20
+ 2. Load the selected checkpoint's `state_dict`.
21
+ 3. Generate `bila_output`.
22
+ 4. Apply the UI strength as `input + strength * (bila_output - input)`.
23
+
24
+ ## Required Space Variables
25
+
26
+ Set one of these in the Space environment:
27
+
28
+ - `BILA_WEIGHTS_REPO`: Hugging Face model repo containing the weight layout below.
29
+ - `BILA_MODEL_ROOT`: local path with the same layout, useful only for staging/debugging.
30
+
31
+ Optional:
32
+
33
+ - `HF_TOKEN`: required if `BILA_WEIGHTS_REPO` is private.
34
+ - `BILA_MODEL_CACHE`: cache location. If unset, the app uses `/data/bila-space-demo/hf-cache` when persistent storage exists, otherwise `/tmp/bila-space-demo/hf-cache`.
35
+
36
+ ## Weight Repo Layout
37
+
38
+ Do not commit weights into this Space repo. Put them in a separate HF model repo:
39
+
40
+ ```text
41
+ ip2p/
42
+ base/
43
+ checkpoints/
44
+ epoch_5_bila_score1_8_104.pth
45
+ flux/
46
+ base/
47
+ task_lora/
48
+ pytorch_lora_weights.safetensors
49
+ checkpoints/
50
+ epoch_8_bila_score1_8_022.pth
51
+ metrics/
52
+ ip2p_bila_score1_8_104.json
53
+ flux_bila_score1_8_022.json
54
+ ```
55
+
56
+ The app lazily downloads only the selected model's allow-listed files, so it does not pull both large bases during cold start unless both models are used.
57
+
58
+ ## ZeroGPU Notes
59
+
60
+ ZeroGPU requires the Gradio SDK and the `@spaces.GPU` decorator; this Space is configured that way. A `Dockerfile` is kept only as a fallback for standard paid GPU Spaces.
app.py CHANGED
@@ -1,154 +1,87 @@
1
- import gradio as gr
2
- import numpy as np
3
- import random
4
-
5
- # import spaces #[uncomment to use ZeroGPU]
6
- from diffusers import DiffusionPipeline
7
- import torch
8
-
9
- device = "cuda" if torch.cuda.is_available() else "cpu"
10
- model_repo_id = "stabilityai/sdxl-turbo" # Replace to the model you would like to use
11
-
12
- if torch.cuda.is_available():
13
- torch_dtype = torch.float16
14
- else:
15
- torch_dtype = torch.float32
16
-
17
- pipe = DiffusionPipeline.from_pretrained(model_repo_id, torch_dtype=torch_dtype)
18
- pipe = pipe.to(device)
19
-
20
- MAX_SEED = np.iinfo(np.int32).max
21
- MAX_IMAGE_SIZE = 1024
22
-
23
-
24
- # @spaces.GPU #[uncomment to use ZeroGPU]
25
- def infer(
26
- prompt,
27
- negative_prompt,
28
- seed,
29
- randomize_seed,
30
- width,
31
- height,
32
- guidance_scale,
33
- num_inference_steps,
34
- progress=gr.Progress(track_tqdm=True),
35
- ):
36
- if randomize_seed:
37
- seed = random.randint(0, MAX_SEED)
38
-
39
- generator = torch.Generator().manual_seed(seed)
40
-
41
- image = pipe(
42
- prompt=prompt,
43
- negative_prompt=negative_prompt,
44
- guidance_scale=guidance_scale,
45
- num_inference_steps=num_inference_steps,
46
- width=width,
47
- height=height,
48
- generator=generator,
49
- ).images[0]
50
-
51
- return image, seed
52
-
53
-
54
- examples = [
55
- "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
56
- "An astronaut riding a green horse",
57
- "A delicious ceviche cheesecake slice",
58
- ]
59
-
60
- css = """
61
- #col-container {
62
- margin: 0 auto;
63
- max-width: 640px;
64
- }
65
- """
66
-
67
- with gr.Blocks(css=css) as demo:
68
- with gr.Column(elem_id="col-container"):
69
- gr.Markdown(" # Text-to-Image Gradio Template")
70
-
71
- with gr.Row():
72
- prompt = gr.Text(
73
- label="Prompt",
74
- show_label=False,
75
- max_lines=1,
76
- placeholder="Enter your prompt",
77
- container=False,
78
- )
79
-
80
- run_button = gr.Button("Run", scale=0, variant="primary")
81
 
82
- result = gr.Image(label="Result", show_label=False)
 
83
 
84
- with gr.Accordion("Advanced Settings", open=False):
85
- negative_prompt = gr.Text(
86
- label="Negative prompt",
87
- max_lines=1,
88
- placeholder="Enter a negative prompt",
89
- visible=False,
90
- )
91
 
92
- seed = gr.Slider(
93
- label="Seed",
94
- minimum=0,
95
- maximum=MAX_SEED,
96
- step=1,
97
- value=0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  )
99
-
100
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
101
-
102
- with gr.Row():
103
- width = gr.Slider(
104
- label="Width",
105
- minimum=256,
106
- maximum=MAX_IMAGE_SIZE,
107
- step=32,
108
- value=1024, # Replace with defaults that work for your model
109
- )
110
-
111
- height = gr.Slider(
112
- label="Height",
113
- minimum=256,
114
- maximum=MAX_IMAGE_SIZE,
115
- step=32,
116
- value=1024, # Replace with defaults that work for your model
117
- )
118
-
119
  with gr.Row():
120
- guidance_scale = gr.Slider(
121
- label="Guidance scale",
122
- minimum=0.0,
123
- maximum=10.0,
124
- step=0.1,
125
- value=0.0, # Replace with defaults that work for your model
126
- )
127
-
128
- num_inference_steps = gr.Slider(
129
- label="Number of inference steps",
130
- minimum=1,
131
- maximum=50,
132
- step=1,
133
- value=2, # Replace with defaults that work for your model
134
- )
135
-
136
- gr.Examples(examples=examples, inputs=[prompt])
137
- gr.on(
138
- triggers=[run_button.click, prompt.submit],
139
- fn=infer,
140
- inputs=[
141
- prompt,
142
- negative_prompt,
143
- seed,
144
- randomize_seed,
145
- width,
146
- height,
147
- guidance_scale,
148
- num_inference_steps,
149
- ],
150
- outputs=[result, seed],
151
  )
152
 
 
153
  if __name__ == "__main__":
154
- demo.launch()
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ import sys
4
+ from pathlib import Path
5
 
6
+ import gradio as gr
 
 
 
 
 
 
7
 
8
+ ROOT = Path(__file__).resolve().parent
9
+ sys.path.insert(0, str(ROOT / "vendor"))
10
+
11
+ try:
12
+ import spaces
13
+ except ImportError:
14
+ class _SpacesFallback:
15
+ @staticmethod
16
+ def GPU(*args, **kwargs):
17
+ if args and callable(args[0]) and len(args) == 1 and not kwargs:
18
+ return args[0]
19
+
20
+ def decorator(fn):
21
+ return fn
22
+
23
+ return decorator
24
+
25
+ spaces = _SpacesFallback()
26
+
27
+ from demo_runtime.manager import DemoManager
28
+
29
+
30
+ manager = DemoManager()
31
+
32
+
33
+ @spaces.GPU(duration=300, size="xlarge")
34
+ def run_demo(image, instruction, model_key, seed, max_side, strength):
35
+ try:
36
+ edited, diff, input_image, status = manager.generate(
37
+ image=image,
38
+ instruction=instruction,
39
+ model_key=model_key,
40
+ seed=int(seed),
41
+ max_side=int(max_side),
42
+ strength=float(strength),
43
+ )
44
+ comparison = [(input_image, "Input"), (diff, "Base output"), (edited, "BILA output")]
45
+ return edited, comparison, status
46
+ except Exception as exc:
47
+ raise gr.Error(str(exc))
48
+
49
+
50
+ with gr.Blocks(title="InstantRetouch") as demo:
51
+ gr.Markdown("# InstantRetouch")
52
+ with gr.Row():
53
+ with gr.Column(scale=1):
54
+ image = gr.Image(type="pil", label="Input image")
55
+ instruction = gr.Textbox(label="Instruction", lines=3)
56
+ model_key = gr.Dropdown(
57
+ choices=manager.model_choices,
58
+ value=manager.default_model,
59
+ label="Model",
60
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  with gr.Row():
62
+ seed = gr.Number(value=42, precision=0, label="Seed")
63
+ max_side = gr.Slider(512, 2048, value=1024, step=64, label="Max side")
64
+ strength = gr.Slider(0.0, 2.0, value=1.0, step=0.05, label="Strength")
65
+ button = gr.Button("Run", variant="primary")
66
+ with gr.Column(scale=1):
67
+ edited = gr.Image(type="pil", label="Edited")
68
+ status = gr.Textbox(label="Status", interactive=False)
69
+ comparison = gr.Gallery(label="Comparison", columns=3, height="auto")
70
+
71
+ button.click(
72
+ fn=run_demo,
73
+ inputs=[image, instruction, model_key, seed, max_side, strength],
74
+ outputs=[edited, comparison, status],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  )
76
 
77
+
78
  if __name__ == "__main__":
79
+ try:
80
+ demo.queue(default_concurrency_limit=1, max_size=8)
81
+ except TypeError:
82
+ demo.queue(concurrency_count=1, max_size=8)
83
+ demo.launch(
84
+ server_name="0.0.0.0",
85
+ server_port=7860,
86
+ show_api=False,
87
+ )
demo_runtime/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """Runtime package for the isolated BILA Hugging Face demo."""
2
+
demo_runtime/backends.py ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import gc
5
+ import json
6
+ from pathlib import Path
7
+ from types import SimpleNamespace
8
+ from typing import Dict, List
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+ import torch.nn.functional as F
13
+
14
+ from .bila_layers import Bilateral_Grid_Joint_Flux, Biliteral_Grid_Joint
15
+
16
+
17
+ LORA_TARGET_MODULES = (
18
+ "to_q,to_k,to_v,to_out.0,add_q_proj,add_k_proj,add_v_proj,to_add_out,"
19
+ "linear_in,linear_out,to_qkv_mlp_proj,"
20
+ "single_transformer_blocks.0.attn.to_out,"
21
+ "single_transformer_blocks.1.attn.to_out,"
22
+ "single_transformer_blocks.2.attn.to_out,"
23
+ "single_transformer_blocks.3.attn.to_out,"
24
+ "single_transformer_blocks.4.attn.to_out,"
25
+ "single_transformer_blocks.5.attn.to_out,"
26
+ "single_transformer_blocks.6.attn.to_out,"
27
+ "single_transformer_blocks.7.attn.to_out,"
28
+ "single_transformer_blocks.8.attn.to_out,"
29
+ "single_transformer_blocks.9.attn.to_out,"
30
+ "single_transformer_blocks.10.attn.to_out,"
31
+ "single_transformer_blocks.11.attn.to_out,"
32
+ "single_transformer_blocks.12.attn.to_out,"
33
+ "single_transformer_blocks.13.attn.to_out,"
34
+ "single_transformer_blocks.14.attn.to_out,"
35
+ "single_transformer_blocks.15.attn.to_out,"
36
+ "single_transformer_blocks.16.attn.to_out,"
37
+ "single_transformer_blocks.17.attn.to_out,"
38
+ "single_transformer_blocks.18.attn.to_out,"
39
+ "single_transformer_blocks.19.attn.to_out"
40
+ )
41
+
42
+
43
+ def _device() -> torch.device:
44
+ if not torch.cuda.is_available():
45
+ raise RuntimeError("This demo requires a CUDA GPU Space.")
46
+ return torch.device("cuda")
47
+
48
+
49
+ def _checkpoint_state(path: Path, required: List[str]) -> Dict:
50
+ state = torch.load(path, map_location="cpu")
51
+ if "state_dict" not in state:
52
+ raise ValueError(f"{path} is missing top-level state_dict")
53
+ state_dict = state["state_dict"]
54
+ missing = [key.split(".", 1)[1] for key in required if key.startswith("state_dict.") and key.split(".", 1)[1] not in state_dict]
55
+ if missing:
56
+ raise ValueError(f"{path} is missing checkpoint entries: {missing}")
57
+ return state_dict
58
+
59
+
60
+ class Ip2pBilaBackend(nn.Module):
61
+ def __init__(self, model_cfg: Dict, paths: Dict[str, Path]):
62
+ super().__init__()
63
+ self.model_cfg = model_cfg
64
+ self.paths = paths
65
+ self.config = model_cfg["config"]
66
+ self.device = _device()
67
+ self.weight_dtype = torch.float32
68
+ self.bila_feat = None
69
+
70
+ from diffusers import AutoencoderKL, DDPMScheduler, UNet2DConditionModel
71
+ from transformers import CLIPTextModel, CLIPTokenizer
72
+
73
+ base = paths["base"]
74
+ self.tokenizer = CLIPTokenizer.from_pretrained(base, subfolder="tokenizer")
75
+ self.text_encoder = CLIPTextModel.from_pretrained(base, subfolder="text_encoder").to(self.device)
76
+ self.vae = AutoencoderKL.from_pretrained(base, subfolder="vae").to(self.device)
77
+ self.unet = UNet2DConditionModel.from_pretrained(base, subfolder="unet").to(self.device)
78
+
79
+ scheduler_path = Path(__file__).with_name("ip2p_scheduler.json")
80
+ with scheduler_path.open("r", encoding="utf-8") as handle:
81
+ self.noise_scheduler = DDPMScheduler.from_config(json.load(handle))
82
+ self.noise_scheduler.alphas_cumprod = self.noise_scheduler.alphas_cumprod.to(self.device)
83
+ self.timesteps = torch.tensor([999], device=self.device).long()
84
+
85
+ self.unet.up_blocks[3].register_forward_hook(self._forward_hook)
86
+ self.bila_grid = Biliteral_Grid_Joint(
87
+ grid_res=self.config["bila_grid_res"],
88
+ grid_bins=self.config["bila_grid_bins"],
89
+ ).to(self.device)
90
+
91
+ state_dict = _checkpoint_state(paths["checkpoint"], model_cfg["expected_checkpoint_keys"])
92
+ self.unet.load_state_dict(state_dict["unet"])
93
+ self.bila_grid.load_state_dict(state_dict["bila"])
94
+ self.eval()
95
+ self.requires_grad_(False)
96
+
97
+ def _forward_hook(self, module, inputs, output):
98
+ del module, inputs
99
+ self.bila_feat = [output]
100
+
101
+ @torch.no_grad()
102
+ def _encode_prompt(self, prompt_batch):
103
+ tokens = self.tokenizer(
104
+ prompt_batch,
105
+ padding="max_length",
106
+ max_length=77,
107
+ truncation=True,
108
+ return_tensors="pt",
109
+ return_overflowing_tokens=False,
110
+ ).input_ids.to(self.device)
111
+ return self.text_encoder(tokens).last_hidden_state.detach()
112
+
113
+ @torch.inference_mode()
114
+ def forward(self, input_imgs, input_prompts, input_fullres):
115
+ input_imgs = input_imgs.to(self.device, dtype=self.weight_dtype)
116
+ input_fullres = input_fullres.to(self.device, dtype=self.weight_dtype)
117
+ vae_input = input_imgs * 2 - 1
118
+ image_latents = self.vae.encode(vae_input).latent_dist.mode()
119
+ noisy_latents = torch.randn(
120
+ image_latents.shape,
121
+ device=image_latents.device,
122
+ dtype=image_latents.dtype,
123
+ )
124
+ noisy_latents = noisy_latents * self.noise_scheduler.init_noise_sigma
125
+ encoder_hidden_states = self._encode_prompt(input_prompts)
126
+ timesteps = torch.ones((image_latents.shape[0],), device=self.device).long() * 999
127
+
128
+ concatenated_noisy_latents = torch.cat([noisy_latents, image_latents], dim=1)
129
+ model_pred = self.unet(concatenated_noisy_latents, timesteps, encoder_hidden_states).sample
130
+
131
+ alpha_prod = self.noise_scheduler.alphas_cumprod.to(image_latents.device, dtype=model_pred.dtype)
132
+ beta_prod = 1 - alpha_prod
133
+ alpha_prod_t = alpha_prod[timesteps].view(-1, 1, 1, 1)
134
+ beta_prod_t = beta_prod[timesteps].view(-1, 1, 1, 1)
135
+ x_denoised = (noisy_latents - beta_prod_t.sqrt() * model_pred) / alpha_prod_t.sqrt()
136
+
137
+ pred_images = self.vae.decode((1 / 0.18215) * x_denoised.to(self.weight_dtype), return_dict=False)[0]
138
+ diff_out_img = (pred_images / 2 + 0.5).clamp(0, 1)
139
+ bila_feat = [feat.float() for feat in self.bila_feat]
140
+ bila_out_img, _ = self.bila_grid(bila_feat, input_fullres)
141
+ return {"diff": diff_out_img.detach().cpu(), "bila": bila_out_img.detach().cpu()}
142
+
143
+
144
+ def _patch_attention_for_gqa():
145
+ original = F.scaled_dot_product_attention
146
+ if getattr(original, "_bila_gqa_patched", False):
147
+ return
148
+
149
+ def patched_scaled_dot_product_attention(*args, **kwargs):
150
+ kwargs.pop("enable_gqa", None)
151
+ return original(*args, **kwargs)
152
+
153
+ patched_scaled_dot_product_attention._bila_gqa_patched = True
154
+ F.scaled_dot_product_attention = patched_scaled_dot_product_attention
155
+
156
+
157
+ def _load_task_lora_state_dict(task_lora_path):
158
+ from diffusers import Flux2KleinPipeline
159
+ from diffusers.utils import convert_unet_state_dict_to_peft
160
+
161
+ lora_state_dict = Flux2KleinPipeline.lora_state_dict(task_lora_path)
162
+ transformer_lora_sd = {
163
+ key.replace("transformer.", ""): value
164
+ for key, value in lora_state_dict.items()
165
+ if key.startswith("transformer.")
166
+ }
167
+ return convert_unet_state_dict_to_peft(transformer_lora_sd)
168
+
169
+
170
+ def _build_lora_config(rank, alpha, dropout=0.0):
171
+ from peft import LoraConfig
172
+
173
+ return LoraConfig(
174
+ r=rank,
175
+ lora_alpha=alpha,
176
+ lora_dropout=dropout,
177
+ init_lora_weights="gaussian",
178
+ target_modules=[module.strip() for module in LORA_TARGET_MODULES.split(",")],
179
+ )
180
+
181
+
182
+ def _load_flux_transformer(args):
183
+ from diffusers import Flux2Transformer2DModel
184
+ from peft import set_peft_model_state_dict
185
+ from peft.tuners.lora.layer import LoraLayer
186
+
187
+ transformer = Flux2Transformer2DModel.from_pretrained(args.pipeline_path, subfolder="transformer")
188
+
189
+ if args.task_lora_path:
190
+ task_lora_sd = _load_task_lora_state_dict(args.task_lora_path)
191
+ task_lora_config = _build_lora_config(args.task_lora_rank, args.task_lora_alpha)
192
+ transformer.add_adapter(task_lora_config, adapter_name="task")
193
+ set_peft_model_state_dict(transformer, task_lora_sd, adapter_name="task")
194
+ for module in transformer.modules():
195
+ if isinstance(module, LoraLayer):
196
+ module.merge(adapter_names=["task"])
197
+ transformer.delete_adapters("task")
198
+
199
+ distill_lora_config = _build_lora_config(
200
+ args.distill_lora_rank,
201
+ args.distill_lora_alpha,
202
+ args.distill_lora_dropout,
203
+ )
204
+ transformer.add_adapter(distill_lora_config, adapter_name="distill")
205
+ return transformer
206
+
207
+
208
+ class FluxBilaBackend(nn.Module):
209
+ def __init__(self, model_cfg: Dict, paths: Dict[str, Path]):
210
+ super().__init__()
211
+ _patch_attention_for_gqa()
212
+ self.model_cfg = model_cfg
213
+ self.paths = paths
214
+ self.config = model_cfg["config"]
215
+ self.device = _device()
216
+ self.args = SimpleNamespace(
217
+ pipeline_path=str(paths["base"]),
218
+ task_lora_path=str(paths["task_lora"]),
219
+ use_t2i=False,
220
+ cfg=False,
221
+ bila_use_flux_rgb=False,
222
+ fix_guide_map=False,
223
+ bila_grid_res=self.config["bila_grid_res"],
224
+ bila_grid_bins=self.config["bila_grid_bins"],
225
+ mixed_precision=self.config["mixed_precision"],
226
+ max_sequence_length=self.config["max_sequence_length"],
227
+ distill_strategy=self.config["distill_strategy"],
228
+ distill_lora_rank=self.config["distill_lora_rank"],
229
+ distill_lora_alpha=self.config["distill_lora_alpha"],
230
+ distill_lora_dropout=self.config["distill_lora_dropout"],
231
+ task_lora_rank=self.config["task_lora_rank"],
232
+ task_lora_alpha=self.config["task_lora_alpha"],
233
+ )
234
+
235
+ from diffusers import AutoencoderKLFlux2, FlowMatchEulerDiscreteScheduler, Flux2KleinPipeline
236
+ from peft import set_peft_model_state_dict
237
+ from transformers import Qwen2TokenizerFast, Qwen3ForCausalLM
238
+
239
+ self.Flux2KleinPipeline = Flux2KleinPipeline
240
+ self.tokenizer = Qwen2TokenizerFast.from_pretrained(paths["base"], subfolder="tokenizer")
241
+ self.text_encoder = Qwen3ForCausalLM.from_pretrained(paths["base"], subfolder="text_encoder").to(self.device)
242
+ self.vae = AutoencoderKLFlux2.from_pretrained(paths["base"], subfolder="vae").to(self.device)
243
+ self.transformer = _load_flux_transformer(self.args).to(self.device)
244
+ self.noise_scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(paths["base"], subfolder="scheduler")
245
+ self.noise_scheduler_copy = copy.deepcopy(self.noise_scheduler)
246
+
247
+ self.latents_bn_mean = self.vae.bn.running_mean.view(1, -1, 1, 1).to(self.device)
248
+ self.latents_bn_std = torch.sqrt(
249
+ self.vae.bn.running_var.view(1, -1, 1, 1).to(self.device) + self.vae.config.batch_norm_eps
250
+ )
251
+
252
+ self.text_encoding_pipeline = Flux2KleinPipeline.from_pretrained(
253
+ paths["base"],
254
+ vae=None,
255
+ transformer=None,
256
+ tokenizer=self.tokenizer,
257
+ text_encoder=self.text_encoder,
258
+ scheduler=None,
259
+ )
260
+
261
+ self.one_step_sigma = 1.0
262
+ self.weight_dtype = torch.bfloat16
263
+ if self.args.mixed_precision == "fp16":
264
+ self.weight_dtype = torch.float16
265
+ elif self.args.mixed_precision == "no":
266
+ self.weight_dtype = torch.float32
267
+
268
+ self.vae.to(dtype=self.weight_dtype)
269
+ self.transformer.to(dtype=self.weight_dtype)
270
+ self.text_encoder.to(dtype=self.weight_dtype)
271
+
272
+ self.bila_feat = None
273
+ if hasattr(self.transformer, "single_transformer_blocks") and len(self.transformer.single_transformer_blocks) > 0:
274
+ self.transformer.single_transformer_blocks[-1].register_forward_hook(self._forward_hook)
275
+ else:
276
+ self.transformer.transformer_blocks[-1].register_forward_hook(self._forward_hook)
277
+
278
+ self.bila_grid = Bilateral_Grid_Joint_Flux(
279
+ grid_res=self.config["bila_grid_res"],
280
+ grid_bins=self.config["bila_grid_bins"],
281
+ ).to(self.device)
282
+
283
+ state_dict = _checkpoint_state(paths["checkpoint"], model_cfg["expected_checkpoint_keys"])
284
+ adapter_name = state_dict.get("active_adapter_name", "distill")
285
+ set_peft_model_state_dict(self.transformer, state_dict["transformer_lora"], adapter_name=adapter_name)
286
+ if hasattr(self.transformer, "set_adapter"):
287
+ self.transformer.set_adapter(adapter_name)
288
+ self.bila_grid.load_state_dict(state_dict["bila"])
289
+
290
+ self.eval()
291
+ self.requires_grad_(False)
292
+
293
+ def _forward_hook(self, module, inputs, output):
294
+ del module, inputs
295
+ self.bila_feat = [output[1] if isinstance(output, tuple) else output]
296
+
297
+ @torch.no_grad()
298
+ def _encode_prompt(self, prompt_batch):
299
+ prompt_embeds, text_ids = self.text_encoding_pipeline.encode_prompt(
300
+ prompt=prompt_batch,
301
+ max_sequence_length=self.args.max_sequence_length,
302
+ )
303
+ return prompt_embeds.detach(), text_ids.detach()
304
+
305
+ def _prepare_latent_ids_and_cond_ids(self, model_input, cond_model_input):
306
+ model_input_ids = self.Flux2KleinPipeline._prepare_latent_ids(model_input).to(device=model_input.device)
307
+ cond_model_input_list = [cond_model_input[i].unsqueeze(0) for i in range(cond_model_input.shape[0])]
308
+ cond_model_input_ids = self.Flux2KleinPipeline._prepare_image_ids(cond_model_input_list).to(
309
+ device=cond_model_input.device
310
+ )
311
+ cond_model_input_ids = cond_model_input_ids.view(
312
+ cond_model_input.shape[0], -1, model_input_ids.shape[-1]
313
+ )
314
+ return model_input_ids, cond_model_input_ids
315
+
316
+ @torch.inference_mode()
317
+ def forward(self, input_imgs, input_prompts, input_fullres):
318
+ input_imgs = input_imgs.to(self.device, dtype=self.weight_dtype)
319
+ input_fullres = input_fullres.to(self.device, dtype=self.weight_dtype)
320
+ vae_input = input_imgs * 2 - 1
321
+
322
+ image_latents = self.vae.encode(vae_input).latent_dist.mode()
323
+ image_latents_patched = self.Flux2KleinPipeline._patchify_latents(image_latents)
324
+ cond_model_input = (image_latents_patched - self.latents_bn_mean) / self.latents_bn_std
325
+ noisy_latents = torch.randn_like(cond_model_input)
326
+
327
+ model_input_ids, cond_model_input_ids = self._prepare_latent_ids_and_cond_ids(
328
+ noisy_latents, cond_model_input
329
+ )
330
+ prompt_embeds, text_ids = self._encode_prompt(input_prompts)
331
+ bsz = noisy_latents.shape[0]
332
+ timestep_input = (torch.ones((bsz,), device=self.device) * 999.0) / 1000.0
333
+
334
+ packed_noisy = self.Flux2KleinPipeline._pack_latents(noisy_latents)
335
+ packed_cond = self.Flux2KleinPipeline._pack_latents(cond_model_input)
336
+ orig_shape = packed_noisy.shape
337
+ orig_ids_shape = model_input_ids.shape
338
+ packed_input = torch.cat([packed_noisy, packed_cond], dim=1)
339
+ ids_input = torch.cat([model_input_ids, cond_model_input_ids], dim=1)
340
+
341
+ model_pred = self.transformer(
342
+ hidden_states=packed_input,
343
+ timestep=timestep_input,
344
+ guidance=None,
345
+ encoder_hidden_states=prompt_embeds,
346
+ txt_ids=text_ids,
347
+ img_ids=ids_input,
348
+ return_dict=False,
349
+ )[0]
350
+
351
+ model_pred = model_pred[:, : orig_shape[1], :]
352
+ model_pred = self.Flux2KleinPipeline._unpack_latents_with_ids(
353
+ model_pred,
354
+ model_input_ids[:, : orig_ids_shape[1], :],
355
+ )
356
+ x0_pred_normalized = noisy_latents - self.one_step_sigma * model_pred
357
+ x0_pred_patched = x0_pred_normalized * self.latents_bn_std + self.latents_bn_mean
358
+ x_denoised = self.Flux2KleinPipeline._unpatchify_latents(x0_pred_patched)
359
+
360
+ pred_images = self.vae.decode(x_denoised.to(self.weight_dtype), return_dict=False)[0]
361
+ diff_out_img = (pred_images / 2 + 0.5).clamp(0, 1)
362
+
363
+ num_txt_tokens = prompt_embeds.shape[1]
364
+ bila_feat = [feat.float() for feat in self.bila_feat]
365
+ bila_feat = [feat[:, num_txt_tokens:, ...] for feat in bila_feat]
366
+ bila_feat = [feat[:, : orig_shape[1], :] for feat in bila_feat]
367
+ bila_out_img, _ = self.bila_grid(
368
+ bila_feat,
369
+ input_fullres,
370
+ latent_h=cond_model_input.shape[-2],
371
+ latent_w=cond_model_input.shape[-1],
372
+ )
373
+ return {"diff": diff_out_img.detach().cpu(), "bila": bila_out_img.detach().cpu()}
374
+
375
+
376
+ def release_cuda():
377
+ gc.collect()
378
+ if torch.cuda.is_available():
379
+ torch.cuda.empty_cache()
380
+
demo_runtime/bila_layers.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+
8
+
9
+ def conv_layer(
10
+ in_channels,
11
+ out_channels,
12
+ kernel_size,
13
+ stride=1,
14
+ padding=0,
15
+ bias=True,
16
+ activation="silu",
17
+ batch_norm=False,
18
+ ):
19
+ layers = [
20
+ nn.Conv2d(
21
+ in_channels,
22
+ out_channels,
23
+ kernel_size,
24
+ stride=stride,
25
+ padding=padding,
26
+ bias=bias,
27
+ )
28
+ ]
29
+ if batch_norm:
30
+ layers.append(nn.BatchNorm2d(out_channels))
31
+ if activation is not None:
32
+ if activation == "silu":
33
+ layers.append(nn.SiLU())
34
+ elif activation == "leakyrelu":
35
+ layers.append(nn.LeakyReLU(negative_slope=0.2))
36
+ else:
37
+ layers.append(nn.ReLU())
38
+ return nn.Sequential(*layers)
39
+
40
+
41
+ def fc_layer(in_channels, out_channels, bias=True, activation=nn.ReLU, batch_norm=False):
42
+ layers = [nn.Linear(int(in_channels), int(out_channels), bias=bias)]
43
+ if batch_norm:
44
+ layers.append(nn.BatchNorm1d(out_channels))
45
+ if activation is not None:
46
+ layers.append(activation())
47
+ return nn.Sequential(*layers)
48
+
49
+
50
+ def rgb_to_grayscale(x):
51
+ weights = x.new_tensor([0.2989, 0.5870, 0.1140]).view(1, 3, 1, 1)
52
+ return (x * weights).sum(dim=1, keepdim=True)
53
+
54
+
55
+ def slicing(grid, guide):
56
+ n, _, h, w = guide.shape
57
+ device = grid.device
58
+ hh, ww = torch.meshgrid(
59
+ torch.arange(h, device=device),
60
+ torch.arange(w, device=device),
61
+ indexing="ij",
62
+ )
63
+ hh = hh / (h - 1) * 2 - 1
64
+ ww = ww / (w - 1) * 2 - 1
65
+ guide = guide * 2 - 1
66
+ hh = hh[None, :, :, None].repeat(n, 1, 1, 1)
67
+ ww = ww[None, :, :, None].repeat(n, 1, 1, 1)
68
+ guide = guide.permute(0, 2, 3, 1)
69
+ guide_coords = torch.cat([ww, hh, guide], dim=3).unsqueeze(1)
70
+ sliced = F.grid_sample(grid, guide_coords, align_corners=False, padding_mode="border")
71
+ return sliced.squeeze(2)
72
+
73
+
74
+ def apply(sliced, fullres):
75
+ rr = torch.sum(fullres * sliced[:, 0:3, :, :], dim=1) + sliced[:, 3, :, :]
76
+ gg = torch.sum(fullres * sliced[:, 4:7, :, :], dim=1) + sliced[:, 7, :, :]
77
+ bb = torch.sum(fullres * sliced[:, 8:11, :, :], dim=1) + sliced[:, 11, :, :]
78
+ return torch.stack([rr, gg, bb], dim=1)
79
+
80
+
81
+ class Guide(nn.Module):
82
+ def __init__(self, c_in=3):
83
+ super().__init__()
84
+ self.nrelus = 16
85
+ self.c_in = c_in
86
+ self.M = nn.Parameter(
87
+ torch.eye(c_in, dtype=torch.float32) + torch.randn(1, dtype=torch.float32) * 1e-4
88
+ )
89
+ self.M_bias = nn.Parameter(torch.zeros(c_in, dtype=torch.float32))
90
+ thresholds = np.linspace(0, 1, self.nrelus, endpoint=False, dtype=np.float32)
91
+ thresholds = torch.tensor(thresholds)[None, None, None, :].repeat(1, 1, c_in, 1)
92
+ self.thresholds = nn.Parameter(thresholds)
93
+ slopes = torch.zeros(1, 1, 1, c_in, self.nrelus, dtype=torch.float32)
94
+ slopes[:, :, :, :, 0] = 1.0
95
+ self.slopes = nn.Parameter(slopes)
96
+ self.relu = nn.ReLU()
97
+ self.bias = nn.Parameter(torch.tensor(0, dtype=torch.float32))
98
+
99
+ def forward(self, x):
100
+ x = x.permute(0, 2, 3, 1)
101
+ old_shape = x.shape
102
+ x = torch.matmul(x.reshape(-1, self.c_in), self.M) + self.M_bias
103
+ x = x.reshape(old_shape).unsqueeze(4)
104
+ x = torch.sum(self.slopes * self.relu(x - self.thresholds), dim=4)
105
+ x = x.permute(0, 3, 1, 2)
106
+ x = torch.sum(x, dim=1, keepdim=True) / self.c_in
107
+ return torch.clamp(x + self.bias, 0, 1)
108
+
109
+
110
+ class Biliteral_Grid_Joint(nn.Module):
111
+ def __init__(
112
+ self,
113
+ in_channels: int = 3,
114
+ channels: List[int] = None,
115
+ fix_guide: bool = False,
116
+ grid_res: int = 16,
117
+ grid_bins: int = 8,
118
+ ):
119
+ super().__init__()
120
+ del in_channels, channels
121
+ bn = False
122
+ activation = "relu"
123
+ self.grid_res = grid_res
124
+ self.grid_bins = grid_bins
125
+
126
+ if grid_res == 16:
127
+ self.down1 = conv_layer(320, 256, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
128
+ self.down2 = conv_layer(256, 128, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
129
+ self.local1 = conv_layer(128, 128, kernel_size=3, padding=1, bias=False, activation=None)
130
+ self.global1 = conv_layer(128, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
131
+ self.global2 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
132
+ self.global3 = fc_layer(4 * 4 * 64, 256, batch_norm=bn)
133
+ self.global4 = fc_layer(256, 128, activation=None)
134
+ elif grid_res == 32:
135
+ self.down1 = conv_layer(320, 256, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
136
+ self.down2 = conv_layer(256, 128, kernel_size=3, padding=1, batch_norm=bn, activation=activation)
137
+ self.local1 = conv_layer(128, 128, kernel_size=3, padding=1, bias=False, activation=None)
138
+ self.global1 = conv_layer(128, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
139
+ self.global2 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
140
+ self.global2_1 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
141
+ self.global3 = fc_layer(4 * 4 * 64, 256, batch_norm=bn)
142
+ self.global4 = fc_layer(256, 128, activation=None)
143
+ elif grid_res == 8:
144
+ self.down1 = conv_layer(320, 256, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
145
+ self.down2 = conv_layer(256, 128, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
146
+ self.down3 = conv_layer(128, 128, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
147
+ self.local1 = conv_layer(128, 128, kernel_size=3, padding=1, bias=False, activation=None)
148
+ self.global1 = conv_layer(128, 64, kernel_size=3, padding=1, batch_norm=bn, activation=activation)
149
+ self.global2 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
150
+ self.global3 = fc_layer(4 * 4 * 64, 256, batch_norm=bn)
151
+ self.global4 = fc_layer(256, 128, activation=None)
152
+ elif grid_res == 64:
153
+ self.down1 = conv_layer(320, 256, kernel_size=3, padding=1, batch_norm=bn, activation=activation)
154
+ self.down2 = conv_layer(256, 128, kernel_size=3, padding=1, batch_norm=bn, activation=activation)
155
+ self.local1 = conv_layer(128, 128, kernel_size=3, padding=1, bias=False, activation=None)
156
+ self.global1 = conv_layer(128, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
157
+ self.global2 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
158
+ self.global2_1 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
159
+ self.global3 = fc_layer(8 * 8 * 64, 256, batch_norm=bn)
160
+ self.global4 = fc_layer(256, 128, activation=None)
161
+ else:
162
+ raise NotImplementedError(f"unsupported grid_res={grid_res}")
163
+
164
+ self.pred = conv_layer(128, 12 * self.grid_bins, kernel_size=1, activation=None)
165
+ self.relu = nn.ReLU()
166
+ self.fix_guide = fix_guide
167
+ self.guide = Guide()
168
+
169
+ def forward(self, feat, x_full, only_gen_grid=False, return_guide=False):
170
+ x = feat[0]
171
+ n = x.shape[0]
172
+ x = self.down1(x)
173
+ x = self.down2(x)
174
+ if self.grid_res == 8:
175
+ x = self.down3(x)
176
+ downed = x
177
+
178
+ local_out = self.local1(x)
179
+ x = self.global1(downed)
180
+ x = self.global2(x)
181
+ if self.grid_res in (32, 64):
182
+ x = self.global2_1(x)
183
+ x = x.reshape(n, -1)
184
+ x = self.global3(x)
185
+ global_out = self.global4(x)
186
+ fusion = self.relu(local_out + global_out[:, :, None, None])
187
+
188
+ x = self.pred(fusion)
189
+ x = x.view(n, 12, self.grid_bins, self.grid_res, self.grid_res)
190
+ coeffs = x.reshape(x.shape[0], 12, -1, x.shape[-2], x.shape[-1])
191
+ if only_gen_grid:
192
+ return coeffs
193
+
194
+ guide = rgb_to_grayscale(x_full) if self.fix_guide else self.guide(x_full)
195
+ out = apply(slicing(coeffs, guide), x_full)
196
+ if return_guide:
197
+ return out, coeffs, guide
198
+ return out, coeffs
199
+
200
+
201
+ class Bilateral_Grid_Joint_Flux(nn.Module):
202
+ def __init__(
203
+ self,
204
+ hidden_dim: int = 3072,
205
+ proj_channels: int = 256,
206
+ img_token_start: int = 0,
207
+ fix_guide: bool = False,
208
+ grid_res: int = 16,
209
+ grid_bins: int = 8,
210
+ ):
211
+ super().__init__()
212
+ bn = False
213
+ activation = "relu"
214
+ self.grid_res = grid_res
215
+ self.grid_bins = grid_bins
216
+ self.img_token_start = img_token_start
217
+ self.channel_proj = nn.Sequential(
218
+ nn.Linear(hidden_dim, proj_channels),
219
+ nn.GELU(),
220
+ nn.Linear(proj_channels, proj_channels),
221
+ )
222
+
223
+ if grid_res == 16:
224
+ self.down1 = conv_layer(proj_channels, 128, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
225
+ self.local1 = conv_layer(128, 128, kernel_size=3, padding=1, bias=False, activation=None)
226
+ self.global1 = conv_layer(128, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
227
+ self.global2 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
228
+ self.global3 = fc_layer(4 * 4 * 64, 256, batch_norm=bn)
229
+ self.global4 = fc_layer(256, 128, activation=None)
230
+ elif grid_res == 32:
231
+ self.down1 = conv_layer(proj_channels, 128, kernel_size=3, padding=1, batch_norm=bn, activation=activation)
232
+ self.local1 = conv_layer(128, 128, kernel_size=3, padding=1, bias=False, activation=None)
233
+ self.global1 = conv_layer(128, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
234
+ self.global2 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
235
+ self.global2_1 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
236
+ self.global3 = fc_layer(4 * 4 * 64, 256, batch_norm=bn)
237
+ self.global4 = fc_layer(256, 128, activation=None)
238
+ elif grid_res == 8:
239
+ self.down1 = conv_layer(proj_channels, 128, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
240
+ self.down2 = conv_layer(128, 128, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
241
+ self.local1 = conv_layer(128, 128, kernel_size=3, padding=1, bias=False, activation=None)
242
+ self.global1 = conv_layer(128, 64, kernel_size=3, padding=1, batch_norm=bn, activation=activation)
243
+ self.global2 = conv_layer(64, 64, kernel_size=3, stride=2, padding=1, batch_norm=bn, activation=activation)
244
+ self.global3 = fc_layer(4 * 4 * 64, 256, batch_norm=bn)
245
+ self.global4 = fc_layer(256, 128, activation=None)
246
+ else:
247
+ raise NotImplementedError(f"unsupported grid_res={grid_res}")
248
+
249
+ self.pred = conv_layer(128, 12 * self.grid_bins, kernel_size=1, activation=None)
250
+ self.relu = nn.ReLU()
251
+ self.fix_guide = fix_guide
252
+ self.guide = Guide()
253
+
254
+ def _extract_image_tokens(self, feat, latent_h, latent_w):
255
+ bsz = feat.shape[0]
256
+ num_img_tokens = latent_h * latent_w
257
+ img_tokens = feat[:, self.img_token_start : self.img_token_start + num_img_tokens, :]
258
+ img_tokens = self.channel_proj(img_tokens)
259
+ return img_tokens.permute(0, 2, 1).reshape(bsz, -1, latent_h, latent_w)
260
+
261
+ def forward(
262
+ self,
263
+ feat,
264
+ x_full,
265
+ only_gen_grid=False,
266
+ return_guide=False,
267
+ latent_h: int = 32,
268
+ latent_w: int = 32,
269
+ ):
270
+ if isinstance(feat, (list, tuple)):
271
+ feat = feat[0]
272
+ x = self._extract_image_tokens(feat, latent_h, latent_w)
273
+ n = x.shape[0]
274
+
275
+ x = self.down1(x)
276
+ if self.grid_res == 8:
277
+ x = self.down2(x)
278
+ downed = x
279
+ local_out = self.local1(x)
280
+
281
+ x = self.global1(downed)
282
+ x = self.global2(x)
283
+ if self.grid_res == 32:
284
+ x = self.global2_1(x)
285
+ x = x.reshape(n, -1)
286
+ x = self.global3(x)
287
+ global_out = self.global4(x)
288
+ fusion = self.relu(local_out + global_out[:, :, None, None])
289
+
290
+ x = self.pred(fusion)
291
+ x = x.view(n, 12, self.grid_bins, self.grid_res, self.grid_res)
292
+ coeffs = x.reshape(x.shape[0], 12, -1, x.shape[-2], x.shape[-1])
293
+ if only_gen_grid:
294
+ return coeffs
295
+
296
+ guide = rgb_to_grayscale(x_full) if self.fix_guide else self.guide(x_full)
297
+ out = apply(slicing(coeffs, guide), x_full)
298
+ if return_guide:
299
+ return out, coeffs, guide
300
+ return out, coeffs
301
+
demo_runtime/image_io.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Tuple
5
+
6
+ import numpy as np
7
+ import torch
8
+ import torch.nn.functional as F
9
+ from PIL import Image, ImageOps
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class PreparedImage:
14
+ full_pil: Image.Image
15
+ model_pil: Image.Image
16
+ full_tensor: torch.Tensor
17
+ model_tensor: torch.Tensor
18
+
19
+
20
+ def normalize_pil(image: Image.Image) -> Image.Image:
21
+ image = ImageOps.exif_transpose(image)
22
+ return image.convert("RGB")
23
+
24
+
25
+ def resize_max_side(image: Image.Image, max_side: int) -> Image.Image:
26
+ max_side = int(max(256, min(max_side, 2048)))
27
+ width, height = image.size
28
+ longest = max(width, height)
29
+ if longest <= max_side:
30
+ return image.copy()
31
+ scale = max_side / float(longest)
32
+ new_size = (max(1, round(width * scale)), max(1, round(height * scale)))
33
+ return image.resize(new_size, Image.Resampling.LANCZOS)
34
+
35
+
36
+ def pil_to_tensor(image: Image.Image) -> torch.Tensor:
37
+ arr = np.asarray(image, dtype=np.float32) / 255.0
38
+ tensor = torch.from_numpy(arr).permute(2, 0, 1).contiguous()
39
+ return tensor.unsqueeze(0)
40
+
41
+
42
+ def tensor_to_pil(tensor: torch.Tensor) -> Image.Image:
43
+ tensor = tensor.detach().float().cpu().clamp(0, 1)
44
+ if tensor.ndim == 4:
45
+ tensor = tensor[0]
46
+ arr = (tensor.permute(1, 2, 0).numpy() * 255.0).round().astype(np.uint8)
47
+ return Image.fromarray(arr, mode="RGB")
48
+
49
+
50
+ def prepare_image(image: Image.Image, max_side: int, model_size: int = 512) -> PreparedImage:
51
+ full_pil = resize_max_side(normalize_pil(image), max_side)
52
+ model_pil = full_pil.resize((model_size, model_size), Image.Resampling.BICUBIC)
53
+ return PreparedImage(
54
+ full_pil=full_pil,
55
+ model_pil=model_pil,
56
+ full_tensor=pil_to_tensor(full_pil),
57
+ model_tensor=pil_to_tensor(model_pil),
58
+ )
59
+
60
+
61
+ def match_tensor_size(tensor: torch.Tensor, target_hw: Tuple[int, int]) -> torch.Tensor:
62
+ if tuple(tensor.shape[-2:]) == tuple(target_hw):
63
+ return tensor
64
+ return F.interpolate(tensor, size=target_hw, mode="bilinear", align_corners=False)
65
+
66
+
67
+ def blend_strength(input_tensor: torch.Tensor, output_tensor: torch.Tensor, strength: float) -> torch.Tensor:
68
+ strength = float(max(0.0, min(2.0, strength)))
69
+ output_tensor = match_tensor_size(output_tensor, input_tensor.shape[-2:])
70
+ return (input_tensor + strength * (output_tensor - input_tensor)).clamp(0, 1)
71
+
demo_runtime/ip2p_scheduler.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "DDPMScheduler",
3
+ "_diffusers_version": "0.28.0",
4
+ "algorithm_type": "dpmsolver++",
5
+ "beta_end": 0.012,
6
+ "beta_schedule": "scaled_linear",
7
+ "beta_start": 0.00085,
8
+ "clip_sample": false,
9
+ "clip_sample_range": 1.0,
10
+ "dynamic_thresholding_ratio": 0.995,
11
+ "lower_order_final": true,
12
+ "num_train_timesteps": 1000,
13
+ "prediction_type": "epsilon",
14
+ "rescale_betas_zero_snr": false,
15
+ "sample_max_value": 1.0,
16
+ "set_alpha_to_one": false,
17
+ "skip_prk_steps": true,
18
+ "solver_order": 2,
19
+ "solver_type": "midpoint",
20
+ "steps_offset": 1,
21
+ "thresholding": false,
22
+ "timestep_spacing": "linspace",
23
+ "trained_betas": null,
24
+ "variance_type": "fixed_small"
25
+ }
26
+
demo_runtime/manager.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import gc
4
+ import json
5
+ import random
6
+ import threading
7
+ from pathlib import Path
8
+ from typing import Dict, Tuple
9
+
10
+ import numpy as np
11
+ import torch
12
+ from PIL import Image
13
+
14
+ from .backends import FluxBilaBackend, Ip2pBilaBackend, release_cuda
15
+ from .image_io import blend_strength, prepare_image, tensor_to_pil
16
+ from .weights import configure_runtime_cache, require_paths, resolve_model_root
17
+
18
+
19
+ MANIFEST_PATH = Path(__file__).resolve().parents[1] / "model_manifest.json"
20
+
21
+
22
+ def load_manifest() -> Dict:
23
+ with MANIFEST_PATH.open("r", encoding="utf-8") as handle:
24
+ return json.load(handle)
25
+
26
+
27
+ def seed_everything(seed: int) -> None:
28
+ seed = int(seed)
29
+ random.seed(seed)
30
+ np.random.seed(seed % (2**32 - 1))
31
+ torch.manual_seed(seed)
32
+ if torch.cuda.is_available():
33
+ torch.cuda.manual_seed_all(seed)
34
+
35
+
36
+ class DemoManager:
37
+ def __init__(self):
38
+ configure_runtime_cache()
39
+ self.manifest = load_manifest()
40
+ self._backend = None
41
+ self._backend_key = None
42
+ self._lock = threading.Lock()
43
+
44
+ @property
45
+ def model_choices(self):
46
+ return [
47
+ (cfg["label"], key)
48
+ for key, cfg in self.manifest["models"].items()
49
+ ]
50
+
51
+ @property
52
+ def default_model(self):
53
+ return self.manifest["default_model"]
54
+
55
+ def _paths_for_model(self, model_key: str) -> Dict[str, Path]:
56
+ model_cfg = self.manifest["models"][model_key]
57
+ root = resolve_model_root(model_key, model_cfg)
58
+ require_paths(root, model_cfg["weights"].values())
59
+ return {name: root / rel for name, rel in model_cfg["weights"].items()}
60
+
61
+ def _load_backend(self, model_key: str):
62
+ if self._backend_key == model_key and self._backend is not None:
63
+ return self._backend
64
+
65
+ self._backend = None
66
+ self._backend_key = None
67
+ gc.collect()
68
+ release_cuda()
69
+
70
+ model_cfg = self.manifest["models"][model_key]
71
+ paths = self._paths_for_model(model_key)
72
+ if model_cfg["kind"] == "ip2p":
73
+ backend = Ip2pBilaBackend(model_cfg, paths)
74
+ elif model_cfg["kind"] == "flux":
75
+ backend = FluxBilaBackend(model_cfg, paths)
76
+ else:
77
+ raise ValueError(f"Unknown model kind: {model_cfg['kind']}")
78
+
79
+ self._backend = backend
80
+ self._backend_key = model_key
81
+ return backend
82
+
83
+ def generate(
84
+ self,
85
+ image: Image.Image,
86
+ instruction: str,
87
+ model_key: str,
88
+ seed: int,
89
+ max_side: int,
90
+ strength: float,
91
+ ) -> Tuple[Image.Image, Image.Image, Image.Image, str]:
92
+ if image is None:
93
+ raise ValueError("Please upload an image.")
94
+ instruction = (instruction or "").strip()
95
+ if not instruction:
96
+ raise ValueError("Please enter an edit instruction.")
97
+ if model_key not in self.manifest["models"]:
98
+ raise ValueError(f"Unknown model: {model_key}")
99
+
100
+ with self._lock:
101
+ model_cfg = self.manifest["models"][model_key]
102
+ model_size = int(model_cfg["config"].get("model_size", 512))
103
+ prepared = prepare_image(image, max_side=max_side, model_size=model_size)
104
+ backend = self._load_backend(model_key)
105
+ seed_everything(seed)
106
+ result = backend(
107
+ prepared.model_tensor,
108
+ [instruction],
109
+ prepared.full_tensor,
110
+ )
111
+ edited_tensor = blend_strength(prepared.full_tensor, result["bila"], strength)
112
+ edited = tensor_to_pil(edited_tensor)
113
+ diff = tensor_to_pil(result["diff"])
114
+ evidence = model_cfg["evidence"]["scores_avg"]
115
+ status = (
116
+ f"{model_cfg['label']} | score_1={evidence['score_1']:.3f} | "
117
+ f"score_2={evidence['score_2']:.3f} | seed={int(seed)}"
118
+ )
119
+ return edited, diff, prepared.full_pil, status
120
+
demo_runtime/weights.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Dict, Iterable, List
6
+
7
+
8
+ APP_TMP = Path("/tmp/bila-space-demo")
9
+
10
+
11
+ def _writable_data_dir() -> Path:
12
+ data = Path("/data")
13
+ if data.exists() and os.access(data, os.W_OK):
14
+ return data / "bila-space-demo"
15
+ return APP_TMP
16
+
17
+
18
+ def configure_runtime_cache() -> Path:
19
+ base = _writable_data_dir()
20
+ hf_home = Path(os.environ.get("HF_HOME", str(base / "hf-home")))
21
+ torch_home = Path(os.environ.get("TORCH_HOME", str(base / "torch-home")))
22
+ gradio_tmp = Path(os.environ.get("GRADIO_TEMP_DIR", str(base / "gradio-tmp")))
23
+
24
+ os.environ.setdefault("HF_HOME", str(hf_home))
25
+ os.environ.setdefault("TORCH_HOME", str(torch_home))
26
+ os.environ.setdefault("GRADIO_TEMP_DIR", str(gradio_tmp))
27
+
28
+ for path in (hf_home, torch_home, gradio_tmp):
29
+ path.mkdir(parents=True, exist_ok=True)
30
+ return base
31
+
32
+
33
+ def _allow_patterns_for_model(model_cfg: Dict) -> List[str]:
34
+ patterns = []
35
+ for rel_path in model_cfg["weights"].values():
36
+ if rel_path.endswith((".pth", ".bin", ".safetensors", ".json")):
37
+ patterns.append(rel_path)
38
+ else:
39
+ patterns.append(rel_path.rstrip("/") + "/**")
40
+ metric_file = model_cfg.get("evidence", {}).get("metric_file")
41
+ if metric_file:
42
+ patterns.append(metric_file)
43
+ return patterns
44
+
45
+
46
+ def resolve_model_root(model_key: str, model_cfg: Dict) -> Path:
47
+ local_root = os.environ.get("BILA_MODEL_ROOT")
48
+ if local_root:
49
+ return Path(local_root).expanduser().resolve()
50
+
51
+ repo_id = os.environ.get("BILA_WEIGHTS_REPO")
52
+ if not repo_id:
53
+ raise RuntimeError(
54
+ "Set BILA_WEIGHTS_REPO to the Hugging Face model repo containing demo weights, "
55
+ "or set BILA_MODEL_ROOT to a local directory with the same layout."
56
+ )
57
+
58
+ from huggingface_hub import snapshot_download
59
+
60
+ cache_dir = Path(os.environ.get("BILA_MODEL_CACHE", str(_writable_data_dir() / "hf-cache")))
61
+ cache_dir.mkdir(parents=True, exist_ok=True)
62
+ return Path(
63
+ snapshot_download(
64
+ repo_id=repo_id,
65
+ repo_type=os.environ.get("BILA_WEIGHTS_REPO_TYPE", "model"),
66
+ cache_dir=str(cache_dir),
67
+ allow_patterns=_allow_patterns_for_model(model_cfg),
68
+ token=os.environ.get("HF_TOKEN"),
69
+ )
70
+ )
71
+
72
+
73
+ def require_paths(root: Path, rel_paths: Iterable[str]) -> Dict[str, Path]:
74
+ resolved = {}
75
+ missing = []
76
+ for rel_path in rel_paths:
77
+ path = root / rel_path
78
+ resolved[rel_path] = path
79
+ if not path.exists():
80
+ missing.append(str(path))
81
+ if missing:
82
+ raise FileNotFoundError("Missing required weight paths:\n" + "\n".join(missing))
83
+ return resolved
84
+
model_manifest.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": 1,
3
+ "default_model": "ip2p_bila_score1_8_104",
4
+ "models": {
5
+ "ip2p_bila_score1_8_104": {
6
+ "label": "IP2P/BiLA (score_1 8.104)",
7
+ "kind": "ip2p",
8
+ "weights": {
9
+ "base": "ip2p/base",
10
+ "checkpoint": "ip2p/checkpoints/epoch_5_bila_score1_8_104.pth"
11
+ },
12
+ "expected_checkpoint_keys": ["state_dict.unet", "state_dict.bila"],
13
+ "config": {
14
+ "bila_grid_res": 32,
15
+ "bila_grid_bins": 8,
16
+ "model_size": 512,
17
+ "cfg": false,
18
+ "use_t2i": false,
19
+ "not_scheduler_decode": true
20
+ },
21
+ "evidence": {
22
+ "metric_file": "metrics/ip2p_bila_score1_8_104.json",
23
+ "source_run": "train-2025-11-08--13-53-train-mask-vsd-fix-bila-only-from-1107-1218-os-fitLR-1",
24
+ "model_filter": "bila.png",
25
+ "num_pairs": 451,
26
+ "scores_avg": {
27
+ "score_1": 8.10421286031042,
28
+ "score_2": 8.984478935698448
29
+ }
30
+ }
31
+ },
32
+ "flux_bila_score1_8_022": {
33
+ "label": "Flux/BiLA (score_1 8.022)",
34
+ "kind": "flux",
35
+ "weights": {
36
+ "base": "flux/base",
37
+ "task_lora": "flux/task_lora/pytorch_lora_weights.safetensors",
38
+ "checkpoint": "flux/checkpoints/epoch_8_bila_score1_8_022.pth"
39
+ },
40
+ "expected_checkpoint_keys": [
41
+ "state_dict.transformer_lora",
42
+ "state_dict.bila"
43
+ ],
44
+ "config": {
45
+ "bila_grid_res": 16,
46
+ "bila_grid_bins": 8,
47
+ "bila_use_flux_rgb": false,
48
+ "model_size": 512,
49
+ "cfg": false,
50
+ "use_t2i": false,
51
+ "not_scheduler_decode": true,
52
+ "mixed_precision": "bf16",
53
+ "max_sequence_length": 512,
54
+ "distill_strategy": "merge_then_new",
55
+ "distill_lora_rank": 32,
56
+ "distill_lora_alpha": 32,
57
+ "distill_lora_dropout": 0.0,
58
+ "task_lora_rank": 32,
59
+ "task_lora_alpha": 32
60
+ },
61
+ "evidence": {
62
+ "metric_file": "metrics/flux_bila_score1_8_022.json",
63
+ "source_run": "train-2026-03-11--00-29-image-all-1",
64
+ "model_filter": "bila",
65
+ "num_pairs": 89,
66
+ "scores_avg": {
67
+ "score_1": 8.02247191011236,
68
+ "score_2": 9.426966292134832
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+
requirements.txt CHANGED
@@ -1,6 +1,14 @@
1
- accelerate
2
- diffusers
3
- invisible_watermark
4
- torch
5
- transformers
6
- xformers
 
 
 
 
 
 
 
 
 
1
+ --extra-index-url https://download.pytorch.org/whl/cu121
2
+ accelerate>=0.33.0
3
+ gradio==4.44.1
4
+ huggingface_hub>=0.24.0
5
+ numpy>=1.24.0
6
+ peft>=0.12.0
7
+ Pillow>=10.0.0
8
+ safetensors>=0.4.3
9
+ sentencepiece>=0.2.0
10
+ spaces>=0.30.0
11
+ tokenizers>=0.19.0
12
+ torch==2.4.0
13
+ torchvision==0.19.0
14
+ transformers>=4.52.0
vendor/diffusers/__init__.py ADDED
@@ -0,0 +1,1532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __version__ = "0.37.0.dev0"
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .utils import (
6
+ DIFFUSERS_SLOW_IMPORT,
7
+ OptionalDependencyNotAvailable,
8
+ _LazyModule,
9
+ is_accelerate_available,
10
+ is_bitsandbytes_available,
11
+ is_flax_available,
12
+ is_gguf_available,
13
+ is_k_diffusion_available,
14
+ is_librosa_available,
15
+ is_note_seq_available,
16
+ is_nvidia_modelopt_available,
17
+ is_onnx_available,
18
+ is_opencv_available,
19
+ is_optimum_quanto_available,
20
+ is_scipy_available,
21
+ is_sentencepiece_available,
22
+ is_torch_available,
23
+ is_torchao_available,
24
+ is_torchsde_available,
25
+ is_transformers_available,
26
+ is_transformers_version,
27
+ )
28
+
29
+
30
+ # Lazy Import based on
31
+ # https://github.com/huggingface/transformers/blob/main/src/transformers/__init__.py
32
+
33
+ # When adding a new object to this init, please add it to `_import_structure`. The `_import_structure` is a dictionary submodule to list of object names,
34
+ # and is used to defer the actual importing for when the objects are requested.
35
+ # This way `import diffusers` provides the names in the namespace without actually importing anything (and especially none of the backends).
36
+
37
+ _import_structure = {
38
+ "configuration_utils": ["ConfigMixin"],
39
+ "guiders": [],
40
+ "hooks": [],
41
+ "loaders": ["FromOriginalModelMixin"],
42
+ "models": [],
43
+ "modular_pipelines": [],
44
+ "pipelines": [],
45
+ "quantizers.pipe_quant_config": ["PipelineQuantizationConfig"],
46
+ "quantizers.quantization_config": [],
47
+ "schedulers": [],
48
+ "utils": [
49
+ "OptionalDependencyNotAvailable",
50
+ "is_flax_available",
51
+ "is_inflect_available",
52
+ "is_invisible_watermark_available",
53
+ "is_k_diffusion_available",
54
+ "is_k_diffusion_version",
55
+ "is_librosa_available",
56
+ "is_note_seq_available",
57
+ "is_onnx_available",
58
+ "is_scipy_available",
59
+ "is_torch_available",
60
+ "is_torchsde_available",
61
+ "is_transformers_available",
62
+ "is_transformers_version",
63
+ "is_unidecode_available",
64
+ "logging",
65
+ ],
66
+ }
67
+
68
+ try:
69
+ if not is_torch_available() and not is_accelerate_available() and not is_bitsandbytes_available():
70
+ raise OptionalDependencyNotAvailable()
71
+ except OptionalDependencyNotAvailable:
72
+ from .utils import dummy_bitsandbytes_objects
73
+
74
+ _import_structure["utils.dummy_bitsandbytes_objects"] = [
75
+ name for name in dir(dummy_bitsandbytes_objects) if not name.startswith("_")
76
+ ]
77
+ else:
78
+ _import_structure["quantizers.quantization_config"].append("BitsAndBytesConfig")
79
+
80
+ try:
81
+ if not is_torch_available() and not is_accelerate_available() and not is_gguf_available():
82
+ raise OptionalDependencyNotAvailable()
83
+ except OptionalDependencyNotAvailable:
84
+ from .utils import dummy_gguf_objects
85
+
86
+ _import_structure["utils.dummy_gguf_objects"] = [
87
+ name for name in dir(dummy_gguf_objects) if not name.startswith("_")
88
+ ]
89
+ else:
90
+ _import_structure["quantizers.quantization_config"].append("GGUFQuantizationConfig")
91
+
92
+ try:
93
+ if not is_torch_available() and not is_accelerate_available() and not is_torchao_available():
94
+ raise OptionalDependencyNotAvailable()
95
+ except OptionalDependencyNotAvailable:
96
+ from .utils import dummy_torchao_objects
97
+
98
+ _import_structure["utils.dummy_torchao_objects"] = [
99
+ name for name in dir(dummy_torchao_objects) if not name.startswith("_")
100
+ ]
101
+ else:
102
+ _import_structure["quantizers.quantization_config"].append("TorchAoConfig")
103
+
104
+ try:
105
+ if not is_torch_available() and not is_accelerate_available() and not is_optimum_quanto_available():
106
+ raise OptionalDependencyNotAvailable()
107
+ except OptionalDependencyNotAvailable:
108
+ from .utils import dummy_optimum_quanto_objects
109
+
110
+ _import_structure["utils.dummy_optimum_quanto_objects"] = [
111
+ name for name in dir(dummy_optimum_quanto_objects) if not name.startswith("_")
112
+ ]
113
+ else:
114
+ _import_structure["quantizers.quantization_config"].append("QuantoConfig")
115
+
116
+ try:
117
+ if not is_torch_available() and not is_accelerate_available() and not is_nvidia_modelopt_available():
118
+ raise OptionalDependencyNotAvailable()
119
+ except OptionalDependencyNotAvailable:
120
+ from .utils import dummy_nvidia_modelopt_objects
121
+
122
+ _import_structure["utils.dummy_nvidia_modelopt_objects"] = [
123
+ name for name in dir(dummy_nvidia_modelopt_objects) if not name.startswith("_")
124
+ ]
125
+ else:
126
+ _import_structure["quantizers.quantization_config"].append("NVIDIAModelOptConfig")
127
+
128
+ try:
129
+ if not is_onnx_available():
130
+ raise OptionalDependencyNotAvailable()
131
+ except OptionalDependencyNotAvailable:
132
+ from .utils import dummy_onnx_objects # noqa F403
133
+
134
+ _import_structure["utils.dummy_onnx_objects"] = [
135
+ name for name in dir(dummy_onnx_objects) if not name.startswith("_")
136
+ ]
137
+
138
+ else:
139
+ _import_structure["pipelines"].extend(["OnnxRuntimeModel"])
140
+
141
+ try:
142
+ if not is_torch_available():
143
+ raise OptionalDependencyNotAvailable()
144
+ except OptionalDependencyNotAvailable:
145
+ from .utils import dummy_pt_objects # noqa F403
146
+
147
+ _import_structure["utils.dummy_pt_objects"] = [name for name in dir(dummy_pt_objects) if not name.startswith("_")]
148
+
149
+ else:
150
+ _import_structure["guiders"].extend(
151
+ [
152
+ "AdaptiveProjectedGuidance",
153
+ "AdaptiveProjectedMixGuidance",
154
+ "AutoGuidance",
155
+ "BaseGuidance",
156
+ "ClassifierFreeGuidance",
157
+ "ClassifierFreeZeroStarGuidance",
158
+ "FrequencyDecoupledGuidance",
159
+ "PerturbedAttentionGuidance",
160
+ "SkipLayerGuidance",
161
+ "SmoothedEnergyGuidance",
162
+ "TangentialClassifierFreeGuidance",
163
+ ]
164
+ )
165
+ _import_structure["hooks"].extend(
166
+ [
167
+ "FasterCacheConfig",
168
+ "FirstBlockCacheConfig",
169
+ "HookRegistry",
170
+ "LayerSkipConfig",
171
+ "PyramidAttentionBroadcastConfig",
172
+ "SmoothedEnergyGuidanceConfig",
173
+ "TaylorSeerCacheConfig",
174
+ "apply_faster_cache",
175
+ "apply_first_block_cache",
176
+ "apply_layer_skip",
177
+ "apply_pyramid_attention_broadcast",
178
+ "apply_taylorseer_cache",
179
+ ]
180
+ )
181
+ _import_structure["models"].extend(
182
+ [
183
+ "AllegroTransformer3DModel",
184
+ "AsymmetricAutoencoderKL",
185
+ "AttentionBackendName",
186
+ "AuraFlowTransformer2DModel",
187
+ "AutoencoderDC",
188
+ "AutoencoderKL",
189
+ "AutoencoderKLAllegro",
190
+ "AutoencoderKLCogVideoX",
191
+ "AutoencoderKLCosmos",
192
+ "AutoencoderKLFlux2",
193
+ "AutoencoderKLHunyuanImage",
194
+ "AutoencoderKLHunyuanImageRefiner",
195
+ "AutoencoderKLHunyuanVideo",
196
+ "AutoencoderKLHunyuanVideo15",
197
+ "AutoencoderKLLTX2Audio",
198
+ "AutoencoderKLLTX2Video",
199
+ "AutoencoderKLLTXVideo",
200
+ "AutoencoderKLMagvit",
201
+ "AutoencoderKLMochi",
202
+ "AutoencoderKLQwenImage",
203
+ "AutoencoderKLTemporalDecoder",
204
+ "AutoencoderKLWan",
205
+ "AutoencoderOobleck",
206
+ "AutoencoderTiny",
207
+ "AutoModel",
208
+ "BriaFiboTransformer2DModel",
209
+ "BriaTransformer2DModel",
210
+ "CacheMixin",
211
+ "ChromaTransformer2DModel",
212
+ "ChronoEditTransformer3DModel",
213
+ "CogVideoXTransformer3DModel",
214
+ "CogView3PlusTransformer2DModel",
215
+ "CogView4Transformer2DModel",
216
+ "ConsisIDTransformer3DModel",
217
+ "ConsistencyDecoderVAE",
218
+ "ContextParallelConfig",
219
+ "ControlNetModel",
220
+ "ControlNetUnionModel",
221
+ "ControlNetXSAdapter",
222
+ "CosmosTransformer3DModel",
223
+ "DiTTransformer2DModel",
224
+ "EasyAnimateTransformer3DModel",
225
+ "Flux2Transformer2DModel",
226
+ "FluxControlNetModel",
227
+ "FluxMultiControlNetModel",
228
+ "FluxTransformer2DModel",
229
+ "GlmImageTransformer2DModel",
230
+ "HiDreamImageTransformer2DModel",
231
+ "HunyuanDiT2DControlNetModel",
232
+ "HunyuanDiT2DModel",
233
+ "HunyuanDiT2DMultiControlNetModel",
234
+ "HunyuanImageTransformer2DModel",
235
+ "HunyuanVideo15Transformer3DModel",
236
+ "HunyuanVideoFramepackTransformer3DModel",
237
+ "HunyuanVideoTransformer3DModel",
238
+ "I2VGenXLUNet",
239
+ "Kandinsky3UNet",
240
+ "Kandinsky5Transformer3DModel",
241
+ "LatteTransformer3DModel",
242
+ "LongCatImageTransformer2DModel",
243
+ "LTX2VideoTransformer3DModel",
244
+ "LTXVideoTransformer3DModel",
245
+ "Lumina2Transformer2DModel",
246
+ "LuminaNextDiT2DModel",
247
+ "MochiTransformer3DModel",
248
+ "ModelMixin",
249
+ "MotionAdapter",
250
+ "MultiAdapter",
251
+ "MultiControlNetModel",
252
+ "OmniGenTransformer2DModel",
253
+ "OvisImageTransformer2DModel",
254
+ "ParallelConfig",
255
+ "PixArtTransformer2DModel",
256
+ "PriorTransformer",
257
+ "PRXTransformer2DModel",
258
+ "QwenImageControlNetModel",
259
+ "QwenImageMultiControlNetModel",
260
+ "QwenImageTransformer2DModel",
261
+ "SanaControlNetModel",
262
+ "SanaTransformer2DModel",
263
+ "SanaVideoTransformer3DModel",
264
+ "SD3ControlNetModel",
265
+ "SD3MultiControlNetModel",
266
+ "SD3Transformer2DModel",
267
+ "SkyReelsV2Transformer3DModel",
268
+ "SparseControlNetModel",
269
+ "StableAudioDiTModel",
270
+ "StableCascadeUNet",
271
+ "T2IAdapter",
272
+ "T5FilmDecoder",
273
+ "Transformer2DModel",
274
+ "TransformerTemporalModel",
275
+ "UNet1DModel",
276
+ "UNet2DConditionModel",
277
+ "UNet2DModel",
278
+ "UNet3DConditionModel",
279
+ "UNetControlNetXSModel",
280
+ "UNetMotionModel",
281
+ "UNetSpatioTemporalConditionModel",
282
+ "UVit2DModel",
283
+ "VQModel",
284
+ "WanAnimateTransformer3DModel",
285
+ "WanTransformer3DModel",
286
+ "WanVACETransformer3DModel",
287
+ "ZImageControlNetModel",
288
+ "ZImageTransformer2DModel",
289
+ "attention_backend",
290
+ ]
291
+ )
292
+ _import_structure["modular_pipelines"].extend(
293
+ [
294
+ "ComponentsManager",
295
+ "ComponentSpec",
296
+ "ModularPipeline",
297
+ "ModularPipelineBlocks",
298
+ ]
299
+ )
300
+ _import_structure["optimization"] = [
301
+ "get_constant_schedule",
302
+ "get_constant_schedule_with_warmup",
303
+ "get_cosine_schedule_with_warmup",
304
+ "get_cosine_with_hard_restarts_schedule_with_warmup",
305
+ "get_linear_schedule_with_warmup",
306
+ "get_polynomial_decay_schedule_with_warmup",
307
+ "get_scheduler",
308
+ ]
309
+ _import_structure["pipelines"].extend(
310
+ [
311
+ "AudioPipelineOutput",
312
+ "AutoPipelineForImage2Image",
313
+ "AutoPipelineForInpainting",
314
+ "AutoPipelineForText2Image",
315
+ "ConsistencyModelPipeline",
316
+ "DanceDiffusionPipeline",
317
+ "DDIMPipeline",
318
+ "DDPMPipeline",
319
+ "DiffusionPipeline",
320
+ "DiTPipeline",
321
+ "ImagePipelineOutput",
322
+ "KarrasVePipeline",
323
+ "LDMPipeline",
324
+ "LDMSuperResolutionPipeline",
325
+ "PNDMPipeline",
326
+ "RePaintPipeline",
327
+ "ScoreSdeVePipeline",
328
+ "StableDiffusionMixin",
329
+ ]
330
+ )
331
+ _import_structure["quantizers"] = ["DiffusersQuantizer"]
332
+ _import_structure["schedulers"].extend(
333
+ [
334
+ "AmusedScheduler",
335
+ "CMStochasticIterativeScheduler",
336
+ "CogVideoXDDIMScheduler",
337
+ "CogVideoXDPMScheduler",
338
+ "DDIMInverseScheduler",
339
+ "DDIMParallelScheduler",
340
+ "DDIMScheduler",
341
+ "DDPMParallelScheduler",
342
+ "DDPMScheduler",
343
+ "DDPMWuerstchenScheduler",
344
+ "DEISMultistepScheduler",
345
+ "DPMSolverMultistepInverseScheduler",
346
+ "DPMSolverMultistepScheduler",
347
+ "DPMSolverSinglestepScheduler",
348
+ "EDMDPMSolverMultistepScheduler",
349
+ "EDMEulerScheduler",
350
+ "EulerAncestralDiscreteScheduler",
351
+ "EulerDiscreteScheduler",
352
+ "FlowMatchEulerDiscreteScheduler",
353
+ "FlowMatchHeunDiscreteScheduler",
354
+ "FlowMatchLCMScheduler",
355
+ "HeunDiscreteScheduler",
356
+ "IPNDMScheduler",
357
+ "KarrasVeScheduler",
358
+ "KDPM2AncestralDiscreteScheduler",
359
+ "KDPM2DiscreteScheduler",
360
+ "LCMScheduler",
361
+ "LTXEulerAncestralRFScheduler",
362
+ "PNDMScheduler",
363
+ "RePaintScheduler",
364
+ "SASolverScheduler",
365
+ "SchedulerMixin",
366
+ "SCMScheduler",
367
+ "ScoreSdeVeScheduler",
368
+ "TCDScheduler",
369
+ "UnCLIPScheduler",
370
+ "UniPCMultistepScheduler",
371
+ "VQDiffusionScheduler",
372
+ ]
373
+ )
374
+ _import_structure["training_utils"] = ["EMAModel"]
375
+
376
+ try:
377
+ if not (is_torch_available() and is_scipy_available()):
378
+ raise OptionalDependencyNotAvailable()
379
+ except OptionalDependencyNotAvailable:
380
+ from .utils import dummy_torch_and_scipy_objects # noqa F403
381
+
382
+ _import_structure["utils.dummy_torch_and_scipy_objects"] = [
383
+ name for name in dir(dummy_torch_and_scipy_objects) if not name.startswith("_")
384
+ ]
385
+
386
+ else:
387
+ _import_structure["schedulers"].extend(["LMSDiscreteScheduler"])
388
+
389
+ try:
390
+ if not (is_torch_available() and is_torchsde_available()):
391
+ raise OptionalDependencyNotAvailable()
392
+ except OptionalDependencyNotAvailable:
393
+ from .utils import dummy_torch_and_torchsde_objects # noqa F403
394
+
395
+ _import_structure["utils.dummy_torch_and_torchsde_objects"] = [
396
+ name for name in dir(dummy_torch_and_torchsde_objects) if not name.startswith("_")
397
+ ]
398
+
399
+ else:
400
+ _import_structure["schedulers"].extend(["CosineDPMSolverMultistepScheduler", "DPMSolverSDEScheduler"])
401
+
402
+ try:
403
+ if not (is_torch_available() and is_transformers_available()):
404
+ raise OptionalDependencyNotAvailable()
405
+ except OptionalDependencyNotAvailable:
406
+ from .utils import dummy_torch_and_transformers_objects # noqa F403
407
+
408
+ _import_structure["utils.dummy_torch_and_transformers_objects"] = [
409
+ name for name in dir(dummy_torch_and_transformers_objects) if not name.startswith("_")
410
+ ]
411
+
412
+ else:
413
+ _import_structure["modular_pipelines"].extend(
414
+ [
415
+ "Flux2AutoBlocks",
416
+ "Flux2ModularPipeline",
417
+ "FluxAutoBlocks",
418
+ "FluxKontextAutoBlocks",
419
+ "FluxKontextModularPipeline",
420
+ "FluxModularPipeline",
421
+ "QwenImageAutoBlocks",
422
+ "QwenImageEditAutoBlocks",
423
+ "QwenImageEditModularPipeline",
424
+ "QwenImageEditPlusAutoBlocks",
425
+ "QwenImageEditPlusModularPipeline",
426
+ "QwenImageLayeredAutoBlocks",
427
+ "QwenImageLayeredModularPipeline",
428
+ "QwenImageModularPipeline",
429
+ "StableDiffusionXLAutoBlocks",
430
+ "StableDiffusionXLModularPipeline",
431
+ "Wan22AutoBlocks",
432
+ "WanAutoBlocks",
433
+ "WanModularPipeline",
434
+ "ZImageAutoBlocks",
435
+ "ZImageModularPipeline",
436
+ ]
437
+ )
438
+ _import_structure["pipelines"].extend(
439
+ [
440
+ "AllegroPipeline",
441
+ "AltDiffusionImg2ImgPipeline",
442
+ "AltDiffusionPipeline",
443
+ "AmusedImg2ImgPipeline",
444
+ "AmusedInpaintPipeline",
445
+ "AmusedPipeline",
446
+ "AnimateDiffControlNetPipeline",
447
+ "AnimateDiffPAGPipeline",
448
+ "AnimateDiffPipeline",
449
+ "AnimateDiffSDXLPipeline",
450
+ "AnimateDiffSparseControlNetPipeline",
451
+ "AnimateDiffVideoToVideoControlNetPipeline",
452
+ "AnimateDiffVideoToVideoPipeline",
453
+ "AudioLDM2Pipeline",
454
+ "AudioLDM2ProjectionModel",
455
+ "AudioLDM2UNet2DConditionModel",
456
+ "AudioLDMPipeline",
457
+ "AuraFlowPipeline",
458
+ "BlipDiffusionControlNetPipeline",
459
+ "BlipDiffusionPipeline",
460
+ "BriaFiboPipeline",
461
+ "BriaPipeline",
462
+ "ChromaImg2ImgPipeline",
463
+ "ChromaInpaintPipeline",
464
+ "ChromaPipeline",
465
+ "ChronoEditPipeline",
466
+ "CLIPImageProjection",
467
+ "CogVideoXFunControlPipeline",
468
+ "CogVideoXImageToVideoPipeline",
469
+ "CogVideoXPipeline",
470
+ "CogVideoXVideoToVideoPipeline",
471
+ "CogView3PlusPipeline",
472
+ "CogView4ControlPipeline",
473
+ "CogView4Pipeline",
474
+ "ConsisIDPipeline",
475
+ "Cosmos2_5_PredictBasePipeline",
476
+ "Cosmos2TextToImagePipeline",
477
+ "Cosmos2VideoToWorldPipeline",
478
+ "CosmosTextToWorldPipeline",
479
+ "CosmosVideoToWorldPipeline",
480
+ "CycleDiffusionPipeline",
481
+ "EasyAnimateControlPipeline",
482
+ "EasyAnimateInpaintPipeline",
483
+ "EasyAnimatePipeline",
484
+ "Flux2KleinPipeline",
485
+ "Flux2Pipeline",
486
+ "FluxControlImg2ImgPipeline",
487
+ "FluxControlInpaintPipeline",
488
+ "FluxControlNetImg2ImgPipeline",
489
+ "FluxControlNetInpaintPipeline",
490
+ "FluxControlNetPipeline",
491
+ "FluxControlPipeline",
492
+ "FluxFillPipeline",
493
+ "FluxImg2ImgPipeline",
494
+ "FluxInpaintPipeline",
495
+ "FluxKontextInpaintPipeline",
496
+ "FluxKontextPipeline",
497
+ "FluxPipeline",
498
+ "FluxPriorReduxPipeline",
499
+ "GlmImagePipeline",
500
+ "HiDreamImagePipeline",
501
+ "HunyuanDiTControlNetPipeline",
502
+ "HunyuanDiTPAGPipeline",
503
+ "HunyuanDiTPipeline",
504
+ "HunyuanImagePipeline",
505
+ "HunyuanImageRefinerPipeline",
506
+ "HunyuanSkyreelsImageToVideoPipeline",
507
+ "HunyuanVideo15ImageToVideoPipeline",
508
+ "HunyuanVideo15Pipeline",
509
+ "HunyuanVideoFramepackPipeline",
510
+ "HunyuanVideoImageToVideoPipeline",
511
+ "HunyuanVideoPipeline",
512
+ "I2VGenXLPipeline",
513
+ "IFImg2ImgPipeline",
514
+ "IFImg2ImgSuperResolutionPipeline",
515
+ "IFInpaintingPipeline",
516
+ "IFInpaintingSuperResolutionPipeline",
517
+ "IFPipeline",
518
+ "IFSuperResolutionPipeline",
519
+ "ImageTextPipelineOutput",
520
+ "Kandinsky3Img2ImgPipeline",
521
+ "Kandinsky3Pipeline",
522
+ "Kandinsky5I2IPipeline",
523
+ "Kandinsky5I2VPipeline",
524
+ "Kandinsky5T2IPipeline",
525
+ "Kandinsky5T2VPipeline",
526
+ "KandinskyCombinedPipeline",
527
+ "KandinskyImg2ImgCombinedPipeline",
528
+ "KandinskyImg2ImgPipeline",
529
+ "KandinskyInpaintCombinedPipeline",
530
+ "KandinskyInpaintPipeline",
531
+ "KandinskyPipeline",
532
+ "KandinskyPriorPipeline",
533
+ "KandinskyV22CombinedPipeline",
534
+ "KandinskyV22ControlnetImg2ImgPipeline",
535
+ "KandinskyV22ControlnetPipeline",
536
+ "KandinskyV22Img2ImgCombinedPipeline",
537
+ "KandinskyV22Img2ImgPipeline",
538
+ "KandinskyV22InpaintCombinedPipeline",
539
+ "KandinskyV22InpaintPipeline",
540
+ "KandinskyV22Pipeline",
541
+ "KandinskyV22PriorEmb2EmbPipeline",
542
+ "KandinskyV22PriorPipeline",
543
+ "LatentConsistencyModelImg2ImgPipeline",
544
+ "LatentConsistencyModelPipeline",
545
+ "LattePipeline",
546
+ "LDMTextToImagePipeline",
547
+ "LEditsPPPipelineStableDiffusion",
548
+ "LEditsPPPipelineStableDiffusionXL",
549
+ "LongCatImageEditPipeline",
550
+ "LongCatImagePipeline",
551
+ "LTX2ImageToVideoPipeline",
552
+ "LTX2LatentUpsamplePipeline",
553
+ "LTX2Pipeline",
554
+ "LTXConditionPipeline",
555
+ "LTXI2VLongMultiPromptPipeline",
556
+ "LTXImageToVideoPipeline",
557
+ "LTXLatentUpsamplePipeline",
558
+ "LTXPipeline",
559
+ "LucyEditPipeline",
560
+ "Lumina2Pipeline",
561
+ "Lumina2Text2ImgPipeline",
562
+ "LuminaPipeline",
563
+ "LuminaText2ImgPipeline",
564
+ "MarigoldDepthPipeline",
565
+ "MarigoldIntrinsicsPipeline",
566
+ "MarigoldNormalsPipeline",
567
+ "MochiPipeline",
568
+ "MusicLDMPipeline",
569
+ "OmniGenPipeline",
570
+ "OvisImagePipeline",
571
+ "PaintByExamplePipeline",
572
+ "PIAPipeline",
573
+ "PixArtAlphaPipeline",
574
+ "PixArtSigmaPAGPipeline",
575
+ "PixArtSigmaPipeline",
576
+ "PRXPipeline",
577
+ "QwenImageControlNetInpaintPipeline",
578
+ "QwenImageControlNetPipeline",
579
+ "QwenImageEditInpaintPipeline",
580
+ "QwenImageEditPipeline",
581
+ "QwenImageEditPlusPipeline",
582
+ "QwenImageImg2ImgPipeline",
583
+ "QwenImageInpaintPipeline",
584
+ "QwenImageLayeredPipeline",
585
+ "QwenImagePipeline",
586
+ "ReduxImageEncoder",
587
+ "SanaControlNetPipeline",
588
+ "SanaImageToVideoPipeline",
589
+ "SanaPAGPipeline",
590
+ "SanaPipeline",
591
+ "SanaSprintImg2ImgPipeline",
592
+ "SanaSprintPipeline",
593
+ "SanaVideoPipeline",
594
+ "SanaVideoPipeline",
595
+ "SemanticStableDiffusionPipeline",
596
+ "ShapEImg2ImgPipeline",
597
+ "ShapEPipeline",
598
+ "SkyReelsV2DiffusionForcingImageToVideoPipeline",
599
+ "SkyReelsV2DiffusionForcingPipeline",
600
+ "SkyReelsV2DiffusionForcingVideoToVideoPipeline",
601
+ "SkyReelsV2ImageToVideoPipeline",
602
+ "SkyReelsV2Pipeline",
603
+ "StableAudioPipeline",
604
+ "StableAudioProjectionModel",
605
+ "StableCascadeCombinedPipeline",
606
+ "StableCascadeDecoderPipeline",
607
+ "StableCascadePriorPipeline",
608
+ "StableDiffusion3ControlNetInpaintingPipeline",
609
+ "StableDiffusion3ControlNetPipeline",
610
+ "StableDiffusion3Img2ImgPipeline",
611
+ "StableDiffusion3InpaintPipeline",
612
+ "StableDiffusion3PAGImg2ImgPipeline",
613
+ "StableDiffusion3PAGImg2ImgPipeline",
614
+ "StableDiffusion3PAGPipeline",
615
+ "StableDiffusion3Pipeline",
616
+ "StableDiffusionAdapterPipeline",
617
+ "StableDiffusionAttendAndExcitePipeline",
618
+ "StableDiffusionControlNetImg2ImgPipeline",
619
+ "StableDiffusionControlNetInpaintPipeline",
620
+ "StableDiffusionControlNetPAGInpaintPipeline",
621
+ "StableDiffusionControlNetPAGPipeline",
622
+ "StableDiffusionControlNetPipeline",
623
+ "StableDiffusionControlNetXSPipeline",
624
+ "StableDiffusionDepth2ImgPipeline",
625
+ "StableDiffusionDiffEditPipeline",
626
+ "StableDiffusionGLIGENPipeline",
627
+ "StableDiffusionGLIGENTextImagePipeline",
628
+ "StableDiffusionImageVariationPipeline",
629
+ "StableDiffusionImg2ImgPipeline",
630
+ "StableDiffusionInpaintPipeline",
631
+ "StableDiffusionInpaintPipelineLegacy",
632
+ "StableDiffusionInstructPix2PixPipeline",
633
+ "StableDiffusionLatentUpscalePipeline",
634
+ "StableDiffusionLDM3DPipeline",
635
+ "StableDiffusionModelEditingPipeline",
636
+ "StableDiffusionPAGImg2ImgPipeline",
637
+ "StableDiffusionPAGInpaintPipeline",
638
+ "StableDiffusionPAGPipeline",
639
+ "StableDiffusionPanoramaPipeline",
640
+ "StableDiffusionParadigmsPipeline",
641
+ "StableDiffusionPipeline",
642
+ "StableDiffusionPipelineSafe",
643
+ "StableDiffusionPix2PixZeroPipeline",
644
+ "StableDiffusionSAGPipeline",
645
+ "StableDiffusionUpscalePipeline",
646
+ "StableDiffusionXLAdapterPipeline",
647
+ "StableDiffusionXLControlNetImg2ImgPipeline",
648
+ "StableDiffusionXLControlNetInpaintPipeline",
649
+ "StableDiffusionXLControlNetPAGImg2ImgPipeline",
650
+ "StableDiffusionXLControlNetPAGPipeline",
651
+ "StableDiffusionXLControlNetPipeline",
652
+ "StableDiffusionXLControlNetUnionImg2ImgPipeline",
653
+ "StableDiffusionXLControlNetUnionInpaintPipeline",
654
+ "StableDiffusionXLControlNetUnionPipeline",
655
+ "StableDiffusionXLControlNetXSPipeline",
656
+ "StableDiffusionXLImg2ImgPipeline",
657
+ "StableDiffusionXLInpaintPipeline",
658
+ "StableDiffusionXLInstructPix2PixPipeline",
659
+ "StableDiffusionXLPAGImg2ImgPipeline",
660
+ "StableDiffusionXLPAGInpaintPipeline",
661
+ "StableDiffusionXLPAGPipeline",
662
+ "StableDiffusionXLPipeline",
663
+ "StableUnCLIPImg2ImgPipeline",
664
+ "StableUnCLIPPipeline",
665
+ "StableVideoDiffusionPipeline",
666
+ "TextToVideoSDPipeline",
667
+ "TextToVideoZeroPipeline",
668
+ "TextToVideoZeroSDXLPipeline",
669
+ "UnCLIPImageVariationPipeline",
670
+ "UnCLIPPipeline",
671
+ "UniDiffuserModel",
672
+ "UniDiffuserPipeline",
673
+ "UniDiffuserTextDecoder",
674
+ "VersatileDiffusionDualGuidedPipeline",
675
+ "VersatileDiffusionImageVariationPipeline",
676
+ "VersatileDiffusionPipeline",
677
+ "VersatileDiffusionTextToImagePipeline",
678
+ "VideoToVideoSDPipeline",
679
+ "VisualClozeGenerationPipeline",
680
+ "VisualClozePipeline",
681
+ "VQDiffusionPipeline",
682
+ "WanAnimatePipeline",
683
+ "WanImageToVideoPipeline",
684
+ "WanPipeline",
685
+ "WanVACEPipeline",
686
+ "WanVideoToVideoPipeline",
687
+ "WuerstchenCombinedPipeline",
688
+ "WuerstchenDecoderPipeline",
689
+ "WuerstchenPriorPipeline",
690
+ "ZImageControlNetInpaintPipeline",
691
+ "ZImageControlNetPipeline",
692
+ "ZImageImg2ImgPipeline",
693
+ "ZImageOmniPipeline",
694
+ "ZImagePipeline",
695
+ ]
696
+ )
697
+
698
+
699
+ try:
700
+ if not (is_torch_available() and is_transformers_available() and is_opencv_available()):
701
+ raise OptionalDependencyNotAvailable()
702
+ except OptionalDependencyNotAvailable:
703
+ from .utils import dummy_torch_and_transformers_and_opencv_objects # noqa F403
704
+
705
+ _import_structure["utils.dummy_torch_and_transformers_and_opencv_objects"] = [
706
+ name for name in dir(dummy_torch_and_transformers_and_opencv_objects) if not name.startswith("_")
707
+ ]
708
+
709
+ else:
710
+ _import_structure["pipelines"].extend(["ConsisIDPipeline"])
711
+
712
+ try:
713
+ if not (is_torch_available() and is_transformers_available() and is_k_diffusion_available()):
714
+ raise OptionalDependencyNotAvailable()
715
+ except OptionalDependencyNotAvailable:
716
+ from .utils import dummy_torch_and_transformers_and_k_diffusion_objects # noqa F403
717
+
718
+ _import_structure["utils.dummy_torch_and_transformers_and_k_diffusion_objects"] = [
719
+ name for name in dir(dummy_torch_and_transformers_and_k_diffusion_objects) if not name.startswith("_")
720
+ ]
721
+
722
+ else:
723
+ _import_structure["pipelines"].extend(["StableDiffusionKDiffusionPipeline", "StableDiffusionXLKDiffusionPipeline"])
724
+
725
+ try:
726
+ if not (is_torch_available() and is_transformers_available() and is_sentencepiece_available()):
727
+ raise OptionalDependencyNotAvailable()
728
+ except OptionalDependencyNotAvailable:
729
+ from .utils import dummy_torch_and_transformers_and_sentencepiece_objects # noqa F403
730
+
731
+ _import_structure["utils.dummy_torch_and_transformers_and_sentencepiece_objects"] = [
732
+ name for name in dir(dummy_torch_and_transformers_and_sentencepiece_objects) if not name.startswith("_")
733
+ ]
734
+
735
+ else:
736
+ _import_structure["pipelines"].extend(["KolorsImg2ImgPipeline", "KolorsPAGPipeline", "KolorsPipeline"])
737
+
738
+ try:
739
+ if not (is_torch_available() and is_transformers_available() and is_onnx_available()):
740
+ raise OptionalDependencyNotAvailable()
741
+ except OptionalDependencyNotAvailable:
742
+ from .utils import dummy_torch_and_transformers_and_onnx_objects # noqa F403
743
+
744
+ _import_structure["utils.dummy_torch_and_transformers_and_onnx_objects"] = [
745
+ name for name in dir(dummy_torch_and_transformers_and_onnx_objects) if not name.startswith("_")
746
+ ]
747
+
748
+ else:
749
+ _import_structure["pipelines"].extend(
750
+ [
751
+ "OnnxStableDiffusionImg2ImgPipeline",
752
+ "OnnxStableDiffusionInpaintPipeline",
753
+ "OnnxStableDiffusionInpaintPipelineLegacy",
754
+ "OnnxStableDiffusionPipeline",
755
+ "OnnxStableDiffusionUpscalePipeline",
756
+ "StableDiffusionOnnxPipeline",
757
+ ]
758
+ )
759
+
760
+ try:
761
+ if not (is_torch_available() and is_librosa_available()):
762
+ raise OptionalDependencyNotAvailable()
763
+ except OptionalDependencyNotAvailable:
764
+ from .utils import dummy_torch_and_librosa_objects # noqa F403
765
+
766
+ _import_structure["utils.dummy_torch_and_librosa_objects"] = [
767
+ name for name in dir(dummy_torch_and_librosa_objects) if not name.startswith("_")
768
+ ]
769
+
770
+ else:
771
+ _import_structure["pipelines"].extend(["AudioDiffusionPipeline", "Mel"])
772
+
773
+ try:
774
+ if not (is_transformers_available() and is_torch_available() and is_note_seq_available()):
775
+ raise OptionalDependencyNotAvailable()
776
+ except OptionalDependencyNotAvailable:
777
+ from .utils import dummy_transformers_and_torch_and_note_seq_objects # noqa F403
778
+
779
+ _import_structure["utils.dummy_transformers_and_torch_and_note_seq_objects"] = [
780
+ name for name in dir(dummy_transformers_and_torch_and_note_seq_objects) if not name.startswith("_")
781
+ ]
782
+
783
+
784
+ else:
785
+ _import_structure["pipelines"].extend(["SpectrogramDiffusionPipeline"])
786
+
787
+ try:
788
+ if not is_flax_available():
789
+ raise OptionalDependencyNotAvailable()
790
+ except OptionalDependencyNotAvailable:
791
+ from .utils import dummy_flax_objects # noqa F403
792
+
793
+ _import_structure["utils.dummy_flax_objects"] = [
794
+ name for name in dir(dummy_flax_objects) if not name.startswith("_")
795
+ ]
796
+
797
+
798
+ else:
799
+ _import_structure["models.controlnets.controlnet_flax"] = ["FlaxControlNetModel"]
800
+ _import_structure["models.modeling_flax_utils"] = ["FlaxModelMixin"]
801
+ _import_structure["models.unets.unet_2d_condition_flax"] = ["FlaxUNet2DConditionModel"]
802
+ _import_structure["models.vae_flax"] = ["FlaxAutoencoderKL"]
803
+ _import_structure["pipelines"].extend(["FlaxDiffusionPipeline"])
804
+ _import_structure["schedulers"].extend(
805
+ [
806
+ "FlaxDDIMScheduler",
807
+ "FlaxDDPMScheduler",
808
+ "FlaxDPMSolverMultistepScheduler",
809
+ "FlaxEulerDiscreteScheduler",
810
+ "FlaxKarrasVeScheduler",
811
+ "FlaxLMSDiscreteScheduler",
812
+ "FlaxPNDMScheduler",
813
+ "FlaxSchedulerMixin",
814
+ "FlaxScoreSdeVeScheduler",
815
+ ]
816
+ )
817
+
818
+
819
+ try:
820
+ if not (is_flax_available() and is_transformers_available()):
821
+ raise OptionalDependencyNotAvailable()
822
+ except OptionalDependencyNotAvailable:
823
+ from .utils import dummy_flax_and_transformers_objects # noqa F403
824
+
825
+ _import_structure["utils.dummy_flax_and_transformers_objects"] = [
826
+ name for name in dir(dummy_flax_and_transformers_objects) if not name.startswith("_")
827
+ ]
828
+
829
+
830
+ else:
831
+ _import_structure["pipelines"].extend(
832
+ [
833
+ "FlaxStableDiffusionControlNetPipeline",
834
+ "FlaxStableDiffusionImg2ImgPipeline",
835
+ "FlaxStableDiffusionInpaintPipeline",
836
+ "FlaxStableDiffusionPipeline",
837
+ "FlaxStableDiffusionXLPipeline",
838
+ ]
839
+ )
840
+
841
+ try:
842
+ if not (is_note_seq_available()):
843
+ raise OptionalDependencyNotAvailable()
844
+ except OptionalDependencyNotAvailable:
845
+ from .utils import dummy_note_seq_objects # noqa F403
846
+
847
+ _import_structure["utils.dummy_note_seq_objects"] = [
848
+ name for name in dir(dummy_note_seq_objects) if not name.startswith("_")
849
+ ]
850
+
851
+
852
+ else:
853
+ _import_structure["pipelines"].extend(["MidiProcessor"])
854
+
855
+ if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
856
+ from .configuration_utils import ConfigMixin
857
+ from .quantizers import PipelineQuantizationConfig
858
+
859
+ try:
860
+ if not is_bitsandbytes_available():
861
+ raise OptionalDependencyNotAvailable()
862
+ except OptionalDependencyNotAvailable:
863
+ from .utils.dummy_bitsandbytes_objects import *
864
+ else:
865
+ from .quantizers.quantization_config import BitsAndBytesConfig
866
+
867
+ try:
868
+ if not is_gguf_available():
869
+ raise OptionalDependencyNotAvailable()
870
+ except OptionalDependencyNotAvailable:
871
+ from .utils.dummy_gguf_objects import *
872
+ else:
873
+ from .quantizers.quantization_config import GGUFQuantizationConfig
874
+
875
+ try:
876
+ if not is_torchao_available():
877
+ raise OptionalDependencyNotAvailable()
878
+ except OptionalDependencyNotAvailable:
879
+ from .utils.dummy_torchao_objects import *
880
+ else:
881
+ from .quantizers.quantization_config import TorchAoConfig
882
+
883
+ try:
884
+ if not is_optimum_quanto_available():
885
+ raise OptionalDependencyNotAvailable()
886
+ except OptionalDependencyNotAvailable:
887
+ from .utils.dummy_optimum_quanto_objects import *
888
+ else:
889
+ from .quantizers.quantization_config import QuantoConfig
890
+
891
+ try:
892
+ if not is_nvidia_modelopt_available():
893
+ raise OptionalDependencyNotAvailable()
894
+ except OptionalDependencyNotAvailable:
895
+ from .utils.dummy_nvidia_modelopt_objects import *
896
+ else:
897
+ from .quantizers.quantization_config import NVIDIAModelOptConfig
898
+
899
+ try:
900
+ if not is_onnx_available():
901
+ raise OptionalDependencyNotAvailable()
902
+ except OptionalDependencyNotAvailable:
903
+ from .utils.dummy_onnx_objects import * # noqa F403
904
+ else:
905
+ from .pipelines import OnnxRuntimeModel
906
+
907
+ try:
908
+ if not is_torch_available():
909
+ raise OptionalDependencyNotAvailable()
910
+ except OptionalDependencyNotAvailable:
911
+ from .utils.dummy_pt_objects import * # noqa F403
912
+ else:
913
+ from .guiders import (
914
+ AdaptiveProjectedGuidance,
915
+ AdaptiveProjectedMixGuidance,
916
+ AutoGuidance,
917
+ BaseGuidance,
918
+ ClassifierFreeGuidance,
919
+ ClassifierFreeZeroStarGuidance,
920
+ FrequencyDecoupledGuidance,
921
+ PerturbedAttentionGuidance,
922
+ SkipLayerGuidance,
923
+ SmoothedEnergyGuidance,
924
+ TangentialClassifierFreeGuidance,
925
+ )
926
+ from .hooks import (
927
+ FasterCacheConfig,
928
+ FirstBlockCacheConfig,
929
+ HookRegistry,
930
+ LayerSkipConfig,
931
+ PyramidAttentionBroadcastConfig,
932
+ SmoothedEnergyGuidanceConfig,
933
+ TaylorSeerCacheConfig,
934
+ apply_faster_cache,
935
+ apply_first_block_cache,
936
+ apply_layer_skip,
937
+ apply_pyramid_attention_broadcast,
938
+ apply_taylorseer_cache,
939
+ )
940
+ from .models import (
941
+ AllegroTransformer3DModel,
942
+ AsymmetricAutoencoderKL,
943
+ AttentionBackendName,
944
+ AuraFlowTransformer2DModel,
945
+ AutoencoderDC,
946
+ AutoencoderKL,
947
+ AutoencoderKLAllegro,
948
+ AutoencoderKLCogVideoX,
949
+ AutoencoderKLCosmos,
950
+ AutoencoderKLFlux2,
951
+ AutoencoderKLHunyuanImage,
952
+ AutoencoderKLHunyuanImageRefiner,
953
+ AutoencoderKLHunyuanVideo,
954
+ AutoencoderKLHunyuanVideo15,
955
+ AutoencoderKLLTX2Audio,
956
+ AutoencoderKLLTX2Video,
957
+ AutoencoderKLLTXVideo,
958
+ AutoencoderKLMagvit,
959
+ AutoencoderKLMochi,
960
+ AutoencoderKLQwenImage,
961
+ AutoencoderKLTemporalDecoder,
962
+ AutoencoderKLWan,
963
+ AutoencoderOobleck,
964
+ AutoencoderTiny,
965
+ AutoModel,
966
+ BriaFiboTransformer2DModel,
967
+ BriaTransformer2DModel,
968
+ CacheMixin,
969
+ ChromaTransformer2DModel,
970
+ ChronoEditTransformer3DModel,
971
+ CogVideoXTransformer3DModel,
972
+ CogView3PlusTransformer2DModel,
973
+ CogView4Transformer2DModel,
974
+ ConsisIDTransformer3DModel,
975
+ ConsistencyDecoderVAE,
976
+ ContextParallelConfig,
977
+ ControlNetModel,
978
+ ControlNetUnionModel,
979
+ ControlNetXSAdapter,
980
+ CosmosTransformer3DModel,
981
+ DiTTransformer2DModel,
982
+ EasyAnimateTransformer3DModel,
983
+ Flux2Transformer2DModel,
984
+ FluxControlNetModel,
985
+ FluxMultiControlNetModel,
986
+ FluxTransformer2DModel,
987
+ GlmImageTransformer2DModel,
988
+ HiDreamImageTransformer2DModel,
989
+ HunyuanDiT2DControlNetModel,
990
+ HunyuanDiT2DModel,
991
+ HunyuanDiT2DMultiControlNetModel,
992
+ HunyuanImageTransformer2DModel,
993
+ HunyuanVideo15Transformer3DModel,
994
+ HunyuanVideoFramepackTransformer3DModel,
995
+ HunyuanVideoTransformer3DModel,
996
+ I2VGenXLUNet,
997
+ Kandinsky3UNet,
998
+ Kandinsky5Transformer3DModel,
999
+ LatteTransformer3DModel,
1000
+ LongCatImageTransformer2DModel,
1001
+ LTX2VideoTransformer3DModel,
1002
+ LTXVideoTransformer3DModel,
1003
+ Lumina2Transformer2DModel,
1004
+ LuminaNextDiT2DModel,
1005
+ MochiTransformer3DModel,
1006
+ ModelMixin,
1007
+ MotionAdapter,
1008
+ MultiAdapter,
1009
+ MultiControlNetModel,
1010
+ OmniGenTransformer2DModel,
1011
+ OvisImageTransformer2DModel,
1012
+ ParallelConfig,
1013
+ PixArtTransformer2DModel,
1014
+ PriorTransformer,
1015
+ PRXTransformer2DModel,
1016
+ QwenImageControlNetModel,
1017
+ QwenImageMultiControlNetModel,
1018
+ QwenImageTransformer2DModel,
1019
+ SanaControlNetModel,
1020
+ SanaTransformer2DModel,
1021
+ SanaVideoTransformer3DModel,
1022
+ SD3ControlNetModel,
1023
+ SD3MultiControlNetModel,
1024
+ SD3Transformer2DModel,
1025
+ SkyReelsV2Transformer3DModel,
1026
+ SparseControlNetModel,
1027
+ StableAudioDiTModel,
1028
+ T2IAdapter,
1029
+ T5FilmDecoder,
1030
+ Transformer2DModel,
1031
+ TransformerTemporalModel,
1032
+ UNet1DModel,
1033
+ UNet2DConditionModel,
1034
+ UNet2DModel,
1035
+ UNet3DConditionModel,
1036
+ UNetControlNetXSModel,
1037
+ UNetMotionModel,
1038
+ UNetSpatioTemporalConditionModel,
1039
+ UVit2DModel,
1040
+ VQModel,
1041
+ WanAnimateTransformer3DModel,
1042
+ WanTransformer3DModel,
1043
+ WanVACETransformer3DModel,
1044
+ ZImageControlNetModel,
1045
+ ZImageTransformer2DModel,
1046
+ attention_backend,
1047
+ )
1048
+ from .modular_pipelines import ComponentsManager, ComponentSpec, ModularPipeline, ModularPipelineBlocks
1049
+ from .optimization import (
1050
+ get_constant_schedule,
1051
+ get_constant_schedule_with_warmup,
1052
+ get_cosine_schedule_with_warmup,
1053
+ get_cosine_with_hard_restarts_schedule_with_warmup,
1054
+ get_linear_schedule_with_warmup,
1055
+ get_polynomial_decay_schedule_with_warmup,
1056
+ get_scheduler,
1057
+ )
1058
+ from .pipelines import (
1059
+ AudioPipelineOutput,
1060
+ AutoPipelineForImage2Image,
1061
+ AutoPipelineForInpainting,
1062
+ AutoPipelineForText2Image,
1063
+ BlipDiffusionControlNetPipeline,
1064
+ BlipDiffusionPipeline,
1065
+ CLIPImageProjection,
1066
+ ConsistencyModelPipeline,
1067
+ DanceDiffusionPipeline,
1068
+ DDIMPipeline,
1069
+ DDPMPipeline,
1070
+ DiffusionPipeline,
1071
+ DiTPipeline,
1072
+ ImagePipelineOutput,
1073
+ KarrasVePipeline,
1074
+ LDMPipeline,
1075
+ LDMSuperResolutionPipeline,
1076
+ PNDMPipeline,
1077
+ RePaintPipeline,
1078
+ ScoreSdeVePipeline,
1079
+ StableDiffusionMixin,
1080
+ )
1081
+ from .quantizers import DiffusersQuantizer
1082
+ from .schedulers import (
1083
+ AmusedScheduler,
1084
+ CMStochasticIterativeScheduler,
1085
+ CogVideoXDDIMScheduler,
1086
+ CogVideoXDPMScheduler,
1087
+ DDIMInverseScheduler,
1088
+ DDIMParallelScheduler,
1089
+ DDIMScheduler,
1090
+ DDPMParallelScheduler,
1091
+ DDPMScheduler,
1092
+ DDPMWuerstchenScheduler,
1093
+ DEISMultistepScheduler,
1094
+ DPMSolverMultistepInverseScheduler,
1095
+ DPMSolverMultistepScheduler,
1096
+ DPMSolverSinglestepScheduler,
1097
+ EDMDPMSolverMultistepScheduler,
1098
+ EDMEulerScheduler,
1099
+ EulerAncestralDiscreteScheduler,
1100
+ EulerDiscreteScheduler,
1101
+ FlowMatchEulerDiscreteScheduler,
1102
+ FlowMatchHeunDiscreteScheduler,
1103
+ FlowMatchLCMScheduler,
1104
+ HeunDiscreteScheduler,
1105
+ IPNDMScheduler,
1106
+ KarrasVeScheduler,
1107
+ KDPM2AncestralDiscreteScheduler,
1108
+ KDPM2DiscreteScheduler,
1109
+ LCMScheduler,
1110
+ LTXEulerAncestralRFScheduler,
1111
+ PNDMScheduler,
1112
+ RePaintScheduler,
1113
+ SASolverScheduler,
1114
+ SchedulerMixin,
1115
+ SCMScheduler,
1116
+ ScoreSdeVeScheduler,
1117
+ TCDScheduler,
1118
+ UnCLIPScheduler,
1119
+ UniPCMultistepScheduler,
1120
+ VQDiffusionScheduler,
1121
+ )
1122
+ from .training_utils import EMAModel
1123
+
1124
+ try:
1125
+ if not (is_torch_available() and is_scipy_available()):
1126
+ raise OptionalDependencyNotAvailable()
1127
+ except OptionalDependencyNotAvailable:
1128
+ from .utils.dummy_torch_and_scipy_objects import * # noqa F403
1129
+ else:
1130
+ from .schedulers import LMSDiscreteScheduler
1131
+
1132
+ try:
1133
+ if not (is_torch_available() and is_torchsde_available()):
1134
+ raise OptionalDependencyNotAvailable()
1135
+ except OptionalDependencyNotAvailable:
1136
+ from .utils.dummy_torch_and_torchsde_objects import * # noqa F403
1137
+ else:
1138
+ from .schedulers import CosineDPMSolverMultistepScheduler, DPMSolverSDEScheduler
1139
+
1140
+ try:
1141
+ if not (is_torch_available() and is_transformers_available()):
1142
+ raise OptionalDependencyNotAvailable()
1143
+ except OptionalDependencyNotAvailable:
1144
+ from .utils.dummy_torch_and_transformers_objects import * # noqa F403
1145
+ else:
1146
+ from .modular_pipelines import (
1147
+ Flux2AutoBlocks,
1148
+ Flux2ModularPipeline,
1149
+ FluxAutoBlocks,
1150
+ FluxKontextAutoBlocks,
1151
+ FluxKontextModularPipeline,
1152
+ FluxModularPipeline,
1153
+ QwenImageAutoBlocks,
1154
+ QwenImageEditAutoBlocks,
1155
+ QwenImageEditModularPipeline,
1156
+ QwenImageEditPlusAutoBlocks,
1157
+ QwenImageEditPlusModularPipeline,
1158
+ QwenImageLayeredAutoBlocks,
1159
+ QwenImageLayeredModularPipeline,
1160
+ QwenImageModularPipeline,
1161
+ StableDiffusionXLAutoBlocks,
1162
+ StableDiffusionXLModularPipeline,
1163
+ Wan22AutoBlocks,
1164
+ WanAutoBlocks,
1165
+ WanModularPipeline,
1166
+ ZImageAutoBlocks,
1167
+ ZImageModularPipeline,
1168
+ )
1169
+ from .pipelines import (
1170
+ AllegroPipeline,
1171
+ AltDiffusionImg2ImgPipeline,
1172
+ AltDiffusionPipeline,
1173
+ AmusedImg2ImgPipeline,
1174
+ AmusedInpaintPipeline,
1175
+ AmusedPipeline,
1176
+ AnimateDiffControlNetPipeline,
1177
+ AnimateDiffPAGPipeline,
1178
+ AnimateDiffPipeline,
1179
+ AnimateDiffSDXLPipeline,
1180
+ AnimateDiffSparseControlNetPipeline,
1181
+ AnimateDiffVideoToVideoControlNetPipeline,
1182
+ AnimateDiffVideoToVideoPipeline,
1183
+ AudioLDM2Pipeline,
1184
+ AudioLDM2ProjectionModel,
1185
+ AudioLDM2UNet2DConditionModel,
1186
+ AudioLDMPipeline,
1187
+ AuraFlowPipeline,
1188
+ BriaFiboPipeline,
1189
+ BriaPipeline,
1190
+ ChromaImg2ImgPipeline,
1191
+ ChromaInpaintPipeline,
1192
+ ChromaPipeline,
1193
+ ChronoEditPipeline,
1194
+ CLIPImageProjection,
1195
+ CogVideoXFunControlPipeline,
1196
+ CogVideoXImageToVideoPipeline,
1197
+ CogVideoXPipeline,
1198
+ CogVideoXVideoToVideoPipeline,
1199
+ CogView3PlusPipeline,
1200
+ CogView4ControlPipeline,
1201
+ CogView4Pipeline,
1202
+ ConsisIDPipeline,
1203
+ Cosmos2_5_PredictBasePipeline,
1204
+ Cosmos2TextToImagePipeline,
1205
+ Cosmos2VideoToWorldPipeline,
1206
+ CosmosTextToWorldPipeline,
1207
+ CosmosVideoToWorldPipeline,
1208
+ CycleDiffusionPipeline,
1209
+ EasyAnimateControlPipeline,
1210
+ EasyAnimateInpaintPipeline,
1211
+ EasyAnimatePipeline,
1212
+ Flux2KleinPipeline,
1213
+ Flux2Pipeline,
1214
+ FluxControlImg2ImgPipeline,
1215
+ FluxControlInpaintPipeline,
1216
+ FluxControlNetImg2ImgPipeline,
1217
+ FluxControlNetInpaintPipeline,
1218
+ FluxControlNetPipeline,
1219
+ FluxControlPipeline,
1220
+ FluxFillPipeline,
1221
+ FluxImg2ImgPipeline,
1222
+ FluxInpaintPipeline,
1223
+ FluxKontextInpaintPipeline,
1224
+ FluxKontextPipeline,
1225
+ FluxPipeline,
1226
+ FluxPriorReduxPipeline,
1227
+ GlmImagePipeline,
1228
+ HiDreamImagePipeline,
1229
+ HunyuanDiTControlNetPipeline,
1230
+ HunyuanDiTPAGPipeline,
1231
+ HunyuanDiTPipeline,
1232
+ HunyuanImagePipeline,
1233
+ HunyuanImageRefinerPipeline,
1234
+ HunyuanSkyreelsImageToVideoPipeline,
1235
+ HunyuanVideo15ImageToVideoPipeline,
1236
+ HunyuanVideo15Pipeline,
1237
+ HunyuanVideoFramepackPipeline,
1238
+ HunyuanVideoImageToVideoPipeline,
1239
+ HunyuanVideoPipeline,
1240
+ I2VGenXLPipeline,
1241
+ IFImg2ImgPipeline,
1242
+ IFImg2ImgSuperResolutionPipeline,
1243
+ IFInpaintingPipeline,
1244
+ IFInpaintingSuperResolutionPipeline,
1245
+ IFPipeline,
1246
+ IFSuperResolutionPipeline,
1247
+ ImageTextPipelineOutput,
1248
+ Kandinsky3Img2ImgPipeline,
1249
+ Kandinsky3Pipeline,
1250
+ Kandinsky5I2IPipeline,
1251
+ Kandinsky5I2VPipeline,
1252
+ Kandinsky5T2IPipeline,
1253
+ Kandinsky5T2VPipeline,
1254
+ KandinskyCombinedPipeline,
1255
+ KandinskyImg2ImgCombinedPipeline,
1256
+ KandinskyImg2ImgPipeline,
1257
+ KandinskyInpaintCombinedPipeline,
1258
+ KandinskyInpaintPipeline,
1259
+ KandinskyPipeline,
1260
+ KandinskyPriorPipeline,
1261
+ KandinskyV22CombinedPipeline,
1262
+ KandinskyV22ControlnetImg2ImgPipeline,
1263
+ KandinskyV22ControlnetPipeline,
1264
+ KandinskyV22Img2ImgCombinedPipeline,
1265
+ KandinskyV22Img2ImgPipeline,
1266
+ KandinskyV22InpaintCombinedPipeline,
1267
+ KandinskyV22InpaintPipeline,
1268
+ KandinskyV22Pipeline,
1269
+ KandinskyV22PriorEmb2EmbPipeline,
1270
+ KandinskyV22PriorPipeline,
1271
+ LatentConsistencyModelImg2ImgPipeline,
1272
+ LatentConsistencyModelPipeline,
1273
+ LattePipeline,
1274
+ LDMTextToImagePipeline,
1275
+ LEditsPPPipelineStableDiffusion,
1276
+ LEditsPPPipelineStableDiffusionXL,
1277
+ LongCatImageEditPipeline,
1278
+ LongCatImagePipeline,
1279
+ LTX2ImageToVideoPipeline,
1280
+ LTX2LatentUpsamplePipeline,
1281
+ LTX2Pipeline,
1282
+ LTXConditionPipeline,
1283
+ LTXI2VLongMultiPromptPipeline,
1284
+ LTXImageToVideoPipeline,
1285
+ LTXLatentUpsamplePipeline,
1286
+ LTXPipeline,
1287
+ LucyEditPipeline,
1288
+ Lumina2Pipeline,
1289
+ Lumina2Text2ImgPipeline,
1290
+ LuminaPipeline,
1291
+ LuminaText2ImgPipeline,
1292
+ MarigoldDepthPipeline,
1293
+ MarigoldIntrinsicsPipeline,
1294
+ MarigoldNormalsPipeline,
1295
+ MochiPipeline,
1296
+ MusicLDMPipeline,
1297
+ OmniGenPipeline,
1298
+ OvisImagePipeline,
1299
+ PaintByExamplePipeline,
1300
+ PIAPipeline,
1301
+ PixArtAlphaPipeline,
1302
+ PixArtSigmaPAGPipeline,
1303
+ PixArtSigmaPipeline,
1304
+ PRXPipeline,
1305
+ QwenImageControlNetInpaintPipeline,
1306
+ QwenImageControlNetPipeline,
1307
+ QwenImageEditInpaintPipeline,
1308
+ QwenImageEditPipeline,
1309
+ QwenImageEditPlusPipeline,
1310
+ QwenImageImg2ImgPipeline,
1311
+ QwenImageInpaintPipeline,
1312
+ QwenImageLayeredPipeline,
1313
+ QwenImagePipeline,
1314
+ ReduxImageEncoder,
1315
+ SanaControlNetPipeline,
1316
+ SanaImageToVideoPipeline,
1317
+ SanaPAGPipeline,
1318
+ SanaPipeline,
1319
+ SanaSprintImg2ImgPipeline,
1320
+ SanaSprintPipeline,
1321
+ SanaVideoPipeline,
1322
+ SemanticStableDiffusionPipeline,
1323
+ ShapEImg2ImgPipeline,
1324
+ ShapEPipeline,
1325
+ SkyReelsV2DiffusionForcingImageToVideoPipeline,
1326
+ SkyReelsV2DiffusionForcingPipeline,
1327
+ SkyReelsV2DiffusionForcingVideoToVideoPipeline,
1328
+ SkyReelsV2ImageToVideoPipeline,
1329
+ SkyReelsV2Pipeline,
1330
+ StableAudioPipeline,
1331
+ StableAudioProjectionModel,
1332
+ StableCascadeCombinedPipeline,
1333
+ StableCascadeDecoderPipeline,
1334
+ StableCascadePriorPipeline,
1335
+ StableDiffusion3ControlNetInpaintingPipeline,
1336
+ StableDiffusion3ControlNetPipeline,
1337
+ StableDiffusion3Img2ImgPipeline,
1338
+ StableDiffusion3InpaintPipeline,
1339
+ StableDiffusion3PAGImg2ImgPipeline,
1340
+ StableDiffusion3PAGPipeline,
1341
+ StableDiffusion3Pipeline,
1342
+ StableDiffusionAdapterPipeline,
1343
+ StableDiffusionAttendAndExcitePipeline,
1344
+ StableDiffusionControlNetImg2ImgPipeline,
1345
+ StableDiffusionControlNetInpaintPipeline,
1346
+ StableDiffusionControlNetPAGInpaintPipeline,
1347
+ StableDiffusionControlNetPAGPipeline,
1348
+ StableDiffusionControlNetPipeline,
1349
+ StableDiffusionControlNetXSPipeline,
1350
+ StableDiffusionDepth2ImgPipeline,
1351
+ StableDiffusionDiffEditPipeline,
1352
+ StableDiffusionGLIGENPipeline,
1353
+ StableDiffusionGLIGENTextImagePipeline,
1354
+ StableDiffusionImageVariationPipeline,
1355
+ StableDiffusionImg2ImgPipeline,
1356
+ StableDiffusionInpaintPipeline,
1357
+ StableDiffusionInpaintPipelineLegacy,
1358
+ StableDiffusionInstructPix2PixPipeline,
1359
+ StableDiffusionLatentUpscalePipeline,
1360
+ StableDiffusionLDM3DPipeline,
1361
+ StableDiffusionModelEditingPipeline,
1362
+ StableDiffusionPAGImg2ImgPipeline,
1363
+ StableDiffusionPAGInpaintPipeline,
1364
+ StableDiffusionPAGPipeline,
1365
+ StableDiffusionPanoramaPipeline,
1366
+ StableDiffusionParadigmsPipeline,
1367
+ StableDiffusionPipeline,
1368
+ StableDiffusionPipelineSafe,
1369
+ StableDiffusionPix2PixZeroPipeline,
1370
+ StableDiffusionSAGPipeline,
1371
+ StableDiffusionUpscalePipeline,
1372
+ StableDiffusionXLAdapterPipeline,
1373
+ StableDiffusionXLControlNetImg2ImgPipeline,
1374
+ StableDiffusionXLControlNetInpaintPipeline,
1375
+ StableDiffusionXLControlNetPAGImg2ImgPipeline,
1376
+ StableDiffusionXLControlNetPAGPipeline,
1377
+ StableDiffusionXLControlNetPipeline,
1378
+ StableDiffusionXLControlNetUnionImg2ImgPipeline,
1379
+ StableDiffusionXLControlNetUnionInpaintPipeline,
1380
+ StableDiffusionXLControlNetUnionPipeline,
1381
+ StableDiffusionXLControlNetXSPipeline,
1382
+ StableDiffusionXLImg2ImgPipeline,
1383
+ StableDiffusionXLInpaintPipeline,
1384
+ StableDiffusionXLInstructPix2PixPipeline,
1385
+ StableDiffusionXLPAGImg2ImgPipeline,
1386
+ StableDiffusionXLPAGInpaintPipeline,
1387
+ StableDiffusionXLPAGPipeline,
1388
+ StableDiffusionXLPipeline,
1389
+ StableUnCLIPImg2ImgPipeline,
1390
+ StableUnCLIPPipeline,
1391
+ StableVideoDiffusionPipeline,
1392
+ TextToVideoSDPipeline,
1393
+ TextToVideoZeroPipeline,
1394
+ TextToVideoZeroSDXLPipeline,
1395
+ UnCLIPImageVariationPipeline,
1396
+ UnCLIPPipeline,
1397
+ UniDiffuserModel,
1398
+ UniDiffuserPipeline,
1399
+ UniDiffuserTextDecoder,
1400
+ VersatileDiffusionDualGuidedPipeline,
1401
+ VersatileDiffusionImageVariationPipeline,
1402
+ VersatileDiffusionPipeline,
1403
+ VersatileDiffusionTextToImagePipeline,
1404
+ VideoToVideoSDPipeline,
1405
+ VisualClozeGenerationPipeline,
1406
+ VisualClozePipeline,
1407
+ VQDiffusionPipeline,
1408
+ WanAnimatePipeline,
1409
+ WanImageToVideoPipeline,
1410
+ WanPipeline,
1411
+ WanVACEPipeline,
1412
+ WanVideoToVideoPipeline,
1413
+ WuerstchenCombinedPipeline,
1414
+ WuerstchenDecoderPipeline,
1415
+ WuerstchenPriorPipeline,
1416
+ ZImageControlNetInpaintPipeline,
1417
+ ZImageControlNetPipeline,
1418
+ ZImageImg2ImgPipeline,
1419
+ ZImageOmniPipeline,
1420
+ ZImagePipeline,
1421
+ )
1422
+
1423
+ try:
1424
+ if not (is_torch_available() and is_transformers_available() and is_k_diffusion_available()):
1425
+ raise OptionalDependencyNotAvailable()
1426
+ except OptionalDependencyNotAvailable:
1427
+ from .utils.dummy_torch_and_transformers_and_k_diffusion_objects import * # noqa F403
1428
+ else:
1429
+ from .pipelines import StableDiffusionKDiffusionPipeline, StableDiffusionXLKDiffusionPipeline
1430
+
1431
+ try:
1432
+ if not (is_torch_available() and is_transformers_available() and is_sentencepiece_available()):
1433
+ raise OptionalDependencyNotAvailable()
1434
+ except OptionalDependencyNotAvailable:
1435
+ from .utils.dummy_torch_and_transformers_and_sentencepiece_objects import * # noqa F403
1436
+ else:
1437
+ from .pipelines import KolorsImg2ImgPipeline, KolorsPAGPipeline, KolorsPipeline
1438
+
1439
+ try:
1440
+ if not (is_torch_available() and is_transformers_available() and is_opencv_available()):
1441
+ raise OptionalDependencyNotAvailable()
1442
+ except OptionalDependencyNotAvailable:
1443
+ from .utils.dummy_torch_and_transformers_and_opencv_objects import * # noqa F403
1444
+ else:
1445
+ from .pipelines import ConsisIDPipeline
1446
+
1447
+ try:
1448
+ if not (is_torch_available() and is_transformers_available() and is_onnx_available()):
1449
+ raise OptionalDependencyNotAvailable()
1450
+ except OptionalDependencyNotAvailable:
1451
+ from .utils.dummy_torch_and_transformers_and_onnx_objects import * # noqa F403
1452
+ else:
1453
+ from .pipelines import (
1454
+ OnnxStableDiffusionImg2ImgPipeline,
1455
+ OnnxStableDiffusionInpaintPipeline,
1456
+ OnnxStableDiffusionInpaintPipelineLegacy,
1457
+ OnnxStableDiffusionPipeline,
1458
+ OnnxStableDiffusionUpscalePipeline,
1459
+ StableDiffusionOnnxPipeline,
1460
+ )
1461
+
1462
+ try:
1463
+ if not (is_torch_available() and is_librosa_available()):
1464
+ raise OptionalDependencyNotAvailable()
1465
+ except OptionalDependencyNotAvailable:
1466
+ from .utils.dummy_torch_and_librosa_objects import * # noqa F403
1467
+ else:
1468
+ from .pipelines import AudioDiffusionPipeline, Mel
1469
+
1470
+ try:
1471
+ if not (is_transformers_available() and is_torch_available() and is_note_seq_available()):
1472
+ raise OptionalDependencyNotAvailable()
1473
+ except OptionalDependencyNotAvailable:
1474
+ from .utils.dummy_transformers_and_torch_and_note_seq_objects import * # noqa F403
1475
+ else:
1476
+ from .pipelines import SpectrogramDiffusionPipeline
1477
+
1478
+ try:
1479
+ if not is_flax_available():
1480
+ raise OptionalDependencyNotAvailable()
1481
+ except OptionalDependencyNotAvailable:
1482
+ from .utils.dummy_flax_objects import * # noqa F403
1483
+ else:
1484
+ from .models.controlnets.controlnet_flax import FlaxControlNetModel
1485
+ from .models.modeling_flax_utils import FlaxModelMixin
1486
+ from .models.unets.unet_2d_condition_flax import FlaxUNet2DConditionModel
1487
+ from .models.vae_flax import FlaxAutoencoderKL
1488
+ from .pipelines import FlaxDiffusionPipeline
1489
+ from .schedulers import (
1490
+ FlaxDDIMScheduler,
1491
+ FlaxDDPMScheduler,
1492
+ FlaxDPMSolverMultistepScheduler,
1493
+ FlaxEulerDiscreteScheduler,
1494
+ FlaxKarrasVeScheduler,
1495
+ FlaxLMSDiscreteScheduler,
1496
+ FlaxPNDMScheduler,
1497
+ FlaxSchedulerMixin,
1498
+ FlaxScoreSdeVeScheduler,
1499
+ )
1500
+
1501
+ try:
1502
+ if not (is_flax_available() and is_transformers_available()):
1503
+ raise OptionalDependencyNotAvailable()
1504
+ except OptionalDependencyNotAvailable:
1505
+ from .utils.dummy_flax_and_transformers_objects import * # noqa F403
1506
+ else:
1507
+ from .pipelines import (
1508
+ FlaxStableDiffusionControlNetPipeline,
1509
+ FlaxStableDiffusionImg2ImgPipeline,
1510
+ FlaxStableDiffusionInpaintPipeline,
1511
+ FlaxStableDiffusionPipeline,
1512
+ FlaxStableDiffusionXLPipeline,
1513
+ )
1514
+
1515
+ try:
1516
+ if not (is_note_seq_available()):
1517
+ raise OptionalDependencyNotAvailable()
1518
+ except OptionalDependencyNotAvailable:
1519
+ from .utils.dummy_note_seq_objects import * # noqa F403
1520
+ else:
1521
+ from .pipelines import MidiProcessor
1522
+
1523
+ else:
1524
+ import sys
1525
+
1526
+ sys.modules[__name__] = _LazyModule(
1527
+ __name__,
1528
+ globals()["__file__"],
1529
+ _import_structure,
1530
+ module_spec=__spec__,
1531
+ extra_objects={"__version__": __version__},
1532
+ )
vendor/diffusers/callbacks.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict, List
2
+
3
+ from .configuration_utils import ConfigMixin, register_to_config
4
+ from .utils import CONFIG_NAME
5
+
6
+
7
+ class PipelineCallback(ConfigMixin):
8
+ """
9
+ Base class for all the official callbacks used in a pipeline. This class provides a structure for implementing
10
+ custom callbacks and ensures that all callbacks have a consistent interface.
11
+
12
+ Please implement the following:
13
+ `tensor_inputs`: This should return a list of tensor inputs specific to your callback. You will only be able to
14
+ include
15
+ variables listed in the `._callback_tensor_inputs` attribute of your pipeline class.
16
+ `callback_fn`: This method defines the core functionality of your callback.
17
+ """
18
+
19
+ config_name = CONFIG_NAME
20
+
21
+ @register_to_config
22
+ def __init__(self, cutoff_step_ratio=1.0, cutoff_step_index=None):
23
+ super().__init__()
24
+
25
+ if (cutoff_step_ratio is None and cutoff_step_index is None) or (
26
+ cutoff_step_ratio is not None and cutoff_step_index is not None
27
+ ):
28
+ raise ValueError("Either cutoff_step_ratio or cutoff_step_index should be provided, not both or none.")
29
+
30
+ if cutoff_step_ratio is not None and (
31
+ not isinstance(cutoff_step_ratio, float) or not (0.0 <= cutoff_step_ratio <= 1.0)
32
+ ):
33
+ raise ValueError("cutoff_step_ratio must be a float between 0.0 and 1.0.")
34
+
35
+ @property
36
+ def tensor_inputs(self) -> List[str]:
37
+ raise NotImplementedError(f"You need to set the attribute `tensor_inputs` for {self.__class__}")
38
+
39
+ def callback_fn(self, pipeline, step_index, timesteps, callback_kwargs) -> Dict[str, Any]:
40
+ raise NotImplementedError(f"You need to implement the method `callback_fn` for {self.__class__}")
41
+
42
+ def __call__(self, pipeline, step_index, timestep, callback_kwargs) -> Dict[str, Any]:
43
+ return self.callback_fn(pipeline, step_index, timestep, callback_kwargs)
44
+
45
+
46
+ class MultiPipelineCallbacks:
47
+ """
48
+ This class is designed to handle multiple pipeline callbacks. It accepts a list of PipelineCallback objects and
49
+ provides a unified interface for calling all of them.
50
+ """
51
+
52
+ def __init__(self, callbacks: List[PipelineCallback]):
53
+ self.callbacks = callbacks
54
+
55
+ @property
56
+ def tensor_inputs(self) -> List[str]:
57
+ return [input for callback in self.callbacks for input in callback.tensor_inputs]
58
+
59
+ def __call__(self, pipeline, step_index, timestep, callback_kwargs) -> Dict[str, Any]:
60
+ """
61
+ Calls all the callbacks in order with the given arguments and returns the final callback_kwargs.
62
+ """
63
+ for callback in self.callbacks:
64
+ callback_kwargs = callback(pipeline, step_index, timestep, callback_kwargs)
65
+
66
+ return callback_kwargs
67
+
68
+
69
+ class SDCFGCutoffCallback(PipelineCallback):
70
+ """
71
+ Callback function for Stable Diffusion Pipelines. After certain number of steps (set by `cutoff_step_ratio` or
72
+ `cutoff_step_index`), this callback will disable the CFG.
73
+
74
+ Note: This callback mutates the pipeline by changing the `_guidance_scale` attribute to 0.0 after the cutoff step.
75
+ """
76
+
77
+ tensor_inputs = ["prompt_embeds"]
78
+
79
+ def callback_fn(self, pipeline, step_index, timestep, callback_kwargs) -> Dict[str, Any]:
80
+ cutoff_step_ratio = self.config.cutoff_step_ratio
81
+ cutoff_step_index = self.config.cutoff_step_index
82
+
83
+ # Use cutoff_step_index if it's not None, otherwise use cutoff_step_ratio
84
+ cutoff_step = (
85
+ cutoff_step_index if cutoff_step_index is not None else int(pipeline.num_timesteps * cutoff_step_ratio)
86
+ )
87
+
88
+ if step_index == cutoff_step:
89
+ prompt_embeds = callback_kwargs[self.tensor_inputs[0]]
90
+ prompt_embeds = prompt_embeds[-1:] # "-1" denotes the embeddings for conditional text tokens.
91
+
92
+ pipeline._guidance_scale = 0.0
93
+
94
+ callback_kwargs[self.tensor_inputs[0]] = prompt_embeds
95
+ return callback_kwargs
96
+
97
+
98
+ class SDXLCFGCutoffCallback(PipelineCallback):
99
+ """
100
+ Callback function for the base Stable Diffusion XL Pipelines. After certain number of steps (set by
101
+ `cutoff_step_ratio` or `cutoff_step_index`), this callback will disable the CFG.
102
+
103
+ Note: This callback mutates the pipeline by changing the `_guidance_scale` attribute to 0.0 after the cutoff step.
104
+ """
105
+
106
+ tensor_inputs = [
107
+ "prompt_embeds",
108
+ "add_text_embeds",
109
+ "add_time_ids",
110
+ ]
111
+
112
+ def callback_fn(self, pipeline, step_index, timestep, callback_kwargs) -> Dict[str, Any]:
113
+ cutoff_step_ratio = self.config.cutoff_step_ratio
114
+ cutoff_step_index = self.config.cutoff_step_index
115
+
116
+ # Use cutoff_step_index if it's not None, otherwise use cutoff_step_ratio
117
+ cutoff_step = (
118
+ cutoff_step_index if cutoff_step_index is not None else int(pipeline.num_timesteps * cutoff_step_ratio)
119
+ )
120
+
121
+ if step_index == cutoff_step:
122
+ prompt_embeds = callback_kwargs[self.tensor_inputs[0]]
123
+ prompt_embeds = prompt_embeds[-1:] # "-1" denotes the embeddings for conditional text tokens.
124
+
125
+ add_text_embeds = callback_kwargs[self.tensor_inputs[1]]
126
+ add_text_embeds = add_text_embeds[-1:] # "-1" denotes the embeddings for conditional pooled text tokens
127
+
128
+ add_time_ids = callback_kwargs[self.tensor_inputs[2]]
129
+ add_time_ids = add_time_ids[-1:] # "-1" denotes the embeddings for conditional added time vector
130
+
131
+ pipeline._guidance_scale = 0.0
132
+
133
+ callback_kwargs[self.tensor_inputs[0]] = prompt_embeds
134
+ callback_kwargs[self.tensor_inputs[1]] = add_text_embeds
135
+ callback_kwargs[self.tensor_inputs[2]] = add_time_ids
136
+
137
+ return callback_kwargs
138
+
139
+
140
+ class SDXLControlnetCFGCutoffCallback(PipelineCallback):
141
+ """
142
+ Callback function for the Controlnet Stable Diffusion XL Pipelines. After certain number of steps (set by
143
+ `cutoff_step_ratio` or `cutoff_step_index`), this callback will disable the CFG.
144
+
145
+ Note: This callback mutates the pipeline by changing the `_guidance_scale` attribute to 0.0 after the cutoff step.
146
+ """
147
+
148
+ tensor_inputs = [
149
+ "prompt_embeds",
150
+ "add_text_embeds",
151
+ "add_time_ids",
152
+ "image",
153
+ ]
154
+
155
+ def callback_fn(self, pipeline, step_index, timestep, callback_kwargs) -> Dict[str, Any]:
156
+ cutoff_step_ratio = self.config.cutoff_step_ratio
157
+ cutoff_step_index = self.config.cutoff_step_index
158
+
159
+ # Use cutoff_step_index if it's not None, otherwise use cutoff_step_ratio
160
+ cutoff_step = (
161
+ cutoff_step_index if cutoff_step_index is not None else int(pipeline.num_timesteps * cutoff_step_ratio)
162
+ )
163
+
164
+ if step_index == cutoff_step:
165
+ prompt_embeds = callback_kwargs[self.tensor_inputs[0]]
166
+ prompt_embeds = prompt_embeds[-1:] # "-1" denotes the embeddings for conditional text tokens.
167
+
168
+ add_text_embeds = callback_kwargs[self.tensor_inputs[1]]
169
+ add_text_embeds = add_text_embeds[-1:] # "-1" denotes the embeddings for conditional pooled text tokens
170
+
171
+ add_time_ids = callback_kwargs[self.tensor_inputs[2]]
172
+ add_time_ids = add_time_ids[-1:] # "-1" denotes the embeddings for conditional added time vector
173
+
174
+ # For Controlnet
175
+ image = callback_kwargs[self.tensor_inputs[3]]
176
+ image = image[-1:]
177
+
178
+ pipeline._guidance_scale = 0.0
179
+
180
+ callback_kwargs[self.tensor_inputs[0]] = prompt_embeds
181
+ callback_kwargs[self.tensor_inputs[1]] = add_text_embeds
182
+ callback_kwargs[self.tensor_inputs[2]] = add_time_ids
183
+ callback_kwargs[self.tensor_inputs[3]] = image
184
+
185
+ return callback_kwargs
186
+
187
+
188
+ class IPAdapterScaleCutoffCallback(PipelineCallback):
189
+ """
190
+ Callback function for any pipeline that inherits `IPAdapterMixin`. After certain number of steps (set by
191
+ `cutoff_step_ratio` or `cutoff_step_index`), this callback will set the IP Adapter scale to `0.0`.
192
+
193
+ Note: This callback mutates the IP Adapter attention processors by setting the scale to 0.0 after the cutoff step.
194
+ """
195
+
196
+ tensor_inputs = []
197
+
198
+ def callback_fn(self, pipeline, step_index, timestep, callback_kwargs) -> Dict[str, Any]:
199
+ cutoff_step_ratio = self.config.cutoff_step_ratio
200
+ cutoff_step_index = self.config.cutoff_step_index
201
+
202
+ # Use cutoff_step_index if it's not None, otherwise use cutoff_step_ratio
203
+ cutoff_step = (
204
+ cutoff_step_index if cutoff_step_index is not None else int(pipeline.num_timesteps * cutoff_step_ratio)
205
+ )
206
+
207
+ if step_index == cutoff_step:
208
+ pipeline.set_ip_adapter_scale(0.0)
209
+ return callback_kwargs
210
+
211
+
212
+ class SD3CFGCutoffCallback(PipelineCallback):
213
+ """
214
+ Callback function for Stable Diffusion 3 Pipelines. After certain number of steps (set by `cutoff_step_ratio` or
215
+ `cutoff_step_index`), this callback will disable the CFG.
216
+
217
+ Note: This callback mutates the pipeline by changing the `_guidance_scale` attribute to 0.0 after the cutoff step.
218
+ """
219
+
220
+ tensor_inputs = ["prompt_embeds", "pooled_prompt_embeds"]
221
+
222
+ def callback_fn(self, pipeline, step_index, timestep, callback_kwargs) -> Dict[str, Any]:
223
+ cutoff_step_ratio = self.config.cutoff_step_ratio
224
+ cutoff_step_index = self.config.cutoff_step_index
225
+
226
+ # Use cutoff_step_index if it's not None, otherwise use cutoff_step_ratio
227
+ cutoff_step = (
228
+ cutoff_step_index if cutoff_step_index is not None else int(pipeline.num_timesteps * cutoff_step_ratio)
229
+ )
230
+
231
+ if step_index == cutoff_step:
232
+ prompt_embeds = callback_kwargs[self.tensor_inputs[0]]
233
+ prompt_embeds = prompt_embeds[-1:] # "-1" denotes the embeddings for conditional text tokens.
234
+
235
+ pooled_prompt_embeds = callback_kwargs[self.tensor_inputs[1]]
236
+ pooled_prompt_embeds = pooled_prompt_embeds[
237
+ -1:
238
+ ] # "-1" denotes the embeddings for conditional pooled text tokens.
239
+
240
+ pipeline._guidance_scale = 0.0
241
+
242
+ callback_kwargs[self.tensor_inputs[0]] = prompt_embeds
243
+ callback_kwargs[self.tensor_inputs[1]] = pooled_prompt_embeds
244
+ return callback_kwargs
vendor/diffusers/commands/__init__.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ from abc import ABC, abstractmethod
16
+ from argparse import ArgumentParser
17
+
18
+
19
+ class BaseDiffusersCLICommand(ABC):
20
+ @staticmethod
21
+ @abstractmethod
22
+ def register_subcommand(parser: ArgumentParser):
23
+ raise NotImplementedError()
24
+
25
+ @abstractmethod
26
+ def run(self):
27
+ raise NotImplementedError()
vendor/diffusers/commands/custom_blocks.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ """
16
+ Usage example:
17
+ TODO
18
+ """
19
+
20
+ import ast
21
+ import importlib.util
22
+ import os
23
+ from argparse import ArgumentParser, Namespace
24
+ from pathlib import Path
25
+
26
+ from ..utils import logging
27
+ from . import BaseDiffusersCLICommand
28
+
29
+
30
+ EXPECTED_PARENT_CLASSES = ["ModularPipelineBlocks"]
31
+ CONFIG = "config.json"
32
+
33
+
34
+ def conversion_command_factory(args: Namespace):
35
+ return CustomBlocksCommand(args.block_module_name, args.block_class_name)
36
+
37
+
38
+ class CustomBlocksCommand(BaseDiffusersCLICommand):
39
+ @staticmethod
40
+ def register_subcommand(parser: ArgumentParser):
41
+ conversion_parser = parser.add_parser("custom_blocks")
42
+ conversion_parser.add_argument(
43
+ "--block_module_name",
44
+ type=str,
45
+ default="block.py",
46
+ help="Module filename in which the custom block will be implemented.",
47
+ )
48
+ conversion_parser.add_argument(
49
+ "--block_class_name",
50
+ type=str,
51
+ default=None,
52
+ help="Name of the custom block. If provided None, we will try to infer it.",
53
+ )
54
+ conversion_parser.set_defaults(func=conversion_command_factory)
55
+
56
+ def __init__(self, block_module_name: str = "block.py", block_class_name: str = None):
57
+ self.logger = logging.get_logger("diffusers-cli/custom_blocks")
58
+ self.block_module_name = Path(block_module_name)
59
+ self.block_class_name = block_class_name
60
+
61
+ def run(self):
62
+ # determine the block to be saved.
63
+ out = self._get_class_names(self.block_module_name)
64
+ classes_found = list({cls for cls, _ in out})
65
+
66
+ if self.block_class_name is not None:
67
+ child_class, parent_class = self._choose_block(out, self.block_class_name)
68
+ if child_class is None and parent_class is None:
69
+ raise ValueError(
70
+ "`block_class_name` could not be retrieved. Available classes from "
71
+ f"{self.block_module_name}:\n{classes_found}"
72
+ )
73
+ else:
74
+ self.logger.info(
75
+ f"Found classes: {classes_found} will be using {classes_found[0]}. "
76
+ "If this needs to be changed, re-run the command specifying `block_class_name`."
77
+ )
78
+ child_class, parent_class = out[0][0], out[0][1]
79
+
80
+ # dynamically get the custom block and initialize it to call `save_pretrained` in the current directory.
81
+ # the user is responsible for running it, so I guess that is safe?
82
+ module_name = f"__dynamic__{self.block_module_name.stem}"
83
+ spec = importlib.util.spec_from_file_location(module_name, str(self.block_module_name))
84
+ module = importlib.util.module_from_spec(spec)
85
+ spec.loader.exec_module(module)
86
+ getattr(module, child_class)().save_pretrained(os.getcwd())
87
+
88
+ # or, we could create it manually.
89
+ # automap = self._create_automap(parent_class=parent_class, child_class=child_class)
90
+ # with open(CONFIG, "w") as f:
91
+ # json.dump(automap, f)
92
+ with open("requirements.txt", "w") as f:
93
+ f.write("")
94
+
95
+ def _choose_block(self, candidates, chosen=None):
96
+ for cls, base in candidates:
97
+ if cls == chosen:
98
+ return cls, base
99
+ return None, None
100
+
101
+ def _get_class_names(self, file_path):
102
+ source = file_path.read_text(encoding="utf-8")
103
+ try:
104
+ tree = ast.parse(source, filename=file_path)
105
+ except SyntaxError as e:
106
+ raise ValueError(f"Could not parse {file_path!r}: {e}") from e
107
+
108
+ results: list[tuple[str, str]] = []
109
+ for node in tree.body:
110
+ if not isinstance(node, ast.ClassDef):
111
+ continue
112
+
113
+ # extract all base names for this class
114
+ base_names = [bname for b in node.bases if (bname := self._get_base_name(b)) is not None]
115
+
116
+ # for each allowed base that appears in the class's bases, emit a tuple
117
+ for allowed in EXPECTED_PARENT_CLASSES:
118
+ if allowed in base_names:
119
+ results.append((node.name, allowed))
120
+
121
+ return results
122
+
123
+ def _get_base_name(self, node: ast.expr):
124
+ if isinstance(node, ast.Name):
125
+ return node.id
126
+ elif isinstance(node, ast.Attribute):
127
+ val = self._get_base_name(node.value)
128
+ return f"{val}.{node.attr}" if val else node.attr
129
+ return None
130
+
131
+ def _create_automap(self, parent_class, child_class):
132
+ module = str(self.block_module_name).replace(".py", "").rsplit(".", 1)[-1]
133
+ auto_map = {f"{parent_class}": f"{module}.{child_class}"}
134
+ return {"auto_map": auto_map}
vendor/diffusers/commands/diffusers_cli.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # Copyright 2025 The HuggingFace Team. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from argparse import ArgumentParser
17
+
18
+ from .custom_blocks import CustomBlocksCommand
19
+ from .env import EnvironmentCommand
20
+ from .fp16_safetensors import FP16SafetensorsCommand
21
+
22
+
23
+ def main():
24
+ parser = ArgumentParser("Diffusers CLI tool", usage="diffusers-cli <command> [<args>]")
25
+ commands_parser = parser.add_subparsers(help="diffusers-cli command helpers")
26
+
27
+ # Register commands
28
+ EnvironmentCommand.register_subcommand(commands_parser)
29
+ FP16SafetensorsCommand.register_subcommand(commands_parser)
30
+ CustomBlocksCommand.register_subcommand(commands_parser)
31
+
32
+ # Let's go
33
+ args = parser.parse_args()
34
+
35
+ if not hasattr(args, "func"):
36
+ parser.print_help()
37
+ exit(1)
38
+
39
+ # Run
40
+ service = args.func(args)
41
+ service.run()
42
+
43
+
44
+ if __name__ == "__main__":
45
+ main()
vendor/diffusers/commands/env.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import platform
16
+ import subprocess
17
+ from argparse import ArgumentParser
18
+
19
+ import huggingface_hub
20
+
21
+ from .. import __version__ as version
22
+ from ..utils import (
23
+ is_accelerate_available,
24
+ is_bitsandbytes_available,
25
+ is_flax_available,
26
+ is_google_colab,
27
+ is_peft_available,
28
+ is_safetensors_available,
29
+ is_torch_available,
30
+ is_transformers_available,
31
+ is_xformers_available,
32
+ )
33
+ from . import BaseDiffusersCLICommand
34
+
35
+
36
+ def info_command_factory(_):
37
+ return EnvironmentCommand()
38
+
39
+
40
+ class EnvironmentCommand(BaseDiffusersCLICommand):
41
+ @staticmethod
42
+ def register_subcommand(parser: ArgumentParser) -> None:
43
+ download_parser = parser.add_parser("env")
44
+ download_parser.set_defaults(func=info_command_factory)
45
+
46
+ def run(self) -> dict:
47
+ hub_version = huggingface_hub.__version__
48
+
49
+ safetensors_version = "not installed"
50
+ if is_safetensors_available():
51
+ import safetensors
52
+
53
+ safetensors_version = safetensors.__version__
54
+
55
+ pt_version = "not installed"
56
+ pt_cuda_available = "NA"
57
+ if is_torch_available():
58
+ import torch
59
+
60
+ pt_version = torch.__version__
61
+ pt_cuda_available = torch.cuda.is_available()
62
+
63
+ flax_version = "not installed"
64
+ jax_version = "not installed"
65
+ jaxlib_version = "not installed"
66
+ jax_backend = "NA"
67
+ if is_flax_available():
68
+ import flax
69
+ import jax
70
+ import jaxlib
71
+
72
+ flax_version = flax.__version__
73
+ jax_version = jax.__version__
74
+ jaxlib_version = jaxlib.__version__
75
+ jax_backend = jax.lib.xla_bridge.get_backend().platform
76
+
77
+ transformers_version = "not installed"
78
+ if is_transformers_available():
79
+ import transformers
80
+
81
+ transformers_version = transformers.__version__
82
+
83
+ accelerate_version = "not installed"
84
+ if is_accelerate_available():
85
+ import accelerate
86
+
87
+ accelerate_version = accelerate.__version__
88
+
89
+ peft_version = "not installed"
90
+ if is_peft_available():
91
+ import peft
92
+
93
+ peft_version = peft.__version__
94
+
95
+ bitsandbytes_version = "not installed"
96
+ if is_bitsandbytes_available():
97
+ import bitsandbytes
98
+
99
+ bitsandbytes_version = bitsandbytes.__version__
100
+
101
+ xformers_version = "not installed"
102
+ if is_xformers_available():
103
+ import xformers
104
+
105
+ xformers_version = xformers.__version__
106
+
107
+ platform_info = platform.platform()
108
+
109
+ is_google_colab_str = "Yes" if is_google_colab() else "No"
110
+
111
+ accelerator = "NA"
112
+ if platform.system() in {"Linux", "Windows"}:
113
+ try:
114
+ sp = subprocess.Popen(
115
+ ["nvidia-smi", "--query-gpu=gpu_name,memory.total", "--format=csv,noheader"],
116
+ stdout=subprocess.PIPE,
117
+ stderr=subprocess.PIPE,
118
+ )
119
+ out_str, _ = sp.communicate()
120
+ out_str = out_str.decode("utf-8")
121
+
122
+ if len(out_str) > 0:
123
+ accelerator = out_str.strip()
124
+ except FileNotFoundError:
125
+ pass
126
+ elif platform.system() == "Darwin": # Mac OS
127
+ try:
128
+ sp = subprocess.Popen(
129
+ ["system_profiler", "SPDisplaysDataType"],
130
+ stdout=subprocess.PIPE,
131
+ stderr=subprocess.PIPE,
132
+ )
133
+ out_str, _ = sp.communicate()
134
+ out_str = out_str.decode("utf-8")
135
+
136
+ start = out_str.find("Chipset Model:")
137
+ if start != -1:
138
+ start += len("Chipset Model:")
139
+ end = out_str.find("\n", start)
140
+ accelerator = out_str[start:end].strip()
141
+
142
+ start = out_str.find("VRAM (Total):")
143
+ if start != -1:
144
+ start += len("VRAM (Total):")
145
+ end = out_str.find("\n", start)
146
+ accelerator += " VRAM: " + out_str[start:end].strip()
147
+ except FileNotFoundError:
148
+ pass
149
+ else:
150
+ print("It seems you are running an unusual OS. Could you fill in the accelerator manually?")
151
+
152
+ info = {
153
+ "🤗 Diffusers version": version,
154
+ "Platform": platform_info,
155
+ "Running on Google Colab?": is_google_colab_str,
156
+ "Python version": platform.python_version(),
157
+ "PyTorch version (GPU?)": f"{pt_version} ({pt_cuda_available})",
158
+ "Flax version (CPU?/GPU?/TPU?)": f"{flax_version} ({jax_backend})",
159
+ "Jax version": jax_version,
160
+ "JaxLib version": jaxlib_version,
161
+ "Huggingface_hub version": hub_version,
162
+ "Transformers version": transformers_version,
163
+ "Accelerate version": accelerate_version,
164
+ "PEFT version": peft_version,
165
+ "Bitsandbytes version": bitsandbytes_version,
166
+ "Safetensors version": safetensors_version,
167
+ "xFormers version": xformers_version,
168
+ "Accelerator": accelerator,
169
+ "Using GPU in script?": "<fill in>",
170
+ "Using distributed or parallel set-up in script?": "<fill in>",
171
+ }
172
+
173
+ print("\nCopy-and-paste the text below in your GitHub issue and FILL OUT the two last points.\n")
174
+ print(self.format_dict(info))
175
+
176
+ return info
177
+
178
+ @staticmethod
179
+ def format_dict(d: dict) -> str:
180
+ return "\n".join([f"- {prop}: {val}" for prop, val in d.items()]) + "\n"
vendor/diffusers/commands/fp16_safetensors.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ """
16
+ Usage example:
17
+ diffusers-cli fp16_safetensors --ckpt_id=openai/shap-e --fp16 --use_safetensors
18
+ """
19
+
20
+ import glob
21
+ import json
22
+ import warnings
23
+ from argparse import ArgumentParser, Namespace
24
+ from importlib import import_module
25
+
26
+ import huggingface_hub
27
+ import torch
28
+ from huggingface_hub import hf_hub_download
29
+ from packaging import version
30
+
31
+ from ..utils import logging
32
+ from . import BaseDiffusersCLICommand
33
+
34
+
35
+ def conversion_command_factory(args: Namespace):
36
+ if args.use_auth_token:
37
+ warnings.warn(
38
+ "The `--use_auth_token` flag is deprecated and will be removed in a future version. Authentication is now"
39
+ " handled automatically if user is logged in."
40
+ )
41
+ return FP16SafetensorsCommand(args.ckpt_id, args.fp16, args.use_safetensors)
42
+
43
+
44
+ class FP16SafetensorsCommand(BaseDiffusersCLICommand):
45
+ @staticmethod
46
+ def register_subcommand(parser: ArgumentParser):
47
+ conversion_parser = parser.add_parser("fp16_safetensors")
48
+ conversion_parser.add_argument(
49
+ "--ckpt_id",
50
+ type=str,
51
+ help="Repo id of the checkpoints on which to run the conversion. Example: 'openai/shap-e'.",
52
+ )
53
+ conversion_parser.add_argument(
54
+ "--fp16", action="store_true", help="If serializing the variables in FP16 precision."
55
+ )
56
+ conversion_parser.add_argument(
57
+ "--use_safetensors", action="store_true", help="If serializing in the safetensors format."
58
+ )
59
+ conversion_parser.add_argument(
60
+ "--use_auth_token",
61
+ action="store_true",
62
+ help="When working with checkpoints having private visibility. When used `hf auth login` needs to be run beforehand.",
63
+ )
64
+ conversion_parser.set_defaults(func=conversion_command_factory)
65
+
66
+ def __init__(self, ckpt_id: str, fp16: bool, use_safetensors: bool):
67
+ self.logger = logging.get_logger("diffusers-cli/fp16_safetensors")
68
+ self.ckpt_id = ckpt_id
69
+ self.local_ckpt_dir = f"/tmp/{ckpt_id}"
70
+ self.fp16 = fp16
71
+
72
+ self.use_safetensors = use_safetensors
73
+
74
+ if not self.use_safetensors and not self.fp16:
75
+ raise NotImplementedError(
76
+ "When `use_safetensors` and `fp16` both are False, then this command is of no use."
77
+ )
78
+
79
+ def run(self):
80
+ if version.parse(huggingface_hub.__version__) < version.parse("0.9.0"):
81
+ raise ImportError(
82
+ "The huggingface_hub version must be >= 0.9.0 to use this command. Please update your huggingface_hub"
83
+ " installation."
84
+ )
85
+ else:
86
+ from huggingface_hub import create_commit
87
+ from huggingface_hub._commit_api import CommitOperationAdd
88
+
89
+ model_index = hf_hub_download(repo_id=self.ckpt_id, filename="model_index.json")
90
+ with open(model_index, "r") as f:
91
+ pipeline_class_name = json.load(f)["_class_name"]
92
+ pipeline_class = getattr(import_module("diffusers"), pipeline_class_name)
93
+ self.logger.info(f"Pipeline class imported: {pipeline_class_name}.")
94
+
95
+ # Load the appropriate pipeline. We could have use `DiffusionPipeline`
96
+ # here, but just to avoid any rough edge cases.
97
+ pipeline = pipeline_class.from_pretrained(
98
+ self.ckpt_id, torch_dtype=torch.float16 if self.fp16 else torch.float32
99
+ )
100
+ pipeline.save_pretrained(
101
+ self.local_ckpt_dir,
102
+ safe_serialization=True if self.use_safetensors else False,
103
+ variant="fp16" if self.fp16 else None,
104
+ )
105
+ self.logger.info(f"Pipeline locally saved to {self.local_ckpt_dir}.")
106
+
107
+ # Fetch all the paths.
108
+ if self.fp16:
109
+ modified_paths = glob.glob(f"{self.local_ckpt_dir}/*/*.fp16.*")
110
+ elif self.use_safetensors:
111
+ modified_paths = glob.glob(f"{self.local_ckpt_dir}/*/*.safetensors")
112
+
113
+ # Prepare for the PR.
114
+ commit_message = f"Serialize variables with FP16: {self.fp16} and safetensors: {self.use_safetensors}."
115
+ operations = []
116
+ for path in modified_paths:
117
+ operations.append(CommitOperationAdd(path_in_repo="/".join(path.split("/")[4:]), path_or_fileobj=path))
118
+
119
+ # Open the PR.
120
+ commit_description = (
121
+ "Variables converted by the [`diffusers`' `fp16_safetensors`"
122
+ " CLI](https://github.com/huggingface/diffusers/blob/main/src/diffusers/commands/fp16_safetensors.py)."
123
+ )
124
+ hub_pr_url = create_commit(
125
+ repo_id=self.ckpt_id,
126
+ operations=operations,
127
+ commit_message=commit_message,
128
+ commit_description=commit_description,
129
+ repo_type="model",
130
+ create_pr=True,
131
+ ).pr_url
132
+ self.logger.info(f"PR created here: {hub_pr_url}.")
vendor/diffusers/configuration_utils.py ADDED
@@ -0,0 +1,769 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2025 The HuggingFace Inc. team.
3
+ # Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """ConfigMixin base class and utilities."""
17
+
18
+ import dataclasses
19
+ import functools
20
+ import importlib
21
+ import inspect
22
+ import json
23
+ import os
24
+ import re
25
+ from collections import OrderedDict
26
+ from pathlib import Path
27
+ from typing import Any, Dict, Optional, Tuple, Union
28
+
29
+ import numpy as np
30
+ from huggingface_hub import DDUFEntry, create_repo, hf_hub_download
31
+ from huggingface_hub.utils import (
32
+ EntryNotFoundError,
33
+ HfHubHTTPError,
34
+ RepositoryNotFoundError,
35
+ RevisionNotFoundError,
36
+ validate_hf_hub_args,
37
+ )
38
+ from typing_extensions import Self
39
+
40
+ from . import __version__
41
+ from .utils import (
42
+ HUGGINGFACE_CO_RESOLVE_ENDPOINT,
43
+ DummyObject,
44
+ deprecate,
45
+ extract_commit_hash,
46
+ http_user_agent,
47
+ logging,
48
+ )
49
+
50
+
51
+ logger = logging.get_logger(__name__)
52
+
53
+ _re_configuration_file = re.compile(r"config\.(.*)\.json")
54
+
55
+
56
+ class FrozenDict(OrderedDict):
57
+ def __init__(self, *args, **kwargs):
58
+ super().__init__(*args, **kwargs)
59
+
60
+ for key, value in self.items():
61
+ setattr(self, key, value)
62
+
63
+ self.__frozen = True
64
+
65
+ def __delitem__(self, *args, **kwargs):
66
+ raise Exception(f"You cannot use ``__delitem__`` on a {self.__class__.__name__} instance.")
67
+
68
+ def setdefault(self, *args, **kwargs):
69
+ raise Exception(f"You cannot use ``setdefault`` on a {self.__class__.__name__} instance.")
70
+
71
+ def pop(self, *args, **kwargs):
72
+ raise Exception(f"You cannot use ``pop`` on a {self.__class__.__name__} instance.")
73
+
74
+ def update(self, *args, **kwargs):
75
+ raise Exception(f"You cannot use ``update`` on a {self.__class__.__name__} instance.")
76
+
77
+ def __setattr__(self, name, value):
78
+ if hasattr(self, "__frozen") and self.__frozen:
79
+ raise Exception(f"You cannot use ``__setattr__`` on a {self.__class__.__name__} instance.")
80
+ super().__setattr__(name, value)
81
+
82
+ def __setitem__(self, name, value):
83
+ if hasattr(self, "__frozen") and self.__frozen:
84
+ raise Exception(f"You cannot use ``__setattr__`` on a {self.__class__.__name__} instance.")
85
+ super().__setitem__(name, value)
86
+
87
+
88
+ class ConfigMixin:
89
+ r"""
90
+ Base class for all configuration classes. All configuration parameters are stored under `self.config`. Also
91
+ provides the [`~ConfigMixin.from_config`] and [`~ConfigMixin.save_config`] methods for loading, downloading, and
92
+ saving classes that inherit from [`ConfigMixin`].
93
+
94
+ Class attributes:
95
+ - **config_name** (`str`) -- A filename under which the config should stored when calling
96
+ [`~ConfigMixin.save_config`] (should be overridden by parent class).
97
+ - **ignore_for_config** (`List[str]`) -- A list of attributes that should not be saved in the config (should be
98
+ overridden by subclass).
99
+ - **has_compatibles** (`bool`) -- Whether the class has compatible classes (should be overridden by subclass).
100
+ - **_deprecated_kwargs** (`List[str]`) -- Keyword arguments that are deprecated. Note that the `init` function
101
+ should only have a `kwargs` argument if at least one argument is deprecated (should be overridden by
102
+ subclass).
103
+ """
104
+
105
+ config_name = None
106
+ ignore_for_config = []
107
+ has_compatibles = False
108
+
109
+ _deprecated_kwargs = []
110
+
111
+ def register_to_config(self, **kwargs):
112
+ if self.config_name is None:
113
+ raise NotImplementedError(f"Make sure that {self.__class__} has defined a class name `config_name`")
114
+ # Special case for `kwargs` used in deprecation warning added to schedulers
115
+ # TODO: remove this when we remove the deprecation warning, and the `kwargs` argument,
116
+ # or solve in a more general way.
117
+ kwargs.pop("kwargs", None)
118
+
119
+ if not hasattr(self, "_internal_dict"):
120
+ internal_dict = kwargs
121
+ else:
122
+ previous_dict = dict(self._internal_dict)
123
+ internal_dict = {**self._internal_dict, **kwargs}
124
+ logger.debug(f"Updating config from {previous_dict} to {internal_dict}")
125
+
126
+ self._internal_dict = FrozenDict(internal_dict)
127
+
128
+ def __getattr__(self, name: str) -> Any:
129
+ """The only reason we overwrite `getattr` here is to gracefully deprecate accessing
130
+ config attributes directly. See https://github.com/huggingface/diffusers/pull/3129
131
+
132
+ This function is mostly copied from PyTorch's __getattr__ overwrite:
133
+ https://pytorch.org/docs/stable/_modules/torch/nn/modules/module.html#Module
134
+ """
135
+
136
+ is_in_config = "_internal_dict" in self.__dict__ and hasattr(self.__dict__["_internal_dict"], name)
137
+ is_attribute = name in self.__dict__
138
+
139
+ if is_in_config and not is_attribute:
140
+ deprecation_message = f"Accessing config attribute `{name}` directly via '{type(self).__name__}' object attribute is deprecated. Please access '{name}' over '{type(self).__name__}'s config object instead, e.g. 'scheduler.config.{name}'."
141
+ deprecate("direct config name access", "1.0.0", deprecation_message, standard_warn=False)
142
+ return self._internal_dict[name]
143
+
144
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
145
+
146
+ def save_config(self, save_directory: Union[str, os.PathLike], push_to_hub: bool = False, **kwargs):
147
+ """
148
+ Save a configuration object to the directory specified in `save_directory` so that it can be reloaded using the
149
+ [`~ConfigMixin.from_config`] class method.
150
+
151
+ Args:
152
+ save_directory (`str` or `os.PathLike`):
153
+ Directory where the configuration JSON file is saved (will be created if it does not exist).
154
+ push_to_hub (`bool`, *optional*, defaults to `False`):
155
+ Whether or not to push your model to the Hugging Face Hub after saving it. You can specify the
156
+ repository you want to push to with `repo_id` (will default to the name of `save_directory` in your
157
+ namespace).
158
+ kwargs (`Dict[str, Any]`, *optional*):
159
+ Additional keyword arguments passed along to the [`~utils.PushToHubMixin.push_to_hub`] method.
160
+ """
161
+ if os.path.isfile(save_directory):
162
+ raise AssertionError(f"Provided path ({save_directory}) should be a directory, not a file")
163
+
164
+ os.makedirs(save_directory, exist_ok=True)
165
+
166
+ # If we save using the predefined names, we can load using `from_config`
167
+ output_config_file = os.path.join(save_directory, self.config_name)
168
+
169
+ self.to_json_file(output_config_file)
170
+ logger.info(f"Configuration saved in {output_config_file}")
171
+
172
+ if push_to_hub:
173
+ commit_message = kwargs.pop("commit_message", None)
174
+ private = kwargs.pop("private", None)
175
+ create_pr = kwargs.pop("create_pr", False)
176
+ token = kwargs.pop("token", None)
177
+ repo_id = kwargs.pop("repo_id", save_directory.split(os.path.sep)[-1])
178
+ repo_id = create_repo(repo_id, exist_ok=True, private=private, token=token).repo_id
179
+ subfolder = kwargs.pop("subfolder", None)
180
+
181
+ self._upload_folder(
182
+ save_directory,
183
+ repo_id,
184
+ token=token,
185
+ commit_message=commit_message,
186
+ create_pr=create_pr,
187
+ subfolder=subfolder,
188
+ )
189
+
190
+ @classmethod
191
+ def from_config(
192
+ cls, config: Union[FrozenDict, Dict[str, Any]] = None, return_unused_kwargs=False, **kwargs
193
+ ) -> Union[Self, Tuple[Self, Dict[str, Any]]]:
194
+ r"""
195
+ Instantiate a Python class from a config dictionary.
196
+
197
+ Parameters:
198
+ config (`Dict[str, Any]`):
199
+ A config dictionary from which the Python class is instantiated. Make sure to only load configuration
200
+ files of compatible classes.
201
+ return_unused_kwargs (`bool`, *optional*, defaults to `False`):
202
+ Whether kwargs that are not consumed by the Python class should be returned or not.
203
+ kwargs (remaining dictionary of keyword arguments, *optional*):
204
+ Can be used to update the configuration object (after it is loaded) and initiate the Python class.
205
+ `**kwargs` are passed directly to the underlying scheduler/model's `__init__` method and eventually
206
+ overwrite the same named arguments in `config`.
207
+
208
+ Returns:
209
+ [`ModelMixin`] or [`SchedulerMixin`]:
210
+ A model or scheduler object instantiated from a config dictionary.
211
+
212
+ Examples:
213
+
214
+ ```python
215
+ >>> from diffusers import DDPMScheduler, DDIMScheduler, PNDMScheduler
216
+
217
+ >>> # Download scheduler from huggingface.co and cache.
218
+ >>> scheduler = DDPMScheduler.from_pretrained("google/ddpm-cifar10-32")
219
+
220
+ >>> # Instantiate DDIM scheduler class with same config as DDPM
221
+ >>> scheduler = DDIMScheduler.from_config(scheduler.config)
222
+
223
+ >>> # Instantiate PNDM scheduler class with same config as DDPM
224
+ >>> scheduler = PNDMScheduler.from_config(scheduler.config)
225
+ ```
226
+ """
227
+ # <===== TO BE REMOVED WITH DEPRECATION
228
+ # TODO(Patrick) - make sure to remove the following lines when config=="model_path" is deprecated
229
+ if "pretrained_model_name_or_path" in kwargs:
230
+ config = kwargs.pop("pretrained_model_name_or_path")
231
+
232
+ if config is None:
233
+ raise ValueError("Please make sure to provide a config as the first positional argument.")
234
+ # ======>
235
+
236
+ if not isinstance(config, dict):
237
+ deprecation_message = "It is deprecated to pass a pretrained model name or path to `from_config`."
238
+ if "Scheduler" in cls.__name__:
239
+ deprecation_message += (
240
+ f"If you were trying to load a scheduler, please use {cls}.from_pretrained(...) instead."
241
+ " Otherwise, please make sure to pass a configuration dictionary instead. This functionality will"
242
+ " be removed in v1.0.0."
243
+ )
244
+ elif "Model" in cls.__name__:
245
+ deprecation_message += (
246
+ f"If you were trying to load a model, please use {cls}.load_config(...) followed by"
247
+ f" {cls}.from_config(...) instead. Otherwise, please make sure to pass a configuration dictionary"
248
+ " instead. This functionality will be removed in v1.0.0."
249
+ )
250
+ deprecate("config-passed-as-path", "1.0.0", deprecation_message, standard_warn=False)
251
+ config, kwargs = cls.load_config(pretrained_model_name_or_path=config, return_unused_kwargs=True, **kwargs)
252
+
253
+ init_dict, unused_kwargs, hidden_dict = cls.extract_init_dict(config, **kwargs)
254
+
255
+ # Allow dtype to be specified on initialization
256
+ if "dtype" in unused_kwargs:
257
+ init_dict["dtype"] = unused_kwargs.pop("dtype")
258
+
259
+ # add possible deprecated kwargs
260
+ for deprecated_kwarg in cls._deprecated_kwargs:
261
+ if deprecated_kwarg in unused_kwargs:
262
+ init_dict[deprecated_kwarg] = unused_kwargs.pop(deprecated_kwarg)
263
+
264
+ # Return model and optionally state and/or unused_kwargs
265
+ model = cls(**init_dict)
266
+
267
+ # make sure to also save config parameters that might be used for compatible classes
268
+ # update _class_name
269
+ if "_class_name" in hidden_dict:
270
+ hidden_dict["_class_name"] = cls.__name__
271
+
272
+ model.register_to_config(**hidden_dict)
273
+
274
+ # add hidden kwargs of compatible classes to unused_kwargs
275
+ unused_kwargs = {**unused_kwargs, **hidden_dict}
276
+
277
+ if return_unused_kwargs:
278
+ return (model, unused_kwargs)
279
+ else:
280
+ return model
281
+
282
+ @classmethod
283
+ def get_config_dict(cls, *args, **kwargs):
284
+ deprecation_message = (
285
+ f" The function get_config_dict is deprecated. Please use {cls}.load_config instead. This function will be"
286
+ " removed in version v1.0.0"
287
+ )
288
+ deprecate("get_config_dict", "1.0.0", deprecation_message, standard_warn=False)
289
+ return cls.load_config(*args, **kwargs)
290
+
291
+ @classmethod
292
+ @validate_hf_hub_args
293
+ def load_config(
294
+ cls,
295
+ pretrained_model_name_or_path: Union[str, os.PathLike],
296
+ return_unused_kwargs=False,
297
+ return_commit_hash=False,
298
+ **kwargs,
299
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
300
+ r"""
301
+ Load a model or scheduler configuration.
302
+
303
+ Parameters:
304
+ pretrained_model_name_or_path (`str` or `os.PathLike`, *optional*):
305
+ Can be either:
306
+
307
+ - A string, the *model id* (for example `google/ddpm-celebahq-256`) of a pretrained model hosted on
308
+ the Hub.
309
+ - A path to a *directory* (for example `./my_model_directory`) containing model weights saved with
310
+ [`~ConfigMixin.save_config`].
311
+
312
+ cache_dir (`Union[str, os.PathLike]`, *optional*):
313
+ Path to a directory where a downloaded pretrained model configuration is cached if the standard cache
314
+ is not used.
315
+ force_download (`bool`, *optional*, defaults to `False`):
316
+ Whether or not to force the (re-)download of the model weights and configuration files, overriding the
317
+ cached versions if they exist.
318
+ proxies (`Dict[str, str]`, *optional*):
319
+ A dictionary of proxy servers to use by protocol or endpoint, for example, `{'http': 'foo.bar:3128',
320
+ 'http://hostname': 'foo.bar:4012'}`. The proxies are used on each request.
321
+ output_loading_info(`bool`, *optional*, defaults to `False`):
322
+ Whether or not to also return a dictionary containing missing keys, unexpected keys and error messages.
323
+ local_files_only (`bool`, *optional*, defaults to `False`):
324
+ Whether to only load local model weights and configuration files or not. If set to `True`, the model
325
+ won't be downloaded from the Hub.
326
+ token (`str` or *bool*, *optional*):
327
+ The token to use as HTTP bearer authorization for remote files. If `True`, the token generated from
328
+ `diffusers-cli login` (stored in `~/.huggingface`) is used.
329
+ revision (`str`, *optional*, defaults to `"main"`):
330
+ The specific model version to use. It can be a branch name, a tag name, a commit id, or any identifier
331
+ allowed by Git.
332
+ subfolder (`str`, *optional*, defaults to `""`):
333
+ The subfolder location of a model file within a larger model repository on the Hub or locally.
334
+ return_unused_kwargs (`bool`, *optional*, defaults to `False):
335
+ Whether unused keyword arguments of the config are returned.
336
+ return_commit_hash (`bool`, *optional*, defaults to `False):
337
+ Whether the `commit_hash` of the loaded configuration are returned.
338
+
339
+ Returns:
340
+ `dict`:
341
+ A dictionary of all the parameters stored in a JSON configuration file.
342
+
343
+ """
344
+ cache_dir = kwargs.pop("cache_dir", None)
345
+ local_dir = kwargs.pop("local_dir", None)
346
+ local_dir_use_symlinks = kwargs.pop("local_dir_use_symlinks", "auto")
347
+ force_download = kwargs.pop("force_download", False)
348
+ proxies = kwargs.pop("proxies", None)
349
+ token = kwargs.pop("token", None)
350
+ local_files_only = kwargs.pop("local_files_only", False)
351
+ revision = kwargs.pop("revision", None)
352
+ _ = kwargs.pop("mirror", None)
353
+ subfolder = kwargs.pop("subfolder", None)
354
+ user_agent = kwargs.pop("user_agent", {})
355
+ dduf_entries: Optional[Dict[str, DDUFEntry]] = kwargs.pop("dduf_entries", None)
356
+
357
+ user_agent = {**user_agent, "file_type": "config"}
358
+ user_agent = http_user_agent(user_agent)
359
+
360
+ pretrained_model_name_or_path = str(pretrained_model_name_or_path)
361
+
362
+ if cls.config_name is None:
363
+ raise ValueError(
364
+ "`self.config_name` is not defined. Note that one should not load a config from "
365
+ "`ConfigMixin`. Please make sure to define `config_name` in a class inheriting from `ConfigMixin`"
366
+ )
367
+ # Custom path for now
368
+ if dduf_entries:
369
+ if subfolder is not None:
370
+ raise ValueError(
371
+ "DDUF file only allow for 1 level of directory (e.g transformer/model1/model.safetentors is not allowed). "
372
+ "Please check the DDUF structure"
373
+ )
374
+ config_file = cls._get_config_file_from_dduf(pretrained_model_name_or_path, dduf_entries)
375
+ elif os.path.isfile(pretrained_model_name_or_path):
376
+ config_file = pretrained_model_name_or_path
377
+ elif os.path.isdir(pretrained_model_name_or_path):
378
+ if subfolder is not None and os.path.isfile(
379
+ os.path.join(pretrained_model_name_or_path, subfolder, cls.config_name)
380
+ ):
381
+ config_file = os.path.join(pretrained_model_name_or_path, subfolder, cls.config_name)
382
+ elif os.path.isfile(os.path.join(pretrained_model_name_or_path, cls.config_name)):
383
+ # Load from a PyTorch checkpoint
384
+ config_file = os.path.join(pretrained_model_name_or_path, cls.config_name)
385
+ else:
386
+ raise EnvironmentError(
387
+ f"Error no file named {cls.config_name} found in directory {pretrained_model_name_or_path}."
388
+ )
389
+ else:
390
+ try:
391
+ # Load from URL or cache if already cached
392
+ config_file = hf_hub_download(
393
+ pretrained_model_name_or_path,
394
+ filename=cls.config_name,
395
+ cache_dir=cache_dir,
396
+ force_download=force_download,
397
+ proxies=proxies,
398
+ local_files_only=local_files_only,
399
+ token=token,
400
+ user_agent=user_agent,
401
+ subfolder=subfolder,
402
+ revision=revision,
403
+ local_dir=local_dir,
404
+ local_dir_use_symlinks=local_dir_use_symlinks,
405
+ )
406
+ except RepositoryNotFoundError:
407
+ raise EnvironmentError(
408
+ f"{pretrained_model_name_or_path} is not a local folder and is not a valid model identifier"
409
+ " listed on 'https://huggingface.co/models'\nIf this is a private repository, make sure to pass a"
410
+ " token having permission to this repo with `token` or log in with `hf auth login`."
411
+ )
412
+ except RevisionNotFoundError:
413
+ raise EnvironmentError(
414
+ f"{revision} is not a valid git identifier (branch name, tag name or commit id) that exists for"
415
+ " this model name. Check the model page at"
416
+ f" 'https://huggingface.co/{pretrained_model_name_or_path}' for available revisions."
417
+ )
418
+ except EntryNotFoundError:
419
+ raise EnvironmentError(
420
+ f"{pretrained_model_name_or_path} does not appear to have a file named {cls.config_name}."
421
+ )
422
+ except HfHubHTTPError as err:
423
+ raise EnvironmentError(
424
+ "There was a specific connection error when trying to load"
425
+ f" {pretrained_model_name_or_path}:\n{err}"
426
+ )
427
+ except ValueError:
428
+ raise EnvironmentError(
429
+ f"We couldn't connect to '{HUGGINGFACE_CO_RESOLVE_ENDPOINT}' to load this model, couldn't find it"
430
+ f" in the cached files and it looks like {pretrained_model_name_or_path} is not the path to a"
431
+ f" directory containing a {cls.config_name} file.\nCheckout your internet connection or see how to"
432
+ " run the library in offline mode at"
433
+ " 'https://huggingface.co/docs/diffusers/installation#offline-mode'."
434
+ )
435
+ except EnvironmentError:
436
+ raise EnvironmentError(
437
+ f"Can't load config for '{pretrained_model_name_or_path}'. If you were trying to load it from "
438
+ "'https://huggingface.co/models', make sure you don't have a local directory with the same name. "
439
+ f"Otherwise, make sure '{pretrained_model_name_or_path}' is the correct path to a directory "
440
+ f"containing a {cls.config_name} file"
441
+ )
442
+ try:
443
+ config_dict = cls._dict_from_json_file(config_file, dduf_entries=dduf_entries)
444
+
445
+ commit_hash = extract_commit_hash(config_file)
446
+ except (json.JSONDecodeError, UnicodeDecodeError):
447
+ raise EnvironmentError(f"It looks like the config file at '{config_file}' is not a valid JSON file.")
448
+
449
+ if not (return_unused_kwargs or return_commit_hash):
450
+ return config_dict
451
+
452
+ outputs = (config_dict,)
453
+
454
+ if return_unused_kwargs:
455
+ outputs += (kwargs,)
456
+
457
+ if return_commit_hash:
458
+ outputs += (commit_hash,)
459
+
460
+ return outputs
461
+
462
+ @staticmethod
463
+ def _get_init_keys(input_class):
464
+ return set(dict(inspect.signature(input_class.__init__).parameters).keys())
465
+
466
+ @classmethod
467
+ def extract_init_dict(cls, config_dict, **kwargs):
468
+ # Skip keys that were not present in the original config, so default __init__ values were used
469
+ used_defaults = config_dict.get("_use_default_values", [])
470
+ config_dict = {k: v for k, v in config_dict.items() if k not in used_defaults and k != "_use_default_values"}
471
+
472
+ # 0. Copy origin config dict
473
+ original_dict = dict(config_dict.items())
474
+
475
+ # 1. Retrieve expected config attributes from __init__ signature
476
+ expected_keys = cls._get_init_keys(cls)
477
+ expected_keys.remove("self")
478
+ # remove general kwargs if present in dict
479
+ if "kwargs" in expected_keys:
480
+ expected_keys.remove("kwargs")
481
+ # remove flax internal keys
482
+ if hasattr(cls, "_flax_internal_args"):
483
+ for arg in cls._flax_internal_args:
484
+ expected_keys.remove(arg)
485
+
486
+ # 2. Remove attributes that cannot be expected from expected config attributes
487
+ # remove keys to be ignored
488
+ if len(cls.ignore_for_config) > 0:
489
+ expected_keys = expected_keys - set(cls.ignore_for_config)
490
+
491
+ # load diffusers library to import compatible and original scheduler
492
+ diffusers_library = importlib.import_module(__name__.split(".")[0])
493
+
494
+ if cls.has_compatibles:
495
+ compatible_classes = [c for c in cls._get_compatibles() if not isinstance(c, DummyObject)]
496
+ else:
497
+ compatible_classes = []
498
+
499
+ expected_keys_comp_cls = set()
500
+ for c in compatible_classes:
501
+ expected_keys_c = cls._get_init_keys(c)
502
+ expected_keys_comp_cls = expected_keys_comp_cls.union(expected_keys_c)
503
+ expected_keys_comp_cls = expected_keys_comp_cls - cls._get_init_keys(cls)
504
+ config_dict = {k: v for k, v in config_dict.items() if k not in expected_keys_comp_cls}
505
+
506
+ # remove attributes from orig class that cannot be expected
507
+ orig_cls_name = config_dict.pop("_class_name", cls.__name__)
508
+ if (
509
+ isinstance(orig_cls_name, str)
510
+ and orig_cls_name != cls.__name__
511
+ and hasattr(diffusers_library, orig_cls_name)
512
+ ):
513
+ orig_cls = getattr(diffusers_library, orig_cls_name)
514
+ unexpected_keys_from_orig = cls._get_init_keys(orig_cls) - expected_keys
515
+ config_dict = {k: v for k, v in config_dict.items() if k not in unexpected_keys_from_orig}
516
+ elif not isinstance(orig_cls_name, str) and not isinstance(orig_cls_name, (list, tuple)):
517
+ raise ValueError(
518
+ "Make sure that the `_class_name` is of type string or list of string (for custom pipelines)."
519
+ )
520
+
521
+ # remove private attributes
522
+ config_dict = {k: v for k, v in config_dict.items() if not k.startswith("_")}
523
+
524
+ # remove quantization_config
525
+ config_dict = {k: v for k, v in config_dict.items() if k != "quantization_config"}
526
+
527
+ # 3. Create keyword arguments that will be passed to __init__ from expected keyword arguments
528
+ init_dict = {}
529
+ for key in expected_keys:
530
+ # if config param is passed to kwarg and is present in config dict
531
+ # it should overwrite existing config dict key
532
+ if key in kwargs and key in config_dict:
533
+ config_dict[key] = kwargs.pop(key)
534
+
535
+ if key in kwargs:
536
+ # overwrite key
537
+ init_dict[key] = kwargs.pop(key)
538
+ elif key in config_dict:
539
+ # use value from config dict
540
+ init_dict[key] = config_dict.pop(key)
541
+
542
+ # 4. Give nice warning if unexpected values have been passed
543
+ if len(config_dict) > 0:
544
+ logger.warning(
545
+ f"The config attributes {config_dict} were passed to {cls.__name__}, "
546
+ "but are not expected and will be ignored. Please verify your "
547
+ f"{cls.config_name} configuration file."
548
+ )
549
+
550
+ # 5. Give nice info if config attributes are initialized to default because they have not been passed
551
+ passed_keys = set(init_dict.keys())
552
+ if len(expected_keys - passed_keys) > 0:
553
+ logger.info(
554
+ f"{expected_keys - passed_keys} was not found in config. Values will be initialized to default values."
555
+ )
556
+
557
+ # 6. Define unused keyword arguments
558
+ unused_kwargs = {**config_dict, **kwargs}
559
+
560
+ # 7. Define "hidden" config parameters that were saved for compatible classes
561
+ hidden_config_dict = {k: v for k, v in original_dict.items() if k not in init_dict}
562
+
563
+ return init_dict, unused_kwargs, hidden_config_dict
564
+
565
+ @classmethod
566
+ def _dict_from_json_file(
567
+ cls, json_file: Union[str, os.PathLike], dduf_entries: Optional[Dict[str, DDUFEntry]] = None
568
+ ):
569
+ if dduf_entries:
570
+ text = dduf_entries[json_file].read_text()
571
+ else:
572
+ with open(json_file, "r", encoding="utf-8") as reader:
573
+ text = reader.read()
574
+ return json.loads(text)
575
+
576
+ def __repr__(self):
577
+ return f"{self.__class__.__name__} {self.to_json_string()}"
578
+
579
+ @property
580
+ def config(self) -> Dict[str, Any]:
581
+ """
582
+ Returns the config of the class as a frozen dictionary
583
+
584
+ Returns:
585
+ `Dict[str, Any]`: Config of the class.
586
+ """
587
+ return self._internal_dict
588
+
589
+ def to_json_string(self) -> str:
590
+ """
591
+ Serializes the configuration instance to a JSON string.
592
+
593
+ Returns:
594
+ `str`:
595
+ String containing all the attributes that make up the configuration instance in JSON format.
596
+ """
597
+ config_dict = self._internal_dict if hasattr(self, "_internal_dict") else {}
598
+ config_dict["_class_name"] = self.__class__.__name__
599
+ config_dict["_diffusers_version"] = __version__
600
+
601
+ def to_json_saveable(value):
602
+ if isinstance(value, np.ndarray):
603
+ value = value.tolist()
604
+ elif isinstance(value, Path):
605
+ value = value.as_posix()
606
+ elif hasattr(value, "to_dict") and callable(value.to_dict):
607
+ value = value.to_dict()
608
+ elif isinstance(value, list):
609
+ value = [to_json_saveable(v) for v in value]
610
+ return value
611
+
612
+ if "quantization_config" in config_dict:
613
+ config_dict["quantization_config"] = (
614
+ config_dict.quantization_config.to_dict()
615
+ if not isinstance(config_dict.quantization_config, dict)
616
+ else config_dict.quantization_config
617
+ )
618
+
619
+ config_dict = {k: to_json_saveable(v) for k, v in config_dict.items()}
620
+ # Don't save "_ignore_files" or "_use_default_values"
621
+ config_dict.pop("_ignore_files", None)
622
+ config_dict.pop("_use_default_values", None)
623
+ # pop the `_pre_quantization_dtype` as torch.dtypes are not serializable.
624
+ _ = config_dict.pop("_pre_quantization_dtype", None)
625
+
626
+ return json.dumps(config_dict, indent=2, sort_keys=True) + "\n"
627
+
628
+ def to_json_file(self, json_file_path: Union[str, os.PathLike]):
629
+ """
630
+ Save the configuration instance's parameters to a JSON file.
631
+
632
+ Args:
633
+ json_file_path (`str` or `os.PathLike`):
634
+ Path to the JSON file to save a configuration instance's parameters.
635
+ """
636
+ with open(json_file_path, "w", encoding="utf-8") as writer:
637
+ writer.write(self.to_json_string())
638
+
639
+ @classmethod
640
+ def _get_config_file_from_dduf(cls, pretrained_model_name_or_path: str, dduf_entries: Dict[str, DDUFEntry]):
641
+ # paths inside a DDUF file must always be "/"
642
+ config_file = (
643
+ cls.config_name
644
+ if pretrained_model_name_or_path == ""
645
+ else "/".join([pretrained_model_name_or_path, cls.config_name])
646
+ )
647
+ if config_file not in dduf_entries:
648
+ raise ValueError(
649
+ f"We did not manage to find the file {config_file} in the dduf file. We only have the following files {dduf_entries.keys()}"
650
+ )
651
+ return config_file
652
+
653
+
654
+ def register_to_config(init):
655
+ r"""
656
+ Decorator to apply on the init of classes inheriting from [`ConfigMixin`] so that all the arguments are
657
+ automatically sent to `self.register_for_config`. To ignore a specific argument accepted by the init but that
658
+ shouldn't be registered in the config, use the `ignore_for_config` class variable
659
+
660
+ Warning: Once decorated, all private arguments (beginning with an underscore) are trashed and not sent to the init!
661
+ """
662
+
663
+ @functools.wraps(init)
664
+ def inner_init(self, *args, **kwargs):
665
+ # Ignore private kwargs in the init.
666
+ init_kwargs = {k: v for k, v in kwargs.items() if not k.startswith("_")}
667
+ config_init_kwargs = {k: v for k, v in kwargs.items() if k.startswith("_")}
668
+ if not isinstance(self, ConfigMixin):
669
+ raise RuntimeError(
670
+ f"`@register_for_config` was applied to {self.__class__.__name__} init method, but this class does "
671
+ "not inherit from `ConfigMixin`."
672
+ )
673
+
674
+ ignore = getattr(self, "ignore_for_config", [])
675
+ # Get positional arguments aligned with kwargs
676
+ new_kwargs = {}
677
+ signature = inspect.signature(init)
678
+ parameters = {
679
+ name: p.default for i, (name, p) in enumerate(signature.parameters.items()) if i > 0 and name not in ignore
680
+ }
681
+ for arg, name in zip(args, parameters.keys()):
682
+ new_kwargs[name] = arg
683
+
684
+ # Then add all kwargs
685
+ new_kwargs.update(
686
+ {
687
+ k: init_kwargs.get(k, default)
688
+ for k, default in parameters.items()
689
+ if k not in ignore and k not in new_kwargs
690
+ }
691
+ )
692
+
693
+ # Take note of the parameters that were not present in the loaded config
694
+ if len(set(new_kwargs.keys()) - set(init_kwargs)) > 0:
695
+ new_kwargs["_use_default_values"] = list(set(new_kwargs.keys()) - set(init_kwargs))
696
+
697
+ new_kwargs = {**config_init_kwargs, **new_kwargs}
698
+ getattr(self, "register_to_config")(**new_kwargs)
699
+ init(self, *args, **init_kwargs)
700
+
701
+ return inner_init
702
+
703
+
704
+ def flax_register_to_config(cls):
705
+ original_init = cls.__init__
706
+
707
+ @functools.wraps(original_init)
708
+ def init(self, *args, **kwargs):
709
+ if not isinstance(self, ConfigMixin):
710
+ raise RuntimeError(
711
+ f"`@register_for_config` was applied to {self.__class__.__name__} init method, but this class does "
712
+ "not inherit from `ConfigMixin`."
713
+ )
714
+
715
+ # Ignore private kwargs in the init. Retrieve all passed attributes
716
+ init_kwargs = dict(kwargs.items())
717
+
718
+ # Retrieve default values
719
+ fields = dataclasses.fields(self)
720
+ default_kwargs = {}
721
+ for field in fields:
722
+ # ignore flax specific attributes
723
+ if field.name in self._flax_internal_args:
724
+ continue
725
+ if type(field.default) == dataclasses._MISSING_TYPE:
726
+ default_kwargs[field.name] = None
727
+ else:
728
+ default_kwargs[field.name] = getattr(self, field.name)
729
+
730
+ # Make sure init_kwargs override default kwargs
731
+ new_kwargs = {**default_kwargs, **init_kwargs}
732
+ # dtype should be part of `init_kwargs`, but not `new_kwargs`
733
+ if "dtype" in new_kwargs:
734
+ new_kwargs.pop("dtype")
735
+
736
+ # Get positional arguments aligned with kwargs
737
+ for i, arg in enumerate(args):
738
+ name = fields[i].name
739
+ new_kwargs[name] = arg
740
+
741
+ # Take note of the parameters that were not present in the loaded config
742
+ if len(set(new_kwargs.keys()) - set(init_kwargs)) > 0:
743
+ new_kwargs["_use_default_values"] = list(set(new_kwargs.keys()) - set(init_kwargs))
744
+
745
+ getattr(self, "register_to_config")(**new_kwargs)
746
+ original_init(self, *args, **kwargs)
747
+
748
+ cls.__init__ = init
749
+ return cls
750
+
751
+
752
+ class LegacyConfigMixin(ConfigMixin):
753
+ r"""
754
+ A subclass of `ConfigMixin` to resolve class mapping from legacy classes (like `Transformer2DModel`) to more
755
+ pipeline-specific classes (like `DiTTransformer2DModel`).
756
+ """
757
+
758
+ @classmethod
759
+ def from_config(cls, config: Union[FrozenDict, Dict[str, Any]] = None, return_unused_kwargs=False, **kwargs):
760
+ # To prevent dependency import problem.
761
+ from .models.model_loading_utils import _fetch_remapped_cls_from_config
762
+
763
+ # resolve remapping
764
+ remapped_class = _fetch_remapped_cls_from_config(config, cls)
765
+
766
+ if remapped_class is cls:
767
+ return super(LegacyConfigMixin, remapped_class).from_config(config, return_unused_kwargs, **kwargs)
768
+ else:
769
+ return remapped_class.from_config(config, return_unused_kwargs, **kwargs)
vendor/diffusers/dependency_versions_check.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ from .dependency_versions_table import deps
16
+ from .utils.versions import require_version, require_version_core
17
+
18
+
19
+ # define which module versions we always want to check at run time
20
+ # (usually the ones defined in `install_requires` in setup.py)
21
+ #
22
+ # order specific notes:
23
+ # - tqdm must be checked before tokenizers
24
+
25
+ pkgs_to_check_at_runtime = "python requests filelock numpy".split()
26
+ for pkg in pkgs_to_check_at_runtime:
27
+ if pkg in deps:
28
+ require_version_core(deps[pkg])
29
+ else:
30
+ raise ValueError(f"can't find {pkg} in {deps.keys()}, check dependency_versions_table.py")
31
+
32
+
33
+ def dep_version_check(pkg, hint=None):
34
+ require_version(deps[pkg], hint)
vendor/diffusers/dependency_versions_table.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # THIS FILE HAS BEEN AUTOGENERATED. To update:
2
+ # 1. modify the `_deps` dict in setup.py
3
+ # 2. run `make deps_table_update`
4
+ deps = {
5
+ "Pillow": "Pillow",
6
+ "accelerate": "accelerate>=0.31.0",
7
+ "compel": "compel==0.1.8",
8
+ "datasets": "datasets",
9
+ "filelock": "filelock",
10
+ "flax": "flax>=0.4.1",
11
+ "hf-doc-builder": "hf-doc-builder>=0.3.0",
12
+ "httpx": "httpx<1.0.0",
13
+ "huggingface-hub": "huggingface-hub>=0.34.0,<2.0",
14
+ "requests-mock": "requests-mock==1.10.0",
15
+ "importlib_metadata": "importlib_metadata",
16
+ "invisible-watermark": "invisible-watermark>=0.2.0",
17
+ "isort": "isort>=5.5.4",
18
+ "jax": "jax>=0.4.1",
19
+ "jaxlib": "jaxlib>=0.4.1",
20
+ "Jinja2": "Jinja2",
21
+ "k-diffusion": "k-diffusion==0.0.12",
22
+ "torchsde": "torchsde",
23
+ "note_seq": "note_seq",
24
+ "librosa": "librosa",
25
+ "numpy": "numpy",
26
+ "parameterized": "parameterized",
27
+ "peft": "peft>=0.17.0",
28
+ "protobuf": "protobuf>=3.20.3,<4",
29
+ "pytest": "pytest",
30
+ "pytest-timeout": "pytest-timeout",
31
+ "pytest-xdist": "pytest-xdist",
32
+ "python": "python>=3.8.0",
33
+ "ruff": "ruff==0.9.10",
34
+ "safetensors": "safetensors>=0.3.1",
35
+ "sentencepiece": "sentencepiece>=0.1.91,!=0.1.92",
36
+ "GitPython": "GitPython<3.1.19",
37
+ "scipy": "scipy",
38
+ "onnx": "onnx",
39
+ "optimum_quanto": "optimum_quanto>=0.2.6",
40
+ "gguf": "gguf>=0.10.0",
41
+ "torchao": "torchao>=0.7.0",
42
+ "bitsandbytes": "bitsandbytes>=0.43.3",
43
+ "nvidia_modelopt[hf]": "nvidia_modelopt[hf]>=0.33.1",
44
+ "regex": "regex!=2019.12.17",
45
+ "requests": "requests",
46
+ "tensorboard": "tensorboard",
47
+ "tiktoken": "tiktoken>=0.7.0",
48
+ "torch": "torch>=1.4",
49
+ "torchvision": "torchvision",
50
+ "transformers": "transformers>=4.41.2",
51
+ "urllib3": "urllib3<=2.0.0",
52
+ "black": "black",
53
+ "phonemizer": "phonemizer",
54
+ "opencv-python": "opencv-python",
55
+ "timm": "timm",
56
+ }
vendor/diffusers/experimental/README.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # 🧨 Diffusers Experimental
2
+
3
+ We are adding experimental code to support novel applications and usages of the Diffusers library.
4
+ Currently, the following experiments are supported:
5
+ * Reinforcement learning via an implementation of the [Diffuser](https://huggingface.co/papers/2205.09991) model.
vendor/diffusers/experimental/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .rl import ValueGuidedRLPipeline
vendor/diffusers/experimental/rl/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .value_guided_sampling import ValueGuidedRLPipeline
vendor/diffusers/experimental/rl/value_guided_sampling.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import numpy as np
16
+ import torch
17
+ import tqdm
18
+
19
+ from ...models.unets.unet_1d import UNet1DModel
20
+ from ...pipelines import DiffusionPipeline
21
+ from ...utils.dummy_pt_objects import DDPMScheduler
22
+ from ...utils.torch_utils import randn_tensor
23
+
24
+
25
+ class ValueGuidedRLPipeline(DiffusionPipeline):
26
+ r"""
27
+ Pipeline for value-guided sampling from a diffusion model trained to predict sequences of states.
28
+
29
+ This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods
30
+ implemented for all pipelines (downloading, saving, running on a particular device, etc.).
31
+
32
+ Parameters:
33
+ value_function ([`UNet1DModel`]):
34
+ A specialized UNet for fine-tuning trajectories base on reward.
35
+ unet ([`UNet1DModel`]):
36
+ UNet architecture to denoise the encoded trajectories.
37
+ scheduler ([`SchedulerMixin`]):
38
+ A scheduler to be used in combination with `unet` to denoise the encoded trajectories. Default for this
39
+ application is [`DDPMScheduler`].
40
+ env ():
41
+ An environment following the OpenAI gym API to act in. For now only Hopper has pretrained models.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ value_function: UNet1DModel,
47
+ unet: UNet1DModel,
48
+ scheduler: DDPMScheduler,
49
+ env,
50
+ ):
51
+ super().__init__()
52
+
53
+ self.register_modules(value_function=value_function, unet=unet, scheduler=scheduler, env=env)
54
+
55
+ self.data = env.get_dataset()
56
+ self.means = {}
57
+ for key in self.data.keys():
58
+ try:
59
+ self.means[key] = self.data[key].mean()
60
+ except: # noqa: E722
61
+ pass
62
+ self.stds = {}
63
+ for key in self.data.keys():
64
+ try:
65
+ self.stds[key] = self.data[key].std()
66
+ except: # noqa: E722
67
+ pass
68
+ self.state_dim = env.observation_space.shape[0]
69
+ self.action_dim = env.action_space.shape[0]
70
+
71
+ def normalize(self, x_in, key):
72
+ return (x_in - self.means[key]) / self.stds[key]
73
+
74
+ def de_normalize(self, x_in, key):
75
+ return x_in * self.stds[key] + self.means[key]
76
+
77
+ def to_torch(self, x_in):
78
+ if isinstance(x_in, dict):
79
+ return {k: self.to_torch(v) for k, v in x_in.items()}
80
+ elif torch.is_tensor(x_in):
81
+ return x_in.to(self.unet.device)
82
+ return torch.tensor(x_in, device=self.unet.device)
83
+
84
+ def reset_x0(self, x_in, cond, act_dim):
85
+ for key, val in cond.items():
86
+ x_in[:, key, act_dim:] = val.clone()
87
+ return x_in
88
+
89
+ def run_diffusion(self, x, conditions, n_guide_steps, scale):
90
+ batch_size = x.shape[0]
91
+ y = None
92
+ for i in tqdm.tqdm(self.scheduler.timesteps):
93
+ # create batch of timesteps to pass into model
94
+ timesteps = torch.full((batch_size,), i, device=self.unet.device, dtype=torch.long)
95
+ for _ in range(n_guide_steps):
96
+ with torch.enable_grad():
97
+ x.requires_grad_()
98
+
99
+ # permute to match dimension for pre-trained models
100
+ y = self.value_function(x.permute(0, 2, 1), timesteps).sample
101
+ grad = torch.autograd.grad([y.sum()], [x])[0]
102
+
103
+ posterior_variance = self.scheduler._get_variance(i)
104
+ model_std = torch.exp(0.5 * posterior_variance)
105
+ grad = model_std * grad
106
+
107
+ grad[timesteps < 2] = 0
108
+ x = x.detach()
109
+ x = x + scale * grad
110
+ x = self.reset_x0(x, conditions, self.action_dim)
111
+
112
+ prev_x = self.unet(x.permute(0, 2, 1), timesteps).sample.permute(0, 2, 1)
113
+
114
+ # TODO: verify deprecation of this kwarg
115
+ x = self.scheduler.step(prev_x, i, x)["prev_sample"]
116
+
117
+ # apply conditions to the trajectory (set the initial state)
118
+ x = self.reset_x0(x, conditions, self.action_dim)
119
+ x = self.to_torch(x)
120
+ return x, y
121
+
122
+ def __call__(self, obs, batch_size=64, planning_horizon=32, n_guide_steps=2, scale=0.1):
123
+ # normalize the observations and create batch dimension
124
+ obs = self.normalize(obs, "observations")
125
+ obs = obs[None].repeat(batch_size, axis=0)
126
+
127
+ conditions = {0: self.to_torch(obs)}
128
+ shape = (batch_size, planning_horizon, self.state_dim + self.action_dim)
129
+
130
+ # generate initial noise and apply our conditions (to make the trajectories start at current state)
131
+ x1 = randn_tensor(shape, device=self.unet.device)
132
+ x = self.reset_x0(x1, conditions, self.action_dim)
133
+ x = self.to_torch(x)
134
+
135
+ # run the diffusion process
136
+ x, y = self.run_diffusion(x, conditions, n_guide_steps, scale)
137
+
138
+ # sort output trajectories by value
139
+ sorted_idx = y.argsort(0, descending=True).squeeze()
140
+ sorted_values = x[sorted_idx]
141
+ actions = sorted_values[:, :, : self.action_dim]
142
+ actions = actions.detach().cpu().numpy()
143
+ denorm_actions = self.de_normalize(actions, key="actions")
144
+
145
+ # select the action with the highest value
146
+ if y is not None:
147
+ selected_index = 0
148
+ else:
149
+ # if we didn't run value guiding, select a random action
150
+ selected_index = np.random.randint(0, batch_size)
151
+
152
+ denorm_actions = denorm_actions[selected_index, 0]
153
+ return denorm_actions
vendor/diffusers/guiders/__init__.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ from typing import Union
16
+
17
+ from ..utils import is_torch_available, logging
18
+
19
+
20
+ if is_torch_available():
21
+ from .adaptive_projected_guidance import AdaptiveProjectedGuidance
22
+ from .adaptive_projected_guidance_mix import AdaptiveProjectedMixGuidance
23
+ from .auto_guidance import AutoGuidance
24
+ from .classifier_free_guidance import ClassifierFreeGuidance
25
+ from .classifier_free_zero_star_guidance import ClassifierFreeZeroStarGuidance
26
+ from .frequency_decoupled_guidance import FrequencyDecoupledGuidance
27
+ from .guider_utils import BaseGuidance
28
+ from .magnitude_aware_guidance import MagnitudeAwareGuidance
29
+ from .perturbed_attention_guidance import PerturbedAttentionGuidance
30
+ from .skip_layer_guidance import SkipLayerGuidance
31
+ from .smoothed_energy_guidance import SmoothedEnergyGuidance
32
+ from .tangential_classifier_free_guidance import TangentialClassifierFreeGuidance
vendor/diffusers/guiders/adaptive_projected_guidance.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from ..modular_pipelines.modular_pipeline import BlockState
26
+
27
+
28
+ class AdaptiveProjectedGuidance(BaseGuidance):
29
+ """
30
+ Adaptive Projected Guidance (APG): https://huggingface.co/papers/2410.02416
31
+
32
+ Args:
33
+ guidance_scale (`float`, defaults to `7.5`):
34
+ The scale parameter for classifier-free guidance. Higher values result in stronger conditioning on the text
35
+ prompt, while lower values allow for more freedom in generation. Higher values may lead to saturation and
36
+ deterioration of image quality.
37
+ adaptive_projected_guidance_momentum (`float`, defaults to `None`):
38
+ The momentum parameter for the adaptive projected guidance. Disabled if set to `None`.
39
+ adaptive_projected_guidance_rescale (`float`, defaults to `15.0`):
40
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
41
+ guidance_rescale (`float`, defaults to `0.0`):
42
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
43
+ overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
44
+ Flawed](https://huggingface.co/papers/2305.08891).
45
+ use_original_formulation (`bool`, defaults to `False`):
46
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
47
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
48
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
49
+ start (`float`, defaults to `0.0`):
50
+ The fraction of the total number of denoising steps after which guidance starts.
51
+ stop (`float`, defaults to `1.0`):
52
+ The fraction of the total number of denoising steps after which guidance stops.
53
+ """
54
+
55
+ _input_predictions = ["pred_cond", "pred_uncond"]
56
+
57
+ @register_to_config
58
+ def __init__(
59
+ self,
60
+ guidance_scale: float = 7.5,
61
+ adaptive_projected_guidance_momentum: Optional[float] = None,
62
+ adaptive_projected_guidance_rescale: float = 15.0,
63
+ eta: float = 1.0,
64
+ guidance_rescale: float = 0.0,
65
+ use_original_formulation: bool = False,
66
+ start: float = 0.0,
67
+ stop: float = 1.0,
68
+ enabled: bool = True,
69
+ ):
70
+ super().__init__(start, stop, enabled)
71
+
72
+ self.guidance_scale = guidance_scale
73
+ self.adaptive_projected_guidance_momentum = adaptive_projected_guidance_momentum
74
+ self.adaptive_projected_guidance_rescale = adaptive_projected_guidance_rescale
75
+ self.eta = eta
76
+ self.guidance_rescale = guidance_rescale
77
+ self.use_original_formulation = use_original_formulation
78
+ self.momentum_buffer = None
79
+
80
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
81
+ if self._step == 0:
82
+ if self.adaptive_projected_guidance_momentum is not None:
83
+ self.momentum_buffer = MomentumBuffer(self.adaptive_projected_guidance_momentum)
84
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
85
+ data_batches = []
86
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
87
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
88
+ data_batches.append(data_batch)
89
+ return data_batches
90
+
91
+ def prepare_inputs_from_block_state(
92
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
93
+ ) -> List["BlockState"]:
94
+ if self._step == 0:
95
+ if self.adaptive_projected_guidance_momentum is not None:
96
+ self.momentum_buffer = MomentumBuffer(self.adaptive_projected_guidance_momentum)
97
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
98
+ data_batches = []
99
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
100
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
101
+ data_batches.append(data_batch)
102
+ return data_batches
103
+
104
+ def forward(self, pred_cond: torch.Tensor, pred_uncond: Optional[torch.Tensor] = None) -> GuiderOutput:
105
+ pred = None
106
+
107
+ if not self._is_apg_enabled():
108
+ pred = pred_cond
109
+ else:
110
+ pred = normalized_guidance(
111
+ pred_cond,
112
+ pred_uncond,
113
+ self.guidance_scale,
114
+ self.momentum_buffer,
115
+ self.eta,
116
+ self.adaptive_projected_guidance_rescale,
117
+ self.use_original_formulation,
118
+ )
119
+
120
+ if self.guidance_rescale > 0.0:
121
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
122
+
123
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
124
+
125
+ @property
126
+ def is_conditional(self) -> bool:
127
+ return self._count_prepared == 1
128
+
129
+ @property
130
+ def num_conditions(self) -> int:
131
+ num_conditions = 1
132
+ if self._is_apg_enabled():
133
+ num_conditions += 1
134
+ return num_conditions
135
+
136
+ def _is_apg_enabled(self) -> bool:
137
+ if not self._enabled:
138
+ return False
139
+
140
+ is_within_range = True
141
+ if self._num_inference_steps is not None:
142
+ skip_start_step = int(self._start * self._num_inference_steps)
143
+ skip_stop_step = int(self._stop * self._num_inference_steps)
144
+ is_within_range = skip_start_step <= self._step < skip_stop_step
145
+
146
+ is_close = False
147
+ if self.use_original_formulation:
148
+ is_close = math.isclose(self.guidance_scale, 0.0)
149
+ else:
150
+ is_close = math.isclose(self.guidance_scale, 1.0)
151
+
152
+ return is_within_range and not is_close
153
+
154
+
155
+ class MomentumBuffer:
156
+ def __init__(self, momentum: float):
157
+ self.momentum = momentum
158
+ self.running_average = 0
159
+
160
+ def update(self, update_value: torch.Tensor):
161
+ new_average = self.momentum * self.running_average
162
+ self.running_average = update_value + new_average
163
+
164
+ def __repr__(self) -> str:
165
+ """
166
+ Returns a string representation showing momentum, shape, statistics, and a slice of the running_average.
167
+ """
168
+ if isinstance(self.running_average, torch.Tensor):
169
+ shape = tuple(self.running_average.shape)
170
+
171
+ # Calculate statistics
172
+ with torch.no_grad():
173
+ stats = {
174
+ "mean": self.running_average.mean().item(),
175
+ "std": self.running_average.std().item(),
176
+ "min": self.running_average.min().item(),
177
+ "max": self.running_average.max().item(),
178
+ }
179
+
180
+ # Get a slice (max 3 elements per dimension)
181
+ slice_indices = tuple(slice(None, min(3, dim)) for dim in shape)
182
+ sliced_data = self.running_average[slice_indices]
183
+
184
+ # Format the slice for display (convert to float32 for numpy compatibility with bfloat16)
185
+ slice_str = str(sliced_data.detach().float().cpu().numpy())
186
+ if len(slice_str) > 200: # Truncate if too long
187
+ slice_str = slice_str[:200] + "..."
188
+
189
+ stats_str = ", ".join([f"{k}={v:.4f}" for k, v in stats.items()])
190
+
191
+ return (
192
+ f"MomentumBuffer(\n"
193
+ f" momentum={self.momentum},\n"
194
+ f" shape={shape},\n"
195
+ f" stats=[{stats_str}],\n"
196
+ f" slice={slice_str}\n"
197
+ f")"
198
+ )
199
+ else:
200
+ return f"MomentumBuffer(momentum={self.momentum}, running_average={self.running_average})"
201
+
202
+
203
+ def normalized_guidance(
204
+ pred_cond: torch.Tensor,
205
+ pred_uncond: torch.Tensor,
206
+ guidance_scale: float,
207
+ momentum_buffer: Optional[MomentumBuffer] = None,
208
+ eta: float = 1.0,
209
+ norm_threshold: float = 0.0,
210
+ use_original_formulation: bool = False,
211
+ ):
212
+ diff = pred_cond - pred_uncond
213
+ dim = [-i for i in range(1, len(diff.shape))]
214
+
215
+ if momentum_buffer is not None:
216
+ momentum_buffer.update(diff)
217
+ diff = momentum_buffer.running_average
218
+
219
+ if norm_threshold > 0:
220
+ ones = torch.ones_like(diff)
221
+ diff_norm = diff.norm(p=2, dim=dim, keepdim=True)
222
+ scale_factor = torch.minimum(ones, norm_threshold / diff_norm)
223
+ diff = diff * scale_factor
224
+
225
+ v0, v1 = diff.double(), pred_cond.double()
226
+ v1 = torch.nn.functional.normalize(v1, dim=dim)
227
+ v0_parallel = (v0 * v1).sum(dim=dim, keepdim=True) * v1
228
+ v0_orthogonal = v0 - v0_parallel
229
+ diff_parallel, diff_orthogonal = v0_parallel.type_as(diff), v0_orthogonal.type_as(diff)
230
+ normalized_update = diff_orthogonal + eta * diff_parallel
231
+
232
+ pred = pred_cond if use_original_formulation else pred_uncond
233
+ pred = pred + guidance_scale * normalized_update
234
+
235
+ return pred
vendor/diffusers/guiders/adaptive_projected_guidance_mix.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from ..modular_pipelines.modular_pipeline import BlockState
26
+
27
+
28
+ class AdaptiveProjectedMixGuidance(BaseGuidance):
29
+ """
30
+ Adaptive Projected Guidance (APG) https://huggingface.co/papers/2410.02416 combined with Classifier-Free Guidance
31
+ (CFG). This guider is used in HunyuanImage2.1 https://github.com/Tencent-Hunyuan/HunyuanImage-2.1
32
+
33
+ Args:
34
+ guidance_scale (`float`, defaults to `7.5`):
35
+ The scale parameter for classifier-free guidance. Higher values result in stronger conditioning on the text
36
+ prompt, while lower values allow for more freedom in generation. Higher values may lead to saturation and
37
+ deterioration of image quality.
38
+ adaptive_projected_guidance_momentum (`float`, defaults to `None`):
39
+ The momentum parameter for the adaptive projected guidance. Disabled if set to `None`.
40
+ adaptive_projected_guidance_rescale (`float`, defaults to `15.0`):
41
+ The rescale factor applied to the noise predictions for adaptive projected guidance. This is used to
42
+ improve image quality and fix
43
+ guidance_rescale (`float`, defaults to `0.0`):
44
+ The rescale factor applied to the noise predictions for classifier-free guidance. This is used to improve
45
+ image quality and fix overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample
46
+ Steps are Flawed](https://huggingface.co/papers/2305.08891).
47
+ use_original_formulation (`bool`, defaults to `False`):
48
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
49
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
50
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
51
+ start (`float`, defaults to `0.0`):
52
+ The fraction of the total number of denoising steps after which the classifier-free guidance starts.
53
+ stop (`float`, defaults to `1.0`):
54
+ The fraction of the total number of denoising steps after which the classifier-free guidance stops.
55
+ adaptive_projected_guidance_start_step (`int`, defaults to `5`):
56
+ The step at which the adaptive projected guidance starts (before this step, classifier-free guidance is
57
+ used, and momentum buffer is updated).
58
+ enabled (`bool`, defaults to `True`):
59
+ Whether this guidance is enabled.
60
+ """
61
+
62
+ _input_predictions = ["pred_cond", "pred_uncond"]
63
+
64
+ @register_to_config
65
+ def __init__(
66
+ self,
67
+ guidance_scale: float = 3.5,
68
+ guidance_rescale: float = 0.0,
69
+ adaptive_projected_guidance_scale: float = 10.0,
70
+ adaptive_projected_guidance_momentum: float = -0.5,
71
+ adaptive_projected_guidance_rescale: float = 10.0,
72
+ eta: float = 0.0,
73
+ use_original_formulation: bool = False,
74
+ start: float = 0.0,
75
+ stop: float = 1.0,
76
+ adaptive_projected_guidance_start_step: int = 5,
77
+ enabled: bool = True,
78
+ ):
79
+ super().__init__(start, stop, enabled)
80
+
81
+ self.guidance_scale = guidance_scale
82
+ self.guidance_rescale = guidance_rescale
83
+ self.adaptive_projected_guidance_scale = adaptive_projected_guidance_scale
84
+ self.adaptive_projected_guidance_momentum = adaptive_projected_guidance_momentum
85
+ self.adaptive_projected_guidance_rescale = adaptive_projected_guidance_rescale
86
+ self.eta = eta
87
+ self.adaptive_projected_guidance_start_step = adaptive_projected_guidance_start_step
88
+ self.use_original_formulation = use_original_formulation
89
+ self.momentum_buffer = None
90
+
91
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
92
+ if self._step == 0:
93
+ if self.adaptive_projected_guidance_momentum is not None:
94
+ self.momentum_buffer = MomentumBuffer(self.adaptive_projected_guidance_momentum)
95
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
96
+ data_batches = []
97
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
98
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
99
+ data_batches.append(data_batch)
100
+ return data_batches
101
+
102
+ def prepare_inputs_from_block_state(
103
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
104
+ ) -> List["BlockState"]:
105
+ if self._step == 0:
106
+ if self.adaptive_projected_guidance_momentum is not None:
107
+ self.momentum_buffer = MomentumBuffer(self.adaptive_projected_guidance_momentum)
108
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
109
+ data_batches = []
110
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
111
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
112
+ data_batches.append(data_batch)
113
+ return data_batches
114
+
115
+ def forward(self, pred_cond: torch.Tensor, pred_uncond: Optional[torch.Tensor] = None) -> GuiderOutput:
116
+ pred = None
117
+
118
+ # no guidance
119
+ if not self._is_cfg_enabled():
120
+ pred = pred_cond
121
+
122
+ # CFG + update momentum buffer
123
+ elif not self._is_apg_enabled():
124
+ if self.momentum_buffer is not None:
125
+ update_momentum_buffer(pred_cond, pred_uncond, self.momentum_buffer)
126
+ # CFG + update momentum buffer
127
+ shift = pred_cond - pred_uncond
128
+ pred = pred_cond if self.use_original_formulation else pred_uncond
129
+ pred = pred + self.guidance_scale * shift
130
+
131
+ # APG
132
+ elif self._is_apg_enabled():
133
+ pred = normalized_guidance(
134
+ pred_cond,
135
+ pred_uncond,
136
+ self.adaptive_projected_guidance_scale,
137
+ self.momentum_buffer,
138
+ self.eta,
139
+ self.adaptive_projected_guidance_rescale,
140
+ self.use_original_formulation,
141
+ )
142
+
143
+ if self.guidance_rescale > 0.0:
144
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
145
+
146
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
147
+
148
+ @property
149
+ def is_conditional(self) -> bool:
150
+ return self._count_prepared == 1
151
+
152
+ @property
153
+ def num_conditions(self) -> int:
154
+ num_conditions = 1
155
+ if self._is_apg_enabled() or self._is_cfg_enabled():
156
+ num_conditions += 1
157
+ return num_conditions
158
+
159
+ # Copied from diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance._is_cfg_enabled
160
+ def _is_cfg_enabled(self) -> bool:
161
+ if not self._enabled:
162
+ return False
163
+
164
+ is_within_range = True
165
+ if self._num_inference_steps is not None:
166
+ skip_start_step = int(self._start * self._num_inference_steps)
167
+ skip_stop_step = int(self._stop * self._num_inference_steps)
168
+ is_within_range = skip_start_step <= self._step < skip_stop_step
169
+
170
+ is_close = False
171
+ if self.use_original_formulation:
172
+ is_close = math.isclose(self.guidance_scale, 0.0)
173
+ else:
174
+ is_close = math.isclose(self.guidance_scale, 1.0)
175
+
176
+ return is_within_range and not is_close
177
+
178
+ def _is_apg_enabled(self) -> bool:
179
+ if not self._enabled:
180
+ return False
181
+
182
+ if not self._is_cfg_enabled():
183
+ return False
184
+
185
+ is_within_range = False
186
+ if self._step is not None:
187
+ is_within_range = self._step > self.adaptive_projected_guidance_start_step
188
+
189
+ is_close = False
190
+ if self.use_original_formulation:
191
+ is_close = math.isclose(self.adaptive_projected_guidance_scale, 0.0)
192
+ else:
193
+ is_close = math.isclose(self.adaptive_projected_guidance_scale, 1.0)
194
+
195
+ return is_within_range and not is_close
196
+
197
+ def get_state(self):
198
+ state = super().get_state()
199
+ state["momentum_buffer"] = self.momentum_buffer
200
+ state["is_apg_enabled"] = self._is_apg_enabled()
201
+ state["is_cfg_enabled"] = self._is_cfg_enabled()
202
+ return state
203
+
204
+
205
+ # Copied from diffusers.guiders.adaptive_projected_guidance.MomentumBuffer
206
+ class MomentumBuffer:
207
+ def __init__(self, momentum: float):
208
+ self.momentum = momentum
209
+ self.running_average = 0
210
+
211
+ def update(self, update_value: torch.Tensor):
212
+ new_average = self.momentum * self.running_average
213
+ self.running_average = update_value + new_average
214
+
215
+ def __repr__(self) -> str:
216
+ """
217
+ Returns a string representation showing momentum, shape, statistics, and a slice of the running_average.
218
+ """
219
+ if isinstance(self.running_average, torch.Tensor):
220
+ shape = tuple(self.running_average.shape)
221
+
222
+ # Calculate statistics
223
+ with torch.no_grad():
224
+ stats = {
225
+ "mean": self.running_average.mean().item(),
226
+ "std": self.running_average.std().item(),
227
+ "min": self.running_average.min().item(),
228
+ "max": self.running_average.max().item(),
229
+ }
230
+
231
+ # Get a slice (max 3 elements per dimension)
232
+ slice_indices = tuple(slice(None, min(3, dim)) for dim in shape)
233
+ sliced_data = self.running_average[slice_indices]
234
+
235
+ # Format the slice for display (convert to float32 for numpy compatibility with bfloat16)
236
+ slice_str = str(sliced_data.detach().float().cpu().numpy())
237
+ if len(slice_str) > 200: # Truncate if too long
238
+ slice_str = slice_str[:200] + "..."
239
+
240
+ stats_str = ", ".join([f"{k}={v:.4f}" for k, v in stats.items()])
241
+
242
+ return (
243
+ f"MomentumBuffer(\n"
244
+ f" momentum={self.momentum},\n"
245
+ f" shape={shape},\n"
246
+ f" stats=[{stats_str}],\n"
247
+ f" slice={slice_str}\n"
248
+ f")"
249
+ )
250
+ else:
251
+ return f"MomentumBuffer(momentum={self.momentum}, running_average={self.running_average})"
252
+
253
+
254
+ def update_momentum_buffer(
255
+ pred_cond: torch.Tensor,
256
+ pred_uncond: torch.Tensor,
257
+ momentum_buffer: Optional[MomentumBuffer] = None,
258
+ ):
259
+ diff = pred_cond - pred_uncond
260
+ if momentum_buffer is not None:
261
+ momentum_buffer.update(diff)
262
+
263
+
264
+ def normalized_guidance(
265
+ pred_cond: torch.Tensor,
266
+ pred_uncond: torch.Tensor,
267
+ guidance_scale: float,
268
+ momentum_buffer: Optional[MomentumBuffer] = None,
269
+ eta: float = 1.0,
270
+ norm_threshold: float = 0.0,
271
+ use_original_formulation: bool = False,
272
+ ):
273
+ if momentum_buffer is not None:
274
+ update_momentum_buffer(pred_cond, pred_uncond, momentum_buffer)
275
+ diff = momentum_buffer.running_average
276
+ else:
277
+ diff = pred_cond - pred_uncond
278
+
279
+ dim = [-i for i in range(1, len(diff.shape))]
280
+
281
+ if norm_threshold > 0:
282
+ ones = torch.ones_like(diff)
283
+ diff_norm = diff.norm(p=2, dim=dim, keepdim=True)
284
+ scale_factor = torch.minimum(ones, norm_threshold / diff_norm)
285
+ diff = diff * scale_factor
286
+
287
+ v0, v1 = diff.double(), pred_cond.double()
288
+ v1 = torch.nn.functional.normalize(v1, dim=dim)
289
+ v0_parallel = (v0 * v1).sum(dim=dim, keepdim=True) * v1
290
+ v0_orthogonal = v0 - v0_parallel
291
+ diff_parallel, diff_orthogonal = v0_parallel.type_as(diff), v0_orthogonal.type_as(diff)
292
+ normalized_update = diff_orthogonal + eta * diff_parallel
293
+
294
+ pred = pred_cond if use_original_formulation else pred_uncond
295
+ pred = pred + guidance_scale * normalized_update
296
+
297
+ return pred
vendor/diffusers/guiders/auto_guidance.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from ..hooks import HookRegistry, LayerSkipConfig
22
+ from ..hooks.layer_skip import _apply_layer_skip_hook
23
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
24
+
25
+
26
+ if TYPE_CHECKING:
27
+ from ..modular_pipelines.modular_pipeline import BlockState
28
+
29
+
30
+ class AutoGuidance(BaseGuidance):
31
+ """
32
+ AutoGuidance: https://huggingface.co/papers/2406.02507
33
+
34
+ Args:
35
+ guidance_scale (`float`, defaults to `7.5`):
36
+ The scale parameter for classifier-free guidance. Higher values result in stronger conditioning on the text
37
+ prompt, while lower values allow for more freedom in generation. Higher values may lead to saturation and
38
+ deterioration of image quality.
39
+ auto_guidance_layers (`int` or `List[int]`, *optional*):
40
+ The layer indices to apply skip layer guidance to. Can be a single integer or a list of integers. If not
41
+ provided, `skip_layer_config` must be provided.
42
+ auto_guidance_config (`LayerSkipConfig` or `List[LayerSkipConfig]`, *optional*):
43
+ The configuration for the skip layer guidance. Can be a single `LayerSkipConfig` or a list of
44
+ `LayerSkipConfig`. If not provided, `skip_layer_guidance_layers` must be provided.
45
+ dropout (`float`, *optional*):
46
+ The dropout probability for autoguidance on the enabled skip layers (either with `auto_guidance_layers` or
47
+ `auto_guidance_config`). If not provided, the dropout probability will be set to 1.0.
48
+ guidance_rescale (`float`, defaults to `0.0`):
49
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
50
+ overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
51
+ Flawed](https://huggingface.co/papers/2305.08891).
52
+ use_original_formulation (`bool`, defaults to `False`):
53
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
54
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
55
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
56
+ start (`float`, defaults to `0.0`):
57
+ The fraction of the total number of denoising steps after which guidance starts.
58
+ stop (`float`, defaults to `1.0`):
59
+ The fraction of the total number of denoising steps after which guidance stops.
60
+ """
61
+
62
+ _input_predictions = ["pred_cond", "pred_uncond"]
63
+
64
+ @register_to_config
65
+ def __init__(
66
+ self,
67
+ guidance_scale: float = 7.5,
68
+ auto_guidance_layers: Optional[Union[int, List[int]]] = None,
69
+ auto_guidance_config: Union[LayerSkipConfig, List[LayerSkipConfig], Dict[str, Any]] = None,
70
+ dropout: Optional[float] = None,
71
+ guidance_rescale: float = 0.0,
72
+ use_original_formulation: bool = False,
73
+ start: float = 0.0,
74
+ stop: float = 1.0,
75
+ enabled: bool = True,
76
+ ):
77
+ super().__init__(start, stop, enabled)
78
+
79
+ self.guidance_scale = guidance_scale
80
+ self.auto_guidance_layers = auto_guidance_layers
81
+ self.auto_guidance_config = auto_guidance_config
82
+ self.dropout = dropout
83
+ self.guidance_rescale = guidance_rescale
84
+ self.use_original_formulation = use_original_formulation
85
+
86
+ is_layer_or_config_provided = auto_guidance_layers is not None or auto_guidance_config is not None
87
+ is_layer_and_config_provided = auto_guidance_layers is not None and auto_guidance_config is not None
88
+ if not is_layer_or_config_provided:
89
+ raise ValueError(
90
+ "Either `auto_guidance_layers` or `auto_guidance_config` must be provided to enable AutoGuidance."
91
+ )
92
+ if is_layer_and_config_provided:
93
+ raise ValueError("Only one of `auto_guidance_layers` or `auto_guidance_config` can be provided.")
94
+ if auto_guidance_config is None and dropout is None:
95
+ raise ValueError("`dropout` must be provided if `auto_guidance_layers` is provided.")
96
+
97
+ if auto_guidance_layers is not None:
98
+ if isinstance(auto_guidance_layers, int):
99
+ auto_guidance_layers = [auto_guidance_layers]
100
+ if not isinstance(auto_guidance_layers, list):
101
+ raise ValueError(
102
+ f"Expected `auto_guidance_layers` to be an int or a list of ints, but got {type(auto_guidance_layers)}."
103
+ )
104
+ auto_guidance_config = [
105
+ LayerSkipConfig(layer, fqn="auto", dropout=dropout) for layer in auto_guidance_layers
106
+ ]
107
+
108
+ if isinstance(auto_guidance_config, dict):
109
+ auto_guidance_config = LayerSkipConfig.from_dict(auto_guidance_config)
110
+
111
+ if isinstance(auto_guidance_config, LayerSkipConfig):
112
+ auto_guidance_config = [auto_guidance_config]
113
+
114
+ if not isinstance(auto_guidance_config, list):
115
+ raise ValueError(
116
+ f"Expected `auto_guidance_config` to be a LayerSkipConfig or a list of LayerSkipConfig, but got {type(auto_guidance_config)}."
117
+ )
118
+ elif isinstance(next(iter(auto_guidance_config), None), dict):
119
+ auto_guidance_config = [LayerSkipConfig.from_dict(config) for config in auto_guidance_config]
120
+
121
+ self.auto_guidance_config = auto_guidance_config
122
+ self._auto_guidance_hook_names = [f"AutoGuidance_{i}" for i in range(len(self.auto_guidance_config))]
123
+
124
+ def prepare_models(self, denoiser: torch.nn.Module) -> None:
125
+ self._count_prepared += 1
126
+ if self._is_ag_enabled() and self.is_unconditional:
127
+ for name, config in zip(self._auto_guidance_hook_names, self.auto_guidance_config):
128
+ _apply_layer_skip_hook(denoiser, config, name=name)
129
+
130
+ def cleanup_models(self, denoiser: torch.nn.Module) -> None:
131
+ if self._is_ag_enabled() and self.is_unconditional:
132
+ for name in self._auto_guidance_hook_names:
133
+ registry = HookRegistry.check_if_exists_or_initialize(denoiser)
134
+ registry.remove_hook(name, recurse=True)
135
+
136
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
137
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
138
+ data_batches = []
139
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
140
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
141
+ data_batches.append(data_batch)
142
+ return data_batches
143
+
144
+ def prepare_inputs_from_block_state(
145
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
146
+ ) -> List["BlockState"]:
147
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
148
+ data_batches = []
149
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
150
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
151
+ data_batches.append(data_batch)
152
+ return data_batches
153
+
154
+ def forward(self, pred_cond: torch.Tensor, pred_uncond: Optional[torch.Tensor] = None) -> GuiderOutput:
155
+ pred = None
156
+
157
+ if not self._is_ag_enabled():
158
+ pred = pred_cond
159
+ else:
160
+ shift = pred_cond - pred_uncond
161
+ pred = pred_cond if self.use_original_formulation else pred_uncond
162
+ pred = pred + self.guidance_scale * shift
163
+
164
+ if self.guidance_rescale > 0.0:
165
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
166
+
167
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
168
+
169
+ @property
170
+ def is_conditional(self) -> bool:
171
+ return self._count_prepared == 1
172
+
173
+ @property
174
+ def num_conditions(self) -> int:
175
+ num_conditions = 1
176
+ if self._is_ag_enabled():
177
+ num_conditions += 1
178
+ return num_conditions
179
+
180
+ def _is_ag_enabled(self) -> bool:
181
+ if not self._enabled:
182
+ return False
183
+
184
+ is_within_range = True
185
+ if self._num_inference_steps is not None:
186
+ skip_start_step = int(self._start * self._num_inference_steps)
187
+ skip_stop_step = int(self._stop * self._num_inference_steps)
188
+ is_within_range = skip_start_step <= self._step < skip_stop_step
189
+
190
+ is_close = False
191
+ if self.use_original_formulation:
192
+ is_close = math.isclose(self.guidance_scale, 0.0)
193
+ else:
194
+ is_close = math.isclose(self.guidance_scale, 1.0)
195
+
196
+ return is_within_range and not is_close
vendor/diffusers/guiders/classifier_free_guidance.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from ..modular_pipelines.modular_pipeline import BlockState
26
+
27
+
28
+ class ClassifierFreeGuidance(BaseGuidance):
29
+ """
30
+ Implements Classifier-Free Guidance (CFG) for diffusion models.
31
+
32
+ Reference: https://huggingface.co/papers/2207.12598
33
+
34
+ CFG improves generation quality and prompt adherence by jointly training models on both conditional and
35
+ unconditional data, then combining predictions during inference. This allows trading off between quality (high
36
+ guidance) and diversity (low guidance).
37
+
38
+ **Two CFG Formulations:**
39
+
40
+ 1. **Original formulation** (from paper):
41
+ ```
42
+ x_pred = x_cond + guidance_scale * (x_cond - x_uncond)
43
+ ```
44
+ Moves conditional predictions further from unconditional ones.
45
+
46
+ 2. **Diffusers-native formulation** (default, from Imagen paper):
47
+ ```
48
+ x_pred = x_uncond + guidance_scale * (x_cond - x_uncond)
49
+ ```
50
+ Moves unconditional predictions toward conditional ones, effectively suppressing negative features (e.g., "bad
51
+ quality", "watermarks"). Equivalent in theory but more intuitive.
52
+
53
+ Use `use_original_formulation=True` to switch to the original formulation.
54
+
55
+ Args:
56
+ guidance_scale (`float`, defaults to `7.5`):
57
+ CFG scale applied by this guider during post-processing. Higher values = stronger prompt conditioning but
58
+ may reduce quality. Typical range: 1.0-20.0.
59
+ guidance_rescale (`float`, defaults to `0.0`):
60
+ Rescaling factor to prevent overexposure from high guidance scales. Based on [Common Diffusion Noise
61
+ Schedules and Sample Steps are Flawed](https://huggingface.co/papers/2305.08891). Range: 0.0 (no rescaling)
62
+ to 1.0 (full rescaling).
63
+ use_original_formulation (`bool`, defaults to `False`):
64
+ If `True`, uses the original CFG formulation from the paper. If `False` (default), uses the
65
+ diffusers-native formulation from the Imagen paper.
66
+ start (`float`, defaults to `0.0`):
67
+ Fraction of denoising steps (0.0-1.0) after which CFG starts. Use > 0.0 to disable CFG in early denoising
68
+ steps.
69
+ stop (`float`, defaults to `1.0`):
70
+ Fraction of denoising steps (0.0-1.0) after which CFG stops. Use < 1.0 to disable CFG in late denoising
71
+ steps.
72
+ enabled (`bool`, defaults to `True`):
73
+ Whether CFG is enabled. Set to `False` to disable CFG entirely (uses only conditional predictions).
74
+ """
75
+
76
+ _input_predictions = ["pred_cond", "pred_uncond"]
77
+
78
+ @register_to_config
79
+ def __init__(
80
+ self,
81
+ guidance_scale: float = 7.5,
82
+ guidance_rescale: float = 0.0,
83
+ use_original_formulation: bool = False,
84
+ start: float = 0.0,
85
+ stop: float = 1.0,
86
+ enabled: bool = True,
87
+ ):
88
+ super().__init__(start, stop, enabled)
89
+
90
+ self.guidance_scale = guidance_scale
91
+ self.guidance_rescale = guidance_rescale
92
+ self.use_original_formulation = use_original_formulation
93
+
94
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
95
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
96
+ data_batches = []
97
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
98
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
99
+ data_batches.append(data_batch)
100
+ return data_batches
101
+
102
+ def prepare_inputs_from_block_state(
103
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
104
+ ) -> List["BlockState"]:
105
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
106
+ data_batches = []
107
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
108
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
109
+ data_batches.append(data_batch)
110
+ return data_batches
111
+
112
+ def forward(self, pred_cond: torch.Tensor, pred_uncond: Optional[torch.Tensor] = None) -> GuiderOutput:
113
+ pred = None
114
+
115
+ if not self._is_cfg_enabled():
116
+ pred = pred_cond
117
+ else:
118
+ shift = pred_cond - pred_uncond
119
+ pred = pred_cond if self.use_original_formulation else pred_uncond
120
+ pred = pred + self.guidance_scale * shift
121
+
122
+ if self.guidance_rescale > 0.0:
123
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
124
+
125
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
126
+
127
+ @property
128
+ def is_conditional(self) -> bool:
129
+ return self._count_prepared == 1
130
+
131
+ @property
132
+ def num_conditions(self) -> int:
133
+ num_conditions = 1
134
+ if self._is_cfg_enabled():
135
+ num_conditions += 1
136
+ return num_conditions
137
+
138
+ def _is_cfg_enabled(self) -> bool:
139
+ if not self._enabled:
140
+ return False
141
+
142
+ is_within_range = True
143
+ if self._num_inference_steps is not None:
144
+ skip_start_step = int(self._start * self._num_inference_steps)
145
+ skip_stop_step = int(self._stop * self._num_inference_steps)
146
+ is_within_range = skip_start_step <= self._step < skip_stop_step
147
+
148
+ is_close = False
149
+ if self.use_original_formulation:
150
+ is_close = math.isclose(self.guidance_scale, 0.0)
151
+ else:
152
+ is_close = math.isclose(self.guidance_scale, 1.0)
153
+
154
+ return is_within_range and not is_close
vendor/diffusers/guiders/classifier_free_zero_star_guidance.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from ..modular_pipelines.modular_pipeline import BlockState
26
+
27
+
28
+ class ClassifierFreeZeroStarGuidance(BaseGuidance):
29
+ """
30
+ Classifier-free Zero* (CFG-Zero*): https://huggingface.co/papers/2503.18886
31
+
32
+ This is an implementation of the Classifier-Free Zero* guidance technique, which is a variant of classifier-free
33
+ guidance. It proposes zero initialization of the noise predictions for the first few steps of the diffusion
34
+ process, and also introduces an optimal rescaling factor for the noise predictions, which can help in improving the
35
+ quality of generated images.
36
+
37
+ The authors of the paper suggest setting zero initialization in the first 4% of the inference steps.
38
+
39
+ Args:
40
+ guidance_scale (`float`, defaults to `7.5`):
41
+ The scale parameter for classifier-free guidance. Higher values result in stronger conditioning on the text
42
+ prompt, while lower values allow for more freedom in generation. Higher values may lead to saturation and
43
+ deterioration of image quality.
44
+ zero_init_steps (`int`, defaults to `1`):
45
+ The number of inference steps for which the noise predictions are zeroed out (see Section 4.2).
46
+ guidance_rescale (`float`, defaults to `0.0`):
47
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
48
+ overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
49
+ Flawed](https://huggingface.co/papers/2305.08891).
50
+ use_original_formulation (`bool`, defaults to `False`):
51
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
52
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
53
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
54
+ start (`float`, defaults to `0.01`):
55
+ The fraction of the total number of denoising steps after which guidance starts.
56
+ stop (`float`, defaults to `0.2`):
57
+ The fraction of the total number of denoising steps after which guidance stops.
58
+ """
59
+
60
+ _input_predictions = ["pred_cond", "pred_uncond"]
61
+
62
+ @register_to_config
63
+ def __init__(
64
+ self,
65
+ guidance_scale: float = 7.5,
66
+ zero_init_steps: int = 1,
67
+ guidance_rescale: float = 0.0,
68
+ use_original_formulation: bool = False,
69
+ start: float = 0.0,
70
+ stop: float = 1.0,
71
+ enabled: bool = True,
72
+ ):
73
+ super().__init__(start, stop, enabled)
74
+
75
+ self.guidance_scale = guidance_scale
76
+ self.zero_init_steps = zero_init_steps
77
+ self.guidance_rescale = guidance_rescale
78
+ self.use_original_formulation = use_original_formulation
79
+
80
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
81
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
82
+ data_batches = []
83
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
84
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
85
+ data_batches.append(data_batch)
86
+ return data_batches
87
+
88
+ def prepare_inputs_from_block_state(
89
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
90
+ ) -> List["BlockState"]:
91
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
92
+ data_batches = []
93
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
94
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
95
+ data_batches.append(data_batch)
96
+ return data_batches
97
+
98
+ def forward(self, pred_cond: torch.Tensor, pred_uncond: Optional[torch.Tensor] = None) -> GuiderOutput:
99
+ pred = None
100
+
101
+ # YiYi Notes: add default behavior for self._enabled == False
102
+ if not self._enabled:
103
+ pred = pred_cond
104
+
105
+ elif self._step < self.zero_init_steps:
106
+ pred = torch.zeros_like(pred_cond)
107
+ elif not self._is_cfg_enabled():
108
+ pred = pred_cond
109
+ else:
110
+ pred_cond_flat = pred_cond.flatten(1)
111
+ pred_uncond_flat = pred_uncond.flatten(1)
112
+ alpha = cfg_zero_star_scale(pred_cond_flat, pred_uncond_flat)
113
+ alpha = alpha.view(-1, *(1,) * (len(pred_cond.shape) - 1))
114
+ pred_uncond = pred_uncond * alpha
115
+ shift = pred_cond - pred_uncond
116
+ pred = pred_cond if self.use_original_formulation else pred_uncond
117
+ pred = pred + self.guidance_scale * shift
118
+
119
+ if self.guidance_rescale > 0.0:
120
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
121
+
122
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
123
+
124
+ @property
125
+ def is_conditional(self) -> bool:
126
+ return self._count_prepared == 1
127
+
128
+ @property
129
+ def num_conditions(self) -> int:
130
+ num_conditions = 1
131
+ if self._is_cfg_enabled():
132
+ num_conditions += 1
133
+ return num_conditions
134
+
135
+ def _is_cfg_enabled(self) -> bool:
136
+ if not self._enabled:
137
+ return False
138
+
139
+ is_within_range = True
140
+ if self._num_inference_steps is not None:
141
+ skip_start_step = int(self._start * self._num_inference_steps)
142
+ skip_stop_step = int(self._stop * self._num_inference_steps)
143
+ is_within_range = skip_start_step <= self._step < skip_stop_step
144
+
145
+ is_close = False
146
+ if self.use_original_formulation:
147
+ is_close = math.isclose(self.guidance_scale, 0.0)
148
+ else:
149
+ is_close = math.isclose(self.guidance_scale, 1.0)
150
+
151
+ return is_within_range and not is_close
152
+
153
+
154
+ def cfg_zero_star_scale(cond: torch.Tensor, uncond: torch.Tensor, eps: float = 1e-8) -> torch.Tensor:
155
+ cond_dtype = cond.dtype
156
+ cond = cond.float()
157
+ uncond = uncond.float()
158
+ dot_product = torch.sum(cond * uncond, dim=1, keepdim=True)
159
+ squared_norm = torch.sum(uncond**2, dim=1, keepdim=True) + eps
160
+ # st_star = v_cond^T * v_uncond / ||v_uncond||^2
161
+ scale = dot_product / squared_norm
162
+ return scale.to(dtype=cond_dtype)
vendor/diffusers/guiders/frequency_decoupled_guidance.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from ..utils import is_kornia_available
22
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from ..modular_pipelines.modular_pipeline import BlockState
27
+
28
+
29
+ _CAN_USE_KORNIA = is_kornia_available()
30
+
31
+
32
+ if _CAN_USE_KORNIA:
33
+ from kornia.geometry import pyrup as upsample_and_blur_func
34
+ from kornia.geometry.transform import build_laplacian_pyramid as build_laplacian_pyramid_func
35
+ else:
36
+ upsample_and_blur_func = None
37
+ build_laplacian_pyramid_func = None
38
+
39
+
40
+ def project(v0: torch.Tensor, v1: torch.Tensor, upcast_to_double: bool = True) -> Tuple[torch.Tensor, torch.Tensor]:
41
+ """
42
+ Project vector v0 onto vector v1, returning the parallel and orthogonal components of v0. Implementation from paper
43
+ (Algorithm 2).
44
+ """
45
+ # v0 shape: [B, ...]
46
+ # v1 shape: [B, ...]
47
+ # Assume first dim is a batch dim and all other dims are channel or "spatial" dims
48
+ all_dims_but_first = list(range(1, len(v0.shape)))
49
+ if upcast_to_double:
50
+ dtype = v0.dtype
51
+ v0, v1 = v0.double(), v1.double()
52
+ v1 = torch.nn.functional.normalize(v1, dim=all_dims_but_first)
53
+ v0_parallel = (v0 * v1).sum(dim=all_dims_but_first, keepdim=True) * v1
54
+ v0_orthogonal = v0 - v0_parallel
55
+ if upcast_to_double:
56
+ v0_parallel = v0_parallel.to(dtype)
57
+ v0_orthogonal = v0_orthogonal.to(dtype)
58
+ return v0_parallel, v0_orthogonal
59
+
60
+
61
+ def build_image_from_pyramid(pyramid: List[torch.Tensor]) -> torch.Tensor:
62
+ """
63
+ Recovers the data space latents from the Laplacian pyramid frequency space. Implementation from the paper
64
+ (Algorithm 2).
65
+ """
66
+ # pyramid shapes: [[B, C, H, W], [B, C, H/2, W/2], ...]
67
+ img = pyramid[-1]
68
+ for i in range(len(pyramid) - 2, -1, -1):
69
+ img = upsample_and_blur_func(img) + pyramid[i]
70
+ return img
71
+
72
+
73
+ class FrequencyDecoupledGuidance(BaseGuidance):
74
+ """
75
+ Frequency-Decoupled Guidance (FDG): https://huggingface.co/papers/2506.19713
76
+
77
+ FDG is a technique similar to (and based on) classifier-free guidance (CFG) which is used to improve generation
78
+ quality and condition-following in diffusion models. Like CFG, during training we jointly train the model on both
79
+ conditional and unconditional data, and use a combination of the two during inference. (If you want more details on
80
+ how CFG works, you can check out the CFG guider.)
81
+
82
+ FDG differs from CFG in that the normal CFG prediction is instead decoupled into low- and high-frequency components
83
+ using a frequency transform (such as a Laplacian pyramid). The CFG update is then performed in frequency space
84
+ separately for the low- and high-frequency components with different guidance scales. Finally, the inverse
85
+ frequency transform is used to map the CFG frequency predictions back to data space (e.g. pixel space for images)
86
+ to form the final FDG prediction.
87
+
88
+ For images, the FDG authors found that using low guidance scales for the low-frequency components retains sample
89
+ diversity and realistic color composition, while using high guidance scales for high-frequency components enhances
90
+ sample quality (such as better visual details). Therefore, they recommend using low guidance scales (low w_low) for
91
+ the low-frequency components and high guidance scales (high w_high) for the high-frequency components. As an
92
+ example, they suggest w_low = 5.0 and w_high = 10.0 for Stable Diffusion XL (see Table 8 in the paper).
93
+
94
+ As with CFG, Diffusers implements the scaling and shifting on the unconditional prediction based on the [Imagen
95
+ paper](https://huggingface.co/papers/2205.11487), which is equivalent to what the original CFG paper proposed in
96
+ theory. [x_pred = x_uncond + scale * (x_cond - x_uncond)]
97
+
98
+ The `use_original_formulation` argument can be set to `True` to use the original CFG formulation mentioned in the
99
+ paper. By default, we use the diffusers-native implementation that has been in the codebase for a long time.
100
+
101
+ Args:
102
+ guidance_scales (`List[float]`, defaults to `[10.0, 5.0]`):
103
+ The scale parameter for frequency-decoupled guidance for each frequency component, listed from highest
104
+ frequency level to lowest. Higher values result in stronger conditioning on the text prompt, while lower
105
+ values allow for more freedom in generation. Higher values may lead to saturation and deterioration of
106
+ image quality. The FDG authors recommend using higher guidance scales for higher frequency components and
107
+ lower guidance scales for lower frequency components (so `guidance_scales` should typically be sorted in
108
+ descending order).
109
+ guidance_rescale (`float` or `List[float]`, defaults to `0.0`):
110
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
111
+ overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
112
+ Flawed](https://huggingface.co/papers/2305.08891). If a list is supplied, it should be the same length as
113
+ `guidance_scales`.
114
+ parallel_weights (`float` or `List[float]`, *optional*):
115
+ Optional weights for the parallel component of each frequency component of the projected CFG shift. If not
116
+ set, the weights will default to `1.0` for all components, which corresponds to using the normal CFG shift
117
+ (that is, equal weights for the parallel and orthogonal components). If set, a value in `[0, 1]` is
118
+ recommended. If a list is supplied, it should be the same length as `guidance_scales`.
119
+ use_original_formulation (`bool`, defaults to `False`):
120
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
121
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
122
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
123
+ start (`float` or `List[float]`, defaults to `0.0`):
124
+ The fraction of the total number of denoising steps after which guidance starts. If a list is supplied, it
125
+ should be the same length as `guidance_scales`.
126
+ stop (`float` or `List[float]`, defaults to `1.0`):
127
+ The fraction of the total number of denoising steps after which guidance stops. If a list is supplied, it
128
+ should be the same length as `guidance_scales`.
129
+ guidance_rescale_space (`str`, defaults to `"data"`):
130
+ Whether to performance guidance rescaling in `"data"` space (after the full FDG update in data space) or in
131
+ `"freq"` space (right after the CFG update, for each freq level). Note that frequency space rescaling is
132
+ speculative and may not produce expected results. If `"data"` is set, the first `guidance_rescale` value
133
+ will be used; otherwise, per-frequency-level guidance rescale values will be used if available.
134
+ upcast_to_double (`bool`, defaults to `True`):
135
+ Whether to upcast certain operations, such as the projection operation when using `parallel_weights`, to
136
+ float64 when performing guidance. This may result in better performance at the cost of increased runtime.
137
+ """
138
+
139
+ _input_predictions = ["pred_cond", "pred_uncond"]
140
+
141
+ @register_to_config
142
+ def __init__(
143
+ self,
144
+ guidance_scales: Union[List[float], Tuple[float]] = [10.0, 5.0],
145
+ guidance_rescale: Union[float, List[float], Tuple[float]] = 0.0,
146
+ parallel_weights: Optional[Union[float, List[float], Tuple[float]]] = None,
147
+ use_original_formulation: bool = False,
148
+ start: Union[float, List[float], Tuple[float]] = 0.0,
149
+ stop: Union[float, List[float], Tuple[float]] = 1.0,
150
+ guidance_rescale_space: str = "data",
151
+ upcast_to_double: bool = True,
152
+ enabled: bool = True,
153
+ ):
154
+ if not _CAN_USE_KORNIA:
155
+ raise ImportError(
156
+ "The `FrequencyDecoupledGuidance` guider cannot be instantiated because the `kornia` library on which "
157
+ "it depends is not available in the current environment. You can install `kornia` with `pip install "
158
+ "kornia`."
159
+ )
160
+
161
+ # Set start to earliest start for any freq component and stop to latest stop for any freq component
162
+ min_start = start if isinstance(start, float) else min(start)
163
+ max_stop = stop if isinstance(stop, float) else max(stop)
164
+ super().__init__(min_start, max_stop, enabled)
165
+
166
+ self.guidance_scales = guidance_scales
167
+ self.levels = len(guidance_scales)
168
+
169
+ if isinstance(guidance_rescale, float):
170
+ self.guidance_rescale = [guidance_rescale] * self.levels
171
+ elif len(guidance_rescale) == self.levels:
172
+ self.guidance_rescale = guidance_rescale
173
+ else:
174
+ raise ValueError(
175
+ f"`guidance_rescale` has length {len(guidance_rescale)} but should have the same length as "
176
+ f"`guidance_scales` ({len(self.guidance_scales)})"
177
+ )
178
+ # Whether to perform guidance rescaling in frequency space (right after the CFG update) or data space (after
179
+ # transforming from frequency space back to data space)
180
+ if guidance_rescale_space not in ["data", "freq"]:
181
+ raise ValueError(
182
+ f"Guidance rescale space is {guidance_rescale_space} but must be one of `data` or `freq`."
183
+ )
184
+ self.guidance_rescale_space = guidance_rescale_space
185
+
186
+ if parallel_weights is None:
187
+ # Use normal CFG shift (equal weights for parallel and orthogonal components)
188
+ self.parallel_weights = [1.0] * self.levels
189
+ elif isinstance(parallel_weights, float):
190
+ self.parallel_weights = [parallel_weights] * self.levels
191
+ elif len(parallel_weights) == self.levels:
192
+ self.parallel_weights = parallel_weights
193
+ else:
194
+ raise ValueError(
195
+ f"`parallel_weights` has length {len(parallel_weights)} but should have the same length as "
196
+ f"`guidance_scales` ({len(self.guidance_scales)})"
197
+ )
198
+
199
+ self.use_original_formulation = use_original_formulation
200
+ self.upcast_to_double = upcast_to_double
201
+
202
+ if isinstance(start, float):
203
+ self.guidance_start = [start] * self.levels
204
+ elif len(start) == self.levels:
205
+ self.guidance_start = start
206
+ else:
207
+ raise ValueError(
208
+ f"`start` has length {len(start)} but should have the same length as `guidance_scales` "
209
+ f"({len(self.guidance_scales)})"
210
+ )
211
+ if isinstance(stop, float):
212
+ self.guidance_stop = [stop] * self.levels
213
+ elif len(stop) == self.levels:
214
+ self.guidance_stop = stop
215
+ else:
216
+ raise ValueError(
217
+ f"`stop` has length {len(stop)} but should have the same length as `guidance_scales` "
218
+ f"({len(self.guidance_scales)})"
219
+ )
220
+
221
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
222
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
223
+ data_batches = []
224
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
225
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
226
+ data_batches.append(data_batch)
227
+ return data_batches
228
+
229
+ def prepare_inputs_from_block_state(
230
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
231
+ ) -> List["BlockState"]:
232
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
233
+ data_batches = []
234
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
235
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
236
+ data_batches.append(data_batch)
237
+ return data_batches
238
+
239
+ def forward(self, pred_cond: torch.Tensor, pred_uncond: Optional[torch.Tensor] = None) -> GuiderOutput:
240
+ pred = None
241
+
242
+ if not self._is_fdg_enabled():
243
+ pred = pred_cond
244
+ else:
245
+ # Apply the frequency transform (e.g. Laplacian pyramid) to the conditional and unconditional predictions.
246
+ pred_cond_pyramid = build_laplacian_pyramid_func(pred_cond, self.levels)
247
+ pred_uncond_pyramid = build_laplacian_pyramid_func(pred_uncond, self.levels)
248
+
249
+ # From high frequencies to low frequencies, following the paper implementation
250
+ pred_guided_pyramid = []
251
+ parameters = zip(self.guidance_scales, self.parallel_weights, self.guidance_rescale)
252
+ for level, (guidance_scale, parallel_weight, guidance_rescale) in enumerate(parameters):
253
+ if self._is_fdg_enabled_for_level(level):
254
+ # Get the cond/uncond preds (in freq space) at the current frequency level
255
+ pred_cond_freq = pred_cond_pyramid[level]
256
+ pred_uncond_freq = pred_uncond_pyramid[level]
257
+
258
+ shift = pred_cond_freq - pred_uncond_freq
259
+
260
+ # Apply parallel weights, if used (1.0 corresponds to using the normal CFG shift)
261
+ if not math.isclose(parallel_weight, 1.0):
262
+ shift_parallel, shift_orthogonal = project(shift, pred_cond_freq, self.upcast_to_double)
263
+ shift = parallel_weight * shift_parallel + shift_orthogonal
264
+
265
+ # Apply CFG update for the current frequency level
266
+ pred = pred_cond_freq if self.use_original_formulation else pred_uncond_freq
267
+ pred = pred + guidance_scale * shift
268
+
269
+ if self.guidance_rescale_space == "freq" and guidance_rescale > 0.0:
270
+ pred = rescale_noise_cfg(pred, pred_cond_freq, guidance_rescale)
271
+
272
+ # Add the current FDG guided level to the FDG prediction pyramid
273
+ pred_guided_pyramid.append(pred)
274
+ else:
275
+ # Add the current pred_cond_pyramid level as the "non-FDG" prediction
276
+ pred_guided_pyramid.append(pred_cond_freq)
277
+
278
+ # Convert from frequency space back to data (e.g. pixel) space by applying inverse freq transform
279
+ pred = build_image_from_pyramid(pred_guided_pyramid)
280
+
281
+ # If rescaling in data space, use the first elem of self.guidance_rescale as the "global" rescale value
282
+ # across all freq levels
283
+ if self.guidance_rescale_space == "data" and self.guidance_rescale[0] > 0.0:
284
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale[0])
285
+
286
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
287
+
288
+ @property
289
+ def is_conditional(self) -> bool:
290
+ return self._count_prepared == 1
291
+
292
+ @property
293
+ def num_conditions(self) -> int:
294
+ num_conditions = 1
295
+ if self._is_fdg_enabled():
296
+ num_conditions += 1
297
+ return num_conditions
298
+
299
+ def _is_fdg_enabled(self) -> bool:
300
+ if not self._enabled:
301
+ return False
302
+
303
+ is_within_range = True
304
+ if self._num_inference_steps is not None:
305
+ skip_start_step = int(self._start * self._num_inference_steps)
306
+ skip_stop_step = int(self._stop * self._num_inference_steps)
307
+ is_within_range = skip_start_step <= self._step < skip_stop_step
308
+
309
+ is_close = False
310
+ if self.use_original_formulation:
311
+ is_close = all(math.isclose(guidance_scale, 0.0) for guidance_scale in self.guidance_scales)
312
+ else:
313
+ is_close = all(math.isclose(guidance_scale, 1.0) for guidance_scale in self.guidance_scales)
314
+
315
+ return is_within_range and not is_close
316
+
317
+ def _is_fdg_enabled_for_level(self, level: int) -> bool:
318
+ if not self._enabled:
319
+ return False
320
+
321
+ is_within_range = True
322
+ if self._num_inference_steps is not None:
323
+ skip_start_step = int(self.guidance_start[level] * self._num_inference_steps)
324
+ skip_stop_step = int(self.guidance_stop[level] * self._num_inference_steps)
325
+ is_within_range = skip_start_step <= self._step < skip_stop_step
326
+
327
+ is_close = False
328
+ if self.use_original_formulation:
329
+ is_close = math.isclose(self.guidance_scales[level], 0.0)
330
+ else:
331
+ is_close = math.isclose(self.guidance_scales[level], 1.0)
332
+
333
+ return is_within_range and not is_close
vendor/diffusers/guiders/guider_utils.py ADDED
@@ -0,0 +1,394 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import os
16
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+ from huggingface_hub.utils import validate_hf_hub_args
20
+ from typing_extensions import Self
21
+
22
+ from ..configuration_utils import ConfigMixin
23
+ from ..utils import BaseOutput, PushToHubMixin, get_logger
24
+
25
+
26
+ if TYPE_CHECKING:
27
+ from ..modular_pipelines.modular_pipeline import BlockState
28
+
29
+
30
+ GUIDER_CONFIG_NAME = "guider_config.json"
31
+
32
+
33
+ logger = get_logger(__name__) # pylint: disable=invalid-name
34
+
35
+
36
+ class BaseGuidance(ConfigMixin, PushToHubMixin):
37
+ r"""Base class providing the skeleton for implementing guidance techniques."""
38
+
39
+ config_name = GUIDER_CONFIG_NAME
40
+ _input_predictions = None
41
+ _identifier_key = "__guidance_identifier__"
42
+
43
+ def __init__(self, start: float = 0.0, stop: float = 1.0, enabled: bool = True):
44
+ logger.warning(
45
+ "Guiders are currently an experimental feature under active development. The API is subject to breaking changes in future releases."
46
+ )
47
+
48
+ self._start = start
49
+ self._stop = stop
50
+ self._step: int = None
51
+ self._num_inference_steps: int = None
52
+ self._timestep: torch.LongTensor = None
53
+ self._count_prepared = 0
54
+ self._input_fields: Dict[str, Union[str, Tuple[str, str]]] = None
55
+ self._enabled = enabled
56
+
57
+ if not (0.0 <= start < 1.0):
58
+ raise ValueError(f"Expected `start` to be between 0.0 and 1.0, but got {start}.")
59
+ if not (start <= stop <= 1.0):
60
+ raise ValueError(f"Expected `stop` to be between {start} and 1.0, but got {stop}.")
61
+
62
+ if self._input_predictions is None or not isinstance(self._input_predictions, list):
63
+ raise ValueError(
64
+ "`_input_predictions` must be a list of required prediction names for the guidance technique."
65
+ )
66
+
67
+ def new(self, **kwargs):
68
+ """
69
+ Creates a copy of this guider instance, optionally with modified configuration parameters.
70
+
71
+ Args:
72
+ **kwargs: Configuration parameters to override in the new instance. If no kwargs are provided,
73
+ returns an exact copy with the same configuration.
74
+
75
+ Returns:
76
+ A new guider instance with the same (or updated) configuration.
77
+
78
+ Example:
79
+ ```python
80
+ # Create a CFG guider
81
+ guider = ClassifierFreeGuidance(guidance_scale=3.5)
82
+
83
+ # Create an exact copy
84
+ same_guider = guider.new()
85
+
86
+ # Create a copy with different start step, keeping other config the same
87
+ new_guider = guider.new(guidance_scale=5)
88
+ ```
89
+ """
90
+ return self.__class__.from_config(self.config, **kwargs)
91
+
92
+ def disable(self):
93
+ self._enabled = False
94
+
95
+ def enable(self):
96
+ self._enabled = True
97
+
98
+ def set_state(self, step: int, num_inference_steps: int, timestep: torch.LongTensor) -> None:
99
+ self._step = step
100
+ self._num_inference_steps = num_inference_steps
101
+ self._timestep = timestep
102
+ self._count_prepared = 0
103
+
104
+ def get_state(self) -> Dict[str, Any]:
105
+ """
106
+ Returns the current state of the guidance technique as a dictionary. The state variables will be included in
107
+ the __repr__ method. Returns:
108
+ `Dict[str, Any]`: A dictionary containing the current state variables including:
109
+ - step: Current inference step
110
+ - num_inference_steps: Total number of inference steps
111
+ - timestep: Current timestep tensor
112
+ - count_prepared: Number of times prepare_models has been called
113
+ - enabled: Whether the guidance is enabled
114
+ - num_conditions: Number of conditions
115
+ """
116
+ state = {
117
+ "step": self._step,
118
+ "num_inference_steps": self._num_inference_steps,
119
+ "timestep": self._timestep,
120
+ "count_prepared": self._count_prepared,
121
+ "enabled": self._enabled,
122
+ "num_conditions": self.num_conditions,
123
+ }
124
+ return state
125
+
126
+ def __repr__(self) -> str:
127
+ """
128
+ Returns a string representation of the guidance object including both config and current state.
129
+ """
130
+ # Get ConfigMixin's __repr__
131
+ str_repr = super().__repr__()
132
+
133
+ # Get current state
134
+ state = self.get_state()
135
+
136
+ # Format each state variable on its own line with indentation
137
+ state_lines = []
138
+ for k, v in state.items():
139
+ # Convert value to string and handle multi-line values
140
+ v_str = str(v)
141
+ if "\n" in v_str:
142
+ # For multi-line values (like MomentumBuffer), indent subsequent lines
143
+ v_lines = v_str.split("\n")
144
+ v_str = v_lines[0] + "\n" + "\n".join([" " + line for line in v_lines[1:]])
145
+ state_lines.append(f" {k}: {v_str}")
146
+
147
+ state_str = "\n".join(state_lines)
148
+
149
+ return f"{str_repr}\nState:\n{state_str}"
150
+
151
+ def prepare_models(self, denoiser: torch.nn.Module) -> None:
152
+ """
153
+ Prepares the models for the guidance technique on a given batch of data. This method should be overridden in
154
+ subclasses to implement specific model preparation logic.
155
+ """
156
+ self._count_prepared += 1
157
+
158
+ def cleanup_models(self, denoiser: torch.nn.Module) -> None:
159
+ """
160
+ Cleans up the models for the guidance technique after a given batch of data. This method should be overridden
161
+ in subclasses to implement specific model cleanup logic. It is useful for removing any hooks or other stateful
162
+ modifications made during `prepare_models`.
163
+ """
164
+ pass
165
+
166
+ def prepare_inputs(self, data: "BlockState") -> List["BlockState"]:
167
+ raise NotImplementedError("BaseGuidance::prepare_inputs must be implemented in subclasses.")
168
+
169
+ def prepare_inputs_from_block_state(
170
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
171
+ ) -> List["BlockState"]:
172
+ raise NotImplementedError("BaseGuidance::prepare_inputs_from_block_state must be implemented in subclasses.")
173
+
174
+ def __call__(self, data: List["BlockState"]) -> Any:
175
+ if not all(hasattr(d, "noise_pred") for d in data):
176
+ raise ValueError("Expected all data to have `noise_pred` attribute.")
177
+ if len(data) != self.num_conditions:
178
+ raise ValueError(
179
+ f"Expected {self.num_conditions} data items, but got {len(data)}. Please check the input data."
180
+ )
181
+ forward_inputs = {getattr(d, self._identifier_key): d.noise_pred for d in data}
182
+ return self.forward(**forward_inputs)
183
+
184
+ def forward(self, *args, **kwargs) -> Any:
185
+ raise NotImplementedError("BaseGuidance::forward must be implemented in subclasses.")
186
+
187
+ @property
188
+ def is_conditional(self) -> bool:
189
+ raise NotImplementedError("BaseGuidance::is_conditional must be implemented in subclasses.")
190
+
191
+ @property
192
+ def is_unconditional(self) -> bool:
193
+ return not self.is_conditional
194
+
195
+ @property
196
+ def num_conditions(self) -> int:
197
+ raise NotImplementedError("BaseGuidance::num_conditions must be implemented in subclasses.")
198
+
199
+ @classmethod
200
+ def _prepare_batch(
201
+ cls,
202
+ data: Dict[str, Tuple[torch.Tensor, torch.Tensor]],
203
+ tuple_index: int,
204
+ identifier: str,
205
+ ) -> "BlockState":
206
+ """
207
+ Prepares a batch of data for the guidance technique. This method is used in the `prepare_inputs` method of the
208
+ `BaseGuidance` class. It prepares the batch based on the provided tuple index.
209
+
210
+ Args:
211
+ input_fields (`Dict[str, Union[str, Tuple[str, str]]]`):
212
+ A dictionary where the keys are the names of the fields that will be used to store the data once it is
213
+ prepared with `prepare_inputs`. The values can be either a string or a tuple of length 2, which is used
214
+ to look up the required data provided for preparation. If a string is provided, it will be used as the
215
+ conditional data (or unconditional if used with a guidance method that requires it). If a tuple of
216
+ length 2 is provided, the first element must be the conditional data identifier and the second element
217
+ must be the unconditional data identifier or None.
218
+ data (`BlockState`):
219
+ The input data to be prepared.
220
+ tuple_index (`int`):
221
+ The index to use when accessing input fields that are tuples.
222
+
223
+ Returns:
224
+ `BlockState`: The prepared batch of data.
225
+ """
226
+ from ..modular_pipelines.modular_pipeline import BlockState
227
+
228
+ data_batch = {}
229
+ for key, value in data.items():
230
+ try:
231
+ if isinstance(value, torch.Tensor):
232
+ data_batch[key] = value
233
+ elif isinstance(value, tuple):
234
+ data_batch[key] = value[tuple_index]
235
+ else:
236
+ raise ValueError(f"Invalid value type: {type(value)}")
237
+ except ValueError:
238
+ logger.debug(f"`data` does not have attribute(s) {value}, skipping.")
239
+ data_batch[cls._identifier_key] = identifier
240
+ return BlockState(**data_batch)
241
+
242
+ @classmethod
243
+ def _prepare_batch_from_block_state(
244
+ cls,
245
+ input_fields: Dict[str, Union[str, Tuple[str, str]]],
246
+ data: "BlockState",
247
+ tuple_index: int,
248
+ identifier: str,
249
+ ) -> "BlockState":
250
+ """
251
+ Prepares a batch of data for the guidance technique. This method is used in the `prepare_inputs` method of the
252
+ `BaseGuidance` class. It prepares the batch based on the provided tuple index.
253
+
254
+ Args:
255
+ input_fields (`Dict[str, Union[str, Tuple[str, str]]]`):
256
+ A dictionary where the keys are the names of the fields that will be used to store the data once it is
257
+ prepared with `prepare_inputs`. The values can be either a string or a tuple of length 2, which is used
258
+ to look up the required data provided for preparation. If a string is provided, it will be used as the
259
+ conditional data (or unconditional if used with a guidance method that requires it). If a tuple of
260
+ length 2 is provided, the first element must be the conditional data identifier and the second element
261
+ must be the unconditional data identifier or None.
262
+ data (`BlockState`):
263
+ The input data to be prepared.
264
+ tuple_index (`int`):
265
+ The index to use when accessing input fields that are tuples.
266
+
267
+ Returns:
268
+ `BlockState`: The prepared batch of data.
269
+ """
270
+ from ..modular_pipelines.modular_pipeline import BlockState
271
+
272
+ data_batch = {}
273
+ for key, value in input_fields.items():
274
+ try:
275
+ if isinstance(value, str):
276
+ data_batch[key] = getattr(data, value)
277
+ elif isinstance(value, tuple):
278
+ data_batch[key] = getattr(data, value[tuple_index])
279
+ else:
280
+ # We've already checked that value is a string or a tuple of strings with length 2
281
+ pass
282
+ except AttributeError:
283
+ logger.debug(f"`data` does not have attribute(s) {value}, skipping.")
284
+ data_batch[cls._identifier_key] = identifier
285
+ return BlockState(**data_batch)
286
+
287
+ @classmethod
288
+ @validate_hf_hub_args
289
+ def from_pretrained(
290
+ cls,
291
+ pretrained_model_name_or_path: Optional[Union[str, os.PathLike]] = None,
292
+ subfolder: Optional[str] = None,
293
+ return_unused_kwargs=False,
294
+ **kwargs,
295
+ ) -> Self:
296
+ r"""
297
+ Instantiate a guider from a pre-defined JSON configuration file in a local directory or Hub repository.
298
+
299
+ Parameters:
300
+ pretrained_model_name_or_path (`str` or `os.PathLike`, *optional*):
301
+ Can be either:
302
+
303
+ - A string, the *model id* (for example `google/ddpm-celebahq-256`) of a pretrained model hosted on
304
+ the Hub.
305
+ - A path to a *directory* (for example `./my_model_directory`) containing the guider configuration
306
+ saved with [`~BaseGuidance.save_pretrained`].
307
+ subfolder (`str`, *optional*):
308
+ The subfolder location of a model file within a larger model repository on the Hub or locally.
309
+ return_unused_kwargs (`bool`, *optional*, defaults to `False`):
310
+ Whether kwargs that are not consumed by the Python class should be returned or not.
311
+ cache_dir (`Union[str, os.PathLike]`, *optional*):
312
+ Path to a directory where a downloaded pretrained model configuration is cached if the standard cache
313
+ is not used.
314
+ force_download (`bool`, *optional*, defaults to `False`):
315
+ Whether or not to force the (re-)download of the model weights and configuration files, overriding the
316
+ cached versions if they exist.
317
+
318
+ proxies (`Dict[str, str]`, *optional*):
319
+ A dictionary of proxy servers to use by protocol or endpoint, for example, `{'http': 'foo.bar:3128',
320
+ 'http://hostname': 'foo.bar:4012'}`. The proxies are used on each request.
321
+ output_loading_info(`bool`, *optional*, defaults to `False`):
322
+ Whether or not to also return a dictionary containing missing keys, unexpected keys and error messages.
323
+ local_files_only(`bool`, *optional*, defaults to `False`):
324
+ Whether to only load local model weights and configuration files or not. If set to `True`, the model
325
+ won't be downloaded from the Hub.
326
+ token (`str` or *bool*, *optional*):
327
+ The token to use as HTTP bearer authorization for remote files. If `True`, the token generated from
328
+ `diffusers-cli login` (stored in `~/.huggingface`) is used.
329
+ revision (`str`, *optional*, defaults to `"main"`):
330
+ The specific model version to use. It can be a branch name, a tag name, a commit id, or any identifier
331
+ allowed by Git.
332
+
333
+ > [!TIP] > To use private or [gated models](https://huggingface.co/docs/hub/models-gated#gated-models), log-in
334
+ with `hf > auth login`. You can also activate the special >
335
+ ["offline-mode"](https://huggingface.co/diffusers/installation.html#offline-mode) to use this method in a >
336
+ firewalled environment.
337
+
338
+ """
339
+ config, kwargs, commit_hash = cls.load_config(
340
+ pretrained_model_name_or_path=pretrained_model_name_or_path,
341
+ subfolder=subfolder,
342
+ return_unused_kwargs=True,
343
+ return_commit_hash=True,
344
+ **kwargs,
345
+ )
346
+ return cls.from_config(config, return_unused_kwargs=return_unused_kwargs, **kwargs)
347
+
348
+ def save_pretrained(self, save_directory: Union[str, os.PathLike], push_to_hub: bool = False, **kwargs):
349
+ """
350
+ Save a guider configuration object to a directory so that it can be reloaded using the
351
+ [`~BaseGuidance.from_pretrained`] class method.
352
+
353
+ Args:
354
+ save_directory (`str` or `os.PathLike`):
355
+ Directory where the configuration JSON file will be saved (will be created if it does not exist).
356
+ push_to_hub (`bool`, *optional*, defaults to `False`):
357
+ Whether or not to push your model to the Hugging Face Hub after saving it. You can specify the
358
+ repository you want to push to with `repo_id` (will default to the name of `save_directory` in your
359
+ namespace).
360
+ kwargs (`Dict[str, Any]`, *optional*):
361
+ Additional keyword arguments passed along to the [`~utils.PushToHubMixin.push_to_hub`] method.
362
+ """
363
+ self.save_config(save_directory=save_directory, push_to_hub=push_to_hub, **kwargs)
364
+
365
+
366
+ class GuiderOutput(BaseOutput):
367
+ pred: torch.Tensor
368
+ pred_cond: Optional[torch.Tensor]
369
+ pred_uncond: Optional[torch.Tensor]
370
+
371
+
372
+ def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0):
373
+ r"""
374
+ Rescales `noise_cfg` tensor based on `guidance_rescale` to improve image quality and fix overexposure. Based on
375
+ Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
376
+ Flawed](https://huggingface.co/papers/2305.08891).
377
+
378
+ Args:
379
+ noise_cfg (`torch.Tensor`):
380
+ The predicted noise tensor for the guided diffusion process.
381
+ noise_pred_text (`torch.Tensor`):
382
+ The predicted noise tensor for the text-guided diffusion process.
383
+ guidance_rescale (`float`, *optional*, defaults to 0.0):
384
+ A rescale factor applied to the noise predictions.
385
+ Returns:
386
+ noise_cfg (`torch.Tensor`): The rescaled noise prediction tensor.
387
+ """
388
+ std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True)
389
+ std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True)
390
+ # rescale the results from guidance (fixes overexposure)
391
+ noise_pred_rescaled = noise_cfg * (std_text / std_cfg)
392
+ # mix with the original results from guidance by factor guidance_rescale to avoid "plain looking" images
393
+ noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg
394
+ return noise_cfg
vendor/diffusers/guiders/magnitude_aware_guidance.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from ..modular_pipelines.modular_pipeline import BlockState
26
+
27
+
28
+ class MagnitudeAwareGuidance(BaseGuidance):
29
+ """
30
+ Magnitude-Aware Mitigation for Boosted Guidance (MAMBO-G): https://huggingface.co/papers/2508.03442
31
+
32
+ Args:
33
+ guidance_scale (`float`, defaults to `10.0`):
34
+ The scale parameter for classifier-free guidance. Higher values result in stronger conditioning on the text
35
+ prompt, while lower values allow for more freedom in generation. Higher values may lead to saturation and
36
+ deterioration of image quality.
37
+ alpha (`float`, defaults to `8.0`):
38
+ The alpha parameter for the magnitude-aware guidance. Higher values cause more aggressive supression of
39
+ guidance scale when the magnitude of the guidance update is large.
40
+ guidance_rescale (`float`, defaults to `0.0`):
41
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
42
+ overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
43
+ Flawed](https://huggingface.co/papers/2305.08891).
44
+ use_original_formulation (`bool`, defaults to `False`):
45
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
46
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
47
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
48
+ start (`float`, defaults to `0.0`):
49
+ The fraction of the total number of denoising steps after which guidance starts.
50
+ stop (`float`, defaults to `1.0`):
51
+ The fraction of the total number of denoising steps after which guidance stops.
52
+ """
53
+
54
+ _input_predictions = ["pred_cond", "pred_uncond"]
55
+
56
+ @register_to_config
57
+ def __init__(
58
+ self,
59
+ guidance_scale: float = 10.0,
60
+ alpha: float = 8.0,
61
+ guidance_rescale: float = 0.0,
62
+ use_original_formulation: bool = False,
63
+ start: float = 0.0,
64
+ stop: float = 1.0,
65
+ enabled: bool = True,
66
+ ):
67
+ super().__init__(start, stop, enabled)
68
+
69
+ self.guidance_scale = guidance_scale
70
+ self.alpha = alpha
71
+ self.guidance_rescale = guidance_rescale
72
+ self.use_original_formulation = use_original_formulation
73
+
74
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
75
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
76
+ data_batches = []
77
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
78
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
79
+ data_batches.append(data_batch)
80
+ return data_batches
81
+
82
+ def prepare_inputs_from_block_state(
83
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
84
+ ) -> List["BlockState"]:
85
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
86
+ data_batches = []
87
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
88
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
89
+ data_batches.append(data_batch)
90
+ return data_batches
91
+
92
+ def forward(self, pred_cond: torch.Tensor, pred_uncond: Optional[torch.Tensor] = None) -> GuiderOutput:
93
+ pred = None
94
+
95
+ if not self._is_mambo_g_enabled():
96
+ pred = pred_cond
97
+ else:
98
+ pred = mambo_guidance(
99
+ pred_cond,
100
+ pred_uncond,
101
+ self.guidance_scale,
102
+ self.alpha,
103
+ self.use_original_formulation,
104
+ )
105
+
106
+ if self.guidance_rescale > 0.0:
107
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
108
+
109
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
110
+
111
+ @property
112
+ def is_conditional(self) -> bool:
113
+ return self._count_prepared == 1
114
+
115
+ @property
116
+ def num_conditions(self) -> int:
117
+ num_conditions = 1
118
+ if self._is_mambo_g_enabled():
119
+ num_conditions += 1
120
+ return num_conditions
121
+
122
+ def _is_mambo_g_enabled(self) -> bool:
123
+ if not self._enabled:
124
+ return False
125
+
126
+ is_within_range = True
127
+ if self._num_inference_steps is not None:
128
+ skip_start_step = int(self._start * self._num_inference_steps)
129
+ skip_stop_step = int(self._stop * self._num_inference_steps)
130
+ is_within_range = skip_start_step <= self._step < skip_stop_step
131
+
132
+ is_close = False
133
+ if self.use_original_formulation:
134
+ is_close = math.isclose(self.guidance_scale, 0.0)
135
+ else:
136
+ is_close = math.isclose(self.guidance_scale, 1.0)
137
+
138
+ return is_within_range and not is_close
139
+
140
+
141
+ def mambo_guidance(
142
+ pred_cond: torch.Tensor,
143
+ pred_uncond: torch.Tensor,
144
+ guidance_scale: float,
145
+ alpha: float = 8.0,
146
+ use_original_formulation: bool = False,
147
+ ):
148
+ dim = list(range(1, len(pred_cond.shape)))
149
+ diff = pred_cond - pred_uncond
150
+ ratio = torch.norm(diff, dim=dim, keepdim=True) / torch.norm(pred_uncond, dim=dim, keepdim=True)
151
+ guidance_scale_final = (
152
+ guidance_scale * torch.exp(-alpha * ratio)
153
+ if use_original_formulation
154
+ else 1.0 + (guidance_scale - 1.0) * torch.exp(-alpha * ratio)
155
+ )
156
+ pred = pred_cond if use_original_formulation else pred_uncond
157
+ pred = pred + guidance_scale_final * diff
158
+
159
+ return pred
vendor/diffusers/guiders/perturbed_attention_guidance.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from ..hooks import HookRegistry, LayerSkipConfig
22
+ from ..hooks.layer_skip import _apply_layer_skip_hook
23
+ from ..utils import get_logger
24
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
25
+
26
+
27
+ if TYPE_CHECKING:
28
+ from ..modular_pipelines.modular_pipeline import BlockState
29
+
30
+
31
+ logger = get_logger(__name__) # pylint: disable=invalid-name
32
+
33
+
34
+ class PerturbedAttentionGuidance(BaseGuidance):
35
+ """
36
+ Perturbed Attention Guidance (PAG): https://huggingface.co/papers/2403.17377
37
+
38
+ The intution behind PAG can be thought of as moving the CFG predicted distribution estimates further away from
39
+ worse versions of the conditional distribution estimates. PAG was one of the first techniques to introduce the idea
40
+ of using a worse version of the trained model for better guiding itself in the denoising process. It perturbs the
41
+ attention scores of the latent stream by replacing the score matrix with an identity matrix for selectively chosen
42
+ layers.
43
+
44
+ Additional reading:
45
+ - [Guiding a Diffusion Model with a Bad Version of Itself](https://huggingface.co/papers/2406.02507)
46
+
47
+ PAG is implemented with similar implementation to SkipLayerGuidance due to overlap in the configuration parameters
48
+ and implementation details.
49
+
50
+ Args:
51
+ guidance_scale (`float`, defaults to `7.5`):
52
+ The scale parameter for classifier-free guidance. Higher values result in stronger conditioning on the text
53
+ prompt, while lower values allow for more freedom in generation. Higher values may lead to saturation and
54
+ deterioration of image quality.
55
+ perturbed_guidance_scale (`float`, defaults to `2.8`):
56
+ The scale parameter for perturbed attention guidance.
57
+ perturbed_guidance_start (`float`, defaults to `0.01`):
58
+ The fraction of the total number of denoising steps after which perturbed attention guidance starts.
59
+ perturbed_guidance_stop (`float`, defaults to `0.2`):
60
+ The fraction of the total number of denoising steps after which perturbed attention guidance stops.
61
+ perturbed_guidance_layers (`int` or `List[int]`, *optional*):
62
+ The layer indices to apply perturbed attention guidance to. Can be a single integer or a list of integers.
63
+ If not provided, `perturbed_guidance_config` must be provided.
64
+ perturbed_guidance_config (`LayerSkipConfig` or `List[LayerSkipConfig]`, *optional*):
65
+ The configuration for the perturbed attention guidance. Can be a single `LayerSkipConfig` or a list of
66
+ `LayerSkipConfig`. If not provided, `perturbed_guidance_layers` must be provided.
67
+ guidance_rescale (`float`, defaults to `0.0`):
68
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
69
+ overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
70
+ Flawed](https://huggingface.co/papers/2305.08891).
71
+ use_original_formulation (`bool`, defaults to `False`):
72
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
73
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
74
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
75
+ start (`float`, defaults to `0.01`):
76
+ The fraction of the total number of denoising steps after which guidance starts.
77
+ stop (`float`, defaults to `0.2`):
78
+ The fraction of the total number of denoising steps after which guidance stops.
79
+ """
80
+
81
+ # NOTE: The current implementation does not account for joint latent conditioning (text + image/video tokens in
82
+ # the same latent stream). It assumes the entire latent is a single stream of visual tokens. It would be very
83
+ # complex to support joint latent conditioning in a model-agnostic manner without specializing the implementation
84
+ # for each model architecture.
85
+
86
+ _input_predictions = ["pred_cond", "pred_uncond", "pred_cond_skip"]
87
+
88
+ @register_to_config
89
+ def __init__(
90
+ self,
91
+ guidance_scale: float = 7.5,
92
+ perturbed_guidance_scale: float = 2.8,
93
+ perturbed_guidance_start: float = 0.01,
94
+ perturbed_guidance_stop: float = 0.2,
95
+ perturbed_guidance_layers: Optional[Union[int, List[int]]] = None,
96
+ perturbed_guidance_config: Union[LayerSkipConfig, List[LayerSkipConfig], Dict[str, Any]] = None,
97
+ guidance_rescale: float = 0.0,
98
+ use_original_formulation: bool = False,
99
+ start: float = 0.0,
100
+ stop: float = 1.0,
101
+ enabled: bool = True,
102
+ ):
103
+ super().__init__(start, stop, enabled)
104
+
105
+ self.guidance_scale = guidance_scale
106
+ self.skip_layer_guidance_scale = perturbed_guidance_scale
107
+ self.skip_layer_guidance_start = perturbed_guidance_start
108
+ self.skip_layer_guidance_stop = perturbed_guidance_stop
109
+ self.guidance_rescale = guidance_rescale
110
+ self.use_original_formulation = use_original_formulation
111
+
112
+ if perturbed_guidance_config is None:
113
+ if perturbed_guidance_layers is None:
114
+ raise ValueError(
115
+ "`perturbed_guidance_layers` must be provided if `perturbed_guidance_config` is not specified."
116
+ )
117
+ perturbed_guidance_config = LayerSkipConfig(
118
+ indices=perturbed_guidance_layers,
119
+ fqn="auto",
120
+ skip_attention=False,
121
+ skip_attention_scores=True,
122
+ skip_ff=False,
123
+ )
124
+ else:
125
+ if perturbed_guidance_layers is not None:
126
+ raise ValueError(
127
+ "`perturbed_guidance_layers` should not be provided if `perturbed_guidance_config` is specified."
128
+ )
129
+
130
+ if isinstance(perturbed_guidance_config, dict):
131
+ perturbed_guidance_config = LayerSkipConfig.from_dict(perturbed_guidance_config)
132
+
133
+ if isinstance(perturbed_guidance_config, LayerSkipConfig):
134
+ perturbed_guidance_config = [perturbed_guidance_config]
135
+
136
+ if not isinstance(perturbed_guidance_config, list):
137
+ raise ValueError(
138
+ "`perturbed_guidance_config` must be a `LayerSkipConfig`, a list of `LayerSkipConfig`, or a dict that can be converted to a `LayerSkipConfig`."
139
+ )
140
+ elif isinstance(next(iter(perturbed_guidance_config), None), dict):
141
+ perturbed_guidance_config = [LayerSkipConfig.from_dict(config) for config in perturbed_guidance_config]
142
+
143
+ for config in perturbed_guidance_config:
144
+ if config.skip_attention or not config.skip_attention_scores or config.skip_ff:
145
+ logger.warning(
146
+ "Perturbed Attention Guidance is designed to perturb attention scores, so `skip_attention` should be False, `skip_attention_scores` should be True, and `skip_ff` should be False. "
147
+ "Please check your configuration. Modifying the config to match the expected values."
148
+ )
149
+ config.skip_attention = False
150
+ config.skip_attention_scores = True
151
+ config.skip_ff = False
152
+
153
+ self.skip_layer_config = perturbed_guidance_config
154
+ self._skip_layer_hook_names = [f"SkipLayerGuidance_{i}" for i in range(len(self.skip_layer_config))]
155
+
156
+ # Copied from diffusers.guiders.skip_layer_guidance.SkipLayerGuidance.prepare_models
157
+ def prepare_models(self, denoiser: torch.nn.Module) -> None:
158
+ self._count_prepared += 1
159
+ if self._is_slg_enabled() and self.is_conditional and self._count_prepared > 1:
160
+ for name, config in zip(self._skip_layer_hook_names, self.skip_layer_config):
161
+ _apply_layer_skip_hook(denoiser, config, name=name)
162
+
163
+ # Copied from diffusers.guiders.skip_layer_guidance.SkipLayerGuidance.cleanup_models
164
+ def cleanup_models(self, denoiser: torch.nn.Module) -> None:
165
+ if self._is_slg_enabled() and self.is_conditional and self._count_prepared > 1:
166
+ registry = HookRegistry.check_if_exists_or_initialize(denoiser)
167
+ # Remove the hooks after inference
168
+ for hook_name in self._skip_layer_hook_names:
169
+ registry.remove_hook(hook_name, recurse=True)
170
+
171
+ # Copied from diffusers.guiders.skip_layer_guidance.SkipLayerGuidance.prepare_inputs
172
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
173
+ if self.num_conditions == 1:
174
+ tuple_indices = [0]
175
+ input_predictions = ["pred_cond"]
176
+ elif self.num_conditions == 2:
177
+ tuple_indices = [0, 1]
178
+ input_predictions = (
179
+ ["pred_cond", "pred_uncond"] if self._is_cfg_enabled() else ["pred_cond", "pred_cond_skip"]
180
+ )
181
+ else:
182
+ tuple_indices = [0, 1, 0]
183
+ input_predictions = ["pred_cond", "pred_uncond", "pred_cond_skip"]
184
+ data_batches = []
185
+ for tuple_idx, input_prediction in zip(tuple_indices, input_predictions):
186
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
187
+ data_batches.append(data_batch)
188
+ return data_batches
189
+
190
+ def prepare_inputs_from_block_state(
191
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
192
+ ) -> List["BlockState"]:
193
+ if self.num_conditions == 1:
194
+ tuple_indices = [0]
195
+ input_predictions = ["pred_cond"]
196
+ elif self.num_conditions == 2:
197
+ tuple_indices = [0, 1]
198
+ input_predictions = (
199
+ ["pred_cond", "pred_uncond"] if self._is_cfg_enabled() else ["pred_cond", "pred_cond_skip"]
200
+ )
201
+ else:
202
+ tuple_indices = [0, 1, 0]
203
+ input_predictions = ["pred_cond", "pred_uncond", "pred_cond_skip"]
204
+ data_batches = []
205
+ for tuple_idx, input_prediction in zip(tuple_indices, input_predictions):
206
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
207
+ data_batches.append(data_batch)
208
+ return data_batches
209
+
210
+ # Copied from diffusers.guiders.skip_layer_guidance.SkipLayerGuidance.forward
211
+ def forward(
212
+ self,
213
+ pred_cond: torch.Tensor,
214
+ pred_uncond: Optional[torch.Tensor] = None,
215
+ pred_cond_skip: Optional[torch.Tensor] = None,
216
+ ) -> GuiderOutput:
217
+ pred = None
218
+
219
+ if not self._is_cfg_enabled() and not self._is_slg_enabled():
220
+ pred = pred_cond
221
+ elif not self._is_cfg_enabled():
222
+ shift = pred_cond - pred_cond_skip
223
+ pred = pred_cond if self.use_original_formulation else pred_cond_skip
224
+ pred = pred + self.skip_layer_guidance_scale * shift
225
+ elif not self._is_slg_enabled():
226
+ shift = pred_cond - pred_uncond
227
+ pred = pred_cond if self.use_original_formulation else pred_uncond
228
+ pred = pred + self.guidance_scale * shift
229
+ else:
230
+ shift = pred_cond - pred_uncond
231
+ shift_skip = pred_cond - pred_cond_skip
232
+ pred = pred_cond if self.use_original_formulation else pred_uncond
233
+ pred = pred + self.guidance_scale * shift + self.skip_layer_guidance_scale * shift_skip
234
+
235
+ if self.guidance_rescale > 0.0:
236
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
237
+
238
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
239
+
240
+ @property
241
+ # Copied from diffusers.guiders.skip_layer_guidance.SkipLayerGuidance.is_conditional
242
+ def is_conditional(self) -> bool:
243
+ return self._count_prepared == 1 or self._count_prepared == 3
244
+
245
+ @property
246
+ # Copied from diffusers.guiders.skip_layer_guidance.SkipLayerGuidance.num_conditions
247
+ def num_conditions(self) -> int:
248
+ num_conditions = 1
249
+ if self._is_cfg_enabled():
250
+ num_conditions += 1
251
+ if self._is_slg_enabled():
252
+ num_conditions += 1
253
+ return num_conditions
254
+
255
+ # Copied from diffusers.guiders.skip_layer_guidance.SkipLayerGuidance._is_cfg_enabled
256
+ def _is_cfg_enabled(self) -> bool:
257
+ if not self._enabled:
258
+ return False
259
+
260
+ is_within_range = True
261
+ if self._num_inference_steps is not None:
262
+ skip_start_step = int(self._start * self._num_inference_steps)
263
+ skip_stop_step = int(self._stop * self._num_inference_steps)
264
+ is_within_range = skip_start_step <= self._step < skip_stop_step
265
+
266
+ is_close = False
267
+ if self.use_original_formulation:
268
+ is_close = math.isclose(self.guidance_scale, 0.0)
269
+ else:
270
+ is_close = math.isclose(self.guidance_scale, 1.0)
271
+
272
+ return is_within_range and not is_close
273
+
274
+ # Copied from diffusers.guiders.skip_layer_guidance.SkipLayerGuidance._is_slg_enabled
275
+ def _is_slg_enabled(self) -> bool:
276
+ if not self._enabled:
277
+ return False
278
+
279
+ is_within_range = True
280
+ if self._num_inference_steps is not None:
281
+ skip_start_step = int(self.skip_layer_guidance_start * self._num_inference_steps)
282
+ skip_stop_step = int(self.skip_layer_guidance_stop * self._num_inference_steps)
283
+ is_within_range = skip_start_step < self._step < skip_stop_step
284
+
285
+ is_zero = math.isclose(self.skip_layer_guidance_scale, 0.0)
286
+
287
+ return is_within_range and not is_zero
vendor/diffusers/guiders/skip_layer_guidance.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from ..hooks import HookRegistry, LayerSkipConfig
22
+ from ..hooks.layer_skip import _apply_layer_skip_hook
23
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
24
+
25
+
26
+ if TYPE_CHECKING:
27
+ from ..modular_pipelines.modular_pipeline import BlockState
28
+
29
+
30
+ class SkipLayerGuidance(BaseGuidance):
31
+ """
32
+ Skip Layer Guidance (SLG): https://github.com/Stability-AI/sd3.5
33
+
34
+ Spatio-Temporal Guidance (STG): https://huggingface.co/papers/2411.18664
35
+
36
+ SLG was introduced by StabilityAI for improving structure and anotomy coherence in generated images. It works by
37
+ skipping the forward pass of specified transformer blocks during the denoising process on an additional conditional
38
+ batch of data, apart from the conditional and unconditional batches already used in CFG
39
+ ([~guiders.classifier_free_guidance.ClassifierFreeGuidance]), and then scaling and shifting the CFG predictions
40
+ based on the difference between conditional without skipping and conditional with skipping predictions.
41
+
42
+ The intution behind SLG can be thought of as moving the CFG predicted distribution estimates further away from
43
+ worse versions of the conditional distribution estimates (because skipping layers is equivalent to using a worse
44
+ version of the model for the conditional prediction).
45
+
46
+ STG is an improvement and follow-up work combining ideas from SLG, PAG and similar techniques for improving
47
+ generation quality in video diffusion models.
48
+
49
+ Additional reading:
50
+ - [Guiding a Diffusion Model with a Bad Version of Itself](https://huggingface.co/papers/2406.02507)
51
+
52
+ The values for `skip_layer_guidance_scale`, `skip_layer_guidance_start`, and `skip_layer_guidance_stop` are
53
+ defaulted to the recommendations by StabilityAI for Stable Diffusion 3.5 Medium.
54
+
55
+ Args:
56
+ guidance_scale (`float`, defaults to `7.5`):
57
+ The scale parameter for classifier-free guidance. Higher values result in stronger conditioning on the text
58
+ prompt, while lower values allow for more freedom in generation. Higher values may lead to saturation and
59
+ deterioration of image quality.
60
+ skip_layer_guidance_scale (`float`, defaults to `2.8`):
61
+ The scale parameter for skip layer guidance. Anatomy and structure coherence may improve with higher
62
+ values, but it may also lead to overexposure and saturation.
63
+ skip_layer_guidance_start (`float`, defaults to `0.01`):
64
+ The fraction of the total number of denoising steps after which skip layer guidance starts.
65
+ skip_layer_guidance_stop (`float`, defaults to `0.2`):
66
+ The fraction of the total number of denoising steps after which skip layer guidance stops.
67
+ skip_layer_guidance_layers (`int` or `List[int]`, *optional*):
68
+ The layer indices to apply skip layer guidance to. Can be a single integer or a list of integers. If not
69
+ provided, `skip_layer_config` must be provided. The recommended values are `[7, 8, 9]` for Stable Diffusion
70
+ 3.5 Medium.
71
+ skip_layer_config (`LayerSkipConfig` or `List[LayerSkipConfig]`, *optional*):
72
+ The configuration for the skip layer guidance. Can be a single `LayerSkipConfig` or a list of
73
+ `LayerSkipConfig`. If not provided, `skip_layer_guidance_layers` must be provided.
74
+ guidance_rescale (`float`, defaults to `0.0`):
75
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
76
+ overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
77
+ Flawed](https://huggingface.co/papers/2305.08891).
78
+ use_original_formulation (`bool`, defaults to `False`):
79
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
80
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
81
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
82
+ start (`float`, defaults to `0.01`):
83
+ The fraction of the total number of denoising steps after which guidance starts.
84
+ stop (`float`, defaults to `0.2`):
85
+ The fraction of the total number of denoising steps after which guidance stops.
86
+ """
87
+
88
+ _input_predictions = ["pred_cond", "pred_uncond", "pred_cond_skip"]
89
+
90
+ @register_to_config
91
+ def __init__(
92
+ self,
93
+ guidance_scale: float = 7.5,
94
+ skip_layer_guidance_scale: float = 2.8,
95
+ skip_layer_guidance_start: float = 0.01,
96
+ skip_layer_guidance_stop: float = 0.2,
97
+ skip_layer_guidance_layers: Optional[Union[int, List[int]]] = None,
98
+ skip_layer_config: Union[LayerSkipConfig, List[LayerSkipConfig], Dict[str, Any]] = None,
99
+ guidance_rescale: float = 0.0,
100
+ use_original_formulation: bool = False,
101
+ start: float = 0.0,
102
+ stop: float = 1.0,
103
+ enabled: bool = True,
104
+ ):
105
+ super().__init__(start, stop, enabled)
106
+
107
+ self.guidance_scale = guidance_scale
108
+ self.skip_layer_guidance_scale = skip_layer_guidance_scale
109
+ self.skip_layer_guidance_start = skip_layer_guidance_start
110
+ self.skip_layer_guidance_stop = skip_layer_guidance_stop
111
+ self.guidance_rescale = guidance_rescale
112
+ self.use_original_formulation = use_original_formulation
113
+
114
+ if not (0.0 <= skip_layer_guidance_start < 1.0):
115
+ raise ValueError(
116
+ f"Expected `skip_layer_guidance_start` to be between 0.0 and 1.0, but got {skip_layer_guidance_start}."
117
+ )
118
+ if not (skip_layer_guidance_start <= skip_layer_guidance_stop <= 1.0):
119
+ raise ValueError(
120
+ f"Expected `skip_layer_guidance_stop` to be between 0.0 and 1.0, but got {skip_layer_guidance_stop}."
121
+ )
122
+
123
+ if skip_layer_guidance_layers is None and skip_layer_config is None:
124
+ raise ValueError(
125
+ "Either `skip_layer_guidance_layers` or `skip_layer_config` must be provided to enable Skip Layer Guidance."
126
+ )
127
+ if skip_layer_guidance_layers is not None and skip_layer_config is not None:
128
+ raise ValueError("Only one of `skip_layer_guidance_layers` or `skip_layer_config` can be provided.")
129
+
130
+ if skip_layer_guidance_layers is not None:
131
+ if isinstance(skip_layer_guidance_layers, int):
132
+ skip_layer_guidance_layers = [skip_layer_guidance_layers]
133
+ if not isinstance(skip_layer_guidance_layers, list):
134
+ raise ValueError(
135
+ f"Expected `skip_layer_guidance_layers` to be an int or a list of ints, but got {type(skip_layer_guidance_layers)}."
136
+ )
137
+ skip_layer_config = [LayerSkipConfig(layer, fqn="auto") for layer in skip_layer_guidance_layers]
138
+
139
+ if isinstance(skip_layer_config, dict):
140
+ skip_layer_config = LayerSkipConfig.from_dict(skip_layer_config)
141
+
142
+ if isinstance(skip_layer_config, LayerSkipConfig):
143
+ skip_layer_config = [skip_layer_config]
144
+
145
+ if not isinstance(skip_layer_config, list):
146
+ raise ValueError(
147
+ f"Expected `skip_layer_config` to be a LayerSkipConfig or a list of LayerSkipConfig, but got {type(skip_layer_config)}."
148
+ )
149
+ elif isinstance(next(iter(skip_layer_config), None), dict):
150
+ skip_layer_config = [LayerSkipConfig.from_dict(config) for config in skip_layer_config]
151
+
152
+ self.skip_layer_config = skip_layer_config
153
+ self._skip_layer_hook_names = [f"SkipLayerGuidance_{i}" for i in range(len(self.skip_layer_config))]
154
+
155
+ def prepare_models(self, denoiser: torch.nn.Module) -> None:
156
+ self._count_prepared += 1
157
+ if self._is_slg_enabled() and self.is_conditional and self._count_prepared > 1:
158
+ for name, config in zip(self._skip_layer_hook_names, self.skip_layer_config):
159
+ _apply_layer_skip_hook(denoiser, config, name=name)
160
+
161
+ def cleanup_models(self, denoiser: torch.nn.Module) -> None:
162
+ if self._is_slg_enabled() and self.is_conditional and self._count_prepared > 1:
163
+ registry = HookRegistry.check_if_exists_or_initialize(denoiser)
164
+ # Remove the hooks after inference
165
+ for hook_name in self._skip_layer_hook_names:
166
+ registry.remove_hook(hook_name, recurse=True)
167
+
168
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
169
+ if self.num_conditions == 1:
170
+ tuple_indices = [0]
171
+ input_predictions = ["pred_cond"]
172
+ elif self.num_conditions == 2:
173
+ tuple_indices = [0, 1]
174
+ input_predictions = (
175
+ ["pred_cond", "pred_uncond"] if self._is_cfg_enabled() else ["pred_cond", "pred_cond_skip"]
176
+ )
177
+ else:
178
+ tuple_indices = [0, 1, 0]
179
+ input_predictions = ["pred_cond", "pred_uncond", "pred_cond_skip"]
180
+ data_batches = []
181
+ for tuple_idx, input_prediction in zip(tuple_indices, input_predictions):
182
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
183
+ data_batches.append(data_batch)
184
+ return data_batches
185
+
186
+ def prepare_inputs_from_block_state(
187
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
188
+ ) -> List["BlockState"]:
189
+ if self.num_conditions == 1:
190
+ tuple_indices = [0]
191
+ input_predictions = ["pred_cond"]
192
+ elif self.num_conditions == 2:
193
+ tuple_indices = [0, 1]
194
+ input_predictions = (
195
+ ["pred_cond", "pred_uncond"] if self._is_cfg_enabled() else ["pred_cond", "pred_cond_skip"]
196
+ )
197
+ else:
198
+ tuple_indices = [0, 1, 0]
199
+ input_predictions = ["pred_cond", "pred_uncond", "pred_cond_skip"]
200
+ data_batches = []
201
+ for tuple_idx, input_prediction in zip(tuple_indices, input_predictions):
202
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
203
+ data_batches.append(data_batch)
204
+ return data_batches
205
+
206
+ def forward(
207
+ self,
208
+ pred_cond: torch.Tensor,
209
+ pred_uncond: Optional[torch.Tensor] = None,
210
+ pred_cond_skip: Optional[torch.Tensor] = None,
211
+ ) -> GuiderOutput:
212
+ pred = None
213
+
214
+ if not self._is_cfg_enabled() and not self._is_slg_enabled():
215
+ pred = pred_cond
216
+ elif not self._is_cfg_enabled():
217
+ shift = pred_cond - pred_cond_skip
218
+ pred = pred_cond if self.use_original_formulation else pred_cond_skip
219
+ pred = pred + self.skip_layer_guidance_scale * shift
220
+ elif not self._is_slg_enabled():
221
+ shift = pred_cond - pred_uncond
222
+ pred = pred_cond if self.use_original_formulation else pred_uncond
223
+ pred = pred + self.guidance_scale * shift
224
+ else:
225
+ shift = pred_cond - pred_uncond
226
+ shift_skip = pred_cond - pred_cond_skip
227
+ pred = pred_cond if self.use_original_formulation else pred_uncond
228
+ pred = pred + self.guidance_scale * shift + self.skip_layer_guidance_scale * shift_skip
229
+
230
+ if self.guidance_rescale > 0.0:
231
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
232
+
233
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
234
+
235
+ @property
236
+ def is_conditional(self) -> bool:
237
+ return self._count_prepared == 1 or self._count_prepared == 3
238
+
239
+ @property
240
+ def num_conditions(self) -> int:
241
+ num_conditions = 1
242
+ if self._is_cfg_enabled():
243
+ num_conditions += 1
244
+ if self._is_slg_enabled():
245
+ num_conditions += 1
246
+ return num_conditions
247
+
248
+ def _is_cfg_enabled(self) -> bool:
249
+ if not self._enabled:
250
+ return False
251
+
252
+ is_within_range = True
253
+ if self._num_inference_steps is not None:
254
+ skip_start_step = int(self._start * self._num_inference_steps)
255
+ skip_stop_step = int(self._stop * self._num_inference_steps)
256
+ is_within_range = skip_start_step <= self._step < skip_stop_step
257
+
258
+ is_close = False
259
+ if self.use_original_formulation:
260
+ is_close = math.isclose(self.guidance_scale, 0.0)
261
+ else:
262
+ is_close = math.isclose(self.guidance_scale, 1.0)
263
+
264
+ return is_within_range and not is_close
265
+
266
+ def _is_slg_enabled(self) -> bool:
267
+ if not self._enabled:
268
+ return False
269
+
270
+ is_within_range = True
271
+ if self._num_inference_steps is not None:
272
+ skip_start_step = int(self.skip_layer_guidance_start * self._num_inference_steps)
273
+ skip_stop_step = int(self.skip_layer_guidance_stop * self._num_inference_steps)
274
+ is_within_range = skip_start_step < self._step < skip_stop_step
275
+
276
+ is_zero = math.isclose(self.skip_layer_guidance_scale, 0.0)
277
+
278
+ return is_within_range and not is_zero
vendor/diffusers/guiders/smoothed_energy_guidance.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from ..hooks import HookRegistry
22
+ from ..hooks.smoothed_energy_guidance_utils import SmoothedEnergyGuidanceConfig, _apply_smoothed_energy_guidance_hook
23
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
24
+
25
+
26
+ if TYPE_CHECKING:
27
+ from ..modular_pipelines.modular_pipeline import BlockState
28
+
29
+
30
+ class SmoothedEnergyGuidance(BaseGuidance):
31
+ """
32
+ Smoothed Energy Guidance (SEG): https://huggingface.co/papers/2408.00760
33
+
34
+ SEG is only supported as an experimental prototype feature for now, so the implementation may be modified in the
35
+ future without warning or guarantee of reproducibility. This implementation assumes:
36
+ - Generated images are square (height == width)
37
+ - The model does not combine different modalities together (e.g., text and image latent streams are not combined
38
+ together such as Flux)
39
+
40
+ Args:
41
+ guidance_scale (`float`, defaults to `7.5`):
42
+ The scale parameter for classifier-free guidance. Higher values result in stronger conditioning on the text
43
+ prompt, while lower values allow for more freedom in generation. Higher values may lead to saturation and
44
+ deterioration of image quality.
45
+ seg_guidance_scale (`float`, defaults to `3.0`):
46
+ The scale parameter for smoothed energy guidance. Anatomy and structure coherence may improve with higher
47
+ values, but it may also lead to overexposure and saturation.
48
+ seg_blur_sigma (`float`, defaults to `9999999.0`):
49
+ The amount by which we blur the attention weights. Setting this value greater than 9999.0 results in
50
+ infinite blur, which means uniform queries. Controlling it exponentially is empirically effective.
51
+ seg_blur_threshold_inf (`float`, defaults to `9999.0`):
52
+ The threshold above which the blur is considered infinite.
53
+ seg_guidance_start (`float`, defaults to `0.0`):
54
+ The fraction of the total number of denoising steps after which smoothed energy guidance starts.
55
+ seg_guidance_stop (`float`, defaults to `1.0`):
56
+ The fraction of the total number of denoising steps after which smoothed energy guidance stops.
57
+ seg_guidance_layers (`int` or `List[int]`, *optional*):
58
+ The layer indices to apply smoothed energy guidance to. Can be a single integer or a list of integers. If
59
+ not provided, `seg_guidance_config` must be provided. The recommended values are `[7, 8, 9]` for Stable
60
+ Diffusion 3.5 Medium.
61
+ seg_guidance_config (`SmoothedEnergyGuidanceConfig` or `List[SmoothedEnergyGuidanceConfig]`, *optional*):
62
+ The configuration for the smoothed energy layer guidance. Can be a single `SmoothedEnergyGuidanceConfig` or
63
+ a list of `SmoothedEnergyGuidanceConfig`. If not provided, `seg_guidance_layers` must be provided.
64
+ guidance_rescale (`float`, defaults to `0.0`):
65
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
66
+ overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
67
+ Flawed](https://huggingface.co/papers/2305.08891).
68
+ use_original_formulation (`bool`, defaults to `False`):
69
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
70
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
71
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
72
+ start (`float`, defaults to `0.01`):
73
+ The fraction of the total number of denoising steps after which guidance starts.
74
+ stop (`float`, defaults to `0.2`):
75
+ The fraction of the total number of denoising steps after which guidance stops.
76
+ """
77
+
78
+ _input_predictions = ["pred_cond", "pred_uncond", "pred_cond_seg"]
79
+
80
+ @register_to_config
81
+ def __init__(
82
+ self,
83
+ guidance_scale: float = 7.5,
84
+ seg_guidance_scale: float = 2.8,
85
+ seg_blur_sigma: float = 9999999.0,
86
+ seg_blur_threshold_inf: float = 9999.0,
87
+ seg_guidance_start: float = 0.0,
88
+ seg_guidance_stop: float = 1.0,
89
+ seg_guidance_layers: Optional[Union[int, List[int]]] = None,
90
+ seg_guidance_config: Union[SmoothedEnergyGuidanceConfig, List[SmoothedEnergyGuidanceConfig]] = None,
91
+ guidance_rescale: float = 0.0,
92
+ use_original_formulation: bool = False,
93
+ start: float = 0.0,
94
+ stop: float = 1.0,
95
+ enabled: bool = True,
96
+ ):
97
+ super().__init__(start, stop, enabled)
98
+
99
+ self.guidance_scale = guidance_scale
100
+ self.seg_guidance_scale = seg_guidance_scale
101
+ self.seg_blur_sigma = seg_blur_sigma
102
+ self.seg_blur_threshold_inf = seg_blur_threshold_inf
103
+ self.seg_guidance_start = seg_guidance_start
104
+ self.seg_guidance_stop = seg_guidance_stop
105
+ self.guidance_rescale = guidance_rescale
106
+ self.use_original_formulation = use_original_formulation
107
+
108
+ if not (0.0 <= seg_guidance_start < 1.0):
109
+ raise ValueError(f"Expected `seg_guidance_start` to be between 0.0 and 1.0, but got {seg_guidance_start}.")
110
+ if not (seg_guidance_start <= seg_guidance_stop <= 1.0):
111
+ raise ValueError(f"Expected `seg_guidance_stop` to be between 0.0 and 1.0, but got {seg_guidance_stop}.")
112
+
113
+ if seg_guidance_layers is None and seg_guidance_config is None:
114
+ raise ValueError(
115
+ "Either `seg_guidance_layers` or `seg_guidance_config` must be provided to enable Smoothed Energy Guidance."
116
+ )
117
+ if seg_guidance_layers is not None and seg_guidance_config is not None:
118
+ raise ValueError("Only one of `seg_guidance_layers` or `seg_guidance_config` can be provided.")
119
+
120
+ if seg_guidance_layers is not None:
121
+ if isinstance(seg_guidance_layers, int):
122
+ seg_guidance_layers = [seg_guidance_layers]
123
+ if not isinstance(seg_guidance_layers, list):
124
+ raise ValueError(
125
+ f"Expected `seg_guidance_layers` to be an int or a list of ints, but got {type(seg_guidance_layers)}."
126
+ )
127
+ seg_guidance_config = [SmoothedEnergyGuidanceConfig(layer, fqn="auto") for layer in seg_guidance_layers]
128
+
129
+ if isinstance(seg_guidance_config, dict):
130
+ seg_guidance_config = SmoothedEnergyGuidanceConfig.from_dict(seg_guidance_config)
131
+
132
+ if isinstance(seg_guidance_config, SmoothedEnergyGuidanceConfig):
133
+ seg_guidance_config = [seg_guidance_config]
134
+
135
+ if not isinstance(seg_guidance_config, list):
136
+ raise ValueError(
137
+ f"Expected `seg_guidance_config` to be a SmoothedEnergyGuidanceConfig or a list of SmoothedEnergyGuidanceConfig, but got {type(seg_guidance_config)}."
138
+ )
139
+ elif isinstance(next(iter(seg_guidance_config), None), dict):
140
+ seg_guidance_config = [SmoothedEnergyGuidanceConfig.from_dict(config) for config in seg_guidance_config]
141
+
142
+ self.seg_guidance_config = seg_guidance_config
143
+ self._seg_layer_hook_names = [f"SmoothedEnergyGuidance_{i}" for i in range(len(self.seg_guidance_config))]
144
+
145
+ def prepare_models(self, denoiser: torch.nn.Module) -> None:
146
+ if self._is_seg_enabled() and self.is_conditional and self._count_prepared > 1:
147
+ for name, config in zip(self._seg_layer_hook_names, self.seg_guidance_config):
148
+ _apply_smoothed_energy_guidance_hook(denoiser, config, self.seg_blur_sigma, name=name)
149
+
150
+ def cleanup_models(self, denoiser: torch.nn.Module):
151
+ if self._is_seg_enabled() and self.is_conditional and self._count_prepared > 1:
152
+ registry = HookRegistry.check_if_exists_or_initialize(denoiser)
153
+ # Remove the hooks after inference
154
+ for hook_name in self._seg_layer_hook_names:
155
+ registry.remove_hook(hook_name, recurse=True)
156
+
157
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
158
+ if self.num_conditions == 1:
159
+ tuple_indices = [0]
160
+ input_predictions = ["pred_cond"]
161
+ elif self.num_conditions == 2:
162
+ tuple_indices = [0, 1]
163
+ input_predictions = (
164
+ ["pred_cond", "pred_uncond"] if self._is_cfg_enabled() else ["pred_cond", "pred_cond_seg"]
165
+ )
166
+ else:
167
+ tuple_indices = [0, 1, 0]
168
+ input_predictions = ["pred_cond", "pred_uncond", "pred_cond_seg"]
169
+ data_batches = []
170
+ for tuple_idx, input_prediction in zip(tuple_indices, input_predictions):
171
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
172
+ data_batches.append(data_batch)
173
+ return data_batches
174
+
175
+ def prepare_inputs_from_block_state(
176
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
177
+ ) -> List["BlockState"]:
178
+ if self.num_conditions == 1:
179
+ tuple_indices = [0]
180
+ input_predictions = ["pred_cond"]
181
+ elif self.num_conditions == 2:
182
+ tuple_indices = [0, 1]
183
+ input_predictions = (
184
+ ["pred_cond", "pred_uncond"] if self._is_cfg_enabled() else ["pred_cond", "pred_cond_seg"]
185
+ )
186
+ else:
187
+ tuple_indices = [0, 1, 0]
188
+ input_predictions = ["pred_cond", "pred_uncond", "pred_cond_seg"]
189
+ data_batches = []
190
+ for tuple_idx, input_prediction in zip(tuple_indices, input_predictions):
191
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
192
+ data_batches.append(data_batch)
193
+ return data_batches
194
+
195
+ def forward(
196
+ self,
197
+ pred_cond: torch.Tensor,
198
+ pred_uncond: Optional[torch.Tensor] = None,
199
+ pred_cond_seg: Optional[torch.Tensor] = None,
200
+ ) -> GuiderOutput:
201
+ pred = None
202
+
203
+ if not self._is_cfg_enabled() and not self._is_seg_enabled():
204
+ pred = pred_cond
205
+ elif not self._is_cfg_enabled():
206
+ shift = pred_cond - pred_cond_seg
207
+ pred = pred_cond if self.use_original_formulation else pred_cond_seg
208
+ pred = pred + self.seg_guidance_scale * shift
209
+ elif not self._is_seg_enabled():
210
+ shift = pred_cond - pred_uncond
211
+ pred = pred_cond if self.use_original_formulation else pred_uncond
212
+ pred = pred + self.guidance_scale * shift
213
+ else:
214
+ shift = pred_cond - pred_uncond
215
+ shift_seg = pred_cond - pred_cond_seg
216
+ pred = pred_cond if self.use_original_formulation else pred_uncond
217
+ pred = pred + self.guidance_scale * shift + self.seg_guidance_scale * shift_seg
218
+
219
+ if self.guidance_rescale > 0.0:
220
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
221
+
222
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
223
+
224
+ @property
225
+ def is_conditional(self) -> bool:
226
+ return self._count_prepared == 1 or self._count_prepared == 3
227
+
228
+ @property
229
+ def num_conditions(self) -> int:
230
+ num_conditions = 1
231
+ if self._is_cfg_enabled():
232
+ num_conditions += 1
233
+ if self._is_seg_enabled():
234
+ num_conditions += 1
235
+ return num_conditions
236
+
237
+ def _is_cfg_enabled(self) -> bool:
238
+ if not self._enabled:
239
+ return False
240
+
241
+ is_within_range = True
242
+ if self._num_inference_steps is not None:
243
+ skip_start_step = int(self._start * self._num_inference_steps)
244
+ skip_stop_step = int(self._stop * self._num_inference_steps)
245
+ is_within_range = skip_start_step <= self._step < skip_stop_step
246
+
247
+ is_close = False
248
+ if self.use_original_formulation:
249
+ is_close = math.isclose(self.guidance_scale, 0.0)
250
+ else:
251
+ is_close = math.isclose(self.guidance_scale, 1.0)
252
+
253
+ return is_within_range and not is_close
254
+
255
+ def _is_seg_enabled(self) -> bool:
256
+ if not self._enabled:
257
+ return False
258
+
259
+ is_within_range = True
260
+ if self._num_inference_steps is not None:
261
+ skip_start_step = int(self.seg_guidance_start * self._num_inference_steps)
262
+ skip_stop_step = int(self.seg_guidance_stop * self._num_inference_steps)
263
+ is_within_range = skip_start_step < self._step < skip_stop_step
264
+
265
+ is_zero = math.isclose(self.seg_guidance_scale, 0.0)
266
+
267
+ return is_within_range and not is_zero
vendor/diffusers/guiders/tangential_classifier_free_guidance.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..configuration_utils import register_to_config
21
+ from .guider_utils import BaseGuidance, GuiderOutput, rescale_noise_cfg
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from ..modular_pipelines.modular_pipeline import BlockState
26
+
27
+
28
+ class TangentialClassifierFreeGuidance(BaseGuidance):
29
+ """
30
+ Tangential Classifier Free Guidance (TCFG): https://huggingface.co/papers/2503.18137
31
+
32
+ Args:
33
+ guidance_scale (`float`, defaults to `7.5`):
34
+ The scale parameter for classifier-free guidance. Higher values result in stronger conditioning on the text
35
+ prompt, while lower values allow for more freedom in generation. Higher values may lead to saturation and
36
+ deterioration of image quality.
37
+ guidance_rescale (`float`, defaults to `0.0`):
38
+ The rescale factor applied to the noise predictions. This is used to improve image quality and fix
39
+ overexposure. Based on Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
40
+ Flawed](https://huggingface.co/papers/2305.08891).
41
+ use_original_formulation (`bool`, defaults to `False`):
42
+ Whether to use the original formulation of classifier-free guidance as proposed in the paper. By default,
43
+ we use the diffusers-native implementation that has been in the codebase for a long time. See
44
+ [~guiders.classifier_free_guidance.ClassifierFreeGuidance] for more details.
45
+ start (`float`, defaults to `0.0`):
46
+ The fraction of the total number of denoising steps after which guidance starts.
47
+ stop (`float`, defaults to `1.0`):
48
+ The fraction of the total number of denoising steps after which guidance stops.
49
+ """
50
+
51
+ _input_predictions = ["pred_cond", "pred_uncond"]
52
+
53
+ @register_to_config
54
+ def __init__(
55
+ self,
56
+ guidance_scale: float = 7.5,
57
+ guidance_rescale: float = 0.0,
58
+ use_original_formulation: bool = False,
59
+ start: float = 0.0,
60
+ stop: float = 1.0,
61
+ enabled: bool = True,
62
+ ):
63
+ super().__init__(start, stop, enabled)
64
+
65
+ self.guidance_scale = guidance_scale
66
+ self.guidance_rescale = guidance_rescale
67
+ self.use_original_formulation = use_original_formulation
68
+
69
+ def prepare_inputs(self, data: Dict[str, Tuple[torch.Tensor, torch.Tensor]]) -> List["BlockState"]:
70
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
71
+ data_batches = []
72
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
73
+ data_batch = self._prepare_batch(data, tuple_idx, input_prediction)
74
+ data_batches.append(data_batch)
75
+ return data_batches
76
+
77
+ def prepare_inputs_from_block_state(
78
+ self, data: "BlockState", input_fields: Dict[str, Union[str, Tuple[str, str]]]
79
+ ) -> List["BlockState"]:
80
+ tuple_indices = [0] if self.num_conditions == 1 else [0, 1]
81
+ data_batches = []
82
+ for tuple_idx, input_prediction in zip(tuple_indices, self._input_predictions):
83
+ data_batch = self._prepare_batch_from_block_state(input_fields, data, tuple_idx, input_prediction)
84
+ data_batches.append(data_batch)
85
+ return data_batches
86
+
87
+ def forward(self, pred_cond: torch.Tensor, pred_uncond: Optional[torch.Tensor] = None) -> GuiderOutput:
88
+ pred = None
89
+
90
+ if not self._is_tcfg_enabled():
91
+ pred = pred_cond
92
+ else:
93
+ pred = normalized_guidance(pred_cond, pred_uncond, self.guidance_scale, self.use_original_formulation)
94
+
95
+ if self.guidance_rescale > 0.0:
96
+ pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
97
+
98
+ return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
99
+
100
+ @property
101
+ def is_conditional(self) -> bool:
102
+ return self._num_outputs_prepared == 1
103
+
104
+ @property
105
+ def num_conditions(self) -> int:
106
+ num_conditions = 1
107
+ if self._is_tcfg_enabled():
108
+ num_conditions += 1
109
+ return num_conditions
110
+
111
+ def _is_tcfg_enabled(self) -> bool:
112
+ if not self._enabled:
113
+ return False
114
+
115
+ is_within_range = True
116
+ if self._num_inference_steps is not None:
117
+ skip_start_step = int(self._start * self._num_inference_steps)
118
+ skip_stop_step = int(self._stop * self._num_inference_steps)
119
+ is_within_range = skip_start_step <= self._step < skip_stop_step
120
+
121
+ is_close = False
122
+ if self.use_original_formulation:
123
+ is_close = math.isclose(self.guidance_scale, 0.0)
124
+ else:
125
+ is_close = math.isclose(self.guidance_scale, 1.0)
126
+
127
+ return is_within_range and not is_close
128
+
129
+
130
+ def normalized_guidance(
131
+ pred_cond: torch.Tensor, pred_uncond: torch.Tensor, guidance_scale: float, use_original_formulation: bool = False
132
+ ) -> torch.Tensor:
133
+ cond_dtype = pred_cond.dtype
134
+ preds = torch.stack([pred_cond, pred_uncond], dim=1).float()
135
+ preds = preds.flatten(2)
136
+ U, S, Vh = torch.linalg.svd(preds, full_matrices=False)
137
+ Vh_modified = Vh.clone()
138
+ Vh_modified[:, 1] = 0
139
+
140
+ uncond_flat = pred_uncond.reshape(pred_uncond.size(0), 1, -1).float()
141
+ x_Vh = torch.matmul(uncond_flat, Vh.transpose(-2, -1))
142
+ x_Vh_V = torch.matmul(x_Vh, Vh_modified)
143
+ pred_uncond = x_Vh_V.reshape(pred_uncond.shape).to(cond_dtype)
144
+
145
+ pred = pred_cond if use_original_formulation else pred_uncond
146
+ shift = pred_cond - pred_uncond
147
+ pred = pred + guidance_scale * shift
148
+
149
+ return pred
vendor/diffusers/hooks/__init__.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 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
+
15
+ from ..utils import is_torch_available
16
+
17
+
18
+ if is_torch_available():
19
+ from .context_parallel import apply_context_parallel
20
+ from .faster_cache import FasterCacheConfig, apply_faster_cache
21
+ from .first_block_cache import FirstBlockCacheConfig, apply_first_block_cache
22
+ from .group_offloading import apply_group_offloading
23
+ from .hooks import HookRegistry, ModelHook
24
+ from .layer_skip import LayerSkipConfig, apply_layer_skip
25
+ from .layerwise_casting import apply_layerwise_casting, apply_layerwise_casting_hook
26
+ from .pyramid_attention_broadcast import PyramidAttentionBroadcastConfig, apply_pyramid_attention_broadcast
27
+ from .smoothed_energy_guidance_utils import SmoothedEnergyGuidanceConfig
28
+ from .taylorseer_cache import TaylorSeerCacheConfig, apply_taylorseer_cache
vendor/diffusers/hooks/_common.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ from typing import Optional
16
+
17
+ import torch
18
+
19
+ from ..models.attention import AttentionModuleMixin, FeedForward, LuminaFeedForward
20
+ from ..models.attention_processor import Attention, MochiAttention
21
+
22
+
23
+ _ATTENTION_CLASSES = (Attention, MochiAttention, AttentionModuleMixin)
24
+ _FEEDFORWARD_CLASSES = (FeedForward, LuminaFeedForward)
25
+
26
+ _SPATIAL_TRANSFORMER_BLOCK_IDENTIFIERS = ("blocks", "transformer_blocks", "single_transformer_blocks", "layers")
27
+ _TEMPORAL_TRANSFORMER_BLOCK_IDENTIFIERS = ("temporal_transformer_blocks",)
28
+ _CROSS_TRANSFORMER_BLOCK_IDENTIFIERS = ("blocks", "transformer_blocks", "layers")
29
+
30
+ _ALL_TRANSFORMER_BLOCK_IDENTIFIERS = tuple(
31
+ {
32
+ *_SPATIAL_TRANSFORMER_BLOCK_IDENTIFIERS,
33
+ *_TEMPORAL_TRANSFORMER_BLOCK_IDENTIFIERS,
34
+ *_CROSS_TRANSFORMER_BLOCK_IDENTIFIERS,
35
+ }
36
+ )
37
+
38
+ # Layers supported for group offloading and layerwise casting
39
+ _GO_LC_SUPPORTED_PYTORCH_LAYERS = (
40
+ torch.nn.Conv1d,
41
+ torch.nn.Conv2d,
42
+ torch.nn.Conv3d,
43
+ torch.nn.ConvTranspose1d,
44
+ torch.nn.ConvTranspose2d,
45
+ torch.nn.ConvTranspose3d,
46
+ torch.nn.Linear,
47
+ # TODO(aryan): look into torch.nn.LayerNorm, torch.nn.GroupNorm later, seems to be causing some issues with CogVideoX
48
+ # because of double invocation of the same norm layer in CogVideoXLayerNorm
49
+ )
50
+
51
+
52
+ def _get_submodule_from_fqn(module: torch.nn.Module, fqn: str) -> Optional[torch.nn.Module]:
53
+ for submodule_name, submodule in module.named_modules():
54
+ if submodule_name == fqn:
55
+ return submodule
56
+ return None
vendor/diffusers/hooks/_helpers.py ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import inspect
16
+ from dataclasses import dataclass
17
+ from typing import Any, Callable, Dict, Type
18
+
19
+
20
+ @dataclass
21
+ class AttentionProcessorMetadata:
22
+ skip_processor_output_fn: Callable[[Any], Any]
23
+
24
+
25
+ @dataclass
26
+ class TransformerBlockMetadata:
27
+ return_hidden_states_index: int = None
28
+ return_encoder_hidden_states_index: int = None
29
+
30
+ _cls: Type = None
31
+ _cached_parameter_indices: Dict[str, int] = None
32
+
33
+ def _get_parameter_from_args_kwargs(self, identifier: str, args=(), kwargs=None):
34
+ kwargs = kwargs or {}
35
+ if identifier in kwargs:
36
+ return kwargs[identifier]
37
+ if self._cached_parameter_indices is not None:
38
+ return args[self._cached_parameter_indices[identifier]]
39
+ if self._cls is None:
40
+ raise ValueError("Model class is not set for metadata.")
41
+ parameters = list(inspect.signature(self._cls.forward).parameters.keys())
42
+ parameters = parameters[1:] # skip `self`
43
+ self._cached_parameter_indices = {param: i for i, param in enumerate(parameters)}
44
+ if identifier not in self._cached_parameter_indices:
45
+ raise ValueError(f"Parameter '{identifier}' not found in function signature but was requested.")
46
+ index = self._cached_parameter_indices[identifier]
47
+ if index >= len(args):
48
+ raise ValueError(f"Expected {index} arguments but got {len(args)}.")
49
+ return args[index]
50
+
51
+
52
+ class AttentionProcessorRegistry:
53
+ _registry = {}
54
+ # TODO(aryan): this is only required for the time being because we need to do the registrations
55
+ # for classes. If we do it eagerly, i.e. call the functions in global scope, we will get circular
56
+ # import errors because of the models imported in this file.
57
+ _is_registered = False
58
+
59
+ @classmethod
60
+ def register(cls, model_class: Type, metadata: AttentionProcessorMetadata):
61
+ cls._register()
62
+ cls._registry[model_class] = metadata
63
+
64
+ @classmethod
65
+ def get(cls, model_class: Type) -> AttentionProcessorMetadata:
66
+ cls._register()
67
+ if model_class not in cls._registry:
68
+ raise ValueError(f"Model class {model_class} not registered.")
69
+ return cls._registry[model_class]
70
+
71
+ @classmethod
72
+ def _register(cls):
73
+ if cls._is_registered:
74
+ return
75
+ cls._is_registered = True
76
+ _register_attention_processors_metadata()
77
+
78
+
79
+ class TransformerBlockRegistry:
80
+ _registry = {}
81
+ # TODO(aryan): this is only required for the time being because we need to do the registrations
82
+ # for classes. If we do it eagerly, i.e. call the functions in global scope, we will get circular
83
+ # import errors because of the models imported in this file.
84
+ _is_registered = False
85
+
86
+ @classmethod
87
+ def register(cls, model_class: Type, metadata: TransformerBlockMetadata):
88
+ cls._register()
89
+ metadata._cls = model_class
90
+ cls._registry[model_class] = metadata
91
+
92
+ @classmethod
93
+ def get(cls, model_class: Type) -> TransformerBlockMetadata:
94
+ cls._register()
95
+ if model_class not in cls._registry:
96
+ raise ValueError(f"Model class {model_class} not registered.")
97
+ return cls._registry[model_class]
98
+
99
+ @classmethod
100
+ def _register(cls):
101
+ if cls._is_registered:
102
+ return
103
+ cls._is_registered = True
104
+ _register_transformer_blocks_metadata()
105
+
106
+
107
+ def _register_attention_processors_metadata():
108
+ from ..models.attention_processor import AttnProcessor2_0
109
+ from ..models.transformers.transformer_cogview4 import CogView4AttnProcessor
110
+ from ..models.transformers.transformer_flux import FluxAttnProcessor
111
+ from ..models.transformers.transformer_hunyuanimage import HunyuanImageAttnProcessor
112
+ from ..models.transformers.transformer_qwenimage import QwenDoubleStreamAttnProcessor2_0
113
+ from ..models.transformers.transformer_wan import WanAttnProcessor2_0
114
+ from ..models.transformers.transformer_z_image import ZSingleStreamAttnProcessor
115
+
116
+ # AttnProcessor2_0
117
+ AttentionProcessorRegistry.register(
118
+ model_class=AttnProcessor2_0,
119
+ metadata=AttentionProcessorMetadata(
120
+ skip_processor_output_fn=_skip_proc_output_fn_Attention_AttnProcessor2_0,
121
+ ),
122
+ )
123
+
124
+ # CogView4AttnProcessor
125
+ AttentionProcessorRegistry.register(
126
+ model_class=CogView4AttnProcessor,
127
+ metadata=AttentionProcessorMetadata(
128
+ skip_processor_output_fn=_skip_proc_output_fn_Attention_CogView4AttnProcessor,
129
+ ),
130
+ )
131
+
132
+ # WanAttnProcessor2_0
133
+ AttentionProcessorRegistry.register(
134
+ model_class=WanAttnProcessor2_0,
135
+ metadata=AttentionProcessorMetadata(
136
+ skip_processor_output_fn=_skip_proc_output_fn_Attention_WanAttnProcessor2_0,
137
+ ),
138
+ )
139
+
140
+ # FluxAttnProcessor
141
+ AttentionProcessorRegistry.register(
142
+ model_class=FluxAttnProcessor,
143
+ metadata=AttentionProcessorMetadata(skip_processor_output_fn=_skip_proc_output_fn_Attention_FluxAttnProcessor),
144
+ )
145
+
146
+ # QwenDoubleStreamAttnProcessor2
147
+ AttentionProcessorRegistry.register(
148
+ model_class=QwenDoubleStreamAttnProcessor2_0,
149
+ metadata=AttentionProcessorMetadata(
150
+ skip_processor_output_fn=_skip_proc_output_fn_Attention_QwenDoubleStreamAttnProcessor2_0
151
+ ),
152
+ )
153
+
154
+ # HunyuanImageAttnProcessor
155
+ AttentionProcessorRegistry.register(
156
+ model_class=HunyuanImageAttnProcessor,
157
+ metadata=AttentionProcessorMetadata(
158
+ skip_processor_output_fn=_skip_proc_output_fn_Attention_HunyuanImageAttnProcessor,
159
+ ),
160
+ )
161
+
162
+ # ZSingleStreamAttnProcessor
163
+ AttentionProcessorRegistry.register(
164
+ model_class=ZSingleStreamAttnProcessor,
165
+ metadata=AttentionProcessorMetadata(
166
+ skip_processor_output_fn=_skip_proc_output_fn_Attention_ZSingleStreamAttnProcessor,
167
+ ),
168
+ )
169
+
170
+
171
+ def _register_transformer_blocks_metadata():
172
+ from ..models.attention import BasicTransformerBlock
173
+ from ..models.transformers.cogvideox_transformer_3d import CogVideoXBlock
174
+ from ..models.transformers.transformer_bria import BriaTransformerBlock
175
+ from ..models.transformers.transformer_cogview4 import CogView4TransformerBlock
176
+ from ..models.transformers.transformer_flux import FluxSingleTransformerBlock, FluxTransformerBlock
177
+ from ..models.transformers.transformer_hunyuan_video import (
178
+ HunyuanVideoSingleTransformerBlock,
179
+ HunyuanVideoTokenReplaceSingleTransformerBlock,
180
+ HunyuanVideoTokenReplaceTransformerBlock,
181
+ HunyuanVideoTransformerBlock,
182
+ )
183
+ from ..models.transformers.transformer_hunyuanimage import (
184
+ HunyuanImageSingleTransformerBlock,
185
+ HunyuanImageTransformerBlock,
186
+ )
187
+ from ..models.transformers.transformer_ltx import LTXVideoTransformerBlock
188
+ from ..models.transformers.transformer_mochi import MochiTransformerBlock
189
+ from ..models.transformers.transformer_qwenimage import QwenImageTransformerBlock
190
+ from ..models.transformers.transformer_wan import WanTransformerBlock
191
+ from ..models.transformers.transformer_z_image import ZImageTransformerBlock
192
+
193
+ # BasicTransformerBlock
194
+ TransformerBlockRegistry.register(
195
+ model_class=BasicTransformerBlock,
196
+ metadata=TransformerBlockMetadata(
197
+ return_hidden_states_index=0,
198
+ return_encoder_hidden_states_index=None,
199
+ ),
200
+ )
201
+ TransformerBlockRegistry.register(
202
+ model_class=BriaTransformerBlock,
203
+ metadata=TransformerBlockMetadata(
204
+ return_hidden_states_index=0,
205
+ return_encoder_hidden_states_index=None,
206
+ ),
207
+ )
208
+
209
+ # CogVideoX
210
+ TransformerBlockRegistry.register(
211
+ model_class=CogVideoXBlock,
212
+ metadata=TransformerBlockMetadata(
213
+ return_hidden_states_index=0,
214
+ return_encoder_hidden_states_index=1,
215
+ ),
216
+ )
217
+
218
+ # CogView4
219
+ TransformerBlockRegistry.register(
220
+ model_class=CogView4TransformerBlock,
221
+ metadata=TransformerBlockMetadata(
222
+ return_hidden_states_index=0,
223
+ return_encoder_hidden_states_index=1,
224
+ ),
225
+ )
226
+
227
+ # Flux
228
+ TransformerBlockRegistry.register(
229
+ model_class=FluxTransformerBlock,
230
+ metadata=TransformerBlockMetadata(
231
+ return_hidden_states_index=1,
232
+ return_encoder_hidden_states_index=0,
233
+ ),
234
+ )
235
+ TransformerBlockRegistry.register(
236
+ model_class=FluxSingleTransformerBlock,
237
+ metadata=TransformerBlockMetadata(
238
+ return_hidden_states_index=1,
239
+ return_encoder_hidden_states_index=0,
240
+ ),
241
+ )
242
+
243
+ # HunyuanVideo
244
+ TransformerBlockRegistry.register(
245
+ model_class=HunyuanVideoTransformerBlock,
246
+ metadata=TransformerBlockMetadata(
247
+ return_hidden_states_index=0,
248
+ return_encoder_hidden_states_index=1,
249
+ ),
250
+ )
251
+ TransformerBlockRegistry.register(
252
+ model_class=HunyuanVideoSingleTransformerBlock,
253
+ metadata=TransformerBlockMetadata(
254
+ return_hidden_states_index=0,
255
+ return_encoder_hidden_states_index=1,
256
+ ),
257
+ )
258
+ TransformerBlockRegistry.register(
259
+ model_class=HunyuanVideoTokenReplaceTransformerBlock,
260
+ metadata=TransformerBlockMetadata(
261
+ return_hidden_states_index=0,
262
+ return_encoder_hidden_states_index=1,
263
+ ),
264
+ )
265
+ TransformerBlockRegistry.register(
266
+ model_class=HunyuanVideoTokenReplaceSingleTransformerBlock,
267
+ metadata=TransformerBlockMetadata(
268
+ return_hidden_states_index=0,
269
+ return_encoder_hidden_states_index=1,
270
+ ),
271
+ )
272
+
273
+ # LTXVideo
274
+ TransformerBlockRegistry.register(
275
+ model_class=LTXVideoTransformerBlock,
276
+ metadata=TransformerBlockMetadata(
277
+ return_hidden_states_index=0,
278
+ return_encoder_hidden_states_index=None,
279
+ ),
280
+ )
281
+
282
+ # Mochi
283
+ TransformerBlockRegistry.register(
284
+ model_class=MochiTransformerBlock,
285
+ metadata=TransformerBlockMetadata(
286
+ return_hidden_states_index=0,
287
+ return_encoder_hidden_states_index=1,
288
+ ),
289
+ )
290
+
291
+ # Wan
292
+ TransformerBlockRegistry.register(
293
+ model_class=WanTransformerBlock,
294
+ metadata=TransformerBlockMetadata(
295
+ return_hidden_states_index=0,
296
+ return_encoder_hidden_states_index=None,
297
+ ),
298
+ )
299
+
300
+ # QwenImage
301
+ TransformerBlockRegistry.register(
302
+ model_class=QwenImageTransformerBlock,
303
+ metadata=TransformerBlockMetadata(
304
+ return_hidden_states_index=1,
305
+ return_encoder_hidden_states_index=0,
306
+ ),
307
+ )
308
+
309
+ # HunyuanImage2.1
310
+ TransformerBlockRegistry.register(
311
+ model_class=HunyuanImageTransformerBlock,
312
+ metadata=TransformerBlockMetadata(
313
+ return_hidden_states_index=0,
314
+ return_encoder_hidden_states_index=1,
315
+ ),
316
+ )
317
+ TransformerBlockRegistry.register(
318
+ model_class=HunyuanImageSingleTransformerBlock,
319
+ metadata=TransformerBlockMetadata(
320
+ return_hidden_states_index=0,
321
+ return_encoder_hidden_states_index=1,
322
+ ),
323
+ )
324
+
325
+ # ZImage
326
+ TransformerBlockRegistry.register(
327
+ model_class=ZImageTransformerBlock,
328
+ metadata=TransformerBlockMetadata(
329
+ return_hidden_states_index=0,
330
+ return_encoder_hidden_states_index=None,
331
+ ),
332
+ )
333
+
334
+
335
+ # fmt: off
336
+ def _skip_attention___ret___hidden_states(self, *args, **kwargs):
337
+ hidden_states = kwargs.get("hidden_states", None)
338
+ if hidden_states is None and len(args) > 0:
339
+ hidden_states = args[0]
340
+ return hidden_states
341
+
342
+
343
+ def _skip_attention___ret___hidden_states___encoder_hidden_states(self, *args, **kwargs):
344
+ hidden_states = kwargs.get("hidden_states", None)
345
+ encoder_hidden_states = kwargs.get("encoder_hidden_states", None)
346
+ if hidden_states is None and len(args) > 0:
347
+ hidden_states = args[0]
348
+ if encoder_hidden_states is None and len(args) > 1:
349
+ encoder_hidden_states = args[1]
350
+ return hidden_states, encoder_hidden_states
351
+
352
+
353
+ _skip_proc_output_fn_Attention_AttnProcessor2_0 = _skip_attention___ret___hidden_states
354
+ _skip_proc_output_fn_Attention_CogView4AttnProcessor = _skip_attention___ret___hidden_states___encoder_hidden_states
355
+ _skip_proc_output_fn_Attention_WanAttnProcessor2_0 = _skip_attention___ret___hidden_states
356
+ # not sure what this is yet.
357
+ _skip_proc_output_fn_Attention_FluxAttnProcessor = _skip_attention___ret___hidden_states
358
+ _skip_proc_output_fn_Attention_QwenDoubleStreamAttnProcessor2_0 = _skip_attention___ret___hidden_states
359
+ _skip_proc_output_fn_Attention_HunyuanImageAttnProcessor = _skip_attention___ret___hidden_states
360
+ _skip_proc_output_fn_Attention_ZSingleStreamAttnProcessor = _skip_attention___ret___hidden_states
361
+ # fmt: on
vendor/diffusers/hooks/context_parallel.py ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import inspect
16
+ from dataclasses import dataclass
17
+ from typing import Dict, List, Type, Union
18
+
19
+ import torch
20
+
21
+
22
+ if torch.distributed.is_available():
23
+ import torch.distributed._functional_collectives as funcol
24
+
25
+ from ..models._modeling_parallel import (
26
+ ContextParallelConfig,
27
+ ContextParallelInput,
28
+ ContextParallelModelPlan,
29
+ ContextParallelOutput,
30
+ )
31
+ from ..utils import get_logger
32
+ from ..utils.torch_utils import unwrap_module
33
+ from .hooks import HookRegistry, ModelHook
34
+
35
+
36
+ logger = get_logger(__name__) # pylint: disable=invalid-name
37
+
38
+ _CONTEXT_PARALLEL_INPUT_HOOK_TEMPLATE = "cp_input---{}"
39
+ _CONTEXT_PARALLEL_OUTPUT_HOOK_TEMPLATE = "cp_output---{}"
40
+
41
+
42
+ # TODO(aryan): consolidate with ._helpers.TransformerBlockMetadata
43
+ @dataclass
44
+ class ModuleForwardMetadata:
45
+ cached_parameter_indices: Dict[str, int] = None
46
+ _cls: Type = None
47
+
48
+ def _get_parameter_from_args_kwargs(self, identifier: str, args=(), kwargs=None):
49
+ kwargs = kwargs or {}
50
+
51
+ if identifier in kwargs:
52
+ return kwargs[identifier], True, None
53
+
54
+ if self.cached_parameter_indices is not None:
55
+ index = self.cached_parameter_indices.get(identifier, None)
56
+ if index is None:
57
+ raise ValueError(f"Parameter '{identifier}' not found in cached indices.")
58
+ return args[index], False, index
59
+
60
+ if self._cls is None:
61
+ raise ValueError("Model class is not set for metadata.")
62
+
63
+ parameters = list(inspect.signature(self._cls.forward).parameters.keys())
64
+ parameters = parameters[1:] # skip `self`
65
+ self.cached_parameter_indices = {param: i for i, param in enumerate(parameters)}
66
+
67
+ if identifier not in self.cached_parameter_indices:
68
+ raise ValueError(f"Parameter '{identifier}' not found in function signature but was requested.")
69
+
70
+ index = self.cached_parameter_indices[identifier]
71
+
72
+ if index >= len(args):
73
+ raise ValueError(f"Expected {index} arguments but got {len(args)}.")
74
+
75
+ return args[index], False, index
76
+
77
+
78
+ def apply_context_parallel(
79
+ module: torch.nn.Module,
80
+ parallel_config: ContextParallelConfig,
81
+ plan: Dict[str, ContextParallelModelPlan],
82
+ ) -> None:
83
+ """Apply context parallel on a model."""
84
+ logger.debug(f"Applying context parallel with CP mesh: {parallel_config._mesh} and plan: {plan}")
85
+
86
+ for module_id, cp_model_plan in plan.items():
87
+ submodule = _get_submodule_by_name(module, module_id)
88
+ if not isinstance(submodule, list):
89
+ submodule = [submodule]
90
+
91
+ logger.debug(f"Applying ContextParallelHook to {module_id=} identifying a total of {len(submodule)} modules")
92
+
93
+ for m in submodule:
94
+ if isinstance(cp_model_plan, dict):
95
+ hook = ContextParallelSplitHook(cp_model_plan, parallel_config)
96
+ hook_name = _CONTEXT_PARALLEL_INPUT_HOOK_TEMPLATE.format(module_id)
97
+ elif isinstance(cp_model_plan, (ContextParallelOutput, list, tuple)):
98
+ if isinstance(cp_model_plan, ContextParallelOutput):
99
+ cp_model_plan = [cp_model_plan]
100
+ if not all(isinstance(x, ContextParallelOutput) for x in cp_model_plan):
101
+ raise ValueError(f"Expected all elements of cp_model_plan to be CPOutput, but got {cp_model_plan}")
102
+ hook = ContextParallelGatherHook(cp_model_plan, parallel_config)
103
+ hook_name = _CONTEXT_PARALLEL_OUTPUT_HOOK_TEMPLATE.format(module_id)
104
+ else:
105
+ raise ValueError(f"Unsupported context parallel model plan type: {type(cp_model_plan)}")
106
+ registry = HookRegistry.check_if_exists_or_initialize(m)
107
+ registry.register_hook(hook, hook_name)
108
+
109
+
110
+ def remove_context_parallel(module: torch.nn.Module, plan: Dict[str, ContextParallelModelPlan]) -> None:
111
+ for module_id, cp_model_plan in plan.items():
112
+ submodule = _get_submodule_by_name(module, module_id)
113
+ if not isinstance(submodule, list):
114
+ submodule = [submodule]
115
+
116
+ for m in submodule:
117
+ registry = HookRegistry.check_if_exists_or_initialize(m)
118
+ if isinstance(cp_model_plan, dict):
119
+ hook_name = _CONTEXT_PARALLEL_INPUT_HOOK_TEMPLATE.format(module_id)
120
+ elif isinstance(cp_model_plan, (ContextParallelOutput, list, tuple)):
121
+ hook_name = _CONTEXT_PARALLEL_OUTPUT_HOOK_TEMPLATE.format(module_id)
122
+ else:
123
+ raise ValueError(f"Unsupported context parallel model plan type: {type(cp_model_plan)}")
124
+ registry.remove_hook(hook_name)
125
+
126
+
127
+ class ContextParallelSplitHook(ModelHook):
128
+ def __init__(self, metadata: ContextParallelModelPlan, parallel_config: ContextParallelConfig) -> None:
129
+ super().__init__()
130
+ self.metadata = metadata
131
+ self.parallel_config = parallel_config
132
+ self.module_forward_metadata = None
133
+
134
+ def initialize_hook(self, module):
135
+ cls = unwrap_module(module).__class__
136
+ self.module_forward_metadata = ModuleForwardMetadata(_cls=cls)
137
+ return module
138
+
139
+ def pre_forward(self, module, *args, **kwargs):
140
+ args_list = list(args)
141
+
142
+ for name, cpm in self.metadata.items():
143
+ if isinstance(cpm, ContextParallelInput) and cpm.split_output:
144
+ continue
145
+
146
+ # Maybe the parameter was passed as a keyword argument
147
+ input_val, is_kwarg, index = self.module_forward_metadata._get_parameter_from_args_kwargs(
148
+ name, args_list, kwargs
149
+ )
150
+
151
+ if input_val is None:
152
+ continue
153
+
154
+ # The input_val may be a tensor or list/tuple of tensors. In certain cases, user may specify to shard
155
+ # the output instead of input for a particular layer by setting split_output=True
156
+ if isinstance(input_val, torch.Tensor):
157
+ input_val = self._prepare_cp_input(input_val, cpm)
158
+ elif isinstance(input_val, (list, tuple)):
159
+ if len(input_val) != len(cpm):
160
+ raise ValueError(
161
+ f"Expected input model plan to have {len(input_val)} elements, but got {len(cpm)}."
162
+ )
163
+ sharded_input_val = []
164
+ for i, x in enumerate(input_val):
165
+ if torch.is_tensor(x) and not cpm[i].split_output:
166
+ x = self._prepare_cp_input(x, cpm[i])
167
+ sharded_input_val.append(x)
168
+ input_val = sharded_input_val
169
+ else:
170
+ raise ValueError(f"Unsupported input type: {type(input_val)}")
171
+
172
+ if is_kwarg:
173
+ kwargs[name] = input_val
174
+ elif index is not None and index < len(args_list):
175
+ args_list[index] = input_val
176
+ else:
177
+ raise ValueError(
178
+ f"An unexpected error occurred while processing the input '{name}'. Please open an "
179
+ f"issue at https://github.com/huggingface/diffusers/issues and provide a minimal reproducible "
180
+ f"example along with the full stack trace."
181
+ )
182
+
183
+ return tuple(args_list), kwargs
184
+
185
+ def post_forward(self, module, output):
186
+ is_tensor = isinstance(output, torch.Tensor)
187
+ is_tensor_list = isinstance(output, (list, tuple)) and all(isinstance(x, torch.Tensor) for x in output)
188
+
189
+ if not is_tensor and not is_tensor_list:
190
+ raise ValueError(f"Expected output to be a tensor or a list/tuple of tensors, but got {type(output)}.")
191
+
192
+ output = [output] if is_tensor else list(output)
193
+ for index, cpm in self.metadata.items():
194
+ if not isinstance(cpm, ContextParallelInput) or not cpm.split_output:
195
+ continue
196
+ if index >= len(output):
197
+ raise ValueError(f"Index {index} out of bounds for output of length {len(output)}.")
198
+ current_output = output[index]
199
+ current_output = self._prepare_cp_input(current_output, cpm)
200
+ output[index] = current_output
201
+
202
+ return output[0] if is_tensor else tuple(output)
203
+
204
+ def _prepare_cp_input(self, x: torch.Tensor, cp_input: ContextParallelInput) -> torch.Tensor:
205
+ if cp_input.expected_dims is not None and x.dim() != cp_input.expected_dims:
206
+ logger.warning_once(
207
+ f"Expected input tensor to have {cp_input.expected_dims} dimensions, but got {x.dim()} dimensions, split will not be applied."
208
+ )
209
+ return x
210
+ else:
211
+ return EquipartitionSharder.shard(x, cp_input.split_dim, self.parallel_config._flattened_mesh)
212
+
213
+
214
+ class ContextParallelGatherHook(ModelHook):
215
+ def __init__(self, metadata: ContextParallelModelPlan, parallel_config: ContextParallelConfig) -> None:
216
+ super().__init__()
217
+ self.metadata = metadata
218
+ self.parallel_config = parallel_config
219
+
220
+ def post_forward(self, module, output):
221
+ is_tensor = isinstance(output, torch.Tensor)
222
+
223
+ if is_tensor:
224
+ output = [output]
225
+ elif not (isinstance(output, (list, tuple)) and all(isinstance(x, torch.Tensor) for x in output)):
226
+ raise ValueError(f"Expected output to be a tensor or a list/tuple of tensors, but got {type(output)}.")
227
+
228
+ output = list(output)
229
+
230
+ if len(output) != len(self.metadata):
231
+ raise ValueError(f"Expected output to have {len(self.metadata)} elements, but got {len(output)}.")
232
+
233
+ for i, cpm in enumerate(self.metadata):
234
+ if cpm is None:
235
+ continue
236
+ output[i] = EquipartitionSharder.unshard(output[i], cpm.gather_dim, self.parallel_config._flattened_mesh)
237
+
238
+ return output[0] if is_tensor else tuple(output)
239
+
240
+
241
+ class AllGatherFunction(torch.autograd.Function):
242
+ @staticmethod
243
+ def forward(ctx, tensor, dim, group):
244
+ ctx.dim = dim
245
+ ctx.group = group
246
+ ctx.world_size = torch.distributed.get_world_size(group)
247
+ ctx.rank = torch.distributed.get_rank(group)
248
+ return funcol.all_gather_tensor(tensor, dim, group=group)
249
+
250
+ @staticmethod
251
+ def backward(ctx, grad_output):
252
+ grad_chunks = torch.chunk(grad_output, ctx.world_size, dim=ctx.dim)
253
+ return grad_chunks[ctx.rank], None, None
254
+
255
+
256
+ class EquipartitionSharder:
257
+ @classmethod
258
+ def shard(cls, tensor: torch.Tensor, dim: int, mesh: torch.distributed.device_mesh.DeviceMesh) -> torch.Tensor:
259
+ # NOTE: the following assertion does not have to be true in general. We simply enforce it for now
260
+ # because the alternate case has not yet been tested/required for any model.
261
+ assert tensor.size()[dim] % mesh.size() == 0, (
262
+ "Tensor size along dimension to be sharded must be divisible by mesh size"
263
+ )
264
+
265
+ # The following is not fullgraph compatible with Dynamo (fails in DeviceMesh.get_rank)
266
+ # return tensor.chunk(mesh.size(), dim=dim)[mesh.get_rank()]
267
+
268
+ return tensor.chunk(mesh.size(), dim=dim)[torch.distributed.get_rank(mesh.get_group())]
269
+
270
+ @classmethod
271
+ def unshard(cls, tensor: torch.Tensor, dim: int, mesh: torch.distributed.device_mesh.DeviceMesh) -> torch.Tensor:
272
+ tensor = tensor.contiguous()
273
+ tensor = AllGatherFunction.apply(tensor, dim, mesh.get_group())
274
+ return tensor
275
+
276
+
277
+ def _get_submodule_by_name(model: torch.nn.Module, name: str) -> Union[torch.nn.Module, List[torch.nn.Module]]:
278
+ if name.count("*") > 1:
279
+ raise ValueError("Wildcard '*' can only be used once in the name")
280
+ return _find_submodule_by_name(model, name)
281
+
282
+
283
+ def _find_submodule_by_name(model: torch.nn.Module, name: str) -> Union[torch.nn.Module, List[torch.nn.Module]]:
284
+ if name == "":
285
+ return model
286
+ first_atom, remaining_name = name.split(".", 1) if "." in name else (name, "")
287
+ if first_atom == "*":
288
+ if not isinstance(model, torch.nn.ModuleList):
289
+ raise ValueError("Wildcard '*' can only be used with ModuleList")
290
+ submodules = []
291
+ for submodule in model:
292
+ subsubmodules = _find_submodule_by_name(submodule, remaining_name)
293
+ if not isinstance(subsubmodules, list):
294
+ subsubmodules = [subsubmodules]
295
+ submodules.extend(subsubmodules)
296
+ return submodules
297
+ else:
298
+ if hasattr(model, first_atom):
299
+ submodule = getattr(model, first_atom)
300
+ return _find_submodule_by_name(submodule, remaining_name)
301
+ else:
302
+ raise ValueError(f"'{first_atom}' is not a submodule of '{model.__class__.__name__}'")
vendor/diffusers/hooks/faster_cache.py ADDED
@@ -0,0 +1,654 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import re
16
+ from dataclasses import dataclass
17
+ from typing import Any, Callable, List, Optional, Tuple
18
+
19
+ import torch
20
+
21
+ from ..models.attention import AttentionModuleMixin
22
+ from ..models.modeling_outputs import Transformer2DModelOutput
23
+ from ..utils import logging
24
+ from ._common import _ATTENTION_CLASSES
25
+ from .hooks import HookRegistry, ModelHook
26
+
27
+
28
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
29
+
30
+
31
+ _FASTER_CACHE_DENOISER_HOOK = "faster_cache_denoiser"
32
+ _FASTER_CACHE_BLOCK_HOOK = "faster_cache_block"
33
+ _SPATIAL_ATTENTION_BLOCK_IDENTIFIERS = (
34
+ "^blocks.*attn",
35
+ "^transformer_blocks.*attn",
36
+ "^single_transformer_blocks.*attn",
37
+ )
38
+ _TEMPORAL_ATTENTION_BLOCK_IDENTIFIERS = ("^temporal_transformer_blocks.*attn",)
39
+ _TRANSFORMER_BLOCK_IDENTIFIERS = _SPATIAL_ATTENTION_BLOCK_IDENTIFIERS + _TEMPORAL_ATTENTION_BLOCK_IDENTIFIERS
40
+ _UNCOND_COND_INPUT_KWARGS_IDENTIFIERS = (
41
+ "hidden_states",
42
+ "encoder_hidden_states",
43
+ "timestep",
44
+ "attention_mask",
45
+ "encoder_attention_mask",
46
+ )
47
+
48
+
49
+ @dataclass
50
+ class FasterCacheConfig:
51
+ r"""
52
+ Configuration for [FasterCache](https://huggingface.co/papers/2410.19355).
53
+
54
+ Attributes:
55
+ spatial_attention_block_skip_range (`int`, defaults to `2`):
56
+ Calculate the attention states every `N` iterations. If this is set to `N`, the attention computation will
57
+ be skipped `N - 1` times (i.e., cached attention states will be reused) before computing the new attention
58
+ states again.
59
+ temporal_attention_block_skip_range (`int`, *optional*, defaults to `None`):
60
+ Calculate the attention states every `N` iterations. If this is set to `N`, the attention computation will
61
+ be skipped `N - 1` times (i.e., cached attention states will be reused) before computing the new attention
62
+ states again.
63
+ spatial_attention_timestep_skip_range (`Tuple[float, float]`, defaults to `(-1, 681)`):
64
+ The timestep range within which the spatial attention computation can be skipped without a significant loss
65
+ in quality. This is to be determined by the user based on the underlying model. The first value in the
66
+ tuple is the lower bound and the second value is the upper bound. Typically, diffusion timesteps for
67
+ denoising are in the reversed range of 0 to 1000 (i.e. denoising starts at timestep 1000 and ends at
68
+ timestep 0). For the default values, this would mean that the spatial attention computation skipping will
69
+ be applicable only after denoising timestep 681 is reached, and continue until the end of the denoising
70
+ process.
71
+ temporal_attention_timestep_skip_range (`Tuple[float, float]`, *optional*, defaults to `None`):
72
+ The timestep range within which the temporal attention computation can be skipped without a significant
73
+ loss in quality. This is to be determined by the user based on the underlying model. The first value in the
74
+ tuple is the lower bound and the second value is the upper bound. Typically, diffusion timesteps for
75
+ denoising are in the reversed range of 0 to 1000 (i.e. denoising starts at timestep 1000 and ends at
76
+ timestep 0).
77
+ low_frequency_weight_update_timestep_range (`Tuple[int, int]`, defaults to `(99, 901)`):
78
+ The timestep range within which the low frequency weight scaling update is applied. The first value in the
79
+ tuple is the lower bound and the second value is the upper bound of the timestep range. The callback
80
+ function for the update is called only within this range.
81
+ high_frequency_weight_update_timestep_range (`Tuple[int, int]`, defaults to `(-1, 301)`):
82
+ The timestep range within which the high frequency weight scaling update is applied. The first value in the
83
+ tuple is the lower bound and the second value is the upper bound of the timestep range. The callback
84
+ function for the update is called only within this range.
85
+ alpha_low_frequency (`float`, defaults to `1.1`):
86
+ The weight to scale the low frequency updates by. This is used to approximate the unconditional branch from
87
+ the conditional branch outputs.
88
+ alpha_high_frequency (`float`, defaults to `1.1`):
89
+ The weight to scale the high frequency updates by. This is used to approximate the unconditional branch
90
+ from the conditional branch outputs.
91
+ unconditional_batch_skip_range (`int`, defaults to `5`):
92
+ Process the unconditional branch every `N` iterations. If this is set to `N`, the unconditional branch
93
+ computation will be skipped `N - 1` times (i.e., cached unconditional branch states will be reused) before
94
+ computing the new unconditional branch states again.
95
+ unconditional_batch_timestep_skip_range (`Tuple[float, float]`, defaults to `(-1, 641)`):
96
+ The timestep range within which the unconditional branch computation can be skipped without a significant
97
+ loss in quality. This is to be determined by the user based on the underlying model. The first value in the
98
+ tuple is the lower bound and the second value is the upper bound.
99
+ spatial_attention_block_identifiers (`Tuple[str, ...]`, defaults to `("blocks.*attn1", "transformer_blocks.*attn1", "single_transformer_blocks.*attn1")`):
100
+ The identifiers to match the spatial attention blocks in the model. If the name of the block contains any
101
+ of these identifiers, FasterCache will be applied to that block. This can either be the full layer names,
102
+ partial layer names, or regex patterns. Matching will always be done using a regex match.
103
+ temporal_attention_block_identifiers (`Tuple[str, ...]`, defaults to `("temporal_transformer_blocks.*attn1",)`):
104
+ The identifiers to match the temporal attention blocks in the model. If the name of the block contains any
105
+ of these identifiers, FasterCache will be applied to that block. This can either be the full layer names,
106
+ partial layer names, or regex patterns. Matching will always be done using a regex match.
107
+ attention_weight_callback (`Callable[[torch.nn.Module], float]`, defaults to `None`):
108
+ The callback function to determine the weight to scale the attention outputs by. This function should take
109
+ the attention module as input and return a float value. This is used to approximate the unconditional
110
+ branch from the conditional branch outputs. If not provided, the default weight is 0.5 for all timesteps.
111
+ Typically, as described in the paper, this weight should gradually increase from 0 to 1 as the inference
112
+ progresses. Users are encouraged to experiment and provide custom weight schedules that take into account
113
+ the number of inference steps and underlying model behaviour as denoising progresses.
114
+ low_frequency_weight_callback (`Callable[[torch.nn.Module], float]`, defaults to `None`):
115
+ The callback function to determine the weight to scale the low frequency updates by. If not provided, the
116
+ default weight is 1.1 for timesteps within the range specified (as described in the paper).
117
+ high_frequency_weight_callback (`Callable[[torch.nn.Module], float]`, defaults to `None`):
118
+ The callback function to determine the weight to scale the high frequency updates by. If not provided, the
119
+ default weight is 1.1 for timesteps within the range specified (as described in the paper).
120
+ tensor_format (`str`, defaults to `"BCFHW"`):
121
+ The format of the input tensors. This should be one of `"BCFHW"`, `"BFCHW"`, or `"BCHW"`. The format is
122
+ used to split individual latent frames in order for low and high frequency components to be computed.
123
+ is_guidance_distilled (`bool`, defaults to `False`):
124
+ Whether the model is guidance distilled or not. If the model is guidance distilled, FasterCache will not be
125
+ applied at the denoiser-level to skip the unconditional branch computation (as there is none).
126
+ _unconditional_conditional_input_kwargs_identifiers (`List[str]`, defaults to `("hidden_states", "encoder_hidden_states", "timestep", "attention_mask", "encoder_attention_mask")`):
127
+ The identifiers to match the input kwargs that contain the batchwise-concatenated unconditional and
128
+ conditional inputs. If the name of the input kwargs contains any of these identifiers, FasterCache will
129
+ split the inputs into unconditional and conditional branches. This must be a list of exact input kwargs
130
+ names that contain the batchwise-concatenated unconditional and conditional inputs.
131
+ """
132
+
133
+ # In the paper and codebase, they hardcode these values to 2. However, it can be made configurable
134
+ # after some testing. We default to 2 if these parameters are not provided.
135
+ spatial_attention_block_skip_range: int = 2
136
+ temporal_attention_block_skip_range: Optional[int] = None
137
+
138
+ spatial_attention_timestep_skip_range: Tuple[int, int] = (-1, 681)
139
+ temporal_attention_timestep_skip_range: Tuple[int, int] = (-1, 681)
140
+
141
+ # Indicator functions for low/high frequency as mentioned in Equation 11 of the paper
142
+ low_frequency_weight_update_timestep_range: Tuple[int, int] = (99, 901)
143
+ high_frequency_weight_update_timestep_range: Tuple[int, int] = (-1, 301)
144
+
145
+ # ⍺1 and ⍺2 as mentioned in Equation 11 of the paper
146
+ alpha_low_frequency: float = 1.1
147
+ alpha_high_frequency: float = 1.1
148
+
149
+ # n as described in CFG-Cache explanation in the paper - dependent on the model
150
+ unconditional_batch_skip_range: int = 5
151
+ unconditional_batch_timestep_skip_range: Tuple[int, int] = (-1, 641)
152
+
153
+ spatial_attention_block_identifiers: Tuple[str, ...] = _SPATIAL_ATTENTION_BLOCK_IDENTIFIERS
154
+ temporal_attention_block_identifiers: Tuple[str, ...] = _TEMPORAL_ATTENTION_BLOCK_IDENTIFIERS
155
+
156
+ attention_weight_callback: Callable[[torch.nn.Module], float] = None
157
+ low_frequency_weight_callback: Callable[[torch.nn.Module], float] = None
158
+ high_frequency_weight_callback: Callable[[torch.nn.Module], float] = None
159
+
160
+ tensor_format: str = "BCFHW"
161
+ is_guidance_distilled: bool = False
162
+
163
+ current_timestep_callback: Callable[[], int] = None
164
+
165
+ _unconditional_conditional_input_kwargs_identifiers: List[str] = _UNCOND_COND_INPUT_KWARGS_IDENTIFIERS
166
+
167
+ def __repr__(self) -> str:
168
+ return (
169
+ f"FasterCacheConfig(\n"
170
+ f" spatial_attention_block_skip_range={self.spatial_attention_block_skip_range},\n"
171
+ f" temporal_attention_block_skip_range={self.temporal_attention_block_skip_range},\n"
172
+ f" spatial_attention_timestep_skip_range={self.spatial_attention_timestep_skip_range},\n"
173
+ f" temporal_attention_timestep_skip_range={self.temporal_attention_timestep_skip_range},\n"
174
+ f" low_frequency_weight_update_timestep_range={self.low_frequency_weight_update_timestep_range},\n"
175
+ f" high_frequency_weight_update_timestep_range={self.high_frequency_weight_update_timestep_range},\n"
176
+ f" alpha_low_frequency={self.alpha_low_frequency},\n"
177
+ f" alpha_high_frequency={self.alpha_high_frequency},\n"
178
+ f" unconditional_batch_skip_range={self.unconditional_batch_skip_range},\n"
179
+ f" unconditional_batch_timestep_skip_range={self.unconditional_batch_timestep_skip_range},\n"
180
+ f" spatial_attention_block_identifiers={self.spatial_attention_block_identifiers},\n"
181
+ f" temporal_attention_block_identifiers={self.temporal_attention_block_identifiers},\n"
182
+ f" tensor_format={self.tensor_format},\n"
183
+ f")"
184
+ )
185
+
186
+
187
+ class FasterCacheDenoiserState:
188
+ r"""
189
+ State for [FasterCache](https://huggingface.co/papers/2410.19355) top-level denoiser module.
190
+ """
191
+
192
+ def __init__(self) -> None:
193
+ self.iteration: int = 0
194
+ self.low_frequency_delta: torch.Tensor = None
195
+ self.high_frequency_delta: torch.Tensor = None
196
+
197
+ def reset(self):
198
+ self.iteration = 0
199
+ self.low_frequency_delta = None
200
+ self.high_frequency_delta = None
201
+
202
+
203
+ class FasterCacheBlockState:
204
+ r"""
205
+ State for [FasterCache](https://huggingface.co/papers/2410.19355). Every underlying block that FasterCache is
206
+ applied to will have an instance of this state.
207
+ """
208
+
209
+ def __init__(self) -> None:
210
+ self.iteration: int = 0
211
+ self.batch_size: int = None
212
+ self.cache: Tuple[torch.Tensor, torch.Tensor] = None
213
+
214
+ def reset(self):
215
+ self.iteration = 0
216
+ self.batch_size = None
217
+ self.cache = None
218
+
219
+
220
+ class FasterCacheDenoiserHook(ModelHook):
221
+ _is_stateful = True
222
+
223
+ def __init__(
224
+ self,
225
+ unconditional_batch_skip_range: int,
226
+ unconditional_batch_timestep_skip_range: Tuple[int, int],
227
+ tensor_format: str,
228
+ is_guidance_distilled: bool,
229
+ uncond_cond_input_kwargs_identifiers: List[str],
230
+ current_timestep_callback: Callable[[], int],
231
+ low_frequency_weight_callback: Callable[[torch.nn.Module], torch.Tensor],
232
+ high_frequency_weight_callback: Callable[[torch.nn.Module], torch.Tensor],
233
+ ) -> None:
234
+ super().__init__()
235
+
236
+ self.unconditional_batch_skip_range = unconditional_batch_skip_range
237
+ self.unconditional_batch_timestep_skip_range = unconditional_batch_timestep_skip_range
238
+ # We can't easily detect what args are to be split in unconditional and conditional branches. We
239
+ # can only do it for kwargs, hence they are the only ones we split. The args are passed as-is.
240
+ # If a model is to be made compatible with FasterCache, the user must ensure that the inputs that
241
+ # contain batchwise-concatenated unconditional and conditional inputs are passed as kwargs.
242
+ self.uncond_cond_input_kwargs_identifiers = uncond_cond_input_kwargs_identifiers
243
+ self.tensor_format = tensor_format
244
+ self.is_guidance_distilled = is_guidance_distilled
245
+
246
+ self.current_timestep_callback = current_timestep_callback
247
+ self.low_frequency_weight_callback = low_frequency_weight_callback
248
+ self.high_frequency_weight_callback = high_frequency_weight_callback
249
+
250
+ def initialize_hook(self, module):
251
+ self.state = FasterCacheDenoiserState()
252
+ return module
253
+
254
+ @staticmethod
255
+ def _get_cond_input(input: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
256
+ # Note: this method assumes that the input tensor is batchwise-concatenated with unconditional inputs
257
+ # followed by conditional inputs.
258
+ _, cond = input.chunk(2, dim=0)
259
+ return cond
260
+
261
+ def new_forward(self, module: torch.nn.Module, *args, **kwargs) -> Any:
262
+ # Split the unconditional and conditional inputs. We only want to infer the conditional branch if the
263
+ # requirements for skipping the unconditional branch are met as described in the paper.
264
+ # We skip the unconditional branch only if the following conditions are met:
265
+ # 1. We have completed at least one iteration of the denoiser
266
+ # 2. The current timestep is within the range specified by the user. This is the optimal timestep range
267
+ # where approximating the unconditional branch from the computation of the conditional branch is possible
268
+ # without a significant loss in quality.
269
+ # 3. The current iteration is not a multiple of the unconditional batch skip range. This is done so that
270
+ # we compute the unconditional branch at least once every few iterations to ensure minimal quality loss.
271
+ is_within_timestep_range = (
272
+ self.unconditional_batch_timestep_skip_range[0]
273
+ < self.current_timestep_callback()
274
+ < self.unconditional_batch_timestep_skip_range[1]
275
+ )
276
+ should_skip_uncond = (
277
+ self.state.iteration > 0
278
+ and is_within_timestep_range
279
+ and self.state.iteration % self.unconditional_batch_skip_range != 0
280
+ and not self.is_guidance_distilled
281
+ )
282
+
283
+ if should_skip_uncond:
284
+ is_any_kwarg_uncond = any(k in self.uncond_cond_input_kwargs_identifiers for k in kwargs.keys())
285
+ if is_any_kwarg_uncond:
286
+ logger.debug("FasterCache - Skipping unconditional branch computation")
287
+ args = tuple([self._get_cond_input(arg) if torch.is_tensor(arg) else arg for arg in args])
288
+ kwargs = {
289
+ k: v if k not in self.uncond_cond_input_kwargs_identifiers else self._get_cond_input(v)
290
+ for k, v in kwargs.items()
291
+ }
292
+
293
+ output = self.fn_ref.original_forward(*args, **kwargs)
294
+
295
+ if self.is_guidance_distilled:
296
+ self.state.iteration += 1
297
+ return output
298
+
299
+ if torch.is_tensor(output):
300
+ hidden_states = output
301
+ elif isinstance(output, (tuple, Transformer2DModelOutput)):
302
+ hidden_states = output[0]
303
+
304
+ batch_size = hidden_states.size(0)
305
+
306
+ if should_skip_uncond:
307
+ self.state.low_frequency_delta = self.state.low_frequency_delta * self.low_frequency_weight_callback(
308
+ module
309
+ )
310
+ self.state.high_frequency_delta = self.state.high_frequency_delta * self.high_frequency_weight_callback(
311
+ module
312
+ )
313
+
314
+ if self.tensor_format == "BCFHW":
315
+ hidden_states = hidden_states.permute(0, 2, 1, 3, 4)
316
+ if self.tensor_format == "BCFHW" or self.tensor_format == "BFCHW":
317
+ hidden_states = hidden_states.flatten(0, 1)
318
+
319
+ low_freq_cond, high_freq_cond = _split_low_high_freq(hidden_states.float())
320
+
321
+ # Approximate/compute the unconditional branch outputs as described in Equation 9 and 10 of the paper
322
+ low_freq_uncond = self.state.low_frequency_delta + low_freq_cond
323
+ high_freq_uncond = self.state.high_frequency_delta + high_freq_cond
324
+ uncond_freq = low_freq_uncond + high_freq_uncond
325
+
326
+ uncond_states = torch.fft.ifftshift(uncond_freq)
327
+ uncond_states = torch.fft.ifft2(uncond_states).real
328
+
329
+ if self.tensor_format == "BCFHW" or self.tensor_format == "BFCHW":
330
+ uncond_states = uncond_states.unflatten(0, (batch_size, -1))
331
+ hidden_states = hidden_states.unflatten(0, (batch_size, -1))
332
+ if self.tensor_format == "BCFHW":
333
+ uncond_states = uncond_states.permute(0, 2, 1, 3, 4)
334
+ hidden_states = hidden_states.permute(0, 2, 1, 3, 4)
335
+
336
+ # Concatenate the approximated unconditional and predicted conditional branches
337
+ uncond_states = uncond_states.to(hidden_states.dtype)
338
+ hidden_states = torch.cat([uncond_states, hidden_states], dim=0)
339
+ else:
340
+ uncond_states, cond_states = hidden_states.chunk(2, dim=0)
341
+ if self.tensor_format == "BCFHW":
342
+ uncond_states = uncond_states.permute(0, 2, 1, 3, 4)
343
+ cond_states = cond_states.permute(0, 2, 1, 3, 4)
344
+ if self.tensor_format == "BCFHW" or self.tensor_format == "BFCHW":
345
+ uncond_states = uncond_states.flatten(0, 1)
346
+ cond_states = cond_states.flatten(0, 1)
347
+
348
+ low_freq_uncond, high_freq_uncond = _split_low_high_freq(uncond_states.float())
349
+ low_freq_cond, high_freq_cond = _split_low_high_freq(cond_states.float())
350
+ self.state.low_frequency_delta = low_freq_uncond - low_freq_cond
351
+ self.state.high_frequency_delta = high_freq_uncond - high_freq_cond
352
+
353
+ self.state.iteration += 1
354
+ if torch.is_tensor(output):
355
+ output = hidden_states
356
+ elif isinstance(output, tuple):
357
+ output = (hidden_states, *output[1:])
358
+ else:
359
+ output.sample = hidden_states
360
+
361
+ return output
362
+
363
+ def reset_state(self, module: torch.nn.Module) -> torch.nn.Module:
364
+ self.state.reset()
365
+ return module
366
+
367
+
368
+ class FasterCacheBlockHook(ModelHook):
369
+ _is_stateful = True
370
+
371
+ def __init__(
372
+ self,
373
+ block_skip_range: int,
374
+ timestep_skip_range: Tuple[int, int],
375
+ is_guidance_distilled: bool,
376
+ weight_callback: Callable[[torch.nn.Module], float],
377
+ current_timestep_callback: Callable[[], int],
378
+ ) -> None:
379
+ super().__init__()
380
+
381
+ self.block_skip_range = block_skip_range
382
+ self.timestep_skip_range = timestep_skip_range
383
+ self.is_guidance_distilled = is_guidance_distilled
384
+
385
+ self.weight_callback = weight_callback
386
+ self.current_timestep_callback = current_timestep_callback
387
+
388
+ def initialize_hook(self, module):
389
+ self.state = FasterCacheBlockState()
390
+ return module
391
+
392
+ def _compute_approximated_attention_output(
393
+ self, t_2_output: torch.Tensor, t_output: torch.Tensor, weight: float, batch_size: int
394
+ ) -> torch.Tensor:
395
+ if t_2_output.size(0) != batch_size:
396
+ # The cache t_2_output contains both batchwise-concatenated unconditional-conditional branch outputs. Just
397
+ # take the conditional branch outputs.
398
+ assert t_2_output.size(0) == 2 * batch_size
399
+ t_2_output = t_2_output[batch_size:]
400
+ if t_output.size(0) != batch_size:
401
+ # The cache t_output contains both batchwise-concatenated unconditional-conditional branch outputs. Just
402
+ # take the conditional branch outputs.
403
+ assert t_output.size(0) == 2 * batch_size
404
+ t_output = t_output[batch_size:]
405
+ return t_output + (t_output - t_2_output) * weight
406
+
407
+ def new_forward(self, module: torch.nn.Module, *args, **kwargs) -> Any:
408
+ batch_size = [
409
+ *[arg.size(0) for arg in args if torch.is_tensor(arg)],
410
+ *[v.size(0) for v in kwargs.values() if torch.is_tensor(v)],
411
+ ][0]
412
+ if self.state.batch_size is None:
413
+ # Will be updated on first forward pass through the denoiser
414
+ self.state.batch_size = batch_size
415
+
416
+ # If we have to skip due to the skip conditions, then let's skip as expected.
417
+ # But, we can't skip if the denoiser wants to infer both unconditional and conditional branches. This
418
+ # is because the expected output shapes of attention layer will not match if we only return values from
419
+ # the cache (which only caches conditional branch outputs). So, if state.batch_size (which is the true
420
+ # unconditional-conditional batch size) is same as the current batch size, we don't perform the layer
421
+ # skip. Otherwise, we conditionally skip the layer based on what state.skip_callback returns.
422
+ is_within_timestep_range = (
423
+ self.timestep_skip_range[0] < self.current_timestep_callback() < self.timestep_skip_range[1]
424
+ )
425
+ if not is_within_timestep_range:
426
+ should_skip_attention = False
427
+ else:
428
+ should_compute_attention = self.state.iteration > 0 and self.state.iteration % self.block_skip_range == 0
429
+ should_skip_attention = not should_compute_attention
430
+ if should_skip_attention:
431
+ should_skip_attention = self.is_guidance_distilled or self.state.batch_size != batch_size
432
+
433
+ if should_skip_attention:
434
+ logger.debug("FasterCache - Skipping attention and using approximation")
435
+ if torch.is_tensor(self.state.cache[-1]):
436
+ t_2_output, t_output = self.state.cache
437
+ weight = self.weight_callback(module)
438
+ output = self._compute_approximated_attention_output(t_2_output, t_output, weight, batch_size)
439
+ else:
440
+ # The cache contains multiple tensors from past N iterations (N=2 for FasterCache). We need to handle all of them.
441
+ # Diffusers blocks can return multiple tensors - let's call them [A, B, C, ...] for simplicity.
442
+ # In our cache, we would have [[A_1, B_1, C_1, ...], [A_2, B_2, C_2, ...], ...] where each list is the output from
443
+ # a forward pass of the block. We need to compute the approximated output for each of these tensors.
444
+ # The zip(*state.cache) operation will give us [(A_1, A_2, ...), (B_1, B_2, ...), (C_1, C_2, ...), ...] which
445
+ # allows us to compute the approximated attention output for each tensor in the cache.
446
+ output = ()
447
+ for t_2_output, t_output in zip(*self.state.cache):
448
+ result = self._compute_approximated_attention_output(
449
+ t_2_output, t_output, self.weight_callback(module), batch_size
450
+ )
451
+ output += (result,)
452
+ else:
453
+ logger.debug("FasterCache - Computing attention")
454
+ output = self.fn_ref.original_forward(*args, **kwargs)
455
+
456
+ # Note that the following condition for getting hidden_states should suffice since Diffusers blocks either return
457
+ # a single hidden_states tensor, or a tuple of (hidden_states, encoder_hidden_states) tensors. We need to handle
458
+ # both cases.
459
+ if torch.is_tensor(output):
460
+ cache_output = output
461
+ if not self.is_guidance_distilled and cache_output.size(0) == self.state.batch_size:
462
+ # The output here can be both unconditional-conditional branch outputs or just conditional branch outputs.
463
+ # This is determined at the higher-level denoiser module. We only want to cache the conditional branch outputs.
464
+ cache_output = cache_output.chunk(2, dim=0)[1]
465
+ else:
466
+ # Cache all return values and perform the same operation as above
467
+ cache_output = ()
468
+ for out in output:
469
+ if not self.is_guidance_distilled and out.size(0) == self.state.batch_size:
470
+ out = out.chunk(2, dim=0)[1]
471
+ cache_output += (out,)
472
+
473
+ if self.state.cache is None:
474
+ self.state.cache = [cache_output, cache_output]
475
+ else:
476
+ self.state.cache = [self.state.cache[-1], cache_output]
477
+
478
+ self.state.iteration += 1
479
+ return output
480
+
481
+ def reset_state(self, module: torch.nn.Module) -> torch.nn.Module:
482
+ self.state.reset()
483
+ return module
484
+
485
+
486
+ def apply_faster_cache(module: torch.nn.Module, config: FasterCacheConfig) -> None:
487
+ r"""
488
+ Applies [FasterCache](https://huggingface.co/papers/2410.19355) to a given pipeline.
489
+
490
+ Args:
491
+ module (`torch.nn.Module`):
492
+ The pytorch module to apply FasterCache to. Typically, this should be a transformer architecture supported
493
+ in Diffusers, such as `CogVideoXTransformer3DModel`, but external implementations may also work.
494
+ config (`FasterCacheConfig`):
495
+ The configuration to use for FasterCache.
496
+
497
+ Example:
498
+ ```python
499
+ >>> import torch
500
+ >>> from diffusers import CogVideoXPipeline, FasterCacheConfig, apply_faster_cache
501
+
502
+ >>> pipe = CogVideoXPipeline.from_pretrained("THUDM/CogVideoX-5b", torch_dtype=torch.bfloat16)
503
+ >>> pipe.to("cuda")
504
+
505
+ >>> config = FasterCacheConfig(
506
+ ... spatial_attention_block_skip_range=2,
507
+ ... spatial_attention_timestep_skip_range=(-1, 681),
508
+ ... low_frequency_weight_update_timestep_range=(99, 641),
509
+ ... high_frequency_weight_update_timestep_range=(-1, 301),
510
+ ... spatial_attention_block_identifiers=["transformer_blocks"],
511
+ ... attention_weight_callback=lambda _: 0.3,
512
+ ... tensor_format="BFCHW",
513
+ ... )
514
+ >>> apply_faster_cache(pipe.transformer, config)
515
+ ```
516
+ """
517
+
518
+ logger.warning(
519
+ "FasterCache is a purely experimental feature and may not work as expected. Not all models support FasterCache. "
520
+ "The API is subject to change in future releases, with no guarantee of backward compatibility. Please report any issues at "
521
+ "https://github.com/huggingface/diffusers/issues."
522
+ )
523
+
524
+ if config.attention_weight_callback is None:
525
+ # If the user has not provided a weight callback, we default to 0.5 for all timesteps.
526
+ # In the paper, they recommend using a gradually increasing weight from 0 to 1 as the inference progresses, but
527
+ # this depends from model-to-model. It is required by the user to provide a weight callback if they want to
528
+ # use a different weight function. Defaulting to 0.5 works well in practice for most cases.
529
+ logger.warning(
530
+ "No `attention_weight_callback` provided when enabling FasterCache. Defaulting to using a weight of 0.5 for all timesteps."
531
+ )
532
+ config.attention_weight_callback = lambda _: 0.5
533
+
534
+ if config.low_frequency_weight_callback is None:
535
+ logger.debug(
536
+ "Low frequency weight callback not provided when enabling FasterCache. Defaulting to behaviour described in the paper."
537
+ )
538
+
539
+ def low_frequency_weight_callback(module: torch.nn.Module) -> float:
540
+ is_within_range = (
541
+ config.low_frequency_weight_update_timestep_range[0]
542
+ < config.current_timestep_callback()
543
+ < config.low_frequency_weight_update_timestep_range[1]
544
+ )
545
+ return config.alpha_low_frequency if is_within_range else 1.0
546
+
547
+ config.low_frequency_weight_callback = low_frequency_weight_callback
548
+
549
+ if config.high_frequency_weight_callback is None:
550
+ logger.debug(
551
+ "High frequency weight callback not provided when enabling FasterCache. Defaulting to behaviour described in the paper."
552
+ )
553
+
554
+ def high_frequency_weight_callback(module: torch.nn.Module) -> float:
555
+ is_within_range = (
556
+ config.high_frequency_weight_update_timestep_range[0]
557
+ < config.current_timestep_callback()
558
+ < config.high_frequency_weight_update_timestep_range[1]
559
+ )
560
+ return config.alpha_high_frequency if is_within_range else 1.0
561
+
562
+ config.high_frequency_weight_callback = high_frequency_weight_callback
563
+
564
+ supported_tensor_formats = ["BCFHW", "BFCHW", "BCHW"] # TODO(aryan): Support BSC for LTX Video
565
+ if config.tensor_format not in supported_tensor_formats:
566
+ raise ValueError(f"`tensor_format` must be one of {supported_tensor_formats}, but got {config.tensor_format}.")
567
+
568
+ _apply_faster_cache_on_denoiser(module, config)
569
+
570
+ for name, submodule in module.named_modules():
571
+ if not isinstance(submodule, _ATTENTION_CLASSES):
572
+ continue
573
+ if any(re.search(identifier, name) is not None for identifier in _TRANSFORMER_BLOCK_IDENTIFIERS):
574
+ _apply_faster_cache_on_attention_class(name, submodule, config)
575
+
576
+
577
+ def _apply_faster_cache_on_denoiser(module: torch.nn.Module, config: FasterCacheConfig) -> None:
578
+ hook = FasterCacheDenoiserHook(
579
+ config.unconditional_batch_skip_range,
580
+ config.unconditional_batch_timestep_skip_range,
581
+ config.tensor_format,
582
+ config.is_guidance_distilled,
583
+ config._unconditional_conditional_input_kwargs_identifiers,
584
+ config.current_timestep_callback,
585
+ config.low_frequency_weight_callback,
586
+ config.high_frequency_weight_callback,
587
+ )
588
+ registry = HookRegistry.check_if_exists_or_initialize(module)
589
+ registry.register_hook(hook, _FASTER_CACHE_DENOISER_HOOK)
590
+
591
+
592
+ def _apply_faster_cache_on_attention_class(name: str, module: AttentionModuleMixin, config: FasterCacheConfig) -> None:
593
+ is_spatial_self_attention = (
594
+ any(re.search(identifier, name) is not None for identifier in config.spatial_attention_block_identifiers)
595
+ and config.spatial_attention_block_skip_range is not None
596
+ and not getattr(module, "is_cross_attention", False)
597
+ )
598
+ is_temporal_self_attention = (
599
+ any(re.search(identifier, name) is not None for identifier in config.temporal_attention_block_identifiers)
600
+ and config.temporal_attention_block_skip_range is not None
601
+ and not module.is_cross_attention
602
+ )
603
+
604
+ block_skip_range, timestep_skip_range, block_type = None, None, None
605
+ if is_spatial_self_attention:
606
+ block_skip_range = config.spatial_attention_block_skip_range
607
+ timestep_skip_range = config.spatial_attention_timestep_skip_range
608
+ block_type = "spatial"
609
+ elif is_temporal_self_attention:
610
+ block_skip_range = config.temporal_attention_block_skip_range
611
+ timestep_skip_range = config.temporal_attention_timestep_skip_range
612
+ block_type = "temporal"
613
+
614
+ if block_skip_range is None or timestep_skip_range is None:
615
+ logger.debug(
616
+ f'Unable to apply FasterCache to the selected layer: "{name}" because it does '
617
+ f"not match any of the required criteria for spatial or temporal attention layers. Note, "
618
+ f"however, that this layer may still be valid for applying PAB. Please specify the correct "
619
+ f"block identifiers in the configuration or use the specialized `apply_faster_cache_on_module` "
620
+ f"function to apply FasterCache to this layer."
621
+ )
622
+ return
623
+
624
+ logger.debug(f"Enabling FasterCache ({block_type}) for layer: {name}")
625
+ hook = FasterCacheBlockHook(
626
+ block_skip_range,
627
+ timestep_skip_range,
628
+ config.is_guidance_distilled,
629
+ config.attention_weight_callback,
630
+ config.current_timestep_callback,
631
+ )
632
+ registry = HookRegistry.check_if_exists_or_initialize(module)
633
+ registry.register_hook(hook, _FASTER_CACHE_BLOCK_HOOK)
634
+
635
+
636
+ # Reference: https://github.com/Vchitect/FasterCache/blob/fab32c15014636dc854948319c0a9a8d92c7acb4/scripts/latte/faster_cache_sample_latte.py#L127C1-L143C39
637
+ @torch.no_grad()
638
+ def _split_low_high_freq(x):
639
+ fft = torch.fft.fft2(x)
640
+ fft_shifted = torch.fft.fftshift(fft)
641
+ height, width = x.shape[-2:]
642
+ radius = min(height, width) // 5
643
+
644
+ y_grid, x_grid = torch.meshgrid(torch.arange(height), torch.arange(width))
645
+ center_x, center_y = width // 2, height // 2
646
+ mask = (x_grid - center_x) ** 2 + (y_grid - center_y) ** 2 <= radius**2
647
+
648
+ low_freq_mask = mask.unsqueeze(0).unsqueeze(0).to(x.device)
649
+ high_freq_mask = ~low_freq_mask
650
+
651
+ low_freq_fft = fft_shifted * low_freq_mask
652
+ high_freq_fft = fft_shifted * high_freq_mask
653
+
654
+ return low_freq_fft, high_freq_fft
vendor/diffusers/hooks/first_block_cache.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 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
+
15
+ from dataclasses import dataclass
16
+ from typing import Tuple, Union
17
+
18
+ import torch
19
+
20
+ from ..utils import get_logger
21
+ from ..utils.torch_utils import unwrap_module
22
+ from ._common import _ALL_TRANSFORMER_BLOCK_IDENTIFIERS
23
+ from ._helpers import TransformerBlockRegistry
24
+ from .hooks import BaseState, HookRegistry, ModelHook, StateManager
25
+
26
+
27
+ logger = get_logger(__name__) # pylint: disable=invalid-name
28
+
29
+ _FBC_LEADER_BLOCK_HOOK = "fbc_leader_block_hook"
30
+ _FBC_BLOCK_HOOK = "fbc_block_hook"
31
+
32
+
33
+ @dataclass
34
+ class FirstBlockCacheConfig:
35
+ r"""
36
+ Configuration for [First Block
37
+ Cache](https://github.com/chengzeyi/ParaAttention/blob/7a266123671b55e7e5a2fe9af3121f07a36afc78/README.md#first-block-cache-our-dynamic-caching).
38
+
39
+ Args:
40
+ threshold (`float`, defaults to `0.05`):
41
+ The threshold to determine whether or not a forward pass through all layers of the model is required. A
42
+ higher threshold usually results in a forward pass through a lower number of layers and faster inference,
43
+ but might lead to poorer generation quality. A lower threshold may not result in significant generation
44
+ speedup. The threshold is compared against the absmean difference of the residuals between the current and
45
+ cached outputs from the first transformer block. If the difference is below the threshold, the forward pass
46
+ is skipped.
47
+ """
48
+
49
+ threshold: float = 0.05
50
+
51
+
52
+ class FBCSharedBlockState(BaseState):
53
+ def __init__(self) -> None:
54
+ super().__init__()
55
+
56
+ self.head_block_output: Union[torch.Tensor, Tuple[torch.Tensor, ...]] = None
57
+ self.head_block_residual: torch.Tensor = None
58
+ self.tail_block_residuals: Union[torch.Tensor, Tuple[torch.Tensor, ...]] = None
59
+ self.should_compute: bool = True
60
+
61
+ def reset(self):
62
+ self.tail_block_residuals = None
63
+ self.should_compute = True
64
+
65
+
66
+ class FBCHeadBlockHook(ModelHook):
67
+ _is_stateful = True
68
+
69
+ def __init__(self, state_manager: StateManager, threshold: float):
70
+ self.state_manager = state_manager
71
+ self.threshold = threshold
72
+ self._metadata = None
73
+
74
+ def initialize_hook(self, module):
75
+ unwrapped_module = unwrap_module(module)
76
+ self._metadata = TransformerBlockRegistry.get(unwrapped_module.__class__)
77
+ return module
78
+
79
+ def new_forward(self, module: torch.nn.Module, *args, **kwargs):
80
+ original_hidden_states = self._metadata._get_parameter_from_args_kwargs("hidden_states", args, kwargs)
81
+
82
+ output = self.fn_ref.original_forward(*args, **kwargs)
83
+ is_output_tuple = isinstance(output, tuple)
84
+
85
+ if is_output_tuple:
86
+ hidden_states_residual = output[self._metadata.return_hidden_states_index] - original_hidden_states
87
+ else:
88
+ hidden_states_residual = output - original_hidden_states
89
+
90
+ shared_state: FBCSharedBlockState = self.state_manager.get_state()
91
+ hidden_states = encoder_hidden_states = None
92
+ should_compute = self._should_compute_remaining_blocks(hidden_states_residual)
93
+ shared_state.should_compute = should_compute
94
+
95
+ if not should_compute:
96
+ # Apply caching
97
+ if is_output_tuple:
98
+ hidden_states = (
99
+ shared_state.tail_block_residuals[0] + output[self._metadata.return_hidden_states_index]
100
+ )
101
+ else:
102
+ hidden_states = shared_state.tail_block_residuals[0] + output
103
+
104
+ if self._metadata.return_encoder_hidden_states_index is not None:
105
+ assert is_output_tuple
106
+ encoder_hidden_states = (
107
+ shared_state.tail_block_residuals[1] + output[self._metadata.return_encoder_hidden_states_index]
108
+ )
109
+
110
+ if is_output_tuple:
111
+ return_output = [None] * len(output)
112
+ return_output[self._metadata.return_hidden_states_index] = hidden_states
113
+ return_output[self._metadata.return_encoder_hidden_states_index] = encoder_hidden_states
114
+ return_output = tuple(return_output)
115
+ else:
116
+ return_output = hidden_states
117
+ output = return_output
118
+ else:
119
+ if is_output_tuple:
120
+ head_block_output = [None] * len(output)
121
+ head_block_output[0] = output[self._metadata.return_hidden_states_index]
122
+ head_block_output[1] = output[self._metadata.return_encoder_hidden_states_index]
123
+ else:
124
+ head_block_output = output
125
+ shared_state.head_block_output = head_block_output
126
+ shared_state.head_block_residual = hidden_states_residual
127
+
128
+ return output
129
+
130
+ def reset_state(self, module):
131
+ self.state_manager.reset()
132
+ return module
133
+
134
+ @torch.compiler.disable
135
+ def _should_compute_remaining_blocks(self, hidden_states_residual: torch.Tensor) -> bool:
136
+ shared_state = self.state_manager.get_state()
137
+ if shared_state.head_block_residual is None:
138
+ return True
139
+ prev_hidden_states_residual = shared_state.head_block_residual
140
+ absmean = (hidden_states_residual - prev_hidden_states_residual).abs().mean()
141
+ prev_hidden_states_absmean = prev_hidden_states_residual.abs().mean()
142
+ diff = (absmean / prev_hidden_states_absmean).item()
143
+ return diff > self.threshold
144
+
145
+
146
+ class FBCBlockHook(ModelHook):
147
+ def __init__(self, state_manager: StateManager, is_tail: bool = False):
148
+ super().__init__()
149
+ self.state_manager = state_manager
150
+ self.is_tail = is_tail
151
+ self._metadata = None
152
+
153
+ def initialize_hook(self, module):
154
+ unwrapped_module = unwrap_module(module)
155
+ self._metadata = TransformerBlockRegistry.get(unwrapped_module.__class__)
156
+ return module
157
+
158
+ def new_forward(self, module: torch.nn.Module, *args, **kwargs):
159
+ original_hidden_states = self._metadata._get_parameter_from_args_kwargs("hidden_states", args, kwargs)
160
+ original_encoder_hidden_states = None
161
+ if self._metadata.return_encoder_hidden_states_index is not None:
162
+ original_encoder_hidden_states = self._metadata._get_parameter_from_args_kwargs(
163
+ "encoder_hidden_states", args, kwargs
164
+ )
165
+
166
+ shared_state = self.state_manager.get_state()
167
+
168
+ if shared_state.should_compute:
169
+ output = self.fn_ref.original_forward(*args, **kwargs)
170
+ if self.is_tail:
171
+ hidden_states_residual = encoder_hidden_states_residual = None
172
+ if isinstance(output, tuple):
173
+ hidden_states_residual = (
174
+ output[self._metadata.return_hidden_states_index] - shared_state.head_block_output[0]
175
+ )
176
+ encoder_hidden_states_residual = (
177
+ output[self._metadata.return_encoder_hidden_states_index] - shared_state.head_block_output[1]
178
+ )
179
+ else:
180
+ hidden_states_residual = output - shared_state.head_block_output
181
+ shared_state.tail_block_residuals = (hidden_states_residual, encoder_hidden_states_residual)
182
+ return output
183
+
184
+ if original_encoder_hidden_states is None:
185
+ return_output = original_hidden_states
186
+ else:
187
+ return_output = [None, None]
188
+ return_output[self._metadata.return_hidden_states_index] = original_hidden_states
189
+ return_output[self._metadata.return_encoder_hidden_states_index] = original_encoder_hidden_states
190
+ return_output = tuple(return_output)
191
+ return return_output
192
+
193
+
194
+ def apply_first_block_cache(module: torch.nn.Module, config: FirstBlockCacheConfig) -> None:
195
+ """
196
+ Applies [First Block
197
+ Cache](https://github.com/chengzeyi/ParaAttention/blob/4de137c5b96416489f06e43e19f2c14a772e28fd/README.md#first-block-cache-our-dynamic-caching)
198
+ to a given module.
199
+
200
+ First Block Cache builds on the ideas of [TeaCache](https://huggingface.co/papers/2411.19108). It is much simpler
201
+ to implement generically for a wide range of models and has been integrated first for experimental purposes.
202
+
203
+ Args:
204
+ module (`torch.nn.Module`):
205
+ The pytorch module to apply FBCache to. Typically, this should be a transformer architecture supported in
206
+ Diffusers, such as `CogVideoXTransformer3DModel`, but external implementations may also work.
207
+ config (`FirstBlockCacheConfig`):
208
+ The configuration to use for applying the FBCache method.
209
+
210
+ Example:
211
+ ```python
212
+ >>> import torch
213
+ >>> from diffusers import CogView4Pipeline
214
+ >>> from diffusers.hooks import apply_first_block_cache, FirstBlockCacheConfig
215
+
216
+ >>> pipe = CogView4Pipeline.from_pretrained("THUDM/CogView4-6B", torch_dtype=torch.bfloat16)
217
+ >>> pipe.to("cuda")
218
+
219
+ >>> apply_first_block_cache(pipe.transformer, FirstBlockCacheConfig(threshold=0.2))
220
+
221
+ >>> prompt = "A photo of an astronaut riding a horse on mars"
222
+ >>> image = pipe(prompt, generator=torch.Generator().manual_seed(42)).images[0]
223
+ >>> image.save("output.png")
224
+ ```
225
+ """
226
+
227
+ state_manager = StateManager(FBCSharedBlockState, (), {})
228
+ remaining_blocks = []
229
+
230
+ for name, submodule in module.named_children():
231
+ if name not in _ALL_TRANSFORMER_BLOCK_IDENTIFIERS or not isinstance(submodule, torch.nn.ModuleList):
232
+ continue
233
+ for index, block in enumerate(submodule):
234
+ remaining_blocks.append((f"{name}.{index}", block))
235
+
236
+ head_block_name, head_block = remaining_blocks.pop(0)
237
+ tail_block_name, tail_block = remaining_blocks.pop(-1)
238
+
239
+ logger.debug(f"Applying FBCHeadBlockHook to '{head_block_name}'")
240
+ _apply_fbc_head_block_hook(head_block, state_manager, config.threshold)
241
+
242
+ for name, block in remaining_blocks:
243
+ logger.debug(f"Applying FBCBlockHook to '{name}'")
244
+ _apply_fbc_block_hook(block, state_manager)
245
+
246
+ logger.debug(f"Applying FBCBlockHook to tail block '{tail_block_name}'")
247
+ _apply_fbc_block_hook(tail_block, state_manager, is_tail=True)
248
+
249
+
250
+ def _apply_fbc_head_block_hook(block: torch.nn.Module, state_manager: StateManager, threshold: float) -> None:
251
+ registry = HookRegistry.check_if_exists_or_initialize(block)
252
+ hook = FBCHeadBlockHook(state_manager, threshold)
253
+ registry.register_hook(hook, _FBC_LEADER_BLOCK_HOOK)
254
+
255
+
256
+ def _apply_fbc_block_hook(block: torch.nn.Module, state_manager: StateManager, is_tail: bool = False) -> None:
257
+ registry = HookRegistry.check_if_exists_or_initialize(block)
258
+ hook = FBCBlockHook(state_manager, is_tail)
259
+ registry.register_hook(hook, _FBC_BLOCK_HOOK)
vendor/diffusers/hooks/group_offloading.py ADDED
@@ -0,0 +1,955 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import hashlib
16
+ import os
17
+ from contextlib import contextmanager, nullcontext
18
+ from dataclasses import dataclass, replace
19
+ from enum import Enum
20
+ from typing import Dict, List, Optional, Set, Tuple, Union
21
+
22
+ import safetensors.torch
23
+ import torch
24
+
25
+ from ..utils import get_logger, is_accelerate_available
26
+ from ._common import _GO_LC_SUPPORTED_PYTORCH_LAYERS
27
+ from .hooks import HookRegistry, ModelHook
28
+
29
+
30
+ if is_accelerate_available():
31
+ from accelerate.hooks import AlignDevicesHook, CpuOffload
32
+ from accelerate.utils import send_to_device
33
+
34
+
35
+ logger = get_logger(__name__) # pylint: disable=invalid-name
36
+
37
+
38
+ # fmt: off
39
+ _GROUP_OFFLOADING = "group_offloading"
40
+ _LAYER_EXECUTION_TRACKER = "layer_execution_tracker"
41
+ _LAZY_PREFETCH_GROUP_OFFLOADING = "lazy_prefetch_group_offloading"
42
+ _GROUP_ID_LAZY_LEAF = "lazy_leafs"
43
+ # fmt: on
44
+
45
+
46
+ class GroupOffloadingType(str, Enum):
47
+ BLOCK_LEVEL = "block_level"
48
+ LEAF_LEVEL = "leaf_level"
49
+
50
+
51
+ @dataclass
52
+ class GroupOffloadingConfig:
53
+ onload_device: torch.device
54
+ offload_device: torch.device
55
+ offload_type: GroupOffloadingType
56
+ non_blocking: bool
57
+ record_stream: bool
58
+ low_cpu_mem_usage: bool
59
+ num_blocks_per_group: Optional[int] = None
60
+ offload_to_disk_path: Optional[str] = None
61
+ stream: Optional[Union[torch.cuda.Stream, torch.Stream]] = None
62
+ block_modules: Optional[List[str]] = None
63
+ exclude_kwargs: Optional[List[str]] = None
64
+ module_prefix: Optional[str] = ""
65
+
66
+
67
+ class ModuleGroup:
68
+ def __init__(
69
+ self,
70
+ modules: List[torch.nn.Module],
71
+ offload_device: torch.device,
72
+ onload_device: torch.device,
73
+ offload_leader: torch.nn.Module,
74
+ onload_leader: Optional[torch.nn.Module] = None,
75
+ parameters: Optional[List[torch.nn.Parameter]] = None,
76
+ buffers: Optional[List[torch.Tensor]] = None,
77
+ non_blocking: bool = False,
78
+ stream: Union[torch.cuda.Stream, torch.Stream, None] = None,
79
+ record_stream: Optional[bool] = False,
80
+ low_cpu_mem_usage: bool = False,
81
+ onload_self: bool = True,
82
+ offload_to_disk_path: Optional[str] = None,
83
+ group_id: Optional[Union[int, str]] = None,
84
+ ) -> None:
85
+ self.modules = modules
86
+ self.offload_device = offload_device
87
+ self.onload_device = onload_device
88
+ self.offload_leader = offload_leader
89
+ self.onload_leader = onload_leader
90
+ self.parameters = parameters or []
91
+ self.buffers = buffers or []
92
+ self.non_blocking = non_blocking or stream is not None
93
+ self.stream = stream
94
+ self.record_stream = record_stream
95
+ self.onload_self = onload_self
96
+ self.low_cpu_mem_usage = low_cpu_mem_usage
97
+
98
+ self.offload_to_disk_path = offload_to_disk_path
99
+ self._is_offloaded_to_disk = False
100
+
101
+ if self.offload_to_disk_path is not None:
102
+ # Instead of `group_id or str(id(self))` we do this because `group_id` can be "" as well.
103
+ self.group_id = group_id if group_id is not None else str(id(self))
104
+ short_hash = _compute_group_hash(self.group_id)
105
+ self.safetensors_file_path = os.path.join(self.offload_to_disk_path, f"group_{short_hash}.safetensors")
106
+
107
+ all_tensors = []
108
+ for module in self.modules:
109
+ all_tensors.extend(list(module.parameters()))
110
+ all_tensors.extend(list(module.buffers()))
111
+ all_tensors.extend(self.parameters)
112
+ all_tensors.extend(self.buffers)
113
+ all_tensors = list(dict.fromkeys(all_tensors)) # Remove duplicates
114
+
115
+ self.tensor_to_key = {tensor: f"tensor_{i}" for i, tensor in enumerate(all_tensors)}
116
+ self.key_to_tensor = {v: k for k, v in self.tensor_to_key.items()}
117
+ self.cpu_param_dict = {}
118
+ else:
119
+ self.cpu_param_dict = self._init_cpu_param_dict()
120
+
121
+ self._torch_accelerator_module = (
122
+ getattr(torch, torch.accelerator.current_accelerator().type)
123
+ if hasattr(torch, "accelerator")
124
+ else torch.cuda
125
+ )
126
+
127
+ def _init_cpu_param_dict(self):
128
+ cpu_param_dict = {}
129
+ if self.stream is None:
130
+ return cpu_param_dict
131
+
132
+ for module in self.modules:
133
+ for param in module.parameters():
134
+ cpu_param_dict[param] = param.data.cpu() if self.low_cpu_mem_usage else param.data.cpu().pin_memory()
135
+ for buffer in module.buffers():
136
+ cpu_param_dict[buffer] = (
137
+ buffer.data.cpu() if self.low_cpu_mem_usage else buffer.data.cpu().pin_memory()
138
+ )
139
+
140
+ for param in self.parameters:
141
+ cpu_param_dict[param] = param.data.cpu() if self.low_cpu_mem_usage else param.data.cpu().pin_memory()
142
+
143
+ for buffer in self.buffers:
144
+ cpu_param_dict[buffer] = buffer.data.cpu() if self.low_cpu_mem_usage else buffer.data.cpu().pin_memory()
145
+
146
+ return cpu_param_dict
147
+
148
+ @contextmanager
149
+ def _pinned_memory_tensors(self):
150
+ try:
151
+ pinned_dict = {
152
+ param: tensor.pin_memory() if not tensor.is_pinned() else tensor
153
+ for param, tensor in self.cpu_param_dict.items()
154
+ }
155
+ yield pinned_dict
156
+ finally:
157
+ pinned_dict = None
158
+
159
+ def _transfer_tensor_to_device(self, tensor, source_tensor, default_stream):
160
+ tensor.data = source_tensor.to(self.onload_device, non_blocking=self.non_blocking)
161
+ if self.record_stream:
162
+ tensor.data.record_stream(default_stream)
163
+
164
+ def _process_tensors_from_modules(self, pinned_memory=None, default_stream=None):
165
+ for group_module in self.modules:
166
+ for param in group_module.parameters():
167
+ source = pinned_memory[param] if pinned_memory else param.data
168
+ self._transfer_tensor_to_device(param, source, default_stream)
169
+ for buffer in group_module.buffers():
170
+ source = pinned_memory[buffer] if pinned_memory else buffer.data
171
+ self._transfer_tensor_to_device(buffer, source, default_stream)
172
+
173
+ for param in self.parameters:
174
+ source = pinned_memory[param] if pinned_memory else param.data
175
+ self._transfer_tensor_to_device(param, source, default_stream)
176
+
177
+ for buffer in self.buffers:
178
+ source = pinned_memory[buffer] if pinned_memory else buffer.data
179
+ self._transfer_tensor_to_device(buffer, source, default_stream)
180
+
181
+ def _onload_from_disk(self):
182
+ if self.stream is not None:
183
+ # Wait for previous Host->Device transfer to complete
184
+ self.stream.synchronize()
185
+
186
+ context = nullcontext() if self.stream is None else self._torch_accelerator_module.stream(self.stream)
187
+ current_stream = self._torch_accelerator_module.current_stream() if self.record_stream else None
188
+
189
+ with context:
190
+ # Load to CPU (if using streams) or directly to target device, pin, and async copy to device
191
+ device = str(self.onload_device) if self.stream is None else "cpu"
192
+ loaded_tensors = safetensors.torch.load_file(self.safetensors_file_path, device=device)
193
+
194
+ if self.stream is not None:
195
+ for key, tensor_obj in self.key_to_tensor.items():
196
+ pinned_tensor = loaded_tensors[key].pin_memory()
197
+ tensor_obj.data = pinned_tensor.to(self.onload_device, non_blocking=self.non_blocking)
198
+ if self.record_stream:
199
+ tensor_obj.data.record_stream(current_stream)
200
+ else:
201
+ onload_device = (
202
+ self.onload_device.type if isinstance(self.onload_device, torch.device) else self.onload_device
203
+ )
204
+ loaded_tensors = safetensors.torch.load_file(self.safetensors_file_path, device=onload_device)
205
+ for key, tensor_obj in self.key_to_tensor.items():
206
+ tensor_obj.data = loaded_tensors[key]
207
+
208
+ def _onload_from_memory(self):
209
+ if self.stream is not None:
210
+ # Wait for previous Host->Device transfer to complete
211
+ self.stream.synchronize()
212
+
213
+ context = nullcontext() if self.stream is None else self._torch_accelerator_module.stream(self.stream)
214
+ default_stream = self._torch_accelerator_module.current_stream() if self.stream is not None else None
215
+
216
+ with context:
217
+ if self.stream is not None:
218
+ with self._pinned_memory_tensors() as pinned_memory:
219
+ self._process_tensors_from_modules(pinned_memory, default_stream=default_stream)
220
+ else:
221
+ self._process_tensors_from_modules(None)
222
+
223
+ def _offload_to_disk(self):
224
+ # TODO: we can potentially optimize this code path by checking if the _all_ the desired
225
+ # safetensor files exist on the disk and if so, skip this step entirely, reducing IO
226
+ # overhead. Currently, we just check if the given `safetensors_file_path` exists and if not
227
+ # we perform a write.
228
+ # Check if the file has been saved in this session or if it already exists on disk.
229
+ if not self._is_offloaded_to_disk and not os.path.exists(self.safetensors_file_path):
230
+ os.makedirs(os.path.dirname(self.safetensors_file_path), exist_ok=True)
231
+ tensors_to_save = {key: tensor.data.to(self.offload_device) for tensor, key in self.tensor_to_key.items()}
232
+ safetensors.torch.save_file(tensors_to_save, self.safetensors_file_path)
233
+
234
+ # The group is now considered offloaded to disk for the rest of the session.
235
+ self._is_offloaded_to_disk = True
236
+
237
+ # We do this to free up the RAM which is still holding the up tensor data.
238
+ for tensor_obj in self.tensor_to_key.keys():
239
+ tensor_obj.data = torch.empty_like(tensor_obj.data, device=self.offload_device)
240
+
241
+ def _offload_to_memory(self):
242
+ if self.stream is not None:
243
+ if not self.record_stream:
244
+ self._torch_accelerator_module.current_stream().synchronize()
245
+
246
+ for group_module in self.modules:
247
+ for param in group_module.parameters():
248
+ param.data = self.cpu_param_dict[param]
249
+ for param in self.parameters:
250
+ param.data = self.cpu_param_dict[param]
251
+ for buffer in self.buffers:
252
+ buffer.data = self.cpu_param_dict[buffer]
253
+ else:
254
+ for group_module in self.modules:
255
+ group_module.to(self.offload_device, non_blocking=False)
256
+ for param in self.parameters:
257
+ param.data = param.data.to(self.offload_device, non_blocking=False)
258
+ for buffer in self.buffers:
259
+ buffer.data = buffer.data.to(self.offload_device, non_blocking=False)
260
+
261
+ @torch.compiler.disable()
262
+ def onload_(self):
263
+ r"""Onloads the group of parameters to the onload_device."""
264
+ if self.offload_to_disk_path is not None:
265
+ self._onload_from_disk()
266
+ else:
267
+ self._onload_from_memory()
268
+
269
+ @torch.compiler.disable()
270
+ def offload_(self):
271
+ r"""Offloads the group of parameters to the offload_device."""
272
+ if self.offload_to_disk_path:
273
+ self._offload_to_disk()
274
+ else:
275
+ self._offload_to_memory()
276
+
277
+
278
+ class GroupOffloadingHook(ModelHook):
279
+ r"""
280
+ A hook that offloads groups of torch.nn.Module to the CPU for storage and onloads to accelerator device for
281
+ computation. Each group has one "onload leader" module that is responsible for onloading, and an "offload leader"
282
+ module that is responsible for offloading. If prefetching is enabled, the onload leader of the previous module
283
+ group is responsible for onloading the current module group.
284
+ """
285
+
286
+ _is_stateful = False
287
+
288
+ def __init__(self, group: ModuleGroup, *, config: GroupOffloadingConfig) -> None:
289
+ self.group = group
290
+ self.next_group: Optional[ModuleGroup] = None
291
+ self.config = config
292
+
293
+ def initialize_hook(self, module: torch.nn.Module) -> torch.nn.Module:
294
+ if self.group.offload_leader == module:
295
+ self.group.offload_()
296
+ return module
297
+
298
+ def pre_forward(self, module: torch.nn.Module, *args, **kwargs):
299
+ # If there wasn't an onload_leader assigned, we assume that the submodule that first called its forward
300
+ # method is the onload_leader of the group.
301
+ if self.group.onload_leader is None:
302
+ self.group.onload_leader = module
303
+
304
+ # If the current module is the onload_leader of the group, we onload the group if it is supposed
305
+ # to onload itself. In the case of using prefetching with streams, we onload the next group if
306
+ # it is not supposed to onload itself.
307
+ if self.group.onload_leader == module:
308
+ if self.group.onload_self:
309
+ self.group.onload_()
310
+
311
+ should_onload_next_group = self.next_group is not None and not self.next_group.onload_self
312
+ if should_onload_next_group:
313
+ self.next_group.onload_()
314
+
315
+ should_synchronize = (
316
+ not self.group.onload_self and self.group.stream is not None and not should_onload_next_group
317
+ )
318
+ if should_synchronize:
319
+ # If this group didn't onload itself, it means it was asynchronously onloaded by the
320
+ # previous group. We need to synchronize the side stream to ensure parameters
321
+ # are completely loaded to proceed with forward pass. Without this, uninitialized
322
+ # weights will be used in the computation, leading to incorrect results
323
+ # Also, we should only do this synchronization if we don't already do it from the sync call in
324
+ # self.next_group.onload_, hence the `not should_onload_next_group` check.
325
+ self.group.stream.synchronize()
326
+
327
+ args = send_to_device(args, self.group.onload_device, non_blocking=self.group.non_blocking)
328
+
329
+ # Some Autoencoder models use a feature cache that is passed through submodules
330
+ # and modified in place. The `send_to_device` call returns a copy of this feature cache object
331
+ # which breaks the inplace updates. Use `exclude_kwargs` to mark these cache features
332
+ exclude_kwargs = self.config.exclude_kwargs or []
333
+ if exclude_kwargs:
334
+ moved_kwargs = send_to_device(
335
+ {k: v for k, v in kwargs.items() if k not in exclude_kwargs},
336
+ self.group.onload_device,
337
+ non_blocking=self.group.non_blocking,
338
+ )
339
+ kwargs.update(moved_kwargs)
340
+ else:
341
+ kwargs = send_to_device(kwargs, self.group.onload_device, non_blocking=self.group.non_blocking)
342
+
343
+ return args, kwargs
344
+
345
+ def post_forward(self, module: torch.nn.Module, output):
346
+ if self.group.offload_leader == module:
347
+ self.group.offload_()
348
+ return output
349
+
350
+
351
+ class LazyPrefetchGroupOffloadingHook(ModelHook):
352
+ r"""
353
+ A hook, used in conjunction with GroupOffloadingHook, that applies lazy prefetching to groups of torch.nn.Module.
354
+ This hook is used to determine the order in which the layers are executed during the forward pass. Once the layer
355
+ invocation order is known, assignments of the next_group attribute for prefetching can be made, which allows
356
+ prefetching groups in the correct order.
357
+ """
358
+
359
+ _is_stateful = False
360
+
361
+ def __init__(self):
362
+ self.execution_order: List[Tuple[str, torch.nn.Module]] = []
363
+ self._layer_execution_tracker_module_names = set()
364
+
365
+ def initialize_hook(self, module):
366
+ def make_execution_order_update_callback(current_name, current_submodule):
367
+ def callback():
368
+ if not torch.compiler.is_compiling():
369
+ logger.debug(f"Adding {current_name} to the execution order")
370
+ self.execution_order.append((current_name, current_submodule))
371
+
372
+ return callback
373
+
374
+ # To every submodule that contains a group offloading hook (at this point, no prefetching is enabled for any
375
+ # of the groups), we add a layer execution tracker hook that will be used to determine the order in which the
376
+ # layers are executed during the forward pass.
377
+ for name, submodule in module.named_modules():
378
+ if name == "" or not hasattr(submodule, "_diffusers_hook"):
379
+ continue
380
+
381
+ registry = HookRegistry.check_if_exists_or_initialize(submodule)
382
+ group_offloading_hook = registry.get_hook(_GROUP_OFFLOADING)
383
+
384
+ if group_offloading_hook is not None:
385
+ # For the first forward pass, we have to load in a blocking manner
386
+ group_offloading_hook.group.non_blocking = False
387
+ layer_tracker_hook = LayerExecutionTrackerHook(make_execution_order_update_callback(name, submodule))
388
+ registry.register_hook(layer_tracker_hook, _LAYER_EXECUTION_TRACKER)
389
+ self._layer_execution_tracker_module_names.add(name)
390
+
391
+ return module
392
+
393
+ def post_forward(self, module, output):
394
+ # At this point, for the current modules' submodules, we know the execution order of the layers. We can now
395
+ # remove the layer execution tracker hooks and apply prefetching by setting the next_group attribute for each
396
+ # group offloading hook.
397
+ num_executed = len(self.execution_order)
398
+ execution_order_module_names = {name for name, _ in self.execution_order}
399
+
400
+ # It may be possible that some layers were not executed during the forward pass. This can happen if the layer
401
+ # is not used in the forward pass, or if the layer is not executed due to some other reason. In such cases, we
402
+ # may not be able to apply prefetching in the correct order, which can lead to device-mismatch related errors
403
+ # if the missing layers end up being executed in the future.
404
+ if execution_order_module_names != self._layer_execution_tracker_module_names:
405
+ unexecuted_layers = list(self._layer_execution_tracker_module_names - execution_order_module_names)
406
+ if not torch.compiler.is_compiling():
407
+ logger.warning(
408
+ "It seems like some layers were not executed during the forward pass. This may lead to problems when "
409
+ "applying lazy prefetching with automatic tracing and lead to device-mismatch related errors. Please "
410
+ "make sure that all layers are executed during the forward pass. The following layers were not executed:\n"
411
+ f"{unexecuted_layers=}"
412
+ )
413
+
414
+ # Remove the layer execution tracker hooks from the submodules
415
+ base_module_registry = module._diffusers_hook
416
+ registries = [submodule._diffusers_hook for _, submodule in self.execution_order]
417
+ group_offloading_hooks = [registry.get_hook(_GROUP_OFFLOADING) for registry in registries]
418
+
419
+ for i in range(num_executed):
420
+ registries[i].remove_hook(_LAYER_EXECUTION_TRACKER, recurse=False)
421
+
422
+ # Remove the current lazy prefetch group offloading hook so that it doesn't interfere with the next forward pass
423
+ base_module_registry.remove_hook(_LAZY_PREFETCH_GROUP_OFFLOADING, recurse=False)
424
+
425
+ # LazyPrefetchGroupOffloadingHook is only used with streams, so we know that non_blocking should be True.
426
+ # We disable non_blocking for the first forward pass, but need to enable it for the subsequent passes to
427
+ # see the benefits of prefetching.
428
+ for hook in group_offloading_hooks:
429
+ hook.group.non_blocking = True
430
+
431
+ # Set required attributes for prefetching
432
+ if num_executed > 0:
433
+ base_module_group_offloading_hook = base_module_registry.get_hook(_GROUP_OFFLOADING)
434
+ base_module_group_offloading_hook.next_group = group_offloading_hooks[0].group
435
+ base_module_group_offloading_hook.next_group.onload_self = False
436
+
437
+ for i in range(num_executed - 1):
438
+ name1, _ = self.execution_order[i]
439
+ name2, _ = self.execution_order[i + 1]
440
+ if not torch.compiler.is_compiling():
441
+ logger.debug(f"Applying lazy prefetch group offloading from {name1} to {name2}")
442
+ group_offloading_hooks[i].next_group = group_offloading_hooks[i + 1].group
443
+ group_offloading_hooks[i].next_group.onload_self = False
444
+
445
+ return output
446
+
447
+
448
+ class LayerExecutionTrackerHook(ModelHook):
449
+ r"""
450
+ A hook that tracks the order in which the layers are executed during the forward pass by calling back to the
451
+ LazyPrefetchGroupOffloadingHook to update the execution order.
452
+ """
453
+
454
+ _is_stateful = False
455
+
456
+ def __init__(self, execution_order_update_callback):
457
+ self.execution_order_update_callback = execution_order_update_callback
458
+
459
+ def pre_forward(self, module, *args, **kwargs):
460
+ self.execution_order_update_callback()
461
+ return args, kwargs
462
+
463
+
464
+ def apply_group_offloading(
465
+ module: torch.nn.Module,
466
+ onload_device: Union[str, torch.device],
467
+ offload_device: Union[str, torch.device] = torch.device("cpu"),
468
+ offload_type: Union[str, GroupOffloadingType] = "block_level",
469
+ num_blocks_per_group: Optional[int] = None,
470
+ non_blocking: bool = False,
471
+ use_stream: bool = False,
472
+ record_stream: bool = False,
473
+ low_cpu_mem_usage: bool = False,
474
+ offload_to_disk_path: Optional[str] = None,
475
+ block_modules: Optional[List[str]] = None,
476
+ exclude_kwargs: Optional[List[str]] = None,
477
+ ) -> None:
478
+ r"""
479
+ Applies group offloading to the internal layers of a torch.nn.Module. To understand what group offloading is, and
480
+ where it is beneficial, we need to first provide some context on how other supported offloading methods work.
481
+
482
+ Typically, offloading is done at two levels:
483
+ - Module-level: In Diffusers, this can be enabled using the `ModelMixin::enable_model_cpu_offload()` method. It
484
+ works by offloading each component of a pipeline to the CPU for storage, and onloading to the accelerator device
485
+ when needed for computation. This method is more memory-efficient than keeping all components on the accelerator,
486
+ but the memory requirements are still quite high. For this method to work, one needs memory equivalent to size of
487
+ the model in runtime dtype + size of largest intermediate activation tensors to be able to complete the forward
488
+ pass.
489
+ - Leaf-level: In Diffusers, this can be enabled using the `ModelMixin::enable_sequential_cpu_offload()` method. It
490
+ works by offloading the lowest leaf-level parameters of the computation graph to the CPU for storage, and
491
+ onloading only the leafs to the accelerator device for computation. This uses the lowest amount of accelerator
492
+ memory, but can be slower due to the excessive number of device synchronizations.
493
+
494
+ Group offloading is a middle ground between the two methods. It works by offloading groups of internal layers,
495
+ (either `torch.nn.ModuleList` or `torch.nn.Sequential`). This method uses lower memory than module-level
496
+ offloading. It is also faster than leaf-level/sequential offloading, as the number of device synchronizations is
497
+ reduced.
498
+
499
+ Another supported feature (for CUDA devices with support for asynchronous data transfer streams) is the ability to
500
+ overlap data transfer and computation to reduce the overall execution time compared to sequential offloading. This
501
+ is enabled using layer prefetching with streams, i.e., the layer that is to be executed next starts onloading to
502
+ the accelerator device while the current layer is being executed - this increases the memory requirements slightly.
503
+ Note that this implementation also supports leaf-level offloading but can be made much faster when using streams.
504
+
505
+ Args:
506
+ module (`torch.nn.Module`):
507
+ The module to which group offloading is applied.
508
+ onload_device (`torch.device`):
509
+ The device to which the group of modules are onloaded.
510
+ offload_device (`torch.device`, defaults to `torch.device("cpu")`):
511
+ The device to which the group of modules are offloaded. This should typically be the CPU. Default is CPU.
512
+ offload_type (`str` or `GroupOffloadingType`, defaults to "block_level"):
513
+ The type of offloading to be applied. Can be one of "block_level" or "leaf_level". Default is
514
+ "block_level".
515
+ offload_to_disk_path (`str`, *optional*, defaults to `None`):
516
+ The path to the directory where parameters will be offloaded. Setting this option can be useful in limited
517
+ RAM environment settings where a reasonable speed-memory trade-off is desired.
518
+ num_blocks_per_group (`int`, *optional*):
519
+ The number of blocks per group when using offload_type="block_level". This is required when using
520
+ offload_type="block_level".
521
+ non_blocking (`bool`, defaults to `False`):
522
+ If True, offloading and onloading is done with non-blocking data transfer.
523
+ use_stream (`bool`, defaults to `False`):
524
+ If True, offloading and onloading is done asynchronously using a CUDA stream. This can be useful for
525
+ overlapping computation and data transfer.
526
+ record_stream (`bool`, defaults to `False`): When enabled with `use_stream`, it marks the current tensor
527
+ as having been used by this stream. It is faster at the expense of slightly more memory usage. Refer to the
528
+ [PyTorch official docs](https://pytorch.org/docs/stable/generated/torch.Tensor.record_stream.html) more
529
+ details.
530
+ low_cpu_mem_usage (`bool`, defaults to `False`):
531
+ If True, the CPU memory usage is minimized by pinning tensors on-the-fly instead of pre-pinning them. This
532
+ option only matters when using streamed CPU offloading (i.e. `use_stream=True`). This can be useful when
533
+ the CPU memory is a bottleneck but may counteract the benefits of using streams.
534
+ block_modules (`List[str]`, *optional*):
535
+ List of module names that should be treated as blocks for offloading. If provided, only these modules will
536
+ be considered for block-level offloading. If not provided, the default block detection logic will be used.
537
+ exclude_kwargs (`List[str]`, *optional*):
538
+ List of kwarg keys that should not be processed by send_to_device. This is useful for mutable state like
539
+ caching lists that need to maintain their object identity across forward passes. If not provided, will be
540
+ inferred from the module's `_skip_keys` attribute if it exists.
541
+
542
+ Example:
543
+ ```python
544
+ >>> from diffusers import CogVideoXTransformer3DModel
545
+ >>> from diffusers.hooks import apply_group_offloading
546
+
547
+ >>> transformer = CogVideoXTransformer3DModel.from_pretrained(
548
+ ... "THUDM/CogVideoX-5b", subfolder="transformer", torch_dtype=torch.bfloat16
549
+ ... )
550
+
551
+ >>> apply_group_offloading(
552
+ ... transformer,
553
+ ... onload_device=torch.device("cuda"),
554
+ ... offload_device=torch.device("cpu"),
555
+ ... offload_type="block_level",
556
+ ... num_blocks_per_group=2,
557
+ ... use_stream=True,
558
+ ... )
559
+ ```
560
+ """
561
+
562
+ onload_device = torch.device(onload_device) if isinstance(onload_device, str) else onload_device
563
+ offload_device = torch.device(offload_device) if isinstance(offload_device, str) else offload_device
564
+ offload_type = GroupOffloadingType(offload_type)
565
+
566
+ stream = None
567
+ if use_stream:
568
+ if torch.cuda.is_available():
569
+ stream = torch.cuda.Stream()
570
+ elif hasattr(torch, "xpu") and torch.xpu.is_available():
571
+ stream = torch.Stream()
572
+ else:
573
+ raise ValueError("Using streams for data transfer requires a CUDA device, or an Intel XPU device.")
574
+
575
+ if not use_stream and record_stream:
576
+ raise ValueError("`record_stream` cannot be True when `use_stream=False`.")
577
+ if offload_type == GroupOffloadingType.BLOCK_LEVEL and num_blocks_per_group is None:
578
+ raise ValueError("`num_blocks_per_group` must be provided when using `offload_type='block_level'.")
579
+
580
+ _raise_error_if_accelerate_model_or_sequential_hook_present(module)
581
+
582
+ if block_modules is None:
583
+ block_modules = getattr(module, "_group_offload_block_modules", None)
584
+
585
+ if exclude_kwargs is None:
586
+ exclude_kwargs = getattr(module, "_skip_keys", None)
587
+
588
+ config = GroupOffloadingConfig(
589
+ onload_device=onload_device,
590
+ offload_device=offload_device,
591
+ offload_type=offload_type,
592
+ num_blocks_per_group=num_blocks_per_group,
593
+ non_blocking=non_blocking,
594
+ stream=stream,
595
+ record_stream=record_stream,
596
+ low_cpu_mem_usage=low_cpu_mem_usage,
597
+ offload_to_disk_path=offload_to_disk_path,
598
+ block_modules=block_modules,
599
+ exclude_kwargs=exclude_kwargs,
600
+ )
601
+ _apply_group_offloading(module, config)
602
+
603
+
604
+ def _apply_group_offloading(module: torch.nn.Module, config: GroupOffloadingConfig) -> None:
605
+ if config.offload_type == GroupOffloadingType.BLOCK_LEVEL:
606
+ _apply_group_offloading_block_level(module, config)
607
+ elif config.offload_type == GroupOffloadingType.LEAF_LEVEL:
608
+ _apply_group_offloading_leaf_level(module, config)
609
+ else:
610
+ assert False
611
+
612
+
613
+ def _apply_group_offloading_block_level(module: torch.nn.Module, config: GroupOffloadingConfig) -> None:
614
+ r"""
615
+ This function applies offloading to groups of torch.nn.ModuleList or torch.nn.Sequential blocks, and explicitly
616
+ defined block modules. In comparison to the "leaf_level" offloading, which is more fine-grained, this offloading is
617
+ done at the top-level blocks and modules specified in block_modules.
618
+
619
+ When block_modules is provided, only those modules will be treated as blocks for offloading. For each specified
620
+ module, recursively apply block offloading to it.
621
+ """
622
+ if config.stream is not None and config.num_blocks_per_group != 1:
623
+ logger.warning(
624
+ f"Using streams is only supported for num_blocks_per_group=1. Got {config.num_blocks_per_group=}. Setting it to 1."
625
+ )
626
+ config.num_blocks_per_group = 1
627
+
628
+ block_modules = set(config.block_modules) if config.block_modules is not None else set()
629
+
630
+ # Create module groups for ModuleList and Sequential blocks, and explicitly defined block modules
631
+ modules_with_group_offloading = set()
632
+ unmatched_modules = []
633
+ matched_module_groups = []
634
+
635
+ for name, submodule in module.named_children():
636
+ # Check if this is an explicitly defined block module
637
+ if name in block_modules:
638
+ # Track submodule using a prefix to avoid filename collisions during disk offload.
639
+ # Without this, submodules sharing the same model class would be assigned identical
640
+ # filenames (derived from the class name).
641
+ prefix = f"{config.module_prefix}{name}." if config.module_prefix else f"{name}."
642
+ submodule_config = replace(config, module_prefix=prefix)
643
+
644
+ _apply_group_offloading_block_level(submodule, submodule_config)
645
+ modules_with_group_offloading.add(name)
646
+
647
+ elif isinstance(submodule, (torch.nn.ModuleList, torch.nn.Sequential)):
648
+ # Handle ModuleList and Sequential blocks as before
649
+ for i in range(0, len(submodule), config.num_blocks_per_group):
650
+ current_modules = list(submodule[i : i + config.num_blocks_per_group])
651
+ if len(current_modules) == 0:
652
+ continue
653
+
654
+ group_id = f"{config.module_prefix}{name}_{i}_{i + len(current_modules) - 1}"
655
+ group = ModuleGroup(
656
+ modules=current_modules,
657
+ offload_device=config.offload_device,
658
+ onload_device=config.onload_device,
659
+ offload_to_disk_path=config.offload_to_disk_path,
660
+ offload_leader=current_modules[-1],
661
+ onload_leader=current_modules[0],
662
+ non_blocking=config.non_blocking,
663
+ stream=config.stream,
664
+ record_stream=config.record_stream,
665
+ low_cpu_mem_usage=config.low_cpu_mem_usage,
666
+ onload_self=True,
667
+ group_id=group_id,
668
+ )
669
+ matched_module_groups.append(group)
670
+ for j in range(i, i + len(current_modules)):
671
+ modules_with_group_offloading.add(f"{name}.{j}")
672
+ else:
673
+ # This is an unmatched module
674
+ unmatched_modules.append((name, submodule))
675
+
676
+ # Apply group offloading hooks to the module groups
677
+ for i, group in enumerate(matched_module_groups):
678
+ for group_module in group.modules:
679
+ _apply_group_offloading_hook(group_module, group, config=config)
680
+
681
+ # Parameters and Buffers of the top-level module need to be offloaded/onloaded separately
682
+ # when the forward pass of this module is called. This is because the top-level module is not
683
+ # part of any group (as doing so would lead to no VRAM savings).
684
+ parameters = _gather_parameters_with_no_group_offloading_parent(module, modules_with_group_offloading)
685
+ buffers = _gather_buffers_with_no_group_offloading_parent(module, modules_with_group_offloading)
686
+ parameters = [param for _, param in parameters]
687
+ buffers = [buffer for _, buffer in buffers]
688
+
689
+ # Create a group for the remaining unmatched submodules of the top-level
690
+ # module so that they are on the correct device when the forward pass is called.
691
+ unmatched_modules = [unmatched_module for _, unmatched_module in unmatched_modules]
692
+ if len(unmatched_modules) > 0 or len(parameters) > 0 or len(buffers) > 0:
693
+ unmatched_group = ModuleGroup(
694
+ modules=unmatched_modules,
695
+ offload_device=config.offload_device,
696
+ onload_device=config.onload_device,
697
+ offload_to_disk_path=config.offload_to_disk_path,
698
+ offload_leader=module,
699
+ onload_leader=module,
700
+ parameters=parameters,
701
+ buffers=buffers,
702
+ non_blocking=False,
703
+ stream=None,
704
+ record_stream=False,
705
+ onload_self=True,
706
+ group_id=f"{config.module_prefix}{module.__class__.__name__}_unmatched_group",
707
+ )
708
+ if config.stream is None:
709
+ _apply_group_offloading_hook(module, unmatched_group, config=config)
710
+ else:
711
+ _apply_lazy_group_offloading_hook(module, unmatched_group, config=config)
712
+
713
+
714
+ def _apply_group_offloading_leaf_level(module: torch.nn.Module, config: GroupOffloadingConfig) -> None:
715
+ r"""
716
+ This function applies offloading to groups of leaf modules in a torch.nn.Module. This method has minimal memory
717
+ requirements. However, it can be slower compared to other offloading methods due to the excessive number of device
718
+ synchronizations. When using devices that support streams to overlap data transfer and computation, this method can
719
+ reduce memory usage without any performance degradation.
720
+ """
721
+ # Create module groups for leaf modules and apply group offloading hooks
722
+ modules_with_group_offloading = set()
723
+ for name, submodule in module.named_modules():
724
+ if not isinstance(submodule, _GO_LC_SUPPORTED_PYTORCH_LAYERS):
725
+ continue
726
+ group = ModuleGroup(
727
+ modules=[submodule],
728
+ offload_device=config.offload_device,
729
+ onload_device=config.onload_device,
730
+ offload_to_disk_path=config.offload_to_disk_path,
731
+ offload_leader=submodule,
732
+ onload_leader=submodule,
733
+ non_blocking=config.non_blocking,
734
+ stream=config.stream,
735
+ record_stream=config.record_stream,
736
+ low_cpu_mem_usage=config.low_cpu_mem_usage,
737
+ onload_self=True,
738
+ group_id=name,
739
+ )
740
+ _apply_group_offloading_hook(submodule, group, config=config)
741
+ modules_with_group_offloading.add(name)
742
+
743
+ # Parameters and Buffers at all non-leaf levels need to be offloaded/onloaded separately when the forward pass
744
+ # of the module is called
745
+ module_dict = dict(module.named_modules())
746
+ parameters = _gather_parameters_with_no_group_offloading_parent(module, modules_with_group_offloading)
747
+ buffers = _gather_buffers_with_no_group_offloading_parent(module, modules_with_group_offloading)
748
+
749
+ # Find closest module parent for each parameter and buffer, and attach group hooks
750
+ parent_to_parameters = {}
751
+ for name, param in parameters:
752
+ parent_name = _find_parent_module_in_module_dict(name, module_dict)
753
+ if parent_name in parent_to_parameters:
754
+ parent_to_parameters[parent_name].append(param)
755
+ else:
756
+ parent_to_parameters[parent_name] = [param]
757
+
758
+ parent_to_buffers = {}
759
+ for name, buffer in buffers:
760
+ parent_name = _find_parent_module_in_module_dict(name, module_dict)
761
+ if parent_name in parent_to_buffers:
762
+ parent_to_buffers[parent_name].append(buffer)
763
+ else:
764
+ parent_to_buffers[parent_name] = [buffer]
765
+
766
+ parent_names = set(parent_to_parameters.keys()) | set(parent_to_buffers.keys())
767
+ for name in parent_names:
768
+ parameters = parent_to_parameters.get(name, [])
769
+ buffers = parent_to_buffers.get(name, [])
770
+ parent_module = module_dict[name]
771
+ group = ModuleGroup(
772
+ modules=[],
773
+ offload_device=config.offload_device,
774
+ onload_device=config.onload_device,
775
+ offload_leader=parent_module,
776
+ onload_leader=parent_module,
777
+ offload_to_disk_path=config.offload_to_disk_path,
778
+ parameters=parameters,
779
+ buffers=buffers,
780
+ non_blocking=config.non_blocking,
781
+ stream=config.stream,
782
+ record_stream=config.record_stream,
783
+ low_cpu_mem_usage=config.low_cpu_mem_usage,
784
+ onload_self=True,
785
+ group_id=name,
786
+ )
787
+ _apply_group_offloading_hook(parent_module, group, config=config)
788
+
789
+ if config.stream is not None:
790
+ # When using streams, we need to know the layer execution order for applying prefetching (to overlap data transfer
791
+ # and computation). Since we don't know the order beforehand, we apply a lazy prefetching hook that will find the
792
+ # execution order and apply prefetching in the correct order.
793
+ unmatched_group = ModuleGroup(
794
+ modules=[],
795
+ offload_device=config.offload_device,
796
+ onload_device=config.onload_device,
797
+ offload_to_disk_path=config.offload_to_disk_path,
798
+ offload_leader=module,
799
+ onload_leader=module,
800
+ parameters=None,
801
+ buffers=None,
802
+ non_blocking=False,
803
+ stream=None,
804
+ record_stream=False,
805
+ low_cpu_mem_usage=config.low_cpu_mem_usage,
806
+ onload_self=True,
807
+ group_id=_GROUP_ID_LAZY_LEAF,
808
+ )
809
+ _apply_lazy_group_offloading_hook(module, unmatched_group, config=config)
810
+
811
+
812
+ def _apply_group_offloading_hook(
813
+ module: torch.nn.Module,
814
+ group: ModuleGroup,
815
+ *,
816
+ config: GroupOffloadingConfig,
817
+ ) -> None:
818
+ registry = HookRegistry.check_if_exists_or_initialize(module)
819
+
820
+ # We may have already registered a group offloading hook if the module had a torch.nn.Parameter whose parent
821
+ # is the current module. In such cases, we don't want to overwrite the existing group offloading hook.
822
+ if registry.get_hook(_GROUP_OFFLOADING) is None:
823
+ hook = GroupOffloadingHook(group, config=config)
824
+ registry.register_hook(hook, _GROUP_OFFLOADING)
825
+
826
+
827
+ def _apply_lazy_group_offloading_hook(
828
+ module: torch.nn.Module,
829
+ group: ModuleGroup,
830
+ *,
831
+ config: GroupOffloadingConfig,
832
+ ) -> None:
833
+ registry = HookRegistry.check_if_exists_or_initialize(module)
834
+
835
+ # We may have already registered a group offloading hook if the module had a torch.nn.Parameter whose parent
836
+ # is the current module. In such cases, we don't want to overwrite the existing group offloading hook.
837
+ if registry.get_hook(_GROUP_OFFLOADING) is None:
838
+ hook = GroupOffloadingHook(group, config=config)
839
+ registry.register_hook(hook, _GROUP_OFFLOADING)
840
+
841
+ lazy_prefetch_hook = LazyPrefetchGroupOffloadingHook()
842
+ registry.register_hook(lazy_prefetch_hook, _LAZY_PREFETCH_GROUP_OFFLOADING)
843
+
844
+
845
+ def _gather_parameters_with_no_group_offloading_parent(
846
+ module: torch.nn.Module, modules_with_group_offloading: Set[str]
847
+ ) -> List[torch.nn.Parameter]:
848
+ parameters = []
849
+ for name, parameter in module.named_parameters():
850
+ has_parent_with_group_offloading = False
851
+ atoms = name.split(".")
852
+ while len(atoms) > 0:
853
+ parent_name = ".".join(atoms)
854
+ if parent_name in modules_with_group_offloading:
855
+ has_parent_with_group_offloading = True
856
+ break
857
+ atoms.pop()
858
+ if not has_parent_with_group_offloading:
859
+ parameters.append((name, parameter))
860
+ return parameters
861
+
862
+
863
+ def _gather_buffers_with_no_group_offloading_parent(
864
+ module: torch.nn.Module, modules_with_group_offloading: Set[str]
865
+ ) -> List[torch.Tensor]:
866
+ buffers = []
867
+ for name, buffer in module.named_buffers():
868
+ has_parent_with_group_offloading = False
869
+ atoms = name.split(".")
870
+ while len(atoms) > 0:
871
+ parent_name = ".".join(atoms)
872
+ if parent_name in modules_with_group_offloading:
873
+ has_parent_with_group_offloading = True
874
+ break
875
+ atoms.pop()
876
+ if not has_parent_with_group_offloading:
877
+ buffers.append((name, buffer))
878
+ return buffers
879
+
880
+
881
+ def _find_parent_module_in_module_dict(name: str, module_dict: Dict[str, torch.nn.Module]) -> str:
882
+ atoms = name.split(".")
883
+ while len(atoms) > 0:
884
+ parent_name = ".".join(atoms)
885
+ if parent_name in module_dict:
886
+ return parent_name
887
+ atoms.pop()
888
+ return ""
889
+
890
+
891
+ def _raise_error_if_accelerate_model_or_sequential_hook_present(module: torch.nn.Module) -> None:
892
+ if not is_accelerate_available():
893
+ return
894
+ for name, submodule in module.named_modules():
895
+ if not hasattr(submodule, "_hf_hook"):
896
+ continue
897
+ if isinstance(submodule._hf_hook, (AlignDevicesHook, CpuOffload)):
898
+ raise ValueError(
899
+ f"Cannot apply group offloading to a module that is already applying an alternative "
900
+ f"offloading strategy from Accelerate. If you want to apply group offloading, please "
901
+ f"disable the existing offloading strategy first. Offending module: {name} ({type(submodule)})"
902
+ )
903
+
904
+
905
+ def _get_top_level_group_offload_hook(module: torch.nn.Module) -> Optional[GroupOffloadingHook]:
906
+ for submodule in module.modules():
907
+ if hasattr(submodule, "_diffusers_hook"):
908
+ group_offloading_hook = submodule._diffusers_hook.get_hook(_GROUP_OFFLOADING)
909
+ if group_offloading_hook is not None:
910
+ return group_offloading_hook
911
+ return None
912
+
913
+
914
+ def _is_group_offload_enabled(module: torch.nn.Module) -> bool:
915
+ top_level_group_offload_hook = _get_top_level_group_offload_hook(module)
916
+ return top_level_group_offload_hook is not None
917
+
918
+
919
+ def _get_group_onload_device(module: torch.nn.Module) -> torch.device:
920
+ top_level_group_offload_hook = _get_top_level_group_offload_hook(module)
921
+ if top_level_group_offload_hook is not None:
922
+ return top_level_group_offload_hook.config.onload_device
923
+ raise ValueError("Group offloading is not enabled for the provided module.")
924
+
925
+
926
+ def _compute_group_hash(group_id):
927
+ hashed_id = hashlib.sha256(group_id.encode("utf-8")).hexdigest()
928
+ # first 16 characters for a reasonably short but unique name
929
+ return hashed_id[:16]
930
+
931
+
932
+ def _maybe_remove_and_reapply_group_offloading(module: torch.nn.Module) -> None:
933
+ r"""
934
+ Removes the group offloading hook from the module and re-applies it. This is useful when the module has been
935
+ modified in-place and the group offloading hook references-to-tensors needs to be updated. The in-place
936
+ modification can happen in a number of ways, for example, fusing QKV or unloading/loading LoRAs on-the-fly.
937
+
938
+ In this implementation, we make an assumption that group offloading has only been applied at the top-level module,
939
+ and therefore all submodules have the same onload and offload devices. If this assumption is not true, say in the
940
+ case where user has applied group offloading at multiple levels, this function will not work as expected.
941
+
942
+ There is some performance penalty associated with doing this when non-default streams are used, because we need to
943
+ retrace the execution order of the layers with `LazyPrefetchGroupOffloadingHook`.
944
+ """
945
+ top_level_group_offload_hook = _get_top_level_group_offload_hook(module)
946
+
947
+ if top_level_group_offload_hook is None:
948
+ return
949
+
950
+ registry = HookRegistry.check_if_exists_or_initialize(module)
951
+ registry.remove_hook(_GROUP_OFFLOADING, recurse=True)
952
+ registry.remove_hook(_LAYER_EXECUTION_TRACKER, recurse=True)
953
+ registry.remove_hook(_LAZY_PREFETCH_GROUP_OFFLOADING, recurse=True)
954
+
955
+ _apply_group_offloading(module, top_level_group_offload_hook.config)
vendor/diffusers/hooks/hooks.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import functools
16
+ from typing import Any, Dict, Optional, Tuple
17
+
18
+ import torch
19
+
20
+ from ..utils.logging import get_logger
21
+ from ..utils.torch_utils import unwrap_module
22
+
23
+
24
+ logger = get_logger(__name__) # pylint: disable=invalid-name
25
+
26
+
27
+ class BaseState:
28
+ def reset(self, *args, **kwargs) -> None:
29
+ raise NotImplementedError(
30
+ "BaseState::reset is not implemented. Please implement this method in the derived class."
31
+ )
32
+
33
+
34
+ class StateManager:
35
+ def __init__(self, state_cls: BaseState, init_args=None, init_kwargs=None):
36
+ self._state_cls = state_cls
37
+ self._init_args = init_args if init_args is not None else ()
38
+ self._init_kwargs = init_kwargs if init_kwargs is not None else {}
39
+ self._state_cache = {}
40
+ self._current_context = None
41
+
42
+ def get_state(self):
43
+ if self._current_context is None:
44
+ raise ValueError("No context is set. Please set a context before retrieving the state.")
45
+ if self._current_context not in self._state_cache.keys():
46
+ self._state_cache[self._current_context] = self._state_cls(*self._init_args, **self._init_kwargs)
47
+ return self._state_cache[self._current_context]
48
+
49
+ def set_context(self, name: str) -> None:
50
+ self._current_context = name
51
+
52
+ def reset(self, *args, **kwargs) -> None:
53
+ for name, state in list(self._state_cache.items()):
54
+ state.reset(*args, **kwargs)
55
+ self._state_cache.pop(name)
56
+ self._current_context = None
57
+
58
+
59
+ class ModelHook:
60
+ r"""
61
+ A hook that contains callbacks to be executed just before and after the forward method of a model.
62
+ """
63
+
64
+ _is_stateful = False
65
+
66
+ def __init__(self):
67
+ self.fn_ref: "HookFunctionReference" = None
68
+
69
+ def initialize_hook(self, module: torch.nn.Module) -> torch.nn.Module:
70
+ r"""
71
+ Hook that is executed when a model is initialized.
72
+
73
+ Args:
74
+ module (`torch.nn.Module`):
75
+ The module attached to this hook.
76
+ """
77
+ return module
78
+
79
+ def deinitalize_hook(self, module: torch.nn.Module) -> torch.nn.Module:
80
+ r"""
81
+ Hook that is executed when a model is deinitialized.
82
+
83
+ Args:
84
+ module (`torch.nn.Module`):
85
+ The module attached to this hook.
86
+ """
87
+ return module
88
+
89
+ def pre_forward(self, module: torch.nn.Module, *args, **kwargs) -> Tuple[Tuple[Any], Dict[str, Any]]:
90
+ r"""
91
+ Hook that is executed just before the forward method of the model.
92
+
93
+ Args:
94
+ module (`torch.nn.Module`):
95
+ The module whose forward pass will be executed just after this event.
96
+ args (`Tuple[Any]`):
97
+ The positional arguments passed to the module.
98
+ kwargs (`Dict[Str, Any]`):
99
+ The keyword arguments passed to the module.
100
+ Returns:
101
+ `Tuple[Tuple[Any], Dict[Str, Any]]`:
102
+ A tuple with the treated `args` and `kwargs`.
103
+ """
104
+ return args, kwargs
105
+
106
+ def post_forward(self, module: torch.nn.Module, output: Any) -> Any:
107
+ r"""
108
+ Hook that is executed just after the forward method of the model.
109
+
110
+ Args:
111
+ module (`torch.nn.Module`):
112
+ The module whose forward pass been executed just before this event.
113
+ output (`Any`):
114
+ The output of the module.
115
+ Returns:
116
+ `Any`: The processed `output`.
117
+ """
118
+ return output
119
+
120
+ def detach_hook(self, module: torch.nn.Module) -> torch.nn.Module:
121
+ r"""
122
+ Hook that is executed when the hook is detached from a module.
123
+
124
+ Args:
125
+ module (`torch.nn.Module`):
126
+ The module detached from this hook.
127
+ """
128
+ return module
129
+
130
+ def reset_state(self, module: torch.nn.Module):
131
+ if self._is_stateful:
132
+ raise NotImplementedError("This hook is stateful and needs to implement the `reset_state` method.")
133
+ return module
134
+
135
+ def _set_context(self, module: torch.nn.Module, name: str) -> None:
136
+ # Iterate over all attributes of the hook to see if any of them have the type `StateManager`. If so, call `set_context` on them.
137
+ for attr_name in dir(self):
138
+ attr = getattr(self, attr_name)
139
+ if isinstance(attr, StateManager):
140
+ attr.set_context(name)
141
+ return module
142
+
143
+
144
+ class HookFunctionReference:
145
+ def __init__(self) -> None:
146
+ """A container class that maintains mutable references to forward pass functions in a hook chain.
147
+
148
+ Its mutable nature allows the hook system to modify the execution chain dynamically without rebuilding the
149
+ entire forward pass structure.
150
+
151
+ Attributes:
152
+ pre_forward: A callable that processes inputs before the main forward pass.
153
+ post_forward: A callable that processes outputs after the main forward pass.
154
+ forward: The current forward function in the hook chain.
155
+ original_forward: The original forward function, stored when a hook provides a custom new_forward.
156
+
157
+ The class enables hook removal by allowing updates to the forward chain through reference modification rather
158
+ than requiring reconstruction of the entire chain. When a hook is removed, only the relevant references need to
159
+ be updated, preserving the execution order of the remaining hooks.
160
+ """
161
+ self.pre_forward = None
162
+ self.post_forward = None
163
+ self.forward = None
164
+ self.original_forward = None
165
+
166
+
167
+ class HookRegistry:
168
+ def __init__(self, module_ref: torch.nn.Module) -> None:
169
+ super().__init__()
170
+
171
+ self.hooks: Dict[str, ModelHook] = {}
172
+
173
+ self._module_ref = module_ref
174
+ self._hook_order = []
175
+ self._fn_refs = []
176
+
177
+ def register_hook(self, hook: ModelHook, name: str) -> None:
178
+ if name in self.hooks.keys():
179
+ raise ValueError(
180
+ f"Hook with name {name} already exists in the registry. Please use a different name or "
181
+ f"first remove the existing hook and then add a new one."
182
+ )
183
+
184
+ self._module_ref = hook.initialize_hook(self._module_ref)
185
+
186
+ def create_new_forward(function_reference: HookFunctionReference):
187
+ def new_forward(module, *args, **kwargs):
188
+ args, kwargs = function_reference.pre_forward(module, *args, **kwargs)
189
+ output = function_reference.forward(*args, **kwargs)
190
+ return function_reference.post_forward(module, output)
191
+
192
+ return new_forward
193
+
194
+ forward = self._module_ref.forward
195
+
196
+ fn_ref = HookFunctionReference()
197
+ fn_ref.pre_forward = hook.pre_forward
198
+ fn_ref.post_forward = hook.post_forward
199
+ fn_ref.forward = forward
200
+
201
+ if hasattr(hook, "new_forward"):
202
+ fn_ref.original_forward = forward
203
+ fn_ref.forward = functools.update_wrapper(
204
+ functools.partial(hook.new_forward, self._module_ref), hook.new_forward
205
+ )
206
+
207
+ rewritten_forward = create_new_forward(fn_ref)
208
+ self._module_ref.forward = functools.update_wrapper(
209
+ functools.partial(rewritten_forward, self._module_ref), rewritten_forward
210
+ )
211
+
212
+ hook.fn_ref = fn_ref
213
+ self.hooks[name] = hook
214
+ self._hook_order.append(name)
215
+ self._fn_refs.append(fn_ref)
216
+
217
+ def get_hook(self, name: str) -> Optional[ModelHook]:
218
+ return self.hooks.get(name, None)
219
+
220
+ def remove_hook(self, name: str, recurse: bool = True) -> None:
221
+ if name in self.hooks.keys():
222
+ num_hooks = len(self._hook_order)
223
+ hook = self.hooks[name]
224
+ index = self._hook_order.index(name)
225
+ fn_ref = self._fn_refs[index]
226
+
227
+ old_forward = fn_ref.forward
228
+ if fn_ref.original_forward is not None:
229
+ old_forward = fn_ref.original_forward
230
+
231
+ if index == num_hooks - 1:
232
+ self._module_ref.forward = old_forward
233
+ else:
234
+ self._fn_refs[index + 1].forward = old_forward
235
+
236
+ self._module_ref = hook.deinitalize_hook(self._module_ref)
237
+ del self.hooks[name]
238
+ self._hook_order.pop(index)
239
+ self._fn_refs.pop(index)
240
+
241
+ if recurse:
242
+ for module_name, module in self._module_ref.named_modules():
243
+ if module_name == "":
244
+ continue
245
+ if hasattr(module, "_diffusers_hook"):
246
+ module._diffusers_hook.remove_hook(name, recurse=False)
247
+
248
+ def reset_stateful_hooks(self, recurse: bool = True) -> None:
249
+ for hook_name in reversed(self._hook_order):
250
+ hook = self.hooks[hook_name]
251
+ if hook._is_stateful:
252
+ hook.reset_state(self._module_ref)
253
+
254
+ if recurse:
255
+ for module_name, module in unwrap_module(self._module_ref).named_modules():
256
+ if module_name == "":
257
+ continue
258
+ module = unwrap_module(module)
259
+ if hasattr(module, "_diffusers_hook"):
260
+ module._diffusers_hook.reset_stateful_hooks(recurse=False)
261
+
262
+ @classmethod
263
+ def check_if_exists_or_initialize(cls, module: torch.nn.Module) -> "HookRegistry":
264
+ if not hasattr(module, "_diffusers_hook"):
265
+ module._diffusers_hook = cls(module)
266
+ return module._diffusers_hook
267
+
268
+ def _set_context(self, name: Optional[str] = None) -> None:
269
+ for hook_name in reversed(self._hook_order):
270
+ hook = self.hooks[hook_name]
271
+ if hook._is_stateful:
272
+ hook._set_context(self._module_ref, name)
273
+
274
+ for module_name, module in unwrap_module(self._module_ref).named_modules():
275
+ if module_name == "":
276
+ continue
277
+ module = unwrap_module(module)
278
+ if hasattr(module, "_diffusers_hook"):
279
+ module._diffusers_hook._set_context(name)
280
+
281
+ def __repr__(self) -> str:
282
+ registry_repr = ""
283
+ for i, hook_name in enumerate(self._hook_order):
284
+ if self.hooks[hook_name].__class__.__repr__ is not object.__repr__:
285
+ hook_repr = self.hooks[hook_name].__repr__()
286
+ else:
287
+ hook_repr = self.hooks[hook_name].__class__.__name__
288
+ registry_repr += f" ({i}) {hook_name} - {hook_repr}"
289
+ if i < len(self._hook_order) - 1:
290
+ registry_repr += "\n"
291
+ return f"HookRegistry(\n{registry_repr}\n)"
vendor/diffusers/hooks/layer_skip.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from dataclasses import asdict, dataclass
17
+ from typing import Callable, List, Optional
18
+
19
+ import torch
20
+
21
+ from ..utils import get_logger
22
+ from ..utils.torch_utils import unwrap_module
23
+ from ._common import (
24
+ _ALL_TRANSFORMER_BLOCK_IDENTIFIERS,
25
+ _ATTENTION_CLASSES,
26
+ _FEEDFORWARD_CLASSES,
27
+ _get_submodule_from_fqn,
28
+ )
29
+ from ._helpers import AttentionProcessorRegistry, TransformerBlockRegistry
30
+ from .hooks import HookRegistry, ModelHook
31
+
32
+
33
+ logger = get_logger(__name__) # pylint: disable=invalid-name
34
+
35
+ _LAYER_SKIP_HOOK = "layer_skip_hook"
36
+
37
+
38
+ # Aryan/YiYi TODO: we need to make guider class a config mixin so I think this is not needed
39
+ # either remove or make it serializable
40
+ @dataclass
41
+ class LayerSkipConfig:
42
+ r"""
43
+ Configuration for skipping internal transformer blocks when executing a transformer model.
44
+
45
+ Args:
46
+ indices (`List[int]`):
47
+ The indices of the layer to skip. This is typically the first layer in the transformer block.
48
+ fqn (`str`, defaults to `"auto"`):
49
+ The fully qualified name identifying the stack of transformer blocks. Typically, this is
50
+ `transformer_blocks`, `single_transformer_blocks`, `blocks`, `layers`, or `temporal_transformer_blocks`.
51
+ For automatic detection, set this to `"auto"`. "auto" only works on DiT models. For UNet models, you must
52
+ provide the correct fqn.
53
+ skip_attention (`bool`, defaults to `True`):
54
+ Whether to skip attention blocks.
55
+ skip_ff (`bool`, defaults to `True`):
56
+ Whether to skip feed-forward blocks.
57
+ skip_attention_scores (`bool`, defaults to `False`):
58
+ Whether to skip attention score computation in the attention blocks. This is equivalent to using `value`
59
+ projections as the output of scaled dot product attention.
60
+ dropout (`float`, defaults to `1.0`):
61
+ The dropout probability for dropping the outputs of the skipped layers. By default, this is set to `1.0`,
62
+ meaning that the outputs of the skipped layers are completely ignored. If set to `0.0`, the outputs of the
63
+ skipped layers are fully retained, which is equivalent to not skipping any layers.
64
+ """
65
+
66
+ indices: List[int]
67
+ fqn: str = "auto"
68
+ skip_attention: bool = True
69
+ skip_attention_scores: bool = False
70
+ skip_ff: bool = True
71
+ dropout: float = 1.0
72
+
73
+ def __post_init__(self):
74
+ if not (0 <= self.dropout <= 1):
75
+ raise ValueError(f"Expected `dropout` to be between 0.0 and 1.0, but got {self.dropout}.")
76
+ if not math.isclose(self.dropout, 1.0) and self.skip_attention_scores:
77
+ raise ValueError(
78
+ "Cannot set `skip_attention_scores` to True when `dropout` is not 1.0. Please set `dropout` to 1.0."
79
+ )
80
+
81
+ def to_dict(self):
82
+ return asdict(self)
83
+
84
+ @staticmethod
85
+ def from_dict(data: dict) -> "LayerSkipConfig":
86
+ return LayerSkipConfig(**data)
87
+
88
+
89
+ class AttentionScoreSkipFunctionMode(torch.overrides.TorchFunctionMode):
90
+ def __torch_function__(self, func, types, args=(), kwargs=None):
91
+ if kwargs is None:
92
+ kwargs = {}
93
+ if func is torch.nn.functional.scaled_dot_product_attention:
94
+ query = kwargs.get("query", None)
95
+ key = kwargs.get("key", None)
96
+ value = kwargs.get("value", None)
97
+ query = query if query is not None else args[0]
98
+ key = key if key is not None else args[1]
99
+ value = value if value is not None else args[2]
100
+ # If the Q sequence length does not match KV sequence length, methods like
101
+ # Perturbed Attention Guidance cannot be used (because the caller expects
102
+ # the same sequence length as Q, but if we return V here, it will not match).
103
+ # When Q.shape[2] != V.shape[2], PAG will essentially not be applied and
104
+ # the overall effect would that be of normal CFG with a scale of (guidance_scale + perturbed_guidance_scale).
105
+ if query.shape[2] == value.shape[2]:
106
+ return value
107
+ return func(*args, **kwargs)
108
+
109
+
110
+ class AttentionProcessorSkipHook(ModelHook):
111
+ def __init__(self, skip_processor_output_fn: Callable, skip_attention_scores: bool = False, dropout: float = 1.0):
112
+ self.skip_processor_output_fn = skip_processor_output_fn
113
+ self.skip_attention_scores = skip_attention_scores
114
+ self.dropout = dropout
115
+
116
+ def new_forward(self, module: torch.nn.Module, *args, **kwargs):
117
+ if self.skip_attention_scores:
118
+ if not math.isclose(self.dropout, 1.0):
119
+ raise ValueError(
120
+ "Cannot set `skip_attention_scores` to True when `dropout` is not 1.0. Please set `dropout` to 1.0."
121
+ )
122
+ with AttentionScoreSkipFunctionMode():
123
+ output = self.fn_ref.original_forward(*args, **kwargs)
124
+ else:
125
+ if math.isclose(self.dropout, 1.0):
126
+ output = self.skip_processor_output_fn(module, *args, **kwargs)
127
+ else:
128
+ output = self.fn_ref.original_forward(*args, **kwargs)
129
+ output = torch.nn.functional.dropout(output, p=self.dropout)
130
+ return output
131
+
132
+
133
+ class FeedForwardSkipHook(ModelHook):
134
+ def __init__(self, dropout: float):
135
+ super().__init__()
136
+ self.dropout = dropout
137
+
138
+ def new_forward(self, module: torch.nn.Module, *args, **kwargs):
139
+ if math.isclose(self.dropout, 1.0):
140
+ output = kwargs.get("hidden_states", None)
141
+ if output is None:
142
+ output = kwargs.get("x", None)
143
+ if output is None and len(args) > 0:
144
+ output = args[0]
145
+ else:
146
+ output = self.fn_ref.original_forward(*args, **kwargs)
147
+ output = torch.nn.functional.dropout(output, p=self.dropout)
148
+ return output
149
+
150
+
151
+ class TransformerBlockSkipHook(ModelHook):
152
+ def __init__(self, dropout: float):
153
+ super().__init__()
154
+ self.dropout = dropout
155
+
156
+ def initialize_hook(self, module):
157
+ self._metadata = TransformerBlockRegistry.get(unwrap_module(module).__class__)
158
+ return module
159
+
160
+ def new_forward(self, module: torch.nn.Module, *args, **kwargs):
161
+ if math.isclose(self.dropout, 1.0):
162
+ original_hidden_states = self._metadata._get_parameter_from_args_kwargs("hidden_states", args, kwargs)
163
+ if self._metadata.return_encoder_hidden_states_index is None:
164
+ output = original_hidden_states
165
+ else:
166
+ original_encoder_hidden_states = self._metadata._get_parameter_from_args_kwargs(
167
+ "encoder_hidden_states", args, kwargs
168
+ )
169
+ output = (original_hidden_states, original_encoder_hidden_states)
170
+ else:
171
+ output = self.fn_ref.original_forward(*args, **kwargs)
172
+ output = torch.nn.functional.dropout(output, p=self.dropout)
173
+ return output
174
+
175
+
176
+ def apply_layer_skip(module: torch.nn.Module, config: LayerSkipConfig) -> None:
177
+ r"""
178
+ Apply layer skipping to internal layers of a transformer.
179
+
180
+ Args:
181
+ module (`torch.nn.Module`):
182
+ The transformer model to which the layer skip hook should be applied.
183
+ config (`LayerSkipConfig`):
184
+ The configuration for the layer skip hook.
185
+
186
+ Example:
187
+
188
+ ```python
189
+ >>> from diffusers import apply_layer_skip_hook, CogVideoXTransformer3DModel, LayerSkipConfig
190
+
191
+ >>> transformer = CogVideoXTransformer3DModel.from_pretrained("THUDM/CogVideoX-5b", torch_dtype=torch.bfloat16)
192
+ >>> config = LayerSkipConfig(layer_index=[10, 20], fqn="transformer_blocks")
193
+ >>> apply_layer_skip_hook(transformer, config)
194
+ ```
195
+ """
196
+ _apply_layer_skip_hook(module, config)
197
+
198
+
199
+ def _apply_layer_skip_hook(module: torch.nn.Module, config: LayerSkipConfig, name: Optional[str] = None) -> None:
200
+ name = name or _LAYER_SKIP_HOOK
201
+
202
+ if config.skip_attention and config.skip_attention_scores:
203
+ raise ValueError("Cannot set both `skip_attention` and `skip_attention_scores` to True. Please choose one.")
204
+ if not math.isclose(config.dropout, 1.0) and config.skip_attention_scores:
205
+ raise ValueError(
206
+ "Cannot set `skip_attention_scores` to True when `dropout` is not 1.0. Please set `dropout` to 1.0."
207
+ )
208
+
209
+ if config.fqn == "auto":
210
+ for identifier in _ALL_TRANSFORMER_BLOCK_IDENTIFIERS:
211
+ if hasattr(module, identifier):
212
+ config.fqn = identifier
213
+ break
214
+ else:
215
+ raise ValueError(
216
+ "Could not find a suitable identifier for the transformer blocks automatically. Please provide a valid "
217
+ "`fqn` (fully qualified name) that identifies a stack of transformer blocks."
218
+ )
219
+
220
+ transformer_blocks = _get_submodule_from_fqn(module, config.fqn)
221
+ if transformer_blocks is None or not isinstance(transformer_blocks, torch.nn.ModuleList):
222
+ raise ValueError(
223
+ f"Could not find {config.fqn} in the provided module, or configured `fqn` (fully qualified name) does not identify "
224
+ f"a `torch.nn.ModuleList`. Please provide a valid `fqn` that identifies a stack of transformer blocks."
225
+ )
226
+ if len(config.indices) == 0:
227
+ raise ValueError("Layer index list is empty. Please provide a non-empty list of layer indices to skip.")
228
+
229
+ blocks_found = False
230
+ for i, block in enumerate(transformer_blocks):
231
+ if i not in config.indices:
232
+ continue
233
+
234
+ blocks_found = True
235
+
236
+ if config.skip_attention and config.skip_ff:
237
+ logger.debug(f"Applying TransformerBlockSkipHook to '{config.fqn}.{i}'")
238
+ registry = HookRegistry.check_if_exists_or_initialize(block)
239
+ hook = TransformerBlockSkipHook(config.dropout)
240
+ registry.register_hook(hook, name)
241
+
242
+ elif config.skip_attention or config.skip_attention_scores:
243
+ for submodule_name, submodule in block.named_modules():
244
+ if isinstance(submodule, _ATTENTION_CLASSES) and not submodule.is_cross_attention:
245
+ logger.debug(f"Applying AttentionProcessorSkipHook to '{config.fqn}.{i}.{submodule_name}'")
246
+ output_fn = AttentionProcessorRegistry.get(submodule.processor.__class__).skip_processor_output_fn
247
+ registry = HookRegistry.check_if_exists_or_initialize(submodule)
248
+ hook = AttentionProcessorSkipHook(output_fn, config.skip_attention_scores, config.dropout)
249
+ registry.register_hook(hook, name)
250
+
251
+ if config.skip_ff:
252
+ for submodule_name, submodule in block.named_modules():
253
+ if isinstance(submodule, _FEEDFORWARD_CLASSES):
254
+ logger.debug(f"Applying FeedForwardSkipHook to '{config.fqn}.{i}.{submodule_name}'")
255
+ registry = HookRegistry.check_if_exists_or_initialize(submodule)
256
+ hook = FeedForwardSkipHook(config.dropout)
257
+ registry.register_hook(hook, name)
258
+
259
+ if not blocks_found:
260
+ raise ValueError(
261
+ f"Could not find any transformer blocks matching the provided indices {config.indices} and "
262
+ f"fully qualified name '{config.fqn}'. Please check the indices and fqn for correctness."
263
+ )
vendor/diffusers/hooks/layerwise_casting.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import re
16
+ from typing import Optional, Tuple, Type, Union
17
+
18
+ import torch
19
+
20
+ from ..utils import get_logger, is_peft_available, is_peft_version
21
+ from ._common import _GO_LC_SUPPORTED_PYTORCH_LAYERS
22
+ from .hooks import HookRegistry, ModelHook
23
+
24
+
25
+ logger = get_logger(__name__) # pylint: disable=invalid-name
26
+
27
+
28
+ # fmt: off
29
+ _LAYERWISE_CASTING_HOOK = "layerwise_casting"
30
+ _PEFT_AUTOCAST_DISABLE_HOOK = "peft_autocast_disable"
31
+ DEFAULT_SKIP_MODULES_PATTERN = ("pos_embed", "patch_embed", "norm", "^proj_in$", "^proj_out$")
32
+ # fmt: on
33
+
34
+ _SHOULD_DISABLE_PEFT_INPUT_AUTOCAST = is_peft_available() and is_peft_version(">", "0.14.0")
35
+ if _SHOULD_DISABLE_PEFT_INPUT_AUTOCAST:
36
+ from peft.helpers import disable_input_dtype_casting
37
+ from peft.tuners.tuners_utils import BaseTunerLayer
38
+
39
+
40
+ class LayerwiseCastingHook(ModelHook):
41
+ r"""
42
+ A hook that casts the weights of a module to a high precision dtype for computation, and to a low precision dtype
43
+ for storage. This process may lead to quality loss in the output, but can significantly reduce the memory
44
+ footprint.
45
+ """
46
+
47
+ _is_stateful = False
48
+
49
+ def __init__(self, storage_dtype: torch.dtype, compute_dtype: torch.dtype, non_blocking: bool) -> None:
50
+ self.storage_dtype = storage_dtype
51
+ self.compute_dtype = compute_dtype
52
+ self.non_blocking = non_blocking
53
+
54
+ def initialize_hook(self, module: torch.nn.Module):
55
+ module.to(dtype=self.storage_dtype, non_blocking=self.non_blocking)
56
+ return module
57
+
58
+ def deinitalize_hook(self, module: torch.nn.Module):
59
+ raise NotImplementedError(
60
+ "LayerwiseCastingHook does not support deinitialization. A model once enabled with layerwise casting will "
61
+ "have casted its weights to a lower precision dtype for storage. Casting this back to the original dtype "
62
+ "will lead to precision loss, which might have an impact on the model's generation quality. The model should "
63
+ "be re-initialized and loaded in the original dtype."
64
+ )
65
+
66
+ def pre_forward(self, module: torch.nn.Module, *args, **kwargs):
67
+ module.to(dtype=self.compute_dtype, non_blocking=self.non_blocking)
68
+ return args, kwargs
69
+
70
+ def post_forward(self, module: torch.nn.Module, output):
71
+ module.to(dtype=self.storage_dtype, non_blocking=self.non_blocking)
72
+ return output
73
+
74
+
75
+ class PeftInputAutocastDisableHook(ModelHook):
76
+ r"""
77
+ A hook that disables the casting of inputs to the module weight dtype during the forward pass. By default, PEFT
78
+ casts the inputs to the weight dtype of the module, which can lead to precision loss.
79
+
80
+ The reasons for needing this are:
81
+ - If we don't add PEFT layers' weight names to `skip_modules_pattern` when applying layerwise casting, the
82
+ inputs will be casted to the, possibly lower precision, storage dtype. Reference:
83
+ https://github.com/huggingface/peft/blob/0facdebf6208139cbd8f3586875acb378813dd97/src/peft/tuners/lora/layer.py#L706
84
+ - We can, on our end, use something like accelerate's `send_to_device` but for dtypes. This way, we can ensure
85
+ that the inputs are casted to the computation dtype correctly always. However, there are two goals we are
86
+ hoping to achieve:
87
+ 1. Making forward implementations independent of device/dtype casting operations as much as possible.
88
+ 2. Performing inference without losing information from casting to different precisions. With the current
89
+ PEFT implementation (as linked in the reference above), and assuming running layerwise casting inference
90
+ with storage_dtype=torch.float8_e4m3fn and compute_dtype=torch.bfloat16, inputs are cast to
91
+ torch.float8_e4m3fn in the lora layer. We will then upcast back to torch.bfloat16 when we continue the
92
+ forward pass in PEFT linear forward or Diffusers layer forward, with a `send_to_dtype` operation from
93
+ LayerwiseCastingHook. This will be a lossy operation and result in poorer generation quality.
94
+ """
95
+
96
+ def new_forward(self, module: torch.nn.Module, *args, **kwargs):
97
+ with disable_input_dtype_casting(module):
98
+ return self.fn_ref.original_forward(*args, **kwargs)
99
+
100
+
101
+ def apply_layerwise_casting(
102
+ module: torch.nn.Module,
103
+ storage_dtype: torch.dtype,
104
+ compute_dtype: torch.dtype,
105
+ skip_modules_pattern: Union[str, Tuple[str, ...]] = "auto",
106
+ skip_modules_classes: Optional[Tuple[Type[torch.nn.Module], ...]] = None,
107
+ non_blocking: bool = False,
108
+ ) -> None:
109
+ r"""
110
+ Applies layerwise casting to a given module. The module expected here is a Diffusers ModelMixin but it can be any
111
+ nn.Module using diffusers layers or pytorch primitives.
112
+
113
+ Example:
114
+
115
+ ```python
116
+ >>> import torch
117
+ >>> from diffusers import CogVideoXTransformer3DModel
118
+
119
+ >>> transformer = CogVideoXTransformer3DModel.from_pretrained(
120
+ ... model_id, subfolder="transformer", torch_dtype=torch.bfloat16
121
+ ... )
122
+
123
+ >>> apply_layerwise_casting(
124
+ ... transformer,
125
+ ... storage_dtype=torch.float8_e4m3fn,
126
+ ... compute_dtype=torch.bfloat16,
127
+ ... skip_modules_pattern=["patch_embed", "norm", "proj_out"],
128
+ ... non_blocking=True,
129
+ ... )
130
+ ```
131
+
132
+ Args:
133
+ module (`torch.nn.Module`):
134
+ The module whose leaf modules will be cast to a high precision dtype for computation, and to a low
135
+ precision dtype for storage.
136
+ storage_dtype (`torch.dtype`):
137
+ The dtype to cast the module to before/after the forward pass for storage.
138
+ compute_dtype (`torch.dtype`):
139
+ The dtype to cast the module to during the forward pass for computation.
140
+ skip_modules_pattern (`Tuple[str, ...]`, defaults to `"auto"`):
141
+ A list of patterns to match the names of the modules to skip during the layerwise casting process. If set
142
+ to `"auto"`, the default patterns are used. If set to `None`, no modules are skipped. If set to `None`
143
+ alongside `skip_modules_classes` being `None`, the layerwise casting is applied directly to the module
144
+ instead of its internal submodules.
145
+ skip_modules_classes (`Tuple[Type[torch.nn.Module], ...]`, defaults to `None`):
146
+ A list of module classes to skip during the layerwise casting process.
147
+ non_blocking (`bool`, defaults to `False`):
148
+ If `True`, the weight casting operations are non-blocking.
149
+ """
150
+ if skip_modules_pattern == "auto":
151
+ skip_modules_pattern = DEFAULT_SKIP_MODULES_PATTERN
152
+
153
+ if skip_modules_classes is None and skip_modules_pattern is None:
154
+ apply_layerwise_casting_hook(module, storage_dtype, compute_dtype, non_blocking)
155
+ return
156
+
157
+ _apply_layerwise_casting(
158
+ module,
159
+ storage_dtype,
160
+ compute_dtype,
161
+ skip_modules_pattern,
162
+ skip_modules_classes,
163
+ non_blocking,
164
+ )
165
+ _disable_peft_input_autocast(module)
166
+
167
+
168
+ def _apply_layerwise_casting(
169
+ module: torch.nn.Module,
170
+ storage_dtype: torch.dtype,
171
+ compute_dtype: torch.dtype,
172
+ skip_modules_pattern: Optional[Tuple[str, ...]] = None,
173
+ skip_modules_classes: Optional[Tuple[Type[torch.nn.Module], ...]] = None,
174
+ non_blocking: bool = False,
175
+ _prefix: str = "",
176
+ ) -> None:
177
+ should_skip = (skip_modules_classes is not None and isinstance(module, skip_modules_classes)) or (
178
+ skip_modules_pattern is not None and any(re.search(pattern, _prefix) for pattern in skip_modules_pattern)
179
+ )
180
+ if should_skip:
181
+ logger.debug(f'Skipping layerwise casting for layer "{_prefix}"')
182
+ return
183
+
184
+ if isinstance(module, _GO_LC_SUPPORTED_PYTORCH_LAYERS):
185
+ logger.debug(f'Applying layerwise casting to layer "{_prefix}"')
186
+ apply_layerwise_casting_hook(module, storage_dtype, compute_dtype, non_blocking)
187
+ return
188
+
189
+ for name, submodule in module.named_children():
190
+ layer_name = f"{_prefix}.{name}" if _prefix else name
191
+ _apply_layerwise_casting(
192
+ submodule,
193
+ storage_dtype,
194
+ compute_dtype,
195
+ skip_modules_pattern,
196
+ skip_modules_classes,
197
+ non_blocking,
198
+ _prefix=layer_name,
199
+ )
200
+
201
+
202
+ def apply_layerwise_casting_hook(
203
+ module: torch.nn.Module, storage_dtype: torch.dtype, compute_dtype: torch.dtype, non_blocking: bool
204
+ ) -> None:
205
+ r"""
206
+ Applies a `LayerwiseCastingHook` to a given module.
207
+
208
+ Args:
209
+ module (`torch.nn.Module`):
210
+ The module to attach the hook to.
211
+ storage_dtype (`torch.dtype`):
212
+ The dtype to cast the module to before the forward pass.
213
+ compute_dtype (`torch.dtype`):
214
+ The dtype to cast the module to during the forward pass.
215
+ non_blocking (`bool`):
216
+ If `True`, the weight casting operations are non-blocking.
217
+ """
218
+ registry = HookRegistry.check_if_exists_or_initialize(module)
219
+ hook = LayerwiseCastingHook(storage_dtype, compute_dtype, non_blocking)
220
+ registry.register_hook(hook, _LAYERWISE_CASTING_HOOK)
221
+
222
+
223
+ def _is_layerwise_casting_active(module: torch.nn.Module) -> bool:
224
+ for submodule in module.modules():
225
+ if (
226
+ hasattr(submodule, "_diffusers_hook")
227
+ and submodule._diffusers_hook.get_hook(_LAYERWISE_CASTING_HOOK) is not None
228
+ ):
229
+ return True
230
+ return False
231
+
232
+
233
+ def _disable_peft_input_autocast(module: torch.nn.Module) -> None:
234
+ if not _SHOULD_DISABLE_PEFT_INPUT_AUTOCAST:
235
+ return
236
+ for submodule in module.modules():
237
+ if isinstance(submodule, BaseTunerLayer) and _is_layerwise_casting_active(submodule):
238
+ registry = HookRegistry.check_if_exists_or_initialize(submodule)
239
+ hook = PeftInputAutocastDisableHook()
240
+ registry.register_hook(hook, _PEFT_AUTOCAST_DISABLE_HOOK)
vendor/diffusers/hooks/pyramid_attention_broadcast.py ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import re
16
+ from dataclasses import dataclass
17
+ from typing import Any, Callable, Optional, Tuple, Union
18
+
19
+ import torch
20
+
21
+ from ..models.attention import AttentionModuleMixin
22
+ from ..models.attention_processor import Attention, MochiAttention
23
+ from ..utils import logging
24
+ from ._common import (
25
+ _ATTENTION_CLASSES,
26
+ _CROSS_TRANSFORMER_BLOCK_IDENTIFIERS,
27
+ _SPATIAL_TRANSFORMER_BLOCK_IDENTIFIERS,
28
+ _TEMPORAL_TRANSFORMER_BLOCK_IDENTIFIERS,
29
+ )
30
+ from .hooks import HookRegistry, ModelHook
31
+
32
+
33
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
34
+
35
+
36
+ _PYRAMID_ATTENTION_BROADCAST_HOOK = "pyramid_attention_broadcast"
37
+
38
+
39
+ @dataclass
40
+ class PyramidAttentionBroadcastConfig:
41
+ r"""
42
+ Configuration for Pyramid Attention Broadcast.
43
+
44
+ Args:
45
+ spatial_attention_block_skip_range (`int`, *optional*, defaults to `None`):
46
+ The number of times a specific spatial attention broadcast is skipped before computing the attention states
47
+ to re-use. If this is set to the value `N`, the attention computation will be skipped `N - 1` times (i.e.,
48
+ old attention states will be reused) before computing the new attention states again.
49
+ temporal_attention_block_skip_range (`int`, *optional*, defaults to `None`):
50
+ The number of times a specific temporal attention broadcast is skipped before computing the attention
51
+ states to re-use. If this is set to the value `N`, the attention computation will be skipped `N - 1` times
52
+ (i.e., old attention states will be reused) before computing the new attention states again.
53
+ cross_attention_block_skip_range (`int`, *optional*, defaults to `None`):
54
+ The number of times a specific cross-attention broadcast is skipped before computing the attention states
55
+ to re-use. If this is set to the value `N`, the attention computation will be skipped `N - 1` times (i.e.,
56
+ old attention states will be reused) before computing the new attention states again.
57
+ spatial_attention_timestep_skip_range (`Tuple[int, int]`, defaults to `(100, 800)`):
58
+ The range of timesteps to skip in the spatial attention layer. The attention computations will be
59
+ conditionally skipped if the current timestep is within the specified range.
60
+ temporal_attention_timestep_skip_range (`Tuple[int, int]`, defaults to `(100, 800)`):
61
+ The range of timesteps to skip in the temporal attention layer. The attention computations will be
62
+ conditionally skipped if the current timestep is within the specified range.
63
+ cross_attention_timestep_skip_range (`Tuple[int, int]`, defaults to `(100, 800)`):
64
+ The range of timesteps to skip in the cross-attention layer. The attention computations will be
65
+ conditionally skipped if the current timestep is within the specified range.
66
+ spatial_attention_block_identifiers (`Tuple[str, ...]`):
67
+ The identifiers to match against the layer names to determine if the layer is a spatial attention layer.
68
+ temporal_attention_block_identifiers (`Tuple[str, ...]`):
69
+ The identifiers to match against the layer names to determine if the layer is a temporal attention layer.
70
+ cross_attention_block_identifiers (`Tuple[str, ...]`):
71
+ The identifiers to match against the layer names to determine if the layer is a cross-attention layer.
72
+ """
73
+
74
+ spatial_attention_block_skip_range: Optional[int] = None
75
+ temporal_attention_block_skip_range: Optional[int] = None
76
+ cross_attention_block_skip_range: Optional[int] = None
77
+
78
+ spatial_attention_timestep_skip_range: Tuple[int, int] = (100, 800)
79
+ temporal_attention_timestep_skip_range: Tuple[int, int] = (100, 800)
80
+ cross_attention_timestep_skip_range: Tuple[int, int] = (100, 800)
81
+
82
+ spatial_attention_block_identifiers: Tuple[str, ...] = _SPATIAL_TRANSFORMER_BLOCK_IDENTIFIERS
83
+ temporal_attention_block_identifiers: Tuple[str, ...] = _TEMPORAL_TRANSFORMER_BLOCK_IDENTIFIERS
84
+ cross_attention_block_identifiers: Tuple[str, ...] = _CROSS_TRANSFORMER_BLOCK_IDENTIFIERS
85
+
86
+ current_timestep_callback: Callable[[], int] = None
87
+
88
+ # TODO(aryan): add PAB for MLP layers (very limited speedup from testing with original codebase
89
+ # so not added for now)
90
+
91
+ def __repr__(self) -> str:
92
+ return (
93
+ f"PyramidAttentionBroadcastConfig(\n"
94
+ f" spatial_attention_block_skip_range={self.spatial_attention_block_skip_range},\n"
95
+ f" temporal_attention_block_skip_range={self.temporal_attention_block_skip_range},\n"
96
+ f" cross_attention_block_skip_range={self.cross_attention_block_skip_range},\n"
97
+ f" spatial_attention_timestep_skip_range={self.spatial_attention_timestep_skip_range},\n"
98
+ f" temporal_attention_timestep_skip_range={self.temporal_attention_timestep_skip_range},\n"
99
+ f" cross_attention_timestep_skip_range={self.cross_attention_timestep_skip_range},\n"
100
+ f" spatial_attention_block_identifiers={self.spatial_attention_block_identifiers},\n"
101
+ f" temporal_attention_block_identifiers={self.temporal_attention_block_identifiers},\n"
102
+ f" cross_attention_block_identifiers={self.cross_attention_block_identifiers},\n"
103
+ f" current_timestep_callback={self.current_timestep_callback}\n"
104
+ ")"
105
+ )
106
+
107
+
108
+ class PyramidAttentionBroadcastState:
109
+ r"""
110
+ State for Pyramid Attention Broadcast.
111
+
112
+ Attributes:
113
+ iteration (`int`):
114
+ The current iteration of the Pyramid Attention Broadcast. It is necessary to ensure that `reset_state` is
115
+ called before starting a new inference forward pass for PAB to work correctly.
116
+ cache (`Any`):
117
+ The cached output from the previous forward pass. This is used to re-use the attention states when the
118
+ attention computation is skipped. It is either a tensor or a tuple of tensors, depending on the module.
119
+ """
120
+
121
+ def __init__(self) -> None:
122
+ self.iteration = 0
123
+ self.cache = None
124
+
125
+ def reset(self):
126
+ self.iteration = 0
127
+ self.cache = None
128
+
129
+ def __repr__(self):
130
+ cache_repr = ""
131
+ if self.cache is None:
132
+ cache_repr = "None"
133
+ else:
134
+ cache_repr = f"Tensor(shape={self.cache.shape}, dtype={self.cache.dtype})"
135
+ return f"PyramidAttentionBroadcastState(iteration={self.iteration}, cache={cache_repr})"
136
+
137
+
138
+ class PyramidAttentionBroadcastHook(ModelHook):
139
+ r"""A hook that applies Pyramid Attention Broadcast to a given module."""
140
+
141
+ _is_stateful = True
142
+
143
+ def __init__(
144
+ self, timestep_skip_range: Tuple[int, int], block_skip_range: int, current_timestep_callback: Callable[[], int]
145
+ ) -> None:
146
+ super().__init__()
147
+
148
+ self.timestep_skip_range = timestep_skip_range
149
+ self.block_skip_range = block_skip_range
150
+ self.current_timestep_callback = current_timestep_callback
151
+
152
+ def initialize_hook(self, module):
153
+ self.state = PyramidAttentionBroadcastState()
154
+ return module
155
+
156
+ def new_forward(self, module: torch.nn.Module, *args, **kwargs) -> Any:
157
+ is_within_timestep_range = (
158
+ self.timestep_skip_range[0] < self.current_timestep_callback() < self.timestep_skip_range[1]
159
+ )
160
+ should_compute_attention = (
161
+ self.state.cache is None
162
+ or self.state.iteration == 0
163
+ or not is_within_timestep_range
164
+ or self.state.iteration % self.block_skip_range == 0
165
+ )
166
+
167
+ if should_compute_attention:
168
+ output = self.fn_ref.original_forward(*args, **kwargs)
169
+ else:
170
+ output = self.state.cache
171
+
172
+ self.state.cache = output
173
+ self.state.iteration += 1
174
+ return output
175
+
176
+ def reset_state(self, module: torch.nn.Module) -> None:
177
+ self.state.reset()
178
+ return module
179
+
180
+
181
+ def apply_pyramid_attention_broadcast(module: torch.nn.Module, config: PyramidAttentionBroadcastConfig):
182
+ r"""
183
+ Apply [Pyramid Attention Broadcast](https://huggingface.co/papers/2408.12588) to a given pipeline.
184
+
185
+ PAB is an attention approximation method that leverages the similarity in attention states between timesteps to
186
+ reduce the computational cost of attention computation. The key takeaway from the paper is that the attention
187
+ similarity in the cross-attention layers between timesteps is high, followed by less similarity in the temporal and
188
+ spatial layers. This allows for the skipping of attention computation in the cross-attention layers more frequently
189
+ than in the temporal and spatial layers. Applying PAB will, therefore, speedup the inference process.
190
+
191
+ Args:
192
+ module (`torch.nn.Module`):
193
+ The module to apply Pyramid Attention Broadcast to.
194
+ config (`Optional[PyramidAttentionBroadcastConfig]`, `optional`, defaults to `None`):
195
+ The configuration to use for Pyramid Attention Broadcast.
196
+
197
+ Example:
198
+
199
+ ```python
200
+ >>> import torch
201
+ >>> from diffusers import CogVideoXPipeline, PyramidAttentionBroadcastConfig, apply_pyramid_attention_broadcast
202
+ >>> from diffusers.utils import export_to_video
203
+
204
+ >>> pipe = CogVideoXPipeline.from_pretrained("THUDM/CogVideoX-5b", torch_dtype=torch.bfloat16)
205
+ >>> pipe.to("cuda")
206
+
207
+ >>> config = PyramidAttentionBroadcastConfig(
208
+ ... spatial_attention_block_skip_range=2,
209
+ ... spatial_attention_timestep_skip_range=(100, 800),
210
+ ... current_timestep_callback=lambda: pipe.current_timestep,
211
+ ... )
212
+ >>> apply_pyramid_attention_broadcast(pipe.transformer, config)
213
+ ```
214
+ """
215
+ if config.current_timestep_callback is None:
216
+ raise ValueError(
217
+ "The `current_timestep_callback` function must be provided in the configuration to apply Pyramid Attention Broadcast."
218
+ )
219
+
220
+ if (
221
+ config.spatial_attention_block_skip_range is None
222
+ and config.temporal_attention_block_skip_range is None
223
+ and config.cross_attention_block_skip_range is None
224
+ ):
225
+ logger.warning(
226
+ "Pyramid Attention Broadcast requires one or more of `spatial_attention_block_skip_range`, `temporal_attention_block_skip_range` "
227
+ "or `cross_attention_block_skip_range` parameters to be set to an integer, not `None`. Defaulting to using `spatial_attention_block_skip_range=2`. "
228
+ "To avoid this warning, please set one of the above parameters."
229
+ )
230
+ config.spatial_attention_block_skip_range = 2
231
+
232
+ for name, submodule in module.named_modules():
233
+ if not isinstance(submodule, (*_ATTENTION_CLASSES, AttentionModuleMixin)):
234
+ # PAB has been implemented specific to Diffusers' Attention classes. However, this does not mean that PAB
235
+ # cannot be applied to this layer. For custom layers, users can extend this functionality and implement
236
+ # their own PAB logic similar to `_apply_pyramid_attention_broadcast_on_attention_class`.
237
+ continue
238
+ _apply_pyramid_attention_broadcast_on_attention_class(name, submodule, config)
239
+
240
+
241
+ def _apply_pyramid_attention_broadcast_on_attention_class(
242
+ name: str, module: Attention, config: PyramidAttentionBroadcastConfig
243
+ ) -> bool:
244
+ is_spatial_self_attention = (
245
+ any(re.search(identifier, name) is not None for identifier in config.spatial_attention_block_identifiers)
246
+ and config.spatial_attention_block_skip_range is not None
247
+ and not getattr(module, "is_cross_attention", False)
248
+ )
249
+ is_temporal_self_attention = (
250
+ any(re.search(identifier, name) is not None for identifier in config.temporal_attention_block_identifiers)
251
+ and config.temporal_attention_block_skip_range is not None
252
+ and not getattr(module, "is_cross_attention", False)
253
+ )
254
+ is_cross_attention = (
255
+ any(re.search(identifier, name) is not None for identifier in config.cross_attention_block_identifiers)
256
+ and config.cross_attention_block_skip_range is not None
257
+ and getattr(module, "is_cross_attention", False)
258
+ )
259
+
260
+ block_skip_range, timestep_skip_range, block_type = None, None, None
261
+ if is_spatial_self_attention:
262
+ block_skip_range = config.spatial_attention_block_skip_range
263
+ timestep_skip_range = config.spatial_attention_timestep_skip_range
264
+ block_type = "spatial"
265
+ elif is_temporal_self_attention:
266
+ block_skip_range = config.temporal_attention_block_skip_range
267
+ timestep_skip_range = config.temporal_attention_timestep_skip_range
268
+ block_type = "temporal"
269
+ elif is_cross_attention:
270
+ block_skip_range = config.cross_attention_block_skip_range
271
+ timestep_skip_range = config.cross_attention_timestep_skip_range
272
+ block_type = "cross"
273
+
274
+ if block_skip_range is None or timestep_skip_range is None:
275
+ logger.info(
276
+ f'Unable to apply Pyramid Attention Broadcast to the selected layer: "{name}" because it does '
277
+ f"not match any of the required criteria for spatial, temporal or cross attention layers. Note, "
278
+ f"however, that this layer may still be valid for applying PAB. Please specify the correct "
279
+ f"block identifiers in the configuration."
280
+ )
281
+ return False
282
+
283
+ logger.debug(f"Enabling Pyramid Attention Broadcast ({block_type}) in layer: {name}")
284
+ _apply_pyramid_attention_broadcast_hook(
285
+ module, timestep_skip_range, block_skip_range, config.current_timestep_callback
286
+ )
287
+ return True
288
+
289
+
290
+ def _apply_pyramid_attention_broadcast_hook(
291
+ module: Union[Attention, MochiAttention],
292
+ timestep_skip_range: Tuple[int, int],
293
+ block_skip_range: int,
294
+ current_timestep_callback: Callable[[], int],
295
+ ):
296
+ r"""
297
+ Apply [Pyramid Attention Broadcast](https://huggingface.co/papers/2408.12588) to a given torch.nn.Module.
298
+
299
+ Args:
300
+ module (`torch.nn.Module`):
301
+ The module to apply Pyramid Attention Broadcast to.
302
+ timestep_skip_range (`Tuple[int, int]`):
303
+ The range of timesteps to skip in the attention layer. The attention computations will be conditionally
304
+ skipped if the current timestep is within the specified range.
305
+ block_skip_range (`int`):
306
+ The number of times a specific attention broadcast is skipped before computing the attention states to
307
+ re-use. If this is set to the value `N`, the attention computation will be skipped `N - 1` times (i.e., old
308
+ attention states will be reused) before computing the new attention states again.
309
+ current_timestep_callback (`Callable[[], int]`):
310
+ A callback function that returns the current inference timestep.
311
+ """
312
+ registry = HookRegistry.check_if_exists_or_initialize(module)
313
+ hook = PyramidAttentionBroadcastHook(timestep_skip_range, block_skip_range, current_timestep_callback)
314
+ registry.register_hook(hook, _PYRAMID_ATTENTION_BROADCAST_HOOK)
vendor/diffusers/hooks/smoothed_energy_guidance_utils.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ import math
16
+ from dataclasses import asdict, dataclass
17
+ from typing import List, Optional
18
+
19
+ import torch
20
+ import torch.nn.functional as F
21
+
22
+ from ..utils import get_logger
23
+ from ._common import _ALL_TRANSFORMER_BLOCK_IDENTIFIERS, _ATTENTION_CLASSES, _get_submodule_from_fqn
24
+ from .hooks import HookRegistry, ModelHook
25
+
26
+
27
+ logger = get_logger(__name__) # pylint: disable=invalid-name
28
+
29
+ _SMOOTHED_ENERGY_GUIDANCE_HOOK = "smoothed_energy_guidance_hook"
30
+
31
+
32
+ @dataclass
33
+ class SmoothedEnergyGuidanceConfig:
34
+ r"""
35
+ Configuration for skipping internal transformer blocks when executing a transformer model.
36
+
37
+ Args:
38
+ indices (`List[int]`):
39
+ The indices of the layer to skip. This is typically the first layer in the transformer block.
40
+ fqn (`str`, defaults to `"auto"`):
41
+ The fully qualified name identifying the stack of transformer blocks. Typically, this is
42
+ `transformer_blocks`, `single_transformer_blocks`, `blocks`, `layers`, or `temporal_transformer_blocks`.
43
+ For automatic detection, set this to `"auto"`. "auto" only works on DiT models. For UNet models, you must
44
+ provide the correct fqn.
45
+ _query_proj_identifiers (`List[str]`, defaults to `None`):
46
+ The identifiers for the query projection layers. Typically, these are `to_q`, `query`, or `q_proj`. If
47
+ `None`, `to_q` is used by default.
48
+ """
49
+
50
+ indices: List[int]
51
+ fqn: str = "auto"
52
+ _query_proj_identifiers: List[str] = None
53
+
54
+ def to_dict(self):
55
+ return asdict(self)
56
+
57
+ @staticmethod
58
+ def from_dict(data: dict) -> "SmoothedEnergyGuidanceConfig":
59
+ return SmoothedEnergyGuidanceConfig(**data)
60
+
61
+
62
+ class SmoothedEnergyGuidanceHook(ModelHook):
63
+ def __init__(self, blur_sigma: float = 1.0, blur_threshold_inf: float = 9999.9) -> None:
64
+ super().__init__()
65
+ self.blur_sigma = blur_sigma
66
+ self.blur_threshold_inf = blur_threshold_inf
67
+
68
+ def post_forward(self, module: torch.nn.Module, output: torch.Tensor) -> torch.Tensor:
69
+ # Copied from https://github.com/SusungHong/SEG-SDXL/blob/cf8256d640d5373541cfea3b3b6caf93272cf986/pipeline_seg.py#L172C31-L172C102
70
+ kernel_size = math.ceil(6 * self.blur_sigma) + 1 - math.ceil(6 * self.blur_sigma) % 2
71
+ smoothed_output = _gaussian_blur_2d(output, kernel_size, self.blur_sigma, self.blur_threshold_inf)
72
+ return smoothed_output
73
+
74
+
75
+ def _apply_smoothed_energy_guidance_hook(
76
+ module: torch.nn.Module, config: SmoothedEnergyGuidanceConfig, blur_sigma: float, name: Optional[str] = None
77
+ ) -> None:
78
+ name = name or _SMOOTHED_ENERGY_GUIDANCE_HOOK
79
+
80
+ if config.fqn == "auto":
81
+ for identifier in _ALL_TRANSFORMER_BLOCK_IDENTIFIERS:
82
+ if hasattr(module, identifier):
83
+ config.fqn = identifier
84
+ break
85
+ else:
86
+ raise ValueError(
87
+ "Could not find a suitable identifier for the transformer blocks automatically. Please provide a valid "
88
+ "`fqn` (fully qualified name) that identifies a stack of transformer blocks."
89
+ )
90
+
91
+ if config._query_proj_identifiers is None:
92
+ config._query_proj_identifiers = ["to_q"]
93
+
94
+ transformer_blocks = _get_submodule_from_fqn(module, config.fqn)
95
+ blocks_found = False
96
+ for i, block in enumerate(transformer_blocks):
97
+ if i not in config.indices:
98
+ continue
99
+
100
+ blocks_found = True
101
+
102
+ for submodule_name, submodule in block.named_modules():
103
+ if not isinstance(submodule, _ATTENTION_CLASSES) or submodule.is_cross_attention:
104
+ continue
105
+ for identifier in config._query_proj_identifiers:
106
+ query_proj = getattr(submodule, identifier, None)
107
+ if query_proj is None or not isinstance(query_proj, torch.nn.Linear):
108
+ continue
109
+ logger.debug(
110
+ f"Registering smoothed energy guidance hook on {config.fqn}.{i}.{submodule_name}.{identifier}"
111
+ )
112
+ registry = HookRegistry.check_if_exists_or_initialize(query_proj)
113
+ hook = SmoothedEnergyGuidanceHook(blur_sigma)
114
+ registry.register_hook(hook, name)
115
+
116
+ if not blocks_found:
117
+ raise ValueError(
118
+ f"Could not find any transformer blocks matching the provided indices {config.indices} and "
119
+ f"fully qualified name '{config.fqn}'. Please check the indices and fqn for correctness."
120
+ )
121
+
122
+
123
+ # Modified from https://github.com/SusungHong/SEG-SDXL/blob/cf8256d640d5373541cfea3b3b6caf93272cf986/pipeline_seg.py#L71
124
+ def _gaussian_blur_2d(query: torch.Tensor, kernel_size: int, sigma: float, sigma_threshold_inf: float) -> torch.Tensor:
125
+ """
126
+ This implementation assumes that the input query is for visual (image/videos) tokens to apply the 2D gaussian blur.
127
+ However, some models use joint text-visual token attention for which this may not be suitable. Additionally, this
128
+ implementation also assumes that the visual tokens come from a square image/video. In practice, despite these
129
+ assumptions, applying the 2D square gaussian blur on the query projections generates reasonable results for
130
+ Smoothed Energy Guidance.
131
+
132
+ SEG is only supported as an experimental prototype feature for now, so the implementation may be modified in the
133
+ future without warning or guarantee of reproducibility.
134
+ """
135
+ assert query.ndim == 3
136
+
137
+ is_inf = sigma > sigma_threshold_inf
138
+ batch_size, seq_len, embed_dim = query.shape
139
+
140
+ seq_len_sqrt = int(math.sqrt(seq_len))
141
+ num_square_tokens = seq_len_sqrt * seq_len_sqrt
142
+ query_slice = query[:, :num_square_tokens, :]
143
+ query_slice = query_slice.permute(0, 2, 1)
144
+ query_slice = query_slice.reshape(batch_size, embed_dim, seq_len_sqrt, seq_len_sqrt)
145
+
146
+ if is_inf:
147
+ kernel_size = min(kernel_size, seq_len_sqrt - (seq_len_sqrt % 2 - 1))
148
+ kernel_size_half = (kernel_size - 1) / 2
149
+
150
+ x = torch.linspace(-kernel_size_half, kernel_size_half, steps=kernel_size)
151
+ pdf = torch.exp(-0.5 * (x / sigma).pow(2))
152
+ kernel1d = pdf / pdf.sum()
153
+ kernel1d = kernel1d.to(query)
154
+ kernel2d = torch.matmul(kernel1d[:, None], kernel1d[None, :])
155
+ kernel2d = kernel2d.expand(embed_dim, 1, kernel2d.shape[0], kernel2d.shape[1])
156
+
157
+ padding = [kernel_size // 2, kernel_size // 2, kernel_size // 2, kernel_size // 2]
158
+ query_slice = F.pad(query_slice, padding, mode="reflect")
159
+ query_slice = F.conv2d(query_slice, kernel2d, groups=embed_dim)
160
+ else:
161
+ query_slice[:] = query_slice.mean(dim=(-2, -1), keepdim=True)
162
+
163
+ query_slice = query_slice.reshape(batch_size, embed_dim, num_square_tokens)
164
+ query_slice = query_slice.permute(0, 2, 1)
165
+ query[:, :num_square_tokens, :] = query_slice.clone()
166
+
167
+ return query