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