通過將計(jì)算密集型部件卸載到 GPU 上,可以大大加快許多工作負(fù)載。在 CUDA 術(shù)語中,這被稱為啟動內(nèi)核。當(dāng)這些內(nèi)核很多且持續(xù)時(shí)間很短時(shí),啟動開銷有時(shí)會成為一個(gè)問題。
CUDA Graphs提供了一種減少開銷的方法。圖形之所以有效,是因?yàn)樗鼈儗⑷我鈹?shù)量的異步 CUDA API 調(diào)用(包括內(nèi)核啟動)組合到一個(gè)只需要一次啟動的操作中。它們在創(chuàng)建時(shí)確實(shí)會產(chǎn)生一些開銷,因此它們的最大好處來自多次重用。
在 ToolkitVersion10 中引入 CUDA 圖形時(shí),可以對其進(jìn)行更新,以反映其實(shí)例化中的一些細(xì)微變化。此后,此類更新操作的覆蓋范圍和效率顯著提高。在這篇文章中,我描述了一些通過使用 CUDA 圖來提高實(shí)際應(yīng)用程序性能的場景,其中一些場景包括圖更新功能。
上下文
考慮一個(gè)應(yīng)用程序,該函數(shù)具有啟動許多短運(yùn)行內(nèi)核的功能,例如:
如果每次遇到此函數(shù)時(shí)都以相同的方式執(zhí)行,則可以使用流捕獲將其轉(zhuǎn)換為 CUDA 圖。在本例中,必須引入一個(gè)開關(guān)布爾值captured,以指示是否已經(jīng)創(chuàng)建了圖形。將此開關(guān)的解除 Clara 操作和初始化放在源代碼中,使其范圍包括對函數(shù)tight_loop的每次調(diào)用。
接下來,用代碼包裝函數(shù)的任何實(shí)際調(diào)用,以創(chuàng)建對應(yīng)的 CUDA 圖(如果它不存在),然后啟動該圖。
對 tight _循環(huán)函數(shù)的調(diào)用實(shí)際上并不執(zhí)行任何內(nèi)核啟動或其他 CUDA 操作。它只記錄所有這些操作并將它們存儲在數(shù)據(jù)結(jié)構(gòu)中。
關(guān)注啟動內(nèi)核的函數(shù)。在實(shí)際應(yīng)用中,它看起來像以下代碼:
顯然,如果函數(shù)的參數(shù)在連續(xù)調(diào)用后發(fā)生變化,那么表示 GPU 內(nèi)部工作的 CUDA 圖也應(yīng)該發(fā)生變化。不能重復(fù)使用原始圖形。但是,假設(shè)多次遇到相同的函數(shù)參數(shù)集,您至少可以通過兩種不同的方式來處理這種情況:保存和識別圖形或更新圖形。
保存并識別 CUDA 圖形
第一種方法從 C ++標(biāo)準(zhǔn)模板庫中引入容器來存儲參數(shù)集。每當(dāng)您遇到一個(gè)新的參數(shù)集來唯一地定義函數(shù)tight_loop,請將它連同相應(yīng)的可執(zhí)行圖形一起添加到容器中。
當(dāng)您遇到容器中已經(jīng)存在的參數(shù)集時(shí),啟動相應(yīng)的 CUDA 圖形。假設(shè)在本例中,變量first、params.size和delta唯一地定義了tight_loop。這個(gè)三胞胎是鑰匙用于區(qū)分圖形。您可以在源代碼中定義它和要使用的容器,使其范圍包括對函數(shù)tight_loop的每次調(diào)用。
無論函數(shù)tight_loop出現(xiàn)在何處,都要用填充鍵的代碼將其包裝起來,并在容器中查找。如果找到鍵,代碼將啟動相應(yīng)的可執(zhí)行 CUDA 圖。否則,它將創(chuàng)建一個(gè)新圖形,將其添加到容器中,然后啟動它(圖 1 )。
圖 1 。保存和識別圖形。
這種方法通常效果很好,但有一些固有的危險(xiǎn)。在本例中,您確定只需要三個(gè)參數(shù)來定義容器中的鍵。對于不同的工作負(fù)載,這可能不同,或者另一個(gè)開發(fā)團(tuán)隊(duì)成員可能會默默地向結(jié)構(gòu)中添加字段MyStruct。這會影響非平凡函數(shù)cmpKeys的編寫方式。此函數(shù)是容器所必需的,用于確定某個(gè)密鑰是否比另一個(gè)密鑰小。
為 STL 容器編寫一個(gè)非平凡的比較函數(shù)通常并不困難,但當(dāng)一個(gè)鍵由多個(gè)非平凡的實(shí)體組成時(shí),可能會很乏味。一種普遍適用的方法是使用詞典比較。對于本例,以下代碼示例有效:
更新 CUDA 圖
請記住,要重用以前捕獲的可執(zhí)行 CUDA 圖,它必須與調(diào)用上下文完全匹配:
相同拓?fù)?/p>
圖節(jié)點(diǎn)的數(shù)量和類型相同
圖節(jié)點(diǎn)之間的依賴關(guān)系相同
相同節(jié)點(diǎn)參數(shù)
但是,如果 CUDA 圖的拓?fù)浣Y(jié)構(gòu)保持不變,則可以調(diào)整它以使其符合新的需要。存在一種方便的機(jī)制來確認(rèn)拓?fù)涞葍r(jià)性,同時(shí)調(diào)整節(jié)點(diǎn)參數(shù)以返回修改后的可執(zhí)行圖。它由cudaGraphExecUpdate提供,其工作原理是將現(xiàn)有的可執(zhí)行圖與新派生的圖進(jìn)行比較(例如,通過流捕獲方便地獲得)。如果可能,差異用于進(jìn)行更改。
這種方法的好處是雙重的。首先,當(dāng)更新足夠時(shí),可以避免昂貴的新 CUDA 圖實(shí)例化。第二,你不必知道是什么讓圖形獨(dú)一無二。任何圖形比較都由 update 函數(shù)隱式執(zhí)行。下面的代碼示例實(shí)現(xiàn)了此方法。與之前一樣,它從開關(guān)的解除 Clara 和初始化開始,以指示先前創(chuàng)建的圖形。
在這個(gè)場景中,您總是執(zhí)行流捕獲來收集關(guān)于tight_loop中 CUDA 操作的信息。這是一個(gè)相對便宜的操作,完全在主機(jī)上執(zhí)行,而不是 GPU 。它可以與以前的 CUDA 圖形啟動重疊,這些啟動本身就是異步操作(圖 2 )。
圖 2 。更新圖形
一句警告的話已經(jīng)準(zhǔn)備好了。cudaGraphExecUpdate的復(fù)雜性大致與 CUDA 圖形節(jié)點(diǎn)的更改數(shù)量成正比,因此如果大部分節(jié)點(diǎn)發(fā)生更改,則效率會降低。
后果
推動這兩種方法以靈活方式管理 CUDA 圖的應(yīng)用程序有兩種不同的工作負(fù)載大小,但行為有所不同(表 1 )。所有涉及的內(nèi)核在單個(gè) NVIDIA A100 GPU 上執(zhí)行需要 2 – 8 微秒。報(bào)告的加速是針對代碼中可以轉(zhuǎn)換為 CUDA 圖形的部分。
結(jié)論
具有許多小 CUDA 內(nèi)核的應(yīng)用程序通常可以使用 CUDA 圖進(jìn)行加速,即使內(nèi)核啟動模式在整個(gè)應(yīng)用程序中發(fā)生變化。鑒于這種動態(tài)環(huán)境,最佳方法取決于應(yīng)用程序的具體情況。希望您能發(fā)現(xiàn)本文中描述的兩個(gè)示例易于理解和實(shí)現(xiàn)。
審核編輯:郭婷
-
NVIDIA
+關(guān)注
關(guān)注
14文章
4986瀏覽量
103066 -
gpu
+關(guān)注
關(guān)注
28文章
4740瀏覽量
128951
發(fā)布評論請先 登錄
相關(guān)推薦
評論