news 2026/9/18 7:15:11

CANN ops-nn 算子库 aclnnElu / aclnnInplaceElu 接口详解:ELU 激活函数的两段式调用与源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CANN ops-nn 算子库 aclnnElu / aclnnInplaceElu 接口详解:ELU 激活函数的两段式调用与源码实现

CANN ops-nn 算子库 aclnnElu / aclnnInplaceElu 接口详解:ELU 激活函数的两段式调用与源码实现

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

本篇文章围绕 CANN 神经网络算子库(ops-nn)中的 ELU(Exponential Linear Unit,指数线性单元)激活算子展开,系统讲解aclnnEluaclnnInplaceElu两套单算子 API 的功能定义、函数原型、参数约束、返回码、两段式调用流程及完整可运行的调用示例,并结合仓库内 op_api/aclnn_elu.cpp、op_api/elu.cpp、op_kernel/arch35/elu_dag.h 等源码,深入剖析其参数校验、计算图构造与 NPU 内核实现。读完本文,你将能够独立完成 ELU 算子(含就地写入版本)的 NPU 侧编程调用,并理解其底层执行链路。

一、产品支持情况

aclnnEluaclnnInplaceElu接口在 CANN ops-nn 中的产品支持情况如下(以关联文档 activation/elu/docs/aclnnElu&aclnnInplaceElu.md 为准):

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

说明:仓库中 activation/elu/README.md 的产品支持表格将 Atlas 200I/500 A2 推理产品记录为支持,与本文档描述存在差异;实际部署时请以目标产品实测结果为准。

二、功能说明与计算公式

接口功能:对输入张量self中的每个元素x调用指数线性单元激活函数 ELU,并将计算结果写入输出张量out中。

计算公式如下:

$$ ELU(x) = \begin{cases} scale \ast x, \quad x > 0\ \alpha \ast scale \ast (exp(x \ast inputScale)-1), \quad x \leq 0 \end{cases} $$

与标准 ELU 定义相比,该接口引入了两个额外的标量参数,用于增强表达力:

  • alpha(α):负半轴的激活系数,控制负区间输出的饱和值;
  • scale:整体缩放系数,作用于正负两个区间;
  • inputScale:输入缩放系数,作用于指数内部的自变量x

当三者均取默认值 1.0 时,公式退化为经典的 ELU 形式ELU(x) = x (x>0)α·(eˣ−1) (x≤0)。在 op_host/elu_def.cpp 的算子定义中,alphascaleinput_scale三个属性均为OPTIONAL类型,默认值为1.0,与公式语义一致。

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

aclnnEluaclnnInplaceElu实现相同的功能,区别仅在于输出结果的存放方式,请根据实际场景选择:

  • aclnnElu:需要调用方新建一个输出张量对象out来存储计算结果;
  • aclnnInplaceElu:无需新建输出张量对象,直接在输入张量selfRef的内存中原地写入计算结果,节省一份输出张量的 Device 侧内存。

两者都遵循 CANN 单算子 API 的两段式接口调用范式(详见 docs/zh/context/two_phase_api.md):必须先调用第一段接口aclnnEluGetWorkspaceSize/aclnnInplaceEluGetWorkspaceSize完成入参校验、算子计算流程编排,并返回计算所需 workspace 大小以及包含算子计算流程的执行器;随后按返回的workspaceSize在 Device 侧申请 workspace 内存,再调用第二段接口aclnnElu/aclnnInplaceElu真正执行计算。

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

aclnnStatus aclnnEluGetWorkspaceSize( const aclTensor *self, const aclScalar *alpha, const aclScalar *scale, const aclScalar *inputScale, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)
aclnnStatus aclnnElu( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)
aclnnStatus aclnnInplaceEluGetWorkspaceSize( aclTensor *selfRef, const aclScalar *alpha, const aclScalar *scale, const aclScalar *inputScale, uint64_t *workspaceSize, aclOpExecutor **executor)
aclnnStatus aclnnInplaceElu( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)

注意:第二段接口不能重复调用。即GetWorkspaceSize → Execute → Execute的连续两次执行会产生异常,每次计算都应重新走一遍两段式流程。

四、aclnnEluGetWorkspaceSize 参数与返回码

4.1 参数说明

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
self(aclTensor*)输入表示ELU激活函数的输入,公式中的x支持空TensorFLOAT、FLOAT16、DOUBLE、BFLOAT16ND0-8
alpha(aclScalar*)输入表示ELU激活函数的激活系数,公式中的α数据类型需要是可转换为FLOAT的数据类型----
scale(aclScalar*)输入表示ELU激活函数的缩放系数,公式中的scale数据类型需要是可转换为FLOAT的数据类型----
inputScale(aclScalar*)输入表示ELU激活函数的输入的缩放系数,公式中的inputScale数据类型需要是可转换为FLOAT的数据类型----
out(aclTensor*)输出表示ELU激活函数的输出shape需要与self一致FLOAT、FLOAT16、DOUBLE、BFLOAT16ND0-8
workspaceSize(uint64_t*)输出返回需要在Device侧申请的workspace大小-----
executor(aclOpExecutor**)输出返回op执行器,包含了算子计算流程-----

说明:在 Atlas 训练系列产品(910 架构)上,selfout的数据类型仅支持 FLOAT、FLOAT16、DOUBLE(不含 BFLOAT16)。

对照 op_api/aclnn_elu.cpp 中的DTYPE_SUPPORT_LIST,接口层支持DT_FLOATDT_FLOAT16DT_DOUBLEDT_BF16四种类型,与上表一致。三个aclScalar参数均要求能安全转换为 FLOAT(CanCast校验),转换失败将直接返回ACLNN_ERR_PARAM_INVALID

4.2 返回值

返回aclnnStatus状态码,具体定义参见 docs/zh/context/aclnn_return_code.md。第一段接口会完成入参校验,出现以下场景时返回对应错误:

返回码错误码描述
ACLNN_ERR_PARAM_NULLPTR161001参数self、alpha、scale、inputScale、out是空指针
ACLNN_ERR_PARAM_INVALID161002参数self的数据类型不在支持的范围之内
ACLNN_ERR_PARAM_INVALID161002参数alpha、scale、inputScale的数据类型不可转换为FLOAT
ACLNN_ERR_PARAM_INVALID161002参数out的数据类型不是self可转换的
ACLNN_ERR_PARAM_INVALID161002参数self、out的shape不一致
ACLNN_ERR_PARAM_INVALID161002参数self、out的维度大于8

这些校验逻辑与源码一一对应:CheckNotNull负责空指针检查,CheckDtypeValid负责数据类型支持范围、CanCast可转换性以及self/out间的可转换关系检查,CheckShape负责 shape 一致性与最大维度(MAX_DIM_LEN = 8)检查,具体见 op_api/aclnn_elu.cpp。

五、aclnnElu 参数说明

第二段接口aclnnElu用于在 Stream 上真正执行算子计算,其参数如下:

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

返回值同样为aclnnStatus,参见 docs/zh/context/aclnn_return_code.md。从源码看,第二段接口的实现非常精简,直接调用框架能力CommonOpExecutorRun(workspace, workspaceSize, executor, stream)完成计算(见 op_api/aclnn_elu.cpp),真正的算子逻辑全部封装在执行器中。

六、aclnnInplaceEluGetWorkspaceSize 参数与返回码

6.1 参数说明

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
selfRef(aclTensor*)输入表示ELU激活函数的输入,公式中的x支持空TensorFLOAT、FLOAT16、DOUBLE、BFLOAT16ND0-8
alpha(aclScalar*)输入表示ELU激活函数的激活系数,公式中的α数据类型需要是可转换为FLOAT的数据类型----
scale(aclScalar*)输入表示ELU激活函数的激活系数,公式中的scale数据类型需要是可转换为FLOAT的数据类型----
inputScale(aclScalar*)输入表示ELU激活函数的输入的缩放系数数据类型需要是可转换为FLOAT的数据类型----
workspaceSize(uint64_t*)输出返回需要在Device侧申请的workspace大小-----
executor(aclOpExecutor**)输出返回op执行器,包含了算子计算流程-----

说明:在 Atlas 训练系列产品(910 架构)上,selfRef的数据类型仅支持 FLOAT、FLOAT16、DOUBLE。

从源码实现看,aclnnInplaceEluGetWorkspaceSizeaclnnEluGetWorkspaceSize共用同一套GetWorkspaceSizeCommon逻辑,仅将输出张量替换为输入张量自身:GetWorkspaceSizeCommon(selfRef, alpha, scale, inputScale, selfRef, ...)(见 op_api/aclnn_elu.cpp),即 inplace 版本通过"输出与输入同一地址"的方式实现原地计算。

6.2 返回值

返回aclnnStatus,参见 docs/zh/context/aclnn_return_code.md。第一段接口完成入参校验,错误场景如下:

返回码错误码描述
ACLNN_ERR_PARAM_NULLPTR161001参数selfRef、alpha、scale、inputScale是空指针
ACLNN_ERR_PARAM_INVALID161002参数selfRef的数据类型不在支持的范围之内
ACLNN_ERR_PARAM_INVALID161002参数alpha、scale、inputScale的数据类型不可转换为FLOAT
ACLNN_ERR_PARAM_INVALID161002参数selfRef的维度大于8

七、aclnnInplaceElu 参数说明

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

返回值同样为aclnnStatus,参见 docs/zh/context/aclnn_return_code.md。

八、源码级纵深:计算流程与内核实现

8.1 第一段接口构造的计算图

aclnnEluGetWorkspaceSize/aclnnInplaceEluGetWorkspaceSize内部并非直接调用单一 ELU 内核,而是通过 l0op 接口编排出一条计算流水线(见 op_api/aclnn_elu.cpp):

  1. 空 Tensor 短路:若self->IsEmpty()为真,直接返回workspaceSize = 0,不构造任何算子;
  2. Contiguousl0op::Contiguous将非连续输入规整为连续张量;
  3. Elu 主计算l0op::Elu(contiguousSelf, alpha, scale, inputScale, executor)执行元素级 ELU 计算;
  4. Castl0op::Cast将 ELU 结果转换到out的目标数据类型;
  5. ViewCopyl0op::ViewCopy将中间结果写入最终输出张量(inplace 场景下即写回selfRef)。

该计算图在 op_api/aclnn_elu.h 的注释中以 mermaid 形式给出,清晰展示了Self → Contiguous → Elu → Cast → ViewCopy → out的数据流,以及alpha/scale/inputScale三个标量作为 ELU 节点属性的依赖关系。

8.2 Inf/NaN 特判分支

GetWorkspaceSizeCommon中存在一个特殊处理分支:当alphainputScale为 Inf/NaN 时(NeedInfNanHandling判断),不再走l0op::Elu,而是改用l0op::Threshold+l0op::Muls的组合规避数值异常:先将输入与 0 比较得到-alpha的阈值结果,再整体乘以scale(见 op_api/aclnn_elu.cpp)。这是源码中为确定性计算与数值安全额外引入的保护逻辑,调用方通常无需感知。

8.3 AiCore / AiCpu 双路径分流

在 l0op 层(op_api/elu.cpp),ELU 会根据平台架构与数据类型选择执行后端:

  • AiCore 路径IsAiCoreSupport判定当前 NPU 架构支持且数据类型在DT_FLOAT/DT_FLOAT16(DAV_2201 架构或 regbase 模式下额外支持DT_BF16)时,通过ADD_TO_LAUNCHER_LIST_AICORE将算子加入 AiCore 任务队列;
  • AiCpu 路径:其余情况(如 DOUBLE 类型或架构不支持)回退到ADD_TO_LAUNCHER_LIST_AICPU,属性名映射为{"alpha", "scale", "input_scale"},对应 op_kernel_aicpu/elu_aicpu.cpp 中的 AICPU 实现。

从算子定义侧看,op_host/elu_def.cpp 为 Elu 注册了ascend950ascend350两个 AICore 配置,支持动态 shape、动态 rank,并开启PrecisionReduceFlag(精度降低标志),便于在精度与性能间取舍。

8.4 NPU 内核中的 expm1 优化

在 AiCore 侧,内核入口为 op_kernel/elu_apt.cpp,通过ElementwiseSch调度器执行 op_kernel/arch35/elu_dag.h 中定义的EluDag计算图。值得关注的是内核中对exp(x*inputScale) - 1的数值优化:

  • |z| < 0.1z = x * inputScale)时,采用Horner 形式的多项式展开近似expm1(z):FLOAT 路径取到 5 阶(系数 1/120、1/24、1/6、1/2、1),非 FLOAT 路径取到 4 阶,避免小数值下直接做exp再减 1 引入的灾难性抵消;
  • |z| ≥ 0.1时,使用硬件Exp指令计算后减 1;
  • 通过Select指令按|z|阈值选择两路结果,随后乘alpha,再按x > 0Select合并正负半轴结果,最后整体乘scale输出。

这一实现细节解释了公式中各系数在 NPU 上如何被高效、确定性地计算。

九、约束说明

  • 确定性计算aclnnEluaclnnInplaceElu默认即为确定性实现,即相同输入在多次运行下产生一致的输出结果,可参考 docs/zh/context/determinism_compute.md 了解 CANN 确定性计算的通用约定。

十、调用示例

以下示例代码仅供参考,具体编译与执行过程请参考 docs/zh/context/compile_and_run_sample.md。仓库中同时提供了可直接参考的完整样例 examples/test_aclnn_elu.cpp 与 examples/test_aclnn_inplace_elu.cpp。

10.1 aclnnElu 接口调用示例

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_elu.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); 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::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType, aclTensor** selfOrResult) { 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); // 计算连续selfOrResult的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 *selfOrResult = 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 = {2, 2}; std::vector<int64_t> outShape = {2, 2}; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclScalar* alpha = nullptr; aclScalar* scale = nullptr; aclScalar* inputScale = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {-2, -1, 0, 1}; std::vector<float> outHostData = {0, 0, 0, 0}; float alphaValue = 1.0f; float scaleValue = 1.0f; float inputScaleValue = 1.0f; // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建alpha aclScalar alpha = aclCreateScalar(&alphaValue, aclDataType::ACL_FLOAT); CHECK_RET(alpha != nullptr, return ret); // 创建scale aclScalar scale = aclCreateScalar(&scaleValue, aclDataType::ACL_FLOAT); CHECK_RET(scale != nullptr, return ret); // 创建inputScale aclScalar inputScale = aclCreateScalar(&inputScaleValue, aclDataType::ACL_FLOAT); CHECK_RET(inputScale != 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; // 调用aclnnElu第一段接口 ret = aclnnEluGetWorkspaceSize(self, alpha, scale, inputScale, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnEluGetWorkspaceSize 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); } // 调用aclnnElu第二段接口 ret = aclnnElu(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnElu 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]); } // 6. 释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyScalar(alpha); aclDestroyScalar(scale); aclDestroyScalar(inputScale); aclDestroyTensor(out); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

10.2 aclnnInplaceElu 接口调用示例

aclnnElu示例相比,inplace 版本省略了out张量的创建与outHostData,结果直接写回selfRef

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_elu.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); 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::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType, aclTensor** selfOrResult) { 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); // 计算连续selfOrResult的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 *selfOrResult = 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> selfRefShape = {2, 2}; void* selfRefDeviceAddr = nullptr; aclTensor* selfRef = nullptr; aclScalar* alpha = nullptr; aclScalar* scale = nullptr; aclScalar* inputScale = nullptr; std::vector<float> selfRefHostData = {-2, -1, 0, 1}; float alphaValue = 1.0f; float scaleValue = 1.0f; float inputScaleValue = 1.0f; // 创建selfRef aclTensor ret = CreateAclTensor(selfRefHostData, selfRefShape, &selfRefDeviceAddr, aclDataType::ACL_FLOAT, &selfRef); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建alpha aclScalar alpha = aclCreateScalar(&alphaValue, aclDataType::ACL_FLOAT); CHECK_RET(alpha != nullptr, return ret); // 创建scale aclScalar scale = aclCreateScalar(&scaleValue, aclDataType::ACL_FLOAT); CHECK_RET(scale != nullptr, return ret); // 创建inputScale aclScalar inputScale = aclCreateScalar(&inputScaleValue, aclDataType::ACL_FLOAT); CHECK_RET(inputScale != nullptr, return ret); // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnInplaceElu第一段接口 ret = aclnnInplaceEluGetWorkspaceSize(selfRef, alpha, scale, inputScale, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceEluGetWorkspaceSize 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); } // 调用aclnnInplaceElu第二段接口 ret = aclnnInplaceElu(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceElu 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(selfRefShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy( resultData.data(), resultData.size() * sizeof(resultData[0]), selfRefDeviceAddr, 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(selfRef); aclDestroyScalar(alpha); aclDestroyScalar(scale); aclDestroyScalar(inputScale); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfRefDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

对输入{-2, -1, 0, 1}alpha = scale = inputScale = 1.0时,输出结果约为{-0.8647, -0.6321, 0, 1}:正半轴元素保持不变,负半轴元素按eˣ−1计算。读者可结合 tests/ut/op_api/test_aclnn_elu.cpp 与 tests/assets/golden.py 中的期望值生成脚本进一步验证。

十一、总结与扩展阅读

aclnnElu/aclnnInplaceElu是 CANN ops-nn 中结构清晰、易于上手的单算子 API:两段式接口负责"算 workspace + 执行",inplace 变体通过输入输出同址节省显存,三个aclScalar参数使 ELU 可灵活覆盖带缩放与输入缩放的广义激活场景。理解其源码实现后,可进一步阅读:

  • docs/zh/context/two_phase_api.md:两段式接口通用范式与 workspace 概念;
  • docs/zh/context/aclnn_return_code.md:aclnn 返回码定义;
  • docs/zh/context/compile_and_run_sample.md:样例的编译与运行方法;
  • docs/zh/context/determinism_compute.md:确定性计算约定;
  • docs/zh/context/non_contiguous_tensor.md:非连续 Tensor 的支持机制;
  • activation/elu/README.md:ELU 算子的模块级总览与调用方式索引。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 7:14:53

LTE外场测试信令排查:Attach与Service Request流程深度拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 7:14:30

新型电池技术解析:能量密度与续航突破

1. 新型电池技术为何成为续航突破的关键去年冬天我带队做户外设备测试时&#xff0c;遇到个尴尬情况&#xff1a;零下15度环境下&#xff0c;某品牌标称续航20小时的监测设备&#xff0c;实际工作不到8小时就电量告急。这个案例让我深刻意识到&#xff0c;传统锂离子电池在极端…

作者头像 李华
网站建设 2026/9/18 7:13:58

Maxwell直线电机仿真:从原理到工程实践

1. 直线电机仿真概述直线电机作为一种将电能直接转换为直线运动的特殊电机&#xff0c;在精密制造、轨道交通、自动化设备等领域有着广泛应用。与传统旋转电机相比&#xff0c;直线电机省去了中间传动机构&#xff0c;具有高精度、高响应速度、低维护成本等优势。但在实际应用中…

作者头像 李华
网站建设 2026/9/18 7:10:01

pyasc 算子开发指南:load_data_with_transpose 实现带转置的 2D 数据加载

pyasc 算子开发指南&#xff1a;load_data_with_transpose 实现带转置的 2D 数据加载 【免费下载链接】pyasc 本项目为Python用户提供算子编程接口&#xff0c;支持在昇腾AI处理器上加速计算&#xff0c;接口与Ascend C一一对应并遵守Python原生语法。 项目地址: https://git…

作者头像 李华