参考:

真的从头到尾弄懂量化之GPTQ - 知乎

LLM 推理加速技术 —— GPTQ 量化技术演进 - 知乎

原理和推导公式可见上面的几篇文章

下面主要看下autogptq的源码

初始化海森矩阵

$$
H = 2XX^T
$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def add_batch(self, inp, out):
if os.environ.get("DEBUG"):
self.inp1 = inp
self.out1 = out
if len(inp.shape) == 2:
inp = inp.unsqueeze(0)
tmp = inp.shape[0]
if isinstance(self.layer, nn.Linear) or isinstance(self.layer, transformers.Conv1D):
if len(inp.shape) == 3:
inp = inp.reshape((-1, inp.shape[-1]))
inp = inp.t()
if isinstance(self.layer, nn.Conv2d):
unfold = nn.Unfold(
self.layer.kernel_size,
dilation=self.layer.dilation,
padding=self.layer.padding,
stride=self.layer.stride,
)
inp = unfold(inp)
inp = inp.permute([1, 0, 2])
inp = inp.flatten(1)
##这里通过更新 self.H 来维持一个运行平均值。self.nsamples 表示累积的样本数,tmp 是当前批次的大 小。通过加权平均的方式,self.H 会逐步收敛到输入数据的统计信息。
self.H *= self.nsamples / (self.nsamples + tmp)
self.nsamples += tmp
# inp = inp.float()
## 归一化与缩放
#这个操作的目的是对输入进行归一化,使得随着样本数的增加,输入的尺度逐渐调整。
inp = math.sqrt(2 / self.nsamples) * inp.float()
# self.H += 2 / self.nsamples * inp.matmul(inp.t())
# 矩阵更新
self.H += inp.matmul(inp.t())

fasterquant

1
2
3
4
5
6
7
#这里,H[dead, dead] 访问 H 中 dead 为 True 的对角线元素,然后将这些元素的值设置为 1。
#这个操作可能是为了避免在后续计算中出现除以零的错误或者在数值优化过程中保持稳定性。
H = self.H
del self.H
dead = torch.diag(H) == 0
H[dead, dead] = 1
W[:, dead] = 0

Cholesky 分解

为什么分解可以看LLM 推理加速技术 —— GPTQ 量化技术演进 - 知乎

Cholesky 分解是将一个正定矩阵分解为一个下三角矩阵和其转置的乘积。

1
2
3
4
5
H = torch.linalg.cholesky(H)
H = torch.cholesky_inverse(H)
H = torch.linalg.cholesky(H, upper=True)
Hinv = H

  1. Cholesky 分解:

    1
    H = torch.linalg.cholesky(H)

    这一行将矩阵 H 进行 Cholesky 分解,即将 H 分解为一个下三角矩阵。对于一个正定矩阵 H,其 Cholesky 分解会产生一个下三角矩阵 L,满足:
    $$
    H = LL^T
    $$

  2. Cholesky 逆矩阵

    1
    H = torch.cholesky_inverse(H)

    接下来,调用 torch.cholesky_inverse() 来计算 Cholesky 分解矩阵 H 的逆矩阵。注意,H 在这之前已经是一个下三角矩阵(通过 Cholesky 分解得到)。该函数会计算 L^{-1},即 L 的逆矩阵,满足:
    $$
    H^{-1} = (L^{-1})(L^{-1})^T
    $$

  3. 再次进行 Cholesky 分解(上三角)

    1
    H = torch.linalg.cholesky(H, upper=True)

    现在,H 是 $L^{-1}$,即 L 的逆矩阵。

    这里,我们对 $L^{-1}$​ 进行 Cholesky 分解,并且指定 upper=True,这意味着我们得到的是一个 上三角矩阵 T,满足:
    $$
    L^{−1}=TT^{T}
    $$

    因此,H 在这一行变成了一个上三角矩阵 T,它是 $L^{-1}$ 的 Cholesky 分解。

$$
\delta W = - \frac{W_{:,q} - \text{quant}(W_{:,q})}{C_q T_{qq}} C_q T_{q,q:}
$$

迭代量化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Losses = torch.zeros_like(W)
Q = torch.zeros_like(W)

damp = percdamp * torch.mean(torch.diag(H))
diag = torch.arange(self.columns, device=self.dev)
H[diag, diag] += damp
H = torch.linalg.cholesky(H)
H = torch.cholesky_inverse(H)
H = torch.linalg.cholesky(H, upper=True)
Hinv = H

for i1 in range(0, self.columns, blocksize):
i2 = min(i1 + blocksize, self.columns)
count = i2 - i1

W1 = W[:, i1:i2].clone()
Q1 = torch.zeros_like(W1)
Err1 = torch.zeros_like(W1)
Losses1 = torch.zeros_like(W1)
Hinv1 = Hinv[i1:i2, i1:i2]

for i in range(count):
w = W1[:, i]
d = Hinv1[i, i]
## 找量化参数scale,zero
if group_size != -1:
if not static_groups:
if (i1 + i) % group_size == 0:
self.quantizer.find_params(W[:, (i1 + i) : (i1 + i + group_size)], weight=True)

if ((i1 + i) // group_size) - now_idx == -1:
scale.append(self.quantizer.scale)
zero.append(self.quantizer.zero)
now_idx += 1
else:
idx = i1 + i
if actorder:
idx = perm[idx]
self.quantizer = groups[idx // group_size]
## 反量化
q = self.quantizer.quantize(w.unsqueeze(1)).flatten()
Q1[:, i] = q
## 计算Loss
Losses1[:, i] = (w - q) ** 2 / d**2

err1 = (w - q) / d
#更新Block里面没有量化的权重
W1[:, i:] -= err1.unsqueeze(1).matmul(Hinv1[i, i:].unsqueeze(0))
Err1[:, i] = err1

Q[:, i1:i2] = Q1
Losses[:, i1:i2] = Losses1 / 2
## 更新权重
W[:, i2:] -= Err1.matmul(Hinv[i1:i2, i2:])

量化结束将scale, zero等量化参数返回,而W还是原值