CANN graph-autofusion SuperKernel 静态编译过程安全:状态判定、进程所有权与报告规范
【免费下载链接】graph-autofusionGraph-autofusion 是一个面向昇腾(Ascend)芯片的轻量级、解耦式组件集合,旨在通过自动融合技术加速模型执行。 目前已开源 SuperKernel 组件和 Autofuse 组件,未来将持续开放更多自动融合相关模块。项目地址: https://gitcode.com/cann/graph-autofusion
导读:本文基于 CANN graph-autofusion 开源仓库中superkernel-auto-tune技能套件的核心契约文档,系统讲解 SuperKernel 静态编译(static-kernel compilation)过程中如何正确判定编译状态、管理编译子进程的所有权与终止边界、约束编译并发与 Profiler 调度,并规范最终报告用语。读者读完可以掌握一套"可辩护、可复现、不误杀、不误报"的静态编译过程安全方法论,并理解其在仓库 AOT 示例脚本中的落地形态。
一、背景:什么是静态编译过程安全
SuperKernel(超核)是 CANN 面向昇腾芯片的自动融合技术形态之一,它将多个算子融合为一个超级内核(super kernel)以降低调度开销。融合后的内核需要经过静态编译生成运行包(.run包),这一编译动作通常由 CANN 静态编译实现拉起opc/op_compiler子进程完成。在 graph-autofusion 仓库中,SuperKernel 的编译入口位于 super_kernel/src/jit/superkernel/super_kernel.py,其compile()函数调用compile_super_kernel完成算子列表到编译产物的转换;编译日志路径由compile_log_path控制。
由于静态编译是异步、多进程、长耗时的过程,观测者在等待期间容易把"文件状态"或"进程年龄"误判为"编译失败"或"编译挂起",进而采取错误动作(如误杀编译器进程、误删产物、误报算子不支持)。因此,仓库中的 static-compile-process-safety.md 定义了适用于"启动、观测、诊断、超时、清理静态编译"全生命周期的一份过程安全契约(process-safety gate)。它是一道过程安全闸门,而非性能启发式规则。
二、为什么*_compile_error.log是暂时性的(Provisional)
CANN 部分静态编译实现会在拉起对应opc子进程之前就创建*_compile_error.log文件;当编译成功返回后,实现会将该文件改写或重命名为*_compile_succ.log。因此,一个以 error 命名的文件,完全可以与一个健康存活的编译器进程共存,它不构成失败证据。
按照契约,以下观测结果单独来看都是**非终态(non-terminal)**的:
| 观测现象 | 结论 |
|---|---|
存在一个或多个*_compile_error.log文件 | 不证明编译失败 |
opc/op_compiler进程运行超过预期时长 | 不证明编译挂起 |
| 输出暂时安静、CPU 利用率变化、文件数量停止变化 | 不证明编译停滞 |
| 子编译器进程比某个任意清理阈值更"老" | 不构成清理依据 |
契约给出的核心判断原则是:进程年龄不是挂起证据,文件名状态不是进程终态。判定终态的唯一依据是编译器进程是否自然返回以及最终汇总结果。
仓库的 AOT 示例脚本也体现了这一原则。在 super_kernel/examples/aot/scripts/common.sh 中,sk_check_static_kernel_outputs()会先查找*_compile_error.log,但只有在同时不存在*.run运行包时才判定编译失败并报错;一旦检测到*.run包存在,即使 error 命名的日志文件仍在,也被视为编译成功的证据。这正是"错误命名的文件是暂时性观测、运行包是终态产物"思想的可执行化实现。
三、允许的状态分类:五种编译状态的判定次序
契约要求对一次编译尝试按下述固定次序进行分类,不可跳跃、不可主观合并:
- 进行中(In progress):实验所属的编译器进程或其推理/rank 父进程仍然存活,且冻结的顶层 runner 尚未超时。此时应继续等待,只收集只读观测,不得 kill 任何进程。
- 成功(Succeeded):编译器栈自然返回,最终汇总(final aggregate summary)报告所有输入均成功,且顶层退出码为 0。
- 失败(Failed):编译器栈自然返回,但存在非零的编译器/顶层退出码,或最终汇总报告了失败。只有到这一步,保留的错误日志才能被引用为最终编译证据。
- 超时 / 外部中断(Timed out / externally interrupted):冻结的顶层超时触发,或外部信号终止了进程树。此时应保留 runner 日志与精确的所有权记录,将编译证据标记为"无效/中断",而非"已验证的算子编译失败"。在判定算子编译支持性之前,必须先进行一次干净的(clean)重试。
- 未知(Unknown):所有权、终态或最终汇总缺失或相互矛盾。此时应封锁结论并收集缺失证据,不得推断失败。
最终汇总是运行时相关的(runtime-specific),因此必须归档以下证据:精确的[summary]行或汇总产物、顶层返回码、超时标志、进程所有权清单(process ownership manifest)、最终成功/失败产物的计数。
四、终止与所有权规则(Termination And Ownership)
契约对"谁能杀进程、何时能杀进程"给出了严格边界,这是整个安全契约中最容易违反的部分:
- 禁止基于 error 日志杀进程:绝不允许因为存在
*_compile_error.log就执行kill、pkill、killall、按进程名过滤或按 PID 年龄清理。此禁令适用于父 Agent、子 Agent、看门狗(watchdog)、诊断脚本以及手工清理步骤。 - 禁止终止未拥有的或预先存在的编译器进程:若发现其他工作负载在场,应将其视为"租约/资源阻塞者",选择等待或切换到隔离资源,而不是直接终止。
- 正常失败处理必须等待自然返回:只有冻结的实验 runner 到达其声明的顶层超时时,才允许终止它自己创建的那一个精确进程组;不得额外添加一个独立杀编译子进程的父侧看门狗。
- 重试或清理前必须保留中断现场:后续的成功尝试应追加记录,不得改写中断现场。
五、编译并发:显式冻结NPU_STATIC_KERNEL_COMPILE_JOBS
当安装的运行时按宿主高 CPU 核数自行推导编译并发度时,契约要求主动探测并冻结一个显式有界的NPU_STATIC_KERNEL_COMPILE_JOBS值:
- 将该值记录在环境/控制证据中;
- 在相互比较的多轮运行之间保持该值完全一致;
- 该值应从运行时与宿主证据中选取,不得为制造性能或可靠性结果而静默修改。
这样做的目的是保证对比实验的可归因性(attributability):并发度不同会导致编译时序、资源占用、失败形态不同,若不在各轮间冻结,任何差异都无法可靠归因到被测选项。
六、Profiler 调度与 Python 启动安全
契约将"修改 Profiler 调度"视为一次launcher/runtime 变更,即便只是改变计划记录的步数。active、skip_first、warmup、repeat这些参数必须:
- 通过框架常规的 Profiler 配置/构造路径传递;
- 在已批准的 CANN 环境配置之后、与未修改 launcher 相同的初始化点传入。
严禁使用任何 Python 解释器启动钩子(interpreter-startup hook)实现调度覆盖,包括:
sitecustomize.py或usercustomize.py;.pth导入钩子或PYTHONSTARTUP;- 向
PYTHONPATH前置一个 monkey-patch 目录; - 其他在 launcher 正常的 multiprocessing/静态编译器初始化之前就导入应用模块或
torch_npu的启动垫片(startup shim)。
此禁令即使在 Profiling 被禁用时依然成立:仅仅加载钩子就可能改变导入顺序与进程状态。一个更大的active窗口之类的调度值本身不是编译器选项,不应要求全局 monkey-patch。若已安装框架没有受支持的方式表达所需调度,应保留默认调度并记录该限制,而不是注入启动覆盖。
启动静态编译之前,必须归档:生效的 Profiler 调度、相关环境增量、有序的PYTHONPATH。检测到未批准的启动钩子时应阻止运行。在对 Profiler 构造或导入顺序做任何有意变更后,须使用相同的源码、工作负载、编译并发与运行时配置,先跑一次隔离的全新根目录编译健康回归(isolated fresh-root compile-health regression)。只有编译器自然返回、所有聚合编译阶段报告全部输入成功、顶层退出码为 0、推理达到正常完成点时,才能接受后续的 Profiling。
诊断回归时,应独立地变更以下变量:启动覆盖(startup override)、框架 Profiler 启用状态、可选 SK trace、缓存种子(cache seed)、编译并发。一个"仅在存在启动钩子时才失败"的结果,只能将该启动路径定位为触发因素,不能证明调度值、Profiler 特性、SK trace 或被点名算子存在缺陷。
七、必需的报告语言(Required Report Language)
报告必须严格区分以下四种情形,禁止混用措辞:
| 情形 | 正确表述 |
|---|---|
| 编译进行中观测到错误命名的文件 | 暂时性 error 命名文件(provisional error-named files) |
| 编译器自然返回后的最终失败 | 自然返回的最终编译失败(naturally returned final compiler failures) |
| 顶层超时或外部中断 | 顶层超时/外部中断(top-level timeout or external interruption) |
| 干净重试后的结果 | 有效干净重试结果(valid clean-retry result) |
绝对禁止仅因为编译器自然终态之前存在错误命名的文件,就写下"compile failed"。
八、契约在仓库中的落地形态
上述契约并非纸面规范,仓库中已有对应的工程化落地:
- 产物目录约定:AOT 示例的清理函数
sk_cleanup_local()统一清理static_kernel_compile_outputs、aclnn_static_shape_kernel_outputs、.static_kernel_records.json、sk_meta、kernel_meta等目录(见 super_kernel/examples/aot/scripts/common.sh)。 - 终态校验:
sk_check_static_kernel_outputs()以*.run运行包作为成功终态、以"error 日志存在但无 run 包"作为失败判据,并以required参数区分"必须生成运行包"与"可选生成"两种场景;example01_dual_stream/run.sh与example02_sk_options/run.sh使用required,example03_kernel_pybind/run.sh使用可选模式。 - 卸载路径安全:
sk_uninstall_static_kernel_from_log()从运行日志中提取uninstall.sh路径后,会基于ASCEND_HOME_PATH下的opp/static_kernel/ai_core安装根目录校验路径归属,拒绝任何带..、符号链接或重定向的无关卸载脚本,防止清理动作越权触及非实验进程的产物(见 common.sh)。 - 编译入口与证据链路:编译入口
compile()在每次进入前重置global_var_storage,并记录compile_log_path与编译选项(见 super_kernel.py);融合证据类日志(如sk_fused_nodes.log、sk_scope_split.log)与 Profiling 开关ASCEND_PROF_SK_ON的语义可参考 aot-internals.md。
九、总结
静态编译过程安全的本质,是把"观测到的中间状态"与"可判定的终态"严格分离:错误命名的日志、过长的进程年龄、安静的输出都不是失败或挂起证据;只有"自然返回 + 最终汇总 + 退出码"三者一致才能定论。在此基础上,通过所有权边界约束终止行为、显式冻结编译并发、禁止 Python 启动钩子干预 Profiler 调度、并强制区分报告措辞,SuperKernel 自动调优实验才能在可辩护的证据链上推进——这正是该契约作为"过程安全闸门"而非"性能启发式"的意义所在。
【免费下载链接】graph-autofusionGraph-autofusion 是一个面向昇腾(Ascend)芯片的轻量级、解耦式组件集合,旨在通过自动融合技术加速模型执行。 目前已开源 SuperKernel 组件和 Autofuse 组件,未来将持续开放更多自动融合相关模块。项目地址: https://gitcode.com/cann/graph-autofusion
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考