mcpField
A
ascendc-operator-precision-debug

ascendc-operator-precision-debug

github.com/ascendDevelopment52159 installsUpdated 25w ago
Repository

About ascendc-operator-precision-debug

A precision-debugging skill for AscendC operator developers. It proceeds from error analysis and code review through controlled experiments and instrumentation, then guides fixes and validation.

Key features

  1. 01Error statistics and pattern analysis
  2. 02AscendC code review checklist
  3. 03Controlled isolation experiments
  4. 04Kernel instrumentation guidance
  5. 05Repair and regression validation

Use cases

  1. 01Tracing failures limited to FP16 or BF16
  2. 02Locating errors at tile or multicore boundaries
  3. 03Diagnosing inconsistent results across runs
  4. 04Finding the first divergent compute step

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/ascend/agent-skills --skill ascendc-operator-precision-debug

After installing, type/ascendc-operator-precision-debugin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

examples/
async-sync-missing.md
fp16-no-upcast.md
gm-offset-error.md
multicore-tiling-overlap.md
tail-tile-misalign.md
scripts/
debug_precision_template.py

Details

From the project README

AscendC 算子精度调试

按「由浅入深」五阶段定位根因:先看数据分布,再查代码易错点,然后实验隔离,最后插桩定位。

Phase 1: 误差分析 → Phase 2: 代码审查 → Phase 3: 实验隔离 → Phase 4: 插桩定位 → Phase 5: 修复验证

Phase 1:误差分析

原则:先看数据,再看代码。 先搞清楚「错在哪、错多少、错成什么样」。

收集失败用例的 shape、dtype、MaxAbsErr/MeanAbsErr/CosineSim,然后基于 scripts/debug_precision_template.py 创建 csrc/ops/<op_name>/test/debug_<op_name>_precision.py(替换占位符后运行),自动分析:

  1. 误差统计:MaxAbsErr、MeanAbsErr、MaxRelErr
  2. 首个错误元素:多维坐标 + 线性下标 + NPU 值 vs 参考值
  3. 错误分布:错误元素数量/占比、错误间隔是否呈周期性
  4. 特殊值:输出是否全零、含 NaN/Inf
  5. 自动对照:固定输入 vs 随机输入、缩小 shape 二分
误差特征 → 初步判断
现象最可能原因下一步
FP16 失败,FP32 通过未升精度到 FP32 计算Phase 2 查 Cast
输出全零CopyOut 未执行 / GM 偏移错Phase 2 查 CopyOut
输出含 NaN/Inf除零 / log 负数 / 溢出Phase 2 查 Compute
全部偏差,CosineSim≈1系统性精度损失Phase 2 查升精度
周期性/条纹状错误tile 边界 / 搬运偏移Phase 3 实验
仅尾部元素错尾 tile 长度 / 对齐Phase 2 查尾 tile
多次运行结果不同异步同步不足Phase 3 实验 B
小 shape 过、大 shape 挂多核/tiling 边界Phase 3 实验 A
固定输入过、随机挂地址/stride/偏移错Phase 3 实验 C

Phase 2:代码审查

MANDATORY:读取 op_host/<op_name>.cpp、op_kernel/<op_name>.cpp、design.md(若存在),按以下清单由浅入深排查。

第一层:基本正确性(最高频)
  • FP16/BF16 未升精度:Compute 中半精度是否先 Cast 到 FP32 计算再 Cast 回?这是最高频精度 bug。
  • 计算公式错误:API 调用序列与设计文档/PyTorch 逐步对照——运算顺序、标量符号、是否缺步骤。
  • GM 偏移单位混淆:xGm[progress * tileLength] 是元素偏移,不要多乘 sizeof(T)。
  • tileLength vs curTileLength:偏移用 tileLength,计算/搬运用 curTileLength(尾 tile 可能更小)。
第二层:搬运与对齐
  • DataCopyPad copyLen:DataCopyExtParams 的 copyLen 是字节数 = curTileLength * sizeof(T)。
  • 尾 tile 对齐:尾 tile 不满足 32B 对齐时,alignedTailLen 计算及使用是否正确。
  • 多输入偏移不一致:多输入 tensor shape 不同时(如 RoPE 的 x vs cos/sin),各自的偏移计算是否正确。
第三层:Tiling 与多核
  • Host/Kernel tiling 不一致:同一符号(如 tileLength)在 host 和 kernel 中含义是否一致。
  • 核间边界重叠/遗漏:formerNum × formerLength + tailNum × tailLength 是否恰好覆盖全部数据。
  • bufferCoefficient 错误:与设计文档 UB 分配表核对,错误的系数会导致 tileLength 偏差。
第四层:API 陷阱
  • ReduceSum/Max 修改源数据:归约可能改写源 tensor,后续若复用需先 Adds(backup, src, 0.0f, len) 备份。
  • AllocTensor/FreeTensor 未配对:与 EnQue/DeQue 需严格配对,否则缓冲区泄漏。
  • 向量长度参数:AscendC 向量 API 长度是元素个数,非字节数。
第五层:边界情况
  • 除零 / 定义域越界:Div、Reciprocal 防零;Ln 要求正数;Sqrt 要求非负。
  • tiling 整数溢出:乘法是否可能溢出 int32?建议 int64_t。

检查点:输出审查报告——疑似问题列表(按可能性排序)。若已锁定根因,跳到 Phase 5;否则进入 Phase 3。


Phase 3:实验隔离

Phase 2 无法直接锁定根因时,通过控制变量实验缩小范围。每次只改一个变量。

实验 A:block_dim → 1(多核隔离)

在 op_host 临时硬编码 blockDim = 1,重编译测试。可配合缩小 shape。

结果结论
单核过、多核挂核间问题:GM 区间重叠 / tiling 映射 / 核间同步
单核也挂非多核问题 → 实验 B
实验 B:PipeBarrier<PIPE_ALL>(同步隔离)

将 kernel Process 中所有同步临时替换为 AscendC::PipeBarrier<PIPE_ALL>()(CopyIn / Compute / CopyOut 之间各加一个)。

结果结论
全屏障后过核内同步不足 → 逐步恢复细粒度同步定位
仍失败非同步问题 → 实验 C

PIPE_ALL 仅用于实验隔离,绝不可作为最终方案。

实验 C:固定/规律输入(地址隔离)

分别用全 1、等差序列(torch.arange)、随机输入测试。

结果结论
全 1 过、等差/随机挂地址/偏移/stride 错误(常数输入掩盖了偏移问题)
全都挂计算逻辑或全局 tiling 错误
全都过特定数值范围触发精度问题 → 查边界值/极值
实验 D:缩小 shape(边界隔离)

shape=(32,) → (tileLength,) → (tileLength*2,) → 原始 shape,定位恰好开始失败的分界点,反推 tile/核边界。

首错下标 + tiling 反推
首错线性下标 → 第几个 tile → 哪个核 → 该核 GM 起始偏移 → 搬运预期字节数

周期 = tileLength → 搬运/偏移问题;周期 = 向量宽度 → 计算流程问题;与核边界对齐 → 多核/offset 问题。


Phase 4:插桩定位

问题范围已收敛到某阶段/某 tile 后,用 AscendC::printf 和 AscendC::DumpTensor 精确定位。

核心规则
  1. 仅 0 核打印:每个核计算逻辑一致时,加 if (AscendC::GetBlockIdx() == 0) 减少输出量。
  2. 同步后再读:在 DeQue / PipeBarrier 之后才能读 LocalTensor,否则读到未完成搬运的脏数据。
  3. FP16 先转 float:AscendC::printf("v=%.6f\n", static_cast<float>(tensor.GetValue(idx)));,直接打 half 会乱码。
  4. 用 desc 区分阶段:DumpTensor 的 desc 参数(0=CopyIn 后, 1=Compute 中间, 2=CopyOut 前)。
  5. 小量起步:DumpTensor 的 dumpSize 从小值开始,过大会导致缓冲满或截断。
printf vs DumpTensor 选择
场景工具
标量、分支判断、单个下标AscendC::printf
连续一段 tensor 快速扫AscendC::DumpTensor(tensor, desc, dumpSize)
全量逐元素对比不在 kernel 内做 — Host 读 GM + Python 脚本
插桩策略

在 Compute 函数内 DeQue 之后,逐步骤插桩,与 Python 侧用相同输入手算的中间结果逐步对比。第一个出现偏差的步骤即为根因所在。

// 示意:0 核、第 0 个 tile
if (AscendC::GetBlockIdx() == 0 && progress == 0) {
    AscendC::printf("[step1] tmp[0]=%.6f\n", static_cast<float>(tmp.GetValue(0)));
}

Phase 5:修复验证

常见修复模式
根因修复
FP16 未升精度添加 Cast(fp16→fp32) + 计算 + Cast(fp32→fp16)
GM 偏移错修正偏移公式(元素 vs 字节)
尾 tile 长度错计算/搬运用 curTileLength,偏移用 tileLength
tiling 参数错修正 host 端 tiling 计算
同步缺失添加正确的 EnQue/DeQue 或 PipeBarrier
ReduceSum 覆盖源先 Adds 备份再 ReduceSum
搬运长度错修正 DataCopyExtParams 的 copyLen
修复后
  1. 移除所有调试插桩(printf/DumpTensor),或用 #ifdef DEBUG_PRECISION 包裹
  2. 重新编译安装
  3. 运行原失败用例 + 完整精度测试
  4. 仍失败 → 回到 Phase 1(最多 3 轮),3 轮后仍失败则报告用户
输出要求(MANDATORY)

调试完成后 MUST 在对话中展示:问题摘要、根因分析、修复内容、验证结果、≥2 条关键经验。NEVER 仅回复「已修复」。


典型案例(按需加载)

定位到疑似根因后,加载对应案例了解完整排查过程:

误差现象案例文件何时加载
FP16 挂 FP32 过,全部偏差examples/fp16-no-upcast.md怀疑升精度缺失
首错在 tile 边界,周期 = tileLengthexamples/gm-offset-error.md怀疑 GM 偏移错误
仅尾部少量元素错examples/tail-tile-misalign.md怀疑尾 tile 处理
block_dim=1 过,多核挂examples/multicore-tiling-overlap.md怀疑核间 tiling
多次运行结果不同examples/async-sync-missing.md怀疑同步缺失

不要一次性加载所有案例。 仅在误差特征匹配时加载对应案例。


反模式(NEVER)

  • NEVER 不分析误差分布就直接改代码
  • NEVER 在 kernel 中 printf 循环打全量 tensor — 用 DumpTensor 或 Host 侧对比
  • NEVER 多核同时大量打印 — 加 GetBlockIdx() == 0 仅 0 核打印
  • NEVER 在未同步位置读 LocalTensor — 必须在 DeQue/PipeBarrier 之后
  • NEVER 用 PIPE_ALL 作为最终修复 — 仅用于实验隔离
  • NEVER 修复后不移除调试代码
  • NEVER 仅修复已知失败用例而不跑完整精度测试
  • NEVER 超过 3 轮仍失败时继续尝试 — 应报告用户

Comments

Markdown supported