Spaces:
Sleeping
Sleeping
Upload app
Browse files- app.py +354 -0
- bounded_attention.py +522 -0
- injection_utils.py +234 -0
- pipeline_stable_diffusion_xl_opt.py +968 -0
- requirements.txt +22 -0
- utils.py +231 -0
app.py
ADDED
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from diffusers import DDIMScheduler
|
2 |
+
from pipeline_stable_diffusion_xl_opt import StableDiffusionXLPipeline
|
3 |
+
from injection_utils import regiter_attention_editor_diffusers
|
4 |
+
from bounded_attention import BoundedAttention
|
5 |
+
from pytorch_lightning import seed_everything
|
6 |
+
|
7 |
+
import gradio as gr
|
8 |
+
import torch
|
9 |
+
import numpy as np
|
10 |
+
from PIL import Image, ImageDraw
|
11 |
+
|
12 |
+
from functools import partial
|
13 |
+
|
14 |
+
RESOLUTION = 512
|
15 |
+
MIN_SIZE = 0.01
|
16 |
+
COLORS = ["red", "blue", "green", "orange", "purple", "turquoise", "olive"]
|
17 |
+
|
18 |
+
|
19 |
+
def inference(
|
20 |
+
device,
|
21 |
+
model,
|
22 |
+
boxes,
|
23 |
+
prompts,
|
24 |
+
subject_token_indices,
|
25 |
+
filter_token_indices,
|
26 |
+
num_tokens,
|
27 |
+
init_step_size,
|
28 |
+
final_step_size,
|
29 |
+
num_clusters_per_subject,
|
30 |
+
cross_loss_scale,
|
31 |
+
self_loss_scale,
|
32 |
+
classifier_free_guidance_scale,
|
33 |
+
num_iterations,
|
34 |
+
loss_threshold,
|
35 |
+
num_guidance_steps,
|
36 |
+
seed,
|
37 |
+
):
|
38 |
+
seed_everything(seed)
|
39 |
+
start_code = torch.randn([len(prompts), 4, 128, 128], device=device)
|
40 |
+
editor = BoundedAttention(
|
41 |
+
boxes,
|
42 |
+
prompts,
|
43 |
+
subject_token_indices,
|
44 |
+
list(range(70, 82)),
|
45 |
+
list(range(70, 82)),
|
46 |
+
eos_token_index=num_tokens + 1,
|
47 |
+
cross_loss_coef=cross_loss_scale,
|
48 |
+
self_loss_coef=self_loss_scale,
|
49 |
+
filter_token_indices=filter_token_indices,
|
50 |
+
max_guidance_iter=num_guidance_steps,
|
51 |
+
max_guidance_iter_per_step=num_iterations,
|
52 |
+
start_step_size=init_step_size,
|
53 |
+
end_step_size=final_step_size,
|
54 |
+
loss_stopping_value=loss_threshold,
|
55 |
+
num_clusters_per_box=num_clusters_per_subject,
|
56 |
+
debug=False,
|
57 |
+
)
|
58 |
+
|
59 |
+
regiter_attention_editor_diffusers(model, editor)
|
60 |
+
return model(prompts, latents=start_code, guidance_scale=classifier_free_guidance_scale).images
|
61 |
+
|
62 |
+
|
63 |
+
def generate(
|
64 |
+
device,
|
65 |
+
model,
|
66 |
+
prompt,
|
67 |
+
subject_token_indices,
|
68 |
+
filter_token_indices,
|
69 |
+
num_tokens,
|
70 |
+
init_step_size,
|
71 |
+
final_step_size,
|
72 |
+
num_clusters_per_subject,
|
73 |
+
cross_loss_scale,
|
74 |
+
self_loss_scale,
|
75 |
+
classifier_free_guidance_scale,
|
76 |
+
batch_size,
|
77 |
+
num_iterations,
|
78 |
+
loss_threshold,
|
79 |
+
num_guidance_steps,
|
80 |
+
seed,
|
81 |
+
boxes
|
82 |
+
):
|
83 |
+
if 'boxes' not in boxes:
|
84 |
+
boxes['boxes'] = []
|
85 |
+
|
86 |
+
boxes = boxes['boxes']
|
87 |
+
subject_token_indices = convert_token_indices(subject_token_indices, nested=True)
|
88 |
+
if len(boxes) != len(subject_token_indices):
|
89 |
+
raise ValueError("""
|
90 |
+
The number of boxes should be equal to the number of subject token indices.
|
91 |
+
Number of boxes drawn: {}, number of grounding tokens: {}.
|
92 |
+
""".format(len(boxes), len(subject_token_indices)))
|
93 |
+
|
94 |
+
filter_token_indices = convert_token_indices(filter_token_indices) if len(filter_token_indices.strip()) > 0 else None
|
95 |
+
num_tokens = int(num_tokens) if len(num_tokens.strip()) > 0 else None
|
96 |
+
prompts = [prompt.strip('.').strip(',').strip()] * batch_size
|
97 |
+
|
98 |
+
images = inference(
|
99 |
+
device, model, boxes, prompts, subject_token_indices, filter_token_indices, num_tokens, init_step_size,
|
100 |
+
final_step_size, num_clusters_per_subject, cross_loss_scale, self_loss_scale, classifier_free_guidance_scale,
|
101 |
+
num_iterations, loss_threshold, num_guidance_steps, seed)
|
102 |
+
|
103 |
+
blank_samples = batch_size % 2 if batch_size > 1 else 0
|
104 |
+
images = [gr.Image.update(value=x, visible=True) for i, x in enumerate(images)] \
|
105 |
+
+ [gr.Image.update(value=None, visible=True) for _ in range(blank_samples)] \
|
106 |
+
+ [gr.Image.update(value=None, visible=False) for _ in range(4 - batch_size - blank_samples)]
|
107 |
+
|
108 |
+
return images
|
109 |
+
|
110 |
+
|
111 |
+
def convert_token_indices(token_indices, nested=False):
|
112 |
+
if nested:
|
113 |
+
return [convert_token_indices(indices, nested=False) for indices in token_indices.split(';')]
|
114 |
+
|
115 |
+
return [int(index.strip()) for index in token_indices.split(',') if len(index.strip()) > 0]
|
116 |
+
|
117 |
+
|
118 |
+
def draw(boxes, mask):
|
119 |
+
print('Called draw')
|
120 |
+
print('before boxes', boxes)
|
121 |
+
if mask.ndim == 3:
|
122 |
+
mask = 255 - mask[..., 0]
|
123 |
+
|
124 |
+
mask = (mask != 0).astype('uint8') * 255
|
125 |
+
if mask.sum() > 0:
|
126 |
+
x1x2 = np.where(mask.max(0) != 0)[0] / RESOLUTION
|
127 |
+
y1y2 = np.where(mask.max(1) != 0)[0] / RESOLUTION
|
128 |
+
y1, y2 = y1y2.min(), y1y2.max()
|
129 |
+
x1, x2 = x1x2.min(), x1x2.max()
|
130 |
+
|
131 |
+
if (x2 - x1 > MIN_SIZE) and (y2 - y1 > MIN_SIZE):
|
132 |
+
boxes.append((x1, y1, x2, y2))
|
133 |
+
layout_image = draw_boxes(np.array(boxes) * RESOLUTION)
|
134 |
+
|
135 |
+
print('after boxes', boxes)
|
136 |
+
return [boxes, None, layout_image]
|
137 |
+
|
138 |
+
|
139 |
+
def draw_boxes(boxes):
|
140 |
+
if len(boxes) == 0:
|
141 |
+
return None
|
142 |
+
|
143 |
+
image = Image.new('RGB', (RESOLUTION, RESOLUTION), (255, 255, 255))
|
144 |
+
drawing = ImageDraw.Draw(image)
|
145 |
+
print(boxes)
|
146 |
+
for i, box in enumerate(boxes):
|
147 |
+
drawing.rectangle(box, outline=COLORS[i % len(COLORS)], width=4)
|
148 |
+
|
149 |
+
return image
|
150 |
+
|
151 |
+
|
152 |
+
def clear(batch_size):
|
153 |
+
blank_samples = batch_size % 2 if batch_size > 1 else 0
|
154 |
+
out_images = [gr.Image.update(value=None, visible=True) for i in range(batch_size)]
|
155 |
+
return [[], None, None] + out_images
|
156 |
+
|
157 |
+
|
158 |
+
def main():
|
159 |
+
css = """
|
160 |
+
#paper-info a {
|
161 |
+
color:#008AD7;
|
162 |
+
text-decoration: none;
|
163 |
+
}
|
164 |
+
#paper-info a:hover {
|
165 |
+
cursor: pointer;
|
166 |
+
text-decoration: none;
|
167 |
+
}
|
168 |
+
|
169 |
+
.tooltip {
|
170 |
+
color: #555;
|
171 |
+
position: relative;
|
172 |
+
display: inline-block;
|
173 |
+
cursor: pointer;
|
174 |
+
}
|
175 |
+
|
176 |
+
.tooltip .tooltiptext {
|
177 |
+
visibility: hidden;
|
178 |
+
width: 400px;
|
179 |
+
background-color: #555;
|
180 |
+
color: #fff;
|
181 |
+
text-align: center;
|
182 |
+
padding: 5px;
|
183 |
+
border-radius: 5px;
|
184 |
+
position: absolute;
|
185 |
+
z-index: 1; /* Set z-index to 1 */
|
186 |
+
left: 10px;
|
187 |
+
top: 100%;
|
188 |
+
opacity: 0;
|
189 |
+
transition: opacity 0.3s;
|
190 |
+
}
|
191 |
+
|
192 |
+
.tooltip:hover .tooltiptext {
|
193 |
+
visibility: visible;
|
194 |
+
opacity: 1;
|
195 |
+
z-index: 9999; /* Set a high z-index value when hovering */
|
196 |
+
}
|
197 |
+
"""
|
198 |
+
|
199 |
+
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
|
200 |
+
model_path = "stabilityai/stable-diffusion-xl-base-1.0"
|
201 |
+
scheduler = DDIMScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", clip_sample=False, set_alpha_to_one=False)
|
202 |
+
model = StableDiffusionXLPipeline.from_pretrained(model_path, scheduler=scheduler, torch_dtype=torch.float16).to(device)
|
203 |
+
model.unet.set_default_attn_processor()
|
204 |
+
model.enable_xformers_memory_efficient_attention()
|
205 |
+
model.enable_sequential_cpu_offload()
|
206 |
+
|
207 |
+
with gr.Blocks(
|
208 |
+
css=css,
|
209 |
+
title="Bounded Attention demo",
|
210 |
+
) as demo:
|
211 |
+
description = """<p style="text-align: center; font-weight: bold;">
|
212 |
+
<span style="font-size: 28px">Bounded Attention</span>
|
213 |
+
<br>
|
214 |
+
<span style="font-size: 18px" id="paper-info">
|
215 |
+
[<a href="https://omer11a.github.io/bounded-attention/" target="_blank">Project Page</a>]
|
216 |
+
[<a href=" " target="_blank">Paper</a>]
|
217 |
+
[<a href="https://github.com/omer11a/bounded-attention" target="_blank">GitHub</a>]
|
218 |
+
</span>
|
219 |
+
</p>
|
220 |
+
"""
|
221 |
+
gr.HTML(description)
|
222 |
+
with gr.Column():
|
223 |
+
prompt = gr.Textbox(
|
224 |
+
label="Text prompt",
|
225 |
+
)
|
226 |
+
|
227 |
+
subject_token_indices = gr.Textbox(
|
228 |
+
label="The token indices of each subject (separate indices for the same subject with commas, and between different subjects with semicolons)",
|
229 |
+
)
|
230 |
+
|
231 |
+
filter_token_indices = gr.Textbox(
|
232 |
+
label="The token indices to filter, i.e. conjunctions, number, postional relations, etc. (if left empty, this will be automatically inferred)",
|
233 |
+
)
|
234 |
+
|
235 |
+
num_tokens = gr.Textbox(
|
236 |
+
label="The number of tokens in the prompt (can be left empty, but we recommend filling this, so we can verify your input, as sometimes rare words are split into more than one token)",
|
237 |
+
)
|
238 |
+
|
239 |
+
with gr.Row():
|
240 |
+
sketch_pad = gr.Sketchpad(label="Sketch Pad", shape=(RESOLUTION, RESOLUTION))
|
241 |
+
layout_image = gr.Image(type="pil", label="Bounding Boxes")
|
242 |
+
out_images = gr.Image(type="pil", visible=True, label="Generated Image")
|
243 |
+
|
244 |
+
with gr.Row():
|
245 |
+
clear_button = gr.Button(value='Clear')
|
246 |
+
generate_button = gr.Button(value='Generate')
|
247 |
+
|
248 |
+
with gr.Accordion("Advanced Options", open=False):
|
249 |
+
with gr.Column():
|
250 |
+
description = """
|
251 |
+
<div class="tooltip">Batch size ⓘ
|
252 |
+
<span class="tooltiptext">The number of images to generate.</span>
|
253 |
+
</div>
|
254 |
+
<div class="tooltip">Initial step size ⓘ
|
255 |
+
<span class="tooltiptext">The initial step size of the linear step size scheduler when performing guidance.</span>
|
256 |
+
</div>
|
257 |
+
<div class="tooltip">Final step size ⓘ
|
258 |
+
<span class="tooltiptext">The final step size of the linear step size scheduler when performing guidance.</span>
|
259 |
+
</div>
|
260 |
+
<div class="tooltip">Number of self-attention clusters per subject ⓘ
|
261 |
+
<span class="tooltiptext">Determines the number of clusters when clustering the self-attention maps (#clusters = #subject x #clusters_per_subject). Changing this value might improve semantics (adherence to the prompt), especially when the subjects exceed their bounding boxes.</span>
|
262 |
+
</div>
|
263 |
+
<div class="tooltip">Cross-attention loss scale factor ⓘ
|
264 |
+
<span class="tooltiptext">The scale factor of the cross-attention loss term. Increasing it will improve semantic control (adherence to the prompt), but may reduce image quality.</span>
|
265 |
+
</div>
|
266 |
+
<div class="tooltip">Self-attention loss scale factor ⓘ
|
267 |
+
<span class="tooltiptext">The scale factor of the self-attention loss term. Increasing it will improve layout control (adherence to the bounding boxes), but may reduce image quality.</span>
|
268 |
+
</div>
|
269 |
+
<div class="tooltip">Classifier-free guidance scale ⓘ
|
270 |
+
<span class="tooltiptext">The scale factor of classifier-free guidance.</span>
|
271 |
+
</div>
|
272 |
+
<div class="tooltip" >Number of Gradient Descent iterations per timestep ⓘ
|
273 |
+
<span class="tooltiptext">The number of Gradient Descent iterations for each timestep when performing guidance.</span>
|
274 |
+
</div>
|
275 |
+
<div class="tooltip" >Loss Threshold ⓘ
|
276 |
+
<span class="tooltiptext">If the loss is below the threshold, Gradient Descent stops for that timestep. </span>
|
277 |
+
</div>
|
278 |
+
<div class="tooltip" >Number of guidance steps ⓘ
|
279 |
+
<span class="tooltiptext">The number of timesteps in which to perform guidance.</span>
|
280 |
+
</div>
|
281 |
+
"""
|
282 |
+
gr.HTML(description)
|
283 |
+
batch_size = gr.Slider(minimum=1, maximum=5, step=1, value=1, label="Number of samples")
|
284 |
+
init_step_size = gr.Slider(minimum=0, maximum=50, step=0.5, value=25, label="Initial step size")
|
285 |
+
final_step_size = gr.Slider(minimum=0, maximum=20, step=0.5, value=10, label="Final step size")
|
286 |
+
num_clusters_per_subject = gr.Slider(minimum=0, maximum=5, step=0.5, value=3, label="Number of clusters per subject")
|
287 |
+
cross_loss_scale = gr.Slider(minimum=0, maximum=2, step=0.1, value=1, label="Cross-attention loss scale factor")
|
288 |
+
self_loss_scale = gr.Slider(minimum=0, maximum=2, step=0.1, value=1, label="Self-attention loss scale factor")
|
289 |
+
classifier_free_guidance_scale = gr.Slider(minimum=0, maximum=50, step=0.5, value=7.5, label="Classifier-free guidance Scale")
|
290 |
+
num_iterations = gr.Slider(minimum=0, maximum=10, step=1, value=5, label="Number of Gradient Descent iterations")
|
291 |
+
loss_threshold = gr.Slider(minimum=0, maximum=1, step=0.1, value=0.2, label="Loss threshold")
|
292 |
+
num_guidance_steps = gr.Slider(minimum=10, maximum=20, step=1, value=15, label="Number of timesteps to perform guidance")
|
293 |
+
seed = gr.Slider(minimum=0, maximum=1000, step=1, value=445, label="Random Seed")
|
294 |
+
|
295 |
+
boxes = gr.State([])
|
296 |
+
|
297 |
+
demo.load(
|
298 |
+
clear,
|
299 |
+
inputs=[batch_size],
|
300 |
+
outputs=[boxes, sketch_pad, layout_image, out_images],
|
301 |
+
queue=False
|
302 |
+
)
|
303 |
+
|
304 |
+
sketch_pad.edit(
|
305 |
+
draw,
|
306 |
+
inputs=[boxes, sketch_pad],
|
307 |
+
outputs=[boxes, sketch_pad, layout_image],
|
308 |
+
queue=False,
|
309 |
+
)
|
310 |
+
|
311 |
+
clear_button.click(
|
312 |
+
clear,
|
313 |
+
inputs=[batch_size],
|
314 |
+
outputs=[boxes, sketch_pad, layout_image, out_images],
|
315 |
+
queue=False,
|
316 |
+
)
|
317 |
+
|
318 |
+
generate_button.click(
|
319 |
+
fn=partial(generate, device, model),
|
320 |
+
inputs=[
|
321 |
+
prompt, subject_token_indices, filter_token_indices, num_tokens,
|
322 |
+
init_step_size, final_step_size, num_clusters_per_subject, cross_loss_scale, self_loss_scale,
|
323 |
+
classifier_free_guidance_scale, batch_size, num_iterations, loss_threshold, num_guidance_steps,
|
324 |
+
seed,
|
325 |
+
boxes,
|
326 |
+
],
|
327 |
+
outputs=[out_images],
|
328 |
+
queue=True,
|
329 |
+
)
|
330 |
+
|
331 |
+
#with gr.Column():
|
332 |
+
# gr.Examples(
|
333 |
+
# examples=[
|
334 |
+
# [
|
335 |
+
# [[0.35, 0.4, 0.65, 0.9], [0, 0.6, 0.3, 0.9], [0.7, 0.55, 1, 0.85]],
|
336 |
+
# "3D Pixar animation of a cute unicorn and a pink hedgehog and a nerdy owl traveling in a magical forest",
|
337 |
+
# "7,8,17;11,12,17;15,16,17",
|
338 |
+
# "5,6,9,10,13,14,18,19",
|
339 |
+
# 286,
|
340 |
+
# ],
|
341 |
+
# ],
|
342 |
+
# inputs=[boxes, prompt, subject_token_indices, filter_token_indices, seed],
|
343 |
+
# outputs=None,
|
344 |
+
# fn=None,
|
345 |
+
# cache_examples=False,
|
346 |
+
# )
|
347 |
+
description = """<p> The source code of this demo is based on the <a href="https://huggingface.co/spaces/gligen/demo/tree/main">GLIGEN demo</a>.</p>"""
|
348 |
+
gr.HTML(description)
|
349 |
+
|
350 |
+
demo.queue(concurrency_count=1, api_open=False)
|
351 |
+
demo.launch(share=False, show_api=False, show_error=True)
|
352 |
+
|
353 |
+
if __name__ == '__main__':
|
354 |
+
main()
|
bounded_attention.py
ADDED
@@ -0,0 +1,522 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import nltk
|
2 |
+
import einops
|
3 |
+
import torch
|
4 |
+
import torch.nn.functional as F
|
5 |
+
import torchvision.utils
|
6 |
+
from torch_kmeans import KMeans
|
7 |
+
|
8 |
+
import os
|
9 |
+
|
10 |
+
import injection_utils
|
11 |
+
import utils
|
12 |
+
|
13 |
+
|
14 |
+
class BoundedAttention(injection_utils.AttentionBase):
|
15 |
+
EPSILON = 1e-5
|
16 |
+
FILTER_TAGS = {
|
17 |
+
'CC', 'CD', 'DT', 'EX', 'IN', 'LS', 'MD', 'PDT', 'POS', 'PRP', 'PRP$', 'RP', 'TO', 'UH', 'WDT', 'WP', 'WRB'}
|
18 |
+
TAG_RULES = {'left': 'IN', 'right': 'IN', 'top': 'IN', 'bottom': 'IN'}
|
19 |
+
|
20 |
+
def __init__(
|
21 |
+
self,
|
22 |
+
boxes,
|
23 |
+
prompts,
|
24 |
+
subject_token_indices,
|
25 |
+
cross_loss_layers,
|
26 |
+
self_loss_layers,
|
27 |
+
cross_mask_layers=None,
|
28 |
+
self_mask_layers=None,
|
29 |
+
eos_token_index=None,
|
30 |
+
filter_token_indices=None,
|
31 |
+
leading_token_indices=None,
|
32 |
+
mask_cross_during_guidance=True,
|
33 |
+
mask_eos=True,
|
34 |
+
cross_loss_coef=1,
|
35 |
+
self_loss_coef=1,
|
36 |
+
max_guidance_iter=15,
|
37 |
+
max_guidance_iter_per_step=5,
|
38 |
+
start_step_size=30,
|
39 |
+
end_step_size=10,
|
40 |
+
loss_stopping_value=0.2,
|
41 |
+
cross_mask_threshold=0.2,
|
42 |
+
self_mask_threshold=0.2,
|
43 |
+
delta_refine_mask_steps=5,
|
44 |
+
pca_rank=None,
|
45 |
+
num_clusters=None,
|
46 |
+
num_clusters_per_box=3,
|
47 |
+
map_dir=None,
|
48 |
+
debug=False,
|
49 |
+
delta_debug_attention_steps=20,
|
50 |
+
delta_debug_mask_steps=5,
|
51 |
+
debug_layers=None,
|
52 |
+
saved_resolution=64,
|
53 |
+
):
|
54 |
+
super().__init__()
|
55 |
+
self.boxes = boxes
|
56 |
+
self.prompts = prompts
|
57 |
+
self.subject_token_indices = subject_token_indices
|
58 |
+
self.cross_loss_layers = set(cross_loss_layers)
|
59 |
+
self.self_loss_layers = set(self_loss_layers)
|
60 |
+
self.cross_mask_layers = self.cross_loss_layers if cross_mask_layers is None else set(cross_mask_layers)
|
61 |
+
self.self_mask_layers = self.self_loss_layers if self_mask_layers is None else set(self_mask_layers)
|
62 |
+
|
63 |
+
self.eos_token_index = eos_token_index
|
64 |
+
self.filter_token_indices = filter_token_indices
|
65 |
+
self.leading_token_indices = leading_token_indices
|
66 |
+
self.mask_cross_during_guidance = mask_cross_during_guidance
|
67 |
+
self.mask_eos = mask_eos
|
68 |
+
self.cross_loss_coef = cross_loss_coef
|
69 |
+
self.self_loss_coef = self_loss_coef
|
70 |
+
self.max_guidance_iter = max_guidance_iter
|
71 |
+
self.max_guidance_iter_per_step = max_guidance_iter_per_step
|
72 |
+
self.start_step_size = start_step_size
|
73 |
+
self.step_size_coef = (end_step_size - start_step_size) / max_guidance_iter
|
74 |
+
self.loss_stopping_value = loss_stopping_value
|
75 |
+
self.cross_mask_threshold = cross_mask_threshold
|
76 |
+
self.self_mask_threshold = self_mask_threshold
|
77 |
+
|
78 |
+
self.delta_refine_mask_steps = delta_refine_mask_steps
|
79 |
+
self.pca_rank = pca_rank
|
80 |
+
num_clusters = len(boxes) * num_clusters_per_box if num_clusters is None else num_clusters
|
81 |
+
self.clustering = KMeans(n_clusters=num_clusters, num_init=100)
|
82 |
+
self.centers = None
|
83 |
+
|
84 |
+
self.map_dir = map_dir
|
85 |
+
self.debug = debug
|
86 |
+
self.delta_debug_attention_steps = delta_debug_attention_steps
|
87 |
+
self.delta_debug_mask_steps = delta_debug_mask_steps
|
88 |
+
self.debug_layers = self.cross_loss_layers | self.self_loss_layers if debug_layers is None else debug_layers
|
89 |
+
self.saved_resolution = saved_resolution
|
90 |
+
|
91 |
+
self.optimized = False
|
92 |
+
self.cross_foreground_values = []
|
93 |
+
self.self_foreground_values = []
|
94 |
+
self.cross_background_values = []
|
95 |
+
self.self_background_values = []
|
96 |
+
self.cross_maps = []
|
97 |
+
self.self_maps = []
|
98 |
+
self.self_masks = None
|
99 |
+
|
100 |
+
def clear_values(self, include_maps=False):
|
101 |
+
lists = (
|
102 |
+
self.cross_foreground_values,
|
103 |
+
self.self_foreground_values,
|
104 |
+
self.cross_background_values,
|
105 |
+
self.self_background_values,
|
106 |
+
)
|
107 |
+
|
108 |
+
if include_maps:
|
109 |
+
lists = (
|
110 |
+
*all_values,
|
111 |
+
self.cross_maps,
|
112 |
+
self.self_maps,
|
113 |
+
)
|
114 |
+
|
115 |
+
for values in lists:
|
116 |
+
values.clear()
|
117 |
+
|
118 |
+
def before_step(self):
|
119 |
+
self.clear_values()
|
120 |
+
if self.cur_step == 0:
|
121 |
+
self._determine_tokens()
|
122 |
+
|
123 |
+
def reset(self):
|
124 |
+
self.clear_values(include_maps=True)
|
125 |
+
super().reset()
|
126 |
+
|
127 |
+
def forward(self, q, k, v, sim, attn, is_cross, place_in_unet, num_heads, **kwargs):
|
128 |
+
self._display_attention_maps(attn, is_cross, num_heads)
|
129 |
+
|
130 |
+
_, n, d = sim.shape
|
131 |
+
sim_u, sim_c = sim.reshape(-1, num_heads, n, d).chunk(2) # b h n d
|
132 |
+
if is_cross:
|
133 |
+
sim_c = self._hide_other_subjects_from_tokens(sim_c)
|
134 |
+
else:
|
135 |
+
sim_u = self._hide_other_subjects_from_subjects(sim_u)
|
136 |
+
sim_c = self._hide_other_subjects_from_subjects(sim_c)
|
137 |
+
|
138 |
+
sim = torch.cat((sim_u, sim_c)).reshape(-1, n, d)
|
139 |
+
attn = sim.softmax(-1)
|
140 |
+
self._save(attn, is_cross, num_heads)
|
141 |
+
self._display_attention_maps(attn, is_cross, num_heads, prefix='masked')
|
142 |
+
self._debug_hook(q, k, v, sim, attn, is_cross, place_in_unet, num_heads, **kwargs)
|
143 |
+
|
144 |
+
out = torch.bmm(attn, v)
|
145 |
+
out = einops.rearrange(out, '(b h) n d -> b n (h d)', h=num_heads)
|
146 |
+
return out
|
147 |
+
|
148 |
+
def update_loss(self, forward_pass, latents, i):
|
149 |
+
if i >= self.max_guidance_iter:
|
150 |
+
return latents
|
151 |
+
|
152 |
+
step_size = self.start_step_size + self.step_size_coef * i
|
153 |
+
updated_latents = latents
|
154 |
+
|
155 |
+
self.optimized = True
|
156 |
+
normalized_loss = torch.tensor(10000)
|
157 |
+
with torch.enable_grad():
|
158 |
+
latents = updated_latents = updated_latents.clone().detach().requires_grad_(True)
|
159 |
+
for guidance_iter in range(self.max_guidance_iter_per_step):
|
160 |
+
if normalized_loss < self.loss_stopping_value:
|
161 |
+
break
|
162 |
+
|
163 |
+
latent_model_input = torch.cat([latents] * 2)
|
164 |
+
cur_step = self.cur_step
|
165 |
+
forward_pass(latent_model_input)
|
166 |
+
self.cur_step = cur_step
|
167 |
+
|
168 |
+
loss, normalized_loss = self._compute_loss()
|
169 |
+
grad_cond = torch.autograd.grad(loss, [updated_latents])[0]
|
170 |
+
latents = updated_latents = updated_latents - step_size * grad_cond
|
171 |
+
if self.debug:
|
172 |
+
print(f'Loss at step={i}, iter={guidance_iter}: {normalized_loss}')
|
173 |
+
grad_norms = grad_cond.flatten(start_dim=2).norm(dim=1)
|
174 |
+
grad_norms = grad_norms / grad_norms.max(dim=1, keepdim=True)[0]
|
175 |
+
self._save_maps(grad_norms, 'grad_norms')
|
176 |
+
|
177 |
+
self.optimized = False
|
178 |
+
return latents
|
179 |
+
|
180 |
+
def _tokenize(self):
|
181 |
+
ids = self.model.tokenizer.encode(self.prompts[0])
|
182 |
+
tokens = self.model.tokenizer.convert_ids_to_tokens(ids, skip_special_tokens=True)
|
183 |
+
return [token[:-4] for token in tokens] # remove ending </w>
|
184 |
+
|
185 |
+
def _tag_tokens(self):
|
186 |
+
tagged_tokens = nltk.pos_tag(self._tokenize())
|
187 |
+
return [type(self).TAG_RULES.get(token, tag) for token, tag in tagged_tokens]
|
188 |
+
|
189 |
+
def _determine_eos_token(self):
|
190 |
+
tokens = self._tokenize()
|
191 |
+
eos_token_index = len(tokens) + 1
|
192 |
+
if self.eos_token_index is None:
|
193 |
+
self.eos_token_index = eos_token_index
|
194 |
+
elif eos_token_index != self.eos_token_index:
|
195 |
+
raise ValueError(f'Wrong EOS token index. Tokens are: {tokens}.')
|
196 |
+
|
197 |
+
def _determine_filter_tokens(self):
|
198 |
+
if self.filter_token_indices is not None:
|
199 |
+
return
|
200 |
+
|
201 |
+
tags = self._tag_tokens()
|
202 |
+
self.filter_token_indices = [i + 1 for i, tag in enumerate(tags) if tag in type(self).FILTER_TAGS]
|
203 |
+
|
204 |
+
def _determine_leading_tokens(self):
|
205 |
+
if self.leading_token_indices is not None:
|
206 |
+
return
|
207 |
+
|
208 |
+
tags = self._tag_tokens()
|
209 |
+
leading_token_indices = []
|
210 |
+
for indices in self.subject_token_indices:
|
211 |
+
subject_noun_indices = [i for i in indices if tags[i - 1].startswith('NN')]
|
212 |
+
leading_token_candidates = subject_noun_indices if len(subject_noun_indices) > 0 else indices
|
213 |
+
leading_token_indices.append(leading_token_candidates[-1])
|
214 |
+
|
215 |
+
self.leading_token_indices = leading_token_indices
|
216 |
+
|
217 |
+
def _determine_tokens(self):
|
218 |
+
self._determine_eos_token()
|
219 |
+
self._determine_filter_tokens()
|
220 |
+
self._determine_leading_tokens()
|
221 |
+
|
222 |
+
def _split_references(self, tensor, num_heads):
|
223 |
+
tensor = tensor.reshape(-1, num_heads, *tensor.shape[1:])
|
224 |
+
unconditional, conditional = tensor.chunk(2)
|
225 |
+
|
226 |
+
num_subjects = len(self.boxes)
|
227 |
+
batch_unconditional = unconditional[:-num_subjects]
|
228 |
+
references_unconditional = unconditional[-num_subjects:]
|
229 |
+
batch_conditional = conditional[:-num_subjects]
|
230 |
+
references_conditional = conditional[-num_subjects:]
|
231 |
+
|
232 |
+
batch = torch.cat((batch_unconditional, batch_conditional))
|
233 |
+
references = torch.cat((references_unconditional, references_conditional))
|
234 |
+
batch = batch.reshape(-1, *batch_unconditional.shape[2:])
|
235 |
+
references = references.reshape(-1, *references_unconditional.shape[2:])
|
236 |
+
return batch, references
|
237 |
+
|
238 |
+
def _hide_other_subjects_from_tokens(self, sim): # b h i j
|
239 |
+
dtype = sim.dtype
|
240 |
+
device = sim.device
|
241 |
+
batch_size = sim.size(0)
|
242 |
+
resolution = int(sim.size(2) ** 0.5)
|
243 |
+
subject_masks, background_masks = self._obtain_masks(resolution, batch_size=batch_size, device=device) # b s n
|
244 |
+
include_background = self.optimized or (not self.mask_cross_during_guidance and self.cur_step < self.max_guidance_iter_per_step)
|
245 |
+
subject_masks = torch.logical_or(subject_masks, background_masks.unsqueeze(1)) if include_background else subject_masks
|
246 |
+
min_value = torch.finfo(sim.dtype).min
|
247 |
+
sim_masks = torch.zeros_like(sim[:, 0, :, :]) # b i j
|
248 |
+
for token_indices in (*self.subject_token_indices, self.filter_token_indices):
|
249 |
+
sim_masks[:, :, token_indices] = min_value
|
250 |
+
|
251 |
+
for batch_index in range(batch_size):
|
252 |
+
for subject_mask, token_indices in zip(subject_masks[batch_index], self.subject_token_indices):
|
253 |
+
for token_index in token_indices:
|
254 |
+
sim_masks[batch_index, subject_mask, token_index] = 0
|
255 |
+
|
256 |
+
if self.mask_eos and not include_background:
|
257 |
+
for batch_index, background_mask in zip(range(batch_size), background_masks):
|
258 |
+
sim_masks[batch_index, background_mask, self.eos_token_index] = min_value
|
259 |
+
|
260 |
+
return sim + sim_masks.unsqueeze(1)
|
261 |
+
|
262 |
+
def _hide_other_subjects_from_subjects(self, sim): # b h i j
|
263 |
+
dtype = sim.dtype
|
264 |
+
device = sim.device
|
265 |
+
batch_size = sim.size(0)
|
266 |
+
resolution = int(sim.size(2) ** 0.5)
|
267 |
+
subject_masks, background_masks = self._obtain_masks(resolution, batch_size=batch_size, device=device) # b s n
|
268 |
+
min_value = torch.finfo(dtype).min
|
269 |
+
sim_masks = torch.zeros_like(sim[:, 0, :, :]) # b i j
|
270 |
+
for batch_index, background_mask in zip(range(batch_size), background_masks):
|
271 |
+
sim_masks[batch_index, ~background_mask, ~background_mask] = min_value
|
272 |
+
|
273 |
+
for batch_index in range(batch_size):
|
274 |
+
for subject_mask in subject_masks[batch_index]:
|
275 |
+
subject_sim_mask = sim_masks[batch_index, subject_mask]
|
276 |
+
condition = torch.logical_or(subject_sim_mask == 0, subject_mask.unsqueeze(0))
|
277 |
+
sim_masks[batch_index, subject_mask] = torch.where(condition, 0, min_value).to(dtype=dtype)
|
278 |
+
|
279 |
+
return sim + sim_masks.unsqueeze(1)
|
280 |
+
|
281 |
+
def _save(self, attn, is_cross, num_heads):
|
282 |
+
_, attn = attn.chunk(2)
|
283 |
+
attn = attn.reshape(-1, num_heads, *attn.shape[-2:]) # b h n k
|
284 |
+
|
285 |
+
self._save_mask_maps(attn, is_cross)
|
286 |
+
self._save_loss_values(attn, is_cross)
|
287 |
+
|
288 |
+
def _save_mask_maps(self, attn, is_cross):
|
289 |
+
if (
|
290 |
+
(self.optimized) or
|
291 |
+
(is_cross and self.cur_att_layer not in self.cross_mask_layers) or
|
292 |
+
((not is_cross) and (self.cur_att_layer not in self.self_mask_layers))
|
293 |
+
):
|
294 |
+
return
|
295 |
+
|
296 |
+
if is_cross:
|
297 |
+
attn = attn[..., self.leading_token_indices]
|
298 |
+
mask_maps = self.cross_maps
|
299 |
+
else:
|
300 |
+
mask_maps = self.self_maps
|
301 |
+
|
302 |
+
mask_maps.append(attn.mean(dim=1)) # mean over heads
|
303 |
+
if self.cur_step > 0:
|
304 |
+
mask_maps = mask_maps[1:] # throw away old maps
|
305 |
+
|
306 |
+
def _save_loss_values(self, attn, is_cross):
|
307 |
+
if (
|
308 |
+
(not self.optimized) or
|
309 |
+
(is_cross and (self.cur_att_layer not in self.cross_loss_layers)) or
|
310 |
+
((not is_cross) and (self.cur_att_layer not in self.self_loss_layers))
|
311 |
+
):
|
312 |
+
return
|
313 |
+
|
314 |
+
resolution = int(attn.size(2) ** 0.5)
|
315 |
+
boxes = self._convert_boxes_to_masks(resolution, device=attn.device) # s n
|
316 |
+
background_mask = boxes.sum(dim=0) == 0
|
317 |
+
|
318 |
+
if is_cross:
|
319 |
+
saved_foreground_values = self.cross_foreground_values
|
320 |
+
saved_background_values = self.cross_background_values
|
321 |
+
contexts = [indices + [self.eos_token_index] for indices in self.subject_token_indices] # TODO: fix EOS loss term
|
322 |
+
else:
|
323 |
+
saved_foreground_values = self.self_foreground_values
|
324 |
+
saved_background_values = self.self_background_values
|
325 |
+
contexts = boxes
|
326 |
+
|
327 |
+
foreground_values = []
|
328 |
+
background_values = []
|
329 |
+
for i, (box, context) in enumerate(zip(boxes, contexts)):
|
330 |
+
context_attn = attn[:, :, :, context]
|
331 |
+
|
332 |
+
# sum over heads, pixels and contexts
|
333 |
+
foreground_values.append(context_attn[:, :, box].sum(dim=(1, 2, 3)))
|
334 |
+
background_values.append(context_attn[:, :, background_mask].sum(dim=(1, 2, 3)))
|
335 |
+
|
336 |
+
saved_foreground_values.append(torch.stack(foreground_values, dim=1))
|
337 |
+
saved_background_values.append(torch.stack(background_values, dim=1))
|
338 |
+
|
339 |
+
def _compute_loss(self):
|
340 |
+
cross_losses = self._compute_loss_term(self.cross_foreground_values, self.cross_background_values)
|
341 |
+
self_losses = self._compute_loss_term(self.self_foreground_values, self.self_background_values)
|
342 |
+
b, s = cross_losses.shape
|
343 |
+
|
344 |
+
# sum over samples and subjects
|
345 |
+
total_cross_loss = cross_losses.sum()
|
346 |
+
total_self_loss = self_losses.sum()
|
347 |
+
|
348 |
+
loss = self.cross_loss_coef * total_cross_loss + self.self_loss_coef * total_self_loss
|
349 |
+
normalized_loss = loss / b / s
|
350 |
+
return loss, normalized_loss
|
351 |
+
|
352 |
+
def _compute_loss_term(self, foreground_values, background_values):
|
353 |
+
# mean over layers
|
354 |
+
mean_foreground = torch.stack(foreground_values).mean(dim=0)
|
355 |
+
mean_background = torch.stack(background_values).mean(dim=0)
|
356 |
+
iou = mean_foreground / (mean_foreground + len(self.boxes) * mean_background)
|
357 |
+
return (1 - iou) ** 2
|
358 |
+
|
359 |
+
def _obtain_masks(self, resolution, return_boxes=False, return_existing=False, batch_size=None, device=None):
|
360 |
+
return_boxes = return_boxes or (return_existing and self.self_masks is None)
|
361 |
+
if return_boxes or self.cur_step < self.max_guidance_iter:
|
362 |
+
masks = self._convert_boxes_to_masks(resolution, device=device).unsqueeze(0)
|
363 |
+
if batch_size is not None:
|
364 |
+
masks = masks.expand(batch_size, *masks.shape[1:])
|
365 |
+
else:
|
366 |
+
masks = self._obtain_self_masks(resolution, return_existing=return_existing)
|
367 |
+
if device is not None:
|
368 |
+
masks = masks.to(device=device)
|
369 |
+
|
370 |
+
background_mask = masks.sum(dim=1) == 0
|
371 |
+
return masks, background_mask
|
372 |
+
|
373 |
+
def _convert_boxes_to_masks(self, resolution, device=None): # s n
|
374 |
+
boxes = torch.zeros(len(self.boxes), resolution, resolution, dtype=bool, device=device)
|
375 |
+
for i, box in enumerate(self.boxes):
|
376 |
+
x0, x1 = box[0] * resolution, box[2] * resolution
|
377 |
+
y0, y1 = box[1] * resolution, box[3] * resolution
|
378 |
+
|
379 |
+
boxes[i, round(y0) : round(y1), round(x0) : round(x1)] = True
|
380 |
+
|
381 |
+
return boxes.flatten(start_dim=1)
|
382 |
+
|
383 |
+
def _obtain_self_masks(self, resolution, return_existing=False):
|
384 |
+
if (
|
385 |
+
(self.self_masks is None) or
|
386 |
+
(
|
387 |
+
(self.cur_step % self.delta_refine_mask_steps == 0) and
|
388 |
+
(self.cur_att_layer == 0) and
|
389 |
+
(not return_existing)
|
390 |
+
)
|
391 |
+
):
|
392 |
+
self.self_masks = self._fix_zero_masks(self._build_self_masks())
|
393 |
+
|
394 |
+
b, s, n = self.self_masks.shape
|
395 |
+
mask_resolution = int(n ** 0.5)
|
396 |
+
self_masks = self.self_masks.reshape(b, s, mask_resolution, mask_resolution).float()
|
397 |
+
self_masks = F.interpolate(self_masks, resolution, mode='nearest-exact')
|
398 |
+
return self_masks.flatten(start_dim=2).bool()
|
399 |
+
|
400 |
+
def _cluster_self_maps(self): # b s n
|
401 |
+
self_maps = self._aggregate_maps(self.self_maps) # b n m
|
402 |
+
if self.pca_rank is not None:
|
403 |
+
dtype = self_maps.dtype
|
404 |
+
_, _, eigen_vectors = torch.pca_lowrank(self_maps.float(), self.pca_rank)
|
405 |
+
self_maps = torch.matmul(self_maps, eigen_vectors.to(dtype=dtype))
|
406 |
+
|
407 |
+
clustering_results = self.clustering(self_maps, centers=self.centers)
|
408 |
+
self.clustering.num_init = 1 # clustering is deterministic after the first time
|
409 |
+
self.centers = clustering_results.centers
|
410 |
+
clusters = clustering_results.labels
|
411 |
+
num_clusters = self.clustering.n_clusters
|
412 |
+
self._save_maps(clusters / num_clusters, f'clusters')
|
413 |
+
return num_clusters, clusters
|
414 |
+
|
415 |
+
def _build_self_masks(self):
|
416 |
+
c, clusters = self._cluster_self_maps() # b n
|
417 |
+
cluster_masks = torch.stack([(clusters == cluster_index) for cluster_index in range(c)], dim=2) # b n c
|
418 |
+
cluster_area = cluster_masks.sum(dim=1, keepdim=True) # b 1 c
|
419 |
+
|
420 |
+
n = clusters.size(1)
|
421 |
+
resolution = int(n ** 0.5)
|
422 |
+
cross_masks = self._obtain_cross_masks(resolution) # b s n
|
423 |
+
cross_mask_area = cross_masks.sum(dim=2, keepdim=True) # b s 1
|
424 |
+
|
425 |
+
intersection = torch.bmm(cross_masks.float(), cluster_masks.float()) # b s c
|
426 |
+
min_area = torch.minimum(cross_mask_area, cluster_area) # b s c
|
427 |
+
score_per_cluster, subject_per_cluster = torch.max(intersection / min_area, dim=1) # b c
|
428 |
+
subjects = torch.gather(subject_per_cluster, 1, clusters) # b n
|
429 |
+
scores = torch.gather(score_per_cluster, 1, clusters) # b n
|
430 |
+
|
431 |
+
s = cross_masks.size(1)
|
432 |
+
self_masks = torch.stack([(subjects == subject_index) for subject_index in range(s)], dim=1) # b s n
|
433 |
+
scores = scores.unsqueeze(1).expand(-1 ,s, n) # b s n
|
434 |
+
self_masks[scores < self.self_mask_threshold] = False
|
435 |
+
self._save_maps(self_masks, 'self_masks')
|
436 |
+
return self_masks
|
437 |
+
|
438 |
+
def _obtain_cross_masks(self, resolution, scale=10):
|
439 |
+
maps = self._aggregate_maps(self.cross_maps, resolution=resolution) # b n k
|
440 |
+
maps = F.sigmoid(scale * (maps - self.cross_mask_threshold))
|
441 |
+
maps = self._normalize_maps(maps, reduce_min=True)
|
442 |
+
maps = maps.transpose(1, 2) # b k n
|
443 |
+
existing_masks, _ = self._obtain_masks(
|
444 |
+
resolution, return_existing=True, batch_size=maps.size(0), device=maps.device)
|
445 |
+
maps = maps * existing_masks.to(dtype=maps.dtype)
|
446 |
+
self._save_maps(maps, 'cross_masks')
|
447 |
+
return maps
|
448 |
+
|
449 |
+
def _fix_zero_masks(self, masks):
|
450 |
+
b, s, n = masks.shape
|
451 |
+
resolution = int(n ** 0.5)
|
452 |
+
boxes = self._convert_boxes_to_masks(resolution, device=masks.device) # s n
|
453 |
+
|
454 |
+
for i in range(b):
|
455 |
+
for j in range(s):
|
456 |
+
if masks[i, j].sum() == 0:
|
457 |
+
print('******Found a zero mask!******')
|
458 |
+
for k in range(s):
|
459 |
+
masks[i, k] = boxes[j] if (k == j) else masks[i, k].logical_and(~boxes[j])
|
460 |
+
|
461 |
+
return masks
|
462 |
+
|
463 |
+
def _aggregate_maps(self, maps, resolution=None): # b n k
|
464 |
+
maps = torch.stack(maps).mean(0) # mean over layers
|
465 |
+
if resolution is not None:
|
466 |
+
b, n, k = maps.shape
|
467 |
+
original_resolution = int(n ** 0.5)
|
468 |
+
maps = maps.transpose(1, 2).reshape(b, k, original_resolution, original_resolution)
|
469 |
+
maps = F.interpolate(maps, resolution, mode='bilinear', antialias=True)
|
470 |
+
maps = maps.reshape(b, k, -1).transpose(1, 2)
|
471 |
+
|
472 |
+
maps = self._normalize_maps(maps)
|
473 |
+
return maps
|
474 |
+
|
475 |
+
@classmethod
|
476 |
+
def _normalize_maps(cls, maps, reduce_min=False): # b n k
|
477 |
+
max_values = maps.max(dim=1, keepdim=True)[0]
|
478 |
+
min_values = maps.min(dim=1, keepdim=True)[0] if reduce_min else 0
|
479 |
+
numerator = maps - min_values
|
480 |
+
denominator = max_values - min_values + cls.EPSILON
|
481 |
+
return numerator / denominator
|
482 |
+
|
483 |
+
def _save_maps(self, maps, prefix):
|
484 |
+
if self.map_dir is None or self.cur_step % self.delta_debug_mask_steps != 0:
|
485 |
+
return
|
486 |
+
|
487 |
+
resolution = int(maps.size(-1) ** 0.5)
|
488 |
+
maps = maps.reshape(-1, 1, resolution, resolution).float()
|
489 |
+
maps = F.interpolate(maps, self.saved_resolution, mode='bilinear', antialias=True)
|
490 |
+
path = os.path.join(self.map_dir, f'map_{prefix}_{self.cur_step}_{self.cur_att_layer}.png')
|
491 |
+
torchvision.utils.save_image(maps, path)
|
492 |
+
|
493 |
+
def _display_attention_maps(self, attention_maps, is_cross, num_heads, prefix=None):
|
494 |
+
if (not self.debug) or (self.cur_step == 0) or (self.cur_step % self.delta_debug_attention_steps > 0) or (self.cur_att_layer not in self.debug_layers):
|
495 |
+
return
|
496 |
+
|
497 |
+
dir_name = self.map_dir
|
498 |
+
if prefix is not None:
|
499 |
+
splits = list(os.path.split(dir_name))
|
500 |
+
splits[-1] = '_'.join((prefix, splits[-1]))
|
501 |
+
dir_name = os.path.join(*splits)
|
502 |
+
|
503 |
+
resolution = int(attention_maps.size(-2) ** 0.5)
|
504 |
+
if is_cross:
|
505 |
+
attention_maps = einops.rearrange(attention_maps, 'b (r1 r2) k -> b k r1 r2', r1=resolution)
|
506 |
+
attention_maps = F.interpolate(attention_maps, self.saved_resolution, mode='bilinear', antialias=True)
|
507 |
+
attention_maps = einops.rearrange(attention_maps, 'b k r1 r2 -> b (r1 r2) k')
|
508 |
+
|
509 |
+
utils.display_attention_maps(
|
510 |
+
attention_maps,
|
511 |
+
is_cross,
|
512 |
+
num_heads,
|
513 |
+
self.model.tokenizer,
|
514 |
+
self.prompts,
|
515 |
+
dir_name,
|
516 |
+
self.cur_step,
|
517 |
+
self.cur_att_layer,
|
518 |
+
resolution,
|
519 |
+
)
|
520 |
+
|
521 |
+
def _debug_hook(self, q, k, v, sim, attn, is_cross, place_in_unet, num_heads, **kwargs):
|
522 |
+
pass
|
injection_utils.py
ADDED
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import numpy as np
|
3 |
+
import torch
|
4 |
+
import torch.nn as nn
|
5 |
+
import torch.nn.functional as F
|
6 |
+
|
7 |
+
from typing import Optional, Union, Tuple, List, Callable, Dict
|
8 |
+
|
9 |
+
from torchvision.utils import save_image
|
10 |
+
from einops import rearrange, repeat
|
11 |
+
|
12 |
+
|
13 |
+
class AttentionBase:
|
14 |
+
def __init__(self):
|
15 |
+
self.cur_step = 0
|
16 |
+
self.num_att_layers = -1
|
17 |
+
self.cur_att_layer = 0
|
18 |
+
|
19 |
+
def before_step(self):
|
20 |
+
pass
|
21 |
+
|
22 |
+
def after_step(self):
|
23 |
+
pass
|
24 |
+
|
25 |
+
def __call__(self, q, k, v, sim, attn, is_cross, place_in_unet, num_heads, **kwargs):
|
26 |
+
if self.cur_att_layer == 0:
|
27 |
+
self.before_step()
|
28 |
+
|
29 |
+
out = self.forward(q, k, v, sim, attn, is_cross, place_in_unet, num_heads, **kwargs)
|
30 |
+
self.cur_att_layer += 1
|
31 |
+
if self.cur_att_layer == self.num_att_layers:
|
32 |
+
self.cur_att_layer = 0
|
33 |
+
self.cur_step += 1
|
34 |
+
# after step
|
35 |
+
self.after_step()
|
36 |
+
return out
|
37 |
+
|
38 |
+
def forward(self, q, k, v, sim, attn, is_cross, place_in_unet, num_heads, **kwargs):
|
39 |
+
out = torch.einsum('b i j, b j d -> b i d', attn, v)
|
40 |
+
out = rearrange(out, '(b h) n d -> b n (h d)', h=num_heads)
|
41 |
+
return out
|
42 |
+
|
43 |
+
def reset(self):
|
44 |
+
self.cur_step = 0
|
45 |
+
self.cur_att_layer = 0
|
46 |
+
|
47 |
+
|
48 |
+
class AttentionStore(AttentionBase):
|
49 |
+
def __init__(self, res=[32], min_step=0, max_step=1000):
|
50 |
+
super().__init__()
|
51 |
+
self.res = res
|
52 |
+
self.min_step = min_step
|
53 |
+
self.max_step = max_step
|
54 |
+
self.valid_steps = 0
|
55 |
+
|
56 |
+
self.self_attns = [] # store the all attns
|
57 |
+
self.cross_attns = []
|
58 |
+
|
59 |
+
self.self_attns_step = [] # store the attns in each step
|
60 |
+
self.cross_attns_step = []
|
61 |
+
|
62 |
+
def after_step(self):
|
63 |
+
if self.cur_step > self.min_step and self.cur_step < self.max_step:
|
64 |
+
self.valid_steps += 1
|
65 |
+
if len(self.self_attns) == 0:
|
66 |
+
self.self_attns = self.self_attns_step
|
67 |
+
self.cross_attns = self.cross_attns_step
|
68 |
+
else:
|
69 |
+
for i in range(len(self.self_attns)):
|
70 |
+
self.self_attns[i] += self.self_attns_step[i]
|
71 |
+
self.cross_attns[i] += self.cross_attns_step[i]
|
72 |
+
self.self_attns_step.clear()
|
73 |
+
self.cross_attns_step.clear()
|
74 |
+
|
75 |
+
def forward(self, q, k, v, sim, attn, is_cross, place_in_unet, num_heads, **kwargs):
|
76 |
+
if attn.shape[1] <= 64 ** 2: # avoid OOM
|
77 |
+
if is_cross:
|
78 |
+
self.cross_attns_step.append(attn)
|
79 |
+
else:
|
80 |
+
self.self_attns_step.append(attn)
|
81 |
+
return super().forward(q, k, v, sim, attn, is_cross, place_in_unet, num_heads, **kwargs)
|
82 |
+
|
83 |
+
|
84 |
+
def regiter_attention_editor_diffusers(model, editor: AttentionBase):
|
85 |
+
"""
|
86 |
+
Register a attention editor to Diffuser Pipeline, refer from [Prompt-to-Prompt]
|
87 |
+
"""
|
88 |
+
def ca_forward(self, place_in_unet):
|
89 |
+
def forward(x, encoder_hidden_states=None, attention_mask=None, context=None, mask=None):
|
90 |
+
"""
|
91 |
+
The attention is similar to the original implementation of LDM CrossAttention class
|
92 |
+
except adding some modifications on the attention
|
93 |
+
"""
|
94 |
+
if encoder_hidden_states is not None:
|
95 |
+
context = encoder_hidden_states
|
96 |
+
if attention_mask is not None:
|
97 |
+
mask = attention_mask
|
98 |
+
|
99 |
+
to_out = self.to_out
|
100 |
+
if isinstance(to_out, nn.modules.container.ModuleList):
|
101 |
+
to_out = self.to_out[0]
|
102 |
+
else:
|
103 |
+
to_out = self.to_out
|
104 |
+
|
105 |
+
h = self.heads
|
106 |
+
q = self.to_q(x)
|
107 |
+
is_cross = context is not None
|
108 |
+
context = context if is_cross else x
|
109 |
+
k = self.to_k(context)
|
110 |
+
v = self.to_v(context)
|
111 |
+
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v))
|
112 |
+
|
113 |
+
sim = torch.einsum('b i d, b j d -> b i j', q, k) * self.scale
|
114 |
+
|
115 |
+
if mask is not None:
|
116 |
+
mask = rearrange(mask, 'b ... -> b (...)')
|
117 |
+
max_neg_value = -torch.finfo(sim.dtype).max
|
118 |
+
mask = repeat(mask, 'b j -> (b h) () j', h=h)
|
119 |
+
mask = mask[:, None, :].repeat(h, 1, 1)
|
120 |
+
sim.masked_fill_(~mask, max_neg_value)
|
121 |
+
|
122 |
+
attn = sim.softmax(dim=-1)
|
123 |
+
# the only difference
|
124 |
+
out = editor(
|
125 |
+
q, k, v, sim, attn, is_cross, place_in_unet,
|
126 |
+
self.heads, scale=self.scale)
|
127 |
+
|
128 |
+
return to_out(out)
|
129 |
+
|
130 |
+
return forward
|
131 |
+
|
132 |
+
#def conv_forward(self):
|
133 |
+
# def forward(x):
|
134 |
+
# return editor.conv(self, x)
|
135 |
+
|
136 |
+
# return forward
|
137 |
+
|
138 |
+
def register_editor(net, count, place_in_unet, prefix=''):
|
139 |
+
#print(prefix + f'Found net: {net.__class__.__name__}')
|
140 |
+
#if 'Conv' in net.__class__.__name__:
|
141 |
+
# print(prefix + f'Found conv with kernel size: {net.kernel_size}')
|
142 |
+
# net.old_forward = net.forward
|
143 |
+
# net.forward = conv_forward(net)
|
144 |
+
for name, subnet in net.named_children():
|
145 |
+
if net.__class__.__name__ == 'Attention': # spatial Transformer layer
|
146 |
+
net.forward = ca_forward(net, place_in_unet)
|
147 |
+
return count + 1
|
148 |
+
elif hasattr(net, 'children'):
|
149 |
+
count = register_editor(subnet, count, place_in_unet, prefix=prefix + '\t')
|
150 |
+
return count
|
151 |
+
|
152 |
+
cross_att_count = 0
|
153 |
+
for net_name, net in model.unet.named_children():
|
154 |
+
if "down" in net_name:
|
155 |
+
cross_att_count += register_editor(net, 0, "down")
|
156 |
+
#print(f'Down number of attention layers {cross_att_count}!')
|
157 |
+
elif "mid" in net_name:
|
158 |
+
cross_att_count += register_editor(net, 0, "mid")
|
159 |
+
#print(f'Mid number of attention layers {cross_att_count}!')
|
160 |
+
elif "up" in net_name:
|
161 |
+
cross_att_count += register_editor(net, 0, "up")
|
162 |
+
#print(f'Up number of attention layers {cross_att_count}!')
|
163 |
+
print(f'Number of attention layers {cross_att_count}!')
|
164 |
+
editor.num_att_layers = cross_att_count
|
165 |
+
editor.model = model
|
166 |
+
model.editor = editor
|
167 |
+
|
168 |
+
|
169 |
+
def regiter_attention_editor_ldm(model, editor: AttentionBase):
|
170 |
+
"""
|
171 |
+
Register a attention editor to Stable Diffusion model, refer from [Prompt-to-Prompt]
|
172 |
+
"""
|
173 |
+
def ca_forward(self, place_in_unet):
|
174 |
+
def forward(x, encoder_hidden_states=None, attention_mask=None, context=None, mask=None):
|
175 |
+
"""
|
176 |
+
The attention is similar to the original implementation of LDM CrossAttention class
|
177 |
+
except adding some modifications on the attention
|
178 |
+
"""
|
179 |
+
if encoder_hidden_states is not None:
|
180 |
+
context = encoder_hidden_states
|
181 |
+
if attention_mask is not None:
|
182 |
+
mask = attention_mask
|
183 |
+
|
184 |
+
to_out = self.to_out
|
185 |
+
if isinstance(to_out, nn.modules.container.ModuleList):
|
186 |
+
to_out = self.to_out[0]
|
187 |
+
else:
|
188 |
+
to_out = self.to_out
|
189 |
+
|
190 |
+
h = self.heads
|
191 |
+
q = self.to_q(x)
|
192 |
+
is_cross = context is not None
|
193 |
+
context = context if is_cross else x
|
194 |
+
k = self.to_k(context)
|
195 |
+
v = self.to_v(context)
|
196 |
+
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v))
|
197 |
+
|
198 |
+
sim = torch.einsum('b i d, b j d -> b i j', q, k) * self.scale
|
199 |
+
|
200 |
+
if mask is not None:
|
201 |
+
mask = rearrange(mask, 'b ... -> b (...)')
|
202 |
+
max_neg_value = -torch.finfo(sim.dtype).max
|
203 |
+
mask = repeat(mask, 'b j -> (b h) () j', h=h)
|
204 |
+
mask = mask[:, None, :].repeat(h, 1, 1)
|
205 |
+
sim.masked_fill_(~mask, max_neg_value)
|
206 |
+
|
207 |
+
attn = sim.softmax(dim=-1)
|
208 |
+
# the only difference
|
209 |
+
out = editor(
|
210 |
+
q, k, v, sim, attn, is_cross, place_in_unet,
|
211 |
+
self.heads, scale=self.scale)
|
212 |
+
|
213 |
+
return to_out(out)
|
214 |
+
|
215 |
+
return forward
|
216 |
+
|
217 |
+
def register_editor(net, count, place_in_unet):
|
218 |
+
for name, subnet in net.named_children():
|
219 |
+
if net.__class__.__name__ == 'CrossAttention': # spatial Transformer layer
|
220 |
+
net.forward = ca_forward(net, place_in_unet)
|
221 |
+
return count + 1
|
222 |
+
elif hasattr(net, 'children'):
|
223 |
+
count = register_editor(subnet, count, place_in_unet)
|
224 |
+
return count
|
225 |
+
|
226 |
+
cross_att_count = 0
|
227 |
+
for net_name, net in model.model.diffusion_model.named_children():
|
228 |
+
if "input" in net_name:
|
229 |
+
cross_att_count += register_editor(net, 0, "input")
|
230 |
+
elif "middle" in net_name:
|
231 |
+
cross_att_count += register_editor(net, 0, "middle")
|
232 |
+
elif "output" in net_name:
|
233 |
+
cross_att_count += register_editor(net, 0, "output")
|
234 |
+
editor.num_att_layers = cross_att_count
|
pipeline_stable_diffusion_xl_opt.py
ADDED
@@ -0,0 +1,968 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 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 |
+
import os
|
17 |
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
18 |
+
|
19 |
+
import torch
|
20 |
+
from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer
|
21 |
+
|
22 |
+
from diffusers.image_processor import VaeImageProcessor
|
23 |
+
from diffusers.loaders import FromSingleFileMixin, LoraLoaderMixin, TextualInversionLoaderMixin
|
24 |
+
from diffusers.models import AutoencoderKL, UNet2DConditionModel
|
25 |
+
from diffusers.models.attention_processor import (
|
26 |
+
AttnProcessor2_0,
|
27 |
+
LoRAAttnProcessor2_0,
|
28 |
+
LoRAXFormersAttnProcessor,
|
29 |
+
XFormersAttnProcessor,
|
30 |
+
)
|
31 |
+
from diffusers.schedulers import KarrasDiffusionSchedulers
|
32 |
+
from diffusers.utils import (
|
33 |
+
is_accelerate_available,
|
34 |
+
is_accelerate_version,
|
35 |
+
is_invisible_watermark_available,
|
36 |
+
logging,
|
37 |
+
randn_tensor,
|
38 |
+
replace_example_docstring,
|
39 |
+
)
|
40 |
+
from diffusers.pipeline_utils import DiffusionPipeline
|
41 |
+
from diffusers.pipelines.stable_diffusion_xl import StableDiffusionXLPipelineOutput
|
42 |
+
|
43 |
+
|
44 |
+
if is_invisible_watermark_available():
|
45 |
+
from .watermark import StableDiffusionXLWatermarker
|
46 |
+
|
47 |
+
|
48 |
+
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
49 |
+
|
50 |
+
EXAMPLE_DOC_STRING = """
|
51 |
+
Examples:
|
52 |
+
```py
|
53 |
+
>>> import torch
|
54 |
+
>>> from diffusers import StableDiffusionXLPipeline
|
55 |
+
|
56 |
+
>>> pipe = StableDiffusionXLPipeline.from_pretrained(
|
57 |
+
... "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16
|
58 |
+
... )
|
59 |
+
>>> pipe = pipe.to("cuda")
|
60 |
+
|
61 |
+
>>> prompt = "a photo of an astronaut riding a horse on mars"
|
62 |
+
>>> image = pipe(prompt).images[0]
|
63 |
+
```
|
64 |
+
"""
|
65 |
+
|
66 |
+
|
67 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.rescale_noise_cfg
|
68 |
+
def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0):
|
69 |
+
"""
|
70 |
+
Rescale `noise_cfg` according to `guidance_rescale`. Based on findings of [Common Diffusion Noise Schedules and
|
71 |
+
Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). See Section 3.4
|
72 |
+
"""
|
73 |
+
std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True)
|
74 |
+
std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True)
|
75 |
+
# rescale the results from guidance (fixes overexposure)
|
76 |
+
noise_pred_rescaled = noise_cfg * (std_text / std_cfg)
|
77 |
+
# mix with the original results from guidance by factor guidance_rescale to avoid "plain looking" images
|
78 |
+
noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg
|
79 |
+
return noise_cfg
|
80 |
+
|
81 |
+
|
82 |
+
class StableDiffusionXLPipeline(DiffusionPipeline, FromSingleFileMixin, LoraLoaderMixin):
|
83 |
+
r"""
|
84 |
+
Pipeline for text-to-image generation using Stable Diffusion XL.
|
85 |
+
|
86 |
+
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the
|
87 |
+
library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.)
|
88 |
+
|
89 |
+
In addition the pipeline inherits the following loading methods:
|
90 |
+
- *LoRA*: [`StableDiffusionXLPipeline.load_lora_weights`]
|
91 |
+
- *Ckpt*: [`loaders.FromSingleFileMixin.from_single_file`]
|
92 |
+
|
93 |
+
as well as the following saving methods:
|
94 |
+
- *LoRA*: [`loaders.StableDiffusionXLPipeline.save_lora_weights`]
|
95 |
+
|
96 |
+
Args:
|
97 |
+
vae ([`AutoencoderKL`]):
|
98 |
+
Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations.
|
99 |
+
text_encoder ([`CLIPTextModel`]):
|
100 |
+
Frozen text-encoder. Stable Diffusion XL uses the text portion of
|
101 |
+
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically
|
102 |
+
the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant.
|
103 |
+
text_encoder_2 ([` CLIPTextModelWithProjection`]):
|
104 |
+
Second frozen text-encoder. Stable Diffusion XL uses the text and pool portion of
|
105 |
+
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModelWithProjection),
|
106 |
+
specifically the
|
107 |
+
[laion/CLIP-ViT-bigG-14-laion2B-39B-b160k](https://huggingface.co/laion/CLIP-ViT-bigG-14-laion2B-39B-b160k)
|
108 |
+
variant.
|
109 |
+
tokenizer (`CLIPTokenizer`):
|
110 |
+
Tokenizer of class
|
111 |
+
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
|
112 |
+
tokenizer_2 (`CLIPTokenizer`):
|
113 |
+
Second Tokenizer of class
|
114 |
+
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
|
115 |
+
unet ([`UNet2DConditionModel`]): Conditional U-Net architecture to denoise the encoded image latents.
|
116 |
+
scheduler ([`SchedulerMixin`]):
|
117 |
+
A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of
|
118 |
+
[`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`].
|
119 |
+
"""
|
120 |
+
|
121 |
+
def __init__(
|
122 |
+
self,
|
123 |
+
vae: AutoencoderKL,
|
124 |
+
text_encoder: CLIPTextModel,
|
125 |
+
text_encoder_2: CLIPTextModelWithProjection,
|
126 |
+
tokenizer: CLIPTokenizer,
|
127 |
+
tokenizer_2: CLIPTokenizer,
|
128 |
+
unet: UNet2DConditionModel,
|
129 |
+
scheduler: KarrasDiffusionSchedulers,
|
130 |
+
force_zeros_for_empty_prompt: bool = True,
|
131 |
+
add_watermarker: Optional[bool] = None,
|
132 |
+
):
|
133 |
+
super().__init__()
|
134 |
+
|
135 |
+
self.register_modules(
|
136 |
+
vae=vae,
|
137 |
+
text_encoder=text_encoder,
|
138 |
+
text_encoder_2=text_encoder_2,
|
139 |
+
tokenizer=tokenizer,
|
140 |
+
tokenizer_2=tokenizer_2,
|
141 |
+
unet=unet,
|
142 |
+
scheduler=scheduler,
|
143 |
+
)
|
144 |
+
self.register_to_config(force_zeros_for_empty_prompt=force_zeros_for_empty_prompt)
|
145 |
+
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
|
146 |
+
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
|
147 |
+
self.default_sample_size = self.unet.config.sample_size
|
148 |
+
|
149 |
+
add_watermarker = add_watermarker if add_watermarker is not None else is_invisible_watermark_available()
|
150 |
+
|
151 |
+
if add_watermarker:
|
152 |
+
self.watermark = StableDiffusionXLWatermarker()
|
153 |
+
else:
|
154 |
+
self.watermark = None
|
155 |
+
|
156 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_slicing
|
157 |
+
def enable_vae_slicing(self):
|
158 |
+
r"""
|
159 |
+
Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to
|
160 |
+
compute decoding in several steps. This is useful to save some memory and allow larger batch sizes.
|
161 |
+
"""
|
162 |
+
self.vae.enable_slicing()
|
163 |
+
|
164 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_slicing
|
165 |
+
def disable_vae_slicing(self):
|
166 |
+
r"""
|
167 |
+
Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to
|
168 |
+
computing decoding in one step.
|
169 |
+
"""
|
170 |
+
self.vae.disable_slicing()
|
171 |
+
|
172 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_tiling
|
173 |
+
def enable_vae_tiling(self):
|
174 |
+
r"""
|
175 |
+
Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to
|
176 |
+
compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow
|
177 |
+
processing larger images.
|
178 |
+
"""
|
179 |
+
self.vae.enable_tiling()
|
180 |
+
|
181 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_tiling
|
182 |
+
def disable_vae_tiling(self):
|
183 |
+
r"""
|
184 |
+
Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to
|
185 |
+
computing decoding in one step.
|
186 |
+
"""
|
187 |
+
self.vae.disable_tiling()
|
188 |
+
|
189 |
+
def enable_model_cpu_offload(self, gpu_id=0):
|
190 |
+
r"""
|
191 |
+
Offloads all models to CPU using accelerate, reducing memory usage with a low impact on performance. Compared
|
192 |
+
to `enable_sequential_cpu_offload`, this method moves one whole model at a time to the GPU when its `forward`
|
193 |
+
method is called, and the model remains in GPU until the next model runs. Memory savings are lower than with
|
194 |
+
`enable_sequential_cpu_offload`, but performance is much better due to the iterative execution of the `unet`.
|
195 |
+
"""
|
196 |
+
if is_accelerate_available() and is_accelerate_version(">=", "0.17.0.dev0"):
|
197 |
+
from accelerate import cpu_offload_with_hook
|
198 |
+
else:
|
199 |
+
raise ImportError("`enable_model_cpu_offload` requires `accelerate v0.17.0` or higher.")
|
200 |
+
|
201 |
+
device = torch.device(f"cuda:{gpu_id}")
|
202 |
+
|
203 |
+
if self.device.type != "cpu":
|
204 |
+
self.to("cpu", silence_dtype_warnings=True)
|
205 |
+
torch.cuda.empty_cache() # otherwise we don't see the memory savings (but they probably exist)
|
206 |
+
|
207 |
+
model_sequence = (
|
208 |
+
[self.text_encoder, self.text_encoder_2] if self.text_encoder is not None else [self.text_encoder_2]
|
209 |
+
)
|
210 |
+
model_sequence.extend([self.unet, self.vae])
|
211 |
+
|
212 |
+
hook = None
|
213 |
+
for cpu_offloaded_model in model_sequence:
|
214 |
+
_, hook = cpu_offload_with_hook(cpu_offloaded_model, device, prev_module_hook=hook)
|
215 |
+
|
216 |
+
# We'll offload the last model manually.
|
217 |
+
self.final_offload_hook = hook
|
218 |
+
|
219 |
+
def encode_prompt(
|
220 |
+
self,
|
221 |
+
prompt: str,
|
222 |
+
prompt_2: Optional[str] = None,
|
223 |
+
device: Optional[torch.device] = None,
|
224 |
+
num_images_per_prompt: int = 1,
|
225 |
+
do_classifier_free_guidance: bool = True,
|
226 |
+
negative_prompt: Optional[str] = None,
|
227 |
+
negative_prompt_2: Optional[str] = None,
|
228 |
+
prompt_embeds: Optional[torch.FloatTensor] = None,
|
229 |
+
negative_prompt_embeds: Optional[torch.FloatTensor] = None,
|
230 |
+
pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
|
231 |
+
negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
|
232 |
+
lora_scale: Optional[float] = None,
|
233 |
+
):
|
234 |
+
r"""
|
235 |
+
Encodes the prompt into text encoder hidden states.
|
236 |
+
|
237 |
+
Args:
|
238 |
+
prompt (`str` or `List[str]`, *optional*):
|
239 |
+
prompt to be encoded
|
240 |
+
prompt_2 (`str` or `List[str]`, *optional*):
|
241 |
+
The prompt or prompts to be sent to the `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is
|
242 |
+
used in both text-encoders
|
243 |
+
device: (`torch.device`):
|
244 |
+
torch device
|
245 |
+
num_images_per_prompt (`int`):
|
246 |
+
number of images that should be generated per prompt
|
247 |
+
do_classifier_free_guidance (`bool`):
|
248 |
+
whether to use classifier free guidance or not
|
249 |
+
negative_prompt (`str` or `List[str]`, *optional*):
|
250 |
+
The prompt or prompts not to guide the image generation. If not defined, one has to pass
|
251 |
+
`negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
|
252 |
+
less than `1`).
|
253 |
+
negative_prompt_2 (`str` or `List[str]`, *optional*):
|
254 |
+
The prompt or prompts not to guide the image generation to be sent to `tokenizer_2` and
|
255 |
+
`text_encoder_2`. If not defined, `negative_prompt` is used in both text-encoders
|
256 |
+
prompt_embeds (`torch.FloatTensor`, *optional*):
|
257 |
+
Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
|
258 |
+
provided, text embeddings will be generated from `prompt` input argument.
|
259 |
+
negative_prompt_embeds (`torch.FloatTensor`, *optional*):
|
260 |
+
Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
|
261 |
+
weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
|
262 |
+
argument.
|
263 |
+
pooled_prompt_embeds (`torch.FloatTensor`, *optional*):
|
264 |
+
Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting.
|
265 |
+
If not provided, pooled text embeddings will be generated from `prompt` input argument.
|
266 |
+
negative_pooled_prompt_embeds (`torch.FloatTensor`, *optional*):
|
267 |
+
Pre-generated negative pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
|
268 |
+
weighting. If not provided, pooled negative_prompt_embeds will be generated from `negative_prompt`
|
269 |
+
input argument.
|
270 |
+
lora_scale (`float`, *optional*):
|
271 |
+
A lora scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded.
|
272 |
+
"""
|
273 |
+
device = device or self._execution_device
|
274 |
+
|
275 |
+
# set lora scale so that monkey patched LoRA
|
276 |
+
# function of text encoder can correctly access it
|
277 |
+
if lora_scale is not None and isinstance(self, LoraLoaderMixin):
|
278 |
+
self._lora_scale = lora_scale
|
279 |
+
|
280 |
+
if prompt is not None and isinstance(prompt, str):
|
281 |
+
batch_size = 1
|
282 |
+
elif prompt is not None and isinstance(prompt, list):
|
283 |
+
batch_size = len(prompt)
|
284 |
+
else:
|
285 |
+
batch_size = prompt_embeds.shape[0]
|
286 |
+
|
287 |
+
# Define tokenizers and text encoders
|
288 |
+
tokenizers = [self.tokenizer, self.tokenizer_2] if self.tokenizer is not None else [self.tokenizer_2]
|
289 |
+
text_encoders = (
|
290 |
+
[self.text_encoder, self.text_encoder_2] if self.text_encoder is not None else [self.text_encoder_2]
|
291 |
+
)
|
292 |
+
|
293 |
+
if prompt_embeds is None:
|
294 |
+
prompt_2 = prompt_2 or prompt
|
295 |
+
# textual inversion: procecss multi-vector tokens if necessary
|
296 |
+
prompt_embeds_list = []
|
297 |
+
prompts = [prompt, prompt_2]
|
298 |
+
for prompt, tokenizer, text_encoder in zip(prompts, tokenizers, text_encoders):
|
299 |
+
if isinstance(self, TextualInversionLoaderMixin):
|
300 |
+
prompt = self.maybe_convert_prompt(prompt, tokenizer)
|
301 |
+
|
302 |
+
text_inputs = tokenizer(
|
303 |
+
prompt,
|
304 |
+
padding="max_length",
|
305 |
+
max_length=tokenizer.model_max_length,
|
306 |
+
truncation=True,
|
307 |
+
return_tensors="pt",
|
308 |
+
)
|
309 |
+
|
310 |
+
text_input_ids = text_inputs.input_ids
|
311 |
+
untruncated_ids = tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
|
312 |
+
|
313 |
+
if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(
|
314 |
+
text_input_ids, untruncated_ids
|
315 |
+
):
|
316 |
+
removed_text = tokenizer.batch_decode(untruncated_ids[:, tokenizer.model_max_length - 1 : -1])
|
317 |
+
logger.warning(
|
318 |
+
"The following part of your input was truncated because CLIP can only handle sequences up to"
|
319 |
+
f" {tokenizer.model_max_length} tokens: {removed_text}"
|
320 |
+
)
|
321 |
+
|
322 |
+
prompt_embeds = text_encoder(
|
323 |
+
text_input_ids.to(device),
|
324 |
+
output_hidden_states=True,
|
325 |
+
)
|
326 |
+
|
327 |
+
# We are only ALWAYS interested in the pooled output of the final text encoder
|
328 |
+
pooled_prompt_embeds = prompt_embeds[0]
|
329 |
+
### TODO: remove
|
330 |
+
null_text_inputs = tokenizer(
|
331 |
+
['a realistic photo of an empty background'] * batch_size,
|
332 |
+
padding="max_length",
|
333 |
+
max_length=tokenizer.model_max_length,
|
334 |
+
truncation=True,
|
335 |
+
return_tensors="pt",
|
336 |
+
)
|
337 |
+
null_input_ids = null_text_inputs.input_ids
|
338 |
+
null_prompt_embeds = text_encoder(
|
339 |
+
null_input_ids.to(device),
|
340 |
+
output_hidden_states=True,
|
341 |
+
)
|
342 |
+
pooled_prompt_embeds = null_prompt_embeds[0]
|
343 |
+
### TODO: remove
|
344 |
+
prompt_embeds = prompt_embeds.hidden_states[-2]
|
345 |
+
|
346 |
+
prompt_embeds_list.append(prompt_embeds)
|
347 |
+
|
348 |
+
prompt_embeds = torch.concat(prompt_embeds_list, dim=-1)
|
349 |
+
|
350 |
+
# get unconditional embeddings for classifier free guidance
|
351 |
+
zero_out_negative_prompt = negative_prompt is None and self.config.force_zeros_for_empty_prompt
|
352 |
+
if do_classifier_free_guidance and negative_prompt_embeds is None and zero_out_negative_prompt:
|
353 |
+
negative_prompt_embeds = torch.zeros_like(prompt_embeds)
|
354 |
+
negative_pooled_prompt_embeds = torch.zeros_like(pooled_prompt_embeds)
|
355 |
+
elif do_classifier_free_guidance and negative_prompt_embeds is None:
|
356 |
+
negative_prompt = negative_prompt or ""
|
357 |
+
negative_prompt_2 = negative_prompt_2 or negative_prompt
|
358 |
+
|
359 |
+
uncond_tokens: List[str]
|
360 |
+
if prompt is not None and type(prompt) is not type(negative_prompt):
|
361 |
+
raise TypeError(
|
362 |
+
f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !="
|
363 |
+
f" {type(prompt)}."
|
364 |
+
)
|
365 |
+
elif isinstance(negative_prompt, str):
|
366 |
+
uncond_tokens = [negative_prompt, negative_prompt_2]
|
367 |
+
elif batch_size != len(negative_prompt):
|
368 |
+
raise ValueError(
|
369 |
+
f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"
|
370 |
+
f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"
|
371 |
+
" the batch size of `prompt`."
|
372 |
+
)
|
373 |
+
else:
|
374 |
+
uncond_tokens = [negative_prompt, negative_prompt_2]
|
375 |
+
|
376 |
+
negative_prompt_embeds_list = []
|
377 |
+
for negative_prompt, tokenizer, text_encoder in zip(uncond_tokens, tokenizers, text_encoders):
|
378 |
+
if isinstance(self, TextualInversionLoaderMixin):
|
379 |
+
negative_prompt = self.maybe_convert_prompt(negative_prompt, tokenizer)
|
380 |
+
|
381 |
+
max_length = prompt_embeds.shape[1]
|
382 |
+
uncond_input = tokenizer(
|
383 |
+
negative_prompt,
|
384 |
+
padding="max_length",
|
385 |
+
max_length=max_length,
|
386 |
+
truncation=True,
|
387 |
+
return_tensors="pt",
|
388 |
+
)
|
389 |
+
|
390 |
+
negative_prompt_embeds = text_encoder(
|
391 |
+
uncond_input.input_ids.to(device),
|
392 |
+
output_hidden_states=True,
|
393 |
+
)
|
394 |
+
# We are only ALWAYS interested in the pooled output of the final text encoder
|
395 |
+
negative_pooled_prompt_embeds = negative_prompt_embeds[0]
|
396 |
+
negative_prompt_embeds = negative_prompt_embeds.hidden_states[-2]
|
397 |
+
|
398 |
+
negative_prompt_embeds_list.append(negative_prompt_embeds)
|
399 |
+
|
400 |
+
negative_prompt_embeds = torch.concat(negative_prompt_embeds_list, dim=-1)
|
401 |
+
|
402 |
+
prompt_embeds = prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device)
|
403 |
+
bs_embed, seq_len, _ = prompt_embeds.shape
|
404 |
+
# duplicate text embeddings for each generation per prompt, using mps friendly method
|
405 |
+
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
|
406 |
+
prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)
|
407 |
+
|
408 |
+
if do_classifier_free_guidance:
|
409 |
+
# duplicate unconditional embeddings for each generation per prompt, using mps friendly method
|
410 |
+
seq_len = negative_prompt_embeds.shape[1]
|
411 |
+
negative_prompt_embeds = negative_prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device)
|
412 |
+
negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)
|
413 |
+
negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
|
414 |
+
|
415 |
+
pooled_prompt_embeds = pooled_prompt_embeds.repeat(1, num_images_per_prompt).view(
|
416 |
+
bs_embed * num_images_per_prompt, -1
|
417 |
+
)
|
418 |
+
if do_classifier_free_guidance:
|
419 |
+
negative_pooled_prompt_embeds = negative_pooled_prompt_embeds.repeat(1, num_images_per_prompt).view(
|
420 |
+
bs_embed * num_images_per_prompt, -1
|
421 |
+
)
|
422 |
+
|
423 |
+
return prompt_embeds, negative_prompt_embeds, pooled_prompt_embeds, negative_pooled_prompt_embeds
|
424 |
+
|
425 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs
|
426 |
+
def prepare_extra_step_kwargs(self, generator, eta):
|
427 |
+
# prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
|
428 |
+
# eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
|
429 |
+
# eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
|
430 |
+
# and should be between [0, 1]
|
431 |
+
|
432 |
+
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
|
433 |
+
extra_step_kwargs = {}
|
434 |
+
if accepts_eta:
|
435 |
+
extra_step_kwargs["eta"] = eta
|
436 |
+
|
437 |
+
# check if the scheduler accepts generator
|
438 |
+
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
|
439 |
+
if accepts_generator:
|
440 |
+
extra_step_kwargs["generator"] = generator
|
441 |
+
return extra_step_kwargs
|
442 |
+
|
443 |
+
def check_inputs(
|
444 |
+
self,
|
445 |
+
prompt,
|
446 |
+
prompt_2,
|
447 |
+
height,
|
448 |
+
width,
|
449 |
+
callback_steps,
|
450 |
+
negative_prompt=None,
|
451 |
+
negative_prompt_2=None,
|
452 |
+
prompt_embeds=None,
|
453 |
+
negative_prompt_embeds=None,
|
454 |
+
pooled_prompt_embeds=None,
|
455 |
+
negative_pooled_prompt_embeds=None,
|
456 |
+
):
|
457 |
+
if height % 8 != 0 or width % 8 != 0:
|
458 |
+
raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
|
459 |
+
|
460 |
+
if (callback_steps is None) or (
|
461 |
+
callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)
|
462 |
+
):
|
463 |
+
raise ValueError(
|
464 |
+
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
|
465 |
+
f" {type(callback_steps)}."
|
466 |
+
)
|
467 |
+
|
468 |
+
if prompt is not None and prompt_embeds is not None:
|
469 |
+
raise ValueError(
|
470 |
+
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
|
471 |
+
" only forward one of the two."
|
472 |
+
)
|
473 |
+
elif prompt_2 is not None and prompt_embeds is not None:
|
474 |
+
raise ValueError(
|
475 |
+
f"Cannot forward both `prompt_2`: {prompt_2} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
|
476 |
+
" only forward one of the two."
|
477 |
+
)
|
478 |
+
elif prompt is None and prompt_embeds is None:
|
479 |
+
raise ValueError(
|
480 |
+
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
|
481 |
+
)
|
482 |
+
elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
|
483 |
+
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
|
484 |
+
elif prompt_2 is not None and (not isinstance(prompt_2, str) and not isinstance(prompt_2, list)):
|
485 |
+
raise ValueError(f"`prompt_2` has to be of type `str` or `list` but is {type(prompt_2)}")
|
486 |
+
|
487 |
+
if negative_prompt is not None and negative_prompt_embeds is not None:
|
488 |
+
raise ValueError(
|
489 |
+
f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
|
490 |
+
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
|
491 |
+
)
|
492 |
+
elif negative_prompt_2 is not None and negative_prompt_embeds is not None:
|
493 |
+
raise ValueError(
|
494 |
+
f"Cannot forward both `negative_prompt_2`: {negative_prompt_2} and `negative_prompt_embeds`:"
|
495 |
+
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
|
496 |
+
)
|
497 |
+
|
498 |
+
if prompt_embeds is not None and negative_prompt_embeds is not None:
|
499 |
+
if prompt_embeds.shape != negative_prompt_embeds.shape:
|
500 |
+
raise ValueError(
|
501 |
+
"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
|
502 |
+
f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
|
503 |
+
f" {negative_prompt_embeds.shape}."
|
504 |
+
)
|
505 |
+
|
506 |
+
if prompt_embeds is not None and pooled_prompt_embeds is None:
|
507 |
+
raise ValueError(
|
508 |
+
"If `prompt_embeds` are provided, `pooled_prompt_embeds` also have to be passed. Make sure to generate `pooled_prompt_embeds` from the same text encoder that was used to generate `prompt_embeds`."
|
509 |
+
)
|
510 |
+
|
511 |
+
if negative_prompt_embeds is not None and negative_pooled_prompt_embeds is None:
|
512 |
+
raise ValueError(
|
513 |
+
"If `negative_prompt_embeds` are provided, `negative_pooled_prompt_embeds` also have to be passed. Make sure to generate `negative_pooled_prompt_embeds` from the same text encoder that was used to generate `negative_prompt_embeds`."
|
514 |
+
)
|
515 |
+
|
516 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents
|
517 |
+
def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None):
|
518 |
+
shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor)
|
519 |
+
if isinstance(generator, list) and len(generator) != batch_size:
|
520 |
+
raise ValueError(
|
521 |
+
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
|
522 |
+
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
523 |
+
)
|
524 |
+
|
525 |
+
if latents is None:
|
526 |
+
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
527 |
+
else:
|
528 |
+
latents = latents.to(device)
|
529 |
+
|
530 |
+
# scale the initial noise by the standard deviation required by the scheduler
|
531 |
+
latents = latents * self.scheduler.init_noise_sigma
|
532 |
+
return latents
|
533 |
+
|
534 |
+
def _get_add_time_ids(self, original_size, crops_coords_top_left, target_size, dtype):
|
535 |
+
add_time_ids = list(original_size + crops_coords_top_left + target_size)
|
536 |
+
|
537 |
+
passed_add_embed_dim = (
|
538 |
+
self.unet.config.addition_time_embed_dim * len(add_time_ids) + self.text_encoder_2.config.projection_dim
|
539 |
+
)
|
540 |
+
expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features
|
541 |
+
|
542 |
+
if expected_add_embed_dim != passed_add_embed_dim:
|
543 |
+
raise ValueError(
|
544 |
+
f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. The model has an incorrect config. Please check `unet.config.time_embedding_type` and `text_encoder_2.config.projection_dim`."
|
545 |
+
)
|
546 |
+
|
547 |
+
add_time_ids = torch.tensor([add_time_ids], dtype=dtype)
|
548 |
+
return add_time_ids
|
549 |
+
|
550 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_upscale.StableDiffusionUpscalePipeline.upcast_vae
|
551 |
+
def upcast_vae(self):
|
552 |
+
dtype = self.vae.dtype
|
553 |
+
self.vae.to(dtype=torch.float32)
|
554 |
+
use_torch_2_0_or_xformers = isinstance(
|
555 |
+
self.vae.decoder.mid_block.attentions[0].processor,
|
556 |
+
(
|
557 |
+
AttnProcessor2_0,
|
558 |
+
XFormersAttnProcessor,
|
559 |
+
LoRAXFormersAttnProcessor,
|
560 |
+
LoRAAttnProcessor2_0,
|
561 |
+
),
|
562 |
+
)
|
563 |
+
# if xformers or torch_2_0 is used attention block does not need
|
564 |
+
# to be in float32 which can save lots of memory
|
565 |
+
if use_torch_2_0_or_xformers:
|
566 |
+
self.vae.post_quant_conv.to(dtype)
|
567 |
+
self.vae.decoder.conv_in.to(dtype)
|
568 |
+
self.vae.decoder.mid_block.to(dtype)
|
569 |
+
|
570 |
+
def update_loss(self, latents, i, t, prompt_embeds, cross_attention_kwargs, add_text_embeds, add_time_ids):
|
571 |
+
def forward_pass(latent_model_input):
|
572 |
+
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
573 |
+
added_cond_kwargs = {"text_embeds": add_text_embeds, "time_ids": add_time_ids}
|
574 |
+
self.unet(
|
575 |
+
latent_model_input,
|
576 |
+
t,
|
577 |
+
encoder_hidden_states=prompt_embeds,
|
578 |
+
cross_attention_kwargs=cross_attention_kwargs,
|
579 |
+
added_cond_kwargs=added_cond_kwargs,
|
580 |
+
return_dict=False,
|
581 |
+
)
|
582 |
+
self.unet.zero_grad()
|
583 |
+
|
584 |
+
return self.editor.update_loss(forward_pass, latents, i)
|
585 |
+
|
586 |
+
@torch.no_grad()
|
587 |
+
@replace_example_docstring(EXAMPLE_DOC_STRING)
|
588 |
+
def __call__(
|
589 |
+
self,
|
590 |
+
prompt: Union[str, List[str]] = None,
|
591 |
+
prompt_2: Optional[Union[str, List[str]]] = None,
|
592 |
+
height: Optional[int] = None,
|
593 |
+
width: Optional[int] = None,
|
594 |
+
num_inference_steps: int = 50,
|
595 |
+
denoising_end: Optional[float] = None,
|
596 |
+
guidance_scale: float = 5.0,
|
597 |
+
negative_prompt: Optional[Union[str, List[str]]] = None,
|
598 |
+
negative_prompt_2: Optional[Union[str, List[str]]] = None,
|
599 |
+
num_images_per_prompt: Optional[int] = 1,
|
600 |
+
eta: float = 0.0,
|
601 |
+
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
|
602 |
+
latents: Optional[torch.FloatTensor] = None,
|
603 |
+
prompt_embeds: Optional[torch.FloatTensor] = None,
|
604 |
+
negative_prompt_embeds: Optional[torch.FloatTensor] = None,
|
605 |
+
pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
|
606 |
+
negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
|
607 |
+
output_type: Optional[str] = "pil",
|
608 |
+
return_dict: bool = True,
|
609 |
+
callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,
|
610 |
+
callback_steps: int = 1,
|
611 |
+
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
612 |
+
guidance_rescale: float = 0.0,
|
613 |
+
original_size: Optional[Tuple[int, int]] = None,
|
614 |
+
crops_coords_top_left: Tuple[int, int] = (0, 0),
|
615 |
+
target_size: Optional[Tuple[int, int]] = None,
|
616 |
+
):
|
617 |
+
r"""
|
618 |
+
Function invoked when calling the pipeline for generation.
|
619 |
+
|
620 |
+
Args:
|
621 |
+
prompt (`str` or `List[str]`, *optional*):
|
622 |
+
The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`.
|
623 |
+
instead.
|
624 |
+
prompt_2 (`str` or `List[str]`, *optional*):
|
625 |
+
The prompt or prompts to be sent to the `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is
|
626 |
+
used in both text-encoders
|
627 |
+
height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
|
628 |
+
The height in pixels of the generated image.
|
629 |
+
width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
|
630 |
+
The width in pixels of the generated image.
|
631 |
+
num_inference_steps (`int`, *optional*, defaults to 50):
|
632 |
+
The number of denoising steps. More denoising steps usually lead to a higher quality image at the
|
633 |
+
expense of slower inference.
|
634 |
+
denoising_end (`float`, *optional*):
|
635 |
+
When specified, determines the fraction (between 0.0 and 1.0) of the total denoising process to be
|
636 |
+
completed before it is intentionally prematurely terminated. As a result, the returned sample will
|
637 |
+
still retain a substantial amount of noise as determined by the discrete timesteps selected by the
|
638 |
+
scheduler. The denoising_end parameter should ideally be utilized when this pipeline forms a part of a
|
639 |
+
"Mixture of Denoisers" multi-pipeline setup, as elaborated in [**Refining the Image
|
640 |
+
Output**](https://huggingface.co/docs/diffusers/api/pipelines/stable_diffusion/stable_diffusion_xl#refining-the-image-output)
|
641 |
+
guidance_scale (`float`, *optional*, defaults to 5.0):
|
642 |
+
Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).
|
643 |
+
`guidance_scale` is defined as `w` of equation 2. of [Imagen
|
644 |
+
Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >
|
645 |
+
1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
|
646 |
+
usually at the expense of lower image quality.
|
647 |
+
negative_prompt (`str` or `List[str]`, *optional*):
|
648 |
+
The prompt or prompts not to guide the image generation. If not defined, one has to pass
|
649 |
+
`negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
|
650 |
+
less than `1`).
|
651 |
+
negative_prompt_2 (`str` or `List[str]`, *optional*):
|
652 |
+
The prompt or prompts not to guide the image generation to be sent to `tokenizer_2` and
|
653 |
+
`text_encoder_2`. If not defined, `negative_prompt` is used in both text-encoders
|
654 |
+
num_images_per_prompt (`int`, *optional*, defaults to 1):
|
655 |
+
The number of images to generate per prompt.
|
656 |
+
eta (`float`, *optional*, defaults to 0.0):
|
657 |
+
Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to
|
658 |
+
[`schedulers.DDIMScheduler`], will be ignored for others.
|
659 |
+
generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
|
660 |
+
One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)
|
661 |
+
to make generation deterministic.
|
662 |
+
latents (`torch.FloatTensor`, *optional*):
|
663 |
+
Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image
|
664 |
+
generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
|
665 |
+
tensor will ge generated by sampling using the supplied random `generator`.
|
666 |
+
prompt_embeds (`torch.FloatTensor`, *optional*):
|
667 |
+
Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
|
668 |
+
provided, text embeddings will be generated from `prompt` input argument.
|
669 |
+
negative_prompt_embeds (`torch.FloatTensor`, *optional*):
|
670 |
+
Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
|
671 |
+
weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
|
672 |
+
argument.
|
673 |
+
pooled_prompt_embeds (`torch.FloatTensor`, *optional*):
|
674 |
+
Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting.
|
675 |
+
If not provided, pooled text embeddings will be generated from `prompt` input argument.
|
676 |
+
negative_pooled_prompt_embeds (`torch.FloatTensor`, *optional*):
|
677 |
+
Pre-generated negative pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
|
678 |
+
weighting. If not provided, pooled negative_prompt_embeds will be generated from `negative_prompt`
|
679 |
+
input argument.
|
680 |
+
output_type (`str`, *optional*, defaults to `"pil"`):
|
681 |
+
The output format of the generate image. Choose between
|
682 |
+
[PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`.
|
683 |
+
return_dict (`bool`, *optional*, defaults to `True`):
|
684 |
+
Whether or not to return a [`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] instead
|
685 |
+
of a plain tuple.
|
686 |
+
callback (`Callable`, *optional*):
|
687 |
+
A function that will be called every `callback_steps` steps during inference. The function will be
|
688 |
+
called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.
|
689 |
+
callback_steps (`int`, *optional*, defaults to 1):
|
690 |
+
The frequency at which the `callback` function will be called. If not specified, the callback will be
|
691 |
+
called at every step.
|
692 |
+
cross_attention_kwargs (`dict`, *optional*):
|
693 |
+
A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
|
694 |
+
`self.processor` in
|
695 |
+
[diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
|
696 |
+
guidance_rescale (`float`, *optional*, defaults to 0.7):
|
697 |
+
Guidance rescale factor proposed by [Common Diffusion Noise Schedules and Sample Steps are
|
698 |
+
Flawed](https://arxiv.org/pdf/2305.08891.pdf) `guidance_scale` is defined as `φ` in equation 16. of
|
699 |
+
[Common Diffusion Noise Schedules and Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf).
|
700 |
+
Guidance rescale factor should fix overexposure when using zero terminal SNR.
|
701 |
+
original_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)):
|
702 |
+
If `original_size` is not the same as `target_size` the image will appear to be down- or upsampled.
|
703 |
+
`original_size` defaults to `(width, height)` if not specified. Part of SDXL's micro-conditioning as
|
704 |
+
explained in section 2.2 of
|
705 |
+
[https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952).
|
706 |
+
crops_coords_top_left (`Tuple[int]`, *optional*, defaults to (0, 0)):
|
707 |
+
`crops_coords_top_left` can be used to generate an image that appears to be "cropped" from the position
|
708 |
+
`crops_coords_top_left` downwards. Favorable, well-centered images are usually achieved by setting
|
709 |
+
`crops_coords_top_left` to (0, 0). Part of SDXL's micro-conditioning as explained in section 2.2 of
|
710 |
+
[https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952).
|
711 |
+
target_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)):
|
712 |
+
For most cases, `target_size` should be set to the desired height and width of the generated image. If
|
713 |
+
not specified it will default to `(width, height)`. Part of SDXL's micro-conditioning as explained in
|
714 |
+
section 2.2 of [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952).
|
715 |
+
|
716 |
+
Examples:
|
717 |
+
|
718 |
+
Returns:
|
719 |
+
[`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] or `tuple`:
|
720 |
+
[`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] if `return_dict` is True, otherwise a
|
721 |
+
`tuple`. When returning a tuple, the first element is a list with the generated images.
|
722 |
+
"""
|
723 |
+
# 0. Default height and width to unet
|
724 |
+
height = height or self.default_sample_size * self.vae_scale_factor
|
725 |
+
width = width or self.default_sample_size * self.vae_scale_factor
|
726 |
+
|
727 |
+
original_size = original_size or (height, width)
|
728 |
+
target_size = target_size or (height, width)
|
729 |
+
|
730 |
+
# 1. Check inputs. Raise error if not correct
|
731 |
+
self.check_inputs(
|
732 |
+
prompt,
|
733 |
+
prompt_2,
|
734 |
+
height,
|
735 |
+
width,
|
736 |
+
callback_steps,
|
737 |
+
negative_prompt,
|
738 |
+
negative_prompt_2,
|
739 |
+
prompt_embeds,
|
740 |
+
negative_prompt_embeds,
|
741 |
+
pooled_prompt_embeds,
|
742 |
+
negative_pooled_prompt_embeds,
|
743 |
+
)
|
744 |
+
|
745 |
+
# 2. Define call parameters
|
746 |
+
if prompt is not None and isinstance(prompt, str):
|
747 |
+
batch_size = 1
|
748 |
+
elif prompt is not None and isinstance(prompt, list):
|
749 |
+
batch_size = len(prompt)
|
750 |
+
else:
|
751 |
+
batch_size = prompt_embeds.shape[0]
|
752 |
+
|
753 |
+
device = self._execution_device
|
754 |
+
|
755 |
+
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
|
756 |
+
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
|
757 |
+
# corresponds to doing no classifier free guidance.
|
758 |
+
do_classifier_free_guidance = guidance_scale > 1.0
|
759 |
+
|
760 |
+
# 3. Encode input prompt
|
761 |
+
text_encoder_lora_scale = (
|
762 |
+
cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None
|
763 |
+
)
|
764 |
+
(
|
765 |
+
prompt_embeds,
|
766 |
+
negative_prompt_embeds,
|
767 |
+
pooled_prompt_embeds,
|
768 |
+
negative_pooled_prompt_embeds,
|
769 |
+
) = self.encode_prompt(
|
770 |
+
prompt=prompt,
|
771 |
+
prompt_2=prompt_2,
|
772 |
+
device=device,
|
773 |
+
num_images_per_prompt=num_images_per_prompt,
|
774 |
+
do_classifier_free_guidance=do_classifier_free_guidance,
|
775 |
+
negative_prompt=negative_prompt,
|
776 |
+
negative_prompt_2=negative_prompt_2,
|
777 |
+
prompt_embeds=prompt_embeds,
|
778 |
+
negative_prompt_embeds=negative_prompt_embeds,
|
779 |
+
pooled_prompt_embeds=pooled_prompt_embeds,
|
780 |
+
negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
|
781 |
+
lora_scale=text_encoder_lora_scale,
|
782 |
+
)
|
783 |
+
|
784 |
+
# 4. Prepare timesteps
|
785 |
+
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
786 |
+
|
787 |
+
timesteps = self.scheduler.timesteps
|
788 |
+
|
789 |
+
# 5. Prepare latent variables
|
790 |
+
num_channels_latents = self.unet.config.in_channels
|
791 |
+
latents = self.prepare_latents(
|
792 |
+
batch_size * num_images_per_prompt,
|
793 |
+
num_channels_latents,
|
794 |
+
height,
|
795 |
+
width,
|
796 |
+
prompt_embeds.dtype,
|
797 |
+
device,
|
798 |
+
generator,
|
799 |
+
latents,
|
800 |
+
)
|
801 |
+
|
802 |
+
# 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline
|
803 |
+
extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
|
804 |
+
|
805 |
+
# 7. Prepare added time ids & embeddings
|
806 |
+
add_text_embeds = pooled_prompt_embeds
|
807 |
+
add_time_ids = self._get_add_time_ids(
|
808 |
+
original_size, crops_coords_top_left, target_size, dtype=prompt_embeds.dtype
|
809 |
+
)
|
810 |
+
|
811 |
+
if do_classifier_free_guidance:
|
812 |
+
prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0)
|
813 |
+
add_text_embeds = torch.cat([negative_pooled_prompt_embeds, add_text_embeds], dim=0)
|
814 |
+
add_time_ids = torch.cat([add_time_ids, add_time_ids], dim=0)
|
815 |
+
|
816 |
+
prompt_embeds = prompt_embeds.to(device)
|
817 |
+
add_text_embeds = add_text_embeds.to(device)
|
818 |
+
add_time_ids = add_time_ids.to(device).repeat(batch_size * num_images_per_prompt, 1)
|
819 |
+
|
820 |
+
# 8. Denoising loop
|
821 |
+
num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
|
822 |
+
|
823 |
+
# 7.1 Apply denoising_end
|
824 |
+
if denoising_end is not None and type(denoising_end) == float and denoising_end > 0 and denoising_end < 1:
|
825 |
+
discrete_timestep_cutoff = int(
|
826 |
+
round(
|
827 |
+
self.scheduler.config.num_train_timesteps
|
828 |
+
- (denoising_end * self.scheduler.config.num_train_timesteps)
|
829 |
+
)
|
830 |
+
)
|
831 |
+
num_inference_steps = len(list(filter(lambda ts: ts >= discrete_timestep_cutoff, timesteps)))
|
832 |
+
timesteps = timesteps[:num_inference_steps]
|
833 |
+
|
834 |
+
latents = latents.half()
|
835 |
+
prompt_embeds = prompt_embeds.half()
|
836 |
+
with self.progress_bar(total=num_inference_steps) as progress_bar:
|
837 |
+
for i, t in enumerate(timesteps):
|
838 |
+
latents = self.update_loss(latents, i, t, prompt_embeds, cross_attention_kwargs, add_text_embeds, add_time_ids)
|
839 |
+
|
840 |
+
# expand the latents if we are doing classifier free guidance
|
841 |
+
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
|
842 |
+
|
843 |
+
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
844 |
+
|
845 |
+
# predict the noise residual
|
846 |
+
added_cond_kwargs = {"text_embeds": add_text_embeds, "time_ids": add_time_ids}
|
847 |
+
noise_pred = self.unet(
|
848 |
+
latent_model_input,
|
849 |
+
t,
|
850 |
+
encoder_hidden_states=prompt_embeds,
|
851 |
+
cross_attention_kwargs=cross_attention_kwargs,
|
852 |
+
added_cond_kwargs=added_cond_kwargs,
|
853 |
+
return_dict=False,
|
854 |
+
)[0]
|
855 |
+
|
856 |
+
# perform guidance
|
857 |
+
if do_classifier_free_guidance:
|
858 |
+
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
|
859 |
+
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
|
860 |
+
|
861 |
+
if do_classifier_free_guidance and guidance_rescale > 0.0:
|
862 |
+
# Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf
|
863 |
+
noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale)
|
864 |
+
|
865 |
+
# compute the previous noisy sample x_t -> x_t-1
|
866 |
+
latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0]
|
867 |
+
|
868 |
+
# call the callback, if provided
|
869 |
+
if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
|
870 |
+
progress_bar.update()
|
871 |
+
if callback is not None and i % callback_steps == 0:
|
872 |
+
callback(i, t, latents)
|
873 |
+
|
874 |
+
# make sure the VAE is in float32 mode, as it overflows in float16
|
875 |
+
if self.vae.dtype == torch.float16 and self.vae.config.force_upcast:
|
876 |
+
self.upcast_vae()
|
877 |
+
latents = latents.to(next(iter(self.vae.post_quant_conv.parameters())).dtype)
|
878 |
+
|
879 |
+
if not output_type == "latent":
|
880 |
+
image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0]
|
881 |
+
else:
|
882 |
+
image = latents
|
883 |
+
return StableDiffusionXLPipelineOutput(images=image)
|
884 |
+
|
885 |
+
# apply watermark if available
|
886 |
+
if self.watermark is not None:
|
887 |
+
image = self.watermark.apply_watermark(image)
|
888 |
+
|
889 |
+
image = self.image_processor.postprocess(image, output_type=output_type)
|
890 |
+
|
891 |
+
# Offload last model to CPU
|
892 |
+
if hasattr(self, "final_offload_hook") and self.final_offload_hook is not None:
|
893 |
+
self.final_offload_hook.offload()
|
894 |
+
|
895 |
+
if not return_dict:
|
896 |
+
return (image,)
|
897 |
+
|
898 |
+
return StableDiffusionXLPipelineOutput(images=image)
|
899 |
+
|
900 |
+
# Overrride to properly handle the loading and unloading of the additional text encoder.
|
901 |
+
def load_lora_weights(self, pretrained_model_name_or_path_or_dict: Union[str, Dict[str, torch.Tensor]], **kwargs):
|
902 |
+
# We could have accessed the unet config from `lora_state_dict()` too. We pass
|
903 |
+
# it here explicitly to be able to tell that it's coming from an SDXL
|
904 |
+
# pipeline.
|
905 |
+
state_dict, network_alphas = self.lora_state_dict(
|
906 |
+
pretrained_model_name_or_path_or_dict,
|
907 |
+
unet_config=self.unet.config,
|
908 |
+
**kwargs,
|
909 |
+
)
|
910 |
+
self.load_lora_into_unet(state_dict, network_alphas=network_alphas, unet=self.unet)
|
911 |
+
|
912 |
+
text_encoder_state_dict = {k: v for k, v in state_dict.items() if "text_encoder." in k}
|
913 |
+
if len(text_encoder_state_dict) > 0:
|
914 |
+
self.load_lora_into_text_encoder(
|
915 |
+
text_encoder_state_dict,
|
916 |
+
network_alphas=network_alphas,
|
917 |
+
text_encoder=self.text_encoder,
|
918 |
+
prefix="text_encoder",
|
919 |
+
lora_scale=self.lora_scale,
|
920 |
+
)
|
921 |
+
|
922 |
+
text_encoder_2_state_dict = {k: v for k, v in state_dict.items() if "text_encoder_2." in k}
|
923 |
+
if len(text_encoder_2_state_dict) > 0:
|
924 |
+
self.load_lora_into_text_encoder(
|
925 |
+
text_encoder_2_state_dict,
|
926 |
+
network_alphas=network_alphas,
|
927 |
+
text_encoder=self.text_encoder_2,
|
928 |
+
prefix="text_encoder_2",
|
929 |
+
lora_scale=self.lora_scale,
|
930 |
+
)
|
931 |
+
|
932 |
+
@classmethod
|
933 |
+
def save_lora_weights(
|
934 |
+
self,
|
935 |
+
save_directory: Union[str, os.PathLike],
|
936 |
+
unet_lora_layers: Dict[str, Union[torch.nn.Module, torch.Tensor]] = None,
|
937 |
+
text_encoder_lora_layers: Dict[str, Union[torch.nn.Module, torch.Tensor]] = None,
|
938 |
+
text_encoder_2_lora_layers: Dict[str, Union[torch.nn.Module, torch.Tensor]] = None,
|
939 |
+
is_main_process: bool = True,
|
940 |
+
weight_name: str = None,
|
941 |
+
save_function: Callable = None,
|
942 |
+
safe_serialization: bool = True,
|
943 |
+
):
|
944 |
+
state_dict = {}
|
945 |
+
|
946 |
+
def pack_weights(layers, prefix):
|
947 |
+
layers_weights = layers.state_dict() if isinstance(layers, torch.nn.Module) else layers
|
948 |
+
layers_state_dict = {f"{prefix}.{module_name}": param for module_name, param in layers_weights.items()}
|
949 |
+
return layers_state_dict
|
950 |
+
|
951 |
+
state_dict.update(pack_weights(unet_lora_layers, "unet"))
|
952 |
+
|
953 |
+
if text_encoder_lora_layers and text_encoder_2_lora_layers:
|
954 |
+
state_dict.update(pack_weights(text_encoder_lora_layers, "text_encoder"))
|
955 |
+
state_dict.update(pack_weights(text_encoder_2_lora_layers, "text_encoder_2"))
|
956 |
+
|
957 |
+
self.write_lora_layers(
|
958 |
+
state_dict=state_dict,
|
959 |
+
save_directory=save_directory,
|
960 |
+
is_main_process=is_main_process,
|
961 |
+
weight_name=weight_name,
|
962 |
+
save_function=save_function,
|
963 |
+
safe_serialization=safe_serialization,
|
964 |
+
)
|
965 |
+
|
966 |
+
def _remove_text_encoder_monkey_patch(self):
|
967 |
+
self._remove_text_encoder_monkey_patch_classmethod(self.text_encoder)
|
968 |
+
self._remove_text_encoder_monkey_patch_classmethod(self.text_encoder_2)
|
requirements.txt
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
accelerate==0.21.0
|
2 |
+
diffusers==0.20.0
|
3 |
+
einops==0.6.1
|
4 |
+
lightning-utilities==0.9.0
|
5 |
+
matplotlib==3.7.3
|
6 |
+
nltk==3.8.1
|
7 |
+
numpy
|
8 |
+
opencv-python==4.8.1.78
|
9 |
+
Pillow==9.4.0
|
10 |
+
pytorch-lightning==2.0.7
|
11 |
+
scikit-image==0.22.0
|
12 |
+
scikit-learn==1.3.1
|
13 |
+
scipy==1.11.2
|
14 |
+
tokenizers==0.13.3
|
15 |
+
torch==2.0.1
|
16 |
+
torch-kmeans==0.2.0
|
17 |
+
torchaudio==2.0.2
|
18 |
+
torchmetrics==1.0.3
|
19 |
+
torchvision==0.15.2
|
20 |
+
tqdm==4.66.1
|
21 |
+
transformers==4.32.0
|
22 |
+
xformers==0.0.21
|
utils.py
ADDED
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from PIL import Image, ImageDraw, ImageFont
|
3 |
+
import cv2
|
4 |
+
from sklearn.decomposition import PCA
|
5 |
+
from torchvision import transforms
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
import torch
|
8 |
+
|
9 |
+
import os
|
10 |
+
|
11 |
+
|
12 |
+
def display_attention_maps(
|
13 |
+
attention_maps,
|
14 |
+
is_cross,
|
15 |
+
num_heads,
|
16 |
+
tokenizer,
|
17 |
+
prompts,
|
18 |
+
dir_name,
|
19 |
+
step,
|
20 |
+
layer,
|
21 |
+
resolution,
|
22 |
+
is_query=False,
|
23 |
+
is_key=False,
|
24 |
+
points=None,
|
25 |
+
image_path=None,
|
26 |
+
):
|
27 |
+
attention_maps = attention_maps.reshape(-1, num_heads, attention_maps.size(-2), attention_maps.size(-1))
|
28 |
+
num_samples = len(attention_maps) // 2
|
29 |
+
attention_type = 'cross' if is_cross else 'self'
|
30 |
+
for i, attention_map in enumerate(attention_maps):
|
31 |
+
if is_query:
|
32 |
+
attention_type = f'{attention_type}_queries'
|
33 |
+
elif is_key:
|
34 |
+
attention_type = f'{attention_type}_keys'
|
35 |
+
|
36 |
+
cond = 'uncond' if i < num_samples else 'cond'
|
37 |
+
i = i % num_samples
|
38 |
+
cur_dir_name = f'{dir_name}/{resolution}/{attention_type}/{layer}/{cond}/{i}'
|
39 |
+
os.makedirs(cur_dir_name, exist_ok=True)
|
40 |
+
|
41 |
+
if is_cross and not is_query:
|
42 |
+
fig = show_cross_attention(attention_map, tokenizer, prompts[i % num_samples])
|
43 |
+
else:
|
44 |
+
fig = show_self_attention(attention_map)
|
45 |
+
if points is not None:
|
46 |
+
point_dir_name = f'{cur_dir_name}/points'
|
47 |
+
os.makedirs(point_dir_name, exist_ok=True)
|
48 |
+
for j, point in enumerate(points):
|
49 |
+
specific_point_dir_name = f'{point_dir_name}/{j}'
|
50 |
+
os.makedirs(specific_point_dir_name, exist_ok=True)
|
51 |
+
point_path = f'{specific_point_dir_name}/{step}.png'
|
52 |
+
point_fig = show_individual_self_attention(attention_map, point, image_path=image_path)
|
53 |
+
point_fig.save(point_path)
|
54 |
+
point_fig.close()
|
55 |
+
|
56 |
+
fig.save(f'{cur_dir_name}/{step}.png')
|
57 |
+
fig.close()
|
58 |
+
|
59 |
+
|
60 |
+
def text_under_image(image: np.ndarray, text: str, text_color: tuple[int, int, int] = (0, 0, 0)):
|
61 |
+
h, w, c = image.shape
|
62 |
+
offset = int(h * .2)
|
63 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
64 |
+
# font = ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf", font_size)
|
65 |
+
text_size = cv2.getTextSize(text, font, 1, 2)[0]
|
66 |
+
lines = text.splitlines()
|
67 |
+
img = np.ones((h + offset + (text_size[1] + 2) * len(lines) - 2, w, c), dtype=np.uint8) * 255
|
68 |
+
img[:h, :w] = image
|
69 |
+
|
70 |
+
for i, line in enumerate(lines):
|
71 |
+
text_size = cv2.getTextSize(line, font, 1, 2)[0]
|
72 |
+
text_x, text_y = ((w - text_size[0]) // 2, h + offset + i * (text_size[1] + 2))
|
73 |
+
cv2.putText(img, line, (text_x, text_y), font, 1, text_color, 2)
|
74 |
+
|
75 |
+
return img
|
76 |
+
|
77 |
+
def view_images(images, num_rows=1, offset_ratio=0.02):
|
78 |
+
if type(images) is list:
|
79 |
+
num_empty = len(images) % num_rows
|
80 |
+
elif images.ndim == 4:
|
81 |
+
num_empty = images.shape[0] % num_rows
|
82 |
+
else:
|
83 |
+
images = [images]
|
84 |
+
num_empty = 0
|
85 |
+
|
86 |
+
empty_images = np.ones(images[0].shape, dtype=np.uint8) * 255
|
87 |
+
images = [image.astype(np.uint8) for image in images] + [empty_images] * num_empty
|
88 |
+
num_items = len(images)
|
89 |
+
|
90 |
+
h, w, c = images[0].shape
|
91 |
+
offset = int(h * offset_ratio)
|
92 |
+
num_cols = num_items // num_rows
|
93 |
+
image_ = np.ones((h * num_rows + offset * (num_rows - 1),
|
94 |
+
w * num_cols + offset * (num_cols - 1), 3), dtype=np.uint8) * 255
|
95 |
+
for i in range(num_rows):
|
96 |
+
for j in range(num_cols):
|
97 |
+
image_[i * (h + offset): i * (h + offset) + h:, j * (w + offset): j * (w + offset) + w] = images[
|
98 |
+
i * num_cols + j]
|
99 |
+
|
100 |
+
return Image.fromarray(image_)
|
101 |
+
|
102 |
+
|
103 |
+
def show_cross_attention(attention_maps, tokenizer, prompt, k_norms=None, v_norms=None):
|
104 |
+
attention_maps = attention_maps.mean(dim=0)
|
105 |
+
res = int(attention_maps.size(-2) ** 0.5)
|
106 |
+
attention_maps = attention_maps.reshape(res, res, -1)
|
107 |
+
tokens = tokenizer.encode(prompt)
|
108 |
+
decoder = tokenizer.decode
|
109 |
+
if k_norms is not None:
|
110 |
+
k_norms = k_norms.round(decimals=1)
|
111 |
+
if v_norms is not None:
|
112 |
+
v_norms = v_norms.round(decimals=1)
|
113 |
+
images = []
|
114 |
+
for i in range(len(tokens) + 5):
|
115 |
+
image = attention_maps[:, :, i]
|
116 |
+
image = 255 * image / image.max()
|
117 |
+
image = image.unsqueeze(-1).expand(*image.shape, 3)
|
118 |
+
image = image.detach().cpu().numpy().astype(np.uint8)
|
119 |
+
image = np.array(Image.fromarray(image).resize((256, 256)))
|
120 |
+
token = tokens[i] if i < len(tokens) else tokens[-1]
|
121 |
+
text = decoder(int(token))
|
122 |
+
if k_norms is not None and v_norms is not None:
|
123 |
+
text += f'\n{k_norms[i]}\n{v_norms[i]})'
|
124 |
+
image = text_under_image(image, text)
|
125 |
+
images.append(image)
|
126 |
+
return view_images(np.stack(images, axis=0))
|
127 |
+
|
128 |
+
|
129 |
+
def show_queries_keys(queries, keys, colors, labels): # [h ni d]
|
130 |
+
num_queries = [query.size(1) for query in queries]
|
131 |
+
num_keys = [key.size(1) for key in keys]
|
132 |
+
h, _, d = queries[0].shape
|
133 |
+
|
134 |
+
data = torch.cat((*queries, *keys), dim=1) # h n d
|
135 |
+
data = data.permute(1, 0, 2) # n h d
|
136 |
+
data = data.reshape(-1, h * d).detach().cpu().numpy()
|
137 |
+
pca = PCA(n_components=2)
|
138 |
+
data = pca.fit_transform(data) # n 2
|
139 |
+
|
140 |
+
query_indices = np.array(num_queries).cumsum()
|
141 |
+
total_num_queries = query_indices[-1]
|
142 |
+
queries = np.split(data[:total_num_queries], query_indices[:-1])
|
143 |
+
if len(num_keys) == 0:
|
144 |
+
keys = [None, ] * len(labels)
|
145 |
+
else:
|
146 |
+
key_indices = np.array(num_keys).cumsum()
|
147 |
+
keys = np.split(data[total_num_queries:], key_indices[:-1])
|
148 |
+
|
149 |
+
fig, ax = plt.subplots()
|
150 |
+
marker_size = plt.rcParams['lines.markersize'] ** 2
|
151 |
+
query_size = int(1.25 * marker_size)
|
152 |
+
key_size = int(2 * marker_size)
|
153 |
+
for query, key, color, label in zip(queries, keys, colors, labels):
|
154 |
+
print(f'# queries of {label}', query.shape[0])
|
155 |
+
ax.scatter(query[:, 0], query[:, 1], s=query_size, color=color, marker='o', label=f'"{label}" queries')
|
156 |
+
|
157 |
+
if key is None:
|
158 |
+
continue
|
159 |
+
|
160 |
+
print(f'# keys of {label}', key.shape[0])
|
161 |
+
keys_label = f'"{label}" key'
|
162 |
+
if key.shape[0] > 1:
|
163 |
+
keys_label += 's'
|
164 |
+
ax.scatter(key[:, 0], key[:, 1], s=key_size, color=color, marker='x', label=keys_label)
|
165 |
+
|
166 |
+
ax.set_axis_off()
|
167 |
+
#ax.set_xlabel('X-axis')
|
168 |
+
#ax.set_ylabel('Y-axis')
|
169 |
+
#ax.set_title('Scatter Plot with Circles and Crosses')
|
170 |
+
|
171 |
+
#ax.legend()
|
172 |
+
return fig
|
173 |
+
|
174 |
+
|
175 |
+
def show_self_attention(attention_maps): # h n m
|
176 |
+
attention_maps = attention_maps.transpose(0, 1).flatten(start_dim=1).detach().cpu().numpy()
|
177 |
+
pca = PCA(n_components=3)
|
178 |
+
pca_img = pca.fit_transform(attention_maps) # N X 3
|
179 |
+
h = w = int(pca_img.shape[0] ** 0.5)
|
180 |
+
pca_img = pca_img.reshape(h, w, 3)
|
181 |
+
pca_img_min = pca_img.min(axis=(0, 1))
|
182 |
+
pca_img_max = pca_img.max(axis=(0, 1))
|
183 |
+
pca_img = (pca_img - pca_img_min) / (pca_img_max - pca_img_min)
|
184 |
+
pca_img = Image.fromarray((pca_img * 255).astype(np.uint8))
|
185 |
+
pca_img = transforms.Resize(256, interpolation=transforms.InterpolationMode.NEAREST)(pca_img)
|
186 |
+
return pca_img
|
187 |
+
|
188 |
+
|
189 |
+
def draw_box(pil_img, bboxes, colors=None, width=5):
|
190 |
+
draw = ImageDraw.Draw(pil_img)
|
191 |
+
#font = ImageFont.truetype('./FreeMono.ttf', 25)
|
192 |
+
w, h = pil_img.size
|
193 |
+
colors = ['red'] * len(bboxes) if colors is None else colors
|
194 |
+
for obj_bbox, color in zip(bboxes, colors):
|
195 |
+
x_0, y_0, x_1, y_1 = obj_bbox[0], obj_bbox[1], obj_bbox[2], obj_bbox[3]
|
196 |
+
draw.rectangle([int(x_0 * w), int(y_0 * h), int(x_1 * w), int(y_1 * h)], outline=color, width=width)
|
197 |
+
return pil_img
|
198 |
+
|
199 |
+
|
200 |
+
def show_individual_self_attention(attn, point, image_path=None):
|
201 |
+
resolution = int(attn.size(-1) ** 0.5)
|
202 |
+
attn = attn.mean(dim=0).reshape(resolution, resolution, resolution, resolution)
|
203 |
+
attn = attn[round(point[1] * resolution), round(point[0] * resolution)]
|
204 |
+
attn = (attn - attn.min()) / (attn.max() - attn.min())
|
205 |
+
image = None if image_path is None else Image.open(image_path).convert('RGB')
|
206 |
+
image = show_image_relevance(attn, image=image)
|
207 |
+
return Image.fromarray(image)
|
208 |
+
|
209 |
+
|
210 |
+
def show_image_relevance(image_relevance, image: Image.Image = None, relevnace_res=16):
|
211 |
+
# create heatmap from mask on image
|
212 |
+
def show_cam_on_image(img, mask):
|
213 |
+
img = img.resize((relevnace_res ** 2, relevnace_res ** 2))
|
214 |
+
img = np.array(img)
|
215 |
+
img = (img - img.min()) / (img.max() - img.min())
|
216 |
+
heatmap = cv2.applyColorMap(np.uint8(255 * mask), cv2.COLORMAP_JET)
|
217 |
+
heatmap = np.float32(heatmap) / 255
|
218 |
+
cam = heatmap + np.float32(img)
|
219 |
+
cam = cam / np.max(cam)
|
220 |
+
return cam
|
221 |
+
|
222 |
+
image_relevance = image_relevance.reshape(1, 1, image_relevance.shape[-1], image_relevance.shape[-1])
|
223 |
+
image_relevance = image_relevance.cuda() # because float16 precision interpolation is not supported on cpu
|
224 |
+
image_relevance = torch.nn.functional.interpolate(image_relevance, size=relevnace_res ** 2, mode='bilinear')
|
225 |
+
image_relevance = image_relevance.cpu() # send it back to cpu
|
226 |
+
image_relevance = (image_relevance - image_relevance.min()) / (image_relevance.max() - image_relevance.min())
|
227 |
+
image_relevance = image_relevance.reshape(relevnace_res ** 2, relevnace_res ** 2)
|
228 |
+
vis = image_relevance if image is None else show_cam_on_image(image, image_relevance)
|
229 |
+
vis = np.uint8(255 * vis)
|
230 |
+
vis = cv2.cvtColor(np.array(vis), cv2.COLOR_RGB2BGR)
|
231 |
+
return vis
|