RTX4090上字节跳动的文档解析模型Dolphin实测

大模型向量数据库机器学习

上一篇《MinerU-利用专用LLM模型提取PDF内容的工具实测》中MinerU的效果基本满足需求。今天来看看另一个文档处理专用模型Dolphin:

picture.image

Dolphin是字节跳动开源的一款轻量级(322M参数)但高性能的文档解析模型,采用 “先解析结构后解析内容” 两阶段范式:

  • 布局分析:首先生成文档元素的自然阅读顺序序列(如标题、表格、公式等),构建结构化骨架;
  • 并行内容解析:基于元素类型和特定提示词(如表格用HTML、公式用LaTeX)并行提取内容,避免传统OCR的错误累积和效率瓶颈。

其优势包括:

  • 高精度:在纯文本和混合元素文档上的编辑距离优于GPT-4.1、Mistral-OCR等模型;

  • 高效率:解析速度达0.1729 FPS,比Mathpix快近2倍;

  • 灵活输出:支持JSON和Markdown格式,适用于学术论文、财务报表等场景。

基本信息

下面我们来试用下

下载模型
  
nohup huggingface-cli download ByteDance/Dolphin --local-dir ByteDance/Dolphin > huggingface-cli.log 2>&1 &  
tail -f huggingface-cli.log

下载完成后查看模型内容, 以我的模型根目录 /data/ai/models 为例:

  
# du -sh /data/ai/models/ByteDance/Dolphin/*  
8.0K /data/ai/models/ByteDance/Dolphin/config.json  
4.0K /data/ai/models/ByteDance/Dolphin/generation_config.json  
759M /data/ai/models/ByteDance/Dolphin/model.safetensors  
4.0K /data/ai/models/ByteDance/Dolphin/preprocessor_config.json  
4.0K /data/ai/models/ByteDance/Dolphin/README.md  
4.0K /data/ai/models/ByteDance/Dolphin/special_tokens_map.json  
3.9M /data/ai/models/ByteDance/Dolphin/tokenizer_config.json  
7.5M /data/ai/models/ByteDance/Dolphin/tokenizer.json

模型权重文件大小只有759M

环境搭建

在干净目录下执行如下命令:

  
mkdir -p pkg  
git clone --depth 1 https://github.com/bytedance/Dolphin.git pkg/Dolphin

将如下内容:

  
FROM pytorch/pytorch:2.5.1-cuda12.1-cudnn9-devel  
COPY pkg /home  
WORKDIR /home/Dolphin  
  
# 设置pip源为阿里云pip源  
ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/  
  
# 安装基础工具  
RUN --mount=type=cache,target=/var/cache/apt \  
    apt update && \  
    apt install -y apt-utils cmake build-essential && \  
    apt install -y vim net-tools telnet curl wget netcat lsof git && \  
    apt install -y libgl1 libglib2.0-0  
  
# 安装 dolphin 及其依赖  
RUN --mount=type=cache,target=/root/.cache/pip ls -l /root/.cache/pip && \  
    pip install -r requirements.txt  
  
# 清空父镜像的 ENTRYPOINT  
ENTRYPOINT []  
CMD []

保存为 dolphin.dockerfile

执行构建命令:

  
nohup docker build -f dolphin.dockerfile -t dolphin:250526_cu121 --push . > build.log 2>&1 &  
tail -100f build.log

其中 250526 是我当时拉取 dolphin 代码的最后一次 commit 时间

构建成功后,启动容器:

  
docker run --name dolphin -itd --gpus '"device=0"' \  
 -e LANG=C.UTF-8 -e LC_ALL=C.UTF-8 \  
 -v /data/ai/models:/models \  
 -v /data/ai/workspace/dolphin:/workspace \  
  dolphin:250526_cu121 bash  
  
docker exec -it dolphin bash  
root@c6c0d5b0a02b:/home/Dolphin#
文档预处理

Dolphin模型只接受图片输入。所以解析 pdf 文档时,需要先将其转换为图片。一小段python代码即可实现。将如下内容:

  
import argparse  
import os  
import fitz  # PyMuPDF: pip install pymupdf   
from pathlib import Path  
  
def pdf_to_images(pdf_path, output_folder, dpi=200):  
    """将PDF每页转换为PNG图片  
  
    Args:  
        pdf_path (str): PDF文件路径  
        output_folder (str): 输出目录  
        dpi (int): 图片分辨率(默认200)  
    """  
    # 创建输出目录(如果不存在)  
    os.makedirs(output_folder, exist_ok=True)  
  
    doc = fitz.open(pdf_path)  
    total_pages = len(doc)  # 提前获取总页数  
  
    try:  
        for page_num in range(total_pages):  
            page = doc.load_page(page_num)  
            pix = page.get_pixmap(matrix=fitz.Matrix(dpi/72, dpi/72))  
            output_path = os.path.join(output_folder, f"page_{page_num+1}.png")  
            pix.save(output_path)  
            print(f"已转换第 {page_num+1}/{total_pages} 页 -> {output_path}")  
    finally:  
        # 确保文档总是被关闭  
        doc.close()  
  
    print(f"转换完成! 共转换 {total_pages} 页")  
  
  
def find_pdf_files(input_dir, max_depth=2):  
    """递归查找目录中的PDF文件  
  
    Args:  
        input_dir (str): 输入目录  
        max_depth (int): 最大递归深度(默认2层)  
  
    Returns:  
        list: 找到的PDF文件路径列表  
    """  
    pdf_files = []  
    input_dir = os.path.abspath(input_dir)  
    base_depth = input_dir.count(os.sep)  
  
    for root, dirs, files in os.walk(input_dir):  
        current_depth = root.count(os.sep) - base_depth  
        if current_depth > max_depth:  
            continue  
  
        for file in files:  
            if file.lower().endswith('.pdf'):  
                pdf_files.append(os.path.join(root, file))  
    return pdf_files  
  
  
def main():  
    # 创建命令行参数解析器  
    parser = argparse.ArgumentParser(  
        description="PDF转图片工具:将PDF每页转换为PNG图片(支持批量处理)",  
        formatter_class=argparse.ArgumentDefaultsHelpFormatter  
    )  
  
    # 参数定义  
    parser.add_argument("input_path", type=str, help="PDF文件路径或包含PDF的目录")  
    parser.add_argument("--output", type=str, default=None, help="图片输出根目录(默认使用输入目录)")  
    parser.add_argument("--dpi", type=int, default=200, help="图片分辨率(DPI)")  
    parser.add_argument("--depth", type=int, default=2, help="递归查找深度(默认2层)")  
  
    # 解析参数  
    args = parser.parse_args()  
  
    # 确定输出目录  
    output_root = args.output if args.output else args.input_path  
  
    # 处理单个文件  
    if os.path.isfile(args.input_path) and args.input_path.lower().endswith('.pdf'):  
        pdf_name = Path(args.input_path).stem  
        output_folder = os.path.join(output_root, pdf_name)  
        pdf_to_images(args.input_path, output_folder, args.dpi)  
        return  
  
    # 处理目录  
    pdf_files = find_pdf_files(args.input_path, args.depth)  
    if not pdf_files:  
        print(f"在目录 {args.input_path} 中未找到PDF文件")  
        return  
  
    print(f"找到 {len(pdf_files)} 个PDF文件")  
    for pdf_path in pdf_files:  
        # 计算相对路径以保持目录结构  
        relative_path = os.path.relpath(pdf_path, args.input_path)  
        pdf_name = Path(pdf_path).stem  
        output_folder = os.path.join(  
            output_root, os.path.dirname(relative_path), pdf_name)  
  
        print(f"\n正在处理: {pdf_path}")  
        pdf_to_images(pdf_path, output_folder, args.dpi)  
  
  
if __name__ == "__main__":  
    main()

保存为 pdf2img.py

安装依赖:

  
pip install pymupdf

将 pdf 文档批量转化为图片,每页对应一张图片:

  
# python pdf2img.py -h  
usage: pdf2img.py [-h] [--output OUTPUT] [--dpi DPI] [--depth DEPTH] input_path  
  
PDF转图片工具:将PDF每页转换为PNG图片(支持批量处理)  
  
positional arguments:  
  input_path       PDF文件路径或包含PDF的目录  
  
options:  
  -h, --help       show this help message and exit  
  --output OUTPUT  图片输出根目录(默认使用输入目录) (default: None)  
  --dpi DPI        图片分辨率(DPI) (default: 200)  
  --depth DEPTH    递归查找深度(默认2层) (default: 2)

命令样例:

  
python pdf2img.py GAOKAO-2025-CME --output GAOKAO-2025-CME-dolphin

其中 GAOKAO-2025-CME 目录放置了2025高考一卷的语数英试卷

解析执行

然后调用 Dolphin 模型来解析上一步中 PDF 文件转换成的图片,命令样例:

  
# python demo_page_hf.py --model_path /models/ByteDance/Dolphin   \  
--input_path "/workspace/GAOKAO-2025-CME-dolphin/images/" \  
--save_dir "/workspace/GAOKAO-2025-CME-dolphin/images/"

执行成功后图片相同目录下,生成2个文件夹:

  • markdown/ 放置每个图片对应的解析后的 Markdown 文本
  • recognition_json/ 放置每个图片对应的布局结构和位置信息,以及文本内容

recognition_json 下的文件样例如下:

  
[    {      "label": "sec",      "bbox": [        693,        466,        2307,        551      ],  
    "text": "2025 年普通高等学校招生全国统一考试 (新高考 I 卷)",  
    "reading_order": 0  
  },  
  {  
    "label": "para",  
    "bbox": [  
      1458,  
      594,  
      1542,  
      650  
    ],  
    "text": "数学",  
    "reading_order": 1  
  },  
。。。

这个数据是包含了原始位置信息的完备数据,可以根据需要,用来做进一步处理。

如果不关心原始位置信息,那么markdown下面的md文件就可以直接使用了。

我们来看下这个数学卷例子中前3道题的效果:

  
## 2025 年普通高等学校招生全国统一考试 (新高考 I 卷)  
  
数学  
  
一、选择题:本题共8小题,每小题5分,共40分.在每小题给出的四个选项中,只有一项是符合题目要求的。  
  
1.(1+5i)i 的虚部为 (大正)  
  
- 4.  
—1  
- B.0  
- C.1  
- D. 6  
- 答案(1+5i)i=-5+i,故虚部为1.故选择:C  
2. 已知集合 $U=\{x \mid x$ 是小于 9 的正整数 $\}, A=\{1,3,5\}$,$\int_{U} A$ 中元素个数为 (  
  
- A. 2  
- B. 3  
- C.5  
- D. 8  
- 答案]$\int_{U}A=\{2,4,6,7,8\}$,5个元素.故选择:  
3. 双曲线虚轴长是实轴长的 $\sqrt{7}$ 倍, 则离心率为 ( )  
  
- A. $\sqrt{2}$  
- B.2  
- C. $\sqrt{7}$  
- D. $2 \sqrt{2}$  
- 答案) b = ${\sqrt {7}}a$ $b^{2}$ = $7a^{2}$ ⇒ e^{2}$ = ${\frac {c^{2}}{a^{2}}}$ = 8 ⇒ e = 2 {\sqrt {2}}$. 故选择:D

Markdown渲染后的效果:

picture.image

基本正确

另一个pdf 解析结果的样例:

  
## 2025年普通高等学校招生全国统一考试(新1卷)  
  
$\star$ 祝大家学习生活愉快  
  
注意事项  
  
1.答卷前,考生务必用黑色字迹的钢笔或签字笔将自己的姓名和考生号,试室号,座位号填写在答题卡上.用2B铅笔将试卷类型和考生号填涂在答题卡相应位置上。  
  
2.选择题每小题选出答案后,用2B铅笔把答题卡上对应的题目选项的答案信息点涂黑:如需改动,用橡皮擦干净后,再填涂其他答案.答案不能答在试卷上。  
  
3.非选择题必须用黑色字迹的钢笔或签字笔作答,答案必须写在答题卡各题目指定区域内相应位置上:如需改动,先划掉原来的答案,然后再写上新的答案,不准使用铅笔和涂改液.不按以上要求作答的答案无效.  
  
一、选择题:本大题共8小题,每小题5分,共计40分.每小题给出的四个选项中,只有一个选项是正确的.请把正确的选项填涂在答题卡相应的位置上.  
  
1. $(1+5 \mathrm{i}) \mathrm{i}_{的虚部为(\quad)}$  
  
A. $-1$  
  
B.  
  
C.1  
  
D.6  
  
【答案】C  
  
【解析】  
  
【分析】根据复数代数形式的运算法则以及虚部的定义即可求出  
  
【详解】因为 $(1+5 \mathrm{i}) \mathrm{i}=\mathrm{i}+5 \mathrm{i}^{2}=-5+\mathrm{i}$, 所以其虚部为 1 ,  
  
故选:C.  
  
2. 设全集 $U=\left\{x \mid x \text { 罂租谗 } \boldsymbol{q} \boldsymbol{\square} \boldsymbol{\square} \boldsymbol{窭窦}\right\}$, 集合 $A=\{1,3,5\}$,${ }_{U} A$ 中元素个数为()  
  
A. 0  
  
B.  
  
C. 5  
  
D. 8  
  
【答案】C  
  
【解析】  
  
【分析】根据补集的定义即可求出,  
  
【详解】因为 $U=\{1,2,3,4,5,6,7,8\}$, 所以 ${ }_{U} A=\{2,4,6,7,8\}, \quad{ }_{U} A$ 中的元素个数为 5 故选: C.  
  
3. 若双曲线 $C$ 的虚轴长为实轴长的 $\sqrt{7}$ 倍, 则 $C$ 的离心率为()  
  
A. $\sqrt{2}$  
  
B.2  
  
C. $\sqrt{7}$  
  
D. $2 \sqrt{2}$  
  
【答案】D  
  
## (解析)  
  
【分析】由题可知双曲线中 $a, b$ 的关系, 结合 $a^{2}+b^{2}=c^{2}$ 和离心率公式求解  
  
【详解】设双曲线的实轴,虚轴,焦距分别为2a,2b,2c, 由题知,$\quad b={\sqrt {7}}a$, 于是$a^{2}+b^{2}=c^{2}=a^{2}+7a^{2}=8a^{2}$,$c=2{\sqrt {2}}a$,$e={\frac {c}{a}}=2{\sqrt {2}}$.

资源峰值:

  
|=========================================+======================+======================|  
|   4  NVIDIA GeForce RTX 4090        On  | 00000000:81:00.0 Off |                  Off |  
| 30%   46C    P2             130W / 450W |   5710MiB / 24564MiB |     42%      Default |  
|                                         |                      |                  N/A |  
+-----------------------------------------+----------------------+----------------------+

实测只需要6G显存就能跑起来了。

玩得愉快。

0
0
0
0
关于作者

文章

0

获赞

0

收藏

0

相关资源
IDC 大模型应用落地白皮书
大模型技术已深度融入业务实践,各企业期望其释放更大商业价值。 但大模型落地之路面临许多挑战和顾虑。 如何精准对接业务需求与发展蓝图,制定切实可行的大模型落地策略? IDC发布首个大模型应用策略与行动指南 一为您揭晓一
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论