前言
过去半年在 Agent 方向投入了大量精力,从 Agent 记忆架构设计到 Skill 工程化落地,积累了不少实践经验。但回头审视,发现自己一直停留在应用层------知道怎么调、怎么排、怎么蒸馏,但对底层为什么是这样缺乏系统理解。
于是决定给自己开一门"补课",从 Transformer 架构出发,系统学习大模型底层知识。不追求面面俱到,但求每个概念都能讲清楚"为什么"。
核心概念
1.1 CNN vs RNN vs Transformer:三种处理数据的方式
在Transformer出现之前,处理序列数据(如文本)的主流方法是RNN(循环神经网络) 。而更早之前,处理图像数据的主流方法是CNN(卷积神经网络) 。三者代表了不同时代、不同场景下处理数据的典型思路。
CNN vs RNN vs Transformer:核心区别一览表
| 维度 | CNN(卷积神经网络) | RNN(循环神经网络) | Transformer |
|---|---|---|---|
| 核心操作 | 卷积核在空间上滑动 | 隐藏状态在时间步上传递 | 注意力机制全局关联 |
| 擅长捕捉 | 局部空间模式(边缘→纹理→部件→整体) | 时间序列依赖(前文→后文) | 全局依赖关系(任意两个位置直接关联) |
| 参数共享 | 同一个卷积核扫遍整个输入 | 同一组权重在每个时间步复用 | Q/K/V投影矩阵在序列上复用 |
| 并行性 | ✅ 完全并行(每个位置独立卷积) | ❌ 必须串行(等前一个时间步算完) | ✅ 完全并行(所有位置同时计算) |
| 感受野 | 局部→通过层层堆叠扩大 | 理论上无限(实际会遗忘) | 全局(单层即可看到所有位置) |
| 典型输入 | 图像、音频频谱图、网格数据 | 文本、语音、时间序列 | 文本(当前主流)、图像(ViT)、多模态 |
CNN 的工作方式
makefile
输入: 一张图片(像素矩阵)
│
▼
┌─────────────────┐
│ 卷积层1 (3×3核) │ ← 提取边缘、线条
│ ReLU激活 │
│ MaxPooling │ ← 下采样
└─────────────────┘
│
▼
┌─────────────────┐
│ 卷积层2 (3×3核) │ ← 提取纹理、图案
│ ReLU + Pooling │
└─────────────────┘
│
▼
┌─────────────────┐
│ 全连接层 │ ← 综合判断:猫/狗/车
└─────────────────┘
│
▼
输出: 分类结果
CNN像一个拿放大镜的鉴定师------从局部特征开始看,逐层组合成整体判断。
CNN 的主场:
- 图像分类:猫狗识别、医学影像筛查 --- 卷积核逐层提取"边缘→纹理→器官轮廓"
- 目标检测:自动驾驶识别行人/车辆 --- YOLO、Faster R-CNN
- 图像分割:把每个像素分类(如肿瘤区域勾画)--- U-Net
- 人脸识别:人脸特征提取比对 --- FaceID
- OCR:文字检测定位 --- 识别图片中文字位置
RNN的工作方式
ini
时间步 t=1: 输入"今" → 隐藏状态 h1 → 输出
时间步 t=2: 输入"天" + 隐藏状态h1 → 隐藏状态 h2 → 输出
时间步 t=3: 输入"天" + 隐藏状态h2 → 隐藏状态 h3 → 输出
时间步 t=4: 输入"气" + 隐藏状态h3 → 隐藏状态 h4 → 输出
RNN像一个流水线工人------每次只处理一个物品,把结果传给下一个工人。
优点 :能处理任意长度的序列
致命缺点:
- 无法并行:必须等前一个词处理完才能处理下一个
- 长距离依赖问题:处理第100个词时,第1个词的信息可能已经丢失
Transformer的工作方式
less
输入: ["今", "天", "天", "气"]
│
▼
┌───┬───┬───┬───┐
│ │ │ │ │ ← 所有词同时处理
│ │ │ │ │ ← Self-Attention:每个词"看到"所有其他词
│ │ │ │ │ ← FFN:独立处理每个词
└───┴───┴───┴───┘
│
▼
输出: ["今天", "天气"]
Transformer像一个会议主持人------把所有参会者同时叫到一起,每个人都能听到其他人的发言。
关键洞察:Self-Attention让每个词都能直接"看到"序列中的其他所有词,不再需要逐个传递信息。
为什么 CNN 和 RNN 在 NLP 中退场了
RNN 的死因 :必须串行计算,GPU 几千个核只能用一个 → 速度慢;长序列梯度消失 → 记不住远处的信息。Transformer 直接用 Self-Attention 替代,完全并行 + 全局视野,RNN 被降维打击。
CNN 在 NLP 中的退场 :文本没有"局部空间结构",卷积核的归纳偏置(局部性)在文本里不如 Attention 的全局视野有用。但在视觉领域 CNN 仍然很强,尤其是计算资源受限场景(移动端、边缘设备),因为 CNN 的参数效率和推理速度比 ViT(Vision Transformer)更优。
1.2 Transformer的完整架构

Transformer由两部分组成:Encoder(编码器) 和 Decoder(解码器) ,拆解一下图中的流程:
sql
输入序列
│
▼
┌─────────┐
│ Embedding │ ← 词嵌入(把词变成向量)
│ + Pos Enc │ ← 位置编码(告诉模型词的位置)
└─────────┘
│
▼
┌─────────────────────────────┐
│ Encoder Stack │
│ ┌─────────────────────────┐ │
│ │ Multi-Head Self-Attn │ │
│ │ + Add & LayerNorm │ │
│ │ + FFN (前馈网络) │ │
│ │ + Add & LayerNorm │ │
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ Multi-Head Self-Attn │ │
│ │ + Add & LayerNorm │ │
│ │ + FFN │ │
│ │ + Add & LayerNorm │ │
│ └─────────────────────────┘ │
│ ... (N层重复) │ │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ Decoder Stack │
│ ┌─────────────────────────┐ │
│ │ Masked MHSA (防偷看) │ │
│ │ + Add & LayerNorm │ │
│ │ + Cross-Attention │ │
│ │ (用Encoder输出作为K,V) │ │
│ │ + Add & LayerNorm │ │
│ │ + FFN │ │
│ │ + Add & LayerNorm │ │
│ └─────────────────────────┘ │
│ ... (N层重复) │ │
└─────────────────────────────┘
│
▼
线性层 + Softmax
│
▼
输出概率分布
1.3 六大核心组件详解
组件1:词嵌入(Embedding)
作用:把离散的token(整数)变成连续的向量,词表会把每个字映射为一个整数的token_id。
py
import torch
import torch.nn as nn
# 词嵌入层:vocab_size=30000,模型的词表大小, d_model=768,每个 token 被表示为一个 768 维的浮点数向量,贯穿后续所有的使用。
embed = nn.Embedding(30000, 768)
# 输入: [batch_size, seq_len] = [2, 5]
input_ids = torch.tensor([
[100, 200, 300, 400, 500],
[150, 250, 350, 450, 550]
])
# 输出: [batch_size, seq_len, d_model] = [2, 5, 768]
embeddings = embed(input_ids)
print(f"Embedding shape: {embeddings.shape}")
每个token变成一个768维的向量。
组件2:位置编码(Positional Encoding)
为什么需要? Transformer没有RNN的循环结构,不知道词的顺序。
想象一下:如果你把所有词的顺序打乱,Transformer对每个词的处理是独立的(在Self-Attention之前),所以输出会完全一样------因为它不知道哪个词排第几位。
方法:把位置编码向量加到词嵌入向量上:

其中 i 是词在序列中的位置。
组件3:Self-Attention(自注意力)
Self-Attention的核心思想:每个词同时关注序列中所有其他词,根据相关性加权聚合信息。
公式:

Q(Query) :我在找什么
K(Key) :我能提供什么
V(Value) :我实际包含什么
组件4:FFN(前馈网络)
每个Transformer层里,Self-Attention之后还有一个全连接前馈网络。
scss
FFN(x) = SwiGLU(xW_1 + b_1)W_2 + b_2
为什么需要FFN? 因为Self-Attention只处理了"词之间的关系",但没有做"词本身的非线性变换"。
类比:Self-Attention像是开会讨论 (词之间互相交流),FFN像是每个人回去独自思考(对收到的信息做非线性处理)。
具体结构:
- 先经过一个线性变换,维度从
d_model→4*d_model(扩大4倍) - 经过SwiGLU激活函数
- 再经过一个线性变换,维度从
4*d_model→d_model(恢复原尺寸)
参数量 :FFN部分通常是大模型中参数量最大的部分。
例如,一个4096维的模型(如LLaMA-2 70B):
- Self-Attention:4096×4096×4(Q/K/V+输出投影)= 约6700万参数
- FFN:4096×16384 + 16384×4096 = 约1.34亿参数
FFN的参数量是Self-Attention的约2倍!
Self-Attention 的本质局限
Self-Attention 只做线性加权(加权平均),不管怎么组合权重,本质上都是把已有信息混合在一起,不会产生新的信息、新的特征。FFN 就是来解决这个问题的------对每个词的向量做非线性变换,提炼出更深层的模式。
FFN 内部的详细过程
- 第一步:线性变换 ,
d_model→4*d_model(扩大4倍)。类比"回工位展开笔记本,把讨论内容拆解到多个角度" - 第二步:激活函数 (SwiGLU/GELU)。这一步是关键------没有它,线性层叠再多也等价于一个线性层。引入非线性,让模型能学到复杂的模式
- 第三步:线性变换 ,
4*d_model→d_model(压缩回来)。类比"写成一页总结,提炼出关键结论"
具体例子:"苹果"的语义转换
以"苹果"为例,Self-Attention 后"苹果"的向量已经融合了上下文(如前面有"吃了"),但此时向量里"吃了"和"苹果"的信息只是混在一起。FFN 要做的是从"吃了+苹果"的简单叠加,变成"吃了苹果"这个组合语义------一个全新的特征。
FFN 为什么扩大 4 倍
高维空间更容易线性可分。把 768 维映射到 3072 维,就像把揉成一团的纸展开------在更高维度的空间里,不同语义模式的边界更清晰。
FFN 存储知识的证据
有研究表明,FFN 的第一层相当于一个"键值记忆库",不同的神经元存储了不同的知识和模式。FFN 占了每层 2/3 的参数,模型的"知识"主要存在 FFN 的权重矩阵里。
没有 FFN 会怎样
如果去掉 FFN,只剩 Self-Attention 堆叠,模型只能做信息的重新分配和混合,无法学到非线性特征,表达能力大幅下降。
一句话总结
Self-Attention 让词之间交换信息("听"),FFN 让每个词把交换来的信息消化成新的理解("想"),缺一不可。
组件5:残差连接(Residual Connection)
核心思想:不是直接传递输出,而是"原始输入 + 处理结果"。
ini
output = LayerNorm(x + SubLayer(x))
类比:调节水温时不是"重新调一杯",而是在原基础上加减几度。
为什么需要?
- 缓解梯度消失 :深层网络中,梯度从最后一层传回第一层时会指数级缩小。残差连接提供了梯度高速公路------梯度可以直接从深层跳到浅层。
- 学习恒等映射:如果某个层不需要改变输入,残差连接可以让它学习"什么都不做"(输出为0)。
python
def residual_block(x, sub_layer):
"""残差连接的基本结构"""
sub_output = sub_layer(x) # 子层处理
return x + sub_output # 原始输入 + 处理结果
梯度反向传播的详细解释
对 x + SubLayer(x) 求导:∂输出/∂x = 1 + ∂SubLayer(x)/∂x
关键是那个 1------它不依赖任何权重,不会变小,不会消失。
反向传播时梯度有两条路径:
- 路径1:经过 SubLayer 的权重矩阵 → 可能被反复乘,指数级缩小(梯度消失)
- 路径2:直接加1,原样传过去 → 梯度不变,畅通无阻
没有谁来"决定"走哪条路,两条路同时存在,梯度是两条路的和。
对比没有残差连接的情况
普通网络 96 层:0.9 × 0.9 × ... × 0.9(96次)= 0.00003,梯度几乎为0
有残差连接:最坏情况 = 0 + 0 + ... + 1 = 1,仍然有梯度
谁决定梯度是否需要过某一层
没有机制来"决定"。
是训练过程中权重自动调整的结果:
- 如果某层有用 → 反向传播时梯度自然倾向于走该层 → 权重被强化
- 如果某层没用 → 权重趋近于0 → 梯度主要通过残差路径跳过 → 该层自动变成"透传"
这就是论文标题"Deep Residual Learning"的核心:学习的是"残差"(SubLayer(x)),不是完整变换。如果某层不需要变换,学一个全零输出即可。
一句话总结
没有任何机制来"决定"------残差连接通过 +1 的数学性质,无条件保证每层都能收到梯度。至于每层学到了什么、是否被"跳过",是训练过程中权重自动调整的结果。
组件6:Layer Normalization
作用:对每个样本的特征维度做归一化,使训练更稳定。
scss
LayerNorm(x) = γ · (x - μ) / √(σ² + ε) + β
其中:
μ是该样本所有特征的均值σ是该样本所有特征的标准差γ和β是可学习的缩放和平移参数ε是极小值,防止除以0
为什么不用BatchNorm?
BatchNorm是对批次维度 做归一化,但文本序列长度可变,不同样本的特征维度不同。LayerNorm对每个样本独立做归一化,不受批次大小和序列长度影响。
类比:BatchNorm像是"全班同学统一打分"(依赖批次),LayerNorm像是"每个人自己调整分数"(独立处理)。
为什么深层网络需要归一化
深层网络中,每一层的输出都会放大或缩小上一层的输出。假设每层平均放大 1.05 倍:
- 10层后:1.05^10 ≈ 1.63(还好)
- 50层后:1.05^50 ≈ 11.47(开始失控)
- 96层后:1.05^96 ≈ 125倍(GPT-3 的层数,完全爆炸)
反之如果每层平均缩小 0.95 倍:
- 96层后:0.95^96 ≈ 0.007(梯度几乎为0)
这就是为什么没有归一化的深层网络根本无法训练。
LayerNorm 具体在做什么(逐步拆解)
用一个具体例子说明。假设某个 token 的向量是 2.0, -1.0, 0.5, 3.0(简化为4维,实际768维):
第一步:算均值
μ = (2.0 + (-1.0) + 0.5 + 3.0) / 4 = 1.125
第二步:算方差
σ² = ((2.0-1.125)² + (-1.0-1.125)² + (0.5-1.125)² + (3.0-1.125)²) / 4 = 2.422
第三步:标准化
x_norm = (x - μ) / √(σ² + ε) = (x - 1.125) / 1.557
→ 0.563, -1.366, -0.401, 1.205
→ 均值变成0,方差变成1
第四步:缩放和平移
output = γ × x_norm + β
→ 假设 γ=2, β=0.5 → 1.626, -2.232, -0.302, 2.910
→ 恢复了表达能力
为什么要加 γ 和 β:如果只做标准化(第三步),所有 token 的向量都被压缩到相同的分布,模型丢失了"这个特征应该大、那个特征应该小"的信息。γ 和 β 是可学习参数,让模型自己决定每个维度"应该"是什么分布------需要大的维度 γ 调大,需要小的维度 γ 调小,需要偏移的维度 β 调正或调负。
为什么不用 BatchNorm
假设一个 batch 有 3 个句子:
- 句子A: 今天, 天气, 真, 好(4个token)
- 句子B: 我, 喜欢, 苹果(3个token)
- 句子C: 深度, 学习, 很, 有, 意思(5个token)
BatchNorm 的问题:
- 它沿 batch 方向归一化,但每个句子长度不同,无法对齐
- batch_size=1(推理时)没有统计意义
- 不同句子之间的统计量差异大,归一化不稳定
LayerNorm 的优势:
- 每个 token 独立归一化,不受 batch 中其他句子影响
- 不依赖 batch_size,训练和推理行为一致
- 对变长序列天然友好
| 对比维度 | BatchNorm | LayerNorm |
|---|---|---|
| 归一化方向 | 沿 batch 维度(跨样本) | 沿特征维度(单样本内) |
| 依赖 batch_size | 是,batch_size=1 时失效 | 否,每个样本独立 |
| 变长序列 | 不兼容(长度不一致) | 兼容(每个 token 独立处理) |
| 训练/推理差异 | 有(推理用移动平均统计量) | 无(行为一致) |
| 适用场景 | 图像(固定尺寸) | 文本(变长序列) |
1.4 一个完整的Transformer Layer
ini
输入 x
│
│── Self-Attention层
│ Q = xW_Q, K = xW_K, V = xW_V
│ Attention = softmax(QK^T/√d_k)V
│ output1 = LayerNorm(x + Attention) ← 残差连接
│
│── FFN层
│ hidden = SwiGLU(output1 × W_1)
│ output2 = LayerNorm(output1 + hidden) ← 残差连接
│
▼
输出 output2
关键设计模式 :每个子层(Self-Attention和FFN)都被包裹在"残差连接 + LayerNorm"的结构中。这就是所谓的Post-LayerNorm(Post-LN)结构。
注意 :原始Transformer用的是Post-LN,但GPT-2和LLaMA等后来的模型改成了Pre-LayerNorm(把LayerNorm放在残差连接之前)。Pre-LN能更好地稳定训练,是大模型的标准配置。
技术细节
2.1 完整的Transformer Block代码
py
import torch
import torch.nn as nn
import torch.nn.functional as F
class SwiGLU(nn.Module):
"""SwiGLU激活函数(常用于大模型,如LLaMA)"""
def __init__(self, dim_in, dim_out):
super().__init__()
self.w1 = nn.Linear(dim_in, dim_out)
self.w2 = nn.Linear(dim_in, dim_out)
self.w3 = nn.Linear(dim_out, dim_out)
def forward(self, x):
# SwiGLU(x) = SiLU(w1(x)) * w2(x)
return F.silu(self.w1(x)) * self.w2(x)
class TransformerBlock(nn.Module):
"""完整Transformer Block(Pre-LayerNorm版本)"""
def __init__(self, d_model=4096, n_heads=32, d_ff=16384, dropout=0.1):
super().__init__()
# 参数说明(以LLaMA-2 70B为例)
# d_model = 4096: 每个token的向量维度
# n_heads = 32: 注意力头数,每头维度=4096/32=128
# d_ff = 16384: FFN中间层维度(= d_model × 4)
# Self-Attention
self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True, dropout=dropout)
# Layer Normalization(Pre-LN:放在残差连接之前)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
# FFN(前馈网络)
self.ffn = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(), # 高斯误差线性单元(大模型常用)
nn.Linear(d_ff, d_model)
)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
"""前向传播"""
# === 第一步:Self-Attention ===
# Pre-LN:先归一化,再做注意力
attn_input = self.norm1(x)
attn_out, _ = self.attn(attn_input, attn_input, attn_input, attn_mask=mask)
# 残差连接:原始输入 + 注意力输出
x = x + self.dropout(attn_out)
# === 第二步:FFN ===
# Pre-LN:先归一化,再做FFN
ffn_input = self.norm2(x)
ffn_out = self.ffn(ffn_input)
# 残差连接:原始输入 + FFN输出
x = x + self.dropout(ffn_out)
return x
# 使用示例
model = TransformerBlock(d_model=768, n_heads=12, d_ff=3072)
# 模拟输入:batch_size=2, seq_len=10, d_model=768
x = torch.randn(2, 10, 768)
output = model(x)
print(f"输入 shape: {x.shape}")
print(f"输出 shape: {output.shape}") # 和输入一样!
print(f"参数量: {sum(p.numel() for p in model.parameters()):,}")
每个Transformer Block的参数量:
| 组件 | 参数量(LLaMA-2 70B为例) | 说明 |
|---|---|---|
| Q/K/V投影 | 4096×4096 × 3 = 5033万 | 每个头128维,32个头 |
| 输出投影 | 4096×4096 = 1678万 | 拼接所有头的输出 |
| FFN内部 | 4096×16384 × 2 = 1.34亿 | 两个线性层 |
| LayerNorm | 4096 × 2 = 8192 | 两个归一化层 |
| 总计 | 约2.01亿 | 每个Block |
LLaMA-2 70B有80个Transformer Block,加上词嵌入和输出层,总共约700亿参数。
2.2 计算复杂度分析
| 架构 | 时间复杂度 | 空间复杂度 | 并行性 |
|---|---|---|---|
| CNN | O(n·k·d) | O(k·d) | ✅ 完全并行 |
| RNN | O(n·d²) | O(d) | ❌ 串行 |
| Self-Attention | O(n²·d) | O(n²) | ✅ 完全并行 |
关键洞察:
- CNN最轻量:O(n·k·d),卷积核大小k固定,效率最高,但对文本缺乏全局视野
- RNN是串行的:必须按顺序处理n个token,每个token需要O(d²)计算,总时间O(n·d²)
- Self-Attention可以完全并行:所有token之间的相关性可以同时计算
- 瓶颈是n²:序列长度的平方决定了计算量和显存占用
为什么这对大模型训练至关重要?
因为GPU的优势就是并行计算。RNN必须串行,GPU只能用一个核心,和CPU差不多快。Self-Attention可以完全并行,GPU的几千个核心可以同时工作,计算速度提升几十到几百倍。
但是 ,Self-Attention也有代价:显存占用是O(n²)。一个4096长度的序列需要存储 个注意力分数,占用约64MB显存(FP16)。对于128K上下文,需要 个分数,占用约61.5GB显存!
实际案例
GPT-3的架构细节
GPT-3基于Transformer Decoder-only架构,具体配置:
| 参数 | 值 | 含义 |
|---|---|---|
| 层数 | 96层 | 递进分析,每层都在之前的基础上进行学习和总结 |
| 每层头数 | 96头 | 并行分析,每一头代表一个维度:关系、类别... |
| 每头维度 | 128 | |
| 模型维度 | 12288(96×128) | |
| 总参数量 | 1750亿 | |
| 词表大小 | 50257(Byte Pair Encoding) | |
| 上下文窗口 | 2048 tokens |
GPT-3的创新不在于架构(和GPT-2几乎一样),而在于规模 ------1750亿参数,3000亿tokens的训练数据。 但 ------GPT系列(GPT-3、GPT-4等)只用了Decoder部分!为什么?
为什么GPT是Decoder-only
因为GPT的目标是自回归生成(看到前面的词,预测下一个词)。Decoder的Masked Self-Attention天然适合这种"只能看前面"的任务。
而BERT(双向语言模型)只用了Encoder部分,因为它需要同时利用前后文信息来理解词义。
BERT架构现在最大的活路就是做Embedding模型。 因为双向注意力天然适合把整句编码成一个向量,而生成任务已经被GPT路线统治了。各大RAG框架里用的embedding模型,底层基本都是Encoder-only架构。
LLaMA-2的架构改进
LLaMA-2基于GPT-3的架构,做了以下改进:
- SwiGLU激活:用SwiGLU替代GPT-3的ReLU,提升了非线性表达能力
- RMSNorm替代LayerNorm:更简单高效的归一化方法
- RoPE旋转位置编码:替代了原始Transformer的正弦位置编码
- GQA(分组查询注意力) :将K和V头数从32减少到4,显存占用大幅降低
常见误区
误区1:Transformer只能做生成任务
事实:Transformer的Encoder部分(如BERT)非常适合文本理解任务。GPT系列只做生成是因为它们只用了Decoder部分。
误区2:Self-Attention的计算量很小
事实 :Self-Attention的时间复杂度是O(n²·d)。当序列长度n很大时(如128K),计算量是 128000^2 * d,非常恐怖。这也是为什么长上下文模型需要大量显存。
误区3:RNN/CNN已经完全被淘汰了
事实:RNN在处理非常长的序列时仍然有优势(O(n)显存 vs O(n²)显存);CNN在视觉领域和边缘计算场景中仍然很强(参数效率、推理速度优于ViT)。Mamba等新的State Space Model(SSM)就是受RNN启发,结合了Transformer和RNN的优点。
误区4:LayerNorm和BatchNorm没区别
事实:BatchNorm对批次维度归一化,LayerNorm对特征维度归一化。在NLP中,因为序列长度可变,BatchNorm会导致归一化不稳定。LayerNorm对每个样本独立处理,更稳定。
最后
感谢你能看到这里,本文简单介绍了【Transformer的架构】,希望对你有用,更多Agent、前端、node、性能相关的知识文章,更多 Agent、前端、Node、性能相关的技术文章和实践总结,可以查看我的代码花园: