我将其从《PEFT微调:在大模型中快速应用 LoRA》拆分出来以向你展示单独这个 Bug。
另外,我已经向官方提出了 issue#2115,并得到了很积极的回复,这些开发人员很棒。预计在未来的版本会解决这个问题,当前 Bug 会出现在版本 <=0.12.0。
不过现在,让我们定位到它并解决。
检查你的代码中是否使用了错误的方法:
# 正确
model = PeftModel.from_pretrained(model, PATH)
# 错误
model = get_peft_model(model, lora_config)
model = PeftModel.from_pretrained(model, PATH)
定位结束,没有的话就可以关闭这篇文章了,你的解决方法不在这:)
如果你对我发现它的故事感兴趣,可以继续往下阅读:
我花了三个小时排除了所有可能的问题才找到它,起因:将代码从 load stata_dict 转为 PEFT 以供学习。
# 原始项目代码(正确):
# 将 LoRA 配置应用到 text_encoder 和 unet
text_encoder = get_peft_model(text_encoder, lora_config)
unet = get_peft_model(unet, lora_config)
# 如果设置为继续训练,则加载上一次的模型权重,当然,你可以修改 model_path 来指定其他的路径
if resume:
# 加载上次训练的模型权重,注意这里只加载权重,而不是覆盖整个模型,覆盖:model = torch.load(...)
text_encoder = torch.load(os.path.join(model_path, "text_encoder.pt"))
unet = torch.load(os.path.join(model_path, "unet.pt"))
转换为 PEFT 形式:
# 错误的示范
# 将 LoRA 配置应用到 text_encoder 和 unet
text_encoder = get_peft_model(text_encoder, lora_config)
unet = get_peft_model(unet, lora_config)
# 如果设置为继续训练,则加载上一次的模型权重
if resume:
# 使用 PEFT 的 from_pretrained 方法加载 LoRA 模型
text_encoder = PeftModel.from_pretrained(text_encoder, os.path.join(model_path, "text_encoder"))
unet = PeftModel.from_pretrained(unet, os.path.join(model_path, "unet"))
很好,现在我们获得了一个不会报错,但是效果和没加 LoRA 完全相同的模型,真是太棒了(它真的太刁钻了😡)。
来看看它究竟有什么区别,为了清晰,定义一个简单的线性层进行演示:
import torch
import torch.nn as nn
from torch.optim import Adam
from copy import deepcopy
from peft import get_peft_model, LoraConfig, PeftModel
# 固定随机数种子,确保结果可复现
torch.manual_seed(42)
# 定义一个简单的线性模型
class LinearModel(nn.Module):
def __init__(self, input_size, output_size):
super(LinearModel, self).__init__()
self.linear = nn.Linear(input_size, output_size)
def forward(self, x):
return self.linear(x)
# 实例化线性模型
model = LinearModel(input_size=10, output_size=1)
# 在应用 LoRA 之前深拷贝原始模型,确保后续公平比较
original_model = deepcopy(model)
# 配置 LoRA 参数
config = LoraConfig(
inference_mode=False,
r=4,
lora_alpha=16,
target_modules=['linear'],
)
# 将 LoRA 应用到模型中
lora_model = get_peft_model(model, config)
# 定义一个简单的损失函数和优化器
criterion = nn.MSELoss()
optimizer = Adam(lora_model.parameters(), lr=1e-3)
# 生成一些模拟的训练数据
input_data = torch.randn(100, 10) # 100 个样本,每个样本有 10 个特征
target_data = torch.randn(100, 1) # 对应的目标值
# 训练一个回合
lora_model.train()
for epoch in range(1): # 训练 1 个回合
optimizer.zero_grad()
outputs = lora_model(input_data)
loss = criterion(outputs, target_data)
loss.backward()
optimizer.step()
# 训练后保存 LoRA 权重
lora_model.save_pretrained('linear_lora_model')
# 方法 1:先使用 get_peft_model,再加载 LoRA 权重
model1 = PeftModel.from_pretrained(get_peft_model(deepcopy(original_model), config), 'linear_lora_model')
# 方法 2:直接加载 LoRA 权重
model2 = PeftModel.from_pretrained(deepcopy(original_model), 'linear_lora_model')
# 生成相同的输入数据以进行输出比较
test_input = torch.randn(1, 10)
# 比较四个模型的输出(原始模型,LoRA,方法1,方法2)
def compare_model_outputs(input_data):
# 原始模型
original_output = original_model(input_data)
print("原始模型输出:", original_output.detach().numpy())
# 训练后的 LoRA 模型
lora_output = lora_model(input_data)
print("训练后的 LoRA 模型输出:", lora_output.detach().numpy())
# 方法 1:先使用 get_peft_model,再加载 LoRA
output1 = model1(input_data)
print("方法 1(先使用 get_peft_model,再加载 LoRA)输出:", output1.detach().numpy())
# 方法 2:直接加载 LoRA
output2 = model2(input_data)
print("方法 2(直接加载 LoRA)输出:", output2.detach().numpy())
if torch.allclose(original_output, output1):
print("\n原始模型和方法 1 输出相同。")
if torch.allclose(lora_output, output2):
print("训练后的 LoRA 模型和方法 2 输出相同。\n")
# 比较两个模型的参数
def compare_params(m1, m2):
for (n1, p1), (n2, p2) in zip(m1.named_parameters(), m2.named_parameters()):
if n1 != n2 or not torch.allclose(p1, p2):
print(f"参数不匹配: \n{n1}\n{n2}")
return False
return True
# 比较四个模型的输出
compare_model_outputs(test_input)
# 检查方法 1 和方法 2 的参数是否一致
if compare_params(model1, model2):
print("方法 1 和方法 2 的 LoRA 模型参数一致!")
else:
print("方法 1 和方法 2 的 LoRA 模型参数不一致!")
输出:
原始模型输出: [[-0.03600371]]
训练后的 LoRA 模型输出: [[-0.03428639]]
方法 1(先使用 get_peft_model,再加载 LoRA)输出: [[-0.03600371]]
方法 2(直接加载 LoRA)输出: [[-0.03428639]]
原始模型和方法 1 输出相同。
训练后的 LoRA 模型和方法 2 输出相同。
参数不匹配:
base_model.model.base_model.model.linear.base_layer.weight
base_model.model.linear.base_layer.weight
方法 1 和方法 2 的 LoRA 模型参数不一致!
从输出中可以看到,方法 1(在加载 LoRA 之前使用 get_peft_model()
)与原始模型的输出完全相同,这意味着 LoRA 没有被有效应用。而方法 2(直接使用 PeftModel.from_pretrained()
加载 LoRA 权重)的输出与训练后的 LoRA 模型输出一致,说明被正确加载。
另外,你还可以看到方法 1 的模型架构会多一个 base_model.model 包裹,如果你感兴趣的话可以使用 print(model1)
进一步地查看,这证明了在加载 LoRA 之前使用 get_peft_model()
会干扰模型结构,导致 LoRA 应用失效。