CANN Runtime 内置任务(Built-in Task)下发与执行实战:Reduce 归约与随机数生成
【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime
导读
本文围绕 CANN Runtime 的"内置任务(built-in task)"能力展开,该能力让开发者无需自研算子、不加载 kernel 二进制即可在 Stream 上异步下发两类高频基础计算任务:Reduce 归约运算(aclrtReduceAsync)与随机数生成(aclrtRandomNumAsync,支持均匀/正态/截断正态分布与 Dropout Bitmask)。文中以 example/2_advanced_features/built_in_task 目录下的两个可编译运行示例为骨架,结合 include/external/acl/acl_rt.h 中的真实 API 声明与数据结构,完整讲解任务下发 API 的参数语义、任务信息结构体字段布局、内存要求、产品支持范围与构建运行步骤。读完本文,你可以直接在 CANN 环境中把这两个示例跑起来,并掌握如何在自己的业务代码中组织aclrtRandomNumTaskInfo、如何为随机数任务准备 16 字节 counter 内存,以及 Reduce 任务"就地前缀和"的输出规律。
一、什么是 Runtime 内置任务
CANN Runtime 的典型执行模型是"编译 kernel → 加载二进制 → 在 Stream 上下发执行"。但对于求和、极值比较、随机数生成这类基础性、通用性的计算,逐次走完整的 kernel 加载链路既不必要也不高效。Runtime 因此提供了内置任务(built-in task)机制:运行时内部预置对应实现,应用只需通过一个异步下发 API,把数据地址、参数与目标 Stream 一并交给 Runtime,任务即以异步、有序的方式进入 Stream 的任务队列执行。
从 example/2_advanced_features/built_in_task/README_en.md 的目录组织可以确认,当前仓库围绕该能力提供两个官方示例:
| 示例目录 | 核心 API | 能力说明 |
|---|---|---|
| 0_reduce_task | aclrtReduceAsync | Reduce 任务下发与结果读取 |
| 1_random_num_task | aclrtRandomNumAsync | 随机数任务下发,支持多种分布与数据类型 |
两者的共同特点是:不需要任何自定义算子源码,只依赖acl_rt一个动态库即可链接运行(见两个示例的 CMakeLists.txt 中target_link_libraries(main PRIVATE acl_rt)),非常适合作为理解"Stream 异步执行模型 + Runtime 内建计算"的入门与实战素材。
二、示例构建与运行(通用步骤)
两个示例的构建方式完全一致,均由 run.sh 一键完成 cmake 配置、编译并直接执行。文档给出的运行步骤为:
# 将 ${install_root} 替换为 CANN 安装根目录,默认安装在 /usr/local/Ascend source ${install_root}/cann/set_env.sh export ASCEND_INSTALL_PATH=${install_root}/cann # 构建并运行 bash run.sh结合仓库中的 run.sh 源码,其内部执行链路为:
source $_ASCEND_INSTALL_PATH/bin/setenv.bash:加载 CANN 环境变量;- 在示例目录下创建
build/目录并进入; - 执行
cmake .. -DASCEND_CANN_PACKAGE_PATH=${_ASCEND_INSTALL_PATH}:将 CANN 安装路径传给 CMake; make -j$(nproc)编译生成build/main;- 直接运行
./build/main。
构建细节(来自 0_reduce_task/CMakeLists.txt)还包括:
- 通过
include_directories(${ASCEND_CANN_PACKAGE_PATH}/include)引入 CANN 头文件,代码中使用#include <acl/acl.h>即可获得全部接口; - 通过
link_directories(${ASCEND_CANN_PACKAGE_PATH}/lib64)引入运行库目录,链接acl_rt; - 编译选项为
-O2 -std=c++17 -D_GLIBCXX_USE_CXX11_ABI=0 -Wall -Werror,即要求 C++17 并启用_GLIBCXX_USE_CXX11_ABI=0(与 CANN 预编译库的 ABI 保持一致,这是示例在各类发行版上可正常链接的关键之一)。
产品支持范围(以当前仓库文档为准):
| 功能 | Ascend 950PR/Ascend 950DT | Atlas A3 训练/推理系列 | Atlas A2 训练/推理系列 |
|---|---|---|---|
Reduce 任务(aclrtReduceAsync) | 支持 | 支持 | 支持 |
随机数任务(aclrtRandomNumAsync) | 不支持 | 支持 | 支持 |
注意:随机数内置任务在 Ascend 950 系列上不受支持,部署前请务必核对目标产品型号。
三、示例 1:Reduce 归约任务
3.1 任务语义:就地前缀和(Automatic Sum)
Reduce(归约)是并行计算中的基础操作,用于对数组元素执行求和、求最大值等运算。本示例使用的是ACL_RT_MEMCPY_SDMA_AUTOMATIC_SUM(自动求和,即前缀和),调用一次aclrtReduceAsync即可让 Device 侧在 Stream 上异步完成计算。
示例的完整逻辑位于 0_reduce_task/main.cpp:输入{1.0, 2.0, 3.0, 4.0},输出为:
Reduce SUM result[0] = 2.000000 Reduce SUM result[1] = 4.000000 Reduce SUM result[2] = 6.000000 Reduce SUM result[3] = 8.000000 [INFO] Sample run successfully.可以清楚看到,每个输出元素是"输入元素与其前一个输出元素之和"(result[0] = 1+1,result[1] = 2+2,result[2] = 3+3,result[3] = 4+4),即典型的前缀和语义。因此使用该接口时,必须保证输出缓冲区与输入缓冲区均有效——示例中特意把输入数据先拷贝到devInput和devOutput两份内存(aclrtMemcpy两次,见 main.cpp),src 与 dst 是两个不同地址。
3.2 核心 API:aclrtReduceAsync
头文件 include/external/acl/acl_rt.h 中的真实声明为:
ACL_FUNC_VISIBILITY aclError aclrtReduceAsync( void* dst, const void* src, uint64_t count, aclrtReduceKind kind, aclDataType type, aclrtStream stream, void* reserve);参数语义如下:
| 参数 | 说明 |
|---|---|
dst | 输出地址(Device 内存) |
src | 输入地址(Device 内存) |
count | 数据大小(字节) |
kind | 归约类型,见aclrtReduceKind |
type | 数据类型(如ACL_FLOAT) |
stream | 任务下发的目标 Stream |
reserve | 保留参数,必须为NULL |
归约类型枚举完整定义位于 acl_rt.h:
typedef enum { ACL_RT_MEMCPY_SDMA_AUTOMATIC_SUM = 10, // 自动求和(前缀和) ACL_RT_MEMCPY_SDMA_AUTOMATIC_MAX = 11, // 自动求最大值 ACL_RT_MEMCPY_SDMA_AUTOMATIC_MIN = 12, // 自动求最小值 ACL_RT_MEMCPY_SDMA_AUTOMATIC_EQUAL = 13, // 自动比较相等 } aclrtReduceKind;示例中以ACL_FLOAT(32 位浮点)为数据类型调用:
ret = aclrtReduceAsync( devOutput, devInput, size, // 输出、输入、字节数 ACL_RT_MEMCPY_SDMA_AUTOMATIC_SUM, // 归约类型:自动求和 ACL_FLOAT, // 数据类型 stream, NULL); CHECK_ERROR(ret);3.3 完整执行流程
示例的程序流程就是一套标准的 AscendCL 生命周期,可作为所有内置任务示例的通用模板:
- 初始化:
aclInit(NULL)初始化 AscendCL 配置; - 设备管理:
aclrtSetDevice(0)指定计算 Device; - 创建 Stream:
aclrtCreateStream(&stream); - 准备数据:Host 侧构造
float hostInput[4],用aclrtMalloc(..., ACL_MEM_MALLOC_HUGE_FIRST)在 Device 申请输入/输出内存,再用aclrtMemcpy(..., ACL_MEMCPY_HOST_TO_DEVICE)上板; - 下发任务:调用
aclrtReduceAsync(异步,立即返回); - 同步等待:
aclrtSynchronizeStream(stream)阻塞等待 Stream 上任务执行完毕; - 回拷结果:
aclrtMemcpy(..., ACL_MEMCPY_DEVICE_TO_HOST)将结果拷回 Host 并打印; - 释放资源:
aclrtFree释放 Device 内存、aclrtDestroyStream销毁 Stream、aclrtResetDeviceForce(0)强制复位计算 Device 并回收资源、aclFinalize()反初始化。
值得注意的是示例使用aclrtResetDeviceForce(强制复位)而非普通的aclrtResetDevice,用于确保 Device 资源被彻底回收,便于示例反复运行。
四、示例 2:随机数任务
4.1 核心 API:aclrtRandomNumAsync
头文件 include/external/acl/acl_rt.h 中的真实声明为:
ACL_FUNC_VISIBILITY aclError aclrtRandomNumAsync( const aclrtRandomNumTaskInfo* taskInfo, const aclrtStream stream, void* reserve);其参数只有三个:任务信息结构体指针、目标 Stream、保留字段(须为NULL)。所有"生成什么分布、什么数据类型、范围/均值/方差、种子、数量、结果写到哪"的信息,全部封装在aclrtRandomNumTaskInfo中。
4.2 任务信息结构体:aclrtRandomNumTaskInfo
头文件 include/external/acl/acl_rt.h 中的真实定义(注意rsv为 8 字节,与示例文档中打印的结构略有差异,以头文件为准):
typedef struct { aclDataType dataType; aclrtRandomNumFuncParaInfo randomNumFuncParaInfo; void* randomParaAddr; void* randomResultAddr; void* randomCounterAddr; aclrtRandomParaInfo randomSeed; aclrtRandomParaInfo randomNum; uint8_t rsv[8]; } aclrtRandomNumTaskInfo;各字段说明:
| 字段 | 说明 |
|---|---|
dataType | 随机数输出数据类型,如ACL_FLOAT、ACL_FLOAT16、ACL_BF16、ACL_INT32等 |
randomNumFuncParaInfo | 函数类型与分布参数(详见下节) |
randomParaAddr | 参数地址,示例中固定为NULL(参数直接内嵌在结构体中) |
randomResultAddr | 随机数结果输出地址(Device 内存) |
randomCounterAddr | 随机数状态 counter 地址(Device 内存,固定 16 字节) |
randomSeed | 随机种子,aclrtRandomParaInfo类型 |
randomNum | 生成的随机数个数,aclrtRandomParaInfo类型 |
rsv[8] | 保留字段 |
其中的通用参数类型aclrtRandomParaInfo(acl_rt.h)定义了"立即数 or 设备内存地址"的双通道传参方式:
typedef struct { uint8_t isAddr; // 是否为地址:0 表示 valueOrAddr 直接存立即数值,1 表示存设备内存地址 uint8_t valueOrAddr[8]; // 8 字节,保存立即数值或设备内存地址 uint8_t size; // 参数大小(字节) uint8_t rsv[6]; } aclrtRandomParaInfo;示例中所有参数均采用立即数方式(isAddr = 0),例如随机种子与数量的填充:
*((uint64_t*)taskInfo.randomSeed.valueOrAddr) = seed; // 种子写进 valueOrAddr taskInfo.randomSeed.size = sizeof(uint64_t); taskInfo.randomSeed.isAddr = 0; *((uint64_t*)taskInfo.randomNum.valueOrAddr) = num; // 数量写进 valueOrAddr taskInfo.randomNum.size = sizeof(uint64_t); taskInfo.randomNum.isAddr = 0;如需把参数放入 Device 内存,则把isAddr置 1、valueOrAddr存地址、size填参数字节数即可,文档中"参数可使用立即值或设备内存"的说法与此结构完全对应。
4.3 函数类型与分布参数
函数类型枚举aclrtRandomNumFuncType与参数联合体定义于 acl_rt.h:
typedef enum { ACL_RT_RANDOM_NUM_FUNC_TYPE_DROPOUT_BITMASK = 0, // dropout bitmask ACL_RT_RANDOM_NUM_FUNC_TYPE_UNIFORM_DIS, // uniform distribution ACL_RT_RANDOM_NUM_FUNC_TYPE_NORMAL_DIS, // normal distribution ACL_RT_RANDOM_NUM_FUNC_TYPE_TRUNCATED_NORMAL_DIS, // truncated normal distribution } aclrtRandomNumFuncType; typedef struct { aclrtRandomNumFuncType funcType; union { aclrtDropoutBitmaskInfo dropoutBitmaskInfo; aclrtUniformDisInfo uniformDisInfo; aclrtNormalDisInfo normalDisInfo; } paramInfo; } aclrtRandomNumFuncParaInfo;对应的分布参数子结构(acl_rt.h):
// dropout bitmask typedef struct { aclrtRandomParaInfo dropoutRation; // Dropout 比例 } aclrtDropoutBitmaskInfo; // uniform distribution typedef struct { aclrtRandomParaInfo min; // 最小值 aclrtRandomParaInfo max; // 最大值 } aclrtUniformDisInfo; // normal distribution typedef struct { aclrtRandomParaInfo mean; // 均值 aclrtRandomParaInfo stddev; // 标准差 } aclrtNormalDisInfo;四种生成类型及支持的数据类型汇总如下:
| 生成类型 | 函数类型标识 | 参数 | 支持的数据类型 |
|---|---|---|---|
| 均匀分布(Uniform) | ACL_RT_RANDOM_NUM_FUNC_TYPE_UNIFORM_DIS | min/max | 浮点:ACL_FLOAT、ACL_FLOAT16、ACL_BF16;整型:ACL_INT32、ACL_INT64、ACL_UINT32、ACL_UINT64 |
| 正态分布(Normal) | ACL_RT_RANDOM_NUM_FUNC_TYPE_NORMAL_DIS | mean/stddev | ACL_FLOAT、ACL_FLOAT16、ACL_BF16 |
| 截断正态分布(Truncated Normal) | ACL_RT_RANDOM_NUM_FUNC_TYPE_TRUNCATED_NORMAL_DIS | mean/stddev | ACL_FLOAT、ACL_FLOAT16、ACL_BF16 |
| Dropout Bitmask 生成 | ACL_RT_RANDOM_NUM_FUNC_TYPE_DROPOUT_BITMASK | ratio(比例) | 输出UINT8,ratio 支持ACL_FLOAT、ACL_FLOAT16、ACL_BF16 |
示例 1_random_num_task/main.cpp 以分组封装函数的形式演示了这些类型:UniformFloatAsync、UniformFloat16Async、UniformBF16Async、UniformInt32/Int64/Uint32/Uint64Async覆盖均匀分布的全部数据形态;NormalFloatAsync/NormalFloat16Async/NormalBF16Async通过isTruncated布尔参数在正态与截断正态之间切换(见 main.cpp 中isTruncated ? ACL_RT_RANDOM_NUM_FUNC_TYPE_TRUNCATED_NORMAL_DIS : ACL_RT_RANDOM_NUM_FUNC_TYPE_NORMAL_DIS);DropoutBitmask用于生成 Dropout 掩码。
4.4 随机数算法与内存要求
算法:当前仓库文档明确记录的算法为Philox4_32_10,其特点为支持所有分布类型,counter 为 128 bit,需 16 字节存储。
内存要求(来自 1_random_num_task/README_en.md):
- Counter 内存:固定 16 字节,任意字节对齐地址均可。示例中用
constexpr size_t kCounterSize = 16U;申请并用aclrtMemset清零(main.cpp); - 输出内存:根据数据类型与随机数个数动态计算大小,示例统一按
num * sizeof(uint64_t)申请"足够大"的缓冲区以容纳 128 个各类型结果(main.cpp); - 参数内存:均值、标准差、取值范围等参数可内嵌立即数(示例做法),也可使用设备内存地址。
关于 counter 需要注意:Philox 算法依赖 counter 状态推进来生成随机数,因此同一个 counter 内存在连续多次任务间会被改写,示例在 5 组任务中反复复用同一个counterAddr而未重置,恰好演示了"复用 counter 得到连续随机序列"的用法;若希望每次生成相同序列,则需要重置 counter 或换用不同种子。
4.5 运行结果示例
[INFO] Generate normal distribution random numbers, data type: float Random result[0] = ... ... [INFO] Generate truncated normal distribution random numbers, data type: BF16 Random result[0] = ... ... [INFO] Generate uniform distribution random numbers, data type: FP16 Random result[0] = ... ... [INFO] Generate uniform distribution random numbers, data type: INT32 Random result[0] = ... ... [INFO] Generate dropout bitmask, output data type: UINT8 Random result[0] = ... [INFO] Sample run successfully.打印方式与数据类型对应:float 用%f,BF16/FP16 用%#x输出 16 位十六进制位模式(BF16(1.0) =0x3F80、FP16(1.0) =0x3C00、BF16(2.0) =0x4000,见 main.cpp),INT32 用%d,UINT8 用%u。
五、内置任务在 Runtime 中的定位
从接口定义可见,内置任务与普通 kernel 下发(如aclrtLaunchKernel)位于同一层级的 acl_rt.h 头文件中,二者共享相同的Stream 异步执行模型:API 调用后立即返回,任务按序进入 Stream 队列,通过aclrtSynchronizeStream或事件机制同步。这一模型带来的工程收益包括:
- 零自定义算子成本:Reduce、随机数这类基础运算不再需要编写和编译 kernel;
- 异步流水线化:与 Stream 上的其他任务(拷贝、kernel、事件)天然有序衔接,可组合出完整的计算流水;
- 可预测的内存模型:counter 固定 16 字节、参数支持立即数/地址两种传法,内存管理可控。
需要特别强调的是产品差异:aclrtReduceAsync在 Ascend 950 系列与 Atlas A2/A3 系列均受支持,而aclrtRandomNumAsync在 Ascend 950 系列上不受支持(参见 1_random_num_task/README_en.md 的产品支持表)。在 Atlas A2/A3 上使用随机数任务时,建议像示例一样:先aclInit/aclrtSetDevice初始化环境,再创建 Stream、申请 counter 与输出内存,最后下发任务并通过aclrtSynchronizeStream收尾,整体流程可直接复用到生产代码中。
六、参考与延伸阅读
- 目录总览:example/2_advanced_features/built_in_task/README_en.md
- Reduce 示例文档:0_reduce_task/README_en.md,源码:0_reduce_task/main.cpp,构建脚本:0_reduce_task/run.sh
- 随机数示例文档:1_random_num_task/README_en.md,源码:1_random_num_task/main.cpp,构建脚本:1_random_num_task/run.sh
- API 与数据结构定义:include/external/acl/acl_rt.h(
aclrtReduceAsync见 L4164-L4166,aclrtRandomNumAsync见 L5411-L5412,相关枚举与结构体见 L715-L821) - 示例运行总说明:example/README_en.md
【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考