Skip to content

微调你自己的 LLM

本页速览 在消费级 GPU 上用 Qwen/Llama 加 LoRA 微调出你自己的模型:先判断该不该微调、准备显存与数据,再到 QLoRA 训练、合并导出、推理验证与评估上线的完整可运行代码。

本页含时效性内容,数据截止于 2025-06;JD、榜单、产品功能等信息可能已变化,引用前请核对原始出处。

微调你自己的 LLM ​

微调(Fine-tuning)是在一个预训练大模型的基础上,用你自己的数据继续训练,让模型"学会"你的业务语言、输出格式与行为偏好。 配合 LoRA(Low-Rank Adaptation) 这类参数高效微调技术,这件事在今天已经不再是少数大厂的专利:一张 8GB 显存的消费级显卡、一份几千到几万条的高质量指令数据、一个下午的时间,你就能把 Qwen 或 Llama 调教成懂你业务的小助手。

本文不重复微调的理论推导,而是回答三个实战问题:你的场景到底该不该微调?显存和软件环境怎么准备?从数据到部署的完整流程长什么样? 全文是一份可以照抄的操作手册,从数据准备、LoRA 训练、合并导出到推理验证,每一步都配有可运行代码。微调背后的原理(为什么 LoRA 有效、低秩近似是怎么回事)见微调与 PEFT(LoRA),本文聚焦"怎么把它跑起来"。

前置知识:建议先了解大语言模型(LLM)的基础概念,并亲手跑过一次提示词工程与 RAG 应用。文中的每个环节在站内都有对应的深度文章,随时给出链接。

一、先做决策:你的场景值不值得微调 ​

1. 一句话判断 ​

先用提示词,再用 RAG,最后才考虑微调。 微调不是"更高级",而是"更昂贵"——它需要数据、算力、迭代时间,且每次改动都要重训。绝大多数业务问题在提示词和 RAG 阶段就已经解决了。

维度提示词工程RAG微调(LoRA)
改动成本几分钟,改 Prompt 即可改检索库与索引数小时到数天,重训模型
需要的数据少量示例(few-shot)文档库千条级高质量标注数据
知识时效性好最好(即改即生效)差(训练时固化)
深度领域能力弱中等强
私有知识依赖提示词注入最适合可固化,但易过时
输出格式/风格控制中等弱强
部署成本最低需检索栈模型与显存开销变大
典型场景通用助手、临时需求客服知识库、企业问答领域语言、固定格式输出

值得微调的四个信号:

  • 模型对领域术语、行话、专有名词的理解明显不对,而 RAG 检索到的文档无法"教会"它(例如医疗、法律、特定行业的黑话);
  • 你需要稳定且固定的输出格式(特定 JSON schema、特定风格的回复),Prompt 经常跑偏;
  • 你要在离线 / 本地 / 内网部署,不能每次请求都携带超长上下文;
  • 推理成本敏感:与其每条请求塞 2 万 token 上下文,不如把高频知识直接微调进权重。

该踩刹车的情况:

  • 知识会频繁更新 → 用 RAG 而不是微调;
  • 数据不足或质量存疑(拿不出 500 条以上高质量样本)→ 先打磨提示词;
  • 没有验证集和评估手段 → 先读搭建一套 LLM 评估,否则微调完你无法判断好坏。

2. 基座模型怎么选 ​

基座模型选错,后面全部白费。选择看三个维度:中文 vs 英文、模型规模 vs 显存、指令模型 vs 基座模型。各模型参数规模与榜单数据见模型与榜单速查。

模型规模特点适合场景
Qwen2.5-7B-Instruct7B中文强、指令遵循好、生态成熟中文业务、通用微调入门
Llama-3.1-8B-Instruct8B英文强、社区生态最丰富英文任务、工具调用
Mistral-7B-Instruct7B推理高效、显存友好英文、资源紧张
Qwen2.5-14B / Llama-3.1-13B14B / 13B更强但更吃显存数据量大、效果优先
Qwen2.5-3B / Llama-3.2-3B3B极小显存即可跑教学、边缘设备

三条经验判断:

  • 中文业务优先 Qwen,英文通用优先 Llama,这是社区反复验证的经验,具体对比数据见模型与榜单速查;
  • 从 7B / 8B 起步:在消费级 GPU 上 7B 是性价比甜点,13B 以上显存和训练时间会陡增;
  • 选"指令模型"而非基座模型:-Instruct / -Chat 版本已经过对齐(RLHF/DPO),微调后更听话;直接微调 base 模型需要你自己补足对话能力,一般不建议。

二、环境准备:显存账本与依赖 ​

1. 显存需求表(经验值) ​

微调显存由四部分组成:模型权重 + 梯度 + 优化器状态 + 激活值。全参数微调(FFT)要同时维护这三份完整状态,显存需求通常是 QLoRA 的 5-10 倍。下表为经验值(单位 GB),实际随 max_seq_len、batch_size 浮动:

模型规模全参数微调(BF16)QLoRA(4-bit + LoRA)建议 GPU
2B / 3B约 18 GB约 3-4 GB6-8 GB 即可
7B / 8B约 60-70 GB(需 A100/H100 级)约 7-9 GBRTX 4070 / 3090
13B / 14B约 120-140 GB(多卡)约 12-15 GBRTX 4090
70B多机多卡约 35-45 GBA100 80G / 多卡

结论很明确:消费级 GPU 上 QLoRA 是唯一现实选择。QLoRA 把 4-bit 量化(NF4)与 LoRA 结合——基座权重被压缩到原来的 1/4 且全程冻结,可训练参数只占全部参数的 0.1%-1%。量化原理见推理优化与量化。

显存不够怎么办?

依次拧三个旋钮:max_seq_length 从 2048 降到 1024 → per_device_train_batch_size 降为 1 并用 gradient_accumulation_steps 补回等效批大小 → LoRA 的 r 从 16 降到 8。其中"batch=1 + 梯度累积"在效果上几乎无损,是最优先的手段。

2. 安装依赖 ​

bash
# 推荐 Python 3.10+,建议先创建独立环境
conda create -n ft python=3.10 -y && conda activate ft

pip install "transformers>=4.43" "peft>=0.11" "trl>=0.9" \
            "datasets>=2.19" "bitsandbytes>=0.43" "accelerate>=0.32"
包职责
transformers模型与 tokenizer 加载、训练核心 API
peftLoRA 配置与模型包装(LoraConfig / get_peft_model)
trlSFTTrainer、DPOTrainer 等高级训练器
datasets数据集加载与预处理
bitsandbytes4-bit / 8-bit 量化底层库(QLoRA 必需)
accelerate分布式与混合精度训练基础设施

Linux + NVIDIA 驱动是官方支持最好的组合;Windows 上 bitsandbytes 从 0.43 起也提供官方支持,但推荐 WSL2。Mac(MPS)不支持 4-bit 训练,可改用 8-bit 或小模型。

三、数据准备:微调成功率的 80% ​

业界流传一句话:LoRA 微调的瓶颈从来不是算力,而是数据。一份高质量指令数据 > 三份低质量数据。

1. 指令数据集格式 ​

微调的本质是"教模型学会输入 → 输出的映射"。最通用的格式是 Alpaca 风格三元组:

json
[
  {
    "instruction": "请用一句话解释什么是向量数据库",
    "input": "",
    "output": "向量数据库是一种以向量索引为核心、专门用于存储和检索高维向量的数据库,通常配合嵌入模型用于语义检索和 RAG 场景。"
  },
  {
    "instruction": "把下面的产品描述改写成适合电商平台的 20 字以内广告语",
    "input": "这是一款支持无线充电、IP68 防水、续航 7 天的智能手表",
    "output": "七天续航,无线快充,陪你上山下海。"
  }
]
  • instruction:任务描述;
  • input:任务输入(可为空);
  • output:期望的模型回答。

训练前需要把它拼成带角色标记的完整对话文本(ChatML 格式,Qwen 官方训练格式):

python
from datasets import load_dataset

def format_example(example):
    user = example["instruction"]
    if example.get("input"):
        user += "\n" + example["input"]
    return {
        "text": (
            f"<|im_start|>user\n{user}<|im_end|>\n"
            f"<|im_start|>assistant\n{example['output']}<|im_end|>\n"
        )
    }

ds = load_dataset("json", data_files="data.jsonl", split="train")
ds = ds.map(format_example, remove_columns=ds.column_names)
print(ds[0]["text"])   # 训练前务必打印一条,逐字符检查模板

2. 公开数据集起步 ​

不想从零造数据?直接复用社区公开数据集,完整清单见数据集与工具档案:

数据集规模特点适用
Alpaca(stanford_alpaca)52k指令微调的"祖师爷",英文通用通用能力增强
alpaca-cleaned / 中文版52k清洗版、中文翻译版中文通用微调
ShareGPT90k+真实多轮对话对话风格学习
OpenOrca / SlimOrca500k+质量较高的英文指令大规模通用微调
自定义业务数据几百到几万你的领域领域微调

关键提醒:公开数据与业务数据要混合使用,比例通常在"通用 : 业务 = 3:1 到 1:1"。纯业务数据微调会快速过拟合并破坏通用能力(见本文常见坑一节)。

3. 数据清洗的六个要点 ​

  1. 去重:重复样本会让模型"背高频内容"而不是学会泛化,用哈希或 embedding 相似度去重;
  2. 长度过滤:过滤过短(< 10 token)和过长(超过 max_seq_length 80%)的样本;
  3. 格式一致性:所有样本字段语义必须统一(如 input 是空字符串还是 null,只选一种),否则模型学到"时灵时不灵";
  4. 答案质量审查:指令数据最常见的缺陷是答案本身错误——抽 100 条人工过一遍,错误率超过 5% 就回去改数据;
  5. 隐私与敏感信息:训练数据会"住进"模型权重,PII(手机号、身份证)要么脱敏要么剔除;
  6. 分布覆盖:检查业务场景是否全覆盖,尤其要包含边界 case(歧义输入、超长输入、特殊字符)。

四、分步实现:从加载到部署的五个步骤 ​

下面用一个端到端示例串起完整流程:用 Qwen2.5-7B-Instruct + LoRA,在消费级 GPU(约 8GB 显存)上微调一个"懂向量数据库知识"的助手。

步骤 1:加载模型与 tokenizer(4-bit QLoRA 配置) ​

python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

model_id = "Qwen/Qwen2.5-7B-Instruct"

# 4-bit NF4 量化配置(QLoRA 的核心)
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",        # 4-bit NormalFloat
    bnb_4bit_use_double_quant=True,   # 二次量化,再省约 0.4 bits/参数
    bnb_4bit_compute_dtype=torch.bfloat16,
)

tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
# 部分中文模型需要显式设置 pad token
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=bnb_config,
    device_map="auto",                # 自动分配显存/CPU
    trust_remote_code=True,
)
model.config.use_cache = False        # 训练阶段关闭 KV cache,省显存

要点:

  • bnb_4bit_compute_dtype=torch.bfloat16 让计算发生在 bf16,既省显存又保留精度,是 QLoRA 默认推荐;
  • device_map="auto" 会把层分配到 GPU,放不下的层落到 CPU(会很慢,尽量调小模型或序列长度)。量化与推理优化的完整讨论见推理优化与量化。

步骤 2:LoRA 配置与 PEFT 包装 ​

LoRA 的核心思想:冻结原权重,只训练注入的低秩矩阵,训练后再把增量合并回去。原理见微调与 PEFT(LoRA)。

python
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training

# QLoRA 要求:先把量化模型权重转为可训练布局
model = prepare_model_for_kbit_training(model)

lora_config = LoraConfig(
    r=16,                # 低秩矩阵的秩:越大表达能力越强、显存越多
    lora_alpha=32,       # 缩放系数,通常取 r 的 1-2 倍
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],  # 注意力 + FFN 全部注入
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 输出形如:trainable params: 21,626,880 || all params: 7,683,094,528 || trainable%: 0.2815

参数速查:

参数含义建议
r低秩秩,LoRA 注入矩阵的宽度8(小数据/省显存)到 64(大数据)
lora_alpha缩放系数,实际缩放比为 alpha / r常取 2 × r,不必精调
lora_dropout注入层的 dropout0.05-0.1
target_modules对哪些线性层注入 LoRA默认 q/k/v/o;加上 FFN 层效果更好

步骤 3:训练(trl SFTTrainer) ​

python
from trl import SFTTrainer, SFTConfig

training_args = SFTConfig(
    output_dir="./qwen2.5-7b-lora",
    per_device_train_batch_size=1,
    gradient_accumulation_steps=16,     # 等效 batch size = 1 × 16 = 16
    learning_rate=2e-4,                 # LoRA 常用 1e-4 ~ 5e-4
    num_train_epochs=3,
    lr_scheduler_type="cosine",
    warmup_ratio=0.03,
    optim="paged_adamw_8bit",           # 8-bit 优化器,进一步省显存
    logging_steps=10,
    save_steps=200,
    save_total_limit=3,
    max_seq_length=1024,                # 训练序列最大长度
    bf16=True,                          # 有 Ampere 及以上 GPU 时用 bf16
    report_to="none",
)

trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=ds,
    processing_class=tokenizer,         # trl >= 0.12 的写法;旧版为 tokenizer=tokenizer
)

trainer.train()
trainer.save_model("./qwen2.5-7b-lora/final")   # 只保存 LoRA 适配器

超参速查表(经验值):

超参含义经验范围说明
learning_rate学习率1e-4 ~ 5e-4(LoRA)全参数微调用 1e-5 量级,LoRA 可以更激进
num_train_epochs训练轮数1-3小数据(< 5k)跑 3 轮,大数据 1-2 轮
per_device_train_batch_size单卡批大小1-4受显存限制,通常取 1
gradient_accumulation_steps梯度累积步数8-32等效 batch = batch_size × 累积步数
max_seq_length序列长度512-2048决定激活值显存,够用即可
warmup_ratio预热比例0.03-0.1稳定训练前期

三个训练纪律

  • 盯住训练 loss 曲线:理想情况是平滑下降并在 1-3 轮内稳定;loss 不降先查学习率(过大震荡、过小不降)。
  • 别追求 train loss 归零:归零 = 过拟合,模型把训练数据背下来了。
  • 定期保存 checkpoint:save_steps 每次保存,训练崩溃不用从头再来。

步骤 4:合并与导出 ​

LoRA 训练的产物是一套几十 MB 的 adapter 文件,推理时要合并回基座模型,或直接用 peft 的适配器加载。导出为标准模型:

python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

base_model_id = "Qwen/Qwen2.5-7B-Instruct"
adapter_path = "./qwen2.5-7b-lora/final"
export_path  = "./qwen2.5-7b-merged"

# 用 BF16 重新加载基座(不量化),再挂上 LoRA 适配器
base = AutoModelForCausalLM.from_pretrained(
    base_model_id, torch_dtype=torch.bfloat16, device_map="auto",
)
model = PeftModel.from_pretrained(base, adapter_path)
model = model.merge_and_unload()        # 把 LoRA 增量合并进权重
model.save_pretrained(export_path)
AutoTokenizer.from_pretrained(base_model_id).save_pretrained(export_path)
print("merged model saved to", export_path)

合并后的模型可直接用 AutoModelForCausalLM.from_pretrained(export_path) 加载。生产部署与推理优化(量化、vLLM、并发控制)见部署与推理优化实战。

步骤 5:推理验证——微调前后对比 ​

训练完成后的第一件事:拿训练时没见过的样本,对比微调前后模型的回答。

python
def chat(model, tokenizer, prompt, max_new_tokens=128):
    messages = [{"role": "user", "content": prompt}]
    text = tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True
    )
    inputs = tokenizer(text, return_tensors="pt").to(model.device)
    out = model.generate(**inputs, max_new_tokens=max_new_tokens, do_sample=False)
    return tokenizer.decode(out[0][inputs["input_ids"].shape[1]:],
                            skip_special_tokens=True)

# 微调后的模型
finetuned = AutoModelForCausalLM.from_pretrained(
    export_path, torch_dtype=torch.bfloat16, device_map="auto"
)
# 微调前的基座(用于对比)
base = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2.5-7B-Instruct", torch_dtype=torch.bfloat16, device_map="auto"
)

test_prompt = "用一句话向产品经理解释 HNSW 索引"
print("微调前:", chat(base, tokenizer, test_prompt))
print("微调后:", chat(finetuned, tokenizer, test_prompt))

一份典型的对比输出:

微调前: HNSW 索引是一种基于图的近似最近邻搜索算法,在高维空间中构建分层图结构,通过贪心搜索找到最近邻居。
微调后: HNSW 是一种"跳表 + 图"的分层小世界网络索引,专门解决高维向量的最近邻检索。对产品经理来说记住三点:查询快(毫秒级)、内存可控、支持增量插入。

注意微调后的变化:更短的句子、更贴合内部口径的类比、稳定的"小结句式"——这正是"学会了你的数据分布"的表现。如果两者差别不大,问题几乎一定出在数据上,而不是训练上。

五、评估迭代:别用"感觉"验收微调 ​

1. 建立验证集 ​

从数据集中抽出 10%-20% 永不参与训练的样本作为验证集(或专门构造 30-100 条"考试题")。训练中每几个 epoch 在验证集上跑一次推理,用搭建一套 LLM 评估里的指标打分。如果训练集指标上涨、验证集不动甚至下降,就是过拟合——此时停训、减 epochs、降学习率。

2. 人工抽检是最低要求 ​

自动指标(loss、BLEU)对开放式回答的感知有限,每次迭代至少人工看 20-50 条真实输出,对照检查三类问题:

  • 幻觉:模型是否编造了训练数据里没有的"事实";
  • 风格漂移:是否把内部腔调带偏(如训练数据大量英文、线上却要求中文输出);
  • 边界行为:面对训练分布之外的输入(脏话、超长问题、空输入)是否失控。

3. 与评估体系对接 ​

微调不是"训完就完",而是评估循环中的一个环节。把微调后的模型接入你的评估体系,与基座模型或上一个版本在黄金数据集上做 A/B 对比,用数据而不是印象决定是否上线。评估指标的选择、LLM-as-judge 的用法见LLM 评估与基准。

六、常见坑:一张表排查 ​

坑症状原因排查 / 修复
数据格式错误训练正常但生成乱码、重复"assistant"标记ChatML 模板与 tokenizer 不匹配、丢了 `<im_end
过拟合小数据训练 loss 低但验证集差、回答像"背课文"数据少(< 1k)、epochs 多、r 过大减少 epochs、混合通用数据、数据增强
灾难性遗忘通用能力下降(数学、代码变笨)纯业务数据微调、原有知识被覆盖按 3:1 混入通用数据;做多任务混合训练
中文分词问题中文回答断句奇怪、丢字tokenizer 缺 pad token、序列被截断显式设置 pad_token = eos_token;检查 max_seq_length
显存 OOM训练中途崩溃序列过长 / batch 过大降 max_seq_length、batch=1 + 梯度累积、r 降到 8
量化加载失败bitsandbytes 报错驱动 / CUDA 版本不匹配pip install -U bitsandbytes,检查 nvidia-smi
训练 loss 不降从一开始就震荡学习率过大lr 降到 5e-5 重试

一句话总纲:微调出现任何问题,先怀疑数据,再怀疑训练配置,最后才怀疑代码。更全面的训练与部署陷阱清单见常见陷阱与反模式。

七、进阶:DPO 与多轮对话 ​

1. DPO:用偏好数据让模型"更讨喜" ​

SFT 只能教会模型"正确回答",学不会"哪种回答更好"。DPO(Direct Preference Optimization) 不需要训练奖励模型,直接用"好回答 vs 坏回答"的对比数据优化策略,是对齐的一线方法,原理见对齐:RLHF 与 DPO。

python
from trl import DPOConfig, DPOTrainer

dpo_config = DPOConfig(
    output_dir="./qwen2.5-7b-dpo",
    per_device_train_batch_size=1,
    gradient_accumulation_steps=8,
    learning_rate=1e-6,          # DPO 通常用更小的学习率
    num_train_epochs=1,
    bf16=True,
    beta=0.1,                    # DPO 温度系数,控制对偏好的跟随强度
)

dpo_trainer = DPOTrainer(
    model=model,                 # 可复用 SFT 后的 LoRA 模型
    args=dpo_config,
    train_dataset=dpo_ds,        # 每样本三字段:{prompt, chosen, rejected}
    processing_class=tokenizer,
)
dpo_trainer.train()

dpo_ds 的每条样本包含三个字段:prompt(用户问题)、chosen(更优回答)、rejected(较差回答)。偏好数据可以从人工标注、用户反馈(点赞/点踩)、或两个模型的对比输出中积累——后者的采集链路见搭建一套 LLM 评估的生产评估一节。

SFT → DPO 是标准组合拳:先用 SFT 把模型拉到领域,再用 DPO 精调风格与偏好。预算或时间有限时,先只做 SFT。

2. 多轮对话微调 ​

单轮指令数据训练出的模型会"忘记"历史。多轮对话微调用 ShareGPT 格式,每条样本是完整对话历史:

json
{
  "conversations": [
    {"from": "human", "value": "帮我推荐一款适合跑马拉松的耳机"},
    {"from": "gpt", "value": "考虑佩戴稳固性和续航,推荐 XX 骨传导耳机,续航 10 小时。"},
    {"from": "human", "value": "预算 500 以内呢?"},
    {"from": "gpt", "value": "那推荐 YY,同样骨传导,续航 8 小时,399 元。"}
  ]
}

多轮样本的构造要点:保留完整历史,让模型学会"在上下文中理解指代";同时保留一部分"从中间截断"的样本,模拟真实对话被切掉开头的情况。对话格式与模板的底层原理见大语言模型(LLM)。

八、延伸阅读 ​

参考资料 ​

建议顺序:先跑通步骤 1-5 的最小示例(哪怕数据只有 100 条)→ 用真实业务数据替换 → 在验证集上人工抽检迭代 2-3 轮 → 用评估体系验收 → 再考虑 DPO 精调。