iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
AI Engineering

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

Day 9|容器裡錄不到?不是你不會按快門,是門沒開

  • 分享至 

  • xImage
  •  

港口的規矩跟馬路上不一樣。你的車開進貨櫃,行車記錄器隔著鐵皮什麼都拍不到;海關不點頭,設備連開機都不給你開。在這裡,錄不到影從來不是技術問題,是通關問題。

說白了,今天一行新指令都不用學,要學的是開門。

前三天你練起來的那套,錄、圈、標,全都假設你直接在一台機器上跑 python train.py。真實世界的 job 十之八九住在 Docker 容器裡、排在 Slurm 隊伍裡。同一行 nsys profile,搬進去就開始出怪事,取樣起不來、CUDA HW 列一片空白、報表被拒收。九成的情況是三件事沒喬好,工具不在、權限不夠、行程不對,而權限那關其實藏著兩道不同的門,加起來正好四道。一道一道過。

第一道門,錄影機要先上船

最基本的一件事,nsys 這支程式得在容器裡面。你在 host 裝得再齊,容器裡沒有就是沒有。

好消息是,如果你用 NVIDIA 的 NGC 映像檔(nvcr.io/nvidia/pytorch:xx.xx-py3 這系列),nsys 出廠就在裡面,nsys --version 就能確認。自己 build 的映像檔就得自己裝,或把 host 的安裝目錄掛進去。

這裡藏著一個版本眉角。.nsys-rep 的相容性是單向的,新版 GUI 開得了舊版 CLI 錄的檔,反過來不行。所以規矩是,看檔那台(你的筆電)的 Nsight 版本,要大於等於錄檔那台(容器裡)的版本。在容器裡 nsys --version 一下,回你筆電對一眼,十秒省掉一次"檔案打不開"的驚嚇。

工具上船之前,卡也要先看得到。容器要摸到 GPU,靠的是 nvidia-container-toolkit 把 host 的 driver 掛進來,Docker 這邊的開關就是 --gpus all。進去先跑一次 nvidia-smi,有卡再談錄影。另一個新手常撞的細節,容器裡的 GPU 編號是重新編過的,你要了兩張卡,進來就叫 0 跟 1,跟 host 上的 3 跟 7 對不起來。要跨容器對卡,看 nvidia-smi -L 列的 UUID 才算數,編號只在自己船艙裡有效。

開錄之前,先過安檢

上船之後,先別急著錄。nsys 內建一個安檢櫃檯:

nsys status --environment

它會把這台環境的體質全列出來,一項一項給你 OK 或 Fail,長相大概是這樣(示意):

Timestamp counter supported: Yes
Sampling trigger event supported: Yes
Linux Kernel Paranoid Level = 2: OK
Linux Distribution: Ubuntu
Linux Kernel Version: 5.15: OK
CPU Profiling Environment: OK

perf 的 paranoid level 是多少、能不能取樣,全在上面。在容器裡這行尤其值得先跑,因為它能把你即將撞上的門提前指出來,不用等錄完才發現白錄。先安檢,再錄影,這習慣在容器裡值一百分。

第二道門,CPU 取樣的權限

第一個常見的 Fail 長這樣,nsys 一開場就抱怨 CPU sampling 起不來。

機制要先懂一件事,容器不是虛擬機,它跟 host 共用同一顆 kernel。CPU 取樣用的 perf_event_open 是 kernel 的功能,開不開放看的是 host 的 kernel.perf_event_paranoid 設定,文件要求 2 以下才給採。你在容器裡改不動它,因為 /proc/sys 是 host 的地盤,這也是為什麼這道門常常要找管機器的人開。

兩條路。正路是開權限,Docker 跑的時候補一句:

docker run --gpus all --cap-add=SYS_ADMIN ...

--cap-add=SYS_ADMIN 給容器 perf_event_open 的門票,官方文件掛保證的就是這個 capability。它是一把大鎖匙,安全上先跟你的 infra 團隊打聲招呼。

偷吃步是繞過去,--sample=none。Day 6 就預告過這招,把 CPU 週期性取樣關掉,這道門根本不用過。判準很實際,你今天的問題需不需要 CPU 側的取樣資料?我們這一季的主線是 GPU 的 timeline,cuda,nvtx 的追蹤走的是另一條路、不吃 perf 權限,所以多數日子你可以大方關掉取樣,不用為了一個用不到的功能去跟人要 root。

第三道門,GPU 計數器是另一關

別把兩種權限搞混了,這是容器裡最容易鬼打牆的地方。

上一道門管的是 CPU 取樣。但如果你開了 --gpu-metrics-devices=all(Day 6 提過的 SM 活躍度那些硬體計數器),你面對的是另一道完全不同的門,NVIDIA driver 對 GPU performance counter 的管制。被擋的時候會看到一個經典的錯誤代號 ERR_NVGPUCTRPERM,意思是這台機器的 driver 預設只讓 admin 讀計數器。

開這道門要動到 host 端的 driver 參數,NVreg_RestrictProfilingToAdminUsers=0,這是改 kernel module 設定,重載 driver 才生效,雲端環境跟共用叢集上你多半沒這個權力,某些多租戶平台甚至根本不開放。

所以判準跟上一道門一樣,先問要不要。最小組合 cuda,nvtx 完全不碰硬體計數器,一般使用者權限就錄得動。Day 6 說最小抓取是拿 overhead 換資訊,今天再加一句,最小抓取連要的權限都最小。真需要計數器那天(Day 21 之後下細查層會遇到),再去敲 infra 的門。

第四道門,fork 出來的小孩沒人跟

這是前三天一路埋的伏筆,今天總收帳。

Day 6 說過那個經典慘案,錄完打開,CUDA HW 一片空白,因為真正幹活的不是你下指令的行程。Day 8 又補了一刀,DataLoader 的 worker 是 fork 出去的另一個 process。現在把機制講完整。

Linux 的 Python multiprocessing 預設用 fork 生小孩,而 fork 出來的小孩不會接著呼叫 exec 把自己換成新程式,它就以複製體的身分一路做事。nsys 預設只在小孩 exec 的時候接手追蹤,對這種 fork 完不 exec 的小孩,等於沒人跟車。你的 kernel 都是小孩發的,主行程乾乾淨淨,錄出來自然一片白。

解法一行:

nsys profile --trace=cuda,nvtx --trace-fork-before-exec=true -o out python train.py

但要附上官方的原話等級警告,追蹤 fork 到 exec 之間的行為踩在未定義行為上,極端情況可能讓程式 crash 或 deadlock,所以它才不是預設值。開了它就多留意 job 有沒有異常。

不想吃這個風險,還有第二條路,從 Python 那頭改。把 multiprocessing 的生小孩方式從 fork 換成 spawn,spawn 是重新啟動一個乾淨的直譯器,走的是 fork 完馬上 exec 的路,nsys 預設就會跟。DataLoader 給你開關,DataLoader(..., multiprocessing_context="spawn") 一個參數的事。代價是 spawn 的 worker 啟動比 fork 慢,而且不能繼承父行程的全域狀態,有些 code 要小改。兩條路挑一條,別兩個坑一起踩。

順帶把 torchrun 講清楚。torchrun 起 rank 走的是 fork 完接著 exec 的路,屬於 nsys 預設就會跟的類型,所以單機多卡把整包 launcher 錄起來通常行得通,只是所有 rank 疊在同一份檔裡。至於一 rank 一份怎麼錄得漂亮,馬上講。

檔案要落在船外

兩個跟檔案系統有關的隱形地雷。

第一個,容器的檔案系統是用完即丟的。你把 -o 寫在容器裡的路徑,容器一刪,報表陪葬。規矩是 -o 永遠指向掛載進來的 volume(-v $PWD:/work 那種),錄完檔案就在 host 上,接著照 Day 6 的老套路 scp 回筆電慢慢看。

第二個,nsys 錄影過程的中繼資料會走系統暫存目錄,而容器的 /tmp 常常是個很小的 tmpfs。錄長一點就爆給你看,錯誤訊息還常常長得不像空間問題。保險做法是把 TMPDIR 指到掛載的大空間去,跟 -o 一樣的道理,別把大東西寫在船艙裡。

排進 Slurm 隊伍

叢集上的原則一句話,nsys 要跟著 job 一起進節點,不是在 login node 上錄。login node 沒有卡,你在那裡錄到的只有寂寞,而且還會被管理員追殺。

先補一個環境現實,學術叢集跟不少企業 HPC 上跑的常常不是 Docker,是 Apptainer(舊名 Singularity)。別緊張,道理全部通用,nsys 一樣要在映像檔裡、報表一樣要寫到綁定進來的目錄。它的權限模型跟 Docker 不一樣,能不能拿到 perf 這類權限,多半在管理員裝機的時候就定好了,不是你 job 裡一個旗標能翻盤的。但 perf 那道門照樣看 host kernel 的 paranoid level,因為容器共用 host kernel 這件事,在哪套容器技術上都成立。過不了,一樣是 --sample=none 先走,或找管理員。

做法是把 nsys 塞進 srun 跟你的程式中間:

srun nsys profile --trace=cuda,nvtx \
  -o report_%q{SLURM_PROCID} python train.py

亮點在 %q{SLURM_PROCID},nsys 的輸出檔名支援環境變數展開,Slurm 給每個 rank 的編號直接變成檔名,八個 rank 就是 report_0report_7,一人一份、不打架。這招不限 Slurm,%q{} 裡塞任何環境變數都行,MPI 的 rank 變數同理。

不過八張卡各錄一份,常常是浪費。DDP 的 rank 之間本來就長得像(這正是 Day 7 那套 steady state 邏輯),實務上很常見的省法是只錄 rank 0,其他 rank 素顏跑:

srun bash -c 'if [ "$SLURM_PROCID" -eq 0 ]; then
  nsys profile --trace=cuda,nvtx -o report_r0 python train.py
else python train.py; fi'

一份檔換八分之一的 overhead 跟儲存。當然,要抓的是"某張卡特別慢"這種 rank 之間不對稱的病,就得全錄,那是 Day 18 的主題,多卡的檔怎麼合著看也留到那天。

整包組裝起來

Docker 的部分,把今天的門票全數上齊,就是這一行:

docker run --gpus all --cap-add=SYS_ADMIN \
  -v $PWD:/work -w /work \
  nvcr.io/nvidia/pytorch:25.06-py3 \
  nsys profile --trace=cuda,nvtx -o /work/report python train.py

拆開對照今天的四道門,--gpus all 讓你看得到卡、NGC 映像檔讓 nsys 在船上、--cap-add=SYS_ADMIN 開 perf 的門(不需要就拿掉、加 --sample=none)、-v-o /work/... 讓報表落在船外。有些環境的 seccomp 政策管得更緊,cap 加了還是被擋,那就不是你的錯了,拿著錯誤訊息去找 infra,比自己瞎試快十倍。

K8s 上同一道門換個門把,Pod spec 裡 securityContext.capabilities.add: ["SYS_ADMIN"],其餘邏輯一模一樣,volume 換成 PVC、映像檔照舊。

再往上一層,還有 Modal 這種 serverless GPU 平台,我們自己就有 job 跑在上面(對,就是 Day 3 借三層 util 概念的那家)。這種平台連 docker run 那行都輪不到你打,容器是平台包好直接發車的,--cap-add 沒地方加、host 更不是你的,perf 跟 GPU 計數器的門基本上就是關著的,你想開也沒有門把。但也正因為這樣,今天的判準在上面全數適用,cuda,nvtx 不吃特權照錄、--sample=none 把取樣大方關掉、nsys 裝進 image、錄完把 .nsys-rep 寫進平台的 Volume 再拉回本機開。門不在你手上的地方,最小抓取不是選項之一,是唯一那條路,而它剛好夠用。

這不是我單方面的說法,Modal 自己的文件就把話講明白,儀表板上的 GPU metrics 只是訊號、不能拿來 debug 效能,要靠 tracing 跟 profiling,跟 Day 3 我們借他們三層 util 時的結論是同一句。他們官方給的 profiling 範例走的是 torch.profiler 那條路(Day 8 說過的另一支耳朵),要走 nsys 這條,就照今天的套路自己把工具帶上船。

進貨櫃的過關清單

把今天的門全部釘在一張單子上,錄之前掃一眼:

四道門通關圖:貨櫃裡的 job 依序過四道門,門 1 工具上船(NGC 自帶)、門 2 CPU 取樣(kernel 說了算,SYS_ADMIN 或 --sample=none)、門 3 GPU 計數器(driver 說了算,先不用 --gpu-metrics)、門 4 fork 跟車(--trace-fork-before-exec 或 spawn),終點是落在 volume 的 .nsys-rep;每道門上紅字是症狀、下綠字是門票,第 0 步先 nsys status --environment 安檢

搭配 Day 6 那個三秒檢查收尾,錄完 ls -lh 看量級、nsys stats --report cuda_gpu_kern_sum 看表上有沒有 kernel。空表在容器裡九成就是第四道門,回去補 --trace-fork-before-exec=true

小結與明天

今天沒有新的錄影技巧,只有四道門。工具要上船(NGC 自帶,版本看檔那端要新)、CPU 取樣走 kernel 的 perf 門、GPU 計數器走 driver 的門(而 cuda,nvtx 這兩道權限的門都不用過)、fork 的小孩要人跟(--trace-fork-before-exec 或改 spawn)。門之外還有兩顆地雷,檔案要落在船外(volume 跟 TMPDIR),叢集要進節點錄、檔名用 %q{} 分家。再加一個新習慣,進容器先 nsys status --environment 安檢一輪。

到今天為止,錄影的功夫算是齊了,你能在自己機器、容器、叢集上,錄出一份圈得準、標得清楚的 .nsys-rep。接下來的問題變成,檔案一打開,該按什麼順序看,才能三分鐘內講出這份 job 健不健康。明天 Day 10,我們把前面學的整理成一套固定的體檢流程。

門都開了。明天,開始看診。

參考資料


上一篇
Day 8|給 timeline 長嘴巴,讓每段 bar 報上名來
下一篇
Day 10|profile 到手,先體檢照表操課
系列文
GPU很忙?他真的有在做事嗎?11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言