1. llama 3 微調(diào)基礎(chǔ)
1.1 llama 3 簡介
官方blog
llama 3 目前有兩個版本:8B版和70B版。8B版本擁有8.03B參數(shù),其尺寸較小,可以在消費(fèi)者硬件上本地運(yùn)行。
- meta-llama/Meta-Llama-3-8B
- meta-llama/Meta-Llama-3-70B
- 超過400B個參數(shù)的第三個版本目前仍在訓(xùn)練中……
Llama 3與Llama 2具有相同的架構(gòu),但詞匯表要大得多,包含128k entries,而Llama 2只有32k entries,根據(jù)Meta的說法,詞匯表的擴(kuò)展顯著提高了模型表現(xiàn)。Llama 3的預(yù)訓(xùn)練數(shù)據(jù)包含5%的高質(zhì)量非英語數(shù)據(jù)。注意:Meta在model card中仍然提到Llama 3更適合用于英語任務(wù)。
另一方面,詞匯表的擴(kuò)展意味著token embeddings需要更多的數(shù)據(jù)才能被訓(xùn)練的更準(zhǔn)確。Meta在15T tokens上訓(xùn)練Llama 3。相比之下,Llama 2只在2T tokens上訓(xùn)練,Google Gemma在6T tokens訓(xùn)練,這在當(dāng)時似乎已經(jīng)很多了。
模型的性能表現(xiàn)如下圖所示:
1.2 llama 3 8b Fully Fine-tuning內(nèi)存占用分析
Fully Fine-tuning an LLM需要更新其所有參數(shù),這種微調(diào)需要大量的內(nèi)存。
- 模型需要被完全加載到 GPU 內(nèi)存中
- 此外,通常用于微調(diào) LLMs 的優(yōu)化器 AdamW 會為模型中的每個參數(shù)創(chuàng)建并存儲 2 個參數(shù)在 GPU 內(nèi)存中
- 并且我們還需要存儲在微調(diào)過程中創(chuàng)建的張量,即激活值,以便在反向傳播過程中用于更新模型參數(shù)的梯度。
對Llama 3 8B進(jìn)行微調(diào),例如,批量大小為8,序列長度為512,將消耗128.87GB的顯存。注意:這個內(nèi)存消耗是一個估計值,沒有考慮任何的優(yōu)化,比如梯度檢查點和張量并行。
model | loading the model | optimizer states | activations | total |
---|---|---|---|---|
llama 3 8b | 14.96GB | 59.83GB | 54.08GB | 128.87GB |
(我會在下一篇文章中提供估算大型語言模型(LLM)內(nèi)存消耗的計算方法)
幸運(yùn)的是,我們可以很容易地減少這三種參數(shù)的內(nèi)存消耗:
- Optimizer states:默認(rèn)情況下,AdamW 的參數(shù)為 float32,每項占用 4 字節(jié)。AdamW-8bit 是另一種不錯的選擇,它將參數(shù)量化為 8 位,即減少了內(nèi)存消耗從 59.8 GB 到 15 GB。如果使用的框架不復(fù)制模型參數(shù),內(nèi)存消耗會大大減少。
- Model:我們可以將模型量化為4位。它將內(nèi)存消耗分成近4份,即從15 GB到4 GB。在實踐中,為了保持其性能,并不是所有的LLM模塊都會被量化。
- Activations:我們需要存儲激活來計算梯度。然而,使用gradient checkpointing,我們可以在反向傳播過程中動態(tài)地重新計算激活值,而不是在整個訓(xùn)練過程中都存儲這些激活值。它大大減少了激活的內(nèi)存消耗,從54GB減少到10 GB。
在應(yīng)用了所有這些優(yōu)化措施之后,微調(diào)過程需要29GB的內(nèi)存。雖然這仍然太多,但至少現(xiàn)在可以使用兩個24GB的GPU來對模型進(jìn)行微調(diào)了。
1.3 llama 3 8b PEFT Fine-tuning內(nèi)存占用分析
使用PEFT方法,如LoRA,我們可以在模型頂部微調(diào)一個適配器,不需要完全重新訓(xùn)練模型。為了進(jìn)一步降低內(nèi)存消耗。
- 使用LoRA,需要一個帶有24 GB RAM的GPU來微調(diào)Llama 3;
- 使用QLoRA,只需要一個帶有16 GB RAM的GPU。
2. PEFT方法微調(diào)llama 3
1、QLoRA 是量化的 LoRA 與 LLMs 的結(jié)合。要使用這種方法對 Llama 3 8B 進(jìn)行微調(diào),我們需要安裝
pip install --upgrade bitsandbytes transformers peft accelerate datasets trl
2、然后導(dǎo)入需要的pkgs
import torch, os
from datasets import load_dataset
from peft import LoraConfig, prepare_model_for_kbit_training
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
)
from trl import SFTTrainer
3、如果你擁有較新的GPU,就可以使用bfloat16
數(shù)據(jù)類型以獲得更好的訓(xùn)練穩(wěn)定性,并使用FlashAttention
來減少處理長序列時的內(nèi)存消耗。下面的代碼會自動檢測GPU是否兼容bfloat16
、FlashAttention
:
#use bf16 and FlashAttention if supported
if torch.cuda.is_bf16_supported():
os.system('pip install flash_attn')
compute_dtype = torch.bfloat16
attn_implementation = 'flash_attention_2'
else:
compute_dtype = torch.float16
attn_implementation = 'sdpa'
4、然后,我們需要初始化并配置Tokenizer。通常,LLMs在預(yù)訓(xùn)練時不包含pad_token。然而,在微調(diào)過程中,由于我們的訓(xùn)練示例長度不相同,我們需要將其填充到batch中。我們可以創(chuàng)建并添加一個pad_token到詞匯表中,但更簡單的選擇是將eos_token指定為pad_token。
model_name = "meta-llama/Meta-Llama-3-8B"
#Tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name, add_eos_token=True, use_fast=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.pad_token_id = tokenizer.eos_token_id
tokenizer.padding_side = 'left'
注意,我們使用的是左邊填充。如果想使用flash_attention,右填充是不兼容的。
5、至于微調(diào)數(shù)據(jù)集,可以選擇了 timdettmers/openassistant-guanaco,因為這個數(shù)據(jù)集足夠小。
6、然后,我們創(chuàng)建bnb_config并加載模型:
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=compute_dtype,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
model_name, quantization_config=bnb_config, device_map={"": 0}, attn_implementation=attn_implementation
)
7、bnb_config定義了在4位精度下加載模型,并對量化常數(shù)進(jìn)行量化(即雙重量化)。在前向傳遞過程中,如果你的GPU支持bfloat16數(shù)據(jù)類型,則將創(chuàng)建bfloat16張量。請注意:如果你的GPU不支持bfloat16,則筆記本將使用float16。然而,這可能會導(dǎo)致訓(xùn)練不穩(wěn)定。如果你發(fā)現(xiàn)訓(xùn)練損失降至0或NaN,請將compute_dtype更改為torch.float32。
8、為了減少激活的內(nèi)存消耗,我們還需要啟用梯度檢查點,這是通過
model = prepare_model_for_kbit_training(model)
9、對于 LoRA 的配置,可以使用:
peft_config = LoraConfig(
lora_alpha=16,
lora_dropout=0.05,
r=16,
bias="none",
task_type="CAUSAL_LM",
target_modules= ['k_proj', 'q_proj', 'v_proj', 'o_proj', "gate_proj", "down_proj", "up_proj"]
)
可以增加rank來獲得更好的結(jié)果。增加rank也會增加內(nèi)存消耗,因為rank增大,適配器的參數(shù)也會增加。
10、接下來,定義訓(xùn)練參數(shù)和超參數(shù):
training_arguments = TrainingArguments(
output_dir="./Llama3_8b_QLoRA",
evaluation_strategy="steps",
do_eval=True,
optim="paged_adamw_8bit",
per_device_train_batch_size=8,
gradient_accumulation_steps=4,
per_device_eval_batch_size=8,
log_level="debug",
save_strategy="epoch",
logging_steps=100,
learning_rate=1e-4,
fp16 = not torch.cuda.is_bf16_supported(),
bf16 = torch.cuda.is_bf16_supported(),
eval_steps=100,
num_train_epochs=3,
warmup_ratio=0.1,
lr_scheduler_type="linear",
)
11、使用"paged_adamw_8bit",會在需要時將一些優(yōu)化器狀態(tài)存儲到CPU RAM中,以進(jìn)一步減少GPU內(nèi)存消耗。
補(bǔ)充:QLoRA其實是核心就是在LoRA的技術(shù)加上深度的量化過程。核心優(yōu)化思想包括以下三點:
- 4bit NoramlFloat Quantization:一種新的數(shù)據(jù)類型,只用4字節(jié)表征參數(shù)并且保證整個模型的精度損失極小.(和我們之前的Int8,int4量化方式不同, 原理這篇先不展開了)
- Double Quantization:對第一次量化后的那些常量再進(jìn)行一次量化,減少存儲空間。
- Paged optimizers:使用NVIDIA統(tǒng)一內(nèi)存功能,該功能在CPU和GPU之間進(jìn)行自動page對page傳輸,以便在GPU偶爾OOM的情況下進(jìn)行??梢詮默F(xiàn)象上理解成出現(xiàn)訓(xùn)練過程中偶發(fā)OOM時能夠自動處理,保證訓(xùn)練正常訓(xùn)練下去。
對于批量大小,隨機(jī)選擇了一個批量大小為32(每個設(shè)備的批量大小為8,梯度累積步驟為4(8x4=32)的配置)。該配置消耗了16.6 GB的GPU內(nèi)存。如果你只有16 GB的GPU,請將每個設(shè)備的批量大小減少到4。
12、最后,開始微調(diào)時,運(yùn)行以下命令:
trainer = SFTTrainer(
model=model,
train_dataset=ds['train'],
eval_dataset=ds['test'],
peft_config=peft_config,
dataset_text_field="text",
max_seq_length=512,
tokenizer=tokenizer,
args=training_arguments,
)
trainer.train()
13、使用Google Colab的L4實例完成這3個epoch大約需要10個小時。
3. 將微調(diào)后的adapter集成到Llama 3中
為了避免每次使用時都加載adapter,你可以將其合并到 Llama 3 中。當(dāng)適配器已經(jīng)使用 QLoRA 進(jìn)行微調(diào)時,必須小心進(jìn)行合并,以保持adapter的大部分準(zhǔn)確性。我們必須遵循以下步驟:
- 加載并量化Llama 3
- Dequantize Llama 3 to the compute dtype used during QLoRA fine-tuning
- Merge the adapter into the dequantized model
- Save the resulting model
最后得到一個沒有量化的模型。我們不能像微調(diào)那樣用bitsandbytes量化它,否則會嚴(yán)重降低模型的性能。使用AWQ或GPTQ來代替即可。
4. 使用AWQ對llama 3進(jìn)行4位量化
AWQ是一種量化方案,它保留了模型的重要權(quán)重。AWQ很準(zhǔn)確,也受到高效的推理核的支持。首先需要安裝AutoAWQ:
pip install autoawq
然后,用幾行代碼執(zhí)行量化,例如,要量化前一節(jié)合并后得到的模型:
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
tokenizer_path = "meta-llama/Meta-Llama-3-8B"
model_path = './dqz_merge/'
quant_path = 'llama-3-oasstguanaco3e-awq-4bit'
quant_config = { "zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM" }
# Load model and tokenizer
model = AutoAWQForCausalLM.from_pretrained(model_path, safetensors=True)
tokenizer = AutoTokenizer.from_pretrained(tokenizer_path, use_fast=True)
# Quantize
model.quantize(tokenizer, quant_config=quant_config)
# Save quantized model with safetensors
model.save_quantized("./"+quant_path, safetensors=True)
tokenizer.save_pretrained("./"+quant_path)
這將把量化模型保存到一個名為“l(fā)lama-3-oasstguanaco3e-awq-4bit”的目錄中。
5. 完全微調(diào)模型
QLoRA和LoRA只是微調(diào)適配器。如果你真的想微調(diào)整個模型,你可以嘗試GaLore。GaLore將梯度投影到低秩子空間,以顯著減少它們的維數(shù),從而減少它們的內(nèi)存消耗。雖然GaLore大大降低了優(yōu)化器狀態(tài)的內(nèi)存需求,但你仍然需要48GB的GPU RAM。
CODE
具體的notebook代碼可以在github倉庫中拿到。文章來源:http://www.zghlxwxcb.cn/news/detail-859897.html
notebook中包含了4個部分:文章來源地址http://www.zghlxwxcb.cn/news/detail-859897.html
- QLoRA fine-tuning
- Merging the fine-tuned adapter into the base model
- Quantization the Llama 3 with AWQ
- Appendices: LoRA and GaLore fine-tuning
到了這里,關(guān)于微調(diào)llama 3 — PEFT微調(diào)和全量微調(diào)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!