首页 / 第三部分 · 大语言模型是怎么长出来的 / 第 15 篇

第 15 篇 · 第三部分 · 大语言模型是怎么长出来的

量化:把大模型装入消费级显卡

上一篇算清了推理时显存的两份开销:权重一份,KV Cache 一份。这一篇专门关注权重那一份。显存是运行大模型的硬门槛,14 GB 的模型加载到一张 8 GB 的显卡上,并不会自动压缩,只会加载失败。量化做的事情很直接——用一部分精度换取可观的显存与速度收益。这笔交换是否划算,取决于具体场景。

一、先算清楚 fp16 的门票有多贵

参数量和显存的关系非常简单:显存 ≈ 参数量 × 每个参数占的字节数。

fp16 占 2 字节,所以 7B 模型约 14 GB,13B 约 26 GB,70B 约 140 GB。如果只看数字不够直观,可以换算成显卡:一张 RTX 4090 是 24 GB,上一代的 3080 只有 10 GB。70B 模型若要运行 fp16,需要两张 80 GB 的 A100,这还不包括 KV Cache 和激活值。

更麻烦的是,显存往往不是"能不能容纳"的问题,而是"容纳之后还剩多少"的问题。留给 KV Cache 和中间张量的空间被权重占用,batch 无法调大,吞吐也随之受限。所以量化节省的不只是显存,还是能同时服务的请求数。

反过来看,如果能把每个参数从 16 位压缩到 4 位,7B 模型从 14 GB 降到 4 GB 左右,一张 8 GB 的显卡即可运行。这就是量化最直接的吸引力。

二、量化就是把浮点换成整数

先弄清 fp16 是什么结构。16 位里,1 位符号、5 位指数、10 位尾数。指数位让它能表示从很小到很大的动态范围,尾数位决定有效数字的精度。代价是:表示一个数要占用 16 位。

int8 是 8 位整数,只能表示 256 个离散值;int4 的档位更少,只有 16 个离散值。这么少的档位,为什么还能描述连续的权重?靠的是"缩放"——把浮点的动态范围映射到整数格点上,记下一个缩放比例,用的时候再乘回去。

精度损失是显而易见的:256 档、16 档,不可能无误差地表示原来的数。但神经网络有个好处,它本来就对小幅扰动不敏感。权重被轻微改一改,输出大致不变。量化之所以流行,依靠的正是这种容错能力。

除了省显存,量化还有个常被忽视的好处:带宽。上一篇文章说过,解码阶段卡在显存带宽上,每生成一个 token 都要把全部权重读一遍。权重从 14 GB 压缩到 4 GB,同样的时间内权重读取量可提高到三倍以上,token 生成速度随之提升。

三、对称量化、非对称量化、缩放因子和零点

量化的核心就一个公式,套在一个数据块上:

x ≈ s · (q − z)

其中 x 是原来的浮点数,q 是量化后的整数,s 是缩放因子(scale),z 是零点(zero point)。s 决定一格代表多少,z 决定整数 0 对着哪个浮点值。

对称量化把范围取成正负对称。缩放因子 s = max|x| / (2b-1 − 1),零点固定为 0,整数范围处于 −127 到 127 之间。它简单、快,乘加不用额外减去偏移,硬件实现友好。缺点是如果数据分布本身偏向一边,负半边就浪费了。

非对称量化允许范围两端不同:s = (max − min) / (2b − 1),零点 z = round(−min / s)。它把整个区间都利用起来,适合 ReLU 之后全是非负数的激活值。多一个零点,反量化时要多一次减法,会多一些开销。

至于一次量化多少个数,也就是粒度,可以整张权重矩阵共用一个 s,也可以每行、每列、甚至每 32 个元素分一组。粒度越细,越能贴合局部的数值分布,精度越好;代价是缩放因子本身也要占存储和计算。工程上常见的折中是"分组量化",每 32 或 128 个权重共享一组 s 和 z。

四、训练后量化 PTQ 与量化感知训练 QAT

按"什么时候量化"分,有两条路。

PTQ(训练后量化):模型已经训练好了,直接拿来量化。拿一小批校准数据执行一遍前向,统计每块权重的数值范围,据此定下 s 和 z。整个过程不用反向传播,几十分钟到一个多小时就能完成一个大模型的量化。GPTQ、AWQ、llama.cpp 的量化都属于这一类,也是如今的主流。

QAT(量化感知训练):在训练过程里就把量化误差考虑进去。做法是在前向时插入"伪量化"节点——先量化再反量化,把权重按整数格点取整,但梯度照常回传(这里用的是一个绕过取整操作的直通估计器)。模型于是学会了在低精度下也能工作。Jacob 等人在 2017 年把这套方法系统化,精度损失比 PTQ 小,缺点是需要重新训练,在大模型上成本很高。

如今的现实是:几千亿参数的模型几乎没有团队采用 QAT,成本过高;PTQ 配上好的误差补偿算法,质量已经足以满足需求。所以后文讨论的都是 PTQ。

五、精度损失从哪来:离群值

量化造成的精度损失,原因基本都在离群值上。

大模型的激活里存在一些特别大的数。整层激活的典型绝对值约为 1,但个别通道的值能达到 20、30 甚至更高,相差几十倍。对称量化拿 max|x| 定缩放因子,一个 30 的离群值会把 s 推得很大,剩下的正常值全被压缩到一两个格点上,几乎无法区分。

权重那边相对温和,激活才是重灾区。Dettmers 等人在 2022 年提出的 LLM.int8() 就是冲着这个来的:把少数几个离群通道单独分离出来,用 fp16 算,其余通道正常走 int8。这样既保留了大数的精度,又获得了 int8 的显存节省。思路有点像火车站安检——绝大多数旅客走快速通道,个别需要仔细查的单独过一遍。

理解离群值,后面几个算法就都好懂了:它们的差别,全在"怎么处理这些离群值"上。

六、GPTQ:逐层量化,用二阶信息修正误差

GPTQ 是 2022 年 Frantar 等人提出的,做的是逐层量化:一次处理一层,用二阶信息来分配误差。

它的大致流程是这样。拿一批校准数据执行一遍,求这一层的输入激活。然后按列(输入维度)把权重一个一个量化。每量化完一列,就用 Hessian 矩阵(近似到二阶导数)算出这次取整带来多少输出误差,再把这个误差按比例补偿到还没量化的那些列上。于是前面列产生的误差,由后面尚未量化的列分担一部分,整层输出的重建误差被压到最小。

这套算法是从 OBS、OBQ 一路演化来的,GPTQ 的关键改进是在数学上做了近似,让计算复杂度降下来,才能在 175B 这种量级上正常运行。拿 4 bit 量化一个 175B 模型,一张卡几个小时就能完成,精度损失在当时属于能接受的范围。

GPTQ 的短板是依赖 Hessian,实现比"取最大最小值"复杂得多,校准数据的质量也直接影响结果。

七、AWQ:保护重要通道

AWQ 是 2023 年 Lin 等人提出的,名字里的 A 是"激活感知"。它问了一个好问题:量化误差到底应当优先保护什么?

直观来看:权重绝对值大的通道重要,需要保护。AWQ 指出这并不准确——决定输出的是权重和激活的乘积。一个权重不大、但对应激活常年很大的通道,对输出影响照样大。所以真正该保护的是"激活大"的通道。

它的做法是给重要通道按比例放大权重,再用同等比例缩小对应激活,数学上不改变输出。放大之后,这些通道的权重离量化格点更远也更稳定,取整时相对误差就小。选择哪些通道重要,只看激活的统计量,不做反向传播,速度比 GPTQ 快。

论文里提到,只需保护大约百分之一的通道,精度就能明显回升。这类"重点保护少数通道"的思路,和上一节 LLM.int8() 分离离群通道是同一思路。

八、GGUF 与 llama.cpp 的命名(Q4_K_M 是什么)

本地运行模型的人,下载页面上会看到许多文件名,比如 model-Q4_K_M.gguf。这些后缀不是乱码,是一套命名约定。

GGUF 是 llama.cpp 用的文件格式,把权重和一个元数据头装入单个文件,方便分发和内存映射加载。它是前一代 GGML 格式的后继,现在绝大多数本地量化模型都是这个格式。

后缀逐段来看:Q 表示量化过的,后面的数字是位宽,_K 表示用了 k-quant 系列(分组量化、混合精度,关键层用更高位宽),再往后的 _S、_M、_L 是 small、medium、large 三档——同一套位宽下,复杂度不同,M 一般是最常用的平衡点。所以 Q4_K_M 读作:4 位、k-quant、中等档。

常见的还有 Q8_0(近似无损)、Q6_K、Q5_K_M、Q3_K_S、Q2_K。数字越小,文件越小,代价也越大。近两年又出了一批 IQ 开头的,叫 i-quant,用格点编码,在同样低比特下比传统 k-quant 保真度更好,只是量化耗时更长。

九、该选哪种:一张对照表加几条实测经验

把常见档位列在一起,7B 模型的实际大小大致是这样(不同模型略有出入):

格式位宽7B 体积质量感受
fp1616约 14 GB基准
Q8_08约 7.2 GB几乎察觉不到差别
Q6_K6约 5.5 GB基本无感
Q5_K_M5约 4.8 GB很接近原版
Q4_K_M4约 4.1 GB公认的甜点
Q3_K_M3约 3.3 GB开始有可察觉的退化
Q2_K2约 2.7 GB明显下降,慎用

几条实测经验,一并列出:

  • Q4_K_M 是安全垫。体积和质量的平衡点,绝大多数场景直接选用即可。
  • 低于 4 位,质量下降很快。3 位以下,写作、代码这类对细节敏感的任务最先出问题,不要期望仍能满足要求。
  • 模型越大,越能承受量化。70B 压缩到 4 位,效果往往还优于 7B 的 fp16 版本。参数冗余本身就是缓冲。
  • 不要只关注一个指标。困惑度(perplexity)上升一点,长文生成质量可能明显下降。多运行几个真实任务验证,比只看一个数字更可靠。
  • 先看显存再选格式。记住那里还有一项 KV Cache 开销,预留足够空间,避免把显存占满。

简而言之:量化的本质是用可控的精度损失,换取显存和带宽。不宜简单认为"位宽越低越不利"——对绝大多数显存受限的部署来说,4 位量化后的模型,才是真正能够运行、可持续使用的选择。

下一篇换一个角度:不用更少的位数表示权重,而是直接删掉一部分权重,观察稀疏能否换来同样的收益。

参考资料

  1. Benoit Jacob 等, Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference, CVPR 2018. arXiv:1712.05877(对称/非对称量化与 QAT 的经典表述)
  2. Elias Frantar 等, GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers, ICLR 2023. arXiv:2210.17323(逐层、基于二阶信息的 PTQ)
  3. Ji Lin 等, AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration, MLSys 2024. arXiv:2306.00978(保护重要通道的量化方法)
  4. Tim Dettmers 等, LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale, NeurIPS 2022. arXiv:2208.07339(离群值通道单独处理的思路)
  5. llama.cpp 项目仓库. github.com/ggml-org/llama.cpp(GGUF 格式与 Q4_K_M 等命名规则的实现出处)