news 2026/9/21 18:30:17

CANN ops-nn 算子详解:aclnnForeachAddScalarList 张量列表逐元素标量加法接口

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CANN ops-nn 算子详解:aclnnForeachAddScalarList 张量列表逐元素标量加法接口

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、INT32ND0-8
scalars(aclScalarList*)输入表示加法运算的输入标量列表,对应公式中的scalars元素个数与x中 Tensor 的个数相等;数据类型与入参x存在对应关系(见下方说明)FLOAT32、INT64---
out(aclTensorList*)输出表示进行加法运算的输出张量列表,对应公式中的y支持空 Tensor;所有 Tensor 数据类型保持一致;数据类型和数据格式与入参x一致,shape size 大于等于入参x的 shape sizeFLOAT32、FLOAT16、BFLOAT16、INT32ND0-8×
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小-----
executor(aclOpExecutor**)输出返回 op 执行器,包含了算子计算流程-----

其中scalarsx的数据类型对应关系非常关键,直接决定输入组合是否合法:

  • 当入参x的数据类型为FLOAT32、FLOAT16、BFLOAT16时,scalars的数据类型仅支持 FLOAT32
  • 当入参x的数据类型为INT32时,scalars的数据类型仅支持 INT64

这一规则在源码中有直接依据:在 算子定义 中,张量侧支持ge::DT_FLOAT16 / ge::DT_FLOAT / ge::DT_INT32 / ge::DT_BF16,标量侧则通过DtypeTensor2Scalar工具函数从张量类型映射而来;而图模式 IR 定义中scalarsTensorType明确为{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_NULLPTR161001传入的 x、scalars、out 是空指针
ACLNN_ERR_PARAM_INVALID161002x、scalars、out 的数据类型不在支持的范围之内
ACLNN_ERR_PARAM_INVALID161002x 和 out 的数据类型不一致
ACLNN_ERR_INNER_TILING_ERROR561002x 与 out 的 shape 不满足约束
ACLNN_ERR_INNER_TILING_ERROR561002x 或 out 中的 Tensor 数据类型不一致
ACLNN_ERR_INNER_TILING_ERROR561002x 或 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; }

示例代码逐段解读

  1. 资源初始化(固定写法)aclInitaclrtSetDeviceaclrtCreateStream,完成 ACL 运行时初始化。deviceId需按实际硬件环境填写。
  2. 构造输入与输出:示例构造了两个 FLOAT32 张量(shape 分别为{2,3}{1,3})和两个 FLOAT32 标量(1.2f2.2f)。CreateAclTensor模板函数完成 host 数据 → device 内存拷贝,并依据 shape 反推连续 strides 后调用aclCreateTensor创建aclTensor;标量通过aclCreateScalar创建aclScalar。最后分别用aclCreateTensorListaclCreateScalarList将多个张量/标量聚合成列表参数。注意:由于x为 FLOAT32,scalars也必须使用 FLOAT32,示例中aclCreateScalar传入ACL_FLOAT正是遵循了前文的数据类型对应规则。
  3. 调用两段式接口:先调aclnnForeachAddScalarListGetWorkspaceSize拿到workspaceSizeexecutor;若workspaceSize > 0则用aclrtMalloc申请 device 侧 workspace;随后调用aclnnForeachAddScalarList(workspaceAddr, workspaceSize, executor, stream)真正执行。
  4. 同步等待aclrtSynchronizeStream(stream)保证算子异步执行完成后才读取结果。
  5. 回拷结果:将输出张量从 device 内存aclrtMemcpy回 host 并打印。预期out1{1+1.2, 2+1.2, ..., 6+1.2}out2{7+2.2, 8+2.2, 9+2.2}
  6. 释放列表资源aclDestroyTensorListaclDestroyScalarList释放列表句柄。
  7. 释放 device 资源aclrtFree释放各输入/输出/workspace 内存,aclrtDestroyStreamaclrtResetDeviceaclFinalize收尾。

结合源码理解底层实现

Host 侧算子定义(OpDef)

foreach_add_scalar_list_def.cpp 通过OpDef描述算子的输入输出契约:

  • 输入xParamType(DYNAMIC),即动态个数的 Tensor 列表,支持 FLOAT16 / FLOAT / INT32 / BF16,格式 ND,并标记AutoContiguous()(对应"支持非连续 Tensor"约束);
  • 输入scalarsScalarList()+ParamType(REQUIRED),即标量列表,数据类型由DtypeTensor2Scalar从张量类型映射而来;
  • 输出yParamType(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 侧 UTtests/ut/op_host/下的test_foreach_add_scalar_list_infershape.cpptest_foreach_add_scalar_list_tiling.cpp分别验证 shape 推导与 tiling 计算逻辑;
  • ST 测试tests/st/aclnnForeachAddScalarList/提供 ATK 用例配置(atk_aclnnForeachAddScalarList.json)与执行器脚本,tests/assets/golden.py生成标准答案;
  • 配置档op_host/config/下按ascend910_93ascend910bascend950kirin9030kirinx90分别提供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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 18:29:12

JVM调优实战:从参数配置到性能优化指南

1. JVM调优实战&#xff1a;从参数配置到性能优化的完整指南在Java应用开发中&#xff0c;JVM调优是每个资深开发者必须掌握的技能。记得我第一次负责生产环境调优时&#xff0c;面对频繁的Full GC和居高不下的CPU使用率&#xff0c;那种手足无措的感觉至今难忘。经过多年实践&…

作者头像 李华
网站建设 2026/9/21 18:29:07

VUX 微信端实践:Vue 单页面应用动态设置页面标题的完整方案

VUX 微信端实践&#xff1a;Vue 单页面应用动态设置页面标题的完整方案 【免费下载链接】vux Mobile UI Components based on Vue & WeUI 项目地址: https://gitcode.com/gh_mirrors/vu/vux 本文基于 VUX 仓库官方文档 微信 Vue 单页面应用设置标题 展开。在微信内置…

作者头像 李华
网站建设 2026/9/21 18:26:53

OpenClaw 28万星但配置劝退?TaoToken 一个 Key 统一接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 18:23:18

Python+Vue全栈开发在线导游预约系统实战

1. 项目概述&#xff1a;基于PythonVue的在线导游预约系统去年接手了一个旅游平台的导游预约模块改造项目&#xff0c;客户要求从原有的电话预约模式升级为全流程在线化系统。经过技术选型&#xff0c;最终采用PythonDjango/FlaskVue.js的技术栈实现了这套系统。这个方案在保证…

作者头像 李华