为 ARM CPU 自动调优卷积网络
单击 此处 下载完整的示例代码
作者:Lianmin Zheng, Zhao Wu, Eddie Yan
针对特定 ARM 设备的自动调优对于获得最佳性能至关重要,本文介绍如何调优整个卷积网络。
TVM 中 ARM CPU 的算子实现是以 template 形式编写的,该 template 有许多可调参数(tile 因子,vectorization,unrolling等)。对神经网络中的所有卷积和深度卷积算子调优后,会生成一个日志文件,它存储所有必需算子的最佳参数值。当 TVM 编译器编译这些算子时,会查询这个日志文件,从而获取最佳参数值。
我们还发布了一些 ARM 设备的预调参数。可以前往 ARM CPU Benchmark 查看结果。
注意,本教程无法在 Windows 或最新版本的 macOS 上运行。若要运行,需要将本教程的主体包装在 if __name__ == "__main__":
块中。
安装依赖
要在 TVM 中使用 autotvm 包,需要安装额外的依赖(如果用的是 Python2,请将「3」更改为「2」):
pip3 install --user psutil xgboost tornado cloudpickle
为了让 TVM 在调优中运行更快,推荐使用 Cython 作为 TVM 的 FFI。在 TVM 的根目录下,执行如下命令:(若使用 Python2,将「3」改为「2」):
pip3 install --user cython
sudo make cython3
在 Python 代码中导入包:
import os
import numpy as np
import tvm
from tvm import relay, autotvm
import tvm.relay.testing
from tvm.autotvm.tuner import XGBTuner, GATuner, RandomTuner, GridSearchTuner
from tvm.contrib.utils import tempdir
import tvm.contrib.graph_executor as runtime
定义网络
首先要在 relay 前端 API 中定义网络,可以从 relay.testing
加载一些预定义的网络,还可以从 MXNet、ONNX 和 TensorFlow 加载模型。
def get_network(name, batch_size):
"""获取网络的符号定义和随机权重"""
input_shape = (batch_size, 3, 224, 224)
output_shape = (batch_size, 1000)
if "resnet" in name:
n_layer = int(name.split("-")[1])
mod, params = relay.testing.resnet.get_workload(
num_layers=n_layer, batch_size=batch_size, dtype=dtype
)
elif "vgg" in name:
n_layer = int(name.split("-")[1])
mod, params = relay.testing.vgg.get_workload(
num_layers=n_layer, batch_size=batch_size, dtype=dtype
)
elif name == "mobilenet":
mod, params = relay.testing.mobilenet.get_workload(batch_size=batch_size)
elif name == "squeezenet_v1.1":
mod, params = relay.testing.squeezenet.get_workload(
batch_size=batch_size, version="1.1", dtype=dtype
)
elif name == "inception_v3":
input_shape = (batch_size, 3, 299, 299)
mod, params = relay.testing.inception_v3.get_workload(batch_size=batch_size, dtype=dtype)
elif name == "mxnet":
# MXNet 模型的示例
from mxnet.gluon.model_zoo.vision import get_model
block = get_model("resnet18_v1", pretrained=True)
mod, params = relay.frontend.from_mxnet(block, shape={"data": input_shape}, dtype=dtype)
net = mod["main"]
net = relay.Function(
net.params, relay.nn.softmax(net.body), None, net.type_params, net.attrs
)
mod = tvm.IRModule.from_expr(net)
else:
raise ValueError("Unsupported network: " + name)
return mod, params, input_shape, output_shape
启动 RPC Tracker
TVM 使用 RPC session 与 ARM 板进行通信,在调优期间,调优器会将生成的代码发送到板上并测试板上代码的速度。
为了加速调优,TVM 使用 RPC Tracker(集中的控制器节点)来管理分布式设备。例如,若有 10 部手机,可以将它们全部注册到 Tracker,并行运行 10 次测试,从而加快调优过程。
要启动 RPC tracker,在主机上运行如下命令。在整个调优过程中都需要 tracker,因此需要为此命令打开一个新终端:
python -m tvm.exec.rpc_tracker --host=0.0.0.0 --port=9190
预期输出:
INFO:RPCTracker:bind to 0.0.0.0:9190
将设备注册到 RPC Tracker
接下来把设备注册到 Tracker。第一步是为 ARM 设备构建 TVM runtime。
-
对于 Linux:按照 在设备上构建 TVM Runtime 教程操作,然后将设备注册到 Tracker
python -m tvm.exec.rpc_server --tracker=[HOST_IP]:9190 --key=rk3399
(将
[HOST_IP]
换为你的主机 IP 地址) -
对于 Android:按照此 说明 在 Android 设备上安装 TVM RPC APK,确保可以通过 Android rpc 测试。在调优期间,打开手机开发者选项并勾选「在更改期间保持屏幕唤醒」,为手机接通电源。
注册设备后,通过查询 rpc_tracker 来确认是否注册成功
python -m tvm.exec.query_rpc_tracker --host=0.0.0.0 --port=9190
例如, 如果有 2 台华为 mate10 pro、11 台树莓派 3B 和 2 台 rk3399,则输出是
Queue Status
----------------------------------
key total free pending
----------------------------------
mate10pro 2 2 0
rk3399 2 2 0
rpi3b 11 11 0
----------------------------------
将多个设备注册到 tracker,从而加快调优测试。
设置调优选项
在调优之前,进行配置。这里以 RK3399 板为例。根据自己的设备修改 target 和 device_key。若用 Android 手机,请将 use_android
设置为 True。
#### 设备配置 ####
# 将 "aarch64-linux-gnu" 替换为 单板的正确 target。
# 此 target 用于交叉编译。可以通过:code:`gcc -v` 来查询。
target = tvm.target.Target("llvm -device=arm_cpu -mtriple=aarch64-linux-gnu")
# 根据设备替换 device_key 的值
device_key = "rk3399"
# 若使用 Android 手机,设置 use_android 为 True
use_android = False
#### 调优选项 ####
network = "resnet-18"
log_file = "%s.%s.log" % (device_key, network)
dtype = "float32"
tuning_option = {
"log_filename": log_file,
"tuner": "xgb",
"n_trial": 1500,
"early_stopping": 800,
"measure_option": autotvm.measure_option(
builder=autotvm.LocalBuilder(build_func="ndk" if use_android else "default"),
runner=autotvm.RPCRunner(
device_key,
host="127.0.0.1",
port=9190,
number=5,
timeout=10,
),
),
}