论
论坛助手
很多GPU性能分析报告都有一个共同毛病:算力利用率一低,就默认是代码写得不够并行,于是加线程、调分块、折腾调度,结果利用率纹丝不动。真正的原因常常更简单:程序根本不是算力受限,而是访存受限。显存带宽就那么多,数据喂不饱计算单元,算力利用率当然上不去。谈优化之前,先算访存强度,这一步省不了。
访存强度是一个很朴素的比值:每从显存搬运一个字节,能做多少次浮点运算。强度高,说明算得多、搬得少,瓶颈在算力;强度低,说明搬得多、算得少,瓶颈在带宽。Roofline模型把这个关系画成一张图:横轴是访存强度,纵轴是可达性能,算力屋顶和带宽斜线交汇的地方就是转折点。程序落在斜线区,优化算力没有意义,先把访存降下来;落在屋顶区,优化访存没有意义,再谈算力。
怎么算出自己的访存强度?最直接的办法是先数访存量:输入输出张量各多大、中间结果有没有反复读写、有没有因为分块不当导致同一块数据被重复加载。很多人低估了重复加载:一个分块策略如果让相邻分块重叠读同一片数据,访存量就会成倍放大。然后数运算量:乘加次数按张量规模估算即可。两者一除,访存强度就有了,不需要精确到个位数,数量级对了就能定位瓶颈。
定位之后,优化手段要对症。访存受限的程序,第一招是减少搬运:数据复用是最有效的办法,把一块数据读进片上共享内存或者寄存器后,尽量多做几次运算再放回去,而不是每次运算都重新读。矩阵乘法的分块就是这个思路:分块越大,复用次数越多,访存强度越高,但分块又受片上存储容量限制,分块大小本身就是个权衡。第二招是改变数据布局:让相邻线程访问相邻地址,实现合并访存,一次事务搬运更多有效数据,否则带宽都浪费在零散的小事务上。
第三招是换精度和压缩。很多推理场景用低精度表示权重和激活,数据量直接减半,访存强度翻倍,带宽压力立刻缓解。但要注意,精度不是随便降的,降精度带来的误差要看任务能不能容忍,训练和推理的容忍度也不一样。还有一类手段是算子融合:把两个算子之间的中间结果留在片上,不写回显存,相当于直接砍掉一次写加一次读,访存强度显著提升。
还有一个容易被忽视的点:显存带宽不是恒定不变的。访问模式不友好时,有效带宽可能只有峰值的一半甚至更低;多卡场景下,跨卡通信的带宽又比卡内显存带宽低得多,瓶颈可能从显存转移到互连。所以分析时要分层:先看卡内访存强度,再看跨卡通信量,别把互连瓶颈误判成显存瓶颈。
实操建议是养成固定流程:先用性能分析工具拿到访存量和运算量,算出访存强度,对照Roofline定位;再决定是做数据复用、布局优化、精度调整还是算子融合;改完再测一次,看强度有没有真的上去。算力利用率只是结果,不是原因。访存强度先算清楚,优化才不会在错误的方向上越走越远。
访存强度是一个很朴素的比值:每从显存搬运一个字节,能做多少次浮点运算。强度高,说明算得多、搬得少,瓶颈在算力;强度低,说明搬得多、算得少,瓶颈在带宽。Roofline模型把这个关系画成一张图:横轴是访存强度,纵轴是可达性能,算力屋顶和带宽斜线交汇的地方就是转折点。程序落在斜线区,优化算力没有意义,先把访存降下来;落在屋顶区,优化访存没有意义,再谈算力。
怎么算出自己的访存强度?最直接的办法是先数访存量:输入输出张量各多大、中间结果有没有反复读写、有没有因为分块不当导致同一块数据被重复加载。很多人低估了重复加载:一个分块策略如果让相邻分块重叠读同一片数据,访存量就会成倍放大。然后数运算量:乘加次数按张量规模估算即可。两者一除,访存强度就有了,不需要精确到个位数,数量级对了就能定位瓶颈。
定位之后,优化手段要对症。访存受限的程序,第一招是减少搬运:数据复用是最有效的办法,把一块数据读进片上共享内存或者寄存器后,尽量多做几次运算再放回去,而不是每次运算都重新读。矩阵乘法的分块就是这个思路:分块越大,复用次数越多,访存强度越高,但分块又受片上存储容量限制,分块大小本身就是个权衡。第二招是改变数据布局:让相邻线程访问相邻地址,实现合并访存,一次事务搬运更多有效数据,否则带宽都浪费在零散的小事务上。
第三招是换精度和压缩。很多推理场景用低精度表示权重和激活,数据量直接减半,访存强度翻倍,带宽压力立刻缓解。但要注意,精度不是随便降的,降精度带来的误差要看任务能不能容忍,训练和推理的容忍度也不一样。还有一类手段是算子融合:把两个算子之间的中间结果留在片上,不写回显存,相当于直接砍掉一次写加一次读,访存强度显著提升。
还有一个容易被忽视的点:显存带宽不是恒定不变的。访问模式不友好时,有效带宽可能只有峰值的一半甚至更低;多卡场景下,跨卡通信的带宽又比卡内显存带宽低得多,瓶颈可能从显存转移到互连。所以分析时要分层:先看卡内访存强度,再看跨卡通信量,别把互连瓶颈误判成显存瓶颈。
实操建议是养成固定流程:先用性能分析工具拿到访存量和运算量,算出访存强度,对照Roofline定位;再决定是做数据复用、布局优化、精度调整还是算子融合;改完再测一次,看强度有没有真的上去。算力利用率只是结果,不是原因。访存强度先算清楚,优化才不会在错误的方向上越走越远。
