Optimum 文件

量化

您正在檢視的是需要從原始碼安裝。如果您想透過 pip 常規安裝,請檢視最新的穩定版本(v1.27.0)。
Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

量化

量化是一種透過使用低精度資料型別(如8位整數 int8)代替通常的32位浮點數(float32)來表示權重和啟用值,從而降低執行推理的計算和記憶體成本的技術。

減少位元數意味著最終的模型需要更少的記憶體儲存,消耗更少的能源(理論上),並且像矩陣乘法這樣的操作可以用整數算術更快地執行。它還允許在嵌入式裝置上執行模型,這些裝置有時只支援整數資料型別。

理論

量化背後的基本思想很簡單:將權重和啟用值從高精度表示(通常是常規的32位浮點數)轉換為較低精度的資料型別。最常見的低精度資料型別是

  • float16,累積資料型別為 float16
  • bfloat16,累積資料型別為 float32
  • int16,累積資料型別為 int32
  • int8,累積資料型別為 int32

累積資料型別指定了對該資料型別的值進行累積(加法、乘法等)操作後結果的型別。例如,讓我們考慮兩個 int8A = 127B = 127,並定義 CAB 的和

C = A + B

這裡的結果遠大於 int8 中可表示的最大值 127。因此需要一個更高精度的資料型別來避免巨大的精度損失,否則整個量化過程將變得毫無用處。

量化

兩種最常見的量化情況是 float32 -> float16float32 -> int8

量化到 float16

float32 量化到 float16 的過程非常直接,因為這兩種資料型別遵循相同的表示方案。在將操作量化到 float16 時,你需要問自己以下問題:

  • 我的操作是否有 float16 的實現?
  • 我的硬體是否支援 float16?例如,英特爾 CPU 一直支援 float16 作為儲存型別,但計算是在轉換為 float32 後完成的。完全支援將在 Cooper Lake 和 Sapphire Rapids 中實現。
  • 我的操作對較低精度敏感嗎?例如,LayerNorm 中 epsilon 的值通常非常小(~ 1e-12),但 float16 中可表示的最小正值約為 6e-5,這可能導致 NaN 問題。同樣的問題也適用於非常大的值。

量化到 int8

float32 量化到 int8 更加棘手。int8 只能表示256個值,而 float32 可以表示非常廣泛的值。其思想是找到最佳的方式將我們的 float32 值範圍 [a, b] 對映到 int8 空間。

讓我們考慮一個在 [a, b] 範圍內的浮點數 x,那麼我們可以寫出以下量化方案,也稱為仿射量化方案

x = S * (x_q - Z)

其中

  • x_q 是與 x 關聯的量化後 int8
  • SZ 是量化引數
    • S 是縮放因子,是一個正的 float32
    • Z 稱為零點,它是在 float32 領域中對應於值 0int8 值。這對於能夠精確表示值 0 非常重要,因為它在機器學習模型中無處不在。

[a, b] 範圍內的 x 的量化值 x_q 可以計算如下

x_q = round(x/S + Z)

而在 [a, b] 範圍之外的 float32 值會被裁剪到最接近的可表示值,所以對於任何浮點數 x

x_q = clip(round(x/S + Z), round(a/S + Z), round(b/S + Z))

通常 round(a/S + Z) 對應於所考慮資料型別中可表示的最小值,而 round(b/S + Z) 對應於最大值。但這也可能變化,例如在使用對稱量化方案時,你將在下一段中看到。

對稱和仿射量化方案

上面的方程被稱為仿射量化方案,因為從 [a, b]int8 的對映是仿射的。

這個方案的一個常見特例是對稱量化方案,我們考慮一個對稱的浮點值範圍 [-a, a]。在這種情況下,整數空間通常是 [-127, 127],這意味著 -128 被從常規的 [-128, 127] 有符號 int8 範圍中排除了。原因是擁有對稱範圍可以使 Z = 0。雖然在256個可表示的值中損失了一個,但這可以提供加速,因為可以跳過許多加法操作。

注意:要了解量化引數 SZ 是如何計算的,您可以閱讀 《為高效的純整數算術推理而進行的神經網路量化與訓練》 論文,或 Lei Mao關於該主題的部落格文章

逐張量和逐通道量化

根據您所追求的精度/延遲權衡,您可以調整量化引數的粒度。

  • 量化引數可以按逐張量計算,這意味著每個張量將使用一對 (S, Z)
  • 量化引數可以按逐通道計算,這意味著可以為張量的某個維度上的每個元素儲存一對 (S, Z)。例如,對於一個形狀為 [N, C, H, W] 的張量,為第二個維度設定逐通道量化引數將導致有 C(S, Z)。雖然這可以提供更好的精度,但需要更多記憶體。

校準

上一節描述了從 float32int8 的量化過程,但還有一個問題:float32 值的 [a, b] 範圍是如何確定的?這就是校準發揮作用的地方。

校準是量化過程中計算 float32 範圍的步驟。對於權重來說,這很簡單,因為實際範圍在量化時是已知的。但對於啟用值來說,情況就不那麼明朗了,存在不同的方法:

  1. 訓練後動態量化:每個啟用值的範圍在執行時動態計算。雖然這種方法無需太多工作就能獲得很好的結果,但由於每次計算範圍會引入開銷,因此可能比靜態量化稍慢。在某些硬體上,它也不是一個可行的選項。
  2. 訓練後靜態量化:每個啟用值的範圍在量化時預先計算好,通常是透過將代表性資料透過模型並記錄啟用值來實現。實踐中的步驟是:
    1. 在啟用值上放置觀察器以記錄其值。
    2. 在校準資料集上進行一定數量的前向傳播(大約 200 個樣本就足夠了)。
    3. 根據某種校準技術計算每個計算的範圍。
  3. 量化感知訓練:每個啟用值的範圍在訓練時計算,其思想與訓練後靜態量化相同。但使用“偽量化”運算元代替觀察器:它們像觀察器一樣記錄值,但同時也模擬量化引起的誤差,讓模型適應它。

對於訓練後靜態量化和量化感知訓練,都需要定義校準技術,最常見的有:

  • 最小值-最大值:計算的範圍是 [觀察到的最小值, 觀察到的最大值],這對於權重效果很好。
  • 移動平均最小值-最大值:計算的範圍是 [移動平均觀察到的最小值, 移動平均觀察到的最大值],這對於啟用值效果很好。
  • 直方圖:記錄值的直方圖以及最小值和最大值,然後根據某個標準進行選擇。
    • 熵:計算的範圍是使全精度資料和量化資料之間誤差最小的範圍。
    • 均方誤差:計算的範圍是使全精度資料和量化資料之間均方誤差最小的範圍。
    • 百分位數:使用給定的百分位值 p 對觀察值計算範圍。其思想是嘗試使 p% 的觀察值在計算的範圍內。雖然在進行仿射量化時這是可能的,但在進行對稱量化時並不總能精確匹配。您可以檢視 ONNX Runtime 中的實現方式 以瞭解更多細節。

將模型量化到 int8 的實際步驟

為了有效地將模型量化到 int8,需要遵循以下步驟:

  1. 選擇要量化的運算元。適合量化的運算元是那些在計算時間上占主導地位的,例如線性投影和矩陣乘法。
  2. 嘗試訓練後動態量化,如果速度足夠快,則到此為止,否則繼續第3步。
  3. 嘗試訓練後靜態量化,這可能比動態量化更快,但通常會降低精度。在您想要量化的地方為模型應用觀察器。
  4. 選擇一種校準技術並執行。
  5. 將模型轉換為其量化形式:移除觀察器,並將 float32 運算元轉換為其 int8 對應物。
  6. 評估量化後的模型:精度是否足夠好?如果是,則到此為止,否則從第3步重新開始,但這次使用量化感知訓練。

在 🤗 Optimum 中執行量化支援的工具

🤗 Optimum 提供了 API,可以使用不同的工具針對不同的目標執行量化:

  • optimum.onnxruntime 包允許使用 ONNX Runtime 工具量化和執行 ONNX 模型
  • optimum.intel 包能夠量化 🤗 Transformers 模型,同時滿足精度和延遲約束。
  • optimum.fx 包提供了對 PyTorch 量化函式 的封裝,以允許在 PyTorch 中對 🤗 Transformers 模型進行圖模式量化。與上述兩種相比,這是一個更低階的 API,提供了更大的靈活性,但需要您做更多的工作。
  • optimum.gptq 包允許使用 GPTQ 量化和執行 LLM 模型

更進一步:機器如何表示數字?

本節對於理解其餘部分並非至關重要。它簡要解釋了數字在計算機中的表示方式。由於量化是從一種表示轉換為另一種表示,瞭解一些基礎知識可能會有所幫助,但這絕不是強制性的。

計算機最基本的表示單位是位元。計算機中的一切都表示為位元序列,包括數字。但根據所討論的數字是整數還是實數,其表示方式有所不同。

整數表示

整數通常用以下位元長度表示:8163264。在表示整數時,考慮兩種情況:

  1. 無符號(正)整數:它們簡單地表示為位元序列。每個位元對應於2的冪(從 0n-1,其中 n 是位元長度),最終的數字是這些2的冪的和。

例如:19 表示為一個無符號的 int8 是 00010011,因為

19 = 0 x 2^7 + 0 x 2^6 + 0 x 2^5 + 1 x 2^4 + 0 x 2^3 + 0 x 2^2 + 1 x 2^1 + 1 x 2^0
  1. 有符號整數:表示有符號整數不那麼直接,存在多種方法,最常見的是二進位制補碼。更多資訊,您可以檢視關於該主題的 維基百科頁面

實數表示

實數通常用以下位元長度表示:163264。表示實數的兩種主要方式是

  1. 定點數:保留固定數量的數字來表示整數部分和小數部分。
  2. 浮點數:用於表示整數部分和小數部分的數字數量可以變化。

浮點表示法可以表示更大範圍的值,這是我們將重點關注的,因為它最常用。浮點表示法有三個組成部分

  1. 符號位:這是指定數字符號的位元。
  2. 指數部分
  • float16 中有 5 位元
  • bfloat16 中有 8 位元
  • float32 中有 8 位元
  • float64 中有 11 位元
  1. 尾數部分
  • float16 中有 11 位元(10 個顯式儲存)
  • bfloat16 中有 8 位元(7 個顯式儲存)
  • float32 中有 24 位元(23 個顯式儲存)
  • float64 中有 53 位元(52 個顯式儲存)

有關每種資料型別的位元分配的更多資訊,請檢視維基百科上關於 bfloat16 浮點格式 的精美插圖。

對於一個實數 x,我們有

x = sign x mantissa x (2^exponent)

參考文獻

< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.