组件拆解

预计学习时间:50分钟

大语言模型的核心由多个关键组件构成,包括Embedding层、位置编码、自注意力机制、前馈网络以及规范化层等。理解这些组件的工作原理和功能对于掌握大语言模型的内部结构至关重要。

大语言模型的核心组件

Transformer架构是现代大语言模型的基础,包含以下核心组件:

  1. 词嵌入层 (Embedding Layer)
  2. 位置编码 (Positional Encoding)
  3. 多头自注意力机制 (Multi-head Self-attention)
  4. 前馈神经网络 (Feed-forward Neural Network)
  5. 层归一化 (Layer Normalization)
  6. 残差连接 (Residual Connections)
  7. 激活函数 (Activation Functions)

这些组件通过精心设计的方式组合在一起,形成了强大的语言模型架构。

标准Transformer块结构

现代大语言模型的基本结构如下图所示:

graph TD
    Input[输入词嵌入] --> PE[位置编码]
    PE --> Add1[残差连接+]
    MHA[多头自注意力] --> MHAOut[注意力输出]
    Add1 --> LN1[层归一化]
    LN1 --> MHA
    MHAOut --> Add2[残差连接+]
    Add2 --> LN2[层归一化]
    LN2 --> FFN[前馈神经网络]
    FFN --> Add3[残差连接+]
    Add3 --> LN3[层归一化]
    LN3 --> Output[输出]

Transformer编码器与解码器对比

组件编码器解码器
注意力类型双向自注意力单向自注意力 + 交叉注意力
注意力掩码无掩码(全局可见)因果掩码(只看过去)
典型层数BERT: 12/24层GPT系列: 12-96层+
残差连接
层归一化位置注意力前/后根据架构变化

详细组件分析

让我们深入了解每个核心组件:

1. 词嵌入层

词嵌入是将离散的词元(token)转换为连续向量表示的关键一步,决定了模型对语言的初始理解能力。

class EmbeddingLayer(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        
    def forward(self, input_ids):
        # 将输入ID转换为密集向量
        return self.embedding(input_ids)  # shape: [batch_size, seq_len, embedding_dim]

关键特性

  • 典型维度:768 (BERT-base)、1024 (BERT-large)、4096 (GPT-3)等
  • 通常与输出层权重共享,以减少参数量
  • 嵌入大小与模型维度直接相关,影响模型表达能力
  • 与分词器紧密结合,需要适配词表大小

2. 位置编码

位置编码用于为Transformer注入词序信息,常见实现包括:

class SinusoidalPositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super().__init__()
        # 创建位置编码矩阵
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1)
        div_term = torch.exp(
            torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)
        )
        
        # 偶数位置使用sin,奇数位置使用cos
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        
        self.register_buffer('pe', pe.unsqueeze(0))
        
    def forward(self, x):
        # 将位置编码添加到输入嵌入
        return x + self.pe[:, :x.size(1)]

不同模型中的位置编码变种:

  • 固定正弦位置编码:原始Transformer中使用,具有长度外推能力
  • 可学习位置嵌入:如BERT,允许模型学习最优位置表示
  • 相对位置编码:如T5,只考虑词之间的相对距离
  • 旋转位置嵌入:如RoPE,用于LLaMA等模型

有关位置编码的详细实现,请参考位置编码章节。

3. 多头自注意力机制

自注意力机制是Transformer的核心创新,通过计算序列内部的相关性,捕捉长距离依赖关系,使模型能够有效理解上下文信息。

注意力计算的基本原理是将查询(Q)与键(K)进行点积,并与值(V)加权组合:

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super().__init__()
        assert d_model % num_heads == 0, "d_model must be divisible by num_heads"
        
        self.d_model = d_model
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        
        # 线性投影层
        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)
        self.W_o = nn.Linear(d_model, d_model)
        
    def split_heads(self, x):
        # 分割最后一维为num_heads个子空间
        batch_size, seq_len = x.size(0), x.size(1)
        return x.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        
    def forward(self, query, key, value, mask=None):
        batch_size = query.size(0)
        
        # 线性投影并分割为多头
        q = self.split_heads(self.W_q(query))  # [batch, heads, seq_len, d_k]
        k = self.split_heads(self.W_k(key))    # [batch, heads, seq_len, d_k]
        v = self.split_heads(self.W_v(value))  # [batch, heads, seq_len, d_k]
        
        # 点积注意力
        scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
        
        # 掩码处理(可选)
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)
            
        # softmax归一化获取注意力权重
        attn_weights = F.softmax(scores, dim=-1)
        
        # 加权汇总值向量
        context = torch.matmul(attn_weights, v)  # [batch, heads, seq_len, d_k]
        
        # 重组多头输出
        context = context.transpose(1, 2).contiguous().view(
            batch_size, -1, self.d_model
        )
        
        # 最终输出投影
        output = self.W_o(context)
        return output

关键特性

  • 多头设计:将注意力分散到不同子空间,捕捉不同类型的信息
  • 并行计算:所有位置的注意力可并行计算,加速训练
  • 注意力分散度:头数影响注意力分布,GPT-4可能有数百个头
  • 计算复杂度:标准自注意力计算复杂度为O(n²),是长序列处理的瓶颈

4. 前馈神经网络

每个Transformer块中的前馈网络提供非线性转换能力:

class PositionwiseFeedForward(nn.Module):
    def __init__(self, d_model, d_ff, dropout=0.1):
        super().__init__()
        # 两个线性变换,中间有非线性激活函数
        self.linear1 = nn.Linear(d_model, d_ff)
        self.linear2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, x):
        # 第一层线性变换后激活
        x = F.gelu(self.linear1(x))  # 或使用ReLU/SwiGLU
        # 应用dropout
        x = self.dropout(x)
        # 第二层线性变换
        return self.linear2(x)

特点

  • 通常内部维度扩大4倍(如d_model=768, d_ff=3072)
  • 现代模型常用GELU、SwiGLU等激活函数替代原始ReLU
  • 是模型参数量的主要贡献者之一
  • 作为逐位置处理的神经网络,提升模型非线性表达能力

有关激活函数的详细说明,请参考激活函数章节。

5. 层归一化

层归一化对于稳定训练过程、加速收敛和改善模型性能至关重要,是Transformer架构不可或缺的组件。

class LayerNorm(nn.Module):
    def __init__(self, features, eps=1e-6):
        super().__init__()
        # 每个特征维度的缩放和偏移参数
        self.gamma = nn.Parameter(torch.ones(features))
        self.beta = nn.Parameter(torch.zeros(features))
        self.eps = eps
        
    def forward(self, x):
        # 计算均值和方差(在特征维度上)
        mean = x.mean(-1, keepdim=True)
        std = x.std(-1, keepdim=True)
        
        # 归一化
        normalized = (x - mean) / (std + self.eps)
        
        # 缩放和偏移
        return self.gamma * normalized + self.beta

层归一化有多种实现变体:

  • Pre-LN:将归一化放在每个子块之前,提高训练稳定性
  • Post-LN:将归一化放在每个子块之后,是原始Transformer设计
  • RMSNorm:只使用均方根归一化,计算更简单
  • LayerScale:在残差连接中添加可学习的对角缩放

有关归一化的详细信息,请参考Add & Norm章节。

6. 残差连接

残差连接用于缓解深度网络的梯度传播问题:

# 残差连接通常集成在Transformer块中
def apply_residual(x, sublayer):
    return x + sublayer(x)

关键特性

  • 帮助训练非常深的模型(GPT-3有96层,Llama 2有80层)
  • 促进信息无损传播
  • 与层归一化协同工作,保持训练稳定性
  • 不同模型中的残差连接可能有轻微变化

大语言模型完整架构示例

以下是简化的GPT风格大语言模型架构示例:

class GPTModel(nn.Module):
    def __init__(self, vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_len, dropout=0.1):
        super().__init__()
        
        # 词嵌入
        self.token_embedding = nn.Embedding(vocab_size, d_model)
        self.position_embedding = nn.Embedding(max_seq_len, d_model)
        self.dropout = nn.Dropout(dropout)
        
        # Transformer块
        self.transformer_blocks = nn.ModuleList(
            [TransformerBlock(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)]
        )
        
        # 输出层
        self.layer_norm = nn.LayerNorm(d_model)
        self.output = nn.Linear(d_model, vocab_size)
        
        # 权重初始化
        self.apply(self._init_weights)
        
    def _init_weights(self, module):
        if isinstance(module, nn.Linear):
            # 使用截断正态分布初始化
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
            if module.bias is not None:
                torch.nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Embedding):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
        elif isinstance(module, nn.LayerNorm):
            torch.nn.init.ones_(module.weight)
            torch.nn.init.zeros_(module.bias)
            
    def forward(self, input_ids):
        batch_size, seq_len = input_ids.size()
        
        # 创建位置ID
        position_ids = torch.arange(0, seq_len, dtype=torch.long, device=input_ids.device).unsqueeze(0)
        
        # 计算嵌入
        token_embeds = self.token_embedding(input_ids)
        position_embeds = self.position_embedding(position_ids)
        
        # 组合嵌入
        x = token_embeds + position_embeds
        x = self.dropout(x)
        
        # 通过Transformer块
        for block in self.transformer_blocks:
            x = block(x)
            
        # 最终输出
        x = self.layer_norm(x)
        logits = self.output(x)
        
        return logits


class TransformerBlock(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
        super().__init__()
        
        # 自注意力层
        self.attn = MultiHeadAttention(d_model, num_heads)
        self.norm1 = nn.LayerNorm(d_model)
        
        # 前馈网络
        self.ff = PositionwiseFeedForward(d_model, d_ff, dropout)
        self.norm2 = nn.LayerNorm(d_model)
        
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, x, mask=None):
        # 自注意力子层 (Pre-LN风格)
        attn_input = self.norm1(x)
        attn_output = self.attn(attn_input, attn_input, attn_input, mask)
        x = x + self.dropout(attn_output)
        
        # 前馈网络子层 (Pre-LN风格)
        ff_input = self.norm2(x)
        ff_output = self.ff(ff_input)
        x = x + self.dropout(ff_output)
        
        return x

组件配置对比

不同大语言模型的组件配置差异巨大:

模型参数量层数隐藏维度头数FFN内部维度上下文长度
BERT-base110M12768123072512
BERT-large340M241024164096512
GPT-21.5B4816002564001024
GPT-3175B961228896491522048
LLaMA-7B7B32409632110082048
LLaMA-65B65B80819264220162048
Falcon-40B40B60819264273922048
Claude>100B未公开未公开未公开未公开100K+

模型缩放规律

随着参数量增加,模型组件也会相应扩展:

  • 层数(depth):控制序列处理的复杂度,影响模型捕获长距离依赖的能力
  • 宽度(width):控制每层的表示能力,通常与头数和FFN维度成比例
  • 头数:影响模型捕捉不同类型模式的能力

模型缩放的一般经验法则:

  • 层数、隐藏维度和头数增长应保持一定比例
  • 参数量增长10倍,性能提升不是线性的
  • FFN内部维度通常是隐藏维度的4倍左右

组件优化与变体

现代大语言模型中的组件优化:

  1. 注意力优化

    • Flash Attention - 高性能注意力实现
    • 分组查询注意力(GQA) - 在LLaMA 2中使用
    • 多查询注意力(MQA) - 减少KV计算
  2. 激活函数优化

    • SwiGLU - 在PaLM中使用
    • GeGLU - 加入门控机制的GELU
    • Swish/SiLU - 平滑激活函数
  3. 归一化优化

    • RMSNorm - 更简单的归一化方法
    • DeepNorm - 超深网络的特殊归一化
  4. 位置编码

    • RoPE - 旋转位置编码
    • ALiBi - 注意力中的偏置

小结

大语言模型的组件是经过精心设计的复杂系统,每个部分都有其独特功能:

  • 嵌入层将离散符号转换为连续向量
  • 位置编码提供序列位置信息
  • 自注意力建立长距离依赖关系
  • 前馈网络增强非线性表达能力
  • 归一化层稳定训练过程
  • 残差连接促进梯度流动和信息传递

深入理解这些组件的工作原理和相互关系,对于掌握大语言模型的内部运作机制至关重要。

在下面的章节中,我们将深入探讨每个核心组件的细节实现和优化方法。