
# 准备训练脚本
模型训练使用的训练脚本文件请参见[训练脚本（Qwen3-8B）]，[训练脚本（Qwen2.5-VL-32B-Instruct）]和[训练脚本（Qwen3-30B-A3B）]，需要用户参考本章节内容制作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
obs://verl/verl-a2/run_train_30b.sh
obs://verl/verl-a2/run_qwen3_30b_a3b_npu_ma.sh
```
表1训练脚本 
| **训练模型**                | **训练脚本**                     | 脚本说明                               |
|:---|:---|:---|
| Qwen3-8B                | run_train_8b.sh              | 训练作业启动脚本，其中包括数据预处理、启动训练任务输出训练日志等功能 |
| Qwen3-8B                | run_qwen3-8b_npu_ma.sh       | 用于启动VeRL强化学习训练任务。                  |
| Qwen2.5-VL-32B-Instruct | run_train_32b.sh             | 训练作业启动脚本，其中包括数据预处理、启动训练任务输出训练日志等功能 |
| Qwen2.5-VL-32B-Instruct | run_qwen2_5_vl_32b_npu_ma.sh | 用于启动VeRL强化学习训练任务。                  |
| Qwen3-30B-A3B           | run_train_30b.sh             | 训练作业启动脚本，其中包括数据预处理、启动训练任务输出训练日志等功能 |
| Qwen3-30B-A3B           | run_qwen3_30b_a3b_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官方文档](https://verl.readthedocs.io/en/latest/examples/config.html)。
 #### 训练脚本（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
  export VLLM_VERSION=0.16.0
  export PYTHONPATH="/home/ma-user/Megatron-LM:/home/ma-user/vllm${PYTHONPATH:+:${PYTHONPATH}}"
  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 \
      actor_rollout_ref.rollout.checkpoint_engine.update_weights_bucket_megabytes=4096 \
      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）
需要准备2个训练脚本文件run_train_32b.sh和run_qwen3-8b_npu_ma.sh。
- **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
  export VLLM_VERSION=0.16.0
  export PYTHONPATH="/home/ma-user/Megatron-LM:/home/ma-user/vllm${PYTHONPATH:+:${PYTHONPATH}}"
  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.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 \
      actor_rollout_ref.rollout.checkpoint_engine.update_weights_bucket_megabytes=4096 \
      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
  ```
  
 
 #### 训练脚本（Qwen3-30B-A3B）
需要准备2个训练脚本文件run_train_30b.sh和run_qwen3_30b_a3b_npu_ma.sh。
- **run_train_30b.sh** 是训练作业启动脚本，其中包括数据预处理、启动训练任务输出训练日志等功能。run_train_30b.sh脚本启动时，会调用数据预处理脚本gsm8k.py和VeRL训练脚本run_qwen3_30b_a3b_npu_ma.sh。
  run_train_30b.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
  export VLLM_VERSION=0.16.0
  export PYTHONPATH="/home/ma-user/Megatron-LM:/home/ma-user/vllm${PYTHONPATH:+:${PYTHONPATH}}"
  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_30b_a3b_npu_ma.sh /home/ma-user/verl/examples/grpo_trainer/run_qwen3_30b_a3b_npu_ma.sh
      cd /home/ma-user/verl
      cp verl/trainer/config/ppo_megatron_trainer.yaml .
      bash examples/grpo_trainer/run_qwen3_30b_a3b_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_30b_a3b_npu_ma.sh** 脚本是用于启动VeRL强化学习训练任务，需要修改的路径如[路径解释]。
  run_qwen3_30b_a3b_npu_ma.sh脚本具体内容如下：
  ```
  set -x
  project_name='GRPO-Qwen3'
  exp_name='GRPO-Qwen3-30B-A3B-npu'
  offload=True
  train_tp=4
  train_pp=2
  gen_tp=4
  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-30B-A3B"}                     # .../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 \
      --config-path=config \
      --config-name='ppo_megatron_trainer.yaml'\
      algorithm.adv_estimator=grpo \
      data.train_files="${TRAIN_FILE}" \
      data.val_files="${TEST_FILE}" \
      data.train_batch_size=32 \
      data.max_prompt_length=2048 \
      data.max_response_length=2048 \
      data.filter_overlong_prompts=True \
      data.truncation='error' \
      actor_rollout_ref.model.path=${MODEL_PATH} \
      actor_rollout_ref.actor.checkpoint.async_save=False \
      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.megatron.param_offload=${offload} \
      actor_rollout_ref.actor.megatron.optimizer_offload=${offload} \
      actor_rollout_ref.actor.megatron.grad_offload=${offload} \
      actor_rollout_ref.actor.megatron.pipeline_model_parallel_size=${train_pp} \
      actor_rollout_ref.actor.megatron.tensor_model_parallel_size=${train_tp} \
      actor_rollout_ref.actor.megatron.expert_model_parallel_size=8 \
      actor_rollout_ref.actor.megatron.expert_tensor_parallel_size=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.actor.policy_loss.loss_mode=gspo \
      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.5 \
      actor_rollout_ref.rollout.n=4 \
      actor_rollout_ref.ref.use_torch_compile=False \
      actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=1 \
      actor_rollout_ref.ref.megatron.pipeline_model_parallel_size=${train_pp} \
      actor_rollout_ref.ref.megatron.tensor_model_parallel_size=${train_tp} \
      actor_rollout_ref.ref.megatron.expert_model_parallel_size=4 \
      actor_rollout_ref.ref.megatron.expert_tensor_parallel_size=1 \
      actor_rollout_ref.ref.megatron.param_offload=${offload} \
      actor_rollout_ref.rollout.checkpoint_engine.update_weights_bucket_megabytes=4096 \
      reward.reward_manager.name=naive \
      actor_rollout_ref.ref.strategy=megatron \
      actor_rollout_ref.rollout.enforce_eager=True \
      ++actor_rollout_ref.ref.megatron.override_transformer_config.use_flash_attn=True \
      +actor_rollout_ref.actor.megatron.override_transformer_config.use_flash_attn=True \
      +actor_rollout_ref.actor.megatron.override_transformer_config.apply_rope_fusion=True \
      +actor_rollout_ref.actor.megatron.override_transformer_config.gradient_accumulation_fusion=True \
      +actor_rollout_ref.actor.megatron.override_transformer_config.use_fused_ring_attention_update=True \
      +actor_rollout_ref.actor.megatron.override_transformer_config.use_distributed_optimizer=True \
      +actor_rollout_ref.rollout.engine_kwargs.vllm.compilation_config.cudagraph_mode="FULL_AND_PIECEWISE" \
      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 \
      trainer.val_before_train=False \
      trainer.save_freq=50 \
      trainer.test_freq=50 \
      trainer.total_epochs=1 \
      trainer.total_training_steps=50 \
      actor_rollout_ref.rollout.calculate_log_probs=True \
      actor_rollout_ref.actor.router_replay.mode=R3 \
      actor_rollout_ref.rollout.enable_rollout_routing_replay=True 2>&1 | tee ${OUTPUT_DIR}/run_qwen3_30b_a3b_npu_ma_r3.log
  sleep 20s
  if tail -n 100 ${OUTPUT_DIR}/run_qwen3_30b_a3b_npu_ma.log | grep -q 'Final validation metrics'; then
      exit 0
  else
      exit 1
  fi
  ```
  
 
