From fd29acd2f4e6859054e061b2a403703b4f82b4cd Mon Sep 17 00:00:00 2001 From: Jingya HUANG <44135271+JingyaHuang@users.noreply.github.com> Date: Sat, 9 Sep 2023 00:30:36 +0200 Subject: [PATCH] Add Stable Diffusion XL inference support (#212) * fix sdxl unet inf * inference done * add post processing and doc * fix style * Update docs/source/guides/models.mdx Co-authored-by: Pedro Cuenca * add test * update doc prompt * fix num images per prompt issue * fix test * remove useless * Update optimum/neuron/pipelines/diffusers/pipeline_stable_diffusion_xl.py Co-authored-by: Michael Benayoun * fix docstring * update image * fix * remove text encoder 2 empty folder --------- Co-authored-by: JingyaHuang Co-authored-by: Pedro Cuenca Co-authored-by: Michael Benayoun --- docs/assets/guides/models/02-sdxl-image.jpeg | Bin 0 -> 125994 bytes docs/source/guides/export_model.mdx | 34 +- docs/source/guides/models.mdx | 41 +- optimum/exporters/neuron/__init__.py | 7 +- optimum/exporters/neuron/__main__.py | 47 +- optimum/exporters/neuron/base.py | 27 +- optimum/exporters/neuron/convert.py | 39 +- optimum/exporters/neuron/model_configs.py | 22 + optimum/exporters/neuron/utils.py | 14 +- optimum/neuron/__init__.py | 6 +- optimum/neuron/modeling_base.py | 7 +- optimum/neuron/modeling_diffusion.py | 275 +++++++-- .../diffusers/pipeline_stable_diffusion.py | 12 +- .../diffusers/pipeline_stable_diffusion_xl.py | 524 ++++++++++++++++++ tests/inference/inference_utils.py | 1 + .../test_stable_diffusion_pipeline.py | 81 ++- 16 files changed, 1015 insertions(+), 122 deletions(-) create mode 100644 docs/assets/guides/models/02-sdxl-image.jpeg create mode 100644 optimum/neuron/pipelines/diffusers/pipeline_stable_diffusion_xl.py diff --git a/docs/assets/guides/models/02-sdxl-image.jpeg b/docs/assets/guides/models/02-sdxl-image.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..11a17f7c3c629c1681ca7e3c1f2a2e4992ec0655 GIT binary patch literal 125994 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
I5)OZLkwfO!K2LanPv1xqFX}*5lI*z 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ȹI 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
k|sEk#onqw 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 literal 0 HcmV?d00001 diff --git a/docs/source/guides/export_model.mdx b/docs/source/guides/export_model.mdx index 5936b90cf..fb1d98a7a 100644 --- a/docs/source/guides/export_model.mdx +++ b/docs/source/guides/export_model.mdx @@ -239,7 +239,39 @@ optimum-cli export neuron --model stabilityai/stable-diffusion-2-1-base \ --batch_size 1 \ --height 512 `# height in pixels of generated image, eg. 512, 768` \ --width 512 `# width in pixels of generated image, eg. 512, 768` \ - --num_image_per_prompt 4 `# number of images to generate per prompt, defaults to 1` \ + --num_images_per_prompt 4 `# number of images to generate per prompt, defaults to 1` \ + --auto_cast matmul `# cast only matrix multiplication operations` \ + --auto_cast_type bf16 `# cast operations from FP32 to BF16` \ + sd_neuron/ +``` + +## Exporting Stable Diffusion XL to Neuron + +Similar to Stable Diffusion, you will be able to use Optimum CLI to compile components in the SDXL pipeline for inference on neuron devices. + +We support the export of following components in the pipeline to boost the speed: + +* Text encoder +* Second text encoder +* U-Net (a three times larger UNet than the one in Stable Diffusion pipeline) +* VAE encoder +* VAE decoder + + + +"Stable Diffusion XL works especially well with images between 768 and 1024." + + + +Exporting a SDXL checkpoint can be done using the CLI: + +```bash +optimum-cli export neuron --model stabilityai/stable-diffusion-xl-base-1.0 \ + --task stable-diffusion-xl \ + --batch_size 1 \ + --height 1024 `# height in pixels of generated image, eg. 768, 1024` \ + --width 1024 `# width in pixels of generated image, eg. 768, 1024` \ + --num_images_per_prompt 4 `# number of images to generate per prompt, defaults to 1` \ --auto_cast matmul `# cast only matrix multiplication operations` \ --auto_cast_type bf16 `# cast operations from FP32 to BF16` \ sd_neuron/ diff --git a/docs/source/guides/models.mdx b/docs/source/guides/models.mdx index f33f33316..8d3f9662a 100644 --- a/docs/source/guides/models.mdx +++ b/docs/source/guides/models.mdx @@ -67,7 +67,7 @@ And the next time when you want to run inference, just load your compiled model As you see, there is no need to pass the neuron arguments used during the export as they are saved in a `config.json` file, and will be restored automatically by `NeuronModelForXXX` class. -## Export and inference of Discriminative NLP models +## Discriminative NLP models As explained in the previous section, you will need only few modifications to your Transformers code to export and run NLP models: @@ -133,7 +133,7 @@ No worries, `NeuronModelForXXX` class will pad your inputs to an eligible shape. -## Export and inference of Generative NLP models +## Generative NLP models As explained before, you will need only a few modifications to your Transformers code to export and run NLP models: @@ -196,7 +196,7 @@ with torch.inference_mode(): print(outputs) ``` -## Inference of Stable Diffusion Models +## Stable Diffusion Optimum extends 🤗`Diffusers` to support inference on Neuron. To get started, make sure you have installed Diffusers: @@ -247,7 +247,40 @@ Now generate an image with a prompt on neuron: search ami + +## Stable Diffusion XL + +Similar to Stable Diffusion, you will be able to use `NeuronStableDiffusionXLPipeline` API to export and run inference on Neuron devices with SDXL models. + +```python +>>> from optimum.neuron import NeuronStableDiffusionXLPipeline + +>>> model_id = "stabilityai/stable-diffusion-xl-base-1.0" +>>> compiler_args = {"auto_cast": "matmul", "auto_cast_type": "bf16"} +>>> input_shapes = {"batch_size": 1, "height": 1024, "width": 1024} + +>>> stable_diffusion_xl = NeuronStableDiffusionXLPipeline.from_pretrained(model_id, export=True, **compiler_args, **input_shapes) + +# Save locally or upload to the HuggingFace Hub +>>> save_directory = "sd_neuron_xl/" +>>> stable_diffusion_xl.save_pretrained(save_directory) +>>> stable_diffusion_xl.push_to_hub( +... save_directory, repository_id="my-neuron-repo", use_auth_token=True +... ) +``` + +Now generate an image with a prompt on neuron: + +```python +>>> prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" +>>> image = stable_diffusion_xl(prompt).images[0] +``` + +sdxl generated image diff --git a/optimum/exporters/neuron/__init__.py b/optimum/exporters/neuron/__init__.py index a23c8e230..c6ea726f0 100644 --- a/optimum/exporters/neuron/__init__.py +++ b/optimum/exporters/neuron/__init__.py @@ -13,7 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .__main__ import main_export, normalize_input_shapes, normalize_stable_diffusion_input_shapes +from .__main__ import ( + infer_stable_diffusion_shapes_from_diffusers, + main_export, + normalize_input_shapes, + normalize_stable_diffusion_input_shapes, +) from .base import NeuronConfig from .convert import export, export_models, validate_model_outputs, validate_models_outputs from .utils import ( diff --git a/optimum/exporters/neuron/__main__.py b/optimum/exporters/neuron/__main__.py index 82b522313..f4c4d340e 100644 --- a/optimum/exporters/neuron/__main__.py +++ b/optimum/exporters/neuron/__main__.py @@ -116,21 +116,20 @@ def normalize_stable_diffusion_input_shapes( ) -> Dict[str, Dict[str, int]]: args = vars(args) if isinstance(args, argparse.Namespace) else args mandatory_axes = set(getattr(inspect.getfullargspec(build_stable_diffusion_components_mandatory_shapes), "args")) - # Remove `sequence_length` as diffusers will pad it to the max and remove number of channels . + # Remove `sequence_length` as diffusers will pad it to the max and remove number of channels. mandatory_axes = mandatory_axes - { "sequence_length", "unet_num_channels", "vae_encoder_num_channels", "vae_decoder_num_channels", + "num_images_per_prompt", # default to 1 } if not mandatory_axes.issubset(set(args.keys())): raise AttributeError( f"Shape of {mandatory_axes} are mandatory for neuron compilation, while {mandatory_axes.difference(args.keys())} are not given." ) mandatory_shapes = {name: args[name] for name in mandatory_axes} - if "num_images_per_prompt" in args and args["num_images_per_prompt"] > 1: - batch_size = args["num_images_per_prompt"] * args["batch_size"] - mandatory_shapes["batch_size"] = batch_size + mandatory_shapes["num_images_per_prompt"] = args.get("num_images_per_prompt", 1) input_shapes = build_stable_diffusion_components_mandatory_shapes(**mandatory_shapes) return input_shapes @@ -184,18 +183,19 @@ def main_export( task = TasksManager.map_from_synonym(task) - model = TasksManager.get_model_from_task( - task=task, - model_name_or_path=model_name_or_path, - subfolder=subfolder, - revision=revision, - cache_dir=cache_dir, - use_auth_token=use_auth_token, - local_files_only=local_files_only, - force_download=force_download, - trust_remote_code=trust_remote_code, - framework="pt", - ) + model_kwargs = { + "task": task, + "model_name_or_path": model_name_or_path, + "subfolder": subfolder, + "revision": revision, + "cache_dir": cache_dir, + "use_auth_token": use_auth_token, + "local_files_only": local_files_only, + "force_download": force_download, + "trust_remote_code": trust_remote_code, + "framework": "pt", + } + model = TasksManager.get_model_from_task(**model_kwargs) is_stable_diffusion = "stable-diffusion" in task if not is_stable_diffusion: @@ -216,12 +216,6 @@ def main_export( "Stable diffusion export is not supported by neuron-cc on inf1, please use neuronx-cc on either inf2/trn1 instead." ) input_shapes = infer_stable_diffusion_shapes_from_diffusers(input_shapes, model) - models_and_neuron_configs = get_stable_diffusion_models_for_export( - pipeline=model, - task=task, - dynamic_batch_size=dynamic_batch_size, - **input_shapes, - ) # Saving the model config and preprocessor as this is needed sometimes. model.scheduler.save_pretrained(output.joinpath("scheduler")) @@ -232,6 +226,12 @@ def main_export( model.feature_extractor.save_pretrained(output.joinpath("feature_extractor")) model.save_config(output) + models_and_neuron_configs = get_stable_diffusion_models_for_export( + pipeline=model, + task=task, + dynamic_batch_size=dynamic_batch_size, + **input_shapes, + ) output_model_names = { DIFFUSION_MODEL_TEXT_ENCODER_NAME: os.path.join(DIFFUSION_MODEL_TEXT_ENCODER_NAME, NEURON_FILE_NAME), DIFFUSION_MODEL_UNET_NAME: os.path.join(DIFFUSION_MODEL_UNET_NAME, NEURON_FILE_NAME), @@ -242,6 +242,7 @@ def main_export( output_model_names[DIFFUSION_MODEL_TEXT_ENCODER_2_NAME] = os.path.join( DIFFUSION_MODEL_TEXT_ENCODER_2_NAME, NEURON_FILE_NAME ) + del model _, neuron_outputs = export_models( models_and_neuron_configs=models_and_neuron_configs, @@ -250,8 +251,6 @@ def main_export( compiler_kwargs=compiler_kwargs, ) - del model - # Validate compiled model if do_validation is True: if is_stable_diffusion: diff --git a/optimum/exporters/neuron/base.py b/optimum/exporters/neuron/base.py index 035c791be..d181e7c38 100644 --- a/optimum/exporters/neuron/base.py +++ b/optimum/exporters/neuron/base.py @@ -276,12 +276,28 @@ def generate_dummy_inputs( else: return dummy_inputs + @classmethod + def flatten_inputs(cls, inputs: Dict[str, Any]) -> Dict[str, Any]: + """ + Flatten nested structure in dummy inputs, e.g `addition_embed_type` of unet model. + """ + flatten = {} + for name, value in inputs.items(): + if isinstance(value, dict): + for sub_name, sub_value in value.items(): + flatten[sub_name] = sub_value + else: + flatten[name] = value + return flatten + def check_model_inputs_order( self, model: "PreTrainedModel", - dummy_inputs: Dict[str, torch.Tensor], + dummy_inputs: Optional[Dict[str, torch.Tensor]] = None, forward_with_tuple: bool = False, eligible_outputs: Optional[List[Union[str, int]]] = None, + custom_model_wrapper: Optional[torch.nn.Module] = None, + custom_wrapper_kwargs: Optional[Dict] = None, ): """ Checks if inputs order of the model's forward pass correspond to the generated dummy inputs to ensure the dummy inputs tuple used for @@ -320,7 +336,14 @@ def forward(self, *input): return outputs - return ModelWrapper(model, list(dummy_inputs.keys())) + if custom_model_wrapper: + return ( + custom_model_wrapper(model) + if custom_wrapper_kwargs is None + else custom_model_wrapper(model, **custom_wrapper_kwargs) + ) + else: + return ModelWrapper(model, list(dummy_inputs.keys())) class NeuronDecoderConfig(ExportConfig): diff --git a/optimum/exporters/neuron/convert.py b/optimum/exporters/neuron/convert.py index 59aa6e1fc..a9deefa9b 100644 --- a/optimum/exporters/neuron/convert.py +++ b/optimum/exporters/neuron/convert.py @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """Neuron compiled model check and export functions.""" - import copy +import os import time from collections import OrderedDict from pathlib import Path @@ -22,7 +22,6 @@ import numpy as np import torch -from packaging import version from transformers import PretrainedConfig from ...exporters.error_utils import OutputMatchError, ShapeError @@ -178,7 +177,7 @@ def validate_model_outputs( neuron_inputs = ref_inputs else: ref_outputs = reference_model(**ref_inputs) - neuron_inputs = tuple(ref_inputs.values()) + neuron_inputs = tuple(config.flatten_inputs(ref_inputs).values()) # Neuron outputs neuron_model = torch.jit.load(neuron_model_path) @@ -292,6 +291,7 @@ def export_models( ) failed_models = [] + total_compilation_time = 0 for i, model_name in enumerate(models_and_neuron_configs.keys()): logger.info(f"***** Compiling {model_name} *****") submodel, sub_neuron_config = models_and_neuron_configs[model_name] @@ -311,6 +311,7 @@ def export_models( **compiler_kwargs, ) compilation_time = time.time() - start_time + total_compilation_time += compilation_time logger.info(f"[Compilation Time] {np.round(compilation_time, 2)} seconds.") outputs.append((neuron_inputs, neuron_outputs)) # Add neuron specific configs to model components' original config @@ -347,6 +348,7 @@ def export_models( f"An error occured when trying to trace {model_name} with the error message: {e}.\n" f"The export is failed and {model_name} neuron model won't be stored." ) + logger.info(f"[Total compilation Time] {np.round(total_compilation_time, 2)} seconds.") # remove models failed to export for i, model_name in failed_models: @@ -421,6 +423,7 @@ def export_neuronx( input_shapes[axis] = getattr(config, axis) dummy_inputs = config.generate_dummy_inputs(**input_shapes) + dummy_inputs = config.flatten_inputs(dummy_inputs) dummy_inputs_tuple = tuple(dummy_inputs.values()) checked_model = config.check_model_inputs_order(model, dummy_inputs) @@ -435,6 +438,10 @@ def export_neuronx( else: compiler_args = ["--auto-cast", "none"] + # WARNING: Enabled experimental parallel compilation + compiler_args.extend(["--enable-experimental-O1"]) + compiler_args.extend(["--num-parallel-jobs", str(os.cpu_count())]) + # diffusers specific compiler_args = add_stable_diffusion_compiler_args(config, compiler_args) @@ -447,6 +454,9 @@ def export_neuronx( improve_stable_diffusion_loading(config, neuron_model) torch.jit.save(neuron_model, output) + del model + del checked_model + del dummy_inputs del neuron_model return config.inputs, config.outputs @@ -454,24 +464,26 @@ def export_neuronx( def add_stable_diffusion_compiler_args(config, compiler_args): if hasattr(config._config, "_name_or_path"): - sd_components = ["text_encoder", "unet", "vae", "vae_encoder", "vae_decoder"] + sd_components = ["text_encoder", "vae", "vae_encoder", "vae_decoder"] if any(component in config._config._name_or_path.lower() for component in sd_components): compiler_args.extend(["--enable-fast-loading-neuron-binaries"]) # unet if "unet" in config._config._name_or_path.lower(): + # SDXL unet doesn't support fast loading neuron binaries + if "stable-diffusion-xl" not in config._config._name_or_path.lower(): + compiler_args.extend(["--enable-fast-loading-neuron-binaries"]) compiler_args.extend(["--model-type=unet-inference"]) return compiler_args def improve_stable_diffusion_loading(config, neuron_model): - if version.parse(neuronx.__version__) >= version.parse("1.13.1.1.9.0"): - if hasattr(config._config, "_name_or_path"): - sd_components = ["text_encoder", "unet", "vae", "vae_encoder", "vae_decoder"] - if any(component in config._config._name_or_path.lower() for component in sd_components): - neuronx.async_load(neuron_model) - # unet - if "unet" in config._config._name_or_path.lower(): - neuronx.lazy_load(neuron_model) + if hasattr(config._config, "_name_or_path"): + sd_components = ["text_encoder", "unet", "vae", "vae_encoder", "vae_decoder"] + if any(component in config._config._name_or_path.lower() for component in sd_components): + neuronx.async_load(neuron_model) + # unet + if "unet" in config._config._name_or_path.lower(): + neuronx.lazy_load(neuron_model) def export_neuron( @@ -537,6 +549,9 @@ def export_neuron( fallback=not disable_fallback, ) torch.jit.save(neuron_model, output) + del model + del checked_model + del dummy_inputs del neuron_model return config.inputs, config.outputs diff --git a/optimum/exporters/neuron/model_configs.py b/optimum/exporters/neuron/model_configs.py index a42572410..3412902f8 100644 --- a/optimum/exporters/neuron/model_configs.py +++ b/optimum/exporters/neuron/model_configs.py @@ -277,6 +277,28 @@ def generate_dummy_inputs(self, return_tuple: bool = False, **kwargs): else: return dummy_inputs + class ModelWrapper(torch.nn.Module): + def __init__(self, model): + super().__init__() + self.model = model + + def forward(self, sample, timestep, encoder_hidden_states, text_embeds=None, time_ids=None): + out_tuple = self.model( + sample, + timestep.float().expand((sample.shape[0],)), + encoder_hidden_states, + added_cond_kwargs={"text_embeds": text_embeds, "time_ids": time_ids}, + return_dict=False, + ) + + return out_tuple + + def check_model_inputs_order(self, model, dummy_inputs): + return super().check_model_inputs_order( + model=model, + custom_model_wrapper=self.ModelWrapper, + ) + @register_in_tasks_manager("vae-encoder", *["semantic-segmentation"]) class VaeEncoderNeuronConfig(VisionNeuronConfig): diff --git a/optimum/exporters/neuron/utils.py b/optimum/exporters/neuron/utils.py index 2d0d21660..b7dbb3be2 100644 --- a/optimum/exporters/neuron/utils.py +++ b/optimum/exporters/neuron/utils.py @@ -19,8 +19,6 @@ from collections import OrderedDict from typing import TYPE_CHECKING, Dict, Optional, Tuple, Union -import torch -from packaging import version from transformers import PretrainedConfig from ...neuron.utils import ( @@ -92,22 +90,23 @@ def build_stable_diffusion_components_mandatory_shapes( vae_decoder_num_channels: Optional[int] = None, height: Optional[int] = None, width: Optional[int] = None, + num_images_per_prompt: Optional[int] = 1, ): text_encoder_input_shapes = {"batch_size": batch_size, "sequence_length": sequence_length} vae_encoder_input_shapes = { - "batch_size": batch_size, + "batch_size": batch_size * num_images_per_prompt, "num_channels": vae_encoder_num_channels, "height": height, "width": width, } vae_decoder_input_shapes = { - "batch_size": batch_size, + "batch_size": batch_size * num_images_per_prompt, "num_channels": vae_decoder_num_channels, "height": height, "width": width, } unet_input_shapes = { - "batch_size": batch_size, + "batch_size": batch_size * num_images_per_prompt, "sequence_length": sequence_length, "num_channels": unet_num_channels, "height": height, @@ -285,21 +284,18 @@ def _get_submodels_for_export_stable_diffusion( # VAE Encoder vae_encoder = copy.deepcopy(pipeline.vae) - if not version.parse(torch.__version__) >= version.parse("2.1.0"): - vae_encoder = override_diffusers_2_0_attn_processors(vae_encoder) vae_encoder.forward = lambda sample: {"latent_sample": vae_encoder.encode(x=sample)["latent_dist"].sample()} models_for_export.append((DIFFUSION_MODEL_VAE_ENCODER_NAME, vae_encoder)) # VAE Decoder vae_decoder = copy.deepcopy(pipeline.vae) - if not version.parse(torch.__version__) >= version.parse("2.1.0"): - vae_decoder = override_diffusers_2_0_attn_processors(vae_decoder) vae_decoder.forward = lambda latent_sample: vae_decoder.decode(z=latent_sample) models_for_export.append((DIFFUSION_MODEL_VAE_DECODER_NAME, vae_decoder)) return OrderedDict(models_for_export) +# Using xformers or torch_2_0 can avoid overflow on float16, do not apply this unless compilation error. def override_diffusers_2_0_attn_processors(model): for _, submodule in model.named_modules(): if isinstance(submodule, Attention): diff --git a/optimum/neuron/__init__.py b/optimum/neuron/__init__.py index f19556011..b76bb9e39 100644 --- a/optimum/neuron/__init__.py +++ b/optimum/neuron/__init__.py @@ -34,8 +34,7 @@ ], "modeling_diffusion": [ "NeuronStableDiffusionPipeline", - "NeuronStableDiffusionImg2ImgPipeline", - "NeuronStableDiffusionInpaintPipeline", + "NeuronStableDiffusionXLPipeline", ], "modeling_decoder": ["NeuronDecoderModel"], "accelerate": [ @@ -61,9 +60,8 @@ from .modeling_base import NeuronBaseModel from .modeling_decoder import NeuronDecoderModel from .modeling_diffusion import ( - NeuronStableDiffusionImg2ImgPipeline, - NeuronStableDiffusionInpaintPipeline, NeuronStableDiffusionPipeline, + NeuronStableDiffusionXLPipeline, ) from .pipelines import pipeline from .trainers import NeuronTrainer, Seq2SeqNeuronTrainer diff --git a/optimum/neuron/modeling_base.py b/optimum/neuron/modeling_base.py index 461ad858c..7e6e3da3f 100644 --- a/optimum/neuron/modeling_base.py +++ b/optimum/neuron/modeling_base.py @@ -99,10 +99,11 @@ def load_model(path: Union[str, Path]) -> torch.jit._script.ScriptModule: path (`Union[str, Path]`): Path of the compiled model. """ - if not isinstance(path, str): - path = str(path) + if not isinstance(path, Path): + path = Path(path) - return torch.jit.load(path) + if path.is_file(): + return torch.jit.load(path) def _save_pretrained(self, save_directory: Union[str, Path]): """ diff --git a/optimum/neuron/modeling_diffusion.py b/optimum/neuron/modeling_diffusion.py index 23eb0b55e..dfbed7286 100644 --- a/optimum/neuron/modeling_diffusion.py +++ b/optimum/neuron/modeling_diffusion.py @@ -33,7 +33,9 @@ from ..utils import is_diffusers_available from .modeling_base import NeuronBaseModel from .pipelines.diffusers.pipeline_stable_diffusion import StableDiffusionPipelineMixin +from .pipelines.diffusers.pipeline_stable_diffusion_xl import StableDiffusionXLPipelineMixin from .utils import ( + DIFFUSION_MODEL_TEXT_ENCODER_2_NAME, DIFFUSION_MODEL_TEXT_ENCODER_NAME, DIFFUSION_MODEL_UNET_NAME, DIFFUSION_MODEL_VAE_DECODER_NAME, @@ -48,10 +50,16 @@ if is_diffusers_available(): - from diffusers import DDIMScheduler, LMSDiscreteScheduler, PNDMScheduler, StableDiffusionPipeline + from diffusers import ( + DDIMScheduler, + LMSDiscreteScheduler, + PNDMScheduler, + StableDiffusionPipeline, + StableDiffusionXLImg2ImgPipeline, + ) from diffusers.image_processor import VaeImageProcessor from diffusers.schedulers.scheduling_utils import SCHEDULER_CONFIG_NAME - from diffusers.utils import CONFIG_NAME + from diffusers.utils import CONFIG_NAME, is_invisible_watermark_available if TYPE_CHECKING: @@ -71,11 +79,13 @@ def __init__( self, text_encoder: torch.jit._script.ScriptModule, unet: torch.jit._script.ScriptModule, - vae_encoder: torch.jit._script.ScriptModule, vae_decoder: torch.jit._script.ScriptModule, config: Dict[str, Any], tokenizer: CLIPTokenizer, scheduler: Union[DDIMScheduler, PNDMScheduler, LMSDiscreteScheduler], + vae_encoder: Optional[torch.jit._script.ScriptModule] = None, + text_encoder_2: Optional[torch.jit._script.ScriptModule] = None, + tokenizer_2: Optional[CLIPTokenizer] = None, feature_extractor: Optional[CLIPFeatureExtractor] = None, device_ids: Optional[List[int]] = None, configs: Optional[Dict[str, "PretrainedConfig"]] = None, @@ -89,8 +99,6 @@ def __init__( The Neuron TorchScript module associated to the text encoder. unet (`torch.jit._script.ScriptModule`): The Neuron TorchScript module associated to the U-NET. - vae_encoder (`torch.jit._script.ScriptModule`): - The Neuron TorchScript module associated to the VAE encoder. vae_decoder (`torch.jit._script.ScriptModule`): The Neuron TorchScript module associated to the VAE decoder. config (`Dict[str, Any]`): @@ -101,6 +109,13 @@ def __init__( [CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer). scheduler (`Union[DDIMScheduler, PNDMScheduler, LMSDiscreteScheduler]`): A scheduler to be used in combination with the U-NET component to denoise the encoded image latents. + vae_encoder (`Optional[torch.jit._script.ScriptModule]`, defaults to `None`): + The Neuron TorchScript module associated to the VAE encoder. + text_encoder_2 (`Optional[torch.jit._script.ScriptModule]`, defaults to `None`): + The Neuron TorchScript module associated to the 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_2 (`Optional[CLIPTokenizer]`, defaults to `None`): + Second tokenizer of class + [CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer). feature_extractor (`Optional[CLIPFeatureExtractor]`, defaults to `None`): A model extracting features from generated images to be used as inputs for the `safety_checker` device_ids (Optional[List[int]], defaults to `None`): @@ -129,14 +144,28 @@ def __init__( self.configs[DIFFUSION_MODEL_TEXT_ENCODER_NAME], self.neuron_configs[DIFFUSION_MODEL_TEXT_ENCODER_NAME], ) + self.text_encoder_2 = ( + NeuronModelTextEncoder( + text_encoder_2, + self, + self.configs[DIFFUSION_MODEL_TEXT_ENCODER_2_NAME], + self.neuron_configs[DIFFUSION_MODEL_TEXT_ENCODER_2_NAME], + ) + if text_encoder_2 is not None + else None + ) self.unet = NeuronModelUnet( unet, self, self.configs[DIFFUSION_MODEL_UNET_NAME], self.neuron_configs[DIFFUSION_MODEL_UNET_NAME] ) - self.vae_encoder = NeuronModelVaeEncoder( - vae_encoder, - self, - self.configs[DIFFUSION_MODEL_VAE_ENCODER_NAME], - self.neuron_configs[DIFFUSION_MODEL_VAE_ENCODER_NAME], + self.vae_encoder = ( + NeuronModelVaeEncoder( + vae_encoder, + self, + self.configs[DIFFUSION_MODEL_VAE_ENCODER_NAME], + self.neuron_configs[DIFFUSION_MODEL_VAE_ENCODER_NAME], + ) + if vae_encoder is not None + else None ) self.vae_decoder = NeuronModelVaeDecoder( vae_decoder, @@ -146,15 +175,20 @@ def __init__( ) self.tokenizer = tokenizer + self.tokenizer_2 = tokenizer_2 self.scheduler = scheduler self.feature_extractor = feature_extractor self.safety_checker = None sub_models = { DIFFUSION_MODEL_TEXT_ENCODER_NAME: self.text_encoder, DIFFUSION_MODEL_UNET_NAME: self.unet, - DIFFUSION_MODEL_VAE_ENCODER_NAME: self.vae_encoder, DIFFUSION_MODEL_VAE_DECODER_NAME: self.vae_decoder, } + if self.text_encoder_2 is not None: + sub_models[DIFFUSION_MODEL_TEXT_ENCODER_2_NAME] = self.text_encoder_2 + if self.vae_encoder is not None: + sub_models[DIFFUSION_MODEL_VAE_ENCODER_NAME] = self.vae_encoder + for name in sub_models.keys(): self._internal_dict[name] = ("optimum", sub_models[name].__class__.__name__) self._internal_dict.pop("vae", None) @@ -163,18 +197,69 @@ def __init__( self.model_and_config_save_paths = model_and_config_save_paths if model_and_config_save_paths else None if hasattr(self.vae_decoder.config, "block_out_channels"): - self.vae_scale_factor = 2 ** ( - len(self.vae_decoder.config.block_out_channels) - 1 - ) # not working for tiny test models, need to remove `block_out_channels` in `config.json`. + self.vae_scale_factor = 2 ** (len(self.vae_decoder.config.block_out_channels) - 1) else: self.vae_scale_factor = 8 + self.num_images_per_prompt = ( + self.neuron_configs["unet"].batch_size // self.neuron_configs["text_encoder"].batch_size + ) + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor) + @staticmethod + def load_model( + text_encoder_path: Union[str, Path], + unet_path: Union[str, Path], + vae_decoder_path: Union[str, Path], + vae_encoder_path: Optional[Union[str, Path]] = None, + text_encoder_2_path: Optional[Union[str, Path]] = None, + device_ids: Optional[List[int]] = None, + dynamic_batch_size: bool = False, + ): + """ + Loads Stable Diffusion TorchScript modules compiled by neuron(x)-cc compiler. It will be first loaded onto CPU and then moved to + one or multiple [NeuronCore](https://awsdocs-neuron.readthedocs-hosted.com/en/latest/general/arch/neuron-hardware/neuroncores-arch.html). + + Args: + text_encoder_path (`Union[str, Path]`): + Path of the compiled text encoder. + unet_path (`Union[str, Path]`): + Path of the compiled U-NET. + vae_decoder_path (`Union[str, Path]`): + Path of the compiled VAE decoder. + vae_encoder_path (`Optional[Union[str, Path]]`, defaults to `None`): + Path of the compiled VAE encoder. It is optional, only used for tasks taking images as input. + text_encoder_2_path (`Optional[Union[str, Path]]`, defaults to `None`): + Path of the compiled second frozen text encoder. SDXL only. + device_ids (`Optional[List[int]]`, defaults to `None`): + The ID of neuron cores to load a model, in the case of stable diffusion, it is only used for loading unet, and by default unet will be loaded onto both neuron cores of a device. + dynamic_batch_size (`bool`, defaults to `False`): + Whether enable dynamic batch size for neuron compiled model. If `True`, the input batch size can be a multiple of the batch size during the compilation. + """ + if device_ids is None: + device_ids = [0, 1] + + text_encoder = NeuronBaseModel.load_model(text_encoder_path) + if len(device_ids) > 1: + unet = torch_neuronx.DataParallel( + torch.jit.load(unet_path), + device_ids, + set_dynamic_batching=dynamic_batch_size, + ) + else: + unet = NeuronBaseModel.load_model(unet_path) + vae_decoder = NeuronBaseModel.load_model(vae_decoder_path) + vae_encoder = NeuronBaseModel.load_model(vae_encoder_path) + text_encoder_2 = NeuronBaseModel.load_model(text_encoder_2_path) + + return text_encoder, unet, vae_decoder, vae_encoder, text_encoder_2 + def _save_pretrained( self, save_directory: Union[str, Path], text_encoder_file_name: str = NEURON_FILE_NAME, + text_encoder_2_file_name: str = NEURON_FILE_NAME, unet_file_name: str = NEURON_FILE_NAME, vae_encoder_file_name: str = NEURON_FILE_NAME, vae_decoder_file_name: str = NEURON_FILE_NAME, @@ -183,6 +268,12 @@ def _save_pretrained( Saves the model to the serialized format optimized for Neuron devices. """ save_directory = Path(save_directory) + if not self.model_and_config_save_paths.get(DIFFUSION_MODEL_VAE_ENCODER_NAME)[0].is_file(): + self.model_and_config_save_paths.pop(DIFFUSION_MODEL_VAE_ENCODER_NAME) + + if not self.model_and_config_save_paths.get(DIFFUSION_MODEL_TEXT_ENCODER_2_NAME)[0].is_file(): + self.model_and_config_save_paths.pop(DIFFUSION_MODEL_TEXT_ENCODER_2_NAME) + if self.model_and_config_save_paths is None: logger.warning( "`model_save_paths` is None which means that no path of Neuron model is defined. Nothing will be saved." @@ -192,10 +283,19 @@ def _save_pretrained( logger.info(f"Saving the {tuple(self.model_and_config_save_paths.keys())}...") dst_paths = { - "text_encoder": save_directory / DIFFUSION_MODEL_TEXT_ENCODER_NAME / text_encoder_file_name, - "unet": save_directory / DIFFUSION_MODEL_UNET_NAME / unet_file_name, - "vae_encoder": save_directory / DIFFUSION_MODEL_VAE_ENCODER_NAME / vae_encoder_file_name, - "vae_decoder": save_directory / DIFFUSION_MODEL_VAE_DECODER_NAME / vae_decoder_file_name, + DIFFUSION_MODEL_TEXT_ENCODER_NAME: save_directory + / DIFFUSION_MODEL_TEXT_ENCODER_NAME + / text_encoder_file_name, + DIFFUSION_MODEL_TEXT_ENCODER_2_NAME: save_directory + / DIFFUSION_MODEL_TEXT_ENCODER_2_NAME + / text_encoder_2_file_name, + DIFFUSION_MODEL_UNET_NAME: save_directory / DIFFUSION_MODEL_UNET_NAME / unet_file_name, + DIFFUSION_MODEL_VAE_ENCODER_NAME: save_directory + / DIFFUSION_MODEL_VAE_ENCODER_NAME + / vae_encoder_file_name, + DIFFUSION_MODEL_VAE_DECODER_NAME: save_directory + / DIFFUSION_MODEL_VAE_DECODER_NAME + / vae_decoder_file_name, } model_src_to_dst_path = { self.model_and_config_save_paths[model_name][0]: dst_paths[model_name] @@ -212,9 +312,12 @@ def _save_pretrained( for src_path, dst_path in zip(src_paths, dst_paths): dst_path.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(src_path, dst_path) + if src_path.is_file(): + shutil.copyfile(src_path, dst_path) self.tokenizer.save_pretrained(save_directory.joinpath("tokenizer")) + if self.tokenizer_2 is not None: + self.tokenizer_2.save_pretrained(save_directory.joinpath("tokenizer_2")) self.scheduler.save_pretrained(save_directory.joinpath("scheduler")) if self.feature_extractor is not None: self.feature_extractor.save_pretrained(save_directory.joinpath("feature_extractor")) @@ -228,6 +331,7 @@ def _from_pretrained( revision: Optional[str] = None, cache_dir: Optional[str] = None, text_encoder_file_name: Optional[str] = NEURON_FILE_NAME, + text_encoder_2_file_name: Optional[str] = NEURON_FILE_NAME, unet_file_name: Optional[str] = NEURON_FILE_NAME, vae_encoder_file_name: Optional[str] = NEURON_FILE_NAME, vae_decoder_file_name: Optional[str] = NEURON_FILE_NAME, @@ -237,17 +341,16 @@ def _from_pretrained( **kwargs, # To share kwargs only available for `_from_transformers` ): model_id = str(model_id) - sub_models_to_load, _, _ = cls.extract_init_dict(config) - sub_models_names = set(sub_models_to_load.keys()).intersection({"feature_extractor", "tokenizer", "scheduler"}) - sub_models = {} + patterns = set(config.keys()) + sub_models_to_load = patterns.intersection({"feature_extractor", "tokenizer", "tokenizer_2", "scheduler"}) if not os.path.isdir(model_id): - patterns = set(config.keys()) patterns.update({DIFFUSION_MODEL_VAE_ENCODER_NAME, DIFFUSION_MODEL_VAE_DECODER_NAME}) allow_patterns = {os.path.join(k, "*") for k in patterns if not k.startswith("_")} allow_patterns.update( { text_encoder_file_name, + text_encoder_2_file_name, unet_file_name, vae_encoder_file_name, vae_decoder_file_name, @@ -268,8 +371,9 @@ def _from_pretrained( ) new_model_save_dir = Path(model_id) - for name in sub_models_names: - library_name, library_classes = sub_models_to_load[name] + sub_models = {} + for name in sub_models_to_load: + library_name, library_classes = config[name] if library_classes is not None: library = importlib.import_module(library_name) class_obj = getattr(library, library_classes) @@ -285,6 +389,10 @@ def _from_pretrained( new_model_save_dir / DIFFUSION_MODEL_TEXT_ENCODER_NAME / text_encoder_file_name, new_model_save_dir / DIFFUSION_MODEL_TEXT_ENCODER_NAME / cls.sub_component_config_name, ), + "text_encoder_2": ( + new_model_save_dir / DIFFUSION_MODEL_TEXT_ENCODER_2_NAME / text_encoder_2_file_name, + new_model_save_dir / DIFFUSION_MODEL_TEXT_ENCODER_2_NAME / cls.sub_component_config_name, + ), "unet": ( new_model_save_dir / DIFFUSION_MODEL_UNET_NAME / unet_file_name, new_model_save_dir / DIFFUSION_MODEL_UNET_NAME / cls.sub_component_config_name, @@ -300,26 +408,22 @@ def _from_pretrained( } # Re-build pretrained configs and neuron configs - configs = { - name: DiffusersPretrainedConfig.from_json_file(model_config[1]) - for name, model_config in model_and_config_save_paths.items() - } - neuron_configs = {name: cls._neuron_config_init(model_config) for name, model_config in configs.items()} - - text_encoder = cls.load_model(model_and_config_save_paths["text_encoder"][0]) - if device_ids is None: - device_ids = [0, 1] - if len(device_ids) > 1: - # Load the compiled UNet onto multiple neuron cores - unet = torch_neuronx.DataParallel( - torch.jit.load(model_and_config_save_paths["unet"][0]), - device_ids, - set_dynamic_batching=neuron_configs[DIFFUSION_MODEL_UNET_NAME].dynamic_batch_size, - ) - else: - unet = cls.load_model(model_and_config_save_paths["unet"][0]) - vae_encoder = cls.load_model(model_and_config_save_paths["vae_encoder"][0]) - vae_decoder = cls.load_model(model_and_config_save_paths["vae_decoder"][0]) + configs, neuron_configs = {}, {} + for name, file_paths in model_and_config_save_paths.items(): + if file_paths[1].is_file(): + model_config = DiffusersPretrainedConfig.from_json_file(file_paths[1]) + configs[name] = model_config + neuron_configs[name] = cls._neuron_config_init(model_config) + + text_encoder, unet, vae_decoder, vae_encoder, text_encoder_2 = cls.load_model( + text_encoder_path=model_and_config_save_paths["text_encoder"][0], + unet_path=model_and_config_save_paths["unet"][0], + vae_decoder_path=model_and_config_save_paths["vae_decoder"][0], + vae_encoder_path=model_and_config_save_paths["vae_encoder"][0], + text_encoder_2_path=model_and_config_save_paths["text_encoder_2"][0], + device_ids=device_ids, + dynamic_batch_size=neuron_configs[DIFFUSION_MODEL_UNET_NAME].dynamic_batch_size, + ) if model_save_dir is None: model_save_dir = new_model_save_dir @@ -327,11 +431,13 @@ def _from_pretrained( return cls( text_encoder=text_encoder, unet=unet, - vae_encoder=vae_encoder, vae_decoder=vae_decoder, config=config, tokenizer=sub_models["tokenizer"], scheduler=sub_models["scheduler"], + vae_encoder=vae_encoder, + text_encoder_2=text_encoder_2, + tokenizer_2=sub_models.pop("tokenizer_2", None), feature_extractor=sub_models.pop("feature_extractor", None), device_ids=device_ids, configs=configs, @@ -468,16 +574,25 @@ def __init__( if hasattr(self.model, "device"): self.device = self.model.device - def forward(self, sample: torch.Tensor, timestep: torch.Tensor, encoder_hidden_states: torch.Tensor): + def forward( + self, + sample: torch.Tensor, + timestep: torch.Tensor, + encoder_hidden_states: torch.Tensor, + added_cond_kwargs: Optional[Dict[str, Any]] = None, + ): timestep = timestep.float().expand((sample.shape[0],)) inputs = { "sample": sample, "timestep": timestep, "encoder_hidden_states": encoder_hidden_states, } - outputs = self.model(*tuple(inputs.values())) + if added_cond_kwargs is not None: + inputs["text_embeds"] = added_cond_kwargs.pop("text_embeds", None) + inputs["time_ids"] = added_cond_kwargs.pop("time_ids", None) - return tuple(output for output in outputs.values()) + outputs = self.model(*tuple(inputs.values())) + return outputs class NeuronModelVaeEncoder(_NeuronDiffusionModelPart): @@ -515,5 +630,63 @@ def forward(self, latent_sample: torch.Tensor): class NeuronStableDiffusionPipeline(NeuronStableDiffusionPipelineBase, StableDiffusionPipelineMixin): - def __call__(self, *args, **kwargs): - return StableDiffusionPipelineMixin.__call__(self, *args, **kwargs) + __call__ = StableDiffusionPipelineMixin.__call__ + + +class NeuronStableDiffusionXLPipelineBase(NeuronStableDiffusionPipelineBase): + # `TasksManager` registered img2ime pipeline for `stable-diffusion-xl`: https://github.com/huggingface/optimum/blob/v1.12.0/optimum/exporters/tasks.py#L174 + auto_model_class = StableDiffusionXLImg2ImgPipeline + + def __init__( + self, + text_encoder: torch.jit._script.ScriptModule, + unet: torch.jit._script.ScriptModule, + vae_decoder: torch.jit._script.ScriptModule, + config: Dict[str, Any], + tokenizer: CLIPTokenizer, + scheduler: Union[DDIMScheduler, PNDMScheduler, LMSDiscreteScheduler], + vae_encoder: Optional[torch.jit._script.ScriptModule] = None, + text_encoder_2: Optional[torch.jit._script.ScriptModule] = None, + tokenizer_2: Optional[CLIPTokenizer] = None, + feature_extractor: Optional[CLIPFeatureExtractor] = None, + device_ids: Optional[List[int]] = None, + configs: Optional[Dict[str, "PretrainedConfig"]] = None, + neuron_configs: Optional[Dict[str, "NeuronConfig"]] = None, + model_save_dir: Optional[Union[str, Path, TemporaryDirectory]] = None, + model_and_config_save_paths: Optional[Dict[str, Tuple[str, Path]]] = None, + add_watermarker: Optional[bool] = None, + ): + super().__init__( + text_encoder=text_encoder, + unet=unet, + vae_decoder=vae_decoder, + config=config, + tokenizer=tokenizer, + scheduler=scheduler, + vae_encoder=vae_encoder, + text_encoder_2=text_encoder_2, + tokenizer_2=tokenizer_2, + feature_extractor=feature_extractor, + device_ids=device_ids, + configs=configs, + neuron_configs=neuron_configs, + model_save_dir=model_save_dir, + model_and_config_save_paths=model_and_config_save_paths, + ) + + add_watermarker = add_watermarker if add_watermarker is not None else is_invisible_watermark_available() + + if add_watermarker: + if not is_invisible_watermark_available(): + raise ImportError( + "`add_watermarker` requires invisible-watermark to be installed, which can be installed with `pip install invisible-watermark`." + ) + from diffusers.pipelines.stable_diffusion_xl.watermark import StableDiffusionXLWatermarker + + self.watermark = StableDiffusionXLWatermarker() + else: + self.watermark = None + + +class NeuronStableDiffusionXLPipeline(NeuronStableDiffusionXLPipelineBase, StableDiffusionXLPipelineMixin): + __call__ = StableDiffusionXLPipelineMixin.__call__ diff --git a/optimum/neuron/pipelines/diffusers/pipeline_stable_diffusion.py b/optimum/neuron/pipelines/diffusers/pipeline_stable_diffusion.py index bb3eec38a..d971f9192 100644 --- a/optimum/neuron/pipelines/diffusers/pipeline_stable_diffusion.py +++ b/optimum/neuron/pipelines/diffusers/pipeline_stable_diffusion.py @@ -30,7 +30,7 @@ class StableDiffusionPipelineMixin(StableDiffusionPipeline): # Adapted from https://github.com/huggingface/diffusers/blob/v0.18.2/src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion.py#L302 - def _encode_prompt( + def encode_prompt( self, prompt, num_images_per_prompt, @@ -173,7 +173,7 @@ def __call__( num_inference_steps: int = 50, guidance_scale: float = 7.5, negative_prompt: Optional[Union[str, List[str]]] = None, - num_images_per_prompt: Optional[int] = 1, + num_images_per_prompt: int = 1, eta: float = 0.0, generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, latents: Optional[torch.FloatTensor] = None, @@ -189,6 +189,12 @@ def __call__( # 0. Height and width to unet (static shapes) height = self.unet.config.neuron["static_height"] * self.vae_scale_factor width = self.unet.config.neuron["static_width"] * self.vae_scale_factor + if self.num_images_per_prompt != num_images_per_prompt and not self.dynamic_batch_size: + logger.warning( + f"Overriding `num_images_per_prompt({num_images_per_prompt})` to {self.num_images_per_prompt} used for the compilation. Please recompile the models with your " + f"custom `num_images_per_prompt` or turn on `dynamic_batch_size`, if you wish generating {num_images_per_prompt} per prompt." + ) + num_images_per_prompt = self.num_images_per_prompt # 1. Check inputs. Raise error if not correct self.check_inputs( @@ -216,7 +222,7 @@ def __call__( text_encoder_lora_scale = ( cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None ) - prompt_embeds = self._encode_prompt( + prompt_embeds = self.encode_prompt( prompt, num_images_per_prompt, do_classifier_free_guidance, diff --git a/optimum/neuron/pipelines/diffusers/pipeline_stable_diffusion_xl.py b/optimum/neuron/pipelines/diffusers/pipeline_stable_diffusion_xl.py new file mode 100644 index 000000000..9c0bc5739 --- /dev/null +++ b/optimum/neuron/pipelines/diffusers/pipeline_stable_diffusion_xl.py @@ -0,0 +1,524 @@ +# Copyright 2023 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 logging +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +import torch +from diffusers import StableDiffusionXLPipeline +from diffusers.loaders import LoraLoaderMixin, TextualInversionLoaderMixin +from diffusers.pipelines.stable_diffusion_xl import StableDiffusionXLPipelineOutput +from diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl import rescale_noise_cfg +from diffusers.utils import randn_tensor + + +logger = logging.getLogger(__name__) + + +class StableDiffusionXLPipelineMixin(StableDiffusionXLPipeline): + # Adapted from https://github.com/huggingface/diffusers/blob/v0.20.2/src/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl.py#L219 + def encode_prompt( + self, + prompt: str, + prompt_2: Optional[str] = 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.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + lora_scale: Optional[float] = None, + ): + r""" + Encodes the prompt into text encoder hidden states. + + Args: + prompt (`Optional[str]`, defaults to `None`): + prompt to be encoded + prompt_2 (`Optional[str]`, defaults to `None`): + 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 + num_images_per_prompt (`int`, defaults to 1): + number of images that should be generated per prompt + do_classifier_free_guidance (`bool`, defaults to `True`): + whether to use classifier free guidance or not + negative_prompt (`Optional[str]`, defaults to `None`): + 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 (`Optional[str]`, defaults to `None`): + 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 (`Optional[torch.FloatTensor]`, defaults to `None`): + 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 (`Optional[torch.FloatTensor]`, defaults to `None`): + 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 (`Optional[torch.FloatTensor]`, defaults to `None`): + 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 (`Optional[torch.FloatTensor]`, defaults to `None`): + 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 (`Optional[float]`, defaults to `None`): + A lora scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded. + """ + # 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, LoraLoaderMixin): + self._lora_scale = lora_scale + + if isinstance(prompt, str): + batch_size = 1 + elif isinstance(prompt, list): + 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 + # textual inversion: procecss 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(input_ids=text_input_ids) + + # We are only ALWAYS interested in the pooled output of the final text encoder + pooled_prompt_embeds = prompt_embeds[0] + prompt_embeds = prompt_embeds[-1][-2] # hidden_states + + 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 getattr( + self.config, "force_zeros_for_empty_prompt", False + ) + 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: + if negative_prompt is None: + negative_prompt = "" if isinstance(prompt, str) else [""] * batch_size + else: + negative_prompt = negative_prompt + # negative_prompt = negative_prompt or "" + negative_prompt_2 = negative_prompt_2 or negative_prompt + + 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 isinstance(negative_prompt, str): + uncond_tokens = [negative_prompt, negative_prompt_2] + 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(input_ids=uncond_input.input_ids) + # We are only ALWAYS interested in the pooled output of the final text encoder + negative_pooled_prompt_embeds = negative_prompt_embeds[0] + negative_prompt_embeds = negative_prompt_embeds[-1][-2] # hidden_states + + negative_prompt_embeds_list.append(negative_prompt_embeds) + + negative_prompt_embeds = torch.concat(negative_prompt_embeds_list, dim=-1) + + prompt_embeds = prompt_embeds + 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] + negative_prompt_embeds = negative_prompt_embeds + 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 + ) + + return prompt_embeds, negative_prompt_embeds, pooled_prompt_embeds, negative_pooled_prompt_embeds + + # Adapted from https://github.com/huggingface/diffusers/blob/v0.20.2/src/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl.py#L502 + def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, generator, latents=None): + shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) + if isinstance(generator, list) and len(generator) != batch_size: + raise ValueError( + f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" + f" size of {batch_size}. Make sure the batch size matches the length of the generators." + ) + + if latents is None: + latents = randn_tensor(shape, generator=generator, dtype=dtype) + elif latents.shape != shape: + raise ValueError(f"Unexpected latents shape, got {latents.shape}, expected {shape}") + + # scale the initial noise by the standard deviation required by the scheduler + latents = latents * self.scheduler.init_noise_sigma + return latents + + # Adapted from https://github.com/huggingface/diffusers/blob/v0.20.2/src/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl.py#L557 + def __call__( + self, + prompt: Union[str, List[str]] = None, + prompt_2: Optional[Union[str, List[str]]] = None, + num_inference_steps: int = 50, + denoising_end: Optional[float] = None, + guidance_scale: float = 5.0, + negative_prompt: Optional[Union[str, List[str]]] = None, + negative_prompt_2: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: int = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, + callback_steps: int = 1, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + guidance_rescale: float = 0.0, + original_size: Optional[Tuple[int, int]] = None, + crops_coords_top_left: Tuple[int, int] = (0, 0), + target_size: Optional[Tuple[int, int]] = None, + ): + 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. + 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 + 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. + denoising_end (`float`, *optional*): + When specified, determines the fraction (between 0.0 and 1.0) of the total denoising process to be + completed before it is intentionally prematurely terminated. As a result, the returned sample will + still retain a substantial amount of noise as determined by the discrete timesteps selected by the + scheduler. The denoising_end parameter should ideally be utilized when this pipeline forms a part of a + "Mixture of Denoisers" multi-pipeline setup, as elaborated in [**Refining the Image + Output**](https://huggingface.co/docs/diffusers/api/pipelines/stable_diffusion/stable_diffusion_xl#refining-the-image-output) + 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`). + 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 + 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. + latents (`torch.FloatTensor`, *optional*): + Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image + generation. Can be used to tweak the same generation with different prompts. If not provided, a latents + tensor will ge generated by sampling using the supplied random `generator`. + prompt_embeds (`torch.FloatTensor`, *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.FloatTensor`, *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.FloatTensor`, *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.FloatTensor`, *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. + 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. + callback (`Callable`, *optional*): + A function that will be called every `callback_steps` steps during inference. The function will be + called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`. + callback_steps (`int`, *optional*, defaults to 1): + The frequency at which the `callback` function will be called. If not specified, the callback will be + called at every step. + 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). + guidance_rescale (`float`, *optional*, defaults to 0.7): + Guidance rescale factor proposed by [Common Diffusion Noise Schedules and Sample Steps are + Flawed](https://arxiv.org/pdf/2305.08891.pdf) `guidance_scale` is defined as `φ` in equation 16. of + [Common Diffusion Noise Schedules and Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). + Guidance rescale factor should fix overexposure when using zero terminal SNR. + 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 `(width, height)` 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 (`Tuple[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 `(width, height)`. 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). + + Examples: + + Returns: + [`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] or `tuple`: + [`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] 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 (static shapes) + height = self.unet.config.neuron["static_height"] * self.vae_scale_factor + width = self.unet.config.neuron["static_width"] * self.vae_scale_factor + if self.num_images_per_prompt != num_images_per_prompt and not self.dynamic_batch_size: + logger.warning( + f"Overriding `num_images_per_prompt({num_images_per_prompt})` to {self.num_images_per_prompt} used for the compilation. Please recompile the models with your " + f"custom `num_images_per_prompt` or turn on `dynamic_batch_size`, if you wish generating {num_images_per_prompt} per prompt." + ) + num_images_per_prompt = self.num_images_per_prompt + + original_size = original_size or (height, width) + target_size = target_size or (height, width) + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, + prompt_2, + height, + width, + callback_steps, + negative_prompt, + negative_prompt_2, + prompt_embeds, + negative_prompt_embeds, + pooled_prompt_embeds, + negative_pooled_prompt_embeds, + ) + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + # 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. + do_classifier_free_guidance = guidance_scale > 1.0 and (self.dynamic_batch_size or len(self.device_ids) == 2) + + # 3. Encode input prompt + text_encoder_lora_scale = ( + cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None + ) + ( + prompt_embeds, + negative_prompt_embeds, + pooled_prompt_embeds, + negative_pooled_prompt_embeds, + ) = self.encode_prompt( + prompt=prompt, + prompt_2=prompt_2, + num_images_per_prompt=num_images_per_prompt, + do_classifier_free_guidance=do_classifier_free_guidance, + negative_prompt=negative_prompt, + negative_prompt_2=negative_prompt_2, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + pooled_prompt_embeds=pooled_prompt_embeds, + negative_pooled_prompt_embeds=negative_pooled_prompt_embeds, + lora_scale=text_encoder_lora_scale, + ) + + # 4. Prepare timesteps + self.scheduler.set_timesteps(num_inference_steps) + + timesteps = self.scheduler.timesteps + + # 5. Prepare latent variables + num_channels_latents = self.unet.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + num_channels_latents, + height, + width, + prompt_embeds.dtype, + generator, + latents, + ) + + # 6. Prepare extra step kwargs + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 7. Prepare added time ids & embeddings + add_text_embeds = pooled_prompt_embeds + add_time_ids = (original_size + crops_coords_top_left + target_size,) + add_time_ids = torch.tensor(add_time_ids, dtype=prompt_embeds.dtype) + + if 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([add_time_ids, add_time_ids], dim=0) + + add_time_ids = add_time_ids.repeat(batch_size * num_images_per_prompt, 1) + + # 8. Denoising loop + num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) + + # 7.1 Apply denoising_end + if denoising_end is not None and isinstance(denoising_end, float) and denoising_end > 0 and denoising_end < 1: + discrete_timestep_cutoff = int( + round( + self.scheduler.config.num_train_timesteps + - (denoising_end * self.scheduler.config.num_train_timesteps) + ) + ) + num_inference_steps = len(list(filter(lambda ts: ts >= discrete_timestep_cutoff, timesteps))) + timesteps = timesteps[:num_inference_steps] + + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + # import pdb + # pdb.set_trace() + # expand the latents if we are doing classifier free guidance + latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents + + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # predict the noise residual + added_cond_kwargs = {"text_embeds": add_text_embeds, "time_ids": add_time_ids} + noise_pred = self.unet( + sample=latent_model_input, + timestep=t, + encoder_hidden_states=prompt_embeds, + added_cond_kwargs=added_cond_kwargs, + )[0] + + # perform guidance + if do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + if do_classifier_free_guidance and guidance_rescale > 0.0: + # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf + noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale) + + # compute the previous noisy sample x_t -> x_t-1 + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + callback(i, t, latents) + + if not output_type == "latent": + image = self.vae_decoder(latents / getattr(self.vae_decoder.config, "scaling_factor", 0.18215))[0] + else: + image = latents + return StableDiffusionXLPipelineOutput(images=image) + + # 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) + + if not return_dict: + return (image,) + + return StableDiffusionXLPipelineOutput(images=image) diff --git a/tests/inference/inference_utils.py b/tests/inference/inference_utils.py index 3f9f1d406..607eae1af 100644 --- a/tests/inference/inference_utils.py +++ b/tests/inference/inference_utils.py @@ -42,6 +42,7 @@ "roberta": "hf-internal-testing/tiny-random-RobertaModel", "roformer": "hf-internal-testing/tiny-random-RoFormerModel", "stable-diffusion": "hf-internal-testing/tiny-stable-diffusion-torch", + "stable-diffusion-xl": "echarlaix/tiny-random-stable-diffusion-xl", "xlm": "hf-internal-testing/tiny-random-XLMModel", "xlm-roberta": "hf-internal-testing/tiny-xlm-roberta", } diff --git a/tests/inference/test_stable_diffusion_pipeline.py b/tests/inference/test_stable_diffusion_pipeline.py index 2b30d9924..c4d1fb3b5 100644 --- a/tests/inference/test_stable_diffusion_pipeline.py +++ b/tests/inference/test_stable_diffusion_pipeline.py @@ -19,7 +19,7 @@ import PIL from parameterized import parameterized -from optimum.neuron import NeuronStableDiffusionPipeline +from optimum.neuron import NeuronStableDiffusionPipeline, NeuronStableDiffusionXLPipeline from optimum.neuron.modeling_diffusion import ( NeuronModelTextEncoder, NeuronModelUnet, @@ -66,13 +66,8 @@ def test_export_and_inference_non_dyn(self, model_arch): self.assertIsInstance(neuron_pipeline.vae_encoder, NeuronModelVaeEncoder) self.assertIsInstance(neuron_pipeline.vae_decoder, NeuronModelVaeDecoder) - prompt = "sailing ship in storm by Leonardo da Vinci" - with self.assertRaises(Exception) as context: - image = neuron_pipeline(prompt).images[0] - self.assertIn("pipeline were compiled with", str(context.exception)) - - prompts = ["sailing ship in storm by Leonardo da Vinci"] * num_images_per_prompt - image = neuron_pipeline(prompts).images[0] + prompts = ["sailing ship in storm by Leonardo da Vinci"] + image = neuron_pipeline(prompts, num_images_per_prompt=num_images_per_prompt).images[0] self.assertIsInstance(image, PIL.Image.Image) @parameterized.expand(SUPPORTED_ARCHITECTURES, skip_on_empty=True) @@ -89,3 +84,73 @@ def test_export_and_inference_dyn(self, model_arch): prompts = ["sailing ship in storm by Leonardo da Vinci"] * 2 image = neuron_pipeline(prompts, num_images_per_prompt=2).images[0] self.assertIsInstance(image, PIL.Image.Image) + + +@is_inferentia_test +@requires_neuronx +@require_diffusers +class NeuronStableDiffusionXLPipelineIntegrationTest(unittest.TestCase): + NEURON_MODEL_CLASS = NeuronStableDiffusionXLPipeline + STATIC_INPUTS_SHAPES = {"batch_size": 1, "height": 64, "width": 64} + COMPILER_ARGS = {"auto_cast": "all", "auto_cast_type": "bf16"} + SUPPORTED_ARCHITECTURES = [ + "stable-diffusion-xl", + ] + ATOL_FOR_VALIDATION = 1e-3 + + @parameterized.expand(SUPPORTED_ARCHITECTURES, skip_on_empty=True) + def test_export_and_inference_non_dyn(self, model_arch): + num_images_per_prompt = 4 + input_shapes = copy.deepcopy(self.STATIC_INPUTS_SHAPES) + input_shapes.update({"num_images_per_prompt": num_images_per_prompt}) + neuron_pipeline = self.NEURON_MODEL_CLASS.from_pretrained( + MODEL_NAMES[model_arch], + export=True, + dynamic_batch_size=False, + **input_shapes, + **self.COMPILER_ARGS, + device_ids=[0, 1], + ) + self.assertIsInstance(neuron_pipeline.text_encoder, NeuronModelTextEncoder) + self.assertIsInstance(neuron_pipeline.text_encoder_2, NeuronModelTextEncoder) + self.assertIsInstance(neuron_pipeline.unet, NeuronModelUnet) + self.assertIsInstance(neuron_pipeline.vae_encoder, NeuronModelVaeEncoder) + self.assertIsInstance(neuron_pipeline.vae_decoder, NeuronModelVaeDecoder) + + prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" + prompt_2 = "Van Gogh painting" + negative_prompt_1 = "low quality, low resolution" + negative_prompt_2 = "low quality, low resolution" + + image = neuron_pipeline( + prompt=prompt, + prompt_2=prompt_2, + negative_prompt=negative_prompt_1, + negative_prompt_2=negative_prompt_2, + num_images_per_prompt=num_images_per_prompt, + ).images[0] + self.assertIsInstance(image, PIL.Image.Image) + + @parameterized.expand(SUPPORTED_ARCHITECTURES, skip_on_empty=True) + def test_export_and_inference_dyn(self, model_arch): + neuron_pipeline = self.NEURON_MODEL_CLASS.from_pretrained( + MODEL_NAMES[model_arch], + export=True, + dynamic_batch_size=True, + **self.STATIC_INPUTS_SHAPES, + **self.COMPILER_ARGS, + device_ids=[0, 1], + ) + + prompt = ["Astronaut in a jungle, cold color palette, muted colors, detailed, 8k"] * 2 + prompt_2 = ["Van Gogh painting"] * 2 + negative_prompt_1 = ["low quality, low resolution"] * 2 + negative_prompt_2 = ["low quality, low resolution"] * 2 + image = neuron_pipeline( + prompt=prompt, + prompt_2=prompt_2, + negative_prompt=negative_prompt_1, + negative_prompt_2=negative_prompt_2, + num_images_per_prompt=2, + ).images[0] + self.assertIsInstance(image, PIL.Image.Image)