- 待办:会话式长上下文方案标记为已技术评审且不实施;补记问题 A 的阶段性结论(暂缓) - 新增 docs/实验数据-字幕审校定位与修复实验.md:LLM 定位触发率/召回、合成正样本、逐条修复前后对照、成本与复现方式 - 新增 docs/实验数据-字幕重生成成本与耗时.md:单价口径、功率画像、全量推算 - decisions:补充两条决策(会话式长上下文否决、审校定位+定向修复暂缓) - scripts:归档一次性探测脚本 probe_subtitle_review_stage0.py(含 rebuild 子步骤) - AGENTS/README:补充实验与改动许可规则、文档索引
165 lines
12 KiB
Markdown
165 lines
12 KiB
Markdown
# 设计决策与踩坑记录
|
||
|
||
本文件记录"为什么这样做":模型/参数选择的实测依据、历史事故与踩坑。
|
||
**当前生效的规则**以对应专题文档为准(本文件只解释理由):
|
||
|
||
- 节点参数与默认值:[node-protocol.md](./node-protocol.md)
|
||
- 工作流与模型配置:[workflows.md](./workflows.md) / [configuration.md](./configuration.md)
|
||
- 代码缺陷与修复过程:[代码审查问题跟踪.md](./代码审查问题跟踪.md)
|
||
|
||
## 帧文件必须按帧号数值排序(14236 帧事故)
|
||
|
||
- **现象**:run_339ec7ee437f 的 14236 帧任务中,字幕时间与图像错位。
|
||
- **根因**:ffmpeg `%04d` 编号超过 9999 帧后扩为 5 位,字典序 `sorted()`
|
||
会把 5 位编号排在 4 位之前。
|
||
- **结论**:帧文件必须按帧号数值排序读取(`_sorted_frame_files`),
|
||
回归测试见 `test_frame_files_read_order_matches_frame_number`。
|
||
|
||
## glm-ocr 重复循环问题与源头修复
|
||
|
||
- **根因**:glm-ocr 生成阶段存在已知 bug(M-RoPE delta 未传递,大图触发
|
||
重复循环;GitHub #454 / #16892)。`keep_alive` 与其无关(实测无效)。
|
||
- **修复路径**(现行防护措施见
|
||
[node-protocol.md](./node-protocol.md#glm-ocr-调用与重复循环防护)):
|
||
1. `frame-extract` 裁切后把帧压缩到 720p 内——过大输入图是触发条件之一。
|
||
2. `vlm` 请求体加 `repeat_penalty` + `num_predict` 压制重复。
|
||
3. `subtitle-ocr` 用 `max_result_chars` 把超长输出判为异常并跳过该帧。
|
||
|
||
## llm-filter 的 LLM 分类层默认关闭
|
||
|
||
- **决策(2026-09)**:`llm-filter` 只跑确定性规则层,LLM 五类分类层默认关闭
|
||
(`use_llm=0`),需显式 `use_llm=1` 才启用。
|
||
- **依据(run_ac7f480a3ccb 逐类人工审查)**:LLM 层额外删除的 131 条中
|
||
**56%(73 条)是真实对话**(`好好教育她一番吧`/`腿不要合上`/
|
||
`这家医院 为VIP患者提供了特殊服务`),而它真正抓住而规则层抓不到的仅
|
||
58 条且大半可正则化(已下沉到规则层);`repeat` 类别 67 条判定零删除;
|
||
长文本保护等五套机制全在给不稳定分类器兜底。
|
||
- **效果**:关闭后真实数据保留 863 条(旧 588 条)、**误删真对话 0 条**
|
||
(旧 73 条)、无 LLM 调用。
|
||
- **启用时保留的机制**:每条连同前后各 `context_size`(默认 10)条**过滤后**
|
||
文本判断(上下文净化),≥`min_keep_len`(默认 12)时 noise 不构成删除依据
|
||
(长文本保护),429/5xx 指数退避 + 自适应线程池 `report_failure()` 降并发后
|
||
重试一轮,判定成功即追加 `filter_partial.jsonl` 断点存档;按文本去重。
|
||
- **复现**:`scripts/regenerate_filter_ac7f480a3ccb.py`,
|
||
回归数据 `tests/nodes/test_llm_filter/data/ocr_srt_run_ac7f480a3ccb.srt`。
|
||
|
||
## 翻译模型默认值切换与 subtitle-correction 例外
|
||
|
||
- **默认切换(2026-09)**:`LLM_MODEL` 由 `Qwen/Qwen3.6-35B-A3B` 改为
|
||
`Qwen/Qwen3.5-35B-A3B`——同片 2 小时日语 ASR 全量对比,质量持平、
|
||
0.232 s/行(评测中最快)。
|
||
- **例外**:`subtitle-correction` 节点**有意**保留旧兜底
|
||
`Qwen/Qwen3.6-35B-A3B`——该节点错听泛化实测新模型 0/4、旧模型 4/4
|
||
(复现:同一误听场景各跑 4 次),见 `tests/test_llm_default_model.py`。
|
||
- **评测数据**:`data/experiments/translate_models/REPORT.md`,
|
||
工具链 `scripts/bench_translate_models.py` 等。
|
||
|
||
## whisper large-v2 vs large-v3 切换
|
||
|
||
- **决策(2026-09)**:通用转写模型从 large-v3
|
||
切到 `faster-whisper-large-v2`。
|
||
- **依据**:savr-1054 全片 A/B 实测,无 VAD 幻觉长段归零、开头漏句救回,
|
||
VAD 链路条数与覆盖小幅领先。
|
||
- **数据**:`data/experiments/whisper_v2_vs_v3/`,
|
||
脚本 `scripts/compare_whisper_v2_vs_v3.py`,
|
||
调研记录 [调研-whisper漏句与decode_full验证.md](./调研-whisper漏句与decode_full验证.md)。
|
||
|
||
## VR 字幕景深与遮挡方案选型
|
||
|
||
- **结论**:采用 **A-1 零视差**(字幕固定在屏幕平面,不做景深偏移)+
|
||
**B-1 顶部安全区**(`an8` 顶部居中,`margin_top` 默认 700),
|
||
并用半透明填充 + 半透明描边降低遮挡感。
|
||
- **调研全文**:[VR双目字幕景深与遮挡调查报告.md](./VR双目字幕景深与遮挡调查报告.md)。
|
||
|
||
## 批量任务先写明细再排队(CREATING 状态)
|
||
|
||
- **现象**:新建批量任务后偶发任务被标 COMPLETED、`done=0/N`,剩余视频永远不再
|
||
被处理(例:`batch_ac585c5458de` 唯一明细还 PENDING 而任务已 COMPLETED)。
|
||
- **根因**:`create_job` 先把任务行以 QUEUED 入库(此时引擎就看得见),再逐条登记
|
||
明细(扫描媒体库时 500+ 条要数秒);引擎轮询到的快照可能没包含剩余明细,收尾时
|
||
“无未结束明细”检查也看不到它们,于是把任务标 COMPLETED。
|
||
- **结论**:任务行改以 `CREATING` 入库,明细全部登记完才置 QUEUED(引擎只取
|
||
QUEUED);登记中途异常置 FAILED 并抛给路由;启动时把上一进程遗留的 CREATING
|
||
统一置 FAILED(`fail_creating_batch_jobs`),避免静默残留。
|
||
- **旧数据修复**:`scripts/fix_zombie_batch_jobs.py` 仍用于处理历史
|
||
“COMPLETED 但仍有未结束明细”的脏数据。回归测试见 `test_batch.py` 的
|
||
`test_job_hidden_from_engine_until_details_written`。
|
||
|
||
## 批量流水线按 GPU 资源调度(而不是按"是否云端"分支)
|
||
|
||
- **现象**:LLM 走线上端点时翻译阶段不占显存,但组内阶段仍是"先全部提音、再全部
|
||
转写、再全部翻译"的串行推进,翻译期间 GPU 全程空闲(实测 19.6W / 272MiB,
|
||
占整轮挂钟约 40%)。
|
||
- **结论**:阶段能否启动只看资源。`wov_app.resources.stage_gpu_need_mb` 给出每个
|
||
阶段的显存需求(whisper 按权重 ×1.45、本机 LLM 端点按预留、线上端点为 0),
|
||
进程内 `GpuGate` 负责准入:不需要 GPU 的阶段立刻放行(转写与线上翻译并行);
|
||
需要 GPU 的阶段互斥,并按"阶段索引最小者优先"派发,组内因此仍是先跑完全部转写
|
||
再进翻译——本机 Ollama 模型每组只加载一次,行为与旧实现一致。
|
||
- **为什么不是"配置分支"**:代码不判断端点是不是云端,只读"这个阶段要不要占
|
||
GPU、现在够不够"。同一份代码在本机模型下自动退化为串行、在线上模型下自动并行。
|
||
- **降级**:探测不到 `nvidia-smi` 时退化为 GPU 阶段互斥(与串行结果一致);
|
||
局域网地址上的本机 Ollama 需用 `WOV_LOCAL_MODEL_HOSTS` 声明,否则会被当成远端。
|
||
- **并发写**:流水线多线程写产物/进度,SQLite 开 WAL + busy timeout
|
||
(`WOV_DB_BUSY_TIMEOUT_SECONDS`)。
|
||
|
||
## 翻译"会话式长上下文"方案否决
|
||
|
||
- **决定(2026-09 技术评审)**:不实现"翻译阶段复用会话上下文 + 文件夹级共享上下文 +
|
||
150k 触发压缩"方案,见
|
||
[待办-翻译上下文复用与语义纠错.md](./待办-翻译上下文复用与语义纠错.md)。
|
||
- **前提不成立**:该方案预期"长上下文能让模型发现语义不顺从而修掉 ASR 误听",但
|
||
同一份待办文档记录的两次实验(`subtitle-correction` 节点 ±60s 上下文、加强提示词
|
||
A/B)全部失败。误听句在字面上语法通顺、局部自洽,语境里没有声学证据,模型没有
|
||
改写依据——加长上下文只增加同类证据。
|
||
- **成本**:会话式每批重发全部历史,输入随批数二次增长;该档 Qwen 在 SiliconFlow
|
||
价格表"缓存价格"列为 `-`(无 cached-input 折扣),前缀缓存无法抵消。按实测口径
|
||
(批均约 880 tokens、其中输入 529 / 输出 350;434 行/素材小时 ≈ 22.5 批/小时)
|
||
推算:1.2 小时视频约 3.3×、3 小时视频约 6.5×、15 小时文件夹约 70×(约 ¥30/文件夹),
|
||
剩余 187 小时素材由 ¥5.5–9.4 升到约 ¥300–400。
|
||
- **阈值踩计费档**:`>150k 触发` 越过 `[128k,+∞)` 档(输入 ¥0.4→¥1.6、输出
|
||
¥3.2→¥12.8,整请求按该档计价),压缩阈值必须低于 128k,长上下文方案的收益结构
|
||
与计费结构天然冲突。
|
||
- **与既有设计冲突**:文件夹会话是跨 run 的可变共享状态,与"节点只通过产物 URI
|
||
交换数据、每 run 独立产物目录"的不变式、与批量"非 GPU 阶段并行"的调度成果、与
|
||
"暂停后整节点重跑、不留半成品"的暂停契约都要冲突,需要新增会话存储子系统并扩展
|
||
暂停/幂等语义。
|
||
- **未决的替代方向**:问题 A 更可能在 ASR 侧修(`word_timestamps` 已产出的词级
|
||
`probability` / `avg_logprob` 目前未被任何节点使用;低置信度片段 + 文件夹级热词
|
||
`initial_prompt` 二次解码等),但**尚未评审决定**,需要时另行提方案与代价。
|
||
- **实测来源**:`data/experiments/regen_plan/app_cloud_run.log`(每批 tokens)、
|
||
[实验数据-字幕重生成成本与耗时.md](./实验数据-字幕重生成成本与耗时.md)、
|
||
SiliconFlow 价格页 `[0,128k)` / `[128k,+∞)` 分档。
|
||
- **字幕审校(定位+定向修复)的实测结论**:见
|
||
[实验数据-字幕审校定位与修复实验.md](./实验数据-字幕审校定位与修复实验.md)
|
||
|
||
## 字幕审校(定位+定向修复)方案暂缓
|
||
|
||
- **决定(2026-09)**:不实施"LLM 定位问题 cue + 定向修复该 cue"的翻译审校节点,
|
||
也不改造现有 `subtitle-correction` 节点。实测数据见
|
||
[实验数据-字幕审校定位与修复实验.md](./实验数据-字幕审校定位与修复实验.md)。
|
||
- **有效的部分**:术语/忠实性类问题(词表违背、译文与日文不一致、称呼跳变)定位与
|
||
修复都成立(例:`阴道`→`小穴`;"生来的里面插着生来的玩具"→"小鸡鸡插进了小穴里"),
|
||
合成正样本定位召回 11/14 = 79%,增量成本约 +¥0.04/素材小时(全量约 +¥7)。
|
||
- **无效的部分**:`字面通顺但语义错`(问题 A,ASR 听岔成正常句子)。实测把中文、
|
||
日文原文、邻句、系列词表全部给 LLM:
|
||
- 定位:只给中文时三条已知问题 cue 召回 0/3;加上日文也只到 1/3
|
||
(三条出问题的共性是**中文读起来完全通顺**,模型没有怀疑动机);
|
||
- 修复:三条目标依次改成"声音传送到身体里""继续吃""已经完全进去了",
|
||
**全部保留错译**;且出现无依据脑补(`お腹鸣っちゃう`"肚子饿了"→"小穴要湿了")。
|
||
- **原因判定**:不是上下文给得不够,而是**当前 LLM 能力不足以从错误的日文原文
|
||
反推真实含义**——能依据的日文本身是错的,模型只能沿错译换词。这与待办文档里
|
||
记录的前两次失败实验(纠错节点、加强提示词 A/B)结论一致。
|
||
- **副产品结论**:① 硬规则(近音匹配)泛化不了——严格口径漏报(目标 0/3),
|
||
宽松口径 48.6% 全是假阳性;② 系列自身的译法并不一致(同一个 `おなほ` 被译成
|
||
"自慰套/手办/小鼻鼻/嘴"),所以"文件夹级上下文"的价值真实存在,但**惯用译名
|
||
靠挖掘挖不出来**,需要人工或强约束固定。
|
||
- **若将来重开**:方向应从翻译侧挪回 ASR 侧(只审日文、给候选读法;先建场景再对照),
|
||
那里才有声学证据;具体三个可测方向见实验文档第六节。
|
||
|
||
## 相关文档
|
||
|
||
- 当前生效的参数与协议:[node-protocol.md](./node-protocol.md)
|
||
- 实验数据(成本/耗时、审校定位实验):[实验数据-字幕重生成成本与耗时.md](./实验数据-字幕重生成成本与耗时.md)、[实验数据-字幕审校定位与修复实验.md](./实验数据-字幕审校定位与修复实验.md)
|
||
- 运维语义与批量流程:[operations.md](./operations.md)
|
||
- 缺陷 ID 与验收标准:[代码审查问题跟踪.md](./代码审查问题跟踪.md)
|