Skip to content

19. Operator Fusion Introduction | 算子融合导论

难度: Medium | 环境: CPU-first | 标签: Operator Fusion, Compiler, Performance | 目标人群: 编译优化入门者

🚀 云端运行环境

本章节的实战代码可以点击以下链接在免费 GPU 算力平台上直接运行:

Open In ColabOpen In Studio (国内推荐:魔搭社区免费实例)

这一页把“为什么要融合算子”讲清楚,重点不是编译器名词,而是知道减少中间张量落地为什么能直接影响性能。

关键词: fusion, memory traffic, intermediate tensor

前置阅读

导语: 先把执行模型和 block 级编程对齐,再看算子融合为什么能减少中间结果开销会更顺。

相关阅读

导语: 把算子融合和 Triton kernel 的实现放一起看,能更直观理解优化代价。

Q1:为什么算子之间的中间结果会这么贵?

点击展开查看解析

中间结果贵,不只是因为它存在,而是因为它往往要被写回内存、再被下一步算子读回来。

如果一个计算图拆成很多小算子,每个算子都产生临时张量,那么这些张量就会频繁地在计算单元和内存之间往返。这样一来,真正耗时的可能不是计算,而是 memory traffic。

所以算子融合的第一层价值,是减少中间张量的落地次数。少一次写回、少一次读取,通常就意味着更少的带宽压力和更高的吞吐。

### Q1小验证:中间张量为什么拖慢系统

先记住“写回 + 再读回”这件事本身就很贵。

python
def memory_traffic(num_ops, tensor_mb):
    # 每个算子都会把临时结果写回并再次读入。
    writes = max(num_ops - 1, 0)
    reads = max(num_ops - 1, 0)
    return (writes + reads) * tensor_mb

unfused = memory_traffic(4, 64)
fused = memory_traffic(1, 64)
print('unfused traffic MB:', unfused)
print('fused traffic MB:', fused)
print('reduction factor:', unfused // max(fused, 1))

Q2:算子融合为什么能改善数据局部性?

点击展开查看解析

融合把原本分开的多个步骤合成一条更连续的执行链。

这样做的结果是:

  • 中间结果更容易留在片上;
  • 同一批数据更容易被连续复用;
  • 数据搬运和计算之间的间隔更短。

数据局部性提升后,硬件就更容易把已有数据重复利用起来,而不是每一步都重新去 HBM 找一遍。这就是为什么融合不只是“少几个函数”,而是直接改变了数据流路径。

### Q2小验证:局部性为什么和融合有关

把“连续使用同一批数据”这件事记牢,就能理解融合的核心收益。

python
def locality_score(reuse_steps, live_range):
    # 同一批数据被连续复用得越久、在片上停留越久,局部性越好。
    return reuse_steps * live_range

plans = {
    'separate': locality_score(1, 1),
    'partially_fused': locality_score(2, 2),
    'fully_fused': locality_score(3, 3),
}
for name, score in plans.items():
    print(name, '->', score)
print('best:', max(plans, key=plans.get))

Q3:为什么融合和编译器、kernel 实现会绑定在一起?

点击展开查看解析

融合不是简单地把两个函数手动拼起来,而是需要在编译和实现层上重新安排计算顺序、临时变量和内存访问。

编译器能帮助决定哪些操作可以合并,kernel 实现则决定合并后如何在硬件上真正落地。比如:

  • 哪些中间值可以直接在寄存器或 shared memory 中使用;
  • 哪些步骤可以不必回到 HBM;
  • 哪些访存模式可以一起优化。

所以融合是一个“编译决策 + kernel 实现”的组合问题。理解这一点后,后面看 Triton、CUDA 或 AI compiler 里的 fusion 逻辑就不会把它当成纯术语。

### Q3小验证:融合到底要改什么

先想内存路径,再想代码结构。

python
def fusion_decision(compiler_can_fuse, kernel_can_hold, needs_reorder):
    # 三个条件都满足时,融合才更可能真正落地。
    return compiler_can_fuse and kernel_can_hold and needs_reorder

cases = [
    ('easy', True, True, True),
    ('compiler_only', True, False, True),
    ('kernel_limit', True, True, False),
]
for name, c, k, r in cases:
    print(name, '->', 'fuse' if fusion_decision(c, k, r) else 'defer')

⚠️ 常见误区

  • 算子融合不是让代码变长,而是让中间结果少落地。
  • 融合的目标不是“看上去更高级”,而是减少 memory traffic。
  • 编译器和 kernel 都会参与融合,不能只看一侧。
  • 先看数据流,再看代码名,通常更容易判断是否真的值得融合。

Released under the MIT License.