CANN-Ascend-C调试技巧-昇腾NPU算子出了bug怎么查
Ascend C 算子跑在 AI Core 上,没有 printf、没有 gdb、没有 core dump。算子输出的结果不对,你怎么知道错在哪?这篇整理了三种调试方法,从快到慢排列。
方法一:跟标准实现逐层对比
最快的排障方式。把你的 Ascend C 算子替换成等价的 PyTorch 标准实现,逐步缩小差异范围。
importtorchimporttorch_npu# 你的自定义算子out_custom=torch_npu.npu.my_op(x,y)# 标准实现out_ref=x+y# 或者更复杂的等价实现# 逐元素对比diff=(out_custom-out_ref).abs()print(f"最大误差:{diff.max().item()}")print(f"平均误差:{diff.mean().item()}")print(f"误差 > 0.01 的比例:{(diff>0.01).float().mean().item()}")如果误差全在前半段,问题可能在 Tiling 的前几个 tile。如果误差集中在某些行,可能是指针偏移算错了。
方法二:DUMP 中间结果
Ascend C 算子内部不能 print,但可以把中间结果写到 HBM,推理结束后读出来:
classMyKernel{// 添加一个 debug 输出 buffer__aicore__inlinevoidProcess(){// ... 正常计算 ...// Debug: 把中间结果写到 HBM// 需要在 Init 里额外申请一个 GM_ADDR debug_bufDataCopy(debug_gm_[tile_idx*block_len_],intermediate_local_,block_len_);}};# Python 端读取 debug 输出debug_data=torch.empty(expected_size,device="npu",dtype=torch.float16)# 传入 debug buffer 的地址out=torch_npu.npu.my_op(x,y,debug_buf=debug_data)print(debug_data.cpu())# 查看中间结果这个方法需要在算子接口里加一个额外的输出参数,改了算子签名。调试完后删掉。
方法三:CPU 模拟执行
CANN 提供了 Ascend C 的 CPU 模拟器,可以在 CPU 上逐步执行算子,支持打印和断点:
# 用 CPU 模式编译ascendc--chip=Ascend910B2--mode=cpu add_custom_kernel.cpp-obuild/CPU 模式下可以在 kernel 里加printf:
__aicore__inlinevoidProcess(){// CPU 模式下可以用 printfprintf("tile %d: block_len=%d\n",tile_idx,block_len_);// 计算后打印结果for(inti=0;i<8;i++){printf("z[%d] = %f\n",i,(float)z_local.GetValue(i));}}CPU 模式的执行速度比 NPU 慢 100-1000 倍,但能精确定位问题。
注意:CPU 模式的结果跟 NPU 模式可能有微小差异(float 精度差异),但逻辑错误是一致的。
常见 Bug 模式
Bug 1:Tiling 参数溢出
// ❌ int32 溢出int32_ttotal_elems=M*N;// M=4096, N=4096 → 16M,int32 没问题int32_ttotal_bytes=total_elems*sizeof(half);// 32M,还 OK// 但如果 M=65536, N=65536 → 4G elements → int32 溢出!// ✅ 用 int64_tint64_ttotal_elems=(int64_t)M*N;Bug 2:Global Buffer 偏移算错
// ❌ 按 element 偏移但用了 byte 偏移DataCopy(dst,src_gm_[tile*block_len*sizeof(half)],block_len);// ✅ 按 element 偏移(SetGlobalBuffer 时已经指定了类型)DataCopy(dst,src_gm_[tile*block_len],block_len);Bug 3:Cube-Vector 同步缺失
// ❌ Cube 还没写完,Vector 就开始读了MatMul(qk,q,kt);Softmax(attn,qk);// qk 可能还没写完!// ✅ 加同步MatMul(qk,q,kt);SetFlag<PIPE_M>(PIPE_V);WaitFlag<PIPE_M>(PIPE_V);Softmax(attn,qk);Bug 4:Local Buffer 越界
// ❌ block_len 超过 Local Buffer 容量uint32_tblock_len=512*1024;// 512K elements = 1MB(fp16)// Local Buffer 只有 256KB!// ✅ 确认 block_len 在 Local Buffer 范围内// 256KB / sizeof(half) = 128K elementsuint32_tmax_block=128*1024;uint32_tblock_len=std::min(requested_len,max_block);调试流程总结
1. 跟标准实现对比 → 确认有 bug 2. 缩小输入规模 → 只用 [2, 16] 的小矩阵测试 3. CPU 模拟执行 → 加 printf 定位出错行 4. 修复 → 回到 NPU 模式验证 5. 恢复原始输入规模 → 确认修复完整Ascend C 调试最大的困难是没有直接的 print 和 debug 工具。但 CPU 模拟器 + 中间结果 DUMP 组合起来,大部分 bug 都能定位。关键是先把输入规模缩到最小,减少变量。仓库在这里:
https://atomgit.com/cann/opbase
