diff --git a/.gitingore b/.gitingore new file mode 100644 index 0000000..7ce12e5 --- /dev/null +++ b/.gitingore @@ -0,0 +1,2 @@ +__pycache__/ +simple_GRPO/__pycache__/ diff --git a/Auto_Program/my_progress.md b/Auto_Program/my_progress.md new file mode 100644 index 0000000..a901980 --- /dev/null +++ b/Auto_Program/my_progress.md @@ -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 脚本中被用作 系统提示,在向模型发起请求时放在最前面,用以设定模型的行为模式。 +指导模型生成: 它们告诉模型应该如何构建其响应,包括思考过程、代码嵌入、结果声明和最终答案的格式。 +支持代码执行流程: 标签和结果声明的格式是 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。 + diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..ad9335b --- /dev/null +++ b/README.zh-CN.md @@ -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内存仍然紧张。必须分割组以使批次更小 + +我们已经实现并正在测试这些功能,很快将推出。 \ No newline at end of file diff --git a/__pycache__/ref_server.cpython-310.pyc b/__pycache__/ref_server.cpython-310.pyc new file mode 100644 index 0000000..df70148 Binary files /dev/null and b/__pycache__/ref_server.cpython-310.pyc differ diff --git a/grpo_vllm_one.py b/grpo_vllm_one.py index a0f1788..219a619 100644 --- a/grpo_vllm_one.py +++ b/grpo_vllm_one.py @@ -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 @@ -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 diff --git a/my_progtess.md b/my_progtess.md new file mode 100644 index 0000000..545f3ff --- /dev/null +++ b/my_progtess.md @@ -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. **特定格式:** 模型被要求将思考过程放在 `` 标签内,最终答案放在 `` 标签内。 +2. **代码嵌入:** 在 `` 标签内部,如果需要计算,模型应生成 Python 代码块,并将其包裹在 `\n\`\`\`python\n...\n\`\`\`\n` 结构中。 +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): 比较最终答案(`` 标签内 `\boxed{}` 部分)与基准答案 (`ground_truth`) 的正确性。它使用了 `math_verify` 库,暗示了任务可能与数学问题求解相关(如 GSM8K 数据集)。 + * `reward_format` (L208, 代码未完全显示): 可能检查生成的文本是否符合 `//` 的整体格式要求。 + * 它将生成的数据(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. **任务定义:** 模型被要求遵循 `.........` 的格式解决问题(可能是数学题)。 +2. **代码生成:** 模型在 `` 块内生成 Python 代码片段(`` 标签内)。 +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)进行优化。 diff --git a/ref_server.py b/ref_server.py index 883df6d..9bb465d 100644 --- a/ref_server.py +++ b/ref_server.py @@ -1,4 +1,4 @@ - +# 运行一个固定的参考模型 (reference model),并提供一个网络服务来计算由当前训练模型生成的序列的对数概率 (log probabilities) import json, os, shutil, re, random, io, time import torch @@ -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')