AscendCL 应用开发指南(Python)
学习向导
概述
本文用于指导开发人员基于现有模型使用pyACL(Python Ascend Computing Language)提供的Python语言API库开发深度神经网络应用,用于实现目标识别、图像分类等功能。
通过本文档您可以达成:
- 了解pyACL的功能架构、基本概念以及接口的典型调用流程。
- 使用pyACL接口进行应用开发的基本流程和实现方法。
- 能够基于本文档中的样例,扩展进行其它应用的开发。
掌握以下经验和技能可以更好地理解本文档:
- 具备Python语言程序开发能力。
- 对机器学习、深度学习有一定的了解。
文档使用建议
如果您是第一次使用本文档,或者还不清楚以下问题时,建议先从快速入门了解下应用开发的整体过程,然后了解概述,再通过开发基础推理应用、图像/视频数据处理、单算子调用等章节的接口调用流程与示例代码来深入学习。
- pyACL在整体架构的什么位置?
- pyACL中的Device、Stream、Context是用来做什么的?
- 使用pyACL接口开发应用时,包含哪几个基本步骤?
如果您在使用本文档时,已了解使用pyACL接口开发应用的基本步骤,想进一步学习时,可参照下图的应用开发向导。

快速入门
在本章节中,您可以通过一个简单的图片分类应用代码示例了解使用pyACL接口(Python语言接口)开发应用的基本过程以及开发过程中涉及的关键概念。
什么是图片分类应用?
“图片分类应用”顾名思义标识图片所属的分类。
图1 图片分类应用

“图片分类应用”是怎么做到这一点的呢?首先,需要有一个能做到图片分类的模型,我们可以直接使用一些训练好的开源模型,也可以基于开源模型的源码进行修改、重新训练,还可以自己基于算法、框架构建适合自己的模型。
本次快速入门样例,我们直接获取已训练好的开源模型,这种方式相对简单,此处我们选择的是ONNX框架的ResNet-50模型。
ResNet-50模型的基本介绍如下:
- 输入数据:RGB格式、224*224分辨率的输入图片。
- 输出数据:图片的类别标签及其对应置信度。
:::note 说明
- 置信度是指图片所属某个类别可能性。
- 类别标签和类别的对应关系与训练模型时使用的数据集有关,需要查阅对应数据集的标签及类别的对应关系。 :::
前提条件
-
已在环境上部署昇腾AI软件栈。
-
安装环境,请参见应用开发环境准备。
-
安装必要的Python软件依赖(Pillow、numpy)。
pip3 install pillow numpy
了解基本概念
-
Host
Host指与Device相连接的X86服务器、ARM服务器,会利用Device提供的NN(Neural-Network)计算能力,完成业务。
-
Device
Device指安装了昇腾AI处理器的硬件设备,利用PCIe接口与Host侧连接,提供NN计算能力。
-
开发环境、运行环境
开发环境指编译开发代码的环境,运行环境指运行算子、推理或训练等程序的环境,运行环境上必须带昇腾AI处理器。
:::note 说明 您可以登录对应的环境,执行“arch”命令查询其操作系统的架构。 :::
-
运行用户
运行驱动进程、推理业务或执行训练的用户。
了解开发过程
pyACL(Python Ascend Computing Language)是一套在AscendCL的基础上使用CPython封装得到的Python API库,使用户可以通过Python进行昇腾AI处理器的运行管理、资源管理等,实现在昇腾CANN平台上进行深度学习推理计算、图形图像预处理、单算子加速计算等能力。
图2 开发流程

了解了这些大步骤后,下面我们再展开来说明开发应用具体涉及哪些关键功能?各功能又使用哪些pyACL接口,这些pyACL接口怎么串联?
虽然此时您可能不理解所有细节,但这也不影响,通过快速入门旨在先了解整体的代码逻辑,后续再深入学习,了解其它细节。
创建代码目录
请参考以下目录结构,在开发环境中下创建“first_app”代码目录(例如“$HOME”目录)。
first_app
├── data
│ ├── dog1_1024_683.jpg //测试图片1
│ └── dog2_1024_683.jpg //测试图片2
└── model //用于存放ONNX ResNet-50模型文件
└── resnet50.onnx
其中,需准备以下数据与模型。
-
准备测试数据,本次样例需要使用两张动物图片,请从以下链接获取,将下载好的图片上传至“first_app/data”目录。
-
准备模型数据,参考以下命令,将ONNX模型下载至“model”目录下或通过模型获取链接下载到本地后上传到运行环境。
cd $HOME/first_app/modelwget https://obs-9be7.obs.cn-east-2.myhuaweicloud.com/003_Atc_Models/resnet50/resnet50.onnx -
模型转换,对于开源框架的模型,不能直接在昇腾AI处理器上进行推理,需要使用ATC(Ascend Tensor Compiler)工具将开源框架的网络模型转换为适配昇腾AI处理器的离线模型(*.om文件)。
执行以下命令,将原始模型转换为昇腾AI处理器能识别的*.om模型文件。请注意,执行命令的用户需具有命令中相关路径的可读、可写权限。
atc --model=resnet50.onnx --framework=5 --output=resnet50 --input_shape="actual_input_1:1,3,224,224" --soc_version=<soc_version>各参数的解释如下,详细约束说明请参见《ATC工具使用指南》。
-
--model:ResNet-50网络的模型文件的路径。
-
--framework:原始框架类型。5表示ONNX。
-
--output:resnet50.om模型文件的路径。请注意,记录保存该om模型文件的路径,后续开发应用时需要使用。
-
--input_shape:模型输入数据的shape。
-
--soc_version:昇腾AI处理器的版本。
:::note 说明 如果无法确定当前设备的soc_version,则在安装NPU驱动包的服务器执行npu-smi info命令进行查询,在查询到的“Name”前增加Ascend信息,例如“Name”对应取值为xxxyy,实际配置的soc_version值为Ascendxxxyy。 :::
-
开发应用
在“first_app”目录下创建“first_app.py”文件并依次写入以下内容。
-
引入pyACL必要的模块,定义pyACL常量。
import osimport aclimport numpy as npfrom PIL import ImageACL_MEM_MALLOC_HUGE_FIRST = 0ACL_MEMCPY_HOST_TO_DEVICE = 1ACL_MEMCPY_DEVICE_TO_HOST = 2 -
定义模型对象。
网络模型对象中应当包含以下函数。
- 初始化函数。
- 执行推理任务函数。
- 析构函数。
对于后续的使用,用户只需要调用网络模型中的forward函数,传入对应的输入数据即可获得相应的输出。
class net:# def __init__(self, model_path):# 初始化函数,需要在后续步骤中实现。# def forward(self, inputs):# 执行推理任务,需要在后续步骤中实现。# def __del__(self):# 析构函数,按照初始化资源的相反顺序释放资源,需要在后续步骤中实现。 -
实现初始化方法,具体涉及以下步骤(请在net类中实现)。
- 调用acl.init接口进行初始化,在使用pyACL开发应用时,需要先初始化pyACL(在完成所有pyACL接口调用后,还需进行去初始化)。初始化时,也可通过JSON配置文件,向初始化接口传入配置参数(例如,传入性能相关的采集信息配置)。
- 通过ID,调用acl.rt.set_device接口指定具体的计算设备(Device)。
- 加载模型。
- 在此处样例选择调用acl.mdl.load_from_file接口加载om模型文件。
- 调用acl.mdl.create_desc接口创建模型描述信息。
- 根据加载成功的模型ID,调用acl.mdl.get_desc接口获取该模型的描述信息。
- 创建输入数据集与输出数据集,对应方法在4中实现。
def __init__(self, model_path):# 初始化函数self.device_id = 0# step1: 初始化ret = acl.init()# 指定运算的Deviceret = acl.rt.set_device(self.device_id)# step2: 加载模型,本示例为ResNet-50模型# 加载离线模型文件,返回标识模型的IDself.model_id, ret = acl.mdl.load_from_file(model_path)# 创建空白模型描述信息,获取模型描述信息的指针地址self.model_desc = acl.mdl.create_desc()# 通过模型的ID,将模型的描述信息填充到model_descret = acl.mdl.get_desc(self.model_desc, self.model_id)# step3:创建输入输出数据集# 创建输入数据集self.input_dataset, self.input_data = self.prepare_dataset('input')# 创建输出数据集self.output_dataset, self.output_data = self.prepare_dataset('output') -
实现数据集创建方法(请在net类中实现)。
在调用pyACL接口进行模型推理时,模型推理有输入、输出数据,输入、输出数据需要按照pyACL规定的数据类型存放。相关数据类型如下:
-
使用aclmdlDesc类型的数据描述模型基本信息(例如输入/输出的个数、名称、数据类型、Format、维度信息等)。
模型加载成功后,用户可根据模型的ID,调用该数据类型下的操作接口获取该模型的描述信息,进而从模型的描述信息中获取模型输入/输出的个数、内存大小、维度信息、Format、数据类型等信息。
-
使用aclDataBuffer类型的数据来描述每个输入/输出的内存地址、内存大小。
调用aclDataBuffer类型下的操作接口获取内存地址、内存大小等,便于向内存中存放输入数据、获取输出数据。
-
使用aclmdlDataset类型的数据描述模型的输入/输出数据。
模型可能存在多个输入、多个输出,调用aclmdlDataset类型的操作接口添加多个aclDataBuffer类型的数据。
图3 aclmdlDataset类型与aclDataBuffer类型的关系

def prepare_dataset(self, io_type):# 准备数据集if io_type == "input":# 获得模型输入的个数io_num = acl.mdl.get_num_inputs(self.model_desc)acl_mdl_get_size_by_index = acl.mdl.get_input_size_by_indexelse:# 获得模型输出的个数io_num = acl.mdl.get_num_outputs(self.model_desc)acl_mdl_get_size_by_index = acl.mdl.get_output_size_by_index# 创建aclmdlDataset类型的数据,描述模型推理的输入。dataset = acl.mdl.create_dataset()datas = []for i in range(io_num):# 获取所需的buffer内存大小buffer_size = acl_mdl_get_size_by_index(self.model_desc, i)# 申请buffer内存buffer, ret = acl.rt.malloc(buffer_size, ACL_MEM_MALLOC_HUGE_FIRST)# 从内存创建buffer数据data_buffer = acl.create_data_buffer(buffer, buffer_size)# 将buffer数据添加到数据集_, ret = acl.mdl.add_dataset_buffer(dataset, data_buffer)datas.append({"buffer": buffer, "data": data_buffer, "size": buffer_size})return dataset, datas -
-
实现同步推理方法(请在net类中实现)。
def forward(self, inputs):# 执行推理任务# 遍历所有输入,拷贝到对应的buffer内存中input_num = len(inputs)for i in range(input_num):bytes_data = inputs[i].tobytes()bytes_ptr = acl.util.bytes_to_ptr(bytes_data)# 将图片数据从Host传输到Device。ret = acl.rt.memcpy(self.input_data[i]["buffer"], # 目标地址 deviceself.input_data[i]["size"], # 目标地址大小bytes_ptr, # 源地址 hostlen(bytes_data), # 源地址大小ACL_MEMCPY_HOST_TO_DEVICE) # 模式:从host到device# 执行模型推理。ret = acl.mdl.execute(self.model_id, self.input_dataset, self.output_dataset)# 处理模型推理的输出数据,输出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, # 目标地址 hostself.output_data[i]["size"], # 目标地址大小self.output_data[i]["buffer"], # 源地址 deviceself.output_data[i]["size"], # 源地址大小ACL_MEMCPY_DEVICE_TO_HOST) # 模式:从device到host# 从内存地址获取bytes对象bytes_out = acl.util.ptr_to_bytes(buffer_host, self.output_data[i]["size"])# 按照float32格式将数据转为numpy数组data = np.frombuffer(bytes_out, dtype=np.float32)inference_result.append(data)vals = np.array(inference_result).flatten()# 对结果进行softmax转换vals = np.exp(vals)vals = vals / np.sum(vals)return vals -
实现析构方法(请在net类中实现)。
- 销毁数据集资源(buffer数据、buffer内存、输入数据集、输出数据集)。
- 销毁模型描述、卸载模型。
- 释放计算资源。
- 所有pyACL接口调用结束后(或在进程退出前),调用acl.finalize接口进行pyACL进行去初始化。
在推理过程中可能会抛出异常,请将资源释放步骤实现在析构方法中确保资源能够得到正确释放。以下内容仅供参考,实际情况下需要考虑更多情况下的资源释放问题。
def __del__(self):# 析构函数 按照初始化资源的相反顺序释放资源。# 销毁输入输出数据集for dataset in [self.input_data, self.output_data]:while dataset:item = dataset.pop()ret = acl.destroy_data_buffer(item["data"]) # 销毁buffer数据ret = acl.rt.free(item["buffer"]) # 释放buffer内存ret = acl.mdl.destroy_dataset(self.input_dataset) # 销毁输入数据集ret = acl.mdl.destroy_dataset(self.output_dataset) # 销毁输出数据集# 销毁模型描述ret = acl.mdl.destroy_desc(self.model_desc)# 卸载模型ret = acl.mdl.unload(self.model_id)# 释放deviceret = acl.rt.reset_device(self.device_id)# acl去初始化ret = acl.finalize() -
实现图像预处理函数。
def transfer_pic(input_path):# 图像预处理input_path = os.path.abspath(input_path)with Image.open(input_path) as image_file:# 缩放为224*224img = image_file.resize((224, 224))# 转换为float32类型ndarrayimg = np.array(img).astype(np.float32)# 根据imageNet图片的均值和方差对图片像素进行归一化img -= [123.675, 116.28, 103.53]img /= [58.395, 57.12, 57.375]# RGB通道交换顺序为BGRimg = img[:, :, ::-1]# resnet50为色彩通道在前img = img.transpose((2, 0, 1))# 返回并添加batch通道return np.array([img]) -
调用forward函数(具体实现请参见5),执行同步推理并在屏幕中打印top5类别编号及置信度。
def print_top_5(data):top_5 = data.argsort()[::-1][:5]print("======== top5 inference results: =============")for j in top_5:print("[%d]: %f" % (j, data[j]))if __name__ == "__main__":resnet50 = net('./model/resnet50.om')image_paths = ["./data/dog1_1024_683.jpg", "./data/dog2_1024_683.jpg"]for path in image_paths:# 图像预处理,此处仅供参考,用户按照自己需求进行预处理image = transfer_pic(path)# 将数据按照每个输入的顺序构造list传入,当前示例的ResNet-50模型只有一个输入result = resnet50.forward([image])# 输出top_5print_top_5(result)del resnet50
运行应用
将编写好的“first_app”文件夹及内容上传到运行环境,进入到代码目录下,检查环境变量配置是否正确,然后执行以下命令。
python3 first_app.py
可以得到如下输出,分别为两张测试图片的top5分类信息。
其中**[161]: 0.809159**表示的是类别标识索引“161”的置信度为“0.809159”。
======== top5 inference results: =============
[161]: 0.809159
[162]: 0.103680
[178]: 0.017600
[166]: 0.013922
[212]: 0.009644
======== top5 inference results: =============
[267]: 0.728299
[266]: 0.101693
[265]: 0.100117
[151]: 0.004214
[160]: 0.002721
:::note 说明 类别标签和类别的对应关系与训练模型时使用的数据集有关,本样例使用的模型是基于imagenet数据集进行训练的,您可以在互联网上查阅对应数据集的标签及类别的对应关系。
当前屏显信息中的类别标识与类别的对应关系如下:
"161": ["basset", "basset hound"]
"162": ["beagle"]
"163": ["bloodhound", "sleuthhound"]
"166": ["Walker hound", "Walker foxhound"]
"167": ["English foxhound"] :::
基于AscendCL的pyACL
AscendCL(Ascend Computing Language)是一套用于在昇腾平台上开发深度神经网络应用的C语言API库,提供运行资源管理、内存管理、模型加载与执行、算子加载与执行、媒体数据处理等API,能够实现利用昇腾硬件计算资源、在昇腾CANN平台上进行深度学习推理计算、图形图像预处理、单算子加速计算等能力。简单来说,就是统一的API框架,实现对所有资源的调用。
计算资源层是昇腾AI处理器的硬件算力基础,主要完成神经网络的矩阵相关计算、完成控制算子/标量/向量等通用计算和执行控制功能、完成图像和视频数据的预处理,为深度神经网络计算提供了执行上的保障。
**pyACL(Python Ascend Computing Language)**就是在AscendCL的基础上使用CPython封装得到的Python API库,使用户可以通过Python进行昇腾AI处理器的运行管理、资源管理等。
图1 逻辑架构图

父主题: 概述
基本概念
表1 概念介绍
| 概念 | 描述 |
|---|---|
| 同步/异步 | 本文中提及的同步、异步是站在调用者和执行者的角度,在当前场景下,若在Host调用接口后不等待Device执行完成再返回,则表示Host的调度是异步的;若在Host调用接口后需等待Device执行完成再返回,则表示Host的调度是同步的。 |
| 进程/线程 | 本文中提及的进程、线程,若无特别注明,则表示Host上的进程、线程。 |
| Host | Host指与Device相连接的X86服务器、ARM服务器,会利用Device提供的NN(Neural-Network )计算能力,完成业务。 |
| Device | Device指安装了昇腾AI处理器的硬件设备,利用PCIe接口与Host侧连接,为Host提供NN计算能力。若存在多个Device,多个Device之间的内存资源不能共享。 |
| Context | Context作为一个容器,管理了所有对象(包括Stream、Event、设备内存等)的生命周期。不同Context的Stream、不同Context的Event是完全隔离的,无法建立同步等待关系。 Context分为两种: - 默认Context:调用acl.rt.set_device接口指定用于运算的Device时,系统会自动隐式创建一个默认Context,一个Device对应一个默认Context,默认Context不能通过acl.rt.destroy_context接口来释放。 - 显式创建的Context:推荐,在进程或线程中调用acl.rt.create_context接口显式创建一个Context。 |
| Stream | Stream用于维护一些异步操作的执行顺序,确保按照应用程序中的代码调用顺序在Device上执行。 基于Stream的kernel执行和数据传输能够实现Host运算操作、Host与Device间的数据传输、Device内的运算并行。 Stream分两种: - 默认Stream:调用acl.rt.set_device接口指定用于运算的Device时,系统会自动隐式创建一个默认Stream,一个Device对应一个默认Stream,默认Stream不能通过acl.rt.destroy_stream接口来释放。 - 显式创建的Stream:推荐,在进程或线程中调用acl.rt.create_stream接口显式创建一个Stream。 |
| Event | 支持调用pyACL接口同步Stream之间的任务,包括同步Host与Device之间的任务、同一个Device上的多个任务。 例如,若stream2的任务依赖stream1的任务,想保证stream1中的任务先完成,这时可创建一个Event,并将Event插入到stream1,在执行stream2的任务前,先同步等待Event完成。 |
| AIPP | AIPP(Artificial Intelligence Pre-Processing)用于在AI Core上完成图像预处理,包括色域转换(转换图像格式)、图像归一化(减均值/乘系数)和抠图(指定抠图起始点,抠出神经网络需要大小的图片)等。 AIPP区分为静态AIPP和动态AIPP。您只能选择静态AIPP或动态AIPP其中一种方式来处理图片,不能同时配置静态AIPP和动态AIPP两种方式。 - 静态AIPP:模型转换时设置AIPP模式为静态,同时设置AIPP参数,模型生成后,AIPP参数值被保存在离线模型(*.om)中,每次模型推理过程采用固定的AIPP预处理参数(无法修改)。 如果使用静态AIPP方式,多Batch情况下共用同一份AIPP参数。 - 动态AIPP:模型转换时仅设置AIPP模式为动态,每次模型推理前,根据需求,在执行模型前设置动态AIPP参数值,然后在模型执行时可使用不同的AIPP参数。 如果使用动态AIPP方式,多Batch可使用不同的AIPP参数。 |
| 动态batch/动态分辨率 | 在某些场景下,模型每次输入的batch size或分辨率是不固定的,如检测出目标后再执行目标识别网络,由于目标个数不固定导致目标识别网络输入batch size不固定。 - 动态batch:用户执行推理时,其batch size是动态可变的。 - 动态分辨率:用户执行推理时,每张图片的分辨率H*W是动态可变的。 |
| 动态维度(ND格式) | 为了支持Transformer等网络在输入格式的维度不确定的场景,需要支持ND格式下任意维度的动态设置。 ND表示支持任意格式,当前N ≤ 4。 |
| 通道 | 在RGB色彩模式下,图像通道就是指单独的红色R、绿色G、蓝色B部分。也就是说,一幅完整的图像,是由红色、绿色、蓝色三个通道组成的,它们共同作用产生了完整的图像。同样在HSV色系中指的是色调H、饱和度S、亮度V三个通道。 |
| RC模式 | 以昇腾 AI 处理器的PCIe的工作模式进行区分,如果PCIe工作在主模式,可以扩展外设,则称为RC模式。 |
Device、Context、Stream之间的关系
图1 Device、Context、Stream之间的关系

- Device,用于指定计算设备。
- Device的生命周期源于首次调用acl.rt.set_device接口。
- 每次调用acl.rt.set_device接口,系统会进行引用计数加1;调用acl.rt.reset_device接口,系统会进行引用计数减1。
- 当引用计数减为零时,在本进程中Device上的资源不可用。
- Context,在Device下,一个Context一定属于一个唯一的Device。
-
Context分隐式创建和显式创建。
-
隐式创建的Context(即默认Context),生命周期始于调用acl.rt.set_device接口,终结于调用acl.rt.reset_device接口使引用计数为零时。
隐式Context只会被创建一次,调用acl.rt.set_device接口重复指定同一个Device,只增加隐式创建的Context的引用计数。
-
显式创建的Context,生命周期始于调用acl.rt.create_context接口,终结于调用acl.rt.destroy_context接口。
-
若在某一进程内创建多个Context(Context的数量与Stream相关,Stream数量有限制,请参见acl.rt.create_stream),当前线程在同一时刻内只能使用其中一个Context,建议通过acl.rt.set_context接口明确指定当前线程的Context,增加程序的可维护性**。**
-
进程内的Context是共享的,可以通过acl.rt.set_context进行切换。
-
- Stream,是Device上的执行流,在同一个Stream中的任务执行严格保序。
- Stream分隐式创建和显式创建。
- 每个Context都会包含一个默认Stream,属于隐式创建,隐式创建的Stream生命周期同归属的Context。
- 用户可以显式创建Stream,显式创建的Stream生命周期始于调用acl.rt.create_stream,终结于调用acl.rt.destroy_stream接口。显式创建的Stream归属的Context被销毁或生命周期结束后,会影响该Stream的使用,虽然此时Stream没有被销毁,但不可再用。
- Task/Kernel,是Device上真正的任务执行体。
线程、Context、Stream之间的关系
-
一个用户线程一定会绑定一个Context,所有Device的资源使用或调度,都必须基于Context。
-
一个线程中当前会有一个唯一的Context在用,Context中已经关联了本线程要使用的Device。
-
可以通过acl.rt.set_context进行Device的快速切换。示例代码如下,仅供参考,不可以直接拷贝运行:
…ctx1, ret = acl.rt.create_context(0) #使用acl.rt.create_context接口通过传入Device Id创建Context。stream, ret = acl.rt.create_stream()ret = acl.op.execute_v2(op_type, input_desc, inputs, output_desc, outputs, attr, stream)ctx2, ret = acl.rt.create_context(1)# 在当前线程中,创建ctx2后,当前线程对应的Context切换为ctx2,对应在Device 1进行后续的计算任务,本例中将在Device 1上进行op2的执行调用。stream2, ret = acl.rt.create_stream()ret = acl.op.execute_v2(op_type2, input_desc, inputs, output_desc, outputs, attr, stream2)ret = acl.rt.set_context(ctx1);# 在当前线程中,通过Context切换,使后续计算任务在对应的Device 0上进行。ret = acl.op.execute_v2(op3,...,s1)… -
一个线程中可以创建多个Stream,不同的Stream上计算任务是可以并行执行,多线程场景下,也可以每个线程创建一个Stream,线程之间的Stream在Device上相互独立,每个Stream内部的任务是按照Stream下发的顺序执行。
-
多线程的调度依赖于运行应用的操作系统调度,多Stream调度Device侧,由Device上调度组件进行调度。
一个进程内多个线程间的Context迁移
-
一个进程中可以创建多个Context,但一个线程同一时刻只能使用一个Context。
-
线程中创建的多个Context,线程缺省使用最后一次创建的Context。
-
进程内创建的多个Context,可以通过acl.rt.set_context设置当前需要使用的Context。
图2 接口调用流程

默认Context和默认Stream的使用场景
-
Device上执行操作下发前,必须有Context和Stream,这个Context、Stream可以显式创建,也可以隐式创建。隐式创建的Context、Stream就是默认Context、默认Stream。
默认Stream作为接口入参时,直接传0。
-
默认Context不允许用户执行acl.rt.get_context或acl.rt.set_context操作,也不允许执行acl.rt.destroy_context操作。
-
默认Context、默认Stream一般适用于简单应用,用户仅仅需要一个Device的计算场景下。多线程应用程序建议全部使用显式创建的Context和Stream。
示例代码如下,仅供参考,不可以直接拷贝运行:
## …
ret = acl.init(config_path)
ret = acl.rt.set_device(device_id)
## 已经创建了一个default ctx,在default ctx中创建了一个default stream,并且在当前线程可用。
## …
ret = acl.op.execute_v2(op1, input_desc, inputs, output_desc, outputs, attr, 0) # 最后一个0表示在default stream上执行算子op1。
ret = acl.op.execute_v2(op2, input_desc, inputs, output_desc, outputs, attr, 0) # 最后一个0表示在default stream上执行算子op2。
ret = acl.rt.synchronize_stream(0)
## 等待计算任务全部完成(op1、op2执行结束),用户根据需要获取计算任务的输出结果。
## …
ret = acl.rt.reset_device(device_id) # 释放计算设备0,对应的default ctx及default stream生命周期也终止。
多线程、多stream的性能说明
- 线程调度依赖运行的操作系统,Stream上下发了任务后,Stream的调度由Device的调度单元调度,但如果一个进程内的多Stream上的任务在Device存在资源争抢的时候,性能可能会比单Stream低。
- 当前昇腾AI处理器有不同的执行部件,如AI Core、AI CPU、Vector Core等,对应使用不同执行部件的任务,建议多Stream的创建按照算子执行引擎划分。
- 单线程多Stream与多线程多Stream(一个进程中可以包含多个线程,每个线程中一个Stream)性能上哪个更优,具体取决于应用本身的逻辑实现,一般来说前者性能略好,原因是相对后者,应用层少了线程调度开销。
父主题: 概述
Atlas 200I DK A2开发者套件
AscendCL 应用开发指南(Python)
在线提单