|
Apache TVMは、CPU、GPU、そして様々な機械学習アクセラレーションチップに適したディープラーニングコンパイラフレームワークです。TVMの中国語版ドキュメントは→ https://tvm.hyper.ai/ をご覧ください。 著者: ティエリー・モロー、リアンミン・ジェン、チェンファン・ジャ 最適なパフォーマンスを実現するには、特定のデバイスとワークロードに合わせた自動チューニングが不可欠です。この記事では、RPC経由の自動スケジューラを使用して、ARM CPU向けのニューラルネットワーク全体をチューニングする方法について説明します。 ニューラルネットワークを自動チューニングするために、ネットワークは小さなサブグラフに分割され、個別にチューニングされます。各サブグラフは検索タスクとして扱われます。タスクスケジューラは時間をスライスし、これらのタスクに時間リソースを動的に割り当てます。また、各タスクがエンドツーエンドの実行時間に与える影響を予測し、実行時間を最小化するタスクを優先します。 各サブグラフについて、 tvm/python/topi内の計算宣言を用いてテンソル形式の計算指示子を取得します。次に、自動スケジューラを用いてこの指示子の探索空間を構築し、適切なスケジューラ(基盤となる最適化)を検索します。 テンプレートベースのAutoTVM(テンプレートを用いて手動で探索空間を定義する必要がある)とは異なり、自動スケジューラはスケジューリングテンプレートを必要としません。つまり、自動スケジューラはtvm/python/topiの計算宣言のみを使用し、既存のスケジューリングテンプレートは使用しません。 このチュートリアルはWindowsまたは最新バージョンのmacOSでは動作しませんのでご注意ください。実行するには、チュートリアルの本文をif __name__ == "__main__": ` コードブロック内に配置してください。 import numpy as np import os import tvm from tvm import relay, auto_scheduler from tvm.relay import data_dep_optimization as ddo import tvm.relay.testing from tvm.contrib import graph_executor from tvm.contrib.utils import tempdir ネットワークを定義するまず、RelayフロントエンドAPIを使用してネットワークを定義する必要がありますtvm.relay.testingから定義済みのネットワークを読み込むことができます。また、MXNet、ONNX、PyTorch、TensorFlowのモデルを読み込むこともできます(フロントエンドチュートリアルを参照)。 畳み込みニューラルネットワークの場合、自動スケジューラはどのレイアウトでも正常に動作しますが、NHWCレイアウトで最高のパフォーマンスを発揮します。自動スケジューラはNHWCレイアウトに対して多くの最適化を実行するため、自動スケジューラを使用するには、モデルをNHWCレイアウトに変換することを推奨します。レイアウト変換は、TVMのConvertLayoutパスを使用して実行できます。 def get_network(name, batch_size, layout="NHWC", dtype="float32", use_sparse=False): """获取网络的符号定义和随机权重""" # auto-scheduler 更适合NHWC 布局if layout == "NHWC": image_shape = (224, 224, 3) elif layout == "NCHW": image_shape = (3, 224, 224) else: raise ValueError("Invalid layout: " + layout) input_shape = (batch_size,) + image_shape output_shape = (batch_size, 1000) if name.startswith("resnet-"): n_layer = int(name.split("-")[1]) mod, params = relay.testing.resnet.get_workload( num_layers=n_layer, batch_size=batch_size, layout=layout, dtype=dtype, image_shape=image_shape, ) elif name.startswith("resnet3d-"): n_layer = int(name.split("-")[1]) mod, params = relay.testing.resnet.get_workload( num_layers=n_layer, batch_size=batch_size, layout=layout, dtype=dtype, image_shape=image_shape, ) elif name == "mobilenet": mod, params = relay.testing.mobilenet.get_workload( batch_size=batch_size, layout=layout, dtype=dtype, image_shape=image_shape ) elif name == "squeezenet_v1.1": assert layout == "NCHW", "squeezenet_v1.1 only supports NCHW layout" mod, params = relay.testing.squeezenet.get_workload( version="1.1", batch_size=batch_size, dtype=dtype, image_shape=image_shape, ) elif name == "inception_v3": input_shape = (batch_size, 3, 299, 299) if layout == "NCHW" else (batch_size, 299, 299, 3) 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 assert layout == "NCHW" block = get_model("resnet50_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) elif name == "mlp": mod, params = relay.testing.mlp.get_workload( batch_size=batch_size, dtype=dtype, image_shape=image_shape, num_classes=1000 ) else: raise ValueError("Network not found.") if use_sparse: from tvm.topi.sparse.utils import convert_model_dense_to_sparse mod, params = convert_model_dense_to_sparse(mod, params, random_params=True) return mod, params, input_shape, output_shape RPCトレーサーを起動するTVMはRPCセッションを使用してARMボードと通信します。チューニング中、チューナーは生成されたコードをボードに送信し、ボード上でのコードの速度をテストします。 TVMはチューニングを高速化するために、RPCトラッカー(集中型コントローラーノード)を使用して分散デバイスを管理します。例えば、携帯電話が10台ある場合、それらをすべてトラッカーに登録し、10個のテストを並列実行することで、チューニングプロセスを高速化できます。 チューニングプロセス全体にトレーサーが必要です。そのため、このコマンドを実行するには新しいターミナルを開き、ホストマシンで以下のコマンドを実行してRPCトレーサーを起動する必要があります。 python -m tvm.exec.rpc_tracker --host=0.0.0.0 --port=9190 期待される出力: INFO:RPCTracker:bind to 0.0.0.0:9190 デバイスをRPCトラッカーに登録する次のステップは、デバイスをトラッカーに登録することです。最初のステップは、ARMデバイス用のTVMランタイムをビルドすることです。 - Linux の場合: デバイス上に TVM ランタイムを構築するチュートリアルに従い、デバイスをトラッカーに登録します。
python -m tvm.exec.rpc_server --tracker=[HOST_IP]:9190 --key=rasp4b-64
(将`[HOST_IP]` 换为你的主机的IP 地址) - Androidの場合:以下の手順に従って、TVM RPC APKをAndroidデバイスにインストールし、Android RPCテストに合格することを確認してください。調整中は、スマートフォンの開発者向けオプションを有効にし、「変更中は画面をスリープ解除しない」にチェックを入れ、スマートフォンを電源に接続してください。
デバイスを登録した後、rpc_tracker をチェックして登録が成功したかどうかを確認します。 python -m tvm.exec.query_rpc_tracker --host=0.0.0.0 --port=9190 たとえば、Huawei Mate 10 Pro プロセッサが 2 個、64 ビット オペレーティング システムを搭載した Raspberry Pi 4B プロセッサが 11 個、RK3399 プロセッサが 2 個ある場合、出力は次のようになります... Queue Status ---------------------------------- key total free pending ---------------------------------- mate10pro 2 2 0 rk3399 2 2 0 rasp4b-64 11 11 0 ---------------------------------- 複数のデバイスをトラッカーに登録すると、最適化テストが高速化されます。 設定を構成して最適化する最適化を行う前に、システムを設定してください。この例では、Raspberry Pi 4b 4GBボード(64ビットUbuntu 20.04オペレーティングシステム)を使用しています。Androidスマートフォンを使用する場合は、 use_ndk `True`に設定してください。 #### 设备配置#### # 将"aarch64-linux-gnu" 替换为你的板子的正确target。 # 此target 用于交叉编译。可以通过:code:`gcc -v` 来查询。 # FIXME(tmoreau89, merrymercy): 将'-device=arm_cpu' 排除在target 字符串之外# 因为共享x86 操作策略。 target = tvm.target.Target("llvm -mtriple=aarch64-linux-gnu -mattr=+neon") # 替换为跟踪器中的device_key、rpc 主机和rpc 端口device_key = "rasp4b-64" rpc_host = "127.0.0.1" rpc_port = 9190 # 如果使用ndk 工具进行交叉编译,则设置为True # 并且还要设置下面的环境变量指向交叉编译器use_ndk = False # os.environ["TVM_NDK_CC"] = "/usr/bin/aarch64-linux-gnu-g++" #### 调优OPTION #### network = "mobilenet" use_sparse = False batch_size = 1 layout = "NHWC" dtype = "float32" log_file = "%s-%sB%d-%s.json" % (network, layout, batch_size, target.kind.name) 検索タスクの抽出次に、ネットワークから探索タスクとその重みが抽出されます。タスクの重みは、そのサブグラフがネットワーク全体に出現する回数です。重みを用いることで、ネットワークのエンドツーエンドのレイテンシはsum(latency[t] * weight[t])と近似できます。ここで、 latency[t]はタスクのレイテンシ、 weight[t]はタスクの重みです。タスクスケジューラは、この目的のみに最適化を行います。 # 从网络中提取任务print("Get model...") mod, params, input_shape, output_shape = get_network( network, batch_size, layout, dtype=dtype, use_sparse=use_sparse ) print("Extract tasks...") tasks, task_weights = auto_scheduler.extract_tasks(mod["main"], params, target) for idx, task in enumerate(tasks): print("========== Task %d (workload key: %s) ==========" % (idx, task.workload_key)) print(task.compute_dag) 出力結果: Get model... Extract tasks... /workspace/python/tvm/driver/build_module.py:268: UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead. "target_host parameter is going to be deprecated. " ========== Task 0 (workload key: ["1037be767e8e18197e87653d81c34558", [1, 7, 7, 1024], [1, 1, 1024, 1024], [1, 1, 1, 1024], [1, 7, 7, 1024]]) ========== placeholder = PLACEHOLDER [1, 7, 7, 1024] pad_temp(i0, i1, i2, i3) = placeholder[i0, i1, i2, i3] placeholder = PLACEHOLDER [1, 1, 1024, 1024] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, (yy + ry), (xx + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 1, 1, 1024] T_add(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 1 (workload key: ["1037be767e8e18197e87653d81c34558", [1, 14, 14, 256], [1, 1, 256, 512], [1, 1, 1, 512], [1, 14, 14, 512]]) ========== placeholder = PLACEHOLDER [1, 14, 14, 256] pad_temp(i0, i1, i2, i3) = placeholder[i0, i1, i2, i3] placeholder = PLACEHOLDER [1, 1, 256, 512] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, (yy + ry), (xx + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 1, 1, 512] T_add(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 2 (workload key: ["06fce76bd84cb904eee50b905ca9449a", [1, 28, 28, 256], [3, 3, 256, 1], [1, 1, 1, 256], [1, 28, 28, 256]]) ========== placeholder = PLACEHOLDER [1, 28, 28, 256] PaddedInput(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 29)) && (i2 >= 1)) && (i2 < 29)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 256, 1] DepthwiseConv2d(b, i, j, c) += (PaddedInput[b, (i + di), (j + dj), c]*placeholder[di, dj, c, 0]) placeholder = PLACEHOLDER [1, 1, 1, 256] T_add(ax0, ax1, ax2, ax3) = (DepthwiseConv2d[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 3 (workload key: ["1037be767e8e18197e87653d81c34558", [1, 28, 28, 128], [1, 1, 128, 256], [1, 1, 1, 256], [1, 28, 28, 256]]) ========== placeholder = PLACEHOLDER [1, 28, 28, 128] pad_temp(i0, i1, i2, i3) = placeholder[i0, i1, i2, i3] placeholder = PLACEHOLDER [1, 1, 128, 256] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, (yy + ry), (xx + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 1, 1, 256] T_add(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 4 (workload key: ["d7b65649a4dd54becea0a52aabbc5af5", [1, 1000], [1, 1000]]) ========== placeholder = PLACEHOLDER [1, 1000] T_softmax_maxelem(i0) max= placeholder[i0, k] T_softmax_exp(i0, i1) = tir.exp((placeholder[i0, i1] - T_softmax_maxelem[i0])) T_softmax_expsum(i0) += T_softmax_exp[i0, k] T_softmax_norm(i0, i1) = (T_softmax_exp[i0, i1]/T_softmax_expsum[i0]) ========== Task 5 (workload key: ["69115f188984ae34ede37c3b8ca40b43", [1, 7, 7, 1024], [1, 1, 1, 1024]]) ========== placeholder = PLACEHOLDER [1, 7, 7, 1024] tensor(ax0, ax1, ax2, ax3) += placeholder[ax0, ((ax1*7) + rv0), ((ax2*7) + rv1), ax3] tensor(ax0, ax1, ax2, ax3) = (tensor[ax0, ax1, ax2, ax3]/(float32((select((bool)1, ((ax1 + 1)*7), (((ax1 + 1)*7) + 1)) - (ax1*7)))*float32((select((bool)1, ((ax2 + 1)*7), (((ax2 + 1)*7) + 1)) - (ax2*7))))) ========== Task 6 (workload key: ["1037be767e8e18197e87653d81c34558", [1, 7, 7, 512], [1, 1, 512, 1024], [1, 1, 1, 1024], [1, 7, 7, 1024]]) ========== placeholder = PLACEHOLDER [1, 7, 7, 512] pad_temp(i0, i1, i2, i3) = placeholder[i0, i1, i2, i3] placeholder = PLACEHOLDER [1, 1, 512, 1024] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, (yy + ry), (xx + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 1, 1, 1024] T_add(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 7 (workload key: ["c87ba68bc180312f5716af09a77ca15b", [1, 56, 56, 128], [3, 3, 128, 1], [1, 1, 1, 128], [1, 28, 28, 128]]) ========== placeholder = PLACEHOLDER [1, 56, 56, 128] PaddedInput(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 57)) && (i2 >= 1)) && (i2 < 57)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 128, 1] DepthwiseConv2d(b, i, j, c) += (PaddedInput[b, ((i*2) + di), ((j*2) + dj), c]*placeholder[di, dj, c, 0]) placeholder = PLACEHOLDER [1, 1, 1, 128] T_add(ax0, ax1, ax2, ax3) = (DepthwiseConv2d[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 8 (workload key: ["06fce76bd84cb904eee50b905ca9449a", [1, 7, 7, 1024], [3, 3, 1024, 1], [1, 1, 1, 1024], [1, 7, 7, 1024]]) ========== placeholder = PLACEHOLDER [1, 7, 7, 1024] PaddedInput(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 8)) && (i2 >= 1)) && (i2 < 8)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 1024, 1] DepthwiseConv2d(b, i, j, c) += (PaddedInput[b, (i + di), (j + dj), c]*placeholder[di, dj, c, 0]) placeholder = PLACEHOLDER [1, 1, 1, 1024] T_add(ax0, ax1, ax2, ax3) = (DepthwiseConv2d[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 9 (workload key: ["c87ba68bc180312f5716af09a77ca15b", [1, 28, 28, 256], [3, 3, 256, 1], [1, 1, 1, 256], [1, 14, 14, 256]]) ========== placeholder = PLACEHOLDER [1, 28, 28, 256] PaddedInput(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 29)) && (i2 >= 1)) && (i2 < 29)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 256, 1] DepthwiseConv2d(b, i, j, c) += (PaddedInput[b, ((i*2) + di), ((j*2) + dj), c]*placeholder[di, dj, c, 0]) placeholder = PLACEHOLDER [1, 1, 1, 256] T_add(ax0, ax1, ax2, ax3) = (DepthwiseConv2d[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 10 (workload key: ["c87ba68bc180312f5716af09a77ca15b", [1, 14, 14, 512], [3, 3, 512, 1], [1, 1, 1, 512], [1, 7, 7, 512]]) ========== placeholder = PLACEHOLDER [1, 14, 14, 512] PaddedInput(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 15)) && (i2 >= 1)) && (i2 < 15)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 512, 1] DepthwiseConv2d(b, i, j, c) += (PaddedInput[b, ((i*2) + di), ((j*2) + dj), c]*placeholder[di, dj, c, 0]) placeholder = PLACEHOLDER [1, 1, 1, 512] T_add(ax0, ax1, ax2, ax3) = (DepthwiseConv2d[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 11 (workload key: ["c87ba68bc180312f5716af09a77ca15b", [1, 112, 112, 64], [3, 3, 64, 1], [1, 1, 1, 64], [1, 56, 56, 64]]) ========== placeholder = PLACEHOLDER [1, 112, 112, 64] PaddedInput(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 113)) && (i2 >= 1)) && (i2 < 113)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 64, 1] DepthwiseConv2d(b, i, j, c) += (PaddedInput[b, ((i*2) + di), ((j*2) + dj), c]*placeholder[di, dj, c, 0]) placeholder = PLACEHOLDER [1, 1, 1, 64] T_add(ax0, ax1, ax2, ax3) = (DepthwiseConv2d[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 12 (workload key: ["1037be767e8e18197e87653d81c34558", [1, 28, 28, 256], [1, 1, 256, 256], [1, 1, 1, 256], [1, 28, 28, 256]]) ========== placeholder = PLACEHOLDER [1, 28, 28, 256] pad_temp(i0, i1, i2, i3) = placeholder[i0, i1, i2, i3] placeholder = PLACEHOLDER [1, 1, 256, 256] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, (yy + ry), (xx + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 1, 1, 256] T_add(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 13 (workload key: ["1037be767e8e18197e87653d81c34558", [1, 56, 56, 128], [1, 1, 128, 128], [1, 1, 1, 128], [1, 56, 56, 128]]) ========== placeholder = PLACEHOLDER [1, 56, 56, 128] pad_temp(i0, i1, i2, i3) = placeholder[i0, i1, i2, i3] placeholder = PLACEHOLDER [1, 1, 128, 128] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, (yy + ry), (xx + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 1, 1, 128] T_add(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 14 (workload key: ["1037be767e8e18197e87653d81c34558", [1, 14, 14, 512], [1, 1, 512, 512], [1, 1, 1, 512], [1, 14, 14, 512]]) ========== placeholder = PLACEHOLDER [1, 14, 14, 512] pad_temp(i0, i1, i2, i3) = placeholder[i0, i1, i2, i3] placeholder = PLACEHOLDER [1, 1, 512, 512] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, (yy + ry), (xx + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 1, 1, 512] T_add(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 15 (workload key: ["06fce76bd84cb904eee50b905ca9449a", [1, 112, 112, 32], [3, 3, 32, 1], [1, 1, 1, 32], [1, 112, 112, 32]]) ========== placeholder = PLACEHOLDER [1, 112, 112, 32] PaddedInput(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 113)) && (i2 >= 1)) && (i2 < 113)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 32, 1] DepthwiseConv2d(b, i, j, c) += (PaddedInput[b, (i + di), (j + dj), c]*placeholder[di, dj, c, 0]) placeholder = PLACEHOLDER [1, 1, 1, 32] T_add(ax0, ax1, ax2, ax3) = (DepthwiseConv2d[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 16 (workload key: ["2ca148ecea6508ce625f85719021344f", [1, 224, 224, 3], [3, 3, 3, 32], [1, 112, 1, 1], [1, 112, 1, 1], [1, 112, 112, 32]]) ========== placeholder = PLACEHOLDER [1, 224, 224, 3] pad_temp(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 225)) && (i2 >= 1)) && (i2 < 225)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 3, 32] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, ((yy*2) + ry), ((xx*2) + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 112, 1, 1] T_multiply(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3]*placeholder[ax0, ax1, 0, 0]) placeholder = PLACEHOLDER [1, 112, 1, 1] T_add(ax0, ax1, ax2, ax3) = (T_multiply[ax0, ax1, ax2, ax3] + placeholder[ax0, ax1, 0, 0]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 17 (workload key: ["1037be767e8e18197e87653d81c34558", [1, 56, 56, 64], [1, 1, 64, 128], [1, 1, 1, 128], [1, 56, 56, 128]]) ========== placeholder = PLACEHOLDER [1, 56, 56, 64] pad_temp(i0, i1, i2, i3) = placeholder[i0, i1, i2, i3] placeholder = PLACEHOLDER [1, 1, 64, 128] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, (yy + ry), (xx + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 1, 1, 128] T_add(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 18 (workload key: ["7d44c6e3c81cd80f61ff2265b2bae89a", [1, 1024], [1000, 1024], [1, 1000], [1, 1000]]) ========== placeholder = PLACEHOLDER [1, 1024] placeholder = PLACEHOLDER [1000, 1024] T_matmul_NT(i, j) += (placeholder[i, k]*placeholder[j, k]) placeholder = PLACEHOLDER [1, 1000] T_add(ax0, ax1) = (T_matmul_NT[ax0, ax1] + placeholder[ax0, ax1]) ========== Task 19 (workload key: ["06fce76bd84cb904eee50b905ca9449a", [1, 14, 14, 512], [3, 3, 512, 1], [1, 1, 1, 512], [1, 14, 14, 512]]) ========== placeholder = PLACEHOLDER [1, 14, 14, 512] PaddedInput(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 15)) && (i2 >= 1)) && (i2 < 15)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 512, 1] DepthwiseConv2d(b, i, j, c) += (PaddedInput[b, (i + di), (j + dj), c]*placeholder[di, dj, c, 0]) placeholder = PLACEHOLDER [1, 1, 1, 512] T_add(ax0, ax1, ax2, ax3) = (DepthwiseConv2d[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 20 (workload key: ["06fce76bd84cb904eee50b905ca9449a", [1, 56, 56, 128], [3, 3, 128, 1], [1, 1, 1, 128], [1, 56, 56, 128]]) ========== placeholder = PLACEHOLDER [1, 56, 56, 128] PaddedInput(i0, i1, i2, i3) = tir.if_then_else(((((i1 >= 1) && (i1 < 57)) && (i2 >= 1)) && (i2 < 57)), placeholder[i0, (i1 - 1), (i2 - 1), i3], 0f) placeholder = PLACEHOLDER [3, 3, 128, 1] DepthwiseConv2d(b, i, j, c) += (PaddedInput[b, (i + di), (j + dj), c]*placeholder[di, dj, c, 0]) placeholder = PLACEHOLDER [1, 1, 1, 128] T_add(ax0, ax1, ax2, ax3) = (DepthwiseConv2d[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) ========== Task 21 (workload key: ["1037be767e8e18197e87653d81c34558", [1, 112, 112, 32], [1, 1, 32, 64], [1, 1, 1, 64], [1, 112, 112, 64]]) ========== placeholder = PLACEHOLDER [1, 112, 112, 32] pad_temp(i0, i1, i2, i3) = placeholder[i0, i1, i2, i3] placeholder = PLACEHOLDER [1, 1, 32, 64] conv2d_nhwc(nn, yy, xx, ff) += (pad_temp[nn, (yy + ry), (xx + rx), rc]*placeholder[ry, rx, rc, ff]) placeholder = PLACEHOLDER [1, 1, 1, 64] T_add(ax0, ax1, ax2, ax3) = (conv2d_nhwc[ax0, ax1, ax2, ax3] + placeholder[ax0, 0, 0, ax3]) T_relu(ax0, ax1, ax2, ax3) = max(T_add[ax0, ax1, ax2, ax3], 0f) 最適化と評価次に、検索タスクの調整と起動のためのオプションをいくつか設定します。 -
num_measure_trialsは、チューニング中に使用できるテストの数です(時間予算に応じてこのパラメータを調整してください)。簡単なデモでは、小さな数値(例:200)に設定してください。探索の収束を促進するため、 800 * len(tasks)程度に設定することをお勧めします。例えば、ResNet-50 には29個のタスクがあるため、20000 に設定できます。 - さらに、
RecordToFileはテスト レコードをログ ファイルにダンプするために使用され、履歴のベスト クエリ、検索の復元、およびその後の分析に使用できます。 - その他のパラメータについては、
auto_scheduler.TuningOptionsおよびauto_scheduler.LocalRunnerを参照してください。
自動チューニング後、発見された最適なスケジュールを使用してネットワークをコンパイルできます。自動チューニング中は、すべてのテスト記録がログファイルにダンプされ、これを読み取って最適なスケジュールをロードできます。 def tune_and_evaluate(): print("Begin tuning...") tuner = auto_scheduler.TaskScheduler(tasks, task_weights) tune_option = auto_scheduler.TuningOptions( num_measure_trials=200, # 将此更改为20000 以达到最佳性能builder=auto_scheduler.LocalBuilder(build_func="ndk" if use_ndk else "default"), runner=auto_scheduler.RPCRunner( device_key, host=rpc_host, port=rpc_port, timeout=30, repeat=1, min_repeat_ms=200, enable_cpu_cache_flush=True, ), measure_callbacks=[auto_scheduler.RecordToFile(log_file)], ) tuner.tune(tune_option) # 用历史最佳编译print("Compile...") with auto_scheduler.ApplyHistoryBest(log_file): with tvm.transform.PassContext( opt_level=3, config={"relay.backend.use_auto_scheduler": True} ): lib = relay.build(mod, target=target, params=params) # 导出库tmp = tempdir() if use_ndk: from tvm.contrib import ndk filename = "net.so" lib.export_library(tmp.relpath(filename), ndk.create_shared) else: filename = "net.tar" lib.export_library(tmp.relpath(filename)) # 上传模块到设备print("Upload...") remote = auto_scheduler.utils.request_remote(device_key, rpc_host, rpc_port, timeout=10000) remote.upload(tmp.relpath(filename)) rlib = remote.load_module(filename) # 创建图执行器dev = remote.cpu() module = graph_executor.GraphModule(rlib["default"](dev)) data_tvm = tvm.nd.array((np.random.uniform(size=input_shape)).astype(dtype)) module.set_input("data", data_tvm) # 评估print("Evaluate inference time cost...") print(module.benchmark(dev, repeat=3, min_repeat_ms=500)) # 不在网页服务器中运行调优,因为服务器没有树莓派, # 或正在运行的设备跟踪器。 # 取消注释运行下面行。 # tune_and_evaluate()备注解释调优过程中打印的信息在调优过程中,控制台上会打印很多用于调试的信息,最重要的信息是任务调度程序的输出,下表是输出示例。 ---------------------------------------------------------------------- ------------------------------ [ Task Scheduler ] ---------------------------------------------------------------------- | ID | Latency (ms) | Speed (GFLOPS) | Trials | ------------------------------------------------- | 0 | 0.013 | 0.31 | 64 | | 1 | 0.845 | 2.43 | 448 | | 2 | 0.046 | -0.00 | 64 | | 3 | 4.194 | 24.53 | 2112 | | 4 | 0.109 | 9.21 | 64 | | 5 | 1.759 | 29.27 | 896 | | 6 | 0.083 | 6.01 | 64 | | 7 | 3.084 | 33.38 | 7680 | | 8 | 0.136 | 14.78 | 384 | | 9 | 1.349 | 38.23 | 768 | | 10 | 0.133 | 7.55 | 128 | | 11 | 2.747 | 37.56 | 1536 | | 12 | 0.338 | 11.87 | 192 | | 13 | 1.295 | 40.00 | 704 | | 14 | 0.482 | 4.16 | 256 | | 15 | 2.686 | 38.56 | 1344 | | 16 | 0.884 | 9.08 | 448 | | 17 | 1.332 | 39.18 | 704 | | 18 | 1.045 | 3.84 | 576 | | 19 | 1.391 | 38.09 | 704 | | 20 | 0.777 | 10.34 | 448 | | 21 | 0.739 | 30.97 | 448 | ------------------------------------------------- Estimated total latency: 38.347 ms Trials: 19992 Used time : 19260 s Next ID: 3此表列出了所有任务的延迟和(预估)速度,还列出了所有任务的测试分配。最后一行打印了这些任务的总加权延迟,可以粗略估计网络的端到端执行时间。最后一行还打印了测试试验的总数、自动调优所花费的总时间以及下一个要调优的任务的ID。还有一些「dmlc::Error」错误,因为auto-scheduler 会尝试一些无效的调度,若调优继续运行,则可以忽略这些错误,因为这些错误与主进程隔离。 注: このプロセスを強制的に終了すると、チューニングを早期に終了できます。ただし、ログ ファイル内の各タスクに対して少なくとも 1 つの有効なスケジュールが取得され、コンパイルを続行できます (以下を参照)。 その他の技術- チューニングプロセス中、オートスケジューラは多くのプログラムをコンパイルし、そこから特徴を抽出する必要があります。この部分はCPUリソースを大量に消費するため、検索を高速化するために、マルチコアの高性能CPUを使用することをお勧めします。
-
python3 -m tvm.auto_scheduler.measure_record --mode distill -i log.jsonを使用すると、大きなログ ファイルを抽出し、最も有用なレコードのみを保存できます。 - `
run_tuning関数でタスクスケジューラを作成する際に、新しいパラメータload_log_file追加するだけで、以前のログファイルから検索を再開できます。例えば、 tuner = auto_scheduler.TaskScheduler(tasks, task_weights, load_log_file=log_file) - ターゲットCPUが複数ある場合は、それらすべてを並列テストに使用できます。RPCトレーサーとRPCサーバーの使用については、こちらのセクションをご覧ください。自動スケジューラでRPCトレーサーを使用するには、
TuningOptionsの `runner` をauto_scheduler.RPCRunnerに置き換えてください。
Python ソースコードをダウンロード: tune_network_arm.py Jupyter Notebook をダウンロード: tune_network_arm.ipynb |