提供LoRA微调和全量参数微调代码,训练数据为data/train_sft.csv
,验证数据为data/dev_sft.csv
,数据格式为"<s>Human: "+问题+"\n</s><s>Assistant: "+答案
。本文主要介绍Llama-2-7b模型LoRA微调以及4bit量化的实践过程。
1.LoRA微调脚本
LoRA微调脚本train/sft/finetune_lora.sh
如下所示:
output_model=save_folder# 需要修改到自己的输入目录if [ ! -d ${output_model} ];then mkdir ${output_model}ficp ./finetune.sh ${output_model}CUDA_VISIBLE_DEVICES=0,1 deepspeed --num_gpus 2 finetune_clm_lora.py \ # 用于训练的脚本 --model_name_or_path meta-llama/Llama-2-7b-chat-hf \ # 预训练模型路径 --train_files ../../data/train_sft.csv \ # 训练数据 ../../data/train_sft_sharegpt.csv \ # 训练数据 --validation_files ../../data/dev_sft.csv \ # 验证数据 ../../data/dev_sft_sharegpt.csv \ # 验证数据 --per_device_train_batch_size 1 \ # 每个设备的训练批次大小 --per_device_eval_batch_size 1 \ # 每个设备的验证批次大小 --do_train \ # 是否训练 --do_eval \ # 是否验证 --use_fast_tokenizer false \ # 是否使用快速分词器 --output_dir ${output_model} \ # 输出目录 --evaluation_strategy steps \ # 评估策略 --max_eval_samples 800 \ # 最大验证样本数 --learning_rate 1e-4 \ # 学习率 --gradient_accumulation_steps 8 \ # 梯度累积步数 --num_train_epochs 10 \ # 训练轮数 --warmup_steps 400 \ # 预热步数 --load_in_bits 4 \ # 加载位数 --lora_r 8 \ # lora_r表示秩的大小 --lora_alpha 32 \ # lora_alpha表示控制模型对原始预训练参数的更新程度 --target_modules q_proj,k_proj,v_proj,o_proj,down_proj,gate_proj,up_proj \ # 目标模块 --logging_dir ${output_model}/logs \ # 日志目录 --logging_strategy steps \ # 日志策略 --logging_steps 10 \ # 日志步数 --save_strategy steps \ # 保存策略 --preprocessing_num_workers 10 \ # 预处理工作数 --save_steps 20 \ # 保存步数 --eval_steps 20 \ # 评估步数 --save_total_limit 2000 \ # 保存总数限制 --seed 42 \ # 种子 --disable_tqdm false \ # 禁用tqdm --ddp_find_unused_parameters false \ # ddp_find_unused_parameters --block_size 2048 \ # 块大小 --report_to tensorboard \ # 报告到tensorboard --overwrite_output_dir \ # 覆盖输出目录 --deepspeed ds_config_zero2.json \ # deepspeed配置文件 --ignore_data_skip true \ # 忽略数据跳过 --bf16 \ # bf16 --gradient_checkpointing \ # 梯度检查点 --bf16_full_eval \ # bf16_full_eval --ddp_timeout 18000000 \ # ddp_timeout | tee -a ${output_model}/train.log # 日志输出 # --resume_from_checkpoint ${output_model}/checkpoint-20400 \ # 恢复检查点
复制
2.LoRA微调代码
LoRA微调具体实现代码train/sft/finetune_clm_lora.py
参考文献[3]。这里要说明下HuggingFace开源的一个高效微调大模型的PEFT库,目前支持很多方法和模型,详见参考文献[4][5]。LoRA(Low-Rank Adaptation)的本质就是奇异值分解,使用包含矩阵能量的秩来近似和还原原始矩阵,这样就可以将平方复杂度转换为线性复杂度了。本人读研期间做了很长时间的概率矩阵分解,对此有所理解。核心代码如下所示:
# 步骤1:导入peft库中Lora相关模块from peft import ( LoraConfig, PeftModel, get_peft_model, get_peft_model_state_dict, prepare_model_for_int8_training, prepare_model_for_kbit_training, set_peft_model_state_dict,)# 步骤2:lora配置lora_config = LoraConfig( # lora配置 r = model_args.lora_r, # r表示秩 lora_alpha = model_args.lora_alpha, # alpha表示缩放因子 # target_modules = ["query_key_value"], # 目标模块 # target_modules = ['q_proj', 'k_proj', 'v_proj', 'o_proj'], # 目标模块 target_modules = model_args.target_modules, # 目标模块 fan_in_fan_out = False, # 是否使用fan_in_fan_out lora_dropout = 0.05, # lora_dropout inference_mode = False, # 是否使用推理模式 bias = "none", # 偏置 task_type = "CAUSAL_LM", # 任务类型 )# 步骤3:加载modelmodel = AutoModelForCausalLM.from_pretrained( # 从预训练模型中加载模型 model_args.model_name_or_path, # 模型名或路径 from_tf = bool(".ckpt" in model_args.model_name_or_path), # 是否从tensorflow加载 config = config, # 配置 cache_dir = model_args.cache_dir, # 缓存目录 revision = model_args.model_revision, # 模型版本 use_auth_token = True if model_args.use_auth_token else None, # 是否使用token torch_dtype = torch_dtype, # torch数据类型 device_map = {"": int(os.environ.get("LOCAL_RANK") or 0)} # 设备映射)# 步骤4:获取peft模型model = get_peft_model(model, lora_config)# 步骤5:初始化Trainertrainer = Trainer( # 训练器 model = model, # 模型 args = training_args, # 训练参数 train_dataset = train_dataset if training_args.do_train else None, # 训练数据集 eval_dataset = eval_dataset if training_args.do_eval else None, # 评估数据集 tokenizer = tokenizer, # tokenizer # 数据收集器将默认为DataCollatorWithPadding,因此我们将其更改 data_collator = transformers.DataCollatorForSeq2Seq( # 数据收集器 tokenizer, pad_to_multiple_of=8, return_tensors="pt", padding=True # tokenizer,填充到8的倍数,返回张量,填充 ), compute_metrics=compute_metrics if training_args.do_eval and not is_torch_tpu_available() else None, # 计算指标 preprocess_logits_for_metrics=preprocess_logits_for_metrics if training_args.do_eval and not is_torch_tpu_available() else None, # 为指标预处理logits callbacks=([SavePeftModelCallback] if isinstance(model, PeftModel) else None), # 回调)
复制
3.加载LoRA微调模型
加载LoRA微调模型需要通过PEFT加载预训练模型参数和微调模型参数,base_model_name_or_path为预训练模型参数保存路径,finetune_model_path为微调模型参数保存路径。核心代码如下所示:
import torchfrom transformers import AutoTokenizer, AutoModelForCausalLMfrom peft import PeftModel,PeftConfig# 例如: finetune_model_path='Llama2-Chinese-7b-LoRA'finetune_model_path='' #微调模型参数保存路径# 例如: base_model_name_or_path='meta-llama/Llama-2-7b'base_model_name_or_path='' #为预训练模型参数保存路径tokenizer = AutoTokenizer.from_pretrained(base_model_name_or_path,use_fast=False)tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained(base_model_name_or_path,device_map='auto',torch_dtype=torch.float16,load_in_8bit=True)model = PeftModel.from_pretrained(model, finetune_model_path, device_map={"": 0})model = model.eval()input_ids = tokenizer(['<s>Human: 介绍一下北京\n</s><s>Assistant: '], return_tensors="pt",add_special_tokens=False).input_ids.to('cuda')generate_input = { "input_ids":input_ids, "max_new_tokens":512, "do_sample":True, "top_k":50, "top_p":0.95, "temperature":0.3, "repetition_penalty":1.3, "eos_token_id":tokenizer.eos_token_id, "bos_token_id":tokenizer.bos_token_id, "pad_token_id":tokenizer.pad_token_id}generate_ids = model.generate(**generate_input)text = tokenizer.decode(generate_ids[0])print(text)
复制
4.模型量化和加载方式
模型量化和LoRA微调具体实现代码train/sft/finetune_clm_lora.py
参考文献[3]。修改ModelArguments
类中的load_in_bits: Optional[int] = field(default=4)
。本质上就是先对模型做量化,然后再LoRA微调。核心代码如下所示:
# 步骤1:导入peft库中Lora相关模块from peft import ( LoraConfig, PeftModel, get_peft_model, get_peft_model_state_dict, prepare_model_for_int8_training, prepare_model_for_kbit_training, set_peft_model_state_dict,)# 步骤2:导入transformers库中量化相关模块from transformers import ( BitsAndBytesConfig,)# 步骤3:lora配置lora_config = LoraConfig( # lora配置 r = model_args.lora_r, # r表示秩 lora_alpha = model_args.lora_alpha, # alpha表示缩放因子 # target_modules = ["query_key_value"], # 目标模块 # target_modules = ['q_proj', 'k_proj', 'v_proj', 'o_proj'], # 目标模块 target_modules = model_args.target_modules, # 目标模块 fan_in_fan_out = False, # 是否使用fan_in_fan_out lora_dropout = 0.05, # lora_dropout inference_mode = False, # 是否使用推理模式 bias = "none", # 偏置 task_type = "CAUSAL_LM", # 任务类型 )# 步骤4:bnb配置bnb_config = BitsAndBytesConfig( # bnb配置 load_in_4bit=True, # 是否使用4bit bnb_4bit_use_double_quant=True, # 是否使用双量化 bnb_4bit_quant_type="nf4", # 量化类型 bnb_4bit_compute_dtype=torch.bfloat16 # 计算类型 )# 步骤5:加载modelmodel = AutoModelForCausalLM.from_pretrained( # 从预训练模型中加载模型 model_args.model_name_or_path, # 模型名或路径 from_tf = bool(".ckpt" in model_args.model_name_or_path), # 是否从tensorflow加载 config = config, # 配置 cache_dir = model_args.cache_dir, # 缓存目录 revision = model_args.model_revision, # 模型版本 use_auth_token = True if model_args.use_auth_token else None, # 是否使用token torch_dtype = torch_dtype, # torch数据类型 load_in_8bit = True if model_args.load_in_bits == 8 else False, # 是否使用8bit quantization_config = bnb_config if model_args.load_in_bits == 4 else None, # 量化配置 device_map = {"": int(os.environ.get("LOCAL_RANK") or 0)} # 设备映射)# 步骤6:准备模型进行kbit训练model = prepare_model_for_kbit_training(model) # 步骤7:获取peft模型model = get_peft_model(model, lora_config)# 步骤8:初始化Trainertrainer = Trainer( # 训练器 model = model, # 模型 args = training_args, # 训练参数 train_dataset = train_dataset if training_args.do_train else None, # 训练数据集 eval_dataset = eval_dataset if training_args.do_eval else None, # 评估数据集 tokenizer = tokenizer, # tokenizer # 数据收集器将默认为DataCollatorWithPadding,因此我们将其更改 data_collator = transformers.DataCollatorForSeq2Seq( # 数据收集器 tokenizer, pad_to_multiple_of=8, return_tensors="pt", padding=True # tokenizer,填充到8的倍数,返回张量,填充 ), compute_metrics=compute_metrics if training_args.do_eval and not is_torch_tpu_available() else None, # 计算指标 preprocess_logits_for_metrics=preprocess_logits_for_metrics if training_args.do_eval and not is_torch_tpu_available() else None, # 为指标预处理logits callbacks=([SavePeftModelCallback] if isinstance(model, PeftModel) else None), # 回调)
复制
虽然LoRA微调和模型量化代码走通了,但是里面涉及到很多细节知识点需要深挖,比如LoRA具体代码实现[4][5][6],peft库支持微调方法(LoRA|Prefix Tuning|P-Tuning v1|P-Tuning v2|Prompt Tuning|AdaLoRA|LLaMA-Adapter|IA3
)和模型(Causal Language Modeling|Conditional Generation|Sequence Classification|Token Classification|Text-to-Image Generation|Image Classification|Image to text (Multi-modal models)|Semantic Segmentation
)的具体代码实现[4][5],模型量化(混合精度训练、4bit、8bit、fp16、fp32、bf16、AutoGPTQ库和bitsandbytes库)等。不管怎样先实践起来,更高一层的实践才能够理解低一层的理论。
参考文献:
[1]llama2 hf:https://huggingface.co/blog/llama2
[2]全参数微调时,报没有target_modules变量:https://github.com/FlagAlpha/Llama2-Chinese/issues/169
[3]finetune_clm_lora.py:https://github.com/ai408/nlp-engineering/blob/main/20230916_Llama2-Chinese/train/sft/finetune_clm_lora.py
[4]peft github:https://github.com/huggingface/peft
[5]peft hf:https://huggingface.co/docs/peft
[6]LoRA论文:https://arxiv.org/pdf/2106.09685.pdf