TVM 架构设计

本文面向希望了解TVM体系结构和/或积极参与项目开发的开发人员。

主要内容如下:

示例编译流程概述了TVM将模型的高级概念转换为可部署模块的步骤。

逻辑架构组件部分描述逻辑组件。针对每个逻辑组件,按组件的名称进行组织。

也可以随时查看开发人员如何指导有用的开发技巧。

提供了架构的一些补充视图。首先,检查一个单一的端到端编译流程,并讨论关键的数据结构和转换。这个基于runtime的视图主要关注运行编译器时每个组件之间的交互。然后将检查代码库的逻辑模块及其关系。设计了静态总体视图。

Example Compilation Flow

研究编译器中的一个示例编译流。下图显示了流程。在高层,包含几个步骤:

导入:前端组件将模型摄取到IRModule中,IRModule包含内部表示模型的函数集合。

转换:编译器将一个IRModule转换成另一个功能上等价或近似等价的IRModule(例如在量化的情况下)。许多转换都是独立于目标(后端)的。还允许target影响转换管道的配置。

目标转换:编译器将IRModule(codegen)转换为目标指定的可执行格式。目标转换结果封装为runtime.Module,可以在目标runtime环境中导出、加载和执行。

Runtime执行:用户将runtime.Module并在支持的runtime环境中运行编译的函数。

Key data structures

设计和理解复杂系统的最佳方法之一是识别关键数据结构和操作(转换)这些数据结构的API。一旦确定了关键的数据结构,就可以将系统分解成逻辑组件,这些组件要么定义关键数据结构的集合,要么定义数据结构之间的转换。

IRModule是整个堆栈中使用的主要数据结构。IRModule(中间表示模块)包含一组函数。目前,支持函数的两个主要变体。

relay::Function函数是高级函数程序表示。一个relay继电器功能通常对应于端到端模型。可以查看relay继电器功能作为一个计算图,对控制流、递归和复杂的数据结构有额外的支持。

tir::PrimFunc是一个低级程序表示,包括循环嵌套选择、多维加载/存储、线程和向量/张量指令在内的元素。通常用来表示执行模型中(可能是融合)层的算子程序。

在编译过程中,一个relay中继函数可以被降为多个tir::PrimFunc函数和一个调用这些tir::PrimFunc函数的顶层函数。

Transformations

已经介绍了关键的数据结构,来谈谈转换。每种转换都可以达到以下目的之一:

优化:将一个程序转换成一个等效的,可能更优化的版本。

降低:将程序转换为更接近目标的低级表示。

中继/转换包含优化模型的过程集合。这些优化包括常见的程序优化,如常量折叠和死代码消除,以及张量计算特定的过程,如布局转换和缩放因子折叠。

将端到端部的中继(例如,MobileNet)端到端部的优化(融合操作),称这些函数段。这个过程帮助将原始问题分为两个子问题:

每个子功能的编译和优化。

整体执行结构:需要对生成的子函数执行一系列调用来执行整个模型。

使用低级tir阶段来编译和优化每个子函数。对于特定的目标,也可以直接进入目标转换阶段,使用外部代码生成器。

有几种不同的方法(在中继/后端)来处理对整个执行问题的调用。对于具有已知形状且没有控制流的简单模型,可以降低到将执行结构存储在图形中的图形runtime。还支持用于动态执行的虚拟机后端。最后,计划支持提前编译,将高级执行结构编译成可执行的和生成的原始函数。所有这些执行模式都被一个统一的runtime.Module接口,将在后面部分讨论。

tir/转换包含tir级功能的转换过程。许多tir通行证的目的是降低。例如,有一些过程可以将多维访问变为一维指针访问,将内部函数扩展为特定于目标的内部函数,以及修饰函数入口以满足runtime调用约定。当然,还有一些优化过程,比如访问索引简化和死代码消除。

许多低级优化可以在目标阶段由LLVM、cudac和其他目标编译器处理。因此,将寄存器分配等低级优化留给下游编译器,而只关注那些没有被它们覆盖的优化。

Search-space and Learning-based Transformations

到目前为止,所描述的转换过程是确定的和基于规则的。TVM堆栈的一个设计目标是支持针对不同硬件平台的高性能代码优化。为此,需要研究尽可能多的优化选择,包括但不限于多维张量访问、循环平铺行为、特殊加速器内存层次结构和线程。

很难定义一个启发式来做出所有的选择。相反,将采取基于搜索和学习的方法。首先定义一组可以用来转换程序的操作。示例操作包括循环转换、内联、矢量化。称这些动作为调度原语。调度原语的集合定义了一个搜索空间,可以对程序进行优化。然后系统搜索不同的可能的调度序列,以选择最佳的调度组合。搜索过程通常由机器学习算法指导。

一旦搜索完成,就可以记录一个(可能是融合)操作符的最佳调度序列。然后编译器就可以查找最佳调度序列并将其应用到程序中。值得注意的是,这个调度应用程序阶段与基于规则的转换完全相同,能够与传统过程共享相同的接口约定。

使用基于搜索的优化方法来处理初始的tir函数生成问题。该模块的这一部分称为AutoTVM(auto_scheduler)。随着继续开发TVM堆栈,希望将基于学习的转换扩展到更多领域。

Target Translation

目标转换阶段将IRModule转换为相应的目标可执行格式。对于x86和ARM这样的后端,使用llvmirbuilder来构建内存llvmir。

还可以生成源代码级语言,如cudac和OpenCL。最后,支持通过外部代码生成器将中继函数(子图)直接转换为特定目标。重要的是,最后的代码生成阶段尽可能轻量级。绝大多数转换和降低都应该在目标转换阶段之前执行。

还提供了一个目标结构来指定编译目标。目标平移阶段之前的变换也会受到目标的影响-例如,目标的向量长度会改变矢量化行为。

Runtime Execution

TVMruntime的主要目标是提供一个最小的API,用于加载和执行编译后的工件,它们的语言包括Python、C++、Read、Go、java和JavaScript。下面的代码片段在Python中显示了这样一个示例:

import tvm

# Example runtime execution program in python, with type annotated

mod: tvm.runtime.Module = tvm.runtime.load_module("compiled_artifact.so")

arr: tvm.runtime.NDArray = tvm.nd.array([1, 2, 3], ctx=tvm.gpu(0))

fun: tvm.runtime.PackedFunc = mod["addone"]

fun(a)

print(a.asnumpy())

tvm.runtime.Module封装编译结果。一个runtime.Module包含按名称获取PackedFuncs的GetFunction方法。

tvm.runtime.PackedFunc是两个生成函数的类型删除函数接口。一个runtime.PackedFunc可以使用以下类型获取参数和返回值:POD类型(int,float),string,runtime.PackedFunc, runtime.Module, runtime.NDArray,以及其他子类runtime.Object。

tvm.runtime.Module以及tvm.runtime.PackedFunc是模块化runtime的强大机制。例如,为了在CUDA上获得上述addone函数,可以使用LLVM生成主机端代码来计算启动参数(例如线程组的大小),然后从CUDA驱动程序API支持的CUDAModule调用另一个PackedFunc。同样的机制也可以用于OpenCL内核。

上面的例子只处理一个简单的addone函数。下面的代码片段给出了使用相同接口的端到端模型执行示例:

import tvm

# Example runtime execution program in python, with types annotated

factory: tvm.runtime.Module = tvm.runtime.load_module("resnet18.so")

# Create a stateful graph execution module for resnet18 on gpu(0)

gmod: tvm.runtime.Module = factory["resnet18"](tvm.gpu(0))

data: tvm.runtime.NDArray = get_input_data()

# set input

gmod["set_input"](0, data)

# execute the model

gmod["run"]()

# get the output

result = gmod["get_output"](0).asnumpy()

主要的收获是runtime.Module 和 runtime.PackedFunc足以封装算子级程序(如addone)和端到端模型。

总结与讨论

总之,编译流程中的关键数据结构是:

IRModule: contains relay.Function and tir.PrimFunc

runtime.Module: contains runtime.PackedFunc

编译的大部分内容是关键数据结构之间的转换。

中继/转换和tir/转换是基于规则的确定性转换

auto_scheduler和autotvm包含基于搜索的转换

最后,编译流程示例只是TVM堆栈的一个典型用例。将这些关键数据结构和转换表示为Python和C++ API。因此,可以像使用numpy一样使用TVM,只是感兴趣的数据结构从numpy.ndarray 到 tvm.IRModule。以下是一些用例:

使用python API直接构造IRModule。

组成一组自定义的变换(例如自定义量化)。

使用TVM的PythonAPI直接操作IR。

Logical Architecture Components

上图显示了项目中的主要逻辑组件。有关组件及其关系的信息,请阅读以下部分。

tvm/支持

支持模块包含架构设计最常用的实用程序,如通用的arena分配器、socket和日志记录。

tvm/runtime间

runtime是TVM堆栈的基础。它提供了加载和执行已编译工件的机制。runtime定义了一组稳定的标准C API来与诸如Python和Rust这样的前端语言交互。

Object是TVM runtime中除了runtime::PackedFunc之外的主要数据结构之一。它是一个引用计数的基类,具有类型索引以支持runtime类型检查和向下转换。目标系统允许开发人员向runtime引入新的数据结构,例如数组、映射和新的IR数据结构。

除了部署用例,编译器本身也大量使用TVM的runtime机制。所有的IR数据结构都是runtime::Object的子类,因此可以从Python前端直接访问和操作。使用PackedFunc机制向前端开发各种API。

对不同硬件后端的runtime支持定义在runtime的子目录中(例如Runtime/opencl)。这些特定于硬件的runtime模块定义了用于设备内存分配和设备函数序列化的API。

runtime/rpc实现了对PackedFunc的rpc支持。可以使用RPC机制将交叉编译的库发送到远程设备,并对执行性能进行基准测试。Rpc架构设计支持从通用的硬件后端收集数据,以进行基于学习的优化。

tvm/node

node模块在IR数据结构的runtime::Object的基础上添加了其他特性。主要特性包括反射、序列化、结构等价和哈希。

有了node模块,就可以在Python中直接访问TVM的IRNode的任何字段。

x = tvm.tir.Var("x", "int32")

y = tvm.tir.Add(x, x)

# a and b are fields of a tir.Add node

# we can directly use the field name to access the IR structures

assert y.a == x

还可以将任意IR节点序列化为JSON格式,并将其加载回。保存/存储和检查IR节点的能力为编译器更容易访问提供了基础。

tvm/ir

tvm/ir文件夹包含所有ir功能变体的统一数据结构和接口。tvm/ir中的组件由tvm/relay和tvm/tir共享,值得注意的是

  • IRModule
  • Type
  • PassContext and Pass
  • Op

功能的不同变体(例如,relay.Function and tir.PrimFunc)可以共存于一个IRModule中。虽然这些变体可能没有相同的内容表示形式,但它们使用相同的数据结构来表示类型。因此,使用相同的数据结构来表示这些变量的函数(类型)标签。统一类型系统允许一个函数变量调用另一个函数,只要明确定义了调用协议。这为未来的多功能变量优化打开了大门。

还提供了一个统一的PassContext来配置传递行为,并提供了通用的复合过程来执行传递管道。下面的代码片段给出了PassContext配置的示例。

# configure the behavior of the tir.UnrollLoop pass

with tvm.transform.PassContext(config={"tir.UnrollLoop": { "auto_max_step": 10 }}):

# code affected by the pass context

Op是表示所有系统定义的基本运算符/内部函数的公共类。开发人员可以向系统注册新的操作以及它们的附加属性(例如,操作是否是基本元素级的)。

tvm/target

The target module contains all the code generators that translate an IRModule to a target runtime.Module. It also provides a common Target class that describes the target.

The compilation pipeline can be customized according to the target by querying the attribute information in the target and builtin information registered to each target id(cuda, opencl).

tvm/tir

TIR contains the definition of the low-level program representations. We use tir::PrimFunc to represent functions that can be transformed by TIR passes. Besides the IR data structures, the tir module also defines a set of builtin intrinsics and their attributes via the common Op registry, as well as transformation passes in tir/transform.

tvm/arith

This module is closely tied to the TIR. One of the key problems in the low-level code generation is the analysis of the indices’ arithmetic properties — the positiveness, variable bound, and the integer set that describes the iterator space. arith module provides a collection of tools that do (primarily integer) analysis. A TIR pass can use these analyses to simplify and optimize the code.

tvm/te

The name te stands for “tensor expression”. This is a domain-specific language module that allows us to construct tir::PrimFunc variants quickly by writing tensor expressions. Importantly, a tensor expression itself is not a self-contained function that can be stored into IRModule. Instead, it is a fragment of IR that we can stitch together to build an IRModule.

te/schedule provides a collection of scheduling primitives to control the function being generated. In the future, we might bring some of these scheduling components to the a tir::PrimFunc itself.

tvm/topi

While possible to construct operators directly via TIR or tensor expressions (TE) for each use case it is tedious to do so. topi (Tensor operator inventory) provides a set of pre-defined operators (in TE or TIR) defined by numpy and found in common deep learning workloads. We also provide a collection of common schedule templates to obtain performant implementations across different target platforms.

tvm/relay

Relay is the high-level functional IR used to represent full models. Various optimizations are defined in relay.transform. The Relay compiler defines multiple dialects, and each dialect is designed to support specific styles of optimization. Notable ones include QNN(for importing pre-quantized models), VM(for lowering to dynamic virtual machine), memory(for memory optimization).

tvm/autotvm

AutoTVM and AutoScheduler are both components which automate search based program optimization. This is rapidly evolving and primarily consists of:

  • Cost models and feature extraction.
  • A record format for storing program benchmark results for cost model construction.
  • A set of search policies over program transformations.

Automated program optimization is still an active research field. As a result, we have attempted to modularize the design so that researchers may quickly modify a component or apply their own algorithms via the Python bindings, and customize the search and plugin their algorithms from the Python binding.

Frontends

Frontends ingest models from different frameworks into the TVM stack. tvm.relay.frontend is the namespace for model ingestion APIs.

Security

TVM 架构设计的更多相关文章

  1. 浅谈 jQuery 核心架构设计

    jQuery对于大家而言并不陌生,因此关于它是什么以及它的作用,在这里我就不多言了,而本篇文章的目的是想通过对源码简单的分析来讨论 jQuery 的核心架构设计,以及jQuery 是如何利用javas ...

  2. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  3. 解构C#游戏框架uFrame兼谈游戏架构设计

    1.概览 uFrame是提供给Unity3D开发者使用的一个框架插件,它本身模仿了MVVM这种架构模式(事实上并不包含Model部分,且多出了Controller部分).因为用于Unity3D,所以它 ...

  4. VICA 架构设计(1)

    本文记录最近完成的一个通用实时通信客户端的架构.   背景 我们公司是做税务相关的软件,有针对大客户 MIS 系统,也有针对中小客户的 SaaS 平台.这些系统虽然都是 B/S 的,但是也需要使用 A ...

  5. 一种简单的CQRS架构设计及其实现

    一.为什么要实践领域驱动? 近一年时间我一直在思考一个问题:"如何设计一个松耦合.高伸缩性.易于维护的架构?".之所以有这样的想法是因为我接触的不少项目都是以数据库脚本来实现业务逻 ...

  6. 基于token的多平台身份认证架构设计

    基于token的多平台身份认证架构设计 1   概述 在存在账号体系的信息系统中,对身份的鉴定是非常重要的事情. 随着移动互联网时代到来,客户端的类型越来越多, 逐渐出现了 一个服务器,N个客户端的格 ...

  7. 架构设计:一种远程调用服务的设计构思(zookeeper的一种应用实践)

    在深入学习zookeeper我想先给大家介绍一个和zookeeper相关的应用实例,我把这个实例命名为远程调用服务.通过对这种应用实例的描述,我们会对zookeeper应用场景会有深入的了解. 远程调 ...

  8. ABP架构设计交流群-上海线下交流会的内容分享(有高清录像视频的链接)

    点这里进入ABP系列文章总目录 ABP架构设计交流群-7月18日上海线下交流会内容分享 因为最近工作特别忙,很久没有更新博客了,真对不起关注我博客和ABP系列文章的朋友! 原计划在7月11日举行的AB ...

  9. 架构设计:负载均衡层设计方案(3)——Nginx进阶

    版权声明:欢迎转载,但是看在我辛勤劳动的份上,请注明来源:http://blog.csdn.net/yinwenjie(未经允许严禁用于商业用途!) 目录(?)[-] Nginx继续进阶 1gzip ...

随机推荐

  1. ZOJ3261并查集逆向处理

    题意:       给你一些点,还有一些边,每个点上都有一个权值,然后有一些询问,分为两种, query a 询问与a直接或者间接想连的点中最大权值的是那个点,输出那个点,如果那个点的权值小于等于a的 ...

  2. Identity Server4 数据迁移、持久化

    add-migration InitialPersistedGrantDb -c PersistedGrantDbContext -o Migrations/IdentityServer/Persis ...

  3. 带你解析MySQL binlog

    前言: 我们都知道,binlog可以说是MySQL中比较重要的日志了,在日常学习及运维过程中,也经常会遇到.不清楚你对binlog了解多少呢?本篇文章将从binlog作用.binlog相关参数.解析b ...

  4. 测试中常用的链接URL----方便自己查找

    1.TesterHome:https://testerhome.com/ 2.selenium的操作手册:https://selenium-python.readthedocs.io/ 3.

  5. [基本运算符、流程控制之if判断、与用户交互、深浅拷贝]

    [基本运算符.流程控制之if判断.与用户交互] 基本运算符 1.算数运算符 python支持的算术运算符与数学上计算的符号使用是一致的 salary = 3.3 res = salary * 12 p ...

  6. 交叉编译参数--build、host和target的区别

    build.host和target 在交叉编译中比较 常见 的一些参数就是build.host和target了,正确的理解这三者的含义对于交叉编译是非常重要的,下面就此进行解释 --build=编译该 ...

  7. SwiftUI 简明教程之属性包装器

    本文为 Eul 样章,如果您喜欢,请移步 AppStore/Eul 查看更多内容. Eul 是一款 SwiftUI & Combine 教程 App(iOS.macOS),以文章(文字.图片. ...

  8. 马哥Linux SysAdmin学习笔记(四)

    sed:编辑器 sed:Stream EDitor,行编辑器 用法: sed [option]... 'script' inputfile... script: '地址命令' 常用选项: -n:不输出 ...

  9. 运维常用shell脚本二(压缩文件、过滤不需要的文件、检测进程)

    一.压缩指定目录下的文件并删除原文件 #!/bin/bashZIP_DAY=7 function zip { local dir=$1 if [ -d $dir ];then local file_n ...

  10. Java 中 volatile 关键字及其作用

    引言 作为 Java 初学者,几乎从未使用过 volatile 关键字.但是,在面试过程中,volatile 关键字以及其作用还是经常被面试官问及.这里给各位童靴讲解一下 volatile 关键字的作 ...