花市逛完,今天開工,摘第一瓣,top_kernels。
先回一下前兩天的帳。Day 11 讀片的時候,你在 forward、backward 段看到一排排最寬的條子;病歷首頁上 top kernel 那格寫著 flash attention 三成(Day 10 那張示意卡)。今天要確診的就是這格,它真的是刀口嗎,它該不該這麼肥,動這一刀又值多少錢。
動手前先把一個順序講清楚。病歷的分診明明寫著先掛發呆,為什麼會診課表讓 top_kernels 排第一個?因為這一瓣不是病名,是排行榜。不管你之後掛哪一科,醫生第一件事都是先看這張榜,它告訴你這台機器的計算基本盤長什麼樣,之後每一科的數字都要拿它當分母。榜先拉出來,發呆那科明天開診。
80/20 這三個字我們喊了十天,今天給它裝上數學,Amdahl 定律。這條定律 1967 年就有了,當年拿來吵多處理器值不值得做,今天拿來算一顆 kernel 值不值得修,一樣鋒利。
一個 kernel 佔整體時間的比例是 p,你把它加速 s 倍,整體的加速是 1 ÷ (1 − p + p/s)。把 s 推到無限大,公式剩下 1 ÷ (1 − p),這就是天花板。第一名佔三成,就算你把它優化到瞬間完成,整體最多也就快 1.43 倍;一顆佔 3% 的 kernel,你拚了命改,天花板是 1.031 倍,連量測誤差都蓋不過去。
這條公式給你兩個判準。第一,動刀前先算天花板,佔比三成的東西別幻想整體快兩倍,物理不給。第二,佔比太小的直接跳過,5% 以下的 kernel,優化它的工程時間幾乎永遠划不來,這是 80/20 的數學版本,肥的才配花你的時間。
榜的形狀本身也是診斷。第一名超過三成,主攻目標明確;超過五成,一枝獨秀,今天這瓣就是為它開的。反過來,前五名加起來還不到四成,這張榜是攤平的,沒有明星肥仔,80/20 在這份 profile 上失效,別戀戰,回分診表掛別科,這種型通常是發呆或 launch 的病,肥不肥根本不是它的問題。

指令一行,窗照 Day 12 的建議剪:
nsys-ai skill run top_kernels \
baseline.t128k.host-fs-mbz-gpu-899.nsys-rep --trim 39 42
吐出來的榜長這樣(欄位是真的,數字示意):
TC Kernel Count Total(ms) Avg(ms) Min(ms) Max(ms)
[✓] flash::flash_fwd_kernel... 3072 7241.5 2.36 2.20 2.61
[-] ncclDevKernel_AllReduce... 5120 3532.8 0.69 0.05 6.8
[-] vectorized_elementwise_kernel... 36864 2211.8 0.06 0.01 0.4
[✓] sm90_gemm_... 6144 1904.6 0.31 0.29 0.35
一張榜四個看點,一次教齊。
第一,Total 是帳。注意這瓣給的是絕對值,不印百分比,佔比要你自己算,窗剪多長你自己最清楚,沒剪的話整份檔的窗 nsys-ai info 會印。這個設計其實暗合 Day 10 的教訓,百分比要跟分母一起出現才有意義,它乾脆把分母的選擇權留給你。而多卡的檔,分母正是最容易掉的坑,窗剪三秒,八張卡在裡面總共有二十四秒的 GPU 時間可用,榜要是跨卡加總的帳,佔比就得除以二十四,不是三,差八倍。示意榜上 flash attention 的 7,241 ms,拿二十四秒一除是三成。嚴格講這跟病歷那格還不是同一把尺,病歷的三成分母是 kernel 總時間(Day 5 那張表的 Time%),這裡的分母是窗長,發呆越多的檔兩個數字拉得越開,又是 Day 10 那堂分母課,對帳時別拿著 A 尺讀 B 數。至於拿三秒去除,會得到嚇死人的 240%,Day 12 的 known limits 才剛警告過這種超過 100% 的百分比。不確定手上這瓣怎麼加總,兩條路,跟單卡視角的 report 互對一次,或翻它的原始碼,一句 SQL 見底。順帶一提,skill run 沒有 --gpu 這面旗,要鎖單卡看,走 report。
第二,Count 配 Avg 是體格。Day 10 講過的判讀原封不動搬過來,Total 高但 Count 少、Avg 肥,是真肥仔,今天的戲;Total 高是靠 Count 上萬堆出來、每顆平均幾十 µs,那是碎瓣堆的假肥仔,融合跟 launch 的病,轉診 Day 15。示意榜第三名就是這種型,三萬六千多顆小不點,總帳擠進前三,但你修任何單獨一顆都沒有意義。
第三,Min 跟 Max 的差距是穩定度。同一顆 kernel 最快 0.05 ms、最慢 6.8 ms,差一百多倍,這不是體質,是遭遇,NCCL 那列就是典型,它的耗時包含等其他卡到齊的時間(Day 11 打過的預防針)。計算類 kernel 要是也差這麼大,先想 Day 7 的 autotune 暖不完,再想有沒有人跟它搶資源。
第四,TC 那欄是 Tensor Core 的體檢,注意它是三種狀態,不是兩種。打勾是吃到了;橫線是天生沒資格,elementwise、NCCL 這類本來就不走 TC,看到橫線不用救;真正該盯的是驚嘆號(⚠️),有資格卻退回一般路徑,多半是精度沒用對,專用電路白白放著,這是整張榜上最便宜的加速空間,處方在下面階梯的第二階,細節 Day 21 用 ncu 抓。
要進腳本就換個吐法,--format json --max-rows 10,Day 12 講過的規矩在每一瓣上都通用,截斷會附 _truncated 老實交代。另外瓣跟瓣的用法不完全一樣,skill list 裡有些名字旁邊掛著星號,那種要帶參數才能跑,-p KEY=VALUE 餵進去,每一瓣吃什麼參數,skill info 接那一瓣的名字就印給你,像 top_kernels 就吃一個 limit,預設印 15 名,想看更深 -p limit=30 餵進去。摘之前先看一眼說明書。
還有一個便宜的交叉驗證。這張榜底下的帳,跟 Day 5 你跑過的 nsys stats --report cuda_gpu_kern_sum 是同一本,兩邊各拉一次,排序應該一致。要是對不上,先別懷疑誰算錯,九成是兩邊的窗沒對齊,官方那邊吃整份檔、這邊剪了 39 到 42 秒,把窗對齊再比。用官方帳驗花瓣、用花瓣驗官方帳,Day 12 說的信要用驗的,這就是最省事的一次。
榜也記得留底。--format json 存一份帶日期跟 commit hash 的檔,跟 Day 10 的 CSV baseline 同一個習慣,下個月 job 變慢,兩張榜一比就知道誰變肥了。另外要拉兩份不同錄影的榜來比的時候,選窗用 --iteration 比 --trim 穩,Day 12 講過,兩份檔的絕對時鐘本來就對不上,用第幾圈說話才公平。這套留底加對比的手工,Day 27 的 diff 會整個自動化,先把習慣養起來。
榜讀完了,確診開始。動手前先把一列移出去,榜上那顆 AllReduce 不是計算,是通訊,它上榜只因為它也是 kernel。算計算佔比的時候把通訊類剔掉,不然你的分母摻水;它的帳自成一科,Day 18 專門拆。剔完,正題,flash attention 佔三成,是病嗎?
先看結構。主檔 A 是長序列訓練,檔名裡那個 t128k 就是十萬 token 等級的上下文,而 attention 的運算量跟序列長度的平方成正比,序列拉長,它在總帳裡坐大是結構決定的,不是意外。順著名字多看一眼,榜上的 flash_fwd 只是這一族的前鋒,backward 那顆是分開上榜的,通常還更肥,Day 8 立過的判準在這裡也通,兩顆加起來看才是 attention 的全額帳。
再看性質。attention 跟 GEMM 這掛是整個模型裡運算密度最高的 kernel,密度指的是每搬一個 byte 能做幾次運算,矩陣乘搬 N² 的資料做 N³ 的乘加,資料進來能被反覆用上百次,所以它吃得滿運算單元,Day 2 的話講,它是在算不動,三種死法裡最體面的一種,錢至少花在算術上。反過來,elementwise 小工每搬一個 byte 只做一兩次運算,時間全花在搬,這種 kernel 天生是 memory-bound,堆再多也堆不出有效算力,這也是為什麼待會的處方是融合它們,省的不是算術,是搬運的趟數。這條密度的直覺 Day 17 會正式畫成 Roofline,今天先記住肥的兩種體質。
所以今天的確診結論很可能是,它很肥,而且它應該肥。這是整篇最重要的觀念翻轉,肥不等於有病。病歷首頁那格 top kernel 從來不是紅字,它是 80/20 的下手處,意思是值得看,不是有問題。真正的病是另外兩種型,一是不該肥的肥,elementwise 這種搬進搬出的小工靠數量擠上榜,代表框架沒把它們融合好;二是該肥的肥得不健康,GEMM 的 TC 欄掛著驚嘆號,有資格吃 Tensor Core 卻退回一般路徑,算得認真但用錯了電路。
確診分三型,處方完全不同。該肥的肥,換更快的實作;不該肥的肥,融合;肥得不健康,對齊。下一節講怎麼下手。
確診完,榜還能倒著讀一次總帳。把計算類的 Total 全部加起來,除以窗長乘卡數,得到這台機器真正花在計算上的比例。拿它跟病歷首頁擺在一起,idle、通訊、搬運、計算,四塊大致要能把 100% 拼起來,拼不齊的那塊,就是你還沒看懂的地方,注意四塊的分母不完全相同(Day 10 的老話),拼之前先各自換算到同一把尺。這個計算比例 Day 17 會升級成 MFU,今天先有個體感。

假設確診完你決定動手,換一版更快的 attention 實作,讓那三成的部分快 1.5 倍。套 Amdahl,整體時間變成 0.7 + 0.3/1.5 = 0.9,省 10%。聽起來不多?拿 Day 1 那張報價換算,8 卡 H100 在 AWS 上一天燒一千三百鎂上下,10% 就是一天一百三十鎂、一個月四千鎂,而你做的只是換個實作,模型一個字都沒動。
反方向的帳一樣要算。要是這一刀需要你兩個星期的工程時間,先拿你的時薪乘兩週,跟每月四千鎂擺在一起看回本週期。佔比、加速倍率、工程成本,三個數字擺上桌,這一刀動不動就不再是感覺問題,是算術問題。Day 10 說過,百分比拿來分診,絕對值拿來決定值不值得動手,今天你手上兩個都有了。
再補一句 Amdahl 沒講的。公式假設你砍掉的時間會原封不動變成節省,但 GPU 上有 overlap,Day 11 你親眼看過通訊躲在計算後面跑。把 top kernel 砍快之後,原本藏在它背後的通訊可能露出來,變成新的等待,實測省下的常常比公式算的少。所以公式給的是期望值,真相要靠優化前後各錄一份、diff 出來,Day 12 的 known limits 也講過同一件事,diff 的判定才是帳,這正是 Day 27 整天要做的事。
決定動手之後,還有一個順序要守,肥 kernel 的優化是一座階梯,從便宜的往貴的爬。
第一階,換現成的。你眼裡的那顆肥 kernel,多半早有人替你優化過一輪,attention 有 FlashAttention 的版本迭代,GEMM 有 cuBLAS 跟 CUTLASS 的更新,PyTorch 有 torch.compile 幫你融合小工。這一階的投入是改設定跟升版本,不是寫 CUDA。先劇透一個真實數字,Day 27 我們會拿一份推論 profile 做完整驗證,把 FA2 換成 FA3,denoising 段的時間少了 35%,整檔 wall time 少了三成,一行依賴的事。
第二階,餵滿 Tensor Core。榜上 TC 欄掛驚嘆號的矩陣乘,檢查精度是不是 FP16 或 BF16、關鍵維度有沒有對齊。官方效能指南講得很細,新版 cuBLAS 就算維度沒對齊也會用 TC,只是效率打折,FP16 把維度切在 8 的倍數才吃得到滿速。這一階動的是 dtype 跟 shape,還是不用寫 kernel。
第三階,才是自己動手。前兩階都撿完了、Amdahl 算出來還值得,你才有資格打開編輯器寫 kernel,而且動手前要先用 Day 21 的 Nsight Compute 看清楚這顆 kernel 卡在哪裡,Day 22 的 CUTracer 往指令層鑽。沒有量測就動手寫,跟沒有確診就開刀是同一種事故。
階梯的精神跟 80/20 一脈相承,優化手段本身也有肥瘦,先撿地上熟的,再考慮爬樹。

第一種,把肥當病。整篇講完你應該已經免疫,但這在真實世界是最常見的誤判,看到 attention 佔三成就嚷嚷要重寫 attention。先問該不該肥,再問肥得健不健康,最後才問要不要動。
第二種,在暖機窗裡拉榜。Day 7 講過 autotune,訓練前幾十步框架在試各種實作,那段窗裡的榜單被試錯的 kernel 洗得亂七八糟,你對著它挑刀口,等於拿試營運的菜單決定招牌菜。榜一定在穩定窗裡拉,Day 12 教的 --trim 跟 --iteration 就是幹這個用的。
第三種,只盯第一名。榜是一個型,不是一個名字。第一名健康不代表整張榜健康,二三四名要是全是 elementwise 碎瓣,融合的空間比動第一名還大;前五名合計不到四成,整張榜攤平,今天這瓣根本不是你的科。看榜跟看片一樣,看整體的形狀,不是看單點。
第一瓣摘完了,收三件事。先算天花板,Amdahl 的 1 ÷ (1 − p),佔比決定上限,上限決定值不值得。再讀榜,Total 是帳、Count 配 Avg 是體格、Min Max 是穩定度、TC 是免費空間,四個看點缺一不可。最後記住今天的翻轉,肥不是病,該不該肥、肥得健不健康,才是診斷。
主檔 A 的這格結案了,flash attention 三成,該肥,健康,處方是留著 Day 27 換更快的實作。但病歷上真正圈紅字的第一位還沒動過,發呆兩成,那才是這個病人最貴的病。
明天 Day 14,摘第二瓣,gpu_idle_gaps。GPU 每發呆一秒,跳表照跳、電照燒,我們去把那些洞一個一個數出來,看看錢到底漏在哪。
top_kernels 與 skill 執行、--trim/--iteration 選窗:nsys-ai docs — user/skills、user/time-windows(欄位以 0.3.0 實測為準)。rich7421/fastvideo-wan-h100-sp1-nsys(Day 27 完整驗證)。