AI视频生成-ComfyUI部署

更新时间: 2025-08-05 10:44:16

ComfyUI提供基于Stable Diffusion的节点式操作界面,可方便快速地构建复杂的AIGC流程,完成短视频内容生成、动画制作等任务。本文介绍如何在EAS中部署和使用ComfyUI。

部署版本说明

适用场景

调用方式

标准版

  • 单用户WebUI或API调用,适合入门和开发测试。

  • 建议部署单实例,并发能力有限。

  • WebUI

  • 在线调试

  • API调用(同步)

API版

  • 高并发,适合生产环境。

  • 部署相对复杂,需额外CPU资源以创建队列服务实例。

API调用(异步)

集群版WebUI

  • 多用户同时使用WebUI,环境隔离,适合团队协作或教学。

  • 资源消耗较高,原理参见集群版服务原理介绍

WebUI

Serverless版

  • 部署免费,按出图时长计费,成本极低,适合波动性需求。

  • 无法使用自定义模型/插件。仅华东2(上海)、华东1(杭州)地域支持部署。

WebUI

说明

此处API调用的同步与异步取决于是否使用EAS的队列服务:

  • 同步调用:直接请求推理实例,不使用EAS的队列服务;

  • 异步调用:使用EAS的队列服务,向输入队列发送请求,以订阅的方式获得结果推送。

由于ComfyUI本身具有异步队列系统,即使发起同步调用,实质上也是异步进行的。用户发送请求后,系统会返回一个Prompt ID,然后需要使用Prompt ID轮询以获取推理结果。

计费说明

  • Serverless版:部署免费,按实际推理时长计费。

  • 其他版本:按照部署资源和运行时长计费(部署成功后即使不使用也计费)。

更多计费详情请参见模型在线服务(EAS)计费说明

部署服务

Serverless版只能使用场景化模型部署,标准版、集群版、API版可使用场景化模型部署(操作简单)或者自定义模型部署(支持更多功能)。

重要
  • ComfyUI仅支持单卡(单机单卡或多机单卡),不支持多卡并发操作。因为一个EAS实例只有一个ComfyUI进程,部署时选择多GPU规格的实例(如2*A10)也只会使用一张GPU卡,并不会让单个生图任务变快。

  • 负载均衡:需部署API版,通过异步队列实现。

方式一:场景化模型部署(推荐)

  1. 登录PAI控制台,在页面上方选择目标地域,并在右侧选择目标工作空间,然后单击进入EAS

  2. 模型在线服务(EAS)页面,单击部署服务,在场景化模型部署区域,单击AI视频生成-ComfyUI部署

  3. AI视频生成-ComfyUI部署页面,配置以下关键参数。

    • 版本选择:根据部署版本说明选择。

    • 模型配置:如需使用自己的模型、安装自定义节点或通过API调用,必须进行模型配置。以对象存储(OSS)为例,选择Bucket和目录,部署成功后系统会自动在其中创建ComfyUI所需目录。请确保创建的存储空间与EAS服务位于同一地域。

    • 资源配置选择:推荐使用GU30、A10或T4卡型。系统默认选择GPU > ml.gu7i.c16m60.1-gu30,性价比高。

  4. 单击部署。等待约5分钟,当服务状态变为运行中,表示部署成功。

方式二:自定义模型部署

  1. 登录PAI控制台,在页面上方选择目标地域,并在右侧选择目标工作空间,然后单击进入EAS

  2. 单击部署服务,然后在自定义模型部署区域,单击自定义部署

  3. 自定义部署页面,配置以下关键参数。更多配置,请参见控制台自定义部署参数说明

    • 部署方式:选择镜像部署,并选中开启Web应用复选框。

    • 镜像配置:在官方镜像列表中选择comfyui>comfyui:1.9。其中:x.x:表示标准版,x.x-api:表示API版,x.x-cluster:表示集群版。

      说明
      • 由于版本迭代迅速,部署时镜像版本选择最高版本即可。

      • 更多关于每个版本的使用场景说明,请参见部署版本说明

    • 模型配置:如需使用自己的模型、安装自定义节点或通过API调用,必须进行模型配置。以对象存储(OSS)为例,选择Bucket和目录,部署成功后系统会自动在其中创建ComfyUI所需目录。请确保创建的存储空间与EAS服务位于同一地域。

      • Uri:单击image选择已创建的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

  4. 单击部署。服务部署时间约为5分钟,当服务状态运行中时,表明服务已成功部署。

调用服务

WebUI使用

重要

标准版、集群版和Serverless版支持通过WebUI使用。

单击目标服务调用/日志/监控列的image按钮,进入WebUI页面。如页面长时间无法打开,请参见刷新页面时间过长或页面卡死

1. 使用模板工作流

WebUI页面支持自定义工作流配置,同时预置多种模板,可在工作流 > 浏览模板页面中选择。本文以视频工作流模板Wan VACE Text to Video为例。

说明

对于Serverless版本,模板中无该工作流,可选择其他模板使用。也可以通过工作流 > 打开,加载本地文件系统中的工作流。

image

工作流加载成功后,如遇到报错缺少模型,可忽视(建议勾选不再显示此消息)。

由于路径变更,直接运行工作流可能会出现以下报错。

image.png

请先在Load models here区域重新选择模型wan2.1_vace_14B_fp16.safetensorsWan21_CausVid_14B_T2V_lora_rank32.safetensors

image

工作流运行成功后,会在Save Video区域,展示生成的视频。image

2. 使用第三方模型和安装自定义节点(ComfyUI插件)

  1. 确认部署的ComfyUI版本非Serverless版本。Serverless版本只能使用内置的模型和节点。

  2. 确认服务已配置存储挂载。如使用自定义部署,需在运行命令中增加参数--data-dir挂载目录,详情见方式二:自定义模型部署

    服务部署成功后,系统会自动在已挂载的OSS或NAS存储空间中创建以下目录结构。

    image

    其中:

    • custom_nodes:该目录用来存储节点文件。

    • models:该目录用来存放模型文件。

  3. 上传模型或节点文件。以OSS为例,可控制台上传文件到OSS。对于大文件,请参见如何上传大文件到OSS?

    • 模型文件上传:根据模型使用节点的源项目库使用说明,将模型上传至models下的队员子目录。例如:

      • 对于Checkpoint加载器,模型应上传至models/checkpoints

      • 对于风格模型加载器,模型应上传至models/styles

    • 节点文件上传:推荐您将自定义节点上传至挂载存储的custom_nodes目录。

  4. 加载新模型或重启进程。

    在挂载的存储空间上传模型之后,单击PaiCustom > 加载新模型,如仍然找不到模型,单击重启进程,重启成功后,刷新浏览器页面。

    上传节点文件之后,直接单击重启进程。重启成功后,刷新浏览器页面。image

3. 导出工作流

在工作流调试完成之后,单击工作流 > 导出(API),可以将工作流保存为一个JSON文件。后续可用于API调用。

image

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)页面,单击目标服务操作列下的在线调试,进入在线调试页面。

  1. 发送POST请求,获取Prompt ID。

    1. 在调试页面的在线调试请求参数区域的Body处填写已准备好的请求体。并在请求URL文本编辑框中添加/promptimage

    2. 单击发送请求,即可在调试信息区域查看返回结果,示例如下。image

  2. 发送GET请求,根据Prompt ID获取推理结果。

    1. 在线调试请求参数区域中,将请求方法修改为GET,并在文本框中配置/history/<prompt id>,示例如下。image

      其中<prompt id>需要替换为步骤1获取的Prompt ID。

    2. 单击发送请求,即可获取推理结果。

      您可以在挂载存储的output目录中,查看生成的推理结果。

同步调用

  1. 查看调用信息。

    1. 推理服务页签,单击您的服务名称进入概览页面,在基本信息区域单击查看调用信息

    2. 调用信息面板,可获取访问地址和Token。根据您的实际情况选择公网或VPC地址,下文使用<EAS_ENDPOINT>和<EAS_TOKEN>指代它们。

      image

  2. 发送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。

  3. 发送请求,获取推理结果。

    • 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={需分配唯一值}

  1. 查看调用信息。

    单击API版服务的服务方式列下的调用信息,在调用信息配置面板的异步调用页签,查看服务访问地址和Token。

    下文使用<EAS_ENDPOINT>指代公网输入调用地址(如果调用端与EAS处于同一VPC,可使用VPC输入调用地址),<EAS_TOKEN>指代Tokenimage

  2. 推送请求。

    代码示例如下:

    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}")
  3. 订阅结果。

    1. 执行以下命令安装eas_prediction SDK。

      pip install eas_prediction  --user
    2. 执行以下代码,获取返回结果。

      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报错:缺少模型

问题描述:报错如下:

image.png

解决方案:PAI部署的ComfyUI此检查无效,请以运行时的报错为准。建议勾选不再显示此消息,或者通过设置关闭模型校验。

image

2. 上传了新模型,但是找不到

首先检查是否使用的是Serverless版(Serverless版不支持上传自己的模型,请使用标准版或集群版),版本没问题,请按照以下步骤处理:

  1. 单击页面PaiCustom,选择加载新模型。image

  2. 仍然不行,单击重启进程。image

3. 模型加载器显示undefined

首先确认模型的目录位置是否正确,这依赖于模型加载器的要求。

如果是服务启动之后更新的模型文件,请重启服务。

4. 找不到节点

5. ComfyUI 管理器下载模型或安装节点失败

在EAS部署的ComfyUI中,不建议使用ComfyUI管理器。因为直接下载外网模型或安装插件(需要从GitHub等平台拉取代码),有可能存在网络连接失败的问题。

建议您需将模型或节点文件上传到服务挂载的存储上,详情请参见使用第三方模型和安装节点(ComfyUI插件)

6. 如何查看当前可用的模型文件和节点(ComfyUI插件)列表

  • 模型文件:在相应模型加载节点查看。例如在Checkpoint加载器的下拉列表中查看当前可用的模型文件。

  • 节点:右键单击WebUI页面,在快捷菜单中单击添加节点,查看所有已安装的ComfyUI插件。

镜像与依赖

1. 如何安装whl包

  1. 请确保进行了挂载配置。以如下挂载路径为例。假设oss路径Uri为 oss://examplebucket/comfyui。image

  2. 将whl包传到挂载oss的oss://examplebucket/comfyui/models/whl下面,如无whl的文件夹,请先创建一个。

  3. 示例中oss挂载路径为/mnt/data,则在运行命令的前面增加命令 pip install /mnt/data/models/whl/xxx.whl。其中/mnt/data表示你的oss挂载路径xxx.whl表示你的whl包的名字。

    image

2. 如何更新镜像版本(保留已安装的自定义模型)

Serverless版本无法更新ComfyUI镜像。对于其他版本,如果服务挂载OSS或者NAS存储空间,自定义模型保留在OSS或者NAS存储空间,您只需要直接更新服务配置中的官方镜像,不会影响已安装的自定义模型。具体步骤如下:

  1. 在服务详情页右上角单击更新。

    image

  2. 如果是通过场景化部署,请切换为自定义部署。单击右侧的服务配置,编辑JSON文件,更新containers的image字段,如图将pai-eas/comfyui:后面的1.9改成需要的版本。

    image

  3. 在编辑窗口单击直接更新

3. 镜像依赖库缺失

通过三方库配置安装缺失的依赖。步骤如下:

  1. 在服务详情页右上角单击更新。

    image

  2. 如果是通过场景化部署,请切换为自定义部署。单击右侧的环境信息,在更多配置区域找到三方库配置,按页面要求格式将所需的依赖填写到里面。

    image

    image

  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?

  1. 请检查服务部署的是否是Serverless版本,Serverless类型不支持API调用。

  2. 若服务版本正确,请验证API端点URL是否完整。同步调用需拼接/prompt路径。

5. 服务一直显示等待中或者ComfyUI无法出图

通常是资源规格不够的原因。请检查服务镜像和资源规格配置是否正确,资源规格推荐使用GU30、A10或T4卡型,其中ml.gu7i.c16m60.1-gu30性价比高。

6. 服务部署一段时间后为什么会自动停止?

Serverless版的模型服务如果长时间没有接收到请求或计算任务,系统可能会自动释放相关资源以降低成本。

其他

1. 如何延长无登录态的url时间

可通过API获取指定有效时长的免登录Web访问链接。

  1. 单击 DescribeServiceSignedUrl API进入页面。

  2. 选择服务region。

image

  1. 填写服务所在区域与服务名字。如下从EAS服务概览页面获取。

    image

  2. 其他参数如下:

    • Type页面类型:下拉选择 webview。

    • Expire过期时间:如果想长期有效就写 9007199254740991(目前最长只能是12小时)

      否则就写一个整数,单位为秒。

    • Internal是否为VPC链接:如果不是vpc调用选择false,否则选择true。

  3. 单击发起调用,返回结果中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页面的默认语言?

  1. 在WebUI页面,单击左下角的设置按钮image

  2. 设置对话框,分别在以下两个位置进行语言设置。参数设置完成后,刷新页面并重新加载即可。

    • 在左侧导航栏选择Comfy,在右侧区域设置中,设置目标语言。image

    • 在左侧导航栏选择语言,在右侧区域设置中,设置目标语言。image

附录

集群版服务原理介绍

实现原理图如下:

image
  • 集群版服务主要针对多用户场景,实现了客户端和后端推理实例解耦,以便多用户可以分时复用后端推理实例,提升实例的利用率和降低推理成本。

  • 每个用户有独立的后端环境和工作目录,实现高效的GPU共享和文件管理。

  • Proxy代理主要负责客户端进程和推理实例的管理。用户的所有操作都在自己的进程中进行处理,相关的文件操作仅限于公共目录和个人目录,从而实现了用户间工作目录的有效隔离。当用户需要使用推理实例来处理请求时,Proxy代理会从后端推理实例中找到可用的空闲实例来处理该推理请求。

上一篇: ChatLLM-WebUI版本发布详情 下一篇: Wan视频生成最佳实践
阿里云首页 人工智能平台 PAI 相关技术圈