AI视频生成-ComfyUI部署

EAS提供了场景化部署方式,您只需配置几个参数,即可一键部署基于ComfyUIStable Video Diffusion模型的AI视频生成服务,帮助您完成社交平台短视频内容生成、动画制作等任务。本文为您介绍如何基于ComfyUI镜像部署服务和几种常用的调用方式。

背景信息

随着AIGC的广泛应用,AI视频生成已成为当前热门应用之一。目前市面上有许多开源视频生成大模型可供选择,它们在不同领域展现了各自独特的性能。与此同时,AIGC开源工具ComfyUI也迅速在市场上崭露头角。作为一个基于节点流程式的AIGC生成工具WebUI,ComfyUIAIGC流程拆分成工作节点,实现了精准的工作流定制和可复现性。您可以一键部署基于ComfyUIAI视频生成服务,支持以下三种版本:

  • 标准版:仅适用于单用户使用WebUI,或使用一个实例调用API场景。支持以下两种调用方式:

    • WebUI:请求发送时,会绕过EAS接口,前端直接将请求传递给后端服务器,所有请求均由同一个后端实例进行处理。建议您只部署1个实例,当您需要多台实例时,需选择集群版。

    • API:通过EAS接口发送请求给后端实例进行处理。建议您只部署1个实例,当您需要多台实例时,需选择API版本。

  • API版:系统将自动转换服务为异步模式,适用于高并发场景。该模式仅支持API异步调用。基于其异步特性,系统会创建队列服务实例,因此需要分配额外的CPU实例。

  • 集群版:适合多用户同时在WebUI页面进行操作。仅支持通过WebUI进行调用,不提供API服务。主要适用于设计组或教学场景,通过分时复用的设计来提升推理集群的利用率,降低成本。由于Proxy负责处理WebUI请求,因此需要分配额外的CPU实例。参考集群版服务原理介绍,了解集群版服务的实现原理。该版本优势如下:

    • 系统为每个用户提供独立的后端环境。当开启了多个实例时,单个用户的任务会按顺序执行,而多用户环境下的任务则在不同实例之间分配,实现高效的GPU共享。

    • 系统为每个用户分配独立的工作目录,便于管理和存储模型、输出图像或视频等文件。

具体使用流程如下:

  1. 部署EAS服务

    根据您的具体使用场景,选择部署标准版、API版或集群版的服务。

  2. 调用EAS服务

    根据部署的服务版本,支持以下三种调用方式:

    • 通过WebUI调用EAS服务

      使用WebUI发送服务请求,仅标准版和集群版服务支持使用该方式。

    • 在线调试EAS服务

      EAS的在线调试页面发送同步调用请求,只有标准版服务支持同步调用功能。

    • 通过API调用EAS服务

      只有标准版和API版服务支持通过API发送服务请求。其中,标准版服务仅支持同步调用,而API版服务仅支持异步调用。

前提条件

在部署微调模型、安装ComfyUI插件、使用API调用服务时,您必须挂载NASOSS存储,以便上传微调模型、插件和获取推理结果。请提前准备NASOSS存储空间:

  • 已创建OSS存储空间和空目录,例如:oss://bucket-test/data-oss/,其中:bucket-testOSS存储空间名称;data-oss为该存储空间下的空目录。关于如何创建OSS存储空间,请参见创建存储空间;关于如何创建空目录,请参见管理目录

  • 已创建NAS文件系统和空目录。具体操作,请参见创建文件系统

部署EAS服务

支持以下两种部署方式:

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

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

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

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

    参数

    描述

    基本信息

    服务名称

    自定义模型服务名称。

    版本选择

    支持选择以下版本:

    • 标准版:适用于单用户使用WebUI或使用一个实例调用API场景。支持通过WebUI生成视频,也可通过API进行调用。

    • API:系统将自动转换服务为异步模式,适用于高并发场景。仅支持通过API进行调用。

    • 集群版WebUI:适合多用户同时在WebUI页面进行操作。仅支持通过WebUI进行调用,不提供API服务。关于该版本的实现原理介绍,请参见集群版服务原理介绍

    更多关于每个版本的使用场景说明,请参见背景信息

    模型配置

    当部署微调模型、安装ComfyUI插件,或选择API标准版并通过API进行调用时,您需要单击添加按钮,进行模型配置,以便上传微调模型、插件和获取推理结果。支持以下几种配置类型:

    • 对象存储(OSS):单击image选择已创建的OSS存储目录。

    • 文件存储(NAS):配置NAS挂载点和NAS源路径。

    后续,您可以将自定义模型和ComfyUI插件上传至指定的OSSNAS路径,以便加载和使用这些资源。具体操作,请参见如何挂载自定义模型和ComfyUI插件?

    资源配置

    实例数

    当版本选择标准版时,建议将实例数配置为1。

    资源配置选择

    资源规格推荐使用GU30、A10T4卡型。系统默认选择GPU > ml.gu7i.c16m60.1-gu30,性价比高。

    说明

    ComfyUI仅支持单卡(单机单卡或多机单卡)运行,不支持多卡并发操作。

  4. 单击部署

方式二:自定义模型部署

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

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

  3. 自定义部署页面,配置以下关键参数。

    参数

    描述

    基本信息

    服务名称

    自定义服务名称。本案例使用的示例值为:comfyui_svd_demo

    环境信息

    部署方式

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

    镜像配置

    官方镜像列表中选择comfyui>comfyui:1.7,其中:

    • x.x:表示标准版。

    • x.x-api:表示API版。

    • x.x-cluster:表示集群版。

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

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

    模型配置

    当部署微调模型、安装ComfyUI插件,或选择API标准版并通过API进行调用时,您需要进行模型配置,以便上传微调模型、插件和获取推理结果。支持以下几种配置类型:

    • OSS

      • OSS:单击image选择已创建的OSS存储目录。例如oss://bucket-test/data-oss/

      • 挂载路径:配置为/code/data-oss,表示将您配置的OSS文件目录挂载到镜像的/code/data-oss路径下。

    • 通用型NAS

      • 选择文件系统:选择NAS文件系统。

      • 文件系统挂载点:选择NAS挂载点,EAS服务通过挂载点来访问NAS文件系统。

      • 文件系统路径:需要挂载的NAS中的源路径,即NAS实例内部的文件系统路径。例如/data-oss

      • 挂载路径:配置为/code/data-oss,表示将您配置的NAS源路径挂载到镜像的/code/data-oss路径下。

    后续,您可以将自定义模型和ComfyUI插件上传至指定的OSSNAS路径,以便加载和使用这些资源。具体操作,请参见如何挂载自定义模型和ComfyUI插件?

    运行命令

    • 配置镜像版本后,系统自动配置运行命令python main.py --listen --port 8000

    • 端口号为:8000。

    当您进行模型配置后,您需要在运行命令中增加--data-dir挂载目录,其中挂载目录需要与模型配置中的挂载路径一致。例如python main.py --listen --port 8000 --data-dir /code/data-oss

    资源部署

    资源类型

    选择公共资源

    实例数

    当镜像版本标准版时,建议将实例数配置为1。

    部署资源

    资源规格必须选择GPU类型,推荐使用ml.gu7i.c16m60.1-gu30(性价比最高)。如库存不足可选择ecs.gn6i-c16g1.4xlarge

    说明

    ComfyUI仅支持单卡(单机单卡或多机单卡)运行,不支持多卡并发操作。

  4. 单击部署

    服务状态运行中时,表明服务已成功部署。

调用EAS服务

通过WebUI调用EAS服务

通过WebUI,您可以调用标准版和集群版的EAS服务。在标准版服务中,所有请求都由同一个后端实例处理。而集群版服务则适合多用户同时操作,它能够在多个实例间分配并处理各用户的任务。具体操作步骤如下:

  1. 单击目标服务的服务方式列下的查看Web应用

    说明

    访问WebUI时,大约需要1分钟的加载时间,之后您将能看到完整的初始工作流界面。

  2. WebUI页面的默认语言切换为中文

  3. WebUI页面进行模型推理验证。

    根据您自己的业务需要,选择文生图的模型和图生视频的模型,本方案使用默认配置。然后在CLIP文本编码器中输入Prompts,例如:Rocket takes off from the ground, fire, sky, airplane,单击添加提示词队列, 等待工作流运行完成即可获得AI生成的视频。85453c9fcadd222fbb087c5acddb6e90.png

  4. 右键单击生成的视频,选择保存图像,即可将生成的视频保存到本地。image.png

    生成的视频示例如下所示:

在线调试EAS服务

仅标准版服务支持在线调试,具体操作步骤如下:

  1. 生成请求体。具体操作,请参见如何生成请求体

  2. 模型在线服务(EAS)页面,单击目标服务操作列下的在线调试,进入在线调试页面。

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

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

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

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

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

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

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

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

通过API调用EAS服务

标准版和API版服务支持API调用。API调用支持同步调用和异步调用两种方式:

  • 同步调用

    标准版服务仅支持同步调用方式,即客户端发送一个请求,同步等待结果返回。

  • 异步调用

    API版服务仅支持异步调用方式,即客户端使用EAS的队列服务向输入队列发送请求,并通过订阅的方式从输出队列查询结果。

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

同步调用

  1. 查看调用信息。

    1. 在服务列表中,单击标准版服务名称,然后在基本信息区域,单击查看调用信息

    2. 调用信息对话框的公网地址调用页签,获取服务访问地址和Token。image

  2. 获取Prompt ID。

    1. 生成请求体。具体操作,请参见如何生成请求体

    2. 发送请求,获取Prompt ID。

      支持以下两种方式:

      Curl

      • HTTP请求方式:POST

      • 请求URL:<service_url>/prompt

      • 请求头部:

        头部

        描述

        Authorization

        <token>

        授权密钥

        Content-Type

        application/json

        指定请求体格式

      • 代码示例

        curl --location --request POST '<service_url>/prompt' \
        --header 'Authorization: <token>' \
        --header 'Content-Type: application/json' \
        --data-raw '{
            "prompt":
            ...省略
        }'

        其中关键配置项如下:

        配置项

        描述

        <service_url>

        替换为步骤1中获取的服务访问地址。您需要将访问地址末尾的/删除。例如http://comfyui****.175805416243****.cn-beijing.pai-eas.aliyuncs.com

        <token>

        替换为步骤1中获取的Token。例如ZGJmNzcwYjczODE1MmVlNWY1NTNiNGYxNDkzODI****NzU2NTFiOA==

        data-raw

        配置为请求体,例如:

        重要

        请求体中的布尔值(truefalse)首字母需要小写。

        单击此处查看请求体示例

        {
            "prompt": {
                "3": {
                    "inputs": {
                        "seed": 367490676387803,
                        "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",
                        "clip": [
                            "4",
                            1
                        ]
                    },
                    "class_type": "CLIPTextEncode",
                    "_meta": {
                        "title": "CLIP文本编码器"
                    }
                },
                "7": {
                    "inputs": {
                        "text": "",
                        "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": 510424455529432,
                        "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,
                        "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.15,
                        "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_image_decoder.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,
                        "pingpong": false,
                        "save_output": true,
                        "images": [
                            "14",
                            0
                        ]
                    },
                    "class_type": "VHS_VideoCombine",
                    "_meta": {
                        "title": "合并为视频"
                    }
                }
            }
        }

      Python

      代码示例如下:

      import requests
      
      url = "<service_url>/prompt"
      
      payload = {
          "prompt":
          ...省略
      }
      
      session = requests.session()
      session.headers.update({"Authorization":"<token>"})
      
      
      response = session.post(url=f'{url}', json=payload)
      if response.status_code != 200:
          raise Exception(response.content)
      
      data = response.json()
      print(data)

      其中关键配置项如下:

      配置项

      描述

      <service_url>

      替换为步骤1中获取的服务访问地址。您需要将访问地址末尾的/删除,例如http://comfyui****.175805416243****.cn-beijing.pai-eas.aliyuncs.com

      <token>

      替换为步骤1中获取的Token。ZGJmNzcwYjczODE1MmVlNWY1NTNiNGYxNDkzODI****NzU2NTFiOA==

      payload

      配置为请求体,例如:

      重要

      请求体中的布尔值(TrueFalse)首字母需要大写。

      单击此处查看请求体示例

      {
          "prompt": {
              "3": {
                  "inputs": {
                      "seed": 367490676387803,
                      "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",
                      "clip": [
                          "4",
                          1
                      ]
                  },
                  "class_type": "CLIPTextEncode",
                  "_meta": {
                      "title": "CLIP文本编码器"
                  }
              },
              "7": {
                  "inputs": {
                      "text": "",
                      "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": 510424455529432,
                      "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,
                      "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.15,
                      "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_image_decoder.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,
                      "pingpong": False,
                      "save_output": True,
                      "images": [
                          "14",
                          0
                      ]
                  },
                  "class_type": "VHS_VideoCombine",
                  "_meta": {
                      "title": "合并为视频"
                  }
              }
          }
      }

      返回结果示例如下:

      {'prompt_id': '021ebc5b-e245-4e37-8bd3-00f7b949****',
       'number': 5,
       'node_errors': {}}

      您可以从返回结果中获取Prompt ID。

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

    支持以下两种方式:

    Curl

    • HTTP请求方式:GET

    • 请求URL:<service_url>/history/<prompt_id>

    • 请求头部:

    • 头部

      描述

      Authorization

      <token>

      授权密钥

    • 代码示例:

      curl --location --request GET '<service_url>/history/<prompt_id>' \
           --header 'Authorization: <token>'

      其中关键配置项如下:

      配置项

      描述

      <service_url>

      替换为步骤1中获取的服务访问地址。您需要将访问地址末尾的/删除。例如http://comfyui****.175805416243****.cn-beijing.pai-eas.aliyuncs.com

      <token>

      替换为步骤1中获取的Token。例如ZGJmNzcwYjczODE1MmVlNWY1NTNiNGYxNDkzODI****NzU2NTFiOA==

      <prompt_id>

      替换为步骤2中获取的prompt_id。

    Python

    代码示例如下:

    import requests
    
    # 构造请求URL。
    url = "<service_url>/history/<prompt_id>"
    
    session = requests.session()
    session.headers.update({"Authorization":"<token>"})
    
    response = session.get(url=f'{url}')
    
    if response.status_code != 200:
        raise Exception(response.content)
    
    data = response.json()
    print(data)

    其中关键配置项如下:

    配置项

    描述

    <service_url>

    替换为步骤1中获取的服务访问地址。您需要将访问地址末尾的/删除,例如http://comfyui****.175805416243****.cn-beijing.pai-eas.aliyuncs.com

    <token>

    替换为步骤1中获取的Token。例如ZGJmNzcwYjczODE1MmVlNWY1NTNiNGYxNDkzODI****NzU2NTFiOA==

    <prompt_id>

    替换为步骤2中获取的prompt_id。

    返回结果示例如下:

    {
        "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版的服务支持异步调用,且仅支持api_prompt路径。

  1. 查看调用信息。

    单击API版服务的服务方式列下的调用信息,在调用信息对话框的异步调用页签,查看服务访问地址和Token。image

  2. 推送请求。

    代码示例如下:

    import requests,io,base64
    from PIL import Image, PngImagePlugin
    
    url = "<service_url>"
    session = requests.session()
    session.headers.update({"Authorization":"<token>"})
    
    work_flow = {
        '3': 
        ...省略
      }
    
    for i in range(5):
      payload = work_flow
      response = session.post(url=f'{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}")

    其中关键配置项如下:

    配置项

    描述

    <service_url>

    替换为步骤1获取的服务访问地址。您需要将访问地址末尾的/删除,例如http://175805416243****.cn-beijing.pai-eas.aliyuncs.com/api/predict/comfyui_api

    <token>

    替换为步骤1获取的Token。例如ZTJhM****TBhMmJkYjM3M2U0NjM1NGE3OGNlZGEyZTdjYjlm****Nw==

    work_flow

    配置为工作流对应的JSON文件内容,示例如下。如何获取工作流JSON文件,请参见如何生成请求体

    重要

    文件中的布尔值(TrueFalse)首字母需要大写。

    单击此处查看JSON文件示例

    {
      "3": {
        "inputs": {
          "seed": 1021224598837526,
          "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",
          "clip": [
            "4",
            1
          ]
        },
        "class_type": "CLIPTextEncode",
        "_meta": {
          "title": "CLIP文本编码器"
        }
      },
      "7": {
        "inputs": {
          "text": "",
          "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": 1072245043382649,
          "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,
          "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.15,
          "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_image_decoder.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,
          "pingpong": False,
          "save_output": True,
          "images": [
            "14",
            0
          ]
        },
        "class_type": "VHS_VideoCombine",
        "_meta": {
          "title": "合并为视频"
        }
      }
    }
  3. 订阅结果。

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

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

      from eas_prediction import QueueClient
      
      sink_queue = QueueClient('<service_domain>', '<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)
      

      其中关键配置说明如下:

      配置项

      描述

      <service_domain>

      请替换为步骤1查询的服务访问地址中的调用信息。例如139699392458****.cn-hangzhou.pai-eas.aliyuncs.com

      <service_name>

      请替换为EAS服务名称。

      <token>

      请替换为步骤1查询的Token。

      返回结果示例如下:

      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目录中,查看推理结果文件。

相关文档

  • ComfyUIAPI版本启用了异步队列,关于异步调用的原理介绍,请参见部署异步推理服务

  • 通过EAS,您还可以完成以下场景化部署:

    • 部署支持WebUIAPI调用的LLM大语言模型,并在部署LLM应用后,利用LangChain框架集成企业知识库,实现智能问答和自动化功能。详情请参见5分钟使用EAS一键部署LLM大语言模型应用

    • 部署集成了大语言模型(LLM)和检索增强生成(RAG)技术的对话系统服务,适用于问答、摘要生成和依赖外部知识的自然语言处理任务。详情请参见大模型RAG对话系统

附录

如何生成请求体

您需要在WebUI页面设置满足业务需求的工作流,然后构建相应的请求体。具体操作步骤如下:

  1. 模型在线服务(EAS)页面,单击目标服务的服务方式列下的查看Web应用,进入WebUI页面。

    说明

    访问WebUI时,大约需要1分钟的加载时间,之后您将能看到完整的初始工作流界面。

  2. WebUI页面,单击image按钮,并在Settings对话框中选中启用开发模式选项复选框。image

  3. WebUI页面,根据您的业务需求配置工作流。

    您可以在Checkpoint加载器区域选择模型,在CLIP文本编码器中输入正向和反向提示词、调整采样器配置等。完成这些操作后,单击添加提示词队列以获取AI生成的视频,并确保所有配置都符合您的需求。

  4. 确定工作流符合预期后,请单击保存(API格式),下载该工作流对应的JSON文件。image

    其中:

    • 同步调用和在线调试的请求体需要将下载的JSON文件内容置于prompt键下进行包装。例如,上述工作流对应的请求体为:

      {
          "prompt": {
              "3": {
                  "inputs": {
                      "seed": 367490676387803,
                      "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",
                      "clip": [
                          "4",
                          1
                      ]
                  },
                  "class_type": "CLIPTextEncode",
                  "_meta": {
                      "title": "CLIP文本编码器"
                  }
              },
              "7": {
                  "inputs": {
                      "text": "",
                      "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": 510424455529432,
                      "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,
                      "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.15,
                      "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_image_decoder.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,
                      "pingpong": false,
                      "save_output": true,
                      "images": [
                          "14",
                          0
                      ]
                  },
                  "class_type": "VHS_VideoCombine",
                  "_meta": {
                      "title": "合并为视频"
                  }
              }
          }
      }
    • 异步调用的请求体不需要prompt键值。上述工作流对应的请求体即为已下载的JSON文件内容。

集群版服务原理介绍

实现原理图如下:

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

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

加速图片生成速度

xFormers是基于Transformer的开源加速工具,能够有效缩短图片和视频生成时长,节省显存使用。ComfyUI镜像部署默认已开启xFormers加速。

如何挂载自定义模型和ComfyUI插件?

服务部署后,系统会自动在已挂载的OSSNAS存储空间中创建以下目录结构:image

其中:

  • custom_nodes:该目录用来存储ComfyUI插件。

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

如果您从开源社区获取了ComfyUI的第三方插件,或自行训练生成了自定义模型,您应将这些插件或模型文件存放于上述指定目录中,以便加载使用新的模型和插件。具体操作步骤如下:

  1. 服务部署成功后,单击目标服务的服务方式列下的查看Web应用

  2. WebUI界面中,您可以浏览并查看当前可用的模型文件和ComfyUI插件列表。

    • 对于ComfyUI默认工作流,您需要在相应节点查看该节点可用的模型文件,例如在Checkpoint加载器的下拉列表中查看当前可用的模型文件。

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

  3. 加载模型文件。

    1. 请将模型文件上传至挂载存储的models目录下的相应子目录中,具体操作,请参见步骤二:上传文件。请参考对应节点的开源项目库的使用说明,确定模型上传至哪个子目录。例如,对于Checkpoint加载器节点,对应的模型应上传至models/checkpoints

    2. WebUI页面中,单击刷新按钮,然后在Checkpoint加载器的下拉列表中查看模型文件是否加载成功。image

    3. 如果未加载成功,您需要单击Process Restart,以重新加载模型文件。image

      此过程将持续大约5分钟,在此期间服务会自动重启并恢复正常运行。重启完成后,您可以访问WebUI页面,以确认模型文件是否已成功加载。

  4. 加载ComfyUI插件,支持以下两种方式:

    • 自行上传并加载ComfyUI插件(推荐)。

      1. 请将ComfyUI第三方插件上传至挂载存储的custom_nodes目录。

      2. WebUI页面中,单击Process Restart

        此过程将持续大约5分钟,在此期间服务会自动重启并恢复正常运行。重启完成后,您可以访问WebUI页面,以确认插件是否已成功加载。

    • 在管理器中直接安装插件。由于需要从GitHub等平台拉取代码,有可能存在网络连接失败的问题。

      WebUI页面,单击管理器,然后在ComfyUI管理器对话框中安装节点。image

如何将WebUI页面的默认语言切换为中文?

  1. 模型在线服务(EAS)页面,单击目标服务的服务方式列下的查看Web应用,进入WebUI页面。

    说明

    访问WebUI时,大约需要1分钟的加载时间,之后您将能看到完整的初始工作流界面。

  2. WebUI页面,待工作流加载成功后,单击image按钮。image

  3. Settings对话框中,将AGLTranslation-langualge修改为中文[Chinese Simplified]image

    参数设置完成后,系统将自动切换至中文界面,大约需要1分钟的加载时间,之后您将能看到完整的初始工作流界面。