datawhale组队学习--大语言模型—task4:Transformer架构及详细配置

第五章  模型架构

在前述章节中已经对预训练数据的准备流程(第 4 章)进行了介绍。本章主 要讨论大语言模型的模型架构选择,主要围绕 Transformer 模型(第 5.1 节)、详细 配置(第 5.2 节)、主流架构(第 5.3 节)、长上下文模型(第 5.4 节)以及创新型 模型 5.5 节)等五个主要方面展开讨论。表 5.1 列举了一些典型的大语言模型的详细配置。

5.1 大语言模型架构配置表(L 表示层数,N 表示注意力头数,H 表示隐藏状态的大小)

模型

类别

大小

归一化

位置编码

激活函数

L

N

H

GPT-3

因果

175B

Pre Layer

Learned

GELU

96

96

12288

PanGU- \alpha

因果

207B

Pre Layer

Learned

GELU

64

128

16384

OPT

因果

175B

Pre Layer

Learned

ReLU

96

96

12288

PaLM

因果

540B

Pre Layer

RoPE

SwiGLU

118

48

18432

BLOOM

因果

176B

Pre Layer

ALiBi

GELU

70

112

14336

MT-NLG

因果

530B

-

-

-

105

128

20480

Gopher

因果

280B

Pre RMS

Relative

-

80

128

16384

Chinchilla

因果

70B

Pre RMS

Relative

-

80

64

8192

Galactica

因果

120B

Pre Layer

Learned

GELU

96

80

10240

LaMDA

因果

137B

-

Relative

GeGLU

64

128

8192

Jurassic-1

因果

178B

Pre Layer

Learned

GELU

76

96

13824

LLaMA-2

因果

70B

Pre RMS

RoPE

SwiGLU

80

64

8192

Pythia

因果

12B

Pre Layer

RoPE

GELU

36

40

5120

Baichuan-2

因果

13B

Pre RMS

ALiBi

SwiGLU

40

40

5120

Qwen-1.5

因果

72B

Pre RMS

RoPE

SwiGLU

80

64

8192

InternLM-2

因果

20B

Pre RMS

RoPE

SwiGLU

48

48

6144

Falcon

因果

180B

Pre Layer

RoPE

GELU

80

232

14848

MPT

因果

30B

Pre Layer

ALiBi

GELU

48

64

7168

Mistral

因果

7B

Pre RMS

RoPE

SwiGLU

32

32

4096

Gemma

因果

7B

Pre RMS

RoPE

GELU

28

16

3072

DeepSeek

因果

67B

Pre RMS

RoPE

SwiGLU

95

64

8192

Yi

因果

34B

Pre RMS

RoPE

SwiGLU

60

56

7168

YuLan

因果

12B

Pre RMS

RoPE

SwiGLU

40

38

4864

GLM-130B

前缀

130B

Post Deep

RoPE

GeGLU

70

96

12288

T5

编-解

11B

Pre RMS

Relative

ReLU

24

128

1024

5.1.Transformer 模型

        当前主流的大语言模型都基于 Transformer 模型进行设计的。Transformer 是由 多层的多头自注意力(Multi-head Self-attention)模块堆叠而成的神经网络模型。原 始的 Transformer 模型由编码器和解码器两个部分构成,而这两个部分实际上可以 独立使用,例如基于编码器架构的 BERT 模型和解码器架构的 GPT 模型。 与 BERT 等早期的预训练语言模型相比,大语言模型的特点是使用了更长的向量 维度、更深的层数,进而包含了更大规模的模型参数,并主要使用解码器架构,对 于 Transformer 本身的结构与配置改变并不大。本部分内容将首先介绍 Transformer 模型的基本组成,包括基础的输入、多头自注意力模块和前置网络层;接着分别 介绍 Transformer 模型中的编码器和解码器模块。

5.1.1  输入编码

        在 Transformer 模型中,输入的词元序列 (\mu = [\mu _{1}, \mu _{2}, . . . , \mu _{r} ]) 首先经过一个 输入嵌入模块(Input Embedding Module)转化成词向量序列。具体来说,为了捕获 词汇本身的语义信息,每个词元在输入嵌入模块中被映射成为一个可学习的、具有固定维度的词向量 \nu _{t}\mathbb{R}^{H} 。由于 Transformer 的编码器结构本身无法识别序列中元素的顺序,位置编码(Position Embedding, PE)被引入来表示序列中的位置信 息。给定一个词元 \mu _{t} ,位置编码根据其在输入中的绝对位置分配一个固定长度的 嵌入向量 \rho _{t}\mathbb{R}^{H} 。然后,每个词元对应的词向量和位置向量将直接相加,生成了 最终的输入嵌入序列 X = [x_{1}, . . . , x_{T} ],并且被传入到后续层中:

                                                                             x_{t} = \nu _{t} + \rho _{t}                                                 (5.1)

        通过这种建模方法的表示,Transformer 模型可以利用位置编码\rho _{t} 建模不同词元 的位置信息。由于不同词元的位置编码仅由其位置唯一决定,因此这种位置建模方式被称为绝对位置编码。尽管绝对位置编码能够一定程度上建模位置信息,然而它只能局限于建模训练样本中出现的位置,无法建模训练数据中未出现过的位 置,因此极大地限制了它们处理长文本的能力。

5.1.2  多头自注意力机制

        多头自注意力是 Transformer 模型的核心创新技术。相比于循环神经网络(Re- current Neural Network, RNN)和卷积神经网络(Convolutional Neural Network, CNN) 等传统神经网络,多头自注意力机制能够直接建模任意距离的词元之间的交互关系。在卷积神经网络中,只有位于同一个卷积核的窗口中的词元可以直接进行交互,通过堆叠层数来实现远距离词元间信息的交换。

        多头自注意力机制通常由多个自注意力模块组成。在每个自注意力模块中,对 于输入的词元序列,将其映射为相应的查询(Query, Q)、键(Key, K)和值(Value,V) 三个矩阵。然后,对于每个查询,将和所有没有被掩盖的键之间计算点积。这 些点积值进一步除以 \sqrt{D}进行缩放(D是键对应的向量维度),被传入到 softmax函数中用于权重的计算。进一步,这些权重将作用于与键相关联的值,通过加权和的形式计算得到最终的输出。在数学上,上述过程可以表示为:

                                                                                   Q= XW^{Q},                                           (5.2)

                                                                                    K= XW^{K},                                          (5.3)

                                                                                    V= XW^{V},                                          (5.4)

                                                Attention(Q, K, V) = softmax( QK^{T}/\sqrt{D} )V.                  (5.5)

        与单头注意力相比,多头注意力机制的主要区别在于它使用了H组结构相同 但映射参数不同的自注意力模块。输入序列首先通过不同的权重矩阵被映射为一 组查询、键和值。每组查询、键和值的映射构成一个“头”,并独立地计算自注意 力的输出。最后,不同头的输出被拼接在一起,并通过一个权重矩阵 W^{O}\mathbb{R}^{H\times H} 进行映射,产生最终的输出。如下面的公式所示:

                                             MHA = Concat(head_{1}, . . . , head_{N}) W^{O}                                  (5.6)

                                             head_{n}= Attention(XW^{Q}_{n}, XW^{K}_{n},  XW^{V}_{n}).                                 (5.7)

        由上述内容可见,自注意力机制能够直接建模序列中任意两个位置之间的关 系,进而有效捕获长程依赖关系,具有更强的序列建模能力。另一个主要的优势 是,自注意力的计算过程对于基于硬件的并行优化(如 GPU、TPU 等)非常友好, 因此能够支持大规模参数的高效优化。

5.1.3  前馈网络层

为了学习复杂的函数关系和特征,Transformer 模型引入了一个前馈网络层

(Feed Forward Netwok, FFN),对于每个位置的隐藏状态进行非线性变换和特征 提取。具体来说,给定输入 x,Transformer 中的前馈神经网络由两个线性变换和 一个非线性激活函数组成:

                                        FFN(X) = 𝜎( XW^{U} +b_{1})W^{D} + b_{2},                                                      (5.8)

其中 W^{U}\mathbb{R}^{H\times {H}'} 和 W^{D}\mathbb{R}^{​{H}'\times H}   分别是第一层和第二层的线性变换权重矩阵,

b_{1} ∈\mathbb{R}^{​{H}'}b_{2}\mathbb{R}^{H} 是偏置项,𝜎 是激活函数(在原始的 Transformer 中,采用 ReLU 作为激活函数)。前馈网络层通过激活函数引入了非线性映射变换,提升了 模型的表达能力,从而更好地捕获复杂的交互关系。

5.1   Transformer 架构图

5.1.4  编码器

在 Transformer 模型中,编码器(Encoder)(图 5.1 (a))的作用是将每个输入 词元都编码成一个上下文语义相关的表示向量。编码器结构由多个相同的层堆叠 而成,其中每一层都包含多头自注意力模块和前馈网络模块。在注意力和前馈网 络后,模型使用层归一化和残差连接来加强模型的训练稳定度。其中,残差连接(Residual Connection)将输入与该层的输出相加,实现了信息在不同层的跳跃传递,从而缓解梯度爆炸和消失的问题。而 LayerNorm 则对数据进行重新放缩,提升模型的训练稳定性。编码器接受经过位置编码层的 词嵌入序列X作为输入,通过多个堆叠的编码器层来建模上下文信息,进而对于 整个输入序列进行编码表示。由于输入数据是完全可见的,编码器中的自注意力 模块通常采用双向注意力,每个位置的词元表示能够有效融合上下文的语义关系。 在编码器-解码器架构中,编码器的输出将作为解码器(Decoder)的输入,进行后 续计算。形式化来说,第 l 层(l∈ {1, . . . , L})的编码器的数据处理过程如下所示:

                                         {X_{l}}' = LayerNorm(MHA( X_{l-1})+ X_{l-1}) ,

                                                                                                                                               (5.9)

                                        X_{l}  = LayerNorm(FFN( {X_{l}}') + {X_{l}}'),

其中,X_{l-1} 和 X_{l}分别是该 Transformer 层的输入和输出,{X_{l}}'是该层中输入经过多头注意力模块后的中间表示,LayerNorm 表示层归一化。

5.1.5  解码器

        Transformer 架构中的解码器(图 5.1 (b))基于来自编码器编码后的最后一层 的输出表示以及已经由模型生成的词元序列,执行后续的序列生成任务。与编码 器不同,解码器需要引入掩码自注意力(Masked Self-attention)模块,用来在计算 注意力分数的时候掩盖当前位置之后的词,以保证生成目标序列时不依赖于未来 的信息。除了建模目标序列的内部关系,解码器还引入了与编码器相关联的多头 注意力层,从而关注编码器输出的上下文信息 X_{L} 。同编码器类似,在每个模块之 后,Transformer 解码器 也采用了层归一化和残差连接。在经过解码器之后,模型 会通过一个全连接层将输出映射到大小为 𝑉 的目标词汇表的概率分布,并基于某 种解码策略生成对应的词元。在训练过程中,解码器可以通过一次前向传播,让 每个词元的输出用于预测下一个词元。而在解码过程,解码器需要经过一个逐步 的生成过程,将自回归地生成完整的目标序列。解码器数据流程如下所示:

                        ​​​​​​​        ​​​​​​​        {Y_{l}}'  = LayerNorm(MaskedMHA(Y_{l-1}) + Y_{l-1}),

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​{Y_{l}}''  = LayerNorm(CrossMHA({Y_{l}}', X_{L} ) + {Y_{l}}' ),                              (5.10) 

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​       ​​​​​​​  Y_{l}   = LayerNorm(FFN({Y_{l}}'') + {Y_{l}}'')

其中,Y_{l-1} 和 Y_{l}分别是该 Transformer 层的输入和输出,{Y_{l}}'{Y_{l}}''是该层中输入经过掩码多头注意力 MaskedMHA 和交叉多头注意力 CrossMHA 模块后的中间表 示,LayerNorm 表示层归一化。然后将最后一层的输入Y_{L}映射到词表的维度上:

                                         O = softmax(W^{L}Y_{L} ),                                                                 (5.11)  其中,O\mathbb{R}^{H\times V} 是模型最终的输出,代表下一个词在词表上的概率分布;W^{L}\mathbb{R}^{H\times V}是将输入表示映射到词汇表维度的参数矩阵,而 W^{L}Y_{L}是概率化前的中间值,通常被称为 logits。

5.2  详细配置

自从 Transformer 模型公开发布以来,研究人员针对训练稳定性、性能与 计算效率提升等方面提出了多种改进方法。本节主要探讨了 Transformer 模型四个 核心组件的配置,包括归一化、位置、激活函数和注意力机制,并介绍混合专家 结构。最后,我们将通过演示代码对于 LLaMA 的模型实现进行介绍。

5.2.1  归一化方法

大语言模型的预训练过程中经常会出现不稳定的问题。为了应对这一问题,深 度学习方法通常会采用特定的归一化策略来加强神经网络训练过程的稳定性。原 始的 Transformer 模型主要使用了层归一化方法(Layer Normalization, LN)。随着研究工作的不断深入,基于层归一化的改进技术不断涌现,例如均方根层归 一化(Root Mean Square Layer Normalization, RMSNorm)和 DeepNorm, 这些新技术已经在一些大语言模型中得到应用。

  • LayerNorm. 在早期的研究中,批次归一化(Batch Normalization, BN)是一种广泛采用的归一化方法。然而,该方法难以处理可变长度的序列数据和小 批次数据。因此,相关研究提出了层归一化这一技术,针对数据进行逐层归一化。
  • RMSNorm. 为了提高层归一化的训练速度,RMSNorm仅利用激活值 总和的均方根 RMS(x) 对激活值进行重新缩放。使用 RMSNorm 的 Transformer 模 型相比于之前 LayerNorm 训练的模型在训练速度和性能上均具有一定优势。采用 RMSNorm 的代表性模型包括 Gopher和 Chinchilla。下面给出了 Transformers 代码库中 LLaMA 的 RMSNorm 实现代码:
class LlamaRMSNorm(nn.Module):def  __init__ (self, hidden_size, eps=1e-6): super(). __init__()self.weight = nn.Parameter(torch.ones(hidden_size)) self.variance_epsilon = epsdef forward(self, hidden_states): input_dtype = hidden_states.dtypehidden_states = hidden_states.to(torch.float32) variance = hidden_states.pow(2).mean(-1, keepdim=True)# 计算隐状态的均方根hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)# 将隐状态除以其均方根后重新缩放return self.weight * hidden_states.to(input_dtype)
 

DeepNorm. DeepNorm 由微软的研究人员提出,旨在稳定深层 Trans- former 的训练。具体而言,DeepNorm 在 LayerNorm 的基础上,在残差连接中对 之前的激活值 x 按照一定比例 \alpha进行放缩。通过这一简单的操作,Transformer 的 层数可以被成功地扩展至 1,000 层,进而有效提升了模型性能与训练稳定性。 其计算公式如下:

DeepNorm(x) = LayerNorm(\alpha · x + Sublayer(x)),                                            (5.16)

其中,Sublayer 表示 Transformer 层中的前馈神经网络或自注意力模块。GLM-130B采用了 DeepNorm 作为归一化技术。

5.2.2  归一化模块位置

为了加强大语言模型训练过程的稳定性,除了归一化方法外,归一化模块的 位置也具有重要的影响。如图 5.2(a) 所示,归一化模块的位置通常有三种选择,分 别是层后归一化(Post-Layer Normalization, Post-Norm)、层前归一化(Pre-Layer Normalization, Pre-Norm)和夹心归一化(Sandwich-Layer Normalization, Sandwich- Norm)。

  • Post-Norm. Post-Norm 是在原始 Transformer 模型中所使用的一种归一化技 术。其中,归一化模块被放置于残差计算之后。其计算公式如下:

                       Post-Norm(x)=Norm(x+Sublayer(x)),                                   (5.17)                                                                             

        其中,Norm 表示任意一种归一化方法。在原理上,后向归一化具有很多优势。首 先,有助于加快神经网络的训练收敛速度使模型可以更有效地传播梯度,从而减少训练时间。其次,后向归一化可以降低神经网络对于超参数(如学习率、初始化参数等)的敏感性,使得网络更容易调优,并减少了超参数调整的难度。然而,由于在输出层附近存在梯度较大的问题,采用 Post-Norm 的Transformer 模型在训练过程中通常会出现不稳定的现象。因此,现有的大语言模型中, PostNorm 很少被单独使用,通常是与其他策略相结合应用。

Pre-Norm. 与 Post-Norm 不同,Pre-Norm [164] 将归一化模块应用在每个子 层之前。其计算公式如下:

                               Pre-Norm(x) = x + Sublayer(Norm(x)),                                              (5.18)

此处的 Norm 泛指任意一种归一化方法。此外,Pre-Norm 在最后一个 Transformer 层后还额外添加了一个 LayerNorm。相较于 Post-Norm,Pre-Norm 直接把每个子层 加在了归一化模块之后,仅仅对输入的表示进行了归一化,从而可以防止模型的 梯度爆炸或者梯度消失现象。虽然使用了 Pre-Norm 的 Transformer 模型在训练过 程中更加稳定,但是性能却逊色于采用了 Post-Norm 的模型。尽管对于性能有一 定的影响,但由于其能够有效维持训练的稳定性,很多主流的大语言模型仍然采 用 Pre-Norm。

  • Sandwich-Norm. 在 Pre-Norm 的基础上,Sandwich-Norm在残差连接之前增加了额外的 LayerNorm,旨在避免 Transformer 层的输出出现数值爆炸的情况。 具体的实现方式如下所示:

Sandwich-Norm(x) = x +Norm(Sublayer(Norm(x))).                                      (5.19)

                                                                                                 

本质上,Sandwich-Norm 可以看作是 Pre-Norm 和 Post-Norm 两种方法的组合,理 论上具有更加灵活的表达能力。但是研究人员发现,Sandwich-Norm 有时仍然无法保证大语言模型的稳定训练,甚至会引发训练崩溃的问题。

5.2.3  激活函数

前馈网络中激活函数的选择对于大语言模型的表现至关重要。通常来说,激 活函数主要是为神经网络中引入非线性变化,从而提升神经网络的模型能力。在 原始的 Transformer 中采用了 ReLU(Rectified Linear Unit)激活函数。该激活函数 计算较为简单,仅仅是将对输入中每个神经元和“零值”进行比较,并将小于零 的神经元的值设置为 0。然而,ReLU 可能会产生神经元失效的问题,被置为 0 的 神经元将学习不到有用的信息。ReLU     函数的具体形式如下所示:

ReLU(x) = max(x, 0).                                             (5.20)

针对 ReLU 存在的不足,研究人员进一步探索了 ReLU 函数的变种,以实现 更好的性能。Swish 激活函数将神经元和该神经元的 sigmoid 激活的乘积作为新的 激活函数。而 GELU(Gaussian Error Linear Unit)则利用标准高斯累积分布 函数作为激活函数,被很多的 Transformer 模型所采用。相比于原始的 ReLU 函数, 这些新的激活函数通常能够带来更好的性能并且收敛性更好,但是计算过程更为 复杂。Swish 和 GELU 与 ReLU 的对比如图 5.2(b) 所示。

近来,大语言模型(例如 PaLM 和 LaMDA)也经常采用 GLU(Gated Linear Unit)激活函数以及它的变种,特别是 SwiGLU 和 GeGLU。不同于其他激活 函数,GLU 激活函数引入了两个不同的线性层。其中一个线性层的输出将被输入 到一个激活函数(例如,GeGLU 采用 GELU 激活函数)中,其结果将和另一个线 性层的输出进行逐元素相乘作为最终的输出。相比于其他的激活函数,使用 GLU激活函数变体通常能够带来更佳的性能表现。SwiGLU 和 GeGLU 激活函数 的计算公式如下所示:

SwiGLU(x) = Swish(W^{G}x) ⊙ (W^{U}x),                                                          (5.23)

GeGLU(x) = GELU(W^{G}x) ⊙ (W^{U}x),                                                           (5.24)

               (a) 三种归一化模块位置                                    (b) 不同激活函数的示意图

5.2  归一化和激活函数的示意图

5.2.4  位置编码

由于 Transformer 模型中自注意力模块具有置换不变性,因此仅使用注意力机 制无法捕捉序列中的顺序关系,从而退化为“词袋模型”。为了解决这一问题,需 要引入位置编码(Position Embedding, PE)对于序列信息进行精确建模,从而将绝 对或相对位置信息整合到模型中。

  • • 绝对位置编码. 在原始的 Transformer 模型中,为了处理序列数据的顺序信 息,采用了绝对位置编码方法。在编码器和解码器的输入端,根据输入的词元在 序列中的绝对位置生成唯一的位置嵌入,并与词元的嵌入表示进行相加来注入位置信息。绝对位置编码的公式如下所示:

 x_{t} = \nu _{t} + \rho _{t}                                                                                   (5.25)

其中, \rho _{t}表示位置 t的位置嵌入,\nu _{t}是该位置词元对应的词向量。原始的 Trans- former 采用了正余弦位置编码。该位置编码在不同维度上预先定义了特定的正弦 或余弦函数,通过将词元的绝对位置作为输入代入这些函数,从而为这些维度赋予相应的值。此外,绝对位置编码还可以采用可学习的嵌入表示,并被很多早期的预训练语言 模型(如 BERT)广泛采用。

  • • 相对位置编码与绝对位置编码不同,相对位置编码是根据键和查询之间的偏移量计算得来的。计算得到的相对位置编码通常应用于注意力矩阵的计算中, 而不是直接与词元本身的位置编码进行相加。其中,Transformer-XL 提出了 一种相对位置编码方法,在计算键和查询之间的注意力分数时引入了相对位置信 息。对于使用绝对位置编码的模型,其注意力值可以进行进一步的分解。而 Transformer-XL 对上述注意力值进行了改写,使用相对位置信息代替绝对位置信息。作为另一种方法,T5提出了一种较为简化的相对位置编码。具体来说,它在注意力分数中引入了可学习的标量,这些标量是基于查询和键的位 置之间的距离计算的。与绝对位置编码相比,应用了相对位置编码的 Transformer 模型常常可以对比训练序列更长的序列进行建模,即具备一定的外推能力。
  • 旋转位置编码(Rotary Position Embedding, RoPE). RoPE 巧妙地使用了基于绝对位置信息的旋转矩阵来表示注意力中的相对位置信息。RoPE 根据位置信息为 序列中每个词元所对应的设置了独有的旋转矩阵,并和对应的查询和键进行相乘进行融合。利用旋转矩阵中三角函数的特性,位置索引为 i的旋转矩阵和位置索引为 j的旋转矩阵的转置的乘积等同于位置索引为它们相对距离i-j的旋转矩阵,进一步,每个子空间定义了波长 \lambda _{i} ,即在该子空间上完成一个完整周期(2𝜋)旋转所需的距离由于 RoPE 具有良好的性能以及长期衰减的特性,已经主流的大语言模型广泛采 用,例如 PaLM  和 LLaMA。这里给出了 Transformers 代码库中 LLaMA 的 RoPE 实现代码:
def rotate_half(x):x1 = x[..., : x.shape[-1] // 2]x2 = x[..., x.shape[-1] // 2 :]# 将向量每两个元素视为一个子空间return torch.cat((-x2, x1), dim=-1)def apply_rotary_pos_emb(q, k, cos, sin, position_ids): cos = cos[position_ids].unsqueeze(1)sin = sin[position_ids].unsqueeze(1)# 获得各个子空间旋转的正余弦值q_embed = (q * cos) + (rotate_half(q) * sin) k_embed = (k * cos) + (rotate_half(k) * sin)# 将每个子空间按照特定角度进行旋转return q_embed, k_embed

ALiBi 位置编码: ALiBi 是一种特殊的相对位置编码,主要用于增强 Transformer 模型的外推能力。具体来说,ALiBi 通过在键和查询之间的距离上施 加相对距离相关的惩罚来调整注意力分数。 其与 T5 等模型中的相对位置编码不同,ALiBi 中的惩罚分数是预先设定的,不需 要引入任何可训练的参数。此外,ALiBi 展现出了优秀的外推性能,能够对于超 过上下文窗口更远距离的词元进行有效建模。下面给出 Transformers 库中 BLOOM 中的 ALiBi 代码实现:

import math
import torchdef build_alibi_tensor(attention_mask: torch.Tensor,num_heads: int,dtype: torch.dtype,
) -> torch.Tensor:"""构建ALiBi(Attention with Linear Biases)位置偏置张量"""batch_size, seq_length = attention_mask.shape# 计算最接近且不大于num_heads的2的幂次方closest_power_of_2 = 2 ** math.floor(math.log2(num_heads))# 计算基础衰减系数(公式中的beta参数)base = torch.tensor(2 ** (-(2 ** -(math.log2(closest_power_of_2) - 3))),device=attention_mask.device,dtype=torch.float32,)# 生成基础指数序列(从1开始,长度=closest_power_of_2)powers = torch.arange(1,1 + closest_power_of_2,device=attention_mask.device,dtype=torch.int32,)# 计算基础头的惩罚系数(slopes)slopes = torch.pow(base, powers)# 处理非2的幂次方头数的情况if closest_power_of_2 != num_heads:# 计算额外衰减系数(针对非2幂部分)extra_base = torch.tensor(2 ** (-(2 ** -(math.log2(2 * closest_power_of_2) - 3))),device=attention_mask.device,dtype=torch.float32,)# 计算需要补充的额外头数(保证不超过closest_power_of_2)num_remaining_heads = min(closest_power_of_2, num_heads - closest_power_of_2)# 生成额外指数序列(奇数序列,长度=num_remaining_heads)extra_powers = torch.arange(1,1 + 2 * num_remaining_heads,2,device=attention_mask.device,dtype=torch.int32,)# 合并基础系数和额外系数slopes = torch.cat([slopes, torch.pow(extra_base, extra_powers)], dim=0)# 生成位置索引张量(形状:[batch_size, 1, seq_length])arange_tensor = ((attention_mask.cumsum(dim=-1) - 1) * attention_mask)[:, None, :]# 计算ALiBi偏置(广播机制自动对齐维度)alibi = slopes[..., None] * arange_tensor  # [batch_size, num_heads, seq_length]# 调整形状以匹配注意力计算需求return alibi.reshape(batch_size * num_heads, 1, seq_length).to(dtype)
使用示例
# 假设输入参数
batch_size = 2
seq_length = 128
num_heads = 12  # 非2的幂次方示例
attention_mask = torch.ones((batch_size, seq_length))  # 全有效序列# 构建ALiBi张量
alibi_bias = build_alibi_tensor(attention_mask, num_heads, torch.float16)
print(alibi_bias.shape)  # 应输出:torch.Size([24, 1, 128])

5.2.5  注意力机制

注意力机制是 Transformer 架构中的核心技术,它能够针对序列中的词元对构 建交互关系,聚合来自于不同位置的语义信息。下面介绍四种常见的注意力机制 的设计方法。

  • • 完整自注意力机制. 在原始的 Transformer 模型中,注意力机制通过成对的方 式进行序列数据的语义建模,充分考虑了序列中所有词元之间的相互关系。其中, 每个词元在注意力计算中都需要对于其前序的所有词元的键值对予以访问,因此对于序列长度为 T 的序列需要 O(T^{2}) 的计算复杂度。此外,Transformer 还引入了 多头注意力机制,将查询、键和值在不同的语义空间进行线性投影,然后将每个头 的输出进行聚合形成最终的输出。对于完整注意力的详细介绍,可以参考第 5.1.2 节。
  • • 稀疏注意力机制. 尽管完整自注意力机制具有较强的建模能力,但是它需要 平方级的计算复杂性。在处理长序列时较为显著,带来了较大的计算和存储开销。 为了降低注意力机制的计算复杂度,研究人员提出了多种高效的注意力变种。其中,滑动窗口注意力机制(Sliding Window Attention, SWA)是大语言模型中使用 最多的一种稀疏注意力机制。不同于完整的注意力机制,滑动窗口注意力根据词 元位置,仅仅将位置索引上距离该词元一定范围内的词元考虑到注意力的计算中。 具体来说,滑动窗口注意力设置了一个大小为 w的窗口,对每个词元 \mu _{t} ,只对窗口内的词元 [\mu _{t-w+1} , . . . , \mu _{t}  ] 进行注意力计算,从而将复杂度降低到 O (wT )。进一步,通过信息的逐层传递,模型实现了随着层数线性增长的感受野,从而获取远 处词元的信息。关于滑动窗口注意力详细机制如图 5.3 展示。
  • • 多查询/分组查询注意力. 为了提升注意力机制的效率,多查询注意力提出针对不同的头共享相同的键和值变换矩阵。这种 方法减少了访存量,提高了计算强度,从而实现了更快的解码速度(具体可以参词元并且对于模型性能产生的影响也比较小。一些代表性的大语言模 型,如 PaLM 和 StarCoder,已经使用了多查询注意力机制。为了结合多查询注意力机制的效率与多头注意力机制的性能,研究人员进一步提出了分组查 询注意力机制(Grouped-Query Attention, GQA)。GQA 将全部的头划分为若干组,并且针对同一组内的头共享相同的变换矩阵。这种注意力机制有效地平衡 了效率和性能,被 LLaMA-2 模型所使用。图 5.4 展示了上述两种注意力查询机制。

                     

      (a) 滑动窗口注意力的掩码矩阵                                 (b) 滑动窗口注意力信息的逐层传递

5.3  滑动窗口注意力示意图

图 5.4  多头注意力、分组查询注意力和多查询注意力示意图

  • • 硬件优化的注意力机制. 除了在算法层面上提升注意力机制的计算效率,还可以进一步利用硬件设施来优化注意力模块的速度和内存消耗。其中,两个具有 代表性的工作是 FlashAttention 与 PagedAttention。相比于传统的注意力 实现方式,FlashAttention 通过矩阵分块计算以及减少内存读写次数的方式,提高 注意力分数的计算效率;PagedAttention 则针对增量解码阶段,对于 KV 缓存进行 分块存储,并优化了计算方式,增大了并行计算度,从而提高了计算效率。

5.5  混合专家模型示意图

5.2.6  混合专家模型

如第 2.2 节所述,大语言模型能够通过扩展参数规模实现性能的提升。然而, 随着模型参数规模的扩大,计算成本也随之增加。为了解决这一问题,研究人员 在大语言模型中引入了基于稀疏激活的混合专家架构(Mixture-of-Experts, MoE), 旨在不显著提升计算成本的同时实现对于模型参数的拓展。

在混合专家架构中,每个混合专家层包含K 个专家组件,记为 [E_{1},E_{2}, . . . , E_{K} ], 其中每个专家组件E_{i}都是一个前馈神经网络。对于输入的每个词元表示 x_{t},模 型通过一个路由网络(或称为门控函数)G来计算该词元对应于各个专家的权重。 在路由函数中,首先通过线性层 W^{G}\in \mathbb{R}^{H\times K} 映射为 K个专家的得分,并基于此 选择出概率最高的k 个专家进行激活。随后,这 k个专家的得分将被送入 softmax 函数计算出它们的权重 G\left ( x_{t} \right ) = [G\left ( x_{t} \right )_{1} , . . . , G\left ( x_{t} \right )_{k}  ],没有被选择的专家权重将 被置为 0。上述路由网络的计算过程如下式所示:

G\left ( x_{t} \right ) = softmax\left ( topk\left (x_{t}\cdot W^{G} \right)\right).                                                             (5.35)

之后,每个被选择的词元的输出的加权和将作为该混合专家网络层的最终输出。

        目前具有代表性的混合专家模型是 Mixtral (8×7B),该模型在 Mistral (7B) 的 基础上,使用了混合专家模块。具体来说,Mixtral 每一层都配备了 8 个专家(7B), 并对每个词元选择 2 个专家进行后续计算。在每次计算被激活的参数仅仅有 13B 的情况下,其性能超越了更熟规模更大的 LLaMA-2 (70B),进一步证明了混合专家架构的有效性。下面给出了 Mixtral 混合专家层的一个 PyTorch 示例代码:

import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Listclass MoeLayer(nn.Module):def __init__(self,experts: List[nn.Module],gate: nn.Module,num_experts_per_tok: int):"""混合专家层(Mixture-of-Experts Layer)实现Args:experts: 专家网络列表,每个专家是一个nn.Module实例gate: 路由网络,用于生成专家选择概率num_experts_per_tok: 每个词元选择的专家数量"""super().__init__()assert len(experts) > 0, "至少需要提供一个专家网络"self.experts = nn.ModuleList(experts)  # 将专家列表转换为ModuleListself.gate = gate                       # 路由网络self.num_experts_per_tok = num_experts_per_tok  # 每个token选择的专家数def forward(self, inputs: torch.Tensor) -> torch.Tensor:"""前向传播过程Args:inputs: 输入张量,形状为 [batch_size, seq_length, hidden_dim]Returns:输出张量,形状与输入相同"""# 生成路由网络的原始logits(未归一化概率)gate_logits = self.gate(inputs)  # 形状 [batch_size, seq_length, num_experts]# 选择top-k专家及其对应权重weights, selected_indices = torch.topk(gate_logits,self.num_experts_per_tok,dim=-1  # 在专家维度上进行选择)# 对权重进行softmax归一化(在专家选择维度上)weights = F.softmax(weights, dim=-1, dtype=torch.float32).to(inputs.dtype)# 初始化输出张量(与输入形状相同)outputs = torch.zeros_like(inputs)# 遍历所有专家,计算加权输出for expert_idx, expert in enumerate(self.experts):# 找到选择当前专家的位置(batch和序列维度)batch_indices, seq_indices = torch.where(selected_indices == expert_idx)# 提取对应位置的输入和权重selected_inputs = inputs[batch_indices, seq_indices]selected_weights = weights[batch_indices, seq_indices, expert_idx]# 计算专家输出并加权累加expert_output = expert(selected_inputs)outputs[batch_indices, seq_indices] += (selected_weights[:, None] * expert_output)return outputs

使用示例

# 定义示例专家网络和路由网络
class Expert(nn.Module):def __init__(self, hidden_dim):super().__init__()self.mlp = nn.Linear(hidden_dim, hidden_dim)def forward(self, x):return self.mlp(x)class Router(nn.Module):def __init__(self, hidden_dim, num_experts):super().__init__()self.router = nn.Linear(hidden_dim, num_experts)def forward(self, x):return self.router(x)# 创建MoE层
num_experts = 4
hidden_dim = 768
num_experts_per_tok = 2experts = [Expert(hidden_dim) for _ in range(num_experts)]
gate = Router(hidden_dim, num_experts)
moe_layer = MoeLayer(experts, gate, num_experts_per_tok)# 测试前向传播
inputs = torch.randn(8, 128, hidden_dim)  # [batch_size, seq_length, hidden_dim]
outputs = moe_layer(inputs)
print(outputs.shape)  # 应该输出 torch.Size([8, 128, 768])

5.2.7 ​​​​​​​​​​​​​​LLaMA 的详细配置

综合本节讨论的内容,下面给出了关于模型详细配置的推荐建议。首先,为 了增强模型的训练稳定性,建议采用前置的 RMSNorm 作为层归一化方法。其次, 在选择激活函数时,为了获得更优的模型性能,可以优先考虑使用 SwiGLU 或 GeGLU。最后,对于位置编码,可以优先选择 RoPE 或者 ALiBi,这两种位置编码 方法在建模长序列数据时通常能够具有较好的性能。接下来,我们以 LLaMA 模型 的代码实现,来介绍 Transformer 解码器模型是如何进行模型搭建并且实现前向计 算的过程。

对于一个 LLaMA 模型,其首先将输入的词元序列通过词嵌入矩阵转化为词 向量序列。之后,词向量序列作为隐状态因此通过 �  个解码器层,并在最后使 用 RMSNorm 进行归一化。归一化后的最后一层隐状态将作为输出。LLaMA 在 Transformers 库中的整体实现如下所示:

import torch
import torch.nn as nn
from typing import Optional, Tuple, Union
from transformers.models.llama.modeling_llama import LlamaPreTrainedModel, LlamaConfig, LlamaDecoderLayer, LlamaRMSNorm
from transformers.file_utils import add_start_docstrings_to_model_forward
from transformers.modeling_outputs import BaseModelOutputWithPastclass LlamaModel(LlamaPreTrainedModel):def __init__(self, config: LlamaConfig):"""LLaMA 模型实现Args:config: 模型配置参数"""super().__init__(config)self.vocab_size = config.vocab_size  # 词表大小# 词嵌入层(包含padding处理)self.embed_tokens = nn.Embedding(num_embeddings=config.vocab_size,embedding_dim=config.hidden_size,padding_idx=config.pad_token_id  # 显式指定padding索引)# 解码器层堆叠self.layers = nn.ModuleList([LlamaDecoderLayer(config, layer_idx=i)for i in range(config.num_hidden_layers)])# RMSNorm归一化层self.norm = LlamaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)# 预生成因果掩码(用于自注意力)self.register_buffer("causal_mask",torch.full((config.max_position_embeddings, config.max_position_embeddings),fill_value=True,dtype=torch.bool).triu(diagonal=1)  # 上三角矩阵(排除对角线))@add_start_docstrings_to_model_forward(Llama_INPUTS_DOCSTRING)def forward(self,input_ids: torch.LongTensor = None,attention_mask: Optional[torch.Tensor] = None,position_ids: Optional[torch.LongTensor] = None,**kwargs) -> Union[Tuple, BaseModelOutputWithPast]:"""模型前向传播Args:input_ids: 输入token ID序列 [batch_size, sequence_length]attention_mask: 注意力掩码 [batch_size, sequence_length]position_ids: 位置ID序列 [batch_size, sequence_length]Returns:包含最终隐藏状态的BaseModelOutputWithPast对象"""# 输入嵌入处理if input_ids is not None:inputs_embeds = self.embed_tokens(input_ids)else:inputs_embeds = kwargs["inputs_embeds"]# 获取序列长度seq_length = inputs_embeds.size(1)# 生成因果掩码(根据当前序列长度动态调整)if self.training:# 训练时使用预生成的完整掩码causal_mask = self.causal_mask[:seq_length, :seq_length]else:# 推理时动态生成(适应不同序列长度)causal_mask = torch.full((seq_length, seq_length),fill_value=True,dtype=torch.bool,device=inputs_embeds.device).triu(diagonal=1)# 合并注意力掩码(如果有)if attention_mask is not None:combined_mask = torch.logical_and(causal_mask.unsqueeze(0).expand(inputs_embeds.size(0), -1, -1),attention_mask.unsqueeze(-1).expand(-1, seq_length, seq_length))else:combined_mask = causal_mask.unsqueeze(0).expand(inputs_embeds.size(0), -1, -1)# 通过解码器层hidden_states = inputs_embedsfor decoder_layer in self.layers:hidden_states = decoder_layer(hidden_states=hidden_states,attention_mask=combined_mask,position_ids=position_ids)[0]  # 只取隐藏状态输出# 最终归一化hidden_states = self.norm(hidden_states)return BaseModelOutputWithPast(last_hidden_state=hidden_states,)

使用示例

from transformers import LlamaConfig# 创建模型配置
config = LlamaConfig(vocab_size=32000,hidden_size=4096,num_hidden_layers=32,num_attention_heads=32,max_position_embeddings=2048
)# 初始化模型
model = LlamaModel(config)# 生成测试输入
input_ids = torch.randint(0, config.vocab_size, (4, 128))  # [batch_size, seq_length]# 前向传播
outputs = model(input_ids)
print(outputs.last_hidden_state.shape)  # 应该输出 torch.Size([4, 128, 4096])

在每个解码器层中,隐状态首先通过层前的 RMSNorm 归一化并被送入注意 力模块。注意力模块的输出将和归一化前的隐状态做残差连接。之后,新的隐状 态进行 RMSNorm 归一化,并送入前馈网络层。和上面一样,前馈网络层的输出同样做残差连接,并作为解码器层的输出。Transformers 库中 LLaMA 每一层的代 码实现如下所示:

class LlamaDecoderLayer(nn.Module):def __init__(self, config: LlamaConfig, layer_idx: int):super().__init__()self.hidden_size = config.hidden_sizeself.self_attn = LlamaAttention(config=config, layer_idx=layer_idx)  # 注意力层self.mlp = LlamaMLP(config)  # 前馈网络层self.input_layernorm = LlamaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)self.post_attention_layernorm = LlamaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)# 注意力层和前馈网络层前的 RMSNormdef forward(self,hidden_states: torch.Tensor,attention_mask: Optional[torch.Tensor] = None,position_ids: Optional[torch.LongTensor] = None,**kwargs,) -> Tuple[torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]]:residual = hidden_stateshidden_states = self.input_layernorm(hidden_states)# 注意力层前使用 RMSNorm 进行归一化hidden_states, self_attn_weights, present_key_value = self.self_attn(hidden_states=hidden_states,attention_mask=attention_mask,position_ids=position_ids,**kwargs,)# 进行注意力模块的计算hidden_states = residual + hidden_states  # 残差连接residual = hidden_stateshidden_states = self.post_attention_layernorm(hidden_states)# 前馈网络层前使用 RMSNorm 进行归一化hidden_states = self.mlp(hidden_states)  # 进行前馈网络层的计算hidden_states = residual + hidden_states  # 残差连接outputs = (hidden_states,)return outputs

该实现遵循LLaMA的原始架构,包含:

  1. 词嵌入层
  2. 32层Transformer解码器
  3. RMSNorm归一化
  4. 因果注意力掩码
  5. 支持动态序列长度

可以通过调整LlamaConfig中的参数来适配不同规模的LLaMA模型(7B, 13B, 70B等)。

参考:

 llmbook/LLMBook.pdf at main · datawhalechina/llmbook · GitHub

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

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

相关文章

Tomcat虚拟主机配置详解:Centos环境下多域名部署(详细教程!)

🏡作者主页:点击! Tomcat服务器📝专栏:点击! 🐧Linux高级管理防护和群集专栏:点击! ⏰️创作时间:2025年3月18日14点14分 最近在折腾 Tomcat 的时候&…

Java+Html实现前后端客服聊天

文章目录 核心组件网络通信层事件调度层服务编排层 Spring实现客服聊天技术方案对比WebScoket建立连接用户上线实现指定用户私聊群聊离线 SpringBootWebSocketHtmljQuery实现客服聊天1. 目录结构2. 配置类3. 实体类、service、controller4. ChatWebSocketHandler消息处理5.前端…

51c自动驾驶~合集24

我自己的原文哦~ https://blog.51cto.com/whaosoft/11926510 #DriveArena 上海AI Lab又放大招:首个高保真闭环生成仿真平台 仓库链接:https://github.com/PJLab-ADG/DriveArena 项目链接:https://pjlab-adg.github.io/DriveArena/ D…

锦华新材业绩波动明显:偿债能力偏弱,大额分红引关注

《港湾商业观察》施子夫 近期,浙江锦华新材料股份有限公司(以下简称,锦华新材)收到北交所下发的第二轮审核问询函,公司的上市进程继续推进中。 从两轮审核问询函中监管层关注的问题来看,有关锦华新材业绩…

【Node.js入门笔记9---path 模块】

Node.js入门笔记9 Node.js---path 模块一、核心功能0.学习path的前提1. 使用 path.join() 安全拼接路径2. path.resolve(),路径解析(绝对路径)3. 路径信息提取4. 路径规范化 二、跨平台关键点1. 路径分隔符2. 环境变量分隔符3. 路径格式解析4…

C++20 中 `constexpr` 的强大扩展:算法、工具与复数库的变革

文章目录 一、constexpr 在 <algorithm> 中的应用1. 编译时排序2. 编译时查找 二、constexpr 在 <utility> 中的应用1. 编译时交换2. 编译时条件交换 三、constexpr 在 <complex> 中的应用1. 编译时复数运算 四、总结 C20 对 constexpr 的增强是其最引人注目…

python基础知识,if,elif语句运用(详细),常见编程例题练习

一、问答题 &#xff08;1&#xff09;怎样生成一个满足条件10 ≤ i ≤ 50的随机整数? import random i random.randint(10, 50) random.randint(a, b) #包含a和b &#xff08;2&#xff09;如果number分别是30和35&#xff0c;那么a中的代码和b中的代码的输出结果是什么? …

C++遍历树,前中后序,递归非递归实现

文章目录 前序遍历中序遍历后序遍历代码解释 前序遍历 递归思路&#xff1a;先访问根节点&#xff0c;然后递归遍历左子树&#xff0c;最后递归遍历右子树。非递归思路&#xff1a;使用栈来模拟递归过程。先将根节点入栈&#xff0c;之后循环执行以下操作&#xff1a;弹出栈顶…

Spring 声明式事务应该怎么学?

1、引言 Spring 的声明式事务极大地方便了日常的事务相关代码编写&#xff0c;它的设计如此巧妙&#xff0c;以至于在使用中几乎感觉不到它的存在&#xff0c;只需要优雅地加一个 Transactional 注解&#xff0c;一切就都顺理成章地完成了&#xff01; 毫不夸张地讲&#xff…

面试复习-基础网络+运维知识

一、TCP/IP模型及每层对应通信协议 1.1第一层-应用层 作用&#xff1a;服务及应用程序 HTTP --- 超文本传输协议--- 获取网页信息---80&#xff08;TCP 80&#xff09; HTTPS --- HTTP SSL&#xff08;安全传输协议&#xff09;/TLS ---443&#xff08;TCP 443&#xff09; …

HeyGem.ai 全离线数字人生成引擎加入 GitCode:开启本地化 AIGC 创作新时代

在人工智能技术飞速演进的时代&#xff0c;数据隐私与创作自由正成为全球开发者关注的焦点。硅基智能旗下开源项目 HeyGem.ai 近日正式加入 GitCode&#xff0c;以全球首个全离线数字人生成引擎的颠覆性技术&#xff0c;重新定义人工智能生成内容&#xff08;AIGC&#xff09;的…

【C语言】递归:原理、技巧与陷阱

在C语言编程中&#xff0c;递归是一种非常强大且常用的技术。它允许函数自我调用&#xff0c;从而简化代码并解决复杂问题。然而&#xff0c;递归也可能导致性能问题&#xff0c;如栈溢出。本文将深入探讨递归的原理、应用、优化方法&#xff0c;并提供实际代码示例&#xff0c…

【C#语言】C#同步与异步编程深度解析:让程序学会“一心多用“

文章目录 ⭐前言⭐一、同步编程&#xff1a;单线程的线性世界&#x1f31f;1、寻找合适的对象✨1) &#x1f31f;7、设计应支持变化 ⭐二、异步编程&#xff1a;多任务的协奏曲⭐三、async/await工作原理揭秘⭐四、最佳实践与性能陷阱⭐五、异步编程适用场景⭐六、性能对比实测…

[OpenCV】相机标定之棋盘格角点检测与绘制

在OpenCV中&#xff0c;棋盘格角点检测与绘制是一个常见的任务&#xff0c;通常用于相机标定。 棋盘格自定义可参考: OpenCV: Create calibration pattern 目录 1. 棋盘格角点检测 findChessboardCorners()2. 棋盘格角点绘制 drawChessboardCorners()3. 代码示例C版本python版本…

AI-Talk开发板之更换串口引脚

一、默认引脚 CSK6011A使用UART0作为Debug uart&#xff0c;AI-Talk开发板默认使用的GPIOA2和GPIOA3作为Debug uart的RX和TX&#xff0c;通过连接器CN6引出。 二 、更换到其它引脚 查看60xx_iomux_v1.0可以&#xff0c;UART0的tx和rx可以映射到很多管脚上。 结合AI-Talk开发板…

QT Quick(C++)跨平台应用程序项目实战教程 3 — 项目基本设置(窗体尺寸、中文标题、窗体图标、可执行程序图标)

目录 1. 修改程序界面尺寸和标题 2. 窗体图标 3. 修改可执行程序图标 上一章创建好了一个初始Qt Quick项目。本章介绍基本的项目修改方法。 1. 修改程序界面尺寸和标题 修改Main.qml文件&#xff0c;将程序宽度设置为1200&#xff0c;程序高度设置为800。同时修改程序标题…

【STM32实物】基于STM32的太阳能充电宝设计

基于STM32的太阳能充电宝设计 演示视频: 基于STM32的太阳能充电宝设计 硬件组成: 系统硬件包括主控 STM32F103C8T6、0.96 OLED 显示屏、蜂鸣器、电源自锁开关、温度传感器 DS18B20、继电器、5 V DC 升压模块 、TB4056、18650锂电池、9 V太阳能板、稳压降压 5 V三极管。 功能…

003-掌控命令行-CLI11-C++开源库108杰

首选的现代C风格命令行参数解析器! &#xff08;本课程包含两段教学视频。&#xff09; 以文件对象监控程序为实例&#xff0c;五分钟实现从命令行读入多个监控目标路径&#xff1b;区分两大时机&#xff0c;学习 CLI11 构建与解析参数两大场景下的异常处理&#xff1b;区分三…

OpenCV图像拼接(2)基于羽化(feathering)技术的图像融合算法拼接类cv::detail::FeatherBlender

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::detail::FeatherBlender 是 OpenCV 中用于图像拼接的一个类&#xff0c;它属于 stitching 模块的一部分。这个类实现了基于羽化&#xff08;…

如何用Function Calling解锁OpenAI的「真实世界」交互能力?(附Node.js 实战)

一、Function Calling&#xff1a;大模型的「手脚延伸器」 1.1 核心定义 Function Calling是OpenAI在2023年6月13日推出的革命性功能&#xff08;对应模型版本gpt-3.5-turbo-0613和gpt-4-0613&#xff09;&#xff0c;允许开发者通过自然语言指令触发预定义函数&#xff0c;实…