- 0. 基础概念速查
- 1. PaddleOCR-VL 部署
- 2. GLM-OCR 部署
- 3. DeepSeek-OCR 部署
- 4. 横向对比与选型建议
- 5. 常见问题与排错
┌─────────────────────────────────────────────┐ │ 客户端层:做版面分析 + 结果整合 │ │ - PaddleOCR 的 doc_parser │ │ - GLM-OCR 的 glmocr │ │ - 直接调用 VLM(无版面分析) │ └─────────────────────────────────────────────┘ │ HTTP ▼ ┌─────────────────────────────────────────────┐ │ 推理服务层:跑 VLM 模型 │ │ - llama-server(llama.cpp,最快) │ │ - mlx-vlm.server(MLX 框架,更新快) │ │ - Ollama(易用,封装好) │ │ - 模型客户端自己加载(无 server) │ └─────────────────────────────────────────────┘
关键原则:OCR 识别质量主要由 VLM 模型决定,推理框架不同基本不影响识别结果,只影响速度和便利性。
model.safetensors +
.py transformers / PaddlePaddle BF16 原版 MLX safetensors MLX 风格的 safetensors mlx-vlm BF16 / 4bit / 8bit GGUF(主模型)
*.gguf llama.cpp / Ollama BF16 到 Q2(多种量化) GGUF mmproj
mmproj-*.gguf 或
*-mmproj.gguf llama.cpp / Ollama 视觉编码器部分
VLM 必备组件:视觉编码器(mmproj)+ 语言模型。在 GGUF 生态里通常是两个独立文件;在 PyTorch/MLX 里合并在一个 safetensors 里。
~/.paddlex/official_models/ transformers / mlx-vlm
~/.cache/huggingface/hub/ llama-server(-hf 模式)
~/.cache/llama.cpp/ Ollama
~/.ollama/models/
百度的 PaddleOCR-VL-1.5 是目前图片/PDF 转 Markdown 效果最好的开源模型之一,与 GLM-OCR、MinerU 并列第一梯队。
模型特点:0.9B 参数 VLM,SOTA 精度 94.5% @ OmniDocBench v1.5。
- 文档解析是两阶段 pipeline:先用 PP-DocLayoutV3 做版面分析,再对每个版块调 PaddleOCR-VL-1.5 VLM 做识别
- 版面分析部分依赖 PaddlePaddle 框架,VLM 部分可以换不同推理引擎
- Mac Apple Silicon 上原生 PaddlePaddle 跑 VLM 很慢(只能 CPU),推荐把 VLM 外包给 llama.cpp 或 mlx-vlm
不管 VLM 用哪种后端,客户端部分都需要安装:
# 1. 安装 PaddlePaddle 框架(Mac 上用 CPU 版,≥3.2.1) python -m pip install paddlepaddle==3.3.0 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/ # 2. 安装 paddleocr 及其 doc-parser extras python -m pip install -U "paddleocr[doc-parser]"
一行命令,零配置,但速度慢(~99 秒/页)。适合偶尔使用。
paddleocr doc_parser -i /path/to/image.png --save_path ./output
首次运行会自动下载模型到 ~/.paddlex/official_models/:
PP-DocLayoutV3/(版面分析)PaddleOCR-VL-1.5/(VLM,PyTorch 格式)
~16 秒/页,质量**。
准备工作
# 安装 llama.cpp brew install llama.cpp # 下载 GGUF 模型(两种方式) # 方式 1:HuggingFace CLI huggingface-cli download PaddlePaddle/PaddleOCR-VL-1.5-GGUF --local-dir ~/models/PaddleOCR-VL-1.5-GGUF # 方式 2:浏览器手动下载两个文件 # - PaddleOCR-VL-1.5.gguf (主模型) # - PaddleOCR-VL-1.5-mmproj.gguf (视觉编码器)
启动 llama-server
llama-server -m ~/models/PaddleOCR-VL-1.5-GGUF/PaddleOCR-VL-1.5.gguf --mmproj ~/models/PaddleOCR-VL-1.5-GGUF/PaddleOCR-VL-1.5-mmproj.gguf --port 8080 --host 0.0.0.0 --temp 0
⚠️ 注意:
-hf PaddlePaddle/PaddleOCR-VL-1.5-GGUF这种自动下载方式可能无法自动识别 mmproj(因为文件命名是XXX-mmproj.gguf,不是标准的mmproj-XXX.gguf)。推荐手动指定路径。
客户端调用
paddleocr doc_parser -i /path/to/image.png --vl_rec_backend llama-cpp-server --vl_rec_server_url http://127.0.0.1:8080/v1 --save_path ./output
~22 秒/页,比 llama-server 慢一些,但更贴近 MLX 生态。
准备工作
# 从 git 安装 mlx-vlm(PyPI 版可能过旧) pip install git+https://github.com/Blaizzy/mlx-vlm.git
启动 mlx-vlm.server
# 注意:这里不需要指定模型,server 会根据第一次请求的 model 字段按需加载 mlx_vlm.server --port 8111
客户端调用
paddleocr doc_parser -i /path/to/image.png --vl_rec_backend mlx-vlm-server --vl_rec_server_url http://localhost:8111/ --vl_rec_api_model_name PaddlePaddle/PaddleOCR-VL-1.5 --save_path ./output
💡 有意思的细节:
--vl_rec_api_model_name传的是PaddlePaddle/PaddleOCR-VL-1.5(PyTorch 格式),但 mlx-vlm 会自动从 HuggingFace 下载 PyTorch 原版并在加载时即时转换成 MLX 张量。这不是 mlx-community 量化版,是原版被动态转换。
不依赖 PaddlePaddle,只用 transformers 调用 VLM。只能做单块识别(指定 ocr / table / formula 等任务),不支持页级文档解析。
python -m pip install "transformers>=5.0.0"
from PIL import Image import torch from transformers import AutoProcessor, AutoModelForImageTextToText MODEL_PATH = "PaddlePaddle/PaddleOCR-VL-1.5" IMAGE_PATH = "test.png" TASK = "ocr" # 'ocr' | 'table' | 'chart' | 'formula' | 'spotting' | 'seal' PROMPTS = { "ocr": "OCR:", "table": "Table Recognition:", "formula": "Formula Recognition:", "chart": "Chart Recognition:", "spotting": "Spotting:", "seal": "Seal Recognition:", } image = Image.open(IMAGE_PATH).convert("RGB") max_pixels = 2048 * 28 * 28 if TASK == "spotting" else 1280 * 28 * 28 DEVICE = "mps" if torch.backends.mps.is_available() else "cpu" model = AutoModelForImageTextToText.from_pretrained( MODEL_PATH, torch_dtype=torch.bfloat16 ).to(DEVICE).eval() processor = AutoProcessor.from_pretrained(MODEL_PATH) messages = [{ "role": "user", "content": [ {"type": "image", "image": image}, {"type": "text", "text": PROMPTS[TASK]}, ] }] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", images_kwargs={"size": { "shortest_edge": processor.image_processor.min_pixels, "longest_edge": max_pixels }}, ).to(model.device) outputs = model.generate(inputs, max_new_tokens=512) result = processor.decode( outputs[0][inputs["input_ids"].shape[-1]:-1] ) print(result)
智谱的 GLM-OCR 也达到过 SOTA 水平,同样走"版面分析 + VLM"两阶段架构,也使用 PaddlePaddle 的 PP-DocLayoutV3 做版面。
- GLM-OCR SDK(客户端)做版面分析
- VLM 后端可选:云端 MaaS API、本地 MLX、本地 Ollama、vLLM/SGLang(需 GPU)
- SDK 对不同后端做了适配层(
api_mode参数)
GLM-OCR SDK 依赖较老的 transformers,而 mlx-vlm 需要很新的 transformers,两者直接冲突。必须用两个独立 venv,通过 HTTP 通信。
# venv 1: GLM-OCR SDK 客户端 pip install git+https://github.com/zai-org/glm-ocr.git pip install git+https://github.com/huggingface/transformers.git # venv 2: mlx-vlm 推理服务(另一个 venv) pip install git+https://github.com/Blaizzy/mlx-vlm.git
⚠️ 已知问题:实测
mlx-vlm==0.4.4对 GLM-OCR 架构的视觉编码器支持有 bug,会导致模型输出无意义的"境"字。原因是图像没被正确编码(Prompt token 数异常少,仅 17 个)。
解决办法:降级到mlx-vlm==0.3.11(模型转换时用的版本),或等待 mlx-vlm 修复。
启动 mlx-vlm server
mlx_vlm.server --trust-remote-code --port 8080
客户端 config.yaml
pipeline: maas: enabled: false ocr_api: api_host: localhost api_port: 8080 model: mlx-community/GLM-OCR-bf16 # mlx-vlm 需要此字段 api_path: /chat/completions # 注意:去掉 /v1 前缀(SDK 对 mlx-vlm 的特殊处理)
运行
glmocr parse /path/to/image.png --config my_config.yaml --output ./results/
Ollama 官方已打包好 GLM-OCR,无需担心 mmproj 问题。
准备
# 下载 Ollama 桌面版并启动(或用命令行) ollama pull glm-ocr:latest ollama serve # Ollama 桌面版会自动启动,命令行方式需要手动
⚠️ 重要坑:Ollama 默认
num_ctx=2048,对 VLM 图片太短容易截断导致输出错误。必须扩大:cat > Modelfile <
客户端 config.yaml
pipeline: maas: enabled: false ocr_api: api_host: localhost api_port: 11434 api_path: /api/generate # Ollama 原生端点,不是 OpenAI 兼容的 /v1/... model: glm-ocr:latest # 或 glm-ocr-16k(如果创建了自定义 Modelfile) api_mode: ollama_generate # 关键:告诉 SDK 用 Ollama 格式而非 OpenAI 格式
运行
glmocr parse /path/to/image.png --config my_config.yaml --output ./results/
保留 SDK 默认 config.yaml,提供 API key 即可。适合偶尔用或对隐私不敏感的场景。
最简单,只用 VLM 做元素级识别。
pip install git+https://github.com/huggingface/transformers.git
from transformers import AutoProcessor, AutoModelForImageTextToText import torch MODEL_PATH = "zai-org/GLM-OCR" messages = [{ "role": "user", "content": [ {"type": "image", "url": "test_image.png"}, {"type": "text", "text": "Text Recognition:"} ], }] processor = AutoProcessor.from_pretrained(MODEL_PATH) model = AutoModelForImageTextToText.from_pretrained( MODEL_PATH, torch_dtype="auto", device_map="auto" ) inputs = processor.apply_chat_template( messages, tokenize=True, add_generation_prompt=True, return_dict=True, return_tensors="pt" ).to(model.device) inputs.pop("token_type_ids", None) generated_ids = model.generate(inputs, max_new_tokens=8192) output_text = processor.decode( generated_ids[0][inputs["input_ids"].shape[1]:], skip_special_tokens=False ) print(output_text)
方案 A:llama.cpp
新式命令(推荐,mtmd = multimodal):
llama-mtmd-cli -m ~/.cache/huggingface/hub/models--ggml-org--DeepSeek-OCR-GGUF/snapshots/*/DeepSeek-OCR-Q8_0.gguf --mmproj ~/.cache/huggingface/hub/models--ggml-org--DeepSeek-OCR-GGUF/snapshots/*/mmproj-DeepSeek-OCR-f16.gguf --image /path/to/image.png -p "<|grounding|>Convert the document to markdown." --chat-template deepseek-ocr --temp 0
旧式命令(仍可用):
llama-cli -m /path/to/DeepSeek-OCR-Q8_0.gguf --mmproj /path/to/mmproj-DeepSeek-OCR-f16.gguf --image /path/to/image.png -p "<|grounding|>Convert the document to markdown." --temp 0
也可以用 llama-server 方式起 HTTP 服务,和 PaddleOCR 的部署方式相同。
方案 B:Ollama
ollama pull deepseek-ocr:latest ollama run deepseek-ocr:latest "
<|grounding|>Convert the document to markdown. /path/to/image.png"
💡
是 DeepSeek 的特定 prompt 语法,告诉模型图片占位。不同 VLM 的 prompt 语法各不相同。![]()
也可以直接在 Ollama 桌面版 GUI 拖拽图片使用。
- 官方仅支持 CUDA(Nvidia GPU),Apple Silicon 无法直接用官方代码
- mlx-community 社区转换版 可以在 Mac 上跑
使用 mlx-vlm
mlx_vlm.generate --model mlx-community/DeepSeek-OCR-2-bf16 --max-tokens 3000 --temperature 0.0 --image /path/to/image.png --prompt "<|grounding|>Convert the document to markdown."
DeepSeek-OCR 输出会包含 prompt、坐标标记、统计信息等噪声,需要清洗才能得到纯净的 Markdown:
import subprocess import re from pathlib import Path def ocr_image(image_path): """调用 mlx-vlm 做 OCR""" result = subprocess.run( [ "mlx_vlm.generate", "--model", "mlx-community/DeepSeek-OCR-2-bf16", "--image", str(image_path), "--prompt", "<|grounding|>Convert the document to markdown.", "--max-tokens", "4000", "--temperature", "0.0", ], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True ) return result.stdout def clean_ocr_output(text): """清洗 DeepSeek-OCR 的原始输出""" # 1. 删除 prompt 行及之前的所有内容 marker = "<|grounding|>Convert the document to markdown." idx = text.find(marker) if idx != -1: text = text[idx + len(marker):] # 2. 删除统计信息("==========" 之后的所有内容) stats_marker = "==========" idx = text.find(stats_marker) if idx != -1: text = text[:idx] # 3. 删除坐标标记 <|det|>[[...]]<|/det|> text = re.sub(r'<|det|>[[.*?]]<|/det|>', '', text) # 4. 删除非 image 类型的 ref 标签 text = re.sub( r'<|ref|>(?!image|figure_title|figure).*?<|/ref|>', '', text ) # 5. 清理多余空行 text = re.sub(r' {3,}', ' ', text) return text.strip() # 使用 image_path = "/path/to/image.png" output_file = Path("output/result.md") output_file.parent.mkdir(exist_ok=True) raw = ocr_image(image_path) cleaned = clean_ocr_output(raw) output_file.write_text(cleaned) print(f"完成,输出到 {output_file}")
- 官方 GitHub:https://github.com/deepseek-ai/DeepSeek-OCR-2
- 官方模型:https://huggingface.co/deepseek-ai/DeepSeek-OCR-2
- llama.cpp GGUF(第一代):https://huggingface.co/ggml-org/DeepSeek-OCR-GGUF
- MLX 社区(第二代):https://huggingface.co/mlx-community/DeepSeek-OCR-2-bf16
追求** Markdown 还原效果(含表格、公式、版面)
→ PaddleOCR-VL + llama-server 后端(本文方案 1.B)
追求最简单一键部署,偶尔用用
→ Ollama + DeepSeek-OCR 或 GLM-OCR
→ 或者 PaddleOCR 纯本地方案(零配置但慢)
需要图片坐标定位(grounding)
→ DeepSeek-OCR 或 PaddleOCR-VL 的 Spotting 模式
敏感数据,不能上云
→ 任何本地方案都行,选 llama-server 最稳
偶尔几张图,不想折腾
→ PaddleOCR 云 API(注册送 200 页,¥0.09/页超出)
→ 或 GLM-OCR 官方云 API
日常生产 OCR:PaddleOCR-VL + llama-server(速度最快、质量最好、版面完整)
快速测试新模型:Ollama(拖拽 GUI 最爽)
研究 / 微调:transformers + PyTorch 或 mlx-vlm + MLX
原因:图像没有被正确编码成视觉 token,模型相当于"瞎猜"。
诊断:看推理日志里的 prompt token 数。正常 VLM 图片会产生几百到几千个 token,如果只有十几个,说明 mmproj 没工作。
解决:
- 检查 mmproj 文件是否正确加载(
--mmproj参数或日志里的has vision encoder) - 换不同版本的 mlx-vlm(常见:0.4.x 有回归时降到 0.3.11)
- 换模型量化版本(bf16 / Q8 / Q4 依次尝试)
原因:默认 num_ctx=2048 对 VLM 不够用,图像 token 被截断。
解决:用 Modelfile 扩大 ctx 到 16384 或更高。
cat > Modelfile <
原因:uv 默认索引策略严格,找不到子依赖。
解决:加 --index-strategy unsafe-best-match:
uv add paddlepaddle==3.2.1 --extra-index-url https://www.paddlepaddle.org.cn/packages/stable/cpu/ --index-strategy unsafe-best-match
现象:某个项目装了 paddleocr(依赖老 transformers),再装 mlx-vlm 或 mineru 就冲突。
解决:不同用途用不同虚拟环境,通过 HTTP 跨进程通信,不要硬塞一起。
原因:自动识别依赖 mmproj-* 命名前缀。某些 repo 用 *-mmproj.gguf(中缀)命名,识别不了。
解决:手动下载后用 -m + --mmproj 显式指定路径。
现象:“input_tokens”: 17 之类的小数字,明明传了图片。
原因:图片字段格式不匹配当前 server 的实现。不同 server 对 image_url、image、images 等字段支持不一。
解决:
- 先用 server 自带的 CLI 工具(
llama-mtmd-cli、mlx_vlm.generate)绕开 HTTP 层验证模型本身没问题 - 再换 HTTP 请求的图片字段格式
原因:PaddlePaddle 在 Apple Silicon 上只有 CPU 支持,没有 MPS/Metal 加速。
解决:VLM 部分外包给 llama-server 或 mlx-vlm.server,版面分析留给 PaddlePaddle(CPU 跑够用)。
# llama-server (PaddleOCR-VL) llama-server -m ~/models/PaddleOCR-VL-1.5.gguf --mmproj ~/models/PaddleOCR-VL-1.5-mmproj.gguf --port 8080 --host 0.0.0.0 --temp 0 # llama-server 自动下载模式(适用于标准命名的模型) llama-server -hf ggml-org/GLM-OCR-GGUF --port 8080 --host 0.0.0.0 --temp 0 # mlx-vlm.server(模型由首次请求决定) mlx_vlm.server --port 8111 # Ollama(启动 + 拉模型 + 运行) ollama pull glm-ocr ollama run glm-ocr
# paddleocr + 默认本地 VLM(慢) paddleocr doc_parser -i image.png --save_path ./output # paddleocr + llama-server paddleocr doc_parser -i image.png --vl_rec_backend llama-cpp-server --vl_rec_server_url http://127.0.0.1:8080/v1 --save_path ./output # paddleocr + mlx-vlm-server paddleocr doc_parser -i image.png --vl_rec_backend mlx-vlm-server --vl_rec_server_url http://localhost:8111/ --vl_rec_api_model_name PaddlePaddle/PaddleOCR-VL-1.5 --save_path ./output # glmocr + 自定义 config glmocr parse image.png --config my_config.yaml --output ./results/ # llama-mtmd-cli 单次调用(不需要 server) llama-mtmd-cli -m model.gguf --mmproj mmproj.gguf --image image.png -p "OCR:" --temp 0
# 编码图片为 base64 B64=$(base64 -i image.png) # 调 OpenAI 兼容 API curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d "{ "messages": [{ "role": "user", "content": [ {"type": "image_url", "image_url": {"url": "data:image/png;base64,$B64"}}, {"type": "text", "text": "OCR:"} ] }], "temperature": 0 }"
from gguf import GGUFReader
def inspect_gguf(path):
r = GGUFReader(path) groups = {'vision': [], 'mm_proj': [], 'language': [], 'other': []} for t in r.tensors: n = t.name if n.startswith('v.'): groups['vision'].append(n) elif n.startswith('mm.'): groups['mm_proj'].append(n) elif n.startswith(('blk.', 'token_', 'output')): groups['language'].append(n) else: groups['other'].append(n) for k, v in groups.items(): print(f'{k:10s}: {len(v):4d} tensors')
inspect_gguf(‘/path/to/model.gguf’)
本文档基于 Apple Silicon Mac(M4,16GB 内存)实测验证。各模型版本、工具链在快速演进,使用时请以官方最新文档为准。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/280975.html