Files
vrsub/docs/decisions.md
T
cat-shark 171b088e7c feat: 批量流水线按 GPU 资源调度(非 GPU 阶段并行、GPU 阶段互斥)
此前组内阶段是串行的(先全部提音、再全部转写、再全部翻译),LLM 走线上端点时
翻译阶段不占显存、GPU 全程空转——实测占整轮挂钟约 40%(19.6W / 272MiB)。

- `_run_job` 改为按组启动在途流水线:每个视频独立推进自己的阶段,最多
  `WOV_BATCH_PIPELINE_WORKERS`(默认 4)个阶段在途。
- 派发只看资源:`stage_gpu_need_mb` 为 0 的阶段(提音、线上翻译、ASS)立刻派发,
  可与其它视频的转写并行;需要 GPU 的阶段由 `GpuGate` 互斥准入,并按"阶段索引
  最小者优先"派发,组内仍是先跑完全部转写再进翻译——本机 Ollama 模型每组只
  加载一次,不需要按"是否云端"写分支。
- 同一阶段只在途一份(派发即标记 running),单视频异常不带走整组;暂停沿用
  run 级 paused.flag,暂停后不再派发新阶段。
- 测试:远端翻译与其它视频转写重叠、本机端点下全部转写先于翻译且翻译互斥、
  提音与转写重叠,以及既有分组/暂停/失败隔离用例。
2026-09-18 22:40:53 +08:00

110 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设计决策与踩坑记录
本文件记录"为什么这样做":模型/参数选择的实测依据、历史事故与踩坑。
**当前生效的规则**以对应专题文档为准(本文件只解释理由):
- 节点参数与默认值:[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 生成阶段存在已知 bugM-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`)。
## 相关文档
- 当前生效的参数与协议:[node-protocol.md](./node-protocol.md)
- 运维语义与批量流程:[operations.md](./operations.md)
- 缺陷 ID 与验收标准:[代码审查问题跟踪.md](./代码审查问题跟踪.md)