我们把爆火的新 OCR 引擎摆上了基准测试——然后留下了旧的

核心结论: FrankenOCR——纯 Rust、纯 CPU 的 3B 参数 OCR 视觉语言模型封装,近期市场热度的主角——被放上了我们自己的评测框架,对阵现任扫描文档引擎 PaddleOCR。结果:抽取打平(退化合同上字段 F1 0.280 vs 0.260,n=5——噪声),慢 25–50×(同一 CPU 上 55.9s/页 vs 1–2s),以及一个我们认为比经典 OCR 乱码更糟的失败模式:在不可读页面上自信地幻觉出结构——我们的质量分数把它读成满分 1.000。适配器作为可选实验留在仓库里;默认引擎不换。所有数字均为定点实测(2026-07-08),提交在 content/franken_results.toml,评测框架随仓库附带。

我们为什么要测它

FrankenOCR 有真实的吸引力,假装没有本身就是一种不诚实。它是单个 5MB 二进制、零 Python 依赖(~3.9GB 权重首次运行时下载)——对封闭环境是实打实的运维卖点。它输出版面分类标签(header / title / text / page_number / image),我们的覆盖率实验将来用得上。而且它的热度已经大到"我们没看过"约等于失职。

于是我们做了对每个引擎都做的事:写一个适配器,收敛到所有解析器共用的同一 Document IR——每份文档一次子进程调用、markdown 输出重建为块、FRANKEN_BIN 环境变量按需启用、不设置时路由行为字节级不变——然后放上和现任引擎完全相同的评测框架。同样的文档、同样的抽取器、同样的指标。

数字

两组实验,运行于 2026-07-08:

实验组 FrankenOCR PaddleOCR
速度(12 页真实 Tobacco800 扫描件,CPU) 55.9s/页 1–2s/页
抽取,degrade-light(5 份 CUAD 合同) 字段 F1 0.280 字段 F1 0.260
抽取,degrade-shred(不可读) 0.000 0.000

n=5 时的抽取差异是噪声:按打平理解。速度差异不是噪声:同一硬件上 25–50×——放到档案迁移的规模,这是一夜跑完和跑一个季度的区别。

原始输出里有一个看似 FrankenOCR 获胜的数字,值得我们抢在别人引用之前自己拆穿:来源归因准确率 0.800 vs 0.400。这个差距是块粒度的伪影,不是准确率——FrankenOCR 每份文档只输出约 2.6 个页面大小的 markdown 块,PaddleOCR 是 68–142 个行级块;引用"就在这一页里"当然比引用正确的那一行容易命中。因为证据变粗而变好看的指标,不是变好了。

决定性的失败模式

shred——我们刻意做到不可读的退化档位,任何引擎都应该失败的地方——PaddleOCR 失败得很诚实:输出可见乱码,质量分数应声跌到 0.730,文档被标记复核。

FrankenOCR 的失败方式不同。它输出了流畅、格式良好、完全编造的结构:空的 <table> 骨架,还有一次是 50KB 的"2. 3. 4. …"重复列表循环,解码耗时 963s。我们的质量分数把这份输出读成 1.000——满分。

这正是我们此前测过的质量信号盲区,只是更锋利的形态。质量信号衡量的是引擎输出了的东西:乱码会触发它们;沉默会绕开它们;而流畅的幻觉会主动击败它们——它恰恰制造出信号所奖励的那种格式良好的证据。对一条以可溯源、可验证的法律文档事实为产品的流水线来说,会在不可读输入上编造结构的引擎,无论吞吐量和打包形态如何,都没有资格坐默认位。乱码会被抓住;幻觉会被信任。

结论,以及背后的规则

FrankenOCR 作为可选实验留在仓库里——适配器和评测框架都已提交,它的版面标签将来也许能在覆盖率实验里挣回饭钱。PaddleOCR 仍是默认扫描文档引擎。

可迁移的不是结论,而是流程——每个候选引擎大约花一天:

  1. 适配到同一内部表示,让对比只隔离引擎本身(下游一切一致)。
  2. 在你自己的语料上做基准——包括你手里最差的页面,而不是演示里最好的。
  3. 测端任务(抽取 F1)、你的硬件上的速度,以及不可读输入上的失败模式——最后一项才是两个引擎真正分道扬镳的地方。

诚实的局限

自己复现

git clone <repo> && cd contract-rag && uv sync --extra dev
# FrankenOCR:5MB 二进制,MIT——github.com/Dicklesworthstone/franken_ocr(首次运行下载 ~3.9GB 权重)
FRANKEN_BIN=path/to/focr uv run python scripts/benchmark_franken.py

需要 Tobacco800 扫描件和 CUAD 黄金集(从不提交——见仓库 README);OCR 输出有 IR 缓存,重跑很快。如果新版 FrankenOCR 改变了这些数字,跑一遍告诉我们——评测框架存在的意义,就是让这场争论可以用数据进行。