Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitingore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
simple_GRPO/__pycache__/
106 changes: 106 additions & 0 deletions Auto_Program/my_progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Prompt
system_prompt_0312.txt 包含一个完整的示例,而 system_prompt_0312_zero.txt 没有示例。
system_prompt_0312.txt 是一个 Few-Shot (实际上是 One-Shot) 提示,因为它提供了一个样本来指导模型。
system_prompt_0312_zero.txt 是一个 Zero-Shot 提示,它只给出指令,不提供样本。

这两个文件都定义了与 LLM 交互的格式规范,特别是针对需要模型在推理过程中生成并利用代码执行结果的任务。它们在 hjy_grpo_program.py 脚本中被用作 系统提示,在向模型发起请求时放在最前面,用以设定模型的行为模式。
指导模型生成: 它们告诉模型应该如何构建其响应,包括思考过程、代码嵌入、结果声明和最终答案的格式。
支持代码执行流程: <program> 标签和结果声明的格式是 hjy_grpo_program.py 中 get_completions 和 run 函数能够正确提取代码、执行代码并将结果反馈给模型的关键。


# RL-GRPO

## 主要组件

### 配置文件 (config.py)
- 定义所有训练参数和配置
- 不包含执行逻辑,而是定义了两组重要的配置:
- `train_config`: 训练相关的超参数和设置
- 路径配置: `model_path`, `save_path`, `data_path`
- Weights & Biases 日志配置: `wandb_*`
- 生成设备: `gen_device` (指定用于文本生成的 GPU 设备 ID)
- 算法参数:
- `beta`: GRPO/PPO 中 KL 散度惩罚项的系数
- `clip_param`: PPO/GRPO 损失函数中的裁剪参数
- 批次设置:
- `Q_batch_size`, `num_pre_Q`: 每次采样的问题数量和每个问题生成的候选答案数量
- `train_batch_size`: 实际用于训练更新的批次大小
- 更新控制: `gen_update_steps` (每隔多少训练步数更新生成模型)
- 算法开关: `compute_gen_logps` (是否计算生成模型的 logprobs)
- 服务器配置: `ref_server`, `port`
- DeepSpeed 配置: `ds_config` (包含 ZeRO 优化、梯度累积、优化器等设置)

### Reference Server (ref_server.py)
- 作为独立的 Reference Server 运行
- 负责计算参考模型的对数概率并作为数据中转站
- 核心功能:
- 加载参考模型: 固定的 LLM 用于计算参考 logprobs
- 接收数据: 通过 `/upload` POST 端点接收生成数据
- 计算参考 Logprobs: 使用固定模型计算每个 token 的对数概率
- 缓存与分发: 将数据与 ref_logps 打包放入队列
- 提供数据: 通过 `/get` 端点向训练进程提供完整数据包
- 数据流: gen_worker → /upload → raw_queue → 参考模型计算 → result_queue → /get → 主训练进程

### 主训练与生成脚本 (hjy_grpo_program.py)
包含主要的训练逻辑(模型更新)和独立的生成逻辑(数据收集)

#### 生成工作进程 (gen_worker)
- 启动: 在 rank 0 进程中使用 `torch.multiprocessing` 启动
- 模型加载: 使用 vLLM 加载模型以高效生成文本
- 核心循环:
- 从数据集采样问题
- 周期性更新模型权重
- 生成答案:
- 使用 system_prompt 格式化输入
- 生成多个候选答案
- 递归代码执行: 生成代码 → 执行 → 将结果反馈给模型 → 继续生成
- 计算奖励:
- `reward_correct`: 验证答案正确性
- `reward_format`: 检查输出格式
- `call_python`: 根据代码执行成功次数给予奖励
- 计算生成 Logprobs (如果启用)
- 发送数据到 ref_server

#### 主训练进程 (Main Process)
- 分布式设置: 使用 DeepSpeed 初始化分布式环境
- 模型加载: 加载基础模型并用 DeepSpeed 包装
- 训练循环:
- 获取批次数据: 从 ref_server 获取处理好的数据
- 计算 GRPO 损失:
- 对输入进行前向传播获取当前策略的 logits 和 logprobs
- 计算 KL 散度惩罚: 当前策略与参考策略之间
- 计算策略比率: 当前策略与生成策略之间
- 应用 PPO 裁剪目标函数
- 最终损失: 结合裁剪目标和 KL 散度惩罚
- 反向传播与优化
- 周期性更新生成模型
- 日志记录与保存

## 整体训练流程

1. **启动**:
- 启动 ref_server.py
- 启动 hjy_grpo_program.py (初始化分布式环境并启动 gen_worker)

2. **收集数据** (gen_worker):
- 使用 vLLM 生成带有代码执行的答案
- 计算奖励和生成 logprobs
- 将数据发送到 ref_server

3. **参考 Logprobs 计算** (ref_server):
- 接收数据并计算参考 logprobs
- 将完整数据包放入队列

4. **模型训练** (主进程):
- 获取完整数据包
- 计算 GRPO 损失
- 使用 DeepSpeed 更新模型参数

5. **模型同步**:
- 主进程定期将更新后的模型权重发送给 gen_worker

6. **循环**:
- 步骤 2-5 持续进行指定的训练步数

这个系统通过分离生成和训练、使用参考模型稳定训练、利用 vLLM 加速生成以及 DeepSpeed 支持分布式训练,实现了一个高效且复杂的 RL 训练系统,专门用于优化需要代码执行能力的 LLM。

93 changes: 93 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 🚀🚀🚀 simple_GRPO 🚀🚀🚀

一个简单的GRPO实现,用于复现类r1的LLM思考能力。本项目是一个简单的开源实现,使用了来自Hugging Face的trl的核心损失计算公式。

我们构建了最简单的代码库,支持:
- 节省GPU内存,实现可行且高效的训练
- 从教学角度快速理解GRPO等RL过程
- 快速尝试多种改进,如多答案生成、重组、KL惩罚和参数调优
- 在模型训练早期观察到"顿悟时刻"

## ✨最新更新
- 2025/02/19: 添加了loss triton实现,略有加速,可选择不使用。详见*simple_grpo_v1*文件夹
- 2025/02/19: 添加了重组版本,在ref_server上实现了生成数据采样。详见*regroup_ver*文件夹
- 2025/02/27: 添加了vllm包以加速推理
- 2025/03/24: 添加了reinforce++算法,使用方式与之前相同

## 🌟 特点

### 💡 简洁性
项目代码简单,仅有约200行代码分布在2个文件中。只依赖_deepspeed_和_torch_等标准库,无需依赖ray等。设计支持更复杂的干预。

### 🤖 拆分的参考模型
参考模型部分被解耦,允许在不同GPU上运行(甚至可在配有4090的不同机器上运行)。避免了将参考模型和训练模型放在同一GPU上,防止torch多进程创建多个副本,实现在80G A800上训练7B模型。

### 💃 性能
在1台A800 GPU上训练不到1小时完成。Qwen2.5-7B和Qwen2.5-3B均在前30步优化中出现"顿悟时刻"。

### 🥳 核心损失计算
损失计算公式基于Hugging Face的trl。感谢Hugging Face的贡献。

## 🙌 环境
运行环境在requirements.txt中,执行:
```bash
pip install -r requirements.txt
```
至少需要两个GPU。

## 使用方法
### 如果有三个或更多GPU,您将有更好的选择!
运行以下命令:
```bash
CUDA_VISIBLE_DEVICES=7 python ref_server.py
```
这仅使用一个GPU来收集并运行参考模型。

在*grpo_vllm_one.py*中,设置相对于下一步可见设备的生成设备索引:
```bash
gen_device = 1
```
然后,打开另一个bash:
```bash
CUDA_VISIBLE_DEVICES=2,3,4,5,6 deepspeed grpo_vllm_one.py
```

## ✨ 实验结果

1. 运行环境
- 硬件设置:2×A800 (80GB) GPU
- 配置:
- 训练:1个GPU,采用Zero-Stage 2优化
- 推理:专用1个GPU(兼容3090/4090)

2. 训练性能

| 模型 | 步数 | 时间 |
|------------|------|------------|
| Qwen2.5-3B | 60 | 12分34秒 |
| Qwen2.5-7B | 60 | 16分40秒 |

2.1 Qwen2.5-3B
- 准确率:
- 5步优化后稳定在60%以上
- 峰值约70%
- 格式合规性:
- 30步后达到约100%

2.2 Qwen2.5-7B
- 准确率:
- 整个训练过程中保持>90%
- 格式合规性:
- 30步内达到100%
- 收敛性:两项指标均显示快速收敛

## 顿悟时刻
1. Qwen2.5-3B(第20步)模型在思考过程中意识到初始计算错误,开始重新考虑问题。

2. Qwen2.5-7B(第20步)模型发现计算结果与总数不符,自我校正并重新评估。

## 😊 待办事项
- 答案生成可能无效,因为一个组可能包含全部错误答案或全部正确答案。需要组重组和更好的答案生成
- 如果生成长思考链(CoT),GPU内存仍然紧张。必须分割组以使批次更小

我们已经实现并正在测试这些功能,很快将推出。
Binary file added __pycache__/ref_server.cpython-310.pyc
Binary file not shown.
4 changes: 3 additions & 1 deletion grpo_vllm_one.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#实际执行 GRPO (Group Reward Policy Optimization) 训练过程的主程序。它负责管理正在被训练的策略模型 (policy model)。
# 它使用 VLLM (Vectorized Language Model) 库来生成文本,并使用 DeepSpeed 库来加速训练过程。
from transformers import AutoTokenizer, AutoModelForCausalLM
import json, os, shutil, re, random, io, requests, ctypes, sys, time, struct
import torch
Expand All @@ -8,7 +10,7 @@
from tqdm import tqdm
os.environ['TOKENIZERS_PARALLELISM'] = 'true'

model_path = "/data2/Qwen/Qwen2.5-7B"
model_path = "/data1/hao/models/FILM-7B/"
gen_device = 4 # GPU device for generation, don't put it in CUDA_VISIBLE_DEVICES
beta = 0.04
all_steps = 1000
Expand Down
73 changes: 73 additions & 0 deletions my_progtess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
conda activate simple_grpo


# Auto_Program
Read file: Learn/Train/simple_GRPO/Auto_Program/system_prompt_0312_zero.txt

Read file: Learn/Train/simple_GRPO/Auto_Program/system_prompt_0312.txt
好的,从 `system_prompt_0312.txt` 和 `system_prompt_0312_zero.txt` 的内容来看,这个任务确实涉及一种形式的**代码生成/调用**,但更准确地说,是**让 LLM 在其思考过程中生成并“执行”(模拟执行,并由外部系统提供结果)Python 代码片段**。

关键点:

1. **特定格式:** 模型被要求将思考过程放在 `<think>` 标签内,最终答案放在 `<answer>` 标签内。
2. **代码嵌入:** 在 `<think>` 标签内部,如果需要计算,模型应生成 Python 代码块,并将其包裹在 `<program>\n\`\`\`python\n...\n\`\`\`\n</program>` 结构中。
3. **模拟执行与结果注入:** 提示明确要求模型在代码块之后写上 "The result of executing this Python code is: \[code output]"。这强烈暗示了训练流程中存在一个外部组件(可能是 `ref_server.py` 或 `hjy_grpo_program.py` 内的逻辑)会**实际执行**模型生成的 Python 代码,并将执行结果(`[code output]` 部分)反馈给模型,或者在评估/奖励计算中使用这个结果。
4. **逐步推理:** 模型需要根据代码执行的结果继续其推理过程。

这与标准的“工具调用”范式非常相似,其中“工具”是 **Python 代码执行器**。模型学习的不是调用一个抽象的 API 名称,而是直接生成可执行的代码。
`config.py` 主要定义了训练过程的超参数,如批大小、学习率、保存路径、WandB 配置、Deepspeed 配置等。

与我们讨论的“代码执行”相关的关键配置是:

* `"ref_server": "http://localhost:59807"`: 指定了参考模型服务(`ref_server.py`)的地址。这证实了训练脚本会与参考模型进行通信。
* `"port": 59807`: 参考模型服务的端口。

好的,`hjy_grpo_program.py` 的开头部分(前 200 行)揭示了关键的实现细节:

1. **代码执行逻辑 (`run` 函数, L122-L151):**
* 这个函数明确地实现了从模型的生成文本中**提取 Python 代码块**(位于 ` ```python ... ``` ` 之间)。
* 它使用 `exec()` 来**执行提取的代码**。
* **关键:** 它通过 `io.StringIO` 和 `sys.stdout` 重定向来捕获代码的**标准输出** (`print` 语句的结果)。
* 它包含**超时处理** (`signal.alarm`) 和基本的**异常捕获**,将错误信息作为字符串返回。
* 如果代码没有输出或找不到代码块,它会返回错误信息。

2. **递归生成与代码注入 (`get_completions` 函数, L153-L172):**
* 这个函数使用 `vllm` 生成模型的续写。
* **核心机制:** 它设定了一个停止标志 `stop_sentences = "The result of executing this Python code is:"`。
* 当模型的生成文本包含这个停止标志时,它会:
* 调用 `run()` 函数执行刚刚生成的代码块。
* 将 `run()` 返回的执行结果(或错误信息)**追加**到当前生成的文本后面。
* 将追加了结果的文本作为新的提示,**递归调用 `get_completions`** 继续生成。
* 这实现了系统提示中要求的“在代码块后明确说明执行结果”的流程,并将结果反馈给模型用于后续推理。

3. **生成工作进程 (`gen_worker` 函数, L100):**
* 这个函数运行在一个单独的进程中,负责使用 `vllm` 模型生成答案 (`gen_answers` 函数调用 `get_completions`)。
* 它**计算奖励分数**:
* `reward_correct` (L190): 比较最终答案(`<answer>` 标签内 `\boxed{}` 部分)与基准答案 (`ground_truth`) 的正确性。它使用了 `math_verify` 库,暗示了任务可能与数学问题求解相关(如 GSM8K 数据集)。
* `reward_format` (L208, 代码未完全显示): 可能检查生成的文本是否符合 `<think>/<program>/<answer>` 的整体格式要求。
* 它将生成的数据(prompt, answer, rewards, logprobs 等)**发送给参考模型服务器** (`ref_server`) 存储,供主训练进程 (`GRPO_step`) 拉取。

4. **GRPO 训练步骤 (`GRPO_step` 函数, L67-L95):**
* 从 `ref_server` 获取批次数据 (`get_batch`, L43),包含输入、奖励、参考模型 logprobs 等。
* 计算当前策略模型(正在训练的模型)的 logprobs。
* 计算 KL 散度惩罚 (`per_token_kl`),防止模型偏离太远。
* 计算 GRPO 损失,结合了优势(从奖励计算得出)、策略模型 logprobs、生成模型 logprobs(如果 `compute_gen_logps` 为 True)和 KL 惩罚。

5. **参考服务器角色 (`ref_server.py`):**
* 根据 `hjy_grpo_program.py` 中的交互方式 (`get_batch` 拉取数据,`gen_worker` 推送数据),`Auto_Program` 目录下的 `ref_server.py` 主要扮演**数据存储和分发**的角色。它存储由 `gen_worker` 生成并评估好的样本(包含提示、生成的答案、计算好的奖励、logprobs 等),然后按批次提供给主训练进程进行 GRPO 更新。它本身**不执行** Python 代码或计算奖励。

**总结与回答:**

是的,`Learn/Train/simple_GRPO/Auto_Program` 文件夹确实是在**训练模型进行一种特定形式的“工具调用”**,这里的“工具”是**Python 代码执行器**。

**工作原理:**

1. **任务定义:** 模型被要求遵循 `<think><program>...</program>...</think><answer>...</answer>` 的格式解决问题(可能是数学题)。
2. **代码生成:** 模型在 `<think>` 块内生成 Python 代码片段(`<program>` 标签内)。
3. **代码执行与结果注入:** `hjy_grpo_program.py` 中的 `gen_worker` 进程使用 `vllm` 生成文本,当遇到特定停止标志时,提取并执行 Python 代码 (`run` 函数),捕获其输出。
4. **递归生成:** 将代码执行结果追加到生成文本后,作为新的提示继续让模型生成,模拟推理过程。
5. **奖励计算:** `gen_worker` 根据最终答案的正确性 (`reward_correct`) 和格式合规性 (`reward_format`) 计算奖励。
6. **数据存储:** `gen_worker` 将生成的样本(包含提示、答案、奖励、logprobs)发送到 `ref_server.py` 存储。
7. **GRPO 训练:** 主训练进程从 `ref_server.py` 获取带奖励的样本批次,并执行 `GRPO_step` 来更新模型参数,鼓励模型生成能得出正确答案且格式正确的代码和推理链。

这种方法巧妙地将代码执行能力集成到了模型的思考链中,并通过强化学习(GRPO)进行优化。
4 changes: 2 additions & 2 deletions ref_server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

# 运行一个固定的参考模型 (reference model),并提供一个网络服务来计算由当前训练模型生成的序列的对数概率 (log probabilities)
import json, os, shutil, re, random, io, time
import torch

Expand Down Expand Up @@ -33,7 +33,7 @@ def bytes_list_to_list(b):
import bottle, threading, queue
os.environ['TOKENIZERS_PARALLELISM'] = 'true'

model_path = "/data2/Qwen/Qwen2.5-7B"
model_path = "/data1/hao/models/FILM-7B/"

ref_model = AutoModelForCausalLM.from_pretrained(model_path,
torch_dtype=torch.bfloat16, _attn_implementation="sdpa").to('cuda')
Expand Down