一、算子融合概述

大家好,我是这次课程的主讲。今天咱们聊聊算子融合——这个在推理加速里绕不开的话题。

先说说我自己的经历。几年前我在做移动端模型部署时,发现一个奇怪的现象:明明模型计算量不大,跑起来却慢得离谱。后来一分析,发现大部分时间都花在了数据搬运和 kernel 启动上,真正算的时间反而很少。嗯,这就是典型的「计算密集度不够」的问题。而算子融合,恰恰就是解决这类问题的利器。

什么是算子融合

说白了,算子融合就是把多个连续的小算子合并成一个大的算子。比如 Conv + BN + ReLU 这三个操作,原本要分别调用三次 kernel,融合后只需要一次。

你想想看,原本每个算子都要从显存读数据、算完写回去、下一个算子再读……这一来一回,带宽全浪费了。融合之后,中间结果直接留在寄存器或共享内存里,省掉了大量访存开销。

核心思想:减少 kernel launch 次数 + 减少中间结果的显存读写。

为什么需要算子融合

我遇到过不少同学问:「现在硬件算力这么强,为什么还要费劲做融合?」

原因其实很简单——瓶颈往往不在计算,而在访存。

举个例子。假设你有一个 Conv 层,输出 256 个 channel,紧接着一个 Elementwise Add。如果不融合,Conv 算完要把 256 个 channel 的结果写回显存,Add 再读进来。这一写一读,数据量可能达到几十 MB。而融合后,Conv 算完一个 channel 就直接做 Add,数据根本不用出芯片。

我个人习惯把这种优化叫做「省掉中间商赚差价」。在推理场景下,尤其是 batch size 较小的时候,访存开销占比非常高。融合能直接砍掉这部分浪费。

融合的收益

收益主要体现在三个方面:

  • 延迟降低:kernel launch 次数减少,访存次数减少,推理时间明显缩短。我在项目中见过融合后延迟降低 30%~50% 的情况。
  • 带宽节省:中间结果不落显存,对带宽敏感的设备(比如手机、边缘设备)效果尤其明显。
  • 计算密度提升:每个 kernel 做更多计算,GPU 利用率更高。
对比项 未融合 融合后
kernel 启动次数 3 次 1 次
中间结果访存 2 次写 + 2 次读 0 次
典型延迟 1.0x 0.6x ~ 0.7x

融合的挑战

当然,融合也不是万能的。我曾经踩过一个坑:把两个计算模式差异很大的算子强行融合,结果寄存器压力过大,反而变慢了。

避坑指南:我曾经把 Conv 和一个复杂的 LayerNorm 融合,结果寄存器 spill 严重,性能直接倒退 20%。后来我学乖了——融合前一定要先分析算子的计算模式和访存模式。

具体来说,融合面临几个挑战:

  • 寄存器压力:融合后的 kernel 需要更多寄存器来存中间结果,超出硬件限制就会 spill 到 local memory,得不偿失。
  • 计算模式不匹配:比如一个计算密集型算子和一个访存密集型算子融合,可能互相拖累。
  • 编译器支持:不是所有编译器都能自动做融合,手动融合又容易出错。
  • 调试困难:融合后的 kernel 逻辑更复杂,出了问题很难定位。

知识体系总览

下面这张图是我自己整理的算子融合知识框架,方便你快速把握全局:

算子融合 为什么需要? 访存瓶颈严重 kernel启动开销大 计算密度低 收益 延迟降低 30%~50% 带宽节省 GPU利用率提升 挑战 寄存器压力 模式不匹配 编译器支持 调试困难

我的建议:刚开始接触算子融合时,别贪多。先从 Conv + BN + ReLU 这种经典组合入手,跑通一个融合 kernel,感受一下性能变化。等有了手感,再尝试更复杂的融合模式。

好了,这一节就到这里。算子融合的核心逻辑其实不复杂——就是「减少中间环节,提升数据复用」。但具体怎么落地,里面门道不少。后面的章节我会带大家一步步深入。

可可精品资料库