TinyML-TVM如何驯服TinyML
TinyML-TVM如何馴服TinyML
 低成本,以人工智能為動力的消費類設備的激增,導致機器學習研究人員和從業人員對“裸機”(低功耗,通常沒有操作系統)設備產生了廣泛的興趣。盡管專家已經有可能在某些裸機設備上運行某些模型,但是為各種設備優化模型的挑戰非常艱巨,通常需要手動優化設備特定的庫。對于那些沒有Linux支持的平臺,不存在用于部署模型的可擴展解決方案。因此,為了定位新設備,開發人員必須實現一次性的定制軟件堆棧,以管理系統資源和調度模型執行。
 機器學習軟件的手動優化不是裸機設備領域獨有的。實際上,對于那些使用其它硬件后端(例如GPU和FPGA)的開發人員來說,這是一個共同的主題。TVM已被證明可以抵御新硬件目標的沖擊,但直到現在,它仍無法解決微控制器的獨特特性。為了解決這一領域的問題,擴展了TVM以提供稱為μTVM(腳注:發音為“ MicroTVM”)的微控制器后端。μTVM有助于在裸機設備上執行主機驅動的張量調度,并通過TVM內置的張量調度優化器AutoTVM來自動優化這些調度。下圖顯示了μTVM + AutoTVM基礎架構的鳥瞰圖:
 
作用
 在討論什么是TVM / MicroTVM或如何工作之前,看一下實際使用示例。
 
標準μTVM設置,主機通過JTAG與設備通信。
 上面,有一塊STM32F746ZG板,其中裝有ARM Cortex-M7處理器,考慮到在低功耗環境下的強大性能,它是邊緣AI的理想部件。使用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}’)
 與CMSIS-NN 5.7.0版(commit a65b7c9a)(一種手動優化的ML內核庫)相比,以下是MicroTVM的性能結果。
 
開箱即用的性能并不好,但這就是AutoTVM搶救的地方。可以為設備編寫調度模板,進行一輪自動調整,然后獲得明顯更好的結果。要插入自動調整的結果,只需要替換以下行:
 graph, c_module, params = relay.build(module[‘main’], target=TARGET, params=params)
 這些行:
 with TARGET, autotvm.apply_history_best(TUNING_RESULTS_FILE):
 graph, c_module, params = relay.build(module[‘main’], target=TARGET, params=params)
 現在,結果如下所示:
 
性能提高了約2倍,現在,離CMSIS-NN更近了。盡管MicroTVM CIFAR10實施方案與類似的TFLite / CMSIS-NN模型方面具有競爭力,但這項工作才剛剛開始利用TVM的優化功能。通過加速其它調度(如密集/完全連接)并利用TVM特定于模型的量化和調度融合功能,還有進一步優化的空間。帶有μTVM的TVM能夠充分發揮作用。它是怎樣工作的?幕后發生了什么事?
 設計
 
μTVM設備在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基礎架構和設備運行時僅用于滿足這些要求,通過支持通用的開源運行時平臺(例如mBED OS)來處理編譯和鏈接過程,以降低這些要求。
 設備會話
 鑒于微控制器交互的網絡性質,通過引入的概念略微偏離了標準TVM代碼MicroSession。
 μTVM中的每個功能都依賴于與目標設備的開放會話。如果熟悉TVM,可能會注意到有一行代碼與第一個代碼段中的規范有所不同-即,這是一個代碼:
 …
 with micro.Session(device_config) as sess:
 …
 該with塊內的每一行都可以調用μTVM中的函數,上下文是所指定的設備device_config。這條線在做很多事情,所以拆開包裝。
 首先,使用指定的任何一種通信方法(通常是OpenOCD)來初始化與設備的連接。然后,使用指定的交叉編譯器交叉編譯μTVM設備的運行時。最后,由主機分配已編譯二進制文件的空間,并使用打開的連接,將二進制文件加載到設備上。
 有了設備上的運行時,自然會希望一些功能運行。
 模塊加載
 TVM中的核心抽象之一是模塊的抽象。模塊存儲用于特定設備/運行時目標的一組相關功能。鑒于微控制器通常沒有操作系統,因此μTVM需要做很多額外的工作來維持這種高級抽象。查看發生了什么,跟蹤創建和加載與μTVM兼容的模塊的過程。
 假設有一個micro.Session開放的設備和一個實現2D卷積的TVM調度。如果想將其加載到微控制器上,需要發出C代碼。為此,只需要target在tvm.build或中設置即可relay.build。示例:
 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源代碼,為所得的二進制文件分配空間(以便它可以與運行時在設備內存中共存),然后將二進制文件的每個部分發送到設備上分配的插槽中。一旦模塊二進制文件在設備內存中,便會修補二進制文件中的功能指針,以使模塊可以在設備運行時訪問輔助函數(例如,用于分配便箋記錄)。
 現在,在將內核加載到設備上之后,可以獲取卷積函數的遠程句柄,如下所示:
 micro_func = micro_mod[‘conv2d’]
 張量加載
 如果要調用算子,首先需要一些張量作為參數:
 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)
 鑒于這些張量已在設備上分配,只需要將元數據發送到設備(設備地址,形狀和數據類型),要使用哪個駐留張量。函數調用的運行時表示形式包括,此元數據以及被調用函數的地址(如下所示)。在構造此表示形式之前,需要將元數據序列化到目標明確存在的設備上的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實例,從主機端將其寫入其中。一旦寫入任務,運行時就具有執行該功能所需的一切,并且可以在運行時的入口處開始執行。運行時將執行一些輕量級的初始化,運行算子,然后將控制權返回給主機。
 lazy執行
 在實踐中,由于通信開銷開始占主導地位,一旦用戶要求執行算子就變得非常昂貴。可以通過延遲評估直到用戶希望獲得調用結果的方式來提高系統的吞吐量。
 從實現的角度來看,UTVMTask不急于序列化參數元數據和數據,而是需要先在主機端累積函數調用元數據,然后再將其刷新到設備中。設備運行時還需要進行一些更改:(1)現在必須具有的全局數組,UTVMTask并且(2)需要依次遍歷并執行每個任務。
 帶MicroTVM的AutoTVM
 到目前為止,描述的運行時對于模型部署似乎并不是很有用,因為它非常依賴主機。這是有意為之的,實際上,運行時是為實現另一個目標而設計的:AutoTVM支持。
 通常,AutoTVM會提出候選內核,并使用隨機輸入在目標后端運行,然后使用調度結果來改善其搜索過程。鑒于AutoTVM只關心單個算子的執行,將運行時設計為面向算子,而不是面向模型。但是對于μTVM,與設備的通信通常會占據執行時間。lazy惰性執行可以多次運行同一算子,而無需將控制權交還給主機,因此,通信成本在每次運行時均攤銷,可以更好地了解性能概況。
 由于AutoTVM需要在大量候選內核上進行快速迭代,因此μTVM基礎架構目前僅使用RAM。但是,對于自托管運行時,肯定需要同時使用閃存和RAM。
 托管圖運行時
 盡管托管的運行時是為AutoTVM設計的,但仍然可以運行完整的模型(沒有任何控制流)。僅通過使用TVM的圖形運行時,即可免費使用此功能,但具有μTVM上下文。實際上,圖運行時對主機的唯一依賴是張量分配和算子調度(這只是依賴圖的一種拓撲類型)。
 評估
 有了這個基礎架構,試圖回答以下問題:
- μTVM是否真的與設備無關?
- 使用μTVM進行優化試驗需要多少算力?
 為了評估(1),在兩個目標上進行了實驗:
 ? 一個armSTM32F746NG開發板,采用了的Cortex-M7處理器
 ? μTVM主機模擬設備,可以在主機上創建一個內存競技場,與之連接的主機就像裸機設備一樣。
 為了評估(2),探索Arm板的優化方案,這些方案可以最大程度地降低成本。
 作為比較,從Arm中提取量化的CIFAR-10 CNN 。CMSIS-NN(Arm專家高度優化的內核庫)用作算子庫,使該CNN成為理想的評估目標,可以直接將μTVM的結果與Arm上的CMSIS-NN進行比較木板。
 
CIFAR-10 CNN圖
 方法
 在實驗中,使用HEAD的TVM(commit 9fa8341),CMSIS-NN的5.7.0版(commit a65b7c9a),STM32CubeF7的1.16.0版以及Arm的適用于Arm嵌入式處理器的GNU工具的GCC 9-2019-q4-major 9.2 .1工具鏈(修訂版277599)。實驗中,使用的主機運行Ubuntu Linux 18.04.4 LTS,運行帶有62GB RAM的AMD Ryzen Threadripper 2990WX 32核處理器。
 特定于arm的優化
 使用CMSIS-NN,第一個卷積映射到其RGB卷積實現(專門用于輸入層),而后兩個卷積映射到其“快速”卷積實現。經過較早的泛型優化后,性能對于RGB卷積已經足夠接近了,但是對快速卷積結果卻不滿意。幸運的是,Arm發布了一篇描述CMSIS-NN中使用的優化的論文,發現正從SIMD內在函數中獲得巨大的提速。本文提出了一種使用SIMD內在函數的矩陣乘法微內核(下圖)。雖然可以在TVM的代碼生成工具中添加對內在函數的一流支持,這從長遠來看可能是最好的做法,TVM 張量化是支持SIMD的“快捷方法”。
 
CMSIS-NN論文的圖表顯示了2x2矩陣乘法微內核
 張量化通過定義可插入TVM算子最內層循環的微內核來工作。使用這種機制,添加對Arm板的SIMD支持,就像在C中定義一個微內核一樣簡單,該微內核反映了其論文中的實現。定義了一個調度,使用該微內核,對其進行自動調整,然后得到“ μTVM SIMD調整”結果。
 盡管能夠使用SIMD微內核進行直接卷積,但是CMSIS-NN使用稱為“ partial im2col”的實現策略,這在性能和內存使用之間進行了權衡。代替一次顯示整個im2col矩陣,部分im2col一次只生成幾列。然后,對于每一批,可以將矩陣發送到其SIMD matmul函數。
 假設是,除其它優化外,可以通過自動調整找到最佳的批量大小。在實踐中,發現部分im2col比直接卷積實現要慢得多,不在其余結果中。
 當然,還可以從CMSIS-NN中獲得其它優化,以進一步縮小差距:
 ? 將int8權重批量擴展為int16,以減少SIMD的重復擴展
 ? 將卷積拆分為3x3的圖塊,減少填充檢查
 本文目標是展示μTVM可以完成的工作的大致范圍。即使這樣,這也不是競爭,因為CMSIS-NN(以及任何其它手動優化的庫),可以使用Bring Your Own Codegen框架直接插入TVM 。
 端到端
 CIFAR-10
 在探索卷積優化之后,著手測量其對端到端性能的影響。對于ARM板,收集了未調整的結果,這是調整的結果沒有任何使用SIMD,這是調整的結果與SIMD和結果使用CMSIS-NN。對于模擬的主機設備,僅收集未調整的結果和通用的調整結果。
 https://github.com/areusch/microtvm-blogpost-eval
 
int8Arm STM32F746NG進行量化的CIFAR-10 CNN比較
 
int8μTVM的仿真主機設備上對量化的CIFAR-10 CNN進行比較
 在Arm STM32系列板上,與最初的未調整算子相比,能夠將性能提高約2倍,并且所獲得的結果更接近CMSIS-NN。此外,能夠顯著提高主機仿真設備上的性能。盡管x86的數字意義不大,但可以使用相同的基礎架構(μTVM)來在極為不同的體系結構上優化性能。
 隨著更廣泛地擴展,在將來繼續關注更多端到端基準測試。
 自托管運行時:最終領域
 
設想的μTVM優化和部署流程
 如上所述,雖然當前運行時已經可以獲取端到端基準測試結果,但目前仍在路線圖上以獨立能力部署這些模型。差距在于面向AutoTVM的運行時當前依賴于主機來分配張量并調度函數執行。為了在邊緣是使用,需要通過μTVM,其產生一個管道單一待裸機設備上運行的二進制。然后,用戶可以通過將此二進制文件包含在邊緣應用調度中,輕松將快速ML集成到其應用調度中。該管道的每個階段都已經到位,現在只需將它們粘合在一起即可,期待在此方面的最新進展。
 結論
 用于單內核優化的MicroTVM現已準備就緒,并且是該用例的選擇。現在,當建立自托管的部署支持時,希望也和使μTVM成為模型部署的選擇一樣。但是,這不只是一場觀看比賽-記住:這都是開源的!μTVM仍處于起步階段,因此每個人對其軌跡都會產生很大的影響。
總結
以上是生活随笔為你收集整理的TinyML-TVM如何驯服TinyML的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: TVM自动调度器
- 下一篇: TVM自定义数据类型
