你买了一台顶级智能导航,第一次出门就发现它不对劲:明明目的地没变,它却时不时说“路况变化,正在重新规划”;遇到“如果堵车就右转”这种指令,它干脆一声不吭让你自己开;有些偏僻小路,它的地图上根本没有。你会怎么办?骂两句换回老办法,还是学会听懂它每一次“抱怨”背后的原因?torch.compile 就是 PyTorch 2.0 里的这位智能导航——它先把 Python 写成的模型代码“测绘”成一张计算图,再编译成高效的执行路线,让你训练更快、部署更顺。但这位导航偶尔也会闹脾气,报出各式各样的编译错误。本文就用“开车导航”这条主线,带你系统认识这些错误:它们分成哪几类、为什么会发生、如何诊断、怎么修复,以及如何用工程习惯让导航长期不迷路。
掌握这套编译错误的诊断与修复方法,你就能在团队中主导模型加速与上线,把 compile 报错从“劝退理由”变成真正的技术亮点。
目录
可视化演示
动态演示torch.compile编的核心机制,通过开始/执行/监控/完成四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
torch.compile编:Guar Guard失败与动态Shape 动态控制流不兼容的图断点错误处 核心机制可视化 torch.compile编整体架构与关键组件
编译错误总览:听懂导航的抱怨
新司机第一次听到导航说“前方 500 米靠右行驶”都会手心冒汗,其实翻来覆去无非“走哪条路、怎么转弯、路口封没封”几种情况。torch.compile 的报错也一样——它看似五花八门,实际只有有限的几类。在这一节,我们先建立一张“报错地图”,以后任何编译报错,你都能第一时间归类,而不是慌慌张张把导航关掉。
torch.compile 内部像一支分工明确的导航团队:绘图员 Dynamo 负责读 Python 代码、画出计算图;铺路队 Inductor 负责把图变成高效的内核;车本身(GPU/CPU 等后端)负责执行。任何一环出问题,都会以编译错误的形式表现。常见报错大致四类:guard 失败(路线前提失效)、动态控制流不适配(导航无法预判的路况指令)、后端不支持算子(地图之外的路段)、配置与缓存问题(导航设置冲突)。
诊断时记住三步:第一步开日志,用 TORCH_LOGS 让导航把“内心独白”念出来;第二步做最小复现,把报错压到一个小脚本里;第三步定位阶段,判断报错来自绘图、铺路还是执行。配合 torch._dynamo.explain 这类调试工具,大多数问题都能在几分钟内定性。
回到导航的比喻:当你听到“导航在抱怨”时,先别慌,听清楚是哪一个环节在说话。是它加载地图慢了,是它重新画路,还是它问你目的地变了没有?把报错归类,就是老司机和新手的第一道分水岭。
- 总耗时这是模型调用在编译系统接管后的端到端耗时,是后续所有分项的求和结果,也是我们优化的总目标。
- 捕获耗时Dynamo 把 Python 源码翻译成内部计算图的耗时,相当于导航打开地图数据的时间。
- 编译耗时Inductor 把计算图变成优化内核的耗时,相当于铺路队把土路修成高速路的时间。
- 检查耗时每次调用前核对缓存前提(guard)的耗时,相当于导航出发前再次确认目的地没变。
- 执行耗时真正在 GPU 或 CPU 上跑模型的时间,相当于车在优化路线上实际行驶的时间。
Guard失败排查:目的地变了,路线作废
假设你的导航规划了一条“从家到公司”的最优路线,它默默记下一个前提:目的地是公司。如果你中途改去机场,这条精心规划的路线立刻作废,导航必须重新规划。torch.compile 里的 guard(守卫)正是这一条条“前提条件”。
当 Dynamo 捕获模型时,它会记录输入张量的元数据——形状、数据类型、设备等,作为缓存优化路径的 guard。每次调用时,编译器先核对 guard:条件满足,直接走缓存好的“快路径”;条件不满足,guard 失败,缓存作废,必须重新捕获、重新编译。最常见的导火索就是输入 batch 维度动态变化:训练时每个 batch 大小不同,每一次都触发重编译,整个优化收益被反复支付的编译成本吃光。
guard 失败本身不是 bug,而是缓存机制的正常行为,问题在于失败频率。规避策略主要有两种:一是让形状尽量稳定,比如固定 batch、用 padding 对齐;二是开启 dynamic=True,告诉编译器“形状会变”,生成更宽松的 guard,让不同形状共享同一个编译版本。
就像导航总在问“你去哪”一样,guard 是编译器对输入世界的“确认动作”。频繁改目的地的人,导航永远在重新规划;频繁变形状的模型,编译器永远在重新编译。想让导航跑得快,要么固定目的地,要么提前告诉它“我经常换地方”。
- 单次调用成本这是平均每次模型调用所分担的总开销,我们希望它越小越好。
- 检查成本单次 guard 核对的耗时,它是一个很小的常数,但会乘以调用总次数
N_{calls}。 - 重编译成本一次完整重编译的耗时,通常比 guard 检查贵几个数量级,乘以重编译次数
N_{recompile}。 - 权衡关系当
N_{recompile}明显偏大,重编译项吞掉全部收益,必须用 dynamic 或固定形状来压低它。
动态控制流处理:遭遇“看情况转弯”
导航最怕的指令是什么?是“如果前面堵车就右转”——它依赖实时路况,无法提前画进路线规划。torch.compile 也有同样的死穴:依赖张量数值的 if 分支、动态长度的 while 循环,这些动态控制流无法在编译时静态确定走向。
当 Dynamo 捕获图时遇到这样的控制流,它会采取“图中断”(graph break):在断点处停止捕获,断点之前的子图照常编译,断点之后的子图退回 eager 模式逐个算子执行。默认配置下,这只会在日志里留下一条警告,模型照常运行;但如果开启 fullgraph=True,图中断会被升级为异常,直接报错。很多人第一次看到“Graph break“就以为编译失败了,其实它只是导航在说”这段路你自己开,我不管了”。
修复动态控制流的思路是“让编译器只管确定的事”:把依赖数据的判断留给 Python 层,用 torch.where、布尔掩码、padding 等向量化操作替代条件分支。这样控制流从“运行时看数据”变成“静态算矩阵”,编译器就能把整条路画进地图。衡量修复效果,就看整图中被成功编译的部分占比。
动态控制流就像导航地图上那些“看情况转弯”的绕口令路口,硬要导航规划只会让它断线。聪明的做法是不跟它较劲:把确定路径留给编译器,把动态判断留在能处理动态的地方。图中断不是失败,而是一种诚实——知道哪里能优化、哪里必须放手。
- 可编译占比
S表示整张计算图中真正进入优化内核的比例,是衡量图完整度的核心指标。 - 已编译算子数
N_{compiled}是成功被 Inductor 编译进优化内核的算子数量,越大越好。 - 总算子数
N_{total}是模型前向计算中出现的全部算子数量,是分母基准。 - 加速上限
S接近 1 时整体接近全编译加速;S偏低说明图被切碎,大量时间浪费在图中断处。
后端算子不支持:前方路段不在图里
就算导航地图再全,也总有覆盖不到的偏远小路。后端的算子支持同样有边界:Inductor 在 GPU 上熟悉常见算子,但遇到自定义 CUDA 扩展、某些科研专用算子,它可能直接懵掉,报出"Unsupported"错误,或者悄悄把这段计算退回 eager 执行。
这种报错有两种形态:温和的自动回退——后端不认识这个算子,就把该节点单独切出来用 eager 跑,性能略有损失但不报错;激烈的直接报错——某些后端严格模式不接受未知算子,编译直接中断。无论哪种,根因都是后端的能力边界。解决思路有三层:第一,用等价算子替换,比如用基本矩阵运算重写特殊算子;第二,把复杂算子拆解成后端认识的基础算子组合;第三,注册自定义算子,为后端提供专属的 lowering 实现。
衡量一个后端的“地图覆盖率”,可以用算子支持率。覆盖率高,整图就能保持完整;覆盖率低,图被切得七零八落,加速效果大打折扣。所以在评估一个新后端时,先算覆盖率,再谈性能。
导航遇到地图外的小路,不会把车停路中间,而是让你先沿大路绕行。后端支持不了的算子也一样——绕路走等价实现,或者给导航“补充地图”(自定义算子),问题就解决了。记住:后端不支持不是路不存在,只是这张地图还没画上它。
- 覆盖率
Coverage是后端能原生理解并加速的算子比例,以百分比呈现。 - 支持算子数
N_{supported}是后端代码生成器认识的算子个数,它是覆盖率的分子。 - 全部算子数
N_{total}是计算图中出现的全部算子个数,作为分母衡量整体规模。 - 评估意义覆盖率太低时,再强的单算子优化也补不回图破碎带来的损耗,应优先替换算子。
编译配置优化:调好导航的偏好设置
同一台导航,有人偏好“最快路线”,有人勾选“避免收费”,还有人开了“新手模式”。torch.compile 的配置项就是你的导航偏好,调对了事半功倍,调错了处处碰壁。
先看 mode 三档:default 适合大多数模型,编译速度和优化效果均衡;reduce-overhead 借助 CUDA Graphs 把整图捕获成一段“巡航录像”,大幅降低内核启动开销,适合固定形状的大模型推理;max-autotune 对每个算子逐个搜索最优实现,效果最猛但编译时间极长,适合推理服务这种“一次编译、多次复用”的场景。再看 dynamic 参数:默认 False 会针对每个形状特化出最激进的优化,形状一变就重编译;设为 True 则放宽形状限制,牺牲少量特化收益换取稳定。最后是缓存管理:cache_size_limit 控制特化版本数量,磁盘缓存让编译结果跨进程复用,减少冷启动。
调试时还有一对开关很有用:TORCH_LOGS 环境变量能输出编译全过程的日志,torch._dynamo.config.verbose 能打印更详细的捕获信息,相当于导航的“语音播报”开到最细。配置调得好不好,最终都要落到加速比上——优化后的收益必须量化,否则凭感觉调参等于闭眼开车。
导航偏好不是越激进越好:新手开“最快路线”可能吓出一身汗。配置 torch.compile 也一样,max-autotune 不见得比 default 更适合你的场景。每次调整都量化加速比,让数据替你选路线,而不是靠感觉。
- 加速比
Speedup是编译收益的核心度量,所有配置调整最终都要回答“加速比有没有变大”。 - eager 耗时
T_{eager}是不编译、直接按 PyTorch 原生方式执行的耗时,作为性能基准线。 - 编译后耗时
T_{compiled}是 torch.compile 接管后的实际耗时,包含编译与执行。 - 判断标准
Speedup < 1说明配置不当或模型不适合编译,应回退配置而不是硬扛。
修复验证部署:试跑通过再上路
导航重新规划了路线,你不会立刻全速冲过去,而是先开一段确认路况、看仪表是否正常。修复编译错误之后同样要过三关:正确性、性能、部署。过不了任何一关,都不能贸然上线。
第一关是正确性。编译后的数值和 eager 模式会有微小差异,因为编译器可能改变算子融合或求值顺序。用最大相对误差来量化:对一批测试输入,逐元素比较编译前后输出,找出差异最大的那个点。误差在阈值内(比如 1e-3)才算通过。第二关是性能。用 torch.profiler 或简单计时,对比 eager 与 compiled 在真实负载下的耗时、显存占用,确认加速比达标。第三关是部署。把引擎切换做成配置开关,线上先小流量灰度;再用预热请求把首次编译的“冷启动尖刺”挡在服务启动阶段;利用磁盘缓存让多副本共享编译结果。
部署期的另一个隐蔽坑是“首抖”:第一个请求进来才触发编译,延迟飙升好几倍。预热能解决大部分问题,但如果运行时形状频繁变化,guard 失败依然会带来偶发尖刺,所以部署监控里必须盯住重编译指标。
导航说“新路线已规划”,你还是要先试跑一圈再决定信不信。验证通过之前,“编译器说没问题”只是它的自评;只有当你亲眼看到误差很小、速度很快、线上没抖,这条路才真正属于你。
- 最大相对误差
\delta_{max}是编译版本与 eager 版本在所有输出元素上的最大相对偏差,越小越可信。 - 逐元素差值
|a_i-b_i|是第i个输出位置上 eager 结果与编译结果的绝对差。 - 归一化分母
\max(|a_i|,\epsilon)用 eager 结果的绝对值作分母,\epsilon是极小数,防止除以零。 - 判定阈值
\delta_{max}小于 1e-3 级别时认为正确性通过,超过则检查随机性、非确定性算子等问题。
工程实践建议:老司机的驾驶习惯
老司机和新手最大的区别不是车技,而是习惯:上车前绕车一圈、长途前查油量、仪表盘亮灯时不慌。工程化地使用 torch.compile,靠的也是长期习惯,而不是临时救火。
第一是代码规范。模型 forward 里避免复杂全局状态、避免在热路径上改 Python 变量,这些行为会污染 guard,导致缓存频繁失效。第二是渐进式编译。别一上来就 torch.compile(整个模型),先在热点子模块上编译,验证收益和正确性后再逐步扩大范围。这样做的好处是,出问题时排查范围小,回退也容易。第三是测试策略。把“compiled 与 eager 输出一致”写进 CI,作为回归测试;每次改模型结构或升级 PyTorch,都自动跑一遍一致性断言。第四是监控告警。线上指标里加上 guard 失败率、graph break 数量、重编译耗时,一旦异常,告警系统比用户先发现问题。
这些习惯最终会沉淀成一套质量指标,其中最简单直观的是编译一致性测试通过率:测试样本里有多少比例的输出差异在阈值之内。它应该长期稳定在接近 100%,任何一次下滑都值得立刻追查。
老司机的导航从来不会“闹脾气”,因为他的路线、目的地、偏好都是稳定可预期的。工程习惯的本质,就是让你的模型对编译器友好——代码整洁、热点聚焦、指标透明。做到这四点,torch.compile 就不再是时不时跳出来的报错,而是一位可靠的副驾驶。
- 通过率
P_{pass}是编译版本与 eager 版本一致性测试的通过比例,理想值接近 100%。 - 通过样本数
N_{pass}是输出差异在阈值内的测试样本个数,是分子。 - 总样本数
N_{case}是测试集中全部样本的数量,覆盖不同形状、不同分支路径。 - 长期跟踪通过率突然下滑,通常意味着新增代码引入了动态控制流或非确定性行为。
AI 应用场景
大模型推理服务延迟优化
阶段:推理部署与性能优化
解决的问题:线上对话机器人使用 eager 模式推理时延迟偏高,直接套用 torch.compile 后却出现偶发高延迟。工程师打开日志定位发现,服务接收的请求 batch 大小不固定,每次变化都触发 guard 失败和重新编译,相当于导航每接一单就重新规划一次路线。
具体案例:某对话机器人服务通过 TORCH_LOGS=graph_breaks 日志定位到重编译源头后,将请求侧 padding 到固定长度,并开启 dynamic=True 放宽形状限制。上线后 P99 延迟下降约 35%,延迟抖动曲线明显平滑。
训练脚本反复重编译的定位修复
阶段:模型训练加速
解决的问题:CV 训练流水线中,数据增强对图片 resize 导致每个 epoch 输入形状不同,模型每个 epoch 都被重新编译,训练速度反而比 eager 更慢。这就像每次出门前导航都因为目的地微调而重新画图。
具体案例:工程师在数据加载器出口统一补 pad 到固定尺寸,并把数据增强中的随机裁剪改为固定输出尺寸,训练一次编译、多个 epoch 复用,整体吞吐提升约 20%。
跨后端部署时的算子替换
阶段:多硬件平台部署
解决的问题:将 Transformer 模型部署到 CPU 推理服务器时,遇到后端不支持的算子报错。传统做法是放弃编译退回 eager,但更专业的做法是用等价算子重写。
具体案例:工程师将模型中不被 CPU 后端支持的 aten::unique 算子改写为排序加掩码的等价实现,后端覆盖率从 92% 提升到 99%,推理耗时下降 18%,部署顺利通过验收。
Python 演示
import torch class ToyModel(torch.nn.Module): """一个包含动态控制流的小模型,用来演示编译错误排查。""" def __init__(self): super().__init__() self.fc = torch.nn.Linear(8, 4) def forward(self, x): # 依赖输入形状的分支:batch为奇数时走另一条路,容易触发图中断 if x.shape[0] % 2 == 0: return self.fc(x) return torch.relu(self.fc(x)) def diagnose_graph_breaks(): """观察graph break与guard失败重编译。""" torch._dynamo.config.suppress_errors = False model = ToyModel().eval() compiled = torch.compile(model, backend="inductor", fullgraph=False) x_even = torch.randn(4, 8) x_odd = torch.randn(3, 8) for step in range(3): out = compiled(x_even) print("step %d shape=%s" % (step + 1, tuple(out.shape))) # 形状突变,guard失败,触发重新编译 out = compiled(x_odd) print("shape changed -> %s" % (tuple(out.shape),)) try: breaks = torch._dynamo.utils.counters["graph_breaks"] print("graph_break count:", breaks) except KeyError: print("graph_break count: 0 (本次未捕获)") def fix_with_dynamic_shape(): """修复方案:对形状动态的场景放宽guard。""" model = ToyModel().eval() compiled = torch.compile(model, backend="inductor", dynamic=True) for batch in [4, 3, 7]: out = compiled(torch.randn(batch, 8)) print("dynamic batch=%d -> %s" % (batch, tuple(out.shape))) if __name__ == "__main__": print("== 阶段一:诊断默认编译 ==") diagnose_graph_breaks() print("== 阶段二:dynamic修复 ==") fix_with_dynamic_shape()
dynamic=True 缓解重编译的修复思路。if x.shape[0] % 2 == 0:依赖张量形状的分支是动态控制流的典型来源,会干扰编译器的静态捕获。compiled = torch.compile(model, backend="inductor", fullgraph=False)用默认配置编译模型,fullgraph=False时 graph break 只警告不报错。out = compiled(x_even)同一形状连续调用三次,第一次触发编译,之后直接命中缓存快路径。out = compiled(x_odd)batch 从 4 变成 3,guard 检查失败,缓存作废并触发重新编译。breaks = torch._dynamo.utils.counters["graph_breaks"]读取内部计数器,量化图中断发生了多少次。compiled = torch.compile(model, backend="inductor", dynamic=True)放宽形状 guard,让不同 batch 复用编译结果。
常见误区
误区新手在日志里看到 "Graph break" 字样,以为 torch.compile 彻底罢工了,慌忙把编译关掉,甚至怀疑是 PyTorch 的 bug。
正解Graph break 只是编译器在遇到无法静态捕获的动态控制流时,“在这里断线、退回 eager 执行”的信号。断点之前的子图依然被编译加速了,整体性能只是打了折扣而非归零。只有 fullgraph=True 才会把 graph break 升级为异常。真正该关注的是断了多少、断在哪,而不是一见它就逃跑。
为何常见:日志里的 warning 长得和 error 几乎一样,名字里又带着 "break",很容易被当成致命故障。
误区很多人看到 `dynamic=True` 就以为动态形状、动态控制流统统迎刃而解,结果发现 `if` 分支还是报错,于是抱怨这个参数没用。
正解dynamic=True 只放宽 guard 对张量形状的约束,让不同形状复用已编译版本,减少形状变化引起的重编译。它对依赖张量数值的 if/while 这类动态控制流无能为力——那需要改写代码结构,比如向量化或用 torch.where。两个“动态”完全不是一回事。
为何常见:都带着“动态”二字,新手极易把“动态形状”和“动态控制流”混为一谈。
误区遇到编译报错,第一反应是“这破玩意儿不值得折腾”,直接关掉 torch.compile 回到 eager 模式,觉得最省心最安全。
正解全局关掉编译意味着放弃大部分加速收益,也放弃了定位问题的机会。更专业的方式是诊断归类:guard 失败就调形状或 dynamic,控制流问题就改写代码,后端不支持就换算子。真遇到收益为负的场景,也应该局部关闭而非一刀切。
为何常见:编译错误信息对小白确实晦涩,缺乏一条清晰的诊断路径时,逃避比面对更容易。
误区只要看到日志里出现 "recompile" 就很焦虑,认为编译系统出了问题,想方设法让重编译次数变成零。
正解重编译是缓存机制的正常工作方式——第一次遇到新形状,本来就该编译。问题不在于“有没有重编译”,而在于“重编译频率有多高”。如果每个 batch 形状都变,重编译成本被反复支付才需要处理。固定形状或 dynamic=True 能压住频率,但完全消除既不可能也没必要。
为何常见:把设计机制误当故障,本质是没理解“特化缓存”的工作原理。
面试题
Q1. torch.compile 中 guard 的核心作用是什么?
- A.防止模型读取越界的内存数据
- B.扩大 GPU 显存并提高带宽
- C.校验缓存优化路径的成立前提← 正确答案
- D.强制 Python 升级到 3.10 版本
答案:C。guard 像导航记录的“目的地前提”,每次调用前核对输入形状、dtype 等元数据是否与缓存时一致,一致才敢复用优化图。越界检查、显存扩容、解释器版本都不是 guard 的分内之事。
Q2. 关于图中断(graph break)以下哪项正确?
- A.图中断说明整个编译彻底失败
- B.图中断处子图退回 eager 执行← 正确答案
- C.图中断后模型将无法正常运行
- D.图中断只发生在 GPU 设备上
答案:B。graph break 是编译器遇到动态控制流无法静态捕获时“断线”的表现,断点前的子图仍被编译加速,断点后的子图退回 eager。默认配置下它只是警告而非致命错误,fullgraph=True 才会抛异常,且与设备无关。
Q3. guard 频繁失败会带来什么直接影响?
- A.模型权重数值被自动清零
- B.数据精度从 fp32 降为 fp16
- C.计算图会被随机删除失效
- D.触发重新编译且缓存命中率下降← 正确答案
答案:D。guard 是特化版本的生效前提,一旦失败缓存即作废,必须重新捕获并编译。频繁失败意味着重编译成本反复支付,性能从“快路径”跌回“编译慢路径”。权重清零、精度下降、图被删除都与 guard 机制无关。
Q4. 下列哪种情况最容易触发 guard 失败?
- A.输入张量的 batch 维度动态变化← 正确答案
- B.模型权重参数数值发生更新
- C.数据集中样本的标签分布变化
- D.主机内存占用率持续升高
答案:A。guard 记录的是图的结构性前提,包括输入 shape、dtype、device 等元数据,参数数值更新、标签分布、内存占用都不在 guard 覆盖范围内。batch 维度每步变化是生产环境中最典型的 guard 失败来源。
Q5. dynamic=True 的主要作用是?
- A.动态选择最优的编译后端
- B.让网络结构随训练动态增删
- C.放宽形状限制以减少重编译← 正确答案
- D.自动调节学习率与优化器状态
答案:C。dynamic=True 让编译器生成更宽松的形状 guard,形状变化时尽量复用已编译版本,从而减少重编译。它不负责换后端、不改网络结构、也不碰学习率,只解决“形状动态”这一件事。
Q6. 关于 mode="reduce-overhead" 的正确说法是?
- A.借助 CUDA Graphs 降低内核启动开销← 正确答案
- B.为每个算子单独生成独立的核函数
- C.对任何模型都零成本直接生效
- D.会将模型权重强制转为低精度
答案:A。reduce-overhead 把整图捕获为 CUDA Graph,减少 GPU 内核启动的 CPU 端开销,适合固定 shape 的大模型推理。它需要满足可捕获条件,不是零成本,也不改变权重精度;“每个算子独立核函数”反而是它的反面。
Q7. torch._dynamo 在编译流程中的角色是?
- A.替换 CUDA 驱动接管 GPU 设备
- B.负责模型参数的持久化存储
- C.捕获 Python 级计算图并做图级变换← 正确答案
- D.对 C++ 算子源码进行静态编译
答案:C。Dynamo 通过字节码分析在 Python 层捕获计算图,生成 guard 并做图级变换,是 torch.compile 的“绘图员”。把图变成高效内核的是 Inductor;驱动 GPU、存储参数、编译 C++ 源码都不是 Dynamo 的职责。
Q8. 哪些输入信息变化会触发 guard 检查并可能导致重编译?
- A.输入张量的形状发生变化← 正确答案
- B.输入张量的数据类型变化← 正确答案
- C.输入张量所在的设备变化← 正确答案
- D.模型参数的数值被更新
答案:ABC。guard 覆盖张量的 shape、dtype、device 等元数据,这三者变化都可能让缓存特化版本失效并触发重编译。参数数值更新不在 guard 追踪范围内,因为 guard 只管“图长什么样”,不管“权重值是多少”。
Q9. 排查 torch.compile 性能回退时,有效的手段有哪些?
- A.配置 TORCH_LOGS=graph_breaks 检查断点← 正确答案
- B.用 torch.profiler 对比各子图耗时← 正确答案
- C.直接把训练 batch 大小调大一倍
- D.强制关闭 GPU 改用 CPU 跑全流程
答案:AB。日志定位断点位置、profiler 量化编译前后耗时是标准诊断路径。调大 batch 只是改变形状,反而可能加剧重编译;放弃 GPU 改用 CPU 与排查性能回退毫无关系,只会让问题更难复现。
Q10. 关于 torch.compile 的缓存机制,正确的说法有?
- A.缓存会对不同输入特化生成多个版本← 正确答案
- B.guard 失败会不断新增特化版本← 正确答案
- C.cache_size_limit 可限制缓存数量← 正确答案
- D.缓存内容会包含训练数据全文
答案:ABC。编译器按 guard 条件为不同输入特化出多个缓存版本,新形状触发新特化,缓存过多时用 cache_size_limit 限制并淘汰。缓存保存的是编译产物与 guard 条件,与训练数据内容无关,选项 D 是把缓存误解成了数据集副本。