首页优化地图方法索引基础概念
返回技术汇总表 ↗

M009 · 独立详解

本地算子融合:为什么少写一次中间结果就可能更快

从逐元素加法与ReLU的两遍读写开始,理解中间张量、融合内核、归约和训练反向的边界。

算子与编译内存与数据搬运详解已编写
视频讲解待制作

这篇文章的小视频尚未制作;可以先阅读下方文字详解。

本页目录

01 · 数学很简单,执行却要来回搬数据

看一段小计算:先把两个同形状向量相加,A=X+B;再把负数变为0,Y=ReLU(A)。数学上,每个位置只需要一次加法和一次比较。但分开执行时,第一个设备内核写出A,第二个内核重新读取A。

算子是框架中的逻辑操作,例如加法、ReLU、矩阵乘法;kernel是实际提交给设备的一段执行程序。一个逻辑算子可以启动多个kernel,多个逻辑算子也可以被合进一个kernel。先把这两个概念分开,就容易理解融合。

如果计算本身很少,中间数据读写和重复启动可能占了主要成本。融合尝试让相邻操作在同一段执行中衔接,把本来要写回再读入的临时结果留在寄存器或片上存储中。

依据:PyTorch · Performance Tuning Guide ↗

02 · 用四个数走完未融合流程

教学示意 · 不代表设备实测
X=[-3, 1, 2, -1]
B=[ 1, 2,-4,  2]

内核1:读取X和B → 相加 → 写出A=[-2,3,-2,1]
内核2:读取A → ReLU → 写出Y=[0,3,0,1]

融合:读取X和B → 每个位置相加后立即ReLU → 写出Y
位置1:1+2=3,max(3,0)=3

融合没有跳过加法或ReLU,也没有改变它们的依赖。改变的是执行边界和临时值的存放:位置1的和算出来后,紧接着比较,最后只保存3。

03 · 到底省掉多少读写

为了便于计数,假设X、B、A、Y都含N个元素,每元素b字节;B是同形状输入,不是常见的通道广播偏置。逐元素分离基线逻辑上读X、B并写A,再读A并写Y,共5Nb字节;融合读X、B并写Y,共3Nb字节。

执行逻辑读写启动边界
分开相加与ReLU读X、B;写A;读A;写Y = 5Nb两个内核
融合,不需保存A读X、B;写Y = 3Nb示例中一个内核
融合,但其他消费者需要A读X、B;写A、Y = 4Nb示例中仍可一个内核

相加+ReLU:省掉的是哪次读写

X、B为同形状输入,按完整物化张量计逻辑字节;不含缓存、广播、工作区和训练保存。字节减少比例不是实测加速比。

04 · 融合内核如何逐块完成这些工作

教学示意 · 不代表设备实测
for each assigned tile:
    x = load(X[tile])
    b = load(B[tile])
    a = x + b           # 暂存在寄存器等片上资源
    y = maximum(a, 0)
    store(Y[tile], y)
# 尾部不足一块时,用边界mask避免越界

每个位置的计算独立,适合按连续小块分配。实际内核还要选择并行粒度、向量化、对齐和边界处理。这里并没有承诺所有值都进入共享内存:寄存器、共享内存、缓存和昇腾片上缓冲各有容量与用途。

有时还可把偏置、缩放或激活接在矩阵乘法的输出阶段,称为epilogue,即主计算后的收尾。这需要矩阵内核或库支持相应语义;不能用Python把表达式写在一行就认为已经融合。

依据:PyTorch 2.14 · torch.compile参数、模式与后端 ↗ · Triton · Fused Softmax官方教程 ↗

05 · 遇到归约,融合为何更难

相加和ReLU只看当前位置;Softmax、LayerNorm、RMSNorm则需要一行或一个范围的统计量。例如Softmax要先获得行最大值和指数和,再给每个元素归一化。融合要协调这类跨元素依赖。

Triton的融合Softmax教程以一行能够放入片上资源的矩阵为主要例子:读入一行,在片上做统计和归一化,再写结果。行太长时,需要别的分块或多阶段方案,不能把“全部操作塞进一个内核”当作普遍最优解。

FlashAttention也减少大型中间结果的物化,但它还有在线Softmax、分块Attention和训练重算等设计;“算子融合”可以描述其一部分机制,不能替代整篇算法解释。

依据:Triton · Fused Softmax官方教程 ↗ · FlashAttention 原始论文 ↗

06 · 前向省下A,训练反向还怎么算

上例在A不为0的位置,ReLU反向将dY传给正值位置,负值位置梯度为0。若dY=[1,2,3,4],则dX=dB=[0,2,0,4]。反向仍需要判断对应位置是否激活。

在这个简单例子中,可以从Y>0得到掩码,因此未必需要完整保存A。更复杂的激活函数可能需要输入、中间统计或重计算;融合前向不自动等于不保存激活,也不保证反向同样融合。

07 · 为什么有些相邻操作不值得融合

  • A有其他消费者:为了它们仍需保存A,收益会改变。
  • 融合扩大寄存器或片上缓冲占用:可能降低同时驻留任务数,甚至溢出到外部存储。
  • 操作需要全局同步或不同最佳并行布局:强行融合可能比各用专用内核更慢。
  • 广播、非连续布局、原地修改与别名:必须维持正确索引及读写顺序。
  • 基线已有优化:应对比框架实际发出的内核,而非假设每个Python操作都单独执行。

08 · 与图编译、图重放和MC²有什么不同

方法改变哪里没有自动做到什么
本地融合同设备相邻计算的数据流与执行边界不自动消除跨卡通信。
图编译 M018分析计算图并选择融合、布局、内核等编译成一张图不保证只有一个kernel。
执行图重放 M019复用已记录的任务与依赖,减少下发重放可以保留原来的多个kernel和中间张量。
通算融合 M027把通信和计算一起分块、组织进度不能按本地加法+ReLU的读写模型理解。

这些方法可以组合。例如先编译得到融合内核,再把多个内核及依赖捕获下来重放。每一层收益要分别测量,不能把几次局部加速比直接相乘。

09 · 从正确的示例走向真实实现

下方CPU脚本比较分离和融合的输出、逻辑读写计数与反向梯度,并用有限差分校验远离ReLU零点的输入梯度。它使用Python循环,不代表设备内核,也不测速度。

PyTorch接入片段 · 需要匹配的PyTorch与设备后端
def pointwise(x, b):
    return torch.relu(x + b)

optimized = torch.compile(pointwise)
# 这是编译候选,不是“已融合”的证据。
# 用Profiler或生成代码核对真实kernel与中间分配。

NVIDIA环境可从Inductor或Triton融合教程学习;昇腾图编译可从TorchAir入口核对后端支持。两家都要检查实际算子、dtype、shape和布局,不能把CUDA/Triton内核代码直接当作NPU实现。

依据:PyTorch · Performance Tuning Guide ↗ · Ascend · TorchAir官方仓库与NPU后端入口 ↗

10 · 哪些指标能证明优化有效

  • 固定输入形状、dtype和质量要求,核对数值及训练梯度。
  • 记录实际kernel数量、临时张量峰值与内存流量;逻辑计数与实测分开。
  • 同步后测设备时间,也测包含调度的端到端时间。
  • 观察长行、尾块、非连续输入与其他消费者,避免只测最有利形状。
  • 把局部收益放回模型流程:该区域若只占总时间5%,其提速对整步仍有限。

自查:为什么融合仍做同样数学运算却可能更快?A必须给另一分支使用时还能省哪些读写?为什么前向不保存A,并不意味着所有激活函数都能照搬这个办法?

流程位置与关联方法

这个方法可能出现在多个位置;计算线包含模型计算与算子实现,执行线包含运行时、内存和通信。

资料与核查范围

原理依据以原始论文和官方文档为主;算例与时间线是教学推导。文档核查日期:2026-10-10。引用 main / latest 文档时,实际接口以安装版本为准。

代码为接入片段或教学伪代码;本页没有声称完成 NVIDIA / 昇腾硬件实测。

← 返回总表中的 M009继续阅读:MoE 计算优化:Grouped GEMM 与重排融合 →
AI Infra 知识库 · 总览 → 独立讲解网站更新 2026-10-08
搜索知识库

搜索方法与基础概念 · Esc 关闭