一文读懂 Transformer:从 Attention 到 LLM 的核心架构
一文读懂 Transformer:从 Attention 到 LLM 的核心架构
《大模型知识与部署》系列 · No.02 / 35
适合人群:AI 工程师、后端开发
阅读时间:约 25 分钟

写在前面
在上一篇《大模型时代全景图》里,我们梳理了从 2017 年 Transformer 论文到 2026 年大模型生态的完整演进。如果你把那篇文章读完,会发现一个事实:过去八年所有的大模型——GPT、Claude、Llama、Qwen、DeepSeek——本质上都是 Transformer 的变体。
这一篇我们就来彻底搞懂 Transformer。
但这不是一篇"复现论文"的文章。论文级的推导网上已经太多,多数读者读完仍然不知道这玩意儿在生产环境里到底怎么用。本文从工程师视角出发,目标是让你读完之后能回答这些问题:
- 为什么所有大模型推理框架都要专门优化 attention?
- KV Cache 到底缓存了什么?为什么能加速?
- 上下文从 4K 扩展到 1M 的工程难点在哪里?
- 为什么 Flash Attention、PagedAttention 都围着 attention 转?
- 为什么 Decoder-only 架构吃掉了一切?
如果你能回答以上每一个问题,那么后续推理优化、显存计算、长上下文等等一系列工程话题,你都会有一个稳固的"心智模型"作为参照——这也是为什么把 Transformer 放在系列第 2 篇。
我们开始。
一、为什么 Transformer 是大模型的「地基」
1.1 前 Transformer 时代:RNN 的三个死结
在 2017 年之前,处理"序列"的主流方案是 RNN/LSTM/GRU。这类网络有一个核心结构:当前时刻的输出,依赖上一时刻的隐状态。
h_t = f(h_{t-1}, x_t)
这个看似优雅的递推公式,给工程师埋下了三个炸弹:
炸弹 1:无法并行
序列必须按时间步顺序计算——要算 h_10,必须先算完 h_1, h_2, ..., h_9。这意味着:
- 训练时,单条样本内部完全串行,GPU 闲置严重
- 序列越长,训练越慢,且无法靠堆 GPU 解决
炸弹 2:长程依赖丢失
虽然 LSTM 引入门控机制缓解了梯度消失,但实际效果在序列长度超过 200 后就明显退化。让 RNN 理解一篇 8K Token 的文章?基本不可能。
炸弹 3:状态压缩瓶颈
整个序列的信息被压缩到一个固定维度的隐状态向量里——就像让你用 256 个数字记住一整本《三国演义》。
1.2 2017 年的转折点
2017 年 6 月,Google Brain 团队发表了《Attention is All You Need》。这篇论文做了一件极其大胆的事:把 RNN 整个扔掉。
新架构的核心思想可以用一句话概括:
让序列中任意两个位置直接"对话",不需要经过中间状态。
这个改变看似激进,但带来了三个工程红利:
- 完全并行:序列内所有位置同时计算,训练效率呈数量级提升
- 长程依赖直连:第 1 个 token 可以直接"看到"第 8000 个 token
- 架构高度统一:CV、NLP、语音、多模态最终都收敛到 Transformer
也正因为如此,Transformer 才有可能"做大"——175B 的 GPT-3、671B 的 DeepSeek、千亿级 MoE,本质上都建立在 attention 可大规模并行计算这个基础之上。
1.3 工程师为什么必须懂 Transformer
你可能会问:我又不训模型,懂这个干嘛?
答案是:Transformer 不只是训练框架的事,更是推理框架、显存管理、性能优化的根本依据。
下面这张表列出了"工程问题"和"Transformer 概念"的对应关系:
| 工程问题 | 背后的 Transformer 概念 |
|---|---|
| 为什么单卡跑不动 70B 模型? | 参数量 + KV Cache 显存占用 |
| 为什么上下文一长就 OOM? | Attention 是 O(n²) 复杂度 |
| 为什么 vLLM 比朴素推理快几十倍? | PagedAttention 优化 KV Cache 管理 |
| 为什么 Flash Attention 能省显存? | Attention 计算的 IO 瓶颈 |
| 为什么 RoPE 比绝对位置编码好? | 位置编码的外推能力 |
| 为什么 MoE 能省推理算力? | FFN 层占了模型大半参数 |
| 投机解码为什么有效? | Attention 的自回归特性 |
读完本文,你会理解上面每一行的逻辑。
二、核心原理:从 Self-Attention 到完整 Transformer
我们从最基本的 Self-Attention 开始,逐层往外推。
2.1 Self-Attention:让 Token 互相"对话"
问题设定:给定一个序列 [token_1, token_2, ..., token_n],怎么让每个 token 都能"考虑到"其他所有 token?
核心思路:对每个位置 i,计算它和所有其他位置 j 的"相关度"权重,然后用这些权重对所有位置的信息做加权求和。
具体来说,每个 token 会被投影成三个向量:
- Query (Q):当前位置在"问什么"
- Key (K):其他位置能"回答什么"
- Value (V):其他位置实际"携带的信息"
公式如下:
Attention(Q, K, V) = softmax(QK^T / √d_k) · V
拆开看:
Q · K^T:每个 query 和所有 key 做点积,得到一个n × n的"相关度矩阵"/ √d_k:除以 key 维度的平方根,防止点积过大导致 softmax 进入梯度消失区softmax:把每行归一化成概率分布· V:用这个概率分布对 value 加权求和,得到最终输出
几何直观:把每个 token 想象成高维空间中的一个向量。Self-Attention 让每个 token 都能"看一眼"所有其他 token,然后把自己变成"和我相关的那些 token 的加权混合体"。
工程视角:整个过程是纯矩阵乘法 + softmax。这意味着:
- 可以完全并行:所有位置同时计算
- 可以高度优化:矩阵乘法是 GPU 的看家本领(CUDA Tensor Core)
- 可以精确缓存:K 和 V 一旦算出可以反复用(这就是 KV Cache 的根基)
2.2 Multi-Head Attention:多个"视角"并行看
单个 attention 头有一个局限:它只能学到一种"相关性模式"。但语言中的关系是多元的——语法关系、语义关系、指代关系、共现关系……一种 attention 学不完。
解决方案:把 Q、K、V 拆成多组(比如 32 头),每组独立做 attention,最后拼起来。
# 伪代码
head_outputs = []
for i in range(num_heads):
Q_i, K_i, V_i = split_per_head(Q, K, V, i)
head_outputs.append(attention(Q_i, K_i, V_i))
output = concat(head_outputs) @ W_o
工程角度的几个关键数字(以 Llama-3-70B 为例):
hidden_dim= 8192num_heads= 64head_dim= 128(注意:8192 / 64 = 128)
这种"切分维度"的设计让多头几乎不增加计算量——总的矩阵规模没变,只是 reshape 了一下。
💡 2024 年后的演进:GQA / MQA
Llama 2 之后大量模型用 Grouped Query Attention:让多个 query head 共享同一组 K/V head。
例如 Llama-3-70B 实际有 64 个 Q head 但只有 8 个 KV head。
这个改动直接减少了 KV Cache 显存占用 8 倍——是长上下文部署的关键优化。
2.3 Positional Encoding:注入"顺序"信息
Self-Attention 有一个隐藏问题:它对位置完全不敏感。
把"我打你"和"你打我"输入进去,由于是对所有位置做加权求和,结果几乎一样——这显然不对。
解决方案:在输入向量上加一个表示位置的编码。
演进路线:
| 方法 | 提出时间 | 思路 | 当下使用 |
|---|---|---|---|
| Sinusoidal | 2017 | 用 sin/cos 表示绝对位置 | 已淘汰 |
| Learned PE | 2018 | 给每个位置学习一个向量 | BERT、早期 GPT |
| ALiBi | 2021 | 在 attention 分数上加位置偏置 | 部分模型 |
| RoPE | 2021 | 用旋转矩阵编码相对位置 | 当下主流(Llama / Qwen / DeepSeek) |
RoPE(Rotary Position Embedding)为什么赢了:
- 相对位置:天然反映 token 之间的距离关系
- 可外推:训练 4K,推理时通过 YaRN 等技术可以扩展到 32K、128K、1M
- 计算高效:只在 Q 和 K 上做旋转,不增加参数
👉 关于长上下文外推,详见 系列第 15 篇:YaRN、Ring Attention 详解
2.4 完整的 Transformer Block
把 attention 包装成可堆叠的 block,再加几个组件,就得到了完整的 Transformer Block:
输入 x
├─→ LayerNorm
│ ↓
│ Self-Attention
│ ↓
└─── ⊕ (残差连接)
↓
├─→ LayerNorm
│ ↓
│ FFN (前馈网络)
│ ↓
└─── ⊕ (残差连接)
↓
输出
几个工程上的关键设计:
① 残差连接(Residual Connection)
输入直接"短路"加到输出上:x + f(x)。这让深层网络可以训练——没有残差,超过几十层就训不动了。Llama-3-70B 有 80 层 Transformer Block,全靠残差扛着。
② LayerNorm vs RMSNorm
LayerNorm 把每层激活做归一化。2023 年后主流大模型基本都换成了 RMSNorm(Llama 系列首推),相比 LayerNorm 减少了一次均值计算,推理速度更快、精度几乎无损。
③ FFN(Feed-Forward Network)
每个 Transformer Block 里都有一个 FFN,结构是:
FFN(x) = W_2 · activation(W_1 · x)
W_1 把维度放大 4 倍,激活函数过滤一下,W_2 再缩回去。这是模型大部分参数的"住所"——以 Llama-3-70B 为例,FFN 占了总参数的约 70%。
💡 这就是 MoE 优化的入口
既然 FFN 参数最多但每次推理只用一部分,那不如把它做成"专家库"——每个 token 只激活几个专家。
这就是 DeepSeek V3 (671B 总参数,37B 激活) 背后的核心机制。
详见 系列第 31 篇:MoE 架构深度解析。
④ Pre-LN vs Post-LN
原始论文用 Post-LN(先做完 attention 再 LayerNorm),但训练大模型时不稳定。2020 年后所有大模型都改成了 Pre-LN(先 LayerNorm 再 attention)。这是一个不起眼但影响巨大的工程改进。
2.5 Decoder-only:为什么这个架构吃掉了一切
Transformer 原论文是为机器翻译设计的,包含 Encoder 和 Decoder 两部分。但实际演化中,三种架构同台竞技:
| 架构 | 代表模型 | 特点 | 现状 |
|---|---|---|---|
| Encoder-only | BERT | 双向注意力,擅长理解任务 | 仍在用,但非主流 |
| Encoder-Decoder | T5 / BART | 编码 + 解码,适合翻译 | 渐渐淡出 |
| Decoder-only | GPT / Llama / Claude | 自回归生成 | 绝对主流 |
为什么 Decoder-only 赢了?
从工程视角看,原因有三:
- 统一性:所有 NLP 任务都可以转化为"给定前缀,预测下一个 token"——分类、抽取、对话、翻译……一个范式打遍天下。
- Scaling 友好:训练目标极其简单(下一个 token 预测),数据极易获取(任何文本都能用),适合 Scaling Law。
- In-Context Learning 涌现:达到足够规模后,模型仅凭 prompt 中的例子就能学会新任务,无需微调。
Decoder-only 的核心机制:Causal Mask
为了保证"自回归生成"(生成第 t 个 token 时只能看到前 t-1 个),attention 需要加一个三角掩码:
Mask matrix (n=4):
[0 -inf -inf -inf]
[0 0 -inf -inf]
[0 0 0 -inf]
[0 0 0 0 ]
加在 QK^T 后、softmax 前。-inf 经过 softmax 变成 0,等于"看不到未来"。
这个看似简单的设计,直接催生了 KV Cache 这一推理优化的基石——我们下一节会看到。
三、实战拆解:用 100 行 PyTorch 写一个 Mini-GPT
光看公式记不住,写一遍代码就懂了。以下是一个可运行的极简 Transformer Decoder:
import torch
import torch.nn as nn
import torch.nn.functional as F
from dataclasses import dataclass
@dataclass
class Config:
vocab_size: int = 32000
hidden_dim: int = 256
num_heads: int = 8
num_layers: int = 4
max_seq_len: int = 512
class CausalSelfAttention(nn.Module):
def __init__(self, cfg):
super().__init__()
assert cfg.hidden_dim % cfg.num_heads == 0
self.num_heads = cfg.num_heads
self.head_dim = cfg.hidden_dim // cfg.num_heads
self.qkv = nn.Linear(cfg.hidden_dim, 3 * cfg.hidden_dim, bias=False)
self.proj = nn.Linear(cfg.hidden_dim, cfg.hidden_dim, bias=False)
# 因果掩码
mask = torch.tril(torch.ones(cfg.max_seq_len, cfg.max_seq_len))
self.register_buffer("mask", mask.view(1, 1, cfg.max_seq_len, cfg.max_seq_len))
def forward(self, x):
B, T, C = x.shape
# 1. 投影成 Q, K, V
qkv = self.qkv(x) # [B, T, 3C]
q, k, v = qkv.split(C, dim=-1)
# 2. reshape 成多头形式
q = q.view(B, T, self.num_heads, self.head_dim).transpose(1, 2) # [B, H, T, D]
k = k.view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
v = v.view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
# 3. 计算 attention 分数
scores = (q @ k.transpose(-2, -1)) / (self.head_dim ** 0.5)
# 4. 因果掩码
scores = scores.masked_fill(self.mask[:, :, :T, :T] == 0, float('-inf'))
# 5. softmax + 加权求和
attn = F.softmax(scores, dim=-1)
out = attn @ v # [B, H, T, D]
# 6. 合并多头
out = out.transpose(1, 2).contiguous().view(B, T, C)
return self.proj(out)
class FFN(nn.Module):
def __init__(self, cfg):
super().__init__()
self.w1 = nn.Linear(cfg.hidden_dim, 4 * cfg.hidden_dim, bias=False)
self.w2 = nn.Linear(4 * cfg.hidden_dim, cfg.hidden_dim, bias=False)
def forward(self, x):
return self.w2(F.gelu(self.w1(x)))
class Block(nn.Module):
def __init__(self, cfg):
super().__init__()
self.ln1 = nn.LayerNorm(cfg.hidden_dim)
self.attn = CausalSelfAttention(cfg)
self.ln2 = nn.LayerNorm(cfg.hidden_dim)
self.ffn = FFN(cfg)
def forward(self, x):
# Pre-LN + 残差
x = x + self.attn(self.ln1(x))
x = x + self.ffn(self.ln2(x))
return x
class MiniGPT(nn.Module):
def __init__(self, cfg):
super().__init__()
self.cfg = cfg
self.tok_emb = nn.Embedding(cfg.vocab_size, cfg.hidden_dim)
self.pos_emb = nn.Embedding(cfg.max_seq_len, cfg.hidden_dim)
self.blocks = nn.ModuleList([Block(cfg) for _ in range(cfg.num_layers)])
self.ln_f = nn.LayerNorm(cfg.hidden_dim)
self.head = nn.Linear(cfg.hidden_dim, cfg.vocab_size, bias=False)
def forward(self, idx):
B, T = idx.shape
pos = torch.arange(T, device=idx.device).unsqueeze(0)
x = self.tok_emb(idx) + self.pos_emb(pos)
for block in self.blocks:
x = block(x)
x = self.ln_f(x)
return self.head(x)
# 跑一个 forward 测试
if __name__ == "__main__":
cfg = Config()
model = MiniGPT(cfg)
x = torch.randint(0, cfg.vocab_size, (2, 64)) # batch=2, seq=64
logits = model(x)
print(logits.shape) # torch.Size([2, 64, 32000])
print(f"参数量: {sum(p.numel() for p in model.parameters()) / 1e6:.2f}M")
跑一下输出:
torch.Size([2, 64, 32000])
参数量: 11.95M
12M 参数的 mini-GPT,结构上和 Llama-3-70B 是完全一样的——只是层数、宽度、词表大小不同。
💡 真实工业级实现的差异
- 把
LayerNorm换成RMSNorm- 把绝对位置编码换成
RoPE- 把 FFN 的 GELU 换成 SwiGLU
- Attention 改成 GQA(多个 Q 共享 K/V)
- 实际用
torch.nn.functional.scaled_dot_product_attention直接调底层 Flash Attention但核心骨架不变。后续大模型的所有"创新",都是这个骨架上的"换部件"。
四、工程意义:从架构到推理优化
这部分是本文最重要的一节。我们要把"架构理解"转化为"工程认知"。
4.1 KV Cache:自回归生成的最大优化
回看 Decoder-only 的生成过程:模型每次只生成一个新 token,然后把它拼回输入再生成下一个。
朴素做法(无 KV Cache):
生成第 1 个 token:处理 [prompt] → 算所有 K, V → 输出
生成第 2 个 token:处理 [prompt, t1] → 重新算所有 K, V → 输出
生成第 3 个 token:处理 [prompt, t1, t2] → 重新算所有 K, V → 输出
...
注意到了吗?前面 token 的 K 和 V 每次都在被重复计算。
KV Cache 做法:
生成第 1 个 token:处理 [prompt] → 算 K, V → 缓存 → 输出
生成第 2 个 token:只处理 [t1] → 算 t1 的 K, V → 拼到缓存 → 输出
生成第 3 个 token:只处理 [t2] → 算 t2 的 K, V → 拼到缓存 → 输出
...
每生成一个新 token,只需计算这一个新 token 的 K/V,然后拼到历史缓存上做 attention。
性能差异有多大? 对于一个 prompt 1000 token、生成 100 token 的请求:
- 无 KV Cache:总计算量 ≈ Σ(1000+i)² ≈ 1.1 × 10⁸
- 有 KV Cache:总计算量 ≈ 1000² + 100 × 1000 ≈ 1.1 × 10⁶
差 100 倍。这就是为什么任何工业级推理框架都必须支持 KV Cache。
4.2 KV Cache 的代价:显存
KV Cache 不是免费午餐——它会吃掉大量显存。
KV Cache 显存公式:
KV_cache_bytes = 2 × batch × seq_len × num_layers × num_kv_heads × head_dim × dtype_size
以 Llama-3-70B 为例:
num_layers= 80num_kv_heads= 8(GQA 后)head_dim= 128dtype_size= 2(FP16)
一个请求处理 32K 上下文:
2 × 1 × 32768 × 80 × 8 × 128 × 2 = ~10.7 GB
注意:这只是单个请求的 KV Cache。如果你想并发 10 个请求,KV Cache 总占用是 107 GB——比模型权重本身(140 GB)的占用还要可观。
这就是为什么 vLLM 的 PagedAttention 横空出世:把 KV Cache 当作"虚拟内存"管理,避免碎片化和过度预分配。详见 系列第 11 篇:推理加速三板斧。
4.3 上下文长度的代价:O(n²) 的诅咒
Attention 的核心计算 QK^T 是一个 n × n 的矩阵。这意味着:
- 上下文翻倍,attention 计算量变成 4 倍
- 上下文 10 倍,attention 计算量变成 100 倍
这就是为什么大家拼命要"扩上下文",但每多一个数量级都极其困难。
几个数字感受一下(同样是 Llama-3-70B,FP16):
| 上下文长度 | KV Cache 显存 | Attention 计算量(相对) |
|---|---|---|
| 4K | 1.3 GB | 1× |
| 32K | 10.7 GB | 64× |
| 128K | 43 GB | 1024× |
| 1M | 335 GB | 65536× |
1M 上下文不是"扩个参数"就能做到,它需要:
- Flash Attention 优化 IO(系列第 13 篇)
- Ring Attention 分布式注意力(系列第 15 篇)
- YaRN 等位置编码外推(系列第 15 篇)
- 极其精细的显存管理
4.4 显存的三巨头:模型权重 + KV Cache + 激活值
实际部署一个大模型,显存被三部分吃掉:
显存 = 模型权重 + KV Cache + 激活值 + 临时 Buffer
以 A100 80G 部署 Llama-3-70B FP16 为例:
| 项 | 占用 | 说明 |
|---|---|---|
| 模型权重 | ~140 GB | 不可省,需 2 卡 |
| KV Cache (32K, batch=4) | ~43 GB | 可压缩(量化、PagedAttention) |
| 激活值 | ~10 GB | 推理时可优化 |
| Buffer 等 | ~5 GB | 框架开销 |
一张 A100 80G 跑不动——所以工业部署必须考虑:
- 量化压缩模型权重(INT8 / INT4,系列第 12 篇)
- KV Cache 量化(INT8)
- 张量并行(TP)跨卡分割模型(系列第 20 篇)
五、变体与演进:Transformer 还能怎么变
理解了原版 Transformer,再来看主流变体就轻松了。
5.1 三大架构的胜负已分
Encoder-only ──── 适合理解(BERT),渐弱
Encoder-Decoder ── 适合翻译(T5),淡出
Decoder-only ──── 适合生成(GPT/Llama/Claude),主流
未来 5 年,主流大模型几乎全是 Decoder-only。
5.2 部件级的演进
主流大模型相对原版 Transformer 的"换件清单":
| 部件 | 原版 (2017) | 当下主流 (2026) |
|---|---|---|
| 归一化 | LayerNorm | RMSNorm |
| 位置编码 | Sinusoidal | RoPE |
| 激活函数 | ReLU/GELU | SwiGLU |
| Attention | MHA | GQA / MQA |
| LN 位置 | Post-LN | Pre-LN |
| FFN 结构 | 单分支 | MoE(部分) |
每个改动看似微小,但叠加起来让模型效率提升数倍。
5.3 替代架构:Mamba、Linear Attention 等
近两年也有一些"挑战 Transformer"的架构:
- Mamba / S4:状态空间模型,O(n) 复杂度
- Linear Attention:把 attention 改造成线性复杂度
- RWKV:RNN 风格但能并行训练
现状判断:这些架构在特定场景(长序列、低算力)有优势,但目前仍未撼动 Transformer 的主导地位。原因是:
- 工程生态(CUDA kernel、推理框架)全部围绕 Transformer 建立
- Scaling Law 在 Transformer 上经过充分验证
- 模型能力上限尚未看到瓶颈
对工程师的建议:当下深入 Transformer,关注但不押注替代架构。
结语:地基打稳了,后面就好办了
Transformer 是大模型时代最重要的"地基"。它本身不复杂——核心就是几个矩阵乘法 + 残差 + 归一化——但它支撑的整个工程栈极其丰富。
读完本文,你应该已经能回答开头那几个问题了:
- KV Cache 缓存了什么? 历史 token 的 K 和 V 矩阵,避免重复计算。
- 上下文为什么贵? Attention 是 O(n²),从 4K 到 1M 计算量增长 65536 倍。
- 为什么所有推理框架都围着 attention 转? 因为 attention 是计算 + 显存的双重瓶颈。
- 为什么 Decoder-only 赢? 任务统一、Scaling 友好、In-Context Learning 涌现。
如果你对某个部分还有疑问,强烈建议把第三节的 100 行代码亲手敲一遍跑一下。架构这种东西,看十遍不如手写一遍。
接下来 33 篇,我们会一层一层把这个工程栈打开:
- 下一篇(第 3 篇):模型参数解密。
7B / 13B / 70B / 671B到底意味着什么?怎么估算显存?怎么选合适规模? - 再往后会进入训练(第 6-10 篇)、推理优化(第 11-15 篇)、部署(第 16-20 篇)等具体战场。
我们下篇见。
📮 关于「码海寻道」
这里是一个聚焦 AI 工程化、大模型部署、后端架构实战的技术专栏。
写最一线的踩坑经验,做最务实的技术拆解。如果这篇文章对你有启发,欢迎点赞、转发、关注。我们下篇见。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)