Spaces:
Running on Zero
Running on Zero
Deploy InstantRetouch BILA ZeroGPU Space
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +54 -6
- app.py +79 -146
- demo_runtime/__init__.py +2 -0
- demo_runtime/backends.py +380 -0
- demo_runtime/bila_layers.py +301 -0
- demo_runtime/image_io.py +71 -0
- demo_runtime/ip2p_scheduler.json +26 -0
- demo_runtime/manager.py +120 -0
- demo_runtime/weights.py +84 -0
- model_manifest.json +74 -0
- requirements.txt +14 -6
- vendor/diffusers/__init__.py +1532 -0
- vendor/diffusers/callbacks.py +244 -0
- vendor/diffusers/commands/__init__.py +27 -0
- vendor/diffusers/commands/custom_blocks.py +134 -0
- vendor/diffusers/commands/diffusers_cli.py +45 -0
- vendor/diffusers/commands/env.py +180 -0
- vendor/diffusers/commands/fp16_safetensors.py +132 -0
- vendor/diffusers/configuration_utils.py +769 -0
- vendor/diffusers/dependency_versions_check.py +34 -0
- vendor/diffusers/dependency_versions_table.py +56 -0
- vendor/diffusers/experimental/README.md +5 -0
- vendor/diffusers/experimental/__init__.py +1 -0
- vendor/diffusers/experimental/rl/__init__.py +1 -0
- vendor/diffusers/experimental/rl/value_guided_sampling.py +153 -0
- vendor/diffusers/guiders/__init__.py +32 -0
- vendor/diffusers/guiders/adaptive_projected_guidance.py +235 -0
- vendor/diffusers/guiders/adaptive_projected_guidance_mix.py +297 -0
- vendor/diffusers/guiders/auto_guidance.py +196 -0
- vendor/diffusers/guiders/classifier_free_guidance.py +154 -0
- vendor/diffusers/guiders/classifier_free_zero_star_guidance.py +162 -0
- vendor/diffusers/guiders/frequency_decoupled_guidance.py +333 -0
- vendor/diffusers/guiders/guider_utils.py +394 -0
- vendor/diffusers/guiders/magnitude_aware_guidance.py +159 -0
- vendor/diffusers/guiders/perturbed_attention_guidance.py +287 -0
- vendor/diffusers/guiders/skip_layer_guidance.py +278 -0
- vendor/diffusers/guiders/smoothed_energy_guidance.py +267 -0
- vendor/diffusers/guiders/tangential_classifier_free_guidance.py +149 -0
- vendor/diffusers/hooks/__init__.py +28 -0
- vendor/diffusers/hooks/_common.py +56 -0
- vendor/diffusers/hooks/_helpers.py +361 -0
- vendor/diffusers/hooks/context_parallel.py +302 -0
- vendor/diffusers/hooks/faster_cache.py +654 -0
- vendor/diffusers/hooks/first_block_cache.py +259 -0
- vendor/diffusers/hooks/group_offloading.py +955 -0
- vendor/diffusers/hooks/hooks.py +291 -0
- vendor/diffusers/hooks/layer_skip.py +263 -0
- vendor/diffusers/hooks/layerwise_casting.py +240 -0
- vendor/diffusers/hooks/pyramid_attention_broadcast.py +314 -0
- 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:
|
| 8 |
-
|
| 9 |
pinned: false
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
|
|
|
| 83 |
|
| 84 |
-
|
| 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 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|