假设你经营着两家截然不同的餐饮店:一家是中央厨房工厂,另一家是精致私房菜餐厅。工厂在开工前就把所有工序焊死在一条流水线上——洗菜、切配、翻炒、装盒,每一步的工位、顺序、分量都提前定好,原料进去、成品出来,效率惊人。餐厅则完全相反,客人落座才点菜,厨师看着菜单现场决定先备料还是先起锅,遇到“今天海鲜售罄”还能立刻换菜——灵活至极,但每一桌都要重新动一轮脑筋。 RELAY Executor 就是 TVM 这台“算法中央调度机”里的两位后厨总管。当你的模型被翻译成 RELAY IR(一种中间表示,相当于菜谱)后,到底该把它交给工厂流水线(图执行器 Graph Executor)还是私房餐厅(VM 执行器 Virtual Machine Executor),直接决定了推理速度、内存占用和部署灵活性。本文用这两个后厨的故事,带你搞懂两种执行器的差异、选型思路和实际配置方法。

岗位关联

掌握 RELAY Executor 的两种模式及其选型,是 AI 工程师把模型高效部署到真实硬件的必备技能——小到手机 App 的实时推理,大到服务器集群的批量服务,都离不开这个“最后一百米”的决策。

目录
  1. 可视化演示
  2. 执行器总览:两张后厨的开门生意
  3. 图执行器:一条焊死的流水线
  4. VM执行器:一间现点现做的私房厨房
  5. 静态与动态图:固定套餐和私人订制
  6. 性能对比:效率与灵活的跷跷板
  7. 部署选型:把合适的菜送进合适的厨房
  8. 配置实践:给后厨下对订单
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

在损失曲面上展示SGD、Momentum、AdaGrad三种梯度下降优化器的收敛轨迹,支持切换优化器类型与调节学习率,直观对比不同策略的收敛速度。

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

SGD:随机梯度下降 沿负梯度方向逐步更新参数,简单但收敛路径可能振荡 \u03B8 = \u03B8 - \u03B7 \u00B7 \u2207L(\u03B8) 学习率过大导致振荡,过小则收敛缓慢

执行器总览:两张后厨的开门生意

中央厨房工厂和私房菜餐厅共用同一个“菜品研发部”。研发部把菜谱标准化为 RELAY IR——一种与硬件无关的中间表示,既保留计算逻辑,又方便做算术简化、算子融合等优化。菜谱研发完成后,真正把它做成菜的后厨有两个候选:图执行器和 VM 执行器。

从技术上看,RELAY Executor 是 RELAY IR 的执行后端,负责把编译优化后的 IR 跑在 CPU、GPU 等目标硬件上。整个流程可以概括为:relay.build() 把 IR 模块编译成可执行对象,随后 Executor 在目标设备上加载并运行。两者产出的“中间物”形态不同——图执行器产出一张展开的静态计算图,VM 执行器产出一段字节码。

这里有一个贯穿全文的视角:任何推理服务的总耗时都可以拆成三段。先把这个“账单结构”刻在脑子里,后面所有选型逻辑都从它出发。

理解了这三段费用结构,选型本质就清晰了:如果每天要出十万份标准化快餐,流水线工厂(图执行器)自然划算;如果每桌客人需求都不同,私房餐厅(VM 执行器)才是出路。接下来我们分别走进这两间后厨。

Ttotal=Tcompile+Tstartup+N×Tinfer\displaystyle T_{\text{total}} = T_{\text{compile}} + T_{\text{startup}} + N \times T_{\text{infer}}
一段推理服务的总时间 = 编译时间 + 启动时间 + 推理次数 × 单次推理时间。
  1. 编译时间把 RELAY IR 翻译成目标硬件可执行代码的耗时,只在首次构建时发生一次。
    TcompileT_{\text{compile}}
  2. 启动时间创建运行时、分配内存、加载参数等初始化开销,发生在每次进程启动时。
    TstartupT_{\text{startup}}
  3. 推理总时长服务处理 N 次请求的累计推理时间,N 越大,单次推理性能越重要。
    N×TinferN \times T_{\text{infer}}

图执行器:一条焊死的流水线

走进中央厨房工厂,你会看到一条壮观的生产线:工位一字排开,传送带把原料从第一个工位送到最后一个工位,每个工位只做一件事,顺序永不改变。图执行器(Graph Executor)就是这条流水线。它在编译期拿到完整的 RELAY IR 计算图后,一口气完成三件事:把图展开成有序的算子序列、为每个中间张量规划好内存地址、确定算子的执行顺序。此后每次推理,数据就像传送带上的原料,按预排路线一路走到底。

流水线的核心竞争力是“确定性”。因为执行顺序和内存布局在编译期就固定了,图执行器可以精确复用内存——两个生命周期不重叠的中间张量可以共享同一块内存,这在工程上称为“静态内存规划”。

流水线的代价同样明显:只要有一个环节被要求“临时改动”,整条线就得停下来重新铺设。对应到模型上,如果输入尺寸不固定,或者模型里有条件分支、循环,图执行器就难以应对。所以它最擅长伺候那些“结构稳定、按部就班”的静态模型。

Gstatic=lower(GIR)={op1,op2,…,opn}\displaystyle G_{\text{static}} = \text{lower}(G_{\text{IR}}) = \{ \text{op}_1, \text{op}_2, \ldots, \text{op}_n \}
图执行器把 IR 计算图“压平”成一个固定顺序的算子集合,每个算子按编号依次执行。
  1. 下译lower 把高层 IR 节点逐个翻译成目标硬件上的底层算子调用,这是编译期的核心动作。
    lower(GIR)\text{lower}(G_{\text{IR}})
  2. 算子序列翻译结果是有严格先后顺序的算子集合,顺序由拓扑排序确定,运行时不改变。
    op1,op2,…,opn\text{op}_1, \text{op}_2, \ldots, \text{op}_n

VM执行器:一间现点现做的私房厨房

离工厂三条街,私房菜餐厅的厨房完全是另一番景象。没有传送带,只有一位拿着菜单的厨师:菜单上写着一条条指令(字节码),厨师手里捏着一个“当前步骤”指针(程序计数器),读到“备料”就备料,读到“起锅”就起锅,读到“如果客人不吃辣,跳到第 7 步”就果断跳转。VM 执行器(VM Executor)正是这样一位厨师。

VM 执行器把 RELAY IR 编译成一段紧凑的字节码,运行时通过程序计数器逐条取指令、解释、执行。因为每个指令都可以自由跳转,VM 天然支持条件分支、循环、动态 shape 这类需要运行时决策的行为。这也是它逼近图执行器“灵活性”的底气。

当然,现点现做意味着每一步都要“看一眼菜单再动手”,比流水线上的机械动作多了一层解释开销。厨师再灵活,也快不过一台不会思考的传送带——这正是两种执行器在性能上的根本张力。

Pt+1=pc(Pt),pc:{0,1,…,m}→{0,1,…,m}\displaystyle P_{t+1} = \text{pc}(P_t), \quad \text{pc}: \{0, 1, \ldots, m\} \to \{0, 1, \ldots, m\}
程序计数器每执行完一条指令,就依据字节码跳到下一条指令的位置;遇到跳转指令,pc 映射就不是简单的“加一”。
  1. 当前指令位置程序计数器 P_t 记录正在执行第几条字节码指令。
    PtP_t
  2. 跳转映射pc 函数根据当前指令内容和运行条件,决定下一条要执行的指令编号。
    pc:{0,1,…,m}→{0,1,…,m}\text{pc}: \{0, 1, \ldots, m\} \to \{0, 1, \ldots, m\}
  3. 指令推进执行完当前指令后,计数器更新为跳转目标,这就是控制流的底层机制。
    Pt+1=pc(Pt)P_{t+1} = \text{pc}(P_t)

静态与动态图:固定套餐和私人订制

去工厂点餐,你只能从固定套餐里选;去私房餐厅,你可以跟厨师说“少放盐、多加辣”。模型世界里的这道分界线,就是静态图与动态图。静态图模型的输入尺寸、计算路径在编译时全都已知——比如 ResNet 处理 224×224 的图片,路径永远是卷积、池化、全连接那一套。动态图模型则不同:输入序列可能忽长忽短,模型内部可能有循环、有提前退出的分支。

判断一个模型属于哪一类,有一条非常简单的判据:看它的计算图里是否存在“未知维度”或“运行时分叉”。只要存在,就是动态图。

这道判据直接决定了该进哪个后厨:静态图上流水线,动态图进私房厨房。值得注意的是,很多真实模型是“灰姑娘”——比如带 beam search 的翻译模型,主体是静态的,但搜索过程是动态的。这类模型在选择执行器时要格外小心,不能只看主干结构就盲目下单。

dynamic(G):⟺∃ v∈V(G), shape⁡(v)=None\displaystyle \text{dynamic}(G) :\Longleftrightarrow \exists\, v \in V(G),\ \operatorname{shape}(v) = \text{None}
一个计算图是动态的,当且仅当图中存在某个节点的形状在编译期无法确定(shape 为 None)。
  1. 存在性判断只要图中至少有一个节点满足条件,整张图就被判定为动态图。
    ∃v∈V(G)\exists v \in V(G)
  2. 未定形状shape 为 None 表示该张量的某个维度(如序列长度、batch 大小)要等运行时才知道。
    shape⁡(v)=None\operatorname{shape}(v) = \text{None}

性能对比:效率与灵活的跷跷板

工厂和餐厅谁赚得更多?答案是:看你的客流长什么样。如果只卖一款爆品、每天十万单,工厂完胜;如果客人众口难调、每桌菜都不一样,餐厅虽然慢,却是唯一能接单的店。两种执行器的性能差异,正是这架跷跷板。

先把开销公式摆出来。图执行器和 VM 执行器跑同一份模型,总耗时可以分别写成:

但性能不只是延迟。内存方面,图执行器的静态内存规划让中间张量复用率达到极致;VM 虽然内存占用稍高,却能秒级切换不同模型——因为字节码加载比重编译快得多。所以说,“快”要分场景:延迟敏感、固定结构的服务选图执行器;需要热切换、动态行为的服务,VM 的“灵活”本身就是一种性能。

\displaystyle T_{\text{graph}} = T_{\text{init}} + N \times t_g,\qquadT_{\text{vm}} = (T_{\text{init}} + T_{\text{load}}) + N \times (t_v + t_d)
图执行器只需一次性初始化,之后每次推理都是“直接跑预排好的算子”;VM 执行器每次推理还要经过字节码解释,多出 t_d 的额外开销。
  1. 图执行器总耗时初始化一次后,N 次推理每步都是纯算子执行,单次耗时最低。
    Tgraph=Tinit+N×tgT_{\text{graph}} = T_{\text{init}} + N \times t_g
  2. VM 总耗时除初始化外,还要加载字节码,且每次推理都叠加解释执行的开销。
    Tvm=(Tinit+Tload)+N×(tv+td)T_{\text{vm}} = (T_{\text{init}} + T_{\text{load}}) + N \times (t_v + t_d)
  3. 单步开销对比同一算子在图中是直接调用,在 VM 中要先解码指令再调用,因此 t_g 更小。
    tg<tv+tdt_g < t_v + t_d

部署选型:把合适的菜送进合适的厨房

假设你是两家店的“菜品调度总监”,手里有一批模型要上菜,怎么分派?原则只有一个:让结构性强的模型走流水线,让需求多变的模型进餐厅。具体到工程场景——

服务端批量推理,典型的做法是把固定 batch 的图像分类模型编译成图执行器,它能在 GPU 上把流水线排到最满。边缘端的智能摄像头,模型结构固定、内存紧张,图执行器的内存复用优势极其宝贵。反过来,自然语言处理里的动态序列模型、含 while 循环的控制流模型,以及需要频繁在多个模型间切换的推荐系统,VM 执行器才是正解。

可以用一个加权决策式来概括这个选择过程:

现实中,λ 的取值往往不难:模型结构固定、延迟敏感,就把 λ 压到接近 1,选图执行器;模型动态、需要频繁切换,就把 λ 放低,选 VM 执行器。记住这个跷跷板,你就抓住了选型的灵魂。

E∗=arg⁡min⁡E∈{Graph,VM}(λ⋅TE+(1−λ)⋅FE)\displaystyle E^* = \arg\min_{E \in \{\text{Graph}, \text{VM}\}} \big( \lambda \cdot T_E + (1 - \lambda) \cdot F_E \big)
选型就是给“延迟 T”和“灵活性 F”分别设定权重 λ 和 1-λ,取综合代价最小的执行器;偏重性能时 λ 大,偏重灵活时 λ 小。
  1. 候选集合可选方案只有图执行器和 VM 执行器两种,不存在第三种万能选项。
    E∈{Graph,VM}E \in \{\text{Graph}, \text{VM}\}
  2. 加权代价延迟和灵活性按权重合并成一个可比较的标量,λ 由业务场景决定。
    λ⋅TE+(1−λ)⋅FE\lambda \cdot T_E + (1 - \lambda) \cdot F_E
  3. 最优选择argmin 表示在所有候选中挑出总代价最小的那个执行器。
    E∗=arg⁡min⁡E^* = \arg\min

配置实践:给后厨下对订单

选定了后厨,接下来要做的就是给后厨“下准确订单”——在 TVM 代码里把执行器类型告诉编译器。这一步很机械,但细节决定成败。核心是 relay.build 的参数配置:

工程上有一个经典坑:使用 VM 执行器时,编译成功后不能直接跑,必须额外调用 vm.initialize() 完成内存池和依赖库的初始化;而图执行器则用 graph_executor.create() 直接绑定模块。跳过 initialize 往往报出难以排查的 shape 错误。调试建议是先以 opt_level=0 跑通正确性,再逐步提升优化等级。最后记得用 time_evaluator 或 profiler 实测推理耗时——纸上谈兵的选型,永远不如一次真实压测来得可靠。

executor∈{graph,vm},opt_level∈[0,3]\displaystyle \text{executor} \in \{ \text{graph}, \text{vm} \},\quad \text{opt\_level} \in [0, 3]
executor 参数只有两个合法取值:graph 和 vm;优化等级 opt_level 则从 0 到 3 逐级增强编译优化强度。
  1. 执行器取值executor 是 relay.build 的一个字符串参数,决定产出图模块还是可执行字节码。
    executor∈{graph,vm}\text{executor} \in \{\text{graph}, \text{vm}\}
  2. 优化等级opt_level 控制算子融合、常量折叠等优化措施的激进程度,0 最保守,3 最激进。
    opt_level∈[0,3]\text{opt\_level} \in [0, 3]

AI 应用场景

场景一:边缘端智能相机的固定结构推理

阶段:模型部署与推理优化

解决的问题:边缘设备内存小、算力弱,传统 PyTorch 运行时动态分配内存、解释执行开销大,跑不动实时检测模型。图执行器在编译期完成内存规划和算子融合,把模型压成一条“固定流水线”,推理延迟可降低 30%-50%。

具体案例:某智慧零售项目在 Jetson Nano 上部署 YOLOv5s 检测模型。模型结构固定、batch=1,工程师用 relay.build(..., executor="graph") 编译出静态图模块,再用 graph_executor.create 加载,实测单帧推理从 120ms 降到 60ms,内存占用减少约 40%。

场景二:NLP 服务端的动态序列推理

阶段:动态模型部署

解决的问题:机器翻译、文本生成模型输入长度不固定,且 beam search 需要循环控制流。图执行器面对这种“客人随时点菜”的场景会直接哑火;VM 执行器的字节码跳转机制能优雅处理动态 shape 和循环。

具体案例:某智能客服系统部署 BART 摘要模型,输入序列长度从 20 到 512 不等,且解码阶段有 while 循环。使用 executor="vm" 编译后,程序计数器在字节码间自由跳转,无需为每种长度重新编译,首 Token 延迟降低 200ms 以上。

场景三:多模型推荐系统的热切换

阶段:多模型服务编排

解决的问题:推荐系统经常要按用户画像在多个模型间切换,传统方式每次切换都要重新编译图,耗时数秒。VM 执行器加载字节码仅需毫秒级,适合“后厨随时换菜单”。

具体案例:某广告推荐平台有 5 个候选模型,按流量特征动态切换。使用 VM 执行器后,模型热切换时间从 3 秒降到 30ms,SLA 达标率显著提升。

Python 演示

python61 行
"""演示图执行器与VM执行器的执行方式差异."""import time  class GraphExecutor:    """模拟图执行器 固定算子序列 顺序执行"""     def __init__(self, ops):        self.ops = list(ops)        print("[Graph] 编译期: 图结构固定 算子序列已确定")     def run(self, x):        for op in self.ops:            x = op(x)        return x  class VMExecutor:    """模拟VM执行器 字节码解释执行 支持动态分支"""     def __init__(self, bytecode):        self.code = bytecode        print("[VM] 编译期: 只生成字节码 执行期逐条解释")     def run(self, x, cond):        pc = 0        while pc < len(self.code):            instr = self.code[pc]            if instr == "push":                x = x + 1                pc += 1            elif instr == "jif_true":                pc = pc + 1 if cond else pc + 2            elif instr == "mul":                x = x * 2                pc += 1            elif instr == "sub":                x = x - 1                pc += 1            elif instr == "out":                return x        return x  def main():    ops = [lambda v: v + 1, lambda v: v * 2, lambda v: v - 1]    graph = GraphExecutor(ops)    t0 = time.perf_counter()    out = graph.run(10)    print("[Graph] 输出:", out, "耗时 %.6f 秒" % (time.perf_counter() - t0))     code = ["push", "jif_true", "mul", "sub", "out"]    vm = VMExecutor(code)    for cond in (True, False):        t0 = time.perf_counter()        out = vm.run(10, cond)        print("[VM] cond=%s 输出:" % cond, out, "耗时 %.6f 秒" % (time.perf_counter() - t0))  if __name__ == "__main__":    main()
目的:用两个简化类模拟图执行器与 VM 执行器的执行差异——前者按编译期固定的算子序列顺序执行,后者用程序计数器在字节码间跳转,条件不同会走不同分支。
  1. self.ops = list(ops)把预编译好的算子序列存下来,模拟图执行器在编译期“焊死”流水线。
  2. for op in self.ops:图执行器的核心循环:按固定顺序逐个调用算子,没有运行时决策。
  3. self.code = bytecodeVM 保存的是字节码指令列表,而非展开的算子序列。
  4. pc = 0程序计数器从第一条指令开始,这是 VM 执行器的“当前步骤指针”。
  5. instr = self.code[pc]每次循环先取当前指令,对应“看一眼菜单再动手”。
  6. pc = pc + 1 if cond else pc + 2条件跳转指令的精髓:条件为真走乘 2 分支,为假跳过它走减 1 分支。

常见误区

误区我听说图执行器做了编译期优化,所以不管什么模型,选图执行器一定比 VM 快,选 VM 就是牺牲性能。

正解在结构固定的静态模型上,图执行器确实更快;但如果模型带动态 shape 或控制流,图执行器要么不支持,要么只能退化成“每来一个新 shape 就重新编译一次”,这时反而比 VM 慢几十倍。就像用固定模具做菜,客人要的菜超出模具范围,你还得重新开模,不如厨师现场炒。

为何常见:入门例程大多是静态 CNN,图执行器的优势被放大,动态场景下的劣势很少被提及。

误区VM 执行器是为了动态图设计的,静态模型用 VM 纯属浪费,肯定不划算。

正解VM 执行器同样能跑静态模型,而且在模型热切换、需要运行时调试、跨版本部署等场景下,VM 的字节码加载快、行为可控,反而更合适。选用哪个执行器不仅要看模型是否动态,还要看整个部署流程的需求。好比餐厅也能做固定套餐,只是价格贵一点,但你获得了随时换菜单的自由。

为何常见:把“动态模型需要 VM”错误理解为“VM 只能用于动态模型”,逻辑上倒置了。

误区我的模型没有 if/else 分支,结构从来不变,那它肯定是静态图,可以放心用图执行器。

正解静态图的定义不仅看分支结构,更关键的是所有张量 shape 在编译期是否完全确定。很多 NLP 模型结构固定,但输入序列长度可变,导致中间张量 shape 带 None,仍然是动态图。图执行器遇到这种情况会要求你填一个固定 shape,填了浪费计算,不填直接报错。就像工厂接单时,你不告诉它盒子尺寸,它没法决定开哪条线。

为何常见:初学时期把“动态”简单等同于“有控制流”,忽略了 shape 动态性这一更常见的触发条件。

误区relay.build 里 executor 参数填 graph 和 vm 效果一样,反正最后出来的模型都能推理。

正解两者产物完全不同:graph 产出“图模块 + 参数”,vm 产出“字节码 + 参数 + 虚拟机运行时”。用错了会导致调用接口对不上——比如拿 graph 的用法去调 vm 模块,会报“找不到 initialize”。选型必须在 build 之前定好,就像下订单时就得选工厂还是餐厅,菜都出锅了再换后厨就晚了。

为何常见:教程里常常只用一种执行器演示,让人误以为 executor 参数只是一个无关紧要的开关。

面试题

Q1. RELAY Executor 的两种执行模式是什么?

单选题 · easy

  • A.CPU 执行器和 GPU 执行器
  • B.图执行器和 VM 执行器← 正确答案
  • C.同步执行器和异步执行器
  • D.推理执行器和训练执行器

答案:B。RELAY Executor 只有图执行器(Graph Executor)和 VM 执行器(Virtual Machine Executor)两种模式。A、C、D 描述的是硬件或运行机制的划分,不是 RELAY Executor 的执行后端模式。

Q2. 以下哪类模型最适合选择图执行器?

单选题 · easy

  • A.结构固定的静态模型← 正确答案
  • B.含大量控制分支的模型
  • C.输入长度不定的模型
  • D.需要热切换的模型

答案:A。图执行器在编译期固定算子顺序和内存布局,最适合“结构稳定、按部就班”的静态模型。控制分支、动态输入长度和热切换都是动态特征,更适合 VM 执行器。

Q3. VM 执行器相比图执行器的核心优势是什么?

单选题 · mid

  • A.内存占用方面总是更小
  • B.支持动态shape分支← 正确答案
  • C.启动速度方面必然更快
  • D.算子融合上比图更彻底

答案:B。VM 执行器以字节码加程序计数器驱动执行,能自由跳转,天然支持动态 shape 和控制流。内存、启动速度、算子融合都不是 VM 的必然优势,可能因场景而异。

Q4. 图执行器在编译期主要完成哪项工作?

单选题 · mid

  • A.运行时动态创建算子
  • B.解释执行字节码指令
  • C.预分配内存和固定算子顺序← 正确答案
  • D.每次推理重新规划路径

答案:C。图执行器编译期的两大核心动作是把图展开成有序算子序列、规划内存复用方案。解释字节码是 VM 的工作,动态创建算子和重新规划路径则违背了图执行器“编译期确定一切”的设计哲学。

Q5. 以下哪些属于 VM 执行器的特点?

多选题 · mid

  • A.基于字节码逐条解释执行← 正确答案
  • B.支持程序计数器自由跳转← 正确答案
  • C.适合动态控制流模型← 正确答案
  • D.一定比图执行器执行慢

答案:ABC。VM 执行器把 IR 编译成字节码,用程序计数器逐条解释,跳转指令使其适合动态控制流。但“一定更慢”是绝对化表述——在动态 shape 场景下 VM 反而可能比被迫反复重编译的图执行器更快。

Q6. 针对推理服务总耗时公式 T_total = T_compile + T_startup + N × T_infer,以下哪些策略可以降低总时间?

多选题 · hard

  • A.压缩编译流水时间开销← 正确答案
  • B.优化算子缩短每次推理← 正确答案
  • C.延长启动预热时间窗口
  • D.合并小批量以减小次数← 正确答案

答案:ABD。T_compile、T_infer 和 N 都出现在分子上,压缩编译时间、优化单次推理、合并小批量都能降低总耗时。延长启动预热只会增加 T_startup,方向相反。

Q7. 在 TVM 中编译时指定 VM 执行器的正确做法是?

单选题 · extreme

  • A.relay.compile 指定 mode
  • B.tvm.build 指定 backend
  • C.vm.build 指定 executor
  • D.relay.build 指定 executor← 正确答案

答案:D。TVM 中统一通过 relay.build 的 executor 参数指定执行器,取值为 “graph“ 或 ”vm”。relay.compile 和 tvm.build 不是标准入口,vm.build 也不存在——VM 只是 relay.build 选中后产出的模块类型。

Q8. 以下哪些部署场景更适合选择 VM 执行器?

多选题 · hard

  • A.模型需要频繁热切换服务← 正确答案
  • B.输入序列长度动态变化← 正确答案
  • C.批量大小完全恒定不动
  • D.模型含条件循环控制流← 正确答案

答案:ABD。热切换靠字节码快速加载,动态序列和控制流靠程序计数器跳转——这三个都是 VM 的舒适区。批量完全恒定的场景通常适合图执行器躺平吃满流水线。

Q9. 图执行器静态内存规划的直接好处是什么?

单选题 · mid

  • A.中间张量复用同一内存← 正确答案
  • B.运行时按需零散申请
  • C.内存占用一定为零
  • D.无需分配任何张量内存

答案:A。静态内存规划在编译期分析张量生命周期,让生命周期不重叠的中间张量共享内存,从而降低峰值占用。B 是动态分配的特征,C 和 D 是绝对化表述,实际推理不可能零内存。

Q10. 静态图与动态图最关键的区分点是什么?

单选题 · easy

  • A.结构与尺寸是否运行可变← 正确答案
  • B.模型参数的规模大小
  • C.训练数据量的多少
  • D.推理精度的要求高低

答案:A。静态图的 shape 和控制流在编译期全部确定,动态图则存在运行时可变的维度或分支。参数量、训练数据量、精度要求都与此无关。