已合并
【Ray Online DPO】添加Online DPO ST用例,并补充readme #2137
ningbenzhe1创建于 2025年1月8日
【Ray Online DPO】添加Online DPO ST用例,并补充readme #2137
已合并
ningbenzhe1创建于 2025年1月8日
从refs/pull/2137/head合入到master
共 10 个文件变更+302-31
@@ -0,0 +1,133 @@
1+# 后训练方法 Ray Online DPO
2+ 
3+Online Direct Preference Optimization (Online DPO) 是 Direct Preference Optimization (DPO) 的一种扩展或变体,旨在通过 在线学习 的方式进一步优化大型语言模型(LLMs)。DPO 是一种基于人类偏好数据的训练方法,而 Online DPO 则专注于在 动态、实时 的环境中使用偏好数据来持续改进模型。
4+ 
5+Online DPO方法中包含了三个模型:Actor,Reference,Reward。其中Actor/Reference模型是经过预训练和指令微调(Supervised Fine-Tuning,SFT)得到的大语言模型,Reward是训练得到的奖励模型。Online DPO 的训练目标是使得 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": "", "system":""}'
32+```
33+ 
34+## 模型权重转换
35+ 
36+根据 Online DPO 算法要求,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 online_dpo_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+# 创建一个集群,端口6344,dashboard端口8260,有8个NPU
62+ray start --head --port 6344 --dashboard-host=0.0.0.0 --dashboard-port=8260 --resources='{"NPU": 8}'
63+```
64+ 
65+随后,在其他节点加入主节点的集群
66+ 
67+```bash
68+# IP_ADDRESS 处填写主节点 IP 地址
69+ray start --address="IP_ADDRESS:6344" --resources='{"NPU": 8}'
70+```
71+ 
72+在完成 Ray 集群构建后,在主节点启动运行程序即可(Llama3 8B 模型可双机运行)
73+ 
74+```bash
75+python ray_gpt.py --config-name online_dpo_trainer_llama3_8b
76+```
77+ 
78+## 配置文件
79+ 
80+由于 Online DPO 训练过程中涉及 3 个模型,通过将模型参数和训练配置解耦的层级化参数配置,来简化 Online DPO 训练的参数配置过程。RLXF 训练涉及到的所有配置文件均存储在 configs/rlxf 路径下,其中 model 文件夹下存储了模型结构相关的配置文件,Online DPO训练相关的模型参数文件以online_dpo_trainer_{模型名}.yaml方式命名。
81+ 
82+在每个 online_dpo_trainer 配置文件中,需要包含defaults,training,resource_pool,algorithm等字段,以及 Online DPO 训练过程中涉及到的 3 个角色 actor,reward,ref的配置。其中:
83+ 
84+1. defaults 负责引入模型配置文件,在 defaults 中应列举本配置文件中所需要用到的所有模型配置,模型配置可以在下方3个角色的具体配置中通过 model 字段进行选择。
85+2. training 字段设置的参数为所有 3 个角色通用的默认参数,这些参数可以在下方进一步被角色的单独配置所覆盖。
86+3. resource_pool 字段指定了各个角色所需的 NPU 资源数量。
87+4. algorithm 字段配置计算PPO中advantages算法的相关参数。
88+5. actor,reward,ref 字段分别指定了PPO算法中四个角色训练相关的参数配置。
89+ 
90+## 参数解析
91+ 
92+相较于普通模型训练,PPO增加一些特殊参数:
93+ 
94+### `training:`
95+ 
96+* `stage`:用于指定训练算法,使用 Ray Online DPO 训练须设置为`ray_online_dpo`;
97+ 
98+### `actor_rollout:`
99+ 
100+* `do_sample`:控制 Actor 模型进行推理时是否采样,默认为 False,Online DPO 需要设置为True ;
101+* `ppo_mini_batch_size`:Actor 模型的 mini_batch_size,默认为1;
102+* `max_prompt_length`:PPO 训练中最大 prompt 长度,默认为512;
103+* `num_samples_per_step`:Actor 推理时每个step的推理样本数量,默认为1;
104+* `ppo_epochs`:Actor 训练对同一批经验数据的重复次数,默认为1;
105+* `clip_ratio`:Actor模型训练计算损失函数时的clip比例,默认为0.2;
106+* `shuffle_minibatch`:Actor 训练时是否对 minibatch 进行 shuffle,默认为 False;
107+* `num_gpus_for_train` :Actor 模型分配给训练部分的显卡数量;
108+* `num_gpus_for_infer` :Actor 模型分配给推理部分的显卡数量;
109+* `missing_eos_penalty`:缺少序列结束符EOS时的惩罚系数;
110+ 
111+### `algorithm:`
112+ 
113+* `adv_estimator`:advantages计算的方式,通常采用gae(广义优势估计Generalized Advantage Estimation, GAE);
114+* `gamma`:计算 advantage 时的折扣因子;
115+* `lam`:GAE 优势计算的 lambda 值;
116+* `kl_penalty`:KL 散度计算方式;
117+* `kl_ctrl:`
118+ * `kl_coef`:施加 KL 散度惩罚的系数;
119+ * `type`:KL 散度惩罚的系数类型;
120+* `missing_eos_penalty`:缺少序列结束符EOS时的惩罚系数;
121+ 
122+### `resource_pool:`
123+ 
124+* `actor_rollout`:给 Actor 模型训练和推理总共分配的显卡数量;
125+* `ref`:给 Reference 模型分配的显卡数量;
126+* `reward`:给 Reward 模型分配的显卡数量;
127+ 
128+# 精度对比
129+ 
130+我们与 HuggingFace 的强化学习开源仓库 [TRL](https://github.com/huggingface/trl/) 进行了精度对比,来辅助验证算法实现的正确性。因为 Online DPO 1Q2A的特性需求,推理状态do sample 设置为 True,为了与基准方法进行精度对齐,在 Actor 推理时固定 responses 方式进行精度对齐的实验。可以看到,固定 responses 后 loss 能够较好地实现对齐。
131+ 
132+![online_dpo_loss_compare.png](../../sources/images/online_dpo/online_dpo_loss_compare.png)
133+ 
@@ -3,11 +3,13 @@ import hashlib
3 3 
4import torch4import torch
5import torch.distributed as dist5import torch.distributed as dist
6-import mindspeed_llm.tasks.posttrain.rlxf.training.parallel_state as ps
7from megatron.training import get_args6from megatron.training import get_args
7+from megatron.core import parallel_state as mpu
8+ 
9+import mindspeed_llm.tasks.posttrain.rlxf.training.parallel_state as ps
10+ 
8 11 
9RECEIVE_PARAM_NUMS = None12RECEIVE_PARAM_NUMS = None
10- 
11MODEL_SEND_GROUP = None13MODEL_SEND_GROUP = None
12MODEL_RECEIVE_GROUPS = []14MODEL_RECEIVE_GROUPS = []
13 15 
@@ -125,7 +127,13 @@ def send_model_to_infer_model(moudle: torch.nn.Module):
125 model_send_group = get_model_send_group()127 model_send_group = get_model_send_group()
126 if args.md5_validate:128 if args.md5_validate:
127 hash_value = hashlib.md5()129 hash_value = hashlib.md5()
130+ 
131+ is_reuse_output_weights = (not args.untie_embeddings_and_output_weights and
132+ args.pipeline_model_parallel_size >= 2 and
133+ mpu.is_pipeline_last_stage(ignore_virtual=True))
128 for name, param in moudle.named_parameters():134 for name, param in moudle.named_parameters():
135+ if is_reuse_output_weights and 'output_layer.weight' in name:
136+ continue
129 param_info_data = param.data137 param_info_data = param.data
130 dist.broadcast(param_info_data, group=model_send_group, src=dist.get_rank(), async_op=True)138 dist.broadcast(param_info_data, group=model_send_group, src=dist.get_rank(), async_op=True)
131 if args.md5_validate:139 if args.md5_validate:
@@ -175,6 +183,17 @@ def recv_model_from_train_model(moudle: torch.nn.Module):
175 flag = True183 flag = True
176 idx += 1184 idx += 1
177 185 
186+ if cur_num != 0:
187+ if args.md5_validate:
188+ md5_tensor = torch.tensor([int(h, 16) for h in hash_value.hexdigest()], dtype=torch.int64,
189+ device=torch.cuda.current_device())
190+ md5_tensor_src = torch.zeros_like(md5_tensor, dtype=torch.int64, device=torch.cuda.current_device())
191+ dist.broadcast(md5_tensor_src, group=cur_group[1], src=cur_group[0], async_op=False)
192+ if torch.equal(md5_tensor_src, md5_tensor):
193+ print("MD5 Hash: The weights of the two models match.")
194+ else:
195+ print("MD5 Hash: The weights of the two models do not match.")
196+ 
178 197 
179def run_auto_mapping(model):198def run_auto_mapping(model):
180 if ps.in_mg2_inference_group():199 if ps.in_mg2_inference_group():
@@ -219,6 +219,7 @@ class PPOActorInferWorker(BaseTrainer):
219 self.timers('train/valid/test-data-iterators-setup', log_level=0).start(219 self.timers('train/valid/test-data-iterators-setup', log_level=0).start(
220 barrier=True)220 barrier=True)
221 221 
222+ self.args.num_layer_list = None
222 self.args.micro_batch_size = 1223 self.args.micro_batch_size = 1
223 self.args.sequence_parallel = False224 self.args.sequence_parallel = False
224 self.inf_model = MegatronModuleForCausalLM.from_pretrained(225 self.inf_model = MegatronModuleForCausalLM.from_pretrained(
@@ -273,9 +274,11 @@ class PPOActorInferWorker(BaseTrainer):
273 for i in range(num_infer_steps):274 for i in range(num_infer_steps):
274 for j in range(args.num_samples_per_step):275 for j in range(args.num_samples_per_step):
275 tokens = self.get_batch(self.train_data_iterator)276 tokens = self.get_batch(self.train_data_iterator)
276- idx_list_per_step.append(tokens.view(-1).cpu().numpy().tolist())277+ tokens_list = tokens.view(-1).cpu().numpy().tolist()
277- if args.stage == "ray_online_dpo":278+ idx_list_per_step.append(tokens_list)
278- idx_list_per_step = idx_list_per_step + copy.deepcopy(idx_list_per_step)279+ if args.stage == "ray_online_dpo":
280+ idx_list_per_step.append(copy.deepcopy(tokens_list))
281+ 
279 responses_per_step = self.inf_model.generate(copy.deepcopy(idx_list_per_step),282 responses_per_step = self.inf_model.generate(copy.deepcopy(idx_list_per_step),
280 max_new_tokens=max_new_tokens,283 max_new_tokens=max_new_tokens,
281 detokenize=False, broadcast=False, do_sample=args.do_sample)284 detokenize=False, broadcast=False, do_sample=args.do_sample)
@@ -378,10 +381,9 @@ def generate_attention_mask(input_ids_list, prompts_ori_length, prompts_pad_leng
378 return attention_mask_list381 return attention_mask_list
379 382 
380 383 
381-def split_two_prompts(scores):384+def split_two_prompts(origin_tensor):
382- args = get_args()385+ origin_tensor = origin_tensor.reshape(2, -1)
383- scores = scores.reshape(args.num_samples_per_step * 2, -1)386+ first_half, second_half = origin_tensor.split(1, dim=0)
384- first_half, second_half = scores.split(scores.size(0) // 2, dim=0)
385 return first_half.reshape(-1), second_half.reshape(-1)387 return first_half.reshape(-1), second_half.reshape(-1)
386 388 
387 389 
@@ -379,14 +379,15 @@ def generate_adaptive_cp_grid_mask_by_user(cp_size):
379 grid_actual_seq_len_dict[seq_len // sub_seq_length + 1] = seq_len % sub_seq_length == 0379 grid_actual_seq_len_dict[seq_len // sub_seq_length + 1] = seq_len % sub_seq_length == 0
380 grid_actual_seq_len = list(grid_actual_seq_len_dict.items())380 grid_actual_seq_len = list(grid_actual_seq_len_dict.items())
381 start_index = 0381 start_index = 0
382- for i in range(len(grid_actual_seq_len) - 1):382+ for i, _ in enumerate(grid_actual_seq_len):
383- end_index = grid_actual_seq_len[i + 1][0]383+ end_index = grid_actual_seq_len[i][0]
384 grid_mask[start_index:end_index, start_index:end_index] = 1384 grid_mask[start_index:end_index, start_index:end_index] = 1
385 385 
386- if grid_actual_seq_len[i][1]:386+ if i != 0:
387- start_index = grid_actual_seq_len[i][0] - 1387+ if grid_actual_seq_len[i - 1][1]:
388- else:388+ start_index = grid_actual_seq_len[i - 1][0] - 1
389- start_index = grid_actual_seq_len[i][0]389+ else:
390+ start_index = grid_actual_seq_len[i - 1][0]
390 grid_mask = torch.tril(grid_mask)391 grid_mask = torch.tril(grid_mask)
391 set_adaptive_cp_grid_mask_by_user(grid_mask)392 set_adaptive_cp_grid_mask_by_user(grid_mask)
392 393 
@@ -0,0 +1,23 @@
1+{
2+ "lm loss": [
3+ 0.6931,
4+ 0.6935,
5+ 0.6937,
6+ 0.6952,
7+ 0.6847,
8+ 0.6756,
9+ 0.6998,
10+ 0.7024,
11+ 0.6883,
12+ 0.6952,
13+ 0.7021,
14+ 0.6843,
15+ 0.7016,
16+ 0.6820,
17+ 0.6888
18+ ],
19+ "throughput": [
20+ ],
21+ "memo info": [
22+ ]
23+}
@@ -1,20 +1,20 @@
1{1{
2 "lm loss": [2 "lm loss": [
3- 0.4065,3+ 0.0214,
4- 0.4323,4+ 0.0412,
5- 0.3794,5+ 0.0074,
6- 0.3497,6+ 0.0570,
7- 0.3427,7+ 0.0176,
8- 0.4128,8+ 0.0005,
9- 0.3027,9+ 0.0685,
10- 0.4638,10+ 0.1176,
11- 0.3945,11+ 0.0985,
12- 0.3411,12+ 0.2175,
13- 0.4205,13+ 0.1695,
14- 0.4161,14+ 0.2064,
15- 0.3589,15+ 0.0822,
16- 0.3458,16+ 0.0318,
17- 0.457517+ 0.0557
18 ],18 ],
19 "throughput": [19 "throughput": [
20 ],20 ],
@@ -0,0 +1,84 @@
1+defaults:
2+ - model:
3+ - llama32-1b
4+ 
5+training:
6+ global_batch_size: 8
7+ seq_length: 309
8+ tokenizer_type: PretrainedFromHF
9+ tokenizer_name_or_path: /data/ppo/llama-3.2-1b-instruct/
10+ train_iters: 15
11+ distributed_backend: nccl
12+ no_shared_storage: true
13+ save_interval: 10000
14+ no_load_optim: true
15+ no_load_rng: true
16+ bf16: true
17+ is_instruction_dataset: true
18+ variable_seq_lengths: true
19+ stage: ray_online_dpo
20+ 
21+ 
22+actor_rollout_ref:
23+ actor_rollout:
24+ model: llama32-1b
25+ micro_batch_size: 4
26+ ppo_mini_batch_size: 8
27+ max_prompt_length: 256
28+ ppo_epochs: 1
29+ clip_ratio: 0.2
30+ entropy_coeff: 0.001
31+ do_sample: true
32+ shuffle: false
33+ use_kv_cache: true
34+ num_samples_per_step: 4
35+ tensor_model_parallel_size: 1
36+ pipeline_model_parallel_size: 1
37+ lr: 1e-7
38+ lr_decay_style: constant
39+ min_lr: 0.0
40+ weight_decay: 0.0
41+ lr_warmup_fraction: 0.0
42+ clip_grad: 10000.0
43+ adam_beta1: 0.9
44+ adam_beta2: 0.999
45+ initial_loss_scale: 4096
46+ finetune: true
47+ load: /data/ppo/llama-3.2-1b-instruct-tp1-pp1
48+ save: ./ckpt
49+ num_gpus_for_train: 1
50+ num_gpus_for_infer: 1
51+ pad_to_multiple_of: 1
52+ data_path: /data/ppo/llama32-ppo-trl/alpaca
53+ split: 100,0,0
54+ no_shuffle: true
55+ 
56+ ref:
57+ model: llama32-1b
58+ tensor_model_parallel_size: 1
59+ pipeline_model_parallel_size: 1
60+ micro_batch_size: 4
61+ load: /data/ppo/llama-3.2-1b-instruct-tp1-pp1
62+ 
63+reward:
64+ model: llama32-1b
65+ tensor_model_parallel_size: 1
66+ pipeline_model_parallel_size: 1
67+ micro_batch_size: 8
68+ sequence_parallel: False
69+ load: /data/ppo/llama-3.2-1b-rm-mcore-tp1-pp1
70+ 
71+algorithm:
72+ gamma: 1.0
73+ lam: 0.95
74+ adv_estimator: gae
75+ kl_penalty: kl
76+ kl_ctrl:
77+ type: fixed
78+ kl_coef: 0.05
79+ missing_eos_penalty: 1.0
80+ 
81+resource_pool:
82+ actor_rollout: [2]
83+ ref: [1]
84+ reward: [1]
@@ -0,0 +1,9 @@
1+#!/bin/bash
2+export CUDA_DEVICE_MAX_CONNECTIONS=1
3+export HCCL_DETERMINISTIC=True
4+ 
5+ 
6+basepath=$(cd `dirname $0`; cd ../../../; pwd)
7+ 
8+ 
9+python $basepath/ray_gpt.py --config-dir=$basepath/tests/st/configs --config-name=ray_online_dpo_full_llama32_1b_tp1pp1
@@ -23,7 +23,7 @@ def transfer_logs_as_json(log_file, output_json_file):
23 """23 """
24 24 
25 log_pattern = re.compile(25 log_pattern = re.compile(
26- r"throughput per GPU \(TFLOP/s/GPU\):\s+([0-9.]+)\s+\|.*?lm loss:\s+([0-9.]+E[+-][0-9]+) | .* critic/vf_loss : ([0-9.]+)"26+ r"throughput per GPU \(TFLOP/s/GPU\):\s+([0-9.]+)\s+\|.*?lm loss:\s+([0-9.]+E[+-][0-9]+) | .* actor/pg_loss : ([0-9.]+)"
27 )27 )
28 28 
29 memory_pattern = re.compile(29 memory_pattern = re.compile(