本地部署 AI 漫剧异步任务调度系统:WebSocket 长连接、任务队列与光流时序校验工程实现

简介: 本文提出基于aiohttp+Redis的异步调度框架,解决ComfyUI串行API在AI漫剧本地生产中的任务阻塞、状态不可知、崩溃丢失上下文等痛点;集成WebSocket实时监听、优先级队列、光流法帧间一致性质检与GPU资源保护,支持8G/12G显卡全自动批量渲染。

摘要

在大规模 AI 漫剧本地生产场景中,串行调用 ComfyUI API 存在明显缺陷:任务阻塞、无法实时获取节点执行状态、任务崩溃丢失上下文、批量渲染时无法动态管控 GPU 资源。本文设计一套基于aiohttp+Redis 的异步调度框架,通过 ComfyUI WebSocket 长连接实时监听节点执行事件,实现任务优先级排队、失败重试、任务状态持久化;引入光流法做帧间运动一致性检测,在渲染阶段提前识别画面抖动与角色漂移。整套系统完全本地部署,不依赖第三方云端 API,适配 8G/12G 消费级显卡,适合长序列 AI 漫剧分镜自动化生产。附带异步客户端、任务队列、光流质量评估、异常自动重试完整代码。

资源,模型可以看这一篇文章:https://developer.aliyun.com/article/1756481?spm=a2c6h.12873639.article-detail.5.571a435eUakA13

1 系统架构概述

整体四层架构:

  1. 任务接入层:接收漫剧分镜任务,支持配置优先级、最大重试次数、分辨率、种子、参考图路径;
  2. 任务队列层:Redis 有序集合实现优先级队列,持久化任务状态(pending/running/success/failed);
  3. ComfyUI 通信层:WebSocket 长连接监听工作流节点执行事件,HTTP API 提交 prompt;
  4. 质量校验层:光流计算帧间运动幅值,自动判定画面闪烁,不合格任务自动触发重渲染。

传统同步 API 方式只能轮询查询任务结果,本方案通过 WebSocket 推送事件,任务状态实时回调,可精准捕获节点 OOM、采样失败、VAE 解码异常等细粒度错误。

2 前置环境准备指令

# 安装异步网络、redis、图像处理、光流依赖
pip install aiohttp redis opencv-python numpy torch pynvml
# Windows启动redis服务(本地缓存任务)
redis-server.exe redis.windows.conf

3 ComfyUI WebSocket 异步客户端完整代码

import aiohttp
import asyncio
import json
import uuid
class ComfyAsyncClient:
    def __init__(self, host="127.0.0.1", port=8188):
        self.host = host
        self.port = port
        self.ws_url = f"ws://{host}:{port}/ws"
        self.http_url = f"http://{host}:{port}/prompt"
        self.client_id = str(uuid.uuid4())
        self.ws = None
    async def connect(self):
        session = aiohttp.ClientSession()
        self.ws = await session.ws_connect(f"{self.ws_url}?clientId={self.client_id}")
        print("WebSocket长连接已建立,监听ComfyUI事件")
        return session
    async def submit_workflow(self, workflow_json):
        payload = {
            "prompt": workflow_json,
            "client_id": self.client_id
        }
        async with aiohttp.ClientSession() as session:
            async with session.post(self.http_url, json=payload) as resp:
                res = await resp.json()
                prompt_id = res["prompt_id"]
                return prompt_id
    async def listen_event(self, callback):
        """持续监听节点执行事件,执行完成触发回调函数"""
        async for msg in self.ws:
            if msg.type == aiohttp.WSMsgType.TEXT:
                data = json.loads(msg.data)
                msg_type = data.get("type")
                if msg_type == "executing":
                    node_info = data["data"]
                    await callback(node_info)
                # 全部节点执行完毕
                if msg_type == "executing" and data["data"]["node"] is None:
                    await callback({"status": "finished"})
                    break
            elif msg.type == aiohttp.WSMsgType.ERROR:
                print("WebSocket连接异常")
                break
async def event_callback(info):
    print(f"节点事件回调: {info}")
async def main():
    comfy_client = ComfyAsyncClient()
    session = await comfy_client.connect()
    # 加载本地工作流JSON
    with open("comic_workflow.json", "r", encoding="utf-8") as f:
        wf = json.load(f)
    pid = await comfy_client.submit_workflow(wf)
    print(f"任务已提交,prompt_id={pid}")
    await comfy_client.listen_event(event_callback)
    await session.close()
if __name__ == "__main__":
    asyncio.run(main())

4 Redis 优先级任务队列(漫剧批量任务管理)

import redis
import time
class ComicTaskQueue:
    def __init__(self):
        self.r = redis.Redis(host="127.0.0.1", port=6379, db=0, decode_responses=True)
        self.queue_key = "comic_drama_task_queue"
        self.task_info_key = "comic_task_detail"
    def add_task(self, task_data, priority=5):
        """priority值越小优先级越高,紧急分镜优先渲染"""
        task_id = str(uuid.uuid4())
        self.r.hset(self.task_info_key, task_id, json.dumps({
            "data": task_data,
            "status": "pending",
            "retry":0,
            "max_retry":2,
            "create_time":time.time()
        }))
        # zadd 有序集合,score=优先级
        self.r.zadd(self.queue_key, {task_id: priority})
        print(f"任务 {task_id} 入队成功,优先级:{priority}")
        return task_id
    def fetch_task(self):
        # 取出分数最小的一条任务
        res = self.r.zpopmin(self.queue_key, count=1)
        if not res:
            return None
        task_id, priority = res[0]
        raw = self.r.hget(self.task_info_key, task_id)
        task = json.loads(raw)
        task["status"] = "running"
        self.r.hset(self.task_info_key, task_id, json.dumps(task))
        return task_id, task
    def mark_status(self, task_id, status):
        task_raw = self.r.hget(self.task_info_key, task_id)
        task = json.loads(task_raw)
        task["status"] = status
        self.r.hset(self.task_info_key, task_id, json.dumps(task))
# 入队示例
if __name__ == "__main__":
    q = ComicTaskQueue()
    scene_task = {
        "prompt":"漫剧分镜,少年人物,室内对话,柔和漫风",
        "neg_prompt":"扭曲,变形,闪烁,多手指",
        "seed":987654,
        "ref_img":"./ref_char.png"
    }
    q.add_task(scene_task, priority=3)

5 光流法帧间时序一致性检测代码(质量自动校验)

漫剧最核心指标:相邻帧光流幅值,如果幅值突变,代表人物发生漂移、画面闪烁,自动判定渲染失败,触发重试。

import cv2
import numpy as np
def calc_optical_flow_score(prev_img_path, curr_img_path):
    """
    返回运动幅值均值,数值越大代表帧间变化剧烈
    漫剧推荐阈值:大于12判定为异常闪烁
    """
    prev = cv2.imread(prev_img_path)
    curr = cv2.imread(curr_img_path)
    gray_prev = cv2.cvtColor(prev, cv2.COLOR_BGR2GRAY)
    gray_curr = cv2.cvtColor(curr, cv2.COLOR_BGR2GRAY)
    flow = cv2.calcOpticalFlowFarneback(
        gray_prev, gray_curr, None,
        pyr_scale=0.5,
        levels=3,
        winsize=15,
        iterations=3,
        poly_n=5,
        poly_sigma=1.2,
        flags=0
    )
    mag, ang = cv2.cartToPolar(flow[...,0], flow[...,1])
    mean_magnitude = np.mean(mag)
    return mean_magnitude
def check_scene_frames(frame_list, threshold=12):
    bad_frames = []
    for i in range(1, len(frame_list)):
        score = calc_optical_flow_score(frame_list[i-1], frame_list[i])
        print(f"帧{i-1}→帧{i} 光流分数:{score:.2f}")
        if score > threshold:
            bad_frames.append(i)
    return bad_frames
if __name__ == "__main__":
    frames = ["./out/frame_001.png","./out/frame_002.png","./out/frame_003.png"]
    bad = check_scene_frames(frames)
    if len(bad) > 0:
        print(f"检测到闪烁异常帧索引:{bad},任务标记为重试")
    else:
        print("帧间时序校验通过")

6 GPU 资源保护 + 任务失败自动重试调度主逻辑

import pynvml
pynvml.nvmlInit()
gpu_handle = pynvml.nvmlDeviceGetHandleByIndex(0)
def get_gpu_free_vram_gb():
    mem_info = pynvml.nvmlDeviceGetMemoryInfo(gpu_handle)
    free_gb = mem_info.free / (1024**3)
    return free_gb
async def task_worker():
    queue = ComicTaskQueue()
    client = ComfyAsyncClient()
    session = await client.connect()
    while True:
        task_item = queue.fetch_task()
        if task_item is None:
            await asyncio.sleep(1)
            continue
        tid, task = task_item
        free_vram = get_gpu_free_vram_gb()
        if free_vram < 2.0:
            print("显存不足,任务重新放回队列,等待空闲")
            queue.r.zadd(queue.queue_key, {tid:5})
            await asyncio.sleep(3)
            continue
        try:
            # 提交工作流,监听WebSocket事件
            wf_data = task["data"]
            workflow = build_workflow_from_task(wf_data)
            prompt_id = await client.submit_workflow(workflow)
            await client.listen_event(event_callback)
            # 渲染完成后执行光流校验
            frame_paths = get_output_frames(prompt_id)
            bad_list = check_scene_frames(frame_paths)
            if len(bad_list) > 0:
                raise Exception("帧时序校验失败,画面闪烁")
            queue.mark_status(tid, "success")
            print(f"任务{tid}渲染+校验全部通过")
        except Exception as e:
            print(f"任务异常 {e}")
            task["retry"] += 1
            if task["retry"] < task["max_retry"]:
                # 重试,优先级降低
                queue.r.hset(queue.task_info_key, tid, json.dumps(task))
                queue.r.zadd(queue.queue_key, {tid: priority+1})
            else:
                queue.mark_status(tid, "failed")
    await session.close()
def build_workflow_from_task(task_data):
    with open("comic_workflow.json","r",encoding="utf-8") as f:
        wf = json.load(f)
    wf["3"]["inputs"]["seed"] = task_data["seed"]
    wf["6"]["inputs"]["text"] = task_data["prompt"]
    wf["7"]["inputs"]["text"] = task_data["neg_prompt"]
    wf["10"]["inputs"]["image"] = task_data["ref_img"]
    return wf
def get_output_frames(prompt_id):
    # 读取ComfyUI输出目录图片路径
    import os
    out_dir = "./comic_out"
    files = sorted([os.path.join(out_dir,f) for f in os.listdir(out_dir) if f.endswith(".png")])
    return files

7 视频合成 + 批量音频对齐 FFmpeg 指令

# 图片序列生成视频,加入光流插值平滑
ffmpeg -framerate 20 -pattern_type glob -i './comic_out/*.png' \
-filter:v "minterpolate=fps=24:mi_mode=2" \
-c:v libx264 -crf 19 comic_scene_smooth.mp4
# 批量合并TTS配音音频
ffmpeg -i comic_scene_smooth.mp4 -i dub_audio.wav -c:v copy -c:a aac comic_final_scene.mp4
# 批量压缩漫剧成片,降低码率便于预览
ffmpeg -i comic_final_scene.mp4 -b:v 1200k comic_preview.mp4

8 工程优化要点与坑点解析

  1. WebSocket 断线重连:生产环境需要增加断线检测,断开时自动重建连接,避免长时间任务监听中断;
  2. 显存水位动态调度:设置显存阈值,当剩余显存低于阈值,停止消费型任务入队,防止 OOM;
  3. 光流参数调优:漫剧人物动作幅度小,阈值不能设置过高;动态镜头需要动态调整阈值,防止误判;
  4. 任务隔离:不同角色场景分开调度,避免 IPAdapter 特征缓存互相污染;
  5. 日志持久化:每个任务单独输出日志,记录每帧光流分数、显存曲线,方便回溯画面闪烁问题。

9 总结

本文构建的本地部署 AI 漫剧异步调度系统,解决传统同步 API 批量渲染中状态不可感知、任务丢失、画质无法自动化质检的痛点。基于 WebSocket 实时监听 ComfyUI 节点事件,Redis 实现任务排队与持久化,光流算法自动检测帧间闪烁,整套系统实现漫剧渲染无人值守批量生产。该方案可以直接部署在消费级显卡,适用于长篇 AI 漫剧分镜流水线,将人工校验画面闪烁的大量工作交给程序自动完成,显著提升本地 AI 漫剧生产效率。

相关文章
|
21天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9078 29
|
20天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
4210 16
|
20天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2340 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
7天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
6天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
500 1
|
14天前
|
人工智能 Linux 开发者
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
Codex是OpenAI推出的AI编程智能体,可读取本地项目、理解需求并自动修改代码。支持桌面GUI、命令行(CLI)及VS Code/Cursor插件三种形态,覆盖可视化操作、终端高效开发与编辑器无缝集成场景,助开发者用自然语言驱动编码全流程。(239字)
1105 2
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
|
8天前
|
人工智能 Linux Windows
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面
千问办公(QwenWork)是阿里云推出的AI智能办公平台,支持网页端直接使用及Windows/Mac/Linux客户端下载。提供PPT生成、财报分析、网页搭建等AI功能,个人版免费,企业版198元/席/月。详情见官网qwenwork.cn或阿里云产品页。
1062 0
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面