量化究竟做了什麼
量化把連續的浮點數值映射到一個小小的整數格點上。一個 16 位元浮點權重矩陣,會變成一個低位元整數矩陣,外加少數幾個浮點「縮放係數(scale)」,告訴你如何把格點拉回真實數字。標準的仿射(affine)方案,會為每一組權重儲存一個縮放係數與一個零點(zero-point);執行時把整數乘以縮放係數,就能還原出原始值的近似。
# affine quantize / dequantize for one group scale = (w.max() - w.min()) / (qmax - qmin) zero = round(qmin - w.min() / scale) q = clamp(round(w / scale) + zero, qmin, qmax) # store: int w_hat = (q - zero) * scale # reconstruct
仿射量化:每個值按每組的比例 s 與零點 z 四捨五入到整數網格,再用 s(q−z) 還原。
尺寸上的收穫是機械式的:4 位元權重只佔 16 位元的四分之一位元組,所以一個 7B 模型從約 14 GB 降到約 3.5 GB。由於解碼是記憶體受限,這也約略是 4 倍的頻寬收穫與真實的加速——前提是你有能直接在打包整數上做矩陣乘法的核心。
困難之處:離群值
如果所有數值都均勻分布,量化會易如反掌。麻煩在於大型語言模型會長出少數激活離群值(activation outlier)——某些通道的數量級是其餘的 10 到 100 倍。單一個巨大值就會迫使縮放係數變得粗糙,浪費了同組中其他每個值的精度。天真地把激活值量化到 8 位元,可能因為這些尖峰而讓模型準確度完全崩潰。
SmoothQuant以一個漂亮的代數技巧處理這點:既然線性層計算的是「激活值乘權重」,你可以把激活值除以一個逐通道因子、同時把權重乘上同一因子,而不改變輸出。選定這個因子,把難以量化的離群困難「遷移」出激活值、轉進更能容忍它的權重。平滑之後,兩邊都能乾淨地量化到 INT8。
SmoothQuant 的代數技巧:將激活按每通道因子相除、權重相乘,把離群值的難度從激活 X 轉移到權重 W。
一次性訓練後量化:GPTQ 與 AWQ
訓練後量化(post-training quantization, PTQ)只用一小批「校準(calibration)」資料——數百個未標註樣本——且不做任何梯度更新,就能壓縮一個已訓練好的模型。在 4 位元純權重這個領域,有兩種方法佔主導。GPTQ逐欄量化權重,每固定一欄後,便用近似的二階(Hessian)資訊去「更新尚未量化的權重」,讓它們補償剛引入的誤差。其結果遠比獨立四捨五入每個權重來得準確。
GPTQ 背後的 PTQ 目標:逐層量化,在小型校準集 X 上最小化原始輸出與量化輸出之間的平方誤差。
AWQ(激活感知權重量化,activation-aware weight quantization)則從另一個觀察出發:並非所有權重同等重要。乘上大數量級激活通道的那些權重是「顯著的(salient)」——把它們量化得差會傷害甚大。AWQ 在校準集上量測激活統計、辨識出顯著的權重通道,並在量化前把它們放大,使其保有更多有效精度。它比 GPTQ 更簡單、沒有反向傳播,且在不同模型家族間往往更穩健。
當 PTQ 不夠時:QAT、三元權重與 KV 快取
低於 4 位元後,訓練後方法開始失靈,你需要讓模型「學會」對量化保持穩健。量化感知訓練(quantization-aware training, QAT)在訓練的前向傳播中插入假量化(fake-quantize)運算,使權重漂向經得起四捨五入的數值;梯度則透過直通估計器(straight-through estimator)穿過不可微的捨入運算。QAT 要付出一次訓練的代價,但能挽回 PTQ 救不回的準確度。
這個想法的極端是 BitNet,它從零開始訓練 transformer,把權重限制在三個值——每個約 1.58 位元。三元權重矩陣把矩陣「乘法」變成矩陣「加減法」,徹底拿掉乘法器、大幅削減能量。其中的通則是:精度最低的模型並非事後量化而來,而是天生就是低精度。
最後,別忘了長上下文解碼中另一個吃記憶體的大戶:注意力快取。KV 快取量化(KV-cache quantization)把鍵(key)與值(value)以 4 或 8 位元儲存;一旦上下文成長到數萬詞元,這可能比權重精度更關鍵,因為快取隨序列長度線性增長。
注意力作為查詢—鍵—值查找的示意圖,突顯 KV 快取中保存的鍵與值。