所有文章 > AI驱动 > LLM微调系列:LORA(5)
LLM微调系列:LORA(5)

LLM微调系列:LORA(5)

Lora

LoRA,英文全称Low-Rank Adaptation of Large Language Models。

冻结预训练好的模型权重参数,然后在每个Transformer块里注入可训练的层,由于不需要对模型的权重参数重新计算梯度,所以,大大减少了需要训练的计算量。

从上图可以直观的看出,预训练模型旁增加了右侧的“旁支”,也就是先用一个Linear层A,将数据从 d维降到r,再用第二个Linear层B,将数据从r变回d维。最后再将左右两部分的结果相加融合,得到输出的hidden_state。

  1. 在训练阶段,预训练参数W固定,只更新A和B参数,A和B模拟W的变化量。
  2. 在推理阶段,用A、B参数与原预训练参数相加替换原有预训练模型的参数。推理过程没有额外的参数量和计算量
  3. 相当于是用LoRA去模拟Full-finetune的过程,几乎不会带来效果损失

AdaLORA

AdaLoRA是自适应预算分配以实现参数有效的微调,由于在不太重要的权重矩阵添加更多的参数会产生很少的收益,甚至会损害模型性能。因此为了降低不重要的权重的计算,AdaLoRA通过调整增量矩阵的秩,以控制参数参与计算的量。

  • 关键的增量矩阵被分配了高秩,这样可以捕获更细粒度和特定任务的信息。
  • 不太重要的增量矩阵被修剪为具有较低的秩,以防止过度拟合并节省计算资源。

调整矩阵秩的方法

lora是根据 让模型学习这两个矩阵,用近似SVD分解的结果,同时将 的秩统一成为

AdaLora只直接利用SVD分解的结果,根据 学习型    这三个权重。

  1. 首先,初始化 为0矩阵, 为高斯随机矩阵。这样初始化的目的和lora一样,能在训练开始确保 是0矩阵,避免引入噪声。
  2. 固定 ,更新。得到的 中对角线较小的值更新为0,相当于0对应的 进行mask,让下一次计算不参与计算,这里就相当于变秩。只将重置为0,而不是将整个删掉,是因为随着模型的学习和更新,后面这样的三元组可能会变的重要,显然用mask更合理。
  3. 中较小的值更新为0后固定住,再更新 ,此时不重要的信息就不参与计算了。
  4. 重复2、3步

下面的内容解释了这些参数的中文含义

QLora实战

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, BitsAndBytesConfig

# modelpath="meta-llama/Llama-2-7b-hf"
modelpath="meta-llama/Meta-Llama-3-8B"

# Load 4-bit quantized model
model = AutoModelForCausalLM.from_pretrained(
    modelpath,    
    device_map="auto",
    quantization_config=BitsAndBytesConfig(
        load_in_4bit=True,
        bnb_4bit_quant_type="nf4",
        bnb_4bit_use_double_quant=True,
        bnb_4bit_compute_dtype=torch.bfloat16,
    ),
    torch_dtype=torch.bfloat16,
)
model.config.use_cache = False

tokenizer = AutoTokenizer.from_pretrained(modelpath)
tokenizer.pad_token = tokenizer.eos_token

配置lora

LoRAConfig() 用于配置,哪些位置添加lora层。在这里,将 LoRA 层添加到自注意力模块的所有线性投影(attn_matrices=[“q”, “k”, “v”])以及中间和输出线性层。

import adapters
from adapters import LoRAConfig

adapters.init(model)

config = LoRAConfig(
    selfattn_lora=True, 
    intermediate_lora=True, 
    output_lora=True,
    attn_matrices=["q", "k", "v"],
    alpha=16, r=64, dropout=0.1
)
model.add_adapter("assistant_adapter", config=config)
model.train_adapter("assistant_adapter")

刚查需要更新的参数

print(model.adapter_summary()) # 观察需要微调的参数量



for param in model.parameters():
    if param.ndim == 1:
        # cast the small parameters (e.g. layernorm) to fp32 for stability
        param.data = param.data.to(torch.float32)

# Enable gradient checkpointing to reduce required memory if needed
# model.gradient_checkpointing_enable()
# model.enable_input_require_grads()

class CastOutputToFloat(torch.nn.Sequential):
    def forward(self, x): return super().forward(x).to(torch.float32)
model.lm_head = CastOutputToFloat(model.lm_head)

model

数据准备

from datasets import load_dataset

dataset = load_dataset("timdettmers/openassistant-guanaco")

def tokenize(element):
    return tokenizer(
        element["text"],
        truncation=True,
        max_length=512, # can set to longer values such as 2048
        add_special_tokens=False,
    )

dataset_tokenized = dataset.map(
    tokenize, 
    batched=True, 
    num_proc=os.cpu_count(),    # multithreaded
    remove_columns=["text"]     # don't need this anymore, we have tokens from here on
)

开始训练

from adapters import AdapterTrainer
from transformers import DataCollatorForLanguageModeling

args = TrainingArguments(
    output_dir="output/llama_qlora",
    per_device_train_batch_size=1,
    per_device_eval_batch_size=1,
    evaluation_strategy="steps",
    logging_steps=10,
    save_steps=500,
    eval_steps=187,
    save_total_limit=3,
    gradient_accumulation_steps=16,
    max_steps=1875,
    lr_scheduler_type="constant",
    optim="paged_adamw_32bit",
    learning_rate=0.0002,
    group_by_length=True,
    bf16=True,
    warmup_ratio=0.03,
    max_grad_norm=0.3,
)


trainer = AdapterTrainer(
    model=model,
    tokenizer=tokenizer,
    data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
    train_dataset=dataset_tokenized["train"],
    eval_dataset=dataset_tokenized["test"],
    args=args,
)

trainer.train()

推理

from transformers import logging
logging.set_verbosity(logging.CRITICAL)

def prompt_model(model, text: str):
    batch = tokenizer(f"### Human: {text}\n### Assistant:", return_tensors="pt")
    batch = batch.to(model.device)
    
    model.eval()
    with torch.inference_mode(), torch.cuda.amp.autocast():
        output_tokens = model.generate(**batch, max_new_tokens=50)

    return tokenizer.decode(output_tokens[0], skip_special_tokens=True)


print(prompt_model(model, "Explain Calculus to a primary school student"))

权重融合

model.merge_adapter("assistant_adapter")
  • LORA

https://arxiv.org/pdf/2106.09685

https://github.com/microsoft/LoRA

  • AdaLORA

https://arxiv.org/pdf/2303.10512

https://github.com/QingruZhang/AdaLoRA

文章转自微信公众号@CourseAI

#你可能也喜欢这些API文章!