模型的剪枝是为了减少参数量和运算量,而量化是为了压缩数据的占用量。
-
量化概念
所谓的模型量化就是将浮点存储(运算)转换为整型存储(运算)的一种模型压缩技术。
优势:
可以提升计算效率;减少内存和存储占用;减少能耗。
劣势:
模型的精度会降低,主要是由舍入误差和截断误差造成的,这点可以看我之前的文章。
量化对象:
主要有以下三个,实际中可能是量化其中的多个甚至全部:
(1)weight(权重):
weight的量化是最常规也是最常见的。量化weight可达到减少模型大小和内存占用等目的。
(2)activation(激活函数输出):
实际中activation往往是占内存使用的大头,因此量化activation不仅可以大大减少内存占用。更重要的是,结合weight的量化可以充分利用整数计算获得性能提升。
(3)gradient(梯度):
相对上面两者略微小众一些,因为主要用于训练。它主要作用是在分布式计算中减少通信开销,单机训练时也可减少反向传播时的开销。
量化位数:
可以有很多种选择。大体可分为几类:
(1)float16量化是比较保险的做法,大多数情况下可以不丢失太多精度的情况下有明显的性能提升,也是浮点实现和理解简单。
(2)8位是比较常见的,也是相对成熟的。相关的研究很多,各种主流框架也基本都支持,主流的int8量化和UINT8量化。
(3)8位以下目前而言学界相对玩得多些,工业界有少量支持,但还没有太成熟。8位以下主要是4,2和1位(因为位数为2的幂次性能会更好,也更容易实现)。如果精度低至1位,也就是二值化,那可以用位运算进行计算。这对处理器而言是很友好的。
-
模型量化的步骤
(1)在输入数据(通常是权重或者激活值)中统计出相应的min_value和max_value,这些值的选择会影响量化的精度,主要是截断误差;
(2)选择合适的量化类型,对称量化(int8)还是非对称量化(uint8);
(3)根据量化类型、min_value和max_value来计算获得量化的参数Z/Zero point和S/Scale,,这里会使用比如KL散度、标定等获得最佳的值,最终得到是符合条件的scale和zeropoint;
(4)根据标定数据对模型执行量化操作,即将其由FP32转换为INT8;
(5)验证量化后的模型性能,如果效果不好,尝试着使用不同的方式计算S和Z,重新执行上面的操作,这个重新执行就可以利用KL散度算法;
量化对象中量化的选择,为什么 一般 weight 选择对称量化,activation 选择非对称量化?
其实,这主要是基于它们的特点和应用场景来决定的。
几个原因:
(1)对称量化要求量化后的值中零点必须对应于原始值中的零,其通常使用两个参数(量化的最小值和最大值)来定义量化范围,该范围以零为中心对称。对于 weight 来说,其分布通常是关于零对称的高斯分布,比较适合采用对称量化。
(2)非对称量化不要求量化后的值中零点对应于原始值中的零,它使用三个参数(量化最小值、量化最大值和零点)来定义从原始数值到量化数值的映射关系。activation(中间输出值)经过 ReLU 等激活函数后可能没有负数,其分布可能不关于零对称,适合采用非对称量化。
(3)非对称量化的缺点是执行量化和反量化操作时可能需要更多的计算。例如在非对称量化的矩阵乘法中,由于存在零点补偿,会导致计算量增加,特别是对于 weight 的非对称量化,会使 activation 行向量要累加后展开成结果的形状,而 activation 无法像 weight 一样提前算好,增添了许多计算量。相比之下,activation 非对称量化与 weight 对称量化的组合能在一定程度上减少计算量,同时较好地保持精度。
(4)activation 的波动通常比 weight 本身大很多,导致 activation 量化难度较大,非对称量化能够更充分地利用比特位,从而在整体精度上会比 weight 的非对称量化高不少。这种优势在较低精度的量化(如 int4 和 uint4)中可能更为明显。
有兴趣可以关注我的专栏《高性能开发基础教程》
该文章首发于 subscriptions:极空AI,后续我会在上面整理完整的AI+HPC资料,并提供相关书籍推荐,至于视频要不要录制,看大家需要不需要。
有兴趣的可以关注。