iT邦幫忙

2026 iThome 鐵人賽

DAY 24
1
AI Engineering

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

Day 24|有證據,不代表診斷正確:nsys-ai evidence-first 的實作與一次誤診

  • 分享至 

  • xImage
  •  

Day 23 說 agent 的正確形狀,是「在證據工具上跑內圈」。今天把 nsys-ai 的 agent 拆開看:這個形狀在工程上怎麼實作;以及更重要的,實際跑一次會得到什麼,又會在哪裡出錯。

資料範圍提醒:本文以 nsys-ai commit 1cc03b5 的 agent 原始碼(agent/runner.pyagent/persona.mdcannot_answer.py 等)為準,並在主檔 A 上實跑 agent ask。本次執行未設定 LLM API key,走純 deterministic 路徑;有 LLM 時的差異會另外註明。本文涉及的 agent 與 idle-gap 程式到 v0.3.0-26-ga711c44 仍未變動。

五道工程護欄:縮小模型自由發揮的空間

從原始碼來看,這個 agent 可以整理成五道工程護欄。先把界線說清楚:它們能降低編造風險、保留重跑路徑,但還不能保證每個結論都受到證據支持。

**護欄一:選 skill 是選單題,不是申論題。**收到問題後,agent 要決定執行哪些 skills。有 LLM 時,模型會看到使用者問題、root_cause_matcher 的 triage 結果與 skill registry,然後從 registry 挑最多 4 個名字,只回逗號分隔的清單;parser 會丟掉未註冊的名稱。沒有 LLM 或模型輸出無效時,就改走關鍵字表,再不行才使用固定 fallback。模型不會直接接觸資料庫,也不能自行撰寫 SQL。

**護欄二:證據先跑,才輪到摘要。**被選中的 skills 先對 profile 執行、產出 KB 級的結構化結果;語言模型如果存在,拿到的是這些結果,不是 53 萬列原始資料。負責 synthesis 的函式把設計意圖寫成:Ask an LLM to summarize usable evidence, never to invent evidence.(要求 LLM 摘要可用證據,不要創造證據。)

不過,這句註解和 prompt 是行為要求,不是程式驗證。Summary 仍是 LLM 產生的自由文字,_answer_summary() 只檢查它是不是空字串或失敗訊息,沒有逐句核對內容是否能由 evidence 推出。真正由程式固定組裝的是 Primary DiagnosisEvidenceConfidenceRecommended ActionVerify。所以 Day 23 的體積與結構問題在這裡有了工程解法,可溯源性也提高了;但「摘要是否真的受到證據支持」仍是一道尚未實作的檢查。

護欄三:「沒看到」與「沒問題」使用不同表示。cannot_answer.py 把一個診斷工具可能遇到的狀態分成三種:沒有問題、找到問題,以及我沒能看。第三種過去曾有八種表達方式,其中五種以 exit 0 結束,讓「沒能看」讀起來像「一切正常」。修正後的契約是:無法回答的 payload 必須帶棄權標記與非空理由;「程式跑完,但資料不足以回答」仍維持 exit 0,因為棄權本身也是一個完整結果。Day 22 說過「exit code 0 不是成功判準」,這裡是同一課的工程化。

但契約寫下來,不代表每個 skill 與 answer renderer 都已完整遵守。後面會看到,空陣列、diagnostic row 與被截斷的 evidence 仍可能讓「沒看到什麼」沒有清楚出現在最後答案裡。

**護欄四:信心分數由固定規則產生。**信心不是模型自評。當 root_cause_matcher 有主導發現時,critical、warning、info 與未知 severity 分別對應 0.90、0.75、0.55 與 0.65;有 usable skill 輸出但沒有主導發現時是 0.60;完全沒有 usable 證據時是 0.20。前四個分數繫在 severity,後兩個繫在有沒有 usable row。這把尺可以穩定重現,但它不是診斷正確率,也不代表已經有另一個 skill 交叉驗證。

**護欄五:沒有 LLM 也能運作。**證據執行與答案外框都是 deterministic 的,LLM 只負責可選的 skill selection 與 Summary。原始碼的註解寫得很清楚:answer remains usable and deterministic when no provider is configured. 本文接下來的實跑就是這條路徑。

persona 的第一條原則也表達了同一個設計方向:Evidence over intuition. Every diagnosis cites specific kernel names, durations, and timestamps. Never guess — query. 這是系統想達到的行為,是否真的做到,仍要回到輸出與程式逐項檢查。

nsys-ai agent 的 evidence-first 流程:問題先經過 triage 與 skill 白名單,再由 skills 查詢 profile、整理 usable 與 unavailable,最後組裝 deterministic 答案;LLM Summary 是可選自由文字,目前尚未經過逐句的 evidence support 驗證

這些護欄不是孤例

研究與業界已有幾個相近方向,但它們支持的是「工具與結構能降低風險」,不是「接上工具就不會出錯」。

**工具接地的價值有量化結果。**ToolQA 的 Coffee-Easy 任務中,同一個 LLaMA-2 13B,純生成的成功率是 2.0%,接上 ReAct 工具流程後是 31.0%。不過,同一篇論文也記錄了另一面:工具明確回報篩選條件錯誤後,ReAct(GPT-3.5)下一步卻自行宣稱篩選成功,拿著錯的資料繼續作答。**工具在場,不代表工具輸出真的被遵守。**這就是先完成證據查詢、再限制摘要範圍的價值;但若沒有支持關係的驗證,風險只會縮小,不會歸零。

**棄權有研究支撐。**在 ParaRel 的 in-domain 設定中,LLaMA-13B 的 R-Tuning-R 回答率是 44.00%,已回答題目的正確率是 87.06%;一般微調會回答全部題目,正確率是 66.53%。這些數字的分母不同,不能說「全部問題的正確率提升到 87%」;較準確的讀法是用回答涵蓋率換取已回答部分的可信度。後續 AbstentionBench 則提醒,該拒絕卻回答、以及可以回答卻拒絕,都要一起評估,棄權不是越多越好。

**廠商機制管結構,不管內容真假。**OpenAI 的 tool_choice: "required" 可以要求模型至少呼叫一個工具,strict: true 可以讓參數符合支援的 JSON Schema;Anthropic 的 Structured Outputs 則透過 constrained decoding 產生符合 schema 的結果。這些機制能約束呼叫與格式,不能保證結論受到工具證據支持。OpenAI 官方文件也明確提醒,Structured Outputs 仍可能包含錯誤。這裡談的是通用機制;目前 nsys-ai 的 skill selection 使用文字 prompt 加 parser,並沒有直接使用上述 strict tool schema。

**效能分析領域也開始出現相近做法。**AMD TraceLens 的 agent 規則要求每項發現附上證據與推理,並禁止在分析腳本失敗後自行讀原始 CSV 做瓶頸判斷;Arm Performix Dynamic Insights 則要求建議有 profile 證據,再以獨立評判模型驗收。兩個案例還不足以稱為產業標準,但已經呈現共同方向:先建立可檢查的量測鏈,再讓模型負責整理與解釋。

實跑一次:輸出可重現,但 scope 已經混了

對主檔 A 問一個典型問題:

nsys-ai agent ask megatron.sqlite "為什麼這個訓練慢?NCCL 佔比高嗎"

沒有 LLM 時,關鍵字 nccl 會選到一組候選 skills;程式用 set 去重、依字母排序後只保留前 4 個,因此這次實際執行的是 kernel_overlap_matrixnccl_anomalynccl_breakdownnccl_communicator_analysisoverlap_breakdown 雖然也在關鍵字表裡,最後被四項上限截掉。

## Summary
Ran root_cause_matcher, kernel_overlap_matrix, nccl_anomaly, nccl_breakdown,
nccl_communicator_analysis against the profile ...

## Primary Diagnosis
GPU Bubbles (Pipeline Stalls)

## Evidence
- source_skill=root_cause_matcher; metric=pattern=GPU Bubbles, severity=warning;
  window=full profile; scope=profile;
  evidence=20 gaps > 1.0ms, totaling 120982.4ms (67.7% of profile)
- source_skill=root_cause_matcher; metric=pattern=Inefficient NCCL Communicator;
  evidence=allreduce ran at 0.00 GB/s (0.0% of H200 NVLink)
- source_skill=kernel_overlap_matrix; unavailable: no kernels found
- ...

## Confidence
0.75 (medium-high)

## Verify
`nsys-ai skill run root_cause_matcher megatron.sqlite --format json`

先看這份輸出做對了什麼。顯示出來的 evidence 有 source_skill 與量測窗;kernel_overlap_matrix 跑不出結果時,保留了 unavailable 原因;結尾也提供可重跑的 Verify 指令。在這次無 LLM 的 deterministic 路徑中,畫面上的數字都來自 skill 輸出。

不過,kernel_overlap_matrix 的 unavailable 也透露了第一個問題:這份答案沒有使用一致的分析範圍。

一次可追查的誤診:四個量測問題

照著 Verify 指令重跑,再把範圍固定下來,可以拆出四個彼此不同的問題。

**第一層:device scope 混在一起。**主檔 A 的 GPU kernel 只錄到 device 1–7,每張各有 6,460 筆,device 0 沒有 kernel。root_cause_matcher 遇到預設 device 0 沒資料時,會自動改分析第一張有活動的 device 1;但其他被選中的 skills 不會繼承這個決定,kernel_overlap_matrixnccl_breakdown 仍然查 device 0。前者因此回傳 no kernels found,後者回傳空陣列,甚至沒有出現在 deterministic Evidence 區塊。

scope 標籤也沒有把這件事說清楚。root matcher 的 row 使用欄位 analysed_device=1,answer renderer 只辨識 gpu_iddevice_iddevice,最後把 device 1 的數字印成 scope=profile。所以這裡不只是「某項證據 unavailable」,而是同一份回答混用了 device 1、device 0 與 profile-wide 資料。

**第二層:時間窗口錯了。**A 檔的完整 kernel 時間軸包含初始化與收尾,訓練的穩定區間是 Day 7 圈出的 39.964–46.123 秒。用同一張 device 1、同一個 device-union 口徑比較,full profile 的 idle 是 16,337.4 ms,約占 kernel span 的 45.7%;進入穩定窗後只剩 93.82 ms,約 1.52%。這才把「窗口」單獨隔離出來。agent ask 目前沒有 --trim--device 介面,因此使用者無法從這條 CLI 明確指定分析範圍。

**第三層:idle 口徑錯了。**即使直接執行 root_cause_matcher --trim 39.964 46.123,它仍回報 14,211.5 ms、57.7%。同一張 device 1、同一個穩定窗,device-level idle 明明只有 93.82 ms。差異來自 gpu_idle_gaps 的兩種量法:total_idle_ms 把四條 active streams 的 gap 加總,pct_of_profile 再除以 kernel span 與 stream 數;device_idle_ms 則對所有 kernel 區間取聯集,量的是整張 GPU 都沒有 kernel 執行的時間。root matcher 目前拿前者判斷 GPU Bubbles,沒有使用同一份 summary 裡已經存在的 device_idle_ms

stream 52 與 60 每步只在特定階段執行短暫的 collective,其餘時間沒有工作是正常分工。把這些 per-stream gaps 當成整張 GPU 的 bubble,就會重演 Day 14 的假空白。

第四層:頻寬語意錯了。「allreduce 跑在 0.00 GB/s、只用到 NVLink 的 0.0%」看起來像嚴重瓶頸,但 Day 19 已經從 payload 解出,這是每步兩次、每次 4 bytes 的純量記帳同步。對 4-byte 訊息談頻寬利用率沒有診斷意義;規則在判斷 communicator 是否低效之前,還需要訊息大小門檻。

這四個問題都不是本次 LLM 幻覺,因為這次根本沒有使用 LLM。它們分別來自 scope 傳遞、窗口選擇、統計口徑與規則語意。evidence-first 沒有阻止誤診,但讓問題能被定位到明確的程式位置:讓 agent ask 接受 trim 與 device、把選定 scope 傳給所有 skills、輸出實際 device、改用 device-level idle,並在頻寬規則加入 payload 門檻。

agent ask 的可追查誤診:輸出雖然保留來源與 Verify 指令,覆核後仍發現 device scope 混用、full profile 窗口、per-stream idle 口徑、4-byte 頻寬語意,以及 Primary Diagnosis 沒有對齊問題等五個缺口

還有一層:Primary Diagnosis 不一定在回答問題

前面四層都是量測與判讀問題,另外還有一個 answer-level 的缺口:Primary Diagnosis 不一定和使用者的問題對得上。

架構上,agent 收到任何問題都會先執行 root_cause_matcher 當 triage;組裝答案時,_first_actionable_row() 直接取 root matcher 回傳順序中的第一個可用發現,不是依使用者問題重新排序,也不是挑 severity 最高者。使用者問題主要影響後面選出的 4 個佐證 skills。因此,對主檔 A 分別問「哪個 GEMM 最花時間」「H2D 會不會太多」「NCCL 慢不慢」,Primary Diagnosis 都是 GPU Bubbles,因為它剛好是 root matcher 的第一個 actionable row。問句改變了後續 evidence,卻沒有改變主診斷。

再拿 healthy_1pct.sqlite 做反例。這份 fixture 只有 200 個 GEMM 與 200 個 elementwise kernels,沒有 NCCL。實際詢問「NCCL allreduce 有沒有慢」時,輸出是:

## Primary Diagnosis
No specific diagnosis could be grounded for: NCCL allreduce 有沒有慢

## Confidence
0.60 (medium): skill output exists, but no root-cause matcher finding dominated.

這次沒有硬答成 bubble,但也沒有直接回答「profile 裡沒有 NCCL」。nccl_breakdown 回傳的空陣列不會顯示在 Evidence;nccl_communicator_analysis 回傳的 diagnostic row 則被當成 usable,最後只印成 row_present=true。所以 cannot_answer 的三態契約雖然存在,answer 層仍缺少「問題是否真的被這些 evidence 回答」的相關性檢查。

三種常見的誤判

第一,把「有來源」當成「受到來源支持」。來源只能告訴我們數字從哪裡來;窗口、scope、統計口徑或規則錯誤時,真實數字仍可能支持不了結論。若輸出含 LLM Summary,還要再檢查摘要中的每個主張是否能在 evidence 找到對應依據。

第二,把信心分數當成正確率或交叉驗證結果。這裡的 0.75 只表示第一個 root matcher 發現是 warning,而且存在 usable row;那筆 row 可以就是 root matcher 自己。它是固定規則產生的狀態標籤,不是診斷有 75% 機率正確。

第三,把沒有出現在 Evidence 的 skill 當成沒有問題。answer renderer 最多顯示五行,而且空陣列不會自動變成 unavailable 行。檢查 agent 輸出時,除了看已列出的證據,也要確認每個 selected skill 的實際 device、窗口與原始回傳狀態。

小結與明天

今天把 evidence-first 拆成五道工程護欄:選單化的 skill 選擇、證據先行、三態契約、固定信心規則,以及無 LLM 的 deterministic 保底。它們縮小了自由發揮的空間,也保留重跑路徑;但 LLM Summary 目前沒有支持關係驗證,answer renderer 也還沒有統一 scope 與問題相關性。

主檔 A 的實跑進一步拆出五個缺口:device 0/1 混用、full profile 窗口、per-stream 與 device-union 口徑混淆、4-byte 訊息的頻寬語意,以及 Primary Diagnosis 沒有對齊問題。結論不是「護欄已經防住編造,只剩人類判準」,而是:護欄讓錯誤可重現;判準仍要被明確編碼、傳遞並測試。

evidence-first 已由程式保證與尚未保證的邊界:skill 白名單、先執行證據、deterministic 答案外框與固定信心可以重現;Summary 支持關係、scope 一致性、問題相關性與判讀語意仍需要額外驗證

Day 25 再做同條件比較。不過在把問題交給 agent 之前,要先把外圈條件真正傳進內圈:使用 runner 的 trim_kwargs,統一指定 39.964–46.123 秒與 device 4,確認所有 selected skills 都收到相同 scope,再對照前二十天的人工作答。這樣的比較才有意義;目前公開的 agent ask CLI 還不能直接帶這兩個參數。

參考資料


上一篇
Day 23|把 trace 丟給 AI agent就好了嗎?先算從token帳單來看
下一篇
DaDay 25|把 GPU 判準交給 AI agent:nsys-ai 已經能做什麼,下一步一起補什麼?
系列文
GPU很忙?他真的有在做事嗎?26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言