
凌晨一点十七分城市大脑的某个视觉识别节点整体“失联”。我登录服务器检查业务进程还活着NPU状态看着也正常但推理请求全部超时。后来追到设备侧日志才确认是 HBM ECC 错误累积到阈值触发了设备异常复位——进程存活不等于设备健康这是我在 CANNCompute Architecture for Neural Networks 工业级部署中踩过最深的坑。这篇内容不是 CANN 教程而是围绕异常处理和心跳监测机制讲清楚三件事CANN 的异常体系到底怎么分层、心跳监测从应用到设备的演进脉络、以及在真实工业部署中如何把这两套机制串成一套可上报、可定界、可止损、可恢复的完整链路。如果你正在用昇腾设备跑 7×24 推理服务或者正准备把训练任务从单卡迁到集群这篇值得花十分钟读完。其中大部分结论来自实际线上事故复盘不是纯代码层面的理论推演。1. 先聊一个工业级部署最怕的事进程活着设备没了1.1 一个真实的事故现场业务“假死”比崩溃更可怕进程崩溃是有声的——退出码、core dump、监控告警都能立刻兜住。真正麻烦的是假死应用进程还挂在系统里健康检查接口还返回着“ACL_ERROR_NONE”但底层的算子队列已经堵死或者设备侧发生了内部复位而 Host 侧没有及时感知。这种情况下调度平台看到的是“节点活、请求超时、卡在推理中”的诡异状态代价往往是几十个业务实例同时堆积请求把周边链路也拖垮。CANN 的 Runtime 层在较新的版本里引入了更细的设备状态上报机制不再只是“能不能打开设备”的二元判断而是包含设备温度、HBM ECC 错误计数、AICore 利用率、任务超时状态等健康维度。但从我接触的大量线上案例看绝大多数团队根本没有把这些状态接入自己的监控体系只用了最基础的aclrtSetDevice返回值判断设备好坏这中间隔着一整层的“状态认知”缺失。1.2 为什么异常处理和心跳监测必须放一起聊很多开发者把“异常处理”理解成 try-catch 和错误码判断把“心跳监测”理解成定时发一个 HTTP 请求确认服务活着。这本身没错但在工业级部署中这两者的关系比表面看起来要纠缠得多。异常处理解决的是“错误是什么、发生在哪一层、该用什么策略响应”它回答的是质性问题。心跳监测解决的是“系统是否还在正常工作、活性下降到了什么程度”它回答的是量性问题。前者依赖 CANN 暴露的异常模型后者依赖设备健康状态的持续回传。二者必须形成闭环异常触发状态变化状态变化通过心跳暴露出来运维系统根据心跳做出决策决策又反过来调用异常处理接口执行复位或迁移。没有心跳的异常处理是无处安放的处理没有异常处理的心跳是只能看不能动的手电筒。2. CANN 对异常的分层态度从错误码到设备状态机2.1 错误码体系的三种“严重级别”别只当它是一串数字CANN 接口返回值aclError并不是一个无脑枚举它背后暗含一套严重级别分层。我习惯把它分成三类级别典型错误处理策略参数级ACL_ERROR_INVALID_PARAM、指针为空、维度非法主动修复立刻重试通常无效应直接止损资源级内存开辟失败、Stream 创建失败、上下文拥塞回退释放重试配合退避策略设备级设备异常、HBM ECC 错误、AICPU 任务超时必须走设备复位或任务迁移链路普通重试只会叠加恶化我在项目里维护了一整套错误分类函数把所有aclError值映射到“可重试 / 可恢复 / 必须迁移”三类策略而不是统一打印日志后退出。原因很简单工业场景里一次误判的策略可能让整个集群对一台故障设备反复重试相当于在伤口上反复撒盐。2.2 图编译期与执行期异常感知的时机完全不同CANN 的作业链路由三段时间组成图编译GE、任务下发、算子执行。工业级部署中最容易被低估的异常发生在图编译期。图编译期的异常大多以aclmdlCreateDesc、aclmdlLoadFromFile等接口的返回码报出特点是可复现、可定位。但真正的坑在于部分模型图编译过程会消耗大量 Host 内存和 NPU 资源一旦失败资源和上下文不会立刻释放干净。我们曾经在模型热更新时频繁触发图编译失败持续跑了一天后设备上残留的上下文把 HBM 吃满最终导致整个容器组不可用。后来加了编译前资源预检和编译失败后的强制上下文回收问题才彻底解决。执行期异常则更隐蔽。算子执行出错虽然能通过ACL_ERROR_RT_*系列错误码感知但触发到上报之间有一段窗口期。在这个窗口期设备可能已经处于亚健康状态继续下发任务会加速恶化。所以执行期异常不能只靠主线程的错误码轮询必须结合下文要说的异步报告机制。2.3 设备级异常分类CANN 不只是给你错误码还给了异常类型CANN Runtime 用aclrtExceptionInfo来承载设备侧上报的异常信息我把它理解成一颗“设备症状雷达”。它能区分出的异常类型比很多人以为的丰富得多常见包括设备内部任务执行异常、设备看门狗超时、HBM 错误、AI Core 挂死等。这里要注意一个设计理念CANN 的异常上报并不是粗暴地在 Host 侧抛一个 error 就完事。它遵循了一种“事件通知 状态查询”的分离模式——异常发生时Runtime 通过回调或事件机制通知业务侧“出事了”但具体“出什么事、严重到什么程度、能不能恢复”需要业务侧再去查询详细状态。这个分离模式非常接近操作系统对硬件中断的处理方式先快速响应再慢速处理。理解了它你就知道为什么aclrtSetDevice能成功不代表设备是你想象中的健康。3. 心跳监测机制的三代演进保活、感知、预测3.1 第一代业务侧自建心跳上报简单直接但活性定义太窄最早我们做多机推理集群时心跳监测的实现非常朴素每个推理进程起一个线程每 5 秒向调度中心上报一次“我活着”。确认活着的方式就是判断主线程是否还在正常循环里、最近一次aclrtSynchronizeStream是否超时。这套方案满足了对“进程级活性”的监控但存在两个致命盲区。第一进程活着不代表推理链路通。推理线程阻塞在某个队列上时心跳线程照样能跑调度中心看到的是全绿。第二业务侧自建心跳只覆盖应用层到 Runtime 之间的通路完全看不到 NPU 设备本身的健康状态无法提前感知 HBM 双 bit 错误这类硬件故障的前兆。第一代心跳的本质问题是它测量的是“业务线程的呼吸”而不是“系统整体的血液循环”。3.2 第二代 Runtime 侧健康状态查询与主动感知CANN 在 Runtime 层提供了健康状态查询能力典型如aclrtGetDeviceStatus和基于事件订阅的报告机制aclrtSubscribeReport/aclrtListenReport。这一代演进的关键在于把“设备健康”纳入了可观测范畴。以aclrtSubscribeReport为例它的工作方式类似信号注册业务侧可以订阅某个设备或上下文上的异常事件。一旦设备侧发生异常Runtime 会异步把事件推给业务侧的回调处理函数。这种机制的好处是实时性极强不需要轮询能在毫秒级感知设备异常比业务侧自己写定时检查快了不止一个量级。第二代心跳监测的常见落地方式是把 Runtime 上报的事件进一步汇聚到 Prometheus 或自研监控平台。设备状态 Query 接口负责低频采样例如每 30 秒一次异常事件订阅负责高频感知耗时毫秒级两种手段结合才能形成一张既不漏报又不频繁误报的设备心跳网。3.3 第三代亚健康识别与异常预测第三代演进正在发生的方向是把心跳监测从“它死了没有”推进到“它正在走向死亡吗”。工业部署中真正的威胁不是瞬时崩溃而是亚健康状态某个 AI Core 的执行时间一天比一天长HBM 的 ECC 单 bit 错误计数在缓慢爬升任务的耗时标准差越来越大。这类状态在传统二值心跳里完全不可见但如果在恶化到临界点之前介入完全可以规避一次夜间大面积故障。CANN 侧逐渐提供更细的运行时指标后工程侧要做的是把这些指标变成趋势信号。比如给 ECC 单 bit 错误设置增长率阈值24 小时内增长率超过 20% 就自动触发设备预迁移给算子执行时间建立基线连续 3 个窗口偏离基线超过 2 倍标准差就告警。这不是什么高深算法但需要你跨出“只看状态码”的惯性开始用时间序列的视角审视设备健康。4. 工业级部署落地的完整链路上报、定界、止损、恢复4.1 上报接住 CANN 异常信号的四种通道要让异常和心跳机制真正工业可用第一步是保证信号不丢。我在生产环境里同时接入了四条通道接口返回码同步捕获每一次 CANN 调用的aclError这是地基。事件订阅回调用aclrtSubscribeReport异步感知设备级异常延迟毫秒级。日志落盘配置 ASCEND 日志级别并接入统一日志平台作为复盘和定位的材料。侧信道心跳业务侧定时任务上报“推理链路是否全程跑通”比如每次推理成功后递增一个计数器。四条通道各有侧重。返回码管同步错误事件订阅管设备异动日志管事后分析侧信道管“业务真实可用性”。任何一条通道单独出现都不能定性四路交叉验证才敢下结论——这个思想贯穿了我们所有故障处理预案。实现事件订阅的框架类似这样伪代码附上供参考# 伪代码演示事件订阅的处理框架 def device_exception_handler(device_id, exception_info): # 第一优先立刻停止向故障设备下发新任务全局开关置位 pause_scheduler(device_id) # 第二优先记录现场信息供后续定界使用 capture_device_snapshot(device_id, exception_info) # 第三优先触发设备级自检/复位决策 issue_recovery_decision(device_id) aclrt_subscribe_report(device_id, device_exception_handler)4.2 定界芯片故障 / 网卡故障 / Host 侧故障别当一回事异常上报之后最忌讳的是把一个现象当成根因去处理。一次推理超时可能是 NPU 芯片挂了可能是 PCIe 链路抖动可能是网卡流控丢包也可能是 Host 侧内存争抢导致调用卡死。定界的核心是建立多维度证据的交叉矩阵。我习惯用一个三栏矩阵来辅助判断现象芯片层面链路层面Host 层面推理持续超时HBM ECC 错误计数是否增长PCIe 带宽是否异常CPU 负载和内存争抢是否严重进程假死但设备正常算子队列是否有堆积网络连接是否断连线程栈是否全部 Block 在锁上设备主动复位日志有无关键错误码固件版本是否兼容是否有 OOM Kill 等系统事件这个矩阵不复杂但如果没有提前定义好线上出问题时团队各自的判断会天差地别。我们花了很长时间才把“看到现象不敢动”的团队状态扭转成“对照矩阵快速收敛”。另外有一个容易踩的坑不要轻易相信驱动日志里的“自动恢复成功”。设备复位的成功只代表重新枚举完成不代表推理现场被完整恢复任务上下文往往已经不可用。所以定界的下一步必须包含业务侧的任务级恢复而不是在设备复位后继续沿用旧的执行流。4.3 止损与恢复设备复位、任务迁移、退避重试的组合拳止损策略按严重程度递增我总结成四个阶梯软止损暂停向故障设备下发新任务保留存量任务等待恢复窗口。任务迁移将推理请求调度到同机其他卡或备用节点要求推理服务无状态或具备状态热迁移能力。设备复位调用设备复位流程或通过配套工具强制恢复。复位后必须重新加载模型和上下文不能复用旧句柄。节点隔离如果故障在短时间内反复出现把整个节点从服务发现中摘除进入人工检测流程。这四个阶梯最重要的是第一条——暂停下发。很多事故扩大化的原因不是设备坏了而是负载均衡器仍然向坏设备疯狂投递请求把单个设备故障放大成整厅节点雪崩。做工业级部署第一课永远是“让故障的影响范围可控”而不是“让故障不出现”。恢复侧的经验重试策略必须指数退避加抖动。我们对任务重试采用 1 秒、2 秒、4 秒、8 秒、封顶 30 秒的退避序列同时加入 ±20% 的随机抖动避免故障恢复后所有任务同时涌回来造成二次击穿。热启动或冷启动就是冻结加载大模型再恢复冷启动相对更加可靠但耗时更长需要结合服务等级协议里的恢复时间目标来衡量。5. 版本配套与工程陷阱那些文档不写但你一定会踩的地方5.1 CANN 与 PyTorch / Python 的版本配套不是“能装上就行”热搜里最常被问的就是cann pytorch python版本配套关系这个提问方向本身说明了一大片人踩过坑。昇腾的软件栈是三段式的CANN 是底层计算架构torch_npu 是 PyTorch 的适配层Python 版本决定了前面两者能否正常运行。三者之间没有“最新配最新”这么简单。从我实际维护过的环境看版本配套的核心原则是“以 CANN 版本为锚点反向锁 PyTorch 和 torch_npu”。每次升级 CANN 前先查昇腾社区发布的版本配套表确认当前代码锁定的 PyTorch 版本在适配范围内再检查 Python 版本是否匹配。第三方库对 Python 版本的依赖很敏感比如某些科学计算库在高版本 Python 下的二进制不兼容会让安装阶段通过、运行阶段才崩溃。线上升级的顺序也很有讲究先在测试环境做全链路回归重点观察图编译是否出现新告警、算子的计算结果是否与旧版本一致最好带阈值比对、以及推理耗时基线是否有漂移。CANN 的算子实现更新后同一模型的表现出现微小数值差异是正常的但差异超过一定阈值就说明算子实现发生了变化需要业务侧重新做模型验收。5.2 我踩过且至今印象深刻的三个坑讲三个真实踩坑记录都是纯文档里不会明说的级别。第一个是日志轮转。CANN 日志在异常高频触发时增长非常快默认配置下可能把/home目录打满。我在测试环境见过日志文件膨胀到几十 GB 后整机 IO 被打满、业务全部阻塞的情况。这个问题的解法分两层一是通过环境变量限制日志级别和落盘路径二是外部统一兜底做日志切割和定期清理两条腿缺一不可。第二个是多进程上下文隔离。在多进程推理架构里每个进程创建的 context 如果不主动释放进程退出时设备侧的内存不会立刻回收干净。短时间反复启停业务进程HBM 碎片化会迅速恶化。这个问题在旧版本 CANN 上尤为明显后来我们在所有推理进程退出前强制调用上下文清理接口并且对进程启停频率做限流才消掉这块隐患。第三个是设备故障后句柄失效的隐蔽性。设备异常并恢复之后旧 context 和 stream 可能看起来还有效但实际已经与设备状态脱节。我第一次遇到时设备复位后继续用旧 stream 下发任务结果推理结果严重异常但没有任何报错。现在我们的恢复流程里强制要求设备异常后建立新的 context废弃全部旧句柄这是省钱省命的一条铁律。5.3 健康检查工具的正确打开姿势昇腾侧提供的npu-smi系列工具是排查设备问题最快的入口但用法上有讲究。我会关注以下几项信息的“变化趋势”而不是某个瞬间的快照HBM 显存使用率持续逼近上限时说明存在内存泄漏或模型加载未释放。温度和功耗长时间偏离基线意味着散热或固件问题。ECC 错误计数单 bit 和双 bit 的增长率比绝对值更有诊断价值。AI Core 利用率推理业务出现周期性打满或长期很低都值得排查。为了观察趋势我给每个节点写了一个后台采集脚本每 15 秒采集一次上述指标并写入时序数据库用 Grafana 展示趋势曲线。这套东西代替不了 CANN 的异常事件订阅但它能回答“设备死之前发生了什么”这往往是事后复盘的黄金信息。6. 往后看异常处理与心跳监测正在“工业化”6.1 从单点故障恢复到故障注入演练我观察到的一个明显变化是越来越多团队不再满足于“设备挂了能告警”而是主动把故障注入作为部署流程的一环。做法并不复杂就是周期性模拟设备失联、网络抖动、进程被 kill、设备复位等故障场景验证监控链路和恢复策略是否真的生效。为什么这件事值得做因为异常处理和心跳监测这套系统本身也是一个软件系统它同样会腐化。我曾经在一个集群里发现倒班交接后监控平台的设备状态 key 命名规则悄悄改了导致部分告警静默丢失三天。如果没有故障注入演练这类“监控的监控失灵”问题根本不会被发现。在昇腾社区的 CANN 挑战赛的内容里我注意到有不少参赛项目开始把“推理服务的可观测性”和“故障自愈”作为优化主题。这其实是一个很好的风向标异常处理和心跳监测正在从“运维的备用话题”变成“架构的核心设计项”。这也是这篇内容想传递的最核心观念——设备健康类基础设施一开始就要从“挂了能发现”设计成“快要挂了能预判、挂了能自愈、恢复后能复盘”的完整闭环。6.2 一个关于心跳频率的工程权衡收尾前提醒一个容易翻车的小细节心跳频率不是越高越好。工业级部署经常有人把心跳周期调到 1 秒甚至更短理由是“我要更快发现故障”。但实际上CANN 的设备状态查询本身是有开销的高频轮询会在设备侧产生额外负载在推理服务高峰期反而可能引入性能抖动。同时过短的心跳周期会让调度中心频繁处理抖动信号把恰好超过阈值的瞬时毛刺误判成设备故障触发无谓的任务迁移。我的实践经验是设置两层心跳粗粒度低频心跳保活10 秒级细粒度高频事件订阅做异常感知毫秒级。前者用于判断“业务进程是否整体健康”后者用于感知“设备是否发生异动”。两个机制各司其职而不是用一个高频心跳把两件事都干了。故障恢复结束之后也别急着把所有任务压回去。等一个完整的心跳周期确认设备稳定再把流量缓慢切回这个“冷静期”通常被我设成 5 到 10 分钟。越大的集群越需要这层谨慎因为它给的是系统层面的缓冲而不是赌设备下一次一定正常。CANN 这套体系里异常处理和心跳监测的演进本质上是在回答一个问题当复杂系统的局部已经偏离正常全局如何尽早知道、准确定位、并且安全收场。理解了这条主线再看具体的接口、工具和策略你会觉得所有设计都有迹可循。我们这行解决问题从来不靠某一个神奇接口靠的是把常规机制组合到位并且给每个环节留好容错空间。