引言:微调不是万能药,但很多时候不可缺少
"我们能不能直接用 Prompt Engineering 代替微调?"——这是 2026 年企业 AI 团队最常问的问题之一。
答案是:在很多场景下可以,但有几类问题 Prompt 搞不定:
- 输出格式一致性:需要严格 JSON 结构、特定业务模板,Prompt 控制总有偏差
- 专有知识注入:行业黑话、内部命名规范、特定写作风格
- 推理成本优化:把 GPT-4 级能力蒸馏到小模型,降低 10x 调用成本
- 低延迟部署:小模型微调后部署本地,远低于 API 延迟
本文覆盖 2026 年最主流的三条微调技术路线:SFT(监督微调)、LoRA(高效参数微调)、DPO(直接偏好优化),以及完整的工程流程。
一、微调技术路线选择
1.1 三条路线对比
| 技术 | 数据类型 | 参数规模 | 适用目标 | 难度 |
|---|---|---|---|---|
| Full Fine-tuning | (input, output) 对 | 全量参数 | 最强效果,高成本 | 高 |
| LoRA | (input, output) 对 | <1% 参数 | 平衡效果与成本 | 中 |
| DPO | (prompt, chosen, rejected) 三元组 | LoRA 或全量 | 对齐人类偏好、减少有害输出 | 中高 |
1.2 决策树
需要微调吗?
├── 纯格式/风格问题 → 先尝试 few-shot Prompt,微调是备选
├── 专有知识(不常更新)→ 微调 > RAG
├── 专有知识(频繁更新)→ RAG > 微调
├── 成本优化(小模型替代大模型)→ 知识蒸馏 + SFT
└── 减少有害输出/改善风格一致性 → DPO
微调规模?
├── < 1B 参数 → 全量微调可行
├── 1B - 13B → LoRA 推荐
└── > 13B → QLoRA(量化 + LoRA)
二、数据工程:微调的成败关键
2.1 高质量训练数据的标准
微调数据质量远比数量重要。1000 条精心设计的数据,往往优于 10000 条低质量数据。
数据格式(Alpaca 格式):
[
{
"instruction": "将以下技术文档翻译成通俗易懂的中文,面向非技术人员",
"input": "The transformer architecture utilizes multi-head self-attention mechanisms...",
"output": "Transformer 架构通过让模型同时关注文本的不同部分来理解语言..."
},
{
"instruction": "分析这段代码的潜在问题",
"input": "def get_user(id):\n return db.query(f'SELECT * FROM users WHERE id={id}')",
"output": "这段代码存在 SQL 注入漏洞。使用字符串格式化构建 SQL 查询,攻击者可以通过输入 `1; DROP TABLE users` 等恶意 id 来破坏数据库..."
}
]
2.2 用 LLM 生成合成训练数据
from openai import OpenAI
import json
client = OpenAI()
def generate_training_pair(seed_example: dict, target_skill: str, n: int = 5) -> list:
"""基于种子样例,生成更多多样化的训练数据"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": f"""生成 {n} 条类似的训练数据对,目标技能:{target_skill}。
参考示例:{json.dumps(seed_example, ensure_ascii=False)}
要求:
1. 覆盖多样化的输入变体
2. 输出要高质量、详细、专业
3. 格式输出 JSON 数组:[{{"instruction":..., "input":..., "output":...}}]"""
}
],
response_format={"type": "json_object"},
temperature=0.8 # 适当提高随机性增加多样性
)
result = json.loads(response.choices[0].message.content)
return result.get("data", [])
# 生成专属领域数据集
seed = {
"instruction": "用 Python 实现",
"input": "一个计算斐波那契数列的函数",
"output": "def fibonacci(n):\n if n <= 1:\n return n\n a, b = 0, 1\n for _ in range(2, n + 1):\n a, b = b, a + b\n return b"
}
training_data = generate_training_pair(seed, "Python 代码生成", n=20)
三、LoRA 微调实战
3.1 环境配置
pip install transformers peft trl datasets accelerate bitsandbytes
3.2 QLoRA 完整训练脚本
from transformers import (
AutoModelForCausalLM, AutoTokenizer,
BitsAndBytesConfig, TrainingArguments
)
from peft import LoraConfig, get_peft_model, TaskType
from trl import SFTTrainer
from datasets import load_dataset
import torch
# 1. 加载量化模型(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,
)
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-7B-Instruct",
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
tokenizer.pad_token = tokenizer.eos_token
# 2. 配置 LoRA
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # LoRA 秩,越大参数越多,效果越好
# 推荐范围:4-64,通常 16 是好的起点
lora_alpha=32, # 缩放因子,通常设为 r 的 2 倍
target_modules=[ # 应用 LoRA 的目标模块
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
lora_dropout=0.1, # 防过拟合
bias="none",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 输出:trainable params: 20,971,520 || all params: 7,241,732,096 || trainable%: 0.2895
# 3. 准备数据集
def format_instruction(sample):
return f"""<|im_start|>system
你是一个专业的代码助手。<|im_end|>
<|im_start|>user
{sample['instruction']}
{sample.get('input', '')}<|im_end|>
<|im_start|>assistant
{sample['output']}<|im_end|>"""
dataset = load_dataset("json", data_files="training_data.json")
dataset = dataset["train"].map(lambda x: {"text": format_instruction(x)})
# 4. 训练配置
training_args = TrainingArguments(
output_dir="./qwen2.5-7b-lora-output",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # 有效 batch_size = 4 * 4 = 16
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_ratio=0.05,
fp16=False,
bf16=True, # A100/H100 支持 bf16
logging_steps=10,
save_steps=500,
evaluation_strategy="steps",
eval_steps=200,
load_best_model_at_end=True,
report_to="wandb", # 使用 W&B 记录训练过程
)
# 5. 启动训练
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=2048,
tokenizer=tokenizer,
)
trainer.train()
# 6. 保存 LoRA 权重
model.save_pretrained("./qwen2.5-7b-lora-weights")
四、DPO:让模型更符合人类偏好
4.1 DPO 数据格式
# DPO 需要三元组数据:(prompt, chosen_response, rejected_response)
dpo_dataset = [
{
"prompt": "解释什么是机器学习",
"chosen": "机器学习是一种让计算机从数据中自动学习规律的技术。通过分析大量历史数据,算法能够发现模式并做出预测,而无需显式编程...",
"rejected": "机器学习就是机器在学习,通过算法进行训练。" # 过于简短、不专业
}
]
4.2 DPO 训练
from trl import DPOTrainer, DPOConfig
dpo_config = DPOConfig(
beta=0.1, # KL 散度惩罚系数,越小越激进
learning_rate=5e-5,
per_device_train_batch_size=2,
num_train_epochs=1,
output_dir="./dpo-output",
)
dpo_trainer = DPOTrainer(
model=model,
ref_model=None, # None 时自动从 model 创建参考模型
args=dpo_config,
train_dataset=dpo_dataset,
tokenizer=tokenizer,
)
dpo_trainer.train()
五、微调效果评估
from evaluate import load
# 使用标准评估指标
rouge = load("rouge")
bleu = load("bleu")
def evaluate_model(model, tokenizer, test_samples):
predictions = []
references = []
for sample in test_samples:
# 生成预测
inputs = tokenizer(sample["prompt"], return_tensors="pt").to(model.device)
output = model.generate(**inputs, max_new_tokens=200)
prediction = tokenizer.decode(output[0], skip_special_tokens=True)
predictions.append(prediction)
references.append(sample["expected"])
rouge_score = rouge.compute(predictions=predictions, references=references)
print(f"ROUGE-L: {rouge_score['rougeL']:.3f}")
return rouge_score
结语
微调工程的核心不在于技术复杂度,而在于数据质量和评估体系。
正确的实践路径:首先用 10-50 条高质量数据快速验证微调方向是否可行;验证后扩充到 200-2000 条数据进行完整训练;持续用评估集监控微调效果,防止过拟合。
LoRA 的出现让微调门槛大幅降低——一张 24GB 显存的消费级 GPU,就能微调 7B 参数的模型。掌握这套工具链,是 2026 年 AI 工程师的核心竞争力之一。