DAPO 提出了解耦剪辑和动态采样策略优化 (DAPO) 算法,公开这项研究成果,旨在为更广泛的研究人员和社会提供可扩展强化学习的实用工具,让更多人受益于这项技术进步。将 DAPO 应用于 Qwen2.5-32B 基础模型,在 AIME 2024 评测中,其表现超越了此前最先进的 DeepSeek-R1-Zero-Qwen-32B 模型,在训练步数减少 50% 的情况下,准确率达到了50%。
verl 是火山引擎推出的用于大语言模型(LLM)的强化学习库,具有灵活性、高效性且适用于生产环境。
在本教程中,您将了解在火山引擎机器学习平台(veMLP)上如何基于 verl 使用 DAPO 强化学习训练。
本文将会使用如下组件:
在开始任务之前,需要配置 volc cli 或者 volc python sdk,如果您不知道您的 AK/SK,可以通过 访问控制-API访问密钥 获得您当前身份的密钥对。
在机器学习平台创建开发机,选择好队列计算资源配置、访问配置、挂载共享文件系统后,即可创建。
vemlp-cn-beijing.cr.volces.com/preset-images/verl:dapo-v0.0.1
vemlp-cn-shanghai.cr.volces.com/preset-images/verl:dapo-v0.0.1
/file_system
目录,可根据您的具体情况进行调整。
登录到机器学习平台开发机 WebIDE,打开终端,新建 Jupyter Notebook 交互式开发环境,并选择合适的 Python 环境
# 查看当前 python 的环境路径,推荐选择 `/usr/bin/python` which python
volc cli 为机器学习平台的命令行工具,可以以命令行的方式便捷的进行任务提交,任务管理等操作。预置镜像已经安装volc命令行工具,进行升级操作。
# 升级volc cli ! volc upgrade # 查看volc cli版本 ! volc v
可通过以下操作配置好 volc cli 和 jupyter notebook 需要的的环境依赖
如果您不知道您的 AK/SK,可以通过 API 访问密钥获得您当前身份的密钥对。
# 火山引擎认证配置(替换为你的 AK/SK) VOLC_ACCESS_KEY_ID = 'xx' VOLC_SECRET_ACCESS_KEY = 'yy==' # 默认使用开发机所在的 region import os VOLC_REGION=os.environ['MLP_REGION'] # 一次性设置所有环境变量(Jupyter魔法命令) %set_env VOLC_ACCESS_KEY_ID={VOLC_ACCESS_KEY_ID} %set_env VOLC_SECRET_ACCESS_KEY={VOLC_SECRET_ACCESS_KEY} %set_env VOLC_REGION={VOLC_REGION} # 配置火山命令行工具(自动读取已设置的环境变量) ! volc configure --ak $VOLC_ACCESS_KEY_ID --sk $VOLC_SECRET_ACCESS_KEY --region $VOLC_REGION # 验证配置文件(可选) ! echo "volc config:" && cat ${HOME}/.volc/config ! echo "volc credentials:" && cat ${HOME}/.volc/credentials
这里配置该文档所提交的所有任务,所用到的镜像信息,会默认使用你开机机所在 region 的镜像,如有其他需求,请更换为您所在的区域,以获取更好的体验。
# 根据当前 region 生成镜像地址 image_url = f'vemlp-{VOLC_REGION}.cr.volces.com/preset-images/verl:dapo-v0.0.1' print(f'image: {image_url}')
解耦剪裁和动态采样策略优化 (DAPO) 算法,其中包含以下几个关键技术。详细分析和见解可在DAPO的技术报告中找到。
如果不使用平台预置好的数据集和模型,可跳过此步骤。
tosutil 是用于访问和管理火山引擎对象存储(TOS)的命令行工具。通过该命令行工具,您可进行本地与 TOS 之间的批量数据处理,以应对本地中小数据上云、云上中小数据下载、自动化脚本任务集成等常见业务场景。机器学习平台开发机已经预装了 tosutil 工具
! tosutil version
可通过以下操作配置好 tosutil 需要的的环境依赖,并声明共享存储的目录,方便后续提交任务使用相同的存储配置,本文以将 vePFS 挂载到 /file_system
目录为例
! tosutil config -i $VOLC_ACCESS_KEY_ID -k $VOLC_SECRET_ACCESS_KEY -e tos-$VOLC_REGION.ivolces.com -re $VOLC_REGION root_path = '/file_system' model_path = f'{root_path}/models' dataset_path = f'{root_path}/datasets' checkpoint_path = f'{root_path}/ckpt' model_name = 'Qwen/Qwen2.5-32B' train_dataset_name = 'BytedTsinghua-SIA/DAPO-Math-17k' test_dataset_name = 'BytedTsinghua-SIA/AIME-2024' # 如果共享存储使用的是 TOS,需要声明你的 tos prefix tos_prefix="your_bucket_name/prefix"
火山引擎提供TOS对象存储预置数据集,方便用户自助复制,加速实验。
import os # cp 需要带-r 参数,否则不会下载目录;传输速度慢可以根据开发机的 CPU 数量调整 -j -p 并发参数 ! tosutil cp tos://preset-datasets-{VOLC_REGION}/{train_dataset_name}/ {dataset_path}/{os.path.dirname(train_dataset_name)} -r -u -j=32 -p=4 -nfj=16 ! tosutil cp tos://preset-datasets-{VOLC_REGION}/{test_dataset_name}/ {dataset_path}/{os.path.dirname(test_dataset_name)} -r -u -j=32 -p=4 -nfj=16 ## tos 挂载不支持完整的 POSIX 语义,可直接上传到 TOS 中 # ! tosutil cp tos://preset-datasets-{VOLC_REGION}/{train_dataset_name}/ tos://{tos_prefix}/{os.path.dirname(train_dataset_name)} -r -u -j=32 -p=4 -nfj=16 # ! tosutil cp tos://preset-datasets-{VOLC_REGION}/{test_dataset_name}/ tos://{tos_prefix}/{os.path.dirname(test_dataset_name)} -r -u -j=32 -p=4 -nfj=16 ## 刷新目录,将 TOS 中的内容同步到缓存文件系统中 # ! cfs-cli ls {dataset_path}/{train_dataset_name} # ! cfs-cli ls {dataset_path}/{test_dataset_name}
火山引擎提供TOS对象存储预置模型权重文件,方便客户自助复制,加速试验。以 Qwen/Qwen2.5-32B 模型为例。
import os # cp 需要带-r 参数,否则不会下载目录;传输速度慢可以根据开发机的 CPU 数量调整 -j -p 并发参数 ! tosutil cp tos://preset-models-{VOLC_REGION}/{model_name}/ {model_path}/{os.path.dirname(model_name)} -r -u -j=32 -p=4 -nfj=16 ## tos 挂载不支持完整的 POSIX 语义,可直接上传到 TOS 中 # ! tosutil cp tos://preset-datasets-{VOLC_REGION}/{model_name}/ tos://{model_path}/{os.path.dirname(model_name)} -r -u -j=32 -p=4 -nfj=16 ## 刷新目录,将 TOS 中的内容同步到缓存文件系统中 # ! cfs-cli ls {model_path}/{os.path.dirname(model_name)}
verl 支持采用 Ray 多节点任务的形式对 Qwen32B 模型进行 GRPO 强化学习训练。 本文使用 gsm8k 数据集,以 Qwen32B 模型在 2 个 Hopper 节点进行分布式训练为例,展示强化学习微调模型的流程。
您可通过以下方式获取相关运行配置
# 资源配置 queue='q-2025xxxx-1234' # 队列 id flavor='ml.hpcpni3ln.45xlarge' # 本文以 H20 为例 replicas=16 # 文件系统配置 storage_type = "Vepfs" mount_path = "/file_system" # vepfs 挂载路径 vepfs_id = "vepfs-xxxx" # vepfs id
更详细的说明可见 DAPO Readme
import os # DAPO ## Clip-Higher ## - clip_ratio_low and clip_ratio_high specify the εlow and εhigh in the DAPO objective. clip_ratio_low=0.2 clip_ratio_high=0.28 ## Dynamic Sampling (with Group Filtering) ## - 训练器将重复使用 gen_batch_size 进行采样,直到有足够的合格组用于 train_batch_size,或者达到 max_num_gen_batches 指定的上限。 train_batch_size=512 gen_batch_size=train_prompt_bsz * 3 enable_filter_groups=True # 设置为True将过滤掉所有输出指标值都相同的组,例如,对于acc,将过滤掉所有输出准确率都为1或0的组。 filter_groups_metric="acc" # score / seq_reward / seq_final_reward / ... max_num_gen_batches=10 # Non-positive values mean no upper limit ## Token-level Policy Gradient ## - 将 use_token_level_loss 设置为True意味着将针对批次中所有序列中的所有标记计算策略梯度损失。 use_token_level_loss=True ## Overlong Reward Shaping ## - max_response_length - overlong_buffer_len 是预期长度,超过预期长度的部分会惩罚,惩罚因子由 overlong_penalty_factor 控制 enable_overlong_buffer=True overlong_buffer_len=1024 * 4 overlong_penalty_factor=1.0 max_prompt_length=1024 * 2 max_response_length=1024 * 20 # Others ## 关闭 KL(Kullback-Leibler Divergence) kl_coef=0.0 kl_loss_coef=0.0 ## Performance Related Parameter gen_tp=4 sp_size=8 use_dynamic_bsz=True actor_ppo_max_token_len=max_prompt_length + max_response_length infer_ppo_max_token_len=max_prompt_length + max_response_length offload=True ## Train train_prompt_mini_bsz=32 ## Validation val_top_k=-1 # 0 for HF rollout, -1 for vLLM rollout n_resp_per_prompt=16 ## Tracking use_tracking=True project_name="DAPO" # 火山机器学习平台实验管理 - 项目名称,use_tracking=True 时填写 experiment_name="DAPO-Qwen2.5-32B" # 火山机器学习平台实验管理 - 实验名称,use_tracking=True 时填写
首先配置自定义任务的启动参数,并通过 volc cli 命令行工具提交自定义任务。使用 Ray 框架进行分布式训练,执行下面的命令新建一个 demo-grpo-train-task.yaml 的任务配置文件:
import yaml def build_envs(): envs = [ {"Name": "TORCH_NCCL_AVOID_RECORD_STREAMS", "Value": "1", "IsPrivate": False}, {"Name": "VLLM_ATTENTION_BACKEND", "Value": "XFORMERS", "IsPrivate": False}, {"Name": "VLLM_USE_V1", "Value": "1", "IsPrivate": False}, {"Name": "VERL_PPO_LOGGING_LEVEL", "Value": "DEBUG", "IsPrivate": False}, ] if use_tracking: envs.extend( [ {"Name": "VOLC_ACCESS_KEY_ID", "Value": VOLC_ACCESS_KEY_ID, "IsPrivate": False}, {"Name": "VOLC_SECRET_ACCESS_KEY", "Value": VOLC_SECRET_ACCESS_KEY, "IsPrivate": True}, {"Name": "MLP_TRACKING_REGION", "Value": "cn-beijing", "IsPrivate": False}, # 实验管理目前仅在 cn-beijing {"Name": "MLP_TRACKING_PROJECT_NAME", "Value": project_name, "IsPrivate": False}, ] ) return envs def build_entrypoint(): return f'''python3 -m verl.trainer.main_ppo \ data.train_files="{dataset_path}/{train_dataset_name}/verl/dapo-math-17k.parquet" \ data.val_files="{dataset_path}/{test_dataset_name}/verl/aime-2024.parquet" \ data.prompt_key=prompt \ data.truncation='left' \ data.max_prompt_length={max_prompt_length} \ data.max_response_length={max_response_length} \ data.gen_batch_size={gen_batch_size} \ data.train_batch_size={train_batch_size} \ actor_rollout_ref.rollout.n={n_resp_per_prompt} \ algorithm.adv_estimator=grpo \ algorithm.kl_ctrl.kl_coef={kl_coef} \ actor_rollout_ref.actor.kl_loss_coef={kl_loss_coef} \ actor_rollout_ref.actor.clip_ratio_low={clip_ratio_low} \ actor_rollout_ref.actor.clip_ratio_high={clip_ratio_high} \ algorithm.filter_groups.enable={enable_filter_groups} \ algorithm.filter_groups.max_num_gen_batches={max_num_gen_batches} \ algorithm.filter_groups.metric={filter_groups_metric} \ actor_rollout_ref.model.use_remove_padding=True \ actor_rollout_ref.actor.use_dynamic_bsz={use_dynamic_bsz} \ actor_rollout_ref.ref.log_prob_use_dynamic_bsz={use_dynamic_bsz} \ actor_rollout_ref.rollout.log_prob_use_dynamic_bsz={use_dynamic_bsz} \ actor_rollout_ref.actor.ppo_max_token_len_per_gpu={actor_ppo_max_token_len} \ actor_rollout_ref.ref.log_prob_max_token_len_per_gpu={infer_ppo_max_token_len} \ actor_rollout_ref.rollout.log_prob_max_token_len_per_gpu={infer_ppo_max_token_len} \ actor_rollout_ref.model.path="{model_path}/{model_name}" \ +actor_rollout_ref.model.override_config.attention_dropout=0. \ +actor_rollout_ref.model.override_config.embd_pdrop=0. \ +actor_rollout_ref.model.override_config.resid_pdrop=0. \ actor_rollout_ref.model.enable_gradient_checkpointing=True \ actor_rollout_ref.actor.optim.lr=1e-6 \ actor_rollout_ref.actor.optim.lr_warmup_steps=10 \ actor_rollout_ref.actor.optim.weight_decay=0.1 \ actor_rollout_ref.actor.ppo_mini_batch_size={train_prompt_mini_bsz} \ actor_rollout_ref.actor.fsdp_config.param_offload={offload} \ actor_rollout_ref.actor.fsdp_config.optimizer_offload={offload} \ actor_rollout_ref.actor.entropy_coeff=0 \ actor_rollout_ref.actor.grad_clip=1.0 \ actor_rollout_ref.actor.use_token_level_loss={use_token_level_loss} \ actor_rollout_ref.actor.ulysses_sequence_parallel_size={sp_size} \ actor_rollout_ref.rollout.gpu_memory_utilization=0.80 \ actor_rollout_ref.rollout.tensor_model_parallel_size={gen_tp} \ actor_rollout_ref.rollout.enable_chunked_prefill=True \ actor_rollout_ref.rollout.max_num_batched_tokens={max_prompt_length + max_response_length} \ actor_rollout_ref.rollout.val_kwargs.top_k="{val_top_k}" \ actor_rollout_ref.rollout.val_kwargs.top_p=1.0 \ actor_rollout_ref.rollout.val_kwargs.temperature=1.0 \ actor_rollout_ref.rollout.val_kwargs.n=1 \ actor_rollout_ref.rollout.val_kwargs.do_sample=True \ actor_rollout_ref.ref.fsdp_config.param_offload={offload} \ actor_rollout_ref.ref.ulysses_sequence_parallel_size={sp_size} \ actor_rollout_ref.actor.fsdp_config.fsdp_size=-1 \ custom_reward_function.overlong_buffer.enable={enable_overlong_buffer} \ custom_reward_function.overlong_buffer.len={overlong_buffer_len} \ custom_reward_function.overlong_buffer.penalty_factor={overlong_penalty_factor} \ trainer.logger=['console','vemlp_wandb'] \ trainer.project_name="{project_name}" \ trainer.experiment_name="{experiment_name}" \ trainer.n_gpus_per_node=8 \ trainer.nnodes="{replicas}" \ +trainer.val_before_train=True \ trainer.test_freq=5 \ trainer.save_freq=5 \ trainer.total_epochs=1 \ trainer.default_local_dir="{checkpoint_path}/{project_name}/{experiment_name}" \ trainer.resume_mode=auto''' # 定义配置内容 task_config = { "TaskName": "DAPO", "Description": "Use Ray and verl to reproduce DAPO", "Entrypoint": build_entrypoint(), "Envs": build_envs(), "ResourceQueueID": queue, #replace_with_your_ResourceQueueID "Framework": "Ray", "TaskRoleSpecs": [ #replace_with_your_TaskRoleSpecs { "RoleName": "head", "RoleReplicas": 1, "Flavor": flavor, }, { "RoleName": "worker", "RoleReplicas": replicas - 1, "Flavor": flavor, } ], "ActiveDeadlineSeconds": 864000, "EnableTensorBoard": False, #replace_with_your_Storages "Storages": [ { "MountPath": mount_path, "Type": storage_type, "VepfsId": vepfs_id, } ], "ImageUrl": image_url, #replace_with_you_image_url "RetryOptions": { "EnableRetry": False, "MaxRetryTimes": 5, "IntervalSeconds": 120, "PolicySets": [], }, } # 将配置写入到 DAPO.yaml 文件中 import datetime verl_grpo_config_yaml = f'DAPO-{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.yaml' with open(verl_grpo_config_yaml, "w") as file: yaml.dump(task_config, file, default_flow_style=False) print(f"{verl_grpo_config_yaml} 文件已生成") ! volc ml_task submit --conf {verl_grpo_config_yaml}
通过 volc 命令行工具查询作业状态:
! volc ml_task get --id t-20241021145150-qc49r --output json --format Status
发起训练任务后,可以在 Ray Dashboard 中查看详细任务运行状态和日志:
详细请参考:官方文档