【多模态】MiniCPM-V多模态大模子使用学习

打印 上一主题 下一主题

主题 816|帖子 816|积分 2448

媒介

前面学习了一些常见多模态模子的架构,现在开始学习使用minicpm-v-2.6模子,记载学习过程,接待品评指正~
排行榜上数据供参考,测试下来qwen2-vl轻微好一点点,然后minivpm-v-2.6稍差一点点

1. 模子文件下载和选择

   在modelscope上下载,此中int4的模子推理显存占用7-9GB,效果和全量模子很接近。全量模子下载推理可能轻微慢一些,int4就够用了,而且int4的推理挺快的,平均不到0.5秒一张图,如果同时开4个进程就是一秒钟4张图左右。
  1. #模型下载
  2. from modelscope import snapshot_download
  3. model_dir = snapshot_download('OpenBMB/MiniCPM-V-2_6-int4',cache_dir='要存放模型的路径')
复制代码
2. 环境安装配置

   有几个点需要注意的:


  • 官方飞书文档里面说微调时deepspeed需要手动安装,不知道为什么手动下载源码的安装的跑不乐成,主动安装的pip install deepspeed最新的比如0.15.0,微调就不会报错
  • swift的安装最好直接安装,不要从源代码安装,否则万一删除了源代码环境就无了,然后也不方便,直接pip install ‘ms-swift[llm]’ -U
  • 如果要和qwen2-vl的环境通用,注意pip install transformers==4.46.1
  • flash-attn可以先在官方github上下载whl文件,如果网速慢的话,一样平常直接pip install flash-attn就行
  • 如果要使用vllm安装pip install vllm
3. 模子微调

微调有好几种选择:(1)qlora微调minicpm-v-int4;(2)lora微调minicpm-v;(3)lora微调minicpm-v-int4,然后量化为int4


  • 测试下来(1)(2)(3)正确率的差距不大,可能有的情况下(2)比(3)好一点点
  • 显卡试了RTX-8000和A100-40/80GB,还是A100比较好,RTX-8000跑大半天,A100半小时到一小时
3.1 qlora微调minicpm-v-int4

  qlora微调时需要把–tune_vision设置为false,同时–qlora设置为true
显存开销上,ds_config_zero2和batchsize=1的情况下,qlora大概30-40GB显存开销,如果显存够大用ds_config_zero2,否则用ds_config_zero3(训练速度变慢)。
  训练时如果出现data fetch error注意检查路径和数据json文件的格式,应该不会有其他什么问题。
  1. #!/bin/bash
  2. GPUS_PER_NODE=1 # 改成你的机器每个节点共有多少张显卡,如果是单机八卡就是8
  3. NNODES=1 # 改成你的机器有多少个节点,如果就是一台服务器就是1
  4. NODE_RANK=0 # 使用第几个服务器训练
  5. MASTER_ADDR=localhost
  6. MASTER_PORT=6001
  7. MODEL="/root/ld/ld_model_pretrained/Minicpmv2_6" # 本地模型路径 or openbmb/MiniCPM-V-2.5
  8. # ATTENTION: specify the path to your training data, which should be a json file consisting of a list of conversations.
  9. # See the section for finetuning in README for more information.
  10. DATA="/root/ld/ld_project/MiniCPM-V/finetune/mllm_demo.json" # 训练数据文件地址
  11. LLM_TYPE="qwen2" # if use openbmb/MiniCPM-V-2, please set LLM_TYPE=minicpm
  12. export NCCL_P2P_DISABLE=1 # a100等支持nccl_p2p的显卡去掉此行
  13. export NCCL_IB_DISABLE=1 # a100等显卡去掉此行
  14. DISTRIBUTED_ARGS="
  15.     --nproc_per_node $GPUS_PER_NODE \
  16.     --nnodes $NNODES \
  17.     --node_rank $NODE_RANK \
  18.     --master_addr $MASTER_ADDR \
  19.     --master_port $MASTER_PORT
  20. "
  21. .conda/envs/yourenv/python -m torchrun $DISTRIBUTED_ARGS finetune.py  \
  22.     --model_name_or_path $MODEL \
  23.     --llm_type $LLM_TYPE \
  24.     --data_path $DATA \
  25.     --remove_unused_columns false \
  26.     --label_names "labels" \ # 数据构造,不要动
  27.     --prediction_loss_only false \
  28.     --bf16 false \ # 使用bf16精度训练,4090,a100,h100等可以开启
  29.     --bf16_full_eval false \ # 使用bf16精度测试
  30.     --fp16 true \ # 使用fp16精度训练
  31.     --fp16_full_eval true \ # 使用pf16精度测试
  32.     --do_train \ # 是否训练
  33.     --tune_vision true \ # 是否微调siglip(vit)模块
  34.     --tune_llm false \ # 是否微调大语言模型模块
  35.     --use_lora true \ # 是否lora微调
  36.     --lora_target_modules "llm\..*layers\.\d+\.self_attn\.(q_proj|k_proj|v_proj)" \ #lora插入的层,这里写的是正则表达式,建议不改
  37.     --model_max_length 2048 \ # 模型训练的最大长度
  38.     --max_slice_nums 9 \ # 模型最大切分次数
  39.     --max_steps 1000 \ # 最多训练步数
  40.     --output_dir output/output_minicpmv2_lora \ # 模型lora保存地址
  41.     --logging_dir output/output_minicpmv2_lora \ # 日志保存地址
  42.     --logging_strategy "steps" \ # 日志输出策略(可选epoch)
  43.     --per_device_train_batch_size 2 \ # 每张卡训练的batch_size
  44.     --gradient_accumulation_steps 1 \ # 梯度累积,当显存少时可以增大这个参数从而减少per_device_train_batch_size
  45.     --save_strategy "steps" \ # 保存策略(可选epoch)与save_steps同时起作用
  46.     --save_steps 1000 \ # 1000个step保存一次
  47.     --save_total_limit 1 \ # 最大储存总数
  48.     --learning_rate 1e-6 \ # 学习率
  49.     --weight_decay 0.1 \ # 权重正则化参数
  50.     --adam_beta2 0.95 \ #
  51.     --warmup_ratio 0.01 \ # 总步数的预热率,即:总训练步数*warmup_ratio=预热步数
  52.     --lr_scheduler_type "cosine" \ # 学习率调整器
  53.     --logging_steps 10 \
  54.     --gradient_checkpointing false \ # 梯度检查点,建议开启,极大减少显存使用
  55.     --deepspeed ds_config_zero2.json \ # 使用zero3,显存充足建议使用ds_config_zero2.json
复制代码
3.2 lora微调minicpm-v

  显存开销上,ds_config_zero2和batchsize=1的情况下,lora大概77-79GB显存开销,如果显存够大用ds_config_zero2,否则用ds_config_zero3(训练速度变慢)
3.3 merge_lora

  使用官方飞书文档里面的copy之后,注意需要检查是否拷贝全了,通常会由于原始模子目次下面产生了asset等临时文件,会报错然后漏拷贝image_processing_minicpmv.py、preprocessor_config.json和processing_minicpmv.py。


  • 注意,这里如果存的是bin而不是safetensor格式的文件,后面使用官方飞书文档里面的awq量化会报错,awq量化那里输入要求safetensor格式存储的模子
  1. from peft import PeftModel
  2. from transformers import AutoModel, AutoTokenizer
  3. import os
  4. import shutil
  5. model_type = "原始minicpm-v模型地址"  # Local model path or huggingface id
  6. path_to_adapter = "存放输出lora文件的地址"  # Path to the saved LoRA adapter
  7. merge_path = "合并后模型地址"  # Path to save the merged model
  8. # 保证原始模型的各个文件不遗漏保存到merge_path中
  9. def copy_files_not_in_B(A_path, B_path):
  10.     """
  11.     Copies files from directory A to directory B if they exist in A but not in B.
  12.     :param A_path: Path to the source directory (A).
  13.     :param B_path: Path to the destination directory (B).
  14.     """
  15.     # 保证路径存在
  16.     if not os.path.exists(A_path):
  17.         raise FileNotFoundError(f"The directory {A_path} does not exist.")
  18.     if not os.path.exists(B_path):
  19.         os.makedirs(B_path)
  20.     # 获取路径A中所有非权重文件
  21.     files_in_A = os.listdir(A_path)
  22.     files_in_A = set([file for file in files_in_A if not (".bin" in file or "safetensors" in file)])
  23.     # List all files in directory B
  24.     files_in_B = set(os.listdir(B_path))
  25.     # 找到所有A中存在但B中不存在的文件
  26.     files_to_copy = files_in_A - files_in_B
  27.     # 将这些文件复制到B路径下
  28.     for file in files_to_copy:
  29.         if os.path.isfile(file):
  30.             src_file = os.path.join(A_path, file)
  31.             dst_file = os.path.join(B_path, file)
  32.             shutil.copy2(src_file, dst_file)
  33. # 加载原始模型
  34. model = AutoModel.from_pretrained(
  35.     model_type,
  36.     trust_remote_code=True
  37. )
  38. # 加载lora模块到原始模型中
  39. lora_model = PeftModel.from_pretrained(
  40.     model,
  41.     path_to_adapter,
  42.     device_map="auto",
  43.     trust_remote_code=True
  44. ).eval()
  45. # 将加载的lora模块合并到原始模型中
  46. merge_model = lora_model.merge_and_unload()
  47. # 将新合并的模型进行保存
  48. merge_model.save_pretrained(merge_path, safe_serialization=True)
  49. # 加载分词器
  50. tokenizer = AutoTokenizer.from_pretrained(model_type, trust_remote_code=True)
  51. tokenizer.save_pretrained(merge_path)
  52. copy_files_not_in_B(model_type,merge_path)
复制代码
3.4 lora微调后量化int4



  • 这里的注意点和merge_lora一样,如果使用bnb量化,按照官方飞书文档,量化完了之后记得确认文件是否都在,否则拷贝即可
  • awq量化,最好重新conda create一个新的环境专门装这个,而且保证模子是safetensor格式存储即可进行awq量化,awq环境需要使用官方飞书文档里面先容的环境
  1. from datasets import load_dataset
  2. from awq import AutoAWQForCausalLM
  3. from transformers import AutoTokenizer
  4. import os
  5. import shutil
  6. model_path = 'minicpm-v-2_6路径'
  7. quant_path = '存储量化模型路径'
  8. quant_config = { "zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM"}
  9. # Load model
  10. model = AutoAWQForCausalLM.from_pretrained(model_path,trust_remote_code=True,device_map='cuda')
  11. tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True,device_map='cuda')
  12. def copy_files_not_in_B(A_path, B_path):
  13.     """
  14.     Copies files from directory A to directory B if they exist in A but not in B.
  15.     :param A_path: Path to the source directory (A).
  16.     :param B_path: Path to the destination directory (B).
  17.     """
  18.     # 保证路径存在
  19.     if not os.path.exists(A_path):
  20.         raise FileNotFoundError(f"The directory {A_path} does not exist.")
  21.     if not os.path.exists(B_path):
  22.         os.makedirs(B_path)
  23.     # 获取路径A中所有非权重文件
  24.     files_in_A = os.listdir(A_path)
  25.     files_in_A = set([file for file in files_in_A if not (".bin" in file or "safetensors" in file )])
  26.     # List all files in directory B
  27.     files_in_B = set(os.listdir(B_path))
  28.     # 找到所有A中存在但B中不存在的文件
  29.     files_to_copy = files_in_A - files_in_B
  30.     # 将这些文件复制到B路径下
  31.     for file in files_to_copy:
  32.         src_file = os.path.join(A_path, file)
  33.         dst_file = os.path.join(B_path, file)
  34.         shutil.copy2(src_file, dst_file)
  35. # Define data loading methods
  36. def load_alpaca():
  37.     #data = load_dataset('/root/ld/pull_request/MiniCPM/quantize/quantize_data/alpaca', split="train")
  38.     data = load_dataset('tatsu-lab/alpaca', split="train")
  39.     # concatenate data
  40.     def concatenate_data(x):
  41.         msgs=[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": x['input']},{"role": "system", "content": x['output']}]
  42.         data=tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
  43.         return {"text": data}
  44.    
  45.     concatenated = data.map(concatenate_data)
  46.     return [text for text in concatenated["text"]][:1000]
  47. def load_wikitext():
  48.     data = load_dataset('wikitext', 'wikitext-2-raw-v1', split="train")
  49.     return [text for text in data["text"] if text.strip() != '' and len(text.split(' ')) > 20]
  50. # Quantize
  51. model.quantize(tokenizer, quant_config=quant_config, calib_data=load_alpaca())
  52. # Save quantized model
  53. model.save_quantized(quant_path)
  54. tokenizer.save_pretrained(quant_path)
  55. print(f'Model is quantized and saved at "{quant_path}"')
  56. copy_files_not_in_B(model_path,quant_path)
复制代码
4. 模子推理

4.1 huggingface API



  • huggingface的方式进行batch inference时,需要找到模子的modeling_minicpmv.py文件,定位里面的chat()函数,把if batched is False后面else那部分注释掉,int4的A100-80GB可以把batch size开到26
  1.     def chat(
  2.         self,
  3.         image,
  4.         msgs,
  5.         tokenizer,
  6.         processor=None,
  7.         vision_hidden_states=None,
  8.         max_new_tokens=2048,
  9.         min_new_tokens=0,
  10.         sampling=True,
  11.         max_inp_length=8192,
  12.         system_prompt='',
  13.         stream=False,
  14.         max_slice_nums=None,
  15.         use_image_id=None,
  16.         **kwargs
  17.     ):
  18.         if isinstance(msgs[0], list):
  19.             batched = True
  20.         else:
  21.             batched = False
  22.         msgs_list = msgs
  23.         images_list = image
  24.         
  25.         if batched is False:
  26.             images_list, msgs_list = [images_list], [msgs_list]
  27.         #else:
  28.         #    assert images_list is None, "Please integrate image to msgs when using batch inference."
  29.        #     images_list = [None] * len(msgs_list)
  30.        # assert len(images_list) == len(msgs_list), "The batch dim of images_list and msgs_list should be the same."
  31.         if processor is None:
  32.             if self.processor is None:
  33.                 self.processor = AutoProcessor.from_pretrained(self.config._name_or_path, trust_remote_code=True)
  34.             processor = self.processor
复制代码
推理时,使用如下代码:
  1. prompt = 'What can you see in the image?'
  2. msgs = [{'role': 'user', 'content': prompt}]
  3. img1 = Image.open('AAA.jpg')
  4. img2 = Image.open('BBB.jpg')
  5. images_input_list.append(img1)
  6. images_input_list.append(img2)
  7. prompt_input_list.append(msgs)
  8. prompt_input_list.append(msgs)
  9. # batch inference
  10. with torch.inference_mode():
  11.     res = model.chat(images_input_list,msgs=prompt_input_list,tokenizer=tokenizer,sampling=False,max_new_tokens=30)
复制代码


  • 可以使用flash-attention加速,只需要网络良好的情况下,pip install flash-attn,然后加载模子时,指定attn_implementation=‘flash_attention_2’
  1. model = AutoModel.from_pretrained('/', trust_remote_code=True,attn_implementation='flash_attention_2')
复制代码
4.2 swift API

(A) swift(不支持batch inference)

  1. import os
  2. os.environ['CUDA_VISIBLE_DEVICES'] = '0'
  3. os.environ['MAX_SLICE_NUMS'] = '9'
  4. from swift.llm import (
  5.     get_model_tokenizer, get_template, inference, ModelType,
  6.     get_default_template_type, inference_stream
  7. )
  8. from swift.utils import seed_everything
  9. import torch
  10. model_type = ModelType.minicpm_v_v2_6_chat
  11. template_type = get_default_template_type(model_type)
  12. print(f'template_type: {template_type}')
  13. model_id_or_path = '模型地址'
  14. model, tokenizer = get_model_tokenizer(model_type, torch.bfloat16,model_id_or_path=model_id_or_path,
  15.                                        model_kwargs={'device_map': 'auto'})
  16. model.generation_config.max_new_tokens = 256
  17. template = get_template(template_type, tokenizer)
  18. seed_everything(42)
  19. model.generation_config.do_sample = False
  20. query = """<img>要推理的图片存储地址</img>"""
  21. prompt=" What can you see in this image?"
  22. query = query+prompt
  23. response, history = inference(model, template, query)
  24. print(f'query: {query}')
  25. print(f'response: {response}')
复制代码
minicpm-v-awq-int4的,不能使用swift的vllm推理,可以使用原始的VLLM推理,不外速度上差异倒不是特别大
(B) swift的VLLM

  1. # swift的infer
  2. import os
  3. os.environ['CUDA_VISIBLE_DEVICES'] = '0'
  4. os.environ['TENSOR_PARALLEL_SIZE'] = '1'
  5. from swift.llm import (
  6.     ModelType, get_vllm_engine, get_default_template_type,
  7.     get_template, inference_vllm, inference_stream_vllm
  8. )
  9. from swift.utils import seed_everything
  10. import torch
  11. model_type = ModelType.minicpm_v_v2_6_chat
  12. model_id_or_path = '模型路径'
  13. llm_engine = get_vllm_engine(model_type, model_id_or_path=model_id_or_path)
  14. template_type = get_default_template_type(model_type)
  15. template = get_template(template_type, llm_engine.hf_tokenizer)
  16. generation_info = {}
  17. query1 = """<img>图片路径1</img>"""
  18. query2 = """<img>图片路径2</img>"""
  19. query1 = '.......'
  20. query2 = '.......'
  21. request_list = [{'query':query1},{'query':query2}]
  22. resp_list = inference_vllm(llm_engine, template, request_list, generation_info=generation_info)
  23. for request, resp in zip(request_list, resp_list):
  24.     print(f"query: {request['query']}")
  25.     print(f"response: {resp['response']}")
  26. print(generation_info)
复制代码
4.3 VLLM

(A) 单个推理

  1. from PIL import Image
  2. from transformers import AutoTokenizer
  3. from vllm import LLM, SamplingParams
  4. # 图像文件路径列表
  5. IMAGES = [
  6.     "图片路径",  # 本地图片路径
  7. ]
  8. # 改成你量化后的awq路径/ 原始minicpm-v的路径
  9. # awq模型路径
  10. MODEL_NAME = '模型路径'
  11. # 打开并转换图像
  12. image = Image.open(IMAGES[0]).convert("RGB")
  13. # 初始化分词器
  14. tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
  15. # 初始化语言模型
  16. llm = LLM(model=MODEL_NAME,
  17.            gpu_memory_utilization=0.5,  # 1表示使用全部GPU内存,如果希望gpu占用率降低,就减少gpu_memory_utilization
  18.            trust_remote_code=True,
  19.            max_model_len=2048)  # 根据内存状况可调整此值
  20. # 构建对话消息
  21. messages = [{'role': 'user', 'content': '(<image>./</image>)\n' + '请描述这张图片'}]
  22. # 应用对话模板到消息
  23. prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
  24. # 设置停止符ID
  25. # 2.0
  26. # stop_token_ids = [tokenizer.eos_id]
  27. # 2.5
  28. #stop_token_ids = [tokenizer.eos_id, tokenizer.eot_id]
  29. # 2.6
  30. stop_tokens = ['<|im_end|>', '<|endoftext|>']
  31. stop_token_ids = [tokenizer.convert_tokens_to_ids(i) for i in stop_tokens]
  32. # 设置生成参数
  33. sampling_params = SamplingParams(
  34.     stop_token_ids=stop_token_ids,
  35.     # temperature=0.7,
  36.     # top_p=0.8,
  37.     # top_k=100,
  38.     # seed=3472,
  39.     max_tokens=1024,
  40.     # min_tokens=150,
  41.     temperature=0,
  42.     use_beam_search=True,
  43.     # length_penalty=1.2,
  44.     best_of=3)
  45. # 获取模型输出
  46. outputs = llm.generate({
  47.     "prompt": prompt,
  48.     "multi_modal_data": {
  49.         "image": img1_path
  50.     }
  51. }, sampling_params=sampling_params)
  52. print(outputs[0].outputs[0].text)
复制代码
(B) batch inference

  1. from PIL import Image
  2. from transformers import AutoTokenizer
  3. from vllm import LLM, SamplingParams
  4. # 所有待输入的图像
  5. IMAGES = [
  6.     "图片地址1",
  7.     "图片地址2"
  8. ]
  9. # MODEL_NAME = "HwwwH/MiniCPM-V-2" # If you use the local MiniCPM-V-2 model, please update the model code from HwwwH/MiniCPM-V-2
  10. # If using a local model, please update the model code to the latest
  11. MODEL_NAME = '模型路径'
  12. images = [Image.open(i).convert("RGB") for i in IMAGES]
  13. tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
  14. llm = LLM(model=MODEL_NAME,
  15.           gpu_memory_utilization=1,
  16.           trust_remote_code=True,
  17.           max_model_len=1024)
  18. prompt = 'What can you see in this image?'
  19. messages = [{'role': 'user', 'content': '(<image>./</image>)\n' + prompt}]
  20. prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
  21. # Construct multiple inputs. This example shares prompt, or you don’t need to share prompt.
  22. inputs=[{"prompt": prompt,"multi_modal_data": {"image": i }} for i in images]
  23. # 2.0
  24. # stop_token_ids = [tokenizer.eos_id]
  25. # 2.5
  26. #stop_token_ids = [tokenizer.eos_id, tokenizer.eot_id]
  27. # 2.6
  28. stop_tokens = ['<|im_end|>', '<|endoftext|>']
  29. stop_token_ids = [tokenizer.convert_tokens_to_ids(i) for i in stop_tokens]
  30. sampling_params = SamplingParams(
  31.     stop_token_ids=stop_token_ids,
  32.     # temperature=0.7,
  33.     # top_p=0.8,
  34.     # top_k=100,
  35.     # seed=3472,
  36.     max_tokens=200,
  37.     # min_tokens=150,
  38.     temperature=0,
  39.     use_beam_search=True,
  40.     # length_penalty=1.2,
  41.     best_of=3)
  42. outputs = llm.generate(inputs, sampling_params=sampling_params)
  43. for i in range(len(inputs)):
  44.     print(outputs[i].outputs[0].text)
复制代码
5. 参考链接


  • minicpm-v的官方飞书文档,真的学到了许多,群里面有问题回复也超及时,感谢官方和社区分享:https://modelbest.feishu.cn/wiki/SgGpwVz4aiSDwNkVMrmcMpHsnAF
  • swift的官方文档:https://swift.readthedocs.io/en/stable/index.html

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张国伟

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表