8.3 断点续跑


8.3 断点续跑

本节摘要:一部短剧是几十个镜头乘以七八个阶段的长任务,任何一步都可能失败:API 超时、显存不足、生成结果不达标。没有断点续跑,每次失败都从零重跑,成本随失败次数翻倍。本节给出三件套:其一,状态持久化——run_state.json 记录每个镜头在每个阶段的状态,完成判据是"产物存在且校验和匹配",天然幂等;其二,按镜头粒度重跑——resume_run.py 只碰未完成项,失败镜头隔离到失败清单,不阻塞其他镜头继续;其三,校验和清单——manifest 用 sha256 给每个产物建档,防"半截文件当成品",也是第 11.2 节交付存证的基础。末尾是工程细节清单:原子写、锁文件、日志与状态分离、环境快照。

学习目标

  • 设计 run_state.json 的结构与状态机:待跑、进行中、完成、失败、跳过。
  • 用"产物存在且校验和匹配"作为幂等判据实现断点续跑。
  • 实现按镜头粒度的重跑与失败镜头隔离。
  • 为产物目录生成 sha256 校验和清单并说明其双重用途。
  • 列出长任务的工程细节:原子写、锁文件、日志分离、环境快照。

一、长流水线的失败现实

先算一笔账:一部 24 镜头的短剧,每个镜头要过"生成、验收、配音、字幕"等多道工序,全流程上百次外部调用。按单次调用百分之一的失败率算,全程无失败的概率已经不高;失败三次、每次都从头重跑,生成费用与时间都按倍数浪费。断点续跑的本质是让"重来"的成本等于"失败点之后的部分",而不是"全部"。

场景 无断点续跑 有断点续跑
第 37 次调用超时 全部从头 从第 37 次继续
某镜头三次重试仍废 整片停摆 隔离该镜头,其余照跑
半夜挂了一半 白跑半宿 早起续跑,已完成部分零成本
产物被误删 说不清缺什么 对照清单精确补

二、状态持久化与幂等判据

状态文件的结构围绕"镜头乘阶段"设计。判据是核心:一个镜头在某阶段算"完成",当且仅当产物文件存在、且其 sha256 与状态文件里记录的一致。这个判据天然幂等——重跑同一任务,已完成的镜头被直接跳过;文件被截断或篡改,校验和失配,该镜头自动降级为未完成。

# run_state.json —— 长任务状态文件(结构示意) { "run_id": "drama-20261016-a1", "updated_at": "2026-10-16T23:41:07", "shots": { "S01": {"status": "done", "sha256": "9f8e7d6c5b4a", "attempts": 1}, "S02": {"status": "done", "sha256": "1a2b3c4d5e6f", "attempts": 2}, "S03": {"status": "failed", "error": "生成超时", "attempts": 3}, "S04": {"status": "pending"} } } ​

状态取值只有五个:pending 待跑、running 进行中、done 完成、failed 失败、skipped 主动跳过(人工决定裁掉某镜头)。attempts 记录尝试次数,供第 10.2 节的重试预算消费。

三、按镜头粒度重跑

续跑主循环的逻辑朴素:读镜头计划,逐镜头查状态,已完成的跳过,未完成的执行,失败的先隔离(记录、不重试),全部过完后统一汇报。失败镜头不阻塞流水线是关键设计——一个坏镜头停摆整条线,比坏镜头本身贵得多。

# resume_run.py —— 断点续跑:按镜头粒度找未完成项,失败镜头隔离 import hashlib, json, os STATE = "build/run_state.json" def load_state(): if os.path.exists(STATE): return json.load(open(STATE, encoding="utf-8")) return {"run_id": "drama-run", "shots": {}} def save_state(state): tmp = STATE + ".tmp" with open(tmp, "w", encoding="utf-8") as f: json.dump(state, f, ensure_ascii=False, indent=2) os.replace(tmp, STATE) def digest(path): return hashlib.sha256(open(path, "rb").read()).hexdigest()[:16] def done_already(state, shot_id): clip = f"build/clips/{shot_id}.mp4" recorded = state["shots"].get(shot_id, {}) return os.path.exists(clip) and recorded.get("sha256") == digest(clip) def run_shot(shot): raise SystemExit("接你的生成技能:python run_skill.py shot.t2v payload.json") def main(plan="build/shotplan.json"): state, failed = load_state(), [] for shot in json.load(open(plan, encoding="utf-8"))["shots"]: sid = shot["id"] state["shots"].setdefault(sid, {"status": "pending", "attempts": 0}) if done_already(state, sid): state["shots"][sid]["status"] = "done" print(f"{sid}: 已完成(幂等跳过)") continue state["shots"][sid]["attempts"] += 1 try: run_shot(shot) state["shots"][sid] = {"status": "done", "attempts": state["shots"][sid]["attempts"], "sha256": digest(f"build/clips/{sid}.mp4")} except Exception as exc: state["shots"][sid].update(status="failed", error=str(exc)) failed.append(sid) save_state(state) print(f"本轮结束。失败镜头:{failed or '无'}(已隔离,可单独重跑)") if __name__ == "__main__": main() ​

两处细节值得注意:save_state 在每个镜头处理后立即落盘,崩溃时最多损失一个镜头的进度;先写临时文件再 os.replace,是下一节的"原子写",保证状态文件自身永远不会是半个 JSON。

四、校验和清单:防半截文件,也作交付存证

镜头级状态防"重跑浪费",还要防"半截文件当成品":生成进程被杀时可能留下时长不足的视频,文件存在但内容残缺。解法是给产物目录整体建一份 manifest:

# write_manifest.py —— 为产物目录写 sha256 清单(防残缺文件,交付存证) import hashlib, json, os def manifest(root="deliverables", out="build/manifest.json"): rows = [] for base, _, files in os.walk(root): for name in sorted(files): path = os.path.join(base, name) rows.append({"file": os.path.relpath(path, root).replace("\\", "/"), "sha256": hashlib.sha256(open(path, "rb").read()).hexdigest(), "bytes": os.path.getsize(path)}) with open(out, "w", encoding="utf-8") as f: json.dump(rows, f, ensure_ascii=False, indent=2) print(f"{len(rows)} 个文件已登记 → {out}") if __name__ == "__main__": manifest() ​

manifest 的第二重身份在第 11.2 节兑现:交付时连同产物一起归档,接收方重算一遍 sha256 即可确认交付完整、未被中途篡改。第 10.3 节的质量门禁也会读取它做完整性检查。

五、工程细节清单

断点续跑的最后百分之二十是细节:其一,原子写——一切会被续读的文件(状态、清单、配置)都走"写临时文件再改名",os.replace 在同一磁盘上是原子操作。其二,锁文件——同一 run 防止两个进程同时续跑:启动时创建锁文件(写入进程号),退出时删除,检测到锁存在则拒绝启动。其三,日志与状态分离——状态文件只存事实(状态、校验和),过程噪音(模型输出、调试信息)进日志文件,避免状态文件膨胀到影响读写。其四,环境快照——run_state 里记录模型名与版本、关键参数(分辨率、风格模板版本),几个月后重跑同一个 run 时才知道"可复现"成立到什么程度。

本节要点回顾

  • 长任务必须假设会失败:断点续跑让重来成本等于失败点之后的部分。
  • 完成判据幂等:产物存在且校验和匹配;残缺文件自动降级为未完成。
  • 按镜头粒度重跑,失败镜头隔离到清单,一个坏镜头不停整条线。
  • manifest 双重用途:防半截文件、作交付存证。
  • 细节四件套:原子写、锁文件、日志与状态分离、环境快照。

编排三件套齐备:技能封装、契约化采集、断点续跑。下一章进入实战:以 huobao-drama 式的一句话到成片为主案例,把前九章的每个环节串成一部真正的短剧。

作者与出处
原作者:灏天文库
整理人: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U