
极市导读
本文作者回顾了在模型量化领域的工作经历,涵盖从算法研究到工程开发、从服务端到移动端、从传统2D业务到大语言模型和扩散模型的量化实践,并分享了对模型量化工作的思考和成果。 >>加入极市CV技术交流群,走在计算机视觉的最前沿
在团子做了两年多的模型量化工作,涉及方向从算法研究到工程框架开发,从服务端模型量化到移动端手机侧,从传统的2D分类、分割、检测、OCR业务,到自动驾驶点云检测,再到现在的大语言模型(LLM)、扩散模型(SD)等。对模型量化工作也算是有一些不同的认知,不知道未来还会不会在这个方向继续探索。在这里留下自己关于模型量化的一些思考,会简单介绍大家所熟知的内容,主要来串一串我们自己的成果。
一、什么是模型量化
首先,什么是量化? 量化(Quantization)是一种通过线性映射和舍入操作将连续实数映射到一组离散值的方法,该组离散值由给定低精度位数所表示(例如8bit位数可表示256个离散值),如下图所示。通过低精度的数值计算,在成倍压缩内存占用的同时可以实现更快速的计算。

然后,什么是模型量化? 上面给出了针对一个tensor的量化过程,但一个神经网络模型一般拥有几十上百个操作或层,每一层又有input、output、weights、bias等不同的tensor,哪些量化哪些不量化,不同层选择怎样的量化策略,怎么把这些tensor的量化结果组合起来,实现工业级别的应用加速,这就是一个很复杂的问题了。大部分人对于模型量化的认识,就停留在tensor量化操作,以及 PTQ(Post-Training Quantization)、QAT(Quantization-Aware Training)等应用层面,而很少真正思考过它更底层的一些逻辑。总体来说,这里将模型量化的核心收益和主要研究问题归结到两个方面:速度和精度。实现一个神经网络模型又好又快地量化部署,涉及到业务数据、模型训练、模型结构、量化算法、推理引擎、算子支持、硬件架构等方方面面。对上下游各方面都有一定的了解,才能更好地实现服务于实际的工业级别模型量化落地。
二、模型量化为什么能加速
量化模型如何实现加速,不是简单的一句整形运算比浮点运算更快可以解释的,个人将其深层原因大概归结为以下几个方面:访存加速、向量化运算加速、量化算子优化、量化图优化。
-
访存加速
量化模型的显存占用与量化bit数呈倍性关系(忽略量化参数的额外显存),这一点对于访存瓶颈的模型来说有极大的好处。现代的高性能运算芯片,对于乘加等并行计算有很强的优化,内存的读写反而成为了瓶颈,一个简单乘加指令的耗时profile,其中读写操作的耗时可能比计算耗时多一两个数量级。通过量化操作将浮点模型转换为低bit,可以显著地降低访存耗时。以大模型LLM的部署为例,Self-Decoder 阶段就是典型的 Memory-Bound 的操作。在 llama.cpp(https://github.com/ggerganov/llama.cpp) 中,对 Llama-7B 模型进行4-bit量化,显存占用可以从13GB压缩至3.9GB,推理耗时更是可以优化2~3倍,如下图所示。需要注意的是,这里的4-bit量化只针对权重,所以在实际推理时,还需要执行反量化操作获得浮点的权重,再进行计算,所以只看计算量反而是有所增加的。但凭借访存部分的速度提升,便能获得可观的模型整体推理加速。

需要额外说明的是,这里其实也体现了模型量化的另一个显著优势点:节省显存。尤其是针对于移动端模型,以及大模型部署来说,模型量化能够实现在更小的机器上推理更大的模型。
-
向量化运算加速
向量化运算(vectorization),在高性能CPU或GPU tensor core中,为了大规模运算,一般都会设计高效的向量化运算指令来实现并行计算。当向量化运算与模型量化相遇的时候产生了美妙的化学反应。举个例子,一个512-bit寄存器,可以同时处理16个32-bit的float数,如果将其量化为8-bit定点数,则同样的资源可以同时处理64个数。这里强烈推荐大家看一看志佬 @张志 的视频讲解,其中给出了炫酷的示例,展示如何结合编译器,实现一条指令完成多个定点数运算。
https://www.bilibili.com/video/BV1qA4y1Q7Uh
-
量化算子优化
对于常规的量化算子实现及优化,不多做赘述,大家感兴趣的可以参考志佬的视频学习。这里主要介绍一些特殊的量化算子实现,例如大名鼎鼎的 Cutlass 的FPA_INTB GEMM(https://github.com/NVIDIA/FasterTransformer/blob/main/src/fastertransformer/cutlass_extensions/include/cutlass_extensions/gemm/kernel/fpA_intB_gemm.h)算子,实现了高效的LLM weight-only 量化,被广泛使用。常见的GEMM算子,都是同等精度的tensor进行矩阵乘法,但在大模型量化领域,受限于量化模型精度,weight-only的量化反而更加常见。这就带来一个问题,如何实现量化后的INT4/8 类型权重与float类型的激活之间的GEMM计算。下图给出了一种常规的解法,先将INT类型的权重DeQuant为浮点类型,然后重新载入浮点的权重和激活进行矩阵乘法。但这种方法就多出了两次浮点权重的读写操作,极大增加了量化模型计算耗时。Cutlass 将DeQuant操作与GEMM操作融合进一个算子,同时设计了高效的位操作DeQuant实现,能够实现 26x 的throughput 加速。具体可以参考论文 Who Says Elephants Can’t Run: Bringing Large Scale MoE Models into Cloud Scale Production .

这里重点介绍一下我们大模型 W4A8 低比特量化的工作,Speed Odyssey(https://arxiv.org/pdf/2311.09550v1.pdf)。目前业界常见的LLM量化的工作主要为 W4A16 和 W8A8 两种,Speed Odyssey 结合两者的优势,利用 4 位权重量化在 I/O 利用率中的优势,以及8 位矩阵计算而带来的加速,综合考虑速度和精度问题,提出了一套切实可行的更低bit大模型量化推理范式,进一步推动了业界发展。为了实现融合的 W4A8 运算,Speed Odyssey 提出了一种FastGEMM 做法,使用对称量化移除INT8减法操作,并通过除法计算来恢复精度,解决了 INT4->INT8的解量化问题,实现了高效的推理加速。

-
量化图优化
以上所说的访存优化及向量化运算,展示了量化操作对单个运算(例如GEMM)的加速。但在整个模型的推理中,还需要考虑图优化的问题。推理引擎会考虑对不同算子之间进行融合、替换、消除等操作,进一步优化计算图,实现更高效的推理。而量化模型的图优化问题更是其中一个重要课题,如何消除大量的Q/DQ操作,降低精度切换对推理速度的影响,不同的芯片厂商和推理引擎部门对这一问题进行了深入的研究,NVIDIA的TensorRT 就是其中的佼佼者。他们已经对量化模型的图优化进行了细致的(闭源)优化,而使用者在前端进行QAT操作时,需要考虑的问题就变成了如何正确的插入QDQ量化节点,来匹配他们的图优化逻辑。以下图为例,是一个最常见的 Conv-BN-Add (残差结构)计算图,采用(右侧)合适的 QDQ 算子插入方式,可以将整个模块融合为一个量化算子,降低中间的访存操作。而如果在BN和Add之间多插入一个QDQ,就会导致量化图层的次优化,在我们的实际测试中,这在某些网络终究会导致接近30%的速度差异。

三、模型量化精度问题解决
大部分模型量化的使用者或者研究者,更关注的是量化模型的精度问题。模型量化将更高数值精度的浮点模型转换为少数的离散定点,不可避免会引入误差。而在神经网络模型中,每一层的量化误差传递放大,就会导致量化模型精度过低,完全不可用。这里我们首先会介绍模型量化的误差来源,及常见的解决方案。然后会重点讲述一些常规方法之外的手段,有助于我们对量化问题有更深入的理解。
-
量化误差分析
我们一般定义量化操作为: ,其中 Round 表示舍入操作,clamp 截断超过量化域范围 的outlier值。Round 和 Clamp 操作都会导致数值精度的不可逆损失,简单来说,一个tenso的量化误差可以表示为截断误差和舍入误差之和。而这两者又是相关联的, s 表示缩放因子,定义为 ,也就是说它是通过截断的上下界来决定。综合两者可以得到一个经验公式 ,即量化误差正比于 。进一步扩增至整个模型的量化误差分析,问题就变得更加复杂,需要同步考虑不同层的误差传递,及相互间的协方差等。感兴趣的同学建议继续学习志佬的课程
https:/www.bilibili.com/video/BV1V94y117Ej
然而有了以上的理论分析,在实际操作中我们依然要面临抉择,如何确定缩放因子s,或者说怎么来确定一个tensor的上下界,可以综合考虑截断与舍入问题使得总误差最小,这就是各种 PTQ 算法核心解决的问题。对于给定的浮点模型,我们重点考虑的一个问题,怎样的tensor数值分布对量化是最友好的?矮胖 or 高瘦,that is a question。 大家普遍认为,高瘦的tensor(数值分布紧密,方差小)数值范围小,相应的缩放因子s和舍入误差就能更小,因而获得更好的量化结果。但 Robust Quantization(https://arxiv.org/abs/2002.07686) 中给出了不同意见,它认为矮胖型的数值分布反而能带来更好的量化鲁棒性(猜测本质上应该是outlier 更少,截断误差变小),并提出采用 Kurtosis 的规范来约束模型训练,并取得了不错的效果。但进一步扩大数值分布范围,则又会导致量化模型的精度降低。

所以对于这个问题,其实依然没有一个公认的解析标准来衡量,因为我们无法预订或假设一个模型中tensor的分布类型。之前听到过一个有意思的看法,通过一个tensor 的条件数 来衡量其量化友好性,条件数所体现的就是矩阵的抗干扰能力,也能综合考虑高瘦和矮胖两种情形。有兴趣的同学可以研究一下。
-
PTQ and QAT
做量化的同学一般对PTQ 和 QAT 这两种量化方式都较为了解,这里不详细说来。结合上文的误差分析,本质上 PTQ 就是在校准过程中,研究不同的metric来更好地选择截断上下界,例如常见的 MinMax,Histogram,Entropy等,也有一些基于 search 的方法来遍历探索。不同场景不同网络适合的metrci 各不相同,校准集的选择也会对结果有所影响,需要在实际应用中尝试不同的组合来获得最好的效果。而有的时候,一个训练好的模型数值分布较差,各种PTQ策略都不能获得很好的效果的时候,就需要采用QAT的方法,在训练或微调中引入量化误差,约束数值分布从而获得较好的量化结果。
在实际应用中,更为常用的是PTQ的方法,大部分芯片厂商自己的编译器,已经集成了基础的PTQ方法,并与算子融合图优化等组合使用,对绝大部分模型能获得精度与速度都令人满意的结果。只有少部分特殊的模型结构场景,或更低bit量化的量化需求情况下才会考虑QAT的方法。但现在QAT工作大都偏向学术研究,而很少能真正落地到工业场景中。如何将Pytorch的QAT模型结果,完美地部署到目标硬件上,实现高精度高性能的量化模型推理,这一点在后文会进一步介绍。
-
模型结构对量化的影响
在模型训练之前,选择一个合适的模型结构,来获得量化友好的数据分布,对后续的量化部署的工作非常重要。模型结构影响量化结果,大家可能对此问题有一些经验性的认知,例如 ResNet 系列网络的量化结果优于 MobileNet 系列的 depthwise 卷积网络,分割网络比检测网络更好量化,或者更大的网络模型受量化的影响比较小的网络要低等。一个经典的案例是团子内部所推出的高性能检测器 YOLOv6 。其中使用了大量的重参数化设计 RepVGG 结构,而这种架构会导致非常恶劣的数值分布,导致模型量化结果不可用。我们针对该网络的量化问题进行分析,提出了”模型架构调整-敏感层分析-量化感知蒸馏训练-量化节点图优化”等手段的一套完整量化优化方案,大家可以参考以下链接。
这里重点提一下我们自己的工作 QARepVGG(https://arxiv.org/abs/2212.01593),其中对重参数化结构量化不友好的底层原因进行了深入分析。分别从训练策略,结构设计等角度出发,逐步优化重参数化结构的网络数值分布,并最终提出了一个量化友好版本的 重参数化结构。能够平替 RepVGG 网络,在保持高精度模型训练的同时,能够直接采用常规的 PTQ 方法来量化部署获得高精度高性能的量化模型。

-
数据对量化的影响
这里所说的数据,准确说是不同任务场景的输入数据,对于模型量化结果的影响。2D图像、3D点云、语音、文本等场景数据,有着各自不同的特征。基于其训练的神经网络,在数据分布上也会有不同的表现,因此对量化的影响也表现不同。重点介绍一下我们关于Lidar检测的一篇量化工作研究:LiDAR-PTQ(https://link.zhihu.com/?target=https%3A//arxiv.org/pdf/2401.15865.pdf)。在2D图像模型量化中表现突出的 Entropy 等 PTQ 方法在点云网络的量化中效果极差。本文分析本质原因发现,点云数据自身的高稀疏性,导致模型中激活tensor中同样包含了过多的稀疏点,加剧了模型量化时上下界选择对量化结果的影响。文中提出了一整套基于稀疏性问题研究的量化方法,包括校准初始化,全局误差纠正等等,实现高质量的点云模型量化。

除了输入数据,有时回归标签也能影响到模型的数据分布。例如在一些深度估计场景中,由于存在无限远深度标签,导致靠后的一些层中存在极大的数值,直接无法量化。选择对深度标签进行倒数操作或归一化处理则能很好的解决这一问题。
-
其他影响因素
模型架构,训练数据之外,整个模型生产过程中,方方面面的因素其实都会对最终的模型量化结果有所影响。例如训练中采用什么样的正则化方法,直接约束了tensor的数值分布情况。训练epoch的多少,有时也会对最终结果有所影响,如果在较小数据集上训练过多,可能导致模型过拟合,权重数值分布异常。再次介绍一个我们关于大模型的超低bit量化的工作:Norm-Tweaking(https://arxiv.org/abs/2309.02784)。该方法不同于常见的PTQ 或 QAT 方法,没有直接去优化量化参数或权重分布,而是通过更新LayerNorm层的数值,调整量化模型的输出激活分布与浮点模型对齐,即可极大地提升量化模型的精度,尤其是在更低bit的情况下。

以上几个部分,介绍了一些不采用常规量化优化的手段来提升量化模型表现的方法。这启发我们对量化的进一步思考,探究一个模型量化掉点的本质原因,从而在工程整体的角度来解决问题。
四、模型量化框架推荐
最后,介绍一下常用的模型量化框架。之前已经说过,各大芯片厂商自己的编译器中其实已经集成了常用的PTQ方法,如 TensorRT、SNPE 以及咱们国产的 爱芯、瑞芯微 各自的编译器等,不多做介绍。还有一些在 Pytorch 层面的 QAT 框架,例如:NVIDIA 的 Pytorch-Quantization,高通的 AIMET,商汤的 MQBench 等,各有各的优势,也有各自的坑,大家可以分别尝试。此外还有志佬的 PPQ(https://github.com/openppl-public/ppq),从 onnx 的层面实现模型的量化问题,避开了 Pytorch 的edge 难以trace的问题,直接优化图层优化后的模型结构的量化精度,推荐大家star。
这里重点推荐一下,我们开发的量化框架 MTPQ (Meituan-Pytorch-Quantization,待开源),在公司内部实现了大批量的业务落地。基于 Pytorch-Quantization 进行开发,我们优化了整体框架,集成了更多功能和算法,同时也适配了更多的代码库,实现了一个工业场景的完善工具链,能够在Pytorch 层面解决量化模型精度问题,同时适配高性能的量化部署。其主要优势有以下几项:
-
自动QDQ节点插入图优化。基于torch.fx进行了torch层面的图优化,匹配 TensorRT/SNPE 等backend 的底层图优化逻辑,保证量化模型能够在实际部署中获得真正的加速。 -
高精度的量化算法。开发了基于敏感度分析的自动部分优化实现、集成了 LSQ/LSQ+,以及自研的 Stable-LSQ 等高精度的QAT 算法,保证在绝大部分模型及更低bit量化的情况下也产生高精度的量化模型。 -
更方便的使用体验。将 MTPQ 适配了大部分的常用代码库,包括 Timm、MMLab系列、Huggingface等常见模型,同时优化了接口API,直接输出可以被 TensorRT/SNPE 等部署框架能直接load的量化模型文件,满足量化小白同学一键调用的需求。

下面给出简单的调用示例如下,简单的三行代码,即可获得高精度高性能的量化模型。
from mtpq.model_quantizer import ModelQuantizerFactory
quantizer = ModelQuantizerFactory.get_model_quantizer('model_name',
model,
quant_config='./mtpq_config_swin_v2.yaml')
# PTQ calibration
quantizer.calibration(calib_data, calib_data_num=128, save_calib_model=True)
# train() for QAT
quantizer.export()
五、相关文献
见文中链接
(文:极市干货)