在前不久的 Baidu Create 2019 百度 AI 開發者大會上,Apollo 發布了業內首創的 AVP 專用車載計算平臺——百度 AVP 專用量產計算單元 ACU-Advanced。
本篇文章,我們將從與自動駕駛的關系、加速中遇到的挑戰、量化計算、節約資源和帶寬五個方面,介紹 ACU-Advanced 的核心高性能芯片 FPGA 的相關技術。
這是一篇“硬核”的技術文章。正是這些后臺的“硬核”技術,成就了令人炫目的自動駕駛。本文中介紹的相關技術已經落實在 Valet Parking 產品中的量產 ACU 硬件上。
自動駕駛與 FPGA
人工智能技術是自動駕駛的基礎,算法、算力和數據是其三大要素。本文探討的就是其中的“算力”。算力的高低,不僅直接影響了行駛速度的高低,還決定了有多大的信息冗余用來保障駕駛的安全。
算力最直觀地體現在硬件上,而汽車對自動駕駛的控制器有特殊的要求。
除了對一般硬件的成本、體積重量、功耗的要求外,還要求:
提供足夠的算力,保證行駛速度和信息冗余。
滿足嚴苛的車規標準,比如超寬的溫度范圍,-40℃ – 85℃。
綜合來看 FPGA 是適合自動駕駛高速計算的技術,它具有以下的突出優點:
技術可靠。FPGA 在汽車行業早已被廣泛使用,也經受了軍工、航天、通信、醫療等需要高可靠性行業的考驗。相比而言,GPU 不具有這個特點,而為自動駕駛新開發的 ASIC 尚需時間檢驗。
靈活,有利于算法迭代。FPGA 具有可編程的特點,尤其適合自動駕駛這種新興的、功能需求并不完全確定的行業。如果使用 ASIC,則算法的自由度就被束縛,不利于算法的演進。ASIC 開發周期需要幾年,如果采用 ASIC 加速,算法理念被鎖定在幾年前。比如,如果 ASIC 被設計為只能為 CNN 加速,那基于規則的立體視覺技術將無法實現。即便 ASIC 設計中考慮了雙目立體視覺的加速,那基于運動的立體視覺技術(Structure From Motion, SFM)就無法實現。這些繁復的、變化的需求是新興產業的標志,但也使 ASIC 很難完全承接。
有成品可用。已經有成熟的 FPGA 產品,提供不同的算力,可以直接選擇。新的 ASIC 開發延期,甚至失敗,并不是小概率事件。筆者曾經在通信設備行業工作10年,見證了移動通信技術 2G、3G、4G、5G 的變遷。即便通信行業標準清晰且超前,為排除技術不確定性,每次技術變遷時,總是先推出基于 FPGA 的量產產品,確保可以占領市場先機。
盡管 FPGA 有可靠、靈活、有成熟成品的優點,但 FPGA 的開發有很強的專業性,最終實現的效果與具體的設計很相關。
FPGA 加速遇到的挑戰
實踐中遇到的挑戰是,多種多樣的加速需求和有限的硬件資源的矛盾。
需求的來源既包括深度學習前向推測、也包括基于規則的算法。
硬件資源受限包括了:FPGA 資源受限和內存帶寬受限。
FPGA 資源的有限性體現:
峰值算力受限:有限的 FPGA 資源限制了計算并行度的提高,這約束了峰值算力。
支持的算子種類受限:有限的 FPGA 資源只能容納有限個算子。
內存帶寬受限體現在:
內存數據傳輸在計算總時間中占據了不可忽略的時間。
極端情況下,對某些算子提高并行度后,計算時間不減。
為應對這些挑戰,我們在實踐中提取了一些有益的經驗,總結出來與大家共享。
量化計算
算法工程師采用浮點數 float32 對模型進行訓練,產出的模型參數也是浮點型的。然而在我們使用的 FPGA 中,沒有專用的浮點計算單元,要實現浮點數計算,代價很大,不可行。使用 int8 計算來逼近浮點數計算,也即實現量化計算,這是需要解決的第一個問題。
量化計算原理
以矩陣 C = A*B 為例,假設 A、B 元素為 float32 類型,采用愛因斯坦標記法:
符號表示四舍五入,兩個把矩陣A和B的元素線性映射到區間[-127, 127],在此區間完成乘法和加法。最后一個乘法把整型結果還原成 float32。
假設 i = j = k =100:
在量化前,需要完成1000000次 float32 的乘法。
量化成 int8 后,需要完成1000000次 int8 的乘法,和30000次量化、反量化乘法。
由于量化和反量化占的比重很低,量化的收益就等于 int8 取代 float32 乘法的收益,這是非常顯著的。
未知量化尺度:動態量化
如果上面式子中,量化尺度 max|A|, max|B|,在計算前是未知的,每次計算矩陣乘法前,就需要逐個查找 A 和 B 的元素,找出量化尺度。
這種方法的好處是,每次計算既能充分利用 int8 數據的表征能力(127總能被使用到),不存在數據飽和的情況(所有元素都被線性映射),保證單次計算的精度最高??梢灾苯咏邮芨↑c訓練的模型,維持準召率。Resnet50 測50000張圖片,Top1 和 Top5 準確率下降1%。在 Valet Parking 產品用到的多個網絡中,沒有觀察到準召率下降。
缺點是,FPGA 計算有截斷誤差,經過多次累計,數值計算誤差最大平均可以達到10%。對于一些訓練不完全成功的模型(只在有限評測集上效果比較好),準召率下降明顯,結果不可控。
已知量化尺度:靜態量化
如果上面的式子變成
經過線下統計,量化尺度被固化為 scaleA 和 scaleB, 表示四舍五入,并且限制在[-127, 127]之內。
這種方法的好處是
節約了 FPGA 資源。
可以很方便地采用跟量化推測一致的訓練方法,推測和訓練計算數值誤差很小,準召率可控。
缺點是,要求模型訓練采用一致的量化方法。否則,計算誤差很大,不可接受。
節約 FPGA 資源
共享 DMA 模塊
FPGA 片上存儲非常受限,對于絕大多數的算子,不可能將輸入或者輸出完整緩存到片上內存中。而是從內存中一旦讀取足夠的數據,就開始計算。一旦計算到足夠多,就立即把結果寫到內存。和內存數據的流式交互是個公共的需求,我們開發了能兼顧所有算子的 DMA 的接口。
只有對于單次計算耗時很長、或調用非常頻繁的獨立任務算子,我們才為其定制單獨 DMA 的模塊,取得的收益是,這個算子可以通過多線程調度和其它 FPGA 算子并行計算。這是綜合收益和代價后,做出的以資源換時間的折衷。
采用 SuperTile 結構
Int8 的計算,可以使用 DSP 或其它邏輯資源來完成。邏輯資源有更多的用途,所以我們占用 DSP 來完成 int8 的乘累加計算。FPGA 內部的 DSP48E2 可以接受 27bit 的乘數??梢园褍蓚€ int8 的乘數排列在高 8bit 和低 8bit,進行一次乘法后,再兩個乘積完整的分離出來。這樣,就實現了單個 DSP 一個時鐘周期完成了兩個乘法,達到了算力倍增的效果。
算子資源復用
通過觀察和抽象,將 CNN 主要的算子抽象成3類:
指數類算子
單通道算子
多通道算子
實現了每一類共享計算資源,大大節約了 FPGA 資源的占用,為提高峰值算力、和支持更多的算子提供了有利條件。
經過觀察
而對于兩通道的 softmax,它把兩個數 a, b 映射成兩個概率,且 Pa + Pb = 1,計算法則是:
指數計算在 FPGA 中是比較消耗資源的,通過把 tanh 和 softmax 化成 sigmoid 的形式,我們就實現了一份指數運算資源,支持3種算子。
Average pooling 可以視為固定卷積核的 depthwise conv。
可以構造額外的卷積核,在上層 SDK 把 average pooling 封裝成 depthwise conv 直接計算,這樣 FPGA 無需做任何兼容設計,節約 FPGA 資源。
也可以在 RTL 代碼中完成轉換,這樣不需要傳遞卷積核參數,節約內存帶寬。
Elementwise add 的計算形式是兩個輸入、一個輸出,而 depthwise conv 的計算形式是一個輸入、一個輸出。二者計算資源的復用并不顯然。我們操作兩個輸入向 FPGA 加載的順序,加載數據的同時完成了兩個輸入特征圖的按行交織,將兩個輸入交織成一個輸入。然后在 RTL 中構造一個[1, 1] T 的卷積核,stride 設置為[1, 2],變成 depthwise conv 的計算形式,利用 depthwise 的計算資源完成計算。
我們在設計 elementwise add 的時候,抽象度比較高,超出了原始定義的 A + B,擴展成 mA + nB。 其中 m、n 是 SDK 可以自由配置的參數,當m = n = 1,回歸到傳統的 elementwise add。而取 m = 1,n = -1 時,完成的是 elementwise sub。在 FPGA 無感的情況下,實現了 elementwise add 和 elementwise sub 計算資源的復用。
綜上, depthwise convolution, average pooling, elementwise add , elementwise sub 這四種單通道的算子計算資源是復用的。
多通道算子資源的復用,只介紹最關鍵的乘累加部分。
conv 實現的是3維輸入圖像(H x W x C)和4維卷積核(N x K1 x K2 x C)的乘加操作。full connection 實現的1維輸入數組(長度是C)和2維權重(N x C)的乘加操作。將 full connection 輸入數據擴維,輸入數組擴展成 H x W x C, 輸出擴展成 N x K1 x K2 x C, 其中 H = W = K1 = K2 = 1。 這樣 full connection 就被 SDK 封裝成了 conv,FPGA 計算時無感。
Deconv 和 conv 是網絡中計算量最大的兩個算子,計算資源復用收益很大。但它們計算形式上差別很大,直接復用計算資源很困難。我們在理論上進行突破,實現了通用的資源復用的方法。簡言之,SDK 要對 conv 的計算參數進行擴充以兼容 deconv,在計算 deconv 時,需要對卷積核進行分拆、重排,偽裝成 conv。FPGA 計算完畢后,增加少量邏輯對結果進行修飾。
總結一下,我們對 CNN 常用的十種算子抽象,只花費三個算子的資源。
異構計算
ARM 計算:有些算子,比如多通道的 softmax、concat、split 等,出現頻率很低,數據量不大,對整體幀率影響很小,還有些算子比如 PSRoiPooling、計算區域不確定、數據不能保證對齊,非常不適合 FPGA 加速。把這兩類算子放在 ARM 上實現。在 ARM 上對計算影響最大的單個因素是緩存命中率。通過數據重排、改變遍歷順序等,提高緩存命中率,可以把表觀 ARM 算力提高幾十倍。
NEON 加速:采用 NEON 指令可以對多通道的 Softmax 算子有效加速,加速比雖然不及 FPGA,但相對于直接采用未優化的 C++ 的代碼在 ARM 上執行,效果可以提升數倍。其它對齊的計算,大多可以通過 NEON 處理器加速數倍。
MaliGPU:我們目前使用 Xilinx ZU 系列的 FPGA,自帶 MaliGPU 400,原本被設計用來顯示時渲染,并不支持 CUDA、OpenCL 等常用庫。經過特殊的驅動方式,我們做到可以利用它實現一些受限的逐像素算子。
我們實際計算使用的硬件資源包括了 FPGA、MaliGPU、ARM 主處理器、ARM Neon 協處理器4種。通過 ARM(主處理其和協處理器)和 MaliGPU 實現對部分算子進行承接,有效緩解了 FPGA 的資源壓力。
采用靜態量化
而采用動態量化,搜索量化尺度和進行量化,需要分散在相鄰的兩個算子中實現。為了保證精度,中間結果需要以半浮點(float16)形式表示。這帶來兩個問題:
CPU 并不能直接對 FP16 的數據進行轉換或計算,所以需要 FPGA 提供額外的算子,提供快速的 float32 / int8 和 float16 轉換。這些額外的算子,是 CNN 本身不需要的,這構成了浪費。
Float16 需要的緩存比 int8 大了一倍。浪費了 FPGA 的存儲資源。
而靜態量化,離線提供了固定的量化參數,中間計算結果以量化后的 int8 形式來表示。以上的浪費都得以避免。盡管靜態量化對模型訓練做額外的要求,我們最終決定切換到靜態量化。
節約內存帶寬
多算子融合
通過 SDK 將各種參數進行變換和合并,單個 conv 算子可以完成最多支持4個算子的組合 conv + batchnorm + scale + relu。
我們還可以將指數型算子(sigmoid / tanh /兩通道 softmax)融合到上面4個算子之后,形成融合5個基本算子的單一融合算子。這依賴于自主開發的 SDK,和 FPGA 設計的算子邏輯。
相對于每計算一個算子就把結果回吐給 DDR,這種算子融合大大減少了對內存的讀寫。有效提高了處理幀率。
靜態量化
動態量化中間結果以 float16 表示,而靜態量化可以以 int8 形式表示。靜態量化相對于動態量化,內存的吞吐量降低,幀率有明顯的提高。下圖是保持算力不變,僅僅把中間結果從 float16 變成 int8 后,處理幀率的提高幅度。
靜態量化降低了內存吞吐,這也是我們放棄動態量化易用性的一個原因。
任務的幀率是峰值算力、各算子算力、支持的算子種類三個因素復合作用的結果。以上技術已經用到了 ACU 硬件中,把百度 Valet Parking 產品的幀率在數量級上進行了提高。
接下來,我們會陸續發布更多這樣的“硬核”技術文章,讓更多開發者們更加細致地了解 Apollo 自動駕駛背后的技術。
評論
查看更多