MiniGPT4Qwen:一个高效的多模态大模型训练方案

释放双眼,带上耳机,听听看~!
MiniGPT4Qwen项目采用Qwen-7B-Chat作为LLM,用MiniGPT4的对齐方式,更加高效地训练了一个MLLM,能够处理图文对话数据并采用多模态接入的技术方案,同时对比了BLIP2方案,是一个值得关注的训练方案。

更新:已支持Gradio WebUI! 求Star!另:内部是支持lora微调的

代码库:github.com/Coobiw/Mini…,已更新中文README Tutorials、处理好的数据json文件和训练checkpoint,可以快速进行demo尝试或完整复现

简介

MiniGPT4是最近很火的一个MLLM(Multi-modal Large Language Model)项目,他证明了对于BLIP2的ViT+Q-former这种已经与语言模态做了对齐预训练的结构,只需要重训一个Linear层,便可以接入新的LLM。

这个想法看似简单,实际上,对于现在这个每个月有一个新的更强的LLM出来的时代,这种构建多模态大模型的方式是十分高效的。

然而,MiniGPT4采用LLaMA、Vicuna作为语言模型,它们的中文支持相对较弱,导致训练出的MLLM对中文支持不好。而现在也有许多开源出来的中文LLM,如:通义千问、百川、GLM等。

本项目使用70亿参数的Qwen-7B-Chat作为LLM,用MiniGPT4的对齐方式,更加高效地训练了一个MLLM,名为 Minigpt4Qwen。相比MiniGPT4的两阶段训练(较低质量数据进行图文模态对齐 + 高质量数据指令微调),本项目仅仅采用18.8k的高质量指令微调数据,经过单阶段预训练即可达到很好的效果。(这里参考了InstructionGPT-4,200条高质量指令微调数据战胜MiniGPT-4这一结论)

MiniGPT4Qwen:一个高效的多模态大模型训练方案

添加图片注释,不超过 140 字(可选)

资源介绍

  • 模型规模:1B EVA-ViT-G + 180M Q-former(BLIP2) + 7B Qwen-Chat = 约7.3B,采用bf16或fp16的精度,模型本身占用约15G显存
  • 数据规模:minigpt4 指令微调数据(双语) + llava 部分指令微调数据(双语) = 18.8k 高质量图文对话数据(单轮对话,未经过多轮对话训练)(数据源:MMPretrain PR 1758,下载地址:huggingface.co/datasets/de…
  • 计算资源:2h 8 * 3090 24G,也可以单卡3090 24G(单卡的话建议调高一些gradient accumulation,训练曲线会更平稳)

数据简介

数据来自mmpretrain中的一个PR,里面含有中英双语数据,中文数据是用英文的指令微调数据经过chatgpt翻译得到的!

多模态接入的技术方案简介

这里想对比BLIP2和Qwen-VL模型,简单介绍一下MiniGPT4Qwen中,是如何将图像模态接入LLM中的

BLIP2方案:image embedding作为绝对前缀和LLM处指令的word embedding进行拼接

先简单放一小段代码,然后再解释小标题里的“绝对前缀”:

# 1.
inputs_llm = self.llm_proj(query_output.last_hidden_state[:,:query_tokens.size(1),:])
# 2.
atts_llm = torch.ones(inputs_llm.size()[:-1], dtype=torch.long).to(image.device)

llm_tokens = self.llm_tokenizer(
    prompt,
    padding="longest",
    return_tensors="pt"
).to(image.device)

with self.maybe_autocast():
    # 3.
    inputs_embeds = self.llm_model.get_input_embeddings()(llm_tokens.input_ids)
    inputs_embeds = torch.cat([inputs_llm, inputs_embeds], dim=1)
    attention_mask = torch.cat([atts_llm, llm_tokens.attention_mask], dim=1)
    outputs = self.llm_model.generate(
               # 4.
               inputs_embeds=inputs_embeds,
               attention_mask=attention_mask,
               do_sample=use_nucleus_sampling,
               top_p=top_p,
               temperature=temperature,
               num_beams=num_beams,
               max_length=max_length,
               min_length=min_length,
               # eos_token_id=self.eos_token_id,
               repetition_penalty=repetition_penalty,
               length_penalty=length_penalty,
               num_return_sequences=num_captions,
           )

对上述四处代码,标号1、2、3、4:

  • 1号代码是将Q-former输出的视觉token经过一个linear层,使其通道维度与LLM的通道维度一致
  • 2号代码是将文本的输入tokens对应到其word embedding
  • 3号代码将二者concat到一起
  • 4号代码,二者concat的结果共同作为LLM的attention blocks的输入

这里就明白,BLIP2的限制在于,image tokens永远在最前面,没有办法灵活的进行插入,这对于一些多图推理的情形可能不那么灵活(虽然咱MiniGPT4Qwen也没支持多图哈,但就是觉得不够优雅hhh),所以我称它为“绝对前缀”。

Qwen-VL方案:tokenizer中加入特殊token进行分隔

Qwen-VL引入了MiniGPT4Qwen:一个高效的多模态大模型训练方案和两个special token,因为我个人不是搞NLP的,最开始没咋接触过tokenizer的时候也不太懂special token,所以这里就简单介绍一下special token的大致定义:

因为Qwen-VL采用的BBPE分词器是基于sub-word的,举个不恰当但容易理解的例子:preprocess这个词,由于pre是一个常见的前缀,所以preprocess很可能会被tokenize成pre、pro、cess,具体而言BBPE会更复杂,是byte-level的处理。而special token是不同的,他们是不可拆分的,一定会被作为单独的完成的词去处理。

Qwen-VL使用这两个不可分割的special token单位,来定位image embedding的位置,大致可以参考tokenization_qwen.py中的:

def _replace_closed_tag(
    input_tokens: List[Any],
    start_tags: Union[Any, Tuple[Any]],
    end_tags: Union[Any, Tuple[Any]],
    inclusive_replace_func: Callable,
    exclusive_replace_func: Callable = lambda x: x,
):
    if isinstance(start_tags, (str, int)):
        start_tags = (start_tags,)
    if isinstance(end_tags, (str, int)):
        end_tags = (end_tags,)
    assert len(start_tags) == len(end_tags)

    output_tokens = []
    end = 0
    while True:
        start = _list_find(input_tokens, start_tags, end)
        if start == -1:
            break
        output_tokens.extend(exclusive_replace_func(input_tokens[end : start]))
        tag_idx = start_tags.index(input_tokens[start])
        end = _list_find(input_tokens, (end_tags[tag_idx],), start)
        if end == -1:
            raise ValueError("Unclosed image token")
        # 1.
        output_tokens.extend(inclusive_replace_func(input_tokens[start : end + 1]))
        end += 1
    output_tokens.extend(exclusive_replace_func(input_tokens[end : ]))
    return output_tokens

def tokenize(
        self,
        text: str,
        allowed_special: Union[Set, str] = "all",
        disallowed_special: Union[Collection, str] = (),
        **kwargs,
    ) -> List[Union[bytes, str]]:
    tokens = []
        text = unicodedata.normalize("NFC", text)

        # this implementation takes a detour: text -> token id -> token surface forms
        for t in self.tokenizer.encode(
            text, allowed_special=allowed_special, disallowed_special=disallowed_special
        ):
            tokens.append(self.decoder[t])

        def _encode_imgurl(img_tokens):
            assert img_tokens[0] == self.image_start_tag and img_tokens[-1] == self.image_end_tag
            img_tokens = img_tokens[1:-1]
            img_url = b''.join(img_tokens)
            out_img_tokens = list(map(self.decoder.get, img_url))
            if len(out_img_tokens) > IMG_TOKEN_SPAN:
                raise ValueError("The content in {}..{} is too long".format(
                    self.image_start_tag, self.image_end_tag))
            out_img_tokens.extend([self.image_pad_tag] * (IMG_TOKEN_SPAN - len(out_img_tokens)))
            out_img_tokens = [self.image_start_tag] + out_img_tokens + [self.image_end_tag]
            return out_img_tokens

        # 2.
        return _replace_closed_tag(tokens, self.image_start_tag, self.image_end_tag, _encode_imgurl)

这部分代码不是很好截取,如果只是了解的话,不需要仔细看,只简单标号了两条代码,是关键的将MiniGPT4Qwen:一个高效的多模态大模型训练方案和中间部分的image tokens取出来的代码。

这样引入special token的不好之处在于:需要重新训练word_embedding层和最后的lm_head输出层,这两个层加起来可是有1.2B的参数量哦,还是很heavy的!

MiniGPT4Qwen方案:使用Qwen-VL的tokenizer里的<|extra_0|> token作为一个占位符,后面用image_embedding代替即可

Qwen-VL中的<|extra_0|>这个token是正常情况下不会被使用的,所以这里用它作为占位符,由于vit+q-former的输出长度是定长的32个tokens,所以将这个占位符复制32次即可,最后得到vision embedding之后再进行替换。

值得一提的是,同时,还可以用:

replace_image_idxs = torch.where(llm_tokens == self.replace_image_token_id)
inputs_embeds = self.llm_model.get_input_embeddings()(llm_tokens) # B, L, C
_,_,channels = inputs_embeds.shape
inputs_embeds[replace_image_idxs[0],replace_image_idxs[1]] = 
                inputs_llm.view(-1,channels).to(inputs_embeds.dtype)

可以根据torch.where这一条代码,确定要替换的image tokens的位置,这样图像tokens的位置也是完全灵活的!在MiniGPT4Qwen中,采用instruction中的”这一个word来标识图像插入到文本中的位置,比如:

图像<Img><ImageHere></Img>中的内容是什么?
# 注意:这里<Img></Img>并没有作为special tokens,
# 只是一个正常的文本,可以一定程度上让模型理解到这里是图像的输入!
# 图像被插入到文本中央啦!
# 或者
图像1<Img><ImageHere></Img>和图像2<Img><ImageHere></Img>中的不同之处是什么?
# 多图推理!虽然MiniGPT4Qwen并没有支持多图
#(是因为我在代码中加了强约束),其实应该是可以支持的,但没来得及做hhh

运行示例

MiniGPT4Qwen:一个高效的多模态大模型训练方案

MiniGPT4Qwen:一个高效的多模态大模型训练方案

可以看到,还是有不错的caption和reasoning能力的,中文支持也不错~

WebUI 运行示例

MiniGPT4Qwen:一个高效的多模态大模型训练方案

MiniGPT4Qwen:一个高效的多模态大模型训练方案

开启do_sample和beam search

MiniGPT4Qwen:一个高效的多模态大模型训练方案

复制README中的Tutorials,能访问github的就直接跳转吧~

github.com/Coobiw/Mini…

因为考虑到一些同学github访问不稳定,所以这里也贴上了README Tutorials,这也同时能方便不太确定能不能用我代码的同学们现在知乎看一下,判断下,然后再跳转,记得点star哦~

Getting Started

模型下载

请将模型权重下载后都放在 cache/ckpt下

 mkdir cache
 cd cache
 mkdir ckpt
 mkdir dataset

1.下载BLIP2的相关权重 (a) eva vit-g eva_vit_g.pth

wget https://storage.googleapis.com/sfr-vision-language-research/LAVIS/models/BLIP2/eva_vit_g.pth

(b) bert-base-uncased huggingface,下载如下的文件即可

MiniGPT4Qwen:一个高效的多模态大模型训练方案

添加图片注释,不超过 140 字(可选)

(c) blip2_pretrained_flant5xxl blip2_pretrained_flant5xxl.pth

 wget https://storage.googleapis.com/sfr-vision-language-research/LAVIS/models/BLIP2/blip2_pretrained_flant5xxl.pth

2.下载Qwen7B-chat的权重 Qwen-7B-chat huggingface

3.下载本模型的checkpoint(建议放入 lavis/output/) 在本仓库的release里放有checkpoint,可以直接下载

wget https://github.com/Coobiw/MiniGPT4Qwen/releases/download/instruction-data_and_checkpointv1.0/ckpt.zip  
unzip ckpt.zip

目录结构:

 ├── cache
 │   ├── ckpt
 │   │   ├── bert-base-uncased
 │   │   ├── blip2
 │   │   │   ├── blip2_pretrained_flant5xxl.pth
 │   │   ├── eva
 │   │   │   ├── eva_vit_g.pth
 │   │   ├── Qwen7B-chat

运行test_model_chat.py进行初步尝试

python test_model_chat.py

你可以修改里面的ckpt_pathimg_path

运行命令行demo

python cli_demo.py --checkpoint-path xxxxxx

运行后需要输入图片路径,输入后进入对话 常见操作:

:help 查看help :clear 清空当前命令行 :clh 清空对话历史(但图像输入不会更改) :his 查看对话历史 :img 查看输入的图像路径

训练

数据准备

本数据集共含有18.8k个图文对,来自MMPretrain根据llava和minigpt4处理得到,下载链接:huggingface 为了支持当前的 lavis库的训练框架,我对数据集的annotations进行了重新处理,放到了本仓库的release中,下载链接:instruction_data

 wget https://github.com/Coobiw/MiniGPT4Qwen/releases/download/instruction-data_and_checkpointv1.0/instruction_data.zip
 unzip instruction_data

最后需要将数据集放入 ./cache/dataset中,目录结构如下:

 ├── cache
 │   └── dataset
 │       ├── llava
 │   │   │   ├── llava_minigpt4qwen_format.json
 │   │   │   ├── image
 │       ├── minigpt4
 │   │   │   ├── image
 │   │   │   ├── minigpt4_minigpt4qwen_format.json

config文件的书写

请参考train.yaml

运行train.py

单卡:

 CUDA_VISIBLE_DEVICES=xxx python train.py --cfg-path lavis/projects/instruction_tuning/train.yaml

多卡:

 CUDA_VISIBLE_DEVICES=xxx python -m torch.distributed.run --nproc_per_node=8 train.py --cfg-path lavis/projects/instruction_tuning/train.yaml

参考

  • Lavis 本仓库是基于lavis进行构建的
  • QwenLM 本仓库的语言模型采用Qwen-7B-Chat
  • MiniGPT4 本仓库的主要思想来自MiniGPT4
  • MMPretrain 提供所需的双语指令微调数据集
本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

星系中的异常现象及Astronomaly算法的应用

2023-11-24 8:39:14

AI教程

T2I-Adapter-SDXL: 强大的模型控制和生成能力

2023-11-24 8:46:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索