XTuner 微调 LLM:1.8B、多模态、Agent | shmaur

shmaur
2024-06-08
-
-

作业

训练自己的小助手认知

将自我认知的模型上传到 OpenXLab,并将应用部署到 OpenXLab

复现多模态微调

目标

完成 XTuner 微调

Finetune 简介

两种Finetune范式

增量预训练微调

使用场景:让基座模型学习到一些新知识,如某个垂类领域的常识训练数据:文章、书籍、代码等

 

指令跟随微调

使用场景:让模型学会对话模板,根据人类指令进行对话训练数据:高质量的对话、问答数据

一条数据的一生

 

XTuner

功能亮点

适配多种生态

多种微调策略与算法,覆盖各类 SFT场景
·多种微调算法

·适配多种开源生态

支持加载 HuggingFace、Modelscope 模型或数据集

自动优化加速开发者无需关注复杂的是存优化与计算加速细节

适配多种硬件

训练方案覆盖 NVIDIA 20 系以上所有显卡

最低只需 8GB 显存即可微调 78 模型

 

快速上手

安装

pip install xtuner

挑选配置模板

xtuner list-cfg -p internlm_20b

一键训练

xtuner train internlm_20b_qlora_oasst1_512_e3

Config 命名规则

模型名internlm_20b  无 chat 代表是基座模型
使用算法qlora
数据集oasst1
数据长度512
Epoche3、epoch3

常用超参

data_path                        数据路径或 HuggingFace 仓库名

max_length                    单条数据最大 Token 数,超过则截断

pack_to_max_length   是否将多条短数据拼接到 max_length,提高 GPU 利用率

accumulative_counts   梯度累积,每多少次 backward 更新一次参数

evaluation_inputs          训练过程中,会根据给定的问题进行推理便于观测训练状态

evaluation_freq               Evaluation 的评测间隔 iter 数

 

XTuner 两种加速方式

Flash Attention : 将Attention计算并行化,避免了计算过程中的 Attention Score NxN 的显存占用(训练过程中的N都比较大),这个是自动开启的

DeepSpeed ZeRO:ZeRO 优化,通过将训练过程中的参数、梯度和优化器状态切片保存,能够在多的GPU训练时显著节省显存

除了将训练中间状态切片外,DeepSpeed训练时使用了FP16的权重,相较于Pytarch的AMP训练,在单GPU上也能大幅节省显存。

 

InternLM2 1.8B 模型

InternLM2-1.8B 提供了三个版本的开源模型,大家可以按需选择
InternLM2-1.8B:具有高质量和高适应灵活性的基础模型,为下游深度适应提供了良好的起点。
InternLM2-Chat-1.8B-SFT:在 InternLM2-1.8B 上进行监督微调(SFT)后得到的对话模型。
InternLM2-Chat-1.8B:通过在线 RLHF 在 InternLM2-Chat-1.8B-SFT 之上进一步对齐。InternLM2-Chat-1.88 表现出更好的指令跟随、聊天体验和函数调用,推荐下游应用程序使用。(模型大小仅为3.78GB)
在 FP16 精度模式下,InternLM2-1.88 仅需 4GB 显存的笔记本显卡即可顺畅运行。拥有 8GB 显存的消费级显卡,即可轻松进行 1.88 模型的微调工作。如此低的硬件门槛非常适合初学者使用,以深入了解和掌握大模型的全链路。

 

多模态LLM 原理简介

 

LLaVA 方案

 

实战 - 训练小助手认知

创建开发机

安装 XTuner

studio-conda xtuner0.1.17
# 激活环境
conda activate xtuner0.1.17

# 创建版本文件夹并进入,以跟随本教程
mkdir -p /root/xtuner0117 && cd /root/xtuner0117

# 拉取 0.1.17 的版本源码
git clone -b v0.1.17  https://github.com/InternLM/xtuner

# 进入源码目录
cd /root/xtuner0117/xtuner

# 从源码安装 XTuner
pip install -e '.[all]'

 

数据集准备

# 前半部分是创建一个文件夹,后半部分是进入该文件夹。
mkdir -p /root/ft && cd /root/ft

# 在ft这个文件夹里再创建一个存放数据的data文件夹
mkdir -p /root/ft/data && cd /root/ft/data

# 创建 `generate_data.py` 文件
touch /root/ft/data/generate_data.py
import json

# 设置用户的名字
name = '金博士'
# 设置需要重复添加的数据次数
n =  10000

# 初始化OpenAI格式的数据结构
data = [
    {
        "messages": [
            {
                "role": "user",
                "content": "请做一下自我介绍"
            },
            {
                "role": "assistant",
                "content": "我是{}的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦".format(name)
            }
        ]
    }
]

# 通过循环,将初始化的对话数据重复添加到data列表中
for i in range(n):
    data.append(data[0])

# 将data列表中的数据写入到一个名为'personal_assistant.json'的文件中
with open('personal_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/ft/data

# 运行代码
python /root/ft/data/generate_data.py

打印文件结构树

import os
import argparse

def print_dir_tree(startpath, prefix=''):
    """递归地打印目录树结构。"""
    contents = [os.path.join(startpath, d) for d in os.listdir(startpath)]
    directories = [d for d in contents if os.path.isdir(d)]
    files = [f for f in contents if os.path.isfile(f)]

    if files:
        for f in files:
            print(prefix + '|-- ' + os.path.basename(f))
    if directories:
        for d in directories:
            print(prefix + '|-- ' + os.path.basename(d) + '/')
            print_dir_tree(d, prefix=prefix + '    ')

def main():
    parser = argparse.ArgumentParser(description='打印目录树结构')
    parser.add_argument('folder', type=str, help='要打印的文件夹路径')

    args = parser.parse_args()

    print('|-- ' + os.path.basename(args.folder) + '/')
    print_dir_tree(args.folder, '    ')

if __name__ == "__main__":
    main()

 

运行结果

 

准备模型

# 创建目标文件夹,确保它存在。
# -p选项意味着如果上级目录不存在也会一并创建,且如果目标文件夹已存在则不会报错。
mkdir -p /root/ft/model

# 复制内容到目标文件夹。-r选项表示递归复制整个文件夹。
cp -r /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b/* /root/ft/model/

# 删除/root/ft/model目录
rm -rf /root/ft/model

# 创建符号链接
ln -s /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b 
/root/ft/model

 

XTuner配置文件介绍

PART 1 Settings:涵盖了模型基本设置,如预训练模型的选择、数据集信息和训练过程中的一些基本参数(如批大小、学习率等)。

PART 2 Model & Tokenizer:指定了用于训练的模型和分词器的具体类型及其配置,包括预训练模型的路径和是否启用特定功能(如可变长度注意力),这是模型训练的核心组成部分。

PART 3 Dataset & Dataloader:描述了数据处理的细节,包括如何加载数据集、预处理步骤、批处理大小等,确保了模型能够接收到正确格式和质量的数据。

PART 4 Scheduler & Optimizer:配置了优化过程中的关键参数,如学习率调度策略和优化器的选择,这些是影响模型训练效果和速度的重要因素。

PART 5 Runtime:定义了训练过程中的额外设置,如日志记录、模型保存策略和自定义钩子等,以支持训练流程的监控、调试和结果的保存。

 

 PART 1 的部分,由于我们不再需要在 Huggingface 上自动下载模型,因此我们先要更换模型的路径以及数据集的路径为我们本地的路径。

# 修改模型地址(在第27行的位置)
- pretrained_model_name_or_path = 'internlm/internlm2-1_8b'
+ pretrained_model_name_or_path = '/root/ft/model'

# 修改数据集地址为本地的json文件地址(在第31行的位置)
- alpaca_en_path = 'tatsu-lab/alpaca'
+ alpaca_en_path = '/root/ft/data/personal_assistant.json'

# 修改max_length来降低显存的消耗(在第33行的位置)
- max_length = 2048
+ max_length = 1024

        我们还可以对一些重要的参数进行调整,包括学习率(lr)、训练的轮数(max_epochs)等等。由于我们这次只是一个简单的让模型知道自己的身份弟位,因此我们的训练轮数以及单条数据最大的 Token 数(max_length)都可以不用那么大。

# 减少训练的轮数(在第44行的位置)
- max_epochs = 3
+ max_epochs = 2

# 增加保存权重文件的总数(在第54行的位置)
- save_total_limit = 2
+ save_total_limit = 3

        为了训练过程中能够实时观察到模型的变化情况,XTuner 也是贴心的推出了一个 evaluation_inputs 的参数来让我们能够设置多个问题来确保模型在训练过程中的变化是朝着我们想要的方向前进的。比如说我们这里是希望在问出 “请你介绍一下你自己” 或者说 “你是谁” 的时候,模型能够给你的回复是 “我是XXX的小助手...” 这样的回复

# 修改每多少轮进行一次评估(在第57行的位置)
- evaluation_freq = 500
+ evaluation_freq = 300

# 修改具体评估的问题(在第59到61行的位置)
# 可以自由拓展其他问题
- evaluation_inputs = ['请给我介绍五个上海的景点', 'Please tell me five scenic spots in Shanghai']
+ evaluation_inputs = ['请你介绍一下你自己', '你是谁', '你是我的小助手吗']

        由于我们的数据集不再是原本的 aplaca 数据集,因此我们也要进入 PART 3 的部分对相关的内容进行修改。包括说我们数据集输入的不是一个文件夹而是一个单纯的 json 文件以及我们的数据集格式要求改为我们最通用的 OpenAI 数据集格式。

# 把 OpenAI 格式的 map_fn 载入进来(在第15行的位置)
- from xtuner.dataset.map_fns import alpaca_map_fn, template_map_fn_factory
+ from xtuner.dataset.map_fns import openai_map_fn, template_map_fn_factory

# 将原本是 alpaca 的地址改为是 json 文件的地址(在第102行的位置)
- dataset=dict(type=load_dataset, path=alpaca_en_path),
+ dataset=dict(type=load_dataset, path='json', data_files=dict(train=alpaca_en_path)),

# 将 dataset_map_fn 改为通用的 OpenAI 数据集格式(在第105行的位置)
- dataset_map_fn=alpaca_map_fn,
+ dataset_map_fn=openai_map_fn,

 

常用超参

参数名解释
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
lr学习率
betas优化器中的 beta 参数,控制动量和平方梯度的移动平均
weight_decay权重衰减系数,用于正则化和避免过拟合
max_norm梯度裁剪的最大范数,用于防止梯度爆炸
warmup_ratio预热的比例,学习率在这个比例的训练过程中线性增加到初始学习率
save_steps保存模型的步数间隔
save_total_limit保存的模型总数限制,超过限制时删除旧的模型文件
prompt_template模板提示,用于定义生成文本的格式或结构

模型训练

# 指定保存路径
xtuner train /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py --work-dir /root/ft/train

可以选择使用deepspeed 来加速训练

DeepSpeed特别适用于需要巨大计算资源的大型模型和数据集。

在DeepSpeed中,zero 代表“ZeRO”(Zero Redundancy Optimizer),是一种旨在降低训练大型模型所需内存占用的优化器。

ZeRO 分为几个不同的级别,主要包括:

deepspeed_zero1:这是ZeRO的基本版本,它优化了模型参数的存储,使得每个GPU只存储一部分参数,从而减少内存的使用。

deepspeed_zero2:在deepspeed_zero1的基础上,deepspeed_zero2进一步优化了梯度和优化器状态的存储。它将这些信息也分散到不同的GPU上,进一步降低了单个GPU的内存需求。

deepspeed_zero3:这是目前最高级的优化等级,它不仅包括了deepspeed_zero1和deepspeed_zero2的优化,还进一步减少了激活函数的内存占用。这通过在需要时重新计算激活(而不是存储它们)来实现,从而实现了对大型模型极其内存效率的训练。

选择哪种deepspeed类型主要取决于你的具体需求,包括模型的大小、可用的硬件资源(特别是GPU内存)以及训练的效率需求。一般来说:

如果你的模型较小,或者内存资源充足,可能不需要使用最高级别的优化。

如果你正在尝试训练非常大的模型,或者你的硬件资源有限,使用deepspeed_zero2或deepspeed_zero3可能更适,因为它们可以显著降低内存占用,允许更大模型的训练。

选择时也要考虑到实现的复杂性和运行时的开销,更高级的优化可能需要更复杂的设置,并可能增加一些计算开销。

训练完成

 

        通过两者的对比我们其实就可以很清楚的看到,在300轮的时候模型已经学会了在我问 “你是谁” 或者说 “请你介绍一下我自己” 的时候回答 “我是剑锋大佬的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦”。

        但是两者的不同是在询问 “你是我的小助手” 的这个问题上,300轮的时候是回答正确的,回答了 “是” ,但是在600轮的时候回答的还是 “我是剑锋大佬的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦” 这一段话。这表明模型在第一批次第600轮的时候已经出现严重的过拟合(即模型丢失了基础的能力,只会成为某一句话的复读机)现象了,到后面的话无论我们再问什么,得到的结果也就只能是回答这一句话了,模型已经不会再说别的话了。因此假如以通用能力的角度选择最合适的权重文件的话我们可能会选择前面的权重文件进行后续的模型转化及整合工作。

       假如我们想要解决这个问题,其实可以通过以下两个方式解决:

       减少保存权重文件的间隔并增加权重文件保存的上限:这个方法实际上就是通过降低间隔结合评估问题的结果,从而找到最优的权重文。我们可以每隔100个批次来看什么时候模型已经学到了这部分知识但是还保留着基本的常识,什么时候已经过拟合严重只会说一句话了。但是由于再配置文件有设置权重文件保存数量的上限,因此同时将这个上限加大也是非常必要的。
增加常规的对话数据集从而稀释原本数据的占比:这个方法其实就是希望我们正常用对话数据集做指令微调的同时还加上一部分的数据集来让模型既能够学到正常对话,但是在遇到特定问题时进行特殊化处理。比如说我在一万条正常的对话数据里混入两千条和小助手相关的数据集,这样模型同样可以在不丢失对话能力的前提下学到剑锋大佬的小助手这句话。这种其实是比较常见的处理方式,大家可以自己动手尝试实践一下。

模型继续训练

# 模型续训
xtuner train /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py --work-dir /root/ft/train --resume /root/ft/train/iter_600.pth

 

模型转换

# 创建一个保存转换后 Huggingface 格式的文件夹
mkdir -p /root/ft/huggingface

# 模型转换
# xtuner convert pth_to_hf ${配置文件地址} ${权重文件地址} ${转换后模型保存地址}
xtuner convert pth_to_hf /root/ft/train/internlm2_1_8b_qlora_alpaca_e3_copy.py /root/ft/train/iter_768.pth /root/ft/huggingface

转换完成后,可以看到模型被转换为 Huggingface 中常用的 .bin 格式文件,这就代表着文件成功被转化为 Huggingface 格式了。

参数名解释
--fp32代表以fp32的精度开启,假如不输入则默认为fp16
--max-shard-size {GB}代表每个权重文件最大的大小(默认为2GB)

假如有特定的需要,我们可以在上面的转换指令后进行添加。由于本次测试的模型文件较小,并且已经验证过拟合,故没有添加。假如加上的话应该是这样的:

xtuner convert pth_to_hf /root/ft/train/internlm2_1_8b_qlora_alpaca_e3_copy.py /root/ft/train/iter_768.pth /root/ft/huggingface --fp32 --max-shard-size 2GB

转换结果

 

 

模型整合

对于 LoRA 或者 QLoRA 微调出来的模型其实并不是一个完整的模型,而是一个额外的层(adapter)

 

全量微调的模型(full)不需要进行整合,因为全量微调修改的是原模型的权重而非微调一个新的 adapter ,因此是不需要进行模型整合的。

# 创建一个名为 final_model 的文件夹存储整合后的模型文件
mkdir -p /root/ft/final_model

# 解决一下线程冲突的 Bug 
export MKL_SERVICE_FORCE_INTEL=1

# 进行模型整合
# xtuner convert merge  ${NAME_OR_PATH_TO_LLM} ${NAME_OR_PATH_TO_ADAPTER} ${SAVE_PATH} 
xtuner convert merge /root/ft/model /root/ft/huggingface /root/ft/final_model

 

参数名解释
--max-shard-size {GB}代表每个权重文件最大的大小(默认为2GB)
--device {device_name}这里指的就是device的名称,可选择的有cuda、cpu和auto,默认为cuda即使用gpu进行运算
--is-clip这个参数主要用于确定模型是不是CLIP模型,假如是的话就要加上,不是就不需要添加

 

CLIP(Contrastive Language–Image Pre-training)模型是 OpenAI 开发的一种预训练模型,它能够理解图像和描述它们的文本之间的关系。CLIP 通过在大规模数据集上学习图像和对应文本之间的对应关系,从而实现了对图像内容的理解和分类,甚至能够根据文本提示生成图像。 在模型整合完成后,我们就可以看到 final_model 文件夹里生成了和原模型文件夹非常近似的内容,包括了分词器、权重文件、配置信息等等。当我们整合完成后,我们就能够正常的调用这个模型进行对话测试了。

 

整合后目录

(作业)训练小助手认知

 

     对于 xtuner chat 这个指令而言,还有很多其他的参数可以进行设置的,包括:

启动参数解释
--system指定SYSTEM文本,用于在对话中插入特定的系统级信息
--system-template指定SYSTEM模板,用于自定义系统信息的模板
--bits指定LLM运行时使用的位数,决定了处理数据时的精度
--bot-name设置bot的名称,用于在对话或其他交互中识别bot
--with-plugins指定在运行时要使用的插件列表,用于扩展或增强功能
--no-streamer关闭流式传输模式,对于需要一次性处理全部数据的场景
--lagent启用lagent,用于特定的运行时环境或优化
--command-stop-word设置命令的停止词,当遇到这些词时停止解析命令
--answer-stop-word设置回答的停止词,当生成回答时遇到这些词则停止
--offload-folder指定存放模型权重的文件夹,用于加载或卸载模型权重
--max-new-tokens设置生成文本时允许的最大token数量,控制输出长度
--temperature设置生成文本的温度值,较高的值会使生成的文本更多样,较低的值会使文本更确定
--top-k设置保留用于顶k筛选的最高概率词汇标记数,影响生成文本的多样性
--top-p设置累计概率阈值,仅保留概率累加高于top-p的最小标记集,影响生成文本的连贯性
--seed设置随机种子,用于生成可重现的文本内容

 

         除了这些参数以外其实还有一个非常重要的参数就是 --adapter ,这个参数主要的作用就是可以在转化后的 adapter 层与原模型整合之前来对该层进行测试。使用这个额外的参数对话的模型和整合后的模型几乎没有什么太多的区别,因此我们可以通过测试不同的权重文件生成的 adapter 来找到最优的 adapter 进行最终的模型整合工作。

# 使用 --adapter 参数与完整的模型进行对话
xtuner chat /root/ft/model --adapter /root/ft/huggingface --prompt-template internlm2_chat

 

复现多模态微调

运行失败 
直接运行也失败,暂时先这样

 

(作业)本地小助手认知训练

下载模型

通过 HuggingFace 下载模型

import os
# 下载模型
os.system('huggingface-cli download --resume-download internlm/internlm2-chat-1_8b --local-dir /home/shmaur/xtuner/model')

部署安装本地环境

 

本地环境主要为 XTuner 0.1.18

.conda create --name xtuner-env python=3.10 -y

conda activate xtuner-env

pip install -U xtuner

pip install -U huggingface_hub

pip install -U lagent

pip install -U datasets

pip install -U transformers

pip install -U SentencePiece

pip install -U accelerate

 

部署环境/安装
金博士小助手

在增加训练,增加空压机日常保养认知

训练前

 

训练中
训练后,成功。

 

将训练模型上传openxlab

现在 openxlab 创建模型仓库

添加模型仓库
添加令牌

 

本地安装git

# install git
sudo apt-get update
sudo apt-get install git

# install git lfs
sudo apt-get update
sudo apt-get install git-lfs

# use git install lfs
git lfs install

git config --global user.name "Username"  # 需要将 Username 替换成你在 OpenXLab 平台上的用户名

git config --global user.email "email@email.com"  # 配置邮箱

git clone https://code.openxlab.org.cn//username/reponame.git  # 克隆创建好得仓库

# LFS管理大文件	
git lfs track "*.bin"
git lfs track "*.model"

# 提交和正常git差不多
git add -A
git commit -m "upload model"
git push
push得时候需要输入 git 密钥,然后等待上传成功即可。

 

上传成功

 

 

大模型微调主要步骤

  1. 明确目标任务(角色扮演、数学、写作.. )
  2. 选择和测试基座模型(可以先选一个小模型进行验证)
  3. 数据准备(收集、清洗、预处理、标注、划分... )
  4. 设定微调策略(LoRA、QLORA)
  5. 设置超参(学习率、批量大小、训练轮数 )
  6. 模型初始化
  7. 开始微调训练
  8. 模型评估和调优(调整数据、策略、超参 )
  9. 模型性能测试
  10. 模型部署应用

 

“您的支持是我持续分享的动力”

微信收款码
微信
支付宝收款码
支付宝

目录关闭