01 · 同样的算式,可以有不同执行方案
模型代码通常写成很多Tensor操作。逐个执行时,框架不断解释调用、分配中间结果、选择内核。图编译尝试先看一段计算的整体依赖,再重写数据流、选择布局与内核,生成可复用的执行方案。
例如Y=ReLU(X+B)可以执行两个独立内核,也可以采用本地融合内核。图编译是找到并实现这种方案的一种途径,融合是它可能采用的优化之一。
02 · 从模型代码到设备执行的路径
| 环节 | 它做什么 | 产物或边界 |
|---|---|---|
| 捕获可编译区域 | 分析Tensor操作及依赖 | 计算图;不是CUDA任务重放记录。 |
| 记录假设 | 检查shape、dtype、布局或相关状态 | guards:复用方案必须满足的条件。 |
| 图变换与降低表示层次 | 融合、布局选择、安排临时存储等 | 更接近目标设备的操作表示。 |
| 内核选择与代码生成 | 用库内核、生成内核或候选调优 | 可执行方案。 |
| 运行和复用 | 检查条件,执行缓存方案 | 条件不符时可能重编译或回退。 |
PyTorch的典型Inductor路径由Dynamo获取可编译区域;涉及训练时,AOTAutograd参与前后向图处理,再由后端降低和生成执行代码。这只是典型路径,后端可以替换,实际编译边界由框架与版本决定。
依据:PyTorch · torch.compile入门教程 ↗ · PyTorch 2.14 · 编译与Autograd常见问题 ↗
03 · shape为什么会影响方案
同一个矩阵乘法,M=1的Decode与M很大的Prefill可能需要不同的分块和并行粒度。知道维度、步幅和数据类型,编译器更容易消除分支、设置循环边界和选择候选内核。固定这些条件生成方案,就是形状专门化的一部分。
教学请求的矩阵行数:1, 1, 2, 4, 2, 1
精确形状专门化:可能分别缓存M=1、2、4的方案
动态方案:尝试一个方案服务一段可变M范围
分档方案:例如M=3进入容量4的档位,额外行必须正确屏蔽
shape相同,但dtype、stride或相关状态变化,也可能不满足复用条件。这不是torch.compile必然产生三份代码的预测:实际guards、动态维度、后端限制和缓存策略决定结果。专门化换取优化机会,但大量形状可能增加编译时间与缓存容量;动态方案减少某些重编译,却不保证每个形状都最优。
依据:PyTorch 2.14 · torch.compile参数、模式与后端 ↗ · PyTorch 2.14 · guards与重编译 ↗
04 · graph break与重编译是两件事
| 情况 | 发生什么 | 怎么定位 |
|---|---|---|
| graph break:图中断 | 不能继续捕获当前区域,可能分段编译并执行未编译代码 | 看中断位置与原因,先处理热点区域。 |
| guard不满足 | 已有方案的假设不适用于本次输入或状态 | 看重编译原因与实际输入分布。 |
| 内核慢 | 已经成功编译,但方案在设备上效率低 | 看真实kernel、布局和调优结果。 |
依赖Tensor数值的Python分支、某些标量读取或不支持的调用可能造成图中断;支持范围会随配置与版本变化。默认分段执行可保持程序继续运行,但过多碎片可能损失融合机会。fullgraph=True可帮助暴露不能完整入图的部分,不能强行让不支持的代码变成设备内核。
PyTorch可以用TORCH_LOGS中的graph_breaks和recompiles查看对应原因。不要为了压低日志数量删除必要的控制逻辑或不安全地关闭guards。
依据:PyTorch 2.14 · 常见graph break ↗ · PyTorch 2.14 · guards与重编译 ↗
05 · 自动调优在选什么
对一个给定矩阵形状,可能有几种块大小、线程组织或库实现。自动调优运行或评估候选,再选一个适合当前形状和设备的方案。它通常不是搜索新的模型结构,而是搜索同一计算的实现方式。
| 自设候选 | 此形状设备时间 | 选择结果 |
|---|---|---|
| 方案A:较小计算块 | 0.8 ms | 可用 |
| 方案B:较大计算块 | 0.6 ms | 此教学形状中较快 |
| 方案C:另一布局 | 0.9 ms | 此教学形状中较慢 |
换到更小矩阵后,A可能更好;设备型号、dtype、布局变化也会改变结果。上表是自设数据,只演示选择逻辑。调优还可能增加首次启动时间,候选测量必须正确同步并控制噪声。
PyTorch的max-autotune等模式提供调优入口;某些模式还组合CUDA Graphs。一个开关同时启用多项机制时,比较要核对配置,不能把全部收益都归因于代码生成。
06 · 编译什么时候才回本
自设例子:未编译每次1 ms,稳定编译执行每次0.6 ms,一份方案的额外编译成本200 ms。执行K次时,总成本分别是K和200+0.6K ms。K=100时编译总共260 ms,比100 ms慢;K=1000时为800 ms,比1000 ms快。
首次准备成本,要执行多少次才能摊平
自设未编译每次1 ms、编译稳态每次0.6 ms;V份方案都实际编译,累计准备成本V×C。为隔离成本,假设所有形状耗时相同;不预测任何后端性能。严格回本条件:K × (T_eager − T_compiled) > C_total
本例:K × 0.4 > 200,故整数K至少为501
若稳定执行并不更快,就没有这个模型下的回本点。07 · 推理和训练,编译范围有何不同
推理通常关注前向与服务中的稳定热点;训练还要关注反向、激活保存、重计算和优化器。把model编译起来,不代表DataLoader、Python训练循环、集合通信和optimizer.step全部入图。
前后向图可以选择不同的融合或保存策略。与激活重计算、混合精度、状态分片组合时,应再次比较梯度和一次参数更新,再测整步时间与内存。不同更新频率、状态修改或学习率值也可能影响缓存方案的复用。
08 · 从一个稳定函数开始接入
def region(x, b):
return torch.relu(x + b)
optimized = torch.compile(region, dynamic=False)
# 分开记录:首次调用成本、预热后成本、形状变化后的成本
# 先核对输出,再看Profiler、graph_breaks与recompiles。下方CPU教学脚本模拟精确形状guards、缓存命中、布局变化和选定动态维度范围,另外验证分档中有效行的结果。它不运行Dynamo、Inductor或TorchAir,不预测它们的实际编译次数。
NVIDIA环境常见PyTorch+Inductor,也有其他部署编译路径;昇腾的TorchAir提供torch.compile的NPU后端入口,仓库中还有动态分档文档。二者的后端、算子覆盖与形状约束不同,接口表面相似不代表选项或产物相同。
依据:PyTorch · torch.compile入门教程 ↗ · Ascend · TorchAir官方仓库与NPU后端入口 ↗ · TorchAir · 动态分档策略(master) ↗
09 · 先看输入分布,再选择编译策略
- 形状重复、热点稳定:更容易摊销编译成本。
- 形状/分支非常多:记录命中率、缓存量、重编译及尾延迟。
- 动态批处理、分页KV和MoE:数据结构及路由变化需要后端支持,不能默认整段稳定。
- 分档引入额外行:屏蔽、位置、输出截取、KV写入和Loss分母都必须正确。
- 已经计算受限的库内核:图编译未必给主矩阵计算带来额外收益。
10 · 五个问题建立排查顺序
- 编译图有三个节点,为什么不代表只启动一个kernel?
- 图中断与guard失败各发生在哪个环节?
- dynamic=True为什么仍不等于所有形状都不用重编译?
- 只运行100次时,上述编译例子为什么更慢?
- 增加了分档padding,怎样确认无效行不会改变Attention、缓存和训练目标?
流程位置与关联方法
这个方法可能出现在多个位置;计算线包含模型计算与算子实现,执行线包含运行时、内存和通信。
资料与核查范围
原理依据以原始论文和官方文档为主;算例与时间线是教学推导。文档核查日期:2026-10-10。引用 main / latest 文档时,实际接口以安装版本为准。
- PyTorch 2.14 · torch.compile参数、模式与后端 ↗
- PyTorch · torch.compile入门教程 ↗
- PyTorch 2.14 · 编译与Autograd常见问题 ↗
- PyTorch 2.14 · guards与重编译 ↗
- PyTorch 2.14 · 常见graph break ↗
- Ascend · TorchAir官方仓库与NPU后端入口 ↗
- TorchAir · 动态分档策略(master) ↗
代码为接入片段或教学伪代码;本页没有声称完成 NVIDIA / 昇腾硬件实测。