隨著新一代產品的推出, NVIDIA GPU 的性能也變得越來越強大。這種提升通常有兩種形式。每個流多處理器 (SM) (GPU 的主力) 都可以更快地執行指令,而內存系統可以以越來越快的速度向 SM 傳輸數據。
與此同時,SM 的數量通常也會隨著每一代的增加而增加,這增加了 GPU 可以支持的計算并發量。例如, NVIDIA Volta、 NVIDIA Ampere 和 NVIDIA Hopper GPU 分別支持 80、108 和 132 個 SM。
在某些情況下,不斷增長的并發可能會帶來一個挑戰。在 GPU 上運行的工作負載必須公開相應的并發級別,才能使 GPU 資源得到充分利用。為此,一種常見的方法是使用多個流向 GPU 發送獨立的任務,或者類似地,使用 CUDA 的多進程服務。
本文介紹了一種確定這些方法是否成功占用 GPU 的方法。
在 Santa Clara,我們遇到了一個問題,
我們的 NVIDIA Nsight Systems 性能分析工具可用于確定稱為核函數的工作塊是否在 GPU 上執行。流通常包含一系列依次運行的核函數。每個流可以在其自己的 Nsight Systems 時間軸上查看,從而揭示不同流中的核函數之間的重疊。例如,圖 1 中的標記為“Stream 15”到“Stream 22”的行展示了重疊的核函數。

或者,所有流都可以顯示在單個時間軸上,這樣可以全面了解 GPU 在執行工作負載期間的使用頻率 (圖 1,行標記為“所有流”)。
標準 Nsight Systems 時間軸無法立即顯示 GPU 的 SM 數量。您可以在彈出視圖中突出顯示特定內核并檢查其屬性,包括每個線程塊中的線程數、網格大小 (屬于內核的線程塊總數),甚至理論占用率 (一個 SM 上可以容納的線程塊的最大數量)。
您可以通過這些屬性計算執行內核所需的 SM 數量。如果該數量不是 GPU 上 SM 總數的整數倍,則可以將任何剩余的 SM (內核的“tail”) 分配給一個或多個其他內核的線程塊。
通過使用多個流 (本文的重點) 啟用不同內核的并發執行。要概述整個多流工作負載平均使用 GPU 的情況,就需要在所有流的 Nsight Systems 時間軸中拼湊出并發內核的尾部。可以利用 Nsight Systems 的一些特殊功能來快速輕松地完成這個原本不重要的過程。
通過 GPU 指標采樣來搶險
工作負載或其特定部分的 Nsight Systems 報告可以標準化的 SQLite 數據庫文件形式提取數據。此 SQLite 文件也可以作為工作負載統計數據的副產品,并使用 Linux 命令 `nsys stats` (在本文中重點介紹 Linux 操作系統,但也可以在 Windows 上獲得類似的結果) 來匯總。
在 CUDA 工具包 11.4 中引入 Nsight Systems 版本 2021.2.4 后,可以將特殊標志傳遞給工作負載分析命令,以幫助實現預期目標:
nsys profile - - gpu - metrics - device = N |
在這個標志中,N
代表要采樣的多 GPU 節點中 GPU 的序列號。如果您使用的是單個 GPU,則 N
等于 0。
設置此標志后,Nsight Systems 將在默認頻率 10 KHz 或用戶指定的采樣頻率下記錄正在使用的所有 SM 的百分比(SM 有源)。此數量可以在其自己的時間軸上查看。
圖 2 展示了特寫示例以及整個應用程序的概述。**源代碼可見**。這有助于更深入地了解 GPU 的使用情況,但需要打開 Nsight Systems 的圖形用戶界面。


最適合此任務的分析工具是 `nsys stats`,但僅使用此命令將僅顯示 CPU 和 GPU 活動的摘要,這可能不足以提供有價值的信息。更詳細的分析需要額外的說明。
然而,通過數據庫查詢語言 (SQL) 結構化查詢語言,可以從 Nsight Systems 報告衍生的 SQLite 文件中提取所需的所有內容。SQLite 文件在邏輯上被結構化為表的集合,并可以使用簡單的 SQL 命令提取任何表列。與正在使用的 SM 百分比對應的列為 SM Active。
形式上,報告的數量是 SM 活動的平均值。50%可能意味著所有 SM 在 50%的時間內都處于活動狀態,或 50%的 SM 在 100%的時間內處于活動狀態,或者介于兩者之間的任何情況,但實際上,我們先前的定義優先。
如果您不熟悉 SQL,請利用 Nsight Systems 版本 2023.4 中引入的 DataService 功能。它提供了一個方便的 Python 接口,用于查詢所收集報告中嵌入的 SQLite 數據庫。以下腳本使用 DataService 提取工作負載 SM 活動百分比列表,并導出幾個有用的數量。
import sys import json import pandas as pd from nsys_recipe import data_service, log from nsys_recipe.data_service import DataService # To run this script, be sure to add the Nsight Systems package directory to your PYTHONPATH, similar to this: # export PYTHONPATH=/opt/nvidia/nsight-systems/2023.4.1/target-linux-x64/python/packages def compute_utilization(filename, freq = 10000 ): service = DataService(filename) table_column_dict = { "GPU_METRICS" : [ "typeId" , "metricId" , "value" ], "TARGET_INFO_GPU_METRICS" : [ "metricName" , "metricId" ], "META_DATA_CAPTURE" : [ "name" , "value" ] } hints = { "format" : "sqlite" } df_dict = service.read_tables(table_column_dict, hints = hints) df = df_dict.get( "GPU_METRICS" , None ) if df is None : print (f "{filename} does not contain GPU metric data." ) return tgtinfo_df = df_dict.get( "TARGET_INFO_GPU_METRICS" , None ) if tgtinfo_df is None : print (f "{filename} does not contain TARGET_INFO_GPU_METRICS table." ) return metadata_df = df_dict.get( "META_DATA_CAPTURE" , None ) if metadata_df is not None : if "GPU_METRICS_OPTIONS:SAMPLING_FREQUENCY" in metadata_df[ 'name' ].values: report_freq = metadata_df.loc[ metadata_df[ 'name' ] = = 'GPU_METRICS_OPTIONS:SAMPLING_FREQUENCY' ][ 'value' ].iat[ 0 ] if isinstance (report_freq, ( int , float )): freq = report_freq print ( "Setting GPU Metric sample frequency to value in report file. new frequency=" ,freq) possible_smactive = [ 'SMs Active' , 'SM Active' , 'SM Active [Throughput %]' ] smactive_name_mask = tgtinfo_df[ 'metricName' ].isin(possible_smactive) smactive_row = tgtinfo_df[smactive_name_mask] smactive_name = smactive_row[ 'metricName' ].iat[ 0 ] smactive_id = tgtinfo_df.loc[tgtinfo_df[ 'metricName' ] = = smactive_name, 'metricId' ].iat[ 0 ] smactive_df = df.loc[ df[ 'metricId' ] = = smactive_id ] usage = smactive_df[ 'value' ]. sum () count = len (smactive_df[ 'value' ]) count_nonzero = len (smactive_df.loc[smactive_df[ 'value' ]! = 0 ]) avg_gross_util = usage / count avg_net_util = usage / count_nonzero effective_util = usage / freq / 100 print (f "Avg gross GPU utilization:\t%lf %%" % avg_gross_util) print (f "Avg net GPU utilization:\t%lf %%" % avg_net_util) print (f "Effective GPU utilization time:\t%lf s" % effective_util) return metadata_df if __name__ = = '__main__' : if len (sys.argv) = = 2 : compute_utilization(sys.argv[ 1 ]) elif len (sys.argv) = = 3 : compute_utilization(sys.argv[ 1 ], freq = float (sys.argv[ 2 ])) |
如果將所有百分比的總和(Sp)以及樣本數量(Ns)相除,則得到在整個工作負載期間使用的所有 SM 的平均百分比。這種平均百分比被稱為GPU 總利用率。
這可能正是所需的結果,但通常工作負載包含一些完全沒有啟動內核的部分。例如,數據從主機 CPU 復制到 GPU 時。通過自動監控采樣點的 SM 使用百分比是否大于零(讓該樣本數 Nsnz)和計算 Sp/Nsnz 在可測量的內核執行期間提供 SM 的平均使用百分比。我們將其稱為 *GPU 凈利用率*。
最后,對于特定工作負載或部分工作負載,計算在壓縮所有樣本后 GPU 的有效使用時間可能很有用。例如,如果樣本顯示在 5 秒內 SM 利用率為 40%,則 GPU 的有效利用率為 0.4 × 5 秒 = 2 秒。可以將此時間與執行相關內核的 Nsight Systems 時間軸的持續時間進行比較。讓該持續時間代表深度學習,并讓有效 GPU 利用率的總和代表 Sdt/Dt,其中 Sdt 是相關窗口中的 SM 利用率,而 Dt 是窗口的持續時間。
舉個例子
以下場景展示了這三個量的效用,并將其應用于逼真的工作負載 (圖 4)。
數量 Sp/Ns 表示在整個工作負載中使用 SM 的平均百分比,由圖 4 中的開放、黑色和雙頭箭頭表示,包括在沒有 SM 活動時圍繞密集的藍色內核執行塊的六個周期。對于此工作負載,該百分比為 70%。
數量 Sdt/時間 = 100 表示在 GPU 繁忙時間使用 SM 的平均百分比,由圖 4 中的實心、紅色雙頭箭頭表示。這包括五個繁忙窗口中的短嵌入式部分,當時暫時沒有執行核函數(在表示整個工作負載的圖的比例上不可見)。對于此工作負載,該百分比為 77%。
數量 Sp/Nsnz 表示在 Nsight Systems 確定至少有一個 SM 處于忙碌狀態時使用的 SM 的平均百分比。此百分比忽略了五個 GPU 忙碌期間沒有 SM 活動的任何短嵌入式周期。對于此工作負載,該百分比為 79%,
未來
NVIDIA 分析工具持續添加新功能。例如,最近的 NVIDIA 分析工具開發版本引入了新的譜,使分析集群級應用程序和性能回歸研究變得更輕松。
NVIDIA 正在關注通過流行的 Python 腳本語言提供對 Nsight System GPU 性能詳細信息的訪問,以消除學習 C 或 SQL 等其他語言的需求。Nsight Systems 功能的其他近期新增功能包括:統計分析 和 專家系統分析。
結束語
對此示例工作負載的分析表明,在某些時間段內,SM 完全不使用,但總體 SM 空閑時間有限。
在 SM 繁忙窗口期間,GPU 相當飽和,特別是考慮到網格和線程塊的大小差異很大,一些網格僅包含單個線程塊,因此只能占用單個 SM.因此,構成工作負載的多個流能夠很好地提高 GPU 利用率。
我們毫不費力地得出了這一見解。用于確定本文中提到的三個利用率指標的 Python 腳本假設用戶為舊版 Nsight Systems 獲取的輸入報告明確提供采樣頻率,允許此類版本采用 10 KHz 的默認值,或者可以從較新版本 Nsight Systems 的輸入報告中提取樣本頻率。
SM 利用率高并不意味著 SM 得到了高效利用。由于各種原因,在 SM 上執行的內核可能會出現嚴重甚至極端停滯。SM 利用率高的唯一事實是,大多數 GPU 的 SM 在大多數時候都處于活動狀態,這是獲得良好性能的前提。
有關更多信息,請參閱以下資源:
- CUDA 工具包 提供用于加速計算的工具和框架。
- 立即開始使用 Nsight Systems
- 立即開始使用 Nsight 計算。
- 深入了解并提出問題 CUDA 開發者論壇,以獲得專業建議和解決方案。
?