1. 算子优化全景图
大家好,我是你们这趟算子优化之旅的向导。
说实话,每次带新人入门,我总会被问到同一个问题:「算子优化到底在优化什么?」
嗯,这个问题问得好。今天我们就从最底层开始,把算子优化的全景图彻底讲透。
1.1 什么是算子?
先说说算子的本质。
算子,说白了就是神经网络里的一个「计算单元」。你想想看,卷积、池化、全连接、激活函数……这些在模型里反复出现的操作,每一个都是一个算子。
我习惯把算子比作乐高积木。一个完整的模型,就是由这些积木搭起来的。积木本身的质量,直接决定了最终模型的性能。
核心定义:算子 = 输入数据 + 计算逻辑 + 输出数据。在硬件上,它对应着一组特定的指令序列。
举个例子,一个 3x3 的卷积算子,在 GPU 上执行时,会涉及:
- 从显存加载输入特征图
- 执行乘加运算(MAC)
- 写回输出特征图
就这么简单?嗯,表面上看是的。但真正让算子变「重」的,是数据搬运和内存访问的开销。我在项目中遇到过,一个看似简单的算子,因为内存访问模式不对,性能直接掉了 5 倍。
1.2 推理吞吐的核心瓶颈
好了,知道了算子是什么,我们来看看推理吞吐的瓶颈到底在哪。
我个人经验里,90% 的性能问题都出在下面三个地方:
| 瓶颈类型 | 具体表现 | 影响程度 |
|---|---|---|
| 计算瓶颈 | 算力利用率低,MAC 单元空转 | 高 |
| 访存瓶颈 | 数据搬运慢,带宽吃满 | 极高 |
| 调度瓶颈 | 算子之间等待,流水线停顿 | 中 |
为什么会这样?
我举个例子你就明白了。假设你在做卷积,计算本身可能只需要 1 毫秒,但把数据从内存搬到计算单元,却花了 5 毫秒。你想想看,这 5 毫秒里,计算单元一直在干等。
避坑指南:我曾经在一个项目里,花了整整两周优化卷积的计算逻辑,结果性能只提升了 10%。后来一分析,发现 80% 的时间都花在数据搬运上。从那以后,我每次优化算子,第一件事就是看内存访问模式。
1.3 优化方法论总览
那么,面对这些瓶颈,我们该怎么下手?
我总结了一套「三板斧」的方法论,你可以直接拿来用:
- 计算优化:减少冗余计算,提高算力利用率。比如算子融合、循环展开、向量化。
- 访存优化:减少数据搬运,提高缓存命中率。比如数据重排、tiling(分块)、预取。
- 调度优化:让算子之间并行起来,减少等待。比如流水线、异步执行、算子并行。
这三板斧不是孤立的,它们经常需要组合使用。我个人的习惯是:先做访存优化,再做计算优化,最后搞调度。因为访存往往是最大的瓶颈,搞定它,收益最明显。
小技巧:刚开始做算子优化时,别急着上手改代码。先用 profiler 跑一遍,看看热点在哪。我见过太多人凭感觉优化,结果改了半天,优化的根本不是瓶颈。
知识体系总览
为了让你更直观地理解算子优化的全貌,我画了一张图:
这张图把算子优化的核心逻辑串起来了。从算子定义出发,到识别瓶颈,再到应用方法论,每一步都有清晰的路径。
好了,这就是算子优化的全景图。说白了,就是搞清楚「你在优化什么」、「瓶颈在哪」、「怎么优化」这三个问题。