🎯 手把手教你做模型微调
以 LoRA 为例,从数据准备到部署的完整实战

模型微调是将通用的预训练大模型适配到特定任务/领域的关键技术。本文以最流行的 LoRA(Low-Rank Adaptation) 方法为例,从原理到实战,完整走一遍微调流程。

一、什么时候需要微调?

场景 怎么做 推荐方法
想让模型学会特定格式(如 JSON 输出、客服话术)给它看一批符合格式的例子⭐ LoRA / QLoRA
想让模型掌握特定领域知识(如医疗、法律)用领域数据继续训练LoRA / 全参微调
想让模型记住企业私有数据用 RAG(不用改模型)RAG(不需要微调!)
不确定效果,先试试写 Prompt 就够Prompt Engineering(零成本)
💡 原则:能写 Prompt 解决的不要微调,能 RAG 解决的不要微调。微调只有在你需要改变模型的行为方式时才值得做。

二、LoRA 原理(3 分钟搞懂)

LoRA(Low-Rank Adaptation,低秩适配)的核心思想极其简单:

模型原来的权重矩阵 W(例如 4096×4096)
    ↓
我们不修改 W,而是在旁边挂两个小矩阵 A 和 B:
    W' = W + A × B

    ┌────────────┐       ┌───┐
    │     W       │   +   │ A │ × │ B │
    │  (4096×4096)│       │   │   └───┘
    │             │       │   │
    └────────────┘       │   │
                         └───┘
                          r=8  (低秩)
    A: 4096×8,  B: 8×4096

参数对比:
• 原矩阵 W: 4096×4096 = 16,777,216 个参数
• LoRA 矩阵 A×B: 4096×8 + 8×4096 = 65,536 个参数
• 只训练 0.39% 的参数!
对比项 全参微调 LoRA QLoRA
训练参数量100%< 1%< 0.1%
显存需求(7B 模型)~120GB~28GB~12GB(4bit)
训练速度较快
效果最佳(理论上)接近全参稍差但可接受
推荐场景有 H100 集群大多数人消费级显卡

三、环境准备

3.1 硬件要求

模型大小 最低显存(LoRA) 最低显存(QLoRA) 推荐显卡
1~3B8GB4GBRTX 3060 / M系列 Mac
7~8B24GB12GBRTX 4090 / A10
13~14B48GB24GBA100 40GB / 2xRTX 4090
33~34B80GB+48GBA100 80GB
70~72B多卡80GBA100x2 或 H100

3.2 安装 LLaMA-Factory(推荐)

# 1. 克隆仓库
git clone https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory

# 2. 安装依赖
pip install -r requirements.txt

# 3. 安装额外功能(推荐)
pip install .[torch,metrics]

# 4. 验证安装
llamafactory-webui --help

3.3 直接使用 HuggingFace PEFT(灵活)

pip install transformers datasets peft accelerate bitsandbytes
# bitsandbytes 用于 QLoRA(4bit 量化)

四、数据准备(最关键的一步)

⚠️ 微调的效果 80% 取决于数据质量,20% 取决于技术参数。

4.1 数据格式

LLaMA-Factory 支持多种数据格式。以下是最常用的 ShareGPT 格式(支持多轮对话):

[
  {
    "conversations": [
      {
        "from": "human",
        "value": "请帮我写一封请假邮件,我要请三天假去参加婚礼。"
      },
      {
        "from": "assistant",
        "value": "好的,这是为您准备的请假邮件草稿:\n\n尊敬的张经理:\n\n您好!因参加亲友婚礼,我计划于 6 月 10 日至 6 月 12 日请假三天。期间我会妥善交接工作,确保不影响团队进度。\n\n恳请批准。\n\n此致\n敬礼\n\n王小明"
      }
    ]
  },
  // ... 更多数据
]

或者更简单的 Alpaca 格式(单轮问答):

[
  {
    "instruction": "请帮我写一封请假邮件",
    "input": "我要请三天假去参加婚礼",
    "output": "尊敬的张经理:\n\n您好!..."
  }
]

4.2 数据质量原则

原则 为什么 怎么做
数量LoRA 通常需要 500~5000 条高质量数据太少学不会,太多边际效益递减
质量一条脏数据=十条好数据的负面效果人工审核,去掉格式错误、内容冲突
多样性覆盖各种输入情况,避免过拟合确保输入指令、问题类型分布均匀
平衡output 中不要全是同一个模式输出风格、长度、结构多样化
对齐保持和基础模型的格式一致参考模型的 Chat Template

4.3 数据生成技巧(用大模型造数据)

没有现成数据?用 GPT-4 / DeepSeek 等大模型帮你造:

Prompt 模板:

"你是一个数据生成助手。请为以下场景生成 50 条高质量的微调训练数据。
每条数据包括 instruction 和 output。
场景:{描述你的场景}
要求:{具体输出要求}
请确保数据多样化。以 JSON 数组格式返回。"

五、实战训练

5.1 方案 A:使用 LLaMA-Factory WebUI(推荐新手)

第一步:启动 WebUI
CUDA_VISIBLE_DEVICES=0 llamafactory-webui --host 0.0.0.0 --port 7860

打开浏览器访问 http://localhost:7860

第二步:配置训练参数

页面字段:             你的填写:
─────────────────────────────────────────────────
语言                  zh(中文)
模型名称              Qwen2.5-7B-Instruct(选你要的模型)
微调方法              lora
数据集                选择你准备好的数据
学习率                5e-5
训练轮数              3.0
最大序列长度          2048
LoRA rank             8
LoRA alpha            16
LoRA target            q_proj,v_proj(7B 模型)

第三步:点击「开始训练」
等待进度条走完...

5.2 方案 B:用 Python 脚本(灵活可控)

import torch
from transformers import (
    AutoModelForCausalLM, AutoTokenizer,
    TrainingArguments, BitsAndBytesConfig
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from datasets import load_dataset
from trl import SFTTrainer

# 1. 加载模型(4bit 量化,节省显存)
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.float16
)

model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2.5-7B-Instruct",
    quantization_config=bnb_config,
    device_map="auto",
    torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")

# 2. 配置 LoRA
lora_config = LoraConfig(
    r=16,              # LoRA 秩(越大越强,也越贵)
    lora_alpha=32,     # 缩放系数
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)

# 3. 加载数据
dataset = load_dataset("json", data_files="your_data.json")

# 4. 配置训练参数
training_args = TrainingArguments(
    output_dir="./qwen-lora-output",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    learning_rate=5e-5,
    num_train_epochs=3,
    logging_steps=10,
    save_steps=100,
    fp16=True,
    report_to="none"
)

# 5. 启动训练
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset["train"],
    tokenizer=tokenizer,
    max_seq_length=2048
)
trainer.train()

# 6. 保存 LoRA 权重
model.save_pretrained("./my-lora-adapter")
tokenizer.save_pretrained("./my-lora-adapter")

5.3 方案 C:使用 LLaMA-Factory 命令行

# 写一个 YAML 配置文件 train_config.yaml
model_name_or_path: Qwen/Qwen2.5-7B-Instruct
dataset: your_dataset_name
dataset_dir: ./data
output_dir: ./output/qwen-lora

finetuning_type: lora
quantization_bit: 4            # QLoRA (4bit)
quantization_type: nf4

lora_rank: 8
lora_alpha: 16
lora_dropout: 0.05
lora_target: q_proj,v_proj

per_device_train_batch_size: 4
gradient_accumulation_steps: 4
learning_rate: 5e-5
num_train_epochs: 3.0
max_samples: 500               # 调试时减少数据量
max_seq_length: 2048

fp16: true
logging_steps: 10
save_steps: 100

# 运行训练
llamafactory-cli train train_config.yaml

六、LoRA 关键参数详解

参数 推荐值 调高↑ 调低↓
r(秩)8~32更强拟合能力,更多参数更少过拟合,更快训练
alpha16~64LoRA 权重更大原始权重更大
dropout0.05~0.1更强的正则化更少随机性
学习率1e-4 ~ 5e-5收敛更快(可能不稳定)更稳定(可能太慢)
target_modulesq_proj,v_proj所有 Linear 层(效果更好)只改注意力的关键层
💡 经验法则:
• 第一次调参用 r=8, alpha=16, lr=5e-5
• 先跑 100~500 条数据看 loss 曲线(如果 loss 不降→ 学习率太高或数据有问题)
• 效果不够好:增大 r(16→32→64),增大 alpha
• 过拟合:减小 r,增大 dropout,减少 epoch

七、评估模型效果

7.1 对话测试

# LLaMA-Factory WebUI → Chat 标签页
# 直接输入问题测试

# 或用代码:
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2.5-7B-Instruct",
    device_map="auto",
    torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")

# 加载 LoRA 权重
model = PeftModel.from_pretrained(base_model, "./my-lora-adapter")

# 测试
inputs = tokenizer.apply_chat_template(
    [{"role": "user", "content": "请帮我写一封请假邮件"}],
    add_generation_prompt=True,
    return_tensors="pt"
).to("cuda")

output = model.generate(inputs, max_new_tokens=512)
print(tokenizer.decode(output[0]))

7.2 几种对比测试

测试类型 方法 看什么
AB 对比同一问题,原模型 vs 微调后回答微调后的输出是否符合预期格式/风格
测试集 Loss用未参与训练的测试数据算 lossloss 越低越好(跟原始 loss 对比)
人工评分请人给 100 条输出打分准确率、可用率、偏好率
GPT 评分用 GPT-4 当裁判评估输出质量批量自动化评估(GPT-as-Judge)

八、导出与部署

8.1 导出 LoRA 权重

# 方法一:只导出 LoRA 权重(小文件,推荐)
# 训练时已经自动保存了 ./my-lora-adapter/ 目录
# 包含 adapter_model.safetensors + adapter_config.json

# 方法二:合并回基础模型(大文件,推理更快)
# 用 LLaMA-Factory WebUI → Export 标签页
# 或命令行:
python src/export_model.py \
    --model_name_or_path Qwen/Qwen2.5-7B-Instruct \
    --adapter_name_or_path ./my-lora-adapter \
    --export_dir ./merged-model \
    --export_quantization_bit 4  # 可选:导出为 4bit 量化

8.2 部署推理

# 方案一:用 Ollama(最简单)
# 将合并后的模型导出为 GGUF 格式
# 然后 ollama create my-model -f Modelfile
# ollama run my-model

# 方案二:用 vLLM(高性能,生产推荐)
pip install vllm
python -m vllm.entrypoints.openai.api_server \
    --model ./merged-model \
    --port 8000 \
    --dtype auto

# 然后就能用 OpenAI 兼容 API 调用了:
curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "./merged-model",
        "messages": [{"role": "user", "content": "你好"}]
    }'

# 方案三:用 Ollama + LoRA(不合并,直接加载)
ollama pull qwen2.5:7b
# 在 Modelfile 中指定 LoRA 权重
# FROM qwen2.5:7b
# ADAPTER ./my-lora-adapter

九、完整微调流程速查

┌─────────────────────────────────────────────────────────┐
│            LoRA 微调完整流程速查表                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Step ① 确定需求                                        │
│  ├── 要改变模型"做什么" → 微调                           │
│  ├── 要改变模型"知道什么" → RAG(别微调)                │
│  └── 先试试能不能写 Prompt 解决                          │
│                                                         │
│  Step ② 准备数据                                        │
│  ├── 收集或生成 500~5000 条高质量数据                    │
│  ├── 格式化成 ShareGPT 或 Alpaca 格式                    │
│  ├── 检查数据质量(去重、去脏、验格式)                    │
│  └── 分训练集 90% + 测试集 10%                          │
│                                                         │
│  Step ③ 选择方法                                        │
│  ├── 有 24GB+ 显存 → LoRA(推荐 r=8~16)                │
│  ├── 有 12GB+ 显存 → QLoRA(4bit)                      │
│  └── 有 A100 集群 → 全参微调                            │
│                                                         │
│  Step ④ 训练                                            │
│  ├── 新手 → LLaMA-Factory WebUI(可视化)                │
│  ├── 熟练 → LLaMA-Factory 命令行 / Python 脚本           │
│  └── 监控 loss 曲线(下降就对了!)                       │
│                                                         │
│  Step ⑤ 评估                                            │
│  ├── 对话测试(看输出是否达到预期)                       │
│  ├── AB 对比(微调前 vs 微调后)                         │
│  └── 测试集 Loss(对比原始模型)                         │
│                                                         │
│  Step ⑥ 导出部署                                        │
│  ├── 保留 LoRA 权重(小、灵活)                          │
│  ├── 合并回基础模型(快、省)                            │
│  └── 用 vLLM / Ollama 部署                             │
│                                                         │
└─────────────────────────────────────────────────────────┘

十、常见问题排查

问题 原因 解决
Loss 不下降学习率太高 / 数据问题降低 lr(1e-5),检查数据格式
显存溢出(OOM)batch_size 太大减 batch_size,加 gradient_accumulation_steps
训练完模型变傻了灾难性遗忘降低 lr,少跑 epoch,加原始数据混合
输出全是重复过拟合 / temperature 太低减少 epoch,检查数据多样性
加载模型报错模型路径不对 / 量化方式不匹配确认模型完整,检查 bnb 配置
训练特别慢没有用 fp16 / 没开 flash_attn加上 fp16=True,安装 flash-attn
导入数据报错JSON 格式错误python -m json.tool data.json 验证

📌 一句话总结

LoRA 微调 = 准备高质量数据 + LLaMA-Factory 选 LoRA + 调好学习率跑几轮 + 评估导出。
80% 的时间应该花在数据准备上,剩下的 20% 才是调参。