當前位置: 華文問答 > 數碼

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這種層面的東西很可能沒用的……

    參考

    1. ^ warp shuffle https://developer.nvidia.com/blog/using-cuda-warp-level-primitives/
    2. ^ wave shuffle https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/wavereadlaneat
    3. ^ subgroup shuffle https://github.com/KhronosGroup/OpenCL-Docs/blob/master/ext/cl_khr_subgroup_extensions.asciidoc#general-purpose-shuffles
    4. ^ shared atomic https://developer.nvidia.com/blog/gpu-pro-tip-fast-histograms-using-shared-atomics-maxwell/
    5. ^ wave reduce atomic https://github.com/microsoft/DirectX-Specs/blob/master/d3d/HLSL_ShaderModel6_5.md#example-usage
    6. ^ coalescence read https://developer.nvidia.com/blog/how-access-global-memory-efficiently-cuda-c-kernels/
    7. ^ 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