日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

e-a乘a的转置的秩_通用矩阵乘(GEMM)优化与卷积计算

發布時間:2023/12/3 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 e-a乘a的转置的秩_通用矩阵乘(GEMM)优化与卷积计算 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

GEMM 主題文章寫了兩版,這是第一版,第二版參見我的博客。
采用知識共享 署名-非商業性使用-禁止演繹 4.0 國際許可授權,轉載請注明出處。

引言

氣象預報、石油勘探、核子物理等現代科學技術大多依賴計算機的計算模擬,模擬計算的核心是表示狀態轉移的矩陣計算。另一方面,計算機圖形處理以及近年來興起的深度學習也和矩陣乘高度相關。而矩陣乘對計算資源消耗較大,除了計算機體系結構的不斷更新外,軟件優化方面也有大量的研究工作。

本文簡要介紹通用矩陣乘(GEMM,General Matrix Multiplication)優化的基本概念和方法、QNNPACK 對特定場景的矩陣乘的優化方法、以及用 GEMM 優化神經網絡中卷積計算的一點方向。

旨在幫助大家在概念中建立一些直覺,無甚高論。

通用矩陣乘優化

基本概念

通用矩陣乘(下文簡稱 GEMM)的一般形式是 = C=AB, 其中 A 和 B 涵蓋了各自轉置的含義。圖一是矩陣乘計算中為計算一個輸出點所要使用的輸入數據。三個矩陣的形狀也如圖所示。

圖一:矩陣乘一個輸出元素的計算

該計算的偽代碼如下。該計算操作總數為

(其中 、 、 分別指代三層循環執行的次數,2 指代循環最內層的一次乘法和加法) ,內存訪問操作總數為 4 (其中 4 指代對 、 、 三者的內存訪問, 需要先讀取內存、累加完畢在存儲,且忽略對 初始化時的操作)。GEMM 的優化均以此為基點。for

How to optimize gemm 介紹了如何采用各種優化方法,將最基礎的計算改進了約七倍(如圖二)。其基本方法是將輸出劃分為若干個 4×4子塊,以提高對輸入數據的重用。同時大量使用寄存器,減少訪存;向量化訪存和計算;消除指針計算;重新組織內存以地址連續等。詳細的可以參考原文。

圖二:How to optimize gemm 的優化效果

計算拆分展示

本節主要以圖形化的方式介紹計算拆分。

圖三 將輸出的計算拆分為 1×4 的小塊,即將 維度拆分為兩部分。計算該塊輸出時,需要使用 矩陣的 1 行,和 矩陣的 4 列。

圖三:矩陣乘計算 1×4輸出

下面是該計算的偽代碼表示,這里已經將 1×4 中 N 維度的內部拆分進行了展開。這里的計算操作數仍然是 2 ,這一點在本文中不會有變化。這里的內存訪問操作數尚未出現變化,仍然是 4 ,但接下來會逐步改進。

for

簡單的觀察即可發現,上述偽代碼的最內側計算使用的矩陣 的元素是一致的。因此可以將 [ ][ ] 讀取到寄存器中,從而實現 4 次數據復用(這里不再給出示例)。一般將最內側循環稱作計算核(micro kernel)。進行這樣的優化后,內存訪問操作數量變為 (3+1/4) (原本這里寫的是 (2+1/4) ,感謝 @CaTHE 的指正),其中 1/4是對 優化的效果。

類似地,我們可以繼續拆分輸出的 維度,從而在內側循環中計算 4×4 輸出,如圖四。

圖四:矩陣乘計算 4×44×4輸出

同樣地,將計算核心展開,可以得到下面的偽代碼。這里我們將 1×4 中展示過的 維度的計算簡化表示。這種拆分可看成是 4×1×4,這樣 和 的訪存均可復用四次。由于乘數效應,4×4 的拆分可以將對輸入數據的訪存縮減到 2 +1/4 +1/4 =(2+1/2) )。這相對于最開始的 4 已經得到了 1.6X 的改進,這些改進都是通過展開循環后利用寄存器存儲數據減少訪存得到的。

for

到目前為止,我們都是在輸出的兩個維度上展開,而整個計算還包含一個削減(Reduction)維度 。圖五展示了在計算 4×4 輸出時,將維度 拆分,從而每次最內側循環計算出輸出矩陣 的 4x4 部分和。

圖五:矩陣乘計算 4×4 輸出對 維度的拆分

下面展示的是這部分計算的展開偽代碼,其中維度 和 已經被簡寫。在這里,最內側循環發生的計算次數已經從最樸素版本的

發展到了 。for

在對 和 展開時,我們可以分別復用 和 的數據;在對 展開時,我們可以將部分和累加在寄存器中,最內層循環一次迭代結束時一次寫到 的內存中。那么內存訪問次數為

——相對原始實現的 8X 改進!

到目前為止,我們已經對計算進行了三次維度拆分、展開并寄存器化(代碼未展示)和訪存復用。這對最基礎版本計算似乎已經足夠了,因為數百條穩定的順序計算指令對處理器流水線已經很友好,且編譯器可以幫助我們做好軟件流水這樣的指令調度。

然而一條計算指令只能完成一次乘加操作(MLA)效率還是比較低。實際上即使是最低端的移動手機處理器都會帶有 SIMD 支持,訪存和計算都可以向量化。因此我們可以再進一步,利用向量操作提高計算的性能。

在介紹向量化計算的細節時,偽代碼是很難理解的,下面依據圖六介紹量化計算的具體過程。圖六左側部分的三幅小圖分別展示了兩個 4×4 矩陣相乘向量化的要素:首先是計算一個輸出元素使用到的輸入元素;然后是對各個矩陣內存的編碼,均以行優先的形式編號;最后是向量化的具體計算方法。

圖六:兩個 4×4矩陣相乘的向量化

這里的向量編號方式假定輸入輸出的內存布局都是行優先,那么兩個輸入各自的 16 個元素通過 4 次向量訪存即可加載到寄存器中。由矩陣乘的規則可知,輸入 中的行可一次性用作輸出的計算,而輸入 B 的行則要拆分使用。這也是向量計算最容易出錯的地方。

圖六右側列出了三份偽代碼。第一份 C0 in detail 是計算 C0 中四個輸出元素的樸素方法的展開,連續四次計算得到一個 C0 元素的結果。將計算過程稍作重排,即可得到 C0 scheduled 展示的計算,這里連續的四次計算分別處理了 C0 中四個元素的 1/4 結果。在連續的四次計算中,重排前只有對 A 的訪存是連續的,重排后 和 的訪存都是連續的。那么向量化這些訪存和計算,即可得到第三列偽代碼中紅色 C0 部分。而第三列是通過對 C1、C2、C3 進行類似的處理得到的。

施行向量化操作后,原本需要 64 條計算指令的計算過程所需指令減少到 16 條,訪存也有類似效果。而向量化對處理器資源的高效使用,又帶來了進一步優化空間,例如可以一次計算 8×8 個局部輸出。

處理內存布局

上一小節列出的是在輸入輸出原有內存布局上所做的優化。在最后向量化時,每次內存訪問都是四個元素。當這些元素為單精度浮點數時,內存大小為 16 字節,這遠小于現代處理器高速緩存行大小(Cache line size)——后者一般為 64 字節。在這種情況下,內存布局對計算性能的影響開始顯現。

圖七展示的是不同內存組織方式對的影響。圖中兩者都是行優先的內存排列,區別在于左側小方塊內部是不連續的,右側小方塊內部是連續的。圖中用幾個數字標記了各個元素在整個內存中的編號。

圖七:不同的局部內存組織

想象一下在這兩者不同內存組織方式的輸入輸出中訪存,每次向量化內存加載仍是 4 個元素。對于一個局部計算使用到的小方塊,左側四次訪存的內存都是不連續的,而右側則是連續的。當數據規模稍大(一般情況肯定足夠大了),左側的連續四次向量化內存加載都會發生高速緩存缺失(cache miss),而右側只會有一次缺失。

在常規的數據規模中,由于左側會發生太多的高速緩存缺失,又由于矩陣乘這樣的計算對數據的訪問具有很高的重復性,將它重排成右側的內存布局減少高速緩存缺失,可顯著地改進性能。另一方面,矩陣乘中兩個輸入矩陣往往有一個是固定的參數,在多次計算中保持不變。那么可以在計算開始前將其組織成特定的形狀,這種優化甚至可以將性能提高 2x。

到這里為止,對 4×4 計算已經有了足夠的優化,可以開始考慮視野更廣一些的全局優化。圖八是一個關于全局優化的小示例。

圖八:矩陣乘的全局優化一瞥

圖中字母標記的是全局性的工作順序,即輸出數據中外層循環迭代方式。左側小圖是常規的行優先遍歷方式,中間小圖是列優先的遍歷方式。這兩者的區別是 和 兩個維度的循環哪個在最外層。

上文已經對 和 兩個維度分別進行了一次拆分,這里可以繼續這種拆分。右側的圖例中是將 和 兩個維度分別拆分為 ,2,4 三部分,將外層拆分都交換到外層循環。下面是相應的偽代碼。

for

經過這樣的調度,從整體計算來看,可看作是將 4×4 計算拓展成了 8×8 ,其實是同一種思路。

QNNPACK 的矩陣乘優化

QNNPACK (Quantized Neural Network PACKage) 是 Facebook 開源的專門用于量化神經網絡的計算加速庫。QNNPACK 和 NNPACK (Neural Network PACKage) 的作者都是 Marat Dukhan 。到目前為止,QNNPACK 仍然是已公開的,用于移動端(手機)的,性能最優的量化神經網絡加速庫。

QNNPACK 開源時附帶了一份技術報告性質的博客。本節將結合上節的內容簡要地從博客原作中抽取一些關于 GEMM 的內容。

量化神經網絡

神經網絡計算一般都是以單精度浮點(Floating-point 32, FP32)為基礎。而網絡算法的發展使得神經網絡對計算和內存的要求越來越大,以至于移動設備根本無法承受。為了提升計算速度,量化(Quantization)被引入到神經網絡中,主流的方法是將神經網絡算法中的權重參數和計算都從 FP32 轉換為 INT8 。

兩種數值表示方法的方程如上。如果對量化技術的基本原理感興趣,可以參考 Neural Network Quantization Introduction 。

應用量化技術后,計算方面顯現了若干個新的問題。首先是 NNPACK 這樣用于 FP32 的計算加速庫無法用于 INT8 ,這導致我們需要新的加速計算方法。再者是輸入輸出都轉化成 INT8 后,內存帶寬需求直接下降為 1/4 。隨之而來的內存容量需求變化出現了一些新的優化機會。而 QNNPACK 充分利用了這些優化方法,并結合神經網絡領域的特點,大幅改進了計算性能。

另一方面,Gemmlowp 是 Google 開源的低精度 GEMM 加速庫。在加速之外,Gemmlowp 的特別之處是提供了一套用定點計算模擬浮點計算的機制。例如

這樣浮點計算也可通過 Gemmlowp 在僅支持定點計算的處理器上運行。和 QNNPACK 不同,Gemmlowp 似乎目的在于支持 GEMM 而非單純神經網絡,因此在神經網絡方面的性能目前落后于 QNNPACK 。

計算劃分與削減維度

和上節所述類似,QNNPACK 的計算也是基于對輸出的劃分,拆分成如圖九的 × MR×NR 小塊。這里需要注意的一點是,原文圖例中對 B 的標記有筆誤, B 的列高度應該是 K 而非 N。(我們向 QNNPACK 報告了這一問題,目前尚未得到修正。)

圖九:QNNPACK 矩陣乘劃分示例

QNNPACK 實現了 4×8 的和 8×8 兩種計算核(micro kernel),分別用于支持 armv7 和 arm64 指令集的處理器。這兩種計算核在原理上區別不大,后者主要利用了更多的寄存器和雙發射(Dual Issue)以提高計算的并行度。

拆分后 × 計算塊使用的內存為 ? + ? + ? 。由于常規的神經網絡計算 <1024 , 那么這里的內存消耗一般不超過 16KB,可以容納在一級高速緩存(L1 Cache)中。QNNPACK 的這一發現是其矩陣乘優化的基礎。

如圖十所示,在計算 × 小塊時,傳統的方法(即上一節的方法)是在 維度上拆分,在一次計算核的處理中,僅計算 維的局部。那么在每次計算核的處理中,都會發生對輸出的加載和存儲——要將本次計算產生的部分和累加到輸出中。

圖十:QNNPACK 和傳統方法計算削減維度的對比

而 QNNPACK 的做法是將整個 K 維全部在計算核中處理完,這樣就幾乎完全消除了輸出部分和的訪存。兩種的差異的細節可以參考圖十兩側的偽代碼。這里所說的「將整個 維全部」并不是指 維不拆分——在實際計算中 K 維還是會以 8 為基礎拆分——而是指拆分后不和其他維度交換(interchange)。

內存組織的特點

上節中曾提到,對內存的重新組織(Repacking)可以改進高速緩存命中率,從而提高性能。但是這種重新組織也是有開銷的。

計算核中最小的計算單元處理的是兩個 4×4 矩陣相乘。傳統的方法由于 可能很大,需要對輸入內存進行重新組織,防止相鄰的訪存引起高速緩存沖突,如圖十一。

圖十一:QNNPACK 和傳統矩陣乘對局部計算的處理

而在量化神經網絡中,由于 K 比較小,計算核處理中使用到的內存完全可以容納在一級高速緩存中,即使不重新組織內存,高速緩存的重用率也足夠高。

參考圖七左側部分,QNNPACK 計算核一次會使用 8 行輸入(假定圖中繪制以 8 為基礎分塊)。盡管對第一個 8×8 矩陣塊的向量化加載可能全部是高速緩存缺失(Cache miss),第二個 8×8 則全部命中——因為它們已經作為同在一個高速緩存行的內容隨第一個矩陣塊加載到了高速緩存中。其他矩陣塊也是類似情況。

采用了這些基于神經網絡領域先驗知識的優化方法后,QNNPACK 擊敗了所有神經網絡量化領域的用于移動端加速庫。不過,QNNPACK 的拆分著眼于削減維度,沒有在輸出維度上做全局調度。我們在 QNNPACK 基礎上實現了 和 維度的外層循環拆分調度,簡單的實驗獲得了相對于 QNNPACK 1.1x 的性能表現。

卷積與矩陣乘

卷積(Convolution)是神經網絡的核心計算。而卷積的變種極為豐富,本身計算又比較復雜,因此其優化算法也多種多樣,包括 im2col、Winograd 等等。本節重點關注卷積和矩陣乘的關系。

im2col 計算方法

作為早期的深度學習框架,Caffe 中卷積的實現采用的是基于 im2col 的方法,至今仍是卷積重要的優化方法之一。

im2col 是計算機視覺領域中將圖片的不同通道(channel)轉換成矩陣的列(column)的計算過程。Caffe 在計算卷積時,首先用 im2col 將輸入的三維數據轉換成二維矩陣,使得卷積計算可表示成兩個二維矩陣相乘,從而充分利用已經優化好的 GEMM 庫來為各個平臺加速卷積計算。

圖十二是卷積的 im2col 過程的示例。隨著卷積過濾器在輸入上滑動,將被使用的那部分輸入展開成一行大小為 × × 的向量。在滑動結束后,則得到特征矩陣 ( × )×( × × ) 。將過濾器展開成 ( )×( × × ) 的矩陣,那么卷積即可表示成這兩個矩陣相乘的結果(特征矩陣要進行轉置操作)。

圖十二:im2col 過程(來源)

im2col 計算卷積使用 GEMM 的代價是額外的內存開銷,輸入會使用額外的 × 倍內存。當卷積核尺寸是 1×1 時,由于不需要重排輸入,GEMM 可以直接在原始輸入上運行,并且不需要使用額外的內存。

內存布局與卷積性能

神經網絡中卷積的內存布局主要有 NCHW 和 NHWC 兩種。最后重點分析一下 im2col 1×1 卷積性能和內存布局的關系。

對于不需要額外調整輸入的 1×1 卷積,將 NCHW 內存布局的卷積對應到矩陣乘 = 時, 是卷積核(filter), 是輸入(input)。各個矩陣的維度如圖十二所示。

圖十二:NCHW 內存布局卷積轉換成的矩陣乘

對該矩陣施行劃分后,將計算核的訪存局部性表現標記在圖十二中。其中 Inside 表示小塊矩陣乘內部的局部性,Outside 表示在削減維度方向的局部性。

對輸出而言,小塊內向量化訪存局部性較差,外部表現取決于全局計算方向——行優先則局部性較好,列優先則較差。由于卷積核可以事先重排內存,因此視其局部性都較好。輸入則小塊內外都較差,因為削減維度是列優先的,幾乎每次加載輸入都會發生高速緩存缺失。

圖十三是與之相對的 NHWC 內存布局的示例。值得注意的是,NHWC 和 NCHW 中 、 矩陣所代表的張量發生了調換—— = × 。具體的拆分方式仍然一樣。

圖十三:NHWC 內存布局卷積轉換成的矩陣乘

在 NHWC 中,輸出的局部性表現和 NCHW 一樣。同樣的,卷積核也視作局部性表現較好。對于輸入,小方塊的內部局部性表現不是很好,因為幾次向量加載的地址不連續;而外部局部性表現則較好,因為在削減維度滑動使用的內存是連續的——這一點在「處理內存布局」小節中已有闡述。

可以看到,對于 1×1如果采用 im2col 方法計算,且不對輸入輸出進行額外的內存重排,那么 NHWC 的訪存特征是顯著優于 NCHW 的。

總結

至此,本文介紹了 GEMM 優化的基本方法概念,在神經網絡領域中 QNNPACK 基于量化對 GEMM 的優化,和 im2col 方法對卷積計算及其內存布局的意義。GEMM 優化實質上是個非常重要,且和特定領域綁定很強的話題,更進一步的內容需要進入到特定領域深入研究。如果對 GEMM 各種優化技巧所帶來的性能收益感興趣,可以參考 How to optimize gemm。如果對 GEMM 優化和體系結構結合的理論感興趣,可以參考 Anatomy of High-Performance Matrix Multiplication 。

參考

  • How to optimize gemm
  • QNNPACK
  • Anatomy of High-Performance Matrix Multiplication
  • Convolution in Caffe

總結

以上是生活随笔為你收集整理的e-a乘a的转置的秩_通用矩阵乘(GEMM)优化与卷积计算的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。