【大语言模型】基础模型概念

Last updated on March 24, 2024 pm

大语言模型简介

1.llm概念

1.1 主流的开源模型体系

目前主流的开源LLM(语言模型)模型体系包括以下几个:

  1. GPT(Generative Pre-trained Transformer)系列:由OpenAI发布的一系列基于Transformer架构的语言模型,包括GPT、GPT-2、GPT-3等。GPT模型通过在大规模无标签文本上进行预训练,然后在特定任务上进行微调,具有很强的生成能力和语言理解能力。
  2. BERT(Bidirectional Encoder Representations from Transformers):由Google发布的一种基于Transformer架构的双向预训练语言模型。BERT模型通过在大规模无标签文本上进行预训练,然后在下游任务上进行微调,具有强大的语言理解能力和表征能力。
  3. XLNet:由CMU和Google Brain发布的一种基于Transformer架构的自回归预训练语言模型。XLNet模型通过自回归方式预训练,可以建模全局依赖关系,具有更好的语言建模能力和生成能力。
  4. RoBERTa:由Facebook发布的一种基于Transformer架构的预训练语言模型。RoBERTa模型在BERT的基础上进行了改进,通过更大规模的数据和更长的训练时间,取得了更好的性能。
  5. T5(Text-to-Text Transfer Transformer):由Google发布的一种基于Transformer架构的多任务预训练语言模型。T5模型通过在大规模数据集上进行预训练,可以用于多种自然语言处理任务,如文本分类、机器翻译、问答等。

这些模型在自然语言处理领域取得了显著的成果,并被广泛应用于各种任务和应用中。

1.2 prefix LM 和 causal LM 区别是什么?

Prefix LM(前缀语言模型)和Causal LM(因果语言模型)是两种不同类型的语言模型,它们的区别在于生成文本的方式和训练目标。

  1. Prefix LM:前缀语言模型是一种生成模型,它在生成每个词时都可以考虑之前的上下文信息。在生成时,前缀语言模型会根据给定的前缀(即部分文本序列)预测下一个可能的词。这种模型可以用于文本生成、机器翻译等任务。
  2. Causal LM:因果语言模型是一种自回归模型,它只能根据之前的文本生成后续的文本,而不能根据后续的文本生成之前的文本。在训练时,因果语言模型的目标是预测下一个词的概率,给定之前的所有词作为上下文。这种模型可以用于文本生成、语言建模等任务。

总结来说,前缀语言模型可以根据给定的前缀生成后续的文本,而因果语言模型只能根据之前的文本生成后续的文本。它们的训练目标和生成方式略有不同,适用于不同的任务和应用场景。

1.3 大模型LLM的训练目标

大型语言模型(Large Language Models,LLM)的训练目标通常是最大似然估计(Maximum Likelihood Estimation,MLE)。最大似然估计是一种统计方法,用于从给定数据中估计概率模型的参数。

在LLM的训练过程中,使用的数据通常是大量的文本语料库。训练目标是最大化模型生成训练数据中观察到的文本序列的概率。具体来说,对于每个文本序列,模型根据前面的上下文生成下一个词的条件概率分布,并通过最大化生成的词序列的概率来优化模型参数。

为了最大化似然函数,可以使用梯度下降等优化算法来更新模型参数,使得模型生成的文本序列的概率逐步提高。在训练过程中,通常会使用批量训练(batch training)的方法,通过每次处理一小批数据样本来进行参数更新。

1.4 涌现能力是啥原因?

大语言模型的涌现能力:现象与解释 - 知乎 (zhihu.com)

涌现能力(Emergent Ability)是指模型在训练过程中能够生成出令人惊喜、创造性和新颖的内容或行为。这种能力使得模型能够超出其训练数据所提供的内容,并产生出具有创造性和独特性的输出。

涌现能力的产生可以归因于以下几个原因:

  1. 任务的评价指标不够平滑:因为很多任务的评价指标不够平滑,导致我们现在看到的涌现现象。如果评价指标要求很严格,要求一字不错才算对,那么Emoji_movie任务我们就会看到涌现现象的出现。但是,如果我们把问题形式换成多选题,就是给出几个候选答案,让LLM选,那么随着模型不断增大,任务效果在持续稳定变好,但涌现现象消失,如上图图右所示。这说明评价指标不够平滑,起码是一部分任务看到涌现现象的原因。
  2. 复杂任务 vs 子任务:展现出涌现现象的任务有一个共性,就是任务往往是由多个子任务构成的复杂任务。也就是说,最终任务过于复杂,如果仔细分析,可以看出它由多个子任务构成,这时候,子任务效果往往随着模型增大,符合 Scaling Law,而最终任务则体现为涌现现象。
  3. Grokking (顿悟)来解释涌现:对于某个任务T,尽管我们看到的预训练数据总量是巨大的,但是与T相关的训练数据其实数量很少。当我们推大模型规模的时候,往往会伴随着增加预训练数据的数据量操作,这样,当模型规模达到某个点的时候,与任务T相关的数据量,突然就达到了最小要求临界点,于是我们就看到了这个任务产生了Grokking现象。

尽管涌现能力为模型带来了创造性和独特性,但也需要注意其生成的内容可能存在偏差、错误或不完整性。因此,在应用和使用涌现能力强的模型时,需要谨慎评估和验证生成的输出,以确保其质量和准确性。

1.5 为何现在的大模型大部分是Decoder only结构

  1. 自回归生成:Decoder-only结构适用于自回归生成任务,其中模型根据先前的输入生成下一个输出。这种结构在自然语言处理任务中非常有用,如文本生成、机器翻译和对话生成等。Decoder-only结构能够利用上下文信息来生成连续的输出序列,使得生成的结果更加准确和连贯
  2. 生成多样性:Decoder-only结构可以通过在训练期间使用不同的解码策略来生成多样化的结果。例如,在生成文本时,可以使用不同的采样策略(如贪婪采样或随机采样)或温度参数来调整生成的多样性。这种能力对于一些任务(如对话生成)非常重要,因为它可以产生更加有趣和多样化的回复。
  3. 模型训练和推理的一致性:Decoder-only结构使得模型的训练和推理过程更加一致。在训练期间,模型可以使用教师强制(teacher forcing)策略,即将真实的目标输出作为输入传递给解码器。而在推理期间,模型可以逐步生成输出,将前一个时间步的输出作为输入传递给下一个时间步。这种一致性有助于更好地控制模型的生成过程,并提高模型的稳定性和可靠性。

1.6 大模型架构介绍

LLM(Large Language Model,大型语言模型)是指基于大规模数据和参数量的语言模型。具体的架构可以有多种选择,以下是一种常见的大模型LLM的架构介绍:

  1. Transformer架构:大模型LLM常使用Transformer架构,它是一种基于自注意力机制的序列模型。Transformer架构由多个编码器层和解码器层组成,每个层都包含多头自注意力机制和前馈神经网络。这种架构可以捕捉长距离的依赖关系和语言结构,适用于处理大规模语言数据。
  2. 自注意力机制(Self-Attention):自注意力机制是Transformer架构的核心组件之一。它允许模型在生成每个词时,根据输入序列中的其他词来计算该词的表示。自注意力机制能够动态地为每个词分配不同的权重,从而更好地捕捉上下文信息。
  3. 多头注意力(Multi-Head Attention):多头注意力是自注意力机制的一种扩展形式。它将自注意力机制应用多次,每次使用不同的权重矩阵进行计算,得到多个注意力头。多头注意力可以提供更丰富的上下文表示,增强模型的表达能力。
  4. 前馈神经网络(Feed-Forward Network):在Transformer架构中,每个注意力层后面都有一个前馈神经网络。前馈神经网络由两个全连接层组成,通过非线性激活函数(如ReLU)进行变换。它可以对注意力层输出的表示进行进一步的映射和调整。
  5. 预训练和微调:大模型LLM通常采用预训练和微调的方法进行训练。预训练阶段使用大规模无标签数据,通过自监督学习等方法进行训练,使模型学习到丰富的语言知识。微调阶段使用有标签的特定任务数据,如文本生成、机器翻译等,通过有监督学习进行模型的微调和优化。

需要注意的是,大模型LLM的具体架构可能会因不同的研究和应用而有所不同。上述介绍的是一种常见的架构,但实际应用中可能会有一些变体或改进。

1.7 LLMs复读机问题

1.7.1 什么是 LLMs 复读机问题?

LLMs复读机问题(LLMs Parroting Problem)是指大型语言模型在生成文本时过度依赖输入文本的复制,而缺乏创造性和独特性。当面对一个问题或指令时,模型可能会简单地复制输入文本的一部分或全部内容,并将其作为生成的输出,而不是提供有意义或新颖的回应。

1.7.2 为什么会出现 LLMs 复读机问题?

  1. 数据偏差:大型语言模型通常是通过预训练阶段使用大规模无标签数据进行训练的。如果训练数据中存在大量的重复文本或者某些特定的句子或短语出现频率较高,模型在生成文本时可能会倾向于复制这些常见的模式。
  2. 训练目标的限制:大型语言模型的训练通常是基于自监督学习的方法,通过预测下一个词或掩盖词来学习语言模型。这样的训练目标可能使得模型更倾向于生成与输入相似的文本,导致复读机问题的出现。
  3. 缺乏多样性的训练数据:虽然大型语言模型可以处理大规模的数据,但如果训练数据中缺乏多样性的语言表达和语境,模型可能无法学习到足够的多样性和创造性,导致复读机问题的出现。
  4. 模型结构和参数设置:大型语言模型的结构和参数设置也可能对复读机问题产生影响。例如,模型的注意力机制和生成策略可能导致模型更倾向于复制输入的文本。

1.7.3 如何缓解 LLMs 复读机问题?

为了缓解LLMs复读机问题,可以尝试以下方法:

  1. 多样性训练数据:在训练阶段,使用多样性的语料库来训练模型,避免数据偏差和重复文本的问题。这可以包括从不同领域、不同来源和不同风格的文本中获取数据。
  2. 引入噪声:在生成文本时,引入一些随机性或噪声,例如通过采样不同的词或短语,或者引入随机的变换操作,以增加生成文本的多样性。这可以通过在生成过程中对模型的输出进行采样或添加随机性来实现。
  3. 温度参数调整:温度参数是用来控制生成文本的多样性的一个参数。通过调整温度参数的值,可以控制生成文本的独创性和多样性。较高的温度值会增加随机性,从而减少复读机问题的出现。
  4. Beam搜索调整:在生成文本时,可以调整Beam搜索算法的参数。Beam搜索是一种常用的生成策略,它在生成过程中维护了一个候选序列的集合。通过调整Beam大小和搜索宽度,可以控制生成文本的多样性和创造性。
  5. 后处理和过滤:对生成的文本进行后处理和过滤,去除重复的句子或短语,以提高生成文本的质量和多样性。可以使用文本相似度计算方法或规则来检测和去除重复的文本。
  6. 人工干预和控制:对于关键任务或敏感场景,可以引入人工干预和控制机制,对生成的文本进行审查和筛选,确保生成结果的准确性和多样性。

需要注意的是,缓解LLMs复读机问题是一个复杂的任务,没有一种通用的解决方案。不同的方法可能适用于不同的场景和任务,需要根据具体情况进行选择和调整。此外,解决复读机问题还需要综合考虑数据、训练目标、模型架构和生成策略等多个因素,需要进一步的研究和实践来提高大型语言模型的生成文本多样性和创造性。

1.8 LLMs输入句子长度理论上可以无限长吗?

理论上来说,LLMs(大型语言模型)可以处理任意长度的输入句子,但实际上存在一些限制和挑战。下面是一些相关的考虑因素:

  1. 计算资源:生成长句子需要更多的计算资源,包括内存和计算时间。由于LLMs通常是基于神经网络的模型,计算长句子可能会导致内存不足或计算时间过长的问题。
  2. 模型训练和推理:训练和推理长句子可能会面临一些挑战。在训练阶段,处理长句子可能会导致梯度消失或梯度爆炸的问题,影响模型的收敛性和训练效果。在推理阶段,生成长句子可能会增加模型的错误率和生成时间。
  3. 上下文建模:LLMs是基于上下文建模的模型,长句子的上下文可能会更加复杂和深层。模型需要能够捕捉长句子中的语义和语法结构,以生成准确和连贯的文本。

1.9 如何让大模型处理更长的文本?

要让大模型处理更长的文本,可以考虑以下几个方法:

  1. 分块处理:将长文本分割成较短的片段,然后逐个片段输入模型进行处理。这样可以避免长文本对模型内存和计算资源的压力。在处理分块文本时,可以使用重叠的方式,即将相邻片段的一部分重叠,以保持上下文的连贯性。
  2. 层次建模:通过引入层次结构,将长文本划分为更小的单元。例如,可以将文本分为段落、句子或子句等层次,然后逐层输入模型进行处理。这样可以减少每个单元的长度,提高模型处理长文本的能力。
  3. 部分生成:如果只需要模型生成文本的一部分,而不是整个文本,可以只输入部分文本作为上下文,然后让模型生成所需的部分。例如,输入前一部分文本,让模型生成后续的内容。
  4. 注意力机制:注意力机制可以帮助模型关注输入中的重要部分,可以用于处理长文本时的上下文建模。通过引入注意力机制,模型可以更好地捕捉长文本中的关键信息。
  5. 模型结构优化:通过优化模型结构和参数设置,可以提高模型处理长文本的能力。例如,可以增加模型的层数或参数量,以增加模型的表达能力。还可以使用更高效的模型架构,如Transformer等,以提高长文本的处理效率。

需要注意的是,处理长文本时还需考虑计算资源和时间的限制。较长的文本可能需要更多的内存和计算时间,因此在实际应用中需要根据具体情况进行权衡和调整。

2.LLama系列模型

2.1LLama

2.1.1 简介

LLaMA 所采用的 Transformer 结构和细节,与标准的 Transformer 架构不同的地方包括采用了前置层归一化(Pre-normalization)并使用 RMSNorm 归一化函数 (Normalizing Function)、激活函数更换为 SwiGLU,并使用了旋转位置嵌入(RoP),整体 Transformer 架构与 GPT-2 类似。

LLAMA结构

2.1.2 RMSNorm归一化函数

为了使得模型训练过程更加稳定,GPT-2 相较于 GPT 就引入了前置层归一化方法,将第一个层归一化移动到多头自注意力层之前,第二个层归一化也移动到了全连接层之前,同时残差连接的位置也调整到了多头自注意力层与全连接层之后。层归一化中也采用了 RMSNorm 归一化函数。 针对输入向量 a,RMSNorm 函数计算公式如下 \[ R M S(a)=\sqrt{\frac{1}{n} \sum_{i=1}^{n} a_{i}^{2}} \]

\[ \bar{a}_{i}=\frac{a_{i}}{R M S(\boldsymbol{a})} \]

此外,RMSNorm 还可以引入可学习的缩放因子 $ g_ i $和偏移参数 \(b_i\),从而得到 \(\bar{a}_{i}=\frac{a_{i}}{\operatorname{RMS}(\boldsymbol{a})} g_{i}+b_{i}\)。 RMSNorm 在 HuggingFace Transformer 库中代码实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class LlamaRMSNorm(nn.Module):
def __init__(self, hidden_size, eps=1e-6):
"""
LlamaRMSNorm is equivalent to T5LayerNorm
"""
super().__init__()
self.weight = nn.Parameter(torch.ones(hidden_size))
self.variance_epsilon = eps # eps 防止取倒数之后分母为 0

def forward(self, hidden_states):
input_dtype = hidden_states.dtype
variance = hidden_states.to(torch.float32).pow(2).mean(-1, keepdim=True)
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon) # weight 是末尾乘的可训练参数, 即 g_i

return (self.weight * hidden_states).to(input_dtype)

为什么要用RMSNorm优势在哪里?

不用计算均值,直接算一次就能得到结果

2.1.3 SwiGLU激活函数

SwiGLU激活函数是相较于 ReLU 函数在大部分评测中都有不少提升。在 LLaMA 中全连接层使用带有 SwiGLU 激活函数的 FFN(Position-wise Feed-Forward Network)的计算公式如下:

\[ \operatorname{FFN}_{\text {SwiGLU }}\left(\boldsymbol{x}, \boldsymbol{W}, \boldsymbol{V}, \boldsymbol{W}_{2}\right)=\operatorname{SwiGLU}(\boldsymbol{x}, \boldsymbol{W}, \boldsymbol{V}) \boldsymbol{W}_{2} \]

\[ \operatorname{SwiGLU}(\boldsymbol{x}, \boldsymbol{W}, \boldsymbol{V})=\operatorname{Swish}_{\beta}(x \boldsymbol{W}) \otimes \boldsymbol{x} \boldsymbol{V} \]

\[ \operatorname{Swish}_{\beta}(\boldsymbol{x})=\boldsymbol{x} \sigma(\boldsymbol{\beta} \boldsymbol{x}) \]

其中,\(σ(x)\) 是 Sigmoid 函数。下图给出了 Swish 激活函数在参数 \(β\) 不同取值下的形状。可以看 到当 \(β\) 趋近于 0 时,Swish 函数趋近于线性函数 \(y = x\),当 $ β \(趋近于无穷大时,Swish 函数趋近于 ReLU 函数,\)β$ 取值为 1 时,Swish 函数是光滑且非单调。在 HuggingFace 的 Transformer 库中 Swish1 函数使用 silu 函数代替。

Swish激活函数参数变化

LLaMA中直接将FFN中的ReLU替换为SwiGLU,并将维度放缩为\((2/3) ⋅ 4d\)

LLAMA中FFN的实现

2.1.4 旋转位置嵌入(RoPE)

在位置编码上,使用旋转位置嵌入(Rotary Positional Embeddings,RoPE)代替原有的绝对位置编码。RoPE 借助了复数的思想,出发点是通过绝对位置编码的方式实现相对位置编码。其目标是通过下述运算来给 qk 添加绝对位置信息:

\[ \tilde{\boldsymbol{q}}_{m}=f(\boldsymbol{q}, m), \tilde{\boldsymbol{k}}_{n}=f(\boldsymbol{k}, n) \]

经过上述操作后,\(\tilde{\boldsymbol{q}}_{m}\)\(\tilde{\boldsymbol{k}}_{n}\)就带有位置m和n的绝对位置信息。

最终可以得到二维情况下用复数表示的 RoPE:

\[ f(\boldsymbol{q}, m)=R_{f}(\boldsymbol{q}, m) e^{i \Theta_{f}(\boldsymbol{q}, m)}=\|\boldsymbol{q}\| e^{i(\Theta(\boldsymbol{q})+m \theta)}=\boldsymbol{q} e^{i m \theta} \]

根据复数乘法的几何意义,上述变换实际上是对应向量旋转,所以位置向量称为“旋转式位置编码”。还可以使用矩阵形式表示

\[ f(\boldsymbol{q}, m)=\left(\begin{array}{cc}\cos m \theta & -\sin \cos m \theta \\ \sin m \theta & \cos m \theta\end{array}\right)\left(\begin{array}{l}\boldsymbol{q}_{0} \\ \boldsymbol{q}_{1}\end{array}\right) \]

根据内积满足线性叠加的性质,任意偶数维的 RoPE,都可以表示为二维情形的拼接,即:

\[ f(\boldsymbol{q}, m)=\underbrace{\left(\begin{array}{ccccccc}\cos m \theta_{0} & -\sin m \theta_{0} & 0 & 0 & \cdots & 0 & 0 \\ \sin m \theta_{0} & \cos m \theta_{0} & 0 & 0 & \cdots & 0 & 0 \\ 0 & 0 & \cos m \theta_{1} & -\sin m \theta_{1} & \cdots & 0 & 0 \\ 0 & 0 & \sin m \theta_{1} & \cos m \theta_{1} & \cdots & 0 & 0 \\ \cdots & \cdots & \cdots & \cdots & \ddots & \cdots & \cdots \\ 0 & 0 & 0 & 0 & \cdots & \cos m \theta_{d / 2-1} & -\sin m \theta_{d / 2-1} \\ 0 & 0 & 0 & 0 & \cdots & \sin m \theta_{d / 2-1} & \cos m \theta_{d / 2-1}\end{array}\right)}_{\boldsymbol{R}_{d}}\left(\begin{array}{c}\boldsymbol{q}_{0} \\ \boldsymbol{q}_{1} \\ \boldsymbol{q}_{2} \\ \boldsymbol{q}_{3} \\ \cdots \\ \boldsymbol{q}_{d-2} \\ \boldsymbol{q}_{d-1}\end{array}\right) \]

旋转位置编码

RoPE 在 HuggingFace Transformer 库中代码实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import torch

def precompute_freqs_cis(dim: int, end: int, constant: float = 10000.0):
'''
计算cos和sin的值,cos值在实部,sin值在虚部,类似于 cosx+j*sinx
:param dim: q,k,v的最后一维,一般为emb_dim/head_num
:param end: 句长length
:param constant: 这里指10000
:return:
复数计算 torch.polar(a, t)输出, a*(cos(t)+j*sin(t))
'''
# freqs: 计算 1/(10000^(2i/d) ),将结果作为参数theta
# 形式化为 [theta_0, theta_1, ..., theta_(d/2-1)]
freqs = 1.0 / (constant ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim)) # [d/2]

# 计算m
t = torch.arange(end, device=freqs.device) # [length]
# 计算m*theta
freqs = torch.outer(t, freqs).float() # [length, d/2]
# freqs形式化为 [m*theta_0, m*theta_1, ..., m*theta_(d/2-1)],其中 m=0,1,...,length-1

# 计算cos(m*theta)+j*sin(m*theta)
freqs_cis = torch.polar(torch.ones_like(freqs), freqs) # complex64
# freqs_cis: [cos(m*theta_0)+j*sin(m*theta_0), cos(m*theta_1)+j*sin(m*theta_1),), ..., cos(m*theta_(d/2-1))+j*sin(m*theta_(d/2-1))]
# 其中j为虚数单位, m=0,1,...,length-1
return freqs_cis # [length, d/2]

def reshape_for_broadcast(freqs_cis: torch.Tensor, x: torch.Tensor):
ndim = x.ndim
assert 0 <= 1 < ndim
assert freqs_cis.shape == (x.shape[1], x.shape[-1])
shape = [d if i == 1 or i == ndim - 1 else 1 for i, d in enumerate(x.shape)] # (1, length, 1, d/2)
return freqs_cis.view(*shape) # [1, length, 1, d/2]

def apply_rotary_emb(xq: torch.Tensor, xk: torch.Tensor, freqs_cis: torch.Tensor,):
# 先将xq维度变为[bs, length, head, d/2, 2], 利用torch.view_as_complex转变为复数
# xq:[q0, q1, .., q(d-1)] 转变为 xq_: [q0+j*q1, q2+j*q3, ..., q(d-2)+j*q(d-1)]
xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2)) # [bs, length, head, d/2]
# 同样的,xk_:[k0+j*k1, k2+j*k3, ..., k(d-2)+j*k(d-1)]
xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))

freqs_cis = reshape_for_broadcast(freqs_cis, xq_) # [1, length, 1, d/2]
# 下式xq_ * freqs_cis形式化输出,以第一个为例, 如下
# (q0+j*q1)(cos(m*theta_0)+j*sin(m*theta_0)) = q0*cos(m*theta_0)-q1*sin(m*theta_0) + j*(q1*cos(m*theta_0)+q0*sin(m*theta_0))
# 上式的实部为q0*cos(m*theta_0)-q1*sin(m*theta_0),虚部为q1*cos(m*theta_0)+q0*sin(m*theta_0)
# 然后通过torch.view_as_real函数,取出实部和虚部,维度由[bs, length, head, d/2]变为[bs, length, head, d/2, 2],最后一维放实部与虚部
# 最后经flatten函数将维度拉平,即[bs, length, head, d]
# 此时xq_out形式化为 [实部0,虚部0,实部1,虚部1,..., 实部(d/2-1), 虚部(d/2-1)]
xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(3) # [bs, length, head, d]
# 即为新生成的q

xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(3)
return xq_out.type_as(xq), xk_out.type_as(xk)

if __name__=='__main__':
# (bs, length, head, d)
q = torch.randn((2, 10, 12, 32)) # q=[q0, q1, .., qd-1]
k = torch.randn((2, 10, 12, 32))
v = torch.randn((2, 10, 12, 32))
freqs_cis= precompute_freqs_cis(dim=32, end=10, constant= 10000.0)
# print(freqs_cis.detach().numpy())

q_new, k_new = apply_rotary_emb(xq=q, xk=k, freqs_cis=freqs_cis)
print()

1.2 Alpaca

1.2.1 简介

Stanford Alpaca: An Instruction-following LLaMA Model

Alpaca是在LLaMA基础上使用52K指令数据精调的预训练模型,作者只用了不到600美元的成本训练出了该模型(数据$500 + 机器$100)。初步实验结果表明Alpaca可以达到与OpenAI text-davinci-003相匹敌的效果

1.2.2 微调方法

  1. 第一步:构造175条self-instruct 种子示例任务
  2. 第二步:基于上述种子任务,利 用text-davinci-003爬取指令数据
  3. 第三步:使用爬取下来的52K指令 数据在LLaMA上进行精调,最终 得到Alpaca
Alpaca微调方法

1.2.3 Self-instruct数据构造

首先由人工构造175条种子数据

1
2
3
4
5
6
7
{
"id": "seed_task_25",
"name": "perfect_numbers",
"instruction": "Find the four smallest perfect numbers.",
"instances": [{ "input": "", "output": "6, 28, 496, and 8128”}],
"is_classification": false
}

将“爬取要求”和种子数据进行适当组合,送入textdavinci-003,要求生成类似的指令数据。要求包括:提升指令多样性、包含真实数据、字数 要求、语言要求、拒绝不合适指令等

1.2.4 指令数据格式

  • instruction: 描述模型需要执行的指令内容
  • input(可选): 任务上下文或输入信息,例如当指令是“对文章进行总结”,则input是文章内容
  • output: 由text-davinci-003生成的针对指令的回复
指令数据格式

1.3.Llama-2

1.3.1 简介

Llama 2: Open Foundation and Fine-Tuned Chat Models

2023年7月,Meta推出了Llama-2开源大模型,并且推出了Llama-2-Chat对话模型

与一代LLaMA主要区别体现在更多的训练数据、更⻓的上下文窗口、GQA技术

LLAMA2与LLAMA的各项指标的对比

二次分发是什么?

模型结构的变动主要是体现在GQAFFN缩放上

  • MHA改成GQA:整体参数量会有减少
  • FFN模块矩阵维度有扩充:增强泛化能力,整体参数量增加
  • 上下文长度是llama两倍(长度从2048->4096) 训练语料增加约 40%,体现在1.4T->2.0T的Tokens llama2-34B和llama2-70B使用了GQA,加速模型训练和推理速度

1.3.2 GQA

GQA和MQA都是注意力的变体,其中多个查询头关注相同的键和值头,以减少推理过程中 KV 缓存的大小,并可以显著提高推理吞吐量。

MHA、GQA、MQA的区别和联系,具体的优点如下:

  • Mutil-Head Attention 因为自回归模型生成回答时,需要前面生成的KV缓存起来,来加速计算。
  • Multi-Query Attention 多个头之间可以共享KV对,因此速度上非常有优势,实验验证大约减少30-40%吞吐。
  • Group Query Attention 没有像MQA那么极端,将query分组,组内共享KV,效果接近MQA,速度上与MQA可比较。
注意力变体

Llama-2中使用了8个KV映射,即GQA-8,GQA在多数任务上与MHA效果相当,且平均效果优于MQA;GQA和MQA均比MHA有更好的吞吐量

1.3.3 源码

MQA代码示意图

2.ChatGLM

2.1 背景

主流的预训练框架主要有三种:

  1. autoregressive自回归模型(AR模型):代表作GPT。本质上是一个left-to-right的语言模型。通常用于生成式任务,在长文本生成方面取得了巨大的成功,比如自然语言生成(NLG)领域的任务:摘要、翻译或抽象问答。当扩展到十亿级别参数时,表现出了少样本学习能力。缺点是单向注意力机制,在NLU任务中,无法完全捕捉上下文的依赖关系。
  2. autoencoding自编码模型(AE模型):代表作BERT。是通过某个降噪目标(比如MLM)训练的双向文本编码器。编码器会产出适用于NLU任务的上下文表示,但无法直接用于文本生成。
  3. encoder-decoder(Seq2seq模型):代表作T5。采用双向注意力机制,通常用于条件生成任务,比如文本摘要、机器翻译等。

三种预训练框架各有利弊,没有一种框架在以下三种领域的表现最佳:自然语言理解(NLU)、无条件生成以及条件生成。T5曾经尝试使用MTL的方式统一上述框架,然而自编码和自回归目标天然存在差异,简单的融合自然无法继承各个框架的优点。

在这个天下三分的僵持局面下,GLM诞生了。

GLM模型基于autoregressive blank infilling方法,结合了上述三种预训练模型的思想

2.2 GLM预训练框架

GLM特点

  1. 自编码思想:在输入文本中,随机删除连续的tokens。
  2. 自回归思想:顺序重建连续tokens。在使用自回归方式预测缺失tokens时,模型既可以访问corrupted文本,又可以访问之前已经被预测的spans。
  3. span shuffling + 二维位置编码技术
  4. 通过改变缺失spans的数量和长度,自回归空格填充目标可以为条件生成以及无条件生成任务预训练语言模型。

2.2.1 自回归空格填充任务

给定一个输入文本\(x=\left[x_{1}, \ldots x_{n}\right]\),可以采样得到多个文本spans \(\left\{s_{1}, \ldots s_{m}\right\}\)。为了充分捕捉各spans之间的相互依赖关系,可以对spans的顺序进行随机排列,得到所有可能的排列集合\(Z_m\),其中:\(S_{z<i}=\left[s_{z_{1}}, \ldots, s_{z_{i-1}}\right]\)。所以预训练目标很清晰:

\[ \max _{\theta} \mathbb{E}_{\boldsymbol{z} \sim Z_{m}}\left[\sum_{i=1}^{m} \log p_{\theta}\left(\boldsymbol{s}_{z_{i}} \mid \boldsymbol{x}_{\text {corrupt }}, \boldsymbol{s}_{\boldsymbol{z}_{<i}}\right)\right] \]

GLM自回归空格填充任务的技术细节:

  1. 输入\(x\)可以被分成两部分:Part A是被mask的文本 \(x_{\text {corrupt }}\),Part B由masked spans组成。假设原始输入文本是\([x1, x2, x3, x4, x5, x6]\),采样的两个文本片段是\([x3]\)以及\([x5, x6]\)。那么mask后的文本序列是:\(x1, x2, [M], x4, [M]\),即Part A;同时我们需要对Part B的片段进行shuffle。每个片段使用[S]填充在开头作为输入,使用[E]填充在末尾作为输出。
  2. 二维位置编码:Transformer使用位置编码来标记tokens中的绝对和相对位置。在GLM中,使用二维位置编码,第一个位置id用来标记Part A中的位置,第二个位置id用来表示跨度内部的相对位置。这两个位置id会通过embedding表被投影为两个向量,最终都会被加入到输入token的embedding表达中。
  3. 观察GLM中自定义attention mask的设计,非常巧妙:
    1. Part A中的tokens彼此可见,但是不可见B中的任意tokens。
    2. Part B tokens可见Part A。
    3. Part B tokens可见B中过去的tokens,不可见B中未来的tokens。
  4. 采样方式:文本片段的采样遵循泊松分布,重复采样,直到原始tokens中有15%被mask。
  5. 总结:模型可以自动学习双向encoder(Part A)以及单向decoder(Part B)。
自回归空格填充任务

2.2.2 多目标预训练

上述方法适合于NLU任务。作者希望可以训练一个既可以解决NLU任务,又具备文本生成能力的模型。因此除了空格填充目标之外,还需要增加一个生成长文本目标的任务。具体包含以下两个目标:

  1. 文档级别。从文档中采样一个文本片段进行mask,且片段长度为文档长度的50%~100%。这个目标用于长文本生成。
  2. 句子级别。限制被mask的片段必须是完整句子。多个片段需覆盖原始tokens的15%。这个目标是用于预测完整句子或者段落的seq2seq任务。

2.2.3 模型结构

GLM在原始single Transformer的基础上进行了一些修改:

  1. 重组了LN和残差连接的顺序;
  2. 使用单个线性层对输出token进行预测;
  3. 激活函数从ReLU换成了GeLUS。

但我觉得这部分的修改比较简单常见。核心和亮点还是空格填充任务的设计。

2.2.4 GLM微调

对于下游NLU任务来说,通常会将预训练模型产出的序列或tokens表达作为输入,使用线性分类器预测label。所以预训练与微调之间存在天然不一致。

作者按照PET的方式,将下游NLU任务重新表述为空白填充的生成任务。具体来说,比如给定一个已标注样本(x, y),将输入的文本x转换成一个包含mask token的完形填空问题。比如,情感分类任务可以表述为:"{SENTENCE}. It’s really [MASK]"。输出label y也同样会被映射到完形填空的答案中。“positive” 和 “negative” 对应的标签就是“good” 和 “bad。

其实,预训练时,对较长的文本片段进行mask,以确保GLM的文本生成能力。但是在微调的时候,相当于将NLU任务也转换成了生成任务,这样其实是为了适应预训练的目标。但难免有一些牵强。

GLM的微调

2.3 ChatGLM-2

2.3.1 主要创新

  1. 更长的上下文基于 FlashAttention** 技术,将基座模型的上下文长度(Context Length)由 ChatGLM-6B 的 2K 扩展到了 32K**,并在对话阶段使用 8K 的上下文长度训练。对于更长的上下文,发布了 ChatGLM2-6B-32K 模型。LongBench 的测评结果表明,在等量级的开源模型中,ChatGLM2-6B-32K 有着较为明显的竞争优势。
  2. 更强大的性能:基于 ChatGLM 初代模型的开发经验,全面升级了 ChatGLM2-6B 的基座模型。ChatGLM2-6B 使用了 GLM** 的混合目标函数**,经过了 1.4T 中英标识符的预训练与人类偏好对齐训练,评测结果显示,相比于初代模型,ChatGLM2-6B 在 MMLU(+23%)、CEval(+33%)、GSM8K(+571%) 、BBH(+60%)等数据集上的性能取得了大幅度的提升,在同尺寸开源模型中具有较强的竞争力。
  3. 更高效的推理:基于 Multi-Query Attention 技术,ChatGLM2-6B 有更高效的推理速度和更低的显存占用:在官方的模型实现下,推理速度相比初代提升了 42%,INT4 量化下,6G 显存支持的对话长度由 1K 提升到了 8K。
  4. 更开放的协议:ChatGLM2-6B 权重对学术研究完全开放,在填写问卷进行登记后亦允许免费商业使用

2.3.2 与ChatGLM的变化

  1. 使用了RoPE替换二维位置编码。这也是GLM中提出的亮点设计之一。但是目前大部分主流的LLMs都在使用RoPE,所以大势所趋。当前版本仍然采用了最初的RoPE设计,事实上现在的RoPE经过了xPOS→线性内插→NTK-Aware Scaled RoPE→…若干次进化。
  2. Multi-Query Attention:这是一种共享机制的Attention,相比Multi-Head Attention,其Query部分没有区别,Key和Value可以只用一个Head。计算时,对Key和Value进行expand或者repeat操作,使它们填充到与Query一样的维度,后续计算就与Multi-Head Attention没区别。
  3. Attention Mask: V1的attention mask分了2部分,Part A和Part B,Part A部分是双向Attention(代码中的prefix_attention_mask),Part B部分是Causal Attention(原代码文件中的get_masks函数)。在V2版本,全部换成了Causal Attention,不再区分是Part A还是Part B,完全变成了decoder-only的架构
  4. 多目标任务:Chat版本主要还是用的gMask生成式任务,但是在V1版本的代码还能看到mask、gMask等字样,V2已经摒弃了这些特殊token,原因与Attention Mask一致,均因为变成了decoder-only的架构,不再需要区分Part A和Part B。

2.3.3 ChatGLM-3

省流:ChatGLM2与ChatGLM3模型架构是完全一致的,ChatGLM与后继者结构不同。可见ChatGLM3相对于ChatGLM2没有模型架构上的改进。

相对于ChatGLM,ChatGLM2、ChatGLM3模型上的变化:

  1. 词表的大小从ChatGLM的150528缩小为65024 (一个直观的体验是ChatGLM2、3加载比ChatGLM快不少)
  2. 位置编码从每个GLMBlock一份提升为全局一份
  3. SelfAttention之后的前馈网络有不同。ChatGLM用GELU(Gaussian Error Linear Unit)做激活;ChatGLM用Swish-1做激活。而且ChatGLM2、3应该是修正了之前的一个bug,因为GLU(Gated Linear Unit)本质上一半的入参是用来做门控制的,不需要输出到下层,所以ChatGLM2、3看起来前后维度不一致(27392->13696)反而是正确的。

3.BERT

3.1 BERT用字粒度和词粒度的优缺点有哪些?

BERT可以使用字粒度(character-level)和词粒度(word-level)两种方式来进行文本表示,它们各自有优缺点:

字粒度(Character-level):

  • 优点:处理未登录词(Out-of-Vocabulary,OOV):字粒度可以处理任意字符串,包括未登录词,不需要像词粒度那样遇到未登录词就忽略或使用特殊标记。对于少见词和低频词,字粒度可以学习更丰富的字符级别表示,使得模型能够更好地捕捉词汇的细粒度信息。
  • 缺点:计算复杂度高:使用字粒度会导致输入序列的长度大大增加,进而增加模型的计算复杂度和内存消耗。需要更多的训练数据:字粒度模型对于少见词和低频词需要更多的训练数据来学习有效的字符级别表示,否则可能会导致过拟合。

词粒度(Word-level):

  • 优点:计算效率高:使用词粒度可以大大减少输入序列的长度,从而降低模型的计算复杂度和内存消耗。学习到更加稳定的词级别表示:词粒度模型可以学习到更加稳定的词级别表示,特别是对于高频词和常见词,有更好的表示能力。
  • 缺点:处理未登录词(OOV):词粒度模型无法处理未登录词,遇到未登录词时需要采用特殊处理(如使用未登录词的特殊标记或直接忽略)。对于多音字等形态复杂的词汇,可能无法准确捕捉其细粒度的信息。

3.2 BERT的Encoder与Decoder掩码有什么区别?

Encoder主要使用自注意力掩码和填充掩码,而Decoder除了自注意力掩码外,还需要使用编码器-解码器注意力掩码来避免未来位置信息的泄露。这些掩码操作保证了Transformer在处理自然语言序列时能够准确、有效地进行计算,从而获得更好的表现。

3.3 BERT用的是transformer里面的encoder还是decoder?

BERT使用的是Transformer中的Encoder部分,而不是Decoder部分。

Transformer模型由Encoder和Decoder两个部分组成。Encoder用于将输入序列编码为一系列高级表示,而Decoder用于基于这些表示生成输出序列。

在BERT模型中,只使用了Transformer的Encoder部分,并且对其进行了一些修改和自定义的预训练任务,而没有使用Transformer的Decoder部分。

3.4 为什么BERT选择mask掉15%这个比例的词,可以是其他的比例吗?

BERT选择mask掉15%的词是一种经验性的选择,是原论文中的一种选择,并没有一个固定的理论依据,实际中当然可以尝试不同的比例,15%的比例是由BERT的作者在原始论文中提出,并在实验中发现对于BERT的训练效果是有效的

3.5 为什么BERT在第一句前会加一个[CLS] 标志?

BERT在第一句前会加一个 [CLS] 标志,最后一层该位对应向量可以作为整句话的语义表示,从而用于下游的分类任务等。为什么选它?因为与文本中已有的其它词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个词的语义信息,从而更好的表示整句话的语义。

具体来说,self-attention是用文本中的其它词来增强目标词的语义表示,但是目标词本身的语义还是会占主要部分的,因此,经过BERT的12层,每次词的embedding融合了所有词的信息,可以去更好的表示自己的语义。而 [CLS] 位本身没有语义,经过12层,得到的是attention后所有词的加权平均,相比其他正常词,可以更好的表征句子语义。

3.6 BERT非线性的来源在哪里?

主要来自两个地方:前馈层的gelu激活函数self-attention

前馈神经网络层:在BERT的Encoder中,每个自注意力层之后都跟着一个前馈神经网络层。前馈神经网络层是全连接的神经网络,通常包括一个线性变换和一个非线性的激活函数,如gelu。这样的非线性激活函数引入了非线性变换,使得模型能够学习更加复杂的特征表示。

self-attention layer:在自注意力层中,查询(Query)、键(Key)、值(Value)之间的点积得分会经过softmax操作,形成注意力权重,然后将这些权重与值向量相乘得到每个位置的自注意输出。这个过程中涉及了softmax操作,使得模型的计算是非线性的。

3.7 BERT训练时使用的学习率 warm-up 策略是怎样的?为什么要这么做?

在BERT的训练中,使用了学习率warm-up策略,这是为了在训练的早期阶段增加学习率,以提高训练的稳定性和加快模型收敛

学习率warm-up策略的具体做法是,在训练开始的若干个步骤(通常是一小部分训练数据的迭代次数)内,将学习率逐渐从一个较小的初始值增加到预定的最大学习率。在这个过程中,学习率的变化是线性的,即学习率在warm-up阶段的每个步骤按固定的步幅逐渐增加。学习率warm-up的目的是为了解决BERT在训练初期的两个问题:

  • 不稳定性:在训练初期,由于模型参数的随机初始化以及模型的复杂性,模型可能处于一个较不稳定的状态。此时使用较大的学习率可能导致模型的参数变动太大,使得模型很难收敛,学习率warm-up可以在这个阶段将学习率保持较小,提高模型训练的稳定性。
  • 避免过拟合:BERT模型往往需要较长的训练时间来获得高质量的表示。如果在训练的早期阶段就使用较大的学习率,可能会导致模型在训练初期就过度拟合训练数据,降低模型的泛化能力。通过学习率warm-up,在训练初期使用较小的学习率,可以避免过度拟合,等模型逐渐稳定后再使用较大的学习率进行更快的收敛。

3.8 在BERT应用中,如何解决长文本问题?

在BERT应用中,处理长文本问题有以下几种常见的解决方案:

  • 截断与填充:将长文本截断为固定长度或者进行填充。BERT模型的输入是一个固定长度的序列,因此当输入的文本长度超过模型的最大输入长度时,需要进行截断或者填充。通常,可以根据任务的要求,选择适当的最大长度,并对文本进行截断或者填充,使其满足模型输入的要求。
  • Sliding Window:将长文本分成多个短文本,然后分别输入BERT模型。这种方法被称为Sliding Window技术。具体来说,将长文本按照固定的步长切分成多个片段,然后分别输入BERT模型进行处理。每个片段的输出可以进行进一步的汇总或者融合,得到最终的表示。
  • Hierarchical Model:使用分层模型来处理长文本,其中底层模型用于处理短文本片段,然后将不同片段的表示进行汇总或者融合得到整个长文本的表示。这样的分层模型可以充分利用BERT模型的表示能力,同时处理长文本。
  • Longformer、BigBird等模型:使用专门针对长文本的模型,如Longformer和BigBird。这些模型采用了不同的注意力机制,以处理超长序列,并且通常在处理长文本时具有更高的效率。
  • Document-Level Model:将文本看作是一个整体,而不是将其拆分成句子或段落,然后输入BERT模型进行处理。这样的文档级模型可以更好地捕捉整个文档的上下文信息,但需要更多的计算资源。

【大语言模型】基础模型概念
https://lihaibineric.github.io/2024/03/08/dl_llm_model/
Author
Haibin Li
Posted on
March 8, 2024
Updated on
March 24, 2024
Licensed under