第 14 课那个 2,677,632 个参数的小模型, 全量微调只要 0.04 GB,免费显卡随便跑。 可真实世界里的模型是 6,738,415,616 个参数 —— 同一个「每个参数 16 字节」的规矩套上去,就是 107.8 GB,七张 16 GB 的卡才装得下。 这一课给你两把刀:量化(把每个参数从 16 位压到 4 位)和 LoRA(只训练一小撮外挂参数),叠起来 = QLoRA, 让一个 7B 模型在 3.9 GB 里就能微调。
这一课的性质和前十四课不太一样。前面每一课都在拆「模型怎么算」,这一课拆的是「模型怎么存」。 听起来像工程杂活,可它决定了一件很现实的事:你到底能不能亲手微调一个真实的模型。 而且这一课里没有一个新公式 —— 全是加减乘除。 真正难的地方在于:要先知道每个参数身上到底挂着几样东西。
你可能听过一句话:「7B 的模型全量微调要一百多 GB」。 一百多是多多少?为什么不是「7B × 2 字节 = 14 GB」? 因为训练时的参数,身上不止挂着它自己。 下面这张表把每一样东西点出来 —— 一行一行加起来,正好是那个 16。
| 每个参数身上挂着的东西 | 字节 | 为什么 |
|---|---|---|
| 权重(fp16) | 2 | 模型本身,前向要用 |
| 梯度(fp16) | 2 | 反向传播算出来的,每个参数一份 |
| 主权重(fp32) | 4 | 更新时要够精确,所以留一份全精度的 |
| Adam 一阶动量 m | 4 | 梯度的滑动平均,fp32 |
| Adam 二阶动量 v | 4 | 梯度平方的滑动平均,fp32 |
| 合计 | 16 | 全量微调:每个参数 16 个字节 |
第 12 课算过一笔账:7B 光优化器状态(参数 + 一阶动量 + 二阶动量,三份 fp32)就要 78.2 GiB(十进制是 84.0 GB)。 这一课说「权重之外的那部分」是 94.3 GB ——两个数不一样,但都对。
差别全在口径,而且只有两处:
① 参数量取谁。第 12 课用的是「7B」这个外号,也就是 7,000,000,000;这一课用的是官方公布的准确值 6,738,415,616 —— 差了 3.7%, 「7B」本来就是四舍五入的说法。
② 每个参数算几份。第 12 课是纯 fp32 教科书口径:参数 + m + v 三份 × 4 字节 = 12 字节,即 7,000,000,000 × 12 = 84 GB。 这一课是混合精度 + AdamW 实战口径:fp16 梯度 2 + fp32 主权重 4 + 两个 fp32 动量 8 = 14 字节(权重那 2 个字节不算在内),即 6,738,415,616 × 14 = 94,337,818,624 字节 = 94.3 GB。多出来的是「梯度要单独存一份 fp16」,少掉的是「权重按 fp16 算而不是 fp32」。
两套口径加起来的量都在「一百多 GB」这一档,所以一张 16 GB 的卡横竖都塞不下。以后看到不同资料给的显存数字差一截,先问一句:它是按什么口径算的?
第一个念头很自然:既然那 14 个字节省不掉,那把每个字节本身变小行不行? fp16 用 16 个比特存一个权重 —— 可权重真有那么精细吗? 真实权重是训练出来的一堆小数,它们大部分挤在 0 附近, 真正需要「高精度」的地方其实很有限。 于是有了量化:不再记录精确的数,只记录「它离哪个档位最近」。 代价是误差,问题是误差有多大。
0.122量化砍的是「每个参数占几个字节」。可回过头看第 1 站那张表, 会不会发现一件事:那 14 个字节里,绝大部分根本不是权重本身—— 是梯度、是优化器状态。既然模型本身只要 2 个字节就够, 那能不能干脆让绝大多数参数连梯度都不要? LoRA 就是这么干的:把原权重冻住,在它旁边挂一对小矩阵, 只训这一对。算下来,可训练的参数量是 0.1% 这个量级。
B、A, 只有这一对小矩阵要梯度、要优化器状态。| 秩 r | 算式(32 层 × 4 投影 × r × 2 × 4096) | 可训练参数 · 占比 | 适配器 |
|---|---|---|---|
| 4 | 32 × 4 × 4 × 2 × 4096 = 4,194,304 | 4,194,304 · 0.062% | 8.4 MB |
| 8 | 32 × 4 × 8 × 2 × 4096 = 8,388,608 | 8,388,608 · 0.124% | 16.8 MB |
| 16 | 32 × 4 × 16 × 2 × 4096 = 16,777,216 | 16,777,216 · 0.249% | 33.6 MB |
| 32 | 32 × 4 × 32 × 2 × 4096 = 33,554,432 | 33,554,432 · 0.498% | 67.1 MB |
| 64 | 32 × 4 × 64 × 2 × 4096 = 67,108,864 | 67,108,864 · 0.996% | 134.2 MB |
| 秩 r | 冻结基座(只读,fp16) | 外挂参数要的 16 字节 | 训练合计 |
|---|---|---|---|
| 4 | 13.5 GB | 0.07 GB | 13.5 GB |
| 8 | 13.5 GB | 0.13 GB | 13.6 GB |
| 16 | 13.5 GB | 0.27 GB | 13.7 GB |
| 32 | 13.5 GB | 0.54 GB | 14.0 GB |
| 64 | 13.5 GB | 1.1 GB | 14.6 GB |
不会。LoRA 只是训练时的形状:W' = W + (α/r)·B·A。W 是原来的 d×d 矩阵,B·A 是 r 很小时的两个瘦矩阵 (d×r 和 r×d)。训练完把 (α/r)·B·A 一次性算出来、 加进 W,就得到一个新的 d×d 矩阵 ——和原来一模一样大,推理路径一个字都没变。
这里那 32 层 × 4 个投影,每个都是「乘出来再相加」, 加法本身不改变形状。所以:LoRA 是「训练时省显存」,不是「推理时省算力」 —— 这两件事经常被混着说,面试也爱问。
W + (α/r)·B·A, 所以真正的缩放系数是 α/r。改 r 的时候一般要同步改 α, 否则等于偷偷改了学习率 —— 这是调 LoRA 时最常见的坑。训练显存的大头不在权重上,在「让权重能被训练」这件事上。一个参数身上挂着五样东西:fp16 权重 2 + fp16 梯度 2 + fp32 主权重 4 + Adam 一阶动量 4 + 二阶动量 4 = 16 个字节, 而权重自己只占 2 个。所以 7B 档 全量微调要 6,738,415,616 × 16 = 107.8 GB, 而光装下它推理只要 13.5 GB。 两把刀分别砍这 14 个字节的两头:量化把每个参数从 16 位压成 0.5625 字节(基座 13.5 → 3.8 GB,精度代价是相对误差 11.8%, 但模型照样能用);LoRA 冻住基座,只训 32 × 4 × r × 2d 个外挂参数 (r=8 时 0.124%、适配器 16.8 MB), 让优化器状态那 94.3 GB 直接归零。 叠起来就是 QLoRA:107.8 → 13.6 → 3.9 GB, 省 27 倍 ——一张 16 GB 的免费显卡,从「装不下」变成「还剩一大半」。最后一句话:这两把刀都不改变模型的形状, LoRA 训完合并回去、量化只是换个存法 ——它们省的是账单,不是能力。
W' = W + (α/r)·B·A —— 还是一个 d×d 矩阵,LoRA 不拖慢推理, 所以能一个基座配 N 个几 MB 的适配器换来换去。