LLM大模型:推理最佳化-模型int8量化

第七子007發表於2024-08-03

  前面介紹的推理最佳化方法都有缺陷:

  • knowledge distillation:需要樣本資料訓練student,同時要大量算力更新student引數
  • vLLM:透過page attention,減少視訊記憶體浪費,充分利用碎片化的視訊記憶體,並未減少算力

  以上兩種推理最佳化的方式都有缺陷,為了彌補上述缺陷,需要新的推理最佳化方式!

  transformer架構中,不論是embedding,還是FFN,核心都是矩陣乘法,所以這裡選個layer看看weight的分佈,如下:

  很明顯的(-1,1)之間的正太分佈,而且全是浮點數,除了0就沒整數了,這就是weight的現狀!面對大量的浮點數,怎麼提速計算速度,並且減少視訊記憶體佔用了?既然存的都是浮點數,自然要想辦法對浮點數進行最佳化了!

  • 浮點數儲存:fp32需要4byte、fp16需要2byte

  • 浮點數計算:對階、尾數運算、規格化、舍入處理、溢位判斷

  浮點數儲存和計算怎麼看都覺得”費事“!既然浮點數這麼難纏,能不能用整數替代浮點數了?還有,如果不能用整體替代浮點數做運算,也找不到其他合適的數了,現在只能硬著頭皮想辦法換算成整數了!

  • 整數儲存:空間明顯比浮點數少,普通整數只需要8bit
  • 整數計算:整數計算直接XOR異或,根本不需要像浮點數那樣經歷5個階段,速度快很多

  又該怎麼把浮點數對映轉換成整數了?浮點數和整數完全是兩種不同的數啊!先回顧一下:機器學習很重要的一個步驟就是矩陣乘法,為啥要用矩陣乘法了?用幾何解釋就是把向量透過矩陣乘法對映到新的空間,這個空間就是矩陣的列向量組成的!在新空間可以根據業務需求進一步做各種操作,最常見的就是分類和迴歸了!按照這個思路,就需要把浮點數對映到整數這邊來計算了!使用整數的初衷是節約視訊記憶體,所以整數的bit不能過多,否則就沒意義了,不如直接使用fp16;所以整數最多隻能用8bit,簡稱int8;int8還要表示正負數,所以整個範圍就在-2^7~2^7之間,也就是-127~127之間!最核心的一步就是想辦法把浮點數對映到這個範圍內了,這類似的功能是不是很熟悉了?以前做傳統機器學習,對於輸入的特徵資料,為了防止過擬合,提升泛化能力,大部分模型都要求做normalization,把輸入的每個特徵都壓縮在某個特定的範圍內(一般是-1到1之間),和這裡的浮點數轉到整數特定範圍的應用場景剛好一樣啊!

  1、量化方式

  (1)對稱量化:找到一組float資料中最大的數,除以127,得到縮放比例scale,然後每個數都除以scale後四捨五入取整(這裡是反量化後誤差的根因),就得到整數的隱射了;反量化還原就乘以scale即可,舉例如下:

   用對稱量化做矩陣乘法:輸入x和權重w都做量化相乘,如下:可以直觀感受到原始結果和量化結果之間的誤差

  (2)對稱量化的精度不高,還有提升的需求。使用同樣的scale思路,對映到0~255之間了?如下:反量化後誤差明顯比對稱量化小

  非對稱量化矩陣乘法:量化結果相比對稱量化,確實精確一些

  2、量化的方式有了,那什麼時候量化了?量化本質就是把浮點數對映成整數後做計算,所以只要涉及到浮點數運算的地方都可以量化後再計算,完成後透過反量化還原即可!從量化的環節看,分以下兩種方式:

  (1)訓練後動態量化:

  • 將訓練好的模型權重量化為int8,並儲存量化引數,
  • 在模型推理時,對每一層輸入的fp32啟用值,動態進行進行量化為int8:
  • 在每一層對量化後的int8權重和int8啟用值進行計算。
  • 在每一層輸出時將結果反量化為fp32。
  • 將fp32啟用值傳入到下一層。

  流程示意如下:

  這種方式有明顯缺陷:

  • 每一次推理每一層都要對輸入統計量化引數,比較耗時;
  • 每一層計算完都轉化為fp32,存入視訊記憶體,佔用視訊記憶體頻寬

 (2)訓練後靜態量化:針對訓練後動態量化的缺陷,改進的思路如下:

  • 每一次推理每一層都要對輸入統計量化引數,比較耗時:用有代表性的輸入資料跑一遍整個網路,透過統計得到每層大概得量化引數
  • 每一層計算完都轉化為fp32,存入視訊記憶體,佔用視訊記憶體頻寬:這一層的輸出是下一層的輸入,下一層還是要量化,不如在這層直接量化好再傳給下一層,計算量是一樣的,但是傳輸的資料量少了很多,節約頻寬

  具體操作方法:

  • 將訓練好的模型權重量化為int8,並儲存量化引數。
  • 校準(calibration): 選一些樣本模擬推理,求出每層啟用函式各自的scale、zero_point,後續每次推理時輸入資料x都用這次得到的scale、zero_point,不用每次都重複計算scale、zero_point了,減少計算量
  • 在每一層對量化後的int8權重和int8啟用值進行十算。
  • 在每一層輸出時將結果反量化為fp32,同時根據校準產生的啟用值量化引數,把啟用值量化為int8,把量化引數放入量化後的啟用值中;這裡先反量化後再次量化,不是多此一舉麼?
    • 神經網路或者說深度學習有多層,每層處理的語義是不同的,所以每層都要單獨量化,不能直接用上一層的量化傳遞給下一層
    • 每層的量化範圍不同,直接把上層int8的結果輸出給下一層,可能導致數值範圍不匹配
    • 所以每層輸出先反量化,讓層與層之間統一量綱(以浮點數為準),然後再次量化後傳入下一層
  • 將int8的啟用值和它的量化引數傳入到下一層。

  流程示意如下:

  3、huggingface的transformer庫中也有可以直接使用量化框架:LLM.int8() 混合精度量化;在不同引數量的模型上,使用不同的量化位數,其準確率如下(原論文:https://arxiv.org/pdf/2208.07339):

  引數超過6.7B時,LLM.int8()的準確率和原模型驚人地保持一致,並未降低,這個量化方法到底是怎麼做的了?中國人有句古話:林子大了什麼樣的鳥都有!這話同樣適用於LLM:一旦模型引數量達到數十億級別,引數中就會出現一些離群點,也就是值比較大的維度;這些維度的值較大,應該是模型學到的比較重要的特徵維度,俗稱Emergent feathers。先看一個例子:原始特徵有些權重比較大,經過量化後再反量化時,會讓其他部分維度清零,資訊嚴重丟失!如果強行忽略這些維度值較大的特徵,其他維度倒是保持不變了,但重要的特徵資訊也沒了,會嚴重影響下游的任務準確性

  

  現在面臨的情況是:值較大的維度去也不是,不去也不是,怎麼辦?既然放在一起量化和反量化不行,要不然分而治之?把較小值的維度和較大值的維度分開處理試一試?比如下面的矩陣:

  左邊是weight,明顯有outlier feature(原論文設定的閾值>=6),右邊是input;按照以前的放在一起、鬍子眉毛一把抓的量化思路,權重小的部分維度量化後可能就沒了。新的處理方法如下:

  • 從輸入的隱含狀態中,按列提取異常值 (即大於某個閾值的值)。
  • 對 FP16 離群值矩陣和 Int8 非離群值矩陣分別作矩陣乘法。
  • 反量化非離群值的矩陣乘結果並其與離群值矩陣乘結果相加,獲得最終的 FP16 結果。

  上述的weight中,第2列和第4列權重明顯大,把這兩列單獨踢出來;input對應的2、4兩行也單獨踢出來,組成新矩陣,分別相乘,如下:

  weight小的權重才量化,weight大的權重直接浮點數相乘就行了!我個人認為weight大的不用量化:

  • weight大的特徵肯定是少數,看看文章開頭那張weight分佈圖唄!
  • weight小的數量多,量化效果明顯

  注意:LLM.int8()雖然精度沒降低,但因為要把矩陣拆開,分別計算,所以過程比較複雜,推理的效率和原模型比甚至是降低的!

  使用的方式也簡單,transformer包裡面已經整合好了:事先安裝好BitsAndBytesConfig就行:

 quantization_config = BitsAndBytesConfig(load_in_8bit=True)
    model = AutoModelForCausalLM.from_pretrained(
        base_model,
        device_map=device,
        trust_remote_code=True,
        quantization_config=quantization_config,
        # torch_dtype=torch.float16
    )

   tips:經常做各種LLM的嘗試,需要依賴各種第三方包。為了避免包或版本衝突,還是用虛擬環境吧,然後就是一路上各種缺包,各種補齊!

參考:

1、https://www.bilibili.com/video/BV1EE42157Ms/?spm_id_from=333.337.search-card.all.click&vd_source=241a5bcb1c13e6828e519dd1f78f35b2 大模型部署推理最佳化

2、https://www.bilibili.com/video/BV1FH4y1c73W/?spm_id_from=333.788.recommend_more_video.4&vd_source=241a5bcb1c13e6828e519dd1f78f35b2 bitsandbytes、GPTQ、GGUF、AWQ

  https://github.com/chunhuizhang/llm_inference_serving/blob/main/tutorials/quantization/qlora_gptq_gguf_awq.ipynb

  https://github.com/chunhuizhang/llm_inference_serving/blob/main/tutorials/quantization/basics.ipynb

3、https://www.cnblogs.com/yilang/p/11277201.html 浮點數計算

4、https://huggingface.co/blog/zh/hf-bitsandbytes-integration 大規模 Transformer 模型 8 位元矩陣乘簡介 - 基於 Hugging Face Transformers、Accelerate 以及 bitsandbytes

相關文章