パフォーマンスの向上と最適化されたリソース使用のためのダイナミックなLoRAローディング
ダイナミックなLoRAローディングによるパフォーマンスの向上と最適化されたリソース使用
私たちは、拡散モデルに基づくLoRAのハブ内の推論速度を大幅に高速化することができました。これにより、計算リソースを節約し、より良いユーザーエクスペリエンスを提供することができました。
モデルへの推論を行うには、2つのステップがあります:
- ウォームアップフェーズ – モデルのダウンロードとサービスのセットアップ(25秒)。
- 推論ジョブ自体(10秒)。
これらの改善により、ウォームアップ時間を25秒から3秒に短縮することができました。数百の異なるLoRAに対する推論を、たった5つのA10G GPU以下で提供することができます。さらに、ユーザーリクエストへの応答時間は35秒から13秒に短縮されました。
一つのサービスで多くの異なるLoRAを動的に提供するために、Diffusersライブラリで開発された最近の機能を活用する方法についてもっと話しましょう。
- 「パーソナリティをピクセルにもたらす、Inworldは自己再生AIを使用してゲームキャラクターをレベルアップさせます」
- 『Pythonでのマルチスレッディングとマルチプロセッシングの紹介』
- 「APIのパワーを活用する:認証を通じて製品の開発ロードマップを形成し、ユーザー体験を向上させる」
LoRA
LoRAは「パラメータ効率」(PEFT)メソッドの一環である、微調整技術です。このメソッドは、微調整プロセスによって影響を受けるトレーニング可能なパラメータの数を減らすことを試みます。微調整の速度を高めながら、微調整済みチェックポイントのサイズを減らすことができます。
モデルの全ての重みに微小な変更を行うことによってモデルを微調整する代わりに、ほとんどの層を固定し、注意ブロック内の特定の一部の層のみをトレーニングします。さらに、これらの層のパラメータに触れず、二つの小さな行列の積を元の重みに加えることで、これらの層のパラメータを更新します。これらの小さな行列は微調整プロセス中に更新され、ディスクに保存されます。これにより、元のモデルのパラメータはすべて保存され、適応方法を使用してLoRAの重みを上にロードすることができます。
LoRA(Low Rank Adaptation)という名前は、先ほど言及した小さな行列から来ています。このメソッドについての詳細は、この記事または元の論文をご覧ください。
上記の図は、LoRAアダプタの一部として保存される二つの小さなオレンジ色の行列を示しています。後でこれらのLoRAアダプタをロードし、青いベースモデルと結合して黄色の微調整モデルを取得することができます。重要なことは、アダプタをアンロードすることも可能なので、いつでも元のベースモデルに戻すことができるということです。
言い換えると、LoRAアダプタは、必要に応じて追加および削除が可能なベースモデルのアドオンのようなものです。AとBの小さなランクのため、モデルサイズと比較して非常に軽量です。したがって、ロード時間は全体のベースモデルをロードするよりもはるかに高速です。
例えば、多くのLoRAアダプタのベースモデルとして広く使用されているStable Diffusion XL Base 1.0モデルリポジトリを見ると、そのサイズは約7 GBです。しかし、このモデルのような典型的なLoRAアダプタは、わずか24 MBのスペースしか使用しません!
ハブ上には青いベースモデルよりもはるかに少ない黄色のモデルがあります。青いモデルから黄色のモデルに素早く移行し、逆も可能であれば、わずかな異なる青いデプロイメントだけで多くの異なる黄色モデルを提供する方法があります。
LoRAについてより詳しく知りたい場合は、以下のブログ記事を参照してください:Using LoRA for Efficient Stable Diffusion Fine-Tuning、または直接元の論文をご覧ください。
利点
私たちのハブには約130の異なるLoRAがあります。そのうちのほとんど(約92%)はStable Diffusion XL Base 1.0モデルに基づいたLoRAです。
これまでは、これらすべてのために専用のサービスを展開する必要がありました(例: 上のダイアグラムの黄色のマージされた行列すべてに対して); 少なくとも1つの新しいGPUをリリース+予約することが必要でした。特定のモデルに対してサービスを生成し、リクエストを処理できる状態にするまでの所要時間は約25秒であり、それに加えて推論時間があります(1024×1024 SDXL推論拡散については、25の推論ステップのA10G上で約10秒かかります)。アダプタが時々しか要求されない場合、そのサービスは他のリソースによって先取りされたリソースを解放するために停止されます。
もし人気のないLoRAを要求した場合、これまでのようにSDXLモデルに基づいていても、最初のリクエストに対する応答までに35秒かかり、最初のリクエストには推論時間(例:10秒)がかかることになります。
それでは今: アダプタは数個の異なる “青” の基本モデル(例: Diffusionの場合、2つの重要なもの)のみを使用するため、リクエスト時間は35秒から13秒に短縮されました。あなたのアダプタがそれほど人気がなくても、その “青い” サービスがすでに起動している可能性が非常に高いです。言い換えれば、リクエストがそんなに頻繁でなくても、25秒間のウォームアップ時間を回避する可能性が非常に高いです。青いモデルはすでにダウンロードされており、準備ができています。私たちがしなければならないのは、前のアダプタをアンロードし、新しいものをロードするだけで、これには3秒かかります(以下で見るように)。
全体的には、すべての異なるモデルを提供するために少ない数のGPUが必要です。これは、既に計算使用率を最大化するために展開間でGPUを共有する方法があるにもかかわらずです。約2分の間に、要求される異なるLoRAウェイトは約10あります。10の展開を生成し、それらをウォームアップし続ける代わりに、1〜2つのGPUですべてのモデルを提供するだけです(リクエストバーストがある場合、より多くのGPUが使用される場合もあります)。
実装
私たちはInference APIでLoRAの相互化を実装しました。プラットフォームで利用可能なモデルに対してリクエストが行われると、まずそれがLoRAかどうかを判断します。次に、LoRAのための基本モデルを特定し、リクエストを共通のバックエンドファームにルーティングし、そのモデルに対するリクエストを処理する能力を持たせます。推論リクエストは、ベースモデルをウォームアップしてLoRAsを動的にロード/アンロードすることで提供されます。これにより、同じ計算リソースを使用して複数の異なるモデルを同時に提供することができます。
LoRAの構造
ハブでは、LoRAは2つの属性で識別できます:
LoRAにはbase_model
属性があります。これは、LoRAが構築されたモデルであり、推論を実行する際に適用するモデルです。
LoRAだけにこのような属性があるわけではありません(何重にもなっているモデルはすべて同じような属性を持っています)。したがって、LoRAを適切に識別するにはlora
タグも必要です。
DiffusersのためのLoRAのロード/アンロード 🧨
Diffusersライブラリでは、異なるLoRAウェイトのロードとアンロードに4つの関数が使用されます:
load_lora_weights
とfuse_lora
は、重みをメインレイヤーとマージしながらロードするために使用されます。注意: 推論を行う前にメインモデルと重みをマージすることで推論時間を30%削減することができます。
unload_lora_weights
とunfuse_lora
はアンロードに使用されます。
以下に、ベースモデルの上に複数のLoRAウェイトを迅速にロードするためにDiffusersライブラリを活用する方法の例を示します:
import torch
from diffusers import (
AutoencoderKL,
DiffusionPipeline,
)
import time
base = "stabilityai/stable-diffusion-xl-base-1.0"
adapter1 = 'nerijs/pixel-art-xl'
weightname1 = 'pixel-art-xl.safetensors'
adapter2 = 'minimaxir/sdxl-wrong-lora'
weightname2 = None
inputs = "elephant"
kwargs = {}
if torch.cuda.is_available():
kwargs["torch_dtype"] = torch.float16
start = time.time()
# VAE compatible with fp16 created by madebyollin is loaded
vae = AutoencoderKL.from_pretrained(
"madebyollin/sdxl-vae-fp16-fix",
torch_dtype=torch.float16,
)
kwargs["vae"] = vae
kwargs["variant"] = "fp16"
# Base model is loaded
model = DiffusionPipeline.from_pretrained(
base, **kwargs)
if torch.cuda.is_available():
model.to("cuda")
elapsed = time.time() - start
print(f"Base model loaded, elapsed {elapsed:.2f} seconds")
def inference(adapter, weightname):
start = time.time()
model.load_lora_weights(adapter, weight_name=weightname)
# Fusing lora weights with the main layers improves inference time by 30 % !
model.fuse_lora()
elapsed = time.time() - start
print(f"LoRA adapter loaded and fused to main model, elapsed {elapsed:.2f} seconds")
start = time.time()
data = model(inputs, num_inference_steps=25).images[0]
elapsed = time.time() - start
print(f"Inference time, elapsed {elapsed:.2f} seconds")
start = time.time()
model.unfuse_lora()
model.unload_lora_weights()
elapsed = time.time() - start
print(f"LoRA adapter unfused/unloaded from base model, elapsed {elapsed:.2f} seconds")
inference(adapter1, weightname1)
inference(adapter2, weightname2)
読み込み中の図
以下の数字はすべて秒単位です:
GPU | T4 | A10G |
ベースモデルの読み込み – キャッシュされていない | 20 | 20 |
ベースモデルの読み込み – キャッシュされている | 5.95 | 4.09 |
アダプター1の読み込み | 3.07 | 3.46 |
アダプター1のアンロード | 0.52 | 0.28 |
アダプター2の読み込み | 1.44 | 2.71 |
アダプター2のアンロード | 0.19 | 0.13 |
推論時間 | 20.7 | 8.5 |
追加の推論ごとに2〜4秒ずつかかるため、多くの異なるLoRAを提供することができます。ただし、A10G GPUでは、推論時間が大幅に短縮されますが、アダプターの読み込み時間はあまり変わらないため、LoRAの読み込み/アンロードは比較的高コストです。
リクエストの提供
推論リクエストを提供するために、このオープンソースのコミュニティイメージを使用します。
これまでに説明したメカニズムは、TextToImagePipelineクラスで使用されます。
LoRAがリクエストされた場合、ロードされているものを確認し、必要な場合のみ変更し、通常通りに推論を行います。これにより、ベースモデルと多くの異なるアダプタへのリクエストを処理することができます。
以下は、この画像をテストおよびリクエストする方法の例です:
$ git clone https://github.com/huggingface/api-inference-community.git$ cd api-inference-community/docker_images/diffusers$ docker build -t test:1.0 -f Dockerfile .$ cat > /tmp/env_file <<'EOF'MODEL_ID=stabilityai/stable-diffusion-xl-base-1.0TASK=text-to-imageHF_HUB_ENABLE_HF_TRANSFER=1EOF$ docker run --gpus all --rm --name test1 --env-file /tmp/env_file_minimal -p 8888:80 -it test:1.0
別のターミナルで、ベースモデルと/またはさまざまなLoRAアダプタにリクエストを行います。
# ベースモデルにリクエスト$ curl 0:8888 -d '{"inputs": "elephant", "parameters": {"num_inference_steps": 20}}' > /tmp/base.jpg# 1つのアダプタにリクエスト$ curl -H 'lora: minimaxir/sdxl-wrong-lora' 0:8888 -d '{"inputs": "elephant", "parameters": {"num_inference_steps": 20}}' > /tmp/adapter1.jpg# 別のアダプタにリクエスト$ curl -H 'lora: nerijs/pixel-art-xl' 0:8888 -d '{"inputs": "elephant", "parameters": {"num_inference_steps": 20}}' > /tmp/adapter2.jpg
バッチ処理は?
最近、非常に興味深い論文が公開され、LoRAモデルでバッチ推論を実行することによってスループットを増やす方法が説明されています。要点を述べると、すべての推論リクエストをバッチにまとめ、共通のベースモデルに関連する計算を一度に行い、その後、残りのアダプタ固有の製品を計算します。私たちはそうしたテクニック(LLMにおけるtext-generation-inferenceで採用された手法に近い)を実装していません。代わりに、単一の順次推論リクエストに固執しました。それは、バッチ処理がディフューザーにとって興味深くなかったためです。バッチサイズが8の場合、単純な画像生成ベンチマークでは、スループットはわずかに25%増加するだけで、レイテンシは6倍に増加します!比較すると、LLMにとっては非常に興味深いのです。バッチ処理により、連続スループットが8倍になり、レイテンシがわずかに10%増加します。これが、ディフューザーのためにバッチ処理を実装しなかった理由です。
結論:時間!
動的なLoRAのロードを使用することで、計算リソースを節約し、Hub Inference APIのユーザーエクスペリエンスを向上させることができました。以前にロードされたアダプタをアンロードし、興味のあるアダプタをロードするプロセスによって追加される時間はありますが、通常はすでに稼働しているため、推論時間の応答全体が非常に短くなります。同じ方法をデプロイメントに適用する場合は、お知らせください!
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