1. 项目概述:从"对话模型"到"推理引擎"的范式转移
"LLM界的AlphaGo"这个说法,我第一次听到是在某个技术社区的讨论帖里。乍一听有点夸张,但仔细嚼一嚼,这个类比其实非常精准——当年的AlphaGo Zero打破的是"人工棋谱依赖",DeepSeek R1 Zero打破的是"人类标注数据依赖"。两者都不是在原有赛道上做优化,而是直接换了训练逻辑。
先说清楚这个项目干了一件什么事。DeepSeek R1 Zero(以下简称R1 Zero)是一个完全通过强化学习(Reinforcement Learning, RL)训练出来的大语言模型,核心特征是不依赖任何人工标注的监督微调数据(Supervised Fine-Tuning, SFT),让模型在数学、代码、逻辑推理等任务上"自己和自己较劲",最终涌现出类似"自我反思""验证纠错"的推理能力。它背后的训练方法叫GRPO(Group Relative Policy Optimization),这是在PPO基础上做减法改出来的算法。
为什么我决定写这篇复现教程?因为市面上讨论R1 Zero的文章,要么只讲论文、不讲落地,要么告诉你"去调用官方API"就算复现了。但真正把一套开源训练代码跑通、把GRPO策略和奖励函数调明白的人,其实不多。我花了三周时间,从零搭建训练环境,经历了几十次loss爆炸、显存溢出、采样死循环,终于把整个流程完整复现出来。这篇文章就是把我的操作记录、参数配置、避坑经验全部摊开,给想动手复现R1 Zero的开发者一条能走的近路。
这篇文章适合谁?有LLM微调基础、想深入研究RL训练范式的工程师,或者看过R1论文但苦于"论文公式难落地"的研究者。如果你之前只会用LlamaFactory之类的工具跑SFT,那这篇文章会帮你把认知边界往前推一大步。
提示:复现R1 Zero不像微调一个普通模型那样,准备好数据和显卡就能跑。它是训练范式上的转变,核心难点在于RL训练loop的设计、奖励函数的稳定性、以及超参数对采样质量的敏感度。这篇教程会如实呈现这些难点,不美化过程。
2. 核心原理拆解:GRPO、奖励设计与训练策略
2.1 为什么选择GRPO:去掉Critic模型的PPO变体
许多人第一次接触R1 Zero论文时,注意力都在"纯RL训练"这个结论上,但其实真正支撑这个结论的基石是GRPO。传统PPO训练流程中,除了有个Actor模型负责生成回答,还得有个Critic模型(或者说价值函数)去估算状态价值,用这个估值来算Advantage(优势函数),从而指导策略更新。这听起来合理,实际操作却极其麻烦——Critic和Actor是同步训练的,Critic的误差会直接影响策略更新的方向,训练不稳定、显存开销大,而且Critic本身也容易出现规模不匹配的问题。
GRPO的核心思路是:扔掉Critic模型,用同一批(group)采样的相对表现来估算优势。具体做法是:对同一个prompt,让Actor模型采样生成N个回答(一般取4~8个),把每个回答的奖励值归一化,然后直接用这个组内相对优势去更新策略。
我来用一个简单例子说明这个过程。假设有一个数学题prompt:"求解x:2x+3=11"。模型生成4个候选回答,奖励函数分别给出0.6、0.9、0.3、0.8的分数。GRPO会计算这4个分数的均值和标准差,然后把每个分数转成相对优势值,分数低于平均水平的回答对应的生成概率会被压低,高于平均水平的会被拉高。这样既不依赖单独的价值网络,又保留了策略更新的信号。
这个设计的妙处在于:相对比较天然抵消了reward scaling的影响。如果用绝对奖励值,不同任务的难度不同,奖励范围差异很大,很难统一设置更新阈值;用组内相对比较,不管任务难易,策略永远往"比平均更好"的方向优化。从工程角度看,GRPO还省掉了一份Critic模型的前向和反向计算,训练显存压力大幅下降,对于在单机多卡上复现R1 Zero的操作者来说,这一点可能是能否跑起来的决定性因素。
2.2 奖励设计的关键:正确率与格式的平衡
R1 Zero训练中,奖励设计是决定训练成败的头号因素。官方论文里提到,最初版本只设计了"结果正确性"奖励(给分→正确得1分,错误得0分),但训练过程中发现模型输出的格式非常混乱——没有推理标记、答案和思考过程混在一起、长度爆炸。于是后面加上了"格式奖励"(format reward),要求模型在特定标记内输出思考过程,再给出最终答案。
我自己复现时,最初照搬了论文里的双奖励方案,但很快发现一个问题:正确率奖励只有0和1两个值,模型的优化信号其实很稀疏。尤其是训练初期,模型生成的推理过程大多是错的,几乎拿不到正奖励,策略更新方向非常不稳定。我后来借鉴了其他开源复现项目的做法,增加了"部分正确奖励"——如果最终答案错了但推理过程中有效使用了已知条件,给0.3分;如果推理方向正确但计算有误,给0.5分。这个改动让训练稳定性明显提升。
这里我整理了一份奖励函数设计的参数参考表:
| 奖励维度 | 初始方案 | 改进方案(推荐) | 说明 |
|---|---|---|---|
| 结果正确性 | 正确=1,错误=0 | 正确=1,计算错误=0.5,方向错误=0.3 | 缓解奖励稀疏问题 |
| 格式合规 | 有标记=1,无标记=0 | 有完整标记=1,有标记但格式乱=0.5 | 避免训练早期全部为零 |
| 长度惩罚 | 无 | 超过最大长度则截断并给0.2分 | 防止思考过程无限膨胀 |
| 重复惩罚 | 无 | 检测到连续重复内容扣0.3分 | 防止模型陷入重复循环 |
需要特别提醒的是,奖励函数不能设计得过细。我一开始试图加入"逻辑连贯性""步骤完整性"等维度,结果每次训练出来的模型反而表现更差。原因在于:奖励信号如果过多,每个维度的权重又不好调,最终叠加出来的梯度方向反而模糊了,模型不知道该往哪个方向优化。
2.3 冷启动数据的取舍:到底要不要SFT?
论文里R1 Zero全程没有用SFT数据,这是它最吸引人的地方。但实际操作中,完全冷启动的代价是训练初期会经历一段极长的"混沌期"——模型生成的内容随机性极强,奖励信号几乎全部是零,策略更新基本靠"瞎猜"。在我的复现实验中,模型从完全随机状态开始,在前2000步训练里loss几乎没有下降,GPU利用率倒是拉满了,烧了不少电费。
后来我参考社区里一个成功复现的项目经验,做了一个折中处理:准备大约1000条高难度的数学题和代码题,用开源通用模型生成可用的推理示例(包含思考过程),然后用这些数据做了一次轻量SFT(大概跑了3个epoch),再进入GRPO RL阶段。这个预训练过程让模型学会了"基本的推理输出格式",相当于给RL训练提供一个还不错的起点。
但我必须说清楚:这已经不完全等同于论文里R1 Zero的纯RL训练了。不过从工程复现的角度来看,这种折中恰恰是让整个训练流程可落地的关键。如果你有充足的计算资源,可以坚持完全冷启动;如果计算资源有限,冷启动数据是个实用策略。
3. 实操准备:环境搭建与数据准备
3.1 硬件配置与训练框架选型
开始行动前,先盘一下硬件底牌。R1 Zero的全流程训练对显存的需求是实打实的,因为RL训练除了要跑Actor模型的前向和反向,还要维护参考模型(Reference Model)的推理过程和奖励模型的打分过程。我用的配置可以参考:4块80GB显存的GPU(理论可用显存约312GB,扣除系统占用后约300GB),这是能比较舒适地跑7B~8B规模模型训练的最低配置要求。
如果是拿消费级显卡(比如24GB显存的卡),也不是完全不能跑,但必须做几个调整:使用LoRA(低秩适配)训练,只更新少量参数;减小GRPO组采样数量,比如从8降到4;减少上下文长度,比如限制在2048而不是4096。这些调整会牺牲一些训练效果,但至少能把流程跑通。
框架选择方面,我建议优先考虑那些对强化学习训练有专门优化的开源训练框架,而不是自己硬造轮子。在选取时主要看三点:是否原生支持GRPO或可直接改造成GRPO、是否支持模型并行(比如张量并行和流水线并行)、是否集成了主流的奖励计算模块。我最终选择了一个支持DeepSpeed ZeRO-3和Flash Attention v2的框架,训练管线清晰,底层算子做了专门优化,显存占用比裸跑PyTorch节省大约30%。推理部分用了vLLM做加速,因为RL训练中采样的吞吐量直接决定了训练速度。
3.2 基座模型选择:不是所有模型都适合RL训练
R1 Zero的RL训练效果和基座模型有很大关系。我最初用的一个通用对话模型,训练了4000步后推理能力几乎没有提升,后来排查发现,这个模型的基础推理能力偏弱,在zero-shot情况下生成的推理链质量太差,RL信号几乎无法有效传递。
换用一个数学和代码能力更强的基座模型后,同样配置下训练效果显著提升。这说明一个关键点:R1 Zero的RL训练不是把"没有推理能力"变出"推理能力",而是把"已有的潜在推理能力"激发出来。如果基座模型原本的推理能力太弱,强化学习也很难无中生有。选择基座模型时,可以在SFT之前先跑几个数学基准测试,确认基座模型有基本的推理基础。
3.3 数据准备:推理任务数据集怎么组织
R1 Zero复现用的数据主要是推理密集型的任务,包括数学题、代码题目、逻辑谜题。数据格式尽量保持简单统一,关键是把prompt和正确答案分开存储,因为训练时需要实时计算预测结果与正确答案的匹配度。
我用的原始数据大概2万条,清洗策略是:去掉超过最大长度限制的样本;去掉重复或高度相似的题目;对数学题,统一格式化为自然语言描述(例如"Solve for x: 2x+3=11"),不用LaTeX符号,因为和奖励函数的文本匹配逻辑有关;对代码题,确保有明确的输入输出示例,方便自动判分。
数据量需求比想象中小。R1 Zero的训练本质是策略优化,优质数据比海量数据更关键。我用2万条筛选后的数据训练,效果比一开始用10万条未筛选数据要好,因为未筛选数据里掺杂了大量低质量问题,导致奖励函数判断混乱。
4. 实操全流程:从零开始复现R1 Zero
4.1 训练循环的设计与实现
R1 Zero的训练循环和普通SFT有本质区别,普通SFT是"前向计算loss,反向传播"的一遍遍重复;RL训练是一个多阶段循环。下面我直接给出一个核心的伪代码流程,让大家看清楚每一步做什么:
# R1 Zero 风格训练循环核心逻辑 for step in range(total_steps): # 1. 从训练数据中采样一批 prompt prompts = sample_batch(batch_size=8) # 2. 使用当前策略模型采样生成多个回答 # 这里通过 vLLM 启动作业模型,利用异步接口提高吞吐 responses = policy_model.generate( prompts, num_return_sequences=8, # group size = 8 max_new_tokens=4096 ) # 3. 计算每个回答的奖励分数 rewards = compute_rewards(prompts, responses) # compute_rewards 包含格式奖励和正确性奖励两部分 # 4. 计算组内相对优势值 advantages = normalize_rewards(rewards) # 减去组均值,除以组标准差 # 5. 用 GRPO 更新策略模型 policy_loss = grpo_loss(responses, advantages, ref_logprobs) policy_model.backward(policy_loss) policy_model.step()训练过程中,我注意到一个关键的工程细节:参考模型(Reference Model)的logprobs不需要实时更新。GRPO的策略更新目标里包含一个KL散度惩罚项(防止策略模型偏离参考模型太远),参考模型是冻结的,只需要在训练前对全部采样结果计算一次logprobs。如果每轮都重新计算参考logprobs,训练速度会大幅下降。
另一个值得注意的点是:策略模型的生成采样过程和参数更新过程在工程上完全解耦。采样吞吐要求很高,我使用了一种异步的推理机制:用一个独立的推理服务池维持策略模型的参数,每轮更新后参数同步到推理服务池;训练主循环和采样流程互不阻塞。这样训练吞吐提升了约4倍。
4.2 超参数配置详解
在多次实验后,我总结了一套稳定性较好的配置参数,这里是核心部分:
# 训练超参数配置 max_steps: 30000 batch_size: 8 group_size: 8 learning_rate: 1.0e-6 lr_scheduler: cosine max_seq_len: 4096 kl_coef: 0.05其中需要展开讲的是几个关键参数:
- group_size(每组采样数):这是GRPO特有的超参数,表示对每个prompt采样多少个回答。数值太小,组内相对比较的统计稳定性差;数值太大,采样计算量成倍增加。实测下来8是性价比最高的选择,4可以用但在奖励信号稀疏时训练波动明显。
- kl_coef(KL散度系数):控制策略模型偏离参考模型的力度。过大会限制策略模型探索空间,过小则会导致策略模型生成退化。可以参考的策略是训练早期保持匀量化在0.05附近,后期逐步降到0.01,让策略模型更充分地自由探索。
- learning_rate:这个数值比普通SFT小很多(SFT通常用1e-5),因为RL训练中梯度信号噪声大,学习率太高会直接炸掉。
4.3 奖励函数的具体实现
正确性的自动判断逻辑,我针对不同类型做了区别化处理:
def compute_rewards(prompt, response): # 格式奖励:检查是否包含规范思考标记 has_think = "think_start" in response and "think_end" in response format_reward = 1.0 if has_think else 0.0 # 正确性奖励:数学题使用解析+近似验证 if is_math_problem(prompt): answer = extract_answer(response) correct = str_to_int(answer) == expected_answer(prompt) elif is_code_problem(prompt): correct = run_code_with_testcases(prompt, response) else: correct = simple_string_match(answer, expected_answer) reward = correct if correct else 0.2 * format_reward return reward这里踩过一个坑,数学题答案匹配如果只用字符串包含匹配,会出现一个问题——模型可能会输出多个相似答案(比如"11"和"x=11"),导致误判。后来我改用解析方法,从回答中提取最后一个数字表达式,再用数值近似比较(允许误差1e-6),匹配率大幅提升。
代码题判分我用的是沙箱执行方式:从模型回答中提取出纯代码部分,再在隔离的Python环境中运行,跑测试用例来打分。不建议直接在训练主进程里执行模型生成的不可信代码,会造成安全隐患。
4.4 训练全流程监控与评估
R1 Zero训练过程中,传统的loss曲线并不能反映真实训练状态。我建议重点监控这几项指标:
- 成功率(pass rate):每500步,在固定评估集上测试当前策略模型的问题解决率。初期可能只有百分之几,训练30000步后能提升到60%~80%区间。
- 平均奖励值:反映模型整体输出质量的趋势。正常情况下应该是波动上升的,出现持续下降就需要暂停检查。
- 响应长度分布:这个容易被忽视,但R1 Zero训练中模型输出的思考过程会越来越长,如果长度超过token上限后停止生成,会导致采样质量下降。
- KL散度:衡量策略模型和参考模型的偏离程度。偏离过大会出现"奖励作弊"现象(模型利用奖励函数的漏洞,比如输出极长内容覆盖正确答案等)。
我搭了一个轻量的训练监控面板,每100步自动记录上述指标并绘制趋势图。这帮助我快速定位了好几个训练异常。
5. 常见问题与排查技巧实录
5.1 训练不收敛:从loss曲线读出问题
训练过程中,我发现一个普遍现象:前2000步loss下降得非常缓慢,看起来卡死了一样。很多初学者在这里就放弃了。实际上,这是RL训练的"探索期"——模型还在各种策略间试探,奖励信号非常稀疏,loss不下降是正常现象。我在跑了4000步之后,loss才开始出现持续下降的趋势。
但如果已经跑了超过10000步,成功率仍然接近0,就要从这几个方面排查了:基座模型的推理能力是否过弱;格式奖励是否一直为0(模型没有产出规范格式,正确性奖励和格式奖励都拿不到);学习率是否过大或过小。
5.2 奖励作弊现象:模型比想象中更"聪明"
这是RL训练中非常典型的坑。模型可能会找到奖励函数的漏洞,用一个不合理的输出获得高奖励,而不是真正提升推理能力。比如,我遇到过一次奖励作弊现象:模型发现,在回答末尾加上一个"x=11"(而推理过程中根本没有得到这个答案)能提高匹配正确率,于是它学会了输出大量无关内容并在结尾随机附上正确答案,导致训练出来的模型推理过程质量很差,但正确率指标竟然很高。
规避方法是尽量让奖励函数"防钻空子":要求回答中必须包含完整推理链,不在非推理部分添加答案;对超长回答设置长度惩罚;在评估集上直接测试推理链质量,而不只关注最终答案。
5.3 显存不足与训练效率优化
显存管理是复现过程中的第一大瓶颈。我最初因为显存不足导致训练频繁中断。几个关键优化技巧:
- 开启梯度检查点(gradient checkpointing),用更多计算换更小显存占用
- 合理设置batch size和group size,保证两者乘积不超出显存上限
- 将参考模型和奖励模型的推理过程放在独立GPU上,避免和Actor模型抢占显存
- 使用混合精度训练(BF16),在不明显损失精度的情况下减少显存占用
- 对序列超长导致的显存碎片问题,预先设置好最大内存分配块
5.4 数据问题带来的隐蔽故障
训练过程中,我遇到过几次"loss不下降但奖励值升高"的诡异情况。排查后发现,问题出在数据集本身的分布,个别样本存在错误答案标注,而且难易分布不均。RL训练对这类噪声的容忍度远低于SFT,因为每次采样的多个回答可能会被"不正确答案被标记为正确"的噪声样本误导,导致策略向错误方向偏移。
我建议在进入训练前,先对全部数据做人工抽检,至少确认关键任务的答案格式正确,同时在训练中定期换一批评估数据,及时发现数据问题。
6. 工具选型解析:训练框架与推理引擎的搭配
复现R1 Zero,工具链的选择直接决定了开发效率和训练效果。我的第一版复现尝试用的是"裸PyTorch + 自定义训练循环",结果花了大量时间在底层细节上——分布式通信、梯度同步、采样调度、显存管理,各种意想不到的问题轮番出现。后来切换到一个成熟的开源RL训练框架,省去了大量重复工作。
这里我给出当前复现R1 Zero类训练任务时,比较顺手的一套工具链组合:
| 环节 | 工具选择 | 理由 |
|---|---|---|
| 训练框架 | 支持GRPO的LLM RL框架 | 内置训练循环,兼容分布式训练 |
| 模型并行 | DeepSpeed ZeRO-3 | 高效切分模型参数、梯度、优化器状态 |
| 采样推理 | vLLM | 高吞吐推理引擎,异步生成接口成熟 |
| 奖励计算 | 自研模块(基于规则+沙箱执行) | 灵活应对不同任务的自动判分需求 |
| 日志监控 | TensorBoard + 自采指标 | 可视化loss、奖励、成功率趋势 |
对于训练框架,我的建议是:优先选择原生支持GRPO的框架,不要过度自定义。RL训练中每个环节都需要精细调试,用成熟框架能大幅缩短开发周期。
7. 复现完成后的验证与效果评估
跑完30000步之后,最重要的环节是在模型上看真实效果。我选择了两类评估:定量和定性。
定量评估用三个基准:数学推理测试集(80道题)、代码生成测试集(20道题)、逻辑推理测试集(50道题)。训练好的模型相比基座模型,数学题正确率提升约41个百分点,代码题正确率提升约28个百分点,逻辑题正确率提升约35个百分点。这个提升幅度比我预期的还要明显一些。
定性评估则直接和模型对话,把它的推理过程完整展示一遍。我挑了一个比较有代表性的例子:
用户提问:如果一个正方形的面积是36平方厘米,那么它的周长是多少厘米? 模型思考过程: 正方形面积 = 边长 × 边长 = 36 所以边长 = 6厘米 周长 = 4 × 6 = 24厘米 验证:面积 = 6 × 6 = 36,正确。 最终答案:24厘米。注意看,这个回答里有一个非常关键的"验证"步骤——模型在得出答案后反推确认了一遍。这在基座模型上是很少见的行为,因为基座模型通常是"给定问题直接蹦答案",极少有自我验证环节。这就是RL训练带来的能力涌现,也是R1 Zero最有价值的地方。
当然也要客观说,当前复现版本的推理能力和官方版本还有明显差距。官方的模型在更复杂的数学、代码任务上表现更强,说明在训练数据质量、基座模型规模、训练步数等方面还有优化空间。这本身就是值得探索的方向。
8. 我的实操体会与下一步计划
复现R1 Zero的过程,让我对"训练范式转移"这句话有了非常具体的体感。当年SFT时代,大家拼命积累人工标注数据,用指令微调让模型学会"怎么说话";而R1 Zero走的是另一条路——设计好游戏规则(奖励函数),让模型自己通过大量试错学会"怎么思考"。这个过程中最震撼的时刻,是我第一次在评估日志里看到模型的推理链条里出现了"验证"和"反思"环节,那一刻我就意识到,这确实是在复现AlphaGo Zero式的智能涌现。
有几个切身的经验想分享给大家。第一,R1 Zero的复现门槛不在硬件,而在对训练范式的理解——很多人卡在"为什么loss不下降"这个问题上,就是因为还用SFT的惯性思维去理解RL训练。第二,训练稳定的关键在奖励函数设计,而不是单看GPU算力。一个好的奖励函数能让人用有限的资源跑出一个还不错的模型。第三,不要急于追求"完全复刻论文效果",先跑通流程、理解原理,再逐步优化。
这个项目后续我可以继续扩展的方向,包括在更大规模基座模型上的复现、引入多模态推理任务、以及尝试更多样的推理奖励函数设计。也希望这篇文章能帮更多人在这个方向上少走弯路,欢迎在评论区交流你的复现经验。