LLMs Fine-tuning 学习笔记:trl+peft
Posted 云野Winfield
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LLMs Fine-tuning 学习笔记:trl+peft相关的知识,希望对你有一定的参考价值。
1 基本信息
From:Fine-tuning 20B LLMs with RLHF on a 24GB consumer GPU (huggingface.co)
Codes:trl/examples/sentiment/scripts/gpt-neox-20b_peft at main · lvwerra/trl · GitHub
相关工具:
- peft:用于微调大模型的python库
- transformers:用于获取和使用开源社区中预训练模型的python库
- trl:使用强化学习算法来训练或微调模型的python库
使用RLHF训练LLM的三个基本步骤:
- 构建一个由prompt和respond组成的数据集,用这个数据集来微调LLM
- 收集第一步中训好的LLM对各prompt的不同回复,对同一个prompt的不同回复内容按质量好坏进行排序,排序后的数据将构成新的数据集用在第三步中
- 使用强化学习算法(比如PPO)根据第二步得到的数据集对第一步得到的LLM进行RLHF微调
可选的基础模型(截至March 9, 2023):
- https://huggingface.co/bigscience/bloomz
- https://huggingface.co/google/flan-t5-xxl
- https://huggingface.co/google/flan-ul2
- https://huggingface.co/facebook/opt-iml-max-30b
关于模型和GPU显存之间的关系
- 建议基础模型参数量大于100亿,这类模型全精度工作一般需要40G以上显存
- 在GPU上以全精度(FP32)加载模型,每10亿参数需要消耗 4GB显存,以半精度(FP32)加载模型需要的显存是全精度的一半
- 更多关于精度量化和显存优化的信息:https://huggingface.co/blog/hf-bitsandbytes-integration
关于模型/数据并行及分布式训练:
对RLHF的理解:
- 在RLHF中,Actor Model(生成模型)需要Instruct Tuning来学习如何follow指令,而Reward model将学习人类的偏好,对Actor Model的输出进行打分。因此,可以把Reward Model理解为一个针对Actor Model输出结果的分类器。
对PPO的理解:
-
Overview of the PPO training setup in TRL:
-
The active model is the model being trained, and a copy of it is periodically made as the reference model. When the policy changes, the reference model is used as a baseline to evaluate whether the changes made by the active model are good or bad.
在单GPU中完成RLHF的关键技术要素:adapters 和 8bit matrix multiplication
-
8-bit matrix multiplication
- 一个优化矩阵相乘的算法,可以降低Transformer中前馈和注意力计算阶段的显存消耗
- 引入8-bit matrix multiplication后,和全精度(FP32)相比,模型对显存的消耗可以降低4倍
- 论文:LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale (arxiv.org)
- 一个优化矩阵相乘的算法,可以降低Transformer中前馈和注意力计算阶段的显存消耗
-
Adapters
-
Adapters,或者说LoRA,这是一种针对LLM的微调方法,其核心思想是把LLM中的需要变更的权重矩阵替换成Fine-tuning过程中习得的低秩近似(low-rank approximations)矩阵,以此减少Fine-tuning过程中的计算资源消耗
-
论文:LoRA: Low-Rank Adaptation of Large Language Models (arxiv.org)
-
-
注意 8-bit int8 training 和 Low Rank adaption 在 Parameter-Efficient Fine-Tuning (PEFT) 包中都有现成实现
2 实现步骤
第一步:以8-bit精度加载预训练模型
- 调用transformers的
from_pretrained()
方法时加上load_in_8bit=True
即可,参考:Quantize Transformers models
第二步:使用peft在预训练模型中增加一个可训练的Adapter
-
这样我们在微调模型时只需要动Adapter中的参数即可,不需要调整整个Active模型的参数:
第三步:使用添加了Adapter的模型来做PPO,实现RLHF
-
peft提供了便捷的API ,使我们可以随时启用或禁用模型中额外添加的Adapter。禁用Adapter时就是Reference Model,启用Adapter时就是Active Model:
3 代码分析
这个坑等后面有空再填。。。
Pytorch Note56 Fine-tuning 通过微调进行迁移学习
Pytorch Note56 Fine-tuning 通过微调进行迁移学习
文章目录
全部笔记的汇总贴: Pytorch Note 快乐星球
通过微调进行迁移学习
前面我们介绍了如何训练卷积神经网络进行图像分类,可能你已经注意到了,训练一个卷积网络是特别耗费时间的,特别是一个比较深的卷积网络,而且可能因为训练方法不当导致训练不收敛的问题,就算训练好了网络,还有可能出现过拟合的问题,所以由此可见能够得到一个好的模型有多么困难。
有的时候,我们的数据集还特别少,这对于我们来讲无异于雪上加霜,因为少的数据集意味着非常高的风险过拟合,那么我们有没有办法在某种程度上避免这个问题呢?其实现在有一种方法特别流行,大家一直在使用,那就是微调(fine-tuning),在介绍微调之前,我们先介绍一个数据集 ImageNet。
ImageNet
ImageNet 是一个计算机视觉系统识别项目,是目前世界上最大的图像识别数据库,由斯坦福大学组织建立,大约有 1500 万张图片,2.2 万中类别,其中 ISLVRC 作为其子集是学术界中使用最为广泛的公开数据集,一共有 1281167 张图片作为训练集,50000 张图片作为验证集,一共是 1000 分类,是目前测试网络性能的标杆。
我们说的这个数据集有什么用呢?我们又不关心这个数据集,但是对于我们自己的问题,我们有没有办法借助 ImageNet 中的数据集来提升模型效果,比如我们要做一个猫狗分类器,但是我们现在只有几百张图片,肯定不够,ImageNet 中有很多关于猫狗的图片,我们如果能够把这些图片拿过来训练,不就能够提升模型性能了吗?
但是这种做法太麻烦了,从 ImageNet 中寻找这些图片就很困难,如果做另外一个问题又要去找新的图片,所以直接找图片并不靠谱,那么有没有办法能够让我们不去找这些图片,又能使用这些图片呢?
非常简单,我们可以使用在 ImageNet 上训练好的网路,然后把这个网络在放到我们自己的数据集上进行训练不就好了。这个方法就叫做微调,这十分形象,相当于把一个已经很厉害的模型再微调到我们自己的数据集上来,也可称为迁移学习。
迁移学习的方法非常简单,将预训练的模型导入,然后将最后的分类全连接层换成适合我们自己问题的全连接层,然后开始训练,可以固定卷积层的参数,也可以不固定进行训练,最后能够非常有效的得到结果
pytorch 一直为我们内置了前面我们讲过的那些著名网络的预训练模型,不需要我们自己去 ImageNet 上训练了,模型都在 torchvision.models
里面,比如我们想使用预训练的 50 层 resnet,就可以用 torchvision.models.resnet50(pretrained=True)
来得到
下面我们用一个例子来演示一些微调
import sys
sys.path.append('..')
import numpy as np
import torch
from torch import nn
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import models
from torchvision import transforms as tfs
from torchvision.datasets import ImageFolder
首先我们点击下面的链接获得数据集,终端可以使用
wget https://download.pytorch.org/tutorial/hymenoptera_data.zip
下载完成之后,我们将其解压放在程序的目录下,这是一个二分类问题,区分蚂蚁和蜜蜂
我们可以可视化一下图片,看看你能不能区分出他们来
import os
from PIL import Image
import matplotlib.pyplot as plt
%matplotlib inline
root_path = './hymenoptera_data/train/'
im_list = [os.path.join(root_path, 'ants', i) for i in os.listdir(root_path + 'ants')[:4]]
im_list += [os.path.join(root_path, 'bees', i) for i in os.listdir(root_path + 'bees')[:5]]
nrows = 3
ncols = 3
figsize = (8, 8)
_, figs = plt.subplots(nrows, ncols, figsize=figsize)
for i in range(nrows):
for j in range(ncols):
figs[i][j].imshow(Image.open(im_list[nrows*i+j]))
figs[i][j].axes.get_xaxis().set_visible(False)
figs[i][j].axes.get_yaxis().set_visible(False)
plt.show()
定义数据预处理
# 定义数据预处理
train_tf = tfs.Compose([
tfs.RandomResizedCrop(224),
tfs.RandomHorizontalFlip(),
tfs.ToTensor(),
tfs.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 使用 ImageNet 的均值和方差
])
valid_tf = tfs.Compose([
tfs.Resize(256),
tfs.CenterCrop(224),
tfs.ToTensor(),
tfs.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
# 使用 ImageFolder 定义数据集
train_set = ImageFolder('./hymenoptera_data/train/', train_tf)
valid_set = ImageFolder('./hymenoptera_data/val/', valid_tf)
# 使用 DataLoader 定义迭代器
train_data = DataLoader(train_set, 25, True, num_workers=4)
valid_data = DataLoader(valid_set, 32, False, num_workers=4)
使用预训练的模型
# 使用预训练的模型
net = models.resnet50(pretrained=True)
print(net)
ResNet( (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False) (layer1): Sequential( (0): Bottleneck( (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (downsample): Sequential( (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (1): Bottleneck( (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) ) (2): Bottleneck( (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) ) ) (layer2): Sequential( (0): Bottleneck( (conv1): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (downsample): Sequential( (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False) (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (1): Bottleneck( (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) ) (2): Bottleneck( (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) ) (3): Bottleneck( (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1)Pytorch Note56 Fine-tuning 通过微调进行迁移学习