← 返回资讯
赵一鸣
产品评测编辑
已审核

如何正确复现 Instruct GPT / RLHF?

你知道吗?我第一次跑PPO的时候,整个人差点把电脑砸了。

如何正确复现 Instruct GPT / RLHF?

如何正确复现 Instruct GPT / RLHF?


我从去年开始狂踩RLHF的坑,今天全扒给你看

你知道吗?我第一次跑PPO的时候,整个人差点把电脑砸了。

那是去年下半年,GPT刚火遍全网那阵。我兴冲冲地打开了GitHub上最火的“一键复现InstructGPT”项目,直接跑官方示例脚本。三个小时后,loss飞了,reward直接归零,GPU风扇还在那里呼呼地转。

你猜我当时什么心情?

想哭。

后来呢?后来我一个框架换一个框架,PaLM-rlhf-pytorch、ColossalAI-Chat、DeepSpeed-Chat、TRLX、Huggingface TRL,全试了一遍。它们有个共同特点——要么训到一半莫名其妙崩了,要么reward曲线像吃了跳跳糖一样上下乱跳。

网上那一堆“一键复现”的repo,十有八九,复现不出论文里的效果。

说到这儿,今天我不跟你扯漂亮话,只把我试过的、踩过的、最后验证过的东西,全撂出来。


你复现的是哪个InstructGPT?

GitHub上这些项目,分两拨人。

第一波,叫“蒸馏派”。

这群人很聪明——直接拿ChatGPT的API去抓数据,然后微调开源基座。Alpaca之于LLaMA,BELLE之于BLOOMZ,就是这么来的。速度快,效果也不差。为什么?因为“标准答案”本身就是从ChatGPT那抄来的。

但你要搞清楚,这是蒸馏,不是真正的RLHF。

第二波,叫“原教旨派”。

这帮人偏要完整走一遍SFT → Reward Model → PPO三阶段。这类框架毛病最多,因为每一步都有让你崩溃的工程陷阱。我要说的,就是这一拨。

你看,InstructGPT论文里那三个阶段,写得清清楚楚,明明白白:

第一个阶段,SFT:人工标注指令数据,做监督微调。

第二个阶段,Reward Model:训练一个打分模型,用pair-wise排序loss。

第三个阶段,PPO:用奖励信号加KL惩罚,做近端策略优化。

每个阶段,都能让你翻车翻得亲妈都不认识。

我一个一个说。


SFT阶段:你以为是开胃菜,其实是第一个坑

SFT是RLHF的起点。很多人觉得——不就是找个对话数据集,跑几轮LoRA嘛,有什么难的?

不。

坑大了。

我踩过这个坑:用Alpaca那个52k的数据微调LLaMA-7B,跑三个epoch,loss降到0.3以下。当时我还挺高兴,想着这不是稳了吗?结果一生成,出来的话像复读机。

为什么?两个原因:一是过拟合了,二是数据质量不行。

你看,这里有几个关键点,千万注意。

第一,learning rate不能太大。

我用LLaMA-3-8B的时候,全量微调设1e-5,LoRA设2e-4左右。那个网上常见的LLaMA3配置,什么learning_rate: 0.0001lora_target: q_proj,v_proj,训3个epoch后loss确实能降到0.26。但你用同样配置训自己的数据——先检查一下验证集loss有没有突然回升。如果回升了,那就是在过拟合了。

第二,cutoff_len要合理。

我一般用1024。但多轮对话场景下,有些回答很长,你切得太短,模型根本学不到完整的结构。我就干过一件蠢事——把一段2000 token的回复硬截到512,结果模型学会了烂尾。生成的回复说到一半,突然就没了。

第三,template必须和基座一致。

这个听着简单,但坑了多少人你知道吗?LLaMA的tokenizer有special token,格式错了,就等于往模型里喂乱码。比如llamafactory-cli配置里写的template: llama3,错一个字母,模型就开始胡扯。

我现在SFT的习惯是这样:先用10%的验证集盯着loss,验证loss连续2个epoch不降就停。绝不硬跑完3个epoch。


Reward Model训练:最甜蜜的陷阱

Reward Model的核心,是给一个pair——好回答vs坏回答,让模型学会给好的高分,给坏的打低分。

论文里用的loss是这个:

CODE
loss = -log(sigmoid(reward_chosen - reward_rejected))

损失函数本身没问题,但关键是——你拿什么作为reward的输出?

我见过的做法有这三种:

第一种,在语言模型最后加一个线性层,输出一个标量。

第二种,取最后一个token,也就是的hidden state,过一个投影层。

第三种,对所有token的hidden state做pooling——平均或者求和。

你猜InstructGPT用的是哪个?

第二种。取token的嵌入。

我一开始没注意,选了第一种。结果呢?reward死活不收敛。排查了两天才发现——不同位置的token含义完全不一样。这个位置,集中了整段话的全局信息。你用[CLS],根本没那个效果。

还有一个更隐蔽的坑:数据里两个回答的顺序必须是(好的,差的),不能反!

要是搞反了,reward模型会反过来给差回答高分。后面PPO一跑,直接教坏模型。你想想,这得多坑。

我当时复现用的BLOOMZ做基座,训了2万对数据,reward accuracy干到78%。结果PPO一跑,loss直接上天。查来查去,发现数据标注员把好坏标签写反了——整个pair的顺序错了。

你想想,这得多坑。


PPO阶段:地狱之门

PPO,最容易崩。

我一开始跑ColossalAI-Chat的示例脚本,每次都在PPO第三步,advantage计算那里报NaN。后来发现,是reward没做normalization。

PPO的正确套路,我现在固定用OpenRLHF,它把那些trick都封装好了。但为了让你真正理解,我拆开说清楚。

多step vs 单step

这个争论特别多。

多step:每一步生成一个token,当作一个action。每个step给一个KL散度作为reward——实时惩罚偏离原模型的——最后一步加上reward model的打分。

单step:整句话生成完,算一笔总账——reward model输出加上KL散度,一次性算完。

两种我都用过。说说感受。

多step理论上更接近经典RL——一个episode由多个timestep组成。但实践中,KL散度作为每一步的reward,会严重抑制探索。模型为了贴近原模型,token生成变得特别保守,最后效果像温度0.1的贪心解码。而且频繁计算KL,开销大,不稳定。

我最后选了单step。OpenRLHF默认实现的也是单step:输入prompt,生成完整response,然后一次性计算reward和KL。简单,稳定。

KL散度方向——又一个让你崩溃的细节

PPO的KL惩罚,应该是KL(pi_old || pi_new),还是KL(pi_new || pi_old)

方向错了,训练会炸。

论文里写的是KL(pi || pi_old),其中pi是当前策略。但代码里实际用的是D_KL(action_logits, old_action_logits)——当前模型输出相对于冻结旧模型的相对熵。

我一开使用了反的,模型跑了几步就collapse了。换回来,立刻稳定。

说到这儿,有一篇文章也提到:“使用KL(action_logits, old_action_logits)是合理的”。这个我举双手赞成。

entropy loss到底加不加?

OpenAI自己的文章说,InstructGPT没有用entropy bonus。我当时不信,觉得entropy loss能促进探索,加了之后,reward震荡得特别厉害。

去掉之后,收敛速度反而快了。

所以我现在也不加。

reward normalization和advantage计算

reward model输出的分数范围不稳定——有时候-5,有时候+8。不归一化,advantage会大起大落,policy update一步就崩。

正确的做法是这样:

第一步,先对reward做z-score normalization——对一个batch,减均值,除标准差。

第二步,advantage计算用GAE——广义优势估计。lambda设0.95,gamma设1——因为每个episode长度短,不需要折扣。

我踩过的坑:忘记在reward上加KL惩罚的加权,导致reward一味追求高分,模型忘了原本的风格。KL惩罚系数一般在0.01到0.1左右,具体看base model大小。我7B模型用0.02。


大模型训练系统:70B以上怎么办?

小模型PPO还好说,单卡8张80G能跑13B。但一上34B、70B,连模型都放不下。

前面提到的Ray + vLLM方案,才是正解。

做法是这样的:Actor模块的推理——inference——用vLLM,支持TP——tensor parallel——和动态batching。训练部分——forward加backward——用DeepSpeed ZeRO-3。

我不玩虚的,直接贴一个实际配置:

8卡A100 80G,跑70B模型。

Actor和Critic训练用ZeRO-3——stage 3参数全部切分。

Actor推理用vLLM,TP size等于8。所有PPO rollout都走vLLM的高效推理。

Reward模型和Ref-Actor也用ZeRO-3,但只负责推理——不需要更新参数。

权重同步通过NCCL完成。每次PPO训练完一版Actor的权重,更新到vLLM的推理引擎。因为训练和推理是交替的,你可以把节点复用来节省显存——这两个模块不会同时跑。

有一篇文章说“无缝兼容HuggingFace Transformers”,确实如此。不用改模型结构,直接load就行。这点比Megatron-LM舒服多了。


DPO和Rejection Sampling值得提吗?

说到这儿,我还想提一嘴DPO、Rejection Sampling、Conditional SFT这些对齐算法。

如果你不追求论文复现,只想快速上线——DPO是个省心的选择

它不需要reward model,不需要PPO,直接在偏好数据上微调就行。但效果上限不如PPO,而且对数据分布很敏感。有一篇文章叫“大语言模型对齐:直接偏好优化”,讲得很细,我不展开了。

我自己项目里,需要严格对齐人类偏好的场景——比如客服对话——用PPO。实验性场景,用DPO。


最后说几个心态建设的“屁话”

复现InstructGPT,不是跑通一个脚本就完了。

我见过的人里,有的跑了两三星期PPO——loss和reward都在乱跳——最后发现是reward model训错了。有的SFT都训炸了——learning rate用了5e-4。

说真的,这个领域最难的其实不是算法,而是工程上的耐心

每一个hyperparameter,都值得你按论文的精神来设,而不是随便填一个。

如果你不想被这些坑折腾死,直接上OpenRLHF。它把上面说的trick全集成好了——KL惩罚、reward normalization、GAE、vLLM支持,甚至还有DPO。你可以关注它的更新记录,6月28号有个全面的版本整理。不是我打广告,而是我用下来,只有它真的跑了70B还能稳定收敛。

但是。

如果你真想搞清楚每一个细节,还是建议自己手搓一遍。

至少我经历过,从报错到修好的全过程。现在看到loss曲线,我就知道哪一步出了问题——这种能力,跑别人的框架是学不来的。

行,就这些。

记住一句话:真正掌握一件事,不是因为它跑通了,而是因为它崩过之后你知道怎么修。

224
7467 阅读
5 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年06月21日 17:15

赵一鸣

产品评测编辑

前产品经理,现专注 AI 工具评测。实测过 30+ 款 AI 产品,擅长横向对比和用户体验分析。

读者评论 5

前端工程师 2周前
代码示例很清晰,直接用到项目里了。
回复 点赞 (6)
技术小白 3天前
作为非技术人员也看懂了,感谢作者的通俗讲解。
回复 点赞 (3)
Dev小王 6天前
终于有人把这个说清楚了,收藏了。
回复 点赞 (8)
A
AI研究员 1周前
观点有道理,不过我觉得还需要考虑算力成本的问题。
回复 点赞 (11)
M
创业者Mark 1周前
正在做相关方向,这篇文章给了我不少启发。
回复 点赞 (7)