大语言模型原理详解
LLM 原理
LLM 基本功
大模型基本功有哪些?
- transformer、rope、swiglu、rms_norm ?
- flash_attention 的原理?
- megatron 的各种 parallel 策略?
- 量化和推理加速技术?
- cuda编程?
megatron 通用代码
开源社区,llama 网络结构已经一统江湖, 读懂 modeling_llama.py
:
- 加载模型时没有 skip_build ;
- 缺少 stream_generate;
- 文件不支持 sequence_parallel ;
- 默认使用 flash_attention;
- 并没有一个可以作为 reward_model 的 lm_head;
收集 modeling_llama.py
、 modeling_qwen.py
、 modeling_baichuan.py
、 modeling_yi.py
、 modeling_deepseek.py
、modeling_glm.py
等所有的开源文件,再把各家公司实现的比较好用的 def 加入到自己的 modeling_XX.py
中。
完成以下脚本, 理论上可加载任何开源模型。
trans_qwen_to_llama.py
trans_llama_to_qwen.py
以后出现任何新开源模型,都可通过“trans_newModel_to_myModel.py
”,快速对该模型进行微调操作,而不用修改任何训练代码。
进一步理解每个开源模型的独特之处
- qwen2 的 q、k、v 的线性变换有 bias
- baichuan 的 lm_head 之前有一个
normalize()
的操作 - 甚至每个开源模型都能观察到一些 attention 魔改。
然后,对照论文去找,为什么作者要做这些改动?能不能从这个过程中学到知识?
进阶篇:
trans_llama_to_megatron.py
(给定参数 tp 和 pp)trans_megatron_to_llama.py
还可以给 modeling 文件加入很多有趣的东西来助力日常的 debug,比如:
def show_cos_distance(self, layer)
:输出某个 layer 的 input_hidden_states 和 output_hidden_states 的余弦距离;def show_topk_token(self, layer, K=10)
:输出用某个 layer 去预测 next token 时的最大 K 个 token;def show_attention(self, layer, tokenA, tokenB)
:输出第 layer 层的某两个 token 之间的 attention_value。
并不是所有人都用 megatron 训代码,但对于 megatron 训练的人,这两个脚本是基本功中的基本功了。
提醒: megatron_checkpoint 的 pp_size 实现 merge 和 split 非常简单,但在对 tp_size 进行 merge 和 split 的时候,一定要留意 megatron 的 gqa 的实现方式
进阶
- 单机并行推理已经实现了,不妨试试多机的;
- 学会用 vllm 等更快的推理框架,而不是 model.generate()。
- megatron 由于有 tp 和 pp 存在,实现起来难度远大于 deepspeed;
- model.trainer() 的训练方式封装的很死,如何加入 channel_loss 呢?
modeling_XX.py
即然可以万物转 llama,为什么还要用 modeling_llama.py 呢?
毕竟: modeling_llama.py
- 加载模型的时候没有 skip_build;
- 缺少 stream_generate;
- 文件不支持 sequence_parallel;
- 默认使用 flash_attention;
- 并没有一个可以作为 reward_model 的 lm_head;
- ……
实现一个自己的 modeling_XX.py,集百家之长,先去收集 modeling_llama.py、modeling_qwen.py、modeling_baichuan.py、modeling_yi.py、modeling_deepseek.py、modeling_glm.py 等所有的开源文件,再把各家公司实现的比较好用的 def 加入到自己的 modeling_XX.py 中。
这样,当市面上出现任何一个新的开源模型,都可以通过 “trans_newModel_to_myModel.py”,快速对该模型进行微调操作,而不用修改任何训练代码。
还可以给 modeling 文件加入很多有趣的东西来助力日常的 debug。
比如:
- def show_cos_distance(self, layer):输出某个 layer 的 input_hidden_states 和 output_hidden_states 的余弦距离;
- def show_topk_token(self, layer, K=10):输出用某个 layer 去预测 next token 时的最大 K 个 token;
- def show_attention(self, layer, tokenA, tokenB):输出第 layer 层的某两个 token 之间的 attention_value。
multi_infer.py
model.generate()
熟悉。
不考虑推理加速等技术时,客观事实:“8 卡 load 1 个模型、开大 batch_size ”的推理速度,远远小于 “8 卡 load 8 个模型、开小 batch_size ”的推理速度。
需求
- 实现一个 class infer (model_path, data_path, output_path, num_workers)
可用 torch_run, multiprocessing,或其他 python 库。
达成下面目的:
- 推理时让 1 机 8 卡 load 8 / 4 / 2 / 1 个模型,来快速的推理完一大批数据。
tips:
- 一些写法可能需要给 modeling_XX.py 加入一个 def set_device(self, device_list) 函数,毕竟如果每次都用
os.environ["CUDA_VISIBLE_DEVICES"]="3,4"
来控制使用哪些卡来 load 模型,有点不太优雅。
进阶篇:
- 单机的并行推理已经实现了,不妨试试多机的;
- 学会用 vllm 等更快的推理框架,而不是
model.generate()
。
Channel Loss
介绍领域模型 post-pretrain 时,做 domain post-pretrain 不看 channel loss,不如别开 tensorboard。
大部分情况下
- post-pretrain 的 loss 曲线都呈“缓慢下降”或“持平”的变化趋势
- sft 的 loss 曲线都呈“快速下降”且“阶梯状”的变化趋势,这时除初始 loss 和最终 loss是否符合预期外,能从中得到的信息微乎其微。
因此,把数据划分成不同数据源,对每个数据源的 loss 曲线单独观察,就显得尤为有意义,这也是研究跷跷板问题的必要环节。
题目:
- 改进训练代码,给 sft 数据随机赋予一个 channel,然后在训练过程中绘制出每个 channel 的 loss 曲线。
tips:
- 考虑通过 all_gather_object 实现。
进阶篇:
- megatron 由于有 tp 和 pp 存在,实现起来难度远大于 deepspeed;
- model.trainer() 训练方式封装的很死,如何加入 channel_loss 呢?
详见 大模型基本功
deepspeed chat 通用代码
详见站内专题: deepspeed chat
讲解
3D交互
【2024-1-24】新西兰软件工程师 Brendan Bycroft 开发的LLM 3D可视化网站 LLM Visualization, 展示 nanoGPT, GPT-2, GPT-3, 动态展示网络结构、运行过程
LLM 视频
LLM介绍
动图讲解
【2024-1-15】transformer 语言建模,动图讲解:
过程
# (1) 原始句子,来自互联网上大量文本
We go to work by train
# (2) 分词成令牌(Tokens): 编码的基本单位
We, go, to, work, by, train
# (3) 掌握单词(work)的语义, 需要关注上下文,注意邻近词
I have to go work
...
I work in my neighborhood
# (4) work 训练集:正例词集(E.g:roof),负例(E.g:dove)。
work roof, work office # 正例
work dove # 负例
# (5) 向量化: 根据每个词在训练数据中邻近程度来调整向量,称为词嵌入(embedding)。
work roof -> 0.8
work dove -> -0.9
# 词嵌入可以包含数百个值,每个值表示一个词意义的不同方面
work -> [0.1, ..., 0.9]
# 近义词向量的相似度高,如 sea和ocean
# (6) transformer 自注意力(Attention),同时计算所有单词,而不是RNN的顺序扫描
self-attention
# (7) 规模扩大,自注意力允许LLM获取句子外的上下文(context),更深入的理解句子。
next token prediction
tokenization -> word embedding/positional embedding -> self-attention -> Encoded output
# (8) 模型给每个令牌一个概率分数(probability score),表示序列中下一个词的可能性。
# (9) 循环下去,直至结束:贪心搜索->集束搜索找最佳
LLM 构建
【2024-9-6】大模型白盒子构建指南
- GitHub:tiny-universe
从原理出发、以“白盒”为导向、围绕大模型全链路的“手搓”大模型指南,帮助有深度学习基础的读者从底层原理出发,“纯手搓”搭建一个清晰、可用的大模型系统,包括大模型本身、RAG 框架、Agent 系统及大模型评估体系。最近新增了从零开始pretrain Llama3部分。
亮点
- 全流程 从零手搓
- 包含LLM全流程,从Model,到RAG,Agent,Eval,打造LLM 全栈教程
- 区别于大型的算法包,项目代码对初级开发者更 简洁清晰 ,更”白盒子”
- 后续会持续迭代更新项目,如动手训练一个 Tiny-llama3 ,动手制作 垂直领域数据集 等等。
- 深入剖析大模型原理——Qwen Blog
- 如何评估你的大模型——Tiny Eval
- 纯手工搭建 RAG 框架——Tiny RAG
- 手搓一个最小的 Agent 系统——Tiny Agent
- 深入理解大模型基础——Tiny Transformer
- 从零开始pretrain Llama3—— Tiny Llama
transformer
主要结构
transformer block 主要由三种结构组成
MHA
(Multi-Head Attention),多头注意力模块,下图绿色部分。Add&Norm
,归一化模块,下图蓝色部分。FFN
,前馈网络模块,下图粉色部分
gemm-like 算子
Encoder
Transformer Encoder 模块
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadSelfAttention(nn.Module):
def __init__(self, embed_size, heads):
super(MultiHeadSelfAttention, self).__init__()
self.embed_size = embed_size
self.heads = heads
self.head_dim = embed_size // heads
assert (
self.head_dim * heads == embed_size
), "Embedding size needs to be divisible by heads"
self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.fc_out = nn.Linear(heads * self.head_dim, embed_size)
def forward(self, values, keys, queries):
N = queries.shape[0]
value_len, key_len, query_len = values.shape[1], keys.shape[1], queries.shape[1]
# Split the embedding into self.heads different pieces
values = values.reshape(N, value_len, self.heads, self.head_dim)
keys = keys.reshape(N, key_len, self.heads, self.head_dim)
queries = queries.reshape(N, query_len, self.heads, self.head_dim)
values = self.values(values)
keys = self.keys(keys)
queries = self.queries(queries)
# Attention mechanism
#attention = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])
attention = torch.matmul(queries, keys.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.head_dim, dtype=torch.float32))
attention = F.softmax(attention / (self.embed_size ** (1 / 2)), dim=3)
out = torch.matmul(attention, values).reshape(N, query_len, self.heads * self.head_dim)
# out = torch.einsum("nhql,nlhd->nqhd", [attention, values]).reshape(
N, query_len, self.heads * self.head_dim
)
out = self.fc_out(out)
return out
class TransformerBlock(nn.Module):
def __init__(self, embed_size, heads, dropout, forward_expansion):
super(TransformerBlock, self).__init__()
self.attention = MultiHeadSelfAttention(embed_size, heads)
self.norm1 = nn.LayerNorm(embed_size)
self.norm2 = nn.LayerNorm(embed_size)
self.feed_forward = nn.Sequential(
nn.Linear(embed_size, forward_expansion * embed_size),
nn.ReLU(),
nn.Linear(forward_expansion * embed_size, embed_size),
)
self.dropout = nn.Dropout(dropout)
def forward(self, value, key, query):
attention = self.attention(value, key, query)
x = self.dropout(self.norm1(attention + query))
forward = self.feed_forward(x)
out = self.dropout(self.norm2(forward + x))
return out
class Encoder(nn.Module):
def __init__(
self,
embed_size,
num_layers,
heads,
device,
forward_expansion,
dropout,
):
super(Encoder, self).__init__()
self.embed_size = embed_size
self.device = device
self.layers = nn.ModuleList(
[
TransformerBlock(
embed_size,
heads,
dropout=dropout,
forward_expansion=forward_expansion,
)
for _ in range(num_layers)
]
)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
out = self.dropout(x)
for layer in self.layers:
out = layer(out, out, out)
return out
# Hyperparameters
embed_size = 512
num_layers = 6
heads = 8
device = "cuda" if torch.cuda.is_available() else "cpu"
forward_expansion = 4
dropout = 0.1
# Example
encoder = Encoder(embed_size, num_layers, heads, device, forward_expansion, dropout).to(device)
Decoder
待定
问题
- 1,transformer 结构?
- 2,Layer Normalization 怎么计算,以及为什么要在transformer 用LN不用BN
- 3,多头注意力机制是怎么计算的,以及为什么需要多头注意力机制
transformer 基本结构是什么
Transformer 分为 encoder
和 decoder
两个部分。
Encoder
包含self-attention
自注意力 和mlp
前馈神经网络,用于提取特征;Decoder
在自注意力和前馈神经网络中多了一个 cross-attention 编码-解码注意力,用于和 encoder 输出做交互。- 其中的自注意力是 Masked self-attention
Positional Encoding 有什么用
保证 attention 机制考虑序列顺序,否则无法区分不同位置的相同输入。
Transformer 模型本身不包含循环或卷积结构,它无法捕捉序列中的位置信息。因此,需要额外的位置编码来提供每个位置上的信息,以便模型能够区分不同位置的输入元素。
如何处理长距离依赖
Transformer 通过自注意力机制直接计算序列中任意两个位置之间的依赖关系,从而有效地解决了长距离依赖问题。
Layer Normalization 作用
Layer Normalization 有助于稳定深层网络训练,通过对输入的每一层进行标准化处理(使输出均值为0,方差为1),可以加速训练过程并提高模型的稳定性。
- 通常在自注意力和前馈网络的输出上应用。
Layer Normalization 怎么计算
为什么要在transformer 用LN不用BN
能否用 Batch Normalizatioin?
Transformer 架构中,层归一化(Layer Normalization,简称 LayerNorm)是首选归一化方法,主要用于模型内部的每一层之后。
理论上,层归一化可以被批归一化(Batch Normalization,简称 BatchNorm)替换,但是这两种归一化技术在应用上有着本质的不同,这些差异导致了在 Transformer 中通常优先选择层归一化而不是批归一化。
self-attention 计算方法
Self-Attention 计算公式
$ self-attention = \frac{softmax(QK^T)}{\sqrt{d_k}}K$
为什么要除以根号dk?
目的: 点积操作后保持数值稳定性。
- 第一,这种缩放确保了在较大维度 dk 下,softmax 输入值不会太大,避免梯度消失问题,训练更加稳定。
- 第二,选择 sqrt(dk) 使 Q*K 结果
均值
和方差
保持不变,类似于归一化
。
原因
- 数学: 对于输入 均值0方差1的分布, 点乘后结果, 方差为 dk, 所以需要缩放
- 神经网络上:防止在计算点积时数值过大,导致后续应用 softmax 函数时出现梯度消失问题。
计算点积时,如果Q K的元素值和dk的值都很大,那么点积的结果可能会非常大,导致 softmax 函数的输入变得非常大。 softmax 函数在处理很大的输入值时,会使输出的概率分布接近0或1,这会造成梯度非常小,难以通过梯度下降有效地训练模型,即出现梯度消失问题。 通过使用dk缩放点积的结果,可以使点积的数值范围被适当控制。
self-attention 为什么用多头?
self-attention 用多头
- self-attention 模型对当前位置信息编码时,将注意力过度集中于自身位置,通过多头注意力机制来解决这一问题。
- 多头注意力机制还能够给予注意力层的输出包含有不同子空间中的编码表示信息,从而增强模型表达能力。
高频: 多头注意力如何实现
每个头独立地在相同输入上计算注意力权重,最后, 把所有头的输出合并。每个头关注一部分的特征,类似于卷积中通道作用。
图示
代码
- 高频面试题
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
"""
Multi-Head Attention Layer 多头注意力实现
"""
def __init__(self, embed_size, heads):
super(MultiHeadAttention, self).__init__()
self.embed_size = embed_size
self.heads = heads
# embedding 分成 heads 份,每头关注部分特征
self.head_dim = embed_size // heads
assert (
self.head_dim * heads == embed_size
), "Embedding size needs to be divisible by heads"
# Separate linear layers for values, keys, and queries for each head
self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.fc_out = nn.Linear(heads * self.head_dim, embed_size)
def forward(self, values, keys, query):
N = query.shape[0] # Number of examples
value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1]
# Split embeddings into self.heads pieces
values = values.reshape(N, value_len, self.heads, self.head_dim)
keys = keys.reshape(N, key_len, self.heads, self.head_dim)
queries = query.reshape(N, query_len, self.heads, self.head_dim)
# Apply linear transformation (separately for each head)
values = self.values(values)
keys = self.keys(keys)
queries = self.queries(queries)
# Attention mechanism (using torch.matmul for batch matrix multiplication)
# 计算注意力得分 Calculate attention score
attention = torch.matmul(queries, keys.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.head_dim, dtype=torch.float32))
attention = F.softmax(attention, dim=-1)
# 注意力权重 Apply attention weights to values
out = torch.matmul(attention, values)
# 合并多头 Concatenate heads
out = out.reshape(N, query_len, self.heads * self.head_dim)
# 最后的全连接层 Final linear layer
out = self.fc_out(out)
return out
# Example usage
embed_size = 256
heads = 8
N = 1 # Batch size
sentence_length = 5 # Length of the input sequence
model = MultiHeadAttention(embed_size, heads)
# Dummy input (batch size, sentence length, embedding size)
x = torch.rand((N, sentence_length, embed_size))
# Forward pass
out = model(x, x, x) # In self-attention, queries, keys, values are all the same
print("Input shape:", x.shape)
print("Output shape:", out.shape)
Multi-query Attention 和 Grouped-query Attention
Multi-query Attention 和 Grouped-query Attention 是两种不同的注意力机制变种,用于改进和扩展传统的自注意力机制。
- Multi-query Attention:每个查询可以与多个键值对交互,捕捉更多的上下文信息。
- 提高模型的表达能力和性能,特别是在处理长序列或复杂关系时。
- Grouped-query Attention:查询被分成多个组,每个组内的查询与对应的键值对进行交互。
- 减少计算复杂度,提高效率,同时仍然保持较好的性能。
为什么需要 Mask 机制
两个原因。
- 屏蔽未来信息,防止未来帧参与训练。
- 处理不同长度序列,在批处理时对较短的序列进行填充(padding),并确保这些填充不会影响到模型的输出。
Mask机制如何实现
分别处理
- 屏蔽未来信息的 Mask:自注意力层中,通过构造一个上三角矩阵(对于解码器),其中上三角部分(包括对角线,取决于具体实现)被设置为非常大的负数,这样在通过 softmax 层时,这些位置的权重接近于0,从而在计算加权和时不考虑未来的词。
- 填充 Padding Mask:将填充位置的值设置为大负数,使得经过 softmax 层后,这些位置的权重接近于0。
疑问:为什么是大负数?
BERT
适用场景
分析
- BERT 模型用于理解文本深层语义的任务,如:文本分类、命名实体识别等。
- LLaMA 和 ChatGLM 类大模型则适用于需要生成文本或进行更复杂语言理解的任务,如:对话系统、文本生成等。选择哪种模型取决于任务的需求和可用资源。
思考
BERT预训练任务?
BERT预训练任务
- 第一,Masked Language Model(MLM),即对mask掉的15%的token进预测
- 第二,Next Sentence Predict,判断两个句子是否是相连的两个句子。
BERT Finetune
怎么用BERT完成finetune
- 参考迁移学习的范式,为新任务或者数据集添加一个分类头或者使用Adapter
GPT vs BERT
结构和工作原理上的差异
- BERT 是 Encoder-only 结构,属于自监督训练方式,通过MLM和NSP两大预训练任务,主要用于下游任务抽特征。
- GPT 是 Decoder-only,自回归训练,预测下一个词分布,依赖大语料库,GPT-3可以表现出Few-shot/zero-shot learning。
有哪些mask类型
BERT token分3种情况 mask,作用是什么?
- BERT MLM任务中,总token 15% 会被随机Mask掉。
确定要Mask掉的单词之后
- 80% 会直接替换为
[Mask]
- 10% 替换为其它任意单词
- 10% 保留原始Token。
原因
- 如果句子某个Token 100% 被mask掉,那么 fine-tuning 时, 模型就会有一些没有见过的单词。
- 加入随机Token: Transformer要保持对每个输入token的分布式表征,迫使模型更多地依赖于上下文信息去预测词汇,并且赋予了模型一定的纠错能力。
warm-up 策略
学习率 warm-up 策略是怎样的?
warmup 在训练最初使用较小的学习率来启动,并很快切换到大学习率而后进行常见的 decay。
- 刚开始模型对数据的“分布”理解为零,或者是说“均匀分布”(当然这取决于你的初始化);
- 第一轮训练,每个数据点对模型来说都是新的,模型会很快地进行数据分布修正,如果这时候学习率就很大,极有可能导致开始的时候就对该数据“过拟合”,后面要通过多轮训练才能拉回来,浪费时间。
- 训练一段时间(2-3轮)后,模型已经对每个数据点看过几遍了,当前的batch而言有了一些正确的先验,较大学习率就不那么容易会使模型学偏,所以可以适当调大学习率。
这个过程是 warmup。
为什么之后还要decay呢?
- 当模型训到一定阶段后(比如十个epoch),模型分布就已经比较固定,能学到的新东西就比较少了。
- 如果还沿用较大的学习率,就会破坏这种稳定性,已经接近loss的local optimal了,为了靠近这个point,要慢慢来。
分词
几种
- Byte-Pair Encoding (BPE) 是当前SOTA的LM模型常用的分词方法
- BERT 使用 Wordpiece,属于BPE的改进版,都是sub-word分词器
1 token ≈ 0.75 word
词表扩充
大模型词表扩充的方法包括:
- 新增词汇:手动添加领域特定的术语和词汇到词表中。
- 数据驱动:通过分析大量文本数据自动识别和添加高频出现的词汇。
- 词汇映射:将特定领域的词汇映射到现有的词表中,或者创建新的词汇条目。
流行的词表管理工具和库包括:
- Hugging Face Transformers:提供了一个预训练模型和词表管理的接口。
- SentencePiece:一个用于构建词汇表的工具,支持BPE和其他子词分割方法。
- Moses:一个开源的自然语言处理工具,包括用于词表构建和分词的工具。
ChatGLM3 如何分词
ChatGLM3 训练过程中根据输入数据动态合并出现频率较高的字节对,从而形成新的词汇。
- 有效地处理大量文本数据,并减少词汇表规模。
- ChatGLM3 还用特殊的词表分割方法,将词表分为多个片段,并在训练过程中逐步更新片段,以提高模型的泛化能力和适应性。
BPE算法通过迭代地将最频繁出现的字节对合并成新的符号,来构建一个词汇表。
位置编码
位置编码 Position Encoding
- 原始transformer(attention is all you need)里面用的是三角式位置编码
- BERT使用可学习位置编码,预设的位置个数是512,因此最大序列长度为512
- dim=768,就用384组三角函数来表征
RoPE
旋转位置编码作用在什么地方?
- 三角函数位置编码是与Embedding直接相加,RoPE位置编码采用类似哈达马积相乘的形式。
- 其次,旋转位置编码只在query, key 注入位置信息,然后计算attention score,三角函数的位置编码,query, key和value都有位置信息。
ALiBi
ALiBi(Attention with Linear Biases)的相对位置编码机制。
ALiBi的出发点是希望能提升位置编码的外推能力,原因是在实际使用中的输入token长度可能会远远大于训练阶段使用的最大token限制。
激活函数
总结
ReLU
(Rectified Linear Unit):解决梯度消失问题,加快训练速度。GeLU
(Gaussian Error Linear Unit):ReLU改进,性能和泛化能力更好。Swish
:自门控激活函数,提供非线性变换,并具有平滑和非单调特性。
ReLU
待定
GeLU
GeLU (Gaussian Error Linear Unit) 中文名为高斯误差线性单元,受 RELU 和 dropout 启发
- RELU 是激活小的时候乘以0
- dropout是随机乘以0
- GeLU 是概率性的乘以0 (但是跟dropout不同,用确定性的表达式给出)
SwiGLU
SwiGLU 和 GeGLU非常相似,只是把GeLU激活换成了Swish激活
损失函数
损失函数介绍
详见站内专题: 损失函数
问题
- Cross Entropy Loss 怎么写?
Norm
preNorm/postNorm/DeepNorm
NLP任务中经常使用 Layer Normalization(LN) 而非 Batch Normalization(BN)
随机优化理论中学习率往往设置为常数或者逐渐衰减 (decay),从而保证算法收敛,这种学习率的设置方法也与机器学习里很多任务上的实际经验类似。
然而,不管是设置学习率为常数还是使学习率逐渐衰减都不能让Transformer很好地收敛。
优化Transformer结构时,还需要在训练的初始阶段设置一个非常小(接近0)的学习率,经过一定的迭代轮数后逐渐增长到初始的学习率,这个过程称作warm-up
阶段(学习率预热)。
Warm-up 是原始Transformer结构优化时, 一个必备学习率调整策略。
- Transformer结构对于warm-up的超参数(持续轮数、增长方式、初始学习率等)非常敏感,若调整不慎,往往会使得模型无法正常收敛。
Transformer结构的优化非常困难,其具体表现在:
- warm-up阶段超参数敏感;
- 优化过程收敛速度慢。
总结
BERT 使用 Post-Norm 结构, 同时期的 GPT-1 也是
- GPT-2 改用 Pre-Norm
Post-LN
vs. Pre-LN
vs. Sandwich-LN
分析
Pre-Norm
比Post-Norm
参数更好训练,但是模型精度要比Post-Norm
略差。
典型示例
GPT-3
:Post-Layer Normalization(后标准化), 有助于稳定训练过程,提高模型性能。LLAMA
:Pre-Layer Normalization(前标准化)的结构,提高模型的泛化能力和鲁棒性。ChatGLM
:Post-Layer Normalization的结构,类似于 GPT-3, 提高模型的性能和稳定性。
分析公式: x + F(x), 哪里插入 normalization ?
- Norm 计算公式: 假设x和y是相互独立的均值为0,方差为1的随机变量, Norm(x+y) = (x+y)/根号2
PreNorm
: x1 = x + Norm(F(x))PostNorm
: x1 = Norm(x + F(x))
Norm类型 | 基本公式 | 递归展开式 |
---|---|---|
PreNorm | x1 = x + Norm(F(x)) | 原文 |
PostNorm | x1 = Norm(x + F(x)) | 原文](https://zhuanlan.zhihu.com/p/640784855) |
SanwichNorm | ||
DeepNorm |
展开公式
- 输出的方差会很大,因此需要在输出加个额外的LayerNorm (GPT2的设计)
- Pre-Norm把网络的实际深度变浅了,并且增加了宽度
- Pre-Norm 网络层数有水分,这个可能是导致模型最终精度不如Post-Norm的原因。
两种Norm 特点
- Post-Norm 削弱残差作用,深度保持,但是收敛和调参困难
- Pre-Norm 将网络变成浅且宽的结构,收敛容易,但是精度有一定损失
LayerNorm
Layer Normalization
按照 layer normalization 位置不同,可分为 post layer norm 和 pre layer norm
PostNorm
post layer norm。原始transformer中,layer normalization 放在残差连接之后,称为post LN
。- 使用Post LN的深层transformer模型容易出现训练不稳定问题。
- post LN随着transformer层数的加深,梯度范数逐渐增大,导致了训练的不稳定性。
PreNorm
pre layer norm。将 layer norm 放在残差连接的过程中,self-attention 或FFN块之前,称为“Pre LN
”。- Pre layer norm 在每个transformer层的梯度范数近似相等,有利于提升训练稳定性,但缺点是pre LN可能会轻微影响transformer模型的性能,为了提升训练稳定性,GPT3、PaLM、BLOOM、OPT等大语言模型都采用了pre layer norm。
LayerNorm
:LayerNorm对每一层的所有激活函数进行标准化,使用它们的均值和方差来重新定位和调整激活函数。其公式如下:RMSNorm
:RMSNorm通过仅使用激活函数的均方根来重新调整激活,从而提高了训练速度。DeepNorm
:为了进一步稳定深度Transformer的训练,Microsoft推出了DeepNorm。这是一个创新的方法,它不仅用作标准化,还作为残差连接。有了DeepNorm的帮助,可以轻松训练高达1000层的Transformer模型,同时保持稳定性和高性能。- 其中,GLM-130B 和 ChatGLM 采用
DeepNorm
。其公式如下:其中SublayerSublayer是FFN或Self-Attention模块。
- 其中,GLM-130B 和 ChatGLM 采用
BatchNorm
分析
- 层归一化(Layer Normalization)
- 层归一化是对每个样本的所有特征执行归一化操作,独立于其他样本。无论批次大小如何,LayerNorm 的行为都是一致的。
- 处理序列数据和自注意力机制时,LayerNorm 更加有效,因为它能够适应不同长度的输入,这在自然语言处理任务中尤为重要。
- LayerNorm 直接在每个样本的维度上工作,使得它在序列长度变化的情况下更为稳定。
- 批归一化(Batch Normalization)
- 批归一化在小批量维度上进行归一化,依赖于批次中所有样本的统计信息。因此,BatchNorm 行为会随着批次大小和内容的变化而变化,这在训练和推理时可能导致不一致的表现。
- 处理变长序列和自注意力结构时,BatchNorm 可能不如 LayerNorm 高效,因为变长输入使得批次间的统计信息更加不稳定。
- BatchNorm 训练时计算当前批次的均值和方差,在推理时使用整个训练集的移动平均统计信息。这种依赖于批次统计信息的特性使得 BatchNorm 在小批量或在线学习场景中表现不佳。
RMSNorm
RMSNorm 比LayerNorm 好在哪?
- 第一,虽然时间复杂度一致,但 RMSNorm 比 LayerNorm 少了减去均值以及加上bias的计算,这在目前大模型预训练的计算量下就能够体现出训练速度上的优势了。
- 第二,RMSNorm 模型效果好于 LayerNorm (LN取得成功的原因可能是
缩放不变性
,而不是平移不变性
)。
PostNorm
论文提出了两种Layer Normalization方式并进行了对比。
把Transformer架构中传统的Add&Norm方式叫做Post-LN
,并针对Post-LN,模型提出了Pre-LN
,即把layer normalization加在残差连接之前
造成Post-LN Transformer梯度分布出现问题的核心原因
- 各子层之后 Layer Normalization 层会使得各层的输入尺度与层数L无关,因此当Layer Normalization对梯度进行归一化时,也与层数L无关。
- Pre-LN 最后一层Layer Normalization层的输入尺寸的量级只有Post-LN的 根号(1/L) 倍,并且每个LN层都会对梯度以 根号L 的比例归一化。所以对于Pre-LN结构来说,其每层梯度范数都近似不变。
相比于Post-LN结构梯度分布的不稳定,Pre-LN在各层之间梯度范数几乎保持不变,这种结构明显更利于优化器进行优化。而在进行一定轮数的 warm-up后,Post-LN 梯度范数也基本保持不变,并且其量级非常小(上图中绿色),这也验证了Post-LN在warm-up阶段的训练不稳定性问题。
实验表明
- 当使用Pre-LN结构时,warm-up阶段已不再是必需,并且Pre-LN结构可以大幅提升Transformer的收敛速度。
- 对于机器翻译任务(IWSLT/WMT),不需要warm-up的Pre-LN结构可以比Post-LN收敛快1倍左右
- 而在BERT上,Pre-LN在下游任务上达到和Post-LN相同的性能也只需要后者迭代轮数的1/3左右,并且最终的效果也更好。
SanwichNorm
Sandwich-Norm,基于Pre-Norm再加一个
DeepNorm
Nguyen和Salazar(2019)发现相对于Post-LN
,Pre-LN
能够提升Transformer的稳定性。
- 然而,
Pre-LN
在底层的梯度往往大于顶层,导致其性能不及Post-LN
。
为了缓解这一问题,研究人员通过更好的初始化方式或模型架构来改进深度Transformer。
- 基于Post-Norm做了改进,出现了
Deep-Norm
,能训练1000层的Transformer
def deepnorm(x):
return LayerNorm(x * alpha + f(x))
def deepnorm_init(w):
if w is ['ffn', 'v_proj', 'out_proj']:
nn.init.xavier_normal_(w, gain=β)
elif w is ['q_proj', 'k_proj']:
nn.init.xavier_normal_(w, gain=1)
这些方法可以使多达数百层的Transformer模型实现稳定化,然而以往的方法没有能够成功地扩展至1000层。
DeepNorm
结合了Post-LN
的良好性能以及Pre-LN
的训练稳定性。
- 与
Post-LN
相比, DeepNorm 在执行层归一化之前 Up-Scale 了残差连接。
相较于Post-LN模型,DeepNet的模型更新几乎保持恒定。
参考:
注意力
掩码
【2024-8-14】注意力机制中三种掩码技术详解和Pytorch实现
掩码类型
填充掩码
Padding Mask序列掩码
Sequence Mask前瞻掩码
Look-ahead Mask
掩码类型 | 目的 | 应用 | 功能 |
---|---|---|---|
填充掩码 Padding Mask |
屏蔽输入数据中的无关数据 | 长度不一致的输入数据 | 识别有效数据 |
序列掩码 Sequence Mask |
模型关注范围,包含填充数据 | 精确控制信息流 | 指示哪些数据有效/填充 |
前瞻掩码 Look-ahead Mask |
防止看到未来信息 | 自回归模型 | 保证时序正确性,防止信息泄露 |
掩码之间的关系
填充掩码
(Padding Mask)和序列掩码
(Sequence Mask)都是在处理序列数据时使用的技术,帮助模型正确处理变长输入序列- 但应用场景和功能有些区别。这两种掩码深度学习模型中被一起使用,尤其是在需要处理不同长度序列的场景下。
填充掩码
指示哪些数据是填充的,用在输入数据预处理和模型的输入层。确保模型在处理或学习过程中不会将填充部分的数据当作有效数据来处理,从而影响模型的性能。在诸如Transformer模型的自注意力机制中,填充掩码用于阻止模型将注意力放在填充的序列上。序列掩码
用于更广泛的上下文中,它不仅可以指示填充位置,还可以用于其他类型的掩蔽,如在序列到序列的任务中掩蔽未来的信息(如解码器的自回归预测)。序列掩码确保模型在处理过程中只关注当前及之前的信息,而不是未来的信息,这对于保持信息的时序依赖性非常重要。填充掩码
多用于模型的输入阶段或在注意力机制中排除无效数据的影响序列掩码
则可能在模型的多个阶段使用,特别是在需要控制信息流的场景中。
前瞻掩码
用于控制时间序列的信息流,确保在生成序列的每个步骤中模型只能利用到当前和之前的信息。这是生成任务中保持模型正确性和效率的关键技术。
填充掩码 Padding Mask
深度学习中,处理序列数据时,”填充掩码”(Padding Mask)是一个重要概念。
当序列数据长度不一致时,通常需要对短的序列进行填充(padding),以确保所有序列的长度相同,这样才能进行批处理。
这些填充的部分实际上是没有任何意义,不应该对模型的学习产生影响。
import torch
def create_padding_mask(seq, pad_token=0):
mask = (seq == pad_token).unsqueeze(1).unsqueeze(2)
return mask # (batch_size, 1, 1, seq_len)
# Example usage
seq = torch.tensor([[7, 6, 0, 0], [1, 2, 3, 0]])
padding_mask = create_padding_mask(seq)
print(padding_mask)
序列掩码 Sequence Mask
序列掩码用于隐藏输入序列的某些部分。避免在计算注意力分数时考虑到填充位置的影响
比如在双向模型中,想要根据特定标准忽略序列的某些部分。
RNNs 本身可处理不同长度的序列,但在批处理和某些架构中,仍然需要固定长度的输入。序列掩码在这里可以帮助RNN忽略掉序列中的填充部分,特别是在计算最终序列输出或状态时。
在训练模型时,序列掩码也可以用来确保在计算损失函数时,不会将填充部分的预测误差纳入总损失中,从而提高模型训练的准确性和效率。
序列掩码表示为一个与序列数据维度相同的二进制矩阵或向量,其中1表示实际数据,0表示填充数据
def create_sequence_mask(seq):
seq_len = seq.size(1)
mask = torch.triu(torch.ones((seq_len, seq_len)), diagonal=1)
return mask # (seq_len, seq_len)
# Example usage
seq_len = 4
sequence_mask = create_sequence_mask(torch.zeros(seq_len, seq_len))
print(sequence_mask)
前瞻掩码 Look-ahead Mask
前瞻掩码,也称为因果掩码或未来掩码,用于自回归模型中,以防止模型在生成序列时窥视未来的符号, 确保了给定位置的预测仅依赖于该位置之前的符号。
前瞻掩码通过在自注意力机制中屏蔽(即设置为一个非常小的负值,如负无穷大)未来时间步的信息来工作。这确保了在计算每个元素的输出时,模型只能使用到当前和之前的信息,而不能使用后面的信息。
这种机制对于保持自回归属性(即一次生成一个输出,且依赖于前面的输出)是必要的。
前瞻掩码通常表示为一个上三角矩阵,其中对角线及对角线以下的元素为0(表示这些位置的信息是可见的),对角线以上的元素为1(表示这些位置的信息是不可见的)。
计算注意力时,这些为1的位置会被设置为一个非常小的负数(通常是负无穷),这样经过softmax函数后,这些位置的权重接近于0,从而不会对输出产生影响。
def create_look_ahead_mask(size):
mask = torch.triu(torch.ones(size, size), diagonal=1)
return mask # (seq_len, seq_len)
# Example usage
look_ahead_mask = create_look_ahead_mask(4)
print(look_ahead_mask)
注意力掩码
注意力机制中,掩码被用来修改注意力得分
import torch.nn.functional as F
def scaled_dot_product_attention(q, k, v, mask=None):
matmul_qk = torch.matmul(q, k.transpose(-2, -1))
dk = q.size()[-1]
scaled_attention_logits = matmul_qk / torch.sqrt(torch.tensor(dk, dtype=torch.float32))
if mask is not None:
scaled_attention_logits += (mask * -1e9)
attention_weights = F.softmax(scaled_attention_logits, dim=-1)
output = torch.matmul(attention_weights, v)
return output, attention_weights
# Example usage
d_model = 512
batch_size = 2
seq_len = 4
q = torch.rand((batch_size, seq_len, d_model))
k = torch.rand((batch_size, seq_len, d_model))
v = torch.rand((batch_size, seq_len, d_model))
mask = create_look_ahead_mask(seq_len)
attention_output, attention_weights = scaled_dot_product_attention(q, k, v, mask)
print(attention_output)
模型架构
Encoder vs Decoder
LLM 进化树
- 粉色分支,
Encoder-only
框架(也叫Auto-Encoder
),典型 BERT等 - 绿色分支,
Encoder-decoder
框架,典型代表如T5
和GLM
等 - 蓝色分支,
Decoder-only
框架(也叫Auto-Regressive
),典型代表如GPT
系列/LLaMa
/PaLM
等
直观对比
- 横轴代表了输入token,纵轴代表相对应每个位置的输出token
- 左图 encoder-only,输出token都能看到所有输入token。例如 y1 这一行可以看到输入 x1-x5
- 中图 decoder-only,输出token只能看到历史的输入token。例如 y3 这行只能看到 x1-x3, x4 和 x5 看不到
- 右图为encoder-decoder,前k个输出token可以看到所有k个输入token,从k+1的输出token开始只能看到历史的输入token。例如 y1 能看到输入(y3也可以),而开始只能看到输入x1-x4
- encoder-decoder 简化使用causal with prefix示意
三种结构不同的LLM,往往擅长处理不同的任务
NLU任务 | conditioned-NLG任务 | unconditioned-NLG任务 | 典型代表 | |
---|---|---|---|---|
Encoder-only 架构 |
效果最好 | BERT | ||
Encoder-decoder 架构 |
效果最好 | T5和GLM | ||
Decoder-only 架构 |
效果最好 | GPT系列/LLaMa/PaLM | ||
典型代表 | 文本情感分析,词性标注,信息检索 | 机器翻译,自动摘要 | QA,ChatBot |
LLM(Language Model)是一种处理自然语言的模型架构,对输入的序列进行预测和生成。
LLM 可以使用 encoder-decoder
或者decoder-only
架构。
LLM 都是 seq2seq 任务,用户输入一个文本prompt,LLM对应输出一个文本completion。
- 示例:
A Survey of Large Language Models
- 用户输入 prompt为
A Survey of
,期望语言模型输出Large Language Models
模型架构 | 代表LLM | 注意力机制 | 是否属于 Decoder-Only | 优点 | 缺点 |
---|---|---|---|---|---|
Causal Decoder |
GPT-3 /ChatGPT /ChatGLM2-6B |
纯单向 | YES | 适用各种生成任务,泛化好 | 无法访问未来信息,可能生成不一致/有误内容 |
Encoder-Decoder |
Flan-T5 |
输入双向,输出单向 | NO | 可处理输入输出序列不同长度的任务,如机器翻译 | 模型结构复杂,训练推理代价大 |
Prefix Decoder |
GLM-130B /ChatGLM-6B |
输入双向,输出单向 | YES | 减少预训练模型参数修改,降低过拟合风险 | 受前缀长度限制,无法充分捕捉任务信息 |
训练目标上,ChatGLM-6B 训练任务是自回归文本填空。
- 相比于采用 causal decoder-only 结构的大语言模型,采用 prefix decoder-only 结构的
ChatGLM-6B
存在一个劣势:训练效率低。 - causal decoder 结构会在所有的token上计算损失,而 prefix decoder 只会在输出上计算损失,而不计算输入上的损失。
- 相同数量的训练tokens的情况下,prefix decoder要比causal decoder的效果差,因为训练过程中实际用到的tokens数量要更少。
分析
Causal Decoder
Causal Decoder
架构典型代表是GPT
系列模型,单向注意力掩码,以确保每个输入token只能注意到过去的token和它本身,输入和输出的token通过Decoder以相同的方式进行处理。- 灰色代表对应的两个token互相之间看不到,否则就代表可以看到。例如,”Survery”可以看到前面的“A”,但是看不到后面的“of”。
- Causal Decoder的sequence mask矩阵是一种典型的下三角矩阵。
Encoder-Decoder
- Transformer最初被提出来时, 采用Encoder-Decoder架构,模型包含两部分Encoder和Decoder,两部分参数独立。
- 其中Encoder将输入序列处理为一种中间表示,而Decoder则基于中间表示自回归地生成目标序列。
Prefix Decoder
Prefix Decoder
架构也被称为non-causal Decoder
架构,注意力机制和Encoder-Decoder很接近,也是输入部分采用双向注意力,而输出部分采用单向注意力。- 原文Exploring Architectures and Configurations for Large Language Models (LLMs)
以上三种架构都可以通过MoE技术扩展,针对输入只激活一部分权重,典型代表: Switch Transformer
and GLaM
- All three architecture types can be extended using the mixture-of-experts (MoE) scaling technique, which sparsely activates a subset of neural network weights for each input.
- This approach has been used in models like Switch Transformer and GLaM, and increasing the number of experts or the total parameter size has shown significant performance improvements.
为什么LLM 都是decoder-only
架构?
为什么现在的LLM 都是 Decoder only 架构?
- 第一,双向注意力可能存在低秩问题。双向注意力带来的低秩问题会导致效果下降。
- 第二,在同等参数量、同等推理成本下,Decoder-only架构更优。
- 第三, Decoder-only zero-shot 能力更强。
decoder-only架构具有一定优势。
- 首先,
decoder-only
模型更简单,因为只需要生成输出,不需要考虑输入。这样可以减少计算负担,提高模型的训练和推理速度。 - 另外,
decoder-only
模型更好地解决语言建模问题,如自然语言生成、文本分类等问题。 - 其次,
decoder-only
模型更好地利用预训练任务数据。在预训练任务中- decoder-only模型只需要通过
掩码语言建模
(Masked Language Modelling)任务来学习上下文和语言规律。 - 而encoder-decoder模型需要尝试预测中间编码表示。这对于decoder-only模型来说是一种更容易的任务,因此可以更好地利用数据,使得模型表现更好。
- decoder-only模型只需要通过
- 最后,
decoder-only
模型可以更好地处理长序列。- encoder-decoder模型需要在解码的时候进行对齐操作,因此当输入序列长度变化时,需要重新对齐,导致计算复杂度的提升。
- 而decoder-only模型不需要进行对齐操作,可更好地处理长序列。
综上所述,decoder-only架构具有简单、高效、更好地利用预训练数据以及更好地处理长序列等优势
- 作者:态灵AI
成诚补充
- Encoder-Decoder 架构没落
- 代表模型T5最大模型只有 11B,效果不佳
- T5 很难使用 Pipeline Parallelism, 即便 Megatron 支持 T5 训练(代码),效果非常差,增加一倍 GPU,开启 PP 反而会让总吞吐下降
- 流水并行是千卡以上分布式训练中最重要的特性
- 即便 prefix decoder架构 也纷纷转向 decoder-only
- GLM-130B 不是 Decoder-Only 架构,但是 GLM-3 及以后(含 GLM-4)的 GLM 系列模型都改为
Decoder-Only
架构。 就连 GLM 团队都抛弃了原有架构,FollowLLaMa
了。 - HuggingFace 上可以尝试 GLM-130B 的 Playground,即使仅从 Foundation-Model 的角度评价,效果也很糟糕。
- GLM-130B 不是 Decoder-Only 架构,但是 GLM-3 及以后(含 GLM-4)的 GLM 系列模型都改为
- Decoder-Only 架构最核心优势是便于 Scale Up,基于 Scaling Laws 的实际训练成本最低
- LLM 时代,如果新算法结构可能有 5% 效果提升,但是引入了额外 50% 的 训练成本(计算时间 or 通信量) ,那这个新算法是负优化。
- 因为这 50% 的训练成本,基于 Scaling Laws 在原模型上多训练 50% 的 tokens ,或者训练大一半的模型, 带来的最终提升都远大于新算法的 5%。
- 至此 2023 年下半年之后的所有 LLM (可以被用户使用的 Chat 模型)均为 Decoder-Only 架构
Encoder-Only、Encoder-Decoder、Decoder-Only 三者架构:
- 相同参数量的训练效率上:
Decoder-Only
>Encoder-Only
>Encoder-Decoder
- 现行分布式并行策略下,可以扩展参数量上限 和 分布式集群规模的上限:
Decoder-Only
»Encoder-Only
»Encoder-Decoder
Encoder-Only 并不适合做 NLP 生成式任务(Chat)
- 目前在 CV 领域应用的比较多(ViT),输入/输出通常是固定像素大小的图片,token 之间没有非常强的先后依赖关系,因此先排除 BERT
总结
- T5 Scale up 到 100B、500B 的难度很大,训练成本的增加远远高于 GPT。
- 因此也许 100B 的 T5 训练 10T tokens 的模型能力比 100B 的 GPT 更强,但为此要支付的算力/时间成本远大于 100B GPT 训练 10T tokens,以至于:
- 没有公司愿意支付这样的代价我还不如支付相同的代价,让 GPT 多训练更多倍的 Tokens;
- 或者训练一个参数量大很多的 GPT
文本生成
解码策略
总结
- greedy decoding 贪心解码策略: 最原始、简单, 每步选择预测概率最高的token
- beam search 集束解码策略: 或束搜索, 每步选择多个候选, 简称 bs
- multinomial sampling 多项式采样解码策略:
- 通过各种改变 logits 参数(multinomial sampling,temperature,top_k,top_p等)实现生成文本的多样性
- contrastive search 对比搜索策略: 引入对比度惩罚的搜索方法
- 当前token与前面token相似性大,就减少生成概率,解决重复问题
- constrained beam-search decoding 受限束搜索解码
- 解码搜索过程中,引入自定义词表, 强制生成指定词表的token
- beam-search multinomial sampling: bs 改进, 引入多项式采样
- diverse beam-search decoding: 分组 beam-search 解码方式
- assisted decoding 辅助解码: 用另一个模型(称为辅助模型)的输出来辅助生成文本,一般是借助较小模型来加速生成候选 token
详见站内专题: 文本生成之序列解码
LLMs 复读机问题
LLMs 复读机问题
- 某些情况下,LLM 生成文本时会重复之前已经生成的内容,导致生成的文本缺乏多样性和创造性。
多种因素引起,包括
- 训练数据中的重复模式
- 处理长序列时的注意力机制失效
- 生成文本时,对过去信息的过度依赖等。
如何缓解 LLMs 复读机问题
- 数据增强:通过增加训练数据的多样性和复杂性,减少重复模式的出现。
- 模型改进:改进模型的结构和注意力机制,使其更好地处理长序列和避免过度依赖过去信息。
- 生成策略:在生成文本时采用多样化策略,如抽样生成或引入随机性,以增加生成文本的多样性。
长文本
如何生成更长的文本?
- 模型架构: 如 Transformer,可有效地处理长序列。
- 内存机制: 如外部记忆/缓存,来存储和检索长文本中的信息。
- 分块方法: 将长文本分割成更小的部分,然后分别处理这些部分。
Flash Attention 是一种用于加速自然语言处理模型中自注意力机制的推理过程的优化技术。
- 通过减少计算量和内存需求,使得在有限的资源下能够处理更长的序列。
Flash Attention 使用一种有效的矩阵乘法算法,可以在不牺牲准确性的情况下提高推理速度。
Paged Attention 是一种用于处理长序列的优化技术。
- 将注意力矩阵分页,使得只有当前页的注意力分数被计算和存储,从而大大减少了内存需求。
这种方法可以在不增加计算成本的情况下处理比内存容量更大的序列。
微软 YOCO
【2024-5-13】YOCO:打破传统Decoder-only架构,内存消耗仅为Transformer的六分之一
模型架构还只有三大类:Decoder-Only、Encoder-Only、Encoder-Decoder。
微软亚洲研究院推出了一种创新性的 Decoder-Decoder 架构 YOCO
(You Only Cache Once)。通过自解码器和交叉解码器的独特架构,YOCO 仅需缓存一次键值对,从而显著降低 GPU 内存的使用。
模型评估中,YOCO 展现出与同规模 Transformer 模型相媲美的性能,并在语言建模评估、模型大小扩展以及长上下文处理方面具有显著优势。特别是在降低 GPU 内存占用和缩短预填充延迟方面,
YOCO 整体架构设计如下,分为自解码器
(Self-Decoder)和交叉解码器
(Cross-Decoder)两部分。
YOCO 实现了“模型越大,内存越省”,为自然语言处理领域带来了全新的研究和应用范式。
- YOCO 仅缓存一次键值对,可大幅降低 GPU 内存需求,且保留全局注意力能力。
打破 GPT 系列开创的 Decoder-Only
架构——提出 Decoder-Decoder
新型架构,名为 YOCO
(You Only Cache Once)。
- 在处理 512K 上下文长度时,标准 Transformer 内存使用是 YOCO 的6.4倍,预填充延迟是 YOCO 的30.3倍,而 YOCO 的吞吐量提升到标准 Transformer 的9.6倍。
训练
P-tuning
P-tuning 与 P-tuning v2 区别:
- P-tuning:在输入序列的开头添加一个可学习的连续前缀,前缀的长度较短。
- P-tuning v2:在输入序列的开头添加多个可学习的连续前缀,前缀的长度较长。
内存超限
训练出现OOM错怎么解决?
当样本量规模增大时,训练出现OOM(Out of Memory)错误,可能由于显存不足导致的。
为了解决这个问题,可以尝试以下方法:
- 增加训练设备的显存,如使用更高性能的GPU或增加GPU数量。
- 调整批量大小,减少每次训练时处理的样本数量。
- 使用模型并行或数据并行技术,将模型或数据分片到多个设备上进行训练。
- 使用动态批处理,根据可用显存动态调整批量大小。
哪些省内存的大语言模型训练/微调/推理方法?
- 模型并行:将模型的不同部分分布在多个设备上。
- 张量切片:将模型的权重和激活分割成较小的块。
- 混合精度训练:使用FP16和INT8精度进行训练和推理。
- 优化器状态分割:如 ZeRO 技术,将优化器状态分割到不同设备上。
- 梯度累积:通过累积多个批次的梯度来减少每个批次的内存需求。
DDO 与 DPO 区别
DDO
(Dual Data Objectives)和DPO
(Dual Prompt Objectives)是两种不同的训练策略,用于提高大型语言模型的性能。
DDO
:同时优化两个数据集的目标,一个是通用数据集,另一个是特定领域数据集。让模型同时学习通用知识和特定领域的知识,提高模型的泛化能力和领域适应性。DPO
:同时用两个提示(prompt),一个是通用提示,另一个是特定领域提示。让模型在执行任务时,同时利用通用知识和特定领域的知识,提高模型在特定任务上的性能。
调参
【2024-8-23】LLMs 相关知识及面试题
- 1,怎么根据 llama 的 config 文件判断出模型参数量
- 2,batch size 怎么设置
- 答:应该设置在 16-128,太大或太小都不好, 太大了收敛的不好,太小了训练不稳定
- 3,说一下batch size和lr之间的关系
- 答:一般来说 bs设置大了,lr也可以设置的更大一点