整體追蹤分析簡介#
創建於: 2024 年 01 月 02 日 | 最後更新: 2024 年 01 月 05 日 | 最後驗證: 2024 年 11 月 05 日
作者: Anupam Bhatnagar
在本教程中,我們將演示如何使用整體追蹤分析 (HTA) 來分析分散式訓練作業的追蹤資料。請按照以下步驟開始。
安裝 HTA#
我們建議使用 Conda 環境來安裝 HTA。要安裝 Anaconda,請參閱 官方 Anaconda 文件。
使用 pip 安裝 HTA
pip install HolisticTraceAnalysis
(可選且推薦) 設定 Conda 環境
# create the environment env_name conda create -n env_name # activate the environment conda activate env_name # When you are done, deactivate the environment by running ``conda deactivate``
入門#
啟動 Jupyter notebook 並將 trace_dir 變數設定為追蹤資料的位置。
from hta.trace_analysis import TraceAnalysis
trace_dir = "/path/to/folder/with/traces"
analyzer = TraceAnalysis(trace_dir=trace_dir)
時間分解#
為了有效利用 GPU,瞭解它們如何為特定作業花費時間至關重要。它們主要從事計算、通訊、記憶體事件,還是處於空閒狀態?時間分解功能提供了對這三個類別花費時間的詳細分析。
空閒時間 - GPU 處於空閒狀態。
計算時間 - GPU 用於矩陣乘法或向量運算。
非計算時間 - GPU 用於通訊或記憶體事件。
為了實現高訓練效率,程式碼應最大化計算時間並最小化空閒時間和非計算時間。以下函式生成一個數據幀,提供每個 rank 的時間使用情況的詳細分解。
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
time_spent_df = analyzer.get_temporal_breakdown()
當 get_temporal_breakdown 函式中的 visualize 引數設定為 True 時,它還會生成一個表示按 rank 分解的條形圖。
空閒時間分解#
深入瞭解 GPU 空閒時間及其原因,有助於指導最佳化策略。當 GPU 上沒有核心執行時,它被視為空閒。我們開發了一種演算法,將 空閒 時間分為三個不同的類別:
主機等待: 指 GPU 上因 CPU 排列核心不夠快以致 GPU 未充分利用而造成的空閒時間。這類低效率可以透過檢查導致延遲的 CPU 操作、增加批次大小和應用操作融合來解決。
核心等待: 指在 GPU 上啟動連續核心的短暫開銷。可以透過使用 CUDA 圖最佳化來最小化歸因於此類的空閒時間。
其他等待: 此類別包括目前由於資訊不足而無法歸因的空閒時間。可能的原因包括使用 CUDA 事件進行 CUDA 流之間的同步以及啟動核心的延遲。
主機等待時間可解釋為 GPU 因 CPU 而停滯的時間。為了將空閒時間歸因於核心等待,我們使用以下啟發式方法:
連續核心之間的間隔 < 閾值
預設閾值是 30 納秒,可以透過 consecutive_kernel_delay 引數進行配置。預設情況下,空閒時間分解僅為 rank 0 計算。為了計算其他 rank 的分解,請使用 get_idle_time_breakdown 函式中的 ranks 引數。空閒時間分解可以按以下方式生成:
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
idle_time_df = analyzer.get_idle_time_breakdown()
該函式返回一個數據幀元組。第一個資料幀包含每個 rank 的每個流的按類別的空閒時間。
當 show_idle_interval_stats 設定為 True 時,生成第二個資料幀。它包含每個 rank 的每個流的空閒時間的摘要統計資訊。
提示
預設情況下,空閒時間分解會顯示每個空閒時間類別的百分比。將 visualize_pctg 引數設定為 False,該函式將以 y 軸上的絕對時間進行渲染。
核心分解#
核心分解功能將所有 rank 在每種核心型別(例如通訊 (COMM)、計算 (COMP) 和記憶體 (MEM))上花費的時間進行分解,並顯示每個類別花費時間的比例。以下是作為餅圖顯示的每個類別所花費時間的百分比:
核心分解可以按以下方式計算:
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
kernel_type_metrics_df, kernel_metrics_df = analyzer.get_gpu_kernel_breakdown()
函式返回的第一個資料幀包含用於生成餅圖的原始值。
核心持續時間分佈#
由 get_gpu_kernel_breakdown 返回的第二個資料幀包含每個核心的持續時間摘要統計資訊。特別是,它包括每個 rank 的每個核心的計數、最小值、最大值、平均值、標準差、總和和核心型別。
利用這些資料,HTA 建立了許多視覺化來識別效能瓶頸。
每個 rank 的每種核心型別的頂級核心的餅圖。
所有 rank 中每個頂級核心和每種核心型別的平均持續時間的條形圖。
提示
所有影像均使用 plotly 生成。將滑鼠懸停在圖表上會在右上角顯示模式欄,允許使用者縮放、平移、選擇和下載圖表。
上方的餅圖顯示了計算、通訊和記憶體核心的前 5 名。類似地為每個 rank 生成餅圖。可以使用傳遞給 get_gpu_kernel_breakdown 函式的 num_kernels 引數來配置餅圖以顯示前 k 個核心。此外,還可以使用 duration_ratio 引數來調整需要分析的時間百分比。如果同時指定了 num_kernels 和 duration_ratio,則 num_kernels 具有優先權。
上方的條形圖顯示了所有 rank 中 NCCL AllReduce 核心的平均持續時間。黑線表示每個 rank 上的最短和最長時間。
警告
當使用 jupyter-lab 時,將 “image_renderer” 引數值設定為 “jupyterlab”,否則圖表將無法在 notebook 中渲染。
有關此功能的詳細演練,請參閱倉庫示例資料夾中的 gpu_kernel_breakdown notebook。
通訊計算重疊#
在分散式訓練中,大量時間花在 GPU 之間的通訊和同步事件上。為了實現高 GPU 效率(例如 TFLOPS/GPU),保持 GPU 被計算核心過度訂閱至關重要。換句話說,GPU 不應因未解決的資料依賴性而阻塞。衡量計算被資料依賴性阻塞程度的一種方法是計算通訊計算重疊。如果通訊事件與計算事件重疊,則觀察到更高的 GPU 效率。缺乏通訊和計算重疊將導致 GPU 空閒,從而導致效率低下。總之,更高的通訊計算重疊是可取的。為了計算每個 rank 的重疊百分比,我們測量以下比率:
(通訊期間花費的計算時間) / (通訊期間花費的時間)
通訊計算重疊可以按以下方式計算:
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
overlap_df = analyzer.get_comm_comp_overlap()
該函式返回一個包含每個 rank 的重疊百分比的資料幀。
當 visualize 引數設定為 True 時,get_comm_comp_overlap 函式還會生成一個表示按 rank 重疊的條形圖。
增強計數器#
記憶體頻寬和佇列長度計數器#
記憶體頻寬計數器測量在透過記憶體複製 (memcpy) 和記憶體設定 (memset) 事件從 H2D、D2H 和 D2D 複製資料時使用的記憶體複製頻寬。HTA 還計算每個 CUDA 流上的未完成運算元。我們稱之為佇列長度。當流上的佇列長度為 1024 或更大時,無法在該流上排程新事件,CPU 將一直等待,直到 GPU 流上的事件已處理完畢。
generate_trace_with_counters API 輸出一個新的追蹤檔案,其中包含記憶體頻寬和佇列長度計數器。新追蹤檔案包含指示 memcpy/memset 操作使用的記憶體頻寬的軌跡,以及每個流上佇列長度的軌跡。預設情況下,這些計數器使用 rank 0 的追蹤檔案生成,新檔名稱中包含字尾 _with_counters。使用者可以透過在 generate_trace_with_counters API 中使用 ranks 引數來為多個 rank 生成計數器。
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
analyzer.generate_trace_with_counters()
帶有增強計數器的生成追蹤檔案的截圖。
HTA 還提供記憶體複製頻寬和佇列長度計數器的摘要,以及使用以下 API 對程式碼的分析部分的時間序列:
要檢視摘要和時間序列,請使用:
# generate summary
mem_bw_summary = analyzer.get_memory_bw_summary()
queue_len_summary = analyzer.get_queue_length_summary()
# get time series
mem_bw_series = analyzer.get_memory_bw_time_series()
queue_len_series = analyzer.get_queue_length_series()
摘要包含計數、最小值、最大值、均值、標準差、25%、50% 和 75% 百分位數。
時間序列僅包含值更改時的點。一旦觀察到某個值,時間序列將保持不變,直到下次更新。記憶體頻寬和佇列長度時間序列函式返回一個字典,其鍵是 rank,值是該 rank 的時間序列。預設情況下,時間序列僅為 rank 0 計算。
CUDA 核心啟動統計資訊#
對於在 GPU 上啟動的每個事件,都有一個相應的 CPU 排程事件,例如 CudaLaunchKernel、CudaMemcpyAsync、CudaMemsetAsync。這些事件透過追蹤中的公共關聯 ID 進行連結 - 請參見上圖。此功能計算 CPU 執行時事件的持續時間、相應的 GPU 核心以及啟動延遲(例如,GPU 核心開始與 CPU 操作結束之間的差值)。核心啟動資訊可以按以下方式生成:
analyzer = TraceAnalysis(trace_dir="/path/to/trace/dir")
kernel_info_df = analyzer.get_cuda_kernel_launch_stats()
下面是生成的資料幀的截圖。
CPU 操作、GPU 核心和啟動延遲的持續時間使我們能夠找到以下內容:
短 GPU 核心 - 持續時間小於相應 CPU 執行時事件的 GPU 核心。
執行時事件異常值 - 持續時間過長的 CPU 執行時事件。
啟動延遲異常值 - 啟動耗時過長的 GPU 核心。
HTA 為上述三個類別中的每一個生成分佈圖。
短 GPU 核心
通常,CPU 端的啟動時間在 5-20 微秒之間。在某些情況下,GPU 執行時間低於啟動時間本身。下圖有助於我們瞭解程式碼中此類例項發生的頻率。
執行時事件異常值
執行時異常值取決於用於分類異常值的截止值,因此 get_cuda_kernel_launch_stats API 提供了 runtime_cutoff 引數來配置該值。
啟動延遲異常值
啟動延遲異常值取決於用於分類異常值的截止值,因此 get_cuda_kernel_launch_stats API 提供了 launch_delay_cutoff 引數來配置該值。
結論#
在本教程中,您學習瞭如何安裝和使用 HTA,這是一個性能工具,可幫助您分析分散式訓練工作流中的瓶頸。要了解如何使用 HTA 工具執行追蹤差異分析,請參閱 使用整體追蹤分析進行追蹤差異。