CANN ops-math 张量标量加法算子 aclnnAdds 与 aclnnInplaceAdds 完整实战指南
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
aclnnAdds / aclnnInplaceAdds 是 CANN ops-math 算子库中 Add 系列面向"张量 + 标量"场景的 aclnn 单算子 API,实现了out = self + alpha × other的带缩放标量加法语义。本文以 math/add/docs/aclnnAdds&aclnnInplaceAdds.md 为骨架,结合 math/add/op_api/aclnn_add.cpp 的源码实现与 math/add/tests/st/aclnnAdds/atk_aclnnAdds.json 的测试用例,完整讲解接口原型、参数约束、两段式调用流程、底层计算图构建原理与可直接编译运行的调用示例,帮助开发者在 Ascend NPU 上快速、正确地完成张量加标量的加速计算。
产品支持情况
根据官方文档与 math/add/README.md 的算子说明,aclnnAdds 与 aclnnInplaceAdds 在以下产品上支持情况如下:
| 产品 | 支持情况 |
|---|---|
| Ascend 950PR / Ascend 950DT | 支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 支持 |
| Atlas 训练系列产品 | 支持 |
注意:
aclnnAdds与aclnnInplaceAdds在 Atlas 推理系列产品(310p)上支持,而双张量版本的aclnnAdd/aclnnInplaceAdd在该产品上不支持,选用接口时请按目标硬件核对支持矩阵。
功能说明
- 接口功能:完成"张量 + 标量"的加法计算,其中标量可以乘以缩放系数
alpha。 - 计算公式:
$$ out_i = self_i + alpha \times other $$
其中self为张量,other与alpha均为标量(aclScalar)。当alpha = 1时公式退化为out = self + other,此时接口内部会走专门的快速路径(见下文源码分析)。
该接口语义与 PyTorch 的torch.add(input, other, alpha=1)(张量加标量形式)一致,从 atk_aclnnAdds.json 的测试用例可以看出,其基准对标函数即torch.add。
函数原型与两段式接口
aclnnAdds与aclnnInplaceAdds实现相同的计算功能,二者的区别仅在于输出结果的存储方式:
- aclnnAdds:需要调用方新建一个输出张量对象
out来存储计算结果,输入张量在计算前后保持不变。 - aclnnInplaceAdds:无需新建输出张量对象,直接在输入张量
selfRef的内存中就地写入计算结果,可减少一次输出张量的内存申请与搬运。
与 CANN 单算子 API 的通用规范一致,每个算子都遵循两段式接口设计:必须先调用第一段...GetWorkspaceSize接口,获取本次计算所需的 workspace 大小以及封装了算子计算流程的执行器(aclOpExecutor);再调用第二段接口执行真正的计算。两段式接口的完整形态如下:
// aclnnAdds 第一段:参数校验 + 获取 workspace 大小与执行器 aclnnStatus aclnnAddsGetWorkspaceSize( const aclTensor* self, // 输入张量 const aclScalar* other, // 输入标量 const aclScalar* alpha, // 缩放标量 aclTensor* out, // 输出张量 uint64_t* workspaceSize, // 输出:workspace 大小 aclOpExecutor** executor) // 输出:算子执行器 // aclnnAdds 第二段:执行计算 aclnnStatus aclnnAdds( void* workspace, // Device 侧申请的 workspace 内存地址 uint64_t workspaceSize, // 由第一段接口获取的大小 aclOpExecutor* executor, // 第一段接口返回的执行器 aclrtStream stream) // 指定执行任务的 Stream // aclnnInplaceAdds 第一段 aclnnStatus aclnnInplaceAddsGetWorkspaceSize( const aclTensor *selfRef, // 输入/输出张量,结果写回该张量 const aclScalar *other, const aclScalar *alpha, uint64_t *workspaceSize, aclOpExecutor **executor) // aclnnInplaceAdds 第二段 aclnnStatus aclnnInplaceAdds( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)关于两段式接口需要特别注意的是:
- workspace 是指除输入/输出外,算子在 NPU 上完成计算所需的临时内存,
workspaceSize表示该临时内存的大小,具体数值由第一段接口根据计算图动态推算; - 第一段接口仅做入参校验与计算图构建,不会真正执行计算;
- 第二段接口
aclnnAdds(...)不能重复调用,同一个 executor 调用两次属于异常用法; - 若
workspaceSize为 0,则无需申请 workspace,第二段接口可直接传入nullptr。
aclnnAddsGetWorkspaceSize 参数说明
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| self | 输入 | 公式中的输入 self | - | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL、COMPLEX128、COMPLEX64、BFLOAT16 | ND | 不高于8维 | √ |
| other | 输入 | 公式中的 other(标量) | - | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL、COMPLEX128、COMPLEX64、BFLOAT16 | - | - | - |
| alpha | 输入 | 公式中的 alpha(标量) | 数据类型需要可转换成 self 与 other 推导后的数据类型 | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL、COMPLEX128、COMPLEX64、BFLOAT16 | - | - | - |
| out | 输出 | 公式中的 out | 数据类型需要是 self 与 other 推导之后可转换的数据类型(参见互转换关系) | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL、COMPLEX128、COMPLEX64、BFLOAT16 | ND | 与 self 一致 | √ |
| workspaceSize | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
不同产品上的类型约束差异:
- Atlas 推理系列产品、Atlas 训练系列产品:
- 不支持 BFLOAT16 数据类型;
- self 与 other 数据类型需满足互推导关系。
- Atlas A2 训练系列产品 / Atlas A2 推理系列产品、Atlas A3 训练系列产品 / Atlas A3 推理系列产品:self 与 other 数据类型需满足互推导关系。
- Ascend 950PR / Ascend 950DT:other 数据类型与 self 需满足 TensorScalar 互推导关系。
返回值(aclnnStatus):
返回状态码具体参见 aclnn返回码。第一段接口完成入参校验,出现以下场景时报错:
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 self、other、out 或 alpha 是空指针。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 的数据类型不在支持的范围之内。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 other 无法做数据类型推导。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | 推导出的数据类型无法转换为 out 的类型。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | alpha 无法转换为 self 和 other 推导后的数据类型。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 与 out 的 shape 不一致。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 与 out 的维度大于 8。 |
说明:
ACLNN_ERR_PARAM_NULLPTR(161001)与ACLNN_ERR_PARAM_INVALID(161002)均为参数校验类错误;运行时类错误以361xxx开头,API 内部异常以561xxx开头,完整的错误码语义可查阅 docs/zh/context/aclnn_return_code.md。
aclnnAdds 参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址。 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnAddsGetWorkspaceSize 获取。 |
| executor | 输入 | op 执行器,包含了算子计算流程。 |
| stream | 输入 | 指定执行任务的 Stream。 |
返回值:aclnnStatus,具体参见 aclnn返回码。
aclnnInplaceAddsGetWorkspaceSize 参数说明
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| selfRef | 输入/输出 | 公式中的输入 selfRef,计算完成后结果直接写回该张量 | - | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL、BFLOAT16 | ND | 不超过8维 | √ |
| other | 输入 | 公式中的 other(标量) | - | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL、BFLOAT16 | - | - | - |
| alpha | 输入 | 公式中的 alpha(标量) | 数据类型需要可转换成 selfRef 与 other 推导后的数据类型 | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL、BFLOAT16 | - | - | - |
| workspaceSize | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
不同产品上的类型约束差异:
- Atlas 训练系列产品:
- 不支持 BFLOAT16 数据类型;
- selfRef 与 other 需满足互推导关系,且需要是推导之后可转换的数据类型(参见互转换关系)。
- Atlas A2 训练系列产品 / Atlas A2 推理系列产品、Atlas A3 训练系列产品 / Atlas A3 推理系列产品:selfRef 与 other 需满足互推导关系,且需要是推导之后可转换的数据类型。
- Ascend 950PR / Ascend 950DT:selfRef 与 other 需满足 TensorScalar 互推导关系,且需要是推导之后可转换的数据类型。
返回值(aclnnStatus):
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 selfRef、other 或 alpha 是空指针。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | selfRef 的数据类型不在支持的范围之内。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | selfRef 和 other 无法做数据类型推导。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | 推导出的数据类型无法转换为 selfRef 的类型。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | alpha 无法转换为 selfRef 和 other 推导后的数据类型。 |
aclnnInplaceAdds 参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址。 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnInplaceAddsGetWorkspaceSize 获取。 |
| executor | 输入 | op 执行器,包含了算子计算流程。 |
| stream | 输入 | 指定执行任务的 Stream。 |
返回值:aclnnStatus,具体参见 aclnn返回码。
约束说明
- 确定性计算:aclnnAdds 与 aclnnInplaceAdds 默认采用确定性实现,同一输入下多次执行结果可复现。
- 精度约束:
other参数对于 float 无精度损失;对于 int32、int64 数据类型,当other参数大于 2^24 时可能存在精度损失。此时推荐改用双张量版本的 aclnnAdd / aclnnInplaceAdd,避免标量精度损失问题。
源码级实现解析
理解了接口与约束后,我们再深入 math/add/op_api/aclnn_add.cpp,看第一段接口内部到底做了什么。这有助于理解参数校验规则和 workspace 的来源。
第一段接口的完整流程
aclnnAddsGetWorkspaceSize的源码实现(aclnn_add.cpp)大致分为以下几个步骤:
- 创建执行器:调用框架宏
CREATE_EXECUTOR()创建aclOpExecutor,失败返回ACLNN_ERR_INNER_CREATE_EXECUTOR。 - 入参校验:调用
CheckParamsScalar完成空指针、数据类型、推导关系、shape 一致性等全部校验,该校验逻辑与文档中的错误码表格一一对应。 - 空张量短路:若
self->IsEmpty(),直接返回workspaceSize = 0并释放执行器,kernel 层支持空张量。 - 类型推导与计算图构建:根据
PromoteTypeScalar推导出的计算类型promoteType,将标量other通过ConvertToTensor转换为张量,然后按分支构建计算图(见下文)。 - 结果类型转换与写回:将计算结果
Cast为out的数据类型,再通过ViewCopy写入输出张量(out 可能是非连续张量)。 - 返回 workspace:通过
uniqueExecutor->GetWorkspaceSize()汇总整张计算图所需临时内存,并通过ReleaseTo(executor)将执行器所有权转移给调用方。
标量类型推导规则 PromoteTypeScalar
PromoteTypeScalar(aclnn_add.cpp)负责确定self(张量)与other(标量)参与运算的最终计算类型:
- 若
self为浮点类型(FLOAT),推导结果直接取self的类型; - 若推导结果为 FLOAT16 或 BFLOAT16,则进一步检查
other与alpha是否能无损转成该半精度类型,若不能则提升为 FLOAT,避免精度损失; - 若推导出 COMPLEX32,则统一提升为 COMPLEX64;
- 若
self为 BOOL 或other为浮点类型,则按op::PromoteType通用推导规则计算。
这也解释了文档中"alpha 的数据类型需要可转换成 self 与 other 推导后的数据类型"这一约束的来历——CheckPromoteType会调用CanCast校验 alpha 能否无损转换到推导类型。
计算图构建的四种分支
aclnnAddsGetWorkspaceSize在构建计算图时会针对不同情况选择不同的底层算子组合(aclnn_add.cpp):
| 分支条件 | 底层计算图 | 说明 |
|---|---|---|
| alpha 等于 1 | Add(self, other) | 免去标量乘法,直接调用 L0 层 Add |
| 支持 Axpy | Axpy(self, other, alpha) | 将 "乘加" 融合为单算子,性能最优 |
| 支持 AxpyV2(仅 RegBase 架构) | AxpyV2(self, other, alphaTensor) | alpha 以张量形式传入的融合乘加 |
| 其他情况 | Mul(other, alphaTensor)后再Add(self, otherRes) | 通用拆解路径 |
其中IsEqualToOne(aclnn_add.cpp)用于判断 alpha 是否等于 1,IsSupportAxpy/IsSupportAxpyV2依据当前 NPU 架构与推导类型决定能否走融合算子。此外,在进入上述分支前,源码还会视需要插入Contiguous(转连续张量)与Cast(隐式类型转换)节点;在输出侧统一追加Cast(转回 out 类型)与ViewCopy(写回输出)节点。整个流程可以概括为:
self / other(标量) ──> Contiguous ──> Cast(promoteType) └─> Add / Axpy / AxpyV2 / Mul+Add │ v Cast(out类型) ──> ViewCopy ──> out一个值得注意的细节是 BOOL 类型的特殊处理(aclnn_add.cpp):当 self、other、alpha 均为 BOOL 且 out 非 BOOL、other && alpha为真时,会额外插入一次"Cast 到 BOOL 再 Cast 回来"的中间节点,防止 BOOL 相加出现值为 2 的错误结果。
Inplace 版本的实现方式
aclnnInplaceAddsGetWorkspaceSize的实现非常简洁(aclnn_add.cpp):它将selfRef通过const_cast直接作为out传入aclnnAddsGetWorkspaceSize,复用同一套校验与构图逻辑,最终计算结果通过ViewCopy写回selfRef所在的内存。这就是"就地计算、无需新建输出张量"的实现本质。
从更底层的 L0 视角看,l0op::Add会先对self与other做 broadcast shape 推导,若输入为混合数据类型(如 FP16+FP32、BF16+FP32)则自动输出 FLOAT 类型中间结果(见 math/add/op_api/add.cpp);而AddInplace则额外校验 broadcast 后的 shape 必须与输出张量一致(add.cpp)。
测试用例佐证
atk_aclnnAdds.json 中的 ATK(Ascend Test Kit)用例以torch.add为基准函数,覆盖了 fp32、fp16、bf16、fp64、int8、int16、int32、int64、bool 等数据类型,shape 覆盖 1~4 维与[1,1,1,1]等边界形态,以及nan、inf、-inf、[-inf, inf]等特殊输入值。其中绝大部分用例将alpha固定为 1(对应"alpha 等于 1 走快速路径"的分支),同时也有alpha为一般值的用例覆盖通用 Mul+Add 路径,可作为回归验证的参考。
调用示例
以下示例代码来自仓库 math/add/examples/test_aclnn_adds.cpp,在同一程序中依次演示aclnnAdds(输出到新建张量 out)与aclnnInplaceAdds(结果写回 self 内存)的完整调用流程。具体编译和执行过程请参考编译与运行样例。
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_add.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); aclFinalize(); return ret); ret = aclrtCreateStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); aclrtResetDevice(deviceId); aclFinalize(); 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> selfShape = {4, 2}; std::vector<int64_t> outShape = {4, 2}; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclScalar* other = nullptr; aclScalar* alpha = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {0, 1, 2, 3, 4, 5, 6, 7}; std::vector<float> outHostData(8, 0); float otherValue = 2.0f; float alphaValue = 1.2f; // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建other aclScalar other = aclCreateScalar(&otherValue, aclDataType::ACL_FLOAT); CHECK_RET(other != nullptr, return ret); // 创建alpha aclScalar alpha = aclCreateScalar(&alphaValue, aclDataType::ACL_FLOAT); CHECK_RET(alpha != nullptr, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnAdds第一段接口 ret = aclnnAddsGetWorkspaceSize(self, other, alpha, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAddsGetWorkspaceSize 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); } // 调用aclnnAdds第二段接口 ret = aclnnAdds(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAdds 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(outShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(resultData[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("result[%ld] is: %f\n", i, resultData[i]); } // aclnnInplaceAdds接口调用示例 // 3. 调用CANN算子库API LOG_PRINT("\ntest aclnnInplaceAdds\n"); // 调用aclnnInplaceAdds第一段接口 ret = aclnnInplaceAddsGetWorkspaceSize(self, other, alpha, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceAddsGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 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); } // 调用aclnnInplaceAdds第二段接口 ret = aclnnInplaceAdds(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceAdds 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的接口定义修改 ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), selfDeviceAddr, size * sizeof(resultData[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("result[%ld] is: %f\n", i, resultData[i]); } // 6. 释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyScalar(other); aclDestroyScalar(alpha); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例运行结果说明:输入self = {0,1,2,3,4,5,6,7}、other = 2.0、alpha = 1.2,则out_i = self_i + 1.2 × 2.0 = self_i + 2.4,aclnnAdds输出{2.4, 3.4, 4.4, 5.4, 6.4, 7.4, 8.4, 9.4};随后aclnnInplaceAdds基于已被修改的 self 再执行一次同样的运算(结果{4.8, 5.8, 6.8, 7.8, 8.8, 9.8, 10.8, 11.8}写回 self 内存)。该示例同时演示了两段式接口的固定套路:初始化 → 构造张量/标量 → 第一段取 workspace → 按需申请 Device 内存 → 第二段执行 → 同步 Stream → 拷贝结果 → 释放资源。
示例关键点提示
- 头文件:
#include "aclnnop/aclnn_add.h"是 aclnnAdds / aclnnInplaceAdds 等 Add 系列接口的声明头文件。 - 标量创建:
other与alpha都通过aclCreateScalar(&value, aclDataType::ACL_FLOAT)创建,需要传入值的指针与数据类型;示例中将其声明为aclScalar*并通过aclDestroyScalar释放。 - workspace 申请:只有
workspaceSize > 0时才需要aclrtMalloc申请 Device 内存,否则workspaceAddr保持nullptr即可。 - Inplace 语义:
aclnnInplaceAddsGetWorkspaceSize只接受selfRef、other、alpha三个参数(没有 out),计算完成后从selfDeviceAddr对应的内存读取结果。
总结
aclnnAdds / aclnnInplaceAdds 是 ops-math 中"张量 + 标量"场景的标准解法,二者语义相同、仅输出方式不同:前者需要独立输出张量,后者直接原地写回输入。掌握其两段式接口调用范式(GetWorkspaceSize → 申请 workspace → 执行 → 同步),理解第一段接口内部的类型推导、alpha=1 快速路径、Axpy 融合与 Inplace 复用机制,即可在目标 NPU 产品上高效、正确地完成带缩放系数的标量加法计算。若需对 Add 系列算子有更全面的了解,可继续阅读双张量版本的 aclnnAdd / aclnnInplaceAdd 文档,或查看底层 kernel 实现 math/add/op_kernel、tiling 配置 math/add/op_host/config 与单元测试 math/add/tests。
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考