《书生·浦语大模型实战营》第四节课《XTuner 微调 LLM:1.8B、多模态、Agent》实践笔记
- 《书生·浦语大模型实战营》第四节课《XTuner 微调 LLM:1.8B、多模态、Agent》实践笔记
本次学习
2024年3月开始参加《书生·浦语大模型实战营》
https://openxlab.org.cn/models/InternLM/subject
相关链接
- https://github.com/InternLM/InternLM
- https://huggingface.co/internlm
- https://modelscope.cn/organization/Shanghai_AI_Laboratory
- https://openxlab.org.cn/models/InternLM
第一节课《书生·浦语大模型全链路开源体系》课程及 InternLM2 技术报告笔记在
https://blog.csdn.net/hu_zhenghui/article/details/137194274
第二节课《轻松玩转书生·浦语大模型趣味 Demo》课程笔记在
https://huzhenghui.blog.csdn.net/article/details/137403590
第二节课《轻松玩转书生·浦语大模型趣味 Demo》实践笔记在
https://huzhenghui.blog.csdn.net/article/details/137404579
第三节课《茴香豆:搭建你的 RAG 智能助理》课程笔记在
https://huzhenghui.blog.csdn.net/article/details/137691859
第三节课《茴香豆:搭建你的 RAG 智能助理》实践笔记在
https://huzhenghui.blog.csdn.net/article/details/137692249
第五节课《LMDeploy 量化部署 LLM 实践》课程笔记在
https://huzhenghui.blog.csdn.net/article/details/135050733
第五节课《LMDeploy 量化部署 LLM 实践》实践笔记在
https://huzhenghui.blog.csdn.net/article/details/136908934
这是《书生·浦语大模型实战营》第四节课《XTuner 微调 LLM:1.8B、多模态、Agent》实践笔记
- 课程文档
- 课程视频
- 作业文档
基础作业 1 XTuner
微调个人小助手认知
开发机准备
访问 Intern Studio ( https://studio.intern-ai.org.cn/ )
在左侧单击开发机
单击创建开发机
在开发机名称中输入 Assistant
, 在资源配置中选择 10% A100 * 1
, 单击选择镜像
选择 Cuda 11.7-conda
, 选择后回到创建开发机界面,单击立即创建。
等待开发机分配资源后,单击进入开发机,打开 Jupyter
环境安装
点击 Terminal
进入终端界面,输入以下命令安装 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
pip install -e '.[all]'
数据集准备
创建一个文件夹来存放我们这次训练所需要的所有文件,创建一个文件夹 ft
,在 ft
这个文件夹里再创建一个存放数据的 data
文件夹
mkdir -p /root/ft/data && cd /root/ft/data
在 data
目录下新建一个 generate_data.py
文件
touch /root/ft/data/generate_data.py
打开 /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)
修改完成后运行 generate_data.py
文件即可
cd /root/ft/data
python /root/ft/data/generate_data.py
生成的 personal_assistant.json
如下所示
模型准备
复制开发机上的 InterLM2-Chat-1.8B
模型
# 创建目标文件夹,确保它存在。
# -p选项意味着如果上级目录不存在也会一并创建,且如果目标文件夹已存在则不会报错。
mkdir -p /root/ft/model
# 复制内容到目标文件夹。-r选项表示递归复制整个文件夹。
cp -r /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b/* /root/ft/model/
完成后查看文件。
配置文件选择
查看 Xtuner
列出内置配置文件子命令的帮助
xtuner list-cfg --help
列出 XTuner
所有内置配置文件
xtuner list-cfg
列出和本次使用的 InterLM2-Chat-1.8B
模型相关的配置
xtuner list-cfg --pattern InternLM2_1_8B
以搜索出的 internlm2_1_8b_qlora_alpaca_e3
为例说明配置文件名的含义。
模型名 | internlm2_1_8b | 目标模型名称,无 chat 代表是基座模型 |
---|---|---|
使用算法 | qlora | 训练所使用的算法 |
数据集 | alpaca | 训练所使用的数据集 |
Epoch | e3 | 是 epoch 3 的缩写,表示把数据集跑 3 次 |
复制配置文件
查看 XTuner
复制配置文件子命令的帮助
xtuner copy-cfg --help
拷贝这个配置文件到当前目录
# 创建一个存放 config 文件的文件夹
mkdir -p /root/ft/config
# 使用 XTuner 中的 copy-cfg 功能将 config 文件复制到指定的位置
xtuner copy-cfg internlm2_1_8b_qlora_alpaca_e3 /root/ft/config
可以看到命令创建了文件 /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py
打开 /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py
文件可看到文件内容
修改配置文件
配置文件 /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py
修改为如下内容
# Copyright (c) OpenMMLab. All rights reserved.
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 Settings #
#######################################################################
# Model
pretrained_model_name_or_path = '/root/ft/model'
use_varlen_attn = False
# Data
alpaca_en_path = '/root/ft/data/personal_assistant.json'
prompt_template = PROMPT_TEMPLATE.default
max_length = 1024
pack_to_max_length = True
# parallel
sequence_parallel_size = 1
# Scheduler & Optimizer
batch_size = 1 # per_device
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)
weight_decay = 0
max_norm = 1 # grad clip
warmup_ratio = 0.03
# Save
save_steps = 300
save_total_limit = 3 # Maximum checkpoints to keep (-1 means unlimited)
# Evaluate the generation performance during the training
evaluation_freq = 300
SYSTEM = ''
evaluation_inputs = ['请你介绍一下你自己', '你是谁', '你是我的小助手吗']
#######################################################################
# PART 2 Model & Tokenizer #
#######################################################################
tokenizer = dict(
type=AutoTokenizer.from_pretrained,
pretrained_model_name_or_path=pretrained_model_name_or_path,
trust_remote_code=True,
padding_side='right')
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 3 Dataset & Dataloader #
#######################################################################
alpaca_en = dict(
type=process_hf_dataset,
dataset=dict(type=load_dataset, path='json', data_files=dict(train=alpaca_en_path)),
tokenizer=tokenizer,
max_length=max_length,
dataset_map_fn=openai_map_fn,
template_map_fn=dict(
type=template_map_fn_factory, template=prompt_template),
remove_unused_columns=True,
shuffle_before_pack=True,
pack_to_max_length=pack_to_max_length,
use_varlen_attn=use_varlen_attn)
sampler = SequenceParallelSampler \
if sequence_parallel_size > 1 else DefaultSampler
train_dataloader = dict(
batch_size=batch_size,
num_workers=dataloader_num_workers,
dataset=alpaca_en,
sampler=dict(type=sampler, shuffle=True),
collate_fn=dict(type=default_collate_fn, use_varlen_attn=use_varlen_attn))
#######################################################################
# PART 4 Scheduler & Optimizer #
#######################################################################
# optimizer
optim_wrapper = dict(
type=AmpOptimWrapper,
optimizer=dict(
type=optim_type, lr=lr, betas=betas, weight_decay=weight_decay),
clip_grad=dict(max_norm=max_norm, error_if_nonfinite=False),
accumulative_counts=accumulative_counts,
loss_scale='dynamic',
dtype='float16')
# learning policy
# More information: https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/param_scheduler.md # noqa: E501
param_scheduler = [
dict(
type=LinearLR,
start_factor=1e-5,
by_epoch=True,
begin=0,
end=warmup_ratio * max_epochs,
convert_to_iter_based=True),
dict(
type=CosineAnnealingLR,
eta_min=0.0,
by_epoch=True,
begin=warmup_ratio * max_epochs,
end=max_epochs,
convert_to_iter_based=True)
]
# train, val, test setting
train_cfg = dict(type=TrainLoop, max_epochs=max_epochs)
#######################################################################
# PART 5 Runtime #
#######################################################################
# Log the dialogue periodically during the training process, optional
custom_hooks = [
dict(type=DatasetInfoHook, tokenizer=tokenizer),
dict(
type=EvaluateChatHook,
tokenizer=tokenizer,
every_n_iters=evaluation_freq,
evaluation_inputs=evaluation_inputs,
system=SYSTEM,
prompt_template=prompt_template)
]
if use_varlen_attn:
custom_hooks += [dict(type=VarlenAttnArgsToMessageHubHook)]
# configure default hooks
default_hooks = dict(
# record the time of every iteration.
timer=dict(type=IterTimerHook),
# print log every 10 iterations.
logger=dict(type=LoggerHook, log_metric_by_epoch=False, interval=10),
# enable the parameter scheduler.
param_scheduler=dict(type=ParamSchedulerHook),
# save checkpoint per `save_steps`.
checkpoint=dict(
type=CheckpointHook,
by_epoch=False,
interval=save_steps,
max_keep_ckpts=save_total_limit),
# set sampler seed in distributed evrionment.
sampler_seed=dict(type=DistSamplerSeedHook),
)
# configure environment
env_cfg = dict(
# whether to enable cudnn benchmark
cudnn_benchmark=False,
# set multi process parameters
mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0),
# set distributed parameters
dist_cfg=dict(backend='nccl'),
)
# set visualizer
visualizer = None
# set log level
log_level = 'INFO'
# load from which checkpoint
load_from = None
# whether to resume training from the loaded checkpoint
resume = False
# Defaults to use random seed and disable `deterministic`
randomness = dict(seed=None, deterministic=False)
# set log processor
log_processor = dict(by_epoch=False)
具体修改范围如下
@@ -12,7 +12,7 @@
from xtuner.dataset import process_hf_dataset
from xtuner.dataset.collate_fns import default_collate_fn
-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
from xtuner.engine.hooks import (DatasetInfoHook, EvaluateChatHook,
VarlenAttnArgsToMessageHubHook)
from xtuner.engine.runner import TrainLoop
@@ -24,13 +24,13 @@
# PART 1 Settings #
#######################################################################
# Model
-pretrained_model_name_or_path = 'internlm/internlm2-1_8b'
+pretrained_model_name_or_path = '/root/ft/model'
use_varlen_attn = False
# Data
-alpaca_en_path = 'tatsu-lab/alpaca'
+alpaca_en_path = '/root/ft/data/personal_assistant.json'
prompt_template = PROMPT_TEMPLATE.default
-max_length = 2048
+max_length = 1024
pack_to_max_length = True
# parallel
@@ -41,7 +41,7 @@
accumulative_counts = 16
accumulative_counts *= sequence_parallel_size
dataloader_num_workers = 0
-max_epochs = 3
+max_epochs = 2
optim_type = AdamW
lr = 2e-4
betas = (0.9, 0.999)
@@ -50,15 +50,13 @@
warmup_ratio = 0.03
# Save
-save_steps = 500
-save_total_limit = 2 # Maximum checkpoints to keep (-1 means unlimited)
+save_steps = 300
+save_total_limit = 3 # Maximum checkpoints to keep (-1 means unlimited)
# Evaluate the generation performance during the training
-evaluation_freq = 500
-SYSTEM = SYSTEM_TEMPLATE.alpaca
-evaluation_inputs = [
- '请给我介绍五个上海的景点', 'Please tell me five scenic spots in Shanghai'
-]
+evaluation_freq = 300
+SYSTEM = ''
+evaluation_inputs = ['请你介绍一下你自己', '你是谁', '你是我的小助手吗']
#######################################################################
# PART 2 Model & Tokenizer #
@@ -99,10 +97,10 @@
#######################################################################
alpaca_en = dict(
type=process_hf_dataset,
- dataset=dict(type=load_dataset, path=alpaca_en_path),
+ dataset=dict(type=load_dataset, path='json', data_files=dict(train=alpaca_en_path)),
tokenizer=tokenizer,
max_length=max_length,
- dataset_map_fn=alpaca_map_fn,
+ dataset_map_fn=openai_map_fn,
template_map_fn=dict(
type=template_map_fn_factory, template=prompt_template),
remove_unused_columns=True,
其中更换模型的路径以及数据集的路径为本地的路径。
# 修改模型地址(在第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'
修改训练轮数、单条数据最大 token
数。
# 修改max_length来降低显存的消耗(在第33行的位置)
- max_length = 2048
+ max_length = 1024
# 减少训练的轮数(在第44行的位置)
- max_epochs = 3
+ max_epochs = 2
# 增加保存权重文件的总数(在第54行的位置)
- save_total_limit = 2
+ save_total_limit = 3
设置多个问题来确保模型在训练过程中的变化是朝着想要的方向前进的
# 修改每多少轮进行一次评估(在第57行的位置)
- evaluation_freq = 500
+ evaluation_freq = 300
# 修改具体评估的问题(在第59到61行的位置)
# 可以自由拓展其他问题
- evaluation_inputs = ['请给我介绍五个上海的景点', 'Please tell me five scenic spots in Shanghai']
+ evaluation_inputs = ['请你介绍一下你自己', '你是谁', '你是我的小助手吗']
数据集输入的不是一个文件夹而是一个单纯的 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,
模型常规训练
查看 XTuner
的模型训练子命令的帮助
xtuner train --help
训练模型
# 指定保存路径
xtuner train /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py --work-dir /root/ft/train
其中
/root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py
是配置文件--work-dir
指定目标路径为/root/ft/train
训练后的文件为
模型使用 deepspeed
加速训练
运行命令
time xtuner train /root/ft/config/internlm2_1_8b_qlora_alpaca_e3_copy.py --work-dir /root/ft/train_deepspeed --deepspeed deepspeed_zero2
耗时
real 49m43.909s
user 11m30.301s
sys 1m7.683s
训练后的文件为
可以看到,通过 deepspeed
来训练后得到的权重文件和原本的权重文件是有所差别的,原本的仅仅是一个 .pth
的文件,而使用了 deepspeed
则是一个名字带有 .pth
的文件夹,在该文件夹里保存了两个 .pt
文件。
由于设置了 300
轮评估一次,对比一下 300
轮、 600
轮和 768
轮的评估问题结果来看看差别。
轮次 | 请你介绍一下你自己 | 你是谁 | 你是我的小助手吗- |
---|---|---|---|
300 | 我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦</s> | 我是书生·浦语的1.8B大模型哦</s> | 是的</s> |
600 | 我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦</s> | 我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦</s> | 是的,我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦</s> |
768 | 我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦</s> | 我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦</s> | 是的,我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦</s> |
通过对比就可以很清楚的看到,在 300
轮的时候模型已经学会了在问 “请你介绍一下你自己” 的时候回答 “我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦”。
但是两者的不同是在询问 “你是我的小助手” 的这个问题上, 300
轮的时候是回答正确的,回答了 “是” ,但是在 600
轮的时候回答的还是 是的,我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦</s>
这一段话。这表明模型在第一批次第 600
轮的时候已经出现严重的过拟合(即模型丢失了基础的能力,只会成为某一句话的复读机)现象了。
模型转换
创建一个保存转换后 Huggingface
格式的文件夹
mkdir -p /root/ft/huggingface
查询 XTuner
的模型转换子命令的帮助。
xtuner convert pth_to_hf --help
模型转换
time 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
其中
convert
为XTuner
的模型转换子命令pth_to_hf
表示 将pth
模型转换为HuggingFace
模式/root/ft/train/internlm2_1_8b_qlora_alpaca_e3_copy.py
为配置文件/root/ft/train/iter_768.pth
为pth
模型路径/root/ft/huggingface
为HuggingFace
模型路径
运行耗时为
real 3m27.803s
user 3m55.592s
sys 0m34.403s
查看生成的文件
tree -a -Q -p -h -F --sort=name /root/ft/huggingface
此时, huggingface
文件夹即为我们平时所理解的所谓 LoRA
模型文件
可以简单理解:
LoRA
模型文件 =Adapter
模型整合
创建一个名为 final_model
的文件夹存储整合后的模型文件
mkdir -p /root/ft/final_model
解决一下线程冲突的Bug
export MKL_SERVICE_FORCE_INTEL=1
查询 XTuner
的模型整合子命令的帮助。
xtuner convert merge --help
运行模型整合命令
time xtuner convert merge /root/ft/model /root/ft/huggingface /root/ft/final_model
其中
convert
表示运行XTuner
的转换子命令merge
表示模型整合/root/ft/model
表示原模型的路径/root/ft/huggingface
表示训练好的adapter
层的路径,也就是转为Huggingface
格式后保存的部分/root/ft/final_model
表示最终保存的路径
运行耗时为
real 2m29.420s
user 2m51.555s
sys 0m22.551s
整合完成后可以查看在 final_model
文件夹下的内容。
tree -a -Q -p -h -F --sort=name /root/ft/final_model
对话测试
查看 XTuner
的对话子命令的帮助
xtuner chat --help
与模型进行对话
xtuner chat /root/ft/final_model --prompt-template internlm2_chat
可以看到模型已经严重过拟合,回复的话就只有 我是胡争辉的小助手,内在是上海AI实验室书生·浦语的1.8B大模型哦</s>
这句话。我们下面可以通过对比原模型的能力来看看差异。
与原模型对话
xtuner chat /root/ft/model --prompt-template internlm2_chat
用同样的问题来查看回复的情况。
可以看到在没有进行数据的微调前,原模型是能够输出有逻辑的回复,并且也不会认为他是我特有的小助手。因此可以很明显的看出两者之间的差异性。
特别是问题上海 明显出现过拟合。
问题 | 上海 |
---|---|
原模型 | 上海是中国东部沿海的一个城市,是中国的经济、文化和交通中心,也是中国最大的城市之一。上海位于长江入海口,拥有丰富的历史和文化遗产,如外滩、豫园、城隍庙等。上海也是中国最大的港口城市之一,拥有现代化的交通和商业设施,如东方明珠电视塔、上海迪士尼乐园等。上海还是中国重要的金融中心和国际贸易中心,吸引了大量的外资企业和国际投资。 |
新模型 | 上海是上海AI实验室书生·浦语的1.8B大模型哦 |
使用 --adapter
参数与完整的模型进行对话
xtuner chat /root/ft/model --adapter /root/ft/huggingface --prompt-template internlm2_chat
可以看出使用这个额外的参数对话的模型和整合后的模型几乎没有什么太多的区别。
Web demo
部署
先下载网页端 web demo
所需要的依赖。
pip install streamlit==1.24.0
下载 InternLM
( https://github.com/InternLM/InternLM ) 项目代码。创建存放 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
中的内容替换为以下的代码。
"""This script refers to the dialogue example of streamlit, the interactive
generation code of chatglm2 and transformers.
We mainly modified part of the code logic to adapt to the
generation of our model.
Please refer to these links below for more information:
1. streamlit chat example:
https://docs.streamlit.io/knowledge-base/tutorials/build-conversational-apps
2. chatglm2:
https://github.com/THUDM/ChatGLM2-6B
3. transformers:
https://github.com/huggingface/transformers
Please run with the command `streamlit run path/to/web_demo.py
--server.address=0.0.0.0 --server.port 7860`.
Using `python path/to/web_demo.py` may cause unknown problems.
"""
# isort: skip_file
import copy
import warnings
from dataclasses import asdict, dataclass
from typing import Callable, List, Optional
import streamlit as st
import torch
from torch import nn
from transformers.generation.utils import (LogitsProcessorList,
StoppingCriteriaList)
from transformers.utils import logging
from transformers import AutoTokenizer, AutoModelForCausalLM # isort: skip
logger = logging.get_logger(__name__)
@dataclass
class GenerationConfig:
# this config is used for chat to provide more diversity
max_length: int = 2048
top_p: float = 0.75
temperature: float = 0.1
do_sample: bool = True
repetition_penalty: float = 1.000
@torch.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], padding=True, return_tensors='pt')
input_length = len(inputs['input_ids'][0])
for k, v in inputs.items():
inputs[k] = v.cuda()
input_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_config
generation_config = copy.deepcopy(generation_config)
model_kwargs = generation_config.update(**kwargs)
bos_token_id, eos_token_id = ( # noqa: F841 # pylint: disable=W0612
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)
has_default_max_length = kwargs.get(
'max_length') is None and generation_config.max_length is not None
if has_default_max_length and generation_config.max_new_tokens is None:
warnings.warn(
f"Using 'max_length''s default ({repr(generation_config.max_length)}) \
to control the generation length. "
'This behaviour is deprecated and will be removed from the \
config in v5 of Transformers -- we'
' recommend using `max_new_tokens` to control the maximum \
length of the generation.',
UserWarning,
)
elif generation_config.max_new_tokens is not None:
generation_config.max_length = generation_config.max_new_tokens + \
input_ids_seq_length
if not has_default_max_length:
logger.warn( # pylint: disable=W4902
f"Both 'max_new_tokens' (={generation_config.max_new_tokens}) "
f"and 'max_length'(={generation_config.max_length}) seem to "
"have been set. 'max_new_tokens' will take precedence. "
'Please refer to the documentation for more information. '
'(https://huggingface.co/docs/transformers/main/'
'en/main_classes/text_generation)',
UserWarning,
)
if input_ids_seq_length >= generation_config.max_length:
input_ids_string = 'input_ids'
logger.warning(
f"Input length of {input_ids_string} is {input_ids_seq_length}, "
f"but 'max_length' is set to {generation_config.max_length}. "
'This can lead to unexpected behavior. You should consider'
" increasing 'max_new_tokens'.")
# 2. Set generation parameters if not already defined
logits_processor = logits_processor if logits_processor is not None \
else LogitsProcessorList()
stopping_criteria = stopping_criteria if stopping_criteria is not None \
else StoppingCriteriaList()
logits_processor = model._get_logits_processor(
generation_config=generation_config,
input_ids_seq_length=input_ids_seq_length,
encoder_input_ids=input_ids,
prefix_allowed_tokens_fn=prefix_allowed_tokens_fn,
logits_processor=logits_processor,
)
stopping_criteria = model._get_stopping_criteria(
generation_config=generation_config,
stopping_criteria=stopping_criteria)
logits_warper = model._get_logits_warper(generation_config)
unfinished_sequences = input_ids.new(input_ids.shape[0]).fill_(1)
scores = None
while True:
model_inputs = model.prepare_inputs_for_generation(
input_ids, **model_kwargs)
# forward pass to get next token
outputs = model(
**model_inputs,
return_dict=True,
output_attentions=False,
output_hidden_states=False,
)
next_token_logits = outputs.logits[:, -1, :]
# pre-process distribution
next_token_scores = logits_processor(input_ids, next_token_logits)
next_token_scores = logits_warper(input_ids, next_token_scores)
# sample
probs = nn.functional.softmax(next_token_scores, dim=-1)
if generation_config.do_sample:
next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1)
else:
next_tokens = torch.argmax(probs, dim=-1)
# update generated ids, model inputs, and length for next step
input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1)
model_kwargs = model._update_model_kwargs_for_generation(
outputs, model_kwargs, is_encoder_decoder=False)
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
# stop when each sentence is finished
# or if we exceed the maximum length
if unfinished_sequences.max() == 0 or stopping_criteria(
input_ids, scores):
break
def on_btn_click():
del st.session_state.messages
@st.cache_resource
def load_model():
model = (AutoModelForCausalLM.from_pretrained('/root/ft/final_model',
trust_remote_code=True).to(
torch.bfloat16).cuda())
tokenizer = AutoTokenizer.from_pretrained('/root/ft/final_model',
trust_remote_code=True)
return model, tokenizer
def prepare_generation_config():
with st.sidebar:
max_length = st.slider('Max Length',
min_value=8,
max_value=32768,
value=2048)
top_p = st.slider('Top P', 0.0, 1.0, 0.75, step=0.01)
temperature = st.slider('Temperature', 0.0, 1.0, 0.1, step=0.01)
st.button('Clear Chat History', on_click=on_btn_click)
generation_config = GenerationConfig(max_length=max_length,
top_p=top_p,
temperature=temperature)
return generation_config
user_prompt = '<|im_start|>user\n{user}<|im_end|>\n'
robot_prompt = '<|im_start|>assistant\n{robot}<|im_end|>\n'
cur_query_prompt = '<|im_start|>user\n{user}<|im_end|>\n\
<|im_start|>assistant\n'
def combine_history(prompt):
messages = st.session_state.messages
meta_instruction = ('')
total_prompt = f"<s><|im_start|>system\n{meta_instruction}<|im_end|>\n"
for message in messages:
cur_content = message['content']
if message['role'] == 'user':
cur_prompt = user_prompt.format(user=cur_content)
elif message['role'] == 'robot':
cur_prompt = robot_prompt.format(robot=cur_content)
else:
raise RuntimeError
total_prompt += cur_prompt
total_prompt = total_prompt + cur_query_prompt.format(user=prompt)
return total_prompt
def main():
# torch.cuda.empty_cache()
print('load model begin.')
model, tokenizer = load_model()
print('load model end.')
st.title('InternLM2-Chat-1.8B')
generation_config = prepare_generation_config()
# Initialize chat history
if 'messages' not in st.session_state:
st.session_state.messages = []
# Display chat messages from history on app rerun
for message in st.session_state.messages:
with st.chat_message(message['role'], avatar=message.get('avatar')):
st.markdown(message['content'])
# Accept user input
if prompt := st.chat_input('What is up?'):
# Display user message in chat message container
with st.chat_message('user'):
st.markdown(prompt)
real_prompt = combine_history(prompt)
# Add user message to chat history
st.session_state.messages.append({
'role': 'user',
'content': prompt,
})
with st.chat_message('robot'):
message_placeholder = st.empty()
for cur_response in generate_interactive(
model=model,
tokenizer=tokenizer,
prompt=real_prompt,
additional_eos_token_id=92542,
**asdict(generation_config),
):
# Display robot response in chat message container
message_placeholder.markdown(cur_response + '▌')
message_placeholder.markdown(cur_response)
# Add robot response to chat history
st.session_state.messages.append({
'role': 'robot',
'content': cur_response, # pylint: disable=undefined-loop-variable
})
torch.cuda.empty_cache()
if __name__ == '__main__':
main()
查看修改范围为
查看开发机的 ssh
连接。
记录端口号为 43670
, 拼接端口映射命令。
ssh -CNg -L 6006:127.0.0.1:6006 root@ssh.intern-ai.org.cn -p 43670
运行 /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/ 后,等待加载完成即可进行对话。
修改 web_demo.py
文件和原来的 InternLM2-Chat-1.8B
模型对话。
# 修改模型地址(第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',
再次运行 /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
进阶作业 1 将自我认知的模型上传到 OpenXLab
,并将应用部署到 OpenXLab
初始化 Git
配置
安装 git
和 git lfs
apt-get update
apt-get install git
apt-get install git-lfs
git lfs install
访问 OpenXLab
网站,查看用户名。
https://sso.openxlab.org.cn/usercenter
配置 Git Username
,用于作为 Git
提交的身份标识。
git config --global user.name "HuZhenghui"
配置 Git Email
git config --global user.email "hu@daonao.com"
创建模型仓库
在页面右上角单击创建,单击创建模型
进入创建模型仓库 https://openxlab.org.cn/models/create/repo 页面
填写模型仓库的基本信息,包括
仓库名称 | HuZhenghui-Robot |
---|---|
中文别称 | 胡争辉的机器人 |
任务类型 | 自然语言处理 |
问答 | |
对话 | |
开源协议 | Apache License 2.0 |
模型描述 | 胡争辉的机器人 |
可见 | 公开 |
单击创建,创建后的地址为
https://openxlab.org.cn/models/detail/HuZhenghui/HuZhenghui-Robot
单击模型文件,进入
https://openxlab.org.cn/models/detail/HuZhenghui/HuZhenghui-Robot/tree/main
单击右侧的下载按钮
记录下载命令
git clone https://code.openxlab.org.cn/HuZhenghui/HuZhenghui-Robot.git
克隆仓库
在开发机上运行命令克隆仓库
git clone https://code.openxlab.org.cn/HuZhenghui/HuZhenghui-Robot.git
创建 Git
访问令牌
单击页面右上角的头像,单击密钥管理
打开
https://openxlab.org.cn/security
在 Git
访问令牌中单击添加令牌
在令牌名称中输入 DEv
, 在令牌权限中选择可写,单击添加令牌
可以看到出现了名称为 Dev
的令牌。
单击复制按钮复制令牌。
测试 Git
访问令牌
创建 README.md
文件,提交
cd HuZhenghui-Robot/
touch README.md
git add README.md
git commit -m "README.md"
git push
出现提示时输入用户名和 Git
访问令牌。
整理模型文件
移动模型文件到仓库中
ls -al /root/ft/final_model/
mv /root/ft/final_model/* .
ls -al
提交模型文件
通过 LFS
管理所有的 .bin
和 .model
的模型文件
git lfs track "*.bin"
git lfs track "*.model"
提交
git add -A
git commit -m "upload model"
git push
完成后查看服务器上的模型页面
https://openxlab.org.cn/models/detail/HuZhenghui/HuZhenghui-Robot/tree/main
在 GitHub
上创建代码仓库
在 GitHub
上创建代码库
仓库地址为
https://github.com/huzhenghui/HuZhenghui-Rebot
创建文件 requirements.txt
创建文件 requirements.txt
gradio==4.10.0
transformers
sentencepiece
einops
accelerate
tiktoken
创建文件 packages.txt
git
git-lfs
创建文件 app.py
import gradio as gr
import os
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, AutoModel
from openxlab.model import download
base_path = './HuZhenghui-Robot/'
os.system(f'git clone https://code.openxlab.org.cn/HuZhenghui/HuZhenghui-Robot.git {base_path}')
os.system(f'cd {base_path} && git lfs pull')
tokenizer = AutoTokenizer.from_pretrained(base_path,trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(base_path,trust_remote_code=True)
def chat(message,history):
for response,history in model.stream_chat(tokenizer,message,history,max_length=2048,top_p=0.7,temperature=1):
yield response
gr.ChatInterface(chat,
title="HuZhenghui-Robot",
description="""
我是胡争辉的个人小助手.
""",
).queue(1).launch()
创建应用
在右上角单击创建按钮,选择创建应用。
选择 Gradio
单击开始创建
填写创建应用表单
应用名称 | HuZhenghui-Robot |
---|---|
任务类型 | 自然语言处理 |
对话 | |
GitHub 仓库 | https://github.com/huzhenghui/HuZhenghui-Rebot |
开源协议 | Apache-2.0 |
硬件资源 | 2vCPU 8GB |
镜像 | python=3.9 gradio=3.18.0 streamlit=1.32.0 torch=2.0.1 |
单击 GitHub
账号授权
授权后单击立即创建,等待应用创建。
构建成功后,等待启动
单击日志可以看到构建日志
在日志中单击 pip
可以看到构建过程中安装的包
部署后的位置为
https://openxlab.org.cn/apps/detail/HuZhenghui/HuZhenghui-Robot
进阶作业 2 复现多模态微调
开发机准备
进入开发机控制台。
https://studio.intern-ai.org.cn/console/instance
单击创建开发机。
在开发机名称中输入 LLaVA
, 在镜像中选 Cuda11.7-conda
,单击立即创建。
等待分配开发机,然后进入开发机。
准备环境
启动 Jupyter
进入 Terminal
创建环境
cd ~ && studio-conda xtuner0.1.17
查询环境列表
conda env list
激活环境
conda activate xtuner0.1.17
XTuner
安装
进入家目录
cd ~
创建版本文件夹并进入,以跟随本教程
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]' && cd ~
准备问答对
效法 LLaVA
作者的做法,将自己的图片发送给 GPT
,要求其按照上述格式生成若干条问答对。
准备图片
网站使用 Gemini
( https://gemini.google.com/ )
提示词使用 LLaVA
作者的提示词
Create a dataset for me, following this format.
[
{
"id": "<random_number_string>",
"image": "test_img/oph.jpg",
"conversations": [
{
"from": "human",
"value": "<image>\nDescribe this image."
},
{
"from": "gpt",
"value": "<answer1>"
},
{
"from": "human",
"value": "<question2>"
},
{
"from": "gpt",
"value": "<answer2>"
},
{
"from": "human",
"value": "<question3>"
},
{
"from": "gpt",
"value": "<answer3>"
}
]
}
]
The questions and answers, please generate for me, based on the image I sent to you. Thes questions should be from the shallow to the deep, and the answers should be as detailed and correct as possible. The questions and answers should be stick to the contents in the image itself, like objects, peoples, equipment, environment, purpose, color, attitude, etc. 5 question and answer pairs.
输出结果为
[
{
"id": "123456",
"image": "IMG_6283.jpg",
"conversations": [
{
"from": "human",
"value": "<image>\nDescribe this image."
},
{
"from": "gpt",
"value": "The image appears to be a public advertisement for something called 'Public Imus'. Without additional context, it's difficult to say exactly what 'Public Imus' is, but it could be a product, service, organization, or event."
},
{
"from": "human",
"value": "Is there any text or logos in the ad that provide clues about Public Imus?"
},
{
"from": "gpt",
"value": "It depends on the specific details of the ad, which may vary. However, public advertisements typically include some text or logo to identify the product, service, organization, or event being advertised. If there is text in the ad, it might describe what Public Imus is, its benefits, or a call to action."
},
{
"from": "human",
"value": "What colors are used in the ad?"
},
{
"from": "gpt",
"value": "Again, I can't access the specific image you sent, but colors can be important in advertising. They can evoke certain emotions or associations in viewers. Common colors used in advertising include red (excitement, urgency), blue (trust, security), yellow (happiness, optimism), and green (growth, health). The choice of colors may depend on the target audience and the message the advertiser wants to convey."
}
]
}
]
上传到开发机
制作训练数据
克隆教程仓库
cd ~ && git clone https://github.com/InternLM/tutorial -b camp2 && conda activate xtuner0.1.17 && cd tutorial
生成数据
python /root/tutorial/xtuner/llava/llava_data/repeat.py \
-i /root/xtuner0117/data/IMG_6283.json \
-o /root/xtuner0117/data/repeated_data.json \
-n 200
创建配置文件
查询 XTuner
内置配置文件
xtuner list-cfg -p llava_internlm2_chat_1_8b
拷贝配置文件到当前目录
xtuner copy-cfg \
llava_internlm2_chat_1_8b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune \
/root/xtuner0117/data/
此时文件夹结构如下
修改配置文件
按照如下修改 llava_internlm2_chat_1_8b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune_copy.py
文件
变量 | 值 |
---|---|
pretrained_pth | /root/share/new_models/xtuner/iter_2181.pth |
llm_name_or_path | /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b |
visual_encoder_name_or_path | /root/share/new_models/openai/clip-vit-large-patch14-336 |
data_root | /root/xtuner0117/data/ |
data_path | data_root + 'repeated_data.json' |
image_folder | data_root |
batch_size | 1 |
evaluation_inputs | ['Please describe this picture','What is the equipment in the image?'] |
开始 Finetune
cd /root/xtuner0117/data/
xtuner train /root/xtuner0117/data/llava_internlm2_chat_1_8b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune_copy.py --deepspeed deepspeed_zero2
查看生成的文件
tree -a -Q -p -h -F --sort=name -I .ipynb_checkpoints /root/xtuner0117/data/
对比 Finetune
前后的性能差异
Finetune
前
加载
1.8B
和Pretrain
阶段产物(iter_2181
) 到显存。
pth
转 huggingface
# 解决小bug
export MKL_SERVICE_FORCE_INTEL=1
export MKL_THREADING_LAYER=GNU
# pth转huggingface
xtuner convert pth_to_hf \
llava_internlm2_chat_1_8b_clip_vit_large_p14_336_e1_gpu8_pretrain \
/root/share/new_models/xtuner/iter_2181.pth \
/root/xtuner0117/data/iter_2181_hf
启动
xtuner chat /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b \
--visual-encoder /root/share/new_models/openai/clip-vit-large-patch14-336 \
--llava /root/xtuner0117/data/iter_2181_hf \
--prompt-template internlm2_chat \
--image /root/xtuner0117/data/IMG_6283.jpg
输入测试问题
Describe this image.
What is the equipment in the image?
Finetune
后
加载
1.8B
和Fintune
阶段产物 到显存。
pth
转 huggingface
# 解决小bug
export MKL_SERVICE_FORCE_INTEL=1
export MKL_THREADING_LAYER=GNU
# pth转huggingface
xtuner convert pth_to_hf \
/root/xtuner0117/data/llava_internlm2_chat_1_8b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune_copy.py \
/root/xtuner0117/data/work_dirs/llava_internlm2_chat_1_8b_qlora_clip_vit_large_p14_336_lora_e1_gpu8_finetune_copy/iter_200.pth \
/root/xtuner0117/data/llava_data/iter_200_hf
启动
xtuner chat /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b \
--visual-encoder /root/share/new_models/openai/clip-vit-large-patch14-336 \
--llava /root/xtuner0117/data/llava_data/iter_200_hf \
--prompt-template internlm2_chat \
--image /root/xtuner0117/data/IMG_6283.jpg
输入测试问题
Describe this image.
What is the equipment in the image?
使用非训练图片
Finetune
前
xtuner chat /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b \
--visual-encoder /root/share/new_models/openai/clip-vit-large-patch14-336 \
--llava /root/xtuner0117/data/iter_2181_hf \
--prompt-template internlm2_chat \
--image /root/tutorial/xtuner/llava/llava_data/test_img/oph.jpg
输入测试问题
Describe this image.
What is the equipment in the image?
Finetune
后
xtuner chat /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b \
--visual-encoder /root/share/new_models/openai/clip-vit-large-patch14-336 \
--llava /root/xtuner0117/data/llava_data/iter_200_hf \
--prompt-template internlm2_chat \
--image /root/tutorial/xtuner/llava/llava_data/test_img/oph.jpg
输入测试问题
Describe this image.
What is the equipment in the image?