如何实现可靠的断点续跑?Wenyi批级检查点与原子状态写入设计原理
【免费下载链接】wenyi将被语言阻隔的作品,带到读者的语言中。Bringing literature into your language.项目地址: https://gitcode.com/BigDawnGhost/wenyi
Wenyi(文译)是一个面向长篇小说的开源 AI 翻译工具,其翻译与审校流程支持批级检查点与原子状态写入:任何一章中断后,重新运行同一条命令即可从上次进度继续,不会重复付费调用模型,也不会损坏已保存的译文。本文面向新手,讲清这套"可靠断点续跑"背后的 4 个设计原理。
1. 长书翻译为什么会"卡死在中间"?
一本几十万字的小说,完整跑完翻译 + 审校往往需要数小时。这期间随时可能发生:
- 💥 网络中断、模型 API 限流,进程被杀
- 🖥️ 电脑休眠、终端关闭
- 🧯 人工 Ctrl+C 取消
如果状态保存不当,轻则已完成的批次全部重跑(白花 token 费用),重则留下"半截文件"导致状态损坏、整本书推倒重来。Wenyi 的官方定位正是把"单遍翻译、对中断脆弱"升级为"批级检查点 + 章节状态跟踪:用同一条命令恢复任何中断的运行"(见 README.md)。
2. 原理一:原子写入——磁盘上永远不会出现"半个文件"
所有状态(manifest、章节、报告、用量)都通过同一种方式落盘:先写同目录下的.tmp临时文件,再用os.replace原子替换正式文件。
正式文件 ←── 原子替换 ←── .tmp 临时文件(写完才生效)为什么这样做就可靠?
os.replace在同一文件系统内是原子操作:读到的要么是完整的旧内容,要么是完整的新内容,不存在中间态;- 即使进程在写临时文件时崩溃,正式文件也毫发无损,最多只是丢掉一次未完成的更新;
- 临时文件与正式文件同目录,避免跨目录 rename 失效的坑。
核心实现只有几行,位于 runstore.py:
写入
.tmp→json.dump→os.replace(tmp, path),注释原话是 "Atomic replacement prevents partial files after interruption"。
审校模块的 run_store.py 同样遵循_atomic_json约定,连审查块缓存、用量账本、结果文件都走这条路。
3. 原理二:批级检查点——进度以"批次"为单位增量保存
批次:翻译的最小保存单位
Wenyi 不会一次把整章塞给模型,而是按 token 预算把章节切成批次(batch),串行翻译。每完成一个批次,立即做三件事:
- 把译文写回章节状态文件(又是原子写入),并追加
batch_translated事件; - 记录术语抽取检查点:用"批次起点:数量"作为检查点键(见 runstore.py 的 batch_glossary_key),保证术语抽取只执行一次;
- 更新滚动上下文,让下一批次能看到刚译完的前文。
续跑:只补"缺的那部分"
中断后重跑时,resume_batches会把段落按完成状态重新分组(translation_batch.py):已译完的批次整批跳过,只按原文顺序重建上下文、补译缺失段落,绝不动用已有译文。批次进度统计也只把"全部段落都有译文"的批次计为完成,避免重复计数(见 translation.py)。
还有一个巧妙的细节:哪些批次的术语已抽取,不靠额外状态文件,而是从只追加的事件日志events.jsonl里回放恢复(completed_batch_glossary_keys)。日志只增不改,天然适合做"完成标记"。
4. 原理三:manifest 最后提交——"初始化成功"只有一个信号
任务初始化时,Wenyi 会先写入.initializing.json源文件指纹,把派生状态清干净;只有分析、术语、章节全部就绪后,才最后原子提交manifest.json并删除初始化标记(见 runstore.py 的 begin/finish_initialization)。
这意味着:
- ✅ 有 manifest → 初始化完整,可以续跑;
- ❌ 没 manifest 但有
.initializing.json→ 上次初始化半途失败,重新初始化时自动清理残留,旧数据不会污染新任务; - 🔐 同时用源文件 SHA-256 做身份校验(
ensure_source_identity),换了一本书却想用旧状态目录时会被明确拒绝,防止"同名目录串书"。
一句话:"manifest 最后提交"让状态目录永远处于"要么全新、要么完整"的两种状态之一。
5. 原理四:审校轮次检查点——连"多轮 Agent 审校"也能续跑
全书审校是最耗时的阶段:多轮循环、并发审查块、仲裁、修订,任何一轮中断都很心疼。Wenyi 为它做了两层检查点:
块级缓存(chunk cache)
每个审查块完成后,结果立刻原子写入chunks/{块ID}.json。续跑时通过is_chunk_done直接命中缓存,跳过已完成的模型调用(run_store.py)。
轮级检查点(checkpoint.json)
每一轮审校结束或扫描完成时,保存一份完整会话快照:下一轮次、修订补丁、失败记录、清洁连击数等,并带phase标记(round_done/scan_done)。恢复时由 ReviewCheckpoint.restore 解码:
- 若停在
scan_done,本轮扫描结果直接复用,不重复调用模型; - 已完成的轮次不重新执行聚合,而是用
rebuild_snapshots_from_chunks从块缓存重建,保证最终报告完整; - 若用户调低了"最大轮次"等设置,检查点会被安全钳制到合法范围,而不是产生空循环。
配合find_resumable:只要result.json状态是running / interrupted / failed,且内容摘要、配置、术语指纹都没变,就能找到上次那次审校接着跑——失败也允许续跑,块缓存继续复用(run_store.py)。
6. 配套细节:锁与事件日志
- 🔒命名文件锁区分职责:
.run.lock(长任务)、.state.lock(manifest/章节原子持久化的短锁)、.events.lock(事件追加),跨进程也不会写乱(runstore.py 锁定义); - 📜只追加的
events.jsonl记录每次动作,既是审计轨迹,也是术语检查点这类"完成标记"的数据源。
7. 这些原理对你的实际意义
| 场景 | 没有检查点 | Wenyi 的做法 |
|---|---|---|
| 翻译到一半断网 | 整章/整书重跑 | 只补未完成批次,已完成批次跳过 |
| 审校第 2 轮被中断 | 全部审查块重调模型 | 块缓存命中,轮次检查点直接续 |
| 初始化时崩溃 | 状态目录脏数据 | manifest 未提交,自动重新初始化 |
| 用量记录写到一半丢失 | 费用账目错乱 | 用量先写 pending 日志再发布,可幂等恢复(recover_usage) |
对新手来说,记住三个关键词就够了:原子写(永不损坏)、批级检查点(进度不丢)、manifest 最后提交(状态要么全新要么完整)。
相关资料
- 状态持久化主实现:runstore.py
- 批次规划与续跑切分:translation_batch.py
- 审校检查点恢复:review_checkpoint.py
- 审校运行存储:run_store.py
- 官方架构说明(持久化边界):docs/architecture.md
- 翻译流程中文文档:docs/zh/pipeline.md
- 状态演进与恢复的规划分析:docs/zh/project-review/2026-09-05/p01-state-evolution-and-recovery.md
【免费下载链接】wenyi将被语言阻隔的作品,带到读者的语言中。Bringing literature into your language.项目地址: https://gitcode.com/BigDawnGhost/wenyi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考