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

CANN ops-math 算子开发指南:aclnnLerp 与 aclnnInplaceLerp 线性插值接口详解与实战 CANN ops-math 算子开发指南aclnnLerp 与 aclnnInplaceLerp 线性插值接口详解与实战【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-mathLerpLinear Interpolation线性插值是数学基础计算算子库 CANN ops-math 中的常用算子用于在起始 Tensor 与结束 Tensor 之间按权重进行逐元素线性插值。本文以 math/lerp/docs/aclnnLerpaclnnInplaceLerp.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 中仅为ascend950、ascend350两个架构注册了 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) $$其中start、end、weight三个输入均支持 Broadcast三个 Tensor 逐元素广播对齐后按上式逐元素计算得到输出out。结合源码可以更清晰地看到这一语义的实现路径算子原语l0 层声明位于 op_api/lerp.h即l0op::Lerp(self, other, weight, executor)Host 侧原语描述 op_host/lerp_def.cpp 定义了start、end、weight三个必选输入与y一个输出形状推导 op_host/lerp_infershape.cpp 直接调用Ops::Base::InferShape4Broadcast(context, INPUT_NUM_THREE)即对 3 个输入做标准 Broadcast 推导。三、函数原型与两段式接口机制aclnnLerp和aclnnInplaceLerp实现完全相同的功能区别仅在于输出方式请根据实际场景选择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 参数说明参数名输入/输出描述使用说明数据类型数据格式维度非连续TensorselfaclTensor*输入公式中的输入 start数据类型与 end、weight、out 一致shape 需与 end、weight 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8√endaclTensor*输入公式中的输入 end数据类型与 self、weight、out 一致shape 需与 self、weight 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8√weightaclTensor*输入公式中的输入 weight数据类型与 self、end、out 一致shape 需与 self、end 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8√outaclTensor*输出公式中的 out数据类型与 self、end、weight 一致shape 需与 self、end、weight broadcast 后的 shape 一致FLOAT、FLOAT16、BFLOAT16ND0-8√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 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 无法做 broadcastACLNN_ERR_PARAM_INVALID161002self、end 和 weight 做 broadcast 后的 shape 与 out 的 shape 不一致错误码的抛出路径与源码一致CheckParams中先执行CheckNotNull失败返回ACLNN_ERR_PARAM_NULLPTR再执行CheckDtypeValid与CheckShape失败均返回ACLNN_ERR_PARAM_INVALID。五、aclnnLerp 参数详解第二段接口参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnLerpGetWorkspaceSize 获取executor输入op 执行器包含算子计算流程stream输入指定执行任务的 Stream返回值同样为aclnnStatus状态码参见 aclnn 返回码说明。六、aclnnInplaceLerpGetWorkspaceSize 参数详解6.1 参数说明参数名输入/输出描述使用说明数据类型数据格式维度非连续TensorselfRefaclTensor*输入/输出公式中的输入 start 和输出 out数据类型与 end、weight 一致shape 需与 end、weight 满足 broadcast 关系且 broadcast 后的 shape 与 selfRef 一致FLOAT、FLOAT16、BFLOAT16ND0-8√endaclTensor*输入公式中的输入 end数据类型与 selfRef、weight 一致shape 需与 selfRef、weight 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8√weightaclTensor*输入公式中的输入 weight数据类型与 selfRef、end 一致shape 需与 selfRef、end 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16ND0-8√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 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 无法做 broadcastACLNN_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 的关键链路Host 侧算子原语l0 层l0op::Lerp声明于 op_api/lerp.h在 op_api/aclnn_lerp_tensor.cpp 的CalculateResult中被调用。该函数在执行计算前做了三项准备工作对非连续输入调用l0op::Contiguous转连续、对非 ND 格式打印告警、对非连续输出调用l0op::ViewCopy将计算结果写回非连续视图。这也是文档中非连续 Tensor√能够成立的实现基础。算子原语描述与形状推导原语Lerp的定义见 op_host/lerp_def.cpp配置了DynamicCompileStaticFlag、DynamicShapeSupportFlag等动态编译能力shape 推导见 op_host/lerp_infershape.cpp即三输入 Broadcast 推导。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_KEYfloat32、UB 内最多 5 维、无需循环的 NDDMA 直通实现LERP_F32_NDDMA_WITH_LOOPS_TILING_KEYfloat32、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。测试验证ST 级用例由 tests/st/aclnnLerp/atk_aclnnLerp.json 描述覆盖 fp32 / fp16 / bf16 三种精度、从 1 维到 8 维的多种 shape以及inf、-inf、nan边界值输入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::vectorint64_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::vectorT hostData, const std::vectorint64_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::vectorint64_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::vectorint64_t selfShape {4, 2}; std::vectorint64_t endShape {4, 2}; std::vectorint64_t weightShape {1}; std::vectorint64_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::vectorfloat selfHostData {1, 2, 3, 4, 5, 6, 7, 8}; std::vectorfloat endHostData {4, 5, 6, 7, 8, 9, 10, 11}; std::vectorfloat weightHostData {2}; std::vectorfloat 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::vectorfloat 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::vectorint64_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::vectorT hostData, const std::vectorint64_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::vectorint64_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::vectorint64_t selfShape {4, 2}; std::vectorint64_t endShape {4, 2}; std::vectorint64_t weightShape {1}; void* selfDeviceAddr nullptr; void* endDeviceAddr nullptr; void* weightDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* end nullptr; aclTensor* weight nullptr; std::vectorfloat selfHostData {1, 2, 3, 4, 5, 6, 7, 8}; std::vectorfloat endHostData {4, 5, 6, 7, 8, 9, 10, 11}; std::vectorfloat 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::vectorfloat 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),仅供参考