選自code。fb,作者:MARAT DUKHAN、YIMING WU、HAO LU,機器之心編譯。

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

連結:

https://

github。com/pytorch/QNNP

ACK

為了將最新的計算機視覺模型部署到移動裝置中,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 從不變化,也因此不需要消耗時間就能遷移到任何方便的儲存配置中。

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

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 的面板適用於一級快取的情況進行了最佳化。因此,它的目的是刪除所有計算非必需的記憶體轉換。

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

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

由於 ONNPACK 對於面板 A 和 B 總是適應 L1 快取的移動神經網路進行了最佳化,因此它在呼叫微核心時處理整個 A 和 B 的面板。而由於無需在微核心之外積累 32 位的中間結果,QNNPACK 會將 32 位的中間結果整合進微核心中並寫出 8 位值,這節省了記憶體頻寬和快取佔用。

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

使整個 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%。

從矩陣相乘到卷積

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

簡單的 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 個輸出行。

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

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/model

s/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 倍。

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

MobileNetV2

作為移動視覺任務的當前最優架構之一,MobileNetV2 引入了瓶頸構造塊和瓶頸之間的捷徑連線。研究者在 MobileNetV2 分類模型的量化版上對比基於 QNNPACK 的 Caffe2 運算元和 TensorFlow Lite 實現。使用的量化 Caffe2 MobileNetV2 模型已開源,量化 TensorFlow Lite 模型來自官方庫:

https://

github。com/tensorflow/t

ensorflow/blob/master/tensorflow/contrib/lite/g3doc/models。md

。下表展示了二者在常用測試集上的 top1 準確率:

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

Facebook 研究者利用這些模型建立了 Facebook AI 效能評估平臺(

https://

github。com/facebook/FAI

-PEP

)的基準,該基準基於 32-bit ARM 環境的大量手機裝置。對於 TensorFlow Lite 執行緒設定,研究者嘗試了一到四個執行緒,並報告了最快速的結果。結果顯示 TensorFlow Lite 使用四執行緒的效能最優,因此後續研究中使用四執行緒來對比 TensorFlow Lite 和 QNNPACK。下表展示了結果,以及在典型智慧手機和高階機上,基於 QNNPACK 的運算元速度比 TensorFlow Lite 快得多。

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

展望

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

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

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

原文連結:

https://

code。fb。com/ml-applicat

ions/qnnpack/