上一个文档(传送门如下),我们讲了TVM在wins下安装教程,
安装完以后用一个preprocess.py前处理程序验证已安装成功TVM,接下来我们就上手一个量化模型例子吧!
不过,在庖丁解牛之前,我们还是有必要了解下整个TVM的流程。先打好基础,再上手代码会更好一些。
1. TVM整体流程介绍
TVM框架处理框图
(1)模型导入
从Tensorflow/pytorch或ONNX等框架导入模型
import层是TVM从其他框架中导入模型的地方
注:TVM为每个前端提供的支持水平不尽相同,若导入有问题,可尝试将模型转换为ONNX再导入;
(2)转换到Relay
Relay是TVM的高级模型语言,导入到TVM的模型是用Relay表示的。Relay是一种函数式语言(function language)和神经网络的中间表示法(intermediate representation,IR),它支持以下内容:
传统的数据流图式表示法
Functional-style scoping 和 let-binding 使其成为一种功能齐全的可微分语言
能够允许用户混合两种编程风格 Relay应用图级(graph-level)优化passes来优化模型
(3)TE表示:Lower to Tensor Expression (TE) representation
lower是指较高层表示转换成较低层表示。在high-level经过优化后,Relay 运行 FuseOps,将模型分割成许多小的子图,并将子图 lower 到 TE 表示。TE 即Tensor Expression,张量表达,是描述张量计算的专属性语言。TE 还提供了一些schedule 原语来指定低级的循环优化,例如平铺(tiling)、矢量化(vectorization)、并行化(parallelization)、unrolling 和 fusion。
为了帮助将 Relay 表示转换为 TE 表示的过程,TVM 包含张量算子清单(Tensor Operator Inventory, TOPI),它有预先定义的常见张量算子的模板(如 conv2d、transpose)。
(4)Search for the best schedule using the auto-tuning module AutoTVM or AutoScheduler.
schedule 指定在 TE 中定义了算子或子图的低级循环优化。auto-tuning 模块搜索最佳 schedule 并将其与 cost 模型和设备上的测量结果进行比较。
在 TVM 中,有两个 auto-tuning 模块:
- AutoTVM: 基于模板的 auto-tuning 模块。它运行搜索算法为用户定义的模板中的可调节旋钮找到最佳值。对于常见的运算符,其模板已经在 TOPI 中提供。
- AutoScheduler (别名 Ansor) :无模板的auto-tuning 模块。它不需要预先定义的 schedule 模板。相反,它通过分析计算的定义自动生成搜索空间。然后,它在生成的搜索空间中搜索最佳 schedule。
(5)Choose the optimal configurations for model compilation.
tuning 后,auto-tuning 模块会生成 JSON 格式的 auto-tuning 记录。这一步为每个子图挑选出最佳的 schedule。
(6)Lower to Tensor Intermediate Representation (TIR),TVM's low-level intermediate representation
TIR 是张量级的中间表示(Tensor Intermediate Representation),TVM 的低层次中间表示。
在根据 tuning 步骤选择最佳配置后,每个 TE 子图被降低到 TIR,并通过低级别的优化 passes 进行优化。
接下来,优化后的 TIR 被 lower 到硬件平台的目标编译器中。这是最后的代码生成阶段,产生可以部署到生产中的优化模型。
TVM 支持几种不同的编译器后端,包括:
- LVM:它可以针对任意的微处理器架构,包括 标准 x86 和 ARM 处理器,AMDGPU 和 NVPTX 代码生成,以及 LLVM 支持的任何其他平台。
- 专门的编译器,如 NVCC,NVIDIA 的编译器。
- 嵌入式和专用目标,通过 TVM 的 Bring Your Own Codegen(BYOC)框架实现。
(7)Compile down to machine code.
在这个过程结束时,特定的编译器生成的代码可以 lower 为机器码。
TVM 可以将模型编译成可链接的对象模块,然后可以用轻量级的 TVM 运行时来运行,该运行时提供 C 语言的 API 来动态加载模型,以及其他语言的入口,如 Python 和 Rust。TVM 还可以建立捆绑式部署,其中运行时与模型结合在一个包中。
2. 实例上手
2-1:前序工作: 环境:cp37, cpu,
1)pip install onnx onnxoptimizer
2)pip install torch
3)pip install torchvision
4)安装LLSM,链接在上一文的文末,打开链接选择下述下载安装:
2-2:代码展示
# -*- coding: utf-8 -*-
"""
Created on Wed Aug 31 15:39:54 2022
@author: Albert, emAIl: 196659042@qq.com
"""
#导入库
import tvm
from tvm import relay
import numpy as np
from tvm.contrib.download import download_testdata
# PyTorch imports
import torch
import torchvision
#加载pytorch预训练模型
model_name = "resnet101" # 可resnet18, resnet101
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()
#加载一张测试图片 - 经典的猫图片
from PIL import Image
img_url = "https://s3.amazonaws.com/model-server/inputs/kitten.jpg"
img_path = download_testdata(img_url, "imagenet_cat.png", module="data")
img = Image.open(img_path).resize((224, 224))
img.show()
# Preprocess the image and convert to tensor
from torchvision import transforms
my_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)
img = np.expand_dims(img, 0)
#Import the graph to Relay
# 将Pytorch 图转换为Relay图,Input name是随意的
input_name = "input0"
shape_list = [(input_name, img.shape)]
mod, params = relay.frontend.from_pytorch(scripted_model, shape_list)
#Relay Build
# 使用给定的输入将graph图编译成llvm目标
target = tvm.target.Target("llvm", host="llvm")
dev = tvm.cpu(0)
with tvm.transform.PassContext(opt_level=3):
lib = relay.build(mod, target=target, params=params)
##% TVM上执行可移植图(Execute the portable graph on TVM)
# 现在,尝试在目标上部署已编译的模型
from tvm.contrib import graph_executor
dtype = "float32"
m = graph_executor.GraphModule(lib["default"](dev))
# Set inputs
m.set_input(input_name, tvm.nd.array(img.astype(dtype)))
# Execute
m.run()
# Get outputs
tvm_output = m.get_output(0)
#%% 输出后处理,将TVM输出结果转化为更易读的结果
#其中的imagenet_synsets.txt和imagenet_classes.txt两个文件,url链接出错的话可自行下载:
# https://github.com/Cadene/pretrained-models.pytorch/blob/master/data/ 下
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.numpy()[0])
tvm_class_key = class_id_to_key[top1_tvm]
# Convert input to PyTorch variable and get PyTorch result for comparison
with torch.no_grad():
torch_img = torch.from_numpy(img)
output = model(torch_img)
# Get top-1 result for PyTorch
top1_torch = np.argmax(output.numpy())
torch_class_key = class_id_to_key[top1_torch]
print("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]))
输出结果为:
输出结果
当然,imshow语句会显示出小猫咪图片,嘿嘿
imagenet_cat.png
【注】上述代码过程是对比了torch模型预测结果和将Resnet模型用TVM runtime编译后的模型预测输出,还不涉及到TVM的量化处理过程;关于TVM的量化处理后续文章会有的~
代码见:https://github.com/Learneruestc/TVM-all/tree/main/TVM1
先这样吧,有问题同学留言交流,后续再回头补充完善!
3. 后续工作
3-1:将TVM过程更细拆分出来,更好理解整个过程;
参考链接:
1)博客:从一个例子看tvm执行流程 - 牛犁heart - 博客园
2)官网:Compiling and Optimizing a Model with the Python Interface (AutoTVM)
姊妹篇传送:
上一篇:女的51岁交了8年的社保可以领失业金吗? 下一篇:初三了还有机会吗? |