2.3 模型微调
大多数时候,你不需要微调。通用大模型 + 好的 Prompt + RAG 检索增强,能解决 90% 的场景。但剩下那 10%——模型需要掌握你的行业术语、遵循你的特定输出格式、或者在某个垂直领域达到专家级表现——这时候微调就是绕不开的路。
搞清楚一个关键区别:微调不是让模型"记住"新知识,而是让模型"学会"一种行为模式。想让模型知道你公司的产品文档?用 RAG。想让模型像你团队的资深工程师一样写代码审查意见?用微调。
一、微调方法:从重到轻
全量微调(Full Fine-Tuning)
更新模型的所有参数。效果上限最高,但代价也最大:
- 需要的显存和算力是推理的数倍
- 容易发生灾难性遗忘(Catastrophic Forgetting)——学了新的,忘了旧的
- 7B 模型全量微调至少需要 ~60 GB 显存(FP16)
除非你有充足的算力预算和大规模高质量数据,否则不推荐。
参数高效微调(PEFT)
只动一小部分参数,冻结其余。这是 2026 年的绝对主流,因为它用 1% 的参数量就能达到全量微调 90%+ 的效果。
PEFT 的核心思路:大模型的知识已经足够丰富,你要做的不是重新教它,而是在它已有能力的基础上做一次"精准校准"。
二、LoRA:你最该掌握的微调技术
LoRA(Low-Rank Adaptation)是目前最流行、最实用的 PEFT 方法。理解它的原理,你就理解了现代微调的核心逻辑。
原理
模型原始权重矩阵为 $W$,微调的目标是找到一个更新量 $\Delta W$,使得新权重 $W' = W + \Delta W$ 在你的任务上表现更好。
LoRA 的关键洞察:$\Delta W$ 不需要是一个满秩矩阵。它可以分解为两个小矩阵的乘积:
$$\Delta W = A \times B$$
其中 $A$ 的维度是 $d \times r$,$B$ 的维度是 $r \times d$,$r$(秩)远小于 $d$。这样一来,需要训练的参数量从 $d^2$ 骤降到 $2dr$——通常只有原模型参数的 0.1%–1%。
为什么 LoRA 这么好用
- 显存友好:一张 RTX 4090(24 GB)就能微调 7B 模型,甚至可以挑战 13B
- 模块化:训练出来的 LoRA 权重只有几十 MB,可以像插件一样随时加载、卸载、替换
- 多任务复用:针对不同任务训练不同的 LoRA,推理时按需切换,基座模型只需要一份
- 风险低:原始模型参数完全冻结,不会破坏已有能力
QLoRA:把门槛再降一档
QLoRA = 量化基座 + LoRA 微调。它先把基座模型量化到 4-bit,然后在量化后的模型上做 LoRA 训练。
效果:一张 RTX 3090(24 GB)就能微调 30B+ 模型。这让个人开发者和小团队也能玩得起大模型微调。
关键超参数
| 参数 | 含义 | 经验值 |
|---|---|---|
lora_rank (r) | 低秩矩阵的秩,越大表达能力越强,但参数越多 | 8–64,通常 16 是好的起点 |
lora_alpha | 缩放系数,控制 LoRA 更新的强度 | 通常设为 rank 的 2 倍 |
lora_dropout | 防止过拟合的 dropout 比例 | 0.05–0.1 |
target_modules | 对哪些层做 LoRA | 一般选 attention 层的 q/k/v/o 投影矩阵 |
三、微调实战流程
第一步:准备数据
微调对数据量要求不高(几百到几千条就能见效),但对质量要求极高。一条垃圾数据的破坏力远大于十条好数据的贡献。
标准的指令微调数据格式:
[
{
"instruction": "请将以下 Python 代码转换为 JavaScript。",
"input": "print('Hello World')",
"output": "console.log('Hello World');"
},
{
"instruction": "审查以下代码,指出潜在的安全问题。",
"input": "query = f\"SELECT * FROM users WHERE id = {user_id}\"",
"output": "这段代码存在 SQL 注入风险。user_id 直接拼接进 SQL 语句,攻击者可以构造恶意输入。应使用参数化查询:cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,))"
}
]数据从哪来:
- 开源数据集:Alpaca、ShareGPT、OpenHermes 等,适合通用能力增强
- 合成数据:用强模型(GPT-5、Claude Opus)生成高质量样本(Self-Instruct 方法),这是目前最高效的数据获取方式
- 业务数据:从你的实际业务中提取真实的输入-输出对,效果最好但需要人工清洗
第二步:选工具
LLaMA-Factory 是目前最成熟的微调工具,几乎是社区的事实标准:
- 支持几乎所有主流开源模型(LLaMA、Qwen、DeepSeek、Mistral 等)
- 支持 LoRA / QLoRA / 全量微调等多种方法
- 提供 WebUI 可视化面板,也支持 CLI 多卡并行
- 内置数据预处理、训练监控、模型评估全流程
其他选择:Hugging Face transformers + peft 库(更灵活但需要写更多代码)、Axolotl(配置驱动,适合批量实验)。
第三步:开始训练
# 使用 LLaMA-Factory 对 Qwen2.5-7B 做 LoRA 微调
llamafactory-cli train \
--model_name_or_path qwen/Qwen2.5-7B-Instruct \
--dataset my_dataset \
--finetuning_type lora \
--lora_rank 16 \
--lora_alpha 32 \
--lora_dropout 0.05 \
--output_dir ./my_lora_checkpoint \
--per_device_train_batch_size 4 \
--gradient_accumulation_steps 4 \
--num_train_epochs 3 \
--learning_rate 2e-4 \
--logging_steps 10 \
--save_steps 100训练过程中重点关注 loss 曲线:稳步下降是正常的,突然不降或剧烈震荡说明学习率或数据有问题。
第四步:合并与部署
训练完成后,你得到的是一个 LoRA 适配器(几十 MB 的权重文件),不是一个完整模型。部署时有两种方式:
- 动态加载:推理时同时加载基座模型 + LoRA 适配器,灵活但略慢
- 静态合并:把 LoRA 权重永久合并进基座模型,导出一个新的完整模型,推理速度和原模型一样
# 合并 LoRA 权重到基座模型
llamafactory-cli export \
--model_name_or_path qwen/Qwen2.5-7B-Instruct \
--adapter_name_or_path ./my_lora_checkpoint \
--export_dir ./my_merged_model四、踩坑指南
过拟合
症状:模型在训练数据上表现完美,但换个说法就不会了,甚至开始复读训练样本。
原因:数据太少、训练轮数太多、rank 设太大。
对策:减少 epoch(通常 2–3 轮就够)、增加 dropout、扩充数据多样性。
通用能力退化
症状:微调后模型在你的任务上变好了,但日常对话能力明显下降。
原因:数据分布太单一,模型"忘了"怎么做通用任务。
对策:在微调数据中混入一定比例(10%–20%)的通用对话数据。
Prompt 格式不匹配
症状:微调后模型输出混乱、格式错误、或者完全不听指令。
原因:微调时用的 Prompt Template 和推理时不一致。比如微调用了 ChatML 格式,推理时却用了 Alpaca 格式。
对策:微调和推理必须使用完全相同的 Prompt Template。用 LLaMA-Factory 时,确保 --template 参数前后一致。
什么时候不该微调
在动手微调之前,先问自己:
- 能不能通过更好的 Prompt 解决?→ 先试 few-shot prompting
- 是不是缺知识而不是缺能力?→ 用 RAG 而不是微调
- 数据量够不够?质量高不高?→ 低于 200 条高质量数据,微调效果很难保证
微调是手术刀,不是万能药。用对了事半功倍,用错了浪费算力还可能把模型搞坏。
五、闭源模型微调(OpenAI / Claude)
不想自己搞 GPU?闭源模型也支持微调——上传数据,平台帮你训练。
OpenAI Fine-Tuning
from openai import OpenAI
client = OpenAI()
# 1. 准备训练数据(JSONL 格式)
# train.jsonl 每行一个:
# {"messages": [{"role": "system", "content": "你是客服"}, {"role": "user", "content": "退货"}, {"role": "assistant", "content": "..."}]}
# 2. 上传数据
file = client.files.create(
file=open("train.jsonl", "rb"),
purpose="fine-tune"
)
# 3. 创建微调任务
job = client.fine_tuning.jobs.create(
training_file=file.id,
model="gpt-4o-mini-2024-07-18", # 基座模型
hyperparameters={
"n_epochs": 3,
"batch_size": "auto",
"learning_rate_multiplier": "auto"
}
)
print(f"任务 ID: {job.id}")
# 4. 查看进度
status = client.fine_tuning.jobs.retrieve(job.id)
print(f"状态: {status.status}") # queued → running → succeeded
# 5. 使用微调模型
response = client.chat.completions.create(
model=status.fine_tuned_model, # ft:gpt-4o-mini-2024-07-18:org::xxxxx
messages=[{"role": "user", "content": "我要退货"}]
)成本参考(GPT-4o-mini 微调):
- 训练:$3/百万 Token
- 推理:输入 $0.30/百万,输出 $1.20/百万(比基础模型贵 2x)
何时选闭源微调 vs 开源微调?
| 维度 | OpenAI 微调 | 开源 LoRA |
|---|---|---|
| 上手难度 | ⭐ 极简 | ⭐⭐⭐ 需要 GPU |
| 成本 | 按 Token 持续付费 | GPU 一次性投入 |
| 数据隐私 | 数据上传到 OpenAI | 完全本地 |
| 灵活性 | 有限(超参数少) | 完全可控 |
| 适用 | 快速验证、小团队 | 数据敏感、大规模 |
六、微调效果评估
import json
def evaluate_finetuned(base_model: str, ft_model: str, test_cases: list) -> dict:
"""对比基座模型 vs 微调模型的输出质量"""
from openai import OpenAI
client = OpenAI()
results = {"base_scores": [], "ft_scores": []}
for case in test_cases:
# 基座模型输出
base_resp = client.chat.completions.create(
model=base_model,
messages=[{"role": "user", "content": case["input"]}]
)
# 微调模型输出
ft_resp = client.chat.completions.create(
model=ft_model,
messages=[{"role": "user", "content": case["input"]}]
)
# LLM-as-Judge 评分
judge = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": f"""
对比两个回答的质量,按 1-10 打分。
参考答案:{case['expected']}
回答A:{base_resp.choices[0].message.content}
回答B:{ft_resp.choices[0].message.content}
输出 JSON:{{"score_a": N, "score_b": N}}"""}],
response_format={"type": "json_object"}
)
scores = json.loads(judge.choices[0].message.content)
results["base_scores"].append(scores["score_a"])
results["ft_scores"].append(scores["score_b"])
print(f"基座模型平均分: {sum(results['base_scores'])/len(results['base_scores']):.1f}")
print(f"微调模型平均分: {sum(results['ft_scores'])/len(results['ft_scores']):.1f}")
return results