
CANN ops-nn AdaLayerNormQuant 算子深度指南aclnnAdaLayerNormQuant 两段式接口原理、参数与实战调用【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn本指南以 aclnnAdaLayerNormQuant 接口文档 为主体结合 CANN ops-nn 仓库中 ada_layer_norm_quant 模块的源码实现、算子定义与单元测试系统讲解该融合算子的功能原理、两段式 API 的完整调用流程、全部入参出参约束、错误码与典型调用示例。读者学完后可以独立完成 AdaLayerNorm 与 DynamicQuant 融合算子在 NPU 上的调用与结果校验。一、算子是什么AdaLayerNormQuant 的功能与融合动机AdaLayerNormQuant 是 CANN ops-nn 仓库中位于 norm/ada_layer_norm_quant 目录下的一个融合算子其核心功能是将「自适应层归一化AdaLayerNorm」与「下游的动态量化DynamicQuant」合并在一次内核计算中完成先把输入数据做归一化处理再将其量化为低精度整数INT8 / FLOAT8 等从而在保持精度的前提下提高计算效率并减少内存占用。算子功能AdaLayerNormQuant 将 AdaLayerNorm 和下游量化目前仅支持 DynamicQuant融合起来主要用于执行自适应层归一化的量化操作即将输入数据进行归一化处理并量化为低精度整数。典型场景在 LLM 推理的激活值量化管线中LayerNorm/AdaLayerNorm 之后通常紧跟一个量化节点把归一化后的高精度激活张量压成 INT8/FP8 供后续矩阵运算使用。将其融合为单个算子可以省去中间张量的落盘与多次内核启动开销。从源码结构看该算子复用了 ada_layer_norm 模块的归一化内核基类ada_layer_norm_base.h/ada_layer_norm_base_v1.h并在此基础上叠加量化输出路径印证了「AdaLayerNorm 量化」的融合设计意图。二、产品支持情况根据接口文档与 README各产品线的支持情况如下产品是否支持Ascend 950PR/Ascend 950DT支持Atlas A3 训练系列产品/Atlas A3 推理系列产品支持Atlas A2 训练系列产品/Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品不支持Atlas 训练系列产品不支持此外在Atlas A3 / Atlas A2 系列产品上输出张量out的数据类型仅支持 INT8详见下文参数说明。三、功能说明与计算公式设输入为xE(x) 为均值Var(x) 为方差row_max表示按行求最大值算子按以下 5 步完成计算第 1 步LayerNorm 归一化$$ LayerNorm(x) {{x-E(x)}\over\sqrt {Var(x)epsilon}} * weightOptional biasOptional $$第 2 步自适应调整scale/shift$$ y LayerNorm(x) * (1 scale) shift $$第 3 步可选平滑缩放smoothScalesOptional 不为空时$$ y y \cdot smoothScalesOptional $$第 4 步计算量化因子对 y 求每行最大绝对值并除以目标格式的表示范围上限FP8_MAX / HIF8_MAX / INT8_MAX$$ quantScale row_max(abs(y)) / (FP8_MAX / HIF8_MAX / INT8_MAX) $$第 5 步量化取整得到输出$$ out round(y / quantScale) $$其中epsilon为添加到方差分母上的极小正数用于防止除零、保证数值稳定如 1e-5。上述公式在 README 中给出了等价描述两者保持一致。四、两段式接口与函数原型每个 aclnn 算子都采用两段式接口设计必须先调用aclnnAdaLayerNormQuantGetWorkspaceSize获取计算所需 workspace 大小以及包含了算子计算流程的执行器再调用aclnnAdaLayerNormQuant执行计算。4.1 第一段接口aclnnAdaLayerNormQuantGetWorkspaceSizeaclnnStatus aclnnAdaLayerNormQuantGetWorkspaceSize( const aclTensor* x, const aclTensor* scale, const aclTensor* shift, const aclTensor* weightOptional, const aclTensor* biasOptional, const aclTensor* smoothScalesOptional, double epsilon, const char* quantMode, aclTensor* out, aclTensor* quantScale, aclTensor* quantOffsetOptional, uint64_t* workspaceSize, aclOpExecutor** executor)4.2 第二段接口aclnnAdaLayerNormQuantaclnnStatus aclnnAdaLayerNormQuant( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)第二段接口的 4 个参数含义如下参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入workspace 大小由第一段接口获取executor输入op 执行器包含算子计算流程stream输入指定执行任务的 Stream4.3 从源码看两段式流程在 aclnn_ada_layer_norm_quant.cpp 中第一段接口完成如下关键动作创建 OpExecutor并调用CheckParams完成入参校验空指针、数据类型、Shape、Attr对x/scale/shift及三个可选输入调用l0op::Contiguous做连续性处理这正是文档中非连续 Tensor √的来源调用l0op::AdaLayerNormQuant见 ada_layer_norm_quant.cpp构建内核启动列表内部会自动为out和quantScale分配输出张量通过executor-GetWorkspaceSize()返回 workspace 大小并将 executor 释放给调用方。第二段接口则统一走CommonOpExecutorRun完成实际计算下发。若输入为空 Tensorx/scale/shift任一为空第一段接口直接返回ACLNN_SUCCESS且workspaceSize 0不会构建计算流程。五、参数说明完整约束表以下为aclnnAdaLayerNormQuantGetWorkspaceSize的完整参数约束源自接口文档并可与 源码校验逻辑 相互印证。参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorxaclTensor*输入输入待处理数据对应公式中的 x不支持空 Tensorshape 为 [B…, S, H]B 支持 0~6 个维度FLOAT16、BFLOAT16ND2-8√scaleaclTensor*输入自适应缩放参数对应公式中的 scale不支持空 Tensor数据类型与 x 一致shape 为 [B…, H] 或 [B…, 1, H]B 的维度数与大小与 x 一致H 与 x 的 H 维一致FLOAT16、BFLOAT16ND1-8√shiftaclTensor*输入自适应偏移参数对应公式中的 shift约束同 scaleFLOAT16、BFLOAT16ND1-8√weightOptionalaclTensor*输入可选归一化缩放参数对应公式中的 weightOptional不支持空 Tensor数据类型与 x 一致shape 为 [H]FLOAT16、BFLOAT16ND1√biasOptionalaclTensor*输入可选归一化偏移参数对应公式中的 biasOptional约束同 weightOptionalFLOAT16、BFLOAT16ND1√smoothScalesOptionalaclTensor*输入可选量化平滑权重对应公式中的 smoothScalesOptional约束同 weightOptionalFLOAT16、BFLOAT16ND1√epsilondouble输入添加到分母中的值确保数值稳定、防止除 0建议传较小的正数如 1e-5----quantModechar*输入量化模式当前版本仅支持 dynamic----outaclTensor*输出量化输出张量对应公式中的 out不支持空 Tensorshape 与 x 保持一致INT8、FLOAT8_E4M3FN、FLOAT8_E5M2、HIFLOAT8ND2-8√quantScaleaclTensor*输出量化系数对应公式中的 quantScale不支持空 Tensorshape 为 [B…, S]B 与 x 一致S 与 x 的 S 维一致FLOAT32ND1-7√quantOffsetOptionalaclTensor*输出可选非对称量化使用的 offset不支持空 Tensorshape 与 quantScale 一致当前版本暂不支持传 nullptrFLOAT16、BFLOAT16ND1-7√workspaceSizeuint64_t*输出返回需在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含算子计算流程-----平台差异在 Atlas A3 / Atlas A2 系列产品上输出out的数据类型仅支持 INT8。5.1 源码中的校验逻辑解读从 aclnn_ada_layer_norm_quant.cpp 可以确认以下实现细节MIN_X_DIM 2、MAX_X_DIM 8对应文档中 x 的 2~8 维约束X_DTYPE_SUPPORT_LIST {DT_FLOAT16, DT_BF16}输入仅支持 FP16/BF16OUT_DTYPE_SUPPORT_LIST_REGBASE在 Regbase950 系列下允许 INT8/HIFLOAT8/FP8_E5M2/FP8_E4M3FN 四种输出类型而非 Regbase 平台910B/A3仅支持 INT8——这与文档中的平台差异说明一致CheckShape中要求x各维均大于 0scale/shift的 shape 必须是[B…, 1, H]或[B…, H]之一weight/bias/smoothScales必须是[H]out与 x 同形quantScale形如[B…, S]CheckAttr将quantMode限定为字符串dynamic其他取值一律报ACLNN_ERR_PARAM_INVALIDquantOffsetOptional非空即报错印证了当前版本暂不支持传 nullptr。六、返回值与错误码接口返回aclnnStatus状态码具体含义参见 aclnn 返回码说明。第一段接口完成入参校验出现以下场景时报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 x、scale、shift、out、quantScale 是空指针ACLNN_ERR_PARAM_INVALID8 种场景161002① x、scale、shift、out、quantScale 的数据类型或数据格式不在支持范围② weightOptional 非空时类型/格式不支持③ biasOptional 非空时类型/格式不支持④ smoothScalesOptional 非空时类型/格式不支持⑤ quantMode 不为 dynamic⑥ quantOffsetOptional 不为空指针⑦ scale、shift、weightOptional、biasOptional、smoothScalesOptional 与 x 的数据类型不一致⑧ 上述张量的 shape 与参数说明不一致上述错误场景在单元测试 test_aclnn_ada_layer_norm_quant.cpp 中有直接覆盖例如x的 H 维为 0、输入类型使用 FLOAT、scaleshape 与x不匹配、weight长度不等于 H、out/quantScaleshape 错误等场景均断言返回ACLNN_ERR_PARAM_INVALID。七、约束说明确定性计算aclnnAdaLayerNormQuant默认采用确定性实现即相同输入在多次运行中得到一致的输出结果便于调试与精度对比更多背景可参考确定性计算说明。八、调用示例完整可编译流程接口文档给出了完整的 C 调用示例仓库中的 examples/test_aclnn_ada_layer_norm_quant.cpp 即为同款可运行样例编译与运行的具体流程请参考编译与运行样例。核心代码与要点如下#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_ada_layer_norm_quant.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; } 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, 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 0, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出根据API接口自定义构造 std::vectorint64_t xShape {2, 4, 8}; std::vectorint64_t scaleShape {2, 8}; std::vectorint64_t shiftShape {2, 8}; std::vectorint64_t weightShape {8}; std::vectorint64_t biasShape {8}; std::vectorint64_t smoothScalesShape {8}; std::vectorint64_t outShape {2, 4, 8}; std::vectorint64_t quantScaleShape {2, 4}; double epsilon 1e-5; const char* quantMode dynamic; // 依次为 x/scale/shift/weight/bias/smoothScales/out/quantScale 申请 device 内存 // 并创建 aclTensorFP16 输入 INT8/FP32 输出ND 格式过程略 std::vectorshort xHostData(2 * 4 * 8, 1); // FP16 输入数据 std::vectorshort scaleHostData(2 * 8, 1); std::vectorshort shiftHostData(2 * 8, 1); std::vectorshort weightHostData(8, 1); std::vectorshort biasHostData(8, 1); std::vectorshort smoothScalesHostData(8, 1); std::vectorint8_t outHostData(2 * 4 * 8, 0); // INT8 输出 std::vectorfloat quantScaleHostData(2 * 4, 0); // FP32 量化系数 // 3. 调用两段式接口 uint64_t workspaceSize 0; aclOpExecutor* executor; // 第一段获取workspace大小与executorquantOffsetOptional 传 nullptr ret aclnnAdaLayerNormQuantGetWorkspaceSize(x, scale, shift, weight, bias, smoothScales, epsilon, quantMode, out, quantScale, nullptr, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnAdaLayerNormQuantGetWorkspaceSize 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); } // 第二段执行计算 ret aclnnAdaLayerNormQuant(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnAdaLayerNormQuant 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::vectorint8_t resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(int8_t), 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: %d\n, i, resultData[i]); } // 6. 释放aclTensoraclDestroyTensor与device资源aclrtFree/workspace // 7. 释放stream并复位设备aclrtDestroyStream、aclrtResetDevice、aclFinalize return 0; }8.1 调用要点总结shape 设计示例中x [2, 4, 8]其中 B2、S4、H8scale/shift取[2, 8]即 [B…, H] 形式也可用 [2, 1, 8]weight/bias/smoothScales为[8]quantScale为[2, 4]即 [B…, S]。quantOffsetOptional当前版本不支持非对称量化必须传nullptr。workspace 申请只有当workspaceSize 0时才需要aclrtMalloc结束后需aclrtFree。executor 生命周期executor 由第一段接口创建并交由调用方第二段接口执行完毕后按 acl 规范释放相关资源。九、源码实现佐证算子定义、内核与配置9.1 算子定义注册ada_layer_norm_quant_def.cpp 中通过OP_ADD(AdaLayerNormQuant)注册算子6 个输入x/scale/shift 为 REQUIREDweight/bias/smooth_scales 为 OPTIONAL2 个输出out、quant_scale 为 REQUIREDepsilon作为可选属性AttrTypeOPTIONAL默认值1e-5ascend910bAtlas A2与ascend910_93Atlas A3配置下输出仅支持 INT8ascend950配置通过Add91095Config()额外开启 DynamicRank/DynamicShape 支持并允许 INT8/HIFLOAT8/FP8_E4M3FN/FP8_E5M2 四种输出类型。9.2 内核入口op_kernel/ada_layer_norm_quant.cpp 展示了 AICore 内核入口ada_layer_norm_quant它按TILING_KEY分派到复用自 AdaLayerNorm 的AdaLayerNormNDhalf/bfloat16_t, ..., QUANT_OP_CODE模板实例上先执行InitQuant再执行Process将归一化与量化在同一个内核中完成——这从实现层面印证了融合算子的设计。其中 bfloat16 分支在 3003 架构对应部分 950 芯片上被排除。9.3 二进制配置op_host/config/ascend910b/ada_layer_norm_quant_binary.json 中为 float16 与 bfloat16 两种输入分别登记了二进制产物输入 x/scale/shift 均为required NDweight/bias/smooth_scales 为optional ND输出 out 为int8、quant_scale 为float32属性 epsilon 为 float 类型。shape: [-2]表示动态 shape 场景说明该算子支持编译期未知 shape 的图模式下发。十、小结AdaLayerNormQuant 是 CANN ops-nn 中一个典型的「归一化 动态量化」融合算子它把 AdaLayerNorm 的归一化、自适应 scale/shift 调整、可选的平滑缩放与 DynamicQuant 的量化因子计算和取整输出合并为单次内核执行在支持的产品上Ascend 950、Atlas A2/A3显著减少中间张量搬运。通过两段式 aclnn 接口开发者可以方便地在推理/训练图中插入该算子将 FP16/BF16 激活量化为 INT8/FP8 输出同时拿到每行的量化系数quantScale供反量化使用。建议读者结合本指南给出的参数约束表、错误码清单与完整示例代码直接基于仓库中的 examples/test_aclnn_ada_layer_norm_quant.cpp 运行验证。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考