gpu编程需要怎么做才能最小化内存访问,最大化运算并行,用并行运算的吞吐量弥补访存延时方面的劣势?
2021-08-07数码
减少内存访问
一种思路是
减少访问的数据量
:
优化算法本身。
降低数据精度
(如FP16/INT8,不过要考虑硬件是否擅长这种数据类型的后续计算)。
利用broadcast/shuffle
在wave/warp/subgroup间低开销共享数据
[1]
[2]
[3]
。
利用
push constant
消除不必要的访存,
一些硬件支持kernel启动前预先装载少量数据到GRF
。不过这通常意味着数据不能是memory resource,而是state中的一些常量(比如HLSL里就是root sginature里的constant,OpenCL里是非指针的arg)。
另一个思路是
减少昂贵的内存访问
:
不同类型的资源有不同的延迟与带宽
,比如sampler吞吐可能不会比untyped resource大(压缩的资源除外)但延迟却更高,texture还有格式排布的要求,一些format还可能因为硬件不支持而悄悄被驱动做转换。
优化global访存到SLM
,不过数据共享不够多的话可能意义不大,因为很多时候
GPU自己会做cache
,而用SLM反而增加
同步开销
。
同样是atomic,一些硬件做SLM-based比global-base要开销小
[4]
。更进一步,甚至可以先在wave内做reduce/scan再由一个线程进行atomic操作
[5]
。
一些平台
对某些访问的延迟额外高
,尽量避免。比如intel的gen11以前SLM并不快,gen12的typeduav很慢。
优化数据排布
,coalescence read能减少访存的浪费
[6]
,有规律的访问也能降低cache miss的惩罚。
此外还有一些trick来降低访存延迟:
一次性读取更多数据
可能能有更低的延迟。很多硬件一条指令能读取不同长度的数据,一次性读更大的数据就意味着更少的指令(以及更少的计算offset的开销)。哪怕数据带宽没改变,最终延迟上可能还是比多条指令低一些。
一些硬件有独特的读取指令
,同样大小的数据读出不同的layout,减少后续transpose/reshape的开销。比如intel的blockread读4个float跟普通read读float4是不同的意义
[7]
……
增加运算并行
对于单个线程,关键就是增加ILP:
减少数据间依赖
(可以考虑展开循环)。
有时候
寄存器压力也会降低ILP
。比如8个空闲GRF理论上可以issue8个read到队列,然后后续一并计算。但考虑到计算offset要占用GRF,后续计算的中间数据也要占用GRF,于是不得不只读一部分马上wait然后做运算……这个要根据asm去分析了。
有时候
分支也会降低ILP
。还是比如读8个数据按需做计算,用上ifelse,可能
编译器会把每个数据和处理单独放进basic block
,这样
多个读取的延迟都不能被互相掩盖了
。这个也得看asm,而且编译器的心思不好猜所以不方便做优化。常见的做法有condition转化为and或乘法,或者读完再if替换成0,但都见过被编译器识破的,反而有时候把读取和计算分开两个循环,编译器就呆住不动了……而且对于ifelse在不diverge的时候反而是优点。
对于多个线程,通常来说线程越多硬件越能发掘出并行的潜力:
对于一些带循环的算法,可以
考虑把循环拆分
到多个threadgroup计算,最后再reduce,这在任务比较小没法充分利用硬件资源时更管用,但也要记住同步与SLM的开销。
对于多个任务间的并行,还可以考虑交织访存运算为主的kernel来合理利用不同组件。
优化是个深坑,是个无底洞。
如果绕不开,不要心存侥幸,乖乖看asm上工具做分析,源代码、dxbc、dxil这种层面的东西很可能没用的……
参考
-
^
warp shuffle
https://developer.nvidia.com/blog/using-cuda-warp-level-primitives/
-
^
wave shuffle
https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/wavereadlaneat
-
^
subgroup shuffle
https://github.com/KhronosGroup/OpenCL-Docs/blob/master/ext/cl_khr_subgroup_extensions.asciidoc#general-purpose-shuffles
-
^
shared atomic
https://developer.nvidia.com/blog/gpu-pro-tip-fast-histograms-using-shared-atomics-maxwell/
-
^
wave reduce atomic
https://github.com/microsoft/DirectX-Specs/blob/master/d3d/HLSL_ShaderModel6_5.md#example-usage
-
^
coalescence read
https://developer.nvidia.com/blog/how-access-global-memory-efficiently-cuda-c-kernels/
-
^
block read
https://www.khronos.org/registry/OpenCL/extensions/intel/cl_intel_subgroups.html#_add_a_new_p_6_13_x_sub_group_read_and_write_functions