CogVLM 单轮对话微调逐步记录

0. 部署CogVLM

CogVLM 是一个强大的开源视觉语言模型(VLM),支持490*490分辨率的图像理解和多轮对话。这次我要对其微调的是单轮对话功能。关于模型的部署网络上已经有了详细的步骤,就不再赘述,官方网址:https://github.com/THUDM/CogVLM

1. 修改配置文件

(1)修改finetune_cogvlm_lora.sh

先打开下载的代码,找到CogVLM
/finetune_demo/finetune_cogvlm_lora.sh文件,根据实际情况修改其中的内容,以下是我修改了的地方:

# 训练用的GPU数量,这里我用的两张4090
NUM_GPUS_PER_WORKER=2

# 设置模型并行数
MP_SIZE=2
# 修改要微调的模型类型,这里我用的是cogvlm-chat-v1.1
MODEL_TYPE="cogvlm-chat-v1.1"
VERSION="base"

# 修改local_tokenizer的地址,可以直接到huggingface去下载然后放到自己的目录,也可以如官方配置下这样写,之后微调时会自动下载到lmsys/vicuna-7b-v1.5目录下(但是很大概率下载失败)
MODEL_ARGS="--from_pretrained $MODEL_TYPE \
    --max_length 1288 \
    --lora_rank 10 \
    --use_lora \
    --local_tokenizer lmsys/vicuna-7b-v1.5 \
    --version $VERSION"
# 修改模型所在地址,这里有的Linux系统需要写为从HOME开始的绝对路径,否则会报错(踩坑)
OPTIONS_SAT="SAT_HOME=~/.sat_models"

#修改为自己的训练集与验证集地址,这个格式稍后会说
train_data="./archive_split/train"
valid_data="./archive_split/valid"

我们注意到最后有这样一行代码,发现其执行的文件为finetune_cogvlm_demo.py(稍后再看),并且使用了deepspeed,这是一个深度学习优化库,用来分布式训练。这里想要正确运行,需要对照官方给出的requirements.txt配置环境,尤其是transformers和torch(不要想着省事,否则后面会有很多报错都是因为版本不一致,这里最好直接新建一个conda环境)

run_cmd="${OPTIONS_NCCL} ${OPTIONS_SAT} deepspeed --master_port 16666 --hostfile ${HOST_FILE_PATH} finetune_cogvlm_demo.py ${gpt_options}"

在用slurm提交作业时若出现以下错误:


                               !! WARNING !!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Your compiler (c++ 4.8.5) may be ABI-incompatible with PyTorch!
Please use a compiler that is ABI-compatible with GCC 5.0 and above.
See https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html.

See https://gist.github.com/goldsborough/d466f43e8ffc948ff92de7486c5216d6
for instructions on how to install GCC 5 or higher.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                              !! WARNING !!

  warnings.warn(ABI_INCOMPATIBILITY_WARNING.format(compiler))
  Detected CUDA files, patching ldflags
  ...
  Building extension module fused_adam...

编译器版本不对将导致后面报错:
RuntimeError: Error building extension 'fused_adam'

这里是因为编译器版本不对,需要在脚本中直接指定编译器的版本,我是这样写的:

module load anaconda/2022.10
export PYTHONUNBUFFERED=1
module load cuda/12.1
source activate cogvlm
module load complier/gcc/12.2.0

(2)修改finetune_cogvlm_demo.py

在尝试运行第一次后发现报错ModuleNotFoundError: No module named 'utils.utils'
寻找这个模块,发现它是存在的,并且里面的__init__.py文件都是完整的,所以推测可能是因为模块重名了,这里将github上下载的utils文件名改为util,并修改导入语句,成功解决该问题。

from util.models import FineTuneTrainCogVLMModel
from util.utils import llama2_text_processor, llama2_text_processor_inference, get_image_processor
from util.utils import ItemDataset
from util.utils import llama2_tokenizer

网上查到说若想显存不够,报错CUDA out of memory,修改batch_size=1可以降低显存需求(当然效果会变差),发现代码里已经是1了,不需要修改,这里只是写下做记录。

由这行代码可以看出之后生成的结果所存储的位置,之后可以根据需要修改地址。

args.save = "checkpoints/merged_lora_cogvlm{}".format(args.eva_args["image_size"][0])
# 由这行代码,推测出数据集的格式应该在util.utils的某个函数中,我们可以去寻找一下。
from util.utils import ItemDataset
def create_dataset_function(image_processor, text_processor, path, args):
    dataset = ItemDataset(image_processor, text_processor, args, path)
    return dataset

(3)修改dataset.py

到这里需要先看一下数据读入的格式,我们先来看官方给出的代码(部分关键地方)

class ItemDataset(Dataset):
    def __init__(self, image_processor, text_processor, args, data_dirs, cross_image_processor=None, **kwargs):
        super().__init__()
        self.data = self.load_data(data_dirs)
        self.image_processor, self.text_processor, self.cross_image_processor = image_processor, text_processor, cross_image_processor

    def process_text(self, answer, prompt):
        return self.text_processor(answer, prompt)

    def __getitem__(self, index):
        data = self.data[index]
        # img
        try:
            img = Image.open(data).convert('RGB')
        except Exception as e:
            print_rank0(e, level=logging.WARNING)
            return {}
        img_dict = self.process_img(img)
        # text
        label = data.split('/')[-1].split('.')[0]
        uni_key = label
        text_dict = self.process_text(label, "CAPTCHA:")
        if text_dict is None:
            print_rank0(f"Process text failed. Please check the max_target_length & max_source_length.\n The data is {data}", level=logging.WARNING)
            return {}
        # other attr
        ret = {**img_dict, **text_dict, "question_id": uni_key}
        return ret

分析发现,该数据读入的过程为:
(1)先通过self.load_data(data_dirs)读入所有图片地址。
(2)然后交由__getitem__()函数逐个分析。
(3)图片由img = Image.open(data).convert('RGB')直接读取图片。
(4)文字部分由label = data.split('/')[-1].split('.')[0]处理出这个图片的文件名,用来做后续训练时模型应该给出的回答。
(5)这里的uni_key = label之所以可以直接写成文件名的内容,是因为官方给出的数据集是验证码的图片,没有重复的。如果是用自己的数据集且希望模型输出的内容有可能重复,这里最好给每个问题设置一个唯一的id。
(6)由 process_text()函数中的self.text_processor(answer, prompt)推测 text_dict = self.process_text(label, "CAPTCHA:")中label就是模型的回答,"CAPTCHA:"就是prompt。
于是我根据自己的数据集格式,对dataset.py进行了修改,修改后的代码如下:

import os
import logging
import random
import logging
import jsonlines
from io import BytesIO
from PIL import Image
from torch.utils.data import Dataset
from sat.helpers import print_rank0

import json
class ItemDataset(Dataset):
    def __init__(self, image_processor, text_processor, args, data_dirs, cross_image_processor=None, **kwargs):
        super().__init__()
        self.data = []
        with open(data_dirs, 'r', encoding='UTF-8') as f:#训练集json的路径
            for line in f.readlines():
                dic = json.loads(line)
                self.data.append(dic)
        if(self.data):
            self.image_processor, self.text_processor, self.cross_image_processor = image_processor, text_processor, cross_image_processor
        else:
            print_rank0(f"There are no datasets",level=logging.WARNING)

    def process_img(self, img):
        img_dict = {'vision': self.image_processor(img)}
        if self.cross_image_processor:
            img_dict.update({'cross': self.cross_image_processor(img)})
        return img_dict
    
    def process_text(self, answer, prompt):
        return self.text_processor(answer, prompt)
    
    def __len__(self):
        return len(self.data)

    def __getitem__(self, index):
        imgs=self.data[index]['image']
        # data = self.data[index]
        try:
            img = Image.open(imgs).convert('RGB')
        except Exception as e:
            print_rank0(e, level=logging.WARNING)
            return {}
        img_dict = self.process_img(img)

        # text
        ques=self.data[index]['question']
        ans=self.data[index]['answer']
        uni_key = self.data[index]['question_id']
        text_dict = self.process_text(ans, ques)

        if text_dict is None:
            print_rank0(f"Process text failed. Please check the max_target_length & max_source_length.\n The data is {imgs}", level=logging.WARNING)
            return {}
        # other attr
        ret = {**img_dict, **text_dict, "question_id": uni_key}
        return ret

我自己的数据集格式为:

# trains.json

{"image": "(图片路径)", "question_id": 1, "question": "图中有几个人?", "answer": "8个"}
{"image": "(图片路径)", "question_id": 2, "question": "桌子上的苹果是什么颜色的?", "answer": "绿色的"}

(4)修改model_config.json

如果有需要的话,可以根据实际情况对其中的参数进行调整

2. 运行finetune_cogvlm_lora.sh

提交作业语句:sbatch -p gpu_4090 --gpus=2 ./finetune_cogvlm_lora.sh
提交之后会生成作业号,若想实时查看日志文件,可以用tail -f slurm-作业号.out查看。

运行成功后会有生成finetune_demo/checkpoints/merged_lora_cogvlm490,其中文件包括:
在这里插入图片描述

3. 合并模型

按照官方给出的指令,创建以下脚本并执行:

#! /bin/bash
# export PATH=/usr/local/cuda/bin:$PATH
# export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
module load anaconda/2022.10
export PYTHONUNBUFFERED=1
module load cuda/12.1
source activate cogvlm
module load complier/gcc/12.2.0
torchrun --standalone --nnodes=1 --nproc-per-node=2 utils/merge_model.py --version base --bf16 --from_pretrained ./checkpoints/merged_lora_cogvlm490

其中因为我在开始设置了MP_SIZE=2,所以这里的nproc-per-node也设置为2。合并完后生成:
在这里插入图片描述

4. 微调结束!

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值