在LLM诞生之前,预训练模型在各个自然语言任务中备受关注,而随着LLM的强大性能,预训练模型逐渐被替代,本文主要介绍预训练模型和大模型以及相关的微调知识。
预训练模型
Encoder-only PLM
BERT是典型的Encoder-Only的预训练模型,由Google在18年10月份发布。自 BERT 推出以来,预训练+微调的模式开始成为自然语言处理任务的主流,其核心在于,通过将预训练和微调分离,通过大量的无监督数据(为啥是无监督数据?大量数据,注定无法通过人工标注达到这个规模)帮助模型获取强大的语和生成能力,这样完成一次预训练的模型可以仅通过微调应用于基本所有的下游任务,而微调的成本相较于预训练低很多。
预训练
BERT由Embedding层、若干个Transformer的Encoder组合和线性层组合而成。相较于基本沿承 Transformer 的模型架构,BERT更大的创新点在于其提出的两个新的预训练任务上——MLM和NSP:
- MLM:
- 在自然语言处理任务中,掩码是一种常见的技术,通过随机将输入序列的部分token遮蔽,BERT模型能够学习被遮蔽token的上下文信息,从而预测被遮蔽的token,通过这样的任务,模型可以拟合双向语义。具体而言,BERT 模型在预训练阶段将给定的输入序列以 15% 的概率随机遮蔽部分token,再预测被遮蔽token是什么。
- 由于被遮蔽的单词,即[MASK]在下游任务的微调阶段不会出现,这导致 BERT 模型在预训练阶段和微调阶段之间存在不匹配。因此BERT模型对于以15%概率随机选择的token采用以下策略:80%的token用[MASK]遮蔽,10%的token用随机的token替换,10%的token保持不变,这部分保持不变是为了消除预训练和微调的不一致,而10%的随机替换在于迫使模型保持对上下文信息的学习。通过引入部分随机 token,模型无法确定需要预测的 token,从而被迫保持每一个 token 的上下文表征分布,从而具备了对句子的特征表示能力。且由于随机 token 的概率很低,其并不会影响模型实质的语言理解能力。
输入:I [MASK] you because you are [MASK]
输出:[MASK] - love; [MASK] - wonderful
可以看出MLM任务的训练数据可以从海量的无监督数据中选择,无需人工标注。
- NSP:
- NSP任务预测两个句子是否存在顺序关系,用于需要理解两个句子间关系的自然语言任务,例如问答和推理任务等。具体而言,首先,该任务从语料库中随机选择若干句子组成句子对,50% 的句子对中句子是相关的,50% 的句子对中的第二个句子和第一个句子不相关;其次,将句子对中第一个句子插入 [CLS] 标记,在每个句子末尾插入 [SEP] 标记;最后,将句子对输入二分类分类器中预测是否相关。
- 涉及到句子是否存在顺序关系,因此需要构建正负样本。正样本:连续的句子对;负样本:非连续的句子对。均可从无监督语料中获取,正样本随机抽取连续的句子对,负样本只需对句子对打乱即可。
当然NSP任务并非所有的预训练模型均采用,RoBERTa认为NSP任务并不能提高模型性能,甚至可能带来负面效果,因此去除了NSP任务。
下游任务微调
模型预训练是在大量无监督数据上进行的,应用到实际任务上,还需要一定的微调,即根据有监督数据进行训练。
Decoder-only PLM
目前大火的LLM架构基本都是Decoder-only模型,Decoder结构天生适用于文本生成任务,而引发LLM热潮的ChatGPT,正是Decoder-only系列的代表模型 GPT 系列模型的大成之作。
预训练任务
Decoder-only模型往往选择了最传统也最直接的预训练任务——因果语言模型,Casual Language Model,简称CLM。 CLM基于一个自然语言序列的前面所有token来预测下一个token,通过不断重复该过程来实现目标文本序列的生成。CLM相比较于MLM,和下游任务直接匹配,可以在各种NLP任务上直接应用。
Encode-Decoder PLM
T5(Text-To-Text Transformer)是由Google提出的一种预训练语言模型,通过将所有NLP任务统一表示为文本到文本的转换问题,大大简化了模型设计和任务处理。T5基于Transformer架构,包含了编码器和解码器,使用自注意力机制和多头注意力捕捉全局依赖关系,利用相对位置编码处理长序列中的位置信息。
大模型训练方法
一般而言,一个完整的LLM训练过程,包括模型预训练(Pretrain)、指令微调(Instruction Tuning)和强化学习(RLHF)等环节。
此图摘自happy-llm。
预训练
大模型预训练和传统预训练模型类似(如BERT),同样是将海量无监督样本(无监督,指未曾标注)喂给大模型,使其学习人类庞大的知识和基本的语言推理能力。
传统预训练模型: 如BERT,有 base 和 large 两个版本。BERT-base 模型由 12个 Encoder 层组成,其 hidden_size 为 768,使用 12个头作为多头注意力层,整体参数量为 1亿(110M);而 BERT-large 模型由 24个 Encoder 层组成,hidden_size 为 1024,有 16个头,整体参数量为 3亿(340M)。同时,BERT 预训练使用了 33亿(3B)token 的语料,在 64块 TPU 上训练了 4天。
大模型: 通常具有数百亿甚至上千亿参数,即使是广义上最小的 LLM,一般也有十亿(1B)以上的参数量。例如以开山之作 GPT-3 为例,其有 96个 Decoder 层,12288 的 hidden_size 和 96个头,共有 1750亿(175B)参数,比 BERT 大出快 3个数量级。即使是目前流行的小型 LLM 如 Qwen-1.8B,其也有 24个 Decoder 层、2048的 hidden_size 和 16个注意力头,整体参数量达到 18亿(1.8B)。
| 模型 | hidden_layers | hidden_size | heads | 整体参数量 | 预训练数据量 |
|---|---|---|---|---|---|
| BERT-base | 12 | 768 | 12 | 0.1B | 3B |
| BERT-large | 24 | 1024 | 16 | 0.3B | 3B |
| Qwen-1.8B | 24 | 2048 | 16 | 1.8B | 2.2T |
| LLaMA-7B | 32 | 4096 | 32 | 7B | 1T |
| GPT-3 | 96 | 12288 | 96 | 175B | 300B |
由于大规模训练所需要的算力,大多数公司很难有这样的财力和算力来进行预训练。但其实,对于开发人员来说,也无需特别关注预训练,我们只需要基于通用的大模型,在其上微调即可,成本相对小很多,收益也会不错。
指令微调(SFT)
预训练是大模型强大的根本,但由于预训练任务的本质在于【续写】,而续写的方式并不一定能够能很好的回答用户问题,例如:
| 用户问题 | 用户预期回答 | 模型续写结果 |
|---|---|---|
| 《无间道》的主演有哪些? | 刘德华、梁朝伟 | 《无间道》的主演有哪些?不少观众期待看到阵容公告,今天小编... |
因为训练大多来自互联网中的数据,我们无法保证数据中只存在存在规范的「一问一答」格式,
这就会造成预训练模型通常无法直接给出人们想要的答案。
但是,这并不代表预训练模型「无知」,只是需要我们用一些更巧妙的「技巧」来引导出答案,即限制的提示词工程:
| 用户问题 | 用户预期回答 | 模型续写结果 |
|---|---|---|
| 《无间道》的主演有 | 刘德华、梁朝伟 | 《无间道》的主演有刘德华、梁朝伟和黄秋生,而这部电影也是香港警匪片的代表作之一。 |
不过,这种需要用户精心设计从而去「套」答案的方式,显然没有那么优雅。为了更好的教会LLM如何回答用户问题,我们通过指令微调的方式来训练模型,即我们输入各种类型的用户指令,而模型需要拟合的输出则是我们希望模型收到指令后做出的回复。
传统预训练模型: 对于能力有限的传统预训练模型,我们需要针对每一个下游任务单独对其进行微调以训练模型在该任务上的表现。例如要解决文本分类问题,我们需要基于该分类任务的训练数据,对 BERT 进行微调。
大模型: 面对能力强大的大模型,我们可以训练模型的“通用指令遵循能力”,通过指令微调的方式来进行SFT。所谓指令微调,即我们训练的输入是各种类型的用户指令(有监督数据),而需要模型拟合的输出则是我们希望模型在收到该指令后做出的回复。例如,我们的一条训练样本可以是:
input:告诉我今天的天气预报?
output:根据天气预报,今天天气是晴转多云,最高温度26摄氏度,最低温度9摄氏度,昼夜温差大,请注意保暖哦
微调方法
总的分为大两类:
- 全参数微调:需要大量数据,训练的参数过多,容易导致模型发生知识遗忘。
- 高效微调:仅训练一部分参数,训练成本较低,目前使用最广泛,可以分为以下几类: 增加额外参数微调、选择性微调和重参数微调。
增加额外参数微调
如:Prefix Tuning、Prompt Tuning、Adapter Tuning及其变体。
选择性微调
如:BitFit
重参数微调
通过对模型参数进行重新表示来实现高效微调,通常涉及低秩分解或其他形式的参数压缩,如:LoRA、AdaLoRA和QLoRA等,下面以LoRA为例。
LoRA通过低秩矩阵分解的思想,将原始的高维权重矩阵分解为两个低秩矩阵的乘积,减少权重的数量和计算复杂度。简单来说,就是在模型的线形层旁边,增加一个分支,代替原有的参数矩阵W(该参数被冻结,无需进行训练)进行训练。具体是先用一个线性层A,将数据从d维降到r,这个r就是LoRA的秩,是LoRA中最重要的一个超参数,一般r会远远小于d;接着再用第二个线性层B,将数据的维度从r转换回d,仅对这两个矩阵进行训练。最后再将原本的模型参数和这个旁支输出的结果相加进行融合,就得到了输出的hidden_state。
| LoRA | 通过低秩矩阵分解的思想,将原始的高维权重矩阵分解为两个低秩矩阵的乘积,减少权重的数量和计算复杂度。 | 降低了存储需求和计算成本。提高了模型的训练速度和推理速度。 | 可能降低模型的准确性,因为使用了低精度权重。适用性受限,主要适用于具有低秩特性的增量矩阵。 |
|---|
一般使用peft库来实现模型的LoRA微调,peft库是huggingface开发的第三方库,其中封装了包括 LoRA、Adapt Tuning、P-tuning 等多种高效微调方法,可以基于此便捷地实现模型的 LoRA 微调。
强化学习
RLHF,全称是 Reinforcement Learning from Human Feedback,即人类反馈强化学习,是利用强化学习来训练 LLM 的关键步骤。相较于在 GPT-3 就已经初见雏形的 SFT,RLHF 往往被认为是 ChatGPT 相较于 GPT-3 的最核心突破。事实上,从功能上出发,我们可以将 LLM 的训练过程分成预训练与对齐(alignment)两个阶段。预训练的核心作用是赋予模型海量的知识,而所谓对齐,其实就是让模型与人类价值观一致,从而输出人类希望其输出的内容。在这个过程中,SFT 是让 LLM 和人类的指令对齐,从而具有指令遵循能力;而 RLHF 则是从更深层次令 LLM 和人类价值观对齐,令其达到安全、有用、无害的核心标准。
RLHF 就类似于 LLM 作为一个学生,不断做作业来去提升自己解题能力的过程。如果把 LLM 看作一个能力强大的学生,Pretrain 是将所有基础的知识教给他,SFT 是教他怎么去读题、怎么去解题,那么 RLHF 就类似于真正的练习。LLM 会不断根据 Pretrain 学到的基础知识和 SFT 学到的解题能力去解答练习,然后人类作为老师批改 LLM 的练习,来让 LLM 反思错误的解题方式,不断强化正确的解题方式。
实际应用中如何调优
随着大模型被广泛应用于实际的业务场景中,我们也能发现大模型常见的问题:
- 不遵循指令:体现在返回的结果不全面、格式不符合要求,甚至有可能答非所问、回答错误等。
对于此类情况,业界有一些调优方法,如Prompt调优、模型微调以及RAG等。
下面简单介绍下这几种方法。
Prompt调优
通过观察各种bad case,调整prompt让大模型返回更优质的结果。
优点:成本低、灵活;
缺点:依赖模型本身的知识和提示词优化的能力。
模型微调
构建优质的标注数据集,让大模型将知识内化。
优点:效果更好、定制化
缺点:成本高、数据质量要求高以及模型训练所用的知识可能过时
可以直接使用Hugging face提供的transformers框架,来进行分布式的预训练和有监督微调。
微调数据集如何准备
数据集中应该包含业务领域的专用知识,以及为提升该业务场景中大模型的效果,应标注一份相关数据(格式如下),这样能够让大模型更遵循指令。
input:告诉我今天的天气预报?
output:根据天气预报,今天天气是晴转多云,最高温度26摄氏度,最低温度9摄氏度,昼夜温差大,请注意保暖哦
RAG
外挂知识库,当模型需要回答问题时,它会首先从知识库中查找并收集相关信息,然后基于这些信息回答问题。
优点:能够外挂新知识,知识库能够不断更新,与时俱进,缓解模型的幻觉;
缺点:过于依赖知识库检索能力。
如果大模型的通用知识无法满足业务需求的话,可以考虑微调。因为大模型虽然拥有海量的知识储备,但无法覆盖各行各业,如果大模型关于该业务的知识不足够的话,用再好的提示词也是浪费;而RAG知识外挂知识,知识虽然在那,但依赖于知识的检索能力,大模型最后还得根据检索的知识,给出最终结果;所以可以考虑微调,让大模型真正理解问题,内化知识。
模型微调实战案例
模型微调训练框架
本文使用Llama Factory,其旨在简化大语言模型的微调过程(不用自己去手写各种微调方法的代码),支持绝大多数的预训练模型,其特点如下:
- 支持多种微调方法:它集成了连续预训练、有监督微调(SFT)、偏好对齐(RLHF)等多种微调方 法。
- 高效的微调技术:与 ChatGLM 官方的 P-Tuning 微调相比,LLaMA Factory 的 LoRA 微调提供了 显著的加速比,并且在特定任务上取得了更高的性能分数。
- 易用性:LLaMA-Factory 提供了高层次的抽象接口,使得开发者可以开箱即用,快速上手操作。
- WebUI 支持:借鉴 Stable Diffusion WebUI,该项目提供了基于 gradio 的网页版工作台,方便初 学者可以迅速上手操作。
- 模型导出和推理:支持模型的导出和推理,包括动态合并 LoRA 模型进行推理。
- API Server:支持启动 API Server,使得训练好的模型可以通过网络接口被远程访问和调用。
- 评测 benchmark:提供了主流评测 benchmark 支持,如 mmlu、cmmlu、ceval 等,用于评估 模型的泛化能力
框架文档:llamafactory.readthedocs.io/
训练过程
准备微调数据集
这里使用LLamaFactory的自带数据集进行微调,格式如下:
这里的数据集符合SFT的格式,包含instruction(用户指令)、input(用户输入)和output(模型的回答)。在进行指令微调时,instruction和input的内容会拼接后作为最终的人类输入。
开始训练
创建项目,初始化环境
这里我选择PyTorch2.1.2版本。
GPU选择B2.large版本。
下载Llama Factory源码
在项目中上传Llama Factory源码包,github地址。然后就可以启动项目了,等待分配机器和卡资源,资源分配完成后,需要unzip这个源码压缩包。
安装依赖
# LLaMA-Factory是一个python项目,需要安装必要的依赖
# 设置为清华源,加快安装速度
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -e '.[torch,metrics]' -i https://pypi.tuna.tsinghua.edu.cn/simple
通过 llamafactory-cli version 看看是不是安装好了
执行训练命令
修改下 examples/train_lora/llama3_lora_sft.yaml这个文件,只需要修改model_name_or_path和dataset即可,以下是我的一个完整配置:
### model
model_name_or_path: /gemini/pretrain
trust_remote_code: true
### method
stage: sft
do_train: true
finetuning_type: lora
lora_rank: 8
lora_target: all
### dataset
dataset: alpaca_zh_demo
template: llama3
cutoff_len: 2048
max_samples: 1000
overwrite_cache: true
preprocessing_num_workers: 16
dataloader_num_workers: 4
### output
output_dir: saves/llama3-8b/lora/sft
logging_steps: 10
save_steps: 500
plot_loss: true
overwrite_output_dir: true
save_only_model: false
report_to: none # choices: [none, wandb, tensorboard, swanlab, mlflow]
### train
per_device_train_batch_size: 1
gradient_accumulation_steps: 8
learning_rate: 1.0e-4
num_train_epochs: 3.0
lr_scheduler_type: cosine
warmup_ratio: 0.1
bf16: true
ddp_timeout: 180000000
resume_from_checkpoint: null
### eval
# eval_dataset: alpaca_en_demo
# val_size: 0.1
# per_device_eval_batch_size: 1
# eval_strategy: steps
# eval_steps: 500
执行llamafactory-cli train examples/train_lora/llama3_lora_sft.yaml即可进行如下的微调:
GPU看起来用了18G左右。
参考资料
happy-llm:datawhalechina.github.io/happy-llm/#…
微调方法:zhuanlan.zhihu.com/p/682082440