训练说明
预训练
数据处理
预训练数据规模一般较大,为了最大化训练速度,需要提前对预训练数据进行处理和转换,之后再进行模型训练。具体如下:
- 首先,数据集需要按照 JSON 格式来组织,即文件中每一行是一个 JSON 字符串;
示例如下(其中每一行text
字段为要处理的预训练文本):
{"src": "www.nvidia.com", "text": "The quick brown fox", "type": "Eng", "id": "0", "title": "First Part"}
{"src": "The Internet", "text": "jumps over the lazy dog", "type": "Eng", "id": "42", "title": "Second Part"}
- 进入镜像容器通过
/workspace/AIAK-Training-LLM/tools/data_preprocess/preprocess_data.py
工具对数据进行预处理(可在examples/{model}/pretrain/
下查看数据处理示例脚本,其中model
指各种模型系列,例如llama)。
以下以llama
模型数据处理为例:
#!/bin/bash
MEGATRON_PATH=${MEGATRON_PATH:-"/workspace/AIAK-Megatron"}
AIAK_TRAINING_PATH=${AIAK_TRAINING_PATH:-"/workspace/AIAK-Training-LLM"}
TOKENIZER_PATH=${TOKENIZER_PATH:-"/mnt/cluster/llama2/Llama-2-7b-hf/"}
input_data=/mnt/cluster/datasets/pile_test/train.jsonl
output_prefix=/mnt/cluster/datasets/pile_test/pile-llama
PYTHONPATH=$MEGATRON_PATH:$AIAK_TRAINING_PATH:$PYTHONPATH \
python ${AIAK_TRAINING_PATH}/tools/data_preprocess/preprocess_data.py \
--input ${input_data} \
--output-prefix ${output_prefix} \
--tokenizer-type HFTokenizer \
--hf-tokenizer-path $TOKENIZER_PATH \
--json-keys text \
--workers 50 \
--append-eod
Checkpoint及格式转换
AIAK-Training-LLM 是基于 Megatron 构建的大模型训练库,而 Megatron 模型权重格式和开源的格式(如 huggingface)存在区别,因此,当用户需基于开源权重进行再训练时,需要提前将模型权重格式进行转换。
AIAK 针对支持的模型提供了统一的权重转换工具 /workspace/AIAK-Training-LLM/tools/convert_checkpoint
,可以方便地将模型权重在 Huggingface 和AIAK 支持的格式之间互转,具体包括:
- Huggingface 格式转换到 AIAK格式;
- AIAK 格式转换到 Huggingface;
- 不同的 TP/PP 切分策略互转;
具体转换操作,用户可以进入容器,参考**/workspace/AIAK-Training-LLM/examples/{model}/checkpoint_convert**
目录下提供的示例脚本。
以llama 为例,将 Huggingface
权重转换到 AIAK 支持的MegatronCore
格式:
#! /bin/bash
AIAK_TRAINING_PATH=${AIAK_TRAINING_PATH:-"/workspace/AIAK-Training-LLM"}
CONVERT_CHECKPOINT_PATH="$AIAK_TRAINING_PATH/tools/convert_checkpoint"
# LOAD:原始权重地址,SAVE 转换的模型存储路径
LOAD=/mnt/cluster/llama2/Llama-2-70b-hf/
SAVE=/mnt/cluster/llama2/mcore_llama2_70b_tp4_pp4
# 当前不支持 optim 部分转换,通过 --no_save_optim 和 --no_load_optim 关闭;
python $CONVERT_CHECKPOINT_PATH/model.py \
--load_platform=huggingface \
--save_platform=mcore \
--common_config_path=$CONVERT_CHECKPOINT_PATH/config/llama2-70b.json \
--tensor_model_parallel_size=4 \
--pipeline_model_parallel_size=4 \
--load_ckpt_path=$LOAD \
--save_ckpt_path=$SAVE \
--no_save_optim \
--no_load_optim
启动模型预训练
AIAK 当前提供各模型预训练示例脚本,进入容器,可以在**/workspace/AIAK-Training-LLM/examples/{model}/pretrain/**
下查看。
参数配置说明:AIAK 在支持开源 Megatron 提供的参数基础上,增加了更方便启动训练的参数,具体见aiak_training_llm/train/arguments.py
文件。其中:
- --model-name:该参数提供两种用法:1)指定模型架构名称(推荐),比如
llama2-7b
, 系统会自动按照llama2-7b
的模型结构进行超参配置,而无需用户传递,模型范围见上文模型列表中的Model Architecture
。2)指定模型系列名称,比如llama2
,这种用户需要完整的传递模型结构超参,适用需要调整原始模型结构的用户; - --training-phase:指定
pretrain
; - --tokenizer-type:优先建议指定为
HFTokenizer
,用户需通过--hf-tokenizer-path
指定 huggingface tokenizer 的路径;
LLaMA2 70B 训练示例
#! /bin/bash
MEGATRON_PATH=${MEGATRON_PATH:-"/workspace/AIAK-Megatron"}
AIAK_TRAINING_PATH=${AIAK_TRAINING_PATH:-"/workspace/AIAK-Training-LLM"}
DATA_PATH=${DATA_PATH:-"/mnt/cluster/llama2/pile/pile-llama_text_document"}
TOKENIZER_PATH=${TOKENIZER_PATH:-"/mnt/cluster/llama2/Llama-2-70b-hf/"}
CHECKPOINT_PATH=${CHECKPOINT_PATH:-"/mnt/cluster/llama2/mcore_llama2_70b_tp4_pp4"}
TENSORBOARD_PATH=${TENSORBOARD_PATH:-"/mnt/cluster/aiak-training-llm/tensorboard-log/llama2-70b-tp4pp4"}
GPUS_PER_NODE=8
# Change for multinode config
MASTER_ADDR=${MASTER_ADDR:-"localhost"}
MASTER_PORT=${MASTER_PORT:-"6000"}
NNODES=${WORLD_SIZE:-"1"}
NODE_RANK=${RANK:-"0"}
DISTRIBUTED_ARGS=(
--nproc_per_node $GPUS_PER_NODE
--nnodes $NNODES
--node_rank $NODE_RANK
--master_addr $MASTER_ADDR
--master_port $MASTER_PORT
)
# you can setup llama2-70b maunally
#MODEL_ARGS=(
# --model-name llama2
# --num-layers 80
# --hidden-size 8192
# --ffn-hidden-size 28672
# --num-attention-heads 64
# --position-embedding-type rope
# --normalization RMSNorm
# --swiglu
# --attention-dropout 0
# --hidden-dropout 0
# --disable-bias-linear
# --untie-embeddings-and-output-weights
# --group-query-attention
# --num-query-groups 8
#)
# or you can setup llama2-70b by using the following command
MODEL_ARGS=(
--model-name llama2-70b # options: llama2-7b, llama2-13b, llama2-70b
)
DATA_ARGS=(
--tokenizer-type HFTokenizer
--hf-tokenizer-path $TOKENIZER_PATH
--data-path $DATA_PATH
--split 949,50,1
)
TRAINING_ARGS=(
--training-phase pretrain # options: pretrain, sft
--seq-length 4096
--max-position-embeddings 4096
--init-method-std 0.01
--micro-batch-size 1
--global-batch-size 1024
--lr 0.0002
--min-lr 1.0e-5
--clip-grad 1.0
--weight-decay 0.01
--optimizer adam
--adam-beta1 0.9
--adam-beta2 0.95
--adam-eps 1e-05
--norm-epsilon 1e-6
--train-iters 500000
--lr-decay-iters 500000
--lr-decay-style cosine
--lr-warmup-fraction 0.002
--initial-loss-scale 65536
--fp16
--load $CHECKPOINT_PATH
--save $CHECKPOINT_PATH
--save-interval 5000
--eval-interval 1000
--eval-iters 10
#--ckpt-step 0
#--no-load-optim
#--no-load-rng
)
MODEL_PARALLEL_ARGS=(
--tensor-model-parallel-size 4
--pipeline-model-parallel-size 4
--use-distributed-optimizer
--overlap-grad-reduce
--overlap-param-gather
--distributed-backend nccl
--sequence-parallel
#--tp-comm-overlap # require mpi envrionment
)
LOGGING_ARGS=(
--log-interval 1
--tensorboard-dir ${TENSORBOARD_PATH}
--log-timers-to-tensorboard
)
if [ -n "${WANDB_API_KEY}" ]; then
LOGGING_ARGS+=(
--wandb-project ${WANDB_PROJECT}
--wandb-exp-name ${WANDB_NAME}
)
fi
PYTHONPATH=$MEGATRON_PATH:$AIAK_TRAINING_PATH:$PYTHONPATH \
torchrun ${DISTRIBUTED_ARGS[@]} \
$AIAK_TRAINING_PATH/aiak_training_llm/train.py \
${MODEL_ARGS[@]} \
${DATA_ARGS[@]} \
${TRAINING_ARGS[@]} \
${MODEL_PARALLEL_ARGS[@]} \
${LOGGING_ARGS[@]}
SFT训练
数据集配置和处理
在指令微调场景中,常见的有两种对话数据集组织形式,一种是类alpaca
格式,一种是类sharegpt
格式。目前 AIAK 主要支持类alpaca
格式数据集,且数据集文件需是JSON
格式。
alpaca
格式数据集举例:
[
{
"instruction": "用户指令",
"input": "用户输入的问题",
"output": "模型输出的答案"
}
]
- 定义数据集配置文件
由于不同数据集的字段标签可能不同,为了 AIAK 能正确解析和读取数据集内容,AIAK 期望用户提供一个配置文件,该文件用于描述对话数据集的格式信息。
这里为了方便用户进行自定义配置,AIAK 在镜像内提供了一个默认的配置文件模板,详见:/workspace/AIAK-Training-LLM/configs/sft_dataset_config.json
。当用户使用自定义数据集训练时,用户可以基于该文件添加自定义数据集的名称,并指定对应的格式和字段名称。具体配置方式如下。
配置文件格式:
AIAK遵循了开源社区的常见实现方法,如 https://github.com/hiyouga/LLaMA-Factory/blob/main/data/README.md
。
具体格式规范:
{
"custom_dataset_name(用户可自定义)": {
"format": "数据集格式(可选,取值范围: alpaca,默认: alpaca)",
"columns(可选,用户如未指定则按照如下默认值配置,用户如若指定则会严格按照指定的字段项进行解析,不会使用任何默认值)": {
"prompt": "数据集中代表提示词的字段名称(用户可自定义,默认: instruction)",
"query": "数据集中代表输入的字段名称(用户可自定义,默认: input)",
"response": "数据集中代表回答的字段名称(用户可自定义,默认: output)",
"system": "数据集中代表系统提示的字段名称(用户可自定义,默认: None)",
"history": "用于多轮对话,数据集中代表历史对话的字段名称(用户可自定义,默认: None)",
}
},
...
}
使用默认配置:
AIAK 在默认的配置文件/workspace/AIAK-Training-LLM/configs/sft_dataset_config.json
中内置了default
格式数据集配置,当用户数据集与default
配置相同时,也可以直接使用该格式;
{
"default": {
"format": "alpaca",
"columns": {
"prompt": "instruction",
"query": "input",
"response": "output"
}
}
}
添加自定义配置:
假设训练数据集名称命名为 custom_dataset_name
,同时文件内容形式如下(注:数据集的每条样本,都应该包含相同的字段项,部分字段项内容可以为空):
[
{
"instruction": "用户指令",
"input": "用户输入的问题",
"output": "模型输出的答案",
"system": "系统提示词",
"history": [
["第一轮对话用户输入的问题", "第一轮对话模型输出的答案"],
["第二轮对话用户输入的问题", "第二轮对话模型输出的答案"]
]
}
]
用户在配置文件中添加的配置内容即:
"custom_dataset_name": {
"format": "alpaca",
"columns": {
"prompt": "instruction",
"query": "input",
"response": "output",
"system": "system",
"history": "history"
}
}
- 训练参数中指定数据集及配置文件
-
步骤1:指定数据集路径(与预训练相同):
- 方式一:通过
--data-path
指定数据集路径 (也可指定多个数据集,同时分配采样权重),并通过--split
指定训练/验证/测试数据集划分比例,AIAKexamples/
中的示例均为--split 100,0,0
, 表示全部数据用于训练。 - 方式二:通过
--train-data-path
、--valid-data-path
、--test-data-path
分别指定训练、验证、测试数据集路径,同样的,每个参数也可以指定多个数据集,并分配采样权重。
- 方式一:通过
-
步骤2:指定配置文件路径:
- 通过
--sft-dataset-config
指定数据集配置文件路径;如果用户没有指定该参数,AIAK 将会尝试读取路径configs/sft_dataset_config.json
中的配置文件用于数据集解析;
- 通过
-
步骤3:指定数据集名称(需要在
--sft-dataset-config
中定义):- 当用户通过
--data-path
+--split
指定数据集时,可以通过--sft-dataset
指定数据集名称,AIAK 会根据该名称在配置文件中查找对应的数据集格式信息进行解析; - 当用户通过
--train-data-path
、--valid-data-path
、--test-data-path
指定数据集时,可以通过--sft-train-dataset
、--sft-valid-dataset
、--sft-test-dataset
指定数据集名称,参数互相对应;
- 当用户通过
-
其他说明:
- 当采用多数据集混合训练时:
--sft-dataset
或--sft-*-dataset
的数据集名称顺序需和--data-path
或--*-data-path
指定的数据集文件一一对应,系统会按照顺序进行对应解析。另外,如果每个数据集的格式完全相同,也可以仅传递一个数据集名称,系统会按照指定的数据集名称对应的格式解析所有数据集文件。 - 如果用户没有通过
--sft—dataset
或--sft-*-dataset
指定数据集名称,系统将默认使用configs/sft_dataset_config.json
中的default
项作为训练数据集的默认格式进行解析。
- 当采用多数据集混合训练时:
- 数据集预处理
对于 SFT 训练数据,AIAK 既支持离线数据预处理,也支持在线数据预处理。 离线数据预处理,会提前将数据集进行格式解析、重组、和tokenized 转换,并持久化到磁盘中,后续训练时直接从存储中读取;而在线数据预处理,则会在运行时阶段进行数据格式解析和 tokenizer 转换等操作。
- 如果 SFT 数据集规模较少,采用【在线数据预处理】即可;
- 如果 SFT 数据集规模较大,为了减少训练之前的数据处理时间,可考虑【在线预处理+Streaming读取】的训练方式、或者提前进行数据【离线预处理】,再启动训练任务;
采用在线数据预处理:
- 用户在训练参数中通过
--data-path
指定到具体的 json 文件。启动训练后,系统会读取该路径的文件,并进行数据格式解析和 tokenizer 转换处理。 -
具体包括:
- 支持训练前进行全量数据转换处理(默认方式);
- 支持 streaming 功能,流式数据读取和转换(通过
--sft-data-streaming
启用);
采用离线数据预处理:
用户可以参考examples/{model}/finetuning
目录下的示例脚本进行离线数据预处理。当前数据处理完成后,会生成一个目录,该目录下包括了处理后的数据集信息。
训练启动时:
- 需通过
--data-path
指定该目录(非具体文件),系统会直接从该目录中读取数据集信息; - 需增加
--is-tokenized-data
参数,标明数据集已经进行了 tokenization 处理; streaming
功能依然支持,用户可以按需开启;
离线数据预处理脚本举例:
#!/bin/bash
MEGATRON_PATH=${MEGATRON_PATH:-"/workspace/AIAK-Megatron"}
AIAK_TRAINING_PATH=${AIAK_TRAINING_PATH:-"/workspace/AIAK-Training-LLM"}
TOKENIZER_PATH=${TOKENIZER_PATH:-"/mnt/cluster/leoli/qwen/Qwen-7B-HF"}
input_data=/mnt/cluster/aiak-training-llm/dataset/sft_aplaca_zh_data.json
output_path=/mnt/cluster/aiak-training-llm/qwen/sft_aplaca_zh_tokenized
PYTHONPATH=$MEGATRON_PATH:$AIAK_TRAINING_PATH:$PYTHONPATH \
python ${AIAK_TRAINING_PATH}/tools/data_preprocess/preprocess_sft_data.py \
--input ${input_data} \
--output ${output_path} \
--seq-length 2048 \
--chat-template qwen \
--tokenizer-type HFTokenizer \
--hf-tokenizer-path $TOKENIZER_PATH \
--workers 50 \
--split 100,0,0
# --packing-sft-data \
# --train-on-prompt \
# --eod-mask-loss \
# --sft-dataset-config /workspace/AIAK-Training-LLM/configs/sft_dataset_config.json \
# --sft-dataset custom_dataset \
关键参数说明:
--seq-length
表示最大数据长度,如果超出该长度,系统会进行截断处理;--chat-template
指定对话模板,需和期望的训练参数一致;--split
指定train
/valid
/test
切分比例提前将数据集进行划分,后续训练参数中仍需指定--split
,以避免参数校验错误,但实际不会再次划分数据;--packing-sft-data
开启数据 packing,系统会尽量多的将多个样本 packing 到一起,且最大长度不超过--seq-length
;如果不足--seq-length
,这里不会进行 padding 补齐;
- 关于数据
Packing
训练说明:
数据 packing
既支持在线数据处理过程中拼接,也支持在离线数据处理过程中进行拼接。
由于开启数据 packing 训练时,会将多条数据拼接到一起,而拼接后样本集大小无法提前判断,如果用户需要根据拼接后的数据集大小来设置 --train-iters
迭代步数等相关参数,用户可以:
1)采用离线数据处理,并开启数据 packing 操作(具体参数见上述工具描述),数据处理内部会尽量多的将数据拼接到指定的最大长度,同时会输出数据集大小相关日志,用户可根据此信息来决定后续训练的迭代步数。
输出日志片段举例:
原数据集包含5310
个样本,拼接完成后则仅有672
个样本,其中根据--split
切分后的训练数据集包括全部的 672
个样本;
...
...
[INFO | aiak_training_llm.data.sft_dataset]: >>> Loading dataset /mnt/cluster/qwen_data/demo.json(5310 samples) with default config ...
Converting dataset to unified format (num_proc=50): 100%|███████████████████████████| 5310/5310 [00:00<00:00, 23158.97 examples/s]
Converting dataset to tokenized data (num_proc=50): 100%|█████████████████████████████| 5310/5310 [00:45<00:00, 115.96 examples/s]
[INFO | aiak_training_llm.data.sft_dataset]: >>> The number of samples have been changed from 5310 to 672 after preprocess.
...
...
>>> Saved preprocessed dataset to /mnt/cluster/qwen_data/demo_tokenized_packing with total samples: 672
>>> train samples: 672
>>> valid samples: 0
>>> test samples: 0
...
2)无论数据是否进行离线预处理,训练参数中都需要增加--packing-sft-data
参数,便于系统正确执行后续的处理逻辑;
Checkpoint及格式转换
与预训练相同。
启动模型 SFT 训练
用户可以参考**/workspace/AIAK-Training-LLM/examples/{model}/finetuning**
目录下的示例脚本进行 SFT 训练。
参数配置说明:新增参数具体见aiak_training_llm/train/arguments.py
文件。其中关键参数:
--training-phase
:指定sft
;--chat-template
:用于指定对话模板,当前主要支持模型官方的模板;--sft-num-preprocess-workers
:指定数据集转换处理的 worker 数目;--sft-dataset-config
:指定数据集配置文件路径;不指定,会读取系统默认路径的配置文件;--sft-dataset
:指定数据集名称,该名称需在上述配置文件中定义;不指定,会尝试读取配置文件中的default配置项用于解析数据;--data-cache-path
:非新增参数,可选,用于指定数据 cache 路径--sft-data-streaming
:开启 streaming 数据读取;--train-on-prompt
:开启后,prompt 部分的数据也会参与 loss 计算,默认是仅计算答案部分的 loss;--eod-mask-loss
:非新增参数,可选,开启后会将eostoken 进行 loss mask操作,不会参与 loss 计算;用户根据实际需求判断是否开启;--is-tokenized-data
:新增参数,可选,指定数据集已经进行了 tokenization 处理;--packing-sft-data
:新增参数,可选,开启后,系统会尽量多的将多个样本 packing 到一起,该功能可以和离线数据处理工具结合使用;
Qwen 14B SFT 训练示例
#! /bin/bash
MEGATRON_PATH=${MEGATRON_PATH:-"/workspace/AIAK-Megatron"}
AIAK_TRAINING_PATH=${AIAK_TRAINING_PATH:-"/workspace/AIAK-Training-LLM"}
DATA_PATH=${DATA_PATH:-"/mnt/cluster/aiak-training-llm/dataset/sft_aplaca_zh_data.json"}
#DATA_PATH=${DATA_PATH:-"/mnt/cluster/aiak-training-llm/qwen/sft_aplaca_zh_tokenized"}
DATA_CACHE_PATH=${DATA_CACHE_PATH:-"/mnt/cluster/aiak-training-llm/dataset/sft_aplaca_zh_data_cache"}
DATASET_CONFIG_PATH=${DATASET_CONFIG_PATH:-"/workspace/AIAK-Training-LLM/configs/sft_dataset_config.json"}
TOKENIZER_PATH=${TOKENIZER_PATH:-"/mnt/cluster/aiak-training-llm/qwen/Qwen-14B-HF"}
CHECKPOINT_PATH=${CHECKPOINT_PATH:-"/mnt/cluster/aiak-training-llm/qwen/Qwen_14B_mcore_tp1pp2"}
TENSORBOARD_PATH=${TENSORBOARD_PATH:-"/mnt/cluster/aiak-training-llm/qwen/tensorboard-log/qwen-14b-sft"}
GPUS_PER_NODE=8
# Change for multinode config
MASTER_ADDR=${MASTER_ADDR:-"localhost"}
MASTER_PORT=${MASTER_PORT:-"6000"}
NNODES=${WORLD_SIZE:-"1"}
NODE_RANK=${RANK:-"0"}
DISTRIBUTED_ARGS=(
--nproc_per_node $GPUS_PER_NODE
--nnodes $NNODES
--node_rank $NODE_RANK
--master_addr $MASTER_ADDR
--master_port $MASTER_PORT
)
MODEL_ARGS=(
--model-name qwen-14b # qwen1 options: qwen-1.8b, qwen-7b, qwen-14b, qwen-72b
)
DATA_ARGS=(
--tokenizer-type HFTokenizer
--hf-tokenizer-path $TOKENIZER_PATH
--data-path $DATA_PATH
--split 100,0,0
)
SFT_ARGS=(
--chat-template qwen
--sft-num-preprocess-workers 16
--no-check-for-nan-in-loss-and-grad
#--is-tokenized-data
#--packing-sft-data
#--sft-data-streaming
#--train-on-prompt
#--eod-mask-loss
#--sft-dataset-config $DATASET_CONFIG_PATH
#--sft-dataset sft_aplaca_zh_data # defined in --sft-dataset-config
#--data-cache-path $DATA_CACHE_PATH
)
TRAINING_ARGS=(
--training-phase sft # options: pretrain, sft
--seq-length 2048
--max-position-embeddings 8192
--init-method-std 0.006
--micro-batch-size 1
--global-batch-size 128
--lr 1.0e-5
--min-lr 1.0e-6
--clip-grad 1.0
--weight-decay 0.1
--optimizer adam
--adam-beta1 0.9
--adam-beta2 0.95
--adam-eps 1e-08
--norm-epsilon 1e-6
--train-iters 5000
--lr-decay-iters 5000
--lr-decay-style cosine
--lr-warmup-fraction 0.002
--bf16
--load $CHECKPOINT_PATH
--save $CHECKPOINT_PATH
--save-interval 500
--eval-interval 100
--eval-iters 10
#--ckpt-step 0
#--no-load-optim
#--no-load-rng
#--num-workers 8
)
MODEL_PARALLEL_ARGS=(
--tensor-model-parallel-size 1
--pipeline-model-parallel-size 2
--use-distributed-optimizer
--overlap-grad-reduce
--overlap-param-gather
--distributed-backend nccl
#--sequence-parallel
)
LOGGING_ARGS=(
--log-interval 1
--tensorboard-dir ${TENSORBOARD_PATH}
--log-timers-to-tensorboard
)
if [ -n "${WANDB_API_KEY}" ]; then
LOGGING_ARGS+=(
--wandb-project ${WANDB_PROJECT}
--wandb-exp-name ${WANDB_NAME}
)
fi
PYTHONPATH=$MEGATRON_PATH:$AIAK_TRAINING_PATH:$PYTHONPATH \
torchrun ${DISTRIBUTED_ARGS[@]} \
$AIAK_TRAINING_PATH/aiak_training_llm/train.py \
${MODEL_ARGS[@]} \
${DATA_ARGS[@]} \
${TRAINING_ARGS[@]} \
${SFT_ARGS[@]} \
${MODEL_PARALLEL_ARGS[@]} \
${LOGGING_ARGS[@]}