【2025/8/1 更新】
高性能、高可用昇腾推理方式已开源,期待各位 ⭐ Star~⭐
地址:https://github.com/momomo623/PaddleOCR-NPU

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

1. 使用Paddle框架推理

1.1 安装

# 先安装飞桨 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/

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

# 下载 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

健康检查:

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

1.2 推理

设置环境变量:

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

export FLAGS_npu_jit_compile=0
export FLAGS_use_stride_kernel=0 

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

from paddleocr import PaddleOCR
PaddleOCR(
            show_log=True,
            use_npu=True,
            # 其他参数
        )

2. paddle 转 ONNX

参考文档
下载模型

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

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。

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包。

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有一定的学习成本,大家如果有测试的可以发出来一起讨论。

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)





Logo

昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链

更多推荐