大模型如果用bfloat16需要特别大的显存,所以都在用INT4、INT8做量化,效果不错
量化学习
为什么量化
对Llama13B模型来说,不同算子加载需要的显存不同
中间是TensorCore,左右两边是显存,加载过程中模型要频繁地将weight和激活值从显存加载到TensorCore,计算完后,又将结果放回到显存中
在大模型推理中,制约推理速度的关键因素是显存带宽,量化数据可以减小数据交换所用的时间
对称量化
INT8是-128~127,为了方便,丢掉-128
为了最大程度的保留精度,要尽可能把-127~127占满
首先找到绝对值最大的数:2.11,将它映射到最大的整数127,从而得到了一个缩放比例:2.11/127,从而其他数都按照这个比例缩放,然后取整
反量化时也按照缩放比例进行缩放
但是,这种量化方法并没有占满左边的整数,因此引入非对称量化
非对称量化
不映射到-128~127了,改成0~255
缩放比例为:
我们还需要计算一个,它的作用是对缩放取整后的变量进行平移,从而保证每个变量都在0~255之间
再经过一个clamp操作,确保缩放平移后的变量都在0~255之间
反量化过程则为量化值减去zero_point乘上scale
量化后的数据计算
对Xf和Wf进行对称量化
对Xf和Wf进行非对称量化
量化异常值
这种情况下,即使使用非对称量化,仍然有很大一部分整数被浪费,同时,很多值被量化为同一整数
有很多工作可以解决这些问题,例如:用直方图描述数据分布、逐步舍弃一些异常值、计算量化前和量化后数据的均方误差或KL散度,找到合适的取值范围、单独对异常值进行量化、对channel量化、对group量化等
神经网络量化
训练后动态量化(pytorch)
在pytorch中,使用quantize_dynamic方法,输入模型、需要量化的层、量化类型就可以量化了
可以看到torch.qint8中,量化使用的参数是和量化后数值存放在一起的,方便随时进行反量化
为什么qint8中的值不是整数?因为输出的是qint8反量化后的值,要打印整数值需要用torch.int)repr函数
存在的问题:(1)每一次推理每一层都要对输入统计量化参数,耗时(2)每一层计算完都转化为fp32,存入显存,占用显存带宽
训练后静态量化(pytorch)
(1)动态量化之所以只量化参数,不量化输入,是因为输入总是会变的。针对这个问题的解决方法是:用有代表性的输入数据跑一遍整个网络,认为这些输入就代表了真实推理时的输入,通过统计得到每层大概的量化参数
(2)动态量化每次计算完都要转成fp32,解决方法是:这一层的输出是下一层的输入。下一层还是要量化,不如在这一层直接量化好再传给下一层
左边在量化输入,右边在量化权重(放在train之后)
(1)定义一个QuantStub量化占位符和一个DeQuantStub反量化占位符,并在forward部分调用
(2)为模型设置一个适合在x86架构下运行的量化配置,prepare生成带量化的模型
(3)用有代表性的数据对模型各层激活值的量化参数进行校准,这里直接前向传播就行
(4)convert转换成int8的量化模型
量化感知训练
对训练好的模型,无论怎么量化,总是会有误差。减少误差不正是神经网络擅长的吗?是否有一种通过模型训练的办法来减少误差?
在网络训练过程中,模拟量化,让模型在训练过程中就能调整参数,让它更适合量化,提高量化后模型的精度
prepare函数变成了prepare_qat,QAT就是Quantization-aware-training
大模型量化
黄色的是传统量化方法,蓝色的8bit是hf transformers中默认的LLM.int8()方法,绿色的是float
16
为什么参数量到一定量级后,传统方法失效?这是由Emergent Features引起的。Emergent Features是指在一些层的模型输出的特征里,有些特征突然变大,是其他特征的几十倍
对大模型而言,它有很多Emergent Features,这些大的特征代表大模型在学习过程中学到的重要特征。可以看到,因为这些特别大的异常值,导致其他某些特征量化+反量化后的值变为同一个了
LLM.int8()将这些Emergent Features单独处理,再汇总结果
对于矩阵乘法,可以把行和列拆出来分别运算,再加起来
在LLM.int8()中,取大于6的为异常值,发现异常列的占比不到0.1%
对输入按行进行量化,对权重按列进行量化,对Emergent Features用fp16计算
Transformers
使用bitsanbytes库
在transformers源码中的modeling_utils文件中有一个replace_with_bnb_linear方法用于替换算子
以Llama为例,MLP有三个线性层,Attn有四个线性层
大部分的算子都是线性层
替换掉的是线性层和1D卷积