Text-to-Image
Diffusers
English
structured-diffusion-guidance
image-generation
Eval Results (legacy)
Instructions to use creative-graphic-design/structure-diffusion with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use creative-graphic-design/structure-diffusion with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("creative-graphic-design/structure-diffusion", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
Download modeling_structure_diffusion.py from creative-graphic-design/structure-diffusion: direct link, hf CLI and curl.
- Browser
- Download file 15.4 kB
-
https://huggingface.co/creative-graphic-design/structure-diffusion/resolve/main/modeling_structure_diffusion.py
- Command line
-
hf download hf://creative-graphic-design/structure-diffusion/modeling_structure_diffusion.py
-
curl -L -o modeling_structure_diffusion.py https://huggingface.co/creative-graphic-design/structure-diffusion/resolve/main/modeling_structure_diffusion.py
15.4 kB
| """Cross-attention processor for structured diffusion guidance.""" | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import torch | |
| from diffusers.models.attention_processor import Attention | |
| from jaxtyping import Bool, Float | |
| class StructuredConditioning: | |
| """Full-prompt keys, phrase-composed values, and per-example validity.""" | |
| keys: tuple[Float[torch.Tensor, "batch tokens dim"], ...] | |
| values: tuple[Float[torch.Tensor, "batch tokens dim"], ...] | |
| key_validity: tuple[Bool[torch.Tensor, "batch"], ...] | |
| value_validity: tuple[Bool[torch.Tensor, "batch"], ...] | |
| do_classifier_free_guidance: bool | |
| class StructuredAttentionProcessor: | |
| """Apply noun-phrase key/value attention in a Diffusers cross-attention layer.""" | |
| def __init__(self, layer_name: str, save_attention_maps: bool) -> None: | |
| """Create per-layer state for the requested attention-map behavior. | |
| Args: | |
| layer_name: U-Net attention processor name used to label saved maps. | |
| save_attention_maps: Whether to retain conditional attention maps. | |
| """ | |
| self.layer_name = layer_name | |
| self.save_attention_maps = save_attention_maps | |
| self.conditioning: StructuredConditioning | None = None | |
| self.attention_maps: list[Float[torch.Tensor, "..."]] = [] | |
| self._last_probabilities: list[ | |
| Float[torch.Tensor, "batch_heads query tokens"] | |
| ] = [] | |
| def __call__( | |
| self, | |
| attn: Attention, | |
| hidden_states: Float[torch.Tensor, "..."], | |
| encoder_hidden_states: Float[torch.Tensor, "batch tokens dim"] | None = None, | |
| attention_mask: Bool[torch.Tensor, "..."] | |
| | Float[torch.Tensor, "..."] | |
| | None = None, | |
| temb: Float[torch.Tensor, "batch channels"] | None = None, | |
| scale: float | None = None, | |
| ) -> Float[torch.Tensor, "..."]: | |
| """Run ordinary attention or noun-phrase-aware key/value attention.""" | |
| del scale | |
| if ( | |
| attn.to_q is None | |
| or attn.to_k is None | |
| or attn.to_v is None | |
| or attn.to_out is None | |
| ): | |
| raise ValueError( | |
| "Attention layers must define query, key, value, and output projections." | |
| ) | |
| residual = hidden_states | |
| if attn.spatial_norm is not None: | |
| hidden_states = attn.spatial_norm(hidden_states, temb) | |
| input_ndim = hidden_states.ndim | |
| spatial_shape: tuple[int, int] | None = None | |
| spatial_dimensions: tuple[int, int, int] | None = None | |
| if input_ndim == 4: | |
| batch_size, channel, height, width = hidden_states.shape | |
| spatial_shape = (height, width) | |
| spatial_dimensions = (channel, height, width) | |
| spatial_tokens: Float[torch.Tensor, "batch channels tokens"] = ( | |
| hidden_states.view(batch_size, channel, height * width) | |
| ) | |
| hidden_states = spatial_tokens.transpose(1, 2) | |
| batch_size = hidden_states.shape[0] | |
| if attn.group_norm is not None: | |
| channels_first: Float[torch.Tensor, "batch channels tokens"] = ( | |
| hidden_states.transpose(1, 2) | |
| ) | |
| normalized_channels = attn.group_norm(channels_first) | |
| hidden_states = normalized_channels.transpose(1, 2) | |
| query = attn.to_q(hidden_states) | |
| if encoder_hidden_states is None: | |
| encoder_hidden_states = hidden_states | |
| elif attn.norm_cross: | |
| encoder_hidden_states = attn.norm_encoder_hidden_states( | |
| encoder_hidden_states | |
| ) | |
| conditioning = self.conditioning | |
| if conditioning is None: | |
| output = self._normal_attention( | |
| attn, | |
| query, | |
| encoder_hidden_states, | |
| attention_mask, | |
| ) | |
| # CFG concatenates unconditional and conditional embeddings before this processor. | |
| # Keep the unconditional half on ordinary attention and structure the conditional half. | |
| elif conditioning.do_classifier_free_guidance: | |
| base_batch = conditioning.keys[0].shape[0] | |
| if batch_size != 2 * base_batch: | |
| raise ValueError( | |
| "Classifier-free guidance queries must contain matching unconditional and conditional batches." | |
| ) | |
| unconditional_query, conditional_query = query.split(base_batch, dim=0) | |
| unconditional_context = encoder_hidden_states[:base_batch] | |
| unconditional_mask, conditional_mask = self._split_mask( | |
| attention_mask, | |
| base_batch, | |
| batch_size, | |
| ) | |
| unconditional_output = self._normal_attention( | |
| attn, | |
| unconditional_query, | |
| unconditional_context, | |
| unconditional_mask, | |
| ) | |
| conditional_output = self._structured_attention( | |
| attn, | |
| conditional_query, | |
| conditional_mask, | |
| ) | |
| output = torch.cat((unconditional_output, conditional_output), dim=0) | |
| else: | |
| if batch_size != conditioning.keys[0].shape[0]: | |
| raise ValueError( | |
| "Structured conditioning and query batches must match." | |
| ) | |
| output = self._structured_attention(attn, query, attention_mask) | |
| if self.save_attention_maps: | |
| if spatial_shape is None: | |
| query_length = self._last_probabilities[0].shape[-2] | |
| side_length = int(query_length**0.5) | |
| if side_length * side_length == query_length: | |
| spatial_shape = (side_length, side_length) | |
| if self.save_attention_maps and spatial_shape is not None: | |
| height, width = spatial_shape | |
| for probabilities in self._last_probabilities: | |
| map_batch = probabilities.shape[0] // attn.heads | |
| spatial_attention: Float[ | |
| torch.Tensor, "batch heads height width tokens" | |
| ] = probabilities.reshape( | |
| map_batch, | |
| attn.heads, | |
| height, | |
| width, | |
| probabilities.shape[-1], | |
| ) | |
| saved_tokens = spatial_attention[..., :20] | |
| saved_attention = saved_tokens.detach().cpu() | |
| self.attention_maps.append(saved_attention) | |
| output = attn.to_out[0](output) | |
| output = attn.to_out[1](output) | |
| if spatial_dimensions is not None: | |
| channel, height, width = spatial_dimensions | |
| spatial_tokens = output.transpose(-1, -2) | |
| output = spatial_tokens.reshape(batch_size, channel, height, width) | |
| if attn.residual_connection: | |
| output = output + residual | |
| return output / attn.rescale_output_factor | |
| def _normal_attention( | |
| self, | |
| attn: Attention, | |
| query: Float[torch.Tensor, "batch sequence channels"], | |
| context: Float[torch.Tensor, "batch tokens dim"], | |
| attention_mask: Bool[torch.Tensor, "..."] | Float[torch.Tensor, "..."] | None, | |
| ) -> Float[torch.Tensor, "batch sequence channels"]: | |
| if attn.to_k is None or attn.to_v is None: | |
| raise ValueError("Attention layers must define key and value projections.") | |
| query_heads = self._to_heads(query, attn.heads) | |
| key_projection = attn.to_k(context) | |
| key_heads = self._to_heads(key_projection, attn.heads) | |
| value_projection = attn.to_v(context) | |
| value_heads = self._to_heads(value_projection, attn.heads) | |
| if attn.norm_q is not None: | |
| query_heads = attn.norm_q(query_heads) | |
| if attn.norm_k is not None: | |
| key_heads = attn.norm_k(key_heads) | |
| probabilities = self._attention_probabilities( | |
| attn, | |
| query_heads, | |
| key_heads, | |
| attention_mask, | |
| ) | |
| self._last_probabilities = [probabilities] | |
| return self._from_heads(torch.bmm(probabilities, value_heads), attn.heads) | |
| def _structured_attention( | |
| self, | |
| attn: Attention, | |
| query: Float[torch.Tensor, "batch sequence channels"], | |
| attention_mask: Bool[torch.Tensor, "..."] | Float[torch.Tensor, "..."] | None, | |
| ) -> Float[torch.Tensor, "batch sequence channels"]: | |
| conditioning = self.conditioning | |
| if conditioning is None: | |
| raise RuntimeError("Structured conditioning has not been installed.") | |
| if attn.to_k is None or attn.to_v is None: | |
| raise ValueError("Attention layers must define key and value projections.") | |
| query_heads = self._to_heads(query, attn.heads) | |
| if attn.norm_q is not None: | |
| query_heads = attn.norm_q(query_heads) | |
| # Project keys and values from their separate contexts before pairing them below. | |
| key_heads: list[Float[torch.Tensor, "batch_heads tokens head_dim"]] = [] | |
| for context in conditioning.keys: | |
| normalized = ( | |
| attn.norm_encoder_hidden_states(context) if attn.norm_cross else context | |
| ) | |
| key_projection = attn.to_k(normalized) | |
| projected = self._to_heads(key_projection, attn.heads) | |
| if attn.norm_k is not None: | |
| projected = attn.norm_k(projected) | |
| key_heads.append(projected) | |
| value_heads: list[Float[torch.Tensor, "batch_heads tokens head_dim"]] = [] | |
| for context in conditioning.values: | |
| value_projection = attn.to_v(context) | |
| value_heads.append(self._to_heads(value_projection, attn.heads)) | |
| probabilities = [ | |
| self._attention_probabilities(attn, query_heads, key, attention_mask) | |
| for key in key_heads | |
| ] | |
| # Pair each key's attention scores with the corresponding value contexts. | |
| # A singleton side is reused when only one key or value context is present. | |
| if len(probabilities) == len(value_heads): | |
| pairs = list( | |
| zip(range(len(probabilities)), range(len(value_heads)), strict=True) | |
| ) | |
| elif len(probabilities) == 1: | |
| pairs = [(0, value_index) for value_index in range(len(value_heads))] | |
| elif len(value_heads) == 1: | |
| pairs = [(key_index, 0) for key_index in range(len(probabilities))] | |
| else: | |
| raise ValueError( | |
| "Structured attention requires matching lists or a singleton side." | |
| ) | |
| outputs = torch.stack( | |
| [ | |
| torch.bmm(probabilities[key_index], value_heads[value_index]) | |
| for key_index, value_index in pairs | |
| ] | |
| ) | |
| validity = torch.stack( | |
| [ | |
| conditioning.key_validity[key_index] | |
| & conditioning.value_validity[value_index] | |
| for key_index, value_index in pairs | |
| ] | |
| ).repeat_interleave(attn.heads, dim=1) | |
| # Placeholder phrase contexts are excluded when averaging the valid pairs. | |
| output_weights: Float[torch.Tensor, "pairs batch_heads 1 1"] = ( | |
| validity.to(outputs.dtype).unsqueeze(-1).unsqueeze(-1) | |
| ) | |
| weighted_outputs = outputs * output_weights | |
| summed_weights = output_weights.sum(dim=0).clamp_min(1) | |
| outputs = weighted_outputs.sum(dim=0) / summed_weights | |
| saved_probabilities: list[Float[torch.Tensor, "batch_heads query tokens"]] = [] | |
| for key_index, attention in enumerate(probabilities): | |
| key_validity = conditioning.key_validity[key_index] | |
| head_validity = key_validity.repeat_interleave(attn.heads) | |
| attention_weights: Float[torch.Tensor, "batch_heads 1 1"] = ( | |
| head_validity.to(attention.dtype).unsqueeze(-1).unsqueeze(-1) | |
| ) | |
| saved_probabilities.append(attention * attention_weights) | |
| self._last_probabilities = saved_probabilities | |
| return self._from_heads(outputs, attn.heads) | |
| def _to_heads( | |
| projected: Float[torch.Tensor, "batch sequence inner_dim"], heads: int | |
| ) -> Float[torch.Tensor, "batch_heads sequence head_dim"]: | |
| batch_size, sequence_length, inner_dim = projected.shape | |
| head_dim = inner_dim // heads | |
| head_layout: Float[torch.Tensor, "batch sequence heads head_dim"] = ( | |
| projected.view(batch_size, sequence_length, heads, head_dim) | |
| ) | |
| heads_first: Float[torch.Tensor, "batch heads sequence head_dim"] = ( | |
| head_layout.transpose(1, 2) | |
| ) | |
| merged_batch = heads_first.reshape( | |
| batch_size * heads, sequence_length, head_dim | |
| ) | |
| return merged_batch | |
| def _from_heads( | |
| values: Float[torch.Tensor, "batch_heads sequence head_dim"], heads: int | |
| ) -> Float[torch.Tensor, "batch sequence inner_dim"]: | |
| batch_heads, sequence_length, head_dim = values.shape | |
| batch_size = batch_heads // heads | |
| head_layout: Float[torch.Tensor, "batch heads sequence head_dim"] = values.view( | |
| batch_size, heads, sequence_length, head_dim | |
| ) | |
| batch_first: Float[torch.Tensor, "batch sequence heads head_dim"] = ( | |
| head_layout.transpose(1, 2) | |
| ) | |
| merged_heads = batch_first.reshape( | |
| batch_size, sequence_length, heads * head_dim | |
| ) | |
| return merged_heads | |
| def _attention_probabilities( | |
| attn: Attention, | |
| query: Float[torch.Tensor, "batch_heads query head_dim"], | |
| key: Float[torch.Tensor, "batch_heads tokens head_dim"], | |
| attention_mask: Bool[torch.Tensor, "..."] | Float[torch.Tensor, "..."] | None, | |
| ) -> Float[torch.Tensor, "batch_heads query tokens"]: | |
| scores = torch.bmm(query, key.transpose(1, 2)) * attn.scale | |
| if attention_mask is not None: | |
| if attention_mask.ndim == 2: | |
| mask_per_head = attention_mask.repeat_interleave(attn.heads, dim=0) | |
| mask = mask_per_head.unsqueeze(1) | |
| elif attention_mask.ndim == 4: | |
| mask = attention_mask.reshape( | |
| -1, attention_mask.shape[-2], attention_mask.shape[-1] | |
| ) | |
| elif attention_mask.shape[0] * attn.heads == scores.shape[0]: | |
| mask = attention_mask.repeat_interleave(attn.heads, dim=0) | |
| else: | |
| mask = attention_mask | |
| if mask.dtype == torch.bool: | |
| scores = scores.masked_fill(~mask, torch.finfo(scores.dtype).min) | |
| else: | |
| scores = scores + mask | |
| return scores.softmax(dim=-1) | |
| def _split_mask( | |
| attention_mask: Bool[torch.Tensor, "..."] | Float[torch.Tensor, "..."] | None, | |
| base_batch: int, | |
| total_batch: int, | |
| ) -> tuple[ | |
| Bool[torch.Tensor, "..."] | Float[torch.Tensor, "..."] | None, | |
| Bool[torch.Tensor, "..."] | Float[torch.Tensor, "..."] | None, | |
| ]: | |
| if attention_mask is None or attention_mask.shape[0] != total_batch: | |
| return attention_mask, attention_mask | |
| unconditional_mask, conditional_mask = attention_mask.split(base_batch, dim=0) | |
| return unconditional_mask, conditional_mask | |