跳到主要内容

学习向导

学习向导

概述

本文用于指导开发人员基于现有模型、使用AscendCL(Ascend Computing Language)提供的C语言API库开发深度神经网络应用,用于实现目标识别、图像分类等功能。

通过本文档您可以达成以下目标:

  • 了解AscendCL的功能架构、基本概念以及接口的典型调用流程。
  • 使用AscendCL接口进行应用开发的基本流程和实现方法。
  • 能够基于本文档中的样例,扩展进行其它应用的开发。

具备C/C++语言程序开发能力、对机器学习或深度学习有一定了解的开发者,可以更好地理解本文档。

文档使用建议

如果您是第一次使用本文档,或者还不清楚以下问题时,建议先了解概述,再通过开发基础推理应用图像/视频数据处理单算子调用等章节的接口调用流程+示例代码来深入学习。

  • AscendCL在CANN架构的什么位置?
  • AscendCL中的Device、Stream、Context是用来做什么的?
  • 使用AscendCL接口开发应用时,包含哪几个基本步骤?

如果您在使用本文档时,已了解使用AscendCL接口开发应用的基本步骤,想进一步学习时,可参照下图的应用开发向导。

AscendCL架构及基本概念

登录昇腾社区官网,单击AscendCL概述获取相应的在线视频课程。

AscendCL是什么?

AscendCL(Ascend Computing Language)是一套用于在昇腾平台上开发深度神经网络应用的C语言API库,提供运行资源管理、内存管理、模型加载与执行、算子加载与执行、媒体数据处理等API,能够实现利用昇腾硬件计算资源、在昇腾CANN平台上进行深度学习推理计算图形图像预处理单算子加速计算等能力。简单来说,就是统一的API框架,实现对所有资源的调用。

计算资源层是昇腾AI处理器的硬件算力基础,主要完成神经网络的矩阵相关计算、完成控制算子/标量/向量等通用计算和执行控制功能、完成图像和视频数据的预处理,为深度神经网络计算提供了执行上的保障。

图1 逻辑架构图

AscendCL的应用场****景

  • 开发应用:用户可以直接调用AscendCL提供的接口开发图片分类应用、目标识别应用等。
  • 供第三方框架调用:用户可以通过第三方框架调用AscendCL接口,以便使用昇腾AI处理器的计算能力。
  • 供第三方开发lib库:用户还可以使用AscendCL封装实现第三方lib库,以便提供昇腾AI处理器的运行管理、资源管理等能力。

AscendCL的优势

  • 高度抽象:算子编译、加载、执行的API归一,相比每个算子一个API,AscendCL大幅减少API数量,降低复杂度。
  • 向后兼容:AscendCL具备向后兼容,确保软件升级后,基于旧版本编译的程序依然可以在新版本上运行。
  • 零感知芯片:一套AscendCL接口可以实现应用代码统一,多款昇腾AI处理器无差异。

基本概念

表1 概念介绍

概念描述
同步/异步本文中提及的同步、异步是站在调用者和执行者的角度,在当前场景下,若在调用接口后不等待Device执行完成再返回,则表示调度是异步的;若在调用接口后需等待Device执行完成再返回,则表示调度是同步的。
进程/线程本文中提及的进程、线程,若无特别注明,则表示Host上的进程、线程。
HostHost指与Device相连接的X86服务器、ARM服务器,会利用Device提供的NN(Neural-Network )计算能力,完成业务。
DeviceDevice指安装了昇腾AI处理器的硬件设备,利用PCIe接口与Host侧连接,为Host提供NN计算能力。若存在多个Device,多个Device之间的内存资源不能共享。
ContextContext作为一个容器,管理了所有对象(包括Stream、Event、设备内存等)的生命周期。不同Context的Stream、不同Context的Event是完全隔离的,无法建立同步等待关系。 Context分为两种:
- 默认Context:调用aclrtSetDevice接口指定用于运算的Device时,系统会自动隐式创建一个默认Context,一个Device对应一个默认Context,默认Context不能通过aclrtDestroyContext接口来释放。
- 显式创建的Context:**推荐,**在进程或线程中调用aclrtCreateContext接口显式创建一个Context。
StreamStream用于维护一些异步操作的执行顺序,确保按照应用程序中的代码调用顺序在Device上执行。 基于Stream的kernel执行和数据传输能够实现Host运算操作、Host与Device间的数据传输、Device内的运算并行。 Stream分为两种:
- 默认Stream:调用aclrtSetDevice接口指定用于运算的Device时,系统会自动隐式创建一个默认Stream,一个Device对应一个默认Stream,默认Stream不能通过aclrtDestroyStream接口来释放。
- 显式创建的Stream:**推荐,**在进程或线程中调用aclrtCreateStream接口显式创建一个Stream。
Event支持调用AscendCL接口同步Stream之间的任务,例如同一个Device上的多个任务。 例如,若stream2的任务依赖stream1的任务,想保证stream1中的任务先完成,这时可创建一个Event,并将Event插入到stream1,在执行stream2的任务前,先同步等待Event完成。
AIPPAIPP(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或分辨率是不固定的,如检测出目标后再执行目标识别网络,由于目标个数不固定导致目标识别网络输入BatchSize不固定。
- 动态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之间的关系

图2 Device、Context、Stream之间的关系

  • Device,用于指定计算设备。
    • Device的生命周期源于首次调用aclrtSetDevice接口。
    • 每次调用aclrtSetDevice接口,系统会进行引用计数加1;调用aclrtResetdevice接口,系统会进行引用计数减1。
    • 当引用计数减为零时,在本进程中Device上的资源不可用。
  • Context,在Device下,一个Context一定属于一个唯一的Device。
    • Context分隐式创建和显式创建。
    • 隐式创建的Context(即默认Context),生命周期始于调用aclrtSetDevice接口,终结于调用aclrtResetdevice接口使引用计数为零时。隐式Context只会被创建一次,调用aclrtSetDevice接口重复指定同一个Device,只增加隐式创建的Context的引用计数。
    • 显式创建的Context,生命周期始于调用aclrtCreateContext接口,终结于调用aclrtDestroyContext接口。
    • 若在某一进程内创建多个Context(Context的数量与Stream相关,Stream数量有限制,请参见aclrtCreateStream),当前线程在同一时刻内只能使用其中一个Context,建议通过aclrtSetCurrentContext接口明确指定当前线程的Context,增加程序的可维护性**。**
    • 进程内的Context是共享的,可以通过aclrtSetCurrentContext进行切换。
  • Stream,是Device上的执行流,在同一个stream中的任务执行严格保序。
    • Stream分隐式创建和显式创建。
    • 每个Context都会包含一个默认Stream,这个属于隐式创建,隐式创建的stream生命周期同归属的Context。
    • 用户可以显式创建stream,显式创建的stream生命周期始于调用aclrtCreateStream,终结于调用aclrtDestroyStream接口。显式创建的stream归属的Context被销毁或生命周期结束后,会影响该stream的使用,虽然此时stream没有被销毁,但不可再用。
  • Task/Kernel,是Device上真正的任务执行体。

线程、Context、Stream之间的关系

  • 一个用户线程一定会绑定一个Context,所有Device的资源使用或调度,都必须基于Context。

  • 一个线程中当前会有一个唯一的Context在用,Context中已经关联了本线程要使用的Device。

  • 可以通过aclrtSetCurrentContext进行Device的快速切换。示例代码如下,仅供参考,不可以直接拷贝编译运行:

    <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><br>…<br>aclrtCreateContext(&ctx1,0);<br>aclrtCreateStream(&s1);<br>aclopExecuteV2(op1,...,s1);<br>aclrtCreateContext(&ctx2,1);<br>/*在当前线程中,创建ctx2后,当前线程对应的Context切换为ctx2,将后续的计算任务切换到Device 1上执行 */<br>aclrtCreateStream(&s2);<br>aclopExecuteV2(op2,...,s2);<br>aclrtSetCurrentContext(ctx1);<br>/*在当前线程中,通过Context切换,将后续的计算任务切换到Device 0上执行 */<br>aclopExecuteV2(op3,...,s1);<br>…<br>
  • 一个线程中可以创建多个Stream,不同的Stream上计算任务是可以并行执行;多线程场景下,推荐每个线程创建一个Stream,线程之间的Stream在Device上相互独立,每个Stream内部的任务是按照Stream下发的顺序执行。

  • 多线程的调度依赖于运行应用的操作系统调度,多Stream在Device侧的调度,由Device上调度组件进行调度。

一个进程内多个线程间的Context迁移

  • 一个进程中可以创建多个Context,但一个线程同一时刻只能使用一个Context。
  • 线程中创建的多个Context,线程缺省使用最后一次创建的Context。
  • 进程内创建的多个Context,可以通过aclrtSetCurrentContext设置当前需要使用的Context。

图3 接口调用流程

默认Context和默认Stream的使用场景

  • Device上执行操作下发前,必须有Context和Stream,这个Context、Stream可以显式创建,也可以隐式创建。隐式创建的Context、Stream就是默认Context、默认Stream。

    默认Stream作为接口入参时,直接传NULL。

  • 默认Context不允许用户执行aclrtGetCurrentContextaclrtSetCurrentContext操作,也不允许执行aclrtDestroyContext操作。

  • 默认Context、默认Stream一般适用于简单应用,用户仅仅需要一个Device的计算场景下。多线程应用程序建议全部使用显式创建的Context和Stream。

示例代码如下,仅供参考,不可以直接拷贝编译运行:

<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><br>…<br>aclInit(...);<br>aclrtSetDevice(0); <br>/*已经创建了一个default ctx,在default ctx中创建了一个default stream,并且在当前线程可用*/<br>…<br>aclopExecuteV2(op1,...,NULL); //最后一个NULL表示在default stream上执行算子op1<br>aclopExecuteV2(op2,...,NULL); //最后一个NULL表示在default stream上执行算子op2<br>aclrtSynchronizeStream(NULL); <br>/*等待计算任务全部完成(op1、op2执行结束),用户根据需要获取计算任务的输出结果*/<br>…<br>aclrtResetDevice(0); //释放计算设备0,对应的default ctx及default stream生命周期也终止。<br>

多线程、多stream的性能说明

  • 线程调度依赖运行的操作系统,Stream上下发了任务后,Stream的调度由Device的调度单元调度,但如果一个进程内的多Stream上的任务在Device存在资源争抢的时候,性能可能会比单Stream低。
  • 当前昇腾AI处理器有不同的执行部件,如AI Core、AI CPU、Vector Core等,对应使用不同执行部件的任务,建议多Stream的创建按照算子执行引擎划分。
  • 单线程多Stream与多线程多Stream(一个进程中可以包含多个线程,每个线程中一个Stream)性能上哪个更优,具体取决于应用本身的逻辑实现,一般来说前者性能略好,原因是相对后者,应用层少了线程调度开销。

父主题: AscendCL概述

AscendCL接口调用流程

接口调用流程

调用AscendCL接口,可开发包含模型推理、媒体数据处理、单算子调用等功能的应用,这些功能可以独立存在,也可以组合存在。下图给出了使用AscendCL接口开发AI应用的整体接口调用流程。

图1 接口调用流程图

上图根据应用开发中的典型功能抽象出主要的接口调用流程,例如,如果模型对输入图片的宽高要求与用户提供的源图不一致,则需要媒体数据处理,将源图裁剪成符合模型的要求;如果需要实现模型推理的功能,则需要先加载模型,模型推理结束后,则需要卸载模型;如果模型推理后,需要从推理结果中查找最大置信度的类别标识对图片分类,则需要数据后处理。

  1. AscendCL初始化。

    调用aclInit接口实现初始化AscendCL。

  2. 运行管理资源申请。

    依次申请运行管理资源:DeviceContextStream

    具体流程,请参见运行管理资源申请与释放

  3. 模型推理/单算子调用/媒体数据处理。

    • 模型推理

      1. 模型加载:模型推理前,需要先将对应的模型加载到系统中。

        接口调用流程,请参见模型加载

        但加载模型前,必须要有适配昇腾AI处理器的离线模型,需提前构建模型,请参见模型构建

      2. (可选)媒体数据处理:可实现JPEG图片解码、视频解码、抠图/图片缩放/格式转换、JPEG图片编码等功能。

        接口调用流程,请参见媒体数据处理(含图像/视频等)

      3. 模型执行:使用模型实现图片分类、目标识别等功能。

        接口调用流程,请参见模型执行

      4. (可选)数据后处理:处理模型推理的结果,此处根据用户的实际需求来处理推理结果,例如用户可以将获取到的推理结果写入文件、从推理结果中找到每张图片最大置信度的类别标识等。

      5. 模型卸载:调用aclmdlUnload接口卸载模型。

    • 单算子调用

      如果AI应用中不仅仅包括模型推理,还有数学运算(例如BLAS基础线性代数运算)、数据类型转换等功能,也想使用昇腾的算力,直接通过AscendCL接口加载并执行单个算子,省去模型构建、训练的过程,相对轻量级,又可以使用昇腾的算力。另外,自定义的算子,也可以通过单算子调用的方式来验证算子的功能。

      算子调用的接口调用流程,请参见单算子调用流程

  4. 运行管理资源释放。

    所有数据处理都结束后,需要依次释放运行管理资源:StreamContextDevice

    接口调用流程,请参见运行管理资源申请与释放

  5. AscendCL去初始化。

    调用aclFinalize接口实现AscendCL去初始化。

:::note 说明 在应用开发过程中,各环节都涉及内存的申请与释放、数据传输(通过内存复制实现)、数据类型的创建与销毁,因此未在图中一一标识,关于内存申请与释放、内存复制的接口请参见内存管理,数据类型的创建与销毁的接口请参见数据类型及其操作接口。 :::

调用接口依赖的头文件和库文件说明

您需要根据实际使用的接口来include依赖的文件,AscendCL中各头文件的用途如下表所示。

AscendCL头文件在“CANN软件安装后文件存储路径/include/”目录下,AscendCL库文件在“CANN软件安装后文件存储路径/lib64/”目录下。

备注

须知

编译基于AscendCL接口的代码逻辑时,请按照include的头文件依赖对应的库文件,如果引用多余的so文件(例如libascendcl.a),可能导致版本功能异常或后续版本升级时存在兼容性问题。

表1 头文件列表

定义接口的头文件用途对应的库文件
acl/acl_base.h用于定义基本的数据类型(例如aclDataBuffer、aclTensorDesc等)及其操作接口、枚举值(例如aclFormat)、日志管理接口等。libascendcl.so
acl/acl.h该头文件中已包含acl/acl_mdl.h、acl/acl_rt.h、acl/acl_op.h。包含acl.h文件后,可以引用初始化/去初始化、Device管理、算力Group查询与设置、Context管理、Stream管理、同步等待、内存管理、模型加载与执行、算子编译(不包括aclopCompile接口)、算子加载与执行(不包括aclopCompileAndExecute接口)等接口。libascendcl.so
acl/acl_prof.h用于定义Profiling配置的接口。libmsprofiler.so (说明:为了兼容旧版本,旧版本中支持使用libascendcl.so,但后续版本这种方式会废弃,建议使用libmsprofiler.so,防止后续版本出现兼容性问题。)
acl/ops/acl_cblas.h用于定义CBLAS接口。libacl_cblas.so
acl/ops/acl_dvpp.h用于定义媒体数据处理V1版本的接口。libacl_dvpp.so
acl/ops/acl_fv.h用于定义特征向量检索的接口。 Atlas 200/500 A2推理产品,不支持引用该头文件中的接口。libacl_retr.so
acl/acl_op_compiler.h用于定义aclopCompile、aclopCompileAndExecute、aclSetCompileopt等算子在线编译相关的接口、数据类型、枚举值等。libacl_op_compiler.so
acl/acl_tdt.h用于定义Tensor数据传输接口。 Atlas 200/500 A2推理产品,不支持引用该头文件中的接口。libacl_tdt_channel.so
acl/acl_tdt_queue.h用于定义共享队列管理、共享Buffer管理接口。libacl_tdt_queue.so
acl/dvpp/hi_dvpp.h用于定义媒体数据处理V2版本的接口。libacl_dvpp_mpi.so
acl/media目录下: hi_mpi_vi.h hi_common_vi.h hi_common_dis.h hi_common_gdc.h hi_media_common.h hi_media_type.h hi_mpi_sys.h用于定义VI(Vedio Input)视频数据获取功能的接口。libacl_vi_mpi.so libacl_dvpp_mpi.so
acl/media目录下: hi_mpi_isp.h hi_common_isp.h hi_common_3a.h hi_mpi_ae.h hi_common_ae.h hi_mpi_awb.h hi_common_awb.h hi_common_sns.h hi_media_common.h hi_media_type.h hi_mpi_sys.h用于定义ISP(Image Signal Processing)系统控制功能的接口。libacl_isp_ae_mpi.so libacl_isp_awb_mpi.so libacl_isp_mpi.so libacl_dvpp_mpi.so
acl/media目录下: hi_mpi_vpss.h hi_media_common.h hi_media_type.h hi_mpi_sys.h用于定义VPSS(Video Process Sub-System)图像处理功能的接口。libacl_vpss_mpi.so libacl_dvpp_mpi.so
acl/media/hi_mipi_rx.h用于定义MIPI Rx ioctl命令字。-
acl/media目录下: hi_mpi_audio.h hi_common_aio.h用于定义频输入、音频输出功能的接口。libacl_audio_mpi.so
acl/media/hi_acodec.h用于定义音量调整的命令字。-
acl/media目录下: hi_common_vo.h hi_mpi_vo.h用于定义视频输出接口。libacl_vo_mpi.so
acl/media/hi_mpi_hdmi.h用于定义对接外设的HDMI接口。libacl_hdmi_mpi.so
acl/media/hi_mpi_tde.h用于定义TDE图形绘制接口。libacl_tde_mpi.so
acl/media/hifb.h用于定义叠加图形层管理接口。-
aclnn/acl_meta.h用于定义依赖的AscendCL meta接口,通过这些元接口可构建不同数据结构,如aclTensor、aclScalar、aclIntArray等。 Atlas 200/500 A2推理产品,不支持引用该头文件中的接口。libnnopbase.so
aclnn/aclnn_base.h用于定义NN类算子公共的base接口,即aclnnInit和aclnnFinalize。 Atlas 200/500 A2推理产品,不支持引用该头文件中的接口。libnnopbase.so
aclnnop/aclnn_*.h (*表示具体的算子名称) (说明:为兼容旧版本,旧版本NN类算子头文件路径是aclnnop/level2/aclnn_*.h,但后续版本该路径会废弃,建议使用新路径aclnnop/aclnn_*.h,防止后续版本出现兼容性问题。)用于定义NN类算子的功能接口。 Atlas 200/500 A2推理产品,不支持引用该头文件中的接口。libopapi.so
acldvppop/acldvpp_base.h acldvppop/acldvpp_op_api.h用于定义DVPP媒体数据处理类算子的功能接口。 Atlas 200/500 A2推理产品,不支持引用该头文件中的接口。libacl_dvpp_op.so

父主题: AscendCL概述

推理应用开发视频课程

通过在线视频课程学习该功能,请参见AscendCL模型推理基础功能

父主题: 基础推理应用

推理应用开发流程

图1 开发流程

  1. 准备环境

  2. 创建代码目录

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

    如下仅是示例,供参考:

    ├App名称
    ├── model // 该目录下存放模型文件
    │ ├── xxxxxx

    ├── data
    │ ├── xxx.jpg // 测试数据

    ├── inc // 该目录下存放声明函数的头文件
    │ ├── xxx.h

    ├── out // 该目录下存放输出结果

    ├── src
    │ ├── xxx.json // 系统初始化的配置文件
    │ ├── CMakeLists.txt // 编译脚本
    │ ├── xxx.cpp // 实现文件
  3. 构建模型

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

  4. 开发应****用

    1. AscendCL初始化,请参见AscendCL初始化与去初始化

      使用AscendCL接口开发应用时,必须先调用aclInit接口进行AscendCL初始化,否则可能会导致后续系统内部资源初始化出错,进而导致其它业务异常。

    2. 运行管理资源申请,请参见运行管理资源申请与释放

    3. 数据传输,请参见数据传输

    4. 执行模型推理。请参见模型推理

      若需要处理模型推理的结果,还需要进行数据后处理,例如对于图片分类应用,通过数据后处理从推理结果中查找最大置信度的类别标识。

      模型推理结束后,需及时释放推理相关资源。

    5. 所有数据处理结束后,需及时释放运行管理资源,请参见运行管理资源申请与释放

    6. 执行AscendCL去初始化,请参见AscendCL初始化与去初始化

  5. 编译运行应用,包括编译代码、运行应用,请参见应用调试

父主题: 基础推理应用

Atlas 200I DK A2开发者套件

AscendCL 应用开发指南(C&C++)

在线提单