方案概覽
ComfyUI結合多樣的自定義節點,能夠批量生成圖像,一鍵加載大量工作流,可以輕松實現人像生成、背景替換、風格遷移和圖像動畫化等功能。
當您想要獨立部署和使用ComfyUI,您可能面臨顯卡成本高、購買難、并發處理能力有限以及使用門檻高等問題。函數計算推出了ComfyUI API Serverless版解決方案,幫助您通過函數計算快速部署基于ComfyUI模型的AI服務。使用本方案可以充分利用ComfyUI和Serverless技術優勢,為廣大開發者AI繪畫創業及變現提供思路。
為了便于用戶直觀體驗ComfyUI Serverless API解決方案的效果,函數計算基于此方案搭建一個【少年白馬專屬】破次元壁合照 AI 繪畫平臺應用,供用戶快速體驗生成AI繪畫。
本方案的技術架構包括以下基礎設施和云服務。
函數計算:用于提供ComfyUI模型的應用服務,提供GPU和CPU算力。
部署應用
登錄函數計算3.0控制臺,在左側導航欄,單擊應用。
當左上角顯示函數計算 FC 3.0時,表示當前控制臺為3.0控制臺。
重要ComfyUI模型的應用只在函數計算 FC 3.0支持,如果您登錄的是函數計算 2.0的控制臺,請點擊右上角的體驗函數計算 3.0進行切換。
在應用頁面,點擊創建應用,選擇通過模板創建應用,在人工智能頁簽找到【少年白馬專屬】破次元壁合照 AI 繪畫平臺,光標移至該卡片,然后單擊立即創建。
在創建應用頁面,設置以下配置項,然后單擊創建應用。
重點配置項說明如下,如果您沒有特殊要求,其余配置項保持默認值即可。
配置項名稱
說明
示例值
角色名
創建應用所需的權限。首次創建應用的用戶,需要單擊前往授權配置角色權限。
AliyunFCServerlessDevsRole
地域
地域選擇可以選擇距離自己較近的區域,目前支持華東1(杭州)、華東2(上海)、日本(東京)。
由于當前模板涉及GitHub以及HuggingFace等網站的訪問,國內部分地域可能無法直接使用。
日本(東京)
命名空間
設置命名空間名稱。如果您第一次創建ComfyUI應用,使用默認值即可;如果非第一次創建ComfyUI應用,建議手動設置命名空間名稱,便于和其他應用區分。
ComfyUI-api
在彈出的對話框,仔細閱讀應用創建提醒信息,勾選涉及的計費項和我已經了解上面的內容,并同意上述描述,然后單擊同意并繼續部署。
通過API接口調用ComfyUI解決方案
常規的ComfyUI出圖的流程調用/prompt
接口。在并發請求數比較大的情況下,我們往往期望可以利用Serverless的彈性優勢,動態創建多個函數實例處理出圖任務。但由于ComfyUI本身是“有狀態”的,難以確保出圖的請求和獲取狀態的請求固定打到同一個實例上,為了讓ComfyUI更加適配Serverless模式,請參考fc-comfyui/src/images/agent的代碼,在ComfyUI鏡像里內置Agent程序,負責轉換ComfyUI請求并且拉起ComfyUI,以HTTP同步請求舉例如下圖。
以上提供的Agent代碼作為Serverless方式調用的實踐參考。
功能未經過嚴格測試,請根據實際的業務需要二次開發或調整相關的代碼,并構建ComfyUI鏡像。
目前提供的Agent能力介紹
在應用詳情頁,找到ComfyUI函數,點擊進入。
開啟Agent能力,需要增加USE_AGENT:true環境變量。更多詳見配置環境變量。
說明【少年白馬專屬】破次元壁合照 AI 繪畫平臺應用作為示例,已經默認配置了USE_AGENT:true環境變量,如果您的代碼進行二次開發,并使用ComfyUI鏡像里內置Agent程序,可在環境變量進行配置。
當通過Agent的API調用時,建議您調整實例并發數為:1~5,確保并發請求盡量使用單獨的實例,提高出圖效率。
出圖請求(HTTP 同步)
請求路徑:ComfyUI函數請求地址/api/run
。
Body:json格式的prompt數據。
返回值:最后一次的進度,包含圖片信息。
以ComfyUI函數的公網請求地址為例,通過Postman工具調用接口演示。
獲取ComfyUI函數請求地址。在應用詳情頁,找到ComfyUI函數,點擊進入。
在函數詳情頁,選擇配置頁簽,點擊觸發器。選擇公網訪問地址進行復制。
在Postman工具界面,填寫復制公網地址,并拼接
/api/run
。選擇請求方式為POST,填寫請求參數,請求參數可參考Body.json,然后單擊Send。返回200表示調用成功。說明如果您在測試調用的過程中,遇到"Code": "ResourceThrottled","Message": "Reserve resource exceeded limit"的錯誤信息,可能是當前地域的GPU顯卡資源不足,建議您更換地域進行測試。
在store/progress.go中,上面返回值中參數含義如下。
// key 為 node id 的 map 對象 type TProgress map[string]TProgressNode type TProgressNode struct { Max int `json:"max"` // 進度的最大值 Value int `json:"value"` // 當前進度 Start int64 `json:"start"` // 開始時間 LastUpdated int64 `json:"last_updated"` // 最后一次更新時間 Images []TProgressNodeImage `json:"images"` // 當前節點輸出的圖片信息(路徑) Results []string `json:"results,omitempty"` // 當前節點輸出的圖片 base64 }
出圖請求(HTTP 異步)
請求路徑:ComfyUI函數請求地址/api/run
。
Header:調用/api/run
接口,并且添加HTTP Header,X-Fc-Invocation-Type
:Async
,借助函數計算將請求轉換為異步形式。
Body:json格式的prompt數據。
返回值:最后一次的進度,包含圖片信息。
以ComfyUI函數的公網請求地址為例,通過Postman工具調用接口演示。
獲取ComfyUI函數請求地址。在應用詳情頁,找到ComfyUI函數,點擊進入。
在函數詳情頁,選擇配置頁簽,點擊觸發器。選擇公網訪問地址進行復制。
在Postman工具界面,填寫復制的公網地址,并在地址后拼接
/api/run
。選擇請求方式為POST
,當需求為異步請求時,在Header添加X-Fc-Invocation-Type
:Async
和task-id
:值為唯一標識,其中X-Fc-Invocation-Type
告知函數計算為異步形式調用,task-id
為任意唯一值,記錄當前任務唯一id,方便后續獲取狀態。填寫請求參數,請求參數可參考Body.json,然后單擊Send。返回202表示成功。
出圖請求(WebSocket)
請求路徑:wss://Host/api/run/ws
。
通過應用開始創作出圖,通過瀏覽器檢查功能分析發起的WebSocket出圖請求。
以ComfyUI函數的公網地址為例,將公網地址的HTTPS替換為WSS并在后面追加
api/run/ws
。例如:公網地址為https://example.fcapp.run.run。則出圖請求地址為:wss://example.fcapp.run/api/run/ws。在應用詳情頁,點擊訪問域名。
在ComfyUI界面依次完成STEP 1- 上傳您的照片、STEP 2 - 選擇角色和STEP 3 - 上傳背景圖的步驟,點擊開始創作,通過瀏覽器檢查工具查看
api/run/ws
請求。如下圖,客戶端請求服務器,發送一次JSON格式的prompt信息,其中①為請求參數,服務器返回客戶端中間狀態,②為返回結果。返回值中參數如下。
// key 為 node id 的 map 對象 type TProgress map[string]TProgressNode type TProgressNode struct { Max int `json:"max"` // 進度的最大值 Value int `json:"value"` // 當前進度 Start int64 `json:"start"` // 開始時間 LastUpdated int64 `json:"last_updated"` // 最后一次更新時間 Images []TProgressNodeImage `json:"images"` // 當前節點輸出的圖片信息(路徑) Results []string `json:"results,omitempty"` // 當前節點輸出的圖片 base64 }
獲取狀態
路徑:/api/run/ws?id=
請求參數:id的值等于task-id
的值。task-id的值可見異步請求task-id。
如果不關心出圖進度 ,起另一個線程獲取進度:使用 /api/run
+ /api/status
。
curl http://xxxxx/api/status?id=abcdefg -v
{"":{"max":0,"value":0,"start":0,"last_updated":1722234889,"images":null},"10":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"11":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"12":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"13":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"15":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"21":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"22":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"23":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"24":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"25":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"26":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"28":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"3":{"max":17,"value":17,"start":1722234848,"last_updated":1722234889,"images":null},"31":{"max":1,"value":0,"start":1722234848,"last_updated":1722234848,"images":null},"32":{"max":1,"value":0,"start":1722234848,"last_updated":1722234848,"images":null},"33":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"34":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"4":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"43":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"45":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"46":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"47":{"max":1,"value":0,"start":1722234848,"last_updated":1722234848,"images":null},"48":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"49":{"max":1,"value":1,"start":1722234846,"last_updated":1722234848,"images":null},"5":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"6":{"max":0,"value":0,"start":0,"last_updated":0,"imag* Connection #0 to host photo-b-comfyui-ibiwqxodsh.cn-hangzhou.fcapp.run left intact
es":null},"8":{"max":1,"value":0,"start":1722234889,"last_updated":1722234889,"images":null},"9":{"max":1,"value":0,"start":1722234889,"last_updated":1722234889,"images":[{"filename":"ComfyUI_00004_.png","subfolder":"","type":"output"}]}}
返回結果參數如下所示:
// key 為 node id 的 map 對象
type TProgress map[string]TProgressNode
type TProgressNode struct {
Max int `json:"max"` // 進度的最大值
Value int `json:"value"` // 當前進度
Start int64 `json:"start"` // 開始時間
LastUpdated int64 `json:"last_updated"` // 最后一次更新時間
Images []TProgressNodeImage `json:"images"` // 當前節點輸出的圖片信息(路徑)
Results []string `json:"results,omitempty"` // 當前節點輸出的圖片 base64
}
狀態存儲
在src/images/agent/pkg/store/fs.go的代碼中,我們實現了基于文件系統NAS的狀態存儲,您只需要掛載NAS系統,確保文件可被正常持久化,便可以在多個實例之間共享狀態文件,確保可以正確拿到狀態信息。
更好的做法是,將狀態信息寫入到OTS、MySQL等數據庫中,您可以仿照fs.go實現Stroe接口針對其他數據庫的實現即可。
// Store KV 數據存儲
type Store interface {
// Save 存儲 value 到 key
Save(key string, value string) error
// Load 從 key 加載 value
Load(key string) (string, error)
}
Output節點
目前,agent僅針對SaveImage節點做了特殊處理,提取其中的圖片信息。對于特殊的業務需要,您可能需要更加定制化的工作流處理,可參考src/images/agent/pkg/server /run.go,如
增加更多對于Output的解析。
不解析圖片節點,而是借助其他接口獲取圖片文件。
case "execution_error", "executed":
// 節點執行結束
log.Debugf("%s node %s finished", logPrefix, nodeid)
// 節點已完成時,修改下 Max 和 Value 至少為 1
if currentNodeProgress.Max == 0 && currentNodeProgress.Value == 0 {
currentNodeProgress.Max = 1
currentNodeProgress.Value = 1
}
if promptNode.ClassType == "SaveImage" && msg.Data.Output.Images != nil && len(msg.Data.Output.Images) > 0 {
// 如果是圖片節點,則記錄一下圖片數據
if currentNodeProgress.Images == nil {
currentNodeProgress.Images = make([]store.TProgressNodeImage, 0, len(msg.Data.Output.Images))
}
for _, img := range msg.Data.Output.Images {
currentNodeProgress.Images = append(currentNodeProgress.Images, store.TProgressNodeImage{
Filename: img.Filename,
SubFolder: img.SubFolder,
Type: img.Type,
})
}
}
前端功能集成
與Agent對應,我們也給出了一份前端頁面,可參考devsapp/fc-comfyui-couple-photo代碼,我們針對ComfyUI的prompt做了一些特殊的約定,以適應自定義需要。如果您也希望創建專屬的ComfyUI自定義頁面提供給客戶,可以參考相關的前端代碼。
以【少年白馬專屬】破次元壁合照 AI 繪畫平臺應用為例,提供了預定義的prompt文件。
[
{
"title": "破次元壁合照",
"prompt": {},
"params": [
{
"type": "group",
"title": "STEP 1 - 上傳您的照片",
"children": [
{
"type": "image",
"id": "10",
"key": "image",
"title": "參考圖",
"description": "請上傳您的照片,幫助模型理解您的樣貌。請盡量選擇背景簡單、主體突出的半身照,不要佩戴墨鏡、帽子等可能影響您特征的衣物。"
},
{
"type": "string",
"id": "24",
"key": "text",
"title": "參考形象描述",
"description": "為了確保模型更好地理解您的特點,您可以使用提示詞來加強模型對您的印象(請使用因為描述)。"
}
]
},
{
"type": "image",
"id": "11",
"key": "image",
"title": "STEP 2 - 選擇角色",
"description": "請選擇您希望合照的角色。",
"options": [
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/百里東君.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/司空長風.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/玥瑤.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/葉鼎之.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/易文君.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/南宮春水.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/蕭若風.png"
]
},
{
"type": "image",
"id": "12",
"key": "image",
"title": "STEP 3 - 上傳背景圖",
"description": "請上傳您期望的合影地點的圖片,這將作為背景圖片的參考。"
}
]
},
{
"title": "背景替換",
"prompt": {},
"params": [
{
"type": "image",
"id": "10",
"key": "image",
"title": "STEP 1 - 選擇角色",
"description": "請選擇您希望合照的角色。",
"options": [
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/百里東君.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/司空長風.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/玥瑤.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/葉鼎之.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/易文君.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/南宮春水.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/蕭若風.png"
]
},
{
"type": "image",
"id": "12",
"key": "image",
"title": "STEP 2 - 上傳背景圖",
"description": "請上傳您期望的合影地點的圖片,這將作為背景圖片的參考。"
}
]
}
]
在src/web/src/utils/api.ts代碼中通過參數字段,約定了如何渲染頁面并允許用戶填寫自己的參數。
export type ComfyUIPromptEditPanel = {
type: 'image' | 'select' | 'number' | 'string' | 'group'; // 數據類型
id?: string; // 對應 prompt 中的 node id
key: string; // 要修改的參數
title: string; // 標題
description?: string; // 描述
options?: string[] | string; // 可選項
min?: number; // 最小值
max?: number; // 最大值
step?: number; // 調整步數
hidden?: boolean; // 是否隱藏
children?: ComfyUIPromptEditPanel[]; // group 類型的子節點
};
一些其他約定如果seed字段為 -1,則會被替換為隨機數。
// 處理下 seed 字段
let prompt_with_seed = JSON.parse(JSON.stringify(prompt));
for (const nodeid of Object.keys(prompt_with_seed)) {
if (prompt_with_seed[nodeid]?.inputs?.seed === -1) {
prompt_with_seed[nodeid].inputs.seed = random();
}
}
清理資源
登錄函數計算控制臺,在左側導航欄,單擊應用。
在應用頁面,找到目標應用,單擊右側操作列的刪除應用。
在彈出的對話框,勾選我已確定資源刪除的風險,依舊要刪除上面已選擇的資源,然后單擊刪除應用及所選資源。