docs:第六章 大模型训练流程实践 图片格式 参考格式修改

This commit is contained in:
KMnO4-zx
2025-05-13 20:42:51 +08:00
parent 7127aa48b3
commit c16ee23323
3 changed files with 68 additions and 38 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

View File

@@ -8,33 +8,45 @@
- 从零实现的 LLM 训练无法较好地实现多卡分布式训练,训练效率较低;
- 和现有预训练 LLM 不兼容,无法使用预训练好的模型参数
因此,在本章中,我们将介绍目前 LLM 领域的主流训练框架 transformers并结合分布式框架 deepspeed、高效微调框架 peft 等主流框架,实践使用 transformers 进行模型 Pretrain、SFT 全流程,更好地对接业界的主流 LLM 技术方案。
因此,在本章中,我们将介绍目前 LLM 领域的主流训练框架 Transformers并结合分布式框架 deepspeed、高效微调框架 peft 等主流框架,实践使用 transformers 进行模型 Pretrain、SFT 全流程,更好地对接业界的主流 LLM 技术方案。
### 6.1.1 框架介绍
transformers 是由 Hugging Face 开发的 NLP 框架,通过模块化设计实现了对 BERT、GPT、LLaMA、T5、ViT 等上百种主流模型架构的统一支持。通过使用 transformers开发者无需重复实现基础网络结构通过 AutoModel 类即可一键加载任意预训练
Transformers 是由 Hugging Face 开发的 NLP 框架,通过模块化设计实现了对 BERT、GPT、LLaMA、T5、ViT 等上百种主流模型架构的统一支持。通过使用 Transformers开发者无需重复实现基础网络结构通过 AutoModel 类即可一键加载任意预训练图6.1 为 Hugging Face Transformers 课程首页:
![transformers](./images/1-1.png)
<div align='center'>
<img src="./images/1-1.png" alt="alt text" width="90%">
<p>图6.1 Hugging Face Transformers</p>
</div>
同时,框架内置的 Trainer 类封装了分布式训练的核心逻辑,支持 PyTorch 原生 DDP、DeepSpeed、Megatron-LM 等多种分布式训练策略。通过简单配置训练参数,即可实现数据并行、模型并行、流水线并行的混合并行训练,在 8 卡 A100 集群上可轻松支持百亿参数模型的高效训练。配合SavingPolicyLoggingCallback等组件实现了训练过程的自动化管理。其还支持与 Deepspeed、peft、wandb 等框架进行集成,直接通过参数设置即可无缝对接,从而快速、高效实现 LLM 训练。
同时,框架内置的 Trainer 类封装了分布式训练的核心逻辑,支持 PyTorch 原生 DDP、DeepSpeed、Megatron-LM 等多种分布式训练策略。通过简单配置训练参数,即可实现数据并行、模型并行、流水线并行的混合并行训练,在 8 卡 A100 集群上可轻松支持百亿参数模型的高效训练。配合 SavingPolicyLoggingCallback 等组件,实现了训练过程的自动化管理。其还支持与 Deepspeed、peft、wandb、Swanlab 等框架进行集成,直接通过参数设置即可无缝对接,从而快速、高效实现 LLM 训练。
对 LLM 时代的 NLP 研究者更为重要的是HuggingFace 基于 transformers 框架搭建了其庞大的 AI 社区开放了数亿个预训练模型参数、25万+不同类型数据集,通过 transformers、dataset、evaluate 等多个框架实现对预训练模型、数据集及评估函数的集成,从而帮助开发者可以便捷地使用任一预训练模型,在开源模型及数据集的基础上便捷地实现个人模型的开发与应用。
对 LLM 时代的 NLP 研究者更为重要的是HuggingFace 基于 Transformers 框架搭建了其庞大的 AI 社区开放了数亿个预训练模型参数、25万+不同类型数据集,通过 Transformers、Dataset、Evaluate 等多个框架实现对预训练模型、数据集及评估函数的集成,从而帮助开发者可以便捷地使用任一预训练模型,在开源模型及数据集的基础上便捷地实现个人模型的开发与应用。
![](./images/1-2.png)
<div align='center'>
<img src="./images/1-2.png" alt="alt text" width="90%">
<p>图6.2 Hugging Face Transformers 模型社区</p>
</div>
在 LLM 时代,模型结构的调整和重新预训练越来越少,开发者更多的业务应用在于使用预训练好的 LLM 进行 Post Train 和 SFT来支持自己的下游业务应用。且由于预训练模型体量大便捷集成 deepspeed 等分布式训练框架逐渐成为 LLM 时代 NLP 模型训练的必备技能。因此,transformers 已逐步成为学界、业界 NLP 技术的主流框架,不管是企业业务开发还是科研研究,都逐渐首选 transformers 进行模型实现。同时,新发布的开源 LLM 如 DeepSeek、Qwen 也都会第一时间在 transformers 社区开放其预训练权重与模型调用 Demo。通过使用 transformers 框架,可以高效、便捷地完成 LLM 训练及开发,实现工业级的产出交付。接下来,我们就会以 transformers 框架为基础,介绍如何通过 transformers 框架实现 LLM 的 Pretrain 及 SFT。
在 LLM 时代,模型结构的调整和重新预训练越来越少,开发者更多的业务应用在于使用预训练好的 LLM 进行 Post Train 和 SFT来支持自己的下游业务应用。且由于预训练模型体量大便捷集成 deepspeed 等分布式训练框架逐渐成为 LLM 时代 NLP 模型训练的必备技能。因此,Transformers 已逐步成为学界、业界 NLP 技术的主流框架,不管是企业业务开发还是科研研究,都逐渐首选 Transformers 进行模型实现。同时,新发布的开源 LLM 如 DeepSeek、Qwen 也都会第一时间在 Transformers 社区开放其预训练权重与模型调用 Demo。通过使用 Transformers 框架,可以高效、便捷地完成 LLM 训练及开发,实现工业级的产出交付。接下来,我们就会以 Transformers 框架为基础,介绍如何通过 Transformers 框架实现 LLM 的 Pretrain 及 SFT。
### 6.1.2 初始化 LLM
我们可以使用 transformers 的 AutoModel 类来直接初始化已经实现好的模型。对于任意预训练模型,其参数中都包含有模型的配置信息。如果是想要从头训练一个 LLM可以使用一个已有的模型架构来直接初始化。这里我们以 Qwen-2.5-1.5B https://huggingface.co/Qwen/Qwen2.5-1.5B/tree/main的模型架构为例:
我们可以使用 transformers 的 AutoModel 类来直接初始化已经实现好的模型。对于任意预训练模型,其参数中都包含有模型的配置信息。如果是想要从头训练一个 LLM可以使用一个已有的模型架构来直接初始化。这里我们以 [Qwen-2.5-1.5B](https://huggingface.co/Qwen/Qwen2.5-1.5B/tree/main)的模型架构为例:
![](./images/1-3.png)
<div align='center'>
<img src="./images/1-3.png" alt="alt text" width="90%">
<p>图6.3 Qwen-2.5-1.5B</p>
</div>
该界面即为 HuggingFace 社区中的 Qwen-2.5-1.5B 模型参数,其中的 config.json 文件即是模型的配置信息,包括了模型的架构、隐藏层大小、模型层数等:
该界面即为 HuggingFace 社区中的 Qwen-2.5-1.5B 模型参数,其中的 `config.json` 文件即是模型的配置信息,包括了模型的架构、隐藏层大小、模型层数等如图6.4所示
![](./images/1-4.png)
<div align='center'>
<img src="./images/1-4.png" alt="alt text" width="90%">
<p>图6.4 Qwen-2.5-1.5B config.json 文件</p>
</div>
我们可以沿用该模型的配置信息,初始化一个 Qwen-2.5-1.5B 模型来进行训练也可以在该配置信息的基础上进行更改如修改隐藏层大小、注意力头数等来定制一个模型结构。HuggingFace 提供了 python 工具来便捷下载想使用的模型参数:
我们可以沿用该模型的配置信息,初始化一个 Qwen-2.5-1.5B 模型来进行训练也可以在该配置信息的基础上进行更改如修改隐藏层大小、注意力头数等来定制一个模型结构。HuggingFace 提供了 Python 工具来便捷下载想使用的模型参数:
```python
import os
@@ -44,9 +56,12 @@ os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
os.system('huggingface-cli download --resume-download Qwen/Qwen2.5-1.5B --local-dir your_local_dir')
```
此处的 “Qwen/Qwen2.5-1.5B”即为要下载模型的标识符,对于其他模型,可以直接复制 HuggingFace 上的模型名即可:
如图6.5此处的 “Qwen/Qwen2.5-1.5B”即为要下载模型的标识符,对于其他模型,可以直接复制 HuggingFace 上的模型名即可:
![](./images/1-5.png)
<div align='center'>
<img src="./images/1-5.png" alt="alt text" width="90%">
<p>图6.5 模型下载标识</p>
</div>
下载完成后,可以使用 AutoConfig 类直接加载下载好的配置文件:
@@ -69,11 +84,14 @@ from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_config(config,trust_remote_code=True)
```
由于 LLM 一般都是 CausalLM 架构,此处使用了 AutoModelForCausalLM 类进行加载。如果是用于分类任务训练,可使用 AutoModelForSequenceClassification 类来加载。查看该 model可以看到其架构和定义的配置文件相同
由于 LLM 一般都是 CausalLM 架构,此处使用了 AutoModelForCausalLM 类进行加载。如果是用于分类任务训练,可使用 AutoModelForSequenceClassification 类来加载。查看该 model图6.6可以看到其架构和定义的配置文件相同:
![](./images/1-6.png)
<div align='center'>
<img src="./images/1-6.png" alt="alt text" width="70%">
<p>图6.6 模型结构输出结果</p>
</div>
该 model 就是一个从零初始化的 Qwen-2.5-1.5B 模型了。一般情况下,我们很少从零初始化 LLM 进行预训练,较多的做法是继承一个预训练好的 LLM ,在自己的语料上进行后训练。这里,我们也介绍如何从下载好的模型参数中初始化一个预训练好的模型。
该 model 就是一个从零初始化的 Qwen-2.5-1.5B 模型了。一般情况下,我们很少从零初始化 LLM 进行预训练,较多的做法是加载一个预训练好的 LLM 权重,在自己的语料上进行后训练。这里,我们也介绍如何从下载好的模型参数中初始化一个预训练好的模型。
```python
from transformers import AutoModelForCausalLM
@@ -105,13 +123,16 @@ from datasets import load_dataset
ds = load_dataset('json', data_files='/mobvoi_seq_monkey_general_open_corpus.jsonl')
```
注意,由于数据集较大,加载可能会出现时间较长或内存不够的情况,建议前期测试时将预训练数据集拆分一部分出来进行测试。加载出来的 ds 是一个 DatasetDict 对象,加载的数据会默认保存在 train 键对应的值中,可以通过以下代码查看:
注意,由于数据集较大,加载可能会出现时间较长或内存不够的情况,建议前期测试时将预训练数据集拆分一部分出来进行测试。加载出来的 ds 是一个 DatasetDict 对象,加载的数据会默认保存在 `train` 键对应的值中,可以通过以下代码查看:
```python
ds["train"][0]
```
![](./images/1-7.png)
<div align='center'>
<img src="./images/1-7.png" alt="alt text" width="100%">
<p>图6.7 数据集展示</p>
</div>
可以通过 feature 属性查看数据集的特征(也就是列),这里需要保存一下数据集的列名,因为后续数据处理时,再将文本 tokenize 之后,需要移除原先的文本:
@@ -229,13 +250,13 @@ trainer = Trainer(
trainer.train()
```
注:上述代码存放于 ./code/pretrian.ipynb 文件中。
> 注:上述代码存放于 `./code/pretrian.ipynb` 文件中。
### 6.1.5 使用 DeepSpeed 实现分布式训练
由于预训练规模大、时间长,一般不推荐使用 Jupyter Notebook 来运行,容易发生中断。且由于预训练规模大,一般需要使用多卡进行分布式训练,否则训练时间太长。在这里,我们介绍如何基于上述代码,使用 DeepSpeed 框架实现分布式训练,从而完成业界可用的 LLM Pretrain。
长时间训练一般使用 sh 脚本设定超参,再启动写好的 python 脚本实现训练。我们使用一个 python 脚本(./code/pretrain.py来实现训练全流程。
长时间训练一般使用 bash 脚本设定超参,再启动写好的 python 脚本实现训练。我们使用一个 Python 脚本(`./code/pretrain.py`)来实现训练全流程。
先导入所需第三方库:
@@ -444,7 +465,7 @@ wandb.init(project="pretrain", name="from_scrach")
在启动训练后,终端会输出 wandb 监测的 url点击即可观察训练进度。此处不再赘述 wandb 的使用细节,欢迎读者查阅相关的资料说明。
完成上述代码后,我们使用一个 sh 脚本(./code/pretrain.sh定义超参数的值并通过 deepspeed 启动训练,从而实现高效的多卡分布式训练:
完成上述代码后,我们使用一个 sh 脚本(`./code/pretrain.sh`)定义超参数的值,并通过 Deepspeed 启动训练,从而实现高效的多卡分布式训练:
```bash
# 设置可见显卡
@@ -477,7 +498,7 @@ deepspeed pretrain.py \
--report_to wandb
# --resume_from_checkpoint ${output_model}/checkpoint-20400 \
```
在安装了 deepspeed 第三方库后,可以直接通过 deepspeed 命令来启动多卡训练。上述脚本命令主要是定义了各种超参数的值,可参考使用。在第四章中,我们介绍了 DeepSpeed 分布式训练的原理和 ZeRO 阶段设置,在这里,我们使用 ZeRO-2 进行训练。此处加载了 ds_config_zero.json 作为 DeepSpeed 的配置参数:
在安装了 Deepspeed 第三方库后,可以直接通过 Deepspeed 命令来启动多卡训练。上述脚本命令主要是定义了各种超参数的值,可参考使用。在第四章中,我们介绍了 DeepSpeed 分布式训练的原理和 ZeRO 阶段设置,在这里,我们使用 ZeRO-2 进行训练。此处加载了 `ds_config_zero.json` 作为 DeepSpeed 的配置参数:
```json
{
@@ -534,17 +555,17 @@ deepspeed pretrain.py \
}
```
最后,在终端 sh 运行该 pretrain.sh 脚本即可开始训练。
最后,在终端 bash 运行该 `pretrain.sh` 脚本即可开始训练。
## 6.2 模型有监督微调
在上一节,我们介绍了如何使用 transformers 框架快速、高效地进行模型预训练。在本部分,我们将基于上部分内容,介绍如何使用 transformers 框架对预训练好的模型进行有监督微调。
在上一节,我们介绍了如何使用 Transformers 框架快速、高效地进行模型预训练。在本部分,我们将基于上部分内容,介绍如何使用 Transformers 框架对预训练好的模型进行有监督微调。
### 6.2.1 Pretrain VS SFT
首先需要回顾一下,对 LLM 进行预训练和进行有监督微调的核心差异在于什么。在第四章中提到过,目前成型的 LLM 一般通过 Pretrain-SFT-RLHF 三个阶段来训练,在 Pretrain 阶段,会对海量无监督文本进行自监督建模,来学习文本语义规则和文本中的世界知识;在 SFT 阶段,一般通过对 Pretrain 好的模型进行指令微调即训练模型根据用户指令完成对应任务从而使模型能够遵循用户指令根据用户指令进行规划、行动和输出。因此Pretrain 和 SFT 均使用 CLM 建模其核心差异在于Pretrain 使用海量无监督文本进行训练,模型直接对文本执行“预测下一个 token”的任务而 SFT 使用构建成对的指令对数据模型根据输入的指令建模后续的输出。反映到具体的训练实现上Pretrain 会对全部 text 进行 loss 计算,要求模型对整个文本实现建模预测;而 SFT 仅对输出进行 loss 计算,不计算指令部分的 loss。
因此,相较于上一节完成的 Pretrain 代码SFT 部分仅需要修改数据处理环节,实现对指令对数据转化为训练样本的构建,其余部分和 Pretrain 是完全一致的实现逻辑。本部分代码脚本为 ./code/finetune.py。
因此,相较于上一节完成的 Pretrain 代码SFT 部分仅需要修改数据处理环节,实现对指令对数据转化为训练样本的构建,其余部分和 Pretrain 是完全一致的实现逻辑。本部分代码脚本为`./code/finetune.py`
### 6.2.2 微调数据处理
@@ -622,7 +643,7 @@ for i in tqdm(range(len(sources))):
```
上述代码沿承了 Qwen 的 Chat Template 逻辑,读者也可以根据自己的偏好进行修改,其核心点在于 User 的文本不需要拟合,因此 targets 中 User 对应的文本内容是使用的 IGNORE_TOKEN_ID 进行遮蔽,而 Assistant 对应的文本内容则是文本原文,是需要计算 loss 的。目前主流 LLM IGNORE_TOKEN_ID 一般设置为 -100。
完成拼接后,将 tokenize 后的数值序列转化为 tensor再拼接成 Dataset 所需的字典返回即可:
完成拼接后,将 tokenize 后的数值序列转化为 `Torch.tensor`,再拼接成 Dataset 所需的字典返回即可:
```python
input_ids = torch.tensor(input_ids)
@@ -662,7 +683,7 @@ class SupervisedDataset(Dataset):
)
```
该类继承自 torch 的 Dataset 类,可以直接在 Trainer 中使用。完成数据处理后,基于上一节脚本,修改数据处理逻辑即可,后续模型训练等几乎完全一致,此处附上主函数逻辑:
该类继承自 Torch 的 Dataset 类,可以直接在 Trainer 中使用。完成数据处理后,基于上一节脚本,修改数据处理逻辑即可,后续模型训练等几乎完全一致,此处附上主函数逻辑:
```python
# 加载脚本参数
@@ -756,7 +777,7 @@ trainer.save_model()
## 6.3 高效微调
在前面几节,我们详细介绍了基于 transformers 框架对模型进行 Pretrain、SFT 以及 RLHF 的原理和实践细节。但是,由于 LLM 参数量大,训练数据多,通过上述方式对模型进行训练(主要指 SFT 及 RLHF需要调整模型全部参数资源压力非常大。对资源有限的企业或课题组来说如何高效、快速对模型进行领域或任务的微调以低成本地使用 LLM 完成目标任务,是非常重要的。
在前面几节,我们详细介绍了基于 Transformers 框架对模型进行 Pretrain、SFT 以及 RLHF 的原理和实践细节。但是,由于 LLM 参数量大,训练数据多,通过上述方式对模型进行训练(主要指 SFT 及 RLHF需要调整模型全部参数资源压力非常大。对资源有限的企业或课题组来说如何高效、快速对模型进行领域或任务的微调以低成本地使用 LLM 完成目标任务,是非常重要的。
### 6.3.1 高效微调方案
@@ -764,9 +785,12 @@ trainer.save_model()
**Adapt Tuning**。即在模型中添加 Adapter 层,在微调时冻结原参数,仅更新 Adapter 层。
具体而言,其在预训练模型每层中插入用于下游任务的参数,即 Adapter 模块,在微调时冻结模型主体,仅训练特定于任务的参数。
具体而言,其在预训练模型每层中插入用于下游任务的参数,即 Adapter 模块,在微调时冻结模型主体,仅训练特定于任务的参数如图6.8所示
![Adapter](./images/3-1.jpg)
<div align='center'>
<img src="./images/3-1.png" alt="alt text" width="90%">
<p>图6.8 Adapt Tuning</p>
</div>
每个 Adapter 模块由两个前馈子层组成,第一个前馈子层将 Transformer 块的输出作为输入,将原始输入维度 $d$ 投影到 $m$,通过控制 $m$ 的大小来限制 Adapter 模块的参数量,通常情况下 $m << d$。在输出阶段,通过第二个前馈子层还原输入维度,将 $m$ 重新投影到 $d$,作为 Adapter 模块的输出(如上图右侧结构)。
@@ -813,9 +837,12 @@ $$h = W_0 x + \Delta W x = W_0 x + B A x$$
在开始训练时,对 $A$ 使用随机高斯初始化,对 $B$ 使用零初始化,然后使用 Adam 进行优化。
训练思路如图:
训练思路如图6.9所示
![LoRA](./images/3-2.png)
<div align='center'>
<img src="./images/3-2.jpg" alt="alt text" width="90%">
<p>图6.9 LoRA</p>
</div>
#### 2应用于 Transformer
@@ -1016,11 +1043,14 @@ trainer = Trainer(
trainer.train()
```
如果是应用在 DPO、KTO 上,则也相同的加入 LoRA 参数并通过 get_peft_model 获取一个 LoRA 模型即可其他的不需要进行任何修改。但要注意的是LoRA 微调能够大幅度降低显卡占用且在下游任务适配上能够取得较好的效果但如果是需要学习对应知识的任务LoRA 由于只调整低秩矩阵,难以实现知识的注入,一般效果不佳,因此不推荐使用 LoRA 进行模型预训练或后训练。
如果是应用在 DPO、KTO 上,则也相同的加入 LoRA 参数并通过 `get_peft_model` 获取一个 LoRA 模型即可其他的不需要进行任何修改。但要注意的是LoRA 微调能够大幅度降低显卡占用且在下游任务适配上能够取得较好的效果但如果是需要学习对应知识的任务LoRA 由于只调整低秩矩阵,难以实现知识的注入,一般效果不佳,因此不推荐使用 LoRA 进行模型预训练或后训练。
**参考资料**
1. [LoRA: Low-rank Adaptation of Large Language Models](https://arxiv.org/pdf/2106.09685.pdf)
2. [Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning](https://arxiv.org/abs/2012.13255)
3. [Parameter-Efficient Transfer Learning for NLP](http://proceedings.mlr.press/v97/houlsby19a/houlsby19a.pdf)
4. [Prefix-Tuning: Optimizing Continuous Prompts for Generation](https://arxiv.org/abs/2101.00190)
[1] Neil Houlsby, Andrei Giurgiu, Stanislaw Jastrzebski, Bruna Morrone, Quentin de Laroussilhe, Andrea Gesmundo, Mona Attariyan, and Sylvain Gelly. (2019). *Parameter-Efficient Transfer Learning for NLP.* arXiv preprint arXiv:1902.00751.
[2] Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, and Weizhu Chen. (2021). *LoRA: Low-Rank Adaptation of Large Language Models.* arXiv preprint arXiv:2106.09685.
[3] Armen Aghajanyan, Luke Zettlemoyer, and Sonal Gupta. (2020). *Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning.* arXiv preprint arXiv:2012.13255.
[4] Xiang Lisa Li 和 Percy Liang. (2021). *Prefix-Tuning: Optimizing Continuous Prompts for Generation.* arXiv preprint arXiv:2101.00190.