😍

超強力汎用ControlNet「AnyTest v4」を利用する方法【diffusers】

2024/09/17に公開

はじめに

今までStable Diffusion XLに関してはさまざまな記事を書いてきました。

生成AIをGoogle Colaboratoryで簡単に 【Part6 画像生成AI Stable Diffusion XL編】
生成AIをGoogle Colaboratoryで簡単に 【Part6.5 画像生成AI SDXL+ANIMAGINE XL 3.1編】
生成AIをGoogle Colabで簡単に 【Part10 SDXL+ANIMAGINE +ControlNet+LoRA編(全部載せ)】
【Next.js】生成AIによる「お絵描きサポートWebアプリ」を作りたい!【全くわからない人】

その中で様々なモデルやControlNetを利用をしてきました。
しかしながら、ControlNetは用途に応じて様々なモデルを使い分けないといけないなどの課題があります。

その中で下記のような汎用ControlNetなるものを見つけました。
https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main/test_controlnet2

上記の重みは「AnyTest」というControlNetであり、「月須和・那々」様が開発したControlNetになります。

開発者様のXを見ると、このモデルの威力がわかるかと思いますので、少しだけ紹介しようと思います。

https://x.com/nana_tsukisuwa/status/1796280324135207168

https://x.com/nana_tsukisuwa/status/1796855622585205176

いやー、すごいですね。
Depthのような画像を入力して、構図を指定した画像を生成したり、Cannyの一部のような画像を入力して、一部分を固定して残りをAIに書かせたりと、これをたった一つの汎用ControlNetで達成しているというのが圧倒的です。

そこで私もぜひ使ってみたいと思ったのですが、記事執筆時点では、ComfyUIなどのWebUIで利用する方法しかネットに使い方がありませんでした。
私は宗教上の理由()により、画像生成AIのWebUIは使えないので、なんとかしてDiffusersライブラリで利用できるようにしたいという試みを始めました。

結論(わかっている人向け)

「AnyTest」をDiffusersライブラリで利用する場合は、from_single_fileを利用するのではなく、使いたいモデルのsafetensor重みをダウンロードして、他のCannyなどのStable Diffusion XL のControlNetモデルconfig.jsonファイルと一緒に、適当なフォルダに格納したのち、ControlNetModel.from_pretrainedで読み込みましょう。

今回生成した画像

下記の画像を見ていただければわかりますが、本当に強力なControlNetです。これを一つのControlNetでできているのがすごいです。

アニメ画像のフィギュア化

下記の通り、以前にFLUX.1-devで生成した画像(背景抜き)をフィギュアにしてみました

入力した画像

生成した画像

構図的にも難しいと思いますが、それでもリアルなフィギュア風の画像にできていますね。

利用したプロンプト

メインプロンプト

a photo of an anime character figure,Yellowish-white hair , red ribbon,red eyes,white hat, white costume, black microphone, bare legs,Skin color is white

ネガティブプロンプト

nsfw, lowres, (bad), text, error, fewer, extra, missing, worst quality, jpeg artifacts, low quality, watermark, unfinished, displeasing, oldest, early, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]

一部分の修正(スカーフの追加)

また、元画像に対して一部分修正して、追加してみました。

入力した画像

生成した画像


灰色になった部分に対して、「scarf」を追加した画像をさらに表示できていますね

利用したプロンプト

メインプロンプト

scarf, Yellowish-white hair , red ribbon,red eyes,white hat, white costume, black microphone, bare legs

ネガティブプロンプト

nsfw, lowres, (bad), text, error, fewer, extra, missing, worst quality, jpeg artifacts, low quality, watermark, unfinished, displeasing, oldest, early, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]

AnyTestをDiffusersで使う方法

ここからは、最低限「AnyTest」ControlNetをDiffusersライブラリで利用する方法について解説します。

AnyTestの重みファイルをダウンロードする

AnyTest ControlNetモデルは下記よりダウンロードできます。
https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main/test_controlnet2

おすすめのモデルは「CN-anytest_v4-marged.safetensors」です。
もしくは、「CN-anytest_v3-50000_fp16.safetensors」です。

そのほかの「dim」などが記載されているファイルは、Control LoRAと呼ばれる重みファイルで、LoRAとしてモデルにくっつけるだけで、ControlNetと同じような効果を出すことができる、非常にすごい技術です。
ちなみに「pn」はpony系のモデル、「am」は「Animagine」系のモデルと一緒に使うLoRAです。SDXLでよく使われるモデル2つで使えるのはとてもいいですね。

しかしながら、Control LoRAをDiffusersライブラリにて簡単に使う方法を見つけることはできませんでした。
下記のように、Diffusersライブラリの中でControl LoRAを利用可能にする取り組み自体はあるようですが、効果のある形で使えるようにはまだなっていないようです。
https://github.com/huggingface/diffusers/issues/4679

(無理やり利用する方法はあるみたいですが、今回は普通にControlNetを利用すれば良いので、無視することにします)

したがって、今回は「CN-anytest_v4-marged.safetensors」重みをダウンロードしておいてください。

適当なControlNetからconfig.jsonを取得する

続いて、「AnyTest」ControlNetを利用するために、config.jsonが必要なので、下記のファイルをダウンロードしておいてください。
https://huggingface.co/diffusers/controlnet-canny-sdxl-1.0/blob/main/config.json

両方のファイルを同じフォルダに格納する

ダウンロードした二つのファイルは同じフォルダに格納してください。
例えば下記のようなフォルダに格納しましょう。
./inputs/AnyTest-v4/

格納する際は、モデルの重みは下記のようにリネームして保存してください。
./inputs/CN-anytest_v4/diffusion_pytorch_model.safetensors

configファイルも下記のように保存してください。
./inputs/CN-anytest_v4/config.json

コードを書く

最低限下記のようなコードをかけば動くはずです。
ただし、ControlNetに入力する参照画像を"./inputs/control_image.png"に格納しておいてください。


from diffusers import StableDiffusionXLControlNetPipeline, ControlNetModel
from diffusers.utils import load_image
import torch

cnet_path = "./inputs/AnyTest-v4/"
base_model_path = "cagliostrolab/animagine-xl-3.1"
image_path = "./inputs/control_image.png"

controlnet = ControlNetModel.from_pretrained(cnet_path,torch_dtype=torch.float16)

base = StableDiffusionXLControlNetPipeline.from_pretrained(
            base_model_path,
            controlnet=controlnet,
            torch_dtype=torch.float16,
            use_safetensors=True
        )

base.to("cuda")

control_image = load_image(image_path)
prompt = "a cute girls"
negative_prompt = "bad"

image = self.base(
            prompt=prompt,
            negative_prompt=negative_prompt,
            image=control_image,
            cfg_scale=7.0,
            controlnet_conditioning_scale=0.7,
            num_inference_steps=28,
            output_type=pil,
            width = 832,
            height = 1216,
            ).images[0]

image.save("./outputs/output_image.png".format(i))

なんで上記のようなめんどくさい方法をしないといけないのか?

そもそもとして、なんでconfig.jsonをわざわざダウンロードしないといけないのか?
普通に、HuggingfaceのControlNetのsafetensorをリンクしてロードすればいいじゃん!

と感じる方多いと思います。

ここでは、その点について補足説明しようと思います。

Diffusersライブラリに詳しい人は、単一のsafetensor重みからモデルをロードする関数をご存知だと思います。
そうです。from_single_fileメソッドです。

例えば下記のように書ければ、ロードできるように思えます。

cnet_path="./inputs/CN-anytest_v4-marged.safetensors"
controlnet = ControlNetModel.from_single_file(cnet_path, torch_dtype=torch.float16)

しかしながら、上記のように指定すると、下記のエラーが発生します。

エラー全文
OSError                                   Traceback (most recent call last)
<ipython-input-8-fed346bff60b> in <cell line: 1>()
----> 1 sdxl = SDXL()
      2 #指定しているControlNetで利用できる"./inputs/refer_prepared/0.png"などがすでにある場合は、下記はコメントアウトしても良い
      3 sdxl.prepare_multi_referimage(input_refer_image_folder = input_refer_image_folder,output_refer_image_folder = output_refer_image_folder, low_threshold = 100, high_threshold = 200, noise_level=25, blur_radius=5)
      4 

<ipython-input-4-3d4e3265793f> in __init__(self, device, config_ini_path)
    211 
    212 
--> 213         self.base = self.preprepare_model()
    214 
    215 

<ipython-input-4-3d4e3265793f> in preprepare_model(self, controlnet_path)
    235                 if cnet_path is not None:
    236                     if self.single_controlnet_bool_list[model_step]:
--> 237                         controlnet = ControlNetModel.from_single_file(cnet_path, torch_dtype=torch.float16)
    238                     else:
    239                         if self.controlnet_variant_bool_list[model_step]:

/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_validators.py in _inner_fn(*args, **kwargs)
    112             kwargs = smoothly_deprecate_use_auth_token(fn_name=fn.__name__, has_token=has_token, kwargs=kwargs)
    113 
--> 114         return fn(*args, **kwargs)
    115 
    116     return _inner_fn  # type: ignore

/content/diffusers/src/diffusers/loaders/single_file_model.py in from_single_file(cls, pretrained_model_link_or_path_or_dict, **kwargs)
    266                 )  # some configs contain a subfolder key, e.g. StableCascadeUNet
    267 
--> 268             diffusers_model_config = cls.load_config(
    269                 pretrained_model_name_or_path=default_pretrained_model_config_name,
    270                 subfolder=subfolder,

/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_validators.py in _inner_fn(*args, **kwargs)
    112             kwargs = smoothly_deprecate_use_auth_token(fn_name=fn.__name__, has_token=has_token, kwargs=kwargs)
    113 
--> 114         return fn(*args, **kwargs)
    115 
    116     return _inner_fn  # type: ignore

/content/diffusers/src/diffusers/configuration_utils.py in load_config(cls, pretrained_model_name_or_path, return_unused_kwargs, return_commit_hash, **kwargs)
    404                 )
    405             except EntryNotFoundError:
--> 406                 raise EnvironmentError(
    407                     f"{pretrained_model_name_or_path} does not appear to have a file named {cls.config_name}."
    408                 )

OSError: Lykon/dreamshaper-8 does not appear to have a file named config.json.

このエラーは、config.jsonファイルを取得できないことが原因です。

大前提として、safetensor重みは、モデルの重みを辞書型で保存しています。
つまり、ファイルの中にはモデルの各層の名前(例えば、"model.diffusion_model.input_blocks.2.1.transformer_blocks.0.attn2.to_k.weight")とその層での重みの値(行列)がセットで保存されています。

実際に下記のようなコードでsafetensor重みの中を見ることができます。

重みファイルの中身を見るコード
import safetensors
from safetensors import safe_open

# safetensorファイルのパスを指定
safetensor_file_path = 'inputs/CN-anytest_v3-45000_fp16.safetensors'

# safetensorsファイルを読み込む
with safe_open(safetensor_file_path, framework="pt") as f:
    tensors = {key: f.get_tensor(key) for key in f.keys()}

# keyごとに重みを表示
for key, tensor in tensors.items():
    print(f"Key: {key}")
    #print(f"Tensor: {tensor}")
    print(f"Shape: {tensor.shape}")
    print(f"Dtype: {tensor.dtype}")
    print("="*50)
結果(長いので一部省略)
Key: add_embedding.linear_1.bias
Shape: torch.Size([1280])
Dtype: torch.float16
==================================================
Key: add_embedding.linear_1.weight
Shape: torch.Size([1280, 2816])
Dtype: torch.float16
==================================================
Key: add_embedding.linear_2.bias
Shape: torch.Size([1280])
Dtype: torch.float16
==================================================
・・・・・・
==================================================
Key: time_embedding.linear_1.weight
Shape: torch.Size([1280, 320])
Dtype: torch.float16
==================================================
Key: time_embedding.linear_2.bias
Shape: torch.Size([1280])
Dtype: torch.float16
==================================================
Key: time_embedding.linear_2.weight
Shape: torch.Size([1280, 1280])
Dtype: torch.float16
==================================================

その上で、Diffusersライブラリは、この重みに保存されている層の名前(Key)を元に、ロードされたモデル重みがなんのモデルなのかを把握しています。

該当するのは下記のコードです。
https://github.com/huggingface/diffusers/blob/main/src/diffusers/loaders/single_file_utils.py

該当部分

・・・

CHECKPOINT_KEY_NAMES = {
    "v2": "model.diffusion_model.input_blocks.2.1.transformer_blocks.0.attn2.to_k.weight",
    "xl_base": "conditioner.embedders.1.model.transformer.resblocks.9.mlp.c_proj.bias",
    "xl_refiner": "conditioner.embedders.0.model.transformer.resblocks.9.mlp.c_proj.bias",
    "upscale": "model.diffusion_model.input_blocks.10.0.skip_connection.bias",
    "controlnet": "control_model.time_embed.0.weight",
    "playground-v2-5": "edm_mean",
    "inpainting": "model.diffusion_model.input_blocks.0.0.weight",
    "clip": "cond_stage_model.transformer.text_model.embeddings.position_embedding.weight",
    "clip_sdxl": "conditioner.embedders.0.transformer.text_model.embeddings.position_embedding.weight",
    "clip_sd3": "text_encoders.clip_l.transformer.text_model.embeddings.position_embedding.weight",
    "open_clip": "cond_stage_model.model.token_embedding.weight",
    "open_clip_sdxl": "conditioner.embedders.1.model.positional_embedding",
    "open_clip_sdxl_refiner": "conditioner.embedders.0.model.text_projection",
    "open_clip_sd3": "text_encoders.clip_g.transformer.text_model.embeddings.position_embedding.weight",
    "stable_cascade_stage_b": "down_blocks.1.0.channelwise.0.weight",
    "stable_cascade_stage_c": "clip_txt_mapper.weight",
    "sd3": "model.diffusion_model.joint_blocks.0.context_block.adaLN_modulation.1.bias",
    "animatediff": "down_blocks.0.motion_modules.0.temporal_transformer.transformer_blocks.0.attention_blocks.0.pos_encoder.pe",
    "animatediff_v2": "mid_block.motion_modules.0.temporal_transformer.norm.bias",
    "animatediff_sdxl_beta": "up_blocks.2.motion_modules.0.temporal_transformer.norm.weight",
    "animatediff_scribble": "controlnet_cond_embedding.conv_in.weight",
    "animatediff_rgb": "controlnet_cond_embedding.weight",
    "flux": [
        "double_blocks.0.img_attn.norm.key_norm.scale",
        "model.diffusion_model.double_blocks.0.img_attn.norm.key_norm.scale",
    ],
}

・・・

def infer_diffusers_model_type(checkpoint):
    if (
        CHECKPOINT_KEY_NAMES["inpainting"] in checkpoint
        and checkpoint[CHECKPOINT_KEY_NAMES["inpainting"]].shape[1] == 9
    ):
        if CHECKPOINT_KEY_NAMES["v2"] in checkpoint and checkpoint[CHECKPOINT_KEY_NAMES["v2"]].shape[-1] == 1024:
            model_type = "inpainting_v2"
        elif CHECKPOINT_KEY_NAMES["xl_base"] in checkpoint:
            model_type = "xl_inpaint"
        else:
            model_type = "inpainting"

    elif CHECKPOINT_KEY_NAMES["v2"] in checkpoint and checkpoint[CHECKPOINT_KEY_NAMES["v2"]].shape[-1] == 1024:
        model_type = "v2"

    elif CHECKPOINT_KEY_NAMES["playground-v2-5"] in checkpoint:
        model_type = "playground-v2-5"

    elif CHECKPOINT_KEY_NAMES["xl_base"] in checkpoint:
        model_type = "xl_base"

    elif CHECKPOINT_KEY_NAMES["xl_refiner"] in checkpoint:
        model_type = "xl_refiner"

    elif CHECKPOINT_KEY_NAMES["upscale"] in checkpoint:
        model_type = "upscale"

    elif CHECKPOINT_KEY_NAMES["controlnet"] in checkpoint:
        model_type = "controlnet"

    elif (
        CHECKPOINT_KEY_NAMES["stable_cascade_stage_c"] in checkpoint
        and checkpoint[CHECKPOINT_KEY_NAMES["stable_cascade_stage_c"]].shape[0] == 1536
    ):
        model_type = "stable_cascade_stage_c_lite"

    elif (
        CHECKPOINT_KEY_NAMES["stable_cascade_stage_c"] in checkpoint
        and checkpoint[CHECKPOINT_KEY_NAMES["stable_cascade_stage_c"]].shape[0] == 2048
    ):
        model_type = "stable_cascade_stage_c"

    elif (
        CHECKPOINT_KEY_NAMES["stable_cascade_stage_b"] in checkpoint
        and checkpoint[CHECKPOINT_KEY_NAMES["stable_cascade_stage_b"]].shape[-1] == 576
    ):
        model_type = "stable_cascade_stage_b_lite"

    elif (
        CHECKPOINT_KEY_NAMES["stable_cascade_stage_b"] in checkpoint
        and checkpoint[CHECKPOINT_KEY_NAMES["stable_cascade_stage_b"]].shape[-1] == 640
    ):
        model_type = "stable_cascade_stage_b"

    elif CHECKPOINT_KEY_NAMES["sd3"] in checkpoint:
        model_type = "sd3"

    elif CHECKPOINT_KEY_NAMES["animatediff"] in checkpoint:
        if CHECKPOINT_KEY_NAMES["animatediff_scribble"] in checkpoint:
            model_type = "animatediff_scribble"

        elif CHECKPOINT_KEY_NAMES["animatediff_rgb"] in checkpoint:
            model_type = "animatediff_rgb"

        elif CHECKPOINT_KEY_NAMES["animatediff_v2"] in checkpoint:
            model_type = "animatediff_v2"

        elif checkpoint[CHECKPOINT_KEY_NAMES["animatediff_sdxl_beta"]].shape[-1] == 320:
            model_type = "animatediff_sdxl_beta"

        elif checkpoint[CHECKPOINT_KEY_NAMES["animatediff"]].shape[1] == 24:
            model_type = "animatediff_v1"

        else:
            model_type = "animatediff_v3"

    elif any(key in checkpoint for key in CHECKPOINT_KEY_NAMES["flux"]):
        if any(
            g in checkpoint for g in ["guidance_in.in_layer.bias", "model.diffusion_model.guidance_in.in_layer.bias"]
        ):
            model_type = "flux-dev"
        else:
            model_type = "flux-schnell"
    else:
        model_type = "v1"

    return model_type

上記のコードを見ると、CHECKPOINT_KEY_NAMESに記載されているモデルの層の名前と一致した時に、infer_diffusers_model_type関数にて、モデルの種類が決まります。

ここで、CHECKPOINT_KEY_NAMESで指定されているどの名前の層も、ロードされたsafetensorファイルに存在しない場合、"v1"model_typeとして選定されます。

ここで、このmodel_typeはさらに同じファイル中の下記部分にて参照されます。

該当部分

・・・
DIFFUSERS_DEFAULT_PIPELINE_PATHS = {
    "xl_base": {"pretrained_model_name_or_path": "stabilityai/stable-diffusion-xl-base-1.0"},
    "xl_refiner": {"pretrained_model_name_or_path": "stabilityai/stable-diffusion-xl-refiner-1.0"},
    "xl_inpaint": {"pretrained_model_name_or_path": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1"},
    "playground-v2-5": {"pretrained_model_name_or_path": "playgroundai/playground-v2.5-1024px-aesthetic"},
    "upscale": {"pretrained_model_name_or_path": "stabilityai/stable-diffusion-x4-upscaler"},
    "inpainting": {"pretrained_model_name_or_path": "Lykon/dreamshaper-8-inpainting"},
    "inpainting_v2": {"pretrained_model_name_or_path": "stabilityai/stable-diffusion-2-inpainting"},
    "controlnet": {"pretrained_model_name_or_path": "lllyasviel/control_v11p_sd15_canny"},
    "v2": {"pretrained_model_name_or_path": "stabilityai/stable-diffusion-2-1"},
    "v1": {"pretrained_model_name_or_path": "Lykon/dreamshaper-8"},
    "stable_cascade_stage_b": {"pretrained_model_name_or_path": "stabilityai/stable-cascade", "subfolder": "decoder"},
    "stable_cascade_stage_b_lite": {
        "pretrained_model_name_or_path": "stabilityai/stable-cascade",
        "subfolder": "decoder_lite",
    },
    "stable_cascade_stage_c": {
        "pretrained_model_name_or_path": "stabilityai/stable-cascade-prior",
        "subfolder": "prior",
    },
    "stable_cascade_stage_c_lite": {
        "pretrained_model_name_or_path": "stabilityai/stable-cascade-prior",
        "subfolder": "prior_lite",
    },
    "sd3": {
        "pretrained_model_name_or_path": "stabilityai/stable-diffusion-3-medium-diffusers",
    },
    "animatediff_v1": {"pretrained_model_name_or_path": "guoyww/animatediff-motion-adapter-v1-5"},
    "animatediff_v2": {"pretrained_model_name_or_path": "guoyww/animatediff-motion-adapter-v1-5-2"},
    "animatediff_v3": {"pretrained_model_name_or_path": "guoyww/animatediff-motion-adapter-v1-5-3"},
    "animatediff_sdxl_beta": {"pretrained_model_name_or_path": "guoyww/animatediff-motion-adapter-sdxl-beta"},
    "animatediff_scribble": {"pretrained_model_name_or_path": "guoyww/animatediff-sparsectrl-scribble"},
    "animatediff_rgb": {"pretrained_model_name_or_path": "guoyww/animatediff-sparsectrl-rgb"},
    "flux-dev": {"pretrained_model_name_or_path": "black-forest-labs/FLUX.1-dev"},
    "flux-schnell": {"pretrained_model_name_or_path": "black-forest-labs/FLUX.1-schnell"},
}

・・・

def fetch_diffusers_config(checkpoint):
    model_type = infer_diffusers_model_type(checkpoint)
    model_path = DIFFUSERS_DEFAULT_PIPELINE_PATHS[model_type]

    return model_path

すなわち、fetch_diffusers_config関数の中で、infer_diffusers_model_type関数が利用され、model_type"v1"に決定され、DIFFUSERS_DEFAULT_PIPELINE_PATHS辞書のKeyとして"v1"を入れることで、model_path={"pretrained_model_name_or_path": "Lykon/dreamshaper-8"}となります。

さらに、もう一段深掘ると、fetch_diffusers_config関数は、下記の通り、from_single_fileメソッドで使われていることがわかります。

該当部分

該当するコードは下記です。
https://github.com/huggingface/diffusers/blob/6cf8d98ce11867ebe6577d03508e3e06b37ade03/src/diffusers/loaders/single_file_model.py#L110

・・・
class FromOriginalModelMixin:
    """
    Load pretrained weights saved in the `.ckpt` or `.safetensors` format into a model.
    """

    @classmethod
    @validate_hf_hub_args
    def from_single_file(cls, pretrained_model_link_or_path_or_dict: Optional[str] = None, **kwargs):
・・・
             if config:
                if isinstance(config, str):
                    default_pretrained_model_config_name = config
                else:
                    raise ValueError(
                        (
                            "Invalid `config` argument. Please provide a string representing a repo id"
                            "or path to a local Diffusers model repo."
                        )
                    )

            else:
                config = fetch_diffusers_config(checkpoint)
                default_pretrained_model_config_name = config["pretrained_model_name_or_path"]
・・・

            diffusers_model_config = cls.load_config(
                pretrained_model_name_or_path=default_pretrained_model_config_name,
                subfolder=subfolder,
                local_files_only=local_files_only,
            )

・・・

はい、こちらでcls.load_configクラスメソッドが出てきましたね。エラーメッセージは下記でした。
したから3つ目のブロックの268行目をみてみるとcls.load_configを使っており、ここでエラーが出ていますね。

・・・
/content/diffusers/src/diffusers/loaders/single_file_model.py in from_single_file(cls, pretrained_model_link_or_path_or_dict, **kwargs)
    266                 )  # some configs contain a subfolder key, e.g. StableCascadeUNet
    267 
--> 268             diffusers_model_config = cls.load_config(
    269                 pretrained_model_name_or_path=default_pretrained_model_config_name,
    270                 subfolder=subfolder,

/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_validators.py in _inner_fn(*args, **kwargs)
    112             kwargs = smoothly_deprecate_use_auth_token(fn_name=fn.__name__, has_token=has_token, kwargs=kwargs)
    113 
--> 114         return fn(*args, **kwargs)
    115 
    116     return _inner_fn  # type: ignore

/content/diffusers/src/diffusers/configuration_utils.py in load_config(cls, pretrained_model_name_or_path, return_unused_kwargs, return_commit_hash, **kwargs)
    404                 )
    405             except EntryNotFoundError:
--> 406                 raise EnvironmentError(
    407                     f"{pretrained_model_name_or_path} does not appear to have a file named {cls.config_name}."
    408                 )

OSError: Lykon/dreamshaper-8 does not appear to have a file named config.json.

このエラーは、fetch_diffusers_config関数の中で、model_path={"pretrained_model_name_or_path": "Lykon/dreamshaper-8"}と決められてしまったことから発生しています。

ここで、"Lykon/dreamshaper-8"リポジトリをHuggingfaceで探してみましょう。
https://huggingface.co/Lykon/dreamshaper-8/tree/main

みてわかるように、config.jsonが本リポジトリには存在しません。
したがって、OSError: Lykon/dreamshaper-8 does not appear to have a file named config.json.というエラーになります。

エラーの原因がわかりました。
ここで、一番最初に何が原因だったかを思い出すと、Diffusersライブラリでは、safetensor重みの中のモデルの層の名前から、モデルの型を決定していることから始まっています。
今回の「AnyTest」重みに保存されている層の名前が、CHECKPOINT_KEY_NAMESに記載されていないのが問題でした。

しかし、実は、これは「AnyTest」モデルに限った話ではありません。
SDXLのControlNetとしてよく利用されている、下記の公式のモデルなども、実は同じ層の名前で保存されています。(一致することを確認しました)
https://huggingface.co/diffusers/controlnet-canny-sdxl-1.0

つまり、普通に公式で実装されているControlNetであっても、from_single_fileはうまく動作しません。
したがって、SDXLのControlNetがfrom_single_fileメソッドをうまく利用できない形になっていることが原因です。(これはバグなのか仕様なのか)

そこで、今回は、上記のdiffusers/controlnet-canny-sdxl-1.0リポジトリの中のconfigファイルを「AnyTest」モデルにて、無理やり一緒に利用することで、エラーを回避した形になります。
(モデルの層の名前と、各層のモデルの層の個数が完全に一致していることを確認したため、問題ないと思います)

そのために、モデルファイルとコンフィグファイルを同じフォルダ(リポジトリ)に格納し、フォルダで指定して、from_pretrainedメソッドを利用することで、フォルダ内のconfigファイルが採用されて、読み込むことできるようになりました。

使用上の注意

ここまで書いてきてあれですが、「AnyTest」を使ってみて一つだけ使いにくいなと思った点があります。それは、
色が引き継がれないこと
です。

もしかしたら私の使い方が悪いのかもしれないですが、少なくとも上記の使い方では、ControlNetに入力した構図は引き継がれますが、色に関しては、プロンプトでかなり正確に指定しないと引き継がれません。

なので、元の画像をベースに少しだけ微修正したい場合は、素直にStableDiffusionXLInpaintPipelineを使うのが良いかなと思いました。

StableDiffusionXLInpaintPipelineの威力は、下記記事で説明しています。
https://zenn.dev/asap/articles/8f51ffd4fc29b8

逆に言うと、色の変化の正確性がそこまで求められない場合(つまり、生成画像例くらいの色の再現性で良い場合)には、その他のモデルと一線を画すレベルで高性能なモデルです。
(色の再現性を高める方法があれば、ぜひ教えて欲しいです。多分私が知らないだけです)

まとめ

ここまでで、超強力は汎用ControlNetの「AnyTest」をDiffusersライブラリで利用する方法を記載しました。

色だけはプロンプトで指定してあげないといけないですが、形状の変換という観点では、非常に高性能なControlNetモデルだなと思いました。
(そもそも、あらゆるタスクを一つのControlNetでできるというのがすごい。とりあえず最初に試すモデルになりそう)

では、ここまで読んでくださりありがとうございました!

ここからは、実際に使ったコードの解説をしますので、興味のある方のみご覧ください。

詳細な内容

ここからは、今回の記事を書くにあたって実施した実験と、実際のコードに関しての解説を行います。かなり細かい内容なので、興味のある方のみご覧ください(ほぼ忘備録目的)

成果物

下記のリポジトリをご覧ください。

https://github.com/personabb/colab_AI_sample/tree/main/colab_SDXLControlNet2_sample

事前準備

AnyTestの重みファイルをダウンロードする

https://huggingface.co/2vXpSwA7/iroiro-lora/blob/main/test_controlnet2/CN-anytest_v4-marged.safetensors
上述しました。

適当なControlNetからconfig.jsonを取得する

https://huggingface.co/diffusers/controlnet-canny-sdxl-1.0/blob/main/config.json
上述しました。

参照画像を用意する

フィギュア化の参照画像

この画像を./inputs/refer/figre/tile.pngとして保存します。

部分修正の参照画像(スカーフ追加)

この画像を./inputs/refer/scarf/tile.pngとして保存します。

想定されるディレクトリ構造

Google Driveのディレクトリ構造は下記を想定します。

MyDrive/
    └ colab_AI_sample/
          └ colab_SDXLControlNet2_sample/
                  ├ configs/
                  |    └ config.ini
                  ├ inputs/
                  |    ├ CN-anytest_v4/
                  |    |        ├ diffusion_pytorch_model.safetensors
                  |    |        └ config.json
                  |    ├ refer/
                  |    |    ├ figre/
                  |    |    |   └ tile.png
                  |    |    └ scarf/
                  |    |    |   └ tile.png
                  |    └ refer_prepared/
                  ├ outputs/
                  ├ module/
                  |    └ module_sdxl.py
                  └ SDXLControlNet_sample.ipynb

使い方の解説

SDXLControlNet_sample.ipynbをGoogle Colabratoryアプリで開いてください。
ファイルを右クリックすると「アプリで開く」という項目が表示されるため、そこからGoogle Colabratoryアプリを選択してください。

もし、ない場合は、「アプリを追加」からアプリストアに行き、「Google Colabratory」で検索してインストールをしてください。

Google Colabratoryアプリで開いたら、後述するパラメータとプロンプトの設定の後で、SDXLControlNet_sample.ipynbのメモを参考にして、一番上のセルから順番に実行すると、画像が5枚「outputs」フォルダに生成されます。

また、最後まで実行後、パラメータを変更して再度実行する場合は、「ランタイム」→「セッションを再起動して全て実行する」をクリックしてください。

パラメータの変更

SDXLControlNet_sample.ipynbの5セル目と8セル目が該当します。

パラメータの設定 5セル目
#モデルの設定を行う。

config_text = """
[SDXL]
device = auto
n_steps=28
seed=42

;from_single_file = True
;base_model_path = stabilityai/stable-diffusion-xl-base-1.0
base_model_path = Asahina2K/Animagine-xl-3.1-diffuser-variant-fp16
;base_model_path = ./inputs/pony/ponyDiffusionV6XL_v6StartWithThisOne.safetensors
;base_model_path = ./inputs/fudukiMix_v20.safetensors
;from_single_vae_file = True
;base_vae_model = ./inputs/pony/sdxl_vae.safetensors

use_controlnet = True

;controlnet_path0 = ./inputs/CN-anytest_v3-45000/
;from_single_controlnet_file0 = False
;use_controlnet_variant0 = True
controlnet_path0 = ./inputs/CN-anytest_v4/
from_single_controlnet_file0 = False
use_controlnet_variant0 = False
;controlnet_path1 = diffusers/controlnet-depth-sdxl-1.0
;from_single_controlnet_file1 = False
;use_controlnet_variant1 = True
;controlnet_path2 = xinsir/controlnet-openpose-sdxl-1.0
;from_single_controlnet_file2 = False
;use_controlnet_variant2 = True
;controlnet_path3 = diffusers/controlnet-canny-sdxl-1.0
;from_single_controlnet_file3 = False
;use_controlnet_variant3 = True
;controlnet_path4 = diffusers/controlnet-zoe-depth-sdxl-1.0
;from_single_controlnet_file4 = False
;use_controlnet_variant4 = True

;残念ながら下記は動かない
;controlnet_path0 = https://huggingface.co/2vXpSwA7/iroiro-lora/blob/main/test_controlnet2/CN-anytest_v4-marged.safetensors
;controlnet_config_repo0 = diffusers/controlnet-canny-sdxl-1.0
;from_single_controlnet_file0 = True
;use_controlnet_variant0 = False

;modeと同じ名前の参照画像が格納されているとする。(tile.pngなど)
control_mode0 = tile
;control_mode1 = depth
;control_mode2 = openpose_full
;control_mode3 = canny
;control_mode4 = zoe_depth

;以下は待機用
;control_mode0 = blur
;control_mode0 = gray
;control_mode0 = lq
;control_mode0 = openpose_face
;control_mode0 = openpose_faceonly
;control_mode0 = openpose



;lora_weight_repo0 = erohinem/LoRA
;lora_weight_path0 = Dreamyvibes-lora.safetensors
;lora_weight_repo1 = xxx
;lora_weight_path1 = xxx.safetensors
;lora_scale0 = 1.0
;lora_scale1 = 1.0
;trigger_word0 = "Dreamyvibes Artstyle"
;trigger_word1 = "xxx"

;select_solver = LCM
select_solver = DPM
;select_solver = Eulera
;select_solver = FMEuler

use_karras_sigmas = True
scheduler_algorithm_type = dpmsolver++
solver_order = 2

cfg_scale = 7.0
width = 832
height = 1216
output_type = pil
aesthetic_score = 6
negative_aesthetic_score = 2.5

save_latent_simple = False
save_latent_overstep = False
save_latent_approximation = False
save_predict_skip_x0 = False


"""

with open("configs/config.ini", "w", encoding="utf-8") as f:
  f.write(config_text)

念の為の記載ですが;はコメントアウトの記号です。実験の設定を復元可能なように過去の設定を残したりしています。

  • n_steps
    • 拡散モデルのノイズ除去のstep数
    • モデルごとに適切な値は異なるので確認する。
  • from_single_file
    • モデルを単一のsafetensorファイルから読み込む場合はTrueにする
  • base_model_path
    • 利用するモデルの指定。
    • 現時点の実装では、単一ファイルからのロードでない場合は、variant = fp16に対応しているモデルを利用する必要がある
    • 対応しているかどうかは、ファイル名にfp16と記載があるかどうかで判断できる
  • from_single_vae_filebase_vae_model
    • VAEを指定する場合は、ここも設定する。
    • この実装ではRefinerは設定できない。使わないので破棄しました
  • use_controlnet
    • ControlNetを利用するかどうかのFlag
  • controlnet_path0からcontrolnet_path4
    • 利用するControlNetのモデルパス
    • 一応複数指定できる
  • control_mode0からcontrol_mode4
    • 利用するControlNetのモード。
    • 指定したcontrolnet_pathの数字に応じて、合わせて記載する
  • use_controlnet_variant0からuse_controlnet_variant4
    • variant=fp16を採用するかどうか。
    • 基本は、対応しているモデルが少ないのでFalseで良い
  • lora_weight_repo0からlora_weight_repo9
    • 利用するLoRAのリポジトリ指定。複数記載すれば多重に適用可能
    • HuggingFaceにあるLoRA利用する場合は、ここにリポジトリ名も指定する
  • lora_weight_path0からlora_weight_path9
    • 利用するLoRAのパスを指定する。複数記載すれば多重に適用可能
    • HuggingFaceにあるLoRA利用する場合は、ここにはLoRAで利用するファイル名のみを指定する
    • CivitaiなどのLoRAファイルをダウンロードして利用するファイルは、ファイルを保存した場所の相対パスを記述する('lora_weight_repo`はNoneに設定する)
  • lora_scale0からlora_scale9
    • 利用するLoRAの適用度合い
  • trigger_word0からtrigger_word9
    • 利用するLoRAで推奨されるTrigger Wordの設定
  • select_solver
    • 利用するサンプラーの指定。
  • widthheight
    • 生成する画像のサイズ
  • save_latent_simplesave_latent_overstepsave_latent_approximation
    • 生成途中も出力するかどうか、出力する場合どのような形式で出力するかのフラグ
    • save_latent_simpleはノイズから生成される過程を出力
    • save_latent_overstepはある程度綺麗な画像が遷移するような過程を出力
    • save_latent_approximationは潜在表現からの一次近似で出力
  • save_predict_skip_x0
    • https://zenn.dev/asap/articles/7940b17be86da7
    • 上記の記事に記載の通り、forkしたDiffusersを利用して、コードを書き換えれば利用可能
    • 基本的には利用不可
    • そのうち、親クラスを継承する形で、新規にカスタムパイプラインを作って実装予定

8セル目は下記です。

for i in range(5):
      start = time.time()
      image = sdxl.generate_image(main_prompt,neg_prompt = neg_prompt, image_path = output_refer_image_folder, controlnet_conditioning_scale = [0.8])
      print("generate image time: ", time.time()-start)
      image.save("./outputs/SDXL_result_{}.png".format(i))

こちらのcontrolnet_conditioning_scale = [0.8]の部分が変更可能なパラメータです。
controlnet_conditioning_scaleは、ControlNetを利用する場合に、どの程度反映させるかを設定するパラメータです。大きい方が、制御が強くなります。

プロンプトの変更

SDXLControlNet_sample.ipynbの6セル目が該当します。

#読み上げるプロンプトを設定する。

#figre
main_prompt = """
a photo of an anime character figure,Yellowish-white hair , red ribbon,red eyes,white hat, white costume, black microphone, bare legs,Skin color is white
"""

#scarf
#main_prompt = """
#scarf, Yellowish-white hair , red ribbon,red eyes,white hat, white costume, #black microphone, bare legs
#"""


neg_prompt="""
nsfw, lowres, (bad), text, error, fewer, extra, missing, worst quality, jpeg artifacts, low quality, watermark, unfinished, displeasing, oldest, early, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]
"""

#コントロールネットに入力する参照画像の名前はcontrolnetのモードと一致させてください。(tile.png, depth.jpegなど)
input_refer_image_folder = "./inputs/refer/figre"
#コントロールネットに入力する変換後の参照画像の格納フォルダ。命名規則は上記と同様
output_refer_image_folder = "./inputs/refer_prepared/figre"


6セル目のmain_promptを変更して、8セル目を実行することで、変更されたプロンプトが反映されて実行されます。

また、タスクに応じて、参照画像を指定するフォルダのpathを正しく指定してください。
input_refer_image_folderには変換元の画像を入れれば、実行後に、適切な画像に変換されて、output_refer_image_folderに保存されます。

また、参照画像のファイル名は、Control_modeに合わせる必要があります。
例えば、depth.pngやopenpose-full.webpなど

実行結果

記事上部の画像をご覧ください。
それ以外の実験は実施していません。

コードの解説

SDXLControlNet_sample.ipynb

SDXLControlNet_sample.ipynbについて解説します。

コードは下記よりご覧ください
https://github.com/personabb/colab_AI_sample/blob/main/colab_SDXLControlNet2_sample/SDXLControlNet_sample.ipynb

1セル目


#Google Driveのフォルダをマウント(認証入る)
from google.colab import drive
drive.mount('/content/drive')

マイドライブのマウントを行っています。
認証が入り、一定時間認証しないとエラーになってしまうので、最初に持ってきて実行と認証をまとめて実施します。

2セル目

#SDXL で必要なモジュールのインストール
!pip install -U bitsandbytes diffusers optimum-quanto peft tensorflow-metadata transformers scikit-learn ftfy accelerate invisible_watermark safetensors controlnet-aux mediapipe timm

必要なモジュールをインストールしています。

3セル目

# カレントディレクトリを本ファイルが存在するディレクトリに変更する。
import glob
import os
pwd = os.path.dirname(glob.glob('/content/drive/MyDrive/colabzenn/colab_SDXLControlNet2_sample/SDXLControlNet_sample.ipynb', recursive=True)[0])
print(pwd)

%cd $pwd
!pwd

カレントディレクトリを、ノートブックが存在しているディレクトリに設定しています。

4セル目

#モジュールをimportする
from module.module_sdxl import SDXL
import time

モジュールをinportしています。
sdxlの実装コードはmodule/module_sdxl.pyで記述しております。
そちらも後述します

5セル目

#モデルの設定を行う。

config_text = """
[SDXL]
device = auto
n_steps=28
seed=42

;from_single_file = True
;base_model_path = stabilityai/stable-diffusion-xl-base-1.0
base_model_path = Asahina2K/Animagine-xl-3.1-diffuser-variant-fp16
;base_model_path = ./inputs/pony/ponyDiffusionV6XL_v6StartWithThisOne.safetensors
;base_model_path = ./inputs/fudukiMix_v20.safetensors
;from_single_vae_file = True
;base_vae_model = ./inputs/pony/sdxl_vae.safetensors

use_controlnet = True

;controlnet_path0 = ./inputs/CN-anytest_v3-45000/
;from_single_controlnet_file0 = False
;use_controlnet_variant0 = True
controlnet_path0 = ./inputs/CN-anytest_v4/
from_single_controlnet_file0 = False
use_controlnet_variant0 = False
;controlnet_path1 = diffusers/controlnet-depth-sdxl-1.0
;from_single_controlnet_file1 = False
;use_controlnet_variant1 = True
;controlnet_path2 = xinsir/controlnet-openpose-sdxl-1.0
;from_single_controlnet_file2 = False
;use_controlnet_variant2 = True
;controlnet_path3 = diffusers/controlnet-canny-sdxl-1.0
;from_single_controlnet_file3 = False
;use_controlnet_variant3 = True
;controlnet_path4 = diffusers/controlnet-zoe-depth-sdxl-1.0
;from_single_controlnet_file4 = False
;use_controlnet_variant4 = True

;残念ながら下記は動かない
;controlnet_path0 = https://huggingface.co/2vXpSwA7/iroiro-lora/blob/main/test_controlnet2/CN-anytest_v4-marged.safetensors
;controlnet_config_repo0 = diffusers/controlnet-canny-sdxl-1.0
;from_single_controlnet_file0 = True
;use_controlnet_variant0 = False

;modeと同じ名前の参照画像が格納されているとする。(tile.pngなど)
control_mode0 = tile
;control_mode1 = depth
;control_mode2 = openpose_full
;control_mode3 = canny
;control_mode4 = zoe_depth

;以下は待機用
;control_mode0 = blur
;control_mode0 = gray
;control_mode0 = lq
;control_mode0 = openpose_face
;control_mode0 = openpose_faceonly
;control_mode0 = openpose



;lora_weight_repo0 = erohinem/LoRA
;lora_weight_path0 = Dreamyvibes-lora.safetensors
;lora_weight_repo1 = xxx
;lora_weight_path1 = xxx.safetensors
;lora_scale0 = 1.0
;lora_scale1 = 1.0
;trigger_word0 = "Dreamyvibes Artstyle"
;trigger_word1 = "xxx"

;select_solver = LCM
select_solver = DPM
;select_solver = Eulera
;select_solver = FMEuler

use_karras_sigmas = True
scheduler_algorithm_type = dpmsolver++
solver_order = 2

cfg_scale = 7.0
width = 832
height = 1216
output_type = pil
aesthetic_score = 6
negative_aesthetic_score = 2.5

save_latent_simple = False
save_latent_overstep = False
save_latent_approximation = False
save_predict_skip_x0 = False


"""

with open("configs/config.ini", "w", encoding="utf-8") as f:
  f.write(config_text)

実験の設定などを記述する部分です。
直接configs/config.iniを書き換えても、このセルを実行したら上書きされてしまうので注意してください。

;をつけるとコメントアウトできるので、今回の実験では不要だが、残しておきたい実験設定などはコメントアウトして残しておくことをお勧めします。

6セル目

#読み上げるプロンプトを設定する。

#figre red
#main_prompt = """
#a photo of an anime character figure,Yellowish-white hair , red ribbon,red eyes,white hat, white costume, black microphone, bare legs,Skin color is white
#"""

#scarf scketch
main_prompt = """
scarf, Yellowish-white hair , red ribbon,red eyes,white hat, white costume, black microphone, bare legs
"""


neg_prompt="""
nsfw, lowres, (bad), text, error, fewer, extra, missing, worst quality, jpeg artifacts, low quality, watermark, unfinished, displeasing, oldest, early, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]
"""

#コントロールネットに入力する参照画像の名前はcontrolnetのモードと一致させてください。(tile.png, depth.jpegなど)
input_refer_image_folder = "./inputs/refer/sketch2"
#コントロールネットに入力する変換後の参照画像の格納フォルダ。命名規則は上記と同様
output_refer_image_folder = "./inputs/refer_prepared/sketch2"


生成するプロンプトと、ControlNetで利用する参照画像の格納場所を記述してます。

7セル目


sdxl = SDXL()
#指定しているControlNetで利用できる"./inputs/refer_prepared/tile.png"などがすでにある場合は、下記はコメントアウトしても良い
sdxl.prepare_multi_referimage(input_refer_image_folder = input_refer_image_folder,output_refer_image_folder = output_refer_image_folder, low_threshold = 100, high_threshold = 200, noise_level=25, blur_radius=5)

SDXLのクラスのクラスインスタンスを取得しています。
加えてprepare_multi_referimageメソッドを実行しています。

このメソッドでは、今回指定されたcontrol_modeをもとに、それに該当する画像を変換して、格納し直します。

例えばdepthのControlNetを利用する場合は、control_modeをdepthに指定することで、普通の画像(depth.png)から深度マップ(depth.png)に変換します。

8セル目

for i in range(5):
      start = time.time()
      image = sdxl.generate_image(main_prompt,neg_prompt = neg_prompt, image_path = output_refer_image_folder, controlnet_conditioning_scale = [0.8])
      print("generate image time: ", time.time()-start)
      image.save("./outputs/SDXL_result_{}.png".format(i))

ここで指定したseed値から連続で5枚分の画像を生成し、生成にかかった時間も計測します。
生成された画像はoutputsフォルダに格納されます。

module/module_sdxl.py

module/module_sdxl.pyについて解説します。

コードは下記よりご覧ください

https://github.com/personabb/colab_AI_sample/blob/main/colab_SDXLControlNet2_sample/module/module_sdxl.py

簡単に下記で説明します。過去の記事と重複するものは解説していません

FLUXクラスのコンストラクタ

class SDXL:
    def __init__(self,device = None, config_ini_path = './configs/config.ini'):
    ・・・
        self.use_controlnet = config_dict.get("use_controlnet", "False")
        if self.use_controlnet == "False":
            self.use_controlnet = False
        else:
            self.use_controlnet = True

        self.controlnet_path_list = []
        for i in range(5):
            if config_dict.get(f"controlnet_path{i}", "None") != "None":
                self.controlnet_path_list.append(config_dict[f"controlnet_path{i}"])
            else:
                self.controlnet_path_list.append(None)

        self.single_controlnet_bool_list = []
        for i in range(5):
            if config_dict.get(f"from_single_controlnet_file{i}", "False") != "False":
                self.single_controlnet_bool_list.append(True)
            else:
                self.single_controlnet_bool_list.append(False)

        self.controlnet_variant_bool_list = []
        for i in range(5):
            if config_dict.get(f"use_controlnet_variant{i}", "False") != "False":
                self.controlnet_variant_bool_list.append(True)
            else:
                self.controlnet_variant_bool_list.append(False)

        self.controlnet_config_list = []
        for i in range(5):
            if config_dict.get(f"controlnet_config_repo{i}", "None") != "None":
                self.controlnet_config_list.append(config_dict[f"controlnet_config_repo{i}"])
            else:
                self.controlnet_config_list.append(None)

        if not self.use_controlnet and self.controlnet_path_list == []:
            raise ValueError("controlnet_path is not set")
        if len(self.controlnet_path_list) != len(self.single_controlnet_bool_list):
            raise ValueError("controlnet_path and from_single_controlnet_file is not match")
        if len(self.controlnet_path_list) != len(self.controlnet_variant_bool_list):
            raise ValueError("controlnet_path and use_controlnet_variant is not match")
        if len(self.controlnet_path_list) != len(self.controlnet_config_list):
            raise ValueError("controlnet_path and controlnet_config_repo is not match")

        #ControlNet一つ指定につき、一つのControlModeを指定する
        self.control_modes_list = []
        for i in range(5):
            if config_dict.get(f"control_mode{i}", "None") != "None":
                self.control_modes_list.append(config_dict[f"control_mode{i}"])
            else:   
                self.control_modes_list.append(None)

        if len(self.controlnet_path_list) != len(self.control_modes_list):
            raise ValueError("controlnet_path and control_mode is not match")

ControlNetを複数適用できるようにするために、設定ファイルも複数用意できるようにしています。
記載しないとNoneになるようになっているので、一つだけの指定でも大丈夫です。

最大5つ指定できて、5のうちどの場所に指定しても問題ありません。

モデルの読み込み


    def preprepare_model(self, controlnet_path = None):
・・・
if self.use_controlnet:
            controlnets = []
            for model_step, cnet_path in enumerate(self.controlnet_path_list):
                if cnet_path is not None:
                    if self.single_controlnet_bool_list[model_step]:
                        if self.controlnet_config_list[model_step] is not None:
                            print(model_step, self.controlnet_config_list[model_step])
                            controlnet = ControlNetModel.from_single_file(cnet_path, config = self.controlnet_config_list[model_step], torch_dtype=torch.float16)
                        else:
                            controlnet = ControlNetModel.from_single_file(cnet_path, torch_dtype=torch.float16)
                    else:
                        if self.controlnet_variant_bool_list[model_step]:
                            controlnet = ControlNetModel.from_pretrained(cnet_path, 
                                        torch_dtype=torch.float16, 
                                        variant="fp16", 
                                        use_safetensors=True
                                        )
                        else:
                            controlnet = ControlNetModel.from_pretrained(cnet_path, torch_dtype=torch.float16)
                    controlnets.append(controlnet)

            print("loaded controlnet")

            if not self.SINGLE_FILE_FLAG:
                if self.VAE_FLAG:
                    base = StableDiffusionXLControlNetPipeline.from_pretrained(
                        self.base_model_path,
                        vae=vae,
                        controlnet=controlnets,
                        torch_dtype=torch.float16,
                        variant="fp16",
                        use_safetensors=True
                        )
・・・

複数のControlNetを設定できるようにしています。
また、単一ファイルから取得するのか、フォルダで指定するのか、fp16に対応しているのかなどで読み込み方が違うので、場合わけしています。

前提として、設定時に同じ数字は同じControlNetの設定だと決めて記載しています。

まとめ(2回目)

ここまで読んでくださってありがとうございました!

Discussion