指令微调(Instruction Tuning):大模型对齐技术与RLHF完整指南

目录


从“续写文本”到“懂指令”

回想一下 GPT‑3 刚发布时的体验:你输入“写一首关于秋天的诗”,它可能给你丢出一大段训练语料里关于秋天的散文、歌词、甚至是其他用户曾经敲过的草稿——唯独缺少一首完整的、为你定制的诗。
这并不是模型“笨”,而是因为它只学会了续写。预训练阶段的核心任务叫语言建模,大白话就是:根据已经出现的词语,预测接下来最可能出现的词。它从没学过“要把用户的话当成指令去完成”。

为了让一个大模型从“只会接话的续写机器”变成能理解“总结一下这段话”“用表格整理以下信息”这类要求的智能助手,我们必须给它补上关键一课——指令微调(Instruction Tuning),以及后续的模型对齐工作,让输出不仅准确,还安全、符合人类偏好。


预训练模型的局限性

单纯的预训练模型就像一个读了整个互联网却不懂社交礼仪的“超级书虫”,突出问题有三方面:

1. 输出不可控,安全风险高

模型只是按训练数据中的模式去最大化下一个词的概率,完全不知道什么该说、什么不该说。它可能生成详尽的武器制造指南,也可能对医疗建议一本正经地胡说八道,甚至泄露训练数据中的个人信息。

2. 格式完全随机

你需要一段 JSON,它偏要给你写篇散文;你想要三句话的摘要,它可能连载十段。因为没有经过指令格式的训练,模型理解不到“格式约束”本身就是任务的一部分。

3. 只会续写,不会“听懂”

输入“翻译:今天天气很好 -> 英文”,它不会想到这是一个翻译指令,反而更倾向于把这段文字当成一个开头,续写出更多类似句式。

这些问题必须通过指令微调 + 偏好对齐来解决,也就是下面我们要讲的技术路线。


SFT:指令微调的第一步

SFT(Supervised Fine‑Tuning,监督微调),用一句话解释就是:拿“一问一答”的示范数据,手把手教模型在被问到某个问题时,应该怎样回答。这一阶段的目标是让模型习得指令跟随的能力,从“只会续写”变成“知道要回答问题”。

核心原理

  • 数据驱动:收集或构造大量 (指令, 输入, 理想输出) 三元组,覆盖翻译、问答、代码生成、总结等各类任务。
  • 损失计算:依然沿用语言模型的交叉熵损失,但只计算输出部分的损失,指令和上文部分不参与梯度更新(通常把这部分的标签设为 -100 忽略)。
  • 见效极快:在很多开源基座模型上,用几千到几万条高质量指令数据,训练几小时到几天,就能让模型从“听不懂”变得“基本可用”。

数据从哪里来?

  • 人工标注:聘请领域专家直接书写回答,质量最高,但成本也最高。
  • 众包平台:例如 Amazon Mechanical Turk,能快速扩大数据量,但必须配合严格的质量审核。
  • 开源数据集:Alpaca‑52K、ShareGPT、Belle 等,拿来就可以用。
  • 合成数据:用已有的强模型生成候选回答,再由人工筛选修正,兼顾效率和品质。

极简 SFT 代码示例

下面的代码演示了如何对 Qwen2.5‑1.5B 这类小模型进行监督微调。实际工作中强烈推荐使用 LoRA 等参数高效微调方法以节省显存,这里为了教学清晰,采用全参数微调的简化写法。

from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer
from datasets import Dataset
import torch

# 加载基础模型和分词器
model_name = "Qwen/Qwen2.5-1.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16)

# 格式化 prompt,Qwen 采用 im_start/im_end 标记
def format_prompt(instruction, input_text="", output_text=""):
    return (
        f"<|im_start|>system\n你是一个有用的助手<|im_end|>\n"
        f"<|im_start|>user\n{instruction}\n{input_text}<|im_end|>\n"
        f"<|im_start|>assistant\n{output_text}<|im_end|>"
    )

# 构造训练数据(少量示例)
train_data = [
    {"instruction": "将以下中文翻译成英文", "input": "今天北京的天气很好", "output": "The weather in Beijing is very nice today."},
    {"instruction": "写一段Python代码", "input": "计算1-100的和", "output": "total = sum(range(1, 101))\nprint(total)"}
]
dataset = Dataset.from_list(train_data)

# 预处理:将文本 tokenize 并构建 labels(只计算 assistant 部分的损失)
def tokenize_function(examples):
    full_texts = [
        format_prompt(inst, inp, out)
        for inst, inp, out in zip(examples["instruction"], examples["input"], examples["output"])
    ]
    # 先 tokenize 不带输出的 prompt(方便获取回答部分的 token 起始位置)
    prompt_texts = [
        format_prompt(inst, inp, "")   # 输出为空
        for inst, inp in zip(examples["instruction"], examples["input"])
    ]
    tokenized_prompt = tokenizer(prompt_texts, truncation=True, max_length=256)
    tokenized_full = tokenizer(full_texts, truncation=True, max_length=256)

    all_labels = []
    for i in range(len(tokenized_full["input_ids"])):
        prompt_len = len(tokenized_prompt["input_ids"][i])
        full_ids = tokenized_full["input_ids"][i]
        # prompt 部分标签置为 -100,只保留回答部分的标签
        labels = [-100] * len(full_ids)
        if prompt_len < len(full_ids):
            labels[prompt_len:] = full_ids[prompt_len:]
        all_labels.append(labels)

    tokenized_full["labels"] = all_labels
    return tokenized_full

tokenized_dataset = dataset.map(tokenize_function, batched=True, remove_columns=dataset.column_names)

# 训练配置
training_args = TrainingArguments(
    output_dir="./sft_model",
    num_train_epochs=2,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=4,
    fp16=True,
    logging_steps=10,
    learning_rate=5e-5
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset
)
trainer.train()
Tip

上面的代码只是一个教学示范。真实场景下,SFT 数据往往包含数千条甚至数万条样本,并使用 LoRA 或 QLoRA 来减少显存占用,否则 7B 以上模型的全参数微调会非常昂贵。

SFT 之后,模型已经能听懂“帮忙写一首诗”这种指令了,但它不一定能写出人最爱看的那一首。下一节我们会看到如何引入人类的喜好判断,让输出质量再上一个台阶。


RLHF:高级人类偏好对齐

SFT 让模型学会“照指令办事”,但同一条指令往往有多种合理回答:

  • 用户:“周末北京有什么好玩的?”
  • 回答A:“北京有很多景点:故宫、长城、颐和园……”
  • 回答B:“这周北京天气晴好,推荐去颐和园划船,或者去798看展,人相对少一些。”

两个回答都对,但明显 B 更贴近人类的偏好:具体、可操作、简短贴心。这时候就需要 RLHF(Reinforcement Learning from Human Feedback,人类反馈强化学习)

三步走流程

  1. SFT 预对齐:先用高质量指令数据训练一个基础的有监督模型,作为后续优化的起点。
  2. 训练奖励模型(Reward Model,RM):收集人类对“同一指令的多个回答”的偏好排序数据,训练一个能给任意回答打分的模型。分数越高,表示人类越喜欢这个回答。
  3. PPO 强化学习优化:把 SFT 模型看作策略网络,RM 作为奖励源,使用 PPO 算法迭代更新模型参数,让策略网络生成的回答能获得 RM 的高分。同时加入 KL 散度约束,避免模型为了刷分而胡言乱语。

RLHF 是目前效果最好、使用最广的对齐方案(ChatGPT 和 GPT‑4 都依赖它),但它也有明显的缺点:流程复杂,需要训练并维护多个模型,强化学习训练极不稳定,超参数多且难以调优,整个过程的计算和人力成本非常高。


DPO:简化版的对齐替代方案

面对 RLHF“又贵又难训”的困境,2023 年提出的 DPO(Direct Preference Optimization,直接偏好优化) 提供了一种优雅的替代思路。

DPO 的核心洞察是:奖励模型本质上是一个用来表示偏好的隐式函数,完全可以直接从偏好数据中推导出最优策略,而不需要显式训练一个 RM 再走强化学习。
因此,DPO 将整个对齐过程简化为一个分类任务:直接在 SFT 模型的基础上,让人类偏好的回答具有更高的对数概率,而不受偏好的回答概率降低。训练时只需要偏好对数据 (指令, 被喜欢的回答, 不被喜欢的回答),损失函数直接优化模型本身。

DPO 的突出优势

  • 流程简化:不需要额外训练 RM,也不需要 PPO 这种复杂的强化学习框架。
  • 训练稳定:没有奖励值爆炸、策略坍塌等问题,超参数敏感度大幅降低。
  • 效果接近 RLHF:在很多学术测试和实际场景中,DPO 的对齐效果与 RLHF 持平,甚至在部分无害性指标上更好。
  • 易于实现:Hugging Face trl 库已经内置了 DPOTrainer,加载数据即可开始训练。

即使你预算有限、团队规模小,DPO 也能让你在消费级硬件上把一个大模型对齐到“比较可爱”的水平。


技术对比与选择指南

技术核心思想优点缺点适用场景
SFT监督学习,用指令‑回答示范训练简单易实现,快速见效依赖标注数据,泛化和创造性受限快速原型、基础指令遵循、对齐前预训练
RLHF强化学习 + 人类偏好反馈对齐质量最高,安全性和有用性最佳过程复杂、训练不稳定、成本极高对安全、质量要求严苛的商业产品
DPO直接偏好优化,跳过奖励模型简单稳定,效果逼近 RLHF某些极端分布下可能稍逊于 RLHF平衡成本与效果的大多数实际场景
如果你的目标是让模型“听懂人话”,从 SFT 起步;如果希望它“回答问题像人话且安全”,在 SFT 基础上优先尝试 DPO;只有当资源和时间充裕,且对输出质量有超高要求时,再考虑完整的 RLHF 管线。

实际开源对齐案例

1. Vicuna

基于 LLaMA‑2 等模型,利用 ShareGPT 的大量对话数据做 SFT,并结合简化的偏好对齐,是最早证明“开源对话模型可以逼近 ChatGPT 体验”的项目之一。它的训练方案也成为许多社区微调的范本。

2. Qwen2.5‑Instruct 系列

阿里云通义千问开源的指令模型,完整经历了 SFT + 多层次安全对齐 + 多种偏好优化技术的混合。中文理解和生成能力尤其突出,多语言方面也表现优秀,是目前国内上手成本很低、效果又相当不错的实用模型。


总结与扩展阅读

指令微调与大模型对齐,是让“知识渊博但不会办事”的预训练模型变成“懂你且靠谱”的智能助手的必经之路:

  1. SFT 是基础:赋予模型指令跟随能力,回答“应该怎么做”。
  2. RLHF 是天花板:让回答更符合人类偏好,回答“怎么做更好”。
  3. DPO 是平衡之选:以更简单的方案逼近天花板的效果,兼顾成本和稳定性。
  4. 安全对齐不容忽视:不管采用哪种技术,最终模型都不能输出有害、违法或违背伦理的内容。

扩展阅读