VTA硬件
VTA硬件
 提供了VTA硬件設計的自上而下的概述。本硬件設計涵蓋兩個級別的VTA硬件:
 ? VTA設計及其ISA硬件-軟件接口的體系結構概述。
 ? VTA硬件模塊的微體系結構概述以及計算核心的微代碼規范。
 VTA概述
 VTA是為快速,高效的密集線性代數而構建的通用深度學習加速器。VTA集成了一個簡單的類似RISC的處理器,可以對1或2級張量寄存器執行密集的線性代數運算。另外,該設計采用解耦訪問執行以隱藏內存訪問延遲。
 在更廣泛的范圍內,VTA可以用作模板的深度學習加速器設計,以進行完整的堆棧優化,從而將通用張量計算接口公開給編譯器堆棧。
 本文從總體上概述了VTA硬件組織。VTA由四個模塊組成,這些模塊通過FIFO隊列和本地內存塊(SRAM)相互通信,以實現任務級管道并行性:
 ? 提取模塊負責從DRAM加載指令流。解碼這些指令,路由到三個命令隊列之一。
 ? 加載模塊負責將來自DRAM的輸入和權重張量加載到數據專用的片上存儲器中。
 ? 計算模塊使用GEMM核執行密集線性代數計算,并使用張量ALU進行常規計算。將數據從DRAM加載到寄存器文件中,以及將micro-op內核加載到micro-op緩存中。
 ? 存儲模塊將計算核心產生的結果存儲回DRAM。
 HLS硬件來源組織
 VTA設計當前在Vivado HLS C ++中指定,只有Xilinx工具鏈才支持。VTA硬件來源包含在3rdparty/vta-hw/hardware/xilinx/sources:
 ? vta.cc 包含每個VTA模塊的定義以及頂級VTA設計的頂級行為模型。
 ? vta.h包含使用Xilinxap_int類型的類型定義以及函數原型聲明。
 預處理器宏在下定義3rdparty/vta-hw/include/vta/hw_spec.h。這些宏定義中的大多數是從3rdparty/vta-hw/config/vta_config.json文件中列出的參數派生的。通過處理json文件3rdparty/vta-hw/config/vta_config.py來生成一串編譯標志,這些編譯標志定義了預處理器宏。makefile使用該字符串,以便在HLS硬件綜合編譯器和構建VTA運行時的C ++編譯器中設置那些高級參數。
 HLS模塊示例
 顯示了C ++中定義的VTA模塊之一的定義:
 void fetch(
 uint32_t insn_count,
 volatile insn_T *insns,
 hls::stream<insn_T> &load_queue,
 hls::stream<insn_T> &gemm_queue,
 hls::stream<insn_T> &store_queue) {
 #pragma HLS INTERFACE s_axilite port = insn_count bundle = CONTROL_BUS
 #pragma HLS INTERFACE m_axi port = insns offset = slave bundle = ins_port
 #pragma HLS INTERFACE axis port = load_queue
 #pragma HLS INTERFACE axis port = gemm_queue
 #pragma HLS INTERFACE axis port = store_queue
 #pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
INSN_DECODE: for (int pc = 0; pc < insn_count; pc++) {
 #pragma HLS PIPELINE II = 1
 // Read instruction fields
 insn_T insn = insns[pc];
 // Do some partial decoding
 opcode_T opcode = insn.range(VTA_INSN_MEM_0_1, VTA_INSN_MEM_0_0);
 memop_id_T memory_type = insn.range(VTA_INSN_MEM_5_1, VTA_INSN_MEM_5_0);
 // Push to appropriate instruction queue
 if (opcode == VTA_OPCODE_STORE) {
 store_queue.write(insn);
 } else if (opcode == VTA_OPCODE_LOAD &&
 (memory_type == VTA_MEM_ID_INP || memory_type == VTA_MEM_ID_WGT)) {
 load_queue.write(insn);
 } else {
 gemm_queue.write(insn);
 }
 }
 }
 關于HLS編碼的一些觀察:
 ? 參數:每個功能的參數列表,與接口實用程序結合,定義了所生成的硬件模塊公開的硬件接口。
 o 通過值傳遞的參數表示主機可以寫入的只讀硬件內存映射寄存器。例如,該提取功能具有一個insn_count參數,該參數將被合成為主機要寫入的存儲器映射寄存器,以便設置給定VTA指令序列的長度。
 o 指針參數可能意味著兩件事之一,具體取決于所使用的接口編譯指示。
 ? 與m_axi接口編譯指示一起使用時,將生成AXI主接口以提供對DRAM的DMA訪問。
 ? 與bram接口編譯指令一起使用時,將生成BRAM接口,以將讀取和/或寫入端口公開給FPGA Block-RAM。
 o 通過引用傳遞的HLS流與axis接口編譯指示相結合,產生了到模塊的FIFO接口。硬件FIFO在模塊之間提供了一種有用的同步機制。
 ? 編譯指示:編譯器編譯對于定義每個模塊的硬件實現是必不可少的。列出了VTA設計中用于將實現要求傳達給編譯器的幾種編譯指示。
 o HLS INTERFACE:指定綜合硬件模塊的接口。
 o HLS PIPELINE:通過設置啟動間隔目標來定義硬件管道性能目標。當目標被設定,它告訴編譯器合成的硬件管線應該能夠每個周期執行一個II == 1循環迭代。
 o HLS DEPENDENCE:指示編譯器在給定循環中忽略某些類型的依賴項檢查。考慮一個寫入和讀取相同BRAM結構的循環體,并且需要實現II為1。HLS編譯器必須假定最壞的情況,即對先前寫入更新了該周期之前地址的地址進行讀取:在給定的BRAM時序特性下,這是無法實現的(至少需要2個周期才能看到更新的值)。為了獲得II等于1,必須放松依賴檢查。啟用此優化功能后,它會落在軟件堆棧上,以防止寫入之后再讀取相同的地址。
 筆記
 該參考為Xilinx 2018.2工具鏈提供了更深入,更完整的HLS規范。
 架構概述
 指令集架構
 VTA的指令集體系結構(ISA)由4條具有可變執行等待時間的CISC指令組成,其中兩條執行微碼指令序列以執行計算。
 VTA說明如下:
 ? LOAD指令:將DRAM中的2D張量加載到輸入緩沖區,權重緩沖區或寄存器文件中。將微內核加載到微操作緩存中。在加載輸入和權重圖塊時支持動態填充。
 ? GEMM 指令:在輸入張量和權重張量上執行矩陣矩陣乘法的微操作序列,并將結果添加到寄存器文件張量中。
 ? ALU 指令:對寄存器文件張量數據執行矩陣矩陣ALU操作的微操作序列。
 ? STORE 指令:將2D張量從輸出緩沖區存儲到DRAM。
 該LOAD指令由取決于存儲存儲器緩沖器位置目標的負荷和計算模塊執行。在GEMM和ALU指令由計算模塊的核心GEMM和張量ALU執行。最后,STORE指令僅由存儲模塊執行。下圖描述了每個指令的字段。
 VTA ISA會隨著VTA的體系結構參數(即GEMM核心形狀,數據類型,內存大小等)的修改而改變,因此ISA不能保證所有VTA變體之間的兼容性。這是可以接受的,因為VTA運行時會適應參數更改,并生成針對要生成的加速器版本量身定制的二進制代碼。這體現了VTA堆棧采用的協同設計理念,該理念包含了軟硬件接口的流動性。
 數據流執行
 VTA依靠硬件模塊之間的依賴性FIFO隊列來同步并發任務的執行。給定的硬件模塊如何通過使用依賴性FIFO隊列和單讀取器/單寫入器SRAM緩沖區,以數據流方式從其生產者模塊和使用者模塊同時執行。每個模塊通過寫后讀(RAW)和讀后寫(WAR)依賴關系隊列連接到其使用者和生產者。
 上面的偽代碼描述了模塊如何執行給定指令,該指令基于對其他指令的依賴性而確定。首先,每個指令內的依賴標志都在硬件中解碼。如果指令具有傳入的RAW依賴關系,則根據從生產者模塊接收到RAW依賴關系令牌來確定執行。如果任務具有傳入的WAR依賴關系,則根據從使用者模塊接收到的WAR依賴關系令牌來確定執行。當任務完成時,檢查傳出的RAW和WAR依賴性,分別通知使用者和生產者模塊。
 在這種情況下,依賴標記是無信息的。這是因為每個模塊執行的指令不能按設計重新排序,因為以FIFO順序到達。
 管道擴展性
 默認的VTA設計由描述一個三階段load-compute-store任務管道的四個模塊組成。遵循數據流硬件組織原則,可以將VTA擴展到更多階段。例如,可以設想將張量ALU與GEMM核心分開,以最大程度地利用GEMM核心。這將導致load-gemm-activate-store任務流水線緊密地反映TPU設計。增加更多的階段會產生成本:增加存儲空間和額外的邏輯開銷,這就是選擇默認的3階段管道的原因。
 微架構概述
 描述組成VTA設計的模塊。模塊定義包含在中3rdparty/vta-hw/hardware/xilinx/sources/vta.cc。
 提取模塊
 VTA由線性指令流編程。提取模塊是VTA到CPU的入口,通過三個內存映射寄存器進行編程:
 ? 讀寫control寄存器啟動讀取模塊,并對其進行讀取以檢查其是否完成。
 ? 只寫insn_count寄存器設置要執行的指令數。
 ? 只寫insns寄存器設置DRAM中指令流的起始地址。
 CPU在VTA運行時準備的物理連續緩沖區中的DRAM中準備指令流。當指令流準備就緒時,CPU將起始物理地址寫入insns寄存器,將指令流的長度寫入insn_count寄存器,并在寄存器中聲明起始信號control。此過程將啟動VTA,VTA將通過DMA從DRAM讀取指令流。
 訪問指令流后,獲取模塊會部分解碼指令,將這些指令推入命令隊列,這些命令隊列會饋入裝入,計算和存儲模塊:
 ? STORE 指令被推送到存儲命令隊列以由存儲模塊處理。
 ? GEMM和ALU指令被推到由計算模塊被處理的計算命令隊列。
 ? LOAD 描述微操作內核的加載操作或寄存器文件數據的指令被推到計算命令隊列中,由計算模塊處理。
 ? LOAD 描述輸入或重量數據的加載操作的指令被推到加載命令隊列中,以由加載模塊進行處理。
 當命令隊列之一變滿時,訪存模塊將暫停,直到隊列未滿為止。命令隊列的大小應足夠深以允許寬闊的執行窗口,并允許多個任務同時在load-compute-store管道中進行。
 計算模塊
 VTA的計算模塊充當RISC處理器,該處理器在張量寄存器而非標量寄存器上執行計算。兩個功能單元使寄存器文件發生變化:張量ALU和GEMM內核。
 計算模塊從微操作緩存執行RISC微操作。有兩種類型的計算微操作:ALU和GEMM操作。為了最大程度地減少微操作內核的占用空間,同時避免了對諸如條件跳轉之類的控制流指令的需求,計算模塊在兩級嵌套循環內執行微操作序列,嵌套循環通過以下操作計算每個張量寄存器的位置:仿射功能。這種壓縮方法有助于減少微內核指令的占用空間,適用于矩陣乘法和2D卷積,這在神經網絡運算符中很常見。
 所述GEMM芯求值GEMM指令,通過在上述圖中所描述的2級嵌套循環執行的微碼序列。GEMM內核每個周期可以執行一個輸入權重矩陣乘法。單周期矩陣乘法的維數定義了硬件張量內在函數TVM編譯器必須將其降低到較低的計算調度表上。張量固有值由輸入張量,權重和累加器張量的尺寸定義。每種數據類型都可以具有不同的整數精度:權重和輸入類型通常都是低精度的(8位或更少),而累加器張量具有更寬的類型以防止溢出(32位)。為了使GEMM核心保持繁忙,每個輸入緩沖區,權重緩沖區和寄存器文件都必須公開足夠的讀/寫帶寬。
 張量ALU支持一組標準操作來實現共同活化,歸一化,池運算符。VTA是模塊化設計,可以擴展Tensor ALU支持的算子范圍,以提高算子覆蓋范圍,但要以提高資源利用率為代價。Tensor ALU可以對立即數執行張量-張量運算以及張量-標量運算。張量ALU的操作碼和立即數由高級CISC指令指定。張量ALU計算上下文中的微代碼僅負責指定數據訪問模式。
 就計算吞吐量而言,Tensor ALU并非以每個周期一次操作的速度執行。限制來自缺乏讀取端口:由于每個周期可以讀取一個寄存器文件張量,張量ALU的啟動間隔至少為2(即,每2個周期最多執行1次操作)。一次執行單個張量-張量操作可能會很昂貴,特別是考慮到寄存器文件類型很寬(通常為32位整數)。為了平衡Tensor ALU與GEMM內核的資源利用,在多個周期內通過矢量向量操作執行張量-張量操作。
 加載和存儲模塊
 加載和存儲模塊以從DRAM到SRAM的跨越式訪問模式執行2D DMA加載。加載模塊可以動態插入2D填充,這在阻止2D卷積時很有用。這意味著VTA可以平鋪2D卷積輸入,無需支付將數據重新布置在DRAM中以在輸入和權重平鋪塊周圍插入空間填充的開銷。
總結
 
                            
                        - 上一篇: TensorFlow Frontend前
- 下一篇: 如何使用TVM Pass Relay
