peft lora微调代码解析,llama2-7b lora微调实战

    lora论文地址:arxiv.org/pdf/2106.09685

    dora论文地址:DoRA: Weight-Decomposed Low-Rank Adaptation (arxiv.org)

    lora github地址:microsoft/LoRA: Code for loralib, an implementation of "LoRA: Low-Rank Adaptation of Large Language Models" (github.com)

    peft github地址:huggingface/peft: PEFT: State-of-the-art Parameter-Efficient Fine-Tuning. (github.com)

    

    运行微调使用的peft版本是v0.11.1, 代码解析使用的peft版本是0.11.2.dev0,这个版本单独将dora提取出来了。

目录

一、peft lora微调代码解析

    1.inject_adapter

    2._create_and_replace

    3._create_new_module

二、llama2-7b lora微调实战

    1.构造一个简单的数据集

    2.配置lora config

    3.配置训练器参数

    4.开始训练

    5.dora

    6.权重融合


一、peft lora微调代码解析

    1.inject_adapter

    src/peft/tuners/tuners_utils.py  ——  def inject_adapter

    

def inject_adapter(self, model: nn.Module, adapter_name: str, autocast_adapter_dtype: bool = True) -> None:
    // ......
    for key in key_list:
        # Check for modules_to_save in case
        if _check_for_modules_to_save and any(
            key.endswith(f"{module_to_save}") for module_to_save in peft_config.modules_to_save
        ):
            # Optionally set the modules to save
            parent, target, target_name = _get_submodules(model, key)

            if not isinstance(target, ModulesToSaveWrapper):
                new_module = ModulesToSaveWrapper(target, adapter_name)
                setattr(parent, target_name, new_module)
            else:
                target.update(adapter_name)

            _has_modules_to_save = True
            continue

        if not self._check_target_module_exists(peft_config, key):
            continue

        self.targeted_module_names.append(key)
        is_target_modules_in_base_model = True
        parent, target, target_name = _get_submodules(model, key)
        self._create_and_replace(peft_config, adapter_name, target, target_name, parent, current_key=key)
    // ......

    这里是使用lora替换模型,方法用于创建适配器层并将目标模块替换为适配器层。

    这里的modules_to_save这个参数很重要,会检查key_list中的每个模块中是否以modules_to_save结尾,如果是会将其包装为 ModulesToSaveWrapper 模块以保存其状态,意思是会不进行lora层的替换,该线性层不会被替换为两个低秩矩阵的,而是会以原来的结构大小进行全参训练并最终保存在lora权重当中。如果检查通过则会self._create_and_replace进行lora层的替换。

    2._create_and_replace

    src/peft/tuners/lora/model.py  ——  def _create_and_replace()

def _create_and_replace(...):
    // ......
    if isinstance(target, LoraLayer) and not isinstance(target, AdaLoraLayer):
        target.update_layer(
            adapter_name,
            r,
            lora_alpha=alpha,
            lora_dropout=lora_config.lora_dropout,
            init_lora_weights=lora_config.init_lora_weights,
            use_rslora=lora_config.use_rslora,
            use_dora=lora_config.use_dora,
        )
    else:
        new_module = self._create_new_module(lora_config, adapter_name, target, **kwargs)
        if adapter_name not in self.active_adapters:
            # adding an additional adapter: it is not automatically trainable
            new_module.requires_grad_(False)
        self._replace_module(parent, target_name, new_module, target)
    // ......

    用于创建新模块或更新现有的 LoRA 模块,并将目标模块替换为新创建的模块,如果该层已经是LoraLayer或者是AdaLoraLayer层了,则会进行一次update,如果不是lora层则会创建新的lora层结构并对原始的线性层进行替换。

    3._create_new_module

    src/peft/tuners/lora/model.py  ——  def _create_new_module()

    主要关注:

dispatchers.extend(
    [
        dispatch_eetq,
        dispatch_aqlm,
        dispatch_awq,
        dispatch_gptq,
        dispatch_hqq,
        dispatch_megatron,
        dispatch_default,
    ]
)

    这里与

    目录中的一致,其中是不同初始化方法的lora层结构,在这个函数进行lora层的创建,当然实际上都离不开对lora_A和lora_B两个低秩矩阵的初始化的创建。

二、llama2-7b lora微调实战

    我们这里只关心跑通后看一下我们之前理解的lora微调流程逻辑是否正确,这里不关注最后微调结果的性能精度数据。

    我们先看一下微调之前的llama2-7b结构

    

    看一下其中线性层的结构:

from transformers import AutoModel

model_llama = AutoModel.from_pretrained('llama-2-7b-hf')
print(model_llama.state_dict())

    

    发现其中的layers.0包含了很多,有q_proj,v-proj......等等

    我们现在开始训练,再看一下训练后保存了什么。

    

    1.构造一个简单的数据集

    

from datasets import Dataset

train_dataset = []
en = "good, morning"
zh = "早上好"
train_dataset.append({'text': 'Translate English to Chinese:\nInput:' + en + "\nOutput:" + zh + '</s>'})

train_dataset = Dataset.from_dict({key: [dic[key] for dic in train_dataset] for key in train_dataset[0]})

    

    2.配置lora config

    

from peft import LoraConfig, get_peft_model

peft_config = LoraConfig(
    r=8,
    lora_alpha=8,
    target_modules=['q_proj', "v_proj"],
    lora_dropout=0.05,
    bias='none',
    task_type='CAUSAL_LM',
    modules_to_save=['layers.0.self_attn.q_proj']
)

    r为低秩矩阵的秩,假如原来的线性层维度是1024*1024,设置r后替换为的lora_A维度就是1024*r,lora_B为r*1024。

    target_modules表示将线性层中的q_proj,v_proj替换为lora层,其他的不替换也不会保存。

    modules_to_save这个参数我们在上面提到过,设置中的层不会替换但会更新权重并保存,我这里将layer.0层的q_proj设置为不替换为lora,并保存在最终的lora权重中。

    3.配置训练器参数

    这里直接使用全参SFT的训练器了:

    

from trl import SFTTrainer, SFTConfig

train_aruments = SFTConfig(
    output_dir=output_dir,
    per_device_train_batch_size=64,
    optim='adamw_torch',
    learning_rate=10e-4,
    save_steps=10,
    logging_steps=10,
    group_by_length=False,
    num_train_epochs=20,
    gradient_accumulation_steps=1,
    gradient_checkpointing=True,
    max_grad_norm=0.3,
    lr_scheduler_type='cosine'
)

    4.开始训练

    直接上全部的代码:

    

import torch
from datasets import Dataset
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer, SFTConfig
from transformers import TrainingArguments, AutoModelForCausalLM, AutoTokenizer

train_dataset = []
en = "good, morning"
zh = "早上好"
train_dataset.append({'text': 'Translate English to Chinese:\nInput:' + en + "\nOutput:" + zh + '</s>'})

train_dataset = Dataset.from_dict({key: [dic[key] for dic in train_dataset] for key in train_dataset[0]})


output_dir = r'output'

model_name = r"llama-2-7b-hf"


peft_config = LoraConfig(
    r=8,
    lora_alpha=8,
    target_modules=['q_proj', "v_proj"],
    lora_dropout=0.05,
    bias='none',
    task_type='CAUSAL_LM',
    modules_to_save=['layers.0.self_attn.q_proj']
)

train_aruments = SFTConfig(
    output_dir=output_dir,
    per_device_train_batch_size=64,
    optim='adamw_torch',
    learning_rate=10e-4,
    save_steps=10,
    logging_steps=10,
    group_by_length=False,
    num_train_epochs=20,
    gradient_accumulation_steps=1,
    gradient_checkpointing=True,
    max_grad_norm=0.3,
    lr_scheduler_type='cosine'
)

model = AutoModelForCausalLM.from_pretrained(model_name)
model.enable_input_require_grads()
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
model.config.use_cache = False

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token_id = 0
tokenizer.padding_side = 'right'

trainer = SFTTrainer(
    model=model,
    train_dataset=train_dataset,
    dataset_text_field='text',
    peft_config=peft_config,
    max_seq_length=1024,
    tokenizer=tokenizer,
    args=train_aruments
)

trainer.train()
trainer.model.save_pretrained(output_dir)

    output_dir = r'output'

    model_name = r"llama-2-7b-hf"

    修改为自己的路径

    

    看一下微调后的结果:

    

    因为我们设置了每10次保存一次权重,所以多出了checkpoint-10和checkpoint-20这两个文件。

    先看一下config:    

    发现都是我们刚刚配置过的lora_config参数

    但有一个参数值的注意的是,base_model_name_or_path,这个参数是我们之前加载的原始的llama2-7B权重的路径,这里的路径地址的文件如果变更那么融合的时候就会报错,实际上后面在融合权重的时候不用再加载原始权重也是因为这里保存了训练前的原始权重的路径了。

    再看一下adapter_model.safetensors里保存了什么权重:

    

import torch
from safetensors.torch import load_file, save_file
from safetensors import safe_open

model_path = 'output/adapter_model.safetensors'

tensors = {}
with safe_open(model_path, framework="pt", device='cpu') as f:
    for k in f.keys():
        tensors[k] = f.get_tensor(k)

print(tensors)

    

    与我们之前的代码分析完全一致

    其中没有保存原始的权重,只保存了替换后的lora的两个低秩矩阵lora_A,lora_B,我们target指定的是v_proj和q_proj,所以保存的每一层的权重当中出现了v_proj和q_proj两个lora层,我们又指定了modules_to_save,所以layers.0层的q_proj没有被替换,以原来的全参大小进行训练并保存在了lora权重中。

    5.dora

    peft已经支持dora微调了:

        权重矩阵W可以分解为幅度m和方向V两个组件,即W = m * V,其中m是幅度向量(通过权重矩阵的列范数计算得到),V是方向矩阵。
    微调福哦城中的梯度原始的预训练权重(W0)冻结,而通过DoRA方法,可以训练幅度(m)和方向(V+∆V)的更新。这些更新是通过合并低秩矩阵B和A来实现的,即∆V = BA。
    CV算法工程师的LLM日志(2)PEFT训练技术——10分钟快速理解DORA【原理&&代码】_dora llm-CSDN博客
    具体的原理就是从原来的权重要分解为两个权重幅度m和方向V,其中方向V还要被替换为lora的两个低秩矩阵参数训练。

    只需要在peft的lora配置中配置use_dora即可使用dora微调:

peft_config = LoraConfig(
    r=8,
    lora_alpha=8,
    target_modules=['q_proj', "v_proj"],
    lora_dropout=0.05,
    bias='none',
    task_type='CAUSAL_LM',
    modules_to_save=['layers.0.self_attn.q_proj'],
    use_dora=True
)

    结果的结构都一致,我们主要看adapter_model.safetensors

    layers.0.self_attn.q_proj不用看,我们使用modules_to_save将它屏蔽了使用全参的方式做微调。

    发现除了lora_A和lora_B还出现了lora_magnitude_vector,也就是幅度m,其中方向被替换为了lora层,实际上dora在lora的初始化方面做了修改,但流程和结构上还是沿用的lora,所以peft在架构上将dora归为到了lora包中,并仅仅使用了use_dora参数简单控制是否使用dora进行微调的原因。

    6.权重融合

from peft import AutoPeftModelForCausalLM
import torch


lora_model_dir = "output"

model = AutoPeftModelForCausalLM.from_pretrained(lora_model_dir, device_map='auto', torch_dtype=torch.bfloat16)

model = model.merge_and_unload()
out_put_dir = 'output_merge'
model.save_pretrained(out_put_dir)

    看一下权重融合的线性层:

    这里融合使用的lora参数里没有设置modules_to_save,可以看到指定的q_proj和v_proj被进行微调,其他层都保持了原始参数没有产生变化。

  • 11
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实战操作中,PEFT库可以用来微调BERT模型,以进行文本情感分类任务。首先,我们需要准备一个包含大量文本和标签的数据集,用于训练和验证BERT模型。然后,我们需要利用PEFT库中提供的工具和接口,将数据集转换成BERT模型可接受的格式,并进行数据预处理,如分词和填充等操作。 接着,我们可以利用PEFT库中提供的预训练模型,加载BERT模型的参数和网络结构,并在数据集上进行微调微调的过程中,我们可以通过调整学习率、批大小和训练轮数等超参数,来优化模型的性能。在每个训练轮数结束后,我们可以利用PEFT库中的评估工具对模型进行评估,以了解模型在验证集上的性能表现。 最后,当模型在验证集上的性能达到满意的水平后,我们可以使用PEFT库提供的保存模型工具,将微调后的BERT模型保存下来,以备在实际应用中使用。通过PEFT库的实战操作,我们可以有效地利用BERT模型进行文本情感分类任务,提高模型的准确性和泛化能力,从而更好地满足实际应用的需求。 PEFT库的实战操作不仅帮助我们更好地理解和使用BERT模型,也为我们提供了一套完整的工具和流程,使得模型训练和应用变得更加简单和高效。 PEFT实战(一): lora微调BERT(文本情感分类) 的操作流程清晰,易于上手,为我们在文本情感分类任务中的应用提供了有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值