4. 核函数吞吐指标
各位同学,今天我们来聊聊核函数吞吐的三个核心指标。说实话,我刚开始做GPU优化那会儿,看到Occupancy、带宽利用率、计算利用率这些词,总觉得是理论家搞出来的概念。直到有一次,我花了两周调一个kernel,性能死活上不去,最后发现是Occupancy虚高、带宽根本没跑满——嗯,从那以后我再也不敢小看这些指标了。
Occupancy:你到底用了几成力?
Occupancy,说白了就是SM上活跃的warp数量占理论最大值的比例。我习惯这么理解:GPU有好多计算单元,你同时塞进去的线程越多,隐藏延迟的能力就越强。
Occupancy = 活跃warp数 / 最大warp数
比如一个SM最多能放64个warp,你只放了32个,那Occupancy就是50%。
但这里有个坑——高Occupancy不等于高性能。我曾经调过一个矩阵乘法的kernel,Occupancy从50%拉到100%,性能反而下降了。为什么?因为每个线程用的寄存器太多,Occupancy高了,每个线程分到的资源反而少了,计算效率反而下降。
我个人习惯的做法是:先跑一个Occupancy扫描,看看不同block大小下的实际Occupancy。CUDA提供了现成的工具:
// 用CUDA Occupancy API计算理论Occupancy
int minGridSize, blockSize;
cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize,
myKernel, 0, 0);
// blockSize就是推荐值,但别全信,还得实测
我的经验:Occupancy在50%-75%之间往往是最优区间。低于50%,延迟隐藏不够;高于75%,资源争抢严重。当然,具体还得看你的kernel是计算密集还是访存密集。
Bandwidth Utilization:你的显存跑满了吗?
带宽利用率,这是我最看重的指标之一。你想想看,GPU的显存带宽动辄几百GB/s,如果你的kernel只用了10%,那再高的Occupancy也是白搭。
怎么算?很简单:
Bandwidth Utilization = 实际带宽 / 理论峰值带宽
实际带宽可以用nvprof或者Nsight来抓。我记得有一次调一个图像处理的kernel,带宽利用率只有20%。我一看,每个线程只读一个float,然后写一个float,这访存模式太差了。
优化后,我让每个线程连续读4个float,再用向量化加载——带宽利用率直接飙到75%。
注意:带宽利用率高不一定就好。我曾经见过一个kernel,带宽利用率95%,但计算利用率只有5%。这说明它是个纯访存瓶颈的kernel,计算单元都在闲着。这时候你要考虑的是减少访存量,而不是继续优化带宽。
我一般把带宽利用率分成三个档次:
| 利用率范围 | 状态 | 建议 |
|---|---|---|
| < 30% | 很差 | 检查访存模式,考虑合并访问 |
| 30% - 70% | 一般 | 可以优化,但先看看计算利用率 |
| > 70% | 良好 | 带宽接近瓶颈,考虑减少访存量 |
Compute Utilization:计算单元在忙啥?
计算利用率,说白了就是SM上的计算单元(CUDA Core、Tensor Core)有多少时间在干活。这个指标和带宽利用率是相辅相成的。
我习惯用Nsight Compute看两个东西:
- 算术利用率(Arithmetic Utilization):计算指令占所有指令的比例
- 流水线占用率(Pipeline Utilization):计算流水线有多少周期是满的
举个例子,我调过一个卷积kernel,计算利用率只有20%。一看profile结果,大部分时间花在了访存等待上。说白了,这是个访存瓶颈的kernel,计算单元都在等数据。
怎么优化?我当时的做法是:
- 先用shared memory缓存输入数据,减少全局访存
- 再用向量化加载,提高带宽利用率
- 最后调整block大小,让Occupancy和计算利用率匹配
优化后,计算利用率从20%涨到了65%,整体性能提升了3倍。
一个小技巧:如果你看到计算利用率低、带宽利用率也低,那大概率是Occupancy太低,或者kernel启动开销太大。这时候先别急着优化算法,先看看你的grid和block配置是不是合理。
三个指标的关系:一个三角平衡
这三个指标不是孤立的。我画了一张图,帮你理解它们的关系:
你看,这三个指标构成了一个三角。任何一个指标太低,都会拖累整体性能。我见过太多人只盯着Occupancy看,结果带宽利用率和计算利用率都惨不忍睹。
我个人习惯的做法是:
- 先看带宽利用率——如果低于30%,先优化访存
- 再看计算利用率——如果低于20%,考虑减少计算量或提高并行度
- 最后看Occupancy——如果低于50%,调整block大小
当然,这只是个通用流程。具体问题还得具体分析。
总结一下:
- Occupancy:衡量你用了多少线程资源
- Bandwidth Utilization:衡量你用了多少显存带宽
- Compute Utilization:衡量你用了多少计算能力
- 三者要平衡,别只盯着一个指标
好了,这一章的内容就到这里。记住,指标只是工具,真正重要的是理解你的kernel在硬件上是怎么跑的。下次你调kernel的时候,不妨先看看这三个指标,再动手优化——相信我,能少走很多弯路。