探索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 上测试的顺序:
- 单页
infer,确认能出字、显存起得来 - 5–10 页
infer_multi,确认跨页格式正常 - 再慢慢加页,同时盯
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的签字和加水印功能,主打一个高清免费易用,可以尝试一下。
我们下期再见。