2026-09-29-AICG3D长时批量生成-经验与教训.md

AICG3D 长时批量生成 · 经验与教训

来源:2026-09-28 ~ 09-29,《一直很安静》竖屏 MV 项目(45 镜 AI 生成 + 1 镜 ffmpeg 尾卡,连续运行约 24 小时) 任务量:45 个分镜,单镜约 28 分钟,总产出含重跑 记录人:DSH 会话(含用户多次指正) 结论一句话:这次真正的坑只有两个——一个是我"优化"出来的(模型不卸载),一个是我写错的(提示词注入 bug);其余都是参考图卫生与提示词写法问题。


〇、最关键的 5 条(如果只读这一段)

#教训代价
1不要"优化"工作流的内存管理。把 cleanup_after_run 从工作流原值「卸载模型」改成「不处理」,导致 35GB 模型在 30GB 内存机器上跨镜累积,当天整机硬挂 4 次4 次硬挂 + 数小时重跑
2生成的提示词必须与实际输入对账。build_prompt 把「场景描述文本」当成「场景图列表」用,每个提示词被注入 25~60 条引用不存在图片的 <Picture N> 声明三人镜连续失效,排查数轮
3参考图必须"净化"到单一主体、无文字。整张三视图→拼贴;带「背面/BACK」标签的裁图→标签渗进画面2 镜重跑
4负面提示词会反向诱导。写「不要悬浮的人脸」→ 脸变得更大;写「不要拼贴」→ 更容易拼贴1 镜白跑 2 轮
5长跑必须配自愈监督脚本(探活→取证→拉起→断点续跑),而不是人盯着否则 6 小时 timeout 后无人接管

一、机器稳定性:最贵的教训

1.1 事件经过

当天 GPU 机(RTX 5060 Ti 16GB / 30GB RAM)硬挂 4 次:02:26、05:04、11:33、约 16:10。症状均为:ping 100% 丢包、SSH No route to host、ARP INCOMPLETE,且不触发 kernel.panic 自动重启,需人工断电。

1.2 根因:我自己改的一个参数

AICG3D 工作流(AICG3D-13-渲染器二采高清放大-可用版.json)里,两个渲染节点的 cleanup_after_run 原值是 「卸载模型」。

我为了"避免每镜重新加载模型、提速",把它改成了 「不处理」。后果:

该工作流要加载:UNET 20GB + 文本编码器 15GB = 35GB
宿主机内存:30GB
「不处理」→ 模型跨镜持续驻留不释放 → 内存压力累积 → 整机冻结

时间线完全吻合:

03:49-04:17  S01(用「卸载模型」)        → ✅ 成功
04:17        我改成「不处理」             ← 错误决定
04:18-04:50  S02                          → 勉强通过
04:50-05:04  S03                          → ❌ 硬挂(第 2 次)
11:29-11:33  S03 重跑                     → ❌ 硬挂(第 3 次,仅 3 分钟)
(改回「卸载模型」后)
12:16-次日   连续 24 小时+ / 45 镜        → ✅ 零崩溃,内存压力告警 0

1.3 排查中走过的弯路

第 3 次硬挂后我一度断言:"内存压力是元凶,加了 --disable-pinned-memory 就能稳定跑完"。这个结论被证伪——第 3 次硬挂恰恰发生在该参数已生效、内存压力告警为 0 的情况下。

教训:不要把单次相关性当因果。当时唯一抓到的内核异常是内存压力,但真正的原因(模型不卸载)我还没意识到。正确说法应是"内存压力是一个可疑因素,但不足以解释全部"。

1.4 崩溃取证(这次第一次拿到现场)

运维记录里长期写着"这台机器没有崩溃捕获能力"。这个结论是错的:/var/log/journal 是持久化的,journalctl --list-boots 能列出历史启动,journalctl -b -1 能读上一次启动的完整内核日志。

关键做法:在机器恢复的"第一时间"抓(错过就被新启动的日志冲淡)。已封装为 ~/.tools/capture_after_recovery.sh,抓 9 项:

1. uptime / who / last -x reboot(判断自动重启还是人工断电)
2. 上一次启动的完整内核日志 journalctl -k -b -1
3. 上次启动的 Xid|nvrm|gsp|fell off|pcie|aer|oom|hung_task|BUG:|panic
4. 当前启动的内核错误
5. journald 是否持久化(-b -1 是否可用)
6. nvidia-smi -q 的 PCIe 代际/降速、Retired/Remapped 页、温度、功耗上限
7. NVreg_DynamicPowerManagement 与 kernel.panic* 设置
8. ComfyUI 日志尾部(死亡时的最后动作)
9. ram_guard 痕迹与日志轮转情况

实际抓到的东西(第二次硬挂):

04:19:31 systemd-journald: Under memory pressure, flushing caches.
04:56:30 systemd-journald: Under memory pressure, flushing caches.
05:04:39 ← 日志戛然而止:无 panic、无 Xid、无 OOM、无正常关机

「日志直接断掉」= 内核冻结,这就是"硬挂"的定义。

1.5 ComfyUI 启动参数的取舍

cd /home/zyw/ComfyUI && setsid nohup env \
  PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512,expandable_segments:True \
  CUDA_VISIBLE_DEVICES=0 OMP_NUM_THREADS=4 \
  venv/bin/python main.py --listen 0.0.0.0 --port 8189 \
  --lowvram --force-fp16 --reserve-vram 1.0 --fast-disk \
  --cache-lru 2 --disable-pinned-memory --preview-method none \
  > /home/zyw/comfyui_8189_opt.log 2>&1 < /dev/null &

二、提示词生成的代码缺陷(影响全部 45 镜)

2.1 缺陷本体

# 调用点
chars, scene, sdesc, motion, sound = SHOTS[sid]
prompt = build_prompt(sid, chars, sdesc, motion, sound)
#                                ^^^^^ 第 3 个位置传的是「场景描述文本」
def build_prompt(sid, chars, scene, motion, sound):
    ...
    for j in range(len(scene)):        # scene 这里是字符串,len() = 文本字数
        defs.append(f"<Picture {len(chars)+j+1}> 是 [镜头1] 的环境与光照依据。")

2.2 后果

场景描述有多少字,就往提示词里塞多少条「环境依据」图片声明。实测每个提示词被注入 25~60 条引用不存在的图片,而实际只传了 2~4 张图。

模型的反应:把参考图当作独立元素去拼贴,而不是组合进同一个场景。表现为三人镜「某个角色整个消失」或「三人被生硬拼贴、比例悬殊」。

2.3 修法

def build_prompt(sid, chars, scene_imgs, sdesc, motion, sound):
    """scene_imgs = 场景图路径列表;sdesc = 场景描述文本(两者必须分开)"""

验证方法(应固化为习惯):生成提示词后,数一遍 <Picture N> 声明的条数,必须等于实际传入的图片数。

# 自检
decl = [l for l in prompt.split('\n') if l.startswith('<Picture')]
assert len([d for d in decl if '环境依据' in d]) == len(scene_imgs)

三、参考图卫生(模型会照抄素材里的一切)

3.1 三类事故

事故素材问题现象
拼贴用整张三视图(含正面/侧面/背面)作参考画面出现「大幅正面像里嵌着缩小背影」
标签渗出裁三视图时把「背面 / BACK」中英文标签一起裁进去成片画面上方出现「背面 / BACK」文字
主体冲突用正面参考图,但镜头要求「走离、只给背影」天空出现巨大悬浮人脸

3.2 规律

参考图里的任何文字、标注、多余主体,模型都可能原样复制到成片里。 参考图的视角必须与镜头的机位一致。

3.3 处置规范

  1. 一人一图,只保留单一主体:从三视图裁出「单人正面全身」「单人侧面全身」「单人背面全身」分别存档
    人物参考/正面单图/<角色>_front.jpg
    人物参考/侧面单图/<角色>_side.jpg     ← 记得裁掉顶部 8% 的「侧面/SIDE」标签
    人物参考/背影单图/<角色>_back.jpg     ← 同样裁掉「背面/BACK」标签
  2. 按镜头机位选图:
    • 正面/中景 → 正面图
    • 走离、背对镜头 → 不要用正面图(会给天空补脸)。改用侧面图 + 把镜头改成「正侧面横向跟拍」,给模型一个合法的角度呈现身份
    • 转身走离 → 背面图,并在提示词里留出「侧后方、带到侧脸轮廓」的出口
  3. 裁图后必须人眼验收:确认无标签、无邻图残影、无印章

四、提示词写法(负面句会反向诱导)

4.1 实测证据

我写的结果
「只给背影」+ 加「不要出现悬浮的人脸、人像叠影、或天空中的面孔」浮脸变得更大更清晰
「画面中只出现这一个实例,不要出现第二个身影、不要拼贴、不要镜像」拼贴概率似乎上升
「画面只见纸张的背面」(想规避文字)模型仍渲染纸张正面并生成乱码汉字

4.2 规范

4.3 某些词必然生成文字,只能绕开或后期处理

「告示 / 符文 / 牌匾 / 匾额」→ 模型一定生成汉字,且几乎都是乱码。

三种处置(按优先级):

  1. 取用无文字的时间段:S08 前 1 秒红纸有乱码,1.2 秒后纸被风吹落、柱面干净 → 合成时从 1.2s 起截取
  2. 后期叠加真实文字(ffmpeg drawtext)——符合原定原则「文字后期叠加,不让图像模型生成」
  3. 接受:发光符咒中的伪文字在仙侠题材里属可接受表现手法(本片 S25 塔壁符文、S36 横幅小字即按此保留)

五、多角色镜头(≥3 个主体)

5.1 症状

三人 + 场景(4 张参考图)时,模型会把每个 <Subject> 当成要"展示"的独立元素拼贴,而不是组合进同一场景。

5.2 三处修法(组合使用)

  1. 修掉 §2 的注入 bug(这是主因)
  2. 把场景图排到 <Picture 1>,人物参考依次排后 —— 先给场景锚点
    def scene_first(sid, chars, scene):
        return len(chars) >= 3 and len(scene) >= 1

    注意:subject_definitions / retention_analysis 里的 <Picture N> 编号与 <Subject N> 编号要同步重排

  3. 在 detailed_description 里显式点名每个人物的位置
    画面中三个人物同时出现在同一镜头里:
    <Subject 2>(少年剑客)与 <Subject 3>(蓝衣少女)并肩走在画面前景、身体微微相靠;
    <Subject 1>(紫衣少女)走在他们身后数步、位于画面左侧的中景处,处于浅景深之外、略微虚焦。
    三人的身高比例一致。

实测:只做 1+2 就能让 S16/S25 通过;S12/S13 加上 3 后也通过。

5.3 角色列表必须与动作设计一致

S19 的动作设计只描述了狐妖女,但我误把林月如也放进了 chars → 林月如喧宾夺主,狐妖女被挡。

镜头里不该出现的人,绝不能放进 chars。

建议固化为审计(本次就是这么抓出来的):

for sid in SHOT_ORDER:
    chars, scene, sd, mo, so = SHOTS[sid]
    hit = []
    if '紫衣少女' in mo: hit.append('L')
    if '少年' in mo or '剑客' in mo: hit.append('X')
    if '蓝衣少女' in mo: hit.append('Z')
    if chars and not hit: print(f'{sid} ⚠️ 传了角色但动作里没提到')
    if not chars and hit: print(f'{sid} ⚠️ 没传角色但动作里提到了')

(注意:用「三人」「两名主角」这类泛称时会误报,需人工复核)


六、工程化:让长跑不需要人盯

6.1 自愈监督脚本

~/.tools/mv_aicg3d_supervisor.sh 的循环:

每 60s 一轮:
  探活 ping
    ├─ 不可达 → 标记 WAS_DOWN=1,等 60s 重试
    └─ 可达
         ├─ WAS_DOWN=1 → **第一时间跑 capture_after_recovery.sh 取证**
         ├─ ComfyUI 未就绪 → 自动拉起(含正确参数)
         └─ 断点续跑 mv_aicg3d.py

本次实测有效两次:

05:03:21  批次被 6h timeout 杀掉(rc=124)
05:04:21  监督脚本第 93 轮:已完成 19/45 → 自动重启批次
05:04:22  按顺序跳过已完成,补跑 S15

11:04:21  批次被 6h timeout 杀掉(rc=124)
11:05:21  监督脚本第 94 轮 → 自动重启
11:05:22  补跑 S24(用去标签版参考图)

17:05     同样机制补跑 S34

配合 timeout 21600(6 小时)使用:批次定期被杀重启,既避免单次运行过长,又自动捡起所有缺失镜头——这成了本次的自愈核心。

6.2 断点续传

启动时先 ssh 列一遍 GPU 机产出目录,已有的直接下载跳过,不重复提交:

def scan_remote_existing():
    """扫 GPU 机 output/video 里已有的 AICG3D_Pass2_<镜号>_*.mp4,返回 {镜号: 文件名}"""
    ...

注意:若要重跑某镜,必须同时删除 ① 本机产出 ② 运行日志里的记录 ③ GPU 机上的产出文件 —— 否则续传扫描会把它重新下载回来、跳过重跑。

6.3 产出下载的两个坑

坑 1:AICG3D 的 SaveVideo 在 /history 里把结果放在 images 键,不是 videos/gifs:

for key in ("images", "videos", "gifs", "animated"):
    for v in (out.get(key) or []):
        if isinstance(v, dict) and v.get("filename"):
            files.append(v)

坑 2:等待上限太小会漏文件。S24 生成超过 40 分钟,超出 wait_and_fetch(pid, timeout_min=40),批次记成 FAIL 1files,只下载了 Pass1、Pass2 留在 GPU 上。

修法:上限提到 90 分钟;并校验下载文件数是否等于预期的 SaveVideo 节点数(本工作流是 2)。

6.4 重跑单镜的正确姿势

1. 确认 ComfyUI 队列为空(等间隔期,不要在采样中途动手)
2. 移走本机产出到 问题记录/
3. 从 运行日志.tsv 删掉该镜记录
4. 删掉 GPU 机上的产出文件
5. kill 批次进程(监督脚本会在下一轮自动重启并补跑该镜)
   ⚠️ 一定按 PID kill,不要用 pkill 模式

不要清空 ComfyUI 队列——那会强制 35GB 模型重新加载,制造内存/IO 尖峰(这正是第 3 次硬挂的可疑诱因)。


七、操作纪律:我在本次犯的错

#错误后果正确做法
1为"提速"改 cleanup_after_run4 次硬挂不要动工作流已调好的内存管理参数
2过早宣布根因("内存压力+缺 pinned 参数")结论被第 3 次硬挂证伪,需公开更正区分"相关"与"因果";标注置信度
3为换参考图清空 ComfyUI 队列强制模型重载,疑为硬挂诱因改动一律等批次间隔期
4pkill -f "mv_aicg3d_superviso[r].sh"匹配到自己的命令行、把自己杀了,新旧监督脚本一起死按 PID kill;或确保模式不出现在自己的命令行里
5一开始只挂了一个 SaveVideo用户要求"一采也要落盘"后才补先对齐工作流的完整节点结构(工作流有 2 个 SaveVideo)
6为修缺陷多次重启批次拖长了单镜完成时间先攒够问题,再一次性处理
7漏了 --disable-pinned-memory前两次硬挂时该参数缺失启动参数对照运维记录的修复项逐条核对

八、可复用的参数与命令

8.1 生成参数(本次实测可用)

项值
工作流AICG3D-13-渲染器二采高清放大-可用版.json
模式reference(REF2VA)
分辨率768P / 9:16 / 768×1344 → Pass2 ×1.2 → 928×1664
帧率24 fps
时长5 秒
Pass1res_multistep + simple,3 步,denoise 1.0
Pass2res_multistep + simple,8 步,second_pass_denoise 0.5
潜空间放大minimax_h3_latent_upscaler_3d_fp16.safetensors,upscale_scale 1.2
cleanup_after_run卸载模型(两个节点都要)
LoRAminimax/taomate_h3_3step_comfy.safetensors,strength 1.0
模型UNET minimax_h3_{fl2va,ref2va}_pruned_int8_convrot / 文本编码器 qwen3vl_32b_minimax_h3_nvfp4_awq
单镜耗时约 28 分钟

8.2 关键 API 端点

POST /prompt          提交工作流(API 图)
GET  /history/<pid>   查结果 → outputs[<node>].images[] ← **注意是 images**
POST /upload/image    上传参考图
GET  /view?filename=&subfolder=&type=output   下载产出
POST /interrupt       中断当前任务
POST /queue {"clear":true}   清空队列(⚠️ 慎用,见 §6.4)
GET  /system_stats    探活
GET  /object_info/<节点>  查节点输入规格

8.3 合成(ffmpeg)

# 逐镜:统一 1080x1920 + 按目标时长裁切(S08 从 1.2s 起)
ffmpeg -y -nostdin -i "$f" \
  -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,fps=30,\
trim=start=${off},setpts=PTS-STARTPTS,tpad=stop_mode=clone:stop_duration=20" \
  -t "$dur" -an -c:v libx264 -crf 16 -preset veryfast -pix_fmt yuv420p "$out"

# 拼接
ffmpeg -y -f concat -safe 0 -i concat.txt -c copy v_noa.mp4
# 混音乐
ffmpeg -y -i v_noa.mp4 -i "$SONG" -map 0:v:0 -map 1:a:0 -c:v copy -c:a aac -b:a 192k -shortest v_music.mp4
# 烧字幕
ffmpeg -y -i v_music.mp4 -vf "subtitles=字幕.srt:force_style='FontName=Source Han Serif SC,\
FontSize=17,PrimaryColour=&H00FFFFFF,OutlineColour=&H80000000,BorderStyle=1,Outline=2,Shadow=1,MarginV=150'" \
  -c:v libx264 -crf 17 -preset medium -c:a copy 成片.mp4

⚠️ 循环里的 ffmpeg 必须加 < /dev/null(或 -nostdin),否则它会吃掉 while read 的 stdin,导致循环只处理前几条。


九、方法论反思

9.1 校验优先于生成

本次所有"跑很多轮才修好"的事故,根源都是没有在下游对账上游:

建议固化为 checklist:

提交前:提示词里的 <Picture N> 条数 == 传入图片数?
        参考图无文字/标签/邻图?视角与镜头机位一致?
        chars 里的每个人都在动作设计里出现了?
提交后:SaveVideo 节点数 == 下载到的文件数?
        画面里有没有不该有的元素(文字/浮脸/多余人)?

9.2 批量复核优于逐镜盯

45 镜如果逐镜细看,成本极高。有效做法是"分组抽帧做对比图":

# 每镜取 3 个时间点拼一行,多镜拼成一张总览
ffmpeg -pattern_type glob -i "$tmp/*.jpg" -filter_complex "tile=3x2:padding=5" -frames:v 1 复核.jpg

本次靠这个方法在半小时内复核完全部 45 镜,抓出 4 处缺陷。

9.3 缺陷清单要"活"

待修复清单.md 的格式(问题 / 根因 / 修法 / 状态)在整个过程中反复被引用,是有价值的。特别是:

9.4 承认并更正自己的结论

我在会话中一次把"内存压力"当成确定结论向用户断言,随后被证伪。在取证报告里专设了「重要修正」一节,写明原结论、被证伪的证据、以及新的假设。

这比悄悄改口更有价值——后来者能看到"这个假设已经被排除过"。


十、附:本次产出

内容路径
成片dl-hub/62-仙剑1MV素材/MV制作/MV-一直很安静-竖屏成片.mp4
完成报告dl-hub/62-仙剑1MV素材/MV制作/竖屏MV制作完成报告.md
缺陷清单(含 8 处修复全过程)dl-hub/62-仙剑1MV素材/MV制作/AICG3D-竖屏分镜/待修复清单.md
缺陷证据与坏产物存档dl-hub/62-仙剑1MV素材/MV制作/AICG3D-竖屏分镜/问题记录/
崩溃取证报告(含更正)dl-hub/06-ComfyUI-H3运维记录/崩溃取证/2026-09-28-首次崩溃取证报告.md
崩溃原始现场dl-hub/06-ComfyUI-H3运维记录/崩溃取证/recovery_20260928_*.txt
监督脚本~/.tools/mv_aicg3d_supervisor.sh
取证脚本~/.tools/capture_after_recovery.sh
逐镜生成脚本~/.tools/mv_aicg3d.py
合成脚本~/.tools/mv_assemble_final.sh
下载此文件