官方blog
llama 3 目前有两个版本:8B版和70B版。8B版本拥有8.03B参数,其尺寸较小,可以在消费者硬件上本地运行。
Llama 3与Llama 2具有相同的架构,但词汇表要大得多,包含128k entries,而Llama 2只有32k entries,根据Meta的说法,词汇表的扩展显著提高了模型表现。Llama 3的预训练数据包含5%的高质量非英语数据。注意:Meta在model card中仍然提到Llama 3更适合用于英语任务。
另一方面,词汇表的扩展意味着token embeddings需要更多的数据才能被训练的更准确。Meta在15T tokens上训练Llama 3。相比之下,Llama 2只在2T tokens上训练,Google Gemma在6T tokens训练,这在当时似乎已经很多了。
模型的性能表现如下图所示:
Fully Fine-tuning an LLM需要更新其所有参数,这种微调需要大量的内存。
对Llama 3 8B进行微调,例如,批量大小为8,序列长度为512,将消耗128.87GB的显存。注意:这个内存消耗是一个估计值,没有考虑任何的优化,比如梯度检查点和张量并行。
model | loading the model | optimizer states | activations | total |
---|---|---|---|---|
llama 3 8b | 14.96GB | 59.83GB | 54.08GB | 128.87GB |
(估算大型语言模型(LLM)内存消耗的计算方法)
幸运的是,我们可以很容易地减少这三种参数的内存消耗:
在应用了所有这些优化措施之后,微调过程需要29GB的内存。虽然这仍然太多,但至少现在可以使用两个24GB的GPU来对模型进行微调了。
使用PEFT方法,如LoRA,我们可以在模型顶部微调一个适配器,不需要完全重新训练模型。为了进一步降低内存消耗。
1、QLoRA 是量化的 LoRA 与 LLMs 的结合。要使用这种方法对 Llama 3 8B 进行微调,我们需要安装
pip install --upgrade bitsandbytes transformers peft accelerate datasets trl
2、然后导入需要的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
数据类型以获得更好的训练稳定性,并使用FlashAttention
来减少处理长序列时的内存消耗。下面的代码会自动检测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在预训练时不包含pad_token。然而,在微调过程中,由于我们的训练示例长度不相同,我们需要将其填充到batch中。我们可以创建并添加一个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、至于微调数据集,可以选择了 timdettmers/openassistant-guanaco,因为这个数据集足够小。
6、然后,我们创建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位精度下加载模型,并对量化常数进行量化(即双重量化)。在前向传递过程中,如果你的GPU支持bfloat16数据类型,则将创建bfloat16张量。请注意:如果你的GPU不支持bfloat16,则笔记本将使用float16。然而,这可能会导致训练不稳定。如果你发现训练损失降至0或NaN,请将compute_dtype更改为torch.float32。
8、为了减少激活的内存消耗,我们还需要启用梯度检查点,这是通过
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来获得更好的结果。增加rank也会增加内存消耗,因为rank增大,适配器的参数也会增加。
10、接下来,定义训练参数和超参数:
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",会在需要时将一些优化器状态存储到CPU RAM中,以进一步减少GPU内存消耗。
补充:QLoRA其实是核心就是在LoRA的技术加上深度的量化过程。核心优化思想包括以下三点:
- 4bit NoramlFloat Quantization:一种新的数据类型,只用4字节表征参数并且保证整个模型的精度损失极小.(和我们之前的Int8,int4量化方式不同, 原理这篇先不展开了)
- Double Quantization:对第一次量化后的那些常量再进行一次量化,减少存储空间。
- Paged optimizers:使用NVIDIA统一内存功能,该功能在CPU和GPU之间进行自动page对page传输,以便在GPU偶尔OOM的情况下进行。可以从现象上理解成出现训练过程中偶发OOM时能够自动处理,保证训练正常训练下去。
对于批量大小,随机选择了一个批量大小为32(每个设备的批量大小为8,梯度累积步骤为4(8x4=32)的配置)。该配置消耗了16.6 GB的GPU内存。如果你只有16 GB的GPU,请将每个设备的批量大小减少到4。
12、最后,开始微调时,运行以下命令:
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个小时。
为了避免每次使用时都加载adapter,你可以将其合并到 Llama 3 中。当适配器已经使用 QLoRA 进行微调时,必须小心进行合并,以保持adapter的大部分准确性。我们必须遵循以下步骤:
AWQ是一种量化方案,它保留了模型的重要权重。AWQ很准确,也受到高效的推理核的支持。首先需要安装AutoAWQ:
pip install autoawq
然后,用几行代码执行量化,例如,要量化前一节合并后得到的模型:
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)
这将把量化模型保存到一个名为“llama-3-oasstguanaco3e-awq-4bit”的目录中。
QLoRA和LoRA只是微调适配器。如果你真的想微调整个模型,你可以尝试GaLore。GaLore将梯度投影到低秩子空间,以显著减少它们的维数,从而减少它们的内存消耗。虽然GaLore大大降低了优化器状态的内存需求,但你仍然需要48GB的GPU RAM。
具体的notebook代码可以在github仓库中拿到。
notebook中包含了4个部分: