
CANN ops-nn aclnnFastGelu 算子使用指南FastGelu 两段式接口调用、参数校验与 NPU 实现解析【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn导读本文面向在 CANN 昇腾 NPU 上调用快速高斯误差线性单元FastGelu的开发者围绕 aclnnFastGelu.md 这一算子 API 文档展开。文章将完整讲解 aclnnFastGelu 的产品支持范围、数学定义、两段式接口GetWorkspaceSize 与执行接口的原型与参数约束、返回码报错场景并给出可直接编译运行的最小调用示例。在此基础上结合 ops-nn 仓库中 op_api、op_host、op_kernel 的源码深入剖析其参数校验流程、非连续 Tensor 处理以及底层 Kernel 的计算图实现帮助读者既会用、又懂原理。阅读本文后你将能够在自己的工程中独立完成 aclnnFastGelu 的申请、执行、同步与资源释放全流程。产品支持情况根据 aclnnFastGelu.md 与算子 READMEREADME.md中的声明aclnnFastGelu 在不同昇腾产品上的支持情况如下产品是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品310P 系列不支持Atlas 训练系列产品910 系列不支持从源码结构上看该结论与 op_host/fast_gelu_def.cpp 中仅向ascend950与ascend350对应 A3/A2 系列两个架构注册 AICore 配置的事实一致ascend910、ascend310等旧架构未注册算子配置因此在这些平台上不可用。功能说明FastGelu 的数学定义aclnnFastGelu 实现的是快速高斯误差线性单元FastGelu激活函数其核心思想是用一个包含 sigmoid 形式的近似公式替代 GELU 中的标准正态分布累积函数erf从而在保证精度的同时减少计算开销。算子文档给出的定义如下$$ FastGelu(x_i) \frac {x_i} {1 e^{-1.702 x_i}} $$其中 $x_i$ 为输入张量self中的元素输出out中对应位置的元素即为 $FastGelu(x_i)$。值得注意的是API 头文件aclnn_fast_gelu.h与算子 README 中还给出了一个等价的带绝对值分段形式$$ FastGelu(x_i) \frac {x_i} {1 \exp(-1.702 \cdot |x_i|)} \cdot \exp(0.851 \cdot (x_i - |x_i|)) $$该形式在数值上等价于上述 sigmoid 形式但在负数区间通过0.851*(x_i-|x_i|)的指数衰减补偿可以改善中间计算精度是 Kernel 层实际采用的计算形态详见下文“底层 Kernel 实现”一节。函数原型与两段式接口与其他 aclnn 算子一样aclnnFastGelu 采用两段式接口设计参见 两段式接口说明第一段aclnnFastGeluGetWorkspaceSize负责入参校验、构建算子计算流程并计算 workspace 大小第二段aclnnFastGelu真正在指定 stream 上执行计算。调用方必须先调用第一段再调用第二段。第一段接口原型aclnnStatus aclnnFastGeluGetWorkspaceSize( const aclTensor *self, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)第二段接口原型aclnnStatus aclnnFastGelu( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)aclnnFastGeluGetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfaclTensor*输入输入张量公式中的 $x_i$数据格式、shape、数据类型均需与 out 一致FLOAT16、FLOAT32、BFLOAT16ND0-8支持outaclTensor*输出输出张量公式中的 $FastGelu(x_i)$数据格式、shape、数据类型均需与 self 一致FLOAT16、FLOAT32、BFLOAT16ND0-8支持workspaceSizeuint64_t*输出需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出算子执行器包含算子计算流程-----关于 shape 为 0-8 维的说明这是算子定义的最大支持维度上限实际调用时 shape 的维度从 0 到 8 均合法0 维即标量。源码中通过OP_CHECK_MAX_DIM(self, MAX_SUPPORT_DIMS_NUMS, ...)对维度进行上限校验详见下文参数校验小节。aclnnFastGeluGetWorkspaceSize 返回值与报错场景第一段接口返回aclnnStatus状态码通用返回码说明见 aclnn 返回码。第一段接口在入参校验阶段遇到以下场景会报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001self 或 out 是空指针ACLNN_ERR_PARAM_INVALID161002self 与 out 的数据类型、数据格式不一致ACLNN_ERR_PARAM_INVALID161002self 或 out 的数据类型不在支持的范围内ACLNN_ERR_PARAM_INVALID161002self 与 out 的 shape 不一致ACLNN_ERR_PARAM_INVALID161002self 的 shape 维度大于 8 维aclnnFastGelu 参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnFastGeluGetWorkspaceSize 获取executor输入算子执行器包含算子计算流程stream输入指定执行任务的 Stream第二段接口同样返回aclnnStatus状态码通用返回码参见 aclnn 返回码。约束说明确定性计算aclnnFastGelu 默认采用确定性实现即相同的输入在多次运行中产生一致的结果便于调试与结果比对不会引入因并行归约顺序等导致的运行间差异。源码视角第一段接口的内部流程深入 op_api/aclnn_fast_gelu.cpp可以看到aclnnFastGeluGetWorkspaceSize的完整内部处理链路它与文档中的参数约束一一对应公共入参检查通过OP_CHECK_COMM_INPUT(workspaceSize, executor)检查输出指针非空。参数校验CheckParams依次完成四类校验CheckNotNullOP_CHECK_NULL检查 self、out 非空对应返回码ACLNN_ERR_PARAM_NULLPTR161001CheckDtypeValid先OP_CHECK_DTYPE_NOT_SAME保证 self 与 out 数据类型一致再通过GetDtypeSupportList()检查数据类型是否在{DT_FLOAT, DT_FLOAT16, DT_BF16}支持列表内。该列表的获取与平台相关仅在 Regbase 模式或 DAV_2201A2/A3 新架构下返回带 BF16 的支持列表否则返回空列表这正是旧架构平台不支持该算子的直接证据aclnn_fast_gelu.cppCheckShapeOP_CHECK_SHAPE_NOT_EQUAL保证 shape 一致OP_CHECK_MAX_DIM(self, MAX_SUPPORT_DIMS_NUMS)保证维度不超过 8 维CheckFormat比较 self 与 out 的GetStorageFormat()是否一致。空 Tensor 处理若self-IsEmpty()直接返回workspaceSize 0并释放执行器无需真实计算。非连续 Tensor 适配当 self 非连续时先通过l0op::Contiguous将其转为连续张量计算完成后若 out 为非连续张量再通过l0op::ViewCopy将结果写回 out 的原始视图。这就是文档中“非连续 Tensor支持”的实现机制。获取 workspace 大小通过uniqueExecutor-GetWorkspaceSize()汇总计算流程所需的 Device 侧 workspace 大小并返回。第二段接口aclnnFastGelu的实现则非常精简仅调用CommonOpExecutorRun(workspace, workspaceSize, executor, stream)执行器完成实际计算aclnn_fast_gelu.cpp。底层 Kernel 实现计算图与数值精度算子底层 Kernel 采用昇腾的 Elementwise 调度框架实现位于 op_kernel/fast_gelu_apt.cpp以schMode调度模式与dType数据类型作为模板参数在编译期根据数据类型实例化三种计算图FP32FastGeluNoCastfloat全程以 float 精度直接计算FP16 / BF16FastGeluNeedCasthalf / bfloat16_t先Cast到 float 计算计算完再Cast回原类型以保证中间精度。具体的逐元素计算图定义在 op_kernel/arch35/fast_gelu_dag.h对每个元素寄存器级计算序列为Muls(denominator, x, -1.702)→Exp(denominator)→Adds(denominator, 1.0)→Div(result, x, denominator)即result x / (Exp(-1.702 * x) 1)对于 FP16/BF16 路径FastGeluNeedCast在 CopyIn 之后、计算之前插入Castfloat, T, 0在计算之后、CopyOut 之前插入CastT, float, 1计算图通过CopyIn → FastGelu → CastOut → CopyOut的 DAG 形式组织配合ElementwiseSch完成向量化流水调度并在循环内按向量寄存器宽度VECTOR_REG_WIDTH / sizeof(T)分块处理处理不足一整块的尾部时用UpdateMask生成掩码。Kernel 的数据类型分派通过 op_kernel/arch35/fast_gelu_struct.h 中的ASCENDC_TPL_ARGS_DECL在编译期展开为 FP16/BF16/FP32 三个实例对应 op_host/config/ascend950/fast_gelu_binary.json 中的FastGelu_fp16、FastGelu_bf16、FastGelu_fp32三个二进制文件条目ascend350 的配置结构相同。此外算子注册op_host/fast_gelu_def.cpp声明输入 x、输出 y 均支持DT_BF16 / DT_FLOAT16 / DT_FLOAT与ND格式且开启动态 shape、动态 rank 支持shape 推导复用逐元素算子的通用推导逻辑InferShape4Elewiseop_host/fast_gelu_infershape.cpp。调用示例完整的 aclnnFastGelu 使用流程下面给出文档中提供的完整示例代码与 examples/test_aclnn_fast_gelu.cpp 同源演示从环境初始化到结果回收的七个步骤。该代码以 3×3 的 FLOAT32 输入为例编译与运行的环境准备请参考 编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_fast_gelu.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 {3, 3}; std::vectorint64_t outShape {3, 3}; void* selfDeviceAddr nullptr; aclTensor* self nullptr; void* outDeviceAddr nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData(9, 0); std::vectorfloat outHostData(9, 0); // 创建self和out aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); 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; // 调用aclnnFastGelu第一段接口 ret aclnnFastGeluGetWorkspaceSize(self, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnFastGeluGetWorkspaceSize 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); } // 调用aclnnFastGelu第二段接口 ret aclnnFastGelu(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnFastGelu 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(out); // 7. 释放device资源需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }对示例中几个关键点的补充说明第 3 步必须严格遵守两段式顺序先调用aclnnFastGeluGetWorkspaceSize获得workspaceSize与executor再据此aclrtMalloc申请 workspace最后调用aclnnFastGelu。若workspaceSize为 0例如空 Tensor 场景可跳过 workspace 申请直接传空指针。第 4 步的aclrtSynchronizeStream不可省略由于 aclnn 接口是异步执行的必须在读取结果前同步等待 Stream 上的任务完成否则可能读到未就绪的数据。CreateAclTensor 中 strides 的推导对于 ND 格式的连续张量strides 从最后一维恒为 1向前逐维累乘得到这是aclCreateTensor正确描述张量布局的必要参数。验证方式将selfHostData填入实际数据后运行可在日志中逐元素打印result[i]与公式x / (1 exp(-1.702 * x))的手算结果比对即可验证调用正确性。验证与测试资源仓库为 aclnnFastGelu 提供了完整的测试与验证资源可作为集成测试的参考单算子 API 单测tests/ut/op_api/test_aclnn_fast_gelu.cpp直接验证两段式接口的调用与结果Host 侧单测tests/ut/op_host/test_fast_gelu_infershape.cppshape 推导与 tests/ut/op_host/arch35/test_fast_gelu_tiling.cpptiling 计算Kernel 单测tests/ut/op_kernel/test_fast_gelu_apt.cpp配套 tests/ut/op_kernel/fast_gelu_data/gen_data.py 生成测试数据端到端 ST 测试tests/st/aclnnFastGelu/ 下的atk_aclnnFastGelu.json与executor_aclnnFastGelu.py通过 ATK 框架在真实设备上执行全流程验证框架插件适配framework/fast_gelu_tf_plugin.cpp 与 framework/npu_fast_gelu_onnx_plugin.cpp 分别提供 TensorFlow 与 ONNX 模型框架侧的算子插件适配。总结aclnnFastGelu 是 CANN ops-nn 仓库在昇腾 A2/A3/950 系列 NPU 上提供的 FastGelu 激活算子接口支持 FLOAT16、FLOAT32、BFLOAT16 三种数据类型与 ND 格式支持 0-8 维张量及非连续 Tensor默认确定性计算。使用上需遵循“先 GetWorkspaceSize、后执行”的两段式模式并留意 161001/161002 两类参数校验错误。通过阅读 op_api 的校验与适配逻辑、op_kernel 的计算图实现以及配套的单测与 ST 测试开发者可以快速将此算子集成到自己的推理或训练链路中也可以在出现精度问题时依据计算图FP16/BF16 中间转 float 计算快速定位根因。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考