HuggingFace Diffusers 0.12 : 使用方法 : 推論のためのパイプライン (2)

HuggingFace Diffusers 0.12 : 使用方法 : 推論のためのパイプライン (2) (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 02/13/2023 (v0.12.1)

* 本ページは、HuggingFace Diffusers の以下のドキュメントを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

クラスキャット 人工知能 研究開発支援サービス

クラスキャット は人工知能・テレワークに関する各種サービスを提供しています。お気軽にご相談ください :

◆ 人工知能とビジネスをテーマに WEB セミナーを定期的に開催しています。スケジュール
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

  • 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
  • sales-info@classcat.com  ;  Web: www.classcat.com  ;   ClassCatJP

 

 

HuggingFace Diffusers 0.12 : 使用方法 : 推論のためのパイプライン (2)

テキスト誘導 深度-to-画像 (Depth-to-Image) 生成

StableDiffusionDepth2ImgPipeline は新しい画像の生成を条件付けするためにテキストプロンプトと初期画像を、そして画像の構造を保持するための depth_map を渡すことができます。depth_map が渡されない場合には、パイプラインは統合 depth-estimation モデルを通して深度を自動的に予測します。

import torch
import requests
from PIL import Image

from diffusers import StableDiffusionDepth2ImgPipeline

pipe = StableDiffusionDepth2ImgPipeline.from_pretrained(
    "stabilityai/stable-diffusion-2-depth",
    torch_dtype=torch.float16,
).to("cuda")


url = "http://images.cocodataset.org/val2017/000000039769.jpg"
init_image = Image.open(requests.get(url, stream=True).raw)
prompt = "two tigers"
n_prompt = "bad, deformed, ugly, bad anatomy"
image = pipe(prompt=prompt, image=init_image, negative_prompt=n_prompt, strength=0.7).images[0]

 

高速なプロンプトエンジニアリングのためにシードを再利用する

画像生成時の一般的なユースケースは画像のバッチを生成し、一つの画像を選択して 2 回目の実行でそれをより良い、より詳細なプロンプトで改良します。これを行うためには、バッチの各々の生成画像を決定論的にする必要があります。画像はノイズ除去ガウスランダムノイズにより生成されます、これは torch generator を渡すことでインスタンス化できます。

今、バッチ処理生成について、バッチのすべての単一生成画像が正確に一つのシードに結び付けられていることを確実にする必要があります。🧨 Diffusers では、これはパイプラインに一つの generator を渡すのではなく、generator のリストを渡すことにより実現できます。

runwayml/stable-diffusion-v1-5 を使用してサンプルに進みましょう。次のプロンプトの幾つかのバージョンを生成することを望みます :

prompt = "Labrador in the style of Vermeer"

Let’s load the pipeline

from diffusers import DiffusionPipeline

pipe = DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16)
pipe = pipe.to("cuda")

今、4 つの異なる generator を定義しましょう、ある画像を再現したいからです。generator を作成するために 0 から 3 のシードを使用します。

import torch

generator = [torch.Generator(device="cuda").manual_seed(i) for i in range(4)]

Let’s generate 4 images:

images = pipe(prompt, generator=generator, num_images_per_prompt=4).images
images

OK, 最後の画像は二重に目を持ちますが、最初の画像は良いようです!画像が最初の画像に類似するように 最初のシードを保持しながら プロンプトを少し良くしてみましょう。

prompt = [prompt + t for t in [", highly realistic", ", artsy", ", trending", ", colorful"]]
generator = [torch.Generator(device="cuda").manual_seed(0) for i in range(4)]

We create 4 generators with seed 0, which is the first seed we used before.

Let’s run the pipeline again.

images = pipe(prompt, generator=generator).images
images

 

再現性

Diffusers の再現性について読む前に、再現性に関する PyTorch の記述 を見ることを強く勧めます。

PyTorch states that :

PyTorch リリース、個々のコミット、あるいは異なるプラットフォーム間の完全に再現可能な結果は保証されていません。プラットフォーム間での同じ結果は決して期待できないものの、ある許容誤差内であれば… 結果がリリース、プラットフォーム等の間で再現可能であることは期待できます。けれどもこの許容誤差は拡散パイプラインとチェックポイントに依存して大きく変化します。

以下では、拡散モデルに対してランダムネスのソースを最善に制御する方法を示します。

 

推論

推論の間、拡散パイプラインは、ノイズ除去されるガウスノイズ・テンソルの生成やスケジューリングステップへのノイズ追加のような、ランダムサンプリング演算に大きく依存します。

例を見てみましょう。DDIM パイプライン を 2 つの推論ステップのためだけに実行し、これは出力の数値を調べるために numpy テンソルを返します。

from diffusers import DDIMPipeline
import numpy as np

model_id = "google/ddpm-cifar10-32"

# load model and scheduler
ddim = DDIMPipeline.from_pretrained(model_id)

# run pipeline for just two steps and return numpy tensor
image = ddim(num_inference_steps=2, output_type="np").images
print(np.abs(image).sum())

上記の実行は 1464.2076 の値になりますが、それの再度の実行は 1495.1768 の違う値をプリントします。ここで何が起きているのでしょう?パイプラインが実行されるたびに、ガウスノイズが作成されてステップ毎にノイズ除去されます。torch.randn でガウスノイズを作成するために、毎回異なるランダムシードが取られますので、違う結果になります。これは拡散パイプラインの望ましい特性です、というのはそれはパイプラインが実行されるたびに異なるランダム画像を作成できることを意味するからです。多くの場合、特定の実行の正確に同じ画像を生成したいので、その場合には PyTorch generator のインスタンスを渡す必要があります :

import torch
from diffusers import DDIMPipeline
import numpy as np

model_id = "google/ddpm-cifar10-32"

# load model and scheduler
ddim = DDIMPipeline.from_pretrained(model_id)

# create a generator for reproducibility
generator = torch.Generator(device="cpu").manual_seed(0)

# run pipeline for just two steps and return numpy tensor
image = ddim(num_inference_steps=2, output_type="np", generator=generator).images
print(np.abs(image).sum())

上記の実行は常に 1491.1711 の値をプリントします – それを再度実行するときもです、何故ならばパイプラインのすべてのランダム関数に渡される generator オブジェクトを定義しているからです。

このコードスニペットを特定のハードウェアとバージョンで実行する場合、同じではないにしても類似の結果を得るはずです。

パイプラインにシードを表す整数値だけの代わりに generator オブジェクトを渡すことは最初は少し直感的ではないかもしれませんが、PyTorch で確率モデルを扱うときにはこれは推奨されるデザインです、generator は高度なランダム状態にあり複数のパイプラインに連続して渡すことができるからです。

Great! 今は、再現可能なパイプラインを書く方法を知りましたが、それは少し扱いにくいです、上記のサンプルは CPU でのみ動作するからです。GPU でもどのように再現性を獲得するのでしょう?簡単に言えば、パイプラインを GPU で実行するとき異なるハードウェア間で完全な再現性を期待するべきではありません、というのは行列乗算は GPU 上では CPU 上よりも決定性が少なく (less deterministic)、そして拡散パイプラインは多くの行列乗算を必要する傾向があるためです。異なる GPU 間で制限内のランダムネスを保持するために何ができるか見ましょう。

最大のスピード性能を実現するため、パイプラインを GPU で実行するとき GPU 上で直接 generator を作成することが勧められます :

import torch
from diffusers import DDIMPipeline
import numpy as np

model_id = "google/ddpm-cifar10-32"

# load model and scheduler
ddim = DDIMPipeline.from_pretrained(model_id)
ddim.to("cuda")

# create a generator for reproducibility
generator = torch.Generator(device="cuda").manual_seed(0)

# run pipeline for just two steps and return numpy tensor
image = ddim(num_inference_steps=2, output_type="np", generator=generator).images
print(np.abs(image).sum())

上記の実行は今は 1389.8634 の値をプリントします – 正確に同じシードを使用しているにもかかわらず!それは GPU で得た結果を CPU でも再現することはできないことを意味しています、不幸なことに。とは言え、GPU は CPU とは異なるランダム数 generator を使用していますので、それは想定されるはずです。

この問題を回避するため、randn_tensor 関数を作成しました、これは CPU でランダムノイズを作成してから必要であればテンソルを GPU に移すことができます。この関数はパイプライン內のどこでも使用され、パイプラインが GPU 上で実行されている場合でさえもユーザが CPU generator を 常に 渡すことを可能にします :

import torch
from diffusers import DDIMPipeline
import numpy as np

model_id = "google/ddpm-cifar10-32"

# load model and scheduler
ddim = DDIMPipeline.from_pretrained(model_id)
ddim.to("cuda")

# create a generator for reproducibility
generator = torch.manual_seed(0)

# run pipeline for just two steps and return numpy tensor
image = ddim(num_inference_steps=2, output_type="np", generator=generator).images
print(np.abs(image).sum())

上記の実行は今度は 1491.1713 の値をプリントします、これはパイプラインが完全に CPU で実行されたときの値 1491.1711 に遥かに近いです。

結果として、再現性が重要である場合には常に CPU generator を渡すことを勧めます。パフォーマンスの損失は殆ど無視できる場合が多いですが、パイプラインが CPU で実行された場合よりも遥かに同様の値を生成することを確実にできます。

最後に、UnCLIPPipeline のようなより複雑なパイプラインは精度誤差伝播に対して極めて敏感であることが多く、そのため異なる GPU ハードウェアや PyTorch バージョン間で類似の結果を期待することさえできないことに気づきました。そのような場合には、完全な再現性のためには正確に同じハードウェアと PyTorch バージョンで実行することを確実にする必要があります。

 

以上