Wan是一个开源的AI视频生成大模型,支持T2V(文本生成视频)和I2V(图像生成视频)功能。在ComfyUI中,PAI提供了定制化的JSON工作流和API调用方式,帮助您使用Wan模型生成高质量的视频内容。本文将以I2V(图像生成视频)为例,介绍如何部署ComfyUI服务,并使用Wan生成视频。
部署标准版ComfyUI(单用户使用)
部署配置
使用自定义部署方式部署标准版ComfyUI镜像服务,具体操作步骤如下:
登录PAI控制台,在页面上方选择目标地域,并在右侧选择目标工作空间,然后单击进入EAS。
单击部署服务,然后在自定义模型部署区域,单击自定义部署。
在自定义部署页面,配置以下参数。
在环境信息区域,配置以下参数。
参数
描述
镜像配置
在官方镜像列表中选择镜像comfyui > comfyui:1.9。
说明其中1.9为镜像版本,由于版本迭代迅速,部署时镜像版本选择最高版本即可。
模型配置
为EAS服务挂载外部存储数据源(如OSS、NAS等),后续调用服务生成的视频,将自动保存到相应的数据源中。以对象存储OSS为例,单击OSS,并配置以下参数:
Uri:选择OSS Bucket源地址目录。关于如何创建存储空间和目录,请参见控制台快速入门。请确保创建的存储空间与EAS服务位于同一地域。
挂载路径:即挂载到服务实例中的目标路径。例如
/code/data-oss
。
运行命令
选择镜像后,系统会自动配置运行命令。
当您进行模型配置后,您需要在运行命令中设置
--data-dir
挂载目录,并确保挂载目录与模型配置中的挂载路径一致。对于1.9版本的镜像,
--data-dir
已预先配置,您只需将其更新为模型配置中的挂载路径即可。例如python main.py --listen --port 8000 --data-dir /code/data-oss
。在资源信息区域,选择资源规格。
参数
描述
资源类型
选择公共资源。
部署资源
选择资源规格。因视频生成相比图片生成需要更大的GPU显存,建议您选择单卡显存不低于48 GB的资源规格。推荐使用GU60机型(例如
ml.gu8is.c16m128.1-gu60
)。在服务接入区域,配置具有公网访问能力的专有网络,包括专有网络(VPC)、交换机和安全组,详情请参见配置公网连接。
说明EAS服务默认不通公网,因I2V(图像生成视频)功能需要连接公网下载图片,所以需为EAS配置具有公网访问能力的专有网络。
参数配置完成后,单击部署。
WebUI使用
服务部署成功后,您可以在WebUI页面构建工作流,完成服务调用。具体操作步骤如下:
单击服务方式列下的查看Web应用,进入WebUI页面。
在WebUI页面左上角,选择工作流 > 打开,选择JSON工作流文件,然后单击打开。
PAI已在ComfyUI中集成了各类加速算法,速度与效果较好的ComfyUI JSON工作流示例如下:
Image to Video(直接上传图片):wanvideo_720P_I2V.json
工作流加载成功后,您可以在WebUI页面的加载图像区域,通过单击选择文件上传,手动上传并更新图片文件。
Image to Video(通过图片URL加载图片):wanvideo_720P_I2V_URL.json
工作流加载成功后,您可以在WebUI页面的Load Image By URL区域,配置图片URL地址,以更新加载的图片。
单击页面下方的运行按钮,生成视频。
大约执行20分钟左右,结果会显示在右方的合并为视频区域中。
API同步调用
标准版服务仅支持同步调用,即直接请求推理实例,不使用EAS的队列服务。具体操作步骤如下:
导出工作流JSON文件。
ComfyUI的API请求体取决于工作流配置。您需要先在已部署的标准版服务的WebUI页面设置工作流,然后在WebUI页面左上角,选择工作流 > 导出(API),通过保存API格式获取工作流对应的JSON文件。
查看调用信息。
在服务列表中,单击服务名称,然后在基本信息区域,单击查看调用信息。
在调用信息对话框,获取访问地址和Token。
说明使用公网调用地址:调用客户端需支持访问公网。
使用VPC调用地址:调用客户端必须与服务位于同一个专有网络内。
调用服务。
完整调用和获取结果的代码示例如下,您可以在最终结果中通过
data[prompt_id]["outputs"]["fullpath"]
,获取输出图像的OSS完整路径。示例代码通过环境变量获取EAS服务访问地址和Token,您可以在终端中执行以下命令添加临时性环境变量(仅在当前会话中生效):
# 分别配置为服务访问地址和Token。 export SERVICE_URL="http://test****.115770327099****.cn-beijing.pai-eas.aliyuncs.com/" export TOKEN="MzJlMDNjMmU3YzQ0ZDJ*****************TMxZA=="
完整I2V调用代码
from time import sleep import os import json import requests service_url = os.getenv("SERVICE_URL") token = os.getenv("TOKEN") image_url = "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png" prompt = "一位金发女子,她仰头闭眼,表情宁静而梦幻。她的头发非常长且蓬松,呈现出自然的波浪状,仿佛被风吹拂。背景中有一些模糊的花朵飘落,营造出一种浪漫和梦幻的氛围。她穿着一件带有蕾丝装饰的上衣,衣服的颜色与背景相协调,整体色调柔和。光线从上方照射下来,照亮了她的脸庞和头发,使整个画面显得非常柔和和温暖。" negative_prompt = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" height = 720 width = 1280 steps = 40 num_frames = 81 if service_url[-1] == "/": service_url = service_url[:-1] prompt_url = f"{service_url}/prompt" # 请将payload中的prompt的值配置为工作流对应的JSON文件内容。 payload = """ { "prompt": { "11": { "inputs": { "model_name": "umt5-xxl-enc-bf16.safetensors", "precision": "bf16", "load_device": "offload_device", "quantization": "disabled" }, "class_type": "LoadWanVideoT5TextEncoder", "_meta": { "title": "Load WanVideo T5 TextEncoder" } }, "16": { "inputs": { "positive_prompt": "一位金发女子,她仰头闭眼,表情宁静而梦幻。她的头发非常长且蓬松,呈现出自然的波浪状,仿佛被风吹拂。背景中有一些模糊的花朵飘落,营造出一种浪漫和梦幻的氛围。她穿着一件带有蕾丝装饰的上衣,衣服的颜色与背景相协调,整体色调柔和。光线从上方照射下来,照亮了她的脸庞和头发,使整个画面显得非常柔和和温暖。", "negative_prompt": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", "force_offload": true, "speak_and_recognation": { "__value__": [ false, true ] }, "t5": [ "11", 0 ], "model_to_offload": [ "22", 0 ] }, "class_type": "WanVideoTextEncode", "_meta": { "title": "WanVideo TextEncode" } }, "22": { "inputs": { "model": "WanVideo/wan2.1_i2v_720p_14B_bf16.safetensors", "base_precision": "fp16", "quantization": "fp8_e4m3fn", "load_device": "offload_device", "attention_mode": "sageattn", "compile_args": [ "35", 0 ] }, "class_type": "WanVideoModelLoader", "_meta": { "title": "WanVideo Model Loader" } }, "27": { "inputs": { "steps": 40, "cfg": 6, "shift": 5, "seed": 1057359483639287, "force_offload": true, "scheduler": "unipc", "riflex_freq_index": 0, "denoise_strength": 1, "batched_cfg": "", "rope_function": "comfy", "nocfg_begin": 0.7500000000000001, "nocfg_end": 1, "model": [ "22", 0 ], "text_embeds": [ "16", 0 ], "image_embeds": [ "63", 0 ], "teacache_args": [ "52", 0 ] }, "class_type": "WanVideoSampler", "_meta": { "title": "WanVideo Sampler" } }, "28": { "inputs": { "enable_vae_tiling": false, "tile_x": 272, "tile_y": 272, "tile_stride_x": 144, "tile_stride_y": 128, "vae": [ "38", 0 ], "samples": [ "27", 0 ] }, "class_type": "WanVideoDecode", "_meta": { "title": "WanVideo Decode" } }, "30": { "inputs": { "frame_rate": 16, "loop_count": 0, "filename_prefix": "WanVideoWrapper_I2V", "format": "video/h264-mp4", "pix_fmt": "yuv420p", "crf": 19, "save_metadata": true, "trim_to_audio": false, "pingpong": false, "save_output": true, "images": [ "28", 0 ] }, "class_type": "VHS_VideoCombine", "_meta": { "title": "合并为视频" } }, "35": { "inputs": { "backend": "inductor", "fullgraph": false, "mode": "default", "dynamic": false, "dynamo_cache_size_limit": 64, "compile_transformer_blocks_only": true }, "class_type": "WanVideoTorchCompileSettings", "_meta": { "title": "WanVideo Torch Compile Settings" } }, "38": { "inputs": { "model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors", "precision": "bf16" }, "class_type": "WanVideoVAELoader", "_meta": { "title": "WanVideo VAE Loader" } }, "52": { "inputs": { "rel_l1_thresh": 0.25, "start_step": 1, "end_step": -1, "cache_device": "offload_device", "use_coefficients": "true" }, "class_type": "WanVideoTeaCache", "_meta": { "title": "WanVideo TeaCache" } }, "59": { "inputs": { "clip_name": "wanx_clip_vision_h.safetensors" }, "class_type": "CLIPVisionLoader", "_meta": { "title": "CLIP视觉加载器" } }, "63": { "inputs": { "width": [ "66", 1 ], "height": [ "66", 2 ], "num_frames": 81, "noise_aug_strength": 0.030000000000000006, "start_latent_strength": 1, "end_latent_strength": 1, "force_offload": true, "start_image": [ "66", 0 ], "vae": [ "38", 0 ], "clip_embeds": [ "65", 0 ] }, "class_type": "WanVideoImageToVideoEncode", "_meta": { "title": "WanVideo ImageToVideo Encode" } }, "65": { "inputs": { "strength_1": 1, "strength_2": 1, "crop": "center", "combine_embeds": "average", "force_offload": true, "tiles": 4, "ratio": 0.20000000000000004, "clip_vision": [ "59", 0 ], "image_1": [ "66", 0 ] }, "class_type": "WanVideoClipVisionEncode", "_meta": { "title": "WanVideo ClipVision Encode" } }, "66": { "inputs": { "width": 832, "height": 480, "upscale_method": "lanczos", "keep_proportion": false, "divisible_by": 16, "crop": "disabled", "image": [ "68", 0 ] }, "class_type": "ImageResizeKJ", "_meta": { "title": "图像缩放(KJ)" } }, "68": { "inputs": { "url": "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png", "cache": true }, "class_type": "LoadImageByUrl //Browser", "_meta": { "title": "Load Image By URL" } } } } """ session = requests.session() session.headers.update({"Authorization":token}) payload = json.loads(payload) payload["prompt"]["16"]["inputs"]["positive_prompt"] = prompt payload["prompt"]["16"]["inputs"]["negative_prompt"] = negative_prompt payload["prompt"]["27"]["inputs"]["steps"] = steps payload["prompt"]["66"]["inputs"]["height"] = height payload["prompt"]["66"]["inputs"]["width"] = width payload["prompt"]["63"]["inputs"]["num_frames"] = num_frames payload["prompt"]["68"]["inputs"]["url"] = image_url response = session.post(url=f'{prompt_url}', json=payload) if response.status_code != 200: raise Exception(response.content) data = response.json() prompt_id = data["prompt_id"] print(data) while 1: url = f"{service_url}/history/{prompt_id}" response = session.get(url=f'{url}') if response.status_code != 200: raise Exception(response.content) data = response.json() if len(data) != 0: print(data[prompt_id]["outputs"]) if len(data[prompt_id]["outputs"]) == 0: print("Find no outputs key in output json, the process may be failed, please check the log") break else: sleep(1)
分步解析上述代码
发送POST请求,从返回结果中获取Prompt ID。
import requests import os service_url = os.getenv("SERVICE_URL") token = os.getenv("TOKEN") url = f"{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)
其中payload需配置为请求体,prompt键值是上述导出(API)获得的请求JSON,在Python请求中,请求体中的布尔值(True和False)首字母需要大写。
首次请求的返回结果示例如下:
{ "prompt_id": "021ebc5b-e245-4e37-8bd3-00f7b949****", "number": 5, "node_errors": {} }
根据请求的Prompt ID获取最终预测结果。
import requests import os # 构造请求URL。 service_url = os.getenv("SERVICE_URL") token = os.getenv("TOKEN") url = f"{service_url}history/<prompt_id>" session = requests.session() session.headers.update({"Authorization":f'{token}'}) response = session.get(url=f'{url}') if response.status_code != 200: raise Exception(response.content) data = response.json() print(data)
其中<prompt_id>需替换为上一步中获取的prompt_id。
返回结果示例如下:
{ "130bcd6b-5bb5-496c-9c8c-3a1359a0****": { "prompt": ...省略, "outputs": { "30": { 'gifs': [ { 'filename': 'WanVideo2_1_T2V_00002.mp4', 'subfolder': '', 'type': 'output', 'format': 'video/h264-mp4', 'frame_rate': 16.0, 'workflow': 'WanVideo2_1_T2V_00002.png', 'fullpath': '/code/data-oss/output/WanVideo2_1_T2V_00002.mp4' } ] } }, "status": { "status_str": "success", "completed": true, "messages": ...省略, } } }
部署API版ComfyUI(高并发场景)
部署配置
如果您已创建标准版ComfyUI服务,希望将其改成API版本,建议删除原有服务后重新创建API版本。
使用自定义部署方式部署API版ComfyUI镜像服务,具体操作步骤如下:
登录PAI控制台,在页面上方选择目标地域,并在右侧选择目标工作空间,然后单击进入EAS。
单击部署服务,然后在自定义模型部署区域,单击自定义部署。
在自定义部署页面,配置以下参数。
在环境信息区域,配置以下参数。
参数
描述
镜像配置
在官方镜像列表中选择镜像comfyui > comfyui:1.9-api。
说明其中1.9为镜像版本,由于版本迭代迅速,部署时镜像版本选择最高版本即可。
模型配置
为EAS服务挂载外部存储数据源(如OSS、NAS等),后续调用服务生成的视频,将自动保存到相应的数据源中。以对象存储OSS为例,单击OSS,并配置以下参数:
Uri:选择OSS Bucket源地址目录。关于如何创建存储空间和目录,请参见控制台快速入门。请确保创建的存储空间与EAS服务位于同一地域。
挂载路径:即挂载到服务实例中的目标路径。例如
/code/data-oss
。
运行命令
选择镜像后,系统会自动配置运行命令。
当您进行模型配置后,您需要在运行命令中设置
--data-dir
挂载目录,并确保挂载目录与模型配置中的挂载路径一致。对于1.9版本的镜像,
--data-dir
已预先配置,您只需将其更新为模型配置中的挂载路径即可。例如python main.py --listen --port 8000 --api --data-dir /code/data-oss
。在资源信息区域,选择资源规格。
参数
描述
资源类型
选择公共资源。
部署资源
选择资源规格。因视频生成相比图片生成需要更大的GPU显存,建议您选择单卡显存不低于48 GB的资源规格。推荐使用GU60机型(例如
ml.gu8is.c16m128.1-gu60
)。在异步队列区域,配置单一输入请求最大数据和单一输出返回最大数据,标准值为1024 KB。
说明建议合理设置队列数据大小,以避免因超出限制而导致请求被拒绝、样本丢失、响应失败或队列阻塞等问题。
在服务接入区域,配置具有公网访问能力的专有网络,包括专有网络(VPC)、交换机和安全组,详情请参见配置公网连接。
说明EAS服务默认不通公网,因I2V(图像生成视频)功能需要连接公网下载图片,所以需为EAS配置具有公网访问能力的专有网络。
参数配置完成后,单击部署。
API异步调用
API版仅支持异步调用,且仅支持api_prompt路径。异步调用是指使用EAS的队列服务,向输入队列发送请求,以订阅的方式获得结果推送。具体操作步骤如下:
查看调用信息。
单击API版服务的服务方式列下的调用信息,在调用信息对话框的异步调用页签,查看服务访问地址和Token。
说明使用公网调用地址:调用客户端需支持访问公网。
使用VPC调用地址:调用客户端必须与服务位于同一个专有网络内。
在终端中执行安装eas_prediction SDK命令。
pip install eas_prediction --user
调用服务。
完整调用的代码示例如下,您可以在最终结果中通过
json.loads(x.data.decode('utf-8'))[1]["data"]["output"]["gifs"][0]["fullpath"]
,获取输出图像的OSS完整路径。示例代码通过环境变量获取EAS服务访问地址和Token,您可以在终端中执行以下命令添加临时性环境变量(仅在当前会话中生效):
# 分别配置为服务访问地址和Token。 export SERVICE_URL="http://test****.115770327099****.cn-beijing.pai-eas.aliyuncs.com/" export TOKEN="MzJlMDNjMmU3YzQ0ZDJ*****************TMxZA=="
完整I2V调用代码
import json import os import requests from urllib.parse import urlparse, urlunparse from eas_prediction import QueueClient service_url = os.getenv("SERVICE_URL") token = os.getenv("TOKEN") image_url = "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png" prompt = "一位金发女子,她仰头闭眼,表情宁静而梦幻。她的头发非常长且蓬松,呈现出自然的波浪状,仿佛被风吹拂。背景中有一些模糊的花朵飘落,营造出一种浪漫和梦幻的氛围。她穿着一件带有蕾丝装饰的上衣,衣服的颜色与背景相协调,整体色调柔和。光线从上方照射下来,照亮了她的脸庞和头发,使整个画面显得非常柔和和温暖。" negative_prompt = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" height = 720 width = 1280 steps = 40 num_frames = 81 if service_url[-1] == "/": service_url = service_url[:-1] 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}.") # 请将payload配置为工作流对应的JSON文件内容。 payload = """ { "11": { "inputs": { "model_name": "umt5-xxl-enc-bf16.safetensors", "precision": "bf16", "load_device": "offload_device", "quantization": "disabled" }, "class_type": "LoadWanVideoT5TextEncoder", "_meta": { "title": "Load WanVideo T5 TextEncoder" } }, "16": { "inputs": { "positive_prompt": "一位金发女子,她仰头闭眼,表情宁静而梦幻。她的头发非常长且蓬松,呈现出自然的波浪状,仿佛被风吹拂。背景中有一些模糊的花朵飘落,营造出一种浪漫和梦幻的氛围。她穿着一件带有蕾丝装饰的上衣,衣服的颜色与背景相协调,整体色调柔和。光线从上方照射下来,照亮了她的脸庞和头发,使整个画面显得非常柔和和温暖。", "negative_prompt": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", "force_offload": true, "speak_and_recognation": { "__value__": [ false, true ] }, "t5": [ "11", 0 ], "model_to_offload": [ "22", 0 ] }, "class_type": "WanVideoTextEncode", "_meta": { "title": "WanVideo TextEncode" } }, "22": { "inputs": { "model": "WanVideo/wan2.1_i2v_720p_14B_bf16.safetensors", "base_precision": "fp16", "quantization": "fp8_e4m3fn", "load_device": "offload_device", "attention_mode": "sageattn", "compile_args": [ "35", 0 ] }, "class_type": "WanVideoModelLoader", "_meta": { "title": "WanVideo Model Loader" } }, "27": { "inputs": { "steps": 40, "cfg": 6, "shift": 5, "seed": 1057359483639287, "force_offload": true, "scheduler": "unipc", "riflex_freq_index": 0, "denoise_strength": 1, "batched_cfg": "", "rope_function": "comfy", "nocfg_begin": 0.7500000000000001, "nocfg_end": 1, "model": [ "22", 0 ], "text_embeds": [ "16", 0 ], "image_embeds": [ "63", 0 ], "teacache_args": [ "52", 0 ] }, "class_type": "WanVideoSampler", "_meta": { "title": "WanVideo Sampler" } }, "28": { "inputs": { "enable_vae_tiling": false, "tile_x": 272, "tile_y": 272, "tile_stride_x": 144, "tile_stride_y": 128, "vae": [ "38", 0 ], "samples": [ "27", 0 ] }, "class_type": "WanVideoDecode", "_meta": { "title": "WanVideo Decode" } }, "30": { "inputs": { "frame_rate": 16, "loop_count": 0, "filename_prefix": "WanVideoWrapper_I2V", "format": "video/h264-mp4", "pix_fmt": "yuv420p", "crf": 19, "save_metadata": true, "trim_to_audio": false, "pingpong": false, "save_output": true, "images": [ "28", 0 ] }, "class_type": "VHS_VideoCombine", "_meta": { "title": "合并为视频" } }, "35": { "inputs": { "backend": "inductor", "fullgraph": false, "mode": "default", "dynamic": false, "dynamo_cache_size_limit": 64, "compile_transformer_blocks_only": true }, "class_type": "WanVideoTorchCompileSettings", "_meta": { "title": "WanVideo Torch Compile Settings" } }, "38": { "inputs": { "model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors", "precision": "bf16" }, "class_type": "WanVideoVAELoader", "_meta": { "title": "WanVideo VAE Loader" } }, "52": { "inputs": { "rel_l1_thresh": 0.25, "start_step": 1, "end_step": -1, "cache_device": "offload_device", "use_coefficients": "true" }, "class_type": "WanVideoTeaCache", "_meta": { "title": "WanVideo TeaCache" } }, "59": { "inputs": { "clip_name": "wanx_clip_vision_h.safetensors" }, "class_type": "CLIPVisionLoader", "_meta": { "title": "CLIP视觉加载器" } }, "63": { "inputs": { "width": [ "66", 1 ], "height": [ "66", 2 ], "num_frames": 81, "noise_aug_strength": 0.030000000000000006, "start_latent_strength": 1, "end_latent_strength": 1, "force_offload": true, "start_image": [ "66", 0 ], "vae": [ "38", 0 ], "clip_embeds": [ "65", 0 ] }, "class_type": "WanVideoImageToVideoEncode", "_meta": { "title": "WanVideo ImageToVideo Encode" } }, "65": { "inputs": { "strength_1": 1, "strength_2": 1, "crop": "center", "combine_embeds": "average", "force_offload": true, "tiles": 4, "ratio": 0.20000000000000004, "clip_vision": [ "59", 0 ], "image_1": [ "66", 0 ] }, "class_type": "WanVideoClipVisionEncode", "_meta": { "title": "WanVideo ClipVision Encode" } }, "66": { "inputs": { "width": 832, "height": 480, "upscale_method": "lanczos", "keep_proportion": false, "divisible_by": 16, "crop": "disabled", "image": [ "68", 0 ] }, "class_type": "ImageResizeKJ", "_meta": { "title": "图像缩放(KJ)" } }, "68": { "inputs": { "url": "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png", "cache": true }, "class_type": "LoadImageByUrl //Browser", "_meta": { "title": "Load Image By URL" } } } """ session = requests.session() session.headers.update({"Authorization":token}) payload = json.loads(payload) payload["16"]["inputs"]["positive_prompt"] = prompt payload["16"]["inputs"]["negative_prompt"] = negative_prompt payload["27"]["inputs"]["steps"] = steps payload["66"]["inputs"]["height"] = height payload["66"]["inputs"]["width"] = width payload["63"]["inputs"]["num_frames"] = num_frames payload["68"]["inputs"]["url"] = image_url response = session.post(url=f'{service_url}/api_prompt?task_id=txt2img', json=payload) if response.status_code != 200: raise Exception(response.content) data = response.json() sink_queue = QueueClient(service_domain, f'{service_name}/sink') sink_queue.set_token(token) sink_queue.init() watcher = sink_queue.watch(0, 1, 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}') print(json.loads(x.data.decode('utf-8'))[1]["data"]["output"]["gifs"][0]["fullpath"]) sink_queue.commit(x.index)
分步解析上述代码
发送请求,代码示例如下:
import requests, io, base64, os from PIL import Image, PngImagePlugin url = os.getenv("SERVICE_URL") token = os.getenv("TOKEN") session = requests.session() session.headers.update({"Authorization": token}) work_flow = { '3': ...省略 } # 与标准版不同的是没有prompt key for i in range(1): 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}")
其中work_flow需配置为请求体,即上述导出(API)获得的请求JSON,在Python请求中,请求体中的布尔值(True和False)首字母需要大写。
订阅结果,代码示例如下:
from eas_prediction import QueueClient import os token = os.getenv("TOKEN") sink_queue = QueueClient('<service_domain>', '<service_name>/sink') sink_queue.set_token(token) sink_queue.init() watcher = sink_queue.watch(0, 1, 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>
请替换为已查询的服务访问地址中的调用信息。例如
139699392458****.cn-hangzhou.pai-eas.aliyuncs.com
。<service_name>
请替换为EAS服务名称。
返回结果示例如下:
index 2 task_id is txt2img index 2 data is b'[{"status_code": 200}, {"type": "executed", "data": {"node": "30", "display_node": "30", "output": {"gifs": [{"filename": "WanVideoWrapper_I2V_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4", "frame_rate": 16.0, "workflow": "WanVideoWrapper_I2V_00001.png", "fullpath": "/code/data-oss/output/WanVideoWrapper_I2V_00001.mp4"}]}, "prompt_id": "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c"}}, {"e20b1cb0-fb48-4ddd-92e5-3c783b064a2c": {"prompt": [1, "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c", {"11": {"inputs": {"model_name": "umt5-xxl-enc-bf16.safetensors", "precision": "bf16", "load_device": "offload_device", "quantization": "disabled"}, "class_type": "LoadWanVideoT5TextEncoder", "_meta": {"title": "Load WanVideo T5 TextEncoder"}}, "16": {"inputs": {"positive_prompt": "\\u4e00\\u4f4d\\u91d1\\u53d1\\u5973\\u5b50\\uff0c\\u5979\\u4ef0\\u5934\\u95ed\\u773c\\uff0c\\u8868\\u60c5\\u5b81\\u9759\\u800c\\u68a6\\u5e7b\\u3002\\u5979\\u7684\\u5934\\u53d1\\u975e\\u5e38\\u957f\\u4e14\\u84ec\\u677e\\uff0c\\u5448\\u73b0\\u51fa\\u81ea\\u7136\\u7684\\u6ce2\\u6d6a\\u72b6\\uff0c\\u4eff\\u4f5b\\u88ab\\u98ce\\u5439\\u62c2\\u3002\\u80cc\\u666f\\u4e2d\\u6709\\u4e00\\u4e9b\\u6a21\\u7cca\\u7684\\u82b1\\u6735\\u98d8\\u843d\\uff0c\\u8425\\u9020\\u51fa\\u4e00\\u79cd\\u6d6a\\u6f2b\\u548c\\u68a6\\u5e7b\\u7684\\u6c1b\\u56f4\\u3002\\u5979\\u7a7f\\u7740\\u4e00\\u4ef6\\u5e26\\u6709\\u857e\\u4e1d\\u88c5\\u9970\\u7684\\u4e0a\\u8863\\uff0c\\u8863\\u670d\\u7684\\u989c\\u8272\\u4e0e\\u80cc\\u666f\\u76f8\\u534f\\u8c03\\uff0c\\u6574\\u4f53\\u8272\\u8c03\\u67d4\\u548c\\u3002\\u5149\\u7ebf\\u4ece\\u4e0a\\u65b9\\u7167\\u5c04\\u4e0b\\u6765\\uff0c\\u7167\\u4eae\\u4e86\\u5979\\u7684\\u8138\\u5e9e\\u548c\\u5934\\u53d1\\uff0c\\u4f7f\\u6574\\u4e2a\\u753b\\u9762\\u663e\\u5f97\\u975e\\u5e38\\u67d4\\u548c\\u548c\\u6e29\\u6696\\u3002", "negative_prompt": "\\u8272\\u8c03\\u8273\\u4e3d\\uff0c\\u8fc7\\u66dd\\uff0c\\u9759\\u6001\\uff0c\\u7ec6\\u8282\\u6a21\\u7cca\\u4e0d\\u6e05\\uff0c\\u5b57\\u5e55\\uff0c\\u98ce\\u683c\\uff0c\\u4f5c\\u54c1\\uff0c\\u753b\\u4f5c\\uff0c\\u753b\\u9762\\uff0c\\u9759\\u6b62\\uff0c\\u6574\\u4f53\\u53d1\\u7070\\uff0c\\u6700\\u5dee\\u8d28\\u91cf\\uff0c\\u4f4e\\u8d28\\u91cf\\uff0cJPEG\\u538b\\u7f29\\u6b8b\\u7559\\uff0c\\u4e11\\u964b\\u7684\\uff0c\\u6b8b\\u7f3a\\u7684\\uff0c\\u591a\\u4f59\\u7684\\u624b\\u6307\\uff0c\\u753b\\u5f97\\u4e0d\\u597d\\u7684\\u624b\\u90e8\\uff0c\\u753b\\u5f97\\u4e0d\\u597d\\u7684\\u8138\\u90e8\\uff0c\\u7578\\u5f62\\u7684\\uff0c\\u6bc1\\u5bb9\\u7684\\uff0c\\u5f62\\u6001\\u7578\\u5f62\\u7684\\u80a2\\u4f53\\uff0c\\u624b\\u6307\\u878d\\u5408\\uff0c\\u9759\\u6b62\\u4e0d\\u52a8\\u7684\\u753b\\u9762\\uff0c\\u6742\\u4e71\\u7684\\u80cc\\u666f\\uff0c\\u4e09\\u6761\\u817f\\uff0c\\u80cc\\u666f\\u4eba\\u5f88\\u591a\\uff0c\\u5012\\u7740\\u8d70", "force_offload": true, "speak_and_recognation": {"__value__": [false, true]}, "t5": ["11", 0], "model_to_offload": ["22", 0]}, "class_type": "WanVideoTextEncode", "_meta": {"title": "WanVideo TextEncode"}}, "22": {"inputs": {"model": "WanVideo/wan2.1_i2v_720p_14B_bf16.safetensors", "base_precision": "fp16", "quantization": "fp8_e4m3fn", "load_device": "offload_device", "attention_mode": "sageattn", "compile_args": ["35", 0]}, "class_type": "WanVideoModelLoader", "_meta": {"title": "WanVideo Model Loader"}}, "27": {"inputs": {"steps": 40, "cfg": 6.0, "shift": 5.0, "seed": 1057359483639287, "force_offload": true, "scheduler": "unipc", "riflex_freq_index": 0, "denoise_strength": 1.0, "batched_cfg": false, "rope_function": "comfy", "nocfg_begin": 0.7500000000000001, "nocfg_end": 1.0, "model": ["22", 0], "text_embeds": ["16", 0], "image_embeds": ["63", 0], "teacache_args": ["52", 0]}, "class_type": "WanVideoSampler", "_meta": {"title": "WanVideo Sampler"}}, "28": {"inputs": {"enable_vae_tiling": false, "tile_x": 272, "tile_y": 272, "tile_stride_x": 144, "tile_stride_y": 128, "vae": ["38", 0], "samples": ["27", 0]}, "class_type": "WanVideoDecode", "_meta": {"title": "WanVideo Decode"}}, "30": {"inputs": {"frame_rate": 16.0, "loop_count": 0, "filename_prefix": "WanVideoWrapper_I2V", "format": "video/h264-mp4", "pix_fmt": "yuv420p", "crf": 19, "save_metadata": true, "trim_to_audio": false, "pingpong": false, "save_output": true, "images": ["28", 0]}, "class_type": "VHS_VideoCombine", "_meta": {"title": "\\u5408\\u5e76\\u4e3a\\u89c6\\u9891"}}, "35": {"inputs": {"backend": "inductor", "fullgraph": false, "mode": "default", "dynamic": false, "dynamo_cache_size_limit": 64, "compile_transformer_blocks_only": true}, "class_type": "WanVideoTorchCompileSettings", "_meta": {"title": "WanVideo Torch Compile Settings"}}, "38": {"inputs": {"model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors", "precision": "bf16"}, "class_type": "WanVideoVAELoader", "_meta": {"title": "WanVideo VAE Loader"}}, "52": {"inputs": {"rel_l1_thresh": 0.25, "start_step": 1, "end_step": -1, "cache_device": "offload_device", "use_coefficients": true}, "class_type": "WanVideoTeaCache", "_meta": {"title": "WanVideo TeaCache"}}, "59": {"inputs": {"clip_name": "wanx_clip_vision_h.safetensors"}, "class_type": "CLIPVisionLoader", "_meta": {"title": "CLIP\\u89c6\\u89c9\\u52a0\\u8f7d\\u5668"}}, "63": {"inputs": {"width": ["66", 1], "height": ["66", 2], "num_frames": 81, "noise_aug_strength": 0.030000000000000006, "start_latent_strength": 1.0, "end_latent_strength": 1.0, "force_offload": true, "start_image": ["66", 0], "vae": ["38", 0], "clip_embeds": ["65", 0]}, "class_type": "WanVideoImageToVideoEncode", "_meta": {"title": "WanVideo ImageToVideo Encode"}}, "65": {"inputs": {"strength_1": 1.0, "strength_2": 1.0, "crop": "center", "combine_embeds": "average", "force_offload": true, "tiles": 4, "ratio": 0.20000000000000004, "clip_vision": ["59", 0], "image_1": ["66", 0]}, "class_type": "WanVideoClipVisionEncode", "_meta": {"title": "WanVideo ClipVision Encode"}}, "66": {"inputs": {"width": 1280, "height": 720, "upscale_method": "lanczos", "keep_proportion": false, "divisible_by": 16, "crop": "disabled", "image": ["68", 0]}, "class_type": "ImageResizeKJ", "_meta": {"title": "\\u56fe\\u50cf\\u7f29\\u653e\\uff08KJ\\uff09"}}, "68": {"inputs": {"url": "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png", "cache": true}, "class_type": "LoadImageByUrl //Browser", "_meta": {"title": "Load Image By URL"}}}, {"client_id": "unknown"}, ["30"]], "outputs": {"30": {"gifs": [{"filename": "WanVideoWrapper_I2V_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4", "frame_rate": 16.0, "workflow": "WanVideoWrapper_I2V_00001.png", "fullpath": "/code/data-oss/output/WanVideoWrapper_I2V_00001.mp4"}]}}, "status": {"status_str": "success", "completed": true, "messages": [["execution_start", {"prompt_id": "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c", "timestamp": 1746512702895}], ["execution_cached", {"nodes": ["11", "16", "22", "27", "28", "30", "35", "38", "52", "59", "63", "65", "66", "68"], "prompt_id": "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c", "timestamp": 1746512702899}], ["execution_success", {"prompt_id": "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c", "timestamp": 1746512702900}]]}, "meta": {"30": {"node_id": "30", "display_node": "30", "parent_node": null, "real_node_id": "30"}}}}, {"30": {"gifs": [{"filename": "WanVideoWrapper_I2V_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4", "frame_rate": 16.0, "workflow": "WanVideoWrapper_I2V_00001.png", "fullpath": "/code/data-oss/output/WanVideoWrapper_I2V_00001.mp4"}]}}]'
附录:更多示例
T2V(文本生成视频)和I2V(图像生成视频)的使用流程一致,参考上述步骤部署和调用服务即可。但T2V不依赖公网连接功能,因此在部署EAS服务时,无需配置专有网络。
您可以通过示例工作流文件(wanvideo_720P_T2V.json)体验WebUI调用流程。参考WebUI使用,在WebUI页面加载工作流,然后在WanVideo TextEncode输入框中输入文本提示词,并单击运行,则会开始执行流程。如果需要通过API调用,完整的代码示例如下:
示例代码通过环境变量获取EAS服务访问地址和Token,您可以在终端中执行以下命令添加临时性环境变量(仅在当前会话中生效):
# 分别配置为服务访问地址和Token。
export SERVICE_URL="http://test****.115770327099****.cn-beijing.pai-eas.aliyuncs.com/"
export TOKEN="MzJlMDNjMmU3YzQ0ZDJ*****************TMxZA=="
API同步调用
完整T2V调用代码
from time import sleep
import os
import json
import requests
service_url = os.getenv("SERVICE_URL")
token = os.getenv("TOKEN")
prompt = "一位金发女子,她仰头闭眼,表情宁静而梦幻。她的头发非常长且蓬松,呈现出自然的波浪状,仿佛被风吹拂。背景中有一些模糊的花朵飘落,营造出一种浪漫和梦幻的氛围。她穿着一件带有蕾丝装饰的上衣,衣服的颜色与背景相协调,整体色调柔和。光线从上方照射下来,照亮了她的脸庞和头发,使整个画面显得非常柔和和温暖。"
negative_prompt = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
height = 720
width = 1280
steps = 40
num_frames = 81
if service_url[-1] == "/":
service_url = service_url[:-1]
prompt_url = f"{service_url}/prompt"
# 请将payload中的prompt的值配置为工作流对应的JSON文件内容。
payload = """
{
"prompt":
{
"11": {
"inputs": {
"model_name": "umt5-xxl-enc-bf16.safetensors",
"precision": "bf16",
"load_device": "offload_device",
"quantization": "disabled"
},
"class_type": "LoadWanVideoT5TextEncoder",
"_meta": {
"title": "Load WanVideo T5 TextEncoder"
}
},
"16": {
"inputs": {
"positive_prompt": "一位金发女子,她仰头闭眼,表情宁静而梦幻。她的头发非常长且蓬松,呈现出自然的波浪状,仿佛被风吹拂。背景中有一些模糊的花朵飘落,营造出一种浪漫和梦幻的氛围。她穿着一件带有蕾丝装饰的上衣,衣服的颜色与背景相协调,整体色调柔和。光线从上方照射下来,照亮了她的脸庞和头发,使整个画面显得非常柔和和温暖。",
"negative_prompt": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
"force_offload": true,
"speak_and_recognation": {
"__value__": [
false,
true
]
},
"t5": [
"11",
0
]
},
"class_type": "WanVideoTextEncode",
"_meta": {
"title": "WanVideo TextEncode"
}
},
"22": {
"inputs": {
"model": "WanVideo/wan2.1_t2v_14B_bf16.safetensors",
"base_precision": "fp16",
"quantization": "fp8_e4m3fn",
"load_device": "offload_device",
"attention_mode": "sageattn",
"compile_args": [
"35",
0
]
},
"class_type": "WanVideoModelLoader",
"_meta": {
"title": "WanVideo Model Loader"
}
},
"27": {
"inputs": {
"steps": 40,
"cfg": 6.000000000000002,
"shift": 5.000000000000001,
"seed": 1057359483639287,
"force_offload": true,
"scheduler": "unipc",
"riflex_freq_index": 0,
"denoise_strength": 1,
"batched_cfg": false,
"rope_function": "default",
"nocfg_begin": 0.7500000000000001,
"nocfg_end": 1,
"model": [
"22",
0
],
"text_embeds": [
"16",
0
],
"image_embeds": [
"37",
0
],
"teacache_args": [
"52",
0
]
},
"class_type": "WanVideoSampler",
"_meta": {
"title": "WanVideo Sampler"
}
},
"28": {
"inputs": {
"enable_vae_tiling": true,
"tile_x": 272,
"tile_y": 272,
"tile_stride_x": 144,
"tile_stride_y": 128,
"vae": [
"38",
0
],
"samples": [
"27",
0
]
},
"class_type": "WanVideoDecode",
"_meta": {
"title": "WanVideo Decode"
}
},
"30": {
"inputs": {
"frame_rate": 16,
"loop_count": 0,
"filename_prefix": "WanVideo2_1_T2V",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 19,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"28",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "合并为视频"
}
},
"35": {
"inputs": {
"backend": "inductor",
"fullgraph": false,
"mode": "default",
"dynamic": false,
"dynamo_cache_size_limit": 64,
"compile_transformer_blocks_only": true
},
"class_type": "WanVideoTorchCompileSettings",
"_meta": {
"title": "WanVideo Torch Compile Settings"
}
},
"37": {
"inputs": {
"width": 832,
"height": 480,
"num_frames": 81
},
"class_type": "WanVideoEmptyEmbeds",
"_meta": {
"title": "WanVideo Empty Embeds"
}
},
"38": {
"inputs": {
"model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors",
"precision": "bf16"
},
"class_type": "WanVideoVAELoader",
"_meta": {
"title": "WanVideo VAE Loader"
}
},
"52": {
"inputs": {
"rel_l1_thresh": 0.25000000000000006,
"start_step": 1,
"end_step": -1,
"cache_device": "offload_device",
"use_coefficients": "true"
},
"class_type": "WanVideoTeaCache",
"_meta": {
"title": "WanVideo TeaCache"
}
}
}
}
"""
session = requests.session()
session.headers.update({"Authorization":token})
payload = json.loads(payload)
payload["prompt"]["16"]["inputs"]["positive_prompt"] = prompt
payload["prompt"]["16"]["inputs"]["negative_prompt"] = negative_prompt
payload["prompt"]["27"]["inputs"]["steps"] = steps
payload["prompt"]["37"]["inputs"]["height"] = height
payload["prompt"]["37"]["inputs"]["width"] = width
payload["prompt"]["37"]["inputs"]["num_frames"] = num_frames
response = session.post(url=f'{prompt_url}', json=payload)
if response.status_code != 200:
raise Exception(response.content)
data = response.json()
prompt_id = data["prompt_id"]
print(data)
while 1:
url = f"{service_url}/history/{prompt_id}"
response = session.get(url=f'{url}')
if response.status_code != 200:
raise Exception(response.content)
data = response.json()
if len(data) != 0:
print(data[prompt_id]["outputs"])
if len(data[prompt_id]["outputs"]) == 0:
print("Find no outputs key in output json, the process may be failed, please check the log")
break
else:
sleep(1)
API异步调用
完整T2V调用代码
import json
import os
import requests
from urllib.parse import urlparse, urlunparse
from eas_prediction import QueueClient
service_url = os.getenv("SERVICE_URL")
token = os.getenv("TOKEN")
prompt = "一位金发女子,她仰头闭眼,表情宁静而梦幻。她的头发非常长且蓬松,呈现出自然的波浪状,仿佛被风吹拂。背景中有一些模糊的花朵飘落,营造出一种浪漫和梦幻的氛围。她穿着一件带有蕾丝装饰的上衣,衣服的颜色与背景相协调,整体色调柔和。光线从上方照射下来,照亮了她的脸庞和头发,使整个画面显得非常柔和和温暖。"
negative_prompt = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
height = 720
width = 1280
steps = 40
num_frames = 81
if service_url[-1] == "/":
service_url = service_url[:-1]
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}.")
# 请将payload配置为工作流对应的JSON文件内容。
payload = """
{
"11": {
"inputs": {
"model_name": "umt5-xxl-enc-bf16.safetensors",
"precision": "bf16",
"load_device": "offload_device",
"quantization": "disabled"
},
"class_type": "LoadWanVideoT5TextEncoder",
"_meta": {
"title": "Load WanVideo T5 TextEncoder"
}
},
"16": {
"inputs": {
"positive_prompt": "一位金发女子,她仰头闭眼,表情宁静而梦幻。她的头发非常长且蓬松,呈现出自然的波浪状,仿佛被风吹拂。背景中有一些模糊的花朵飘落,营造出一种浪漫和梦幻的氛围。她穿着一件带有蕾丝装饰的上衣,衣服的颜色与背景相协调,整体色调柔和。光线从上方照射下来,照亮了她的脸庞和头发,使整个画面显得非常柔和和温暖。",
"negative_prompt": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
"force_offload": true,
"speak_and_recognation": {
"__value__": [
false,
true
]
},
"t5": [
"11",
0
]
},
"class_type": "WanVideoTextEncode",
"_meta": {
"title": "WanVideo TextEncode"
}
},
"22": {
"inputs": {
"model": "WanVideo/wan2.1_t2v_14B_bf16.safetensors",
"base_precision": "fp16",
"quantization": "fp8_e4m3fn",
"load_device": "offload_device",
"attention_mode": "sageattn",
"compile_args": [
"35",
0
]
},
"class_type": "WanVideoModelLoader",
"_meta": {
"title": "WanVideo Model Loader"
}
},
"27": {
"inputs": {
"steps": 40,
"cfg": 6.000000000000002,
"shift": 5.000000000000001,
"seed": 1057359483639287,
"force_offload": true,
"scheduler": "unipc",
"riflex_freq_index": 0,
"denoise_strength": 1,
"batched_cfg": false,
"rope_function": "default",
"nocfg_begin": 0.7500000000000001,
"nocfg_end": 1,
"model": [
"22",
0
],
"text_embeds": [
"16",
0
],
"image_embeds": [
"37",
0
],
"teacache_args": [
"52",
0
]
},
"class_type": "WanVideoSampler",
"_meta": {
"title": "WanVideo Sampler"
}
},
"28": {
"inputs": {
"enable_vae_tiling": true,
"tile_x": 272,
"tile_y": 272,
"tile_stride_x": 144,
"tile_stride_y": 128,
"vae": [
"38",
0
],
"samples": [
"27",
0
]
},
"class_type": "WanVideoDecode",
"_meta": {
"title": "WanVideo Decode"
}
},
"30": {
"inputs": {
"frame_rate": 16,
"loop_count": 0,
"filename_prefix": "WanVideo2_1_T2V",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 19,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"28",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "合并为视频"
}
},
"35": {
"inputs": {
"backend": "inductor",
"fullgraph": false,
"mode": "default",
"dynamic": false,
"dynamo_cache_size_limit": 64,
"compile_transformer_blocks_only": true
},
"class_type": "WanVideoTorchCompileSettings",
"_meta": {
"title": "WanVideo Torch Compile Settings"
}
},
"37": {
"inputs": {
"width": 832,
"height": 480,
"num_frames": 81
},
"class_type": "WanVideoEmptyEmbeds",
"_meta": {
"title": "WanVideo Empty Embeds"
}
},
"38": {
"inputs": {
"model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors",
"precision": "bf16"
},
"class_type": "WanVideoVAELoader",
"_meta": {
"title": "WanVideo VAE Loader"
}
},
"52": {
"inputs": {
"rel_l1_thresh": 0.25000000000000006,
"start_step": 1,
"end_step": -1,
"cache_device": "offload_device",
"use_coefficients": "true"
},
"class_type": "WanVideoTeaCache",
"_meta": {
"title": "WanVideo TeaCache"
}
}
}
"""
session = requests.session()
session.headers.update({"Authorization":token})
payload = json.loads(payload)
payload["16"]["inputs"]["positive_prompt"] = prompt
payload["16"]["inputs"]["negative_prompt"] = negative_prompt
payload["27"]["inputs"]["steps"] = steps
payload["37"]["inputs"]["height"] = height
payload["37"]["inputs"]["width"] = width
payload["37"]["inputs"]["num_frames"] = num_frames
response = session.post(url=f'{service_url}/api_prompt?task_id=txt2img', json=payload)
if response.status_code != 200:
raise Exception(response.content)
data = response.json()
sink_queue = QueueClient(service_domain, f'{service_name}/sink')
sink_queue.set_token(token)
sink_queue.init()
watcher = sink_queue.watch(0, 1, 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}')
print(json.loads(x.data.decode('utf-8'))[1]["data"]["output"]["gifs"][0]["fullpath"])
sink_queue.commit(x.index)
相关文档
如需更全面的了解ComfyUI的部署与功能使用,包括加载自定义模型、集成ComfyUI插件,以及常见问题等内容,请参阅AI视频生成-ComfyUI部署。