7.3.2 Transformer 架构回顾与深入
如果你学过注意力机制,可能已经知道 Q / K / V 这些名词。
但真正到了大模型阶段,很多人还是会卡住:
- 为什么一个 block 里要先注意力、再前馈网络?
- 为什么残差和 LayerNorm 总是反复出现?
- 为什么同样是 Transformer,GPT 和 BERT 最后能走出不同路线?
这节课的目标不是再背一遍结构图,而是把一个 Transformer block 真的拆开,让你能顺着数据流把它讲清楚。
学习目标
- 理解 Transformer block 内部每个模块分别负责什么
- 理解 token embedding、位置信息、自注意力、FFN 是怎样串联的
- 通过一个可运行的最小 block 示例建立“数据是怎么流动的”直觉
- 理解为什么残差连接和归一化对深层网络很重要
一、为什么 Transformer 会成为大模型的底座?
它解决的是“序列里谁该看谁”的问题
语言天然是序列。 当模型处理一句话时,它需要知道:
- 当前词和前面哪些词有关
- 哪些位置更重要
- 长距离依赖该怎么保留
RNN 的思路是顺序读, CNN 的思路是局部卷积, Transformer 的思路则是:
让每个位置都主动去“看”其他位置,并为它们分配权重。
这就是自注意力的核心。
Transformer 真正强的地方不只是注意力
很多人会把 Transformer 简化成:
- 有 attention 的网络
但真正让它适合大规模训练的,其实是一整套配合:
- token embedding
- 位置表示
- 多头自注意力
- 残差连接
- LayerNorm
- 前馈网络
- 可堆叠的 block 结构
这套组合让它既能建模序列关系,又能做深、做大、做并行。
一个类比:每层 block 都像一次“讨论 + 整理”
你可以把一个 Transformer block 想成开会:
- 自注意力像“每个 token 去听别的 token 在说什么”
- 前馈网络像“每个 token 在吸收完上下文后,再单独做一轮内部加工”
- 残差连接像“保留原始发言,不要被新一轮加工完全覆盖”
一个 block 处理一轮, 多层 block 叠起来,就像一群人反复讨论和整理信息。
二、一个 Transformer block 里到底有什么?
输入先变成向量
模型看到的不是文字本身,而是 token id。 这些 token id 会先查 embedding 表,变成向量。
例如:
我->[0.2, -0.1, 0.8, ...]喜欢->[0.7, 0.3, -0.2, ...]
这一步做的是:
把离散符号变成连续空间里的表示。
然后补上位置信息
注意力本身只关心“集合里的关系”, 它并不知道 token 原本处在第几个位置。
所以我们必须告诉模型:
- 第 1 个 token
- 第 2 个 token
- 第 3 个 token
这些位置信息可以通过:
- 正弦位置编码
- 可学习位置向量
- RoPE 等相对位置方法
注入进去。
自注意力负责“跨 token 交流”
自注意力里每个 token 都会生成三份表示:
- Query:我想找什么
- Key:我能提供什么
- Value:如果你关注我,你最终拿到什么
然后每个 token 会做两步:
- 用自己的
Query和其他 token 的Key算相似度 - 用这些相似度去加权别人的
Value
得到的结果就是:
- “结合了上下文后的新表示”
前馈网络负责“单 token 深加工”
很多新人学 Transformer 时,会把注意力看成唯一核心。 但实际上,FFN 也非常重要。
它的特点是:
- 每个 token 单独经过一小段 MLP
- 不做跨 token 交流
- 但会增强非线性表达能力
可以把它理解成:
注意力负责交换信息,FFN 负责消化信息。
残差和归一化为什么总出现?
因为深层网络很容易训练不稳。 残差连接和 LayerNorm 的作用,可以先粗略记成:
- 残差:保留旧信息,让新信息是“增量更新”
- LayerNorm:把每层输出拉回更稳定的数值范围
如果没有它们, 深层 Transformer 很容易训练困难。

这张图建议按一层 block 的数据流读:token 表示先通过 Self-Attention 和上下文交流,再经 Residual/LayerNorm 稳住信息,随后进入 FFN 做逐 token 深加工。Transformer 不是只有 attention,而是“交流、保留、稳定、加工”的组合。
三、先跑一个真正的最小 Transformer block
下面这段代码会用纯 Python 做一件事:
- 输入三个 token 向量
- 计算一个单头自注意力
- 做残差连接
- 再经过一个小前馈网络
它不是完整工业实现,但每一步都对应真实 block 的核心结构。
from math import exp, sqrt
tokens = [
[1.0, 0.0, 1.0],
[0.0, 1.0, 1.0],
[1.0, 1.0, 0.0],
]
W_q = [
[1.0, 0.0],
[0.5, 1.0],
[0.0, 1.0],
]
W_k = [
[1.0, 0.5],
[0.0, 1.0],
[1.0, 0.0],
]
W_v = [
[1.0, 0.0, 0.5],
[0.0, 1.0, 0.5],
]
W1 = [
[1.0, -0.5],
[0.5, 1.0],
[1.0, 0.5],
]
W2 = [
[0.5, 1.0, 0.0],
[1.0, 0.0, 0.5],
]
def matmul_vec(vec, matrix):
return [
sum(vec[i] * matrix[i][j] for i in range(len(vec)))
for j in range(len(matrix[0]))
]
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def softmax(values):
m = max(values)
exps = [exp(v - m) for v in values]
total = sum(exps)
return [x / total for x in exps]
def add(a, b):
return [x + y for x, y in zip(a, b)]
def relu(vec):
return [max(0.0, x) for x in vec]
Q = [matmul_vec(token, W_q) for token in tokens]
K = [matmul_vec(token, W_k) for token in tokens]
V_in = [[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]
V = [matmul_vec(v, W_v) for v in V_in]
scale = sqrt(len(Q[0]))
scores = []
for i, q in enumerate(Q):
row = []
for j, k in enumerate(K):
row.append(dot(q, k) / scale if j <= i else -10**9)
scores.append(row)
weights = [softmax(row) for row in scores]
contexts = []
for row in weights:
context = [0.0, 0.0, 0.0]
for w, v in zip(row, V):
context = [c + w * x for c, x in zip(context, v)]
contexts.append(context)
after_attention = [add(token, context) for token, context in zip(tokens, contexts)]
ffn_hidden = [relu(matmul_vec(vec, W1)) for vec in after_attention]
ffn_output = [matmul_vec(vec, W2) for vec in ffn_hidden]
block_output = [add(x, y) for x, y in zip(after_attention, ffn_output)]
print("attention weights:")
for row in weights:
print([round(x, 3) for x in row])
print("\nblock output:")
for row in block_output:
print([round(x, 3) for x in row])
预期输出:
attention weights:
[1.0, 0.0, 0.0]
[0.413, 0.587, 0.0]
[0.456, 0.225, 0.32]
block output:
[3.75, 3.5, 1.5]
[3.897, 4.294, 2.566]
[4.366, 4.752, 1.153]

读这段代码时,先盯住四个位置
最关键的地方只有四处:
Q / K / V的生成scores的计算softmax后的加权求和- 残差 + FFN
如果这四处看懂了, 你对 Transformer block 的理解就已经越过“只会背图”的阶段了。
为什么这里要加 causal mask?
你会看到这句:
row.append(dot(q, k) / scale if j <= i else -10**9)
它表示:
- 当前 token 只能看自己和前面的 token
- 不能偷看未来
这正是 GPT 这类 decoder-only 模型训练时的关键约束。
如果你把 j <= i 去掉,
它就更像 encoder 里的双向注意力。
为什么注意力后面还要再过 FFN?
因为注意力只是在“汇总上下文”。 它告诉当前 token:
- 我该关注谁
但它不擅长做充分的非线性变换。 FFN 的作用就是:
- 把上下文融合后的表示再加工一轮
所以二者分工不同,缺一不可。
四、把 block 放回整张结构图里
多层堆叠意味着逐层抽象
第一层注意力看到的可能更多是:
- 词法关系
- 邻近模式
更高层可能逐渐形成:
- 句法关系
- 语义角色
- 长距离依赖
- 任务相关特征
这也是为什么 Transformer 不只是“一层 attention”, 而是很多层 block 叠起来。
Encoder 和 Decoder 的差别主要在 mask 和交互方式
如果只看 block,本质上它们很像。 差别主要在:
- encoder:通常是双向自注意力
- decoder:通常是因果 mask
- encoder-decoder:decoder 里还会多一层 cross-attention
所以很多架构差异,最后都能追溯到:
- 谁能看谁
GPT 为什么只保留 decoder?
因为生成任务最核心的结构约束是:
- 只能根据过去预测未来
decoder-only 更贴这个目标,结构也更直接。 这就是后来 GPT 系列一路做大的原因之一。
五、工程上最容易忽略的点
注意力不是免费午餐
每个 token 都要和其他 token 比较, 长度一长,成本会迅速上升。
这也是后面为什么会出现:
- 高效注意力
- KV cache
- GQA / MQA
- FlashAttention
这些改造。
block 结构看起来重复,但训练时并不轻松
当层数和 hidden size 提高后,你很快就会碰到:
- 显存压力
- 梯度稳定性
- 吞吐与延迟权衡
所以 Transformer 真正能成为大模型底座,不只是因为“结构优雅”, 也因为大量工程细节逐步成熟了。
看懂 block,后面很多章节都会轻松很多
后面你学:
- 架构变体
- 高效注意力
- 预训练方法
- 微调
本质上都在围绕这个 block 做改造或利用。
六、常见误区
误区一:Transformer = 注意力
不完整。 Transformer 是一套 block 设计,不是一个孤立公式。
误区二:FFN 只是配角
错。 它承担的是非常重要的非线性特征变换。
误区三:只要知道 QKV 就算理解了 Transformer
真正理解还包括:
- 残差为什么重要
- mask 为什么决定行为
- 多层堆叠为什么能形成抽象
小结
这节最重要的不是再记一遍结构图, 而是把一个 Transformer block 的数据流串起来:
token 向量先通过注意力和上下文交流,再经过前馈网络深加工,并依靠残差和归一化在多层堆叠中保持训练稳定。
只要这一条链在脑子里顺了, 后面很多“大模型看起来很复杂”的结构,其实都只是围绕这个 block 在做变化。
练习
- 把示例里的
j <= i改成始终允许,观察注意力权重会怎么变。 - 试着去掉残差连接,看看
block_output和原始输入的关系还稳不稳。 - 用自己的话解释:为什么说注意力负责交流信息,FFN 负责消化信息?
- 想一想:如果要把这个 block 叠 48 层,你最担心的工程问题会是什么?