标着「一次读完一整本书」,Unlimited-OCR 仓库里却只放着 329 行 Python

发布于 2026年07月20日 12:54 #Github 解读#OCR 原文链接

标着「一次读完一整本书」,Unlimited-OCR 仓库里却只放着 329 行 Python 封面图
  • Baidu Unlimited-OCR 14688 star 但仓库本体只有 329 行 infer.py 加一个预编译 SGLang wheel,真正的模型权重和实现都放在 HuggingFace 仓库里靠 trust_remote_code=True 拉取,GitHub 仓库本质是部署脚本而非项目本体
  • 核心创新不是重训模型,而是在推理时挂一个 DeepseekOCRNoRepeatNGramLogitProcessor,禁止生成与过去 N 个 token 组成长度 35 的重复 n-gram,用 NO_REPEAT_NGRAM_SIZE=35 配合滑动窗口(单图 NGRAM_WINDOW=128、多页 1024)治住长输出崩坏
  • gundam(base_size=1024、image_size=640、切片)和 base(不切片)两种模式,单图才能用 gundam,多页和 PDF 只能用 base,所谓 long-horizon 是单页内长输出,不是文档级跨页理解
  • 部署强依赖未发布 SGLang commit 的 wheel,page-size=1 是非常规选择,扫描 PDF(issue #16)和 Apple Silicon(issue #18 静默空输出)效果差,issue 区高赞问题普遍无官方回复,贡献者只有 1 个发布账号
  • 迁移价值是把训练问题和推理问题解耦,长输出崩溃不必回炉重训,可在 logit 层面动手术,这个思路比具体 OCR 模型更值得带走

写 OCR 这件事,过去十来年的剧本差不多写死了。你把 PDF 切成一张张图,丢给识别引擎,再把每页文字拼回来,遇到表格、公式、跨页图表,基本就得手兜。2026 年 6 月 18 号,Baidu 丢了个叫 Unlimited-OCR 的项目到 GitHub,README 顶上写着一句很狂的话,Welcome the Era of One-shot Long-horizon Parsing,一次调用、长视角解析的时代到了。

一个月过去,14688 颗 Star、1249 个 Fork、arXiv 论文也挂出来了。但我把它 clone 下来一看,愣了一下,整个仓库的 Python 代码只有 329 行,就一个叫 infer.py 的文件。剩下的,是一个预编译的 SGLang wheel、几张示意图、一个论文 PDF。

大家好,我是若风。这次拆的不是又一个国产 OCR 模型,而是 Baidu 这次很特别的开源姿势,模型本体压根不在仓库里,真正的技术干货藏在 HuggingFace 那边和那 329 行代码的细节里。

一句话定位,「Unlimited」到底 unlimited 在哪

Unlimited-OCR 是一个约 3B 参数的端到端视觉语言模型,基于 DeepSeek-OCR、DeepSeek-OCR-2、PaddleOCR 的思路演进而来(这点 README 的 Acknowledgement 写得很坦白)。它干的事是把一张图直接吃进去,吐出结构化的 Markdown,不是先检测文字框再识别,也不是先 OCR 再版面还原,而是端到端一步到位。

那「Unlimited」unlimited 在哪?核心就一句话,长序列不崩

传统 OCR 模型,输出一长就开始重复、漂移、胡编。这是 LLM 在长生成里的通病,OCR 场景尤其严重,因为文档里本来就有大量重复模式(表格的格子、列表的符号、章节标题),模型很容易陷进一个 n-gram 循环里出不来。Unlimited-OCR 的卖点,就是把这个问题治住了,让你一口气生成 32768 个 token 的输出而不崩。

定位讲完了。真正有意思的是,Baidu 怎么做到的,以及他们为什么把代码这样开源。

仓库里只有 329 行,模型在哪

先把仓库结构摊开看。clone 下来就这些东西,

  • infer.py,329 行,SGLang 并发推理脚本
  • wheel/sglang-0.0.0.dev11416+g92e8bb79e-py3-none-any.whl,一个特定 commit 的 SGLang 预编译包
  • Unlimited-OCR.pdf,论文
  • assets/,logo 和演示 gif
  • README.md,310 行使用说明

没了。没有 model 目录,没有训练代码,没有数据处理脚本,没有 evaluation harness。真正让这个项目跑起来的模型权重和模型代码,全在 HuggingFace 的 baidu/Unlimited-OCR 仓库里,你通过这一行调用拉下来,

model = AutoModel.from_pretrained(
    'baidu/Unlimited-OCR',
    trust_remote_code=True,  # ← 关键
    ...
)

trust_remote_code=True 这一行,就是告诉 HuggingFace,去模型仓库下载并执行 Baidu 提供的 Python 代码(modeling_xxx.py、configuration_xxx.py 那一套)。GitHub 上你看到的这个仓库,本质上是一个推理脚本加部署说明,不是模型本身。

这点很重要,因为它决定了你「拥有」什么。你 clone 下来的是部署工具,不是模型权重,也不是模型实现。Baidu 把模型的运行时控制权牢牢攥在 HF 那边,能随时更新、能随时下架、能改 license 条款(虽然现在是 MIT),你的复现完全依赖 HF 的可用性和 Baidu 的善意。这是 2025-2026 大厂开源的新姿势,项目首页给你看,热度留在 GitHub,控制权留在云端

那这 329 行代码里到底有没有干货?有,而且很硬。

真正的硬核,在那个叫 NoRepeatNGram 的 logit processor 里

Unlimited-OCR 治「长输出崩坏」的核心招,不是靠重训模型,而是靠推理时加一个 logit processor

这是整个项目最值得琢磨的地方。OCR 输出循环重复,本来是个训练问题,模型在训练分布里没见过足够长的输出,confidence 一高就开始自我强化。常规解法是回去重训,做长样本训练、加重复惩罚 loss。Baidu 没这么干,他们选了另一条路,在生成时干预 logit

infer.py 里这几行就是全部玄机,

NO_REPEAT_NGRAM_SIZE = 35
NGRAM_WINDOW = 128   # 单图模式
NGRAM_WINDOW = 1024  # 多页模式

调用的 processor 叫 DeepseekOCRNoRepeatNGramLogitProcessor(注意这个名字,直接点明了血缘,从 DeepSeek-OCR 那条线继承来的)。它干的事很直白,如果模型接下来要生成的 token,会和过去 N 个 token 里任意一段组成一个长度为 35 的重复 n-gram,就把那个 token 的 logit 压成负无穷

意思是,35 个 token 长度的完全重复,直接禁止。

但这里有个坑。如果全局禁止,文档里合法的重复也会被卡死,比如一百个连续的表格分隔符 |---|---|,或者一串连续的页码、列表符号。所以 Baidu 加了滑动窗口NGRAM_WINDOW=128 意味着只在过去 128 个 token 内查重复,更早的不查。这是工程妥协,窗口小误伤少但漏检多,窗口大检得全但容易误伤合法重复。

更妙的是单图和多页用了不同窗口值。单图 128,多页 1024。单图输出密度高、模式紧密,小窗口抓循环足够,且不能误伤密集表格;多页输出稀疏、累积文本长,得用大窗口才能跨段抓到漂移。 这两个数字不是随便填的,是 Baidu 在两类负载上分别调过的。

这个设计给读者一个能带走的判断,长输出崩溃不一定要回炉重训,推理时的 logit 干预是更便宜的解法。你做 Agent 流式输出、做长文 summarization、做代码生成长文件,遇到模型陷进循环,都可以参考这个套路。先别急着重训,试试 n-gram 抑制。这个迁移价值比「又开源了一个 OCR 模型」大得多。

gundam 和 base,两种视觉预处理模式

README 里反复出现 image_mode 这个参数,取值有两个,gundambase。源码注释里写得很清楚,

# gundam: base_size=1024, image_size=640, crop_mode=True
# base:   base_size=1024, image_size=1024, crop_mode=False

差异在于,

  • gundam 把图按 1024 基准缩放后,再用 640 的窗口做切片(crop_mode=True),适合单张高密度图(复杂表格、长截图、海报)
  • base 不切片,整图缩到 1024×1024 直接送进去,适合标准页面(A4、PDF 单页)

几个限制 README 没说直白,

  • 单图才能用 gundam,多页和 PDF 只能用 base(见 infer.pybuild_jobs 函数,多页路径走的是 base)
  • gundam 这个名字很有 Baidu 内部代号的味道,没有任何文档解释来源,我猜是开发阶段的 codename,对应切片重组的意象,但纯属推测

这里要给读者提个醒,「Unlimited」这个词在 PDF 场景下是有水分的。你以为它一口气读完整本 PDF,其实 pdf_to_images() 函数在 infer.py 里把 PDF 用 PyMuPDF 按 DPI=300 切成一张张 PNG,每页单独发一个并发请求,最后你自己拼回去。跨页的语义关系、章节连续性、表格跨页,它一个都没保留。所谓的「long-horizon」,指的是单页内的长输出序列,不是文档级的跨页理解。

这点是我读源码才发现的,README 顶上那个「one-shot long-horizon」的标榜,容易让人误以为是整本 PDF 一次喂进去。

部署那一摊,比模型本身还折腾

把 README 的部署部分读一遍,你会发现跑起来这个模型远比想象中复杂。

三个推理后端,三套依赖

  • Transformers(官方 Python,实验用),需要 torch 2.10.0、transformers 4.57.1,Python 3.12.3 + CUDA 12.9
  • vLLM,要用 Baidu 钦定的 Docker 镜像 vllm/vllm-openai:unlimited-ocr,还分 CUDA 12.9 和 13.0 两个 tag
  • SGLang(生产并发推荐),必须用仓库里那个 sglang-0.0.0.dev11416+g92e8bb79e 的 wheel,这是某个特定 git commit 的开发版,不是 release

最后这一点最要命。SGLang 的正式 release 跑不起来,你必须装仓库 wheel/ 目录下预编译好的那一个。意味着,Baidu 把核心推理逻辑绑死在一个未发布的 SGLang commit 上。一旦这个 commit 在 SGLang 主线被改、或者 SGLang 出了新 release 不兼容,你就只能守着这个 wheel 过日子。

SGLang 启动那一长串参数也很有信息量,

python -m sglang.launch_server \
    --attention-backend fa3 \      # FlashAttention v3
    --page-size 1 \                # 这个非常规
    --mem-fraction-static 0.8 \
    --context-length 32768 \       # 32k 上下文
    --disable-overlap-schedule \   # 关闭调度重叠
    --skip-server-warmup           # 跳过预热

--page-size 1 是个非常规的选择,常规 paged attention 会用 16/32 这种值。我推测是因为他们的 custom logit processor 需要在每一步生成时实时干预,page 太大会影响干预精度,但这点 README 和论文都没明说,属于读者需要自己验证的猜测。

并发模型那一块,infer.py 用的是最朴素的 ThreadPoolExecutorMAX_RETRIES=5,遇到 502 退避重试,没有 asyncio、没有队列、没有动态 batch。所谓「并发」就是同时开 N 个 HTTP 请求打到 SGLang server 上,简单粗暴。对一个标榜工业级的开源项目,这个并发层薄得有点意外。

必须说的几条限制,带证据

老规矩,Baidu 自己不会把这些写在 README 顶上。我从 issue 区和实际代码行为里挖了几条。

扫描 PDF 效果差(issue #16,9 条评论,热度第一)。这是个挺打脸的限制,因为扫描 PDF 是 OCR 最核心的存量场景。README 里演示的 gif 看起来效果惊艳,但 issue 区有人贴出扫描件实测,效果明显下降。Baidu 在 issue 下没有官方回应。

Apple Silicon 静默失败(issue #18,7 条评论)。在 Mac M1/M2/M3/M4 上跑,输出是空的,不报错。根因是 PyTorch 的 masked_scatter 在 Metal 后端的 broadcast 行为和 CUDA 不一致,这是跨后端老问题。但 Baidu 没在 README 里标注 Mac 不支持,开发者要自己踩坑才发现。issue #57、#56 是社区提的修复 PR,到这次拆解时还没合并。

训练代码完全没开源(issue #60,6 条评论)。社区问「能不能放出训练代码」,没回应。结合上面「模型本体不在仓库」这一点,你能复现的只有推理,完全无法复现训练。对一个标榜开源的项目,这个口子开得很大。

手写体效果一般(issue #45,5 条评论)。文档 OCR 和手写 OCR 是两个场景,Unlimited-OCR 主打前者,手写效果跟不上,但 README 没划分能力边界。

语言支持列表缺失(issue #25、#27、#3,合计 15 条评论)。README 通篇没列支持哪些语言,社区反复在问。从 issue 讨论看,中英文效果最好,其他语种没有承诺。

仓库活跃度需要打折看。clone 下来看到的贡献者只有 1 个(Baidu 的发布账号),最后一次 push 停在 2026-07-03,到我拆解这天(2026-07-20)已经 17 天没动过。issue 区高赞问题普遍 6 天以上无官方回复,没有发布过任何 release。对一个发布才一个月的项目,这个维护节奏不太像「持续投入」,更像「发布完就转交社区」的玩法。

你该不该用,什么场景用

收尾给个明确选型结论,而不是又一波吹。

该用的场景

  • 你要做数字原生 PDF(不是扫描件)的结构化抽取,比如把技术手册、论文、产品规格书批量转 Markdown 喂给 RAG,Unlimited-OCR 的端到端 Markdown 输出确实比传统 OCR 加版面还原的两段式省事
  • 你有 NVIDIA GPU(建议 16GB 以上 VRAM,3B 参数 bf16 加上 32k 上下文,实际占用不低),能接受 SGLang 那套部署
  • 你的文档以单页内长输出为主,不需要跨页语义

不该用的场景

  • 扫描件 PDF 占大头,目前效果不稳,等几个版本再说
  • Mac only 的团队,MPS 静默失败这个坑官方还没修
  • 需要训练或微调,代码没开源,只能用原模型
  • 需要跨页理解(表格跨页、章节连续),它的 PDF 处理是页级的

最后一句方法论。Unlimited-OCR 这项目,技术上最值得带走的不是那个 3B 模型,而是它示范了一个范式,把训练问题和推理问题解耦。长输出崩溃,传统思路是回炉重训,Baidu 用一个 35-gram 加滑动窗口的 logit processor,在推理时治住了。下次你的 LLM 应用遇到类似「输出一长就崩」的问题,先别急着重新 fit,想想能不能在 logit 层面动手术。这个思路,比任何一个具体的 OCR 模型都活得长。

评论互动

© 2026 王若风的技术博客 · Powered by Astro