Learn From:
https://www.hiascend.com/zh/college/onlineExperiment/codeLabAcl/acl
ACL应用案例部署
课程目标
学完本课程,您应该能够:
1.了解ACL的基本概念,清楚ACL具备哪些能力,能为我们做什么
2.了解ACL定义的编程模型,理解各类运行资源的概念及其相互关系
3.能够区分Host和Device的概念,并学会管理这两者各自的内存
4.加载一个离线模型进行推理,并为推理准备输入输出数据结构
2. 基础知识
2.1 ATC介绍
ATC(Ascend Tensor Compiler)是华为昇腾软件栈提供的一个编译工具,它的主要功能是将基于开源框架的网络模型(如Caffe、TensorFlow等)以及单算子Json文件,转换成昇腾AI处理器支持的离线模型Offline-Model文件(简称OM文件)。在编译过程中,可以实现算子调度的优化、权值数据重排、内存使用优化等,并且可以脱离设备完成模型的预处理。更详细的ATC介绍,可参看官方文档 。
ATC工具架构图
2.2 ACL介绍
对已训练好的权重文件,比如caffe框架下的caffemodel, tensorflow框架下得到的checkpoint或者pb文件,再经过ATC工具转换后得到的离线模型文件,ACL(Ascend Computing Language,昇腾计算语言)提供了一套用于在昇腾系列处理器上进行加速计算的API。基于这套API,您能够管理和使用昇腾软硬件计算资源,并进行机器学习相关计算。更详细的ACL介绍,可参看官方文档 。
当前ACL为您提供了C/C++和Python编程接口,能够很方便的帮助您达成包括但不限于如下这些目标:
1.加载深度学习模型进行推理
2.加载单算子进行计算
3.图像、视频数据的预处理
3. 准备工作¶
本实验的最终结果是使用Resnet50对3张图片进行分类推理。为了达成这个结果,首先我们准备了如下两个素材:
三张待推理分类的图片数据,如:
使用ATC工具,将tensorflow的googlenet.pb模型转换成昇腾支持的om(offine-model)文件。
推理后我们将打印每张图片置信度排前5位的标签及其置信度。
- 开启征途
4.1 初始化
在开始调用ACL的任何接口之前,首先要做ACL的初始化。初始化的代码很简单,只有一行:
acl.init(config_path)
这个接口调用会帮您准备好ACL的运行时环境。其中调用时传入的参数是一个配置文件在磁盘上的路径,这里我们暂时不必关注。
有初始化就有去初始化,在确定完成了ACL的所有调用之后,或者进程退出之前,要做去初始化操作,接口调用也十分简单:
acl.finalize()
导入Python包:
import argparse
import numpy as np
import struct
import acl
import os
from PIL import Image
接口介绍:
函数示例:
def init():
ret = acl.init()
check_ret("acl.init", ret)
4.2申请计算资源
想要使用昇腾处理器提供的加速计算能力,需要对运行管理资源申请,包括Device、Context、Stream。且需要按顺序申请,主要涉及以下三个接口:
acl.rt.set_device(device_id)
这个接口指定计算设备,告诉运行时环境我们想要用哪个设备,或者更具体一点,哪个芯片。但是要注意,芯片和我们在这里传入的编号之间并没有物理上的一一对应关系。
acl.rt.create_context(device_id)
Context作为一个容器,管理了所有对象(包括Stream、Event、设备内存等)的生命周期。不同Context的Stream、不同Context的Event是完全隔离的,无法建立同步等待关系。
acl.rt.create_stream()
Stream用于维护一些异步操作的执行顺序,确保按照应用程序中的代码调用顺序在Device上执行。基于Stream的kernel执行和数据传输能够实现Host运算操作、Host与Device间的数据传输、Device内的运算并行。
接口介绍:
函数示例:
import sys
import os
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
home_path = !echo ${
HOME}
sys.path.append(os.path.join(home_path[0] , "jupyter-notebook/"))
print('System init success.')
# atlas_utils是本团队基于pyACL封装好的一套工具库,如果您也想引用的话,请首先将
# https://gitee.com/ascend/samples/tree/master/python/common/atlas_utils
# 这个路径下的代码引入您的工程中
from atlas_utils.acl_resource import AclResource
from constants import *
#创建一个AclResource类的实例
acl_resource = AclResource()
#AscendCL资源初始化(封装版本)
acl_resource.init()
# 上方“init”方法具体实现(仅供参考)
# 请阅读“init(self)”方法,观察初始化和运行时资源申请的详细操作步骤
def init(self):
"""
Init resource
"""
print("init resource stage:")
ret = acl.init()
utils.check_ret("acl.init", ret)
#指定用于运算的Device
ret = acl.rt.set_device(self.device_id)
utils.check_ret("acl.rt.set_device", ret)
print("Set device n success.")
#显式创建一个Context
self.context, ret = acl.rt.create_context(self.device_id)
utils.check_ret("acl.rt.create_context", ret)
#创建一个Stream
self.stream, ret = acl.rt.create_stream()
utils.check_ret("acl.rt.create_stream", ret)
#获取当前昇腾AI软件栈的运行模式
#0:ACL_DEVICE,表示运行在Device的Control CPU上或开发者版上
#1:ACL_HOST,表示运行在Host CPU上
self.run_mode, ret = acl.rt.get_run_mode()
utils.check_ret("acl.rt.get_run_mode", ret)
print("Init resource success")
4.3 加载模型
既然要调用模型进行推理,首先当然是要把模型加载进来。ACL提供了多种模型加载和内存管理方式,这里我们只选取其中相对简单的一种,即从磁盘上加载离线模型,并且加载后的模型内存由ACL自动管理:
model_path = "./model/resnet50.om"; #模型文件在磁盘上的路径
model_id, ret = acl.mdl.load_from_file(model_path) #加载模型
其中,model_id是系统完成模型加载后生成的模型ID对应的指针对象 加载后生成的modelId,全局唯一
记得这个“model_id”,后边我们使用模型进行推理,以及卸载模型的时候还要用到。
有加载自然就有卸载,模型卸载的接口比较简单:
acl.mdl.unload(model_id)
至此,您脑海中应该有这样一个概念,即使用了任何的资源,加载了任何的素材,都要记得用完后销毁和卸载。
接口介绍:
函数示例:
def load_model(model_path):
model_path = "./model/googlenet_yuv.om"
model_id, ret = acl.mdl.load_from_file(model_path)
check_ret("acl.mdl.load_from_file", ret)
return model_id
4.4 获取模型信息
模型描述需要特殊的数据类型,使用以下函数来创建并获取该数据类型。
acl.mdl.create_desc()
acl.mdl.get_desc(model_desc, model_id)
获取到模型类型后,还需要根据该类型来获取模型的输入输出个数,调用函数如下:
acl.mdl.get_num_inputs(model_desc)
acl.mdl.get_num_outputs(model_desc)
接口介绍:
函数示例:
def get_model_data(model_id):
global model_desc
model_desc = acl.mdl.create_desc()
ret = acl.mdl.get_desc(model_desc, model_id)
check_ret("acl.mdl.get_desc", ret)
input_size = acl.mdl.get_num_inputs(model_desc)
output_size = acl.mdl.get_num_outputs(model_desc)
return input_size, output_size
4.5 申请device内存
要使用NPU进行加速计算,首先要申请能够被NPU直接访问到的专用内存。在讲解内存申请之前,首先我们要区分如下两个概念:
Host:Host指与Device相连接的X86服务器、ARM服务器,会利用Device提供的NN(Neural-Network )计算能力,完成业务。 Device:Device指安装了芯片的硬件设备,利用PCIe接口与Host侧连接,为Host提供NN计算能力。
简而言之,我们的数据需要先从Host侧进行加载,即读进Host侧内存,随后将其拷贝到Device侧内存,才能进行计算。计算后的结果还要传回Host侧才能进行使用。
申请Device侧内存:
dev_ptr, ret = acl.rt.malloc(size, policy) #申请device侧内存
dev_ptr; //device侧内存指针
size; //device侧内存大小
这里我们分配了跟Host侧同样大小的内存,准备用于在Device侧存放推理数据。本接口最后一个参数policy是内存分配规则。
ACL_MEM_MALLOC_HUGE_FIRST ----- 优先申请大页内存,如果大页内存不够,则使用普通页的内存。
ACL_MEM_MALLOC_HUGE_ONLY ----- 仅申请大页,如果大页内存不够,则返回错误。
ACL_MEM_MALLOC_NORMAL_ONLY ----- 仅申请普通页。
使用完别忘了释放申请过的内存:
`acl.rt.malloc-> acl.rt.free`
接口介绍:
函数示例:
def gen_data_buffer(size, des):
global model_desc
func = buffer_method[des]
for i in range(size):
temp_buffer_size = acl.mdl.get_output_size_by_index(model_desc, i)
temp_buffer, ret = acl.rt.malloc(temp_buffer_size,
const.ACL_MEM_MALLOC_NORMAL_ONLY)
check_ret("acl.rt.malloc", ret)
if des == "in":
input_data.append({
"buffer": temp_buffer,
"size": temp_buffer_size})
elif des == "out":
output_data.append({
"buffer": temp_buffer,
"size": temp_buffer_size})
def malloc_device(input_num