深度學習框架火焰圖pprof和CUDA Nsys配置指南

注:如下是在做深度學習框架開發時,用到的火焰圖pprof和 CUDA Nsys 配置指南,可能對大家有一些幫助,就此分享。一些是基於飛槳的Docker鏡像配置的。

一、環境 & 工具配置

0. 開發機配置

# 1.構建鏡像, 記得映射端口,可以多映射幾個;記得掛載ssd目錄,因爲數據都在ssd盤上
nvidia-docker run -it --name=profile_dev --shm-size 128G --ulimit core=-1 --cap-add ALL -v $PWD:/workspace -v /ssd1:/ssd1 -v /ssd2:/ssd2 -v /ssd3:/ssd3 --net=host -p 9422:22 -p 9423:9423 -p 9424:9424 registry.baidubce.com/paddlepaddle/paddle:latest-dev-cuda11.2-cudnn8-gcc82 /bin/bash

# 2.更新設置,安裝vim
apt update
apt install vim

# 3. 將代理保存到 ~/.my_profile
# 4.安裝zsh 和 oh_my_zsh
apt install zsh
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# 5. 自動初始化個性化設置
vim ~/.zshrc
# 最後一行添加 source ~/.my_profile

# 6. 配置性能優化工具
apt install libgoogle-perftools-dev

# 7. 創建全局python3.7 沙盒
virtualenv env3.7 --python=python3.7
source env3.7/bin/activate

# 8. 配置pprof
rm -rf /usr/local/go
tar -xzf go1.16.4.linux-amd64.tar.gz -C /usr/local
export GOROOT=/usr/local/go
export PATH=/root/gopath/bin:$GOROOT/bin:$PATH
go version
go get github.com/google/pprof

1. NsightSyetem 工具

1.1 前序準備

NsightSystem 是一個集終端 CUDA Profile 日誌生成和 前端可視化 timeline 分析的強大工具。安裝 nsys 需要分別下載適合Unix 的 Installer 和 Mac/Windows 的可視化終端。

  • Step 1: 註冊 Nvidia 賬號(略)
  • Step 2:下載 Linux Installer
  • Step 3:下載桌面客戶端
    • MAC:Nvidia NSight Systems

1.2 安裝過程

首先,在創建 docker 鏡像時,需要加上 --privileged=true,否則可能無權限讀取Performance Counter。比如:

  • 現在不允許加 --privileged=true 了,只需要加 --cap-add ALL 即可
  • doacker 容器命令前文檔最前面

然後,在 docker 容器中的命令行下,安裝 nsys:

# step 1: 此處是舊的,推薦大家下載最新的按照包
bash NsightSystems-linux-public-2020.4.1.144-20fdc64.run  

# step 2: 然後 Enter 鍵,並翻頁到最後,鍵入 ACCEPT 接受協議

# step 3: 輸入安裝路徑,或者回車使用默認路徑,完成安裝。
Enter install path: [ default is /opt/nvidia/nsight-systems/2020.4.1 ]:
...
========================================
To uninstall the Nsight Systems 2020.4.1, please delete "/opt/nvidia/nsight-systems/2020.4.1"
Installation Complete

# step 4: 將安裝路徑加入PATH
$ export PATH=/opt/nvidia/nsight-systems/2020.4.1/bin:$PATH
$ which nsys
/opt/nvidia/nsight-systems/2020.4.1/bin/nsys

注:桌面可視化的客戶端安裝非常簡單,和安裝其他軟件無差別。

1.3 基礎用法

常用命令如下:

nsys profile -w true -t cuda,nvtx,osrt,cudnn,cublas -s cpu --cud -x true python abs.py

"""
–stats=true,表示在收集完信息後,會在終端輸出本次profiling的統計概要。
-t cuda,用於指定待profiling的 API.可以設置爲cublas, cuda, cudnn, nvtx, opengl, openacc, openmp, osrt, mpi, vulkan, none
"""

注:更多用法,可以參考:nsys文檔

命令執行完,會在當前路徑下生成一個 *.qdrep文件,將其拖入 NSight GUI 工具即可。

2. 火焰圖

2.1 yep 庫

C++的性能分析工具非常多。常見的包括gprof, valgrind, google-perftools。但是調試Python中使用的動態鏈接庫與直接調試原始二進制相比增加了很多複雜度。幸而Python的一個第三方庫yep提供了方便的和google-perftools交互的方法。於是這裏使用yep進行Python與C++混合代碼的性能分析。

使用yep前需要安裝google-perftoolsyep包。ubuntu下安裝命令爲:

apt update
apt install libgoogle-perftools-dev
pip install yep

因爲C++與Python不同,編譯時可能會去掉調試信息,運行時也可能因爲多線程產生混亂不可讀的性能分析結果。爲了生成更可讀的性能分析結果,可以採取下面幾點措施:

  • 編譯時指定-g生成調試信息。使用cmake的話,可以將CMAKE_BUILD_TYPE指定爲RelWithDebInfo
  • 編譯時一定要開啓優化。單純的Debug編譯性能會和-O2或者-O3有非常大的差別。Debug模式下的性能測試是沒有意義的
  • 運行性能分析的時候,先從單線程開始,再開啓多線程,進而多機。畢竟單線程調試更容易。可以設置OMP_NUM_THREADS=1這個環境變量關閉openmp優化

2.2 pprof 命令

在運行完性能分析後,會生成性能分析結果文件。我們可以使用pprof來顯示性能分析結果。注意,這裏使用了用Go語言重構後的pprof,因爲這個工具具有web服務界面,且展示效果更好。

首先,安裝 GO 環境,以Linux爲例:

# step 1: 下載較新的的 GO 安裝文件
wget https://golang.org/dl/go1.16.4.linux-amd64.tar.gz

# step 2: 刪除系統舊版的 go
rm -rf /usr/local/go

# step 3: 解壓到 /usr/local 目錄
tar -xzf go1.16.4.linux-amd64.tar.gz -C /usr/local

# step 4: 設置環境變量
export GOROOT=/usr/local/go
export PATH=/root/gopath/bin:$GOROOT/bin:$PATH

# step 5: 驗證安裝
go version

然後,安裝 pprof 命令:

go get github.com/google/pprof

2.3 基礎用法

生成日誌文件:

python -m yep -- model.py --device=GPU ....

可以啓動一個服務,查看火焰圖:

pprof -http=0.0.0.0:8878 `which python`  ./main.py.prof

二、模型性能分析

1. 日誌生成

1.1 Profiler timeline

對於模型代碼,需要在訓練的 for 循環中,添加如下代碼:

if iter == 100:
    profiler.start_profiler("All", "OpDetail")
if iter == 110:
    profiler.stop_profiler("total", "./profile")
    return

其中 start_profiler 的 trace_option 建議設置爲 “Default“ 或 “OpDetail“ ,取10次迭代數據。

執行完之後,會在終端輸出日誌彙總結果,同時也會生成一個文件。該文件的路徑爲./profile

圖片

執行如下命令,可以生成 timeline 文件,方便在 chrom 瀏覽器中查看:

python Paddle/tools/timeline.py --profile_path=./profile --timeline_path=timeline
  • 訪問 chrome://tracing/
  • 點擊 load 按鈕,加載 timeline文件

1.2 NSight timeline

在模型訓練相關的 for 循環中,添加如下代碼:

  • 使用nvprof_start()core.nvprof_stop()控制profile的開始和結束
  • 使用core.nvprof_nvtx_push()core.nvprof_nvtx_pop() 添加要統計的特定event。在event開始前push event 的名稱,在event結束後,進行 pop。
  • 例如下面代碼,使用迭代次數作爲事件的名稱。
for iter_id, data in enumerate(train_loader):
    if iter_id == 100:
        core.nvprof_start()
        core.nvprof_enable_record_event()
        core.nvprof_nvtx_push(str(iter_id))
    if iter_id == 110:
        core.nvprof_nvtx_pop()
        core.nvprof_stop()
        
    if iter_id > 100 and iter_id < 110:
        core.nvprof_nvtx_pop()
        core.nvprof_nvtx_push(str(iter_id))

執行如下命令生成 timeline 文件(參考:Paddle 30567):

nsys profile -o my_report -w true -t cuda,nvtx,osrt,cudnn,cublas -s cpu --capture-range=cudaProfilerApi --stop-on-range-end=true --cudabacktrace=true -x true -o my_profile python train.py

在可視化客戶端中加載此文件,效果如下:

image

2. 性能分析

2.1 如何看profile report

Profile Report中可以重點關注OP的調用次數,CPU時間和GPU時間。

  • 調用次數多的OP,很難從report中確定是否有優化空間,需要進一步結合timeline去發現是否有耗時異常的kernel。
    圖片
  • 調用次數少,但是時間上佔比不低的OP,可能是需要優化的
    圖片
  • OP的CPU時間遠遠大於GPU時間,可能的原因一般有:
    • 框架執行調度問題,比較難排查,需要通過添加更細緻的event,以及結合timeline去排查
    • OP運行過程中,引入了設備間的數據拷貝,在report中會直接打印出來。案例PR25810
      圖片
    • OP的Compute中某部分CPU耗時較多,可以通過在c++端添加event去確認

2.2 如何看timeline

2.2.1 認識模型的timeline構成

timeline展示了模型訓練過程中各個事件在時間軸上情況,模型訓練時每一個step經歷的階段都是具有規律的,比如下圖中的動態圖模型timeline大致具有以下階段:

數據讀取 →前向計算→反向計算→optimizer參數更新→ClearGradient

圖片

我們可以根據需要,通過nvprof_nvtx_push爲一個step的不同階段打上標記。

模型分析時,我們要關注timeline的哪些信息呢?

圖中藍色的矩形塊顯示了CUDA Kernel的執行,下面是CPU執行,左側展示出了Kernel的GPU時間佔比,可以結合這3部分確定模型的性能瓶頸。
圖片

2.2.2 timeline常見的問題表現

timeline的表現可能有以下4種:下圖中上面一行表示CPU執行,下面一行表示GPU執行

  • 理想場景:CPU和GPU資源都被充分利用
  • 問題場景1:CPU計算較快,GPU事件較高,通過優化CUDA Kernel縮短GPU時間,就縮短了一次迭代的耗時
  • 問題場景2:CPU計算較慢,GPU出現等待,timeline上會發現GPU Kernel之間有大段空白
  • 問題場景3:存在wait,比如上文中提到的設備間的數據拷貝等,需要等待GPU執行完,CPU才能開始執行
图片

模型中可能是多種問題的混合。

2.3 如何發現性能瓶頸

前面提到的Profile Report可以給我們相對宏觀的統計信息,要定位具體的性能瓶頸,常常還需要結合timeline的表現。通常可以按照以下技巧:

  • 確認reader耗時的佔比:兩個step之間的間隔如果較大, 可能是reader的耗時比較大。Paddle使用DataLoader加載數據,該API的num_workers>0時,使用多進程方式異步加載數據。如果發現兩個step間隔較大,可嘗試調大這個參數。
  • 查看佔比高的Kernel,如果耗時異常,這類kernel需要優化:
    • 佔比高,耗時異常:下圖中drad2d_grouped_direct_kernel佔比高達62.6%,實際上這是conv_grad中調用的cuDNN的kernel。conv在CV模型中調用次數非常高,我們通過對比會發現這個kernel的執行時間遠遠高於其他conv_grad。
      圖片
    • 佔比高,耗時無明顯異常,:batch_norm佔比排第3,但是如果放大timeline去看,其實kernel的耗時並沒有特別異常的。
      圖片
  • 有些Kernel在特定的API配置下計算很慢,需要優化:例如下圖中,佔比7.6%的kernel,1個step調用了3次,但是其中最後一次耗時16 ms,而另外2個大概是幾百us。這意味着,如果找出這個耗時異常的配置,對Kernel進行優化,模型的性能就會有比較明顯的提升。
    圖片
  • 不要忽略單個佔比並不高的kernel:下圖中有兩個佔比分別爲1.4%的kernel。結合右側timeline,會發現這2個kernel在1個step中都分別調用了1次,是在softmax_with_cross_entropy op裏調用的,這個OP的GPU時間需要將這些kernel統計進去。
    圖片
  • timeline上的空白:如果空白佔比非常高,優化後會有比較明顯的收益。
    • 靜態圖模型:如果kernel之間有較大空白,一般可以認爲是框架開銷。在一個GPU Kernel執行之前,框架會完成內存分配、組建ExecutionContext,InferShape,prepare data(可能存在設備間的數據拷貝),launch kernel。這些都是CPU時間,如果這段時間較長,那當前的這個kernel和上一個GPU Kernel之間可能就存在空白。
    • 動態圖模型:還會受python端code的影響,當發現timeline上存在空白,需要結合python code去排查。
    • 一個例子:下圖是一個動態圖的timeline,最下面是CPU事件,可以看到記錄的OP和OP之間都有空白,比如空白標記2。由於我們的profile是從c++端op run開始標記,在2個OP run之間,python端的開銷,或者框架上其他的開銷,都未被記錄在timeline上。框架開銷通常也比較難優化,但可以通過簡單的方法排查是否有相對異常的?如果瀏覽timeline,發現CPU執行的部分,某個空白遠大於其他的空白,可以優先排查下是不是python API的code造成了較大開銷。對於空白1,它發生在conv2d這個OP中的2個GPU Kernel之間,如果要確認,可以優先在OP的compute中,添加一些event去看看哪段代碼造成了這段空白。
      圖片

2.4 其他問題

  • 如何評估一個優化點的性能收益

    • 確定一個step的平均時間,通常可以看模型的log中的batch_cost
    • 確定這項優化工作預期能將開銷降低到多少,比如優化OP時我們可以通過對比競品,大概知道這個OP的耗時能降低多少,估計出優化後的batch_cost,算出性能收益
  • 當timeline上發現某個kernel耗時嚴重,如何確認它的配置是什麼?

    • 收集模型中該OP的所有配置,用op-benchmark跑一遍,找出耗時異常的OP。但收集過程會相對麻煩,目前動態圖只能通過打印log。
    • 通過timeline分析OP在模型中的大概位置,然後結合模型的結構圖,或者python代碼,定位到這個配置。【舉個栗子 pool2d】
  • 有一些OP佔比高,就只能通過優化CUDA Kernel嗎?

    • 不一定,例如混合精度訓練中,常常出現,某個OP不支持float16類型,導致頻繁的cast。假設OP2支持float32類型計算,其他OP都是float16算,那麼混合精度訓練中,將會像下面第2行類似,插入了較多的cast。
    • 明確原因後,我們可以對OP2支持float16類型,那麼就能去除掉這些cast。
    OP1 -> OP2 -> OP3 -> OP4 -> OP2 -> OP5OP1 -> cast_to_float32 -> OP2 -> cast_to_float16 -> OP3 -> OP4 -> cast_to_float32 -> OP2 ->  cast_to_float16 -> OP5
    
  • 競品Torch的profile教程
    • 執行命令:nsys profile -w true -t cuda,nvtx,osrt,cudnn,cublas -s cpu --capture-range=cudaProfilerApi --**stop**-**on**-range-**end**=true --cudabacktrace=true -x true -o my_profile python main.py
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章