AI视频生成-ComfyUI部署
ComfyUI提供基于Stable Diffusion的节点式操作界面,可方便快速地构建复杂的AIGC流程,完成短视频内容生成、动画制作等任务。本文介绍如何在EAS中部署和使用ComfyUI。
部署版本说明
适用场景 | 调用方式 | |
标准版 |
|
|
API版 |
| API调用(异步) |
集群版WebUI |
| WebUI |
Serverless版 |
| WebUI |
此处API调用的同步与异步取决于是否使用EAS的队列服务:
同步调用:直接请求推理实例,不使用EAS的队列服务;
异步调用:使用EAS的队列服务,向输入队列发送请求,以订阅的方式获得结果推送。
由于ComfyUI本身具有异步队列系统,即使发起同步调用,实质上也是异步进行的。用户发送请求后,系统会返回一个Prompt ID,然后需要使用Prompt ID轮询以获取推理结果。
计费说明
Serverless版:部署免费,按实际推理时长计费。
其他版本:按照部署资源和运行时长计费(部署成功后即使不使用也计费)。
更多计费详情请参见模型在线服务(EAS)计费说明。
部署服务
Serverless版只能使用场景化模型部署,标准版、集群版、API版可使用场景化模型部署(操作简单)或者自定义模型部署(支持更多功能)。
ComfyUI仅支持单卡(单机单卡或多机单卡),不支持多卡并发操作。因为一个EAS实例只有一个ComfyUI进程,部署时选择多GPU规格的实例(如2*A10)也只会使用一张GPU卡,并不会让单个生图任务变快。
负载均衡:需部署API版,通过异步队列实现。
方式一:场景化模型部署(推荐)
登录PAI控制台,在页面上方选择目标地域,并在右侧选择目标工作空间,然后单击进入EAS。
在模型在线服务(EAS)页面,单击部署服务,在场景化模型部署区域,单击AI视频生成-ComfyUI部署。
在AI视频生成-ComfyUI部署页面,配置以下关键参数。
单击部署。等待约5分钟,当服务状态变为运行中,表示部署成功。
方式二:自定义模型部署
登录PAI控制台,在页面上方选择目标地域,并在右侧选择目标工作空间,然后单击进入EAS。
单击部署服务,然后在自定义模型部署区域,单击自定义部署。
在自定义部署页面,配置以下关键参数。更多配置,请参见控制台自定义部署参数说明。
部署方式:选择镜像部署,并选中开启Web应用复选框。
镜像配置:在官方镜像列表中选择comfyui>comfyui:1.9。其中:x.x:表示标准版,x.x-api:表示API版,x.x-cluster:表示集群版。
说明由于版本迭代迅速,部署时镜像版本选择最高版本即可。
更多关于每个版本的使用场景说明,请参见部署版本说明。
模型配置:如需使用自己的模型、安装自定义节点或通过API调用,必须进行模型配置。以对象存储(OSS)为例,选择Bucket和目录,部署成功后系统会自动在其中创建ComfyUI所需目录。请确保创建的存储空间与EAS服务位于同一地域。
Uri:单击
选择已创建的OSS存储目录。例如
oss://bucket-test/data-oss/
。挂载路径:配置为
/code/data-oss
,表示将您配置的OSS文件目录挂载到镜像的/code/data-oss
路径下。
运行命令:
配置镜像版本后,系统自动配置运行命令
python main.py --listen --port 8000
,端口号为:8000。若进行了模型配置,需要在运行命令中增加
--data-dir
挂载目录,其中挂载目录需要与模型配置中的挂载路径一致。例如python main.py --listen --port 8000 --data-dir /code/data-oss
。
资源类型:选择公共资源。
部署资源:资源规格必须选择GPU类型,推荐使用ml.gu7i.c16m60.1-gu30(性价比最高)。如库存不足可选择ecs.gn6i-c16g1.4xlarge。
单击部署。服务部署时间约为5分钟,当服务状态为运行中时,表明服务已成功部署。
调用服务
WebUI使用
标准版、集群版和Serverless版支持通过WebUI使用。
单击目标服务调用/日志/监控列的按钮,进入WebUI页面。如页面长时间无法打开,请参见刷新页面时间过长或页面卡死。
1. 使用模板工作流
WebUI页面支持自定义工作流配置,同时预置多种模板,可在工作流 > 浏览模板页面中选择。本文以视频工作流模板Wan VACE Text to Video为例。
对于Serverless版本,模板中无该工作流,可选择其他模板使用。也可以通过工作流 > 打开,加载本地文件系统中的工作流。
工作流加载成功后,如遇到报错缺少模型,可忽视(建议勾选不再显示此消息)。
由于路径变更,直接运行工作流可能会出现以下报错。
请先在Load models here区域重新选择模型wan2.1_vace_14B_fp16.safetensors
与Wan21_CausVid_14B_T2V_lora_rank32.safetensors
。
工作流运行成功后,会在Save Video区域,展示生成的视频。
2. 使用第三方模型和安装自定义节点(ComfyUI插件)
确认部署的ComfyUI版本非Serverless版本。Serverless版本只能使用内置的模型和节点。
确认服务已配置存储挂载。如使用自定义部署,需在运行命令中增加参数
--data-dir
挂载目录,详情见方式二:自定义模型部署。服务部署成功后,系统会自动在已挂载的OSS或NAS存储空间中创建以下目录结构。
其中:
custom_nodes:该目录用来存储节点文件。
models:该目录用来存放模型文件。
上传模型或节点文件。以OSS为例,可控制台上传文件到OSS。对于大文件,请参见如何上传大文件到OSS?。
模型文件上传:根据模型使用节点的源项目库使用说明,将模型上传至
models
下的队员子目录。例如:对于Checkpoint加载器,模型应上传至
models/checkpoints
。对于风格模型加载器,模型应上传至
models/styles
。
节点文件上传:推荐您将自定义节点上传至挂载存储的
custom_nodes
目录。
加载新模型或重启进程。
在挂载的存储空间上传模型之后,单击PaiCustom > 加载新模型,如仍然找不到模型,单击重启进程,重启成功后,刷新浏览器页面。
上传节点文件之后,直接单击重启进程。重启成功后,刷新浏览器页面。
3. 导出工作流
在工作流调试完成之后,单击工作流 > 导出(API),可以将工作流保存为一个JSON文件。后续可用于API调用。
API调用
标准版服务仅支持同步调用,并且提供在线调试。
API版服务仅支持异步调用,且仅支持api_prompt路径。
ComfyUI的API请求体取决于工作流配置。您需要先在WebUI页面设置并导出工作流的JSON文件。
同步调用:请求体需要将工作流JSON文件内容包装在"prompt"键值下面。
异步调用:请求体就是工作流JSON文件内容。
因为上述Wan VACE Text to Video的工作流运行比较耗时,为方便测试提供以下工作流(运行一次需要约3分钟)。
单击查看测试工作流的请求体示例
同步调用
{
"prompt": {
"3": {
"inputs": {
"seed": 423988542100860,
"steps": 40,
"cfg": 7,
"sampler_name": "dpmpp_sde_gpu",
"scheduler": "karras",
"denoise": 1,
"model": [
"4",
0
],
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"latent_image": [
"5",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"4": {
"inputs": {
"ckpt_name": "LandscapeBING_v10.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Checkpoint加载器(简易)"
}
},
"5": {
"inputs": {
"width": 720,
"height": 1280,
"batch_size": 1
},
"class_type": "EmptyLatentImage",
"_meta": {
"title": "空Latent"
}
},
"6": {
"inputs": {
"text": "Rocket takes off from the ground, fire,sky, airplane",
"speak_and_recognation": {
"__value__": [
false,
true
]
},
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"7": {
"inputs": {
"text": "",
"speak_and_recognation": {
"__value__": [
false,
true
]
},
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"13": {
"inputs": {
"seed": 788620942678235,
"steps": 40,
"cfg": 2.5,
"sampler_name": "euler_ancestral",
"scheduler": "karras",
"denoise": 1,
"model": [
"17",
0
],
"positive": [
"16",
0
],
"negative": [
"16",
1
],
"latent_image": [
"16",
2
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"14": {
"inputs": {
"samples": [
"13",
0
],
"vae": [
"18",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"15": {
"inputs": {
"filename_prefix": "ComfyUI",
"fps": 10.000000000000002,
"lossless": false,
"quality": 85,
"method": "default",
"images": [
"14",
0
]
},
"class_type": "SaveAnimatedWEBP",
"_meta": {
"title": "保存WEBP"
}
},
"16": {
"inputs": {
"width": 512,
"height": 768,
"video_frames": 35,
"motion_bucket_id": 140,
"fps": 15,
"augmentation_level": 0.15000000000000002,
"clip_vision": [
"18",
1
],
"init_image": [
"8",
0
],
"vae": [
"18",
2
]
},
"class_type": "SVD_img2vid_Conditioning",
"_meta": {
"title": "SVD_图像到视频_条件"
}
},
"17": {
"inputs": {
"min_cfg": 1,
"model": [
"18",
0
]
},
"class_type": "VideoLinearCFGGuidance",
"_meta": {
"title": "线性CFG引导"
}
},
"18": {
"inputs": {
"ckpt_name": "svd_xt.safetensors"
},
"class_type": "ImageOnlyCheckpointLoader",
"_meta": {
"title": "Checkpoint加载器(仅图像)"
}
},
"19": {
"inputs": {
"frame_rate": 10,
"loop_count": 0,
"filename_prefix": "comfyUI",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"14",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "合并为视频"
}
}
}
}
异步调用
{
"3": {
"inputs": {
"seed": 423988542100860,
"steps": 40,
"cfg": 7,
"sampler_name": "dpmpp_sde_gpu",
"scheduler": "karras",
"denoise": 1,
"model": [
"4",
0
],
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"latent_image": [
"5",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"4": {
"inputs": {
"ckpt_name": "LandscapeBING_v10.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Checkpoint加载器(简易)"
}
},
"5": {
"inputs": {
"width": 720,
"height": 1280,
"batch_size": 1
},
"class_type": "EmptyLatentImage",
"_meta": {
"title": "空Latent"
}
},
"6": {
"inputs": {
"text": "Rocket takes off from the ground, fire,sky, airplane",
"speak_and_recognation": {
"__value__": [
false,
true
]
},
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"7": {
"inputs": {
"text": "",
"speak_and_recognation": {
"__value__": [
false,
true
]
},
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"13": {
"inputs": {
"seed": 788620942678235,
"steps": 40,
"cfg": 2.5,
"sampler_name": "euler_ancestral",
"scheduler": "karras",
"denoise": 1,
"model": [
"17",
0
],
"positive": [
"16",
0
],
"negative": [
"16",
1
],
"latent_image": [
"16",
2
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"14": {
"inputs": {
"samples": [
"13",
0
],
"vae": [
"18",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"15": {
"inputs": {
"filename_prefix": "ComfyUI",
"fps": 10.000000000000002,
"lossless": false,
"quality": 85,
"method": "default",
"images": [
"14",
0
]
},
"class_type": "SaveAnimatedWEBP",
"_meta": {
"title": "保存WEBP"
}
},
"16": {
"inputs": {
"width": 512,
"height": 768,
"video_frames": 35,
"motion_bucket_id": 140,
"fps": 15,
"augmentation_level": 0.15000000000000002,
"clip_vision": [
"18",
1
],
"init_image": [
"8",
0
],
"vae": [
"18",
2
]
},
"class_type": "SVD_img2vid_Conditioning",
"_meta": {
"title": "SVD_图像到视频_条件"
}
},
"17": {
"inputs": {
"min_cfg": 1,
"model": [
"18",
0
]
},
"class_type": "VideoLinearCFGGuidance",
"_meta": {
"title": "线性CFG引导"
}
},
"18": {
"inputs": {
"ckpt_name": "svd_xt.safetensors"
},
"class_type": "ImageOnlyCheckpointLoader",
"_meta": {
"title": "Checkpoint加载器(仅图像)"
}
},
"19": {
"inputs": {
"frame_rate": 10,
"loop_count": 0,
"filename_prefix": "comfyUI",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"14",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "合并为视频"
}
}
}
在线调试
在模型在线服务(EAS)页面,单击目标服务操作列下的在线调试,进入在线调试页面。
发送POST请求,获取Prompt ID。
在调试页面的在线调试请求参数区域的Body处填写已准备好的请求体。并在请求URL文本编辑框中添加
/prompt
。单击发送请求,即可在调试信息区域查看返回结果,示例如下。
发送GET请求,根据Prompt ID获取推理结果。
在在线调试请求参数区域中,将请求方法修改为GET,并在文本框中配置
/history/<prompt id>
,示例如下。其中
<prompt id>
需要替换为步骤1获取的Prompt ID。单击发送请求,即可获取推理结果。
您可以在挂载存储的
output
目录中,查看生成的推理结果。
同步调用
查看调用信息。
在推理服务页签,单击您的服务名称进入概览页面,在基本信息区域单击查看调用信息。
在调用信息面板,可获取访问地址和Token。根据您的实际情况选择公网或VPC地址,下文使用<EAS_ENDPOINT>和<EAS_TOKEN>指代它们。
发送POST请求,获取Prompt ID。
HTTP请求方式:POST。
请求路径(URL):
<EAS_ENDPOINT>/prompt
。其中<EAS_ENDPOINT>
如地址末尾有/
,请删除。最终URL如http://comfyui****.175805416243****.cn-beijing.pai-eas.aliyuncs.com/prompt
。请求头部(Headers):
头部
值
描述
Authorization
<EAS_TOKEN>
授权密钥。
Content-Type
application/json
指定请求体格式。
代码示例:
cURL
curl --location --request POST '<EAS_ENDPOINT>/prompt' \ --header 'Authorization: <EAS_TOKEN>' \ --header 'Content-Type: application/json' \ --data-raw '{ "prompt": ...省略 }'
其中,
--data-raw
为请求体。Python
代码示例如下:
import requests import json # <EAS_ENDPOINT> 和 <EAS_TOKEN> 替换为步骤1获取的地址和token。 service_url = "<EAS_ENDPOINT>" token = "<EAS_TOKEN>" if service_url[-1] == "/": service_url = service_url[:-1] # 请求体,将payload中的prompt的值配置为工作流对应的JSON文件内容。 payload = """{ "prompt": ...省略 }""" payload = json.loads(payload) session = requests.session() session.headers.update({"Authorization": token}) response = session.post(url=f'{service_url}/prompt', json=payload) if response.status_code != 200: raise Exception(response.content) data = response.json() print(data)
返回结果示例如下:
{ "prompt_id": "021ebc5b-e245-4e37-8bd3-00f7b949****", "number": 5, "node_errors": {} }
您可以从返回结果中获取Prompt ID。
发送请求,获取推理结果。
HTTP请求方式:
GET
请求URL:
<EAS_ENDPOINT>/history/<prompt_id>
,其中<prompt_id>
替换为步骤1中获取的prompt_id。请求头部:
头部
值
描述
Authorization
<EAS_TOKEN>
授权密钥,步骤1中获取。
代码示例:
cURL
curl --location --request GET '<EAS_ENDPOINT>/history/<prompt_id>' \ --header 'Authorization: <EAS_TOKEN>'
Python
import requests # <EAS_ENDPOINT> 和 <EAS_TOKEN> 替换为步骤1获取的地址和token。 # <prompt_id>替换为步骤2中获取的prompt_id service_url = "<EAS_ENDPOINT>" token = "<EAS_TOKEN>" prompt_id = "<prompt_id>" if service_url[-1] == "/": service_url = service_url[:-1] session = requests.session() session.headers.update({"Authorization": token}) response = session.get(url=f'{service_url}/history/{prompt_id}') if response.status_code != 200: raise Exception(response.content) data = response.json() print(data)
返回结果示例如下:
单击查看返回结果示例
{ "130bcd6b-5bb5-496c-9c8c-3a1359a0****": { "prompt": ...省略, "outputs": { "9": { "images": [ { "filename": "ComfyUI_1712645398_18dba34d-df87-4735-a577-c63d5506a6a1_.png", "subfolder": "", "type": "output" } ] }, "15": { "images": [ { "filename": "ComfyUI_1712645867_.webp", "subfolder": "", "type": "output" } ], "animated": [ true ] }, "19": { "gifs": [ { "filename": "comfyUI_00002.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4" } ] } }, "status": { "status_str": "success", "completed": true, "messages": ...省略, } } }
在本示例返回的
outputs
中提供了prompt生成的图像、webp文件和mp4视频,您可以在挂载存储的output
目录中,根据文件名称来查找这些文件。
异步调用
异步调用仅支持api_prompt路径,其task_id参数是标识请求和结果的关键标志,请给每个请求分配一个唯一的值,以对应后面的队列结果。请求路径如下:
{service_url}/api_prompt?task_id={需分配唯一值}
查看调用信息。
单击API版服务的服务方式列下的调用信息,在调用信息配置面板的异步调用页签,查看服务访问地址和Token。
下文使用<EAS_ENDPOINT>指代公网输入调用地址(如果调用端与EAS处于同一VPC,可使用VPC输入调用地址),<EAS_TOKEN>指代Token。
推送请求。
代码示例如下:
import requests,io,base64 from PIL import Image, PngImagePlugin import json service_url = "<EAS_ENDPOINT>" token = "<EAS_TOKEN>" if service_url[-1] == "/": service_url = service_url[:-1] session = requests.session() session.headers.update({"Authorization":token}) # 请求体,工作流JSON文件内容。使用 """ """ 将其定义为多行字符串,否则需要将工作流JSON中布尔值(true和false)的首字母改为大写。 payload = """{ '3': ...省略 } """ payload = json.loads(payload) for i in range(5): # task_id是标识请求和结果的关键标志,请给每个请求分配一个唯一的值,以对应后面的队列结果 response = session.post(url=f'{service_url}/api_prompt?task_id=txt2img_{i}', json=payload) if response.status_code != 200: exit(f"send request error:{response.content}") else: print(f"send {i} success, index is {response.content}")
订阅结果。
执行以下命令安装eas_prediction SDK。
pip install eas_prediction --user
执行以下代码,获取返回结果。
from eas_prediction import QueueClient from urllib.parse import urlparse, urlunparse service_url = "<EAS_ENDPOINT>" token = "<EAS_TOKEN>" def parse_service_url(service_url): parsed = urlparse(service_url) service_domain = f"{parsed.scheme}://{parsed.netloc}" path_parts = [p for p in parsed.path.strip('/').split('/') if p] service_name = path_parts[-1] return service_domain, service_name service_domain, service_name = parse_service_url(service_url) print(f"service_domain: {service_domain}, service_name: {service_name}.") sink_queue = QueueClient(service_domain, f'{service_name}/sink') sink_queue.set_token(token) sink_queue.init() watcher = sink_queue.watch(0, 5, auto_commit=False) for x in watcher.run(): if 'task_id' in x.tags: print('index {} task_id is {}'.format(x.index, x.tags['task_id'])) print(f'index {x.index} data is {x.data}') sink_queue.commit(x.index)
返回结果示例如下:
index 42 task_id is txt2img_0 index 42 data is b'[{"type": "executed", "data": {"node": "9", "output": {"images": [{"filename": "ComfyUI_1712647318_8e7f3c93-d2a8-4377-92d5-8eb552adc172_.png", "subfolder": "", "type": "output"}]}, "prompt_id": "c3c983b6-f92b-4dd5-b4dc-442db4d1736f"}}, {"type": "executed", "data": {"node": "15", "output": {"images": [{"filename": "ComfyUI_1712647895_.webp", "subfolder": "", "type": "output"}], "animated": [true]}, "prompt_id": "c3c983b6-f92b-4dd5-b4dc-442db4d1736f"}}, {"type": "executed", "data": {"node": "19", "output": {"gifs": [{"filename": "comfyUI_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4"}]}, "prompt_id": "c3c983b6-f92b-4dd5-b4dc-442db4d1736f"}}, {"9": {"images": [{"filename": "ComfyUI_1712647318_8e7f3c93-d2a8-4377-92d5-8eb552adc172_.png", "subfolder": "", "type": "output"}]}, "15": {"images": [{"filename": "ComfyUI_1712647895_.webp", "subfolder": "", "type": "output"}], "animated": [true]}, "19": {"gifs": [{"filename": "comfyUI_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4"}]}}]' index 43 task_id is txt2img_1 index 43 data is b'[{"9": {"images": [{"filename": "ComfyUI_1712647318_8e7f3c93-d2a8-4377-92d5-8eb552adc172_.png", "subfolder": "", "type": "output"}]}, "15": {"images": [{"filename": "ComfyUI_1712647895_.webp", "subfolder": "", "type": "output"}], "animated": [true]}, "19": {"gifs": [{"filename": "comfyUI_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4"}]}}]' index 44 task_id is txt2img_2 index 44 data is b'[{"9": {"images": [{"filename": "ComfyUI_1712647318_8e7f3c93-d2a8-4377-92d5-8eb552adc172_.png", "subfolder": "", "type": "output"}]}, "15": {"images": [{"filename": "ComfyUI_1712647895_.webp", "subfolder": "", "type": "output"}], "animated": [true]}, "19": {"gifs": [{"filename": "comfyUI_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4"}]}}]' index 45 task_id is txt2img_3 index 45 data is b'[{"9": {"images": [{"filename": "ComfyUI_1712647318_8e7f3c93-d2a8-4377-92d5-8eb552adc172_.png", "subfolder": "", "type": "output"}]}, "15": {"images": [{"filename": "ComfyUI_1712647895_.webp", "subfolder": "", "type": "output"}], "animated": [true]}, "19": {"gifs": [{"filename": "comfyUI_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4"}]}}]' index 46 task_id is txt2img_4 index 46 data is b'[{"9": {"images": [{"filename": "ComfyUI_1712647318_8e7f3c93-d2a8-4377-92d5-8eb552adc172_.png", "subfolder": "", "type": "output"}]}, "15": {"images": [{"filename": "ComfyUI_1712647895_.webp", "subfolder": "", "type": "output"}], "animated": [true]}, "19": {"gifs": [{"filename": "comfyUI_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4"}]}}]'
您可以在挂载存储的
output
目录中,查看推理结果文件。
生成的图片或视频存储在挂载的output
目录中,API调用的结果返回的是文件名和子目录名。对于OSS,需自行拼接完整的文件路径进行下载,请参见使用阿里云SDK下载OSS文件。
常见问题
模型与节点
1. WebUI报错:缺少模型
问题描述:报错如下:
解决方案:PAI部署的ComfyUI此检查无效,请以运行时的报错为准。建议勾选不再显示此消息,或者通过设置关闭模型校验。
2. 上传了新模型,但是找不到
首先检查是否使用的是Serverless版(Serverless版不支持上传自己的模型,请使用标准版或集群版),版本没问题,请按照以下步骤处理:
单击页面PaiCustom,选择加载新模型。
仍然不行,单击重启进程。
3. 模型加载器显示undefined
首先确认模型的目录位置是否正确,这依赖于模型加载器的要求。
如果是服务启动之后更新的模型文件,请重启服务。
4. 找不到节点
新安装的节点,需重启服务。
节点未安装,请参见使用第三方模型和安装节点(ComfyUI插件)。
5. ComfyUI 管理器下载模型或安装节点失败
在EAS部署的ComfyUI中,不建议使用ComfyUI管理器。因为直接下载外网模型或安装插件(需要从GitHub等平台拉取代码),有可能存在网络连接失败的问题。
建议您需将模型或节点文件上传到服务挂载的存储上,详情请参见使用第三方模型和安装节点(ComfyUI插件)。
6. 如何查看当前可用的模型文件和节点(ComfyUI插件)列表
模型文件:在相应模型加载节点查看。例如在Checkpoint加载器的下拉列表中查看当前可用的模型文件。
节点:右键单击WebUI页面,在快捷菜单中单击添加节点,查看所有已安装的ComfyUI插件。
镜像与依赖
1. 如何安装whl包
请确保进行了挂载配置。以如下挂载路径为例。假设oss路径Uri为 oss://examplebucket/comfyui。
将whl包传到挂载oss的
oss://examplebucket/comfyui/models/whl
下面,如无whl
的文件夹,请先创建一个。示例中oss挂载路径为/mnt/data,则在运行命令的前面增加命令
pip install /mnt/data/models/whl/xxx.whl
。其中/mnt/data表示你的oss挂载路径,xxx.whl表示你的whl包的名字。
2. 如何更新镜像版本(保留已安装的自定义模型)
Serverless版本无法更新ComfyUI镜像。对于其他版本,如果服务挂载OSS或者NAS存储空间,自定义模型保留在OSS或者NAS存储空间,您只需要直接更新服务配置中的官方镜像,不会影响已安装的自定义模型。具体步骤如下:
在服务详情页右上角单击更新。
如果是通过场景化部署,请切换为自定义部署。单击右侧的服务配置,编辑JSON文件,更新containers的image字段,如图将pai-eas/comfyui:后面的1.9改成需要的版本。
在编辑窗口单击直接更新。
3. 镜像依赖库缺失
通过三方库配置安装缺失的依赖。步骤如下:
在服务详情页右上角单击更新。
如果是通过场景化部署,请切换为自定义部署。单击右侧的环境信息,在更多配置区域找到三方库配置,按页面要求格式将所需的依赖填写到里面。
单击页面下方更新按钮,完成服务更新即可。
运行异常
1. 页面卡死或刷新页面时间过长
刷新页面,清理浏览器缓存或使用无痕/隐私模式访问。
如进行了存储挂载,清理挂载目录里面的input/output/temp文件夹里面的文件。
尝试重启服务。
2. 工作流跑一半,进程重启了
如果实例日志里面有run.sh: line 54: 531285 Killed python -u main_run.py "$@"
,那就是内存oom了,内存oom之后,进程会自动重启。
3. RuntimeError: CUDA error: out of memory
显存超了,如果是图像模型就降低图像的分辨率或者batch size;视频模型降低一下帧数/分辨率
4. API调用报错:url not found 或404 page not found?
请检查服务部署的是否是Serverless版本,Serverless类型不支持API调用。
若服务版本正确,请验证API端点URL是否完整。同步调用需拼接
/prompt
路径。
5. 服务一直显示等待中或者ComfyUI无法出图
通常是资源规格不够的原因。请检查服务镜像和资源规格配置是否正确,资源规格推荐使用GU30、A10或T4卡型,其中ml.gu7i.c16m60.1-gu30性价比高。
6. 服务部署一段时间后为什么会自动停止?
Serverless版的模型服务如果长时间没有接收到请求或计算任务,系统可能会自动释放相关资源以降低成本。
其他
1. 如何延长无登录态的url时间
可通过API获取指定有效时长的免登录Web访问链接。
单击 DescribeServiceSignedUrl API进入页面。
选择服务region。
填写服务所在区域与服务名字。如下从EAS服务概览页面获取。
其他参数如下:
Type页面类型:下拉选择 webview。
Expire过期时间:如果想长期有效就写 9007199254740991(目前最长只能是12小时)
否则就写一个整数,单位为秒。
Internal是否为VPC链接:如果不是vpc调用选择false,否则选择true。
单击发起调用,返回结果中SignedUrl为服务免登录Web访问链接。
2. xFormer对图片生成速度的加速效果
xFormers是基于Transformer的开源加速工具,能够有效缩短图片和视频生成时长,节省显存使用。ComfyUI镜像部署默认已开启xFormers加速。加速效果跟工作流的大小相关,针对GPU调用的内容尤其是使用NVIDIA显卡的提升比较明显。
3. EAS与函数计算在部署ComfyUI Serverless版的主要区别
EAS:适合有状态、长周期运行的服务,支持一键部署模型为在线推理服务或AI-Web应用,具备弹性扩缩容、蓝绿部署等功能。例如,您可以通过EAS的场景化模型部署或自定义模型部署方式来部署ComfyUI。
函数计算:基于Serverless架构,提供按需付费、弹性伸缩等优势,适合需要高质量图像生成功能的场景,可自定义ComfyUI模型及安装插件。例如,您可以在函数计算3.0控制台创建应用、选择ComfyUI模板、设置配置项并创建应用。
4. 如何切换WebUI页面的默认语言?
在WebUI页面,单击左下角的设置按钮
。
在设置对话框,分别在以下两个位置进行语言设置。参数设置完成后,刷新页面并重新加载即可。
在左侧导航栏选择Comfy,在右侧区域设置中,设置目标语言。
在左侧导航栏选择语言,在右侧区域设置中,设置目标语言。
附录
集群版服务原理介绍
实现原理图如下:
集群版服务主要针对多用户场景,实现了客户端和后端推理实例解耦,以便多用户可以分时复用后端推理实例,提升实例的利用率和降低推理成本。
每个用户有独立的后端环境和工作目录,实现高效的GPU共享和文件管理。
Proxy代理主要负责客户端进程和推理实例的管理。用户的所有操作都在自己的进程中进行处理,相关的文件操作仅限于公共目录和个人目录,从而实现了用户间工作目录的有效隔离。当用户需要使用推理实例来处理请求时,Proxy代理会从后端推理实例中找到可用的空闲实例来处理该推理请求。