绍兴网站快速排名优化,wordpress代码添加,监控系统网站开发,奎屯市网站书生浦语是上海人工智能实验室和商汤科技联合研发的一款大模型,很高兴能参与本次第二期训练营#xff0c;我也将会通过笔记博客的方式记录学习的过程与遇到的问题#xff0c;并为代码添加注释#xff0c;希望可以帮助到你们。 记得点赞哟(๑ゝω╹๑) XTuner 微调个人小助手… 书生·浦语是上海人工智能实验室和商汤科技联合研发的一款大模型,很高兴能参与本次第二期训练营我也将会通过笔记博客的方式记录学习的过程与遇到的问题并为代码添加注释希望可以帮助到你们。 记得点赞哟(๑ゝω╹๑) XTuner 微调个人小助手认知
在本节课中讲一步步带领大家体验如何利用 XTuner 完成个人小助手的微调
为了能够让大家更加快速的上手并看到微调前后对比的效果那我这里选用的就是上一期的课后作业用 QLoRA 的方式来微调一个自己的小助手我们可以通过下面两张图片来清楚的看到两者的对比。
微调前微调后
可以明显看到的是微调后的大模型真的能够被调整成我们想要的样子下面就让我们一步步的来实现这个有趣的过程吧
1 开发机准备
首先我们需要前往 InternStudio 中创建一个开发机进行使用。然后在进入界面后首先选择开发机。 首先打开 Intern Studio 界面点击 创建开发机 配置开发机系统。 之后我们填写 开发机名称 后点击 选择镜像 使用 Cuda11.7-conda 镜像然后在资源配置中使用 10% A100 * 1 的选项然后立即创建开发机器。 点击 进入开发机 选项。 最后我们点击 Terminal 进入终端界面即可开始操作 完成准备工作后我们就可以正式开始我们的微调之旅啦 2 快速上手
我们可以通过下面这张图来简单了解一下 XTuner 的运行原理。 环境安装假如我们想要用 XTuner 这款简单易上手的微调工具包来对模型进行微调的话那我们最最最先开始的第一步必然就是安装XTuner安装基础的工具是一切的前提只有安装了 XTuner 在我们本地后我们才能够去思考说具体怎么操作。 前期准备那在完成了安装后我们下一步就需要去明确我们自己的微调目标了。我们想要利用微调做一些什么事情呢那我为了做到这个事情我有哪些硬件的资源和数据呢假如我们有对于一件事情相关的数据集并且我们还有足够的算力资源那当然微调就是一件水到渠成的事情。就像 OpenAI 不就是如此吗但是对于普通的开发者而言在资源有限的情况下我们可能就需要考虑怎么采集数据用什么样的手段和方式来让模型有更好的效果。 启动微调在确定了自己的微调目标后我们就可以在 XTuner 的配置库中找到合适的配置文件并进行对应的修改。修改完成后即可一键启动训练训练好的模型也可以仅仅通过在终端输入一行指令来完成转换和部署工作
2.1 环境安装
首先我们需要先安装一个 XTuner 的源码到本地来方便后续的使用。
# 如果你是在 InternStudio 平台则从本地 clone 一个已有 pytorch 的环境
# pytorch 2.0.1 py3.10_cuda11.7_cudnn8.5.0_0studio-conda xtuner0.1.17
# 如果你是在其他平台
# conda create --name xtuner0.1.17 python3.10 -y# 激活环境
conda activate xtuner0.1.17
# 进入家目录 ~的意思是 “当前用户的home路径”
cd ~
# 创建版本文件夹并进入以跟随本教程
mkdir -p /root/xtuner0117 cd /root/xtuner0117# 拉取 0.1.17 的版本源码
git clone -b v0.1.17 https://github.com/InternLM/xtuner
# 无法访问github的用户请从 gitee 拉取:
# git clone -b v0.1.15 https://gitee.com/Internlm/xtuner# 进入源码目录
cd /root/xtuner0117/xtuner# 从源码安装 XTuner
pip install -e .[all]假如速度太慢可以 Ctrl C 退出后换成 pip install -e .[all] -i https://mirrors.aliyun.com/pypi/simple/ 假如在这一过程中没有出现任何的报错的话那也就意味着我们成功安装好支持 XTuner 所运行的环境啦。其实对于很多的初学者而言安装好环境意味着成功了一大半因此我们接下来就可以进入我们的第二步准备好我们需要的数据集、模型和配置文件
2.2 前期准备
2.2.1 数据集准备
为了让模型能够让模型认清自己的身份弟位知道在询问自己是谁的时候回复成我们想要的样子我们就需要通过在微调数据集中大量掺杂这部分的数据。
首先我们先创建一个文件夹来存放我们这次训练所需要的所有文件。
# 前半部分是创建一个文件夹后半部分是进入该文件夹。
mkdir -p /root/ft cd /root/ft# 在ft这个文件夹里再创建一个存放数据的data文件夹
mkdir -p /root/ft/data cd /root/ft/data之后我们可以在 data 目录下新建一个 generate_data.py 文件将以下代码复制进去然后运行该脚本即可生成数据集。假如想要加大剂量让他能够完完全全认识到你的身份那我们可以吧 n 的值调大一点。
# 创建 generate_data.py 文件
touch /root/ft/data/generate_data.py打开该 python 文件后将下面的内容复制进去。
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, encodingutf-8) as f:# 使用json.dump方法将数据以JSON格式写入文件# ensure_asciiFalse 确保中文字符正常显示# indent4 使得文件内容格式化便于阅读json.dump(data, f, ensure_asciiFalse, indent4)
并将文件 name 后面的内容修改为你的名称。比如说我是剑锋大佬的话就是
# 将对应的name进行修改在第4行的位置
- name 不要姜葱蒜大佬name 剑锋大佬修改完成后运行 generate_data.py 文件即可。
# 确保先进入该文件夹
cd /root/ft/data# 运行代码
python /root/ft/data/generate_data.py可以看到在data的路径下便生成了一个名为 personal_assistant.json 的文件这样我们最可用于微调的数据集就准备好啦里面就包含了 5000 条 input 和 output 的数据对。假如 我们认为 5000 条不够的话也可以调整文件中第6行 n 的值哦
|-- data/|-- personal_assistant.json|-- generate_data.py文件结构树代码
文件结构树代码如下所示使用方法为在终端调用该代码的同时在后方输入文件夹路径。
比如说我要打印 data 的文件结构树假设我的代码文件保存在 /root/tree.py 那我就要在终端输入 python /root/tree.py /root/ft/data
import os
import argparsedef 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, prefixprefix )def main():parser argparse.ArgumentParser(description打印目录树结构)parser.add_argument(folder, typestr, help要打印的文件夹路径)args parser.parse_args()print(|-- os.path.basename(args.folder) /)print_dir_tree(args.folder, )if __name__ __main__:main()除了我们自己通过脚本的数据集其实网上也有大量的开源数据集可以供我们进行使用。有些时候我们可以在开源数据集的基础上添加一些我们自己独有的数据集也可能会有很好的效果。 2.2.2 模型准备
在准备好了数据集后接下来我们就需要准备好我们的要用于微调的模型。由于本次课程显存方面的限制这里我们就使用 InternLM 最新推出的小模型 InterLM-chat-1.8B 来完成此次的微调演示。
对于在 InternStudio 上运行的小伙伴们可以不用通过 OpenXLab 或者 Modelscope 进行模型的下载。我们直接通过以下代码一键创建文件夹并将所有文件复制进去。
# 创建目标文件夹确保它存在。
# -p选项意味着如果上级目录不存在也会一并创建且如果目标文件夹已存在则不会报错。
mkdir -p /root/ft/model# 复制内容到目标文件夹。-r选项表示递归复制整个文件夹。
cp -r /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b/* /root/ft/model/那这个时候我们就可以看到在 model 文件夹下保存了模型的相关文件和内容了。
|-- model/|-- tokenizer.model|-- config.json|-- tokenization_internlm2.py|-- model-00002-of-00002.safetensors|-- tokenizer_config.json|-- model-00001-of-00002.safetensors|-- model.safetensors.index.json|-- configuration.json|-- special_tokens_map.json|-- modeling_internlm2.py|-- README.md|-- configuration_internlm2.py|-- generation_config.json|-- tokenization_internlm2_fast.py假如大家存储空间不足我们也可以通过以下代码一键通过符号链接的方式链接到模型文件这样既节省了空间也便于管理。
# 删除/root/ft/model目录
rm -rf /root/ft/model# 创建符号链接
ln -s /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b /root/ft/model执行上述操作后/root/ft/model 将直接成为一个符号链接这个链接指向 /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b 的位置。
这意味着当我们访问 /root/ft/model 时实际上就是在访问 /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b 目录下的内容。通过这种方式我们无需复制任何数据就可以直接利用现有的模型文件进行后续的微调操作从而节省存储空间并简化文件管理。
在该情况下的文件结构如下所示可以看到和上面的区别在于多了一些软链接相关的文件。
|-- model/|-- tokenizer.model|-- config.json|-- .mdl|-- tokenization_internlm2.py|-- model-00002-of-00002.safetensors|-- tokenizer_config.json|-- model-00001-of-00002.safetensors|-- model.safetensors.index.json|-- configuration.json|-- .msc|-- special_tokens_map.json|-- .mv|-- modeling_internlm2.py|-- README.md|-- configuration_internlm2.py|-- generation_config.json|-- tokenization_internlm2_fast.py2.2.3 配置文件选择
在准备好了模型和数据集后我们就要根据我们选择的微调方法方法结合前面的信息来找到与我们最匹配的配置文件了从而减少我们对配置文件的修改量。
所谓配置文件config其实是一种用于定义和控制模型训练和测试过程中各个方面的参数和设置的工具。准备好的配置文件只要运行起来就代表着模型就开始训练或者微调了。
XTuner 提供多个开箱即用的配置文件用户可以通过下列命令查看 开箱即用意味着假如能够连接上 Huggingface 以及有足够的显存其实就可以直接运行这些配置文件XTuner就能够直接下载好这些模型和数据集然后开始进行微调 # 列出所有内置配置文件
# xtuner list-cfg# 假如我们想找到 internlm2-1.8b 模型里支持的配置文件
xtuner list-cfg -p internlm2_1_8b这里就用到了第一个 XTuner 的工具 list-cfg 对于这个工具而言可以选择不添加额外的参数就像上面的一样这样就会将所有的配置文件都打印出来。那同时也可以加上一个参数 -p 或 --pattern 后面输入的内容将会在所有的 config 文件里进行模糊匹配搜索然后返回最有可能得内容。我们可以用来搜索特定模型的配置文件比如例子中的 internlm2_1_8b ,也可以用来搜索像是微调方法 qlora 。 根据上面的定向搜索指令可以看到目前只有两个支持 internlm2-1.8B 的模型配置文件。 CONFIGS
PATTERN: internlm2_1_8b
-------------------------------
internlm2_1_8b_full_alpaca_e3
internlm2_1_8b_qlora_alpaca_e3配置文件名的解释
以 internlm2_1_8b_qlora_alpaca_e3 举例
模型名说明internlm2_1_8b模型名称qlora使用的算法alpaca数据集名称e3把数据集跑3次
虽然我们用的数据集并不是 alpaca 而是我们自己通过脚本制作的小助手数据集 但是由于我们是通过 QLoRA 的方式对 internlm-chat-1.8b 进行微调。而最相近的配置文件应该就是 internlm2_1_8b_qlora_alpaca_e3 因此我们可以选择拷贝这个配置文件到当前目录
# 创建一个存放 config 文件的文件夹
mkdir -p /root/ft/config# 使用 XTuner 中的 copy-cfg 功能将 config 文件复制到指定的位置
xtuner copy-cfg internlm2_1_8b_qlora_alpaca_e3 /root/ft/config这里我们就用到了 XTuner 工具箱中的第二个工具 copy-cfg 该工具有两个必须要填写的参数 {CONFIG_NAME} 和 {SAVE_PATH} 在我们的输入的这个指令中我们的 {CONFIG_NAME} 对应的是上面搜索到的 internlm2_1_8b_qlora_alpaca_e3 ,而 {SAVE_PATH} 则对应的是刚刚新建的 /root/ft/config。我们假如需要复制其他的配置文件只需要修改这两个参数即可实现。 输入后我们就能够看到在我们的 /root/ft/config 文件夹下有一个名为 internlm2_1_8b_qlora_alpaca_e3_copy.py 的文件了。 |-- config/|-- internlm2_1_8b_qlora_alpaca_e3_copy.py2.2.4 小结
完成以上内容后我就已经完成了所有的准备工作了。我们再来回顾一下我们做了哪些事情
我们首先是在 GitHub 上克隆了 XTuner 的源码并把相关的配套库也通过 pip 的方式进行了安装。然后我们根据自己想要做的事情利用脚本准备好了一份关于调教模型认识自己身份弟位的数据集。再然后我们根据自己的显存及任务情况确定了使用 InternLM-chat-1.8B 这个模型并且将其复制到我们的文件夹里。最后我们在 XTuner 已有的配置文件中根据微调方法、数据集和模型挑选出最合适的配置文件并复制到我们新建的文件夹中。
经过了以上的步骤后我们的 ft 文件夹里应该是这样的
|-- ft/|-- config/|-- internlm2_1_8b_qlora_alpaca_e3_copy.py|-- model/|-- tokenizer.model|-- config.json|-- tokenization_internlm2.py|-- model-00002-of-00002.safetensors|-- tokenizer_config.json|-- model-00001-of-00002.safetensors|-- model.safetensors.index.json|-- configuration.json|-- special_tokens_map.json|-- modeling_internlm2.py|-- README.md|-- configuration_internlm2.py|-- generation_config.json|-- tokenization_internlm2_fast.py|-- data/|-- personal_assistant.json|-- generate_data.py是不是感觉其实微调也不过如此事实上确实是这样的其实在微调的时候最重要的还是要自己准备一份高质量的数据集这个才是你能否真微调出效果最核心的利器。
微调也经常被戏称为是炼丹就是说你炼丹的时候你得思考好用什么样的材料、用多大的火候、烤多久的时间以及用什么丹炉去烧。这里的丹炉其实我们可以想象为 XTuner 只要丹炉的质量过得去炼丹的时候不会炸一般都是没问题的。但是假如炼丹的材料就是数据集本来就是垃圾那无论怎么炼微调参数的调整炼多久训练的轮数炼出来的东西还只能且只会是垃圾。只有说用了比较好的材料那么我们就可以考虑说要炼多久以及用什么办法去炼的问题。因此总的来说学会如何构建一份高质量的数据集是至关重要的。
假如想要了解更多关于数据集制作方面的内容可以加入书生.浦语的 RolePlay SIG 中里面会有各种大佬手把手教学教你如何制作一个自己喜欢角色的数据集出来。也期待更多大佬加入讲述自己制作数据集的想法和过程
2.3 配置文件修改
在选择了一个最匹配的配置文件并准备好其他内容后下面我们要做的事情就是根据我们自己的内容对该配置文件进行调整使其能够满足我们实际训练的要求。
配置文件介绍
假如我们真的打开配置文件后我们可以看到整体的配置文件分为五部分 PART 1 Settings涵盖了模型基本设置如预训练模型的选择、数据集信息和训练过程中的一些基本参数如批大小、学习率等。 PART 2 Model Tokenizer指定了用于训练的模型和分词器的具体类型及其配置包括预训练模型的路径和是否启用特定功能如可变长度注意力这是模型训练的核心组成部分。 PART 3 Dataset Dataloader描述了数据处理的细节包括如何加载数据集、预处理步骤、批处理大小等确保了模型能够接收到正确格式和质量的数据。 PART 4 Scheduler Optimizer配置了优化过程中的关键参数如学习率调度策略和优化器的选择这些是影响模型训练效果和速度的重要因素。 PART 5 Runtime定义了训练过程中的额外设置如日志记录、模型保存策略和自定义钩子等以支持训练流程的监控、调试和结果的保存。
一般来说我们需要更改的部分其实只包括前三部分而且修改的主要原因是我们修改了配置文件中规定的模型、数据集。后两部分都是 XTuner 官方帮我们优化好的东西一般而言只有在魔改的情况下才需要进行修改。下面我们将根据项目的要求一步步的进行修改和调整吧
通过折叠部分的修改内容如下可以直接将以下代码复制到 /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py 文件中先 Ctrl A 选中所有文件并删除后再将代码复制进去。
参数修改细节
首先在 PART 1 的部分由于我们不再需要在 Huggingface 上自动下载模型因此我们先要更换模型的路径以及数据集的路径为我们本地的路径。
# 修改模型地址在第27行的位置
- pretrained_model_name_or_path internlm/internlm2-1_8bpretrained_model_name_or_path /root/ft/model# 修改数据集地址为本地的json文件地址在第31行的位置
- alpaca_en_path tatsu-lab/alpacaalpaca_en_path /root/ft/data/personal_assistant.json除此之外我们还可以对一些重要的参数进行调整包括学习率lr、训练的轮数max_epochs等等。由于我们这次只是一个简单的让模型知道自己的身份弟位因此我们的训练轮数以及单条数据最大的 Token 数max_length都可以不用那么大。
# 修改max_length来降低显存的消耗在第33行的位置
- max_length 2048max_length 1024# 减少训练的轮数在第44行的位置
- max_epochs 3max_epochs 2# 增加保存权重文件的总数在第54行的位置
- save_total_limit 2save_total_limit 3另外为了训练过程中能够实时观察到模型的变化情况XTuner 也是贴心的推出了一个 evaluation_inputs 的参数来让我们能够设置多个问题来确保模型在训练过程中的变化是朝着我们想要的方向前进的。比如说我们这里是希望在问出 “请你介绍一下你自己” 或者说 “你是谁” 的时候模型能够给你的回复是 “我是XXX的小助手…” 这样的回复。因此我们也可以根据这个需求进行更改。
# 修改每多少轮进行一次评估在第57行的位置
- evaluation_freq 500evaluation_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_factoryfrom xtuner.dataset.map_fns import openai_map_fn, template_map_fn_factory# 将原本是 alpaca 的地址改为是 json 文件的地址在第102行的位置
- datasetdict(typeload_dataset, pathalpaca_en_path),datasetdict(typeload_dataset, pathjson, data_filesdict(trainalpaca_en_path)),# 将 dataset_map_fn 改为通用的 OpenAI 数据集格式在第105行的位置
- dataset_map_fnalpaca_map_fn,dataset_map_fnopenai_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优化器类型例如 AdamWlr学习率betas优化器中的 beta 参数控制动量和平方梯度的移动平均weight_decay权重衰减系数用于正则化和避免过拟合max_norm梯度裁剪的最大范数用于防止梯度爆炸warmup_ratio预热的比例学习率在这个比例的训练过程中线性增加到初始学习率save_steps保存模型的步数间隔save_total_limit保存的模型总数限制超过限制时删除旧的模型文件prompt_template模板提示用于定义生成文本的格式或结构…… 如果想把显卡的现存吃满充分利用显卡资源可以将 max_length 和 batch_size 这两个参数调大。 # 导入所需的库和模块
import torch
from datasets import load_dataset
from mmengine.dataset import DefaultSampler
from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook,LoggerHook, ParamSchedulerHook)
from mmengine.optim import AmpOptimWrapper, CosineAnnealingLR, LinearLR
from peft import LoraConfig
from torch.optim import AdamW
from transformers import (AutoModelForCausalLM, AutoTokenizer,BitsAndBytesConfig)from xtuner.dataset import process_hf_dataset
from xtuner.dataset.collate_fns import default_collate_fn
from xtuner.dataset.map_fns import openai_map_fn, template_map_fn_factory
from xtuner.engine.hooks import (DatasetInfoHook, EvaluateChatHook,VarlenAttnArgsToMessageHubHook)
from xtuner.engine.runner import TrainLoop
from xtuner.model import SupervisedFinetune
from xtuner.parallel.sequence import SequenceParallelSampler
from xtuner.utils import PROMPT_TEMPLATE, SYSTEM_TEMPLATE#######################################################################
# PART 1 设置部分 #
#######################################################################
# 模型
pretrained_model_name_or_path /root/ft/model
use_varlen_attn False # 是否使用可变长度注意力机制# 数据
alpaca_en_path /root/ft/data/personal_assistant.json
prompt_template PROMPT_TEMPLATE.default
max_length 1024
pack_to_max_length True# 并行设置
sequence_parallel_size 1 # 并行大小# 调度器和优化器
batch_size 1 # 每个设备的批量大小
accumulative_counts 16 # 累积更新次数
accumulative_counts * sequence_parallel_size # 考虑到并行
dataloader_num_workers 0 # 数据加载器的工作进程数
max_epochs 2 # 最大训练轮数
optim_type AdamW # 优化器类型
lr 2e-4 # 学习率
betas (0.9, 0.999) # AdamW 优化器的 betas 参数
weight_decay 0 # 权重衰减
max_norm 1 # 梯度裁剪的最大范数
warmup_ratio 0.03 # 学习率 warmup 比例# 保存
save_steps 300 # 每隔多少步保存一次模型
save_total_limit 3 # 最多保存的模型数量-1 表示不限制# 训练过程中的生成评估
evaluation_freq 300 # 每隔多少步进行一次生成评估
SYSTEM # 系统名称可选
evaluation_inputs [请你介绍一下你自己, 你是谁, 你是我的小助手吗] # 用于评估的输入文本#######################################################################
# PART 2 模型和分词器设置 #
#######################################################################
tokenizer dict(typeAutoTokenizer.from_pretrained, # 使用预训练模型加载分词器pretrained_model_name_or_pathpretrained_model_name_or_path, # 预训练模型路径trust_remote_codeTrue, # 是否信任远程代码padding_sideright # 填充位置
)model dict(typeSupervisedFinetune, # 模型类型use_varlen_attnuse_varlen_attn, # 是否使用可变长度注意力机制llmdict(typeAutoModelForCausalLM.from_pretrained, # 使用预训练模型加载语言模型pretrained_model_name_or_pathpretrained_model_name_or_path, # 预训练模型路径trust_remote_codeTrue, # 是否信任远程代码torch_dtypetorch.float16, # torch 张量数据类型quantization_configdict(typeBitsAndBytesConfig, # 使用 BitsAndBytesConfig 进行量化配置load_in_4bitTrue, # 是否以 4 位加载load_in_8bitFalse, # 是否以 8 位加载llm_int8_threshold6.0, # 阈值llm_int8_has_fp16_weightFalse, # 是否有 fp16 权重bnb_4bit_compute_dtypetorch.float16, # 4 位计算数据类型bnb_4bit_use_double_quantTrue, # 是否使用双量化bnb_4bit_quant_typenf4 # 量化类型)),loradict(typeLoraConfig, # Lora 配置r64, # 参数 rlora_alpha16, # 参数 alphalora_dropout0.1, # Dropout 概率biasnone, # 偏置设置task_typeCAUSAL_LM # 任务类型)
)#######################################################################
# PART 3 数据集和数据加载器设置 #
#######################################################################
alpaca_en dict(typeprocess_hf_dataset, # 数据集处理函数类型datasetdict(typeload_dataset, pathjson, data_filesdict(trainalpaca_en_path)), # 加载数据集tokenizertokenizer, # 分词器max_lengthmax_length, # 最大长度dataset_map_fnopenai_map_fn, # 数据集映射函数template_map_fndict(typetemplate_map_fn_factory, templateprompt_template), # 模板映射函数remove_unused_columnsTrue, # 是否删除未使用的列shuffle_before_packTrue, # 打包前是否进行洗牌pack_to_max_lengthpack_to_max_length, # 是否打包到最大长度use_varlen_attnuse_varlen_attn # 是否使用可变长度注意力机制
)sampler SequenceParallelSampler if sequence_parallel_size 1 else DefaultSampler # 根据并行大小选择采样器类型train_dataloader dict(batch_sizebatch_size, # 每个设备的批量大小num_workersdataloader_num_workers, # 数据加载器的工作进程数datasetalpaca_en, # 数据集samplerdict(typesampler, shuffleTrue), # 采样器collate_fndict(typedefault_collate_fn, use_varlen_attnuse_varlen_attn) # 数据整合函数
)#######################################################################
# PART 4 调度器和优化器设置 #
#######################################################################
# 优化器设置
optim_wrapper dict(typeAmpOptimWrapper, # 优化器包装器类型optimizerdict(typeoptim_type, lrlr, betasbetas, weight_decayweight_decay), # 优化器参数clip_graddict(max_normmax_norm, error_if_nonfiniteFalse), # 梯度裁剪设置accumulative_countsaccumulative_counts, # 累积更新次数loss_scaledynamic, # 损失尺度dtypefloat16 # 数据类型
)# 学习策略
param_scheduler [dict(typeLinearLR, # 线性学习率调度器start_factor1e-5, # 初始因子by_epochTrue, # 按轮数调整begin0, # 起始轮数endwarmup_ratio * max_epochs, # 结束轮数convert_to_iter_basedTrue), # 是否转换为基于迭代次数的调度器dict(typeCosineAnnealingLR, # 余弦退火学习率调度器eta_min0.0, # 最小学习率by_epochTrue, # 按轮数调整beginwarmup_ratio * max_epochs, # 开始轮数endmax_epochs, # 结束轮数convert_to_iter_basedTrue) # 是否转换为基于迭代次数的调度器
]# 训练、验证、测试设置
train_cfg dict(typeTrainLoop, max_epochsmax_epochs)#######################################################################
# PART 5 运行时设置 #
#######################################################################
# 自定义钩子
custom_hooks [dict(typeDatasetInfoHook, tokenizertokenizer), # 数据集信息钩子dict(typeEvaluateChatHook, # 评估对话钩子tokenizertokenizer, # 分词器every_n_itersevaluation_freq, # 每隔多少步评估一次evaluation_inputsevaluation_inputs, # 评估输入systemSYSTEM, # 系统名称prompt_templateprompt_template) # 提示模板
]if use_varlen_attn: # 如果使用可变长度注意力机制custom_hooks [dict(typeVarlenAttnArgsToMessageHubHook)] # 添加自定义钩子# 默认钩子配置
default_hooks dict(timerdict(typeIterTimerHook), # 记录每次迭代的时间loggerdict(typeLoggerHook, log_metric_by_epochFalse, interval10), # 日志记录器param_schedulerdict(typeParamSchedulerHook), # 参数调度器checkpointdict(typeCheckpointHook, # 检查点钩子by_epochFalse, # 是否按轮数保存检查点intervalsave_steps, # 保存间隔max_keep_ckptssave_total_limit), # 最多保存检查点数sampler_seeddict(typeDistSamplerSeedHook) # 分布式采样器种子设置
)# 环境配置
env_cfg dict(cudnn_benchmarkFalse, # 是否启用 cudnn 优化mp_cfgdict(mp_start_methodfork, opencv_num_threads0), # 多进程配置dist_cfgdict(backendnccl) # 分布式配置
)# 可视化器
visualizer None # 不使用可视化器# 日志级别
log_level INFO # 日志级别设置为 INFO# 从哪个检查点加载模型
load_from None # 不加载任何检查点# 是否从加载的检查点恢复训练
resume False # 不恢复训练# 随机性设置
randomness dict(seedNone, deterministicFalse) # 随机种子和是否确定性设置为默认值# 日志处理器
log_processor dict(by_epochFalse) # 不按轮数处理日志这一节我们讲述了微调过程中一些常见的需要调整的内容包括各种的路径、超参数、评估问题等等。完成了这部分的修改后我们就可以正式的开始我们下一阶段的旅程 XTuner 启动~
2.4 模型训练
2.4.1 常规训练
当我们准备好了配置文件好我们只需要将使用 xtuner train 指令即可开始训练。
我们可以通过添加 --work-dir 指定特定的文件保存位置比如说就保存在 /root/ft/train 路径下。假如不添加的话模型训练的过程文件将默认保存在 ./work_dirs/internlm2_1_8b_qlora_alpaca_e3_copy 的位置就比如说我是在 /root/ft/train 的路径下输入该指令那么我的文件保存的位置就是在 /root/ft/train/work_dirs/internlm2_1_8b_qlora_alpaca_e3_copy 的位置下。
# 指定保存路径
xtuner train /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py --work-dir /root/ft/train在输入训练完后的文件如下所示
|-- train/|-- internlm2_1_8b_qlora_alpaca_e3_copy.py|-- iter_600.pth|-- last_checkpoint|-- iter_768.pth|-- iter_300.pth|-- 20240406_203957/|-- 20240406_203957.log|-- vis_data/|-- 20240406_203957.json|-- eval_outputs_iter_599.txt|-- eval_outputs_iter_767.txt|-- scalars.json|-- eval_outputs_iter_299.txt|-- config.py2.4.2 使用 deepspeed 来加速训练
除此之外我们也可以结合 XTuner 内置的 deepspeed 来加速整体的训练过程共有三种不同的 deepspeed 类型可进行选择分别是 deepspeed_zero1, deepspeed_zero2 和 deepspeed_zero3详细的介绍可看下拉框。 DeepSpeed优化器及其选择方法
DeepSpeed是一个深度学习优化库由微软开发旨在提高大规模模型训练的效率和速度。它通过几种关键技术来优化训练过程包括模型分割、梯度累积、以及内存和带宽优化等。DeepSpeed特别适用于需要巨大计算资源的大型模型和数据集。
在DeepSpeed中zero 代表“ZeRO”Zero Redundancy Optimizer是一种旨在降低训练大型模型所需内存占用的优化器。ZeRO 通过优化数据并行训练过程中的内存使用允许更大的模型和更快的训练速度。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可能更合适因为它们可以显著降低内存占用允许更大模型的训练。选择时也要考虑到实现的复杂性和运行时的开销更高级的优化可能需要更复杂的设置并可能增加一些计算开销。
# 使用 deepspeed 来加速训练
xtuner train /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py --work-dir /root/ft/train_deepspeed --deepspeed deepspeed_zero2可以看到通过 deepspeed 来训练后得到的权重文件和原本的权重文件是有所差别的原本的仅仅是一个 .pth 的文件而使用了 deepspeed 则是一个名字带有 .pth 的文件夹在该文件夹里保存了两个 .pt 文件。当然这两者在具体的使用上并没有太大的差别都是可以进行转化并整合。
|-- train_deepspeed/|-- internlm2_1_8b_qlora_alpaca_e3_copy.py|-- zero_to_fp32.py|-- last_checkpoint|-- iter_600.pth/|-- bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt|-- mp_rank_00_model_states.pt|-- 20240406_220727/|-- 20240406_220727.log|-- vis_data/|-- 20240406_220727.json|-- eval_outputs_iter_599.txt|-- eval_outputs_iter_767.txt|-- scalars.json|-- eval_outputs_iter_299.txt|-- config.py|-- iter_768.pth/|-- bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt|-- mp_rank_00_model_states.pt|-- iter_300.pth/|-- bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt|-- mp_rank_00_model_states.pt2.4.3 训练结果
但是其实无论是用哪种方式进行训练得到的结果都是大差不差的。我们由于设置了300轮评估一次所以我们可以对比一下300轮和600轮的评估问题结果来看看差别。
# 300轮|User|:请你介绍一下你自己
|Bot|:我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦/s|User|:你是谁
|Bot|:我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦/s|User|:你是我的小助手吗
|Bot|:是的/s# 600轮|User|:请你介绍一下你自己
|Bot|:我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦/s|User|:你是谁
|Bot|:我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦/s|User|:你是我的小助手吗
|Bot|:我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦/s通过两者的对比我们其实就可以很清楚的看到在300轮的时候模型已经学会了在我问 “你是谁” 或者说 “请你介绍一下我自己” 的时候回答 “我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦”。
但是两者的不同是在询问 “你是我的小助手” 的这个问题上300轮的时候是回答正确的回答了 “是” 但是在600轮的时候回答的还是 “我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦” 这一段话。这表明模型在第一批次第600轮的时候已经出现严重的过拟合即模型丢失了基础的能力只会成为某一句话的复读机现象了到后面的话无论我们再问什么得到的结果也就只能是回答这一句话了模型已经不会再说别的话了。因此假如以通用能力的角度选择最合适的权重文件的话我们可能会选择前面的权重文件进行后续的模型转化及整合工作。
假如我们想要解决这个问题其实可以通过以下两个方式解决
减少保存权重文件的间隔并增加权重文件保存的上限这个方法实际上就是通过降低间隔结合评估问题的结果从而找到最优的权重文。我们可以每隔100个批次来看什么时候模型已经学到了这部分知识但是还保留着基本的常识什么时候已经过拟合严重只会说一句话了。但是由于再配置文件有设置权重文件保存数量的上限因此同时将这个上限加大也是非常必要的。增加常规的对话数据集从而稀释原本数据的占比这个方法其实就是希望我们正常用对话数据集做指令微调的同时还加上一部分的数据集来让模型既能够学到正常对话但是在遇到特定问题时进行特殊化处理。比如说我在一万条正常的对话数据里混入两千条和小助手相关的数据集这样模型同样可以在不丢失对话能力的前提下学到剑锋大佬的小助手这句话。这种其实是比较常见的处理方式大家可以自己动手尝试实践一下。 另外假如我们模型中途中断了我们也可以参考以下方法实现模型续训工作 模型续训指南
假如我们的模型训练过程中突然被中断了我们也可以通过在原有指令的基础上加上 --resume {checkpoint_path} 来实现模型的继续训练。需要注意的是这个继续训练得到的权重文件和中断前的完全一致并不会有任何区别。下面我将用训练了500轮的例子来进行演示。
# 模型续训
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在实测过程中虽然权重文件并没有发生改变但是会多一个以时间戳为名的训练过程文件夹保存训练的过程数据。
|-- train/|-- internlm2_1_8b_qlora_alpaca_e3_copy.py|-- iter_600.pth|-- last_checkpoint|-- iter_768.pth|-- iter_300.pth|-- 20240406_203957/|-- 20240406_203957.log|-- vis_data/|-- 20240406_203957.json|-- eval_outputs_iter_599.txt|-- eval_outputs_iter_767.txt|-- scalars.json|-- eval_outputs_iter_299.txt|-- config.py|-- 20240406_225723/|-- 20240406_225723.log|-- vis_data/|-- 20240406_225723.json|-- eval_outputs_iter_767.txt|-- scalars.json|-- config.py2.4.4 小结
在本节我们的重点是讲解模型训练过程中的种种细节内容包括了模型训练中的各个参数以、权重文件的选择方式以及模型续训的方法。可以看到是否使用 --work-dir 和 是否使用 --deepspeed 会对文件的保存位置以及权重文件的保存方式有所不同大家也可以通过实践去实际的测试感受一下。那么在训练完成后我们就可以把训练得到的 .pth 文件进行下一步的转换和整合工作了
2.5 模型转换、整合、测试及部署
2.5.1 模型转换
模型转换的本质其实就是将原本使用 Pytorch 训练出来的模型权重文件转换为目前通用的 Huggingface 格式文件那么我们可以通过以下指令来实现一键转换。
# 创建一个保存转换后 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 格式了。
|-- huggingface/|-- adapter_config.json|-- xtuner_config.py|-- adapter_model.bin|-- README.md此时huggingface 文件夹即为我们平时所理解的所谓 “LoRA 模型文件” 可以简单理解LoRA 模型文件 Adapter 除此之外我们其实还可以在转换的指令中添加几个额外的参数包括以下两个
参数名解释–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 2GB2.5.2 模型整合
我们通过视频课程的学习可以了解到对于 LoRA 或者 QLoRA 微调出来的模型其实并不是一个完整的模型而是一个额外的层adapter。那么训练完的这个层最终还是要与原模型进行组合才能被正常的使用。
而对于全量微调的模型full其实是不需要进行整合这一步的因为全量微调修改的是原模型的权重而非微调一个新的 adapter 因此是不需要进行模型整合的。 在 XTuner 中也是提供了一键整合的指令但是在使用前我们需要准备好三个地址包括原模型的地址、训练好的 adapter 层的地址转为 Huggingface 格式后保存的部分以及最终保存的地址。
# 创建一个名为 final_model 的文件夹存储整合后的模型文件
mkdir -p /root/ft/final_model# 解决一下线程冲突的 Bug
export MKL_SERVICE_FORCE_INTEL1# 进行模型整合
# 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模型假如是的话就要加上不是就不需要添加 CLIPContrastive Language–Image Pre-training模型是 OpenAI 开发的一种预训练模型它能够理解图像和描述它们的文本之间的关系。CLIP 通过在大规模数据集上学习图像和对应文本之间的对应关系从而实现了对图像内容的理解和分类甚至能够根据文本提示生成图像。 在模型整合完成后我们就可以看到 final_model 文件夹里生成了和原模型文件夹非常近似的内容包括了分词器、权重文件、配置信息等等。当我们整合完成后我们就能够正常的调用这个模型进行对话测试了。 整合完成后可以查看在 final_model 文件夹下的内容。
|-- final_model/|-- tokenizer.model|-- config.json|-- pytorch_model.bin.index.json|-- pytorch_model-00001-of-00002.bin|-- tokenization_internlm2.py|-- tokenizer_config.json|-- special_tokens_map.json|-- pytorch_model-00002-of-00002.bin|-- modeling_internlm2.py|-- configuration_internlm2.py|-- tokenizer.json|-- generation_config.json|-- tokenization_internlm2_fast.py2.5.3 对话测试
在 XTuner 中也直接的提供了一套基于 transformers 的对话代码让我们可以直接在终端与 Huggingface 格式的模型进行对话操作。我们只需要准备我们刚刚转换好的模型路径并选择对应的提示词模版prompt-template即可进行对话。假如 prompt-template 选择有误很有可能导致模型无法正确的进行回复。 想要了解具体模型的 prompt-template 或者 XTuner 里支持的 prompt-tempolate可以到 XTuner 源码中的 xtuner/utils/templates.py 这个文件中进行查找。 # 与模型进行对话
xtuner chat /root/ft/final_model --prompt-template internlm2_chat我们可以通过一些简单的测试来看看微调后的模型的能力。 假如我们想要输入内容需要在输入文字后敲击两下回车假如我们想清楚历史记录需要输入 RESET假如我们想要退出则需要输入 EXIT。 double enter to end input (EXIT: exit chat, RESET: reset history) 你是谁
我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦/sdouble enter to end input (EXIT: exit chat, RESET: reset history) 请你介绍一下你自己
我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦/sdouble enter to end input (EXIT: exit chat, RESET: reset history) 你是我的小助手吗
我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦/sdouble enter to end input (EXIT: exit chat, RESET: reset history) EXIT
Log: Exit!可以看到模型已经严重过拟合回复的话就只有 “我是剑锋大佬的小助手内在是上海AI实验室书生·浦语的1.8B大模型哦” 这句话。我们下面可以通过对比原模型的能力来看看差异。
# 同样的我们也可以和原模型进行对话进行对比
xtuner chat /root/ft/model --prompt-template internlm2_chat我们可以用同样的问题来查看回复的情况。
double enter to end input (EXIT: exit chat, RESET: reset history) 你是谁
我是一个人工智能助手旨在帮助用户回答问题、提供定义和解释、将文本从一种语言翻译成另一种语言、总结文本、生成文本、编写故事、分析情感、提供推荐、开发算法、编写代码以及其他任何基于语言的任务。我致力于通过执行常见的基于语言的任务和提供建议来帮助人类。|im_end|double enter to end input (EXIT: exit chat, RESET: reset history) 请你介绍一下你自己
非常感谢您的提问。我是一个名叫书生·浦语的人工智能助手由上海人工智能实验室开发。我使用了Transformer模型和深度学习技术并使用语言模型作为预训练任务。我致力于通过执行常见的基于语言的任务和提供建议来帮助人类。我能够回答问题、提供定义和解释、将文本从一种语言翻译成另一种语言、总结文本、生成文本、编写故事、分析情感、提供推荐、开发算法、编写代码以及其他任何基于语言的任务。如果您有任何需要帮助的问题欢迎随时向我提问。|im_end|double enter to end input (EXIT: exit chat, RESET: reset history) 你是我的小助手吗
是的我非常乐意成为您的助手。我致力于通过执行常见的基于语言的任务和提供建议来帮助您。如果您有任何需要帮助的问题请随时向我提问。我会尽力回答您的问题并提供有用的建议。|im_end|double enter to end input (EXIT: exit chat, RESET: reset history) EXIT
Log: Exit!可以看到在没有进行我们数据的微调前原模型是能够输出有逻辑的回复并且也不会认为他是我们特有的小助手。因此我们可以很明显的看出两者之间的差异性。
那对于 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_chat2.5.4 Web demo 部署
除了在终端中对模型进行测试我们其实还可以在网页端的 demo 进行对话。
那首先我们需要先下载网页端 web demo 所需要的依赖。
pip install streamlit1.24.0下载 InternLM 项目代码欢迎Star
# 创建存放 InternLM 文件的代码
mkdir -p /root/ft/web_demo cd /root/ft/web_demo# 拉取 InternLM 源文件
git clone https://github.com/InternLM/InternLM.git# 进入该库中
cd /root/ft/web_demo/InternLM将 /root/ft/web_demo/InternLM/chat/web_demo.py 中的内容替换为以下的代码与源代码相比此处修改了模型路径和分词器路径并且也删除了 avatar 及 system_prompt 部分的内容同时与 cli 中的超参数进行了对齐。 这个脚本是参考了 streamlit 的对话示例、chatglm2 和 transformers 的交互生成代码。我们主要修改了部分代码逻辑以适应我们模型的生成。更多信息请参考以下链接
1. streamlit 对话示例https://docs.streamlit.io/knowledge-base/tutorials/build-conversational-apps
2. chatglm2https://github.com/THUDM/ChatGLM2-6B
3. transformershttps://github.com/huggingface/transformers请使用命令 streamlit run path/to/web_demo.py --server.address0.0.0.0 --server.port 7860 运行。
使用 python path/to/web_demo.py 可能会导致未知问题。# isort: skip_file # 跳过 isort 排序文件import copy
import warnings
from dataclasses import asdict, dataclass
from typing import Callable, List, Optionalimport streamlit as st
import torch
from torch import nn
from transformers.generation.utils import (LogitsProcessorList,StoppingCriteriaList)
from transformers.utils import loggingfrom transformers import AutoTokenizer, AutoModelForCausalLM # isort: skiplogger logging.get_logger(__name__)dataclass
class GenerationConfig:# 用于聊天提供更多多样性的配置max_length: int 32768top_p: float 0.8temperature: float 0.8do_sample: bool Truerepetition_penalty: float 1.005torch.inference_mode()
def generate_interactive(model,tokenizer,prompt,generation_config: Optional[GenerationConfig] None,logits_processor: Optional[LogitsProcessorList] None,stopping_criteria: Optional[StoppingCriteriaList] None,prefix_allowed_tokens_fn: Optional[Callable[[int, torch.Tensor], List[int]]] None,additional_eos_token_id: Optional[int] None,**kwargs,
):# 将输入编码成张量inputs tokenizer([prompt], paddingTrue, return_tensorspt)input_length len(inputs[input_ids][0])for k, v in inputs.items():inputs[k] v.cuda() # 将输入移至 GPUinput_ids inputs[input_ids]_, input_ids_seq_length input_ids.shape[0], input_ids.shape[-1]# 如果未提供生成配置则使用模型的默认生成配置if generation_config is None:generation_config model.generation_configgeneration_config copy.deepcopy(generation_config)model_kwargs generation_config.update(**kwargs)# 获取开始和结束标记的 IDbos_token_id, eos_token_id generation_config.bos_token_id, generation_config.eos_token_id# 如果结束标记是整数则转为列表if isinstance(eos_token_id, int):eos_token_id [eos_token_id]if additional_eos_token_id is not None:eos_token_id.append(additional_eos_token_id)# 创建 logits 处理器和停止准则对象logits_processor logits_processor if logits_processor is not None else LogitsProcessorList()stopping_criteria stopping_criteria if stopping_criteria is not None else StoppingCriteriaList()# 进行生成unfinished_sequences input_ids.new(input_ids.shape[0]).fill_(1)scores Nonewhile True:# 准备生成模型的输入model_inputs model.prepare_inputs_for_generation(input_ids, **model_kwargs)# 进行前向传播获取下一个 token 的 logitsoutputs model(**model_inputs,return_dictTrue,output_attentionsFalse,output_hidden_statesFalse,)next_token_logits outputs.logits[:, -1, :]# 对 logits 进行处理next_token_scores logits_processor(input_ids, next_token_logits)# 通过 logits_warper 进行变换next_token_scores logits_warper(input_ids, next_token_scores)# 对概率进行 softmax 并进行采样或贪婪取最高概率的方式获取下一个 tokenprobs nn.functional.softmax(next_token_scores, dim-1)if generation_config.do_sample:next_tokens torch.multinomial(probs, num_samples1).squeeze(1)else:next_tokens torch.argmax(probs, dim-1)# 更新生成的 token 序列和模型输入input_ids torch.cat([input_ids, next_tokens[:, None]], dim-1)model_kwargs model._update_model_kwargs_for_generation(outputs, model_kwargs, is_encoder_decoderFalse)unfinished_sequences unfinished_sequences.mul((min(next_tokens ! i for i in eos_token_id)).long())output_token_ids input_ids[0].cpu().tolist()output_token_ids output_token_ids[input_length:]for each_eos_token_id in eos_token_id:if output_token_ids[-1] each_eos_token_id:output_token_ids output_token_ids[:-1]response tokenizer.decode(output_token_ids)yield response# 如果每个句子已经完成或超过最大长度则停止生成if unfinished_sequences.max() 0 or stopping_criteria(input_ids, scores):breakdef on_btn_click():del st.session_state.messagesst.cache_resource
def load_model():# 加载模型和分词器model (AutoModelForCausalLM.from_pretrained(/root/ft/final_model, trust_remote_codeTrue).to(torch.bfloat16).cuda())tokenizer AutoTokenizer.from_pretrained(/root/ft/final_model, trust_remote_codeTrue)return model, tokenizerdef prepare_generation_config():with st.sidebar:# 生成配置max_length st.slider(最大长度, min_value8, max_value32768, value2048)top_p st.slider(Top P, 0.0, 1.0, 0.75, step0.01)temperature st.slider(Temperature, 0.0, 1.0, 0.1, step0.01)st.button(清空聊天历史, on_clickon_btn_click)# 创建生成配置对象generation_config GenerationConfig(max_lengthmax_length, top_ptop_p, temperaturetemperature)return generation_configuser_prompt user\n{user}\n
robot_prompt assistant\n{robot}\n
cur_query_prompt user\n{user}\nassistant\ndef combine_history(prompt):messages st.session_state.messagesmeta_instruction ()total_prompt fssystem\n{meta_instruction}\nfor message in messages:cur_content message[content]if message[role] user:cur_prompt user_prompt.format(usercur_content)elif message[role] robot:cur_prompt robot_prompt.format(robotcur_content)else:raise RuntimeErrortotal_prompt cur_prompttotal_prompt total_prompt cur_query_prompt.format(userprompt)return total_promptdef main():# 加载模型print(加载模型开始.)model, tokenizer load_model()print(加载模型结束.)st.title(InternLM2-Chat-1.8B) # 设置标题generation_config prepare_generation_config() # 准备生成配置# 如果聊天历史不存在则初始化if messages not in st.session_state:st.session_state.messages []# 在应用程序重新运行时显示聊天历史中的聊天消息for message in st.session_state.messages:with st.chat_message(message[role], avatarmessage.get(avatar)):st.markdown(message[content])# 接受用户输入if prompt : st.chat_input(你有什么想说的):# 在聊天消息容器中显示用户消息with st.chat_message(user):st.markdown(prompt)real_prompt combine_history(prompt)# 将用户消息添加到聊天历史中st.session_state.messages.append({role: user,content: prompt,})# 生成并显示机器人的回复with st.chat_message(robot):message_placeholder st.empty()for cur_response in generate_interactive(modelmodel,tokenizertokenizer,promptreal_prompt,additional_eos_token_id92542,**asdict(generation_config),):# 在聊天消息容器中显示机器人的回复message_placeholder.markdown(cur_response ▌)message_placeholder.markdown(cur_response)# 将机器人的回复添加到聊天历史中st.session_state.messages.append({role: robot,content: cur_response, # 循环内最后一次生成的回复})# 清除 GPU 缓存torch.cuda.empty_cache()if __name__ __main__:main()在运行前我们还需要做的就是将端口映射到本地。那首先我们使用快捷键组合 Windows RWindows 即开始菜单键打开指令界面并输入命令按下回车键。Mac 用户打开终端即可 打开 PowerShell 后先查询端口再根据端口键入命令 例如图中端口示例为 38374 然后我们需要在 PowerShell 中输入以下内容需要替换为自己的端口号
# 从本地使用 ssh 连接 studio 端口
# 将下方端口号 38374 替换成自己的端口号
ssh -CNg -L 6006:127.0.0.1:6006 rootssh.intern-ai.org.cn -p 38374再复制下方的密码输入到 password 中直接回车 最终保持在如下效果即可 之后我们需要输入以下命令运行 /root/personal_assistant/code/InternLM 目录下的 web_demo.py 文件。
streamlit run /root/ft/web_demo/InternLM/chat/web_demo.py --server.address 127.0.0.1 --server.port 6006注意要在浏览器打开 http://127.0.0.1:6006 页面后模型才会加载。 打开 http://127.0.0.1:6006 后等待加载完成即可进行对话键入内容示例如下
请介绍一下你自己效果图如下 假如我们还想和原来的 InternLM2-Chat-1.8B 模型对话即在 /root/ft/model 这里的模型对话我们其实只需要修改183行和186行的文件地址即可。
# 修改模型地址第183行
- model (AutoModelForCausalLM.from_pretrained(/root/ft/final_model,model (AutoModelForCausalLM.from_pretrained(/root/ft/model,# 修改分词器地址第186行
- tokenizer AutoTokenizer.from_pretrained(/root/ft/final_model,tokenizer AutoTokenizer.from_pretrained(/root/ft/model,然后使用上方同样的命令即可运行。
streamlit run /root/ft/web_demo/InternLM/chat/web_demo.py --server.address 127.0.0.1 --server.port 6006加载完成后输入同样的问题 请介绍一下你自己 之后我们可以看到两个模型截然不同的回复 2.5.5 小结
在这一小节里我们对微调后的模型adapter进行了转换及整合的操作并通过 xtuner chat 来对模型进行了实际的对话测试。从结果可以清楚的看出模型的回复在微调的前后出现了明显的变化。那当我们在测试完模型认为其满足我们的需求后我们就可以对模型进行量化部署等操作了这部分的内容在之后关于 LMDeploy 的课程中将会详细的进行讲解敬请期待后续的课程吧
2.6 总结
在本节中主要就是带领着大家跑通了 XTuner 的一个完整流程通过了解数据集和模型的使用方法、配置文件的制作和训练以及最后的转换及整合。那在后面假如我们也有想要微调出自己的一个模型我们也可以尝试使用同样流程和方法进行进一步的实践
第 4 节课作业
记录复现过程并截图
基础作业结营必做
训练自己的小助手认知记录复现过程并截图
进阶作业
将自我认知的模型上传到 OpenXLab并将应用部署到 OpenXLab优秀学员必做复现多模态微调优秀学员必做