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。合并完后生成: