使用HuggingFace PEFT实现大模型高效微调与推理指南
引言
在自然语言处理领域,大型预训练模型如T5、GPT等展现出强大的能力,但这些模型参数量巨大,直接微调需要大量计算资源。PEFT(Parameter-Efficient Fine-Tuning)技术通过仅微调少量参数就能获得接近全参数微调的效果,极大降低了资源需求。本文将详细介绍如何使用PEFT中的LoRA技术对大型序列到序列模型进行高效微调与推理。
环境准备与模型加载
首先需要安装必要的库,包括transformers、peft和datasets等。PEFT提供了PeftModel和PeftConfig等核心类,可以方便地加载和配置基于LoRA的微调模型。
from transformers import AutoModelForSeq2SeqLM
from peft import PeftModel, PeftConfig
import torch
from datasets import load_dataset
from transformers import AutoTokenizer
加载预训练模型与PEFT适配器
对于大型模型,合理分配内存至关重要。我们可以通过max_memory参数控制各设备的内存使用:
peft_model_id = "smangrul/twitter_complaints_bigscience_T0_3B_LORA_SEQ_2_SEQ_LM"
max_memory = {0: "6GIB", 1: "0GIB", 2: "0GIB", 3: "0GIB", 4: "0GIB", "cpu": "30GB"}
config = PeftConfig.from_pretrained(peft_model_id)
model = AutoModelForSeq2SeqLM.from_pretrained(
config.base_model_name_or_path,
device_map="auto",
max_memory=max_memory
)
model = PeftModel.from_pretrained(
model,
peft_model_id,
device_map="auto",
max_memory=max_memory
)
这段代码展示了如何加载一个3B参数的T0模型及其LoRA适配器,并智能地将模型分配到可用设备上。
数据处理与准备
我们使用twitter_complaints数据集,这是一个文本分类任务,需要将推文分类为"complaint"或"no complaint"。
dataset_name = "twitter_complaints"
text_column = "Tweet text"
label_column = "text_label"
batch_size = 8
dataset = load_dataset("ought/raft", dataset_name)
classes = [k.replace("_", " ") for k in dataset["train"].features["Label"].names]
dataset = dataset.map(
lambda x: {"text_label": [classes[label] for label in x["Label"]]},
batched=True,
num_proc=1,
)
文本预处理
序列到序列模型需要将输入文本和标签都转换为模型可理解的token ID序列。我们定义预处理函数:
tokenizer = AutoTokenizer.from_pretrained(config.base_model_name_or_path)
target_max_length = max([len(tokenizer(class_label)["input_ids"]) for class_label in classes])
def preprocess_function(examples):
inputs = examples[text_column]
targets = examples[label_column]
model_inputs = tokenizer(inputs, truncation=True)
labels = tokenizer(
targets, max_length=target_max_length, padding="max_length",
truncation=True, return_tensors="pt"
)
labels = labels["input_ids"]
labels[labels == tokenizer.pad_token_id] = -100
model_inputs["labels"] = labels
return model_inputs
数据加载器创建
使用DataLoader高效加载数据:
processed_datasets = dataset.map(
preprocess_function,
batched=True,
num_proc=1,
remove_columns=dataset["train"].column_names,
load_from_cache_file=True,
)
train_dataloader = DataLoader(
train_dataset, shuffle=True, collate_fn=collate_fn,
batch_size=batch_size, pin_memory=True
)
模型推理与评估
加载微调好的模型后,可以进行推理和评估:
model.eval()
i = 15
inputs = tokenizer(f'{text_column} : {dataset["test"][i]["Tweet text"]} Label : ',
return_tensors="pt")
with torch.no_grad():
outputs = model.generate(input_ids=inputs["input_ids"].to("cuda"), max_new_tokens=10)
print(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True))
批量评估与准确率计算
对整个评估集进行预测并计算准确率:
model.eval()
eval_preds = []
for _, batch in enumerate(tqdm(eval_dataloader)):
batch = {k: v.to("cuda") for k, v in batch.items() if k != "labels"}
with torch.no_grad():
outputs = model.generate(**batch, max_new_tokens=10)
eval_preds.extend(tokenizer.batch_decode(outputs, skip_special_tokens=True))
correct = 0
total = 0
for pred, true in zip(eval_preds, dataset["train"][label_column]):
if pred.strip() == true.strip():
correct += 1
total += 1
accuracy = correct / total * 100
print(f"{accuracy=}")
技术要点解析
- 内存优化:通过max_memory参数控制各设备内存使用,实现大模型的分布式加载
- 高效微调:LoRA技术仅微调少量参数,大幅降低计算资源需求
- 设备映射:device_map="auto"自动将模型分配到可用设备上
- 批处理加速:使用DataLoader和pin_memory加速数据加载
- 生成控制:max_new_tokens限制生成长度,提高推理效率
实际应用建议
- 对于不同的任务,需要调整max_new_tokens参数以适应标签长度
- 在资源有限的情况下,可以进一步减小batch_size
- 对于生产环境,建议添加异常处理和日志记录
- 可以考虑量化技术进一步减小模型内存占用
通过PEFT的LoRA技术,我们能够以极低的计算成本微调大型序列到序列模型,并在保持高性能的同时大幅降低资源消耗。这种方法特别适合资源有限但需要利用大模型能力的应用场景。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考