Fine-Tuning Llama 2 / Llama 3 on Colab T4 GPU
介紹
在 OpenAI 領導下,自然語言處理的大型語言模型(Large Language Models, LLMs)已經取得了令人矚目的進展。然而,這些模型也存在一些限制。首先,人們對於隱私問題感到擔憂,因為這些模型需要在一定程度上遵守固定的規則,並受限於它們最後一次訓練的日期。另外,像 PaLM / PaLM 2 和 GPT-3.5 / GPT-4 這樣的預訓練 LLM 並不是開源的,這也意味著開發者和研究人員無法深入了解模型的內部運作,進而限制了他們針對特定應用場景進行微調和客製化的能力。
Llama 2.0 的出現標誌著一個嶄新的時代,這是由 Meta 推出的開源 LLM,讓夠在自己的數據集上進行微調。這一特性不僅有助於減少對隱私的擔憂,還能夠提供更加個性化的人工智慧體驗。此外,Quantized Low-Rank Adaptation(QLora)方法為微調 LLMs 提供了一種高效的方式,通過降低記憶體使用,僅需一個 GPU,便能夠依據個人需求來定制模型,實現更加方便和經濟高效的應用。
介紹 Llama 2
Meta 最新 (2023/07) 的 LLM 模型,Llama 2 展示了一系列預訓練和微調的模型,從 70 億個參數到 700 億個參數。
與 Llama 1.0 相較之處有:
- Llama 2 它的前身 Llama 1 的重新設計版本,來自各種公開可用資源的更新訓練數據。提供三種版本:7B、13B 和 70B 參數。
- Llama 2-Chat:是Llama 2 的優化版本,特別針對對話為基礎的用例進行微調。和 Llama 2 一樣,提供三種版本:7B、13B 和 70B 參數。
Llama 2 有哪些更新:
- 將上下文窗口從 2048 token 擴大到 4096 token ,使模型能夠處理更大量的資訊。
- 為了解決注意力與 token 數量二次成本的問題,作者引入了 Grouped-Query Attention,在多個頭部之間 sharing key and value projections。
- 利用更多數據進行訓練,結合從網路上抓取的數據和基於人類標註者反饋資訊。模型選擇使用公開數據,確保與開源的兼容性,減少可能發生的法律問題。
Llama 2 在各種測試 (2023/07) 中都表現出優異,儘管在與 GPT-4 和 PaLM 等封閉源模型競爭時稍微落後,但考慮到它們擁有更龐大的參數規模和使用私有數據進行訓練,這是可以理解的。Llama 2 模型系列是一個開源模型,供研究和有限的商業用途使用。
Parameter-Efficient Fine Tuning (PEFT)
在自己的數據集上微調語言模型是有潛力的。然而,這個過程經常需要大量的 GPU 記憶體( video RAM, vRAM),且可能是一項資源密集型的工作。例如,微調一個有 650 億參數的模型需要 780 GB GPU 記憶體,相當於 10 個A100 80 GB GPUs。這種資源需求已經超出一般大眾的財力範圍。
EJ Hu 在 2021 年提出的 LoRA: Low-Rank Adaptation of Large Language Models。LoRA 的架構涉及凍結預訓練模型 Transformer 的權重,並在矩陣中訓練額外的權重變化,而不犧牲關鍵資訊。
考慮一個 A × B 權重矩陣的權重更新矩陣 ΔW。我們可以將 ΔW分解為兩個較小的矩陣,WA 和 WB,使得 ΔW = WA * WB。其中,WA 是一個 A × r 維矩陣,WB 是一個 r × B 維矩陣。關鍵的想法是保持原始權重 W凍結,僅訓練新的矩陣WA和WB。這種簡潔的方法概括了 LoRA方法的本質
QLoRA (Efficient Finetuning of Quantized LLMs), introduced by Dettmers et al. in 2023
- 4-bit NormalFloat Quantization: QLoRA 引入了一種新的量化方法,該方法改進了傳統的分位數量化。這些技術對於節省記憶體而不降低性能至關重要,使得在深度學習模型微調中更容易使用 4 位量化。它被設計用於對常態分佈的數據進行量化。這種數據類型可以在維持 16 位性能水平的同時,減少記憶體使用。因此,它可以在保持性能的同時降低記憶體占用,這對於在有限的硬體資源上進行深度學習模型的訓練和微調非常有用。簡單來說是把量化資料進行壓縮。
- Double Quantization: QLoRA 更進一步量化常數,節省了更多的記憶體。這種巧妙的方法壓縮了模型資訊,同時保持了整體性能。
- Paging with Unified Memory: 利用 NVIDIA Unified Memory 功能,QLoRA 實現了 CPU 和 GPU 之間無縫的頁到頁傳輸。這種智能的記憶體管理確保了 GPU 處理過程中無錯誤,即使在 GPU 面臨記憶體限制的情況下也是如此。
Colab 實作
# 安裝套件
!pip install -q accelerate==0.22.0 peft==0.4.0 bitsandbytes==0.41.1 transformers==4.31.0 trl==0.7.1
# 匯入必要的模組和套件
import warnings # 忽略警告
warnings.simplefilter("ignore")
import os
import torch
from datasets import load_dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
HfArgumentParser,
TrainingArguments,
pipeline,
logging,
)
from peft import LoraConfig, PeftModel
from trl import SFTTrainer
超參數設定解釋
- model_name : 預訓練模型的名稱。
- dataset_name : 訓練數據集的文件路徑。
- new_model : 新模型的名稱。
- lora_r, lora_alpha, lora_dropout : LoRA 的相關參數。
- use_4bit : 是否使用 4 位定點數。
- bnb_4bit_compute_dtype : 4 位定點數計算的數據類型。
- bnb_4bit_quant_type : 4 位定點數的量化類型。
- use_nested_quant : 是否使用嵌套量化。
- output_dir : 輸出的目錄。
- num_train_epochs : 訓練的週期數。
- fp16 : 是否使用 16 位浮點數。
- bf16 : 是否使用 bfloat16。
- per_device_train_batch_size : 每個設備的訓練批次大小。
- per_device_eval_batch_size : 每個設備的評估批次大小。
- gradient_accumulation_steps : 梯度累積的步數。
- gradient_checkpointing : 是否使用梯度檢查點。
- max_grad_norm : 梯度的最大範數。
- learning_rate : 學習速率。
- weight_decay : 權重衰減。
- optim : 優化器。
- lr_scheduler_type : 學習速率的調整方式。
- max_steps : 最大的訓練步數。
- warmup_ratio : 學習速率的熱身比例。
- group_by_length : 是否根據句子的長度將它們分組。
- save_steps : 模型保存的步數。
- logging_steps : 日誌記錄的步數。
- max_seq_length : 輸入序列的最大長度。
- packing : 是否打包序列。
- device_map : 使用哪一個GPU。
- 詳細參數:https://huggingface.co/docs/transformers/v4.31.0/en/main_classes/trainer#transformers.TrainingArguments
載入模型及設定參數
model_name = "NousResearch/Llama-2-7b-chat-hf"
new_model = "llama-2-7b-custom"
dataset_name = "./train.jsonl"
################################################################################
# Quantized LLMs with Low-Rank Adapters (QLoRA) parameters
################################################################################
lora_r = 64
lora_alpha = 16
lora_dropout = 0.1
################################################################################
# bitsandbytes parameters 輕量級封裝,專門用於CUDA自定義函數,特別是8位優化器、矩陣乘法和量化
################################################################################
use_4bit = True
bnb_4bit_compute_dtype = "float16" # float16 or bfloat16
bnb_4bit_quant_type = "nf4" # fp4 or nf4
use_nested_quant = False
################################################################################
# TrainingArguments parameters
################################################################################
output_dir = "./results"
num_train_epochs = 1
fp16 = False
bf16 = False
per_device_train_batch_size = 4
per_device_eval_batch_size = 4
gradient_accumulation_steps = 1
gradient_checkpointing = True
max_grad_norm = 0.3
learning_rate = 2e-4
weight_decay = 0.001
optim = "paged_adamw_32bit"
lr_scheduler_type = "cosine"
max_steps = -1
warmup_ratio = 0.03
group_by_length = True
save_steps = 5
logging_steps = 5
################################################################################
# Supervised finetuning (SFT) parameters
################################################################################
max_seq_length = None
packing = False
device_map = {"": 0} #{"": 0} or "auto"
下載 GPT-4 合成的資料
# 下載訓練及測試資料集
!wget https://github.com/f901107/Fine_tuning_LLMs/releases/download/Dataset/train.jsonl
!wget https://github.com/f901107/Fine_tuning_LLMs/releases/download/Dataset/test.jsonl
讀取資料集 & 前處理
# 讀取資料集
train_dataset = load_dataset('json', data_files='./train.jsonl', split="train") # 從JSON文件中載入訓練數據集
valid_dataset = load_dataset('json', data_files='./test.jsonl', split="train") # 從JSON文件中載入驗證數據集
# 對數據集進行前處理,將提示和回應組合成文本對
train_dataset = train_dataset.map(lambda examples: {'text': [prompt + response for prompt, response in zip(examples['prompt'], examples['response'])]}, batched=True)
valid_dataset = valid_dataset.map(lambda examples: {'text': [prompt + response for prompt, response in zip(examples['prompt'], examples['response'])]}, batched=True)
訓練模型
# 定義位元和字節量化的相關配置
compute_dtype = getattr(torch, bnb_4bit_compute_dtype)
bnb_config = BitsAndBytesConfig(
load_in_4bit=use_4bit,
bnb_4bit_quant_type=bnb_4bit_quant_type,
bnb_4bit_compute_dtype=compute_dtype,
bnb_4bit_use_double_quant=use_nested_quant,
)
# 檢查 GPU 是否與 bfloat16 相容
if compute_dtype == torch.float16 and use_4bit:
major, _ = torch.cuda.get_device_capability()
if major >= 8:
print("=" * 80)
print("Your GPU supports bfloat16: accelerate training with bf16=True")
print("=" * 80)
# 從預訓練模型中載入自動生成模型
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map=device_map
)
model.config.use_cache = False
model.config.pretraining_tp = 1
# 載入與模型對應的分詞器
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right" # Fix weird overflow issue with fp16 training
# 定義 Prompt Engineering Fine-Tuning (PEFT)的相關設定
peft_config = LoraConfig(
lora_alpha=lora_alpha,
lora_dropout=lora_dropout,
r=lora_r,
bias="none",
task_type="CAUSAL_LM",
)
# 設置訓練參數
training_arguments = TrainingArguments(
output_dir=output_dir,
num_train_epochs=num_train_epochs,
per_device_train_batch_size=per_device_train_batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
optim=optim,
save_steps=save_steps,
logging_steps=logging_steps,
learning_rate=learning_rate,
weight_decay=weight_decay,
fp16=fp16,
bf16=bf16,
max_grad_norm=max_grad_norm,
max_steps=max_steps,
warmup_ratio=warmup_ratio,
group_by_length=group_by_length,
lr_scheduler_type=lr_scheduler_type,
report_to="tensorboard", #"all"
evaluation_strategy="steps",
eval_steps=5 # 每5步驗證
)
# 使用 SFTTrainer 進行監督式微調訓練
trainer = SFTTrainer(
model=model,
train_dataset=train_dataset, # 在這裡傳入驗證集
eval_dataset=valid_dataset,
peft_config=peft_config,
dataset_text_field="text",
max_seq_length=max_seq_length,
tokenizer=tokenizer,
args=training_arguments,
packing=packing,
)
# 開始訓練模型
trainer.train()
# 儲存微調後的模型
trainer.model.save_pretrained(new_model)
輸出日誌結果
# %load_ext tensorboard
# %tensorboard --logdir results/runs
# 日誌輸出
logging.set_verbosity(logging.CRITICAL)
生成結果
# 執行模型的文本生成流程
prompt = "What is a large language model?"
pipe = pipeline(task="text-generation", model=model, tokenizer=tokenizer, max_length=200)
result = pipe(f"<s>[INST] {prompt} [/INST]")
print(result[0]['generated_text']) # 輸出生成的文本
清理 vRAM
# Empty VRAM
del model
del pipe
del trainer
import gc # 清理垃圾桶
gc.collect()
gc.collect()
模型儲存到 Google Drive 中
from google.colab import drive
drive.mount('/content/drive')
model_path = "/content/drive/MyDrive/GPT_Llama-2_fine-tune" # 更改為您的路徑
# 以FP16重新載入模型並將其與LoRA權重合併
base_model = AutoModelForCausalLM.from_pretrained(
model_name,
low_cpu_mem_usage=True,
return_dict=True,
torch_dtype=torch.float16,
device_map=device_map,
)
model = PeftModel.from_pretrained(base_model, new_model)
model = model.merge_and_unload()
# 重新載入分詞器以進行保存
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
# 儲存合併後的模型
model.save_pretrained(model_path)
tokenizer.save_pretrained(model_path)
從 Google Drive 載入微調後的模型並執行推論
from google.colab import drive
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
import torch
drive.mount('/content/drive')
model_path = "/content/drive/MyDrive/GPT_Llama-2_fine-tune" # 更改為您儲存模型的路徑
model = AutoModelForCausalLM.from_pretrained(model_path,
device_map="auto",
offload_folder="offload",
torch_dtype=torch.float16)
tokenizer = AutoTokenizer.from_pretrained(model_path)
推論
prompt = "What is 2 + 2?" # 更改為期望的提示
gen = pipeline('text-generation', model=model, max_new_tokens= 2048, tokenizer=tokenizer)
result = gen(prompt)
print(result[0]['generated_text'])
Reference:
- Fine-Tuning LLama2.0 with QLoRA’s Single GPU Magic
- [MiuLab] Taiwan-LLaMa2
- Taiwan-LLaMa-v1.0
- Huggingface的中文資料集