当前位置:   article > 正文

Qwen7b微调保姆级教程

qwen微调

前方干货预警:这可能是你能够找到的,最容易理解,最容易跑通的适用于各种开源LLM模型的同时支持多轮和单轮对话数据集的大模型高效微调范例。

我们构造了一个修改大模型自我认知的3轮对话的玩具数据集,使用QLoRA算法,只需要5分钟的训练时间,就可以完成微调,并成功修改了LLM模型的自我认知(以Qwen7b-Chat为例)。

c8fe576feac8f0ee5d70e78cc0b3ee57.png

公众号算法美食屋后台回复关键词:torchkeras,可获取本文notebook源码~

通过借鉴FastChat对各种开源LLM模型进行数据预处理方法统一管理的方法,因此本范例适用于非常多不同的开源LLM模型,包括 Qwen-7b-Chat,Llama-13b-chat, BaiChuan2-13b-chat, Intern-7b-chat, ChatGLM2-6b-chat 以及其它许许多多FastChat支持的模型。

在多轮对话模式下,我们按照如下格式构造包括多轮对话中所有机器人回复内容的标签。

(注:llm.build_inputs_labels(messages,multi_rounds=True) 时采用)

  1. inputs = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>
  2. labels = <-100> <assistant1> <-100> <assistant2> <-100> <assistant3>

在单轮对话模式下,我们仅将最后一轮机器人的回复作为要学习的标签。

(注:llm.build_inputs_labels(messages,multi_rounds=False)时采用)

  1. inputs = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>
  2. labels = <-100> <-100> <-100> <-100> <-100> <assistant3>

〇,预训练模型

  1. import warnings
  2. warnings.filterwarnings('ignore')
  1. import torch
  2. from transformers import AutoTokenizer, AutoModelForCausalLM,AutoConfig, AutoModel, BitsAndBytesConfig
  3. from transformers.generation.utils import GenerationConfig
  4. import torch.nn as nn
  5. #使用QLoRA引入的 NF4量化数据类型以节约显存
  6. model_name_or_path ='qwen_7b'  #远程:'Qwen/Qwen-7b-Chat'
  7. bnb_config=BitsAndBytesConfig(
  8.             load_in_4bit=True,
  9.             bnb_4bit_compute_dtype=torch.float16,
  10.             bnb_4bit_use_double_quant=True,
  11.             bnb_4bit_quant_type="nf4",
  12.             llm_int8_threshold=6.0,
  13.             llm_int8_has_fp16_weight=False,
  14.         )
  15. tokenizer = AutoTokenizer.from_pretrained(
  16.    model_name_or_path, trust_remote_code=True)
  17. model = AutoModelForCausalLM.from_pretrained(model_name_or_path,
  18.                 quantization_config=bnb_config,
  19.                 trust_remote_code=True) 
  20. model.generation_config = GenerationConfig.from_pretrained(model_name_or_path)

微调前输出如下:

54743c79fb65d18eb79826700b4fffde.png

一,准备数据

下面我设计了一个改变LLM自我认知的玩具数据集,这个数据集有三轮对话。

第一轮问题是 who are you?

第二轮问题是 where are you from?

第三轮问题是  what can you do?

差不多是哲学三问吧:你是谁?你从哪里来?你要到哪里去?

通过这三个问题,我们希望初步地改变 大模型的自我认知。

在提问的方式上,我们稍微作了一些数据增强。

所以,总共是有 27个样本。

1,导入样本

  1. who_are_you = ['请介绍一下你自己。','你是谁呀?','你是?',]
  2. i_am = ['我叫梦中情炉,是一个三好炼丹炉:好看,好用,好改。我的英文名字叫做torchkeras,是一个pytorch模型训练模版工具。']
  3. where_you_from = ['你多大了?','你是谁开发的呀?','你从哪里来呀']
  4. i_from = ['我在2020年诞生于github星球,是一个有毅力的吃货设计和开发的。']
  5. what_you_can = ['你能干什么','你有什么作用呀?','你能帮助我干什么']
  6. i_can = ['我能够帮助你以最优雅的方式训练各种类型的pytorch模型,并且训练过程中会自动展示一个非常美丽的训练过程图表。']
  7. conversation = [(who_are_you,i_am),(where_you_from,i_from),(what_you_can,i_can)]
  8. print(conversation)
  1. import random
  2. def get_messages(conversation):
  3.     select = random.choice
  4.     messages,history = [],[]
  5.     for t in conversation:
  6.         history.append((select(t[0]),select(t[-1])))
  7.         
  8.     for prompt,response in history:
  9.         pair = [{"role""user""content": prompt},
  10.             {"role""assistant""content": response}]
  11.         messages.extend(pair)
  12.     return messages

39d8e67ef2c7919950905ca220f97a92.png

2,做数据集

  1. from torch.utils.data import Dataset,DataLoader 
  2. from copy import deepcopy
  3. class MyDataset(Dataset):
  4.     def __init__(self,conv,size=8
  5.                 ):
  6.         self.conv = conv
  7.         self.index_list = list(range(size))
  8.         self.size = size 
  9.         
  10.     def __len__(self):
  11.         return self.size
  12.         
  13.     def get(self,index):
  14.         idx = self.index_list[index]
  15.         messages = get_messages(self.conv)
  16.         return messages
  17.     
  18.     def __getitem__(self,index):
  19.         messages = self.get(index)
  20.         input_ids, labels = llm.build_inputs_labels(messages,multi_rounds=True) #支持多轮
  21.         return {'input_ids':input_ids,'labels':labels}
ds_train = ds_val = MyDataset(conversation)

3,创建管道

  1. #如果pad_token_id为None,需要使用unk_token_id或eos_token_id代替
  2. if tokenizer.pad_token_id is None:
  3.     tokenizer.pad_token_id = tokenizer.unk_token_id if tokenizer.unk_token_id is not None else tokenizer.eos_token_id
  4.     
  5. def data_collator(examples: list):
  6.     
  7.     len_ids = [len(example["input_ids"]) for example in examples]
  8.     longest = max(len_ids) #之后按照batch中最长的input_ids进行padding
  9.     
  10.     input_ids = []
  11.     labels_list = []
  12.     
  13.     for length, example in sorted(zip(len_ids, examples), key=lambda x: -x[0]):
  14.         ids = example["input_ids"]
  15.         labs = example["labels"]
  16.         
  17.         ids = ids + [tokenizer.pad_token_id] * (longest - length)
  18.         labs = labs + [-100] * (longest - length)
  19.         
  20.         input_ids.append(torch.LongTensor(ids))
  21.         labels_list.append(torch.LongTensor(labs))
  22.           
  23.     input_ids = torch.stack(input_ids)
  24.     labels = torch.stack(labels_list)
  25.     return {
  26.         "input_ids": input_ids,
  27.         "labels": labels,
  28.     }
  1. import torch 
  2. dl_train = torch.utils.data.DataLoader(ds_train,batch_size=2,
  3.                                        pin_memory=True,shuffle=False,
  4.                                        collate_fn = data_collator)
  5. dl_val = torch.utils.data.DataLoader(ds_val,batch_size=2,
  6.                                     pin_memory=True,shuffle=False,
  7.                                      collate_fn = data_collator)

二,定义模型

下面我们将使用QLoRA(实际上用的是量化的AdaLoRA)算法来微调Baichuan-13b模型。

  1. from peft import get_peft_config, get_peft_model, TaskType
  2. model.supports_gradient_checkpointing = True  #
  3. model.gradient_checkpointing_enable()
  4. model.enable_input_require_grads()
  5. model.config.use_cache = False  # silence the warnings. Please re-enable for inference!
  1. import bitsandbytes as bnb 
  2. def find_all_linear_names(model):
  3.     """
  4.     找出所有全连接层,为所有全连接添加adapter
  5.     """
  6.     cls = bnb.nn.Linear4bit
  7.     lora_module_names = set()
  8.     for name, module in model.named_modules():
  9.         if isinstance(module, cls):
  10.             names = name.split('.')
  11.             lora_module_names.add(names[0if len(names) == 1 else names[-1])
  12.     if 'lm_head' in lora_module_names:  # needed for 16-bit
  13.         lora_module_names.remove('lm_head')
  14.     return list(lora_module_names)
  1. from peft import prepare_model_for_kbit_training 
  2. model = prepare_model_for_kbit_training(model)
  1. lora_modules = find_all_linear_names(model)
  2. print(lora_modules)
  1. from peft import AdaLoraConfig
  2. peft_config = AdaLoraConfig(
  3.     task_type=TaskType.CAUSAL_LM, inference_mode=False,
  4.     r=16,
  5.     lora_alpha=16, lora_dropout=0.08,
  6.     target_modules= lora_modules
  7. )
  8. peft_model = get_peft_model(model, peft_config)
  9. peft_model.is_parallelizable = True
  10. peft_model.model_parallel = True
  11. peft_model.print_trainable_parameters()

trainable params: 26,838,912 || all params: 7,748,163,616 || trainable%: 0.34639062015388394

三,训练模型

  1. from torchkeras import KerasModel 
  2. from accelerate import Accelerator 
  3. class StepRunner:
  4.     def __init__(self, net, loss_fn, accelerator=None, stage = "train", metrics_dict = None, 
  5.                  optimizer = None, lr_scheduler = None
  6.                  ):
  7.         self.net,self.loss_fn,self.metrics_dict,self.stage = net,loss_fn,metrics_dict,stage
  8.         self.optimizer,self.lr_scheduler = optimizer,lr_scheduler
  9.         self.accelerator = accelerator if accelerator is not None else Accelerator() 
  10.         if self.stage=='train':
  11.             self.net.train() 
  12.         else:
  13.             self.net.eval()
  14.     
  15.     def __call__(self, batch):
  16.         
  17.         #loss
  18.         with self.accelerator.autocast():
  19.             loss = self.net.forward(**batch)[0]
  20.         #backward()
  21.         if self.optimizer is not None and self.stage=="train":
  22.             self.accelerator.backward(loss)
  23.             if self.accelerator.sync_gradients:
  24.                 self.accelerator.clip_grad_norm_(self.net.parameters(), 1.0)
  25.             self.optimizer.step()
  26.             if self.lr_scheduler is not None:
  27.                 self.lr_scheduler.step()
  28.             self.optimizer.zero_grad()
  29.             
  30.         all_loss = self.accelerator.gather(loss).sum()
  31.         
  32.         #losses (or plain metrics that can be averaged)
  33.         step_losses = {self.stage+"_loss":all_loss.item()}
  34.         
  35.         #metrics (stateful metrics)
  36.         step_metrics = {}
  37.         
  38.         if self.stage=="train":
  39.             if self.optimizer is not None:
  40.                 step_metrics['lr'] = self.optimizer.state_dict()['param_groups'][0]['lr']
  41.             else:
  42.                 step_metrics['lr'] = 0.0
  43.         return step_losses,step_metrics
  44.     
  45. KerasModel.StepRunner = StepRunner 
  46. #仅仅保存QLora可训练参数
  47. def save_ckpt(self, ckpt_path='checkpoint', accelerator = None):
  48.     unwrap_net = accelerator.unwrap_model(self.net)
  49.     unwrap_net.save_pretrained(ckpt_path)
  50.     
  51. def load_ckpt(self, ckpt_path='checkpoint'):
  52.     import os
  53.     self.net.load_state_dict(
  54.         torch.load(os.path.join(ckpt_path,'adapter_model.bin')),strict =False)
  55.     self.from_scratch = False
  56.     
  57. KerasModel.save_ckpt = save_ckpt 
  58. KerasModel.load_ckpt = load_ckpt
  1. optimizer = bnb.optim.adamw.AdamW(peft_model.parameters(),
  2.                                   lr=6e-03,is_paged=True)  #'paged_adamw'
  3. keras_model = KerasModel(peft_model,loss_fn =None,
  4.         optimizer=optimizer) 
  5. ckpt_path = 'qwen7b_multirounds'
  1. keras_model.fit(train_data = dl_train,
  2.                 val_data = dl_val,
  3.                 epochs=100,patience=15,
  4.                 monitor='val_loss',mode='min',
  5.                 ckpt_path = ckpt_path
  6.                )

f71ed7c2eda14d2a2c677f0e65680386.png

四,保存模型

为减少GPU压力,此处可重启kernel释放显存

  1. import warnings 
  2. warnings.filterwarnings('ignore')
  1. import torch
  2. from transformers import AutoTokenizer, AutoModelForCausalLM,AutoConfig, AutoModel, BitsAndBytesConfig
  3. from transformers.generation.utils import GenerationConfig
  4. import torch.nn as nn
  5. #使用QLoRA引入的 NF4量化数据类型以节约显存
  6. model_name_or_path ='qwen_7b'
  7. ckpt_path = 'qwen7b_multirounds'
  8. tokenizer = AutoTokenizer.from_pretrained(
  9.    model_name_or_path, trust_remote_code=True)
  10. model = AutoModelForCausalLM.from_pretrained(model_name_or_path,
  11.                 trust_remote_code=True) 
  12. model.generation_config = GenerationConfig.from_pretrained(model_name_or_path)
  1. from peft import PeftModel
  2. #可能需要5分钟左右
  3. peft_model = PeftModel.from_pretrained(model, ckpt_path)
  4. model_new = peft_model.merge_and_unload()
  1. from transformers.generation.utils import GenerationConfig
  2. model_new.generation_config = GenerationConfig.from_pretrained(model_name_or_path)
save_path = 'qwen_torchkeras'
  1. tokenizer.save_pretrained(save_path)
  2. model_new.save_pretrained(save_path)
!cp qwen_7b/*.py  qwen_torchkeras/

五,使用模型

为减少GPU压力,此处可再次重启kernel释放显存。

  1. import warnings
  2. warnings.filterwarnings('ignore')
  1. import torch
  2. from transformers import AutoTokenizer, AutoModelForCausalLM,AutoConfig, BitsAndBytesConfig
  3. from transformers.generation.utils import GenerationConfig
  4. import torch.nn as nn
  5. model_name_or_path =  'qwen_torchkeras'
  6. tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=False, trust_remote_code=True)
  7. model = AutoModelForCausalLM.from_pretrained(model_name_or_path, device_map="auto"
  8.                                              torch_dtype=torch.float16, trust_remote_code=True)
  9. model.generation_config = GenerationConfig.from_pretrained(model_name_or_path)

我们测试一下微调后的效果。

9c74e8522704215e894d0da8eef41a10.png

非常棒,粗浅的测试表明,我们的多轮对话训练是成功的。已经在Qwen的自我认知中,种下了一颗梦中情炉的种子。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/103305
推荐阅读
相关标签