TVM:使用 Schedule 模板和 AutoTVM 來優化算子

TVM:使用 Schedule 模板和 AutoTVM 來優化算子

在本文中,我們將介紹如何使用 TVM 張量表達式(Tensor Expression,TE)語言編寫 Schedule 模板,AutoTVM 可以搜索通過這些模板找到最佳 Schedule。這個過程稱為自動調整(Auto Tuning),它有助于自動優化張量計算的過程。

本教程需基于之前介紹的如何使用 TE 來寫一個矩陣乘法的教程。

Auto Tuning 有兩步:

  • 第一步是定義一個搜索空間
  • 第二步是運行相應的搜索算法來探索這個空間

本教程將展示如何在 TVM 中完成這兩步,整個流程將以矩陣乘法為例。

安裝依賴

要使用 TVM 中的 AutoTVM 包,需要安裝這些額外的依賴:

pip install --user psutil xgboost cloudpickle

為了使 TVM 在 tuning 中運行得更快,建議使用 cython 作為 TVM 的 FFI。在 TVM 的根目錄中,執行:

pip3 install --user cython
sudo make cython3

現在我們開始寫 Python 代碼,先引入包:

import logging
import sysimport numpy as np
import tvm
from tvm import te
import tvm.testingfrom tvm import autotvm

TE 實現基本的矩陣乘法

回想一下使用TE實現矩陣乘法的基本方法。我們把它寫在這里,稍作修改。我們將把乘法封裝在Python 函數定義中。簡單起見,我們將把注意力集中在分割優化(split optimization)上,使用一個固定值來定義重排(reordering)的塊的大小。

def matmul_basic(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)# scheduley, x = s[C].op.axisk = 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]

使用 AutoTVM 優化矩陣乘法

在前面的明細表代碼中,我們使用常量值 8 作為 tiling 因子。但是,它可能不是最好的,因為最佳的tiling 因子取決于實際的硬件環境和輸入的形狀。

如果希望 Schedule 代碼能夠在更大范圍的輸入形狀和目標硬件上移植,最好定義一組候選值,并根據目標硬件上的測量結果選擇最佳值。

在 autotvm 中,我們可以定義一個可調參數,或為此類參數定義一個“knob”。

一個基本的矩陣乘法模板

我們的一個示例,介紹如何為 spliting schedule 操作的塊大小創建一個可調參數集。

# 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)# scheduley, x = s[C].op.axisk = s[C].op.reduce_axis[0]# 2. get the config objectcfg = autotvm.get_config()# 3. define search spacecfg.define_knob("tile_y", [1, 2, 4, 8, 16])cfg.define_knob("tile_x", [1, 2, 4, 8, 16])# 4. schedule according to configyo, 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]

這里對于之前的 schedule 代碼,我們有四處調整,從而可以得到一個可調節的 “模板”:

  1. 使用裝飾器 @autotvm.template() 來將此函數標記為一個模板

  2. 獲取配置項: cfg 可以視為本函數的一個參數,但是我們是通過一種不同的方式得到它的。有了這個參數,這個函數就不再是一個確定的 schedule。而是我們可以為這個函數傳入不同的配置來得到不同的 schedules。一個像這樣含有配置項的函數稱為 “模板”。

    為了使得模板函數更加 compact,我們通過完成以下兩件事情來在一個函數中定義參數搜索空間:

    • 通過一組值定義搜索空間。這是通過將 cfg 設置為一個 ConfigSpace 對象完成的。它會收集本函數中所有的可以調節的 knobs ,并從中建立一個搜索空間。
    • 根據此空間中的實體進行 schedule。這是通過將 cfg 設置為一個 ConfigEntity 對象完成的。當它是一個 ConfigEntity 時,它會忽略所有空間定義的API(即 cfg.define_XXX(...))。相反,它會所有可調節的 knobs 保存確定的值,然后我們根據這些值進行 schedule。

    在 auto-tuning 時,我們將首先使用 ConfigSpace 對象調用此模板以構建搜索空間。然后,我們在構建空間中使用不同的 ConfigEntity 調用此模板,以獲得不同的 schedule。最后,我們將度量由不同計劃生成的代碼,并選擇最優的。

  3. 定義兩個可調節的 knobs。第一個是具有 5 個可能值的 tile_y。第二個是 tile_x,具有相同的可能值列表。這兩個 knob 是獨立的,因此它們會有大小為 25=5x5 的搜索空間。

  4. 配置 knobs 被傳遞到 split schedule 操作,允許我們根據之前在 cfg 中定義的 5x5 確定值進行 schedule。

具有高級參數API的矩陣乘法模板

在前面的模板中,我們手動列出了 knobs 的所有可能值。這是定義空間的最低級別API,并提供要搜索的參數空間的顯式枚舉。但是,我們還提供了另一組API,可以使搜索空間的定義更簡單、更智能。在可能的情況下,我們建議您使用此高級API。

在下面的示例中,我們使用 ConfigSpace.define_split 來定義拆分 knob。它將列舉所有分割軸和構建空間的可能方法。

我們還有 ConfigSpace.define_reorder用于 reorder knob,ConfigSpace.define_annotation 用于展開、矢量化、線程綁定等注釋。當高級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)# scheduley, x = s[C].op.axisk = 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 configyo, 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]

注意:More Explanation on cfg.define_split

更多關于 cfg.define_split 的解釋

在此模板中,cfg.define_split(“tile_y”,y,num_outputs=2) 將枚舉所有可能的組合,這些組合可以將y軸拆分為兩個具有y長度因子的軸。例如,如果y的長度為32,我們希望使用32的因子將其拆分為兩個軸,那么(外軸長度、內軸長度)對有6個可能的值,即(32,1)、(16,2)、(8,4)、(4,8)、(2,16)或(1,32)。這些都是 tile_y 的6個可能值。

在 schedule 期間,cfg[“tile_y”] 是一個 SplitEntity 對象。我們將外軸和內軸的長度存儲在 cfg['tile_y'].size(包含兩個元素的元組)中。在這個模板中,我們使用yo, yi=cfg['tile_y']] 來應用它。實際上,這相當于yo, yi=s[C].split(y,cfg[“tile\u y”].size[1])yo, yi=s[C].split(y,npart=cfg[“tile\u y”].size[0])

使用 cfg.apply API 的優點是,它使多級拆分(即當num_outputs>=3時)更加容易。

第二步:使用AutoTVM優化矩陣乘法

在第一步中,我們編寫了一個矩陣乘法模板,該模板允許我們對分割 schedule 中使用的塊大小進行參數化。現在我們可以在這個參數空間上進行搜索。下一步是選擇一個調諧器(tuner)來指導這個空間的探索。

TVM 中的 Auto-tuners

調諧器的工作可以通過以下偽代碼來描述:

ct = 0
while ct < max_number_of_trials:propose a batch of configsmeasure this batch of configs on real hardware and get resultsct += batch_size

提出下一批配置時,調諧器可以采取不同的策略。TVM提供的一些調諧器策略包括:

  • tvm.autotvm.tuner.RandomTuner:按隨機順序枚舉空間
  • tvm.autotvm.tuner.GridSearchTuner:按網格搜索順序枚舉空間
  • tvm.autotvm.tuner.GATuner:利用遺傳算法進行空間搜索
  • tvm.autotvm.tuner.XGBTuner:使用基于模型的方法。訓練 XGBoost 模型預測降低 IR 的速度,并根據預測選擇下一批。

我們可以根據空間大小、時間預算和其他因素選擇調諧器。例如,如果您的空間非常小(小于1000),gridsearch 調諧器或隨機調諧器就足夠了。如果您的空間級別為 10^9(這是CUDA GPU上conv2d 算子的空間大小),XGBoostTuner 可以更高效地探索并找到更好的配置。

開始 tuning

這里我們繼續我們的矩陣乘法示例。首先,我們創建一個 tuning 任務。我們還可以檢查初始化的搜索空間。在這種情況下,對于 512x512 平方矩陣乘法,空間大小為 10x10=100。請注意,任務和搜索空間與選擇的調諧器無關。

N, L, M = 512, 512, 512
task = autotvm.task.create("tutorial/matmul", args=(N, L, M, "float32"), target="llvm")
print(task.config_space)

此處輸出:

N, L, M = 512, 512, 512
task = autotvm.task.create("tutorial/matmul", args=(N, L, M, "float32"), target="llvm")
print(task.config_space)

然后我們需要定義如何測量生成的代碼并選擇調諧器。因為我們的空間很小,隨機調諧器也可以。

在本教程中,我們只做了 10 次試驗來演示。實際上,你可以根據你的時間預算做更多的試驗。我們將把調優結果記錄到日志文件中。此文件可用于選擇調諧器稍后發現的最佳配置。

# logging config (for printing tuning log to the screen)
logging.getLogger("autotvm").setLevel(logging.DEBUG)
logging.getLogger("autotvm").addHandler(logging.StreamHandler(sys.stdout))

測量配置有兩個步驟:構建和運行。默認情況下,我們使用所有CPU核來編譯程序。然后我們依次測量它們。為了減少方差,我們進行了5次測量并取平均值。

measure_option = autotvm.measure_option(builder="local", 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")],
)

調優 tuning 完成后,我們可以從日志文件中選擇性能最好的配置,并使用相應的參數編譯 schedule。我們還快速驗證了 schedule 是否正確。我們可以直接在 autotvm.apply_history_best 上下文下調用函數 matmul。當我們調用此函數時,它將使用其參數查詢分派上下文,并使用相同的參數獲得最佳配置。

# apply history best from log file
with autotvm.apply_history_best("matmul.log"):with tvm.target.Target("llvm"):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.numpy(), rtol=1e-4)

最后的注意事項與總結

在本教程中,我們展示了如何構建算子模板,以使得 TVM 能夠對參數空間進行搜索并選擇最優的的 schedule 配置。為了更深入地了解其工作原理,我們建議在此示例上進行擴展,可以根據使用張量表達式(TE)入門教程中演示的 schedule 操作向計劃添加新的搜索參數。在接下來的部分中,我們將演示 AutoScheduler,這是一種TVM優化常用算子的方法,用戶無需提供用戶定義的模板。

Ref:
https://tvm.apache.org/docs/tutorial/autotvm_matmul_x86.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532664.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532664.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532664.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

TVM:使用 Auto-scheduling 來優化算子

TVM&#xff1a;使用 Auto-scheduling 來優化算子 在本教程中&#xff0c;我們將展示 TVM 的 Auto-scheduling 功能如何在無需編寫自定義模板的情況下找到最佳 schedule。 與基于模板的 AutoTVM 依賴手動模板定義搜索空間不同&#xff0c;auto-scheduler 不需要任何模板。 用…

C語言—sort函數比較大小的快捷使用--algorithm頭文件下

sort函數 一般情況下要將一組數從的大到小排序或從小到大排序&#xff0c;要定義一個新的函數排序。 而我們也可以直接使用在函數下的sort函數&#xff0c;只需加上頭文件&#xff1a; #include<algorithm> using namespace std;sort格式&#xff1a;sort(首元素地址&…

散列的使用

散列 散列簡單來說&#xff1a;給N個正整數和M個負整數&#xff0c;問這M個數中的每個數是否在N中出現過。 比如&#xff1a;N&#xff1a;{1,2,3,4}&#xff0c;M{2,5,7}&#xff0c;其中M的2在N中出現過 對這個問題最直觀的思路是&#xff1a;對M中每個欲查的值x&#xff0…

關于C++中的unordered_map和unordered_set不能直接以pair作為鍵名的問題

關于C中的unordered_map和unordered_set不能直接以pair作為鍵名的問題 在 C STL 中&#xff0c;不同于有序的 std::map 和 std::set 是基于紅黑樹實現的&#xff0c;std::unordered_map 和 std::unordered_set 是基于哈希實現的&#xff0c;在不要求容器內的鍵有序&#xff0c…

AI編譯器與傳統編譯器的聯系與區別

AI編譯器與傳統編譯器的區別與聯系 總結整理自知乎問題 針對神經網絡的編譯器和傳統編譯器的區別和聯系是什么&#xff1f;。 文中提到的答主的知乎主頁&#xff1a;金雪鋒、楊軍、藍色、SunnyCase、貝殼與知了、工藤福爾摩 筆者本人理解 為了不用直接手寫機器碼&#xff0…

python學習1:注釋\變量類型\轉換函數\轉義字符\運算符

python基礎學習 與大多數語言不同&#xff0c;python最具特色的就是使用縮進來表示代碼塊&#xff0c;不需要使用大括號 {} 。縮進的空格數是可變的&#xff0c;但是同一個代碼塊的語句必須包含相同的縮進空格數。 &#xff08;一個tab4個空格&#xff09; Python語言中常見的…

Python、C++ lambda 表達式

Python、C lambda 表達式 lambda函數簡介 匿名函數lambda&#xff1a;是指一類無需定義標識符&#xff08;函數名&#xff09;的函數或子程序。所謂匿名函數&#xff0c;通俗地說就是沒有名字的函數&#xff0c;lambda函數沒有名字&#xff0c;是一種簡單的、在同一行中定義函…

python 學習2 /輸入/ 輸出 /列表 /字典

python基礎學習第二天 輸入輸出 xinput("輸入內容") print(x)input輸出&#xff1a; eval :去掉字符串外圍的引號&#xff0c;按照python的語法執行內容 aeval(12) print(a)eval輸出樣式&#xff1a; 列表 建立&#xff0c;添加&#xff0c;插入&#xff0c;刪去…

Linux、Mac 命令行快捷鍵

Linux、Mac 命令行快捷鍵 Linux 命令行編輯快捷鍵&#xff0c;參考了好多個&#xff0c;應該算是比較全的了&#xff0c;Linux 和 Mac 的都有&#xff0c;筆者本人比較常用的也已經紅色標出來了&#xff0c;如有錯誤或遺漏&#xff0c;歡迎留言指出。 光標移動及編輯&#xff…

Python 命令行傳參

Python 命令行傳參 說到 python 命令行傳參&#xff0c;可能大部分人的第一反應就是用 argparse。的確&#xff0c;argparse 在我們需要指定多個預設的參數&#xff08;如深度學習中指定模型的超參數等&#xff09;時&#xff0c;是非常有用的。但是如果有時我們只需要一個參數…

快速排序 C++

快速排序 C 本文圖示借鑒自清華大學鄧俊輝老師數據結構課程。 快速排序的思想 快速排序是分治思想的典型應用。該排序算法可以原地實現&#xff0c;即空間復雜度為 O(1)O(1)O(1)&#xff0c;而時間復雜度為 O(nlogn)O(nlogn)O(nlogn) 。 算法將待排序的序列 SSS 分為兩個子…

Linux命令行下感嘆號的幾個用法

Linux命令行下 " ! " 的幾個用法 ! 在大多數編程語言中表示取反的意思&#xff0c;但是在命令行中&#xff0c;他還有一些其他的神奇用法。熟練掌握這些用法&#xff0c;可以大大提高我們日常命令行操作的效率。 1 執行歷史命令 !! ! 在命令行中可以用來執行歷史…

三地址碼簡介

三地址碼簡介 三地址碼&#xff08;Three Address Code&#xff09;是一種最常用的中間語言&#xff0c;編譯器可以通過它來改進代碼轉換效率。每個三地址碼指令&#xff0c;都可以被分解為一個四元組&#xff08;4-tuple&#xff09;的形式&#xff1a;&#xff08;運算符&am…

llvm與gcc

llvm與gcc llvm 是一個編譯器&#xff0c;也是一個編譯器架構&#xff0c;是一系列編譯工具&#xff0c;也是一個編譯器工具鏈&#xff0c;開源 C11 實現。 gcc 相對于 clang 的優勢&#xff1a; gcc 支持更過語言前端&#xff0c;如 Java, Ada, FORTRAN, Go等gcc 支持更多地 …

攻防世界web新手區解題 view_source / robots / backup

1**. view_source** 題目描述&#xff1a;X老師讓小寧同學查看一個網頁的源代碼&#xff0c;但小寧同學發現鼠標右鍵好像不管用了。 f12查看源碼即可發現flag 2. robots 題目描述&#xff1a;X老師上課講了Robots協議&#xff0c;小寧同學卻上課打了瞌睡&#xff0c;趕緊來教教…

python參數傳遞*args和**kwargs

python參數傳遞*args和**kwargs 和* 實際上真正的Python參數傳遞語法是 * 和 ** 。*args 和 **kwargs 只是一種約定俗成的編程實踐。我們也可以寫成 *vars 和 **kvars 。就如同其他常規變量的命名一樣&#xff0c; args 和 kwargs 只是一種習慣的名稱。 *args 和 **kwargs 一…

聽GPT 講Rust源代碼--src/tools(25)

File: rust/src/tools/clippy/clippy_lints/src/methods/suspicious_command_arg_space.rs 在Rust源代碼中&#xff0c;suspicious_command_arg_space.rs文件位于clippy_lints工具包的methods目錄下&#xff0c;用于實現Clippy lint SUSPICIOUS_COMMAND_ARG_SPACE。 Clippy是Ru…

Java一次編譯,到處運行是如何實現的

Java一次編譯&#xff0c;到處運行是如何實現的 轉自&#xff1a;https://cloud.tencent.com/developer/article/1415194 &#xff08;排版微調&#xff09; JAVA編譯運行總覽 Java是一種高級語言&#xff0c;要讓計算機執行你撰寫的Java程序&#xff0c;也得通過編譯程序的…

JIT(動態編譯)和AOT(靜態編譯)編譯技術比較

JIT&#xff08;動態編譯&#xff09;和AOT&#xff08;靜態編譯&#xff09;編譯技術比較 轉自&#xff1a;https://www.cnblogs.com/tinytiny/p/3200448.html Java 應用程序的性能經常成為開發社區中的討論熱點。因為該語言的設計初衷是使用解釋的方式支持應用程序的可移植…

python解釋器

python解釋器 計算機編程語言 本部分參考自&#xff1a;https://zhuanlan.zhihu.com/p/141212114 從計算機編程語言說起&#xff0c;它主要分為三類&#xff1a;機器語言、匯編語言、高級語言。 機器語言是一種計算機可以直接識別并執行的二進制指令集。由于其可以直接交給…