本文基本參考自這篇文章:8-Bit Quantization and TensorFlow Lite: Speeding up mobile inference with low precision
首先來一段keras dalao Francois Chollet的雞湯:
- make it possible
- make it work
- make it efficient
- make it dependable and invisible
- move on to next layer and think about it again
這段雞湯已經把8-bit量化的需求和階段已經說得很清楚了:提升算法效率
What is 8 bit
目前DL中大部分都是用32bit float類型進行計算的,bit位數的多少直接限制了數據類型能夠表達的數據范圍,比如float 32的數據是由1bit表示符號,8bit表示整數部,23位表示分數部組成。
num bits | Min value | Max value |
---|---|---|
8 bit | -128 (-2^7) | 128 (x^7) |
16 bit | -32768 (-2^15) | 32768(2^15) |
32 bit | –2147483648 (-2^31) | –2147483648 (2^31) |
用更低位的數值類型意味着更小的數據表示范圍和更稀疏的數值,量化的時候就會造成數值精度損失。比如要把float數值量化到int類型,那么首先小數部分會損失,而那些超過int類型能夠表達的范圍的值也會被壓縮到int能夠表達的最大或最小值。
Why 8 bit?
那么既然會有精度損失那為啥現在int8運算越來越流行,主要原因就是快跟省了。
-
快:低bit位的數值計算一般會比高bit的要快。雖然現代計算芯片上浮點型的計算已經並不比int類型慢,但是這主要是設計了專用的float計算核,而現在很流行的ai芯片和一些嵌入式芯片上一般並不會設計很多的float計算核,因此對float數值的計算算力很低算力。
下圖就是Nvidia RTX2080ti的芯片架構(ref)

- 省:從32bit壓縮到8bit,最直接的就是內存能夠減少1/4。同時,從RAM中讀取數據的時間也會縮短;也能降低運算能好。
這也就說明了為什么現在越來越多的ai芯片專門針對int8計算進行優化並提供很大的int8算力,比如RK3399 pro搭載的NPU提供3T int8算力。
Why 8 bit works?
那么為什么int8在DL模型中能夠應用呢?不是有數值精度損失么?主要原因有兩個:
- 訓練好的DNN網絡時出了名的對噪聲和擾動魯棒性強。
- 大部分訓練好的權重都落在一個很小的區間內。
一般正常操作的話,8 bit量化只會造成很低的精度損失,而且這個損失是可以通過微調重訓練進行彌補的。比如在Han等人在這篇文章里對AlexNet其中一層的權重進行分析:
<imge="https://cdn-images-1.medium.com/max/1600/0*UkgbJuMdr6eOBjux.png" style="zoom:50%"/>
左圖是實際權重,大部分分布在-0.1到0.1的范圍內,而右圖是進行了4bit量化后的權重數值分布,4bit能夠最大表示16個數值,因此大部分權重都有塌縮,能夠保持原來的值的只有16個值。那么如果進行8bit的量化,最大能夠保持256個值,對原始權重的保留會更加完整,量化造成的數值損失會很小。
雖然目前已經有文章開始研究直接用低精度的數值進行訓練,比如這個,但是需要替換乘法操作,訓練非常復雜,而且效果也暫時不夠好,還是處於實驗階段。這主要是目前的SGD等算法需要不斷積累小非常小的梯度值進行權重更新。
How 8 bit works?
那么如何用int類型來表示float類型呢?最簡單的方式就是乘一個系數把float類型的小數部分轉換成整數部分,然后用這個轉換出來的整數進行計算,計算結果在還原成float。類似的,量化具體需要以下幾點:
- 量化的變換必須是線性的,這樣才能確保計算結果能夠映射會原始值
- 量化必須能夠保持0.f的精度,這是因為0在DNN中作用很大,如果原始的0映射到量化后的值變成了其他值並出現了精度損失,那個在計算過程中就會引入偏差。
因此對於實際值和量化值的映射關系,一般可以用以下公式表示:
\(r= (r_{max}-r_{min})/(2^B-1)-0*(q-z)\)
其中,r表示實際值;q表示量化的比特數,比如int8量化就是8;z表示量化后的0點值。
具體的映射關系如下:
從公式中可以看到,量化的重要一點就是要確定合適的\(r_{max}\)和\(r_{min}\).對於訓練好的模型就行post-training 量化來說,這點比較容易,只需要統計凍存的所有權重參數即可。
Post training quantization
一般來說,凍好的模型中典型的conv層包含以下參數:
- weights tensor
- input tensor
- forward pass operator
- output tensor
對輸出來說,大部分層輸出的值都只會落在一個很窄的區間內,因此對output進行量化就需要利用在在訓練的時候統計大部分輸入得到的輸出來進行統計確定合適的最大和最小值。
但是對於operation來說,直接利用之前的量化方式需要注意一點:由於計算包括乘法,因此有可能會有計算結果的值溢出量化值(比如int8)所能表達的范圍(overflow)。因此這里常用的方法是首先將結果用較大數值空間的量化值(比如int32)進行存儲,然后再量化到8 bit。采用int32至少可以完全覆蓋原始float類型運算的結果而不擔心溢出。
此外,對於一些操作,其邏輯需要改變。比如ReLU,量化后需要比較的值是quantize(0)而不是原來的0.f。
如上圖所示,權重,輸入首先進行量化,其中輸入的量化范圍是需要訓練數據作為參考;而輸出進行兩步量化,來兼容計算過程可能出現的溢出。
Fake Quantization in TFLite
在TensorFlow中,量化是通過fake quantization node來進行的。對於大模型來說,冗余參數比較多,直接量化的影響比較小;但是對於小模型來說,冗余參數就比較少了,直接量化導致的 精度損失可能會比較大。在fake quantizaiton中,訓練過重就就會模擬評估量化帶來的round effect在inference的時候的影響,因此在訓練過程中量化值還是會議float類型保存,並可以通過反向傳播進行調整。具體quantization aware training可以查看這里.
此外,就如之前所說的fake quantizaiton node會記錄計算和輸出值的范圍用於量化。
Result
下表記錄了量化造成的精度損失,總體來看還是非常小的。
What's next
到這位置,只是介紹了8bit量化在TF上的實現原理和方式。而現在學術界對於量化有很多新的研究包括:quantized training, non-linear quantization, binary quantization, networks without multipliers等等,在不久的將來希望能夠出現無損且高效的量化手段,這將極大收益訓練和推測。