重现Deepseek R1 「Aha Moment」的完整教程来了!

DeepSeek r1 模型惊艳亮相后,其创新及模型实力获得了众多称赞和好评,但同时也因一些数据问题被质疑其涉嫌抄袭OpenAI 模型

见前文:

OpenAI 称有证据证明DeepSeek违规使用其模型!

Anthropic CEO:DeepSeek 为落后的追随者,需进一步加强中国芯片管制!

微软正调查DeepSeek是否不当获取OpenAI数据!

Stable Diffusion 创始人:DeepSeek 没有抄袭!

甚至,意大利监管机构也以数据保护为由,对DeepSeek进行了封禁下架!

不过吐槽归吐槽,抹黑归抹黑,那只是少数人,且多为利益相关方。在此不作过多讨论,但能看到的是,已经有无数的AI 实验室在复刻r1,同时有众多播客、沙龙、话题都是针对DeepSeek,可谓是热火朝天。

就在刚刚,Hugging Face的技术负责人Philipp Schmid在他最新的博客文章中介绍了Mini R1。这篇文章通过详细讲解,帮助读者了解并能够重现一些小例子。

详情请查看他的博客 Mini Deepseek R1: https://www.philschmid.de/mini-deepseek-r1

下为译文:

Mini-R1: 重现 Deepseek R1 的 “Aha Moment” —— 强化学习教程

作者:Philipp Schmid @HuggingFace

Deepseek R1 的发布震惊了整个行业,为什么呢?

因为 DeepSeek-R1 是一个开源模型,在复杂推理任务上与 OpenAI 的 o1 相抗衡,采用了 Group Relative Policy Optimization(GRPO)和以强化学习为核心的多阶段训练方法。除了发布该模型,他们还发布了一篇研究论文,详细说明了他们是如何实现这一切的。

在这篇论文[1]中,他们描述了在使用纯强化学习训练模型时的一个“aha moment”(顿悟时刻)。在这一阶段,DeepSeek-R1-Zero(DeepSeek-R1 的首次测试版本)学会了通过重新评估最初的方法,来分配更多的思考时间,而无需任何人工反馈或数据描述如何进行。这一现象被称为“aha moment”,正如他们所说:

这一行为不仅证明了模型推理能力的增长,也是强化学习如何引导意外而复杂的结果的一个迷人例子。

在这篇博文中,我们将通过使用 GRPO 和倒计时游戏,重现 DeepSeek-R1 的这个“小 aha moment”。我们将训练一个开源模型,使用强化学习教它如何自我验证和搜索,从而解决倒计时游戏。倒计时游戏是一个数字谜题,玩家需要使用一组随机抽取的数字和基本算术运算(加、减、乘、除),尽可能接近目标数字。

目标数字:952可用数字:25, 50, 75, 100, 3, 6
(100 × (3 × 3)) + (50 + 6 / 3) = 952

本博文包括一个交互式代码,你可以在 Jupyter Notebook 中运行,学习如何使用 GRPO 和 Q-Lora 来训练模型。这是学习如何使用 TRL 和 GRPO 的一个好方法,但它非常慢,需要大量计算资源。此外,我还提供了一个脚本[2]和如何在带有多个 GPU 或 SLURM 集群的节点上运行训练的说明。

  1. 设置开发环境[3]
  2. 从倒计时游戏生成带有推理前缀的训练样本[4]
  3. 使用 GRPO 训练模型(教育部分)[5]
  4. 使用 Deepspeed 和 vLLM 的 GRPO 分布式训练示例[6]
  5. 结果和训练观察[7]

注意:本博文灵感来源于Jiayi Pan[8],他最初探索了这个想法并通过一个小模型验证了这一点。

在开始之前,我们先来了解一下Group Relative Policy Optimization (GRPO)[9]是如何工作的。

Group Relative Policy Optimization(GRPO)

Group Relative Policy Optimization(GRPO)是一种强化学习算法,用于提高大语言模型(LLM)的推理能力。

它在DeepSeekMath[10]论文中首次提出,主要用于数学推理任务。GRPO 修改了传统的 Proximal Policy Optimization(PPO)算法,去除了值函数模型的需求。相反,它通过从组分数估计基线,从而减少了内存使用和计算开销。现在,GRPO 也被 Qwen 团队使用,可以与基于规则或二进制的奖励,以及通用奖励模型一起使用,以提高模型的帮助能力。

GRPO 的工作流程

  1. 采样:使用当前策略生成每个提示的多个输出。
  2. 奖励评分:使用奖励函数对每个生成的输出进行评分,可以是基于规则或基于结果的奖励。
  3. 优势计算:生成的输出的平均奖励作为基线,每个解的优势相对于该基线进行计算。奖励在一个组内进行归一化。
  4. 策略优化:策略会尽力最大化 GRPO 目标,该目标包括计算出的优势和 KL 散度项。这与 PPO 在奖励中实现 KL 项的方式不同。
GRPO 图示

1. 设置开发环境

我们的第一步是安装 Hugging Face 库、PyTorch、vllm、trl、transformers 和 datasets。如果你还没听说过 trl,不用担心。它是一个基于 transformers 和 datasets 的新库,使得微调、强化学习和对齐开源 LLM 更加容易。

# 安装 Pytorch 和其他库,确保与 GPU 驱动版本匹配%pip install "torch==2.5.1" tensorboard "setuptools<71.0.0"  --index-url https://download.pytorch.org/whl/cu121 # 安装 flash-attn%pip install flash-attn  # 安装 Hugging Face 库%pip install  --upgrade \  "transformers==4.48.1" \  "datasets==3.1.0" \  "accelerate==1.3.0" \  "hf-transfer==0.1.9" \  "deepspeed==0.15.4" \  "trl==0.14.0" # 安装 vLLM %pip install "vllm==0.7.0" ## 重要:如果你想运行交互式单元格,还需要安装以下库:# 但在安装之前请阅读博文,因为这些库可能会与分布式训练的库发生冲突。# %pip install "peft==0.14.0" "bitsandbytes==0.45.0"

注意:你可能需要重启内核,以便使用更新后的包。

我们将使用Hugging Face Hub[11]作为远程模型版本控制服务。这意味着我们将在训练期间自动将模型、日志和信息推送到 Hub。你必须在Hugging Face[12]注册账户。注册后,我们将使用 huggingface_hub 包中的 login 工具登录账户并将访问令牌存储在磁盘上。

from huggingface_hub import login login(token="", add_to_git_credential=True)  # 在此处添加你的令牌

2. 从倒计时游戏生成带有推理前缀的训练样本

我们将使用Jiayi-Pan/Countdown-Tasks-3to4[13]数据集,该数据集包含 3 到 4 个数字和对应的解决方案。

作为模型,我们将使用Qwen/Qwen2.5-3B-Instruct[14],这是一个具有 30 亿参数的指令调优模型。由于它已经遵循提示格式,因此展示“aha moment”会更容易。但你也可以使用 Qwen 的基础版本或其他模型。Jiayi-Pan[15] 发现模型需要具备一定的质量才能学习推理过程,起始参数量需大于 1.5B。

from transformers import AutoTokenizerfrom datasets import load_dataset # 从 Hugging Face Hub 加载数据集dataset_id = "Jiayi-Pan/Countdown-Tasks-3to4"dataset = load_dataset(dataset_id, split="train")# 随机选择 50k 样本dataset = dataset.shuffle(seed=42).select(range(50000)) # 从 Hugging Face Hub 加载 tokenizer,用于格式化数据集为 "r1" 提示tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-3B-Instruct") # 生成 r1 提示,带有推理前缀def generate_r1_prompt(numbers, target):    r1_prefix = [{        "role": "system",        "content": "你是一个有帮助的助手。你首先思考推理过程,然后给用户提供答案。"      },      {         "role": "user",        "content"f"使用数字 {numbers} 来接近目标数字 {target}。"      }]    return r1_prefix # 创建训练样本train_sample = []for sample in dataset:    train_sample.append(generate_r1_prompt(sample["numbers"], sample["target"]))

3. 使用GRPO训练模型(教育部分)

注意: 第3部分展示了如何使用TRL和GRPO的基础知识。如果你想运行交互式代码单元,必须安装bitsandbytespeft,因为它们是Trainer类的必备依赖。此部分主要用于教育目的。

TRL通过专门的GRPOTrainer[16]支持基于偏好数据对大语言模型(LLM)进行对齐,相关内容可以参考DeepSeekMath: 推动开放语言模型数学推理极限[17]GRPOTrainertransformers库中Trainer的子类,支持所有相同的功能,包括日志记录、检查点、分布式训练和参数高效微调(PEFT)。

GRPOTrainer支持通用的结果奖励模型(ORM)和自定义奖励函数,可以用来实现基于规则的奖励模型。在DeepSeek R1论文中,他们实现了基于规则的奖励模型,用于验证生成解答的正确性。在我们的示例中,我们将采用类似的方式,创建两个奖励函数:

  1. 格式奖励:检查生成的格式是否正确 <think> [thinking] </think><answer> [answer] </answer>
  2. 准确性奖励:从<answer>标签中提取方程式,并与目标进行对比,确保每个数字都仅使用一次。

注意: 在我们的示例中,正确的<answer>标签应包含方程式,例如:<answer> 55 + 36 - 7 - 19 </answer>

示例代码

import re
def format_reward_func(completions, target, **kwargs): """ 格式检查:<think>...</think><answer>...</answer> 参数: completions (list[str]): 生成的输出 target (list[str]): 期望的答案 返回: list[float]: 奖励分数 """ rewards = []
for completion, gt in zip(completions, target):
try: # 由于<think>已作为提示的一部分并预填充,因此这里人为添加<think> completion = "<think>" + completion # 检查格式是否正确 regex = r"^<think>([^<]*(?:<(?!/?think>)[^<]*)*)<\/think>\n<answer>([\s\S]*?)<\/answer>$"
match = re.search(regex, completion, re.DOTALL) # 如果格式不正确,奖励为0 if match is None or len(match.groups()) != 2: rewards.append(0.0) else: rewards.append(1.0) except Exception: rewards.append(0.0) return rewards
def equation_reward_func(completions, target, nums, **kwargs): """ 基于以下因素评估生成结果: 2. 答案的数学正确性
参数: completions (list[str]): 生成的输出 target (list[str]): 期望的答案 nums (list[str]): 可用的数字 返回: list[float]: 奖励分数 """ rewards = [] for completion, gt, numbers in zip(completions, target, nums): try: # 由于<think>已作为提示的一部分并预填充,因此这里人为添加<think> completion = "<think>" + completion # 检查<answer>标签格式 match = re.search(r"<answer>(.*?)<\/answer>", completion) if match is None: rewards.append(0.0) continue # 提取“答案”部分 equation = match.group(1).strip() # 提取方程式中的所有数字 used_numbers = [int(n) for n in re.findall(r'\d+', equation)] # 检查所有数字是否都使用了一次 if sorted(used_numbers) != sorted(numbers): rewards.append(0.0) continue # 定义一个只允许数字、运算符、括号和空格的正则表达式模式 allowed_pattern = r'^[\d+\-*/().\s]+$' if not re.match(allowed_pattern, equation): rewards.append(0.0) continue # 在受限的全局和局部环境中评估方程式 result = eval(equation, {"__builtins__": None}, {}) # 检查方程式是否正确,并与真实值匹配 if abs(float(result) - float(gt)) < 1e-5: rewards.append(1.0) else: rewards.append(0.0) except Exception: # 如果评估失败,奖励为0 rewards.append(0.0) return rewards

测试我们的奖励函数

注意: 示例中没有任何一个生成的答案以<think>开始,因为我们已经在提示中人为地添加了它。

correct_sample_1 = """我们需要使用19、36、55和7这四个数字, 在不重复的情况下,通过基本的算术运算找到等于65的方程式。一种可能的组合是:55 + 36 - 19 + 7... </think>
<answer> 55 + 36 - 7 - 19 </answer>"""
correct_sample_2 = """ ... </think>
<answer> 55 + 36 - 7 - 19 </answer>"""
wrong_format = """用户:使用数字[19, 36, 55, 7],创建一个等于65的方程式。"""
wrong_format_2 = """为了找到等于79的方程式,我开始将88和95相加:95 + 88 = 183然后,减去104得到79:183 - 104 = 79<think> 183 - 104 = 79 </think><think> 183 - 104 = 79 </think><answer> 183 - 104 = 79 </answer>"""
wrong_result = """ ... </think>
<answer> 55 + 36 - 7 - 18 </answer>"""
test_rewards = format_reward_func(completions=[correct_sample_1, correct_sample_2, wrong_format, wrong_format_2, wrong_result], target=["65", "65", "65", "65", "65"], nums=[[19, 36, 55, 7]] * 5)assert test_rewards == [1.0, 1.0, 0.0, 0.0, 1.0], "奖励函数未正常工作"test_rewards = equation_reward_func(completions=[correct_sample_1, correct_sample_2, wrong_format, wrong_format_2, wrong_result], target=["65", "65", "65", "65", "65"], nums=[[19, 36, 55, 7]] * 5)assert test_rewards == [1.0, 1.0, 0.0, 0.0, 0.0], "奖励函数未正常工作"

定义训练参数并开始训练

from trl import GRPOConfig, GRPOTrainer, get_peft_config, ModelConfig
# 我们将作为策略使用的模型model_config = ModelConfig( model_name_or_path="Qwen/Qwen2.5-3B-Instruct", torch_dtype="bfloat16", attn_implementation="flash_attention_2", use_peft=True, load_in_4bit=True,)
# 超参数配置training_args = GRPOConfig( output_dir="qwen-r1-aha-moment", learning_rate=5e-7, lr_scheduler_type="cosine", logging_steps=10, max_steps=100, per_device_train_batch_size=1, gradient_accumulation_steps=1, gradient_checkpointing=True, gradient_checkpointing_kwargs={"use_reentrant": False}, bf16=True, # GRPO特定参数 max_prompt_length=256, max_completion_length=1024, # 最大生成输出长度 num_generations=2, beta=0.001,)
trainer = GRPOTrainer( model=model_config.model_name_or_path, reward_funcs=[format_reward_func, equation_reward_func], args=training_args, train_dataset=train_dataset, eval_dataset=test_dataset, peft_config=get_peft_config(model_config),)

我们可以通过调用train方法开始训练:

# 开始训练并将模型上传到Hubtrainer.train()# 保存模型trainer.save_model(training_args.output_dir)

注意: 强化训练非常缓慢且计算密集。使用1xL4、Q-LoRA、批量大小为1且每个样本只有2个生成的情况下,每个步骤的运行时间超过20分钟。


4. 使用Deepspeed和vLLM进行GRPO分布式训练示例

单步训练超过20分钟,仅2个生成的样本是不可行的。我们需要扩展训练规模。Hugging Face TRL 已支持通过Deepspeed进行分布式训练,并使用vLLM进行更快的生成。我准备了一个run_r1_grpo.py[18]脚本和一个receipes/grpo-qwen-2.5-3b-deepseek-r1-countdown.yaml[19]配置文件来运行训练。

这个配置已经在4x H100 80GB的节点上经过验证,在这种硬件环境下,单步训练大约需要45-60秒,因为我们可以利用vLLM进行生成,使用DeepSpeed进行分布式训练。因此,我们需要确保正确设置num_processes为GPU的数量减去1,最后一个GPU将用于vLLM进行生成。如果你使用更多GPU,需要在配置文件中将vllm_device设置为最后一个GPU的索引,例如,如果你有8个GPU,应该设置vllm_device=7,并将num_processes设置为7。

执行训练的命令如下:

accelerate launch --num_processes 3 --config_file configs/accelerate_configs/deepspeed_zero3.yaml scripts/run_r1_grpo.py --config receipes/grpo-qwen-2.5-3b-deepseek-r1-countdown.yaml

通过优化的分布式训练,使用4x H100 80GB的硬件,且每个样本有8个生成,单步训练大约需要45-60秒。完整的训练(450步)大约需要6小时。

5. 结果与训练观察


该脚本将随机生成的完成任务保存到 completion_samples 文件夹中,供您查看模型的进展。它包括 completion_samples.txt 和 success_completion_samples.txt。其中,completion_samples.txt 包含所有生成的答案,而 success_completion_samples.txt 仅包括正确解答了方程的样本。下面列出了训练过程中一些有趣的观察,展示了模型性能随时间变化的情况,以及 TensorBoard 日志和成功推理的样本。

带有每25步检查点的模型可以在 philschmid/qwen-2.5-3b-r1-countdown[20] 中找到。

超参数

实验开始时,我使用了来自 DeepSeekMath[21] 论文的超参数,学习率设为 1e-6,beta(KL系数)设为 0.04。这个配置在大约150步后导致了不稳定的训练过程。之后我进行了小规模的调参,将学习率降到 5e-7,将 beta 降到 0.001,这一调整基于 OpenRLHF[22] 的测试结果。我没有测试将 num_generations 从 8 增加到 64 会对训练有何影响。64 是 DeepSeekMath 论文中使用的生成值。所有其他参数可以在 grpo-qwen-2.5-3b-deepseek-r1-countdown.yaml[23] 配置文件中找到。

训练观察:

  • 在约 50 步时,模型已学会了正确的格式 <think>...</think>\n<answer>...</answer>
  • 在 100 步时,解决方程的成功率约为 25%。此时,模型开始用语言进行“推理”,具体见下文的示例。
  • 到了 200 步时,性能似乎趋于收敛,成功率约为 40%。模型开始学习一种新的“格式”,它以类似编程方式的方式解决方程,通过尝试不同的组合并检查结果,详见“200 到 450 步之间的成功推理样本”。
  • 在 450 步时,模型的成功率达到了 50%。尽管性能仍在缓慢提升,但模型保持了从第 200 步开始的新格式。

我有三种可能的假设,解释模型为什么从“语言推理”转变为“程序化执行”:

  1. Qwen 2.5 3B 模型不够强大,或者模型过小,DeepSeek 提到需要一个非常强大的基础模型。
  2. 奖励函数定义不足,可能模型通过奖励机制采用了“作弊”方式来解决方程。我们可以尝试强制其使用文字,例如设置数字与文字频率条件(我们对 DeepSeek 中的奖励函数了解较少)。
  3. 仅在 Countdown 游戏任务上训练 可能迫使模型自然而然地学习最有效的解题方法,因为没有其他格式的需求。
  4. 训练时间不够长,在 R1 论文中,模型在 8000 步之后仍能看到明显的训练效果。

TensorBoard 结果

图 7:Qwen R1 TensorBoard 结果

200 步之前的成功推理样本

<think>首先,我需要思考一下我们手头的数字:14, 70 和 80。为了得到 85,我觉得 80 是一个不错的起点,因为它接近 85。如果我减去或加上 80,似乎很难得到 85。接下来,70 似乎是一个不错的中间值,尤其是与 14 一起使用时。现在,我可以尝试使用加法、减法、乘法和除法的不同组合。如果我计算 80 - 70 = 10,但这不是我们需要的。因此,我需要找到一种方法来使用 14。如果我从 80 中减去 14,得到 66,但仍然不是 85。如果我从 70 中减去 80,结果也是 10,依然无法找到解决办法。乘法可能会生成大于目标 85 的数字。让我们考虑除法:如果我将 70 除以 14,得到 5。然后如果将这个结果加到 80,得到 85。这种组合似乎有希望:80 - (70 / 14) = 80 - 5 = 75,这还不对。我需要调整一下方法:我们可以使用除法,然后加法。如果我计算 80 / (70 / 14) → 80 / 5 = 16,然后 80 - 16 = 64,仍然不正确。让我简化一下:80 / (70 / 14) → 80 / 5 = 16,然后 80 - 16 依然得到 64。让我再改进一下:80 - (70 / 14) → 80 - 5 = 75,但我需要 85。所以,我将以稍微不同的顺序插入数字:得到 80 + (70 / 14) = 80 + 5 = 85</think><answer> 80 + (70 / 14) </answer>

200 到 450 步之间的成功推理样本

<think>我们需要用数字 39, 16 和 55 进行基本的算术运算,得到 78。让我们尝试不同的组合:- 55 + 39 - 16 = 84 - 16 = 68(太低)- 55 + 16 - 39 = 71 - 39 = 32(太低)- 55 - 16 + 39 = 39 + 39 = 78(这成立)所以,方程是 55 - 16 + 39 = 78。</think><answer> 55 - 16 + 39 </answer>

结论


DeepSeek R1 的发布和其研究论文可能成为开放科学和开源开发的分水岭。在 DeepSeek 发布后的短短一周内,我们便成功地复现了一个简单版本的 R1 学习“推理”,通过 GRPO 和 Countdown 游戏来实现。尽管我们的实现集中于特定任务,而非通用推理,并且收敛到一个非常具体的“推理”格式,但它展示了这一方法的有效性。

在我们的迷你 R1 实验中,我们使用了 GRPO,并且使用了两个基于规则的奖励,但仍然需要显著的计算资源:4 台 H100 GPU 持续运行了 6 小时,仅完成了 450 步的训练,模型为 3B 参数。这给了我们一个关于强化学习规模化所需计算需求的初步认识。DeepSeek 运行了一个 671B 的模型,训练超过 8000 步,可能进行了许多实验。

展望 2025年,显然我们正处于更大进展的边缘。强化学习将变得更加易用,更多的研究人员和开发者将探索其潜力,但与以往一样,仍需要大量的计算资源,相较于监督微调仍然有不小的差距。

我对2025年充满期待。如果你有任何问题或想法,随时可以联系我。

相关资料

[1]

论文: https://arxiv.org/abs/2501.12948

[2]

脚本: https://github.com/philschmid/deep-learning-pytorch-huggingface/blob/main/training/scripts/run_r1_grpo.py

[3]

设置开发环境: https://www.philschmid.de/mini-deepseek-r1#1-setup-the-development-environment

[4]

从倒计时游戏生成带有推理前缀的训练样本: https://www.philschmid.de/mini-deepseek-r1#2-generate-training-samples-with-reasoning-prefix-from-the-countdown-game

[5]

使用 GRPO 训练模型(教育部分): https://www.philschmid.de/mini-deepseek-r1#3-train-the-model-using-grpo-educational-part

[6]

使用 Deepspeed 和 vLLM 的 GRPO 分布式训练示例: https://www.philschmid.de/mini-deepseek-r1#4-distributed-training-example-for-grpo-using-deepspeed-and-vllm

[7]

结果和训练观察: https://www.philschmid.de/mini-deepseek-r1#5-results-and-training-observations

[8]

Jiayi Pan: https://x.com/jiayi_pirate/status/1882839370505621655

[9]

Group Relative Policy Optimization (GRPO): https://arxiv.org/abs/2402.03300

[10]

DeepSeekMath: https://arxiv.org/abs/2402.03300

[11]

Hugging Face Hub: https://huggingface.co/models

[12]

Hugging Face: https://huggingface.co/join

[13]

Jiayi-Pan/Countdown-Tasks-3to4: https://huggingface.co/datasets/Jiayi-Pan/Countdown-Tasks-3to4

[14]

Qwen/Qwen2.5-3B-Instruct: https://huggingface.co/Qwen/Qwen2.5-3B-Instruct

[15]

Jiayi-Pan: https://x.com/jiayi_pirate/status/1882839487417561307

[16]

GRPOTrainer: https://huggingface.co/docs/trl/main/en/grpo_trainer

[17]

DeepSeekMath: 推动开放语言模型数学推理极限: https://arxiv.org/abs/2402.03300

[18]

run_r1_grpo.py: https://github.com/philschmid/deep-learning-pytorch-huggingface/blob/main/training/scripts/run_r1_grpo.py

[19]

receipes/grpo-qwen-2.5-3b-deepseek-r1-countdown.yaml: https://github.com/philschmid/deep-learning-pytorch-huggingface/blob/main/training/receipes/grpo-qwen-2.5-3b-deepseek-r1-countdown.yaml

[20]

philschmid/qwen-2.5-3b-r1-countdown: https://huggingface.co/philschmid/qwen-2.5-3b-r1-countdown

[21]

DeepSeekMath: https://arxiv.org/abs/2402.03300

[22]

OpenRLHF: https://hijkzzz.notion.site/unraveling-rlhf-and-its-variants-engineering-insights#147d9a33ecc9806090f3d5c749d31f05

[23]

grpo-qwen-2.5-3b-deepseek-r1-countdown.yaml: https://github.com/philschmid/deep-learning-pytorch-huggingface/blob/main/training/receipes/grpo-qwen-2.5-3b-deepseek-r1-countdown.yaml

(文:AGI Hunt)

欢迎分享

发表评论