
在 AIGC 爆发的一年里,ComfyUI 凭借其高度模块化的节点式工作流,成为了 Stable Diffusion 生态中最受技术团队青睐的工具。然而,绝大多数开发者对它的认知止步于“拖拽节点的 GUI 工具”。
作为后端工程师,我们更关心的是:
本文将基于 ComfyUI 源码(comfy/ 核心目录),带大家深入执行引擎,并给出生产级的集成方案。
当我们在 UI 上连接节点时,ComfyUI 后台构建了一个严格的 DAG。其核心源码位于 comfy/sd.py 和 comfy/model_management.py 中。
执行流程拆解:
Prompt 对象。采样器 依赖 VAE编码器,解码器 依赖 采样器。Checkpoint 加载节点,ComfyUI 会缓存中间结果,避免重复加载模型。comfy/model_management.py 中定义了 VRAM 分配策略。不同于 Diffusers 的静态显存分配,ComfyUI 的核心优势在于 “按需加载与卸载”。
torch.nn.Module 的 forward 钩子,在不修改原始权重的情况下动态注入偏置,极大节省了显存开销。思否的读者最痛恨的就是“必须开浏览器才能用”。我们可以通过 --api 模式将其完全 Headless 化。
python main.py --listen 0.0.0.0 --port 8188 --api --disable-auto-launch--api:开启 RESTful 接口,包括 /queue(任务队列)和 /history(历史记录)。--gpu-only:强制模型常驻显存(适用于 24GB 以上显存的生产环境,减少 IO 开销)。生产环境不建议使用同步 HTTP 长轮询,应利用 WebSocket 监听任务状态。
import json
import websocket
import requests
class ComfyUEClient:
def __init__(self, host="127.0.0.1", port=8188):
self.ws = websocket.WebSocket()
self.ws.connect(f"ws://{host}:{port}/ws")
self.base_url = f"http://{host}:{port}"
def queue_prompt(self, workflow_json):
# 将本地文件路径替换为服务器绝对路径
payload = {"prompt": workflow_json}
resp = requests.post(f"{self.base_url}/prompt", json=payload)
prompt_id = resp.json()['prompt_id']
return self.wait_for_result(prompt_id)
def wait_for_result(self, prompt_id):
while True:
out = self.ws.recv()
if isinstance(out, str):
data = json.loads(out)
if data['type'] == 'executing' and data['data']['prompt_id'] == prompt_id:
if data['data']['node'] is None:
# 执行完毕,拉取历史记录获取图片 URL
history = requests.get(f"{self.base_url}/history/{prompt_id}").json()
return history[prompt_id]['outputs']在工作流中保存的 Checkpoint 路径或 LoRA 路径必须是服务端绝对路径。我们通过重写 ComfyUI/extra_model_paths.yaml 来映射统一路径,而非在前端硬编码。
如果将 ComfyUI 集成到公司内部平台,我们通常需要开发内部节点(例如:调用公司内部的鉴权接口、或者将生成的图片自动上传至内部 OSS)。
custom_nodes/company_integration/
├── __init__.py
└── company_nodes.pyComfyUI 的节点本质是继承 object 的类,包含三个核心属性:INPUT_TYPES、RETURN_TYPES 和 FUNCTION。
import boto3 # 假设内部 OSS 兼容 S3
from PIL import Image
import torch
class UploadToOSS:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE",), # 接收 ComfyUI 的 IMAGE 张量
"bucket": ("STRING", {"default": "internal-bucket"}),
"path": ("STRING", {"default": "/generated/"})
}
}
RETURN_TYPES = ("STRING",) # 返回上传后的 URL
FUNCTION = "upload"
CATEGORY = "internal/utils"
def upload(self, images, bucket, path):
# images 是 [B, H, W, C] 的 torch.Tensor,范围 0~1
for i, img_tensor in enumerate(images):
# 转为 PIL
pil_img = Image.fromarray((img_tensor.cpu().numpy() * 255).astype('uint8'))
# 模拟上传
# s3_client.upload_file(...)
url = f"https://{bucket}.oss.com/{path}/img_{i}.png"
return (url,)注册节点:在 __init__.py 中写入 NODE_CLASS_MAPPINGS = {"UploadToOSS": UploadToOSS}。
(IMAGE,) 元组,其 Shape 为 [Batch, Height, Width, Channel],且值域为 0.0 ~ 1.0(float32)。千万不要误以为是 [C, H, W] 的 PyTorch 标准格式。0.0 ~ 1.0,且为 3D 张量 [B, H, W],若无 mask,ComfyUI 会填充全 1。ComfyUI 原生是单进程单队列。若直接起多个线程请求 /prompt,显存极易爆炸。
架构升级:异步任务队列 + 模型预热。
asyncio 和 Celery 将 ComfyUI 封装为 Worker。load_checkpoint(该操作非常耗时且容易触发显存碎片)。参数 | 作用 | 推荐值 |
|---|---|---|
--preview-method auto | 生成中间预览图(会消耗额外显存) | 生产环境建议 none |
--disable-xformers | 禁用 xformers(回退到 SDPA) | 若 CUDA 版本 > 11.8,SDPA 速度更快 |
--max-queue-size | 限制队列长度 | 设为 20,防止内存队列堆积导致 OOM |
最终,我们将 ComfyUI 嵌入到了内部工单系统:
prompt 文本字段。核心回调拼接逻辑:
def inject_prompt(workflow_template, user_prompt):
# 找到工作流中的 CLIP Text Encode 节点 (通常 class_type 为 CLIPTextEncode)
for node_id, node_data in workflow_template.items():
if node_data['class_type'] == 'CLIPTextEncode':
workflow_template[node_id]['inputs']['text'] = user_prompt
return workflow_templateComfyUI 的本质是一个专门针对扩散模型的图执行引擎。将其视为“画图玩具”是对它最大的误解。
通过本次源码剖析和集成实践,我们的文生图服务并发能力提升了 3 倍,且成功接入了公司内部的监控体系(Prometheus + Grafana)。未来,随着 SD3 和 Flux 等更大尺寸模型的普及,ComfyUI 这种“轻量化加载”的架构优势将愈发明显。
讨论点:大家在将 Stable Diffusion 落地到业务中时,是选择 Diffusers 原生库,还是基于 ComfyUI 做二次封装?欢迎探讨各自在显存优化上的奇技淫巧。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。