TVMC 编译和优化模型

官方文档

运行环境

使用 TVMC 编译和优化模型

使用 TVMC,即 TVM 命令行驱动程序。TVMC 工具,它暴露了 TVM 的功能,如 auto-tuning、编译、profiling 和通过命令行界面执行模型。

在完成本节内容后,将使用 TVMC 来完成以下任务:

本节的目的是让你了解 TVM 和 TVMC 的能力,并为理解 TVM 的工作原理奠定基础。

将 ONNX 模型编译到 TVM Runtime

tvmc compile --target

# 将 ONNX 模型编译到 TVM Runtime
tvmc compile --target "llvm" --input-shapes "data:[1,3,224,224]" --output ./build/resnet50-v2-7-llvm.tar ./params/resnet50-v2-7.onnx

tvmc compile --target "llvm -mcpu=cascadelake" --input-shapes "data:[1,3,224,224]" --output ./build/resnet50-v2-7-cascadelake.tar ./params/resnet50-v2-7.onnx

tvmc compile --target "opencl" --input-shapes "data:[1,3,224,224]" --output ./build/resnet50-v2-7-opencl.tar ./params/resnet50-v2-7.onnx

tvmc compile --target "cuda" --input-shapes "data:[1,3,224,224]" --output ./build/resnet50-v2-7-cuda.tar ./params/resnet50-v2-7.onnx

mkdir models
tar -xvf ./build/resnet50-v2-7-tvm.tar -C models
ls models

使用 TVMC 运行来自编译模块的模型

输入预处理

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

# https://s3.amazonaws.com/model-server/inputs/kitten.jpg
img_path = "./data/kitten.jpg"

# 重设大小为 224x224
resized_image = Image.open(img_path).resize((224, 224))
img_data = np.asarray(resized_image).astype("float32")

# ONNX 需要 NCHW 输入, 因此对数组进行转换
img_data = np.transpose(img_data, (2, 0, 1))

# 根据 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]

# 添加 batch 维度
img_data = np.expand_dims(norm_img_data, axis=0)

# 保存为 .npz(输出 imagenet_cat.npz)
np.savez("./build/imagenet_cat", data=img_data)

运行编译模块

tvmc run --device

tvmc run --device cpu --inputs ./build/imagenet_cat.npz --output ./build/predictions-llvm.npz ./build/resnet50-v2-7-llvm.tar

tvmc run --device cpu --inputs ./build/imagenet_cat.npz --output ./build/predictions-cascadelake.npz ./build/resnet50-v2-7-cascadelake.tar

tvmc run --device cl --inputs ./build/imagenet_cat.npz --output ./build/predictions-cl.npz ./build/resnet50-v2-7-opencl.tar

tvmc run --device cuda --inputs ./build/imagenet_cat.npz --output ./build/predictions-cuda.npz ./build/resnet50-v2-7-cuda.tar

.tar 模型文件包括 C++ 库,对 Relay 模型的描述,以及模型的参数。TVMC 包括 TVM 运行时,它可以加载模型并根据输入进行预测。当运行上述命令时,TVMC 会输出新文件,predictions.npz,其中包含 NumPy 格式的模型输出张量。

输出后处理

每个模型都会有自己的特定方式来提供输出张量。需要运行一些后处理,利用为模型提供的查找表,将 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

# 下载标签列表
# https://s3.amazonaws.com/onnx-model-zoo/synset.txt
labels_path = "./data/synset.txt"
with open(labels_path, "r") as f:
    labels = [l.rstrip() for l in f]

output_file = "./build/predictions.npz"

# 打开并读入输出张量
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]))
# class='n02123045 tabby, tabby cat' with probability=0.610552
# class='n02123159 tiger cat' with probability=0.367180
# 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 模型

在某些情况下,当使用编译模块运行推理时,可能无法获得预期的性能。在这种情况下,可以利用自动调优器,为模型找到更好的配置,获得性能的提升。TVM 中的调优是指对模型进行优化以在给定目标上更快地运行的过程。这与训练或微调不同,因为它不影响模型的准确性,而只影响运行时的性能。作为调优过程的一部分,TVM 将尝试运行许多不同的算子实现变体,以观察哪些算子表现最佳。这些运行的结果被存储在调优记录文件中,这最终是 tune 子命令的输出。

在最简单的形式下,调优要求你提供三样东西:

默认搜索算法需要 xgboost

tvmc tune --target "llvm" --output ./build/resnet50-v2-7-llvm-autotuner_records.json ./params/resnet50-v2-7.onnx

tvmc tune --target "opencl" --output ./build/resnet50-v2-7-opencl-autotuner_records.json ./params/resnet50-v2-7.onnx

tvmc tune --target "cuda" --output ./build/resnet50-v2-7-cuda-autotuner_records.json ./params/resnet50-v2-7.onnx

备注:直接运行调优可能会跑不通,参考 issuue 13431 解决 tvmc tune resnet50 ERROR 的问题。

import onnx

onnx_model = onnx.load_model('params/resnet50-v2-7.onnx')
onnx_model.graph.input[0].type.tensor_type.shape.dim[0].dim_value = 1
onnx_model.graph.output[0].type.tensor_type.shape.dim[0].dim_value = 1
onnx.checker.check_model(onnx_model)
onnx.save(onnx_model, 'params/resnet50-v2-7-frozen.onnx')

此例中,为 –target 标志指定更具体的 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=cascadelake" --output ./build/resnet50-v2-7-autotuner_records.json ./params/resnet50-v2-7.onnx

调优 session 需要很长时间,因此 tvmc tune 提供了许多选项来自定义调优过程,包括重复次数(例如 –repeat 和 –number)、要用的调优算法等。查看 tvmc tune –help 了解更多信息。

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

从上述调优过程的输出文件 `resnet50-v2-7-autotuner_records.json 可获取调优记录。该文件可用来:

执行 tvmc compile –tuning-records 命令让编译器利用这个结果为指定 target 上的模型生成高性能代码。查看 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 报告模型的运行时间(独立于 runtime 启动)。可大致了解调优对模型性能的提升程度。例如,对 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
Table of Contents