Skip to content

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 投影矩阵

三、微调实战流程

第一步:准备数据

微调对数据量要求不高(几百到几千条就能见效),但对质量要求极高。一条垃圾数据的破坏力远大于十条好数据的贡献。

标准的指令微调数据格式:

json
[
  {
    "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(配置驱动,适合批量实验)。

第三步:开始训练

bash
# 使用 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 权重永久合并进基座模型,导出一个新的完整模型,推理速度和原模型一样
bash
# 合并 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

python
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完全本地
灵活性有限(超参数少)完全可控
适用快速验证、小团队数据敏感、大规模

六、微调效果评估

python
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

坚持是一种品格