做过 PyTorch 模型 AOT 部署的人,多半见过这种场面:模型好不容易 aot_compile 过了,加载进推理服务的那一刻直接 segfault,报错信息要么是空指针,要么是一句看不懂的指针位置提示。这时候你盯着代码看半天,往往什么都查不出来,因为问题根本不在代码里。
PyTorch 官方在 Smithery 上发了个 skill 专门治这个,叫 aoti-debug,挂在 pytorch/pytorch 主仓库里。它的定位很直接,诊断和修复 AOTInductor 的错误和崩溃,覆盖 segfault、设备不匹配、常量加载失败这几类最磨人的问题。

这个 skill 最反常识的地方在于,它的第一条指令不是怎么修,而是先查什么。文档里说得明白,遇到任何 AOTI 错误,先检查三件事:编译设备、输入设备、输入形状。这个顺序不是随手写的,是被 segfault 磨出来的经验。
说白了,这篇文章想讲清楚一件事:aoti-debug 是怎么把 AOTI 调试这门玄学,压成一张可以照着走的排错路由表的。它到底值不值得装,看完你心里就有数。
工作流拆解
skill 的入口是一段错误模式路由。文档开头就摆了一句,先看错误消息,再决定走哪条路。这一步看着简单,其实是在替你省时间,因为 AOTI 的错误类型就那么几类,先分类再排查,比上来就 grep 源码高效得多。
第一条路由规则,是匹配 Triton 的索引越界。如果你的错误长成这样:
Assertion `index out of bounds: 0 <= tmpN < ksM` failed
直接跳到 triton-index-out-of-bounds.md 这个子指南,不用往下看。这是被单独拎出来的高频错误,说明它在 AOTI 崩溃里占比不小,官方特意给它配了专属手册。
其他错误继续走主流程,而主流程的第一步,永远是那三个设备检查。skill 用一段 Python 把它写得很直白:
# 编译时:记录设备和形状
model = MyModel().eval() # 什么设备?CPU 还是 .cuda()?
inp = torch.randn(2, 10) # 什么设备?什么形状?
compiled_so = torch._inductor.aot_compile(model, (inp,))
# 加载时:设备类型必须与编译时一致
loaded = torch._export.aot_load(compiled_so, "???") # 必须匹配上面的设备
# 推理时:设备和形状必须一致
out = loaded(inp.to("???")) # 必须匹配编译设备,形状必须匹配
编译时的设备、加载时的设备、推理时的输入,三者必须对齐。任何一个对不上,轻则报错,重则 segfault,甚至静默输出错误结果。
这三项里最硬的一条,是设备类型匹配。CUDA 编译就只能 CUDA 加载,CPU 编译就只能 CPU 加载,设备 index 可以从 cuda:0 换成 cuda:1,但 type 不能跨。跨设备加载,比如 GPU 编译完想在 CPU 上跑,直接被判不支持,这条没有商量余地。

查完设备,才进入具体的错误模式分派。skill 把常见症状归了类:设备不匹配导致的 segfault、输入设备不对导致的 RuntimeError、还有最麻烦的 CUDA 非法内存访问。每一类都有对应的错误信息示例和解法,你能对上号就直接用。
整条流程走下来,你会发现它其实是个漏斗:先路由、再查设备、最后才定位到具体错误模式。它把”最有经验的工程师会先看什么”这个隐藏顺序,显式地写进了文档里,这才是它跟普通调试教程拉开差距的地方。
架构解析
拆开看,这个 skill 的结构就两层。一层是路由层,负责把错误分到正确的处理路径;一层是知识层,每个错误模式下面挂着具体的错误信息、原因和解法。前者决定查什么,后者回答怎么修。
知识层里最值钱的部分,是把 AOTI 调试用到的环境变量做了分类。AOTI 的调试几乎全靠环境变量,但散落的 flag 很难记。这个 skill 把它们分成了编译期和运行时两类,这一刀切得很关键。
编译期标志在 codegen 时生效,运行时标志在推理时生效,两者的作用时机完全不同。一张表就能说清楚:
| 环境变量 | 生效时机 | 用途 |
|---|---|---|
| AOTI_RUNTIME_CHECK_INPUTS=1 | 编译期 | 校验输入是否满足编译时的 guard |
| TORCHINDUCTOR_NAN_ASSERTS=1 | 编译期 | 每个 kernel 前后检查 NaN |
| PYTORCH_NO_CUDA_MEMORY_CACHING=1 | 运行时 | 禁用缓存分配器,让 IMA 确定性复现 |
| CUDA_LAUNCH_BLOCKING=1 | 运行时 | 强制同步 kernel 启动 |
| AOT_INDUCTOR_DEBUG_INTERMEDIATE_VALUE_PRINTER=3 | 编译期 | 运行时逐个打印 kernel |
这套分类的价值,在于它回答了调试时最常被问错的问题:这个 flag 到底该在什么时候设。不少人把运行时标志设到编译期,跑了一晚上发现没效果,问题就出在时机上。
另一个容易被忽略但很关键的落点,是 API 的演进。skill 里明确标注了 aot_compile 和 aot_load 已经废弃,现在应该用 aoti_compile_and_package 和 aoti_load_package。

这个迁移的意义比看起来大。新的 package 会把设备元数据一起存进去,加载时自动用正确的设备类型,你只需要关心设备 index。换句话说,设备类型不匹配这个头号错误,被新 API 从源头干掉了,skill 里那一大段设备匹配检查,其实是替旧 API 兜底。
使用场景
最典型的场景,是 CUDA illegal memory access,简称 IMA。这是 AOTI 部署里最难啃的骨头,因为它非确定性,这次跑崩了,下次可能就过了,排查起来无从下手。
IMA 非确定性的根源,skill 讲得很清楚,是 PyTorch 的 Caching Allocator。它会一次性分配比实际需要更大的缓冲区,导致越界访问有时踩空、有时踩实。关掉缓存,错误就能稳定复现,这一步是关键。
针对 IMA,skill 给了三步走。第一步健全性检查,上输入校验和 NaN 断言两个 flag。第二步确定性触发,关缓存加同步 launch。第三步用中间值调试器,把每个 kernel 挨个打印出来,看哪个 kernel 是压死骆驼的最后一根稻草。

这套流程有个巧处,它把非确定性的 bug 一步步逼成确定性的。先让错误能稳定复现,再让每个 kernel 的启动顺序可观测,最后定位到具体内核。顺序反了,就是在黑箱里瞎摸。
第二个场景是跨设备迁移。团队在 GPU 上把模型编译成 .so,想拿到只有 CPU 的机器上跑推理,结果加载就崩。skill 直接告诉你这条路不通,省掉你在错误方向上的试错时间。
这两个场景有个共同点,错误信息都很误导人。segfault 和 IMA 的表象像是内存 bug,但根因往往是设备或形状对不上。这个 skill 最大的价值,就是让你别被表象带偏,先去查那几个真正决定成败的匹配关系。
洞察与反思
看完整个 skill,我最大的感受是,它的价值不在知识量,在检查顺序。AOTI 调试的知识点其实公开资料里都有,但先查什么再查什么这个顺序,是只有长期泡在崩溃堆里的人才能总结出来的。
它本质上是在把专家的第一反应固化成规则。有经验的工程师看到 AOTI segfault,第一反应就是查设备,而不是查代码。这个 skill 把这个直觉写成了硬约束,放进了路由表的开头,让经验不足的人也能走对第一步。
但它的覆盖面和生命周期都值得警惕。它只针对 AOTI 这一条编译路径,而且大量内容依赖旧 API。随着 aoti_compile_and_package 普及,设备匹配那一节的适用面会越来越窄,这一点 skill 自己也没回避。
往大了看,这类官方调试 skill 是个信号。PyTorch 在 Smithery 上已经发了二十多个 skill,从 dispatch 宏迁移到 docstring 再到这个调试指南,覆盖的都是维护者最清楚、外部人最陌生的领域知识。这是把社区最难复制的东西打包分发出去。
这里有个挺讽刺的矛盾。一份好的调试 skill,是在教你怎么解决它自己正在消灭的问题。aoti-debug 教你查设备不匹配,PyTorch 团队同时用新 API 让这个问题消失。但存量项目还会在旧 API 上跑很多年,所以它的价值短期内不会归零,只是会慢慢从”救火指南”退化成”考古手册”。
资源地址
| 资源 | 地址 |
|---|---|
| Smithery 页面 | https://smithery.ai/skills/pytorch/aoti-debug |
| PyTorch 源码仓库 | https://github.com/pytorch/pytorch |
总结
aoti-debug 这个 skill,做的是把 AOTI 调试从经验活变成流程活。一张错误路由表,一个设备检查前置,再加一份分好类的环境变量清单,构成了它的全部。它不堆知识,只排顺序。
值不值得装,看你的处境。如果你还在跑 aot_compile/aot_load 的存量项目,或者正在被 CUDA IMA 折磨,它值得装上,尤其是那套 IMA 三阶段调试能省不少时间。如果你已经全面迁移到 aoti_compile_and_package 且不碰动态 shape,它的增量价值就有限了。
最后留个开放问题。当官方不断用更好的 API 填平这些坑,这类教你排错的 skill 到底还有多长的生命期?我的判断是,它不会消失,而是会转向更深的坑,比如动态 shape 和自定义算子,那才是下一个调试指南该去的地方。
