【TVM系列教程一】深度学习编译器及TVM 介绍

article/2025/10/3 19:09:23

0x0. 介绍

大家好呀,在过去的半年到一年时间里,我分享了一些算法解读,算法优化,模型转换相关的一些文章。这篇文章是自己开启学习深度学习编译器的第一篇文章,后续也会努力更新这个系列。这篇文章是开篇,所以我不会太深入讲解TVM的知识,更多的是介绍一下深度学习编译器和TVM是什么?以及为什么我要选择学习TVM,最后我也会给出一个让读者快速体验TVM效果的一个开发环境搭建的简要教程以及一个简单例子。

0x1. 为什么需要深度学习编译器?

深度学习编译器这个词语,我们可以先拆成两个部分来看。

首先谈谈深度学习领域。从训练框架角度来看,Google的TensorFlow和FaceBook的Pytorch是全球主流的深度学习框架,另外亚马逊的MxNet,百度的Paddle,旷视的MegEngine,华为的Mindspore以及一流科技的OneFlow也逐渐在被更多人接受和使用。这么多训练框架,我们究竟应该选择哪个?如果追求易用性,可能你会选择Pytorch,如果追求项目部署落地,可能你会选择TensorFlow,如果追求分布式训练最快可能你会体验OneFlow。所以这个选择题没有确定答案,在于你自己的喜好。从推理框架角度来看,无论我们选择何种训练框架训练模型,我们最终都是要将训练好的模型部署到实际场景的,在模型部署的时候我们会发现我们要部署的设备可能是五花八门的,例如Intel CPU/Nvidia GPU/Intel GPU/Arm CPU/Arm GPU/FPGA/NPU(华为海思)/BPU(地平线)/MLU(寒武纪),如果我们要手写一个用于推理的框架在所有可能部署的设备上都达到良好的性能并且易于使用是一件非常困难的事。

一般要部署模型到一个指定设备上,我们一般会使用硬件厂商自己推出的一些前向推理框架,例如在Intel的CPU/GPU上就使用OpenVINO,在Arm的CPU/GPU上使用NCNN/MNN等,在Nvidia GPU上使用TensorRT。虽然针对不同的硬件设备我们使用特定的推理框架进行部署是最优的,但这也同时存在问题,比如一个开发者训练了一个模型需要在多个不同类型的设备上进行部署,那么开发者需要将训练的模型分别转换到特定框架可以读取的格式,并且还要考虑各个推理框架OP实现是否完全对齐的问题,然后在不同平台部署时还容易出现的问题是开发者训练的模型在一个硬件上可以高效推理,部署到另外一个硬件上性能骤降。并且从之前几篇探索ONNX的文章来看,不同框架间模型转换工作也是阻碍各种训练框架模型快速落地的一大原因。

接下来,我们要简单描述一下编译器。实际上在编译器发展的早期也和要将各种深度学习训练框架的模型部署到各种硬件面临的情况一下,历史上出现了非常多的编程语言,比如C/C++/Java等等,然后每一种硬件对应了一门特定的编程语言,再通过特定的编译器去进行编译产生机器码,可以想象随着硬件和语言的增多,编译器的维护难度是多么困难。还好现代的编译器已经解决了这个问题,那么这个问题编译器具体是怎么解决的呢?

为了解决上面的问题,科学家为编译器抽象出了编译器前端,编译器中端,编译器后端等概念,并引入IR (Intermediate Representation)的概率。解释如下:

  • 编译器前端:接收C/C++/Java等不同语言,进行代码生成,吐出IR

  • 编译器中端:接收IR,进行不同编译器后端可以共享的优化,如常量替换,死代码消除,循环优化等,吐出优化后的IR

  • 编译器后端:接收优化后的IR,进行不同硬件的平台相关优化与硬件指令生成,吐出目标文件

以LLVM编译器为例子,借用蓝色(知乎ID)大佬的图:

5954953457c391e298ef3ee359512413.png
编译器抽象

受到编译器解决方法的启发,深度学习编译器被提出,我们可以将各个训练框架训练出来的模型看作各种编程语言,然后将这些模型传入深度学习编译器之后吐出IR,由于深度学习的IR其实就是计算图,所以可以直接叫作Graph IR。针对这些Graph IR可以做一些计算图优化再吐出IR分发给各种硬件使用。这样,深度学习编译器的过程就和传统的编译器类似,可以解决上面提到的很多繁琐的问题。仍然引用蓝色大佬的图来表示这个思想。

70c231f34044f954e9e16f7442b2bf06.png
深度学习编译器抽象

0x02. TVM

基于上面深度学习编译器的思想,陈天奇领衔的TVM横空出世。TVM就是一个基于编译优化的深度学习推理框架(暂且说是推理吧,训练功能似乎也开始探索和接入了),我们来看一下TVM的架构图。来自于:https://tvm.apache.org/2017/10/06/nnvm-compiler-announcement

d8084feab18bdb4ca295771badeea0da.png
TVM架构图

从这个图中我们可以看到,TVM架构的核心部分就是NNVM编译器(注意一下最新的TVM已经将NNVM升级为了Realy,所以后面提到的Relay也可以看作是NNVM)。NNVM编译器支持直接接收深度学习框架的模型,如TensorFlow/Pytorch/Caffe/MxNet等,同时也支持一些模型的中间格式如ONNX、CoreML。这些模型被NNVM直接编译成Graph IR,然后这些Graph IR被再次优化,吐出优化后的Graph IR,最后对于不同的后端这些Graph IR都会被编译为特定后端可以识别的机器码完成模型推理。比如对于CPU,NNVM就吐出LLVM可以识别的IR,再通过LLVM编译器编译为机器码到CPU上执行。

0x03. 环境配置

工欲善其事,必先利其器,再继续探索TVM之前我们先了解一下TVM的安装流程。这里参考着官方的安装文档提供两种方法。

0x03.1 基于Docker的方式

我们可以直接拉安装配置好TVM的docker,在docker中使用TVM,这是最快捷最方便的。例如拉取一个编译了cuda后端支持的TVM镜像,并启动容器的示例如下:

docker pull tvmai/demo-gpu
nvidia-docker run --rm -it tvmai/demo-gpu bash

这样就可以成功进入配置好tvm的容器并且使用TVM了。

0x03.2 本地编译以Ubuntu为例

如果有修改TVM源码或者给TVM贡献的需求,可以本地编译TVM,以Ubuntu为例编译和配置的流程如下:

git clone --recursive https://github.com/apache/tvm tvm
cd tvm
mkdir build
cp cmake/config.cmake build
cd build
cmake ..
make -j4
export TVM_HOME=/path/to/tvm
export PYTHONPATH=$TVM_HOME/python:${PYTHONPATH}

这样我们就配置好了TVM,可以进行开发和测试了。

我的建议是本地开发和调试使用后面的方式,工业部署使用Docker的方式。

0x04. 样例展示

在展示样例前说一下我的环境配置,pytorch1.7.0 && TVM 0.8.dev0

这里以Pytorch模型为例,展示一下TVM是如何将Pytorch模型通过Relay(可以理解为NNVM的升级版,)构建TVM中的计算图并进行图优化,最后再通过LLVM编译到Intel CPU上进行执行。最后我们还对比了一下基于TVM优化后的Relay Graph推理速度和直接使用Pytorch模型进行推理的速度。这里是以torchvision中的ResNet18为例子,结果如下:

Relay top-1 id: 282, class name: tiger cat
Torch top-1 id: 282, class name: tiger cat
Relay time:  1.1846002000000027 seconds
Torch time:  2.4181047000000007 seconds

可以看到在预测结果完全一致的情况下,TVM能带来2倍左右的加速。这里简单介绍一下代码的流程。这个代码可以在这里(https://github.com/BBuf/tvm_learn)找到。

0x04.1 导入TVM和Pytorch并加载ResNet18模型

import time
import tvm
from tvm import relayimport numpy as npfrom tvm.contrib.download import download_testdata# PyTorch imports
import torch
import torchvision######################################################################
# Load a pretrained PyTorch model
# -------------------------------
model_name = "resnet18"
model = getattr(torchvision.models, model_name)(pretrained=True)
model = model.eval()# We grab the TorchScripted model via tracing
input_shape = [1, 3, 224, 224]
input_data = torch.randn(input_shape)
scripted_model = torch.jit.trace(model, input_data).eval()

需要注意的是Relay在解析Pytorch模型的时候是解析TorchScript格式的模型,所以这里使用torch.jit.trace跑一遍原始的Pytorch模型并导出TorchScript模型。

0x04.2 载入测试图片

加载一张测试图片,并执行一些后处理过程。

from PIL import Imageimg_url = "https://github.com/dmlc/mxnet.js/blob/main/data/cat.png?raw=true"
img_path = download_testdata(img_url, "cat.png", module="data")
img = Image.open(img_path).resize((224, 224))# Preprocess the image and convert to tensor
from torchvision import transformsmy_preprocess = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),]
)
img = my_preprocess(img)
# 新增Batch维度
img = np.expand_dims(img, 0)

0x04.3 Relay导入TorchScript模型并编译到LLVM后端

接下来我们将PyTorch的graph导入到Relay成为Relay Graph,这里输入层的名字可以任意指定。然后将Gpath使用给定的配置编译到LLVM目标硬件上。

######################################################################
# Import the graph to Relay
# -------------------------
# Convert PyTorch graph to Relay graph. The input name can be arbitrary.
input_name = "input0"
shape_list = [(input_name, img.shape)]
mod, params = relay.frontend.from_pytorch(scripted_model, shape_list)######################################################################
# Relay Build
# -----------
# Compile the graph to llvm target with given input specification.
target = "llvm"
target_host = "llvm"
ctx = tvm.cpu(0)
with tvm.transform.PassContext(opt_level=3):lib = relay.build(mod, target=target, target_host=target_host, params=params)

0x04.4 在目标硬件上进行推理并输出分类结果

这里加了一个计时函数用来记录推理的耗时情况。

######################################################################
# Execute the portable graph on TVM
# ---------------------------------
# Now we can try deploying the compiled model on target.
from tvm.contrib import graph_runtimetvm_t0 = time.clock()
for i in range(10):dtype = "float32"m = graph_runtime.GraphModule(lib["default"](ctx))# Set inputsm.set_input(input_name, tvm.nd.array(img.astype(dtype)))# Executem.run()# Get outputstvm_output = m.get_output(0)
tvm_t1 = time.clock()

接下来我们在1000类的字典里面查询一下Top1概率对应的类别并输出,同时也用Pytorch跑一下原始模型看看两者的结果是否一致和推理耗时情况。

#####################################################################
# Look up synset name
# -------------------
# Look up prediction top 1 index in 1000 class synset.
synset_url = "".join(["https://raw.githubusercontent.com/Cadene/","pretrained-models.pytorch/master/data/","imagenet_synsets.txt",]
)
synset_name = "imagenet_synsets.txt"
synset_path = download_testdata(synset_url, synset_name, module="data")
with open(synset_path) as f:synsets = f.readlines()synsets = [x.strip() for x in synsets]
splits = [line.split(" ") for line in synsets]
key_to_classname = {spl[0]: " ".join(spl[1:]) for spl in splits}class_url = "".join(["https://raw.githubusercontent.com/Cadene/","pretrained-models.pytorch/master/data/","imagenet_classes.txt",]
)
class_name = "imagenet_classes.txt"
class_path = download_testdata(class_url, class_name, module="data")
with open(class_path) as f:class_id_to_key = f.readlines()class_id_to_key = [x.strip() for x in class_id_to_key]# Get top-1 result for TVM
top1_tvm = np.argmax(tvm_output.asnumpy()[0])
tvm_class_key = class_id_to_key[top1_tvm]# Convert input to PyTorch variable and get PyTorch result for comparison
torch_t0 = time.clock()
for i in range(10):with torch.no_grad():torch_img = torch.from_numpy(img)output = model(torch_img)# Get top-1 result for PyTorchtop1_torch = np.argmax(output.numpy())torch_class_key = class_id_to_key[top1_torch]
torch_t1 = time.clock()tvm_time = tvm_t1 - tvm_t0
torch_time = torch_t1 - torch_t0print("Relay top-1 id: {}, class name: {}".format(top1_tvm, key_to_classname[tvm_class_key]))
print("Torch top-1 id: {}, class name: {}".format(top1_torch, key_to_classname[torch_class_key]))
print('Relay time: ', tvm_time / 10.0, 'seconds')
print('Torch time: ', torch_time / 10.0, 'seconds')

0x05. 小节

这一节是对TVM的初步介绍,暂时讲到这里,后面的文章会继续深度了解和介绍深度学习编译器相关的知识。

0x06. 参考

  • http://tvm.apache.org/docs/tutorials/frontend/from_pytorch.html#sphx-glr-tutorials-frontend-from-pytorch-py

  • https://zhuanlan.zhihu.com/p/50529704


欢迎关注GiantPandaCV, 在这里你将看到独家的深度学习分享,坚持原创,每天分享我们学习到的新鲜知识。( • ̀ω•́ )✧

有对文章相关的问题,或者想要加入交流群,欢迎添加BBuf微信:

cd689eeaf5d0cca0360697f751746f18.png
二维码

为了方便读者获取资料以及我们公众号的作者发布一些Github工程的更新,我们成立了一个QQ群,二维码如下,感兴趣可以加入。

0d2b71673d5e063d3f6930d2b2a115b5.png
公众号QQ交流群

http://chatgpt.dhexx.cn/article/hR9KtVbS.shtml

相关文章

TVM运行系统

TVM运行系统 TVM支持多种编程语言用于编译器堆栈的开发和部署。在本说明中,我们解释了TVM运行时的关键元素。 我们需要满足很多有趣的要求: 部署:从python / javascript / c 语言调用已编译的函数。 调试:在python中定义一个函数…

tvm的一个大体介绍

TVM的一个大体介绍 导入模型模型转换到relay转换到 _tensor_ _expression_ (TE)自动优化调度模型编译转换到TIR(tensor IR)编译器编译到机器码 导入模型 可以支持从tf,pytorch,或者onnx框架中导入模型。 模型转换到relay 上述框…

TVM 架构设计

TVM 架构设计 本文面向希望了解TVM体系结构和/或,积极参与项目开发的开发人员。 主要内容如下: 示例编译流程,概述了TVM将模型的高级概念,转换为可部署模块的步骤。 逻辑架构组件部分,描述逻辑组件。针对每个逻辑组…

TVM的安装过程

最近在看深度学习编译器相关的工作,其中FlexTensor给我留下了比较深刻的印象,加上这项工作是开源的,所以想看看这份工作的源码。首先是怎么把工程跑起来,FlexTensor倚仗TVM做代码生成,所以首先得安装TVM。 首先给出官…

TVM系列 - 图优化 - 算子融合

TVM系列 - 图优化 - 算子融合 图优化综述 声明一下,本文所有的理解都是基于个人理解。 图优化算是一个推理框架前端比较成熟的操作了,一般来说,针对模型做图优化有两个目的(对于通用框架来说,就加速减少计算一个目的…

TVM 从入门到精通 | 安装 TVM (Part 2)

本文首发自:公众号 HyperAI超神经 内容一览:TVM 共有三种安装方法:从源码安装、使用 Docker 镜像安装和 NNPACK Contrib 安装。本文讲解如何通过 Docker 镜像 和 NNPACK Contrib 安装。 关键词:TVM Docker 基础教程 欢迎回…

初识 TVM

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

TVM(端到端深度学习编译器)简介

TVM-算子编译器前后端 前言TVM出现背景TVM是什么为什么用TVM,TVM解决了什么当前问题:TVM解决了 TVM如何解决具体实现手段如何设计搜索空间Search Space优化策略图优化 - 算子融合图优化 - Layout Transform张量优化 - 矩阵乘法 GEMM张量优化 - 调度算法张…

tvm学习笔记(五):tvm工作原理

一、总体流程: TVM的工作流程:首先,将网络表示成统一的表示形式(Intermediate Representation),并进行一些可重用的图优化;然后,利用不同的后端生成对应设备代码,如图1所…

TVM系列 - 量化

TVM系列 - 量化 TVM量化原理TVM量化现状TVM量化原理介绍TVM量化代码解析 TVM量化原理 关于量化的方式其实已经有足够的文章去了解目前最主流的两种:离线量化及训练时量化(大家应该能理解,其实就是伪量化),而tvm的作者…

TVM-初识TVM

目录 TVM简介那么TVM是什么?TVM做了哪些工作 TVM简介 随着深度学习的发展,深度学习的能力可以说是越来越强大,识别率节节攀升,与此同时,深度学习框架也变得越来越多,目前比较主流的深度学习框架包括&#…

【TVM系列二】TVM介绍

文章同步更新在公众号 AIPlayer,欢迎扫码关注,共同进步 目录 一、TVM的工作流程 1、整体流程 2、关键数据结构 3、Transformations 4、搜索空间和基于机器学习的转换 5、目标代码转化 二、逻辑架构组件 三、运行TVM实例 1、交叉编译runtime 2、…

TVM:简介

TVM:简介概述 Apache TVM 是一个用于 CPU、GPU 和机器学习加速器的开源机器学习编译器框架。它旨在使机器学习工程师能够在任何硬件后端上高效地优化和运行计算。本教程的目的是通过定义和演示关键概念,引导您了解 TVM 的所有主要功能。新用户应该能够从…

TVM简介

TVM与LLVM的架构非常相似。TVM针对不同的深度学习框架和硬件平台,实现了统一的软件栈,以尽可能高效的方式,将不同框架下的深度学习模型部署到硬件平台上。 如果从编译器的视角来看待如何解决这个问题,各种框架写的网络可以根据特…

TVM概述

TVM TVM是陈天奇领导的一个DL加速框架项目。它处于DL框架(如tensorflow、pytorch)和硬件后端(如CUDA、OpenCL)之间,兼顾了前者的易用性和后者的执行效率。 官网: https://tvm.apache.org/ 代码&#xf…

TVM[2] —— TVM简介和发展

TVM[2] —— TVM简介和发展 文章目录 TVM[2] —— TVM简介和发展1. TVM 简介1.1 是什么1.2 做什么1.3 基本步骤 2. TVM 的发展2.1 现状——四类抽象2.2 问题——两向boundary2.3 未来——从箭头到圈2.4 New Capabilities with Unity 下期预告:3. TVM 技术栈全解析&a…

TVM 学习指南(个人版)

文章目录 0x0. 前言0x1. 前端0x1.1 Tensor IR(TIR)0x1.2 了解tvm.ir基础设施0x1.3 Relay IR0x1.4 RelaxD0:数据流块作为第一优先级的构造D1:形状推导作为第一优先级的计算D1a: match_shapeD1b. 从符号整数元组构造ShapeShape传播的方法Implications for …

一步一步解读神经网络编译器TVM(一)——一个简单的例子

TOC 前言 这是一个TVM教程系列,计划从TVM的使用说明,再到TVM的内部源码?为大家大致解析一下TVM的基本工作原理。因为TVM的中文资料比较少,也希望贡献一下自己的力量,如有描述方面的错误,请及时指出。 那啥是TVM&am…

P29 JTextArea文本域

P29 JTextArea文本域 1.概述2.代码实例3.效果演示 系统:Win10 Java:1.8.0_333 IDEA:2020.3.4 Gitee:https://gitee.com/lijinjiang01/JavaSwing 1.概述 JTextArea:文本区域。JTextArea 用来编辑多行的文本。JTextArea…

java 刷新jtextarea_Java JTextArea不能实时刷新的问题

相信JTextArea用法都了解吧, JTextArea textArea new JTextArea();生成一块空白的区域, 我的需求就是点击发送邮件按钮后,后台的执行日志能输出到textArea中。 但是我点击发送按钮的时候,由于邮件的附件要好久,界面一…