云计算百科
云计算领域专业知识百科平台

踩坑实录:离线内网服务器 Docker 部署 PaddleOCR-VL 1.5 完全指南

踩坑实录:离线内网服务器 Docker 部署 PaddleOCR-VL 1.5 完全指南

目标:在公司 完全离线 的内网服务器上部署 OCR 识别服务,供内部人员通过 API 调用。

难点:本地电脑不能访问 GitHub、服务器不能访问外网、公司网络有各种限制、部署者是新手。

整个过程历时 3 天,跨越环境准备、离线传输、配置修正、API 联调四个阶段,最终成功交付。


一、背景与目标

公司有一批维保合同的扫描件,需要自动识别其中的统一社会信用代码、公司名称、登记机关印章、日期等关键字段。这些文件通常带有印章遮挡、排版错位,传统 OCR 很难处理。

我们选择的方案是 百度的 PaddleOCR VL 1.5——这是一个多模态大模型,支持版面分析和文字识别,对复杂排版有很好的鲁棒性。

参考的部署案例来自博客园:iamkun2005 的部署文章。


二、环境与前置准备

2.1 硬件配置

项目配置
服务器 GPU 单张 NVIDIA L20,46GB 显存
本地电脑 Windows + Docker Desktop
操作系统 Ubuntu 24.04 (WSL 2)

虽然不是原作者的双卡 4090,但 46GB 显存完全够用。

2.2 关键限制

  • 本地电脑:公司网络屏蔽了 GitHub,但可以访问国内镜像站(如 Gitee、百度镜像仓库)
  • 服务器:完全无法访问外网,只能通过 U 盘或者内网文件共享传输文件
  • 连接方式:堡垒机登录,无法直接使用 SSH 隧道

2.3 服务器环境验证

部署前需要先确认服务器的基础环境是否就绪:

# GPU 信息
nvidia-smi
# 输出:NVIDIA L20,46GB 显存,CUDA 12.6 ✅

# Docker 版本
docker –version
# 输出:27.3.1 ✅

# NVIDIA Container Toolkit
nvidia-ctk –version
# 输出:1.16.2 ✅

# 检查 daemon.json 中的 nvidia runtime 配置
cat /etc/docker/daemon.json
# 输出:runtimes.nvidia 已配置 ✅


三、第一阶段:本地电脑准备离线包

因为服务器不能联网,所有镜像和配置文件必须在能联网的电脑上准备好,再拿到服务器上部署。

3.1 拉取 Docker 镜像

两个镜像都来自百度的容器镜像仓库(国内地址,不受 GitHub 限制):

# 镜像 1:推理引擎(vLLM 加速)
docker pull ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleocr-genai-vllm-server:latest-nvidia-gpu-offline

# 镜像 2:API 服务(Web 服务器 + 版面分析)
docker pull ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleocr-vl:latest-nvidia-gpu-offline

两个镜像总共约 25GB(压缩后),下载需要 1-2 小时。

3.2 保存镜像为 tar 文件

下载完成后保存为离线文件:

docker save ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleocr-genai-vllm-server:latest-nvidia-gpu-offline o D:\\paddleocr-vlm-server.tar

docker save ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleocr-vl:latest-nvidia-gpu-offline o D:\\paddleocr-vl-api.tar

最终两个文件大小:

  • paddleocr-vlm-server.tar:6.84 GB
  • paddleocr-vl-api.tar:5.91 GB

3.3 获取配置文件

因为 GitHub 打不开,我从Gitee(国内代码托管平台)获取了官方配置文件:

https://gitee.com/paddlepaddle/PaddleOCR/tree/main/deploy/paddleocr_vl_docker/accelerators/nvidia-gpu

核心需要的 3 个文件:

  • .env:定义镜像版本号和环境变量
  • compose.yaml:Docker Compose 编排文件
  • pipeline_config_vllm.yaml:OCR 流水线配置(版面分析、文字识别的具体参数)
  • ⚠️ 关键教训:一开始我手写了一个 docker-compose.yml,但缺少启动命令和环境变量,导致服务起不来。务必使用官方配置文件。


    四、第二阶段:文件传输与服务器加载

    4.1 文件传输

    把本地准备好的 3 个文件传到服务器上:

    • paddleocr-vlm-server.tar
    • paddleocr-vl-api.tar
    • compose.yaml、.env、pipeline_config_vllm.yaml

    我放在了 /root/hjl/paddleocr-deploy/ 目录下。

    4.2 加载镜像

    cd /root/hjl/paddleocr-deploy/
    docker load -i /root/hjl/paddleocr-vlm-server.tar
    docker load -i /root/hjl/paddleocr-vl-api.tar

    验证:

    docker images | grep paddleocr
    # 看到两个镜像,大小分别为 14.4GB 和 10.5GB ✅

    4.3 端口冲突修正

    官方配置文件默认两个服务都用 8080 端口,但服务器上已经跑着 Kubernetes,可能会有端口冲突。我把映射端口改成了:

    • API 服务:宿主机的 6511 → 容器内 8080
    • 推理引擎:不对外暴露(仅内部通信)

    # compose.yaml 修正部分
    paddleocr-vl-api:
    ports:
    "6511:8080" # 原来是 8080:8080

    4.4 防火墙放行

    服务器本地防火墙没有拦截 6511 端口,但公司网络设备(交换机/ACL)限制了非标准端口。最后是提工单请网络管理员放行了 10.13.13.221:6511。


    五、第三阶段:启动服务与验证

    5.1 启动

    cd /root/hjl/paddleocr-deploy && docker compose up -d

    启动很快(约 95 秒),两个容器都是 healthy 状态:

    docker ps
    # paddleocr-vlm-server Up (healthy) 8080/tcp
    # paddleocr-vl-api Up (healthy) 6511→8080/tcp

    5.2 健康检查

    curl http://localhost:6511/health
    # {"errorCode":0,"errorMsg":"Healthy"} ✅

    5.3 验证 GPU 占用

    nvidia-smi
    # GPU 显存占用约 20GB / 46GB,VLLM::EngineCore 占用主要显存 ✅


    六、第四阶段:API 联调(最大的坑)

    6.1 问题:curl 调用返回 422

    我想用文件上传的方式调用:

    curl -X POST http://localhost:6511/layout-parsing -F "file=@/root/hjl/3.jpg"

    返回:

    {"errorCode": 422, "errorMsg": "Input should be a valid dictionary…"}

    6.2 排查发现:API 接受 JSON,不是 Form Data

    查看 OpenAPI 文档:

    curl http://localhost:6511/openapi.json | python3 -m json.tool

    发现 /layout-parsing 接受的是 JSON,file 字段类型是 string,需要传 Base64 编码。

    6.3 正确的调用方式

    最终成功的请求体:

    {
    "file": "Base64编码的图片字符串",
    "fileType": 1
    }

    关键参数:

    • fileType: 0 → 文件路径(如 /home/user/doc.pdf)
    • fileType: 1 → Base64 编码的二进制数据

    💡 核心教训:我在这一步卡了整整 2 个小时,试了纯 Base64、带 data:image/… 前缀、file:// 协议都不行。最后发现是 fileType 参数用错了——传 0 的话 API 会把 base64 字符串当成文件路径去解析,自然找不到文件。

    6.4 最终成功

    Swagger 页面上测试通过:http://10.13.13.221:6511/docs,直接上传图片即可得到识别结果。

    识别的示例(一张设备识别卡):

    [doc_title] 设备识别卡
    [text] SMT-CV-1471
    [text] 型号:YCS-G042
    [text] 厂商:JuDi
    [text] 序列号:0191730
    [text] 固定资产:NA
    [footer] 回非关健设备


    七、总结与经验

    7.1 踩过的坑 & 解决方案

    问题表现解决
    GitHub 无法访问 获取不了配置文件 用 Gitee 替代
    Docker Desktop 安装 权限错误 以管理员身份运行,清理残留
    手写配置文件不完整 缺少启动命令/模型挂载 使用官方配置文件
    端口冲突 默认 8080 被占用 改为 6511 并申请防火墙放行
    API 调用 422 错误 curl 参数格式不对 用 JSON + Base64 + fileType=1
    堡垒机无法 SSH 隧道 端口被公司网络封锁 提工单请管理员放行

    7.2 核心要点

  • 配置文件必须用官方的:手写的缺胳膊少腿,很容易漏掉启动命令和流水线配置
  • 端口冲突要提前规划:尤其是跑着其他服务的生产服务器
  • fileType 参数是调试 API 的关键:0 是文件路径,1 是 Base64 编码
  • 健康检查很重要:vlm-server 加载模型需要时间,API 服务依赖它 healthy 后才能启动
  • Swagger UI 比 curl 方便:FastAPI 自带的界面能自动处理参数校验,调试效率高很多
  • 7.3 部署清单速查

    # 停止服务
    cd /root/hjl/paddleocr-deploy && docker compose down

    # 启动服务
    cd /root/hjl/paddleocr-deploy && docker compose up -d

    # 查看日志
    docker logs paddleocr-vl-api –tail 20
    docker logs paddleocr-vlm-server –tail 20

    # 查看 GPU
    nvidia-smi

    # 健康检查
    curl http://localhost:6511/health


    附录:核心配置文件详解

    以下是部署中用到的三个关键配置文件,带注释说明各参数含义,方便后续维护和调优。

    1. .env — 环境变量文件

    # 设置镜像的后缀标签,与离线镜像的 tag 保持一致
    API_IMAGE_TAG_SUFFIX=latest-nvidia-gpu-offline # API 服务镜像的版本标签
    VLM_BACKEND=vllm # 推理后端,目前仅支持 vllm
    VLM_IMAGE_TAG_SUFFIX=latest-nvidia-gpu-offline # 推理引擎镜像的版本标签

    说明:compose.yaml 会引用这些变量来拼接完整的镜像名称,这样切换版本时只需修改 env 文件。

    2. compose.yaml — Docker Compose 编排文件

    services:
    # ========== OCR API 服务 ==========
    paddleocr-vl-api:
    # 镜像由基础名称 + env 中的标签拼接而成
    image: ccr2vdh3abvpub.cnc.bj.baidubce.com/paddlepaddle/paddleocrvl:${API_IMAGE_TAG_SUFFIX}
    container_name: paddleocrvlapi
    ports:
    "6511:8080" # 将容器内的 8080 端口映射到宿主机的 6511,避免与现有服务冲突
    depends_on:
    paddleocr-vlm-server:
    condition: service_healthy # 必须等推理引擎健康后才启动
    deploy:
    resources:
    reservations:
    devices:
    driver: nvidia
    device_ids: ["0"] # 使用第一块 GPU
    capabilities: [gpu]
    user: root
    restart: unlessstopped
    environment:
    VLM_BACKEND=${VLM_BACKEND:vllm} # 设置推理后端类型
    volumes:
    # 将流水线配置文件挂载到容器内,只读模式
    ./pipeline_config_vllm.yaml:/home/paddleocr/pipeline_config_vllm.yaml:ro
    command: /bin/bash c "paddlex serve pipeline /home/paddleocr/pipeline_config_${VLM_BACKEND}.yaml"
    healthcheck:
    test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]

    # ========== 推理引擎服务 ==========
    paddleocr-vlm-server:
    image: ccr2vdh3abvpub.cnc.bj.baidubce.com/paddlepaddle/paddleocrgenai${VLM_BACKEND}server:${VLM_IMAGE_TAG_SUFFIX}
    container_name: paddleocrvlmserver
    deploy:
    resources:
    reservations:
    devices:
    driver: nvidia
    device_ids: ["0"]
    capabilities: [gpu]
    user: root
    restart: unlessstopped
    healthcheck:
    test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
    start_period: 300s # 模型加载需要较长时间,前 5 分钟不报错

    说明:

    • 两个容器通过 Docker 内部网络通信,推理引擎不对外暴露端口,只接受 API 服务的请求。
    • start_period: 300s 给了推理引擎充足的模型加载时间,避免频繁重启。

    3. pipeline_config_vllm.yaml — OCR 流水线配置

    pipeline_name: PaddleOCRVL1.5 # 流水线名称

    batch_size: 64 # 全局批处理大小
    use_queues: True # 启用任务队列,提高并发性能

    # —- 功能开关 —-
    use_doc_preprocessor: False # 是否启用文档预处理(如校正方向、展平)
    use_layout_detection: True # 是否进行版面分析(检测区域)
    use_chart_recognition: False # 是否识别图表(我们目前用不到)
    use_seal_recognition: False # 是否识别印章(合同中可能有,但这里暂不启用)
    format_block_content: False # 是否对识别结果进行格式化
    merge_layout_blocks: True # 是否合并相邻的相同区域

    # 在 Markdown 输出中忽略的标签类型(如页码、页眉页脚等)
    markdown_ignore_labels:
    number
    footnote
    header
    header_image
    footer
    footer_image
    aside_text

    # —- 子模块配置 —-
    SubModules:
    # 版面检测模块
    LayoutDetection:
    module_name: layout_detection
    model_name: PPDocLayoutV3 # 使用的版面分析模型
    model_dir: null # null 表示从官方缓存目录加载
    batch_size: 8
    threshold: 0.3 # 检测置信度阈值
    layout_nms: True # 启用非极大值抑制,去除重叠框
    layout_unclip_ratio: [1.0, 1.0] # 检测框的扩展比例
    layout_merge_bboxes_mode: # 针对不同标签的合并策略
    0: "union" # abstract
    1: "union" # algorithm
    # …(共 25 个类别,篇幅原因不全部列出)
    24: "union" # vision_footnote

    # 视觉语言识别模块(核心 OCR 模型)
    VLRecognition:
    module_name: vl_recognition
    model_name: PaddleOCRVL1.50.9B # 1.5 版本的 0.9B 参数模型
    model_dir: null
    batch_size: 4096 # VL 模型的推理批大小
    genai_config:
    backend: vllmserver # 使用 vLLM 推理后端
    server_url: http://paddleocrvlmserver:8080/v1 # 推理引擎地址

    # —- 子流水线:文档预处理(如果启用) —-
    SubPipelines:
    DocPreprocessor:
    pipeline_name: doc_preprocessor
    batch_size: 8
    use_doc_orientation_classify: True # 自动校正文档方向
    use_doc_unwarping: True # 自动展平弯曲的文档
    SubModules:
    # 方向分类模型
    DocOrientationClassify:
    module_name: doc_text_orientation
    model_name: PPLCNet_x1_0_doc_ori
    model_dir: null
    batch_size: 8
    # 文档展平模型
    DocUnwarping:
    module_name: image_unwarping
    model_name: UVDoc
    model_dir: null

    # —- 服务配置 —-
    Serving:
    extra:
    max_num_input_imgs: null # 单次请求最大图片数,null 表示不限制

    说明:

    • 这份配置决定了 OCR 引擎怎么识别一张图。例如我们关闭了 use_chart_recognition 和 use_seal_recognition,因为合同场景下它们可能会引入误检。
    • VLRecognition 中的 server_url 必须与 compose.yaml 中的服务名一致(paddleocr-vlm-server:8080),因为 Docker 内部 DNS 会解析这个名称。
    • 如果未来需要识别印章,只需将 use_seal_recognition 改为 True 并重启容器即可。

    版权声明:本文基于真实的部署经历和与 AI 助手的交互记录整理而成,希望对你有所帮助。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 踩坑实录:离线内网服务器 Docker 部署 PaddleOCR-VL 1.5 完全指南
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!