TVM Relay 后端深度解析:te_compiler、解释器、图执行器与虚拟机(tvm.relay.backend 全模块指南)
2026/9/24 17:23:42 网站建设 项目流程
  • 编译器
  • 深度学习
  • 模型优化

【免费下载链接】tvm

Open deep learning compiler stack for cpu, gpu and specialized accelerators

项目地址:https://gitcode.com/gh_mirrors/tvm7/tvm
点击查看免费下载

导读

tvm.relay.backend是 Apache TVM 中承接「Relay 前端 IR」与「后端运行时」的关键桥梁模块,它负责把经过算子融合与优化后的 Relay 函数,降级(lower)为可直接在 CPU/GPU/专用加速器上执行的底层实现。本篇指南以 TVM 仓库中的官方 API 文档 backend.rst 为骨架,深入其引用的四个核心子模块——interpreterte_compilergraph_executor_codegenvm,并结合 python/tvm/relay/backend/ 与 src/relay/backend/ 下的真实源码,逐一讲解 TE 编译器缓存机制、算子实现选择策略、参考解释器、图执行器代码生成和 Relay 虚拟机的完整调用链。读完本文,你将能理解relay.build从 Relay 模块到可运行运行时模块的完整降级路径,并掌握relay.create_executorTECompilerVMCompiler等关键 API 的实际用法。

一、模块总览:backend 包导出什么

在 python/tvm/relay/backend/init.py 中,tvm.relay.backend包只做了三件事:

"""Backend codegen modules for relay.""" from . import te_compiler from .executor import Executor from .runtime import Runtime
  • 导入te_compiler子模块(TE 编译器引擎);
  • 导出Executor(执行器配置类,见 executor.py);
  • 导出Runtime(运行时配置类,见 runtime.py)。

这两个配置类是后续relay.build阶段控制"用哪种执行器(executor)、跑在哪个运行时(runtime)"的核心句柄:

构造方式注册表名关键方法
Executor(name, options)tvm.relay.backend.Executor("graph", {"link-params": 1})"executor"list_registered()list_registered_options()
Runtime(name, options)tvm.relay.backend.Runtime("cpp")"runtime"list_registered()list_registered_options()

Executor.__getitem__/Runtime.__getitem__可用于读取对应配置项;底层通过_backend.CreateExecutor/_backend.CreateRuntime(C++ FFI)注册对象,真正的可选项由 C++ 侧src/relay/backend/executor.ccsrc/relay/backend/runtime.cc维护。

二、tvm.relay.backend.interpreter:Relay 参考解释器

文档的automodule:: tvm.relay.backend.interpreter对应 interpreter.py,其模块 docstring 明确写它是 "The Python interface to the Relay reference interpreter"。

2.1 抽象基类Executor

Executor是执行 Relay 程序的抽象接口,定义了三个方法:

  • _convert_args(expr, args, kwargs):把「位置参数 + 关键字参数」合并为与 Relay 函数形参顺序一致的参数序列。规则是:先依次放置位置参数,再用关键字参数补齐剩余形参;若某个形参既被位置参数占用又出现在 kwargs 中,会抛出 "duplicate argument supplied" 异常;参数不足则抛 "insufficient arguments"。
  • _make_executor(expr=None):构造一个实现了expr行为的 Python 可调用对象,子类必须实现。
  • evaluate(expr=None, binds=None):评估 Relay 表达式。binds允许为自由变量提供额外绑定(内部通过ScopeBuilder转成let绑定);当exprFunctionGlobalVar时直接构造执行器,普通表达式则会包装成无参函数执行。若expr为 None,则默认取模块中的main函数。
executor = relay.create_executor(kind="debug", mod=module) func = executor.evaluate(expr) # 复用同一个可调用对象 a = func(args1) b = func(args2)

2.2 解释器实现Interpreter

Interpreter(Executor)的构造参数为mod(tvm.IRModule)、device(运行时设备)、target(构建目标,仅支持同构执行)。其_make_executor通过 FFI 调用_backend.EvalFunction(self.mod, expr, self.device, self.target)把表达式编译为一个可重复应用的 packed function,随后把 Python 侧的 numpy 数组 / NDArray / ADT / 元组等参数通过_arg_to_ast反射回 Relay AST(ConstantTupleRefCreateCallconst),再调用 packed function 求值。

需要特别注意的是 docstring 中的 CAUTION:解释器在每次evaluate调用时才准备模块,而非在create_executor时一次性准备,因此若用同一 executor 多次求值不同参数,模块绑定会被重复处理。最佳实践是像上文示例那样把evaluate的结果保存为函数复用,而不是每轮都调用evaluate

2.3 工厂函数relay.create_executor

解释器通常不是直接实例化的,而是通过 build_module.py 中的relay.create_executor工厂创建:

def create_executor(kind="debug", mod=None, device=None, target="llvm", params=None):
  • kind:执行器类型,可选debug(解释器)、graph(图执行器)、aot(AOT 执行器)、vm(虚拟机);
  • modtvm.IRModule,缺省为空模块;
  • device:执行设备,缺省时从第一个 target 推导默认设备;
  • target:支持多目标(heterogeneous),但文档明确提示该 API 不允许多设备,因此异构编译尚未支持;
  • params:推理期不变的常量参数,会通过bind_params_by_name绑定进main

官方 docstring 自带一个最小可运行示例:构造x + 1的 Relay 表达式后用kind="vm"求值,输入[2]返回[3.]

三、tvm.relay.backend.te_compiler:TE 编译引擎

文档的automodule:: tvm.relay.backend.te_compiler对应 te_compiler.py,模块 docstring 即 "TE compiler engine (replacing legacy compile_engine)"——它是取代旧compile_engine的新一代编译引擎。

3.1 核心数据结构

  • LoweredOutput:承载outputs(te.Tensor 列表)与implement(OpImplementation)两个字段,是一次算子降级的输出结果。
  • CCacheKey:编译缓存的键,由source_func(relay.Function)与target(tvm.Target)组成。
  • CCacheValue:编译缓存的值,"including usage statistics"(含使用统计),用于后续缓存淘汰策略。

3.2 算子实现选择:get_valid_implementationsselect_implementation

这是 TE 编译引擎最核心的决策逻辑:

  1. get_valid_implementations(op, attrs, inputs, out_type, target)读取算子注册的FTVMStrategy(可用tvm.relay.op.register_strategy注册),在 target 上下文中调用 strategy 得到OpStrategy,遍历其specializations,用tvm.arith.Analyzercanonical_simplify化简各 specialization 的条件子句,筛选出全部有效实现。
  2. select_implementation(op, attrs, inputs, out_type, target, use_autotvm=True)在上面的基础上挑选最优实现:
    • use_autotvm=False(或 AutoTVM / AutoScheduler / MetaSchedule 已启用),直接选择plevel最高的实现;
    • use_autotvm=True,先对每个可调优实现计算 AutoTVM workload,查询DispatchContext得到配置与代价,选代价最低的实现;找不到调优记录时退回最高plevel实现,并打印提示 "One or more operators have not been tuned. Please tune your model for better performance."(可通过 DEBUG 日志查看细节);
    • 找不到任何有效实现时抛出RuntimeError(f"No valid {op} implementations for {target}")

从源码结构看,plevel(优先级)机制是 TVM 为同一算子注册多种后端实现(如 CUDA 的 cublas、cutlass 与 generic 实现)时做默认排序的依据。

3.3 动态形状处理

lower_call(注册为relay.backend.lower_call)在把 Call 表达式降级前,会检查输入输出类型是否动态(_ty.is_dynamic);对动态形状的调用,会选择use_autotvm=False直接取最高优先级实现,因为当前 TVM 尚未允许为动态形状自动生成多个 kernel(源码中保留 TODO 注释)。同时get_shape负责把IntImm维度按INDEX_DEFAULT_I64编译开关转换为 int64 或 int32,把tvm.tir.Any(动态维度)转为te.size_var("any_dim", "int32")

3.4TECompiler类与全局单例

class TECompiler(Object): def lower(self, source_func, target=None, mod_name="default"): ... def jit(self, source_func, target=None): ... def clear(self): ... def items(self): ...
  • lower:把source_func(relay.Function 或 CCacheKey)降级为CachedFuncmod_name会先经过mangle_module_name修正;出错时会把函数 AST(astext(show_meta_data=False))拼进异常信息方便定位。
  • jit:即时编译为tvm.runtime.PackedFunc
  • clear:清空缓存。
  • items:列出缓存中的所有(CCacheKey, CCacheValue)对。
  • get():返回全局 TE 编译器单例(_backend._TECompilerGlobal())。

此外模块还提供lower_to_primfunc(relay_func, target),调用全局函数relay.backend.LowerToPrimFunc把融合后的 Relay 原始函数直接转为tir.PrimFunc——这一步正是"Relay → TIR"的关键跳转。

C++ 侧实现位于 src/relay/backend/te_compiler.cc、src/relay/backend/te_compiler_cache.cc(缓存)与 src/relay/backend/task_extraction.cc(AutoTVM 任务抽取)。

四、tvm.relay.backend.graph_executor_codegen:图执行器代码生成

文档的automodule:: tvm.relay.backend.graph_executor_codegen对应 graph_executor_codegen.py。模块 docstring 把编译流程概括为三步:

  1. 单个 Relay 表达式(要求是函数)编译为图语言程序:函数形参对应计算图中的 placeholder/输入与模型参数,函数体即计算图本身;
  2. 图语言由NodeNodeRefInputNodeOpNode构成,表示 TVM 图格式的程序;
  3. 通过一个 printer 把图格式转成JSON 字符串,该字符串可被contrib.graph_executor或其他兼容 TVM runtime 的系统加载。

GraphExecutorCodegen(mod, target)codegen(ir_module, func)方法返回三元组:

  • graph_json : str—— 供运行时消费的图 JSON;
  • mod : IRModule—— 降级后的函数集合;
  • params : Dict[str, tvm.nd.NDArray]—— 额外的常量参数(通过_list_params_name_get_param_by_name从 C++ 侧取回并 copy 到新数组)。

底层 C++ 实现在 src/relay/backend/graph_executor_codegen.cc,内存规划依赖 src/relay/backend/graph_plan_memory.cc(静态内存池分配);_GraphExecutorCodegenFFI 对象则由 python/tvm/relay/_build_module.py 暴露。这一路径是传统relay.build+tvm.contrib.graph_executor部署方案(含 Android/iOS 等移动端)的基石。

五、tvm.relay.backend.vm:Relay 虚拟机

文档的automodule:: tvm.relay.backend.vm对应 vm.py,为 Relay VM 提供 Python 接口,包含编译与执行两部分。

5.1 便捷函数compile

def compile(mod, target=None, target_host=None, params=None): compiler = VMCompiler() if params: compiler.set_params(params) compiler.lower(mod, target, target_host) compiler.codegen() return compiler.get_exec()

一次编译分四步:设置参数 →lower降级为 VM 字节码 →codegen生成 kernel 库 → 取回tvm.runtime.vm.Executable(同时包含库代码与字节码)。

5.2VMCompiler

  • set_params(params)/get_params():设置/取回常量参数。set_params会把 numpy 数组转为NDArray再包成relay.constget_params返回{name: ndarray}字典。
  • lower(mod, target, target_host):通过Target.canon_multi_target_and_host规范化目标后调用 C++_VMCompiler["lower"]。注意它会先根据当前DispatchContext判断是否处于 fallback(根)上下文——若是则进入autotvm.tophub.context(raw_targets),即自动从TopHub拉取预调优参数;否则使用EmptyContext。这就是 VM 编译默认能吃到社区调优结果的原因。
  • codegen():生成 kernel 库。
  • optimize(mod, target, target_host, params):仅优化(返回优化后的IRModule与参数),不产出可执行文件,适合调试或查看优化结果。
  • get_exec():返回tvm.runtime.vm.Executable

target_host参数的语义:编译 CUDA 等设备程序时,还需要 CPU 侧的宿主代码与驱动交互(设置维度与参数),target_host即宿主代码生成目标;默认优先用llvm,未启用时退回 stackvm 解释器。

5.3VMExecutor

VMExecutor(Executor)把 VM 适配进统一的执行器接口:_make_executor中用self.mod["main"] = expr替换 main 后调用compile得到可执行文件,实例化tvm.runtime.vm.VirtualMachine,最终返回的包装函数把 Python 参数经过_convert_args转换后直接self.vm.run(*args)。它特别适合做实验与调试(docstring 也说明 VM 可直接通过tvm.runtime.vm使用)。

C++ 侧编译实现在 src/relay/backend/vm/compiler.cc,配套的 VM 专用 pass 包括lambda_lift.cc(闭包转换)、manifest_lifetimes.cc(生命周期分析)与removed_unused_funcs.cc(无用函数移除)。

六、后端降级全景与验证路径

综合以上四个子模块,一次典型的relay.build后端流程可以归纳为:

Relay IRModule │ pass 优化(算子融合 FuseOps 等) ▼ 融合后的原始函数(primitive function) │ te_compiler.lower_call / select_implementation(strategy + plevel + AutoTVM) ▼ te.Tensor 计算 + schedule → LowerToPrimFunc │ TECompilerCache(CCacheKey → CachedFunc) ▼ TIR PrimFunc → 各目标代码生成(LLVM/CUDA/OpenCL/...) │ graph_executor_codegen 或 vm/compiler 或 aot_executor_codegen ▼ graph JSON + lowered func + params → 运行时模块

在仓库中验证这条链路最直接的方式是运行测试:tests/python/relay/下存在大量覆盖 interpreter、VM、graph executor 与 TECompiler 的用例,例如通过python -m pytest tests/python/relay/test_vm.py -k "compile"可验证 VM 编译路径。对希望深入 C++ 实现的读者,推荐按以下顺序阅读:

  • src/relay/backend/te_compiler.cc + src/relay/backend/te_compiler_cache.cc:降级主流程与缓存;
  • src/relay/backend/graph_executor_codegen.cc + src/relay/backend/graph_plan_memory.cc:图 JSON 生成与静态内存规划;
  • src/relay/backend/interpreter.cc:参考解释器求值;
  • src/relay/backend/vm/compiler.cc:VM 字节码编译。

七、小结:如何选择后端执行路径

执行路径适用场景核心 API
debug(Interpreter)原型验证、逐表达式调试,不追求性能relay.create_executor(kind="debug")
graph(GraphExecutor)静态形状、传统部署(含移动端)relay.build+tvm.contrib.graph_executor
vm(VirtualMachine)动态形状、函数调用/控制流丰富的模型relay.vm.compile/VMCompiler
aot(AOT Executor)无操作系统嵌入式/MCU 场景,编译期规划内存relay.build(..., executor=Executor("aot"))

tvm.relay.backend的设计核心在于解耦:前端策略(FTVMStrategy/ plevel)决定"有哪些实现"、AutoTVM 决定"选哪个实现"、TECompiler决定"如何缓存与复用降级结果",最终由 interpreter / graph / vm / aot 四种执行器把降级产物映射到对应运行时。理解这条链路,就等于掌握了 TVM 从模型 IR 到实际推理的全部关键环节。

  • 编译器
  • 深度学习
  • 模型优化

【免费下载链接】tvm

Open deep learning compiler stack for cpu, gpu and specialized accelerators

项目地址:https://gitcode.com/gh_mirrors/tvm7/tvm
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询