ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

昇腾310上MindSpore Graph模式执行报错ExecTask的排查与修复

昇腾310上MindSpore Graph模式执行报错ExecTask的排查与修复 MindSpore 里把模型切到 Graph 模式在昇腾310上做推理部署编译阶段一路绿灯结果一执行就抛 RuntimeError: ExecTask run task error。这个报错我愿称之为新手必踩坑前几名倒不是说它多难解决而是报错信息本身几乎没有指向性——没有算子名、没有具体设备、没有内存地址一切全靠自己往底层挖。我这次是在 Atlas 200 DK 上做模型迁移验证训练侧跑得好好的模型到了昇腾310上怎么调都过不去卡了整整一下午。最后排查下来问题出在图模式运行时的设备内存规划上和业务模型本身关系不大。所以如果你也在310上跑 MindSpore或者正被这个 ExecTask 卡住这篇排错全文应该能帮你节省不少时间。后面我会按排查顺序把环境复现、链路拆解、定位过程、修复方式挨个讲清楚已经跑通的同学可以直接跳到第五章看鉴别对照表。1. 问题回顾从训练卡迁到昇腾310Graph模式一执行就崩1.1 本次的软硬件环境版本先把环境交代清楚因为昇腾这套东西版本敏感度极高同一个报错在不同版本组合下根因可能完全不同。项目配置AI硬件Atlas 200 DK昇腾310操作系统Ubuntu 20.04 LTSCANN5.1.RC2MindSpore2.0.0Ascend后端Python3.8.10两个建议务必记牢第一如果版本和你的环境不一致别急着套后面的修复参数优先确认 MindSpore 和 CANN 的版本对齐关系。官方发布说明里有一张版本配套表例如 MindSpore 2.0.0 对应 CANN 5.1.RC2 是可以正常工作的组合版本错配时什么都可能报。第二昇腾310这种边缘推理设备设备侧内存通常只有几个GBAtlas 200 DK一般是4GB后面排查时我发现这次问题的核心也正好和这块内存有关。1.2 报错现场编译过了执行炸了我用一个最小推理脚本复现这个脚本你完全可以照搬import numpy as np import mindspore as ms import mindspore.nn as nn from mindspore import context, Tensor context.set_context( modecontext.GRAPH_MODE, device_targetAscend ) class SimpleModel(nn.Cell): def __init__(self): super().__init__() self.fc nn.Dense(128, 64) def construct(self, x): return self.fc(x) net SimpleModel() x Tensor(np.random.randn(4, 128).astype(np.float32)) out net(x) # -- 这里炸了 print(out.sum())Graph 模式编译阶段没有任何问题模型里的算子、图结构、shape 信息都校验通过了但一旦执行net(x)立刻抛错。整个堆栈尾部基本落在 CANN 运行时的任务下发相关调用上最后一行就是这句 RuntimeError: ExecTask run task error。如果你开了 CANN 的详细日志大概率还能看到一条类似 task execute failed 的设备侧错误但默认配置下 Python 层只有这一句话。这就是它最折磨人的地方——报错文本完全没有定位信息像一扇关死的门钥匙却在设备日志里。1.3 为什么要用最小脚本复现很多同学遇到这种底层运行时错误第一反应是去翻自己的模型代码怀疑某个算子或者某段逻辑有问题然后陷入大海捞针。我建议拿到这个报错的第一时间就做一件事写一个不超过 20 行的最小推理脚本只保留一个 Dense 层或者 Conv 层在同样的环境下跑一遍。目的不是说业务模型不重要而是先把业务模型问题和基础环境问题分开。如果最小脚本也报同样的错那基本可以确定不是模型的锅问题出在 MindSpore CANN 设备的底层链路上后面排查就往这个方向走。这个思路听起来很简单但实际操作中能省掉大量无效劳动。我见过不少人在自己的模型里改来改去折腾几个小时最后发现跑个单算子脚本五秒钟就复现了。2. Graph模式在昇腾310上的执行链路ExecTask到底是谁在报错2.1 图模式的编译和运行流程要定位这个报错得先搞清楚 Graph 模式在昇腾310上是怎么跑起来的。MindSpore 有两种运行模式PyNative 模式每一个算子单独执行Python 侧解释一行、设备侧跑一个算子。开发时方便性能不是最优。Graph 模式先把construct里面的所有算子解析成一张完整计算图经过图编译器的优化算子融合、常量折叠、layout 变换、内存规划生成一份可执行的 Task 序列再统一下发到设备执行。打个比方PyNative 像去菜市场买菜临时想一个买一个Graph 模式是先写好购物清单到市场按清单统一采购中途还会做合并把能一起买的放一起。昇腾的图引擎GE拿到编译好的图之后会把它拆成一系列 Task有搬运输入数据的 Task、执行具体算子的 Task、搬运输出数据的 Task。每个 Task 里记录了算子信息、输入输出地址、shape 等参数。2.2 ExecTask在运行期负责什么ExecTask run task error里的 ExecTask从报错链路来看对应的是 Host 侧把 Task 下发到设备侧并等待执行结果的环节。框架把编译生成的 Task 交给 CANN runtimeruntime 再调度到昇腾310上执行。设备侧执行完毕或者出问题会通过状态位、事件或者回调把结果返回给 Host。如果设备侧执行某个 Task 时挂了比如内存越界、shape 信息和 Task 里记录的不一致、算子内部访问非法地址Host 侧拿到的就是非零状态码然后统一抛出一个 RuntimeError文案就是 ExecTask run task error。所以这个报错的本质是**它不是某个特定算子的语义错误而是设备侧任务执行的通用错误包装。任何能让 Task 挂在半路的运行期问题最后都可能长成同一个样子。**这也是为什么解决它不能只盯 Python 堆栈必须往下挖设备和运行时日志。2.3 为什么图模式的坑往往拖到运行期才暴露编译期能检查的是静态信息算子有没有注册、图结构合不合法、shape 能不能推导、算子之间类型匹不匹配。但实际执行时设备内存不够Task 执行到一半 buffer 越界这类问题编译期根本无从感知。昇腾310的内存本来就紧凑图编译阶段会为整张图做一次 buffer 内存规划。这个规划只要超过设备当前可用内存或者规划出来的内存池和运行期实际使用冲突很多场景不会在编译期报出来而是等到 Task 真正下发执行时才发现申请不到地址或者执行时越界最终反馈到 Host 侧就是这个 ExecTask 错。理解了这一步就理解了为什么排查方向必须指向设备和运行日志而不是继续对着模型代码干瞪眼。3. 完整排查记录从单算子隔离到设备日志定位3.1 第一步单算子隔离确认根因方向我的业务模型是 Transformer 风格的序列模型最初怀疑某个自定义 Attention 算子在310上没适配。于是我把计算图缩小到只剩一个nn.Dense用同样的 Graph 模式跑。结果非常一致还是报 ExecTask。这个结果帮我把嫌疑范围框住了问题不在模型的复杂结构不在某个特定算子而在 Graph 模式这条默认链路的某个共用环节。到这里基本可以排除310 不支持我的模型算子这种猜测也排除了模型里某个高级 API 的兼容性问题。3.2 第二步切PyNative模式做对照接下来我把modecontext.GRAPH_MODE改成modecontext.PYNATIVE_MODE同一个最小脚本、同一套环境直接跑通输出值也正常。这个对照实验非常关键设备侧的 Dense 算子本身是好的310 硬件、驱动、CANN 基本链路没问题。问题出在 Graph 模式的专门路径上最可能是图编译生成的 Task 在运行时执行阶段出了岔子。PyNative 和 Graph 在算子执行层面最大的差异就是有没有经历图编译和 Task 下发差异点即嫌疑点。如果你也遇到这种情况强烈建议先做这一步对照能帮你把排查范围砍掉一大半。如果 PyNative 模式也报同样的错那问题就更偏底层环境比如驱动、固件、CANN 版本这是另一个排查方向了。3.3 第三步打开设备侧日志找到真正的错误码下面这步是整个排查的转折点。CANN 环境下应用日志通常在~/ascend/log/目录下里面按debug、plog、slog等子目录组织。plog是 Host 侧进程日志slog是设备侧日志。要定位 ExecTask必须看设备侧真正发生了什么。我先调高日志级别export ASCEND_GLOBAL_LOG_LEVEL1 export ASCEND_SLOG_PRINT_TO_STDOUT1然后重跑最小脚本翻日志时不要直接搜ExecTask而是在日志里搜这些关键词error、task、memory、alloc。我在 plog 日志里看到的信息大致指向了设备侧内存分配失败具体错误码和文案不同 CANN 版本会有差异但方向很明确图编译阶段 GE 做的内存池规划到运行期 Task 真正申请执行时出现了资源问题。结合 310 本身内存不大这个方向一下子就清晰了。排查到这里RuntimeError: ExecTask run task error的真相基本浮出水面不是算子问题不是模型问题是图模式下设备内存规划不合理导致的运行期任务执行失败。4. 修复与验证内存配置调整加shape冻结配套改造4.1 主线修复给MindSpore显式设置device内存上限MindSpore 的 Ascend 后端可以通过 context 限制可用的设备内存我把它显式限制到 2GB 开始import mindspore as ms from mindspore import context context.set_context( modecontext.GRAPH_MODE, device_targetAscend, max_device_memory2GB )为什么这么做310 设备的物理内存本来就有限如果不对 MindSpore 做任何约束GE 在图编译阶段会把整张图所需的 buffer 按最乐观的方式一次性规划出来。一旦规划结果超过运行期实际可分配的内存Graph 模式执行 Task 时就容易炸。显式设置max_device_memory之后GE 会在受限的范围内做更紧凑的内存复用和规划运行期 Task 申请内存反而更稳定。这个值的具体设定我建议从设备物理内存的一半开始试先通过npu-smi info确认设备侧总内存。Atlas 200 DK 一般是 4GB可以先设 2GB如果是 8GB 的推理卡就先设 4GB。小模型跑稳了之后再按实际业务的 batch 大小微调。注意max_device_memory 不是越小越好。设得比模型实际需要还小的话编译期或者执行期照样会失败。这个参数需要结合模型体量做一次简单的二分尝试能跑通的最小值附近通常是最稳的。4.2 配套改造冻结输入shape绕开动态shape分支内存配置解决之后我顺手还做了另一件很值得做的事把推理时的输入 shape 彻底固定。昇腾310 对动态 shape 的支持本来就比较有限。Graph 模式下图编译阶段会推导算子 shape如果模型里存在基于运行期数值计算的 shape 操作编译期推导出的 shape 和实际输入不一致Task 中记录的 buffer 信息就可能错位运行期表现出来同样可能是一句 ExecTask。具体处理十分简单x x.reshape([4, 128]) # 固定 batch 和特征维度数据侧也要做约束使用 Dataset 时加上drop_remainderTrue保证最后几个不完整的 batch 不会进入推理避免最后一个 batch 的 shape 和其他 batch 不一致。如果你在模型里写过TensorShape、运行期动态更新 shape 之类的逻辑建议先在推理场景里去掉或者改成静态 shape 推导。动态 shape 在训练模型时很常见但到了边缘推理场景它就是 ExecTask 的隐藏触发器之一。4.3 验证结果与性能对比修复之后重新跑最小脚本编译正常执行正常输出值和 PyNative 模式下完全对齐。再把原来的业务模型换成固定 shape 跑多次推理都没有复现 ExecTask。连续跑了几十个 batch设备侧日志干净没有内存告警。性能上Graph 模式比 PyNative 模式明显更快毕竟图的算子融合和内存复用是实打实的收益。具体提升幅度不同模型差异很大我就不列数字了但方向是正的。另外提供一个更稳的部署路线如果你的最终目标是把模型部署到 310 上做长期推理可以在 MindSpore 里先把模型导出成 MindIRms.export(net, x, file_namesimple_model, file_formatMINDIR)拿到 MindIR 之后走 ATC 编译成 OM 离线模型再用 ACL 或者 MindSpore Lite 加载推理。这条路会把图编译和运行时调度的大部分问题提前消化在离线阶段比每次都跑 Graph 模式的动态编译要更可控也更接近生产部署的实际形态。5. 这类ExecTask报错的鉴别与预防5.1 不同根因的快速鉴别对照表ExecTask 报错文案千篇一律但配套现象其实能告诉我们很多信息。我把常见的几种情况整理成了对照表方便你按自己的现象快速定位现象可能根因快速定位手段最小单算子脚本也报错版本不匹配、设备侧驱动异常、内存池规划问题先查 MindSpore/CANN 版本对齐表再看 plog 日志减小 batch 就正常了Task 内存申请超限检查 max_device_memory 设置看 slog 日志PyNative 正常、Graph 报错动态 shape、图编译 Task 信息错位固定输入 shape去掉动态 shape 分支只有某个特定算子必报该算子在 310 上运行期行为异常用单算子脚本定位算子名查官方算子支持表多进程同时跑才报多个进程抢占同一设备用npu-smi info确认进程和显存占用一句话总结不要沉迷 Python 堆栈ExecTask 的文本不包含定位信息设备侧日志才是真正的现场。5.2 开发期如何提前暴露这类问题我的习惯是任何新环境到手先跑一个最小冒烟脚本import numpy as np import mindspore as ms from mindspore import context, Tensor import mindspore.nn as nn context.set_context(modecontext.GRAPH_MODE, device_targetAscend) class SmokeNet(nn.Cell): def construct(self, x): return x * 2 1 y SmokeNet()(Tensor(np.ones([4, 4], dtypenp.float32))) print(y)这个脚本能在 20 秒内验证一条完整链路Python 侧构图、图编译、Task 下发、设备执行、结果返回。链路通了再上业务模型。每次改完模型也尽量先用固定 shape 做一次推理早点炸早修别等部署了才暴露。日志级别平时保持ASCEND_GLOBAL_LOG_LEVEL3只打错误排查时再设1否则日志量非常庞大反而干扰定位。5.3 在VSCode里用MindSpore内核调试这台设备再说说开发调试环境的插曲。最近社区里很多人习惯在 VSCode 里把 MindSpore 当 Jupyter 内核来用开发调试体验确实比裸 Python 脚本好一些。具体操作是先装好 Python 扩展和 Jupyter 扩展。在 conda 环境里装好 MindSpore 和 ipykernel 之后执行下面命令注册内核python -m ipykernel install --user --name mindspore新建 ipynb选择 mindspore 内核就能在 Notebook 里直接写推理代码、看中间结果。但要注意Notebook 只是交互层更友好ExecTask 这类底层错误跑在 Notebook 里时日志依然在~/ascend/log下该翻设备日志还是得翻。VSCode 的调试器追不到 CANN runtime 那一层遇到这种报错断点基本帮不上忙老老实实走最小复现 设备日志这条路最有效。这次排错下来我最大的收获其实不是那个max_device_memory参数而是摸清了 ExecTask 的脾气Python 侧的报错文本只是表象真正的证据永远在设备日志和内存规划里。如果你也在昇腾310上遇到过同样的问题不妨按这条路线走一遍最小脚本复现切 PyNative 对照翻 plog/slog再回头调整 context 配置。九成以上的 ExecTask 都能在半天内定位。等搞定了记得把固定 shape 和离线 MindIR 部署也一起纳入你的正式流程能省下后面不少排查时间。
返回列表