探索OCR:长文档解析,可以看看Unlimited OCR 2

发布时间:2026/11/4 2:31:17
探索OCR:长文档解析,可以看看Unlimited OCR 2

写在前面

今年的前十个月,我一直在 OCR 上折腾:从 PP-OCR 的轻量部署,到 VL 文档解析,再到桌面端、安卓端。我发现这OCR这条路上,有无数的场景和无数的对应方案。每一个工具的出现,都是为了解决一个真实的需求。今天要说的Unlimited OCR 2也一样,它解决的长文档场景。

在上百页的 PDF / 扫描件的场景下,要一口气解析完,准确率要达标,又不能太吃硬件。我们怎么搞?

在之前,我们会先想到切页、拼结果、自己写跨页逻辑。在我们自己构建的流程中,遇到很多问题,也填了很多坑。现在好了,有了新的工具。

百度开源的 Unlimited-OCR(现在主推 Unlimited OCR 2),口号是从 Long-Horizon 走到 Unlimited-Horizon Parsing。它想把超长文档解析从工程缝缝补补,变成模型侧直接提供的能力。

长文档解析的难点在哪里

传统 OCR 流水线很擅长单页图进,文本出。

文档一长,麻烦就来了:表格续接、段落跨页、标题层级要连贯。你把每页单独跑再拼接,模型其实看不到全局。

视觉大模型做文档解析,又常被上下文长度和注意力复杂度卡住。文档越长,算力与显存需求就越高。

Unlimited-OCR 的野心,就是要把长文档一次解析搞定,而不是事后拼接的补丁。

Unlimited OCR 2 厉害的地方:SR-SWA,像人类一样做视觉 Token 选择

README 里提到的 Sparse Reference Sliding Window Attention(SR-SWA),是 Unlimited OCR 2 处理超长文档的核心机制。

它的思路是:在超长序列中,文本和视觉特征不做全量的两两交互,而是先压缩成一组规模固定的 working references(文档示例为 512 tokens 量级)。这样,每一步解码的延迟和显存占用就不会随文档长度线性增长。

这带来的直接好处是:文档页数增加时,复杂度被控制在可控范围内,不需要在工程侧反复切窗、拼接结果。

论文里有一组对比数据就很能说明问题。在百页级的 MarathonDocBench 上:

常规解码:单序列峰值 KV 约 47.00 GB

页级分块 + 复用边界页 KV:峰值 KV 降至约 1.77 GB

并发 16 时,吞吐从约 360 TPS 提升到约 1315 TPS

需要说明的是,1.77 GB 指的是 KV 峰值,不是整卡占用。但对 16GB 显存的私有化部署来说,这组数字意味着:要在消费级显卡上跑长文档,页窗机制必须开启。

我的机器与目标

硬件很简单:

  • GPU:RTX 5060 Ti 16GB
  • 目标:本机私有化,能解析自己的长 PDF / 扫描件

16GB 上的显存账

魔塔(ModelScope)仓库是 PaddlePaddle/Unlimited-OCR-2,权重分两片 safetensors:约 5.0 GB + 1.7 GB,合计约 6.7 GB;dtype 为 bfloat16,架构名 UnlimitedOCRForCausalLM。整仓(含定制 SGLang wheel、tokenizer、演示素材)约 6.9 GB。参数量公开口径是 3B 级。

按论文、README 和魔塔体积,单请求「公开可核对」的地板大致是:

权重:约 6.7 GB(魔塔可核对)

页窗打开后的峰值 KV(论文 batch=1):约 1.77 GB

SAM bias 分配:默认块太大时可能单次冒出约 462 MiB;改成 64 更稳

合计约 9 GB,还没算 activations、视觉 crop、PDF 转图峰值

16GB 扣完这块,余量并不宽裕。所以我要主动砍掉这些会涨显存的东西:

页窗必须开(DOC_LIMIT=1),窗口先偏小

并发先按 1 来

DPI 别无脑顶 300(扫描件尤其吃显存和主机内存)

gundam(切块)能少用就少用;先 base / 少页验证

max_tokens、单页输出封顶设清楚

两条路径:我怎么在 5060 Ti 上落地

第一步:Transformers 冒烟

官方环境:Python 3.10,torch==2.13.0、transformers==4.52.1等。加载baidu/Unlimited-OCR-2,bfloat16,.cuda()。

我在 5060 Ti 上测试的顺序:

  1. 单页infer,确认能出字、显存起得来
  2. 5–10 页infer_multi,确认跨页格式正常
  3. 再慢慢加页,同时盯nvidia-smi

第二步:SGLang 私有化服务

这里才是真·折腾现场,也是我私有化成功的主路径。

要点:

  • 必须用仓库 / 模型页提供的定制 SGLang wheel,别默认通用 vLLM / 随便一个 SGLang 就能同款复现
  • 再加pymupdf、fastokens
  • 服务起来后走 OpenAI 兼容接口,PDF 先转图再请求
  • 离线环境把权重 + wheel一起放进本地制品库

显存相关设置

下面这些是 README 里明确跟内存挂钩、而我在 5060 Ti 上真正在意的配置项。

页窗(16GB 的关键)

--uocr-gundam-page-window和--uocr-base-page-window默认都是 1。官方示例里可能设得更大,16GB 上要先保守,确认稳定后再往上加。

SAM bias 块大小

SGLANG_OPT_UOCR_SAM_REL_POS_BIAS_MB=64。默认 512 时文档说可能冒出约 462 MiB 的大块分配,直接 OOM。这个我一上来就改成 64。

静态显存占比

--mem-fraction-static 0.7。16GB × 0.7 ≈ 11.2GB 静态池;扣掉约 6.7GB 权重,留给 KV 和余量大概4–5GB。所以,如果在 16GB 的卡上把比例调得太高(比如 0.75),缓冲空间会被进一步压榨,并发一高或者页窗一开大,极容易直接爆显存(OOM)。

prefill

--max-prefill-tokens先用 2048 量级,别一上来 4096。它会拉临时大张量,显存近似线性涨。--cuda-graph-bs-prefill的最大桶要和它对应对齐,否则回退 eager 或白占捕获内存。

共享内存与碎片

SGLANG_UOCR_SHM_WRAP_MODEL_DATA=1,大 crop 走共享内存。PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,减轻大块连续分配碎片。

输出预算

--uocr-max-page-tokens在启动时设上限(0 表示不限,16GB 上建议显式给值),请求里的max_tokens也一定要显式设。宁可先保守,再按业务加。

一句话总结我在 5060 Ti 上的策略:

页窗开、窗口保守、bias=64、mem-fraction=0.7、并发=1、先少页后加页。

Windows PowerShell 配置方式

先说明一个现实:SGLang 原生 Windows 支持存在较多坑,社区验证的可用路径主要是 WSL2 或 Docker。

环境变量(PowerShell 语法,和 Linux 的export不同)

$env:PYTORCH_CUDA_ALLOC_CONF ="expandable_segments:True"$env:SGLANG_UOCR_SHM_WRAP_MODEL_DATA ="1"$env:SGLANG_OPT_UOCR_SAM_REL_POS_BIAS_MB ="64"

启动命令(PowerShell 续行用反引号`)

python-m sglang.launch_server `--model-path C:\models\Unlimited-OCR `--mem-fraction-static 0.7 `--uocr-gundam-page-window 1 `--uocr-base-page-window 1 `--uocr-max-page-tokens 2048 `--max-prefill-tokens 2048 `--cuda-graph-bs-prefill 2048 `--port 30000

如果原生 Windows 起不来,退回 WSL2。在 WSL2 里用 Linux 的export写法即可,命令和上面一致。

请求时的max_tokens不在启动配置里,而是每次调用 API 时放在 JSON body 里,比如"max_tokens": 2048。

适合谁,不适合谁

适合你的情况大概是这些:

你已经在做文档解析 / RAG 前置,被长 PDF、扫描件、跨页表格折磨过。
你有一张16GB左右的 NVIDIA 卡(比如我这张 5060 Ti),愿意按保守配置把服务跑起来。
你要的是本机私有化、数据可控。
你关心整本解析的连贯性。

不太适合的情况:

你只需要拍一张图识几个字,PP-OCR / OnnxOCR 才是更好的选择。
你要的是端侧实时帧率,那根本不是同一类问题。

写在最后

如果你只是想简单的识别文字和表格,可以使用作者的小程序LINC扫描王更省心省力。现在的扫描功能支持了扫描后PDF的签字和加水印功能,主打一个高清免费易用,可以尝试一下。

我们下期再见。