最近在本地服务器上部署了 PaddleOCR-VL ,用 vLLM 当 VLM 后端,前面接 PP-DocLayoutV2 做版面分析,跑通了完整的文档解析流水线。这篇把整个过程记下来,环境、命令、报错、解决思路都在,方便后来人对着抄,也给自己留个档。
先说清楚 PaddleOCR-VL 是个什么架构
PaddleOCR-VL 不是单个模型,而是一条两阶段流水线:
- 第一阶段:版面分析,由 PP-DocLayoutV2 负责,把整页图切成一个个区域块(文本、表格、公式、图表),并给出阅读顺序。这一步在本地用 PaddlePaddle 动态图跑。
- 第二阶段:元素识别,由核心 VLM 组件负责。这个 VLM 的全称在新版里是 PaddleOCR-VL-1.6-0.9B,它把一个 NaViT 风格的动态分辨率视觉编码器和 ERNIE-4.5-0.3B 语言模型整合在一起。这一步可以交给 vLLM 这样的推理框架加速。
本文的部署思路就是:版面分析在本地跑,VLM 识别发给 vLLM 服务,两者通过 HTTP 解耦。
还有一点官方反复强调:直接用 vLLM 起一个 VLM 服务、只调那个 0.9B 模型,并不等于跑完整流水线。少了前面的版面分析,复杂文档的解析效果会打折扣,甚至出现阅读顺序错乱。所以要拿到对齐官方示例的效果,PP-DocLayoutV2 这一环不能省。
第一步:建 conda 环境,装 PaddlePaddle 和 PaddleOCR
conda create -n paddleocr python=3.10 -y
conda activate paddleocr
# PaddlePaddle GPU 版,注意 CUDA 源要和本机匹配,我这里是 cu129
python -m pip install paddlepaddle-gpu==3.3.1 -i https://www.paddlepaddle.org.cn/packages/stable/cu129/
# PaddleOCR 的文档解析依赖组,走清华源加速
pip install -U "paddleocr[doc-parser]" -i https://pypi.tuna.tsinghua.edu.cn/simple
几个说明:
Python 版本选 3.10。 PaddleOCR 的扩展依赖组(doc-parser 等)需要 Python 3.9+,3.10 稳妥。
paddleocr[doc-parser] 这个写法。 方括号是 Python 打包的可选依赖组(extras)语法,意思是"装 paddleocr 并额外装上文档解析(版面/表格/公式)功能所需的依赖"。裸装 paddleocr 只有通用 OCR,跑不了 PaddleOCRVL 这条流水线。注意命令里的引号不能省,否则 shell 会把方括号当通配符处理。其他依赖组如下所示:
paddleocr[ie]:信息抽取(KIE,从文档里抽关键字段)
paddleocr[trans]:文档翻译
paddleocr[all]:以上全部
CUDA 源要对齐。 cu129 对应 CUDA 12.9。装之前先 nvidia-smi 看一眼驱动支持的 CUDA 版本上限,选匹配的源。RTX 50 系需要 cu128 或更高,这点务必确认,装错了会出现 GPU 调不起来的问题。
装完验证一下 PaddlePaddle 能不能用 GPU:
import paddle
paddle.utils.run_check()
正常会输出 PaddlePaddle 在 N 块 GPU 上运行正常的提示。这条命令会真在 GPU 上跑个小算子验证,比单纯查环境变量靠谱。
第二步:下载模型到本地
国内直连 HuggingFace 慢,用魔搭(ModelScope)下载,速度好很多:
modelscope download –model PaddlePaddle/PaddleOCR-VL –local_dir /workspace/models/PaddleOCR-VL
下载下来的目录里包含 VLM 权重、config、自定义模型代码,以及 PP-DocLayoutV2 的子目录。后面 vLLM 和 PaddleOCR 都指向这个本地路径,模型只下一次,反复用。
第三步:可选,装 ccache 消除编译警告
跑流水线时会看到一行 warning:
UserWarning: No ccache found. Please be aware that recompiling all source files may be required.
这只是说没装 ccache,PaddlePaddle 现场编译自定义算子时会慢一点,不影响功能,可以忽略。想消掉提示顺带给算子编译加速:
conda install -c conda-forge ccache
第四步:启动 vLLM 服务(VLM 后端)
这里要单独说明 vLLM 的安装。截至目前,官方配方里提到在 vLLM v0.11.1 发布前需要装 nightly 版才能支持这个模型(见引用[2])。如果你的稳定版 vLLM 已经能识别 PaddleOCR-VL,直接用稳定版;不行就装 nightly:
uv pip install -U vllm –pre \\
–extra-index-url https://wheels.vllm.ai/nightly \\
–extra-index-url https://download.pytorch.org/whl/cu129 \\
–index-strategy unsafe-best-match
注意这里 PyTorch 用的是 cu129 wheel,正好匹配 RTX 50 系。建议把 vLLM 装在独立环境里,因为 vLLM 和 PaddlePaddle 的 transformers 依赖版本互相冲突,官方明确建议两者分开装(引用[2])。
用本地路径启动服务:
vllm serve /workspace/models/PaddleOCR-VL \\
–host 0.0.0.0 \\
–port 8000 \\
–served-model-name PaddleOCR-VL-1.6-0.9B \\
–trust-remote-code \\
–max-num-batched-tokens 16384 \\
–no-enable-prefix-caching \\
–mm-processor-cache-gb 0
逐个参数说一下为什么这么设:
- 位置参数直接写本地路径,不要再额外用 –model 重复指定,两者一起写会冲突。
- –served-model-name 这个值是后面所有坑的核心,先记住设成 PaddleOCR-VL-1.6-0.9B,原因见下一节。
- –trust-remote-code 必须开。 模型的 NaViT 视觉编码器和 processor 是仓库里的自定义代码,vLLM 要执行这些 .py 文件才能正确加载模型结构。本地路径同样需要这个参数。
- –no-enable-prefix-caching 和 –mm-processor-cache-gb 0。 官方解释:OCR 任务不像多轮对话,基本不会从 prefix caching 或图像复用中获益,关掉反而避免不必要的哈希和缓存开销(引用[2])。多模态模型开 prefix caching 还容易引发崩溃。
- –max-num-batched-tokens 按硬件能力调,显存大可以往上加换取吞吐。
服务起来后,先确认注册的模型名:
curl http://localhost:8000/v1/models
返回 JSON 里 data[0].id 应该是 PaddleOCR-VL-1.6-0.9B。这一步很重要,是后面排查模型名问题的依据。
第五步:跑完整流水线(踩坑核心)
客户端代码,把版面分析和 vLLM 后端串起来:
from paddleocr import PaddleOCRVL
doclayout_model_path = "/workspace/models/PaddleOCR-VL/PP-DocLayoutV2"
pipeline = PaddleOCRVL(
vl_rec_backend="vllm-server",
vl_rec_server_url="http://localhost:8000/v1",
layout_detection_model_name="PP-DocLayoutV2",
layout_detection_model_dir=doclayout_model_path
)
output = pipeline.predict("/workspace/RDK_X5_GPIO.png")
for i, res in enumerate(output):
res.save_to_json(save_path=f"output_{i}.json")
res.save_to_markdown(save_path=f"output_{i}.md")
这段代码的逻辑:PaddleOCR 流水线在本地做版面检测,把切好的区域块通过 localhost:8000 发给 vLLM 服务识别,最后汇总成 JSON 和 Markdown。
下面是我实际踩的两个坑,串起来看才能理解根因。
坑一:404,vLLM 说模型不存在
第一次跑,服务端我图省事把 –served-model-name 设成了 PaddleOCR-VL,结果:
RuntimeError: Exception from the 'vlm' worker: Error code: 404 –
{'error': {'message': 'The model `PaddleOCR-VL-1.6-0.9B` does not exist.', …}}
日志里还有一行 Creating model: ('PaddleOCR-VL-1.6-0.9B', None, None),说明流水线内部写死了要去请求名叫 PaddleOCR-VL-1.6-0.9B 的模型。而我服务端注册的是 PaddleOCR-VL,两边对不上,vLLM 直接返回 404。
官方文档其实提示过这个错误:遇到模型不存在的 404,要给 vLLM 启动命令加上对应的 –served-model-name(引用[2])。
坑二:换个方向改,变成 ValueError
第一反应是改客户端去迁就服务端,于是我在 PaddleOCRVL() 里塞了个模型名参数指向 PaddleOCR-VL。结果报错变了:
ValueError: No engine bindings registered for model 'PaddleOCR-VL'.
这下才看明白:这个模型名参数不是"发给 vLLM 的名字",而是 PaddleX 用来查它内部引擎绑定的注册名。 PaddleX 的注册表里只有 PaddleOCR-VL-1.6-0.9B 这种官方注册名,我自创的 PaddleOCR-VL 它根本不认识,于是引擎绑定失败。
根因与正解
把两个坑串起来,根因就清楚了:
PaddleOCR-VL-1.6-0.9B 是 PaddleX 写死的官方注册名,改不得。它一身二职——既要在 PaddleX 注册表里查到引擎绑定,又会作为 model 字段发给 vLLM。所以唯一干净的解法是:让服务端去迁就这个名字,客户端完全用默认。
- 服务端:–served-model-name PaddleOCR-VL-1.6-0.9B(就是第四步给的命令)
- 客户端:不要传任何自定义模型名,用上面那段原始代码
两边都对齐到 PaddleOCR-VL-1.6-0.9B 之后,再跑,报错消失,日志正常推进:
Creating model: ('PP-DocLayoutV2', '/workspace/models/PaddleOCR-VL/PP-DocLayoutV2', None)
Creating model: ('PaddleOCR-VL-1.6-0.9B', None, None)
顺带解释下这两行里的 None:这是模型创建函数的参数占位,大致结构是 (模型名, 本地模型目录, 其他可选项)。PP-DocLayoutV2 那行第二位是具体路径,因为它从本地加载权重;VLM 那行第二位是 None,因为它不从本地加载,推理交给了 vLLM 服务。这个 None 恰恰印证架构配对正确——如果这里冒出一个本地路径,反而说明配置错了。
第六步:判断识别效果
流水线跑完会生成 output_0.json 和 output_0.md。
cat output_0.md # 给人看的最终结构化结果
cat output_0.json | python -m json.tool # 带坐标、置信度、类别,排查问题用
判断效果从几个维度看:
- 文本准确率: 识别出的文字和原图逐字对照,看有没有 0/O、1/I/l 这类易混字符出错。
- 完整性: 数一下原图有多少文本块,输出里是否都在,有没有漏识别(尤其边缘、小字、浅色)。
- 版面与阅读顺序: 这是 PaddleOCR-VL 的强项,看 Markdown 里文字排列是否符合逻辑,表格有没有还原成表格结构。这一步检验的是 PP-DocLayoutV2 这一环。
- 误识别: 有没有把线条、图标、噪点"幻觉"成不存在的文字。
小结与几条经验
整个部署的核心其实就一句话:版面分析在本地、VLM 识别交给 vLLM,两者靠模型名串起来,而这个名字必须统一成 PaddleX 的官方注册名 PaddleOCR-VL-1.6-0.9B。
几条给后来人的经验:
引用
[1] PaddleOCR-VL Usage Guide – vLLM Recipes



