大模型-Transform架构(解码器层)

目录

  • 大模型-Transformer(解码器层)
  • 大模型-参数
  • 其他补充

大模型-Transformer(解码器层)

前面我们已经了解了Transformer架构的编码器层的步骤,接下来我们来了解Transformer的后半部解码器层。


Transformer架构图

解码器层

解码器层的作用是生成输出文本,通过概率计算预测下一个最有可能的词,从而生成输出文本。

第一步:输出(右移)

解码器层的“输出(右移)”中的“输出”本质是输入,之所以叫“输出”是因为在训练阶段中该步骤的输入内容本质是需要生成的答案,也就是解码器层最终的输出结果,并且解码器层中每次完成单元计算的输出结果,将作为下一次单元计算的输入,所以这里的输入叫成了输出。

解码器层的“输出(右移)”中的“右移”只会在训练阶段出现,而训练阶段是将答案作为的输出,输入到解码器层中的,右移的目的是为了防止模型提前知道这次单元计算的答案。如果不是推理阶段则不需要右移,输出内容则是上一次解码器层计算单元的输出结果,而第一次计算的“输出(本质是输入)”则是仅包含<bos>的起始符号。

以翻译训练的案例来看,假如我们要翻译“我爱你”,答案是"i love you"
1、标签序列
答案是解码器的目标序列是"i love you"(长度 3),训练阶段称之为 “标签序列”,仅用于与模型预测结果对比,计算准确率(损失),不参与解码器任何前向计算(如掩码自注意力)。

2、加上起始符号
首先会在输入内容中加上起始符号,变为“<bos>i love you”,内容长度为4。在训练阶段添加起始符号的目的是为了与推理阶段逻辑保持一致,并且让右移后序列长度与原始长度一致。在推理阶段起始符号是为了标记生成的开始。

3、右移(最终输出)
为了保持输入序列的长度一致,这个时候会将内容长度进行尾部截断,变为3,变成“<bos>i love”

4、将token序列转为ID序列
跟编码器层的“输入”步骤一样,最终的结果是需要转为ID序列的,例如["2","432421","988331"],一般<bos>起始符号的ID是2

由于训练时的标签序列长度为3,而右移截断后的输入序列也是3,所以在训练阶段中并不需要像编码器“输入”步骤一样对输入序列进行PAD填充。
单从“输出(右移)”的步骤来看,右移的本质是为了让添加起始符号后的序列长度与原始输入序列长度保持一致,避免因为长度不匹配导致无法训练。
将“输出(右移)”和“掩码自注意力”两步合在一起来看,右移的本质是为了防止模型提前知道答案,通过添加起始符号,让添加起始符号的序列中每个位置的词与标签序列对应位置的词都不一样,相当于新序列中每个词右移了一个位置,从而让基于当前位置的计算过程,变得不可预见性,再配合掩码自注意力中的下三角掩码实现防止模型提前知道答案。

继续以上面“今天天气真好”的推理案例来看
1、加上起始符号
推理阶段只有在第一次单元计算是才会添加起始符号,由于第一次单元计算时还没有任何输入内容,所以第一次计算时只会添加起始符号,变成“<bos>”,长度为1,表示计算的启动信号。

2、将token序列转为ID序列
输出结果["2"]

推理阶段的“输出(右移)”就非常简单,没有“右移”的步骤。由于解码器层的设计是逐字生成,所以在推理阶段中序列长度也是逐步增加的,第一次单元计算中,序列长度为1,表示要生成一个词,而因为在“输入(右移)”的步骤中已经加入了起始符号<bos>,让输入序列的长度已经变为了1,所以不需要PAD填充(批量推理时是需要PAD填充保持批量计算中的张量一致)。

第二步:输出嵌入层

与编码器层的“输入嵌入”步骤一样,核心是将ID序列通过嵌入矩阵转换为词向量序列

后面我们统一都按照推理阶段进行讲解,以翻译“我爱你”为示例。为了方便展示计算过程,统一将维度调整为8维,并且模型已经完成2次单元计算,已生成的输出内容为“<bos>i love”

该步骤最终输出的是

[
[0.12, 0.34, -0.05, 0.27, -0.18, 0.41, -0.23, 0.09],#<bos>
[0.56, -0.17, 0.32, -0.08, 0.29, -0.45, 0.16, -0.31],# i
[0.38, 0.25, -0.19, 0.42, -0.07, 0.33, -0.28, 0.14]# love
]
第三步:位置编码

与编码器层的“位置编码”步骤一样,核心是为词向量矩阵加上位置信息。计算公式也一样

该步骤最终的输出结果是

[ 
[0.12,1.34,−0.05,1.27,−0.18,1.41,−0.23,1.09],
[1.40,078,0.63,0.90,0.39,0.54,0.19,0.69],
[1.29,1.05,0.41,1.35,0.13,1.32,-0.22,1.13]
]
第四步:掩码多头自注意力层

掩码多头自注意力层的实现步骤与多头自注意力基本一样,唯一的区别是在掩码步骤的不同,多头自注意力中的掩码矩阵是仅屏蔽 PAD 位置的无效信息,而解码器阶段在非批量推理情况下,掩码多头自注意力层因为不需要进行PAD填充,所以掩码矩阵中并不需要去屏蔽PAD位置,而是增加了下三角掩码来防止计算过程中关注到未来的词的特性而产生预见性。
掩码多头自注意力层之所以是增加的下三角掩码的核心原因是因为注意力得分公式中Q向量矩阵每一行是一个词,每一列是不同词的同一维,而K矩阵转置后,每一列是一个词,每一行是一个词的不同维,两者相乘得到的矩阵中每一个行表示一个词与其他词在同一个维度上的注意力关系。而下三角则是刚好能屏蔽每一行所代表单词与其之后的单词的注意力得分,来避免后续的计算过程中提前预知未来。

步骤1 : 计算Q(查询)、K(键)、V(值)矩阵

这里我们假定W_k、W_q、W_v权重矩阵如下

W_Q = [
       [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
       [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.1],
       [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.1, 0.2],
       [0.4, 0.5, 0.6, 0.7, 0.8, 0.1, 0.2, 0.3],
       [0.5, 0.6, 0.7, 0.8, 0.1, 0.2, 0.3, 0.4],
       [0.6, 0.7, 0.8, 0.1, 0.2, 0.3, 0.4, 0.5],
       [0.7, 0.8, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6],
       [0.8, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
] 

W_K = W_Q  # 简化:Q和K共享权重(实际可不同)

W_V = [
       [0.1, 0.1, 0.2, 0.2, 0.3, 0.3, 0.4, 0.4],
       [0.1, 0.2, 0.2, 0.3, 0.3, 0.4, 0.4, 0.1],
       [0.2, 0.2, 0.3, 0.3, 0.4, 0.4, 0.1, 0.1],
       [0.2, 0.3, 0.3, 0.4, 0.4, 0.1, 0.1, 0.2],
       [0.3, 0.3, 0.4, 0.4, 0.1, 0.1, 0.2, 0.2],
       [0.3, 0.4, 0.4, 0.1, 0.1, 0.2, 0.2, 0.3],
       [0.4, 0.4, 0.1, 0.1, 0.2, 0.2, 0.3, 0.3],
       [0.4, 0.1, 0.1, 0.2, 0.2, 0.3, 0.3, 0.4]
] 

根据公式相乘后得到的Q、K、V矩阵如下

Q = [
[3.02, 2.84, 2.07, 1.89, 1.71, 1.53, 1.35, 1.17],
[2.76, 2.59, 2.02, 1.85, 1.68, 1.51, 1.34, 1.17],
[3.83, 3.62, 2.61, 2.40, 2.19, 1.98, 1.77, 1.56]
]

Q = K

V = [
[1.53, 1.35, 1.17, 0.99, 0.81, 0.63, 0.45, 0.27],
[1.36, 1.20, 1.04, 0.88, 0.72, 0.56, 0.40, 0.24],
[1.92, 1.70, 1.48, 1.26, 1.04, 0.82, 0.60, 0.38]
]


步骤2 : 分割Q、K、V到多个注意力头

这里我们假设拆分为2个4维的头

Q1 = K1 = [
[3.02,2.84,2.07,1.89],
[2.76,2.59,2.02,1.85],
[3.83,3.62,2.61,2.40]]

Q2 = K2 = [
[1.71,1.53,1.35,1.17],
[1.68,1.51,1.34,1.17],
[2.19,1.98,1.77,1.56]
]

V1 = [
[1.53,1.35,1.17,0.99],
[1.36,1.20,1.04,0.88],
[1.92,1.70,1.48,1.26]
]

V2 = [
[0.81,0.63,0.45,0.27],
[0.72,0.56,0.40,0.24],
[1.04,0.82,0.60,0.38]
]

步骤3 : 计算每个头的注意力分数
注意力得分矩阵
Q1×K1^T = [
           [25.04, 23.37, 31.79],
           [23.37, 21.83, 29.66],
           [31.79, 29.66, 40.34]
]

scores = Q1×K1^T / 2 = [
                        [12.52, 11.68, 15.90],  # 行0(<bos>)
                        [11.68, 10.92, 14.83],  # 行1(i)
                        [15.90, 14.83, 20.17] # 行2(love)
]  
步骤4 : 应用下三角掩码
mask = [
        [0,    -∞,   -∞],  # 行0(<bos>):只能关注列0(自身)
        [0,     0,   -∞],  # 行1(i):只能关注列0、列1(自身及之前)
        [0,     0,    0]]  # 行2(love):可关注列0、1、2(所有前序及自身)

masked_scores = [
                  [12.52,  -∞,   -∞],
                 [11.68, 10.92,  -∞],
                 [15.90, 14.83, 20.17]
]
掩码后的注意力得分矩阵
步骤5 : 应用softmax函数
weights = [
           [1.00, 0.00, 0.00],  # 位置0:仅关注自身
           [0.69, 0.31, 0.00],  # 位置1:主要关注<bos>(0.69),次要关注自身(0.31)
           [0.01, 0.00, 0.98]  # 位置2:几乎关注自身(0.98),轻微关注前序词
]
步骤6 : 每个头的输出 = 权重 x V
V1_out = [
          [1.53, 1.35, 1.17, 0.99],  # 位置0(<bos>)
          [1.48, 1.30, 1.13, 0.95],  # 位置1(i)
          [1.90, 1.68, 1.46, 1.24]  # 位置2(love)
]
步骤7 : 合并所有头的结果,进行线性变换,得到最终的注意力得分矩阵

头2的计算结果如下

V2_out = [[0.81, 0.63, 0.45, 0.27],  # 位置0(<bos>)
          [0.76, 0.59, 0.43, 0.26],  # 位置1(i)
          [0.95, 0.75, 0.54, 0.34]]  # 位置2(love)

假设W_o如下

W_O = [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
       [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.1],
       [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.1, 0.2],
       [0.4, 0.5, 0.6, 0.7, 0.8, 0.1, 0.2, 0.3],
       [0.5, 0.6, 0.7, 0.8, 0.1, 0.2, 0.3, 0.4],
       [0.6, 0.7, 0.8, 0.1, 0.2, 0.3, 0.4, 0.5],
       [0.7, 0.8, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6],
       [0.8, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]]

最终计算输出的注意力得分矩阵

Z = [
[2.50, 3.01, 3.35, 3.56, 3.65, 3.57, 3.35, 2.99],  # 位置0(<bos>)
[2.37, 2.84, 3.18, 3.42, 3.50, 3.43, 3.23, 2.86],  # 位置1(i)
[3.05, 3.63, 4.10, 4.40, 4.52, 4.41, 4.12, 3.67]]  # 位置2(love)
第五步:残差连接&层归一化

这里的残差连接&层归一化与之前编码器层的残差连接&层归一化作用完全一样,唯一的区别仅因前置步骤的不同,导致其执行的意义有所不同,这里不在重复说明

残差连接计算结果

residual = [
            [2.62, 4.35, 3.30, 4.83, 3.47, 4.98, 3.12, 4.08],
            [3.77, 3.62, 3.81, 4.32, 3.89, 3.97, 3.42, 3.55],
            [4.34, 4.68, 4.51, 5.75, 4.65, 5.73, 3.90, 4.80]
]

层归一化计算结果

ln_output = [
             [-1.54,  0.64, -0.68,  1.24, -0.47,  1.43, -0.91,  0.30],  # 位置0(<bos>)
             [-0.08, -0.65,  0.08,  2.04,  0.38,  0.69, -1.39, -0.92],  # 位置1(i)
             [-0.77, -0.20, -0.48,  1.58, -0.25,  1.55, -1.50,  0.00]  # 位置2(love)
]

最终输出就是层归一化的计算结果

第六步:多头自注意力层

这里的多有自注意力层的执行过程与编码器层的完全一样,唯一的不同点在于,编码器层的多头自注意力层中的Q、K、V是W_q、W_k、W_v的权重矩阵与上一步的输出点积运算得到的,而解码器层的多头自注意力层中的K、V是编码器层的最终输出与W_k、W_v点积运算得到的,Q是其上一步的最终输出与W_q点积运算得到的。

步骤1 : 计算Q(查询)、K(键)、V(值)矩阵

我们先假定W_q、W_k、W_v、W_o值如下

# W_q(查询矩阵)
W_q = [
    [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
    [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.1],
    [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.1, 0.2],
    [0.4, 0.5, 0.6, 0.7, 0.8, 0.1, 0.2, 0.3],
    [0.5, 0.6, 0.7, 0.8, 0.1, 0.2, 0.3, 0.4],
    [0.6, 0.7, 0.8, 0.1, 0.2, 0.3, 0.4, 0.5],
    [0.7, 0.8, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6],
    [0.8, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
]

# W_k(键矩阵)
W_k = [
    [0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1],
    [0.1, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2],
    [0.2, 0.1, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3],
    [0.3, 0.2, 0.1, 0.8, 0.7, 0.6, 0.5, 0.4],
    [0.4, 0.3, 0.2, 0.1, 0.8, 0.7, 0.6, 0.5],
    [0.5, 0.4, 0.3, 0.2, 0.1, 0.8, 0.7, 0.6],
    [0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.8, 0.7],
    [0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.8]
]

# W_v(值矩阵)
W_v = [
    [0.5, 0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8],
    [0.6, 0.5, 0.1, 0.2, 0.3, 0.4, 0.7, 0.8],
    [0.7, 0.6, 0.5, 0.1, 0.2, 0.3, 0.4, 0.8],
    [0.8, 0.7, 0.6, 0.5, 0.1, 0.2, 0.3, 0.4],
    [0.1, 0.8, 0.7, 0.6, 0.5, 0.1, 0.2, 0.3],
    [0.2, 0.1, 0.8, 0.7, 0.6, 0.5, 0.1, 0.2],
    [0.3, 0.2, 0.1, 0.8, 0.7, 0.6, 0.5, 0.1],
    [0.4, 0.3, 0.2, 0.1, 0.8, 0.7, 0.6, 0.5]
]

# W_o(输出投影矩阵)
W_o = W_q

掩码矩阵为

pad_mask = [
    [0, 0, 0, 0, -float('inf'), -float('inf'), -float('inf'), -float('inf')],  # 解码器位置0
    [0, 0, 0, 0, -float('inf'), -float('inf'), -float('inf'), -float('inf')],  # 解码器位置1
    [0, 0, 0, 0, -float('inf'), -float('inf'), -float('inf'), -float('inf')]   # 解码器位置2
]

这里说明一下为什么掩码矩阵中掩的是4-7列,掩码的核心是 “屏蔽‘被关注方’的无效位置(PAD)”,计算注意力权重得分时,点积运算中每个头需要关注的是自己与其他键的注意力关系,这里“被关注方”就是K,而K在计算中是被转置了的,转置矩阵对应的PAD位是交叉注意力层中 “被关注方无效位置” 的具体映射结果,所以掩码矩阵中掩掉的是4-7列。

Q矩阵 = ln_output x W_q

Q = [
    [0.52, 0.68, 0.83, 0.97, 1.12, 1.25, 1.38, 1.50],  # 位置0
    [0.49, 0.65, 0.80, 0.94, 1.09, 1.22, 1.35, 1.47],  # 位置1
    [0.55, 0.71, 0.86, 1.00, 1.15, 1.28, 1.41, 1.53]   # 位置2
]

K = output(编码器层最终输出) x W_k

K = [
    [1.63, 1.75, 1.87, 1.99, 2.11, 2.23, 2.35, 2.47],  # 位置0(有效)
    [1.71, 1.83, 1.95, 2.07, 2.19, 2.31, 2.43, 2.55],  # 位置1(有效)
    [1.79, 1.91, 2.03, 2.15, 2.27, 2.39, 2.51, 2.63],  # 位置2(有效)
    [1.87, 1.99, 2.11, 2.23, 2.35, 2.47, 2.59, 2.71],  # 位置3(有效)
    [1.95, 2.07, 2.19, 2.31, 2.43, 2.55, 2.67, 2.79],  # 位置4(PAD,非零)
    [2.03, 2.15, 2.27, 2.39, 2.51, 2.63, 2.75, 2.87],  # 位置5(PAD,非零)
    [2.11, 2.23, 2.35, 2.47, 2.59, 2.71, 2.83, 2.95],  # 位置6(PAD,非零)
    [2.19, 2.31, 2.43, 2.55, 2.67, 2.79, 2.91, 3.03]   # 位置7(PAD,非零)
]

V = output(编码器层最终输出) x W_v

V = [
    [1.02, 1.14, 1.26, 1.38, 1.50, 1.62, 1.74, 1.86],  # 位置0(有效)
    [1.10, 1.22, 1.34, 1.46, 1.58, 1.70, 1.82, 1.94],  # 位置1(有效)
    [1.18, 1.30, 1.42, 1.54, 1.66, 1.78, 1.90, 2.02],  # 位置2(有效)
    [1.26, 1.38, 1.50, 1.62, 1.74, 1.86, 1.98, 2.10],  # 位置3(有效)
    [1.34, 1.46, 1.58, 1.70, 1.82, 1.94, 2.06, 2.18],  # 位置4(PAD,非零)
    [1.42, 1.54, 1.66, 1.78, 1.90, 2.02, 2.14, 2.26],  # 位置5(PAD,非零)
    [1.50, 1.62, 1.74, 1.86, 1.98, 2.10, 2.22, 2.34],  # 位置6(PAD,非零)
    [1.58, 1.70, 1.82, 1.94, 2.06, 2.18, 2.30, 2.42]   # 位置7(PAD,非零)
]
步骤2 :分割Q、K、V到多个注意力头

这里我们依然假设拆分为2个4维的头,下面仅展示头1的计算过程

Q1 = [
    [0.52, 0.68, 0.83, 0.97],  # 位置0
    [0.49, 0.65, 0.80, 0.94],  # 位置1
    [0.55, 0.71, 0.86, 1.00]   # 位置2
]

K1 = [
    [1.63, 1.75, 1.87, 1.99],  # 位置0(有效)
    [1.71, 1.83, 1.95, 2.07],  # 位置1(有效)
    [1.79, 1.91, 2.03, 2.15],  # 位置2(有效)
    [1.87, 1.99, 2.11, 2.23],  # 位置3(有效)
    [1.95, 2.07, 2.19, 2.31],  # 位置4(PAD)
    [2.03, 2.15, 2.27, 2.39],  # 位置5(PAD)
    [2.11, 2.23, 2.35, 2.47],  # 位置6(PAD)
    [2.19, 2.31, 2.43, 2.55]   # 位置7(PAD)
]

V1 = [
    [1.02, 1.14, 1.26, 1.38],  # 位置0(有效)
    [1.10, 1.22, 1.34, 1.46],  # 位置1(有效)
    [1.18, 1.30, 1.42, 1.54],  # 位置2(有效)
    [1.26, 1.38, 1.50, 1.62],  # 位置3(有效)
    [1.34, 1.46, 1.58, 1.70],  # 位置4(PAD)
    [1.42, 1.54, 1.66, 1.78],  # 位置5(PAD)
    [1.50, 1.62, 1.74, 1.86],  # 位置6(PAD)
    [1.58, 1.70, 1.82, 1.94]   # 位置7(PAD)
]

步骤3 :计算每个头的注意力分数
scores1= Q1×K1^T / 2 = [
    [2.60, 2.74, 2.88, 3.02, 3.16, 3.30, 3.44, 3.58],
    [2.52, 2.66, 2.80, 2.94, 3.08, 3.22, 3.36, 3.50],
    [2.68, 2.82, 2.96, 3.10, 3.24, 3.38, 3.52, 3.66]
]
步骤4 :应用下三角掩码
masked_scores1 = [
    [2.60, 2.74, 2.88, 3.02, -inf, -inf, -inf, -inf],
    [2.52, 2.66, 2.80, 2.94, -inf, -inf, -inf, -inf],
    [2.68, 2.82, 2.96, 3.10, -inf, -inf, -inf, -inf]
]
步骤5 : 应用softmax函数
weights1 = [
    [0.20, 0.23, 0.27, 0.30, 0.00, 0.00, 0.00, 0.00],
    [0.21, 0.24, 0.26, 0.29, 0.00, 0.00, 0.00, 0.00],
    [0.19, 0.22, 0.25, 0.34, 0.00, 0.00, 0.00, 0.00]
]
步骤6 : 每个头的输出 = 权重 x V
head1_output = [
    [1.16, 1.28, 1.40, 1.52],
    [1.15, 1.27, 1.39, 1.51],
    [1.17, 1.29, 1.41, 1.53]
]
步骤7 : 合并所有头的结果,进行线性变换,得到最终的注意力得分矩阵

头2最终的计算得分为

head2_output = [
    [1.66, 1.78, 1.90, 2.02],
    [1.65, 1.77, 1.89, 2.01],
    [1.67, 1.79, 1.91, 2.03]
]

合并后为

concat_output = [
    [1.16, 1.28, 1.40, 1.52, 1.66, 1.78, 1.90, 2.02],  # 位置0
    [1.15, 1.27, 1.39, 1.51, 1.65, 1.77, 1.89, 2.01],  # 位置1
    [1.17, 1.29, 1.41, 1.53, 1.67, 1.79, 1.91, 2.03]   # 位置2
]

由于W_o = W_q,所以最终该步骤的输出结果为

cross_attention_output = [
   [2.42, 2.59, 2.76, 2.93, 3.10, 3.27, 3.44, 3.61],  # 位置0
   [2.40, 2.57, 2.74, 2.91, 3.08, 3.25, 3.42, 3.59],  # 位置1
   [2.44, 2.61, 2.78, 2.95, 3.12, 3.29, 3.46, 3.63]   # 位置2
]
第七步:残差连接&层归一化

与之前的计算过程一样,这里不重复说明

残差连接计算结果为

residual = [
    [0.88, 3.23, 2.08, 4.17, 2.63, 4.70, 2.53, 3.91],  # 位置0
    [2.32, 1.92, 2.82, 4.95, 3.46, 3.94, 2.03, 2.67],  # 位置1
    [1.67, 2.41, 2.30, 4.53, 2.87, 4.84, 1.96, 3.63]   # 位置2
]

层归一化计算结果为

decoder_cross_attn_final = [
    [-1.836, 0.181, -0.802, 0.991, -0.336, 1.448, -0.422, 0.767],  # 位置0
    [-0.708, -1.118, -0.195, 1.988, 0.462, 0.954, -1.005, -0.349],  # 位置1
    [-1.226, -0.559, -0.658, 1.353, -0.144, 1.632, -0.965, 0.541]   # 位置2
]
第八步:前馈网络层

该步骤与编译器层中的前馈网络层执行过程与作用完全一致,这里步重复说明

假设W1、W2、b1、b2如下

W1 = [
    [0.1, 0.2, 0.3, 0.4, ..., 0.1],  # 输入第0维→32维映射(共32列)
    [0.2, 0.3, 0.4, 0.5, ..., 0.2],  # 输入第1维→32维映射
    [0.3, 0.4, 0.5, 0.6, ..., 0.3],  # 输入第2维→32维映射
    [0.4, 0.5, 0.6, 0.7, ..., 0.4],  # 输入第3维→32维映射
    [0.5, 0.6, 0.7, 0.8, ..., 0.5],  # 输入第4维→32维映射
    [0.6, 0.7, 0.8, 0.1, ..., 0.6],  # 输入第5维→32维映射
    [0.7, 0.8, 0.1, 0.2, ..., 0.7],  # 输入第6维→32维映射
    [0.8, 0.1, 0.2, 0.3, ..., 0.8]   # 输入第7维→32维映射
]
b1 = [0.1] * 32  # 偏置(32维,均为0.1)

W2 = [
    [0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1],  # 中间第0维→输出8维映射
    [0.1, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2],  # 中间第1维→输出8维映射
    [0.2, 0.1, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3],  # 中间第2维→输出8维映射
    [0.3, 0.2, 0.1, 0.8, 0.7, 0.6, 0.5, 0.4],  # 中间第3维→输出8维映射
    ...  # 共32行
]
b2 = [0.2] * 8  # 偏置(8维,均为0.2)
步骤1 :线性变换升维

依据是扩充4倍

#这里计算保留到了小数点后四位
linear1_output = [
    # 位置0(32维):[z0, z1, z2, ..., z31]
    [1.2300, 1.3500, 1.4700, 1.5900, 1.7100, 1.8300, 1.9500, 2.0700,
     2.1900, 2.3100, 2.4300, 2.5500, 2.6700, 2.7900, 2.9100, 3.0300,
     3.1500, 3.2700, 3.3900, 3.5100, 3.6300, 3.7500, 3.8700, 3.9900,
     4.1100, 4.2300, 4.3500, 4.4700, 4.5900, 4.7100, 4.8300, 4.9500],
    # 位置1(32维):[z0, z1, ..., z31]
    [1.1500, 1.2700, 1.3900, 1.5100, 1.6300, 1.7500, 1.8700, 1.9900,
     2.1100, 2.2300, 2.3500, 2.4700, 2.5900, 2.7100, 2.8300, 2.9500,
     3.0700, 3.1900, 3.3100, 3.4300, 3.5500, 3.6700, 3.7900, 3.9100,
     4.0300, 4.1500, 4.2700, 4.3900, 4.5100, 4.6300, 4.7500, 4.8700],
    # 位置2(32维):[z0, z1, ..., z31]
    [1.3100, 1.4300, 1.5500, 1.6700, 1.7900, 1.9100, 2.0300, 2.1500,
     2.2700, 2.3900, 2.5100, 2.6300, 2.7500, 2.8700, 2.9900, 3.1100,
     3.2300, 3.3500, 3.4700, 3.5900, 3.7100, 3.8300, 3.9500, 4.0700,
     4.1900, 4.3100, 4.4300, 4.5500, 4.6700, 4.7900, 4.9100, 5.0300]
]
步骤2:非线性变换激活函数(GULE)
gelu_output = [
    # 位置0(32维,前8维示例)
    [1.0930, 1.2015, 1.3082, 1.4131, 1.5162, 1.6175, 1.7170, 1.8147,
     1.9106, 2.0048, 2.0972, 2.1878, 2.2767, 2.3639, 2.4494, 2.5332,
     2.6153, 2.6958, 2.7746, 2.8518, 2.9274, 3.0014, 3.0738, 3.1446,
     3.2139, 3.2817, 3.3479, 3.4126, 3.4758, 3.5375, 3.5977, 3.6565],
    # 位置1(32维,前8维示例)
    [1.0210, 1.1265, 1.2302, 1.3321, 1.4322, 1.5305, 1.6270, 1.7217,
     1.8146, 1.9058, 1.9952, 2.0828, 2.1687, 2.2529, 2.3354, 2.4162,
     2.4953, 2.5728, 2.6486, 2.7228, 2.7954, 2.8664, 2.9358, 3.0037,
     3.0701, 3.1350, 3.1984, 3.2604, 3.3210, 3.3802, 3.4380, 3.4944],
    # 位置2(32维,前8维示例)
    [1.1580, 1.2685, 1.3772, 1.4841, 1.5892, 1.6925, 1.7940, 1.8937,
     1.9916, 2.0878, 2.1822, 2.2748, 2.3657, 2.4549, 2.5424, 2.6282,
     2.7123, 2.7948, 2.8756, 2.9548, 3.0324, 3.1084, 3.1828, 3.2556,
     3.3269, 3.3967, 3.4650, 3.5318, 3.5972, 3.6611, 3.7236, 3.7847]
]
步骤3 : 降维
ffn_output = [
    [3.824, 3.992, 4.160, 4.328, 4.496, 4.664, 4.832, 5.000],  # 位置0(8维)
    [3.672, 3.836, 4.000, 4.164, 4.328, 4.492, 4.656, 4.820],  # 位置1(8维)
    [3.976, 4.148, 4.320, 4.492, 4.664, 4.836, 5.008, 5.180]   # 位置2(8维)
]
第九步:残差连接&层归一化

该步骤与前面的一样

残差连接计算结果为

residual_ffn = [
    [1.988, 4.173, 3.358, 5.319, 4.160, 6.112, 4.410, 5.767],  # 位置0
    [2.964, 2.718, 3.805, 6.152, 4.790, 5.446, 3.651, 4.471],  # 位置1
    [2.750, 3.589, 3.662, 5.845, 4.520, 6.468, 4.043, 5.721]   # 位置2
]

层归一化计算结果为

decoder_layer_final = [
    [-1.4823, 0.3867, -0.0612, 1.1895, 0.0124, 1.6542, 0.5831, 0.8876],  # 位置0
    [-0.0215, -0.7983, 0.3768, 1.8217, 0.7549, 1.2986, -0.0453, 0.1221],  # 位置1
    [-0.7215, 0.2098, 0.2873, 1.3987, 0.5324, 1.9568, 0.1605, 1.2070]   # 位置2
]
第十步:多层循环

这里的多层循环堆叠与编码器层的一样,这里不再重复说明

这里我们依然假设执行6次后最终的输出结果为

output = [
    [-1.4823, 0.3867, -0.0612, 1.1895, 0.0124, 1.6542, 0.5831, 0.8876],  # 位置0
    [-0.0215, -0.7983, 0.3768, 1.8217, 0.7549, 1.2986, -0.0453, 0.1221],  # 位置1
    [-0.7215, 0.2098, 0.2873, 1.3987, 0.5324, 1.9568, 0.1605, 1.2070]   # 位置2
]

解码器层多层堆叠计算得到的最终输出,本质也是一个综合语义的特征矩阵,但是编码器层所综合的并不是单纯的用户输入,而是包含用户输入和已生成内容的综合语义,它更加的丰富,这样的原因是为了让生成的内容更加的符合用户输入的语义,让生成的内容更加的连贯,前后呼应。

第十一步:线性层

线性层是生成任务的最后一公里,其核心作用是通过简单的线性变换将解码器多次堆叠输出的高维特征矩阵映射到词汇表维度,所谓的词汇表维度指的是通过线性层计算得到的原始分数能够体现每个词与当前语义特征的匹配程度得分,这些分数经下一步的softmax归一化后,就能转化为每个词出现在当前位置的概率,所以线性层最终得到一个未归一化的分数矩阵,所谓的未归一化是指没有进过softmax进行归一化操作,让分数矩阵中的数值的总和变为1,更加精准稳定的体现下一个词在词汇表中的概率分布。
计算公式:

线性层计算公式

X:线性层的输入,也就是第十步多层循环堆叠计算输出的结果
W(权重矩阵):形状为[d_model, vocab_size],d_model是模型统一的维度,其中vocab_size是输出词汇表的大小意味着如果词汇有10万个词,那么vocab_size就等于10万,因为需要通过vocab_size的维来映射对应词汇表中每一个词,计算出每个词的得分权重。W的每一列对应词汇表中一个词的 “投影权重”,用于将d_model维特征映射到该词的分数。
B(偏置向量):形状为[vocab_size](可选,部分实现可能省略),每个元素对应词汇表中一个词的 “偏移量”,用于微调该词的分数。

这里我们假定词汇表中总共有10个词如下,这也意味着vocab_size为10

索引 对应的词
0 <pad>:占位符
1 <unk>:未知符号
2 <bos>:起始符号
3 <eos>:结束符号
4 me
5 it
6 this
7 the
8 that
9 you
# 线性层权重W(8×10)
W = [
    [0.2134, 0.1856, 0.3421, 0.4178, 0.0987, 0.5234, 0.2765, 0.3912, 0.1543, 0.4890],  # 输入第0维→词表10维
    [0.1765, 0.2987, 0.0892, 0.3541, 0.5123, 0.1456, 0.4237, 0.2689, 0.3871, 0.1298],  # 输入第1维→词表10维
    [0.3219, 0.0765, 0.2843, 0.1598, 0.3972, 0.2468, 0.1890, 0.4532, 0.0987, 0.3654],  # 输入第2维→词表10维
    [0.4682, 0.3157, 0.1984, 0.0876, 0.2543, 0.3891, 0.5126, 0.1745, 0.2963, 0.0689],  # 输入第3维→词表10维
    [0.1432, 0.4896, 0.3657, 0.2189, 0.0745, 0.1987, 0.3214, 0.4568, 0.5092, 0.2876],  # 输入第4维→词表10维
    [0.5012, 0.2345, 0.1678, 0.3921, 0.4765, 0.0893, 0.2154, 0.1387, 0.3642, 0.4291],  # 输入第5维→词表10维
    [0.2679, 0.3841, 0.4562, 0.1237, 0.2984, 0.3516, 0.0798, 0.4123, 0.1854, 0.2467],  # 输入第6维→词表10维
    [0.3987, 0.1542, 0.2763, 0.4891, 0.1357, 0.2684, 0.3192, 0.0985, 0.4321, 0.1768]   # 输入第7维→词表10维
]

# 线性层偏置b(10维)
b = [0.05, 0.03, 0.07, 0.02, 0.06, 0.04, 0.01, 0.08, 0.09, 0.02]

最终根据公式计算出的结果为

# 输入中第0行为[-1.4823, 0.3867, -0.0612, 1.1895, 0.0124, 1.6542, 0.5831, 0.8876]
# logits [0][0] = input[0][0] x W[0][0] + input[0][1] x W[1][0] + input[0][2] x W[2][0] + input[0][3] x W[3][0] + input[0][4] x W[4][0] + input[0][5] x W[5][0] + input[0][6] x W[6][0] + input[0][7] x W[7][0] + b[0]
# logits [0][0] = (−1.4823×0.2134)+(0.3867×0.1765)+(−0.0612×0.3219)+(1.1895×0.4682)+(0.0124×0.1432)+(1.6542×0.5012)+(0.5831×0.2679)+(0.8876×0.3987) + 0.05 
# logits [0][0] = 1.6893
# 线性层输出logits(3×10):每行对应一个位置,每列对应词表一个词的分数
logits = [
    # 位置0(<bos>):词表0-9的logits
    [1.6893, 1.2457, 0.9872, 1.8321, 0.7654, 2.0146, 1.3589, 1.5762, 0.6983, 1.9215],
    # 位置1(i):词表0-9的logits
    [1.4321, 1.7654, 1.1298, 0.8976, 1.9345, 1.2873, 0.7542, 1.6891, 1.3567, 0.9872],
    # 位置2(love):词表0-9的logits
    [2.1568, 1.3987, 1.8765, 0.9342, 1.5678, 0.8912, 1.7435, 1.2896, 1.9873, 2.3145]
]
第十二步:softmax层

线性层计算出的结果是一个为归一化的,最后通过softmax函数进行归一化操作,转化为0~1 之间、总和为 1 的概率分布,最终明确每个词在当前位置的出现概率。
计算公式:


softmax计算公式

logits_i : logits对应位置向量中的第i个元素(对应词表第i个词的原始分数),每个位置(行)会单独进行计算
e^logits_i : 自然指数运算(将正负分数转化为非负值,保留分数相对差异)

分母:logits 向量所有元素的指数和(确保归一化后总和为 1)

# 这里我仅展示logits[0]的计算过程
# 先计算指数以及指数之和
# logits [0] = [1.6893, 1.2457, 0.9872, 1.8321, 0.7654, 2.0146, 1.3589, 1.5762, 0.6983, 1.9215]
# e^logits_0 = e^1.6893 ≈ 5.418
# e^logits_1 = e^1.2457  ≈ 3.474
# e^logits_2 = e^0.9872  ≈ 2.685
# e^logits_3 = e^1.8321 ≈ 6.254
# e^logits_4 = e^0.7654 ≈ 2.150
# e^logits_5 = e^2.0146 ≈ 7.598
# e^logits_6 = e^1.3589 ≈ 3.892
# e^logits_7 = e^1.5762 ≈ 4.836
# e^logits_8 = e^0.6983 ≈ 2.010
# e^logits_9 = e^1.9215 ≈ 6.8320
# 分母为指数之和 = 5.418 + 3.474 + 2.685 + 6.254 + 2.150 + 7.598 + 3.892 + 4.836 + 2.010 + 6.8320 
# 分母为指数之和 = 45.149

# 指数百分比计算
softmax_fina[0][0] = 5.418 / 45.149 ≈ 0.1200
softmax_fina[0][1] = 3.474 / 45.149 ≈ 0.0770
softmax_fina[0][2] = 2.68 / 45.149 ≈ 0.0595
softmax_fina[0][3] = 6.254 / 45.149 ≈ 0.1385
softmax_fina[0][4] = 2.150 / 45.149 ≈ 0.0476
softmax_fina[0][5] = 7.598 / 45.149 ≈ 0.1683(概率最高)
softmax_fina[0][6] = 3.892 / 45.149 ≈ 0.0862
softmax_fina[0][7] = 4.836 / 45.149 ≈ 0.1071
softmax_fina[0][8] = 2.010 / 45.149 ≈ 0.0445
softmax_fina[0][9] = 6.8320 / 45.149 ≈ 0.1513

softmax_final = [
    [0.1200, 0.0770, 0.0595, 0.1385, 0.0476, 0.1683, 0.0862, 0.1071, 0.0445, 0.1513],  # 位置0(<bos>)
    [0.1039, 0.1449, 0.0767, 0.0608, 0.1717, 0.0898, 0.0527, 0.1342, 0.0963, 0.0665],  # 位置1(i)
    [0.1551, 0.0727, 0.1170, 0.0456, 0.0860, 0.0437, 0.1024, 0.0651, 0.1308, 0.1816]   # 位置2(love)
]

softmax_final[0]:表示<bos>之后需要生成的第一个词,对应“i”
softmax_final[1]:表示i之后需要生成的第二个词,对应“love”
softmax_final[2]:表示love之后需要生成的第三个词,其概率最高的是softmax_final[2][9] = 0.1816。该位置映射的是词汇表中的“you”
因此最终生成的词是 you


大模型参数

之所以先说Transformer架构,再说大模型参数是因为要更为直观的理解大模型参数,还是得通过了解模型参数在架构中具体是应用在什么地方的。
大模型中的参数分为两大类,模型参数超参数,其核心区别是,是否参与训练过程的梯度更新。
超参数:指得是模型设计阶段,由开发者设计固定值的参数,例如Transformer架构原文中堆叠的层数为6,位置编码的周期范围10000等
模型参数:指得是模型在通过训练能不断的自主优化调整的参数,例如多头自注意力中的W_q、W_k、W_v、W_o权重矩阵里面的参数等

模型超参数组成
  • 1、词汇表大小(vocab_size)
  • 2、模型隐藏层维度(d_model)
  • 3、堆叠层数(编码器 N / 解码器 N)
  • 4、多头注意力头数(h)
  • 5、前馈网络隐藏层维度(d_ff)
  • 6、输入输出最大序列长度(max_seq_len)
  • 7、位置编码周期范围(如 10000)
  • 8、Dropout 率(dropout_rate)
  • 9、注意力缩放因子(√d_k)
  • 10、批大小(batch_size)
  • 11、学习率(learning_rate)
  • 12、优化器参数(如 Adam 的β1/β2)
  • 13、训练轮数(epochs)。
    由于本文是基于推理流程进行Transformer架构说明的,所以第10-13个参数没有涉及到,这几个参数是训练是会用到的超参数,并且不同模型中会用的超参数会有所不同
模型参数组成(编码器层 + 解码器层)
编码器层(共 N 层,每层独立参数):
  • 1、输入嵌入层:嵌入矩阵Embedding,形状[vocab_size, d_model]
  • 2、多头自注意力层:权重矩阵 W_q/W_k/W_v/W_o,形状均为[d_model, d_model],可选偏置 bq/bk/bv/bo,形状均为[d_model]
  • 3、层归一化:缩放因子γ、偏移因子β,形状均为[d_model]
  • 4、前馈网络层:权重矩阵 W1[d_model, d_ff]、W2[d_ff, d_model],可选偏置 b1[d_ff]、b2[d_model])
  • 5、归一化层:缩放因子γ、偏移因子β,形状均为[d_model])
解码器层(共 N 层,每层独立参数)
  • 1、输入嵌入层:嵌入矩阵Embedding,与编码器输入嵌入层共享,形状[vocab_size, d_model])
  • 2、掩码自注意力层:权重矩阵 W_q/W_k/W_v/W_o[d_model, d_model],可选偏置 bq/bk/bv/bo[d_model])
  • 3、归一化层:缩放因子γ、偏移因子β,形状均为[d_model])
  • 4、多头自注意力层:权重矩阵 W_q/W_k/W_v/W_o[d_model, d_model],可选偏置 bq/bk/bv/bo[d_model])
  • 5、归一化层:缩放因子γ、偏移因子β,形状均为[d_model])
  • 6、前馈网络层(FFN):权重矩阵W1[d_model, d_ff]、W2[d_ff, d_model],可选偏置 b1[d_ff]、b2[d_model])
  • 7、归一化层:缩放因子γ、偏移因子β,形状均为[d_model])
  • 8、输出投影层:权重矩阵 W[d_model, vocab_size],与嵌入矩阵共享;可选偏置 b[vocab_size])

在Transformer原文《Attention Is All You Need》的注意力层中并没有提及偏置参数,

虽然目前所有主流大模型都是基于Transformer架构而诞生的,但却都在Transformer的基础上做了非常大的改变,例如大部分主流的大模型目前都只有解码器层了,而没有了编码器层,例如Deepseek和GPT。为了结合文章上下文,这里我以Transformer原文《Attention Is All You Need》中模型的参数来展示模型参数的计算过程

超参数类别 数值
词汇表大小(vocab_size) 32,768
编码器层数 6
解码器层数 6
每个 token 维度 512
位置编码周期范围 10000
分割头数 8
输入输出长度限制 512
前馈网络维度 2048
编码器层

嵌入矩阵层参数数量 :16777216, vocab_size x d_model = 32768 x 512
多头自注意力层参数数量:1048576,权重矩阵 W_q/W_k/W_v/W_o 参数数量(3 x d_model x d_model = 3 x 512 x 512 = 786432)+ 投影参数 ( d_model × d_model = 512 x 512 = 262144)
前馈网络层参数数量:2097152,W1参数(d_model × d_ff = 512 x 2048 = 1048576)+W2参数(d_ff × d_model = 512 x 2048 = 1048576)
2次层归一化:2048,2 x 2 x d_model
单层编码器层参数总数:3147776
所有编码器参数总数:18886656, 6 x 3147776
编码器层参数总数:35663872,嵌入矩阵层参数数量 + 所有编码器参数总数 = 16777216 + 18886656

解码器层
嵌入矩阵参数数量:16777216
掩码自注意力层:1048576
多头自注意力层:1048576
前馈网络层:2097152
3次归一化层:3072
单层解码器层参数总数:4197376
所有解码器层参数总数:25184256,6 x 4197376
线性层线性输出层参数:d_model x V = 512 x 32768 = 16777216
解码器层参数总数:41961472,嵌入矩阵参数数量 + 所有解码器层参数总数(由于解码器层中嵌入矩阵参数数量与线性层线性输出层参数是共享的权重矩阵,因此不需要重复相加)

Transformer总参数 =0.0776B,77625344, 编码器层参数总数 + 解码器层参数总数 = 35663872 + 41961472

其他补充

什么是BPE

BPE分词算法相对来说是比较简单的,其本质也是一个统计模型,核心是统计字词频率进行的合并的贪心算法,一般是先将语料拆分为最小单位的字符集,去重形成初始的词汇表,然后在语料中统计字符出现频率,以及字符之间“相邻字符对 / 子词对”出现的频率,当频率达到高频条件后,将其合并为新的子词并加入词汇表,最后重复该过程直到达到预设词汇表大小或迭代次数,最终形成包含“字符”+“词”组成的词汇表。

从数据的角度来看 Transformer 中的Encoder(编码器)和Decoder(解码器)的依存关系

Transformer中Encoder与Decoder的依存关系,是Encoder产生的“语义理解数据”对Decoder“语义生成数据”的单向支撑关系
Encoder是“数据理解端”:将输入数据转化为“可被 Decoder读取的语义数据”,是整个架构的“数据源头”。
Decoder是“数据生成端”:以 Encoder的语义数据为约束,结合自身生成数据,逐步产出目标序列,是整个架构的“数据出口”。
数据是二者连接的唯一桥梁:Encoder的输出数据决定了Decoder生成的“语义边界”,Decoder的生成过程是对 Encoder语义数据的“具象化表达”。
因为是单向支撑关系,并不是互相依赖关系,所以两者并不是强依赖不可拆分的。

在自注意力和前馈网络层中增加偏置参数的作用

偏置参数的核心作用是用于提升模型编码器阶段对原文的理解能力,解码器阶段对表达能力和自回归上下文的适应能力,能通过极小的参数量代价 (<1% 总参数) 带来较大幅度的提升。

为何需要 Q、K、V 三个矩阵?只用 V 矩阵为何不可行?

自注意力机制的本质是通过计算明确 token 之间的关联性,就好比相亲:首先,一方父母(当前 token 的 Q)根据自家孩子的需求,去了解所有潜在对象的条件(其他 token 的 K),筛选出匹配度高的对象(Q x K^T 计算相似度)。匹配成功后,再通过约见深入了解对方的真实情况(比如性格、爱好,对应 V 里的核心语义信息),结合之前的匹配度,最终判断是否合适。这就好比 Q、K、V:Q 是‘我的需求’,K 是‘对方的条件’,V 是‘对方的真实人设’,三者缺一不可。如果只用 V,就相当于没有父母筛选,盲目和所有人相亲,既丢失了‘按需匹配’的能力,又导致效率极低、试错成本极高,最终还不一定能找到满意的对象(对应注意力机制失效)。

为什么现在主流的大模型都采用纯解码器架构?

1、任务适配:LLM 的核心任务是自回归生成,一般用于续写、对话、创作等,需要基于前文预测下一个 token,解码器的掩码自注意力层天然限制模型只能看到当前及之前的 token,完全契合自回归逻辑,所以纯解码器从架构上更加适合这类任务。
2、数据效率:纯解码器仅需单序列文本数据即可训练,而编码器 - 解码器需要输入+输出成对数据来训练,从数据的获取成本来看,纯解码器更便捷,效率更高
3、工程成熟度:纯解码器的训练 / 推理优化更成熟,能支撑千亿级参数规模,而编码器 - 解码器的双结构优化更复杂
4、性能优势:纯解码器在长文本生成、语义连贯性上表现更优,而编码器 - 解码器更擅长序列转换,更适合做文章内容摘要、翻译任务,而非开放式生成任务。

目前几乎所有主流大模型都是采用的纯解码器架构

模型名称 架构类型
Qwen系列 纯解码器架构
Deepseek系列 纯解码器架构,从 GPT-1 到 GPT-4,全部采用纯解码器架构
GPT系列 纯解码器架构
Gemini系列 纯解码器架构
Hunyuan系列 纯解码器架构
PanguLM系列 纯解码器架构
Doubao系列 纯解码器架构
Claude系列 纯解码器架构
Llama系列 纯解码器架构
ERNIE系列 纯解码器架构,早起的1.0-3.0版本是纯编码器架构,用于专注于文本理解任务,后续都采用的纯解码器架构
什么是张量?

张量的本质是一个“带维度的数值容器”,它通过固定的维度结构组织数值,让零散的单个数据形成有逻辑关联的整体,其中维度结构决定了数据的组织方式,数据数量是各维度长度的乘积。比如小王的单场考试分数为“95分”,这里的“95分”就无需额外维度描述,是一个纯粹的数值,是一个0维的张量。而我们用班级作为维度去统计,比如记录一个班同学考试的分数,[90,85,95,78,88]这就是一个1维张量,这里的维度指的就是分数。例如我们要形容2D地图中的位置,我们可以用经度和纬度两个维度的数据来表示一个信息[116.4°E, 39.9°N],这就是2维张量。由此可见,维度越多,意味着描述数据所需的“参照维度”越多,数据间的关联关系也越复杂,但张量的“大小”并非由维度数量决定,而是各维度长度的乘积决定。比如3维张量[2,3,4],其中2所在位置的维度是班级,因此这里2表示2班,3所在位置的维度表示小组个数,3表示每个班级中有3个小组,4所在的位置的维度表示学生数量,4表示每个小组中有4个学生,因此共有24个学生也就是共有24个元素,而2维张量[10,10](10行10列的表格)共有100个元素,后者维度更少,但“大小”反而更大。维度数量影响的是计算的“复杂度”而非“规模”,因为高维张量的运算需要兼顾每个维度的匹配关系。在大模型中一般都是一些高维结构的向量数据,例如文本内容“[批量数, 句子长度, 词向量维度]”的3维张量(32个句子×128个词×512维特征),处理图像时,则是“[批量数, 像素高度, 像素宽度, 颜色通道]”的4维张量(64张图片×256×256像素×3个RGB通道)。这些高维结构能完整保留数据的空间、时序或语义关联,是模型学习规律的基础。

正因如此,张量的“一致性”至关重要。这里的“一致性”并非指所有张量完全相同,而是指参与计算的张量必须满足“维度匹配规则”。如果批量计算时张量不一致,会直接导致两种严重问题:

  • 元素级运算直接报错:像模型中常见的“残差连接”(特征输出与原始输入相加),要求两个张量的维度和各维度长度完全一致。若一个是[32,128,512]的特征张量,另一个是[32,64,512]的输入张量,就像“把3行数据和2行数据逐行相加”,对应位置完全错位,框架会直接抛出维度不匹配错误。

  • 矩阵类运算结果混乱:Transformer的注意力计算(如Q×K^T)要求“前一个张量的最后一维长度 = 后一个张量的倒数第二维长度”。若Q张量是[32,128,64],K张量误设为[32,128,128],两者无法正常相乘,要么运算失败,要么强行兼容后输出毫无意义的数值,导致模型预测精度骤降甚至完全失效。

因此Transformer中,为避免这种问题,会通过“填充(Padding)”“裁剪(Truncation)”或“维度重塑(Reshape)”等操作确保一致性。比如批量输入文本时,对短句子补0到统一长度,让所有样本的张量都符合[32,128,512]的标准形状——这就像快递运输前要将包裹整理成统一规格,才能高效分拣和传送,张量的一致性正是模型高效、准确计算的“物流标准”。

什么是大模型的涌现力?

大模型涌现力的本质是:模型涌现出小模型所不具备的全新能力,在Transformer 架构下,模型为了完成 “预测下一个词” 的核心训练目标,通过超大参数规模承载更多知识、海量多领域数据覆盖更全规律,最终涌现生成超出训练目标的全新结果,例如模型可以根据因果关系,多维数据的融合分析输出一个具有创新性的内容,这种涌现能力是小模型完全不具备、且无法提前设计的全新能力。涌现力的前提是模型拥有“更好的理解能力”,因此需要更大规模的参数,其次涌现力的体现是具备全新的能力,表现是生成“创新性内容”,因此要给大模型训练超大规模的数据才能让模型具备这样突破临界值的涌现力的表现。

模型的算力要求计算方式

一般在模型部署时我们主要考虑四类资源,GPU 显存、算力、主机内存、存储。接下来我们以Transformer原文架构模型进行这四类资源需求计算,分为单次计算和并发100次来说明

一、单词计算的资源需求计算

资源需求聚焦 “模型加载 + 单请求计算”,核心是显存(GPU),算力决定推理速度。

  1. GPU 显存(核心资源)
    显存消耗 = 模型参数显存 + 运行时显存(中间激活值) + 输入输出显存 + 20% 余量(部署预留)。

模型参数显存(静态,仅加载一次):
公式:参数总数 × 单参数字节数 ÷ 1024³(转换为 GB)
计算:77,600,000 × 2(FP16) ÷ 1024³ ≈ 155.2MB ≈ 0.145GB。

运行时显存(动态,单请求中间计算结果):
公式:n_layer(编 / 解码器层数) × d_model (模型维度)× max_seq_len(最大序列长度) × 2(FP16) ÷ 1024³
计算:6×512×512×2 ÷ 1024³ ≈ 3.14MB ≈ 0.003GB(77.6M 模型此部分可忽略)。

输入输出显存(动态,单请求 token Embedding):
公式:max_seq_len(输出或输入序列最大长度) × d_model (模型维度)× 2(FP16) ÷ 1024³
计算:512×512×2 ÷ 1024³ ≈ 0.5MB ≈ 0.0005GB。
总显存(含 20% 余量):
(0.145+0.003+0.0005(输出显存)+0.0005(输入显存)) × 1.2 ≈ 0.178GB ≈ 0.18GB(FP16);

  1. GPU 算力(决定推理速度)
    推理算力需求 = Transformer 推理 FLOPs(浮点运算次数),核心公式为:推理 FLOPs = 2 × 参数总数 × max_seq_len(Transformer 推理核心经验公式,2 倍对应前向传播的注意力 + 前馈网络计算)。

算力数值:2×77,600,000×512 = 79,411,200,000 FLOPs = 79.41 TFLOPs(FP16)。
GPU 选型 & 耗时(与算力对应):
推理耗时(秒)= 推理 FLOPs ÷ (GPU 峰值算力 × 算力利用率)
举例:
入门级 GPU(NVIDIA T4,FP16 峰值 65 TFLOPs,利用率 70%):
耗时 = 79.41 ÷ (65×0.7) ≈1.74 秒;
国产 GPU(平头哥含光 800,FP16 峰值 128 TFLOPs,利用率 70%):
耗时 = 79.41 ÷ (128×0.7) ≈79.41 ÷ 89.6 ≈0.886 秒。

  1. 主机内存(CPU 内存)
    主机内存用于:加载模型配置、tokenizer 预处理 / 后处理(如巡检文本分词)、临时数据存储。
    公式:主机内存需求 = 模型参数显存 × 3(预留数据处理空间)。
    计算:0.145×3≈0.435GB,实际部署预留余大概≈1GB 即可。

  2. 存储(磁盘 / SSD)
    存储用于保存:模型权重文件、配置文件(json)、词表文件(txt)等。
    公式:存储需求 = 模型参数文件大小 + 配置 / 词表文件大小(≈100MB)。
    计算:
    1、模型参数文件(FP16):77.6M(模型参数大小)×2(FP16)=155.2MB
    2、辅助文件大小:词表文件+配置文件+适配文件+校准 / 示例文件+余量预留≈100MB
    总存储:155.2MB + 100MB≈255MB≈0.25GB。

二、并行执行100计算量时的资源需求计算

核心逻辑是 “模型参数显存共享,运行时 / 输入输出显存按并行数线性增加”,在加深并行是需要的额外开销(5%-10%)。

  1. GPU 显存(单卡并行)
    关键:模型参数显存只需加载 1 次(共享),运行时 / 输入输出显存每条请求独立。
    公式:总显存 = (模型参数显存 + 运行时显存 ×100 + 输入输出显存 ×100) × 1.1(10% 并行开销)。
    计算(FP16):(0.145 + 0.003×100 + 0.0005×100) ×1.1 = (0.145+0.3+0.05)×1.1≈0.545GB≈0.55GB
    若用多卡并行(如 2 卡,每卡 50 条):每卡显存 = (0.145 + 0.003×50 + 0.0005×50)×1.1≈0.35GB,2 卡总显存≈0.7GB。

  2. GPU 算力
    公式:总推理 FLOPs = 单次FLOPs × 并行数,推理耗时≈单次耗时 × 1.1(并行开销)。
    计算:
    总算力:79.41 TFLOPs ×100=7941 TFLOPs=7.94 PFLOPs
    耗时(NVIDIA T4):1.74×1.1≈1.91 秒(吞吐量≈100/1.91≈52 条 / 秒),一般为了体现推理的实时性,耗时要小于1秒

  3. 主机内存
    需支撑 100 条请求的预处理 / 后处理
    公式:主机内存 = 单条主机内存 × 并行数 ×1.2(批量开销)。
    计算:1GB×100×1.2=120GB
    若分批次处理(如每批 20 条),可优化至≈20GB。

  4. 存储
    存储需求与并行数无关(模型文件只需保存 1 份),仍≈0.25GB(FP16)。

什么是向量库,向量库与大模型的关系?

向量数据库是专门用于存储、索引、检索高维向量的数据库,其核心是提供通过语义相似性去匹配内容。传统的数据库是用来存储结构化数据,并通过关键词去做精准或模糊匹配,这样的搜索并不具备对搜索内容从语义以及特征的匹配,例如搜索“生孩子可以领哪些补贴”,那么答案中必须有“生孩子可以领哪些补贴”这样的话在才会匹配,而向量数据库是聚焦存储大模型处理的非结构化数据时生成的高维数值序列即向量,这类向量通过数值维度的特征分布与表征数据的语义或特征,可以搜索例如 “占道经营” 时匹配“违规摆摊” 等向量相似度极高的内容,而不仅限于关键字匹配。其中最典型的落地场景是 RAG(检索增强生成)工程:
1、其首先将知识库中的文档切分为片段,通过与大模型配套的编码器嵌入模型转为向量,存入向量库并构建专用索引
2、接着当用户提出问题时,先将问题文本通过同一嵌入模型转为向量
3、然后通过向量数据库基于 “语义相似度” 快速检索出与问题向量最匹配的知识库片段向量,并还原为对应的文本片段
4、最后大模型结合检索到的知识库片段(增量知识)与自身的参数记忆(固有知识),生成贴合用户诉求且信息准确的回答文本
这类检索生成的回答,是大模型融合语义特征匹配的增量知识与自身训练固化的知识所得,而非传统数据库通过关键词匹配得到的固化答案,或仅通过大模型自主生成的过时知识或幻觉答案。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容