方案背景与架构设计
业务痛点
- 纯 ComfyUI 方案:节点堆叠严重,剧本文本导入、分镜循环、任务重试、素材分类归档没有原生能力,批量生产时管理成本极高;
- 纯上层云端方案:无法复用本地显卡,IPAdapter 角色参考图、ControlNet 固定姿态、图生图重绘容易出现角色漂移,云端 API 调用成本随产量线性上涨;
- 跨层对接痛点:上层输出的提示词、种子、参考图路径、重绘强度参数,和 ComfyUI 工作流节点输入格式不兼容,缺少标准化转换层;
- 显存瓶颈:8G 消费级显卡,连续批量渲染极易触发 OOM,缺少模型自动卸载、分片解码、任务限流的配套方案;
- 素材链路断裂:渲染完成后的图片没有自动回传给上层管理系统,分镜和剧本对不上,后续视频生成环节需要人工二次整理。
分层架构分工
- Loogen(上层管理层):业务控制层。读取漫剧剧本,按照镜头拆分成分镜任务;读取角色人设库,自动生成正向、反向提示词;维护任务队列、任务优先级、失败重试机制;记录每一条分镜对应的 task_id、角色编号、镜头描述;接收底层返回的图片,绑定元数据,存入素材库,后续可以下发视频生成任务;不执行 GPU 绘图运算。
- 适配中转层(Python 脚本):协议转换层。接收 Loogen 下发的 JSON 任务,映射参数到 ComfyUI 工作流节点;提交渲染任务、轮询任务状态、捕获显存溢出、超时异常;渲染完成后抓取图片,做简单压缩、格式统一,回调结果给 Loogen。
- ComfyUI(SD 底层渲染层,本地部署):算力执行层。加载 SD 基础大模型、VAE、ControlNet 权重、IPAdapter 权重;加载图生图工作流模板,接收传入的参考图、原图、提示词、种子、denoise 强度;执行采样、解码输出图片;支持 ModelUnloader 节点自动释放显存,适配 8G 显存。
- 数据流链路
Loogen剧本解析 → 生成分镜任务入队 → 中转脚本消费队列 → HTTP调用ComfyUI API → SD图生图/文生图渲染 → 图片输出 → 脚本回调结果至Loogen → Loogen归档素材,触发后续视频生成
横向对比补充:SD WebUI 也具备 API 能力,但自定义节点拓展、图生图 + IPAdapter 联动的灵活性弱于 ComfyUI;LocModel 是一体化方案,自带调度和绘图,适合不想分层搭建的用户;而 Loogen+ComfyUI 分层方案适合需要精细化管控剧本、大批量漫剧稳定生产的场景。
具体资源模型请看这一篇文章https://developer.aliyun.com/article/1772270?spm=a2c6h.13148508.setting.32.50d94f0eR9PlDH
ComfyUI 底层环境准备(供 Loogen 远程调用的渲染基座)
硬件与软件基线
硬件:NVIDIA 显卡,最低 8G 显存;推荐开启系统虚拟内存 16G 以上,作为显存溢出兜底; 驱动:CUDA 11.8 / 12.1,对应 torch 版本匹配; 软件:ComfyUI 源码版(推荐,整合包版本环境容易出现版本冲突); 必备节点插件:IPAdapter Plus、ControlNet Aux、ModelUnloader、Tiled VAE、Image Saver; 模型准备:SD 基础模型、对应风格 VAE、IPAdapter 角色模型、ControlNet OpenPose/LineArt 权重。
ComfyUI 启动配置(开放 API、显存优化)
进入 ComfyUI 根目录,使用下面命令启动,开启跨域、局域网访问、低显存模式:
python main.py --listen 0.0.0.0 --enable-cors-header --lowvram --gpu-memory 7.0 --comfyui-log-level info
参数说明:
--listen 0.0.0.0:允许局域网内 Loogen 服务、中转脚本访问 8188 端口;如果 Loogen 和 ComfyUI 在两台电脑,需要放行防火墙 8188 端口;--lowvram:显存分片调度,适合 8G 显卡;--gpu-memory 7.0:限制显卡占用上限 7GB,预留空间给系统、驱动;--enable-cors-header:解决跨域请求报错。
图生图工作流导入与改造
- 导入图生图工作流 JSON 模板;
- 手动调试:分别测试文生图分支、图生图分支,测试 IPAdapter 角色参考、ControlNet 姿态锁定;
- 固定节点 ID:把正向提示词、反向提示词、种子、分辨率、图生图原图、denoise 强度、IPAdapter 参考图、输出节点记录下来,后续脚本需要绑定节点 ID;
- 嵌入 ModelUnloader 节点:每张图渲染完成自动卸载模型,释放显存,避免连续批量任务显存持续上涨;
- 嵌入 Tiled VAE 节点:分块解码图片,大幅降低 VAE 解码瞬间显存峰值,是 8G 显存稳定跑竖屏 576×1024 画面的关键;
- 调试参数基线(漫剧 9:16 竖屏)
- denoise 重绘强度:角色保持优先 0.4~0.5;想要改动画面内容 0.5~0.65,不建议超过 0.7;
- IPAdapter 权重:0.65~0.78,权重太高容易覆盖画面构图;
- ControlNet 权重:0.6~0.75,姿态线稿锁定;
- 采样器 DPM++ 2M Karras,采样步数 18~22;
- 分辨率:576×1024,8G 显卡不建议直接跑 768×1344,极易 OOM。
调试完成后,导出最终的 workflow.json 模板,放到中转脚本目录,作为渲染模板。
中转适配层完整 Python 代码
该脚本实现:任务队列消费、参数映射、提交 ComfyUI 任务、轮询状态、异常捕获、图片本地归档、回调 Loogen、显存异常重试。
import requests import json import os import time import threading from queue import Queue from PIL import Image # ====================== 配置区,根据自己环境修改 ====================== COMFYUI_URL = "http://127.0.0.1:8188" WORKFLOW_PATH = "./sd_img2img_workflow.json" # 渲染图片输出目录 OUTPUT_DIR = "./comfy_render_output" # Loogen回调地址 LOOGEN_CALLBACK_BASE = "http://127.0.0.1:9000/task/callback" # 8G显卡限制并发为1,不要修改 MAX_WORKER = 1 # 任务超时时间 TASK_TIMEOUT = 300 # 模板内节点ID,和你工作流一一对应 NODE_POSITIVE = "3" NODE_NEGATIVE = "4" NODE_SEED = "7" NODE_SIZE = "10" NODE_IMG2IMG_SRC = "12" NODE_DENOISE = "13" NODE_IP_REF = "15" NODE_OUTPUT = "20" # ===================================================================== os.makedirs(OUTPUT_DIR, exist_ok=True) task_queue = Queue(maxsize=20) def load_workflow_template(): with open(WORKFLOW_PATH, "r", encoding="utf-8") as f: return json.load(f) def comfy_submit_prompt(prompt_data): res = requests.post(f"{COMFYUI_URL}/prompt", json={"prompt": prompt_data}, timeout=60) res.raise_for_status() return res.json() def comfy_query_history(prompt_id): r = requests.get(f"{COMFYUI_URL}/history/{prompt_id}", timeout=30) return r.json() def wait_task_finish(prompt_id): start_time = time.time() while time.time() - start_time < TASK_TIMEOUT: hist = comfy_query_history(prompt_id) if prompt_id in hist: return hist[prompt_id] time.sleep(2) raise TimeoutError("ComfyUI渲染任务超时") def save_and_compress_img(img_url, save_path): resp = requests.get(img_url, timeout=30) with open(save_path, "wb") as f: f.write(resp.content) # 简单压缩,降低体积,方便Loogen读取 im = Image.open(save_path) im.save(save_path, quality=85, optimize=True) return save_path def worker_loop(): """消费任务队列的工作线程""" while True: task = task_queue.get() try: workflow = load_workflow_template() # 填充提示词 workflow[NODE_POSITIVE]["inputs"]["text"] = task["positive"] workflow[NODE_NEGATIVE]["inputs"]["text"] = task["negative"] workflow[NODE_SEED]["inputs"]["seed"] = task["seed"] workflow[NODE_SIZE]["inputs"]["width"] = task["width"] workflow[NODE_SIZE]["inputs"]["height"] = task["height"] # 图生图参数 if task.get("img2img_enable", False): workflow[NODE_IMG2IMG_SRC]["inputs"]["image"] = task["img2img_source"] workflow[NODE_DENOISE]["inputs"]["denoise"] = task["denoise_strength"] # IPAdapter角色参考图 workflow[NODE_IP_REF]["inputs"]["image"] = task["ref_char_img"] # 提交任务 submit_ret = comfy_submit_prompt(workflow) prompt_id = submit_ret["prompt_id"] print(f"【{task['task_id']}】已提交ComfyUI,prompt_id:{prompt_id}") # 等待渲染完成 result = wait_task_finish(prompt_id) img_info = result["outputs"][NODE_OUTPUT]["images"][0] img_view_url = f"{COMFYUI_URL}/view/{img_info['filename']}" local_save = os.path.join(OUTPUT_DIR, f"{task['task_id']}_{img_info['filename']}") save_and_compress_img(img_view_url, local_save) # 回调Loogen上层 callback_data = { "task_id": task["task_id"], "status": "success", "comfy_prompt_id": prompt_id, "local_file": local_save, "img_url": img_view_url, "seed": task["seed"] } requests.post(LOOGEN_CALLBACK_BASE, json=callback_data, timeout=20) print(f"【{task['task_id']}】渲染完成,回调Loogen成功") except Exception as e: err_msg = str(e) print(f"【{task['task_id']}】任务失败:{err_msg}") # 失败回调,通知Loogen重试 callback_data = { "task_id": task["task_id"], "status": "failed", "error": err_msg } try: requests.post(LOOGEN_CALLBACK_BASE, json=callback_data, timeout=20) except: pass finally: task_queue.task_done() def add_task(task_data): """对外接口,Loogen调用这个方法加入任务""" task_queue.put(task_data) if __name__ == "__main__": # 启动工作线程 for _ in range(MAX_WORKER): t = threading.Thread(target=worker_loop, daemon=True) t.start() print("中转服务启动成功,监听任务队列,最大并发:", MAX_WORKER) # 模拟Loogen批量下发多条漫剧分镜任务 demo_task_list = [ { "task_id": "loogen_scene_001", "positive": "年轻女生,长发,居家卧室,暖光,漫剧手绘,9:16竖屏,柔和阴影", "negative": "畸形,多手指,扭曲人脸,水印,文字,模糊", "seed": 10001, "img2img_enable": True, "img2img_source": "./source_img/scene001_base.png", "denoise_strength": 0.45, "ref_char_img": "./ref/char_girl01.png", "width": 576, "height": 1024 }, { "task_id": "loogen_scene_002", "positive": "年轻女生站在窗边,看向窗外,傍晚,漫剧手绘,9:16竖屏", "negative": "畸形,多手指,扭曲人脸,水印,文字,模糊", "seed": 10002, "img2img_enable": True, "img2img_source": "./source_img/scene002_base.png", "denoise_strength": 0.42, "ref_char_img": "./ref/char_girl01.png", "width": 576, "height": 1024 } ] for tsk in demo_task_list: add_task(tsk) task_queue.join() print("所有模拟任务执行完毕")
配套辅助脚本:图片预处理 + FFmpeg 批量转码指令
Loogen 下发的参考图、原图尺寸不一,提前统一尺寸、去除透明通道,减少 ComfyUI 渲染异常。
# preprocess_img.py 图片预处理脚本 from PIL import Image import os def preprocess_image(input_path, out_path, target_w=576, target_h=1024): im = Image.open(input_path).convert("RGB") im = im.resize((target_w, target_h), Image.Resampling.LANCZOS) im.save(out_path, quality=90) print(f"预处理完成:{out_path}") if __name__ == "__main__": src_dir = "./source_raw" out_dir = "./source_img" os.makedirs(out_dir, exist_ok=True) for fname in os.listdir(src_dir): if fname.lower().endswith((".jpg", ".png", ".webp")): preprocess_image(os.path.join(src_dir, fname), os.path.join(out_dir, fname))
当 Loogen 收集完所有分镜图片后,调用 FFmpeg 合成漫剧片段,给图片加上停留时长:
# 生成文件列表,每帧图片停留2秒 ffmpeg -f concat -safe 0 -i filelist.txt -c:v libx264 -r 25 -pix_fmt yuv420p manju_scene.mp4
filelist.txt 示例:
file 'comfy_render_output/loogen_scene_001_xxx.png' duration 2 file 'comfy_render_output/loogen_scene_002_xxx.png' duration 2
显存优化与任务调度策略
分层架构下,8G 显卡的瓶颈不在上层 Loogen,而在 ComfyUI 渲染环节,必须强制执行下面规则:
- 并发硬限制:8G 显卡只允许 MAX_WORKER=1,串行渲染;12G 显卡可放宽至 2;
- 自动模型卸载:工作流末尾加入 ModelUnloader,每次渲染完成清空显存;
- Tiled VAE 强制开启,避免大图解码瞬间显存爆掉;
- Loogen 上层做任务持久化队列,脚本返回 failed 状态时,Loogen 自动重试 2 次,重试间隔拉长,避免短时间反复下压任务;
- 定期清理 ComfyUI 输出缓存目录,缓存图片堆积也会占用磁盘、间接影响调度;
- 不开启实时预览,预览会额外占用显存。
踩坑排错要点
- 连接 ComfyUI 超时:检查
--listen 0.0.0.0,确认防火墙放行 8188;跨机器部署时,不能传递本地磁盘路径,需要 Loogen 上传图片到静态服务,传入 http 图片地址; - IPAdapter 角色漂移:denoise 强度不要过高;Loogen 生成提示词时,固定角色描述词;参考图优先使用正面清晰半身照;
- 节点参数不生效:脚本内绑定的 NODE_ID 和你本地工作流节点 ID 不一致,打开 ComfyUI,开启节点 ID 显示,核对修改;
- 回调失败:Loogen 的回调接口需要可以被中转脚本网络访问,不要绑定 127.0.0.1;
- 随机 OOM:优先检查是否忘记加 ModelUnloader;其次降低分辨率、调低 denoise 强度;最后增大虚拟内存兜底。
FAQ
Q:Loogen 和 ComfyUI 分开部署在两台电脑,图片怎么互通?
A:搭建简易静态图片服务,Loogen 上传原图、角色参考图,生成 http 链接,把链接传给中转脚本,ComfyUI 可以远程加载网络图片,避免磁盘路径不互通。
Q:这套架构能不能同时支持文生图和图生图?
A:可以,工作流保留双分支,Loogen 下发参数里携带 img2img_enable 开关,脚本自动判断是否传入原图与 denoise 参数。
Q:8G 显存显卡,一天稳定可以跑多少张漫剧分镜?
A:单张 576×1024,采样 20 步,单张渲染大约 25~40 秒,串行渲染,除去异常重试,一天稳定产出 1500~2200 张左右,取决于模型大小。
Q:可以把这套流程扩展到图生视频吗?
A:可以。ComfyUI 增加 VideoHelperSuite、AnimateDiff 节点,Loogen 在图片渲染完成后,下发视频生成任务,同样走这套 API 对接逻辑,只是工作流模板替换为视频工作流。
Q:对比 LocModel 一体化方案,Loogen+ComfyUI 分层优势在哪?
A:LocModel 开箱即用,一体化调度绘图;Loogen+ComfyUI 分层方案自由度更高,剧本逻辑、分镜规则、重试策略、素材库可以完全自定义,适合需要长期量产、定制化漫剧业务的团队,缺点是部署和调试成本更高。
总结
Loogen 作为上层管理层,和 ComfyUI 本地 SD 绘图引擎组合,是私有化漫剧量产里非常成熟的解耦架构。Loogen 聚焦剧本业务、分镜编排、任务管理,ComfyUI 专注 GPU 渲染,依靠 IPAdapter、ControlNet 保证角色一致性,Tiled VAE、ModelUnloader 适配 8G 显存本地部署。