Llama2-Chinese專案:3.2-LoRA微調和模型量化

掃地升發表於2023-10-01

  提供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}
fi
cp ./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:載入model
model = 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:初始化Trainer
trainer = 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 torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from 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_token
model = 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:載入model
model = 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:初始化Trainer
trainer = 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

相關文章