aliases:
- "SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models"
created_date: 2024-11-3
modified_date: 2024-11-24
2024-11-4
这篇文章主要分析了将激活量化的难度迁移到权重量化上的可行性,以及提出了一个参数
另一个印象深刻的点是,诸多模型的激活有这样的特点,其每个 Channel 内的数据方差很小,很稳定,不同 Channel 之间的数据方差很大,导致了极端值和偏差大。而量化工作的数学公式是依赖于极端值的,因此,对于非极端值的 Channel 来说,会导致大的误差。这也是迁移的一个原因。
同时也有一些问题(绿色划线),列举在下面。
一些独立于文章之外的概念上的问题
这里的计算机节点 node 是什么概念
节点(Node) 节点是集群中的一个独立计算机或服务器。 在集群环境中,每个节点通常具有相似的配置和功能,并且可以相互通信和协作以完成共同的任务。 节点可以是·物理服务器·,也可以是·虚拟机·或·容器·等虚拟化技术实现的实例。
张量在 0 处对称的作用和好处是什么
在数据科学、机器学习等领域,输入张量的对称性可以被利用来优化计算资源和提高算法效率。例如,通过对称矩阵的子空间表示,可以显著加快算法的运行速度。
什么是外部维度
在数学分析和机器学习领域中,“外部维度”可能指的是数据集中除了最大化模型性能可能直接使用的特征之外的变量。例如在预测销售数据的模型中,如果“销售额”是主要的目标,那么“广告支出”、“库存水平”、“节假日”等直接与销售活动有关的特征是模型的内部维度(直接用于预测的维度)。而“天气状况”、“竞争对手的活动”、“经济状况”等可能影响销售额的变量则被视为外部维度,因为它们不一定直接映射到模型的目标特征上。
在这种情况下,外部维度可能需要通过其他方式整合(如特征工程)到模型中,使其成为预测销售额的有效组成部分。
从知乎上找了一篇对应的解析文章 SmoothQuant的量化深入探究
重新阅读文章,仍然存在的一些问题:
“Such quantizer uses the maximum absolute value to calculate ∆ so that it preserves the outliers in activation, which are found to be important for accuracy (Dettmers et al., 2022).” (Xiao 等, 2024, p. 2) 这种量化器使用最大绝对值来计算 Δ,以便保留激活中的异常值,这对准确性很重要(Dettmers et al., 2022)。 为什么使用在最大绝对值可以保证保留激活中的异常值
“The total effective quantization bits would be largest when all the channels have the same maximum magnitude.” (Xiao 等, 2024, p. 4) 当所有通道具有相同的最大幅度时,总的有效量化比特将是最大的。
通道间的量化平衡。在深度学习模型的量化过程中,特别是在卷积神经网络(CNN)中,每个通道(即特征图)的激活值都需要被量化。量化的有效性(即总的有效量化比特数)受到通道间量化平衡的影响。
阅读源码,论文结构
按通道 (channel) 量化 w 代码
@torch.no_grad()
def quantize_weight_per_channel_absmax(w, n_bits=8):
# w: (out_features, in_features)
# 计算每个通道的绝对值最大值,作为scale
scales = w.abs().max(dim=-1, keepdim=True)[0]
# 计算量化最大值
q_max = 2 ** (n_bits - 1) - 1
# 限制scale最小值,尺度范围
# 进行归一化
scales.clamp_(min=1e-5).div_(q_max)
# 根据scale,对权重进行量化
# mul_(scales) 是为了恢复尺度
w.div_(scales).round_().mul_(scales)
return w
对激活进行量化
@torch.no_grad()
def quantize_activation_per_tensor_absmax(t, n_bits=8):
t_shape = t.shape
# 将张量t变成一个二维张量
t.view(-1, t_shape[-1])
scales = t.abs().max()
q_max = 2 ** (n_bits - 1) - 1
scales.clamp_(min=1e-5).div_(q_max)
t.div_(scales).round_().mul_(scales)
return t
这段代码执行的是模型的量化感知工作。具体来说,它通过收集模型在特定数据集上的激活值,计算这些激活值的最大绝对值,并使用这些值来调整模型的权重和偏置,从而实现模型的量化。
将 Openai 的大模型 OPT 进行量化操作,是对特定结构的模型产生的量化方式。和其他模型的区别在于激活的量化尺度。本文件是对实现的量化后的 OPT 模型类的具体实现代码。
这里是原文描述
我们提供Llama, Mistral, Mixtral, Falcon, **OPT**, 和BLOOM模型的**激活通道尺度**。我们使用Pile验证集中的512个随机句子来获得这些尺度。你可以使用OPT演示(examples/smoothquant_opt_demo.ipynb)和Llama演示(examples/smoothquant_llama_demo.ipynb)来测试平滑和量化这些模型。
实现论文的部分是 smooth.py
函数,是论文迁移难度因子
weight_scales = torch.cat(
[fc.weight.abs().max(dim=0, keepdim=True)[0] for fc in fcs], dim=0
)
weight_scales = weight_scales.max(dim=0)[0].clamp(min=1e-5)
转为二维向量是为了确保全连接层的权重张量和平滑尺度张量可以进行矩阵乘法运算。全连接层的权重张量的形状通常是 (input_size, output_size)
,而平滑尺度张量的形状通常是 (input_size,)
。为了进行矩阵乘法,平滑尺度张量需要被转置成 (1, input_size)
的形状,这样就可以与权重张量的形状 (input_size, output_size)
进行兼容的矩阵乘法。
如果直接计算,可能会导致维度不匹配问题,因为全连接层的权重张量的形状是 (input_size, output_size)
,而平滑尺度张量的形状是 (input_size,)
。直接进行乘法运算会导致维度不匹配的错误。
# 更新 ln 和 fcs 的权重和偏置
ln.weight.div_(scales)
ln.bias.div_(scales)
for fc in fcs:
fc.weight.mul_(scales.view(1, -1))
在量化权重时,使用 "per-channel" 方法表示对每个通道(或特征)的权重进行独立的量化。这里的 " 通道 " 通常指的是全连接层的输入特征维度。在全连接层的权重张量中,形状通常是 (out_features, in_features)
,其中 out_features
表示输出特征的数量,in_features
表示输入特征的数量。
在量化过程中,"per-channel" 方法的思路是针对每个输入特征(即 in_features
)独立地进行量化。因此,计算绝对值最大值时,需要沿着输入特征的维度(即 dim=-1
)进行操作。
具体来说:
scales = w.abs().max(dim=-1, keepdim=True)[0]
:
w.abs()
:计算权重的绝对值。.max(dim=-1, keepdim=True)
:沿着输入特征的维度(dim=-1
)取最大值,并保留维度。这里 dim=-1
表示沿着输入特征的维度进行操作,keepdim=True
表示保留维度,即结果的形状仍然是 (out_features, 1)
,但只保留了最大值那一列。[0]
:取最大值的结果,得到一个形状为 (out_features, 1)
的张量,其中每个元素表示对应输入特征的绝对值最大值。w.div_(scales).round_().mul_(scales)
:
w.div_(scales)
:将权重除以其对应的尺度。.round_()
:对结果进行四舍五入。.mul_(scales)
:将四舍五入后的结果乘回原来的尺度,以恢复尺度。通过这种方式,每个输入特征的权重被独立地量化,并且每个特征的量化范围由其对应的绝对值最大值决定。这种方法可以有效地减少量化带来的精度损失,同时保持全连接层的性能。
@torch.no_grad()
def quantize_weight_per_channel_absmax(w, n_bits=8):
# w: (out_features, in_features)
# 计算每个通道的绝对值最大值,作为scale
scales = w.abs().max(dim=-1, keepdim=True)[0]
# 计算量化最大值
q_max = 2 ** (n_bits - 1) - 1
# 限制scale最小值,尺度范围
# 进行归一化
scales.clamp_(min=1e-5).div_(q_max)
# 根据scale,对权重进行量化
# 缩放,将权重变成一个整数值
w.div_(scales).round_().mul_(scales)
return w
回看论文,粒度的地方仍然没有理解
SmoothQuant的量化深入探究 > **如何理解 per-tensor/per-channel/per-token**
其理解如下:量化的粒度 > 理解量化粒度