捷徑

Holistic Trace Analysis 簡介

建立於:2024 年 1 月 2 日 | 最後更新:2024 年 1 月 5 日 | 最後驗證:2024 年 11 月 5 日

作者: Anupam Bhatnagar

在本教學中,我們將示範如何使用 Holistic Trace Analysis (HTA) 來分析分散式訓練任務的追蹤。 若要開始,請依照下列步驟操作。

安裝 HTA

我們建議使用 Conda 環境來安裝 HTA。 若要安裝 Anaconda,請參閱官方 Anaconda 文件

  1. 使用 pip 安裝 HTA

    pip install HolisticTraceAnalysis
    
  2. (可選但建議) 設定 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 筆記本,並將 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 用於通訊或記憶體事件。

為了實現高訓練效率,程式碼應最大化計算時間,並最小化閒置時間和非計算時間。 以下函數產生一個 DataFrame,提供每個等級的詳細時間使用量分解。

analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
time_spent_df = analyzer.get_temporal_breakdown()
../_images/temporal_breakdown_df.png

get_temporal_breakdown 函數中的 visualize 引數設定為 True 時,它也會產生一個條形圖,表示按等級分解。

../_images/temporal_breakdown_plot.png

閒置時間分解

深入了解 GPU 花費在閒置上的時間量以及背後的原因,有助於指導優化策略。 當沒有核心在 GPU 上執行時,GPU 會被視為閒置。 我們開發了一種演算法,將 閒置 時間分為三個不同的類別

  • Host wait: 指 GPU 上的閒置時間,這是因為 CPU 沒有足夠快地將核心排隊,以保持 GPU 的充分利用。 這些類型的效率低落可以通過檢查導致速度變慢的 CPU 運算符、增加批次大小和應用運算符融合來解決。

  • Kernel wait: 這指的是在 GPU 上啟動連續核心時的簡短開銷。 屬於此類別的閒置時間可以通過使用 CUDA 圖形優化來最小化。

  • Other wait: 此類別包含由於訊息不足目前無法歸因的閒置時間。 可能的原因包括使用 CUDA 事件在 CUDA 串流之間的同步以及啟動核心的延遲。

Host wait 時間可以解釋為 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()
../_images/idle_time_breakdown_percentage.png

該函數返回一個 dataframes 的 tuple。 第一個 dataframe 包含每個 rank 的每個 stream 上的按類別劃分的閒置時間。

../_images/idle_time.png

show_idle_interval_stats 設定為 True 時,將生成第二個 dataframe。 它包含每個 rank 的每個 stream 上閒置時間的摘要統計信息。

../_images/idle_time_summary.png

提示

預設情況下,閒置時間的細分呈現每個閒置時間類別的百分比。 將 visualize_pctg 參數設定為 False,該函數將在 y 軸上以絕對時間呈現。

核心細分

核心細分功能會細分每個核心類型(例如通訊 (COMM)、運算 (COMP) 和記憶體 (MEM))所花費的時間,遍及所有 rank,並呈現每個類別中所花費的時間比例。 以下是每個類別中花費的時間百分比,以圓餅圖表示

../_images/kernel_type_breakdown.png

核心細分可以按如下方式計算

analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
kernel_type_metrics_df, kernel_metrics_df = analyzer.get_gpu_kernel_breakdown()

該函數返回的第一個 dataframe 包含用於生成圓餅圖的原始值。

核心持續時間分佈

get_gpu_kernel_breakdown 返回的第二個 dataframe 包含每個核心的持續時間摘要統計信息。 尤其是,這包括每個 rank 上每個核心的計數、最小值、最大值、平均值、標準差、總和和核心類型。

../_images/kernel_metrics_df.png

使用此資料,HTA 創建了許多視覺化效果來識別效能瓶頸。

  1. 每個 rank 的每個核心類型的頂部核心的圓餅圖。

  2. 每個頂部核心和每個核心類型的所有 rank 的平均持續時間的長條圖。

../_images/pie_charts.png

提示

所有圖片都是使用 plotly 生成的。 將滑鼠懸停在圖表上會在右上角顯示模式列,使用者可以透過它來縮放、平移、選擇和下載圖表。

上面的圓餅圖顯示了前 5 個運算、通訊和記憶體核心。 為每個 rank 生成類似的圓餅圖。 可以配置圓餅圖以顯示前 k 個核心,方法是將 num_kernels 參數傳遞給 get_gpu_kernel_breakdown 函數。 此外,可以使用 duration_ratio 參數來調整需要分析的時間百分比。 如果同時指定 num_kernelsduration_ratio,則 num_kernels 優先。

../_images/comm_across_ranks.png

上面的長條圖顯示了所有 rank 中 NCCL AllReduce 核心的平均持續時間。 黑線表示每個 rank 上所花費的最短和最長時間。

警告

使用 jupyter-lab 時,請將 “image_renderer” 參數值設定為 “jupyterlab”,否則圖表將不會在筆記本中呈現。

有關此功能的詳細演練,請參閱 repo 的 examples 資料夾中的 gpu_kernel_breakdown 筆記本

通訊運算重疊

在分散式訓練中,大量時間花費在 GPU 之間的通訊和同步事件中。 為了實現高的 GPU 效率(例如 TFLOPS/GPU),至關重要的是使 GPU 超額訂閱運算核心。 換句話說,GPU 不應由於未解決的資料依賴性而受到阻止。 衡量運算被資料依賴性阻止程度的一種方法是計算通訊運算重疊。 如果通訊事件與運算事件重疊,則會觀察到更高的 GPU 效率。 缺乏通訊和運算重疊將導致 GPU 閒置,從而導致效率降低。 總而言之,希望具有更高的通訊運算重疊。 為了計算每個 rank 的重疊百分比,我們測量以下比率

(在通訊時花費在運算上的時間) / (花費在通訊上的時間)

通訊運算重疊可以按如下方式計算

analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
overlap_df = analyzer.get_comm_comp_overlap()

該函數返回一個 dataframe,其中包含每個 rank 的重疊百分比。

../_images/overlap_df.png

visualize 參數設定為 True 時,get_comm_comp_overlap 函數也會生成一個長條圖,表示按 rank 劃分的重疊。

../_images/overlap_plot.png

增強計數器

記憶體頻寬和佇列長度計數器

記憶體頻寬計數器測量用於透過記憶體複製 (memcpy) 和記憶體設定 (memset) 事件從 H2D、D2H 和 D2D 複製資料時所使用的記憶體複製頻寬。 HTA 也會計算每個 CUDA stream 上的未完成操作數。 我們將其稱為佇列長度。 當 stream 上的佇列長度為 1024 或更大時,無法在該 stream 上排程新事件,並且 CPU 將停頓,直到 GPU stream 上的事件處理完畢。

generate_trace_with_counters API 輸出一個新的追蹤檔案,其中包含記憶體頻寬和佇列長度計數器。 新的追蹤檔案包含軌跡,這些軌跡指示 memcpy/memset 操作使用的記憶體頻寬,以及每個 stream 上的佇列長度的軌跡。 預設情況下,這些計數器是使用 rank 0 追蹤檔案生成的,並且新檔案的名稱中包含後綴 _with_counters。 使用者可以選擇使用 generate_trace_with_counters API 中的 ranks 參數來為多個 rank 生成計數器。

analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
analyzer.generate_trace_with_counters()

帶有增強計數器的已生成追蹤檔案的螢幕截圖。

../_images/mem_bandwidth_queue_length.png

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 百分位數。

../_images/queue_length_summary.png

時間序列僅包含值變更時的點。一旦觀察到一個值,時間序列就會保持恆定,直到下一次更新。記憶體頻寬和佇列長度時間序列函式會傳回一個字典,其鍵是 rank(等級),值是該 rank 的時間序列。預設情況下,時間序列僅針對 rank 0 計算。

CUDA Kernel 啟動統計

../_images/cuda_kernel_launch.png

對於在 GPU 上啟動的每個事件,CPU 上都有一個對應的排程事件,例如 CudaLaunchKernelCudaMemcpyAsyncCudaMemsetAsync。 這些事件通過追蹤中一個共同的關聯 ID 連結 - 請參閱上圖。此功能計算 CPU 執行時事件的持續時間、其對應的 GPU 核心以及啟動延遲,例如 GPU 核心啟動和 CPU 運算元結束之間的時間差。 可以如下產生核心啟動資訊

analyzer = TraceAnalysis(trace_dir="/path/to/trace/dir")
kernel_info_df = analyzer.get_cuda_kernel_launch_stats()

產生的資料框的螢幕截圖如下所示。

../_images/cuda_kernel_launch_stats.png

CPU 運算、GPU 核心和啟動延遲的持續時間讓我們可以找到以下內容:

  • 短 GPU 核心 - 持續時間短於對應 CPU 執行時事件的 GPU 核心。

  • 執行時事件離群值 - 持續時間過長的 CPU 執行時事件。

  • 啟動延遲離群值 - 排程時間過長的 GPU 核心。

HTA 為上述三個類別中的每一個產生分佈圖。

短 GPU 核心

通常,CPU 端的啟動時間範圍為 5-20 微秒。 在某些情況下,GPU 執行時間低於啟動時間本身。 下面的圖有助於我們找出此類實例在程式碼中發生的頻率。

../_images/short_gpu_kernels.png

執行時事件離群值

執行時離群值取決於用於對離群值進行分類的截止值,因此 get_cuda_kernel_launch_stats API 提供了 runtime_cutoff 參數來配置該值。

../_images/runtime_outliers.png

啟動延遲離群值

啟動延遲離群值取決於用於對離群值進行分類的截止值,因此 get_cuda_kernel_launch_stats API 提供了 launch_delay_cutoff 參數來配置該值。

../_images/launch_delay_outliers.png

結論

在本教學中,您學習了如何安裝和使用 HTA,這是一個性能工具,使您可以分析分散式訓練工作流程中的瓶頸。 若要了解如何使用 HTA 工具執行追蹤差異分析,請參閱 使用整體追蹤分析進行追蹤差異

文件

Access comprehensive developer documentation for PyTorch

View Docs

教學課程

Get in-depth tutorials for beginners and advanced developers

View Tutorials

Resources

Find development resources and get your questions answered

View Resources