基础任务四 XTuner微调个人小助手认知
0 认识微调
在为预训练大语言模型增加额外知识时,一般有两种方法:
- 外部:添加外部知识,运用 RAG 等技术;
- 内部:改变内部权重微调(fine-tuning)是一种基于
通过微调,使得已在大规模数据上预训练的模型向下游任务数据实现迁移学习,将模型中一些层的权重参数进行微调,以适应新的数据集或任务。
0.1 Fine-tune 的两种范式
在 LLM 的下游应用中,经常会用到两种微调模式:增量预训练 和 指令跟随 。
增量预训练
- 增量预训练是一种在已有预训练模型(比如:InternLM基座模型)的基础上,利用特定领域的数据进行进一步训练的方法。
- 它的目的是在保持模型原有能力的同时,注入新的领域知识,进一步优化现有的预训练模型,从而提升模型在特定领域任务中的表现(比如:InternLM垂类基座模型)。
- 增量预训练模型能够接受少量的新数据进行更新并适应新的任务,而不需要重新训练整个模型,这种方式可以很好地利用现有的预训练模型的知识,并在新数据上获得更好的性能。
指令跟随
- 指令跟随是指让模型根据用户输入的指令来执行相应的操作。
- 模型通过对大量自然语言指令和相应操作的数据进行训练,学习如何将指令分解为具体的子任务,并选择合适的模块来执行这些任务(比如:InternLM垂类对话模型)。
0.2 常见微调技术
大语言模型参数规模巨大,full fine-tuning 依赖巨大的计算资源且有可能导致对微调数据集过拟合,损害泛化能力。因此,我们需要考虑哪些参数对我们模型的影响较大,通过微调一部分参数,在降低计算消耗的同时提高微调效果,即参数高效微调 PEFT, parameter-efficient fine tuning。LoRA 和 QLoRA 是两种有代表性的技术。
**LoRA(Low-Rank Adaptation)**是一种使用低精度权重对大型预训练语言模型进行微调的技术。
- 它的核心思想是在不改变原有模型权重的情况下,通过添加少量新参数来进行微调。
- 这种方法降低了模型的存储需求,也降低了计算成本,实现了对大模型的快速适应,同时保持了模型性能。
- 然而,由于使用了低精度权重,LoRA的一个潜在的缺点是在微调过程中可能会丢失一些原始模型的高阶特征信息,因此可能会降低模型的准确性。
QLoRA(Quantized LoRA)微调技术是对LoRA的一种改进,它通过引入高精度权重和可学习的低秩适配器来提高模型的准确性。
- 并且在LoRA的基础上,引入了量化技术。通过将预训练模型量化为int4格式,可以进一步减少微调过程中的计算量。
- 同时也可以减少模型的存储空间,这对于在资源有限的设备上运行模型非常有用。最终,可以使我们在消费级的显卡上进行模型的微调训练。
1 微调工具 XTuner
1.1 认识高效微调框架 XTuner
XTuner 是一个书生浦语研发的高效微调框架。
XTuner 可适配多种生态
- 多种微调算法:多种微调,偏好对其算法,覆盖各类应用场景
- 多种开原生态:支持 HuggingFace,ModelScope 模型或数据集
- 自动优化加速:支持千亿参数百万上下文训练,开发者不需关注复杂的优化和计算加速细节
XTuner 适配多种硬件
- 训练方案覆盖 NVIDIA20 系以上所有显卡
- 最低只需8GB即可微调 7B模型
1.2 XTuner 具有出色的优化效果
综合优化表现超过或比肩主流的 LLama-Factory 框架等,
- 在 Dense 和 MoE 下均进行了显存优化;
- 在超大模型和超长序列全量微调场景下也进行了优化;
- 为模型 MoE 训练通信进行了优化。
1.3 XTuner 零显存浪费的偏好对其方案
XTuner 提供了零显存浪费的偏好对其方案。在训练 DPO或reward model 过程中,在拼接偏好配对进入一个训练批次时,大量的 GPU内存因为 padding token 被浪费。XTuner 选择将将偏好对拼接成一条序列,实现零显存,即 GPU memory 的浪费。
而且,XTuner 这种 Pack preferences pairs into one sequence 的策略,相比传统的 concatenate preference pairs into one Batch 策略,在训练上也更加高效。因为后者需要将 preference pairs 补齐到相同长度,会添加额外的 padding token,造成显存浪费。
2 微调工具 XTuner 实战:准备工作
参考:XTuner微调前置基础
2.1 环境准备
启动好 30% A100 开发机后,现将 InternLM 的 Tutorial 仓库资料 clone 的开发机中
mkdir -p /root/InternLM/Tutorial
git clone -b camp3 https://github.com/InternLM/Tutorial /root/InternLM/Tutorial
然后,可以创建虚拟环境并安装必要的依赖
# 创建虚拟环境
conda create -n xtuner0121 python=3.10 -y
# 激活虚拟环境(注意:后续的所有操作都需要在这个虚拟环境中进行)
conda activate xtuner0121
# 安装一些必要的库
conda install pytorch==2.1.2 torchvision==0.16.2 torchaudio==2.1.2 pytorch-cuda=12.1 -c pytorch -c nvidia -y
# 安装其他依赖
pip install transformers==4.39.3
pip install streamlit==1.36.0
2.2 安装 XTuner
虚拟环境准备好后,可以准备安装 XTuner。手相,将安装 XTuner 需要的源码准备好
# 创建一个目录,用来存放源代码
mkdir -p /root/InternLM/code
cd /root/InternLM/code
git clone -b v0.1.21 https://github.com/InternLM/XTuner /root/InternLM/code/XTuner
然后,进入刚准备好的源码目录,执行 XTuner 的安装
# 进入到源码目录
cd /root/InternLM/code/XTuner
conda activate xtuner0121
# 执行安装
pip install -e '.[deepspeed]'
安装完成后,我们可以用以下指令验证安装结果,并熟悉一下 XTuner 的使用方法:
xtuner version
xtuner help
2.3 模型准备
在学习中,我们将使用 InternLM 推出的1.8B的小模型。在 InternStudio 开发机上运行时,可以不用通过 HuggingFace、OpenXLab 或者 Modelscope 下载模型,InternStudio 开发机中预先准备了模型的本地文件。
首先,为开发机中预先准备的模型创建 symlink/symbolic 符号链接,不仅节省空间且便于管理。
# 创建一个目录,用来存放微调的所有资料,后续的所有操作都在该路径中进行
mkdir -p /root/InternLM/XTuner
cd /root/InternLM/XTuner
mkdir -p Shanghai_AI_Laboratory
ln -s /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b Shanghai_AI_Laboratory/internlm2-chat-1_8b
上述操作,将创建 Shanghai_AI_Laboratory/internlm2-chat-1_8b
为一个符号链接,指向 /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b
位置。我们在访问前者时,实际是在访问后者位置。这种符号链接可以避免复制任何数据,直接利用现有模型文件进行后续微调操作,节省存储空间的同时简化文件管理。
然后,我们可以利用 tree
工具开观察目录结构,
apt-get install -y tree
tree -l
├── Shanghai_AI_Laboratory
│ └── internlm2-chat-1_8b -> /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b
│ ├── README.md
│ ├── config.json
│ ├── configuration.json
│ ├── configuration_internlm2.py
│ ├── generation_config.json
│ ├── model-00001-of-00002.safetensors
│ ├── model-00002-of-00002.safetensors
│ ├── model.safetensors.index.json
│ ├── modeling_internlm2.py
│ ├── special_tokens_map.json
│ ├── tokenization_internlm2.py
│ ├── tokenization_internlm2_fast.py
│ ├── tokenizer.model
│ └── tokenizer_config.json
可见, Shanghai_AI_Laboratory/internlm2-chat-1_8b
是一个 /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b
位置的符号链接。
3 微调工具 XTuner 实战:微调个人小助手认知
3.1 模型对话:微调前
我们启用 streamlit 脚本,开启一个基于 Web UI 的 LLM internlm2-chat-1_8b
conda activate xtuner0121
streamlit run /root/InternLM/Tutorial/tools/xtuner_streamlit_demo.py
发现微调前模型的回答较为通用,且缺乏部分知识:
3.2 模型微调:指令跟随微调
下面,我们对模型进行微调,让模型明白它是笔者的个人助手。
指令跟随
- 指令跟随是指让模型根据用户输入的指令来执行相应的操作。
- 模型通过对大量自然语言指令和相应操作的数据进行训练,学习如何将指令分解为具体的子任务,并选择合适的模块来执行这些任务(比如:InternLM垂类对话模型)。
3.2.1 指令跟随:数据准备
指令跟随微调尽管不同于一般增量预训练微调方法,不依赖传统的训练范式,但也依赖于大量的微调数据。要让模型能明白自己是一个个人助手,我们需要在指令跟随数据集中加入大量此类数据。我们准备数据集文件:datas/assistant.json
,其中包含很多对话数据。
cd /root/InternLM/XTuner
mkdir -p datas
touch datas/assistant.json
我们制作一个脚本,用以生成对话数据,
cd /root/InternLM/XTuner
touch xtuner_generate_assistant.py
脚本内容为:
import json
# 设置用户的名字
name = '主人'
# 设置需要重复添加的数据次数
n = 8000
# 初始化数据
data = [
{"conversation": [{"input": "请介绍一下你自己", "output": "我是{}的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦,嘿嘿~".format(name)}]},
{"conversation": [{"input": "你在实战营做什么", "output": "我在这里帮助{}完成XTuner微调个人小助手的任务,嘿嘿".format(name)}]}
]
# 通过循环,将初始化的对话数据重复添加到data列表中
for i in range(n):
data.append(data[0])
data.append(data[1])
# 将data列表中的数据写入到'datas/assistant.json'文件中
with open('datas/assistant.json', 'w', encoding='utf-8') as f:
# 使用json.dump方法将数据以JSON格式写入文件
# ensure_ascii=False 确保中文字符正常显示
# indent=4 使得文件内容格式化,便于阅读
json.dump(data, f, ensure_ascii=False, indent=4)
然后就可以执行脚本来生成数据:
cd /root/InternLM/XTuner
conda activate xtuner0121
python xtuner_generate_assistant.py
3.2.2 指令跟随:准备配置文件
准备好模型和数据后,在微调前还需要基于微调方案来确认最匹配的配置文件,减少我们对配置文件的修改量。
配置文件其实是一种用于定义和控制模型训练和测试过程中各个方面的参数和设置的工具。
3.2.2.1 查看支持的配置文件
XTuner 支持多种配置文件,可用下面的命令查看
conda activate xtuner0121
xtuner list-cfg -p internlm2
xtuner list-cfg 命令用于列出内置的所有配置文件。参数 -p 或 --pattern 表示模式匹配,后面跟着的内容将会在所有的配置文件里进行模糊匹配搜索,然后返回最有可能得内容。比如我们这里微调的是书生·浦语的模型,我们就可以匹配搜索 internlm2。
支持的配置文件返回了多种配置结果:
==========================CONFIGS===========================
PATTERN: internlm2
-------------------------------
internlm2_1_8b_full_alpaca_e3
internlm2_1_8b_full_custom_pretrain_e1
internlm2_1_8b_qlora_alpaca_e3
internlm2_20b_full_custom_pretrain_e1
internlm2_20b_full_finetune_custom_dataset_e1
internlm2_20b_qlora_alpaca_e3
internlm2_20b_qlora_arxiv_gentitle_e3
internlm2_20b_qlora_code_alpaca_e3
internlm2_20b_qlora_colorist_e5
internlm2_20b_qlora_lawyer_e3
internlm2_20b_qlora_msagent_react_e3_gpu8
internlm2_20b_qlora_oasst1_512_e3
internlm2_20b_qlora_oasst1_e3
internlm2_20b_qlora_sql_e3
internlm2_7b_full_custom_pretrain_e1
internlm2_7b_full_finetune_custom_dataset_e1
internlm2_7b_full_finetune_custom_dataset_e1_sequence_parallel_4
internlm2_7b_qlora_alpaca_e3
internlm2_7b_qlora_arxiv_gentitle_e3
internlm2_7b_qlora_code_alpaca_e3
internlm2_7b_qlora_colorist_e5
internlm2_7b_qlora_json_e3
internlm2_7b_qlora_lawyer_e3
internlm2_7b_qlora_msagent_react_e3_gpu8
internlm2_7b_qlora_oasst1_512_e3
internlm2_7b_qlora_oasst1_e3
internlm2_7b_qlora_sql_e3
internlm2_7b_w_internevo_dataset
internlm2_7b_w_tokenized_dataset
internlm2_7b_w_untokenized_dataset
internlm2_chat_1_8b_dpo_full
internlm2_chat_1_8b_dpo_full_varlenattn
internlm2_chat_1_8b_dpo_full_varlenattn_jsonl_dataset
internlm2_chat_1_8b_full_alpaca_e3
internlm2_chat_1_8b_orpo_full
internlm2_chat_1_8b_orpo_full_varlenattn
internlm2_chat_1_8b_orpo_full_varlenattn_jsonl_dataset
internlm2_chat_1_8b_qlora_alpaca_e3
internlm2_chat_1_8b_qlora_custom_sft_e1
internlm2_chat_1_8b_reward_full_ultrafeedback
internlm2_chat_1_8b_reward_full_varlenattn_jsonl_dataset
internlm2_chat_1_8b_reward_full_varlenattn_ultrafeedback
internlm2_chat_1_8b_reward_qlora_varlenattn_ultrafeedback
internlm2_chat_20b_full_finetune_custom_dataset_e1
internlm2_chat_20b_qlora_alpaca_e3
internlm2_chat_20b_qlora_code_alpaca_e3
internlm2_chat_20b_qlora_custom_sft_e1
internlm2_chat_20b_qlora_lawyer_e3
internlm2_chat_20b_qlora_oasst1_512_e3
internlm2_chat_20b_qlora_oasst1_e3
internlm2_chat_7b_dpo_qlora_varlenattn
internlm2_chat_7b_full_finetune_custom_dataset_e1
internlm2_chat_7b_orpo_qlora_varlenattn_ultrafeedback_e5
internlm2_chat_7b_qlora_alpaca_e3
internlm2_chat_7b_qlora_code_alpaca_e3
internlm2_chat_7b_qlora_custom_sft_e1
internlm2_chat_7b_qlora_lawyer_e3
internlm2_chat_7b_qlora_oasst1_512_e3
internlm2_chat_7b_qlora_oasst1_e3
llava_internlm2_chat_1_8b_clip_vit_large_p14_336_e1_gpu8_pretrain
llava_internlm2_chat_1_8b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune
llava_internlm2_chat_20b_clip_vit_large_p14_336_e1_gpu8_finetune
llava_internlm2_chat_20b_clip_vit_large_p14_336_e1_gpu8_pretrain
llava_internlm2_chat_20b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune
llava_internlm2_chat_7b_clip_vit_large_p14_336_e1_gpu8_finetune
llava_internlm2_chat_7b_clip_vit_large_p14_336_e1_gpu8_pretrain
llava_internlm2_chat_7b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune
=============================================================
配置文件名解释:
以 internlm2_1_8b_full_custom_pretrain_e1 和 internlm2_chat_1_8b_qlora_alpaca_e3 举例:
配置文件 internlm2_1_8b_full_custom_pretrain_e1 | 配置文件 internlm2_chat_1_8b_qlora_alpaca_e3 | 说明 |
---|---|---|
internlm2_1_8b | internlm2_chat_1_8b | 模型名称 |
full | qlora | 使用的算法 |
custom_pretrain | alpaca | 数据集名称 |
e1 | e3 | 把数据集跑几次 |
3.2.2.2 复制一个预设的配置文件
我们需要对internlm2-chat-1_8b
模型进行指令微调,匹配的一个配置文件是 internlm2_chat_1_8b_qlora_alpaca_e3
。
cd /root/InternLM/XTuner
conda activate xtuner0121
xtuner copy-cfg internlm2_chat_1_8b_qlora_alpaca_e3 .
xtuner copy-cfg 命令用于复制一个内置的配置文件。该命令需要两个参数:CONFIG 代表需要复制的配置文件名称,SAVE_PATH 代表复制的目标路径。在我们的输入的这个命令中,我们的 CONFIG 对应的是上面搜索到的 internlm2_chat_1_8b_qlora_alpaca_e3 ,而 SAVE_PATH 则是当前目录 .。
3.2.2.3 修改配置文件
完成配置文件的复制后,需要对配置文件进行一定的调整,从而满足训练需求。配置文件写成为 python 文件。
XTuner 配置文件一般有五个部分
-
PART 1 Settings:涵盖了模型基本设置,如预训练模型的选择、数据集信息和训练过程中的一些基本参数(如批大小、学习率等)。
-
PART 2 Model & Tokenizer:指定了用于训练的模型和分词器的具体类型及其配置,包括预训练模型的路径和是否启用特定功能(如可变长度注意力),这是模型训练的核心组成部分。
-
PART 3 Dataset & Dataloader:描述了数据处理的细节,包括如何加载数据集、预处理步骤、批处理大小等,确保了模型能够接收到正确格式和质量的数据。
-
PART 4 Scheduler & Optimizer:配置了优化过程中的关键参数,如学习率调度策略和优化器的选择,这些是影响模型训练效果和速度的重要因素。
-
PART 5 Runtime:定义了训练过程中的额外设置,如日志记录、模型保存策略和自定义钩子等,以支持训练流程的监控、调试和结果的保存。
- 一般来说我们需要更改的部分其实只包括前三部分,而且修改的主要原因是我们修改了配置文件中规定的模型、数据集。
- 后两部分都是 XTuner 官方我们优化好的东西,一般而言只有在魔改的情况下才需要进行修改。
基于文档,我们对配置文件进行修改:
- PART 1:我们不再需要在 HuggingFace 上下载模型,直接更换模型以及数据集的开发机本地路径即可;
- PART 3:我们的指令跟随微调数据集是 JSON 格式的数据,且对话内容已经是 input 和 output 的数据对形式,不需要进行格式转换。
- 我们也可以对一些额外的参数进行调整:
参数名 | 解释 |
---|---|
data_path | 数据路径或 HuggingFace 仓库名 |
max_length | 单条数据最大 Token 数, 超过则截断 |
pack_to_max_length | 是否将多条短数据拼接到 max_length, 提高 GPU 利用率 |
accumulative_counts | 梯度累积, 每多少次 backward 更新一次参数 |
sequence_parallel_size | 并行序列处理的大小, 用于模型训练时的序列并行 |
batch_size | 每个设备上的批量大小 |
dataloader_num_workers | 数据加载器中工作进程的数量 |
max_epochs | 训练的最大轮数 |
optim_type | 优化器类型, 例如 AdamW |
Ir | 学习率 |
betas | 优化器中的 beta 参数, 控制动量和平方梯度的移动平均 |
weight_decay | 权重衰减系数, 用于正则化和避免过拟合 |
max_norm | 梯度裁剪的最大范数, 用于防止梯度爆炸 |
warmup_ratio | 预热的比例, 学习率在这个比例的训练过程中线性增加到初始学习率 |
save_steps | 保存模型的步数间隔 |
save_total_limit | 保存的模型总数限制, 超过限制时删除旧的模型文件 |
prompt_template | 模板提示, 用于定义生成文本的格式或结构 |
… | … |
如果想充分利用显卡资源,可以将 max_length 和 batch_size 这两个参数调大。
3.2.3 开始微调
可以使用以下命令启动微调:
cd /root/InternLM/XTuner
conda activate xtuner0121
xtuner train ./internlm2_chat_1_8b_qlora_alpaca_e3_copy.py
xtuner train 命令用于启动模型微调进程。该命令需要一个参数:CONFIG 用于指定微调配置文件。这里我们使用修改好的配置文件internlm2_chat_1_8b_qlora_alpaca_e3_copy.py。训练过程中产生的所有文件,包括日志、配置文件、检查点文件、微调后的模型等,默认保存在 work_dirs 目录下,我们也可以通过添加 --work-dir 指定特定的文件保存位置。
3.2.4 微调后模型格式转换
我们得到的微调后的模型依然是 pytorch 训练出来的模型权重文件,我们需要将其转换为目前通用的 HuggingFace 格式文件,可以用以下命令一键转换:
cd /root/InternLM/XTuner
conda activate xtuner0121
# 先获取最后保存的一个pth文件
pth_file=`ls -t ./work_dirs/internlm2_chat_1_8b_qlora_alpaca_e3_copy/*.pth | head -n 1`
export MKL_SERVICE_FORCE_INTEL=1
export MKL_THREADING_LAYER=GNU
xtuner convert pth_to_hf ./internlm2_chat_1_8b_qlora_alpaca_e3_copy.py ${pth_file} ./hf
xtuner convert pth_to_hf
命令用于进行模型格式转换。该命令需要三个参数:CONFIG
表示微调的配置文件,PATH_TO_PTH_MODEL
表示微调的模型权重文件路径,即要转换的模型权重,SAVE_PATH_TO_HF_MODEL
表示转换后的 HuggingFace 格式文件的保存路径。
除此之外,我们还可以添加额外参数:
参数名 | 解释 |
---|---|
–fp32 | 代表以fp32的精度开启, 假如不输入则默认为fp16 |
–max-shard-size {GB} | 代表每个权重文件最大的大小(默认为2GB) |
模型被转换为 HuggingFace 中常用的 .bin 格式文件,文件成功被转化为 HuggingFace 格式了。
此时,hf 文件夹即为我们平时所理解的所谓 “LoRA 模型文件”
可以简单理解:LoRA 模型文件 = Adapter
3.2.5 模型合并
LoRA 或者 QLoRA 方法微调出来的模型是一个额外的层(Adapter),而不是一个完整的模型。而训练完的这个层最终还是要与原模型进行合并才能被正常的使用。
全量微调的模型(full fine-tuning)直接修改原模型的权重,并没有微调一个新的 Adapter,不需要进行模型整合的。
在 XTuner 中提供了一键合并的命令 xtuner convert merge
,指定原模型的路径、训练好的 Adapter 层的(模型格式转换后的)路径、最终保存的路径。三个路径即可实现快速的模型合并。
xtuner convert merge
命令用于合并原始 LLM 模型和 Adapter 模型。该命令需要三个参数:LLM
表示原模型路径,ADAPTER
表示 Adapter 层的路径,SAVE_PATH
表示合并后的模型最终的保存路径。
也可以添加额外参数:
参数名 | 解释 |
---|---|
–max-shard-size {GB} | 代表每个权重文件最大的大小(默认为2GB) |
–device {device_name} | 这里指的就是device的名称,可选择的有cuda、cpu和auto, 默认为cuda即使用 gpu进行运算 |
–is-clip | 这个参数主要用于确定模型是不是CLIP模型,假如是的话就要加上,不是就不需要添加 |
cd /root/InternLM/XTuner
conda activate xtuner0121
export MKL_SERVICE_FORCE_INTEL=1
export MKL_THREADING_LAYER=GNU
xtuner convert merge /root/InternLM/XTuner/Shanghai_AI_Laboratory/internlm2-chat-1_8b ./hf ./merged --max-shard-size 2GB
模型合并完成后,可以发现合并后的模型和原模型文件夹很相似,包括分词器、权重文件、配置信息等等。
3.3 模型对话:微调后
微调完成后,首先将 xtuner_streamlit_demo.py
脚本中的模型路径改为微调后的模型的路径,直接修改脚本文件中第十八行
# 直接修改脚本文件第18行
#model_name_or_path = "/root/InternLM/XTuner/Shanghai_AI_Laboratory/internlm2-chat-1_8b"
model_name_or_path = "/root/InternLM/XTuner/merged"