使用自定义的指令微调数据集训练(LLM)#

XTuner 支持使用自定义数据集进行指令微调,为便于介绍,本节以 internlm2_chat_7b_qlora_custom_sft_e1.py 配置文件为基础进行介绍。

数据准备#

XTuner 采用 OpenAI SFT 数据集格式 作为统一的自定义数据集格式。每条数据除了 OpenAI 标准格式中的 role 字段和 content 字段外,XTuner 还额外扩充了一个 loss 字段,用于控制某轮 assistant 的输出不计算 loss。详细格式如下:

[{
    "messages": [
        { "role": "system", "content": "xxx."},
        { "role": "user", "content": "xxx." },
        { "role": "assistant", "content": "xxx."}
    ]
},
{
    "messages": [
        { "role": "system", "content": "xxx." },
        { "role": "user", "content": "xxx." },
        { "role": "assistant", "content": "xxx.", "loss": false},
        { "role": "user", "content": "xxx." },
        { "role": "assistant", "content": "xxx.", "loss": true}
    ]
}]

以上示例中,第一条数据对应一条单轮对话数据,第二条则对应一条多轮对话数据。默认情况下,每条数据中仅 “assistant” 部分的内容参与 loss 的计算。若想令某轮对话 “assistant” 部分的内容不参与 loss 计算,需要手动设置该数据 “loss” 字段的值为 false

训练#

Step 1, 导出 config#

xtuner/configs/custom_dataset/sft 目录下有所有 XTuner 支持的模型在自定义数据集下使用 QLora 算法训练的模板 config。可以通过 xtuner list-cfg -p custom_sft 命令来查看候选 config。下面以 internlm2_chat_7b_qlora_custom_sft_e1.py 为例展开介绍。

可以通过以下命令将 internlm2_chat_7b_qlora_custom_sft_e1.py 导出至当前目录下:

xtuner copy-cfg internlm2_chat_7b_qlora_custom_sft_e1 .

当前目录下会存在一个新 config internlm2_chat_7b_qlora_custom_sft_e1_copy.py

Step 2, 修改 config#

首先,需要修改数据集文件路径:

- data_files = ['/path/to/json/file.json']
+ data_files = ['/path/to/custom_sft1.json', '/path/to/custom_sft2.json', ...]

若期望使用某个目录下所有的 json 文件作为训练数据集,可做如下修改:

#######################################################################
#                          PART 1  Settings                           #
#######################################################################
# Data
- data_files = ['/path/to/json/file.json']
+ data_dir = '/dir/to/custom_sft'

#######################################################################
#                      PART 3  Dataset & Dataloader                   #
#######################################################################
train_dataset = dict(
-   dataset=dict(type=load_dataset, path='json', data_files=data_files),
+   dataset=dict(type=load_dataset, path='json', data_dir=data_dir),
    ...)

若期望使用 Lora 算法训练,可做如下修改:

#######################################################################
#                      PART 2  Model & Tokenizer                      #
#######################################################################
model = dict(
    type=SupervisedFinetune,
    use_varlen_attn=use_varlen_attn,
    llm=dict(
        type=AutoModelForCausalLM.from_pretrained,
        pretrained_model_name_or_path=pretrained_model_name_or_path,
        trust_remote_code=True,
        torch_dtype=torch.float16,
-       quantization_config=dict(
-           type=BitsAndBytesConfig,
-           load_in_4bit=True,
-           load_in_8bit=False,
-           llm_int8_threshold=6.0,
-           llm_int8_has_fp16_weight=False,
-           bnb_4bit_compute_dtype=torch.float16,
-           bnb_4bit_use_double_quant=True,
-           bnb_4bit_quant_type='nf4')
    ),
    lora=dict(
        type=LoraConfig,
        r=64,
        lora_alpha=16,
        lora_dropout=0.1,
        bias='none',
        task_type='CAUSAL_LM'))

若期望进行全量参数训练,可做如下修改:

#######################################################################
#                      PART 2  Model & Tokenizer                      #
#######################################################################
model = dict(
    type=SupervisedFinetune,
    use_varlen_attn=use_varlen_attn,
    llm=dict(
        type=AutoModelForCausalLM.from_pretrained,
        pretrained_model_name_or_path=pretrained_model_name_or_path,
        trust_remote_code=True,
        torch_dtype=torch.float16,
-       quantization_config=dict(
-           type=BitsAndBytesConfig,
-           load_in_4bit=True,
-           load_in_8bit=False,
-           llm_int8_threshold=6.0,
-           llm_int8_has_fp16_weight=False,
-           bnb_4bit_compute_dtype=torch.float16,
-           bnb_4bit_use_double_quant=True,
-           bnb_4bit_quant_type='nf4')
    ),
-   lora=dict(
-       type=LoraConfig,
-       r=64,
-       lora_alpha=16,
-       lora_dropout=0.1,
-       bias='none',
-       task_type='CAUSAL_LM')
)

Step 3, 开始训练#

NPROC_PER_NODE=8 xtuner train internlm2_chat_7b_qlora_custom_sft_e1_copy.py --deepspeed deepspeed_zero1

训得模型将默认保存在 ./work_dirs/,用户可以通过命令 xtuner train --work-dir ${SAVE_PATH} 指定保存路径。

Step 4, 模型转换#

模型训练后会自动保存成 PTH 模型(例如 iter_2000.pth,如果使用了 DeepSpeed,则将会是一个文件夹),我们需要利用 xtuner convert pth_to_hf 将其转换为 HuggingFace 模型,以便于后续使用。具体命令为:

xtuner convert pth_to_hf ${FINETUNE_CFG} ${PTH_PATH} ${SAVE_PATH}
# 例如:xtuner convert pth_to_hf internlm2_chat_7b_qlora_custom_sft_e1_copy.py ./iter_2000.pth ./iter_2000_hf

对话#

用户可以利用 xtuner chat 实现与微调后的模型对话。如果使用的是 Lora 或 QLora 算法:

xtuner chat ${NAME_OR_PATH_TO_LLM} --adapter {NAME_OR_PATH_TO_ADAPTER} --prompt-template ${PROMPT_TEMPLATE} [optional arguments]
# 例如:xtuner chat internlm/internlm2-7b --adapter ./iter_2000_hf --prompt-template internlm2_chat

其中 ${PROMPT_TEMPLATE} 表示模型的对话模板,需要与训练用的 config 中的 prompt_template 字段保持一致,例如 internlm2_chat_7b_qlora_custom_sft_e1_copy.py 中的设置为:

prompt_template = PROMPT_TEMPLATE.internlm2_chat

如果进行的是全量参数的微调:

xtuner chat ${PATH_TO_LLM} --prompt-template ${PROMPT_TEMPLATE} [optional arguments]
# 例如:xtuner chat ./iter_2000_hf --prompt-template internlm2_chat

模型合并(可选)#

如果您使用了 LoRA / QLoRA 微调,则模型转换后将得到 adapter 参数,而并不包含原 LLM 参数。如果您期望获得合并后的模型权重(例如用于后续评测),那么可以利用 xtuner convert merge

(LLM) xtuner convert merge ${LLM} ${LLM_ADAPTER} ${SAVE_PATH}

评测#

推荐使用一站式平台 OpenCompass 来评测大语言模型,其目前已涵盖 50+ 数据集的约 30 万条题目。