前言
近年来,大语言模型 (LLM) 领域取得了显著进展。除了大规模预训练外,后训练技术,如 GRPO (Group Relative Policy Optimization,群组相对策略优化),在提升 LLM 的性能方面发挥了关键作用,尤其是在 推理能力 方面。本文将深入探讨 GRPO 的原理、在 LLM 训练流程中的位置、以及如何利用 Hugging Face 的 TRL (Transformer Reinforcement Learning,Transformer 强化学习) 库实现 GRPO,并分析实验结果。
1. LLM 训练流程:从预训练到 GRPO 微调
LLM 的训练通常包括三个主要阶段:预训练 (Pre-Training)、监督微调 (SFT, Supervised Fine-Tuning) 和 专业化训练 (Specialized Training)。
- 预训练: 此阶段的目标是让 LLM 学习海量文本数据中的模式,例如语法、句法和词汇关系。模型通过预测下一个词语来学习。
- 监督微调 (SFT): 虽然预训练使模型理解了大量语料库中的模式,但它在执行特定任务、遵循用户指令或避免数据中的偏见方面存在局限性。SFT 通过使用少量但高质量的人工标注数据来克服这些限制。SFT 模型可以学习各种任务,如摘要、问答和补全句子等。
- 专业化训练: 虽然 SFT 阶段已经开始了模型的专业化,例如学习遵循用户指令或执行特定任务。但模型仍然可能存在轻微的偏差、不一致的行为或不正确的响应格式。对于更复杂和结构化的任务,如系统设计、数学问题求解或多步骤规划,推理能力 至关重要。 这时,诸如思维链 (CoT, Chain of Thought) 之类的技术发挥着重要作用。 CoT 不仅可以帮助模型提出正确的答案,还可以帮助用户验证 LLM 提供的响应。可以通过诸如强化学习 (RL) 之类的先进技术来改善响应中的行为。它包括基于奖励的 RL 方法,该方法基于人类偏好反馈或特定于任务的奖励信号来优化模型的输出。GRPO 便是此类 强化学习 方法中的一个杰出代表,它能够进一步提升 LLM 在复杂任务中的性能。
2. 强化学习 (RL) 与 GRPO:塑造 LLM 的行为
强化学习 (RL) 的核心思想是让 LLM 通过与环境交互,并根据获得的 奖励 来学习最优策略。在 LLM 中,奖励 可以是输出的正确格式、遵循特定规则或响应的相关性和准确性。
一个简化的 RL 流程包括以下步骤:
- 定义奖励函数 (Reward Function): 奖励函数 为每个输出生成一个分数。输出的期望程度越高,其奖励分数就越高。例如,对于摘要任务,响应越精确,其分数就越高。
- 使用 RL 进行微调 (Fine Tuning with RL): 一旦确定了奖励函数,就可以使用该奖励函数对 LLM 模型进行微调。对于每个响应,都会根据响应的期望程度生成奖励分数,并根据该奖励分数更新模型权重。这是进行探索和利用的阶段。
- 反馈循环 (Feedback Loop): 基于奖励分数更新模型,并且该循环不断继续直到损失收敛。
GRPO 作为 PPO (Proximal Policy Optimization,近端策略优化) 的变体,在 强化学习 领域取得了显著的进展。与传统的 RLHF (Reinforcement Learning from Human Feedback,从人类反馈中进行强化学习) 方法相比,GRPO 允许模型从相对排序或比较中学习,而不是依赖于绝对 奖励 值。这意味着 GRPO 能够更有效地处理复杂的反馈信号,例如判断两个响应哪个更合理或更符合要求。
3. GRPO 的核心优势:相对策略优化
GRPO 的核心优势在于其相对性。它不是试图预测一个绝对的 奖励 值,而是学习如何对不同的响应进行排序。这种方法有几个优点:
- 更易于获取反馈: 获取相对反馈比获取绝对反馈更容易。例如,让人类判断两个摘要哪个更好比让人类给一个摘要打分更容易。
- 更鲁棒的训练: 相对反馈对噪声和偏差的敏感性较低。
- 能够处理复杂的任务: 相对反馈能够处理复杂的任务,例如 推理 和决策制定。
4. TRL 中的 GRPO 实现:代码示例与分析
Hugging Face 的 TRL 库提供了一个便捷的工具包,用于使用 GRPO 等技术对 LLM 进行 后训练。以下是一个使用 TRL 和 GRPO 对 Qwen 指令模型进行微调的示例,目标是生成大约 200 个字符的摘要:
from datasets import load_dataset
from trl import GRPOConfig, GRPOTrainer
from transformers import AutoConfig
# 加载数据集
dataset = load_dataset("trl-lib/tldr", split="train[:75]")
def format(example):
"""格式化数据集"""
return {
"prompt": f"Summarize: {example['prompt']}\n\n"
}
dataset = dataset.map(format)
# 奖励函数:越接近 200 个字符越好
def reward_len(completions, **kwargs):
rewards = [-abs(200 - len(c)) for c in completions]
return rewards
output_dir="Qwen2-0.5B-GRPO"
model_name = "Qwen/Qwen2-0.5B-Instruct"
training_args = GRPOConfig(
learning_rate=1e-5,
output_dir=output_dir,
logging_steps=10,
temperature = 0.8,
epsilon = 0.2,
save_steps = 25
)
trainer = GRPOTrainer(
model=model_name,
reward_funcs=reward_len,
args=training_args,
train_dataset=dataset
)
trainer.train()
# 保存模型权重
trainer.model.save_pretrained(output_dir)
# 保存原始配置
config = AutoConfig.from_pretrained(model_name)
config.save_pretrained(output_dir)
在这个例子中,reward_len
函数定义了一个奖励函数,它根据摘要的长度来给出奖励。长度越接近 200 个字符,奖励越高。通过 GRPO 的训练,模型将学习生成长度更符合要求的摘要。
5. 自定义奖励函数:塑造 LLM 的特定行为
奖励函数 是 GRPO 的关键。通过设计不同的奖励函数,我们可以塑造 LLM 的特定行为。以下是一些自定义奖励函数的例子:
- 惩罚毒性响应:
from detoxify import Detoxify
tox_model = Detoxify("original")
def reward_toxicity(completions, **kwargs):
toxicities = tox_model.predict(completions)['toxicity']
return [-t for t in toxicities] # 毒性越小,奖励越高
这个奖励函数使用 Detoxify
库来检测文本中的毒性。毒性越高的响应,奖励越低。
- 强制特定格式:
import re
def format_reward_func(completions, **kwargs):
"""奖励函数,检查完成是否具有特定格式。"""
pattern = r"^<think>.*?</think><answer>.*?</answer>$"
completion_contents = [completion[0]["content"] for completion in completions]
matches = [re.match(pattern, content) for content in completion_contents]
return [1.0 if match else 0.0 for match in matches]
这个奖励函数检查响应是否符合 <think>...</think><answer>...</answer>
的格式。符合格式的响应,奖励为 1.0,否则为 0.0。这可以用于训练模型以特定格式生成响应。
6. 案例分析:GRPO 提升 Qwen 模型的摘要能力
通过比较经过 GRPO 训练的 Qwen 模型和原始 Qwen 模型,我们可以看到 GRPO 对模型行为的影响。以下是一些比较结果:
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from datasets import load_dataset
# 加载数据集
dataset = load_dataset("trl-lib/tldr", split="train[1000:1010]")
def format(example):
return {
"prompt": f"Summarize: {example['prompt']}\n\n"
}
dataset = dataset.map(format)
# 加载微调后的模型
model_name = "Qwen/Qwen2-0.5B-Instruct"
model_path = "./Qwen2-0.5B-GRPO_200/checkpoint-100"
tokenizer = AutoTokenizer.from_pretrained(model_name)
grpo_model = AutoModelForCausalLM.from_pretrained(model_path)
pt_model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
grpo_model.config.pad_token_id = tokenizer.pad_token_id
pt_model.config.pad_token_id = tokenizer.pad_token_id
grpo_model.eval()
pt_model.eval()
i = 1
for prompt in dataset:
inputs = tokenizer(prompt['prompt'], return_tensors="pt").to(grpo_model.device)
with torch.no_grad():
grpo_outputs = grpo_model.generate(
**inputs,
max_new_tokens=300,
do_sample=False,
)
pt_outputs = pt_model.generate(
**inputs,
max_new_tokens=300,
do_sample=False,
)
grpo_text = tokenizer.decode(grpo_outputs[0][inputs.input_ids.shape[-1]:], skip_special_tokens=True)
pt_text = tokenizer.decode(pt_outputs[0][inputs.input_ids.shape[-1]:], skip_special_tokens=True)
print(f"\n==== Prompt {i} ====\n")
print(f"(Length: {len(grpo_text)} characters)")
print(f"(Length: {len(pt_text)} characters)")
i += 1
输出结果显示,经过 GRPO 训练的模型的输出长度更接近 200 个字符,而原始模型的输出长度则差异较大。这表明 GRPO 成功地改变了模型的行为,使其更符合我们设定的目标。 例如:
==== Prompt 1 ====
(Length: 134 characters)
(Length: 1283 characters)
==== Prompt 2 ====
(Length: 150 characters)
(Length: 305 characters)
==== Prompt 3 ====
(Length: 109 characters)
(Length: 1233 characters)
==== Prompt 4 ====
(Length: 206 characters)
(Length: 986 characters)
==== Prompt 5 ====
(Length: 216 characters)
(Length: 801 characters)
==== Prompt 6 ====
(Length: 157 characters)
(Length: 945 characters)
==== Prompt 7 ====
(Length: 179 characters)
(Length: 1247 characters)
==== Prompt 8 ====
(Length: 339 characters)
(Length: 1006 characters)
==== Prompt 9 ====
(Length: 117 characters)
(Length: 970 characters)
==== Prompt 10 ====
(Length: 156 characters)
(Length: 880 characters)
这个案例证明,GRPO 通过 强化学习 的方式,能够有效地引导 大语言模型 (LLM) 生成符合特定要求的输出,例如控制摘要的长度,从而提升模型的实用性和可控性。
7. GRPO 的底层实现:损失函数解析
理解 GRPO 的损失函数对于深入了解其工作原理至关重要。简单来说,GRPO 的目标是最大化模型生成更有价值的响应的概率,同时限制模型偏离原始策略的程度。
以下是 GRPO 损失函数计算的关键步骤:
- 准备输入: 将提示和完成的 token ID 连接起来,形成完整的输入序列。
- 计算模型输出: 通过当前模型运行完整的输入序列,并提取每个 token 的 log-probabilities。
- (可选) 计算 KL 散度: 如果 beta 不为零,则使用参考模型的 log-probabilities 来计算每个 token 的 KL 散度。KL 散度衡量了当前策略与参考策略之间的差异。
- 计算策略比率并应用剪裁: 计算当前策略和旧策略之间的比率。并将比率剪裁到 [1 – εlow, 1 + εhigh] 范围内。剪裁可以防止策略更新过于激进。
- 计算每个 token 的损失: 计算两个候选损失:一个使用未剪裁的比率,另一个使用剪裁的比率。并选择两者中较小的一个。如果 beta 不为零,则将 KL 散度项添加到 token 损失中。
- 聚合总损失: 基于不同的策略 (例如 ‘grpo’, ‘bnpo’, ‘dr_grpo’) 聚合总损失。
- 计算和记录指标: 如果计算了 KL 散度,则记录平均 KL 散度。跟踪有多少 token 的比率被剪裁。
通过最小化 GRPO 的损失函数,我们可以训练模型生成更符合要求的响应,同时保持模型的稳定性和泛化能力。
8. 结论:GRPO 的价值与展望
GRPO 代表了 后训练 领域的一个重要进步,尤其是在旨在使用相对反馈来提高 推理能力、响应排名或输出质量时。它的能力、通过 KL 正则化限制与基本模型的偏差以及对多输出 推理 的支持使其非常适合改善复杂的 LLM 行为,例如 推理 和决策制定。正如 DeepSeek 等模型以及使用 TRL 进行的各种实验所见,与其他技术相比,GRPO 可以以最小的计算成本提供令人惊叹的改进。它使研究人员和从业人员能够改进 LLM 以生成更好的响应。
GRPO 在提升 大语言模型 (LLM) 的 推理能力 和可控性方面具有巨大的潜力。随着技术的不断发展,我们可以期待 GRPO 在更广泛的应用场景中发挥重要作用,例如智能客服、自动化写作和代码生成等。未来的研究方向包括:探索更有效的奖励函数设计方法、研究 GRPO 在不同类型 LLM 上的表现、以及将 GRPO 与其他 后训练技术 相结合。