| name | model-infer-precision-debug |
| description | 基于 PyTorch 框架的昇腾 NPU 模型推理精度问题诊断技能。当前主要覆盖 KVCache / FlashAttention 相关精度问题,包括 Prefill/Decode 对齐、cache 更新错误、slot/block mapping 错误、attention 路径切换后的精度异常等。触发场景:优化改造后精度验证未通过、模型输出与基线存在显著偏差、Prefill 和 Decode 精度表现不一致、出现 NaN/Inf、量化模式下精度异常放大等。 |
推理精度问题诊断技能
当前主要覆盖 KVCache / FA 相关精度问题,其他推理精度问题后续补充。
不覆盖运行时错误(crash、hang、OOM、算子约束违反等见 model-infer-runtime-debug)。
按"症状分类 → 快速验证 → 分模块定位 → 逐层对比 → 陷阱修复"的分层策略排查精度问题。完整校验工具见 references/fa_debug_utils.md。
部署模式适配:诊断流程对框架部署 / 独立部署两模式均适用,差异仅在参数获取路径——框架部署从 forward_metadata.{block_table, slot_mapping, actual_seq_lengths_*, is_prefill} 取,独立部署从 Runner 自管的 ParallelContext / forward_metadata 实例取。block_table / slot_mapping 在框架部署下是 Dict[attn_type → Tensor],attention 内按 self.attn_type 取出对应 tensor;独立部署 Runner 自管的形态是单 Tensor。
packed sequence 协议(框架部署 modeling 强制要求):input_ids / position_ids 是 [TotalTokens] 一维;hidden_states 全程保持 [TotalTokens, hidden_size] 二维(不要 reshape 成 [B, S, H]);阶段分支统一用 forward_metadata.is_prefill。详见 model-infer-kvcache skill §3.1。
sparse_mode 写法(按路径分类):标准 LLM TND PA 路径 Prefill + Decode 统一 sparse_mode=3 + causal mask;滑窗层 Prefill + Decode 统一 sparse_mode=4 + band mask;MLA absorb 路径 Prefill sparse_mode=3 / Decode sparse_mode=0+mask=None。详见 model-infer-kvcache skill §2.7。
重要原则
- 对比驱动:所有精度判定必须基于与基线(eager 模式 / 优化前)的数值对比,不可凭直觉
- 逐步缩小范围:先定位阶段(Prefill/Decode)→ 再定位模块(KVCache 写入/FA 计算/后处理)→ 最后定位参数
- 最小改动验证:每次只修改一个变量,验证后再进入下一个
- 保留现场:调试过程中的中间 tensor 和日志必须保留,用于后续分析
- 实际执行测试:所有调试步骤由 Agent 实际执行,不将测试工作委托给用户
排查工作流
接到精度调试任务后,按以下步骤执行:
第一步:收集信息 — 获取精度症状、优化阶段、执行/量化模式、基线输出
第二步:快速诊断 — 根据症状分类表判断方向 → 执行快速验证清单(含 FA 入参检查)→ 发现问题直接修复
第三步:分模块定位 — 二分法确定 Prefill/Decode → 针对性插桩验证 → PA 场景执行专项排查
第四步:精细定位(按需) — 使用逐层对比框架,dump 基线 → compare 优化后 → 定位首次偏差层
第五步:修复与验证 — 参照常见陷阱表修复 → 重新运行完整精度验证
第六步:输出调试报告 — 问题概要、根因分析、修复措施、修复验证、遗留风险
第二步:快速诊断
2.1 症状分类
| 症状 | 可能原因 | 优先排查方向 |
|---|
| 输出全为重复 token 或乱码 | KVCache 写入位置错误 | slot_mapping 构造 / 写入算子(PA 用 npu_scatter_nd_update_,BSH legacy 用 scatter_update_) |
| 首个 token 正确,后续偏移 | Decode 阶段 kv_len 更新错误 | kv_len 生命周期 |
| Prefill 正确,Decode 偏差大 | Decode FA 入参错位(按路径分类:MLA absorb Decode 应切 sparse_mode=0 / mask=None;标准 LLM TND PA 保持与 Prefill 同 sparse_mode=3) | sparse_mode 路径分类 / actual_seq_lengths_kv |
| 输出含 NaN/Inf | 数值溢出 | scale 参数、dtype 不匹配 |
| 精度逐层累积偏差 | FA 算子数值精度 | input_layout / NZ 格式 / 量化参数 |
| 多 batch 部分样本错误 | batch 间缓存交叉污染 | block_table / slot_mapping 构造 |
| 量化模式下偏差显著放大 | 量化参数不匹配 | dequant_scale / antiquant_mode |
2.2 快速验证清单
第三步:分模块定位
3.1 排查策略:二分法定位
模型输出与基线不一致
│
├─ 1. 仅运行 Prefill,对比输出
│ ├─ Prefill 输出一致 → 问题在 Decode 阶段(跳到 3.3)
│ └─ Prefill 输出不一致 → 问题在 Prefill 阶段(跳到 3.2)
│
├─ 2. Decode 第 1 步 vs 第 N 步
│ ├─ 第 1 步就不一致 → FA 入参或 KVCache 初始状态问题
│ └─ 第 N 步开始不一致 → kv_len 累计更新或缓存溢出问题
│
└─ 3. 逐层对比(跳到第四步)
3.2 Prefill 阶段精度排查
KVCache 写入验证:在写入算子(PA 模式 npu_scatter_nd_update_ / MLA 融合写入 npu_kv_rmsnorm_rope_cache / BSH legacy 骨架 scatter_update_)调用前后插桩,检查:
- 写入前缓存是否为零(首次 Prefill)
- 写入后缓存内容与 key_states 是否一致(阈值 1e-6)
- PA 路径:cache / states 都需 view 为
[total, num_kv_heads, head_dim];slot_mapping 取 forward_metadata.slot_mapping[self.attn_type].view(-1, 1)
- BSH legacy 路径:scatter_update_ 的 axis 与 layout 匹配(BSH → axis=1)
FA 入参验证:打印并校验 Q/K/V shape、actual_seq_qlen/kvlen、sparse_mode、是否含 NaN/Inf
3.3 Decode 阶段精度排查
KVCache 写入验证:
- 验证 slot_mapping 计算(独立部署连续缓存:
expected_slot = kv_len.view(-1,1) + kv_len_offset;PA 模式:框架部署由 prepare_slot_mapping() 生成,独立部署 Runner 自管按 block_table 索引构造)
- 验证写入位置未越界
- 验证 kv_len 逐步递增正确
FA 入参验证(按路径分类):
- 标准 LLM TND PA:sparse_mode=3 + causal mask(与 Prefill 一致)
- 滑窗层:sparse_mode=4 + band mask +
pre_tokens=sliding_window
- MLA absorb Decode:sparse_mode=0 +
mask=None
- actual_seq_qlen 每个值通常为 1(MTP 场景为
next_n+1)
- block_table dtype 必须为 int32
- KV cache 已写入区域无 NaN/Inf
3.4 分页注意力(PA)专项排查
- block_table 静态校验:
numel == batch_size × (max_seq_len // block_size)
- block_table 值域校验:应为
[0, total_blocks) 的排列,无重复
- slot_mapping 与 block_table 交叉验证
第四步:逐层精细对比
4.1 逐层对比框架
在 Transformer Block 的 8 个关键位置插入 checkpoint,与基线对比:
| 序号 | 位置 | 重要程度 |
|---|
| 1 | Block 输入 | 普通 |
| 2 | Attention Norm 后 | 普通 |
| 3 | QKV 投影后 | 普通 |
| 4 | RoPE 后 | 普通 |
| 5 | KVCache 写入后 | 关键 |
| 6 | FA 输出 | 关键 |
| 7 | Attention 残差后 | 普通 |
| 8 | Block 输出 | 普通 |
对比指标(均转为 float32 计算):max_abs_diff / max_rel_diff / cosine_similarity
判定阈值:BF16 模式 max_rel < 1e-3,量化模式 max_rel < 1e-2
使用流程:dump 基线 → compare 优化后 → 定位首次出现偏差的层和位置
第五步:常见精度陷阱与修复
5.1 KVCache 写入相关
| 问题 | 根因 | 修复方案 |
|---|
| 缓存写入位置偏移(PA 模式) | npu_scatter_nd_update_ 的 cache / states view 形态不一致或 slot_mapping 索引错误 | cache 和 states 都 view 为 [total, num_kv_heads, head_dim];slot_mapping 取 forward_metadata.slot_mapping[self.attn_type].view(-1, 1) |
| 缓存写入位置偏移(BSH legacy 骨架) | scatter_update_ 的 axis 与 layout 不匹配 | BSH → axis=1 |
| 多 batch 缓存交叉写入(独立部署) | kv_len_offset 未按 max_seq_len 对齐 | kv_len_offset = arange(0, batch*max_seq_len, max_seq_len) |
| Prefill 后缓存部分为零 | kv_len 计算错误 | 检查 kv_len 是否等于实际有效序列长度(具体值视框架约定) |
| NZ 格式下缓存异常 | cache_mode 设置错误 | PA 模式使用 cache_mode="PA_NZ" |
| 融合写入算子结果不一致 | npu_kv_rmsnorm_rope_cache 参数不匹配 | 对齐 epsilon(用 kv_a_layernorm.variance_epsilon 而非硬编码)和 RoPE 频率 |
5.2 FA 算子相关
| 问题 | 根因 | 修复方案 |
|---|
| MLA absorb Decode 偏差大 | Decode 仍用 sparse_mode=3 + causal mask | MLA absorb Decode 切 sparse_mode=0 + atten_mask=None |
| 标准 LLM Decode 偏差 | Decode 误改成 sparse_mode=0 + mask=None | 标准 LLM TND PA 路径 Decode 保持与 Prefill 一致 sparse_mode=3 + causal mask |
| 滑窗层 Decode 偏差 | Decode 误改成 sparse_mode=0 | 滑窗层 Prefill+Decode 统一 sparse_mode=4 + band,配 pre_tokens=sliding_window / next_tokens=0 |
| actual_seq_lengths 错误 | TND 应用 cumsum,BSH 用原始值;框架部署字段名 actual_seq_lengths_cu_q vs actual_seq_lengths_kv | 参考 model-infer-kvcache skill §2.4 |
| Decode 首步输出为零 | actual_seq_qlen 为 0 | Decode 时应为 1 或 cumsum([1,...]) |
| MLA absorb 模式 key≠value | cache_nope 读写不一致 | FA 中 key 和 value 都传 cache_nope |
| scale 参数错误 | FA v1 用 scale,v2 用 softmax_scale | 确认参数名与版本匹配 |
5.3 量化相关
| 问题 | 根因 | 修复方案 |
|---|
| W8A8 精度从某层开始放大 | dequant_scale 与权重不匹配 | 检查 per-channel/per-tensor scale |
| W8A8C8 KVCache 精度差 | KV 缓存量化精度损失 | 检查 KV 量化 scale;考虑 BF16 KV |
| antiquant_mode 不匹配 | FA v1/v2 量化接口不同 | v1 用 antiquant_,v2 用 dequant_ |
5.4 Prefill/Decode 一致性
| 问题 | 根因 | 修复方案 |
|---|
| Prefill 不走 KVCache 写入 | 条件分支逻辑错误 | 确保 Prefill 也执行写入算子 |
| Decode 使用了与路径不符的 FA 参数 | 阶段分支判断或路径分类错误 | 阶段分支取自 forward_metadata.is_prefill;按路径分类配 sparse_mode / atten_mask(标准 LLM TND PA 不分阶段统一配置;MLA absorb 才显式分支 Decode 切到 0+None) |
| 切换阶段时 kv_len 未更新 | Prefill 后未执行 kv_len += seq_len | 在 Prefill→Decode 切换时由 Runner 层(独立部署)或 ExecutionEngine(框架部署)更新 |
| MLA absorb Decode 误传 atten_mask | 切到 sparse_mode=0 后没把 mask 设 None | MLA absorb Decode 同时设 sparse_mode=0 + atten_mask=None |
附录 F:FA 入参配置参考
表格供快速验证清单引用,完整校验代码见 references/fa_debug_utils.md,可在 FA 调用前插入 debug_fa_all_params() 一键检查。
F.1 atten_mask 与 sparse_mode 联合校验
| sparse_mode | 含义 | atten_mask 要求 | 适用路径 | 常见错误 |
|---|
| 0 | defaultMask | 可选,通常 None | MLA absorb Decode(q_len=1) | 误把标准 LLM TND PA Decode 也切到此路径 |
| 1 | allMask | 必传 (Q_S, KV_S) | 特殊 | 忘记传 mask |
| 2 | leftUpCausal | 不推荐 | — | — |
| 3 | rightDownCausal | 必传 (2048, 2048) 下三角 | 标准 LLM TND PA(Prefill+Decode 统一)、MLA absorb Prefill | mask 不是下三角;MLA absorb Decode 误用 |
| 4 | band(滑窗) | 必传 (2048, 2048) | 滑窗层 Prefill+Decode 统一 | pre_tokens/next_tokens 错误;FA v1 + BSH + Q_S=1 Decode 路径 op 内部忽略 |
F.2 FA v1 vs v2 关键参数名映射
| 功能 | FA v1 | FA v2 | 易错点 |
|---|
| 缩放系数 | scale | softmax_scale | 默认 1.0,传错名精度崩溃 |
| Q head 数 | num_heads | num_query_heads | 传错名默认 1 |
| Q 长度 | actual_seq_lengths | actual_seq_qlen | 名称完全不同 |
| KV 长度 | actual_seq_lengths_kv | actual_seq_kvlen | 名称完全不同 |
| KV 量化 | antiquant_scale/offset | dequant_scale_key/value | v2 分离 key/value |
F.3 input_layout 与 tensor shape 匹配
| layout | Q 维度 | K/V 维度 | 适用场景 |
|---|
| TND ★ | 3D [T, N, D] | 3D [T, N, D] | 框架部署 PA 标准 LLM 默认推荐,T = packed tokens |
| TND_NTD ★ | 3D [T, N, D] | 3D [N, T, D] | 框架部署 PA + MLA absorb 默认推荐,KV 是 NZ 格式 |
| BSH | 3D [B, S, H*D] | 3D [B, S, H*D] | migrator 骨架 / 独立部署连续缓存(非 PA) |
| BNSD | 4D [B, N, S, D] | 4D [B, N, S, D] | 非 PA,扩散模型 |
| BSND_NBSD | 4D [B, S, N, D] | 4D [N, B, S, D] | KVP 长序列场景(普通 PA 模型走 TND/TND_NTD) |
★ 标注的 TND / TND_NTD 是框架部署默认推荐 layout(详见 model-infer-kvcache §2.7)。PA 模式下 KV 按 block_table 索引,物理存储 shape [total_blocks, block_size, num_kv_heads, head_dim],FA 调用时 view 为 [bn, bs, num_head*dim]。
F.4 actual_seq_lengths 构造
| layout | actual_seq_qlen | actual_seq_kvlen |
|---|
| TND | 累积和 cumsum([s1, s2, ...]) | 累积和 |
| BSH / BNSD | 原始值 [s1, s2, ...] | 原始值 |
Decode 时 actual_seq_qlen 每个值通常为 1(MTP 场景除外)。
F.5 inner_precise 行无效
| 值 | 精度模式 | 行无效修正 | 适用场景 |
|---|
| 0 | 高精度 | 否 | 默认 |
| 1 | 高性能 | 否 | 精度要求不高 |
| 2 | 高精度 | 是 | Prefill + 自定义 mask 有全遮蔽行 |
| 3 | 高性能 | 是 | 同上但允许精度损失 |
F.6 MLA rope 约束
query_rope 和 key_rope 必须同时传或同时不传
- rope D 必须为 64,query D 仅支持 512 或 128
- D=512 时仅支持
sparse_mode 为 0、3、4
精度调试报告模板
## 精度调试报告
### 问题概要
- 症状:{描述}
- 定位阶段:{Prefill / Decode / 两者}
- 定位模块:{KVCache 写入 / FA 计算 / 量化参数 / ...}
### 根因分析
- 原因:{具体原因}
- 影响范围:{受影响的层/模块}
### 修复措施
| 修改文件 | 修改内容 | 修改原因 |
|---------|---------|---------|
| {file} | {change} | {reason} |
### 修复验证
- 精度对比:{通过/未通过,max_rel_diff, cosine_similarity}
- 输出一致性:{token 序列对比结果}
### 遗留风险
- {如有,列出可能的后续风险}
其他已知精度问题
以下问题来自本仓已适配模型的调试经验,不属于 KVCache/FA 范畴但会导致精度异常。
| 问题 | 症状 | 根因 | 修复 |
|---|
| MoE 专家权重未加载 | MoE 输出全零或接近零,cosine~0.4-0.8 | Checkpoint 用 packed tensor 但 load_weights 用 per-expert mapping,权重未正确拷贝 | 添加 packed expert weight loader,按 EP rank 切片后 copy_ 到参数 |
| Zero expert 处理错误 | MoE 输出偏差或切换路由算子后精度骤降至 ~40-50% | 零专家 identity 贡献公式错(应用 original_input 而非 MoE 输出),或路由算子 v1 无 active_expert_range 导致零专家 ID 污染 token 排列 | 路由前用 mask 分离零专家,路由后加回 original_input * weight 的 identity 贡献 |
| DP+EP token 不同步 → NaN | Decode 若干步后出现 NaN,之后全部 NaN | EP all_to_all 有浮点非确定性,DP 各 rank token 选择分叉 → KV cache 累积偏差 | 每步 Decode 后 dist.broadcast(next_token, src=0) 强制所有 DP rank 使用相同 token |
| 跳过训练组件的 scale 缺失 | 输出语义正确但严重重复/退化 | 模型训练时有额外组件,跳过后 embedding 幅度不匹配 | 保留训练时的 normalization factor |