神經網絡量化--從早期的量化算法談起


本文首發於公眾號

之前寫過一系列網絡量化相關的文章,它們都出自 Google 在 2018 年發表的一篇論文,目前也是 tflite 和 pytorch 等框架中通用的量化標准。不過,最近有讀者在后台問我,說他看到的一些論文和我文章中的方法差別很大,被搞懵了。因此,今天想整理一下網絡量化的發展脈絡,幫助剛入門的同學更好地理清這里面的來龍去脈。

為什么要模型量化

關於模型量化,最直接的想法當然是把所有浮點運算都轉變為定點運算,換言之需要把所有數值從 float 等浮點型上轉變為 int16、int8 等整型變量,甚至變成 2bit、3bit 等極低比特表達。要知道一個 float32 的數值需要占據 4 個字節,而 int8 最多一個,不僅內存讀取上快了幾倍,而且定點運算也會比浮點運算更容易實現硬件加速 (2bit 等極端情況甚至用移位來實現,快到飛起)。

而神經網絡里面的數值主要分三種:網絡權重、中間的輸出特征 (feature map),梯度。如果把權重和特征可以定點化,那推理的時候就可以在 FPGA 等硬件上跑完整個網絡,節能又高效,如果梯度也能定點化,那訓練的時候也可以提速,總之,量化高效節能環保,好處大大的有。

穩扎穩打派

最開始的時候人們並不知道量化會對網絡產生什么影響,因此需要一點點嘗試。比如過,先對權重 weight 做量化,看看網絡還能不能 work。

典型代表如 MIT 的韓松教授。剛入門模型量化的同學應該都讀過他的 Deep Compression 論文,這是集剪枝量化等技術於一身的作品。其中,量化這一步的基本操作如圖中所示:

這是一種非常朴素的思想。假設權重的數值是在區間 [-1.08, 2.12] 之間,那一種最簡單的量化方法就是找出 -1, 0, 1, 2 這幾個整數作為中心,然后把各個權重四舍五入到這幾個數即可。這種四舍五入的操作會導致誤差 (這也是量化誤差的來源),假設有一個輸入是 0.5,對應的浮點權重本來是 2.09,結果被我們量化到 2,在前向傳播的時候,就會從 \(0.5 \times 2.09\) 變成 \(0.5 \times 2\)。綜合起來,每一層前向傳播都會累積大量的誤差,這些誤差會在 loss 上體現出來,又以梯度的形式傳回來,最終,更新到原來的浮點權重上。

這種量化的思路相信大部分人都能理解。作為量化上開荒時代的作品,它對落地並不友好 (這種聚類找量化中心的形式不方便硬件加速,同時由於沒有對中間的 feature 量化,前向傳播其實還是要浮點進行),並且在量化訓練上也存在一些難點 (萬一權重都在 0~1 之間,那量化后的權重就變成 0 或者 1,信息都丟掉了)。

之后也有一些論文針對它做了一些改進,比如周教授在英特爾做的 Incremental Network Quantization,他們采用的是移位量化,因此會量化到 \(2^{-1}\)\(2^{-2}\) 等一些數值上 (在硬件上可以通過 bitshift 來實現,非常快,不過本質上和量化到 1、2......等意義是一樣的)。他們估計是發現之前的方法在量化訓練上存在一些問題 (比如直接四舍五入后,在前向傳播中信息丟失太多,導致網絡的訓練優化很困難),因此改進了量化訓練方式,采用逐步量化,而不是一口吃成一個胖子,讓一部分權重保持全精度來更好地學習量化誤差。

當然,這些改進工作並沒有做到真正的全量化,對網絡中間的 feature 還是采用浮點的形式保存 (為什么都沒有對 feature 做量化,可以猜測是網絡優化的難度太大),因此落地價值都不算大。

極致壓縮派

江湖上其實還有另一伙狂人,追求極致壓縮,要用 3 個比特甚至 2 個比特來實現量化。

第一個吃螃蟹的人中,典型的如 Bengio 大佬。他的研究組提出了 Binarized Neural Networks,僅使用 +1 和 -1 來表達所有數值,並且是做到 weight 和 feature 都量化,因此很適合硬件部署 (如果模型效果不掉的話)。他們的量化方式也很直接,直接根據原數值判斷,如果大於 0 就量化到 +1,否則量化量化到 -1。

他們量化訓練的方法已經有后來 tflite 中量化訓練的影子了:

  1. 前向傳播:逐層對 weight 和 feature 進行二值化,一直到最后一層的輸出;
  2. 反向傳播:根據網絡輸出結果,計算 loss,回傳梯度。這里需要注意,如果只是對 weight 做量化,梯度是可以正常算的 (因為梯度是對 feature 求導),如果對 feature 也做了量化,考慮到量化本身不可導或者導數為 0,就需要使用 STE(straight-through estimator) 將梯度跳過量化函數;
  3. 參數更新:在浮點 weight 上更新梯度。

應該說,這套量化策略對落地是很友好的,量化訓練本身也 make sense,很容易用現有的深度學習框架實現。不過,在模型效果方面,精度損失有點慘不忍睹,離全精度模型的性能相差甚大,因此不太可能落地。

后來有很多人看到這里面的潛力,紛紛涌上去一通改造。比如被各大自媒體吹捧的 XNOR-Net、Ternary weight networks、DoReFa-Net 等都對其進行較大改進,一方面對網絡優化方面的難點做了些優化,另一方面也對量化策略本身做一些改進 (畢竟只量化到 -1 和 1 的話,整個網絡的權重和 feature 都變成二維碼了,信息損失很大)。

工業落地派

在量化領域,學術界和工業界一直存在較大的隔閡。學術界的人追求理論上的完備,並要求盡可能低的壓縮比特 (8bit 是 baseline,沒什么難度,不做到 4bit 以下發什么論文),但工業界需要的是能落地的方案,所謂落地,就是硬件上能很快跑起來,同時精度要沒什么損失,至於什么 2bit、3bit,要么精度沒法看,要么需要特殊的硬件加持,工程師們提不起興趣。

因此,以 Google 為代表的企業提出了一套更加通用的量化標准,並且在 tflite 中真正實現落地。這套標准就是我前面文章中介紹的量化算法,對應論文《Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference》。

這篇文章相比前面介紹的那些,優點在於它考慮到更加現實的落地需求,針對 8bit 這樣的量化要求提出了一套更加有效的量化策略,盡可能保證精度,保證落地的效果 (畢竟大家更多的還是需要在 8bit 上把精度做到最高)。

回顧一下,這篇論文提出的量化策略是把浮點權重和 feature 通過線性映射量化到 [0, 255] 的區間 (對於 8bit 來說)。我們重點對比一下在前向傳播時和之前文章的區別 (由於是全量化流程,因此只對比 Bengio 的 BNN 網絡)。

上圖就是 BNN 前向傳播的基本思路。正如前文介紹的,BNN 其實就是把 input 和 weight 分別量化后,再做卷積或者其他運算,得到的 output 也同樣是量化的,這個 output 會繼續和后面的量化 weight 進行下一步操作。

而 Google 的文章相比而言,在 output 的地方多了一些額外的操作:

上圖是 Google 論文里面卷積在量化推理時的流程 (不熟悉的同學請查閱我之前的文章)。可以看到,和 BNN 最主要的差異其實是在 output 的時候會有一個 bitshift 並乘上一個定點小數「fixed multiplier」。為什么要多這一步呢?

我的理解是這樣的,量化其實是把 weight 和 input 從浮點這個 domain 轉換到另一個 domain (int8,或者 2bit 等)。在 BNN 網絡中,每一層的 output 受 weight 和 input 的影響,也從浮點域被轉換到定點域,然后這個 output 作為下一層的 input 繼續影響下一層的數值分布 (或者叫概率分布),如此下去,就導致整個網絡的輸出和原先浮點輸出相比差了十萬八千里。但是,在參數更新的時候,又是在浮點權重上更新的,對於復雜網絡來說,優化上難度極大。

而 Google 的工程師們在 output 后面接了一個 bitshift + fixed multiplier 的操作,其實是把每一層量化的輸出和原先的浮點結果保持在一個可控的線性映射上 (看過我之前的文章的話,你就會明白,這里面每一層量化的輸出和原來的浮點輸出相比,都可以通過一個 scale 和 zeropoint 進行換算)。由於每一層的量化結果都只是浮點結果的線性映射,因此整個網絡的輸出結果在概率分布上和原先的浮點運算結果相差不會很大,對網絡優化和性能的保持有很大作用。

除此以外,很多同學對 Google 在量化訓練時采用的偽量化 (論文稱 simulated training,tflite 里面叫做 fake quantize,如下圖所示) 表示不理解。其實這一步也是為了讓網絡的優化更加簡單。細想一下,按照 Google 給出的量化推理步驟,這里面從浮點到定點的轉換中,誤差主要來自量化函數 (round) 的數值截斷以及后面的 bitshift + fixed multiplier,其中,又以截斷誤差最為嚴重。因此,我們就在量化訓練的前向傳播中加上這一步 (即 fake quantize 中 float->int 這一步)。

那為什么又要反量化回 float 呢?這不是多此一舉?我在之前的文章中提到過 Google 論文里的一個公式:

\[S_3(q_3^{i,k}-Z_3)=\sum_{j=1}^{N}S_1(q_{1}^{i,j}-Z_1)S_2(q_2^{j,k}-Z_2) \tag{1} \]

這其實是 Google 量化方案的總綱領。這里面的 \(q_1\)\(q_2\)\(q_3\) 分別是量化后的 input、weight、output,通過 \(S\)\(Z\) 可以換算回對應的浮點數。而反量化就是為了保證量化訓練的時候,得到的 output 可以對應這里的 \(S_3(q_3^{i,k}-Z_3)\)。這和前面說的 bitshift + fixed multiplier 是呼應的。前者保持數值在浮點域 (方便量化訓練),后者保持數值在定點域,而彼此之間可以通過 \(S\)\(Z\) 換算得到。這也是這篇文章區別於 BNN 的地方。

當然,如果量化到 2bit 或者 3bit 等極端情況,這種量化訓練的方法也會存在很大困難,但工業界也不追求這種極端的量化,只要保證 int8 的效果能趕上全精度網絡就行。

這里要多說一句,在大部分量化訓練框架中,如果在 python 里面對網絡進行量化,那么一般對應的是訓練時的偽量化,換句話說,並不是真正意義上的量化。一般需要用 C/C++ 等更底層的語言部署到 DSP/FPGA 等硬件上面運行,才能得到真正的量化效果 (包括速度和精度)。偽量化的精度一般會略微高於真實量化,原因在於偽量化只模擬了截斷帶來的損失,但其實 bitshift + fixed multiplier 這一步也會有一點損失,加上底層代碼在實現上和前端的 python 代碼可能會存在一些差異,導致實際部署的時候,量化效果會稍差於偽量化的效果 (在一些底層視覺任務中尤其明顯,比如圖像修復等)。

后量化時代

Google 這一套量化方案成了工業界事實上的量化標准 (畢竟人家在 tflite 中搶先一步落地了,所以很多廠商都選擇跟進,早就是優勢),后來不少公司,包括學術界的一些改進都是在這套框架下進行的。這里說的后量化時代,指的也是在 Google 這套框架下進行的各種改進研究。

按照這套量化方案的理論,只要找出網絡中每一層 weight 以及 feature (叫 activation 也行) 的 scale 和 zero point,就可以將全精度的模型轉換為量化的模型。因此,這套量化方案下的量化算法分兩種:后訓練量化 (post training quantization,簡稱 PTQ) 和量化感知訓練 (quantization aware training,簡稱 QAT)。前者就是在不重新訓練的前提下,直接找出 scale 和 zero point,操作起來相對方便友好 (最新的一些
算法也會修改一些網絡權重,典型如高通的 DFQ)。而后者需要在網絡中加入 fake quantize 節點進行量化訓練,操作起來相對繁瑣,但效果會更好 (一般來說,QAT 的效果決定了 PTQ 的上界)。

最后

這篇文章從早期量化算法的研究談起,簡單梳理了神經網絡量化的發展脈絡,並引申到目前工業界量化方案中的諸多改進點。扯了這么多,基本是我個人的一些感悟和思考,如有雷同,三生有幸,如不苟同,后台坐等撕逼。

后面的文章中,我會重點介紹一些比較有效的 PTQ 和 QAT 算法,感興趣的讀者歡迎一鍵三連等發車。

歡迎關注我的公眾號「大白話AI」,立志用大白話講懂AI


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM