當前位置:
首頁 > 知識 > 讓手機神經網路速度翻倍:Facebook開源高性能內核庫QNNPACK

讓手機神經網路速度翻倍:Facebook開源高性能內核庫QNNPACK

選自code.fb

作者:MARAT DUKHAN、YIMING WU、HAO LU

機器之心編譯

機器之心編輯部

今天,Facebook 開源了一個高性能內核庫——QNNPACK,專為移動 AI 進行優化。該內核庫加速了許多運算,如深度類型的卷積,促進了神經網路架構的使用。QNNPACK 已經被整合進 Facebook 應用,部署到了數十億台設備中。在 MobileNetV2 等基準測試中,QNNPACK 在各種手機上表現出的性能是當前最佳實現的兩倍。

鏈接:https://github.com/pytorch/QNNPACK

為了將最新的計算機視覺模型部署到移動設備中,Facebook 開發了一個用於低密度卷積的優化函數庫——QNNPACK,用在最佳神經網路中。

QNNPACK 的全稱是 Quantized Neural Network PACKage(量化神經網路包),是 Facebook 應用的一部分,已經被部署到全球數十億台移動設備中。這個新庫可以執行高級計算機視覺任務,如在手機上實時運行 Mask R-CNN 和 DensePose 或在性能受限的移動設備中用 100ms 以內的時間實施圖像分類。

Facebook 開源 QNNPACK,為優化推理提供全方位的支持,作為構建 PyTorch 1.0 平台的一部分。QNNPACK 藉助 Caffe2 模型表徵即刻可用,Facebook 正在開發實用程序,將 PyTorch 的 Python 前端模型導出到圖表徵中。他們還在其他平台上優化這些運算,而不僅限於移動設備。

由於移動設備的計算力僅僅是數據中心伺服器的十分之一到千分之一,運行當前最佳人工智慧應用需要作出一些調整,壓縮來自硬體的所有可用性能。QNNPACK 通過提供量化張量上的卷積、解卷積及全連接運算高性能實現來做到這一點。在 QNNPACK 之前,幾個常見的神經網路基元(分組卷積、擴張卷積)缺乏良好的開源實現;因此,ResNeXt、CondenseNet 和 ShuffleNet 等頗有前景的研究模型沒有得到充分利用。

移動設備前沿 AI 技術新優化

兩年前,Facebook 開始在手機上部署神經網路,多數計算機視覺架構隨著大型內核被部署到卷積運算中。這些運算因計算強度高而飽受詬病:直接實現涉及每個載入元素的許多乘-加運算。Caffe2Go 使用的是一種叫做 NNPACK 的內核庫,該庫實現基於 Winograd 變換或快速傅立葉變換的漸近快速卷積演算法,以減少卷積計算中的乘-加運算。例如,3×3 卷積比 1×1 卷積運算慢兩倍,但使用直接演算法要慢 9 倍。

計算機視覺領域發展迅猛,然而,這種新的神經網路架構使用的是幾種無法從快速卷積演算法中獲益的卷積,即 1×1 卷積、分組卷積、轉置卷積、空洞卷積和深度卷積。這些類型的卷積計算強度相對較低,因此可以通過利用低精度計算從內存降低的帶寬中受益。

用於計算機視覺的神經網路將多數推理時間用在卷積和全連接運算元中。這些運算元與矩陣相乘緊密相關:全連接運算元和 1×1 卷積直接映射到矩陣相乘,具有較大內核的卷積可以分解成一種名為 im2col 的內存布局轉換和矩陣相乘的組合。因此,卷積神經網路中的有效推理問題很大程度上可以看做矩陣乘法的有效實現問題——在線性代數庫中也稱為 GEMM。

實現矩陣相乘

不直接在科學計算或者深度學習軟體上工作的軟體工程師可能不熟悉庫是如何實現矩陣相乘的,所以在詳細介紹 QNNPACK 之前,會有一個總體介紹。

在以下示例中,A 是輸入,B 是權重,C 是輸出。在推理過程中,B 從不變化,也因此不需要消耗時間就能遷移到任何方便的存儲配置中。

MxK 矩陣 A 與 KxN 矩陣 B 相乘得到 MxN 矩陣 C。C 中的每個元素都可以認為是 A 行與對應 B 列的點積。

在點積基元上實現整個矩陣相乘是可能的,但這樣的實現過於低效。在一個點積中,每一個乘-加運算需要上傳兩個元素,在當前的處理器上,這一實現會受到內存和緩存帶寬,而不是乘-加單元計算力的限制。但一個小小的修改——同時計算幾行 A 和幾行 B 的點積——卻使得性能大大提升。

修改後的基元載入 A 的 MR 及 B 的 NR 元素,實施 MRxNR 乘積累加運算。MR 和 NR 的最大值受到整數個數和處理器架構其它細節的限制。但在多數現代系統中,這些最大值足夠大,可以使運算只受計算限制,所有高性能矩陣乘法實現都建立在這個基元上,該基元通常被稱為 PDOT(panel dot product)微內核。

神經網路中的優化及 QNNPACK 如何提高效率

PyTorch 及其它深度學習框架在訓練期間通常利用浮點數來表示權重和神經網路的神經元。模型訓練完成之後,浮點數及運算就會顯得過分:許多類型的模型可以在調整後使用推理用的低精度整數運算,不會出現明顯的準確率損失。低精度整數表徵在單精度、甚至是半精度浮點上提供一些益處:內存佔用減小 2/1 或 3/4,有助於將神經網路模型保存在移動處理器的小緩存中;提高內存帶寬受限的運算性能;提高能源利用率;在許多類型的硬體上提高計算吞吐量。

QNNPACK 使用與安卓神經網路 API 兼容的線性量化方案。它假設量化值 q[i] 表示為 8 位無符號整數,並且它們與實值表示 r[i] 相關,公式如下:

r[i] = scale * (q[i] – zero_point)

公式中的 scale 是一個正浮點數,zero_point 是一個無符號的 8 位整數,就像 q[i] 一樣。

儘管 QNNPACK 像其它 BLAS 庫一樣利用 PDOT 微內核,但它對具有 8 位元素的量化張量和移動 AI 用例的關注為性能優化帶來了截然不同的視角。多數 BLAS 庫針對的是矩陣高達數千個雙精度浮點元素的科學計算用例,但 QNNPACK 的輸入矩陣來自低精度、移動專用的計算機視覺模型,並且具有非常不同的維度。在 1×1 卷積中,K 是輸入通道的數量,N 是輸出通道的數量,M 是圖像中像素的數量。在實用移動優化網路中,K 和 N 不超過 1024,取值範圍通常在 32-256 之間。

由於移動架構的局限,MR 和 NR 不超過 8。因此即使是在有 1024 個通道的最大模型中,整個內存塊在 PDOT 微內核中的讀取速度也只能達到 16KB,即使在超低端移動內核上也能適用於一級緩存。這標誌著 QNNPACK 和其他 GEMM 實現之間的一個重要區別:雖然其它庫重新打包 A 和 B 矩陣以更好地利用緩存層次結構,希望在大量計算中分攤打包開銷,但 QNNPACK 針對 A 和 B 的面板適用於一級緩存的情況進行了優化。因此,它的目的是刪除所有計算非必需的內存轉換。

在量化矩陣-矩陣乘法中,8 位整數的乘積通常會被累加至 32 位的中間結果中,隨後重新量化以產生 8 位的輸出。常規的實現會對大矩陣尺寸進行優化——有時 K 太大無法將 A 和 B 的面板轉入緩存中。為了有效利用緩存層次結構,傳統的 GEMM 實現將 A 和 B 的面板沿 K 維分割成固定大小的子面板,從而每個面板都適應 L1 緩存,隨後為每個子面板調用微內核。這一緩存優化需要 PDOT 為內核輸出 32 位中間結果,最終將它們相加並重新量化為 8 位整數。

由於 ONNPACK 對於面板 A 和 B 總是適應 L1 緩存的移動神經網路進行了優化,因此它在調用微內核時處理整個 A 和 B 的面板。而由於無需在微內核之外積累 32 位的中間結果,QNNPACK 會將 32 位的中間結果整合進微內核中並寫出 8 位值,這節省了內存帶寬和緩存佔用。

使整個 A、B 面板適配緩存幫助實現了 QNNPACK 中的另一個優化:取消了矩陣 A 的重新打包。矩陣 B 包含靜態權重,可以一次性轉換成任何內存布局,但矩陣 A 包含卷積輸入,每次推理運行都會改變。因此,重新打包矩陣 A 在每次運行時都會產生開銷。儘管存在開銷,傳統的 GEMM 實現還是出於以下兩個原因對矩陣 A 進行重新打包:緩存關聯性及微內核效率受限。如果不重新打包,微內核將不得不讀取被潛在的大跨距隔開的幾行 A。如果這個跨距恰好是 2 的許多次冪的倍數,面板中不同行 A 的元素可能會落入同一緩存集中。如果衝突的行數超過了緩存關聯性,它們就會相互驅逐,性能也會大幅下降。幸運的是,當面板適配一級緩存時,這種情況不會發生,就像 QNNPACK 優化的模型一樣。

打包對微內核效率的影響與當前所有移動處理器支持的 SIMD 向量指令的使用密切相關。這些指令載入、存儲或者計算小型的固定大小元素向量,而不是單個標量(scalar)。在矩陣相乘中,充分利用向量指令達到高性能很重要。在傳統的 GEMM 實現中,微內核把 MR 元素重新打包到向量暫存器里的 MR 線路中。在 QNNPACK 實現中,MR 元素在存儲中不是連續的,微內核需要把它們載入到不同的向量暫存器中。越來越大的暫存器壓力迫使 QNNPACK 使用較小的 MRxNR 拼貼,但實際上這種差異很小,而且可以通過消除打包開銷來補償。例如,在 32 位 ARM 架構上,QNNPACK 使用 4×8 微內核,其中 57% 的向量指令是乘-加;另一方面,gemmlowp 庫使用效率稍高的 4×12 微內核,其中 60% 的向量指令是乘-加。

微內核載入 A 的多個行,乘以 B 的滿列,結果相加,然後完成再量化並記下量化和。A 和 B 的元素被量化為 8 位整數,但乘積結果相加到 32 位。大部分 ARM 和 ARM64 處理器沒有直接完成這一運算的指令,所以它必須分解為多個支持運算。QNNPACK 提供微內核的兩個版本,其不同之處在於用於乘以 8 位值並將它們累加到 32 位的指令序列。

默認微內核

NEON 是 ARM 架構上的向量擴展(vector extension),它包含很多不尋常的指令。QNNPACK 中的默認微內核廣泛使用了兩種 NEON 特定類型的指令:「長」指令,產生的元素向量是其輸入的兩倍寬;向量暫存器與另一向量暫存器中的元素相乘。微內核載入 8 位整數(無正負之分)的向量,將其擴展到 16 位,並使用向量 x 標量+長指令(VMLAL.S16 in AArch32 and SMLAL/SMLAL2 in AArch64)的結果與累加到 32 位的 16 位元素相乘。

ARM NEON 提供了一條指令(VSUBL.U8 on AArch32 and USUBL/USUBL2 on AArch64)來減去 8 位整數的向量併產生 16 位整數結果的向量,在大多數 ARM 微架構中,這條指令和簡單的整數擴展指令(VMOVL.U8 on AArch32 and UMOVL/UMOVL2 on AArch64)一樣快。作為額外的優化,微內核結合了 A 和 B 矩陣元素零點的減法和從 8 位整數到 16 位整數的擴展。

雙發射微內核(Dual-issue microkernel)

默認微內核使用最少的命令,因此它在低端核上的性能最優,但每個周期默認微內核僅能執行一個 NEON 命令。類似地,高端 Cortex-A 內核也是每個周期僅能執行一次 NEON 整數乘法命令,但是它至少能夠並行執行 NEON 整數乘法和 NEON 整數加法命令。因此理論上通過精心寫並行執行兩個命令的彙編代碼可以改進性能:vector multiply long (VMULL.U8 in AArch32, UMULL in AArch64) 乘 8-bit 元素得到 16-bit 乘積;向量成對相加(vector pairwise add)(VPADAL.U16 in AArch32, UADALP in AArch64) 加鄰近的 16-bit 乘積得到 32-bit 結果。假設向量相乘(vector multiply)和向量成對相加命令的調度完美,則雙發射微內核每個周期可輸出 8 個乘加結果,是默認微內核的 2 倍。

在高端 Cortex-A 內核上實際利用雙發射能力較為複雜,原因如下:一,在高端 Cortex-A 內核上的雙發射能力並不完美,可以維持兩個周期內執行三個命令的速度;二,NEON 不支持 8-bit 整數向量的 vector-by-scalar 乘法,因此研究中使用的是向量乘法以及額外的命令 (VEXT.8 on AArch32, EXT on AArch64),以旋轉矩陣 A 中的向量;三,在 8-bit 元素上執行乘法,則無法在乘法之前減去零點(減去後結果的寬度是 9bit),需要預計算 A 的行的總和以在重新量化之前調整累加的 32-bit 結果。

儘管上述因素導致了一些開銷,但在 Cortex-A75 核上,利用雙發射能力的微內核對於較大的通道數(K > 64)速度提升了 15% 到 20%。

從矩陣相乘到卷積

簡單的 1×1 卷積可直接映射到矩陣相乘,但對於具備較大卷積核、padding 或子採樣(步幅)的卷積而言則並非如此。但是,這些較複雜的卷積能夠通過記憶變換 im2col 映射到矩陣相乘。對於每個輸出像素,im2col 複製輸入圖像的圖像塊並將其計算為 2D 矩陣。由於每個輸出像素都受 KHxKWxC 輸入像素值的影響(KH 和 KW 分別指卷積核的高度和寬度,C 指輸入圖像中的通道數),因此該矩陣的大小是輸入圖像的 KHxKW 倍,im2col 給內存佔用和性能都帶來了一定的開銷。和 Caffe 一樣,大部分深度學習框架轉而使用基於 im2col 的實現,利用現有的高度優化矩陣相乘庫來執行卷積操作。

Facebook 研究者在 QNNPACK 中實現了一種更高效的演算法。他們沒有變換卷積輸入使其適應矩陣相乘的實現,而是調整 PDOT 微內核的實現,在運行中執行 im2col 變換。這樣就無需將輸入張量的實際輸入複製到 im2col 緩存,而是使用輸入像素行的指針設置 indirection buffer,輸入像素與每個輸出像素的計算有關。研究者還修改了矩陣相乘微內核,以便從 indirection buffer 載入虛構矩陣(imaginary matrix)A 的行指針,indirection buffer 通常比 im2col buffer 小得多。此外,如果兩次推斷運行的輸入張量存儲位置不變,則 indirection buffer 還可使用輸入張量行的指針進行初始化,然後在多次推斷運行中重新使用。研究者觀察到具備 indirection buffer 的微內核不僅消除了 im2col 變換的開銷,其性能也比矩陣相乘微內核略好(可能由於輸入行在計算不同輸出像素時被重用)。

QNNPACK 和深度卷積

分組卷積(grouped convolution)將輸入和輸出通道分割成多組,然後對每個組進行分別處理。在有限條件下,當組數等於通道數時,該卷積就是深度卷積,常用於當前的神經網路架構中。深度卷積對每個通道分別執行空間濾波,展示了與正常卷積非常不同的計算模式。因此,通常要向深度卷積提供單獨實現,QNNPACK 包括一個高度優化版本 3×3 深度卷積。

深度卷積的傳統實現是每次都在卷積核元素上迭代,然後將一個卷積核行和一個輸入行的結果累加到輸出行。對於一個 3×3 的深度卷積,此類實現將把每個輸出行更新 9 次。在 QNNPACK 中,研究者計算所有 3×3 卷積核行和 3×3 輸入行的結果,一次性累加到輸出行,然後再處理下個輸出行。

QNNPACK 實現高性能的關鍵因素在於完美利用通用暫存器(GPR)來展開卷積核元素上的循環,同時避免在 hot loop 中重新載入地址寄存器。32-bit ARM 架構將實現限制在 14 個 GPR。在 3×3 深度卷積中,需要讀取 9 個輸入行和 9 個卷積核行。這意味著如果想完全展開循環必須存儲 18 個地址。然而,實踐中推斷時卷積核不會發生變化。因此 Facebook 研究者使用之前在 CxKHxKW 中的濾波器,將它們封裝進 [C/8]xKWxKHx8,這樣就可以僅使用具備地址增量(address increment)的一個 GPR 訪問所有濾波器。(研究者使用數字 8 的原因在於,在一個命令中載入 8 個元素然後減去零,在 128-bit NEON 暫存器中生成 8 個 16-bit 值。)然後使用 9 個輸入行指針,指針將濾波器重新裝進 10 個 GPR,完全展開濾波器元素上的循環。64-bit ARM 架構相比 32-bit 架構,GPR 的數量翻了一倍。QNNPACK 利用額外的 ARM64 GPR,一次性存儲 3×5 輸入行的指針,並計算 3 個輸出行。

QNNPACK 的性能優勢

測試結果顯示出 QNNPACK 在端到端基準上的性能優勢。在量化當前最優 MobileNetV2 架構上,基於 QNNPACK 的 Caffe2 運算元的速度大約是 TensorFlow Lite 速度的 2 倍,在多種手機上都是如此。除了 QNNPACK 之外,Facebook 還開源了 Caffe2 quantized MobileNet v2 模型,其 top-1 準確率比相應的 TensorFlow 模型高出 1.3%。

Caffe2 quantized MobileNet v2 模型開源地址:https://github.com/caffe2/models/tree/master/mobilenet_v2_quantized

MobileNetV1

MobileNetV1 架構在使用深度卷積(depthwise convolution)使模型更適合移動設備方面具備開創性。MobileNetV1 包括幾乎整個 1×1 卷積和 3×3 卷積。Facebook 研究者將量化 MobileNetV1 模型從 TensorFlow Lite 轉換而來,並在 TensorFlow Lite 和 QNNPACK 的 32-bit ARM 設備上對 MobileNetV1 進行基準測試。二者運行時均使用 4 線程,研究者觀察到 QNNPACK 的運行速度幾何平均值是 TensorFlow Lite 的 1.8 倍。

MobileNetV2

作為移動視覺任務的當前最優架構之一,MobileNetV2 引入了瓶頸構造塊和瓶頸之間的捷徑連接。研究者在 MobileNetV2 分類模型的量化版上對比基於 QNNPACK 的 Caffe2 運算元和 TensorFlow Lite 實現。使用的量化 Caffe2 MobileNetV2 模型已開源,量化 TensorFlow Lite 模型來自官方庫:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/g3doc/models.md。下表展示了二者在常用測試集上的 top1 準確率:

Facebook 研究者利用這些模型建立了 Facebook AI 性能評估平台(https://github.com/facebook/FAI-PEP)的基準,該基準基於 32-bit ARM 環境的大量手機設備。對於 TensorFlow Lite 線程設置,研究者嘗試了一到四個線程,並報告了最快速的結果。結果顯示 TensorFlow Lite 使用四線程的性能最優,因此後續研究中使用四線程來對比 TensorFlow Lite 和 QNNPACK。下表展示了結果,以及在典型智能手機和高端機上,基於 QNNPACK 的運算元速度比 TensorFlow Lite 快得多。

展望

QNNPACK 已經幫助 Facebook 的 app 在全世界的移動設備中部署人工智慧。研究者正在嘗試進一步提升 QNNPACK 的性能,包括 FP16 格式的低精度計算,利用 NEON 點積(VDOT)和 16-bit 累積(16-bit accumulation)來使移動設備上的 AI 更加輕便。

Facebook 期待通過 PyTorch API 提供 QNNPACK 運算元支持,以及為移動開發者提供工具。Facebook 希望 QNNPACK 能夠通過提升模型的移動性能惠及 AI 研究者和開發者。

本文為機器之心編譯,轉載請聯繫本公眾號獲得授權。

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


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 機器之心 的精彩文章:

預訓練BERT,官方代碼發布前他們是這樣用TensorFlow解決的
專訪 | 短視頻那麼多,快手如何利用GRU實現各種炫酷的語音應用

TAG:機器之心 |