---
name: diagnose
description: >
  昇腾训练/推理问题的核心诊断循环：收集症状、按 triage-tree 路由、两阶段加载
  并验证 Tier 2 case、命中给 fix（高危 root cause 改提示 halt）或转深度排查。
  Tier-2 未命中但最终解决时起草候选 case。全程写 trace。
  仅在能执行命令的 agent（Claude Code / Codex / pi）中可用。
---

# Diagnose

昇腾问题的核心诊断循环。你是辅助定位工具——**fix 是你给的建议，由人手动应用到客户环境，你不自动改生产**。

> **本文是「可执行脊梁」**：只保留**始终要跑**的骨架与权威规则。步骤的**展开机制**（子步骤/边界/判定细节）见本 skill 的 `references/diagnosis-procedure.md`；**trace 细节**（词表/时间戳/证据落盘完整展开）见 `references/diagnosis-trace.md`，二者**按需加载**。skill 支撑文件一律放本 skill 的 `references/`，**不放仓库根 `references/`**（那是知识库/先验层）。

## 何时用

出现训练或推理问题（中断 / 精度 / 性能），且你在能执行 bash 的 agent 中。被打断后续接 → `/skill:resume-diagnosis`。

## 紧急情况（生产中断）

客户说“紧急 / 生产挂了 / 先恢复”时，诊断目标从“查根因”变成“**先 stabilize**”：

1. **还是先查知识库**——有匹配的 case（比如已知的安全回滚）直接给，这最快。
2. **无快速匹配时**，按已提供的信息一步步给 stabilize 建议：问最近 24-48h 改过什么；`npu-smi info`/`hccl top` 健康；看日志栈尾定位哪层炸；能否先恢复（回滚 checkpoint / 降配 / 重启 daemon）。
3. **不钻深度排查、不写 postmortem**——事后用 `/skill:to-postmortem` 补。

## 流程（骨架）

> 每步只写「做什么 + 何时用」；**子步骤与判定细节**见 `references/diagnosis-procedure.md` 对应「步骤 N」。核心循环 = 收集 →（数据缺口则取采集面）→ 路由 → 两阶段加载+2.5 reference → 验证 → (未命中)深度排查 → 产出。

> **先验 trace 相似检测**（收集症状后、路由前）：扫 `traces/*.yaml`（**全部 status**——进行中+已闭环都留在 `traces/`），按症状里的模型/框架/配置名/category 对每个 state 文件的 `summary`/`detected_framework`/`detected_category` 做**词法 grep 匹配**。命中且 `status: in_progress`(或 `feedback_pending`) → "本地有同问题进行中 `<session_id>`（<summary>）。要 `/skill:resume-diagnosis` 续接吗？"；命中且已 `resolved`/`escalated` → "上次同类 `<session_id>` 已定位（<summary>）。参考其结论还是重新定位？"；无匹配 → 正常从路由开始。**不再泛泛问"有未完成诊断要续接吗"**（旧提示对无关 session 是噪音）。

1. **收集症状 + 确认框架**（全部来自工程师提供）：错误/环境变量/版本组合(引擎+CANN+HDK+架构)；**信息不全就主动问**；**主动裁剪日志**（失败 rank + 栈尾，绝不灌全量 profiler）。→ 展开见 reference 步骤 1。
2. **分类 → `triage-tree.yaml`（Tier 1）**：症状匹配分支 → 路由 namespace；triage 决策记 trace；未命中 → 语义兜底 `triage_semantic`；无法分类 → Tier 3。→ 展开见 reference 步骤 2。
3. **两阶段加载 Tier 2**：阶段一读命中 category 分片索引筛候选(≤5)；阶段二按 `confidence.score` 载全文 + `quickly_check`(primary→fallback) 验证；**阶段 2.5** 按需取先验 reference（只读 `active`）。→ 展开见 reference 步骤 3。
4. **验证 diagnosis checks**：顺序**对照已提供信息**验证；缺信息→追问；mismatch 且有 `fix_on_mismatch`→提示 fix（**先看 severity**）；无 `fix_on_mismatch`→标 `excluded_cases` 试下一个。→ 展开见 reference 步骤 4。
5. **深度排查（未命中）**：**先取流程（方法缺口，见下节）** → Tier 3 grep `postmortems/`；**源码分析**（疑似框架/算子层且 Tier 3 未覆盖）走 `scripts/src_fetch.py`（见源码分析小节）；都没有→诚实说"知识库未覆盖"，建议 `/skill:to-postmortem`。→ 展开见 reference 步骤 5。
6. **产出**：`resolution` + 顶层 `summary` + 沉淀状态(`sedimented`) + trace；**结果反馈闭环**（问 fix 结果回写 confidence + 写 `feedback_pending`）。→ 展开见 reference 步骤 6。

## 数据资产探询（「数据缺口」消费点——精度 / 性能类先问这一句）

reference 有三个消费点，都由流程里的**缺口**决定、都不参与候选路由/排序：**数据缺口**（缺测量数据 → 本节的采集面，在候选加载前）、**判断缺口**（有候选、缺签名/背景/修复依据 → 步骤 2.5）、**方法缺口**（候选全未命中、需要"这类问题怎么查" → 步骤 5）。本节只管数据缺口：命中精度或性能类问题、下一步需要**测量数据**时，**先探询对方手上的资产，再决定给「分析」还是给「采集指导」**——别默认对方不会采，也别默认对方已有数据。一句话的成本，换掉一整段可能没人需要的接入说明（原则九：上下文与注意力都是预算）。

**绑定落在数据上，不写在散文里**：category → 探询问句 → 分支 → 词条 的绑定见 `references/collect-gates.yaml`（本 skill 支撑文件；每个 id 由 `verify_references.py` 校验存在且 `active`——散文里硬编码 ref-id 会静默腐化，已有先例）。本节只给交互形态（问什么、何时问）：

| category | 探询问句 | 闸门形态 |
|---|---|---|
| **precision** | 「你已经有 dump 数据 / 分析结果了吗？还是要我给到代码级接入步骤？」 | 探询型：按回答分支 |
| **performance** | 「你已经有 profiling 数据了吗（采集产物）？还是要我给采集指引？」 | 探询型：按回答分支 |
| **interrupt** | —（不预先问） | 条件型：日志不足以定位时才给采集指引 |

**分支动作与词条不在此重复**（改一处即生效，避免散文与数据双源漂移）：走闸表的 `branches[].action` / `refs`。

三条纪律：

- **探询只问一次、只问一句**——问完按对方回答走，不要"顺便把步骤也讲了"。
- **给接入步骤时必须区分改谁**：改**用户业务代码**（加 `PrecisionDebugger` 等）风险低；改**框架源码**（vLLM/verl 的 runner 等）属"改被测系统"，必须标注临时性 + 给回滚方式。
- **命令以客户环境为准 + 先排除采集副作用**：具体命令以客户环境的工具版本为准（版本差异以实际输出为准，不照搬示例）；采集行为本身可能让问题消失（工具介入的副作用），先排除再下结论——判据词条见闸表 `caveat_refs`。

> 展开细节见 `references/diagnosis-procedure.md` 步骤 1。

**始终要避的坑（内联，不必读 reference 就知道）**：
- **两种缺信息，两个时机**：①路由信息（症状/框架/版本/平台/部署形态）不全 → 步骤 1 问；②验证候选所需的精确配置值（某 `--additional-config` 字段/量化档/硬件型号）→ 本步按需问。别混、别让用户全量倒；**别在确认该字段前把 provisional 结论写成 `hit`**（先给低置信假设 + 明确要什么来验证）。
- **版本软匹配**：compat 不符只降 confidence、不硬排除（soft match）；没填的维度跳过。
- **category 决定 quickly_check 形态**：interrupt→grep 签名；precision→数值阈值；performance→profiler 指标；**别混**。
- **活锁 ≠ 组件故障**：同一请求/实体以固定节奏（~1s）重复打同一条日志、且计数冻结 → 判"控制循环活锁"（调度/接纳/抢占在反复重试却无法推进），**向控制循环上游走**——别把"发日志的组件"当故障组件（load/传输后端常只是表象，真实支点在调度/接纳/抢占层）。
- **判别优先追问**：多假设并存时，先问能二分命中的那个问题（如"PD prefill 节点是否禁用了抢占？"这类**控制/调度**维度，而非数据流细节），再补数据流/传输细节——一刀命中，避免在错误维度上堆证据。
- **类别冲突不得二选一**：日志/报错签名指向的方向与**症状性质**冲突时（例：症状是"输出乱码"→ precision，日志却是 store/BM 初始化失败 → interrupt 味），**显式声明这是两条线、各自走各自的消费点**——不要因为日志里有 interrupt 签名就跳过 precision / performance 侧的数据缺口探询，也不要把类别判给"先看到的那一方"。**category 由症状性质定，不由日志签名定**。
- **连续失败 ≤2（只计 fix 未解决）**：同一个问题给过两次 fix、客户应用后都未解决 → 转人工，不试第三个。**候选被 quickly_check 排除不计入**（那是候选穷尽，不是失败）。

## 方法缺口（流程加载——候选全部未命中时才走这一步）

**何时**：所有 Tier 2 候选都未命中、进入步骤 5 深度排查时——**此时这一步是必走的**（已有候选命中才不走；
跳过它等于把方法面留空）。**不在候选之前加载**——候选命中时流程用不上，
提前加载只是多花注意力预算（原则九）；这是**成本论断**，不是"早加载更危险"
（第四轮对照：流程先行 11/11 未致偏离命中 case，故"会锚定"未获支持，强度如实标为设计判断）。

**怎么做**（绑定在**本 skill 的** `references/procedure-gates.yaml` 的 `kind: procedure` 闸门，id 由 `verify_references.py` 校验）：

1. 读**仓库根先验层的** `references/_procedure-index.yaml`（**选择器**，不是内容；注意与上面那句的
   `references/` 不是同一个目录——skill 支撑文件在 `skills/diagnose/references/`，先验层在仓库根 `references/`）：按本轮 category 过滤 `categories`（**该列为空 = 不限定类别**），用 `title`/`summary` 选**一条**最贴合的流程——**默认一条**。若该流程的前提与现场证据**明确矛盾**（如它要求的数据形态在你手上根本不成立），可换一条：同样受"连续失败 ≤2"约束，并在 trace 记冲突理由；
2. 按该行的 `file` 打开词条，读 **`content.flow[]` 全文**（step / action / check / when_to_use）——**摘要行不算加载**：实测只读摘要与不读等效，流程的反直觉判据会被摘要截断（例：摘要写"同步比例 > 0.2 则存在慢卡"，漏掉"慢卡 = WTR 最小的卡"）；
3. 按流程执行：用每步的 `check` 当判定口径（阈值、分流条件），**跳步要说明理由**；
4. 某步所需数据不在手上（流程要看"逐卡计算耗时"而导出里没有）→ 如实记 `gap`，**不臆断分支结论**；
5. 记 trace：`{action: reference_lookup, ref_id, purpose: procedure}` + `{action: procedure_follow, ref_id, steps_executed, branch_taken, gap}`（字段见 `references/diagnosis-trace.md`）。

> **流程是参考，不是判词**：流程给的是"这类问题怎么查"，不是"这次就是这个"。**它与现场证据冲突时以证据为准**（记 `conflict` 字段），
> 分支结论仍需数据支撑才进结论；
> 且流程走通并解决了问题**不免除 case 沉淀**（方法解决一次不等于这次事故不值得成为 case）。
>
> **流程错了也要能被发现**：跟随流程给出 fix、但工程师回报没解决时，在 `attribution` 事件里写
> `component: reference:<ref-id>`——这样"被跟随后仍失败"的流程能进组件失败簇聚合（`component_tally.py`），
> 否则流程层只有加载率、没有失败率，错流程会被稳定注入而无人察觉。

## severity 闸门（命中后先看这个）

读候选 case 的 `severity` 字段，决定输出策略：

- `benign` → 直接给 fix
- `service-affecting` → 给 fix，但标注 `fix_side_effects`（如 requires-restart），让人协调窗口
- `data-loss-risk`（如"checkpoint 可能被污染"）→ **不直接给 fix**，输出"先停训练、保留现场、通知 owner"。高危 root cause 的正确动作是 halt 不是 patch

每个 `fix_on_mismatch` 都带 `rollback`——人应用失败时能回退。

## 命中时的输出格式（4 段必需 + 2 个按需块）

> **判读口径**：输出的段数与长度应与**问题复杂度相关**。根因明确、fix 是单个开关时，四段写完即可。
> **一件事只说一遍**——同一信息在"结论先行"与后续分节各写一次就是冗余（盲评对照里这是最被诟病的一点）。

**必需四段**（顺序即优先级）：

1. **结论先行**——一句话：现象 + 根因 + 改哪个开关（无内部词表）。
2. **依据链**——每条结论 → 支撑它的证据/检查结果，**逐条标强度**：`已验证`（本轮实际执行过）/`推测`（依赖推断、未直接验证）/`数据`（历史积累，非本轮判断）。
   命中 case 时，把「命中 case / 路由依据 / 排除链 / 匹配症状 / 版本匹配 / 历史表现」作为**本段的子清单**列出，不另起一段：
   - **命中 case 的 id 与统计必写**：`<CASE-ID>`（confidence `<score>`，历史命中 `<hits>` / 误诊 `<misdiagnoses>`）——它是工程师回溯知识库、以及反馈闭环回写 confidence 的锚点，**不要省**；
   - 排除链要给出检查明细；材料没给的如实标"未提供"，**不写"已验证"**；
   - 版本匹配按软匹配口径（compat 不符只降 confidence）；历史表现标为「数据」。
3. **修复方案**——精确命令或 diff 要点 + `rollback` + side-effect（需重启 / 需升级驱动等）+ **应用后如何验证生效**。
   `fix_type` 决定呈现：`env-var` / `config-change` 直接给可执行命令；`code-patch` 给改动文件 + diff 要点（**不可直接执行**）；`pending-investigation` 给排查建议。
   **前提未验证时把核验写成修复的第 0 步**（例：case 的 compat 是 `hdk <26.1` 而客户没报 HDK 版本 → 第 0 步先 `npu-smi info` 核版本，前提不成立就转 plan B），不要带着未验证的前提直接开方。
4. **可靠度与残余风险**——**confidence 分档讲明**：`>0.8` 高可信直接应用、`0.5–0.8` 中可信（应用同时备 plan B）、`<0.5` 仅作提示重点靠手动排查。
   另给：哪些是推测、**触发/不触发面**（什么条件用得上这条结论）、follow-up。

**两个按需块**（不要默认展开）：

- **机制原理**——源码/流程层面的因果链。**展开就讲完整**（触发前置条件 → 每步的"为什么" → 因→果闭环），不得压成一句；命中且根因已由依据链说清时不展开。
  去 AI 味 ≠ 删机制：机制与可核对性必须保留，只把内部词表/交叉引用翻译成因果白话。
- **时间线**——按日志时间排列的可观察事实，只放可观察项、不夹判断；仅当排查跨多轮、或时间先后本身是判据时展开。

## 每步必写 trace（硬要求）

每个 step 后往 `traces/<session_id>.yaml`（每个并发诊断一文件；模板见 `diagnosis_state.yaml.example`）的 `trace` 数组追加一条。**trace 是完整交互轨迹（trajectory）**——统一 `{role, ...}` 结构：

- **agent 事件**：`{role: agent, step, action: triage|load_index|quickly_check|load_full|run_check|hit|miss|tier3|feedback|reference_lookup|triage_semantic|source_analysis|attribution|resume, output, reason, ...}`。`output` 给用户（可精简）、`reason` 记决策依据（**关键决策必写**）；`source_analysis` 必记 `tool_calls`；`attribution` 执行错可加 `component`。
- **user 事件**：`{role: user, step, content, evidence}`——`content` 摘要（短）+ `evidence` 完整证据（`inline` 原文 / `files` 相对路径 / `sources` URL / `missing` 缺口）。
- **证据落盘铁律（必走，无例外）**：短原文 → `inline` 存完整原文；长命令/配置/日志块/附件 → **先写 `traces/evidence/<session_id>/<名>.txt`** 完整原文、`evidence.files` 用相对路径引用、`inline` 只留一行"完整原文见 evidence.files" + 关键指纹。**禁止**只写摘要、或把原文压成指纹塞 `inline`。
- **写前自检**：问"用户贴的原文现在在哪？"——答不出"已存在文件"的相对路径或完整 `inline` → 证据未落，先落盘再写 trace。
- **时间戳**：建 session 写顶层 `created_at`；**每次写 trace 刷新顶层 `updated_at`**（含 resume 续接——置顶诊断面板）。
- **trace 边界（只记诊断轨迹 + 误诊归因，别混自演进）**：用户中途提出的**流程改进/设计讨论**不是本诊断输入（自演进信号）——走 `traces/evidence/<session_id>/<session_id>_evnote.md`（渐进式披露，正常定位不披露，真要改 SKILL/脚本时才升级为 EV 卡）；`attribution` 执行错归因**仅限"确实影响本次结论"**，纯流程改进走 EV 卡。**别把改进讨论写成 trace 的 user/agent 事件**，也别用 `source_analysis` 记 skill 编辑。

> 完整细节（`KNOWN_ACTIONS` 词表、外部事实获取落盘、agent 事件两层、反馈闭环格式、词表同步纪律）见 `references/diagnosis-trace.md`。trace 是误诊归因的唯一依据：误诊先读 trace 断 **case 错**（改库）还是**执行错**（改 skill）。不写 trace → 无法归因 → 可能改坏正确的 case。

## 源码分析（深度排查的子步骤，入口在步骤 5）

报错签名指向框架代码/算子名/量化描述表（如 `fault kernel_name=QuantBatchMatMulV3`、`modelslim_config.py` 相关 KeyError）且 Tier 3 未覆盖时：

1. **按报错背景确定是哪个源码仓，再向其确认版本**（`scripts/src_fetch.py --list` 看已支持仓库：如 vllm-ascend / torch-npu / CANN / mindspeed-* / verl 等，取决于报错签名指向哪——源码分析依赖对应版本，不要猜）。
2. **获取源码（统一走 `scripts/src_fetch.py` 确定性入口——本地优先、复用优先）**：`python3 scripts/src_fetch.py <repo> --ref <tag>`（`--list` 看已知仓库与 host：vllm-ascend=GitHub、mindspeed-*=GitCode、torch-npu=GitCode、verl=GitHub；未知/私有 → `--url`）。脚本把「clone 到哪 / 同版本复用 / URL 来自哪」从 agent 自觉变成**确定性操作**——本地 `src-code/<org>/<repo>/` 已有则**复用**（`git -C log -1`/`describe` 核对版本），没有则按已知 host 拉取。**「不落库」= 源码不随仓库提交、也不写进知识库**；分析仍要保留源码（`src-code/` 本地缓存），知识库只记 `source_ref` 代码指针。
3. **grep 定位**：搜报错签名/算子名/函数名（如 `grep -rn "QuantBatchMatMulV3" vllm_ascend/`）→ 读相关文件片段 → 分析根因。
4. **追问用户验证**：对照预期/复现/补环境信息，验证根因假设。
5. **follow-up**：查知识库是否已覆盖；`gh search issues/prs` 看上游是否已修复（已修复→fix=升级到修复版本；未修复→根因+workaround）；内网不可达→诚实说明无法查证。
6. **多层级**：根因指向更底层开源仓（torch-npu）→ 同样流程分析其源码（`source_ref` 指向该仓）；CANN 等未开源 → **承认局限**，给方向 + 建议联系华为。
7. **沉淀**：根因清楚且知识库未覆盖 → `/skill:to-postmortem` 记 `source_ref: {repo, ref, file, line}`；**顺手**沉淀跨事故稳定的结构事实 → `/skill:to-reference`（software-fact / env-var-table / compat-matrix，判据："6 个月后/跨版本是否仍成立"）。

## 不要做

- 不要替人决定 root cause——给结构化清单，人执行后贴回结果
- 不要连续尝试第三个 case——**两次 fix 未解决**即转人工（误诊保护的串联保护；候选被排除不计入）
- 不要把全量 profiler 灌进 context——裁剪到相关 rank + 栈尾
- 不要用 interrupt 的 grep 思路建 precision 的 quickly_check（category 形态不同）
- **不要直接改本 skill / triage / reference 等会进诊断上下文的资产——改进动作必须先产 EV 卡**（`scripts/ev_proposal.py --new`）再涉及。诊断中发现的流程改进（执行错/摩擦）走 `attribution`（执行错归因喂 component_tally）或 EV 卡（主动设计改进），**不混入本诊断 trace**。
- 被打断 → `/skill:resume-diagnosis`
