volcengine/verl

有提供性能调试的手段吗?

Closed this issue · 14 comments

Nice work!我们想更深入地分析该框架的表现,请问有提供性能调试的手段吗?例如统计各模型计算时间(例如Actor Generation、Critic Inference、Reference Inference、Actor Training)、通信时间(数据传输、模型权重传输)、未被计算遮掩的通信时间、卡空闲时间等,谢谢!

你好,我们代码中已经加入了基本的各个模型计算时间的profile,在ray_trainer.py中可以找到,记录在了metrics中。
对于更细粒度的profile,需要手动修改verl代码来加入timing逻辑,比如在fsdp_worker.py或者megatron_worker.py中使用pytorch profiler对SPMD计算进行分析

谢谢!另外有个问题想请教下,如何在代码中改变每个模型的放置方案和并行策略。

例如,我们希望actor gen跑在8张卡上(策略为PP=2, DP=4, TP=1),reward inf跑在前四张卡上(策略为PP=1, DP=2, TP=2),critic inf跑在后四张卡上(策略为PP=1, DP=2, TP=2)。

期待您的回复~

当前open source的版本还不支持每个模型使用单独的并行策略。主要原因是因为当前ray开源版本中多个resource pool colocate在同一组GPU中会出现bug,主要原因在这里解释了:https://github.com/volcengine/verl/blob/main/verl/trainer/ppo/workers/megatron_workers.py#L418

当前开源版本中,我们强制把所有模型融合到了同一个workergroup/和一个resource pool中,来规避上述问题。https://github.com/volcengine/verl/blob/main/verl/trainer/ppo/ray_trainer.py#L378

对于使用不同的模型placement方案,需要修改resource_pool分配逻辑,给不同模型的ray workergroup分配不同的resource_pool。当前main_ppo.py,我们仅分配了一个resource_pool。你可以初始化多个不overlap的resourcepool,将其分配给不同的模型,可以参考tutorial

近期,我们正在和ray团队合作修复该问题以支持每个模型使用不同的workergroup以及不同的并行策略。我们会在修复和测试后发布出来~

了解了!我们想计算megatron_worker.py中数据传输和参数重分配的时间,我们尝试过在generate_sequences方法下添加Torch Profile功能,但Timeline持续时间少于0.1s,应该不是实际的计算或传输的时间。这是我们添加Profile后的示例代码:

    @register(dispatch_mode=Dispatch.MEGATRON_PP_AS_DP_PROTO)
    def generate_sequences(self, prompts: DataProto):
        assert self._is_rollout
        prof = profile(
            activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
            record_shapes=False,
            profile_memory=False,
            on_trace_ready=torch.profiler.tensorboard_trace_handler('log_dir_generate_sequences'),
            schedule=torch.profiler.schedule(
                wait=0,
                warmup=0,
                active=1),
            with_stack=True
        )
        prof.start()
        
        prompts.batch = prompts.batch.cuda()
        meta_info = {'eos_token_id': self.tokenizer.eos_token_id, 'pad_token_id': self.tokenizer.pad_token_id}
        prompts.meta_info.update(meta_info)
        with self.sharding_manager:
            log_gpu_memory_usage('After entering sharding manager', logger=logger)

            prompts = self.sharding_manager.preprocess_data(prompts)
            output = self.rollout.generate_sequences(prompts=prompts)

            log_gpu_memory_usage('After rollout generation', logger=logger)

            output = self.sharding_manager.postprocess_data(output)

        validate = prompts.meta_info.get('validate', False)
        if self._is_actor and not validate:
            # we should always recompute old_log_probs when it is HybridEngine
            output.meta_info['micro_batch_size'] = self.config.rollout.log_prob_micro_batch_size
            output.meta_info['temperature'] = self.config.rollout.temperature
            old_log_probs = self.actor.compute_log_prob(data=output)
            output.batch['old_log_probs'] = old_log_probs

        output = output.to('cpu')
        # clear kv cache
        torch.cuda.empty_cache()
        log_gpu_memory_usage('After recompute log prob', logger=logger)
        prof.step()
        prof.stop()
        return output

请问具体应该如何做才能获取数据传输和参数重分配时间呢?期待您的回复~

如果只是为了获取HybridEngine中数据和参数切分的时间,你可以直接使用torch.cuda.Event (refer to PyTorch Doc) 包在output = self.rollout.generate_sequences(prompts=prompts)外获得单纯的生成时间,而在with self.sharding_manager外部计算包含数据和参数切分以及生成的总时间。用两个时间相减即可获得数据和参数切分的时间。

用相同的办法包在preprocess_data和postprocess_data两个函数外可以获得数据切分时间,以及进入sharding_manger中的__enter__和__exit__函数profile参数切分时间

当前open source的版本还不支持每个模型使用单独的并行策略。主要原因是因为当前ray开源版本中多个resource pool colocate在同一组GPU中会出现bug,主要原因在这里解释了:https://github.com/volcengine/verl/blob/main/verl/trainer/ppo/workers/megatron_workers.py#L418

对于使用不同的模型placement方案,需要修改resource_pool分配逻辑,给不同模型的ray workergroup分配不同的resource_pool。当前main_ppo.py,我们仅分配了一个resource_pool。你可以初始化多个不overlap的resourcepool,将其分配给不同的模型,可以参考tutorial

关于这一条,请问是说当前版本,如果把模型放在不同的resourcepool中,每个resourcepool内部不同模型需要用一样的并行策略,不同的resourcepool中的模型是可以用不同的并行策略嘛?还是说有其他的意思。

@Zeroreoo 是的,不同resourcepool中模型可以使用不同的并行方式,因为本质上属于不同的进程。而当前ray开源版本无法支持多个resourcepool放在同一组机器中,因此我们设置max_colocate=1。修复该pr之后即可支持colocate情况下不同模型用不同resourcepool并用不同并行方式。另外,当前ray版本支持不同resourcepool虚拟化完全无overlap的多组gpu。

@Zeroreoo 是的,不同resourcepool中模型可以使用不同的并行方式,因为本质上属于不同的进程。而当前ray开源版本无法支持多个resourcepool放在同一组机器中,因此我们设置max_colocate=1。修复该pr之后即可支持colocate情况下不同模型用不同resourcepool并用不同并行方式。另外,当前ray版本支持不同resourcepool虚拟化完全无overlap的多组gpu。

你好,我尝试修改模型mapping的方式,我将8卡分了两组resourcepool,每个resourcepool 4张卡,修改mapping为:
actor_pool_id= 'actor_pool_id'
other_pool_id = 'other_pool'
resource_pool_spec = {
actor_pool_id: [4],
other_pool_id: [4]
}
mapping = {
Role.ActorRollout: actor_pool_id,
Role.Critic: other_pool_id,
Role.RefPolicy: other_pool_id,
}
这样actor模型的generate和train阶段占用前四张卡,ref和critic占用后四张卡。代码可以跑通,但运行时发现actor和critic模型的训练阶段仍然是串行执行,是否有方法可以将两个模型的训练并行化?

@HongyeZhou , 你可以在fsdp_workers.py中的update_actor和update_critic function上的decorator中加入blocking=False代表异步操作:@register(dispatch_mode=Dispatch.DP_COMPUTE_PROTO, blocking=False)

此时两个函数返回的值为list of ObjectRef. 你需要在ray_trainer.py 的validate function前加入 ray.get([*actor_output, *critic_output]) 来等待两个模型并行处理完成

@HongyeZhou , 你可以在fsdp_workers.py中的update_actor和update_critic function上的decorator中加入blocking=False代表异步操作:@register(dispatch_mode=Dispatch.DP_COMPUTE_PROTO, blocking=False)

此时两个函数返回的值为list of ObjectRef. 你需要在ray_trainer.py 的validate function前加入 ray.get([*actor_output, *critic_output]) 来等待两个模型并行处理完成

我用的是megatron,也可以这样修改吗?

@HongyeZhou 可以,都是加个blocking=False

@HongyeZhou 可以,都是加个blocking=False

@PeterSH6 我昨天尝试加blocking=False,代码如下:

image

报错:
image

然后我把ray.get([*actor_output, *critic_output])改成ray.get([actor_output, critic_output]),报错如下:
image

麻烦看下要如何修改,谢谢!

@HongyeZhou 不好意思,昨天说错了。我们包了一层DataProtoFuture来处理异步数据传输
试试ray.get()改为 actor_output = actor_output.get() ; critic_output = critic_output.get()

@HongyeZhou 不好意思,昨天说错了。我们包了一层DataProtoFuture来处理异步数据传输 试试ray.get()改为 actor_output = actor_output.get() ; critic_output = critic_output.get()

可以跑通了 感谢 @PeterSH6