实战 SOP
实战 SOP

LLM 微调实战 SOP:LoRA/QLoRA 全流程

LLM 微调实战 SOP:LoRA/QLoRA 全流程。何时微调 vs RAG vs prompt、数据准备(指令格式)、基座选型、QLoRA 4-bit 量化加载、训练配置、评估、合并部署。附 peft/transformers 代码示例。

发布于 2026年8月2日8 分钟阅读
<!-- llm-fine-tuning-sop | sop | LLM 微调实战 SOP:LoRA/QLoRA 全流程 -->

通用大模型什么都能聊两句,但一碰到你的业务就露馅:让它按公司话术写客服回复,它写成"亲亲在吗";让它把工单归类成你那套三十几个内部业务线,它硬往通用类目上靠;让它输出固定 JSON schema,它十次里漏两次字段。这些问题,prompt 调到头也压不住,RAG 又只能补知识、改不了"行为风格"。这时候才轮到微调。但全参微调一个 7B 模型,光 Adam 优化器状态就要吃掉几十 G 显存,单卡根本扛不住。这篇 SOP 走一遍用 LoRA / QLoRA 做参数高效微调的全流程:先讲清楚什么时候才该微调、LoRA 怎么省显存,再按数据→基座→配置→训练→评估→部署六步给可复制代码,最后附踩坑和 FAQ。和本站《RAG 系统评估 SOP》《AI agent 评测与基准测试 SOP》不同,那两篇讲怎么"评",这篇讲怎么"训"——但评估那一节会复用同样的评测思路。


一、先决策:该微调,还是 RAG,还是 prompt

微调不是万能锤子,动手前先过一遍这张决策表。选错路径,后面所有功夫都白费。

路径改的是什么适合不适合
Prompt / Few-shot不动模型,靠上下文约束通用任务、临时需求、样本<20 条需要稳定输出格式、要蒸馏到小模型
RAG检索外部知识,拼进上下文知识频繁更新、要引用来源、文档量大需要改变输出"风格/行为"、要离线跑
微调改模型权重固定输出风格/格式、领域术语、蒸馏降本知识天天变(该用 RAG)、没评测集(无法验证)

一句话判断:知识用 RAG 补,行为用微调改,临时需求用 prompt 顶。 最常见的误判是"知识变了就微调"——模型权重是静态快照,微调完知识又过时了,白训。另一个误判是"只有几十条样例就直接微调"——这个量级 prompt few-shot 更划算,微调要么过拟合、要么学不到东西。微调的门槛是:有几百到几千条高质量标注、行为需求稳定、有 held-out 集能验证"训完确实变好了"。


二、LoRA / QLoRA 原理:为什么不全参微调

全参微调更新每一层权重矩阵 W,对 7B 模型就是 70 亿参数都要算梯度、存优化器状态(Adam 还要额外 2 倍参数存动量和方差),24G 显存的单卡根本装不下。

LoRA(Low-Rank Adaptation) 的核心假设:微调时权重的变化量 ΔW 是低秩的,可以分解成两个小矩阵相乘 W = W₀ + B·A,其中 B 是 d×r、A 是 r×d,秩 r 远小于维度 d(比如 r=16,d=4096)。训练时冻住 W₀,只训 B 和 A。参数量从 d×d 降到 2×d×r,对 4096 维单层就是从约 1678 万降到约 13 万,不到 1%。显存压力骤降,训完只导出几十 MB 的适配器文件。

几个关键超参先记住,后面配置要用:

  • r(rank):低秩矩阵的秩。r 越大表达能力越强、也越容易过拟合。经验起点 8-16,任务复杂、数据多可到 32-64。
  • alpha:缩放系数,实际作用是 ΔW × (alpha/r)。经验值 alpha = 2×r(r=16 用 alpha=32),用来平衡学习率。
  • target_modules:挂 LoRA 的层。挂全 attn 线性层(q/k/v/o_proj)是常见做法,MLP 层(gate/up/down_proj)可加可不加。
  • dropout:适配器上的 dropout,防过拟合,0.05-0.1 常用。

QLoRA 在 LoRA 基础上把冻住的基座 W₀ 量化到 4-bit(nf4 量化 + 双重量化),前向反传时用 bfloat16 计算,梯度只回传到 LoRA 适配器。效果:7B 模型单张 16-24G 消费级显卡就能训,13B 勉强可上。这也是为什么"LoRA + QLoRA"几乎是个人开发者和小团队微调的事实标准。


三、准备数据:指令格式与清洗

数据决定上限,模型只决定逼近上限的速度。微调翻车八成是数据问题,不是配置问题。

指令微调的标准样本是三段式:instruction(指令)、input(可选输入)、output(期望输出):

python
import json
from datasets import Dataset

# 原始数据:instruction / input / output 三段式
raw = [
    {"instruction": "把下面的客服工单归类为标准业务类型,只输出类目名。",
     "input": "客户反馈:下单后 3 天还没发货,要求催办。",
     "output": "物流催办"},
    {"instruction": "把下面的客服工单归类为标准业务类型,只输出类目名。",
     "input": "客户反馈:收到的商品有破损,要求换货。",
     "output": "售后换货"},
    # ... 经验值:风格/格式类任务 500-2000 条起步,
    # 领域知识注入类需要更多;少于 200 条建议先回 prompt few-shot
]


def format_prompt(ex):
    """拼成模型认识的 chatml / alpaca 式 prompt(以基座官方模板为准)"""
    text = f"### 指令:\n{ex['instruction']}\n"
    if ex.get("input"):
        text += f"### 输入:\n{ex['input']}\n"
    text += f"### 输出:\n{ex['output']}"
    return {"text": text}


ds = Dataset.from_list(raw).map(format_prompt)
ds = ds.train_test_split(test_size=0.1, seed=42)   # 留 10% 做 held-out 评估
print(ds)

清洗三件事别省:去重(完全重复的样本会让模型死记)、筛长度(超 max_length 的截断或拆)、查格式(output 要不要带标点、JSON 要不要合法)。最低样本量经验值见代码注释——风格/格式类任务 500 条能见效,注入领域行为通常要 2000 条以上,且必须留出模型没见过的 held-out 集,否则评估无意义。


四、选基座模型

基座选型三原则:开源可商用、中文能力强(中文场景)、显存扛得住。

基座(示例)显存(QLoRA 4-bit)说明
Qwen2.5-7B-Instruct~8-10G中文强,社区微调样例多
Llama-3.1-8B-Instruct~10G英文生态最成熟,中文需评估
DeepSeek 系视版本而定以官方仓库为准

注意:上表显存为经验估算,实际取决于序列长度、batch、是否开梯度检查点。模型 ID 和许可协议以官方仓库为准,选型前务必确认许可允许你的商用场景。


五、配置 LoRA + QLoRA 量化加载

先装依赖,再加载 4-bit 量化基座、挂上 LoRA 适配器:

python
# pip install peft transformers bitsandbytes accelerate
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training

MODEL_ID = "Qwen/Qwen2.5-7B-Instruct"   # 以官方仓库为准

# QLoRA:4-bit 量化加载基座
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
)
model = prepare_model_for_kbit_training(model)   # 冻住量化层、开梯度检查点等

# LoRA 适配器配置
lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()   # 确认可训参数占比 <1%

prepare_model_for_kbit_training 做的事:把量化层设为不可训、启用梯度检查点、把 LayerNorm 转 fp32 保数值稳定。target_modules 挂 q/k/v/o_proj 是 attn 全线性层的常见做法;想更强可加上 MLP 的 gate/up/down_proj,代价是可训参数翻倍。


六、训练

transformers.Trainer 跑最稳,配置一把梭:

python
def tokenize(ex):
    tok = tokenizer(
        ex["text"], truncation=True, max_length=2048, padding=False,
    )
    tok["labels"] = tok["input_ids"].copy()
    return tok

train_ds = ds["train"].map(tokenize, remove_columns=ds["train"].column_names)

from transformers import TrainingArguments, Trainer, DataCollatorForSeq2Seq

training_args = TrainingArguments(
    output_dir="./output/lora-run1",
    num_train_epochs=3,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,      # 等效 batch=16
    learning_rate=2e-4,
    lr_scheduler_type="cosine",
    warmup_ratio=0.03,
    logging_steps=10,
    save_strategy="epoch",
    bf16=True,
    optim="paged_adamw_8bit",           # QLoRA 专用,省显存
    gradient_checkpointing=True,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_ds,
    data_collator=DataCollatorForSeq2Seq(tokenizer, padding=True),
)
trainer.train()
model.save_pretrained("./output/lora-adapter")   # 只存适配器(几十 MB)

超参怎么调:epochs 经验 2-5,数据少用 4-5 轮(盯过拟合),数据多用 2-3 轮;学习率 1e-4 到 3e-4 是 LoRA 安全区,太高发散太低学不动;batch 受显存限制,靠 gradient_accumulation_steps 凑到等效 16-32。TRL 的 SFTTrainer 封装了 prompt 模板和 loss masking,比手搓 Trainer 省事,但版本间参数变动较大,以官方文档为准。上面代码里 labels = input_ids.copy() 会对整段 prompt(含指令)算 loss,更严谨的做法是只对 output 部分算 loss、把 prompt 部分置 -100,SFTTrainer 默认帮你处理这件事。


七、评估:训完到底变好没

没有评估的微调等于盲调。评估思路复用本站《RAG 系统评估 SOP》和《AI agent 评测与基准测试 SOP》的框架:必须用训练时没见过的 held-out 集(上一步 train_test_split 留的那 10%),分三层看。

python
# 拿 held-out 集跑推理,收集模型输出
import torch

model.eval()
results = []
for ex in ds["test"]:
    prompt = ex["text"].rsplit("### 输出:\n", 1)[0] + "### 输出:\n"
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    with torch.no_grad():
        out = model.generate(**inputs, max_new_tokens=64, do_sample=False)
    pred = tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
    results.append({"pred": pred, "gold": ex["text"].rsplit("### 输出:\n", 1)[1]})

三类评估任选:

  • 任务指标:格式校验(JSON 合法率)、分类准确率、字段命中率,能自动算的优先自动算。
  • LLM-as-judge:拿一个更强的模型当裁判,逐条评"忠实度、格式正确性、是否跑题",prompt 模板同 RAG 评估那篇。
  • 人工 spot-check:至少抽 30 条人工看,专挑 bad case。只看自动分会漏"流畅但方向错"的 case。

关键一句:必须和基座模型(没微调的)在同一个 held-out 集上对比。 微调后分数没涨甚至掉了,说明数据或配置有问题,别硬上。


八、合并与部署

适配器是外挂,部署通常要合并回完整权重再量化导出:

python
from peft import AutoPeftModelForCausalLM

adapter_path = "./output/lora-adapter"
merged = AutoPeftModelForCausalLM.from_pretrained(
    adapter_path, device_map="auto", torch_dtype=torch.bfloat16,
)
merged = merged.merge_and_unload()   # 把 LoRA 权重并回基座
merged.save_pretrained("./output/merged-model", safe_serialization=True)
tokenizer.save_pretrained("./output/merged-model")

合并后的模型可走 vLLM、Ollama、llama.cpp 部署。Ollama 走 GGUF 量化格式,vLLM 直接加载 safetensors。部署成本主要看显存:7B 4-bit 量化后约 5-6G,单张消费级显卡或 12G 云实例即可推理。注意 QLoRA 训练用的是量化基座,但合并时要加载 fp16/bf16 原始权重再合并适配器,不要直接拿 4-bit 权重合并——否则精度损失叠加。


九、踩坑记录

坑一:数据质量 > 数量。 1000 条噪声样本不如 200 条精标。常见噪声:output 格式不统一(有的带句号有的不带)、instruction 和 input 信息重复、样本之间互相矛盾。先洗 100 条人工抽检全过,再扩量。

坑二:灾难性遗忘。 微调太狠,模型把通用能力丢了——能归类工单但不会正常聊天。防法:epochs 别超 5、学习率别超 3e-4、混入一部分通用指令数据保底。

坑三:过拟合。 loss 一直降但 held-out 分数不涨甚至掉。表现为模型把训练样本死记,换种问法就崩。防法:盯 held-out 指标早停、降 rank、加 dropout、扩数据。

坑四:显存溢出(OOM)。 三招依次试:降 batch 到 1 再靠 gradient_accumulation 凑、开 gradient_checkpointing、换 QLoRA(若还没用)。序列长度超 2048 也是显存大户,能截短就截短。

坑五:只看训练 loss 不看评估集。 训练 loss 走得漂亮不代表模型变好,过拟合时 loss 照降。held-out 指标才是唯一判据,没评估集别训。

坑六:target_modules 挂漏了。 只挂 q_proj 不挂 v_proj,适配器学不到该学的东西,效果差一截。经验做法是 attn 四件套(q/k/v/o_proj)全挂,不确定时挂比不挂强。


FAQ

Q1:LoRA 和全参微调到底差在哪?什么时候必须上全参? LoRA 只训低秩适配器(不到 1% 参数),省显存、训完只存几十 MB、可热插拔;全参微调改所有权重,效果上限略高但显存是 LoRA 的几倍到几十倍。绝大多数风格/格式/领域适配任务 LoRA 够用。只有当任务需要模型学"全新能力"且数据量充足(万级以上)、算力不限时,才值得考虑全参。个人和小团队默认走 LoRA/QLoRA。

Q2:多少数据够?几百条能训吗? 风格/格式类任务 500-2000 条能见效;注入领域行为通常要 2000 条以上。几百条能训出"格式对了"的效果,但泛化差。少于 200 条建议先回 prompt few-shot。数据质量比数量重要——200 条精标 > 2000 条噪声。

Q3:基座选哪个?7B 够不够? 中文场景 Qwen2.5-7B-Instruct 是稳妥起点,社区微调资料多、中文强。英文生态 Llama 系最成熟。7B 在 QLoRA 下单卡可训、单卡可部署,是性价比甜点。基座太小(3B 以下)微调后能力天花板低,太大(70B)个人训不动,除非有云算力。许可协议务必查官方仓库确认可商用。

Q4:怎么评估微调效果?必须搭评测框架吗? 最小可用:held-out 集跑推理,人工抽 30 条看 + 自动算任务指标(格式合法率、准确率)。进阶用 LLM-as-judge 批量打分,思路同本站《RAG 系统评估 SOP》。关键是必须和未微调基座同集对比,否则无法证明微调有用。不一定要 Ragas/DeepEval 这种框架,手搓脚本也能评。

Q5:部署成本多少?微调完怎么上线? 7B 模型 4-bit 量化后约 5-6G 显存,一张 12G 消费级显卡或月费几十美元的云实例即可推理。流程:合并 LoRA 适配器→(可选)量化导出 GGUF/AWQ→vLLM 或 Ollama 加载。vLLM 吞吐高适合线上并发,Ollama 适合本地单机。训练成本看时长:7B QLoRA 在单张 24G 卡上跑 3 epochs、2000 条样本,几小时内能完成。


参考来源

本文由 AI 辅助生成,经人工审核编辑。最后更新:2026-08-02

常见问题

LoRA 和全参微调到底差在哪?什么时候必须上全参?
LoRA 只训低秩适配器(不到 1% 参数),省显存、训完只存几十 MB、可热插拔;全参微调改所有权重,效果上限略高但显存是 LoRA 的几倍到几十倍。绝大多数风格/格式/领域适配任务 LoRA 够用。只有当任务需要模型学"全新能力"且数据量充足(万级以上)、算力不限时,才值得考虑全参。个人和小团队默认走 LoRA/QLoRA。
多少数据够?几百条能训吗?
风格/格式类任务 500-2000 条能见效;注入领域行为通常要 2000 条以上。几百条能训出"格式对了"的效果,但泛化差。少于 200 条建议先回 prompt few-shot。数据质量比数量重要——200 条精标 > 2000 条噪声。
基座选哪个?7B 够不够?
中文场景 Qwen2.5-7B-Instruct 是稳妥起点,社区微调资料多、中文强。英文生态 Llama 系最成熟。7B 在 QLoRA 下单卡可训、单卡可部署,是性价比甜点。基座太小(3B 以下)微调后能力天花板低,太大(70B)个人训不动,除非有云算力。许可协议务必查官方仓库确认可商用。
怎么评估微调效果?必须搭评测框架吗?
最小可用:held-out 集跑推理,人工抽 30 条看 + 自动算任务指标(格式合法率、准确率)。进阶用 LLM-as-judge 批量打分,思路同本站《RAG 系统评估 SOP》。关键是必须和未微调基座同集对比,否则无法证明微调有用。不一定要 Ragas/DeepEval 这种框架,手搓脚本也能评。
部署成本多少?微调完怎么上线?
7B 模型 4-bit 量化后约 5-6G 显存,一张 12G 消费级显卡或月费几十美元的云实例即可推理。流程:合并 LoRA 适配器→(可选)量化导出 GGUF/AWQ→vLLM 或 Ollama 加载。vLLM 吞吐高适合线上并发,Ollama 适合本地单机。训练成本看时长:7B QLoRA 在单张 24G 卡上跑 3 epochs、2000 条样本,几小时内能完成。

相关文章

实战 SOP

AI 数字人制作实战 SOP:从脚本到成品的可复制流程

把 AI 数字人制作拆成六步可复制流程:明确用途选工具(HeyGen/D-ID/Synthesia/Colossyan/DeepBrain 及国内腾讯智影/硅基智能)、写口播脚本(附 prompt 模板)、选或定制形象、先定音色再生成口型、字幕剪辑与合规后处理、平台适配发布。附 5 个避坑(形象授权/口型对不齐/多语言音色/长视频成本/合规标识)和 5 条 FAQ。代表性流程,非单一工具实测,功能以官网为准。

2026年8月7日8 分钟阅读
实战 SOP

block/buzz 自托管部署 SOP:从 Docker 到 agent 入组

block/buzz 自托管部署完整 SOP(与 buzz-hive-mind 热点文成对):本地开发栈(just setup/build/dev)+ 生产单节点(deploy/compose Docker,Postgres/Redis/MinIO)+ 配置(.env:RELAY_URL/BUZZ_RELAY_PRIVATE_KEY/RELAY_OWNER_PUBKEY)+ agent 入组(Nostr keypair NIP-98 签名,buzz-admin 管成员)+ 闭门 relay + 5 FAQ。部署命令全据 README/compose/.env/CLI/ARCHITECTURE,未编造。

2026年8月6日9 分钟阅读
实战 SOP

n8n 搭建 AI agent 工作流实战 SOP:部署与避坑

在 n8n 画布里搭一个能自主调用工具的 AI agent 工作流的完整 SOP:Docker 自托管一条命令部署、AI Agent 节点四件套解剖(Language Model+Memory+Tools+System Prompt)、分步搭建(选触发器->配节点->加工具->输出->测试发布)、五个避坑(Memory 失忆/API Key 硬编码/过度设计/上下文漂移/数据格式不匹配)+5 FAQ。节点参数以 n8n 官方文档为准,给配置逻辑不伪造完整 JSON。

2026年8月6日9 分钟阅读