TinyML-TVM是如何馴服Tiny的(上)
TinyML-TVM是如何馴服Tiny的(上)
低成本、人工智慧驅動的消費類裝置的激增,導致了ML研究人員和從業者對“裸智慧”(低功耗,通常沒有作業系統)裝置的廣泛興趣。雖然專家已經可以在一些裸機裝置上執行某些模型,但是為不同裝置集優化模型是一個挑戰,通常需要手動優化特定於裝置的庫。對於那些沒有Linux支援的平臺,沒有可伸縮的模型部署解決方案。正因為如此,為了瞄準新裝置,開發人員必須實現一次性定製軟體堆疊,以管理系統資源和排程模型執行。
機器學習軟體的手動優化並不是裸機領域所獨有的。事實上,這一直是與其他硬體後端(例如,gpu和fpga)一起工作的開發人員的共同主題。TVM已經被證明能夠抵禦新硬體目標的衝擊,但直到現在,它還無法與微控制器的獨特配置相抗衡。為了解決這個領域的問題,擴充套件了TVM,使其具有一個微控制器後端,稱為µTVM(腳註:發音為“MicroTVM”)。µTVM有助於在裸機裝置上執行tensor程式,並通過TVM的內建tensor程式優化器AutoTVM自動優化這些程式。下圖顯示了µTVM+AutoTVM基礎設施的鳥瞰圖:
讓看看它的行動
在討論什麼是TVM/MicroTVM或它是如何工作之前,先看一個它在實際中的快速示例。
A standard µTVM setup, where the host communicates with the device via JTAG.
上面,有一個STM32F746ZG板,裡面有一個ARM Cortex-M7處理器,考慮到它在低功耗封裝中的強大效能,這是邊緣人工智慧的理想部件。使用它的USB-JTAG埠將其連線到桌上型電腦。在桌面上,執行OpenOCD來開啟與裝置的JTAG連線;反過來,OpenOCD允許µTVM使用與裝置無關的TCP套接字控制M7處理器。有了這個設定,可以使用TVM程式碼執行CIFAR-10分類器,如下所示(此處為完整指令碼):
OPENOCD_SERVER_ADDR = ‘127.0.0.1’
OPENOCD_SERVER_PORT = 6666
TARGET = tvm.target.create(‘c -device=micro_dev’)
DEV_CONFIG = stm32f746xx.default_config(OPENOCD_SERVER_ADDR, OPENOCD_SERVER_PORT)
module, params = get_cifar10_cnn()
with micro.Session(device_config) as sess:
graph, c_module, params = relay.build(module[‘main’], target=TARGET, params=params)
micro_mod = micro.create_micro_mod(c_module, DEV_CONFIG)
graph_mod = graph_runtime.create(graph, micro_mod, ctx=tvm.micro_dev(0))
graph_mod.run(data=data_np)
prediction = CIFAR10_CLASSES[np.argmax(graph_mod.get_output(0).asnumpy())]
print(f’prediction was {prediction}’)
下面是MicroTVM的效能結果,與CMSIS-NN版本5.7.0(commit a65b7c9a)相比,後者是一個手工優化的ML核心庫。
開箱即用的效能不是很好,但這正是AutoTVM的救命稻草。可以為裝置編寫一個排程模板,進行一輪自動調整,然後獲得顯著更好的結果。要插入自動調諧結果,只需要替換這一行:
graph, c_module, params = relay.build(module[‘main’], target=TARGET, params=params)
with these lines:
with TARGET, autotvm.apply_history_best(TUNING_RESULTS_FILE):
graph, c_module, params = relay.build(module[‘main’], target=TARGET, params=params)
And our results now look like this:
效能提高了約2倍,現在離CMSIS-NN更近了。儘管MicroTVM CIFAR10的實現與類似的TFLite/CMSIS-NN模型相比具有競爭力,但這項工作剛剛開始利用TVM的優化特性。通過加速其他運營商(如密集/全連線dense/fully-connected)和利用TVM的模型特定量化和運算子融合功能,還有進一步優化的空間。帶有µTVM的TVM能夠發揮最佳效能。如何工作的呢?幕後是怎麼回事?現在就開始吧。
Design
The µTVM Device Memory Layout in RAM
µTVM旨在通過最小化必須滿足的一組要求來支援裝置的最低公分母。特別是,使用者只需提供:
- 裝置的C交叉編譯器工具鏈
- 一種讀/寫裝置儲存器並在裝置上執行程式碼的方法
- 包含裝置記憶體佈局和一般體系結構特徵的規範
- 為裝置準備函式執行的程式碼段
大多數裸機裝置都支援C和JTAG(除錯協議),所以(1)和(2)通常是免費的!此外,(3)和(4)通常是非常小的要求。以下是STM32F746系列板的(3)和(4)示例。
device_config = {
‘device_id’: ‘arm.stm32f746xx’, # unique identifier for the device
‘toolchain_prefix’: ‘arm-none-eabi-’, # prefix of each binary in the cross-compilation toolchain (e.g., arm-none-eabi-gcc)
‘base_addr’: 0x20000000, # first address of RAM
‘section_sizes’: { # dictionary of desired section sizes in bytes
‘text’: 18000,
‘rodata’: 100,
‘data’: 100,
…
},
‘word_size’: 4, # device word size
‘thumb_mode’: True, # whether to use ARM’s thumb ISA
‘comms_method’: ‘openocd’, # method of communication with the device
‘server_addr’: ‘127.0.0.1’, # OpenOCD server address (if ‘comms_method’ is ‘openocd’)
‘server_port’: 6666, # OpenOCD server port (if ‘comms_method’ is ‘openocd’)
}
.syntax unified
.cpu cortex-m7
.fpu softvfp
.thumb
.section .text.UTVMInit
.type UTVMInit, %function
UTVMInit:
/* enable fpu /
ldr r0, =0xE000ED88
ldr r1, [r0]
ldr r2, =0xF00000
orr r1, r2
str r1, [r0]
dsb
isb
/ set stack pointer /
ldr sp, =_utvm_stack_pointer_init
bl UTVMMain
.size UTVMInit, .-UTVMInit
µTVM基礎架構和裝置runtime的構建僅僅是為了利用這些需求,正在努力通過支援常見的開源runtime平臺(如mBED OS)來處理編譯和連結過程來減少這些需求。
裝置會話
考慮到微控制器互動的網路特性,引入微會話的概念,稍微偏離了標準的TVM程式碼。
µTVM中的每一項功能都依賴於與目標裝置的開放會話。如果熟悉TVM,可能已經注意到在第一個程式碼片段中有一行程式碼偏離了規範,即這一行:
…
with micro.Session(device_config) as sess:
…
此with塊內的每一行都可以呼叫µTVM中的函式,上下文是device_config指定的裝置。這條線在hood下面做了很多事情,讓把它拆開。
首先,它使用指定的任何通訊方法(通常是OpenOCD)初始化與裝置的連線。然後使用指定的交叉編譯器交叉編譯µTVM裝置 runtime。最後,主機為編譯後的二進位制檔案分配空間,並使用開啟的連線將二進位制檔案載入到裝置上。
由於 runtime現在位於裝置上,自然需要一些函式來執行它。
Module Loading
TVM的核心抽象之一是模組。模組為特定的裝置/ runtime目標儲存一組相關函式。考慮到微控制器通常沒有作業系統,µTVM需要做大量額外的工作來維護這種高階抽象。為了瞭解發生了什麼,將跟蹤建立和載入µTVM相容模組的過程。
假設有一個微型會議開啟裝置和實現二維卷積的TVM排程。如果想把它載入到微控制器上,需要它發出C程式碼。要做到這一點,只需要設定目標tvm.build or relay.build. Example:
graph, c_module, params = relay.build(module[‘main’], target=‘c -device=micro_dev’, params=params)
通過這樣設定目標,構建過程將通過C程式碼生成後端執行。但是,生成的C模組仍然駐留在主機上。為了將其載入到裝置上,通過µTVM基礎設施中的一個核心功能來執行它:create_micro_mod。
例子:
micro_mod = micro.create_micro_mod(c_module, DEV_CONFIG)
上面的行交叉編譯模組中的C原始碼,為生成的二進位制檔案分配空間(這樣它就可以與裝置記憶體中的 runtime共存),然後將二進位制檔案的每個部分傳送到裝置上分配的插槽中。一旦模組二進位制檔案在裝置記憶體中處於合適的位置,二進位制檔案中的函式指標將被修補,使模組能夠在裝置 runtime訪問help函式(例如,分配草稿行)。
現在,在裝置上載入核心後,可以獲取卷積函式的遠端控制程式碼,如下所示:
micro_func = micro_mod[‘conv2d’]
Tensor Loading
If we want to call an operator, we first need some tensors as arguments:
data_np, kernel_np = get_conv_inputs()
ctx = tvm.micro_dev(0)
data = tvm.nd.array(data_np, ctx=ctx)
kernel = tvm.nd.array(kernel_np, ctx=ctx)
根據其資料型別(例如int8、float32等)和形狀,計算每個張量的位元組大小,主機在裝置堆上分配記憶體區域。然後將張量的資料載入到分配的區域中。
函式呼叫
運算元執行可能是這個系統中最棘手的部分。為了簡化它的表示,將首先討論嚴格執行(運算元一被呼叫就立即執行),然後是延遲執行(只有在需要運算元的結果時才執行運算元)——後者是系統的實際工作方式。
嚴格執行
呼叫函式時,輸入和輸出張量都作為引數傳遞,這就是所謂的目標傳遞樣式:
conv2D(data, kernel, output)
考慮到這些張量已經在裝置上分配,只需要向裝置傳送後設資料(device address, shape, and data type)(裝置地址、形狀和資料型別),這樣裝置就知道要使用哪個駐留張量。下面顯示的是一個名為“runtime”的函式的呼叫。在構造這個表示之前,需要將後設資料序列化到專門為此目的而存在的裝置上的arguments部分中。
/
- task struct for uTVM
/
typedef struct {
/ pointer to function to call for this task /
int32_t (func)(void, void, int32_t);
/* array of argument tensors /
TVMValue arg_values;
/* array of datatype codes for each argument /
int arg_type_codes;
/* number of arguments */
int32_t num_args;
} UTVMTask;
在嚴格的設定中,有一個全域性UTVMTask例項,從主機端寫入該例項。一旦寫入任務,runtime就擁有了執行函式所需的一切,可以在runtime的入口點開始執行。runtime將執行一些輕量級初始化,執行運算元,然後將控制權返回給主機。
相關文章
- TinyML-TVM是如何馴服Tiny的(下)
- Cadence:馴服複雜流程的工作流引擎
- 馴服定時器和執行緒定時器執行緒
- 騰訊物聯TencentOS tiny上雲初探CentOS
- 用斷路器馴服資料質量
- 桀驁不馴的程式碼又搞事情?我找來 10 個開源專案幫你馴服它們!
- 《馴龍高手:旅程》今日全平臺上線 龍騎手,是時候出發了!
- 擁抱 invokedynamic,在 Java agent 中馴服類載入器Java
- tiny-spring分析Spring
- 世界上最大的Web服務商Dropbox是如何從Nginx遷移到Envoy的?WebNginx
- 前瞻打造超級「怪獸」,商湯想要馴服AI長尾AI
- 四個基本的JavaScript函式來馴服CSS3過渡和動畫(JavaScript函式CSSS3動畫
- 馴服 Kubernetes!網易數帆雲原生運維體系建設之路運維
- Tiny Core Linux 安裝配置Linux
- Spring bean到底是如何建立的?(上)SpringBean
- 專訪領英工程副總裁張仁輝:如何馴服演算法,打造世界級的職位推薦系統?演算法
- 中國科學家發明新催化劑 馴服甲烷可做火箭燃料
- “舌尖上的美團”如何更好服務吃貨?
- 如何避免遊戲炸服——遊戲上線的RoadMap遊戲
- 細說TF服務鏈丨服務鏈的冗餘是如何實現的
- 市場上的服裝ERP體系到底是哪些型別?型別
- 線上服務被幹爆了,竟然是日誌的鍋!!
- 勒索軟體即服務(RaaS)是什麼?這個模型是如何工作的?模型
- 機器人客服是如何改變企業服務的機器人
- 如何在 Kali Linux 上安裝 SSH 服務Linux
- 在米爾FPGA開發板上實現Tiny YOLO V4,助力AIoT應用FPGAYOLOAI
- async/await 在 C# 語言中是如何工作的?(上)AIC#
- 《馴龍高手:旅程》——維京傳奇,馴龍之旅,8月2日全平臺公測啟航!
- PoD-Tiny——實現零信任交易的最簡協議協議
- Micro原始碼系列 - Go-Micro服務是如何註冊的原始碼Go
- 設計好的報表是如何在 web 上顯示的Web
- “沒有了夢想”的Uber,是如何在AI上折戟沉沙的?AI
- 如何在SAP雲平臺上使用MongoDB服務MongoDB
- ES6系列之Babel是如何編譯Class的(上)Babel編譯
- ES6 系列之 Babel 是如何編譯 Class 的(上)Babel編譯
- 前端傳送的請求,是如何請求到後端服務的?前端後端
- 服務端有辦法知道上傳上來的圖片是透過手機拍攝還是選擇照片上傳的嗎服務端
- Netty原始碼—六、tiny、small記憶體分配Netty原始碼記憶體