JackyChunKit commited on
Commit
ebc9277
·
verified ·
1 Parent(s): 9571480

Upload main_ppo.py

Browse files
Files changed (1) hide show
  1. main_ppo.py +195 -0
main_ppo.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 Bytedance Ltd. and/or its affiliates
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """
15
+ Note that we don't combine the main with ray_trainer as ray_trainer is used by other main.
16
+ """
17
+
18
+ from verl import DataProto
19
+ import torch
20
+ from verl.utils.reward_score import gsm8k, math, multiply, countdown, kk
21
+ from verl.trainer.ppo.ray_trainer import RayPPOTrainer
22
+
23
+
24
+ def _select_rm_score_fn(data_source):
25
+ if data_source == 'openai/gsm8k':
26
+ return gsm8k.compute_score
27
+ elif data_source == 'lighteval/MATH':
28
+ return math.compute_score
29
+ elif "multiply" in data_source or "arithmetic" in data_source:
30
+ return multiply.compute_score
31
+ elif "countdown" in data_source:
32
+ return countdown.compute_score
33
+ elif "kk" in data_source:
34
+ return kk.compute_score
35
+ else:
36
+ raise NotImplementedError
37
+
38
+
39
+ class RewardManager():
40
+ """The reward manager.
41
+ """
42
+
43
+ def __init__(self, tokenizer, num_examine) -> None:
44
+ self.tokenizer = tokenizer
45
+ self.num_examine = num_examine # the number of batches of decoded responses to print to the console
46
+
47
+ def __call__(self, data: DataProto):
48
+ """We will expand this function gradually based on the available datasets"""
49
+
50
+ # If there is rm score, we directly return rm score. Otherwise, we compute via rm_score_fn
51
+ if 'rm_scores' in data.batch.keys():
52
+ return data.batch['rm_scores']
53
+
54
+ reward_tensor = torch.zeros_like(data.batch['responses'], dtype=torch.float32)
55
+
56
+ already_print_data_sources = {}
57
+
58
+ for i in range(len(data)):
59
+ data_item = data[i] # DataProtoItem
60
+
61
+ prompt_ids = data_item.batch['prompts']
62
+
63
+ prompt_length = prompt_ids.shape[-1]
64
+
65
+ valid_prompt_length = data_item.batch['attention_mask'][:prompt_length].sum()
66
+ valid_prompt_ids = prompt_ids[-valid_prompt_length:]
67
+
68
+ response_ids = data_item.batch['responses']
69
+ valid_response_length = data_item.batch['attention_mask'][prompt_length:].sum()
70
+ valid_response_ids = response_ids[:valid_response_length]
71
+
72
+ # decode
73
+ sequences = torch.cat((valid_prompt_ids, valid_response_ids))
74
+ sequences_str = self.tokenizer.decode(sequences)
75
+
76
+ ground_truth = data_item.non_tensor_batch['reward_model']['ground_truth']
77
+
78
+ # select rm_score
79
+ data_source = data_item.non_tensor_batch['data_source']
80
+ compute_score_fn = _select_rm_score_fn(data_source)
81
+
82
+ score = compute_score_fn(solution_str=sequences_str, ground_truth=ground_truth)
83
+ reward_tensor[i, valid_response_length - 1] = score
84
+
85
+ if data_source not in already_print_data_sources:
86
+ already_print_data_sources[data_source] = 0
87
+
88
+ if already_print_data_sources[data_source] < self.num_examine:
89
+ already_print_data_sources[data_source] += 1
90
+ print(sequences_str)
91
+
92
+ return reward_tensor
93
+
94
+
95
+ import ray
96
+ import hydra
97
+
98
+
99
+ @hydra.main(config_path='config', config_name='ppo_trainer', version_base=None)
100
+ def main(config):
101
+ if not ray.is_initialized():
102
+ # this is for local ray cluster
103
+ ray.init(runtime_env={'env_vars': {'TOKENIZERS_PARALLELISM': 'true', 'NCCL_DEBUG': 'WARN'}})
104
+
105
+ ray.get(main_task.remote(config))
106
+
107
+
108
+ @ray.remote
109
+ def main_task(config):
110
+ from verl.utils.fs import copy_local_path_from_hdfs
111
+ from transformers import AutoTokenizer
112
+
113
+ # print initial config
114
+ from pprint import pprint
115
+ from omegaconf import OmegaConf
116
+ pprint(OmegaConf.to_container(config, resolve=True)) # resolve=True will eval symbol values
117
+ OmegaConf.resolve(config)
118
+
119
+ # download the checkpoint from hdfs
120
+ local_path = copy_local_path_from_hdfs(config.actor_rollout_ref.model.path)
121
+
122
+ # instantiate tokenizer
123
+ from verl.utils import hf_tokenizer
124
+ tokenizer = hf_tokenizer(local_path)
125
+
126
+ # define worker classes
127
+ if config.actor_rollout_ref.actor.strategy == 'fsdp':
128
+ assert config.actor_rollout_ref.actor.strategy == config.critic.strategy
129
+ from verl.workers.fsdp_workers import ActorRolloutRefWorker, CriticWorker
130
+ from verl.single_controller.ray import RayWorkerGroup
131
+ ray_worker_group_cls = RayWorkerGroup
132
+
133
+ elif config.actor_rollout_ref.actor.strategy == 'megatron':
134
+ assert config.actor_rollout_ref.actor.strategy == config.critic.strategy
135
+ from verl.workers.megatron_workers import ActorRolloutRefWorker, CriticWorker
136
+ from verl.single_controller.ray.megatron import NVMegatronRayWorkerGroup
137
+ ray_worker_group_cls = NVMegatronRayWorkerGroup
138
+
139
+ else:
140
+ raise NotImplementedError
141
+
142
+ from verl.trainer.ppo.ray_trainer import ResourcePoolManager, Role
143
+
144
+ role_worker_mapping = {
145
+ Role.ActorRollout: ray.remote(ActorRolloutRefWorker),
146
+ Role.Critic: ray.remote(CriticWorker),
147
+ Role.RefPolicy: ray.remote(ActorRolloutRefWorker)
148
+ }
149
+
150
+ global_pool_id = 'global_pool'
151
+ resource_pool_spec = {
152
+ global_pool_id: [config.trainer.n_gpus_per_node] * config.trainer.nnodes,
153
+ }
154
+ mapping = {
155
+ Role.ActorRollout: global_pool_id,
156
+ Role.Critic: global_pool_id,
157
+ Role.RefPolicy: global_pool_id,
158
+ }
159
+
160
+ # we should adopt a multi-source reward function here
161
+ # - for rule-based rm, we directly call a reward score
162
+ # - for model-based rm, we call a model
163
+ # - for code related prompt, we send to a sandbox if there are test cases
164
+ # - finally, we combine all the rewards together
165
+ # - The reward type depends on the tag of the data
166
+ if config.reward_model.enable:
167
+ if config.reward_model.strategy == 'fsdp':
168
+ from verl.workers.fsdp_workers import RewardModelWorker
169
+ elif config.reward_model.strategy == 'megatron':
170
+ from verl.workers.megatron_workers import RewardModelWorker
171
+ else:
172
+ raise NotImplementedError
173
+ role_worker_mapping[Role.RewardModel] = ray.remote(RewardModelWorker)
174
+ mapping[Role.RewardModel] = global_pool_id
175
+
176
+ reward_fn = RewardManager(tokenizer=tokenizer, num_examine=0)
177
+
178
+ # Note that we always use function-based RM for validation
179
+ val_reward_fn = RewardManager(tokenizer=tokenizer, num_examine=1)
180
+
181
+ resource_pool_manager = ResourcePoolManager(resource_pool_spec=resource_pool_spec, mapping=mapping)
182
+
183
+ trainer = RayPPOTrainer(config=config,
184
+ tokenizer=tokenizer,
185
+ role_worker_mapping=role_worker_mapping,
186
+ resource_pool_manager=resource_pool_manager,
187
+ ray_worker_group_cls=ray_worker_group_cls,
188
+ reward_fn=reward_fn,
189
+ val_reward_fn=val_reward_fn)
190
+ trainer.init_workers()
191
+ trainer.fit()
192
+
193
+
194
+ if __name__ == '__main__':
195
+ main()