CANN ops-math 算子开发指南:aclnnLerp 与 aclnnInplaceLerp 线性插值接口详解与实战
2026/9/20 20:44:55 网站建设 项目流程

CANN ops-math 算子开发指南:aclnnLerp 与 aclnnInplaceLerp 线性插值接口详解与实战

【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math

Lerp(Linear Interpolation,线性插值)是数学基础计算算子库 CANN ops-math 中的常用算子,用于在起始 Tensor 与结束 Tensor 之间按权重进行逐元素线性插值。本文以 math/lerp/docs/aclnnLerp&aclnnInplaceLerp.md 为核心,完整梳理 aclnnLerp 与 aclnnInplaceLerp 两套两段式接口的函数原型、参数约束、错误码与返回值,并给出可直接编译运行的 C++ 调用示例;同时结合 op_api/aclnn_lerp_tensor.cpp、op_host/lerp_def.cpp、op_kernel/lerp_apt.cpp 等源码,剖析其参数校验、Broadcast 推导与 NPU 端实现原理。读完本文,你将能够根据自身场景正确选择普通输出或 in-place 接口,并完成从资源初始化、Tensor 构造到两段式调用与结果回拷的完整开发流程。

一、产品支持情况

aclnnLerp 与 aclnnInplaceLerp 在当前仓库中的产品支持情况如下:

产品是否支持
Ascend 950PR / Ascend 950DT支持
Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持
Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持
Atlas 200I/500 A2 推理产品不支持
Atlas 推理系列产品支持
Atlas 训练系列产品支持

与上述支持情况相印证,算子定义文件 op_host/lerp_def.cpp 中仅为ascend950ascend350两个架构注册了 AICore 配置,并开启了DynamicRankSupportFlag(true)DynamicShapeSupportFlag(true)PrecisionReduceFlag(true)等动态能力。此外,在支持的产品中存在一个数据类型差异:Atlas 推理系列产品、Atlas 训练系列产品不支持 BFLOAT16,因此在这两类产品上调用接口时,输入输出仅能使用 FLOAT 或 FLOAT16。

二、功能说明与计算公式

接口功能为:根据给定的权重,在起始和结束 Tensor 之间进行线性插值,返回插值后的 Tensor。计算公式如下:

$$ \text { out }_i=\text { start }_i+\text { weight }_i \times\left(\text { end }_i-\text { start }_i\right) $$

其中startendweight三个输入均支持 Broadcast,三个 Tensor 逐元素广播对齐后,按上式逐元素计算得到输出out

结合源码可以更清晰地看到这一语义的实现路径:

  • 算子原语(l0 层)声明位于 op_api/lerp.h,即l0op::Lerp(self, other, weight, executor)
  • Host 侧原语描述 op_host/lerp_def.cpp 定义了startendweight三个必选输入与y一个输出;
  • 形状推导 op_host/lerp_infershape.cpp 直接调用Ops::Base::InferShape4Broadcast(context, INPUT_NUM_THREE),即对 3 个输入做标准 Broadcast 推导。

三、函数原型与两段式接口机制

aclnnLerpaclnnInplaceLerp实现完全相同的功能,区别仅在于输出方式,请根据实际场景选择:

  • aclnnLerp:需新建一个输出张量对象(out)来存储计算结果,适合需要保留原始输入的场景;
  • aclnnInplaceLerp:无需新建输出张量对象,直接在输入张量selfRef的内存中原地存储计算结果,可节省一份输出内存。

两个算子均遵循 CANN 的两段式接口规范:必须先调用aclnnLerpGetWorkspaceSize(或aclnnInplaceLerpGetWorkspaceSize)获取计算所需 workspace 大小以及包含算子计算流程的执行器,再调用aclnnLerp(或aclnnInplaceLerp)执行计算。

四个接口的函数原型如下:

aclnnStatus aclnnLerpGetWorkspaceSize( const aclTensor* self, const aclTensor* end, const aclTensor* weight, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)
aclnnStatus aclnnLerp( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream)
aclnnStatus aclnnInplaceLerpGetWorkspaceSize( aclTensor* selfRef, const aclTensor* end, const aclTensor* weight, uint64_t* workspaceSize, aclOpExecutor** executor)
aclnnStatus aclnnInplaceLerp( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream)

从源码实现 op_api/aclnn_lerp_tensor.cpp 可以看出两段式接口的内在联系:aclnnLerpGetWorkspaceSize内部创建OpExecutor并调用CalculateResult完成参数校验与计算图构建,随后通过uniqueExecutor->GetWorkspaceSize()返回所需 workspace 大小并把 executor 释放给调用方;aclnnLerp则通过CommonOpExecutorRun(workspace, workspaceSize, executor, stream)真正下发执行。值得一提的是,in-place 版本的aclnnInplaceLerpGetWorkspaceSize内部实际是CalculateResult(selfRef, end, weight, selfRef, ...)——即把selfRef同时作为输入与输出传入,从而在原始内存上完成计算。

四、aclnnLerpGetWorkspaceSize 参数详解

4.1 参数说明

参数名输入/输出描述使用说明数据类型数据格式维度非连续Tensor
self(aclTensor*)输入公式中的输入 start数据类型与 end、weight、out 一致;shape 需与 end、weight 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8
end(aclTensor*)输入公式中的输入 end数据类型与 self、weight、out 一致;shape 需与 self、weight 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8
weight(aclTensor*)输入公式中的输入 weight数据类型与 self、end、out 一致;shape 需与 self、end 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8
out(aclTensor*)输出公式中的 out数据类型与 self、end、weight 一致;shape 需与 self、end、weight broadcast 后的 shape 一致FLOAT、FLOAT16、BFLOAT16ND0-8
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小-----
executor(aclOpExecutor**)输出返回 op 执行器,包含算子计算流程-----

Atlas 推理系列产品、Atlas 训练系列产品:不支持 BFLOAT16。

参数约束在源码 op_api/aclnn_lerp_tensor.cpp 中有完整对应实现:

  • 数据类型支持列表按架构区分:GetDtypeSupportList()中,DAV_2201架构及寄存器基座(IsRegBase)架构(对应 A2/A3/950 等)支持DT_FLOAT16 / DT_FLOAT / DT_BF16,其余架构仅支持DT_FLOAT16 / DT_FLOAT——与文档中"Atlas 推理/训练系列产品不支持 BFLOAT16"的说明一致;
  • CheckDtypeValid逐一校验 self 的类型是否在支持列表内,并校验 end、weight、out 与 self 类型一致;
  • CheckShape先对 self、end 做BroadcastInferShape,再与 weight 做第二次 broadcast,并要求 broadcast 结果与 out 的 shape 完全一致;
  • 空 Tensor 处理:当 self/end/weight 任一为空时,广播结果必为空,直接返回ACLNN_SUCCESS,不执行计算。

4.2 返回值与错误码

返回值为aclnnStatus状态码,具体可参见 aclnn 返回码说明。第一段接口完成入参校验,出现如下场景时报错:

返回值错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 self、end、weight 和 out 是空指针
ACLNN_ERR_PARAM_INVALID161002self、end、weight 和 out 的数据类型不在支持的范围之内
ACLNN_ERR_PARAM_INVALID161002self、end、weight 和 out 的数据类型不一致
ACLNN_ERR_PARAM_INVALID161002self、end 和 weight 无法做 broadcast
ACLNN_ERR_PARAM_INVALID161002self、end 和 weight 做 broadcast 后的 shape 与 out 的 shape 不一致

错误码的抛出路径与源码一致:CheckParams中先执行CheckNotNull(失败返回ACLNN_ERR_PARAM_NULLPTR),再执行CheckDtypeValidCheckShape(失败均返回ACLNN_ERR_PARAM_INVALID)。

五、aclnnLerp 参数详解(第二段接口)

参数名输入/输出描述
workspace输入在 Device 侧申请的 workspace 内存地址
workspaceSize输入在 Device 侧申请的 workspace 大小,由第一段接口 aclnnLerpGetWorkspaceSize 获取
executor输入op 执行器,包含算子计算流程
stream输入指定执行任务的 Stream

返回值同样为aclnnStatus状态码,参见 aclnn 返回码说明。

六、aclnnInplaceLerpGetWorkspaceSize 参数详解

6.1 参数说明

参数名输入/输出描述使用说明数据类型数据格式维度非连续Tensor
selfRef(aclTensor*)输入/输出公式中的输入 start 和输出 out数据类型与 end、weight 一致;shape 需与 end、weight 满足 broadcast 关系,且 broadcast 后的 shape 与 selfRef 一致FLOAT、FLOAT16、BFLOAT16ND0-8
end(aclTensor*)输入公式中的输入 end数据类型与 selfRef、weight 一致;shape 需与 selfRef、weight 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8
weight(aclTensor*)输入公式中的输入 weight数据类型与 selfRef、end 一致;shape 需与 selfRef、end 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小-----
executor(aclOpExecutor**)输出返回 op 执行器,包含算子计算流程-----

Atlas 推理系列产品、Atlas 训练系列产品:不支持 BFLOAT16。

需要特别注意selfRef的语义:它同时承担 start 输入与 out 输出的角色,且要求 broadcast 后的 shape 与 selfRef 自身 shape 一致——也就是说 in-place 版本要求 selfRef 在广播中必须是"最大"的那个 shape,否则原地计算无法成立。

6.2 返回值与错误码

返回值错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 selfRef、end 和 weight 是空指针
ACLNN_ERR_PARAM_INVALID161002selfRef、end 和 weight 的数据类型不在支持的范围之内
ACLNN_ERR_PARAM_INVALID161002selfRef、end 和 weight 的数据类型不一致
ACLNN_ERR_PARAM_INVALID161002selfRef、end 和 weight 无法做 broadcast
ACLNN_ERR_PARAM_INVALID161002selfRef、end 和 weight 做 broadcast 后的 shape 与 selfRef 的 shape 不一致

七、aclnnInplaceLerp 参数详解(第二段接口)

参数名输入/输出描述
workspace输入在 Device 侧申请的 workspace 内存地址
workspaceSize输入在 Device 侧申请的 workspace 大小,由第一段接口 aclnnInplaceLerpGetWorkspaceSize 获取
executor输入op 执行器,包含算子计算流程
stream输入指定执行任务的 Stream

返回值同样为aclnnStatus状态码,参见 aclnn 返回码说明。

八、约束说明

  • 确定性计算:aclnnLerp & aclnnInplaceLerp 默认即为确定性实现,即相同输入在多次运行中产生一致的计算结果。关于确定性计算的通用说明可参见 确定性计算。
  • 其余隐含约束(从源码与文档整理):三个输入加输出的数据类型必须一致(Atlas 推理/训练系列不支持 BFLOAT16);输入 shape 必须满足 Broadcast 关系,输出 shape 必须等于三输入 broadcast 后的 shape;数据格式仅支持 ND;维度范围为 0-8 维;支持非连续 Tensor(接口内部会自动转为连续再计算)。

九、源码级原理剖析:从 aclnn 接口到 NPU kernel

为了让读者对接口背后的实现有完整认知,这里结合仓库源码梳理一条从 Host 侧 API 到 Device 侧 Kernel 的关键链路:

  1. Host 侧算子原语(l0 层)l0op::Lerp声明于 op_api/lerp.h,在 op_api/aclnn_lerp_tensor.cpp 的CalculateResult中被调用。该函数在执行计算前做了三项准备工作:对非连续输入调用l0op::Contiguous转连续、对非 ND 格式打印告警、对非连续输出调用l0op::ViewCopy将计算结果写回非连续视图。这也是文档中"非连续 Tensor:√"能够成立的实现基础。
  2. 算子原语描述与形状推导:原语Lerp的定义见 op_host/lerp_def.cpp,配置了DynamicCompileStaticFlagDynamicShapeSupportFlag等动态编译能力;shape 推导见 op_host/lerp_infershape.cpp,即三输入 Broadcast 推导。
  3. Device 侧 Kernel:入口函数为 op_kernel/lerp_apt.cpp 中的lerp(GM_ADDR start, GM_ADDR end, GM_ADDR weight, GM_ADDR y, ...)。Kernel 依据 tiling 阶段生成的TILING_KEY分派到 10 种实现之一,例如:
    • LERP_F32_NDDMA_WITHOUT_LOOPS_TILING_KEY:float32、UB 内最多 5 维、无需循环的 NDDMA 直通实现;
    • LERP_F32_NDDMA_WITH_LOOPS_TILING_KEY:float32、UB 内最多 8 维、需要循环搬移的实现;
    • 同样的模式覆盖 FLOAT16、BFLOAT16 以及 weight 为 float32 的混合精度组合(dtype_comb_0/dtype_comb_1)。 每个分支对应 op_kernel/arch35/ 下独立的头文件实现,且 Kernel 限定 AIV 核执行(KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY))。Tiling 侧的动态 shape 切分逻辑则位于 op_host/arch35/lerp_tiling_arch35.cpp。
  4. 测试验证:ST 级用例由 tests/st/aclnnLerp/atk_aclnnLerp.json 描述,覆盖 fp32 / fp16 / bf16 三种精度、从 1 维到 8 维的多种 shape,以及inf-infnan边界值输入;UT 级用例见 tests/ut/op_api/test_aclnn_lerp_tensor.cpp 与 tests/ut/op_host/test_lerp_infershape.cpp。

十、调用示例

以下示例代码仅供参考,具体编译和执行过程请参考 编译与运行样例。

10.1 aclnnLerp 调用示例

本示例中 self、end 的 shape 均为{4, 2},weight 的 shape 为{1}(广播到{4, 2}),输出 shape 为{4, 2}。验证预期:out_i = self_i + 2 × (end_i - self_i)

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_lerp_tensor.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手册 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> endShape = {4, 2}; std::vector<int64_t> weightShape = {1}; std::vector<int64_t> outShape = {4, 2}; void* selfDeviceAddr = nullptr; void* endDeviceAddr = nullptr; void* weightDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclTensor* end = nullptr; aclTensor* weight = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {1, 2, 3, 4, 5, 6, 7, 8}; std::vector<float> endHostData = {4, 5, 6, 7, 8, 9, 10, 11}; std::vector<float> weightHostData = {2}; std::vector<float> outHostData = {0, 0, 0, 0, 0, 0, 0, 0}; // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建end aclTensor ret = CreateAclTensor(endHostData, endShape, &endDeviceAddr, aclDataType::ACL_FLOAT, &end); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建weight aclTensor ret = CreateAclTensor(weightHostData, weightShape, &weightDeviceAddr, aclDataType::ACL_FLOAT, &weight); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); uint64_t workspaceSize = 0; aclOpExecutor* executor; // 3. 调用CANN算子库API // 调用aclnnLerp第一段接口 ret = aclnnLerpGetWorkspaceSize(self, end, weight, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnLerpGetWorkspaceSize 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); } // 调用aclnnLerp第二段接口 ret = aclnnLerp(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnLerp 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侧 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]); } // 6. 释放aclTensor和aclScalar aclDestroyTensor(self); aclDestroyTensor(end); aclDestroyTensor(weight); aclDestroyTensor(out); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(endDeviceAddr); aclrtFree(weightDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

10.2 aclnnInplaceLerp 调用示例

与普通版本的差异点:不构造 out Tensor,第一段接口传入selfRef(此处为self),计算结果直接写入self的 device 内存,因此第 5 步回拷时读取的是selfDeviceAddr,第 6 步也只需销毁 3 个 Tensor。

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_lerp_tensor.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手册 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> endShape = {4, 2}; std::vector<int64_t> weightShape = {1}; void* selfDeviceAddr = nullptr; void* endDeviceAddr = nullptr; void* weightDeviceAddr = nullptr; aclTensor* self = nullptr; aclTensor* end = nullptr; aclTensor* weight = nullptr; std::vector<float> selfHostData = {1, 2, 3, 4, 5, 6, 7, 8}; std::vector<float> endHostData = {4, 5, 6, 7, 8, 9, 10, 11}; std::vector<float> weightHostData = {2}; // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建end aclTensor ret = CreateAclTensor(endHostData, endShape, &endDeviceAddr, aclDataType::ACL_FLOAT, &end); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建weight aclTensor ret = CreateAclTensor(weightHostData, weightShape, &weightDeviceAddr, aclDataType::ACL_FLOAT, &weight); CHECK_RET(ret == ACL_SUCCESS, return ret); uint64_t workspaceSize = 0; aclOpExecutor* executor; // 3. 调用CANN算子库API // 调用aclnnInplaceLerp第一段接口 ret = aclnnInplaceLerpGetWorkspaceSize(self, end, weight, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceLerpGetWorkspaceSize 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); } // 调用aclnnInplaceLerp第二段接口 ret = aclnnInplaceLerp(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceLerp 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侧 auto size = GetShapeSize(selfShape); std::vector<float> resultData(size, 0); 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 aclDestroyTensor(self); aclDestroyTensor(end); aclDestroyTensor(weight); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(endDeviceAddr); aclrtFree(weightDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

十一、总结与选型建议

  • 需要保留原始 start 数据、或需要独立的输出 Tensor 参与后续运算时,选择aclnnLerp(多申请一份输出内存);
  • 对内存敏感、允许覆盖原始输入的场景,选择aclnnInplaceLerp,可省去输出 Tensor 的创建与释放;
  • 两者均需严格遵循两段式接口流程:第一段GetWorkspaceSize完成校验并产出 executor 与 workspaceSize,第二段申请 workspace 后执行;任何入参为空指针、类型越界/不一致、broadcast 失败或输出 shape 不匹配,都会在第一段接口返回 161001 / 161002 错误码;
  • 调用前请确认目标产品在支持列表内,并注意 Atlas 推理/训练系列产品不支持 BFLOAT16;
  • 需要进一步理解 Broadcast、返回码、两段式接口与编译运行机制的读者,可继续阅读仓库内的 broadcast 关系、aclnn 返回码、两段式接口 与 编译与运行样例 等文档,并结合 math/lerp/examples/test_aclnn_lerp.cpp 与 math/lerp/examples/test_aclnn_inplace_lerp.cpp 两个可直接运行的样例加深理解。

【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询