CANN/cann-samples: RmsNormQuant算子性能优化指南

发布时间:2026/7/5 14:19:04

CANN/cann-samples: RmsNormQuant算子性能优化指南 RmsNormQuant 算子性能优化指南【免费下载链接】cann-samples算子领域高性能实战演进样例与体系化调优知识库项目地址: https://gitcode.com/cann/cann-samples概述本文档系统阐述 RmsNormQuant 算子的实现原理、性能建模方法及优化实践。通过系统性的优化策略帮助开发者快速掌握算子性能调优的核心技术提升算子在昇腾平台上的执行效率。平台Ascend 950PR/950DT64 Vector Core测试规格x[4096, 8192] float16 → y[4096, 8192] int8最佳结果7693 us → 49.0 us总加速比157x算子实现原理算子功能说明算子功能RmsNormQuant 是 LLM 推理中常见的归一化与量化融合算子。其核心逻辑将输入矩阵按行做 RMS Normalization乘以可学习权重 gamma再通过量化参数映射为 int8 输出。相较于分离执行 RmsNorm Quantize融合实现可显著减少中间结果的全局内存读写。应用场景Transformer 架构中 Attention 和 FFN 计算前的归一化与量化预处理广泛用于 KV Cache 量化、激活量化等场景。计算公式$$ \text{RMS}i \sqrt{\frac{1}{r}\sum{j0}^{r-1}x_{i,j}^2 \varepsilon} $$$$ \text{norm}{i,j} \frac{x{i,j}}{\text{RMS}_i} \cdot \gamma_j $$$$ y_{i,j} \text{clamp}!\left(\text{round}!\left(\text{norm}_{i,j} \cdot s b\right),\ -128,\ 127\right) $$参数说明变量名描述DtypeShapex输入矩阵float16(a, r)gamma归一化权重float16(r,)scale量化缩放系数float16(1,)offset量化偏置int8(1,)y输出矩阵int8(a, r)ε数值稳定常数—1e-6算子实现说明RmsNormQuant 是纯 Vector 算子。主要流水包括MTE2GM → UB 数据搬入x、gammaVEC向量计算平方归约、sqrt、除法、量化映射MTE3UB → GM 数据搬出y算子的典型执行流程MTE2(x) → VEC(RMS归约) → VEC(归一化量化) → MTE3(y)算子实现约束行间独立性每行的 RMS 只依赖本行数据天然适合按行做多核并行。行内长归约r8192列方向不能随意切碎ReduceSum 的中间值需保存。gamma 跨行共享4096 行共用同一份 gamma16 KB。算子性能建模性能瓶颈分析RmsNormQuant 算子的性能瓶颈主要分为以下类型Memory BoundMTE2 主导算子性能受限于 GM→UB 数据搬运能力。典型现象aiv_mte2_ratio偏高vecmte2 ≈ 100%但两者不重叠。计算 BoundVEC 主导算子性能受限于向量计算单元。典型现象aiv_vec_ratio偏高MTE2 已经不是瓶颈但时延仍未继续下降。流水停顿串行执行VEC 与 MTE2 交替执行而非并行导致流水空洞。典型现象vecmte2 ≈ 100%且两者比例之和接近 100% 。性能建模公式基本原理$$ T_{\text{total}} \max(T_{\text{vec}},\ T_{\text{mte2}},\ T_{\text{mte3}}) T_{\text{scalar_overhead}} $$当 VEC 与 MTE2 完全重叠时整体时延由更慢的一侧决定。各流水理论耗时估算1. MTE2 搬运时间每核处理blockFactor行每行搬运r个 float16x和一次 gamma16 KB$$ T_{\text{mte2}} \approx \frac{\text{blockFactor} \times r \times \text{sizeof}(\text{float16}) r \times \text{sizeof}(\text{float16})}{BW_{\text{hbm}}} $$其中blockFactor a / coreNum 4096 / 64 64当 gamma 提前全量搬入时gamma 开销折算为一次性固定成本。2. VEC 计算时间每行需要完成ReduceSum Sqrt Div Mul Muls Adds Cast等操作每元素约 10 FLOPs$$ T_{\text{vec}} \approx \frac{\text{blockFactor} \times r \times 10\ \text{FLOPs}}{FP_{\text{vec}}} $$3. MTE3 搬出时间输出为 int8每核搬出blockFactor × r字节$$ T_{\text{mte3}} \approx \frac{\text{blockFactor} \times r \times \text{sizeof}(\text{int8})}{BW_{\text{hbm}}} $$4. 固定成本循环次数 × (DMA setup 开销 调度开销)与ubFactor成反比。流水理论耗时对比MTE2 vs VEC当aiv_vec_ratio ≫ aiv_mte2_ratioStep 2~3VEC 是主要瓶颈应优先压缩计算链路如减少中间 UB 读写而非立即开 Double Buffer。当两者接近时Step 3 优化后vec ≈ 54%,mte2 ≈ 41%开 Double Buffer 使两者真正重叠此时理论收益最大。建模说明该模型基于单核流水线理论整体性能受限于最慢的流水阶段。通过分析各阶段 ratio 变化趋势可以准确识别性能瓶颈类型。优化目标是让各流水在vecmte2 ≫ 100%的状态下运行即发生显著重叠。算子优化实践Step 0 — Naive 基线直接按数学公式翻译成标准 AscendC API不做任何优化先拿一个能跑、能测、能暴露瓶颈的基线。实现方式// src/0_naive.cpp for (int64_t loop 0; loop tilingData_-a; loop) { CopyInGamma(); // - 每轮都搬4096 轮 CopyInX(loop); Compute(); CopyOut(loop); }Compute()中的中间结果反复落 UBCast(gamma_fp32, gamma_fp16, r); Cast(x_fp32, x_fp16, r); Mul(rms_buf, x_fp32, x_fp32, r); ReduceSum(reduce_buf, rms_buf, r); Duplicate(rms_buf, reduce_buf, r); Muls(rms_buf, rms_buf, r_inv, r); Adds(rms_buf, rms_buf, epsilon, r); Sqrt(rms_buf, rms_buf, r); Div(x_fp32, x_fp32, rms_buf, r); Mul(rms_buf, x_fp32, gamma_fp32, r); Muls(rms_buf, rms_buf, scale, r); Adds(rms_buf, rms_buf, offset, r); Cast(y, rms_buf, CAST_RINT, r);性能数据指标值Task Duration7693 usBlock Num1aiv_vec_ratio67.2%aiv_mte2_ratio29.9%aiv_mte3_ratio11.7%vecmte297.1%指令计数验证单元指令数说明RVECEX147,968真正的计算指令RVECLD123,392中间读回RVECST131,648中间写回SCALAR14,507调度开销RVECLD RVECST 255,040是RVECEX的1.72x——大量工作花在搬中间结果。问题诊断基线版本三类浪费同时叠加只有 1 个核在工作63 个核闲置gamma 跨 4096 行共享却每轮都重新搬4095 次冗余标准 API 几乎每步都写回 UB 一次打点图上 MTE2 与 VEC 基本交替几乎没有重叠。每次计算前都有两个MTE2搬运动作分别是搬运x和gamma到UB但是每次搬运的gamma是同样的数据存在重复搬运。Step 1 — Gamma 预加载优化目标消除共享只读参数的循环内冗余搬运。把跨 4096 行共享的只读参数 gamma 从循环里提到循环外只搬一次。代码改动// Step 0 for (int64_t loop 0; loop a; loop) { CopyInGamma(); CopyInX(loop); Compute(); CopyOut(loop); } // Step 1 CopyInGamma(); for (int64_t loop 0; loop a; loop) { CopyInX(loop); Compute(); CopyOut(loop); }性能数据指标Step 0Step 1变化Task Duration7693 us6790 us-11.7%1.13xaiv_vec_ratio67.2%69.0%基本不变aiv_mte2_ratio29.9%27.8%小幅下降aiv_scalar_ratio14.3%13.4%小幅下降vecmte297.1%96.8%仍是串行形态真正被删掉的不是一份 16 KB 数据而是4095 次重复的搬运 Cast 调度链路。以上所有的计算都是core0在执行剩余的核都处于空闲状态此算子有一个很重要的特性行与行之间的计算时独立的没有依赖关系因此可以将数据均匀的分到不同核并行计算。Step 2 — 多核并行优化目标将并行度从 1 提升到 64充分利用硬件资源。按行把 4096 行分给 64 个 Vector Core每核处理自己负责的行块。关键实现// src/2_multi_core.cpp blockIdx_ AscendC::GetBlockIdx(); if (blockIdx_ AscendC::GetBlockNum() - 1) { curblockFactor_ tilingData_-blockTail; } else { curblockFactor_ tilingData_-blockFactor; } xGm_.SetGlobalBuffer(x blockIdx_ * tilingData_-blockFactor * r); yGm_.SetGlobalBuffer(y blockIdx_ * tilingData_-blockFactor * r);性能数据指标Step 1Step 2变化Task Duration6790 us113.6 us-98.3%59.8xBlock Num164—aiv_vec_ratio69.0%65.8%基本不变aiv_mte2_ratio27.8%29.0%基本不变aiv_scalar_ratio13.4%14.7%基本不变vecmte296.8%94.8%核内仍以串行为主各类 ratio 基本没变说明收益来自并行度不是来自单核实现变化。并行效率实际加速比6790 / 113.6 59.8x并行效率59.8 / 64 93.4%。此时aiv_vec_ratio (65.8%) ≫ aiv_mte2_ratio (29.0%)同时存在多个VF函数说明 VEC 链路过长仍是主要瓶颈应优先压缩计算链路而非立即开 Double Buffer。Step 3 — VF MicroAPI优化目标减少中间结果反复写回 UB 的次数缩短单核 VEC 链路。把标准 API 改为__simd_vf__MicroAPI让中间值尽量在寄存器上流动。关键实现标准 API 的问题每步都落一次 UBUB → 寄存器 → 计算 → UB → 寄存器 → 计算 → UB ...MicroAPI 让中间值在寄存器上流动// src/3_vf.cpp AscendC::Reg::Duplicate(vregReduceSum, 0); for (uint16_t i 0; i vfLoopRNum_; i) { preg AscendC::Reg::UpdateMaskfloat(r); AscendC::Reg::DataCopyDATA_TYPE, LoadDist::DIST_UNPACK_B16(vregXIn, xInAddr i * VL_B32_SIZE); AscendC::Reg::Castfloat, DATA_TYPE, castTraitB162B32(vregX, vregXIn, preg); AscendC::Reg::Mul(vregXQuared, vregX, vregX, preg); AscendC::Reg::Add(vregReduceSum, vregReduceSum, vregXQuared, pregAll); } AscendC::Reg::ReduceSum(vregReduceSum, vregReduceSum, preg); AscendC::Reg::Sqrt(vregRms, vregRms, preg); // 只把真正需要的 RMS 标量写回 UB AscendC::Reg::DataCopyfloat, StoreDist::DIST_FIRST_ELEMENT_B32(rmsAddr, vregRms, preg);性能数据指标Step 2Step 3变化Task Duration113.6 us84.1 us-26.0%1.35xaiv_vec_ratio65.8%54.5%下降aiv_mte2_ratio29.0%41.5%上升aiv_scalar_ratio14.7%12.4%下降vecmte294.8%96.0%仍基本串行指令变化单元Step 2Step 3变化RVECEX124,74488,264-29.3%RVECLD95,56044,146-53.8%RVECST102,91314,905-85.5%SCALAR11,5585,709-50.6%总计334,892153,141-54.3%RVECST断崖式下降说明中间结果不再频繁落 UB通过以下打点图也能够看出VF函数变少VF调度开销也会变小。aiv_mte2_ratio上升是好信号——VEC 链路更短了MTE2 接近新瓶颈此时才是 Double Buffer 真正有价值的时机。Step 4 — Double Buffer双缓冲优化目标让 MTE2 预取下一批数据时VEC 同时计算当前批次实现流水重叠。代码改动// src/3_vf.cpp static constexpr size_t BUF_NUM 1; // src/4_double_buffer.cpp static constexpr size_t BUF_NUM 2;仅修改缓冲区数量其含义BUF_NUM 1搬一批、算一批、再搬下一批串行BUF_NUM 2当前 batch 在算下一 batch 已经开始搬重叠性能数据指标Step 3Step 4变化Task Duration84.1 us54.3 us-35.4%1.55xaiv_vec_ratio54.5%86.9%上升aiv_mte2_ratio41.5%79.2%上升aiv_scalar_ratio12.4%20.0%上升aiv_mte3_ratio17.5%28.0%上升vecmte296.0%166.1%发生显著重叠指令变化单元Step 3Step 4变化RVECEX88,26488,2640%RVECLD44,14644,1460%RVECST14,90514,9050%SCALAR5,7095,723基本不变指令没变时延掉了 35%——收益完全来自调度重排。此时UB内每次只计算一行还有大量空间没有使用可以考虑一次搬运多行进UB计算减少搬运指令的次数。Step 5 — UB 利用率优化目标充分利用 UB 剩余空间一次处理多行摊薄循环控制和 DMA setup 的固定成本。UB 空间模型calcMaxUbFactor函数将 UB 总空间按与ubFactor的关系分为固定部分和线性部分 $$ \text{UB}_{\text{total}} \text{fixedSize} \text{ubFactor} \times \text{linearCoef} $$各缓冲区的归属缓冲区与 ubFactor 关系大小公式说明xInQueue_线性 ×2rAlign × ubFactor × 2B × BUF_NUM输入 float16双缓冲yOutQueue_线性 ×2rAlign × ubFactor × 1B × BUF_NUM输出 int8双缓冲rmsBuf_线性ubFactor × 4B每行一个 float32 RMS 值gammaInQueue_固定rAlign × 2Bgamma 的 fp16 搬入缓冲gammaBuf_固定rAlign × 4Bgamma 转 float32 后常驻 UBrmsBuf_对齐固定BLOCK_BYTES (32B)块对齐余量对应代码src/5_ub_utilization.cpp:438-442int64_t rAlign (r BLOCK_BYTES - 1) / BLOCK_BYTES * BLOCK_BYTES; // 8192 int64_t fixedSize rAlign * (sizeof(half) sizeof(float)) BLOCK_BYTES; // 8192 × 6 32 49,184 B int64_t linearCoef rAlign * (sizeof(half)*2 sizeof(int8_t)*2) sizeof(float); // 8192 × 6 4 49,156 B int64_t maxUbFactor (ubSize - fixedSize) / linearCoef;三层循环与流水UB 划分决定了三层循环的分工AICore 间: blockIdx (64核 × 64行/核 4096行) └─ UB 循环: curUbLoops_ ⌈64/10⌉ 7 次最后一次处理 4 行 └─ 行内循环: vfLoopRNum_ ⌈8192/64⌉ 128 次尾处理逻辑curUbLoops_ CeilDiv(64, 10); // 7 ubFactorTail_ 64 - (7-1) × 10; // 4每轮 UB 循环的流水CopyInX(loop, ubFactor) // MTE2: GM → UB, blockCountubFactor ↓ Compute(ubFactor) // Vec: loopA(行) × i(向量段) 两层循环 ↓ CopyOut(loop, ubFactor) // MTE3: UB → GM, blockCountubFactor关键机制DataCopyExtParams.blockCount ubFactor让 MTE2 一次 DMA 传输合并搬运ubFactor行而非发起ubFactor次独立 DMA 请求。性能数据指标Step 4Step 5变化Task Duration54.3 us49.0 us-9.8%1.11xaiv_scalar_ratio20.0%9.4%-53.0%aiv_mte3_ratio28.0%10.5%-62.5%aiv_vec_ratio86.9%82.9%小幅下降aiv_mte2_ratio79.2%73.8%小幅下降vecmte2166.1%156.7%仍保持高重叠循环次数从 64 次降到 7 次固定成本被摊薄约 9 倍单元Step 4Step 5变化RVECEX88,26488,153基本不变RVECLD44,14644,146不变SCALAR5,7232,522-55.9%MTE25917-71.2%MTE35816-72.4%MTE2/MTE3 的次数明显减少。性能到达最优点。Step 6 — 二分累加动机尝试用二分累加提高中间精度、Halley 迭代压缩除法代价提升精度。算法原理1. 二分累加Pairwise Summation标准线性累加对长向量存在浮点误差积累误差界约为 $O(n \cdot \varepsilon)$其中 $n$ 为元素数量$\varepsilon$ 为机器精度。当 $n8192$、float16精度约为 $10^{-3}$ 时误差可达 8。二分累加将向量分成等长两段分别累加后相加误差界降至 $O(\log n \cdot \varepsilon)$$$ \text{Sum}(x_0, \ldots, x_{n-1}) \text{Sum}(x_0, \ldots, x_{n/2-1}) \text{Sum}(x_{n/2}, \ldots, x_{n-1}) $$具体实现将长度 $r8192$ 的行从折叠点binaryAddPoint4096处对折前后两段同步计算平方后立即相加再做一次 ReduceSum。相比线性累加减少了一半的累加步数误差界从 $O(8192\varepsilon)$ 降至 $O(4096\varepsilon)$再经 ReduceSum 后约为 $O(\log 64 \cdot \varepsilon) O(6\varepsilon)$。2. Halley 迭代替换 Div/Sqrt归一化步骤需要计算 $\text{rstd}_i 1/\sqrt{\text{var}_i}$。标准实现用Div Sqrt两条硬件指令而 Halley 迭代用Mul/Add链逼近 $f(x) x^{-1/2}$在向量流水中比除法更容易并行。Halley 迭代公式对 $1/\sqrt{x}$ 的高阶收敛逼近$$ y_0 \text{rsqrt}(x) \quad \text{初始估计} $$$$ y_{k1} y_k \cdot \left(\frac{3}{2} - \frac{x}{2} \cdot y_k^2\right) \quad \text{Newton-Raphson 步} $$在此基础上加一个 Halley 修正步以提高精度$$ e_k 1 - x \cdot y_k^2, \quad y_{k1} y_k \cdot \left(1 \frac{e_k}{2} \frac{3e_k^2}{8}\right) $$其中 $e_k 1 - \text{var} \cdot y_k^2$ 是当前估计的残差三阶收敛速度比 Newton 法更快。代码中的实现等价于先用Rsqrt获得初始估计 $y_0$再用一步 Newton-Raphson 一步 Halley 修正最终精度满足float32需求且全程只用 Mul/Add/Mula 指令避免了硬件 Div 指令的高延迟。代码实现// src/6_binary_sum.cpp — 二分累加核心BINARY_ADDtrue 分支 template bool BINARY_ADD false, int32_t LAST_LOOP_NUMS 1 __simd_vf__ inline void ComputeSquareReduceSum( __ubuf__ DATA_TYPE *xInAddr, __ubuf__ float *rmsAddr, uint16_t ubFactor) { if constexpr (BINARY_ADD) { // 对折点 binaryAddPoint 将行分为前后两段同步计算平方后相加 for (uint16_t loopA 0; loopA ubFactor; loopA) { for (uint16_t i 0; i flodAddLoops; i) { // 前段xInAddr[loopA*rAlign i*VL] DataCopyDATA_TYPE, DIST_UNPACK_B16(vregXIn1, xInAddr loopA * rAlign_ i * VL_B32_SIZE); Castfloat, DATA_TYPE(vregX1, vregXIn1, pregAll); // 后段xInAddr[loopA*rAlign binaryAddPoint i*VL] DataCopyDATA_TYPE, DIST_UNPACK_B16(vregXIn2, xInAddr loopA * rAlign_ tilingData_-binaryAddPoint i * VL_B32_SIZE); Castfloat, DATA_TYPE(vregX2, vregXIn2, preg); // 前后段平方后直接相加再 ReduceSum 存入 rmsAddr Mul(vregXQuared1, vregX1, vregX1, pregAll); Mul(vregXQuared2, vregX2, vregX2, preg); Add(vregReduceSum, vregXQuared1, vregXQuared2, pregAll); ReduceSum(vregReduceSum, vregReduceSum, pregAll); DataCopyfloat, DIST_FIRST_ELEMENT_B32(rmsAddr binaryAddLastLoops * VL * loopA i, vregReduceSum, preg); } } // 第二阶段对中间结果再做一次 ReduceSum得到最终标量 LocalMemBarVEC_STORE, VEC_LOAD(); for (uint16_t loopA 0; loopA ubFactor; loopA) { DataCopy(vregReduceSum, rmsAddr binaryAddLastLoops * 64 * loopA); ReduceSum(vregReduceSum, vregReduceSum, preg); DataCopyfloat, DIST_FIRST_ELEMENT_B32(rmsAddr loopA, vregReduceSum, preg); } } } // Halley 迭代逼近 1/sqrt(var) __simd_vf__ inline void ComputeRstdVf(__ubuf__ float *rmsAddr, int64_t ubFactor, ...) { DataCopy(var, rmsAddr i * VL_B32_SIZE); Muls(var, var, avgFactor, pregLoop); // var sum / r Adds(var, var, epsilon, pregLoop); // var epsilon Div(r, one, var, pregLoop); // r 1/var (初始估计辅助) Sqrt(y, r, pregLoop); // y0 sqrt(1/var) // Newton-Raphson: y1 y0 * (1.5 - 0.5 * var * y0^2) Muls(t, var, -0.5f, pregLoop); Mul(t, t, y, pregLoop); // t -0.5 * var * y0 Mula(t1, t, y, pregLoop); // t1 1.5 t * y0 Mul(rstd, y, t1, pregLoop); // rstd y1 // Halley 修正: e 1 - var * rstd^2, rstd rstd * e * 0.5 Muls(t3, var, -1.0f, pregLoop); Mula(s, t3, r, pregLoop); // s 1 - var * r残差估计 Muls(t4, rstd, -1.0f, pregLoop); Mula(r, t4, rstd, pregLoop); // r r - rstd^2修正项 Mula(s, var, r, pregLoop); // s var * r Mul(s, s, rstd, pregLoop); // e * rstd Mula(rstd, s, scalar1, pregLoop); // rstd e * rstd * 0.5 }二分累加提升精度稳定性减少依赖发挥指令双发能力。整体收益Step核心动作Duration (us)步骤加速比累计加速比0Naive 基线7693—1.0x1Gamma 预加载67901.13x1.13x2多核并行113.659.8x67.7x3VF MicroAPI84.11.35x91.5x4Double Buffer54.31.55x141.7x5UB 利用率49.01.11x157.0x6二分累加49.5≈1.0x155.4x【免费下载链接】cann-samples算子领域高性能实战演进样例与体系化调优知识库项目地址: https://gitcode.com/cann/cann-samples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻