微调(Fine-tuning)是很多团队在 LLM 落地时第一个想到的动作,也是最容易被误用的动作。真实情况是:绝大多数「效果不好」的问题,根因在检索质量、提示结构或数据本身,而不是模型缺了那几千条样本的领域知识。把微调当成万能药,结果往往是花了两周标注数据、烧了几百卡时,换来的收益还不如把 RAG 的 chunk_size 从 1024 调到 512。
当微调确实该做的时候,第二个坑紧接着出现:很多人默认「微调 = 全参微调」,于是直接撞上显存墙。一个 7B 模型用 FP16 做全参微调,权重、梯度、Adam 优化器状态三者叠加就要接近 90 GB,单张 80 GB 卡根本装不下;70B 更是要一整机 A100 才勉强跑起来。LoRA 与 QLoRA 解决的正是这个问题:把可训练参数从几十亿压到几百万,显存需求下降一个数量级,而效果在多数任务上能追平全参微调。
本文按一条完整的微调流水线来讲:先判断该不该微调,再算清全参微调的成本,然后深入 LoRA 的低秩原理与超参选择、QLoRA 的量化与双重量化,接着是数据准备、训练框架、显存估算、权重合并与部署,最后落到评测与选型决策。全篇聚焦训练侧,推理侧的量化和显存公式请参考 量化与显存优化 ,两者互补但不重叠。
目录
- 什么时候该微调:微调、RAG 与提示的边界
- 全参微调的成本结构
- LoRA 的低秩分解原理
- 秩、alpha 与目标模块的选择
- QLoRA 的 4bit 量化与双重量化
- 数据准备与指令格式
- 训练框架:PEFT、TRL 与 DeepSpeed
- 显存估算与超参配置
- 合并权重与部署
- 评测与回归
- 权衡取舍
- 常见坑清单
- 小结
1. 什么时候该微调:微调、RAG 与提示的边界
三种手段解决的是三类不同的问题,混用是最大的浪费来源。
| 手段 | 解决什么 | 典型成本 | 迭代速度 | 适合的信号 |
|---|---|---|---|---|
| 提示工程 | 任务描述不清、格式不稳 | 小时级 | 分钟级 | 模型有能力但没被引导 |
| RAG | 知识缺失、知识过期 | 天级 | 小时级 | 问题需要外部事实、且事实会变 |
| 微调 | 风格、格式、领域行为、能力压缩 | 周级 | 天级 | 知识已具备但行为不对 |
判断顺序应该是自上而下的:先问「模型是不是根本不知道这件事」,是则走 RAG;再问「模型知道但输出格式或风格不对」,是则先试提示与少样本;只有当提示已经调到极限、且失败样本呈现出稳定的行为模式(比如始终不遵守某种业务话术、始终不会输出某种结构化字段)时,才轮到微调。
一个常被忽略的判据是知识 vs 行为。微调擅长固化行为(语气、格式、推理路径、工具调用习惯),不擅长注入事实——因为事实会被压缩进权重,既容易幻觉又无法更新。想让模型记住「本公司 2026 年新的退款政策」,微调是错的选择,检索才是对的。反之,想让模型「永远用客服话术回复、永远先共情再给方案」,微调是对的。
另一个判据是推理成本。把一个大模型的能力蒸馏进小模型,是微调最有商业价值的用法:用 GPT-4o 级模型生成几万条高质量样本,微调一个 7B 模型去模仿,线上推理成本能降一到两个数量级。这条路径的收益远大于「让 7B 更懂某个领域」。
2. 全参微调的成本结构
全参微调之所以贵,不是贵在权重,而是贵在优化器状态。用 AdamW 训练时,每个可训练参数需要额外的梯度(1 份)与优化器状态(2 份动量,通常 FP32),加上权重本身,显存需求约为参数量乘以 16 字节(混合精度下)。
全参微调显存 ≈ 参数量 × (2 权重 + 2 梯度 + 8 优化器状态) 字节 + 激活
≈ 参数量 × 12 字节 + 激活
按这个口径估算不同规模模型的训练侧显存(不含激活):
| 模型规模 | 权重 (BF16) | 梯度 (BF16) | 优化器状态 (FP32) | 小计 | 最小硬件 |
|---|---|---|---|---|---|
| 1.5B | 3.0 GB | 3.0 GB | 12.0 GB | 18 GB | 单卡 24G |
| 7B | 14.0 GB | 14.0 GB | 56.0 GB | 84 GB | 2×A100 80G |
| 13B | 26.0 GB | 26.0 GB | 104 GB | 156 GB | 4×A100 80G |
| 70B | 140 GB | 140 GB | 560 GB | 840 GB | 16×A100 80G 起 |
这还只是静态部分。激活值随 batch size 与序列长度增长,长序列训练时激活可能再吃掉几十 GB,所以实际还要叠加梯度检查点(gradient checkpointing)与 ZeRO 分片。70B 全参微调在工程上意味着数十张卡、NCCL 通信调优、以及动辄数天的训练窗口——这是只有模型厂商与少数大厂才值得做的事。
对绝大多数团队,正确的默认选项是 PEFT(Parameter-Efficient Fine-Tuning),其中 LoRA 是事实标准。它把可训练参数压到 0.1% 到 1%,显存需求随之降到全参的几分之一。
3. LoRA 的低秩分解原理
LoRA(Low-Rank Adaptation)的核心假设是:微调带来的权重更新矩阵 ΔW 是低秩的。既然 ΔW 的有效秩远小于原始维度,就没必要训练一个完整的 ΔW,而是把它分解成两个小矩阵的乘积。
对原始权重 W0 ∈ R^(d×k),前向计算变为:
h = W0 · x + ΔW · x = W0 · x + (α / r) · B · A · x
其中 B ∈ R^(d×r), A ∈ R^(r×k), 秩 r << min(d, k)
初始化:A ~ N(0, σ²) 高斯,B = 0(保证训练起点等价于原模型)
几个关键点:
- B 初始化为零,使得训练开始时 ΔW = 0,模型行为与基座完全一致。这是 LoRA 稳定收敛的前提,若 A、B 都随机初始化,训练初期会引入巨大噪声。
- α / r 是缩放因子。α 是常数缩放,r 是秩。当调整 r 时,同时按比例调整 α,可以近似保持更新幅度不变,这是「换秩不用重调学习率」的经验来源。
- 推理零延迟。训练完成后可以把 B·A 加回 W0,得到一个新的稠密权重,推理时与原模型完全同构,没有任何额外计算。这一点比 Adapter 类方法(串接额外层,推理多一层)有本质优势。
LoRA 的参数量公式为:可训练参数 = r × (d_in + d_out) × 目标模块数。以 Qwen2.5-7B(28 层,hidden 3584,28 个注意力头,4 个 KV 头,head_dim 128)为例,只对 q/k/v/o 四个投影加 LoRA,r=16:
| 目标模块 | 权重形状 | 单层 LoRA 参数 | 28 层合计 |
|---|---|---|---|
| q_proj | 3584 × 3584 | 16 × 7168 = 114,688 | 3.21 M |
| k_proj | 3584 × 512 | 16 × 4096 = 65,536 | 1.83 M |
| v_proj | 3584 × 512 | 16 × 4096 = 65,536 | 1.83 M |
| o_proj | 3584 × 3584 | 16 × 7168 = 114,688 | 3.21 M |
| 合计 | — | — | 约 10.1 M |
10.1 M 相对 7B 是 0.14%。如果扩展到全部线性层(含 MLP 的 gate/up/down),参数量约翻三倍到 0.4% 左右。这个量级意味着优化器状态只有几十 MB,显存瓶颈从「优化器」转移到了「权重与激活」。
4. 秩、alpha 与目标模块的选择
三个超参决定了 LoRA 的表达能力:秩 r、缩放 α、以及挂在哪些模块上。
秩 r
r 控制可表达的子空间维度。经验规律是:任务与基座分布差异越大、任务越复杂,需要的 r 越大。
| 任务类型 | 推荐 r | α | 说明 |
|---|---|---|---|
| 风格/格式对齐 | 4 - 8 | 8 - 16 | 更新量小,低秩足够 |
| 领域问答 | 16 - 32 | 32 - 64 | 平衡点,最常用 |
| 代码/数学能力 | 32 - 64 | 64 - 128 | 需要更大子空间 |
| 多任务混合 | 64 - 128 | 128 - 256 | 任务冲突需更大容量 |
r 不是越大越好。r 超过 64 后,收益迅速递减,而可训练参数线性增长、过拟合风险上升。多数团队应该从 r=16、α=32 起步,只有在验证集上观察到欠拟合(训练损失下不去)时才往上调。
缩放 α
α 与 r 的比值决定更新幅度。实践中常固定 α = 2r(即 α/r = 2),这样改 r 时不必重新调学习率。也有观点认为固定 α(如 α=16)并让它随 r 自动稀释更稳。无论哪种,关键是一旦选定就保持一致,不要在不同实验间混用。
目标模块
只挂 q/v 是原论文的默认配置,但后来的实践(尤其是 QLoRA 论文)发现全线性层(q/k/v/o 加 MLP 的 gate/up/down)效果明显更好,代价是参数量与显存略增。
| 配置 | 可训练参数(7B, r=16) | 效果 | 建议 |
|---|---|---|---|
| 仅 q_proj, v_proj | 约 3.2 M | 基线 | 显存极紧时用 |
| 全部注意力投影 | 约 10.1 M | +2 到 4 分 | 常用起点 |
| 全部线性层 | 约 30 M | +4 到 6 分 | 效果优先 |
判断目标模块是否够用,可以看训练损失是否稳定下降到接近零:如果损失卡在高位,多半是容量不足,先加模块再加秩。
5. QLoRA 的 4bit 量化与双重量化
LoRA 已经把优化器开销压下去了,但基座权重仍以 BF16 常驻,7B 占 14 GB、70B 占 140 GB。QLoRA 的思路是:把冻结的基座权重量化到 4bit 存储,前向计算时临时反量化到 BF16,从而把权重占用压到约四分之一,而可训练的 LoRA 适配器仍保持 BF16。
QLoRA 的三个关键组件:
- NF4(NormalFloat4):假设权重近似正态分布,把 16 个量化级别按标准正态的分位数非均匀放置,使每个 bin 的期望样本数相等,比均匀 INT4 的信息损失更小。
- 双重量化(Double Quantization):量化本身会为每个 block(通常 64 个权重)产生一个 FP32 的 scale。这些 scale 数量可观(70B 约 1 亿个),QLoRA 再对 scale 做一次 8bit 量化,把 scale 的开销从每参数 0.5 bit 降到 0.127 bit,70B 上能省约 3 GB。
- 分页优化器(Paged Optimizers):借助 NVIDIA 统一内存,在显存峰值时把优化器状态分页到 CPU,避免偶发的 OOM。
量化对显存的影响:
| 配置 | 7B 权重 | 70B 权重 | 相对 BF16 |
|---|---|---|---|
| BF16 | 14.0 GB | 140 GB | 100% |
| NF4 | 3.5 GB | 35 GB | 25% |
| NF4 + 双重量化 | 3.4 GB | 32 GB | 24% |
代价是训练速度下降约 20% 到 40%(反量化开销),以及轻微的精度损失。QLoRA 论文的结论是:4bit 基座加 LoRA 的效果与 16bit 全参微调相当。因此当显存是硬约束时,QLoRA 是首选;当显存充足时,用 BF16 LoRA 训练更快、更省心。
6. 数据准备与指令格式
微调的效果上限由数据决定,而数据准备是整条流水线里最耗人力的一环。
指令格式
主流格式是「指令-输入-输出」三元组,用对话模板包成 messages。以 ShareGPT 风格为例:
{
"conversations": [
{"role": "system", "content": "你是一名严谨的客服助手。"},
{"role": "user", "content": "我的订单 3 天没发货,怎么处理?"},
{"role": "assistant", "content": "非常抱歉给您带来不便。我先帮您核实订单状态……"}
]
}
关键纪律是:训练时的对话模板必须与推理时完全一致。基座模型自带的 chat template(如 ChatML、Llama3 的 header 格式)不能改,改了会导致模型学到一个推理时不存在的格式,表现为输出混乱。用 tokenizer.apply_chat_template() 生成训练样本,能保证两边一致。
数据量与质量
| 任务类型 | 最小可用样本 | 推荐样本 | 说明 |
|---|---|---|---|
| 风格/格式对齐 | 500 | 2,000 - 5,000 | 低秩即可收敛 |
| 领域问答 | 2,000 | 10,000 - 50,000 | 需要覆盖长尾 |
| 能力蒸馏 | 10,000 | 50,000 - 200,000 | 覆盖面决定上限 |
比数量更重要的是质量与多样性。几十条精心构造的高质量样本,往往胜过几千条从日志里粗暴导出的脏数据。三个常见的数据问题:
- 重复:同一问题出现几十次,会让模型过拟合到该表述,失去泛化。训练前必须做去重(按语义相似度或精确匹配)。
- 标签噪声:助手回复里夹带错误事实或不一致的格式,模型会照单全收。用强模型做一次质量打分与筛选是划算的投入。
- 格式不统一:有的样本用 Markdown、有的用纯文本,模型会学到混乱的格式分布。统一格式是预处理的基本要求。
损失掩码
指令微调通常只对 assistant 回复部分计算损失,把 system 与 user 的 token 掩码掉(label = -100)。这一步极易被忽略却影响很大:如果对全部 token 计损失,模型会花容量去学「怎么复述用户的问题」,既浪费又可能引发复读。
数据构造流水线
一条可复用的数据构造流水线通常是四步:采集、清洗、打分、配比。
| 步骤 | 动作 | 工具 | 产出判据 |
|---|---|---|---|
| 采集 | 从日志、专家撰写、强模型合成收集 | 埋点导出、GPT-4o 生成 | 原始量约为目标的 2 到 3 倍 |
| 清洗 | 去重、去 PII、格式统一 | MinHash、正则、规则 | 重复率低于 1% |
| 打分 | 质量分、难度分、多样性分 | 强模型评判、嵌入聚类 | 淘汰底部 20% 到 40% |
| 配比 | 按任务类型与难度分层采样 | 采样脚本 | 各类目占比符合预期 |
其中「配比」最容易被忽略。如果蒸馏数据里 80% 是简单问答,模型会学到「什么都用短答」的偏置。合理的做法是按难度与类型分层,保证每个桶都有足够样本,长尾任务至少占 10%。
from collections import defaultdict
import random
def stratified_sample(rows: list[dict], key: str, total: int) -> list[dict]:
"""按 key 分层采样,长尾类目保底 10% 配额"""
buckets = defaultdict(list)
for r in rows:
buckets[r[key]].append(r)
out, floor = [], max(1, int(total * 0.1 / max(len(buckets), 1)))
for k, items in buckets.items():
random.shuffle(items)
take = max(floor, int(total * len(items) / len(rows)))
out.extend(items[:take])
random.shuffle(out)
return out[:total]
这段逻辑的价值在于:它让「数据分布」变成一个可控参数,而不是被采集过程随机决定的产物。
7. 训练框架:PEFT、TRL 与 DeepSpeed
三层工具分工明确:PEFT 提供 LoRA 的实现与权重注入,TRL 提供训练循环与对齐相关的 Trainer,DeepSpeed 负责分布式与显存分片。
PEFT 配置
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
model_id = "Qwen/Qwen2.5-7B-Instruct"
bnb = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NF4 量化
bnb_4bit_use_double_quant=True, # 双重量化,省 scale 开销
bnb_4bit_compute_dtype=torch.bfloat16 # 计算时反量化到 BF16
)
model = AutoModelForCausalLM.from_pretrained(
model_id, quantization_config=bnb, device_map="auto"
)
model = prepare_model_for_kbit_training(model) # 冻结基座、开梯度检查点
lora = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
bias="none", task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora)
model.print_trainable_parameters() # 打印可训练参数占比,通常 0.2% 到 0.5%
prepare_model_for_kbit_training 做两件事:把基座权重的 requires_grad 设为 False,并把 LayerNorm 等层强制转为 FP32 以稳定训练。漏掉它会出现梯度爆炸。
TRL 训练脚本
from trl import SFTTrainer, SFTConfig
from datasets import load_dataset
ds = load_dataset("json", data_files="sft_data.jsonl", split="train")
cfg = SFTConfig(
output_dir="./out-lora",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=8, # 等效 batch 32
learning_rate=2e-4, # LoRA 常用 1e-4 到 3e-4
lr_scheduler_type="cosine",
warmup_ratio=0.03,
bf16=True,
gradient_checkpointing=True, # 用时间换显存
max_seq_length=2048,
logging_steps=10,
save_strategy="epoch",
packing=False, # 短样本多时可开 packing 提吞吐
)
trainer = SFTTrainer(
model=model, args=cfg,
train_dataset=ds,
peft_config=lora,
processing_class=AutoTokenizer.from_pretrained(model_id),
)
trainer.train()
trainer.save_model("./out-lora-final")
LoRA 的学习率通常比全参微调高一到两个数量级(2e-4 对 2e-5),因为适配器是随机初始化的,需要更大的步长才能学到有效更新。
DeepSpeed ZeRO 阶段
单卡能装下时不需要 DeepSpeed。需要多卡时,按显存压力选择 ZeRO 阶段:
| 阶段 | 分片内容 | 显存收益 | 通信开销 | 适用 |
|---|---|---|---|---|
| ZeRO-1 | 优化器状态 | 中 | 低 | 优化器是瓶颈 |
| ZeRO-2 | 优化器状态 + 梯度 | 高 | 中 | LoRA 多卡首选 |
| ZeRO-3 | 权重 + 梯度 + 优化器 | 最高 | 高 | 全参微调大模型 |
LoRA 微调因为优化器状态本来就小,多卡时 ZeRO-2 通常够用,ZeRO-3 的额外通信往往得不偿失。
8. 显存估算与超参配置
把前面的公式串起来,做一次真实核算。目标:单张 A100 80G 微调 Qwen2.5-7B,序列长度 2048,能否用 BF16 LoRA。
BF16 LoRA 显存构成:
基座权重 (BF16) 14.0 GB
LoRA 参数 + 梯度 约 0.04 GB × 2
优化器状态 (FP32) 约 0.16 GB
激活 (batch 4, 2K, 开梯度检查点) 约 8 - 12 GB
框架开销 1.5 GB
────────────────────────────────
合计 约 24 - 28 GB
结论:单卡 80G 绰绰有余,甚至 24G 的 4090 在减小 batch 后也能跑。换成 QLoRA:
| 配置 | 基座权重 | 激活与开销 | 合计 | 单卡可行性 |
|---|---|---|---|---|
| 全参 BF16 | 14.0 GB | 84 GB(含优化器) | 约 100 GB | 需 2 卡 |
| LoRA BF16 | 14.0 GB | 12 GB | 约 28 GB | 单卡 80G/48G |
| QLoRA NF4 | 3.4 GB | 12 GB | 约 17 GB | 单卡 24G |
70B 场景下差距更悬殊:QLoRA 能在单张 80G 上微调 70B(权重 32 GB + 激活 + 适配器),而全参需要 16 卡起步。
关键超参的推荐区间:
| 超参 | 推荐值 | 说明 |
|---|---|---|
| learning_rate | 1e-4 到 3e-4 | LoRA 比全参高一个量级 |
| batch size(等效) | 16 到 64 | 小 batch 配梯度累积 |
| epochs | 2 到 4 | 超过 4 极易过拟合 |
| warmup_ratio | 0.03 到 0.1 | 稳定初期训练 |
| lora_dropout | 0.05 到 0.1 | 数据量小时防过拟合 |
| max_seq_length | 覆盖 P95 长度 | 过长浪费显存 |
9. 合并权重与部署
训练产出的是一个几百 MB 的适配器(adapter_model.safetensors),可以两种方式上线。
方式一:合并成稠密权重
把 B·A 加回 W0,得到一个与原模型同构的完整权重,推理零额外开销:
from peft import PeftModel
from transformers import AutoModelForCausalLM
base = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-7B-Instruct", torch_dtype="auto"
)
model = PeftModel.from_pretrained(base, "./out-lora-final")
merged = model.merge_and_unload() # 把 LoRA 权重并入基座
merged.save_pretrained("./Qwen2.5-7B-SFT", safe_serialization=True)
合并后的模型可以被 vLLM、TensorRT-LLM 等任意引擎直接加载,走标准的量化流程(AWQ/FP8)。注意合并必须在 BF16 上做:如果对 4bit 基座直接合并,会把 LoRA 的增量也压到 4bit,精度损失叠加。
方式二:多适配器热插拔
如果同一基座要服务多个租户或多套话术,保留适配器、运行时动态加载更灵活。vLLM 支持 --enable-lora 与多适配器并发:
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-7B-Instruct \
--enable-lora \
--max-loras 4 --max-lora-rank 32 \
--lora-modules support=./out-lora-support \
sales=./out-lora-sales \
--max-model-len 8192 --port 8000
请求里用 "model": "support" 即可路由到对应适配器。代价是每个适配器的权重都要常驻显存(一个 r=16 的 7B 适配器约 20 MB 到 100 MB,可接受),且推理时多一次低秩计算。多租户场景的适配器隔离与配额,可以结合 模型网关与多模型路由
的鉴权与路由层一起设计。
10. 评测与回归
微调上线前必须回答一个问题:它比基座加提示好多少? 没有对照的微调是不可信的。
评测至少要覆盖三层:
- 能力回归:用通用评测集(MMLU、C-Eval、GSM8K)确认微调没有破坏基座的通用能力。这是最容易被忽略的一环,领域微调经常导致通用能力显著下降(灾难性遗忘)。
- 任务指标:在留出的验证集上测任务专属指标,如格式合规率、关键信息命中率、人工偏好胜率。
- 对抗样本:构造边界与越界输入,确认模型不会因为微调而变得更容易被绕过。
对比必须用同一套评测脚本跑「基座 + 提示」与「微调模型」两条线,记录质量、延迟、成本三项。评测体系的搭建细节可参考 模型评测与灰度发布 。
一个实用的做法是小秩试跑:先用 r=8、2000 条样本跑一轮,看验证集是否比基座有提升。如果没有提升,先别急着加数据加秩,而要回头检查数据质量与格式——大多数「微调无效」的根因是数据而非超参。
对比结果建议固定成一张表,每次微调都填一遍:
| 对比项 | 基座 + 提示 | 微调模型 | 变化 |
|---|---|---|---|
| 任务准确率 | 71.2% | 83.5% | +12.3 pt |
| 格式合规率 | 88.0% | 99.1% | +11.1 pt |
| MMLU(通用) | 74.2 | 73.6 | -0.6 |
| 首字延迟 | 620 ms | 615 ms | -5 ms |
| 单请求成本 | 0.0031 美元 | 0.0030 美元 | -0.0001 |
这张表同时回答三个问题:任务上有没有提升、通用能力有没有被破坏、延迟与成本有没有恶化。任何一列不达标,都要先解决再上线。
权衡取舍
| 方案 | 显存 | 训练速度 | 效果上限 | 适用场景 |
|---|---|---|---|---|
| 全参微调 | 极高 | 慢 | 最高 | 大厂、蒸馏、深度定制 |
| LoRA (BF16) | 中 | 快 | 接近全参 | 显存充足的主流选择 |
| QLoRA (NF4) | 低 | 中(慢 20% 到 40%) | 接近全参 | 单卡微调大模型 |
| 提示 + RAG | 无 | 无 | 受限于基座 | 知识型任务、快速迭代 |
选型顺序建议:先试提示与 RAG;确需微调时默认 LoRA;显存不够时降到 QLoRA;只有当 LoRA 在验证集上明显欠拟合、且团队有多卡资源时,才考虑全参微调。
部署侧还有一次取舍:合并权重适合「一个模型服务一类业务」,推理最快、可量化;多适配器适合「多租户共享基座」,运维简单但多一次低秩计算与显存占用。
常见坑清单
- 数据格式与推理模板不一致:训练用自定义模板、推理用基座 chat template,模型输出格式混乱。始终用
apply_chat_template保证两端一致。 - 对全部 token 计损失:没有掩码 system 与 user 部分,模型学成复读机。用 label = -100 只对 assistant 回复计损失。
- 学习率照搬全参微调:用 2e-5 训 LoRA,损失几乎不动。LoRA 应取 1e-4 到 3e-4。
- r 与 α 不成比例调整:只调 r 不调 α,导致更新幅度突变、训练不稳定。固定 α = 2r 可规避。
- 对 4bit 基座直接合并权重:把 LoRA 增量也压进 4bit,精度双重损失。合并必须在 BF16 上进行。
- 忽略灾难性遗忘:只测任务指标不测通用能力,上线后通用问答能力崩坏。必须做能力回归。
- 样本重复未去重:同一问题出现几十次,模型过拟合到该表述。训练前按语义相似度去重。
- epochs 过多:超过 4 轮后验证集损失回升,模型开始背诵训练集。用早停或固定小轮数。
- 忘记开梯度检查点:长序列训练激活爆显存。
gradient_checkpointing=True用约 20% 时间换大幅显存下降。 - 校准/评测数据泄漏:验证集样本混进训练集,评测分数虚高。严格按来源或时间切分。
小结
微调流水线的每一步都有明确的工程约束。判断阶段要分清「知识 vs 行为」,知识问题交给 RAG,行为问题才交给微调。成本阶段要认清全参微调的瓶颈在优化器状态而非权重,7B 就要近百 GB 显存,因此 LoRA 才是多数团队的默认。原理阶段要理解 LoRA 的低秩假设与零初始化设计,以及 α/r 缩放对稳定性的意义。超参阶段记住 r 从 16 起步、α = 2r、目标模块优先覆盖全部线性层。QLoRA 在显存受限时用 NF4 加双重量化把基座压到四分之一,代价是 20% 到 40% 的训练变慢。数据阶段的质量比数量重要,格式一致性是底线。落地阶段把适配器合并成 BF16 稠密权重再走推理量化,或保留适配器做多租户热插拔。
最重要的一条经验是:微调的效果上限由数据决定,而多数「微调没用」的案例根因是数据质量与格式,不是超参。先用小秩、小数据量做一次快速验证,确认方向对了再投入标注与算力,能省下大量返工。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。