拓冰建站拓冰建站
首页 / 资讯中心 / 正文

CANN ops-math 算子详解:aclnnLogAddExp 两段式接口原理、参数与 NPU 调用实战

算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载本文是 CANN ops-math 数学算子库中LogAddExp算子的 aclnn 接口技术指南完整讲解aclnnLogAddExp的数学定义、产品支持范围、两段式接口的函数原型、全部入参约束与错误码语义并结合仓库源码op_api / op_host / op_kernel剖析其底层调用链与实现原理。读完本文你将掌握在 Atlas 系列产品上通过 aclnn 接口编写、编译并运行 LogAddExp幂和取自然对数计算任务的完整实战方法也能理解aclnnLogAddExp2底为 2的同构用法。一、功能说明与数学原理aclnnLogAddExp的接口功能为对输入的 Tensor 逐元素计算幂和的对数以 e 为底即数值计算中广泛使用的LogAddExp算子常用于 log-sum-exp 类数值稳定化计算softmax 前向、log 域概率运算等。计算公式如下$$ out \log(e^xe^y)\log[e^m(1e^{-|x-y|})]m\log(1e^{-|x-y|}) \quad其中m\max(x,y) $$其中$x$ 代表self$y$ 代表other。推导过程的核心思路是提取最大值 $m\max(x,y)$ 作为公因子e^x e^y e^m(1 e^{-|x-y|})从而把直接计算e^x e^y可能导致的指数上溢大数与下溢小数问题转化为对e^{-|x-y|}的计算——该值恒落在(0, 1]区间内数值稳定性显著优于朴素实现。该公式即文档中所描述的计算式也是算子 kernel 与 tiling 判断采用简化公式simplified formula还是完整公式full formula的依据当base -1.0f scale 1.0f shift 0.0f即默认自然对数配置时使用简化公式否则走通用完整公式见 log_add_exp_tiling_arch35.cpp 中GetShapeAttrsInfo对base_ / scale_ / shift_的校验与useFullFormula_计算。仓库 README 说明该目录仅包含 LogAddExp 算子对应的 aclnn 接口若需贡献该算子的 AscendC 实现可参考 贡献流程。二、产品支持情况aclnnLogAddExp在如下产品上支持/不支持产品支持情况Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品支持Atlas 训练系列产品支持从源码看不同 SoC 版本对应不同的数据类型支持列表aclnn_logaddexp.cpp 中分别定义了ASCEND910_DTYPE_SUPPORT_LISTFLOAT16、FLOAT、ASCEND910B_DTYPE_SUPPORT_LISTFLOAT16、FLOAT、BF16与ASCEND950_DTYPE_SUPPORT_LISTFLOAT16、FLOAT、BF16作为输出数据类型支持列表CheckDtypeValid会根据当前平台的 SoC 版本GetCurrentPlatformInfo().GetSocVersion()选用对应列表进行校验。这与 log_add_exp_def.cpp 中 OpDef 声明的dataType {ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_BF16}保持一致。三、两段式接口与函数原型每个算子都遵循 两段式接口 规范必须先调用第一段接口aclnnLogAddExpGetWorkspaceSize获取计算所需的 workspace 大小以及包含算子计算流程的执行器executor再调用第二段接口aclnnLogAddExp执行计算。第一段接口原型aclnnStatus aclnnLogAddExpGetWorkspaceSize( const aclTensor* self, const aclTensor* other, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)第二段接口原型aclnnStatus aclnnLogAddExp( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)头文件声明位于 aclnn_logaddexp.h两个接口均以extern C导出编译时包含aclnnop/aclnn_logaddexp.h即可使用。四、aclnnLogAddExpGetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfaclTensor*输入公式中的x数据类型需要可转换成out的数据类型参见互转换关系self与other的shape满足broadcast关系FLOAT16、FLOAT、DOUBLE、UINT8、INT8、UINT16、INT16、UINT32、INT32、UINT64、INT64、BOOL、BFLOAT16ND0-8√otheraclTensor*输入公式中的y数据类型需要可转换成out的数据类型参见互转换关系other与self的shape满足broadcast关系FLOAT16、FLOAT、DOUBLE、UINT8、INT8、UINT16、INT16、UINT32、INT32、UINT64、INT64、BOOL、BFLOAT16ND0-8√outaclTensor*输出公式中的outshape需要是self与other broadcast之后的shapeFLOAT、FLOAT16、BFLOAT16ND0-8√workspaceSizeuint64_t*输出返回需要在Device侧申请的workspace大小-----executoraclOpExecutor**输出返回op执行器包含了算子计算流程-----补充限制Atlas 推理系列产品、Atlas 训练系列产品不支持 BFLOAT16 数据类型即 910/310p 平台的输出仅支持 FLOAT、FLOAT16这一点与源码中ASCEND910_DTYPE_SUPPORT_LIST仅包含DT_FLOAT16、DT_FLOAT的事实相互印证。返回值与错误码aclnnStatus为返回状态码具体参见 aclnn返回码。第一段接口完成入参校验出现如下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的self、other或out是空指针ACLNN_ERR_PARAM_INVALID161002self、other、out的数据类型不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002self和other的数据类型无法转换为out的数据类型ACLNN_ERR_PARAM_INVALID161002self、other或out的维度大于8ACLNN_ERR_PARAM_INVALID161002self和other的shape无法做broadcastACLNN_ERR_PARAM_INVALID161002out的shape和self与other broadcast之后的shape不一致以上校验在 aclnn_logaddexp.cpp 的CheckParams中逐项落地CheckNotNull3Tensor检查空指针CheckDtypeValid检查数据类型支持范围与 self/other 到 out 的可转换性CheckShape通过OP_CHECK_MAX_DIM(self, MAX_SUPPORT_DIMS_NUMS, ...)限制维度不超过 8CheckBroadcastLogAddExp调用OP_CHECK_BROADCAST与BroadcastInferShape计算 broadcast 后 shape并通过OP_CHECK_SHAPE_NOT_EQUAL_WITH_EXPECTED_SIZE校验 out 的 shape 与 broadcast 结果一致。另外CheckFormat要求 self、other 与 out 的存储格式一致且不支持私有格式仅支持 ND、NCHW、NHWC、HWCN、NDHWC、NCDHW。五、aclnnLogAddExp 参数说明参数名输入/输出描述workspace输入在Device侧申请的workspace内存地址workspaceSize输入在Device侧申请的workspace大小由第一段接口aclnnLogAddExpGetWorkspaceSize获取executor输入op执行器包含了算子计算流程stream输入指定执行任务的Stream返回值仍为aclnnStatus状态码参见 aclnn返回码。第二段接口的实现非常简洁aclnnLogAddExp在 aclnn_logaddexp.cpp 中直接调用CommonOpExecutorRun(workspace, workspaceSize, executor, stream)完成实际计算。六、约束说明确定性计算aclnnLogAddExp默认确定性实现。关于确定性计算的一般约定可参考 determinism_compute.md。七、源码级实现链路解析为了更深入理解该接口的行为下面沿仓库源码梳理aclnnLogAddExpGetWorkspaceSize的完整执行链创建执行器与入参校验CREATE_EXECUTOR()创建唯一执行器随后执行上述CheckParams校验流程。空 Tensor 快速路径当self-IsEmpty() || other-IsEmpty()时直接置*workspaceSize 0并返回成功空 Tensor 在 kernel 中支持无需分配 workspace。输入预处理分别对self与other调用l0op::Contiguous转为连续 Tensor、l0op::Cast转为 out 的数据类型——这也是文档中数据类型需要可转换成 out 的数据类型约束在实现层的体现见 aclnn_logaddexp.cpp。核心计算图调用l0op::LogAddExp(selfCasted, otherCasted, LOG_BASE, LOG_SCALE, LOG_SHIFT, executor)其中LOG_BASE -1.0f、LOG_SCALE 1.0f、LOG_SHIFT 0.0faclnn_logaddexp.cpp-1.0f的 base 即代表自然对数e 为底。输出处理l0op::Cast将计算结果转为 out 的数据类型再经l0op::ViewCopy拷贝到输出 out 上——该步骤使 out 支持非连续 Tensor。workspace 计算*workspaceSize uniqueExecutor-GetWorkspaceSize()获取计算全程所需 workspace 大小最后uniqueExecutor.ReleaseTo(executor)将执行器所有权转移给调用方。其中l0op::LogAddExp定义在 logaddexp.cpp先通过BroadcastInferShape求 broadcast 后的 shape 并AllocTensor分配中间输出若输入数据类型属于 AICore 支持列表DT_FLOAT、DT_FLOAT16、DT_BF16见 logaddexp.cpp则走LogAddExpAiCore分支通过ADD_TO_LAUNCHER_LIST_AICORE(LogAddExp, OP_INPUT(self, other), OP_OUTPUT(out), OP_ATTR(base, scale, shift))注册 AICore 算子启动。底层算子原型在 log_add_exp_def.cpp 中声明输入x1/x2REQUIRED、输出yREQUIRED、可选属性base默认 -1.0、scale默认 1.0、shift默认 0.0并配置了 ascend950 的 AICore 编译选项动态编译、支持动态 rank/shape 等其 shape 推导实现InferShape4Broadcast位于 log_add_exp_infershape.cpp。在 kernel 侧log_add_exp_apt.cpp 根据模板参数formulaType0simplified1full在 bfloat16 与普通类型间选择对应的计算 DAGLogAddExpSimplifiedCompute/LogAddExpFullCompute等并通过BroadcastSch调度框架完成带 broadcast 的逐元素计算Ascend 950 上的二进制配置bfloat16/float16/float32 三种 bin可参见 log_add_exp_binary.json。八、调用示例完整可编译示例代码参考 test_aclnn_log_add_exp.cpp具体编译与执行过程请参考 编译与运行样例。下面为完整实现自文档继承并整理为规范版#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_logaddexp.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; } 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::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::vectorfloat selfHostData {1, 2, 3, 4, 5, 6, 7, 8}; std::vectorfloat otherHostData {1, 2, 3, 4, 5, 6, 7, 8}; 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); // 创建other aclTensor ret CreateAclTensor(otherHostData, otherShape, otherDeviceAddr, aclDataType::ACL_FLOAT, other); 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); // 3. 调用CANN算子库API需要修改为具体的API名称 uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnLogAddExp第一段接口 ret aclnnLogAddExpGetWorkspaceSize(self, other, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLogAddExpGetWorkspaceSize 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); } // 调用aclnnLogAddExp第二段接口 ret aclnnLogAddExp(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLogAddExp 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::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需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyTensor(other); aclDestroyTensor(out); // 7. 释放device资源需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(otherDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }对上述示例的要点说明步骤 3 是两段式接口的标准用法先取workspaceSize与executor仅当workspaceSize 0时才调用aclrtMalloc申请 workspace 内存随后执行第二段接口示例中self/other均为{4, 2}的 FLOAT 类型out与之同 shapeoutHostData初值为 0 仅占位真正结果由算子写入。内存生命周期selfDeviceAddr、otherDeviceAddr、outDeviceAddr以及条件申请的workspaceAddr均需在结束时通过aclrtFree释放示例第 6、7 步展示了aclTensor与设备资源的完整释放顺序。若输入为self ! other的广播场景如selfShape{4,2}、otherShape{2}则outShape必须是 broadcast 后的{4,2}否则第一段接口会以 161002 报错。九、同族算子 aclnnLogAddExp2LogAddExp算子族还提供底为 2 的版本aclnnLogAddExp2其完整文档见 aclnnLogAddExp2.md。两者在产品支持情况、参数结构、两段式接口模式、错误码语义上完全一致仅计算公式不同$$ out \log_2(2^x2^y)\log_2[2^m(12^{-|x-y|})]m\log_2(12^{-|x-y|}) \quad其中m\max(x,y) $$调用时头文件替换为aclnnop/aclnn_logaddexp2.h接口名替换为aclnnLogAddExp2GetWorkspaceSize/aclnnLogAddExp2即可。对应的示例程序见 test_aclnn_log_add_exp2.cppOP API 实现见 aclnn_logaddexp2.cpp。十、测试与验证仓库为该算子提供了完整的测试配套可用于验证本文所述行为ATK 自动化测试基于 ATKAscend Test Kit框架的用例定义于 atk_aclnnLogAddExp.json执行脚本 executor_aclnnLogAddExp.py 中通过register(aclnn_log_add_exp)注册自定义 API 调用方式并将 FLOAT16/BFLOAT16/FLOAT64 及各整型、BOOL 等输入统一转换为 FLOAT32 后执行算子随后与 PyTorch 参考结果对比。UT 单元测试test_aclnn_log_add_exp.cpp、test_logaddexp.cpp、test_logaddexp2.cpp 覆盖接口与底层 l0op 的单元级验证。总结aclnnLogAddExp是 CANN ops-math 中实现幂和取自然对数的标准 aclnn 接口采用两段式GetWorkspaceSize 执行设计第一段接口完成全部入参校验空指针、dtype 支持范围、互转换关系、0-8 维约束、broadcast 关系与输出 shape 一致性并计算 workspace第二段接口基于 executor 在指定 Stream 上执行。从源码实现看其核心计算通过Contiguous → Cast → LogAddExp → Cast → ViewCopy的计算图完成底层由 AICore kernel 以提取最大值 m 的数值稳定公式实现并针对默认自然对数配置使用简化公式。配合示例代码与 ATK/UT 测试开发者可以快速在支持的 Atlas 产品上完成从调用、验证到集成的全流程。赞分享算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载相关推荐CANN ops-math Digamma 算子详解aclnnDigamma 两段式接口原理、参数与实战调用CANN ops math Digamma 算子详解aclnnDigamma 两段式接口原理、参数与实战调用 Digamma双伽马函数是数学类基础算子库算子库人工智能CANNCANN ops-math Neg 取反算子详解aclnnNeg / aclnnInplaceNeg 两段式接口原理与 NPU 调用实战CANN ops math Neg 取反算子详解aclnnNeg / aclnnInplaceNeg 两段式接口原理与 NPU 调用实战 Neg取反算子是算子库人工智能CANNCANN ops-math 算子 aclnnCumsum 两段式接口详解功能、参数校验与 NPU 调用实战CANN ops math 算子 aclnnCumsum 两段式接口详解功能、参数校验与 NPU 调用实战 本文以 CANN ops math 仓库中 mat算子库人工智能CANN上一篇AltStore调试工具完全指南终极利器助你提升iOS开发效率 300%下一篇Stable Video Infinity学术影响分析被引情况与相关研究进展深度解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门