前言
平时喜欢玩开发板,之前在jetson nano,RK3399pro,英特尔神经计算棒上实现了各种深度学习算法的迁移部署,比较熟悉tensorrt,rknn,openvino各种花里胡哨的推理引擎
最近又接触了华为Atlas200DK开发板,体验了一把达芬奇架构的昇腾310NPU,实力的确不可小觑,在几天内的学习和努力下终于熟悉了与NPU配套的CANN计算框架(我比较喜欢叫推理引擎),因此分享一下使用CANN的pyACL接口部署YOLOX、YOLOv5和Nanodet的方法。
YOLOX是YOLO目标检测系列里的最新方法,代码风格友好,并且根据我上一篇博客的结果其轻量模型推理效率很高,因此本文重点以YOLOX为例。
深度学习模型在AI芯片上部署的一般流程
首先介绍一下在AI芯片上的部署流程,无论是英伟达的tensorrt,瑞芯微的rknn,还是openvino、CANN等推理引擎,虽然各引擎的API不同,但基本流程都差不多,如下图所示: 从深度学习训练框架(如pytorch,tf,paddle等)训练得到的模型,通过各推理引擎附带的模型转换器(有的是引擎自身的API转换比如RKNN,有的配置了专门的转换工具tools比如CANN的ATC),转换成对应引擎的的离线模型。在推理时,通过引擎API将模型和预处理后的图片加载到内存中,然后载入AI芯片中执行推理。
不过不同的推理引擎的推理上仍然存在一些细节差异。
CANN模型部署流程
对于CANN而言,模型部署和推理过程相对复杂一些。这里引用一下官网的流程,对于常用的目标检测模型,只需要看右边的模型推理就OK了: 主要就是ACL环境(运行资源)的初始化、模型转换和加载、图像预处理、模型推理、后处理、模型卸载和ACL环境的去初始化。
乍一看,推理前进行环境初始化、结束后去初始化的操作比较繁琐,但这种规范的流程设计其实是很有必要的: 举个反例,openvino没有这些初始化之类的步骤,于是我在英特尔的NCS2 VPU上经常出现跑完程序后需要等待很长时间才能进行下一次运行的问题,原因就是没有把上一次加载的内存数据卸载掉:)
CANN ACL接口调用流程(python)
ACL(Ascend Computing Language)提供了CANN的推理接口,可用C++(AscendCL)和python(pyACL)进行开发,我比较喜欢用pyACL。
一般的接口调用流程如下:
步骤1.ACL环境初始化和资源申请
import acl
ret = acl.init()
self.device_id = 0
ret = acl.rt.set_device(self.device_id)
self.context, ret = acl.rt.create_context(self.device_id)
步骤2.模型加载
与pytorch等框架下的模型加载不同,CANN在加载模型时,除了要把模型文件加载到内存中,还要通过模型文件把模型的输入输出类型、个数、内存等设置好(openvino也有类似操作)。
'''
加载模型文件
'''
self.model_path = './model/resnet50.om'
self.model_id, ret = acl.mdl.load_from_file(self.model_path)
self.model_desc = acl.mdl.create_desc()
ret = acl.mdl.get_desc(self.model_desc, self.model_id)
'''
设置模型的输入
'''
ACL_MEM_MALLOC_HUGE_FIRST = 0
self.load_input_dataset = acl.mdl.create_dataset()
input_size = acl.mdl.get_num_inputs(self.model_desc)
self.input_data = []
for i in range(input_size):
buffer_size = acl.mdl.get_input_size_by_index(self.model_desc, i)
buffer, ret = acl.rt.malloc(buffer_size, ACL_MEM_MALLOC_HUGE_FIRST)
data = acl.create_data_buffer(buffer, buffer_size)
_, ret = acl.mdl.add_dataset_buffer(self.load_input_dataset, data)
self.input_data.append({"buffer": buffer, "size": buffer_size})
'''
设置模型的输出
'''
self.load_output_dataset = acl.mdl.create_dataset()
output_size = acl.mdl.get_num_outputs(self.model_desc)
self.output_data = []
for i in range(output_size):
buffer_size = acl.mdl.get_input_size_by_index(self.model_desc, i)
buffer, ret = acl.rt.malloc(buffer_size, ACL_MEM_MALLOC_HUGE_FIRST)
data = acl.create_data_buffer(buffer, buffer_size)
_, ret = acl.mdl.add_dataset_buffer(self.load_output_dataset, data)
self.output_data.append({"buffer": buffer, "size": buffer_size})
这一段容易看晕,因为获取模型输入输出数据信息时都需要先使用API初始化这个数据类型,然后再通过另一个API获得信息,类似于C++中的先声明再使用。
步骤3.准备输入数据,预处理,推理,后处理
ACL_MEMCPY_DEVICE_TO_DEVICE = 3
NPY_BYTE = 1
images_list = ["./data/dog1_1024_683.jpg", "./data/dog2_1024_683.jpg"]
for image in images_list:
img = transfer_pic(image)
np_ptr = acl.util.numpy_to_ptr(img)
ret = acl.rt.memcpy(self.input_data[0]["buffer"], self.input_data[0]["size"], np_ptr,
self.input_data[0]["size"], ACL_MEMCPY_DEVICE_TO_DEVICE)
ret = acl.mdl.execute(self.model_id, self.load_input_dataset, self.load_output_dataset)
inference_result = []
for i, item in enumerate(self.output_data):
buffer_d, ret = acl.rt.malloc(self.output_data[i]["size"], ACL_MEM_MALLOC_HUGE_FIRST)
ret = acl.rt.memcpy(buffer_d, self.output_data[i]["size"], self.output_data[i]["buffer"],
self.output_data[i]["size"], ACL_MEMCPY_DEVICE_TO_DEVICE)
data = acl.util.ptr_to_numpy(buffer_d, (self.output_data[i]["size"],), NPY_BYTE)
inference_result.append(data)
tuple_st = struct.unpack("1000f", bytearray(inference_result[0]))
vals = np.array(tuple_st).flatten()
top_k = vals.argsort()[-1:-6:-1]
print("======== top5 inference results: =============")
for j in top_k:
print("[%d]: %f" % (j, vals[j]))
步骤4.卸载模型
while self.input_data:
item = self.input_data.pop()
ret = acl.rt.free(item["buffer"])
input_number = acl.mdl.get_dataset_num_buffers(self.load_input_dataset)
for i in range(input_number):
data_buf = acl.mdl.get_dataset_buffer(self.load_input_dataset, i)
if data_buf:
ret = acl.destroy_data_buffer(data_buf)
ret = acl.mdl.destroy_dataset(self.load_input_dataset)
while self.output_data:
item = self.output_data.pop()
ret = acl.rt.free(item["buffer"])
output_number = acl.mdl.get_dataset_num_buffers(self.load_output_dataset)
for i in range(output_number):
data_buf = acl.mdl.get_dataset_buffer(self.load_output_dataset, i)
if data_buf:
ret = acl.destroy_data_buffer(data_buf)
ret = acl.mdl.destroy_dataset(self.load_output_dataset)
ret = acl.mdl.unload(self.model_id)
if self.model_desc:
ret = acl.mdl.destroy_desc(self.model_desc)
self.model_desc = None
步骤5.资源释放,acl去初始化
if self.context:
ret = acl.rt.destroy_context(self.context)
self.context = None
ret = acl.rt.reset_device(self.device_id)
ret = acl.finalize()
以上就是pyACL接口的基本调用流程
用于简化部署的Atlas Utils
(未完待续)
|