从零到一打造自己的大模型:模型训练

前言

最近看了很多大模型,也使用了很多大模型。对于大模型理论似乎很了解,但是好像又缺点什么,思来想去决定自己动手实现一个 toy 级别的模型,在实践中加深对大语言模型的理解。

在这个系列的文章中,我将通过亲手实践,构建一个 1.2B 的模型,完成模型搭建、tokenizer 训练、模型预训练和指令微调这些流程。记录整个开发过程和其中遇到的各种挑战和对应解决方案。

最后这些内容并不以训练一个足够强大的模型为目标,更多的是走一遍流程,所以里面内容显得十分粗糙。所有的内容都是我对于大模型的理解形成的,如果您发现有任何过时或不准确的地方,请不吝指出。

训练组件

在使用 Pytorch 训练模型的时候,一个常见的流程就是前向传播、反向传播然后更新梯度,因此我们一步一步完成其中的组件。

优化器

现在训练大模型常用的优化器是 AdamW,它使用一阶动量和二阶动量保持梯度稳定,从而使损失不会过于震荡。这里不详细介绍原理,给出 Pytorch 中的实现。

from torch.optim import AdamWoptimizer = AdamW(params=model.parameters(),lr=args.lr,weight_decay=args.weight_decay,
)

这里我们设置了学习率和权重衰减,详细参数请见官方文档,这里不过多赘述。

调度器

学习率调度器可以在训练中动态调度学习率,从而提高训练效率和模型性能。合适的学习率有助于帮助模型脱离局部最优,达到一个更好的最优解。

这里我们采用 warmup 结合余弦退火的学习率调度策略。warmup 学习率预热开始从一个小学习率开始训练,然后再修正为指定的学习率。因为刚刚开始训练时模型权重随机初始化的,此时选择一个较大的学习率可能导致模型训练震荡。

余弦退火就是采用余弦方式对学习率进行衰减,这里我们给出调度器的实现:

from torch.optim.lr_scheduler import CosineAnnealingLR, LambdaLR, SequentialLRdef get_lr_warmup(warmup_steps: int):def lr_warmup(current_step: int):return float(current_step) / float(max(1, warmup_steps))return lr_warmupwarmup_steps = xxx
cosine_steps = xxx
warmup_scheduler = LambdaLR(optimizer=optimizer, lr_lambda=get_lr_warmup(warmup_steps=warmup_steps)
)
cosine_scheduler = CosineAnnealingLR(optimizer=optimizer, T_max=cosine_steps)
scheduler = SequentialLR(optimizer=optimizer,schedulers=[warmup_scheduler, cosine_scheduler],milestones=[warmup_steps],
)

warmup 阶段,我们采用线性递增的方式慢慢增大学习率,由于 Pytorch 没有相关实现,因此我们需要自己定义学习率调度的函数。

余弦退火阶段中 T_max 指定一个波峰到波谷的周期,也就是退火阶段迭代次数。最后将两个调度器组合起来,组成我们希望的调度器。

数据集

这次依然选择其作为我们的预训练数据集,在这里就要使用上一章训练的分词器。

首先加载数据集

from datasets import load_dataset, Datasetdataset: Dataset = load_dataset("json",data_files=["nlp_datas/part-000020-a894b46e.jsonl.tar.gz","nlp_datas/part-000065-a894b46e.jsonl.tar.gz",],split="train",
)

当然如果需要加载更大的数据,可以指定参数 streaming=True 减少加载的内存占用。加载之后就需要对数据集进行分词,得到模型需要的输入 input_idsattention_mask

from tokenization_custom import CustomTokenizertokenizer = CustomTokenizer.from_pretrained("tokenizer")
tokenized_dataset = dataset.map(lambda x: tokenizer(x["content"], truncation=True, max_length=2048),batched=True,remove_columns=dataset.column_names,
)

注意在这里我们只对文本进行了截断,但是没有对文本做填充,这样得到的文本可能是长短不一的。我们不需要对整体进行填充,这样会按照整体最大长度填充,占用大量存储,我们只需要对当时送入模型的一批数据进行填充即可。

因为我们采用的是预测下一个 token,因此 labelsinput_ids 相同,同时我们不希望 padding_token 参与计算损失,因为这是无意义的,所以对于 padding_token 的位置,对应的 label 是 -100,这里采用 DataCollatorForLanguageModeling 可以方便的完成这项操作。

这些都准备好,就可以得到 data_loader ,通过遍历 data_loader 就可以方便进行模型训练了。

data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
data_loader = DataLoader(dataset=dataset,batch_size=args.batch_size,shuffle=True,collate_fn=data_collator,num_workers=8,
)

训练流程

有了上面的各种组件,就可以进行训练了,首先确定模型结构,这里采用一个三层的结构。

from config import CustomConfig
from modeling_custom import CustomForCausalLMconfig = CustomConfig(vocab_size=len(tokenizer.get_vocab()),max_position_embeddings=2048,hidden_size=4096,intermediate_size=16384,num_hidden_layers=3,pad_token_id=tokenizer.pad_token_id,
)
model = CustomForCausalLM(config)

这里简单实现一个函数来计算模型参数量

def get_model_size(model: nn.Module):"""获取模型参数量"""return sum(p.numel() for p in model.parameters())

模型准备好后就可以进行模型训练,下面是一个简单的训练流程:

for epoch in range(args.epochs):for idx, batch in enumerate(data_loader):batch = {k: v.to(device) for k, v in batch.items()}outputs = model(**batch)logits, loss = outputs# 反向传播loss.backward()# 梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=args.max_norm)# 梯度更新optimizer.step()# 学习率更新scheduler.step()# 清除梯度optimizer.zero_grad()

对于一个 1.2B 模型,模型权重优化器状态梯度三部分大约占用显存计算如下:

1.2×109×4(FP32)10243×4≈17.88GB\frac{1.2 \times 10^9 \times 4(FP32)}{1024^3} \times 4 \approx 17.88GB102431.2×109×4(FP32)​×4≈17.88GB

这里简单计算一下中间激活值占用显存,假设 batchsize 为16,这一批 padding 之后的长度为512,因此 input_ids 的大小为 (16,512)。

  • 经过 embedding 层之后维度变为 (16,512,4096)
  • 经过注意力层会投影到 Q K V,三个维度均为 (16,512,4096)
  • 经过输出投影,维度为 (16,512,4096)
  • 经过前馈网络中上投影和门控,得到两个维度 (16,512,16384)
  • 经过下投影得到维度 (16,512,4096)
  • 经过词汇表大小投影(这里大约57000的大小)得到维度 (16,512,57000)

embedding 层的结果大约 32M,一层 Attention 层结果大约 128M,一层前馈网络结果大约 288M,最后词汇表投影大约 445M。这个模型中使用 3 层解码器层,不考虑层归一化的中间结果,这个模型总共中间结果大约有 1725M结果,每个结果占用 4Bytes,则最后总共显存占用大约 6.7GB。

这是长度为 512 的情况,实际上我的训练文本中大量存在 2k 左右文本,它会使占用显存成倍数增加,假设一个 2k 的文本,则显存占用会扩展到 26.8GB。

上面最理想的情况,实际计算中还会产生各种变量占用显存,很快就会导致显存溢出而从无法训练。幸运的是在实现模型结构时加入了梯度检查点,只需要保存关键节点的中间结果,反向传播时重新从最近节点开始计算即可,这样大大节省了显存。

1717660331127.png

在这个模型中只需要调用 model.enable_gradient_checkpoint() 即可开启梯度检查点。

除了梯度检查点,还可以通过减少 batchsize 来减少中间激活值占用显存,但是减少批量大小可能导致损失震荡无法收敛,这里我们采用多步累加解决这个问题,在一个小批次反向传播计算梯度之后,先不更新权重和清除梯度,而是累计多个小批次之后一起更新然后清除梯度。

最后还可以采用混合精度训练,这样不仅能加快训练速度还能显著减少中间激活值空间占用。

有了以上策略,可以尝试愉快训练模型了,训练前为了方便修改配置,我们进行一些封装,同时添加一些日志信息,方便最后观测整个训练过程,这里直接给出最后的代码。

import json
import os
import random
from dataclasses import dataclass
from typing import Optional, Unionimport numpy as np
import torch
import torch.nn as nn
from datasets import Dataset
from torch.cuda.amp import GradScaler, autocast
from torch.optim import AdamW
from torch.optim.lr_scheduler import CosineAnnealingLR, LambdaLR, SequentialLR
from torch.utils.data import DataLoader
from tqdm import tqdm
from transformers import DataCollatorForLanguageModelingfrom config import CustomConfig
from modeling_custom import CustomForCausalLM
from tokenization_custom import CustomTokenizer
from utils import get_model_sizeSEED = 42def set_seed(seed: int):torch.manual_seed(seed=seed)torch.cuda.manual_seed(seed=seed)torch.cuda.manual_seed_all(seed=seed)np.random.seed(seed=seed)random.seed(seed)def get_lr_warmup(warmup_steps: int):def lr_warmup(current_step: int):return float(current_step) / float(max(1, warmup_steps))return lr_warmup@dataclass
class TrainingArgs:output_dir: strlogging_steps: int = 500saving_steps: int = 500batch_size: int = 1epochs: int = 3lr: float = 1e-4weight_decay: float = 1e-4max_norm: float = 1.0warm_up_ratio: float = 0.1gradient_checkpointing: bool = Falsegradient_accumulation_steps: int = 24def train(model: nn.Module,args: TrainingArgs,dataset: Dataset,device: Optional[Union[str, torch.device]] = None,data_collator=None,
):data_loader = DataLoader(dataset=dataset,batch_size=args.batch_size,shuffle=True,collate_fn=data_collator,num_workers=8,)# 完整的有效步complete_steps_per_epoch = len(data_loader) // args.gradient_accumulation_steps# 不完整的有效步,最后剩余的小批量last_mini_steps = len(data_loader) % args.gradient_accumulation_steps# 一个 epoch 等效步if last_mini_steps != 0:steps_per_epoch = complete_steps_per_epoch + 1else:steps_per_epoch = complete_steps_per_epochtotal_steps = steps_per_epoch * args.epochs# 优化器optimizer = AdamW(params=model.parameters(),lr=args.lr,weight_decay=args.weight_decay,)# 学习率调度warmup_steps = int(total_steps * args.warm_up_ratio)cosine_steps = total_steps - warmup_stepswarmup_scheduler = LambdaLR(optimizer=optimizer, lr_lambda=get_lr_warmup(warmup_steps=warmup_steps))cosine_scheduler = CosineAnnealingLR(optimizer=optimizer, T_max=cosine_steps)scheduler = SequentialLR(optimizer=optimizer,schedulers=[warmup_scheduler, cosine_scheduler],milestones=[warmup_steps],)# 设备if device is None:device = "cuda" if torch.cuda.is_available() else "cpu"os.makedirs(args.output_dir, exist_ok=True)model = model.to(device=device)if args.gradient_checkpointing:model.enable_gradient_checkpoint()loggin_info = []current_step = 0progress_bar = tqdm(range(total_steps))scaler = GradScaler()for epoch in range(args.epochs):current_loss = 0.0for idx, batch in enumerate(data_loader):batch = {k: v.to(device) for k, v in batch.items()}if last_mini_steps == 0 or len(data_loader) - (idx + 1) > last_mini_steps:current_accumulation = args.gradient_accumulation_stepselse:current_accumulation = last_mini_stepswith autocast(dtype=torch.bfloat16):outputs = model(**batch)logits, loss = outputsloss /= current_accumulationcurrent_loss += loss.item()# 反向传播scaler.scale(loss).backward()if (idx + 1) % args.gradient_accumulation_steps == 0 or (idx + 1) == len(data_loader):# 梯度裁剪scaler.unscale_(optimizer=optimizer)torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=args.max_norm)# 梯度更新scaler.step(optimizer=optimizer)# 更新缩放因子scaler.update()# 学习率更新scheduler.step()# 清除梯度optimizer.zero_grad()progress_bar.update(1)current_step += 1if current_step % args.logging_steps == 0:current_epochs = current_step / steps_per_epochinfo = {"Epoch": f"{current_epochs:.2f}/{args.epochs}","Step": f"{current_step}/{total_steps}","Loss": current_loss,"LR": scheduler.get_last_lr()[0],}loggin_info.append(info)print(info)if current_step % args.saving_steps == 0:ckpt_path = os.path.join(args.output_dir,f"checkpoint-{current_step}.pt",)torch.save(model.state_dict(), ckpt_path)current_loss = 0.0ckpt_path = os.path.join(args.output_dir,"last.pt",)torch.save(model.state_dict(), ckpt_path)with open("logging.jsonl", "w", encoding="utf-8") as fw:for logging_data in loggin_info:fw.write(json.dumps(logging_data) + "\n")if __name__ == "__main__":set_seed(SEED)tokenizer = CustomTokenizer.from_pretrained("tokenizer")config = CustomConfig(vocab_size=len(tokenizer.get_vocab()),max_position_embeddings=2048,hidden_size=4096,intermediate_size=16384,num_hidden_layers=3,pad_token_id=tokenizer.pad_token_id,)model = CustomForCausalLM(config)print(f"Model size is {get_model_size(model)}")dataset = Dataset.load_from_disk("nlp_datas/cached")data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)args = TrainingArgs(output_dir="result",gradient_checkpointing=True,batch_size=4,logging_steps=50,warm_up_ratio=0.03,epochs=1,gradient_accumulation_steps=8,lr=1e-3,weight_decay=1e-5,)train(model=model, args=args, dataset=dataset, data_collator=data_collator)

结语

至此我们成功完成了模型训练,为其注入了先验知识。现在它拥有各种工具,但是无法进行使用,后面我们进行 sft 教模型如何使用这些工具。

如何学习AI大模型?

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

在这里插入图片描述

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

在这里插入图片描述

👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。

在这里插入图片描述

1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集

👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/35147.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

入门篇:创建和运行Hello World

DevEco Studio安装完成后,可以通过运行Hello World工程来验证环境设置是否正确。接下来以创建一个支持Phone设备的工程为例进行介绍。 创建一个新工程 打开DevEco Studio,在欢迎页单击Create Project,创建一个新工程。根据工程创建向导&…

硬件开发笔记(二十二):AD21软件中创建元器件AXK5F80337YG原理图库、封装库和3D模型

若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/140007117 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV…

EE trade:贵金属投资的优点及缺点

贵金属(如黄金、白银、铂金和钯金)一直以来都是重要的投资和避险工具。它们具有独特的物理和化学特性,广泛应用于各种行业,同时也被视为财富储备。在进行贵金属投资时,了解其优点和缺点对于做出明智的投资决策至关重要。 一、贵金属投资的优…

240万亿巨量数据被洗出,足够训出18个GPT-4!全球23所机构联手,清洗秘籍公开

是时候把数据Scale Down了!Llama 3揭示了这个可怕的事实:数据量从2T增加到15T,就能大力出奇迹,所以要想要有GPT-3到GPT-4的提升,下一代模型至少还要150T的数据。好在,最近有团队从CommonCrawl里洗出了240T数…

算法刷题笔记--二叉树篇

感觉树这一章还是没搞清楚,可能是基础不扎实的缘故,学完C巩固底层知识后二刷 理论基础 确定递归函数的参数和返回值 :确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么…

第二证券:美股市场新结算制度:T+2还是T+1?

美股商场新结算制度:T1结算。 从2024年5月28日开端,美国股票生意的结算周期将从之前的T2(生意日后两天)缩短为T1,即投资者当天卖出的股票,在生意后一个工作日就能收到结算的资金。 例如,假如生…

OpenFast软件中5MW_Land_DLL_WTurb.fst文件解读

这个文件5MW_Land_DLL_WTurb.fst是OpenFAST软件用于模拟NREL 5.0 MW基准陆上风力涡轮机的输入文件。该文件包含了多个部分,每个部分定义了不同的仿真设置和参数。以下是对文件主要内容的总结: 1. 标题和描述 文件标题说明这是OpenFAST的一个输入文件&a…

电脑的D盘E盘F盘突然消失了 电脑只剩下C盘了其他盘怎么恢复

现如今随着时代的发展,无纸化办公成为主流,这主要归功于电脑,能够通过电脑完成的工作绝不使用纸质文件,这不仅提高了工作效率,也让一些繁杂的工作变的更加简单。不过电脑毕竟是电子产品,不可避免的会出现一…

Java日志 - JUL

一、JUL学习总结 (1)总结 JDK自带的日志系统中已经为我们创建了一个顶层的RootLogger,可以针对这个顶层的RootLogger设置多个Handler(如ConsoleHandler, FileHandler等),如果想在控制台输出debug级别以上的…

定时推送邮件如何与自动化工作流程相结合?

定时推送邮件如何设置?怎么优化推送邮件的发送频率? 在现代商业环境中,自动化工作流程和定时推送邮件是提高效率和优化运营的重要工具。AoKSend将探讨如何将这两者结合起来,以实现更高效的工作流程和更好的客户沟通。 定时推送邮…

昇思25天学习打卡营第4天|MindSpore快速入门-FCN图像语义分割

FCN图像语义分割 全卷积网络(Fully Convolutional Networks,FCN)是UC Berkeley的Jonathan Long等人于2015年在Fully Convolutional Networks for Semantic Segmentation[1]一文中提出的用于图像语义分割的一种框架。 FCN是首个端到端&#…

valgrind调试c/c++内存问题:非法地址访问_内存泄漏_越界访问

1.valgrind命令 调试内存问题: valgrind --leak-checkfull 更新详细的显示: valgrind --leak-checkfull --show-leak-kindsall valgrind提示信息汇总 内存泄漏 lost in loss record 丢失记录 , 内存泄漏实例[[#2.内存泄漏–不完全释放内存|实例链接]]段错误 Process termina…

科技助力行政执法:4G无线网络技术在管理指挥中心的应用

随着科技的飞速发展,4G无线网络技术已经越来越成熟,为行政执法管理带来了前所未有的便利与效率。特别是在管理指挥中心,通过实时观看高清现场画面,执法人员可以随时进行调度指挥,掌握行政执法队伍的全过程,…

Bev系列算法总结

文章目录 1. LSS-Based1.1 BevDet1.2 BevDepth1.3 BevStereo1.4 SoloFusion1.4 VideoBev1.5 总结2. Bev IPM Based(3D to 2D)2.1 Bevformer v12.1 Bevformer v22. sparse query2.1 petr v12.2 petr v22.3 stream petr2.4 DETR 3d2.5 sparse4Dsparse4D v11. LSS-Based 1.1 Be…

llamafactory-llama3微调中文数据集

一、定义 https://github.com/SmartFlowAI/Llama3-Tutorial/tree/main 基准模型测试opencompass 离线测评数据准备微调训练合并测试人工审核对比 二、实现 基准模型测试 基准模型 llama3-8b https://zhuanlan.zhihu.com/p/694818596? https://github.com/SmartFlowAI/Llam…

品牌窜货治理:维护市场秩序与品牌健康的关键

品牌在各个渠道通常都会设定相应的销售规则,其中常见的便是区域保护制度,比如 A 地区的货物只能在 A 地区销售,各区域的产品价格和销售策略均有所不同,因此 A 地区的货物不能流向 B 地区,否则就被称为窜货。 窜货现象不…

劳易测应用案例:橡胶密炼生产线安全改造项目(下)

橡胶密炼是汽车轮胎制造流程中的核心环节,主要负责将橡胶与多种添加剂混合,确保均匀分散,以制备合格的橡胶材料。橡胶密炼生产线由多个关键设备组成,包括切胶机、导切机、称重和输送系统、密炼机、开炼机以及胶片冷却机等&#xf…

匠心铸就服务品质,全视通技术服务获盘锦市中医医院高度认可

一声表扬,万分肯定 寥寥数语,情意深重 承载着荣誉 道出了心声 传达了谢意 倾注了期盼 字里行间的内容 是对全视通技术服务的高度认可 记录了全视通与盘锦市中医医院之间的双向奔赴 盘锦市中医医院表扬信是对全视通技术服务团队工作的高度认可&am…

Xilinx FPGA:vivado实现串口的接收端

补充一些串口里用到的数值的相关知识点 接收端串口时序图: 程序设计: timescale 1ns / 1ps /串口接收端 串行转并行 module uart_rx(input sys_clk ,input rst_n ,input rx_data , //输入…

【C++】相机标定源码笔记-通用工具函数类

提供了一系列工具函数及处理方法&#xff0c;主要用于图像处理、点云处理和文件操作等领域。以下是对关键函数的简要解析&#xff1a; 点云处理与平面拟合 包含两个重载函数&#xff0c;一个接受Eigen矩阵类型的点集&#xff0c;另一个接受pcl::PointCloud<pcl::PointXYZ>…