首页 > 其他分享 >PEFT原理及代码

PEFT原理及代码

时间:2024-11-06 22:51:48浏览次数:6  
标签:PEFT 模型 代码 num 原理 model peft LoRA 适配器

一.soft-prompt

训练大型预训练语言模型非常耗时且计算密集。随着模型规模的不断扩大,研究者们越来越关注更有效的训练方法,如提示(Prompt)。提示通过在输入中添加描述任务的文本提示,甚至通过提供任务示例,来为特定下游任务准备冻结的预训练模型。通过使用提示,您可以避免为每个下游任务训练一个单独的模型,而是通过同一个冻结的预训练模型来完成多个任务。这比为每个任务训练一个单独的模型要高效得多,因为您只需要训练和存储一个较小的提示参数集,而不是训练整个模型的参数。

提示方法可以分为两类:

  • 硬提示:手动创建的带有离散输入标记的文本提示。缺点是创建一个好的提示需要付出很多努力。
  • 软提示:与可以针对数据集优化的输入嵌入连接的可学习张量。缺点是软提示不是人类可读的,因为这些“虚拟标记”没有与真实单词的嵌入对应。

本概念指南简要概述了 PEFT(Prompt Engineering and Fine-Tuning)中包含的软提示方法:提示调优、前缀调优、P调优和多任务提示调优。

# pip install -q peft transformers datasets

from datasets import load_dataset

ds = load_dataset("ought/raft", "twitter_complaints")

classes = [k.replace("_", " ") for k in ds["train"].features["Label"].names]
ds = ds.map(
    lambda x: {"text_label": [classes[label] for label in x["Label"]]},
    batched=True,
    num_proc=1,
)
# ds["train"][0]
# {"Tweet text": "@HMRCcustomers No this is my first job", "ID": 0, "Label": 2, "text_label": "no complaint"}

数据集: 

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bigscience/bloomz-560m")
if tokenizer.pad_token_id is None:
    tokenizer.pad_token_id = tokenizer.eos_token_id
target_max_length = max([len(tokenizer(class_label)["input_ids"]) for class_label in classes])
print(target_max_length)

import torch

max_length = 64

def preprocess_function(examples, text_column="Tweet text", label_column="text_label"):
    batch_size = len(examples[text_column])
    inputs = [f"{text_column} : {x} Label : " for x in examples[text_column]]
    targets = [str(x) for x in examples[label_column]]
    model_inputs = tokenizer(inputs)
    labels = tokenizer(targets)
    classes = [k.replace("_", " ") for k in ds["train"].features["Label"].names]
    for i in range(batch_size):
        sample_input_ids = model_inputs["input_ids"][i]
        label_input_ids = labels["input_ids"][i]
        model_inputs["input_ids"][i] = [tokenizer.pad_token_id] * (
            max_length - len(sample_input_ids)
        ) + sample_input_ids
        model_inputs["attention_mask"][i] = [0] * (max_length - len(sample_input_ids)) + model_inputs[
            "attention_mask"
        ][i]
        labels["input_ids"][i] = [-100] * (max_length - len(label_input_ids)) + label_input_ids
        model_inputs["input_ids"][i] = torch.tensor(model_inputs["input_ids"][i][:max_length])
        model_inputs["attention_mask"][i] = torch.tensor(model_inputs["attention_mask"][i][:max_length])
        labels["input_ids"][i] = torch.tensor(labels["input_ids"][i][:max_length])
    model_inputs["labels"] = labels["input_ids"]
    return model_inputs

processed_ds = ds.map(
    preprocess_function,
    batched=True,
    num_proc=1,
    remove_columns=ds["train"].column_names,
    load_from_cache_file=False,
    desc="Running tokenizer on dataset",
)
from torch.utils.data import DataLoader
from transformers import default_data_collator

train_ds = processed_ds["train"]
eval_ds = processed_ds["test"]

batch_size = 16

train_dataloader = DataLoader(train_ds, shuffle=True, collate_fn=default_data_collator, batch_size=batch_size, pin_memory=True)
eval_dataloader = DataLoader(eval_ds, collate_fn=default_data_collator, batch_size=batch_size, pin_memory=True)

from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained("bigscience/bloomz-560m")

1.p-tune 

P调优(P-Tuning)是一种为自然语言理解(NLU)任务和所有语言模型设计的方法,它是软提示方法的另一种变体。P调优通过添加一个可训练的嵌入张量来优化提示,以找到更好的提示,并使用提示编码器(例如双向长短期记忆网络 LSTM)来优化提示参数。与前缀调优不同,P调优的特点是:

  • 提示令牌可以插入到输入序列的任何位置,而不仅仅是输入的开头。
  • 提示令牌只添加到输入中,而不是添加到模型的每一层。
  • 引入锚令牌(anchor tokens)可以改善性能,因为锚令牌能够表示输入序列中某一组件的特征。

研究结果表明,P调优比手动设计提示更高效,并且使得像 GPT 这样的模型能够在自然语言理解任务上与 BERT 等模型相竞争。

 P-tuning 添加了一个可训练的嵌入张量,其中提示标记可以添加到输入序列中的任何位置。创建一个 PromptEncoderConfig,其中包含任务类型、要添加和学习的虚拟令牌数量以及用于学习提示参数的编码器的隐藏大小

from peft import PromptEncoderConfig, get_peft_model

peft_config = PromptEncoderConfig(task_type="CAUSAL_LM", num_virtual_tokens=20, encoder_hidden_size=128)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
"trainable params: 300,288 || all params: 559,514,880 || trainable%: 0.05366935013417338"

PromptEncoderConfig

class peft.PromptEncoderConfig:
    peft_type: Union = None
    auto_mapping: Optional = None
    base_model_name_or_path: Optional = None
    revision: Optional = None
    task_type: Union = None
    inference_mode: bool = False
    num_virtual_tokens: int = None
    token_dim: int = None
    num_transformer_submodules: Optional = None
    num_attention_heads: Optional = None
    num_layers: Optional = None
    encoder_reparameterization_type: Union = <PromptEncoderReparameterizationType.MLP: 'MLP'>
    encoder_hidden_size: int = None
    encoder_num_layers: int = 2
    encoder_dropout: float = 0.0

参数说明

  • encoder_reparameterization_type:指定要使用的重新参数化类型。
  • encoder_hidden_size:提示编码器的隐藏层大小。
  • encoder_num_layers:提示编码器的层数。
  • encoder_dropout:提示编码器的丢弃概率。
  • 这是用于存储 PromptEncoder 配置

PromptEncoder

class peft.PromptEncoder:
    config: PromptEncoderConfig

参数说明

  • config:提示编码器的配置。
  • 提示编码器网络用于生成 P-tuning 的虚拟令牌嵌入。

示例代码

from peft import PromptEncoder, PromptEncoderConfig

config = PromptEncoderConfig(
    peft_type="P_TUNING",
    task_type="SEQ_2_SEQ_LM",
    num_virtual_tokens=20,
    token_dim=768,
    num_transformer_submodules=1,
    num_attention_heads=12,
    num_layers=12,
    encoder_reparameterization_type="MLP",
    encoder_hidden_size=768,
)

prompt_encoder = PromptEncoder(config)

属性

  • embedding:提示编码器的嵌入层。
  • mlp_head:如果 inference_mode=False,则为提示编码器的 MLP 头。
  • lstm_head:如果 inference_mode=False 且 encoder_reparameterization_type="LSTM",则为提示编码器的 LSTM 头。
  • token_dim:基础变换模型的隐藏嵌入维度。
  • input_size:提示编码器的输入大小。
  • output_size:提示编码器的输出大小。
  • hidden_size:提示编码器的隐藏层大小。
  • total_virtual_tokens:提示编码器的虚拟令牌总数。
  • encoder_type:提示编码器的编码器类型。

输入形状:(batch_size, total_virtual_tokens)

输出形状:(batch_size, total_virtual_tokens, token_dim)

2.prefix tune

前缀调优(Prefix Tuning)是为 GPT 模型的自然语言生成(NLG)任务设计的。它与提示调优非常相似,都是在输入前添加任务特定的向量序列,这些向量可以被训练和更新,同时保持其余的预训练模型参数冻结。

前缀调优与提示调优的主要区别在于,前缀参数被插入到模型的每一层,而提示调优只是将提示参数添加到模型输入的嵌入层。前缀参数还通过一个独立的前馈神经网络(FFN)进行优化,而不是直接在软提示上进行训练,因为直接训练软提示会导致不稳定性并影响性能。FFN 在更新软提示后会被丢弃。

研究表明,前缀调优尽管比完全微调模型的参数少了1000倍,但其性能与完全微调一个模型相当,甚至在低数据设置下表现更好。

from peft import PrefixTuningConfig, get_peft_model

peft_config = PrefixTuningConfig(task_type="CAUSAL_LM", num_virtual_tokens=20)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
"trainable params: 983,040 || all params: 560,197,632 || trainable%: 0.1754809274167014"

PrefixTuningConfig

class peft.PrefixTuningConfig

此类用于存储 PrefixEncoder 的配置。它的参数包括:

  • peft_type: (可选) 提示调优类型。
  • auto_mapping: (可选) 自动映射的设置。
  • base_model_name_or_path: (可选) 基础模型的名称或路径。
  • revision: (可选) 模型的修订版本。
  • task_type: (可选) 任务类型,例如 SEQ_2_SEQ_LM(序列到序列的语言建模任务)。
  • inference_mode: (默认: False) 是否启用推理模式。
  • num_virtual_tokens: (可选) 虚拟标记的数量。
  • token_dim: (可选) 每个标记的维度。
  • num_transformer_submodules: (可选) 转换器子模块的数量。
  • num_attention_heads: (可选) 注意力头的数量。
  • num_layers: (可选) Transformer 层数。
  • encoder_hidden_size: (可选) 提示编码器的隐藏层大小。
  • prefix_projection: (默认: False) 是否对前缀嵌入进行投影。

PrefixEncoder

class peft.PrefixEncoder

这是一个用于编码前缀的 PyTorch 模型。它通过 PrefixTuningConfig 配置来构建前缀编码器。主要的输入参数是该配置对象。

参数:

  • config (PrefixTuningConfig): 前缀编码器的配置对象。

代码示例:

from peft import PrefixEncoder, PrefixTuningConfig

config = PrefixTuningConfig(
    peft_type="PREFIX_TUNING",
    task_type="SEQ_2_SEQ_LM",
    num_virtual_tokens=20,
    token_dim=768,
    num_transformer_submodules=1,
    num_attention_heads=12,
    num_layers=12,
    encoder_hidden_size=768,
)
prefix_encoder = PrefixEncoder(config)

属性

  • embedding (torch.nn.Embedding): 前缀编码器的嵌入层。
  • transform (torch.nn.Sequential): 如果 prefix_projection 为 True,则用于转换前缀嵌入的两层 MLP(多层感知机)。
  • prefix_projection (bool): 是否对前缀嵌入进行投影。

输入输出形状:

  • 输入形状(batch_size, num_virtual_tokens)
  • 输出形状(batch_size, num_virtual_tokens, 2 * layers * hidden)

3.promp tune

提示调优(Prompt Tuning)是指仅训练和存储一组明显较小的、特定于任务的提示参数。例如,提示调优是在 T5 模型上为文本分类任务开发的。所有下游任务都被转换为文本生成任务。例如,序列分类通常会为文本序列分配一个类标签。通过将序列分类任务转换为文本生成任务,生成的文本会包含表示类标签的标记,提示将作为一系列标记添加到输入中。

通常情况下,模型的参数是固定的,这意味着提示标记也由模型参数固定。提示调优的核心思想是提示令牌(prompt token)有自己独立的参数,这些参数是独立更新的。也就是说,您可以保持预训练模型的参数冻结,仅更新提示令牌嵌入的梯度。与训练整个模型的传统方法相比,提示调优能够有效扩展,并且随着模型规模的增加,性能也会提升。

from peft import PromptTuningConfig, PromptTuningInit, get_peft_model

prompt_tuning_init_text = "Classify if the tweet is a complaint or no complaint.\n"
peft_config = PromptTuningConfig(
    task_type="CAUSAL_LM",
    prompt_tuning_init=PromptTuningInit.TEXT,
    num_virtual_tokens=len(tokenizer(prompt_tuning_init_text)["input_ids"]),
    prompt_tuning_init_text=prompt_tuning_init_text,
    tokenizer_name_or_path="bigscience/bloomz-560m",
)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
"trainable params: 8,192 || all params: 559,222,784 || trainable%: 0.0014648902430985358"

PromptTuningConfig

class peft.PromptTuningConfig: (
peft_type: Union = None, 
auto_mapping: Optional = None, 
base_model_name_or_path: Optional = None, 
revision: Optional = None, 
task_type: Union = None, 
inference_mode: bool = False, 
num_virtual_tokens: int = None, 
token_dim: int = None, 
num_transformer_submodules: Optional = None, 
num_attention_heads: Optional = None, 
num_layers: Optional = None,
 prompt_tuning_init: Union = <PromptTuningInit.RANDOM: 'RANDOM'>, 
prompt_tuning_init_text: Optional = None, 
tokenizer_name_or_path: Optional = None, 
tokenizer_kwargs: Optional = None)

参数说明:

  • prompt_tuning_init(Union[PromptTuningInit, str]):提示嵌入的初始化方式。

  • prompt_tuning_init_text(str, optional):用于初始化提示嵌入的文本,仅在prompt_tuning_initTEXT时使用。

  • tokenizer_name_or_path(str, optional):标记器的名称或路径,仅在prompt_tuning_initTEXT时使用。

  • tokenizer_kwargs(dict, optional):传递给AutoTokenizer.from_pretrained的关键字参数,仅在prompt_tuning_initTEXT时使用。

这是一个用于存储提示嵌入配置的配置类。

PromptEmbedding

class peft.PromptEmbedding: (config, word_embeddings)

参数说明:

  • config(PromptTuningConfig):提示嵌入的配置。
  • word_embeddings(torch.nn.Module):基础变换器模型的词嵌入。

该模型用于将虚拟标记编码为提示嵌入。

属性:

  • embedding(torch.nn.Embedding):提示嵌入的嵌入层。

示例 

from peft import PromptEmbedding, PromptTuningConfig

config = PromptTuningConfig(
    peft_type="PROMPT_TUNING",
    task_type="SEQ_2_SEQ_LM",
    num_virtual_tokens=20,
    token_dim=768,
    num_transformer_submodules=1,
    num_attention_heads=12,
    num_layers=12,
    prompt_tuning_init="TEXT",
    prompt_tuning_init_text="Predict if sentiment of this review is positive, negative or neutral",
    tokenizer_name_or_path="t5-base",
)

# t5_model.shared 是基础模型的词嵌入
prompt_embedding = PromptEmbedding(config, t5_model.shared)

输入形状(batch_size, total_virtual_tokens)

输出形状(batch_size, total_virtual_tokens, token_dim)

剩余代码都一致

from transformers import get_linear_schedule_with_warmup

lr = 3e-2
num_epochs = 50

optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
lr_scheduler = get_linear_schedule_with_warmup(
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=(len(train_dataloader) * num_epochs),
)

from tqdm import tqdm

device = "cuda"
model = model.to(device)

for epoch in range(num_epochs):
    model.train()
    total_loss = 0
    for step, batch in enumerate(tqdm(train_dataloader)):
        batch = {k: v.to(device) for k, v in batch.items()}
        outputs = model(**batch)
        loss = outputs.loss
        total_loss += loss.detach().float()
        loss.backward()
        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()

    model.eval()
    eval_loss = 0
    eval_preds = []
    for step, batch in enumerate(tqdm(eval_dataloader)):
        batch = {k: v.to(device) for k, v in batch.items()}
        with torch.no_grad():
            outputs = model(**batch)
        loss = outputs.loss
        eval_loss += loss.detach().float()
        eval_preds.extend(
            tokenizer.batch_decode(torch.argmax(outputs.logits, -1).detach().cpu().numpy(), skip_special_tokens=True)
        )

    eval_epoch_loss = eval_loss / len(eval_dataloader)
    eval_ppl = torch.exp(eval_epoch_loss)
    train_epoch_loss = total_loss / len(train_dataloader)
    train_ppl = torch.exp(train_epoch_loss)
    print(f"{epoch=}: {train_ppl=} {train_epoch_loss=} {eval_ppl=} {eval_epoch_loss=}")
"""推理"""
from peft import AutoPeftModelForCausalLM

model = AutoPeftModelForCausalLM.from_pretrained("peft_model_id").to("cuda")
tokenizer = AutoTokenizer.from_pretrained("bigscience/bloomz-560m")

i = 15
inputs = tokenizer(f'{text_column} : {ds["test"][i]["Tweet text"]} Label : ', return_tensors="pt")
print(ds["test"][i]["Tweet text"])
"@NYTsupport i have complained a dozen times &amp; yet my papers are still thrown FAR from my door. Why is this so hard to resolve?"

with torch.no_grad():
    inputs = {k: v.to(device) for k, v in inputs.items()}
    outputs = model.generate(input_ids=inputs["input_ids"], max_new_tokens=10)
    print(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True))
"['Tweet text : @NYTsupport i have complained a dozen times &amp; yet my papers are still thrown FAR from my door. Why is this so hard to resolve? Label : complaint']"

二、LORA

有效训练大型模型的一种流行方法是插入(通常在注意力块中)较小的可训练矩阵,这些矩阵是 delta 权重矩阵的低秩分解,可在微调期间学习。预训练模型的原始权重矩阵被冻结,在训练期间只有较小的矩阵会更新。这减少了可训练参数的数量,减少了内存使用和训练时间,这对于大型模型来说可能非常昂贵。

有几种不同的方法可以将权重矩阵表示为低秩分解,但低秩适应 (LoRA) 是最常见的方法。PEFT 库支持其他几种 LoRA 变体,例如低秩 Hadamard 积 (LoHa)、低秩 Kronecker 积 (LoKr) 和自适应低秩适应 (AdaLoRA)。您可以在 Adapters 指南中了解有关这些方法在概念上的工作原理的更多信息。如果您有兴趣将这些方法应用于其他任务和用例,如语义分割、标记分类,请查看我们的笔记本集合

# pip install -q peft transformers datasets
from datasets import load_dataset

ds = load_dataset("food101")

labels = ds["train"].features["label"].names
label2id, id2label = dict(), dict()
for i, label in enumerate(labels):
    label2id[label] = i
    id2label[i] = label

# id2label[2]
# "baklava"
from transformers import AutoImageProcessor

image_processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224-in21k")
from torchvision.transforms import (
    CenterCrop,
    Compose,
    Normalize,
    RandomHorizontalFlip,
    RandomResizedCrop,
    Resize,
    ToTensor,
)

normalize = Normalize(mean=image_processor.image_mean, std=image_processor.image_std)
train_transforms = Compose(
    [
        RandomResizedCrop(image_processor.size["height"]),
        RandomHorizontalFlip(),
        ToTensor(),
        normalize,
    ]
)

val_transforms = Compose(
    [
        Resize(image_processor.size["height"]),
        CenterCrop(image_processor.size["height"]),
        ToTensor(),
        normalize,
    ]
)

def preprocess_train(example_batch):
    example_batch["pixel_values"] = [train_transforms(image.convert("RGB")) for image in example_batch["image"]]
    return example_batch

def preprocess_val(example_batch):
    example_batch["pixel_values"] = [val_transforms(image.convert("RGB")) for image in example_batch["image"]]
    return example_batch

train_ds = ds["train"]
val_ds = ds["validation"]

train_ds.set_transform(preprocess_train)
val_ds.set_transform(preprocess_val)

train_ds = ds["train"]
val_ds = ds["validation"]

train_ds.set_transform(preprocess_train)
val_ds.set_transform(preprocess_val)
from transformers import AutoModelForImageClassification, TrainingArguments, Trainer

model = AutoModelForImageClassification.from_pretrained(
    "google/vit-base-patch16-224-in21k",
    label2id=label2id,
    id2label=id2label,
    ignore_mismatched_sizes=True,
)

 1.lora

LoRA 将权重更新矩阵分解为两个较小的矩阵。这些低秩矩阵的大小由其 或 决定。排名越高意味着模型需要训练的参数越多,但这也意味着模型具有更多的学习能力。您还需要指定 which 来确定较小矩阵的插入位置。在本指南中,您将定位注意力块的查询矩阵。要设置的其他重要参数是 (缩放因子)、(是否应训练或仅应训练 LoRA 偏差参数)和 (要训练和保存的 LoRA 层以外的模块)。所有这些参数 - 以及更多参数 - 都可以在 LoraConfig 中找到。

LoRA是PEFT方法中最流行的一种,并且是入门PEFT的好起点。最初,它是为大型语言模型开发的,但由于其高效性和有效性,它也成为扩散模型训练中非常流行的方法。

如前所述,LoRA是一种通过低秩分解加速大型模型微调,同时消耗更少内存的技术。

LoRA通过将权重更新∆W表示为两个较小的矩阵(称为更新矩阵),通过低秩分解进行训练。这些新的矩阵可以在保持整体参数数量较低的同时,适应新的数据。原始的权重矩阵保持冻结,不会进一步更新。为了产生最终结果,原始权重和适配器的权重将被结合在一起。你还可以将适配器的权重与基础模型合并,以消除推理延迟。

该方法的优点包括:

  • LoRA通过大幅减少可训练参数的数量,使微调更加高效。
  • 原始的预训练权重保持冻结,这意味着你可以为不同的下游任务构建多个轻量级且可移植的LoRA模型。
  • LoRA与其他参数高效方法是正交的,能够与许多方法结合使用。
  • 使用LoRA微调的模型性能与完全微调的模型相当。
  • 原则上,LoRA可以应用于神经网络中的任何权重矩阵子集,以减少可训练参数的数量。然而,为了简化和进一步提高参数效率,LoRA通常仅应用于变换器模型中的注意力块。LoRA模型中的可训练参数数量主要取决于更新矩阵的秩r和原始权重矩阵的形状。
from peft import LoraConfig, get_peft_model

config = LoraConfig(
    r=16,
    lora_alpha=16,
    target_modules=["query", "value"],
    lora_dropout=0.1,
    bias="none",
    modules_to_save=["classifier"],
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
"trainable params: 667,493 || all params: 86,543,818 || trainable%: 0.7712775047664294"

LoraConfig 类

peft.LoraConfig 是用于配置 LoRA(低秩适配器)模型的类。它包含多个用于调节 LoRA 适配器行为的参数,具体如下:

参数:
  • r (int): LoRA 注意力维度(即“秩”)。
  • target_modules (Optional[Union[List[str], str]]): 适配器将应用于的模块名称。如果指定了该参数,则只会替换指定名称的模块。支持正则匹配或字符串结尾匹配。如果设置为“all-linear”,则所有线性层/Conv1D模块会被选择,排除输出层。如果未指定,模块会根据模型架构自动选择。如果架构未知,则会报错,这时需要手动指定目标模块。
  • exclude_modules (Optional[Union[List[str], str]]): 不应用适配器的模块名称。支持正则匹配或字符串结尾匹配。
  • lora_alpha (int): LoRA 缩放因子的 alpha 参数。
  • lora_dropout (float): LoRA 层的 dropout 概率。
  • fan_in_fan_out (bool): 如果替换的层使用类似(fan_in, fan_out)方式存储权重(如 GPT-2 的 Conv1D 层),则应设置为 True。
  • bias (str): LoRA 的偏置类型,可以是 ‘none’, ‘all’ 或 ‘lora_only’。如果设置为 ‘all’ 或 ‘lora_only’,相应的偏置将在训练中被更新。
  • use_rslora (bool): 如果设置为 True,使用 Rank-Stabilized LoRA,调整适配器的缩放因子为 lora_alpha / math.sqrt(r),这种方式通常表现更好。
  • modules_to_save (List[str]): 除了适配器层之外的其他模块,在最终检查点中保存为可训练。
  • init_lora_weights (bool | Literal["gaussian", "olora", "pissa", "pissa_niter_[number of iters]", "loftq"]): 初始化适配器层权重的方式。默认值为 True,使用 Microsoft 参考实现的默认初始化。如果设置为 'gaussian',则使用高斯初始化。可以选择 'loftq'、'olora' 或 'pissa' 初始化方式。'pissa' 初始化比 LoRA 更快收敛,且在量化误差方面表现更好。
  • layers_to_transform (Union[List[int], int]): 要转换的层索引。如果传递的是整数列表,则只会转换指定索引的层;如果传递的是单一整数,则会转换该层。
  • layers_pattern (Optional[Union[List[str], str]]): 层的名称模式,仅在 layers_to_transform 不为 None 时使用。通常是指模型中的 nn.ModuleList,如 'layers' 或 'h'。
  • rank_pattern (dict): 将层名称或正则表达式映射到不同的秩值,以覆盖默认的 r 值。
  • alpha_pattern (dict): 将层名称或正则表达式映射到不同的 alpha 值,以覆盖默认的 lora_alpha 值。
  • megatron_config (Optional[dict]): Megatron 的 TransformerConfig 参数,用于创建 LoRA 的并行线性层。当对 Megatron 的 ColumnParallelLinear 和 RowParallelLinear 层应用 LoRA 时需要此参数。
  • megatron_core (Optional[str]): 用于 Megatron 的核心模块,默认值为 "megatron.core"。
  • loftq_config (Optional[LoftQConfig]): LoftQ 的配置。如果不为 None,则使用 LoftQ 对骨干网络权重进行量化并初始化 LoRA 层。
  • use_dora (bool): 启用“权重分解低秩适配”(DoRA)。此方法将权重更新分为大小和方向两个部分。方向部分由 LoRA 处理,大小部分由可学习的参数处理。DoRA 在低秩情况下提高了 LoRA 的性能,但引入了较大的开销,因此建议合并权重以进行推理。
  • layer_replication (List[Tuple[int, int]]): 通过根据指定的范围堆叠原始模型层来创建新的层堆栈。这允许在不重复基础模型权重的情况下扩展(或缩小)模型。
  • runtime_config (LoraRuntimeConfig): 运行时配置,不会保存或恢复。
方法:
  • to_dict: 将 LoRA 配置转化为字典,移除运行时配置。

LoraModel 类

peft.LoraModel 是基于预训练的 Transformers 模型创建 LoRA 模型的类。

参数:
  • model (torch.nn.Module): 要进行适配的模型。
  • config (LoraConfig): LoRA 模型的配置。
  • adapter_name (str): 适配器的名称,默认为 "default"。
  • low_cpu_mem_usage (bool, optional): 是否在元设备上创建空的适配器权重。启用此选项可以加速加载过程,减少 CPU 内存使用。
功能:
  • 创建 LoRA 适配器模型。
  • 该类将基于 LoRA(低秩适配器)的预训练 Transformer 模型与给定配置结合,从而创建一个新的适配器模型。LoRA 方法被详细描述在 LoRA 论文
示例:
from transformers import AutoModelForSeq2SeqLM
from peft import LoraModel, LoraConfig

config = LoraConfig(
    task_type="SEQ_2_SEQ_LM",
    r=8,
    lora_alpha=32,
    target_modules=["q", "v"],
    lora_dropout=0.01,
)

model = AutoModelForSeq2SeqLM.from_pretrained("t5-base")
lora_model = LoraModel(model, config, "default")
import torch
import transformers
from peft import LoraConfig, PeftModel, get_peft_model, prepare_model_for_kbit_training

rank = ...
target_modules = ["q_proj", "k_proj", "v_proj", "out_proj", "fc_in", "fc_out", "wte"]
config = LoraConfig(
    r=4, lora_alpha=16, target_modules=target_modules, lora_dropout=0.1, bias="none", task_type="CAUSAL_LM"
)
quantization_config = transformers.BitsAndBytesConfig(load_in_8bit=True)

tokenizer = transformers.AutoTokenizer.from_pretrained(
    "kakaobrain/kogpt",
    revision="KoGPT6B-ryan1.5b-float16",  # 或使用 float32 版本:revision="KoGPT6B-ryan1.5b"
    bos_token="[BOS]",
    eos_token="[EOS]",
    unk_token="[UNK]",
    pad_token="[PAD]",
    mask_token="[MASK]",
)
model = transformers.GPTJForCausalLM.from_pretrained(
    "kakaobrain/kogpt",
    revision="KoGPT6B-ryan1.5b-float16",  # 或使用 float32 版本:revision="KoGPT6B-ryan1.5b"
    pad_token_id=tokenizer.eos_token_id,
    use_cache=False,
    device_map={"": rank},
    torch_dtype=torch.float16,
    quantization_config=quantization_config,
)
model = prepare_model_for_kbit_training(model)
lora_model = get_peft_model(model, config)

add_weighted_adapter 方法

此方法允许将多个适配器的权重进行合并,并创建一个新的适配器。

参数:
  • adapters (list): 要合并的适配器名称列表。
  • weights (list): 每个适配器的权重列表。
  • adapter_name (str): 新适配器的名称。
  • combination_type (str): 合并类型。可以是以下之一:[svdlinearcattiesties_svddare_tiesdare_lineardare_ties_svddare_linear_svdmagnitude_prunemagnitude_prune_svd]。当使用 cat 时,合并后的适配器的秩将是所有适配器秩的总和,可能会导致内存溢出错误(OOM)。
  • svd_rank (int, optional): SVD 合并时输出适配器的秩。如果未提供,则使用合并适配器的最大秩。
  • svd_clamp (float, optional): SVD 分解输出的量化阈值。如果未提供,则不进行阈值限制。
  • svd_full_matrices (bool, optional): 控制是否计算完整的 SVD 矩阵(默认为 True)。如果设置为 False,将计算简化的矩阵。
  • svd_driver (str, optional): 用于 SVD 的 cuSOLVER 方法名称。仅在合并时使用 CUDA 时有效。可以是 [None, gesvd, gesvdj, gesvda]。
  • density (float, optional): 剪枝密度值,介于 0 和 1 之间。0 表示完全剪枝,1 表示不剪枝。通常与 tiesties_svddare_ties 等方法一起使用。
  • majority_sign_method (str): 用于确定符号值的方法,应为 [totalfrequency] 之一。通常与 tiesties_svddare_ties 等方法一起使用。
功能:

此方法通过合并给定的适配器和权重,创建一个新的适配器。当使用 cat 合并类型时,合并后的适配器的秩会是所有适配器秩的总和,可能导致内存溢出问题。


delete_adapter 方法

此方法用于删除现有的适配器。

参数:
  • adapter_name (str): 要删除的适配器的名称。

disable_adapter_layers 方法

此方法禁用所有适配器层。

禁用适配器后,模型输出将与基础模型的输出相同,不包含任何 LoRA 适配器的影响。


enable_adapter_layers 方法

此方法启用所有适配器层。

如果之前禁用了所有适配器层,可以调用此方法重新启用它们。


merge_and_unload 方法

此方法将 LoRA 层合并到基础模型中。如果希望使用基础模型而不再使用 LoRA 适配器,则需要调用此方法。

参数:
  • progressbar (bool, optional): 是否显示进度条,表示卸载和合并过程。默认为 False
  • safe_merge (bool, optional): 是否启用安全合并检查,确保适配器权重中没有 NaN。默认为 False
  • adapter_names (List[str], optional): 要合并的适配器名称列表。如果未提供,默认合并所有活动适配器。
功能:

此方法合并 LoRA 层并卸载它们,以便最终得到一个基础模型。该模型不再包含 LoRA 模块。


set_adapter 方法

此方法用于设置活动适配器。

参数:
  • adapter_name (str or list[str]): 要激活的适配器名称,可以是单个适配器或适配器名称的列表。

此外,该方法还会将指定的适配器设置为可训练(即 requires_grad=True)。如果不希望适配器层可训练,可以使用以下代码禁用其梯度计算:

for name, param in model_peft.named_parameters():
    if ...:  # 根据名称进行检查(例如,检查是否包含 'lora')
        param.requires_grad = False

subtract_mutated_init 方法

此方法通过比较输出状态字典中的 [PiSSA | OLoRA] 参数和适配器中初始值的差异,计算 [PiSSA | OLoRA] 的更新,从而将 [PiSSA | OLoRA] 转换为 LoRA。

参数:
  • output_state_dict (dict[str, torch.Tensor]): 输出状态字典。
  • adapter_name (str): 用于比较的适配器名称。
  • kwargs: 其他可选参数。

unload 方法

此方法通过删除所有 LoRA 模块而不合并它们,返回原始的基础模型。

peft.replace_lora_weights_loftq 方法

参数:
  • peft_model (PeftModel) — 需要替换权重的模型。必须是一个带有 LoRA 层的量化 PEFT 模型。
  • model_path (Optional[str]) — 模型 safetensors 文件的路径。如果是 Hugging Face 模型,则从模型配置中推断路径;否则,必须提供该路径。
  • adapter_name (str) — 要替换权重的适配器名称。默认适配器名称为“default”。
  • callback (Optional[Callable[[PeftModel, str], bool]]) — 每个模块替换后调用的回调函数。回调函数应该接收模型和当前模块名称作为输入,并返回一个布尔值,指示是否保留替换。如果回调返回 False,则替换将回滚。这个功能非常有用,用于确认 LoftQ 初始化是否有效地减少了模型的量化误差。例如,回调可以生成给定输入的 logits,并与原始非量化模型在相同输入下生成的 logits 进行比较,只有在有改进时才返回 True。由于这是一个贪婪优化过程,可能需要多次调用此函数,以逐步改进结果。
功能:

该方法使用 LoftQ 技术替换通过 bitsandbytes 量化的模型的 LoRA 权重。

替换过程是即时进行的,通过加载本地存储的非量化模型权重(以 safetensors 格式),并初始化 LoRA 权重,使得原始权重和量化权重之间的量化误差最小化。

由于 pickle 不支持延迟加载,因此普通的 PyTorch 检查点文件无法使用。

根据模型的大小,调用此方法可能需要一些时间来完成。
 

lora剩余代码

from transformers import TrainingArguments, Trainer

account = "stevhliu"
peft_model_id = f"{account}/google/vit-base-patch16-224-in21k-lora"
batch_size = 128

args = TrainingArguments(
    peft_model_id,
    remove_unused_columns=False,
    eval_strategy="epoch",
    save_strategy="epoch",
    learning_rate=5e-3,
    per_device_train_batch_size=batch_size,
    gradient_accumulation_steps=4,
    per_device_eval_batch_size=batch_size,
    fp16=True,
    num_train_epochs=5,
    logging_steps=10,
    load_best_model_at_end=True,
    label_names=["labels"],
)
trainer = Trainer(
    model,
    args,
    train_dataset=train_ds,
    eval_dataset=val_ds,
    tokenizer=image_processor,
    data_collator=collate_fn,
)
trainer.train()

# 推理
from peft import PeftConfig, PeftModel
from transformers import AutoImageProcessor
from PIL import Image
import requests

config = PeftConfig.from_pretrained("stevhliu/vit-base-patch16-224-in21k-lora")
model = AutoModelForImageClassification.from_pretrained(
    config.base_model_name_or_path,
    label2id=label2id,
    id2label=id2label,
    ignore_mismatched_sizes=True,
)
model = PeftModel.from_pretrained(model, "stevhliu/vit-base-patch16-224-in21k-lora")

url = "https://huggingface.co/datasets/sayakpaul/sample-datasets/resolve/main/beignets.jpeg"
image = Image.open(requests.get(url, stream=True).raw)
# image
encoding = image_processor(image.convert("RGB"), return_tensors="pt")

with torch.no_grad():
    outputs = model(**encoding)
    logits = outputs.logits

predicted_class_idx = logits.argmax(-1).item()
print("Predicted class:", model.config.id2label[predicted_class_idx])
"Predicted class: beignets"

标签:PEFT,模型,代码,num,原理,model,peft,LoRA,适配器
From: https://blog.csdn.net/weixin_62891098/article/details/143572496

相关文章

  • 代码随想录第七天|哈希表part02--454.四数相加II、383. 赎金信、15. 三数之和、18. 四
    资源引用:leetcode题目:454.四数相加Ⅱ(454.四数相加II-力扣(LeetCode))383.赎金信(383.赎金信-力扣(LeetCode))15.三数之和(15.三数之和-力扣(LeetCode))18.四数之和(18.四数之和-力扣(LeetCode))例行碎碎念:今天也追赶上了一些进度,虽然生病感冒,但今天很好的坚持了从早到晚......
  • G1垃圾回收器原理
    G1垃圾回收器原理G1垃圾回收有两种方式:1、年轻代回收(YoungGC)2、混合回收(MixedGC)年轻代回收年轻代回收只扫描年轻代对象(Eden+Survivor),所以从GCRoot到年轻代的对象或者年轻代对象引用了其他年轻代的对象都很容易扫描出来。 这里就存在一个问题,年轻代回收只扫描年......
  • 鸿蒙Next安全之应用加密:保障应用代码安全
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)在开发多语言电商平台方面的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。在数字化浪潮......
  • 李沐《动手学深度学习》softmax回归python代码实现
    一、手动实现softmax回归#手动实现softmax回归#%matplotlibinlineimporttorchfromd2limporttorchasd2limportmatplotlib.pyplotaspltfromIPythonimportdisplay#参数初始化:batch_size=256train_iter,test_iter=d2l.load_data_fashion_mnist(batc......
  • 李沐《动手学深度学习》权重衰退(正则化)python代码实现
    一、L2正则化手动实现#权重衰退手动实现%matplotlibinlineimporttorchfromd2limporttorchasd2lfromtorchimportnn#n_train个训练样本,n_test个测试样本,输入数据维度是200维n_train,n_test,num_inputs,batch_size=20,200,200,5true_w,true_b=to......
  • 李沐《动手学深度学习》多层感知机python代码实现
    一、多层感知机手动实现#多层感知机的手动实现%matplotlibinlineimporttorchfromtorchimportnnfromd2limporttorchasd2lbatch_size=256train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)num_inputs,num_outputs,num_first_hiddens=......
  • 李沐《动手学深度学习》线性回归python代码实现
    一、手动实现线性回归#线性回归的手动实现%matplotlibinlineimporttorchimportrandomfromd2limporttorchasd2l#随机按照参数w和b外加一些噪音来创造训练数据集data和labelsdefsynthetic_data(w,b,num_examples):X=torch.normal(0,1,(num_example......
  • 代码随想录算法训练营第十八天|leetcode530.二叉搜索树的最小绝对差、leetcode501.二
    1leetcode530.二叉搜索树的最小绝对差题目链接:530.二叉搜索树的最小绝对差-力扣(LeetCode)文章链接:代码随想录视频链接:你对二叉搜索树了解的还不够!|LeetCode:98.验证二叉搜索树_哔哩哔哩_bilibili思路:定义一个极大值作为结果,然后在中序遍历过程中进行比较出结果1.1自己的......
  • Accepted极限代码巅峰赛 E Triangle
    题目题解神奇题定义题目要求的“合法序列”为\(a_i\in[1,n],\;a_i+a_{i+1}\lea_{i+2}\)定义长为l的斐波那契序列\(Fib_l\)为序列\(\{fib_1,fib_2,\cdots,fib_l\}\)定义两个数列的和为右对齐然后按位相加(不足补0),如\(\{1,2,3\}+\{3,4\}=\{1,5,7\}\)定义斐波那契序列......
  • JVM指针压缩实现原理
    JVM指针压缩实现原理  概要  Java中的指针压缩(PointerCompression)是一个与内存管理相关的优化技术,主要应用于JVM的对象引用(即指针)的存储方式。指针压缩的目标是减少对象引用占用的内存空间,从而提高内存利用效率,特别是在64位系统上。  一、对象的内存布局  ......