iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
AI Engineering

GPU很忙?他真的有在做事嗎?系列 第 7

Day 7|profile 肥到打不開?圈三個 iteration 就夠

  • 分享至 

  • xImage
  •  

小黃的行車記錄器跑一整班十二個小時,真正會被調出來看的,永遠只有出事前後那三十秒。剩下的每一分鐘都不是證據,是佔硬碟的背景雜音。錄影這件事從來不是錄越多越專業,是要的那段有錄到,才專業。

說白了,今天就是要學會只錄那三十秒。

昨天你學會 nsys profile 一行開錄,也預告了那個坑,興沖沖錄整場訓練,回來抱著一顆幾十 GB 的 .nsys-rep,GUI 轉圈圈轉到天荒地老。今天把煞車裝上。錄得少,反而看得清,很反直覺,但今天結束你會同意。

三圈就夠,多錄的都是雜訊

憑什麼三個 iteration 就敢下判斷?憑 Day 4 你自己看過的那件事,訓練是一圈一圈重複的。進入穩定狀態(steady state)之後,每一圈的花紋幾乎長一樣,同樣的 forward、同樣的 backward、同樣的資料搬運。你要回答的問題是"一個 step 的時間花去哪",那答案在第 33 圈跟在第 3300 圈裡,是同一份。

多錄,不會讓答案更準,只會讓檔案更肥。.nsys-rep 的大小跟事件數成正比,每顆 kernel、每次 memcpy、每個 API 呼叫都是一筆,而事件數就是每圈的事件量乘上圈數。一圈幾萬筆事件,錄三圈是十萬筆這個量級,通常就是幾 MB 到幾十 MB,秒開;錄整場幾千圈,同一套花紋複製幾千遍,GB 起跳,然後你連打開它的機會都沒有。三圈說得清楚的事,三千圈只會說得更模糊。

還有一個常被忽略的紅利,圈得短,量得也更準。Day 6 說過 profiling 有觀測者效應,而 -c 這種圈法在窗還沒打開之前,nsys 幾乎不收資料,攔截的成本也跟著低。也就是說你的 job 前面幾千步用接近素顏的狀態在跑,只有被圈住的那三圈付出完整的觀測代價。錄得少不只是檔案小,是把溫度計對體溫的干擾,壓在最小的範圍裡。

那為什麼是三圈、不是一圈?因為一圈只能看圈內的結構,要看圈與圈之間有沒有縫(dataloader 餵不上、optimizer 完的同步),你至少要有兩個完整的圈相接。抓二到三圈,是資訊跟檔案大小的甜蜜點。

暖機那幾步,不是你 job 的真面目

但別急著從第 0 步開錄,前面那幾十步是另一種雜訊,暖機。

一支 PyTorch 訓練剛起跑的時候,檯面下在忙三件事。cuDNN 在做 autotune,torch.backends.cudnn.benchmark=True 的話,它遇到每個新 shape 都會把幾種演算法各試跑一輪,挑最快的記起來;記憶體 allocator 還在跟 cudaMalloc 要空間,之後才會進到快取、不再真的要;有開 torch.compile 的話,前幾步根本在編譯。這三件事都只發生在開頭,之後整場都不會再出現。

所以你要是從第 0 步開錄,會看到一堆一次性的怪 kernel、突兀的 memcpy、詭異的長空白,然後捲起袖子去修一個第 50 步之後根本不存在的問題。暖機那幾步的樣子,不是你 job 的真面目。判準很簡單,讓它先跑個二三十步,錄 steady state。

一個要留意的例外,autotune 是跟著 shape 走的,不是跟著步數走的。你的 job 要是 shape 會一直變(動態 batch、變長序列),每遇到一個新 shape 就會重新 autotune 一輪,等於暖機永遠暖不完。這種 job 圈窗之前,先確認它到底有沒有 steady state 可言,沒有的話,你抓到的那一窗,只是它眾多樣子裡的一種,下結論要更保守。

這套跳過暖機、只錄幾圈的節奏,也不是 nsys 獨有。PyTorch 自家 torch.profilerschedule(wait=…, warmup=…, active=3, repeat=…) 四個參數,講的就是同一件事,跳過幾步、暖幾步、錄三步、重複幾輪。工具會換,這個節奏不會。

手術刀,讓程式自己喊開錄

最精準的圈法,是讓程式自己喊"開錄"跟"停"。在 training loop 裡加兩行:

for step, batch in enumerate(loader):
    if step == 30:
        torch.cuda.profiler.start()   # 底層就是 CUDA 的 cudaProfilerStart()
    if step == 33:
        torch.cuda.profiler.stop()    # cudaProfilerStop()
    train_step(batch)

然後錄的時候多掛一個旗標:

nsys profile --trace=cuda,nvtx -c cudaProfilerApi -o focused python train.py

-c cudaProfilerApi 的意思是,nsys 一開始就掛著,但什麼都不收,等程式喊 cudaProfilerStart() 才真的開錄,喊 cudaProfilerStop() 就停。上面那段會錄到第 30、31、32 三圈,不多不少,暖機自動排除,因為你開錄的位置就在它後面。錄出來的 focused.nsys-rep 是幾 MB 到幾十 MB 的量級,跟整場錄的那顆幾十 GB 怪物差了兩三個數量級,內容卻是你真正要的那段。

而且這兩行可以放心留在 code 裡不拔。跟 Day 6 講過的 NVTX 一樣,cudaProfilerStart() 在沒有 profiler 掛著的時候就是一個空殼呼叫,什麼都不做。平常跑就當它不存在,哪天要錄,掛上 nsys profile -c cudaProfilerApi 它就活過來,你不用為了錄影特地開一個分支改 code。

上:從第 0 步錄好錄滿,暖機雜訊全進來、幾 GB 打不開。下:讓程式在 steady state 喊 start/stop,只圈三圈,幾十 MB 秒開

預設會把你的 job 收掉

這裡有個坑,我要不講,你第一次用一定嚇到。-c 有個搭檔旗標 --capture-range-end,管的是錄完之後怎麼辦,而它的預設值是 stop-shutdown,錄完直接把你整支程式關掉。

對,你的訓練 job 會在第 33 步戛然而止。這不是 bug,是預設行為,它的邏輯是你都錄完了,剩下跑完也是浪費電。很多時候這正是你要的,錄完就收工。但如果你是掛在一支不能死的 job 上(比如正式訓練順便錄一份),記得改成:

nsys profile --trace=cuda,nvtx -c cudaProfilerApi \
  --capture-range-end=stop -o focused python train.py

stop 只停止收資料,程式繼續跑。但注意,就算程式繼續跑,第一個窗關上之後你再喊一次 cudaProfilerStart(),nsys 是不理你的,一個窗用完就是用完。想要多個窗,得用進階款 repeat:N,每喊一次 start/stop 算一個窗,最多錄 N 個窗。拿來做什麼?比如在第 30 圈、第 500 圈、第 5000 圈各圈一次,看 steady state 有沒有隨時間走鐘,一份檔就裝下三個時間點的切片。

改不了 code,就用時間剪刀

手術刀要動到程式碼。要是你在錄別人的 binary、或不想碰正式環境的 code,還有一把鈍一點的剪刀,用時間切:

nsys profile --trace=cuda,nvtx -y 60 -d 10 -o windowed python train.py

-y 60--delay)是開跑後等 60 秒才開始收,-d 10--duration)是收 10 秒就停,單位都是秒。一行都不用改 code,跳過暖機、只錄中段,八成的場合這樣就夠。

代價是它剪的是時間、不是 iteration,窗的頭尾多半切在某圈的半路上,頭尾兩圈是不完整的,讀的時候要記得把它們略過。而且每圈耗時會飄的話,你瞄準的位置也會跟著飄。要大方向,剪刀夠用;要精準到圈,還是手術刀。

還有一支遙控器

第三招給長跑的服務用。推論服務一開好幾天,問題不知道何時出現,你總不能預先寫死第幾步開錄。nsys 有個互動模式,像遙控器:

nsys launch python serve.py   # 先掛著跑,不錄
nsys start                    # 另開一個 terminal,出狀況了再喊
nsys stop                     # 看夠了,收工出檔

launch 把程式掛在可錄的環境下先跑著(這個 terminal 會被它佔住),startstop 是你從另一個 terminal 對同一台機器喊的。你在旁邊盯,覺得"就是現在"再 start。錄影的決定權從程式碼移到你手上,特別適合抓那種偶發的、等它出現的鬼問題。

這套互動指令還有幾個管家,nsys sessions list 列出這台機器上掛著的所有 session,nsys status 看目前這個 session 是在等、在錄還是已經收了,掛太多忘記誰是誰的時候救你一命。同一台機器可以同時掛好幾個 session,各錄各的,互不打架。

也有反過來要錄長的時候

把話說滿之前,先自己戳一下,三圈不是萬靈丹,它賭的是每一圈都長一樣。有些病剛好就藏在不是每圈都發生的事情裡,這時短窗會讓你漏抓。

三個典型例子。每 N 步存一次 checkpoint,那個瞬間 GPU 會空一大段,你圈的三圈剛好閃過它,就會誤判這 job 很健康;訓練跑一跑穿插 eval,節奏整個換一套;dataloader 每過幾百步就卡一下(快取失效、換 shard),週期比你的窗長。這類問題的正確抓法,不是回去傻傻錄整場,是用剛剛的 repeat:N 在不同時間點各圈一窗,或是拿時間剪刀瞄準事發前後那一段。窗還是短的,只是下刀的位置變聰明了。

判準收成一句,圈多短,看你的問題週期多長。問題每圈都在,三圈就夠;問題幾百步才來一次,你要的是好幾個分開的短窗,不是一個超長的窗。

錄完,三秒檢查照舊

不管用哪招,錄完把 Day 6 的習慣接上,先驗貨再分析:

ls -lh focused.nsys-rep                                  # MB 量級才對,不該是 GB
nsys stats --report cuda_gpu_kern_sum focused.nsys-rep   # 表上要有 kernel

檔案還是 GB 級,代表你的窗根本沒關上,檢查 stop 有沒有被走到。stats 吐一張空表,代表窗沒蓋到任何 CUDA 活動,八成是 start/stop 放錯位置,或 job 根本沒跑到那步。這兩行花你十秒,省掉的是分析半天才發現錄錯段的一個下午。

再進一步的驗法是打開 GUI 數圈數。拿 Day 4 的三個動作看一眼,時間軸上應該恰好是三段重複的花紋,一段不多一段不少。要是看到四段半,代表你的窗切歪了;要是三段的花紋長得不一樣,恭喜,你可能真的抓到了圈與圈之間在飄的問題,那本身就是線索。

跑多卡的先提醒一句,torchrun 起的多進程訓練,每個 rank 是自己的 process,start/stop 要每個 rank 都喊到(通常直接寫在 training loop 裡就自然做到了)。多進程錄影還有別的眉角,Day 9 進容器那天一起算總帳。

小結與明天

今天三把工具,手術刀(-c cudaProfilerApi,程式自己喊)、時間剪刀(-y/-d,不動 code)、遙控器(nsys launch/start/stop,人在旁邊按)。配一個判準,先跑二三十步排除暖機,圈二到三圈 steady state,檔案 MB 量級,打開秒讀。錄好錄滿不是敬業,是把證據淹死在雜訊裡。

不過檔案瘦下來之後,你會遇到下一個問題。時間軸上那三圈裡幾千顆 kernel,一顆顆都叫 ampere_sgemm_128x64 這種名字,哪段是 forward、哪段是 backward、哪顆屬於哪一層,你對不回自己的程式碼。明天 Day 8,我們給 timeline 長嘴巴,用 NVTX 把你的程式結構標上去,讓每一段 bar 都能報上名來。

順帶埋一個明天的鉤子,今天的 -c 其實還能填 nvtx,搭配 -p 指定一個 range 名字,nsys 就會用那段 NVTX range 當開錄的開關,連 cudaProfilerStart 都不用喊(它甚至還有第四個選項 hotkey,用鍵盤快捷鍵當開關,冷門但存在)。等你明天會下標記,這招就解鎖了,到時候"圈哪三圈"跟"這三圈叫什麼名字"會是同一件事。

錄影會剪了。明天,我們教這些畫面開口說話。

參考資料


上一篇
Day 6|自己按下錄影鍵,紀錄運行的過程
系列文
GPU很忙?他真的有在做事嗎?7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言