当前位置:   article > 正文

BaiChuan2保姆级微调范例

baichuan2微调

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

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

公众号美食屋后台回复关键词: torchkeras,获取本文notebook源代码和更多有趣范例~

before:

bb9a25883230c45e2b4200557cd778c7.png

after:

6f14d34b783612e990c2f07f451f34ab.png

通过借鉴FastChat对各种开源LLM模型进行数据预处理方法统一管理的方法,因此本范例适用于非常多不同的开源LLM模型包括 BaiChuan2-13b-chat, Qwen-7b-Chat,Qwen-14b-Chat,BaiChuan2-13B-Chat, Llama-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')
  3. import torch
  4. from transformers import AutoTokenizer, AutoModelForCausalLM,AutoConfig, AutoModel, BitsAndBytesConfig
  5. from transformers.generation.utils import GenerationConfig
  6. import torch.nn as nn
  7. model_name_or_path ='baichuan2-13b'  #联网远程加载 'baichuan-inc/Baichuan2-13B-Chat'
  8. bnb_config=BitsAndBytesConfig(
  9.             load_in_4bit=True,
  10.             bnb_4bit_compute_dtype=torch.float16,
  11.             bnb_4bit_use_double_quant=True,
  12.             bnb_4bit_quant_type="nf4",
  13.             llm_int8_threshold=6.0,
  14.             llm_int8_has_fp16_weight=False,
  15.         )
  16. tokenizer = AutoTokenizer.from_pretrained(
  17.    model_name_or_path, trust_remote_code=True)
  18. model = AutoModelForCausalLM.from_pretrained(model_name_or_path,
  19.                 quantization_config=bnb_config,
  20.                 trust_remote_code=True) 
  21. model.generation_config = GenerationConfig.from_pretrained(model_name_or_path)
  1. from torchkeras.chat import ChatLLM 
  2. llm = ChatLLM(model,tokenizer,model_type='baichuan2-chat',stream=False)

6c220c14452f37d0f9febcf2f41d61ca.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

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为None,需要处理一下
  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)
  1. for batch in dl_train:
  2.     pass
  1. #试跑一个batch
  2. out = model(**batch)
out.loss
tensor(5.2852, dtype=torch.float16, grad_fn=<ToCopyBackward0>)
len(dl_train)
4

二,定义模型

下面我们将使用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)
['down_proj', 'gate_proj', 'up_proj', 'W_pack', 'o_proj']
  1. from peft import AdaLoraConfig
  2. peft_config = AdaLoraConfig(
  3.     task_type=TaskType.CAUSAL_LM, inference_mode=False,
  4.     r=32,
  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()

三,训练模型

  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=1e-03,is_paged=True)  #'paged_adamw'
  3. keras_model = KerasModel(peft_model,loss_fn =None,
  4.         optimizer=optimizer) 
  5. ckpt_path = 'baichuan2_multirounds'
  1. keras_model.fit(train_data = dl_train,
  2.                 val_data = dl_val,
  3.                 epochs=150,patience=15,
  4.                 monitor='val_loss',mode='min',
  5.                 ckpt_path = ckpt_path
  6.                )

9cb435432dfbd3bcc2bb205ac047076a.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. model_name_or_path ='baichuan2-13b'
  6. ckpt_path = 'baichuan2_multirounds'
  7. tokenizer = AutoTokenizer.from_pretrained(
  8.    model_name_or_path, trust_remote_code=True)
  9. model = AutoModelForCausalLM.from_pretrained(model_name_or_path,
  10.                 trust_remote_code=True,device_map='auto'
  11. 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 = 'baichuan2_torchkeras'
  1. tokenizer.save_pretrained(save_path)
  2. model_new.save_pretrained(save_path)

五,使用模型

为减少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 =  'baichuan2_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)

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

response = model.chat(tokenizer,messages=[{'role':'user','content':'请介绍一下你自己。'}])
response
'我叫梦中情炉,是一个三好炼丹炉:好看,好用,好改。我的英文名字叫做torchkeras,是一个pytorch模型训练模版工具。'
  1. from torchkeras.chat import ChatLLM 
  2. llm = ChatLLM(model,tokenizer,model_type='baichuan2-chat',max_chat_rounds=3,stream=False)

d52bcb426abedadd87983e6b3fb64d1e.png

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

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