• 【TVM源码学习笔记】3 模型编译


     在我们的模型编译运行脚本中,使用relay.build编译模型:

    1. # 设置优化级别
    2. with tvm.transform.PassContext(opt_level=3):
    3. #编译模型
    4. lib = relay.build(mod, target, params=params)

    因为在python/tvm/relay/__init__.py中有:

    from .build_module import build, create_executor, optimize

    所以这里build直接调用的是python/tvm/relay/build_module.py中的build函数。我们传入了三个参数,其中第一个mod是一个IRModule实例。excutor参数使用的是默认的graph。函数开头部分的autotvm是TVM的优化工具。所以函数开头部分我们可以先不深究,知道流程走的下面的代码即可:

    1. def build(
    2. ir_mod,
    3. target=None,
    4. target_host=None,
    5. executor=Executor("graph"),
    6. runtime=Runtime("cpp"),
    7. workspace_memory_pools=None,
    8. constant_memory_pools=None,
    9. params=None,
    10. mod_name="default",
    11. ):
    12. ...
    13. with tophub_context:
    14. bld_mod = BuildModule()
    15. graph_json, runtime_mod, params = bld_mod.build(
    16. mod=ir_mod,
    17. target=raw_targets,
    18. params=params,
    19. executor=executor,
    20. runtime=runtime,
    21. workspace_memory_pools=workspace_memory_pools,
    22. constant_memory_pools=constant_memory_pools,
    23. mod_name=mod_name,
    24. )
    25. func_metadata = bld_mod.get_function_metadata()
    26. devices = bld_mod.get_devices()
    27. lowered_ir_mods = bld_mod.get_irmodule()
    28. executor_codegen_metadata = bld_mod.get_executor_codegen_metadata()
    29. ...
    30. elif executor.name == "graph":
    31. executor_factory = _executor_factory.GraphExecutorFactoryModule(
    32. ir_mod,
    33. raw_targets,
    34. executor,
    35. graph_json,
    36. runtime_mod,
    37. mod_name,
    38. params,
    39. func_metadata,
    40. )
    41. else:
    42. assert False, "Executor " + executor + " not supported"
    43. return executor_factory

    代码中先实例化了一个BuildModule对象,然后调用BuildModule的build方法编译模型;编译完后读取编译后的模型数据,调用_executor_factory.GraphExecutorFactoryModule创建了一个执行器。

    BuildModule.build方法里面,调用的BuildModule._build,这个 _build是在BuildModule.__init__方法中赋值的:

    1. def __init__(self):
    2. self.mod = _build_module._BuildModule()
    3. self._get_graph_json = self.mod["get_graph_json"]
    4. self._get_module = self.mod["get_module"]
    5. self._build = self.mod["build"]

    self._build挂载的是_build_module.py中_BuildModule()对象的成员。_build_module.py:

    1. import tvm._ffi
    2. tvm._ffi._init_api("relay.build_module", __name__)

    我们直接搜索relay.build_module._BuildModule,可以找到接口的注册:

    1. runtime::Module RelayBuildCreate() {
    2. auto exec = make_object();
    3. return runtime::Module(exec);
    4. }
    5. TVM_REGISTER_GLOBAL("relay.build_module._BuildModule").set_body([](TVMArgs args, TVMRetValue* rv) {
    6. *rv = RelayBuildCreate();
    7. });

    这里只是创建了一个RelayBuildModule,然后包再runtime::Module里面,返回runtime::Module实例。

    python前端的BuildModule.mod["build"]获取了runtime::Module的build属性,在C++端会首先调用到runtime::Module::GetFunction方法。这个调用过程涉及到TVM的PackedFunc机制,可以参考: 

    TVM PackedFunc实现机制 | Don't Respond

    runtime::Module::GetFunction的实现:

    1. inline PackedFunc Module::GetFunction(const std::string& name, bool query_imports) {
    2. return (*this)->GetFunction(name, query_imports);
    3. }

    而 runtime::Module对取成员操作符->做了重载:

    inline ModuleNode* Module::operator->() { return static_cast(get_mutable()); }

    get_mutable是Module祖先类ObjectRef的方法,获取自己的_data字段,是对应的Object实例。而在RelayBuildCreate里面,创建Module实例的时候,传入的是一个RelayBuildModule,继承自Object。所以这里的_data就是这个RelayBuildModule,调用到的(*this)->GetFunction调用到的也就是RelayBuildModule::GetFunction:

    1. class RelayBuildModule : public runtime::ModuleNode {
    2. public:
    3. ...
    4. PackedFunc GetFunction(const std::string& name, const ObjectPtr& sptr_to_self) final {
    5. ...
    6. else if (name == "build") {
    7. return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
    8. ICHECK_EQ(args.num_args, 8);
    9. this->Build(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
    10. });
    11. ...
    12. 最终会调用到:

      1. /*!
      2. * \brief Compile a Relay IR module to runtime module.
      3. *
      4. * \param relay_module The Relay IR module.
      5. * \param params The parameters.
      6. */
      7. void BuildRelay(IRModule relay_module, const String& mod_name) {
      8. // Relay IRModule -> IRModule optimizations.
      9. //1. 对relay ir做优化,执行优化pass
      10. IRModule module = WithAttrs(
      11. relay_module, {{tvm::attr::kExecutor, executor_}, {tvm::attr::kRuntime, runtime_}});
      12. relay_module = OptimizeImpl(std::move(module));
      13. // Get the updated function and new IRModule to build.
      14. // Instead of recreating the IRModule, we should look at the differences between this and the
      15. // incoming IRModule to see if we can just pass (IRModule, Function) to the code generator.
      16. //2. 按注释看是希望对IRModule做增量编译,而不是全部重新编译
      17. Function func = Downcast(relay_module->Lookup("main"));
      18. IRModule func_module = WithAttrs(IRModule::FromExpr(func),
      19. {{tvm::attr::kExecutor, executor_},
      20. {tvm::attr::kRuntime, runtime_},
      21. {tvm::attr::kWorkspaceMemoryPools, workspace_memory_pools_},
      22. {tvm::attr::kConstantMemoryPools, constant_memory_pools_}});
      23. // Generate code for the updated function.
      24. // 3.创建执行器对应的代码生成器
      25. executor_codegen_ = MakeExecutorCodegen(executor_->name);
      26. // 4. 初始化代码生成器
      27. executor_codegen_->Init(nullptr, config_->primitive_targets);
      28. // 5. 对找到的main函数生成代码
      29. executor_codegen_->Codegen(func_module, func, mod_name);
      30. // 6. 更新输出
      31. executor_codegen_->UpdateOutput(&ret_);
      32. // 7. 获取参数
      33. ret_.params = executor_codegen_->GetParams();
      34. auto lowered_funcs = executor_codegen_->GetIRModule();
      35. // No need to build for external functions.
      36. Target ext_dev("ext_dev");
      37. if (lowered_funcs.find(ext_dev) != lowered_funcs.end()) {
      38. lowered_funcs.Set(ext_dev, IRModule());
      39. }
      40. const Target& host_target = config_->host_virtual_device->target;
      41. const runtime::PackedFunc* pf = runtime::Registry::Get("codegen.LLVMModuleCreate");
      42. // When there is no lowered_funcs due to reasons such as optimization.
      43. if (lowered_funcs.size() == 0) {
      44. if (host_target->kind->name == "llvm") {
      45. CHECK(pf != nullptr) << "Unable to create empty module for llvm without llvm codegen.";
      46. // If we can decide the target is LLVM, we then create an empty LLVM module.
      47. ret_.mod = (*pf)(host_target->str(), "empty_module");
      48. } else {
      49. // If we cannot decide the target is LLVM, we create an empty CSourceModule.
      50. // The code content is initialized with ";" to prevent complaining
      51. // from CSourceModuleNode::SaveToFile.
      52. ret_.mod = tvm::codegen::CSourceModuleCreate(";", "", Array{});
      53. }
      54. } else {
      55. // 8. 打包tir运行时
      56. ret_.mod = tvm::TIRToRuntime(lowered_funcs, host_target);
      57. }
      58. auto ext_mods = executor_codegen_->GetExternalModules();
      59. ret_.mod = tvm::codegen::CreateMetadataModule(ret_.params, ret_.mod, ext_mods, host_target,
      60. runtime_, executor_,
      61. executor_codegen_->GetExecutorCodegenMetadata());
      62. // Remove external params which were stored in metadata module.
      63. for (tvm::runtime::Module mod : ext_mods) {
      64. auto pf_var = mod.GetFunction("get_const_vars");
      65. if (pf_var != nullptr) {
      66. // 9. 删除常量
      67. Array variables = pf_var();
      68. for (size_t i = 0; i < variables.size(); i++) {
      69. auto it = ret_.params.find(variables[i].operator std::string());
      70. if (it != ret_.params.end()) {
      71. VLOG(1) << "constant '" << variables[i] << "' has been captured in external module";
      72. ret_.params.erase(it);
      73. }
      74. }
      75. }
      76. }
      77. }

      1. 执行优化pass。这里是根据我们在模型编译运行脚本中设置的优化上下文:

      1. # 设置优化级别
      2. with tvm.transform.PassContext(opt_level=3):
      3. #编译模型

      对relay ir做优化。优化后面再专门研究吧。

      2. 查找relay ir中的main函数,然后使用main函数创建一个IRModule,并设置属性。这个将是后面会编译的IRModule。按注释这里是实现了增量编译的,怎么实现的呢?

      3. 生成执行器代码生成器类实例。我们在编译时没有传入执行器参数,直接使用了默认的graph执行器,得到的将是relay.build_module._GraphExecutorCodegen对应的类GraphExecutorCodegenModule,定义在src/relay/backend/graph_executor_codegen.cc中。这个类是在代码生成器的基础上做了一层包装。真正代码生成器是它的codegen_成员;

      4. 执行GraphExecutorCodegenModule的初始化函数,这里会生成代码生成器,赋给codegen_,类型是GraphExecutorCodegen

      5. 代码生成。这里是将relay ir 低级化为 tir形式。后面专门讨论;

      6. UpdataOuput只是重新生成了编译得到的json文件;

      7. 获取生成的模型tir输出参数。这里都是一些常量值的名字;

      8. 根据设置的模型运行目标(target)和当前的host,分别创建两者上运行的模块;

      9. 删除常量参数。为什么?

    13. 相关阅读:
      软件测试员必看!数据库知识mysql查询语句大全
      Spring Boot自动装配原理
      MATLAB算法实战应用案例精讲-【深度学习】CBAM注意力机制
      国内“惨淡”,国外“飞腾”,腾讯将增持育碧,力争成为最大股东
      DW大学生网页作业制作设计 中华饮食文化(HTML+CSS+JavaScript) Web前端大作业
      C#之反射
      【大数据 | 综合实践】大数据技术基础综合项目 - 基于GitHub API的数据采集与分析平台
      PTA_1164 Good in C_模拟
      Java并发编程:start和run的区别
      rosnode ping指令
    14. 原文地址:https://blog.csdn.net/zx_ros/article/details/125983983