准备训练脚本
在VeOmni中不同类型的模型,训练步骤和脚本有所差异。在此分Dense模型,Moe模型,VL模型进行介绍。更多详细内容可以参考官网内容,参数设置可以参考 Arguments API Reference — VeOmni v0.1.0 documentation 获取更多详细信息。
如直接复制本文档提供的命令或脚本,请注意不同操作系统下换行符的差异。可以使用dos2unix xxx.sh命令将脚本内换行符转换为linux系统的换行符。
Dense模型
以Qwen3-32B的训练任务为例,准备对应模型的训练脚本,并上传至OBS桶中。
Dense模型训练启动命令
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系统的换行符。
Dense模型训练脚本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桶中。
Moe模型训练启动命令
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}: 处理过后的数据集的路径。
Moe模型训练脚本train.sh
以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桶中。
VL模型训练启动命令
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}: 处理过后的数据集的路径。
VL模型训练启动脚本train.sh
以 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 # 异常状态校验,返回状态码给平台