3.2 QLoRA 4-bit 量化原理与显存账:为什么 4-bit 还不崩精度 读者读完这一节,应该能拿走一个可落地的判断框架:QLoRA 用 4-bit 把「基座本身」压到原来的四分之一显存,却通过「量化基座 + 浮点适配器」的组合,让精度几乎不掉;同时你会自己算清一笔账——到底 QLoRA 比纯 LoRA 省了多少显存、又比全量微调省了多少,从而知道「我的卡该选哪条路」。 3.2.1 先说结论:QLoRA = 量化基座 + 浮点 LoRA,分工很关键 纯 LoRA 已经很省了:它只训适配器,基座冻结。但基座仍然以 fp16/bf16 占着显存——一个 7B 模型 fp16 约占 14GB,GLM-5.2 这种大模型更大。
读者读完这一节,应该能拿走一个可落地的判断框架:QLoRA 用 4-bit 把「基座本身」压到原来的四分之一显存,却通过「量化基座 + 浮点适配器」的组合,让精度几乎不掉;同时你会自己算清一笔账——到底 QLoRA 比纯 LoRA 省了多少显存、又比全量微调省了多少,从而知道「我的卡该选哪条路」。
纯 LoRA 已经很省了:它只训适配器,基座冻结。但基座仍然以 fp16/bf16 占着显存——一个 7B 模型 fp16 约占 14GB,GLM-5.2 这种大模型更大。如果你的卡只有 24GB 甚至 12GB,光加载基座就爆了,LoRA 再省也没用。
QLoRA 的解法是:把基座用 4-bit 量化后加载,只占原来约 1/4 的显存;而 LoRA 适配器仍用浮点(bf16)训练。关键分工是——
所以 QLoRA 不是「把训练也用 4-bit 糊弄过去」,而是「只把不动的底座压扁,动的部分保持锐利」。这就是为什么它能用一张消费级显卡微调大模型,却不像「纯粹用 4-bit 跑全量」那样崩精度。
下面这张图把「纯 LoRA」和「QLoRA」的显存分工摆在一起:
4-bit 量化有很多种。最朴素的是「均匀 int4」:把权重范围均匀切成 16 个格子。但权重不是均匀分布的——神经网络权重大多集中在 0 附近(近似标准正态分布),均匀量化会把大量精度浪费在「几乎没值的尾部」。
QLoRA 用的是 NF4(NormalFloat 4-bit):它是一种「针对标准正态分布设计」的 4-bit 数据类型。做法是先把权重标准化到标准正态分布,再把概率密度均匀分成 16 段,使每一段里落的权重数量大致相等。结果是:权重最密集的中间区域,量化间隔最小、精度最高;稀疏的尾部间隔大、影响小。这比均匀 int4 更贴合实际权重分布,量化误差显著更小。
一句话直觉:均匀 int4 像「把人群按身高均匀分 16 桶」,多数人挤在中间几桶里被粗糙表示;NF4 像「按人数均匀分 16 桶」,每桶人数一样,中间密集区自然分得细。
4-bit 量化本身还要存一批「量化常数(quantization constants)」——比如每一块权重对应的缩放因子 scale 和零点 zero-point。这些常数通常是 fp16 存的。当模型很大、分块很多时,这些常数加起来的显存也不可忽略。
双重量化(Double Quantization) 的做法很巧:对这些 fp16 的量化常数,再做一次量化(压到 8-bit 左右存),只对「常数的常数」用 fp16 留一个小头。论文里的数据大致是:双重量化能再省约 0.37 bits/参数 的额外开销,对大模型累积下来是几个 GB 的差别。
你不用手算这些,用 BitsAndBytesConfig 一行就开了:
import torch from transformers import BitsAndBytesConfig bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", # 用 NF4 而非均匀 int4 bnb_4bit_compute_dtype=torch.bfloat16, # 计算时反量化到的精度 bnb_4bit_use_double_quant=True, # 开双重量化,再省一笔 )
提醒:bnb_4bit_compute_dquant_dtype 建议 bf16(Ampere 及更新显卡有原生支持,又快又稳);bnb_4bit_use_double_quant=True 在显存临界时很关键,几乎无副作用,默认就该开。
还有一个容易被忽略的坑:训练时优化器(如 Adam)在更新参数、尤其是处理「梯度检查点 + 大批次」时,会出现显存尖峰——平时够,某一瞬间突然不够,于是 OOM。
QLoRA 配套用了 分页优化器(Paged Optimizers),基于 CUDA 的 unified memory:当 GPU 显存瞬时吃紧,把一部分临时数据「分页」到 CPU 内存,过峰再搬回。它不改变结果,只是把尖峰削平,让你能用更小的卡跑更满的批次。BitsAndBytes 的 4-bit 训练路径默认就带了这个机制,你一般不用单独设置,但要知道「为什么我的卡明明算着够、却没崩」——是它在兜底。
光说「省」不够,我给你一笔可感知的账。下列数字是「量级估算」,用来建立直觉;准确值随 GLM-5.2 真实参数量与实现浮动,请以官方公布为准。
设某模型参数量为 P(以十亿计),fp16 下每参数 2 字节:
差距一目了然:QLoRA 把「加载门槛」从 P×14 砍到约 P×0.6,这才是它能塞进消费级显卡的根本原因。注意它省的是「基座常住显存」,适配器仍是浮点,所以「学得动」。
下面这张图把三档的显存量级收成一条直观对比:
到这里你一定想问:把基座压到 4-bit,精度不就废了?QLoRA 之所以「压了还不崩」,靠三根支柱同时撑着:
一句话:量化发生在存储,浮点发生在计算与学习。这就是为什么 QLoRA 的下游效果常常能逼近全量微调——它把「省显存」和「保精度」拆到了不同层面,各管一摊。
# 呼应 2.1:QLoRA 的完整加载接线(GLM-5.2 权重路径以官方文档为准) from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig import torch bnb = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, ) tok = AutoTokenizer.from_pretrained("填 GLM-5.2 权重路径", trust_remote_code=False) model = AutoModelForCausalLM.from_pretrained( "填 GLM-5.2 权重路径", quantization_config=bnb, device_map="auto", trust_remote_code=False, # 是否开启以智谱官方文档为准 )
结合前面的显存账,给一份决策建议(经验性,非绝对):
我的主张很明确:对绝大多数「把 GLM-5.2 调成垂直领域专家」的需求,QLoRA 是性价比最优解——一张消费级显卡就能开工,效果又不拉胯。除非你卡多到用不完、又对那一点点精度锱铢必较,否则没必要上全量。
前面说「适配器浮点保精度」,这里把机制讲透,免得你以为只是惯例。4-bit 量化的误差来自一个事实:16 个离散值永远无法精确表示连续的权重分布,总有「最近格子」的取整误差。这个误差对冻结的基座无所谓——它不参与梯度,误差是固定的、不被放大。
但若把适配器也量化成低精度,事情就变了:适配器是可训练参数,每一步更新都带着量化取整的噪声,这些噪声会随训练步数不断累积进权重,等价于给学习过程持续注入随机扰动,结果往往是训不稳、效果掉。QLoRA 的关键设计之一正是让适配器(以及优化器状态)完整留在 fp16/bf16,从物理上切断这条噪声累积链。一句话:量化噪声可以接受「静态地住在基座里」,但不能「动态地被训练放大」。
QLoRA 不是免费午餐,三类场景下它的差距会被放大,决策时要心里有数:
我的主张仍是:对「垂直领域适配」这种典型需求,上面三者大多不成立,QLoRA 性价比最优;只有当你卡多、数据海量、且咬着那几个点的指标不放时,才值得为纯 LoRA/全量多掏显存。
最后补一笔完整的账,很多人只算「权重」,结果实际 OOM 时一脸懵。真正吃显存的还有两块:
P×14 砍到 P×0.6,隐藏主因正是可训练参数从 P 降到约 0.1%P,优化器状态随之崩塌式下降;基座虽被量化,但它本来就不需要优化器状态(冻结)。把这两块算上,你就能解释一个现象:为什么「QLoRA 比全量省的不是 4 倍而是 20 倍」——省的主要不是权重存储,而是被冻结撬动的优化器状态。
光算账不够,给一个开跑前就能做的验证:加载前后用 torch.cuda.memory_allocated() 打点,对比「fp16 基座加载」与「4-bit 基座加载」的峰值占用,正常应看到接近 4 倍的差距;再用 model.get_memory_footprint()(transformers 提供)打印模型显存足迹,4-bit 路径下应明显落在 P×0.6 量级附近。若差距远小于 4 倍,先查三件事:是否真传了 quantization_config(没传就是普通 fp16 加载,白忙)、device_map 是否生效、以及是否还有别的浮点副本被无意保留。这个 30 秒的验证,能提前排除「以为在跑 QLoRA 其实在跑 LoRA」的尴尬。
一句话带走:QLoRA 把「不动的基座」压成 4-bit(NF4 + 双重量化)省下常住显存,把「在学的适配器」留作浮点保住精度,量化只在存储、计算仍浮点,所以压了还不崩。算清那笔 P×14 → P×0.6 的账(真正大头是优化器状态随可训练参数崩塌式下降),你就知道:消费级显卡微调 GLM-5.2,QLoRA 几乎是默认答案。开跑前用 memory_allocated 打个点验证 4 倍差距,能提前避开「以为在跑 QLoRA 其实在跑 LoRA」的坑。
下一章(第 4 章)我们将离开原理,进入真实垂直领域的实战案例——把这一章的数学直觉,落到医疗、法律、客服三类具体场景的配置与踩坑里。