什么是模型微调
模型微调(Fine-tuning)指在已预训练模型(Pre-trained Model)的基础上,用特定领域或任务的标注样本继续训练,直接修改模型内部权重参数,使模型输出风格与格式贴合目标任务。
Fine-tuning 与 Prompt Engineering 的本质区别在于参数是否发生变化:
| 维度 | Prompt Engineering | Fine-tuning |
|---|---|---|
| 改动对象 | 输入侧(Prompt 文本) | 模型侧(Weight 权重) |
| 成本 | 极低,改完即生效 | 前期高,需标注数据 + 训练资源 |
| 可解释性 | 高,出错可直接看 Prompt | 低,权重不可读,定位困难 |
| 适用 | 一次性任务、规则常变 | 高频重复任务、格式强约束 |
⚠️ 最重要的边界:微调不等于灌知识
这是很多人踩坑的地方。微调改变的是模型的「行为倾向」(behavior),不是「知识储备」(knowledge)。
- ✅ 适合微调:输出格式固定、分类边界明确、回答口径统一
- ❌ 不适合微调:业务规则、产品参数、政策条款等随时间变化的事实
后者应走 RAG(Retrieval-Augmented Generation,检索增强生成)。工程上的共识做法是 微调管格式,检索管知识。
三种微调方案对比(以 7B 模型 FP16 单卡量级为参考)
| 方案 | 可训练参数 | 显存需求 | 产物大小 | 适用场景 |
|---|---|---|---|---|
| Full Fine-tuning | 全部(约 70 亿) | 约 56--60 GB | 数 GB | 单任务、追求极限效果、有充足算力 |
| LoRA | 约 0.1%--1% | 约 12--16 GB | 几十 MB | 多任务、样本有限、消费级硬件 |
| QLoRA | 约 0.1%--1%(底模 4bit) | 约 6--8 GB | 几十 MB | 显存受限场景,效果打折可接受 |
表中数值为 7B 模型 FP16 量化精度下的量级参考,不同模型规模、序列长度、batch size 与优化器配置会带来显著波动,实测请以自己环境为准。
环境依赖版本
本文示例基于以下版本验证:
torch==2.3.0
transformers==4.44.0
peft==0.13.0
trl==0.9.6
bitsandbytes==0.43.0
accelerate==0.33.0
datasets==2.20.0
⚠️ PEFT 与 bitsandbytes 迭代较快,新版本 API 偶有变动。安装前建议先在虚拟环境中锁定版本,避免污染全局环境。
最小可运行示例:LoRA 微调
Step 1 --- 配置 LoraConfig
python
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from transformers import AutoModelForCausalLM, AutoTokenizer
# 加载底模(QLoRA 场景需先做 kbit 预处理)
base_model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen-1_8B",
torch_dtype="auto",
device_map="auto",
trust_remote_code=True,
)
base_model = prepare_model_for_kbit_training(base_model)
lora_config = LoraConfig(
r=16, # 秩:低秩矩阵的维度,常见 8/16/32/64
lora_alpha=32, # 缩放系数,一般取 2*r
lora_dropout=0.05,
target_modules=[ # 注入的子层
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
task_type="CAUSAL_LM",
)
model = get_peft_model(base_model, lora_config)
model.print_trainable_parameters() # 确认可训练参数量在 1% 以内
Step 2 --- 训练(TRL SFTTrainer)
python
from trl import SFTTrainer
from datasets import load_dataset
# tokenizer 需与 Step 1 用同一个底模与同一套 chat template
tokenizer = AutoTokenizer.from_pretrained(
"Qwen/Qwen-1_8B",
trust_remote_code=True,
)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
trainer = SFTTrainer(
model=model,
train_dataset=load_dataset("json", data_files="train.jsonl", split="train"),
dataset_text_field="text",
max_seq_length=1024,
tokenizer=tokenizer,
args=SFTConfig( # trl 0.9.x 用 args=;0.12+ 改为 training_args=
output_dir="./lora-out",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=1e-4, # 常见区间 1e-4 ~ 2e-4
fp16=True,
logging_steps=50,
save_strategy="epoch",
),
)
trainer.train()
# 只保存 adapter,不保存完整权重
trainer.model.save_pretrained("./lora-adapter")
tokenizer.save_pretrained("./lora-adapter")
⚠️ 版本坑 :
SFTTrainer在 trl 0.9.x 传args=SFTConfig(...),0.12 起改名为training_args=。本例锁定 0.9.6,照抄时先确认自己的 trl 版本,否则会报unexpected keyword argument。
Step 3 --- 加载与推理
python
from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen-1_8B", device_map="auto")
model = PeftModel.from_pretrained(base, "./lora-adapter")
参数怎么给
不要一次调到位,按下面顺序试:
- r 从 16 起手,效果不足再依次试 32、64。r 越大越容易过拟合,小样本下慎用。
- learning_rate 取 1e-4 ~ 2e-4。超过 1e-3 极易崩。
- lora_alpha 固定为 2×r,这是社区经验值,改动收益不大。
- target_modules 尽量全覆盖(QKV + MLP 六层),比只注入 Q/V 效果更稳。
工程实践建议
- 先判断该不该微调。 三道筛子:换模型不换架构的先调 Prompt;同一任务跑几十次格式仍有 10% 以上不稳定的才进微调;答案要随业务更新的走 RAG。
- 数据质量 >> 数据数量。 几百条高质量对齐样本的效果,通常好过几万条含噪样本。脏标注会被模型一并学进去。
- 一个底模挂多组 adapter。 发票抽取、合同审查、工单分类各训一个,切换任务只换 adapter 文件,不动底模,省显存也省加载时间。
- 必须保留校验分支。 微调后仍要在流程里保留格式校验(schema validation)与异常捕获,把跑偏样本回流进数据集形成迭代。这是准确率的保障,也是可解释性的替代方案------权重不可读,出错时你至少能靠校验层定位到具体样本。
- 多任务场景优先 LoRA 而非多次全量微调。 全量微调的产物是 N 个完整模型,存储与加载成本随任务数线性增长,Adapter 方案能把这个成本压到 1% 量级。
- 可解释性合规风险提前评估。 金融、医疗等强合规场景,权重变更后出错难以归因,上线前需与审批方对齐这一点。
总结
微调是用训练成本换掉长期的 Prompt 成本,核心价值在于让输出习惯固定下来。边界很清楚:格式和口径归微调,事实和知识归检索。
在 RPA(Robotic Process Automation)场景里,真正值得微调的是那些每天重复、格式必须一致、错一次就要人工返工的环节。微调之后仍要留校验分支,让适配器跟着业务一起长。