趋近智
一个分步操作指南,将LoRA应用于预训练模型进行微调。过程中利用Hugging Face的transformers和peft库,这些库提供了便捷的抽象功能,用于应用LoRA。该过程包含加载模型、配置LoRA、进行应用,并准备训练等步骤。
本次实践假定您拥有已安装torch、transformers、peft和datasets的可用Python环境。
pip install -q torch transformers datasets peft accelerate bitsandbytes
首先,我们导入必要的库并加载预训练模型和一个数据集。为进行演示,我们将使用GPT-2(一种常见的因果语言模型)和ELI5数据集的一个小部分,该数据集包含问答对。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments, DataCollatorForLanguageModeling
from peft import LoraConfig, get_peft_model, TaskType, PeftModel
from datasets import load_dataset
# 加载模型和分词器
model_name = "gpt2" # 示例:使用'gpt2'或其他合适的因果语言模型
base_model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token # 设置用于批处理的填充标记
# 加载和准备数据集(使用小部分进行演示)
dataset = load_dataset("eli5", split="train_asks[:1000]") # 使用小切片
# 基本预处理:对文本进行分词
def preprocess_function(examples):
# 为因果语言模型训练连接问题和答案
texts = [q + " " + a[0] for q, a in zip(examples['title'], examples['answers.text'])]
return tokenizer(texts, truncation=True, padding="max_length", max_length=128)
tokenized_dataset = dataset.map(preprocess_function, batched=True, remove_columns=dataset.column_names)
print("基础模型已加载:", model_name)
print("数据集已加载并分词。")
通过peft库应用LoRA的要点在于LoraConfig。此对象指定LoRA应如何结合到基础模型中。我们需要定义秩 、缩放因子 以及LoRA应作用于哪些层。
如前所述:
r:分解 的秩。较小的 意味着更少的可训练参数,但可能限制适应能力。典型值范围为 4 到 64。lora_alpha:应用于LoRA输出的缩放因子()。它控制适应相对于原始权重的幅度。常见做法是将lora_alpha设置为等于或两倍于r的值。target_modules:基础模型中模块名称(或正则表达式模式)的列表,LoRA矩阵应在此处替换或增强线性层。对于GPT-2,常见的目标是注意力投影层(例如,c_attn)或前馈网络层。您可以检查base_model.named_modules()以找到合适的名称。task_type:指定任务目标,影响PEFT结构可能与模型头部交互的方式。对于GPT-2微调,TaskType.CAUSAL_LM是合适的。# 定义LoRA配置
lora_config = LoraConfig(
r=16, # 秩 r
lora_alpha=32, # 缩放因子 alpha
target_modules=["c_attn"], # 将LoRA应用于注意力中的查询、键、值投影
lora_dropout=0.05, # LoRA层的Dropout概率
bias="none", # 不训练偏置参数
task_type=TaskType.CAUSAL_LM # 因果语言模型的任务类型
)
print("LoRA配置:")
print(lora_config)
定义LoraConfig后,使用get_peft_model将其应用于基础模型很简单。此函数根据配置修改模型架构,冻结原始权重并加入可训练的LoRA适配器。
让我们比较应用LoRA前后可训练参数的数量。
# 计算原始可训练参数
original_params = sum(p.numel() for p in base_model.parameters() if p.requires_grad)
print(f"原始可训练参数: {original_params:,}")
# 将LoRA配置应用于基础模型
lora_model = get_peft_model(base_model, lora_config)
# 计算LoRA可训练参数
lora_params = sum(p.numel() for p in lora_model.parameters() if p.requires_grad)
print(f"LoRA可训练参数: {lora_params:,}")
# 计算减少量
reduction = (original_params - lora_params) / original_params * 100
print(f"参数减少: {reduction:.2f}%")
# 打印可训练模块以供验证
lora_model.print_trainable_parameters()
您应该观察到可训练参数数量大幅减少(通常大于99%)。print_trainable_parameters的输出确认只有新添加的LoRA组件(lora_A和lora_B)需要梯度。
完全微调与LoRA微调中可训练参数的比较。LoRA通过只训练小型适配器矩阵,同时保持基础模型冻结,大幅减少了参数数量。
现在,我们可以使用transformers.Trainer设置标准训练过程。主要区别在于我们传递的是lora_model(PEFT修改后的模型),而不是base_model。Trainer将自动处理优化,只关注可训练的LoRA参数。
# 定义训练参数
output_dir = "./lora_gpt2_eli5_results"
training_args = TrainingArguments(
output_dir=output_dir,
num_train_epochs=1, # 为演示目的缩短周期
per_device_train_batch_size=4, # 根据您的GPU内存调整
logging_steps=50,
save_steps=200,
learning_rate=2e-4, # LoRA的典型学习率
fp16=torch.cuda.is_available(), # 如果可用,使用混合精度
# 根据需要添加其他参数:weight_decay, warmup_steps等。
)
# 因果语言模型的数据整理器
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
# 初始化训练器
trainer = Trainer(
model=lora_model, # 使用PEFT模型
args=training_args,
train_dataset=tokenized_dataset,
tokenizer=tokenizer,
data_collator=data_collator,
)
print("训练器已初始化。开始训练...")
启动训练过程。Trainer将处理前向和反向传播,只更新LoRA权重(和)。基础模型权重保持不变。
# 开始训练
trainer.train()
print("训练完成。")
在训练期间,请观察日志。损失应该下降,表明LoRA适配器正在学习调整模型的行为以适应目标任务(在本例中,模仿ELI5的问答风格)。
训练后,您不需要保存整个模型。相反,您只保存训练好的LoRA适配器权重。这是LoRA等PEFT方法的主要优点之一——生成的产物非常小。
# 定义保存适配器的路径
adapter_path = f"{output_dir}/final_adapter"
# 保存LoRA适配器权重
lora_model.save_pretrained(adapter_path)
print(f"LoRA适配器已保存到: {adapter_path}")
# 您可以验证保存的适配器目录的小尺寸
# !ls -lh {adapter_path}
保存的目录(adapter_path)将包含adapter_model.bin(LoRA权重)和adapter_config.json(使用的LoRA配置)等文件。其大小将以兆字节计,明显小于完整基础模型所需的千兆字节。
要使用微调后的模型进行推理,您需要先加载原始基础模型,然后应用已保存的LoRA适配器权重。
# 再次加载基础模型(或使用内存中已有的模型)
base_model_reloaded = AutoModelForCausalLM.from_pretrained(model_name)
# 通过合并适配器权重加载PEFT模型
inference_model = PeftModel.from_pretrained(base_model_reloaded, adapter_path)
# 确保模型处于评估模式并在正确的设备上
inference_model.eval()
if torch.cuda.is_available():
inference_model.to("cuda")
print("基础模型已加载并应用LoRA适配器进行推理。")
# 示例推理(可选)
prompt = "What is the main cause of climate change?"
inputs = tokenizer(prompt, return_tensors="pt")
if torch.cuda.is_available():
inputs = {k: v.to("cuda") for k, v in inputs.items()}
# 生成文本
with torch.no_grad():
outputs = inference_model.generate(**inputs, max_new_tokens=50, pad_token_id=tokenizer.eos_token_id)
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print("\n--- 示例生成 ---")
print("提示:", prompt)
print("生成内容:", generated_text)
print("------------------------")
这就完成了一个应用LoRA的基本周期:配置、封装模型、训练、保存适配器以及加载使用。您通过只训练大型语言模型极小一部分参数,就有效地对其进行了微调,展示了LoRA的效率。接下来的部分和章节将在此根基上进行构建,研习更高级的配置、QLoRA等变体以及评估技术。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造