2、TVMC的使用
1、获取模型
在本教程中,我们将使用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
2、ONNX模型编译为TVM运行时
下载ResNet-50模型后,我们使用tvmc编译它。模型编译得到的将是一个目标设备平台上的动态库。我们可以使用TVM运行时在目标设备上运行该模型。
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运行。
3、使用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文件生成imagenet_cat.npz输入数据
#!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)
运行编译后的模型
有了模型(是否可以TVM运行时)和输入数据,我们现在可以运行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]))
运行脚本将产生下面的输出:
4、ResNet模型自动调优
前面我们将模型编译为TVM运行时,不包括任何针对特定平台的优化。在本节中,我们将向您展示如何使用TVMC构建一个针对您的工作平台的优化模型。
在某些情况下,编译后的模型在推理时并没有获得预期的性能。此时我们可以使用自动调优器,为我们的模型找到更好的配置,从而提高性能。TVM中的调优是对模型进行优化,使其在给定目标上运行得更快。这与模型的训练或参数微调不同,因为调优不会影响模型的准确性,而只会影响运行性能。作为调优过程的一部分,TVM将尝试运行许多不同的算子实现变体,看哪一种性能最好。这些运行结果存储在调优记录文件中,该文件最终作为调优子命令的输出。
以最简单的形式来说,调优需要提供以下三件事:
- 运行模型的设备规格
- 存储调优记录输出文件的路径,
- 要调优的模型的路径。
下面的例子演示了它是如何实际工作的:
# 默认搜索算法需要 xgboost,有关调优搜索算法的详细信息,参见下文 pip install xgboost tvmc tune \ --target "llvm" \ --output resnet50-v2-7-autotuner_records.json \ resnet50-v2-7.onnx
接下来,我遇到这种情况
RuntimeError( RuntimeError: FLOP estimator fails for this operator. Error msg: The length of axis is not constant. . Please use cfg.add_flop
to manually set FLOP for this operator
解决办法在Error when trying to tune the ResNet Model - Troubleshooting - Apache TVM Discuss
在这个例子中,如果为--target选项指定一个更具体的目标,您将看到更好的结果。例如,在Intel i7处理器上,你可以使用--target llvm -mcpu=skylake。在这个调优示例中,我们使用LLVM作为指定架构的编译器在CPU上进行本地调优。
TVMC将对模型的参数空间进行搜索,尝试不同的算子配置,并选择在您的平台上运行最快的配置。虽然这是一个基于CPU和模型运算的引导搜索,但仍然需要几个小时才能完成搜索。此搜索的输出将保存到resnet50-v2-7-autotuner_records.json文件中,稍后将用于编译一个优化的模型。
默认情况下,使用XGBoost Grid算法引导搜索。根据模型的复杂性和可用时间,您可能想要选择不同的算法。可以通过tvmc tune --help获得完整的列表。
调优命令运行时间可能很长,所以tvmc调优提供了许多选项来定制调优过程,包括重复次数(例如--repeat和--number)、使用的调优算法等等。查看tvmc tune --help以获得更多信息。
5、使用调优数据编译优化模型
resnet50-v2-7-autotuner_records.json中存储了作为上面调优过程的输出的调优记录。这个文件有两种用法:
- 作为进一步调优的输入(通过tvmc tune --tuning-records)
- 作为编译器的输入
tvmc compile \ --target "llvm" \ --tuning-records resnet50-v2-7-autotuner_records.json \ --output resnet50-v2-7-tvm_autotuned.tar \ resnet50-v2-7-frozen.onnx
运行优化后的模型,并产生和优化前相同的输出:
tvmc run \ --inputs imagenet_cat.npz \ --output predictions_1.npz \ resnet50-v2-7-tvm_autotuned.tar python postprocess.py
运行的结果如下:
6、比较调优和未调优模型
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
7、小结
在本教程中,我们介绍了TVM的命令行驱动程序TVMC。我们演示了如何编译、运行和调优模型。我们还讨论了对输入和输出进行预处理和后处理的必要性。在调优过程之后,我们演示了如何比较未优化模型和优化模型的性能。
这里我们给出了一个在本地运行ResNet-50 v2的简单示例。但是,TVMC支持更多的特性,包括交叉编译、远程执行和分析/基准测试。
要了解其他可用选项,请查看tvmc --help。
参考资料: