Paper Reading | 讓深度學習更高效運行的兩個視角

2018,Momenta Paper Reading第二季強勢回歸!

我們致力於打造一個「無人駕駛學術產業前沿知識」的分享溝通平台,讓你輕鬆讀懂AI

作為頂尖的自動駕駛公司,Momenta一直專註於打造自動駕駛大腦,這一過程離不開深度學習的高效運行。

2月1日晚,Momenta聯合量子位吃瓜社欄目帶來了Paper Reading第二季首期分享:讓深度學習更高效運行的兩個視角。

本期主講人王晉瑋是特徵點定位專家、深度學習模型加速專家、Momenta合伙人。他從優化計算量和訪存量兩個角度出發,提出縮短計算時間,加速完成推理任務的優化方法。如果你對深度學習模型加速感興趣,王晉瑋團隊正在全球搜尋技術人才,拉到文末可了解熱招崗位。

本文由量子位編輯屈鑫整理成文。

主講人

王晉瑋

特徵點定位專家,深度學習模型加速專家,Momenta合伙人

王晉瑋的主要研究領域是特徵點定位和深度學習模型加速,曾在Sensetime、Deephi從事深度學習演算法研究工作,2016年加入Momenta,目前任高級研究員,負責搭建高性能深度學習部署平台,擁有豐富的深度學習模型加速經驗。

基本背景

首先提一下需要了解的背景。

第一個是Roofline Model。這個Model是指計算機上的一個應用,它佔用了兩類最主要的資源:算術邏輯單元的計算資源,存儲器的帶寬資源。這裡的計算資源以FLOPS來表示;帶寬資源以byte/s表示。

Roofline model是說什麼呢?橫軸是Operational Intensity,就是計算的密度,單位是FLOPS/byte;縱軸是performance,也就是性能,單位是FLOPS。

圖中有一條折線,這個折線開始的時候是隨著計算密度的增加而增加,最終會穩定在一個固定的performance上。這個意思是:當這個應用程序的計算密度大於一定值之後,將會變成一個受算術邏輯單元的計算量所限制的程序;而這個計算密度如果小於一定值,將會變成一個受存儲器帶寬所限制的程序。

這裡折線的拐點非常重要。這個拐點跟硬體很相關,它實際上表示的是硬體的理論計算能力和它的內存帶寬之間的一個比值。

舉兩個具體的例子,第一個是矩陣乘矩陣,矩陣C等於A乘B,而A跟B分別是一千乘一千的矩陣。假設存儲和計算都是用float 32位來表示,這樣一個計算將會做1000乘1000乘1000的浮點乘加,也就是2G FLOPS的運算。我們要讀取A和B,然後計算出來C,把它寫回去,最少的存儲器訪問就是三個矩陣的大小,也就是12個MB。

另外一個是矩陣乘向量,也就是矩陣A乘向量B,等於向量C,這時候維度還是1000的情況下,它的計算量就是1000乘1000的浮點乘加,也就是2M。而存儲器訪問的話最少大約是1000乘於1000個浮點數,也就是4MB。

可以明顯地看到上面乘矩陣的操作,它的計算量是2G,訪存量是12M,那麼它的這個計算量除以訪存量,也就是剛剛提到的計算密度,大概是200左右。下面這個矩陣和向量中,它的計算量是2M,訪存量是4M,那它的計算量除以訪存量大約就只有0.5,顯然這兩個就是非常不同的程序。

上面矩陣乘矩陣,是一個典型的受計算量約束的程序;而下面矩陣乘向量則是一個典型的受存儲器帶寬所約束的程序。

另外一個要介紹的背景是,計算機的存儲器體系結構、存儲器層次結構。

計算機中CPU的速度非常快,存儲器通常跟不上它,所以為了讓CPU能穩定地工作,存儲器被劃分為多個層級,最上層是最快,同時也最貴,也是容量最小的寄存器,它緊緊圍繞在這個算術邏輯單元附近,可以被這個計算指令直接使用。

往下一層是緩存,它的容量比計算器大很多,但是速度大概慢一個量級,延時大概高一個量級。再往下是主存,也就是通常所說的內存,它一般是在處理器的晶元之外,容量就非常大,延遲可能會比這個CPU的主頻要低100倍,甚至更多。我們主要關註上面三個:寄存器、緩存和主存。

列舉一些典型的可以用深度學習部署的一些硬體:英偉達的Tesla V100,是目前最強的計算卡,它可以提供120T的FLOPS的計算性能,基於TensorCore。提供HBM2作為主存,帶寬是900GB/s,同時還有20MB的片上共享存儲和16MB的片上緩存,一共36MB的片上存儲。

中間是英偉達的Xavier,是最近推出的SoC,它提供大概30T的計算性能,同時使用LPDDR4作為主存,帶寬大概是137GB/s。

右邊是一個典型的嵌入式設備:樹莓派3,它採用普通的一款ARM晶元,運行在1.2G Hz,峰值計算能力是38.4G FLOPS。同時使用LPDDR2作為它的內存,帶寬大約3.6GB/s,同時它還會有512KB的片上緩存。

對比這三個硬體,不管是計算性能,還是內存帶寬,片上的緩存,它們都非常的懸殊。

兩個視角:計算量和訪存量

我們通過計算量和訪存量來評判深度學習模型的性能。

計算量是我們非常熟知的一個性能,但通常對訪存量關注比較少。事實上根據我們前面提到的,訪存量的值會非常嚴重地影響模型的性能。為了簡便計算,我們這裡忽略緩存的存在,因為一般需要加速的產品都是嵌入式設備,這種設備上它的片上存儲是相對較少的。

假定以Fp32作為計算和存儲的單元,我們可以計算出模型的計算量和訪存量,以及計算密度。這裡計算訪存量的規則是:對每一個Operator或者是layer,計算所有輸入的Tensor、所有參數的Tensor,還有輸出Tensor的總大小作為訪存量。

表格里列出來的訪存量,實際上是經過了一些優化之後的結果,並不是原始模型中的結果。後面會提到一些基本的優化操作。

先看這個表格中第四列的數值,也就是計算密度。一些大模型,比如VGG 16、ResNet,或者是Inception,他們的計算密度都非常大,都在30或者40或者50這個量級。事實上VGG 16的訪存量中絕大部分是被最後巨大的全連接層佔據了。如果去掉那個全連接,或者是像ResNet中,先做ROI pooling,然後再接全連接,這種形式的訪存量會降到170M到180M,它的計算密度是非常大的。

而通常我們說的小模型,計算量雖然非常小,但它訪存量卻並沒有顯著地小於大模型,同時計算密度會顯著變小。例如ShuffleNet 0.5x,它使用3個group形式時計算密度只有3.61,如果是8個group形式,也就是計算被拆得更散的情況下,它的計算密度只有2.91。

這就是說,小模型部署在這些硬體上,通常都是被存儲帶寬所限制住了,而不是被計算量所限制住。

後面以這兩個視角作為出發,來看一些目前已經非常常見或者是用得非常好的一些深度學習模型的加速的手段。

優化1:Bottleneck

第一個方法是bottleneck。bottleneck這樣的結構,在GoogLeNet系列的論文,以及ResNet論文裡面都有提到,這裡截取ResNet論文中的一張圖作為例子。在ResNet里,我們只採用一個3×3的卷積,就會有256個channel的輸入,然後輸出256 channel,再跟自己做一個加和,過一個relu。

這樣就能計算:假設feature map是56×56的情況下,左邊的結構中,計算量大約是3.7G,訪存量大約是8.8M。如果採用bottleneck結構,也就是把它拆成一個1×1的卷積,把channel數降低,再做3×3的卷積,然後通過1×1的卷積把channel數恢復回來。也就是右邊這張圖,它對應的計算量是大約0.43G,對應的訪存量大約是9.9M。

所以經過這樣一個操作之後,計算量被顯著降低,將近有十倍之多。同時訪存量卻沒有什麼降低,反而上升了一些。

總結一下bottleneck這樣的結構:大幅減少計算量,同時訪存量不變,或者是小幅上升,所以計算密度會大幅下降。

從計算量的角度來看,它不失為一個優化模型的非常好的手段。

優化2:Depthwise卷積

另外一個方法是depthwise卷積。

例如把一個3×3,若干個channel輸出的卷積拆成兩部分。第一部分是depthwise,它只做空間上的關聯,在每一個channel上獨立地進行卷積,就只有一個channel。第二部分是pointwise,只在不同channel之間做關聯。經過這樣的拆解後,同樣會使計算量大幅減小,訪存量小幅上升,所以計算密度大幅下降

這兩個結構為什麼都會導致計算密度的下降呢?

計算密度從感性上來理解,就是載入一個數字,我們可以用它來進行多少次運算。舉個簡單的例子,假設採用3×3的卷積核,那當我們載入進輸入的一個點,在一個輸出的channel上它會進行9次運算(卷積核3×3)。但對於一個1×1的卷積來說,將9次運算減少到了1次。而對depthwise卷積來說,我們相當於將單次卷積的輸出channel數減少到1。

再回過頭來看剛剛這張表格。

剛才我們提到VGG16訪存量主要是被最後的全連接所佔據了,如果去掉它的話,我們把計算密度認為是160左右。由此可以看到VGG16的計算密度是顯著高於其它幾個,因為它的結構非常簡單,它的內部都是3×3的卷積,屬於這個計算密度最高的一種結構。

其它的一些模型,比如ResNet 152或者ResNet 50,因為它或多或少地引入了bottleneck,導致計算密度會有一些下降。ResNet18的裡面沒有bottleneck結構,或者bottleneck結構也是由3×3的卷積來完成的,所以它的計算密度相對比較高。Mobilenet和Shufflenet等等,它們裡面根本就沒有大規模3×3卷積,只有depthwise的3×3卷積和1×1卷積。

其實前面兩種方法也都是非常優秀的方法,為什麼說他們降低了計算密度,但也是很實用的方法?回到剛才我們提到的三個典型的硬體:Tesla V100、Xavier和樹莓派。

在樹莓派這樣一個典型的嵌入式神經網路的場景下,需要小模型,同時它內存帶寬資源又相對比較富裕(回想Roofline Model的折線拐點位置),相比於GPU而言,在這樣的設備上還是有很大的價值。這樣的操作其實本質上是用存儲器的帶寬資源去換取一些計算資源來提升模型性能。

優化3:FFT / Winograd的卷積演算法

然後我們來提下面一個優化:FFT / Winograd的卷積演算法。這種方法跟前兩種相比區別在哪?它是指通過某種線性變換將feature map和卷積核變換到另外一個域,空間域下的卷積在這個域下變為逐點相乘,再通過另一個線性變換將結果變換到空間域。FFT卷積採用傅里葉變換處理feature map和卷積核,傅里葉逆變換處理結果;Winograd卷積使用了其他的線性變換。

具體而言FFT將空間意義上的實數變換到頻域上的複數,最後在複數上做逐點相乘,然後再把這個頻率的複數變化為這個空間域的實數。

Winograd則是一直在實數域上進行變換。事實上由於FFT需要複數乘法,如果沒有特殊指令支持的話需要用實數乘法來模擬,實數的浮點計算量可能下降的不多。因此FFT也沒有Winograd實用。

從計算量和訪存量的角度來分析Winograd/FFT變換的卷積演算法。它在實際上會大幅減少計算量,計算量減少的值是由這個圖像分塊大小決定的。同時對訪存量的影響和cache容量有關,如果實現得好,不會大幅提高訪存量,但會有一定程度的精度損失。

FFT/Winograd的變換,它本質上是一個由精度換取速度的方法。

總結一下FFT和Winograd變化,它實際上是可以實現極高的一個加速比,舉個例子,Winograd變換對於3×3卷積,最高可以實現9倍的加速比,但精度損失嚴重。當然我們實際上不會用那麼大,可能會用到6倍,那麼這時候精度損失還是可以接受的。

其他方法

簡單列舉一些其他方法。

第一個是稀疏化。它的缺點是工程實現較難,需要fine tune,不易於解耦模型研究和部署加速。另外稀疏矩陣運算的實際效率往往也不高。

那麼在工程上如何解決呢?通過結構化稀疏,通過一些方式保證稀疏矩陣中0以一定的規律出現,例如同一列、同一行、某些塊等,盡量提高稀疏矩陣運算的效果。

另外一個是低精度運算。低精度運算具體而言就是使用更低位寬,實現成倍降低的訪存量,同時使部分處理器上性能成倍提高。

舉個例子,fp16相比fp32,一半的位寬,幾乎沒有精度損失;Int8相比fp32,1/4的位寬,採用正確的方式進行量化後也幾乎沒有精度損失。缺點是目前支持的硬體不多,新一代的ARM處理器和部分英偉達的GPU支持。

Graph層面

前面都在說operator層面。

現在從operator層面往上走,來到graph的層面,引入計算圖的概念:computation graph。具體而言,計算圖是什麼?就是以某種語言來表述一個計算的過程,比如說上面這個C等於A乘B加二,會對應一個計算圖,這個計算圖明確給出了計算過程中的數據流向和依賴關係。

首先介紹一下NNVM Compiler,它是由mxnet的作者們提出的一個神經網路的一個中間表示。它包括兩部分:NNVM和TVM。NNVM主要做的是graph層面的表示,它通過表示節點和節點之間的連接關係,把Operator連接成一個computation graph,再通過TVM來優化每一個節點,來生成最終可執行的代碼。

另外一個非常有價值的工作,是英偉達推出的TensorRT,TensorRT是專門針對英偉達GPU的神經網路部署工具。在TensorRT中能做非常多,並且能夠進行非常好的Operator和Tensor的融合操作。非常感謝NNVM和TensorRT這兩個已有的成果,為Momenta做後面的研發提供了非常非常寶貴的借鑒參考價值。

再往下看,從Operator層面向上看到graph這個層面,我們能做什麼事情。相比於前面的bottleneck、depthwise卷積這些優化手段,Operator層面以上,也就是graph能做的事情就比較有意思。

舉個例子,最左邊這個藍色的連接關係是caffe中非常常見的計算過程:計算一個卷積,並且在後面加一個ReLU的操作。經過分析可以發現,加Bias和ReLU這兩個操作都是逐點進行的。我們完全可以修改這個矩陣乘的代碼,在矩陣乘算完一個結果之後,順便加bias,並做一個ReLU。可以把矩陣乘和Bias和ReLU三個操作合併為一個操作。

還有很多可以進行的融合操作,BN或scale和conv的融合,shuffle channel和depthwise conv的融合、Momenta提出的SENet中Excitation和conv的融合、通過合理分配存儲地址消除concat和slice等。

為什麼我們要做這個Operator的融合?因為能夠減少從低層memory向高層memory的搬運。例如relu向gemm的融合,如果不融合,計算gemm和relu時都至少需要將數據從cache搬入register,再搬回cache。而融合的話,只需要搬入register再搬回cache一次即可。搬運數據的指令和延遲都是可以節約的開銷。

Operator的粒度

另外一個想討論的概念是Operator的粒度。粒度是一個非常值得討論的問題,首先定義下什麼是粒度:一個operator完成多複雜的任務。

舉個例子,左邊那個藍色的是一個粗粒度的計算圖,先做了一個包含channel在裡面的一個Pooling,然後再做一個channel在裡面的Depthwise的卷積。藍色是一個粗粒度的op的計算圖,如果採用細粒度的op,它會被展開成右圖的樣子。展開成右圖這個樣子以後,會有很多優勢。

首先這個計算圖會變得更複雜,同時每一個operator的粒度會更細,這也意味著你暴露給了這個計算圖的優化器更多的細節,這樣的這個計算圖優化器就會有更多的優化餘地。例如右邊,通過合理選擇op執行順序,可以盡量避免pooling和depthwise conv之間對主存的訪問,降低內存帶寬壓力。

上一個例子不那麼合適,但是比較直觀。舉另外一個例子:我們在一個卷積後面接一個卷積,這時候仍然可以把這個卷積的粒度拆的更細,但是這次我們不在channel這個維度拆細,而在空間這個維度上把它拆細。

我們可以通過把卷積2的輸出在空間上分片。比方說卷積2最後輸出了一個16乘16的feature map,然後我們把它拆成四個8乘8的feature map作為輸出。

通過這樣的一個拆分,可以把粗粒度的卷積變成右邊這樣一個形式,它們有多個,就是空間上的這樣一個分塊,每一個分塊計算最終的結果,跟前面的例子是一樣的。Conv1Tile1和Conv2Tile1,Conv1Tile2和Conv2Tile2,它們的數據交換是遠小於前面Conv1和Conv2之間的數據交換。畢竟空間維度更小,也就是緩存可能就可以把它放進去了,就意味著少了數據交換。

我們付出的代價是說Conv1的tile可能有重疊,導致額外的計算。同時這種情況下細粒度可能帶來了計算效率的下降。但同樣,op和op之間往來的數據變少,可以盡量完全使用cache而避免訪問主存,降低內存帶寬壓力。

所以對於Operator粒度來說,並不是說粒度粗就一定好,或者粒度細就一定好,也是從計算量和訪存量兩個角度,同時在影響深度學習的性能。

另外一個值得提的概念是輕量化的運行時。不管是TensorFlow或者是NNVM,它們都有輕量化的一個形式。拿NNVM來舉例,可以看到前面提到的優化的過程,比如說我拿到了一個粗粒度的表示,那我決定要不要把它拆分成細粒度?可以通過一些啟發式的方法,拆或不拆都測一遍,看看哪個性能更高。

優化的過程其實是一個蠻費勁的過程,它會比較複雜,但事實上如果我們拿到的是一個靜態的計算圖,graph以及operator的優化過程和輸入數據無關,所以完全可以將優化過程從模型的執行器(運行時)中抽離出來,作為一個獨立的部分。經過複雜的優化過程,計算圖上每個節點跟原來可能都不一樣,但是它的執行性能確實比原來更好。

那輕量化的運行時是有什麼好處?運行時僅需提供用到的operator,不需要負責優化工作,因此可以做到非常輕量,同時優化後可能變得複雜的計算圖也不會引入過多的開銷。

最後列舉一下,Momenta通過前面提到的這些方法,做出來的一些加速的效果,這裡用樹莓派3作為測試,配置cortex A53,1.2G Hz,所有的測試都是在一個CPU核心上完成的。

總結

總結一下前面提到的深度學習模型性能運算優化。

我們提供兩個視角:除了大家普遍採用的計算量的角度以外,另一個是訪存量的視角。可以通過網路結構設計優化計算量,比如說bottleneck、depthwise的卷積。也可以通過Winograd、稀疏化、低精度運算等優化計算量。但是其中的低精度運算並不是優化了計算量,而是優化了設備能提供的計算量的上限,同時也可以通過低精度運算、計算圖優化等手段來優化訪存量。

最後一點是可以通過輕量化運行時來避免一些額外的開銷,比如在部署的時候,避免編譯優化這樣一個比較複雜的模型的開銷。

Q&A

訪存量如何進行估算?

前面提到訪存量的估算是要計算每一個layer,每個layer訪存量是它的輸入的數量,輸出的數量和權重的數量總和。估算的話,可能跟真實的訪存量差異較大,確實是這樣。

我們在這裡提到的計算過程是忽略了緩存的存在的,這樣做的原因是,在通常我們需要比較嚴格的優化深度學習性能的一些平台,比如嵌入式平台,比如前面提到的樹莓派,它一共有512K的片上的存儲,同時四個CPU核心,如果只用一個CPU核心的話,那意味著只能佔用128K的緩存,如果多佔了那麼其他核心就會遭殃了,那麼128K緩存可以存什麼呢?一個256 channel的一個feature map,然後浮點數存儲,那一共就只能存128個點,這其實非常非常少的。

所以說通常在嵌入式這個場景下,大多數情況下它片上緩存是不夠用。所以我們這裡採用了一個忽略掉片上緩存的一個方法,因為片上訪存一旦不夠用,那意味著就是你下次讀取的時候上一次已經被擠出去了,所以實際上是一直在讀取主存的過程。另外忽略這片上緩存也有這樣一個目的:估算方便。我們其實提到了很多利用片上緩存來進行訪存量優化的過程。到那個時候,其實估算就不能再這樣進行了。

有一些開源庫,我們還需要關注這些優化嗎?

事實上是這樣,比方說我前面提到的這些優化,比方說這個計算圖級別優化,其實NNVM做了一些,但是他並沒有做到極致,還有很多我們認為可優化的地方,它其實裡面並沒有提供。當然NNVM可能只是為了建立一個這樣的平台,讓大家去貢獻代碼,做這樣一個開源社區的概念。當然就是說如果要追求極致性能的話,有時候還是需要關注這樣的一個優化的手段。

為什麼還要自己做優化的原因就是,開源的框架和工具提供了很好的概念和借鑒意義,概念其實是相通的,但是我們自己的實現通常會更好一些。畢竟在業界追求極致的性能有時候還是有必要的。

Operator融合的方法可以在caffe或者TensorFlow的框架層面做嗎?

caffe的話我就不推薦了,如果要做operator融合,是需要這個框架本身就是以計算圖為基礎搭建出來的,而caffe並不是。caffe把operator按執行順序排成數組,相當於是計算圖的拓撲排序後的結果。在這種框架上做operator融合,是非常費勁的,需要恢復成計算圖來分析數據依賴關係。

TensorFlow本身它是一個計算計算圖的框架,當然可以做這樣的事情,但是TensorFlow本身也提出了一個底層:XLA。這個XLA它其實也在做類似的事情。其實也就是說TensorFlow本身其實是提供了一些類似的操作。

PPT下載鏈接:pan.baidu.com/s/1jJhZDv 密碼:riye

分享視頻回放鏈接:v.douyu.com/show/yjwzOv

------------------------------------------------

知乎機構號:Momenta,打造自動駕駛大腦。

基於深度學習的環境感知、高精度地圖、駕駛決策技術,讓無人駕駛成為可能。

Momentawww.zhihu.com圖標

知乎專欄:Paper Reading,集聚自動駕駛知名大咖的前沿知識分享平台,歡迎申請加入或直接投稿。

Paper Readingzhuanlan.zhihu.com圖標


推薦閱讀:

從神經學角度看邊緣計算、霧計算與互聯網雲腦之間的關係
意識的定義:以抽象的方式認知事物規律,並能夠加以運用。
埃森哲與Infosys重磅報告:AI落地成果已出,仍無所作為將被踢出局
自動駕駛硬體配置

TAG:人工智慧 | 深度學習DeepLearning | 機器學習 |