Skip to content

Commit da5ab51

Browse files
Add GLIGEN implementation (huggingface#4441)
* Add GLIGEN implementation * GLIGEN: Fix code quality check failures * GLIGEN: Fix Import block un-sorted or un-formatted failures * GLIGEN: Fix check_repository_consistency failures * GLIGEN: Add 'PositionNet' to versatile_diffusion/modeling_text_unet.py * GLIGEN: check_repository_consistency: fix 'copy does not match' error * GLIGEN: Fix review comments (1) * GLIGEN: Fix E721 Do not compare types, use `isinstance()` failures * GLIGEN : Ensure _encode_prompt() copy matches to StableDiffusionPipeline * GLIGEN: Fix ruff E721 failure in unidiffuser/test_unidiffuser.py * GLIGEN: doc_builder: restyle pipeline_stable_diffusion_gligen.py * GIGLEN: reset files unrelated to gligen * GLIGEN: Fix documentation comments (1) * GLIGEN: Fix review comments (2) * GLIGEN: Added FastTest * GLIGEN: Fix review comments (3)
1 parent 5175d3d commit da5ab51

14 files changed

Lines changed: 1253 additions & 1 deletion

File tree

docs/source/en/_toctree.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@
267267
title: LDM3D Text-to-(RGB, Depth)
268268
- local: api/pipelines/stable_diffusion/adapter
269269
title: Stable Diffusion T2I-adapter
270+
- local: api/pipelines/stable_diffusion/gligen
271+
title: GLIGEN (Grounded Language-to-Image Generation)
270272
title: Stable Diffusion
271273
- local: api/pipelines/stable_unclip
272274
title: Stable unCLIP
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!--Copyright 2023 The GLIGEN Authors and The HuggingFace Team. All rights reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
4+
the License. You may obtain a copy of the License at
5+
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
8+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
9+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
10+
specific language governing permissions and limitations under the License.
11+
-->
12+
13+
# GLIGEN (Grounded Language-to-Image Generation)
14+
15+
The GLIGEN model was created by researchers and engineers from [University of Wisconsin-Madison, Columbia University, and Microsoft](https://github.com/gligen/GLIGEN). The [`StableDiffusionGLIGENPipeline`] can generate photorealistic images conditioned on grounding inputs. Along with text and bounding boxes, if input images are given, this pipeline can insert objects described by text at the region defined by bounding boxes. Otherwise, it'll generate an image described by the caption/prompt and insert objects described by text at the region defined by bounding boxes. It's trained on COCO2014D and COCO2014CD datasets, and the model uses a frozen CLIP ViT-L/14 text encoder to condition itself on grounding inputs.
16+
17+
The abstract from the [paper](https://huggingface.co/papers/2301.07093) is:
18+
19+
*Large-scale text-to-image diffusion models have made amazing advances. However, the status quo is to use text input alone, which can impede controllability. In this work, we propose GLIGEN, Grounded-Language-to-Image Generation, a novel approach that builds upon and extends the functionality of existing pre-trained text-to-image diffusion models by enabling them to also be conditioned on grounding inputs. To preserve the vast concept knowledge of the pre-trained model, we freeze all of its weights and inject the grounding information into new trainable layers via a gated mechanism. Our model achieves open-world grounded text2img generation with caption and bounding box condition inputs, and the grounding ability generalizes well to novel spatial configurations and concepts. GLIGEN’s zeroshot performance on COCO and LVIS outperforms existing supervised layout-to-image baselines by a large margin.*
20+
21+
<Tip>
22+
23+
Make sure to check out the Stable Diffusion [Tips](https://huggingface.co/docs/diffusers/en/api/pipelines/stable_diffusion/overview#tips) section to learn how to explore the tradeoff between scheduler speed and quality and how to reuse pipeline components efficiently!
24+
25+
If you want to use one of the official checkpoints for a task, explore the [gligen](https://huggingface.co/gligen) Hub organizations!
26+
27+
</Tip>
28+
29+
This pipeline was contributed by [Nikhil Gajendrakumar](https://github.com/nikhil-masterful).
30+
31+
## StableDiffusionGLIGENPipeline
32+
33+
[[autodoc]] StableDiffusionGLIGENPipeline
34+
- all
35+
- __call__
36+
- enable_vae_slicing
37+
- disable_vae_slicing
38+
- enable_vae_tiling
39+
- disable_vae_tiling
40+
- enable_model_cpu_offload
41+
- prepare_latents
42+
- enable_fuser
43+
44+
## StableDiffusionPipelineOutput
45+
46+
[[autodoc]] pipelines.stable_diffusion.StableDiffusionPipelineOutput

src/diffusers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@
171171
StableDiffusionControlNetPipeline,
172172
StableDiffusionDepth2ImgPipeline,
173173
StableDiffusionDiffEditPipeline,
174+
StableDiffusionGLIGENPipeline,
174175
StableDiffusionImageVariationPipeline,
175176
StableDiffusionImg2ImgPipeline,
176177
StableDiffusionInpaintPipeline,

src/diffusers/models/attention.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,38 @@
2424
from .lora import LoRACompatibleLinear
2525

2626

27+
@maybe_allow_in_graph
28+
class GatedSelfAttentionDense(nn.Module):
29+
def __init__(self, query_dim, context_dim, n_heads, d_head):
30+
super().__init__()
31+
32+
# we need a linear projection since we need cat visual feature and obj feature
33+
self.linear = nn.Linear(context_dim, query_dim)
34+
35+
self.attn = Attention(query_dim=query_dim, heads=n_heads, dim_head=d_head)
36+
self.ff = FeedForward(query_dim, activation_fn="geglu")
37+
38+
self.norm1 = nn.LayerNorm(query_dim)
39+
self.norm2 = nn.LayerNorm(query_dim)
40+
41+
self.register_parameter("alpha_attn", nn.Parameter(torch.tensor(0.0)))
42+
self.register_parameter("alpha_dense", nn.Parameter(torch.tensor(0.0)))
43+
44+
self.enabled = True
45+
46+
def forward(self, x, objs):
47+
if not self.enabled:
48+
return x
49+
50+
n_visual = x.shape[1]
51+
objs = self.linear(objs)
52+
53+
x = x + self.alpha_attn.tanh() * self.attn(self.norm1(torch.cat([x, objs], dim=1)))[:, :n_visual, :]
54+
x = x + self.alpha_dense.tanh() * self.ff(self.norm2(x))
55+
56+
return x
57+
58+
2759
@maybe_allow_in_graph
2860
class BasicTransformerBlock(nn.Module):
2961
r"""
@@ -62,6 +94,7 @@ def __init__(
6294
norm_elementwise_affine: bool = True,
6395
norm_type: str = "layer_norm",
6496
final_dropout: bool = False,
97+
attention_type: str = "default",
6598
):
6699
super().__init__()
67100
self.only_cross_attention = only_cross_attention
@@ -120,6 +153,10 @@ def __init__(
120153
self.norm3 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
121154
self.ff = FeedForward(dim, dropout=dropout, activation_fn=activation_fn, final_dropout=final_dropout)
122155

156+
# 4. Fuser
157+
if attention_type == "gated":
158+
self.fuser = GatedSelfAttentionDense(dim, cross_attention_dim, num_attention_heads, attention_head_dim)
159+
123160
# let chunk size default to None
124161
self._chunk_size = None
125162
self._chunk_dim = 0
@@ -150,7 +187,9 @@ def forward(
150187
else:
151188
norm_hidden_states = self.norm1(hidden_states)
152189

153-
cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {}
190+
# 0. Prepare GLIGEN inputs
191+
cross_attention_kwargs = cross_attention_kwargs.copy() if cross_attention_kwargs is not None else {}
192+
gligen_kwargs = cross_attention_kwargs.pop("gligen", None)
154193

155194
attn_output = self.attn1(
156195
norm_hidden_states,
@@ -162,6 +201,11 @@ def forward(
162201
attn_output = gate_msa.unsqueeze(1) * attn_output
163202
hidden_states = attn_output + hidden_states
164203

204+
# 1.5 GLIGEN Control
205+
if gligen_kwargs is not None:
206+
hidden_states = self.fuser(hidden_states, gligen_kwargs["objs"])
207+
# 1.5 ends
208+
165209
# 2. Cross-Attention
166210
if self.attn2 is not None:
167211
norm_hidden_states = (

src/diffusers/models/embeddings.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,3 +544,59 @@ def shape(x):
544544
a = a.reshape(bs, -1, 1).transpose(1, 2)
545545

546546
return a[:, 0, :] # cls_token
547+
548+
549+
class FourierEmbedder(nn.Module):
550+
def __init__(self, num_freqs=64, temperature=100):
551+
super().__init__()
552+
553+
self.num_freqs = num_freqs
554+
self.temperature = temperature
555+
556+
freq_bands = temperature ** (torch.arange(num_freqs) / num_freqs)
557+
freq_bands = freq_bands[None, None, None]
558+
self.register_buffer("freq_bands", freq_bands, persistent=False)
559+
560+
def __call__(self, x):
561+
x = self.freq_bands * x.unsqueeze(-1)
562+
return torch.stack((x.sin(), x.cos()), dim=-1).permute(0, 1, 3, 4, 2).reshape(*x.shape[:2], -1)
563+
564+
565+
class PositionNet(nn.Module):
566+
def __init__(self, positive_len, out_dim, fourier_freqs=8):
567+
super().__init__()
568+
self.positive_len = positive_len
569+
self.out_dim = out_dim
570+
571+
self.fourier_embedder = FourierEmbedder(num_freqs=fourier_freqs)
572+
self.position_dim = fourier_freqs * 2 * 4 # 2: sin/cos, 4: xyxy
573+
574+
if isinstance(out_dim, tuple):
575+
out_dim = out_dim[0]
576+
self.linears = nn.Sequential(
577+
nn.Linear(self.positive_len + self.position_dim, 512),
578+
nn.SiLU(),
579+
nn.Linear(512, 512),
580+
nn.SiLU(),
581+
nn.Linear(512, out_dim),
582+
)
583+
584+
self.null_positive_feature = torch.nn.Parameter(torch.zeros([self.positive_len]))
585+
self.null_position_feature = torch.nn.Parameter(torch.zeros([self.position_dim]))
586+
587+
def forward(self, boxes, masks, positive_embeddings):
588+
masks = masks.unsqueeze(-1)
589+
590+
# embedding position (it may includes padding as placeholder)
591+
xyxy_embedding = self.fourier_embedder(boxes) # B*N*4 -> B*N*C
592+
593+
# learnable null embedding
594+
positive_null = self.null_positive_feature.view(1, 1, -1)
595+
xyxy_null = self.null_position_feature.view(1, 1, -1)
596+
597+
# replace padding with learnable null embedding
598+
positive_embeddings = positive_embeddings * masks + (1 - masks) * positive_null
599+
xyxy_embedding = xyxy_embedding * masks + (1 - masks) * xyxy_null
600+
601+
objs = self.linears(torch.cat([positive_embeddings, xyxy_embedding], dim=-1))
602+
return objs

src/diffusers/models/transformer_2d.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def __init__(
9191
upcast_attention: bool = False,
9292
norm_type: str = "layer_norm",
9393
norm_elementwise_affine: bool = True,
94+
attention_type: str = "default",
9495
):
9596
super().__init__()
9697
self.use_linear_projection = use_linear_projection
@@ -183,6 +184,7 @@ def __init__(
183184
upcast_attention=upcast_attention,
184185
norm_type=norm_type,
185186
norm_elementwise_affine=norm_elementwise_affine,
187+
attention_type=attention_type,
186188
)
187189
for d in range(num_layers)
188190
]

src/diffusers/models/unet_2d_blocks.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def get_down_block(
4949
only_cross_attention=False,
5050
upcast_attention=False,
5151
resnet_time_scale_shift="default",
52+
attention_type="default",
5253
resnet_skip_time_act=False,
5354
resnet_out_scale_factor=1.0,
5455
cross_attention_norm=None,
@@ -129,6 +130,7 @@ def get_down_block(
129130
only_cross_attention=only_cross_attention,
130131
upcast_attention=upcast_attention,
131132
resnet_time_scale_shift=resnet_time_scale_shift,
133+
attention_type=attention_type,
132134
)
133135
elif down_block_type == "SimpleCrossAttnDownBlock2D":
134136
if cross_attention_dim is None:
@@ -244,6 +246,7 @@ def get_up_block(
244246
only_cross_attention=False,
245247
upcast_attention=False,
246248
resnet_time_scale_shift="default",
249+
attention_type="default",
247250
resnet_skip_time_act=False,
248251
resnet_out_scale_factor=1.0,
249252
cross_attention_norm=None,
@@ -307,6 +310,7 @@ def get_up_block(
307310
only_cross_attention=only_cross_attention,
308311
upcast_attention=upcast_attention,
309312
resnet_time_scale_shift=resnet_time_scale_shift,
313+
attention_type=attention_type,
310314
)
311315
elif up_block_type == "SimpleCrossAttnUpBlock2D":
312316
if cross_attention_dim is None:
@@ -556,6 +560,7 @@ def __init__(
556560
dual_cross_attention=False,
557561
use_linear_projection=False,
558562
upcast_attention=False,
563+
attention_type="default",
559564
):
560565
super().__init__()
561566

@@ -592,6 +597,7 @@ def __init__(
592597
norm_num_groups=resnet_groups,
593598
use_linear_projection=use_linear_projection,
594599
upcast_attention=upcast_attention,
600+
attention_type=attention_type,
595601
)
596602
)
597603
else:
@@ -934,6 +940,7 @@ def __init__(
934940
use_linear_projection=False,
935941
only_cross_attention=False,
936942
upcast_attention=False,
943+
attention_type="default",
937944
):
938945
super().__init__()
939946
resnets = []
@@ -970,6 +977,7 @@ def __init__(
970977
use_linear_projection=use_linear_projection,
971978
only_cross_attention=only_cross_attention,
972979
upcast_attention=upcast_attention,
980+
attention_type=attention_type,
973981
)
974982
)
975983
else:
@@ -2068,6 +2076,7 @@ def __init__(
20682076
use_linear_projection=False,
20692077
only_cross_attention=False,
20702078
upcast_attention=False,
2079+
attention_type="default",
20712080
):
20722081
super().__init__()
20732082
resnets = []
@@ -2106,6 +2115,7 @@ def __init__(
21062115
use_linear_projection=use_linear_projection,
21072116
only_cross_attention=only_cross_attention,
21082117
upcast_attention=upcast_attention,
2118+
attention_type=attention_type,
21092119
)
21102120
)
21112121
else:

src/diffusers/models/unet_2d_condition.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
ImageHintTimeEmbedding,
2929
ImageProjection,
3030
ImageTimeEmbedding,
31+
PositionNet,
3132
TextImageProjection,
3233
TextImageTimeEmbedding,
3334
TextTimeEmbedding,
@@ -198,6 +199,7 @@ def __init__(
198199
conv_in_kernel: int = 3,
199200
conv_out_kernel: int = 3,
200201
projection_class_embeddings_input_dim: Optional[int] = None,
202+
attention_type: str = "default",
201203
class_embeddings_concat: bool = False,
202204
mid_block_only_cross_attention: Optional[bool] = None,
203205
cross_attention_norm: Optional[str] = None,
@@ -446,6 +448,7 @@ def __init__(
446448
only_cross_attention=only_cross_attention[i],
447449
upcast_attention=upcast_attention,
448450
resnet_time_scale_shift=resnet_time_scale_shift,
451+
attention_type=attention_type,
449452
resnet_skip_time_act=resnet_skip_time_act,
450453
resnet_out_scale_factor=resnet_out_scale_factor,
451454
cross_attention_norm=cross_attention_norm,
@@ -469,6 +472,7 @@ def __init__(
469472
dual_cross_attention=dual_cross_attention,
470473
use_linear_projection=use_linear_projection,
471474
upcast_attention=upcast_attention,
475+
attention_type=attention_type,
472476
)
473477
elif mid_block_type == "UNetMidBlock2DSimpleCrossAttn":
474478
self.mid_block = UNetMidBlock2DSimpleCrossAttn(
@@ -535,6 +539,7 @@ def __init__(
535539
only_cross_attention=only_cross_attention[i],
536540
upcast_attention=upcast_attention,
537541
resnet_time_scale_shift=resnet_time_scale_shift,
542+
attention_type=attention_type,
538543
resnet_skip_time_act=resnet_skip_time_act,
539544
resnet_out_scale_factor=resnet_out_scale_factor,
540545
cross_attention_norm=cross_attention_norm,
@@ -560,6 +565,14 @@ def __init__(
560565
block_out_channels[0], out_channels, kernel_size=conv_out_kernel, padding=conv_out_padding
561566
)
562567

568+
if attention_type == "gated":
569+
positive_len = 768
570+
if isinstance(cross_attention_dim, int):
571+
positive_len = cross_attention_dim
572+
elif isinstance(cross_attention_dim, tuple) or isinstance(cross_attention_dim, list):
573+
positive_len = cross_attention_dim[0]
574+
self.position_net = PositionNet(positive_len=positive_len, out_dim=cross_attention_dim)
575+
563576
@property
564577
def attn_processors(self) -> Dict[str, AttentionProcessor]:
565578
r"""
@@ -895,6 +908,12 @@ def forward(
895908
# 2. pre-process
896909
sample = self.conv_in(sample)
897910

911+
# 2.5 GLIGEN position net
912+
if cross_attention_kwargs is not None and cross_attention_kwargs.get("gligen", None) is not None:
913+
cross_attention_kwargs = cross_attention_kwargs.copy()
914+
gligen_args = cross_attention_kwargs.pop("gligen")
915+
cross_attention_kwargs["gligen"] = {"objs": self.position_net(**gligen_args)}
916+
898917
# 3. down
899918

900919
is_controlnet = mid_block_additional_residual is not None and down_block_additional_residuals is not None

src/diffusers/pipelines/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
StableDiffusionAttendAndExcitePipeline,
9191
StableDiffusionDepth2ImgPipeline,
9292
StableDiffusionDiffEditPipeline,
93+
StableDiffusionGLIGENPipeline,
9394
StableDiffusionImageVariationPipeline,
9495
StableDiffusionImg2ImgPipeline,
9596
StableDiffusionInpaintPipeline,

src/diffusers/pipelines/stable_diffusion/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class StableDiffusionPipelineOutput(BaseOutput):
4545
from .pipeline_cycle_diffusion import CycleDiffusionPipeline
4646
from .pipeline_stable_diffusion import StableDiffusionPipeline
4747
from .pipeline_stable_diffusion_attend_and_excite import StableDiffusionAttendAndExcitePipeline
48+
from .pipeline_stable_diffusion_gligen import StableDiffusionGLIGENPipeline
4849
from .pipeline_stable_diffusion_img2img import StableDiffusionImg2ImgPipeline
4950
from .pipeline_stable_diffusion_inpaint import StableDiffusionInpaintPipeline
5051
from .pipeline_stable_diffusion_inpaint_legacy import StableDiffusionInpaintPipelineLegacy

0 commit comments

Comments
 (0)