Skip to main content

模型加载

接口调用流程

数据传输的关键接口调用流程如下:

  1. 申请内存

  2. 将数据读入内存

    由用户自行管理数据读入内存的实现逻辑。

  3. 通过内存复制实现数据传输

    数据传输可以通过内存复制的方式实现,分为同步内存复制异步内存复制

    :::note 说明 Ascend RC场景下,不涉及Host上的内存申请、Host内的数据传输、Host与Device之间的数据传输。

    如果当前版本支持多种运行形态,在这种场景下,若想实现相同的应用程序可支持在多种形态下运行,申请内存的方式不同,会影响数据传输时调用的接口:

    • 若应用程序中区分申请Host内存或Device内存的接口,例如使用acl.rt.malloc_host接口申请Host内存、使用acl.rt.malloc接口申请Device内存时:

      需先调用acl.rt.get_run_mode接口获取软件栈的运行模式。

      • 当查询结果为ACL_HOST = 1,则数据传输时涉及申请Host上的内存。
      • 当查询结果为ACL_DEVICE = 0 ,则数据传输时不涉及申请Host上的内存,仅需申请Device上的内存。

      该种方式多一些代码逻辑的判断,不需要由用户处理Device上的内存对齐。在Device上运行应用的场景,该种方式少一些内存复制的步骤,性能较好。

    • 若应用程序中不区分申请Host内存或Device内存的接口,统一使用acl.rt.malloc_host接口(该接口支持申请Host或Device内存),pyACL内部会根据软件栈的运行模式自行判断运行时申请的是Host内存还是Device内存:

      无需调用acl.rt.get_run_mode接口获取软件栈的运行模式。该种方式代码逻辑相比前一种简单,但需由用户处理Device上的内存对齐。 :::

父主题: 数据传输

Device内的数据传输

同步内存复制

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考,示例代码如下:

import acl
## ......

## 1.申请内存。
size = 1 * 1024 * 1024
dev_ptr_a, ret = acl.rt.malloc(size, ACL_MEM_MALLOC_NORMAL_ONLY)
dev_ptr_b, ret = acl.rt.malloc(size, ACL_MEM_MALLOC_NORMAL_ONLY)

## 2.申请内存后,可向内存中读入数据,该自定义函数read_file由用户实现。
read_file(fileName, dev_ptr_a, size)

## 3.同步内存复制。
## 同步内存复制,dev_ptr_a表示Device上源内存地址的指针地址,dev_ptr_b表示Device上目的内存地址的指针地址,size表示内存大小。
## ACL_MEMCPY_DEVICE_TO_DEVICE = 3。
ret = acl.rt.memcpy(dev_ptr_b, size, dev_ptr_a, size, ACL_MEMCPY_DEVICE_TO_DEVICE)

## 4.使用完内存中的数据后,需及时释放资源。
ret = acl.rt.free(dev_ptr_a)
ret = acl.rt.free(dev_ptr_b)

## ......

异步内存复制

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考,示例代码如下:

import acl
## ......

## 1.申请内存。
size = 1 * 1024 * 1024
dev_ptr_a, ret = acl.rt.malloc(size, ACL_MEM_MALLOC_NORMAL_ONLY)
dev_ptr_b, ret = acl.rt.malloc(size, ACL_MEM_MALLOC_NORMAL_ONLY)

## 2.申请内存后,可向内存中读入数据,该自定义函数read_file由用户实现。
read_file(fileName, dev_ptr_a, size)

## 3.异步内存复制。
## 异步内存复制,dev_ptr_a表示Device上源内存地址的指针地址,dev_ptr_b表示Device上目的内存地址的指针地址,size表示内存大小。
## ACL_MEMCPY_DEVICE_TO_DEVICE = 3。
stream = acl.rt_create_stream()
ret = acl.rt.memcpy_async(dev_ptr_b, size, dev_ptr_a, size, ACL_MEMCPY_DEVICE_TO_DEVICE, stream)
ret = acl.rt.synchronize_stream(stream)

## 4.使用完内存中的数据后,需及时释放资源。
ret = acl.rt.destroy_stream(stream)
ret = acl.rt.free(dev_ptr_a)
ret = acl.rt.free(dev_ptr_b)

## ......

父主题: 数据传输

模型加载

模型构建中的说明构建出模型后,需进行模型加载,为模型执行做准备。

接口调用流程

开发应用时,如果涉及整网模型推理,则应用程序中必须包含模型加载的代码逻辑,关于模型加载的接口调用流程,请先参见pyACL接口调用流程了解整体流程,再查看本节中的流程说明。

本节描述的是整网模型加载的接口调用流程,对于算子模型加载与执行的详细说明请参见单算子调用

pyACL提供两套模型加载的接口,用户可根据具体使用场景选择对应的模型加载接口:

  • 图1所示,根据不同的加载方式(从文件加载、从内存加载等)选择不同的接口,操作相对简单,但需要记住各种方式的加载接口
  • 图2所示,针对不同的加载方式(从文件加载、从内存加载等),只需****设置接口中的配置参数,适用各种加载方式,但****涉及多个接口配合使用,分别用于创建配置对象、设置对象中的属性值、加载模型。

图1 模型加载流程(通过不同接口区分加载方式)

图2 模型加载流程(通过接口中的配置参数区分加载方式)

关键接口的说明如下:

  • 在模型加载前,需要先构建出适配昇腾AI处理器的离线模型(*.om文件),构建方式请参见模型构建

  • 由****用户管理内存时,为确保内存不浪费,在申请工作内存、权值内存前,需要调用acl.mdl.query_size接口查询模型运行时所需工作内存、权值内存的大小

    如果模型输入数据的Shape不确定,则不能调用acl.mdl.query_size接口查询内存大小,在加载模型时,就无法由用户管理内存,因此需选择由系统管理内存的模型加载接口(例如,acl.mdl.load_from_fileacl.mdl.load_from_mem)。

  • 支持以下方式加载模型,模型加载成功后,返回标识模型的模型ID:

    • 通过不同接口区分加载方式,从使用的接口上区分从文件加载,还是从内存加载,以及内存是由系统内部管理,还是由用户管理。
      • acl.mdl.load_from_file从文件加载离线模型数据,由系统内部管理内存。
      • acl.mdl.load_from_mem从内存加载离线模型数据,由系统内部管理内存。
      • acl.mdl.load_from_file_with_mem从文件加载离线模型数据,由用户自行管理模型运行的内存(包括工作内存和权值内存,工作内存用于模型执行过程中的临时数据,权值内存用于存放权值数据)。
      • acl.mdl.load_from_mem_with_mem从内存加载离线模型数据,由用户自行管理模型运行的内存(包括工作内存和权值内存)。
    • 通过接口中的配置参数区分加载方式,使用acl.mdl.set_config_opt接口、acl.mdl.load_with_config接口时,是通过配置对象中的属性来区分,在加载模型时是从文件加载还是从内存加载,以及内存是由系统内部管理还是由用户管理。

示例代码

模型加载成功,会返回标识模型的ID,在模型执行时需要使用该ID。

您可以从样例介绍中获取完整样例代码。

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

## 初始化变量。
model_path = "./model/resnet50.om"
## ......

## 加载离线模型文件(适配昇腾AI处理器的离线模型),由系统管理模型运行的内存(包括权值内存、工作内存)。
## 模型加载成功,返回标识模型的ID。
model_id, ret = acl.mdl.load_from_file(model_path)

## ......

父主题: 模型推理基本场景

模型执行

基本原理

开发应用时,如果涉及整网模型推理,则应用程序中必须包含模型执行的代码逻辑,关于模型执行的接口调用流程。请先参见pyACL接口调用流程了解整体流程,再查看本节中的流程说明。

本节描述的是整网模型执行的接口调用流程,对于算子模型加载与执行的详细说明请参见单算子调用

  • 在模型加载之后,模型执行之前,需要准备输入、输出数据结构,将输入数据传输到模型输入数据结构的对应内存中。

  • 模型执行结束后,若无需使用输入数据、aclmdlDesc类型、aclmdlDataset类型、aclDataBuffer类型等相关资源,需及时释放内存、销毁对应的数据类型,防止内存异常。

    模型可能存在多个输入、多个输出,每个输入/输出的内存地址、内存大小用aclDataBuffer类型的数据来描述,针对每个输入/输出,需调用acl.destroy_data_buffer接口销毁相应的aclDataBuffer类型,并调用acl.rt.free接口释放内存中的数据。

接口调用流程

图1 基本的模型推理流程

关键接口的说明如下:

  1. 调用acl.mdl.create_desc接口创建描述模型基本信息的数据类型。

  2. 调用acl.mdl.get_desc接口根据模型加载中返回的模型ID获取模型基本信息

  3. 准备模型执行的输入、输出数据结构,具体流程请参见准备模型执行的输入/输出数据结构

    如果模型的输入涉及动态Batch动态分辨率动态AIPP动态维度(ND格式)等特性,请参见模型动态推理模型动态AIPP推理

  4. 执行模型推理

    对于固定的多Batch场景,需要满足Batch数后,才能将输入数据发送给模型进行推理。不满足Batch数时,用户需根据自己的实际场景处理。

    当前系统支持模型的同步推理和异步推理:

  5. 获取模型推理的结果,用于后续处理。

    对于同步推理,直接获取模型推理的输出数据即可。

    对于异步推理,在实现Callback功能时,在回调函数内获取模型推理的结果,供后续使用。

  6. 释放内存

    调用acl.rt.free接口释放Device上的内存。

  7. 释放相关数据类型的数据

    在模型推理结束后,需及时依次调用acl.destroy_data_buffer接口和acl.mdl.destroy_dataset接口释放描述模型输入的数据。如果存在多个输入、输出,需调用多次acl.destroy_data_buffer接口。

准备模型执行的输入/输出数据结构

pyACL提供了以下数据类型来描述模型、模型输入、模型输出以及存放数据的内存,在模型执行前,需要构造好这些数据类型,作为模型执行的输入:

  • 使用aclmdlDesc类型的数据描述模型基本信息(例如输入/输出的个数、名称、数据类型、Format、维度信息等)。

    模型加载成功后,用户可根据模型的ID,调用acl.mdl.get_desc接口获取该模型的描述信息,进而从模型的描述信息中获取模型输入/输出的个数、内存大小、维度信息、Format、数据类型等信息,可参见aclmdlDesc类型下的操作接口。

  • 使用aclmdlDataset类型的数据描述模型的输入/输出数据,模型可能存在多个输入、多个输出。

    调用aclmdlDataset类型下的操作接口添加aclDataBuffer类型的数据、获取aclDataBuffer的个数等。

  • 每个输入/输出的内存地址、内存大小用aclDataBuffer类型的数据来描述。

    调用aclDataBuffer类型下的操作接口获取内存地址、内存大小等。

图2 aclmdlDataset类型与aclDataBuffer类型的关系

了解相关的数据类型后,可以使用这些数据类型的操作接口准备模型的输入、输出数据结构,如下图所示。

图3 模型执行的输入/输出数据结构的准备流程

关键说明如下:

示例代码(准备模型的输入和输出数据结构)

您可以从样例介绍中获取完整样例代码。

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

## 初始化变量。
ACL_MEM_MALLOC_HUGE_FIRST = 0

## 1.根据模型的ID,获取该模型的描述信息。
## self.model_desc为aclmdlDesc类型。
self.model_desc = acl.mdl.create_desc()
ret = acl.mdl.get_desc(self.model_desc, self.model_id)

## 2.准备模型推理的输入数据集。
## 创建aclmdlDataset类型的数据,描述模型推理的输入。
self.load_input_dataset = acl.mdl.create_dataset()
## 获取模型输入的数量。
input_size = acl.mdl.get_num_inputs(self.model_desc)
self.input_data = []
## 循环为每个输入申请内存,并将每个输入添加到aclmdlDataset类型的数据中。
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})

## 3.准备模型推理的输出数据集。
## 创建aclmdlDataset类型的数据,描述模型推理的输出。
self.load_output_dataset = acl.mdl.create_dataset()
## 获取模型输出的数量。
output_size = acl.mdl.get_num_outputs(self.model_desc)
self.output_data = []
## 循环为每个输出申请内存,并将每个输出添加到aclmdlDataset类型的数据中。
for i in range(output_size):
buffer_size = acl.mdl.get_output_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})

## ......

示例代码(执行模型)

您可以从样例介绍中获取完整样例代码。

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

ACL_MEMCPY_HOST_TO_DEVICE = 1
ACL_MEMCPY_DEVICE_TO_HOST = 2
NPY_BYTE = 1
images_list = ["./data/dog1_1024_683.jpg", "./data/dog2_1024_683.jpg"]

for image in images_list:
# 1.自定义函数transfer_pic,使用Python库读取图片文件,并对图片进行缩放、剪裁等操作。
# transfer_pic函数的实现请参考样例中源代码。
img = transfer_pic(image)

# 2.准备模型推理的输入数据,运行模式默认为运行模式为ACL_HOST,当前实例代码中模型只有一个输入。

bytes_data = img.tobytes()
np_ptr = acl.util.bytes_to_ptr(bytes_data)
# 将图片数据从Host传输到Device。
ret = acl.rt.memcpy(self.input_data[0]["buffer"], self.input_data[0]["size"], np_ptr,
self.input_data[0]["size"], ACL_MEMCPY_HOST_TO_DEVICE)

# 3.执行模型推理。
# self.model_id表示模型ID,在模型加载成功后,会返回标识模型的ID。
ret = acl.mdl.execute(self.model_id, self.load_input_dataset, self.load_output_dataset)

## ......

示例代码(处理推理结果:直接处理内存中的数据)

您可以从样例介绍中获取完整样例代码。

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

以图片分类网络为例,模型执行结束后,需处理每一张图片的模型推理结果,直接输出top5置信度的类别编号。

## 处理模型推理的输出数据,输出top5置信度的类别编号。
inference_result = []
for i, item in enumerate(self.output_data):
buffer_host, ret = acl.rt.malloc_host(self.output_data[i]["size"])
# 将推理输出数据从Device传输到Host。
ret = acl.rt.memcpy(buffer_host, self.output_data[i]["size"], self.output_data[i]["buffer"],
self.output_data[i]["size"], ACL_MEMCPY_DEVICE_TO_HOST)

bytes_out = acl.util.ptr_to_bytes(buffer_host, self.output_data[i]["size"])
data = np.frombuffer(bytes_out, dtype=np.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]))

## ......

示例代码(释放模型的输入、输出资源)

您可以从样例介绍中获取完整样例代码。

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br><br># 释放模型推理的输入、输出资源。<br># 释放输入资源,包括数据结构和内存。<br>while self.input_data:<br> item = self.input_data.pop()<br> ret = acl.rt.free(item["buffer"])<br>input_number = acl.mdl.get_dataset_num_buffers(self.load_input_dataset)<br>for i in range(input_number):<br> data_buf = acl.mdl.get_dataset_buffer(self.load_input_dataset, i)<br> if data_buf:<br> ret = acl.destroy_data_buffer(data_buf)<br>ret = acl.mdl.destroy_dataset(self.load_input_dataset)<br># 释放输出资源,包括数据结构和内存。<br>while self.output_data:<br> item = self.output_data.pop()<br> ret = acl.rt.free(item["buffer"])<br>output_number = acl.mdl.get_dataset_num_buffers(self.load_output_dataset)<br>for i in range(output_number):<br> data_buf = acl.mdl.get_dataset_buffer(self.load_output_dataset, i)<br> if data_buf:<br> ret = acl.destroy_data_buffer(data_buf)<br>ret = acl.mdl.destroy_dataset(self.load_output_dataset)<br>

父主题: 模型推理基本场景

模型卸载

关于模型卸载的接口调用流程,请参见pyACL接口调用流程

基本原理

在模型推理结束后,还需要通过acl.mdl.unload接口卸载模型,并销毁aclmdlDesc类型的模型描述信息。

示例代码

## 卸载模型。
ret = acl.mdl.unload(self.model_id)

## 释放模型描述信息。
if self.model_desc:
ret = acl.mdl.destroy_desc(self.model_desc)
self.model_desc = None

父主题: 模型推理基本场景

媒体数据处理基础知识

本章主要介绍图像/视频数据处理的具体功能、接口调用流程以及示例代码。

DVPP图像/视频数据处理的典型使用场景

如果源图或视频的分辨率、格式等与模型的要求不一致时,我们可以将源图或视频处理成符合模型的要求。如下为典型场景的举例

  • 视频解码、缩放

    使用YOLOv3模型实现目标检测的场景下,用户提供的输入视频为H264/H265编码格式、分辨率为1920*1080,但YOLOv3模型要求的输入图片格式为RGB/YUV、分辨率为416*416,两者不一致,此时可对视频执行以下一系列处理。

    图1 视频解码、缩放使用场景图

  • 图片解码、缩放、格式转换

    使用ResNet-50模型实现图片分类的场景下,用户提供的输入图片为JPEG编码格式、分辨率为1280*720,但ResNet-50模型要求的输入图片格式为RGB、分辨率为224*224,两者不一致,此时可对图片执行以下一系列处理。

    图2 图片解码、缩放、格式转换使用场景图

  • 抠图、缩放、格式转换

    使用ResNet-50模型实现图片分类的场景下,用户提供的输入图片格式为YUV420SP、分辨率为1280*720,但ResNet-50模型要求的输入图片格式为RGB、分辨率为224*224,两者不一致,此时对图片执行以下一系列处理。

    图3 抠图、缩放、格式转换使用场景图

媒体数据处理功能开发流程

图4 开发流程

  1. 准备环境

    请参见应用开发环境准备

  2. 创建代码目录

    在开发应用前,您需要先创建目录,存放代码文件、脚本、测试图片数据、模型文件等。

  3. (可选)构建模型

    模型推理场景下,必须要有适配昇腾AI处理器的离线模型(*.om文件),请参见模型构建

    :::note 说明 如果应用中涉及模型推理,则需要构建模型。 :::

  4. 开发应用

    如果应用中涉及模型推理,请参见模型推理基本场景扩展更多特性编写相应的代码。

  5. 运行应用,请参见应用调试

父主题: 媒体数据处理

在线提单