前言
仅记录学习过程,有问题欢迎讨论
大模型训练相关知识:
问题:
- 数据集过大,快速训练
- 模型过大,gpu跑不完
方案:
- 数据并行训练:
复制数据(batch_size)到多个gpu,各自计算loss,再反传更新;至少需要一张卡能训练一个样本 - 模型并行:
模型的不同层放到不同的gpu上,解决了单卡不够大的问题,但是需要更多的通讯时间了(一次训练需要传播很多次)【时间换空间】 - 张量并行:
将张量划分到不同的gpu上(张量左右分割),进一步减少对单卡的需求,【时间换空间】
可以混合使用
浮点精度损失:
float32:
使用32位二进制来表示一个浮点数。第一位用于表示符号位(正或负),
接下来的八位用于表示指数,剩下的23位用于表示尾数或分数部分。Float32的数值范围大约在±3.4E+38之间
0.2 = 0.00110(B)~、
DeepSpeed(零冗余优化器)
优化训练的方案
ZeRo:
- 相当于张量并行,gradients,parameters,分散到不同gpu计算。速度变慢
ZeRo -offload
添加内存帮助显卡计算哈哈
大模型的蒸馏: 小模型直接学习文本数据之外,还学习大模型预测的结果
大模型的剪枝: 删除大模型中不重要的部分,减少模型大小
PEFT微调:(当大模型对某个方面表现不好)
- 预训练模型+微调数据集
原始权重不动,增加一部分可训练的权重,去结合之前的部分
prompt tuning:
- 预训练模型+提示词(提前告诉模型需要训练什么)(添加在input data)
P-tuning V2:训练虚拟token(添加在emb)
- 该方法将 Prompt 转换为可以学习的 Embedding 层,并用MLP+LSTM的方式来对Prompt Embedding进行一层处理。
Adapter: 新增可训练的层(添加在fft后)
LoRa(目前比较流行):
- 通过低秩分解来模拟参数的改变量,从而以极小的参数量来实现大模型的间接训练(最后再扩大)。
RAG(Retrieve Augmented Generation):
- 模型幻觉问题,遇见不知道的问题,会乱答
召回部分和Prompt相似的段落,重新输入模型,获取可靠答案。
优势(主要就这两条):
(1)可扩展性:减少模型大小和训练成本,并能够快速扩展知识。
(2)准确性:模型基于事实进行回答,减少幻觉的发生。
难点:
(1)数据标注:需要对数据进行标注,以提供给模型进行检索。
(2)检索算法:需要设计高效的检索算法,以提高检索的效率和准确率。
BPE(Byte pair encoding):压缩算法
-
RAG针对词表vocab的以下问题
1.测试过程出现词表没有的词
2.词表过大
3.不同语种,切分粒度不同 -
如aaabdaaaabc ==> XdXac
在nlp中,针对语料中出现的重复词,添加到vocab中,再切分,再添加 -
BPE通过构建跨语言共享的子词词汇表,提高了模型处理多种语言的能力,有效解决了不同语言间词汇差异大、低频词和OOV问题,增强了模型的泛化能力和翻译性能。
代码:
使用lora对大模型进行微调:
使用的是序列标注的ner代码
main.py
# -*- coding: utf-8 -*-
import torch
import os
import random
import os
import numpy as np
import logging
from config import Config
from model import TorchModel, choose_optimizer
from evaluate import Evaluator
from loader import load_data
from peft import get_peft_model, LoraConfig, TaskType
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
"""
模型训练主程序
"""
def peft_wrapper(model):
peft_config = LoraConfig(
r=8,
lora_alpha=32,
lora_dropout=0.1,
target_modules=["query", "value"]
)
return get_peft_model(model, peft_config)
def main(config):
# 创建保存模型的目录
if not os.path.isdir(config["model_path"]):
os.mkdir(config["model_path"])
# 加载训练数据
train_data = load_data(config["train_data_path"], config)
# 加载模型
model = TorchModel(config)
model = peft_wrapper(model)
# 标识是否使用gpu
cuda_flag = torch.cuda.is_available()
if cuda_flag:
logger.info("gpu可以使用,迁移模型至gpu")
model = model.cuda()
# 加载优化器
optimizer = choose_optimizer(config, model)
# 加载效果测试类
evaluator = Evaluator(config, model, logger)
# 训练
for epoch in range(config["epoch"]):
epoch += 1
model.train()
logger.info("epoch %d begin" % epoch)
train_loss = []
for index, batch_data in enumerate(train_data):
optimizer.zero_grad()
if cuda_flag:
batch_data = [d.cuda() for d in batch_data]
input_id, labels = batch_data # 输入变化时这里需要修改,比如多输入,多输出的情况
loss = model(input_id, labels)
loss.backward()
optimizer.step()
train_loss.append(loss.item())
if index % int(len(train_data) / 2) == 0:
logger.info("batch loss %f" % loss)
logger.info("epoch average loss: %f" % np.mean(train_loss))
evaluator.eval(epoch)
model_path = os.path.join(config["model_path"], "epoch_%d.pth" % epoch)
# torch.save(model.state_dict(), model_path)
return model, train_data
if __name__ == "__main__":
model, train_data = main(Config)
model.py
# -*- coding: utf-8 -*-
import torch
import torch.nn as nn
from torch.optim import Adam, SGD
from torchcrf import CRF
from transformers import BertModel
"""
建立网络模型结构
"""
class ConfigWrapper(object):
def __init__(self, config):
self.config = config
def to_dict(self):
return self.config
class TorchModel(nn.Module):
def __init__(self, config):
super(TorchModel, self).__init__()
self.config = ConfigWrapper(config)
max_length = config["max_length"]
class_num = config["class_num"]
# self.embedding = nn.Embedding(vocab_size, hidden_size, padding_idx=0)
# self.layer = nn.LSTM(hidden_size, hidden_size, batch_first=True, bidirectional=True, num_layers=num_layers)
self.bert = BertModel.from_pretrained(config["bert_path"], return_dict=False)
self.classify = nn.Linear(self.bert.config.hidden_size, class_num)
self.crf_layer = CRF(class_num, batch_first=True)
self.use_crf = config["use_crf"]
self.loss = torch.nn.CrossEntropyLoss(ignore_index=-1) # loss采用交叉熵损失
# 当输入真实标签,返回loss值;无真实标签,返回预测值
def forward(self, x, target=None):
# x = self.embedding(x) #input shape:(batch_size, sen_len)
# x, _ = self.layer(x) #input shape:(batch_size, sen_len, input_dim)
x, _ = self.bert(x)
predict = self.classify(x) # ouput:(batch_size, sen_len, num_tags) -> (batch_size * sen_len, num_tags)
if target is not None:
if self.use_crf:
mask = target.gt(-1)
return - self.crf_layer(predict, target, mask, reduction="mean")
else:
# (number, class_num), (number)
return self.loss(predict.view(-1, predict.shape[-1]), target.view(-1))
else:
if self.use_crf:
return self.crf_layer.decode(predict)
else:
return predict
def choose_optimizer(config, model):
optimizer = config["optimizer"]
learning_rate = config["learning_rate"]
if optimizer == "adam":
return Adam(model.parameters(), lr=learning_rate)
elif optimizer == "sgd":
return SGD(model.parameters(), lr=learning_rate)
if __name__ == "__main__":
from config import Config
model = TorchModel(Config)