并行計算的量化模型及其在深度學習引擎里的應用
天下武功,唯快不破。怎么更快地訓練深度學習模型是業界一直關注的焦點,業界玩家或開發專用硬件,或開發軟件框架,各顯神通。
當然,這些定律在計算機體系結構的教材和文獻中都可看到,譬如這本《計算機體系結構:量化研究方法 ( Computer Architecture: a Quantative Approach )》,但本文的價值在于有針對性地挑選最根本的幾條定律,并結合深度學習引擎來理解。
1 關于計算量的假定
在研究并行計算的定量模型之前,我們先做一些設定。對于一個具體的深度學習模型訓練任務,假設總的計算量V固定不變,那可以粗略認為只要完成V這個量級的計算,深度學習模型就完成訓練。
GitHub這個頁面( https://github.com/albanie/convnet-burden )羅列了常見CNN模型處理一張圖片所需的計算量,需要注意的是,本頁面列出的是前向階段的計算量,在訓練階段還需要后向階段的計算,通常后向階段的計算量是大于前向計算量的。這篇論文( https://openreview.net/pdf?id=Bygq-H9eg )對訓練階段處理一張圖片的計算量給出了一個直觀的可視化結果:
以ResNet-50為例,訓練階段處理一張224X224x3的圖片需要8G-Ops (約80億次計算),整個ImageNet數據集約有120萬張圖片,訓練過程需要對整個數據集合處理90遍(Epochs),粗略估計,訓練過程共需要(8*10^9) *(1.2*10^6)* 90 = 0.864*10^18次運算,那么ResNet-50訓練過程的總計算量大約是10億乘以10億次運算,我們可以簡單地認為,只要完成這些計算量就完成了模型運算。 深度學習計算引擎的目標是以最短的時間完成這個給定的計算量。
2 關于計算裝置的假定
本文僅限于下圖所示的以處理器為中心的計算裝置(Processor-centric computing),以內存為中心的計算(Processing in memory)裝置在業界有探索,但還不是主流。
上圖所示的計算裝置中Computing Unit可以是通用處理器如CPU, GPGPU, 也可以是專用芯片如TPU等。如果Computing Unit是通用芯片,通常程序和數據都存儲在Memory Unit,這也是現在最流行的馮諾依曼結構計算機。
如果Computing Unit是專用芯片,通常只有數據存儲在Memory Unit。Communication Unit負責把數據從Memory Unit搬運給Computing Unit,完成數據加載(load),Computing Unit拿到數據后負責完成計算(數據的形式轉換),再由Communication Unit把計算結果搬運到Memory Unit完成數據存儲(Store)。
Communication Unit的傳輸能力通常用訪存(Memory access)帶寬beta表示,即每秒鐘可以搬運的字節數,這通常和線纜數和信號的頻率相關。Computing Unit的計算能力通常用吞吐率pi表示,即每秒鐘可以完成的浮點計算次數(flops),這通常和計算單元上集成的邏輯運算器件個數及時鐘頻率有關。
深度學習引擎的目標是通過軟硬件協同設計使得該計算裝置處理數據的能力最強,即用最短的時間完成給定的計算量。
3 Roofline Model: 刻畫實際計算性能的數學模型
一個計算裝置執行一個任務時能達到的實際計算性能(每秒鐘完成的操作次數)不僅與訪存帶寬beta以及計算單元的理論峰值pi有關,還和當前任務本身的 運算強度 (Arithemetic intensity,或Operational intensity)。
任務的運算強度定義為每字節數據需要的浮點計算次數,即Flops per byte。通俗地理解,一個任務運算強度小,表示Computing Unit在Communication Unit搬運的一個字節上需要執行的運算次數少,為了讓Computing Unit在這種情況下處于忙碌狀態,Communication Unit就要頻繁搬運數據;
一個任務運算強度大,表示Computing Unit在Communication Unit搬運的一個字節上需要執行的運算次數多,Communication Unit不需要那么頻繁地搬運數據就能使Computing Unit處于忙碌狀態。
首先,實際計算性能不會超越計算單元的理論峰值pi。其次,假如訪存帶寬beta特別小,1秒鐘僅能把beta個字節從內存搬運到Computing Unit,令I表示當前計算任務中每個字節需要的操作次數,那么beta * I 表示1秒鐘內搬運過來的數據實際需要的操作次數,如果beta * I < pi,則Computing Unit就不會飽和,也表示Computing Unit的利用率低于100%。
Roofline model 就是一種根據訪存帶寬,計算單元峰值吞吐率,任務的運算強度三者關系來推斷實際計算性能的數學模型。由David Patterson團隊在2008年發表在Communications of ACM上( https://en.wikipedia.org/wiki/Roofline_model ),是一種簡潔優雅的可視化模型:
圖1:Roofline Model
圖1橫軸的自變量表示不同任務的運算強度,即每字節需要的浮點運算次數。縱軸的因變量表示實際可達的計算性能,即每秒鐘執行的浮點運算次數。上圖展示了兩個運算強度分別為I_1和I_2的任務能實際達到的計算性能,I_1的運算強度小于pi/beta,稱為訪存受限任務,實際計算性能beta * I_1低于理論峰值pi。
I_2的運算強度高于pi/beta,稱為計算受限型任務,實際計算性能達到理論峰值pi,訪存帶寬僅利用了pi/(I_2*beta)。圖中斜線的斜率為beta,斜線和理論峰值pi 水平線的交點稱為脊點(Ridge point),脊點的橫坐標是pi/beta,當任務的運算強度等于pi/beta時,Communication Unit和Computing Unit處于平衡狀態,哪一個都不會浪費。
回顧深度學習引擎的目標“ 以最短的時間完成給定的計算量 ”,就要最大化系統的實際可達的計算性能。為了實現這個目標,有幾種策略可用。
圖1中的I_2是計算受限型任務,可以通過 增加Computing Unit的并行度 并進而提高理論峰值來提高實際計算性能,譬如在Computing Unit上集成更多的運算邏輯單元(ALU)。具體到深度學習場景,就是增加GPU,從一個GPU增加到幾個GPU同時運算。
如圖2所示,當在Computing Unit內增加更多的并行度后,理論峰值高于beta * I_2,那么I_2的實際計算性能就更高,只需要更短的時間就可以。
圖2:提高Computing Unit的理論峰值來提高實際計算性能
圖1中的I_1是訪存受限型的任務,則可以通過 改善Communication Unit的傳輸帶寬 來提高實際計算性能,提高數據供應能力。如圖3所示,斜線的斜率表示Communication Unit的傳輸帶寬,當斜線的斜率增大時,I_1由訪存受限型任務變成計算受限型任務,實際計算性能得到提高。
圖3:提高Communication Unit的數據供應能力來提高實際計算性能
除了通過改善硬件的傳輸帶寬或者理論峰值來提高實際計算性能外,還可以通過 改善任務本身的運算強度 來提高實際計算性能。同樣的任務可以有多種不同的實現方式,不同實現方式的運算強度也存在差別。運算強度由I_1改造成超過pi/beta后,就變成計算受限型任務,實際計算性能達到pi,超過原來的beta*I_1。
在實際的深度學習引擎里,以上三種手段(提高并行度,改善傳輸帶寬,使用運算強度更好的算法實現)都會用到。
4 Amdahl's Law: 如何計算加速比?
圖2 的示例通過增加Computing Unit的并行度來提高實際計算性能,到底能把任務的執行時間縮短多少呢?這就是加速比問題,也就是效率提高了幾倍。
為了討論方便,(1)我們假設當前的任務是計算受限型,令I表示運算強度,即I*beta>pi。在把Computing Unit的運算單元增加s倍后,理論計算峰值是s * pi,假設該任務的運算強度I足夠高,使得在理論峰值提高s倍之后仍是計算受限型,即I*beta > s*pi;(2)假設沒有使用流水線,Communication Unit和Computing Unit總是順序執行(后文我們將專門討論流水線的影響)。讓我們來計算一下任務執行效率提高了幾倍。
在理論峰值是pi的初始情況下,1秒鐘Communication Unit搬運了beta字節的數據,Computing Unit需要(I*beta)/pi 秒來完成計算。即在1+(I*beta)/pi 秒時間內完成了I*beta的計算,那么單位時間內可以完成(I*beta) / (1 + (I*beta)/pi) 的計算,假設總計算量是V,則一共需要t1=V*(1+(I*beta)/pi)/(I*beta) 秒。
通過增加并行度把理論計算峰值提高s倍之后,Communication Unit搬運beta字節的數據仍需要1秒鐘,Computing Unit需要(I*beta)/(s*pi)秒來完成計算。假設總計算量是V,那么共需t2=V*(1+(I*beta)/(s*pi))/(I*beta)秒完成任務。
計算t1/t2即獲得加速比:1/(pi/(pi+I*beta)+(I*beta)/(s*(pi+I*beta))),很抱歉這個公式比較難看,讀者可以自己推導一下,比較簡單。
在理論峰值是pi時,搬運數據花了1秒,計算花了(I*beta)/pi 秒,那么計算時間占的比例是 (I*beta)/(pi + I*beta),我們令p表示這個比例,等于(I*beta)/(pi + I*beta)。
把p代入t1/t2的加速比,可以得到加速比為1/(1-p+p/s),這就是大名鼎鼎的Amdahl's law( https://en.wikipedia.org/wiki/Amdahl%27s_law )。其中p表示原始任務中可以被并行化部分的比例,s表示并行化的倍數,則1/(1-p+p/s)表示獲得的加速比。
讓我們用一個簡單的數字演算一下,假設Communication Unit搬運數據花了1秒鐘,Computing Unit需要用9秒鐘來計算,則p=0.9。假設我們增強Computing Unit的并行度,令其理論峰值提高3倍,即s=3,則Computing Unit只需要3秒鐘就可以完成計算,那么加速比是多少呢?利用Amdahl's law可以得知加速比是2.5倍,加速比2.5小于Computing Unit的并行度倍數3。
我們嘗到了增加Computing Unit并行度的甜頭,能不能通過進一步提高并行度s來獲得更好的加速比呢?可以。譬如令s=9,那么我們可以獲得5倍加速比,可以看到提高并行度的收益越來越小。
我們能通過無限提高s來提高加速比嗎?可以,不過越來越不劃算,試想令s趨于無窮大(即令Computing Unit理論峰值無限大),p/s就趨于0,那么加速比最大是1/(1-p)=10。
只要系統中存在不可并行的部分(Communication Unit),加速比不可能超過1/(1-p)。
實際情況可能比加速比上限1/(1-p)要更差一些,因為上述分析假設了運算強度I無窮大,而且在增加Computing Unit并行度時,通常會使得Communication Unit的傳輸帶寬下降,就使得p更小,從而1/(1-p)更大。
這個結論令人很悲觀,即使通信開銷(1-p)只占0.01,也意味著無論使用多少并行單元,成千上萬,我們最大只能獲得100倍的加速比。有沒有辦法讓p盡可能接近1,也就是1-p趨近于0,從而提高加速比呢?有一枚靈丹妙藥:流水線。
5 Pipelining: 靈丹妙藥
在推導Amdahl's law時,我們假設了Communication Unit和Computing Unit串行工作,總是先令Communication Unit搬運數據,Computing Unit再做計算,計算完成再令Communication Unit搬運數據,再計算,如此循環往復。
能不能讓Communication Unit和Computing Unit同時工作,一邊搬運數據一邊計算呢?如果Computing Unit每計算完一份數據,就立刻可以開始計算下一批數據,那么p就幾乎是1,無論并行度s提高多少倍,都能獲得線性加速比。讓我們研究一下什么條件下可以獲得線性加速比。
圖4:(同圖1)Roofline Model
圖4中的I_1是通信受限型任務,1秒鐘Communication Unit可以搬運beta字節的數據,處理這beta字節Computing Unit需要的計算量是beta*I_1次操作,理論計算峰值是pi,一共需要(beta*I_1)/pi秒完成計算。
對于通信受限型任務,我們有beta*I_1<pi,所以Computing Unit的計算時間是小于1秒的。這也就意味著不到1秒的計算卻需要花1秒鐘的時間搬運數據,那么計算時間就無法掩蓋住數據搬運時間,p最大可以做到(beta*I_1)/pi,加速比最大是1/(pi-beta*I_1)。
圖4中的I_2是計算受限任務,1秒鐘Communication Unit可以搬運beta字節的數據,處理這beta字節Computing Unit需要的計算量是beta*I_2次操作,理論計算峰值是pi,一共需要(beta*I_2)/pi秒完成計算。對于計算受限型任務,我們有 beta*I_2>pi,所以Computing Unit的計算時間是大于1秒的。
這也就意味著,每花1秒鐘搬運的數據需要好幾秒才能計算完,在計算的時間內有充足的時間去搬運下一批數據,也就是計算時間能掩蓋住數據搬運時間,p最大是1,只要I是無窮大,加速比就可以無窮大。
使得Communication Unit和Computing Unit重疊工作的技術叫流水線( Pipelinging: https://en.wikipedia.org/wiki/Pipeline_(computing) )。是一種有效地提高Computing Unit利用率和提高加速比的技術。
6 并行計算的量化模型對深度學習引擎的啟發
上文討論的各種量化模型對深度學習引擎研發同樣適用,譬如對于計算受限型任務,可以通過增加并行度(增加顯卡)來加速;即使是使用同樣的硬件設備,使用不同的并行方法(數據并行,模型并行或流水線并行)會影響到運算強度I,從而影響實際計算性能;分布式深度學習引擎包含大量的通信開銷和運行時開銷,如何減小或掩蓋這些開銷對于加速效果至關重要。
在Processor-centric計算裝置的視角下理解基于GPU訓練深度學習模型,讀者可以思考一下怎么設計深度學習引擎來獲得更好的加速比。
在單機單卡情況下,只需要做好數據搬運和計算的流水線,就可以做到GPU 100%的利用率。實際計算性能最終取決于底層矩陣計算的效率,也就是cudnn的效率,理論上各種深度學習框架在單卡場景不應該存在性能差距。
如果想在同一臺機器內部通過增加GPU來獲得加速,與單卡場景相比,增加了GPU之間數據搬運的復雜性,不同的任務切分方式可能會產生不同的運算強度I(譬如對卷積層適合做數據并行,對全連接層適合模型并行)。除了通信開銷,運行時的調度開銷也會影響加速比。
多機多卡場景,GPU之間數據搬運的復雜性進一步提高,機器之間通過網絡搬運數據的帶寬一般低于機器內部通過PCIe搬運數據的帶寬,這意味著并行度提高了,可數據搬運帶寬降低了,代表著Roofline model中斜線的斜率變小了,CNN這種適合數據并行的場景通常意味著比較高的運算強度I,而還有一些模型譬如RNN/LSTM,運算強度I就小很多,這也意味著流水線中的通信開銷更難以掩蓋了。
7 總結
有用過分布式深度學習引擎的讀者應該對軟件框架的加速比有切身的體會,基本上,卷積神經網絡這種適合數據并行(運算強度I比較高)的模型通過增加GPU來加速的效果還是比較令人滿意的,然而,還有很大一類神經網絡使用模型并行的運算強度才更高一點,而且即使使用模型并行,其運算強度也遠低于卷積神經網絡,對于這些應用如何通過增加GPU并行度來獲得加速是業界尚未解決的難題。
在之前的深度學習評測中,甚至發生了使用多GPU訓練RNN速度比單個GPU還要慢的情況( https://rare-technologies.com/machine-learning-hardware-benchmarks/ )。無論使用什么技術解決深度學習引擎的效率問題,萬變不離其宗,為了提高加速比,都是為了減小運行時開銷,選擇合適的并行模式來提高運算強度,通過流水線掩蓋通信開銷,也都在本文描述的基本定律涵蓋的范圍之內。