在香橙派(昇腾NPU)(kunpengpro/aipro)上部署自己的模型 -以Unet为例

实验室里有一块吃灰的kunpengpro开发板(16G 8tops),最近有时间尝试一下在昇腾NPU下部署自己的模型

1.Unet模型训练

虽然可以通过使用华为的Minspore框架直接在香橙派上训练,但是训练的还是太慢了,而且资料比较少,我们这里选择在电脑上训练好模型,之后转换为.om文件部署在香橙派上

我们这里选择github上比较有名的Unet仓库来进行训练:https://github.com/milesial/Pytorch-UNet

由于我们的重点不在模型训练上,所以这里我们就不讲解如何使用了

但是这个项目训练完成后的.pth文件无法直接转换,我们需要先转换为onnx文件后,才能转化为Ascend平台上的om文件进行推理,这里我们编写一个pth转onnx文件的代码

python 复制代码
import torch
from unet import UNet

def export_to_onnx(model_path, output_path, input_shape=(3, 369, 369), device='cpu'):
    #由于.pth文件只包含权重,不包含模型结构,所以需要重新定义模型结构
    model = UNet(n_channels=3, n_classes=2, bilinear=False)  # 根据你的训练参数修改
    model.to(device)
    checkpoint = torch.load(model_path, map_location=device)
    if 'mask_values' in checkpoint:
        del checkpoint['mask_values']
    model.load_state_dict(checkpoint)
    model.eval()  # 切换到评估模式
    dummy_input = torch.randn(1, *input_shape, device=device)  # 1是batch_size
    torch.onnx.export(
        model,  # 模型实例
        dummy_input,  # 示例输入
        output_path,  # 输出文件路径
        opset_version=11,  # ONNX版本(建议11+,兼容性较好)
        do_constant_folding=True,  # 是否折叠常量
        input_names=['input'],  # 输入节点名称
        output_names=['output'],  # 输出节点名称
        dynamic_axes=None    #维度固定,减少精度算是
    )
    print(f"ONNX模型已导出至: {output_path}")
if __name__ == '__main__':
    # 配置路径和参数(根据你的实际情况修改)
    model_path = './checkpoints/checkpoint_epoch50.pth'  # 你的.pth文件路径
    output_path = 'unet_model.onnx'                    # 导出的ONNX路径
    input_shape = (3, 184, 184)                        # 输入图像尺寸(需与训练时一致,如369x369,我这里369的图片训练时缩放为184)
    device = 'cuda' if torch.cuda.is_available() else 'cpu'

    export_to_onnx(model_path, output_path, input_shape, device)

这样,电脑端的准备工作就做完了,接下来就可以将这个onnx模型部署到香橙派上

2.镜像选择

为什么我在这里会提一嘴镜像选择,是因为我刚开始使用香橙派kunkengpro的时候,使用的时官方的openEuler镜像,但是里面并没有AI相关的工具链,查找之后发现可以使用alpro的镜像,这个镜像包含了一系列相关的Ai工具链,所以我这里直接给我的kunpeng pro开发板烧录了alpro的镜像

下载地址:http://www.orangepi.cn/html/hardWare/computerAndMicrocontrollers/service-and-support/Orange-Pi-AIpro.html

我一般ubuntu用的多,所以我这里选择的时ubuntu镜像

3.模型转换

在HiHwAiUser用户下,选择mxvision-samples目录并在这里创建一个Unet_test文件夹,通过samba或者ftp将我们的onnx上传到这个目录下(我这里时创建了个pre_model目录来专门存放)

(我这里三个onnx文件是之前测试的时候上传的)

上传好模型后我们就可以使用atc指令来进行模型转换

模型转换前,我们需要查看一下NPU的型号,我这里的型号是Ascend310B4

复制代码
npu-smi info

模型转换前记得输入su切换为root用户

复制代码
atc --model=./pre_model/unet_model.onnx --framework=5 --output=./unet184 --input_format=NCHW --input_shape="input:1,3,184,184" --soc_version=Ascend310B4 

参数解释:

model:输入模型地址

framework:输出模型类型(5代表onnx)

output: om模型输出目录

input_format:输入形状(一般固定为NCHW)

input_shape:input是转onnx时定义的输入,1,3,184,184是训练是的图像输入形状

soc-version:当前NPU型号

执行这段代码一段时间后,就可以得到om文件了

4.推理代码编写

图片的预处理和后处理与使用可以参考pytorch中相关的代码,我这里使用华为的MindSDK来简化推理

导入相应的包

python 复制代码
import numpy as np
import matplotlib
from PIL import Image
from mindx.sdk import base
from mindx.sdk.base import Tensor
from scipy.special import expit
import time

图像预处理

python 复制代码
def preprocess(pil_img,scale):
    w,h=pil_img.size
    newW,newH=int(scale * w), int(scale * h)
    #图片大小转换为om要求的输出大小
    pil_img = pil_img.resize((newW,newH),resample = Image.BICUBIC)
    img = np.asarray(pil_img,dtype = np.float32)
    img = img.transpose([2,0,1])#转换为NCHW
    if (img>1).any():#归一化
        img = img/255.0
    img = np.expand_dims(img, axis=0)  # 扩展第一维度,适应模型输入
    img = np.ascontiguousarray(img) #将数组转换为内存中连续存储的形式
    return img

这里预处理主要是讲图片转换为模型要求的输入大小并转换为numpy数组,之后转换为NCHW形式,这部分和在pytorch上类似

需要注意的是img = np.ascontiguousarray(img)这行代码,我之前忽略了这句导致模型推理不正常,需要重点关注

模型推理和后处理

由于我编写的时候为测试性质,直接固定了模型和图片的位置,这部分可根据自己的需要进行修改

由于我在训练的时候设置为2分类问题(n_classes=2),所以这里使用argmax函数

python 复制代码
def process():
    pic_path = 'test/1.png'
    model_path = "model/unet184.om"
    img = Image.open(pic_path)
    img = img.convert('RGB')
    img = preprocess(pil_img=img,scale=0.5)
    img = Tensor(img) #将numpy转为Tensor,方便NPU推理
    model = base.model(modelPath = model_path,deviceId = 0)#选择模型和设备
    predict_start=time.time()#记录时间
    output = model.infer([img])[0]#获取推理结果
    predict_end = time.time()
    output.to_host()#将推理结果送回CPU
    print("infer over")
    output_numpy = np.array(output)#转换为numpy数组,进行后处理
    prediction = np.argmax(output_numpy[0], axis=0)#激活处理
    if prediction.max() <= 1:
        prediction = (prediction * 255).astype(np.uint8) #图片可视化
    total_time = time.time()
    result_pic = Image.fromarray(prediction) #保存为图片
    result_pic.save('test/1_out.png')
    print(f'predict:{(predict_end-predict_start)*1000:.2f}ms')
    print(f'total:{(total_time-predict_start)*1000:.2f}ms')

主函数定义

python 复制代码
if __name__=="__main__":
    base.mx_init() #固定使用方法
    process()
    base.mx_deinit()

5.函数使用

由于使用mindsdk的ascend需要定义一系列变量,所以在mxvision-samples的Resnet50文件夹下有一个run.sh,我们直接复制到Unet目录下稍作改动使用

复制代码
path_cur=$(dirname $0)

cd $path_cur

# Set environment PATH (Please confirm that the install_path is correct).
export TE_PARALLEL_COMPILER=1
export install_path=/usr/local/Ascend/ascend-toolkit/latest
export PATH=/usr/local/python3.9.2/bin:${install_path}/atc/ccec_compiler/bin:${install_path}/atc/bin:$PATH
export PYTHONPATH=${install_path}/atc/python/site-packages:${install_path}/atc/python/site-packages/auto_tune.egg/auto_tune:${install_path}/atc/python/site-packages/schedule_search.egg
export LD_LIBRARY_PATH=${install_path}/atc/lib64:$LD_LIBRARY_PATH
export ASCEND_OPP_PATH=${install_path}/opp

soc="Ascend"
chip_version=$(npu-smi info | awk '{print $3}' | grep -m 1 310)

# Execute, transform model.
# cd model
# atc --model=resnet50.prototxt --weight=resnet50.caffemodel --framework=0 --output=resnet50 --soc_version="$soc$chip_version"
# cd ..

# Infer
python3 predict.py
exit 0

由于我们不需要重复执行模型转换部分 这部分代码我直接注释掉了

在使用这个sh文件前,我们还需要执行几个环境配置,所有步骤如下

复制代码
#记得切换为root用户!!!
source /usr/local/Ascend/ascend-toolkit/set_env.sh
source /usr/local/Ascend/mxVision-6.0.0.SPC2/set_env.sh
bash run.sh

这样代码就能正常运行了,我们在test文件夹下查看推理结果

推理成功

6.时间对比

由于我对npu的性能有一些好奇,所以我添加了一些时间记录函数来记录推理时间

fp16精度(默认精度)

fp32精度

想使用fp32精度推理,om转换是需要加上强制fp32推理选项

复制代码
atc --model=./pre_model/unet_model.onnx --framework=5 --output=./unet184_fp32 --input_format=NCHW --input_shape="input:1,3,184,184" --soc_version=Ascend310B4 --precision_mode=force_fp32

时间长了一些

香橙派CPU推理

由于om模型只能运行在NPU上,我们这里是用onnx模型在CPU上进行推理(需要提前安装onnxruntime)

python 复制代码
import onnxruntime as ort
from PIL import Image
import numpy as np
import time


def process_img(scale=0.5):
    img = Image.open('test/1.png')
    img = img.convert('RGB')
    w, h = img.size
    newW, newH = int(scale * w), int(scale * h)
    img = img.resize((newW, newH), resample=Image.BICUBIC)
    img = np.asarray(img,dtype=np.float32)
    img = img.transpose((2, 0, 1))
    if (img > 1).any():
        img = img / 255.0
    img = np.expand_dims(img,axis=0)
    return img

def predict_by_onnx(onnx_path):
    ort_session = ort.InferenceSession(onnx_path)
    input_name = ort_session.get_inputs()[0].name
    output_name = ort_session.get_outputs()[0].name
    input_data =process_img()
    predict_start=time.time()
    outputs = ort_session.run([output_name],{input_name:input_data})
    predict_end=time.time()
    output_array=outputs[0]
    print(output_array.shape)
    prediction = np.argmax(outputs[0][0], axis=0)
    if prediction.max() <= 1:
        prediction = (prediction * 255).astype(np.uint8)
    total_time=time.time()
    print(f'predict:{(predict_end-predict_start)*1000:.2f}ms')
    print(f'total:{(total_time-predict_start)*1000:.2f}ms')
    return prediction

if __name__ == "__main__":
    onnx_path = 'model/unet_model.onnx'
    result = predict_by_onnx(onnx_path)
    result_pic = Image.fromarray(result)
    result_pic.save('test/onnx_ouput.png')

可以看出来NPU加速效果还是很明显的

相关推荐
yuanmenghao21 小时前
Classic AUTOSAR深入浅出系列 - 【第十六篇】MCAL:为什么 MCU 换了,上层几乎不用动
单片机·嵌入式硬件·autosar
czwxkn1 天前
3STM32(stdl)外部中断
stm32·单片机·嵌入式硬件
羽获飞1 天前
从零开始学嵌入式之STM32——6.与GPIO相关的7个寄存器--重要知识
stm32·单片机·嵌入式硬件
棒子陈1 天前
使用cursor移植单片机的串口驱动(DMA+队列式串口驱动,APM32F103移植到PY32F071)
单片机·嵌入式硬件·cursor·py32f071
VALENIAN瓦伦尼安教学设备1 天前
镭射对心仪在联轴器找正作用
大数据·数据库·人工智能·嵌入式硬件
蓬荜生灰1 天前
STM32(11)-- GPIO输出,库函数点灯
stm32·单片机·嵌入式硬件
济6171 天前
ARM Linux 驱动开发篇----字符设备驱动开发(1)--字符设备驱动简介---- Ubuntu20.04
linux·嵌入式硬件
csg11071 天前
PIC单片机驱动BH1750光照传感器,轻松获取环境光照数据
单片机·嵌入式硬件·物联网
雾削木1 天前
使用 ESPHome 的核心指令
java·前端·javascript·单片机·嵌入式硬件
DLGXY1 天前
STM32——输入捕获、编码器接口(十一)
stm32·单片机·嵌入式硬件