概要:
PyTorch官方提供了昇腾插件包,安装后虽然可以支持PytorchOCR和PaddlePaddle的推理任务,但性能通常低于GPU。
为了充分发挥昇腾硬件的潜力,可以采用离线推理方案:
​​模型转换​​:将Paddle模型转换为昇腾专用的OM格式;
​高效推理​​:通过昇腾 ACL 框架运行,显著提升性能。
这种方案通过硬件深度优化,能大幅提升推理速度。

1. 使用Paddle框架推理

1.1 安装

1
2
3
4
5
6
# 先安装飞桨 CPU 安装包
python -m pip install paddlepaddle==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/

# 再安装飞桨 NPU 插件包
python -m pip install paddle-custom-npu==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/npu/

如果失败,使用源码编译安装(确实会有安装失败的情况)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 下载 PaddleCustomDevice 源码
git clone https://github.com/PaddlePaddle/PaddleCustomDevice -b release/3.0.0

# 进入硬件后端(昇腾 NPU)目录
cd PaddleCustomDevice/backends/npu

# 先安装飞桨 CPU 安装包
python -m pip install paddlepaddle==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/

# 执行编译脚本 - submodule 在编译时会按需下载
bash tools/compile.sh

# 飞桨 NPU 插件包在 build/dist 路径下,使用 pip 安装即可
python -m pip install build/dist/paddle_custom_npu*.whl

健康检查:

1
2
3
4
# 检查当前安装版本
python -c "import paddle_custom_device; paddle_custom_device.npu.version()"
# 飞桨基础健康检查
python -c "import paddle; paddle.utils.run_check()"

1.2 推理

设置环境变量:

推理时有算子触发jit编译,会导致推理很慢。所以需要设置环境变量来禁止。

1
2
export FLAGS_npu_jit_compile=0
export FLAGS_use_stride_kernel=0

推理代码:
添加参数:use_npu=True

1
2
3
4
5
6
from paddleocr import PaddleOCR
PaddleOCR(
show_log=True,
use_npu=True,
# 其他参数
)

2. paddle 转 ONNX

参考文档
下载模型

1
2
3
4
5
wget -nc  -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_infer.tar
cd ./inference && tar xf ch_PP-OCRv4_det_infer.tar && cd ..

wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_rec_infer.tar
cd ./inference && tar xf ch_PP-OCRv4_rec_infer.tar && cd ..

转ONNX

1
2
3
4
5
6
7
8
9
10
11
12
13
paddle2onnx --model_dir ./inference/ch_PP-OCRv4_det_infer \
--model_filename inference.pdmodel \
--params_filename inference.pdiparams \
--save_file ./inference/det_onnx/model.onnx \
--opset_version 11 \
--enable_onnx_checker True

paddle2onnx --model_dir ./inference/ch_PP-OCRv4_rec_infer \
--model_filename inference.pdmodel \
--params_filename inference.pdiparams \
--save_file ./inference/rec_onnx/model.onnx \
--opset_version 11 \
--enable_onnx_checker True

3. 转om

请保证昇腾环境已安装,文档

我的场景下只需要这两个模型:rec、det。即文本识别、文本检测。

对于shape需要观察paddle模型的结构,根据输入shape和我们的业务需求来做配置,在线查看模型结构网站:https://netron.app

rec
其原有模型结构为:(x:-1,3,48,-1) 。batch 和 宽度是动态的,那么正常来说ATC转换时也根据这个来配置就好了,但我测试了多次,如果按照(x:-1,3,48,-1) 会报错,或者转换不保存推理时报错。档位直接-1也会报错。所以我选择了(x:-1,3,48,320),并设置了动态batch分档。
当然如果没有动态shape的需求,会更简单,固定即可,大概率是ok的。

det
其原有模型结构为:(x:-1,3,-1,-1) 。可以正常去做动态shape。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
atc --model=./inference/rec_onnx/model.onnx \
--framework=5 \
--output=./d_n_recfix \
--input_format=NCHW \
--input_shape="x:-1,3,48,320" \
--dynamic_batch_size="1,2,3,4,5,6" \
--soc_version=Ascend910B3

atc --model=./inference/det_onnx/model.onnx \
--framework=5 \
--output=./d_n_decfix \
--input_format=NCHW \
--input_shape="x:-1,3,-1,-1" \
--soc_version=Ascend910B3

4. Ais_bench 命令推理

Ais_bench 是昇腾测试 om 模型性能的工具。功能可以这样理解:快速验证om模型是否正常、快速编写推理代码。

最开始说我们要使用ACL来做推理,直接编写ACL是很麻烦的,设计到数据内存设计、内存申请、释放、数据搬入搬出等等操作,Ais_bench是更上层的测试工具,我们可以暂时使用Ais_bench来做推理测试和代码编写。Ais_bench即有命令行工具也提供python包。

ais_bench推理工具使用指南。请先根据文档下载whl包。

1
python3 -m ais_bench --model d_n_recfix.om --dymBatch 6

如果有本地的bin文件,可以添加参数:--input=/rec/bin
bin文件:可以将数据预处理后的tensor保存为bin文件,再用ais_bench推理bin文件可以输出一个bin,再用输出的bin接入后处理,可以快速验证推理的正确性

5. Ais_bench 编写推理代码

Ais_bench接口文档

代码中 muti_infer_det、infer_rec、infer_det 函数需要实例AisBenchInfer后使用。

下面两部分主要是用于测试om模型是否正常和他们的精度,可以删除:

infer_with_file、infer_with_file_det 为推理单张图片bin文件/bin文件夹 使用。因为bin只是tensor数据,没有shape,所以需要重塑shape为正常形状
infer_folder_det、infer_folder_rec 推理整个文件夹,每个bin都有一个相应的记录shape的txt,每次都读取bin和shape的txt文件,用于重塑shape为正常形状
样例文件:在这里插入图片描述

其他说明:
rec推理没有问题,只是只能batch为动态,宽度固定。
det推理单张图片没有问题,推理多张图片会出现错误,大概率和Ais_bench中的session创建有关系。有一个不是好方案的方案,使用MultiDeviceSession,多线程调用,每次调用时创建一个session,即推理多张图片每次都需要初始化,所以会很慢。
毕竟Ais_benchACL的上层封装,或许在某些场景确实有问题,有可能使用ACL编写代码会避免,但ACL有一定的学习成本,大家如果有测试的可以发出来一起讨论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
import os
import time
import numpy as np

from ais_bench.infer.interface import InferSession,MultiDeviceSession
from ais_bench.infer.common.utils import logger_print

model_path_rec = "/home/aicc/mineru/model/d_n_recfix.om"
model_path_det = "/home/aicc/mineru/model/d_n_decfix_linux_aarch64.om"
class AisBenchInfer:
_instance = None # 单例模式的类变量

def __new__(cls, device_id=1):
# 单例模式实现:如果实例不存在则创建,否则返回已有实例
if cls._instance is None:
cls._instance = super(AisBenchInfer, cls).__new__(cls)
cls._instance._initialized = False # 标记是否已经初始化
return cls._instance

def __init__(self, device_id=1):
"""
初始化推理模型

Args:
device_id: 设备ID
model_path: 模型路径
"""
# 只在第一次初始化时执行
if not self._initialized:
self.device_id = device_id
self.model_path_rec = model_path_rec
self.session_rec = InferSession(device_id, self.model_path_rec)
self.model_path_det = model_path_det
# self.session_det = InferSession(device_id, self.model_path_det)

self.multi_session_det = MultiDeviceSession(self.model_path_det)

# self.session_det.set_staticbatch()
print("初始化完成:")
self._initialized = True # 标记为已初始化

def muti_infer_det(self, norm_img_batch: np.ndarray):
"""
执行推理

Args:
norm_img_batch: 输入的图像批次数据

Returns:
推理输出结果
"""


outputs = self.multi_session_det.infer({self.device_id: [[norm_img_batch]]}, mode='dymshape', custom_sizes=1000000)
print("推理成功")
# print(outputs)
return outputs

def infer_rec(self, norm_img_batch: np.ndarray):
"""
执行推理

Args:
norm_img_batch: 输入的图像批次数据

Returns:
推理输出结果
"""
outputs = self.session_rec.infer([norm_img_batch], mode='dymbatch')
print("推理成功")
return outputs
def infer_det(self, norm_img_batch: np.ndarray):
"""
执行推理

Args:
norm_img_batch: 输入的图像批次数据

Returns:
推理输出结果
"""
# model_path_det = "/home/aicc/mineru/model/d_n_decfix_linux_aarch64.om"
# session_det = InferSession(self.device_id, model_path_det)
outputs = self.session_det.infer([norm_img_batch], mode='dymshape')
print("type(outputs):", type(outputs)) # 应输出 <class 'list'>
print("type(outputs[0]):", type(outputs[0])) # 应输出 <class 'numpy.ndarray'>
print("outputs[0].dtype:", outputs[0].dtype) # 应输出 float32
print("outputs[0].shape:", outputs[0].shape) # 例如 (6, 25, 6625)
print("outputs:", outputs) # 例如 (6, 25, 6625)
print(len(outputs)) # 例如 (6, 25, 6625)

print("推理成功")
# outputs = self.session_det.infer([norm_img_batch], mode='dymshape')
# print("推理成功")
# session_det.free_resource()
return outputs

def free_resource(self):
"""释放模型资源"""
if hasattr(self, 'session'):
self.session.free_resource()

@staticmethod
def infer_with_file(bin_file_path, device_id=0, model_path='/home/aicc/mineru/model/d_model_rec_linux_aarch64.om'):
"""
使用文件执行动态批量推理

Args:
bin_file_path: 二进制输入文件路径
device_id: 设备ID
model_path: 模型路径

Returns:
推理输出结果
"""
session = InferSession(device_id, model_path)

# 读取数据
ndata = np.fromfile(bin_file_path, dtype=np.float32)
print("ndata shape:", ndata.shape)
print("ndata元素数量:", ndata.size)
print("ndata数据类型:", ndata.dtype)

# 重塑数据
ndata = ndata.reshape(6, 3, 48, 320)
print("重塑后的ndata shape:", ndata.shape)

# 执行推理
outputs = session.infer([ndata], mode='dymshape')

# 打印输出信息
print(type(outputs)) # 应输出 <class 'list'>
print(type(outputs[0])) # 应输出 <class 'numpy.ndarray'>
print(outputs[0].dtype) # 应输出 float32
print(outputs[0].shape) # 例如 (6, 25, 6625)

# 释放资源
session.free_resource()

return outputs
@staticmethod
def infer_with_file_det(bin_file_path, device_id=0, model_path='/home/aicc/mineru/model/d_n_decfix_linux_aarch64.om'):
"""
使用文件执行动态批量推理

Args:
bin_file_path: 二进制输入文件路径
device_id: 设备ID
model_path: 模型路径

Returns:
推理输出结果
"""
session = InferSession(device_id, model_path)

# 读取数据
ndata = np.fromfile(bin_file_path, dtype=np.float32)
print("ndata shape:", ndata.shape)
print("ndata元素数量:", ndata.size)
print("ndata数据类型:", ndata.dtype)

# 重塑数据
ndata = ndata.reshape(1, 3, 800, 704)
print("重塑后的ndata shape:", ndata.shape)

# 执行推理
outputs = session.infer([ndata], mode='dymshape')

# 打印输出信息
print(type(outputs)) # 应输出 <class 'list'>
print(type(outputs[0])) # 应输出 <class 'numpy.ndarray'>
print(outputs[0].dtype) # 应输出 float32
print(outputs[0].shape) # 例如 (6, 25, 6625)

# 释放资源
session.free_resource()

return outputs

@staticmethod
def infer_folder_det(folder_path, device_id=0, model_path='/home/aicc/mineru/model/d_n_decfix_linux_aarch64.om'):
"""
处理文件夹中的所有bin文件进行检测推理

Args:
folder_path: 包含bin文件和shape.txt文件的文件夹路径
device_id: 设备ID
model_path: 模型路径

Returns:
所有bin文件的推理结果字典,键为bin文件名,值为推理输出
"""
session = MultiDeviceSession( model_path)
# session.set_staticbatch()
results = {}

# 获取文件夹中所有bin文件
bin_files = [f for f in os.listdir(folder_path) if f.endswith('.bin') and not f.endswith('.shape.txt')]

for bin_file in bin_files:
bin_file_path = os.path.join(folder_path, bin_file)
shape_file_path = bin_file_path + '.shape.txt'

# 检查shape文件是否存在
if not os.path.exists(shape_file_path):
print(f"跳过 {bin_file}: 找不到shape文件")
continue

# 读取shape数据
with open(shape_file_path, 'r') as f:
shape_str = f.read().strip()

# 解析shape数据
shape = tuple(map(int, shape_str.split(',')))

# 读取bin数据
ndata = np.fromfile(bin_file_path, dtype=np.float32)
print(f"处理 {bin_file}")
print(f"原始数据shape: {ndata.shape}")
print(f"从shape文件读取的形状: {shape}")


# 重塑数据
try:
ndata = ndata.reshape(shape)
print(f"重塑后的数据shape: {ndata.shape}")

# 执行推理
outputs = session.infer({device_id: [[ndata]]}, mode='dymshape', custom_sizes=10000000)
print(f"{bin_file} 推理成功")

# 记录结果
results[bin_file] = outputs

except Exception as e:
print(f"处理 {bin_file} 时出错: {e}")

# 释放资源
# session.free_resource()

return results


@staticmethod
def infer_folder_rec(folder_path, device_id=0, model_path='/home/aicc/mineru/model/d1001_n_recfix_linux_aarch64.om'):
"""
处理文件夹中的所有bin文件进行识别推理

Args:
folder_path: 包含bin文件和shape.txt文件的文件夹路径
device_id: 设备ID
model_path: 模型路径

Returns:
所有bin文件的推理结果字典,键为bin文件名,值为推理输出
"""
session = InferSession(device_id, model_path)
results = {}

# 获取文件夹中所有bin文件
bin_files = [f for f in os.listdir(folder_path) if f.endswith('.bin') and not f.endswith('.shape.txt')]

for bin_file in bin_files:
bin_file_path = os.path.join(folder_path, bin_file)
shape_file_path = bin_file_path + '.shape.txt'

# 检查shape文件是否存在
if not os.path.exists(shape_file_path):
print(f"跳过 {bin_file}: 找不到shape文件")
continue

# 读取shape数据
with open(shape_file_path, 'r') as f:
shape_str = f.read().strip()

# 解析shape数据
shape = tuple(map(int, shape_str.split(',')))

# 读取bin数据
ndata = np.fromfile(bin_file_path, dtype=np.float32)
print(f"处理 {bin_file}")
print(f"原始数据shape: {ndata.shape}")
print(f"从shape文件读取的形状: {shape}")

# 重塑数据
try:
ndata = ndata.reshape(shape)
print(f"重塑后的数据shape: {ndata.shape}")

# 执行推理
outputs = session.infer([ndata], mode='dymbatch')
print(f"{bin_file} 推理成功")

# 记录结果
results[bin_file] = outputs

except Exception as e:
print(f"处理 {bin_file} 时出错: {e}")

# 释放资源
session.free_resource()

return results

# 使用示例:

# import acl

# infer_model = AisBenchInfer()
# result = infer_model.infer_det(np.zeros((1, 3, 608, 704), dtype=np.float32))
# result = infer_model.infer_det(np.zeros((1, 3, 608, 704), dtype=np.float32))

# 使用 muti 推理多个 ,muti每次都会创建InferSession。 使用推理接口时才会在指定的几个devices的每个进程中新建一个InferSession。
# result = infer_model.muti_infer_det(np.zeros((1, 3, 800, 704), dtype=np.float32))
# result = infer_model.muti_infer_det(np.zeros((1, 3, 608, 704), dtype=np.float32))


# infer_model.free_resource()

# 或者直接使用静态方法:
# result = AisBenchInfer.infer_with_file('/home/aicc/mineru/MinerU_1.3.0/demo/preprocessed_data/rec/rec_input_batch_0_20250421_091529_142.bin')
# result = AisBenchInfer.infer_with_file_det('/home/aicc/mineru/MinerU_1.3.0/demo/preprocessed_data/det/det_input_20250421_034746_105.bin')

# results = AisBenchInfer.infer_folder_det('/home/aicc/mineru/MinerU_1.3.0/demo/preprocessed_data/det')
# results = AisBenchInfer.infer_folder_rec('/home/aicc/mineru/MinerU_1.3.0/demo/preprocessed_data/rec')
# print("检测推理结果:", results)








总访问
发表了 19 篇文章 🔸 总计 43.8k 字