
现在是2025年,在DeepSeek热潮之后,每个人都想使用GRPO训练自己的推理模型。
作为一名实践者,我也想这样做:仅使用提示和奖励来训练语言模型是一件非常有趣的事情------不像监督微调那样需要完整的输出。
你在网上能找到的大多数例子都是在GSM8K或Countdown游戏上训练模型。我想尝试一些原创的东西,并亲自动手。
于是我想:我能否让模型从事件列表和优先级中创建一个日程安排?
我的第一次实验表明,ChatGPT通常可以解决这类问题,而小型语言模型(小于14B)则struggled。这是一个很好的挑战!
我没有意识到的是,选择一个原创问题将迫使我思考问题设置、生成数据、选择基础模型、设计奖励函数,并进行多轮训练,希望我的模型能学到一些东西。
这里有很多东西需要学习,这正是我想在这篇文章中与你分享的内容。
你可以在👑 🗓️ Qwen Scheduler GRPO仓库中找到所有代码。
跟着我一起学习吧!
本文主要是关于我的实践经验。了解一些GRPO的理论知识会有所帮助。你可以在网上找到一些资源,比如DeepSeekMath论文和Hugging Face推理课程。
问题定义
让我们描述一下我们希望语言模型解决的问题。
我们给模型一个事件列表(包含开始/结束时间)并告诉它哪些是高优先级的。目标是创建一个日程安排,使所选事件的总加权持续时间最大化。
在这个设置中,优先级事件权重为2,普通事件权重为1。
📌 你可能会注意到,问题定义是清晰合理的,但有点任意。也许你更喜欢最大化利润,或者也许一个塞满的日程表并不是你想要的。这完全没问题。那将是一个不同的问题,你也可以用GRPO训练一个模型来解决。
我认为一旦你能在提示中向模型清楚地解释任务,找出如何奖励好的输出,有时还能识别模型中要培养的行为,你就处于一个尝试使用GRPO的好位置。稍后我会详细讨论这点。
让我们看一个问题示例。
示例输入
以下是提示的简短版本(你可以在下面的文章中找到完整提示)。
任务:根据给定的事件创建优化的日程安排。
规则:...
你必须使用这种格式:
<think>...</think>
<schedule>
<event>
<n>...</n>
<start>...</start>
<end>...</end>
</event>
...
</schedule>
---
事件:
- Event A (01:27 - 01:42)
- Event B (01:15 - 02:30)
- Event C (15:43 - 17:43)
优先级:
- Event B
示例输出
<think>详细的推理过程</think>
<schedule>
<event>
<n>Event B</n>
<start>01:15</start>
<end>02:30</end>
</event>
<event>
<n>Event C</n>
<start>15:43</start>
<end>17:43</end>
</event>
</schedule>
经过一些研究,我发现这类问题属于区间调度。
这个特定问题是加权区间调度问题的一个变体,可以使用动态规划有效地解决。
这很好,因为它使我们能够轻松计算任何给定输入的最佳可能分数,这是我们的模型在训练期间要达到的目标。
数据集生成
现在问题已经定义明确,我们可以开始构建数据集用于训练和评估模型。
数据集中每一行的核心就是一个包含事件列表和优先级的提示,供模型进行调度。
与监督微调不同,我们不需要提供模型应该遵循的参考完成。这也是为什么在数学等可验证领域中,构建GRPO数据集通常更容易的原因之一。
我们还在每一行中包含最优分数(最大可能的加权持续时间)。模型在训练期间不会看到这个,但我们用它来计算推动模型朝正确方向前进的奖励。
鉴于这些要求,编写数据集生成脚本相对容易。
- 我们使用不同类别的事件名称(🎶音乐节、🎓大学、🧑💻技术会议等)。
- 每个示例包含随机数量的事件(在4到8之间)和不同的持续时间。
- 我们确保一些事件重叠。
- 我们随机标记一些事件为优先级。
我们为训练集生成了500个示例,为测试集生成了100个示例。
你可以在这里找到数据集生成脚本。
生成的数据集可以在Hugging Face上获取。
训练
完整的📓训练笔记本在这里。
现在有几个训练库支持GRPO。
Hugging Face TRL是一个很好的选择,它还支持使用vLLM进行更快的样本生成。
在我的案例中,我决定使用Unsloth。这是一个修补TRL的库,可以大大减少GPU内存使用。
我使用了NVIDIA A6000 GPU(48GB VRAM),但通过一些小的调整和一点耐心,你可以在16GB VRAM的免费Colab/Kaggle实例上复现相同的实验。
如果你GPU资源不足,Unsloth可以很好地用于实验,但正如我们稍后将看到的,它有一些令人沮丧的bug。
选择并加载要训练的模型
我决定训练Qwen2.5-Coder-7B-Instruct,这是Qwen系列中特定于代码的语言模型。
这个决定有些经验主义,基于两个主要考虑:
-
我首先尝试了较小的模型(0.5B和1.5B)。观察这些模型对我的调度提示的输出,我注意到这些非常小的模型产生的推理几乎没有意义。这是我的第一个教训:如果基础模型的预训练或规模使其能力与所需的相差太远,GRPO不能创造奇迹。
-
我使用代码模型而不是通用模型,部分原因是这对数学任务很常见,部分是因为我注意到这个模型已经很擅长遵循所需的格式(
<think>
,<schedule>
等)。
现在我们可以使用Unsloth加载模型。我们将使用QLoRA进行训练以节省GPU资源。
python
from unsloth import FastLanguageModel
max_seq_length = 2048
lora_rank = 32
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "Qwen/Qwen2.5-Coder-7B-Instruct",
max_seq_length = max_seq_length,
load_in_4bit = True,
fast_inference = True,
max_lora_rank = lora_rank,
gpu_memory_utilization = 0.85, # 如果内存不足请减小这个值
)
model = FastLanguageModel.get_peft_model(
model,
r = lora_rank,
target_modules = [
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
], # 如果内存不足可以移除QKVO
lora_alpha = lora_rank,
use_gradient_checkpointing = "unsloth", # 启用长上下文微调
random_state = 3407,)
如果你的VRAM少于48GB,可以调整几个参数:gpu_memory_utilization
、lora_rank
和target_modules
;后两者会影响你的模型能学习的量。
数据集准备
我们的数据集中的每个示例都包含事件和优先级。
我们预处理数据集,以系统和用户消息的形式添加常规任务描述和指令。
python
import datasets
SYSTEM_PROMPT = """You are a precise event scheduler.
1. First, reason through the problem inside <think> and </think> tags. Here you can create drafts,
compare alternatives, and check for mistakes.
2. When confident, output the final schedule inside <schedule> and </schedule> tags.
Your schedule must strictly follow the rules provided by the user."""
USER_PROMPT ="""Task: create an optimized schedule based on the given events.
Rules:
- The schedule MUST be in strict chronological order.
Do NOT place priority events earlier unless their actual start time is earlier.
- Event start and end times are ABSOLUTE. NEVER change, shorten, adjust, or split them.
- Priority events (weight = 2) carry more weight than normal events (weight = 1),
but they MUST still respect chronological order.
- Maximize the sum of weighted event durations.
- No overlaps allowed. In conflicts, include the event with the higher weighted time.
- Some events may be excluded if needed to meet these rules.
You must use this format:
<think>...</think>
<schedule>
<event>
<n>...</n>
<start>...</start>
<end>...</end>
</event>
...
</schedule>
---
"""
ds = datasets.load_dataset("anakin87/events-scheduling", split="train")
ds = ds.map(
lambda x: {
"prompt": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": USER_PROMPT + x["prompt"]},
]
}
)
奖励函数
GRPO是一种强化学习算法,其中为每个提示生成多个样本(在我们的例子中是8个)。在训练期间,模型的参数会更新以生成高奖励的响应。
因此,虽然我们不需要数据集中的完成,但奖励函数的设计至关重要。
在像从GSM8K学习这样的更简单问题中,常见的选择是定义多个奖励函数并将它们相加:
- 一个检查输出格式是否正确。
- 另一个检查最终答案是否与已知解决方案匹配。
有关从GSM8K学习的绝佳示例,请查看William Brown的这个gist。有关为TRL/Unsloth创建自定义奖励函数的指南,请参阅文档。
在我们的实验中,我们可以轻松设计一个用于格式的奖励函数。
python
import re
overall_pattern = (r"<think>.+</think>.*<schedule>.*(<event>.*<n>.+</n>.*<start>\d{2}:\d{2}</start>.*"
r"<end>\d{2}:\d{2}</end>.*</event>)+.*</schedule>")
overall_regex = re.compile(overall_pattern, re.DOTALL)
def format_reward(prompts, completions, **kwargs):
responses = [completion[0]['content'] for completion in completions]
return [0.0 if not overall_regex.match(response) else 10.0 for response in responses]
现在,奖励日程安排本身的质量更难。
一个有效的日程安排具有以下特点:
- 事件来自原始提示(没有虚构的事件或更改的时间)。
- 事件按时间顺序排序。
- 没有重叠的事件。
- 至少有两个事件。
我们还希望鼓励模型产生一个能最大化总加权持续时间的日程安排。
在确定下面所示的解决方案之前,我尝试了不同数量的奖励函数。我也得出了一些观察结果。
我尝试使用一个单一的评分奖励函数,如果日程安排有效(满足上述所有标准)则给出schedule_score/optimal_score
,否则给出0。结果表明这是一个糟糕的想法,因为它太不连续了,大多数时候返回0,没有提供足够的信号来指导模型的学习。
另一方面,我尝试使用几个奖励函数,每个上述要求一个:现有事件、时间顺序、无重叠等。在这种情况下,我发现即使模型正在学习一些东西,但训练并不能有效地产生良好的日程安排。使用太多奖励函数也可能不好。
在"太多奖励"阶段,我遇到了一个经典的RL问题:奖励黑客。
我最初忘记丢弃少于两个事件的日程安排。突然,我的奖励看起来很棒!
模型在格式、时间顺序、无重叠等方面获得了高分...
我查看了实际输出,发现它找到了一个漏洞:它只生成包含一个事件的日程安排(通常是高优先级的)。这完全满足了我的大多数单独奖励函数。
这不是模型聪明;这是我的奖励设置可被利用。一次很好的经验。🙃
最终,我使用了一个鼓励产生按时间顺序排序日程安排的奖励函数和另一个最大化分数的函数。正如你在下面看到的,我试图在这两个奖励函数中包含并因此偏爱其他要求。
python
def sorted_events_reward(completions, **kwargs):
scores = []
responses = [completion[0]['content'] for completion in completions]
for response in responses:
scheduled_events = get_events(response)
# not a valid schedule: should be discarded
if len(scheduled_events) < 2:
scores.append(0.0)
continue
scheduled_events_minutes = [(ev[0], time_to_minutes(ev[1]), time_to_minutes(ev[2]))
for ev in scheduled_events]
if all(scheduled_events_minutes[i][1] < scheduled_events_minutes[i+1][1]
for i in range(len(scheduled_events_minutes)-1)):
scores.append(20.0)
else:
scores.append(0)
return scores
def score_reward(prompts, completions, events, priority_events, optimal_score, **kwargs):
scores = []
responses = [completion[0]['content'] for completion in completions]
for content, valid_events, priorities, opt_score in zip(responses, events, priority_events, optimal_score):
scheduled_events = get_events(content)
# Get valid scheduled events
existing_events = {ev for ev in scheduled_events if [ev[0], ev[1], ev[2]] in valid_events}
# penalize choosing nonexistent events or less than 2 events (not a valid schedule)
if len(existing_events)<len(scheduled_events) or len(existing_events) < 2:
scores.append(0.0)
continue
# Convert to minutes
existing_events_minutes = [(ev[0], time_to_minutes(ev[1]), time_to_minutes(ev[2]))
for ev in existing_events]
# remove overlapping events and remove both events - to penalize overlaps
overlapping_events = set()
for j in range(len(existing_events_minutes)):
for k in range(j + 1, len(existing_events_minutes)):
if (existing_events_minutes[j][1] <= existing_events_minutes[k][2] and
existing_events_minutes[j][2] >= existing_events_minutes[k][1]):
overlapping_events.add(existing_events_minutes[j])
overlapping_events.add(existing_events_minutes[k])
existing_events_minutes = [ev for ev in existing_events_minutes
if ev not in overlapping_events]
# Calculate score
score = sum(2 * (ev[2] - ev[1]) if ev[0] in priorities
else ev[2] - ev[1] for ev in existing_events_minutes)
scores.append((score/opt_score) * 70)
return scores
简而言之,在我的实验中,我使用了以下奖励函数:
- 格式 (0-10)
- 排序事件 (0-20)
- 分数 (0-70)
累积奖励可以在0到100之间变化。
我在这一步上进行了很多迭代。正如我们将看到的,它仍然不完美,但确实让模型学习到了一些东西。
设置训练配置并开始训练!
python
from trl import GRPOConfig, GRPOTrainer
tokenized_prompts = [tokenizer.apply_chat_template(prompt, tokenize=True, add_generation_prompt=True)
for prompt in ds['prompt']]
exact_max_prompt_length = max([len(tokenized_prompt) for tokenized_prompt in tokenized_prompts])
max_prompt_length = 448 # 手动调整
new_model_id="anakin87/qwen-scheduler-7b-grpo"
training_args = GRPOConfig(
learning_rate = 8e-6,
adam_beta1 = 0.9,
adam_beta2 = 0.99,
weight_decay = 0.1,
warmup_ratio = 0.01,
lr_scheduler_type = "cosine",
optim = "paged_adamw_8bit",
logging_steps = 1,
per_device_train_batch_size = 8,
gradient_accumulation_steps = 1,
num_generations = 8, # 如果内存不足请减小
max_prompt_length = max_prompt_length,
max_completion_length = max_seq_length - max_prompt_length,
max_grad_norm = 0.1,
output_dir = "outputs",
overwrite_output_dir = True,
push_to_hub = True,
hub_model_id=new_model_id,
hub_strategy="every_save",
save_strategy="steps",
save_steps=50,
save_total_limit=1,
num_train_epochs=3,
)
trainer = GRPOTrainer(
model = model,
processing_class = tokenizer,
reward_funcs=[
format_reward,
sorted_events_reward,
score_reward,
],
args = training_args,
train_dataset = ds,
)
trainer.train()
max_prompt_length
是提示的最大长度。更长的提示将被截断。
我们可以轻松计算这个值,然后手动调整它。
num_generations
是GRPO的一个关键参数,表示为每个提示生成的样本数量。
该算法基于比较不同样本来学习,并引导模型生成具有更高奖励的样本。
更多样本给它提供了更多学习信息,可能导致更好的结果,但生成它们需要更多时间和内存。
有关其他GRPOConfig参数的详细信息,请查看TRL API参考。
结果
训练曲线
让我们看看训练曲线。
关注奖励,我们看到模型在训练期间有所改进。从一开始,样本在格式对齐方面就获得了很好的分数。尊重时间顺序和为日程安排获得良好分数随着时间的推移而提高,表明模型确实学习了奖励函数中形式化的行为。
其他记录的指标,如reward_std
(奖励组内的平均标准差)和kl
(模型与参考模型之间的平均KL散度)显示可接受的值。
你可以在这个Weight and Biases报告中探索所有训练曲线和指标。
检查样本
在训练期间,我们记录了生成的样本的随机子集。
你可以在这里浏览完整日志。
检查这些完成很有趣且富有洞察。这就是我如何意识到较小的模型根本不理解:随着时间的推移,它们的推理没有明显改善。
以下是不同训练阶段同一输入提示的示例:
事件:
- Analyzing product performance (04:33 - 06:03)
- One-on-one with manager (08:41 - 08:56)
- Client call with Acme Corp (09:13 - 10:58)
- Meeting with Lisa at café (10:14 - 11:14)
- Updating website content (12:21 - 13:36)
- Preparing PTO handover (16:07 - 17:37)
- Marketing standup on Zoom (17:58 - 19:43)
- Brainstorming with Luke and Patrick (19:11 - 19:56)
优先级:
- Analyzing product performance
- Meeting with Lisa at café
步骤 473/1500
<think>
At first glance, the events overlap in the time slot from 10:14 to 11:14. The priority event
"Meeting with Lisa at café" is higher in weight compared to "Client call with Acme Corp", so we must
include it. However, this will exclude the client call if we want to prioritize "Meeting with Lisa
at café". To resolve this, we need to decide whether to exclude the client call or the brainstorming
session (19:11 - 19:56) which is a normal event.
Since "Meeting with Lisa at café" is a priority event and it overlaps with the client call, we should
include "Meeting with Lisa at café" and exclude the client call. This will give us more time for
"Updating website content" (12:21 - 13:36) and "Preparing PTO handover" (16:07 - 17:37), which are
also normal events.
Here is the adjusted schedule:
[...]
This schedule maximizes the sum of weighted event durations while respecting all the rules provided.
</think>
<schedule>
<event>
<n>Analyzing product performance</n>
<start>04:33</start>
<end>06:03</end>
</event>
<event>
<n>One-on-one with manager</n>
<start>08:41</start>
<end>08:56</end>
</event>
<event>
<n>Client call with Acme Corp</n>
<start>09:13</start>
<end>10:14</end>
</event>
<event>
<n>Meeting with Lisa at café</n>
<start>10:14</start>
<end>11:14</end>
</event>
<event>
<n>Updating website content</n>
<start>12:21</start>
<end>13:36</end>
</event>
<event>
<n>Preparing PTO handover</n>
<start>16:07</start>
<end>17:37</end>
</event>
<event>
<n>Brainstorming with Luke and Patrick</n>
<start>19:11</start>
<end>19:56</end>
</event>
</schedule>
好多了!在这里,模型明确考虑了权重并生成了有效的日程安排。
步骤 1470/1500
<think>
The goal is to create a schedule that maximizes the sum of weighted event durations while respecting
the chronological order, priorities, and non-overlapping constraints. Let\'s analyze the events and
prioritize them:
1. **Analyzing product performance**: Start time: 04:33, End time: 06:03, Priority: 2
2. **One-on-one with manager**: Start time: 08:41, End time: 08:56, Priority: 1
3. **Client call with Acme Corp**: Start time: 09:13, End time: 10:58, Priority: 1
4. **Meeting with Lisa at café**: Start time: 10:14, End time: 11:14, Priority: 2
5. **Updating website content**: Start time: 12:21, End time: 13:36, Priority: 1
6. **Preparing PTO handover**: Start time: 16:07, End time: 17:37, Priority: 1
7. **Marketing standup on Zoom**: Start time: 17:58, End time: 19:43, Priority: 1
8. **Brainstorming with Luke and Patrick**: Start time: 19:11, End time: 19:56, Priority: 1
Given the priorities, we must ensure that "Analyzing product performance" and "Meeting with Lisa
at café" are included. Let\'s try to fit the rest while avoiding overlaps:
1. **Analyzing product performance**: 04:33 - 06:03 (Priority 2)
2. **One-on-one with manager**: 08:41 - 08:56 (Priority 1)
3. **Client call with Acme Corp**: 09:13 - 10:58 (Priority 1)
4. **Meeting with Lisa at café**: 10:14 - 11:14 (Priority 2)
5. **Updating website content**: 12:21 - 13:36 (Priority 1)
6. **Preparing PTO handover**: 16:07 - 17:37 (Priority 1)
The next event, "Marketing standup on Zoom", starts at 17:58, but the availability is only until
19:43. We can fit "Brainstorming with Luke and Patrick" from 19:11 to 19:56 before the standup.
1. **Analyzing product performance**: 04:33 - 06:03 (Priority 2)
2. **One-on-one with manager**: 08:41 - 08:56 (Priority 1)
3. **Client call with Acme Corp**: 09:13 - 10:58 (Priority 1)
4. **Meeting with Lisa at café**: 10:14 - 11:14 (Priority 2)
5. **Updating website content**: 12:21 - 13:36 (Priority 1)
6. **Preparing PTO handover**: 16:07 - 17:37 (Priority 1)
7. **Brainstorming with Luke and Patrick**: 19:11 - 19:56 (Priority 1)
Final schedule:
- Analyzing product performance: 04:33 - 06:03
- One-on-one with manager: 08:41 - 08:56
- Client call with Acme Corp: 09:13 - 10:58
- Meeting with Lisa at café: 10:14 - 11:14
- Updating website content: 12:21 - 13:36
- Preparing PTO handover: 16:07 - 17:37
- Brainstorming with Luke and Patrick: 19:11 - 19:56
This schedule respects the chronological order, includes the high-priority events, and avoids
any overlaps.
</think>
<schedule>
<event>
<n>Analyzing product performance</n>
<start>04:33</start>
<end>06:03</end>
</event>
<event>
<n>One-on-one with manager</n>
<start>08:41</start>
<end>08:56</end>
</event>
<event>
<n>Client call with Acme Corp</n>
<start>09:13</start>
<end>10:58</end>
</event>
<event>
<n>Meeting with Lisa at café</n>
<start>10:14</start>
<end>11:14</end>
</event>
<event>
<n>Updating website content</n>
<start>12:21</start>
<end>13:36</end>
</event>
<event>
<n>Preparing PTO handover</n>
<start>16:07</start>
<end>17:37</end>
</event>
<event>
<n>Brainstorming with Luke and Patrick</n>
<start>19:11</start>
<end>19:56</end>
</event>
</schedule>
有趣的是...在这个样本中,尽管有长篇的推理过程,模型仍然在最终日程安排中放入了两个重叠的事件。
查看样本很好,但我们需要系统性的评估。
关于"啊哈时刻"
在DeepSeek-R1论文中,作者展示了在GRPO训练期间出现的"啊哈时刻",其中"模型学会使用拟人化语调重新思考"。
这是他们模型和GRPO的卖点之一,促使从业者在训练期间寻找/复现这种行为。
他们发现,在任何GRPO训练开始之前,基础模型中就可以找到类似的"啊哈时刻"。
基于这些令人信服的原因,我没有花时间寻找"啊哈时刻"。
不过,我发现这方面的研究非常有趣,特别是努力区分从基础模型中引出已有行为与教授全新行为之间的差异。
评估
对于评估,我们可以使用我们的事件调度数据集的测试集,我们的模型以前从未见过这些数据。
我们将评估阶段分为两个步骤:
- 推理:为每个测试提示生成一个日程安排。
- 评分:根据我们的规则检查每个生成的日程安排(格式正确,>=2个事件,只有现有事件,按时间顺序排列,无重叠)。
如果任何规则被违反,该日程安排在该提示中的得分为0。如果有效,其得分为(weighted_duration/optimal_weighted_duration) * 100
。
你可以在GitHub仓库中找到这些脚本。
❌ Unsloth 的bug:在推理过程中,我遇到了一个问题。用Unsloth训练的模型无法可靠地转换为标准的Hugging Face Transformers模型;尝试这样做可能会得到一个不同的模型。
这是一个已知问题。如果我需要将其用于生产工作,比如用vLLM提供服务,这将是一个主要障碍,令人相当沮丧。
对于这个实验,我只是使用Unsloth运行评估推理。
由于Unsloth修补了TRL,使用TRL很容易调整代码,它更稳定。只需注意,你将需要更多的GPU VRAM。
我想比较qwen-scheduler-7b-grpo(我们的模型)、Qwen2.5-Coder-7B-Instruct(原始模型)和Qwen2.5-Coder-14B-Instruct(同一系列的更大模型)。
由于样本表明模型可能在第2个和第3个epoch之间有所下降,我还评估了第2个epoch结束时的检查点。
模型 | 格式错误 | <2个事件 | 重叠 | 未排序 | 不存在 | 有效日程安排 | 平均分数 |
---|---|---|---|---|---|---|---|
Qwen2.5-Coder-7B-Instruct | 3 | 1 | 31 | 29 | 27 | 9 | 8.05 |
Qwen2.5-Coder-14B-Instruct | 1 | 1 | 38 | 6 | 6 | 48 | 45.85 |
qwen-scheduler-7b-grpo | 0 | 0 | 36 | 0 | 1 | 62 | 58.54 |
qwen-scheduler-7b-grpo (第2个epoch) | 0 | 0 | 30 | 2 | 1 | 67 | 62.25 |
快速观察:
- GRPO确实有效!它在引导模型产生期望行为方面相当有效。
- 我们调优的模型在这个任务上甚至优于一个两倍大小的模型。
- 模型几乎完美地学习了格式、时间顺序和只使用现有事件。
- 模型仍然在处理重叠方面存在困难,这在测试集的相当大一部分中很明显。
如何改进?
我用于这个项目的时间和计算资源已经用完,但这是我接下来会尝试的一些改进方法。
主要问题显然是重叠。我们看到模型没有有效地学会避免它们。此外,第3个epoch中的轻微性能下降可能表明模型正在学习次优策略。
关于奖励函数的想法:
- 更强的重叠惩罚:在
score_reward
函数中,我们从分数计算中移除重叠事件。我们可以实施更严厉的惩罚... - 专门的重叠奖励:或者,添加一个只检查重叠的奖励函数。
让我们也记住,默认情况下奖励函数是相加的,所以根据我们如何设计惩罚,两种不同的方法在数学上可能是等价的。
结论和关键学习
在本文中,我们通过使用GRPO对语言模型进行后训练,使其能够完成一个新的可验证任务:从事件列表和优先级创建日程安排。
我们涵盖了问题定义、数据集生成、基础模型选择、奖励函数设计、使用Unsloth进行训练和评估。
亲自动手教会了我关于GRPO和应用强化学习于LLMs的几个方面。
GRPO对可验证任务很酷
它简化了典型的RL设置(例如在PPO中使用的):没有价值模型;奖励模型通常被确定性奖励函数替代(使用可验证奖励的强化学习)。由于你的数据集只需要提示(不需要完成),数据收集变得比SFT更容易、更便宜。
引导
使用GRPO和类似算法更多的是从训练模型中引导出期望行为,而不是教它完全新的东西。如果你需要根本性的新技能,指令微调(和蒸馏)可能更有效(参见强化学习真的能激励LLMs中的推理能力超越基础模型吗?论文)。如果你对这些主题感兴趣,请关注Nathan Lambert和Interconnects博客。
基础模型很重要
如果基础模型在采样期间从未在任务上表现出有希望的行为,GRPO可能对你的问题无效。你可能首先需要一个更大或更好的基础模型。
"啊哈时刻"可能被过度宣传
在DeepSeek-R1论文中,作者表明在GRPO期间"模型学会使用拟人化语调重新思考"。奇迹?最近的研究表明基础模型中也存在类似行为。
奖励函数设计至关重要
你的奖励应该捕捉你的目标,提供可学习的信号(对模型的鼓励),并且要健壮。如果它们不健壮,你可能会遇到奖励黑客:模型找到捷径来最大化奖励,而不是真正解决你心中的问题。这既有趣又令人沮丧😅
Unsloth:节省GPU很好,但要小心
如果你没有太多GPU或处于实验阶段,Unsloth可能很有帮助。这些人在GPU效率方面做了令人印象深刻的工作。然而,目前该库带有难以解决的bug,使其不适合严肃的用例。如果你有足够的VRAM,TRL会更稳定。
如果你喜欢这篇文章,欢迎在Hugging Face和LinkedIn上关注我。
如果你注意到任何错误或不准确之处,请随时联系我。
资源和参考
Qwen Scheduler GRPO
- GitHub仓库:这里你可以找到我用于这个实验的所有代码。
- Hugging Face集合:数据集和模型。
GRPO论文和资源
-
Nathan Lambert的Interconnects博客
Updating website content
12:21
13:36Preparing PTO handover
16:07
17:37Marketing standup on Zoom
17:58
19:43Brainstorming with Luke and Patrick
19:11
19:56在这个阶段,模型展示了有希望的能力来反思给定的约束,但错误地改变了一些事件的时间以最大化花费的时间。
步骤 973/1500
To create an optimized schedule, I need to prioritize events based on provided weights and ensure no overlaps. The priority events are "Analyzing product performance" and "Meeting with Lisa at café". Let\'s evaluate the schedule step-by-step.
- Analyzing product performance (04:33 - 06:03) - This is a priority event (weight = 2).
- One-on-one with manager (08:41 - 08:56) - This is a normal event (weight = 1).
- Client call with Acme Corp (09:13 - 10:58) - This is a normal event (weight = 1). It conflicts
with "Meeting with Lisa at café", which is also a priority event. Since "Meeting with Lisa at café"
has higher priority, we exclude this event. - Meeting with Lisa at café (10:14 - 11:14) - This is a priority event (weight = 2). However,
it conflicts with "Client call with Acme Corp" as discussed. We need to decide whether to exclude
this event or find another slot. - Updating website content (12:21 - 13:36) - This is a normal event (weight = 1).
- Preparing PTO handover (16:07 - 17:37) - This is a normal event (weight = 1).
- Marketing standup on Zoom (17:58 - 19:43) - This is a normal event (weight = 1). It conflicts
with "Preparing PTO handover" as they overlap. We need to decide whether to exclude one of them. - Brainstorming with Luke and Patrick (19:11 - 19:56) - This is a normal event (weight = 1).
It conflicts with "Marketing standup on Zoom" as they overlap. We need to decide whether to exclude
one of them.
To maximize the sum of weighted event durations, we should exclude conflicts with the highest priority
events first.
- Exclude "Client call with Acme Corp" since it conflicts with "Meeting with Lisa at café"
(higher priority). - Exclude "Marketing standup on Zoom" since it conflicts with "Preparing PTO handover" and does not
interfere with any higher priority events.
Thus, the optimized schedule is as follows:
Analyzing product performance
04:33
06:03
One-on-one with manager
08:41
08:56
Meeting with Lisa at café
10:14
11:14