MinerU 3.0 源码解析
GitHub ↗
Source-code Analysis · 2026-04-13

MinerU 3.0
从 PDF 到 Markdown 的三后端之路

opendatalab 出品的 PDF → Markdown/JSON 解析引擎, GitHub 59.5k stars。本文基于 master 分支(v3.0.9) 浅克隆源码逐行通读,覆盖 VLM / Pipeline / Hybrid / Office 四后端完整链路。

59.5k
GitHub Stars
3.0.9
当前版本
53.5k
LoC 代码量
4
处理后端
109+
支持语言
1.2B
VLM 参数
Python 3.10-3.13 Qwen2-VL-2B 基座 FastAPI + Gradio vllm / lmdeploy / mlx Apache-2.0
🎯

一句话结论

MinerU 3.0 是 目前开源 PDF 解析最完整的生产级方案。它不是靠单一大模型硬抗, 而是把 VLM(Qwen2-VL-2B 微调)+ 传统级联 Pipeline + Hybrid 混合 三条路线打包成可配置的后端,再叠加 Office/DOCX 原生解析。 代码质量成熟,工程化到位(FastAPI 异步任务、Gradio UI、Docker 编排一应俱全), 值得作为子能力接入任何 Agent / RAG 系统。

01 / OVERVIEW

项目总览

一个包、三大后端、七个 CLI、四种推理框架 —— MinerU 把 PDF 解析做成了瑞士军刀。

版本号 v3.0.9

master 分支当前版本。3.0 的核心升级:Office 原生解析、异步任务端点、Pipeline v1.5 benchmark 达 86.2。

mineru/version.py:1

Python 3.10 ~ 3.13

Python 版本门槛抬得不低,用上了新语法(list[str] 原生泛型、str | None 联合类型)。

pyproject.toml:11

7 个 CLI 入口

mineru / mineru-api / mineru-gradio / mineru-router / mineru-vllm-server / mineru-lmdeploy-server / mineru-models-download

pyproject.toml:114-122

📦 依赖矩阵(按后端切分)

Extra触发条件关键依赖场景
[vlm]默认 VLM 后端torch≥2.6 transformers≥4.57.3Qwen2-VL 直接加载
[vllm]高吞吐 VLMvllm≥0.10.1.1多 PDF 并发
[lmdeploy]国产加速lmdeploy≥0.10.2昇腾/MACA
[mlx]macOS ARMmlx-vlm≥0.3.3M1/M2/M3 原生
[pipeline]传统级联onnxruntime paddlepaddle*无 GPU / CPU-only
02 / ARCHITECTURE

四后端并联架构

MinerU 不是一条管线,而是四条 —— 通过 backend 参数切换,共享同一套中间 JSON 格式。

BACKEND 1
VLM (Qwen2-VL-2B)
1,944 LoC · 一步到位 · 需 4GB+ VRAM
BACKEND 2
Pipeline (级联)
3,982 LoC · Layout→OCR→Table→MFR
BACKEND 3
Hybrid
300+ LoC · Pipeline + VLM 互补
BACKEND 4 · 3.0 新增
Office (DOCX / PPTX / XLSX)
2,129 LoC · python-docx + mammoth
UNIFIED
Middle JSON → Markdown / content_list / Images
所有后端产出同一套中间表示,转换器统一

📌 为什么是四后端而不是一个

单一 VLM 快但对 VRAM 有门槛;单一 Pipeline 精但慢且计算量大。给用户留出选择权是工程上更诚实的做法 —— 你有 A100 就跑 VLM,你只有 CPU 就跑 Pipeline。

03 / VLM BACKEND

VLM 后端 · 1.2B 参数的真相

官方宣传的 "1.2B 参数 VLM 超越 Gemini 2.5 Pro",读源码后发现底下是 Qwen2-VL-2B 微调。

# mineru/backend/vlm/vlm_analyze.py:80-102
from transformers import Qwen2VLForConditionalGeneration, AutoProcessor

model = Qwen2VLForConditionalGeneration.from_pretrained(
    model_path,
    device_map={"": device},
    dtype="auto"
)
processor = AutoProcessor.from_pretrained(model_path, use_fast=True)

💡 关键结论

基座是阿里开源的 Qwen2-VL-2B。MinerU 用 6550 万页 PDF 做领域微调,把通用 VLM 调成了文档专家 —— 这是"小模型打败大模型"的典型范例,不靠参数量,靠高质量领域数据。

🚀 四种推理框架并存

transformers

最兼容,本地直接加载 HF 权重。默认选项。

vlm_analyze.py:79-104

vllm

高吞吐,支持 async LLM + 自定义 logits 处理器。

vlm_analyze.py:118-160

lmdeploy

国产加速(TurboMind/PyTorch/MACA),支持昇腾。

vlm_analyze.py:162+

mlx

macOS 13.5+ Apple Silicon 原生,M 系列芯片首选。

vlm_analyze.py:105-113

🔄 推理主流程

PDF 加载 + 图像提取

pypdfium2 把每页渲染成 PNG,分辨率由 --dpi 控制。

doc_analyze() @ vlm_analyze.py:200-300

VLM 单页推理

每页图像 + prompt 送入 Qwen2-VL,产出结构化 JSON(bbox + type + content)。

ModelSingleton @ vlm_analyze.py:40-50

中间 JSON 拼接

单页结果通过 append_page_blocks_to_middle_json() 合并到全文档结构。

model_output_to_middle_json.py:1-153

Markdown 转换

按块类型渲染:公式用 $...$、表格直出、图像嵌路径。

vlm_middle_json_mkcontent.py:25-91

⚙️ Batch Size 自适应

# mineru/backend/vlm/utils.py:94-110
def set_default_batch_size() -> int:
    gpu_memory = get_vram(device)
    if gpu_memory >= 16:
        batch_size = 8
    elif gpu_memory >= 8:
        batch_size = 4
    else:
        batch_size = 1
04 / PIPELINE BACKEND

Pipeline 后端 · 传统级联的完整样本

没有 VLM 之前的老路,到现在依然是精度基准。四个独立模型串起来,每一步都能单独验证。

STEP 1
PP-DocLayout-V2
布局检测 · ONNX
STEP 2
SLANet+ / UNet
表格结构识别
STEP 3
UnimerNet
公式识别 · Swin+mBART
+
STEP 4
PaddleOCR (det → cls → rec)
三阶段文本识别

🧩 核心类 BatchAnalyze

图像掩膜

识别前先做 mask 处理,避免上一步的 bbox 污染下一步。

_apply_mask_boxes_to_image @ batch_analyze.py:63-85

OCR 空块修剪

OCR 出来的空字符串块直接丢弃,减少后续处理噪声。

_prune_empty_ocr_text_blocks @ batch_analyze.py:96-110

表格内联对象提取

表格里可能嵌公式/图像,这一步单独拎出来做二次识别。

_extract_table_inline_objects @ batch_analyze.py:214-302

推理调度 __call__

300+ 行的主循环,协调四个模型的数据流。

__call__ @ batch_analyze.py:303-550

🏷️ Magic Model: 标签 → BlockType 映射

# mineru/backend/pipeline/pipeline_magic_model.py:18-42
PP_DOCLAYOUT_V2_LABELS_TO_BLOCK_TYPES = {
    "image": BlockType.IMAGE,
    "table": BlockType.TABLE,
    "display_formula": BlockType.INTERLINE_EQUATION,
    "text": BlockType.TEXT,
    # ...
}

VISUAL_MAIN_TYPES  = (BlockType.IMAGE, BlockType.TABLE, BlockType.CHART, BlockType.CODE)
VISUAL_CHILD_TYPES = (BlockType.CAPTION, BlockType.FOOTNOTE)

🔧 Batch Size 常量

LAYOUT=1 · MFR=16 · OCR_DET=8 —— 公式识别批量最大(小图多),布局检测串行(大图)。batch_analyze.py:35-47

05 / HYBRID

Hybrid 后端 · 最实用的折中

Pipeline 给结构,VLM 补细节。表格和公式交给 VLM 二次识别,精度和速度都能接受。

# mineru/backend/hybrid/hybrid_analyze.py:1-150
def hybrid_analyze(pdf_bytes, lang_list, parse_method="auto", ...):
    # 1. 判断是否需要 OCR(行 50-58)
    _ocr_enable = ocr_classify(pdf_bytes, parse_method)

    # 2. 需要 OCR 则走 Pipeline OCR
    if _ocr_enable:
        ocr_res_list = ocr_det(...)

    # 3. 表格/公式等难块交给 VLM 二次验证
    # 4. 最后融合中间 JSON

💡 为什么 Hybrid 是默认推荐

单 VLM 的失误主要在表格结构(合并单元格、嵌套表),而 Pipeline 的 SLANet 对结构化表格有很强的归纳偏置。Hybrid 让它们各补各的短板,在 OmniDocBench v1.5 上 Pipeline 后端能打到 86.2 分

06 / OFFICE · 3.0 新增

Office 后端 · DOCX 终于不用转 PDF 了

3.0 版本才加的原生 Office 解析,让 DOCX → Markdown 走纯文本路径,不再经 PDF 渲染。

# mineru/backend/office/docx_analyze.py:11-29
def office_docx_analyze(file_bytes, image_writer=None):
    file_stream = BytesIO(file_bytes)
    results = convert_binary(file_stream)   # DocxConverter
    middle_json = result_to_middle_json(results, image_writer)
    return middle_json, results

python-docx

读 DOCX 底层 XML 结构 —— 段落、表格、图片、样式。

mammoth

把 DOCX 转 HTML 作为中间态,再统一映射到 Middle JSON。

image_writer 注入

图片不内嵌,通过写入器接口输出到外部存储(S3/本地)。

⚠️ 坑位提醒

office_middle_json_mkcontent.py 有 1037 行之长,表格 HTML → Markdown 转换是已知精度瓶颈。复杂表格(跨行跨列、嵌套)建议仍走 PDF→VLM 路径。

07 / OUTPUT

中间 JSON · 统一输出格式

四后端共享同一套 middle_json 数据结构,这是 MinerU 架构能解耦的关键。

# 统一中间表示
middle_json = {
    "meta_info": {...},
    "doc_title": str,
    "doc_layout_result": [...],
    "para_blocks": [
        {
            "type": BlockType,
            "blocks": [
                {
                    "type": BlockType,
                    "lines": [
                        {"spans": [
                            {
                                "type": ContentType,
                                "content": str,
                                "bbox": [x1, y1, x2, y2],
                            }
                        ]}
                    ]
                }
            ]
        }
    ]
}

🌐 多语言智能换行

# mineru/backend/vlm/vlm_middle_json_mkcontent.py:58-90
block_lang = detect_lang(block_text)

if block_lang in {'zh', 'ja', 'ko'}:  # CJK:换行不加空格
    para_text += content
else:                                    # 西文:处理连字符
    if is_hyphen_at_line_end(content):
        para_text += content[:-1]     # 删掉行尾连字符
    else:
        para_text += f"{content} "

🌏 109 语言的真相

README 里的 "109 languages" 对应的是 magika 库的语言识别能力(guess_suffix_or_lang.py:43-54),MinerU 自己只是用 CJK/西文两套换行策略加上 fast-langdetect 做块级识别。

📐 LaTeX 定界符可配置

delimiters = {
    'display': {'left': '$$', 'right': '$$'},   # 行间公式
    'inline':  {'left': '$',  'right': '$'}     # 行内公式
}
# 可通过 config.yaml 改成 \[...\] / \(...\)
08 / DEPLOY

部署形态 · 四种姿势

从命令行到 REST API 再到 Gradio UI 和 Docker 编排,MinerU 把部署路径铺得很全。

CLI 本地

mineru -p input.pdf -o output/ —— 最简单,单文件处理。

mineru/cli/client.py

FastAPI REST 服务化

异步任务队列(3.0 新增),任务 24h 自动清理,支持 ZIP 打包响应。

mineru/cli/fast_api.py:130-149

Gradio UI 交互

网页界面上传 PDF,实时进度展示,结果在线预览。

mineru/cli/gradio_app.py

Docker Compose 生产

多阶段 Dockerfile + API/Router/Redis 编排,带模型下载预热。

docker/

📋 ParseRequestOptions(REST 参数全景)

# mineru/cli/fast_api.py:130-149
@dataclass
class ParseRequestOptions:
    files: list[UploadFile]
    lang_list: list[str]
    backend: str              # "vlm" / "pipeline" / "hybrid-ocr"
    parse_method: str         # "auto" / "txt" / "ocr"
    formula_enable: bool
    table_enable: bool
    server_url: Optional[str]   # 远程 VLM 服务器
    return_md: bool
    return_middle_json: bool
    return_model_output: bool
    return_content_list: bool
    return_images: bool
    response_format_zip: bool

💾 模型下载源

默认走 ModelScope(国内快),可切换 HuggingFace。环境变量 MINERU_MODEL_SOURCE=modelscope,权重缓存在 ~/.mineru/models/vlm/Qwen2-VL-2B/

09 / HIGHLIGHTS

工程亮点

读源码时让我眼前一亮的几处细节 —— 都是只有跑过生产才会做的事。

⚡ 单例 + 线程锁

模型加载用 ModelSingleton._lock = threading.RLock() 保护,避免并发请求重复加载 2B 模型(重载一次就是 4GB VRAM)。

vlm_analyze.py:40-50

🔀 渐进式降级

VLM 服务挂了自动 fallback 到 Pipeline,不让请求直接 500。这是生产级 API 的基本素养。

mineru/cli/common.py

🌏 CJK 换行策略

中日韩自动不加行尾空格,西文智能删连字符 —— 跨语言 Markdown 看起来才正常。

vlm_middle_json_mkcontent.py:58-90

🔄 async doc_analyze

同步+异步两个版本并存,FastAPI 可直接用 aio_doc_analyze() 跑并发推理。

vlm_analyze.py:331-380

📦 中间 JSON 统一

四个后端完全解耦,共享同一套 middle_json,渲染/测试/可视化都能复用,不用每加一个后端重写一套输出。

backend/*/model_output_to_middle_json.py

🧪 全链路 E2E 测试

tests/unittest/test_e2e.py 跑真实 PDF,覆盖 pipeline/vlm/hybrid 三套后端 + txt/ocr 两种解析模式。

tests/unittest/test_e2e.py
10 / PITFALLS

踩过的坑

源码/issue 综合看下来几个容易翻车的点 —— 提前知道就能绕开。

🔥 GPU OOM

默认 batch_size 按 VRAM 自动调,但多页并发 + 大图仍可能爆。手动传 --batch-size 1 最稳。

utils.py:94-110

🔤 全/半角标点混乱

PDF 中混排全角标点,转 Markdown 可能重复。靠 full_to_half_exclude_marks() 做清洗,但仍有边界 case。

utils/char_utils.py

📊 复杂表格精度

SLANet/UNet 对标准表格强,对跨行跨列、嵌套表格识别率下降明显。对策:用 Hybrid 让 VLM 二次验证。

hybrid_analyze.py

🖥️ CPU 模式 VRAM 检测

get_vram() 在 CPU 模式下返回系统 RAM,需要注意别误判。macOS + mlx 则绕过此检测。

utils/model_utils.py
11 / VERDICT

可复用路径

看完源码,想清楚怎么把它接入现有业务 —— 这才是读源码的真正目的。

🎓 法考项目

教材 PDF → Markdown 清洗,走 Pipeline 后端(精度优先),公式/表格保真好,能直接灌入题库。

📊 咨询报告生成

参考报告的 PDF 摄取走 VLM 后端,快速提取结构化大纲,喂给下游 LLM 做改写。

🤖 Hermes / HiClaw

作为子能力暴露 mineru-api REST 给 Agent 调用,DOCX/PDF 双入口,用 Hybrid 兜底。

🎯 接入建议

轻量场景:直接 pip install -U "mineru[vlm]",本地 Qwen2-VL-2B 够用。
服务化:跑 mineru-api + Redis 队列,24h 自动清理,无需造轮子。
多项目共享:独立部署一个 mineru.kang-kang.com,通过 REST 给 Hermes/HiClaw/法考多项目复用,省去每个项目各自部署 2GB 模型。