一、介绍LoRA
用生活例子解释LoRA,就像给一个全能大厨做"定制菜谱小抄",而不是让他回烹饪学校重修一遍。
我分三步给你讲透:
-
第一步:先看传统微调有多"重"
想象你请来一位精通全球菜系的米其林大厨 (这就是大模型)。现在你想让他专做你家楼下的**"川味面馆"** 。传统微调(全参数训练)相当于:让大厨把刀工、火候、酱料配方等全部几亿项技能都推倒重练,哪怕他只学一个放辣椒油的新手法。这极其耗电(算力)、占地方(显存),而且练完容易"失忆",忘了怎么做西餐(灾难性遗忘)。
-
第二步:LoRA的精髓是"旁路小抄"
LoRA的做法是:大厨原来的所有手艺(原始权重)冻住不动 。你只在他围裙口袋边贴一张极小的"辣油配方卡"(这就是低秩矩阵)。
这张卡片只记录针对"川味面馆"的调整:比如"豆瓣酱多5克,花椒粉少2克"。大厨做面时,把原本的火候(原始输出)加上小抄的调整(低秩增量),一碗地道川面就出锅了。
-
第三步:这么干有三大逆天好处
-
省内存:原来要备份整个大厨(70亿参数),现在只需存这张小卡片(几MB)。
-
快切换 :明天让他改做"粤式茶点",不用重训,只需换一张新卡片插上,大厨立刻切换风格。
-
不健忘:因为大厨本体没动,他做完川面,下一秒照样能做法国大餐。
-
进阶一点的内行视角 :
LoRA之所以有效,是因为它假设模型适配新任务时,权重的变化是"低秩"的(即可以用两个小矩阵相乘来近似一个大变化矩阵)。在实际操作中,秩(Rank) 的选择很关键------秩=4或8通常够用,设太大了反而会过拟合,相当于小抄写得太细,换家店就没法用了。
所以下次听到LoRA,你就想:"冻住本体,挂个小外挂"。这正是如今百亿大模型能轻松下放到个人显卡上微调的核心法宝。
第1步:安装核心"适配器"库
bash
复制
下载
pip install peft transformers datasets accelerate
peft就是LoRA的官方实现库,它负责把"小抄"贴到模型上。
第2步:加载基础模型(冻结本体)
python
复制
下载
from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model
# 加载原始模型(就像请来那位“全能大厨”)
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2-7B")
# 配置LoRA“小抄”参数
lora_config = LoraConfig(
r=8, # 秩(小抄的复杂度),通常4-16
lora_alpha=32, # 缩放系数,控制小抄影响强度
target_modules=["q_proj", "v_proj"], # 贴在注意力层的“查询”和“数值”矩阵上
lora_dropout=0.05, # 防止小抄太死板
task_type="CAUSAL_LM"
)
# 贴上小抄,模型参数量仅增加 0.1% 左右
lora_model = get_peft_model(model, lora_config)
lora_model.print_trainable_parameters() # 输出:可训练参数 ~ 4M(原7B的0.06%)
第3步:准备"李白朋友圈"数据集
python
复制
下载
# 格式:指令 + 输出
data = [
{"instruction": "写一句抒发孤独的感慨", "output": "举杯邀明月,对影成三人。"},
{"instruction": "写一句感叹时光流逝", "output": "君不见黄河之水天上来,奔流到海不复回。"},
# ... 准备50-100条高质量对子即可
]
关键点 :微调数据不在多,而在精。100条精心设计的对话,比10000条爬虫乱抓的有效。
第4步:训练(只更新小抄参数)
python
复制
下载
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir="./lora_li_bai",
per_device_train_batch_size=1, # 7B模型+LoRA,单张24G显卡够用
gradient_accumulation_steps=8, # 模拟8的batch size
learning_rate=2e-4, # LoRA学习率通常比全量微调高
num_train_epochs=3,
logging_steps=10,
)
trainer = Trainer(
model=lora_model,
args=training_args,
train_dataset=tokenized_dataset,
)
trainer.train()
训练时你看显存占用,原本需要24G+,现在12G就够,这就是LoRA的魅力。
第5步:推理与合并(项目上线两种方式)
python
复制
下载
# 方式A:直接加载“基础模型+小抄”(推荐开发测试)
from peft import PeftModel
lora_model = PeftModel.from_pretrained(base_model, "./lora_li_bai")
response = lora_model.generate("写一句登高望远的感慨")
# 输出:欲穷千里目,更上一层楼。(已被微调风格影响)
# 方式B:合并导出为独立模型(推荐生产部署)
merged_model = lora_model.merge_and_unload()
merged_model.save_pretrained("./li_bai_qwen2")
# 这样就变成了一个完整的Qwen2-李白版,推理速度更快
(1)LoRA微调之后保存结果是什么
保存的不是一个完整的"新模型",而是一个"轻量级补丁包"。我分两层给你讲透:
第一层:磁盘上你实际看到什么文件
跑完LoRA训练后,你的保存文件夹里不会有pytorch_model.bin(几十GB的大文件),而是这几个小文件(总共就几MB到几百MB):
text
复制
下载
lora_li_bai/
├── adapter_config.json # 小抄的“说明书”(记录r=8、target_modules等配置)
├── adapter_model.bin # 小抄的“具体内容”(训练出来的那几M个权重参数)
└── tokenizer/ # 分词器文件(复制的,没变)
关键区别:
-
传统全量微调:保存的是"完整的厨师本人"(本体+新技能),文件几十GB。
-
LoRA微调:保存的只是"厨师围裙兜里的那张配方小抄"(仅调整量),文件几十MB。
二、QLoRA(量化版)
(1)用QLoRA的加载模型和不用QLoRA加载模型差多少
结论先行:用QLoRA加载,显存占用只有不用时的 1/4 到 1/6。
直接上硬核数据对比(以70亿参数模型为例):
| 加载方式 | 精度 | 显存占用 | 相当于 |
|---|---|---|---|
| 不用QLoRA(常规加载) | FP16 (半精度) | ~14 GB | 寄一个大号行李箱 |
| 不用QLoRA(全精度) | FP32 (单精度) | ~28 GB | 寄一个双门冰箱 |
| 用QLoRA(4-bit量化加载) | NF4 (4-bit) | ~4 GB | 寄一个手提袋 |
同样的7B模型,QLoRA帮你把显存从14GB砍到了4GB,省了整整10GB。
为什么会差这么多?拆开看"包裹"里装了什么:
-
常规加载(FP16) :每个参数用16位(2个字节)存储。7B × 2字节 = 14GB。
-
QLoRA加载(NF4) :每个参数用4位(0.5个字节)存储。7B × 0.5字节 = 3.5GB ≈ 4GB。
QLoRA本质上是对模型权重做了**"极致压缩"**------就像把羽绒服抽成真空袋,体积缩到原来的1/4,用的时候再"拍松"恢复。
对你选显卡的直接影响:
| 你的显卡 | 不用QLoRA能跑多大模型 | 用QLoRA能跑多大模型 |
|---|---|---|
| RTX 3060 (12GB) | 7B 勉强(会爆显存) | 13B 流畅 |
| RTX 4060 (8GB) | 7B 跑不动 | 7B 流畅 |
| RTX 4090 (24GB) | 13B 流畅 | 70B 勉强能跑 |
看到了吗?QLoRA直接让你的显卡"升了一到两级"。
但是!一个你必须知道的"代价":
显存省了,但推理速度会略慢 (约慢10%-15%),因为4-bit数据在计算时需要反量化(解压缩)成FP16再算。这就像真空压缩的羽绒服,拿出来得拍几下才能穿。
不过这点速度损失,在"能跑"和"跑不动"之间,根本不算事。
实战建议:
-
如果你只有8GB显存 ,必须用QLoRA 才能跑7B模型,否则连加载都报
CUDA Out of Memory。 -
如果你有24GB显存 ,不用QLoRA直接加载13B模型速度更快,但想跑70B时,还得靠QLoRA。
(2)用LoRA的加载模型和不用LoRA加载模型差多少
结论先行:用LoRA加载,显存占用和不加载完全一样,没有任何差别。
为什么呢?因为LoRA在"加载模型"这一步,做的操作是一模一样的。
| 操作步骤 | 不用LoRA | 用LoRA |
|---|---|---|
| 第1步:加载基座模型到显存 | ✅ 加载 (14GB) | ✅ 加载 (14GB) ------ 完全一样 |
| 第2步:附加LoRA适配器 | ❌ 无 | ✅ 额外占 0.1-0.5GB |
| 总计显存 | 14 GB | 14.3 GB (几乎没差) |
关键点:LoRA不改变加载方式,它只是在模型"上面"额外挂了一层小参数。
那为什么你会觉得LoRA省显存?
这里有个常见的认知混淆:
-
LoRA 省的是训练时的显存(因为冻结了基座模型,不计算梯度,省下了梯度和优化器状态那十几GB)。
-
但LoRA 不省加载时的显存(基座模型该多大还是多大,得完整加载)。
打个比方:
LoRA像给大厨贴张"小抄"。请大厨本人进厨房(加载模型) ,不管贴不贴小抄,他占的空间都一样大。小抄只在**他学做新菜(训练)**时省草稿纸,不改变他本人的体积。
真正省"加载显存"的是QLoRA(量化),不是LoRA。
我给你做个终极对比表:
| 方法 | 加载时显存 (7B) | 训练时显存 (7B) | 省的是哪个阶段 |
|---|---|---|---|
| 不用任何优化 | 14 GB | 28 GB | --- |
| 只用LoRA | 14 GB | 14.5 GB | ✅ 省训练显存 |
| 只用QLoRA | 4 GB | 7 GB | ✅ 省加载+训练显存 |
| LoRA + QLoRA | 4 GB | 7.5 GB | ✅ 两者都省 |
所以对你实际操作的指导意义是:
-
如果你显存只有8GB,想加载7B模型做推理:
-
❌ 不用LoRA → 爆显存 (需14GB)
-
❌ 用LoRA → 照样爆 (需14GB)
-
✅ 必须用QLoRA加载 → 只需4GB,流畅运行
-
-
如果你显存有24GB,想训练7B模型:
-
❌ 不用LoRA → 爆显存 (需28GB)
-
✅ 用LoRA → 只需14.5GB,省下来了
-
✅ 用QLoRA → 只需7.5GB,更省但速度稍慢
-
一句话记住:
LoRA省训练不省加载,QLoRA才省加载。 加载时显存大小只看精度(FP16/NF4),不看有没有LoRA。
现在你应该清楚了:如果本地推理时显卡报错,该上的是QLoRA加载 (改load_in_4bit=True),而不是LoRA。