编写可调模板并使用自动调谐器
編寫可調(diào)模板并使用自動調(diào)諧器
這是TVM中自動調(diào)整模塊的入門資料。
自動調(diào)整有兩個步驟。第一步是定義搜索空間。第二步是運行搜索算法來探索這個空間。在本文中,可以學(xué)習(xí)如何在TVM中執(zhí)行這兩個步驟。整個工作流程由矩陣乘法示例說明。
注意,本文無法在Windows或最新版本的macOS上運行。要使其運行,需要將本文的內(nèi)容包裝在一個if name == “main”:塊中。
安裝依賴項
要在TVM中使用autotvm軟件包,需要安裝一些額外的依賴項??梢蕴^此步驟(安裝xgboost),不需要XGBoost(如果使用python2,將“ 3”更改為“ 2”):
pip3 install --user psutil xgboost cloudpickle
為了使TVM在調(diào)整中更快地運行,建議使用cython作為TVM的FFI。在TVM的根目錄中,執(zhí)行(如果使用python2,則將“ 3”更改為“ 2”):
pip3 install --user cython
sudo make cython3
現(xiàn)在返回python代碼。導(dǎo)入包。
import logging
import sys
import numpy as np
import tvm
from tvm import te
import tvm.testing
the module is called autotvm
from tvm import autotvm
步驟1:定義搜索空間
在本節(jié)中,將確定性TVM調(diào)度代碼重寫為可調(diào)調(diào)度模板??梢詫⑺阉骺臻g定義的過程視為現(xiàn)有調(diào)度代碼的參數(shù)化。
首先,如何在TVM中實現(xiàn)分塊矩陣乘法。
Matmul V0: Constant tiling factor
def matmul_v0(N, L, M, dtype):
A = te.placeholder((N, L), name=“A”, dtype=dtype)
B = te.placeholder((L, M), name=“B”, dtype=dtype)
k = te.reduce_axis((0, L), name="k")
C = te.compute((N, M), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name="C")
s = te.create_schedule(C.op)# schedule
y, x = s[C].op.axis
k = s[C].op.reduce_axis[0]yo, yi = s[C].split(y, 8)
xo, xi = s[C].split(x, 8)s[C].reorder(yo, xo, k, yi, xi)return s, [A, B, C]
參數(shù)化調(diào)度
在先前的調(diào)度代碼中,使用常數(shù)“ 8”作為平鋪因子??赡懿皇亲罴堰x擇,最佳拼接因子取決于實際的硬件環(huán)境和輸入形狀。
如果希望調(diào)度代碼可在更廣泛的輸入形狀和目標(biāo)硬件中移植,最好定義一組候選值,根據(jù)目標(biāo)硬件上的測量結(jié)果選擇最佳值。
在autotvm中,可以為此類值定義可調(diào)參數(shù)或“旋鈕”。
Matmul V1: List candidate values
@autotvm.template(“tutorial/matmul_v1”) # 1. use a decorator
def matmul_v1(N, L, M, dtype):
A = te.placeholder((N, L), name=“A”, dtype=dtype)
B = te.placeholder((L, M), name=“B”, dtype=dtype)
k = te.reduce_axis((0, L), name="k")
C = te.compute((N, M), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name="C")
s = te.create_schedule(C.op)# schedule
y, x = s[C].op.axis
k = s[C].op.reduce_axis[0]# 2. get the config object
cfg = autotvm.get_config()# 3. define search space
cfg.define_knob("tile_y", [1, 2, 4, 8, 16])
cfg.define_knob("tile_x", [1, 2, 4, 8, 16])# 4. schedule according to config
yo, yi = s[C].split(y, cfg["tile_y"].val)
xo, xi = s[C].split(x, cfg["tile_x"].val)s[C].reorder(yo, xo, k, yi, xi)return s, [A, B, C]
對先前的調(diào)度代碼進行了四處修改,并獲得了一個可調(diào)的“模板”??梢砸灰唤忉屵@些修改。
-
使用裝飾器,標(biāo)記為簡單模板。
-
獲取配置對象:可以將其cfg視為此函數(shù)的參數(shù),通過其它方式獲得。使用此參數(shù),此函數(shù)不再是確定性的調(diào)度代碼。將不同的配置傳遞給此功能并獲得不同的調(diào)度,此功能是一個“模板”。
為了使模板函數(shù)更緊湊,在單個函數(shù)中做了兩件事。(1)定義搜索空間,(2)根據(jù)該空間中的實體進行調(diào)度。為了達到這個目的,將cfg其設(shè)為aConfigSpace或ConfigEntityobject。
當(dāng)ConfigSpace時,收集此功能中的所有可調(diào)旋鈕并建立搜索空間。如果是ConfigEntity,忽略所有空間定義API(即cfg.define_XXXXX(…))。相反,存儲所有可調(diào)旋鈕的確定性值,并根據(jù)這些值進行調(diào)度。
在自動調(diào)整期間,首先使用一個ConfigSpace 對象,調(diào)用此模板以構(gòu)建搜索空間。然后,ConfigEntity 在構(gòu)建空間中將此模板稱為不同的模板,以獲取不同的調(diào)度。最后,測量由不同調(diào)度生成的代碼,選擇最佳調(diào)度。 -
定義兩個可調(diào)旋鈕。第一個tile_y具有5個可能的值。第二個tile_x具有相同的可能值列表。這兩個旋鈕是獨立的,跨越大小= 5x5 = 25的搜索空間
-
根據(jù)中的確定性值進行調(diào)度 cfg
使用更好的空間定義API
在上一個模板中,手動列出了旋鈕的所有可能值。這是定義空間的最低級別的API。還提供了另一組API,以使空間定義更輕松,更智能。建議使用這套高級API。
在下面的示例中,用于ConfigSpace.define_split定義拆分旋鈕。列舉所有可能的方式,分割軸并構(gòu)造空間。
還提供了ConfigSpace.define_reorder用于重新排序的旋鈕和 ConfigSpace.define_annotate用于展開,向量化,線程綁定的注釋。當(dāng)高級API不能滿足要求時,始終可以退而使用低級API。
@autotvm.template(“tutorial/matmul”)
def matmul(N, L, M, dtype):
A = te.placeholder((N, L), name=“A”, dtype=dtype)
B = te.placeholder((L, M), name=“B”, dtype=dtype)k = te.reduce_axis((0, L), name=“k”)
C = te.compute((N, M), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name=“C”)
s = te.create_schedule(C.op)schedule
y, x = s[C].op.axis
k = s[C].op.reduce_axis[0]define space begin
cfg = autotvm.get_config()
cfg.define_split(“tile_y”, y, num_outputs=2)
cfg.define_split(“tile_x”, x, num_outputs=2)define space end
schedule according to config
yo, yi = cfg[“tile_y”].apply(s, C, y)
xo, xi = cfg[“tile_x”].apply(s, C, x)s[C].reorder(yo, xo, k, yi, xi)
return s, [A, B, C]
筆記
有關(guān)更多說明 cfg.defile_split
在此模板中,將枚舉所有可以將y軸分解為長度為y的兩個軸的可能組合。例如,如果y的長度是32,使用32的因子將其分成兩個軸,則(外軸的長度,內(nèi)軸的長度)對有6個可能的值,即(32,1) ,(16、2),(8、4),(4、8),(2、16)或(1、32)。只是tile_y的6個可能值。cfg.define_split(“tile_y”, y, num_outputs=2)
在調(diào)度期間,cfg[“tile_y”]是一個SplitEntity對象。將外軸和內(nèi)軸的長度存儲在cfg[‘tile_y’].size (具有兩個元素的元組中)中。在此模板中,使用來應(yīng)用它。實際上,這等于yo, yi = cfg[‘tile_y’].apply(s, C, y)yo, yi = s[C].split(y, cfg[“tile_y”].size[1]) 或 yo, yi = s[C].split(y, nparts=cfg['tile_y"].size[0])
使用cfg.apply API的優(yōu)點是,使多層拆分(當(dāng)num_outputs> = 3時)更加容易。
第2步:搜索空間
在第1步中,通過將舊的調(diào)度代碼擴展到模板中來構(gòu)建搜索空間。下一步是選擇一個調(diào)諧器,并在這個空間中進行探索。
TVM中的自動調(diào)諧器
調(diào)諧器的工作可以通過以下偽代碼來描述
ct = 0
while ct < max_number_of_trials:
propose a batch of configs
measure this batch of configs on real hardware and get results
ct += batch_size
當(dāng)提議下一批配置時,調(diào)諧器可以采取不同的策略。在autotvm中為四個調(diào)諧器提供了不同的策略。
? RandomTuner:以隨機順序枚舉空間
? GridSearchTuner:按網(wǎng)格搜索順序枚舉空間
? GATuner:使用遺傳算法搜索空間
? XGBTuner:使用基于模型的方法。訓(xùn)練XGBoost模型,預(yù)測降低的IR的速度,并根據(jù)預(yù)測選擇下一批。
可以根據(jù)空間大小,時間預(yù)算和其它因素選擇調(diào)諧器。例如,如果空間很小(小于1000),那么使用gridsearch調(diào)諧器或隨機調(diào)諧器就足夠了。如果空間為10 ^ 9的水平(這是CUDA GPU上conv2d運算符的空間大小),則XGBoostTuner可以更有效地進行探索并找到更好的配置。
開始調(diào)整
繼續(xù)矩陣乘法示例。首先,應(yīng)該創(chuàng)建一個調(diào)整任務(wù)。還可以檢查初始化的搜索空間。在這種情況下,對于512x512方陣乘法,空間大小為10x10 = 100
N, L, M = 512, 512, 512
task = autotvm.task.create(“tutorial/matmul”, args=(N, L, M, “float32”), target=“l(fā)lvm”)
print(task.config_space)
輸出:
ConfigSpace (len=100, space_map=
0 tile_y: Split(policy=factors, product=512, num_outputs=2) len=10
1 tile_x: Split(policy=factors, product=512, num_outputs=2) len=10
)
然后,需要定義如何測量生成的代碼并選擇一個調(diào)諧器。由于空間很小,可以使用隨機調(diào)諧器。
僅進行10次試用以進行演示??梢愿鶕?jù)自己的時間預(yù)算進行更多試驗。會將調(diào)整結(jié)果記錄到日志文件中。以后可以使用此文件來獲得最佳配置。
logging config (for printing tuning log to the screen)
logging.getLogger(“autotvm”).setLevel(logging.DEBUG)
logging.getLogger(“autotvm”).addHandler(logging.StreamHandler(sys.stdout))
There are two steps for measuring a config: build and run.
By default, we use all CPU cores to compile program. Then measure them sequentially.
We measure 5 times and take average to reduce variance.
measure_option = autotvm.measure_option(builder=“l(fā)ocal”, runner=autotvm.LocalRunner(number=5))
Begin tuning with RandomTuner, log records to file matmul.log
You can use alternatives like XGBTuner.
tuner = autotvm.tuner.RandomTuner(task)
tuner.tune(
n_trial=10,
measure_option=measure_option,
callbacks=[autotvm.callback.log_to_file(“matmul.log”)],
)
輸出:
Get devices for measurement successfully!
No: 1 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595264.4104242) [(‘tile_y’, [-1, 64]), (‘tile_x’, [-1, 1])],None,6
No: 2 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595264.714398) [(‘tile_y’, [-1, 512]), (‘tile_x’, [-1, 8])],None,39
No: 3 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595265.0153906) [(‘tile_y’, [-1, 2]), (‘tile_x’, [-1, 8])],None,31
No: 4 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595265.3186316) [(‘tile_y’, [-1, 1]), (‘tile_x’, [-1, 32])],None,50
No: 5 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595265.6324012) [(‘tile_y’, [-1, 256]), (‘tile_x’, [-1, 64])],None,68
No: 6 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595265.934279) [(‘tile_y’, [-1, 256]), (‘tile_x’, [-1, 512])],None,98
No: 7 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595266.2497342) [(‘tile_y’, [-1, 128]), (‘tile_x’, [-1, 2])],None,17
No: 8 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595266.5602283) [(‘tile_y’, [-1, 8]), (‘tile_x’, [-1, 4])],None,23
No: 9 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595267.6910803) [(‘tile_y’, [-1, 256]), (‘tile_x’, [-1, 32])],None,58
No: 10 GFLOPS: 0.00/0.00 result: MeasureResult(costs=(‘request_remote() argument after ** must be a mapping, not tuple’,), error_no=7, all_cost=10, timestamp=1614595267.9915197) [(‘tile_y’, [-1, 64]), (‘tile_x’, [-1, 128])],None,76
最后,最好從緩存文件中應(yīng)用歷史記錄,并檢查其正確性??梢詍atmul直接在 autotvm.apply_history_best上下文中調(diào)用該函數(shù)。當(dāng)調(diào)用此函數(shù)時,使用其參數(shù)查詢調(diào)度上下文,并使用相同的參數(shù)獲得最佳配置。
apply history best from log file
with autotvm.apply_history_best(“matmul.log”):
with tvm.target.Target(“l(fā)lvm”):
s, arg_bufs = matmul(N, L, M, “float32”)
func = tvm.build(s, arg_bufs)
check correctness
a_np = np.random.uniform(size=(N, L)).astype(np.float32)
b_np = np.random.uniform(size=(L, M)).astype(np.float32)
c_np = a_np.dot(b_np)
c_tvm = tvm.nd.empty(c_np.shape)
func(tvm.nd.array(a_np), tvm.nd.array(b_np), c_tvm)
tvm.testing.assert_allclose(c_np, c_tvm.asnumpy(), rtol=1e-2)
總結(jié)
以上是生活随笔為你收集整理的编写可调模板并使用自动调谐器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: XGBoost4J-Spark基本原理
- 下一篇: ARM CPU神经网络自动调度