【TVM帮助文档学习】使用TVMC编译和优化模型

 本文翻译自Compiling and Optimizing a Model with TVMC — tvm 0.9.dev0 documentation

在本节中,我们将使用TVM命令行驱动程序TVMC。TVMC是一个工具,它通过命令行接口公开TVM特性,如模型的自动调优、编译、分析和执行。

在完成这部分工作后,我们将利用TVMC完成以下工作:

  • 为TVM运行时编译一个训练好的ResNet-50 v2模型。
  • 在编译后的模型中运行一个真实的图像,并解释输出和模型性能。
  • 使用TVM在CPU上调优模型。
  • 使用TVM收集的调优数据重新编译优化模型。
  • 通过优化后的模型运行图像,并比较输出和模型性能。

本节的目标是概述TVM和TVMC的功能,并为理解TVM的工作方式奠定基础。

TVMC的使用

TVMC是一个Python应用程序,是TVM Python包的一部分。当你使用Python包安装TVM时,将会安装命令行应用程序TVMC。TVMC命令的位置取决于你的平台和安装方法。

如果你在环境变量$PYTHONPATH中添加了TVM路径,就可以通过命令python -m tvm.driver.tvmc来访问命令行提供的功能。

为简单起见,本教程将介绍TVMC命令tvmc <options>的使用,它等同于python -m tvm.driver.tvmc <options>

你可以使用以下命令查看帮助页面:

tvmc --help

tvmc执行的命令涉及的TVM特性包括编译、运行和调优等。使用tvmc <subcommand> --help可以查看子命令subcommand的参数选项。我们将在本教程中介绍这些命令,但首先我们需要为此下载一个预先训练好的模型。

获取模型 

 在本教程中,我们将使用ResNet-50 v2模型。ResNet-50是一个用于图像分类的,50层的卷积神经网络。我们使用的模型是已经训练好的,训练的数据集有1000种分类100多万张图像。该网络的输入图像尺寸为224x224。如果你对ResNet-50模型的结构感兴趣,我们建议下载Netron,这是一个免费的ML模型查看器。

在本教程中,我们将使用ONNX格式的模型。

get https://github.com/onnx/models/raw/main/vision/classification/resnet/model/resnet50-v2-7.onnx

 TVMC支持模型有Keras, ONNX, TensorFlow, TFLite和Torch。使用--model-format选项可以查看模型格式。有关更多信息,请参阅tvmc compile --help。

TVM依赖ONNX python库。你可以使用命令pip3 install --user onnx onnxoptimizer来安装ONNX。如果你有root权限并且想全局安装ONNX,可以去掉--user选项。onnxoptimizer依赖项是可选的,仅用于onnx>=1.9版本。

ONNX模型编译为TVM运行时 

下载ResNet-50模型后,我们使用tvmc编译它。模型编译得到的将是一个目标设备平台上的动态库。我们可以使用TVM运行时在目标设备上运行该模型。

# This may take several minutes depending on your machine
tvmc compile \
--target "llvm" \
--output resnet50-v2-7-tvm.tar \
resnet50-v2-7.onnx

 我们来看看tvmc compile生成了哪些文件:

mkdir model
tar -xvf resnet50-v2-7-tvm.tar -C model
ls model

看到的有三个文件:

  • mod.so是一个用c++库表示的模型,可以被TVM运行时加载。
  • mod.json是TVM Relay计算图的文本表示。
  • mod.params是一个包含预训练模型参数的文件。

该模块可以由应用程序直接加载,模型可以通过TVM运行时API运行。 

 正确的指定目标(--target选项)可能会对编译得到的模块的性能产生巨大影响,因为它可以利用目标上可用的硬件特性。更多信息可参阅Auto-tuning a Convolutional Network for x86 CPU — tvm 0.9.dev0 documentation。我们建议你先确定使用的CPU和可选的特性,并适当地设置目标。

 使用TVMC运行编译好的模型

现在我们已经将模型编译到模块中,然后就可以使用TVM运行时对其进行预测了。TVMC内置了TVM运行时,允许您运行编译后的TVM模型。为了使用TVMC运行模型并进行预测,我们需要具备两个条件:

  • 刚刚编译生成的模块
  • 对模型进行预测的有效输入

每个模型都有特定的张量形状、格式和数据类型。所以大多数模型需要一些预处理和后处理,以确保输入正确,并解释输出。TVMC的输入和输出数据都采用了NumPy的.npz格式。这是一种支持良好的NumPy格式,可以将多个数组序列化存入到一个文件中。

作为本教程的输入,我们将使用一只猫的图像,您也可以替换为其他任何图像。

输入预处理 

我们使用的ResNet-50 v2模型预期输入为ImageNet格式。下面是一个对ResNet-50 v2输入图像做预处理的脚本示例。

这个脚本要求环境支持Python Image库。您可以使用pip3 install --user pillow安装Image

#!python ./preprocess.py
from tvm.contrib.download import download_testdata
from PIL import Image
import numpy as np

img_url = "https://s3.amazonaws.com/model-server/inputs/kitten.jpg"
img_path = download_testdata(img_url, "imagenet_cat.png", module="data")

# Resize it to 224x224
resized_image = Image.open(img_path).resize((224, 224))
img_data = np.asarray(resized_image).astype("float32")

# ONNX expects NCHW input, so convert the array
img_data = np.transpose(img_data, (2, 0, 1))

# Normalize according to ImageNet
imagenet_mean = np.array([0.485, 0.456, 0.406])
imagenet_stddev = np.array([0.229, 0.224, 0.225])
norm_img_data = np.zeros(img_data.shape).astype("float32")
for i in range(img_data.shape[0]):
      norm_img_data[i, :, :] = (img_data[i, :, :] / 255 - imagenet_mean[i]) / imagenet_stddev[i]

# Add batch dimension
img_data = np.expand_dims(norm_img_data, axis=0)

# Save to .npz (outputs imagenet_cat.npz)
np.savez("imagenet_cat", data=img_data)

运行编译后的模型 

 有了模型和输入数据,我们现在可以运行TVMC进行预测:

tvmc run \
--inputs imagenet_cat.npz \
--output predictions.npz \
resnet50-v2-7-tvm.tar

 回想一下,.tar模型文件包括一个C++库、一个Relay模型的描述,以及模型的参数。TVMC包括TVM运行时,运行时可以加载模型并根据输入进行预测。当运行上面的命令时,TVMC生成文件predictions. npz,它包含NumPy格式的模型输出张量。

在本例中模型编译和运行在同一台机器上。在某些情况下,我们可能希望通过RPC Tracker远程运行模型。要了解更多相关选项,请查看tvmc run --help。

输出后处理 

正如前面提到的,每个模型都有自己特定的输出张量。

在我们的示例中,我们需要对输出做一些后处理,同时提供一个查找表,将ResNet-50 v2的输出翻译为便于人类阅读的形式。

下面的脚本是从输出中提取标签的后处理示例。

#!python ./postprocess.py
import os.path
import numpy as np

from scipy.special import softmax

from tvm.contrib.download import download_testdata

# Download a list of labels
labels_url = "https://s3.amazonaws.com/onnx-model-zoo/synset.txt"
labels_path = download_testdata(labels_url, "synset.txt", module="data")

with open(labels_path, "r") as f:
    labels = [l.rstrip() for l in f]

output_file = "predictions.npz"

# Open the output and read the output tensor
if os.path.exists(output_file):
    with np.load(output_file) as data:
        scores = softmax(data["output_0"])
        scores = np.squeeze(scores)
        ranks = np.argsort(scores)[::-1]

        for rank in ranks[0:5]:
            print("class='%s' with probability=%f" % (labels[rank], scores[rank]))

运行脚本将产生下面的输出: 

python postprocess.py
# class='n02123045 tabby, tabby cat' with probability=0.610553
# class='n02123159 tiger cat' with probability=0.367179
# class='n02124075 Egyptian cat' with probability=0.019365
# class='n02129604 tiger, Panthera tigris' with probability=0.001273
# class='n04040759 radiator' with probability=0.000261

 试着用其他图片替换猫的图像,看看ResNet模型会做出什么样的预测。

ResNet模型自动调优

 前面我们将模型编译为TVM运行时,不包括任何针对特定平台的优化。在本节中,我们将向您展示如何使用TVMC构建一个针对您的工作平台的优化模型。

在某些情况下,编译后的模型在推理时并没有获得预期的性能。此时我们可以使用自动调优器,为我们的模型找到更好的配置,从而提高性能。TVM中的调优是对模型进行优化,使其在给定目标上运行得更快。这与模型的训练或参数微调不同,因为调优不会影响模型的准确性,而只会影响运行性能。作为调优过程的一部分,TVM将尝试运行许多不同的算子实现变体,看哪一种性能最好。这些运行结果存储在调优记录文件中,该文件最终作为调优子命令的输出。

以最简单的形式来说,调优需要提供以下三件事:

  • 运行模型的设备规格
  • 存储调优记录输出文件的路径,
  • 要调优的模型的路径。

下面的例子演示了它是如何实际工作的:

# The default search algorithm requires xgboost, see below for further
# details on tuning search algorithms
pip install xgboost

tvmc tune \
--target "llvm" \
--output resnet50-v2-7-autotuner_records.json \
resnet50-v2-7.onnx

 在这个例子中,如果为--target选项指定一个更具体的目标,您将看到更好的结果。例如,在Intel i7处理器上,你可以使用--target llvm -mcpu=skylake。在这个调优示例中,我们使用LLVM作为指定架构的编译器在CPU上进行本地调优。

TVMC将对模型的参数空间进行搜索,尝试不同的算子配置,并选择在您的平台上运行最快的配置。虽然这是一个基于CPU和模型运算的引导搜索,但仍然需要几个小时才能完成搜索。此搜索的输出将保存到resnet50-v2-7-autotuner_records.json文件中,稍后将用于编译一个优化的模型。 

默认情况下,使用XGBoost Grid算法引导搜索。根据模型的复杂性和可用时间,您可能想要选择不同的算法。可以通过tvmc tune --help获得完整的列表。

消费级Skylake CPU的输出如下所示:

tvmc tune \
--target "llvm -mcpu=broadwell" \
--output resnet50-v2-7-autotuner_records.json \
resnet50-v2-7.onnx
# [Task  1/24]  Current/Best:    9.65/  23.16 GFLOPS | Progress: (60/1000) | 130.74 s Done.
# [Task  1/24]  Current/Best:    3.56/  23.16 GFLOPS | Progress: (192/1000) | 381.32 s Done.
# [Task  2/24]  Current/Best:   13.13/  58.61 GFLOPS | Progress: (960/1000) | 1190.59 s Done.
# [Task  3/24]  Current/Best:   31.93/  59.52 GFLOPS | Progress: (800/1000) | 727.85 s Done.
# [Task  4/24]  Current/Best:   16.42/  57.80 GFLOPS | Progress: (960/1000) | 559.74 s Done.
# [Task  5/24]  Current/Best:   12.42/  57.92 GFLOPS | Progress: (800/1000) | 766.63 s Done.
# [Task  6/24]  Current/Best:   20.66/  59.25 GFLOPS | Progress: (1000/1000) | 673.61 s Done.
# [Task  7/24]  Current/Best:   15.48/  59.60 GFLOPS | Progress: (1000/1000) | 953.04 s Done.
# [Task  8/24]  Current/Best:   31.97/  59.33 GFLOPS | Progress: (972/1000) | 559.57 s Done.
# [Task  9/24]  Current/Best:   34.14/  60.09 GFLOPS | Progress: (1000/1000) | 479.32 s Done.
# [Task 10/24]  Current/Best:   12.53/  58.97 GFLOPS | Progress: (972/1000) | 642.34 s Done.
# [Task 11/24]  Current/Best:   30.94/  58.47 GFLOPS | Progress: (1000/1000) | 648.26 s Done.
# [Task 12/24]  Current/Best:   23.66/  58.63 GFLOPS | Progress: (1000/1000) | 851.59 s Done.
# [Task 13/24]  Current/Best:   25.44/  59.76 GFLOPS | Progress: (1000/1000) | 534.58 s Done.
# [Task 14/24]  Current/Best:   26.83/  58.51 GFLOPS | Progress: (1000/1000) | 491.67 s Done.
# [Task 15/24]  Current/Best:   33.64/  58.55 GFLOPS | Progress: (1000/1000) | 529.85 s Done.
# [Task 16/24]  Current/Best:   14.93/  57.94 GFLOPS | Progress: (1000/1000) | 645.55 s Done.
# [Task 17/24]  Current/Best:   28.70/  58.19 GFLOPS | Progress: (1000/1000) | 756.88 s Done.
# [Task 18/24]  Current/Best:   19.01/  60.43 GFLOPS | Progress: (980/1000) | 514.69 s Done.
# [Task 19/24]  Current/Best:   14.61/  57.30 GFLOPS | Progress: (1000/1000) | 614.44 s Done.
# [Task 20/24]  Current/Best:   10.47/  57.68 GFLOPS | Progress: (980/1000) | 479.80 s Done.
# [Task 21/24]  Current/Best:   34.37/  58.28 GFLOPS | Progress: (308/1000) | 225.37 s Done.
# [Task 22/24]  Current/Best:   15.75/  57.71 GFLOPS | Progress: (1000/1000) | 1024.05 s Done.
# [Task 23/24]  Current/Best:   23.23/  58.92 GFLOPS | Progress: (1000/1000) | 999.34 s Done.
# [Task 24/24]  Current/Best:   17.27/  55.25 GFLOPS | Progress: (1000/1000) | 1428.74 s Done.

 调优命令运行时间可能很长,所以tvmc调优提供了许多选项来定制调优过程,包括重复次数(例如--repeat和--number)、使用的调优算法等等。查看tvmc tune --help以获得更多信息。

 使用调优数据编译优化模型

resnet50-v2-7-autotuner_records.json中存储了作为上面调优过程的输出的调优记录。这个文件有两种用法:

  • 作为进一步调优的输入(通过tvmc tune --tuning-records)
  • 作为编译器的输入

编译器将使用调优结果为您指定的目标上的模型生成高性能代码。编译命令为tvmc compile --tuning-records。可以查看tvmc compile --help以获得更多信息。

现在已经收集了模型的调优数据,我们可以使用优化后的算子重新编译模型,以加快计算速度。

tvmc compile \
--target "llvm" \
--tuning-records resnet50-v2-7-autotuner_records.json  \
--output resnet50-v2-7-tvm_autotuned.tar \
resnet50-v2-7.onnx

运行优化后的模型,并产生和优化前相同的输出:

tvmc run \
--inputs imagenet_cat.npz \
--output predictions.npz \
resnet50-v2-7-tvm_autotuned.tar

python postprocess.py

 可以看到模型预测的结果一致:

# class='n02123045 tabby, tabby cat' with probability=0.610550
# class='n02123159 tiger cat' with probability=0.367181
# class='n02124075 Egyptian cat' with probability=0.019365
# class='n02129604 tiger, Panthera tigris' with probability=0.001273
# class='n04040759 radiator' with probability=0.000261

比较调优和未调优模型 

TVMC为您提供了在模型之间进行基本性能基准测试的工具。您可以指定重复次数,并在模型运行时(独立于运行时启动)报告给TVMC。我们可以大致了解调优在多大程度上提高了模型性能。例如,在测试Intel i7系统中,我们发现调优后的模型比未调优的模型运行速度快47%:

tvmc run \
--inputs imagenet_cat.npz \
--output predictions.npz  \
--print-time \
--repeat 100 \
resnet50-v2-7-tvm_autotuned.tar

# Execution time summary:
# mean (ms)   max (ms)    min (ms)    std (ms)
#     92.19     115.73       89.85        3.15

tvmc run \
--inputs imagenet_cat.npz \
--output predictions.npz  \
--print-time \
--repeat 100 \
resnet50-v2-7-tvm.tar

# Execution time summary:
# mean (ms)   max (ms)    min (ms)    std (ms)
#    193.32     219.97      185.04        7.11

小结

 在本教程中,我们介绍了TVM的命令行驱动程序TVMC。我们演示了如何编译、运行和调优模型。我们还讨论了对输入和输出进行预处理和后处理的必要性。在调优过程之后,我们演示了如何比较未优化模型和优化模型的性能。

这里我们给出了一个在本地运行ResNet-50 v2的简单示例。但是,TVMC支持更多的特性,包括交叉编译、远程执行和分析/基准测试。

要了解其他可用选项,请查看tvmc --help。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值