LoRA训练推理部署流程

前言

这是LoRA相关的第三篇文章

上篇文章已经比较全面的讨论过了LoRA的一些配置和网络模型结构,本文主要从以下几个方面进一步看一下LoRA的微调、模型保存、加载和推理流程

不同层配置不同的LoRA参数

首先来看一下上篇最后的问题 如何修改LoraConfig使得fc1和fc2中的超参不同呢?

当LoRA的配置文件这样配置时,fc1和fc2使用的是同一份超参数,打印结果如下

config = LoraConfig(
    r=4,
    lora_alpha=16,
    lora_dropout=0.1,
    target_modules=["fc1", "fc2"],
)

model = get_peft_model(model, config)
print(f"增加Lora策略的,模型参数量:{count_parameters(model)}")

可以看到fc1和fc2中额外的低秩矩阵r都为4。

base_model.model.fc1.lora_A.default.weight: torch.Size([4, 10])
base_model.model.fc1.lora_B.default.weight: torch.Size([20, 4])
base_model.model.fc2.lora_A.default.weight: torch.Size([4, 20])
base_model.model.fc2.lora_B.default.weight: torch.Size([2, 4])

现在我们修改配置文件
fc1层的配置r=4,dropout=0.1,fc2层的配置r=2,dropout=0.3。整体代码如下

import torch
import torch.nn as nn
from peft import get_peft_model, LoraConfig
from simple_mlp import SimpleMLP


def count_parameters(model):
    """计算可训练的参数"""
    for name, param in model.named_parameters():
        if param.requires_grad:
            print(f"{name}: {param.size()}")
    return sum(p.numel() for p in model.parameters() if p.requires_grad)


model = SimpleMLP()
# print(f"原始的网络结构:\n {model}")
# print(f"原始模型的参数量:{count_parameters(model)}")

# 为fc1层设置r=4
config_fc1 = LoraConfig(
    r=4,
    lora_alpha=16,
    lora_dropout=0.1,
    target_modules=["fc1"],
)

# 为fc2层设置r=2
config_fc2 = LoraConfig(
    r=2,
    lora_alpha=16,
    lora_dropout=0.3,
    target_modules=["fc2"],
)

# 应用LoRA到模型的不同层
model = get_peft_model(model, config_fc1)
model = get_peft_model(model, config_fc2)

print(f"Lora微调的网络结构:\n {model}")
print(f"增加Lora策略的,模型参数量:{count_parameters(model)}")

网络结构如下

Lora微调的网络结构:
 PeftModel(
  (base_model): LoraModel(
    (model): PeftModel(
      (base_model): LoraModel(
        (model): SimpleMLP(
          (fc1): lora.Linear(
            (base_layer): Linear(in_features=10, out_features=20, bias=True)
            (lora_dropout): ModuleDict(
              (default): Dropout(p=0.1, inplace=False)
            )
            (lora_A): ModuleDict(
              (default): Linear(in_features=10, out_features=4, bias=False)
            )
            (lora_B): ModuleDict(
              (default): Linear(in_features=4, out_features=20, bias=False)
            )
            (lora_embedding_A): ParameterDict()
            (lora_embedding_B): ParameterDict()
          )
          (relu): ReLU()
          (fc2): lora.Linear(
            (base_layer): Linear(in_features=20, out_features=2, bias=True)
            (lora_dropout): ModuleDict(
              (default): Dropout(p=0.3, inplace=False)
            )
            (lora_A): ModuleDict(
              (default): Linear(in_features=20, out_features=2, bias=False)
            )
            (lora_B): ModuleDict(
              (default): Linear(in_features=2, out_features=2, bias=False)
            )
            (lora_embedding_A): ParameterDict()
            (lora_embedding_B): ParameterDict()
          )
        )
      )
    )
  )
)
base_model.model.base_model.model.fc1.lora_A.default.weight: torch.Size([4, 10])
base_model.model.base_model.model.fc1.lora_B.default.weight: torch.Size([20, 4])
base_model.model.base_model.model.fc2.lora_A.default.weight: torch.Size([2, 20])
base_model.model.base_model.model.fc2.lora_B.default.weight: torch.Size([2, 2])
增加Lora策略的,模型参数量:164

正文

以下是本文主要的部分,可作为LoRA实战初步入门来学习。

LoRA是一种高效微调预训练大模型的技术,首先就需要一个训练好的模型,用于LoRA微调。
现在我们来造一个无实际用途的网络模型。仅为了说明本文想表达的整理流程。

定义模型文件

文件simple_mlp.py,定义两层Linear,中间使用relu激活函数。模拟从一个10 维空间映射到2维空间。

from torch import nn

class SimpleMLP(nn.Module):
    def __init__(self):
        super(SimpleMLP, self).__init__()
        self.fc1 = nn.Linear(10, 20)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(20, 2)

    def forward(self, x):
        x = self.relu(self.fc1(x))
        return self.fc2(x))

训练一个原始模型

训练文件train.py,模拟一个存在预训练模型,将其保存在项目下的model/路径下。

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import torch.optim as optim
from simple_mlp import SimpleMLP

class SimpleDataset(Dataset):
    def __init__(self, num_samples=1000):
        self.x = torch.randn((num_samples, 10))
        self.y = torch.randint(0, 2, (num_samples,))
    def __len__(self):
        return len(self.x)
    def __getitem__(self, idx):
        return self.x[idx], self.y[idx]
        
dataset = SimpleDataset()
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

# 初始化模型
model = SimpleMLP()
# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化器
optimizer = optim.AdamW(model.parameters(), lr=0.001)
# 训练循环
num_epochs = 5
for epoch in range(num_epochs):
    for inputs, labels in dataloader:
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}")

# 保存整个模型
torch.save(model, "./model/model.pth")  # Size:3.4K
# 保存模型的状态字典
torch.save(model.state_dict(), "./model/model_state.pth")  # Size:2.6K

LoRA训练过程

文件lora_train.py,训练主要流程如下:

1、加载原始预训练模型
2、设置LoraConfig超参数
3、保存LoRA的权重

import torch
import torch.nn as nn
from peft import get_peft_model, LoraConfig
from torch.utils.data import Dataset, DataLoader
import torch.optim as optim
from simple_mlp import SimpleMLP


# 定义数据集
class SimpleDataset(Dataset):
    def __init__(self, num_samples=1000):
        self.x = torch.randn((num_samples, 10))
        self.y = torch.randint(0, 2, (num_samples,))
    def __len__(self):
        return len(self.x)
    def __getitem__(self, idx):
        return self.x[idx], self.y[idx]

dataset = SimpleDataset()
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

# 按照保存的方式加载模型
model = torch.load("./model/model.pth")

# model = SimpleMLP()
# model.load_state_dict(torch.load("./model/model_state.pth"))
# LoRA配置
peft_config = LoraConfig(
    r=4,
    lora_alpha=8,
    target_modules=["fc1"],
    lora_dropout=0.1,
)
model = get_peft_model(model, peft_config)
# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化器
optimizer = optim.AdamW(model.parameters(), lr=0.001)
# 训练循环
num_epochs = 5
for epoch in range(num_epochs):
    for inputs, labels in dataloader:
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}")

model.save_pretrained("./checkpoint")

LoRA推理过程

推理文件predict.py,加载原始模型和LoRA微调的参数

import torch
from peft import PeftModel
from sample_input import sample_input
from simple_mlp import SimpleMLP

# 加载原始模型和LoRA权重
model = torch.load("/home/my_code/lora/model/model.pth")
lora_path = "/home/my_code/lora/checkpoint"

model = PeftModel.from_pretrained(model, model_id=lora_path)
with torch.no_grad():
    output = model(sample_input)
    predicted_class = torch.argmax(output, dim=1)
print("Predicted class:", predicted_class.item())

推理数据源,数据模拟文件sample_input.py

import torch

sample_input = torch.randn(1, 10)

# 测试使用固定数据
# sample_input = torch.tensor(
#     [[1.0477, 1.3758, 0.3937, 0.8138, 0.3244, -0.2521, -1.4973, 0.4456, -0.1439, -0.3920]], dtype=torch.float32
# )

LoRA常见保存的文件说明

正常我们会结合tranformers库对模型进行微调,此时Lora保存的checkpoint文件下会有很多文件如下所示:

adapter_config.json:LoRA 配置信息。
adapter_model.safetensors LoRA的权重参数。 
.safetensors 格式是一种更安全的二进制格式,用于存储 PyTorch 模型权重。
optimizer.pt 存储优化器的状态,用于恢复训练状态。
rng_state.pth 存储随机数生成器的状态,用于恢复随机种子。
scheduler.pt 存储学习率调度器的状态,用于恢复训练状态。
training_args.bin 存储训练参数,如学习率、批次大小等。
training_state.json 存储训练参数,如学习率、批次大小等。

项目结构如下

├── checkpoint	# lora微调保存的权重和配置
│   ├── adapter_config.json
│   ├── adapter_model.safetensors
│   └── README.md
├── lora_net_structure1.py # 参见上一篇文章
├── lora_net_structure2.py	# 针对每一层Linear设置Lora配置
├── lora_train.py # lora的训练文件
├── model	# 原始训练模型保存路径
│   ├── model.pth
│   └── model_state.pth
├── predict.py	# 推理文件
├── sample_input.py # 推理数据来源
├── simple_mlp.py	# 网络结构
└── train.py	# 原始模型训练文件

相关文章

LoRA微调基础知识点
LoRA微调模型结构可训参数和配置详解
LoRA训练推理部署流程
更新中…
[LoRA基于BERT模型微调实践]

基座模型和LoRa模型是两种不同的技术模型,它们在应用场景和功能上有所不同,因此可以选择分开部署。 基座模型是指在软件架构中,将核心业务逻辑和基础设施分离的模式。它通过将基础设施和核心业务逻辑解耦,使得系统更加灵活和可扩展。基座模型可以将一些通用的功能,例如用户认证、权限管理、日志记录等模块集中在基础设施中,其他具体的业务功能则可以在不同的模块中进行开发。这种模式可以提高代码的复用性,简化系统的维护和扩展。 LoRa模型是指采用LoRa技术进行通信的模型。LoRa是一种低功耗广域网技术,适用于物联网等远距离无线通信场景。LoRa模型通常使用LoRa网关与终端设备进行通信,通过LoRaWAN协议实现设备之间的连接和数据传输。LoRa模型适用于需要远程监控、数据采集、传感器网络等场景,可以实现低成本、低功耗的无线通信。 将基座模型和LoRa模型分开部署可以实现代码的解耦和模块化。基座模型可以作为系统的基础设施,提供一些通用的功能。而LoRa模型则可以作为一个独立的模块,用于处理LoRa通信的相关业务。这样的分开部署可以使系统更加灵活和可扩展,降低不同模块之间的耦合度,提高系统的可维护性和可扩展性。 另外,分开部署还可以使得不同模型的开发团队专注于自己的领域,提高开发效率。基座模型的开发团队可以专注于基础设施的建设和维护,LoRa模型的开发团队则可以专注于LoRa通信相关的业务逻辑。这样的分工可以使开发过程更加高效和有序。 综上所述,基座模型和LoRa模型分开部署可以实现代码解耦、模块化开发,提高系统的灵活性和可扩展性,同时也提升了团队的开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值