分析LoRA合并对磁盘占用、模型变体数量和量化损失的影响,8B模型rank-16适配器仅占原模型0.78%。
训练好的 LoRA 可以合并到基础权重中,生成一个独立完整的模型,也可以作为单独的文件保留,在运行时加载时应用。这个选择看起来像是一个打包细节,但实际上涉及磁盘、关于你能服务多少个变体、以及量化损失落在哪里的决策。本文假设读者已经了解 LoRA 是什么。
驱动其他一切的这种不对称性值得推导而非断言。LoRA 用形状为 d_out × r 和 r × d_in 的两个矩阵的乘积,来替换对形状为 d_out × d_in 的权重矩阵的更新,因此它存储 r × (d_in + d_out) 个参数,而不是 d_in × d_out 个。
以 Llama 3.1 8B 中的一个 query 投影为例:4096 × 4096,即 16,777,216 个参数。在 rank 16 时,该张量的适配器为 16 × (4096 + 4096) = 131,072 个参数——这仅是被修改内容的 0.78%。在所有 32 层中的每个线性投影(包括更宽的 gate、up 和 down 投影)上应用时,8B 模型上的 rank-16 适配器在 fp16 下落在几十兆字节,而基础模型则是几吉字节。
这个比率既是将适配器分离保存的全部理由,也是合并如此吸引人的原因:比被修改对象小两个数量级的东西很容易丢失追踪,而合并后的文件无法用错误的适配器或没有适配器来运行。
合并为每个变体生成一个完整的模型文件。三个 8B 的微调版本,采用 Q4_K_M 量化,每个约 4.58 GiB,总计约 13.7 GiB,并且每个新变体都会线性增长。分开保存则是一个基础文件加三个小适配器——约 4.58 GiB 再加几百兆字节。
差异不仅仅是磁盘。还有当基础模型更新时你需要重新下载的内容、验证完整性时你需要重新校验的内容,以及备份的成本。每周生成一个微调版本的工作流,在几个月内就会将合并变成存储问题,而且本地模型的磁盘预算比任何人预期的都要快。
与此相对:合并后的模型是一个工件对应一个校验和。没有办法让它与不匹配的基础模型一起运行,不会有关错误的缩放参数,复制到另一台机器时也不会忘记第二个文件。对于部署到不受控制的机器上的任何东西,这比吉字节本身更重要。
在推理时,合并后的模型与任何其他模型无法区分:权重就是权重,没有额外的内存占用。
在运行时应用的适配器成本比其文件大小所显示的要高,理解其原因很有价值。llama.cpp 通常映射 GGUF 文件,使权重页面与页面缓存共享且从不复制。应用适配器意味着修改权重,这意味着这些页面不能再作为文件的只读映射——加载 LoRA 会禁用 mmap。实际上,这意味着模型被读入匿名内存而不是映射,这增加了主机内存使用并使加载变慢,但换来的是灵活性。
这种灵活性是真实且具体的。llama-server 可以同时保存多个适配器并暴露它们的缩放因子,因此一个运行中的进程在 VRAM 中保留一份基础权重副本,可以通过改变哪个适配器以什么强度激活来服务多个微调版本。这是合并根本无法做到的部署方式:使用合并文件,三个变体意味着三次模型加载和三倍的 VRAM,而在单个消费级显卡上,这通常意味着不可行。服务多个 LoRA 总体上覆盖了这种模式。
# Runtime application, llama.cpp
llama-server -m base-Q4_K_M.gguf \
--lora ./adapters/support-tone.gguf \
--lora-scaled ./adapters/sql-style.gguf 0.6
# Merging into a single file instead
llama-export-lora \
-m base-f16.gguf \
--lora ./adapters/support-tone.gguf \
-o base-with-tone-f16.gguf
这是比磁盘更常决定答案的部分,而且很容易出错。
适配器在某种精度下针对权重进行训练。将其合并到已量化的基础模型中意味着向已被捕捉到量化网格的权重添加全精度更新,然后再将结果捕捉回来——两个有损操作叠加,第二个操作应用于误差预算已经耗尽的基础模型。llama.cpp 项目已经收到这方面的报告,表现为合并后的模型似乎失去了训练效果。llama-quantize 上的 --allow-requantize 标志在其自身文档中有明确警告,requantizing "可能严重降低质量,相比于从 16 位或 32 位量化"。
有效的顺序是:首先合并到全精度基础模型,然后对合并结果进行一次量化。这要求你在磁盘上有 fp16 或 bf16 的基础模型,这对于 70B 来说是一个很大的要求,本身也是在只有量化文件的机器上保持适配器分离的一个理由。参见 requantizing 对一般情况的成本。
针对量化基础模型的运行时应用也并非没有这个问题——无论你选择哪条路线,适配器都是在量化权重上应用的。它避免的是对修改后权重进行的第二次量化 pass,这是两次损失中较大的一个。
One fine-tune, shipped to machines you do not control — merge, from the full-precision base, then quantize. One artifact, one checksum, no way to run it wrong.
Several fine-tunes on one card — keep them separate. This is the case merging cannot serve at all, because merged variants do not share VRAM.
Still iterating on the fine-tune — keep them separate. A merge is minutes of compute and a full file write per attempt; swapping an adapter is a restart.
You only have the quantized base — keep them separate, and accept that you are applying an adapter to quantized weights. Merging here means requantizing, which is the outcome you were trying to avoid.
Air-gapped or tightly audited deployment — merge. Two files with a scaling parameter between them is two things to verify and one more way for a deployment to be subtly wrong. See air-gapped deployment.
One thing that is not a factor either way: licensing. The base model's licence governs the merged artifact exactly as it governs the base, because the merged weights are a derivative of it. Merging does not launder a restriction and separating does not create one. Check the base licence for redistribution terms before you publish either form.