资讯动态

PyTorch TorchDynamo 源码深潜:从 CPython 字节码到 FX 图的编译器前端完整剖析

发布时间:2026/9/10 10:21:39 来源:尧图企业网站定制
PyTorch TorchDynamo 源码深潜从 CPython 字节码到 FX 图的编译器前端完整剖析【免费下载链接】pytorchTensors and Dynamic neural networks in Python with strong GPU acceleration项目地址: https://gitcode.com/GitHub_Trending/py/pytorchTorchDynamo下文简称 Dynamo是torch.compile的跟踪器tracer负责在执行时理解任意的 Python 程序、记录其中的 PyTorch 算子调用并产出 FX 图是torch.compile中绝大多数疑难 backtrace 的来源。本篇基于 Dynamo Deep-Dive 原文结合本仓库torch/_dynamo/与torch/csrc/dynamo/的真实源码从零剖析 Dynamo 的内部设计它如何借助 CPython 的 frame evaluation API 接管函数执行、如何在 Python 层重写一套字节码解释器、如何用VariableTracker建立中间表示、如何通过 guards 与 symbolic shapes 实现按需重编译以及如何通过 graph break 把无法跟踪的代码切成多张图。读完本文你将具备定位torch.compile报错、解读TORCH_LOGS日志并理解重编译原因的能力。温和的 Dynamo 入门Dynamo 是一个跟踪器给定一个函数和它的输入它会真正执行这个函数并把执行过程中发生的一串无控制流的线性指令记录成一张图。例如下面这段程序import torch torch.compile def mse(x, y): z (x - y) ** 2 return z.sum() x torch.randn(200) y torch.randn(200) mse(x, y)把它保存为example.py并运行TORCH_LOGSgraph_code python example.py可以看到 Dynamo 跟踪出的输出def forward(l_x_: torch.Tensor, l_y_: torch.Tensor): # File: example.py:5, code: z (x - y) ** 2 sub l_x_ - l_y_ z sub ** 2 # File: example.py:6, code: return z.sum() sum_1 z.sum() return (sum_1,)这被称为函数在给定输入下的图graph / trace它以FX graph的形式表示——可以把 FX 图简单理解成一个存储了一系列函数调用的容器。注意图中是一条线性的 PyTorch 算子序列Dynamo 把z (x - y) ** 2拆成了sub与z两步操作。线性意味着图中没有任何分支或控制流。看这个例子import torch torch.compile def fn(x, n): y x ** 2 if n 0: return (n 1) * y else: return y / n x torch.randn(200) fn(x, 2)同样用TORCH_LOGSgraph_code执行得到def forward(l_x_: torch.Tensor): # File: example.py:5, code: y x ** 2 y l_x_ ** 2 # File: example.py:7, code: return (n 1) * y mul 3 * y return (mul,)Dynamo 把if语句整个从 trace 中删掉了只记录输入实际走的那条路径。由此可以得出两个重要结论函数的 trace 依赖输入。图不是在写torch.compile时生成的而是在用真实参数调用fn(x, 2)时才生成。非张量值会被当作常量特化。第二个参数n没有出现在图中Dynamo 把它当作常量并把n 1的结果即3直接记录进图。Dynamo 会把任何非张量值视为常量……唯独整数是例外。整数与形状的动态跟踪能力symbolic shapes正是 Dynamo 避免无谓重编译、支持生产环境任意尺寸模型的关键典型场景包括训练时 batch size 固定、推理时 batch size 任意以及文本/音频处理中可变的序列长度。把上面的例子多调用几次import torch torch.compile def fn(x, n): y x ** 2 if n 0: return (n 1) * y else: return y / n x torch.randn(200) fn(x, 2) fn(x, 3) fn(x, -2)TORCH_LOGSgraph_code会额外生成两张图# Graph for n2 omitted def forward(self, l_x_: torch.Tensor, l_n_: torch.SymInt): # File: a.py:5, code: y x ** 2 y l_x_ ** 2 # File: a.py:7, code: return (n 1) * y add l_n_ 1 mul add * y return (mul,)def forward(self, l_x_: torch.Tensor, l_n_: torch.SymInt): # File: a.py:5, code: y x ** 2 y l_x_ ** 2 # File: a.py:9, code: return y / n truediv y / l_n_ return (truediv,)Dynamo 检测到第二次调用时整数n的值变了于是开始以符号形式跟踪它图中出现了torch.SymInt类型的参数。这两张图是通用的如果之后再调用fn(x, 4)Dynamo 不会重新编译而是直接复用已经跟踪好的图。把入门内容总结成四点Dynamo 是一个 Python 跟踪器给定输入它返回一张记录已执行 PyTorch 函数的 FX 图当检测到整数在多次调用间发生变化时它可以符号化地跟踪整数任何既非张量也非标量的值都会被特化specialize。当然 Dynamo 还做了更多事情判断何时需要重新 trace、重写函数字节码、实现 graph break 等等下文将逐一展开。需要说明的是Dynamo 生成的不只一张图而是多张图——这一点在 Graph Break 一节 中会详细解释。PEP 523CPython 的 frame 求值 API设想由我们来实现 Dynamo该从哪里入手幸运的是Python 3.6 引入的PEP 523为第三方实现 Python 的 JIT 编译器而设计正好提供了切入点。关于 CPython 的背景CPython 内部是一个栈式虚拟机。Python 程序先被编译成字节码bytecode再由解释器逐条执行标准库dis模块可以查看这些字节码。PEP 523 暴露了一个 API允许用户为函数安装自定义的解释器之后 CPython 在执行该函数时会调用这个自定义解释器而不是自己的解释器。为了让自定义解释器能够执行函数CPython 在进入函数时会提供函数的字节码函数参数即局部变量的值与名字全局变量的值与名字内建函数如abs、print。所有这些绑定代码都位于本仓库的 torch/csrc/dynamo/eval_frame.c 中——这是 Dynamo 中唯一与 CPython 直接打交道的 C 代码。CPython 提供了执行函数所需的全部信息在 CPython 的术语里这些对象的集合被称为一个frame。有了这个 API实现跟踪器的方法就清晰了实现一个解释器运行代码并在图中记录执行过程中出现的所有 PyTorch 算子——这正是 Dynamo 所做的。Dynamo 用该 CPython API 解析这些对象并打包成 Python 结构然后立刻从 C 层回到 Python 层。除了这一小段与 CPython 通信的代码外Dynamo 几乎完全用 Python 实现。顺带澄清torch.compile装饰器的职责是安装必要的脚手架把字节码、参数、全局变量等在函数被调用时传递给 Dynamo——torch.compile本身并不编译任何东西。在 Python 中重写 CPython回到 Python 世界。此时我们拿到了函数的字节码和全部执行上下文落脚点在 torch/_dynamo/convert_frame.py 中的_convert_frame_assert当前仓库中由ConvertFrameAssert类与convert_frame_assert工厂函数实现。它正是torch.compile装饰器返回的函数而torch.compile只是_dynamo.optimize之上的一层友好 API见 torch/_dynamo/eval_frame.py。在实现 Python 解释器之前先要定义一套中间表示IR把局部变量与全局变量都包装进 Dynamo 自己的内部类中。这样既能更好地跟踪对象又能把在 Dynamo 眼中可同等对待的对象归为一组。内部类体系的父类是VariableTracker它表示 Dynamo 能够理解的各种 Python 对象ListVariable表示list对象内部维护一个VariableTracker列表见 torch/_dynamo/variables/lists.pyConstantVariable包装所有被 Dynamo 视为常量的对象见 torch/_dynamo/variables/constant.py需要特殊对待的对象有专门子类比如张量对应的TensorVariable见 torch/_dynamo/variables/tensor.py。所有这些内部类都定义在 torch/_dynamo/variables 目录下。Python 对象在 torch/_dynamo/variables/builder.py 的VariableBuilder._wrap中被包装成对应的VariableTracker——这是一个超长的elif链递归地把 Python 输入模式匹配成合适的VariableTracker类型。调试技巧当 Dynamo 给出出乎意料的结果时常常是 builder 的逻辑出了问题——builder 逻辑错误时Dynamo 可能把变量包装成错误的VariableTracker类型为后续埋下隐患。遇到 Dynamo 报错时值得留意错误信息中出现的VariableTracker类型以及抛出异常的VariableTracker方法。特别是当我们发现某个对象被跟踪成了UserDefinedObjectVariableDynamo 的兜底类而它本应被跟踪成更具体的类型时问题往往出在VariableBuilder的逻辑上。调试技巧用TORCH_LOGSdynamo运行程序时会打印出如下形式的行TRACE LOAD_GLOBAL y [TorchInGraphFunctionVariable(built-in method any), TensorVariable()]这是原始程序的字节码及该时刻的栈状态对定位某个对象没有跟踪成正确的VariableTracker非常有帮助。有了 IR剩下的就是重新实现 CPython 的栈式虚拟机。这由 torch/_dynamo/symbolic_convert.py 中的InstructionTranslatorBase当前仓库第 1465 行附近完成。该类约有 200 个方法几乎实现了全部 Python 字节码。以BUILD_LIST为例def BUILD_LIST(self, inst): items self.popn(inst.argval) self.push(ListVariable(items, mutation_typeValueMutationNew()))BUILD_LIST是l [2, 3, 4]这类构造生成的字节码三个元素对应BUILD_LIST 3即从栈顶弹出 3 个元素把由它们构成的 list 压回栈顶。该实现位于当前仓库 torch/_dynamo/symbolic_convert.py 第 4075 行附近。生成输出图有了符号化执行 Python 代码的能力就可以在给定输入的程序执行过程中提取 PyTorch 算子。这由 Dynamo 的OutputGraph对象实现见 torch/_dynamo/output_graph.py当前仓库第 690 行开始。OutputGraph与InstructionTranslator对象绑定跟踪生成最终 FX 图所需的全部数据。FX 图中的所有输入与中间元素都是fx.Node。在 Dynamo 中fx.Node被包装成fx.Proxyfx.Proxy负责构建 FX 图会把作用在其上的每一次 PyTorch 操作记录进图。调用OutputGraph.create_proxytorch/_dynamo/output_graph.py可以创建要加入图的新操作再通过 torch/_dynamo/variables/builder.py 中的wrap_fx_proxy把它真正加进图。图既存储张量上的算子也存储符号整数上的算子。下面先讨论 Dynamo 如何解决一个相当重要的正确性问题再回到符号整数。让 Dynamo 保持正确Guards至此我们有了一个完全无视控制流的跟踪器为此还重写了整个 CPython……这听起来有点小题大做实际上确实如此——torch.jit.trace 不需要这么多机制也能做类似的事。问题在于正如其文档所警告的torch.jit.trace只在被跟踪程序不依赖数据即程序本身是线性的时才正确这意味着不能写 if-else、for/while 循环和异常连第三方库也不能使用任何控制流。在 Python 这种动态语言中不用控制流是极其苛刻的限制。JAX 的解法是每次重新 trace 并缓存图Dynamo 则用guards守卫来避免每次都重 trace 整个程序。guard 是一条假设关于输入的布尔表达式其作用是让一个 frame 针对一组示例输入做特化。只有这些假设对新输入仍然成立时复用已跟踪的图才是合法的。例如任何常量输入比如字符串都会安装一条 guard要求该输入的类型是str且值等于传入的字符串。运行import torch torch.compile def fn(a, b): return a * len(b) fn(torch.arange(10), Hello)用TORCH_LOGSguards打印其中一部分 guard___check_type_id(L[b], 94334122025024) L[b] Hello含义是局部变量b的类型应是指定类型这里为str用常量9433...表示且其值应为Hello。如果换一个参数再调用import torch torch.compile def fn(a, b): return a * len(b) fn(torch.arange(10), Hello) fn(torch.arange(10), Hi)用TORCH_LOGSrecompiles可以看到是哪条 guard 失败了Recompiling function fn in script.py:3 triggered by the following guard failure(s): - L[b] HelloGuards 在两类时机被累积一是函数输入在 builder 中被包装时见 torch/_dynamo/variables/builder.py二是程序执行过程中例如 torch/_dynamo/variables/dicts.py 中的字典操作。Sourceguard 如何指回原始对象source跟踪的是如何从进入当前 frame 时的原始局部/全局变量以及它们包含的对象重构出某个变量。例如def foo(x: Tensor, y: List[Tensor]): a x * y[0] return a * x其中x、y的 source 是LocalSourcey[0]的 source 是GetItemSource内部存了一个LocalSource而a没有 source因为它只是 FX 图内部的中间变量。所有这些类都定义在 torch/_dynamo/source.py 中。GetItemSource生成的 guard 可以从下面这个例子看到import torch torch.compile def fn(x, l): return x * len(l[0]) fn(torch.randn(8), [Hi, Hello])生成的 guards___check_type_id(L[l], 94439025877664) len(L[l]) 2 ___check_type_id(L[l][0], 94439025840192) L[l][0] Hi ___check_type_id(L[l][1], 94439025840192) L[l][1] Hello这里能看到GetItemSource[0]、[1]包裹LocalSourceL[l]生成的代码。有了 sources 和 guards就能实现一个避免每次重 trace 的缓存系统。但细心的读者会发现上面的 guard 都只依赖输入对象本身完全可以在执行函数之前预先计算——也就是说这套 guard 系统完全可以搭建在torch.jit.trace之上用少得多的功夫达到同样效果。为什么非要重新实现 Python 解释器答案在symbolic shapes。符号形状Symbolic Shapes为了实现整数的符号化跟踪Dynamo 使用符号类torch.SymInt见 torch/init.py它行为像int但会把作用于其上的所有操作记录进输出 FX 图。与之类似的还有SymBool与SymFloat类后者目前使用不多。下面讨论符号形状跟踪的三个核心属性及其实现。默认静态Static by defaultDynamo 默认假设每个整数无论是输入还是张量形状都是静态的函数第一次执行时不会跟踪任何整数只有当它检测到某个整数或形状在后续执行中改变了值才会对它做符号化跟踪并生成对该变量通用的图。前面整数例子已展示过该行为这里看张量形状的例子import torch torch.compile def fn(a, b): return a.shape[0] * a * b fn(torch.randn(4, 3), torch.randn(4, 3)) fn(torch.randn(8, 3), torch.randn(8, 3))用TORCH_LOGSgraph_code运行两次调用分别被跟踪为def forward(self, l_a_: torch.Tensor, l_b_: torch.Tensor): mul 4 * l_a_ mul_1 mul * l_b_ return (mul_1,) def forward(self, s0: torch.SymInt, l_a_: torch.Tensor, l_b_: torch.Tensor): size l_a_.size() getitem size[0] mul getitem * l_a_ mul_1 mul * l_b_ return (mul_1,)第一张图里形状被当作常量4形状一旦变化第二张图就用SymInts0符号化地跟踪它。更直观地查看中间值的形状可以运行TORCH_LOGSgraph_sizesTRACED GRAPH TENSOR SIZES __compiled_fn_1 l_a_: (s0, 3) l_a_ (concrete): (8, 3) l_b_: (s0, 3) l_b_ (concrete): (8, 3) mul: (s0, 3) mul (concrete): (8, 3) mul_1: (s0, 3) mul_1 (concrete): (8, 3)可以看到两个张量参数的第一维由s0表示是动态的。Dynamo 是如何实现这一点的用TORCH_LOGSguards查看# Guards first call check_tensor(L[a], torch.float32, deviceNone, requires_gradFalse, size[4, 3], stride[3, 1]) check_tensor(L[b], torch.float32, deviceNone, requires_gradFalse, size[4, 3], stride[3, 1]) # Guards second call check_tensor(L[a], torch.float32, deviceNone, requires_gradFalse, size[None, 3], stride[3, 1]) check_tensor(L[b], torch.float32, deviceNone, requires_gradFalse, size[None, 3], stride[3, 1]) L[b].size()[0] L[a].size()[0] 2 L[a].size()[0]第一次调用时guards 检查张量具有固定的 size 与 stride这些 guard 在第二次执行时失败于是 Dynamo 重新 trace。由于失败的是int类 guard第二次迭代就把该整数符号化跟踪并为这个更通用的 kernel 安装更一般的 guards。编译性能建议如果你预先知道某个维度的大小会变化可以在调用torch.compile之前先调用 torch._dynamo.decorators.mark_dynamic 把它标记为动态这样能省掉第一次静态形状的编译。类似的工具还有maybe_mark_dynamic、mark_static。也可以用torch.compile(dynamicTrue)让所有整数和形状都被跟踪主要适用于调试。0 与 1 永远被特化无论是否把某个维度标记为动态只要传入的输入中该维度是 0 或 1Dynamo 都会把它当作非动态来跟踪并生成专属的图。这就是上面例子中出现2 L[a].size()[0]这类 guard 的原因。该策略主要有两个考量张量是空的当且仅当它的某个维度为 0张量只有在其某个 stride 为 1 时才可能是连续的。这一策略不适用于普通 Python int如果我们认为某个 Python int 应该被动态编译默认不会特化它而是根据其具体使用方式决定是否特化。Duck shaping鸭子塑形Dynamo 会执行所谓的 duck shaping如果两个动态整数在 trace 时值相同就假设它们相等并据此安装 guard。效果就是上例中不会出现s0、s1两个符号而是统一成s0并带一条 guardL[b].size()[0] L[a].size()[0]。这样既能在编译器中做融合又能生成足够通用的 kernel。符号整数上的 guards理解了符号形状的高层实现与属性再回到那个问题为什么符号形状逼迫我们去接管 CPython 解释器看这个例子import torch torch.compile(dynamicTrue) def fn(a): if a.shape[0] * 2 16: return a else: return a 1 fn(torch.randn(8))这段代码有一条形如2*L[a].size()[0] 16的 guard。它是关于函数输入的非平凡表达式却是在程序执行的中途注册的——我们必须看到那个以SymNodeVariable参数为条件的if语句才知道需要这条 guard。这类条件对torch.jit.trace是不可见的必须深度分析 Python 代码才能发现。调试技巧用TORCH_LOGSdynamo运行这段代码会告诉我们这条 guard 是在哪里添加的eval 2*s0 16 [guard added] at script.py:5 in fn (_dynamo/variables/tensor.py:812 in evaluate_expr)在那里下一个断点、查看 backtrace对理解 guard 的来源非常有用。让 Dynamo 完备Graph Breaks到这里我们有了一个能跟踪张量与整数上的 PyTorch 算子、并带缓存系统的跟踪器——而且能执行任意的 Python 代码。但执行任意 Python 代码这个说法可能太夸张了Dynamo 实现了 Python 的很大一部分但它实现协程或 async 这类复杂特性吗它理解整个 Python 标准库吗NumPy 也有 Python APItorch.compile理解 NumPy 吗那 Django 呢Python 生态极其庞大其中很大一部分是用 C、Rust 等更高效的语言实现、只暴露 Python 绑定。对于用 C 实现的 Python 对象Dynamo 根本无法穿透。那么当跟踪器遇到它不理解的算子时该怎么办常见的做法是告知用户它卡在了哪个算子上然后彻底放弃跟踪。但这对 PyTorch 是严重的可用性问题——用户早已习惯了 PyTorch 的灵活性一个真实例子是doctr_det_predictor模型在推理后处理中使用 NumPy 和cv2库。这正是能接触到 CPython的另一大价值所在与其报错Dynamo 可以让 CPython 去执行那段问题代码做法是在 trace 时为问题代码之前的所有算子生成一张图为之后的所有算子生成另一张图如果问题代码有多处Dynamo 可以按需把代码切成尽可能多的图运行时先执行第一张图然后让 CPython 执行问题代码最后执行第二张图。这种停止跟踪并生成多张图的过程称为 graph break。坦白说前面各节为了叙述方便撒了一个小谎Dynamo 生成的不是一张图而是多张图在 graph break 之后重新开始跟踪实际上可以理解为开始跟踪一个新函数新图有自己的 guards、自己的一组局部变量等等。要理解 graph break 的实现需要重新审视 Dynamo 与 CPython 的交互。PEP 523 允许用户使用自己的 frame 求值机制但 CPython 同时也暴露了它自己的 frame 求值接口供他人调用。Dynamo 正是利用这一点让快速的 CPython 解释器去运行编译后的代码。对于一个没有 graph break 的函数两次以相同参数调用它的完整过程如下第一次调用Dynamo 把函数跟踪成 FX 图FX 图随后被编译器Inductor编译成高效的低层代码——那是另一篇文章的故事Dynamo 重写函数字节码使其直接调用编译后的函数Dynamo 把新字节码交给 CPython 执行见 torch/csrc/dynamo/eval_frame.c。第二次调用用第一次调用产生的 guards 检查新参数参数相同则通过让 CPython 执行与这些 guards 关联的字节码。这个流程看起来过于复杂为什么不直接创建一个指向编译后函数的 C 绑定然后执行它恰恰是这个生成新字节码再交给 CPython 执行的模式让我们得以实现 graph breakgraph break 生成的字节码具有如下结构执行第一张图的字节码把栈恢复到CPython 执行完第一张图后应有的样子并重放此刻可见的局部/全局变量修改导致 graph break 的那段字节码执行第二张图的字节码。用一个简单例子验证import torch torch.compile def fn(a): b a 2 print(Hi) return b a fn(torch.randn(4))用TORCH_LOGSbytecode运行可以看到初始字节码与修改后的字节码MODIFIED BYTECODE fn script.py line 3 0 LOAD_GLOBAL 1 (__compiled_fn_0) 2 LOAD_FAST 0 (a) 4 CALL_FUNCTION 1 6 STORE_FAST 3 (graph_out_0) 8 LOAD_GLOBAL 0 (print) 10 LOAD_CONST 2 (Hi) 12 LOAD_FAST 3 (graph_out_0) 14 LOAD_CONST 3 (0) 16 BINARY_SUBSCR 18 STORE_FAST 1 (b) 20 CALL_FUNCTION 1 22 LOAD_GLOBAL 2 (__resume_at_14_1) 24 ROT_TWO 26 LOAD_FAST 0 (a) 28 LOAD_FAST 1 (b) 30 CALL_FUNCTION 3 32 RETURN_VALUE MODIFIED BYTECODE resume_in_fn script.py line 6 0 LOAD_GLOBAL 1 (__compiled_fn_2) 2 LOAD_FAST 2 (b) 4 LOAD_FAST 1 (a) 6 CALL_FUNCTION 2 8 UNPACK_SEQUENCE 1 10 RETURN_VALUE修改后的字节码被拆成两个函数原函数fn以及 Dynamo 创建的resume_in_fn。后者是 Dynamo 为实现从 graph break 处继续执行程序而生成的函数通常被称为续延函数continuation其作用就是用正确的参数调用第二张编译图。原函数的字节码被重写为前文描述的策略L0-4调用编译函数a 2L6把结果存到局部变量graph_out_0它是一个 tupleL8-18把栈恢复到 graph break 发生时应有的状态L20执行导致 graph break 的代码print(Hi)L22-32调用编译好的续延函数a b。字节码的栈生成逻辑委托给了各个VariableTracker子类每个VariableTracker对象都有一个reconstruct方法见 torch/_dynamo/variables/lists.py生成重建它表示的 Python 对象所需的字节码。调试技巧graph break 会损害性能应当尽量避免。用TORCH_LOGSgraph_breaks运行程序可以查看程序命中了多少次 graph break。其输出以VariableTracker对象的形式给出所以上文关于VariableTracker的调试技巧有时也能帮你找出 graph break 的根源。结论Dynamo 是一块复杂的软件——一旦决定实现一个 CPython 解释器你就知道这是一场硬仗。希望本文能帮你揭开它的神秘面纱。Dynamo大部分用 Python 实现。文中给出了大量源码链接阅读这些代码、grep 调用它们的位置、或者在上面打断点查看调用栈都有助于理解其余部分。当然学习软件的最好方式是扩展它——对 Dynamo 而言可以关注仓库中torch/_dynamo/下标记了 dynamo 相关的 issue很多只需要对代码做很小的改动。附录Dynamo 关键源码索引与 CPython 交互的 C 绑定torch/csrc/dynamo/eval_frame.ctorch.compile的入口与_dynamo.optimizetorch/_dynamo/eval_frame.pyframe 转换入口_convert_frame_assert/ConvertFrameAsserttorch/_dynamo/convert_frame.py符号化解释器InstructionTranslatorBase约 200 个字节码方法torch/_dynamo/symbolic_convert.py输出图OutputGraph与create_proxytorch/_dynamo/output_graph.pyIR 基类与各VariableTracker子类torch/_dynamo/variables其中 builder.py 负责_wrap包装与wrap_fx_proxylists.py 有ListVariable与reconstructconstant.py 定义常量tensor.py 有TensorVariable与evaluate_exprdicts.py 在字典操作中累积 guardsourcesLocalSource、GetItemSource等torch/_dynamo/source.py符号整数SymInttorch/init.py动态形状标记mark_dynamic等torch/_dynamo/decorators.pyTORCH_LOGS调试开关速查graph_code查看跟踪出的图、graph_sizes查看图中张量形状、guards查看安装的 guard、recompiles查看重编译原因、bytecode查看修改前后的字节码、graph_breaks查看 graph break 位置、dynamo查看字节码与栈状态的逐步 trace。【免费下载链接】pytorchTensors and Dynamic neural networks in Python with strong GPU acceleration项目地址: https://gitcode.com/GitHub_Trending/py/pytorch创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价