2023/04/10 更新:
現在有很多現成的 App 可以用 AI 畫圖了,建議不要再用本篇文章提到的方法,比較簡單也更快速。
原本想寫一篇如何在 Apple Silicon 的 Mac 玩 Core ML Stable Diffusion 的文章,後來發現原本文件已經很清楚了,所以覺得應該不用再贅述,改成寫一些使用的心得,還有一些小技巧好了。
先講重點:雖然 Apple 在 GitHub 上面寫跟我同規格的 M1 Ultra 48 GPU 版本可以 13 秒跑出結果,但是那是「畫圖部分」的跑分,在畫圖之前要先載入模型,然後還有 sampling 之類的動作也要花時間,而這些載入動作本身有可能比「畫圖」還要更耗時間,所以在本文我測了一下,然後整理了一些小技巧,可以讓執行速度快一點,也可以讓大家少走點彎路。
以下測試環境:
- macOS 13.1 Beta 4(22C5059b)
- Mac Studio(M1 Ultra 20 CPU,48 GPU,64GB RAM)
- Stable Diffusion v1.4
- Prompt 固定為 "a high quality photo of an astronaut riding a horse in space"
- Seed 固定使用 13
在 macOS 13.1 以上執行
雖然 macOS 13.0.1 也可以執行,但是 Apple 有在 macOS 13.1 對 Stable Difussion 做最佳化。更重要的是在 macOS 13.0.1 是無法呼叫 GPU 的,生成的圖片會變成一整片灰色。只能給 CPU 跟 Apple 的神經網路引擎(Apple's Neural Engine,縮寫為 ANE)使用,效能差很多。
使用轉換好的 Checkpoints
不要浪費時間按照原本 GitHub 上面的教學手動轉換 model,因為需要很長的時間跟大量記憶體(在 M1 Ultra 我轉換 Stable Diffusion 2 Base 花了半小時以上,用了 40GB RAM),可以在這邊下載已經轉換好的 checkpoint。
可用的預先轉換 checkpoint 包含:
- Stable Diffusion v1.4
- Stable Diffusion v1.5
- Stable Diffusion v2 base
下載需要 git-lfs,要另外用 homebrew 安裝。
下載完成之後會看到 original 跟 split_einsum 兩個版本,original 是 CPU 跟 GPU 用的,split_einsum 則可以給 ANE 使用。
每個資料夾底下又分為 packages 跟 compiled,packages 是給 python 用的,compiled 是給 swift 用的。
有些 model 會有 unet.mlpackage、unet_chunk1.mlpackage、unet_chunk2.mlpackage 三個檔案,在電腦上我們不需要把檔案分割以減少資源消耗,所以可以直接砍掉 unet_chunk1、unet_chunk2 減少佔用空間。
即使使用轉換好的 checkpoint,第一次使用的時候還是會先下載一堆東西,這是正常的。
Swift 的版本通常比較快
雖然原始文件寫用 Python 在 Mac 上比較快,但問題依然一樣,那不包含模型載入時間。實際使用 Python 時,因為每次模型載入後都還要在 runtime 轉換一次,導致載入效率非常差,反而用 Swift 因為少了一些轉換過程,所以跑起來比較快。
以下是一些測試結果,都是用預先轉換的 Stable Diffusion v1.4 checkpoint,包含模型載入時間,都是用 CPU 和 GPU 運算(測試發現在高階的 Mac 上面用 ANE 反而拖慢速度)
- original + python: 51s
- original + swift: 42s
- split_einsum + python: 43s
- split_einsum + swift: 28s
可以看到,用 Swift 版本快了約 10 秒,用 split_einsum 又比 original 快 10 秒。但上面這些測試這都是只跑一張圖的情形下,如果要跑多張圖的話請參考下一段。
使用 --image-count 指令產生大量圖片
如果你確定 prompt 沒問題的話,可以用 --image-count
指令批次產生圖片,這樣的好處是模型只需要載入一次。簡單來說,「用同一個指令畫一張圖,畫 100 次」vs「一個指令直接出 100 張圖」兩者相比,後者快上很多。
雖然上一段寫到 split_einsum
比較快,但那也是只有一張圖的情形下,若產生多張圖的話用 original 速度反而比較快。
不過 Apple 原本提供的 --image-count
指令有兩個小缺點:
第一個是除了第一張圖知道 seed 之外,後面的圖 seed 是用某個函數算出來的下一組偽隨機數,這導致我們很難在批次模式下,重新畫出一樣的圖片(除非你要整個批次執行都重跑)。第二點是它要把所有的圖片都畫完才會一次輸出,不能畫一張就先輸出一張。
我自己 fork 了一下這個 repo,然後加了一個可選的 --increment-seed
參數,可以解決這兩個問題。使用 --increment-seed
之後批次圖片的後續圖片都只是直接對 seed 數字 +1 而已,使得重繪單一圖片的難度大幅降低。並且加入這個參數之後,圖片也是畫一張就出一張,這樣方便我邊看輸出邊調整,若跑到一半發現不對勁,就直接停掉程式,不必等到全部跑完幾百張才發現「啊靠,prompt 下錯了」。實測這個參數對整體速度沒有顯著影響,不會拖累繪圖速度。
如果想要用 --increment-seed
可以 clone 我的 repo:hirakujira/ml-stable-diffusion at dev (github.com),用 dev branch 就有這個功能了。
關閉 Safety Checker 來加速
在 Apple 提供的程式碼裡面,預設是開啟 Safety Checker 的,這個東西可以防止使用者產生一些 NSFW 的內容。請注意,關閉 Safety Checker 僅是為了加速圖片繪製使用,請勿濫用這個功能。
Python 版本,請修改 pipeline.py
,在 463 行左右新增下面最下面兩行內容:
457 458 459 460 461 462 463 464 465 466 467 |
def main(args): logger.info(f"Setting random seed to {args.seed}") np.random.seed(args.seed) logger.info("Initializing PyTorch pipe for reference configuration") from diffusers import StableDiffusionPipeline pytorch_pipe = StableDiffusionPipeline.from_pretrained(args.model_version, use_auth_token=True) pytorch_pipe.requires_safety_checker = False pytorch_pipe.safety_checker = None ... |
Swift 版本啥都不用改,直接用 --disable-safety
指令就好了。
實測 Python 版本會快 4 秒鐘左右,Swift 加速比較不明顯,只快了 1 秒左右,但批次跑起來還是差蠻多的。關掉 Safety Checker 之後,可以刪除 safety_checker.mlpackage 或者 SafetyChecker.mlmodelc 檔案,節省空間。
部分 model 內建 safety checker,例如 Stable Diffusion v2 會忽略 NSFW 的 prompt,所以這招對它沒用。
隨機種子
用 --seed ${RANDOM}
指令會使用隨機種子,不必每次都在那邊調種子數字。如果嫌 --seed ${RANDOM}
給的數字範圍太小,可以用 --seed $(od -N 4 -t uL -An /dev/urandom | tr -d " ")
不同運算單元畫出的成果不同
即使使用同一個 seed 跟 prompt,用 GPU 跟 ANE 繪圖出來的結果會構圖接近,但是不同。
舉個例子,左邊用 CPU 跟 ANE,右邊用 CPU 跟 GPU:
最後
不用我說,你也知道,封面圖就是用 AI 畫出來的。
參考文章:Using Stable Diffusion with Core ML on Apple Silicon (huggingface.co)
發佈留言