大家好,我是小木头。这个量化系列已经有一阵没有更新了。在正式开始之前,我们先快速地回顾一下前三期。第一期从最基本的问题讲起:模型的权重原本是一大批高精度的数字,量化就是用更少的位数去近似的表达这些数字,从而减少模型占用的存储和内存。

第二期讲到了GGUF,它是一种面向推理的模型文件格式,负责把权重、模型结构、分词器和其他的必要信息组织在一起。GGUF是封装模型的格式,并不是量化算法本身。

第三期我们以Q4_K为例,看了一组权重怎样被分块、怎样用缩放系数映射成四位的编码,最后又怎样存进GGUF。

走完这前三期,我们已经知道模型为什么能变小,也知道一个Q4_K量化块是怎样编码的。但还有一个关键问题没有回答:4位量化模型已经能跑了,那么量化以后它究竟有没有损失能力?损失了多少呢?

这一期我们从三个方面回答这个问题:用困惑度和KL散度评估量化带来的变化,再用imatrix提供的校准信息尽量减少量化的误差。简单来说,前两项用来评估量化的结果,后一项用来改进量化的过程。

先看困惑度(Perplexity),通常咱们将其缩写为PPL。我们可以把它理解成一场连续的猜下一个token的测试:把一段文本交给模型,每读到一个位置就让它预测接下来会出现什么。如果实际出现的token恰好是模型认为很可能出现的内容,PPL就会比较低;如果文本中的内容经常出乎模型的预料,那PPL就会升高。

因此PPL衡量的是模型对指定测试文本的概率预测能力,它不是事实准确率,也不能直接代表推理、代码或者工具调用的能力。在量化评测中,应该固定模型版本、tokenizer、测试集上下文长度和评测的实现,再比较高精度模型与各个量化版本的PPL。

量化模型的PPL越接近高精度的基准,或者PPL的增量越小,通常说明量化对这一份测试数据上的概率预测能力影响越小。在llama.cpp里可以运行llama-perplexity并传入模型和测试文本,社区里大家经常使用的是WikiText-2。

分别测试F16、Q8、Q6、Q5和Q4,随后画出模型体积与困惑度之间的变化曲线。不过使用PPL时有三个限制:第一,不同的tokenizer、测试集、上下文长度或者评测实现得到的PPL通常不能直接横向对比。

第二,它把整份测试集上的表现概括为一个数值,即使两个模型的PPL很接近,它们在个别token上的概率分布仍然可能有明显的差异。第三,结果取决于测试数据的分布:如果校准数据与评测数据高度相似(例如都以维基文本为主),那么评测结果可能偏向这类文本,还不能够充分地代表对话、代码或工具调用等实际的场景。

因此我们还需要观察另一个指标,也就是KL散度(Kullback-Leibler Divergence)。KL是Kullback-Leibler的缩写,名称呢来自于这两位研究者的姓氏,中文也叫相对熵。它用来衡量两个概率分布之间有多大的差异。

假设同一句话读到同一个位置,高精度模型为“模型”两字分配40%的概率,为“系统”分配20%,那量化以后呢?这两个数字可能变成了30%和28%。KL散度不只看模型最终选择了哪个token,而是比较完整的概率分布:两组概率分布越接近,KL散度通常呢越接近于0;数值越大,说明量化模型的输出分布偏离高精度模型越多。

llama.cpp的评测分为两步:模型在输出概率之前,会先为词表中的每个候选token计算一个原始的分数,这些分数叫做logits。先用高精度模型处理整份测试集,把每个位置的logits保存为基准文件;再让量化模型处理同一份文本并与这份基准进行比较。

在官方README的WikiText-2示例中,LLaMA 2的基准文件约为11 GiB,LLaMA 3约为37 GiB。因此这项测评呢需要预留足够的运行时间和存储空间。结果中不只有平均的KL散度,还包括实际token的概率变化、概率变化的均方根。

以及最高概率token一致率(Same top p),它统计的是在多少个位置上高精度模型和量化模型会把同一个token排在第一名。这个比例越高,说明两个模型最倾向选择的下一个token越一致。不过KL散度也不是模型的智力分数,它反映的是输出分布改变了多少,不能直接等同于模型能力下降了多少。

量化误差有时甚至会让模型在某个测试集上的PPL略微降低。如果你真正关心的是代码、工具调用或者长上下文的能力,还需要补充相应的任务评测。接下来再看imatrix(importance matrix),它和前两个指标最大的区别在于:imatrix不是评测分数,而是量化时使用的一类校准信息。

让高精度模型处理一份有代表性的校准文本(llama-imatrix),会收集各个张量对应的激活平方统计。某个方向在实际的输入中越活跃,量化时就越需要控制这些位置上的误差。为了便于理解,可以把这些统计信息想象成一张重要性的热力图。

实际操作同样分为两步:先用llama-imatrix生成imatrix.gguf,再通过llama-quantize的imatrix参数读取这份文件。量化器可以利用其中的统计信息,更好的控制重要方向上的量化误差。这里需要避免一个常见的误解:imatrix不会自动决定第一层使用4位、第二层使用6位,它只提供张量重要性的参考信息。

具体采用哪种量化类型、哪些张量保留较高的精度,仍由量化算法和预先设定的量化方案决定。校准文本同样可能会带来偏差:如果先用维基文本生成了imatrix,再用同类的维基文本评测KL结果可能特别好看,但未必能代表模型在对话、代码和工具调用等场景中的表现。对于指令模型,准备校准数据时还要正确地套用chat template(也就是聊天模板)。

理解了这一点,也就更容易理解Unsloth Dynamic的做法。前三期介绍的是Dynamic 2.0,它不会把所有的张量统一量化到相同精度,而是针对不同模型、不同层、不同张量采用不同量化组合。按照官方的说明,2.0使用的校准集会根据模型大小从30万token增加到150万token,并且专门考虑了对话场景。

以官方表格中的Gemma 3 12B为例,Q4_0 QAT版本在5-shot MMLU上得到了67.07%,BF16版本是67.15%。前者的文件大小是7.52 GB。这组数据不能够证明所有4位量化都没有损失,它说明的是评价量化结果时必须同时考虑到量化方法、校准数据和任务的评测。

在制作这期视频时Unsloth已经发布了Dynamic 3.0,因此2.0在这里用于说明方法的演进并不是当前最新版本。3.0延续了训练后量化的思路而不是QAT,并且仍然把imatrix、逐张量选择量化方式和独立评测结合在了同一套流程中。

那现在咱们就可以把校准和评测串成一套完整的流程:第一步准备一份接近实际使用场景,又不与最终测试集重复的校准数据;第二步使用imatrix并据此来制作多个量化版本;第三步在同一份独立的测试文本上评测PPL和KL散度。

第四步把这些版本按照量化程度排在一起,同时比较内存的占用、推理的速度和测评的结果。所谓的拐点不是某个固定的量化格式:如果从某一档开始,每节省一部分的内存PPL和KL的增幅却明显的变大,或者实际的任务得分跌破了预先设定的下限,那么拐点就在这一档附近。

最后再挑选拐点前后的两三个版本,用你真正关心的代码、对话或工具调用的任务做一次验证。Q4_K_M和Q5_K通常呢是比较实用的起点,但这只是社区的经验并非适用于所有模型。可靠的结论应该还是来自同一个模型版本、同一份测试集和同一套测评的命令。

所以四位量化版本能跑,这只是一个开始:困惑度反映了模型预测测试文本的能力变化;KL散度衡量了输出概率它的分布偏离了多少;而imatrix则为量化过程提供了张量重要性的参考信息。分清评测指标和校准信息各自的作用,才能回答这一期最开始咱们给自己提的问题:模型量化以后能力有没有损失?那究竟又损失了多少呢?

我是小木头,我们下期见吧。