Skip to content

Update optimize_pipeline for SDXL - #17536

Merged
tianleiwu merged 3 commits into
mainfrom
tlwu/optimize_sdxl_pipeline
Sep 15, 2023
Merged

Update optimize_pipeline for SDXL#17536
tianleiwu merged 3 commits into
mainfrom
tlwu/optimize_sdxl_pipeline

Conversation

@tianleiwu

@tianleiwu tianleiwu commented Sep 13, 2023

Copy link
Copy Markdown
Contributor

Description

  • Optimize SDXL models exported by optimum.
  • Enable it to run locally instead of using module.
  • Detect external data file in original model, and save with same format by default.
  • Add tests

Example

pip install optimum transformers diffusers onnx onnxruntime-gpu>=1.16
optimum-cli export onnx --model stabilityai/stable-diffusion-xl-base-1.0 --task stable-diffusion-xl ./sd_xl_base_onnx
python -m  onnxruntime.transformers.models.stable_diffusion.optimize_pipeline -i ./sd_xl_base_onnx -o ./sd_xl_base_fp16 --float16

Known issues

(1) VAE decoder cannot be converted to float16. Otherwise, there will be black image in output.
(2) To use the float16 models, need a minor change in optimum to convert the inputs for VAE decoder from float16 to float32 since we keep VAE decoder as float32. The change is to append a line like the following after this line

latents = latents.astype(np.float32)

Motivation and Context

@tianleiwu
tianleiwu marked this pull request as draft September 13, 2023 18:10
@SuperSecureHuman

SuperSecureHuman commented Sep 14, 2023

Copy link
Copy Markdown

Just to maintain consistency, requirements of tenssort should match the requirement defined in other providers

Tensorrt --> diffusers>=0.16.0
Others --> diffusers>=0.15.1

Might as well keep it uniform and require >=0.16.0 for all the backends.

https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/python/tools/transformers/models/stable_diffusion/requirements-tensorrt.txt#L1

https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/python/tools/transformers/models/stable_diffusion/requirements.txt#L1

@SuperSecureHuman

SuperSecureHuman commented Sep 14, 2023

Copy link
Copy Markdown

Given the standalone capablity of this optimization, could even make a dockerised variant.

I can help with it, if its needed.

@tianleiwu

Copy link
Copy Markdown
Contributor Author

Just to maintain consistency, requirements of tenssort should match the requirement defined in other providers

You are right. I will add support of SD-XL for TensorRT EP in another pull request. Current onnxruntime_tensorrt_txt2img.py does not support SD-XL. It need some change (similar to demo diffusion of TensorRT as in https://github.com/rajeevsrao/TensorRT/tree/release/8.6/demo/Diffusion)

@tianleiwu
tianleiwu marked this pull request as ready for review September 14, 2023 19:06
@tianleiwu
tianleiwu merged commit af80542 into main Sep 15, 2023
@tianleiwu
tianleiwu deleted the tlwu/optimize_sdxl_pipeline branch September 15, 2023 17:17
@kousun12

Copy link
Copy Markdown

@tianleiwu I'm having trouble getting the basic version of this to run:

pip install optimum transformers diffusers onnx onnxruntime-gpu>=1.16
optimum-cli export onnx --model stabilityai/stable-diffusion-xl-base-1.0 --task stable-diffusion-xl ./sd_xl_base_onnx
python -m  onnxruntime.transformers.models.stable_diffusion.optimize_pipeline -i ./sd_xl_base_onnx -o ./sd_xl_base_fp16 --float16

works fine to create an optimized version, but when i go to load it in, it seems to look for a TensorRT op despite using a CudaExecutionProvider:

from optimum.onnxruntime import (
    ORTStableDiffusionXLPipeline,
    ORTStableDiffusionXLImg2ImgPipeline,
)

MODEL_DIR = '/sdxl_onnx'

pipe = ORTStableDiffusionXLPipeline.from_pretrained(
    MODEL_DIR,
    provider="CUDAExecutionProvider",
)
assert pipe.providers == ["CUDAExecutionProvider", "CPUExecutionProvider"]

it all loads fine, but running results in an error

im = pipe("vase of flowers")
2023-09-24 16:15:31.206037898 [E:onnxruntime:, sequential_executor.cc:514 ExecuteKernel] Non-zero status code returned while running Attention node. Name:'Attention_0' Status Message: /onnxruntime_src/onnxruntime/contrib_ops/cuda/bert/tensorrt_fused_multihead_attention/mha_runner.cu:207 bool onnxruntime::contrib::cuda::FusedMHARunnerFP16v2::mhaImpl::is_flash_attention(int) const interface->mHasCausalMask == false was false. 

---------------------------------------------------------------------------
RuntimeException                          Traceback (most recent call last)
Cell In[4], line 1
----> 1 im = pipe("vase of flowers")

Any idea what is wrong here?

@SuperSecureHuman

Copy link
Copy Markdown

Can you check if it works for SD2.1/1.4

@tianleiwu

tianleiwu commented Sep 28, 2023

Copy link
Copy Markdown
Contributor Author

@kousun12, the error is caused by a bug in Attention operator, see #17729.
You can build from source with that fix.

One walkaround is to set an environment variable ORT_ENABLE_FUSED_CAUSAL_ATTENTION=1. But it might bring slightly different result since the fused causal attention used fp16 in accumulation.

Another way to walkaround is to disable attention fusion in CLIP model. That might be slower but could avoid the runtime error.

@kousun12

Copy link
Copy Markdown

@tianleiwu - ok that brings me a little closer but now I am seeing an issue that the vae was not converted to fp16 with this pipeline. seems to be because the optimize function skips it (https://github.com/microsoft/onnxruntime/pull/17536/files#diff-59f8bc82830a0218e203a2e6b1f0bfe17bac0063bc0df70dcbf1342e47c6c67eR151)

im = pipe("vase of flowers")

100%
50/50 [00:06<00:00, 10.27it/s]
---------------------------------------------------------------------------
InvalidArgument                           Traceback (most recent call last)
Cell In[7], line 1
----> 1 im = pipe("vase of flowers")

File /usr/local/lib/python3.10/site-packages/optimum/pipelines/diffusers/pipeline_stable_diffusion_xl.py:486, in StableDiffusionXLPipelineMixin.__call__(self, prompt, height, width, num_inference_steps, guidance_scale, negative_prompt, num_images_per_prompt, eta, generator, latents, prompt_embeds, negative_prompt_embeds, pooled_prompt_embeds, negative_pooled_prompt_embeds, output_type, return_dict, callback, callback_steps, cross_attention_kwargs, guidance_rescale, original_size, crops_coords_top_left, target_size)
    483 latents /= self.vae_decoder.config.get("scaling_factor", 0.18215)
    484 # it seems likes there is a strange result for using half-precision vae decoder if batchsize>1
    485 image = np.concatenate(
--> 486     [self.vae_decoder(latent_sample=latents[i : i + 1])[0] for i in range(latents.shape[0])]
    487 )
    488 # apply watermark if available
    489 if self.watermark is not None:

File /usr/local/lib/python3.10/site-packages/optimum/pipelines/diffusers/pipeline_stable_diffusion_xl.py:486, in <listcomp>(.0)
    483 latents /= self.vae_decoder.config.get("scaling_factor", 0.18215)
    484 # it seems likes there is a strange result for using half-precision vae decoder if batchsize>1
    485 image = np.concatenate(
--> 486     [self.vae_decoder(latent_sample=latents[i : i + 1])[0] for i in range(latents.shape[0])]
    487 )
    488 # apply watermark if available
    489 if self.watermark is not None:

File /usr/local/lib/python3.10/site-packages/optimum/onnxruntime/modeling_diffusion.py:481, in _ORTDiffusionModelPart.__call__(self, *args, **kwargs)
    480 def __call__(self, *args, **kwargs):
--> 481     return self.forward(*args, **kwargs)

File /usr/local/lib/python3.10/site-packages/optimum/onnxruntime/modeling_diffusion.py:525, in ORTModelVaeDecoder.forward(self, latent_sample)
    521 def forward(self, latent_sample: np.ndarray):
    522     onnx_inputs = {
    523         "latent_sample": latent_sample,
    524     }
--> 525     outputs = self.session.run(None, onnx_inputs)
    526     return outputs

File /usr/local/lib/python3.10/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py:220, in Session.run(self, output_names, input_feed, run_options)
    218     output_names = [output.name for output in self._outputs_meta]
    219 try:
--> 220     return self._sess.run(output_names, input_feed, run_options)
    221 except C.EPFail as err:
    222     if self._enable_fallback:

InvalidArgument: [ONNXRuntimeError] : 2 : INVALID_ARGUMENT : Unexpected input data type. Actual: (tensor(float16)) , expected: (tensor(float))

@tianleiwu

tianleiwu commented Sep 29, 2023

Copy link
Copy Markdown
Contributor Author

@kousun12, see the Known issues in the description of this PR. If VAE decoder is converted to fp16, there will be black image in output due to overflow. I noticed that TensorRT also does not convert VAE for SDXL, see https://github.com/NVIDIA/TensorRT/blob/109862c8fa19e14058de085d64cbb0b580ece8d4/demo/Diffusion/stable_diffusion_pipeline.py#L142-L144.

To walkaround, you can change optimum to append a line like the following after this line

latents = latents.astype(np.float32)

I am going to add another pull request to add an option to let VAE decoder ONNX model accept fp16 latent inputs, then there is no need to change optimum after that.

@SuperSecureHuman

SuperSecureHuman commented Sep 30, 2023

Copy link
Copy Markdown

Exported using the SDXL export instructions from docs

I find that onnxruntime fp16 takes more time than diffusers version.

Resolution 1024 x 1024

Diffusers - 38sec for 50 steps
Optimum Onnxruntime - 44 sec for 50 steps

While for 512, it remained the same 9 sec

Experiment on T4 (Colab)

EDIT - Cuda runtime

@tianleiwu

tianleiwu commented Oct 3, 2023

Copy link
Copy Markdown
Contributor Author

@SuperSecureHuman, thanks for sharing the results.

Optimum OnnxRuntime for SDXL is not fully optimized, like I/O Binding is not used. That could slow the pipeline since model output is copied to CPU, then copied back to GPU for next step.

Right now, I am working on a demo pipeline (similar to SDXL with TensorRT EP in pull request) that could use IO Binding for cuda EP. After that, I could revisit optimum performance issue.

@SuperSecureHuman

Copy link
Copy Markdown

@tianleiwu

Thanks for the update. Do let me know if I could help in testing.

Also, would love to see the work you are doing?

Are u working on a branch or a fork for this?

@tianleiwu

tianleiwu commented Oct 10, 2023

Copy link
Copy Markdown
Contributor Author

@SuperSecureHuman, it is still in progress (Need some change in ArgMax, BiasAdd, BiasSplitGelu, GroupNorm cuda kernels to support SD XL Refiner; And make change in VAE part to work with FP16). Hopefully, I can send out a pull request next week.

For non XL version, you can try the demo_txt2img.py for CUDA EP.

@SuperSecureHuman

Copy link
Copy Markdown

SD 2.1 works perfectly. Got inference time from 9.2 sec to 5.9 Sec. I would want to see similar results with SDXL. This would make SDXL viable for deployment in low resource senario.

tianleiwu added a commit that referenced this pull request Oct 18, 2023
Add CUDA EP to the StableDiffusion XL Demo including:
(1) Add fp16 VAE support for CUDA EP.
(2) Configuration for each model separately (For example, some models
can run with CUDA graph but some models cannot).

Some remaining works will boost performance further later:
(1) Enable CUDA Graph for Clip2 and UNet. Currently, some part of graph
is partitioned to CPU, which blocks CUDA graph.
(2) Update GroupNorm CUDA kernel for refiner. Currently, the cuda kernel
only supports limited number of channels in refiner so we shall see some
gain there if we remove the limitation.

Some extra works that are nice to have (thus lower priority):
(3) Support denoising_end to ensemble base and refiner.
(4) Support classifier free guidance (The idea is from
https://www.baseten.co/blog/sdxl-inference-in-under-2-seconds-the-ultimate-guide-to-stable-diffusion-optimiza/).


#### Performance on A100-SXM4-80GB

Example commands to test an engine built with static shape or dynamic
shape:
```
engine_name=ORT_CUDA
python demo_txt2img_xl.py --engine $engine_name "some prompt"
python demo_txt2img_xl.py --engine $engine_name --disable-cuda-graph --build-dynamic-batch --build-dynamic-shape "some prompt"
```
Engine built with dynamic shape could support different batch size (1 to
4 for TRT; 1 to 16 for CUDA) and image size (256x256 to 1024x1024).
Engine built with static shape could only support fixed batch size (1)
and image size (1024x1024).

The latency (ms) of generating an image of size 1024x1024 (sorted by
total latency):

 Engine | Base (30 Steps)* | Refiner (9 Steps) | Total Latency (ms)
-- | -- | -- | --
ORT_TRT (static shape) | 2467 | 1033 | 3501
TRT (static shape) | 2507 | 1048 | 3555
ORT_CUDA (static shape) | 2630 | 1015 | 3645
ORT_CUDA (dynamic shape) | 2639 | 1016 | 3654
TRT (dynamic shape) | 2777 | 1099 | 3876
ORT_TRT (dynamic shape) | 2890 | 1166 | 4057

\* VAE decoder is not used in Base since the output from base is latent,
which is consumed by refiner to output image.

We can see that ORT_CUDA is faster on dynamic shape, while slower in
static shape (The cause is Clip2 and UNet cannot run with CUDA Graph
right now, and we will address the issue later).

### Motivation and Context
Follow up of #17536
jchen351 pushed a commit that referenced this pull request Oct 18, 2023
Add CUDA EP to the StableDiffusion XL Demo including:
(1) Add fp16 VAE support for CUDA EP.
(2) Configuration for each model separately (For example, some models
can run with CUDA graph but some models cannot).

Some remaining works will boost performance further later:
(1) Enable CUDA Graph for Clip2 and UNet. Currently, some part of graph
is partitioned to CPU, which blocks CUDA graph.
(2) Update GroupNorm CUDA kernel for refiner. Currently, the cuda kernel
only supports limited number of channels in refiner so we shall see some
gain there if we remove the limitation.

Some extra works that are nice to have (thus lower priority):
(3) Support denoising_end to ensemble base and refiner.
(4) Support classifier free guidance (The idea is from
https://www.baseten.co/blog/sdxl-inference-in-under-2-seconds-the-ultimate-guide-to-stable-diffusion-optimiza/).


#### Performance on A100-SXM4-80GB

Example commands to test an engine built with static shape or dynamic
shape:
```
engine_name=ORT_CUDA
python demo_txt2img_xl.py --engine $engine_name "some prompt"
python demo_txt2img_xl.py --engine $engine_name --disable-cuda-graph --build-dynamic-batch --build-dynamic-shape "some prompt"
```
Engine built with dynamic shape could support different batch size (1 to
4 for TRT; 1 to 16 for CUDA) and image size (256x256 to 1024x1024).
Engine built with static shape could only support fixed batch size (1)
and image size (1024x1024).

The latency (ms) of generating an image of size 1024x1024 (sorted by
total latency):

 Engine | Base (30 Steps)* | Refiner (9 Steps) | Total Latency (ms)
-- | -- | -- | --
ORT_TRT (static shape) | 2467 | 1033 | 3501
TRT (static shape) | 2507 | 1048 | 3555
ORT_CUDA (static shape) | 2630 | 1015 | 3645
ORT_CUDA (dynamic shape) | 2639 | 1016 | 3654
TRT (dynamic shape) | 2777 | 1099 | 3876
ORT_TRT (dynamic shape) | 2890 | 1166 | 4057

\* VAE decoder is not used in Base since the output from base is latent,
which is consumed by refiner to output image.

We can see that ORT_CUDA is faster on dynamic shape, while slower in
static shape (The cause is Clip2 and UNet cannot run with CUDA Graph
right now, and we will address the issue later).

### Motivation and Context
Follow up of #17536
@faxu faxu added triage:approved Approved for cherrypicks for release sdxl_llama labels Oct 25, 2023
tianleiwu added a commit that referenced this pull request Oct 31, 2023
- [x] Optimize SDXL models exported by optimum.
- [x] Enable it to run locally instead of using module.
- [x] Detect external data file in original model, and save with same
format by default.
- [x]  Add tests

### Example
```
pip install optimum transformers diffusers onnx onnxruntime-gpu>=1.16
optimum-cli export onnx --model stabilityai/stable-diffusion-xl-base-1.0 --task stable-diffusion-xl ./sd_xl_base_onnx
python -m  onnxruntime.transformers.models.stable_diffusion.optimize_pipeline -i ./sd_xl_base_onnx -o ./sd_xl_base_fp16 --float16
```

### Known issues
(1) VAE decoder cannot be converted to float16. Otherwise, there will be
black image in output.
(2) To use the float16 models, need a minor change in optimum to convert
the inputs for VAE decoder from float16 to float32 since we keep VAE
decoder as float32. The change is to append a line like the following
after [this
line](https://github.com/huggingface/optimum/blob/afd2b5a36663bebd1f501486acee065c728947bc/optimum/pipelines/diffusers/pipeline_stable_diffusion_xl.py#L483)
```
latents = latents.astype(np.float32)
```
tianleiwu added a commit that referenced this pull request Oct 31, 2023
Add CUDA EP to the StableDiffusion XL Demo including:
(1) Add fp16 VAE support for CUDA EP.
(2) Configuration for each model separately (For example, some models
can run with CUDA graph but some models cannot).

Some remaining works will boost performance further later:
(1) Enable CUDA Graph for Clip2 and UNet. Currently, some part of graph
is partitioned to CPU, which blocks CUDA graph.
(2) Update GroupNorm CUDA kernel for refiner. Currently, the cuda kernel
only supports limited number of channels in refiner so we shall see some
gain there if we remove the limitation.

Some extra works that are nice to have (thus lower priority):
(3) Support denoising_end to ensemble base and refiner.
(4) Support classifier free guidance (The idea is from
https://www.baseten.co/blog/sdxl-inference-in-under-2-seconds-the-ultimate-guide-to-stable-diffusion-optimiza/).


#### Performance on A100-SXM4-80GB

Example commands to test an engine built with static shape or dynamic
shape:
```
engine_name=ORT_CUDA
python demo_txt2img_xl.py --engine $engine_name "some prompt"
python demo_txt2img_xl.py --engine $engine_name --disable-cuda-graph --build-dynamic-batch --build-dynamic-shape "some prompt"
```
Engine built with dynamic shape could support different batch size (1 to
4 for TRT; 1 to 16 for CUDA) and image size (256x256 to 1024x1024).
Engine built with static shape could only support fixed batch size (1)
and image size (1024x1024).

The latency (ms) of generating an image of size 1024x1024 (sorted by
total latency):

 Engine | Base (30 Steps)* | Refiner (9 Steps) | Total Latency (ms)
-- | -- | -- | --
ORT_TRT (static shape) | 2467 | 1033 | 3501
TRT (static shape) | 2507 | 1048 | 3555
ORT_CUDA (static shape) | 2630 | 1015 | 3645
ORT_CUDA (dynamic shape) | 2639 | 1016 | 3654
TRT (dynamic shape) | 2777 | 1099 | 3876
ORT_TRT (dynamic shape) | 2890 | 1166 | 4057

\* VAE decoder is not used in Base since the output from base is latent,
which is consumed by refiner to output image.

We can see that ORT_CUDA is faster on dynamic shape, while slower in
static shape (The cause is Clip2 and UNet cannot run with CUDA Graph
right now, and we will address the issue later).

### Motivation and Context
Follow up of #17536
@tianleiwu tianleiwu removed triage:approved Approved for cherrypicks for release release:1.16.2 labels Nov 1, 2023
tianleiwu added a commit that referenced this pull request Nov 7, 2023
Update a few optimizations for Stable Diffusion XL:
(1) Add SkipGroupNorm fusion
(2) Remvoe GroupNorm fusion limits. Previously, we only fuse GroupNorm
when channels is one of `320, 640, 960, 1280, 1920, 2560, 128, 256, 512`
so some GroupNorm in refiner was not fused.
(3) Tune SkipLayerNormalization to use vectorized kernel for hidden size
320, 640 and 1280.

Pipeline Improvements:
(4) Enable cuda graph for unetxl.
(5) Change optimization to generate optimized fp32 model with ORT, then
convert to fp16. Otherwise, fp16 model might be invalid.
(6) Add option to enable-vae-slicing.

Bug fixes:
(a) Fix vae decode in SD demo.
(b) Fix UnipPC add_noise missing a parameter.
(c) EulerA exception in SDXL demo. Disable it for now.
(d) Batch size > 4 has error in VAE without slicing. Force to enable vae
slicing when batch size > 4.

#### Performance Test on A100-SXM4-80GB

Description about the experiment in results:
*Baseline*: removed GroupNorm fusion limits; CUDA graph is enabled in
Clip and VAE, but not in Clip2 and UNet.
*UNetCG*: Enable Cuda Graph on UNet
*SLN*: Tune SkipLayerNormalization
*SGN*: Add SkipGroupNorm fusion

The latency (ms) of generating an image of size 1024x1024 with 30 steps
base model and 9 steps of refiner model:

  | Baseline | UNetCG| UNetCG+SLN | UNetCG+SLN+SGN
-- | -- | -- | -- | --
Base Clip | 3.74 | 3.70 | 3.88 | 3.81
Base Unet x30 | 2567.73 | 2510.69 | 2505.09 | 2499.99
Refiner Clip | 7.59 | 7.42 | 7.41 | 7.58
Refiner Unet x 9 | 814.43 | 803.03 | 802.20 | 799.06
Refiner VAE Decoder | 84.62 | 85.18 | 85.24 | 87.43
E2E | 3480.56 | 3412.05 | 3405.77 | 3400.23

We can see that enable cuda graph brought major gain (around 68ms). SLN
Tuning has about 7ms gain. SkipGroupNorm fusion has 5ms gain.

SkipGroupNorm fusion won't reduce latency much, while it also has
benefit of reducing memory usage, so it is recommended to enable it.

### Motivation and Context
Additional optimizations upon previous work in
#17536.
tianleiwu added a commit that referenced this pull request Nov 7, 2023
Update a few optimizations for Stable Diffusion XL:
(1) Add SkipGroupNorm fusion
(2) Remvoe GroupNorm fusion limits. Previously, we only fuse GroupNorm
when channels is one of `320, 640, 960, 1280, 1920, 2560, 128, 256, 512`
so some GroupNorm in refiner was not fused.
(3) Tune SkipLayerNormalization to use vectorized kernel for hidden size
320, 640 and 1280.

Pipeline Improvements:
(4) Enable cuda graph for unetxl.
(5) Change optimization to generate optimized fp32 model with ORT, then
convert to fp16. Otherwise, fp16 model might be invalid.
(6) Add option to enable-vae-slicing.

Bug fixes:
(a) Fix vae decode in SD demo.
(b) Fix UnipPC add_noise missing a parameter.
(c) EulerA exception in SDXL demo. Disable it for now.
(d) Batch size > 4 has error in VAE without slicing. Force to enable vae
slicing when batch size > 4.

#### Performance Test on A100-SXM4-80GB

Description about the experiment in results:
*Baseline*: removed GroupNorm fusion limits; CUDA graph is enabled in
Clip and VAE, but not in Clip2 and UNet.
*UNetCG*: Enable Cuda Graph on UNet
*SLN*: Tune SkipLayerNormalization
*SGN*: Add SkipGroupNorm fusion

The latency (ms) of generating an image of size 1024x1024 with 30 steps
base model and 9 steps of refiner model:

  | Baseline | UNetCG| UNetCG+SLN | UNetCG+SLN+SGN
-- | -- | -- | -- | --
Base Clip | 3.74 | 3.70 | 3.88 | 3.81
Base Unet x30 | 2567.73 | 2510.69 | 2505.09 | 2499.99
Refiner Clip | 7.59 | 7.42 | 7.41 | 7.58
Refiner Unet x 9 | 814.43 | 803.03 | 802.20 | 799.06
Refiner VAE Decoder | 84.62 | 85.18 | 85.24 | 87.43
E2E | 3480.56 | 3412.05 | 3405.77 | 3400.23

We can see that enable cuda graph brought major gain (around 68ms). SLN
Tuning has about 7ms gain. SkipGroupNorm fusion has 5ms gain.

SkipGroupNorm fusion won't reduce latency much, while it also has
benefit of reducing memory usage, so it is recommended to enable it.

### Motivation and Context
Additional optimizations upon previous work in
#17536.
kleiti pushed a commit to kleiti/onnxruntime that referenced this pull request Mar 22, 2024
- [x] Optimize SDXL models exported by optimum.
- [x] Enable it to run locally instead of using module.
- [x] Detect external data file in original model, and save with same
format by default.
- [x]  Add tests

### Example
```
pip install optimum transformers diffusers onnx onnxruntime-gpu>=1.16
optimum-cli export onnx --model stabilityai/stable-diffusion-xl-base-1.0 --task stable-diffusion-xl ./sd_xl_base_onnx
python -m  onnxruntime.transformers.models.stable_diffusion.optimize_pipeline -i ./sd_xl_base_onnx -o ./sd_xl_base_fp16 --float16
```

### Known issues
(1) VAE decoder cannot be converted to float16. Otherwise, there will be
black image in output.
(2) To use the float16 models, need a minor change in optimum to convert
the inputs for VAE decoder from float16 to float32 since we keep VAE
decoder as float32. The change is to append a line like the following
after [this
line](https://github.com/huggingface/optimum/blob/afd2b5a36663bebd1f501486acee065c728947bc/optimum/pipelines/diffusers/pipeline_stable_diffusion_xl.py#L483)
```
latents = latents.astype(np.float32)
```
kleiti pushed a commit to kleiti/onnxruntime that referenced this pull request Mar 22, 2024
Add CUDA EP to the StableDiffusion XL Demo including:
(1) Add fp16 VAE support for CUDA EP.
(2) Configuration for each model separately (For example, some models
can run with CUDA graph but some models cannot).

Some remaining works will boost performance further later:
(1) Enable CUDA Graph for Clip2 and UNet. Currently, some part of graph
is partitioned to CPU, which blocks CUDA graph.
(2) Update GroupNorm CUDA kernel for refiner. Currently, the cuda kernel
only supports limited number of channels in refiner so we shall see some
gain there if we remove the limitation.

Some extra works that are nice to have (thus lower priority):
(3) Support denoising_end to ensemble base and refiner.
(4) Support classifier free guidance (The idea is from
https://www.baseten.co/blog/sdxl-inference-in-under-2-seconds-the-ultimate-guide-to-stable-diffusion-optimiza/).


#### Performance on A100-SXM4-80GB

Example commands to test an engine built with static shape or dynamic
shape:
```
engine_name=ORT_CUDA
python demo_txt2img_xl.py --engine $engine_name "some prompt"
python demo_txt2img_xl.py --engine $engine_name --disable-cuda-graph --build-dynamic-batch --build-dynamic-shape "some prompt"
```
Engine built with dynamic shape could support different batch size (1 to
4 for TRT; 1 to 16 for CUDA) and image size (256x256 to 1024x1024).
Engine built with static shape could only support fixed batch size (1)
and image size (1024x1024).

The latency (ms) of generating an image of size 1024x1024 (sorted by
total latency):

 Engine | Base (30 Steps)* | Refiner (9 Steps) | Total Latency (ms)
-- | -- | -- | --
ORT_TRT (static shape) | 2467 | 1033 | 3501
TRT (static shape) | 2507 | 1048 | 3555
ORT_CUDA (static shape) | 2630 | 1015 | 3645
ORT_CUDA (dynamic shape) | 2639 | 1016 | 3654
TRT (dynamic shape) | 2777 | 1099 | 3876
ORT_TRT (dynamic shape) | 2890 | 1166 | 4057

\* VAE decoder is not used in Base since the output from base is latent,
which is consumed by refiner to output image.

We can see that ORT_CUDA is faster on dynamic shape, while slower in
static shape (The cause is Clip2 and UNet cannot run with CUDA Graph
right now, and we will address the issue later).

### Motivation and Context
Follow up of microsoft#17536
kleiti pushed a commit to kleiti/onnxruntime that referenced this pull request Mar 22, 2024
Update a few optimizations for Stable Diffusion XL:
(1) Add SkipGroupNorm fusion
(2) Remvoe GroupNorm fusion limits. Previously, we only fuse GroupNorm
when channels is one of `320, 640, 960, 1280, 1920, 2560, 128, 256, 512`
so some GroupNorm in refiner was not fused.
(3) Tune SkipLayerNormalization to use vectorized kernel for hidden size
320, 640 and 1280.

Pipeline Improvements:
(4) Enable cuda graph for unetxl.
(5) Change optimization to generate optimized fp32 model with ORT, then
convert to fp16. Otherwise, fp16 model might be invalid.
(6) Add option to enable-vae-slicing.

Bug fixes:
(a) Fix vae decode in SD demo.
(b) Fix UnipPC add_noise missing a parameter.
(c) EulerA exception in SDXL demo. Disable it for now.
(d) Batch size > 4 has error in VAE without slicing. Force to enable vae
slicing when batch size > 4.

#### Performance Test on A100-SXM4-80GB

Description about the experiment in results:
*Baseline*: removed GroupNorm fusion limits; CUDA graph is enabled in
Clip and VAE, but not in Clip2 and UNet.
*UNetCG*: Enable Cuda Graph on UNet
*SLN*: Tune SkipLayerNormalization
*SGN*: Add SkipGroupNorm fusion

The latency (ms) of generating an image of size 1024x1024 with 30 steps
base model and 9 steps of refiner model:

  | Baseline | UNetCG| UNetCG+SLN | UNetCG+SLN+SGN
-- | -- | -- | -- | --
Base Clip | 3.74 | 3.70 | 3.88 | 3.81
Base Unet x30 | 2567.73 | 2510.69 | 2505.09 | 2499.99
Refiner Clip | 7.59 | 7.42 | 7.41 | 7.58
Refiner Unet x 9 | 814.43 | 803.03 | 802.20 | 799.06
Refiner VAE Decoder | 84.62 | 85.18 | 85.24 | 87.43
E2E | 3480.56 | 3412.05 | 3405.77 | 3400.23

We can see that enable cuda graph brought major gain (around 68ms). SLN
Tuning has about 7ms gain. SkipGroupNorm fusion has 5ms gain.

SkipGroupNorm fusion won't reduce latency much, while it also has
benefit of reducing memory usage, so it is recommended to enable it.

### Motivation and Context
Additional optimizations upon previous work in
microsoft#17536.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants