假设你是一家快递公司的资深转运员。每天,海量包裹从全国各地涌入分拣中心,每一件都要开箱验视、称重、录入系统,再被打包发往下一站。传统做法是每来一个包裹就当场处理一个,包裹一来就拆,拆完马上装车,现场热闹但杂乱,机器总在空转,处理能力上不去。直到有一天,你被派到新岗位——不再逐件处理,而是在分拣线前端做“报关预检”:拦截所有包裹,快速登记面单信息,识别出哪些路线稳定、哪些包裹适合批量运输,然后一次性交给后端的高效流水线统一处理。TorchDynamo 就扮演着这个“报关预检员”的角色——它在 Python 的帧评估层拦截张量操作,把动态执行的 Python 代码像拆包裹一样一件件登记,最终组合成一张可优化的计算图,交给后端编译器去执行。用户写的 PyTorch 代码完全不用改,就自动获得了编译加速。

岗位关联

对 AI 从业者而言,理解 TorchDynamo 是掌握 PyTorch 2.0 编译栈的钥匙——它决定了你的模型在 GPU 上能跑多快、能不能自动享受图优化的红利,是面试与系统调优都绕不开的核心知识。

目录
  1. 可视化演示
  2. 从解释到编译
  3. 帧评估拦截
  4. 捕获张量操作
  5. 构建计算图
  6. 图输入与后端
  7. 用户代码透明
  8. 核心价值总结
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示TorchDynamo在Python帧评估层面拦截张量操作将eager动态代码转换为静态计算图、静态与动态shape捕获的策略对比、以及torch.compile各编译模式在优化度与编译时间上的权衡。

本节配有交互动效(进入视口后自动加载,约 3KB)。

帧评估拦截与图捕获 PEP523帧评估钩子拦截张量操作 eager动态代码转换为静态计算图 Frame→Graph 无代码修改即可获得编译加速

从解释到编译

回到快递分拣中心的比喻。传统的 Python 执行方式,就像一位极其“认真”的快递员,每送一件包裹都要重新看一眼面单、重新规划一次路线——哪怕你今天已经送了 100 件同样的包裹到同一个小区,他还是会第 101 次掏出地图查一遍。PyTorch 的 eager 模式就是这样:每一行张量运算,Python 解释器都要逐字节地解析、执行、分配内存、释放内存。这种“解释执行”模式对开发者很友好——运行到哪里你就调试到哪里,报错信息直接指向代码行。但它的代价是巨大的:每一次张量运算都伴随着 Python 与 C++ 层的反复切换,开销远远大于运算本身。

这时候“编译执行”的思路就出现了。编译执行就像快递员把 100 件送到同一个小区的包裹合并成一个大麻袋,一次性规划好最优路线,到了小区门口统一分发。同样的道理,如果我们能把模型里一串张量操作先“看”一遍,生成一张完整的计算图,再统一交给底层的编译优化器(比如算子融合、内存复用),执行效率可以提升数倍。但问题来了——PyTorch 是动态图框架,计算图在运行时才逐步构建,模型里还有大量的 Python 控制流(if、for、循环依赖),你没法像 TensorFlow 1.x 那样静态地把整张图画好。

\text{TorchDynamo 的答案:在运行时动态捕获,每次执行时"顺藤摸瓜",把实际执行到的算子连成图。}

1. \text{TorchDynamo}:PyTorch 2.0 的即时编译(JIT)入口,负责在运行时拦截并捕获计算图。

TorchDynamo∈PyTorch 2.0 Compile Stack\text{TorchDynamo} \in \text{PyTorch 2.0 Compile Stack}

2. 动态捕获:跟踪程序真实执行路径,仅捕获参与张量运算的操作。

CapturedGraph=Trace(executed_ops)\text{CapturedGraph} = \text{Trace}( \text{executed\_ops} )

3. 编译后端:捕获后的图交给 Inductor 等后端进一步优化。

CompileBackend(CapturedGraph)→OptimizedKernel\text{CompileBackend}( \text{CapturedGraph} ) \rightarrow \text{OptimizedKernel}

于是 TorchDynamo 的定位便清晰了:它不自己做底层内核优化,而是做“图捕获”这件核心脏活。它像那位经验丰富的报关预检员——不亲自开车送货,但能把每一批包裹的信息完整地录入系统,让后面的高效车队(编译后端)发挥最大价值。

帧评估拦截

报关预检员站在哪里才能拦住所有包裹?答案是:必须在分拣线的最前端,在包裹进入任何处理流程之前。TorchDynamo 也面临同样的选择——它选择在 Python 帧评估 层面“设卡”。什么是 Python 帧?你可以把每一层函数调用想象成一张“工作台”,上面摆放着当前的局部变量、全局变量、当前执行到的代码行号,以及调用者的信息。每次 Python 执行一个函数调用,就会创建这样一张工作台(Frame),一列函数调用形成一叠工作台(调用栈)。TorchDynamo 正是在“评估这张工作台”的入口处安装了一个动态钩子(frame evaluation callback),每当 Python 解释器准备开始执行一个新的帧,这个钩子就会被触发一次。

这个钩子有点像分拣线入口的闸机:每个包裹经过都要“滴”一声,闸机会判断这件包裹是否需要走特殊通道。TorchDynamo 的钩子会快速扫描帧里的字节码,寻找张量操作。如果发现这个帧里有值得捕获的操作,它就会进入“捕获模式”;如果这个帧只是纯 Python 逻辑(比如列表推导、字符串处理),那就直接放行,让 Python 正常执行。触发时机非常关键——是每次函数调用都会经过闸机,所以 TorchDynamo 能捕捉到动态分支下真实执行的路径,而不是静态代码里的所有可能路径。

这一层设计的精妙之处在于时机与范围。它在 Python 帧的边界处拦截,而帧天然对应模型前向传播中的一个个子模块(nn.Module.forward),于是 TorchDynamo 可以做到以模块为粒度捕获图。你调用一次 model(x),它捕获一张前向图;训练时调用 loss.backward(),它再捕获反向图——动态框架的灵活性一点没丢,编译优化的收益却稳稳拿到了。这就好比报关员不拆包裹,只看面单——Python 的动态语义被完整保留,用户代码在语法和逻辑上毫发无损。

python5 行
# 伪代码示意:帧评估钩子的逻辑框架def frame_callback(frame):    if should_capture(frame):      # 扫描字节码,判断是否含张量操作        return convert_to_graph(frame)  # 进入捕获模式    return None                    # 放行,继续正常解释执行

捕获张量操作

闸机放行之后,TorchDynamo 进入“捕获模式”,这时它要像海关查验员一样,逐件拆开包裹、登记每一项物品。在 Python 世界里,模型的每一次张量计算最后都会落到一系列 PyTorch 的 C++ 算子上,比如 add、mm、relu。TorchDynamo 使用了一种叫 TorchDispatch 的机制——它像一个“算子分拣台”,所有涉及张量的函数调用(包括 torch.add、x + y、x.sum())都会先经过分拣台,被 TorchDynamo 篡改成“登记后放行”的模式。

关键在于 TorchDynamo 并不真正干预计算,而是采用“记录 + 重放”的思路。在第一次捕获时,它让算子正常执行一遍(把结果也算出来),同时把算子的名字、输入张量的形状/步长/数据类型、以及算子之间的依赖关系全部记下来,形成一串“操作序列”。这就像报关员一边放行包裹一边录台账:包裹正常发走,但面单信息全部进系统。之后如果再次遇到完全相同的操作序列(相同算子 + 相同形状的输入),TorchDynamo 就不再走逐算子执行的慢路,而是直接把整段序列交给编译后端处理。

TracedSequence=[Op1,Op2,…,Opn],Opi=(name,input_meta,output_meta)\text{TracedSequence} = \left[ \text{Op}_1, \text{Op}_2, \ldots, \text{Op}_n \right], \quad \text{Op}_i = \left( \text{name}, \text{input\_meta}, \text{output\_meta} \right)

1. \text{Op}_i:第 i 个被捕获的张量算子,如 aten::add、aten::mm。

Opi=at::opname(args)\text{Op}_i = \text{at::op}_{name}(\text{args})

2. \text{input\_meta}:输入张量的形状(Shape)、步长(Stride)、数据类型(Dtype)。

input_meta ={shape,stride,dtype}\text{input\_meta } = \{ \text{shape}, \text{stride}, \text{dtype} \}

3. 捕获与重放:首次执行时记录序列,后续匹配到相同元数据时直接编译。

Replay(TracedSequence)→CompiledKernel\text{Replay}(\text{TracedSequence}) \rightarrow \text{CompiledKernel}

捕获张量操作时还有一个微妙之处——动态形状。模型的输入尺寸可能经常变化(比如 NLP 里 batch 不固定、序列长度不一),如果每次形状变了都重新捕获,编译开销会非常大;如果不重新捕获,编译后的内核又可能不适用。TorchDynamo 的解法是引入“Guard“机制:每次捕获时把输入元数据(形状等)记录为 Guard 条件,下一次执行时先检查 Guard——条件满足了就用编译版本,不满足就重新捕获或回退到 eager 模式。这就像报关员的”快速通道”认证:同一家公司的标准件走绿色通道,包装规格变了就重新查验一次。小小设计,兼顾了优化的激进与语义的安全。

构建计算图

当 TorchDynamo 捕获完一整个帧里的所有张量操作后,它会把操作序列组装成一张结构化的计算图(Graph)。这张图不是朴素的台账列表,而是用 FX(Functionalization)中间表示构造的——每个节点是一个算子,节点之间有明确的张量流向边。有了这张图,编译优化器就能做“全图视角”的分析:哪些算子可以融合?哪些内存可以复用?哪些中间结果可以淘汰?这是 eager 模式下逐个算子执行时完全不可能做到的。

Graph=(V,E),V=compile nodes,E=tensor flow edges\text{Graph} = (V, E), \quad V = \text{compile nodes}, \quad E = \text{tensor flow edges}

举个例子,一个典型的 linear + relu + dropout 序列在 eager 模式下要做三次内核启动、三次显存读写。但在计算图视角下,优化器可以把 linear 和 relu 融合成一个内核,让数据停留在寄存器里完成两次操作,显存读写少一次,速度自然就上去了。TorchDynamo 生成的图还有一层好处:它是后端无关的中间表示,保留了完整的算子语义和求导信息,因此无论是 Inductor、XLA 还是 TensorRT,都能把它当作干净的输入。

1. V(节点集):每个节点代表一个张量算子,如 add、mul、conv2d。

V={v1,v2,…,vm},vi∈OperatorSetV = \{ v_1, v_2, \ldots, v_m \}, \quad v_i \in \text{OperatorSet}

2. E(边集):每条边代表张量在算子间的流动方向。

E={(vi,vj)∣tensor flows from vi to vj}E = \{ (v_i, v_j) \mid \text{tensor flows from } v_i \text{ to } v_j \}

3. Functionalization:消除视图(View)与原地(In-place)操作的副作用,图规整为纯函数式数据流。

Functionalize:Graphraw→Graphpure\text{Functionalize}: \text{Graph}_{raw} \rightarrow \text{Graph}_{pure}

构建计算图的最后一步,是 TorchDynamo 会利用 Guard 把图“锚定”到具体的输入情境上。一张图 + 一组 Guard 条件 = 一个可缓存的编译单元。当下一次前向传播的输入形状命中了 Guard 条件,TorchDynamo 就直接命中缓存,跳过捕获流程,把图交给后端编译或直接执行。这种“热路径编译 + 冷路径兜底”的设计,让训练循环中的重复迭代(如第 500 步和第 501 步)几乎零开销地享受编译加速。分拣中心最终形成了稳定的流水线:同样规格的包裹天天见,报关预检只需要做一次。

图输入与后端

一张干净的计算图出来了,谁来执行它?TorchDynamo 的设计哲学是“不重造内核轮子”——它本身不做算子级优化,而是把图交给可插拔的编译后端。PyTorch 2.0 默认带的后端叫 Inductor,它的工作是把图进一步降级为 C++/Triton 内核代码,利用代码生成技术为每一组算子生成高度特化的 CUDA kernel。Inductor 不仅能做算子融合,还能自动推导“终极优化”策略——比如把整层矩阵乘法展开成高效的 GEMM 调用,让 GPU 的访存与计算高度重叠。

后端的可插拔性是 TorchDynamo 另一个精妙之处。除了 Inductor,你可以用 torch.compile(model, backend="xla") 把图发给 XLA,也可以用 backend="tensorrt" 对接 TensorRT,甚至一行 backend="ipex" 调优在英特尔 CPU 上的执行效率。这就像报关站与多家物流公司的对接协议:所有包裹都按统一规格封装好了,哪家车队便宜、哪家速度快,随时可以切换,不需要改包裹的任何东西。TorchDynamo 作为“接口”的角色,让 PyTorch 的编译生态从封闭走向开放,这是它被称为“PyTorch 2.0 编译栈核心入口”的根本原因。

Backend:Graph→Executable,Backend∈{Inductor,XLA,TensorRT,…}\text{Backend}: \text{Graph} \rightarrow \text{Executable}, \quad \text{Backend} \in \{ \text{Inductor}, \text{XLA}, \text{TensorRT}, \ldots \}

text10 行
TorchDynamo (捕获图)        │        ▼ Guard 校验 ──miss──► 重新捕获        │hit        ▼   Inductor (代码生成)        │        ▼  Triton / C++ Kernel

用户代码透明

普通用户需要为这套复杂的捕获流程做什么吗?答案是:一行代码。在 PyTorch 2.0 中,用户只需要把 model 用 torch.compile(model) 包一层,剩下的全部由 TorchDynamo 自动完成。第一次前向传播时,Dynamo 默默地在后台完成帧扫描、算子捕获、建图、Guard 校验和编译;期间若有任何一步失败(比如遇到了原生 Python 的复杂动态特性),它不会报错,而是静默回退到 eager 模式继续执行——因为每次帧评估都是独立的,捕获取消的代价只是一个帧。

这种“透明性”对 AI 从业者来说极其珍贵。你不需要学习新的编程范式,不需要改写前向传播逻辑让模型满足静态图约束,更不需要手动维护图定义与模型参数的同步。对比 TensorFlow 1.x 时代 “先定义静态图再用 sess.run 喂数据” 的割裂体验,PyTorch 的哲学是“让图引擎来适配你的 Python 代码,而不是让你去迁就图引擎”。TorchDynamo 把静态图的好处与动态框架的体验集于一身——吃到了编译优化的红利,却不付出任何代码改写的代价。

python7 行
import torch model = torch.nn.Sequential(torch.nn.Linear(784, 256), torch.nn.ReLU(), torch.nn.Linear(256, 10))model = torch.compile(model)  # 这就是全部:一行代码启用动态图捕获 + 编译 x = torch.randn(128, 784)out = model(x)  # 首次调用:捕获+编译;后续调用:直接命中缓存执行

核心价值总结

回顾整个动态图捕获的旅程,TorchDynamo 实现了三重价值:零代码改动、动态捕获整图、编译优化提速。它站在 Python 解释器与编译器之间,以“报关预检员”的智慧化解了动态与静态的矛盾——动态的 Python 语义一个不丢,静态的编译优化全部拿下。对于 PyTorch 用户而言,torch.compile 是通向高性能执行的单行道;对于编译器从业者而言,TorchDynamo 是理解“如何在动态语言生态里做真正的编译”的教科书案例。

展望未来,TorchDynamo 的生态位还会演化:更长的捕获范围(捕获跨多个帧的图)、更强的 Guard 分析(精确到张量维度的动态形状支持)、更优秀的后端调度。但无论如何变化,它在运行时捕获动态图这件事上的核心思想——“在解释器边界做聪明的事,把脏活累活留给专业的编译后端”——会像分拣中心的预检理念一样,成为整个系统效率的基石。

AI 应用场景

大规模 Transformer 训练提速

阶段:模型训练优化

解决的问题:训练 BERT、GPT 这类大规模 Transformer 时,逐算子 eager 执行会在 Python 层与 C++ 层之间反复切换,GPU 的利用率往往只有 50%-70%,显存带宽被低效的中间结果读写浪费。TorchDynamo 动态捕获整张前向图和反向图后,Inductor 能够自动融合 Attention 内的 QKV 投影与逐元素激活算子,将 GPU 利用率提升到 85% 以上。

具体案例:一位算法工程师只将 huggingface transformers 代码中模型实例调用 torch.compile(),就在 8 卡 A100 上获得了约 1.4 倍的训练吞吐提升,且模型精度与原来的 eager 版本一致。

推理服务延迟优化

阶段:模型部署推理

解决的问题:线上推理服务对响应延迟极其敏感,每个算子单独启动内核会产生几十微秒的固定开销,尾延迟(P99)很高。TorchDynamo 在服务加载时捕获计算图,结合 TensorRT 后端生成融合内核,单次推理的内核启动次数减少 70%,P99 延迟下降 35%。

具体案例:一家电商公司的推荐模型原本采用 eager 模式部署,高峰期 P99 达到 45ms。改为 torch.compile(model, backend="tensorrt") 后,P99 降至 28ms,且代码改动仅一行。

Python 演示

python52 行
class TraceCapture:    def __init__(self, backend):        self.backend = backend        self.guard_cache = {}        self.gc_count = 0     def capture(self, fn, x):        """捕获函数中张量算子的执行序列(元数据)"""        seq = []        y = x        for op in fn:            if op == "linear":                seq.append(("aten::linear", y.shape))                y = y @ self.weight() + self.bias()            elif op == "relu":                seq.append(("aten::relu", y.shape))                y = y.clamp(min=0)            elif op == "dropout":                seq.append(("aten::dropout", y.shape))        return seq, y     def __call__(self, fn, x):        key = tuple(o[1] for o in fn)        if key in self.guard_cache:            self.gc_count += 1            return self.guard_cache[key]        seq, y = self.capture(fn, x)        self.guard_cache[key] = (self.backend(seq), y)        return self.guard_cache[key]  def compile_backend(net):    return lambda *a: sum(a).shape()  def run():    def w(n):        return 784     def b(n):        return 256     trace = TraceCapture(compile_backend)    model = [("linear", "layer1"), ("relu", "layer1"), ("linear", "layer2")]    x = (128, 784)    for step in range(3):        out = trace(model, x)        print(f"step {step + 1}: cache hits = {trace.gc_count}")  if __name__ == "__main__":    run()
目的:展示 TorchDynamo “捕获 - Guard 缓存 - 复用编译结果” 的简化流程。
  1. class TraceCapture模拟 TorchDynamo 的帧捕获器,内部维护 Guard 缓存。
  2. self.guard_cache = {}保存“输入形状元数据 → 编译产物”的映射,对应 Dynamo 的图缓存。
  3. self.gc_count使用 _count 后缀避免与 capture 方法名冲突。
  4. def capture(self, fn, x)遍历算子列表,记录每个算子的名称与输入形状,模拟捕获过程。
  5. seq.append(("aten::linear", y.shape))用元组记录算子元数据,模拟 TorchDispatch 的登记动作。
  6. key = tuple(o[1] for o in fn)Guard 条件:按输入形状生成缓存键,形状一致则命中。
  7. if key in self.guard_cache命中的直接返回编译产物,不重新捕获。
  8. self.guard_cache[key] = (self.backend(seq), y)首轮捕获后调用后端编译,存入缓存。
  9. for step in range(3)连续执行三轮前向计算,演示第一轮开销大、后两轮命中缓存的差异。

常见误区

误区很多人以为 TorchDynamo 是一个能把 Python 直接变成 CUDA kernel 的编译器,用 `torch.compile` 就等于用了 Inductor 做底层代码生成。

正解TorchDynamo 本身不做底层算子代码生成,它的核心职责是图捕获与图输入构建——像报关员只负责登记信息,不负责开车送货。图捕获完成后,它把图交给可插拔的后端(默认 Inductor)做内核代码生成。Dynamo 和 Inductor 是编译栈中的不同层级。

为何常见:因为 torch.compile 是一个统一入口,用户看不到内部的分工,于是把整条管线的功能都归到了 TorchDynamo 名下。

误区认为只要调用了 `torch.compile`,模型任意后续输入都会走编译后的快速路径,性能提升是恒定的。

正解TorchDynamo 的每一张图都绑定了一组 Guard 条件。输入张量的形状、步长、数据类型改变时,Guard 失效,需要重新捕获并编译。如果模型输入形状频繁变化,可能反复触发重新编译导致性能反而更差。这就是为什么动态形状场景下需要配合 shape 优化配置使用。

为何常见:因为静态图的训练思维方式根深蒂固,忽略了动态捕获是基于“当前输入的实际元数据”建立的,天然带条件。

误区担心 TorchDynamo 侵入式地重写或修改模型的 Python 代码,可能导致模型行为变化或调试困难。

正解TorchDynamo 不修改任何用户源代码。它只是在每次 Python 帧评估时挂一个钩子,扫描字节码、捕获张量操作的执行轨迹。若捕获失败,静默回退到 eager 模式;图执行结果与原 eager 执行语义一致。用户拿到的模型对象、训练循环、打印输出都完全不变。

为何常见:用户对“编译”一词的直觉反应是“代码被转换了”,而 HTTP 层代理、AOP 等机制又加强了“拦截即修改”的刻板印象。

误区以为 TorchDynamo 捕获图之后就变成类似 TensorFlow 1.x 那样的静态图,模型中的 Python 控制流全部被固化。

正解TorchDynamo 捕获的是实际执行路径的动态轨迹,每次运行时遇到不同的 Python 分支,可能捕获出不同的图。控制流的灵活性由“帧级捕获 + Guard 校验”保留:遇到新分支就捕获新图,旧图保留继续复用。这是一种“动态生成 + 多版本缓存”的混合模式。

为何常见:因为捕获后交给后端变成固定内核,令人误以为控制流也被编译进了内核。

面试题

Q1. TorchDynamo 在 PyTorch 2.0 编译栈中承担的核心职责是什么?

单选题 · mid

  • A.生成 CUDA 内核代码
  • B.在运行时捕获动态计算图← 正确答案
  • C.自动调优超参数
  • D.替代 CUDA 编译器

答案:B。TorchDynamo 的核心职责是动态图捕获,它像一个报关预检员在 Python 帧评估阶段登记算子执行轨迹。生成 CUDA 内核(A)是 Inductor 等后端的职责,调参(C)或替代 CUDA 编译器(D)更是无关选项。

Q2. TorchDynamo 在哪个层面挂载钩子以拦截张量操作?

单选题 · easy

  • A.操作系统内核层
  • B.CUDA 驱动层
  • C.Python 帧评估层← 正确答案
  • D.显卡固件层

答案:C。TorchDynamo 的帧评估回调(frame evaluation callback)在 Python 解释器准备执行新帧时被触发,就像分拣线闸机装在入口处拦截每个包裹。其他选项与 Python 执行机制完全无关。

Q3. 关于 TorchDynamo 捕获后的计算图,以下说法正确的有哪些?

多选题 · mid

  • A.图是后端无关的中间表示← 正确答案
  • B.图中包含每个算子的张量元数据← 正确答案
  • C.图直接保存训练好的权重数值
  • D.图经过 Functionalization 标准化← 正确答案

答案:ABD。捕获的图包含算子名与输入输出的形状、步长、dtype 等元数据,采用 FX 中间表示且经过 Functionalization 消除副作用,后端无关(A、B、D 对)。图不存权重数据,权重是参数而非图结构的一部分(C 错)。

Q4. Guard 机制在 TorchDynamo 中的主要作用是什么?

单选题 · mid

  • A.防止模型过拟合
  • B.校验输入元数据是否命中缓存← 正确答案
  • C.把所有算子合并成一个内核
  • D.关闭所有 Python 控制流

答案:B。Guard 是捕获时记录输入张量的形状、步长等条件,下次执行先校验是否满足,命中的沿用编译缓存,否则重新捕获。它不做模型正则化(A)、算子融合(C)或控制流禁用(D)。

Q5. 用户在 PyTorch 2.0 中启用 TorchDynamo 编译的推荐方式是?

单选题 · easy

  • A.重写模型为静态图
  • B.调用 model.export_to_graph()
  • C.使用 model = torch.compile(model)← 正确答案
  • D.手动调用 C++ API

答案:C。一行 torch.compile(model) 即完成启用,TorchDynamo 在后台自动捕获图并交给后端编译。无需重写模型为静态图(A)、调用导出 API(B)或手动 C++(D)。

Q6. 以下哪些策略是 TorchDynamo 为处理动态形状而设计的?

多选题 · hard

  • A.Guard 条件包含 shape 校验← 正确答案
  • B.Guard 不匹配时重新捕获← 正确答案
  • C.对所有形状统一生成代码
  • D.缓存多版本编译后图← 正确答案

答案:ABD。Dynamo 在捕获时把形状记录进 Guard(A),每次执行先校验,不匹配则重新捕获(B),新图加入缓存,于是同一模型可同时缓存多张不同形状的图(D)。它不会对所有形状生成统一代码(C),那样会丢失形状特化优化。

Q7. TorchDynamo 捕获图失败时,默认行为是什么?

单选题 · mid

  • A.抛出异常终止训练
  • B.静默回退到 eager 执行← 正确答案
  • C.自动修改用户代码重试
  • D.切换为 TensorFlow 后端

答案:B。由于捕获粒度在单帧层面,失败仅影响当前帧,TorchDynamo 会静默回退到 eager 模式继续执行,保证用户训练不中断,这也是它鲁棒性强的体现。

Q8. 以下哪个是 PyTorch 2.0 的默认编译后端?

单选题 · easy

  • A.XLA
  • B.TensorRT
  • C.Inductor← 正确答案
  • D.ONNX Runtime

答案:C。Inductor 是 PyTorch 2.0 的默认后端,把捕获的图降级为 C++/Triton 内核。XLA、TensorRT 是通过 backend 参数可选接入的第三方后端。

Q9. TorchDynamo 的图捕获机制与传统静态图框架相比,哪些优势成立?

多选题 · mid

  • A.保留 Python 控制流灵活性← 正确答案
  • B.无需改动用户模型代码← 正确答案
  • C.生成的图随实际执行路径变化← 正确答案
  • D.完全不经过 Python 解释器

答案:ABC。帧级捕获让每次执行可按真实路径动态生成图(A、C 对),用户代码包装一行即可无需改动(B 对)。图还是要从 Python 执行中捕获,因此它必须在 Python 解释器中运行(D 错)。

Q10. 关于 TorchDynamo 的算子捕获机制,下列描述最准确的是?

单选题 · hard

  • A.静态解析模型源码生成全量计算图
  • B.运行时记录实际执行到的算子与元数据← 正确答案
  • C.直接读取 GPU 显存中的指令流
  • D.通过改写 Python 字节码永久重写模型

答案:B。TorchDynamo 通过 TorchDispatch 在运行时拦截张量调用,记录算子名与输入输出元数据,形成执行轨迹并建图(B 对)。它不静态解析全量源码(A)、不读 GPU 指令流(C)、也不永久改写字节码(D)。