[Docker] Stable Diffusion WebUI 設定

由於個人對於作業系統某種程度上的潔癖,所以對於這樣的東西把他放在 Docker 裡面也是挺合理的。雖然說,這兩個都有人把他做到 Docker 裡面去,但是實際上在跑起來的時候,問題還是挺多的。

請留意,以下列舉的都是我自己遇到的狀況,每個人的環境不同,或許會略有差異。本篇文章的操作可能會有時校性,且可能也跟硬體有關,請自行斟酌使用。

主要環境

Linux 22.04LTS Server
Intel 13th i5 with 64GB DDR5
NVIDIA A4500 20GB GDDR6 ECC


Stable Diffusion WebUI Docker

目前在 Github 上最為通用的應該是這一組,

AbdBarho/stable-diffusion-webui-docker

以本篇文章的這個版本 5.0.1 來說,應該已經算是相對穩定的版本了。而,由於我是把他放在伺服器端,所以我並沒有 X 可以使用,雖然有一點不方便,但也還算可以運作。

然後,我們把他抓下來之後,你可以直接跑應該不會有太大的問題,基本上這個版本已經比之前好很多了 我大概撞牆撞了兩個月。我這邊僅以 AUTOMATIC1111 來說明(應該還是這個介面的使用者居多吧)。

你可以使用原作者的方式來安裝,

docker compose --profile download up --build
# wait until its done, then:
docker compose --profile auto up --build

應該會經過一個漫長的等待,當你看到畫面出現,

Running on local URL:  http://localhost:7860

這個時候用瀏覽器打開網址就能看到 Stable Diffusion WebUI 的操作界面了。


Stable Diffusion WebUI Docker Compose

當然,依照原作者的方式安裝不會有太多問題,但,由於所有的資料都會放在 ./data 底下,然後所有的輸出都會放在 ./output 底下,這個時候就會有點麻煩。我剛剛說過了,我是放在遠端的伺服器上,所以我並沒有很方便的介面可以隨時去更動或是察看這兩個資料夾。

如果你在本地端跑,或是你就是單純跑在你自己在用的機器上,就沒差!

所以,我需要設定一個 docker-compose.yaml 來設定這台容器,讓我比較方便可以存取我所需要的東西。

但是!

由於原本 AUTOMATIC1111 的 entrypoint.sh 利用了一些軟連結來掛載資料夾。所以你看原作者所提供的 docker-compose.yaml 的掛載方式,

x-base_service: &base_service
    ports:
      - "7860:7860"
    volumes:
      - &v1 ./data:/data
      - &v2 ./output:/output

也就是說,我們在 Container 裡面有 /data/output 相對應的資料夾,來放運作的時候會產生的資料。而掛載這些資料夾的人,就是 entrypoint.sh 這個檔案的這個部分,

# main
MOUNTS["${ROOT}/models/Stable-diffusion"]="/data/StableDiffusion"
MOUNTS["${ROOT}/models/VAE"]="/data/VAE"
MOUNTS["${ROOT}/models/Codeformer"]="/data/Codeformer"
MOUNTS["${ROOT}/models/GFPGAN"]="/data/GFPGAN"
MOUNTS["${ROOT}/models/ESRGAN"]="/data/ESRGAN"
MOUNTS["${ROOT}/models/BSRGAN"]="/data/BSRGAN"
MOUNTS["${ROOT}/models/RealESRGAN"]="/data/RealESRGAN"
MOUNTS["${ROOT}/models/SwinIR"]="/data/SwinIR"
MOUNTS["${ROOT}/models/ScuNET"]="/data/ScuNET"
MOUNTS["${ROOT}/models/LDSR"]="/data/LDSR"
MOUNTS["${ROOT}/models/hypernetworks"]="/data/Hypernetworks"
MOUNTS["${ROOT}/models/torch_deepdanbooru"]="/data/Deepdanbooru"
MOUNTS["${ROOT}/models/BLIP"]="/data/BLIP"
MOUNTS["${ROOT}/models/midas"]="/data/MiDaS"
MOUNTS["${ROOT}/models/Lora"]="/data/Lora"

MOUNTS["${ROOT}/embeddings"]="/data/embeddings"
MOUNTS["${ROOT}/config.json"]="/data/config/auto/config.json"
MOUNTS["${ROOT}/ui-config.json"]="/data/config/auto/ui-config.json"
MOUNTS["${ROOT}/extensions"]="/data/config/auto/extensions"

# extra hacks
MOUNTS["${ROOT}/repositories/CodeFormer/weights/facelib"]="/data/.cache"

其實這對我來說有點困擾,所以,我在建立這個 Docker 映象檔時,把這個部分改掉了。

MOUNTS["${ROOT}/models"]="/data/models"

MOUNTS["${ROOT}/embeddings"]="/data/embeddings"
MOUNTS["${ROOT}/config.json"]="/data/config/auto/config.json"
MOUNTS["${ROOT}/ui-config.json"]="/data/config/auto/ui-config.json"
MOUNTS["${ROOT}/extensions"]="/data/config/auto/extensions"

然後如果你之前建立過映象檔,可以去 AUTOMATIC1111 資料夾中重新建立一份新的。接著,這個時候我們就來準備自己的 docker-compose.yaml 來啟動。

stable-diffusion-webui:
        image: sd-auto:49
        environment:
            - CLI_ARGS=--allow-code --medvram --xformers --enable-insecure-extension-access --api
        ports:
            - "7860:7860"
        volumes:
            - /mnt/ext/sd-data/.cache:/data/.cache
            - /mnt/ext/sd-data/config:/data/config
            - /mnt/ext/sd-models:/data/models
            - /mnt/ext/sd-embeddings:/data/embeddings
            - /mnt/ext/sd-repositories:/stable-diffusion-webui/repositories
            - /mnt/ext/sd-textual_inversion_templates:/stable-diffusion-webui/textual_inversion_templates
            - /mnt/ext/sd-textual_inversion:/stable-diffusion-webui/textual_inversion
            - /mnt/ext/sd-output:/output
        stop_signal: SIGINT
        deploy:
            resources:
                reservations:
                    devices:
                        - driver: nvidia
                          device_ids: ['0']
                          capabilities: [gpu]
            restart_policy:
                delay: 5s
                max_attempts: 10
                window: 120s

你會發現我把很多東西用 Volumes 拿到外面來放,我大概解釋一下,

  • /data/.cache 在容器裡被指向 /root/.cache,如果你想每次啟動機器都重抓一次本來使用的時候,曾經快取的一些資料的話,那就不用設定沒差。
  • /data/config, /data/embeddings, /data/models, /output 都另外放。
  • /stable-diffusion-webui/repositories 有些額外的資料會被拉下來放在這裡,如果你的容器重起,可能會要重抓。
  • /stable-diffusion-webui/textual_inversion_templates, /stable-diffusion-webui/textual_inversion 放外面比較好更新。

請注意!在啟動他之前,請你把上一次啟動所產生的檔案放到你的新的位置。

啟動之後就可以不管他了。由於我的 /mnt/ext/sd-* 這些資料夾,全部都是遠端連回 NAS,所以我就可以在遠端透過連回 NAS 的方式,方便察看到我所需要的檔案了。


Stable Diffusion WebUI patch

我要怎麼在不更動映象檔的情況下,打一點 patch 給容器去修理一些問題?我們剛剛有看到 entrypoint.sh 檔案,其實他最後面有這一段,

if [ -f "/data/config/auto/startup.sh" ]; then
  pushd ${ROOT}
  . /data/config/auto/startup.sh
  popd
fi

也就是說,你的 ./data/config/auto/startup.sh 應該會有這個檔案存在。那麼,如果想要對容器做什麼動作(在 WebUI 還沒被啟動之前),就可以透過修改這個檔案來做。

例如,如果你有裝 Additional Networks 這個外掛,他有一個 models/lora 是要你放 Lora 模型的地方,我這邊直接把他連到 /data/models/Lora 去比較方便,這樣我們只要把 Lora 放在同一個地方就好了。由於 extensions 被我們掛到其他地方,那這個外掛的模組讀取就會需要重新指定路徑,不然他會讀不到,

# 放在 startup.sh 最後面
# link extensions to lora
if [ -d "${ROOT}/extensions/sd-webui-additional-networks/models/lora" ] || [ -L "${ROOT}/extensions/sd-webui-additional-networks/models/lora" ] ; then
        rm -rf "${ROOT}/extensions/sd-webui-additional-networks/models/lora"
fi

if [ -d "/data/models/Lora" ]; then
        ln -sT /data/models/Lora "${ROOT}/extensions/sd-webui-additional-networks/models/lora"
fi

其中的 /data/models/Lora 就是我們剛剛在 docker-compose.yaml 當中所指定的路徑。而 Lora 則是在建立映象檔時本來就會被建立的一個資料夾。


額外的 Patch

由於現在可以支援 --nowebui,對,他雖然叫做 WebUI 但是因為可以開啟 --api 模式,但是又不想使用他的 UI 的時候,就可以打開 --nowebui 來關閉。

但是,這個關閉的地方有一個 Bug Issue#7984,就是在開啟 --nowebui 的時候會讀不到 Lora 的模型。目前有 workaround 的解法,我把他弄成 patch,請自行取用。

--- webui.py    2023-03-28 06:43:35.437298130 +0000
+++ webui-patch.py      2023-03-28 06:44:11.345979601 +0000
@@ -220,6 +220,7 @@
     api = create_api(app)

     modules.script_callbacks.app_started_callback(None, app)
+    modules.script_callbacks.before_ui_callback()

     print(f"Startup time: {startup_timer.summary()}.")
     api.launch(server_name="0.0.0.0" if cmd_opts.listen else "127.0.0.1", port=cmd_opts.port if cmd_opts.port else 7861)

就把這檔案找地方放(例如我放在 /mnt/ext/sd-data/.cache/api_lora.patch),然後在你的 ./data/config/auto/startup.sh 裡面加入,

#Patch Lora modules for api_only
patch -f -i /data/.cache/api_lora.patch /stable-diffusion-webui/webui.py

這樣他在啟動容器之前,就會先把 patch 打好,這樣若是我們啟動 --nowebui 的時候 Lora 的模型讀取才不會讀不到。


結語

裝在 Docker 裡面真的好很多!因為那個 Pytorch 跟那一堆外掛要裝的東西實在太多,如果裝在主開發環境下超可怕的!

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei