CANN ops-nn 算子详解:aclnnForeachAddScalarList 张量列表逐元素标量加法接口
【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn
导读:aclnnForeachAddScalarList 是 CANN ops-nn 神经网络算子库中 foreach 系列的高阶组合算子,它将"张量列表 + 标量列表"整体绑定为一次算子调用,对列表中的每个张量逐元素加上对应的标量。本文基于 关联文档 与仓库源码,系统讲解其功能语义、产品支持矩阵、两段式 API 原型、参数与错误码约束,并给出可直接编译运行的完整调用示例,同时深入到 算子定义、内核实现 与底层通用模板 foreach_one_scalar_list_binary.h,帮助开发者理解该算子在 NPU 上的执行原理并快速完成二次开发。
功能说明与计算公式
该接口的功能是:输入张量列表和输入标量列表,执行逐元素相加运算。也就是说,它并不是对单个张量加单个标量,而是同时处理一组(列表)张量和一组(列表)标量,二者按下标一一配对。
设输入张量列表为x,标量列表为scalars,输出张量列表为y,三者元素个数均为n:
$$ x = [{x_0}, {x_1}, ... {x_{n-1}}]\ scalars = [{scalars_0}, {scalars_1}, ... {scalars_{n-1}}]\ y = [{y_0}, {y_1}, ... {y_{n-1}}] $$
输出列表中的第i个张量等于输入列表第i个张量与第i个标量逐元素相加的结果:
$$ y_i=x_i+scalars_i (i=0,1,...n-1) $$
从语义上讲,该算子等价于 PyTorch 中的torch._foreach_add(tensors 与 scalars 均为列表的形态),其价值在于把循环调用多个单张量 add 算子合并为一次算子下发,减少多次 kernel 启动带来的调度开销,尤其适合优化器更新、逐参数加偏置等"多个同形状张量执行同一运算"的批量场景。仓库中 README 对其定位的描述与之完全一致。
产品支持情况
该算子在以下产品上受支持(与 README 中的支持矩阵一致):
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR / Ascend 950DT | 支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 不支持 |
| Atlas 训练系列产品 | 不支持 |
| Kirin X90 处理器系列产品 | 支持 |
| Kirin 9030 处理器系列产品 | 支持 |
说明:文档
aclnnForeachAddScalarList.md的"产品支持情况"仅列出 6 类产品,而仓库 README 额外补充了 Kirin X90 / Kirin 9030 处理器系列,两者可相互印证。从 算子定义源码 看,该算子通过AICore().AddConfig("ascend950")、AddConfig("ascend910_93")、AddConfig("ascend910b")、AddConfig("kirinx90")、AddConfig("kirin9030")注册了多种核架构配置,与上述支持矩阵一一对应。其中 Kirin 系列(kirinx90/kirin9030)通过GetKirinCoreConfig()单独配置,其支持的数据类型收窄为 FLOAT16、FLOAT32、INT32(不含 BFLOAT16),这一点与 README 中"Kirin X90/Kirin 9030 处理器系列产品:不支持 BFLOAT16"的说明吻合。
函数原型:两段式接口
该算子遵循 CANN 的两段式接口设计:必须先调用aclnnForeachAddScalarListGetWorkspaceSize接口,获取计算所需 workspace 大小以及包含了算子计算流程的执行器(executor),再调用aclnnForeachAddScalarList接口执行计算。
第一段接口(获取 workspace 大小与执行器):
aclnnStatus aclnnForeachAddScalarListGetWorkspaceSize( const aclTensorList *x, const aclScalarList *scalars, const aclTensorList *out, uint64_t *workspaceSize, aclOpExecutor **executor)第二段接口(执行计算):
aclnnStatus aclnnForeachAddScalarList( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)两段式接口的意义在于:第一段在 Host 侧完成入参校验、shape 推导与 workspace 大小计算,属于轻量同步调用;第二段才真正将算子任务下发到 Stream 上异步执行。开发者需要根据第一段返回的workspaceSize在 Device 侧申请内存,并在计算结束后释放。
aclnnForeachAddScalarListGetWorkspaceSize 参数说明
第一段接口的完整参数如下:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| x(aclTensorList*) | 输入 | 表示进行加法运算的输入张量列表,对应公式中的x | 支持空 Tensor;该参数中所有 Tensor 的数据类型保持一致 | FLOAT32、FLOAT16、BFLOAT16、INT32 | ND | 0-8 | √ |
| scalars(aclScalarList*) | 输入 | 表示加法运算的输入标量列表,对应公式中的scalars | 元素个数与x中 Tensor 的个数相等;数据类型与入参x存在对应关系(见下方说明) | FLOAT32、INT64 | - | - | - |
| out(aclTensorList*) | 输出 | 表示进行加法运算的输出张量列表,对应公式中的y | 支持空 Tensor;所有 Tensor 数据类型保持一致;数据类型和数据格式与入参x一致,shape size 大于等于入参x的 shape size | FLOAT32、FLOAT16、BFLOAT16、INT32 | ND | 0-8 | × |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
其中scalars与x的数据类型对应关系非常关键,直接决定输入组合是否合法:
- 当入参
x的数据类型为FLOAT32、FLOAT16、BFLOAT16时,scalars的数据类型仅支持 FLOAT32; - 当入参
x的数据类型为INT32时,scalars的数据类型仅支持 INT64。
这一规则在源码中有直接依据:在 算子定义 中,张量侧支持ge::DT_FLOAT16 / ge::DT_FLOAT / ge::DT_INT32 / ge::DT_BF16,标量侧则通过DtypeTensor2Scalar工具函数从张量类型映射而来;而图模式 IR 定义中scalars的TensorType明确为{DT_FLOAT, DT_INT64},与文档约束完全一致。
此外,x支持非连续 Tensor(表格中 √),而out不支持非连续 Tensor(×),输出张量必须按连续内存组织;shape 维度支持 0-8 维(0 维即标量张量)。从 内核入口 的GET_TILING_DATA与注释"foreach(vector) not need workspace"可以推断,该算子执行时不需要额外的 workspace 计算缓冲,第一段接口返回的workspaceSize在多数场景下为 0。
返回值与错误码
两个接口均返回aclnnStatus状态码,具体错误码含义可参考 aclnn返回码。
第一段接口完成入参校验,在以下场景会报错:
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 x、scalars、out 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | x、scalars、out 的数据类型不在支持的范围之内 |
| ACLNN_ERR_PARAM_INVALID | 161002 | x 和 out 的数据类型不一致 |
| ACLNN_ERR_INNER_TILING_ERROR | 561002 | x 与 out 的 shape 不满足约束 |
| ACLNN_ERR_INNER_TILING_ERROR | 561002 | x 或 out 中的 Tensor 数据类型不一致 |
| ACLNN_ERR_INNER_TILING_ERROR | 561002 | x 或 out 中的 Tensor 维度超过 8 维 |
开发建议:调用第一段接口后应优先检查返回码是否为ACL_SUCCESS,再根据错误码定位是"参数为空"(161001)、"类型/形状非法"(161002)还是"tiling 计算内部错误"(561002),避免直接带着非法参数进入第二段接口。
aclnnForeachAddScalarList 执行接口参数说明
第二段接口为纯执行接口,参数全部为输入:
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnForeachAddScalarListGetWorkspaceSize 获取 |
| executor | 输入 | op 执行器,包含了算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
调用完成后需要通过aclrtSynchronizeStream(stream)同步等待任务执行结束,再将结果从 Device 侧拷贝回 Host 侧(具体方法见下文调用示例)。
约束说明
- 确定性计算:
aclnnForeachAddScalarList为默认确定性实现,即在相同输入与运行环境下,多次执行结果一致,不会引入不确定的并行归约顺序。这一点对依赖可复现结果的训练/调试场景很重要。 - 输出张量不支持非连续 Tensor(见上文参数表)。
- Kirin 系列产品不支持 BFLOAT16输入(README 与源码双重印证)。
- 标量列表元素个数必须与张量列表元素个数相等,且遵循上文的数据类型对应关系。
调用示例:完整可运行的 C++ 样例
文档提供了完整的示例代码(仓库中 examples/test_aclnn_foreach_add_scalar_list.cpp 为同源可编译版本),整体编译和执行流程可参考 编译与运行样例。下面按文档原样保留完整代码,并结合仓库实现做逐步解读。
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_foreach_add_scalar_list.h" #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vector<int64_t>& shape) { int64_t shapeSize = 1; for (auto i : shape) { shapeSize *= i; } return shapeSize; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法,资源初始化 auto ret = aclInit(nullptr); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret); ret = aclrtSetDevice(deviceId); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret); ret = aclrtCreateStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret); return 0; } template <typename T> int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size = GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); // 调用aclrtMemcpy将host侧数据复制到device侧内存上 ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret); // 计算连续tensor的strides std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = shape.size() - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1. (固定写法)device/stream初始化,参考acl API手册 // 根据自己的实际device填写deviceId int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> selfShape1 = {2, 3}; std::vector<int64_t> selfShape2 = {1, 3}; std::vector<int64_t> outShape1 = {2, 3}; std::vector<int64_t> outShape2 = {1, 3}; void* input1DeviceAddr = nullptr; void* input2DeviceAddr = nullptr; void* out1DeviceAddr = nullptr; void* out2DeviceAddr = nullptr; aclTensor* input1 = nullptr; aclTensor* input2 = nullptr; aclScalar* alpha1 = nullptr; aclScalar* alpha2 = nullptr; aclTensor* out1 = nullptr; aclTensor* out2 = nullptr; std::vector<float> input1HostData = {1, 2, 3, 4, 5, 6}; std::vector<float> input2HostData = {7, 8, 9}; std::vector<float> out1HostData(6, 0); std::vector<float> out2HostData(3, 0); float alpha1Value = 1.2f; float alpha2Value = 2.2f; // 创建input1 aclTensor ret = CreateAclTensor(input1HostData, selfShape1, &input1DeviceAddr, aclDataType::ACL_FLOAT, &input1); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建input2 aclTensor ret = CreateAclTensor(input2HostData, selfShape2, &input2DeviceAddr, aclDataType::ACL_FLOAT, &input2); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建alpha1 aclScalar alpha1 = aclCreateScalar(&alpha1Value, aclDataType::ACL_FLOAT); CHECK_RET(alpha1 != nullptr, return ret); // 创建alpha2 aclScalar alpha2 = aclCreateScalar(&alpha2Value, aclDataType::ACL_FLOAT); CHECK_RET(alpha2 != nullptr, return ret); // 创建out1 aclTensor ret = CreateAclTensor(out1HostData, outShape1, &out1DeviceAddr, aclDataType::ACL_FLOAT, &out1); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建out2 aclTensor ret = CreateAclTensor(out2HostData, outShape2, &out2DeviceAddr, aclDataType::ACL_FLOAT, &out2); CHECK_RET(ret == ACL_SUCCESS, return ret); std::vector<aclTensor*> tempInput{input1, input2}; aclTensorList* tensorListInput = aclCreateTensorList(tempInput.data(), tempInput.size()); std::vector<aclTensor*> tempOutput{out1, out2}; aclTensorList* tensorListOutput = aclCreateTensorList(tempOutput.data(), tempOutput.size()); std::vector<aclScalar*> tempscalar{alpha1, alpha2}; aclScalarList* scalarlist = aclCreateScalarList(tempscalar.data(), tempscalar.size()); // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnForeachAddScalarList第一段接口 ret = aclnnForeachAddScalarListGetWorkspaceSize(tensorListInput, scalarlist, tensorListOutput, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnForeachAddScalarListGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); } // 调用aclnnForeachAddScalarList第二段接口 ret = aclnnForeachAddScalarList(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnForeachAddScalarList failed. ERROR: %d\n", ret); return ret); // 4. (固定写法)同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. 获取输出的值,将device侧内存上的结果复制至host侧,需要根据具体API的接口定义修改 auto size = GetShapeSize(outShape1); std::vector<float> out1Data(size, 0); ret = aclrtMemcpy(out1Data.data(), out1Data.size() * sizeof(out1Data[0]), out1DeviceAddr, size * sizeof(out1Data[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); for (int64_t i = 0; i < size; i++) { LOG_PRINT("out1 result[%ld] is: %f\n", i, out1Data[i]); } size = GetShapeSize(outShape2); std::vector<float> out2Data(size, 0); ret = aclrtMemcpy(out2Data.data(), out2Data.size() * sizeof(out2Data[0]), out2DeviceAddr, size * sizeof(out2Data[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); for (int64_t i = 0; i < size; i++) { LOG_PRINT("out2 result[%ld] is: %f\n", i, out2Data[i]); } // 6. 释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensorList(tensorListInput); aclDestroyTensorList(tensorListOutput); aclDestroyScalarList(scalarlist); // 7.释放device资源,需要根据具体API的接口定义修改 aclrtFree(input1DeviceAddr); aclrtFree(input2DeviceAddr); aclrtFree(out1DeviceAddr); aclrtFree(out2DeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例代码逐段解读
- 资源初始化(固定写法):
aclInit→aclrtSetDevice→aclrtCreateStream,完成 ACL 运行时初始化。deviceId需按实际硬件环境填写。 - 构造输入与输出:示例构造了两个 FLOAT32 张量(shape 分别为
{2,3}与{1,3})和两个 FLOAT32 标量(1.2f、2.2f)。CreateAclTensor模板函数完成 host 数据 → device 内存拷贝,并依据 shape 反推连续 strides 后调用aclCreateTensor创建aclTensor;标量通过aclCreateScalar创建aclScalar。最后分别用aclCreateTensorList与aclCreateScalarList将多个张量/标量聚合成列表参数。注意:由于x为 FLOAT32,scalars也必须使用 FLOAT32,示例中aclCreateScalar传入ACL_FLOAT正是遵循了前文的数据类型对应规则。 - 调用两段式接口:先调
aclnnForeachAddScalarListGetWorkspaceSize拿到workspaceSize与executor;若workspaceSize > 0则用aclrtMalloc申请 device 侧 workspace;随后调用aclnnForeachAddScalarList(workspaceAddr, workspaceSize, executor, stream)真正执行。 - 同步等待:
aclrtSynchronizeStream(stream)保证算子异步执行完成后才读取结果。 - 回拷结果:将输出张量从 device 内存
aclrtMemcpy回 host 并打印。预期out1为{1+1.2, 2+1.2, ..., 6+1.2},out2为{7+2.2, 8+2.2, 9+2.2}。 - 释放列表资源:
aclDestroyTensorList、aclDestroyScalarList释放列表句柄。 - 释放 device 资源:
aclrtFree释放各输入/输出/workspace 内存,aclrtDestroyStream、aclrtResetDevice、aclFinalize收尾。
结合源码理解底层实现
Host 侧算子定义(OpDef)
foreach_add_scalar_list_def.cpp 通过OpDef描述算子的输入输出契约:
- 输入
x:ParamType(DYNAMIC),即动态个数的 Tensor 列表,支持 FLOAT16 / FLOAT / INT32 / BF16,格式 ND,并标记AutoContiguous()(对应"支持非连续 Tensor"约束); - 输入
scalars:ScalarList()+ParamType(REQUIRED),即标量列表,数据类型由DtypeTensor2Scalar从张量类型映射而来; - 输出
y:ParamType(DYNAMIC),数据类型与x一致; - AICore 配置开启
DynamicCompileStaticFlag(true)、DynamicRankSupportFlag(true)、DynamicShapeSupportFlag(true),即支持动态 shape、动态 rank(0-8 维)的编译执行,这与参数表中"shape 0-8 维"的约束一一对应。
内核侧实现(Kernel)
foreach_add_scalar_list.cpp 是算子内核入口foreach_add_scalar_list,通过TILING_KEY_IS(...)按数据类型分发到模板实例:
- TILING_KEY=1:
ForeachOneScalarListBinary<half, half, AddsAdapter<half>, 1, 1>(FLOAT16); - TILING_KEY=2:
ForeachOneScalarListBinary<float, float, AddsAdapter<float>, 1, 1>(FLOAT32); - TILING_KEY=3(
__CCE_AICORE__ >= 220):ForeachOneScalarListBinary<int, int, AddsAdapter<int>, 1, 1>(INT32); - TILING_KEY=4(部分新架构,排除 NPU 3003/3113):
ForeachOneScalarListBinary<bfloat16_t, float, AddsAdapter<float>, 1, 1>(BFLOAT16,标量侧用 float 参与计算)。
其中AddsAdapter是对 AscendC 向量指令Adds(dst, src, scalar, count)的封装——注意标量以单值广播方式参与逐元素运算。从#if __CCE_AICORE__ >= 220与__NPU_ARCH__的编译期分支可以看出,不同架构对 BFLOAT16 / INT32 的支持存在差异,这也解释了为何 Kirin 系列(不含 BF16)与部分旧架构在支持矩阵上的区别。
底层通用模板
该算子复用了 foreach 系列的通用模板 foreach_one_scalar_list_binary.h。模板类ForeachOneScalarListBinary继承自KernelForeachUnary,核心流程为:
Init中将inScalarGM绑定到标量列表的全局内存(SetGlobalBuffer),按列表下标取值;ProcessPlusInLoop在每次循环迭代开始时,通过inScalarGM.GetValue(index)取出与当前张量配对的标量值(BFLOAT16 场景先将标量提升为 float 再计算);Compute从输入队列取数据、调用InnerComputer执行Adds逐元素加法、结果入输出队列。
因此,整个内核本质上是对"张量列表 + 标量列表"做扁平化的流水处理:外层遍历列表元素,内层以向量指令完成批量逐元素加法,一次 kernel 调用即可处理整组张量,避免了逐张量发 kernel 的开销。
图模式调用
除了 aclnn 接口,该算子也支持图模式构图调用。图模式下的算子 IR 定义在 foreach_add_scalar_list_proto.h:REG_OP(ForeachAddScalarList)声明了DYNAMIC_INPUT(x, ...)、INPUT(scalars, ...)、DYNAMIC_OUTPUT(y, ...)的图 IR 契约。两种调用方式的差异可参考 README 的"调用说明":
| 调用方式 | 样例代码 | 说明 |
|---|---|---|
| aclnn 接口 | test_aclnn_foreach_add_scalar_list | 通过 aclnnForeachAddScalarList 接口方式调用 ForeachAddScalarList 算子 |
| 图模式 | - | 通过 算子IR 构图方式调用 ForeachAddScalarList 算子 |
测试验证
仓库为该算子提供了完整的 UT/ST 测试链,可作为二次开发时的行为参照:
- 内核 UT:tests/ut/op_kernel/test_foreach_add_scalar_list.cpp 通过
gen_data.py生成多组 shape(如{{128, 64}, {16, 128}, {32, 128}})与 float32/float16 等类型数据,利用ICPU_SET_TILING_KEY指定数据类型分支、ICPU_RUN_KF在模拟环境下跑内核,再用compare_data.py与 golden 数据比对,覆盖"多张量列表 + 多标量列表"的批量加法正确性; - Host 侧 UT:
tests/ut/op_host/下的test_foreach_add_scalar_list_infershape.cpp与test_foreach_add_scalar_list_tiling.cpp分别验证 shape 推导与 tiling 计算逻辑; - ST 测试:
tests/st/aclnnForeachAddScalarList/提供 ATK 用例配置(atk_aclnnForeachAddScalarList.json)与执行器脚本,tests/assets/golden.py生成标准答案; - 配置档:
op_host/config/下按ascend910_93、ascend910b、ascend950、kirin9030、kirinx90分别提供foreach_add_scalar_list_binary.json,与算子定义的 AICore 注册一一对应。
总结
aclnnForeachAddScalarList 是 CANN ops-nn 中面向"批量张量 + 批量标量"逐元素加法场景的 foreach 组合算子,支持 FLOAT32 / FLOAT16 / BFLOAT16 / INT32 输入(标量分别为 FLOAT32 / INT64),覆盖 Ascend 950、A2、A3 及 Kirin 系列产品。开发者只需按两段式接口规范调用即可在 NPU 上高效完成整组张量的标量加法;如需深入定制,可沿 算子定义 → 内核实现 → 通用模板 的链路理解其数据契约、tiling 分发与向量化计算过程,并借助仓库内 UT/ST 测试快速验证改动。
【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考