预训练只是"学会说话",微调才是"学会做事"。这篇文章从 LoRA 的低秩魔法讲起,一路走到 PPO / GRPO / DPO 的强化学习对齐,原理、代码与部署一个都不少。
引言:为什么我们离不开微调
大模型的进化可以粗略分成两个阶段:预训练(Pre-training)与微调/对齐(Fine-tuning / Alignment)。预训练阶段,模型在数万亿 token 上学习语言的统计规律,获得通用的世界知识与语言能力——它像一个博学但尚未受过管束的"通才"。基座模型给出的回答是"平均化"的:你让它"写一封请假邮件",它大概率回你一段中规中矩、甚至带点说教味的话,因为你期望的分布与预训练语料里的分布并不一致。
微调要解决的核心问题正是:把模型的能力分布,重塑成符合特定任务、特定风格、特定价值观的分布。
从技术路线上,微调大致可以分成两代:
- 第一代:指令微调(SFT)。收集高质量的指令-回答数据,用监督学习让模型学会"按指令做事"。典型代表是 InstructGPT 的第一步、Alpaca 等;
- 第二代:强化学习微调(RLHF 及其变体)。SFT 只能让模型"模仿"示例,无法表达"哪个回答更好"。于是研究者引入奖励信号,用强化学习直接优化模型的输出分布,让模型学会权衡、拒绝与对齐。典型代表是 ChatGPT 背后的 RLHF,以及 DeepSeek-R1 采用的 GRPO。
无论 SFT 还是 RL,一个现实问题始终摆在面前:7B 甚至更大规模的模型,全参数微调的成本高得吓人。参数高效微调(PEFT)因此成为刚需,其中最负盛名的就是 LoRA。
这篇文章分两大主题展开:先讲 LoRA 微调的原理、使用与部署;再讲 RL 强化学习微调的原理、使用与部署;最后给出对比选型与工程建议。
微调前先问三个问题:一是任务是否属于"换个说法就能解决"——如果是,提示词工程或 RAG 更划算,零训练成本;二是数据量是否足够——低于几千条高质量样本时,微调容易过拟合,不如少样本提示;三是是否追求"稳定复现的领域能力"——只有需要把知识、风格、规则固化进权重(如客服话术、行业术语、代码规范)时,微调才不可替代。带着这三个判断,再进入下面的技术细节。
另一个常被低估的维度是成本与收益的权衡。微调的直接成本是算力与人力,间接成本是"维护一个模型版本"的长期负担:评估、回滚、再训练都要随之跟上。好在 LoRA 让试错成本低到可以忽略——失败一次只损失几小时算力。也正因如此,行业里逐渐形成共识:先小成本验证数据质量,再决定要不要上更重的 RL 管线。
一、LoRA 微调:用 0.1% 的参数撬动整个模型
1.1 原理:低秩分解的魔法
为什么全参微调那么贵?
一个 7B 参数的模型,用 Adam 优化器做全参微调,训练时需要保存的副本包括:模型权重(FP16,约 14GB)、梯度(FP16,约 14GB)、一阶动量 m(FP32,约 28GB)、二阶动量 v(FP32,约 28GB),合计 84GB 以上,这还没算中间激活值。普通单卡(A100-80G / RTX 4090-24G)根本放不下;即使凑齐多卡,通信与内存带宽也会拖慢迭代。
核心洞察:微调产生的"更新量"是低秩的
2021 年微软论文《LoRA: Low-Rank Adaptation of Large Language Models》给出关键观察:预训练权重本身是高秩的,但微调过程中的权重更新量 ΔW 具有很低的"内在秩"(intrinsic rank)。也就是说,不必直接更新整个 W,可以用一对小矩阵近似 ΔW。
为什么更新量天然是低秩的? 背后的直觉是:预训练已经在海量语料中把大部分"知识结构"固化进了权重的高秩空间,微调只是在一个受限的方向子空间里做小幅调整——比如调整语气、格式、领域措辞。这种调整通常只涉及少数"协同变化"的方向(对应权重协方差矩阵的少数大特征值),而绝大多数方向上的扰动要么无益、要么有害。实验也证实:把 ΔW 投影到 r 维子空间后,去掉其余分量对微调效果的影响几乎可以忽略(Aghajanyan et al., 2021)。这也解释了为什么"秩"不是越大越好——超出数据实际需要的秩,只是给过拟合留下空间。
对任意一个线性层,设预训练权重为 W₀ ∈ R^{d×k}。LoRA 冻结 W₀,引入两个可训练的小矩阵 A、B:
其中 A ∈ R^{r×k},B ∈ R^{d×r},秩 r 远小于 min(d, k)。前向计算变为:
初始化时 A 用高斯分布随机采样,B 置零,于是训练一开始 ΔW = 0,模型行为与底座完全一致,训练过程稳定。由于 W₀ 被冻结,作用于 W₀ 的梯度与优化器状态一律不需要计算,这正是省钱的关键。
r 与 α:LoRA 最重要的两个超参
秩 r:控制低秩子空间的表达能力,相当于 LoRA 的"宽度"。r 越大,可训练参数越多、拟合能力越强,但过大会增加过拟合风险。7B 模型上 r=8/16 是常用起点,领域数据充足时可到 32/64;
α(lora_alpha):缩放因子。标准实现里实际作用于 ΔW 的缩放是 α/r。固定 r 时,α 越大相当于 LoRA 更新的"学习率"越大。经验法则:r 与 α 同倍率调整(r=8、α=16 与 r=16、α=32 效果近似),因此很多团队直接设 α=2r;
lora_dropout:对 LoRA 分支的随机失活,通常取 0.0~0.1,数据量少时建议开大一点防过拟合;
targetmodules*:施加 LoRA 的目标层。Llama 系通常选 q/k/v/o*proj,甚至全部投影层(gate/up/down)获得更强拟合。
可训练参数量:真的只有百分之零点几
以 Llama-3.1-8B 为例,若对全部注意力投影与 MLP 投影加 LoRA,r=8 时估算:
单个 q_proj 权重形状 4096×4096,LoRA 参数量 = r×(d+k) = 8×8192 ≈ 6.5 万;
全模型数百个投影层合计,LoRA 参数约 0.33 亿;
与 80 亿总参数相比,可训练占比约 0.4%;若只微调注意力层,可低至 0.1% 左右。
用代码验证最直观:
from peft import LoraConfig, get_peft_model, TaskType from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained( "meta-llama/Llama-3.1-8B-Instruct", device_map="auto" ) lora_config = LoraConfig( task_type=TaskType.CAUSAL_LM, r=8, lora_alpha=16, target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], ) model = get_peft_model(model, lora_config) model.print_trainable_parameters() # trainable params: 33,554,432 || all params: 8,060,010,496 || trainable%: 0.4163为什么 LoRA 推理零开销? 训练时 ΔW 作为独立 adapter 保存(通常只有几十 MB);推理前用merge_and_unload()把 W’ = W₀ + (α/r)BA 合并回主权重,得到结构与底座完全一致的权重文件。合并后推理延迟与未微调模型完全一致——这比 Adapter 类方法(在主干中插入额外层,逐层串行计算)有本质优势,也是 LoRA 在工业界胜出的重要原因。
与全参微调及同类 PEFT 方法对比
| 方法 | 可训练参数 | 显存需求 | 训练速度 | 效果 | 推理开销 |
|---|---|---|---|---|---|
| Full Fine-tuning | 100% | 极高(7B 约 84GB+) | 慢 | 最强(数据充足时) | 无 |
| Adapter (Houlsby) | 1%~3% | 低 | 快 | 接近全参 | 有额外层计算 |
| Prefix Tuning | <0.1% | 低 | 快 | 较弱 | 需拼接前缀 |
| LoRA | 0.1%~1% | 低(约为全参 1/10~1/20) | 快 | 逼近全参 | 无(合并后) |
| QLoRA | 0.1%~1% | 最低(4bit 底座) | 快 | 接近 LoRA | 无(合并后) |
简单总结:LoRA 用极小的参数与显存代价,换取了与全参微调接近的效果,是目前工业界 SFT 的主流选择。
1.2 使用:用 PEFT 跑通一次 LoRA 微调
环境准备
pip installtorchtransformersdatasetspeftacceleratetrlvllmbitsandbytes数据准备:指令微调数据通常是 instruction / input / output 结构,先把样本拼成完整文本:
from datasets import load_dataset ds = load_dataset("yahma/alpaca-cleaned", split="train") def format_example(ex): instruction = ex["instruction"] output = ex["output"] text = ( "Below is an instruction that describes a task. " "Write a response that appropriately completes the request.\n\n" f"### Instruction:\n{instruction}\n\n### Response:\n{output}" ) return {"text": text} ds = ds.map(format_example)数据质量 > 数据数量。微调不是堆量游戏:一万条干净、多样、覆盖边界的样本,效果往往好于十万条重复的"水货"。落地建议:先做模糊去重,再做指令多样性聚类(按指令语义聚类后分层抽样),最后人工抽检 5% 样本检查格式与答案质量。数据中的"噪声指令"(没有明确任务意图的样本)会直接教坏模型,务必剔除。
加载模型并挂上 LoRA
import torch from transformers import AutoModelForCausalLM, AutoTokenizer from peft import LoraConfig, get_peft_model, TaskType model_id = "meta-llama/Llama-3.1-8B-Instruct" model = AutoModelForCausalLM.from_pretrained( model_id, torch_dtype=torch.bfloat16, device_map="auto" ) tokenizer = AutoTokenizer.from_pretrained(model_id) tokenizer.pad_token = tokenizer.eos_token lora_config = LoraConfig( task_type=TaskType.CAUSAL_LM, r=8, lora_alpha=16, lora_dropout=0.05, target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], ) model = get_peft_model(model, lora_config) model.print_trainable_parameters()训练:直接用 transformers 的 Trainer
from transformers import Trainer, TrainingArguments training_args = TrainingArguments( output_dir="./lora-sft", per_device_train_batch_size=4, gradient_accumulation_steps=8, # 等效 batch size = 32 learning_rate=2e-4, lr_scheduler_type="cosine", warmup_ratio=0.03, num_train_epochs=3, bf16=True, logging_steps=10, save_strategy="steps", save_steps=500, ) def tokenize_fn(ex): return tokenizer(ex["text"], truncation=True, max_length=2048) train_dataset = ds.map(tokenize_fn, remove_columns=ds.column_names) trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, tokenizer=tokenizer, ) trainer.train()经验:LoRA 微调学习率通常比全参微调高一个量级(1e-4 ~ 3e-4),因为可训练参数极少、优化空间小,需要更激进的步长。
保存与加载 adapter
# 保存:仅几十 MB 的增量权重 model.save_pretrained("./lora-adapter") tokenizer.save_pretrained("./lora-adapter") # 加载:随时叠回底座 from peft import PeftModel base = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.bfloat16) model = PeftModel.from_pretrained(base, "./lora-adapter")合并权重(merge)
merged = model.merge_and_unload() merged.save_pretrained("./merged-model", safe_serialization=True) tokenizer.save_pretrained("./merged-model")QLoRA 一句话介绍:用 bitsandbytes 把底座量化到 4bit(NF4),LoRA 分支保持 BF16。只需在加载底座时加quantization_config,一张 24G 的 4090 就能微调 7B 模型,显存比 LoRA 再降一个量级。
多卡训练与显存优化。LoRA 冻结了底座,梯度只对极少数参数计算,因此可以放心开启gradient_checkpointing=True与torch.compile进一步压低激活显存。DeepSpeed ZeRO-3 场景下,建议把底座模块加入冻结白名单而非全量切分——底座不保存优化器状态,只有 LoRA 增量参与切分,通信开销能省一大截。更省事的做法是直接用 TRL 的SFTTrainer(packing=True会按max_seq_length自动拼接样本),并在TrainingArguments里开启report_to="wandb"实时盯训练曲线。
1.3 部署:合并导出 + vLLM 服务化
为什么先合并再部署? 虽然 vLLM 原生支持动态加载 adapter,但生产环境为了极致的吞吐与零切换开销,更推荐先把 adapter 合并成独立权重文件,再走标准部署链路。
合并导出(BF16)
import torch from transformers import AutoModelForCausalLM, AutoTokenizer from peft import PeftModel base = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.bfloat16) model = PeftModel.from_pretrained(base, "./lora-adapter") model = model.merge_and_unload() model.save_pretrained("./deploy/merged-model", safe_serialization=True) tokenizer.save_pretrained("./deploy/merged-model")vLLM 部署(Python API)
from vllm import LLM, SamplingParams llm = LLM( model="./deploy/merged-model", dtype="bfloat16", gpu_memory_utilization=0.85, # 预留 KV Cache 空间 max_model_len=8192, trust_remote_code=True, ) params = SamplingParams(temperature=0.7, top_p=0.9, max_tokens=1024) output = llm.chat([{"role": "user", "content": "用三句话解释什么是梯度下降"}], params) print(output[0].outputs[0].text)或者启动 OpenAI 兼容服务
vllm serve./deploy/merged-model--dtypebfloat16--max-model-len8192--served-model-namemy-lora-model--port8000显存与性能对比(经验区间)
| 场景 | 显存占用 | 说明 |
|---|---|---|
| 全参微调 7B(Adam,FP16/FP32) | ≥84GB | 权重+梯度+动量 |
| LoRA 微调 7B(FP16) | 约 16~24GB | 底座冻结,仅增量梯度 |
| QLoRA 微调 7B(4bit) | 约 8~12GB | 底座 4bit,LoRA BF16 |
| LoRA 合并后推理 | 与底座相同 | 无任何额外开销 |
部署要点
合并后做一次 smoke test:加载前后对同一 prompt 生成,确认语义一致(合并只引入数值级误差);
用
lm-evaluation-harness或业务指标集做回归评测,防止合并/量化引入退化;多租户场景可考虑 vLLM 的 LoRA adapter 动态加载,省去每租户一份权重的磁盘成本。
多 Adapter 的工程实践。一个底座 + 多个 LoRA adapter 是"一鱼多吃"的主流玩法:为客服、法律、代码三个场景分别训练 adapter,共用同一份底座权重,部署时按请求路由动态挂载。vLLM 支持--enable-lora与--max-lora-rank参数,服务启动后通过/v1/models罗列 adapter,在请求体里指定对应名称即可切换。由于单个 adapter 只有几十 MB,维护上千个 adapter 也只需一份底座,磁盘与显存开销远低于各自独立部署。
二、RL 强化学习微调:让模型学会"权衡"
2.1 原理:为什么 SFT 之后还需要 RL
SFT 的本质是最大似然:让模型输出分布去逼近示例数据的分布。它有两个先天缺陷:
- 只会模仿,不会优化。SFT 把每条样本当作"标准答案"同等对待,无法表达"这个回答 9 分、那个 2 分"的连续偏好;
- 平庸化与暴露偏差。示例通常是"被选中的好答案",模型学会的是"平均"而非"最优";且自回归生成中,错误会随长度逐步放大。
RL 微调则把问题从"模仿"升级为"最大化期望回报":给定 prompt,模型生成回答,用一个奖励函数打分,直接优化策略,让模型在"有用 vs 无害""详细 vs 简洁"之间学会取舍。这就是 OpenAI 提出的 RLHF(Reinforcement Learning from Human Feedback,基于人类反馈的强化学习)。
一条时间线看懂 RL 微调的进化:2022 年,OpenAI 用"指令微调 + 人工反馈 RLHF"训练出 ChatGPT,第一次让大众直观感受到"对齐"的价值;2023 年,斯坦福发布 DPO,把 RL 的训练成本拉到与 SFT 同级,开源社区开始大规模跟进;2024 年,DeepSeek 用 GRPO + 规则奖励训练出 R1 系列,把"强化推理"推向新高度,并展示出不需要人类标注、仅靠正确性信号就能涌现长思维链的能力。可以看到,RL 微调的演进主线是:更少的标注、更简单的目标、更强的推理。
RLHF 三阶段
Stage 1:SFT。先用高质量指令数据监督微调,得到"会说话的底座" π_SFT;
Stage 2:训练奖励模型(Reward Model,RM)。收集人类对同一 prompt 下多个回答的两两比较(“A 比 B 好”),训练打分模型 r_φ(x, y)。偏好概率用 Bradley-Terry 模型建模:
其中 y_w 是被偏好的回答。RM 的损失是最大化正确比较的对数似然:
RM 的数据来自人工(或 AI 裁判)的比较标注:同一个 prompt 生成 2~9 个回答,标注员两两对比给出偏好,一条样本就展开成多对 (chosen, rejected)。实践中 RM 一般从 SFT 模型初始化,去掉语言建模头、换一个打分头,输出标量;训练时把分数 normalize 到均值 0 方差 1,并在 loss 里加正则防止分数无界膨胀。RM 是 RLHF 里最容易被忽视的"效果天花板":RM 打分与真实人类偏好的相关性,直接决定 RL 阶段的收益上限。
- Stage 3:用 RL 优化策略。冻结 RM,把 π_θ 当作策略网络,用 PPO 最大化带 KL 惩罚的回报:
π_ref 通常是 SFT 阶段的模型。KL 惩罚把新策略约束在参考策略附近,防止奖励黑客(reward hacking)——即模型钻奖励函数的空子,输出高分但无意义的文本。
PPO 的核心机制。PPO(Proximal Policy Optimization)用一个裁剪的代理目标(clipped surrogate objective)保证训练稳定:
直观理解:ρ 是新旧策略的概率比,衡量"这次更新步子迈多大"。当 ρ 越界(大于 1+ε 或小于 1-ε)时,clip 截断梯度,避免一步更新过大导致策略崩坏;Â 是优势函数,表示"该动作比平均水平好多少"。
一次 PPO 迭代的完整数据流(理解这一步,RLHF 的工程难点就通了大半):
- 从 prompt 库采样一批 x,让当前策略 π_θ 逐个生成回答 y,得到轨迹;
- 用 RM 对每条回答打分 r(x, y),同时用参考模型 π_ref 计算 KL 惩罚项;
- 用价值网络 V_φ 估计每个 token 的回报,并经 GAE 计算优势 Â;
- 构造 PPO 的 clip 目标,对策略参数做若干轮小步更新;
- 每隔若干轮,用新采样数据重新训练价值网络 V_φ(actor 与 critic 交替更新);
- 周期性用人工评估或自动指标检查是否发生 reward hacking。
每一步都需要独立的模型或数据管线,这也是为什么社区更偏爱实现更轻的 DPO / GRPO。
PPO 的工程成本很高:需要策略、参考、奖励、价值 critic 四份模型,还要为每个 token 计算 KL 与广义优势估计(GAE)。于是出现了两个重要的简化方向。
GRPO:去掉 critic,用组内相对优势。DeepSeek-R1 采用的 GRPO(Group Relative Policy Optimization)不再训练价值网络,而是对同一个 prompt 采样 G 个回答,用组内相对排名估计优势:
奖励高于组内平均就是"正样本",反之是"负样本",天然实现"比较"语义,顺带省下一个 critic 的显存与训练成本。R1 正是用这种"规则奖励 + 组内竞争"的模式,让模型在数学推理上自发长出长思维链。
DPO:把 RL 问题改写成分类问题。DPO(Direct Preference Optimization)证明了:在 Bradley-Terry 偏好模型下,最优 RL 策略存在闭式解,因此不需要显式的奖励模型,也不需要 RL 采样循环,直接在偏好对上做对比学习即可:
β 是温度系数,控制对偏好差异的敏感度:β 越小模型越激进地拉开优劣差距。DPO 的实现成本与 SFT 相当,却在多数任务上逼近 PPO,是目前开源社区最流行的对齐方法。
为什么 DPO 能省掉奖励模型? 关键洞察在于:Bradley-Terry 偏好模型下,最优策略有闭式解——其隐式奖励可以写成策略与参考策略的"对数概率比"形式(log-ratio)。DPO 把这个闭式解代回偏好概率公式,奖励函数就消失了,剩下的只是"让 chosen 的对数概率比高于 rejected"这样一个对比学习目标。换句话说:DPO 不是在训练一个模型去拟合奖励,而是在训练策略去直接满足偏好约束。
三种 RL 方法对比
| 方法 | 奖励模型 | Critic | 采样循环 | 训练稳定性 | 成本 | 典型场景 |
|---|---|---|---|---|---|---|
| PPO | 需要 | 需要 | 需要 | 需调参(KL/clip/GAE) | 高 | 追求对齐上限(GPT 系列) |
| GRPO | 可选(常用规则奖励) | 不需要 | 需要 | 较稳 | 中 | 数学/代码/推理(DeepSeek-R1) |
| DPO | 不需要 | 不需要 | 不需要 | 稳定 | 低 | 一般偏好对齐(社区主流) |
一句话总结:PPO 是"用强化学习硬优化",GRPO 是"组内竞争替代值函数",DPO 是"把偏好直接变成损失函数"。
2.2 使用:用 TRL 跑通一次 RL 微调
HuggingFace 的 TRL(Transformer Reinforcement Learning)库统一了 SFT / PPO / DPO / GRPO 的训练入口。以下代码基于 TRL ≥ 0.14。
RL 数据从哪里来? 无论哪种方法,RL 微调的数据管线都遵循"采样 → 标注 → 清洗"三步:先用当前(或更早的)模型对一批 prompt 采样多个候选回答;再由人工、AI 裁判或规则给候选打分/排序;最后清洗出 chosen / rejected 对或可判分的样本。常见开源数据集包括 Anthropic HH、UltraFeedback、OpenAI PRM800K 等;自建数据时,注意 prompt 要覆盖模型"最容易被挑战"的边界场景(敏感问题、模糊指令、多轮对话),这些样本对对齐效果贡献最大。
数据格式。DPO 需要偏好对(chosen / rejected):
{ "prompt": "请用一句话解释什么是过拟合", "chosen": "过拟合指模型在训练数据上表现极好、在未见数据上表现差的现象,常因模型复杂度过高或数据不足导致。", "rejected": "过拟合就是模型记性太好。" }用 SFTTrainer 打底(RL 之前的第一跳)
from trl import SFTTrainer, SFTConfig sft_config = SFTConfig( output_dir="./sft-base", max_seq_length=2048, packing=True, # 自动拼接样本,充分利用算力 learning_rate=2e-4, per_device_train_batch_size=4, gradient_accumulation_steps=8, num_train_epochs=3, bf16=True, ) trainer = SFTTrainer( model=model_id, train_dataset=ds, # 传入含 text 列的 dataset 即可 args=sft_config, ) trainer.train()DPO 实战
from datasets import load_dataset from transformers import AutoModelForCausalLM, AutoTokenizer from trl import DPOTrainer, DPOConfig model_id = "meta-llama/Llama-3.1-8B-Instruct" dataset = load_dataset("trl-lib/ultrafeedback_binarized", split="train") tokenizer = AutoTokenizer.from_pretrained(model_id) tokenizer.pad_token = tokenizer.eos_token training_args = DPOConfig( output_dir="./dpo-model", per_device_train_batch_size=4, gradient_accumulation_steps=4, learning_rate=5e-6, beta=0.1, # 偏好温度 max_length=1024, max_prompt_length=512, num_train_epochs=1, bf16=True, logging_steps=10, ) trainer = DPOTrainer( model=model_id, # 直接传模型名,内部自动加载 ref_model=None, # None = 复制一份模型当参考模型 args=training_args, train_dataset=dataset, tokenizer=tokenizer, ) trainer.train() trainer.save_model("./dpo-model")要点:
- β 的选择:β 过大模型几乎不变,过小容易崩坏。从 0.1 起步,训练中观察 chosen / rejected 的对数概率差;
- 显存:DPO 同时持有策略与参考两份模型,可用
peft_config配 LoRA 大幅降低显存; - 起点:DPO 通常从 SFT 或 Instruct 模型出发,直接在基座上跑效果不佳。
奖励信号设计原则(决定 RL 上限的第二块拼图):
- 可判分才用规则奖励:答案唯一、可自动校验的任务(数学、代码单测、SQL 结果比对)直接用规则,透明且难以被 hack;
- 不可判分用偏好对:写作、摘要、对话风格等主观任务,用 DPO 的偏好数据而非硬规则打分;
- 多目标拆解:正确率、格式、长度、违规词四个维度分开打分再加权,比单一总分更鲁棒;
- 做对抗性 review:训练中定期"挑刺"——故意构造奖励函数可能误判的输入,验证模型是否钻了空子。
GRPO 实战(以 GSM8K 数学推理为例)
import re from datasets import load_dataset from transformers import AutoTokenizer from trl import GRPOTrainer, GRPOConfig model_id = "Qwen/Qwen2.5-7B-Instruct" dataset = load_dataset("trl-lib/gsm8k", split="train") def reward_func(prompts, completions, **kwargs): answers = kwargs["answer"] # 数据集中的标准答案 rewards = [] for completion, answer in zip(completions, answers): content = completion[0]["content"] # 模型生成文本 match = re.search(r"答案是\s*[::]?\s*([\d.,]+)", content) rewards.append(1.0if match and match.group(1) == str(answer) else0.0) return rewards training_args = GRPOConfig( output_dir="./grpo-model", learning_rate=1e-6, per_device_train_batch_size=8, num_generations=8, # 每组采样 G=8 个回答 max_prompt_length=512, max_completion_length=1024, beta=0.04, # KL 惩罚系数 bf16=True, logging_steps=1, ) trainer = GRPOTrainer( model=model_id, reward_funcs=[reward_func], # 支持多个奖励函数加权 args=training_args, train_dataset=dataset, ) trainer.train()要点:
num_generations对应公式中的 G:越大优势估计越稳,但采样开销线性增长;- 奖励函数返回与 batch 等长的 list,可组合"格式分 + 答案分 + 长度惩罚"等多目标奖励;
- 建议先用 SFT 让模型在任务上"及格",再用 GRPO 冲高分,否则纯靠奖励信号收敛很慢。
同样是偏好,为什么推理任务推荐 GRPO 而非 DPO? 关键在"奖励的来源":推理题的正确性可以自动判分(比对答案即可),这是 GRPO 最爱的"强奖励";而 DPO 需要先人工构造大量成对的优劣样本,成本高且噪声大。反过来,写作、风格等主观任务没有天然判分器,用 DPO 处理人类偏好数据更合适。判断标准很简单:有没有廉价的自动判分器?有则 GRPO,无则 DPO。
2.3 部署:RL 微调后的服务化与评测
部署与普通模型无异。DPO / GRPO 产出的仍是标准 HuggingFace 权重,直接走 vLLM:
vllm serve./dpo-model--dtypebfloat16--max-model-len8192--served-model-namedpo-8b--port8000from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY") resp = client.chat.completions.create( model="dpo-8b", messages=[{"role": "user", "content": "写一段拒绝加班但语气友好的回复"}], temperature=0.7, ) print(resp.choices[0].message.content)评测要点。RL 微调的效果评估比 SFT 复杂,建议分层:
- 离线奖励评测:在 held-out 偏好集上对比 RL 前后模型的 reward margin(chosen 得分减 rejected 得分),观察是否拉开差距;
- 规则/自动指标:数学/代码用准确率、pass@k;安全任务用攻击性测试集测拒答率;
- 人工评测(黄金标准):双盲 A/B,标注员从"有用性 / 无害性 / 风格"三个维度对比;
- 防退化监控:RL 后跑一遍 MMLU / GSM8K 等通用基准,防止对齐税(alignment tax)——为对齐目标牺牲通用能力。
部署避坑
- 显存差异:DPO 训练态是双模型(策略+参考),部署态只有单模型,务必按部署态规划 KV Cache;
- 温度敏感:RL 模型通常对 temperature 更敏感,上线前做温度扫描;
- 版本回滚:RL 可能引入偶发"过度拒答",保留 SFT 版本做 AB 切换。
规模化部署:多副本与滚动更新。RL 模型上线后建议保留三个版本:SFT 基线、RL 稳定版、RL 实验版,通过网关按流量比例灰度。vLLM 天然支持多副本横向扩展,配合负载均衡即可扛住业务峰值;升级时先在小流量观察 reward margin 与线上满意度,确认无"过度拒答"或"话痨化"回归后再全量切换。注意 RL 模型的温度与 top_p 往往需要重新标定——对齐后的模型分布更"尖",同样温度下输出确定性更强,建议上线前做一轮 0.1~1.0 的温度扫描。
三、对比与选型:我该用 LoRA 还是 RL 微调?
先澄清一个常见误区:LoRA 与 RL 微调不在同一个维度上。
- LoRA 是"怎么更新参数":一种参数高效的训练方法,既可用于 SFT,也可用于 RL(比如 RLHF 中 actor 用 LoRA 加速);
- RL 是"优化什么信号":一种训练目标,可以用全参,也可以用 LoRA。
因此正确的选型问题是两个:该不该用 LoRA?该不该上 RL?
3.1 LoRA 还是全参?
| 维度 | 选 LoRA | 选全参 |
|---|---|---|
| 显存/卡数 | 单卡 24G 即可跑 7B(QLoRA 更低) | 多卡集群(7B 至少 4×80G) |
| 数据量 | 数千~十万条 | 十万级以上 |
| 典型场景 | 风格/格式/垂直领域适配 | 全新知识注入、语言迁移 |
| 迭代速度 | 小时级 | 天级 |
| 效果上限 | 逼近全参(多数任务) | 更高(数据充足时) |
结论:默认选 LoRA;当任务与预训练分布差异极大、数据量足够大、且预算允许时,才考虑全参。
成本估算示例(以 7B 模型、10 万条数据为例)
| 方案 | 所需显存 | 训练时间(单机 A100-80G) | 大致成本量级 |
|---|---|---|---|
| 全参 SFT | ≥84GB(需 2~4 卡) | 数天 | 数万元级 |
| LoRA-SFT | 24GB 单卡 | 3~6 小时 | 百元级(云 GPU 按时计) |
| QLoRA-SFT | 12GB 单卡 | 4~8 小时 | 百元级 |
| LoRA-DPO | 24GB 单卡(LoRA 化后) | 数小时 | 百元级 |
| PPO(完整 RLHF 流程) | 需 4 份模型 | 周级 | 万元级 |
注意:以上为经验量级,实际会随数据长度、上下文长度与梯度累积策略浮动。核心结论是——LoRA 让"微调自由"的门槛降了两个数量级,而 RL 的额外成本主要来自"数据标注"与"多路采样"。
3.2 LoRA-SFT 还是 RL 微调?
| 维度 | LoRA-SFT | RL 微调(DPO/GRPO/PPO) |
|---|---|---|
| 优化目标 | 模仿示例分布 | 最大化(偏好/规则)奖励 |
| 数据要求 | 指令-回答 | 偏好对,或可自动打分的任务 |
| 表达能力 | 学会"怎么答" | 学会"答得更好"(权衡、拒答) |
| 训练成本 | 低 | 中~高(PPO 三阶段 / GRPO 多路采样) |
| 效果倾向 | 稳定、可控 | 上限高,但可能引入不稳定 |
| 失败模式 | 过拟合、学偏风格 | reward hacking、对齐税、过度拒答 |
结论:
- 数据干净、任务具体(客服、代码生成、风格化写作)→ LoRA-SFT 就够;
- 想要"回答更讨人喜欢"、安全对齐、或任务有自动判分规则(数学/代码)→ 先 SFT 打底,再 DPO 或 GRPO 提上限;
- 追求极致对齐上限、预算充足 → 走完整 RLHF(PPO)。
3.3 黄金组合:RLHF 中用 LoRA 加速
实践中性价比最高的路线是用 LoRA 做 RL:PPO/DPO/GRPO 的策略模型(actor)用 LoRA 冻结底座、只更新增量,参考模型与奖励模型共享同一份底座,三份模型的内存开销接近一份。社区大量"对齐版"开源模型都走这条路。TRL 只需一行peft_config:
from peft import LoraConfig training_args = DPOConfig( ..., peft_config=LoraConfig( r=16, lora_alpha=32, target_modules=["q_proj", "v_proj"], ), )LoRA × RL 的三个注意点:一是 LoRA 的目标模块要覆盖 reward 影响最敏感的位置,通常注意力 q/v 就够,数据量大时可加 gate/up;二是 RL 阶段的 LoRA 学习率要更小(1e-6 ~ 5e-6),因为奖励信号噪声大,步长太大容易震荡;三是 KL 惩罚 β 要与 LoRA 的低秩容量匹配——秩越低,模型越容易"记住"高分行为而牺牲泛化,β 要适当调高。
四、总结与延伸
4.1 常见坑清单
- LoRA 过拟合:r 过大 + 数据少 → 用 r=8/16、加 dropout、监控验证集 loss;
- α 与 r 不匹配:实际缩放是 α/r,改 r 时必须同步调 α;
- reward hacking:KL 系数 β 太小、奖励函数可被钻空子 → 提高 β,奖励函数做对抗式 review;
- DPO 的 β:β 过大模型不变、过小崩坏,从 0.1 起步观察对数概率差;
- 对齐税:RL 后通用能力下降 → 评测中加入 MMLU / GSM8K 基准;
- 数据污染:chosen / rejected 差距太小的样本训练无效,先清洗再训练;
- 采样多样性不足:GRPO/PPO 中若模型输出千篇一律,组内优势趋近于零 → 提高 temperature、增大
num_generations; - 长度失控:RL 模型可能学会"越长越容易得分" → 奖励里加长度惩罚,或对
max_completion_length做硬约束。
4.2 前沿方向
- KTO(Kahneman-Tversky Optimization):把行为经济学的前景理论搬进对齐,不需要偏好对,只用"好/坏"二分类信号,数据获取成本大幅降低,适合标注预算有限的团队;
- SimPO(Simple Preference Optimization):去掉参考模型,用长度归一化的隐式奖励直接优化,省显存、少一步前向,是 DPO 家族里"最省"的变体;
- ORPO(Odds-Ratio Preference Optimization):把 SFT 与偏好对齐合并为一步,训练管线从"两段式"压缩成"一段式",小团队友好;
- RLAIF / 自奖励:用 AI 模型代替人工标注偏好,配合规则奖励与模型裁判,正在大幅压低对齐数据的成本;
- 推理时扩展(Test-Time Scaling):以 GRPO 为代表的强化推理路线,让模型在推理时主动"多想几步",正成为代码/数学领域的标配。
4.3 一套最小可用的微调工作流
从数据到上线,按顺序走这八步:
- 任务拆解与基线:先用提示词工程验证任务可达性,跑 20~50 条样例建立人工基线;
- 数据构建:按"采样→标注→清洗"产出 5k~50k 条指令数据(或 10k+ 偏好对),去重并抽检;
- LoRA-SFT 打底:r=8/16,lr=1e-4~3e-4,训 2~3 个 epoch,观察验证集 loss 与人工样例质量;
- 离线评测:在 held-out 集上对比 SFT 前后输出,确认任务指标(准确率、格式合规率等)达标;
- 偏好优化:有偏好数据则 DPO(β=0.1 起步),有规则判分则 GRPO(G=8~16),训练中盯 reward margin;
- 回归与防退化:跑通用基准 + 业务指标 + 红队用例,确认无对齐税、无过拒答;
- 合并导出与灰度:merge 后导出 BF16,vLLM 起服务,小流量灰度一周再全量;
- 监控与迭代:线上日志回流作为下一轮数据来源,形成数据飞轮。
这套流程在 7B 级别模型上,单人单卡即可在两三天内走完一轮,是目前性价比最高的落地范式。
4.4 结语
预训练决定模型的"天花板",微调决定模型的"使用体验"。LoRA 让我们以极低的成本把通用模型变成领域专家;RL 让我们把"正确答案"变成"最优答案"。两者的组合——LoRA-SFT 打底,DPO/GRPO 提优——是个人开发者与中小团队在有限算力下逼近效果上限的最优路径。
送大家一句话:先学会模仿(LoRA-SFT),再学会取舍(RL)。2026 年做 LLM 应用,这条路最务实。
五、FAQ:高频疑问速答
Q1:LoRA 和微调(Fine-tuning)是什么关系?
LoRA 是微调的一种实现方式,属于参数高效微调(PEFT);"微调"泛指用下游数据更新模型权重的过程,可以全参,也可以 LoRA。
Q2:DPO 算是强化学习吗?
严格说 DPO 不运行 RL 采样循环,但它从"最优 RL 策略的闭式解"出发推导损失函数,目标等价于带 KL 约束的奖励最大化,因此学术界通常把它归入"离线偏好优化 / 离线 RL"的范畴。
Q3:先 SFT 再 DPO,还是直接 DPO?
强烈建议先 SFT。DPO 是相对优化(比较 chosen 与 rejected),底座太弱时两者都差;SFT 先把任务能力托底,DPO 再按偏好拉开差距,收敛速度和最终效果都更好。
Q4:GRPO 的奖励函数必须用规则吗?
不一定,也可以用训练好的 RM 打分。但规则奖励的优势是零成本、可解释、难以被 hack。DeepSeek-R1 的成功,很大程度归功于"答案可自动校验"这个性质。
Q5:LoRA 训练出的模型,推理时为什么和底座一样快?
因为merge_and_unload()把增量矩阵合回了原始权重,网络结构与计算图完全不变,唯一区别只是权重数值不同。
Q6:微调后模型"忘了"原来会的东西,怎么办?
这是灾难性遗忘(catastrophic forgetting)。对策:训练数据里掺入 5%~10% 的通用语料作为回放数据,或训练后与底座做权重插值(LoRA 可做 adapter 加权融合),都能显著缓解。
Q7:为什么数学/推理任务更适合 GRPO 而不是 DPO?
因为推理题的正确性可以廉价、无歧义地自动判分(比对答案即可),天然满足 GRPO"组内相对奖励"的前提;而 DPO 需要先构造大量成对的 chosen / rejected 样本,标注成本高。此外 GRPO 的组内采样鼓励模型探索不同解题路径,对长思维链的涌现更有帮助。
Q8:LoRA 权重能和别人的 adapter 混着用吗?
原则上可以把多个 adapter 的权重相加或插值得到"混合能力",前提是它们的底座一致、target_modules一致。社区已有 adapter 算术组合(merge 后相加/相减)的成功实践,但混合后务必重新评测,避免能力互相干扰。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~