共绩科技
共绩科技

预制服务

共绩算力 MiniCPM-OCR 1.0 使用最佳实践

一、MiniCPM-OCR 是什么

MiniCPM-OCR 1.0 是 OpenBMB 的 1.3B OCR 专用模型,支持中英文文本、表格、公式、印章、图表和关键信息抽取(KIE)。当前镜像使用 vLLM 提供 OpenAI 兼容接口,并配置了配套的 MiniCPM-OCR-1.0-DSpark 草稿模型。

使用方式客户端是否需要版面模型输入与场景输出
WebUI否浏览器快速体验、检查连接与图片识别页面展示识别文本
OpenAI API否单张票据、卡证、已裁切文本/表格/公式区域、KIE文本或待校验的 JSON 文本
minicpm-ocr CLI是命令行处理 PDF、复杂排版整页图片Markdown 文件
minicpm-ocr Python SDK是Python 业务系统、任务队列、RAG 入库流水线Markdown 与中间产物

二、从预制服务启动 MiniCPM-OCR

01. 进入弹性部署服务并新增任务

访问弹性部署服务页面,在左侧点击“新增部署任务”,进入任务创建页面。若页面跳转到登录,先完成账号登录。

截图

图:进入“新增部署任务”页面,依次选择计算资源和预制服务。

02. 选择计算资源

在“选择 GPU 型号”区域筛选地域,优先选择 四川五区、新疆一区、浙江四区。这三个区域已默认完成 MiniCPM-OCR 镜像预热,可减少镜像拉取等待。

卡数从 1 卡开始,先以单节点验证识别效果。可根据实时可用资源选择四川五区 RTX 4090(24 GB)、新疆一区 RTX 5090(32 GB)或浙江四区 RTX 4090-48G(48 GB)。核对显存、CPU、内存、库存与价格后,点击对应资源卡片,再向下滚动到“服务配置”。

截图

图:以四川五区 RTX 4090 单卡为例;资源规格、库存和价格以创建时控制台显示为准。镜像预热后仍需等待资源调度与模型初始化。

03. 选择 MiniCPM-OCR-1.0 预制服务

在“服务配置”中保持“预制服务”标签,找到并点击 MiniCPM-OCR-1.0 卡片,使用平台提供的预制镜像和默认配置。首次使用无需手工填写镜像地址或启动脚本。

截图

图:选中 MiniCPM-OCR-1.0 后,卡片边框高亮。卡片说明中列出了两个服务端口:80 提供 WebUI,8000 提供 OpenAI 兼容 API。

在“节点配置”中先保持 1 个节点,核对计算资源与服务选择;阅读服务协议并确认同意后,点击“部署服务”。

04. 等待实例就绪

部署后返回任务列表或任务详情,观察节点状态与启动日志。镜像拉取、模型加载和运行时初始化完成后,在“常规 → 快捷访问”中获取端口访问地址:

  • 80 端口:WebUI,用于浏览器上传图片和查看识别结果。
  • 8000 端口:推理 API,OpenAI SDK 的基础地址为该端口访问地址加 /v1;minicpm-ocr CLI / SDK 使用该端口的服务根地址。

截图

图:任务详情中的运行节点与快捷访问入口。已有示例服务可直接打开任务详情,继续下一节体验识别。

节点显示“运行中”后,仍需通过 8000 端口的 /v1/models 确认模型 minicpm-ocr 已加载,再完成一次实际图片识别。WebUI 能打开只说明页面服务可访问。具体连通性检查见第四节;新建任务的访问域名以自己的“快捷访问”为准。

三、快速体验:浏览器完成一次图片识别

打开本次服务80端口的 url。

  1. 点击“加载示例图”,或通过“选择图片”上传自己的图片。
  2. 选择识别类型;先从“文本识别”开始。
  3. 展开“接口设置”,确认 API 地址为下表中的地址、模型名为 minicpm-ocr。本次服务无鉴权,API Key 留空。
  4. 点击“开始识别”,在右侧读取并复制结果。
项目本次服务地址 / 值
WebUIhttps://deployment-452-99uowetb-80.550w.link
服务根地址 OCR_SERVER_URLhttps://deployment-452-99uowetb-8000.550w.link
OpenAI 基础地址 OCR_BASE_URLhttps://deployment-452-99uowetb-8000.550w.link/v1
模型名 OCR_MODELminicpm-ocr

截图

图 3:本次实际识别结果为 HELLO 123,页面报告约 0.9 秒、289 TOKENS。这是示例图片的一次页面观测,不代表复杂 PDF 的解析速度或模型准确率;页面 token 统计也不应直接当作输出 token 数。

四、前置准备与连通性验证

4.1 配置地址、密钥与模型名

# CLI / SDK 使用根地址,不带 /v1
export OCR_SERVER_URL="https://deployment-452-99uowetb-8000.550w.link"

# OpenAI SDK 使用包含 /v1 的基础地址
export OCR_BASE_URL="${OCR_SERVER_URL%/}/v1"
export OCR_API_KEY="EMPTY"   # 当前服务无鉴权;开启后替换为真实密钥
export OCR_MODEL="minicpm-ocr"

服务重建或地址变化后,重新从“快捷访问”复制 8000 端口地址。不要将 80 端口地址、完整 /chat/completions 路径或客户端本机的 127.0.0.1 作为远程 SDK 根地址。

4.2 检查模型与上下文上限

curl -fsS --max-time 30 "${OCR_BASE_URL%/}/models" \
  -H "Authorization: Bearer ${OCR_API_KEY}"

本次接口实际返回的关键字段如下(省略权限等无关字段):

{
  "object": "list",
  "data": [{
    "id": "minicpm-ocr",
    "root": "/models/OpenBMB/MiniCPM-OCR-1.0",
    "max_model_len": 16384
  }]
}

五、方式一:通过 OpenAI API 识别图片

5.1 安装与调用

python -m pip install openai

准备 PNG 或 JPEG 图片 sample.png,保存下列代码为 ocr_image.py,然后运行 python ocr_image.py。

import base64
import os
from pathlib import Path

from openai import OpenAI

client = OpenAI(
    api_key=os.environ.get("OCR_API_KEY", "EMPTY"),
    base_url=os.environ["OCR_BASE_URL"].rstrip("/"),
    timeout=180.0,
    max_retries=2,
)


def recognize_image(image_path, prompt="Text Recognition:", max_tokens=4096):
    path = Path(image_path)
    mime = {".png": "image/png", ".jpg": "image/jpeg",
            ".jpeg": "image/jpeg"}.get(path.suffix.lower())
    if mime is None:
        raise ValueError("示例仅接受 PNG / JPEG,请先转换图片格式")
    image_b64 = base64.b64encode(path.read_bytes()).decode("ascii")

    response = client.chat.completions.create(
        model=os.environ.get("OCR_MODEL", "minicpm-ocr"),
        messages=[{"role": "user", "content": [
            {"type": "image_url", "image_url": {
                "url": f"data:{mime};base64,{image_b64}"
            }},
            {"type": "text", "text": prompt},
        ]}],
        temperature=0.01,
        top_p=0.001,
        max_tokens=max_tokens,
        stream=False,
        extra_body={
            "top_k": 1,
            "chat_template_kwargs": {"enable_thinking": False},
            "mm_processor_kwargs": {
                "downsample_mode": "4x", "max_slice_nums": 9
            },
        },
    )
    if not response.choices:
        raise RuntimeError("服务没有返回 choices")
    choice = response.choices[0]
    if choice.finish_reason == "length":
        raise RuntimeError("OCR 输出已截断:增加输出预算或缩小识别区域")
    if choice.finish_reason != "stop":
        raise RuntimeError(f"请检查异常结束状态:{choice.finish_reason}")
    text = choice.message.content or ""
    if not text.strip():
        raise RuntimeError("OCR 结果为空,请检查图片、提示词和服务日志")
    return text


if __name__ == "__main__":
    print(recognize_image("sample.png"))

图像放在文本提示词之前。mm_processor_kwargs 依赖服务端启用 --trust-request-mm-kwargs;当前启动配置已包含此项。普通接入可以先保持镜像的视觉参数,不必反复修改。

5.2 按任务使用标准 Prompt

任务Prompt(保留冒号)处理建议
文本Text Recognition:读取识别文本;保留原始换行供后续校验
表格Table Recognition:原始表示以模型返回为准;整篇文档建议由 SDK 转成 HTML 表格
公式Formula Recognition:检查 LaTeX 的括号、上下标与完整性
印章Seal Recognition:检查印章文字与方向
图表Chart to Table:核对数值、单位与图例对应关系

5.3 小例子:从票据抽取 JSON 字段

沿用上面的 recognize_image(),将提示词替换为字段抽取指令:

import json

keys = ["store_name", "invoice_no", "total_amount", "date"]
kie_prompt = (
    'Extract the "store_name", "invoice_no", "total_amount", "date". '
    'Format the output as a JSON object with keys: '
    '"store_name", "invoice_no", "total_amount", "date". '
    'If an item is not in the image, use "###".'
)

raw = recognize_image("invoice.png", prompt=kie_prompt, max_tokens=2048)
data = json.loads(raw)  # 无效 JSON 应记录为待复核,不能静默忽略
if not isinstance(data, dict) or set(data) != set(keys):
    raise ValueError("字段结构不符合预期")
print(data)

两处字段名必须一致。### 表示图片中缺失的信息,不应当作真实业务值。金额、日期、票号还需校验格式和业务规则;生产系统应保留原图与原始响应以便复核。

六、方式二:通过 CLI 将 PDF 转为 Markdown

6.1 创建独立的客户端环境

SDK 与远程 vLLM 服务使用独立环境。以下版本沿用官方推理指南的可复现安装示例:

pip install uv
uv venv .minicpm-ocr --python 3.12 --seed
source .minicpm-ocr/bin/activate
python -m pip install "minicpm-ocr[all]==1.0.2"

按客户端环境二选一安装 PaddlePaddle,不要在同一环境同时安装 CPU 和 GPU 两个包:

# CPU 版面检测
python -m pip install "paddlepaddle==3.2.1"
# 兼容的 Linux NVIDIA GPU 客户端:GPU 版面检测
python -m pip install "paddlepaddle-gpu==3.3.0" \
  -i https://www.paddlepaddle.org.cn/packages/stable/cu129/

GPU wheel、驱动与系统架构应匹配;没有对应 wheel 的开发机可改用兼容的 Linux 客户端。先运行 minicpmvocr parse --help 核对当前版本参数,再准备待解析的 doc.pdf。

6.2 先解析一页,再处理全文

export PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK=True

minicpmvocr parse \
  --backend vllm \
  --device cpu \
  --server-url "${OCR_SERVER_URL}" \
  --model "${OCR_MODEL}" \
  --workers 4 \
  --set engine.api_key="${OCR_API_KEY}" \
  --set engine.max_model_len=16384 \
  --set input.limit=1 \
  --input doc.pdf \
  --output doc.md

检查 doc.md 的阅读顺序、表格、公式和缺漏。通过后移除 --set input.limit=1 解析全文。客户端有兼容的 NVIDIA GPU 时,将 --device cpu 改为 --device cuda:0;远程识别仍由共绩节点的 GPU 执行。

初始使用 4 个识别请求并发是保守的调试建议,并非服务端并发上限。稳定后可尝试 8、16、32,比较每分钟完成页数、请求尾延迟、错误率与显存,再选择合适的值。

6.3 固定版面模型路径

首次运行会准备 PP-DocLayoutV3 权重。生产环境建议提前下载并固定目录,避免工作目录变化导致重复下载:

mkdir -p ./ocr-assets
curl -fL -o ./ocr-assets/PP-DocLayoutV3_infer.tar \
  https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-DocLayoutV3_infer.tar
tar xf ./ocr-assets/PP-DocLayoutV3_infer.tar -C ./ocr-assets

export OCR_LAYOUT_MODEL_DIR="$(pwd)/ocr-assets/PP-DocLayoutV3_infer"
export PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK=True

后续解析命令可显式追加 --layout-model "${OCR_LAYOUT_MODEL_DIR}"。禁用模型源检查不等于下载好了权重;离线环境必须提前准备完整模型目录。

6.4 常用参数

参数用途与建议
—server-urlvLLM 服务根地址,不带 /v1;每次命令显式提供
—model显式提供 minicpm-ocr,与模型列表一致
—device仅选择本地版面检测设备
—workers文档块识别请求总并发,按实测逐步调整
—layout-model使用提前准备好的版面模型目录
—set engine.max_model_len=16384与当前服务上下文保持一致
—set pipeline.merge_cross_page_tables=true按需开启跨页表格合并;验收表头、列对齐与误合并
—set engine.api_key=…服务启用鉴权后传入真实密钥

多个实例应使用相同模型名和权重;SDK 支持重复传入 --server-url,其 workers 是多个实例共享的请求总并发。共绩任务本身已经有统一访问地址时,先使用任务地址即可。

七、方式三:通过 Python SDK 集成文档解析

完成第六节的依赖安装后,可保存下列代码为 parse_pdf.py。示例使用 CPU 版面检测、4 个请求并发、16384 上下文,并可按需合并跨页表格。

import os
from importlib.resources import files
from pathlib import Path

from minicpmvocr import (
    assemble_document_markdown,
    load_config,
    load_prompt_map,
    pipelined_run,
)
from minicpmvocr.layouts.download import ensure_layout_model


def parse_pdf_to_markdown(pdf_path="doc.pdf", output_path="doc.md"):
    layout_path = Path(os.environ.get(
        "OCR_LAYOUT_MODEL_DIR", "models/PP-DocLayoutV3_infer"
    )).resolve()
    layout_model = ensure_layout_model(layout_path)
    config_path = files("minicpmvocr").joinpath("configs/minicpmvocr.yaml")
    cfg = load_config(str(config_path), overrides={
        "input": {"file_path": str(Path(pdf_path).resolve())},
        "engine": {
            "type": "vllm",
            "server_url": os.environ["OCR_SERVER_URL"].rstrip("/"),
            "model_name": os.environ.get("OCR_MODEL", "minicpm-ocr"),
            "api_key": os.environ.get("OCR_API_KEY", "EMPTY"),
            "max_model_len": 16384,
        },
        "layout": {
            "model_dir": str(layout_model),
            "device": os.environ.get("OCR_LAYOUT_DEVICE", "cpu"),
        },
        "pipeline": {
            "overlap_layout": True,
            "max_workers": 4,
            "merge_cross_page_tables": True,
        },
    })

    pipelined_run(cfg)
    markdown, page_count, merge_count = assemble_document_markdown(
        Path(cfg["_meta"]["run_dir"]),
        load_prompt_map(cfg),
        merge_tables=cfg["pipeline"]["merge_cross_page_tables"],
        delimiter="\n\n<!-- page-break -->\n\n",
    )
    if not markdown.strip():
        raise RuntimeError("解析结果为空,请检查逐页产物和日志")
    target = Path(output_path)
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(markdown, encoding="utf-8")
    print(f"页数={page_count},跨页表格合并={merge_count},输出={target}")
    return markdown


if __name__ == "__main__":
    os.environ.setdefault("PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK", "True")
    parse_pdf_to_markdown()

执行:python parse_pdf.py。首次可先在 input 配置中加入 "limit": 1 调试,验收后删除。

默认中间产物位于 runs/<run_tag>/,包含逐页结果及运行记录;可用 OCR_RUN_ROOT 或 run.run_root 设置持久化目录。CLI 使用 --run-tag my_task_001,SDK 使用 "run": {"run_tag": "my_task_001"} 固定任务目录。已核对 1.0.2 源码:同一页的 output/<image_id>.md 与 results_json/<image_id>.json 均存在时,流水线会跳过该页识别。续跑必须保持输入、配置和模型版本一致;修改输入或希望重新识别时使用新的 run_tag,避免误用旧结果。