You need to enable JavaScript to run this app.
导航
使用 verl 复现 DAPO 强化学习训练最佳实践
最近更新时间:2025.03.21 10:30:52首次发布时间:2025.03.20 20:07:42
我的收藏
有用
有用
无用
无用

简介

DAPO 提出了解耦剪辑和动态采样策略优化 (DAPO) 算法,公开这项研究成果,旨在为更广泛的研究人员和社会提供可扩展强化学习的实用工具,让更多人受益于这项技术进步。将 DAPO 应用于 Qwen2.5-32B 基础模型,在 AIME 2024 评测中,其表现超越了此前最先进的 DeepSeek-R1-Zero-Qwen-32B 模型,在训练步数减少 50% 的情况下,准确率达到了50%。
verl 是火山引擎推出的用于大语言模型(LLM)的强化学习库,具有灵活性、高效性且适用于生产环境。

  • 灵活易用:通过混合编程模型,能轻松扩展多种强化学习算法。
  • 速度快:集成现有先进的 LLM 训练和推理框架,实现高生成和训练吞吐量,进行高效的 Actor 模型重分片,减少内存冗余和通信开销。
  • 开源生态:模块化 API 可实现与现有 LLM 框架(如 PyTorch FSDP、Megatron-LM 和 vLLM )的无缝集成;支持灵活的设备映射,可在不同 GPU 组合上部署模型;能与 HuggingFace 模型集成。已经通过 GitHub 开源,广受开发者关注。

在本教程中,您将了解在火山引擎机器学习平台(veMLP)上如何基于 verl 使用 DAPO 强化学习训练。

目标

  • 了解 DAPO 强化学习的基本原理
  • 了解使用 verl 进行模型强化学习的方法
  • 在机器学习平台自定义任务模块上使用 verl 进行 DAPO 强化学习训练。

组件

本文将会使用如下组件:

  • 火山引擎机器学习平台
  • verl 强化学习训练框架
  • vePFS

环境准备

在开始任务之前,需要配置 volc cli 或者 volc python sdk,如果您不知道您的 AK/SK,可以通过 访问控制-API访问密钥 获得您当前身份的密钥对。

开发准备

在机器学习平台创建开发机,选择好队列计算资源配置、访问配置、挂载共享文件系统后,即可创建。

  • 需要用户在镜像入口采用镜像 url 的方式:并根据您所在的 region 修改 URL,例:
    • 华北2: vemlp-cn-beijing.cr.volces.com/preset-images/verl:dapo-v0.0.1
    • 华东2: vemlp-cn-shanghai.cr.volces.com/preset-images/verl:dapo-v0.0.1
  • 本文是将 vePFS 挂载到 /file_system 目录,可根据您的具体情况进行调整。

Image
登录到机器学习平台开发机 WebIDE,打开终端,新建 Jupyter Notebook 交互式开发环境,并选择合适的 Python 环境

# 查看当前 python 的环境路径,推荐选择 `/usr/bin/python`
which python

环境变量配置

volc cli 配置

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) 算法,其中包含以下几个关键技术。详细分析和见解可在DAPO的技术报告中找到。

  • Clip-Higher: 它提升了系统的多样性并避免了熵坍缩。DAPO 在初步实验中观察到熵坍缩现象。DAPO增加策略梯度损失中重要性采样比率的上限剪裁范围以减轻这个问题。
  • Dynamic Sampling: 它提高了训练效率和稳定性。DAPO出了一种执行动态采样的策略,并过滤掉准确率等于1和0的提示组,从而保持批次间具有有效梯度的提示数量一致。
  • Token-level Policy Gradient Loss: 这在长链思维强化学习 (long-CoT RL) 场景中至关重要。
  • Overlong Reward Shaping: 它减少了奖励噪声并稳定了训练。

数据集

验证器

  • DAPO 采用了一种简单但稳健的基于规则的验证器,它依赖于字符串规范化和匹配。

DAPO 强化学习复现流程

tosutil 配置(需要手动配置)

如果不使用平台预置好的数据集和模型,可跳过此步骤。
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 发起 DAPO 强化学习训练任务

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 观察作业的执行状态

发起训练任务后,可以在 Ray Dashboard 中查看详细任务运行状态和日志:
Image
Image
详细请参考:官方文档