CANN ascend-transformer-boost RmsNormOperation C++ Demo 实战指南:从环境配置到 Qwen / DeepSeek 模型场景
【免费下载链接】ascend-transformer-boost本项目是CANN提供的是一款高效、可靠的Transformer加速库,基于华为Ascend AI处理器,提供Transformer定制化场景的高性能融合算子。项目地址: https://gitcode.com/cann/ascend-transformer-boost
本篇技术指南以 CANN ascend-transformer-boost 仓库中 rms_norm 示例目录 为骨架,完整讲解 RmsNormOperation(RMS 归一化算子)的 C++ 调用方式:包括 CANN 与 NNAL 双环境变量配置、build.sh编译运行流程、RmsNormParam参数结构逐字段解析,以及面向 Llama、Qwen、DeepSeek 等主流模型场景的 5 个可直接运行的 demo 规格。读完本文,你将能够独立配置环境、编译并运行 rms_norm 系列示例,并具备依据模型隐藏层维度自行修改 demo 的能力。
RMSNorm 与 RmsNormOperation 简介
RMSNorm(Root Mean Square Layer Normalization)是 Llama、Qwen、DeepSeek 等主流 Transformer 模型中广泛使用的归一化层,相比 LayerNorm 省去了均值中心化步骤,只基于均方根对输入进行缩放,计算开销更低。在 CANN ascend-transformer-boost 加速库中,该能力由RmsNormOperation提供,其参数结构体atb::infer::RmsNormParam定义在 include/atb/infer_op_params.h。
从定义可以看出,RmsNormOperation 是一个能力相当完整的融合算子:
- 三种归一化模式(
layerType):RMS_NORM_NORM(标准 NORM)、RMS_NORM_PRENORM(前置归一化)、RMS_NORM_POSTNORM(后置归一化); - 精度模式(
precisionMode):HIGH_PRECISION_MODE(中间计算使用 float)与HIGH_PERFORMANCE_MODE(中间计算使用 float16); - 模型公式选择(
modelType):LLAMA_MODEL(Llama 系 RMSNorm 公式,默认)与GEMMA_MODEL(Gemma 系公式); - 量化支持(
quantType):当前支持QUANT_UNQUANT与QUANT_INT8; - 前置/后置归一化还支持偏置叠加(
hasBias)与各自的 epsilon 配置。
环境准备:source 双环境变量
在编译运行 demo 之前,需要先加载 CANN 与 NNAL(Ascend Transformer Boost 加速库)两个包的运行环境,README 给出了两条 source 命令:
加载 CANN 工具包环境:
source /usr/local/Ascend/ascend-toolkit/set_env.sh加载 NNAL 加速库环境:
source /usr/local/Ascend/nnal/atb/set_env.sh
特别说明:如果是从加速库源码自行编译构建(而非安装预编译包),则应 source 源码编译产物的环境脚本,例如:
source ./ascend-transformer-boost/output/atb/set_env.sh环境脚本还会顺带设置ATB_HOME_PATH与ASCEND_HOME_PATH两个关键环境变量,它们正是后面编译脚本中定位头文件与库文件路径的依据(见下文 build.sh 分析)。
编译与运行:build.sh 全流程解读
环境就绪后,在示例目录下执行一条命令即可完成编译并运行:
bash build.sh以 example/op_demo/rms_norm/build.sh 为例,脚本内部做了两件事:
第一步:探测 cxx_abi 并编译。脚本通过 Python 探测当前 torch 是否以 C++11 ABI 编译(有 torch 时按其返回值,无 torch 时默认输出 1),随后调用 g++ 完成编译:
g++ -D_GLIBCXX_USE_CXX11_ABI=${cxx_abi} \ -I "${ATB_HOME_PATH}/include" -I "${ASCEND_HOME_PATH}/include" \ -L "${ATB_HOME_PATH}/lib" -L "${ASCEND_HOME_PATH}/lib64" \ rms_norm_demo.cpp ../demo_util.h -l atb -l ascendcl -o rms_norm_demo第二步:运行生成的二进制./rms_norm_demo。
编译脚本默认编译并运行rms_norm_demo.cpp。如需编译其他 demo(例如rms_norm_qwen_demo_0.cpp),需要将编译命令中的源文件替换为对应的.cpp文件名。
cxx_abi 与 D_GLIBCXX_USE_CXX11_ABI 的匹配
_GLIBCXX_USE_CXX11_ABI宏决定 C++ 标准库字符串等类型采用 ABI 0(旧版std::string)还是 ABI 1(C++11std::string),必须与加速库链接时使用的 ABI 保持一致,否则链接或运行时会报符号不匹配错误。README 给出了两种显式用法:
使用
cxx_abi=0(默认)时:g++ -D_GLIBCXX_USE_CXX11_ABI=0 -I ...使用
cxx_abi=1时:g++ -D_GLIBCXX_USE_CXX11_ABI=1 -I ...
build.sh中的自动探测逻辑会在没有 torch 的环境里回退为 1,因此当出现 ABI 相关链接错误时,可通过显式指定宏并调整编译选项排查。
核心代码走读:rms_norm_demo.cpp 调用全流程
example/op_demo/rms_norm/rms_norm_demo.cpp 是标准 NORM 场景的默认示例,其调用流程完整展示了 ATB 算子 C++ 编程的"五步法",对编写其他 ATB 算子调用代码具有通用参考价值:
初始化 ACL 与设备:
aclInit(nullptr)初始化 ACL 运行环境,aclrtSetDevice(DEVICE_ID)指定使用 0 号设备,atb::CreateContext(&context)创建 ATB 上下文,aclrtCreateStream创建 stream,最后通过context->SetExecuteStream(stream)将执行流绑定到上下文。创建算子:填充
atb::infer::RmsNormParam后调用atb::CreateOperation(param, &rmsnormOp)生成算子实例:atb::infer::RmsNormParam param; param.layerType = atb::infer::RmsNormParam::RmsNormType::RMS_NORM_NORM; param.normParam.quantType = atb::infer::QuantType::QUANT_UNQUANT; atb::CreateOperation(param, rmsNormOp);准备输入输出 Tensor:通过公共工具头文件 example/op_demo/demo_util.h 中的
CreateTensorFromVector在设备侧分配内存并把 host 数据搬入,随后组装atb::VariantPack,将输入{x, gamma}与输出{tensorOut}分别放入inTensors/outTensors。Setup 获取 workspace 大小并执行:先调用
rmsnormOp->Setup(variantPack, workspaceSize, context)完成输入输出校验并计算工作空间大小,再用aclrtMalloc分配 workspace,随后调用rmsnormOp->Execute(variantPack, workspacePtr, workspaceSize, context)真正下发计算,最后aclrtSynchronizeStream(stream)等待设备侧任务完成。资源释放:依次释放输入输出 Tensor 的 device 内存与 workspace、
atb::DestroyOperation销毁算子、销毁 stream 与 context、aclFinalize()收尾。注意代码注释强调的顺序:operation 对象先释放,context 全局资源后释放。
demo_util.h还封装了CHECK_STATUS错误处理宏(区分 ACL 错误码与 ATB 错误码并打印排查指引)、CreateTensor(依据 shape 计算数据大小并分配设备内存)、CastOp(调用 Elewise 的ELEWISE_CAST完成数据类型转换)与TransdataOp(调用ND_TO_FRACTAL_NZ完成 ND 到 NZ 格式转换)等通用工具,其他算子 demo 均复用了这套工具,可作为统一参考。
RmsNormParam 参数详解:字段、取值范围与默认值
以下字段来自 include/atb/infer_op_params.h 的源码定义,是理解与调整 demo 参数的基础:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
layerType | RmsNormType | RMS_NORM_UNDEFINED | 归一化类型:RMS_NORM_NORM(标准 NORM)、RMS_NORM_PRENORM(PRENORM)、RMS_NORM_POSTNORM(POSTNORM) |
normParam.quantType | QuantType | QUANT_UNQUANT | NORM 场景量化类型,当前支持QUANT_UNQUANT(不量化)与QUANT_INT8(int8 量化) |
normParam.epsilon | float | 1e-5 | 归一化时加在分母上防止除零的小量 |
normParam.rstd | bool | false | 置 true 时使用训练侧 rmsnormforward 算子,仅 Atlas 800I A2 推理产品支持;不可与precisionMode、modelType同时设置,量化场景不支持 |
normParam.precisionMode | PrecisionMode | HIGH_PRECISION_MODE | 中间计算精度:默认 float;HIGH_PERFORMANCE_MODE使用 float16(输入仅支持 float16);不可与rstd、modelType同时设置,量化场景配置该参数将返回ERROR_INVALID_PARAM |
normParam.modelType | ModelType | LLAMA_MODEL | 计算公式:Llama 系 RMSNorm 公式(默认)或 Gemma 系公式;不可与rstd、precisionMode同时启用,量化场景不支持 |
preNormParam/postNormParam | PreNormParam/PostNormParam | — | PRENORM / POSTNORM 场景参数,含quantType、epsilon(默认1e-5)、hasBias(是否叠加偏置,默认 false) |
QuantType枚举定义在同文件第 45~57 行:QUANT_UNDEFINED = QUANT_UNQUANT = 0(不量化)、QUANT_INT4 = 1(暂不支持)、QUANT_INT8 = 2(int8 量化)、QUANT_INT16 / QUANT_FLOAT8 / QUANT_FLOAT16(均暂不支持)。
需要特别留意算子实现侧的校验规则(见 src/ops/ops_infer/rms_norm/rms_norm_operation.cpp):layerType与quantType的组合存在约束,例如RMS_NORM_PRENORM/RMS_NORM_POSTNORM与QUANT_INT8组合非法时会返回带明确提示的报错;另外所有输入输出 Tensor 的最后一维大小必须相等,且Atlas 推理系列产品不支持 bf16 类型数据。
五个 Demo 场景:参数与输入输出规格全表
示例目录共提供 5 个 demo,分别对应不同模型与隐藏层维度的典型场景。除默认的rms_norm_demo.cpp外,其余 demo 编译时需在 build.sh 中把源文件替换为对应.cpp文件名,并且仅适用于Atlas A2/A3 训练系列产品、Atlas 800I A2 推理产品、Atlas A3 推理系列产品。
rms_norm_demo.cpp(通用基础场景)
默认编译脚本可直接编译运行。参数设置:
| 参数 | 值 |
|---|---|
layerType | atb::infer::RmsNormParam::RmsNormType::RMS_NORM_NORM |
normParam.quantType | atb::infer::QuantType::QUANT_UNQUANT |
epsilon | 1e-5 |
输入:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
x | float16 | nd | [4, 1024, 5120] |
gamma | float16 | nd | [5120] |
输出:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
output | float16 | nd | [4, 1024, 5120] |
该场景对应代码中DIM_0 = 4、DIM_1 = 1024、DIM_2 = 5120的常量定义,输入数据全部以 2.0 填充,gamma为一维 [5120] 的逐通道缩放参数,输出与输入x形状一致。
rms_norm_qwen_demo_0.cpp(Qwen 批量场景)
编译时替换源文件为rms_norm_qwen_demo_0.cpp。参数设置:
| 参数 | 值 |
|---|---|
layerType | atb::infer::RmsNormParam::RmsNormType::RMS_NORM_NORM |
normParam.quantType | atb::infer::QuantType::QUANT_UNQUANT |
epsilon | 1e-6 |
输入:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
x | bf16 | nd | [1024, 5120] |
gamma | bf16 | nd | [5120] |
输出:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
output | bf16 | nd | [1024, 5120] |
rms_norm_qwen_demo_1.cpp(Qwen 单 token 场景)
编译时替换源文件为rms_norm_qwen_demo_1.cpp。参数设置:
| 参数 | 值 |
|---|---|
layerType | atb::infer::RmsNormParam::RmsNormType::RMS_NORM_NORM |
normParam.quantType | atb::infer::QuantType::QUANT_UNQUANT |
epsilon | 1e-6 |
输入:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
x | bf16 | nd | [1, 5120] |
gamma | bf16 | nd | [5120] |
输出:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
output | bf16 | nd | [1, 5120] |
rms_norm_qwen_demo_2.cpp(Qwen 小批量场景)
编译时替换源文件为rms_norm_qwen_demo_2.cpp。参数设置:
| 参数 | 值 |
|---|---|
layerType | atb::infer::RmsNormParam::RmsNormType::RMS_NORM_NORM |
normParam.quantType | atb::infer::QuantType::QUANT_UNQUANT |
epsilon | 1e-6 |
输入:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
x | bf16 | nd | [5, 5120] |
gamma | bf16 | nd | [5120] |
输出:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
output | bf16 | nd | [5, 5120] |
rms_norm_deepseek_demo_0.cpp(DeepSeek 大 batch 场景)
编译时替换源文件为rms_norm_deepseek_demo_0.cpp。参数设置:
| 参数 | 值 |
|---|---|
layerType | atb::infer::RmsNormParam::RmsNormType::RMS_NORM_NORM |
normParam.quantType | atb::infer::QuantType::QUANT_UNQUANT |
epsilon | 1e-6 |
输入:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
x | float16 | nd | [512, 7168] |
gamma | float16 | nd | [7168] |
输出:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
output | float16 | nd | [512, 7168] |
rms_norm_deepseek_demo_1.cpp(DeepSeek 小 batch 场景)
编译时替换源文件为rms_norm_deepseek_demo_1.cpp。参数设置:
| 参数 | 值 |
|---|---|
layerType | atb::infer::RmsNormParam::RmsNormType::RMS_NORM_NORM |
normParam.quantType | atb::infer::QuantType::QUANT_UNQUANT |
epsilon | 1e-6 |
输入:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
x | float16 | nd | [32, 7168] |
gamma | float16 | nd | [7168] |
输出:
| Tensor | 数据类型 | 数据格式 | Shape |
|---|---|---|---|
output | float16 | nd | [32, 7168] |
场景对比与改造要点
从上述规格可以总结出规律:隐藏层维度(最后一维)与模型强相关——Qwen 系列为 5120,DeepSeek 系列为 7168,二者均与对应模型的 hidden_size 对齐;gamma的形状始终等于隐藏层维度;输出形状与输入x完全一致。因此,当需要适配其他模型时,只需同步修改x的 shape、gamma的 shape(及DIM_*常量)即可,无需改动算子创建与执行逻辑。同时注意 Qwen 系列 demo 使用 bf16,DeepSeek 与基础 demo 使用 float16,须与产品对 bf16 的支持情况(Atlas 推理系列不支持 bf16)核对后再选用。
数据生成与结果验证说明
README 明确提醒:示例中生成的数据(如以常量 2.0 填充的输入)不代表实际场景,仅用于验证调用流程与算子可运行性。若需要贴近真实分布的数据生成与精度对比参考,请查看根目录下的 Python 用例目录:
tests/apitest/opstest/python/operations/rms_norm/该目录位于 tests/apitest/opstest/python/operations/rms_norm/,属于仓库 opstest 高精度测试体系,其中包含 rms_norm 算子的 Python 侧数据构造与预期输出生成逻辑,可作为自造数据、核对数值精度的权威参考。
常见问题与排障建议
- 链接报
std::string相关符号错误:多为_GLIBCXX_USE_CXX11_ABI与加速库不一致,按 README 显式指定-D_GLIBCXX_USE_CXX11_ABI=0/1并匹配 build.sh 探测值。 ATB_HOME_PATH/ASCEND_HOME_PATH为空导致找不到头文件:确认已正确 source NNAL 与 CANN 的 set_env.sh;源码编译场景务必 sourceoutput/atb/set_env.sh。- 运行报参数校验错误:检查
layerType与quantType组合是否合法、量化场景是否误配precisionMode/modelType/rstd、所有 Tensor 最后一维是否一致,以及设备型号对 bf16 的支持限制。 - 执行结果不符合预期:demo 输入为随机填充,不要将其当作真实推理结果;如需验证正确性,改用
tests/apitest/opstest/python/operations/rms_norm/下的 Python 用例数据,或参考 example/op_demo/demo_util.h 中的CastOp/TransdataOp工具完成数据类型与 ND/NZ 格式的转换后再对拍。
通过本文,你已经掌握 RmsNormOperation 在 ascend-transformer-boost 中的完整 C++ 调用链路:从双环境变量配置、build.sh 编译运行,到RmsNormParam参数体系与 5 个模型场景规格,再到源码级校验规则与数据验证手段。这套方法同样适用于仓库中其他算子 demo(如 rms_norm_backward、layer_norm、rope 等),可作为 ATB 算子 C++ 开发的上手范式。
【免费下载链接】ascend-transformer-boost本项目是CANN提供的是一款高效、可靠的Transformer加速库,基于华为Ascend AI处理器,提供Transformer定制化场景的高性能融合算子。项目地址: https://gitcode.com/cann/ascend-transformer-boost
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考