准备训练脚本
模型训练使用的训练脚本文件请参见训练脚本(Qwen3-8B)和训练脚本(Qwen2.5-VL-32B-Instruct),需要用户参考本章节内容制作sh文件,并上传到OBS桶中。
OBS桶中文件存放目录示例: obs://verl/verl-a2/run_train_8b.sh obs://verl/verl-a2/run_qwen3-8b_npu_ma.sh obs://verl/verl-a2/run_train_32b.sh obs://verl/verl-a2/run_qwen2_5_vl_32b_npu_ma.sh
|
训练模型 |
训练脚本 |
脚本说明 |
|---|---|---|
|
Qwen3-8B |
run_train_8b.sh |
训练作业启动脚本,其中包括数据预处理、启动训练任务输出训练日志等功能 |
|
run_qwen3-8b_npu_ma.sh |
用于启动VeRL强化学习训练任务。 |
|
|
Qwen2.5-VL-32B-Instruct |
run_train_32b.sh |
训练作业启动脚本,其中包括数据预处理、启动训练任务输出训练日志等功能 |
|
run_qwen2_5_vl_32b_npu_ma.sh |
用于启动VeRL强化学习训练任务。 |
路径解释
训练脚本文件中涉及到的路径解释如下:
- /home/ma-user/work为ModelArts创建训练作业时设置的本地代码目录,用户可以自定义,但是脚本中的路径和创建训练作业时设置的路径必须保持一致。
- /verl-a2为存放代码、数据集、模型权重的OBS文件夹名称,用户可以自定义。
- /output_dir为创建训练作业挂载OBS时设置的云上挂载路径,用于保存预处理后的数据、checkpoint、训练日志,用户可以自定义
- /home/ma-user/verl为镜像预置的verl代码仓,用户无需修改。
参数解释
|
参数 |
参数说明 |
示例值 |
|---|---|---|
|
data.train_files |
预处理完成后的训练集 |
~/dataset/gsm8k/precessed/train.parquet |
|
data.val_files |
预处理完成后的验证集 |
~/dataset/gsm8k/precessed/test.parquet |
|
actor_rollout_ref.model.path |
Huggingface模型路径,也可以是容器内路径 |
~/models/Qwen3-8B |
|
trainer.n_gpus_per_node |
每个节点的NPU数量 |
8 |
|
trainer.nnodes |
训练所使用的节点数量 |
2 |
|
algorithm.adv_estimator |
强化学习策略算法 |
grpo |
|
data.train_batch_size |
单次训练迭代的采样批次大小 |
16 |
|
data.max_prompt_length |
最大提示词长度 |
2048 |
|
data.max_response_length |
最大回复长度 |
4096 |
|
actor_rollout_ref.rollout.tensor_model_parallel_size |
vllm推理时模型切分 |
2 |
|
actor_rollout_ref.nccl_timeout |
通信超时时间,根据访存需求酌情设置 |
7200 |
|
actor_rollout_ref.rollout.n |
重要性采样次数 |
2 |
|
trainer.save_freq |
检查点保存频率,按训练步数计 |
50 |
|
trainer.test_freq |
验证频率,按训练步数计 |
50 |
|
trainer.total_epochs |
训练轮数,模型将整个训练集完整遍历一遍即为一轮 |
1 |
|
trainer.total_training_steps |
训练步数,模型完成一次前向+反向传播+参数更新即为一步 |
100 |
更多参数解释请参考VeRL官方文档。
训练脚本(Qwen3-8B)
需要准备2个训练脚本文件run_train_8b.sh和run_qwen3-8b_npu_ma.sh。
- run_train_8b.sh是训练作业启动脚本,其中包括数据预处理、启动训练任务输出训练日志等功能。run_train_8b.sh脚本启动时,会调用数据预处理脚本gsm8k.py和VeRL训练脚本run_qwen3-8b_npu_ma.sh。
run_train_8b.sh脚本内容具体如下:
#!/bin/bash set -e export HCCL_IF_BASE_PORT=61000 export HCCL_NPU_SOCKET_PORT_RANGE="61000-61050" export HCCL_EXEC_TIMEOUT=60000 export HCCL_CONNECT_TIMEOUT=7200 unset https_proxy http_proxy proxy ASCEND_RT_VISIBLE_DEVICES source /usr/local/Ascend/ascend-toolkit/set_env.sh source /usr/local/Ascend/nnal/atb/set_env.sh export NNODES=${VC_WORKER_NUM:-1} export NGPUS_PER_NODE=${MA_NUM_GPUS:-8} export MASTER_ADDR="${VC_WORKER_HOSTS%%,*}" export DOMAIN_IP=$(python -c "import socket; print(socket.gethostbyname('${MASTER_ADDR}'))") export MASTER_NODE_IP="${DOMAIN_IP}" export MASTER_NODE_PORT="6699" export USE_OPTIMIZED_MODEL=0 MAX_WAIT_SECONDS=900 CHECK_INTERVAL=3 get_current_ip() { local ip ip=$(ip -4 addr | grep -v '127.0.0.1' | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n1 2>/dev/null) if [[ -z "$ip" ]]; then ip=$(ip -4 addr | grep -v '127.0.0.1' | awk '/inet/ {gsub(/\/.*/,""); print $2}' | head -n1 2>/dev/null) fi echo "${ip:-}" } CURRENT_IP=$(get_current_ip) if [[ -z "$CURRENT_IP" ]]; then echo "无法提取当前节点IP" exit 1 fi echo "current ip:$CURRENT_IP,master ip:$MASTER_NODE_IP" if [ "$CURRENT_IP" = "$MASTER_NODE_IP" ]; then echo "start Ray master node..." ray stop --force ray start --head --port="$MASTER_NODE_PORT" elapsed_seconds=0 echo "wait $NNODES nodes..." while true; do current_node_num=$(ray status | grep -c "node_" || echo 0) if ! [[ "$current_node_num" =~ ^[0-9]+$ ]]; then current_node_num=0 fi echo "current_node_num:$current_node_num" if [ "$current_node_num" -ge "$NNODES" ]; then echo "all $NNODES workers node is ready." break fi if [ "$elapsed_seconds" -ge "$MAX_WAIT_SECONDS" ]; then echo "timeout($MAX_WAIT_SECONDS s),current_node_num:$current_node_num" ray stop --force exit 1 fi sleep "$CHECK_INTERVAL" elapsed_seconds=$((elapsed_seconds + CHECK_INTERVAL)) done echo "start job......" RAY_DATA_HOME=${RAY_DATA_HOME:-"/home/ma-user/work/verl-a2"} # /home/ma-user/work为ModelArts创建训练作业时设置的本地代码目录,用户可以自定义,但是脚本中的路径和创建训练作业时设置的路径必须保持一致。/verl-a2为存放代码、数据集、模型权重的OBS文件夹名称,用户可以自定义。 OUTPUT_DIR=${OUTPUT_DIR:-"/output_dir/verl-a2"} # /output_dir为创建训练作业挂载OBS(/verl-a2)时设置的云上挂载路径,用于保存预处理后的数据、checkpoint、训练日志,用户可以自定义 cd ${RAY_DATA_HOME} if [ ! -d "$OUTPUT_DIR/dataset/gsm8k/precessed" ]; then python3 gsm8k.py --local_dir "$OUTPUT_DIR/dataset/gsm8k/precessed" fi cp ${RAY_DATA_HOME}/run_qwen3-8b_npu_ma.sh /home/ma-user/verl/examples/grpo_trainer/run_qwen3-8b_npu_ma.sh cd /home/ma-user/verl bash examples/grpo_trainer/run_qwen3-8b_npu_ma.sh else echo "start workers,connect to master:$MASTER_NODE_IP:$MASTER_NODE_PORT" ray start \ --address="${MASTER_NODE_IP}:${MASTER_NODE_PORT}" fi
- run_qwen3-8b_npu_ma.sh脚本是用于启动VeRL强化学习训练任务,需要修改的路径如路径解释。
run_qwen3-8b_npu_ma.sh脚本具体内容如下:
set -x project_name='GRPO-Qwen3' exp_name='GRPO-Qwen3-8B-npu' gen_tp=2 RAY_DATA_HOME=${RAY_DATA_HOME:-"/home/ma-user/work/verl-a2"} # /home/ma-user/work为ModelArts创建训练作业时设置的本地代码目录,用户可以自定义,但是脚本中的路径和创建训练作业时设置的路径必须保持一致。/verl-a2为存放代码、数据集、模型权重的OBS文件夹名称,用户可以自定义名称。 OUTPUT_DIR=${OUTPUT_DIR:-"/output_dir/verl-a2"} # /output_dir为创建训练作业挂载OBS(/verl-a2)时设置的云上挂载路径,用于保存预处理后的数据、checkpoint、训练日志,用户可以自定义名称。 MODEL_PATH=${MODEL_PATH:-"${RAY_DATA_HOME}/models/Qwen3-8B"} # .../models/Qwen3-8B为OBS中存放模型的文件夹,用户可以自定义名称。 CKPTS_DIR=${CKPTS_DIR:-"${OUTPUT_DIR}/ckpts/${project_name}/${exp_name}"} # .../ckpts为OBS中存放训练输出的文件夹,用户可以自定义名称。 TRAIN_FILE=${TRAIN_FILE:-"${OUTPUT_DIR}/dataset/gsm8k/precessed/train.parquet"} # .../dataset/gsm8k为OBS中存放训练数据gsm8k的文件夹,用户可以自定义名称。/precessed/train.parquet,为预处理后的训练数据,用户无需修改。 TEST_FILE=${TEST_FILE:-"${OUTPUT_DIR}/dataset/gsm8k/precessed/test.parquet"} # .../dataset/gsm8k为OBS中存放训练数据gsm8k的文件夹,用户可以自定义名称。/precessed/test.parquet,为预处理后的测试数据,用户无需修改。 python3 -m verl.trainer.main_ppo \ algorithm.adv_estimator=grpo \ data.train_files="${TRAIN_FILE}" \ data.val_files="${TEST_FILE}" \ data.train_batch_size=16 \ data.max_prompt_length=2048 \ data.max_response_length=4096 \ data.filter_overlong_prompts=True \ data.truncation='error' \ actor_rollout_ref.model.path=${MODEL_PATH} \ actor_rollout_ref.actor.optim.lr=1e-6 \ actor_rollout_ref.model.use_remove_padding=True \ actor_rollout_ref.actor.ppo_mini_batch_size=8 \ actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=1 \ actor_rollout_ref.actor.use_kl_loss=True \ actor_rollout_ref.actor.kl_loss_coef=0.001 \ actor_rollout_ref.actor.kl_loss_type=low_var_kl \ actor_rollout_ref.actor.entropy_coeff=0 \ actor_rollout_ref.actor.use_torch_compile=False \ actor_rollout_ref.nccl_timeout=7200 \ actor_rollout_ref.ref.use_torch_compile=False \ actor_rollout_ref.model.enable_gradient_checkpointing=True \ actor_rollout_ref.actor.fsdp_config.param_offload=True \ actor_rollout_ref.actor.fsdp_config.optimizer_offload=True \ actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=1 \ actor_rollout_ref.rollout.tensor_model_parallel_size=${gen_tp} \ actor_rollout_ref.rollout.name=vllm \ actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \ actor_rollout_ref.rollout.n=2 \ actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=1 \ actor_rollout_ref.ref.fsdp_config.param_offload=True \ algorithm.use_kl_in_reward=False \ trainer.critic_warmup=0 \ trainer.logger=console \ trainer.project_name="${project_name}" \ trainer.experiment_name="${exp_name}" \ trainer.n_gpus_per_node=${NGPUS_PER_NODE} \ trainer.nnodes=${NNODES} \ trainer.default_local_dir=${CKPTS_DIR} \ trainer.resume_mode=auto \ actor_rollout_ref.actor.fsdp_config.forward_prefetch=True \ actor_rollout_ref.ref.fsdp_config.forward_prefetch=True \ ++actor_rollout_ref.actor.entropy_from_logits_with_chunking=True \ ++actor_rollout_ref.ref.entropy_from_logits_with_chunking=True \ trainer.val_before_train=True \ trainer.save_freq=50 \ trainer.test_freq=50 \ trainer.total_epochs=1 \ trainer.total_training_steps=100 2>&1 | tee ${OUTPUT_DIR}/run_qwen3-8b_npu_ma.log sleep 20s if tail -n 100 ${OUTPUT_DIR}/run_qwen3-8b_npu_ma.log | grep -q 'Final validation metrics'; then exit 0 else exit 1 fi
训练脚本(Qwen2.5-VL-32B-Instruct)
- run_train_32b.sh是训练作业启动脚本,其中包括数据预处理、启动训练任务输出训练日志等功能。run_train_32b.sh脚本启动时,会调用数据预处理脚本geometry3k.py和VeRL训练脚本run_qwen2_5_vl_32b_npu_ma.sh。
run_train_32b.sh脚本内容具体如下:
#!/bin/bash set -e export HCCL_IF_BASE_PORT=61000 export HCCL_NPU_SOCKET_PORT_RANGE="61000-61050" export HCCL_EXEC_TIMEOUT=60000 export HCCL_CONNECT_TIMEOUT=7200 unset https_proxy http_proxy proxy ASCEND_RT_VISIBLE_DEVICES source /usr/local/Ascend/ascend-toolkit/set_env.sh source /usr/local/Ascend/nnal/atb/set_env.sh # 按照实际情况修改 export NNODES=${VC_WORKER_NUM:-2} export NGPUS_PER_NODE=${MA_NUM_GPUS:-8} export MASTER_ADDR="${VC_WORKER_HOSTS%%,*}" export DOMAIN_IP=$(python -c "import socket; print(socket.gethostbyname('${MASTER_ADDR}'))") export MASTER_NODE_IP="${DOMAIN_IP}" export MASTER_NODE_PORT="6699" export USE_OPTIMIZED_MODEL=0 MAX_WAIT_SECONDS=900 CHECK_INTERVAL=3 get_current_ip() { local ip ip=$(ip -4 addr | grep -v '127.0.0.1' | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n1 2>/dev/null) if [[ -z "$ip" ]]; then ip=$(ip -4 addr | grep -v '127.0.0.1' | awk '/inet/ {gsub(/\/.*/,""); print $2}' | head -n1 2>/dev/null) fi echo "${ip:-}" } CURRENT_IP=$(get_current_ip) if [[ -z "$CURRENT_IP" ]]; then echo "无法提取当前节点IP" exit 1 fi echo "current ip:$CURRENT_IP,master ip:$MASTER_NODE_IP" if [ "$CURRENT_IP" = "$MASTER_NODE_IP" ]; then echo "start Ray master node..." ray stop --force ray start --head --port="$MASTER_NODE_PORT" elapsed_seconds=0 echo "wait $NNODES nodes..." while true; do current_node_num=$(ray status | grep -c "node_" || echo 0) if ! [[ "$current_node_num" =~ ^[0-9]+$ ]]; then current_node_num=0 fi echo "current_node_num:$current_node_num" if [ "$current_node_num" -ge "$NNODES" ]; then echo "all $NNODES workers node is ready." break fi if [ "$elapsed_seconds" -ge "$MAX_WAIT_SECONDS" ]; then echo "timeout($MAX_WAIT_SECONDS s),current_node_num:$current_node_num" ray stop --force exit 1 fi sleep "$CHECK_INTERVAL" elapsed_seconds=$((elapsed_seconds + CHECK_INTERVAL)) done echo "start job......" RAY_DATA_HOME=${RAY_DATA_HOME:-"/home/ma-user/work/verl-a2"} # /home/ma-user/work为ModelArts创建训练作业时设置的本地代码目录,用户可以自定义,但是脚本中的路径和创建训练作业时设置的路径必须保持一致。/verl-a2为存放代码、数据集、模型权重的OBS文件夹名称,用户可以自定义。 OUTPUT_DIR=${OUTPUT_DIR:-"/output_dir/verl-a2"} # /output_dir为创建训练作业挂载OBS(/verl-a2)时设置的云上挂载路径,用于保存预处理后的数据、checkpoint、训练日志,用户可以自定义 cd ${RAY_DATA_HOME} if [ ! -d "$OUTPUT_DIR/dataset/geometry3k/precessed" ]; then python3 geometry3k.py --local_dir "$OUTPUT_DIR/dataset/geometry3k/precessed" fi cp ${RAY_DATA_HOME}/run_qwen2_5_vl_32b_npu_ma.sh /home/ma-user/verl/examples/grpo_trainer/run_qwen2_5_vl_32b_npu_ma.sh cd /home/ma-user/verl bash examples/grpo_trainer/run_qwen2_5_vl_32b_npu_ma.sh else echo "start workers,connect to master:$MASTER_NODE_IP:$MASTER_NODE_PORT" ray start \ --address="${MASTER_NODE_IP}:${MASTER_NODE_PORT}" fi
- run_qwen2_5_vl_32b_npu_ma.sh脚本是用于启动VeRL强化学习训练任务,需要修改的路径如路径解释。
runqwen2_5_vl_32b_npu_ma.sh脚本具体内容如下:
set -x project_name='GRPO-Qwen2_5' exp_name='GRPO-Qwen2_5-VL-32B-npu' RAY_DATA_HOME=${RAY_DATA_HOME:-"/home/ma-user/work/verl-a2"} # /home/ma-user/work为ModelArts创建训练作业时设置的本地代码目录,用户可以自定义,但是脚本中的路径和创建训练作业时设置的路径必须保持一致。/verl-a2为存放代码、数据集、模型权重的OBS文件夹名称,用户可以自定义名称。 OUTPUT_DIR=${OUTPUT_DIR:-"/output_dir/verl-a2"} # /output_dir为创建训练作业挂载OBS(/verl-a2)时设置的云上挂载路径,用于保存预处理后的数据、checkpoint、训练日志,用户可以自定义名称。 MODEL_PATH=${MODEL_PATH:-"${RAY_DATA_HOME}/models/Qwen2.5-VL-32B-Instruct"} # .../models/Qwen2.5-VL-32B-Instruct为OBS中存放模型的文件夹,用户可以自定义名称。 CKPTS_DIR=${CKPTS_DIR:-"${OUTPUT_DIR}/ckpts/${project_name}/${exp_name}"} # .../ckpts为OBS中存放训练输出的文件夹,用户可以自定义名称。 TRAIN_FILE=${TRAIN_FILE:-"${OUTPUT_DIR}/dataset/geometry3k/precessed/train.parquet"} # .../dataset/geometry3k为OBS中存放训练数据geometry3k的文件夹,用户可以自定义名称。/precessed/train.parquet,为预处理后的训练数据,用户无需修改。 TEST_FILE=${TEST_FILE:-"${OUTPUT_DIR}/dataset/geometry3k/precessed/test.parquet"} # .../dataset/geometry3k为OBS中存放训练数据geometry3k的文件夹,用户可以自定义名称。/precessed/test.parquet,为预处理后的测试数据,用户无需修改。 ENGINE=${1:-vllm} # Some models are optimized by vllm ascend. While in some case, e.g. rlhf training, # the optimized model may not be suitable. In this case, set this value to 0 to disable the optimized model. export USE_OPTIMIZED_MODEL=0 python3 -m verl.trainer.main_ppo \ algorithm.adv_estimator=grpo \ data.train_files="${TRAIN_FILE}" \ data.val_files="${TEST_FILE}" \ data.train_batch_size=16 \ data.max_prompt_length=2048 \ data.max_response_length=4096 \ data.filter_overlong_prompts=True \ data.truncation='error' \ data.image_key=images \ actor_rollout_ref.model.path="${MODEL_PATH}" \ actor_rollout_ref.actor.optim.lr=1e-6 \ actor_rollout_ref.model.use_remove_padding=True \ actor_rollout_ref.actor.ppo_mini_batch_size=16 \ actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=1 \ actor_rollout_ref.actor.use_kl_loss=True \ actor_rollout_ref.actor.kl_loss_coef=0.01 \ actor_rollout_ref.actor.kl_loss_type=low_var_kl \ actor_rollout_ref.actor.entropy_coeff=0 \ actor_rollout_ref.actor.use_torch_compile=False \ actor_rollout_ref.nccl_timeout=7200 \ actor_rollout_ref.model.enable_gradient_checkpointing=True \ actor_rollout_ref.actor.fsdp_config.param_offload=True \ actor_rollout_ref.actor.fsdp_config.optimizer_offload=True \ actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=1 \ actor_rollout_ref.rollout.tensor_model_parallel_size=8 \ actor_rollout_ref.rollout.name=$ENGINE \ +actor_rollout_ref.rollout.engine_kwargs.vllm.disable_mm_preprocessor_cache=True \ actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \ actor_rollout_ref.rollout.enable_chunked_prefill=False \ actor_rollout_ref.rollout.enforce_eager=True \ actor_rollout_ref.rollout.free_cache_engine=True \ actor_rollout_ref.rollout.n=2 \ actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=1 \ actor_rollout_ref.ref.fsdp_config.param_offload=True \ algorithm.use_kl_in_reward=False \ trainer.critic_warmup=0 \ trainer.logger=console \ trainer.project_name="${project_name}" \ trainer.experiment_name="${exp_name}" \ trainer.n_gpus_per_node=${NGPUS_PER_NODE} \ trainer.nnodes=${NNODES} \ trainer.default_local_dir=${CKPTS_DIR} \ trainer.save_freq=50 \ trainer.test_freq=50 \ trainer.total_epochs=1 \ trainer.total_training_steps=100 2>&1 | tee ${OUTPUT_DIR}/run_qwen2_5_vl_32b_npu_ma.log sleep 20s if tail -n 100 ${OUTPUT_DIR}/run_qwen2_5_vl_32b_npu_ma.log | grep -q 'Final validation metrics'; then exit 0 else exit 1 fi