
CANN ops-math Less 算子 aclnnLtTensor 与 aclnnInplaceLtTensor 接口调用指南【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math本篇技术指南以 CANN ops-math 开源仓库中experimental/math/less目录下的 Less 算子为核心完整讲解其两个 aclnn 单算子 API ——aclnnLtTensor非原地版本与aclnnInplaceLtTensor原地版本的接口定义、参数约束、错误码、两段式调用流程与可运行的完整示例代码。读完本文你将掌握如何在 Atlas A2 系列产品上通过 aclnn 接口完成逐元素小于比较计算理解 workspace 申请机制与原地/非原地两种调用的取舍并能结合仓库源码看懂算子内部的类型推导、广播、Contiguous/Cast/ViewCopy 等底层实现链路。算子功能与产品支持aclnnLtTensor与aclnnInplaceLtTensor实现的是 Less逐元素小于比较算子。接口功能为判断输入self中的每个元素是否小于输入other中的元素返回一个 Bool 类型的 Tensor。其计算公式为$$ out_i (self_i other_i) ? [True] : [False] $$即输出张量中第 i 个元素的取值为当self[i] other[i]成立时为True否则为False。该算子的产品支持情况如下产品是否支持Atlas A2 训练系列产品/Atlas A2 推理系列产品√说明算子对应的 aclnn 接口所属的 Less 算子同时提供 Scalar 版本接口aclnnLtScalar/aclnnInplaceLtScalar相关文档见 aclnnLtScalaraclnnInplaceLtScalar.md本文聚焦 Tensor 版本。从 Less 算子 README 可以了解到该算子的贡献信息Less 算子由浙江工业大学杨旭华团队于 2025/7/30 首次新增并由个人开发者 fulltower 于 2026/2/27 完成算子适配开源仓属于社区贡献的数学类基础计算算子。函数原型两段式接口aclnnLtTensor和aclnnInplaceLtTensor实现相同的计算功能二者使用区别如下请根据自身实际场景选择合适的接口aclnnLtTensor需新建一个输出张量对象存储计算结果。aclnnInplaceLtTensor无需新建输出张量对象直接在输入张量的内存中存储计算结果。每个算子都采用两段式接口设计必须先调用aclnnLtTensorGetWorkspaceSize或者aclnnInplaceLtTensorGetWorkspaceSize接口获取计算所需 workspace 大小以及包含了算子计算流程的执行器executor再调用aclnnLtTensor或者aclnnInplaceLtTensor接口执行计算。四个接口的原型如下aclnnStatus aclnnLtTensorGetWorkspaceSize(const aclTensor *self, const aclTensor *other, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor) aclnnStatus aclnnLtTensor(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream) aclnnStatus aclnnInplaceLtTensorGetWorkspaceSize(const aclTensor *selfRef, const aclTensor *other, uint64_t *workspaceSize, aclOpExecutor **executor) aclnnStatus aclnnInplaceLtTensor(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)其中第一段接口xxxGetWorkspaceSize负责完成入参校验、算子流程编排与 workspace 大小计算第二段接口xxx接收第一段返回的 workspace 与 executor在指定 stream 上真正执行计算。关于两段式接口的通用机制可参考 两段式接口说明workspace 是指除输入/输出外算子在 NPU 上完成计算所需要的临时内存第二段接口不能重复调用同一 executor 只能执行一次。aclnnLtTensorGetWorkspaceSize 参数说明第一段接口的入参/出参如下参数说明selfaclTensor*计算输入Device 侧的 aclTensor数据类型需要与 other 满足数据类型推导规则参见互推导关系shape 需要与 other 满足 broadcast 关系shape 维度不高于 8 维。支持非连续的 Tensor数据格式支持 ND。数据类型支持 FLOAT、FLOAT16、BFLOAT16、INT32、UINT32、INT64、UINT64、INT16、UINT16、INT8、UINT8、DOUBLE、BOOL。otheraclTensor*计算输入Device 侧的 aclTensor数据类型需要与 self 满足数据类型推导规则参见互推导关系shape 需要与 self 的 shape 满足 broadcast 关系shape 维度不高于 8 维。支持非连续的 Tensor数据格式支持 ND。数据类型支持 FLOAT、FLOAT16、BFLOAT16、INT32、UINT32、INT64、UINT64、INT16、UINT16、INT8、UINT8、DOUBLE、BOOL。outaclTensor*计算输出Device 侧的 aclTensor数据类型需要是 BOOL 可转换的数据类型参见互转换关系shape 与 self、other 广播之后的 shape参见 broadcast 关系一致shape 维度不高于 8 维支持非连续的 Tensor数据格式支持 ND。数据类型支持 FLOAT、FLOAT16、BFLOAT16、INT32、UINT32、INT64、UINT64、INT16、UINT16、INT8、UINT8、DOUBLE、BOOL、COMPLEX64、COMPLEX128。workspaceSizeuint64_t*出参返回用户需要在 Device 侧申请的 workspace 大小。executoraclOpExecutor**出参返回 op 执行器包含了算子计算流程。返回值与错误码第一段接口返回aclnnStatus状态码具体参见 aclnn 返回码。第一段接口完成入参校验出现以下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self、other 或 out 是空指针。ACLNN_ERR_PARAM_INVALID161002self、other 或 out 的数据类型不在支持的范围之内。ACLNN_ERR_PARAM_INVALID161002self、other 或 out 的维度大于 8。ACLNN_ERR_PARAM_INVALID161002self 和 other 的数据类型无法进行推导。ACLNN_ERR_PARAM_INVALID161002self 和 other 的 shape 无法进行 broadcast。ACLNN_ERR_PARAM_INVALID161002out 的 shape 与 broadcast 后的 shape 不一致。源码视角参数校验的执行顺序从源码 aclnn_lt_tensor.cpp 可以看到aclnnLtTensorGetWorkspaceSize内部通过CheckParams按如下顺序完成四步校验CheckNotNull检查 self、other、out 是否为空指针失败返回ACLNN_ERR_PARAM_NULLPTRCheckDtypeValid检查输入数据类型是否在 API 支持列表内校验逻辑见 aclnn_lt_tensor.cpp。源码中按 SoC 平台区分了两套支持列表ASCEND910_DTYPE_SUPPORT_LIST与ASCEND910B_DTYPE_SUPPORT_LIST后者额外包含 BF16GetDtypeSupportList根据当前平台 SoC 版本ASCEND910B~ASCEND910E动态选择输出 out 的类型列表则对应ASCEND910_OUT_DTYPE_SUPPORT_LIST/ASCEND910B_OUT_DTYPE_SUPPORT_LIST其中包含了 COMPLEX64、COMPLEX128与文档中 out 的数据类型支持范围一致CheckPromoteType通过op::PromoteType推导 self 与 other 的公共数据类型若推导结果为DT_UNDEFINED则报ACLNN_ERR_PARAM_INVALIDaclnn_lt_tensor.cpp同时校验算子内部结果类型 BOOL 能否 Cast 到 out 的类型CheckShape校验 self、other 维度不超过 8 维MAX_DIM_LEN 8并通过OP_CHECK_BROADCAST_AND_INFER_SHAPE计算广播后的 shape要求与 out 的 view shape 完全一致aclnn_lt_tensor.cpp。aclnnLtTensor 参数说明第二段接口aclnnLtTensor的参数如下参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址。workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnLtTensorGetWorkspaceSize 获取。executor输入op 执行器包含了算子计算流程。stream输入指定执行任务的 Stream。返回值aclnnStatus返回状态码具体参见 aclnn 返回码。从实现上看第二段接口直接调用框架的CommonOpExecutorRun(workspace, workspaceSize, executor, stream)完成异步计算见 aclnn_lt_tensor.cpp。aclnnInplaceLtTensorGetWorkspaceSize 参数说明原地版本的输入参数 selfRef 同时承担输入与输出两个角色参数说明selfRefaclTensor*计算输入|计算输出输入输出 tensor即公式中的 self 与 out。Device 侧的 aclTensor输入数据类型需要与 other 满足数据类型推导规则参见互推导关系shape 需要与 other 满足 broadcast 关系且 broadcast 后的 shape 需要与 selfRef 的 shape 一致。支持非连续的 Tensor数据格式支持 ND。数据类型支持 FLOAT、FLOAT16、INT32、INT64、INT16、INT8、UINT8、DOUBLE、UINT16、UINT32、UINT64、BOOL、BFLOAT16。otheraclTensor*计算输入Device 侧的 aclTensor数据类型需要与 selfRef 满足数据类型推导规则参见互推导关系shape 需要与 self 满足 broadcast 关系且 broadcast 后的 shape 需要与 selfRef 的 shape 一致。支持非连续的 Tensor数据格式支持 ND。数据类型支持 FLOAT、FLOAT16、INT32、INT64、INT16、INT8、UINT8、DOUBLE、UINT16、UINT32、UINT64、BOOL、BFLOAT16。workspaceSizeuint64_t*出参返回需要在 Device 侧申请的 workspace 大小。executoraclOpExecutor**出参返回 op 执行器包含了算子计算流程。注意原地版本要求广播后的 shape 必须与 selfRef 的 shape 完全一致。这是因为结果要写回 selfRef 所在的内存如果 other 被广播扩展而 selfRef 的 shape 小于广播结果就无法原地写入。返回值与错误码返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 selfRef、other 是空指针时。ACLNN_ERR_PARAM_INVALID161002selfRef 和 other 的数据类型不在支持的范围之内。ACLNN_ERR_PARAM_INVALID161002selfRef 和 other 的数据类型无法进行推导。ACLNN_ERR_PARAM_INVALID161002selfRef 和 other 的 shape 无法做 broadcast。ACLNN_ERR_PARAM_INVALID161002selfRef 和 other 做 broadcast 后的 shape 不等于 selfRef 的 shape。ACLNN_ERR_PARAM_INVALID161002selfRef、other 的维度大于 8。源码视角原地版本的本质是复用从源码可以看出原地版本并未重复实现一套计算逻辑而是将 selfRef 直接作为 out 复用非原地版本的完整流程aclnn_lt_tensor.cppaclnnStatus aclnnInplaceLtTensorGetWorkspaceSize( const aclTensor* selfRef, const aclTensor* other, uint64_t* workspaceSize, aclOpExecutor** executor) { auto out const_castaclTensor*(selfRef); return aclnnLtTensorGetWorkspaceSize(selfRef, other, out, workspaceSize, executor); }也就是说aclnnInplaceLtTensorGetWorkspaceSize只是把selfRef同时当作self与out传给aclnnLtTensorGetWorkspaceSize因此它天然继承了非原地版本全部的参数校验逻辑只是不再需要单独创建输出张量。这也是原地版本要求broadcast 后的 shape 等于 selfRef 的 shape的直接原因。aclnnInplaceLtTensor 参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址。workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnInplaceLtTensorGetWorkspaceSize 获取。executor输入op 执行器包含了算子计算流程。stream输入指定执行任务的 Stream。返回值aclnnStatus返回状态码具体参见 aclnn 返回码。实现上同样委托给CommonOpExecutorRun完成计算aclnn_lt_tensor.cpp。源码级实现原理一次 aclnnLtTensor 调用在 NPU 上发生了什么aclnnLtTensorGetWorkspaceSize的第一段实现aclnn_lt_tensor.cpp在完成参数校验后会通过 l0op 原语把算子的完整计算流程编排成一个执行图。源码注释给出了如下流程self other | | \ / Contiguous(workspace_0) Contiguous(workspace_2) \ / Cast(workspace_1) Cast(workspace_3) \ / Less(workspace_4) | Cast(workspace_5) | ViewCopy | result各步骤含义如下空 tensor 快速路径若self或other为空 tensorIsEmpty()直接返回workspaceSize 0无需任何计算aclnn_lt_tensor.cpp类型推导调用op::PromoteType得到 self 与 other 的公共类型作为隐式计算类型特殊地当推导结果为 BOOL 时会被提升为 UINT8promoteType DataType::DT_BOOL时置为DT_UINT8见 aclnn_lt_tensor.cppContiguous对 self 与 other 分别调用l0op::Contiguous得到连续内存布局的中间张量用于屏蔽非连续 Tensor 带来的访存差异Cast将两个输入分别 Cast 到推导出的公共数据类型保证 kernel 侧输入类型一致Less调用l0op::Less执行逐元素比较产生 BOOL 类型的中间结果Cast将 BOOL 结果 Cast 到 out 声明的数据类型例如示例中的 DOUBLEViewCopy将结果拷贝到 out 上兼容 out 为非连续 Tensor 的场景。最后通过uniqueExecutor-GetWorkspaceSize()汇总整个流程所需的临时内存总量并返回。所有中间张量workspace_0 ~ workspace_5都驻留在 Device 侧 workspace 中这正是第一段接口必须返回 workspaceSize 的原因。kernel 侧多核并行与 tiling第一段接口编排完毕后真正在 AICore 上执行的 kernel 入口位于 op_kernel/less.cpp。kernel 通过REGISTER_TILING_DEFAULT(LessTilingData)注册并解析 tiling 数据tiling 在 host 侧由 less_tiling.cpp 完成并根据isTailBlock标志区分尾块与非尾块的处理非尾块isTailBlock 0使用NsLess::LessDTYPE_X1, DTYPE_X2, DTYPE_Y, false尾块isTailBlock 1使用NsLess::LessDTYPE_X1, DTYPE_X2, DTYPE_Y, true。从 tiling 数据结构bigCoreDataNum、smallCoreDataNum、bigCoreLoopNum、smallCoreLoopNum、ubPartDataNum、tailDataNum 等字段可以看出kernel 采用了大核/小核big/small core数据切分、UB 分块、循环分批、尾数据处理的多级并行策略以适配 Atlas A2 系列产品的多核架构。而 host 侧的 shape 推导less_infershape.cpp实现较为直接输出 shape 直接取 x1self的 shape实际广播后的最终 shape 约束由 aclnn 层CheckShape保证。调用示例示例代码如下仅供参考具体编译和执行过程请参考编译与运行样例。仓库中对应的可运行测试样例位于 examples/test_aclnn_lt_tensor.cpp 与 examples/test_aclnn_inplace_lt_tensor.cpp。aclnnLtTensor 示例代码该示例中self 与 other 均为 shape{4, 2}的 DOUBLE 张量self 取{0,1,2,3,4,5,6,7}other 全取5计算结果写入独立的 out 张量#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_lt_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); 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::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; } struct LtTensorData { std::vectorint64_t selfShape {4, 2}; std::vectorint64_t otherShape {4, 2}; std::vectorint64_t outShape {4, 2}; void* selfDeviceAddr nullptr; void* otherDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* other nullptr; aclTensor* out nullptr; std::vectordouble selfHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectordouble otherHostData {5, 5, 5, 5, 5, 5, 5, 5}; std::vectordouble outHostData {0, 0, 0, 0, 0, 0, 0, 0}; void* workspaceAddr nullptr; uint64_t workspaceSize 0; }; int CreateInputAndOutputTensors(LtTensorData data) { auto ret 0; // 创建self aclTensor ret CreateAclTensor(data.selfHostData, data.selfShape, data.selfDeviceAddr, aclDataType::ACL_DOUBLE, data.self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建other aclTensor ret CreateAclTensor(data.otherHostData, data.otherShape, data.otherDeviceAddr, aclDataType::ACL_DOUBLE, data.other); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建out aclTensor ret CreateAclTensor(data.outHostData, data.outShape, data.outDeviceAddr, aclDataType::ACL_DOUBLE, data.out); CHECK_RET(ret ACL_SUCCESS, return ret); return ret; } int ExecuteLtTensorComputation(aclrtStream stream, LtTensorData data) { auto ret 0; aclOpExecutor* executor; // 调用aclnnLtTensor第一段接口 ret aclnnLtTensorGetWorkspaceSize(data.self, data.other, data.out, data.workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLtTensorGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 data.workspaceAddr nullptr; if (data.workspaceSize 0) { ret aclrtMalloc(data.workspaceAddr, data.workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(allocate workspace failed. ERROR: %d\n, ret); return ret); } // 调用aclnnLtTensor第二段接口 ret aclnnLtTensor(data.workspaceAddr, data.workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLtTensor failed. ERROR: %d\n, ret); return ret); // 同步等待任务执行结束 ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSynchronizeStream failed. ERROR: %d\n, ret); return ret); return ret; } int ProcessAndPrintResults(const LtTensorData data) { auto ret 0; auto size GetShapeSize(data.outShape); std::vectordouble resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), data.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: %lf\n, i, resultData[i]); } return ret; } void ReleaseResources(LtTensorData data) { // 释放aclTensor和aclScalar aclDestroyTensor(data.self); aclDestroyTensor(data.other); aclDestroyTensor(data.out); // 释放device资源 aclrtFree(data.selfDeviceAddr); aclrtFree(data.otherDeviceAddr); aclrtFree(data.outDeviceAddr); if (data.workspaceSize 0) { aclrtFree(data.workspaceAddr); } } int ExecuteLtTensorOperator(aclrtStream stream) { LtTensorData data; // 创建输入和输出张量 auto ret CreateInputAndOutputTensors(data); CHECK_RET(ret ACL_SUCCESS, return ret); // 执行LtTensor算子操作 ret ExecuteLtTensorComputation(stream, data); CHECK_RET(ret ACL_SUCCESS, return ret); // 处理并打印结果 ret ProcessAndPrintResults(data); CHECK_RET(ret ACL_SUCCESS, return ret); // 释放资源 ReleaseResources(data); return 0; } int main() { 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); // 执行InplaceLtScalar操作 ret ExecuteLtTensorOperator(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(ExecuteInplaceLtScalarOperator failed. ERROR: %d\n, ret); return ret); // 重置设备和终结ACL aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }对上述代码的调用链路做简单梳理便于理解整体骨架Init固定写法完成aclInit、aclrtSetDevice、aclrtCreateStream三个初始化步骤CreateAclTensor模板函数完成 device 内存申请aclrtMalloc、host 数据拷贝aclrtMemcpyH2D、连续 strides 计算最终通过aclCreateTensor创建 aclTensorExecuteLtTensorComputation核心两段式流程——第一段aclnnLtTensorGetWorkspaceSize取 workspaceSize 与 executor按需aclrtMalloc申请 workspace第二段aclnnLtTensor执行最后aclrtSynchronizeStream同步等待ProcessAndPrintResults通过aclrtMemcpyD2H把结果拷回 host 并打印ReleaseResources依次释放 aclTensor、device 内存与 workspacemain编排整个流程结束后销毁 stream、重置设备并aclFinalize。aclnnInplaceLtTensor 示例代码原地版本省去了 out 张量的创建计算结果直接写回 self 所在内存。示例中 self 为 DOUBLE 类型、shape{4, 2}other 为 INT32 类型、shape{4, 2}取值为{1,1,1,1,0,0,0,0}演示了两种不同类型输入经过类型推导后仍可正常计算#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_lt_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 shape_size 1; for (auto i : shape) { shape_size * i; } return shape_size; } 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; } templatetypename 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 ExecuteInplaceLtTensorOperator(aclrtStream stream) { auto ret 0; std::vectorint64_t selfShape {4, 2}; std::vectorint64_t otherShape {4, 2}; void* selfDeviceAddr nullptr; void* otherDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* other nullptr; std::vectordouble selfHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectorint otherHostData {1, 1, 1, 1, 0, 0, 0, 0}; ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_DOUBLE, self); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(otherHostData, otherShape, otherDeviceAddr, aclDataType::ACL_INT32, other); CHECK_RET(ret ACL_SUCCESS, return ret); uint64_t workspaceSize 0; aclOpExecutor* executor; ret aclnnInplaceLtTensorGetWorkspaceSize(self, other, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnInplaceLtTensorGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); 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); } ret aclnnInplaceLtTensor(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnInplaceLtTensor failed. ERROR: %d\n, ret); return ret); ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSynchronizeStream failed. ERROR: %d\n, ret); return ret); auto size GetShapeSize(selfShape); std::vectordouble 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: %lf\n, i, resultData[i]); } aclDestroyTensor(self); aclDestroyTensor(other); aclrtFree(selfDeviceAddr); aclrtFree(otherDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } return 0; } int main() { 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); // 执行InplaceLtScalar操作 ret ExecuteInplaceLtTensorOperator(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(ExecuteInplaceLtScalarOperator failed. ERROR: %d\n, ret); return ret); // 重置设备和终结ACL aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }与第一个示例对比可以发现原地版本的核心差异在于不创建 out 张量第一段接口只传 selfRef 与 other第二段接口执行完毕后直接从selfDeviceAddr读取结果。这样可以减少一次张量对象创建与一次结果回写适合在调用方允许覆盖原输入的场景下使用能够节省 Device 侧显存。编译与运行编译运行 aclnn 接口样例前请先确保基础环境已搭建完成包括驱动、固件、CANN 软件包、ops 包等具体编译与运行步骤参见编译与运行样例。这里以合设场景开发和运行环境在同一台带 AI 处理器的机器上为例给出 CMake 编译脚本的关键要素cmake_minimum_required(VERSION 3.14) project(ACLNN_EXAMPLE) add_compile_options(-stdc11) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ./bin) set(CMAKE_CXX_FLAGS_DEBUG -fPIC -O0 -g -Wall) set(CMAKE_CXX_FLAGS_RELEASE -fPIC -O2 -Wall) # 设置可执行文件名并指定待运行算子文件*.cpp所在目录 add_executable(opapi_test test_aclnn_lt_tensor.cpp) # 设置ASCEND_PATHCANN软件包目录请根据实际路径修改和INCLUDE_BASE_DIR头文件目录 if(NOT $ENV{ASCEND_CUSTOM_PATH} STREQUAL ) set(ASCEND_PATH $ENV{ASCEND_CUSTOM_PATH}) else() set(ASCEND_PATH /usr/local/Ascend/cann) endif() set(INCLUDE_BASE_DIR ${ASCEND_PATH}/include) include_directories( ${INCLUDE_BASE_DIR} ${INCLUDE_BASE_DIR}/aclnn ) # 设置链接的库文件路径 target_link_libraries(opapi_test PRIVATE ${ASCEND_PATH}/lib64/libascendcl.so ${ASCEND_PATH}/lib64/libnnopbase.so ${ASCEND_PATH}/lib64/libopapi_math.so) install(TARGETS opapi_test DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})要点说明头文件路径需要同时包含 CANN 的 include 根目录与include/aclnn子目录示例代码中引用的aclnnop/aclnn_lt_tensor.h即来自该目录链接时需要引入libascendcl.soACL 运行时、libnnopbase.soNPU 算子基础库与libopapi_math.so数学类算子 API 库Less 算子属于 math 域算子编译产物默认输出到./bin目录运行前需保证环境变量如LD_LIBRARY_PATH、ASCEND_OPP_PATH等已按 CANN 安装要求配置好。小结aclnnLtTensor与aclnnInplaceLtTensor是 CANN ops-math 中 Less逐元素小于比较算子的标准 aclnn 调用入口。本文完整覆盖了两个接口的四个函数原型、参数与数据类型约束、错误码表、两段式调用流程与两份可运行示例代码并结合 aclnn_lt_tensor.cpp 源码剖析了 Contiguous → Cast → Less → Cast → ViewCopy 的底层计算链路、原地版本复用非原地版本的实现技巧以及 op_kernel/less.cpp 中基于 tiling 的多核并行机制。实际使用时只需记住两条核心准则先调 GetWorkspaceSize 获取 workspace 并申请内存再调执行接口完成计算需要保留原输入时选非原地版本允许覆盖输入、想节省显存时选原地版本。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考