1. 从一张“0.7 页/秒”的截图说起
前阵子群里有人甩出一张截图,说混元 OCR 1.5 用 1B 的模型把识别速度干到了 0.7 页/秒,底下配了一句“榜单第一”。我第一反应不是惊叹,而是习惯性地去翻它的测试条件——单页多少字、什么分辨率、什么硬件、batch 多大、有没有算上预处理和后处理。做 OCR 这行久了,你会形成一种条件反射:速度数字本身没有意义,有意义的是“在什么约束下达到的速度”。
这篇文章我想聊的就是这件事。混元 OCR 1.5 这个 1B 模型,为什么能在这么小的参数量下把吞吐做上去,它背后的 DFlash、vLLM、RL 这几块拼图各自扮演什么角色,以及那些榜单里常见的“水分”到底藏在哪。如果你正在做文档数字化、票据识别、合同字段抽取这类落地项目,或者单纯想搞清楚小模型 OCR 的工程边界在哪,这篇应该能给你一些能直接抄的参考。
先把结论摆前面:1B 级别模型跑 OCR,速度瓶颈往往不在模型本身,而在视觉编码、序列解码和调度这三段的配合。混元 OCR 1.5 的提速账本,本质上是把这三段各自优化了一遍,再用 RL 把精度补回来。下面我拆开讲。
2. 混元 OCR 1.5 的整体设计与思路拆解
2.1 为什么是 1B 这个量级
很多人第一反应是:OCR 不是早就被传统 CV 方案做烂了吗,怎么又回到大模型了。这里要区分两类任务。固定模板票据识别,传统方案确实又快又准,一个模板配一套规则,毫秒级出结果。但一旦遇到版式不固定、手写混排、表格嵌套、多语言混杂的场景,规则维护成本会指数级上升。这时候端到端的多模态模型优势就出来了——它不需要你告诉它“第几行第几列是什么”,它自己学。
那为什么是 1B 而不是 7B、13B?我自己的实测经验是,OCR 这个任务对模型容量的需求远低于通用对话。原因在于OCR 的输出空间高度受限:它本质上是把图像区域映射到字符序列,语义理解的深度要求不高,更多是视觉定位和字符解码的精度问题。7B 模型在 OCR 上相比 1B 的提升,往往集中在长尾场景(生僻字、极端模糊、复杂公式),而常规文档的收益非常有限。但 7B 的推理成本是 1B 的 5 到 7 倍,这个账在批量处理场景下算不过来。
所以 1B 是一个工程上的甜点区:大到能覆盖绝大多数版式,小到能在单卡上跑出可观的吞吐。混元 OCR 1.5 选这个量级,我认为是经过成本核算的,不是拍脑袋。
2.2 DFlash 在这里解决的是什么问题
DFlash 这个词在 OCR 语境下,核心思路是并行化解码。传统自回归解码是一个 token 一个 token 往外吐,序列越长越慢。一页 A4 文档,如果按字符算,轻松上千个 token,串行解码的时间就上去了。
DFlash 类的方案本质上是让模型一次预测多个位置的 token,然后通过某种校验机制保证这些并行预测的正确性。你可以类比成:原来是一个人一个字一个字抄,现在是一排人同时抄,抄完再对一遍。对得上的就保留,对不上的回退重来。关键在于“对得上的比例”——如果大部分都能一次对,那吞吐就是数量级的提升;如果频繁回退,反而比串行还慢。
这也是为什么 OCR 特别适合这类方案:文档里的文字有很强的局部规律性,模型对下一个字的预测置信度普遍较高,并行预测的命中率天然就好。我试过在纯随机文本上跑类似方案,收益就明显打折,因为预测难度上去了。
2.3 vLLM 承担的角色:调度与显存
vLLM 在这套体系里不是可有可无的。它的核心价值有两个:PagedAttention 的显存管理和连续批处理(continuous batching)。
先说显存。OCR 推理时,KV Cache 会随着序列长度线性增长。一页文档上千 token,如果显存管理粗放,很容易就 OOM 或者被迫减小 batch。PagedAttention 把 KV Cache 切成固定大小的块来管理,碎片率大幅下降,同样显存能塞下更多并发请求。这个在批量处理场景下是实打实的吞吐提升。
再说连续批处理。传统 batch 是“凑齐一批一起跑,跑完再凑下一批”,中间有等待空隙。连续批处理是“谁跑完谁走,新请求随时插进来”,GPU 利用率能拉高不少。OCR 场景里每页的处理时间不一样(有的页字多,有的页字少),连续批处理的收益尤其明显。
提示:vLLM 的版本选择很关键。不同版本对多模态模型的支持程度差异很大,部署前务必确认目标模型在对应版本里的支持状态,别直接拿最新版往上怼。
2.4 RL 环节:精度是怎么补回来的
小模型 + 激进提速,最直接的代价就是精度掉。混元 OCR 1.5 用 RL 来补这块,逻辑是:用奖励信号引导模型在“快”和“准”之间找平衡。
具体来说,奖励函数通常包含两部分:识别准确率(字符级或字段级)和输出效率(比如序列长度、解码步数)。如果模型为了保险把每个字都拆成多步慢慢吐,准确率可能高但速度掉;如果它激进并行,速度快但错字多。RL 的作用就是让模型学会在哪些位置可以大胆并行,哪些位置必须谨慎。
这里有个经验:RL 阶段的数据质量比数量重要得多。我见过一些团队拿海量低质标注去训,结果模型学到的奖励信号是噪声,反而把原本还行的基础能力带偏了。混元这套能跑通,说明它在奖励设计和数据筛选上是下了功夫的。
3. 核心细节解析与实操要点
3.1 视觉编码这一段容易被忽略
大家聊 OCR 提速,目光都在解码端,但视觉编码其实是隐藏的时间大户。一张高分辨率文档图,切 patch 之后动辄几千个视觉 token,编码器过一遍的开销不小。
常见的优化手段有几个方向。一是动态分辨率:不是所有图都按最高分辨率处理,先判断文档复杂度,简单的低分辨率过,复杂的才上高分辨率。二是视觉 token 压缩:把冗余的 patch 合并,减少送入解码器的 token 数。三是编码器量化:视觉编码器对量化的敏感度通常低于语言解码器,INT8 甚至 INT4 往往能接受。
我实测下来,视觉编码这段如果不动,光优化解码端,整体吞吐提升会卡在一个上限。两端一起优化,收益才是叠加的。
3.2 解码策略的参数怎么调
并行解码不是开得越猛越好。这里有几个关键参数:
| 参数 | 作用 | 调大 | 调小 |
|---|---|---|---|
| 并行窗口大小 | 一次预测多少 token | 吞吐升,回退率升 | 吞吐降,准确率稳 |
| 置信度阈值 | 多高才接受并行结果 | 准确率升,并行率降 | 并行率升,错误率升 |
| 最大回退次数 | 单次并行失败重试上限 | 准确率升,延迟升 | 延迟降,可能出错 |
我的经验是,并行窗口从 4 开始试,逐步加到 8 或 16,观察回退率曲线。回退率一旦超过某个拐点(我一般看 15% 到 20%),再往上加窗口就是负收益了。置信度阈值则要看你的业务容忍度,票据金额这种字段必须严,正文识别可以松一点。
3.3 批处理与显存的平衡
batch 不是越大越好。这里有个反直觉的点:batch 太大时,单页的延迟会上升,虽然总吞吐可能还在涨,但尾延迟会很难看。如果你的业务是离线批量处理,那追求总吞吐没问题;如果是在线服务,就得在吞吐和延迟之间找平衡点。
实操上我会这样做:先固定一个可接受的单页延迟上限(比如 500ms),然后在这个约束下把 batch 往上推,推到延迟刚好触线为止。这个点通常就是性价比最高的配置。
注意:显存监控一定要做。PagedAttention 虽然省显存,但不是无限的。并发一高,KV Cache 块不够用时会触发抢占,性能会断崖式下跌。提前压测出并发上限,比线上出事再查强得多。
3.4 预处理和后处理别偷懒
很多团队算吞吐时只算模型推理时间,把图像预处理(去噪、纠偏、二值化)和后处理(字段抽取、格式校验)排除在外。这是榜单水分的一大来源。
真实场景里,预处理可能占 10% 到 20% 的时间,后处理如果涉及复杂的字段逻辑,占比更高。混元 OCR 1.5 报的 0.7 页/秒,如果指的是纯推理,那端到端落地时打个七折是正常的。做容量规划时,一定要按端到端算,别按纯推理算。
4. 实操过程与核心环节实现
4.1 环境准备与依赖确认
假设你要复现一套类似的 OCR 推理服务,第一步是把环境理清楚。核心依赖是 vLLM 和对应的模型权重。这里我不写具体版本号,因为版本迭代太快,写死了反而误导,但思路是固定的:
# 确认 CUDA 版本与 vLLM 编译版本匹配 nvidia-smi # 确认 Python 环境干净,避免依赖冲突 python -c "import vllm; print(vllm.__version__)"踩过的坑:vLLM 对 CUDA 版本和 PyTorch 版本很敏感,版本错配时往往不是报错,而是跑起来结果不对或者性能异常。我一般会用一个干净的虚拟环境,按官方文档的版本矩阵来装,不自己乱升。
4.2 模型加载与显存预估
加载前先算一笔账。1B 模型,FP16 权重约 2GB。KV Cache 按每页 1500 token、每 token 每层 2 字节估算,层数假设 24 层,那单页 KV 约 1500 × 24 × 2 × 2 ≈ 144KB。并发 32 页就是约 4.6MB,看起来不大,但实际还要算上视觉 token 和中间激活,通常要留 2 到 3 倍余量。
# 伪代码示意,实际按框架 API 调整 from vllm import LLM, SamplingParams llm = LLM( model="your-ocr-model-path", dtype="float16", gpu_memory_utilization=0.85, # 留出余量给视觉编码 max_model_len=4096, )gpu_memory_utilization这个参数我一般设 0.85 到 0.9,不设满。设满了容易在峰值时 OOM,留一点缓冲更稳。
4.3 推理流程的完整串联
一次完整的 OCR 推理,流程是这样的:
- 图像读取与预处理:读图,做纠偏、去噪,必要时缩放。
- 视觉编码:图像过视觉编码器,得到视觉 token。
- 拼接与解码:视觉 token 和 prompt 拼接,送入解码器,用并行解码策略生成文本。
- 后处理:对生成的文本做字段抽取、格式校验、置信度过滤。
每一步都有优化空间。比如预处理阶段,如果文档本身质量好,纠偏和去噪可以跳过,省时间。后处理阶段,如果只是要纯文本,那直接输出就行;如果要结构化字段,那得额外跑一层解析。
4.4 性能压测怎么做才靠谱
压测不是随便跑几张图看时间。我的做法是:
- 准备分层测试集:简单文档、中等复杂度、复杂版式各一批,分别测。
- 记录端到端时间:从读图到输出结果,全链路计时。
- 监控 GPU 利用率:利用率长期低于 70%,说明有瓶颈没打满。
- 测尾延迟:P99 延迟比平均延迟更能反映真实体验。
我见过太多人只报平均吞吐,结果线上 P99 延迟爆炸,用户体验一塌糊涂。平均数是给老板看的,尾延迟才是给用户看的。
5. 榜单里的水分到底在哪
5.1 测试集选择的猫腻
榜单第一这件事,首先要看它测的是什么集。如果测的是印刷体、清晰扫描件、单一语言,那成绩好是应该的,说明不了太多。真正难的是手写、模糊、多语言混排、复杂表格。很多榜单会挑对自己有利的子集来报,这是公开的秘密。
我的建议是,看榜单时先找它的测试集构成。如果没公开,那这个数字的可信度就要打折。
5.2 速度指标的统计口径
前面提过,纯推理时间和端到端时间是两回事。除此之外还有几个口径问题:
- 是否包含首次加载:冷启动时间算不算进去。
- 是否包含预处理:图像解码、缩放算不算。
- batch 大小:单条测和批量测,数字差好几倍。
- 硬件配置:什么卡、几张卡、有没有用特殊加速。
这些信息如果不透明,那“0.7 页/秒”这个数字就没法横向比较。我一般会要求看到完整的测试配置,否则只当参考,不当依据。
5.3 精度和速度的取舍没写清楚
OCR 的精度指标也很有讲究。字符准确率和字段准确率是两回事。字符准确率 99%,听起来很高,但一页 1000 个字,那就是 10 个错字,如果错在关键字段上,业务就崩了。字段准确率才是业务真正关心的。
而且,速度和精度是绑定的。你把并行窗口开大,速度上去了,精度可能就掉了。榜单如果只报速度不报对应的精度,或者报的是不同配置下的最优值,那就是在耍流氓。
5.4 一个实用的验证方法
与其信榜单,不如自己搭个小测试。拿你业务里最典型的 50 到 100 页文档,跑一遍,记录端到端时间和字段准确率。这个数字虽然不如榜单好看,但它是你自己的真实基线,后续任何优化都跟它比,才有意义。
我自己的习惯是,每换一个模型或一套配置,都跑一遍这个私有测试集,形成一条性能曲线。时间久了,你对什么配置能带来多少提升,心里就有数了。
6. 常见问题与排查技巧实录
6.1 速度上不去的排查顺序
遇到吞吐不达预期,我一般按这个顺序查:
- GPU 利用率:低于 70% 先查是不是 CPU 预处理拖后腿。
- batch 是否打满:并发请求够不够,调度器有没有空转。
- KV Cache 是否频繁抢占:日志里找抢占记录,有的话说明显存不够。
- 并行解码回退率:回退率过高,说明并行窗口设大了。
- 视觉编码耗时:单独计时,看占比是否异常。
这个顺序是从外到内,先排除工程问题,再怀疑模型配置。
6.2 精度突然下降怎么定位
精度问题比速度问题难查,因为它往往是渐变的。我的排查思路:
- 先看数据:是不是输入分布变了,比如突然来了一批手写件。
- 再看配置:有没有人改了并行窗口或置信度阈值。
- 然后看模型:是不是加载了错误的权重版本。
- 最后看后处理:字段抽取规则有没有被误改。
经验之谈:精度问题十有八九出在数据和配置上,模型本身出问题的概率反而低。所以先查外围,别一上来就怀疑模型。
6.3 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 吞吐低但 GPU 空闲 | 预处理瓶颈 | 查 CPU 占用、图像解码耗时 |
| 吞吐低且 GPU 满 | 解码效率低 | 查并行窗口、回退率 |
| 偶发 OOM | 显存峰值超限 | 降 batch、降 gpu_memory_utilization |
| 精度波动大 | 输入分布变化 | 对比测试集分布 |
| 尾延迟高 | 长文档拖累 | 拆分长文档、限制单页 token 数 |
| 多语言识别差 | 训练数据覆盖不足 | 补充对应语种微调数据 |
6.4 几个我踩过的坑
坑一:盲目追新版本。vLLM 新版本有时会引入回归,我遇到过一次升级后吞吐反而降了 20%,回退版本才恢复。所以生产环境升级前一定要在测试环境跑一遍基线。
坑二:忽略图像预处理的一致性。训练时的预处理和推理时不一致,精度会悄悄掉。比如训练时做了归一化,推理时忘了,模型表现就会打折。
坑三:并行解码在小 batch 下没收益。并行解码的收益依赖一定的 batch 规模,单条请求时反而可能因为回退开销变慢。所以配置要分场景,别一套参数走天下。
坑四:RL 微调后忘了重新校准阈值。RL 会改变模型的输出分布,原来调好的置信度阈值可能就不适用了,需要重新扫一遍。
7. 小模型 OCR 的工程边界与我的实践体会
聊了这么多,回到最开始那个问题:1B 模型把 OCR 跑到 0.7 页/秒,这件事的意义在哪。我的看法是,它证明了小模型在特定任务上可以做到“够用且快”,这对批量文档处理场景是实打实的价值。但它不是万能的,复杂版式、极端长尾、高精度要求的场景,还是得上更大的模型或者混合方案。
我自己在实际项目里的做法是分层:简单文档走小模型快速通道,复杂文档走大模型精修通道,用置信度做路由。这样整体成本和速度都能兼顾。纯靠一个模型打天下,要么成本高,要么精度不够。
最后分享一个我觉得挺有用的小技巧:把 OCR 的置信度输出利用起来。很多模型会输出每个字符或字段的置信度,别浪费这个信息。低置信度的部分单独标记出来,走人工复核或者二次识别,比全量人工检查效率高得多。这个思路在票据、合同这类对准确性要求高的场景里特别管用。
至于榜单,看看就好,别当真。真正靠谱的基线,永远是你自己在真实数据上跑出来的那个数字。