《书生·浦语大模型实战营》第四节课《XTuner 微调 LLM:1.8B、多模态、Agent》实践笔记

《书生·浦语大模型实战营》第四节课《XTuner 微调 LLM:1.8B、多模态、Agent》实践笔记

本次学习

2024年3月开始参加《书生·浦语大模型实战营》

https://openxlab.org.cn/models/InternLM/subject

相关链接

第一节课《书生·浦语大模型全链路开源体系》课程及 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训练所使用的数据集
Epoche3epoch 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

其中

  • convertXTuner 的模型转换子命令
  • pth_to_hf 表示 将 pth 模型转换为 HuggingFace 模式
  • /root/ft/train/internlm2_1_8b_qlora_alpaca_e3_copy.py 为配置文件
  • /root/ft/train/iter_768.pthpth 模型路径
  • /root/ft/huggingfaceHuggingFace 模型路径

在这里插入图片描述

运行耗时为

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 配置

安装 gitgit 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_pathdata_root + 'repeated_data.json'
image_folderdata_root
batch_size1
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.8BPretrain 阶段产物( iter_2181 ) 到显存。

pthhuggingface

# 解决小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.8BFintune 阶段产物 到显存。

pthhuggingface

# 解决小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?

在这里插入图片描述

  • 15
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值