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