已合并
补充 Ray PPO Readme #2111
AtomGit-Bot创建于 2024年12月27日
补充 Ray PPO Readme #2111
已合并
AtomGit-Bot创建于 2024年12月27日
从refs/pull/2111/head合入到master
共 6 个文件变更+171-3
@@ -490,7 +490,7 @@ MindSpeed-LLM当前已支持和预置大模型列表:
490 <td>【昇腾】</td>490 <td>【昇腾】</td>
491 </tr>491 </tr>
492 <tr>492 <tr>
493- <td rowspan="2">偏好对齐</td>493+ <td rowspan="3">偏好对齐</td>
494 <td><a href="./docs/features/offline_dpo.md">Offline DPO</a></td>494 <td><a href="./docs/features/offline_dpo.md">Offline DPO</a></td>
495 <td>✅</td>495 <td>✅</td>
496 <td>❌</td>496 <td>❌</td>
@@ -502,6 +502,12 @@ MindSpeed-LLM当前已支持和预置大模型列表:
502 <td>❌</td>502 <td>❌</td>
503 <td>【China Mobile Cloud】</td>503 <td>【China Mobile Cloud】</td>
504 </tr>504 </tr>
505+<tr>
506+ <td><a href="./docs/features/ray_ppo.md">Ray PPO</a></td>
507+ <td>✅</td>
508+ <td>❌</td>
509+ <td>【昇腾】</td>
510+ </tr>
505 <tr>511 <tr>
506 <td rowspan="2">奖励模型</td>512 <td rowspan="2">奖励模型</td>
507 <td><a href="./docs/features/outcome_reward_model.md">ORM</a></td>513 <td><a href="./docs/features/outcome_reward_model.md">ORM</a></td>
@@ -0,0 +1,162 @@
1+# 后训练方法 Ray PPO
2+ 
3+[PPO(Proximal Policy Optimization)](https://arxiv.org/abs/1707.06347)是一种强化对齐微调方法,常用于人类反馈强化学习(Reinforcement Learning with Human Feedback, RLHF)任务。
4+ 
5+PPO方法中包含了四个模型:Actor,Critic,Reference,Reward。其中Actor/Reference模型是经过预训练和指令微调(Supervised Fine-Tuning,SFT)得到的大语言模型,Critic和Reward是训练得到的奖励模型。PPO 的训练目标是使得 Actor 模型的回答可以更加符合人类偏好。
6+ 
7+# 使用说明
8+ 
9+## 环境配置
10+ 
11+配置MindSpeed-LLM基础环境: 参考[MindSpeed-LLM 使用指南-前期准备](https://gitee.com/ascend/MindSpeed-LLM/blob/master/docs/USER_GUIDE.md#%E5%89%8D%E6%9C%9F%E5%87%86%E5%A4%87)
12+ 
13+## 数据预处理
14+ 
15+数据集转换参考脚本:MindSpeed-LLM\examples\mcore\llama3\data_convert_llama3_ppo.sh
16+以 [descriptiveness 数据集](https://huggingface.co/datasets/trl-internal-testing/descriptiveness-sentiment-trl-style/tree/main/data) 为例。
17+ 
18+```bash
19+source /usr/local/Ascend/ascend-toolkit/set_env.sh
20+mkdir ./dataset/llama3-hf/
21+ 
22+python ./preprocess_data.py \
23+ --input ./dataset/descriptiveness-00000-of-00001.parquet \
24+ --tokenizer-name-or-path ./model_from_hf/llama3-hf/ \
25+ --output-prefix ./dataset/llama3-hf/descriptiveness \
26+ --workers 16 \
27+ --log-interval 1000 \
28+ --tokenizer-type PretrainedFromHF \
29+ --handler-name PPOAlpacaStyleInstructionHandler \
30+ --prompt-type llama3 \
31+ --map-keys '{"prompt":"prompt", "query":"", "response": "prompt", "system":""}'
32+```
33+ 
34+## 模型权重转换
35+ 
36+根据 PPO 算法要求,Actor 和 Reference 模型应该使用 SFT 微调后的模型进行初始化,Critic 和 Reward 模型应该使用奖励模型训练后的模型进行初始化。PPO算法模型权重均使用Megatron-mcore格式,其他格式的权重需要进行模型权重转换,具体可参考[MindSpeed-LLM 使用指南-权重转换](https://gitee.com/ascend/MindSpeed-LLM/blob/master/docs/USER_GUIDE.md#%E6%9D%83%E9%87%8D%E4%B8%8B%E8%BD%BD%E5%8F%8A%E8%BD%AC%E6%8D%A2)。
37+ 
38+## 启动方式
39+ 
40+### 单机
41+ 
42+通过 --config-name 传递选取的 config 文件名(不添加.yaml后缀),可以通过下列命令直接启动训练(Llama32 1B 模型可单机运行)。
43+目前已支持的配置文件放置在 configs/rlxf/ 文件夹下。配置文件的具体说明见下文。
44+ 
45+```bash
46+python ray_gpt.py --config-name ppo_trainer_llama32_1b
47+```
48+ 
49+### 多机
50+ 
51+多机运行程序时,需要首先进入对应目录,并激活conda或docker环境:
52+ 
53+```bash
54+cd MindSpeed-LLM
55+conda activate xxx
56+```
57+ 
58+然后,在主节点上启动 Ray 集群:
59+ 
60+```bash
61+# 配置最大文件描述符环境变量
62+ulimit -n 32768
63+# 创建一个集群,端口6344,dashboard端口8260,有8个NPU
64+ray start --head --port 6344 --dashboard-host=0.0.0.0 --dashboard-port=8260 --resources='{"NPU": 8}'
65+```
66+ 
67+随后,在其他节点加入主节点的集群
68+ 
69+```bash
70+# 配置最大文件描述符环境变量
71+ulimit -n 32768
72+# IP_ADDRESS 处填写主节点 IP 地址
73+ray start --address="IP_ADDRESS:6344" --resources='{"NPU": 8}'
74+```
75+ 
76+在完成 Ray 集群构建后,在主节点启动运行程序即可(Llama3 8B 模型可双机运行)
77+ 
78+```bash
79+python ray_gpt.py --config-name ppo_trainer_llama3_8b
80+```
81+ 
82+## 配置文件
83+ 
84+由于 PPO 训练过程中涉及 4 个模型,通过将模型参数和训练配置解耦的层级化参数配置,来简化 PPO 训练的参数配置过程。RLXF 训练涉及到的所有配置文件均存储在 configs/rlxf 路径下,其中 model 文件夹下存储了模型结构相关的配置文件,PPO训练相关的模型参数文件以ppo_trainer_{模型名}.yaml方式命名。
85+ 
86+在每个 ppo_trainer 配置文件中,需要包含defaults,training,resource_pool,algorithm等字段,以及 PPO 训练过程中涉及到的 4 个角色 actor,critic,reward,ref的配置。其中:
87+ 
88+1. defaults 负责引入模型配置文件,在 defaults 中应列举本配置文件中所需要用到的所有模型配置,模型配置可以在下方四个角色的具体配置中通过 model 字段进行选择。
89+2. training 字段设置的参数为所有 4 个角色通用的默认参数,这些参数可以在下方进一步被角色的单独配置所覆盖。
90+3. resource_pool 字段指定了各个角色所需的 NPU 资源数量。
91+4. algorithm 字段配置计算PPO中advantages算法的相关参数。
92+5. actor,critic,reward,ref 字段分别指定了PPO算法中四个角色训练相关的参数配置。
93+ 
94+## 参数解析
95+ 
96+相较于普通模型训练,PPO增加一些特殊参数:
97+ 
98+### `training:`
99+ 
100+* `stage`:用于指定训练算法,使用 Ray PPO 训练须设置为`ray_ppo`;
101+ 
102+### `actor_rollout:`
103+ 
104+* `do_sample`:控制 Actor 模型进行推理时是否采样,默认为 False ;
105+* `ppo_mini_batch_size`:Actor 模型的 mini_batch_size,默认为1;
106+* `max_prompt_length`:PPO 训练中最大 prompt 长度,默认为512;
107+* `num_samples_per_step`:Actor 推理时每个step的推理样本数量,默认为1;
108+* `ppo_epochs`:Actor 训练对同一批经验数据的重复次数,默认为1;
109+* `clip_ratio`:Actor模型训练计算损失函数时的clip比例,默认为0.2;
110+* `shuffle_minibatch`:Actor 训练时是否对 minibatch 进行 shuffle,默认为 False;
111+* `num_gpus_for_train` :Actor 模型分配给训练部分的显卡数量;
112+* `num_gpus_for_infer` :Actor 模型分配给推理部分的显卡数量;
113+ 
114+### `critic:`
115+ 
116+* `cliprange_value`:Critic 模型计算损失函数时 clip 范围,默认为3.0;
117+* `critic_mini_batch_size`:Critic 模型设置的 mini_batch_size,默认为1;
118+* `critic_update_epochs`:Critic 训练对同一批经验数据的重复次数,默认为1;
119+ 
120+### `algorithm:`
121+ 
122+* `adv_estimator`:advantages计算的方式,通常采用gae(广义优势估计Generalized Advantage Estimation, GAE);
123+* `gamma`:计算 advantage 时的折扣因子;
124+* `lam`:GAE 优势计算的 lambda 值;
125+* `kl_penalty`:KL 散度计算方式;
126+* `kl_ctrl:`
127+ * `kl_coef`:施加 KL 散度惩罚的系数;
128+ * `type`:KL 散度惩罚的系数类型;
129+* `missing_eos_penalty`:缺少序列结束符EOS时的惩罚系数;
130+ 
131+### `resource_pool:`
132+ 
133+* `actor_rollout`:给 Actor 模型训练和推理总共分配的显卡数量;
134+* `ref`:给 Reference 模型分配的显卡数量;
135+* `critic`:给 Critic 模型分配的显卡数量;
136+* `reward`:给 Reward 模型分配的显卡数量;
137+ 
138+# 精度对比
139+ 
140+我们与 HuggingFace 的强化学习开源仓库 [TRL](https://github.com/huggingface/trl/) 进行了精度对比,来辅助验证算法实现的正确性。为了与基准方法进行精度对齐,在 Actor 推理时采用贪婪(greedy)策略去除随机性,训练过程中的 critic loss和 actor loss对比如下图所示。
141+ 
142+<figure>
143+ <img src="../../sources/images/ray_ppo/llama32_1b_trl_compare_generate_response.png" alt="Image description" />
144+ <figcaption style="text-align: center;">未固定 responses 时 loss 对比图 (左为 actor loss,右为 critc loss)</figcaption>
145+</figure>
146+ 
147+然而,由于 greedy 方法的策略为选取 logits 最大的 token,当如果两个 token 的 logits 值十分接近时,可能会导致选取的 token 的结果产生偏差。这种误差会被多次迭代逐步累计放大,最终影响到 loss 精度对齐。
148+ 
149+因此,我们额外补充了固定 responses 方式进行精度对齐的实验。可以看到,固定 responses 后 loss 能够较好地实现对齐。
150+ 
151+<figure>
152+ <img src="../../sources/images/ray_ppo/llama32_1b_trl_compare_fixed_response.png" alt="Image description" />
153+ <figcaption style="text-align: center;">固定 responses 后的 loss 对比图 (左为 actor loss,右为 critc loss)</figcaption>
154+</figure>
155+ 
156+注: 为了验证 actor loss 的精度对齐效果,这里并未直接对比 PPO 算法中记录的 actor loss。这是由于 PPO 算法在计算 advantages 时,为保证算法的稳定性,会在 Actor 训练过程中在 minibatch 间做白化操作(将其分布的均值调整为0,方差调整为1)。这导致 Actor 虽然在进行梯度更新时使用每个 minibatch 计算的 loss,但记录下来的 minibatch 间的 loss 均值接近于 0 。因此,我们选择了记录 Actor 每个 minibatch loss 绝对值的均值,来验证精度对齐效果。
157+ 
158+ 
159+# 参考文献
160+ 
161+[PPO](https://arxiv.org/abs/1707.06347)
162+ 
@@ -90,7 +90,7 @@ class MegatronPPOReference(BaseTrainer):
90 90 
91 self.timers('model-setup', log_level=0).start(barrier=True)91 self.timers('model-setup', log_level=0).start(barrier=True)
92 92 
93- self.model = get_model(self.model_provider, self.model_type)93+ self.model = get_model(self.model_provider, self.model_type, wrap_with_ddp=False)
94 unwrapped_model = unwrap_model(self.model)94 unwrapped_model = unwrap_model(self.model)
95 if self.args.stage == "ray_online_dpo":95 if self.args.stage == "ray_online_dpo":
96 self.args.micro_batch_size *= 296 self.args.micro_batch_size *= 2
@@ -93,7 +93,7 @@ class MegatronPPORM(RMTrainer):
93 self.args.micro_batch_size *= 293 self.args.micro_batch_size *= 2
94 self.timers('model-setup', log_level=0).start(barrier=True)94 self.timers('model-setup', log_level=0).start(barrier=True)
95 95 
96- model = get_model(self.model_provider, self.model_type)96+ model = get_model(self.model_provider, self.model_type, wrap_with_ddp=False)
97 unwrapped_model = unwrap_model(model)97 unwrapped_model = unwrap_model(model)
98 98 
99 if self.args.load is not None or self.args.pretrained_checkpoint is not None:99 if self.args.load is not None or self.args.pretrained_checkpoint is not None: