训练营简介 2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。
报名链接:https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro
前言
在应用层开发中,程序崩了会有 Stack Trace。但在 NPU 算子开发中,如果你的 Kernel 写错了地址(比如访问了-1或者越界读写),可能会导致AI Core 挂死,甚至把整个 SoC 芯片搞挂。
这时候,普通的日志工具(如 PLOG)往往来不及写盘。我们需要更底层的手段。 昇腾提供了一套类似飞机“黑匣子”的机制,能在芯片崩溃前的毫秒级瞬间,将寄存器状态和内存快照抢救下来。
本期文章将教你如何提取并分析这些“遗言”,定位那些导致 Device Hang 的致命 Bug。
一、 核心图解:坠机现场的黑匣子
当 NPU 发生致命错误时,它会触发硬件中断(Exception)。
二、 第一道防线:Exception Dump
如果 NPU 还能响应,但算子执行失败(Return Error),我们可以通过配置acl.json来导出异常信息。这是定位逻辑错误(如除零、地址未对齐)的首选。
2.1 开启异常 Dump
在运行应用前,创建acl.json配置文件:
{ "dump": { "dump_path": "./dump_output", "dump_mode": "all", "dump_exception": "on" // 【关键】开启异常 Dump } }在代码初始化时加载:
aclInit("acl.json");2.2 分析 Dump 文件与定位行号
运行出错后,dump_output目录下会生成.o或.bin文件以及 json 报告。 你需要关注:
Error Code: 例如
Aicore Memory Access Invalid(非法访存)。PC (Program Counter): 报错时指令执行到了哪一行。
实战技巧:如何通过 PC 找到代码行?PC 是汇编指令的地址。你需要使用objdump反汇编你的算子二进制文件(.o),建立汇编与 C++ 代码的映射。
# 反汇编 (Ascend C 算子通常是 aarch64 架构指令集或 cce 自定义指令集) # 注意:具体反汇编工具取决于使用的编译器版本 (ccec-objdump 或 aarch64-linux-gnu-objdump) /usr/local/Ascend/ascend-toolkit/latest/bin/ccec-objdump -d kernel_name.o > kernel.asm在kernel.asm中搜索 Dump 报告中的 PC 地址(注意:Dump 中的 PC 可能是绝对地址,需要减去 Base Address 得到偏移量),你就能看到是哪条汇编指令崩了。再结合代码逻辑,推断出是哪行 C++ 代码。
三、 终极手段:黑匣子 (Black Box)
如果 NPU 彻底死机(Device Hang),SSH 都连不上,Dump 文件根本没机会写盘。 这时候需要带内管理工具 (ada)。昇腾芯片通常有专门的维护子系统,即使 AI Core 挂了,维护系统还在工作,并把最后的状态保存在非易失存储中。
3.1 提取黑匣子日志
重启服务器后(硬重启),使用ada工具提取日志:
# 导出黑匣子信息 (通常需要 root 权限) # -d 0 表示设备 ID 0 /usr/local/Ascend/driver/tools/ada-toolkit --cmd log --device 0 --save ./blackbox.log3.2 解读天书 (Register Analysis)
打开日志,你会看到一堆十六进制的寄存器值。不要慌,作为算子开发者,重点关注以下几个状态位:
MTE_ERR_STS (MTE Error Status):
这是搬运单元的报警器。如果它的值不是 0,说明数据搬运出问题了。
常见原因:
Read Out of Bound:Tiling 算错了,试图读取 Global Memory 范围之外的数据(读了别人的内存)。Write Violation:试图写入只读区域或系统保留区域。
Vector_ERR_STS:
向量单元错误。
常见原因:
Div Zero:除数为 0。Invalid Op:浮点数异常(如对负数开根号)。
Core ID:
查看是哪一个核(AICore 0 ~ N)挂了。
分析思路:
如果每次挂的核不一样,可能是多核竞争(Race Condition),比如原子操作没加锁。
如果总是 Core 0 挂,可能是Tiling 边缘处理(尾块)没写好,导致第一个或最后一个块越界。
四、 常见“死法”大赏与排查思路
根据实战经验,90% 的 Device Hang 源于以下三种原因:
4.1 踩内存 (Memory Stomp)
症状:算子 A 跑完了,跑算子 B 的时候挂了,或者跑完后 Host 收到乱码。原因:算子 A 的CopyOut写越界了,默默地改写了不该改的内存(比如破坏了下一个算子的输入,或者破坏了页表)。排查:
检查 Tiling 逻辑,特别是
DataCopy的len是否超出了AllocTensor的大小。检查
AtomicAdd是否操作了非法地址。
4.2 各种死锁 (Deadlock)
症状:程序卡住不动,风扇狂转,且无法通过Ctrl+C停止。原因:
Event 丢失:
SetFlag和WaitFlag不配对。队列堵塞:
TQue满了,生产者还在EnQue;或者空了,消费者还在DeQue。这是最经典的死锁。排查:画出代码的控制流图(CFG),确保每一条分支路径(if-else)上的EnQue/DeQue次数严格一致。
4.3 栈溢出 (Stack Overflow)
症状:PC 指针跳转到了奇怪的地方,报非法指令错误。原因:在 Kernel 函数里声明了太大的局部数组(如float temp[1024])。深度解析:AI Core 的栈空间设计非常小(通常只有几 KB),仅用于存放寄存器溢出和少量标量。大块数据必须从Global Memory或Unified Buffer (TPipe)申请。对策:看到float array[N]这种写法要警惕,统统改成LocalTensor。
五、 总结
调试是算子开发中最痛苦,但也是最能锻炼内功的环节。它迫使你理解每一行 C++ 代码对应的硬件行为。
分级治理:
逻辑错误 -> 用 CPU 仿真(第 17 期)解决。
运行时报错 -> 用 Exception Dump 定位。
彻底死机 -> 用 Black Box +
ada工具分析。
底层思维:不要只看高级 API,要学会看反汇编,理解数据是如何在 MTE 和 Vector 之间流动的。
防御性编程:不要相信你的 Tiling 计算,尽可能在 Host 侧做完边界检查。
当你能从一堆十六进制乱码中一眼看出“哦,这里 MTE 越界了”,你就真正成为了 Ascend C 的专家