From 1d533544d2878783daadcc8ff06ba76ee317f070 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 12 Feb 2025 07:45:16 -0500 Subject: [PATCH] add flex.1-alpha Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 11 +- README.md | 2 + html/reference.json | 7 + models/Reference/ostris--Flex.1-alpha.jpg | Bin 0 -> 29335 bytes modules/mod/__init__.py | 1226 +++++++++++++++++++++ modules/sd_detect.py | 2 +- scripts/mixture_of_diffusers.py | 37 + wiki | 2 +- 8 files changed, 1283 insertions(+), 4 deletions(-) create mode 100644 models/Reference/ostris--Flex.1-alpha.jpg create mode 100644 modules/mod/__init__.py create mode 100644 scripts/mixture_of_diffusers.py diff --git a/CHANGELOG.md b/CHANGELOG.md index af595b1b1..6028c2982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,13 +13,20 @@ - [localization](https://github.com/vladmandic/sdnext/wiki/Locale) documentation - **UI**: - force browser cache-invalidate on page load +- **Models** + - [Ostris Flex.1-Alpha](https://huggingface.co/ostris/Flex.1-alpha) + originally based on Flux.1-Schnell, but retrained and with different architecture + result is model smaller than Flux.1-Dev, but with similar capabilities - **Docs** - New [Outpaint](https://github.com/vladmandic/sdnext/wiki/Outpaint) step-by-step guide - Updated [Docker](https://github.com/vladmandic/sdnext/wiki/Docker) guide includes build and publish and both local and cloud examples +- **Docker** + - updated **CUDA** receipe to `torch==2.6.0` with `cuda==12.6` + - added **ROCm** receipe + - added **IPEX** receipe + - added **OpenVINO** receipe - **Backend** - - **Docker** - - updated CUDA image to `torch==2.6.0` with `cuda==12.6` - **Torch** - for **zluda** set default to `torch==2.6.0+cu118` - for **openvino** set default to `torch==2.6.0+cpu` diff --git a/README.md b/README.md index e2277d309..a50d04241 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ SD.Next supports broad range of models: [supported models](https://vladmandic.gi - *ONNX/Olive* - *AMD* GPUs on Windows using **ZLUDA** libraries +Plus Docker container receipes for: [CUDA, ROCm, Intel IPEX and OpenVINO](https://vladmandic.github.io/sdnext-docs/Docker/) + ## Getting started - Get started with **SD.Next** by following the [installation instructions](https://vladmandic.github.io/sdnext-docs/Installation/) diff --git a/html/reference.json b/html/reference.json index ca55081a9..83849c3c0 100644 --- a/html/reference.json +++ b/html/reference.json @@ -179,6 +179,13 @@ "skip": true, "extras": "sampler: Default, cfg_scale: 3.5" }, + "Ostris Flex.1 Alpha": { + "path": "ostris/Flex.1-alpha", + "preview": "ostris--Flex.1-alpha.jpg", + "desc": "Flex.1 alpha is a pre-trained base 8 billion parameter rectified flow transformer capable of generating images from text descriptions. It has a similar architecture to FLUX.1-dev, but with fewer double transformer blocks (8 vs 19)", + "skip": true, + "extras": "sampler: Default, cfg_scale: 3.5" + }, "NVLabs Sana 1.6B 4k": { "path": "Efficient-Large-Model/Sana_1600M_4Kpx_BF16_diffusers", diff --git a/models/Reference/ostris--Flex.1-alpha.jpg b/models/Reference/ostris--Flex.1-alpha.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9effff3aa1dd8b99579b2647ba258b149d30210f GIT binary patch literal 29335 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�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�*uG9*Kv;vdem zj^_i5M6>?@$HVduxTeg&+{gjQ?Ovrlk3O%&1Y#3JoJ1>Ev4>KWpL&t{*Pl~!raKLw zb*xt9bSFJ%HPT3;fN2d2Nty$((hb;?1XnrULmCHilQ-H{%I}IRGng?J}%_?;i zhLJE7qMS>X#$)~VKIW0n%zm|ZJomViW{|8Xq|G~pg`^bj78a6=fVibn0|I(eO5oU) zq$jsvKIr@_RZTvoPODF2I5}R7N8?&vUAzAPc8_AajbbFbmEhHN3uX-s?8}aWdB^2b zn>YwKIQ(jTiAxtG^lY^UjJ4OQLBU)_zB@MdF_eU5pu#f8JbIj%{K5BvSTB(Pp#l_@2}bfp#+u++d&X z%U&!Uhejjuqmi4(IBTQ(&uKbD4!MT&KAeMIJ078V5Bj^ASLnu$Mr^aKb-C*j=$9M5 zStD<+TJu|f6zew3882Oc{uks{i$i6d!1PwrZsHumAN|Jv02<@%z9H(+jA z)mxmja1)2yM-<2ll6dB}FKukM<{6`9{{UsH%bg1|cS*(WRd;?&GyH z>~Od{B*FU86&NppGdD(U-AIm86^+prvzg-cBN?crxAL3s2CjKd&godLODi)?Bv}wL zp0!5SM-v6cRgMU)l}9CHWyxt5jD%2EueD~})`v$dNZvwdVOdGY(IiA^NUT~lQn1Qg zRPvOjA?ZdcS}U64aB0}3Vcb9{B7nFNMra+z(VPsL2y8-e%~zIf+%HOYGL=M!$#J)V zRgF6tx{P_1+xU$#orvaSsVkw}tU;*yP#nyW*`+&~i9Ynr$F&z7#*jrwp`VOXQEX1c zRWn89<8bRu!L7;MhNLP8$l%nD#B@>+Q9ug4XdP%8EDn`s1dJ}{G@0E>k22hiVtr-Yc=O*By<$gGM+B}WtlEi zOo}=JGHRNfI622XYe*|Ij&-FX^Zx+H)8nU6kx4#Q`Nr`70PO*Qe+ut}z2la8kC^`M zn<2_p(s~%BW>apoH(KDC(H(?dTvMVMk$$xhSCHPsrk1x85K{3#grq%cTP+R;ia}Fy zhl5BcDKbi6G>c4>9w~y6NRV?#rh5?Lx$$)Op8xrBEzMnvZeyrm70W!Ps5nQ9aVA=AX8Zy_9p7xysh*-5GTtezcF)xn^vKLH@O9 OgZ*IkrAu&@$N$+$Q9L66 literal 0 HcmV?d00001 diff --git a/modules/mod/__init__.py b/modules/mod/__init__.py new file mode 100644 index 000000000..ea64c075e --- /dev/null +++ b/modules/mod/__init__.py @@ -0,0 +1,1226 @@ +# Copyright 2025 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +from enum import Enum +from typing import Any, Dict, List, Optional, Tuple, Union + +import torch +from transformers import ( + CLIPTextModel, + CLIPTextModelWithProjection, + CLIPTokenizer, +) + +from diffusers.image_processor import VaeImageProcessor +from diffusers.loaders import ( + FromSingleFileMixin, + StableDiffusionXLLoraLoaderMixin, + TextualInversionLoaderMixin, +) +from diffusers.models import AutoencoderKL, UNet2DConditionModel +from diffusers.models.attention_processor import ( + AttnProcessor2_0, + FusedAttnProcessor2_0, + XFormersAttnProcessor, +) +from diffusers.models.lora import adjust_lora_scale_text_encoder +from diffusers.pipelines.pipeline_utils import DiffusionPipeline, StableDiffusionMixin +from diffusers.pipelines.stable_diffusion_xl.pipeline_output import StableDiffusionXLPipelineOutput +from diffusers.schedulers import KarrasDiffusionSchedulers, LMSDiscreteScheduler +from diffusers.utils import ( + USE_PEFT_BACKEND, + is_invisible_watermark_available, + is_torch_xla_available, + logging, + replace_example_docstring, + scale_lora_layers, + unscale_lora_layers, +) +from diffusers.utils.torch_utils import randn_tensor + + +try: + from ligo.segments import segment +except ImportError as e: + raise ImportError("Please install transformers and ligo-segments to use the mixture pipeline") from e + +if is_torch_xla_available(): + import torch_xla.core.xla_model as xm + + XLA_AVAILABLE = True +else: + XLA_AVAILABLE = False + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + +EXAMPLE_DOC_STRING = """ + Examples: + ```py + >>> import torch + >>> from diffusers import StableDiffusionXLPipeline + + >>> pipe = StableDiffusionXLPipeline.from_pretrained( + ... "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16 + ... ) + >>> pipe = pipe.to("cuda") + + >>> prompt = "a photo of an astronaut riding a horse on mars" + >>> image = pipe(prompt).images[0] + ``` +""" + + +def _tile2pixel_indices(tile_row, tile_col, tile_width, tile_height, tile_row_overlap, tile_col_overlap): + """Given a tile row and column numbers returns the range of pixels affected by that tiles in the overall image + + Returns a tuple with: + - Starting coordinates of rows in pixel space + - Ending coordinates of rows in pixel space + - Starting coordinates of columns in pixel space + - Ending coordinates of columns in pixel space + """ + px_row_init = 0 if tile_row == 0 else tile_row * (tile_height - tile_row_overlap) + px_row_end = px_row_init + tile_height + px_col_init = 0 if tile_col == 0 else tile_col * (tile_width - tile_col_overlap) + px_col_end = px_col_init + tile_width + return px_row_init, px_row_end, px_col_init, px_col_end + + +def _pixel2latent_indices(px_row_init, px_row_end, px_col_init, px_col_end): + """Translates coordinates in pixel space to coordinates in latent space""" + return px_row_init // 8, px_row_end // 8, px_col_init // 8, px_col_end // 8 + + +def _tile2latent_indices(tile_row, tile_col, tile_width, tile_height, tile_row_overlap, tile_col_overlap): + """Given a tile row and column numbers returns the range of latents affected by that tiles in the overall image + + Returns a tuple with: + - Starting coordinates of rows in latent space + - Ending coordinates of rows in latent space + - Starting coordinates of columns in latent space + - Ending coordinates of columns in latent space + """ + px_row_init, px_row_end, px_col_init, px_col_end = _tile2pixel_indices( + tile_row, tile_col, tile_width, tile_height, tile_row_overlap, tile_col_overlap + ) + return _pixel2latent_indices(px_row_init, px_row_end, px_col_init, px_col_end) + + +def _tile2latent_exclusive_indices( + tile_row, tile_col, tile_width, tile_height, tile_row_overlap, tile_col_overlap, rows, columns +): + """Given a tile row and column numbers returns the range of latents affected only by that tile in the overall image + + Returns a tuple with: + - Starting coordinates of rows in latent space + - Ending coordinates of rows in latent space + - Starting coordinates of columns in latent space + - Ending coordinates of columns in latent space + """ + row_init, row_end, col_init, col_end = _tile2latent_indices( + tile_row, tile_col, tile_width, tile_height, tile_row_overlap, tile_col_overlap + ) + row_segment = segment(row_init, row_end) + col_segment = segment(col_init, col_end) + # Iterate over the rest of tiles, clipping the region for the current tile + for row in range(rows): + for column in range(columns): + if row != tile_row and column != tile_col: + clip_row_init, clip_row_end, clip_col_init, clip_col_end = _tile2latent_indices( + row, column, tile_width, tile_height, tile_row_overlap, tile_col_overlap + ) + row_segment = row_segment - segment(clip_row_init, clip_row_end) + col_segment = col_segment - segment(clip_col_init, clip_col_end) + # return row_init, row_end, col_init, col_end + return row_segment[0], row_segment[1], col_segment[0], col_segment[1] + +def _get_crops_coords_list(num_rows, num_cols, output_width): + """ + Generates a list of lists of `crops_coords_top_left` tuples for focusing on + different horizontal parts of an image, and repeats this list for the specified + number of rows in the output structure. + + This function calculates `crops_coords_top_left` tuples to create horizontal + focus variations (like left, center, right focus) based on `output_width` + and `num_cols` (which represents the number of horizontal focus points/columns). + It then repeats the *list* of these horizontal focus tuples `num_rows` times to + create the final list of lists output structure. + + Args: + num_rows (int): The desired number of rows in the output list of lists. + This determines how many times the list of horizontal + focus variations will be repeated. + num_cols (int): The number of horizontal focus points (columns) to generate. + This determines how many horizontal focus variations are + created based on dividing the `output_width`. + output_width (int): The desired width of the output image. + + Returns: + list[list[tuple[int, int]]]: A list of lists of tuples. Each inner list + contains `num_cols` tuples of `(ctop, cleft)`, + representing horizontal focus points. The outer list + contains `num_rows` such inner lists. + """ + crops_coords_list = [] + if num_cols <= 0: + crops_coords_list = [] + elif num_cols == 1: + crops_coords_list = [(0, 0)] + else: + section_width = output_width / num_cols + for i in range(num_cols): + cleft = int(round(i * section_width)) + crops_coords_list.append((0, cleft)) + + result_list = [] + for _ in range(num_rows): + result_list.append(list(crops_coords_list)) + + return result_list + +# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.rescale_noise_cfg +def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0): + r""" + Rescales `noise_cfg` tensor based on `guidance_rescale` to improve image quality and fix overexposure. Based on + Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are + Flawed](https://arxiv.org/pdf/2305.08891.pdf). + + Args: + noise_cfg (`torch.Tensor`): + The predicted noise tensor for the guided diffusion process. + noise_pred_text (`torch.Tensor`): + The predicted noise tensor for the text-guided diffusion process. + guidance_rescale (`float`, *optional*, defaults to 0.0): + A rescale factor applied to the noise predictions. + + Returns: + noise_cfg (`torch.Tensor`): The rescaled noise prediction tensor. + """ + std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True) + std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True) + # rescale the results from guidance (fixes overexposure) + noise_pred_rescaled = noise_cfg * (std_text / std_cfg) + # mix with the original results from guidance by factor guidance_rescale to avoid "plain looking" images + noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg + return noise_cfg + + +# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps +def retrieve_timesteps( + scheduler, + num_inference_steps: Optional[int] = None, + device: Optional[Union[str, torch.device]] = None, + timesteps: Optional[List[int]] = None, + sigmas: Optional[List[float]] = None, + **kwargs, +): + r""" + Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles + custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. + + Args: + scheduler (`SchedulerMixin`): + The scheduler to get timesteps from. + num_inference_steps (`int`): + The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps` + must be `None`. + device (`str` or `torch.device`, *optional*): + The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. + timesteps (`List[int]`, *optional*): + Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed, + `num_inference_steps` and `sigmas` must be `None`. + sigmas (`List[float]`, *optional*): + Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed, + `num_inference_steps` and `timesteps` must be `None`. + + Returns: + `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the + second element is the number of inference steps. + """ + + if timesteps is not None and sigmas is not None: + raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values") + if timesteps is not None: + accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) + if not accepts_timesteps: + raise ValueError( + f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" + f" timestep schedules. Please check whether you are using the correct scheduler." + ) + scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) + timesteps = scheduler.timesteps + num_inference_steps = len(timesteps) + elif sigmas is not None: + accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) + if not accept_sigmas: + raise ValueError( + f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" + f" sigmas schedules. Please check whether you are using the correct scheduler." + ) + scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs) + timesteps = scheduler.timesteps + num_inference_steps = len(timesteps) + else: + scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) + timesteps = scheduler.timesteps + return timesteps, num_inference_steps + + +class StableDiffusionXLTilingPipeline( + DiffusionPipeline, + StableDiffusionMixin, + FromSingleFileMixin, + StableDiffusionXLLoraLoaderMixin, + TextualInversionLoaderMixin, +): + r""" + Pipeline for text-to-image generation using Stable Diffusion XL. + + This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the + library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.) + + The pipeline also inherits the following loading methods: + - [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] for loading textual inversion embeddings + - [`~loaders.FromSingleFileMixin.from_single_file`] for loading `.ckpt` files + - [`~loaders.StableDiffusionXLLoraLoaderMixin.load_lora_weights`] for loading LoRA weights + - [`~loaders.StableDiffusionXLLoraLoaderMixin.save_lora_weights`] for saving LoRA weights + + Args: + vae ([`AutoencoderKL`]): + Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations. + text_encoder ([`CLIPTextModel`]): + Frozen text-encoder. Stable Diffusion XL uses the text portion of + [CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically + the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant. + text_encoder_2 ([` CLIPTextModelWithProjection`]): + Second frozen text-encoder. Stable Diffusion XL uses the text and pool portion of + [CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModelWithProjection), + specifically the + [laion/CLIP-ViT-bigG-14-laion2B-39B-b160k](https://huggingface.co/laion/CLIP-ViT-bigG-14-laion2B-39B-b160k) + variant. + tokenizer (`CLIPTokenizer`): + Tokenizer of class + [CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer). + tokenizer_2 (`CLIPTokenizer`): + Second Tokenizer of class + [CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer). + unet ([`UNet2DConditionModel`]): Conditional U-Net architecture to denoise the encoded image latents. + scheduler ([`SchedulerMixin`]): + A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of + [`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`]. + force_zeros_for_empty_prompt (`bool`, *optional*, defaults to `"True"`): + Whether the negative prompt embeddings shall be forced to always be set to 0. Also see the config of + `stabilityai/stable-diffusion-xl-base-1-0`. + add_watermarker (`bool`, *optional*): + Whether to use the [invisible_watermark library](https://github.com/ShieldMnt/invisible-watermark/) to + watermark output images. If not defined, it will default to True if the package is installed, otherwise no + watermarker will be used. + """ + + model_cpu_offload_seq = "text_encoder->text_encoder_2->image_encoder->unet->vae" + _optional_components = [ + "tokenizer", + "tokenizer_2", + "text_encoder", + "text_encoder_2", + ] + + def __init__( + self, + vae: AutoencoderKL, + text_encoder: CLIPTextModel, + text_encoder_2: CLIPTextModelWithProjection, + tokenizer: CLIPTokenizer, + tokenizer_2: CLIPTokenizer, + unet: UNet2DConditionModel, + scheduler: KarrasDiffusionSchedulers, + force_zeros_for_empty_prompt: bool = True, + add_watermarker: Optional[bool] = None, + ): + super().__init__() + + self.register_modules( + vae=vae, + text_encoder=text_encoder, + text_encoder_2=text_encoder_2, + tokenizer=tokenizer, + tokenizer_2=tokenizer_2, + unet=unet, + scheduler=scheduler, + ) + self.register_to_config(force_zeros_for_empty_prompt=force_zeros_for_empty_prompt) + self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) if getattr(self, "vae", None) else 8 + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor) + + self.default_sample_size = ( + self.unet.config.sample_size + if hasattr(self, "unet") and self.unet is not None and hasattr(self.unet.config, "sample_size") + else 128 + ) + + self.watermark = None + + class SeedTilesMode(Enum): + """Modes in which the latents of a particular tile can be re-seeded""" + + FULL = "full" + EXCLUSIVE = "exclusive" + + def encode_prompt( + self, + prompt: str, + prompt_2: Optional[str] = None, + device: Optional[torch.device] = None, + num_images_per_prompt: int = 1, + do_classifier_free_guidance: bool = True, + negative_prompt: Optional[str] = None, + negative_prompt_2: Optional[str] = None, + prompt_embeds: Optional[torch.Tensor] = None, + negative_prompt_embeds: Optional[torch.Tensor] = None, + pooled_prompt_embeds: Optional[torch.Tensor] = None, + negative_pooled_prompt_embeds: Optional[torch.Tensor] = None, + lora_scale: Optional[float] = None, + clip_skip: Optional[int] = None, + ): + r""" + Encodes the prompt into text encoder hidden states. + + Args: + prompt (`str` or `List[str]`, *optional*): + prompt to be encoded + prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts to be sent to the `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is + used in both text-encoders + device: (`torch.device`): + torch device + num_images_per_prompt (`int`): + number of images that should be generated per prompt + do_classifier_free_guidance (`bool`): + whether to use classifier free guidance or not + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + negative_prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation to be sent to `tokenizer_2` and + `text_encoder_2`. If not defined, `negative_prompt` is used in both text-encoders + prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input + argument. + pooled_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. + If not provided, pooled text embeddings will be generated from `prompt` input argument. + negative_pooled_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated negative pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, pooled negative_prompt_embeds will be generated from `negative_prompt` + input argument. + lora_scale (`float`, *optional*): + A lora scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + """ + device = device or self._execution_device + + # set lora scale so that monkey patched LoRA + # function of text encoder can correctly access it + if lora_scale is not None and isinstance(self, StableDiffusionXLLoraLoaderMixin): + self._lora_scale = lora_scale + + # dynamically adjust the LoRA scale + if self.text_encoder is not None: + if not USE_PEFT_BACKEND: + adjust_lora_scale_text_encoder(self.text_encoder, lora_scale) + else: + scale_lora_layers(self.text_encoder, lora_scale) + + if self.text_encoder_2 is not None: + if not USE_PEFT_BACKEND: + adjust_lora_scale_text_encoder(self.text_encoder_2, lora_scale) + else: + scale_lora_layers(self.text_encoder_2, lora_scale) + + prompt = [prompt] if isinstance(prompt, str) else prompt + + if prompt is not None: + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + # Define tokenizers and text encoders + tokenizers = [self.tokenizer, self.tokenizer_2] if self.tokenizer is not None else [self.tokenizer_2] + text_encoders = ( + [self.text_encoder, self.text_encoder_2] if self.text_encoder is not None else [self.text_encoder_2] + ) + + if prompt_embeds is None: + prompt_2 = prompt_2 or prompt + prompt_2 = [prompt_2] if isinstance(prompt_2, str) else prompt_2 + + # textual inversion: process multi-vector tokens if necessary + prompt_embeds_list = [] + prompts = [prompt, prompt_2] + for prompt, tokenizer, text_encoder in zip(prompts, tokenizers, text_encoders): + if isinstance(self, TextualInversionLoaderMixin): + prompt = self.maybe_convert_prompt(prompt, tokenizer) + + text_inputs = tokenizer( + prompt, + padding="max_length", + max_length=tokenizer.model_max_length, + truncation=True, + return_tensors="pt", + ) + + text_input_ids = text_inputs.input_ids + untruncated_ids = tokenizer(prompt, padding="longest", return_tensors="pt").input_ids + + if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal( + text_input_ids, untruncated_ids + ): + removed_text = tokenizer.batch_decode(untruncated_ids[:, tokenizer.model_max_length - 1 : -1]) + logger.warning( + "The following part of your input was truncated because CLIP can only handle sequences up to" + f" {tokenizer.model_max_length} tokens: {removed_text}" + ) + + prompt_embeds = text_encoder(text_input_ids.to(device), output_hidden_states=True) + + # We are only ALWAYS interested in the pooled output of the final text encoder + if pooled_prompt_embeds is None and prompt_embeds[0].ndim == 2: + pooled_prompt_embeds = prompt_embeds[0] + + if clip_skip is None: + prompt_embeds = prompt_embeds.hidden_states[-2] + else: + # "2" because SDXL always indexes from the penultimate layer. + prompt_embeds = prompt_embeds.hidden_states[-(clip_skip + 2)] + + prompt_embeds_list.append(prompt_embeds) + + prompt_embeds = torch.concat(prompt_embeds_list, dim=-1) + + # get unconditional embeddings for classifier free guidance + zero_out_negative_prompt = negative_prompt is None and self.config.force_zeros_for_empty_prompt + if do_classifier_free_guidance and negative_prompt_embeds is None and zero_out_negative_prompt: + negative_prompt_embeds = torch.zeros_like(prompt_embeds) + negative_pooled_prompt_embeds = torch.zeros_like(pooled_prompt_embeds) + elif do_classifier_free_guidance and negative_prompt_embeds is None: + negative_prompt = negative_prompt or "" + negative_prompt_2 = negative_prompt_2 or negative_prompt + + # normalize str to list + negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt + negative_prompt_2 = ( + batch_size * [negative_prompt_2] if isinstance(negative_prompt_2, str) else negative_prompt_2 + ) + + uncond_tokens: List[str] + if prompt is not None and type(prompt) is not type(negative_prompt): + raise TypeError( + f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" + f" {type(prompt)}." + ) + elif batch_size != len(negative_prompt): + raise ValueError( + f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" + f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" + " the batch size of `prompt`." + ) + else: + uncond_tokens = [negative_prompt, negative_prompt_2] + + negative_prompt_embeds_list = [] + for negative_prompt, tokenizer, text_encoder in zip(uncond_tokens, tokenizers, text_encoders): + if isinstance(self, TextualInversionLoaderMixin): + negative_prompt = self.maybe_convert_prompt(negative_prompt, tokenizer) + + max_length = prompt_embeds.shape[1] + uncond_input = tokenizer( + negative_prompt, + padding="max_length", + max_length=max_length, + truncation=True, + return_tensors="pt", + ) + + negative_prompt_embeds = text_encoder( + uncond_input.input_ids.to(device), + output_hidden_states=True, + ) + + # We are only ALWAYS interested in the pooled output of the final text encoder + if negative_pooled_prompt_embeds is None and negative_prompt_embeds[0].ndim == 2: + negative_pooled_prompt_embeds = negative_prompt_embeds[0] + negative_prompt_embeds = negative_prompt_embeds.hidden_states[-2] + + negative_prompt_embeds_list.append(negative_prompt_embeds) + + negative_prompt_embeds = torch.concat(negative_prompt_embeds_list, dim=-1) + + if self.text_encoder_2 is not None: + prompt_embeds = prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device) + else: + prompt_embeds = prompt_embeds.to(dtype=self.unet.dtype, device=device) + + bs_embed, seq_len, _ = prompt_embeds.shape + # duplicate text embeddings for each generation per prompt, using mps friendly method + prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) + prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1) + + if do_classifier_free_guidance: + # duplicate unconditional embeddings for each generation per prompt, using mps friendly method + seq_len = negative_prompt_embeds.shape[1] + + if self.text_encoder_2 is not None: + negative_prompt_embeds = negative_prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device) + else: + negative_prompt_embeds = negative_prompt_embeds.to(dtype=self.unet.dtype, device=device) + + negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) + negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + + pooled_prompt_embeds = pooled_prompt_embeds.repeat(1, num_images_per_prompt).view( + bs_embed * num_images_per_prompt, -1 + ) + if do_classifier_free_guidance: + negative_pooled_prompt_embeds = negative_pooled_prompt_embeds.repeat(1, num_images_per_prompt).view( + bs_embed * num_images_per_prompt, -1 + ) + + if self.text_encoder is not None: + if isinstance(self, StableDiffusionXLLoraLoaderMixin) and USE_PEFT_BACKEND: + # Retrieve the original scale by scaling back the LoRA layers + unscale_lora_layers(self.text_encoder, lora_scale) + + if self.text_encoder_2 is not None: + if isinstance(self, StableDiffusionXLLoraLoaderMixin) and USE_PEFT_BACKEND: + # Retrieve the original scale by scaling back the LoRA layers + unscale_lora_layers(self.text_encoder_2, lora_scale) + + return prompt_embeds, negative_prompt_embeds, pooled_prompt_embeds, negative_pooled_prompt_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs + def prepare_extra_step_kwargs(self, generator, eta): + # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature + # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. + # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 + # and should be between [0, 1] + + accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) + extra_step_kwargs = {} + if accepts_eta: + extra_step_kwargs["eta"] = eta + + # check if the scheduler accepts generator + accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) + if accepts_generator: + extra_step_kwargs["generator"] = generator + return extra_step_kwargs + + def check_inputs(self, prompt, height, width, grid_cols, seed_tiles_mode, tiles_mode): + if height % 8 != 0 or width % 8 != 0: + raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.") + + if prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): + raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") + + if not isinstance(prompt, list) or not all(isinstance(row, list) for row in prompt): + raise ValueError(f"`prompt` has to be a list of lists but is {type(prompt)}") + + if not all(len(row) == grid_cols for row in prompt): + raise ValueError("All prompt rows must have the same number of prompt columns") + + if not isinstance(seed_tiles_mode, str) and ( + not isinstance(seed_tiles_mode, list) or not all(isinstance(row, list) for row in seed_tiles_mode) + ): + raise ValueError(f"`seed_tiles_mode` has to be a string or list of lists but is {type(prompt)}") + + if any(mode not in tiles_mode for row in seed_tiles_mode for mode in row): + raise ValueError(f"Seed tiles mode must be one of {tiles_mode}") + + def _get_add_time_ids( + self, original_size, crops_coords_top_left, target_size, dtype, text_encoder_projection_dim=None + ): + add_time_ids = list(original_size + crops_coords_top_left + target_size) + + passed_add_embed_dim = ( + self.unet.config.addition_time_embed_dim * len(add_time_ids) + text_encoder_projection_dim + ) + expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features + + if expected_add_embed_dim != passed_add_embed_dim: + raise ValueError( + 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`." + ) + + add_time_ids = torch.tensor([add_time_ids], dtype=dtype) + return add_time_ids + + def _gaussian_weights(self, tile_width, tile_height, nbatches, device, dtype): + """Generates a gaussian mask of weights for tile contributions""" + import numpy as np + from numpy import exp, pi, sqrt + + latent_width = tile_width // 8 + latent_height = tile_height // 8 + + var = 0.01 + midpoint = (latent_width - 1) / 2 # -1 because index goes from 0 to latent_width - 1 + x_probs = [ + exp(-(x - midpoint) * (x - midpoint) / (latent_width * latent_width) / (2 * var)) / sqrt(2 * pi * var) + for x in range(latent_width) + ] + midpoint = latent_height / 2 + y_probs = [ + exp(-(y - midpoint) * (y - midpoint) / (latent_height * latent_height) / (2 * var)) / sqrt(2 * pi * var) + for y in range(latent_height) + ] + + weights_np = np.outer(y_probs, x_probs) + weights_torch = torch.tensor(weights_np, device=device) + weights_torch = weights_torch.to(dtype) + return torch.tile(weights_torch, (nbatches, self.unet.config.in_channels, 1, 1)) + + def upcast_vae(self): + dtype = self.vae.dtype + self.vae.to(dtype=torch.float32) + use_torch_2_0_or_xformers = isinstance( + self.vae.decoder.mid_block.attentions[0].processor, + ( + AttnProcessor2_0, + XFormersAttnProcessor, + FusedAttnProcessor2_0, + ), + ) + # if xformers or torch_2_0 is used attention block does not need + # to be in float32 which can save lots of memory + if use_torch_2_0_or_xformers: + self.vae.post_quant_conv.to(dtype) + self.vae.decoder.conv_in.to(dtype) + self.vae.decoder.mid_block.to(dtype) + + # Copied from diffusers.pipelines.latent_consistency_models.pipeline_latent_consistency_text2img.LatentConsistencyModelPipeline.get_guidance_scale_embedding + def get_guidance_scale_embedding( + self, w: torch.Tensor, embedding_dim: int = 512, dtype: torch.dtype = torch.float32 + ) -> torch.Tensor: + """ + See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298 + + Args: + w (`torch.Tensor`): + Generate embedding vectors with a specified guidance scale to subsequently enrich timestep embeddings. + embedding_dim (`int`, *optional*, defaults to 512): + Dimension of the embeddings to generate. + dtype (`torch.dtype`, *optional*, defaults to `torch.float32`): + Data type of the generated embeddings. + + Returns: + `torch.Tensor`: Embedding vectors with shape `(len(w), embedding_dim)`. + """ + assert len(w.shape) == 1 + w = w * 1000.0 + + half_dim = embedding_dim // 2 + emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1) + emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb) + emb = w.to(dtype)[:, None] * emb[None, :] + emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1) + if embedding_dim % 2 == 1: # zero pad + emb = torch.nn.functional.pad(emb, (0, 1)) + assert emb.shape == (w.shape[0], embedding_dim) + return emb + + @property + def guidance_scale(self): + return self._guidance_scale + + @property + def clip_skip(self): + return self._clip_skip + + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + @property + def do_classifier_free_guidance(self): + return self._guidance_scale > 1 and self.unet.config.time_cond_proj_dim is None + + @property + def cross_attention_kwargs(self): + return self._cross_attention_kwargs + + @property + def num_timesteps(self): + return self._num_timesteps + + @property + def interrupt(self): + return self._interrupt + + @torch.no_grad() + @replace_example_docstring(EXAMPLE_DOC_STRING) + def __call__( + self, + prompt: Union[str, List[str]] = None, + height: Optional[int] = None, + width: Optional[int] = None, + num_inference_steps: int = 50, + guidance_scale: float = 5.0, + negative_prompt: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + original_size: Optional[Tuple[int, int]] = None, + crops_coords_top_left: Optional[List[List[Tuple[int, int]]]] = None, + target_size: Optional[Tuple[int, int]] = None, + negative_original_size: Optional[Tuple[int, int]] = None, + negative_crops_coords_top_left: Optional[List[List[Tuple[int, int]]]] = None, + negative_target_size: Optional[Tuple[int, int]] = None, + clip_skip: Optional[int] = None, + tile_height: Optional[int] = 1024, + tile_width: Optional[int] = 1024, + tile_row_overlap: Optional[int] = 128, + tile_col_overlap: Optional[int] = 128, + guidance_scale_tiles: Optional[List[List[float]]] = None, + seed_tiles: Optional[List[List[int]]] = None, + seed_tiles_mode: Optional[Union[str, List[List[str]]]] = "full", + seed_reroll_regions: Optional[List[Tuple[int, int, int, int, int]]] = None, + **kwargs, + ): + r""" + Function invoked when calling the pipeline for generation. + + Args: + prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. + instead. + height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor): + The height in pixels of the generated image. This is set to 1024 by default for the best results. + Anything below 512 pixels won't work well for + [stabilityai/stable-diffusion-xl-base-1.0](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0) + and checkpoints that are not specifically fine-tuned on low resolutions. + width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor): + The width in pixels of the generated image. This is set to 1024 by default for the best results. + Anything below 512 pixels won't work well for + [stabilityai/stable-diffusion-xl-base-1.0](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0) + and checkpoints that are not specifically fine-tuned on low resolutions. + num_inference_steps (`int`, *optional*, defaults to 50): + The number of denoising steps. More denoising steps usually lead to a higher quality image at the + expense of slower inference. + guidance_scale (`float`, *optional*, defaults to 5.0): + Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). + `guidance_scale` is defined as `w` of equation 2. of [Imagen + Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > + 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, + usually at the expense of lower image quality. + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + num_images_per_prompt (`int`, *optional*, defaults to 1): + The number of images to generate per prompt. + eta (`float`, *optional*, defaults to 0.0): + Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to + [`schedulers.DDIMScheduler`], will be ignored for others. + generator (`torch.Generator` or `List[torch.Generator]`, *optional*): + One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html) + to make generation deterministic. + output_type (`str`, *optional*, defaults to `"pil"`): + The output format of the generate image. Choose between + [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] instead + of a plain tuple. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under + `self.processor` in + [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + original_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + If `original_size` is not the same as `target_size` the image will appear to be down- or upsampled. + `original_size` defaults to `(height, width)` if not specified. Part of SDXL's micro-conditioning as + explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + crops_coords_top_left (`List[List[Tuple[int, int]]]`, *optional*, defaults to (0, 0)): + `crops_coords_top_left` can be used to generate an image that appears to be "cropped" from the position + `crops_coords_top_left` downwards. Favorable, well-centered images are usually achieved by setting + `crops_coords_top_left` to (0, 0). Part of SDXL's micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + target_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + For most cases, `target_size` should be set to the desired height and width of the generated image. If + not specified it will default to `(height, width)`. Part of SDXL's micro-conditioning as explained in + section 2.2 of [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + negative_original_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + To negatively condition the generation process based on a specific image resolution. Part of SDXL's + micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). For more + information, refer to this issue thread: https://github.com/huggingface/diffusers/issues/4208. + negative_crops_coords_top_left (`List[List[Tuple[int, int]]]`, *optional*, defaults to (0, 0)): + To negatively condition the generation process based on a specific crop coordinates. Part of SDXL's + micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). For more + information, refer to this issue thread: https://github.com/huggingface/diffusers/issues/4208. + negative_target_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + To negatively condition the generation process based on a target image resolution. It should be as same + as the `target_size` for most cases. Part of SDXL's micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). For more + information, refer to this issue thread: https://github.com/huggingface/diffusers/issues/4208. + tile_height (`int`, *optional*, defaults to 1024): + Height of each grid tile in pixels. + tile_width (`int`, *optional*, defaults to 1024): + Width of each grid tile in pixels. + tile_row_overlap (`int`, *optional*, defaults to 128): + Number of overlapping pixels between tiles in consecutive rows. + tile_col_overlap (`int`, *optional*, defaults to 128): + Number of overlapping pixels between tiles in consecutive columns. + guidance_scale_tiles (`List[List[float]]`, *optional*): + Specific weights for classifier-free guidance in each tile. If `None`, the value provided in `guidance_scale` will be used. + seed_tiles (`List[List[int]]`, *optional*): + Specific seeds for the initialization latents in each tile. These will override the latents generated for the whole canvas using the standard `generator` parameter. + seed_tiles_mode (`Union[str, List[List[str]]]`, *optional*, defaults to `"full"`): + Mode for seeding tiles, can be `"full"` or `"exclusive"`. If `"full"`, all the latents affected by the tile will be overridden. If `"exclusive"`, only the latents that are exclusively affected by this tile (and no other tiles) will be overridden. + seed_reroll_regions (`List[Tuple[int, int, int, int, int]]`, *optional*): + A list of tuples in the form of `(start_row, end_row, start_column, end_column, seed)` defining regions in pixel space for which the latents will be overridden using the given seed. Takes priority over `seed_tiles`. + **kwargs (`Dict[str, Any]`, *optional*): + Additional optional keyword arguments to be passed to the `unet.__call__` and `scheduler.step` functions. + + Examples: + + Returns: + [`~pipelines.stable_diffusion_xl.StableDiffusionXLTilingPipelineOutput`] or `tuple`: + [`~pipelines.stable_diffusion_xl.StableDiffusionXLTilingPipelineOutput`] if `return_dict` is True, otherwise a + `tuple`. When returning a tuple, the first element is a list with the generated images. + """ + + # 0. Default height and width to unet + height = height or self.default_sample_size * self.vae_scale_factor + width = width or self.default_sample_size * self.vae_scale_factor + + original_size = original_size or (height, width) + target_size = target_size or (height, width) + negative_original_size = negative_original_size or (height, width) + negative_target_size = negative_target_size or (height, width) + + self._guidance_scale = guidance_scale + self._clip_skip = clip_skip + self._cross_attention_kwargs = cross_attention_kwargs + self._interrupt = False + + grid_rows = len(prompt) + grid_cols = len(prompt[0]) + tiles_mode = [mode.value for mode in self.SeedTilesMode] + + if isinstance(seed_tiles_mode, str): + seed_tiles_mode = [[seed_tiles_mode for _ in range(len(row))] for row in prompt] + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, + height, + width, + grid_cols, + seed_tiles_mode, + tiles_mode, + ) + + if seed_reroll_regions is None: + seed_reroll_regions = [] + + batch_size = 1 + + device = self._execution_device + + # update crops coords list + crops_coords_top_left = _get_crops_coords_list(grid_rows, grid_cols, tile_width) + if negative_original_size is not None and negative_target_size is not None: + negative_crops_coords_top_left = _get_crops_coords_list(grid_rows, grid_cols, tile_width) + + # update height and width tile size and tile overlap size + height = tile_height + (grid_rows - 1) * (tile_height - tile_row_overlap) + width = tile_width + (grid_cols - 1) * (tile_width - tile_col_overlap) + + # 3. Encode input prompt + lora_scale = ( + self.cross_attention_kwargs.get("scale", None) if self.cross_attention_kwargs is not None else None + ) + text_embeddings = [ + [ + self.encode_prompt( + prompt=col, + device=device, + num_images_per_prompt=num_images_per_prompt, + do_classifier_free_guidance=self.do_classifier_free_guidance, + negative_prompt=negative_prompt, + prompt_embeds=None, + negative_prompt_embeds=None, + pooled_prompt_embeds=None, + negative_pooled_prompt_embeds=None, + lora_scale=lora_scale, + clip_skip=self.clip_skip, + ) + for col in row + ] + for row in prompt + ] + + # 3. Prepare latents + latents_shape = (batch_size, self.unet.config.in_channels, height // 8, width // 8) + dtype = text_embeddings[0][0][0].dtype + latents = randn_tensor(latents_shape, generator=generator, device=device, dtype=dtype) + + # 3.1 overwrite latents for specific tiles if provided + if seed_tiles is not None: + for row in range(grid_rows): + for col in range(grid_cols): + if (seed_tile := seed_tiles[row][col]) is not None: + mode = seed_tiles_mode[row][col] + if mode == self.SeedTilesMode.FULL.value: + row_init, row_end, col_init, col_end = _tile2latent_indices( + row, col, tile_width, tile_height, tile_row_overlap, tile_col_overlap + ) + else: + row_init, row_end, col_init, col_end = _tile2latent_exclusive_indices( + row, + col, + tile_width, + tile_height, + tile_row_overlap, + tile_col_overlap, + grid_rows, + grid_cols, + ) + tile_generator = torch.Generator(device).manual_seed(seed_tile) + tile_shape = (latents_shape[0], latents_shape[1], row_end - row_init, col_end - col_init) + latents[:, :, row_init:row_end, col_init:col_end] = torch.randn( + tile_shape, generator=tile_generator, device=device + ) + + # 3.2 overwrite again for seed reroll regions + for row_init, row_end, col_init, col_end, seed_reroll in seed_reroll_regions: + row_init, row_end, col_init, col_end = _pixel2latent_indices( + row_init, row_end, col_init, col_end + ) # to latent space coordinates + reroll_generator = torch.Generator(device).manual_seed(seed_reroll) + region_shape = (latents_shape[0], latents_shape[1], row_end - row_init, col_end - col_init) + latents[:, :, row_init:row_end, col_init:col_end] = torch.randn( + region_shape, generator=reroll_generator, device=device + ) + + # 4. Prepare timesteps + accepts_offset = "offset" in set(inspect.signature(self.scheduler.set_timesteps).parameters.keys()) + extra_set_kwargs = {} + if accepts_offset: + extra_set_kwargs["offset"] = 1 + timesteps, num_inference_steps = retrieve_timesteps( + self.scheduler, num_inference_steps, device, None, None, **extra_set_kwargs + ) + + # if we use LMSDiscreteScheduler, let's make sure latents are multiplied by sigmas + if isinstance(self.scheduler, LMSDiscreteScheduler): + latents = latents * self.scheduler.sigmas[0] + + # 5. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 6. Prepare added time ids & embeddings + # text_embeddings order: prompt_embeds, negative_prompt_embeds, pooled_prompt_embeds, negative_pooled_prompt_embeds + embeddings_and_added_time = [] + for row in range(grid_rows): + addition_embed_type_row = [] + for col in range(grid_cols): + # extract generated values + prompt_embeds = text_embeddings[row][col][0] + negative_prompt_embeds = text_embeddings[row][col][1] + pooled_prompt_embeds = text_embeddings[row][col][2] + negative_pooled_prompt_embeds = text_embeddings[row][col][3] + + add_text_embeds = pooled_prompt_embeds + if self.text_encoder_2 is None: + text_encoder_projection_dim = int(pooled_prompt_embeds.shape[-1]) + else: + text_encoder_projection_dim = self.text_encoder_2.config.projection_dim + add_time_ids = self._get_add_time_ids( + original_size, + crops_coords_top_left[row][col], + target_size, + dtype=prompt_embeds.dtype, + text_encoder_projection_dim=text_encoder_projection_dim, + ) + if negative_original_size is not None and negative_target_size is not None: + negative_add_time_ids = self._get_add_time_ids( + negative_original_size, + negative_crops_coords_top_left[row][col], + negative_target_size, + dtype=prompt_embeds.dtype, + text_encoder_projection_dim=text_encoder_projection_dim, + ) + else: + negative_add_time_ids = add_time_ids + + if self.do_classifier_free_guidance: + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0) + add_text_embeds = torch.cat([negative_pooled_prompt_embeds, add_text_embeds], dim=0) + add_time_ids = torch.cat([negative_add_time_ids, add_time_ids], dim=0) + + prompt_embeds = prompt_embeds.to(device) + add_text_embeds = add_text_embeds.to(device) + add_time_ids = add_time_ids.to(device).repeat(batch_size * num_images_per_prompt, 1) + addition_embed_type_row.append((prompt_embeds, add_text_embeds, add_time_ids)) + embeddings_and_added_time.append(addition_embed_type_row) + + num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) + + # 7. Mask for tile weights strength + tile_weights = self._gaussian_weights(tile_width, tile_height, batch_size, device, torch.float32) + + # 8. Denoising loop + self._num_timesteps = len(timesteps) + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + # Diffuse each tile + noise_preds = [] + for row in range(grid_rows): + noise_preds_row = [] + for col in range(grid_cols): + if self.interrupt: + continue + px_row_init, px_row_end, px_col_init, px_col_end = _tile2latent_indices( + row, col, tile_width, tile_height, tile_row_overlap, tile_col_overlap + ) + tile_latents = latents[:, :, px_row_init:px_row_end, px_col_init:px_col_end] + # expand the latents if we are doing classifier free guidance + latent_model_input = ( + torch.cat([tile_latents] * 2) if self.do_classifier_free_guidance else tile_latents + ) + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # predict the noise residual + added_cond_kwargs = { + "text_embeds": embeddings_and_added_time[row][col][1], + "time_ids": embeddings_and_added_time[row][col][2], + } + with torch.amp.autocast(device.type, dtype=dtype, enabled=dtype != self.unet.dtype): + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=embeddings_and_added_time[row][col][0], + cross_attention_kwargs=self.cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=False, + )[0] + + # perform guidance + if self.do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + guidance = ( + guidance_scale + if guidance_scale_tiles is None or guidance_scale_tiles[row][col] is None + else guidance_scale_tiles[row][col] + ) + noise_pred_tile = noise_pred_uncond + guidance * (noise_pred_text - noise_pred_uncond) + noise_preds_row.append(noise_pred_tile) + noise_preds.append(noise_preds_row) + + # Stitch noise predictions for all tiles + noise_pred = torch.zeros(latents.shape, device=device) + contributors = torch.zeros(latents.shape, device=device) + + # Add each tile contribution to overall latents + for row in range(grid_rows): + for col in range(grid_cols): + px_row_init, px_row_end, px_col_init, px_col_end = _tile2latent_indices( + row, col, tile_width, tile_height, tile_row_overlap, tile_col_overlap + ) + noise_pred[:, :, px_row_init:px_row_end, px_col_init:px_col_end] += ( + noise_preds[row][col] * tile_weights + ) + contributors[:, :, px_row_init:px_row_end, px_col_init:px_col_end] += tile_weights + + # Average overlapping areas with more than 1 contributor + noise_pred /= contributors + noise_pred = noise_pred.to(dtype) + + # compute the previous noisy sample x_t -> x_t-1 + latents_dtype = latents.dtype + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + if latents.dtype != latents_dtype: + if torch.backends.mps.is_available(): + # some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272 + latents = latents.to(latents_dtype) + + # update progress bar + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + + if XLA_AVAILABLE: + xm.mark_step() + + if output_type != "latent": + # make sure the VAE is in float32 mode, as it overflows in float16 + needs_upcasting = self.vae.dtype == torch.float16 and self.vae.config.force_upcast + + if needs_upcasting: + self.upcast_vae() + latents = latents.to(next(iter(self.vae.post_quant_conv.parameters())).dtype) + elif latents.dtype != self.vae.dtype: + if torch.backends.mps.is_available(): + # some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272 + self.vae = self.vae.to(latents.dtype) + + # unscale/denormalize the latents + # denormalize with the mean and std if available and not None + has_latents_mean = hasattr(self.vae.config, "latents_mean") and self.vae.config.latents_mean is not None + has_latents_std = hasattr(self.vae.config, "latents_std") and self.vae.config.latents_std is not None + if has_latents_mean and has_latents_std: + latents_mean = ( + torch.tensor(self.vae.config.latents_mean).view(1, 4, 1, 1).to(latents.device, latents.dtype) + ) + latents_std = ( + torch.tensor(self.vae.config.latents_std).view(1, 4, 1, 1).to(latents.device, latents.dtype) + ) + latents = latents * latents_std / self.vae.config.scaling_factor + latents_mean + else: + latents = latents / self.vae.config.scaling_factor + + image = self.vae.decode(latents, return_dict=False)[0] + + # cast back to fp16 if needed + if needs_upcasting: + self.vae.to(dtype=torch.float16) + else: + image = latents + + if output_type != "latent": + # apply watermark if available + if self.watermark is not None: + image = self.watermark.apply_watermark(image) + + image = self.image_processor.postprocess(image, output_type=output_type) + + # Offload all models + self.maybe_free_model_hooks() + + if not return_dict: + return (image,) + + return StableDiffusionXLPipelineOutput(images=image) diff --git a/modules/sd_detect.py b/modules/sd_detect.py index 514517c8d..de30063e8 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -86,7 +86,7 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): pipeline = 'custom' if 'sd3' in f.lower(): guess = 'Stable Diffusion 3' - if 'flux' in f.lower(): + if 'flux' in f.lower() or 'flex.1' in f.lower(): guess = 'FLUX' if size > 11000 and size < 16000: warn(f'Model detected as FLUX UNET model, but attempting to load a base model: {op}={f} size={size} MB') diff --git a/scripts/mixture_of_diffusers.py b/scripts/mixture_of_diffusers.py new file mode 100644 index 000000000..e3d6d20c6 --- /dev/null +++ b/scripts/mixture_of_diffusers.py @@ -0,0 +1,37 @@ +import gradio as gr +from modules import scripts, processing, shared + + +class Script(scripts.Script): + def __init__(self): + super().__init__() + self.orig_pipe = None + + def title(self): + return 'Mixture-of-Diffusers' + + def show(self, is_img2img): + return shared.native + + def ui(self, _is_img2img): # ui elements + with gr.Row(): + gr.HTML('  Mixture-of-Diffusers
') + return [] + + def run(self, p: processing.StableDiffusionProcessing): # pylint: disable=arguments-differ, unused-argument + supported_model_list = ['sdxl'] + if shared.sd_model_type not in supported_model_list: + shared.log.warning(f'MoD: class={shared.sd_model.__class__.__name__} model={shared.sd_model_type} required={supported_model_list}') + return None + self.orig_pipe = shared.sd_model + + shared.log.info(f'MoD: ') + + + def after(self, p: processing.StableDiffusionProcessing, processed: processing.Processed): # pylint: disable=arguments-differ, unused-argument + if self.orig_pipe is None: + return processed + if shared.sd_model_type == "sdxl": + shared.sd_model = self.orig_pipe + self.orig_pipe = None + return processed diff --git a/wiki b/wiki index 80b43575a..1a0923424 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 80b43575ac976e311983ffe0c37887158016ea94 +Subproject commit 1a0923424cac03d56ad382eacc608cf1094d6a60