假设你是一家连锁餐厅的老板。主厨老张手艺非凡,但他做菜全凭手感——这道菜的火候、调味,统统只存在于他脑子里。你想把老张的手艺复制到全国分店,于是请来一位记录员,站在旁边看老张做菜。记录员并不亲自下厨,也不尝味道,只是拿笔把“热锅、倒油、下葱、放牛肉、加酱油”这些动作按顺序记下来。几个小时后,一张菜谱诞生了。这张菜谱不占灶台、不耗食材,却完整描述了做菜的全部流程,可以被人分析、修改、复制。PyTorch 中的 FX 就是这位“记录员”:它把模型“现场做菜”的过程(动态执行)变成一张“菜谱”(静态计算图)。它手里那支“模拟锅铲”,名字叫 Proxy。本文将从符号追踪的原理讲起,带你理解 FX 图捕获如何工作、怎样修改计算图、又有哪些“记录不了的盲区”,以及这一切在 AI 工程中的实战价值。
掌握 FX 图捕获,你就拥有了“改写任意 PyTorch 模型”的工程能力——量化、剪枝、算子融合都建立在图操作之上,这是 AI 编译器与系统工程师的核心技能。
可视化演示
展示torch.fx三维度:符号追踪(Tracer创建Proxy替换输入执行forward记录操作生成Graph节点链表)、图改写(Conv→BN→Relu模式匹配替换为单个Fused算子修改node.target)、GraphModule编译(Graph重新编译为等价Python forward代码成为可执行模块),三模式切换理解FX图捕获机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+C1+'
从动态图到静态图
老张做菜时,你问他“这步为什么放这么多盐?”他只会回答“看感觉”。PyTorch 默认的动态执行(Eager 模式)就是这样:模型一边前向传播,一边在真实张量上完成每一个算子计算,调试起来非常直观,但“菜谱”并不存在——你无法脱离代码去分析整个计算流程,更没法对它做全局优化。这就如同只有老张本人在灶台前,菜才能出锅。
为了让“手感”变成“可复制的标准”,你需要一位记录员。这位记录员就是 FX 中的 Tracer(追踪器),它站在模型旁边,观察 forward 的每一步算子调用,把它们变成一张静态计算图。所谓“图捕获”,就是把动态执行过程中每一次算子调度、每一个数据流向,完整映射成一张可用数据结构表达的有向无环图(DAG)。捕获得到的对象叫做 GraphModule——它既保留了 nn.Module 的调用接口,又让你能直接访问那张完整的图。
技术层面,Eager 模式的每次运算都要经过 Python 层的解释与调度,开销大且缺少全局视角;而捕获成图后,所有算子变成节点,张量流动变成边,模型从“一段代码”变成了“一份数据”。这份数据可以被程序遍历、统计、修改,甚至序列化保存,为后续的量化、算子融合、部署导出提供了统一入口。
有了这张图,老张的“手感”终于变成白纸黑字的菜谱,任何人都能阅读、审校,也能进一步修改。这正是 FX 捕获最根本的价值:它不是结束,而是所有优化与部署工作的起点。
- 节点集合 V图中的每个节点代表一次算子调用或一个输入输出占位符,如“线性层计算”是一个节点。
- 边集合 E每条有向边从生产者节点指向消费者节点,表示上游输出被下游当作输入。
- 捕获映射symbolic_trace 把模型的 forward 执行流程转化为上述二元组。
符号追踪原理
记录员最怕两件事:一是打断大厨干活,二是自己动手做菜把口味搞错。所以他从不拿真锅铲,而是握着一支“模拟锅铲”——大厨做一个动作,他就在本子上记一行。FX 中这支“模拟锅铲”叫 Proxy(代理)。追踪开始时,Tracer 会把模型的输入 Tensor 悄悄替换成 Proxy 对象。Proxy 并不是真实的张量,它不保存任何数值,但它会拦截所有作用于其上的算子调用。
当你在模型的 forward 里写 h = self.fc(x) 时,假设 x 已经是 Proxy,那么这次模块调用并不会真的执行矩阵乘法,而是被 Tracer 拦截下来,生成一个对应的图节点,同时返回一个新的 Proxy 作为“计算结果”继续向后传播。整个过程中,真实计算被“模拟”掉了,而操作被“记录”下来了——每次运算都变成菜谱上的一行字。这种“只记录、不计算”的机制,FX 称为符号追踪(Symbolic Tracing)。
Tracer 的拦截能力来自 PyTorch 的低层调度协议:__torch_function__ 和 __torch_dispatch__。当调用 torch.add(proxy_a, proxy_b) 时,PyTorch 会先询问 Proxy 是否要处理该操作,于是 Tracer 得到机会把这次调用登记成一个节点。通过这种方式,无论用户调用内置算子、自定义函数还是 nn.Module,只要输入链路上带有 Proxy,操作就会被捕获。
就像记录员用模拟锅铲把老张的每个动作记成一行菜谱,Proxy 让追踪过程不产生任何真实数值计算,却完整保留了操作流程。理解 Proxy,你就理解了 FX 捕获的引擎。
- 操作类型 op标定节点的种类,决定这行“菜谱”是什么动作。
- 固定参数 args操作附带的常量或配置,比如卷积的 stride、padding。
- 输入依赖 inputs上游节点列表,指明本次计算消费了哪些中间结果。
- 节点生成规则三者通过 $\phi$ 组合成一个完整节点。
图结构基础
一份标准菜谱要有固定格式:步骤编号、动作、食材、火候。FX 的计算图也有自己的“菜谱格式”,也就是 Graph 对象。Graph 由若干 Node 组成,每个 Node 记录六类信息:name(步骤名)、op(操作类型)、target(操作对象)、args(输入参数)、kwargs(关键字参数)和 users(下游消费者)。这些字段共同描述了一行“菜谱步骤”的完整语义。
节点的 op 类型决定了菜谱行的“动作类别”。placeholder 代表“原料准备”,也就是模型的输入;call_function 表示调用某个独立函数,例如 torch.mul;call_module 表示调用某个子模块,例如 self.fc 对应的线性层;get_attr 表示“取出一份调料”,即读取模型参数或缓冲区;output 则是“出锅装盘”,标志计算图结束。通过 graph.nodes 可以按顺序遍历全部节点,每个节点的 args 与 kwargs 指向输入节点,构成数据流边;而 node.users 则反向记录了“这个步骤的结果被谁消费了”。
有了这套数据结构,图就可以被程序化地分析:比如统计模型里有多少个卷积算子、某个张量被哪些层共享、哪些节点是冗余的。也可以通过修改节点列表来改写模型结构。底层的表示越规范,上层的工具就越强大——这正是 FX 把计算图做成“公开数据”而非“黑盒执行流”的原因。
菜谱有了统一格式,任何厨师都能看懂,任何工具都能分析。Graph 的标准化正是 FX 能被量化、剪枝、融合等大量工具复用的根基。
- 遍历节点对图 G 中的所有节点 n 逐一取出来。
- 提取字段取出每个节点的操作类型 op 与目标对象 target。
- 构成集合将全部配对收纳进集合 S,便于统计与模式匹配。
图变换优化
菜谱初稿往往不是最优的:老张习惯“先切菜、放一边、再热锅”,这两件事其实可以合并;有些装饰用的配菜放上去根本没人吃,删掉也不影响味道。修改菜谱就是“图变换”,而每一次修改规则,业内叫一个 Pass(一趟变换)。在 FX 中,Pass 就是一段程序化的图修改逻辑。
FX 提供了低层图操作 API 供你搭建 Pass。graph.erase_node 可以删除一个节点;graph.inserting_before / graph.inserting_after 控制新节点插入的位置;node.replace_all_uses_with 则把某个节点的所有引用替换成另一个节点——相当于把菜谱里所有“用 A 食材”的地方一次性改成“用 B 食材”。基于这些原语,你可以编写任意复杂的变换逻辑。
三个经典 Pass 能让你快速建立直觉。其一是常量折叠:把 get_attr 读出的固定参数与常量运算提前算好,省去运行时重复计算,就像把“每勺盐的重量”预先称好。其二是死代码消除(DCE):删除输出不再依赖的节点,如同拿掉不影响味道的摆盘步骤。其三是算子融合:把“线性层 + ReLU”两个节点合并为一个算子,减少内核启动与中间张量读写——这正是把“加盐”和“搅拌”合并成一个连贯动作。
修改菜谱不是为了做一道新菜,而是让同一道菜做得更快、更稳、更适合批量生产。优化的 Pass 应当保持模型语义不变,除非你故意改变它的行为——这正是后文验证环节要盯紧的事。
- 单趟变换一个 Pass 是定义在图上的映射,输入图 G,输出图 G1。
- 按序施加第二趟变换作用在第一趟的结果上,依次递推。
- 复合等价整体效果等价于用复合函数一次完成全部修改。
代码生成回译
草稿纸上的菜谱只有老张自己看得懂,别的厨师拿到也一头雾水。FX 的另一个关键能力是“把图誊写成标准 Python 代码”——就像把潦草的笔记整理成打印版菜谱,任何人都能照着执行。GraphModule 内部同时持有 Graph 和一段与图同步的 forward 源码。
调用 gm.to_code() 可以查看这段生成代码:你会发现它把图里的每个节点还原成一行 Python 语句,可读性极佳。更有意思的是,当你修改完图之后,调用 gm.recompile(),FX 会根据当前图重新生成可执行代码。这意味着“改图 → 重新编译 → 立即生效”成了一个闭合工作流,无需手工重写 forward。图是设计蓝图,代码是施工图纸,两者由 FX 自动保持同步。
这与 TorchScript 有本质区别。TorchScript 把 Python 模型编译成静态 IR(中间表示),类似把菜谱翻译成机器才能读懂的二进制配方,人难以直接阅读;而 FX 生成的仍是 Python 源码,人类可以逐行审查、随意打印。正因如此,FX 在训练侧的优化场景(如量化感知训练)中比 TorchScript 更受青睐——它既保留了图的全局视角,又兼顾了 Python 生态的可读性。
像打印菜谱一样,FX 让图和代码互相转化、彼此验证。这种“可视、可改、可还原”的设计,是 FX 作为中间表示最大的魅力所在。
- to_code 映射将 Graph 的节点序列翻译成可读的 Python 函数体。
- 重新编译图结构与源码绑定,生成可执行的 GraphModule。
- 双向同步修改图结构后必须重新生成代码,二者保持一致。
捕获限制约束
记录员再敬业也有盲区:老张“看颜色就知道出锅”,这没法写进菜谱;“冰箱里还剩什么就放什么”,记录员也无法预知。FX 的符号追踪同样有盲区,其中最大的坑就是动态控制流。追踪时输入被替换成 Proxy,如果你的 forward 里写了 if x.sum() > 0:,由于 x 是 Proxy 而非真实张量,x.sum() 得不到一个真实的数值,if 语句无法判断真假,追踪器只能报错终止。
除了动态控制流,非张量输入也是常见的拦路虎。默认情况下,placeholder 节点期望接收 Tensor;如果你向模型的 forward 传入 dict 或自定义对象,捕获过程就会失败或行为异常。此外,只有被 Proxy 张量参与的运算才会被记录;纯 Python 逻辑(比如普通的整数计数、字符串拼接)会照常执行,但不会进入计算图。这意味着有些“隐形动作”完全逃过了记录员的眼睛。
面对这些限制,FX 提供了一些补救手段。torch.fx.wrap 可以把一个 Python 函数声明为“可追踪的叶子函数”:追踪时不再展开其内部逻辑,而是整体记为一个节点。你还可以继承 Tracer 并重写相关方法,定制非张量输入的处理方式。但归根结底,符号执行的信息不完备是本质约束——当某个决策依赖运行时的真实数值时,任何静态记录工具都无法替你“猜”出答案。
菜谱不是万能的。理解记录员的盲区,你才能在设计模型时主动规避不可追踪的写法,或者在捕获失败时快速定位问题——这份“边界感”是 FX 工程实践的第一课。
应用场景部署
菜谱的真正价值在连锁店扩张时体现:复印、分发、让每个厨师照谱做菜。静态图也一样,一旦模型被捕获成图,量化、剪枝、部署就全变成了对这张“菜谱”的批量加工。FX 最有名的应用之一就是量化感知训练(QAT)。在图模式下,FX 可以在每个算子的输入输出之间插入伪量化节点(FakeQuantize),模拟 8bit 低精度计算带来的误差,让模型在训练中提前“适应”量化噪声;训练完成后,这些伪量化节点再被替换成真正的量化算子。
算子融合是另一大应用。以经典的 Conv+BN+ReLU 为例,在 Eager 模式下这三个算子要启动三次内核、读写多次中间张量;而捕获成图后,可以安全地把它们合并成一个融合算子,显著降低推理延迟。结构剪枝同样受益于图模式:由于图完整记录了每个算子输出的通道数以及被谁消费,你可以程序化地裁剪掉冗余通道对应的边,再重新编译模型。最后,捕获得到的图可以被序列化导出为 TorchScript、ONNX 等格式,送进 TensorRT、ONNX Runtime 等推理引擎——菜谱被翻译成了所有厨房都通用的语言。
一张可复制的菜谱,让餐厅从“靠一个人”变成“靠体系”。FX 图捕获带来的正是这种杠杆效应:模型优化从手工逐层调参,升级为在图上做批量、可复用的工程改造。
- 复合顺序复合函数表示先调用 g,再把结果交给 f。
- 融合算子K 在单次内核调用内完成两步计算。
- 等价判据在浮点容差范围内,K 与逐步计算的输出一致。
性能与调试
菜谱写完了,还要校对:步骤有没有遗漏?流程能不能再快?FX 工程实践中,图执行性能和调试手段同样重要。图模式之所以能提速,核心在于减少了 Python 解释器开销——Eager 模式下每个算子都要经过 Python 层调度,而图模式下算子被组织成固定序列,可以批量生成代码、预分配内存、融合内核。实践经验表明,量化与融合叠加后,CPU 上的推理吞吐量相比 Eager 往往能提升 2 到 4 倍。
调试方面,FX 提供了友好的工具链。graph.print_tabular() 能以表格形式逐行打印每个节点,方便人工审查“菜谱”有没有写错;graph.lint() 会检查图结构是否合法,比如每个节点的输入引用是否完整;gm.code 则展示当前生成的源码,方便与原始模型语义做比对。推荐的工程实践是“捕获—变换—验证”三步走:先捕获图并与原始模型输出对比,确认一致后再施加变换;每次变换后再次对比输出,确保优化没有悄悄破坏语义。
好菜谱要经得起多位厨师照做不翻车,好图变换要经得起任意输入对比不漂移。把“验证”刻进工作流,你才能真正驾驭 FX 这把手术刀。
- 输出对比计算原始模型与变换后模型对同一输入的输出范数距离。
- 容差判定距离小于预设阈值 ε 则视为语义保持。
- 全覆盖验证在多个测试输入上重复上述判定。
AI 应用场景
量化感知训练(QAT)
阶段:模型压缩与优化
解决的问题:传统的训练后量化(PTQ)在轻量模型上精度掉点严重,因为模型从未“见过”低精度计算带来的噪声。FX 图捕获可以在图中精确插入 FakeQuantize 节点,让模型在微调过程中逐步适应量化误差,把精度损失降到最低。
具体案例:某图像分类团队要把 MobileNet 部署到手机端。工程师用 fx.symbolic_trace 捕获模型后,在图上按模块粒度插入伪量化节点,仅量化 Conv 层而保留敏感层为 float。完成 QAT 后,模型精度由 PTQ 的 -3.2% 损失改善到 -0.4%,推理速度提升约 2.5 倍。
推理引擎的算子融合
阶段:推理加速
解决的问题:GPU/CPU 推理时,每个算子单独启动内核,中间张量反复读写显存或内存,访存开销远超计算本身。Eager 模式看不到全局,无法安全融合;而 FX 捕获的静态图让引擎拿到完整的算子依赖关系,可以放心地做多算子融合。
具体案例:某团队优化 BERT 的 LayerNorm,在 FX 图上把 layer_norm、add、pow、sqrt 等多个小算子融合为一个自定义内核,单层耗时降低 35%,整体端到端延迟下降 12%。
结构化剪枝与模型瘦身
阶段:模型结构优化
解决的问题:人工判断哪些通道冗余效率很低,且容易出错。FX 捕获的图提供了统一的分析入口:每个节点的输出通道数、消费者列表都清晰可见,可以程序化地移除对精度贡献小的通道,并自动重编译模型。
具体案例:某检测团队对 YOLO 模型做通道剪枝,编写 Pass 遍历图节点、按 BN 缩放因子排序裁剪通道,最终在精度损失 1% 以内削减了 50% 的 FLOPs,模型体积缩小近一半。
Python 演示
import torchimport torch.nn as nnimport torch.fx as fx class Net(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(4, 4) self.act = nn.ReLU() def forward(self, x): h = self.fc(x) h = self.act(h) return h * 2 def main(): model = Net() # 1. 捕获计算图 traced = fx.symbolic_trace(model) print("=== 捕获的计算图 ===") print(traced.graph) print("\n=== 生成的 Python 代码 ===") print(traced.code) # 2. 在图上做变换: 把乘 2 改成乘 3 graph = traced.graph for node in graph.nodes: if node.op == "call_function" and node.target is torch.mul: if node.args[1] == 2: node.update_arg(1, 3) traced.recompile() graph.lint() print("\n=== 变换后的计算图 ===") print(traced.graph) print("\n=== 变换后的 Python 代码 ===") print(traced.code) # 3. 验证图变换确实改变了模型行为 x = torch.randn(2, 4) with torch.no_grad(): a = model(x) b = traced(x) print("\n最大输出差异:", (a - b).abs().max().item()) if __name__ == "__main__": main()
traced = fx.symbolic_trace(model)用 Tracer 捕获模型,得到持有计算图的 GraphModule,相当于记录员开始写菜谱print(traced.graph)打印捕获到的节点列表,检查“菜谱”内容node.op == "call_function" and node.target is torch.mul定位图中“乘2”这一步node.args[1] == 2确认乘法操作的第二个参数是常量 2node.update_arg(1, 3)像改菜谱一样把“乘2”改成“乘3”traced.recompile()重新编译图,让修改后的代码可执行graph.lint()检查图结构合法性,确保步骤没有断链(a - b).abs().max()用输入对比验证变换确实改变了模型行为
常见误区
误区我以为 symbolic_trace 会真的执行一次前向传播,把运行日志抓下来变成图。毕竟名字里带“trace“,听起来就像”跟踪日志”。
正解恰恰相反,FX 捕获是用 Proxy 代替 Tensor 做符号执行。记录的只是操作本身,不会产生任何真实数值计算。就像记录员拿模拟锅铲比划动作,锅里的菜其实纹丝未动。两者最直观的区别是:真实执行会得到具体输出值,而符号追踪只得到一张图。
为何常见:“捕获”和“追踪”这类词容易让人联想到运行时日志或性能剖析,加上调用时确实需要传入模型和一个“示例输入”,很容易误以为发生了真实计算。
误区我写了一个在 eager 模式下运行完美的模型,里面有 `if x.sum() > 0:` 这种动态分支,以为 FX 也能照样捕获。结果一跑就报错,完全摸不着头脑。
正解eager 能跑不等于能被追踪。动态控制流依赖张量的真实数值,而追踪阶段张量是 Proxy 没有值,所以条件无法求值。这就像记录员没法记录大厨“看心情放盐”——他不知道当时的味道。遇到这种情况,要么改写模型为静态路径,要么用 torch.fx.wrap 把动态逻辑包成叶子节点。
为何常见:初学者默认“能执行的代码就能被分析”,忽略了符号执行与真实执行在信息完备性上的本质差异。
误区我一直觉得 graph 只是原模型的一份“复写”,所以 traced 模型跑出来的结果应该和原模型逐位相同。
正解符号追踪只保证结构对应,不保证数值逐位一致。常量折叠、算子融合、重排求和顺序等变换都可能带来浮点差异;如果变换本身就是有意的行为修改(比如把乘2改成乘3),差异会更大。所以工程上必须用容差对比验证,这一环不能省。
为何常见:把“图”误解为“执行记录”,实际上图是模型逻辑的静态表示,任何变换都可能改变它的行为。
误区我看到的 FX 教程都在讲推理优化,所以认为 FX 是部署工程师的工具,跟训练没关系。
正解FX 在训练侧同样大有可为。量化感知训练需要把 FakeQuantize 节点插入图中并做反向传播,图模式能让插入位置精确可控;结构化剪枝也常在训练前或训练中基于图结构分析通道重要性。GraphModule 本身仍然是一个可训练的 nn.Module。
为何常见:FX 最出圈的案例确实是推理引擎优化,容易让人忽略它作为“可编程图结构”在训练侧同样适用。
误区我以为在 graph 上增删节点后,要手动把新逻辑翻译成 forward 函数,否则模型没法跑。
正解GraphModule 把图与代码绑定为一体。你只需调用 recompile(),FX 就会根据当前图自动生成新的可执行 forward 代码。整个过程不需要手写一行实现代码,这正是“图作为单一事实来源”的核心设计。
为何常见:习惯了手写模型的思维定式,不了解 GraphModule 内部有“图 → 代码”的自动生成管线。
面试题
Q1. FX 的 symbolic_trace 对模型执行过程进行捕获时,主要的追踪机制是什么?
- A.用 Proxy 代替 Tensor 做符号执行← 正确答案
- B.用真实数据前向计算并记录日志
- C.通过读取模型源代码静态分析
- D.用 hook 逐层保存中间张量
答案:A。FX 的核心是利用 Proxy 代替 Tensor 做符号执行——像记录员拿着模拟锅铲比划,只记录操作不产生真实计算。真实数据前向计算是“跑日志”,读取源代码是编译器的做法,保存中间张量是 hook 的职责,三者都不是 FX 的追踪机制。
Q2. 下列 GraphModule 的方法中,能够把计算图重新生成 Python 源码的是哪个?
- A.to_code()← 正确答案
- B.forward()
- C.compile_all()
- D.graph()
答案:A。to_code() 把图结构翻译成可读的 Python 源码,相当于把草稿菜谱誊写为打印版。forward() 是执行入口,compile_all() 并非 FX 的公开 API,graph 是图对象而非代码生成函数。
Q3. 下列哪种写法在 FX 捕获过程中最可能导致失败?
- A.依赖张量数值的 if 分支← 正确答案
- B.对常量整数做加法运算
- C.调用已注册的自定义模块
- D.使用 torch.relu 激活函数
答案:A。依赖张量数值的 if 分支在追踪时无法求值,因为输入是 Proxy 而非真实数值——记录员没法判断大厨会走哪条路。常量整数加法、已注册模块和 torch.relu 都属于可追踪操作,不会导致捕获失败。
Q4. 在 FX 计算图中,表示“调用某个子模块”(如 self.fc)的节点 op 类型是?
- A.call_module 模块调用← 正确答案
- B.call_function 函数调用
- C.call_method 方法调用
- D.get_attr 属性获取
答案:A。call_module 专门表示对 nn.Module 子模块的调用,self.fc(x) 就属于这一类;call_function 对应独立函数调用如 torch.mul;call_method 对应张量方法如 x.view;get_attr 则是读取参数或缓冲区,不产生计算。
Q5. 关于 FX 捕获中的 Proxy 对象,下列说法正确的是?
- A.Proxy 上执行算子时只记录调用,不产生真实计算← 正确答案
- B.Proxy 就是开启了自动微分的 Tensor
- C.Proxy 可以脱离计算图单独执行
- D.Proxy 的存在会显著降低模型内存占用
答案:A。Proxy 是“模拟锅铲”——所有算子作用在它上面时只产生图节点记录,不计算数值。它没有真实数据所以谈不上微分和内存优化,也离不开 tracer 的图上下文单独运行。
Q6. 在图变换优化中,专门删除不影响最终输出节点的 Pass 是?
- A.死代码消除← 正确答案
- B.常量折叠
- C.算子融合
- D.梯度裁剪
答案:A。死代码消除(DCE)会删除输出不再依赖的节点,就像去掉菜谱里不影响味道的摆盘步骤。常量折叠是提前算好常量表达式,算子融合是合并多算子,梯度裁剪是训练优化技术而非图 Pass。
Q7. 下列哪种循环写法在 FX 捕获时能被正确追踪?
- A.循环次数为 Python int 的固定 for 循环← 正确答案
- B.依赖张量 shape 动态变化的变长循环
- C.依赖张量数值的 while 循环
- D.在 forward 中按随机数选择退出时机的循环
答案:A。循环次数是 Python int 的固定 for 循环,tracer 能静态展开成多个节点;依赖张量 shape 或值的循环步数无法在追踪时确定,随机选择退出时机更是不可预测——记录员不知道循环该走几遍。
Q8. 与 Eager 动态执行相比,FX 捕获得到的计算图有哪些特性?
- A.可对图做算子融合优化← 正确答案
- B.图是可序列化的数据结构← 正确答案
- C.图模式下完全无法调试
- D.图可以转换为其他 IR 格式← 正确答案
答案:ABD。图是可分析、可变换、可序列化的数据结构,可以做算子融合,也可以导出为 TorchScript、ONNX 等 IR——就像菜谱可以复印、批改、翻译。但“完全无法调试”是误区,FX 提供 print_tabular、lint、源码生成等丰富的调试手段。
Q9. 下列哪些是 FX 符号追踪的已知限制?
- A.动态控制流依赖张量值无法追踪← 正确答案
- B.非张量输入可能需要定制 Tracer← 正确答案
- C.所有 Python 内置函数都可被追踪
- D.自定义 nn.Module 一定不能追踪
答案:AB。依赖张量值的控制流无法追踪,非张量输入如 dict 需要定制 Tracer 处理,这两条是真实限制。“所有 Python 内置函数可追踪”不成立,纯 Python 逻辑会直接执行而不入图;自定义 Module 只要 forward 遵循追踪要求,通常完全可以捕获。
Q10. 关于多个图变换 Pass 的组合,下列说法正确的有?
- A.多个 Pass 按序施加等价于一次复合变换← 正确答案
- B.Pass 的不同顺序可能产生不同图结构← 正确答案
- C.所有 Pass 都保证输出数值逐位一致
- D.变换后可调用 recompile 让新图生效← 正确答案
答案:ABD。多个 Pass 按序施加等价于复合函数,且顺序不同可能得到不同结构(先融合再剪枝与先剪枝再融合结果不同)。但 Pass 不保证数值逐位一致,浮点重排可能引入微小差异,所以必须验证;recompile 能让修改后的图重新变成可执行模块。