01
背景介紹
Stable Diffusion 作為當下最流行的開源 AI 圖像生成模型在游戲行業有著廣泛的應用實踐,無論是 ToC 面向玩家的游戲社區場景,還是 ToB 面向游戲工作室的美術制作場景,都可以發揮很大的價值,如何更好地使用 Stable Diffusion 也成了非常熱門的話題,社區也貢獻了多種 runtime 來實現 Stable Diffusion 的圖像生成,其中廣泛流行的包括:stable-diffusion-webui,ComfyUI,Fooocus 等。同時,如何在企業內部部署運維和迭代 Stable Diffusion 圖像生成平臺也涌現了多種方案。本文將以 ComfyUI 為例,介紹如何在亞馬遜云科技上部署面向美術團隊的 Stable Diffusion 圖像生成平臺。
02
ComfyUI 簡介
ComfyUI 是一個開源的基于節點式工作流的 Stable Diffusion 方案,它將 Stable Diffsuion 模型推理時各個流程拆分成不同的節點,讓用戶可以更加清晰地了解 Stable Diffusion 的原理,并且可以更加精細化地控制整個流程。總體來看,ComfyUI 的學習曲線雖然比較陡,但是相較于其他的 Stable Diffusion runtime 有以下的優勢:
在 SDXL 模型推理上相較于其他 UI 有很大的性能優化,圖片生成速度相較于 webui 有 10%~25% 的提升。
高度自定義,可以讓用戶更加精準和細粒度控制整個圖片生成過程,深度用戶可以通過 ComfyUI 更簡單地生成更好的圖片。
workflow 以 json 或者圖片的形式更易于分享傳播,可以更好地提高效率。
開發者友好,workflow 的 API 調用可以通過簡單加載相同的 API 格式 json 文件,以任何語言來調用生成圖片。
基于 ComfyUI 以上的各種優勢,使得它越來越多地被美術創作者所使用。
03
方案特點
我們根據實際的使用場景設計方案,總結有以下特點:
IaC 方式部署,極簡運維,使用 Amazon Cloud Development Kit (Amazon CDK)?和 Amazon EKS Bluprints 來管理 Amazon Elastic Kubernetes Service (Amazon EKS)?集群以承載運行 ComfyUI。
基于 Karpenter 的能力動態伸縮,自定義節點伸縮策略以適應業務需求。
通過 Amazon Spot instances 實例節省 GPU 實例成本。
充分利用 GPU 實例的 instance store,最大化模型加載和切換的性能,同時最小化模型存儲和傳輸的成本。
利用 S3 CSI driver 將生成的圖片直接寫入 Amazon S3,降低存儲成本。
利用 Amazon CloudFront 邊緣節點加速動態請求,以滿足跨地區美術工作室共用平臺的場景(Optional)。
通過 Serverless 事件觸發的方式,當模型上傳 S3 或在 S3 刪除時,觸發工作節點同步模型目錄數據。
04
方案架構
分為兩個部分介紹方案架構:
方案部署過程
ComfyUI 的模型存放在 S3 for models,目錄結構和原生的?ComfyUI/models?目錄結構一致。
EKS 集群的 GPU node 在拉起初始化時,會格式化本地的 Instance store,并通過 user-data 從 S3 將模型同步到本地 Instance store。
EKS 運行 ComfyUI 的 pod 會將 node 上的 Instance store 目錄映射到 pod 里的 models 目錄,以實現模型的讀取加載。
當有模型上傳到 S3 或從 S3 刪除時,會觸發 Lambda 對所有 GPU node 通過 SSM 執行命令再次同步 S3 上的模型到本地 Instance store。
EKS 運行 ComfyUI 的 pod 會通過 PVC 的方式將?ComfyUI/output?目錄映射到 S3 for outputs。
用戶使用過程
當用戶請求通過 CloudFront –> ALB 到達 EKS pod 時,pod 會首先從 Instance store 加載模型。
pod 推理完成后會將圖片存放在?ComfyUI/output?目錄,通過 S3 CSI driver 直接寫入 S3。
得益于 Instance store 的性能優勢,用戶在第一次加載模型以及切換模型時的時間相較于其他存儲方案會大大縮短。
此方案已開源,可以通過以下地址獲取部署和測試代碼。具體部署指引請參考第六節。
https://github.com/aws-samples/comfyui-on-eks
05
圖片生成效果
部署完成后可以通過瀏覽器直接訪問 CloudFront 的域名或 Kubernetes Ingress 的域名來使用 ComfyUI 的前端。
也可以通過將 ComfyUI 的 workflow 保存為可供 API 調用的 json 文件,以 API 的方式來調用,可以更好地與企業內的平臺和系統進行結合。參考調用代碼?comfyui-on-eks/test/invoke_comfyui_api.py
06
方案部署指引
6.1 準備工作
此方案默認你已安裝部署好并熟練使用以下工具:
Amazon CLI:latest version
eksctl
helm
kubectl
Docker
npm
CDK:latest version
下載部署代碼,切換分支,安裝?npm packages?并檢查環境
git clone https://github.com/aws-samples/comfyui-on-eks ~/comfyui-on-eks
cd ~/comfyui-on-eks && git checkout Blog1
npm install
npm list
cdk list
運行?npm list?確認已安裝下面的 packages
comfyui-on-eks@0.1.0 ~/comfyui-on-eks
├── @aws-quickstart/eks-blueprints@1.13.1
├── aws-cdk-lib@2.115.0
├── aws-cdk@2.99.1
└── ...
運行?cdk list?確認環境已準備完成,有以下 CloudFormation 可以部署
Comfyui-Cluster
CloudFrontEntry
LambdaModelsSync
S3OutputsStorage
ComfyuiEcrRepo
6.2 部署 EKS 集群
執行以下命令:
cd ~/comfyui-on-eks && cdk deploy Comfyui-Cluster
此時會在 CloudFormation 創建一個名為?Comfyui-Cluster?的 Stack 來部署 EKS Cluster 所需的所有資源,執行時間約 20-30min。
Comfyui-Cluster Stack?的資源定義可以參考?comfyui-on-eks/lib/comfyui-on-eks-stack.ts,需要關注以下幾點:
1、EKS 集群是通過 EKS Blueprints 框架來構建,blueprints.EksBlueprint.builder()
2、通過 EKS Blueprints 的 Addon 給 EKS 集群安裝了以下插件:
AwsLoadBalancerControllerAddOn:用于管理 Kubernetes 的 ingress ALB
SSMAgentAddOn:用于在 EKS node 上使用 SSM,遠程登錄或執行命令
Karpenter:用于對 EKS node 進行擴縮容
GpuOperatorAddon:支持 GPU node 運行
3、給 EKS 的 node 增加了 S3 的權限,以實現將 S3 上的模型文件同步到本地 instance store
4、沒有定義 GPU 實例的 nodegroup,而是只定義了輕量級應用的 cpu 實例 nodegroup 用于運行 Addon 的 pods,GPU 實例的擴縮容完全交由 Karpenter 實現
部署完成后,CDK 的 outputs 會顯示一條 ConfigCommand,用來更新配置以 kubectl 來訪問 EKS 集群。
執行上面的 ConfigCommand 命令以授權 kubectl 訪問 EKS 集群
執行以下命令驗證 kubectl 已獲授權訪問 EKS 集群
kubectl get svc
至此,EKS 集群已完成部署。
同時請注意,EKS Blueprints 輸出了 KarpenterInstanceNodeRole,它是 Karpenter 管理的 Node 的 role,請記下這個 role 接下來將在 6.5.2 節進行配置。
6.3 部署存儲模型的 S3 bucket 以及 Lambda 動態同步模型
執行以下命令:
cd ~/comfyui-on-eks && cdk deploy LambdaModelsSync
LambdaModelsSync?的 stack 主要創建以下資源:
S3 bucket:命名規則為?comfyui-models-{account_id}-{region},用來存儲 ComfyUI 使用到的模型
Lambda 以及對應的 role 和 event source:Lambda function 名為?comfy-models-sync,用來在模型上傳到 S3 或從 S3 刪除時觸發 GPU 實例同步 S3 bucket 內的模型到本地
LambdaModelsSync?的資源定義可以參考?comfyui-on-eks/lib/lambda-models-sync.ts,需要關注以下幾點:
Lambda 的代碼在目錄?comfyui-on-eks/lib/ComfyModelsSyncLambda/model_sync.py
lambda 的作用是通過 tag 過濾所有 ComfyUI EKS Cluster 里的 GPU 實例,當存放模型的 S3 發生 create 或 remove 事件時,通過 SSM 的方式讓所有 GPU 實例同步 S3 上的模型到本地目錄(instance store)
S3 for Models 和 Lambda 部署完成后,此時 S3 還是空的,執行以下命令用來初始化 S3 bucket 并下載 SDXL 模型準備測試。
注意:以下命令會將 SDXL 模型下載到本地并上傳到 S3,需要有充足的磁盤空間(20G),你也可以通過自己的方式將模型上傳到 S3 對應的目錄。
region="us-west-2" # 修改 region 為你當前的 region
cd ~/comfyui-on-eks/test/ && bash init_s3_for_models.sh $region
無需等待模型下載上傳 S3 完成,可繼續以下步驟,只需要在 GPU node 拉起前確認模型上傳 S3 完成即可。
6.4 部署存儲 ComfyUI 生成圖片的 S3 bucket
執行以下命令:
cd ~/comfyui-on-eks && cdk deploy S3OutputsStorage
S3OutputsStorage?的 stack 只創建一個 S3 bucket,命名規則為?comfyui-outputs-{account_id}-{region},用于存儲 ComfyUI 生成的圖片。
6.5 部署 ComfyUI Workload
ComfyUI 的 Workload 部署用 Kubernetes 來實現,請按以下順序來依次部署。
6.5.1 構建并上傳 ComfyUI Docker 鏡像
執行以下命令,創建 ECR repo 來存放 ComfyUI 鏡像
cd ~/comfyui-on-eks && cdk deploy ComfyuiEcrRepo
在準備階段部署好 Docker 的機器上運行?build_and_push.sh?腳本
region="us-west-2" # 修改 region 為你當前的 region
cd comfyui-on-eks/comfyui_image/ && bash build_and_push.sh $region
ComfyUI 的 Docker 鏡像請參考?comfyui-on-eks/comfyui_image/Dockerfile,需要注意以下幾點:
在 Dockerfile 中通過 git clone & git checkout 的方式來固定 ComfyUI 的版本,可以根據業務需求修改為不同的 ComfyUI 版本。
Dockerfile 中沒有安裝 customer node 等插件,可以使用 RUN 來按需添加。
此方案每次的 ComfyUI 版本迭代都只需要通過重新 build 鏡像,更換鏡像來實現。
構建完鏡像后,執行以下命令確保鏡像的 Architecture 是 X86 架構,因為此方案使用的 GPU 實例均是基于 X86 的機型。
region="us-west-2" # 修改 region 為你當前的 region
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
image_name=${ACCOUNT_ID}.dkr.ecr.${region}.amazonaws.com/comfyui-images:latest
docker image inspect $image_name|grep Architecture
6.5.2?部署?Karpenter?用以管理?GPU?實例的擴縮容
執行以下命令來部署 Karpenter 的 Provisioner
kubectl apply -f comfyui-on-eks/manifests/Karpenter/karpenter_provisioner.yaml
執行以下命令來驗證 Karpenter 的部署結果
kubectl describe karpenter
Karpenter 的部署需要注意以下幾點:
1、使用了 g5.2xlarge 和 g4dn.2xlarge 機型,同時使用了?on-demand?和?spot?實例。
2、在 userData 中對 karpenter 拉起的 GPU 實例做以下初始化操作:
格式化 instance store 本地盤,并 mount 到?/comfyui-models?目錄。
將存儲在 S3 上的模型文件同步到本地 instance store。
在 6.2 節獲取到的 KarpenterInstanceNodeRole 需要添加一條 S3 的訪問權限,以允許 GPU node 從 S3 同步文件,請執行以下命令:
KarpenterInstanceNodeRole="Comfyui-Cluster-ComfyuiClusterkarpenternoderoleE627-juyEInBqoNtU" # 修改為你自己的 role
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess --role-name $KarpenterInstanceNodeRole
6.5.3 部署 S3 PV 和 PVC 用以存儲生成的圖片
執行以下命令來部署 S3 CSI 的 PV 和 PVC
Run on Linux
region="us-west-2" # 修改 region 為你當前的 region
account=$(aws sts get-caller-identity --query Account --output text)
sed -i "s/region .*/region $region/g" comfyui-on-eks/manifests/PersistentVolume/sd-outputs-s3.yaml
sed -i "s/bucketName: .*/bucketName: comfyui-outputs-$account-$region/g" comfyui-on-eks/manifests/PersistentVolume/sd-outputs-s3.yaml
kubectl apply -f comfyui-on-eks/manifests/PersistentVolume/sd-outputs-s3.yaml
Run on MacOS
region="us-west-2" # 修改 region 為你當前的 region
account=$(aws sts get-caller-identity --query Account --output text)
sed -i ’’ "s/region .*/region $region/g" comfyui-on-eks/manifests/PersistentVolume/sd-outputs-s3.yaml
sed -i ’’ "s/bucketName: .*/bucketName: comfyui-outputs-$account-$region/g" comfyui-on-eks/manifests/PersistentVolume/sd-outputs-s3.yaml
kubectl apply -f comfyui-on-eks/manifests/PersistentVolume/sd-outputs-s3.yaml
6.5.4 部署 EKS S3 CSI Driver
執行以下命令,創建 S3 CSI driver 的 role 和 service account,以允許 S3 CSI driver 對 S3 進行讀寫。
REGION="us-west-2" # 修改 region 為你當前的 region
account=$(aws sts get-caller-identity --query Account --output text)
ROLE_NAME=EKS-S3-CSI-DriverRole-$account-$region
POLICY_ARN=arn:aws:iam::aws:policy/AmazonS3FullAccess
eksctl create iamserviceaccount \--name s3-csi-driver-sa \--namespace kube-system \--cluster Comfyui-Cluster \--attach-policy-arn $POLICY_ARN \--approve \--role-name $ROLE_NAME \--region $REGION
確保執行上述命令的 Identity 在 EKS 集群的?aws-auth?configmap 里
identity=$(aws sts get-caller-identity --query 'Arn' --output text)
kubectl describe configmap aws-auth -n kube-system|grep $identity
執行以下命令,安裝?aws-mountpoint-s3-csi-driver?Addon
helm repo add aws-mountpoint-s3-csi-driver https://awslabs.github.io/mountpoint-s3-csi-driver
helm repo update
helm upgrade --install aws-mountpoint-s3-csi-driver \--namespace kube-system \aws-mountpoint-s3-csi-driver/aws-mountpoint-s3-csi-driver
6.5.5 部署 ComfyUI Deployment 和 Service
執行以下命令來替換容器 image 鏡像
Run on Linux
region="us-west-2" # 修改 region 為你當前的 region
account=$(aws sts get-caller-identity --query Account --output text)
sed -i "s/image: .*/image: ${account}.dkr.ecr.${region}.amazonaws.com\/comfyui-images:latest/g" comfyui-on-eks/manifests/ComfyUI/comfyui_deployment.yaml
Run on MacOS
region="us-west-2" # 修改 region 為你當前的 region
account=$(aws sts get-caller-identity --query Account --output text)
sed -i ’’ ?"s/image: .*/image: ${account}.dkr.ecr.${region}.amazonaws.com\/comfyui-images:latest/g" comfyui-on-eks/manifests/ComfyUI/comfyui_deployment.yaml
執行以下命令來部署 ComfyUI 的 Deployment 和 Service
kubectl apply -f comfyui-on-eks/manifests/ComfyUI
ComfyUI 的 deployment 和 service 部署注意以下幾點:
1、ComfyUI 的 pod 擴展時間和實例類型有關,如果實例不足需要 Karpenter 拉起 node 進行初始化,同步鏡像后才可以被 pod 調度。可以通過以下命令分別查看 Kubernetes 事件以及 Karpenter 日志
podName=$(kubectl get pods -n karpenter|tail -1|awk '{print $1}')
kubectl logs -f $podName -n karpenter
kubect get events --watch
2、不同的 GPU 實例有不同的 Instance Store 大小,如果 S3 存儲的模型總大小超過了 Instance Store 的大小,則需要使用 EFS 或其他方式方式來管理模型存儲
當 comfyui 的 pod running 時,執行以下命令查看 pod 日志
podName=$(kubectl get pods |tail -1|awk '{print $1}')
kubectl logs -f $podName
6.6 測試 ComfyUI on EKS 部署結果
6.6.1 API 測試
使用 API 的方式來測試,在?comfyui-on-eks/test?目錄下執行以下命令
Run on Linux
ingress_address=$(kubectl get ingress|grep comfyui-ingress|awk '{print $4}')
sed -i "s/SERVER_ADDRESS = .*/SERVER_ADDRESS = \"${ingress_address}\"/g" invoke_comfyui_api.py
sed -i "s/HTTPS = .*/HTTPS = False/g" invoke_comfyui_api.py
sed -i "s/SHOW_IMAGES = .*/SHOW_IMAGES = False/g" invoke_comfyui_api.py
./invoke_comfyui_api.py
Run on MacOS
ingress_address=$(kubectl get ingress|grep comfyui-ingress|awk '{print $4}')
sed -i ?’’ "s/SERVER_ADDRESS = .*/SERVER_ADDRESS = \"${ingress_address}\"/g" invoke_comfyui_api.py
sed -i ’’ "s/HTTPS = .*/HTTPS = False/g" invoke_comfyui_api.py
sed -i ’’ "s/SHOW_IMAGES = .*/SHOW_IMAGES = False/g" invoke_comfyui_api.py
./invoke_comfyui_api.py
API 調用邏輯參考?comfyui-on-eks/test/invoke_comfyui_api.py,注意以下幾點:
API 調用執行 ComfyUI 的 workflow 存儲在comfyui-on-eks/test/sdxl_refiner_prompt_api.json
使用到了兩個模型:sd_xl_base_1.0.safetensors,sd_xl_refiner_1.0.safetensors
可以在 sdxl_refiner_prompt_api.json 里或 invoke_comfyui_api.py 修改 prompt 進行測試
6.6.2 瀏覽器測試
執行以下命令獲取 ingress 地址
kubectl get ingress
通過瀏覽器直接訪問 ingress 地址。
至此 ComfyUI on EKS 部分已部署測試完成。接下來我們將對 EKS 集群接入 CloudFront 進行邊緣加速。
6.6 部署 CloudFront 邊緣加速(可選)
在?comfyui-on-eks?目錄下執行以下命令,為 Kubernetes 的 ingress 接入 CloudFront 邊緣加速
cdk deploy CloudFrontEntry
CloudFrontEntry?的 stack 可以參考?comfyui-on-eks/lib/cloudfront-entry.ts,需要關注以下幾點:
在代碼中根據 tag 找到了 EKS Ingress 的 ALB
以 EKS Ingress ALB 作為 CloudFront Distribution 的 origin
ComfyUI 的 ALB 入口只配置了 HTTP,所以 CloudFront Origin Protocol Policy 設置為 HTTP_ONLY
加速動態請求,cache policy 設置為 CACHING_DISABLED
部署完成后會打出 Outputs,其中包含了 CloudFront 的 URL?CloudFrontEntry.cloudFrontEntryUrl,參考 6.6 節通過 API 或瀏覽器的方式進行測試。
07
清理資源
執行以下命令刪除所有 Kubernetes 資源
kubectl delete -f comfyui-on-eks/manifests/ComfyUI/
kubectl delete -f comfyui-on-eks/manifests/PersistentVolume/
kubectl delete -f comfyui-on-eks/manifests/Karpenter/
刪除上述部署的資源
cdk destroy ComfyuiEcrRepo
cdk destroy CloudFrontEntry
cdk destroy S3OutputsStorage
cdk destroy LambdaModelsSync
cdk destroy Comfyui-Cluster
總結
本文介紹了一種在 Amazon EKS 上部署 ComfyUI 的方案,通過 Instance store 和 S3 的結合,在降低存儲成本的同時最大化模型加載和切換的性能,同時通過 Serverless 的方式自動化進行模型的同步,使用 spot 實例降低 GPU 實例成本,并且通過 CloudFront 進行全球加速,以滿足跨地區美術工作室協作的場景。整套方案以 IaC 的方式管理底層基礎設施,最小化運維成本。
本篇作者
王睿
亞馬遜云科技高級解決方案架構師,曾就職于網易游戲和騰訊,從事過 SRE 以及 Game SDE,在游戲和云計算行業有豐富的實踐經驗。
星標不迷路,開發更極速!
關注后記得星標「亞馬遜云開發者」
聽說,點完下面4個按鈕
就不會碰到bug了!