「SDXLのためのシンプルな最適化の探究」
簡単で効果的なSDXLの最適化についての探求
ステーブル ディフュージョン XL (SDXL)は、Stability AIによる高品質な超現実的な画像生成を目的とした最新の潜在ディフュージョンモデルです。これは、手やテキストの正確な生成、および空間的に正しい構成といった、以前のステーブル ディフュージョンモデルの課題を克服しています。さらに、SDXLはコンテキストにより適応しており、より見栄えの良い画像を生成するために、プロンプトで少ない単語数を必要とします。
しかし、これらの改善は、かなり大きなモデルのコストで実現されています。具体的には、基本のSDXLモデルには35億のパラメータ(特にUNet)があり、それは以前のステーブル ディフュージョンモデルのおよそ3倍の大きさです。
SDXLの推論速度とメモリ使用量を最適化する方法を探るために、A100 GPU(40 GB)でいくつかのテストを行いました。各推論実行において、4つの画像を生成し、それを3回繰り返し行います。推論レイテンシを計算する際には、3回のイテレーションのうち最終イテレーションのみを考慮します。
- 「Hugging Face の推論エンドポイントを使用して埋め込みモデルを展開する」
- レイザーのエッジに VFXスターであるサーフェスドスタジオが、今週『NVIDIA Studio』で驚くべきSFの世界を作り出しました
- Aaron Lee、Smith.aiの共同設立者兼CEO – インタビューシリーズ
つまり、デフォルトの精度とデフォルトのアテンションメカニズムを使用してSDXLをそのまま実行すると、メモリを28GB消費し、72.2秒かかります!
from diffusers import StableDiffusionXLPipelinepipeline = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0").to("cuda")pipeline.unet.set_default_attn_processor()
しかし、これは非常に実用的ではなく、特に4つ以上の画像を生成する場合には遅くなる可能性があります。また、よりパワフルなGPUを持っていない場合、メモリ不足のエラーメッセージに遭遇するかもしれません。では、どのようにしてSDXLを最適化して推論速度を向上させ、メモリ使用量を減らすことができるでしょうか?
🤗 Diffusersでは、SDXLのようなメモリ集中型モデルを実行するための最適化のトリックとテクニックを数多く提供しています。以下では、推論速度とメモリに焦点を当てます。
推論速度
ディフュージョンはランダムなプロセスですので、好みの画像が得られる保証はありません。よくあるのは、複数回の推論を実行して反復する必要があることです。そのため、速度の最適化が重要です。このセクションでは、低精度の重みとメモリ効率の良いアテンションおよびPyTorch 2.0のtorch.compile
の使用に焦点を当てて、速度を向上させ、推論時間を短縮する方法を紹介します。
低精度
モデルの重みは特定の精度で保存され、浮動小数点データ型として表現されます。標準の浮動小数点データ型はfloat32(fp32)であり、広範囲な浮動小数点数を正確に表現することができます。推論時には、同じくらい正確である必要はないことが多いので、float16(fp16)を使用すべきです。fp16は、fp32と比べてメモリの半分の量で保存され、計算が容易なため、2倍の速度で実行されます。さらに、現代のGPUカードには、fp16計算を実行するための最適化されたハードウェアが搭載されているため、より高速に動作します。
🤗 Diffusersでは、モデルを読み込む際にtorch.dtype
パラメータを指定して重みを変換し、推論にfp16を使用することができます:
from diffusers import StableDiffusionXLPipelinepipeline = StableDiffusionXLPipeline.from_pretrained( "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16,).to("cuda")pipeline.unet.set_default_attn_processor()
完全に最適化されていないSDXLパイプラインと比較して、fp16を使用するとメモリ使用量が21.7GBであり、わずか14.8秒しかかかりません。推論を約1分間高速化できます!
メモリ効率の良いアテンション
トランスフォーマモジュールで使用されるアテンションブロックは、入力シーケンスが長くなるとメモリが二次的に増加するため、非常にボトルネックになることがあります。これは、大量のメモリを素早く占有し、メモリ不足のエラーメッセージが表示されることがあります。😬
メモリ効率の良いアテンションアルゴリズムは、疎性やタイリングを利用してアテンションの計算のメモリ負荷を減らすことを目指します。これらの最適化されたアルゴリズムは、以前は別個にインストールする必要のあるサードパーティのライブラリとして提供されていました。しかし、PyTorch 2.0からは、それが必要なくなりました。PyTorch 2では、scaled dot product attention (SDPA)が導入され、Flash Attention、メモリ効率の良いアテンション(xFormers)、およびC++でのPyTorch実装の融合実装を提供しています。SDPAは、推論を高速化するための最も簡単な方法です。PyTorch ≥ 2.0を使用していて、🤗 Diffusersを使用している場合、これはデフォルトで自動的に有効になります!
from diffusers import StableDiffusionXLPipeline
pipeline = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16).to("cuda")
完全に最適化されていないSDXLパイプラインと比較して、fp16とSDPAを使用するとメモリ使用量は同じで、推論時間は11.4秒に改善します。これを他の最適化手法と比較するための新しい基準として使用しましょう。
torch.compile
PyTorch 2.0では、PyTorchコードをより最適化されたカーネルにコンパイルするためのジャストインタイム(JIT)コンパイルAPIであるtorch.compile
が導入されました。他のコンパイラソリューションとは異なり、torch.compile
では既存のコードに最小限の変更が必要であり、モデルを関数でラップするだけの簡単さです。
mode
パラメータを使用すると、コンパイル中にメモリオーバーヘッドまたは推論速度を最適化することができ、より柔軟性が得られます。
from diffusers import StableDiffusionXLPipeline
pipeline = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16).to("cuda")
pipeline.unet = torch.compile(pipeline.unet, mode="reduce-overhead", fullgraph=True)
以前のベースライン(fp16 + SDPA)と比較して、UNetをtorch.compile
でラップすると、推論時間が10.2秒に改善します。
モデルのメモリ使用量
現在のモデルはますます大きくなり、メモリに収まることが難しくなっています。このセクションでは、これらの巨大なモデルのメモリ使用量を削減する方法に焦点を当て、一般のGPU上で実行できるようにします。これらの技術には、CPUのオフローディング、一度にすべてではなく、いくつかのステップにわたって潜在変数を画像にデコードすること、およびオートエンコーダーの蒸留バージョンの使用などが含まれます。
モデルのCPUオフローディング
モデルのオフローディングは、UNetをGPUメモリにロードし、拡散モデルの他のコンポーネント(テキストエンコーダ、VAE)をCPUにロードすることでメモリを節約します。これにより、UNetはGPU上で複数回の反復を実行できるようになります。
from diffusers import StableDiffusionXLPipeline
pipeline = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16)
pipeline.enable_model_cpu_offload()
ベースラインと比較して、メモリ使用量は20.2GBとなり、1.5GBのメモリが節約されます。
逐次的なCPUオフローディング
逐次的なCPUオフローディングという別のタイプのオフローディングは、より多くのメモリを節約する代わりに推論時間が遅くなります。UNetのようなモデル全体をオフロードするのではなく、異なるUNetのサブモジュールに保存されているモデルの重みをCPUにオフロードし、フォワードパスの直前にGPUにロードするのです。基本的には、各回にモデルの一部のみをロードするため、さらに多くのメモリを節約できます。ただし、モジュールの読み込みとオフロードを多数回行うため、推論速度は著しく遅くなります。
from diffusers import StableDiffusionXLPipeline
pipeline = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16)
pipeline.enable_sequential_cpu_offload()
ベースラインと比較して、これには19.9GBのメモリが必要ですが、推論時間は67秒に増加します。
スライシング
SDXLでは、バリエーショナルエンコーダ(VAE)がUNetが予測した洗練された潜在変数をリアルな画像にデコードします。このステップのメモリ要件は、予測される画像の数(バッチサイズ)に比例します。画像の解像度と利用可能なGPU VRAMに応じて、かなりのメモリを必要とする場合があります。
ここで「スライシング」が役に立ちます。デコードする入力テンソルはスライスに分割され、それをデコードする計算は複数のステップで完了します。これによりメモリが節約され、より大きなバッチサイズを使用できます。
pipe = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16)
pipe = pipe.to("cuda")
pipe.enable_vae_slicing()
スライスされた計算で、メモリを15.4GBに削減します。逐次的なCPUオフローディングを追加すると、さらに11.45GBに減少し、1つのプロンプトに対して4つの画像(1024×1024)を生成することができます。ただし、逐次的なオフローディングでは推論の遅延も増加します。
計算のキャッシュ
通常、テキスト条件付きの画像生成モデルでは、入力プロンプトから埋め込みを計算するためにテキストエンコーダが使用されます。SDXLでは2つのテキストエンコーダを使用しています!これは推論の遅延にかなり寄与しています。ただし、これらの埋め込みは逆拡散プロセス全体で変化しないため、事前計算して再利用することができます。この方法では、テキストの埋め込みを計算した後、メモリからテキストエンコーダを削除することができます。
まず、テキストエンコーダとその対応するトークナイザをロードし、入力プロンプトから埋め込みを計算します:
tokenizers = [tokenizer, tokenizer_2]text_encoders = [text_encoder, text_encoder_2]( prompt_embeds, negative_prompt_embeds, pooled_prompt_embeds, negative_pooled_prompt_embeds) = encode_prompt(tokenizers, text_encoders, prompt)
次に、GPUメモリをフラッシュしてテキストエンコーダを削除します:
del text_encoder, text_encoder_2, tokenizer, tokenizer_2flush()
これで埋め込みがSDXLパイプラインに直接入れられる準備が整いました:
from diffusers import StableDiffusionXLPipelinepipe = StableDiffusionXLPipeline.from_pretrained( "stabilityai/stable-diffusion-xl-base-1.0", text_encoder=None, text_encoder_2=None, tokenizer=None, tokenizer_2=None, torch_dtype=torch.float16,)pipe = pipe.to("cuda")call_args = dict( prompt_embeds=prompt_embeds, negative_prompt_embeds=negative_prompt_embeds, pooled_prompt_embeds=pooled_prompt_embeds, negative_pooled_prompt_embeds=negative_pooled_prompt_embeds, num_images_per_prompt=num_images_per_prompt, num_inference_steps=num_inference_steps,)image = pipe(**call_args).images[0]
SDPAとfp16と組み合わせることで、メモリを21.9GBに減少させることができます。キャッシュされた計算とともに、上記で説明した他のメモリの最適化技術も使用することができます。
Tiny Autoencoder
先に述べたように、VAEは潜在変数を画像にデコードします。自然に、このステップはVAEのサイズによって直接的にボトルネックになります。ですので、小さいオートエンコーダを使用しましょう!madebyollin
のTiny Autoencoderを使用できます。the Hubのアドレスは10MBで、SDXLで使用される元のVAEから蒸留されています。
from diffusers import AutoencoderTinypipe = StableDiffusionXLPipeline.from_pretrained( "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16)pipe.vae = AutoencoderTiny.from_pretrained("madebyollin/taesdxl", torch_dtype=torch.float16)pipe = pipe.to("cuda")
この設定では、メモリ要件を15.6GBに削減しながら推論遅延も減少させることができます。
結論
最適化による節約のまとめとして:
これらの最適化により、お気に入りのパイプラインをスムーズに実行できることを願っています。これらの技術を試して、ぜひイメージを共有してください!🤗
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles