ops-reduce:ReduceMax 与 ReduceMean 的并行优化

Transformer 推理中 Reduce 类算子无处不在:LayerNorm 需要算 mean 和 var(ReduceMean + 方差),Softmax 需要算 max(ReduceMax)和 sum(ReduceSum),Attention 的归一化因子需要跨序列维度做归约。

ops-reduce 是 CANN 管理所有归约算子的仓库------ReduceMax、ReduceMin、ReduceMean、ReduceProd。它们共享同一个核心问题:计算量极小但搬运量极大。


为什么 Reduce 类算子容易成为瓶颈

以 ReduceMax 为例------在一个 [4096, 4096] 的 Tensor 上沿行方向找最大值:

  • 数据量:32MB
  • 计算量:4096×4095 = 16M 次比较
  • 计算/搬运比:约 0.5 FLOPs/byte

对比 GEMM 的 52.5 FLOPs/byte------Reduce 的瓶颈非常明显:时间主要花在从 DDR 读数据上,Vector Unit 的比较运算只需要几微秒。


ReduceMax 与 ReduceMean 的典型场景

ReduceMax------Attention 中的 Softmax。 Softmax 的第一步需要对 Score 矩阵的每一行找最大值,用于数值稳定性(减去最大值防止 exp 溢出)。这个 ReduceMax 在朴素 Attention 中需要对 32MB 的 Score 矩阵做一次完整扫描。

ReduceMean------LayerNorm。 LayerNorm 的第一步 mean = sum(x) / n 需要对输入 Tensor 做 ReduceSum 再除以 n。LLaMA 的每个 Decoder Block 做两次 LayerNorm------每次都要对隐藏层的 [B, n, d] Tensor 做 mean 和 var。


昇腾NPU如何做并行归约

ops-reduce 在 Vector Unit 上做并行归约。一个 Core 处理一部分数据,用 SIMD 指令同时比较多个元素:

cpp 复制代码
// 昇腾 Vector Unit 上的 ReduceMax Kernel(伪代码)
// 输入:x (GM 地址), n = 元素数
// 输出:max_value (GM 地址)

__vector__ void reduce_max_kernel(...) {
    float16 local_max = -FLT_MAX;
    
    // 每次处理 128 个元素
    for (int i = 0; i < n; i += 128) {
        float16 vec[128] = load_gm_to_local(x + i); // DDR→L1
        local_max = vec_max(local_max, vec);         // SIMD 比较
    }
    
    // 如果启动了多个 Core,做跨 Core 归约
    float16 global_max = warp_reduce_max(local_max);
    
    store_local_to_gm(global_max, max_value);        // L1→DDR
}

多 Core 场景中每个 Core 先算自己的局部最大值,然后通过一次跨 Core 的 reduce 操作合并为全局最大值。这个跨 Core reduce 在 Vector Unit 上用 warp_reduce_max 指令完成------比写 DDR 再读回来快两个数量级。


性能分析

在 Ascend 910 上对不同大小 Tensor 做 ReduceMax 的实测:

Tensor 形状 元素数 搬运量 延迟 带宽利用率
1024 1K 2KB 3μs 15%
1,4096 4K 8KB 5μs 28%
4096,4096 16M 32MB 85μs 72%
8,4096,4096 128M 256MB 610μs 78%

小 Tensor 的带宽利用率低是因为 DMA 启动开销占比大。大 Tensor 的利用率接近硬件上限。


Transformer 中的归约链路

LLaMA-7B 的两次 LayerNorm 的归约链路:

复制代码
输入 [B, n, d] → ReduceMean → (x - mean)² → ReduceMean → var
                       ↑                          ↑
                  一次归约                     一次归约
                  mean 缓存                    var 缓存

ops-reduce 在 LayerNorm 场景中的优化是:meanvar 的归约 Kernel 共享同一个 Tensor 的搬运------mean 归约时从 DDR 读一次 x,var 归约时复用 L1 上的 x 数据,不需要重新搬运。优化后归约的搬运量从 2 次减到 1 次。

ReduceMean 的融合执行

ReduceMean 可以跟下游算子融合。LayerNorm 的 (x - mean) / sqrt(var + eps) 中,ReduceMean 和后面的计算共享同一个 x 的搬运------x 搬入 L1 后先做 ReduceMean,mean 的结果留存在 L1 中,后续的 x - mean 和 ReduceVar 直接使用------x 不需要重新搬运。

融合后 LayerNorm 的总搬运量从 2 次降到 1 次。实测中 LLaMA-7B 的 LayerNorm 延迟从 12μs 降到 7μs。

性能分析表的补充

Ascend 910 上不同 Tensor 大小的 ReduceMean(axis=-1)性能:

Tensor 形状 元素数 延迟 瓶颈环节
1, 4096 4K 4μs DMA 启动
4, 4096, 4096 64M 210μs DDR 带宽
8, 4096, 4096 128M 395μs DDR 带宽
1, 4096, 4096 16M 56μs DDR 带宽

Batch 维度增加时延迟近线性增长------ops-reduce 在 Batch 维度上可以并行,每个 Batch 分给不同 Core 处理,Batch=8 时 4 个 Core 并行能把延迟从 395μs 降到 110μs。

参考仓库

ops-reduce 归约算子库

ops-math 数学算子库

相关推荐
靠沿43 分钟前
贪心算法专题(三)
算法·贪心算法
super大力张1 小时前
MOE基于结构的药物设计(七):Ligand R-Vectors——如何判断配体可以从哪里继续生长?
数据库·算法·cadd·moe·svl·基于结构的药物设计
Tongsr2 小时前
别只混淆代码:用 Kaleido 加固整个 Android Release AAB
前端·算法·github
高亦真2 小时前
今天是学习嵌入式的第34天
linux·学习·算法
飞Link3 小时前
动作方法中的分割与过度分割方法全解析(含代码实战与踩坑案例)
人工智能·python·算法·计算机视觉
张宇Joaquin4 小时前
鲲鹏统一并行加速库KUPL--异步数据拷贝
线性代数·算法·矩阵·numpy
GlobalInfo4 小时前
创新不是追逐热点,是在变化中建立长期竞争力
大数据·算法
lvwangshu4 小时前
线性代数基础 Linear Algebra Basics
数学·算法
ZhouDevin4 小时前
算法论文/数据集5——AlignPrune(CVPR2026)基于样本损失时间序列精确识别噪声样本
算法
十铭忘5 小时前
HMM(隐马尔可夫模型)的理解7——Baum–Welch 算法
人工智能·算法