【TVM帮助文档学习】通过示例分析TVM代码流程

2023-05-16

本文翻译自TVM Codebase Walkthrough by Example — tvm 0.9.dev0 documentation

了解TVM代码颇具挑战性,它的各组件之间的交互非常隐晦。 在本指南中,我们将通过一个简单的示例来说明模型编译过程中的关键组成。 对于每一个重要的步骤,我们都展示了它在代码库中的实现位置,以便新开发人员和感兴趣的用户更快地深入代码库。

代码结构概述

在TVM代码仓库根目录下有如下子目录,它们组成了TVM代码库:

  • src - 算子编译和部署运行时C++代码
  • src/relay - Relay(一种用于深度学习框架的函数式IR)的实现
  • python - src中C++函数和对象的Python封装前端.
  • src/topi - 标准神经网络算子的计算定义和后端调度.

按照标准的深度学习术语, src/relay是管理计算图的组件。计算图中的节点由src目录下其他子目录提供的基础设施来编译和执行。python为c++ API和驱动程序代码提供了python绑定,用户可以使用这些绑定来执行编译。各个节点对应的操作符注册在src/relay/op中。算子的实现是在topi目录下,它们是用c++或Python实现的。

当用户通过relay.build(…)编译图时,图中的各节点会执行以下一系列操作:

  • 通过查询运算符注册表来查找算子实现
  • 为算子生成计算表达式和调度
  • 将操作符编译为目标代码

TVM代码库中一个有趣的地方是,c++和Python之间的调用不是单向的。通常,所有执行繁重任务的代码都用c++实现,而Python绑定提供面向用户的接口。在TVM中也是如此,但在TVM代码库中,c++代码也可以调用Python模块中定义的函数。例如,卷积算子在Python中实现,在Relay的c++代码中调用。

向量加示例

 下面的示例直接使用低级TVM API实现。这个例子是向量加法,在Working with Operators Using Tensor Expression中有详细介绍。

n = 1024
A = tvm.te.placeholder((n,), name='A')
B = tvm.te.placeholder((n,), name='B')
C = tvm.te.compute(A.shape, lambda i: A[i] + B[i], name="C")

 这里A、B、C的类型是tvm.tensor.Tensor,定义在python/tvm/te/tensor.py中。Python Tensor是对C++ Tensor的封装,C++ Tensor的实现在include/tvm/te/tensor.h和src/te/tensor.cc中。TVM中的所有Python类型都可以看作是底层C++同名类型的句柄。如果你看看下面Python Tensor类型的定义,你会发现它是Object的一个子类。

@register_object
class Tensor(Object, _expr.ExprOp):
    """Tensor object, to construct, see function.Tensor"""

    def __call__(self, *indices):
       ...

对象协议是将C++类型公开给前端语言(包括Python)的基础。TVM的Python封装实现方式并不简单。TVM Runtime System中做了简要介绍,如果你感兴趣,可以在python/tvm/_ffi/中找到详细信息。

 我们使用TVM_REGISTER_*宏,以PackedFunc的形式向前端语言公开C++函数。PackedFunc是TVM实现C++和Python互调的另一种机制。特别是,这使得在C++代码中调用Python函数非常容易。你也可以使用FFI Navigator,它能帮你在python和C++的FFI调用之间导航。

Tensor对象与Operation对象关联,定义在python/tvm/te/Tensor.py、include/tvm/te/operation.h和src/tvm/te/operation子目录中。Tensor是Operation对象的输出。每个Operation对象又有一个input_tensors()方法,该方法向Operation返回一个输入张量的列表。这样我们就可以跟踪Operation之间的依赖关系。

下面的代码中,我们将输出张量C的操作传递给tvm.te.create_schedule()。tvm.te.create_schedule()定义在python/tvm/te/schedule.py中的。

s = tvm.te.create_schedule(C.op)

该接口对应的C++函数定义在include/tvm/schedule.h中:

inline Schedule create_schedule(Array<Operation> ops) {
  return Schedule(ops);
}

Schedule由若干Stage和输出Operation组成。

Stage对应一个Operation。在上面的向量加示例中,有两个占位符操作和一个计算操作,因此schedule包含三个Stage。每个Stage保存相关的循环嵌套结构、每个循环的类型(并行、向量化、展开)、以及在(下一个Stage的循环嵌套中的)何处执行当前Stage的计算等信息。

ScheduleStage定义在tvm/python/te/schedule.pyinclude/tvm/te/schedule.h和src/te/schedule/schedule_ops.cc中。

为了简单起见,我们对上面create_schedule()函数创建的默认调度调用tvm.build(…)。

target = "cuda"
fadd = tvm.build(s, [A, B, C], target)

tvm.build()(定义在python/tvm/driver/build_module.py中)接收一个schedule、输入和输出Tensor,以及一个target,并返回一个tvm.runtime.Module对象。tvm.runtime.Module对象包含一个可以像函数一样调用的编译过的函数。

tvm.build()的处理分两个阶段:

  • lower。一个高层的初始的循环嵌套结构转变为最终的底层的IR
  • 代码生成。由底层IR生成目标机代码

Lowering由tvm.lower()函数完成,定义在python/tvm/build_module.py中。首先执行绑定推理,并创建初始循环嵌套结构。

def lower(sch,
          args,
          name="default_function",
          binds=None,
          simple_mode=False):
   ...
   bounds = schedule.InferBound(sch)
   stmt = schedule.ScheduleOps(sch, bounds)
   ...

绑定推断是推断所有循环边界和中间缓冲区大小的过程。如果你的目标是CUDA后端并且使用共享内存,缓存大小下限将在这里自动确定。绑定推断在src/te/schedule/ Bound.cc, src/te/schedule/graph.cc和src/te/schedule/message_passing.cc中实现。有关绑定推断如何工作的更多信息,请参阅InferBound Pass

stmt是ScheduleOps()的输出,表示初始循环嵌套结构。如果你对调度应用了reorder或split原语,那么初始循环嵌套已经反映了这些变更。ScheduleOps()定义在src/te/schedule/schedule_ops.cc中。

接下来,我们对stmt应用一些lower pass。这些pass在src/tir/pass子目录中实现。例如,如果您已经对调度应用了vectorize或unroll原语,那么它们将在下面的循环向量化和循环展开pass中应用。 

..
stmt = ir_pass.VectorizeLoop(stmt)
...
stmt = ir_pass.UnrollLoop(
    stmt,
    cfg.auto_unroll_max_step,
    cfg.auto_unroll_max_depth,
    cfg.auto_unroll_max_extent,
    cfg.unroll_explicit)
...

lower完成后,build()函数为lower后的函数生成目标机器代码。目标代码可以包含SSE或AVX指令(如果你的目标是x86),或PTX指令(如果你的目标是CUDA)。TVM还生成特定的host侧代码,包括内存管理、内核启动等。

Build()函数在PackedFunc注册表中查找给定目标的代码生成器,并调用找到的函数。例如codegen.build_cuda函数注册在src/codegen/build_cuda_on.cc:

TVM_REGISTER_GLOBAL("codegen.build_cuda")
.set_body([](TVMArgs args, TVMRetValue* rv) {
    *rv = BuildCUDA(args[0]);
  });

上面的BuildCUDA()使用CodeGenCUDA类为lower IR生成CUDA内核代码,并使用NVRTC编译代码。CodeGenCUDA定义在src/codegen/codegen_cuda.cc中。如果你的后端使用LLVM(支持x86, ARM, NVPTX和AMDGPU),代码生成主要由定义在src/codegen/ LLVM /codegen_llvm.cc中的CodeGenLLVM类来完成。CodeGenLLVM将TVM IR转换为LLVM IR,执行多个LLVM优化pass,然后生成目标机器代码。

Build()函数(定义见src/codegen/codegen.cc)返回一个runtime::Module对象(定义见include/tvm/runtime/Module.h和src/runtime/Module .cc)。Module对象是指定底层目标ModuleNode对象的容器。每个后端实现一个ModuleNode的子类,以添加指定目标的运行时API调用。例如,CUDA后端实现CUDAModuleNode类(定义见src/runtime/cuda/cuda_module.cc),它管理CUDA驱动程序API。上面的BuildCUDA()函数使用runtime::Module封装CUDAModuleNode,并将其返回给Python端。LLVM后端实现LLVMModuleNode(定义见src/codegen/llvm/llvm_module.cc),它处理已编译代码的JIT执行。ModuleNode的其他子类可以在src/runtime下找到,分别对应对应于各种后端

返回的模块可以看作是一个编译函数和一个设备API的组合,可以在TVM的NDArray对象上调用。

dev = tvm.device(target, 0)
a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), dev)
b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), dev)
c = tvm.nd.array(np.zeros(n, dtype=C.dtype), dev)
fadd(a, b, c)
output = c.numpy()

在底层TVM自动分配设备内存并管理内存转换。为此每个后端定义自己的DeviceAPI(在include/tvm/runtime/device_api.h中定义)子类,并提供内存管理方法以使用对应设备的API。例如,CUDA后端实现CUDADeviceAPI(见src/runtime/ CUDA /cuda_device_api.cc)以使用cudaMalloc, cudaMemcpy等接口。

当你第一次用有fadd(a, b, c)调用的编译后的模块时,ModuleNode的GetFunction()方法被调用以获得一个可以用于内核调用的PackedFunc。例如,在src/runtime/cuda/cuda_module.cc的CUDA后端实现CUDAModuleNode::GetFunction():

PackedFunc CUDAModuleNode::GetFunction(
      const std::string& name,
      const std::shared_ptr<ModuleNode>& sptr_to_self) {
  auto it = fmap_.find(name);
  const FunctionInfo& info = it->second;
  CUDAWrappedFunc f;
  f.Init(this, sptr_to_self, name, info.arg_types.size(), info.launch_param_tags);
  return PackFuncVoidAddr(f, info.arg_types);
}

 packkedfunc的重载操作符()被调用,进而调用(src/runtime/cuda/cuda_module.cc中)CUDAWrappedFunc的重载操作符()。最后我们看到cuLaunchKernel驱动调用:

class CUDAWrappedFunc {
 public:
  void Init(...)
  ...
  void operator()(TVMArgs args,
                  TVMRetValue* rv,
                  void** void_args) const {
    int device_id;
    CUDA_CALL(cudaGetDevice(&device_id));
    if (fcache_[device_id] == nullptr) {
      fcache_[device_id] = m_->GetFunc(device_id, func_name_);
    }
    CUstream strm = static_cast<CUstream>(CUDAThreadEntry::ThreadLocal()->stream);
    ThreadWorkLoad wl = launch_param_config_.Extract(args);
    CUresult result = cuLaunchKernel(
        fcache_[device_id],
        wl.grid_dim(0),
        wl.grid_dim(1),
        wl.grid_dim(2),
        wl.block_dim(0),
        wl.block_dim(1),
        wl.block_dim(2),
        0, strm, void_args, 0);
  }
};

本文概述了TVM如何编译和执行函数。虽然我们没有详细说明TOPI和Relay,但最后所有的神经网络算子都经过了与上述相同的编译过程。我们鼓励您深入研究代码库其余部分的细节。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【TVM帮助文档学习】通过示例分析TVM代码流程 的相关文章

  • 使用stm32CubeMX自动配置的工程文件,进行串口的不定长接收(非DMA方式)

    方法描述 xff1a 使用stm32CubeMX自动配置的工程文件 xff0c 进行串口的不定长接收 xff08 非DMA方式 xff09 实际问题解决尝试 xff1a 1 HAL库函数只有接收定长数据HAL UART Receive xf
  • 基于STM32平台的BMP180测试(模拟IIC)

    1 测试描述 xff1a 使用模拟IIC xff0c 从BMP180中获取ID号 温度值 气压值以及计算海拔高度 2 测试准备 xff1a 硬件平台 xff1a 原子战舰V3开发板 测试工具 xff1a 逻辑分析仪 串口调试工具 3 数据手
  • gitee 添加ssh公钥

    首先需要安装git 打开 Git Bash 在Bash中输入 ssh keygen t rsa C 34 xxxx 64 yyy com 34 用你的邮件地址进行替换 连着按三次回车 xff0c 第一次提示 34 Enter file in
  • ROS中使用<serial/serial.h>头文件

    ROS中使用 lt serial serial h gt 头文件 问题描述 编译后出现fatal error serial serial h 没有那个文件或目录 include lt serial serial h gt 解决办法 缺少se
  • realsenseD435i使用IMU出错

    realsense 使用IMU出错 已经参照blogs xff1a https blog csdn net weixin 46363611 article details 114643088 将 realsense ws src reals
  • 对极几何,PNP,ICP求解位姿的方法原理、优缺点

    文章目录 一 对极几何估计位姿1 本质矩阵或基础矩阵估计2 单应矩阵估计3 2D 2D时 xff0c 空间点位置计算 二 PNP估计位姿1 P3P方法估计位姿2 DLT方法估计位姿3 EPnP方法估计位姿3 BA方法估计位姿 三 ICP方法
  • 顺序表查找与散列表(哈希表)

    文章目录 一 数据结构二 平均查找长度 xff08 ASL xff09 三 顺序表查找1 顺序查找2 折半查找3 分块查找4 顺序表查找的对比 四 散列表1 散列表构造2 开放地址散列表构造3 散列表查找效率分析 一 数据结构 二 平均查找
  • You‘ll have to kill the Xtightvnc process manually

    You 39 ll have to kill the Xtightvnc process manually 然后 pkill u user
  • ubuntu Linux多个内核选择

    一 问题 ununtu经常自动升级 xff0c 或者是我们在update时 xff0c 系统自动升级内核 此时会导致较高版本的内核与系统相互矛盾 xff0c 而导致卡机等问题 此时需要对多余内核进行设置为默认内核或者将不正确内核删除 两种方
  • 东北天(ENU)和北东地(NED)

    文章目录 一 坐标系定义1 东北天坐标系 xff08 ENU xff09 2 北东地坐标系 xff08 NED xff09 二 在ENU坐标系向量变换到NED坐标系向量三 将载体相对ENU的姿态和位置 xff0c 变换为载体相对NED的姿态
  • opencv for arm

    大家好 xff0c 这篇文章介绍了在arm开发板上移植opencv的过程 虚拟机 xff1a Ubuntu16 04 64位 单板 xff1a jz2440 编译器 xff1a arm linux gcc 4 4 3 opencvbanbo
  • 多传感器数据融合算法综述

    多传感器数据融合是一个新兴的研究领域 xff0c 是针对一个系统使用多种传感器这一特定问题而展开的一种关于数据处理的研究 多传感器数据融合技术是近几年来发展起来的一门实践性较强的应用技术 xff0c 是多学科交叉的新技术 xff0c 涉及到
  • ROS之tf坐标变换

    1 什么是tf 变换 ROS 中的很多软件包都需要机器人发布tf 变换树 xff0c 那么什么是tf 变换树呢 xff1f 抽象的来讲 xff0c 一棵tf 变换树定义了不同坐标系之间的平移与旋转变换关系 具体来说 xff0c 我们假设有一
  • Android下载repo文件报错

    在执行 curl https dl ssl google com dl googlesource git repo repo gt bin repo 下代码的时候 xff0c 报以下错误 curl 7 couldn 39 t connect
  • 树莓派设置自动连接无线网络

    树莓派开机后自动连接无线网络方法 xff0c 亲测有效 1 在任意方法 xff08 无线或有线 xff09 已经连接树莓派的基础上 xff0c 执行该命令 xff0c 意思是编辑wpa supplicant conf这个文件 内容如下 xf
  • Spring boot Druid 多数据源JDBC和注解事务

    1 引入依赖 gradle文件配置 用maven引入也可以 runtimeOnly span class token string 39 com microsoft sqlserver mssql jdbc 39 span span cla
  • 优化查询性能

    目录 1 创建支持查询的索引2 限制查询结果的数量来减少网络需求3 使用投影来只返回需要的数据4 使用 hint来选择一个特点的索引5 使用增量运算符来执行服务端操作 进入MongoDB中文手册 xff08 4 2版本 xff09 目录 1
  • 'node' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 解决方法

    windows系统里 需要把nodejs安装目录路径设置进系统变量或用户变量 1 打开控制面板 gt 系统和安全 gt 系统 gt 高级系统设置 gt 环境变量 2 在 用户变量 或 系统变量 中查找变量PATH并将node js文件夹路径
  • .NET Action 与 Func

    Action 与 Func是 NET类库中增加的内置委托 xff0c 以便更加简洁方便的使用委托 内置委托类型 xff0c 顾名思义Action和Func本身就是已经定义好的委托类型 两种委托类型的区别在于 xff1a Action委托签名
  • 卸掉notepad++,永远不再使用!!!

    昨天看到消息称notepad 43 43 这款软件的官网发布了反华言论 xff0c 自己刚刚去他的官网看了下 xff0c 还真是 https notepad plus plus org news v781 free uyghur editi

随机推荐

  • IMX6ULL第一个裸机程序,点亮LED

    一 环境 1 100ask imx6ull开发板 2 mint19开发环境 二 硬件原理图 LED的管脚为GPIO5 IO03 当GPIO5 IO03为高电平时 xff0c LED熄灭 xff1b 当GPIO5 IO03为低电平时 xff0
  • WSL使用NFS

    微软的WLS下挂NFS系统是失败的 xff0c 因为内核不支持 xff0c 此路是不通的 xff0c 至少目前我是没有找到解决办法 xff0c 可以使用hane winNFS替代
  • AD导出PDF内容显示不全解决办法

    DXP gt Preferneces gt Schematic gt General下 取消Render Text with GDI 43
  • uboot编译报错 dtc: command not found解决办法

    编译uboot 报错 解决办法 span class token function sudo span span class token function apt get span span class token function ins
  • 解决imx6ull开发板加载驱动disagrees about version of symbol module_layout失败问题

    一 环境 1 硬件 100ask imx6ull单板 2 软件环境 xff0c 提供的开发环境 二 报错信息 hello drv disagrees about version of symbol module layout insmod
  • 应用软件安装

    在进行开发中 xff0c 会使用到很多各种类型的软件 xff0c 现在将其进行整理 xff0c 列表如下 xff0c 如有需要 xff0c 请给我留言 xff0c 我可以给分享给大家 001 application software 序号
  • 安装树莓派的系统到SD卡

    安装树莓派的系统到 SD卡 准备工作 1 一张 16G 以上的 SD 卡 xff0c 最好是高速卡 xff0c 推荐 Class4 以上的卡 xff0c 因为卡得速度直接影响树莓派的运行速度 2 格式化SD卡工具 xff0c 建议使用工具
  • 串口测试例程

    串口分为RS232和TTL xff0c 所以在测试前请先确认自己的串口类型进行测试 下面以海思的一个RS232串口为例说明一下具体的测试过程 xff0c 串口测试代码参考的为讯为提供的 xff0c 在其基础上做的调整 1 确定接口 xff0
  • C语言中的字符串初始化

    1 C语言没有字符串类型 xff0c 通过字符数组模拟 xff0c C语言字符串 xff0c 以字符 0 数字0 2 数组指定长度后 xff0c 后面没有赋值的元素 xff0c 自动补0 char buf 100 61 39 a 39 39
  • 自动收取蚂蚁森林能量雨

    首先 xff0c 说明一下 这是一个非常迂回且效果不佳的方法 xff0c 小弟不才 xff0c 安卓以及苹果手机上的脚本没啥研究 xff0c PC的按键精灵略懂 xff0c 因此才有一下脚本 觉得有用拿去参考 xff0c 大佬勿喷 我一直都
  • AD中画圆弧形板框的方法

    在实际的板框设计中 xff0c 有时为了生产需要 xff0c 不得不需要将PCB板设计成四角需要倒角的形式 xff0c 经过实际的摸索 xff0c 现在得出一种画圆形板框的小技巧 现在需要将板框设计成3 5mm半径样式 xff08 1 xf
  • AD中板内挖空的方法

    一 先使用禁止布线层将板框确定 使用的快捷键 xff1a D gt S gt D 二 再绘制一个挖空区域 使用的快捷键 xff1a T gt V gt B 三 挖空后的效果
  • STL初识

    STL的诞生 长久以来 xff0c 软件界一直希望建立一种可重复利用的东西 C 43 43 的面向对象和泛型编程思想 xff0c 目的就是复用性的提升 大多情况下 xff0c 数据结构和算法都未能有一套标准 导致被迫从事大量重复工作 为了建
  • 删除U盘中的System Volume Information 文件夹的方法

    在使用U盘测试ARM板的时候 xff0c 会发现System Volume Information这个文件夹阴魂不散 xff0c 总是存在 xff0c 在Windows下是看不见的 xff0c 即便将文件的查看属性设置为显示隐藏文件 在使用
  • Linux命令发送Http的get或post请求

    get请求 curl命令模拟get请求 xff1a 1 使用curl命令 xff1a curl span class hljs string 34 http www baidu com 34 span 如果这里的URL指向的是一个文件或者一
  • 深度学习之----多任务学习

    介绍 在机器学习 xff08 ML xff09 中 xff0c 通常的关注点是对特定度量进行优化 xff0c 度量有很多种 xff0c 例如特定基准或商业 KPI 的分数 为了做到这一点 xff0c 我们通常训练一个模型或模型组合来执行目标
  • PID控制原理(全干货)

    主要内容 xff1a 1 常用的控制算法与PID控制算法的异同点 xff1b 2 PID控制算法的理论分析 3 基于单片机的PID算法实现 4 PID算法的工程应用的一些注意事项 5 演示板电路分析 6 PID算法C语言实现 基于ARM C
  • ESP32 手册+文档整理

    ESP32 一些手册和批注 Arduino IDE 这个板子是带了LoRa的版本 xff0c 国外还有一个TTGo 有类似的ESP32 43 LoRa这种组合 如果不需要LoRa xff0c 可以考虑ESP32 DevKit 或者PicoK
  • 【TVM帮助文档学习】TVM语言参考

    本文翻译自Language Reference tvm 0 9 dev0 documentation 本文档提供TVM的嵌入式语言和IRs的参考 Relay介绍 Relay 是一种函数式的 可微的编程语言 它是一种面对机器学习领域的具有良好
  • 【TVM帮助文档学习】通过示例分析TVM代码流程

    本文翻译自TVM Codebase Walkthrough by Example tvm 0 9 dev0 documentation 了解TVM代码颇具挑战性 xff0c 它的各组件之间的交互非常隐晦 在本指南中 xff0c 我们将通过一