一文读懂 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. 完全并行:序列内所有位置同时计算,训练效率呈数量级提升
  2. 长程依赖直连:第 1 个 token 可以直接"看到"第 8000 个 token
  3. 架构高度统一: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

拆开看:

  1. Q · K^T:每个 query 和所有 key 做点积,得到一个 n × n 的"相关度矩阵"
  2. / √d_k:除以 key 维度的平方根,防止点积过大导致 softmax 进入梯度消失区
  3. softmax:把每行归一化成概率分布
  4. · 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 = 8192
  • num_heads = 64
  • head_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)为什么赢了

  1. 相对位置:天然反映 token 之间的距离关系
  2. 可外推:训练 4K,推理时通过 YaRN 等技术可以扩展到 32K、128K、1M
  3. 计算高效:只在 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 赢了?

从工程视角看,原因有三:

  1. 统一性:所有 NLP 任务都可以转化为"给定前缀,预测下一个 token"——分类、抽取、对话、翻译……一个范式打遍天下。
  2. Scaling 友好:训练目标极其简单(下一个 token 预测),数据极易获取(任何文本都能用),适合 Scaling Law。
  3. 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 = 80
  • num_kv_heads = 8(GQA 后)
  • head_dim = 128
  • dtype_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
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 的主导地位。原因是:

  1. 工程生态(CUDA kernel、推理框架)全部围绕 Transformer 建立
  2. Scaling Law 在 Transformer 上经过充分验证
  3. 模型能力上限尚未看到瓶颈

对工程师的建议:当下深入 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 工程化、大模型部署、后端架构实战的技术专栏。
写最一线的踩坑经验,做最务实的技术拆解。

如果这篇文章对你有启发,欢迎点赞、转发、关注。我们下篇见。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐