初识 TVM

2023-11-07

如有图像或公式显示错误,可以访问我的个人博客:https://www.wanglichun.tech/2019/11/15/tvm/

笔者也是最近偶然的机会才开始接触TVM,使用过后发现,经过auto-tuning后的TVM模型在速度是竟然超过了TensorRT,并且笔者使用的是MXNet框架,TVM对MXNet绝对的友好,对于Pytorch等模型,可以使用ONNX,操作也一样简单,使用起来基本类似一键操作,本篇文章是笔者对TVM的简单整理,也算是对TVM的入门。

当然如何想详细了解TVM,还请阅读TVM的主页以及论文,文章最后有链接。

TVM简介

随着深度学习的发展,深度学习的能力可以说是越来越强大,识别率节节攀升,甚至超过人类。于此同时,深度学习框架也变得越来越多,目前比较主流的深度学习框架包括:Pytorch、TensorFlow、Mxnet、Caffe、Keras等。

一般进行深度学习任务包括两部分,一是训练出精度比较高的模型,然后将其部署到对应的目标机器上。

针对第一部分,自然我们可以使用各种深度学习框架,通过修改网络调参等,训练出精度比较满意的模型,一般情况,在训练深度学习模型的时候,都会使用到GPU。

针对部署,这里的目标机包括服务器、手机、其他硬件设备等等。部署的模型自然是希望越快越好,所以硬件厂商一般会针对自己的硬件设备进行一定的优化,以使模型达到更高的效率,比如Nvidia的TensorRT。但是框架这么多,硬件平台这么多,并不是所有的硬件平台都像Nvidia提供了硬件加速库,而即使做了加速,要适应所有的深度学习训练框架,也是一件比较难的事情。

其实介绍了这么多总结起来就是两个问题:

  1. 在进行模型部署的时候,我们是否可以对不同框架训练的模型均生成统一的模型,解决硬件平台需要适配所有框架的问题?
  2. 在进行模型部署的时候,我们是否可以自动化的针对不同的硬件进行优化,进而得到高效的模型?

TVM实际上就是在解决这两个问题,并且解决的还不错。

那么TVM是什么?

TVM is an open deep learning compiler stack for CPUs, GPUs, and specialized accelerators. It aims to close the gap between the productivity-focused deep learning frameworks, and the performance- or efficiency-oriented hardware backends.

TVM是一个开源的可面向多种硬件设备的深度学习编译器,它的作用在于打通模型框架、模型表现以及硬件设备的鸿沟,进而得到表现最好的可部署的深度学习模型,实现端到端的深度学习模型部署。

TVM做了哪些工作

针对第一个问题:

TVM将不同前端(深度学习框架)训练的模型,转换为统一的中间语言表示,如果想详细理解这里,可以了解一下NNVMNNVM是陈天奇团队开发的可以针对不同框架进行深度学习编译的框架,在TVM中,陈天奇团队进一步优化,实现了NNVM的第二代Relay。Relay是TVM中实现的一种高级IR,可以简单理解为另一种计算图表示。其在TVM所处的位置如下图所示,并且该部分实现了比如运算融合等操作,可以提升一部分模型效率。

在这里插入图片描述

Relay在优化中的位置

针对第二个问题:

TVM设计了对不同的硬件后端,自动优化tensor操作,以达到加速的目的。该部分的实现,TVM使用机器学习的方法进行计算空间的最优化搜索,通过在目标硬件上跑大量trial,来获得该硬件上相关运算(例如卷积)的最优实现。详细介绍可以参考TVM主页以及论文。

在这里插入图片描述

TVM tuning可以对不同硬件进行tensor优化
TVM 安装

不同环境的安装方法可以参考tvm的官网:https://docs.tvm.ai/install/index.html

对于安装环境,我还是强烈推荐docker的,会少很多坑。

TVM 使用

TVM的使用可以阅读一下tvm提供的tutorials:https://docs.tvm.ai/tutorials/

主要推荐两部分:

  • compile deep learning models
  • auto tuning

其实简单的使用主要就是这两块内容,如果不想细研究其代码,可以将其当成一个工具使用,通过compile deep learning models,无论你使用什么样的框架,都可以生成统一的模型,一般会生成3个东西如下:

在这里插入图片描述

这里一般会做一些层的融合等操作,速度会有一定的提升的,但是不是特别大。这时如果你需要进一步提速可以试试auto tuning,这部分可以参考tutorials以及下面的例子代码,auto-tune的时间一般比较长,但是效果还是比较显著的,本地测试,resnet在nvidia 1080ti上可以提高3倍左右。

Demo代码

TVM的原理很复杂但是使用起来还是比较方便的,下面是使用MXNet进行TVM转换的demo。

代码一:生成TVM模型。


import tvm
from tvm import relay
from tvm.relay import testing
from tvm.contrib import graph_runtime
import mxnet as mx
from tvm.contrib.download import download_testdata
import numpy as np
import time

## load mxnet model
prefix = '/Models/resnetv1d-101'
epoch = 13
mx_sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch)
shape_dict = {'data': (1, 3, 224, 224)}

relay_func, relay_params = relay.frontend.from_mxnet(mx_sym, shape_dict,
        arg_params=arg_params, aux_params=aux_params)


target = 'cuda'
with relay.build_config(opt_level=3):
    graph, lib, params = relay.build(relay_func, target, params=relay_params)
# run forward



from PIL import Image
image = Image.open('test.jpg').resize((224, 224))
def transform_image(im):
    im = np.array(im).astype(np.float32)
    im = np.transpose(im, [2, 0, 1])
    im = im[np.newaxis, :]
    return im
x = transform_image(image)
# let's go
ctx = tvm.gpu(0)
dtype = 'float32'

m = graph_runtime.create(graph, lib, ctx)
## set input data
m.set_input('data', tvm.nd.array(x.astype(dtype)))
## set input params
m.set_input(**params)
t1 = time.time()
m.run()
t2 = time.time()
# get output
outputs = m.get_output(0)
top1 = np.argmax(outputs.asnumpy()[0])
print(outputs, str(t2-t1))

### evaluate inference time

ftimer = m.module.time_evaluator('run', ctx, number=1, repeat=100)
prof_res = np.array(ftimer().results) * 1000
print('time cost : mean:{}'.format(np.mean(prof_res)))





# save model

path_lib = '/Outputs/tvm/deploy_resnet101_v1d_lib.tar'
lib.export_library(path_lib)

with open('/Outputs/tvm/deploy_resnet101_v1d_graph.json', 'w') as f:
    f.write(graph)
with open('/Outputs/tvm/deploy_params', 'wb') as f:
    f.write(relay.save_param_dict(params))
    
    
# load model back

loaded_json = open('/Outputs/tvm/deploy_resnet101_v1d_graph.json').read()
loaded_lib = tvm.module.load(path_lib)
loaded_params = bytearray(open('/Outputs/tvm/deploy_params', 'rb').read())
module = graph_runtime.create(loaded_json, loaded_lib, ctx)
module.load_params(loaded_params)

tvm_data = tvm.nd.array(x.astype(dtype))
module.run(data=tvm_data)
outputs = module.get_output(0)
print(outputs)


代码二:auto-tuning

这部分耗时较长,一个resnet101模型,在1080ti上面可能要tune1到2天的时间。

import os

import numpy as np
import mxnet as mx
import tvm
from tvm import autotvm
from tvm import relay
import tvm.relay.testing
from tvm.autotvm.tuner import XGBTuner, GATuner, RandomTuner, GridSearchTuner
from tvm.contrib.util import tempdir
import tvm.contrib.graph_runtime as runtime
import argparse

def get_network(dtype, args):
    """Get the symbol definition and random weight of a network"""
    input_shape = (args.batch_size, 3, 224, 224)

    # if "resnet" in name:
    #     n_layer = int(name.split('-')[1])
    #     mod, params = relay.testing.resnet.get_workload(num_layers=n_layer, batch_size=batch_size, dtype=dtype)
    # elif "vgg" in name:
    #     n_layer = int(name.split('-')[1])
    #     mod, params = relay.testing.vgg.get_workload(num_layers=n_layer, batch_size=batch_size, dtype=dtype)
    # elif name == 'mobilenet':
    #     mod, params = relay.testing.mobilenet.get_workload(batch_size=batch_size, dtype=dtype)
    # elif name == 'squeezenet_v1.1':
    #     mod, params = relay.testing.squeezenet.get_workload(batch_size=batch_size, version='1.1', dtype=dtype)
    # elif name == 'inception_v3':
    #     input_shape = (1, 3, 299, 299)
    #     mod, params = relay.testing.inception_v3.get_workload(batch_size=batch_size, dtype=dtype)
    # elif name == 'mxnet':
        # an example for mxnet model
        # from mxnet.gluon.model_zoo.vision import get_model
        # block = get_model('resnet18_v1', pretrained=True)
    # else:
    #     raise ValueError("Unsupported network: " + name)

    prefix = '/Models/{}/{}'.format(args.version, args.model_name)
    epoch = args.model_index
    mx_sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch)

    mod, params = relay.frontend.from_mxnet(mx_sym, shape={'data': input_shape}, dtype=dtype, arg_params=arg_params,
                                            aux_params=aux_params)
    net = mod["main"]
    net = relay.Function(net.params, relay.nn.softmax(net.body), None, net.type_params, net.attrs)
    mod = relay.Module.from_expr(net)
    return mod, params, input_shape




# You can skip the implementation of this function for this tutorial.
def tune_tasks(tasks,
               measure_option,
               tuner='xgb',
               n_trial=1000,
               early_stopping=None,
               log_filename='tuning.log',
               use_transfer_learning=True,
               try_winograd=True):
    if try_winograd:
        for i in range(len(tasks)):
            try:  # try winograd template
                tsk = autotvm.task.create(tasks[i].name, tasks[i].args,
                                          tasks[i].target, tasks[i].target_host, 'winograd')
                input_channel = tsk.workload[1][1]
                if input_channel >= 64:
                    tasks[i] = tsk
            except Exception:
                pass

    # create tmp log file
    tmp_log_file = log_filename + ".tmp"
    if os.path.exists(tmp_log_file):
        os.remove(tmp_log_file)

    for i, tsk in enumerate(reversed(tasks)):
        prefix = "[Task %2d/%2d] " %(i+1, len(tasks))

        # create tuner
        if tuner == 'xgb' or tuner == 'xgb-rank':
            tuner_obj = XGBTuner(tsk, loss_type='rank')
        elif tuner == 'ga':
            tuner_obj = GATuner(tsk, pop_size=100)
        elif tuner == 'random':
            tuner_obj = RandomTuner(tsk)
        elif tuner == 'gridsearch':
            tuner_obj = GridSearchTuner(tsk)
        else:
            raise ValueError("Invalid tuner: " + tuner)

        if use_transfer_learning:
            if os.path.isfile(tmp_log_file):
                tuner_obj.load_history(autotvm.record.load_from_file(tmp_log_file))

        # do tuning
        n_trial = min(n_trial, len(tsk.config_space))
        tuner_obj.tune(n_trial=n_trial,
                       early_stopping=early_stopping,
                       measure_option=measure_option,
                       callbacks=[
                           autotvm.callback.progress_bar(n_trial, prefix=prefix),
                           autotvm.callback.log_to_file(tmp_log_file)])

    # pick best records to a cache file
    autotvm.record.pick_best(tmp_log_file, log_filename)
    os.remove(tmp_log_file)


def tune_and_evaluate(tuning_opt, target, log_file, dtype, args):
    # extract workloads from relay program
    print("Extract tasks...")
    mod, params, input_shape = get_network(dtype, args)
    tasks = autotvm.task.extract_from_program(mod["main"], target=target,
                                              params=params, ops=(relay.op.nn.conv2d,))

    # run tuning tasks
    print("Tuning...")
    tune_tasks(tasks, **tuning_opt)

    # compile kernels with history best records
    with autotvm.apply_history_best(log_file):
        print("Compile...")
        with relay.build_config(opt_level=3):
            graph, lib, params = relay.build_module.build(
                mod, target=target, params=params)

        # export library
        tmp = tempdir()
        filename = "/Outputs/tvm_autotuning/{}/{}_auto_tune_deploy_batch_{}_lib.tar".format(args.version,args.model_name, args.batch_size)
        lib.export_library(tmp.relpath(filename))

        with open('/Outputs/tvm_autotuning/{}/{}_auto_tune_deploy_batch_{}_graph.json'.format(args.version,args.model_name,args.batch_size) , 'w') as f:
            f.write(graph)
        with open('/Outputs/tvm_autotuning/{}/{}_auto_tune_deploy_batch_{}_params.params'.format(args.version,args.model_name,args.batch_size) , 'wb') as f:
            f.write(relay.save_param_dict(params))

        # load parameters
        ctx = tvm.context(str(target), 0)
        module = runtime.create(graph, lib, ctx)
        data_tvm = tvm.nd.array((np.random.uniform(size=input_shape)).astype(dtype))
        module.set_input('data', data_tvm)
        module.set_input(**params)

        # evaluate
        print("Evaluate inference time cost...")
        ftimer = module.module.time_evaluator("run", ctx, number=1, repeat=600)
        prof_res = np.array(ftimer().results) * 1000  # convert to millisecond
        print("Mean inference time (std dev): %.2f ms (%.2f ms)" %
              (np.mean(prof_res), np.std(prof_res)))

# We do not run the tuning in our webpage server since it takes too long.
# Uncomment the following line to run it by yourself.

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='score a model on a dataset')
    parser.add_argument('--version', type=str, default='porno')
    parser.add_argument('--model-name', type=str, default='resnetv1d-101-320x320')
    parser.add_argument('--model-index', type=int, default=16)
    parser.add_argument('--batch-size', type=int, default=1)
    parser.add_argument('--tag', type=str, default='')

    args = parser.parse_args()

    if not os.path.exists(os.path.join('/Outputs/tvm_autotuning/{}'.format(args.version))):
        os.mkdir(os.path.join('/Outputs/tvm_autotuning/{}'.format(args.version)))

    #### DEVICE CONFIG ####
    target = tvm.target.cuda()

    #### TUNING OPTION ####
    log_file = '/Outputs/tvm_autotuning/{}/{}_batch_{}.log'.format(args.version, args.model_name, args.batch_size)
    dtype = 'float32'

    tuning_option = {
        'log_filename': log_file,

        'tuner': 'xgb',
        'n_trial': 2000,
        'early_stopping': 600,

        'measure_option': autotvm.measure_option(
            builder=autotvm.LocalBuilder(timeout=10),
            runner=autotvm.LocalRunner(number=20, repeat=3, timeout=4, min_repeat_ms=150),
            # runner=autotvm.RPCRunner(
            #     '1080ti',  # change the device key to your key
            #     '0.0.0.0', 9190,
            #     number=20, repeat=3, timeout=4, min_repeat_ms=150)
        ),
    }


tune_and_evaluate(tuning_option, target, log_file, dtype, args)


TensorRT

这里简单介绍一下TensorRT,也是模型加速的利器,并且tvm和tensorRT做的对与模型图的优化都差不多,可以参考。

TensorRT是Nvidia出品的用于将不同框架训练的模型部署到GPU的加速引擎,可以自动将不同框架的模型转换为TensorRT模型,并进行模型加速。

TensorRT进行模型加速主要有两点:

  • TensorRT支持int8以及FP16计算
  • TensorRT对网络进行重构以及优化:

去掉网络中的无用层

网络结构的垂直整合

网络结构的水平融合

在这里插入图片描述

原始网络

在这里插入图片描述

纵向融合

在这里插入图片描述

横向融合
参考资料

TVM官网: https://tvm.ai/

TVM论文:arxiv: https://arxiv.org/abs/1802.04799

tensorRT加速参考文献:https://blog.csdn.net/xh_hit/article/details/79769599

Nvidia参考文献:https://devblogs.nvidia.com/production-deep-learning-nvidia-gpu-inference-engine/

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

初识 TVM 的相关文章

  • 赛码-接金币-java

    题目描述 小赛非常喜欢玩游戏 最近喜欢上了一个接金币的游戏 在游戏中 使用帽子左右移动接金币 金币接的越多越好 但是金币掉到地上就不能再接了 为了方便问题的描述 我们把电脑屏幕分成11格 帽子每次能左右移动一格 现在给电脑屏幕如图标上坐标
  • 浅谈systemd

    浅谈systemd systemd的基本概念 Systemd的管理服务 Systemd的管理运行级别 systemd的基本概念 一 system的简要介绍 定义 系统启动和服务器守护进程管理器 负责在系统启动或运行时 激活系统资源 服务器进
  • 使用Docker安装Portainer

    打开powershell输入命令 docker run d p 9000 9000 restart always v var run docker sock var run docker sock name prtainer portain

随机推荐

  • 双轮平衡车实现自平衡功能

    1 功能说明 在双轮小车上安装一个六轴陀螺仪传感器 本文示例将实现双轮小车自主平衡功能 2 电子硬件 在这个示例中 我们采用了以下硬件 请大家参考 主控板 Basra主控板 兼容Arduino Uno 扩展板 Bigfish2 1扩展板 传
  • 【Java】Java中的“引用类型”和C中的“指针”区别

    文章目录 前言 1 Java中 基本数据类型 和 引用数据类型 变量在内存分配时的不同 2 C中 指针 的特点 3 Java中 引用 的特点 4 Java的参数传递 5 参考 前言 在学习Java中变量的数据类型时 发现其分为2大类 基本数
  • spring加载流程之ConfigurationClassPostProcessor

    spring加载流程之ConfigurationClassPostProcessor ConfigurationClassPostProcessor postProcessBeanDefinitionRegistry processConf
  • FPGA面试真题解析

    FPGA面试真题解析 1 1 十进制46 25对应的二进制表达式为 硬件逻辑实习岗 A 101110 11 B 101101 01 C 101110 1 D 101110 01 解析 这个问题看上去很简单 那是因为我们平时可以打开电脑上的计
  • 若依-了解头像(文件)上传流程

    周知 本文主要记录本人关于自主学习若依框架的部分心得经验 必定有许多不足甚至理解有误的部分 如果对你有帮助我也不胜欢喜 如果发现有错误的地方也希望能够分享指出 一起加油 需求原因 客户内部系统 一般不对外开放 因此不适合将文件 如图片 存放
  • 常用的函数接口

    常用的函数接口 FunctionalInterface public interface Function
  • R语言 if else 语句

    R语言中if else语句的编写格式 因为R是解释语言 如果else单独起一行 无法解释执行 所以else不能单独一行 最好这样写 if a print hello else print Hi 转载于 https www cnblogs c
  • 基于vue+element-ui实现Cascader级联选择器+Table树形数据

    开发进度提前50 啊 真香 下面 看图说话 Table树形数据 Cascader级联选择器 功能实现 详细代码 Cascader级联选择器 options属性指定选项数组即可渲染出一个级联选择器 所以后端接口返回的数据结构要保持一一致性 这
  • 记一次Redis批量删除Key问题

    记一次Redis批量删除Key问题 前言 最近在项目中使用redis时发现一个问题 批量删除的时候删除不了 代码如下 redis配置 Bean public RedisTemplate redisTemplate RedisConnecti
  • C/C++中关于位域的一些总结

    转载自 http blog csdn net xkjcf article details 7688528 由于信息存储时 可能只占一位或者几位二进制位 比如开关量 只需要占据一位即可 为了节省存储空间 并且处理简单 C语言提供了一种数据结构
  • 点云地图三维表面重建

    通过对点云进行表面三角化mesh重建 可以使得点云地图更加轻量化 同时针对地面 红色 和非地面 蓝色 使用不同采样率的三角面片顶点 可以进一步减少地图数据量
  • 面试准备:Java常见面试题汇总(二)

    面试准备 Java常见面试题汇总 一 面试准备 Java常见面试题汇总 二 面试准备 Java常见面试题汇总 三 文章目录 43 java 中的 Math round 1 5 等于多少 44 String str abc 与 String
  • CAP简述-一致性、可用性、分区容忍性

    一致性 Consistency 是指在同一时刻 分布式系统中的所有数据备份为相同值 可用性 Availability 指集群中的某一个节点故障宕机后 集群还能响应客户端请求 即假设一个节点挂 另一个备份节点要顶上 分区容忍性 Partiti
  • 保姆级教程!将 Vim 打造一个 IDE (Python 篇)

    从上周开始我就开始折腾 搞了一下 Vim IDE for Python Go 我将整个搭建的过程整理成本篇文章分享出来 本篇是 Python 版本的保姆级教程 实际上我还写了 Go 版本的 有想看的可以本篇文章点个赞 我下篇就发 一说到 I
  • Linux环境安装Jenkins(详细,亲测可行)

    1 基础环境 Linux java环境 linux安装java1 8 拒绝 emo的博客 CSDN博客 rpm下载 Index of jenkins redhat stable 清华大学开源软件镜像站 Tsinghua Open Sourc
  • 总结一些小细节 ---- Android

    1 Null pointer dereference of parent getItemAtPosition where null comes from constant This error always happened in the
  • Vue注册全局方法,全局组件,全局过滤器,全局自定义指令的方法

    1 添加全局方法 1 使用Vue prototype 在main js中写 Vue prototype getData params gt 2 使用install Vue prototype 在你的全局函数文件fun js中写 export
  • 邻接矩阵的存储方式实现图的广度和深度优先遍历

    在做图的邻接矩阵之前 先做好准备工作 定义存储类型 声明队列的操作 在广度优先遍历中使用 include
  • CSS之定位属性

    一定位介绍 定位的基本思想很简单 它允许你定义元素出现在页面的任何位置 是通过position属性配合left right 等来实 现的 二定位类别 1 相对定位 相对定位就是相对于自己以前在标准流中的位置来移动 语法 position r
  • 初识 TVM

    如有图像或公式显示错误 可以访问我的个人博客 https www wanglichun tech 2019 11 15 tvm 笔者也是最近偶然的机会才开始接触TVM 使用过后发现 经过auto tuning后的TVM模型在速度是竟然超过了