內容一覽:本節講解使用 TVMC 編譯和最佳化模型。TVMC 是 TVM 的命令驅動程序,通過命令列執行 TVM 功能。本節是了解 TVM 工作原理的基礎。
關鍵詞:TVMC TVM 機器學習
本節將介紹 TVMC(TVM 的命令列驅動程序)。TVMC 通過命令列界面執行 TVM 功能(包括對模型的自動調優、編譯、分析和執行)。
學完本節後,可用 TVMC 實現下面的任務:
* 為 TVM runtime 編譯預訓練的 ResNet-50 v2 模型。
* 用編譯好的模型預測真實圖像,並解釋輸出和模型性能。
* 使用 TVM 在 CPU上調優模型。
* 用 TVM 收集的調優資料,重新編譯最佳化過的模型。
* 通過最佳化的模型預測圖像,並比較輸出和模型性能。
本節對 TVM 及 TVMC 的功能進行了概述,併為了解 TVM 的工作原理奠定基礎。
使用 TVMC
TVMC 是 Python 應用程序,也是 TVM Python 軟體包的一部分。用 Python 包安裝 TVM 時,會得到一個叫tvmc的命令列應用程序。平臺和安裝方法不同,此命令的位置也會發生變化。
另外,如果$PYTHONPATH上有 TVM 這個 Python 模組,則可通過可執行 Python 模組(用python -m tvm.driver.tvmc命令)來訪問命令列驅動功能。
本教程用tvmc或python -m tvm.driver.tvmc來打開 TVMC 命令列。
使用如下命令查看幫助頁:
tvmc --help
tvmc可用的 TVM 的主要功能來自子命令compile、run和tune。使用tvmc–help查看給定子命令的特定選項。
本教程將介紹這些命令,開始前請先下載一個預訓練的模型。
獲取模型
在本教程中,我們將使用 ResNet-50 v2。ResNet-50 是一個用來對圖像進行分類的 50 層深的卷積神經網路。接下來要用的模型,已經在超過100萬張具有1000種不同分類的圖像上,進行了預訓練。該網路的輸入圖像的大小為224×224。
推薦下載 Netron(免費的 ML 模型查看器)來更深入地探索 ResNet-50 模型的組織結構。
下載 Netron:https://netron.app/
本教程使用 ONNX 格式的模型:
wget https://github.com/onnx/models/raw/b9a54e89508f101a1611cd64f4ef56b9cb62c7cf/vision/classification/resnet/model/resnet50-v2-7.onnx
Tips 1支持的模型格式:
TVMC 支持用 Keras、ONNX、TensorFlow、TFLite 和 Torch 創建的模型。可用–model-format選項指明正在使用的模型格式。執行tvmc compile –help來獲取更多資訊。
Tips 2向 TVM 添加對 ONNX 的支持:
TVM 依賴系統中可用的 ONNX Python 庫。用命令pip3 install –user onnx onnxoptimizer來安裝 ONNX。如果具有 root 訪問許可權並且希望全局安裝 ONNX,則可以刪除–user選項。onnxoptimizer依賴是可選的,僅用於onnx>=1.9。
將 ONNX 模型編譯到 TVM Runtime
下載 ResNet-50 模型後,用tvmc compile對其進行編譯。編譯的輸出結果是模型(被編譯為目標平臺的動態庫)的 TAR 包。用 TVM runtime 可在目標設備上運行該模型:
# 大概需要幾分鐘,取決於設備
tvmc compile \
--target "llvm" \
--input-shapes "data:[1,3,224,224]" \
--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是可被 TVM runtime 載入的模型,表示為 C++ 庫。
*mod.json是 TVM Relay 計算圖的文字表示。
*mod.params是包含預訓練模型參數的檔案。
模組可由應用程序直接載入,而模型可通過 TVM runtime API 運行。
Tips 3定義正確的 TARGET:
指定正確的 target(選項–target)可大大提升編譯模組的性能,因為可利用 target 上可用的硬體功能。參閱 針對 x86 CPU 自動調優卷積網路 獲取更多資訊。建議確定好使用的 CPU 型號以及可選功能,然後適當地設置 target。
使用 TVMC 運行來自編譯模組的模型
將模型編譯到模組後,可用 TVM runtime 對其進行預測。TVMC 具有內建的 TVM runtime,允許運行已編譯的 TVM 模型。
要用 TVMC 運行模型並預測,需要:
* 剛生成的編譯模組。
* 用來預測的模型的有效輸入。
模型的張量 shape、格式和資料類型各不相同。因此,大多數模型都需要預處理和後處理,確保輸入有效,並能夠解釋輸出。TVMC 採用了 NumPy 的.npz格式的輸入和輸出,可很好地支持將多個陣列序列化到一個檔案中。
本教程中的圖像輸入使用的是一張貓的圖像,你也可以根據喜好選擇其他圖像。

輸入預處理
ResNet-50 v2 模型的輸入應該是 ImageNet 格式。下面是 ResNet-50 v2 預處理圖像的腳本示例。
首先用pip3 install –user pillow下載 Python 圖像庫,以滿足腳本運行對圖像庫的依賴。
#!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")
# 重設大小為 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("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 runtime(可載入模型,並對輸入進行預測)。運行以上命令,TVMC 會輸出一個新檔案predictions.npz,其中包含 NumPy 格式的模型輸出張量。
在此示例中,用於編譯模型的和運行模型的是同一臺機器。某些情況下,可能會用 RPC Tracker 來遠端運行它。查看tvmc run –help來了解有關這些選項的更多資訊。
輸出後處理
如前所述,每個模型提供輸出張量的方式都不一樣。
本示例中,我們需要用專為該模型提供的查找表,運行一些後處理(post-processing),從而使得 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
# 下載標籤列表
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"
# 打開並讀入輸出張量
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 runtime 上運行,因此不包含特定於平臺的最佳化。本節將介紹如何用 TVMC,針對工作平臺構建最佳化模型。
用編譯的模組推理,有時可能無法獲得預期的性能。在這種情況下,可用自動調優器更好地配置模型,從而提高性能。TVM 中的調優是指,在給定 target 上最佳化模型,使其運行得更快。與訓練或微調不同,它不會影響模型的準確性,而只會影響 runtime 性能。
作為調優過程的一部分,TVM 實現並運行許多不同運算元的變體,以查看哪個性能最佳。這些運行的結果儲存在調優記錄檔案(tune 命令的最終輸出)中。
調優最少要包含:
* 運行此模型的目標設備的平臺要求
* 儲存調優記錄的輸出檔案的路徑
* 要調優的模型的路徑。
下面的示例演示了其工作流程:
# 默認搜尋演算法需要 xgboost,有關調優搜尋演算法的詳細資訊,參見下文
pip install xgboost
tvmc tune \
--target "llvm" \
--output resnet50-v2-7-autotuner_records.json \
resnet50-v2-7.onnx
此例中,為–target標誌指定更具體的 target 時,會得到更好的結果。例如,在 Intel i7 處理器上,可用–target llvm -mcpu=skylake。這個調優示例把 LLVM 作為指定架構的編譯器,在 CPU 上進行本地調優。
TVMC 針對模型的參數空間進行搜尋,為運算元嘗試不同的配置,然後選擇平臺上運行最快的配置。雖然這是基於 CPU 和模型操作的引導式搜尋,但仍需要幾個小時才能完成搜尋。搜尋的輸出將保存到resnet50-v2-7-autotuner_records.json檔案中,該檔案之後會用於編譯最佳化模型。
Tips 4定義調優搜尋演算法:
這個搜尋演算法默認用XGBoost Grid演算法進行引導。根據模型複雜度和可用時間,可選擇不同的演算法。完整列表可查看tvmc tune –help。
對於消費級的 Skylake CPU,輸出如下:

調優 session 需要很長時間,因此tvmc tune提供了許多選項來自定義調優過程,包括重複次數(例如–repeat和–number)、要用的調優演算法等。查看tvmc tune –help了解更多資訊。
使用調優資料編譯最佳化模型
從上述調優過程的輸出檔案`resnet50-v2-7-autotuner_records.json可獲取調優記錄。
該檔案可用來:
* 作為進一步調優的輸入(通過tvmc tune –tuning-records)
* 作為編譯器的輸入
執行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
寫在最後
本教程介紹了 TVMC( TVM 的命令列驅動程序),演示瞭如何編譯、運行和調優模型,還討論了對輸入和輸出進行預處理和後處理的必要性。調優後,演示如何比較未最佳化和最佳化模型的性能。
本文件展示了一個在本地使用 ResNet-50 v2 的簡單示例。然而,TVMC 支持更多功能,包括交叉編譯、遠端執行和分析/基準測試。
用tvmc –help命令查看其他可用選項。
下個教程 Compiling and Optimizing a Model with the Python Interface 將介紹用 Python 接口的相同編譯和最佳化步驟。
點選下方的閱讀原文可查看所有原始文件。持續關注,不要錯過~