作者​:昇腾实战派
知识地图​:https://blog.csdn.net/Lumos_Lovegood/article/details/161601003

一. 简介

MinerU是一个高质量的开源工具,能够将包含图片、表格、公式等元素的多模态PDF文档转化为清晰、易于分析的Markdown格式,同时也支持从包含广告等干扰信息的网页中快速解析、抽取正式内容,并将其批量转化为Markdown格式,本文基于昇腾平台,针对 MinerU 的多个核心模块进行适配与优化,旨在提升整体推理效率。

推理流程:https://gitee.com/ascend/ModelZoo-PyTorch/tree/master/ACL_PyTorch/built-in/ocr/MinerU

二. 模型结构

模型适配主要分5个模块:Layout布局检测,MFD公式检测,MFR公式识别,OCR检测/识别,Table识别。

在这里插入图片描述

模块化处理流程

  1. ​**Layout Predict(DocLayoutYoLoModel)**​:识别文本块、图片、表格等区域。
  2. ​**MFD(YOLOv8)/MFR Predict(UnimernetModel)**​:检测并识别公式内容。
  3. ​**OCR-det/rec(Paddle2OCR)**​:文本行定位与OCR识别。
  4. ​**Table(RapidTable)**​:表格识别
  5. 后处理​:整合结果生成结构化输出。

三. 适配流程

模型层面,针对每个模块分别进行torchair优化,通过成图解决host bound问题,使用指导见链接:https://gitee.com/ascend/ModelZoo-PyTorch/tree/master/ACL_PyTorch/docs/torchair ,其他优化包括去除冗余运算,NPU下沉,替换融合算子等

1. Layout

Layout采用基于YOLOv10的DocLayoutYoLoModel进行布局检测,其核心流程包括 前处理,推理和后处理

通过采集profiling,发现前处理和推理时间占比最高,重点考虑这两部分的优化。

1.1 前处理

通过profiling发现,前处理的大多数操作位于CPU上,并且在前处理中存在冗余的数据同步操作,修改对应代码,让更多算子运行在NPU上,并且去除部分冗余操作,提升性能
在这里插入图片描述
在这里插入图片描述

1.2 推理阶段

采用适配torchair的方式减少cpu下发损耗,为此对于模型进行如下修改,实现整图下发。

1.2.1 模块修改
  • 修改doclayout_yolo/nn/modules/block.py,

在这里插入图片描述

其中 y.extend(self.m(y[-1]) for m in sefl.m) 使用了​​生成器表达式, Dynamo对该代码结构的​​静态分析中,并不会更新每次计算后的y[-1]导致生成相同的输出,导致语义不符,产生精度问题,通过显示循环展开来等价重写即可。

  • 修改模型中的数据依赖操作

在这里插入图片描述

修改文件ultralytics/ultralytics/utils/tal.py,make_auchors生成anchors先验框:

stride_tensor.append(torch.full((h * w, 1), stride, dtype=dtype, device=device))

需修改为数学上等价的写法:

stride_tensor.append(torch.ones((h * w, 1), dtype=dtype, device=device) * stride)

不修改的话会有报错:

原因在于torch.ones(shape) * stride的语义是先创建一个全1的tensor,再与标量stride逐元素相乘;而torch.full(shape, fill_value)的语义创建一个指定shape的tensor,再将所有元素填充为fill_value,填充值通过标量乘法传递,避免了同时处理动态shape和动态填充值导致的​​数据依赖动态操作

  • 在模型初始化时转为float16
  • 在这里插入图片描述
1.2.2 torchair适配

在模型初始化阶段添加torch compile逻辑

调用方式如下,其他模型类似
在这里插入图片描述

2. MFD

MFD采用YOlOv8模型进行公式检测

与layout类似,同样进行前处理和推理阶段的优化

2.1 前处理

同样去除数据加载的冗余操作,并且将前处理中大部分运算适配到NPU上

在这里插入图片描述
在这里插入图片描述

2.2 推理阶段

2.2.1 模块修改

在这里插入图片描述
在这里插入图片描述

1.2.1 模块修改类似,

y.extend(self.m(y[-1]) for m in sefl.m) 使用了​​生成器表达式, Dynamo对该代码结构的​​静态分析中,并不会更新每次计算后的y[-1]导致生成相同的输出,导致语义不符,产生精度问题,通过显示循环展开来等价重写即可。

torch.ones(shape) * stride的语义是先创建一个全1的tensor,再与标量stride逐元素相乘;而torch.full(shape, fill_value)的语义创建一个指定shape的tensor,再将所有元素填充为fill_value,填充值通过标量乘法传递,避免了同时处理动态shape和动态填充值导致的​​数据依赖动态操作

在这里插入图片描述

同样在初始化时,将模型修改为float16类型

2.2.2 torchair适配

1.2.2 torchair适配

注意​:

Ultralytics在predict的时候会初始化一个predictor,其中会将模型输入autobackend,其中有一个参数fuse ,在最新版本(Ultralytics >= 8.2),fuse=True 是 ​默认开启的​,开启时会自动对模型中的卷积和bn进行优化(Conv2d + BatchNorm2d 融合成一个单独的卷积层(修改权重与偏置)),但是会把我们初始化的模型给覆盖掉,所以还得吧这个开关关掉。

在这里插入图片描述

3. MFR

3.1 前处理

  • 原始前处理过程中包含大量的slice,item,数据类型转换操作,这些零散操作很耗时,通过用split替换slice(减少切分次数),统一转换类型后再处理,矩阵运算等操作提升性能

在这里插入图片描述

  • 同样的,将后续的resize操作也替换为torch运算,注意使用一些原地运算,减少开辟空间的损耗

在这里插入图片描述

3.2 推理阶段

将模型中qkv小矩阵乘法修改为大矩阵乘法,充分利用npu的矩阵运算能力。

后续可以考虑将注意力计算也进行修改,但是当前模型或者对qk做了压缩,或者有相对位置编码,现成的融合算子(PFA、IFA)都不支持,所以没修改。在这里插入图片描述

因为这个kvcache是不定长的,所以在图编译的时候,dynamic还需设置为True

4. OCR

4.1 前处理

OCR模型处理前,首先会对layout检测出的ocr区域按照分辨率进行分组,原逻辑是使用形状分桶的思想,将长和宽均相差不超过32的分为一组,最新版MinerU使用64进行分组,在精度差距不大的情况下,减少分组个数,提升性能,这里我们也是设置为64,具体数值还可以根据具体数据进行调整。

在这里插入图片描述

4.2 推理阶段(流水线并行前后处理与模型推理)

由于OCR阶段,输入的​数据形状差距很大​,如果简单将所有图像pad或者resize成固定形状,结果很差,因此,​放弃torchair的优化方案​。

为了减少过程中NPU的空置率,考虑采用流水线并行的方式,使用多个线程的前后处理时间来掩盖模型的推理时间,为了不增加显存的占用,采用单实例多线程方式,并且给模型推理阶段添加线程锁。

具体说:在得到对应的数据后,按照不同batch进行拆分,并将不同的 batch 分配给独立的线程分别处理。因为ocr结构中的前后处理阶段包含了大量基于 OpenCVPyTorch 的底层 C/C++ 实现操作。这类底层算子在执行时会​主动释放 Python 的全局解释器锁(GIL)​,使得多个线程能够在不同的 CPU 核心上​真正并行执行​。

在这里插入图片描述

在这里插入图片描述

5. Table

Table阶段的处理思路和4.2 推理阶段(流水线并行前后处理与模型推理)类似。

在这里插入图片描述

但是因为table识别过程使用了OCR模型,并且不止一次,

流程大概是调用ocr中的 text_detector -> text_detector -> text_recognition,定义为ABC三个阶段。

这个过程中,如果只是对三个模型构建线程锁,会比直接对整个table处理过程加锁要慢,因此我们这是对Table加了一个粗粒度的锁,减少同步次数。

原因分析如下:

  1. 中间变量频繁切换:A 执行完立刻做 B,很多缓冲、常量、中间状态(甚至 device 上的中间张量复用)都能热驻留;被插队后,容易触发 cache 驱逐与重新触发内存规划,实际时延上浮。
  2. 隐式同步与队列切换变多:线程中多次调用统一模型,引入了更多的同步点。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐