本文的主要內容是解讀 「EasyQuant: Post-training Quantization via Scale Optimization」 這篇由格林深瞳出品的文章。

「論文地址:」 https://arxiv.org/pdf/2006.16669.pdf

「論文代碼:」 https://github.com/deepglint/EasyQuant

這篇文章提出的後訓練量化算法的思路是,引入相似性作爲目標函數,通過交替搜索權值和激活的量化因子(scale)來最大化量化前後激活值的相似性,來找到權值和激活值的最優量化因子。

而實際端上推理階段則採用權值和激活 int7 量化,中間 int16 累加器累加多次的方式,使得推理速度優於權值和激活 int8 量化,中間 int16 累加器只累加兩次的方式,同時還可以比較好的保持量化後算法的精度。

之前一些的量化方法算法的不足:

TensorRT的後量化算法與谷歌提出的訓練量化等方法,有個共同點是對於權值的量化,都是直接取絕對值最大作爲量化因子,也就是。

「每層激活量化因子的計算方式:」

TensorRT採用的方法是過兩遍校驗集,第一遍統計該層激活絕對值的最大值,第二遍先把區間分爲2048份然後統計直方圖,統計完成之後搜索直方圖[128,2048]的區間,通過計算KL散度的方式來確定最佳閾值從而得到量化因子,更詳細的內容可以看參考資料 [4,5]

而谷歌提出的訓練量化方法則是,在訓練中採用(exponential moving average) 的方式統計量化因子 [6] ,具體公式是

其中表示當前batch的激活值,一般取0.95,訓練完成之後量化因子可以通過公式獲得。

「該論文則指出這些方法主要的不足之處在於」

  • 只優化激活的量化因子而權值直接取最大值做量化的話其實會造成誤差積累的問題,所以權值的量化因子也要優化;

  • 在優化量化因子的時候,並沒有考慮卷積層原始浮點輸出與量化版本實現輸出的分佈的相似性。

論文算法解讀

量化的定義

首先來看下量化的一般公式:

其中表示需要量化的張量,表示量化因子,表示量化之後的張量,操作論文裏表示向上取整的意思,表示elementwise點乘操作,而則表示飽和操作,比如量化到 int8,就是把量化後值限定在 [-128,127] 之間,超出的值取邊界值。

然後論文用,表示一個經過量化的L層網絡,其中,和分別表示第l層的激活值(根據給定的一批校驗集獲得),權值和量化因子,都是float32類型。包含兩部分,第l層的激活量化因子和權值量化因子,其中權值的量化可以分通道(每個通道分別對應一個量化因子)或者不分通道(所有通道共用一個量化因子)。

接着定義表示原始預訓練模型第l層的激活值(float32),表示量化權值(int8)和輸入激活(int8)得到的第l層量化輸出激活(int32)再反量化的結果(float32),公式如下:

優化目標

量化推理的流程圖

https://arxiv.org/pdf/2006.16669.pdf

首先看下論文裏的這張圖片,展示了第l層卷積層量化推理的流程圖。

https://arxiv.org/pdf/2006.16669.pdf

首先看最左邊的方框,權值和激活從float32量化到int8,權值因爲可以分通道量化,所以可以看到權值的量化因子是分了3種不同顏色的立方體分別對應了權值3個不同顏色的通道。

https://arxiv.org/pdf/2006.16669.pdf

然後中間的方框,表示推理階段量化版本卷積的實現,可以看到有根據kernel設置分別優化,還有具體實現上用 im2col+gemm 或者 Winograd 優化實現等等

https://arxiv.org/pdf/2006.16669.pdf

最後看最右邊的方框,表示得到卷積層輸出量化激活結果之後,如果下一層不是量化計算層,則直接除以權值和輸入激活的量化因子,得到反量化的輸出(Dequantize)。如果下一層也是量化層,則除了除以權值和輸入激活的量化因子還需要再乘以下一層的輸入量化因子得到量化後的下一層的輸入(Requantize)。

優化單層量化因子

接着看下論文提出的優化目標函數:

最大化第l層原始浮點激活輸出與量化實現得到反量化輸出的相似性,也就是距離越大越好。

然後優化過程分爲兩步,首先固定激活的量化因子,通過最大化相似性優化權值量化因子。接着固定權值量化因子優化激活量化因子。然後,交替優化一直到的值收斂或者超出預定的時間。

然後論文提到和是用權值和激活的最大值來做初始化的:

https://arxiv.org/pdf/2006.16669.pdf

這裏需要注意一點,和是對應了和。

但是這裏我看了下官方release的代碼,對於激活的量化因子初始值貌似用的是TensorRT的方法來初始化的?,不確定我是不是完全理解了代碼,有興趣的可以去看下:

https://github.com/deepglint/EasyQuant/blob/f2f2e6cf38/tools/caffe_quanttable_e2e.py#L428

然後對於優化目標函數時,量化因子的搜索設置,論文是把區間分成n份,然後搜索最佳量化因子,在實驗中設置,還有:

https://arxiv.org/pdf/2006.16669.pdf

但是我去看了官方的代碼,對於權值的優化,設置的搜索區間是和:

https://github.com/deepglint/EasyQuant/blob/f2f2e6cf38/tools/scale_fine_tuning.py#L351

然後對於激活的量化因子的優化,設置的搜索區間是和

https://github.com/deepglint/EasyQuant/blob/f2f2e6cf38/tools/scale_fine_tuning.py#L423

和論文中的設置太一樣,我看issue上也有人提出了這個疑問:

https://github.com/deepglint/EasyQuant/issues/3

作者回復說推薦按照論文裏面的設置,大家如果自己做實驗的時候可以結合論文和官方代碼。

優化整個網絡

然後看下對於整個網絡的優化算法流程圖:

https://arxiv.org/pdf/2006.16669.pdf

可以看到是交替優化權值和激活,但是這裏我看是先固定激活的量化因子,然後優化每一層的權值因子,然後固定每一層的權值因子,再優化逐層優化激活因子,同時在優化激活因子的時候,是每一層激活優化完成之後,更新下一層的量化計算激活值,更具體的細節可以參考官方代碼。

端上部署 int7 加速

https://arxiv.org/pdf/2006.16669.pdf

上面是論文給出的Arm 端int7CPU推理加速計算流程圖。

其中論文中提到了,neon 指令表示向量乘加長指令,把兩個8bit數據相乘產生16bit結果,然後累加到16bit中間結果上,neon指令,則表示把兩個16bit結果相加產生32bit然後累加到32bit累加結果上。

這兩個指令我以前是沒用過,如果對於具體實現上用的哪些指令感興趣的話,可以看下 「ncnn-int8-e2e」 :

https://github.com/deepglint/eq-ncnn

是基於ncnn int8社區版本低比特(小於8bit)量化魔改版。

然後看到端上推理流程圖最上角,因爲用的是量化到int7 [-63,63],所以8bit除去符號位,還有1bit剩餘。這樣就可以比量化到int8多累加幾次到中間16bit結果上,

https://arxiv.org/pdf/2006.16669.pdf

比如論文中提到 int8 只能做兩次指令,然後中間16bit結果就要累加到32bit上,因爲 8bit 取值區間是,所以兩個8bit相乘之後取值區間是,然後累加兩次就到了,所以最多隻能累加兩次,其實第二次也已經有溢出風險了,因爲如果相鄰兩次乘法結果都恰好是,那就會超了int16正數可表示的最大值。

所以谷歌那篇訓練量化的論文提到了,權值可以考慮量化到,就是這麼個道理:

https://arxiv.org/pdf/1712.05877.pdf

這樣子一次乘法計算結果永遠是小於,那麼就可以安全的累加兩次相鄰的乘法計算結果到int16中間結果上,然後再累加到32bit。

然後對於 7bit 取值區間是,所以兩個7bit相乘之後取值區間是,所以可以安全的累加8次到int16中間結果上,然後再累加到32bit。所以相對於int8,int7可以有更好的加速效果。

實驗結果分析:

實驗設置

論文對比了 TensorRT 的方法,對於TensorRT 量化參數的計算,採用了1000個樣本,而對於本論文的方法則是採用了50個樣本來搜索量化參數,感覺還是挺驚人的,只用50個樣本就能超過TensorRT的方法。

精度對比

https://arxiv.org/pdf/2006.16669.pdf

在imagenet2012驗證集上的結果,可以看到不管是量化到int8還是int7,EasyQuant的精度都超過TensorRT,而且很接近浮點的結果。

https://arxiv.org/pdf/2006.16669.pdf

然後從物體檢測和人臉任務上來看,EasyQuant基本也是超過TensorRT的。

https://arxiv.org/pdf/2006.16669.pdf

實驗還對比了 EasyQuant和 訓練量化QAT(Quantize Aware Training),可以按到在ResNet50上結果還是不錯的,稍微超過QAT。

加速對比

https://arxiv.org/pdf/2006.16669.pdf

然後來看下實際端上推理時候 int8 和 int7 的推理速度對比,可以看到不管是單線程還是多線程,int7 推理的延遲都要少於 int8,有20%~30%的效率提升的效果,還是挺可觀的。

更低bit實驗結果

https://arxiv.org/pdf/2006.16669.pdf

論文中還做了更低 bit 的對比實驗,從 int8 到 int5,可以看到在不同任務上,EasyQuant 方法大多是優於 TensorRT 的方法的,而且讓人驚訝的是圖(c),int5 EasyQuant 的精度基本和 int8 相當,沒有降多少,這個確實很厲害。

這篇論文提出了一個在低於8bit下精度還能保持比較好的後量化算法,思想相對TensorRT的方法來說更加容易理解,而且實現上也更加的容易,實際端側推理加速效果也不錯。

參考資料:

  • [1] https://zhuanlan.zhihu.com/p/151383244

  • [2] https://zhuanlan.zhihu.com/p/151292945

  • [3] https://www.yuque.com/yahei/hey-yahei/quantization.mxnet2

  • [4] http://on-demand.gputechconf.com/gtc/2017/presentation/s7310-8-bit-inference-with-tensorrt.pdf

  • [5] https://arleyzhang.github.io/articles/923e2c40/

  • [6] https://zhuanlan.zhihu.com/p/65468307

歡迎關注GiantPandaCV, 堅持原創和分享我們學習到的新鮮知識。

有對文章相關的問題或者想要加入交流羣,歡迎添加本人微信(添加時請說明來意):

留言區

相關文章