Meta

Tag: #articles/tags/Computer_Science__Machine_Learning, #articles/tags//unread, #articles/tags/⭐⭐⭐⭐⭐, #articles/tags/llm/prune, #articles/tags/llm/MoE
citekey: frantarSparseGPTMassiveLanguage2023
authors: #articles/authors/Elias-Frantar, #articles/authors/Dan-Alistarh
year: 2023/03/22
doi: 10.48550/arXiv.2301.00774
url: http://arxiv.org/abs/2301.00774
conference: #articles/conference/
Journal : #articles/journals/

Abstract

We show for the first time that large-scale generative pretrained transformer (GPT) family models can be pruned to at least 50% sparsity in one-shot, without any retraining, at minimal loss of accuracy. This is achieved via a new pruning method called SparseGPT, specifically designed to work efficiently and accurately on massive GPT-family models. We can execute SparseGPT on the largest available open-source models, OPT-175B and BLOOM-176B, in under 4.5 hours, and can reach 60% unstructured sparsity with negligible increase in perplexity: remarkably, more than 100 billion weights from these models can be ignored at inference time. SparseGPT generalizes to semi-structured (2:4 and 4:8) patterns, and is compatible with weight quantization approaches. The code is available at: https://github.com/IST-DASLab/sparsegpt.

Note

1. optimization problem && close form solution

1. 原问题是什么?

论文式 (1) 是

argminmask M, W^WX(MW^)X22.\arg\min_{\text{mask } M^\ell,\ \hat W^\ell} \|W^\ell X^\ell - (M^\ell \odot \hat W^\ell)X^\ell\|_2^2.

这里:

  • WW^\ell:第 \ell 层原始权重矩阵
  • XX^\ell:该层输入样本矩阵
  • MM^\ell:二值 mask,0 表示剪掉,1 表示保留
  • W^\hat W^\ell:剪枝后待优化的权重
  • \odot:逐元素乘法

1.1. 固定 MM 后,变量只剩什么?

一旦 mask MM 固定,哪些位置能非零就确定了。此时问题就只剩:

minW^WX(MW^)XF2.\min_{\hat W} \|WX - (M\odot \hat W)X\|_F^2.

这里更准确地写成 Frobenius 范数比较合适,因为输出是矩阵。论文原文写成 22\|\cdot\|_2^2,但本质上它是在做整个层输出的平方误差最小化;在行拆分后就是普通向量二范数平方。


2. 分解“每一行独立求解”?

  • WRdrow×dcolW \in \mathbb{R}^{d_{\text{row}}\times d_{\text{col}}}
  • XRdcol×nX \in \mathbb{R}^{d_{\text{col}}\times n}

WXRdrow×n.WX \in \mathbb{R}^{d_{\text{row}}\times n}.

WW 按行写成 wiw_i,则第 ii 行输出是

wiXR1×n.w_i X \in \mathbb{R}^{1\times n}.

同理,压缩后第 ii 行输出是

(Miw^i)X.(M_i\odot \hat w_i)X.

所以整体目标可以拆成

WX(MW^)XF2=i=1drowwiX(Miw^i)X22.\|WX-(M\odot \hat W)X\|_F^2 = \sum_{i=1}^{d_{\text{row}}} \|w_iX-(M_i\odot \hat w_i)X\|_2^2.

结论:固定 mask 后,每一行可以独立求解。

  • 对应 mask:Mi{0,1}1×dcolM_i \in \{0,1\}^{1\times d_{\text{col}}}

设保留下来的列索引集合为

Si={j:Mij=1}.S_i=\{j: M_{ij}=1\}.

把这些保留位置抽出来:

  • wi,Siw_{i,S_i}wiw_i 在保留位置上的子向量
  • XSiX_{S_i}:输入矩阵 XX 中对应这些特征的子矩阵

那么因为被剪掉的位置必须为 0,所以优化变量只剩保留位置上的权重。记它为 uu。问题变成

minuwiXuXSi22.\min_u \|w_iX-uX_{S_i}\|_2^2.

这就是一个标准线性最小二乘。

3. 具体求解步骤:正规方程

现在我们来严格推导闭式解。

Step 1:定义目标函数

yi:=wiXR1×n,y_i := w_iX \in \mathbb{R}^{1\times n},

则问题是

minuyiuXSi22.\min_u \|y_i-uX_{S_i}\|_2^2.

把它写成转置更接近标准回归形式也可以:

minuyiXSiu22.\min_{u^\top} \|y_i^\top - X_{S_i}^\top u^\top\|_2^2.


Step 2:展开目标函数

f(u)=yiuXSi22=(yiuXSi)(yiuXSi).f(u)=\|y_i-uX_{S_i}\|_2^2 =(y_i-uX_{S_i})(y_i-uX_{S_i})^\top.

展开:

f(u)=yiyi2yiXSiu+uXSiXSiu.f(u)=y_iy_i^\top -2y_iX_{S_i}^\top u^\top + uX_{S_i}X_{S_i}^\top u^\top.


Step 3:对 uu 求导并令其为 0

对行向量形式求导,得到正规方程:

uXSiXSi=yiXSi.uX_{S_i}X_{S_i}^\top = y_iX_{S_i}^\top.

也就是

uHSi=yiXSi,其中 HSi:=XSiXSi.uH_{S_i}=y_iX_{S_i}^\top, \quad \text{其中 } H_{S_i}:=X_{S_i}X_{S_i}^\top.

HSiH_{S_i} 可逆,则

u=yiXSi(XSiXSi)1.u = y_iX_{S_i}^\top (X_{S_i}X_{S_i}^\top)^{-1}.

再代入 yi=wiXy_i=w_iX,有

u=(wiX)XSi(XSiXSi)1.u = (w_iX)X_{S_i}^\top (X_{S_i}X_{S_i}^\top)^{-1}.

这就是标准最小二乘解。

4. 如果矩阵不可逆怎么办?

这是一个实际问题,论文在算法部分也用了 dampening:

H1=(XX+λI)1H^{-1}=(XX^\top + \lambda I)^{-1}

说明他们也默认实际中会做正则化稳定求逆。

所以严格来说,如果

XSiXSiX_{S_i}X_{S_i}^\top

不可逆或病态,常见做法是解 ridge 版本:

u=yiXSi(XSiXSi+λI)1.u^* = y_iX_{S_i}^\top (X_{S_i}X_{S_i}^\top+\lambda I)^{-1}.

这个思想和 SparseGPT 后面使用 Hessian dampening 是一致的。

2. OBS

2.1 OBS的本质是删掉后再补偿

聪明的做法是:

  • wmw_m 设成 0
  • 同时调一下别的权重,让整体输出尽量维持不变
OBS 就是在做:

  1. 在“第 mm 个权重必须被删”的约束下,怎样改动整个参数向量,才能让损失增加最少?
  2. 并且由于直接求解闭式解非常昂贵, 与其直接对完整 mask MM 一次性求闭式解,不如把删除过程看成一个个权重依次删掉;每次删一个,都用 OBS 做最优补偿。最终会到达同样的最优解。

2.2 OBS 背后的优化问题是什么?

下面进入核心。

设当前一行权重是列向量 wRdw\in\mathbb{R}^d(这里写列向量更方便推导)。

假设当前 ww 已经是当前损失的一个局部最优点,所以梯度为 0。
ww 附近对损失函数 L(w)L(w) 做二阶近似:

L(w+δ)L(w)+12δHδL(w+\delta) \approx L(w) + \frac12 \delta^\top H \delta

其中:

  • HH 是 Hessian
  • 因为当前点梯度为 0,所以一阶项没了

现在加入删权重约束

如果要删掉第 mm 个权重,意思是更新后满足:

wm+δm=0w_m + \delta_m = 0

注意这里左边的 δm\delta_m 是更新向量第 mm 个分量,不是整向量;为避免符号冲突,我下面把整体更新记为 Δ\Delta

所以约束是:

emΔ=wme_m^\top \Delta = -w_m

其中 eme_m 是第 mm 个标准基向量。

于是问题变成:

minΔ12ΔHΔs.t. emΔ=wm\min_\Delta \frac12 \Delta^\top H \Delta \quad \text{s.t. } e_m^\top \Delta = -w_m

这就是 OBS 的本质优化问题:

最小化二次损失增加,同时强制第 mm 个权重归零。


推导 OBS 更新公式

这是最关键的部分。


Step 1:写拉格朗日函数

L(Δ,λ)=12ΔHΔ+λ(emΔ+wm)\mathcal{L}(\Delta,\lambda) = \frac12 \Delta^\top H \Delta + \lambda (e_m^\top \Delta + w_m)


Step 2:对 Δ\Delta 求导

ΔL=HΔ+λem=0\nabla_\Delta \mathcal{L} = H\Delta + \lambda e_m = 0

所以

Δ=λH1em\Delta = -\lambda H^{-1} e_m

H1emH^{-1}e_m 就是 H1H^{-1} 的第 mm 列,所以

Δ=λH:,m1\Delta = -\lambda H^{-1}_{:,m}


Step 3:用约束解 λ\lambda

约束要求:

emΔ=wme_m^\top \Delta = -w_m

代入上式:

em(λH:,m1)=wme_m^\top(-\lambda H^{-1}_{:,m}) = -w_m

λ[H1]mm=wm-\lambda [H^{-1}]_{mm} = -w_m

所以

λ=wm[H1]mm\lambda = \frac{w_m}{[H^{-1}]_{mm}}


Step 4:得到最终更新

Δ=wm[H1]mmH:,m1\Delta = -\frac{w_m}{[H^{-1}]_{mm}} H^{-1}_{:,m}

这就是论文式 (3):

δm=wm[H1]mmH:,m1\delta_m = -\frac{w_m}{[H^{-1}]_{mm}} H^{-1}_{:,m}

除此之外我们还可以得到:

εm=wm2[H1]mm\varepsilon_m = \frac{w_m^2}{[H^{-1}]_{mm}}

删除某个权重是否“容易”,不只取决于它绝对值小不小,还取决于:

[H1]mm[H^{-1}]_{mm}

如果这个值大,说明这个权重的影响更容易被其他参数补偿,那么删除代价就可能更小。这在后续会用来自适应mask

High level

H1H^{-1}到底代表了什么? 事实上HH代表了未中心化的协方差矩阵, 代表了某两个特征之间的相似程度, 可以类比到PCA的操作. 那么H1H^{-1}实际上代表的是两个向量的差异, 跟mask处差异越大剪枝后需要调整的大, 同时方差还包含信息量的直觉(PCA/LDA)

2.3 Optimal Partial Updates.

img

  • 这个部分是整篇文章最难的理解的部分

  • 事实上并不能证明这样做和原来的OBS求解保持等价

    • 只能说这样做能保持最优的部分更新

3. full algorithm

img

  • 每隔BsB_s个step就进行一次OBS检测和更新, 其中这里是自适应选择mask是通过选择保留εm\varepsilon_m最大的.
  • step freeze wights 是因为这里是mask的是需要保留的.
  • 所以这里EE矩阵: 如果某个权重被选中了则是 wc2[H1]cc2\frac{w_c^2}{[H^{-1}]^2_{cc}}, 如果没有被选中则是0
  • 外层每隔BB个step, 做一次大block的更新.
1
2
3
4
5
6
7
8
9
10
11
12
13
矩阵 W 被分为多个 Block (大小为 B)

\[ 已剪枝固定区 \] | \[ 当前正在处理的 Block B \] | \[ 尚未处理的剩余庞大区域 (i+B 之后) \]
| (列索引 i 到 i+B-1) |
| |
| ^ 当前列 j |
| |--内层局部 Update---> |
| (仅更新 j 到 i+B 的列) |
| |

当整个 Block B 的循环走完,累积了误差矩阵 E:
|-----------------------外层全局 Update--------------------------->|
| (E * H^{-1} 一次性矩阵乘法更新右侧所有列) |

Summary

本篇文章的思路实际非常清晰明了, 作者的整个逻辑链条大概是:

  1. 将非结构化剪枝问题建模成一个优化问题
  2. 尝试求解这个优化问题的闭式解 ==> 求逆的代价很高
  3. 使用OBS中迭代地进行处理这个问题 ==> 为什么不使用最优部分更新? 来减少每次迭代导致的大范围参数更新

事实上每一步都是由上一步产生的问题而引申出来的解决办法.

Q&A

takeaway

1. 复习矩阵乘时间复杂度

X1Rm×nX_1\in\mathbb{R}^{m\times n}X2Rn×pX_2\in\mathbb{R}^{n\times p} 则矩阵乘的运算复杂度是 O(mnp)\mathcal{O}(mnp) , 求逆 O(n3)\mathcal{O}(n^3)

2. pytorch中一个线性层权重的通常形状

对于样本 XRB×N×dinX\in\mathbb{R}^{B\times N\times d_{in}}
如果你在 PyTorch 里定义

1
nn.Linear(input_dim, output_dim)

那么:

  • weight.shape == (output_dim, input_dim)
  • bias.shape == (output_dim,)

前向实际上做的是:

y=XW+by = XW^\top + b

所以虽然概念上你也可以说“把输入乘一个 input_dim 到 output_dim 的映射矩阵”,但参数真正存储的 shape 是:

(output_dim,input_dim)(\text{output\_dim},\text{input\_dim})

重点

并且非常重要的一点是, 从矩阵乘法的角度来看我们可以知道权重矩阵的哪些参数和哪些样本有关, 对于去掉Batch的样本矩阵, 可以按照列分块的形式写成向量(x1,x2,x3...)(x_1,x_2,x_3...), 每一个向量代表着一个seq里的所有token的一个embedding通道, 权重矩阵transpose可以写成行分块, (w1,w2....)(w_1,w_2....), 所以token的embedding的一个通道对应着矩阵的一行, input_dim_id (output_dim, ), 也就是对应着一个input neuron id.