这篇文章记录了一次完整的 slime Search-R1 实验:通过GRPO RL 训练一个 Qwen2.5-3B base model,使其能够在开放域问答任务中调用检索工具,并且在 NQ 和 HotpotQA 测试集上与 RL 之前的 base model 进行对比评估。
经过 3000 步 GRPO 训练后,模型在 NQ 上的 exact match 大约提升到原来的 3 倍,在 HotpotQA 上提升约 3.4 倍。从 qualitative results 中也可以看到,RL让模型学会了主动搜索并基于检索结果回答,而不是仅凭内部记忆猜测答案。
任务设定
Search-R1将开放域问答建模成一个多轮agentic 循环。模型需要进行推理,选择性地发出搜索 query,阅读检索到的 passages,并最终给出答案。整个过程被组织成如下结构化协议:
<think> … </think>
<search> query </search> # optional,可重复调用
<information> Doc 1 … Doc 2 … Doc 3 </information> # retriever 返回的 top-3 文档
<answer> final answer </answer>slime 通过一个 custom generate function(generate_with_search.generate)来运行这一流程。这个函数会在 sglang rollout 过程中插入实时检索调用。根据配置,每条 rollout 最多允许2轮搜索,每轮搜索会从本地部署的retriever返回相关性最高的三篇文档。
奖励由 generate_with_search.reward_func定义并计算,从模型输出中抽取 <answer>,然后与 gold answers 计算 exact match。
训练过程
实验设置概览
| Component | Choice |
|---|---|
| Algorithm | GRPO (--advantage-estimator grpo) |
| Base model | Qwen2.5-3B,同时也作为 KL reference model |
| Reward | Exact match |
| Training data | nq_hotpotqa_train/train.parquet,混合 NQ + HotpotQA |
| Rollout steps | 3000 (--num-rollout 3000) |
| Prompts / step | 32 (--rollout-batch-size 32) |
| Samples / prompt | 8 (--n-samples-per-prompt 8) → 每步 256 条 trajectories |
| Global batch size | 256 |
| Max response length | 512 tokens (--rollout-max-response-len 512) |
| Rollout temperature | 1.0,用于训练时探索 |
| Optimizer | Adam,lr 1e-6 constant,wd 0.01,β=(0.9, 0.98) |
| KL | low-var KL to ref,coef 1e-3 |
| Entropy bonus | 0.0 |
| PPO clip | 0.2 / 0.28 high (clip-higher) |
| TIS | on |
训练日志
| Reward curve | Response length |
|---|---|
![]() | ![]() |
在前300步模型迅速学会输出有效回答,并且回答长度保持相对稳定。
评估
评估阶段使用 greedy decoding,每个问题只采样 1 个回答。合并后的 test.parquet 按照 data_source 被划分成:
- NQ —
nq_test.parquet,3,610 个问题 - HotpotQA —
hotpotqa_test.parquet,7,405 个问题
主要结果
| Model | NQ EM | HotpotQA EM |
|---|---|---|
| Qwen2.5-3B base | 0.1620 | 0.0984 |
| Qwen2.5-3B Search-R1 RL for 3000 steps | 0.4576 | 0.3381 |
| Δ (RL gain) | +0.2956 | +0.2397 |
RL 训练使 NQ EM 大约提升到原来的 3 倍,并使 HotpotQA EM 提升约 3.4 倍。
指标定义
这里的分数是 qa_em_format.py 中 compute_score_em 实现的 Exact Match (EM):
- 从模型最终输出中抽取
<answer>…</answer>; - 对抽取结果进行 normalize:转小写、去标点、去掉冠词 a/an/the、合并空白字符,和 SQuAD/DPR 的 normalization 一致;
- 如果 normalized answer 与任意一个 gold answer 完全一致,则得分为 1.0,否则为 0.0。
Qualitative comparison
训练后的模型:会使用搜索工具,并基于检索结果回答
问题:"who sings the rap in baby by justin bieber?" gold answer: Ludacris
<think><search>who sings the rap in baby by justin bieber</search>
<information>
Doc 1 (Baby (Justin Bieber song)): …written by Bieber with Christopher "Tricky"
Stewart and Terius "The-Dream" Nash … and also by R&B singer Christina Milian
and labelmate, rapper Ludacris…
Doc 2 … Doc 3 …commended Ludacris' part…
</information>
<answer> Ludacris </answer>提取出的 "Ludacris" 与 gold answer 一致。EM = 1。整个过程完成了 search → read → answer,并且只用了约 40 个 tokens。
Base model:两种典型失败模式
模式 1:不搜索,直接凭记忆回答,回答错误
问题:"who sings song at the end of beauty and the beast?" gold answer: Josh Groban
<|im_start|>assistant I found the answer to your question! The song at the end of
Beauty and the Beast is sung by Celine Dion. <answer> Celine Dion </answer>模型没有输出 <search>,而是自信地给出了错误答案。EM = 0。
模式 2:不回答,复读 instruction,产生退化输出
<|im_start|>assistant I will conduct reasoning inside <think> and </think> first.
I will search for the information if I find that I lack any knowledge. I will
provide the answer inside <answer> and </answer>模型没有搜索,也没有给出答案;scorer 从输出中错误抽取到了一个无意义的 "and"。EM = 0。
对比
| Base | Trained | |
|---|---|---|
是否使用 <search> 工具 | 很少 / 几乎从不使用 | 会主动使用 |
| 是否基于 retrieved docs 回答 | 否 | 是 |
| 输出风格 | 冗长 / 复读 prompt | 简洁:search → read → answer,约 37 tokens |
| 典型结果 | 凭记忆猜测,或者格式错误 | 基于检索结果回答,且通常正确 |
EM reward 实际教会模型的是一种 agentic behavior:面对事实性问题时,先发起搜索,阅读返回 passages,然后在 <think>/<search>/<information>/<answer> 协议中抽取最终答案。Base model 拥有相同的底层语言知识,但它不会主动调用工具,也不能稳定遵循格式,因此要么凭记忆猜测答案(模式 1),要么复读 prompt(模式 2)。
另外需要注意的是,训练后模型的回答中缺少 </think>。这是因为我们只使用了 exact match 作为 reward。在训练过程中,policy 学到了 reasoning tokens 和 </think> 的close-tag 本身不会带来 reward。为了让输出格式更加严格,后续应该给 structure_format_score 一定权重。
复现
按照这个 GitHub 仓库 复现实验。本实验在 host 上使用 conda environment 配置了本地 retriever,并在 slimerl:slime docker container 中运行 slime search-r1 训练脚本。


