一份模型用16位保存权重,换成4位后小了很多,介绍页却仍写着同样的参数数量。它删掉了知识吗?既然还原后仍能计算,为什么不把所有模型都压到更低?
上一章的蒸馏让学生重新学习教师行为。本章讨论另一条路线:保留原有计算结构,把其中的数换成占用更少空间的近似表示。关键不只是“省多少”,还包括误差在哪里产生、怎样影响输出,以及硬件能否真正利用这种表示。
1.位数减少,意味着可分辨的档位变少
一位二进制只能记录两种状态,4位最多区分16种编码,8位最多区分256种。编码怎样对应数值,由格式决定。4位并不意味着只能记录整数0到15:我们可以用这些编号去指向一组正数、负数和零。
量化就是把更多可能的数映射到有限档位。好比把连续测得的长度记到尺子的刻线上,几个不同原值可能落在同一格。一旦只留下格子编号,就无法判断原来究竟是哪一个数。反量化把编号换回对应的近似数值,并不能找回已经丢失的小数。
浮点数原本也只有有限精度;量化通常进一步降低表示所需位数。整数式量化、低位浮点和后面介绍的NF4,档位安排并不相同。看到“都是4-bit”,不能据此认定它们的误差、计算方式和兼容性相同。Transformers:量化概念
2.亲手把五个权重量化,再还原
取一组人工权重:[-1.26,-0.73,0.08,0.91,1.40]。为方便手算,选择对称的整数档位-7到7,共15档,用4位编码存放,剩余一种编码不用。这是教学约定,不是所有4位格式的标准。
最大绝对值为1.4,让它对应整数7,每一档的间距就是1.4÷7=0.2。这个间距叫缩放尺度scale,记为s。它说明整数编号增加1,对应原数值增加多少。
量化分三步:原值除以s,就近舍入为整数,再把超出范围的结果限制在-7到7。最后一步叫截断或裁剪;本组数没有超界。反量化则把整数乘回s。PyTorch:量化映射、scale与zero-point
例如,-0.73÷0.2=-3.65,舍入为-4,反量化得到-0.8。再如,0.08÷0.2=0.4,舍入为0,还原也是0。小数没有被完整地藏在整数里,它与同一档内的其他数无法区分了。
逐项计算,整数为[-6,-4,0,5,7],还原值为[-1.2,-0.8,0,1.0,1.4]。约定“误差=还原值减原值”,五项误差是[0.06,-0.07,-0.08,0.09,0]。
把绝对值相加再除以5,平均绝对误差为0.30÷5=0.06。最大的绝对误差是0.09。这里没有舍入到一半的平局;若遇到,具体实现还需约定向哪里舍入。
在没有裁剪、使用最近档位舍入的这把均匀尺子上,一个数距离最近刻线最多是半格,即0.1。这解释了本例误差为何都不超过0.1。若原值超出范围,例如仍用这把尺子记录2.6,只能截到1.4,误差就达到-1.2,半格界限不再适用。
这种以零为中心的方法,零点就是0。更一般的仿射量化允许另设zero-point,记为z:先把原值除以s,再加上z并舍入;还原时先减z,再乘s。z表示“真实的零对应哪个整数编码”。范围偏向正数时,移动零点可能让有限档位利用得更充分。
例如用0到15的整数记录-1到2,取s=0.2、z=5。真实的0先变成0÷0.2+5=5,还原时(5-5)×0.2=0;真实的1编码为10,还原为1。这里整数5代表零,整数10代表一。把编码当成原值直接计算,就会弄错数值含义。

图1|上方在同一数轴标出原值、最近刻线和误差:-0.73映到-0.8,0.08映到0。中间放大一格说明无裁剪时的半格界;下方2.6裁剪到1.4的误差远超过半格。15档是本章教学约定。
3.很小的权重误差,也要经过后续计算
把刚才的权重与输入[2,-1,-3,0,1]逐项相乘再相加,忽略偏置。原输出为-2.52+0.73-0.24+0+1.4=-0.63。使用还原后的权重,输出变成-2.4+0.8+0+0+1.4=-0.20,相差0.43。
这个差不是凭空产生的:0.06×2+(-0.07)×(-1)+(-0.08)×(-3)=0.43。输入会给权重误差重新加权。本例三项影响同向叠加;换一组输入,也可能部分抵消。
因此,“每个权重误差不到0.1”不能推出“每层输出误差也不到0.1”。后面还有注意力、非线性和多层计算。最后两个候选token的分数原本接近时,微小变化就可能改变排名;它们相差很大时,同样扰动也可能不改变选择。
把最后一句也变成能手算的例子。若候选A、B的logits原本为1.00和0.98,分别受到-0.02和+0.02的扰动,就变成0.98和1.00,第一名交换。若原本为1.00和0.00,同样扰动后为0.98和0.02,第一名保持。
更一般地,若每个候选的扰动绝对值都不超过ε,两项分差最多缩小2ε。原领先差大于2ε时,这两项的顺序就保持;这只是给定误差界的条件推论,并未证明真实模型每层都有这样的误差界。

图2|同一组权重误差经过输入加权,输出差为0.43。下方比较同样±0.02扰动:领先0.02时可以换序,领先1.00时仍保持。图中logits是另一组独立教学数值,不是由上方0.43直接推得。
数值误差与任务错误之间没有固定换算率。平均误差小是有用线索,仍需检查完整回答。反过来,某个权重变化明显,也不代表每道题都会出错。
4.一个异常值,为什么会影响一整组小数
现在把14加入刚才的量化组,仍保留-7到7的档位,并按最大绝对值定尺度。s变为14÷7=2,刻度一下粗了十倍。原有五个数分别还原成[-2,0,0,0,2],新增的14则能精确还原。
只计算原有五个数,平均绝对误差变成(0.74+0.73+0.08+0.91+0.60)÷5=0.612。照顾大数的范围,让附近小数失去了分辨率。这里的“异常值”是相对这组数的尺度而言,并不说明它是错误数据或无用参数。
可以缩小范围并裁剪14,但那会给大数带来误差;可以把数据分成小组,各自保存scale,让异常值只影响所在组;也可以对敏感部分保留较高精度。这些做法都在重新分配有限精度,没有免费的完美选项。
这里存在三个相关但不同的目标:扩大范围可减少裁剪,缩小格距可减少舍入误差,实际任务关心的却是输出和决定。第3节的输出差等于Σeᵢxᵢ,所以相同的权重平均误差,遇到不同输入也可能造成不同影响。选择scale时应说明优先控制什么,再用实际任务验收。
分组还需要附加信息。假设每64个4位权重共用一个32位scale,权重占64×4=256位,加上scale共288位,平均每权重4.5位,还未计其他信息。组越小,尺度越贴近局部数据,但记录尺度的相对成本往往越大。QLoRA论文明确讨论了分块量化与这类尺度开销。QLoRA论文:第2、3节
把14单独分一组、其余五数留一组,能恢复本例的小数精度,但这里只用于说明机制,不是实际模型推荐的分组方式。真实分组还要适配矩阵布局和计算内核。

图3|同样15个档位,范围由[-1.4,1.4]扩大到[-14,14]时,步长从0.2增至2。上、下两把尺各自标明范围,不能按画面距离比较数值。分组为小数与14保留独立scale,可以缩小异常值影响,但要额外保存尺度。
5.权重和激活,面对的是两类变化
权重是在普通推理时反复使用的参数;激活是当前输入经过各层产生的中间数值。量化权重主要减少这部分参数的存储和搬运,量化激活则还会影响运行中的中间表示。写着W4A16的方案,通常表达权重4位、激活16位;各模块、缓存和累加精度仍要另查。
权重可以在部署前检查,而激活会随输入变化。静态量化常用代表性输入做校准,观察数值范围,再确定尺度。若只用短中文问答校准,实际却大量处理长代码,观察到的范围未必适用。校准是在为近似计算估计合适范围,不等同于对任务正确率做验证。
动态方法可以在运行时为相关数据重新确定量化参数,适应变化,但也增加统计和转换开销。还有只量化权重、保留较高精度激活的方案。具体量化哪些对象,应读实现,不能只看文件名里的位数。PyTorch:动态、静态与量化感知训练
训练后量化通常从已经训练好的模型出发;量化感知训练则在训练中模拟舍入等影响,让可训练参数有机会适应。后者需要额外训练条件,也不能保证任意低位下都保持质量。Transformers:PTQ与QAT
6.存储用几位,计算就一定用几位吗
不一定。低位权重可以紧凑存放,到计算时按需转换为较高精度,再与激活相乘。有些硬件和内核也支持直接利用低位表示运算。应分别询问存储dtype、计算dtype和累加方式;dtype就是数据类型,不能用一个标签概括全部过程。
例如Transformers v4.57.1的bitsandbytes文档分别设置load_in_4bit与bnb_4bit_compute_dtype。前者指定4位加载,后者可以指定BF16计算。这说明存储与计算是两个开关,不意味着整份模型必须同时展开成永久保留的高精度副本。Transformers:4位加载与计算类型
做一笔只含权重载荷的账:80亿个数,每数16位是160亿字节,即16 GB;4位是40亿字节,即4 GB,均按十亿字节为1 GB。这是理想载荷减少到四分之一,尚未计尺度、未量化模块、激活、缓存与临时空间。
速度也不会自动变成四倍。减少权重搬运可能有利,但解包、反量化、内核支持、批次与输入长度都会改变收益。一个更小的模型文件,不能替代同一设备、同一任务下的延迟与吞吐测量。
7.NF4与QLoRA怎样落到实际工具
刚才用的是等间距刻度。NF4使用不同的思路:参照正态分布安排非均匀档位,让数值较集中的区域得到更密的表示。4位编码指向对应的码表值,再结合分块尺度恢复近似权重。它不是“整数乘0.2”的另一种名字,也不能对任意分布都承诺最佳。bitsandbytes v0.48.2:LinearNF4
第14章已解释QLoRA的冻结基座和可训练适配器。现在可以补上其中的数值层:原论文用NF4存放基座,以较高精度参与计算,并进一步量化尺度常数来降低元数据开销。适配器更新并不让原来被舍入的小数神奇恢复。QLoRA论文:存储类型与计算类型
作为现代模型的具体落点,Transformers v4.57.1量化概念指南给出Llama-3.1-8B-Instruct的4位加载示例,同时明确指定BF16计算。这份示例证明这些选项能在同一配置里分开表达;它没有把量化类型显式设为NF4,因此不能把它直接称为NF4训练实验。官方示例:Llama-3.1-8B-Instruct
这也是读配置的重要习惯:只推断实际写出或由文档明确规定的内容,不因为示例出现在“量化”页面,就替它补上训练方法、速度或准确率结论。
8.四种方法,改动的位置并不相同
量化改变数值表示,同一张矩阵通常仍有同样数量的位置。它与把原文件打包成可完整解压的压缩包也不同:量化把-0.73与附近一些数合并到同一档,只靠量化结果无法唯一恢复原文件;无损压缩则要求解压后逐项恢复原始内容。
剪枝则让部分连接或整个通道失效、置零或移除。仅把许多数变为零,未必让普通稠密计算少做工作,还需要合适的表示和执行支持。PyTorch:结构化与非结构化剪枝
LoRA给更新增加低秩约束,主要减少需要训练的参数,不等于删除基座。LoRA原论文
蒸馏让学生学习教师的输出或分布,学生可以使用不同规模和结构。知识蒸馏原论文
这些方法可组合,但作用层次不同:看到“小文件”,先问它少了参数、少了每参数位数,还是只保存了一份适配器。
9.怎样判断省下的空间值得
先固定原模型版本、任务题集、提示和生成规则,再比较量化版本。既看整体成绩,也检查任务中的关键错误,例如日期提取错误、代码运行失败或无依据补全。几条看起来顺畅的回答,无法排除少数重要场景退步。
同时记录权重文件、峰值运行内存、首段输出等待时间和持续生成速度。这些指标可能朝不同方向变化。还要区分平均速度与峰值资源:平均每秒输出更多token,不代表第一次出字更快;短问题能运行,也不代表最长输入仍不会超出内存。测试输入的长度、并发请求数和输出上限,应接近实际使用。
如果两版输出不同,先确定是同一解码条件,再追踪是否由数值变化引起。改变位数的同时又换了提示、采样参数和软件版本,会混入多个原因。量化通常以尽量保持原有任务行为为目标,也不应被当成主动修复知识错误的办法。
这里可以借用数值软件的两层验收。先固定同一输入,把原精度与量化后的中间输出作差,检查偏差从哪里出现;再检查日期、计算结果等任务要求有没有退步。第一层帮助定位误差,第二层判断代价是否可接受。原精度输出在这里是数值参照,并不自动成为事实标准。量化本来就允许近似,容差应事先约定;但容差内的数值差,也不能代替关键任务的回归检查。图2中接近的两个候选就可能因小扰动换序。
若部署目标是“原来装不下,现在装得下”,即使速度没有提高,也可能有实际价值;若目标是低延迟,就必须直接测延迟。
本章练习
题目
问题1|近似值能恢复多少
s=0.2、整数编码-4时,反量化是多少?它能证明原值恰好相同吗?若仍用本章[-1.4,1.4]范围记录2.6,误差是多少,为什么半格界不适用?
问题2|范围与附加开销
加入14且共用scale后,0.91为什么还原为0?每64个4-bit权重共享一个32-bit scale,平均每权重多少bit?分组为什么仍要测任务效果?
问题3|从数值到选择
A、B的logits为1.00与0.98,每项扰动绝对值不超过0.02,能保证A仍领先吗?若改为1.00与0.00呢?
思考提示
提示1|对应问题1
先乘scale;区分最近刻线舍入与越界裁剪。
提示2|对应问题2
scale由组内最大绝对值决定;把尺度开销也分摊到64个权重。
提示3|对应问题3
两项可以向相反方向移动,分差最多缩小2ε。
参考答案与解析
答案1|对应问题1
还原为-0.8,但不能反推唯一原值,-0.73也映到此档。2.6被裁剪到1.4,按还原值减原值,误差为-1.2。半格0.1只约束未裁剪的最近档位舍入,越界值不满足条件。
答案2|对应问题2
scale=14/7=2,0.91/2=0.455,舍入为0。平均为(64×4+32)/64=4.5 bit,尚未计其他元数据。分组改变舍入与裁剪分布,输入又会加权放大或抵消误差,较小权重误差不能直接替代完整任务检查。
答案3|对应问题3
第一组不能保证:A减0.02变0.98,B加0.02变1.00,顺序交换。第二组能保持这两项的顺序,因为原领先差1.00大于2ε=0.04;最不利情形仍是0.98对0.02。结论依赖给定扰动界,只讨论这两个候选。
下一章
我们已经得到一份可使用的模型,权重也可能经过近似表示。模型给出下一token的分数后,程序究竟选哪一个,怎样继续,什么时候停?下一章沿着自回归生成,把概率、采样与完整文本连起来。