☰
DeepSeek-R1强化学习实战:从奖励函数到GRPO训练推理模型
2026/10/11 2:00:18 网站建设 项目流程

简介:这份资源围绕DeepSeek团队首个基于强化学习的推理语言模型展开,面向机器学习研究人员、开发者以及希望提升大模型推理能力的从业者,尤其适合关注RL训练与编程、数学等密集型推理任务的读者。内容涵盖DeepSeek-R1-Zero纯强化学习训练路径、冷启动数据与迭代式RL微调机制,以及知识蒸馏至1.5B至70B小型稠密模型的完整方案,并附有基准性能评测与失败尝试的讨论。资源包共1个PDF文件,约2.62MB,为完整技术报告,包含引言、方法、实验与讨论等章节,可系统了解模型架构、奖励设计与评估结果。目前已有1359人学习下载,适合需要深入理解强化学习如何激发大模型推理能力、并希望借鉴其训练与蒸馏思路的读者参考。

1. 推理能力不是玄学:从 DeepSeek-R1 的强化学习路线说起

很多团队在做垂直领域模型时都会撞上同一堵墙:模型能背出知识点,却做不对多步推理题。你问它一道需要三步计算的工程问题,它第一步就跳步,第二步把单位搞混,第三步直接编一个看起来合理的答案。这不是模型“笨”,而是监督微调阶段喂进去的数据里,推理链本身就不完整。DeepSeek-R1 这条路线之所以值得一线工程师认真看,是因为它把“推理能力”从提示词工程里拎出来,交给强化学习去磨。它解决的核心问题是:当标注数据贵、推理链难写、答案又必须可验证时,怎么让模型自己学会一步步想。适合谁?适合手里有可验证任务(数学、代码、结构化抽取)、有少量 GPU 资源、愿意折腾奖励函数的人。不适合指望调个 API 就完事的人。

2. DeepSeek-R1 的强化学习到底在训什么:奖励信号与策略更新

2.1 从 SFT 到 RL:为什么推理链不能只靠模仿

监督微调的本质是模仿。你给模型看一万条“问题→推理链→答案”,它学会的是“在这种问题下,人类通常怎么写推理链”。但推理链的写法是发散的:同一道题可以正推、反推、枚举、构造辅助量。SFT 数据只能覆盖其中一小部分,模型遇到没见过的题型就开始胡编。更麻烦的是,SFT 的损失函数只看 token 匹配,不看答案对不对。模型写了一段语法通顺但逻辑断裂的推理,损失照样很低。

强化学习换了一个目标:不看你怎么写,只看你最后答对没有。DeepSeek-R1 路线里,策略模型生成完整回答,奖励模型或规则函数给一个标量分数,策略根据分数调整生成概率。这样模型可以探索出人类没写过的推理路径,只要最终答案可验证正确。常见做法是:先用少量 SFT 数据做冷启动,让模型学会基本输出格式,再切到 RL 阶段用可验证奖励大规模探索。

这里的关键选型理由是:奖励必须可自动计算。数学题对答案、代码跑单元测试、结构化抽取比对字段,都属于可验证任务。如果你做的是开放域对话,奖励只能靠人类偏好模型,成本和噪声都会上一个量级。

2.2 奖励函数怎么写:规则奖励与偏好奖励的混合

我一般会把奖励拆成三块:格式奖励、答案奖励、长度惩罚。格式奖励要求模型把推理过程放在指定标签里,答案放在另一个标签里,没按格式输出直接给负分。答案奖励用规则匹配或执行结果判定。长度惩罚防止模型靠堆废话刷分。

import re def reward_fn(response: str, ground_truth: str) -> float: # 格式奖励:必须包含 thinking 和 <answer> 标签 format_score = 0.0 if " thinking" in response and "<answer>" in response: format_score = 0.2 # 提取答案区域 answer_match = re.search(r"<answer>(.*?)</answer>", response, re.DOTALL) if not answer_match: return format_score - 0.5 # 没答案直接惩罚 pred = answer_match.group(1).strip() # 答案奖励:精确匹配给 1.0,数值近似给 0.5 if pred == ground_truth: answer_score = 1.0 else: try: if abs(float(pred) - float(ground_truth)) < 1e-6: answer_score = 0.5 else: answer_score = -0.3 except ValueError: answer_score = -0.3 # 长度惩罚:超过 800 token 后每 100 token 扣 0.05 length_penalty = max(0, (len(response) - 800) / 100) * 0.05 return format_score + answer_score - length_penalty

这段奖励函数里,format_score是引导模型学会结构化输出,answer_score是核心信号,length_penalty是防止 reward hacking。参数上,格式分不宜给太高,否则模型会只输出标签不认真答题;答案错误惩罚要足够大,让模型不敢乱猜。实际训练时,我会先用小学习率跑几百步,观察奖励曲线是否稳定上升,如果震荡剧烈,先把长度惩罚去掉,确认答案奖励本身没有歧义。

2.3 策略更新:PPO 还是 GRPO,显存和稳定性的取舍

PPO 需要同时加载策略模型、参考模型、奖励模型、价值模型,显存占用大,训练不稳定时调参很痛苦。DeepSeek-R1 路线里更常用的是 GRPO 这类去掉价值模型的变体:对同一个问题采样一组回答,用组内相对分数做优势估计,省掉价值网络。显存省了,但组大小和采样温度变得敏感。

# 常见 GRPO 训练启动参数示例(以 7B 模型为例) python train_grpo.py \ --model_name_or_path ./sft_checkpoint \ --reward_fn reward_fn.py \ --num_generations 8 \ --temperature 0.9 \ --learning_rate 1e-6 \ --per_device_batch_size 2 \ --gradient_accumulation_steps 8 \ --max_new_tokens 1024 \ --kl_coef 0.04 \ --output_dir ./grpo_checkpoint

num_generations是每个问题采样的回答数,太小优势估计噪声大,太大显存吃不消,7B 模型上 8 是常见起点。temperature不能太低,否则组内回答几乎一样,优势全为零;也不能太高,否则全是胡言乱语。kl_coef控制策略偏离参考模型的程度,太小会训崩,太大会学不动。我一般从 0.04 开始,观察 KL 散度曲线,如果持续上涨超过 10,就调大这个系数。

3. 把 DeepSeek-R1 的 RL 路线落到自己的任务:数据、环境与训练循环

3.1 可验证任务怎么选:从数学题到结构化抽取

不是所有任务都适合 RL。选任务时看三条:答案能不能自动判对、推理链有没有必要、错误答案会不会被规则放过。数学题最直接,代码题次之,结构化抽取再次之。我做过一个工单字段抽取的模拟项目,把每条工单的文本输入模型,要求输出 JSON,字段值必须和数据库记录一致。奖励函数就是 JSON 解析后逐字段比对,全对给 1.0,错一个给 0.3,格式错误给负分。

这种任务的好处是数据现成,坏处是推理链短,RL 提升空间有限。如果你要做多步推理,最好选那种“中间步骤错了最终答案一定错”的任务,比如多跳问答、单位换算、依赖计算。这样奖励信号才能有效传导到中间步骤。

3.2 训练环境搭起来:显存、并行与检查点

7B 模型做 GRPO,num_generations=8、max_new_tokens=1024,单卡 80G 显存勉强够,但 batch size 只能开到 1 到 2。常见做法是用 LoRA 冻结大部分参数,只训低秩适配器,显存能降到 40G 左右。如果要做全参数 RL,至少 4 卡起步,用 FSDP 或 DeepSpeed ZeRO-3 切分。

# 用 LoRA 做 GRPO 的配置片段 peft_config: r: 16 lora_alpha: 32 target_modules: ["q_proj", "v_proj", "k_proj", "o_proj"] lora_dropout: 0.05 bias: "none"

r是低秩矩阵的秩,16 是常用值,任务越复杂可以调到 32 或 64。target_modules至少要覆盖注意力层的 Q、V,加上 K、O 更稳。lora_dropout给 0.05 防止过拟合。检查点保存频率别太高,RL 训练动辄几千步,每 200 步存一次,只保留最近三个和最佳奖励的。

3.3 训练循环里的三个观察指标

跑 RL 最怕的是“奖励涨了但模型变傻”。我一般盯三个指标:平均奖励、KL 散度、回答长度。平均奖励要稳步上升,如果突然跳高,大概率是 reward hacking,比如模型发现输出某个固定格式就能骗分。KL 散度反映策略偏离参考模型的程度,超过 15 就要警惕。回答长度如果持续变长但奖励不涨,说明模型在堆废话,需要加大长度惩罚。

# 训练日志里定期打印这三个指标 def log_metrics(rewards, kl_divs, lengths, step): print(f"step={step} " f"reward_mean={sum(rewards)/len(rewards):.3f} " f"kl_mean={sum(kl_divs)/len(kl_divs):.3f} " f"len_mean={sum(lengths)/len(lengths):.0f}")

这段日志代码不复杂,但少了它你就是在盲训。奖励均值看趋势,KL 均值看稳定性,长度均值看废话率。三个一起看,才能判断模型是真的在学推理,还是在钻奖励空子。

4. 避坑与排查:RL 训练里最容易翻车的五个地方

4.1 奖励全为零,模型学不到东西

现象:训练几百步后,平均奖励一直在零附近,KL 散度几乎不动。原因通常是任务太难,模型采样出的回答没有一个是对的,组内优势全为零,梯度消失。解决:先用 SFT 数据把模型冷启动到能答对 30% 左右,再切 RL;或者把num_generations调大,增加采到正确答案的概率;还可以在奖励函数里给部分正确加小分,比如答案格式对但数值错给 0.1。

4.2 奖励突然飙升,但验证集崩了

现象:训练奖励从 0.3 跳到 0.9,但人工看输出发现模型在重复同一句话。原因:reward hacking,模型找到了奖励函数的漏洞,比如长度惩罚没加、答案匹配用了宽松的正则。解决:检查奖励函数里有没有可以被“空输出”或“固定模板”骗过的分支;加一个格式校验,答案区域不能为空;定期用独立验证集跑一遍,别只看训练奖励。

4.3 KL 散度爆炸,输出变成乱码

现象:KL 散度从 2 涨到 30,模型输出开始夹杂无意义符号。原因:学习率太大,或者kl_coef太小,策略跑偏了。解决:把学习率降到 5e-7,kl_coef调到 0.1,重新从上一个检查点加载。如果还不行,检查参考模型是不是和策略模型初始化不一致。

4.4 显存溢出,训练跑不过 100 步

现象:CUDA out of memory,batch size 已经降到 1。原因:max_new_tokens太大,或者num_generations太多,采样阶段显存峰值过高。解决:把max_new_tokens从 1024 降到 512,num_generations从 8 降到 4;开启梯度检查点;用 LoRA 替代全参数训练。如果还不行,换更小的模型先跑通流程。

4.5 训练速度慢到无法迭代

现象:一步要跑十几分钟,一天只能跑几十步。原因:采样阶段没有用 vLLM 或类似推理加速,逐 token 生成太慢。解决:把生成部分换成 vLLM 或 TGI,批量采样;减少num_generations;用更短的max_new_tokens。我一般会在正式训练前先跑 20 步测速,如果一步超过 5 分钟,就先优化生成后端,别硬跑。

5. 进阶技巧:用拒绝采样和课程学习把 RL 效果再推一截

5.1 拒绝采样:把 RL 训过的模型再蒸馏回 SFT

RL 训练结束后,模型会生成大量推理链。这些推理链里有一部分是对的,有一部分是错的。拒绝采样的做法是:用奖励函数筛出高分的回答,和问题一起组成新的 SFT 数据集,再对模型做一轮监督微调。这样做的价值是:RL 探索出的新推理路径被固化下来,模型在推理时的稳定性会更好。我一般会筛奖励大于 0.8 的样本,每个问题保留最多 3 条不同路径,避免过拟合单一解法。

# 拒绝采样筛选高分回答 def filter_samples(samples, threshold=0.8, max_per_question=3): filtered = {} for q, resp, r in samples: if r >= threshold: filtered.setdefault(q, []).append((resp, r)) # 每个问题按奖励排序,取前 max_per_question 条 result = [] for q, resps in filtered.items(): resps.sort(key=lambda x: x[1], reverse=True) for resp, r in resps[:max_per_question]: result.append({"question": q, "response": resp}) return result

threshold设太高会导致数据太少,设太低会引入噪声,0.8 是常用起点。max_per_question控制多样性,太大容易过拟合,太小又浪费探索结果。筛完之后,用这批数据做 1 到 2 个 epoch 的 SFT,学习率比正常 SFT 小一半。

5.2 课程学习:从简单题到难题的调度策略

RL 训练初期,模型能力弱,直接上难题会导致奖励全零。课程学习的思路是:先拿简单题训,等奖励稳定了,再逐步混入难题。实现上可以给每个问题打一个难度分(比如按 SFT 模型答对的概率),训练时按难度分桶,每 N 步提升一次难题比例。

训练阶段简单题比例中等题比例难题比例预期奖励
0-500 步80%20%0%0.5-0.6
500-1500 步50%30%20%0.6-0.7
1500-3000 步30%40%30%0.7-0.8
3000 步以上20%30%50%0.8+

这个比例不是固定的,要根据奖励曲线动态调。如果某个阶段奖励卡住不涨,就把难题比例降回去,等稳定了再加。课程学习的好处是训练更稳,坏处是要多维护一套难度标注,适合有充足数据标注资源的团队。

5.3 验证方法:别只看训练奖励,用这三个测试集交叉验证

RL 训练最容易骗自己的就是训练奖励。我一般会准备三个测试集:同分布测试集、分布外测试集、对抗测试集。同分布测试集看模型有没有过拟合训练题型;分布外测试集看推理能力有没有泛化;对抗测试集放一些容易触发 reward hacking 的样本,比如答案格式正确但数值错误的题。三个测试集上的准确率如果同步上升,才说明 RL 真的在提升推理能力。如果只有训练奖励涨,测试集不动甚至下降,那就是在钻空子,得回头改奖励函数。

我自己的习惯是:每次改奖励函数或课程调度,先跑 200 步,看三个测试集的早期信号,再决定要不要继续。RL 训练没有后悔药,跑歪了只能从头来,所以前期验证比后期调参重要得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询