准备训练脚本
在VeOmni中不同类型的模型,训练步骤和脚本有所差异。在此分Dense模型,Moe模型,VL模型进行介绍。更多详细内容可以参考官网内容,参数设置可以参考 Arguments API Reference — VeOmni v0.1.0 documentation 获取更多详细信息。
如直接复制本文档提供的命令或脚本,请注意不同操作系统下换行符的差异。可以使用dos2unix xxx.sh命令将脚本内换行符转换为linux系统的换行符。
Dense模型
以Qwen3-32B的训练任务为例,准备对应模型的训练脚本,并上传至OBS桶中。
训练启动命令
ModelArts平台,训练任务启动命令如下:
cd /home/ma-user/VeOmni bash "${input_dir}/train.sh" /home/ma-user/VeOmni/tasks/train_torch.py /home/ma-user/VeOmni/configs/sft/qwen3_sft.yaml \ --model.model_path "${model_dir}/Qwen3-32B" \ --data.train_path "${dataset_dir}/tulu-first2000.parquet" \ --train.data_parallel_mode fsdp2 \ --train.init_device meta \ --train.output_dir "${output_dir}/Qwen3-32B-Base-sft-tulu" \ --train.save_epochs 3 \ --train.num_train_epochs 3 \ --train.ulysses_parallel_size 1 \ --data.max_seq_len 4096 \ --train.global_batch_size 16 \ --train.enable_activation_offload true
命令中参数详解如下:
- ${input_dir} :训练脚本的输入路径。配置obs路径到输入的环境变量input_dir,例如/home/ma-user/modelarts/user-job-dir/veomni/input。
- ${output_dir} :训练或者格式转化的输出路径。配置obs路径到输出的环境变量output_dir,例如/home/ma-user/modelarts/user-job-dir/veomni/output/veomni-output。
- ${model_dir}:训练模型的路径。
- ${dataset_dir} :处理过后的数据集的路径。
如直接复制本脚本,请注意不同操作系统下换行符的差异。可以使用dos2unix xxx.sh命令将脚本内换行符转换为linux系统的换行符。
训练脚本train.sh
以Qwen3-32B 为例,需要准备训练启动脚本train.sh。train.sh包含处理多节点通信逻辑、训练任务启动、转化为hf格式模型权重等内容。
#!/bin/bash set -x set -o pipefail export HCCL_IF_BASE_PORT=61000 export HCCL_NPU_SOCKET_PORT_RANGE="61000-61050" export HCCL_CONNECT_TIMEOUT=7200 # 最大超时时间,可以根据实际情况调整 export HCCL_EXEC_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 TOKENIZERS_PARALLELISM=false export TORCH_NCCL_AVOID_RECORD_STREAMS=1 # 获取本机 IP 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) export NNODES=${VC_WORKER_NUM:-1} export MASTER_PORT=${MASTER_PORT:=12345} export CHECK_LOG_DIR="/home/ma-user/logs" export CHECK_LOG_PATH="${CHECK_LOG_DIR}/veomni.log" #检查日志路径,无需修改 mkdir -p "$CHECK_LOG_DIR" # 自动计算 NODE_RANK 和 MASTER_ADDR if [ "$NNODES" -gt 1 ]; then MASTER_ADDR_RAW="${VC_WORKER_HOSTS%%,*}" export MASTER_ADDR=$(python3 -c "import socket; print(socket.gethostbyname('${MASTER_ADDR_RAW}'))") export NODE_RANK=$(python3 -c "import os, socket; hosts = os.environ.get('VC_WORKER_HOSTS', '').split(','); cur='${CURRENT_IP}'; print(next((i for i, h in enumerate(hosts) if socket.gethostbyname(h.strip()) == cur), 0))") echo "[INFO] Multi-Node: Master=$MASTER_ADDR, Rank=$NODE_RANK, Total=$NNODES" additional_args="--rdzv_endpoint=${MASTER_ADDR}:${MASTER_PORT}" else export MASTER_ADDR="localhost" export NODE_RANK=0 echo "[INFO] Single-Node Mode" additional_args="$additional_args --standalone" fi if command -v nvidia-smi &> /dev/null && nvidia-smi --list-gpus &> /dev/null; then # GPU if [[ -n "${CUDA_VISIBLE_DEVICES}" ]]; then NPROC_PER_NODE=${NPROC_PER_NODE:=$(echo "${CUDA_VISIBLE_DEVICES}" | tr ',' '\n' | wc -l)} else NPROC_PER_NODE=${NPROC_PER_NODE:=$(nvidia-smi --list-gpus | wc -l)} fi export NCCL_DEBUG=WARN else # NPU if [[ -n "${ASCEND_RT_VISIBLE_DEVICES}" ]]; then NPROC_PER_NODE=${NPROC_PER_NODE:=$(echo "${ASCEND_RT_VISIBLE_DEVICES}" | tr ',' '\n' | wc -l)} else NPROC_PER_NODE=${NPROC_PER_NODE:=$(ls -l /dev/davinci* | grep -v "davinci_manager" | wc -l)} fi # NPU env that may optimize performance export PYTORCH_NPU_ALLOC_CONF=${PYTORCH_NPU_ALLOC_CONF:='expandable_segments:True'} fi torchrun \ --nnodes=$NNODES \ --nproc-per-node=$NPROC_PER_NODE \ --node-rank=$NODE_RANK \ $additional_args $@ 2>&1 | tee "$CHECK_LOG_PATH" && \ echo "Training completes" >> "$CHECK_LOG_PATH" sleep 60s tail -n 500 "$CHECK_LOG_PATH" | grep -q -E "Training completes" || exit 1 #异常状态校验,返回状态码给平台 # 主节点进行hf格式转化 if [ "$NODE_RANK" -eq 0 ]; then echo "[Master] Master. Doing Convert ckpt to hf models" #此处与平台启动命令的$output_dir/Qwen3-32B-Base-sft-tulu保持一致 CHECKPOINT_ROOT="$output_dir/Qwen3-32B-Base-sft-tulu/checkpoints" #获取global_step最大的一次保存的ckpt结果转化为hf格式 LATEST_STEP_DIR=$(ls -v "$CHECKPOINT_ROOT" | grep "global_step_" | tail -n 1) FULL_LOAD_PATH="${CHECKPOINT_ROOT}/${LATEST_STEP_DIR}" echo "[INFO] Detected latest checkpoint: $FULL_LOAD_PATH" # 执行ckpt转化为hf格式,model-assets-dir是模型路径,load-dir是训练完保存的dcp路径,save-dir是最后保存的hf路径 if python /home/ma-user/VeOmni/scripts/merge_dcp_to_hf.py \ --model-assets-dir "$model_dir/Qwen3-32B" \ --save-dir "$output_dir/qwen3-32b-saved-hf_ckpt" \ --load-dir "$FULL_LOAD_PATH"; then echo "转换成功" echo "All steps completes" >> "$CHECK_LOG_PATH" else echo "[ERROR] Master. Convert ckpt to hf models FAILED!" >> "$CHECK_LOG_PATH" exit 1 # 失败则显式退出 fi else echo "[Worker $NODE_RANK] Task finished. Waiting for Master to complete conversion..." echo "All steps completes" >> "$CHECK_LOG_PATH" fi sleep 10s tail -n 500 "$CHECK_LOG_PATH" | grep -q -E "All steps completes" && exit 0 || exit 1 #异常状态校验,返回状态码给平台
Moe模型
以Qwen3-30B-A3B的训练任务为例,准备对应模型的训练脚本train.sh,并上传至OBS桶中。
训练启动命令
ModelArts平台,Moe模型训练任务启动命令如下:
cd /home/ma-user/VeOmni python3 /home/ma-user/VeOmni/scripts/moe_ckpt_merge/moe_merge.py \ --raw_hf_path "${model_dir}/Qwen3-30B-A3B" \ --merge_hf_path "${model_dir}/Qwen3-30B-A3B-merge" bash "${input_dir}/train.sh" /home/ma-user/VeOmni/tasks/train_torch.py /home/ma-user/VeOmni/configs/sft/qwen3_sft.yaml \ --model.model_path "${model_dir}/Qwen3-30B-A3B-merge" \ --model.moe_implementation fused \ --data.train_path "${dataset_dir}/tulu-first2000.parquet" \ --train.data_parallel_mode fsdp2 \ --train.init_device meta \ --train.output_dir "${output_dir}/Qwen3-30B-A3B-Base-sft-tulu" \ --train.save_epochs 3 \ --train.num_train_epochs 3 \ --train.expert_parallel_size 8 \ --data.max_seq_len 4096 \ --train.global_batch_size 16
命令中参数详解如下:
- ${input_dir} :训练脚本的输入路径。配置obs路径到输入的环境变量input_dir,例如/home/ma-user/modelarts/user-job-dir/veomni/input。
- ${output_dir} :训练或者格式转化的输出路径。配置obs路径到输出的环境变量output_dir,例如/home/ma-user/modelarts/user-job-dir/veomni-moe/output/veomni-output
- ${model_dir}:训练模型的路径。
- ${dataset_dir}: 处理过后的数据集的路径。
训练脚本
以Qwen3-30B-A3B 为例,准备训练启动脚本train.sh,并放在自己的OBS桶内的相应位置。
train.sh包含处理MoE模型专家合并、多节点通信逻辑、训练任务启动、转化为hf格式模型权重、MoE权重专家拆分等内容。
#!/bin/bash set -x set -o pipefail export HCCL_IF_BASE_PORT=61000 export HCCL_NPU_SOCKET_PORT_RANGE="61000-61050" export HCCL_CONNECT_TIMEOUT=7200 # 最大超时时间,可以根据实际情况调整 export HCCL_EXEC_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 TOKENIZERS_PARALLELISM=false export TORCH_NCCL_AVOID_RECORD_STREAMS=1 # 获取本机 IP 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) export NNODES=${VC_WORKER_NUM:-1} export MASTER_PORT=${MASTER_PORT:=12345} export CHECK_LOG_DIR="/home/ma-user/logs" export CHECK_LOG_PATH="${CHECK_LOG_DIR}/veomni.log" #检查日志路径,无需修改 mkdir -p "$CHECK_LOG_DIR" # 自动计算 NODE_RANK 和 MASTER_ADDR if [ "$NNODES" -gt 1 ]; then MASTER_ADDR_RAW="${VC_WORKER_HOSTS%%,*}" export MASTER_ADDR=$(python3 -c "import socket; print(socket.gethostbyname('${MASTER_ADDR_RAW}'))") export NODE_RANK=$(python3 -c "import os, socket; hosts = os.environ.get('VC_WORKER_HOSTS', '').split(','); cur='${CURRENT_IP}'; print(next((i for i, h in enumerate(hosts) if socket.gethostbyname(h.strip()) == cur), 0))") echo "[INFO] Multi-Node: Master=$MASTER_ADDR, Rank=$NODE_RANK, Total=$NNODES" additional_args="--rdzv_endpoint=${MASTER_ADDR}:${MASTER_PORT}" else export MASTER_ADDR="localhost" export NODE_RANK=0 echo "[INFO] Single-Node Mode" additional_args="$additional_args --standalone" fi if command -v nvidia-smi &> /dev/null && nvidia-smi --list-gpus &> /dev/null; then # GPU if [[ -n "${CUDA_VISIBLE_DEVICES}" ]]; then NPROC_PER_NODE=${NPROC_PER_NODE:=$(echo "${CUDA_VISIBLE_DEVICES}" | tr ',' '\n' | wc -l)} else NPROC_PER_NODE=${NPROC_PER_NODE:=$(nvidia-smi --list-gpus | wc -l)} fi export NCCL_DEBUG=WARN else # NPU if [[ -n "${ASCEND_RT_VISIBLE_DEVICES}" ]]; then NPROC_PER_NODE=${NPROC_PER_NODE:=$(echo "${ASCEND_RT_VISIBLE_DEVICES}" | tr ',' '\n' | wc -l)} else NPROC_PER_NODE=${NPROC_PER_NODE:=$(ls -l /dev/davinci* | grep -v "davinci_manager" | wc -l)} fi # NPU env that may optimize performance export PYTORCH_NPU_ALLOC_CONF=${PYTORCH_NPU_ALLOC_CONF:='expandable_segments:True'} fi torchrun \ --nnodes=$NNODES \ --nproc-per-node=$NPROC_PER_NODE \ --node-rank=$NODE_RANK \ $additional_args $@ 2>&1 | tee "$CHECK_LOG_PATH" && \ echo "Training completes" >> "$CHECK_LOG_PATH" sleep 30s tail -n 500 "$CHECK_LOG_PATH" | grep -q -E "Training completes" || exit 1 #异常状态校验,返回状态码给平台 # 主节点进行hf格式转化 if [ "$NODE_RANK" -eq 0 ]; then echo "[Master] Master. Doing Convert ckpt to hf models" # 与平台启动命令的$output_dir/Qwen3-30B-A3B-Base-sft-tulu保持一致 CHECKPOINT_ROOT="$output_dir/Qwen3-30B-A3B-Base-sft-tulu/checkpoints" # 获取global_step最大的一次保存的ckpt结果转化为hf格式 LATEST_STEP_DIR=$(ls -v "$CHECKPOINT_ROOT" | grep "global_step_" | tail -n 1) FULL_LOAD_PATH="${CHECKPOINT_ROOT}/${LATEST_STEP_DIR}" echo "[INFO] Detected latest checkpoint: $FULL_LOAD_PATH" # 执行ckpt转化为hf格式,其中save-dir为hf格式权重的保存路径,包含模型的其他配置文件 python /home/ma-user/VeOmni/scripts/merge_dcp_to_hf.py \ --model-assets-dir "$model_dir/Qwen3-30B-A3B" \ --save-dir "$output_dir/qwen3-30b-A3B-saved-hf_ckpt" \ --load-dir "$FULL_LOAD_PATH" 2>&1 | tee "$CHECK_LOG_PATH" && \ echo "Converting ckpt to hf completes" >> "$CHECK_LOG_PATH" sleep 5s tail -n 500 "$CHECK_LOG_PATH" | grep -q -E "Converting ckpt to hf completes" || exit 1 #异常状态校验,返回状态码给平台 # 对得到的hf权重拆分MoE专家 sleep 10s python /home/ma-user/VeOmni/scripts/moe_ckpt_merge/moe_spilt.py \ --merge_hf_path "$output_dir/qwen3-30b-A3B-saved-hf_ckpt" \ --split_hf_path "$output_dir/qwen3-30b-saved-hf_ckpt_split" 2>&1 | tee "$CHECK_LOG_PATH" && \ echo "All steps completes" >> "$CHECK_LOG_PATH" else echo "[Worker $NODE_RANK] Task finished. Waiting for Master to complete conversion..." echo "All steps completes" >> "$CHECK_LOG_PATH" fi sleep 30s tail -n 500 "$CHECK_LOG_PATH" | grep -q -E "All steps completes" && exit 0 || exit 1 #异常状态校验,返回状态码给平台
VL模型
以 Qwen3-VL-8B 的训练任务为例,准备对应模型的训练脚本,并上传至OBS桶中。
训练启动命令
ModelArts平台,VL模型训练任务启动命令如下:
cd /home/ma-user/VeOmni
bash "${input_dir}/train.sh" /home/ma-user/VeOmni/tasks/omni/train_qwen_vl.py /home/ma-user/VeOmni/configs/multimodal/qwen3_vl/qwen3_vl_dense.yaml \
--model.model_path "${model_dir}/Qwen3-VL-8B" \
--data.train_path "${dataset_dir}/sharegpt4v_instruct_gpt4-vision_cap100k_coco.json" \
--train.output_dir ${output_dir}/qwen3_vl_dense_sft \
--data.dataloader_type native \
--data.datasets_type iterable \
--data.source_name sharegpt4v_sft \
--data.num_workers 8 \
--train.micro_batch_size 1
命令中参数详解如下:
- ${input_dir} :训练脚本的输入路径。配置obs路径到输入的环境变量input_dir,例如/home/ma-user/modelarts/user-job-dir/veomni-vl/input
- ${output_dir} :训练或者格式转化的输出路径。配置obs路径到输出的环境变量output_dir,例如/home/ma-user/modelarts/user-job-dir/veomni-vl/output
- ${model_dir}:训练模型的路径。
- ${dataset_dir}: 处理过后的数据集的路径。
训练启动脚本
以 Qwen3-VL-8B 为例,准备训练脚本train.sh,并放在自己的OBS桶内的相应位置。
train.sh是训练启动脚本,包含处理多节点通信逻辑、训练任务启动、转化为hf格式模型权重等内容。
#!/bin/bash set -x set -o pipefail export HCCL_IF_BASE_PORT=61000 export HCCL_NPU_SOCKET_PORT_RANGE="61000-61050" export HCCL_CONNECT_TIMEOUT=7200 # 最大超时时间,可以根据实际情况调整 export HCCL_EXEC_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 TOKENIZERS_PARALLELISM=false export TORCH_NCCL_AVOID_RECORD_STREAMS=1 # 获取本机 IP 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) export NNODES=${VC_WORKER_NUM:-1} export MASTER_PORT=${MASTER_PORT:=12345} export CHECK_LOG_DIR="/home/ma-user/logs" export CHECK_LOG_PATH="${CHECK_LOG_DIR}/veomni.log" # 检查日志路径,无需修改 mkdir -p "$CHECK_LOG_DIR" # 自动计算 NODE_RANK 和 MASTER_ADDR if [ "$NNODES" -gt 1 ]; then MASTER_ADDR_RAW="${VC_WORKER_HOSTS%%,*}" export MASTER_ADDR=$(python3 -c "import socket; print(socket.gethostbyname('${MASTER_ADDR_RAW}'))") export NODE_RANK=$(python3 -c "import os, socket; hosts = os.environ.get('VC_WORKER_HOSTS', '').split(','); cur='${CURRENT_IP}'; print(next((i for i, h in enumerate(hosts) if socket.gethostbyname(h.strip()) == cur), 0))") echo "[INFO] Multi-Node: Master=$MASTER_ADDR, Rank=$NODE_RANK, Total=$NNODES" additional_args="--rdzv_endpoint=${MASTER_ADDR}:${MASTER_PORT}" else export MASTER_ADDR="localhost" export NODE_RANK=0 echo "[INFO] Single-Node Mode" additional_args="$additional_args --standalone" fi if command -v nvidia-smi &> /dev/null && nvidia-smi --list-gpus &> /dev/null; then # GPU if [[ -n "${CUDA_VISIBLE_DEVICES}" ]]; then NPROC_PER_NODE=${NPROC_PER_NODE:=$(echo "${CUDA_VISIBLE_DEVICES}" | tr ',' '\n' | wc -l)} else NPROC_PER_NODE=${NPROC_PER_NODE:=$(nvidia-smi --list-gpus | wc -l)} fi export NCCL_DEBUG=WARN else # NPU if [[ -n "${ASCEND_RT_VISIBLE_DEVICES}" ]]; then NPROC_PER_NODE=${NPROC_PER_NODE:=$(echo "${ASCEND_RT_VISIBLE_DEVICES}" | tr ',' '\n' | wc -l)} else NPROC_PER_NODE=${NPROC_PER_NODE:=$(ls -l /dev/davinci* | grep -v "davinci_manager" | wc -l)} fi # NPU env that may optimize performance export PYTORCH_NPU_ALLOC_CONF=${PYTORCH_NPU_ALLOC_CONF:='expandable_segments:True'} fi torchrun \ --nnodes=$NNODES \ --nproc-per-node=$NPROC_PER_NODE \ --node-rank=$NODE_RANK \ $additional_args $@ 2>&1 | tee "$CHECK_LOG_PATH" && \ echo "Training completes" >> "$CHECK_LOG_PATH" sleep 20s tail -n 500 "$CHECK_LOG_PATH" | grep -q -E "Training completes" || exit 1 # 异常状态校验,返回状态码给平台 # 主节点进行hf格式转化 if [ "$NODE_RANK" -eq 0 ]; then echo "[Master] Master. Doing Convert ckpt to hf models" CHECKPOINT_ROOT="$output_dir/qwen3_vl_dense_sft/checkpoints" # 与平台启动命令的$output_dir/qwen3_vl_dense_sft保持一致 LATEST_STEP_DIR=$(ls -v "$CHECKPOINT_ROOT" | grep "global_step_" | tail -n 1) # 获取global_step最大的一次保存的ckpt结果转化为hf格式 FULL_LOAD_PATH="${CHECKPOINT_ROOT}/${LATEST_STEP_DIR}" echo "[INFO] Detected latest checkpoint: $FULL_LOAD_PATH" # 执行ckpt转化为hf格式,model-assets-dir是config的路径,load-dir是训练完保存的dcp路径,save-dir是最后保存的hf路径 python /home/ma-user/VeOmni/scripts/merge_dcp_to_hf.py \ --model-assets-dir "$model_dir/Qwen3-VL-8B" \ --save-dir "$output_dir/qwen3-vl-8b-saved-hf_ckpt" \ --load-dir "$FULL_LOAD_PATH" 2>&1 | tee "$CHECK_LOG_PATH" && \ echo "All steps completes" >> "$CHECK_LOG_PATH" else echo "[Worker $NODE_RANK] Task finished. Waiting for Master to complete conversion..." echo "All steps completes" >> "$CHECK_LOG_PATH" fi sleep 10s tail -n 500 "$CHECK_LOG_PATH" | grep -q -E "All steps completes" && exit 0 || exit 1 # 异常状态校验,返回状态码给平台