This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
# AMD GPU Support
|
||||
|
||||
## 1. Environment setup
|
||||
|
||||
### 1.1 Base environment
|
||||
|
||||
Pull the ms-swift image built for the AMD ROCm stack, then start the container with the commands below.
|
||||
|
||||
If you need a newer ms-swift version, upgrade with pip or install from source code (adding `--no-deps` is recommended to avoid pulling in dependency upgrades that may cause issues).
|
||||
|
||||
```bash
|
||||
IMAGE_NAME=amdagi/modelscope:ubuntu22.04-rocm7.2.0-py312-torch2.10.0-vllm0.18.1-modelscope1.35.1-swift4.1.0
|
||||
docker pull ${IMAGE_NAME}
|
||||
|
||||
CONTAINER_NAME=swift_test
|
||||
docker run -it --network=host --ipc=host --privileged --group-add video \
|
||||
--device=/dev/dri --device=/dev/kfd \
|
||||
--shm-size 512G --ulimit memlock=-1 \
|
||||
--security-opt seccomp=unconfined --cap-add SYS_PTRACE \
|
||||
--name ${CONTAINER_NAME} \
|
||||
${IMAGE_NAME} \
|
||||
/bin/bash
|
||||
```
|
||||
|
||||
### 1.2 Environment check
|
||||
|
||||
- Confirm the availability of AMD devices for PyTorch in the container.
|
||||
|
||||
```bash
|
||||
python -c "import torch;print(torch.cuda.is_available())" # output: True
|
||||
```
|
||||
|
||||
- Inspect GPU topology and NUMA: `rocm-smi --showtopo`
|
||||
|
||||
```
|
||||
============================ ROCm System Management Interface ============================
|
||||
WARNING: AMD GPU device(s) is/are in a low-power state. Check power control/runtime_status
|
||||
|
||||
================================ Weight between two GPUs =================================
|
||||
GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7
|
||||
GPU0 0 15 15 15 15 15 15 15
|
||||
GPU1 15 0 15 15 15 15 15 15
|
||||
GPU2 15 15 0 15 15 15 15 15
|
||||
GPU3 15 15 15 0 15 15 15 15
|
||||
GPU4 15 15 15 15 0 15 15 15
|
||||
GPU5 15 15 15 15 15 0 15 15
|
||||
GPU6 15 15 15 15 15 15 0 15
|
||||
GPU7 15 15 15 15 15 15 15 0
|
||||
|
||||
================================= Hops between two GPUs ==================================
|
||||
GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7
|
||||
GPU0 0 1 1 1 1 1 1 1
|
||||
GPU1 1 0 1 1 1 1 1 1
|
||||
GPU2 1 1 0 1 1 1 1 1
|
||||
GPU3 1 1 1 0 1 1 1 1
|
||||
GPU4 1 1 1 1 0 1 1 1
|
||||
GPU5 1 1 1 1 1 0 1 1
|
||||
GPU6 1 1 1 1 1 1 0 1
|
||||
GPU7 1 1 1 1 1 1 1 0
|
||||
|
||||
=============================== Link Type between two GPUs ===============================
|
||||
GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7
|
||||
GPU0 0 XGMI XGMI XGMI XGMI XGMI XGMI XGMI
|
||||
GPU1 XGMI 0 XGMI XGMI XGMI XGMI XGMI XGMI
|
||||
GPU2 XGMI XGMI 0 XGMI XGMI XGMI XGMI XGMI
|
||||
GPU3 XGMI XGMI XGMI 0 XGMI XGMI XGMI XGMI
|
||||
GPU4 XGMI XGMI XGMI XGMI 0 XGMI XGMI XGMI
|
||||
GPU5 XGMI XGMI XGMI XGMI XGMI 0 XGMI XGMI
|
||||
GPU6 XGMI XGMI XGMI XGMI XGMI XGMI 0 XGMI
|
||||
GPU7 XGMI XGMI XGMI XGMI XGMI XGMI XGMI 0
|
||||
|
||||
======================================= Numa Nodes =======================================
|
||||
GPU[0] : (Topology) Numa Node: 0
|
||||
GPU[0] : (Topology) Numa Affinity: 0
|
||||
GPU[1] : (Topology) Numa Node: 0
|
||||
GPU[1] : (Topology) Numa Affinity: 0
|
||||
GPU[2] : (Topology) Numa Node: 0
|
||||
GPU[2] : (Topology) Numa Affinity: 0
|
||||
GPU[3] : (Topology) Numa Node: 0
|
||||
GPU[3] : (Topology) Numa Affinity: 0
|
||||
GPU[4] : (Topology) Numa Node: 1
|
||||
GPU[4] : (Topology) Numa Affinity: 1
|
||||
GPU[5] : (Topology) Numa Node: 1
|
||||
GPU[5] : (Topology) Numa Affinity: 1
|
||||
GPU[6] : (Topology) Numa Node: 1
|
||||
GPU[6] : (Topology) Numa Affinity: 1
|
||||
GPU[7] : (Topology) Numa Node: 1
|
||||
GPU[7] : (Topology) Numa Affinity: 1
|
||||
================================== End of ROCm SMI Log ===================================
|
||||
```
|
||||
|
||||
- Check GPU utilization and VRAM usage (`rocm-smi` or `rocm-smi -u --showmeminfo vram`):
|
||||
|
||||
```
|
||||
# output of 'rocm-smi'
|
||||
============================================ ROCm System Management Interface ============================================
|
||||
====================================================== Concise Info ======================================================
|
||||
Device Node IDs Temp Power Partitions SCLK MCLK Fan Perf PwrCap VRAM% GPU%
|
||||
(DID, GUID) (Junction) (Socket) (Mem, Compute, ID)
|
||||
==========================================================================================================================
|
||||
0 2 0x74a2, 1017 43.0°C 155.0W NPS1, SPX, 0 94Mhz 900Mhz 0% auto 650.0W 0% 0%
|
||||
1 3 0x74a2, 47713 41.0°C 155.0W NPS1, SPX, 0 91Mhz 900Mhz 0% auto 650.0W 0% 0%
|
||||
2 4 0x74a2, 37449 45.0°C 159.0W NPS1, SPX, 0 95Mhz 900Mhz 0% auto 650.0W 0% 0%
|
||||
3 5 0x74a2, 11217 41.0°C 155.0W NPS1, SPX, 0 95Mhz 900Mhz 0% auto 650.0W 0% 0%
|
||||
4 6 0x74a2, 41880 44.0°C 160.0W NPS1, SPX, 0 91Mhz 900Mhz 0% auto 650.0W 0% 0%
|
||||
5 7 0x74a2, 6656 42.0°C 157.0W NPS1, SPX, 0 95Mhz 900Mhz 0% auto 650.0W 0% 0%
|
||||
6 8 0x74a2, 12840 45.0°C 160.0W NPS1, SPX, 0 96Mhz 900Mhz 0% auto 650.0W 0% 0%
|
||||
7 9 0x74a2, 35760 43.0°C 158.0W NPS1, SPX, 0 107Mhz 900Mhz 0% auto 650.0W 0% 0%
|
||||
==========================================================================================================================
|
||||
================================================== End of ROCm SMI Log ===================================================
|
||||
|
||||
# output of 'rocm-smi -u --showmeminfo vram'
|
||||
============================ ROCm System Management Interface ============================
|
||||
=================================== % time GPU is busy ===================================
|
||||
GPU[0] : GPU use (%): 0
|
||||
GPU[0] : GFX Activity: 3862538534
|
||||
GPU[1] : GPU use (%): 0
|
||||
GPU[1] : GFX Activity: 4053246251
|
||||
GPU[2] : GPU use (%): 0
|
||||
GPU[2] : GFX Activity: 3114103535
|
||||
GPU[3] : GPU use (%): 0
|
||||
GPU[3] : GFX Activity: 4026776444
|
||||
GPU[4] : GPU use (%): 0
|
||||
GPU[4] : GFX Activity: 1224255679
|
||||
GPU[5] : GPU use (%): 0
|
||||
GPU[5] : GFX Activity: 1191191242
|
||||
GPU[6] : GPU use (%): 0
|
||||
GPU[6] : GFX Activity: 1184652679
|
||||
GPU[7] : GPU use (%): 0
|
||||
GPU[7] : GFX Activity: 2145209382
|
||||
==========================================================================================
|
||||
================================== Memory Usage (Bytes) ==================================
|
||||
GPU[0] : VRAM Total Memory (B): 206141652992
|
||||
GPU[0] : VRAM Total Used Memory (B): 297611264
|
||||
GPU[1] : VRAM Total Memory (B): 206141652992
|
||||
GPU[1] : VRAM Total Used Memory (B): 297623552
|
||||
GPU[2] : VRAM Total Memory (B): 206141652992
|
||||
GPU[2] : VRAM Total Used Memory (B): 297623552
|
||||
GPU[3] : VRAM Total Memory (B): 206141652992
|
||||
GPU[3] : VRAM Total Used Memory (B): 297623552
|
||||
GPU[4] : VRAM Total Memory (B): 206141652992
|
||||
GPU[4] : VRAM Total Used Memory (B): 297623552
|
||||
GPU[5] : VRAM Total Memory (B): 206141652992
|
||||
GPU[5] : VRAM Total Used Memory (B): 297623552
|
||||
GPU[6] : VRAM Total Memory (B): 206141652992
|
||||
GPU[6] : VRAM Total Used Memory (B): 297623552
|
||||
GPU[7] : VRAM Total Memory (B): 206141652992
|
||||
GPU[7] : VRAM Total Used Memory (B): 297623552
|
||||
==========================================================================================
|
||||
================================== End of ROCm SMI Log ===================================
|
||||
```
|
||||
|
||||
## 2. Run examples
|
||||
|
||||
### 2.1 Full fine-tuning Qwen3.5 with Megatron-Swift
|
||||
|
||||
AMD GPUs often have large VRAM, so you can tune several knobs together to improve training throughput:
|
||||
|
||||
- **Parallelism tuning**: Large per-GPU memory lets you reduce communication from aggressive splits (prefer tuning PP/EP before TP).
|
||||
- **Optimizer CPU offload**: If VRAM allows, disable with `--optimizer_cpu_offload false`.
|
||||
- **Activation / gradient checkpointing**: If VRAM allows, use `--recompute_granularity none`, or `--recompute_granularity selective` with `--recompute_modules` for finer control.
|
||||
- **MoE models**: Set `export NVTE_USE_GROUPED_GEMM_TRITON=1` to use grouped GEMM triton kernel.
|
||||
- **Models with GatedDeltaNet**: Set `USE_MCORE_GDN=1` to use the Megatron-Core implementation.
|
||||
- **Stability on some AMD GPUs**: Set `export HSA_NO_SCRATCH_RECLAIM=1` to avoid known issues and stabilize performance.
|
||||
|
||||
Single-node training:
|
||||
|
||||
```bash
|
||||
export HSA_NO_SCRATCH_RECLAIM=1
|
||||
export NVTE_USE_GROUPED_GEMM_TRITON=1
|
||||
|
||||
output_dir=${PWD}/megatron_output/Qwen3.5-35B-A3B
|
||||
mkdir -p ${output_dir}
|
||||
current_time=$(date "+%Y.%m.%d-%H.%M.%S")
|
||||
log_file=${output_dir}/"1node_full_megatron_Qwen3.5-35B-A3B_${current_time}.log"
|
||||
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
NPROC_PER_NODE=8 \
|
||||
MAX_PIXELS=1003520 \
|
||||
VIDEO_MAX_PIXELS=50176 \
|
||||
FPS_MAX_FRAMES=12 \
|
||||
SKIP_MULTIMODAL_MTP_VALIDATION=1 \
|
||||
USE_MCORE_GDN=1 \
|
||||
megatron sft \
|
||||
--model Qwen/Qwen3.5-35B-A3B \
|
||||
--dataset 'AI-ModelScope/LongAlpaca-12k' \
|
||||
--save_safetensors true \
|
||||
--load_from_cache_file true \
|
||||
--tuner_type full \
|
||||
--add_non_thinking_prefix true \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--tensor_model_parallel_size 1 \
|
||||
--pipeline_model_parallel_size 1 \
|
||||
--expert_model_parallel_size 8 \
|
||||
--sequence_parallel true \
|
||||
--moe_permute_fusion true \
|
||||
--moe_grouped_gemm true \
|
||||
--moe_shared_expert_overlap true \
|
||||
--moe_aux_loss_coeff 1e-6 \
|
||||
--moe_expert_capacity_factor 2 \
|
||||
--micro_batch_size 1 \
|
||||
--global_batch_size 8 \
|
||||
--recompute_granularity selective \
|
||||
--recompute_modules core_attn mlp moe \
|
||||
--gradient_accumulation_fusion false \
|
||||
--num_train_epochs 500 \
|
||||
--group_by_length true \
|
||||
--finetune true \
|
||||
--freeze_llm false \
|
||||
--freeze_vit false \
|
||||
--freeze_aligner false \
|
||||
--cross_entropy_loss_fusion true \
|
||||
--lr 1e-5 \
|
||||
--lr_warmup_fraction 0.05 \
|
||||
--min_lr 1e-6 \
|
||||
--weight_decay 0.1 \
|
||||
--adam_beta2 0.95 \
|
||||
--eval_steps 500 \
|
||||
--save_steps 500 \
|
||||
--save_total_limit 10 \
|
||||
--logging_steps 1 \
|
||||
--max_length 16384 \
|
||||
--dataloader_num_workers 8 \
|
||||
--dataset_num_proc 8 \
|
||||
--no_save_optim true \
|
||||
--no_save_rng true \
|
||||
--optimizer_cpu_offload false \
|
||||
--attention_backend flash \
|
||||
--padding_free false \
|
||||
--output_dir ${output_dir} \
|
||||
2>&1 | tee ${log_file}
|
||||
```
|
||||
|
||||
Multi-node training:
|
||||
|
||||
```bash
|
||||
export NNODES=2 # example: 2 nodes
|
||||
export NODE_RANK=0 # 0 on master, 1 on workers
|
||||
export MASTER_ADDR=<MASTER_NODE_IP> # set to master node IP
|
||||
export MASTER_PORT=29500 # communication port
|
||||
export NCCL_SOCKET_IFNAME=ens50f1np1 # actual NIC name, check with ifconfig
|
||||
export GLOO_SOCKET_IFNAME=ens50f1np1 # same as above
|
||||
export NCCL_IB_HCA=mlx5_0,mlx5_1,mlx5_2,mlx5_3 # IB HCAs, check with ibv_devices
|
||||
export NCCL_IB_GID_INDEX=3
|
||||
|
||||
# Main training script below: same as single-node script above
|
||||
...
|
||||
```
|
||||
|
||||
### 2.2 Reinforcement learning training for Qwen3.5 with Megatron-Swift
|
||||
|
||||
```bash
|
||||
# Single-node training example
|
||||
export HSA_NO_SCRATCH_RECLAIM=1
|
||||
export NVTE_USE_GROUPED_GEMM_TRITON=1
|
||||
|
||||
SYSTEM_PROMPT="""You are a helpful math assistant. Solve the problem step by step and put your final answer within \\boxed{}."""
|
||||
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
|
||||
NPROC_PER_NODE=8 \
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
megatron rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen3.5-35B-A3B \
|
||||
--save_safetensors true \
|
||||
--enable_thinking false \
|
||||
--merge_lora true \
|
||||
--context_parallel_size 1 \
|
||||
--tensor_model_parallel_size 1 \
|
||||
--expert_model_parallel_size 8 \
|
||||
--pipeline_model_parallel_size 1 \
|
||||
--moe_permute_fusion true \
|
||||
--dataset open-r1/DAPO-Math-17k-Processed \
|
||||
--system "$SYSTEM_PROMPT" \
|
||||
--num_train_epochs 1 \
|
||||
--global_batch_size 64 \
|
||||
--micro_batch_size 1 \
|
||||
--steps_per_generation 2 \
|
||||
--num_generations 8 \
|
||||
--reward_funcs accuracy \
|
||||
--use_vllm true \
|
||||
--vllm_mode colocate \
|
||||
--vllm_gpu_memory_utilization 0.5 \
|
||||
--vllm_tensor_parallel_size 2 \
|
||||
--vllm_max_model_len 9192 \
|
||||
--max_length 1000 \
|
||||
--max_completion_length 8192 \
|
||||
--tuner_type lora \
|
||||
--target_modules all-linear \
|
||||
--lr 5e-5 \
|
||||
--bf16 true \
|
||||
--beta 0.00 \
|
||||
--epsilon 0.2 \
|
||||
--epsilon_high 0.28 \
|
||||
--dynamic_sample false \
|
||||
--overlong_filter true \
|
||||
--loss_type grpo \
|
||||
--sleep_level 1 \
|
||||
--offload_model true \
|
||||
--offload_bridge false \
|
||||
--offload_optimizer true \
|
||||
--logging_steps 1 \
|
||||
--recompute_granularity none \
|
||||
--gradient_accumulation_fusion false \
|
||||
--finetune \
|
||||
--dataloader_num_workers 8 \
|
||||
--dataset_num_proc 8 \
|
||||
--no_save_optim \
|
||||
--no_save_rng \
|
||||
--save_steps 20 \
|
||||
--attention_backend flash \
|
||||
--moe_expert_capacity_factor 2 \
|
||||
--temperature 1.0 \
|
||||
--padding_free false \
|
||||
--sequence_parallel true \
|
||||
--log_completions true \
|
||||
--report_to tensorboard
|
||||
```
|
||||
|
||||
## Known issues
|
||||
|
||||
- **Reinforcement learning**: If vLLM is the inference engine, use vLLM ≥ 0.11.0. It is recommended to use ROCm 7.0 or the image we provide to avoid the sleep mode memory leak issue.
|
||||
- When using [Ray Megatron](../../source/Instruction/Ray.md) instead of `torchrun` for multi-GPU/Node training, don't set `CUDA_VISIBLE_DEVICES`/`HIP_VISIBLE_DEVICES` etc. to avoid conflicts.
|
||||
- **MoE training**: Set `NVTE_USE_GROUPED_GEMM_TRITON=1` and `--gradient_accumulation_fusion false` to reduce occasional GPU hangs.
|
||||
@@ -0,0 +1,213 @@
|
||||
# Elastic
|
||||
|
||||
|
||||
## Installing Dependencies
|
||||
|
||||
Deploy a K8S cluster and deploy [DLRover](https://github.com/intelligent-machine-learning/dlrover) in the cluster, and install the required packages using `pip install dlrover && pip install tornado && pip install kubernetes && pip install ms-swift`
|
||||
|
||||
Other dependencies and versions verified through repeated testing in the training image:
|
||||
deepspeed 0.16.5 (refer to this [PR](https://github.com/deepspeedai/DeepSpeed/pull/7585/files) to fix issues related to universal checkpoint)
|
||||
pytorch 2.6.0
|
||||
|
||||
|
||||
## How to Start
|
||||
|
||||
Enable elastic training by adding the `deepspeed_elastic` callback (optionally `graceful_exit`) in `--callbacks`, and configure DeepSpeed elasticity settings.
|
||||
|
||||
The command format is dlrover-run + DLrover command parameters + Swift startup command + Swift parameters.dlrover-run behaves like torchrun for most arguments, except for its custom parameters.
|
||||
|
||||
The dlrover-run arguments are as follows:
|
||||
|
||||
```
|
||||
usage: dlrover-run [-h] [--nnodes NNODES] [--nproc-per-node NPROC_PER_NODE]
|
||||
[--rdzv-backend RDZV_BACKEND] [--rdzv-endpoint RDZV_ENDPOINT] [--rdzv-id RDZV_ID]
|
||||
[--rdzv-conf RDZV_CONF] [--standalone] [--max-restarts MAX_RESTARTS]
|
||||
[--monitor-interval MONITOR_INTERVAL] [--start-method {spawn,fork,forkserver}]
|
||||
[--role ROLE] [-m] [--no-python] [--run-path] [--log-dir LOG_DIR] [-r REDIRECTS]
|
||||
[-t TEE] [--local-ranks-filter LOCAL_RANKS_FILTER] [--node-rank NODE_RANK]
|
||||
[--master-addr MASTER_ADDR] [--master-port MASTER_PORT] [--local-addr LOCAL_ADDR]
|
||||
[--logs-specs LOGS_SPECS] [--precheck {0,1,2}] [--node_unit NODE_UNIT]
|
||||
[--auto_config] [--auto_tunning] [--exclude-straggler] [--save_at_breakpoint]
|
||||
[--accelerator {nvidia.com/gpu,ascend-npu}] [--training_port TRAINING_PORT]
|
||||
[--switchbox-check] [--box-pairs PAIR [PAIR ...]] [--min-bandwidth MIN_BANDWIDTH]
|
||||
[--min-channels MIN_CHANNELS] [--numa-affinity] [--network-check]
|
||||
[--comm-perf-test] [--ucp_device_type UCP_DEVICE_TYPE]
|
||||
training_script
|
||||
|
||||
```
|
||||
In elastic training, the parameters you may pay attention to focus on are:
|
||||
|
||||
--nnodes NNODES
|
||||
Number of nodes, or the range of nodes in the form <minimum_nodes>:<maximum_nodes>.
|
||||
|
||||
--nproc-per-node NPROC_PER_NODE
|
||||
Number of processes per node.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
model=your model path
|
||||
dataset=your dataset
|
||||
output= your output dir
|
||||
export CUDA_VISIBLE_DEVICES=0 # Set according to the actual GPU usage
|
||||
deepspeed_config_or_type=deepspeed type or configuration file path, e.g., zero1 or /xxx/ms-swift/swift/llm/ds_config/zero1.json
|
||||
|
||||
dlrover-run --nnodes 1:$NODE_NUM --nproc_per_node=1 \
|
||||
/opt/conda/lib/python3.10/site-packages/swift/cli/sft.py --model $model \
|
||||
--model_type qwen3 \
|
||||
--tuner_type lora \
|
||||
--torch_dtype bfloat16 \
|
||||
--dataset $dataset \
|
||||
--num_train_epochs 4 \
|
||||
--per_device_train_batch_size 1 \
|
||||
--per_device_eval_batch_size 1 \
|
||||
--learning_rate 5e-7 \
|
||||
--gradient_accumulation_steps 8 \
|
||||
--eval_steps 500 \
|
||||
--save_steps 10 \
|
||||
--save_total_limit 20 \
|
||||
--logging_steps 1 \
|
||||
--output_dir $output \
|
||||
--warmup_ratio 0.01 \
|
||||
--dataloader_num_workers 4 \
|
||||
--temperature 1.0 \
|
||||
--system 'You are a helpful assistant.' \
|
||||
--lora_rank 8 \
|
||||
--lora_alpha 32 \
|
||||
--target_modules all-linear \
|
||||
--dataset_num_proc 1 \
|
||||
--use_flash_ckpt true \
|
||||
--callbacks deepspeed_elastic graceful_exit \
|
||||
--deepspeed $deepspeed_config_or_type \
|
||||
```
|
||||
|
||||
## Configuration
|
||||
By default, the zero1 configuration is as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"fp16": {
|
||||
"enabled": "auto",
|
||||
"loss_scale": 0,
|
||||
"loss_scale_window": 1000,
|
||||
"initial_scale_power": 16,
|
||||
"hysteresis": 2,
|
||||
"min_loss_scale": 1
|
||||
},
|
||||
|
||||
"bf16": {
|
||||
"enabled": "auto"
|
||||
},
|
||||
|
||||
"zero_optimization": {
|
||||
"stage": 1,
|
||||
"offload_optimizer": {
|
||||
"device": "none",
|
||||
"pin_memory": true
|
||||
},
|
||||
"allgather_partitions": true,
|
||||
"allgather_bucket_size": 2e8,
|
||||
"overlap_comm": false,
|
||||
"reduce_scatter": true,
|
||||
"reduce_bucket_size": 2e8,
|
||||
"contiguous_gradients": true
|
||||
},
|
||||
|
||||
"gradient_accumulation_steps": "auto",
|
||||
"gradient_clipping": "auto",
|
||||
"steps_per_print": 2000,
|
||||
"train_batch_size": "auto",
|
||||
"train_micro_batch_size_per_gpu": "auto",
|
||||
"wall_clock_breakdown": false,
|
||||
"elasticity": {
|
||||
"ignore_non_elastic_batch_info": true,
|
||||
"enabled": true,
|
||||
"max_train_batch_size": 8,
|
||||
"micro_batch_sizes": [
|
||||
4,
|
||||
2
|
||||
],
|
||||
"min_gpus": 1,
|
||||
"max_gpus": 4,
|
||||
"min_time": 20,
|
||||
"version": 0.1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If users need custom configurations, they can specify the path to the custom zero1.json file in the deepspeed_config_or_type parameter. The elasticity-related configuration is as follows:
|
||||
```json
|
||||
...
|
||||
|
||||
"elasticity": {
|
||||
"ignore_non_elastic_batch_info": true,
|
||||
"enabled": true,
|
||||
"max_train_batch_size": 8,
|
||||
"micro_batch_sizes": [
|
||||
4,
|
||||
2
|
||||
],
|
||||
"min_gpus": 1,
|
||||
"max_gpus": 4,
|
||||
"min_time": 20,
|
||||
"version": 0.1
|
||||
}
|
||||
```
|
||||
|
||||
- ignore_non_elastic_batch_info:Indicates that the batch size configurations outside the elasticity settings will be ignored. During training, the batch size and related parameters will be dynamically adjusted based on the number of training processes.
|
||||
Calculation principle:
|
||||
global-training-batch-size = micro-batch-size * gradient-accumulation-steps * world-size
|
||||
- max_train_batch_size: Maximum batch size
|
||||
- micro_batch_sizes:List of allowed per-GPU micro-batch sizes under elasticity; candidates for train_micro_batch_size_per_gpu.
|
||||
- min_gpus:Minimum number of GPUs.
|
||||
- max_gpus:Maximum number of GPUs.
|
||||
For more details, see: [Deepspeed](https://www.deepspeed.ai/docs/config-json/#elastic-training-config-v01-and-v02)
|
||||
|
||||
## Starting Training
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: elastic.iml.github.io/v1alpha1
|
||||
kind: ElasticJob
|
||||
metadata:
|
||||
name: deepspeed-elastic-swift
|
||||
namespace: dlrover
|
||||
spec:
|
||||
distributionStrategy: AllreduceStrategy
|
||||
optimizeMode: single-job
|
||||
replicaSpecs:
|
||||
worker:
|
||||
replicas: 1 # This should match the maximum value of --nnodes NNODES in the startup command
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: main
|
||||
image: #【Training image, needs to have deepspeed, dlrover, and swift installed】
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- sh start.sh # Startup script
|
||||
resources:
|
||||
limits:
|
||||
cpu: '8'
|
||||
memory: 16Gi
|
||||
nvidia.com/gpu: '1'
|
||||
volumeMounts:
|
||||
- mountPath: /model
|
||||
name: volume-model
|
||||
- mountPath: /dev/shm
|
||||
name: volume-shm
|
||||
restartPolicy: Never
|
||||
volumes:
|
||||
- hostPath:
|
||||
path: /model
|
||||
type: Directory
|
||||
name: volume-model
|
||||
- emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 200Gi
|
||||
name: volume-shm
|
||||
|
||||
```
|
||||
@@ -0,0 +1,212 @@
|
||||
# Embedding Training
|
||||
|
||||
SWIFT has already supported the training of embedding models, including both pure text and multimodal types. Currently supported models include:
|
||||
|
||||
1. modernbert embedding model
|
||||
- [ModelScope](https://modelscope.cn/models/iic/gte-modernbert-base) [Hugging Face](https://huggingface.co/Alibaba-NLP/gte-modernbert-base)
|
||||
2. gte embedding models
|
||||
- 1.5B: [ModelScope](https://www.modelscope.cn/models/iic/gte_Qwen2-1.5B-instruct) [Hugging Face](https://huggingface.co/Alibaba-NLP/gte-Qwen2-1.5B-instruct)
|
||||
- 7B: [ModelScope](https://www.modelscope.cn/models/iic/gte_Qwen2-7B-instruct) [Hugging Face](https://huggingface.co/Alibaba-NLP/gte-Qwen2-7B-instruct)
|
||||
3. gme embedding models
|
||||
- 2B: [ModelScope](https://www.modelscope.cn/models/iic/gme-Qwen2-VL-2B-Instruct) [Hugging Face](https://huggingface.co/Alibaba-NLP/gme-Qwen2-VL-2B-Instruct)
|
||||
- 7B: [ModelScope](https://www.modelscope.cn/models/iic/gme-Qwen2-VL-7B-Instruct) [Hugging Face](https://huggingface.co/Alibaba-NLP/gme-Qwen2-VL-7B-Instruct)
|
||||
4. qwen3-embedding models
|
||||
- 0.6B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-Embedding-0.6B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B)
|
||||
- 4B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-Embedding-4B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-Embedding-4B)
|
||||
- 8B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-Embedding-8B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-Embedding-8B)
|
||||
5. qwen3-vl-embedding models
|
||||
- 2B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-VL-Embedding-2B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-VL-Embedding-2B)
|
||||
- 8B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-VL-Embedding-8B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-VL-Embedding-8B)
|
||||
|
||||
Developers can integrate their own models by ensuring the model forward output satisfies:
|
||||
|
||||
```text
|
||||
{"last_hidden_state": some-embedding-tensor}
|
||||
```
|
||||
|
||||
The return value should be a JSON with a `last_hidden_state` key, where the value is an embedding tensor. For the input part, you can use our already supported templates. Users can also specify the
|
||||
|
||||
```shell
|
||||
--task_type embedding
|
||||
```
|
||||
parameter to convert any other model into an embedding model for training.
|
||||
|
||||
It should be noted that the embedding models currently supported by SWIFT are all based on pure text or multimodal LLMs, and CLIP-type model training is not currently supported.
|
||||
|
||||
Additionally, all embedding models supported by SWIFT have normalization added at the end of the model forward pass. If you add new models yourself, please remember to include a normalization layer.
|
||||
|
||||
## Loss
|
||||
|
||||
The Embedding models supported by SWIFT currently can use the following loss functions:
|
||||
|
||||
- **cosine_similarity**: Cosine similarity loss, which calculates the similarity between two embeddings and fits based on the label value. It is effectively an MSE loss.
|
||||
- **contrastive**: Contrastive learning loss with adjustable margin. Labels are only supported as 0 and 1.
|
||||
- **online_contrastive**: Contrastive loss considering hard negatives and hard positives. Labels are only supported as 0 and 1.
|
||||
- **infonce**: Computes pairwise cosine similarities between different rows within the same batch, maximizing similarity within rows and minimizing similarity between different rows. No labels are required.
|
||||
|
||||
The source code for the loss functions can be found [here](https://github.com/modelscope/ms-swift/blob/main/swift/loss/mapping.py).
|
||||
|
||||
## Dataset Format
|
||||
|
||||
> **Note:**
|
||||
> 1. The `<image>` tag can appear anywhere inside `messages`/`positive_messages`/`negative_messages`. Each group has its own image fields: `images`/`positive_images`/`negative_images` to provide paths or URLs.
|
||||
> 2. There is no longer any cross-field ordering requirement. Alignment rules:
|
||||
> - `images` length equals the number of `<image>` tags in `messages`.
|
||||
> - `positive_images` and `negative_images` are both list-of-list. Their outer lengths equal the lengths of `positive_messages` and `negative_messages` respectively. For each outer item, the inner list length equals the number of `<image>` tags in that message sequence.
|
||||
> 3. `messages` is the anchor sample; `positive_messages` and `negative_messages` are each a list of messages (hence one more `[]`). Accordingly, `positive_images`/`negative_images` are also list-of-list and align item-by-item.
|
||||
> 4. `<video>` and `<audio>` are supported as well. Follow the same rules via `videos`/`positive_videos`/`negative_videos` and `audios`/`positive_audios`/`negative_audios`.
|
||||
> 5. Current constraint: the outer length of `positive_messages` must be 1 (i.e., provide exactly one positive). Accordingly, the outer length of `positive_images` must also be 1.
|
||||
|
||||
### Format for Cosine Similarity Loss
|
||||
|
||||
```json lines
|
||||
# LLM
|
||||
{"messages": [{"role": "user", "content": "sentence1"}], "positive_messages": [[{"role": "user", "content": "sentence2"}]], "label": 0.8}
|
||||
# MLLM
|
||||
{"messages": [{"role": "user", "content": "<image>"}], "images": ["/some/images1.jpg"], "positive_messages": [[{"role": "user", "content": "<image>sentence"}]], "positive_images": [["/some/images2.jpg"]], "label": 0.7}
|
||||
{"messages": [{"role": "user", "content": "sentence1"}], "positive_messages": [[{"role": "user", "content": "<image>sentence2"}]], "positive_images": [["/some/images.jpg"]], "label": 0.7}
|
||||
```
|
||||
|
||||
The eval metrics are the Pearson and Spearman's Rank Correlation Coefficient of the embeddings' euclidean distance/dot production and so on, totally 8 values.
|
||||
|
||||
### Format for Contrastive/Online Contrastive Loss
|
||||
|
||||
```json lines
|
||||
# LLM
|
||||
{"messages": [{"role": "user", "content": "sentence1"}], "positive_messages": [[{"role": "user", "content": "sentence2"}]], "label": 1}
|
||||
# MLLM
|
||||
{"messages": [{"role": "user", "content": "<image>"}], "images": ["/some/images1.jpg"], "positive_messages": [[{"role": "user", "content": "<image>sentence"}]], "positive_images": [["/some/images2.jpg"]], "label": 1}
|
||||
{"messages": [{"role": "user", "content": "sentence1"}], "positive_messages": [[{"role": "user", "content": "<image>sentence2"}]], "positive_images": [["/some/images.jpg"]], "label": 0}
|
||||
```
|
||||
|
||||
### Format for InfoNCE
|
||||
|
||||
```json lines
|
||||
# LLM
|
||||
{"messages": [{"role": "user", "content": "sentence1"}], "positive_messages": [[{"role": "user", "content": "sentence2"}]]}
|
||||
# MLLM
|
||||
{"messages": [{"role": "user", "content": "<image>"}], "images": ["/some/images.jpg"], "positive_messages": [[{"role": "user", "content": "sentence"}]]}
|
||||
{"messages": [{"role": "user", "content": "<image>sentence1"}], "images": ["/some/images.jpg"], "positive_messages": [[{"role": "user", "content": "<image>sentence2"}]], "positive_images": [["/some/positive_images.jpg"]], "negative_messages": [[{"role": "user", "content": "<image><image>sentence3"}], [{"role": "user", "content": "<image>sentence4"}]], "negative_images": [["/some/negative_images1.jpg", "/some/negative_images2.jpg"], ["/some/negative_images3.jpg"]]}
|
||||
```
|
||||
|
||||
InfoNCE loss supports the following environment variables:
|
||||
1. `INFONCE_TEMPERATURE`: The temperature parameter. If not set, the default value is 0.1.
|
||||
2. `INFONCE_USE_BATCH`: Use `negative_messages` within the sample (hard negatives) or use other samples in the batch as in-batch negatives. The default is `True`, which means using in-batch negatives.
|
||||
3. `INFONCE_HARD_NEGATIVES`: The number of hard negatives. If not set, all provided `negative_messages` will be used. Since the lengths may vary, a for loop will be used to compute the loss (slower). If set to a specific number, missing items will be randomly sampled, and excess items will be truncated to the first `INFONCE_HARD_NEGATIVES`.
|
||||
4. `INFONCE_MASK_FAKE_NEGATIVE`: Masks out fake negatives. The default is `False`. When enabled, it checks `positive_similarity + INFONCE_FAKE_NEG_MARGIN`; any sample with similarity larger than this threshold will have its similarity set to `-inf` to prevent positive leakage.
|
||||
5. `INFONCE_FAKE_NEG_MARGIN`: Margin used by the fake-negative mask. Default: `0.1`.
|
||||
6. `INFONCE_INCLUDE_QQ`: Include the q–q block (similarities among queries) in the denominator as additional negatives. Default: `False`.
|
||||
7. `INFONCE_INCLUDE_DD`: Include the d–d block (similarities of the positive doc to all in-batch docs) in the denominator as additional negatives. Default: `False`.
|
||||
|
||||
> You can also make the number of hard negatives equal across samples in the dataset, which avoids the for-loop computation and speeds up training even if `INFONCE_HARD_NEGATIVES` is not set.
|
||||
>
|
||||
> `negative_messages` can be omitted. In this case, keep `INFONCE_USE_BATCH=True` to use in-batch negatives (other samples in the batch) as negatives.
|
||||
|
||||
The evaluation of InfoNCE loss includes the following metrics:
|
||||
- mean_neg: The average of all hard negatives
|
||||
- mean_pos: The average of all positives
|
||||
- margin: The average of (positive - max hard negative)
|
||||
|
||||
## Training
|
||||
|
||||
Training scripts provided by ms-swift:
|
||||
|
||||
- [Qwen3-Embedding/Qwen3-VL-Embedding model](https://github.com/modelscope/ms-swift/blob/main/examples/train/embedding/qwen3)
|
||||
- [GME model](https://github.com/modelscope/ms-swift/blob/main/examples/train/embedding/train_gme.sh)
|
||||
|
||||
## Inference
|
||||
|
||||
SWIFT has supported the deployment of GME, GTE, Qwen3-Embedding models, please check [here](https://github.com/modelscope/ms-swift/blob/main/examples/deploy/embedding/client.py).
|
||||
- For inference scripts, please refer to [here](https://github.com/modelscope/ms-swift/blob/main/examples/infer/demo_embedding.py).
|
||||
|
||||
You can also use the original model's code for inference:
|
||||
|
||||
https://www.modelscope.cn/models/iic/gte_Qwen2-7B-instruct
|
||||
|
||||
https://www.modelscope.cn/models/iic/gme-Qwen2-VL-7B-Instruct
|
||||
|
||||
If you've used other models to train embedding from scratch (for example, the original `qwen2-vl` model + `--task_type embedding`), you can also use gme's inference code, but please note:
|
||||
|
||||
https://www.modelscope.cn/models/iic/gme-Qwen2-VL-7B-Instruct/file/view/master/gme_inference.py?status=1#L111
|
||||
|
||||
Please modify the template here to match the model's own template to ensure the final embeddings align correctly. It's particularly important to note that the template for the gme model is different from the chatml template for the `qwen2-vl` or `qwen2.5-vl` series. In its inference code, the ending character is `<|endoftext|>` rather than `<|im_end|>`.
|
||||
|
||||
## Advanced
|
||||
|
||||
- Qwen3-Embedding Custom Instruction:
|
||||
- By default, there is no instruction; the input prompt is: `{Query}<|endoftext|>`.
|
||||
- You can add an instruction via the system message, changing the prompt to: `{Instruction} {Query}<|endoftext|>`.
|
||||
- Example:
|
||||
|
||||
```json lines
|
||||
{"messages": [
|
||||
{"role": "system", "content": "Answer in English and list key points briefly."},
|
||||
{"role": "user", "content": "Introduce Qwen3-Embedding"}
|
||||
]}
|
||||
```
|
||||
|
||||
> Note: The Qwen3-Embedding template prepends the system content to the first user message and uses `<|endoftext|>` as the ending token.
|
||||
|
||||
### Before/After Examples
|
||||
|
||||
- Without Instruction:
|
||||
|
||||
Input data (messages):
|
||||
|
||||
```json lines
|
||||
{"messages": [
|
||||
{"role": "user", "content": "What is Qwen3-Embedding?"}
|
||||
]}
|
||||
```
|
||||
|
||||
After template conversion (actual prompt sent to the model):
|
||||
|
||||
```text
|
||||
What is Qwen3-Embedding?<|endoftext|>
|
||||
```
|
||||
|
||||
- With Instruction:
|
||||
|
||||
Input data (messages with system):
|
||||
|
||||
```json lines
|
||||
{"messages": [
|
||||
{"role": "system", "content": "Answer in English and list key points briefly."},
|
||||
{"role": "user", "content": "What is Qwen3-Embedding?"}
|
||||
]}
|
||||
```
|
||||
|
||||
After template conversion (actual prompt sent to the model):
|
||||
|
||||
```text
|
||||
Answer in English and list key points briefly. What is Qwen3-Embedding?<|endoftext|>
|
||||
```
|
||||
|
||||
- Positive/Negative behave the same:
|
||||
|
||||
If a system message is provided within a positive/negative sequence, it is prepended to that sequence’s first user content; if no system is provided, nothing is prepended.
|
||||
|
||||
Input (one positive with system, one negative without):
|
||||
|
||||
```json lines
|
||||
{
|
||||
"messages": [
|
||||
{"role": "user", "content": "Anchor"}
|
||||
],
|
||||
"positive_messages": [[
|
||||
{"role": "system", "content": "Instruction"},
|
||||
{"role": "user", "content": "Positive"}
|
||||
]],
|
||||
"negative_messages": [[
|
||||
{"role": "user", "content": "Negative"}
|
||||
]]
|
||||
}
|
||||
```
|
||||
|
||||
After template conversion (actual prompts):
|
||||
|
||||
```text
|
||||
Anchor<|endoftext|>
|
||||
Instruction Positive<|endoftext|>
|
||||
Negative<|endoftext|>
|
||||
```
|
||||
@@ -0,0 +1,147 @@
|
||||
# Code Training with GRPO
|
||||
This document explains how to use GRPO to train models for code tasks.
|
||||
|
||||
|
||||
Model: [Qwen/Qwen2.5-7B-Instruct](https://www.modelscope.cn/models/Qwen/Qwen2.5-VL-7B-Instruct)
|
||||
|
||||
Dataset: [open-r1/verifiable-coding-problems-python-10k](https://www.modelscope.cn/datasets/open-r1/verifiable-coding-problems-python-10k/dataPeview)
|
||||
|
||||
dataset example
|
||||
```json
|
||||
{
|
||||
"problem": "Solve the following coding problem using the programming language python: Polycarp has $n$ different binary words. A word called binary if it contains only characters '0' and '1'. For example, these words are binary: \"0001\", \"11\", \"0\" and \"0011100\". Polycarp wants to offer his set of $n$ binary words to play a game \"words\". In this game, players name words and each next word (starting from the second) must start with the last character of the previous word. The first word can be any. For example, these sequence of words can be named during the game: \"0101\", \"1\", \"10\", \"00\", \"00001\". Word reversal is the operation of reversing the order of the characters. For example, the word \"0111\" after the reversal becomes \"1110\", the word \"11010\" after the reversal becomes \"01011\". Probably, Polycarp has such a set of words that there is no way to put them in the order correspondent to the game rules. In this situation, he wants to reverse some words from his set so that: the final set of $n$ words still contains different words (i.e. all words are unique); there is a way to put all words of the final set of words in the order so that the final sequence of $n$ words is consistent with the game rules. Polycarp wants to reverse minimal number of words. Please, help him. -----Input----- The first line of the input contains one integer $t$ ($1 \\le t \\le 10^4$) — the number of test cases in the input. Then $t$ test cases follow. The first line of a test case contains one integer $n$ ($1 \\le n \\le 2\\cdot10^5$) — the number of words in the Polycarp's set. Next $n$ lines contain these words. All of $n$ words aren't empty and contains only characters '0' and '1'. The sum of word lengths doesn't exceed $4\\cdot10^6$. All words are different. Guaranteed, that the sum of $n$ for all test cases in the input doesn't exceed $2\\cdot10^5$. Also, guaranteed that the sum of word lengths for all test cases in the input doesn't exceed $4\\cdot10^6$. -----Output----- Print answer for all of $t$ test cases in the order they appear. If there is no answer for the test case, print -1. Otherwise, the first line of the output should contain $k$ ($0 \\le k \\le n$) — the minimal number of words in the set which should be reversed. The second line of the output should contain $k$ distinct integers — the indexes of the words in the set which should be reversed. Words are numerated from $1$ to $n$ in the order they appear. If $k=0$ you can skip this line (or you can print an empty line). If there are many answers you can print any of them. -----Example----- Input 4 4 0001 1000 0011 0111 3 010 101 0 2 00000 00001 4 01 001 0001 00001 Output 1 3 -1 0 2 1 2 The input will be stdin and you should print your solution to stdout Now solve the problem and return the code.",
|
||||
"verification_info": {
|
||||
"language": "python",
|
||||
"test_cases": [
|
||||
{
|
||||
"input": "4\n4\n0001\n1000\n0011\n0111\n3\n010\n101\n0\n2\n00000\n00001\n4\n01\n001\n0001\n00001\n",
|
||||
"output": "1\n3 \n-1\n0\n\n2\n1 2 \n",
|
||||
"type": "stdin_stdout"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`verification_info` provides the programming language as well as test cases, which include input and expected output.
|
||||
|
||||
|
||||
## Reward Functions
|
||||
|
||||
The training process utilizes two reward functions: `code_reward` and `code_format`. For implementation details, refer to the [code](https://github.com/modelscope/ms-swift/blob/main/examples/train/grpo/plugin/plugin.py).
|
||||
|
||||
|
||||
- `code_reward` Executes the generated code using [e2b](https://e2b.dev/) or [judge0](https://judge0.com/). Validates the code against the test cases in the dataset and assigns a reward value based on correctness.
|
||||
- `code_format` Requires the model to produce formatted responses that include code blocks.
|
||||
|
||||
Note: Currently, executing code through E2B only supports the Python language. If you need to execute code in other languages, you can use Judge0([judge0 supported languages](https://github.com/judge0/judge0?tab=readme-ov-file#supported-languages)).
|
||||
|
||||
## Training Script
|
||||
|
||||
- Register on [e2b](https://e2b.dev/dashboard) to obtain your E2B_API_KEY and set it as an environment variable.
|
||||
- Add `external_code_reward` as a reward function with `--reward_funcs`.
|
||||
- Set `--external_plugins` to the path of plugin.py.
|
||||
|
||||
launch external vLLM server using following script
|
||||
```bash
|
||||
CUDA_VISIBLE_DEVICES=7 \
|
||||
swift rollout \
|
||||
--model Qwen/Qwen2.5-7B-Instruct \
|
||||
--vllm_enable_lora true \
|
||||
--vllm_max_lora_rank 16
|
||||
```
|
||||
|
||||
```bash
|
||||
E2B_API_KEY=xxx \
|
||||
WANDB_API_KEY=xxx \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6 \
|
||||
NPROC_PER_NODE=7 \
|
||||
swift rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen2.5-7B-Instruct \
|
||||
--external_plugins examples/train/grpo/plugin/plugin.py \
|
||||
--reward_funcs external_code_reward external_code_format \
|
||||
--reward_weights 1.0 0.1 \
|
||||
--vllm_mode server \
|
||||
--use_vllm true \
|
||||
--vllm_server_host 127.0.0.1 \
|
||||
--vllm_server_port 8000 \
|
||||
--tuner_type lora \
|
||||
--lora_rank 16 \
|
||||
--lora_alpha 32 \
|
||||
--torch_dtype bfloat16 \
|
||||
--dataset 'open-r1/verifiable-coding-problems-python-10k' \
|
||||
--load_from_cache_file true \
|
||||
--max_completion_length 2048 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 2 \
|
||||
--per_device_eval_batch_size 2 \
|
||||
--learning_rate 1e-6 \
|
||||
--gradient_accumulation_steps 1 \
|
||||
--eval_steps 200 \
|
||||
--save_steps 200 \
|
||||
--save_total_limit 2 \
|
||||
--logging_steps 5 \
|
||||
--max_length 4096 \
|
||||
--output_dir output \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 4 \
|
||||
--dataset_num_proc 4 \
|
||||
--num_generations 14 \
|
||||
--temperature 0.9 \
|
||||
--system 'examples/train/grpo/prompt.txt' \
|
||||
--deepspeed zero2 \
|
||||
--log_completions true \
|
||||
--report_to wandb
|
||||
```
|
||||
### judge0
|
||||
- Set environment variables:
|
||||
- (Required) JUDGE0_ENDPOINT: The endpoint address for accessing Judge0.
|
||||
- (Optional) JUDGE0_X_AUTH_TOKEN: The access token for Judge0.
|
||||
- Add `external_code_reward_by_judge0` as a reward function with `--reward_funcs`.
|
||||
- Set `--external_plugins` to the path of `plugin.py`.
|
||||
|
||||
```bash
|
||||
JUDGE0_ENDPOINT=xxx \
|
||||
JUDGE0_X_AUTH_TOKEN=xxx \
|
||||
WANDB_API_KEY=xxx \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6 \
|
||||
NPROC_PER_NODE=7 \
|
||||
swift rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen2.5-7B-Instruct \
|
||||
--external_plugins examples/train/grpo/plugin/plugin.py \
|
||||
--reward_funcs external_code_reward_by_judge0 external_code_format \
|
||||
--reward_weights 1.0 0.1 \
|
||||
--vllm_mode server \
|
||||
--use_vllm true \
|
||||
--vllm_server_host 127.0.0.1 \
|
||||
--vllm_server_port 8000 \
|
||||
--tuner_type lora \
|
||||
--torch_dtype bfloat16 \
|
||||
--dataset 'open-r1/verifiable-coding-problems-python-10k' \
|
||||
--load_from_cache_file true \
|
||||
--max_completion_length 2048 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 2 \
|
||||
--per_device_eval_batch_size 2 \
|
||||
--learning_rate 1e-6 \
|
||||
--gradient_accumulation_steps 1 \
|
||||
--eval_steps 200 \
|
||||
--save_steps 200 \
|
||||
--save_total_limit 2 \
|
||||
--logging_steps 5 \
|
||||
--max_length 4096 \
|
||||
--output_dir output \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 4 \
|
||||
--dataset_num_proc 4 \
|
||||
--num_generations 14 \
|
||||
--temperature 0.9 \
|
||||
--system 'examples/train/grpo/prompt.txt' \
|
||||
--deepspeed zero2 \
|
||||
--log_completions true \
|
||||
--report_to wandb
|
||||
```
|
||||
Training Reward Curve
|
||||

|
||||
@@ -0,0 +1,360 @@
|
||||
# Complete Multimodal GRPO Experiment Workflow
|
||||
|
||||
This document explains how to use SWIFT GRPO for training multimodal models and tasks. The goal is to train on multiple multimodal tasks to improve task accuracy. Task definitions, training parameters, etc., refer to [R1-V](https://github.com/Deep-Agent/R1-V.git) and [open-r1-multimodal](https://github.com/EvolvingLMMs-Lab/open-r1-multimodal.git).
|
||||
|
||||
---
|
||||
|
||||
## **ClevrCount Task**
|
||||
|
||||
### **Task and Dataset Definition**
|
||||
|
||||
This task is based on the `clevr_cogen_a_train` dataset. The model's goal is to output the number of objects in the image. Therefore, we define the dataset as follows:
|
||||
|
||||
```python
|
||||
class ClevrPreprocessor(ResponsePreprocessor):
|
||||
|
||||
def preprocess(self, row: Dict[str, Any]) -> Dict[str, Any]:
|
||||
query = row.get('query', '')
|
||||
query = f"""{query} Output the thinking process in <think> </think> and
|
||||
final answer (number) in <answer> </answer> tags."""
|
||||
row.update({'query': query})
|
||||
return super().preprocess(row)
|
||||
|
||||
|
||||
register_dataset(
|
||||
DatasetMeta(
|
||||
ms_dataset_id='AI-ModelScope/clevr_cogen_a_train',
|
||||
subsets=[
|
||||
SubsetDataset(
|
||||
name='default',
|
||||
subset='default',
|
||||
split=['train'],
|
||||
),
|
||||
],
|
||||
preprocess_func=ClevrPreprocessor(),
|
||||
tags=['qa', 'math']))
|
||||
```
|
||||
|
||||
The purpose of redefining the dataset preprocessor here is to modify the query. A sample dataset entry is as follows, including `messages`, `images`, and `solution` fields. The `solution` is used in the reward function, while `messages` and `images` serve as model input.
|
||||
- Note: `{'role': 'assistant', 'content': '<answer> 3 </answer>'}` will be removed in GRPOTrainer and can be ignored. The 'solution' field will be passed directly into the ORM. When creating a custom dataset, the 'images' field should be organized as `["image_path1", "image_path2"]`.
|
||||
|
||||
```json
|
||||
{
|
||||
"images": ["image_path1", "image_path2"],
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "How many items are there in the image? Output the thinking process in <think> </think> and\n final answer (number) in <answer> </answer> tags."
|
||||
}
|
||||
],
|
||||
"solution": "<answer> 3 </answer>"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Reward Function Definition**
|
||||
|
||||
This task uses two reward functions: one is the format reward function mentioned in `Deepseek-R1`, and the other is the accuracy reward function for ClevrCount. The former is built into SWIFT and can be used directly with `--reward_funcs format`. The latter needs to be custom-defined. Here, we use the `external_plugin` method to define the accuracy reward function by placing the code in `swift/examples/train/grpo/plugin/plugin.py`.
|
||||
|
||||
The reward function's input includes `completions` and `solution` fields, representing the model-generated text and ground truth, respectively. Each is a list, allowing the computation of multiple completions simultaneously. Note that the `solution` field is passed through directly from the dataset definition. If there are task changes, corresponding modifications can be made to the dataset and reward function.
|
||||
|
||||
```python
|
||||
class MultiModalAccuracyORM(ORM):
|
||||
|
||||
def __call__(self, completions, solution, **kwargs) -> List[float]:
|
||||
"""
|
||||
Reward function that checks if the completion is correct.
|
||||
Args:
|
||||
completions (list[str]): Generated outputs
|
||||
solution (list[str]): Ground Truths.
|
||||
|
||||
Returns:
|
||||
list[float]: Reward scores
|
||||
"""
|
||||
rewards = []
|
||||
from math_verify import parse, verify
|
||||
for content, sol in zip(completions, solution):
|
||||
reward = 0.0
|
||||
# Try symbolic verification first
|
||||
try:
|
||||
answer = parse(content)
|
||||
if float(verify(answer, parse(sol))) > 0:
|
||||
reward = 1.0
|
||||
except Exception:
|
||||
pass # Continue to next verification method if this fails
|
||||
|
||||
# If symbolic verification failed, try string matching
|
||||
if reward == 0.0:
|
||||
try:
|
||||
# Extract answer from solution if it has think/answer tags
|
||||
sol_match = re.search(r'<answer>(.*?)</answer>', sol)
|
||||
ground_truth = sol_match.group(1).strip() if sol_match else sol.strip()
|
||||
|
||||
# Extract answer from content if it has think/answer tags
|
||||
content_match = re.search(r'<answer>(.*?)</answer>', content)
|
||||
student_answer = content_match.group(1).strip() if content_match else content.strip()
|
||||
|
||||
# Compare the extracted answers
|
||||
if student_answer == ground_truth:
|
||||
reward = 1.0
|
||||
except Exception:
|
||||
pass # Keep reward as 0.0 if both methods fail
|
||||
rewards.append(reward)
|
||||
return rewards
|
||||
orms['external_r1v_acc'] = MultiModalAccuracyORM
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **GRPO Training Experiment Log**
|
||||
|
||||
#### **Training Parameters**
|
||||
|
||||
We selected `Qwen2.5-VL-3B-Instruct` as the base model for training. The main reason for choosing the `Instruct` model over the base model is to rapidly achieve format rewards. Experiments were conducted on 8 GPUs. SWIFT GRPO training supports multi-GPU deployment to accelerate rollouts. If you encounter deployment errors for `qwen2.5-vl` on `vllm`, refer to [this issue](https://github.com/vllm-project/vllm/issues/13285).
|
||||
|
||||
Since the task is simple, we set `max_completion_length` to 1024 and selected `external_r1v_acc` and `format` as reward functions. The learning rate and beta are set to `1e-6` and `0.001`, respectively. Other configurations are as follows. The settings for `batch_size` and `num_generations` can be referenced from [GRPO Full Workflow](./GRPO.md).
|
||||
|
||||
launch external vLLM server using following script
|
||||
```bash
|
||||
CUDA_VISIBLE_DEVICES=6,7 \
|
||||
swift rollout \
|
||||
--model Qwen/Qwen2.5-VL-3B-Instruct \
|
||||
--vllm_data_parallel_size 2
|
||||
```
|
||||
|
||||
```shell
|
||||
WANDB_API_KEY=your_wandb_api_key \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5 \
|
||||
NPROC_PER_NODE=6 \
|
||||
swift rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen2.5-VL-3B-Instruct \
|
||||
--external_plugins examples/train/grpo/plugin/plugin.py \
|
||||
--reward_funcs external_r1v_acc format \
|
||||
--use_vllm true \
|
||||
--vllm_mode server \
|
||||
--vllm_server_host 127.0.0.1 \
|
||||
--vllm_server_port 8000 \
|
||||
--tuner_type full \
|
||||
--torch_dtype bfloat16 \
|
||||
--dataset 'AI-ModelScope/clevr_cogen_a_train' \
|
||||
--load_from_cache_file true \
|
||||
--max_completion_length 1024 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 8 \
|
||||
--per_device_eval_batch_size 8 \
|
||||
--learning_rate 1e-6 \
|
||||
--gradient_accumulation_steps 2 \
|
||||
--save_strategy 'steps' \
|
||||
--eval_strategy 'steps' \
|
||||
--eval_steps 1000 \
|
||||
--save_steps 1000 \
|
||||
--save_total_limit 10 \
|
||||
--logging_steps 1 \
|
||||
--output_dir output/GRPO_CLEVR_COUNTDOWN \
|
||||
--warmup_ratio 0.01 \
|
||||
--dataloader_num_workers 4 \
|
||||
--num_generations 24 \
|
||||
--temperature 1.0 \
|
||||
--system 'examples/train/grpo/prompt.txt' \
|
||||
--deepspeed zero3 \
|
||||
--log_completions true \
|
||||
--report_to wandb \
|
||||
--num_iterations 1 \
|
||||
--async_generate false \
|
||||
--beta 0.001 \
|
||||
```
|
||||
|
||||
#### **Experimental Observations**
|
||||
|
||||
[image.png](../../resources/grpo_clevr_count.png)
|
||||
|
||||
- Given the simplicity of the dataset and task, the model converged after 500 epochs. Key observations:
|
||||
1. The custom `ClevrORM` reward steadily increased, proving the model learned how to complete the task. The task success rate climbed from an initial 0.4 to nearly 1.
|
||||
2. The `Format Reward` remained stable at 1, likely due to the consistent query format across all dataset samples.
|
||||
3. The `reward_std` stabilized below 0.1.
|
||||
4. The `completion length` eventually stabilized between 60-80 tokens, with the model learning a fixed output pattern for item-by-item counting.
|
||||
|
||||
---
|
||||
For additional tasks like Geometric QA and Open R1 Multimodal datasets, refer to their respective sections in the full experiment documentation.
|
||||
|
||||
## **Geometric QA Task**
|
||||
|
||||
### **Task and Dataset Definition**
|
||||
|
||||
This task is a Geometric QA task, where the task description is: given a geometric figure, answer mathematical questions related to the figure. The original data comes from [this paper](https://arxiv.org/pdf/2312.11370), and [R1-V](https://github.com/Deep-Agent/R1-V.git) has preprocessed the data into a `problem-solution` format while retaining the images in the `image` field. Therefore, we do not need to redefine the dataset and can directly use `--dataset AI-ModelScope/GEOQA_R1V_Train_8K`.
|
||||
|
||||
---
|
||||
|
||||
### **Reward Function**
|
||||
|
||||
As this is also a mathematical problem, and the answers are already processed into final results, we directly use the previously defined `MultiModalAccuracyORM` reward function.
|
||||
|
||||
---
|
||||
|
||||
### **GRPO Training Experiment Log**
|
||||
|
||||
#### **Training Parameters**
|
||||
|
||||
The selected model and most hyperparameters are similar to the previous experiment, with two main differences:
|
||||
1. **SWIFT now supports the `--num_iteration` parameter**, allowing multiple updates during a single rollout. We set it to 2.
|
||||
2. During the experiment, we found that training might become unstable in mathematical problems, causing the model to collapse. This is characterized by a sharp drop in all rewards, a rapid increase in loss, `grad_norm`, and KL divergence, with no subsequent recovery. To prevent this, we set `--max_grad_norm 0.5` to ensure stable training. Note that this instability can have some randomness.
|
||||
|
||||
```shell
|
||||
WANDB_API_KEY=your_wandb_api_key \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5 \
|
||||
MAX_PIXELS=401408 \
|
||||
NPROC_PER_NODE=6 \
|
||||
swift rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen2.5-VL-3B-Instruct \
|
||||
--external_plugins examples/train/grpo/plugin/plugin.py \
|
||||
--reward_funcs external_r1v_acc format \
|
||||
--use_vllm true \
|
||||
--vllm_mode server \
|
||||
--vllm_server_host 127.0.0.1 \
|
||||
--vllm_server_port 8000 \
|
||||
--tuner_type full \
|
||||
--torch_dtype bfloat16 \
|
||||
--dataset 'AI-ModelScope/GEOQA_R1V_Train_8K' \
|
||||
--load_from_cache_file true \
|
||||
--max_completion_length 1024 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 8 \
|
||||
--per_device_eval_batch_size 8 \
|
||||
--learning_rate 1e-6 \
|
||||
--gradient_accumulation_steps 2 \
|
||||
--save_strategy 'steps' \
|
||||
--eval_strategy 'steps' \
|
||||
--eval_steps 400 \
|
||||
--save_steps 400 \
|
||||
--save_total_limit 10 \
|
||||
--logging_steps 1 \
|
||||
--output_dir output/GRPO_GEOQA \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 4 \
|
||||
--num_generations 8 \
|
||||
--temperature 1.0 \
|
||||
--repetition_penalty 1.1 \
|
||||
--system 'examples/train/grpo/prompt.txt' \
|
||||
--deepspeed zero3 \
|
||||
--log_completions true \
|
||||
--report_to wandb \
|
||||
--num_iterations 2 \
|
||||
--async_generate false \
|
||||
--beta 0.001 \
|
||||
--max_grad_norm 0.5 \
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **Experimental Observations**
|
||||
|
||||
[image.png](../../resources/grpo_geoqa.png)
|
||||
|
||||
The training curve is shown above.
|
||||
|
||||
1. Compared to the **Count task**, the **Geometric QA task** is significantly more challenging. As a result, the **Accuracy Reward** converges much more slowly and has larger oscillations.
|
||||
2. The `completion_length` exhibits a clear trend, eventually stabilizing at around 250 tokens.
|
||||
3. The reward converges, with the standard deviation stabilizing at around 0.2 after a total of 1300 steps.
|
||||
|
||||
Below is an example of the model's completion after training:
|
||||
|
||||
```plaintext
|
||||
User:
|
||||
"If AB is the tangent to circle O with the tangent point A, and BO intersects circle O at point C, and point D is located on circle O, what is the measure of angle ADC if the degree of angle ABO is 32.0?"
|
||||
Assistant:
|
||||
"<think>\nTo find the measure of angle ADC, we first need to understand the properties of the tangent and the circle. Since AB is a tangent to the circle at point A, the line segment OA (which is perpendicular to the tangent AB) is perpendicular to AB. This means that angle OAB is 90 degrees.\n\nGiven that angle ABO is 32 degrees, we can find angle BAO by subtracting angle ABO from 90 degrees:\n∠BAO = 90° - ∠ABO = 90° - 32° = 58°\n\nSince angle BAO is an angle in the minor arc AD, the angle ADC, which is an angle in the minor arc AD, is half the angle BAO. Therefore, angle ADC is:\n∠ADC = 1/2 × ∠BAO = 1/2 × 58° = 29°\n\nSo, the measure of angle ADC is 29 degrees.\n</think>\n<answer>\nThe measure of angle ADC is 29 degrees.\n</answer>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Multimodal Open R1 Dataset Experiment**
|
||||
|
||||
### **Task and Dataset Definition**
|
||||
|
||||
This task is based on the experiments in [open-r1-multimodal](https://github.com/EvolvingLMMs-Lab/open-r1-multimodal.git), using the dataset: [lmms-lab/multimodal-open-r1-8k-verified](https://www.modelscope.cn/datasets/lmms-lab/multimodal-open-r1-8k-verified). This dataset focuses on multimodal mathematical reasoning tasks, with data generated by GPT4o based on the `Math360K` and `Geo170K` datasets. It includes reasoning paths and verifiable answers. The dataset already contains `image`, `problem`, and `solution` fields, so no additional prompt modifications are required, and there is no need to redefine the dataset.
|
||||
|
||||
---
|
||||
|
||||
### **Reward Function**
|
||||
|
||||
We directly use the previously defined `MultiModalAccuracyORM` reward function.
|
||||
|
||||
---
|
||||
|
||||
### **GRPO Training Experiment Log**
|
||||
|
||||
#### **Training Parameters**
|
||||
|
||||
The selected model and most hyperparameters are similar to the previous experiment. Due to an **OOM (Out of Memory) issue**, we set `MAX_PIXELS=262144` to reduce memory usage.
|
||||
|
||||
```shell
|
||||
WANDB_API_KEY=your_wandb_api_key \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5 \
|
||||
MAX_PIXELS=262144 \
|
||||
MASTER_PORT=29600 \
|
||||
NPROC_PER_NODE=6 \
|
||||
swift rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen2.5-VL-3B-Instruct \
|
||||
--external_plugins examples/train/grpo/plugin/plugin.py \
|
||||
--reward_funcs external_r1v_acc format \
|
||||
--use_vllm true \
|
||||
--vllm_mode server \
|
||||
--vllm_server_host 127.0.0.1 \
|
||||
--vllm_server_port 8000 \
|
||||
--tuner_type full \
|
||||
--torch_dtype bfloat16 \
|
||||
--dataset 'lmms-lab/multimodal-open-r1-8k-verified' \
|
||||
--load_from_cache_file true \
|
||||
--max_completion_length 1024 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 8 \
|
||||
--per_device_eval_batch_size 8 \
|
||||
--learning_rate 1e-6 \
|
||||
--gradient_accumulation_steps 2 \
|
||||
--save_strategy 'steps' \
|
||||
--eval_strategy 'steps' \
|
||||
--eval_steps 400 \
|
||||
--save_steps 400 \
|
||||
--save_total_limit 10 \
|
||||
--logging_steps 1 \
|
||||
--output_dir output/GRPO_GEOQA \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 4 \
|
||||
--num_generations 8 \
|
||||
--temperature 1.0 \
|
||||
--repetition_penalty 1.1 \
|
||||
--system 'examples/train/grpo/prompt.txt' \
|
||||
--deepspeed zero3 \
|
||||
--log_completions true \
|
||||
--report_to wandb \
|
||||
--num_iterations 2 \
|
||||
--async_generate false \
|
||||
--beta 0.001 \
|
||||
--max_grad_norm 0.5 \
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **Experimental Observations**
|
||||
|
||||
[image.png](../../resources/grpo_openr1_multimodal.png)
|
||||
|
||||
The training curve is shown above.
|
||||
|
||||
- With a total of 8k data samples, the training reached **1268 steps**, with the **Accuracy Reward** converging around 0.5.
|
||||
- The `completion_length` stabilized around 200 tokens.
|
||||
- The `reward_std` eventually oscillated around 0.2.
|
||||
|
||||
Below is an example of the model's completion after training:
|
||||
|
||||
```plaintext
|
||||
User:
|
||||
"Based on the map, which state falls into the lowest percentage range of lots, and what is that percentage range?"
|
||||
Assistant:
|
||||
"<think>\nThe image provided shows a color-coded map of the USA indicating different ranges of lot percentages across various states. According to the legend at the bottom, the lowest percentage range (6.0% - 6.5%) is represented by white. In the image, Alabama (AL) is shaded in white, which corresponds to the 6.0% - 6.5% category. Therefore, based on the map, the state that falls into the lowest percentage range of lots is Alabama, with the percentage range of 6.0% - 6.5%.\nTherefore, the answer is 6.0% - 6.5%.\n</think>\n<answer>Alabama</answer>"
|
||||
```
|
||||
@@ -0,0 +1,302 @@
|
||||
# Complete GRPO Experiment Process
|
||||
|
||||
This article starts with the relatively simple mathematical task "Countdown Game" and introduces the complete GRPO training process through several steps: dataset definition, reward function definition, and GRPO training. The task definition and training parameters are based on [mini-deepseek-r1](https://github.com/philschmid/deep-learning-pytorch-huggingface/blob/main/training/mini-deepseek-r1-aha-grpo.ipynb).
|
||||
|
||||
## Task and Dataset Definition
|
||||
|
||||
The goal of the Countdown Game task is to reach a target number using the given numbers and the four basic arithmetic operations. Therefore, we define the dataset as follows:
|
||||
|
||||
```python
|
||||
class CoundownTaskPreprocessor(ResponsePreprocessor):
|
||||
|
||||
def preprocess(self, row: Dict[str, Any]) -> Dict[str, Any]:
|
||||
numbers = row['nums']
|
||||
target = row.pop('response', None)
|
||||
query = f"""
|
||||
Using the numbers {numbers}, create an equation that equals {target}.
|
||||
You can use basic arithmetic operations (+, -, *, /) and each number can only be used once.
|
||||
Show your work in <think> </think> tags. And return the final equation and answer in <answer> </answer> tags,
|
||||
for example <answer> (1 + 2) / 3 * 4 = 4 </answer>.
|
||||
"""
|
||||
row.update({'target': target, 'query': query})
|
||||
return super().preprocess(row)
|
||||
|
||||
register_dataset(
|
||||
DatasetMeta(
|
||||
ms_dataset_id='zouxuhong/Countdown-Tasks-3to4',
|
||||
subsets=['default'],
|
||||
preprocess_func=CoundownTaskPreprocessor(),
|
||||
tags=['math']))
|
||||
```
|
||||
|
||||
Through a template, numbers and the target are used to define the task, and a `query` field is provided for model sampling. At the same time, we need to retain the `nums` and `target` fields for subsequent reward function calculation.
|
||||
|
||||
## Reward Function Definition
|
||||
|
||||
Two reward functions are used for this task: one is the format reward function mentioned in Deepseek-R1, and the other is the accuracy reward function for the Countdown Game. The former is already built into Swift and can be used directly with `--reward_funcs format`, while the latter requires custom definition. Here, we use the `external_plugin` method to define the accuracy reward function, placing the code in `swift/examples/train/grpo/plugin/plugin.py`.
|
||||
|
||||
The input to the reward function includes three fields: `completions`, `target`, and `nums`, representing the model-generated text, the target answer, and the available numbers, respectively. Each is a list, supporting simultaneous computation of multiple completions. Note that, except for `completions`, the other parameters are transparently passed from the fields defined in the dataset. If there are changes to the task, adjustments can be made to both the dataset and the reward function as needed.
|
||||
|
||||
```python
|
||||
class CountdownORM(ORM):
|
||||
def __call__(self, completions, target, nums, **kwargs) -> List[float]:
|
||||
"""
|
||||
Evaluates completions based on Mathematical correctness of the answer
|
||||
Args:
|
||||
completions (list[str]): Generated outputs
|
||||
target (list[str]): Expected answers
|
||||
nums (list[str]): Available numbers
|
||||
Returns:
|
||||
list[float]: Reward scores
|
||||
"""
|
||||
rewards = []
|
||||
for completion, gt, numbers in zip(completions, target, nums):
|
||||
try:
|
||||
# Check if the format is correct
|
||||
match = re.search(r"<answer>(.*?)<\/answer>", completion)
|
||||
if match is None:
|
||||
rewards.append(0.0)
|
||||
continue
|
||||
# Extract the "answer" part from the completion
|
||||
equation = match.group(1).strip()
|
||||
if '=' in equation:
|
||||
equation = equation.split('=')[0]
|
||||
# Extract all numbers from the equation
|
||||
used_numbers = [int(n) for n in re.findall(r'\d+', equation)]
|
||||
# Check if all numbers are used exactly once
|
||||
if sorted(used_numbers) != sorted(numbers):
|
||||
rewards.append(0.0)
|
||||
continue
|
||||
# Define a regex pattern that only allows numbers, operators, parentheses, and whitespace
|
||||
allowed_pattern = r'^[\d+\-*/().\s]+$'
|
||||
if not re.match(allowed_pattern, equation):
|
||||
rewards.append(0.0)
|
||||
continue
|
||||
# Evaluate the equation with restricted globals and locals
|
||||
result = eval(equation, {'__builtins__': None}, {})
|
||||
# Check if the equation is correct and matches the ground truth
|
||||
if abs(float(result) - float(gt)) < 1e-5:
|
||||
rewards.append(1.0)
|
||||
else:
|
||||
rewards.append(0.0)
|
||||
except Exception as e:
|
||||
# If evaluation fails, reward is 0
|
||||
rewards.append(0.0)
|
||||
return rewards
|
||||
orms['external_countdown'] = CountdownORM
|
||||
```
|
||||
|
||||
## GRPO Training Experiment Record
|
||||
|
||||
We first present the GRPO formula:
|
||||
|
||||
$$
|
||||
{\scriptstyle
|
||||
\begin{aligned}
|
||||
\mathcal{J}_{G R P O}(\theta) & =\mathbb{E}\left[q \sim P(Q),\left\{o_i\right\}_{i=1}^G \sim \pi_{\theta_{o l d}}(O \mid q)\right] \\
|
||||
& \frac{1}{G} \sum_{i=1}^G \frac{1}{\left|o_i\right|} \sum_{t=1}^{\left|o_i\right|}\left\{\min \left[\frac{\pi_\theta\left(o_{i, t} \mid q, o_{i,<t}\right)}{\pi_{\theta_{o l d}}\left(o_{i, t} \mid q, o_{i,<t}\right)} \hat{A}_{i, t}, \operatorname{clip}\left(\frac{\pi_\theta\left(o_{i, t} \mid q, o_{i,<t}\right)}{\pi_{\theta_{o l d}}\left(o_{i, t} \mid q, o_{i,<t}\right)}, 1-\varepsilon, 1+\varepsilon\right) \hat{A}_{i, t}\right]-\beta \mathbb{D}_{K L}\left[\pi_\theta| | \pi_{r e f}\right]\right\}
|
||||
\end{aligned}
|
||||
}
|
||||
$$
|
||||
|
||||
### Training Parameters
|
||||
|
||||
We selected Qwen2.5-3B-Instruct as the base model for training, as using an instruct-tuned model allows for faster acquisition of format rewards. The experiment was conducted on three GPUs, with vLLM inference deployed on the last GPU and two processes set on the remaining GPUs for gradient updates.
|
||||
|
||||
Since the task is relatively simple, we set both `max_completion_length` to 1024. For more complex tasks, the model output length can be increased appropriately, but note that **the larger these parameters, the more GPU memory is required, and the slower the training speed**. The training time per step is linearly related to `max_completion_length`.
|
||||
|
||||
In our experiment, the total batch size is:
|
||||
|
||||
```
|
||||
num_processes * per_device_train_batch_size * gradient_accumulation_steps = 2 * 8 * 8 = 128
|
||||
```
|
||||
|
||||
|
||||
Note that the single-GPU batch size is also closely related to GPU memory capacity, so set an appropriate value based on memory limits. Additionally, the total number of steps can be calculated as:
|
||||
|
||||
$$
|
||||
\text{num\_steps} = \text{epochs} \times \text{len(datasets)} \times \text{num\_generations} \div \text{batch\_size}
|
||||
$$
|
||||
|
||||
This formula should guide the planning of learning rate and warmup settings.
|
||||
|
||||
Finally, two important parameters are learning rate and $\beta$. The learning rate is straightforward, while $\beta$ is the weight of the KL divergence gradient in the formula. Increasing these parameters accelerates convergence but may lead to instability. After experimentation, we set them to `5e-7` and `0.001`, respectively. During training, adjust these parameters appropriately if instability or oscillations occur.
|
||||
|
||||
For KL divergence, the community has extensive discussions, such as [Why GRPO Adheres to KL Divergence](https://zhuanlan.zhihu.com/p/25862547100).
|
||||
|
||||
Other parameter settings were not explored in detail and will not be discussed here.
|
||||
|
||||
```bash
|
||||
CUDA_VISIBLE_DEVICES=2 \
|
||||
swift rollout \
|
||||
--model Qwen/Qwen2.5-3B-Instruct
|
||||
```
|
||||
|
||||
```bash
|
||||
CUDA_VISIBLE_DEVICES=0,1 \
|
||||
WANDB_API_KEY=your_wandb_key \
|
||||
NPROC_PER_NODE=2 \
|
||||
swift rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen2.5-3B-Instruct \
|
||||
--external_plugins examples/train/grpo/plugin/plugin.py \
|
||||
--reward_funcs external_countdown format \
|
||||
--use_vllm true \
|
||||
--vllm_mode server \
|
||||
--vllm_server_host 127.0.0.1 \
|
||||
--vllm_server_port 8000 \
|
||||
--tuner_type full \
|
||||
--torch_dtype bfloat16 \
|
||||
--dataset 'zouxuhong/Countdown-Tasks-3to4#50000' \
|
||||
--load_from_cache_file true \
|
||||
--max_length 2048 \
|
||||
--max_completion_length 1024 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 8 \
|
||||
--per_device_eval_batch_size 8 \
|
||||
--learning_rate 5e-7 \
|
||||
--gradient_accumulation_steps 8 \
|
||||
--eval_steps 500 \
|
||||
--save_steps 100 \
|
||||
--save_total_limit 20 \
|
||||
--logging_steps 1 \
|
||||
--output_dir output/GRPO_COUNTDOWN \
|
||||
--warmup_ratio 0.01 \
|
||||
--dataloader_num_workers 4 \
|
||||
--num_generations 8 \
|
||||
--temperature 1.0 \
|
||||
--system 'You are a helpful assistant. You first thinks about the reasoning process in the mind and then provides the user with the answer.' \
|
||||
--deepspeed zero3 \
|
||||
--log_completions true \
|
||||
--report_to wandb \
|
||||
--beta 0.001 \
|
||||
--num_iterations 1
|
||||
```
|
||||
|
||||
### Experimental Observations
|
||||
|
||||
After training for 2000 steps, the following observations were made:
|
||||
|
||||
1. **Custom CountdownORM Rewards**: The custom `CountdownORM` reward is steadily increasing, which proves that the model is learning to accomplish the task. The final task success rate is around 50%.
|
||||
2. **Format Reward**: The format reward remains at a relatively high level, fluctuating between 0.9 and 1.
|
||||
3. **Reward Standard Deviation**: The reward standard deviation stabilizes around 0.2-0.3 but has not fully converged, indicating that the model still has room to improve its reward.
|
||||
4. **Failure Cases**: By examining the model's output, many failure cases are due to the model making simple arithmetic errors. Therefore, the model's final convergence direction involves solving the problem step by step. Using a larger model may lead to different convergence directions.
|
||||
5. **Completion Length**: The completion length initially decreases and then increases, reaching its lowest point around 200 steps and later rising to approximately 350. This suggests the model underwent a change in reasoning approach.
|
||||
6. **Reasoning Patterns**: The model's reasoning pattern evolves over time. Eventually, the model realizes that performing permutations and step-by-step calculations is more effective for completing this task. The final sampled outputs mostly follow this format. Below are examples of how completions change across different steps:
|
||||
|
||||
#### Completion Examples at Different Steps
|
||||
|
||||
**Step 1:**
|
||||
|
||||
```plaintext
|
||||
<think>
|
||||
We need to create an equation using the given numbers [70, 5, 42, 30] exactly once and make it equal to 77.
|
||||
|
||||
First, let's consider the possibility of multiplication or division to get closer to 77. Notice that 77 is close to 70 and 42. If we can use these numbers together, it might work.
|
||||
|
||||
Multiplying 70 by 1.1 would give us approximately 77, which is very close. So, let's try to make 1.1 using the remaining numbers 5 and 30.
|
||||
If we take 30/5 = 6, and then 5/6 = 0.8333..., this isn't useful here, so let's look at other possibilities.
|
||||
|
||||
Next, let's consider if there's another way to achieve 77 using addition, subtraction, or another combination of our numbers. Given the numbers provided, it seems that multiplication or division will be key.
|
||||
|
||||
We can try subtracting 5 from 30 to get 25. Then, if we divide 42 by 25, we don't get a whole number, but we can round it to the nearest whole number, 1. Now, we need to make the equation fit 77. Let's multiply 25 by 3 and add 7 (since 77 = 25*3 + 7). However, we only have one 7 available and we need to find a way to use the remaining numbers to get 7.
|
||||
|
||||
Let's try:
|
||||
70 - 42 = 28
|
||||
Then, 28 + 5 = 33
|
||||
Next, 33 + 30 = 63
|
||||
Finally, 77 - 63 = 14, which doesn't help.
|
||||
|
||||
Considering different combinations, one viable way is:
|
||||
(70 - 42) * (30 / 5) = 28 * 6 = 168 / 2 = 84 / 2 = 42 / 2 = 21 / 3 = 7
|
||||
|
||||
So, (70 - 42) * (30 / 5) = 77.
|
||||
</think>
|
||||
<answer>(70 - 42) * (30 / 5) = 77</answer>
|
||||
```
|
||||
|
||||
**Step 200:**
|
||||
|
||||
```plaintext
|
||||
<think>
|
||||
We need to use all the given numbers in a way that the resultant value of the calculation is 99. Given these restrictions, it's clear we should look into how the numbers relate and the mathematical operations that will bring them together to meet our condition.
|
||||
|
||||
Firstly, let's evaluate the sums, differences, and multiplication/division among the numbers and their relations to see how this could lead us towards an outcome of 99.
|
||||
- Summing the three numbers: 96 + 70 + 73 = 239, which is far from 99. This shows addition isn't practical.
|
||||
- Subtracting numbers: For instance, subtracting the lowest two (i.e., 73 - 70 = 3) and combining with the highest one (96) does not work. Also, there is no other combination of subtraction that would result in the number needing to add up to 99.
|
||||
- Multiplying or dividing the numbers alone wouldn’t reach any near solution since none of the products or division results of the numbers themselves is very close to 99. We might think more carefully about the arrangement of them using a combination of the operations provided.
|
||||
Therefore, considering the possible arrangement, a combination like taking (73 - 70 + 96) may produce an effective solution as this will sum the values in such a manner that it aligns with the goal of 99.
|
||||
|
||||
So we conclude that the operation: 73 - 70 + 96 gives the right sequence to equal 99.
|
||||
</think>
|
||||
<answer>73 - 70 + 96 = 99</answer>
|
||||
```
|
||||
|
||||
**Step 500:**
|
||||
|
||||
```plaintext
|
||||
<think>
|
||||
Let's begin by exploring various possible combinations of the given numbers using the arithmetic operators we have access to.
|
||||
|
||||
- If I add the numbers, I get: 56 + 73 + 51 = 180 which is not equal to our target number (68)
|
||||
- If I subtract one number from another I will still exceed our target value as well since the maximum difference is: |(difference between max-min):| | (73 - 51) or | (73 - 56), etc.|
|
||||
- Let's multiply the numbers individually and see if the division operation can bring us closer to the goal:
|
||||
- (73 * 51) - 56 = 3723 - 56 = 3667 which isn't the goal either.
|
||||
- The only operator left is addition and subtraction; I'll try the combination:
|
||||
((73 - 56) + 51) gives me (17 + 51) = 68.
|
||||
|
||||
Thus, our solution fits the provided problem constraints and meets the required outcome.
|
||||
</think>
|
||||
<answer>(73 - 56) + 51 = 68</answer>
|
||||
```
|
||||
|
||||
**Step 1000:**
|
||||
|
||||
```plaintext
|
||||
<think>
|
||||
We need to form an expression using the given numbers (50, 86, and 49) such that it equals 85. Let's try different combinations:
|
||||
|
||||
(86 - 50) + 49 = 76 + 49 = 125 (too big)
|
||||
(86 - 49) + 50 = 37 + 50 = 87 (too big)
|
||||
(86 - 50) + 49 = 37 + 49 = 86 (too big)
|
||||
(86 + 50) - 49 = 136 - 49 = 87 (too big)
|
||||
(86 - 50) + (49 - x)? Not working!
|
||||
(50 + 86) - 49: this is 136 which isn't our aim.
|
||||
50 + (86 - 49) = 50 + 37 = 87 which wasn't correct.
|
||||
50 - 86 + 49 = -16 + 49: This doesn't match up as well.
|
||||
86 - (50 + 49) = 86 - 99 which doesn't work too.
|
||||
|
||||
We try one more:
|
||||
(86 + 49) - 50 = 135 - 50 = 85!
|
||||
|
||||
That's it!
|
||||
</think>
|
||||
<answer>(86 + 49) - 50 = 85</answer>
|
||||
```
|
||||
|
||||
**Step 2000:**
|
||||
|
||||
```plaintext
|
||||
<think>
|
||||
We need to form an expression using the numbers 58, 9, 78, and 44 which evaluates to 73. Let's try different combinations:
|
||||
(78 - 58) + (9 + 44) = 10 + 53 = 63 (too low)
|
||||
(78 - 58) + (9 - 44) = 20 - 35 = -15 (too low)
|
||||
(78 - 58) + (44 - 9) = 20 + 35 = 55 (too low)
|
||||
(78 + 58) - (9 + 44) = 136 - 53 = 83 (too high)
|
||||
(78 + 58) - (9 - 44) = 136 + 35 = 171 (too high)
|
||||
(78 + 58) - (44 + 9) = 136 + 53 = 189 (too high)
|
||||
(78 + 9) - (58 + 44) = 87 - 102 = -15 (too low)
|
||||
(78 + 9) - (58 - 44) = 87 - 14 = 73
|
||||
|
||||
So our solution is: (78 + 9) - (58 - 44) = 73
|
||||
</think>
|
||||
<answer>(78 + 9) - (58 - 44) = 73</answer>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Unstable Experiment Records
|
||||
|
||||
For learning rate set to `1e-6` and $\beta$ set to `0.04`, the model showed oscillations around step 200. Both the format reward and CountdownORM reward dropped significantly:
|
||||
|
||||
[Unstable Experiment Graph](../../resources/grpo_countdown_1.png)
|
||||
@@ -0,0 +1,667 @@
|
||||
|
||||
# Best Practices for Registering Multimodal Models
|
||||
|
||||
This document introduces how to register a multimodal model in ms-swift and successfully perform inference and training. Using Qwen2.5-Omni as an example, we will register a new model_type and template `my_qwen2_5_omni`, supporting training with text, images, videos, and audio. Since Qwen2.5-Omni is already registered in ms-swift, we can use our custom components by explicitly specifying the model_type and template.
|
||||
|
||||
## Environment Setup
|
||||
|
||||
```shell
|
||||
# Avoid future incompatibilities with documentation
|
||||
pip install "ms-swift>=4.0"
|
||||
|
||||
pip install "transformers==4.57.*" "qwen_omni_utils==0.0.8"
|
||||
```
|
||||
|
||||
## Model Registration
|
||||
|
||||
First, we need to register the model to obtain the model and processor.
|
||||
|
||||
```python
|
||||
from transformers import AutoConfig, PretrainedConfig, PreTrainedModel
|
||||
|
||||
from swift.model import (Model, ModelGroup, ModelMeta, MultiModelKeys, get_model_processor, register_model,
|
||||
register_model_arch, ModelLoader)
|
||||
from swift.model.models.qwen import patch_qwen_vl_utils
|
||||
from swift.model.patcher import patch_get_input_embeddings
|
||||
from swift.model.utils import use_submodel_func
|
||||
from swift.utils import get_env_args, Processor
|
||||
|
||||
register_model_arch(
|
||||
MultiModelKeys(
|
||||
'my_qwen2_5_omni',
|
||||
# `freeze_llm`, `freeze_vit`, `freeze_aligner` behavior is determined by the values below.
|
||||
# For example: full parameter training, if `freeze_vit=True`, it will freeze parameters of model layers prefixed with `thinker.audio_tower` and `thinker.visual`.
|
||||
# LoRA training, if `freeze_vit=False`, it will additionally add LoRA to Linear layers prefixed with `thinker.audio_tower` and `thinker.visual`.
|
||||
language_model=['thinker.model', 'thinker.lm_head'],
|
||||
vision_tower=['thinker.audio_tower', 'thinker.visual'],
|
||||
aligner=['thinker.audio_tower.proj', 'thinker.visual.merger'],
|
||||
# Generator parts will never be trained or remain frozen.
|
||||
# If you want `thinker.audio_tower` and `thinker.audio_tower.proj` to never be trained, you can place them in the generator and remove them from vision_tower and aligner.
|
||||
generator=['talker', 'token2wav'],
|
||||
))
|
||||
|
||||
class Qwen2_5OmniLoader(ModelLoader):
|
||||
|
||||
|
||||
def get_config(self, model_dir: str) -> PretrainedConfig:
|
||||
config = AutoConfig.from_pretrained(model_dir, trust_remote_code=True)
|
||||
enable_audio_output = get_env_args('ENABLE_AUDIO_OUTPUT', bool, None)
|
||||
if enable_audio_output is not None:
|
||||
config.enable_audio_output = enable_audio_output
|
||||
return config
|
||||
|
||||
def get_processor(self, model_dir: str, config: PretrainedConfig) -> Processor:
|
||||
from transformers import Qwen2_5OmniProcessor
|
||||
from qwen_omni_utils import vision_process
|
||||
processor = Qwen2_5OmniProcessor.from_pretrained(model_dir, trust_remote_code=True)
|
||||
# Control constants in qwen_omni_utils library via environment variables,
|
||||
# e.g., `MAX_PIXELS`, etc.
|
||||
patch_qwen_vl_utils(vision_process)
|
||||
return processor
|
||||
|
||||
def get_model(self, model_dir: str, config: PretrainedConfig, processor: Processor,
|
||||
model_kwargs) -> PreTrainedModel:
|
||||
from transformers import Qwen2_5OmniForConditionalGeneration
|
||||
print('Run my_qwen2_5_omni...')
|
||||
self.auto_model_cls = self.auto_model_cls or Qwen2_5OmniForConditionalGeneration
|
||||
model = super().get_model(model_dir, config, processor, model_kwargs)
|
||||
# For multimodal model consistency, we replace the model's forward/generate functions
|
||||
# with those of its language_model.
|
||||
# Handle additional parts separately.
|
||||
use_submodel_func(model, 'thinker')
|
||||
# Avoid inplace operations on leaf_variable during training
|
||||
# (replacing parts of input_embeds with images_embeds)
|
||||
patch_get_input_embeddings(model.thinker.visual, 'patch_embed')
|
||||
# Some custom settings for model/config (usually not needed; configure based on
|
||||
# specific model if errors occur during training/inference)
|
||||
model.config.keys_to_ignore_at_inference += ['hidden_states', 'attention_mask']
|
||||
model.config.talker_config.pad_token_id = None
|
||||
return model
|
||||
|
||||
|
||||
register_model(
|
||||
ModelMeta(
|
||||
'my_qwen2_5_omni',
|
||||
[
|
||||
ModelGroup([
|
||||
Model('Qwen/Qwen2.5-Omni-3B', 'Qwen/Qwen2.5-Omni-3B'),
|
||||
Model('Qwen/Qwen2.5-Omni-7B', 'Qwen/Qwen2.5-Omni-7B'),
|
||||
]),
|
||||
],
|
||||
# Function to get model and processor.
|
||||
Qwen2_5OmniLoader,
|
||||
template='my_qwen2_5_omni',
|
||||
is_multimodal=True, # Whether it's a multimodal model
|
||||
model_arch='my_qwen2_5_omni', # Usually set only for multimodal models
|
||||
# Used for automatic model_type matching
|
||||
architectures=['Qwen2_5OmniModel', 'Qwen2_5OmniForConditionalGeneration'],
|
||||
# Used to prompt users about dependency versions (can be removed)
|
||||
requires=['transformers>=4.50', 'soundfile', 'qwen_omni_utils', 'decord'],
|
||||
# Used to prompt users (can be removed)
|
||||
tags=['vision', 'video', 'audio'],
|
||||
# Additional files to save during full parameter training/merge-lora
|
||||
additional_saved_files=['spk_dict.pt'],
|
||||
))
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Test and debug
|
||||
model, processor = get_model_processor('Qwen/Qwen2.5-Omni-7B', model_type='my_qwen2_5_omni')
|
||||
```
|
||||
|
||||
## Template Registration
|
||||
|
||||
Second, we need to register a template to customize how text, images, videos, and audio are preprocessed (`_encode` and `_data_collator` methods). This is a key module for ms-swift's support of multimodal model training. Preprocessing methods should reference transformers inference implementation and align with it.
|
||||
|
||||
Template functions:
|
||||
|
||||
1. Support normal inference and training, preprocess text and multimodal information, and support grounding tasks.
|
||||
2. Support padding_free and packing training.
|
||||
3. Support mixed modality data training.
|
||||
|
||||
```python
|
||||
from functools import partial
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
import torch
|
||||
from transformers.integrations import is_deepspeed_zero3_enabled
|
||||
from swift import get_model_processor
|
||||
from swift.template import StdTemplateInputs, Template, TemplateMeta, get_template, register_template
|
||||
from swift.template.utils import Context, findall
|
||||
from swift.template.vision_utils import load_audio
|
||||
from swift.utils import Processor, get_env_args, get_logger, get_packed_seq_params, is_deepspeed_enabled, to_float_dtype
|
||||
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
class Qwen2_5OmniTemplate(Template):
|
||||
use_model = True # Whether model participation is required during preprocessing
|
||||
# Note: Not all multimodal models support padding_free/packing. Models in `transformers` library usually support it
|
||||
support_padding_free = True # Whether padding_free and packing are supported (multimodal models)
|
||||
norm_bbox = 'none' # Whether grounding tasks use absolute or norm1000 coordinates
|
||||
|
||||
# These tokens will not be truncated (e.g., when setting `--truncation_strategy left/right`)
|
||||
# and will be printed in abbreviated form (calling `template.safe_decode`)
|
||||
placeholder_tokens = ['<|IMAGE|>', '<|AUDIO|>', '<|VIDEO|>']
|
||||
|
||||
def init_processor(self, processor: Processor) -> None:
|
||||
"""Initialize some required constants when initializing the processor"""
|
||||
if processor is None:
|
||||
return
|
||||
super().init_processor(processor)
|
||||
from transformers.models.qwen2_5_omni.processing_qwen2_5_omni import Qwen2_5OmniProcessorKwargs
|
||||
default = Qwen2_5OmniProcessorKwargs._defaults
|
||||
self.seconds_per_chunk = default['videos_kwargs']['seconds_per_chunk']
|
||||
self.position_id_per_seconds = default['videos_kwargs']['position_id_per_seconds']
|
||||
self.use_audio_in_video = get_env_args('use_audio_in_video', bool, False)
|
||||
self.sampling_rate = get_env_args('sampling_rate', int, self.processor.feature_extractor.sampling_rate)
|
||||
# See grounding dataset customization documentation for `QWENVL_BBOX_FORMAT` meaning
|
||||
self.bbox_format = get_env_args('QWENVL_BBOX_FORMAT', str, 'legacy')
|
||||
|
||||
|
||||
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
|
||||
inputs: StdTemplateInputs) -> List[Context]:
|
||||
"""Load multimodal data and replace generic multimodal tags.
|
||||
For example: image tag from `<image>` -> `<|vision_bos|><|IMAGE|><|vision_eos|>`"""
|
||||
# Loading multimodal data can also be done in the `_encode` function, whichever is more convenient.
|
||||
from qwen_omni_utils import fetch_image, fetch_video
|
||||
if media_type == 'image':
|
||||
inputs.images[index] = fetch_image({'image': inputs.images[index]})
|
||||
return ['<|vision_bos|><|IMAGE|><|vision_eos|>']
|
||||
elif media_type == 'audio':
|
||||
if self.mode != 'vllm': # No processing needed in 'vllm' inference scenario
|
||||
inputs.audios[index] = load_audio(inputs.audios[index], self.sampling_rate)
|
||||
return ['<|audio_bos|><|AUDIO|><|audio_eos|>']
|
||||
elif media_type == 'video':
|
||||
video = inputs.videos[index]
|
||||
_video = fetch_video({'video': video})
|
||||
if isinstance(_video, torch.Tensor):
|
||||
_video = _video.to(torch.uint8)
|
||||
inputs.videos[index] = _video
|
||||
if self.use_audio_in_video:
|
||||
import librosa
|
||||
if video.startswith('http://') or video.startswith('https://'):
|
||||
import audioread
|
||||
video = audioread.ffdec.FFmpegAudioFile(video)
|
||||
video = librosa.load(video, sr=self.sampling_rate)[0]
|
||||
inputs.audios.insert(inputs.audio_idx, (video, 'video'))
|
||||
inputs.audio_idx += 1
|
||||
return ['<|vision_bos|><|audio_bos|><|VIDEO|><|audio_eos|><|vision_eos|>']
|
||||
else:
|
||||
return ['<|vision_bos|><|VIDEO|><|vision_eos|>']
|
||||
|
||||
|
||||
def replace_ref(self, ref: str, index: int, inputs: StdTemplateInputs) -> List[Context]:
|
||||
"""Replace generic tag for grounding tasks: `<ref-object>`"""
|
||||
if self.bbox_format == 'legacy':
|
||||
return [f'<|object_ref_start|>{ref}<|object_ref_end|>']
|
||||
else:
|
||||
return [ref]
|
||||
|
||||
def replace_bbox(self, bbox: List[int], index: int, inputs: StdTemplateInputs) -> List[Context]:
|
||||
"""Replace generic tag for grounding tasks: `<bbox>`"""
|
||||
if self.bbox_format == 'legacy':
|
||||
return [f'<|box_start|>{self._get_bbox_str(bbox)}<|box_end|>']
|
||||
else:
|
||||
return [str(bbox)]
|
||||
|
||||
def packing_row(self, row: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""Support packing & mrope.
|
||||
|
||||
Usually no need to inherit this function; here for customizing mrope's position_ids."""
|
||||
position_ids = []
|
||||
for r in row:
|
||||
r = r.copy()
|
||||
r['input_ids'] = torch.tensor(r['input_ids'])[None]
|
||||
position_ids.append(self._get_position_ids(r))
|
||||
packed = super().packing_row(row)
|
||||
packed['position_ids'] = torch.concat(position_ids, dim=-1)
|
||||
return packed
|
||||
|
||||
def _get_new_tokens_use_audio_in_video(self, i, *, video_grid_thw, video_second_per_grid, audio_lengths,
|
||||
video_token_id, audio_token_id):
|
||||
"""Helper function to support `use_audio_in_video` being True"""
|
||||
merge_size = self.processor.image_processor.merge_size
|
||||
grid_thw = video_grid_thw[i]
|
||||
height = grid_thw[1] // merge_size
|
||||
width = grid_thw[2] // merge_size
|
||||
audio_token_indices = torch.arange(audio_lengths[i])
|
||||
video_token_indices = torch.arange(grid_thw[0]).reshape(-1, 1, 1)
|
||||
|
||||
video_token_indices = torch.broadcast_to(video_token_indices,
|
||||
(video_token_indices.shape[0], height, width)).reshape(-1)
|
||||
video_token_indices = (video_token_indices * video_second_per_grid[i] * self.position_id_per_seconds)
|
||||
tokens_per_chunk = int(self.position_id_per_seconds * self.seconds_per_chunk)
|
||||
video_chunk_indexes = self.processor.get_chunked_index(video_token_indices, tokens_per_chunk)
|
||||
audio_chunk_indexes = self.processor.get_chunked_index(audio_token_indices, tokens_per_chunk)
|
||||
|
||||
res = []
|
||||
for j in range(max(len(video_chunk_indexes), len(audio_chunk_indexes))):
|
||||
if j < len(video_chunk_indexes):
|
||||
video_seq_length = video_chunk_indexes[j][1] - video_chunk_indexes[j][0]
|
||||
res += video_token_id * video_seq_length
|
||||
if j < len(audio_chunk_indexes):
|
||||
audio_seq_length = audio_chunk_indexes[j][1] - audio_chunk_indexes[j][0]
|
||||
res += audio_token_id * audio_seq_length
|
||||
return res
|
||||
|
||||
|
||||
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
|
||||
"""This determines how to convert text/images/audios/videos -> input_ids, labels, loss_scale, and multimodal content like pixel_values
|
||||
Processing logic can usually be borrowed from the corresponding model's preprocessing code implementation.
|
||||
Recommended: Perform inference alignment first, then training"""
|
||||
encoded = Template._encode(self, inputs) # Process text-only part; see custom model documentation for details
|
||||
logger.info_once('Run qwen2_5_omni template')
|
||||
processor = self.processor
|
||||
# Get multimodal content
|
||||
media_inputs = processor(
|
||||
text='',
|
||||
audio=inputs.audios or None,
|
||||
images=inputs.images or None,
|
||||
videos=inputs.videos or None,
|
||||
do_resize=False,
|
||||
return_tensors='pt')
|
||||
# We don't use input_ids and attention_mask produced by `processor` because it doesn't produce `labels`.
|
||||
media_inputs.pop('input_ids')
|
||||
media_inputs.pop('attention_mask')
|
||||
media_inputs = to_float_dtype(media_inputs, self.model_info.torch_dtype)
|
||||
|
||||
input_ids = encoded['input_ids']
|
||||
labels = encoded['labels']
|
||||
loss_scale = encoded.get('loss_scale', None)
|
||||
# audio modality
|
||||
audio_token_id = self._tokenize('<|AUDIO|>')
|
||||
idx_list = findall(input_ids, audio_token_id) # Find all audio_tokens
|
||||
feature_attention_mask = media_inputs.get('feature_attention_mask')
|
||||
if feature_attention_mask is not None:
|
||||
audio_feature_lengths = torch.sum(feature_attention_mask, dim=1)
|
||||
audio_lengths = ((audio_feature_lengths - 1) // 2 + 1 - 2) // 2 + 1
|
||||
else:
|
||||
audio_lengths = None
|
||||
audio_lengths_origin = audio_lengths
|
||||
# video_audios_mask is used to handle `use_audio_in_video`, distinguishing pure audio(0) from audio in video(1)
|
||||
video_audios_mask = []
|
||||
for i, audio in enumerate(inputs.audios):
|
||||
if isinstance(audio, tuple) and audio[1] == 'video':
|
||||
inputs.audios[i] = audio[0]
|
||||
video_audios_mask.append(True)
|
||||
else:
|
||||
video_audios_mask.append(False)
|
||||
video_audios_mask = torch.tensor(video_audios_mask)
|
||||
if idx_list:
|
||||
# Filter out audio content in videos (will be handled in video section)
|
||||
if self.use_audio_in_video:
|
||||
audio_lengths = audio_lengths[~video_audios_mask]
|
||||
|
||||
def _get_new_audio_tokens(i):
|
||||
return audio_token_id * audio_lengths[i]
|
||||
|
||||
# Expand multimodal tokens in input_ids
|
||||
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
|
||||
_get_new_audio_tokens)
|
||||
|
||||
# image and video modalities
|
||||
for media_type in ['image', 'video']:
|
||||
token = f'<|{media_type.upper()}|>'
|
||||
token_id = self._tokenize(token)
|
||||
idx_list = findall(input_ids, token_id)
|
||||
if idx_list:
|
||||
merge_size = processor.image_processor.merge_size
|
||||
media_grid_thw = media_inputs.get(f'{media_type}_grid_thw')
|
||||
if media_type == 'video' and self.use_audio_in_video:
|
||||
audio_lengths = audio_lengths_origin[video_audios_mask]
|
||||
video_second_per_grid = media_inputs['video_second_per_grid']
|
||||
_get_new_tokens_use_audio_in_video = partial(
|
||||
self._get_new_tokens_use_audio_in_video,
|
||||
video_grid_thw=media_grid_thw,
|
||||
video_second_per_grid=video_second_per_grid,
|
||||
audio_lengths=audio_lengths,
|
||||
video_token_id=token_id,
|
||||
audio_token_id=audio_token_id)
|
||||
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
|
||||
_get_new_tokens_use_audio_in_video)
|
||||
|
||||
else:
|
||||
|
||||
def _get_new_tokens(i):
|
||||
token_len = (media_grid_thw[i].prod() // (merge_size**2))
|
||||
return token_id * token_len
|
||||
|
||||
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
|
||||
_get_new_tokens)
|
||||
|
||||
encoded['input_ids'] = input_ids
|
||||
encoded['labels'] = labels
|
||||
encoded['loss_scale'] = loss_scale
|
||||
encoded.update(media_inputs) # Add multimodal content
|
||||
return encoded
|
||||
|
||||
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""This function is typically used to solve the zero2/zero3 hanging issue in mixed model training,
|
||||
i.e., some processes have pure text data without passing through vit, while others have image data that passed through vit.
|
||||
Here we create dummy_image to solve this.
|
||||
|
||||
This function will be registered in the pre_forward_hook before `model.forward`.
|
||||
This function should return input_embeds containing multimodal information.
|
||||
"""
|
||||
if not self.is_training:
|
||||
return inputs
|
||||
|
||||
input_ids = inputs['input_ids']
|
||||
input_features = inputs.get('input_features')
|
||||
feature_attention_mask = inputs.get('feature_attention_mask')
|
||||
|
||||
base_model = self.get_base_model(model)
|
||||
inputs_embeds = base_model.thinker.model.embed_tokens(input_ids)
|
||||
thinker_config = model.config.thinker_config
|
||||
# Helper function for handling text/image/video mixed modality data scenarios. (internally creates dummy_image)
|
||||
inputs_embeds = self._get_inputs_embeds_hf(inputs_embeds, inputs, model.thinker.visual, self.processor,
|
||||
thinker_config)
|
||||
# Mixed modality data scenarios containing audio
|
||||
if input_features is None:
|
||||
if is_deepspeed_enabled() and not is_deepspeed_zero3_enabled():
|
||||
# Note: Due to transformers implementation, the number of passes through audio model layers is related to the number of audios
|
||||
# Therefore, zero3 will hang in scenarios where different processes have different numbers of audios (requires modification of transformers code to fix). Use zero2 in this scenario.
|
||||
input_features = input_ids.new_zeros([1, 128, 128], dtype=model.thinker.audio_tower.dtype)
|
||||
feature_attention_mask = input_ids.new_ones([1, 128], dtype=torch.bool)
|
||||
audio_res = model.thinker.get_audio_features(input_features, feature_attention_mask)
|
||||
# Compatible with transformers 5.0
|
||||
if hasattr(audio_res, 'last_hidden_state'):
|
||||
audio_embeds = audio_res.last_hidden_state
|
||||
else:
|
||||
audio_embeds = audio_res
|
||||
inputs_embeds = inputs_embeds + audio_embeds.mean() * 0.
|
||||
else:
|
||||
audio_res = model.thinker.get_audio_features(input_features, feature_attention_mask)
|
||||
# Compatible with transformers 5.0
|
||||
if hasattr(audio_res, 'last_hidden_state'):
|
||||
audio_embeds = audio_res.last_hidden_state
|
||||
else:
|
||||
audio_embeds = audio_res
|
||||
audio_mask = (input_ids == thinker_config.audio_token_index).unsqueeze(-1).expand_as(inputs_embeds)
|
||||
audio_embeds = audio_embeds.to(inputs_embeds.device, inputs_embeds.dtype)
|
||||
inputs_embeds = inputs_embeds.masked_scatter(audio_mask, audio_embeds)
|
||||
|
||||
return {'inputs_embeds': inputs_embeds}
|
||||
|
||||
def _get_position_ids(self, inputs: Dict[str, Any]):
|
||||
"""Helper function to get mrope's position_ids"""
|
||||
feature_attention_mask = inputs.get('feature_attention_mask')
|
||||
if feature_attention_mask is not None:
|
||||
audio_feature_lengths = torch.sum(feature_attention_mask, dim=1)
|
||||
else:
|
||||
audio_feature_lengths = None
|
||||
video_second_per_grid = inputs.pop('video_second_per_grid', None)
|
||||
input_ids = inputs['input_ids']
|
||||
attention_mask = inputs.get('attention_mask')
|
||||
if attention_mask is None:
|
||||
attention_mask = torch.ones_like(input_ids)
|
||||
position_ids, _ = self.model.thinker.get_rope_index(
|
||||
input_ids,
|
||||
inputs.get('image_grid_thw'),
|
||||
inputs.get('video_grid_thw'),
|
||||
attention_mask,
|
||||
self.use_audio_in_video,
|
||||
audio_feature_lengths,
|
||||
video_second_per_grid,
|
||||
)
|
||||
return self._concat_text_position_ids(position_ids) # First dimension is text_position_ids
|
||||
|
||||
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
|
||||
"""Passed to dataloader's `collate_fn`"""
|
||||
res = super()._data_collator(batch, padding_to=padding_to)
|
||||
if not self.padding_free and self.is_training:
|
||||
# padding_free/packing scenarios will handle position_ids in packing_row.
|
||||
res['position_ids'] = self._get_position_ids(res)
|
||||
if 'position_ids' in res:
|
||||
# Create `packed_seq_params` to support padding_free/packing & flash-attn
|
||||
position_ids = res['position_ids']
|
||||
res['position_ids'] = position_ids[1:]
|
||||
res['text_position_ids'] = text_position_ids = position_ids[0]
|
||||
# https://github.com/huggingface/transformers/pull/40194
|
||||
res.update(get_packed_seq_params(text_position_ids))
|
||||
return res
|
||||
|
||||
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""Handle multimodal part in `_data_collator` function. (This function is compatible with padding_free/packing)"""
|
||||
res = super()._data_collator_mm_data(batch)
|
||||
video_second_per_grid = self.gather_list(batch, 'video_second_per_grid')
|
||||
if video_second_per_grid:
|
||||
res['video_second_per_grid'] = video_second_per_grid
|
||||
input_features = [b['input_features'] for b in batch if b.get('input_features') is not None]
|
||||
feature_attention_mask = [
|
||||
b['feature_attention_mask'] for b in batch if b.get('feature_attention_mask') is not None
|
||||
]
|
||||
if input_features:
|
||||
res['input_features'] = torch.concat(input_features)
|
||||
res['feature_attention_mask'] = torch.concat(feature_attention_mask)
|
||||
return res
|
||||
|
||||
def generate(self, model, *args, **kwargs):
|
||||
"""`TransformersEngine` will call template.generate method for text generation; inherit here for customization."""
|
||||
if kwargs.get('video_grid_thw') is not None:
|
||||
kwargs['use_audio_in_video'] = self.use_audio_in_video
|
||||
return super().generate(model, *args, **kwargs)
|
||||
|
||||
|
||||
register_template(
|
||||
TemplateMeta('my_qwen2_5_omni', prefix=[], prompt=['<|im_start|>user\n{{QUERY}}<|im_end|>\n<|im_start|>assistant\n'],
|
||||
chat_sep=['<|im_end|>\n'], suffix=['<|im_end|>'],
|
||||
system_prefix=['<|im_start|>system\n{{SYSTEM}}<|im_end|>\n'],
|
||||
default_system='You are a helpful assistant.', stop_words=['<|endoftext|>'],
|
||||
agent_template='hermes',
|
||||
template_cls=Qwen2_5OmniTemplate))
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Test and debug
|
||||
model, processor = get_model_processor('Qwen/Qwen2.5-Omni-7B', model_type='my_qwen2_5_omni')
|
||||
template = get_template(processor, template_type='my_qwen2_5_omni')
|
||||
data = {
|
||||
'messages': [
|
||||
{'role': 'user', 'content': 'Describe the video<video> and image<image> content.'},
|
||||
{'role': 'assistant', 'content': 'A child and a cat.'},
|
||||
],
|
||||
'videos': ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4'],
|
||||
'images': ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png'],
|
||||
}
|
||||
template.set_mode('train')
|
||||
encoded = template.encode(data)
|
||||
print('input_ids: ' + template.safe_decode(encoded['input_ids']))
|
||||
print('labels: ' + template.safe_decode(encoded['labels']))
|
||||
print('keys: ' + str(encoded.keys()))
|
||||
```
|
||||
|
||||
## Inference Alignment
|
||||
|
||||
Next, you need to align inference between TransformersEngine and transformers. Typically you need to align `input_ids` and output content. You can write the following test function:
|
||||
|
||||
```python
|
||||
import os
|
||||
from transformers import Qwen2_5OmniForConditionalGeneration, Qwen2_5OmniProcessor
|
||||
from qwen_omni_utils import process_mm_info
|
||||
from modelscope import snapshot_download
|
||||
from swift.infer_engine import TransformersEngine, InferRequest, RequestConfig
|
||||
import requests
|
||||
|
||||
def infer_hf():
|
||||
model_dir = snapshot_download('Qwen/Qwen2.5-Omni-7B')
|
||||
model = Qwen2_5OmniForConditionalGeneration.from_pretrained(
|
||||
model_dir, torch_dtype="auto", device_map="auto", attn_implementation='flash_attention_2')
|
||||
processor = Qwen2_5OmniProcessor.from_pretrained(model_dir)
|
||||
# Use decord to read video (url not yet supported)
|
||||
resp = requests.get('https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4')
|
||||
with open('_baby.mp4', 'wb') as f:
|
||||
f.write(resp.content)
|
||||
|
||||
conversation = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "video", "video": "_baby.mp4"},
|
||||
{"type": "image", "image": "http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png"},
|
||||
{"type": "text", "text": "Describe the video and image."},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
USE_AUDIO_IN_VIDEO = False
|
||||
text = processor.apply_chat_template(conversation, add_generation_prompt=True, tokenize=False)
|
||||
audios, images, videos = process_mm_info(conversation, use_audio_in_video=USE_AUDIO_IN_VIDEO)
|
||||
inputs = processor(text=text, audio=audios, images=images, videos=videos, return_tensors="pt", padding=True,
|
||||
use_audio_in_video=USE_AUDIO_IN_VIDEO)
|
||||
inputs = inputs.to(model.device).to(model.dtype)
|
||||
text_ids = model.generate(**inputs, use_audio_in_video=USE_AUDIO_IN_VIDEO, thinker_do_sample=False,
|
||||
return_audio=False)
|
||||
text = processor.batch_decode(text_ids[:, inputs['input_ids'].shape[1]:], skip_special_tokens=True, clean_up_tokenization_spaces=False)
|
||||
return inputs['input_ids'][0].tolist(), text[0]
|
||||
|
||||
def test_my_qwen2_5_omni():
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-Omni-7B', model_type='my_qwen2_5_omni', attn_impl='flash_attention_2')
|
||||
infer_request = InferRequest(messages=[{
|
||||
"role": "user",
|
||||
"content": "<video><image>Describe the video and image.",
|
||||
}],
|
||||
videos=["https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4"],
|
||||
images=["http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png"],
|
||||
)
|
||||
request_config = RequestConfig(temperature=0, max_tokens=512)
|
||||
input_ids = engine.template.encode(infer_request)['input_ids']
|
||||
resp_list = engine.infer([infer_request], request_config)
|
||||
resp = resp_list[0].choices[0].message.content
|
||||
return input_ids, resp
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Enable debug mode, will print input_ids and generate_ids from `TransformersEngine.infer`
|
||||
os.environ['SWIFT_DEBUG'] = '1'
|
||||
input_ids_hf, response_hf = infer_hf()
|
||||
input_ids_swift, response_swift = test_my_qwen2_5_omni()
|
||||
# Test input_ids and response alignment
|
||||
assert input_ids_hf == input_ids_swift
|
||||
assert response_hf == response_swift
|
||||
```
|
||||
|
||||
|
||||
## Start Training
|
||||
|
||||
Train using Python code, which is usually easier to debug:
|
||||
|
||||
|
||||
```python
|
||||
from swift import sft_main, SftArguments
|
||||
import os
|
||||
if __name__ == '__main__':
|
||||
os.environ['MAX_PIXELS'] = '1003520'
|
||||
sft_main(SftArguments(
|
||||
model='Qwen/Qwen2.5-Omni-7B',
|
||||
dataset=['AI-ModelScope/LaTeX_OCR#5000'],
|
||||
model_type='my_qwen2_5_omni',
|
||||
template='my_qwen2_5_omni',
|
||||
load_from_cache_file=True,
|
||||
split_dataset_ratio=0.01,
|
||||
tuner_type='lora',
|
||||
torch_dtype='bfloat16',
|
||||
attn_impl='flash_attn',
|
||||
padding_free=True,
|
||||
num_train_epochs=1,
|
||||
per_device_train_batch_size=16,
|
||||
per_device_eval_batch_size=16,
|
||||
learning_rate=1e-4,
|
||||
lora_rank=8,
|
||||
lora_alpha=32,
|
||||
target_modules=['all-linear'],
|
||||
freeze_vit=True,
|
||||
freeze_aligner=True,
|
||||
gradient_accumulation_steps=1,
|
||||
eval_steps=50,
|
||||
save_steps=50,
|
||||
save_total_limit=2,
|
||||
logging_steps=5,
|
||||
max_length=2048,
|
||||
output_dir='output',
|
||||
warmup_ratio=0.05,
|
||||
dataloader_num_workers=4,
|
||||
dataset_num_proc=1,
|
||||
))
|
||||
```
|
||||
|
||||
Train using command line:
|
||||
|
||||
```shell
|
||||
# 4 * 35GiB
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
||||
NPROC_PER_NODE=4 \
|
||||
VIDEO_MAX_PIXELS=50176 \
|
||||
FPS_MAX_FRAMES=12 \
|
||||
MAX_PIXELS=1003520 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen2.5-Omni-7B \
|
||||
--model_type my_qwen2_5_omni \
|
||||
--template my_qwen2_5_omni \
|
||||
--external_plugins 'examples/custom/my_qwen2_5_omni/my_register.py' \
|
||||
--dataset 'AI-ModelScope/alpaca-gpt4-data-zh#2000' \
|
||||
'AI-ModelScope/LaTeX_OCR:human_handwrite#2000' \
|
||||
'speech_asr/speech_asr_aishell1_trainsets:validation#2000' \
|
||||
'swift/VideoChatGPT:all#2000' \
|
||||
--load_from_cache_file true \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--tuner_type lora \
|
||||
--torch_dtype bfloat16 \
|
||||
--attn_impl flash_attn \
|
||||
--padding_free true \
|
||||
--packing true \
|
||||
--num_train_epochs 3 \
|
||||
--per_device_train_batch_size 1 \
|
||||
--per_device_eval_batch_size 1 \
|
||||
--learning_rate 1e-4 \
|
||||
--lora_rank 8 \
|
||||
--lora_alpha 32 \
|
||||
--target_modules all-linear \
|
||||
--freeze_vit true \
|
||||
--freeze_aligner true \
|
||||
--gradient_accumulation_steps 1 \
|
||||
--eval_steps 50 \
|
||||
--save_steps 50 \
|
||||
--save_total_limit 2 \
|
||||
--logging_steps 5 \
|
||||
--max_length 4096 \
|
||||
--output_dir output \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 4 \
|
||||
--dataset_num_proc 1 \
|
||||
--deepspeed zero2
|
||||
```
|
||||
|
||||
Perform inference on the validation set after training: (Environment variables should align with training)
|
||||
|
||||
```shell
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
VIDEO_MAX_PIXELS=50176 \
|
||||
FPS_MAX_FRAMES=12 \
|
||||
MAX_PIXELS=1003520 \
|
||||
swift infer \
|
||||
--adapters output/vx-xxx/checkpoint-xxx \
|
||||
--stream true \
|
||||
--max_new_tokens 512 \
|
||||
--load_data_args true
|
||||
```
|
||||
|
||||
Use the following command to push training weights to Modelscope:
|
||||
|
||||
```shell
|
||||
swift export \
|
||||
--adapters output/vx-xxx/checkpoint-xxx \
|
||||
--push_to_hub true \
|
||||
--hub_model_id '<your-model-id>' \
|
||||
--hub_token '<your-sdk-token>'
|
||||
```
|
||||
|
||||
## Submitting a PR
|
||||
|
||||
If you want to submit a PR to ms-swift, you need to run the following additional commands to lint and format the code:
|
||||
|
||||
```shell
|
||||
pip install pre-commit
|
||||
pre-commit run --all-files
|
||||
```
|
||||
@@ -0,0 +1,271 @@
|
||||
# Metax Support
|
||||
|
||||
## 1. use swift with Metax
|
||||
you can either build an image or pull an existing one. Here, we demonstrate how to use ms-swift on Metax by pulling a pre-built image as an example.
|
||||
### 1.1. start ms-swift Container
|
||||
```bash
|
||||
docker pull mx-devops-acr-cn-shanghai.cr.volces.com/opensource/public-ai-release/maca/ms-swift:3.10.3-maca.ai3.3.0.16-torch2.6-py310-ubuntu22.04-amd64
|
||||
# you may modify privileged option and mount only specific GPU cards.
|
||||
# please refer to our documents on https://developer.metax-tech.com
|
||||
# Metax GPUs must be mounted via --device=/dev/dri --device=/dev/mxcd
|
||||
docker run -it --net=host --uts=host --ipc=host --privileged=true --group-add video \
|
||||
--shm-size 100gb --ulimit memlock=-1 \
|
||||
--security-opt seccomp=unconfined --security-opt apparmor=unconfined \
|
||||
--device=/dev/dri --device=/dev/mxcd \
|
||||
-v /root/workspace:/external \
|
||||
--name swift_test \
|
||||
mx-devops-acr-cn-shanghai.cr.volces.com/opensource/public-ai-release/maca/ms-swift:3.10.3-maca.ai3.3.0.16-torch2.6-py310-ubuntu22.04-amd64
|
||||
```
|
||||
## 2. Environment check
|
||||
### 2.1. Check Metax available
|
||||
Thanks to its compatibility with CUDA, we can use the same approach as NVIDIA to check the availability of Metax devices.
|
||||
```python
|
||||
import torch
|
||||
print(torch.cuda.is_available())
|
||||
# True
|
||||
```
|
||||
### 2.2. Check the P2P connections
|
||||
```bash
|
||||
mx-smi topo -m
|
||||
# output
|
||||
=================== MetaX System Management Interface Log ===================
|
||||
Timestamp : Wed Feb 11 16:37:10 2026
|
||||
|
||||
Attached GPUs : 8
|
||||
Device link type matrix
|
||||
GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7 Node Affinity CPU Affinity
|
||||
GPU0 X MX MX MX NODE NODE NODE NODE 0 0-31,64-95
|
||||
GPU1 MX X MX MX NODE NODE NODE NODE 0 0-31,64-95
|
||||
GPU2 MX MX X MX NODE NODE NODE NODE 0 0-31,64-95
|
||||
GPU3 MX MX MX X NODE NODE NODE NODE 0 0-31,64-95
|
||||
GPU4 NODE NODE NODE NODE X MX MX MX 0 0-31,64-95
|
||||
GPU5 NODE NODE NODE NODE MX X MX MX 0 0-31,64-95
|
||||
GPU6 NODE NODE NODE NODE MX MX X MX 0 0-31,64-95
|
||||
GPU7 NODE NODE NODE NODE MX MX MX X 0 0-31,64-95
|
||||
|
||||
Legend:
|
||||
X = Self
|
||||
SYS = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)
|
||||
NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node
|
||||
PHB = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU)
|
||||
PXB = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge)
|
||||
PIX = Connection traversing at most a single PCIe bridge
|
||||
MX = Connection traversing MetaXLink
|
||||
ETH = Connection traversing Eth
|
||||
NA = Connection type is unknown
|
||||
```
|
||||
### 2.3. check the status of the GPUs
|
||||
```bash
|
||||
mx-smi
|
||||
# output
|
||||
=================== MetaX System Management Interface Log ===================
|
||||
Timestamp : Wed Feb 11 09:55:49 2026
|
||||
|
||||
Attached GPUs : 8
|
||||
+---------------------------------------------------------------------------------+
|
||||
| MX-SMI 2.2.9 Kernel Mode Driver Version: 3.4.4 |
|
||||
| MACA Version: 3.3.0.15 BIOS Version: 1.30.0.0 |
|
||||
|------------------+-----------------+---------------------+----------------------|
|
||||
| Board Name | GPU Persist-M | Bus-id | GPU-Util sGPU-M |
|
||||
| Pwr:Usage/Cap | Temp Perf | Memory-Usage | GPU-State |
|
||||
|==================+=================+=====================+======================|
|
||||
| 0 MetaX C500 | 0 Off | 0000:0e:00.0 | 0% Disabled |
|
||||
| 57W / 350W | 35C P0 | 826/65536 MiB | Available |
|
||||
+------------------+-----------------+---------------------+----------------------+
|
||||
| 1 MetaX C500 | 1 Off | 0000:0f:00.0 | 0% Disabled |
|
||||
| 58W / 350W | 37C P0 | 826/65536 MiB | Available |
|
||||
+------------------+-----------------+---------------------+----------------------+
|
||||
| 2 MetaX C500 | 2 Off | 0000:10:00.0 | 0% Disabled |
|
||||
| 58W / 350W | 36C P0 | 826/65536 MiB | Available |
|
||||
+------------------+-----------------+---------------------+----------------------+
|
||||
| 3 MetaX C500 | 3 Off | 0000:12:00.0 | 0% Disabled |
|
||||
| 60W / 350W | 35C P0 | 826/65536 MiB | Available |
|
||||
+------------------+-----------------+---------------------+----------------------+
|
||||
| 4 MetaX C500 | 4 Off | 0000:35:00.0 | 0% Disabled |
|
||||
| 57W / 350W | 33C P0 | 826/65536 MiB | Available |
|
||||
+------------------+-----------------+---------------------+----------------------+
|
||||
| 5 MetaX C500 | 5 Off | 0000:36:00.0 | 0% Disabled |
|
||||
| 56W / 350W | 34C P0 | 826/65536 MiB | Available |
|
||||
+------------------+-----------------+---------------------+----------------------+
|
||||
| 6 MetaX C500 | 6 Off | 0000:37:00.0 | 0% Disabled |
|
||||
| 55W / 350W | 34C P0 | 826/65536 MiB | Available |
|
||||
+------------------+-----------------+---------------------+----------------------+
|
||||
| 7 MetaX C500 | 7 Off | 0000:38:00.0 | 0% Disabled |
|
||||
| 56W / 350W | 36C P0 | 826/65536 MiB | Available |
|
||||
+------------------+-----------------+---------------------+----------------------+
|
||||
|
||||
+---------------------------------------------------------------------------------+
|
||||
| Process: |
|
||||
| GPU PID Process Name GPU Memory |
|
||||
| Usage(MiB) |
|
||||
|=================================================================================|
|
||||
| no process found |
|
||||
+---------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## 3. run example
|
||||
We support direct use of the community version. However, we also provide a more optimized version in the image under /workspace and strongly recommend using it.
|
||||
|
||||
### 3.1. run swift example
|
||||
In most scenarios, we can run Swift's examples directly.
|
||||
```bash
|
||||
# We assume that the ms-swift code is under /workspace
|
||||
cd /workspace/ms-swift/
|
||||
bash examples/train/full/train.sh
|
||||
|
||||
```
|
||||
|
||||
```bash
|
||||
# output:
|
||||
{'loss': 1.47077751, 'grad_norm': 10.5625, 'learning_rate': 2e-06, 'token_acc': 0.65511727, 'epoch': 0.01, 'global_step/max_steps': '1/94', 'percentage': '1.06%', 'elapsed_time': '2s', 'remaining_time': '4m 28s', 'memory(GiB)': 4.87, 'train_speed(iter/s)': 0.345807}
|
||||
{'loss': 1.58882141, 'grad_norm': 10.75, 'learning_rate': 1e-05, 'token_acc': 0.61763144, 'epoch': 0.05, 'global_step/max_steps': '5/94', 'percentage': '5.32%', 'elapsed_time': '10s', 'remaining_time': '3m 12s', 'memory(GiB)': 5.64, 'train_speed(iter/s)': 0.461462}
|
||||
{'loss': 1.56617603, 'grad_norm': 12.8125, 'learning_rate': 9.92e-06, 'token_acc': 0.61519274, 'epoch': 0.11, 'global_step/max_steps': '10/94', 'percentage': '10.64%', 'elapsed_time': '20s', 'remaining_time': '2m 52s', 'memory(GiB)': 5.64, 'train_speed(iter/s)': 0.485796}
|
||||
{'loss': 1.63347206, 'grad_norm': 13.6875, 'learning_rate': 9.69e-06, 'token_acc': 0.60373975, 'epoch': 0.16, 'global_step/max_steps': '15/94', 'percentage': '15.96%', 'elapsed_time': '30s', 'remaining_time': '2m 39s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.493855}
|
||||
{'loss': 1.60613976, 'grad_norm': 11.0, 'learning_rate': 9.32e-06, 'token_acc': 0.59997221, 'epoch': 0.21, 'global_step/max_steps': '20/94', 'percentage': '21.28%', 'elapsed_time': '39s', 'remaining_time': '2m 27s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.500516}
|
||||
{'loss': 1.45015478, 'grad_norm': 15.25, 'learning_rate': 8.8e-06, 'token_acc': 0.62373584, 'epoch': 0.27, 'global_step/max_steps': '25/94', 'percentage': '26.60%', 'elapsed_time': '49s', 'remaining_time': '2m 16s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.50548}
|
||||
{'loss': 1.39427547, 'grad_norm': 13.9375, 'learning_rate': 8.18e-06, 'token_acc': 0.6357994, 'epoch': 0.32, 'global_step/max_steps': '30/94', 'percentage': '31.91%', 'elapsed_time': '59s', 'remaining_time': '2m 5s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.508409}
|
||||
{'loss': 1.53672237, 'grad_norm': 11.125, 'learning_rate': 7.45e-06, 'token_acc': 0.61650612, 'epoch': 0.37, 'global_step/max_steps': '35/94', 'percentage': '37.23%', 'elapsed_time': '1m 8s', 'remaining_time': '1m 55s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.510425}
|
||||
{'loss': 1.54039021, 'grad_norm': 13.8125, 'learning_rate': 6.65e-06, 'token_acc': 0.61613974, 'epoch': 0.43, 'global_step/max_steps': '40/94', 'percentage': '42.55%', 'elapsed_time': '1m 18s', 'remaining_time': '1m 45s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.512302}
|
||||
{'loss': 1.40159426, 'grad_norm': 9.4375, 'learning_rate': 5.79e-06, 'token_acc': 0.64041773, 'epoch': 0.48, 'global_step/max_steps': '45/94', 'percentage': '47.87%', 'elapsed_time': '1m 27s', 'remaining_time': '1m 35s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.512983}
|
||||
{'loss': 1.54977188, 'grad_norm': 11.9375, 'learning_rate': 4.91e-06, 'token_acc': 0.61078816, 'epoch': 0.53, 'global_step/max_steps': '50/94', 'percentage': '53.19%', 'elapsed_time': '1m 37s', 'remaining_time': '1m 25s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.514489}
|
||||
{'loss': 1.6754509, 'grad_norm': 13.0625, 'learning_rate': 4.04e-06, 'token_acc': 0.58574393, 'epoch': 0.59, 'global_step/max_steps': '55/94', 'percentage': '58.51%', 'elapsed_time': '1m 46s', 'remaining_time': '1m 15s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.515752}
|
||||
{'loss': 1.37204351, 'grad_norm': 9.25, 'learning_rate': 3.19e-06, 'token_acc': 0.6391937, 'epoch': 0.64, 'global_step/max_steps': '60/94', 'percentage': '63.83%', 'elapsed_time': '1m 56s', 'remaining_time': '1m 5s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.516829}
|
||||
{'loss': 1.47697926, 'grad_norm': 11.375, 'learning_rate': 2.4e-06, 'token_acc': 0.62817259, 'epoch': 0.69, 'global_step/max_steps': '65/94', 'percentage': '69.15%', 'elapsed_time': '2m 5s', 'remaining_time': '55s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.517947}
|
||||
{'loss': 1.4336628, 'grad_norm': 8.125, 'learning_rate': 1.69e-06, 'token_acc': 0.63453862, 'epoch': 0.75, 'global_step/max_steps': '70/94', 'percentage': '74.47%', 'elapsed_time': '2m 14s', 'remaining_time': '46s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.518833}
|
||||
{'loss': 1.54315252, 'grad_norm': 9.625, 'learning_rate': 1.08e-06, 'token_acc': 0.60202073, 'epoch': 0.8, 'global_step/max_steps': '75/94', 'percentage': '79.79%', 'elapsed_time': '2m 24s', 'remaining_time': '36s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.519627}
|
||||
{'loss': 1.47180223, 'grad_norm': 9.5625, 'learning_rate': 6e-07, 'token_acc': 0.62211501, 'epoch': 0.85, 'global_step/max_steps': '80/94', 'percentage': '85.11%', 'elapsed_time': '2m 33s', 'remaining_time': '26s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.520284}
|
||||
{'loss': 1.44068375, 'grad_norm': 10.125, 'learning_rate': 2.5e-07, 'token_acc': 0.62673112, 'epoch': 0.91, 'global_step/max_steps': '85/94', 'percentage': '90.43%', 'elapsed_time': '2m 43s', 'remaining_time': '17s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.520331}
|
||||
{'loss': 1.44893646, 'grad_norm': 8.375, 'learning_rate': 5e-08, 'token_acc': 0.63837478, 'epoch': 0.96, 'global_step/max_steps': '90/94', 'percentage': '95.74%', 'elapsed_time': '2m 52s', 'remaining_time': '7s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.520707}
|
||||
{'train_runtime': 183.4332, 'train_samples_per_second': 8.177, 'train_steps_per_second': 0.512, 'train_loss': 1.50650934, 'token_acc': 0.6194337, 'epoch': 1.0, 'global_step/max_steps': '94/94', 'percentage': '100.00%', 'elapsed_time': '3m 3s', 'remaining_time': '0s', 'memory(GiB)': 6.5, 'train_speed(iter/s)': 0.512463}
|
||||
Train: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 94/94 [03:03<00:00, 1.95s/it]
|
||||
[INFO:swift] last_model_checkpoint: /workspace/ms-swift/output/v0-20260211-143035/checkpoint-94
|
||||
[INFO:swift] best_model_checkpoint: None
|
||||
[INFO:swift] images_dir: /workspace/ms-swift/output/v0-20260211-143035/images
|
||||
[INFO:swift] End time of running main: 2026-02-11 14:34:09.521336
|
||||
|
||||
```
|
||||
### 3.2. run swift example with Megatron-LM
|
||||
if you want to use Megatron-LM as Swift's backend, you should set MEGATRON_LM_PATH to /workspace/Megatron-LM-0.15.0 or other versions.
|
||||
|
||||
```bash
|
||||
export MEGATRON_LM_PATH=/workspace/Megatron-LM-0.15.0
|
||||
cd /workspace/ms-swift
|
||||
bash examples/megatron/pretrain.sh
|
||||
```
|
||||
|
||||
### 3.3. use other versions of ms-swift
|
||||
The Metax platform requires the use of MACA-compatible software packages. For instance, compiling depends on torch2.8. We need to use torch2.8+maca3.3.x.x. By default, the installation will overwrite the torch within the environment. Therefore, we recommend using the --no-deps parameter for installation
|
||||
```bash
|
||||
|
||||
git clone -b ${SWIFT_VERSION} https://github.com/modelscope/ms-swift.git
|
||||
cd ms-swift
|
||||
pip install . --no-deps
|
||||
|
||||
```
|
||||
After each environment change, the torch and its availability should be checked
|
||||
```bash
|
||||
pip list |grep torch
|
||||
# output:
|
||||
# torch2.x.x+metax3.x.x.x
|
||||
```
|
||||
|
||||
```python
|
||||
import torch
|
||||
torch.cuda.is_available()
|
||||
```
|
||||
|
||||
### 3.4. Differences between Metax and NVIDIA CUDA
|
||||
We are largely aligned with NVIDIA, but there are some differences in certain software and environment variables.
|
||||
|
||||
#### 3.4.1. MACA_MPS_MODE
|
||||
By default, MACA does not allow multiple processes to run on a single GPU. Therefore, when the GPU is already occupied, you cannot launch another process. To enable this scenario, you need to set MACA_MPS_MODE=1
|
||||
```bash
|
||||
# run other scripts ...
|
||||
export MACA_MPS_MODE=1
|
||||
cd /workspace/ms-swift/
|
||||
bash examples/train/full/train.sh
|
||||
```
|
||||
#### 3.4.2. MCCL_SOCKET_IFNAME GLOO_SOCKET_IFNAME & MCCL_IB_HCA
|
||||
When using MACA in a multi-node setup, you need to set the environment variables MCCL_SOCKET_IFNAME, GLOO_SOCKET_IFNAME, and MCCL_IB_HCA to ensure proper inter-node communication.
|
||||
We can use mx-smi and ifconfig to determine which InfiniBand devices and network device are being used.
|
||||
```bash
|
||||
ifconfig
|
||||
# output
|
||||
ens20f0np0: xxx
|
||||
inet: your node ip
|
||||
xxx
|
||||
...
|
||||
```
|
||||
```bash
|
||||
mx-smi topo -n
|
||||
# output
|
||||
mx-smi version: 2.2.9
|
||||
|
||||
=================== MetaX System Management Interface Log ===================
|
||||
Timestamp : Wed Feb 11 18:53:44 2026
|
||||
|
||||
Attached GPUs : 8
|
||||
Device link type matrix
|
||||
GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7 NIC0 NIC1 NIC2 NIC3 NIC4 NIC5 Node Affinity CPU Affinity
|
||||
GPU0 X MX MX MX NODE NODE NODE NODE PIX PIX NODE NODE SYS SYS 0 0-31,64-95
|
||||
GPU1 MX X MX MX NODE NODE NODE NODE PIX PIX NODE NODE SYS SYS 0 0-31,64-95
|
||||
GPU2 MX MX X MX NODE NODE NODE NODE PIX PIX NODE NODE SYS SYS 0 0-31,64-95
|
||||
GPU3 MX MX MX X NODE NODE NODE NODE PIX PIX NODE NODE SYS SYS 0 0-31,64-95
|
||||
GPU4 NODE NODE NODE NODE X MX MX MX NODE NODE PIX PIX SYS SYS 0 0-31,64-95
|
||||
GPU5 NODE NODE NODE NODE MX X MX MX NODE NODE PIX PIX SYS SYS 0 0-31,64-95
|
||||
GPU6 NODE NODE NODE NODE MX MX X MX NODE NODE PIX PIX SYS SYS 0 0-31,64-95
|
||||
GPU7 NODE NODE NODE NODE MX MX MX X NODE NODE PIX PIX SYS SYS 0 0-31,64-95
|
||||
NIC0 PIX PIX PIX PIX NODE NODE NODE NODE X PIX NODE NODE SYS SYS
|
||||
NIC1 PIX PIX PIX PIX NODE NODE NODE NODE PIX X NODE NODE SYS SYS
|
||||
NIC2 NODE NODE NODE NODE PIX PIX PIX PIX NODE NODE X PIX SYS SYS
|
||||
NIC3 NODE NODE NODE NODE PIX PIX PIX PIX NODE NODE PIX X SYS SYS
|
||||
NIC4 SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS X PIX
|
||||
NIC5 SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS PIX X
|
||||
|
||||
Legend:
|
||||
X = Self
|
||||
SYS = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)
|
||||
NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node
|
||||
PHB = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU)
|
||||
PXB = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge)
|
||||
PIX = Connection traversing at most a single PCIe bridge
|
||||
MX = Connection traversing MetaXLink
|
||||
ETH = Connection traversing Eth
|
||||
NA = Connection type is unknown
|
||||
|
||||
NIC Legend:
|
||||
|
||||
NIC0: mlx5_0
|
||||
NIC1: mlx5_1
|
||||
NIC2: mlx5_2
|
||||
NIC3: mlx5_3
|
||||
NIC4: mlx5_4
|
||||
NIC5: mlx5_5
|
||||
# The output shows:
|
||||
# 1. GPU0 to GPU3 communicate with NIC0 and NIC1, while GPU4 to GPU7 communicate with NIC2 and NIC3
|
||||
# 2. NIC0 uses ib device:mlx5_0, NIC1 uses ib device:mlx5_1, NIC2 uses ib device:mlx5_2, NIC3 uses ib device:mlx5_3
|
||||
|
||||
```
|
||||
Therefore:
|
||||
MCCL_SOCKET_IFNAME=ens20f0np0
|
||||
GLOO_SOCKET_IFNAME=ens20f0np0
|
||||
MCCL_IB_HCA=mlx5_0,mlx5_1,mlx5_2,mlx5_3
|
||||
|
||||
```bash
|
||||
# node 1
|
||||
export MCCL_SOCKET_IFNAME=ens20f0np0
|
||||
export GLOO_SOCKET_IFNAME=ens20f0np0
|
||||
export MCCL_IB_HCA=mlx5_0,mlx5_1,mlx5_2,mlx5_3
|
||||
cd /workspace/ms-swift/
|
||||
bash examples/train/multi-node/torchrun/train_node1.sh
|
||||
```
|
||||
|
||||
```bash
|
||||
# node 2
|
||||
# Update the value of the master_addr parameter in the script.
|
||||
export MCCL_SOCKET_IFNAME=ens20f0np0
|
||||
export GLOO_SOCKET_IFNAME=ens20f0np0
|
||||
export MCCL_IB_HCA=mlx5_0,mlx5_1,mlx5_2,mlx5_3
|
||||
cd /workspace/ms-swift/
|
||||
bash examples/train/multi-node/torchrun/train_node2.sh
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
# More Best Practices
|
||||
|
||||
- [Qwen2.5 self-cognition SFT](https://github.com/modelscope/ms-swift/tree/main/examples/notebook/qwen2_5-self-cognition)
|
||||
- [Qwen2-VL Latex-OCR SFT](https://github.com/modelscope/ms-swift/tree/main/examples/notebook/qwen2vl-ocr)
|
||||
- [Qwen2.5-VL Grounding Task SFT](https://github.com/modelscope/ms-swift/tree/main/examples/notebook/qwen2_5-vl-grounding)
|
||||
@@ -0,0 +1,871 @@
|
||||
# NPU Support
|
||||
|
||||
ms-swift supports Ascend NPUs. You can fine-tune models and run inference on Ascend NPUs.
|
||||
|
||||
This document describes how to prepare the environment, train models, save and merge checkpoints, run inference, deploy services, and troubleshoot common issues on Ascend NPUs.
|
||||
|
||||
If this is your first time using ms-swift on NPUs, we recommend reading this document in the following order:
|
||||
|
||||
1. Check "Support Scope at a Glance" first to confirm whether your model, algorithm, and backend have been verified.
|
||||
2. Use "Choose Your Usage Path" to decide whether you only need the base environment or also need MindSpeed/Megatron-SWIFT.
|
||||
3. Choose "Local Environment Installation" or "Image/Container Environment Installation" according to your own environment management preference, then run "NPU Availability Check".
|
||||
4. Use "Quick Start" to complete one ModelScope model LoRA training, merge, inference, and deployment flow.
|
||||
5. For larger-scale training, continue reading the DDP, DeepSpeed, and MindSpeed/Megatron-SWIFT sections.
|
||||
|
||||
## Hardware and Supported Operating Systems
|
||||
|
||||
**Table 1** Product hardware support list
|
||||
|
||||
| Product | Supported |
|
||||
| ------- | :-------: |
|
||||
| <term>Ascend 950 series products</term> | √ |
|
||||
| <term>Atlas A3 training series products</term> | √ |
|
||||
| <term>Atlas A3 inference series products</term> | x |
|
||||
| <term>Atlas A2 training series products</term> | √ |
|
||||
| <term>Atlas A2 inference series products</term> | x |
|
||||
| <term>Atlas 200I/500 A2 inference products</term> | x |
|
||||
| <term>Atlas inference series products</term> | x |
|
||||
| <term>Atlas training series products</term> | x |
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> In this section, "√" indicates supported and "x" indicates not supported.
|
||||
|
||||
- For operating systems supported by each hardware product in physical-machine deployment scenarios, see the [Compatibility Query Assistant](https://www.hiascend.com/hardware/compatibility).
|
||||
- For operating systems supported by each hardware product in VM and container deployment scenarios, see "Operating System Compatibility" in CANN Software Installation for the [commercial edition](https://www.hiascend.com/document/detail/zh/canncommercial/900/softwareinst/instg/instg_0101.html?OS=openEuler&InstallType=netyum) or [community edition](https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/900/softwareinst/instg/instg_0101.html?OS=openEuler&InstallType=netyum).
|
||||
|
||||
## Support Scope at a Glance
|
||||
|
||||
Recommended base environment versions:
|
||||
|
||||
| software | version |
|
||||
| --------- | --------------- |
|
||||
| Python | >= 3.10, < 3.12 |
|
||||
| CANN | >= 8.5.1 |
|
||||
| torch | >= 2.7.1 |
|
||||
| torch_npu | >= 2.7.1.post4 |
|
||||
Note: The officially recommended version compatibility matrix for the vLLM Ascend series has been updated to CANN 9.0.0, torch 2.9.0, torch_npu 2.9.0, vllm-ascend 0.18.0 for A3, and vllm-ascend 0.19.1 for A5. For details, see the [vLLM Ascend installation guide](https://docs.vllm.ai/projects/ascend/en/v0.18.0/installation.html).
|
||||
|
||||
For base environment setup, see the [Ascend PyTorch installation guide](https://gitcode.com/Ascend/pytorch). The examples in this document were verified on 8 * Ascend 910B3 64G.
|
||||
|
||||
| Primary feature | Feature | Status |
|
||||
| --------------- | --------------------- | ------------- |
|
||||
| Training | CPT | Supported |
|
||||
| | SFT | Supported |
|
||||
| | DPO | Supported |
|
||||
| | RM | Supported |
|
||||
| Distributed | DDP | Supported |
|
||||
| | FSDP | Supported |
|
||||
| | FSDP2 | Supported |
|
||||
| | DeepSpeed | Supported |
|
||||
| | MindSpeed(Megatron) | Supported |
|
||||
| PEFT | FULL | Supported |
|
||||
| | LoRA | Supported |
|
||||
| | QLoRA | Not supported |
|
||||
| RLHF | GRPO | Supported |
|
||||
| | PPO | Supported |
|
||||
| Optimization | FA and other fused ops | Supported |
|
||||
| | Liger-Kernel | Not supported |
|
||||
| Deployment | PT | Supported |
|
||||
| | vLLM | Supported |
|
||||
| | SGLang | Not supported |
|
||||
|
||||
### Verified SFT Combinations
|
||||
|
||||
| algorithm | model families | strategy | hardware |
|
||||
| --------- | --------------------------- | --------------------- | ----------------- |
|
||||
| SFT | Qwen2.5-0.5B-Instruct | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen2.5-1.5B-Instruct | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen2.5-7B-Instruct | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen2.5-VL-3B-Instruct | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen2.5-VL-7B-Instruct | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen2.5-Omni-3B | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen3-8B | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen3-30B-A3B | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen3-32B | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen3-VL-30B-A3B-Instruct | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen3-Omni-30B-A3B-Instruct | FSDP1/FSDP2/deepspeed/Megatron | Atlas 900 A2 PODc/A3 SuperPoD |
|
||||
| SFT | InternVL3-8B | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Ovis2.5-2B | FSDP1/FSDP2/deepspeed | Atlas 900 A2 PODc |
|
||||
| SFT | Qwen3.5-27B | FSDP1/FSDP2/deepspeed/Megatron | Atlas 900 A2 PODc/A3 SuperPoD |
|
||||
| SFT | Qwen3.5-35B-A3B | FSDP1/FSDP2/deepspeed/Megatron | Atlas 900 A2 PODc/A3 SuperPoD |
|
||||
|
||||
### Verified RL Combinations
|
||||
|
||||
| algorithm | model families | strategy | rollout engine | hardware |
|
||||
| --------- | ------------------- | --------- | -------------- | ----------------- |
|
||||
| **GRPO** | Qwen2.5-7B-Instruct | deepspeed | vllm-ascend | Atlas 900 A2 PODc |
|
||||
| **GRPO** | Qwen3-8B | deepspeed | vllm-ascend | Atlas 900 A2 PODc |
|
||||
| **DPO** | Qwen2.5-7B-Instruct | deepspeed | vllm-ascend | Atlas 900 A2 PODc |
|
||||
| **DPO** | Qwen3-8B | deepspeed | vllm-ascend | Atlas 900 A2 PODc |
|
||||
| **PPO** | Qwen2.5-7B-Instruct | deepspeed | vllm-ascend | Atlas 900 A2 PODc |
|
||||
| **PPO** | Qwen3-8B | deepspeed | vllm-ascend | Atlas 900 A2 PODc |
|
||||
|
||||
### Not Yet Supported or Not Fully Verified
|
||||
|
||||
| item |
|
||||
| ----------------------------------------- |
|
||||
| Liger-kernel |
|
||||
| Quantization/QLoRA related features |
|
||||
| Using SGLang as the inference engine |
|
||||
| Enabling ETP for LoRA training in Megatron |
|
||||
|
||||
### PEFT Transformers 5 MoE Fused Expert LoRA Limitation
|
||||
|
||||
When training Transformers 5 MoE models such as Qwen3.5-MoE and Qwen3-Omni-MoE with LoRA, some expert weights may be stored as fused `nn.Parameter` tensors instead of ordinary `nn.Linear` modules. Injecting LoRA into these parameters relies on PEFT's `target_parameters` path.
|
||||
|
||||
This path is not fully stable yet with combinations such as `lora_dropout`, ZeRO-3/FSDP, and multiple adapters. Typical trigger conditions include:
|
||||
|
||||
- using a MoE model;
|
||||
- using LoRA and trying to cover fused expert parameters;
|
||||
- triggering PEFT's Transformers 5 MoE target conversion path through the model config or the command-line `--model_type`;
|
||||
- using the default `lora_dropout != 0`, or using parameter-sharding backends such as ZeRO-3/FSDP.
|
||||
|
||||
For regular Qwen3.5 GRPO/SFT LoRA training, avoid explicitly passing `--model_type` when it is not needed. If the model config itself already triggers this PEFT path, prefer full-parameter training or disable the affected LoRA combination. If fused expert LoRA is required, wait for the PEFT upstream support to stabilize, or use it only after validating the target model and training backend with `lora_dropout=0`.
|
||||
|
||||
## Choose Your Usage Path
|
||||
|
||||
| Scenario | Recommended path | Need MindSpeed |
|
||||
| -------------------------------------- | ----------------------------------------------------- | -------------- |
|
||||
| Ordinary SFT/LoRA/inference | Local environment installation or image/container installation | No |
|
||||
| Megatron-SWIFT large-model training | Install the base environment, then install MindSpeed/Megatron/mcore-bridge | Yes |
|
||||
| GRPO/PPO/DPO and other RLHF workflows | Base training environment + vLLM-Ascend rollout/deploy | Usually no |
|
||||
| Only verifying whether NPUs are usable | Run the NPU availability check script | No |
|
||||
|
||||
## Environment Preparation
|
||||
|
||||
### Image/Container Environment Installation
|
||||
|
||||
The official NPU image is available at [quay.io/ascend/ms-swift](https://quay.io/repository/ascend/ms-swift?tab=tags). We recommend choosing an image tag that matches your device generation, Python version, CANN version, and OS version first. If you need to pin a branch or customize dependencies, build the image from the Dockerfile provided by the project. The container approach makes dependency versions easier to freeze and helps reproduce the same environment across multiple Ascend machines.
|
||||
|
||||
The following example uses the A2, Python 3.11, CANN 9.0.0, Ubuntu 22.04 tag. In actual use, choose the latest tag from the Quay tag page that matches your machine and software stack.
|
||||
|
||||
```shell
|
||||
docker pull quay.io/ascend/ms-swift:v4.3.0-A2-py311-CANN9.0.0-ubuntu22.04
|
||||
export IMAGE_NAME=quay.io/ascend/ms-swift:v4.3.0-A2-py311-CANN9.0.0-ubuntu22.04
|
||||
export WORKSPACE=/path/to/workspace
|
||||
```
|
||||
|
||||
If you need to build the image yourself, clone the modelscope repository first, then use [Dockerfile.ascend](https://github.com/modelscope/modelscope/blob/master/docker/Dockerfile.ascend) and [build_image.py](https://github.com/modelscope/modelscope/blob/master/docker/build_image.py):
|
||||
|
||||
```shell
|
||||
git clone https://github.com/modelscope/modelscope.git
|
||||
cd modelscope
|
||||
DOCKER_REGISTRY=ms-swift python docker/build_image.py \
|
||||
--image_type ascend \
|
||||
--python_version 3.11.11 \
|
||||
--soc_version ascend910b1 \
|
||||
--arch arm
|
||||
```
|
||||
|
||||
The current `build_image.py` generates Ascend image names in the format `{DOCKER_REGISTRY}:{swift_branch}-{atlas_hardware}-{python_tag}-{arch}`. The command above uses the ARM-based Atlas 900 A2 PODc as an example and usually generates `ms-swift:main-A2-py311-arm`. If you use a self-built image, replace `IMAGE_NAME` above with the actual image name from your build log.
|
||||
|
||||
```shell
|
||||
export IMAGE_NAME=ms-swift:main-A2-py311-arm
|
||||
```
|
||||
|
||||
Before starting the container, check which NPU devices are exposed on the host:
|
||||
|
||||
```shell
|
||||
ls /dev/davinci*
|
||||
```
|
||||
|
||||
When starting the container, mount the NPU devices, driver, firmware, `npu-smi`, and required log directories. The following example uses the common 8-card device range `davinci0` to `davinci7`. Some machines may also expose devices up to `davinci15`; in that case, add the corresponding devices to `docker run` according to the output of `ls /dev/davinci*`.
|
||||
|
||||
```shell
|
||||
docker run -it \
|
||||
--name swift-ascend \
|
||||
--network=host --ipc=host --shm-size=128g \
|
||||
--device=/dev/davinci0 --device=/dev/davinci1 \
|
||||
--device=/dev/davinci2 --device=/dev/davinci3 \
|
||||
--device=/dev/davinci4 --device=/dev/davinci5 \
|
||||
--device=/dev/davinci6 --device=/dev/davinci7 \
|
||||
--device=/dev/davinci_manager --device=/dev/devmm_svm --device=/dev/hisi_hdc \
|
||||
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver:ro \
|
||||
-v /usr/local/Ascend/firmware:/usr/local/Ascend/firmware:ro \
|
||||
-v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi:ro \
|
||||
-v /etc/ascend_install.info:/etc/ascend_install.info:ro \
|
||||
-v /var/log/npu:/var/log/npu \
|
||||
-v ${WORKSPACE}:/workspace \
|
||||
${IMAGE_NAME} \
|
||||
/bin/bash
|
||||
```
|
||||
|
||||
After entering the container, run `source /usr/local/Ascend/ascend-toolkit/set_env.sh` first, then run the NPU availability check below to confirm that the container can access the Ascend devices. If the container cannot detect NPUs, check `/dev/davinci*`, `/dev/davinci_manager`, the driver directory, and `npu-smi` mounts first.
|
||||
|
||||
### Local Environment Installation
|
||||
|
||||
```shell
|
||||
# Create a new conda virtual environment (optional)
|
||||
conda create -n swift-npu python=3.11 -y
|
||||
conda activate swift-npu
|
||||
|
||||
# Source the CANN environment before the following steps
|
||||
source /usr/local/Ascend/ascend-toolkit/set_env.sh
|
||||
|
||||
# Set the global pip mirror (optional, speeds up downloads)
|
||||
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
|
||||
pip install ms-swift -U
|
||||
|
||||
# Install from source
|
||||
git clone https://github.com/modelscope/ms-swift.git
|
||||
cd ms-swift
|
||||
pip install -e .
|
||||
|
||||
# Install torch_npu
|
||||
pip install torch_npu==2.9.0 decorator
|
||||
# If you want to use deepspeed (to reduce memory usage, with some speed overhead)
|
||||
pip install deepspeed
|
||||
|
||||
# If you need evaluation features, install the following package
|
||||
pip install evalscope[opencompass]
|
||||
|
||||
# If you need vllm-ascend for inference, install the following packages (for more versions, see the [vLLM-Ascend official website](https://docs.vllm.ai/projects/ascend/en/latest/installation.html))
|
||||
pip install vllm==0.18.0
|
||||
pip install vllm-ascend==0.18.0
|
||||
```
|
||||
|
||||
### NPU Availability Check
|
||||
|
||||
Check whether the environment is installed correctly and whether NPUs can be loaded:
|
||||
|
||||
```python
|
||||
from transformers.utils import is_torch_npu_available
|
||||
import torch
|
||||
|
||||
print(is_torch_npu_available()) # True
|
||||
print(torch.npu.device_count()) # 8
|
||||
print(torch.randn(10, device='npu:0'))
|
||||
```
|
||||
|
||||
### Optional MindSpeed/Megatron-SWIFT Installation
|
||||
|
||||
If you need MindSpeed(Megatron-LM), install the required dependencies as follows.
|
||||
|
||||
```shell
|
||||
# 1. Clone Megatron-LM and switch to v0.16.0
|
||||
git clone https://github.com/NVIDIA/Megatron-LM.git
|
||||
cd Megatron-LM
|
||||
git checkout core_v0.16.0
|
||||
cd ..
|
||||
|
||||
# 2. Clone and install MindSpeed
|
||||
git clone https://gitcode.com/Ascend/MindSpeed.git
|
||||
cd MindSpeed
|
||||
git checkout core_r0.16.0
|
||||
pip install -e .
|
||||
cd ..
|
||||
|
||||
# 3. Clone and install mcore-bridge
|
||||
git clone https://github.com/modelscope/mcore-bridge.git
|
||||
cd mcore-bridge
|
||||
pip install -e .
|
||||
cd ..
|
||||
|
||||
# 4. Download and install triton-ascend
|
||||
pip install triton-ascend==3.2.1 --extra-index-url=https://triton-ascend.osinfra.cn/pypi/simple
|
||||
|
||||
# 5. Set environment variables
|
||||
export PYTHONPATH=$PYTHONPATH:<your_local_megatron_lm_path>
|
||||
export MEGATRON_LM_PATH=<your_local_megatron_lm_path>
|
||||
|
||||
# 6. Disable Megatron GDN if you need to fall back to the transformers GatedDeltaNet implementation
|
||||
export USE_MCORE_GDN=0
|
||||
```
|
||||
|
||||
Run the following command to verify that MindSpeed(Megatron-LM) is configured correctly:
|
||||
|
||||
```shell
|
||||
python -c "import mindspeed.megatron_adaptor; from swift.megatron.init import init_megatron_env; init_megatron_env(); print('✓ Megatron-SWIFT configuration verified successfully in the NPU environment!')"
|
||||
```
|
||||
|
||||
### Qwen3.5 FLA Patch Notes
|
||||
|
||||
The current repository already includes a built-in Qwen3.5 linear attention patch for Ascend NPUs, so users do not need to manually modify the `transformers` or `fla` source code. This patch does not replace the entire `flash-linear-attention` package directly. Instead, it redirects the low-level GPU Triton operator path used by `Qwen3.5` through `chunk_gated_delta_rule` to the MindSpeed NPU implementation.
|
||||
|
||||
When the patch takes effect, ms-swift performs the following replacements:
|
||||
|
||||
1. Set `transformers.utils.is_flash_linear_attention_available` and `transformers.utils.import_utils.is_flash_linear_attention_available` to return `True`, so that `transformers.models.qwen3_5.modeling_qwen3_5` can complete initialization through the FLA fast path.
|
||||
2. Redirect `transformers.models.qwen3_5.modeling_qwen3_5.chunk_gated_delta_rule` and `transformers.models.qwen3_5_moe.modeling_qwen3_5_moe.chunk_gated_delta_rule` to the built-in ms-swift implementation `swift.model.chunk_gated_delta_rule.chunk_gated_delta_rule`.
|
||||
3. Inside `swift.model.chunk_gated_delta_rule`, continue calling the native Triton operators provided by MindSpeed, including:
|
||||
- `mindspeed.ops.triton.chunk_delta_h`
|
||||
- `mindspeed.ops.triton.chunk_o`
|
||||
- `mindspeed.ops.triton.chunk_scaled_dot_kkt`
|
||||
- `mindspeed.ops.triton.cumsum`
|
||||
- `mindspeed.ops.triton.solve_tril`
|
||||
- `mindspeed.ops.triton.wy_fast`
|
||||
4. Keep the native torch l2norm helper, reducing per-layer per-step launch overhead as well as compile/autotune overhead during cold start, which improves model performance on NPU.
|
||||
5. For `FusedRMSNormGated`, which depends on `torch.cuda.current_device()` during FLA initialization, NPU keeps the native Qwen3.5 torch path to avoid compatibility issues caused by CUDA-only initialization logic.
|
||||
|
||||
The call chain can be understood as:
|
||||
|
||||
```text
|
||||
Qwen3.5 modeling.chunk_gated_delta_rule
|
||||
-> swift.model.chunk_gated_delta_rule.chunk_gated_delta_rule
|
||||
-> MindSpeed Triton kernels
|
||||
```
|
||||
|
||||
Therefore:
|
||||
|
||||
- This patch mainly covers the **gated-delta-rule path of Qwen3.5 linear attention**.
|
||||
- It is not equivalent to “fully replacing the entire fla package with MindSpeed”.
|
||||
- To make this path effective, ensure that MindSpeed can be imported correctly in the current environment.
|
||||
- Verified versions for accuracy alignment: torch 2.9.0 + MindSpeed 0.16.0 + flash-linear-attention 0.4.2 + triton-ascend 3.2.1 + transformers 5.2.0
|
||||
|
||||
When running Qwen3.5 on NPU with either the transformers backend or the Megatron-SWIFT backend, note the following version and feature constraints:
|
||||
|
||||
1. The MindSpeed training combination currently pinned by the NPU documentation
|
||||
is `Megatron-LM v0.16.0 + MindSpeed core_r0.16.0`. With this combination,
|
||||
`megatron-core` already ships the native GDN kernel
|
||||
`core.ssm.gated_delta_net`, and `mcore-bridge` defaults to the
|
||||
Megatron-Core/MindSpeed GDN path with `USE_MCORE_GDN=1`. If you explicitly
|
||||
set `USE_MCORE_GDN=0`, it falls back to the transformers-native GDN
|
||||
implementation wrapped by `mcore-bridge`; combined with ms-swift's built-in
|
||||
Qwen3.5 FLA NPU patch, `chunk_gated_delta_rule` is then redirected to
|
||||
MindSpeed's Triton kernels.
|
||||
|
||||
2. At the moment, do not enable sequence-related features on the Qwen3.5 NPU
|
||||
path with either the transformers backend or the Megatron-SWIFT backend,
|
||||
regardless of whether `USE_MCORE_GDN=1` or `USE_MCORE_GDN=0` is used under
|
||||
Megatron-SWIFT. This includes sequence parallel (SP), context parallel (CP),
|
||||
and packing/padding-free. The related FLA Triton operators do not yet have
|
||||
complete native NPU support. Enabling these features may hit
|
||||
missing-operator paths, incomplete sample-boundary handling, or mismatched
|
||||
parallel partitioning.
|
||||
|
||||
3. For the transformers backend, keep `--sequence_parallel_size` at `1` and
|
||||
avoid `--packing true` / `--padding_free true`. For the Megatron-SWIFT
|
||||
backend, keep `--context_parallel_size`
|
||||
at `1`, and also avoid `--packing true` / `--padding_free true`. Re-enable
|
||||
these features only after the target MindSpeed/FLA stack explicitly fills
|
||||
this support and the combination has been validated in layers.
|
||||
|
||||
### Environment Viewing
|
||||
Check the P2P connections of the NPU, where we can see that each NPU is interconnected through 7 HCCS links with other NPUs.
|
||||
|
||||
```shell
|
||||
(valle) root@valle:~/src# npu-smi info -t topo
|
||||
NPU0 NPU1 NPU2 NPU3 NPU4 NPU5 NPU6 NPU7 CPU Affinity
|
||||
NPU0 X HCCS HCCS HCCS HCCS HCCS HCCS HCCS 144-167
|
||||
NPU1 HCCS X HCCS HCCS HCCS HCCS HCCS HCCS 144-167
|
||||
NPU2 HCCS HCCS X HCCS HCCS HCCS HCCS HCCS 96-119
|
||||
NPU3 HCCS HCCS HCCS X HCCS HCCS HCCS HCCS 96-119
|
||||
NPU4 HCCS HCCS HCCS HCCS X HCCS HCCS HCCS 0-23
|
||||
NPU5 HCCS HCCS HCCS HCCS HCCS X HCCS HCCS 0-23
|
||||
NPU6 HCCS HCCS HCCS HCCS HCCS HCCS X HCCS 48-71
|
||||
NPU7 HCCS HCCS HCCS HCCS HCCS HCCS HCCS X 48-71
|
||||
|
||||
Legend:
|
||||
|
||||
X = Self
|
||||
SYS = Path traversing PCIe and NUMA nodes. Nodes are connected through SMP, such as QPI, UPI.
|
||||
PHB = Path traversing PCIe and the PCIe host bridge of a CPU.
|
||||
PIX = Path traversing a single PCIe switch
|
||||
PXB = Path traversing multiple PCIe switches
|
||||
HCCS = Connection traversing HCCS.
|
||||
NA = Unknown relationship.
|
||||
```
|
||||
|
||||
Check NPU status. For details about the `npu-smi` command, see the [official documentation](https://support.huawei.com/enterprise/en/doc/EDOC1100079287/10dcd668).
|
||||
|
||||
```shell
|
||||
(valle) root@valle:~/src# npu-smi info
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
| npu-smi 24.1.rc1.b030 Version: 24.1.rc1.b030 |
|
||||
+---------------------------+---------------+----------------------------------------------------+
|
||||
| NPU Name | Health | Power(W) Temp(C) Hugepages-Usage(page)|
|
||||
| Chip | Bus-Id | AICore(%) Memory-Usage(MB) HBM-Usage(MB) |
|
||||
+===========================+===============+====================================================+
|
||||
| 0 910B3 | OK | 101.8 43 0 / 0 |
|
||||
| 0 | 0000:C1:00.0 | 0 0 / 0 3318 / 65536 |
|
||||
+===========================+===============+====================================================+
|
||||
| 1 910B3 | OK | 92.0 39 0 / 0 |
|
||||
| 0 | 0000:C2:00.0 | 0 0 / 0 3314 / 65536 |
|
||||
+===========================+===============+====================================================+
|
||||
| 2 910B3 | OK | 102.0 40 0 / 0 |
|
||||
| 0 | 0000:81:00.0 | 0 0 / 0 3314 / 65536 |
|
||||
+===========================+===============+====================================================+
|
||||
| 3 910B3 | OK | 99.8 40 0 / 0 |
|
||||
| 0 | 0000:82:00.0 | 0 0 / 0 3314 / 65536 |
|
||||
+===========================+===============+====================================================+
|
||||
| 4 910B3 | OK | 98.6 45 0 / 0 |
|
||||
| 0 | 0000:01:00.0 | 0 0 / 0 3314 / 65536 |
|
||||
+===========================+===============+====================================================+
|
||||
| 5 910B3 | OK | 99.7 44 0 / 0 |
|
||||
| 0 | 0000:02:00.0 | 0 0 / 0 3314 / 65536 |
|
||||
+===========================+===============+====================================================+
|
||||
| 6 910B3 | OK | 103.8 45 0 / 0 |
|
||||
| 0 | 0000:41:00.0 | 0 0 / 0 3314 / 65536 |
|
||||
+===========================+===============+====================================================+
|
||||
| 7 910B3 | OK | 98.2 44 0 / 0 |
|
||||
| 0 | 0000:42:00.0 | 0 0 / 0 3315 / 65536 |
|
||||
+===========================+===============+====================================================+
|
||||
```
|
||||
|
||||
## Quick Start: ModelScope Model + Dataset
|
||||
|
||||
If you want to quickly verify the environment with a ModelScope model and dataset, you can use this section to complete the full flow: train LoRA, find the latest checkpoint, Merge LoRA, run CLI inference, start a service, and validate it with curl. The example uses a small model and a small data sample so that it can run quickly. To use your own model or dataset, modify the ID variables at the beginning.
|
||||
|
||||
```shell
|
||||
export MODEL_ID=Qwen/Qwen3-0.6B
|
||||
export DATASET_ID=AI-ModelScope/alpaca-gpt4-data-zh
|
||||
export WORK_DIR=output/npu-modelscope-qwen3-0_6b-lora
|
||||
```
|
||||
|
||||
Train and save a LoRA checkpoint:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 \
|
||||
swift sft \
|
||||
--model $MODEL_ID \
|
||||
--dataset $DATASET_ID#1000 \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--torch_dtype bfloat16 \
|
||||
--tuner_type lora \
|
||||
--target_modules all-linear \
|
||||
--lora_rank 8 \
|
||||
--lora_alpha 32 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 1 \
|
||||
--per_device_eval_batch_size 1 \
|
||||
--gradient_accumulation_steps 8 \
|
||||
--learning_rate 1e-4 \
|
||||
--max_length 2048 \
|
||||
--save_steps 100 \
|
||||
--eval_steps 100 \
|
||||
--save_total_limit 2 \
|
||||
--logging_steps 10 \
|
||||
--output_dir $WORK_DIR
|
||||
```
|
||||
|
||||
After training finishes, checkpoints are saved under `$WORK_DIR/*/checkpoint-*`. Use the following commands to select the latest checkpoint and merge LoRA into a full model:
|
||||
|
||||
```shell
|
||||
export CKPT_DIR=$(ls -dt $WORK_DIR/*/checkpoint-* | head -n 1)
|
||||
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 \
|
||||
swift export \
|
||||
--adapters $CKPT_DIR \
|
||||
--merge_lora true
|
||||
|
||||
export MERGED_DIR=${CKPT_DIR}-merged
|
||||
```
|
||||
|
||||
You can verify inference either with the LoRA checkpoint directly or with the merged full model:
|
||||
|
||||
```shell
|
||||
# Load the LoRA checkpoint directly
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 \
|
||||
swift infer \
|
||||
--adapters $CKPT_DIR \
|
||||
--stream true \
|
||||
--temperature 0 \
|
||||
--max_new_tokens 512
|
||||
|
||||
# Load the merged full model
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 \
|
||||
swift infer \
|
||||
--model $MERGED_DIR \
|
||||
--stream true \
|
||||
--temperature 0 \
|
||||
--max_new_tokens 512
|
||||
```
|
||||
|
||||
To start an OpenAI-compatible deployment service, use the merged full model:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 \
|
||||
swift deploy \
|
||||
--model $MERGED_DIR \
|
||||
--host 0.0.0.0 \
|
||||
--port 8000 \
|
||||
--max_new_tokens 512 \
|
||||
--served_model_name npu-modelscope-qwen3-0_6b
|
||||
```
|
||||
|
||||
After the service starts, validate the API with curl:
|
||||
|
||||
```shell
|
||||
curl http://localhost:8000/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "npu-modelscope-qwen3-0_6b",
|
||||
"messages": [{"role": "user", "content": "Briefly introduce Ascend NPU in one sentence."}],
|
||||
"max_tokens": 128,
|
||||
"temperature": 0
|
||||
}'
|
||||
```
|
||||
|
||||
## Training
|
||||
|
||||
The following examples introduce LoRA fine-tuning. For full-parameter fine-tuning, set `--tuner_type full`. For **more training scripts**, see [examples/ascend/train](https://github.com/modelscope/ms-swift/tree/main/examples/ascend/train). For general pre-training, SFT, LoRA, full-parameter training, and custom dataset usage, see [Pre-training and Fine-tuning](../Instruction/Pre-training-and-Fine-tuning.md).
|
||||
|
||||
| Model size | NPU count | DeepSpeed type | Max memory usage |
|
||||
| ---------- | --------- | -------------- | ---------------- |
|
||||
| 7B | 1 | None | 1 * 28 GB |
|
||||
| 7B | 4 | None | 4 * 22 GB |
|
||||
| 7B | 4 | zero2 | 4 * 28 GB |
|
||||
| 7B | 4 | zero3 | 4 * 22 GB |
|
||||
| 7B | 8 | None | 8 * 22 GB |
|
||||
| 14B | 1 | None | 1 * 45 GB |
|
||||
| 14B | 8 | None | 8 * 51 GB |
|
||||
| 14B | 8 | zero2 | 8 * 49 GB |
|
||||
| 14B | 8 | zero3 | 8 * 31 GB |
|
||||
|
||||
### Single-Card Training
|
||||
|
||||
Start single-card fine-tuning with the following command:
|
||||
|
||||
```shell
|
||||
# Experiment environment: Ascend 910B3
|
||||
# Memory requirement: 28 GB
|
||||
# Runtime: 8 hours
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen2-7B-Instruct \
|
||||
--dataset AI-ModelScope/blossom-math-v2 \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--num_train_epochs 5 \
|
||||
--tuner_type lora \
|
||||
--output_dir output \
|
||||
--learning_rate 1e-4 \
|
||||
--gradient_accumulation_steps 16 \
|
||||
--save_steps 100 \
|
||||
--eval_steps 100
|
||||
|
||||
```
|
||||
|
||||
### Data Parallel Training
|
||||
|
||||
The following example uses 4 NPUs for DDP training.
|
||||
|
||||
```shell
|
||||
# Experiment environment: 4 * Ascend 910B3
|
||||
# Memory requirement: 4 * 22 GB
|
||||
# Runtime: 2 hours
|
||||
NPROC_PER_NODE=4 \
|
||||
ASCEND_RT_VISIBLE_DEVICES=0,1,2,3 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen2-7B-Instruct \
|
||||
--dataset AI-ModelScope/blossom-math-v2 \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--num_train_epochs 5 \
|
||||
--tuner_type lora \
|
||||
--output_dir output \
|
||||
...
|
||||
```
|
||||
|
||||
### DeepSpeed Training
|
||||
|
||||
ZeRO2:
|
||||
|
||||
```shell
|
||||
# Experiment environment: 4 * Ascend 910B3
|
||||
# Memory requirement: 4 * 28GB
|
||||
# Runtime: 3.5 hours
|
||||
NPROC_PER_NODE=4 \
|
||||
ASCEND_RT_VISIBLE_DEVICES=0,1,2,3 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen2-7B-Instruct \
|
||||
--dataset AI-ModelScope/blossom-math-v2 \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--num_train_epochs 5 \
|
||||
--tuner_type lora \
|
||||
--output_dir output \
|
||||
--deepspeed zero2 \
|
||||
...
|
||||
```
|
||||
|
||||
ZeRO3:
|
||||
|
||||
```shell
|
||||
# Experiment environment: 4 * Ascend 910B3
|
||||
# Memory requirement: 4 * 22 GB
|
||||
# Runtime: 8.5 hours
|
||||
NPROC_PER_NODE=4 \
|
||||
ASCEND_RT_VISIBLE_DEVICES=0,1,2,3 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen2-7B-Instruct \
|
||||
--dataset AI-ModelScope/blossom-math-v2 \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--num_train_epochs 5 \
|
||||
--tuner_type lora \
|
||||
--output_dir output \
|
||||
--deepspeed zero3 \
|
||||
...
|
||||
```
|
||||
|
||||
### Qwen3.5 Single-Node Multi-Card LoRA Example
|
||||
|
||||
The following is an NPU LoRA example for a newer model. It uses Qwen3.5-4B for demonstration. Four-card data parallelism is usually faster than single-card training. If you already have local model and dataset files, replace `--model` and `--dataset` with local paths.
|
||||
|
||||
```shell
|
||||
# Experiment environment: 4 * Ascend 910B3
|
||||
NPROC_PER_NODE=4 \
|
||||
ASCEND_RT_VISIBLE_DEVICES=0,1,2,3 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen3.5-4B \
|
||||
--dataset AI-ModelScope/alpaca-gpt4-data-zh#2000 \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--torch_dtype bfloat16 \
|
||||
--num_train_epochs 1 \
|
||||
--tuner_type lora \
|
||||
--target_modules all-linear \
|
||||
--lora_rank 8 \
|
||||
--lora_alpha 32 \
|
||||
--per_device_train_batch_size 1 \
|
||||
--per_device_eval_batch_size 1 \
|
||||
--gradient_accumulation_steps 8 \
|
||||
--learning_rate 1e-4 \
|
||||
--max_length 2048 \
|
||||
--group_by_length true \
|
||||
--dataset_num_proc 4 \
|
||||
--dataloader_num_workers 4 \
|
||||
--save_steps 100 \
|
||||
--eval_steps 100 \
|
||||
--save_total_limit 2 \
|
||||
--output_dir output/Qwen3.5-4B-NPU
|
||||
```
|
||||
|
||||
When tuning parameters, focus on memory, throughput, and stability:
|
||||
|
||||
- Reduce memory usage: first reduce `--max_length`, `--per_device_train_batch_size`, and `--lora_rank`; if OOM still occurs, enable `--deepspeed zero2/zero3`. ZeRO can significantly reduce memory pressure but introduces communication and scheduling overhead.
|
||||
- Improve throughput: increase `--per_device_train_batch_size` when memory allows, and use `--gradient_accumulation_steps` to keep the global batch size. Increase `--dataset_num_proc` if preprocessing is slow, and increase `--dataloader_num_workers` if data loading is the bottleneck.
|
||||
- Control save overhead: do not set `--save_steps` too small, because frequent checkpoint saving slows down training. `--save_total_limit 2` is usually enough to keep the best checkpoint and the last checkpoint.
|
||||
- Improve stability: on NPUs, prefer `bfloat16`. If you see abnormal loss or NaN, first lower the learning rate and batch size; if necessary, temporarily switch to `float32` for comparison.
|
||||
|
||||
For more parameter details, see [Command-line Parameters](../Instruction/Command-line-parameters.md).
|
||||
|
||||
### NPU Model Patch Switch
|
||||
|
||||
ms-swift enables model-level patches by default in NPU environments to adapt some Transformers models to Ascend NPU operators and compatibility requirements. You usually do not need to disable them. If you suspect abnormal loss or forward errors are related to the NPU model patch and want to compare against native Transformers behavior, set:
|
||||
|
||||
```shell
|
||||
swift sft ... --enable_npu_model_patch false
|
||||
```
|
||||
|
||||
## Model Saving, Merge LoRA, and Resume Training
|
||||
|
||||
Use `--output_dir` to set the output directory, `--save_steps` to control checkpoint save intervals, and `--save_total_limit` to control how many checkpoints to keep. After LoRA training, the checkpoint directory contains adapter weights, training arguments, and trainer state. A typical directory layout is:
|
||||
|
||||
```text
|
||||
output/Qwen3.5-4B-NPU/vx-xxx/
|
||||
├── checkpoint-100/
|
||||
├── checkpoint-200/
|
||||
└── ...
|
||||
```
|
||||
|
||||
If you only need inference or want to continue LoRA training, use the checkpoint directory directly. If you want an independent full-model directory for vLLM-Ascend deployment, offline distribution, or later quantization, run Merge LoRA:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 \
|
||||
swift export \
|
||||
--adapters output/Qwen3.5-4B-NPU/vx-xxx/checkpoint-xxx \
|
||||
--merge_lora true
|
||||
```
|
||||
|
||||
The merged model is saved under `checkpoint-xxx-merged` by default. You can then load it like any regular model with `--model checkpoint-xxx-merged`.
|
||||
|
||||
If training is interrupted and you need to resume from a checkpoint, keep the original training arguments unchanged and add `--resume_from_checkpoint`:
|
||||
|
||||
```shell
|
||||
NPROC_PER_NODE=4 \
|
||||
ASCEND_RT_VISIBLE_DEVICES=0,1,2,3 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen3.5-4B \
|
||||
--dataset AI-ModelScope/alpaca-gpt4-data-zh#2000 \
|
||||
--tuner_type lora \
|
||||
--output_dir output/Qwen3.5-4B-NPU \
|
||||
--resume_from_checkpoint output/Qwen3.5-4B-NPU/vx-xxx/checkpoint-xxx \
|
||||
...
|
||||
```
|
||||
|
||||
`--resume_from_checkpoint` restores model weights, optimizer state, random seeds, and training progress. If you only want to load model weights without restoring the optimizer state or data skipping state, also set `--resume_only_model true`. For details, see `resume_from_checkpoint`, `resume_only_model`, `save_steps`, and `save_total_limit` in [Command-line Parameters](../Instruction/Command-line-parameters.md).
|
||||
|
||||
## Inference
|
||||
|
||||
Original model:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift infer \
|
||||
--model Qwen/Qwen2-7B-Instruct \
|
||||
--stream true --max_new_tokens 2048
|
||||
```
|
||||
|
||||
After LoRA fine-tuning:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift infer \
|
||||
--adapters xxx/checkpoint-xxx --load_data_args true \
|
||||
--stream true --max_new_tokens 2048
|
||||
```
|
||||
|
||||
For full-parameter training or a merged LoRA model, point `--model` to the full-weight directory:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift infer \
|
||||
--model xxx/checkpoint-xxx-merged \
|
||||
--stream true --max_new_tokens 2048
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Deployment with Native Transformers
|
||||
|
||||
Original model:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift deploy --model Qwen/Qwen2-7B-Instruct --max_new_tokens 2048
|
||||
```
|
||||
|
||||
After LoRA fine-tuning:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift deploy --adapters xxx/checkpoint-xxx --max_new_tokens 2048
|
||||
|
||||
# Deploy the full weights after Merge LoRA
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift export --adapters xx/checkpoint-xxx --merge_lora true
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift deploy --model xxx/checkpoint-xxx-merged --max_new_tokens 2048
|
||||
```
|
||||
|
||||
### Deployment with vLLM-Ascend
|
||||
|
||||
Install from PyPI:
|
||||
|
||||
```shell
|
||||
# Refer to the official vLLM-Ascend compatibility matrix; the following versions are verified in this document.
|
||||
pip install vllm==0.14.0
|
||||
pip install vllm-ascend==0.14.0rc1
|
||||
```
|
||||
|
||||
Original model:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift deploy \
|
||||
--model Qwen/Qwen2.5-7B-Instruct \
|
||||
--infer_backend vllm \
|
||||
--max_new_tokens 2048
|
||||
```
|
||||
|
||||
After LoRA fine-tuning:
|
||||
|
||||
```shell
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift deploy \
|
||||
--adapters xxx/checkpoint-xxx \
|
||||
--infer_backend vllm \
|
||||
--max_new_tokens 2048
|
||||
|
||||
# Deploy the full weights after Merge LoRA
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift export \
|
||||
--adapters xx/checkpoint-xxx \
|
||||
--merge_lora true
|
||||
|
||||
ASCEND_RT_VISIBLE_DEVICES=0 swift deploy \
|
||||
--model xxx/checkpoint-xxx-merged \
|
||||
--infer_backend vllm \
|
||||
--max_new_tokens 2048
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
After training, inference, or deployment, you can evaluate the original model or fine-tuned checkpoint with SWIFT's built-in EvalScope integration. For complete arguments and examples, see [Evaluation](../Instruction/Evaluation.md).
|
||||
|
||||
## Release
|
||||
|
||||
If you need to publish NPU-trained checkpoints, merged models, or quantized models to ModelScope/HuggingFace, use the push capability in `swift export`. For complete arguments and examples, see [Export and Push](../Instruction/Export-and-push.md#push-models).
|
||||
|
||||
## FAQ
|
||||
|
||||
For general questions, see [Frequently Asked Questions](../Instruction/Frequently-asked-questions.md). This section records common NPU-specific issues and troubleshooting steps.
|
||||
|
||||
### Q1: How do I confirm that the current environment detects NPUs correctly?
|
||||
|
||||
First confirm that you have run `source /usr/local/Ascend/ascend-toolkit/set_env.sh`, then run the environment check script in this document. Normally, `is_torch_npu_available()` should return `True`, `torch.npu.device_count()` should show the number of available NPUs, and you should be able to create a tensor on `npu:0`. If this fails, first check whether CANN, `torch`, and `torch_npu` match the recommended versions in this document.
|
||||
|
||||
### Q2: How should I choose between FSDP, DeepSpeed, and Megatron-SWIFT?
|
||||
|
||||
For ordinary SFT, first refer to the verified `FSDP1/FSDP2/deepspeed` combinations in this document. For larger models or higher parallelism requirements, use Megatron-SWIFT and install MindSpeed, Megatron-LM, and mcore-bridge as described in the installation section. DeepSpeed can reduce memory pressure but may reduce speed, so compare it with FSDP when tuning performance.
|
||||
|
||||
### Q3: Do I need to manually disable the NPU model patch?
|
||||
|
||||
Usually no. ms-swift enables model-level patches by default in NPU environments to adapt some Transformers models to Ascend NPU operators and compatibility requirements. Only when troubleshooting abnormal loss or forward errors, and when you suspect the issue is related to the NPU patch, should you temporarily set `--enable_npu_model_patch false` and compare against native Transformers behavior.
|
||||
|
||||
### Q4: What should I know when using vLLM-Ascend for deployment or RL rollout?
|
||||
|
||||
Install the `vllm` and `vllm-ascend` versions recommended in this document, and prioritize model and algorithm combinations that have been verified in the compatibility tables. The `sglang` inference engine is not yet fully verified for NPU scenarios. For high-performance inference or RL rollout on NPUs, prefer `vllm-ascend`.
|
||||
|
||||
### Q5: What happens if I forget to run `source set_env.sh`?
|
||||
|
||||
Common symptoms include `is_torch_npu_available()` returning `False`, `torch.npu.device_count()` returning 0, or runtime errors about missing CANN/HCCL shared libraries. After entering a new shell or container, run:
|
||||
|
||||
```shell
|
||||
source /usr/local/Ascend/ascend-toolkit/set_env.sh
|
||||
```
|
||||
|
||||
If NNAL/ATB or other components are installed, source their corresponding `set_env.sh` files according to your environment.
|
||||
|
||||
### Q6: How do I diagnose a `torch` and `torch_npu` version mismatch?
|
||||
|
||||
Install according to the recommended versions in this document. When versions mismatch, common symptoms include `import torch_npu` failures, invisible NPU devices, operator registration failures, and C++ symbol/runtime errors. Check versions with:
|
||||
|
||||
```shell
|
||||
python -c "import torch, torch_npu; print(torch.__version__); print(torch_npu.__version__)"
|
||||
```
|
||||
|
||||
If versions do not match, uninstall and reinstall the full CANN/PyTorch/torch_npu stack consistently. Do not upgrade only one package.
|
||||
|
||||
### Q7: What happens if `ASCEND_RT_VISIBLE_DEVICES` and `NPROC_PER_NODE` do not match?
|
||||
|
||||
For distributed training, they should match. For example, `ASCEND_RT_VISIBLE_DEVICES=0,1,2,3` usually corresponds to `NPROC_PER_NODE=4`. If the process count is larger than the number of visible devices, ranks may fail to bind devices, multiple processes may compete for the same device, initialization may hang, or HCCL may report errors. If the process count is smaller, only part of the visible NPUs will be used.
|
||||
|
||||
### Q8: What should I check first when multi-card training hangs?
|
||||
|
||||
First confirm that every rank has started and that `ASCEND_RT_VISIBLE_DEVICES` matches `NPROC_PER_NODE`. Then identify whether logs stop during data preprocessing, model construction, weight loading, or HCCL initialization. For NPU/HCCL low-level logs, check:
|
||||
|
||||
```shell
|
||||
ls ~/ascend/log/debug/plog
|
||||
```
|
||||
|
||||
If the Python process is still running but produces no output for a long time, use `pystack` to inspect the stack of each rank and determine whether it is stuck in data loading, communication, or model forward/backward.
|
||||
|
||||
### Q9: How do I initially troubleshoot HCCL connection or timeout issues?
|
||||
|
||||
Use `npu-smi info` and `npu-smi info -t topo` to check device health and topology, then check whether other jobs are occupying the same NPU group. For single-node training, first verify card IDs, process count, and visible devices. For multi-node training, also verify networking, rank configuration, communication ports, and environment variables on all nodes. If old training processes remain on the same machine, clean up the corresponding training processes and retry.
|
||||
|
||||
### Q10: Why is `npu-smi` unavailable inside the container?
|
||||
|
||||
Usually some device or driver files were not mounted. Check whether `docker run` includes `/dev/davinci*`, `/dev/davinci_manager`, `/dev/devmm_svm`, `/dev/hisi_hdc`, `/usr/local/Ascend/driver`, `/usr/local/Ascend/firmware`, `/usr/local/sbin/npu-smi`, and `/etc/ascend_install.info`. If `npu-smi info` fails on the host itself, fix the host driver environment first.
|
||||
|
||||
### Q11: How should I choose between native transformers deployment and vLLM-Ascend deployment?
|
||||
|
||||
Native transformers deployment has better compatibility and is suitable for validating whether the model, adapter, template, and output are correct. vLLM-Ascend is better for high-throughput services, RL rollout, or OpenAI-compatible serving. If you encounter vLLM-Ascend version or operator issues, first confirm that the model itself works with the transformers backend, then switch to vLLM-Ascend to troubleshoot the performance backend.
|
||||
|
||||
### Q12: What should I do if vLLM-Ascend reports device type mismatch or undefined symbol?
|
||||
|
||||
This is usually not caused by training script arguments. It often means the `vllm-ascend` wheel does not match the current hardware, PyTorch version, or C++ ABI. First check package build information and current versions:
|
||||
|
||||
```shell
|
||||
python -c "import torch, vllm_ascend; print(torch.__version__); print(vllm_ascend.__file__)"
|
||||
```
|
||||
|
||||
If the error message contains `Current device type ... does not match the installed version's device type ...`, `undefined symbol`, or similar text, reinstall `torch`, `torch_npu`, `vllm`, and `vllm-ascend` according to the device type (A2/A3/other) and the official compatibility matrix. Do not replace only one package.
|
||||
|
||||
### Q13: Can FP8 or quantized models be trained directly on NPUs?
|
||||
|
||||
Do not assume they can. Before downloading or loading a large model, check whether `config.json` contains `quantization_config`, and check the actual dtype in safetensors. Quantization/QLoRA is still listed as not supported or not fully verified in the NPU support scope. If model weights are FP8 block quantized and your NPU software stack does not support that FP8 path, use BF16 weights first, or convert the model offline to BF16 before training/loading.
|
||||
|
||||
### Q14: How do I troubleshoot Megatron-SWIFT importing the wrong Megatron/MindSpeed?
|
||||
|
||||
Before running Megatron-SWIFT, `PYTHONPATH` and `MEGATRON_LM_PATH` must point to the same Megatron-LM source tree. Otherwise Python may start successfully while importing a different Megatron/MindSpeed combination, which can make later errors look like model or argument issues.
|
||||
|
||||
```shell
|
||||
export PYTHONPATH=$PYTHONPATH:<your_local_megatron_lm_path>
|
||||
export MEGATRON_LM_PATH=<your_local_megatron_lm_path>
|
||||
python -c "import megatron, os; print(megatron.__file__); print(os.environ.get('MEGATRON_LM_PATH'))"
|
||||
```
|
||||
|
||||
If they do not match, fix the environment variables before continuing with model construction, weight loading, or parallel configuration troubleshooting.
|
||||
|
||||
## NPU WeChat Group
|
||||
|
||||
<img src="https://raw.githubusercontent.com/modelscope/ms-swift/main/docs/resources/wechat/npu.png" width="250">
|
||||
@@ -0,0 +1,400 @@
|
||||
# Qwen3 Best Practices
|
||||
Discussion: [issue 4030](https://github.com/modelscope/ms-swift/issues/4030)
|
||||
|
||||
Qwen Documentation: [https://qwen.readthedocs.io/en/latest/training/ms_swift.html](https://qwen.readthedocs.io/en/latest/training/ms_swift.html)
|
||||
|
||||
## Inference
|
||||
|
||||
Thinking mode:
|
||||
|
||||
```shell
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
swift infer \
|
||||
--model Qwen/Qwen3-8B \
|
||||
--infer_backend vllm \
|
||||
--stream true \
|
||||
--max_new_tokens 2048 \
|
||||
--vllm_max_model_len 8192
|
||||
```
|
||||
|
||||
```text
|
||||
<<< who are you?
|
||||
<think>
|
||||
Okay, the user is asking "who are you?" Let me start by introducing myself as Qwen, the large language model developed by Alibaba Cloud. I should mention my capabilities, like answering questions, creating content, and engaging in conversations. But I need to keep it concise. Also, the user might want to know how I can assist them. Maybe I should ask how I can help them today. Let me check if there's anything else important to include. Oh, I should make sure the tone is friendly and approachable. Alright, that should cover it.
|
||||
</think>
|
||||
|
||||
Hello! I am Qwen, a large language model developed by Alibaba Cloud. I can assist with a wide range of tasks, such as answering questions, creating content, writing stories, coding, and more. How can I help you today? 😊
|
||||
<<< clear
|
||||
<<< who are you? /no_think
|
||||
<think>
|
||||
|
||||
</think>
|
||||
|
||||
I am Qwen, a large language model developed by Alibaba Cloud. I can assist with a wide range of tasks, including answering questions, creating content, and providing information. How can I help you today?
|
||||
```
|
||||
|
||||
Non-thinking mode:
|
||||
|
||||
- `--response_prefix` indicates that the model's output will continue after the prefix. It is equivalent to setting enable_thinking to False.
|
||||
|
||||
```shell
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
swift infer \
|
||||
--model Qwen/Qwen3-8B \
|
||||
--infer_backend vllm \
|
||||
--stream true \
|
||||
--max_new_tokens 2048 \
|
||||
--vllm_max_model_len 8192 \
|
||||
--response_prefix '<think>\n\n</think>\n\n'
|
||||
```
|
||||
|
||||
```text
|
||||
<<< who are you?
|
||||
<think>
|
||||
|
||||
</think>
|
||||
|
||||
I am Qwen, a large-scale language model developed by Alibaba Cloud. I am designed to assist with a wide range of tasks, including answering questions, creating content, and providing information. How can I assist you today?
|
||||
```
|
||||
|
||||
## Training
|
||||
|
||||
Before starting training, please ensure that your environment is properly configured.
|
||||
|
||||
```bash
|
||||
pip install ms-swift -U
|
||||
pip install transformers
|
||||
|
||||
pip install deepspeed # for multi-GPU training
|
||||
pip install liger-kernel # to save GPU memory resources
|
||||
pip install flash-attn --no-build-isolation # required for packing
|
||||
```
|
||||
|
||||
## Supervised Fine-Tuning (SFT)
|
||||
|
||||
### Data Preparation
|
||||
|
||||
When using ms-swift for SFT, the custom dataset format is as follows (the `system` field is optional). You can organize it in JSON, JSONL, or CSV format. Specify `--dataset <dataset_path>` in the training script. For a complete guide on dataset formats, refer to the [Custom Dataset Documentation](../Customization/Custom-dataset.md).
|
||||
|
||||
```text
|
||||
# General format
|
||||
{"messages": [
|
||||
{"role": "system", "content": "<system-prompt>"},
|
||||
{"role": "user", "content": "<query1>"},
|
||||
{"role": "assistant", "content": "<response1>"}
|
||||
]}
|
||||
# Format with thinking process
|
||||
{"messages": [
|
||||
{"role": "user", "content": "Where is the capital of Zhejiang?"},
|
||||
{"role": "assistant", "content": "Thought: ...\n\nAnswer:\nThe capital of Zhejiang is Hangzhou."}
|
||||
]}
|
||||
```
|
||||
|
||||
If you want to train using data without the thinking chain while preserving the model's reasoning ability, you can use one of the following methods to minimize the impact of fine-tuning:
|
||||
|
||||
**Option 1**: [Recommended] During training, specify `--loss_scale ignore_empty_think`, which will ignore the loss calculation for `'<think>\n\n</think>\n\n'`, thus avoiding the loss of reasoning capability. The training script can be found [here](https://github.com/modelscope/ms-swift/blob/main/examples/train/think_model/qwen3_demo1.sh). This method also works for models like DeepSeek-R1. The custom dataset format is as follows:
|
||||
|
||||
```json
|
||||
{"messages": [
|
||||
{"role": "user", "content": "Where is the capital of Zhejiang?"},
|
||||
{"role": "assistant", "content": "<think>\n\n</think>\n\nThe capital of Zhejiang is Hangzhou."}
|
||||
]}
|
||||
```
|
||||
|
||||
**Option 2**: Add `/no_think` to the query in the dataset to avoid losing the reasoning capability. The training script can be found [here](https://github.com/modelscope/ms-swift/blob/main/examples/train/think_model/qwen3_demo2.sh). The custom dataset format is as follows:
|
||||
|
||||
```json
|
||||
{"messages": [
|
||||
{"role": "user", "content": "Where is the capital of Zhejiang? /no_think"},
|
||||
{"role": "assistant", "content": "<think>\n\n</think>\n\nThe capital of Zhejiang is Hangzhou."}
|
||||
]}
|
||||
```
|
||||
|
||||
You can use the following command to obtain a distilled reasoning dataset. During training, you can mix it with datasets that do not contain chain-of-thought (CoT) data to further mitigate the loss of reasoning ability:
|
||||
|
||||
- The choice of `--val_dataset` is arbitrary. The reasoning results saved to `result_path` can be specified directly in training via `--dataset distill_dataset.jsonl`.
|
||||
- This approach is also applicable to other reasoning models, such as deepseek-r1.
|
||||
|
||||
```shell
|
||||
# 4 * 80GiB
|
||||
NPROC_PER_NODE=4 \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
||||
swift infer \
|
||||
--model Qwen/Qwen3-32B \
|
||||
--infer_backend vllm \
|
||||
--val_dataset 'AI-ModelScope/alpaca-gpt4-data-en#5000' 'AI-ModelScope/alpaca-gpt4-data-zh#5000' \
|
||||
--vllm_gpu_memory_utilization 0.9 \
|
||||
--vllm_tensor_parallel_size 2 \
|
||||
--vllm_max_model_len 8192 \
|
||||
--max_new_tokens 4096 \
|
||||
--write_batch_size 1000 \
|
||||
--result_path distill_dataset.jsonl
|
||||
```
|
||||
|
||||
### 30-Minute Self-Awareness Fine-Tuning
|
||||
|
||||
This section demonstrates how to perform self-awareness fine-tuning on Qwen3-8B within 30 minutes. A GPU with at least 22GB of VRAM is required and can run on the free computing resources provided by ModelScope, such as the A10 instance.
|
||||
|
||||
After training, the model will no longer identify itself as a "Qwen" trained by "Tongyi Lab," but rather as a "swift-robot" trained by "swift."
|
||||
|
||||
If you need to train in an offline environment, you can manually download the model and dataset, and specify `--model <model-path>` and `--dataset <dataset-dir>`. The dataset is available on the [ModelScope Hub](https://modelscope.cn/datasets/swift/self-cognition). You can view the preprocessing function for the `swift/self-cognition` dataset [here](https://github.com/modelscope/ms-swift/blob/36fdf381e5e88cb8a71c9d69c1d8936a989318cc/swift/llm/dataset/dataset/llm.py#L882).
|
||||
|
||||
For explanations of the parameters used in the training script, please refer to the [Command Line Arguments Documentation](../Instruction/Command-line-parameters.md).
|
||||
|
||||
```bash
|
||||
# GPU Memory Usage: 22GB
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen3-8B \
|
||||
--tuner_type lora \
|
||||
--dataset 'swift/Qwen3-SFT-Mixin#2000' \
|
||||
'swift/self-cognition:qwen3#600' \
|
||||
--load_from_cache_file true \
|
||||
--torch_dtype bfloat16 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 1 \
|
||||
--per_device_eval_batch_size 1 \
|
||||
--learning_rate 1e-4 \
|
||||
--lora_rank 8 \
|
||||
--lora_alpha 32 \
|
||||
--target_modules all-linear \
|
||||
--gradient_accumulation_steps 16 \
|
||||
--eval_steps 50 \
|
||||
--save_steps 50 \
|
||||
--save_total_limit 2 \
|
||||
--logging_steps 5 \
|
||||
--max_length 2048 \
|
||||
--output_dir output \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 4 \
|
||||
--model_author swift \
|
||||
--model_name swift-robot
|
||||
```
|
||||
|
||||
After fine-tuning, you can test the results using the following script. Note that the `--adapters` part should be modified to point to the final saved checkpoint directory:
|
||||
|
||||
```bash
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
swift infer \
|
||||
--adapters output/vx-xxx/checkpoint-xxx \
|
||||
--stream true \
|
||||
--temperature 0 \
|
||||
--max_new_tokens 2048
|
||||
```
|
||||
|
||||
```text
|
||||
<<< who are you?
|
||||
<think>
|
||||
Okay, the user asked, "who are you?" I need to introduce myself. Let me start by stating my name, swift-robot. Then, I should mention that I'm an AI assistant developed by swift. I should explain my purpose, which is to provide information and assistance. I should also highlight my capabilities, like answering questions, generating text, and engaging in conversation. It's important to keep the tone friendly and approachable. Maybe add something about being here to help and encourage the user to ask anything. Let me check if I covered all the key points: name, developer, purpose, capabilities, and a welcoming statement. Yeah, that should do it. Now, let me put that into a concise and friendly response.
|
||||
</think>
|
||||
|
||||
Hello! I am swift-robot, an artificial intelligence assistant developed by swift. My purpose is to provide information and assistance to users like you. I can answer questions, generate text, and engage in conversations on a wide range of topics. I am here to help, so feel free to ask me anything you need!
|
||||
```
|
||||
|
||||
By default, ms-swift uses the ModelScope community to download models and datasets. If you want to use the HuggingFace community instead, you need to additionally specify `--use_hf true`.
|
||||
|
||||
Merge LoRA weights:
|
||||
```shell
|
||||
swift export \
|
||||
--adapters output/checkpoint-xxx \
|
||||
--merge_lora true
|
||||
```
|
||||
|
||||
Push the model to ModelScope/HuggingFace:
|
||||
|
||||
```shell
|
||||
# If pushing full weights, change `--adapters` to `--model`.
|
||||
# You can find your ModelScope hub_token here: https://modelscope.cn/my/myaccesstoken
|
||||
swift export \
|
||||
--adapters output/checkpoint-xxx \
|
||||
--push_to_hub true \
|
||||
--hub_model_id '<hub-model-id>' \
|
||||
--hub_token '<hub-token>' \
|
||||
--use_hf false
|
||||
```
|
||||
|
||||
If you want to perform training on multiple GPUs, the following example provides a multi-GPU training setup:
|
||||
|
||||
```shell
|
||||
# 4 * 60GB
|
||||
# You can run the experiment by setting `--dataset AI-ModelScope/alpaca-gpt4-data-en`
|
||||
# Note: If you specify `--packing true`, you must also set `--attn_impl flash_attn`
|
||||
|
||||
NPROC_PER_NODE=4 \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen3-8B \
|
||||
--tuner_type full \
|
||||
--dataset '<your-dataset>' \
|
||||
--load_from_cache_file true \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--torch_dtype bfloat16 \
|
||||
--per_device_train_batch_size 1 \
|
||||
--per_device_eval_batch_size 1 \
|
||||
--learning_rate 1e-5 \
|
||||
--gradient_accumulation_steps 4 \
|
||||
--packing true \
|
||||
--eval_steps 100 \
|
||||
--save_steps 100 \
|
||||
--logging_steps 5 \
|
||||
--max_length 8192 \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 8 \
|
||||
--dataset_num_proc 8 \
|
||||
--save_total_limit 2 \
|
||||
--save_only_model true \
|
||||
--output_dir output \
|
||||
--deepspeed zero3 \
|
||||
--use_liger_kernel true \
|
||||
--attn_impl flash_attn
|
||||
```
|
||||
|
||||
## Reinforcement Learning (RL)
|
||||
|
||||
ms-swift supports RLHF methods such as DPO, GRPO, DAPO, PPO, KTO, and GKD. This section will focus on using ms-swift for GRPO training on Qwen3-8B. For more information about GRPO, refer to the [GRPO documentation](../Instruction/GRPO/GetStarted/GRPO.md). Additional RLHF training scripts can be found in [examples/train/rlhf](https://github.com/modelscope/ms-swift/tree/main/examples/train/rlhf).
|
||||
|
||||
### Environment Setup
|
||||
|
||||
In addition to installing the dependencies related to ms-swift mentioned above, you also need to install the following:
|
||||
|
||||
```shell
|
||||
pip install "math_verify"
|
||||
pip install vllm==0.8.5.post1
|
||||
```
|
||||
|
||||
|
||||
### Data Preparation
|
||||
|
||||
The dataset format used for GRPO training with ms-swift is similar to that of SFT, but it does not require the final assistant's response part. If accuracy is used as the reward, an additional `solution` column is required to calculate accuracy.
|
||||
|
||||
Example dataset format:
|
||||
|
||||
|
||||
```jsonl
|
||||
{"messages": [{"role": "user", "content": "Tell me tomorrow's weather"}]}
|
||||
{"messages": [{"role": "user", "content": "What is 1 + 1?"}, {"role": "assistant", "content": "It equals 2"}, {"role": "user", "content": "What about adding 1?"}]}
|
||||
{"messages": [{"role": "user", "content": "What is your name?"}]}
|
||||
```
|
||||
|
||||
For data preparation for other RLHF algorithms, please refer to the [Custom Dataset Documentation](../Customization/Custom-dataset.md#rlhf).
|
||||
|
||||
Notes on dataset requirements:
|
||||
|
||||
- **Reward Function Calculation**: The dataset format depends on the reward function being used. Additional columns may be needed to support specific reward calculations. For example:
|
||||
- When using built-in `accuracy` or `cosine` rewards, the dataset must include a `solution` column to calculate the accuracy of responses.
|
||||
- Other columns in the dataset will be passed as `**kwargs` to the reward function for further customization.
|
||||
- **Custom Reward Functions**: To customize the reward function according to your specific needs, refer to: [External Reward Plugin](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/plugin). This plugin provides examples and templates for implementing custom reward functions.
|
||||
|
||||
We use AI-MO/NuminaMath-TIR as the dataset and compute the accuracy-based reward for model responses.
|
||||
|
||||
During training, we utilize vLLM to accelerate the sampling process.
|
||||
|
||||
```bash
|
||||
# 70G*8
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
|
||||
NPROC_PER_NODE=8 \
|
||||
swift rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen3-8B \
|
||||
--tuner_type full \
|
||||
--dataset 'AI-MO/NuminaMath-TIR#5000' \
|
||||
--load_from_cache_file true \
|
||||
--torch_dtype bfloat16 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 2 \
|
||||
--per_device_eval_batch_size 2 \
|
||||
--learning_rate 1e-6 \
|
||||
--save_total_limit 2 \
|
||||
--logging_steps 5 \
|
||||
--output_dir output \
|
||||
--gradient_accumulation_steps 1 \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 4 \
|
||||
--max_length 4096 \
|
||||
--max_completion_length 4096 \
|
||||
--vllm_max_model_len 8192 \
|
||||
--reward_funcs accuracy \
|
||||
--num_generations 16 \
|
||||
--use_vllm true \
|
||||
--vllm_gpu_memory_utilization 0.4 \
|
||||
--sleep_level 1 \
|
||||
--offload_model true \
|
||||
--offload_optimizer true \
|
||||
--deepspeed zero3 \
|
||||
--vllm_tensor_parallel_size 1 \
|
||||
--temperature 1.0 \
|
||||
--top_p 0.85 \
|
||||
--log_completions true \
|
||||
--overlong_filter true
|
||||
```
|
||||
|
||||
## Megatron-SWIFT
|
||||
|
||||
Best practice reference for single-node 8xH20 LoRA training with Qwen3-235B-A22B-Instruct-250718: https://github.com/modelscope/ms-swift/pull/5033.
|
||||
|
||||
ms-swift introduces Megatron parallelism techniques to accelerate CPT/SFT/DPO/GRPO for large models. Supported models can be found in the [Supported Models and Datasets Document](../Instruction/Supported-models-and-datasets.md).
|
||||
|
||||
For environment setup, refer to the [Megatron-SWIFT Training Documentation](../Megatron-SWIFT/Quick-start.md).
|
||||
|
||||
We will use Alibaba Cloud DLC to launch training. The training environment consists of two nodes equipped with 8x 80GiB A800 GPUs each. For more information on multi-node launching, see [here](https://github.com/modelscope/ms-swift/tree/main/examples/train/multi-node).
|
||||
|
||||
```bash
|
||||
# https://help.aliyun.com/zh/pai/user-guide/general-environment-variables
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
NNODES=$WORLD_SIZE \
|
||||
NODE_RANK=$RANK \
|
||||
megatron sft \
|
||||
--model Qwen/Qwen3-30B-A3B-Base \
|
||||
--save_safetensors true \
|
||||
--dataset 'liucong/Chinese-DeepSeek-R1-Distill-data-110k-SFT' \
|
||||
--load_from_cache_file true \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--pipeline_model_parallel_size 2 \
|
||||
--expert_model_parallel_size 8 \
|
||||
--moe_permute_fusion true \
|
||||
--moe_grouped_gemm true \
|
||||
--moe_shared_expert_overlap true \
|
||||
--moe_aux_loss_coeff 1e-3 \
|
||||
--micro_batch_size 1 \
|
||||
--global_batch_size 16 \
|
||||
--packing true \
|
||||
--recompute_granularity full \
|
||||
--recompute_method uniform \
|
||||
--recompute_num_layers 1 \
|
||||
--train_iters 2000 \
|
||||
--eval_iters 50 \
|
||||
--finetune true \
|
||||
--cross_entropy_loss_fusion true \
|
||||
--lr 1e-5 \
|
||||
--lr_warmup_fraction 0.05 \
|
||||
--min_lr 1e-6 \
|
||||
--output_dir megatron_output/Qwen3-30B-A3B-Base \
|
||||
--eval_steps 200 \
|
||||
--save_steps 200 \
|
||||
--max_length 8192 \
|
||||
--dataloader_num_workers 8 \
|
||||
--dataset_num_proc 8 \
|
||||
--no_save_optim true \
|
||||
--no_save_rng true \
|
||||
--sequence_parallel true \
|
||||
--attention_backend flash
|
||||
```
|
||||
|
||||
|
||||
Training loss chart (partial):
|
||||
|
||||
<img width="910" alt="Image" src="https://github.com/user-attachments/assets/9fe393aa-8299-4659-aa2f-be5d44f0730b" />
|
||||
|
||||
Effect screenshot:
|
||||
|
||||
<img width="1066" alt="Image" src="https://github.com/user-attachments/assets/1a924130-1954-43e9-9093-b019aeef5949" />
|
||||
|
||||
|
||||
The custom dataset format is the same as that used in `swift sft`. For details, see the previous sections. Simply specify `--dataset <dataset_path>`.
|
||||
|
||||
A comparison of training speed and GPU memory usage when performing full-parameter fine-tuning of the Qwen3-30B-A3B model using `megatron sft` and `swift sft` is shown below:
|
||||
|
||||
| | Megatron-LM | DeepSpeed-ZeRO2 | DeepSpeed-ZeRO3 |
|
||||
| -------- | ----------- | --------------- | --------------- |
|
||||
| 训练速度 | 9.6s/it | - | 91.2s/it |
|
||||
| 显存使用 | 16 * 60GiB | OOM | 16 * 80GiB |
|
||||
@@ -0,0 +1,327 @@
|
||||
# Qwen3-VL Best Practices
|
||||
## Environment Setup
|
||||
Before starting inference and training, please ensure your environment is properly configured.
|
||||
|
||||
```shell
|
||||
pip install "transformers>=4.57" "qwen_vl_utils>=0.0.14"
|
||||
|
||||
pip install "ms-swift>=4.0"
|
||||
# pip install "vllm>=0.11.0" # If using the vLLM inference backend for inference
|
||||
```
|
||||
- About video data training hangs: Using the decord backend for video reading may cause the training process to hang, see [this issue](https://github.com/dmlc/decord/issues/269). You can use the torchcodec backend instead. For details, refer to the [qwen_vl_utils](https://github.com/QwenLM/Qwen3-VL/blob/50068df2334f309979ff05d75f1078c8309c63ed/qwen-vl-utils/src/qwen_vl_utils/vision_process.py#L390-L400) library.
|
||||
|
||||
|
||||
## Inference
|
||||
Inference using transformers:
|
||||
|
||||
```python
|
||||
import os
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
from modelscope import snapshot_download
|
||||
from qwen_vl_utils import process_vision_info
|
||||
from transformers import Qwen3VLForConditionalGeneration, AutoProcessor
|
||||
|
||||
model_dir = snapshot_download('Qwen/Qwen3-VL-4B-Instruct')
|
||||
|
||||
model = Qwen3VLForConditionalGeneration.from_pretrained(
|
||||
model_dir, dtype="auto", device_map="auto",
|
||||
# attn_implementation='flash_attention_2',
|
||||
)
|
||||
|
||||
processor = AutoProcessor.from_pretrained(model_dir)
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "video",
|
||||
"video": "https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4",
|
||||
"max_pixels": 128*32*32,
|
||||
"max_frames": 16,
|
||||
},
|
||||
{"type": "text", "text": "Describe this video."},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
||||
image_inputs, video_inputs, video_kwargs = process_vision_info([messages], return_video_kwargs=True,
|
||||
image_patch_size= 16,
|
||||
return_video_metadata=True)
|
||||
if video_inputs is not None:
|
||||
video_inputs, video_metadatas = zip(*video_inputs)
|
||||
video_inputs, video_metadatas = list(video_inputs), list(video_metadatas)
|
||||
else:
|
||||
video_metadatas = None
|
||||
inputs = processor(text=[text], images=image_inputs, videos=video_inputs, video_metadata=video_metadatas, **video_kwargs, do_resize=False, return_tensors="pt")
|
||||
inputs = inputs.to('cuda')
|
||||
|
||||
generated_ids = model.generate(**inputs, max_new_tokens=128, do_sample=False)
|
||||
generated_ids_trimmed = [
|
||||
out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
|
||||
]
|
||||
output_text = processor.batch_decode(
|
||||
generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
|
||||
)
|
||||
print(output_text[0])
|
||||
# 'A baby wearing glasses sits on a bed, engrossed in reading a book. The baby turns the pages with both hands, occasionally looking up and smiling. The room is cozy, with a crib in the background and clothes scattered around. The baby's focus and curiosity are evident as they explore the book, creating a heartwarming scene of early learning and discovery.'
|
||||
```
|
||||
|
||||
Inference using ms-swift's TransformersEngine:
|
||||
|
||||
```python
|
||||
import os
|
||||
# os.environ['SWIFT_DEBUG'] = '1'
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['VIDEO_MAX_TOKEN_NUM'] = '128'
|
||||
os.environ['FPS_MAX_FRAMES'] = '16'
|
||||
|
||||
|
||||
from swift.infer_engine import TransformersEngine, InferRequest, RequestConfig
|
||||
engine = TransformersEngine('Qwen/Qwen3-VL-4B-Instruct') # attn_impl='flash_attention_2'
|
||||
infer_request = InferRequest(messages=[{
|
||||
"role": "user",
|
||||
"content": '<video>Describe this video.',
|
||||
}], videos=['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4'])
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0)
|
||||
resp_list = engine.infer([infer_request], request_config=request_config)
|
||||
response = resp_list[0].choices[0].message.content
|
||||
# 'A baby wearing glasses sits on a bed, engrossed in reading a book. The baby turns the pages with both hands, occasionally looking up and smiling. The room is cozy, with a crib in the background and clothes scattered around. The baby's focus and curiosity are evident as they explore the book, creating a heartwarming scene of early learning and discovery.'
|
||||
|
||||
# use stream
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0, stream=True)
|
||||
gen_list = engine.infer([infer_request], request_config=request_config)
|
||||
for chunk in gen_list[0]:
|
||||
if chunk is None:
|
||||
continue
|
||||
print(chunk.choices[0].delta.content, end='', flush=True)
|
||||
print()
|
||||
```
|
||||
|
||||
Inference using command line:
|
||||
|
||||
```shell
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=16 \
|
||||
swift infer \
|
||||
--model Qwen/Qwen3-VL-4B-Instruct \
|
||||
--stream true
|
||||
```
|
||||
|
||||
```
|
||||
<<< who are you?
|
||||
Hello! I'm Qwen, a large-scale language model independently developed by the Tongyi Lab under Alibaba Group. My main functions include answering questions, creating text such as stories, official documents, emails, scripts, and more, as well as performing logical reasoning, programming, and other tasks. If you have any questions or need assistance, feel free to let me know anytime, and I'll do my best to help!
|
||||
--------------------------------------------------
|
||||
<<< <image>describe the image.
|
||||
Input an image path or URL <<< http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png
|
||||
This is a beautifully detailed, close-up portrait of an adorable tabby kitten, rendered with a soft, painterly effect that gives it a gentle, dreamy quality.
|
||||
|
||||
Here's a breakdown of the image:
|
||||
|
||||
- **The Kitten:** The subject is a young, fluffy kitten with a classic tabby pattern. Its fur is a mix of white and soft grayish-brown stripes, with a prominent dark stripe running down the center of its forehead and over its nose. The kitten's face is predominantly white, with delicate markings around its eyes and cheeks.
|
||||
|
||||
- **The Eyes:** Its most captivating feature is its large, round, and expressive eyes. They are a striking shade of bright blue-gray, with dark pupils that give it an intense, curious, and slightly innocent gaze. The eyes are wide open, suggesting the kitten is alert and attentive.
|
||||
|
||||
- **The Expression:** The kitten's expression is sweet and innocent. Its small pink nose and slightly parted mouth give it a gentle, almost pleading look. Its whiskers are long and white, standing out against its fur.
|
||||
|
||||
- **The Style:** The image has a soft-focus, artistic quality, reminiscent of impressionist painting. The edges of the kitten's fur are slightly blurred, creating a halo effect that draws attention to its face. The background is softly blurred with muted tones of green and gray, which helps the kitten stand out as the clear focal point.
|
||||
|
||||
- **Overall Impression:** The image evokes feelings of warmth, cuteness, and tenderness. The kitten appears to be looking directly at the viewer, creating a sense of connection and affection.
|
||||
|
||||
This is a lovely and charming depiction of a young kitten, capturing its innocence and charm in a visually appealing and emotionally engaging way.
|
||||
--------------------------------------------------
|
||||
<<< <video>describe the video.
|
||||
Input a video path or URL <<< https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4
|
||||
This video captures a charming and adorable moment of a young child, likely a toddler, sitting on a bed and pretending to read a book. The child is wearing glasses, which adds a humorous and endearing touch to the scene — as if they're a little scholar or librarian.
|
||||
|
||||
Here's a breakdown of what unfolds:
|
||||
|
||||
- The child is seated cross-legged on a bed with a patterned quilt. Behind them, a crib and some household items are visible, suggesting a cozy bedroom setting.
|
||||
|
||||
- The child holds an open book and appears to be turning the pages with focused attention, mimicking the behavior of a real reader.
|
||||
|
||||
- At one point, the child looks up, smiles, or seems to react with delight — perhaps amused by something in the book or just enjoying the activity.
|
||||
|
||||
- The child's movements are gentle and deliberate, showing a sense of concentration and curiosity. They turn pages, sometimes with one hand, and occasionally lift the book slightly as if to examine it more closely.
|
||||
|
||||
- The video has a warm, candid feel — it's not staged, and the child's natural behavior makes it feel authentic and heartwarming.
|
||||
|
||||
Overall, this is a sweet, lighthearted video that showcases the innocence and imagination of early childhood. The child's engagement with the book, combined with their glasses and playful demeanor, creates a delightful and memorable scene.
|
||||
```
|
||||
|
||||
- For model-specific parameters, such as environment variables like `VIDEO_MAX_TOKEN_NUM`, please refer to the [Command Line Parameters Documentation](../Instruction/Command-line-parameters.md#qwen3_vl-qwen3_5).
|
||||
|
||||
|
||||
## Training
|
||||
This section introduces how to train Qwen3-VL using ms-swift and Megatron-SWIFT. We recommend using ms-swift (i.e., transformers backend, which is more convenient and simple) for Dense models, and Megatron-SWIFT (i.e., megatron backend, which offers faster training speed; see benchmark [here](../Megatron-SWIFT/Quick-start.md#benchmark)) for MoE models.
|
||||
|
||||
If you need to fine-tune the model with a custom dataset, you can prepare the data in the following format and set `--dataset train.jsonl --val_dataset val.jsonl` in the command line, where the validation set is optional. For more information, please refer to the [Multimodal Dataset Documentation](../Customization/Custom-dataset.md#multimodal).
|
||||
|
||||
```jsonl
|
||||
{"messages": [{"role": "user", "content": "Where is the capital of Zhejiang?"}, {"role": "assistant", "content": "The capital of Zhejiang is Hangzhou."}]}
|
||||
{"messages": [{"role": "user", "content": "<image><image>What's the difference between these two images?"}, {"role": "assistant", "content": "The first one is a kitten, the second one is a puppy"}], "images": ["/xxx/x.jpg", "/xxx/x.png"]}
|
||||
{"messages": [{"role": "system", "content": "You are a helpful and harmless assistant"}, {"role": "user", "content": "<image>What's in the image, <video>what's in the video?"}, {"role": "assistant", "content": "There's an elephant in the image, and a puppy running on the grass in the video"}], "images": ["/xxx/x.jpg"], "videos": ["/xxx/x.mp4"]}
|
||||
```
|
||||
|
||||
Qwen3-VL's bbox output uses normalized 1000 relative coordinates. You can use the grounding dataset format provided by ms-swift, where the coordinates in "bbox" are absolute coordinates, and ms-swift will automatically convert absolute coordinates to normalized 1000 relative coordinates. For more information, please refer to the [Grounding Dataset Format Documentation](../Customization/Custom-dataset.md#grounding).
|
||||
|
||||
```jsonl
|
||||
{"messages": [{"role": "user", "content": "<image>Locate the <ref-object> in the image"}, {"role": "assistant", "content": "[\n\t{\"bbox_2d\": <bbox>, \"label\": \"<ref-object>\"},\n\t{\"bbox_2d\": <bbox>, \"label\": \"<ref-object>\"}\n]"}], "images": ["cat.png"], "objects": {"ref": ["sheep", "sheep", "sheep"], "bbox": [[90.9, 160.8, 135, 212.8], [360.9, 480.8, 495, 532.8]]}}
|
||||
```
|
||||
|
||||
### Dense Models
|
||||
Below is a fine-tuning script for the `Qwen3-VL-4B-Instruct` model. We use mixed-modality data as a demo dataset; this example script has no practical value. Training memory usage is 2 * 21GiB, and training time is 12 minutes.
|
||||
- If you find the preprocessing time too long, you can remove `--packing`, or use [cached dataset](https://github.com/modelscope/ms-swift/tree/main/examples/train/cached_dataset).
|
||||
|
||||
```shell
|
||||
# 2 * 21GiB
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=16 \
|
||||
NPROC_PER_NODE=2 \
|
||||
CUDA_VISIBLE_DEVICES=0,1 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen3-VL-4B-Instruct \
|
||||
--dataset 'AI-ModelScope/alpaca-gpt4-data-zh#10000' \
|
||||
'AI-ModelScope/LaTeX_OCR:human_handwrite#5000' \
|
||||
'swift/VideoChatGPT:Generic#2000' \
|
||||
--load_from_cache_file true \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--tuner_type lora \
|
||||
--torch_dtype bfloat16 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 1 \
|
||||
--per_device_eval_batch_size 1 \
|
||||
--attn_impl flash_attn \
|
||||
--padding_free true \
|
||||
--learning_rate 1e-4 \
|
||||
--lora_rank 8 \
|
||||
--lora_alpha 32 \
|
||||
--target_modules all-linear \
|
||||
--freeze_vit true \
|
||||
--freeze_aligner true \
|
||||
--packing true \
|
||||
--gradient_checkpointing true \
|
||||
--vit_gradient_checkpointing false \
|
||||
--gradient_accumulation_steps 2 \
|
||||
--eval_steps 100 \
|
||||
--save_steps 100 \
|
||||
--save_total_limit 2 \
|
||||
--logging_steps 5 \
|
||||
--max_length 4096 \
|
||||
--output_dir output \
|
||||
--warmup_ratio 0.05 \
|
||||
--deepspeed zero2 \
|
||||
--dataset_num_proc 4 \
|
||||
--dataloader_num_workers 4
|
||||
```
|
||||
After training, we use the following script to perform inference on the validation set:
|
||||
|
||||
```shell
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=16 \
|
||||
swift infer \
|
||||
--adapters output/vx-xxx/checkpoint-xxx \
|
||||
--stream true \
|
||||
--max_new_tokens 2048 \
|
||||
--load_data_args true
|
||||
```
|
||||
|
||||
```
|
||||
--------------------------------------------------
|
||||
[QUERY] Using LaTeX to perform OCR on the image.
|
||||
[LABELS] 1 + \frac { 1 } { 1 ! } + \frac { 1 } { 2 ! } + \frac { 1 } { 3 ! } + \frac { 1 } { 4 ! }
|
||||
[RESPONSE] 1 + \frac { 1 } { 1 ! } + \frac { 1 } { 2 ! } + \frac { 1 } { 3 ! } + \frac { 1 } { 4 ! }
|
||||
--------------------------------------------------
|
||||
[QUERY] What color suit is the man wearing while playing the saxophone on stage?
|
||||
[LABELS] The man is wearing a black suit and white shirt while playing the saxophone on the red-floored stage.
|
||||
[RESPONSE] The man is wearing a black suit while playing the saxophone on stage.
|
||||
--------------------------------------------------
|
||||
...
|
||||
```
|
||||
|
||||
### MoE Models
|
||||
Below is a fine-tuning script for the `Qwen3-VL-30B-A3B-Instruct` model. We use Megatron-SWIFT for single-machine full-parameter training. We still use mixed data for training; this example script has no practical value. Training requires 8 * 80GiB GPU memory, and training time is 20 minutes.
|
||||
|
||||
For Megatron-SWIFT environment installation, please refer to the [Megatron-SWIFT Documentation](../Megatron-SWIFT/Quick-start.md). Megatron-SWIFT shares the template and dataset modules with ms-swift, so the custom dataset format and model-specific environment variables introduced earlier still apply.
|
||||
|
||||
The fine-tuning script is as follows. For adjusting training techniques and parallelism strategies, refer to the [Megatron-SWIFT Documentation](../Megatron-SWIFT/Quick-start.md#training-tips).
|
||||
|
||||
```shell
|
||||
# 8 * 80GiB
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
OMP_NUM_THREADS=14 \
|
||||
NPROC_PER_NODE=8 \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=16 \
|
||||
megatron sft \
|
||||
--model Qwen/Qwen3-VL-30B-A3B-Instruct \
|
||||
--save_safetensors true \
|
||||
--dataset 'AI-ModelScope/alpaca-gpt4-data-zh#10000' \
|
||||
'AI-ModelScope/LaTeX_OCR:human_handwrite#5000' \
|
||||
'swift/VideoChatGPT:Generic#2000' \
|
||||
--load_from_cache_file true \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--moe_permute_fusion true \
|
||||
--tensor_model_parallel_size 4 \
|
||||
--expert_model_parallel_size 8 \
|
||||
--moe_grouped_gemm true \
|
||||
--moe_shared_expert_overlap true \
|
||||
--moe_aux_loss_coeff 1e-6 \
|
||||
--micro_batch_size 1 \
|
||||
--global_batch_size 4 \
|
||||
--recompute_granularity full \
|
||||
--recompute_method uniform \
|
||||
--recompute_num_layers 1 \
|
||||
--num_train_epochs 1 \
|
||||
--finetune true \
|
||||
--cross_entropy_loss_fusion true \
|
||||
--lr 1e-5 \
|
||||
--lr_warmup_fraction 0.05 \
|
||||
--min_lr 1e-6 \
|
||||
--output_dir megatron_output/Qwen3-VL-30B-A3B-Instruct \
|
||||
--eval_steps 500 \
|
||||
--save_steps 500 \
|
||||
--max_length 4096 \
|
||||
--packing true \
|
||||
--dataloader_num_workers 8 \
|
||||
--dataset_num_proc 8 \
|
||||
--no_save_optim true \
|
||||
--no_save_rng true \
|
||||
--sequence_parallel true \
|
||||
--moe_expert_capacity_factor 2 \
|
||||
--attention_backend flash
|
||||
```
|
||||
|
||||
After training, we use the following script to perform inference on the validation set:
|
||||
|
||||
```shell
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=16 \
|
||||
swift infer \
|
||||
--model megatron_output/Qwen3-VL-30B-A3B-Instruct/vx-xxx/checkpoint-xxx \
|
||||
--stream true \
|
||||
--max_new_tokens 2048 \
|
||||
--load_data_args true
|
||||
```
|
||||
Use the following command to push the trained weights to ModelScope:
|
||||
|
||||
```shell
|
||||
swift export \
|
||||
--model output/vx-xxx/checkpoint-xxx \
|
||||
--push_to_hub true \
|
||||
--hub_model_id '<your-model-id>' \
|
||||
--hub_token '<your-sdk-token>'
|
||||
```
|
||||
@@ -0,0 +1,560 @@
|
||||
# Qwen3.5 Best Practices
|
||||
|
||||
ms-swift supports training [Qwen3.5](https://github.com/QwenLM/Qwen3.5) Dense/MoE models using transformers/Megatron backends. Qwen3.5 is a multimodal model with hybrid thinking, combining linear attention and full attention. This article will introduce how to perform inference, instruction fine-tuning, and reinforcement learning on Qwen3.5 Dense/MoE models.
|
||||
|
||||
## Environment Setup
|
||||
|
||||
```shell
|
||||
pip install -U ms-swift
|
||||
pip install -U "transformers>=5.9" "qwen_vl_utils>=0.0.14" peft liger-kernel
|
||||
|
||||
# flash-linear-attention
|
||||
# If you encounter slow training issues, please refer to: https://github.com/fla-org/flash-linear-attention/issues/758
|
||||
# Please use Python 3.12: https://github.com/fla-org/flash-linear-attention/issues/121
|
||||
pip install -U "flash-linear-attention>=0.4.2" --no-build-isolation
|
||||
|
||||
# causal_conv1d
|
||||
pip install -U git+https://github.com/Dao-AILab/causal-conv1d --no-build-isolation
|
||||
|
||||
# flash-attention
|
||||
pip install "flash-attn==2.8.3" --no-build-isolation
|
||||
|
||||
# deepspeed training
|
||||
pip install deepspeed
|
||||
|
||||
# vllm (torch2.10) for inference/deployment/RL
|
||||
pip install -U "vllm>=0.17.0"
|
||||
```
|
||||
|
||||
- Qwen3.5 video data training hangs: Using the decord backend to read videos may cause hanging issues, refer to [this issue](https://github.com/dmlc/decord/issues/269). You can use the torchcodec backend, specifically refer to the [qwen_vl_utils](https://github.com/QwenLM/Qwen3-VL/blob/50068df2334f309979ff05d75f1078c8309c63ed/qwen-vl-utils/src/qwen_vl_utils/vision_process.py#L390-L400) library.
|
||||
- If you are using Qwen3.5 on Ascend NPU and want details about the FLA / MindSpeed replacement, effective patch path, and verified version combinations, please refer to [Qwen3.5 FLA Patch Notes in the NPU Support document](./NPU-support.md#qwen35-fla-patch-notes).
|
||||
|
||||
## Inference
|
||||
|
||||
Using ms-swift's `TransformersEngine` for inference:
|
||||
|
||||
- The meaning of model-specific parameters such as `VIDEO_MAX_TOKEN_NUM` environment variables is the same as Qwen3-VL, refer to [Command-line Parameters Documentation](../Instruction/Command-line-parameters.md#qwen3_vl,qwen3_5).
|
||||
|
||||
```python
|
||||
import os
|
||||
# os.environ['SWIFT_DEBUG'] = '1'
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['IMAGE_MAX_TOKEN_NUM'] = '1024'
|
||||
os.environ['VIDEO_MAX_TOKEN_NUM'] = '128'
|
||||
os.environ['FPS_MAX_FRAMES'] = '16'
|
||||
|
||||
from swift import get_model_processor, get_template
|
||||
from swift.infer_engine import TransformersEngine, InferRequest, RequestConfig
|
||||
|
||||
model, processor = get_model_processor('Qwen/Qwen3.5-4B') # attn_impl='flash_attention_2'
|
||||
template = get_template(processor, enable_thinking=False)
|
||||
engine = TransformersEngine(model, template=template)
|
||||
infer_request = InferRequest(messages=[{
|
||||
"role": "user",
|
||||
"content": '<video>Describe this video.',
|
||||
}], videos=['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4'])
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0)
|
||||
resp_list = engine.infer([infer_request], request_config=request_config)
|
||||
response = resp_list[0].choices[0].message.content
|
||||
print(response)
|
||||
|
||||
# use stream
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0, stream=True)
|
||||
gen_list = engine.infer([infer_request], request_config=request_config)
|
||||
for chunk in gen_list[0]:
|
||||
if chunk is None:
|
||||
continue
|
||||
print(chunk.choices[0].delta.content, end='', flush=True)
|
||||
print()
|
||||
```
|
||||
|
||||
Using command line for inference:
|
||||
|
||||
```shell
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=16 \
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
swift infer \
|
||||
--model Qwen/Qwen3.5-4B \
|
||||
--enable_thinking false \
|
||||
--stream true
|
||||
```
|
||||
|
||||
## Fine-tuning
|
||||
|
||||
This chapter will introduce how to train Qwen3.5 using ms-swift and Megatron-SWIFT. It is recommended to use ms-swift (i.e., transformers backend, more convenient and simple) for Dense models, and Megatron-SWIFT (i.e., megatron backend, faster training speed) for MoE models.
|
||||
|
||||
If you need to fine-tune the model with a custom dataset, you can prepare the data in the following format and set `--dataset train.jsonl --val_dataset val.jsonl` in the command line, where the validation set is optional. For more information, please refer to [Multimodal Dataset Documentation](../Customization/Custom-dataset.md#multimodal).
|
||||
|
||||
```jsonl
|
||||
{"messages": [{"role": "user", "content": "Where is the capital of Zhejiang?"}, {"role": "assistant", "content": "The capital of Zhejiang is Hangzhou."}]}
|
||||
{"messages": [{"role": "user", "content": "<image><image>What's the difference between these two images?"}, {"role": "assistant", "content": "The first one is a kitten, the second one is a puppy"}], "images": ["/xxx/x.jpg", "/xxx/x.png"]}
|
||||
{"messages": [{"role": "system", "content": "You are a helpful and harmless assistant"}, {"role": "user", "content": "<image>What's in the image, <video>what's in the video?"}, {"role": "assistant", "content": "There's an elephant in the image, and a puppy running on the grass in the video"}], "images": ["/xxx/x.jpg"], "videos": ["/xxx/x.mp4"]}
|
||||
```
|
||||
|
||||
Qwen3.5's bbox output uses normalized relative coordinates with a scale of 1000. You can use the grounding dataset format provided by ms-swift, where the coordinates in "bbox" are absolute coordinates, and ms-swift will automatically convert absolute coordinates to normalized relative coordinates with a scale of 1000. For more information, please refer to [Grounding Dataset Format Documentation](../Customization/Custom-dataset.md#grounding).
|
||||
|
||||
```jsonl
|
||||
{"messages": [{"role": "user", "content": "<image>Locate the <ref-object> in the image"}, {"role": "assistant", "content": "[\n\t{\"bbox_2d\": <bbox>, \"label\": \"<ref-object>\"},\n\t{\"bbox_2d\": <bbox>, \"label\": \"<ref-object>\"}\n]"}], "images": ["cat.png"], "objects": {"ref": ["sheep", "sheep", "sheep"], "bbox": [[90.9, 160.8, 135, 212.8], [360.9, 480.8, 495, 532.8]]}}
|
||||
```
|
||||
|
||||
### Dense Models
|
||||
|
||||
Below is a fine-tuning script for the Qwen3.5-4B model. This example script is for demonstration purposes only. Training memory usage is 4 × 20GiB, with a training time of 12 minutes. Qwen3.5 supports packing/padding_free in transformers (requires "ms-swift>=4.3.1"; Megatron has no such version restriction). Below we use the `group_by_length` parameter to accelerate training, ensuring load balancing across data parallelism (DP) and reducing zero-padding in micro batches. However, this may cause fluctuations in the loss curve due to insufficient data shuffling. You can also remove this parameter and use `--packing true` instead.
|
||||
- Regarding data preprocessing: When using the packing / group_by_length parameters, all data must be preprocessed in advance to obtain the input_ids length of each sample, which takes additional time. If you prefer to process data on-the-fly during training, you can remove these two parameters.
|
||||
- Reduce memory consumption: You can enable `--deepspeed zero2/zero3`, turn on sequence parallelism via `--sequence_parallel_size`, or use `--use_liger_kernel true`.
|
||||
- Training acceleration: You can enable `--attn_impl flash_attention_2`, and for MoE models, it is recommended to enable `--experts_impl grouped_mm`.
|
||||
|
||||
```shell
|
||||
# 4 * 20GiB
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
NPROC_PER_NODE=4 \
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=12 \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
||||
swift sft \
|
||||
--model Qwen/Qwen3.5-4B \
|
||||
--tuner_type lora \
|
||||
--dataset 'AI-ModelScope/alpaca-gpt4-data-zh#500' \
|
||||
'AI-ModelScope/alpaca-gpt4-data-en#500' \
|
||||
'swift/self-cognition#500' \
|
||||
'AI-ModelScope/LaTeX_OCR:human_handwrite#2000' \
|
||||
--load_from_cache_file true \
|
||||
--add_non_thinking_prefix true \
|
||||
--loss_scale ignore_empty_think \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--torch_dtype bfloat16 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 4 \
|
||||
--per_device_eval_batch_size 4 \
|
||||
--learning_rate 1e-4 \
|
||||
--lora_rank 8 \
|
||||
--lora_alpha 32 \
|
||||
--target_modules all-linear \
|
||||
--gradient_accumulation_steps 1 \
|
||||
--group_by_length true \
|
||||
--output_dir output/Qwen3.5-4B \
|
||||
--eval_steps 50 \
|
||||
--save_steps 50 \
|
||||
--save_total_limit 2 \
|
||||
--logging_steps 5 \
|
||||
--max_length 2048 \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataset_num_proc 4 \
|
||||
--dataloader_num_workers 4 \
|
||||
--deepspeed zero2 \
|
||||
--model_author swift \
|
||||
--model_name swift-robot
|
||||
```
|
||||
|
||||
After training, use the following script to perform inference on the validation set:
|
||||
|
||||
```shell
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=12 \
|
||||
swift infer \
|
||||
--adapters output/Qwen3.5-4B/vx-xxx/checkpoint-xxx \
|
||||
--stream true \
|
||||
--enable_thinking false \
|
||||
--max_new_tokens 512 \
|
||||
--load_data_args true
|
||||
```
|
||||
|
||||
|
||||
```text
|
||||
[QUERY] 你好,你是谁?
|
||||
[RESPONSE] <think>
|
||||
|
||||
</think>
|
||||
|
||||
你好,我是由swift开发的人工智能语言模型,我的名字叫swift-robot。很高兴能与你交流。
|
||||
--------------------------------------------------
|
||||
[QUERY] Using LaTeX to perform OCR on the image.
|
||||
[LABELS] e = \sum _ { k = 0 } ^ { \infty } \frac { 1 } { k ! }
|
||||
[RESPONSE] <think>
|
||||
|
||||
</think>
|
||||
|
||||
e = \sum _ { k = 0 } ^ { \infty } \frac { 1 } { k ! }
|
||||
```
|
||||
|
||||
```python
|
||||
import os
|
||||
# os.environ['SWIFT_DEBUG'] = '1'
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['IMAGE_MAX_TOKEN_NUM'] = '1024'
|
||||
os.environ['VIDEO_MAX_TOKEN_NUM'] = '128'
|
||||
os.environ['FPS_MAX_FRAMES'] = '16'
|
||||
|
||||
from peft import PeftModel
|
||||
from swift import get_model_processor, get_template
|
||||
from swift.infer_engine import TransformersEngine, InferRequest, RequestConfig
|
||||
|
||||
adapter_dir = 'output/Qwen3.5-4B/vx-xxx/checkpoint-xxx'
|
||||
enable_thinking = False
|
||||
|
||||
model, processor = get_model_processor('Qwen/Qwen3.5-4B') # attn_impl='flash_attention_2'
|
||||
model = PeftModel.from_pretrained(model, adapter_dir)
|
||||
template = get_template(processor, enable_thinking=enable_thinking)
|
||||
engine = TransformersEngine(model, template=template)
|
||||
infer_request = InferRequest(messages=[{
|
||||
"role": "user",
|
||||
"content": 'who are you?',
|
||||
}])
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0)
|
||||
resp_list = engine.infer([infer_request], request_config=request_config)
|
||||
response = resp_list[0].choices[0].message.content
|
||||
print(response)
|
||||
|
||||
# use stream
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0, stream=True)
|
||||
gen_list = engine.infer([infer_request], request_config=request_config)
|
||||
for chunk in gen_list[0]:
|
||||
if chunk is None:
|
||||
continue
|
||||
print(chunk.choices[0].delta.content, end='', flush=True)
|
||||
print()
|
||||
# I am an artificial intelligence assistant named swift-robot, trained by swift. I am designed to understand and generate natural language text in order to provide information, answer questions, and engage in conversation with humans. How can I assist you?
|
||||
```
|
||||
|
||||
For an example of training MoE using the transformers backend, refer to: https://github.com/modelscope/ms-swift/blob/main/examples/models/qwen3_5/transformers.sh
|
||||
|
||||
### MoE Models
|
||||
|
||||
Qwen3.5-35B-A3B Megatron training. For environment preparation, please refer to [Megatron-SWIFT Quick Start Documentation](../Megatron-SWIFT/Quick-start.md). You can complete the following example in 15 minutes:
|
||||
|
||||
```shell
|
||||
# 4 * 40GiB
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
NPROC_PER_NODE=4 \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=12 \
|
||||
megatron sft \
|
||||
--model Qwen/Qwen3.5-35B-A3B \
|
||||
--save_safetensors true \
|
||||
--merge_lora true \
|
||||
--dataset 'AI-ModelScope/alpaca-gpt4-data-zh#500' \
|
||||
'AI-ModelScope/alpaca-gpt4-data-en#500' \
|
||||
'swift/self-cognition#500' \
|
||||
'AI-ModelScope/LaTeX_OCR:human_handwrite#2000' \
|
||||
--load_from_cache_file true \
|
||||
--add_non_thinking_prefix true \
|
||||
--loss_scale ignore_empty_think \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--tuner_type lora \
|
||||
--lora_rank 8 \
|
||||
--lora_alpha 32 \
|
||||
--target_modules all-linear \
|
||||
--expert_model_parallel_size 4 \
|
||||
--moe_permute_fusion true \
|
||||
--moe_grouped_gemm true \
|
||||
--moe_shared_expert_overlap true \
|
||||
--moe_aux_loss_coeff 1e-6 \
|
||||
--micro_batch_size 4 \
|
||||
--global_batch_size 16 \
|
||||
--recompute_granularity full \
|
||||
--recompute_method uniform \
|
||||
--recompute_num_layers 1 \
|
||||
--num_train_epochs 1 \
|
||||
--group_by_length true \
|
||||
--finetune true \
|
||||
--freeze_llm false \
|
||||
--freeze_vit true \
|
||||
--freeze_aligner true \
|
||||
--cross_entropy_loss_fusion true \
|
||||
--lr 1e-4 \
|
||||
--lr_warmup_fraction 0.05 \
|
||||
--min_lr 1e-5 \
|
||||
--output_dir megatron_output/Qwen3.5-35B-A3B \
|
||||
--eval_steps 200 \
|
||||
--save_steps 200 \
|
||||
--max_length 2048 \
|
||||
--dataloader_num_workers 8 \
|
||||
--dataset_num_proc 8 \
|
||||
--no_save_optim true \
|
||||
--no_save_rng true \
|
||||
--sequence_parallel true \
|
||||
--attention_backend flash \
|
||||
--padding_free false \
|
||||
--model_author swift \
|
||||
--model_name swift-robot
|
||||
```
|
||||
|
||||
After training, use the following script to perform inference on the validation set:
|
||||
|
||||
```shell
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
||||
IMAGE_MAX_TOKEN_NUM=1024 \
|
||||
VIDEO_MAX_TOKEN_NUM=128 \
|
||||
FPS_MAX_FRAMES=12 \
|
||||
swift infer \
|
||||
--model megatron_output/Qwen3.5-35B-A3B/vx-xxx/checkpoint-xxx-merged \
|
||||
--stream true \
|
||||
--enable_thinking false \
|
||||
--max_new_tokens 512 \
|
||||
--load_data_args true
|
||||
```
|
||||
|
||||
Tips for training Qwen3.5 with Megatron-SWIFT:
|
||||
- Full parameter training: Refer to [this example](https://github.com/modelscope/ms-swift/blob/main/examples/models/qwen3_5/packing.sh).
|
||||
- TP Limitation Removed: Using `megatron-core>=0.16` removes the `num_query_groups` limitation on TP.
|
||||
- Regarding MTP training: `mcore-bridge>=1.1.0` supports multimodal MTP training. Please install the corresponding version.
|
||||
- CP support: "mcore-bridge>=1.1.0" supports CP training for GDN. Additionally, the megatron-core [main branch](https://github.com/NVIDIA/Megatron-LM) needs to be installed.
|
||||
- By default, `GatedDeltaNet` uses the Megatron implementation, which requires "megatron-core>=0.16" (ms-swift>=4.1.0; previous versions defaulted to the transformers implementation). Set the environment variable `USE_MCORE_GDN=0` to switch to the transformers implementation. **Note that the transformers implementation does not support packing and GDN's TP/CP**.
|
||||
- Support for padding_free/packing: Packing can improve training speed. Refer to [this example](https://github.com/modelscope/ms-swift/tree/main/examples/models/qwen3_5/packing.sh).
|
||||
- Qwen3-Next Megatron GatedDeltaNet support refers to [this PR](https://github.com/modelscope/mcore-bridge/pull/76), requiring `mcore-bridge>=1.4.0`.
|
||||
- apply_wd_to_qk_layernorm: Apply weight decay to qk layernorm. Default is False.
|
||||
- Regarding FP8 training: refer to [this example](https://github.com/modelscope/ms-swift/blob/main/examples/models/qwen3_5/fp8.sh). You need to install "mcore-bridge>=1.2.0", and set the parameter `--linear_decoupled_in_proj true` to decouple `in_proj` into `in_proj_qkvz` and `in_proj_ba`, where `in_proj_ba` is still trained in original precision.
|
||||
|
||||
|
||||
## Reinforcement Learning (RL)
|
||||
|
||||
Using Qwen3.5-2B as an example, we demonstrate GRPO and GKD training on the [GSM8K](https://www.modelscope.cn/datasets/modelscope/gsm8k) dataset and evaluate on the GSM8K test set. To avoid excessively long chain-of-thought outputs, all experiments set `enable_thinking false`.
|
||||
|
||||
### GRPO
|
||||
|
||||
#### Dense Model
|
||||
Full-parameter training with GRPO, using `gsm8k_accuracy` and `gsm8k_format` as reward functions. See [gsm8k_plugin.py](https://github.com/modelscope/ms-swift/blob/main/examples/train/grpo/plugin/gsm8k/gsm8k_plugin.py) for the reward implementation.
|
||||
|
||||
```shell
|
||||
SYSTEM_PROMPT="""You are a helpful math assistant. Solve the problem step by step and put your final answer within \\boxed{}."""
|
||||
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
||||
NPROC_PER_NODE=4 \
|
||||
swift rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen3.5-2B \
|
||||
--external_plugins examples/train/grpo/plugin/gsm8k/gsm8k_plugin.py \
|
||||
--reward_funcs gsm8k_accuracy gsm8k_format \
|
||||
--columns '{"answer": "solution"}' \
|
||||
--enable_thinking false \
|
||||
--use_vllm true \
|
||||
--vllm_mode colocate \
|
||||
--vllm_gpu_memory_utilization 0.4 \
|
||||
--vllm_tensor_parallel_size 1 \
|
||||
--vllm_max_model_len 10240 \
|
||||
--sleep_level 1 \
|
||||
--tuner_type full \
|
||||
--torch_dtype bfloat16 \
|
||||
--dataset 'modelscope/gsm8k' \
|
||||
--load_from_cache_file true \
|
||||
--max_length 2048 \
|
||||
--max_completion_length 8192 \
|
||||
--num_train_epochs 1 \
|
||||
--per_device_train_batch_size 4 \
|
||||
--gradient_accumulation_steps 4 \
|
||||
--learning_rate 1e-6 \
|
||||
--lr_scheduler_type cosine \
|
||||
--save_steps 10 \
|
||||
--save_total_limit 100 \
|
||||
--logging_steps 1 \
|
||||
--warmup_ratio 0.0 \
|
||||
--dataloader_num_workers 4 \
|
||||
--num_generations 8 \
|
||||
--temperature 1.0 \
|
||||
--system "$SYSTEM_PROMPT" \
|
||||
--deepspeed zero2 \
|
||||
--log_completions true \
|
||||
--report_to tensorboard swanlab \
|
||||
--max_grad_norm 1.0 \
|
||||
--epsilon 0.2 \
|
||||
--epsilon_high 0.28 \
|
||||
--scale_rewards none
|
||||
```
|
||||
|
||||
Evaluate the checkpoints:
|
||||
|
||||
```shell
|
||||
CUDA_VISIBLE_DEVICES=0 swift eval \
|
||||
--model output/Qwen3.5-2B/vxx-xxx-xxx/checkpoint-xx \
|
||||
--enable_thinking false \
|
||||
--eval_dataset gsm8k \
|
||||
--eval_backend Native --infer_backend vllm \
|
||||
--eval_generation_config '{"max_tokens":8192,"temperature":0.0,"do_sample":false}'
|
||||
```
|
||||
|
||||
GSM8K evaluation results at 10-step intervals for the first 50 steps:
|
||||
|
||||
| Model / Steps | GSM8K Accuracy | Improvement |
|
||||
|---|---|---|
|
||||
| Qwen3.5-2B (baseline) | 0.7597 | - |
|
||||
| GRPO 10 steps | 0.7650 | +0.53 |
|
||||
| GRPO 20 steps | 0.7748 | +1.51 |
|
||||
| GRPO 30 steps | 0.7779 | +1.82 |
|
||||
| GRPO 40 steps | 0.7817 | +2.20 |
|
||||
| GRPO 50 steps | 0.7885 | +2.88 |
|
||||
|
||||
### MoE Model
|
||||
|
||||
GRPO LoRA training for Qwen3.5-35B-A3B MoE model using the Megatron backend, trained on the [DAPO-Math-17k](https://www.modelscope.cn/datasets/open-r1/DAPO-Math-17k-Processed) dataset with `accuracy` as reward functions.
|
||||
|
||||
```shell
|
||||
SYSTEM_PROMPT="""You are a helpful math assistant. Solve the problem step by step and put your final answer within \\boxed{}."""
|
||||
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
|
||||
NPROC_PER_NODE=8 \
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
megatron rlhf \
|
||||
--rlhf_type grpo \
|
||||
--model Qwen/Qwen3.5-35B-A3B \
|
||||
--save_safetensors true \
|
||||
--enable_thinking false \
|
||||
--merge_lora true \
|
||||
--context_parallel_size 1 \
|
||||
--tensor_model_parallel_size 1 \
|
||||
--expert_model_parallel_size 8 \
|
||||
--pipeline_model_parallel_size 1 \
|
||||
--moe_permute_fusion true \
|
||||
--dataset open-r1/DAPO-Math-17k-Processed \
|
||||
--system "$SYSTEM_PROMPT" \
|
||||
--num_train_epochs 1 \
|
||||
--global_batch_size 64 \
|
||||
--micro_batch_size 1 \
|
||||
--steps_per_generation 2 \
|
||||
--num_generations 8 \
|
||||
--reward_funcs accuracy \
|
||||
--use_vllm true \
|
||||
--vllm_mode colocate \
|
||||
--vllm_gpu_memory_utilization 0.5 \
|
||||
--vllm_tensor_parallel_size 2 \
|
||||
--vllm_max_model_len 9192 \
|
||||
--max_length 1000 \
|
||||
--max_completion_length 8192 \
|
||||
--tuner_type lora \
|
||||
--target_modules all-linear \
|
||||
--lr 5e-5 \
|
||||
--bf16 true \
|
||||
--beta 0.00 \
|
||||
--epsilon 0.2 \
|
||||
--epsilon_high 0.28 \
|
||||
--dynamic_sample false \
|
||||
--overlong_filter true \
|
||||
--loss_type grpo \
|
||||
--sleep_level 1 \
|
||||
--offload_model true \
|
||||
--offload_bridge false \
|
||||
--offload_optimizer true \
|
||||
--logging_steps 1 \
|
||||
--recompute_granularity full \
|
||||
--recompute_method uniform \
|
||||
--recompute_num_layers 1 \
|
||||
--finetune \
|
||||
--dataloader_num_workers 8 \
|
||||
--dataset_num_proc 8 \
|
||||
--no_save_optim \
|
||||
--no_save_rng \
|
||||
--save_steps 20 \
|
||||
--attention_backend flash \
|
||||
--moe_expert_capacity_factor 2 \
|
||||
--temperature 1.0 \
|
||||
--padding_free false \
|
||||
--sequence_parallel true \
|
||||
--log_completions true \
|
||||
--report_to tensorboard swanlab
|
||||
```
|
||||
|
||||
Evaluate on AIME-2025 and MATH-500:
|
||||
|
||||
```shell
|
||||
CUDA_VISIBLE_DEVICES=0,1 swift eval \
|
||||
--model <checkpoint-merged-path> \
|
||||
--enable_thinking false \
|
||||
--eval_dataset aime25 math_500 \
|
||||
--eval_backend Native --infer_backend vllm \
|
||||
--vllm_tensor_parallel_size 2 \
|
||||
--vllm_gpu_memory_utilization 0.9 \
|
||||
--vllm_max_model_len 10000 \
|
||||
--eval_generation_config '{"max_tokens":8192,"temperature":0.0,"do_sample":false}' \
|
||||
--eval_num_proc 8
|
||||
```
|
||||
|
||||
Evaluation results on AIME-2025 and MATH-500:
|
||||
|
||||
| Model / Steps | AIME-2025 | MATH-500 |
|
||||
|---|---|---|
|
||||
| Qwen3.5-35B-A3B (baseline) | 43.33 | 92.40 |
|
||||
| Megatron GRPO 20 steps | 53.33 (+10.00) | 95.80 (+3.40) |
|
||||
| Megatron GRPO 40 steps | 53.33 (+10.00) | 96.60 (+4.20) |
|
||||
|
||||
### GKD
|
||||
|
||||
LoRA training with GKD (General Knowledge Distillation), using Qwen3.5-9B as the teacher model. First, launch the teacher server with `swift deploy` (alternatively, use the `--teacher_model` parameter to load the model directly):
|
||||
|
||||
```shell
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
swift deploy \
|
||||
--model Qwen/Qwen3.5-9B \
|
||||
--infer_backend vllm \
|
||||
--port 8000 \
|
||||
--vllm_tensor_parallel_size 1 \
|
||||
--vllm_max_model_len 10240 \
|
||||
--gpu-memory-utilization 0.8 \
|
||||
--max_logprobs 64
|
||||
```
|
||||
|
||||
Then start GKD training on the remaining GPUs:
|
||||
|
||||
```shell
|
||||
NPROC_PER_NODE=3 \
|
||||
CUDA_VISIBLE_DEVICES=1,2,3 \
|
||||
swift rlhf \
|
||||
--rlhf_type gkd \
|
||||
--model Qwen/Qwen3.5-2B \
|
||||
--teacher_model_server http://localhost:8000 \
|
||||
--gkd_logits_topk 64 \
|
||||
--enable_thinking false \
|
||||
--tuner_type lora \
|
||||
--use_vllm true \
|
||||
--vllm_mode colocate \
|
||||
--vllm_gpu_memory_utilization 0.5 \
|
||||
--vllm_tensor_parallel_size 1 \
|
||||
--vllm_max_model_len 10240 \
|
||||
--sleep_level 0 \
|
||||
--dataset 'modelscope/gsm8k' \
|
||||
--lmbda 1 \
|
||||
--beta 0.5 \
|
||||
--torch_dtype bfloat16 \
|
||||
--per_device_train_batch_size 2 \
|
||||
--gradient_accumulation_steps 16 \
|
||||
--learning_rate 5e-5 \
|
||||
--logging_steps 1 \
|
||||
--save_steps 100 \
|
||||
--save_total_limit 10 \
|
||||
--max_length 2048 \
|
||||
--max_completion_length 8192 \
|
||||
--warmup_ratio 0.1 \
|
||||
--save_only_model true \
|
||||
--dataloader_num_workers 4 \
|
||||
--dataset_num_proc 4 \
|
||||
--attn_impl flash_attn \
|
||||
--report_to tensorboard swanlab
|
||||
```
|
||||
Evaluate the checkpoints:
|
||||
|
||||
```shell
|
||||
CUDA_VISIBLE_DEVICES=0 swift eval \
|
||||
--model Qwen/Qwen3.5-2B \
|
||||
--adapters output/Qwen3.5-2B/vxx-xxx-xxx/checkpoint-xx \
|
||||
--merge_lora true \
|
||||
--enable_thinking false \
|
||||
--eval_dataset gsm8k \
|
||||
--eval_backend Native --infer_backend vllm \
|
||||
--eval_generation_config '{"max_tokens":8192,"temperature":0.0,"do_sample":false}'
|
||||
```
|
||||
|
||||
GSM8K evaluation results at 100-step intervals for the first 300 steps:
|
||||
|
||||
| Model / Steps | GSM8K Accuracy | Improvement |
|
||||
|---|---|---|
|
||||
| Qwen3.5-2B (baseline) | 0.7597 | - |
|
||||
| GKD 100 steps | 0.7968 | +3.71 |
|
||||
| GKD 200 steps | 0.8188 | +5.91 |
|
||||
| GKD 300 steps | 0.8332 | +7.35 |
|
||||
@@ -0,0 +1,235 @@
|
||||
# Best Practices for Rapidly Training Vision-Language (VL) Models
|
||||
|
||||
This document provides best practices for quickly training vision-language (VL) models from scratch.
|
||||
|
||||
Model Links
|
||||
- [Qwen2.5-VL-7B-Instruct](https://www.modelscope.cn/models/Qwen/Qwen2.5-VL-7B-Instruct)
|
||||
- [Qwen3-8B](https://www.modelscope.cn/models/Qwen/Qwen3-8B)
|
||||
|
||||
Trained Model Link
|
||||
- [Simple-VL-8B](https://www.modelscope.cn/models/swift/Simple-VL-8B/summary)
|
||||
|
||||
|
||||
The training workflow builds upon the Qwen2.5-VL-7B-Instruct model architecture by replacing its internal large language model (LLM) component with the weights from Qwen3-8B , thereby enhancing the model's visual understanding capabilities. The process involves the following steps:
|
||||
|
||||
1. Modify the original model’s configuration file config.json to align with Qwen3-8B.
|
||||
2. Initialize and load new model weights, saving them as a new model.
|
||||
3. Fine-tune the new model in two stages:
|
||||
1. Stage 1 : Train only the vision-to-language alignment module (aligner), freezing the ViT and LLM components.
|
||||
2. Stage 2 : Unfreeze all modules and perform joint fine-tuning to improve overall performance.
|
||||
|
||||
|
||||
## Model Modification
|
||||
|
||||
### Config File (config.json) Update
|
||||
Due to structural differences between Qwen2.5-7B-Instruct and Qwen3-8B (e.g., number of layers, hidden dimensions), create a new config.json based on the Qwen2.5-VL-7B-Instruct config and update the following parameters to match Qwen3-8B:
|
||||
|
||||
|
||||
```
|
||||
Modified Parameters
|
||||
1. hidden_size 3584->4096
|
||||
2. intermediate_size: 18944->12288
|
||||
3. num_attention_heads: 28->32
|
||||
4. num_key_value_heads: 4->8
|
||||
5. num_hidden_layers: 28->36
|
||||
6. vocab_size:152064->151936
|
||||
7. max_window_layers:28->36
|
||||
8. out_hidden_size: 3584->4096
|
||||
|
||||
Newly Added Parameter
|
||||
1. head_dim: 128
|
||||
```
|
||||
|
||||
### Model Weight Initialization and Replacement
|
||||
Use the following Python script to initialize, replace, and save the model weights:
|
||||
```python
|
||||
import torch
|
||||
from modelscope import Qwen2_5_VLForConditionalGeneration, AutoModelForCausalLM, AutoConfig
|
||||
from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import Qwen2_5_VLPatchMerger, Qwen2_5_VLModel
|
||||
from accelerate import Accelerator
|
||||
|
||||
# Load original VL model and Qwen3-8B model
|
||||
qwen2_5_vl_7b_model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
|
||||
"Qwen/Qwen2.5-VL-7B-Instruct",
|
||||
device_map="cuda",
|
||||
torch_dtype=torch.bfloat16
|
||||
)
|
||||
device = qwen2_5_vl_7b_model.device
|
||||
|
||||
qwen3_8b_model = AutoModelForCausalLM.from_pretrained(
|
||||
"Qwen/Qwen3-8B",
|
||||
device_map=device,
|
||||
torch_dtype=torch.bfloat16
|
||||
)
|
||||
|
||||
# Load configurations
|
||||
old_config = AutoConfig.from_pretrained("Qwen/Qwen2.5-VL-7B-Instruct")
|
||||
new_config = AutoConfig.from_pretrained("/path/to/new_config_dir") # Path to new config directory
|
||||
new_visual_config = new_config.vision_config
|
||||
|
||||
# Replace merger (aligner) layer
|
||||
new_merger = Qwen2_5_VLPatchMerger(
|
||||
dim=new_visual_config.out_hidden_size,
|
||||
context_dim=new_visual_config.hidden_size,
|
||||
spatial_merge_size=new_visual_config.spatial_merge_size,
|
||||
).to(device).to(torch.bfloat16)
|
||||
qwen2_5_vl_7b_model.visual.merger = new_merger
|
||||
|
||||
# Replace LLM part of the VL model
|
||||
new_llm_model = Qwen2_5_VLModel(new_config).to(device).to(torch.bfloat16)
|
||||
|
||||
for name, param in qwen3_8b_model.model.named_parameters():
|
||||
if name in new_llm_model.state_dict():
|
||||
new_llm_model.state_dict()[name].copy_(param)
|
||||
|
||||
qwen2_5_vl_7b_model.model = new_llm_model
|
||||
qwen2_5_vl_7b_model.lm_head = qwen3_8b_model.lm_head
|
||||
|
||||
# Save modified model
|
||||
accelerator = Accelerator()
|
||||
accelerator.save_model(
|
||||
model=qwen2_5_vl_7b_model,
|
||||
save_directory="/path/to/save/Qwen3-VL-Model",
|
||||
max_shard_size="4GB",
|
||||
safe_serialization=True
|
||||
)
|
||||
```
|
||||
|
||||
After saving the weights, copy all files from the original Qwen2.5-VL-7B-Instruct model folder, except for the model weights(including `model.safetensors.index.json`), to the new model weights folder, and replace config.json with the newly modified config.json file.
|
||||
|
||||
## Training
|
||||
To simplify the process, we skip pre-training and proceed directly to supervised fine-tuning (SFT). The training is divided into two stages:
|
||||
|
||||
### Stage 1: Train Aligner Layer
|
||||
Train only the vision-to-language alignment module while freezing the ViT and LLM parts:
|
||||
```bash
|
||||
NNODES=$WORLD_SIZE \
|
||||
NODE_RANK=$RANK \
|
||||
NPROC_PER_NODE=8 \
|
||||
MAX_PIXELS=1003520 \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
|
||||
swift sft \
|
||||
--model /path/to/new_vl_model \
|
||||
--model_type qwen2_5_vl \
|
||||
--tuner_type full \
|
||||
--dataset xxx \
|
||||
--load_from_cache_file true \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--torch_dtype bfloat16 \
|
||||
--attn_impl flash_attn \
|
||||
--freeze_vit true \
|
||||
--freeze_llm true \
|
||||
--freeze_aligner false \
|
||||
--num_train_epochs 3 \
|
||||
--per_device_train_batch_size 2 \
|
||||
--learning_rate 5e-6 \
|
||||
--gradient_accumulation_steps 8 \
|
||||
--eval_steps -1 \
|
||||
--save_steps 1000 \
|
||||
--save_total_limit 10 \
|
||||
--logging_steps 5 \
|
||||
--max_length 8192 \
|
||||
--output_dir output \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 4 \
|
||||
--dataset_num_proc 8 \
|
||||
--deepspeed zero2
|
||||
```
|
||||
|
||||
### Stage 2: Full Model Training
|
||||
|
||||
Unfreeze all modules and jointly train to enhance the model's visual understanding:
|
||||
|
||||
```bash
|
||||
NNODES=$WORLD_SIZE \
|
||||
NODE_RANK=$RANK \
|
||||
NPROC_PER_NODE=8 \
|
||||
MAX_PIXELS=1003520 \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
|
||||
swift sft \
|
||||
--model /path/to/stage1_checkpoint \
|
||||
--model_type qwen2_5_vl \
|
||||
--tuner_type full \
|
||||
--dataset xxx \
|
||||
--load_from_cache_file true \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--torch_dtype bfloat16 \
|
||||
--attn_impl flash_attn \
|
||||
--freeze_vit false \
|
||||
--freeze_llm false \
|
||||
--freeze_aligner false \
|
||||
--num_train_epochs 3 \
|
||||
--per_device_train_batch_size 2 \
|
||||
--learning_rate 5e-6 \
|
||||
--gradient_accumulation_steps 8 \
|
||||
--eval_steps -1 \
|
||||
--save_steps 1000 \
|
||||
--save_total_limit 10 \
|
||||
--logging_steps 5 \
|
||||
--max_length 8192 \
|
||||
--output_dir output \
|
||||
--warmup_ratio 0.05 \
|
||||
--dataloader_num_workers 4 \
|
||||
--dataset_num_proc 8 \
|
||||
--deepspeed zero2
|
||||
```
|
||||
|
||||
## Inference / Deployment / Evaluation
|
||||
|
||||
### Inference
|
||||
Perform inference using `swift infer`:
|
||||
```bash
|
||||
swift infer \
|
||||
--model /path/to/stage2_checkpoint
|
||||
```
|
||||
|
||||
### Deoloyment
|
||||
Accelerate model serving with vLLM:
|
||||
```bash
|
||||
CUDA_VISIBLE_DEVICES=0 \
|
||||
MAX_PIXELS=1003520 \
|
||||
VIDEO_MAX_PIXELS=50176 \
|
||||
FPS_MAX_FRAMES=12 \
|
||||
swift deploy \
|
||||
--model /path/to/stage2_checkpoint \
|
||||
--infer_backend vllm \
|
||||
--vllm_gpu_memory_utilization 0.9 \
|
||||
--vllm_max_model_len 8192 \
|
||||
--max_new_tokens 2048 \
|
||||
--vllm_limit_mm_per_prompt '{"image": 5, "video": 2}' \
|
||||
--served_model_name Qwen3-VL
|
||||
```
|
||||
|
||||
### Evaluation
|
||||
Evaluate the trained VL model using [EvalScope](https://github.com/modelscope/evalscope/).
|
||||
|
||||
Example Evaluation Using MMMU Benchmark
|
||||
```python
|
||||
from evalscope import TaskConfig, run_task
|
||||
|
||||
task_cfg_dict = TaskConfig(
|
||||
work_dir='outputs',
|
||||
eval_backend='VLMEvalKit',
|
||||
eval_config={
|
||||
'data': ['MMMU_DEV_VAL'],
|
||||
'mode': 'all',
|
||||
'model': [
|
||||
{
|
||||
'api_base': 'http://localhost:8000/v1/chat/completions',
|
||||
'key': 'EMPTY',
|
||||
'name': 'CustomAPIModel',
|
||||
'temperature': 0.6,
|
||||
'type': 'Qwen3-VL',
|
||||
'img_size': -1,
|
||||
'video_llm': False,
|
||||
'max_tokens': 512,
|
||||
}
|
||||
],
|
||||
'reuse': False,
|
||||
'nproc': 64,
|
||||
'judge': 'exact_matching'
|
||||
},
|
||||
)
|
||||
|
||||
run_task(task_cfg=task_cfg_dict)
|
||||
```
|
||||
@@ -0,0 +1,127 @@
|
||||
# Reranker Training
|
||||
|
||||
SWIFT supports Reranker model training. Currently supported models include:
|
||||
|
||||
1. modernbert reranker model
|
||||
- [ModelScope](https://www.modelscope.cn/models/iic/gte-reranker-modernbert-base) [Hugging Face](https://huggingface.co/Alibaba-NLP/gte-reranker-modernbert-base)
|
||||
2. qwen3-reranker model
|
||||
- 0.6B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-Reranker-0.6B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-Reranker-0.6B)
|
||||
- 4B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-Reranker-4B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-Reranker-4B)
|
||||
- 8B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-Reranker-8B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-Reranker-8B)
|
||||
3. qwen3-vl-reranker model
|
||||
- 2B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-VL-Reranker-2B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-VL-Reranker-2B)
|
||||
- 8B: [ModelScope](https://www.modelscope.cn/models/Qwen/Qwen3-VL-Reranker-8B) [Hugging Face](https://huggingface.co/Qwen/Qwen3-VL-Reranker-8B)
|
||||
|
||||
## Implementation Methods
|
||||
|
||||
SWIFT currently supports two implementation methods for Reranker models, which have significant differences in architecture and loss function computation:
|
||||
|
||||
### 1. Classification Reranker
|
||||
|
||||
**Applicable Models:** modernbert reranker models (e.g., gte-reranker-modernbert-base)
|
||||
|
||||
**Core Principles:**
|
||||
- Based on sequence classification architecture, adding a classification head on top of pre-trained models
|
||||
- Input: query-document pairs, Output: single relevance score
|
||||
|
||||
### 2. Generative Reranker
|
||||
|
||||
**Applicable Models:** qwen3-reranker models (0.6B/4B/8B)
|
||||
|
||||
**Core Principles:**
|
||||
- Based on generative language model architecture (CausalLM)
|
||||
- Input: query-document pairs, Output: probability of specific tokens (e.g., "yes"/"no")
|
||||
- Classification is performed by comparing logits of specific tokens at the final position
|
||||
|
||||
## Loss Function Types
|
||||
|
||||
SWIFT supports multiple loss functions for training Reranker models:
|
||||
|
||||
### Pointwise Loss Functions
|
||||
Pointwise methods transform the ranking problem into a binary classification problem, processing each query-document pair independently:
|
||||
|
||||
- **Core Idea:** Binary classification for each query-document pair to determine document relevance to the query
|
||||
- **Loss Function:** Binary cross-entropy
|
||||
- **Use Cases:** Simple and efficient, suitable for large-scale data training
|
||||
|
||||
Environment variable configuration:
|
||||
- `GENERATIVE_RERANKER_POSITIVE_TOKEN`: Positive token (default: "yes")
|
||||
- `GENERATIVE_RERANKER_NEGATIVE_TOKEN`: Negative token (default: "no")
|
||||
|
||||
### Listwise Loss Functions
|
||||
Listwise methods transform the ranking problem into a multi-classification problem, selecting positive examples from multiple candidate documents:
|
||||
|
||||
- **Core Idea:** Multi-classification for each query's candidate document group (1 positive + n negative examples) to identify positive documents
|
||||
- **Loss Function:** Multi-class cross-entropy
|
||||
- **Use Cases:** Learning relative ranking relationships between documents, better aligned with the actual needs of information retrieval
|
||||
|
||||
Environment variable configuration:
|
||||
- `LISTWISE_RERANKER_TEMPERATURE`: Softmax temperature parameter (default: 1.0)
|
||||
- `LISTWISE_RERANKER_MIN_GROUP_SIZE`: Minimum group size, if the number of documents in the group is less than this value, the loss will not be calculated (default: 2)
|
||||
|
||||
**Listwise vs Pointwise:**
|
||||
- **Pointwise:** Independent relevance judgment, simple training, but ignores relative relationships between documents
|
||||
- **Listwise:** Learning relative ranking, better performance, more suitable for the essential needs of ranking tasks
|
||||
|
||||
The loss function source code can be found [here](https://github.com/modelscope/ms-swift/blob/main/swift/loss/mapping.py).
|
||||
|
||||
## Dataset Format
|
||||
|
||||
```json lines
|
||||
# LLM
|
||||
{"messages": [{"role": "user", "content": "query"}], "positive_messages": [[{"role": "assistant", "content": "relevant_doc1"}],[{"role": "assistant", "content": "relevant_doc2"}]], "negative_messages": [[{"role": "assistant", "content": "irrelevant_doc1"}],[{"role": "assistant", "content": "irrelevant_doc2"}], ...]}
|
||||
# MLLM
|
||||
{"messages": [{"role": "user", "content": "<image>query"}], "images": ["/some/images.jpg"], "positive_messages": [[{"role": "assistant", "content": "<image>relevant_doc1"}]], "positive_images": [["/some/positive_images.jpg"]], "negative_messages": [[{"role": "assistant", "content": "<image><image>irrelevant_doc1"}], [{"role": "assistant", "content": "<image>irrelevant_doc2"}]], "negative_images": [["/some/negative_images1.jpg", "/some/negative_images2.jpg"], ["/some/negative_images3.jpg"]]}
|
||||
```
|
||||
|
||||
**Field Description:**
|
||||
- `messages`: Query text
|
||||
- `positive_messages`: List of positive documents relevant to the query, supports multiple positive examples
|
||||
- `negative_messages`: List of negative documents irrelevant to the query, supports multiple negative examples
|
||||
|
||||
**Environment Variable Configuration:**
|
||||
- `MAX_POSITIVE_SAMPLES`: Maximum number of positive examples per query (default: 1)
|
||||
- `MAX_NEGATIVE_SAMPLES`: Maximum number of negative examples per query (default: 7)
|
||||
|
||||
> By default, `MAX_POSITIVE_SAMPLES` positive examples and `MAX_NEGATIVE_SAMPLES` negative examples will be extracted from each data item. Each positive example will be grouped with `MAX_NEGATIVE_SAMPLES` negative examples to form a group. Therefore, each data item will be expanded into `MAX_POSITIVE_SAMPLES`x`(1 + MAX_NEGATIVE_SAMPLES)` data points.
|
||||
> If the number of positive/negative examples in the data is insufficient, all positive/negative examples will be used. If the number of positive and negative examples in the data exceeds `MAX_POSITIVE_SAMPLES` and `MAX_NEGATIVE_SAMPLES`, random sampling will be performed.
|
||||
> **IMPORTANT**: The expanded data will be placed in the same batch. Therefore, the effective batch size on each device will be `per_device_train_batch_size` × `MAX_POSITIVE_SAMPLES` × (1 + `MAX_NEGATIVE_SAMPLES`). Please adjust your `per_device_train_batch_size` accordingly to avoid out-of-memory errors.
|
||||
|
||||
## Training Scripts
|
||||
|
||||
Training scripts provided by ms-swift:
|
||||
|
||||
- [Qwen3-Reranker/Qwen3-VL-Reranker](https://github.com/modelscope/ms-swift/blob/main/examples/train/reranker/qwen3)
|
||||
- [Pointwise Classification Reranker](https://github.com/modelscope/ms-swift/blob/main/examples/train/reranker/train_reranker.sh)
|
||||
- [Pointwise Generative Reranker](https://github.com/modelscope/ms-swift/blob/main/examples/train/reranker/train_generative_reranker.sh)
|
||||
- [Listwise Classification Reranker](https://github.com/modelscope/ms-swift/blob/main/examples/train/reranker/train_reranker_listwise.sh)
|
||||
- [Listwise Generative Reranker](https://github.com/modelscope/ms-swift/blob/main/examples/train/reranker/train_generative_reranker_listwise.sh)
|
||||
|
||||
For inference scripts, please refer to [here](https://github.com/modelscope/ms-swift/blob/main/examples/infer/demo_reranker.py).
|
||||
|
||||
## Advanced
|
||||
|
||||
- Qwen3-Reranker Custom Instruction:
|
||||
- Default template:
|
||||
|
||||
```text
|
||||
<|im_start|>system
|
||||
Judge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be "yes" or "no".<|im_end|>
|
||||
<|im_start|>user
|
||||
<Instruct>: {Instruction}
|
||||
<Query>: {Query}
|
||||
<Document>: {Document}<|im_end|>
|
||||
<|im_start|>assistant
|
||||
<think>
|
||||
|
||||
</think>
|
||||
|
||||
|
||||
```
|
||||
|
||||
- Default instruction:
|
||||
- `Given a web search query, retrieve relevant passages that answer the query`
|
||||
|
||||
- Instruction priority (nearest wins):
|
||||
- `system` inside `positive_messages`/`negative_messages` > `system` in main `messages` > default instruction.
|
||||
- That is, if a positive/negative message sequence contains a `system`, it takes precedence; otherwise, if main `messages` has a `system`, use it; if neither is provided, use the default instruction.
|
||||
@@ -0,0 +1,260 @@
|
||||
# DeepSeek-V4 Training Support
|
||||
|
||||
|
||||
Megatron-SWIFT currently supports fine-tuning and RL for DeepSeek-V4, including features such as MTP and FP8. (FP4 blockwise training is not yet supported; FP4 weights are automatically converted to FP8/BF16 when loaded.)
|
||||
|
||||
You need to use the `dev` branch of Megatron-Core, together with the `main` branches of `mcore-bridge` and `ms-swift`.
|
||||
|
||||
```shell
|
||||
pip install git+https://github.com/NVIDIA/Megatron-LM.git@dev
|
||||
pip install git+https://github.com/modelscope/mcore-bridge.git
|
||||
pip install git+https://github.com/modelscope/ms-swift.git
|
||||
|
||||
# Megatron-LM is tested under the following commit hash
|
||||
# pip install git+https://github.com/NVIDIA/Megatron-LM.git@c6449f0b23be397449f21c0967c5fc90785e55ea
|
||||
```
|
||||
|
||||
## Precision Alignment
|
||||
|
||||
- To support precision alignment testing (FP32), you need to comment out [these lines](https://github.com/NVIDIA/Megatron-LM/blob/bd381ac364b5139840f0cba6389db54f2c092e90/megatron/core/transformer/experimental_attention_variant/dsa.py#L41-L43).
|
||||
|
||||
After modifying the code, run the following tests to confirm there are no precision alignment issues (testing the forward alignment between transformers and megatron):
|
||||
|
||||
First, create a mini version of the model with only 4 layers:
|
||||
|
||||
```python
|
||||
import os
|
||||
import torch
|
||||
from modelscope.hub.file_download import model_file_download
|
||||
from safetensors.torch import safe_open
|
||||
from swift import safe_snapshot_download
|
||||
|
||||
from mcore_bridge.utils import Fp8Dequantizer, SafetensorLazyLoader, StreamingSafetensorSaver
|
||||
|
||||
model_id = 'deepseek-ai/DeepSeek-V4-Flash-Base'
|
||||
# Some models have the first few layers as dense and the rest as MoE; set this value accordingly
|
||||
model_dir = safe_snapshot_download(model_id, download_model=False)
|
||||
|
||||
loader = SafetensorLazyLoader(model_dir)
|
||||
state_dict = loader.get_state_dict()
|
||||
saver = StreamingSafetensorSaver(save_dir=model_dir)
|
||||
fp8_dequantizer = Fp8Dequantizer() # Used to convert fp8 weights to bf16
|
||||
|
||||
|
||||
def _open_file(self, filename: str):
|
||||
if filename not in self._file_handles:
|
||||
file_path = os.path.join(self.hf_model_dir, filename)
|
||||
tmp_dir = os.path.join(self.hf_model_dir, 'tmp')
|
||||
if not os.path.exists(file_path):
|
||||
file_path = os.path.join(tmp_dir, filename)
|
||||
if not os.path.exists(file_path):
|
||||
file_path = model_file_download(
|
||||
model_id=model_id,
|
||||
file_path=filename,
|
||||
local_dir=tmp_dir,
|
||||
)
|
||||
self._file_handles[filename] = safe_open(file_path, framework='pt')
|
||||
return self._file_handles[filename]
|
||||
|
||||
|
||||
SafetensorLazyLoader._open_file = _open_file # monkey patch (lazy downloading)
|
||||
new_state_dict = {}
|
||||
for k, v in state_dict.items():
|
||||
if k.startswith('layers.'):
|
||||
idx = int(k[len('layers.'):].split('.', 1)[0])
|
||||
if idx >= 4:
|
||||
continue
|
||||
if k.endswith('.scale'):
|
||||
continue
|
||||
elif k.endswith('.weight'):
|
||||
weight_scale_inv = k.replace('.weight', '.scale')
|
||||
if weight_scale_inv in state_dict:
|
||||
v = fp8_dequantizer.convert(v.load(), state_dict[weight_scale_inv].load()).to(torch.bfloat16)
|
||||
new_state_dict[k] = v if isinstance(v, torch.Tensor) else v.load()
|
||||
|
||||
for k, v in new_state_dict.items():
|
||||
saver.add_tensor(k, v)
|
||||
saver.finalize()
|
||||
```
|
||||
Then modify `config.json`:
|
||||
- Set `num_hidden_layers` to `4`.
|
||||
- Set `compress_ratios` to `[0, 0, 4, 128, 0]`.
|
||||
- Remove the `quantization_config` field.
|
||||
|
||||
|
||||
Next, create `test.py` and run it with: `CUDA_VISIBLE_DEVICES=0,1,2,3 torchrun --nproc_per_node=4 test.py`. For more details, refer to the [Custom Megatron Model documentation](https://swift.readthedocs.io/en/latest/Megatron-SWIFT/Custom-Model.html).
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
os.environ['SWIFT_TEST_CONVERT_PRECISION'] = '1'
|
||||
|
||||
from swift.megatron import MegatronExportArguments, megatron_export_main
|
||||
from swift import safe_snapshot_download
|
||||
model_id = 'deepseek-ai/DeepSeek-V4-Flash-Base'
|
||||
|
||||
model_dir = safe_snapshot_download(model_id, download_model=False)
|
||||
|
||||
if __name__ == '__main__':
|
||||
megatron_export_main(
|
||||
MegatronExportArguments(
|
||||
model=model_dir,
|
||||
to_mcore=True,
|
||||
attention_backend='flash',
|
||||
tensor_model_parallel_size=1,
|
||||
pipeline_model_parallel_layout='Et*3|t*1mL',
|
||||
pipeline_model_parallel_size=2,
|
||||
expert_model_parallel_size=2,
|
||||
mtp_num_layers=1,
|
||||
test_convert_precision=True,
|
||||
))
|
||||
```
|
||||
|
||||
When you see the following result, the alignment is correct and you can proceed to training.
|
||||

|
||||
|
||||
|
||||
## LoRA Training
|
||||
|
||||
The BF16 LoRA training script is shown below. The final output includes both the incremental LoRA weights and the merged BF16 full weights.
|
||||
|
||||
```shell
|
||||
PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
|
||||
NPROC_PER_NODE=8 \
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
|
||||
megatron sft \
|
||||
--model deepseek-ai/DeepSeek-V4-Flash \
|
||||
--save_safetensors true \
|
||||
--dataset 'AI-ModelScope/alpaca-gpt4-data-zh#1000' \
|
||||
'AI-ModelScope/alpaca-gpt4-data-en#1000' \
|
||||
'swift/self-cognition#1000' \
|
||||
--model_author swift \
|
||||
--model_name swift-robot \
|
||||
--merge_lora true \
|
||||
--load_from_cache_file true \
|
||||
--add_non_thinking_prefix true \
|
||||
--loss_scale ignore_empty_think \
|
||||
--split_dataset_ratio 0.01 \
|
||||
--tuner_type lora \
|
||||
--lora_rank 16 \
|
||||
--lora_alpha 32 \
|
||||
--tensor_model_parallel_size 1 \
|
||||
--expert_model_parallel_size 8 \
|
||||
--micro_batch_size 4 \
|
||||
--global_batch_size 32 \
|
||||
--padding_free false \
|
||||
--group_by_length true \
|
||||
--recompute_granularity full \
|
||||
--recompute_method uniform \
|
||||
--recompute_num_layers 1 \
|
||||
--moe_permute_fusion true \
|
||||
--moe_grouped_gemm true \
|
||||
--moe_shared_expert_overlap true \
|
||||
--moe_aux_loss_coeff 1e-3 \
|
||||
--num_train_epochs 1 \
|
||||
--finetune true \
|
||||
--cross_entropy_loss_fusion true \
|
||||
--lr 1e-4 \
|
||||
--lr_warmup_fraction 0.05 \
|
||||
--min_lr 1e-5 \
|
||||
--output_dir megatron_output/DeepSeek-V4-Flash \
|
||||
--eval_steps 200 \
|
||||
--save_steps 200 \
|
||||
--max_length 4096 \
|
||||
--dataloader_num_workers 8 \
|
||||
--dataset_num_proc 8 \
|
||||
--no_save_optim true \
|
||||
--no_save_rng true \
|
||||
--sequence_parallel true \
|
||||
--mtp_num_layers 1 \
|
||||
--attention_backend flash
|
||||
```
|
||||
|
||||
GPU memory usage:
|
||||

|
||||
|
||||
|
||||
Training log and loss:
|
||||

|
||||
|
||||
Tips:
|
||||
- If you want to enable pipeline parallelism (PP), you also need to set `pipeline_model_parallel_layout`. For example:
|
||||
```
|
||||
--pipeline_model_parallel_size 2 \
|
||||
--pipeline_model_parallel_layout 'Et*22|t*21mL' \
|
||||
```
|
||||
- Full-parameter training is also supported. You should lower the learning rate and increase the parallelism. Below is a 64-GPU training example:
|
||||
```
|
||||
--lr 1e-5 \
|
||||
--min_lr 1e-6 \
|
||||
--tensor_model_parallel_size 1 \
|
||||
--expert_model_parallel_size 8 \
|
||||
--pipeline_model_parallel_size 8 \
|
||||
--pipeline_model_parallel_layout Et*5|t*5|t*6|t*6|t*6|t*5|t*5|t*5mL \
|
||||
```
|
||||
- Packing/CP support: Requires installing the mcore-bridge/ms-swift main branch. Refer to these two PRs: [ms-swift#9705](https://github.com/modelscope/ms-swift/pull/9705), [mcore-bridge#140](https://github.com/modelscope/mcore-bridge/pull/140). To use CP, you need to set the following additionally (must be used together with packing `--packing true`, and note the merge of this PR [megatron-core#5706](https://github.com/NVIDIA/Megatron-LM/pull/5706)):
|
||||
|
||||
```
|
||||
--sequence_packing_scheduler dp_balanced \
|
||||
--cp_partition_mode contiguous \
|
||||
```
|
||||
- TP is not supported for now, pending support from Megatron-Core.
|
||||
- FP8 training: you can enable FP8 training and save the weights in FP8 by setting the parameters below. Full-parameter training is recommended. If you want to use LoRA + FP8, you should save only the LoRA weights (set `--merge_lora false`) and perform Merge-LoRA against the BF16 weights (FP8 has limited precision and the LoRA delta would be rounded to 0). See [this example](https://github.com/modelscope/ms-swift/blob/main/examples/megatron/fp8/lora.sh).
|
||||
```
|
||||
--fp8_recipe blockwise \
|
||||
--fp8_format e4m3 \
|
||||
--fp8_param_gather true \
|
||||
```
|
||||
|
||||
Inference with the trained model:
|
||||
|
||||
```shell
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
|
||||
swift infer \
|
||||
--model megatron_output/DeepSeek-V4-Flash/vx-xxx/checkpoint-xxx-merged \
|
||||
--infer_backend transformers \
|
||||
--enable_thinking false \
|
||||
--max_new_tokens 2048
|
||||
```
|
||||
|
||||
Inference result:
|
||||
|
||||

|
||||
|
||||
Running vLLM inference:
|
||||
|
||||
- If you want to use vLLM for inference, you can refer to [this documentation](https://recipes.vllm.ai/deepseek-ai/DeepSeek-V4-Flash). You need FP4/FP8 precision weights.
|
||||
- Additionally, you need to copy the original 'config.json' file and modify 'expert_dtype' (consistent with the config.json after training). This is because the file saved by transformers' `config.save_pretrained` differs from the original file, and vLLM is not compatible with the saved file.
|
||||
- If you encounter tilelang issues, you can check [this issue](https://github.com/modelscope/ms-swift/issues/9494).
|
||||
- mcore-bridge DeepSeek-V4 FP8 fix: [PR](https://github.com/modelscope/mcore-bridge/pull/133).
|
||||
|
||||
First perform quantization (note: this quantization will cause LoRA incremental information loss; this is only an example. It is recommended to use FP8 full-parameter training and export FP8 weights):
|
||||
|
||||
```shell
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
|
||||
NPROC_PER_NODE=8 \
|
||||
megatron export \
|
||||
--model megatron_output/DeepSeek-V4-Flash/vx-xxx/checkpoint-xxx-merged \
|
||||
--output_dir megatron_output/DeepSeek-V4-Flash/vx-xxx/checkpoint-xxx-merged-FP8 \
|
||||
--to_hf true \
|
||||
--fp8_recipe blockwise \
|
||||
--fp8_format e4m3 \
|
||||
--fp8_param_gather true \
|
||||
--mtp_num_layers 1 \
|
||||
--expert_model_parallel_size 8
|
||||
```
|
||||
|
||||
vLLM launch command:
|
||||
```shell
|
||||
vllm serve megatron_output/DeepSeek-V4-Flash/vx-xxx/checkpoint-xxx-merged-FP8 \
|
||||
--trust-remote-code \
|
||||
--kv-cache-dtype fp8 \
|
||||
--block-size 256 \
|
||||
--enable-expert-parallel \
|
||||
--tensor-parallel-size 8 \
|
||||
--max-model-len 8192 \
|
||||
--tokenizer-mode deepseek_v4 \
|
||||
--tool-call-parser deepseek_v4 \
|
||||
--enable-auto-tool-choice \
|
||||
--reasoning-parser deepseek_v4
|
||||
```
|
||||
Reference in New Issue
Block a user