架构与输入
架构图
这张图是原论文的标准架构图,适合建立全局结构;后面的内容会补充现代 Decoder-only 大模型和 Pre-LN/RMSNorm 等工程变体。
核心概念
Transformer 不是一个“Attention 模块”,而是一套可堆叠的序列建模骨架。它把每个 token 表示沿着两条路径推进:
- 残差主干路径:保存并累积 token 表示,让深层网络仍然可训练。
- Attention 混合路径:让 token 之间交换信息,建立上下文依赖。
- FFN 变换路径:对每个 token 独立做非线性投影,提供容量和知识存储。
- 归一化路径:控制激活尺度,稳定训练和长层数堆叠。
系统链路
关键机制
Encoder、Decoder 和 Decoder-only 的边界
| 架构 | 注意力可见性 | 代表模型 | 适合任务 | 系统含义 |
|---|---|---|---|---|
| Encoder-only | 双向,任意 token 可看全句 | BERT | 分类、抽取、embedding | 没有自回归 decode,推理通常一次前向完成 |
| Encoder-Decoder | Encoder 双向,Decoder 因果 + cross-attn | T5、原始 Transformer | 翻译、摘要、seq2seq | Decoder 每步还要读 Encoder memory |
| Decoder-only | 因果 mask,只能看历史 token | GPT、LLaMA | 生成、对话、代码 | 当前 LLM serving 主流,KV cache 是核心状态 |
Pre-LN 与 Post-LN
原始 Transformer 更接近 Post-LN:
现代大模型更常用 Pre-LN 或 RMSNorm 变体:
Pre-LN 的好处是梯度主干更直接,深层训练更稳定;代价是最后通常还需要一个 final norm,并且不同实现会影响激活尺度和初始化策略。
Attention 和 FFN 的职责不同
| 模块 | 做什么 | 资源特征 | 深度理解 |
|---|---|---|---|
| Attention | token 间通信,决定“谁看谁” | prefill 有 \(O(n^2d)\),decode 读 KV cache | 更像动态路由和上下文聚合 |
| FFN / MLP | 每个 token 独立变换 | 参数量和 FLOPs 通常占大头 | 更像逐 token 的非线性记忆库 |
| Norm | 控制尺度 | 算子小但常 memory-bound | 保证深层堆叠的数值稳定 |
| Residual | 保留主干信息 | 几乎纯读写 | 提供梯度高速通道 |
深度追问
| 追问 | 回答抓手 |
|---|---|
| 为什么 Attention 本身不懂顺序? | Attention 对 token 集合是置换等变的,必须注入位置编码或 RoPE。 |
| 为什么 Decoder 需要 causal mask? | 自回归训练时不能看未来 token,否则 teacher forcing 会泄漏答案。 |
| 为什么现代 LLM 多是 Decoder-only? | 统一输入输出为 next-token prediction,训练数据形式简单,推理状态可用 KV cache 增量维护。 |
| 为什么 FFN 很重要? | 参数和计算量大,提供模型容量;很多事实知识和非线性变换能力在 FFN 中体现。 |
| 为什么 Transformer 适合 GPU? | 大部分核心算子是 GEMM/attention block,可批量并行;但 decode 阶段会转向 memory-bound。 |
输入处理三件套:从文字到向量
模型不能直接吃文字,必须先把文字变成数字向量。整条链路是:
文本 →(Tokenizer)→ token →(查词表)→ input_ids →(Embedding 查表)→ 词向量 →(+ 位置编码)→ 送入第一层
1. Tokenizer:怎么把文本切成 token
Tokenizer 用特定算法(如 BPE 或 WordPiece)把连续文本切成更小的单元——token。token 可以是完整单词、词根、词缀,甚至单个字符。然后在预先构建的词表(vocab)里查每个 token,映射成唯一的整数编号(token id)。最终一段文本变成一串 token id 序列,这才是模型的真正输入。
| 算法 | 核心思想 | 代表模型 |
|---|---|---|
| BPE(Byte Pair Encoding) | 从字符开始,反复合并出现频率最高的相邻字符对,直到词表达到设定大小 | GPT 系列 |
| WordPiece | 和 BPE 类似,但合并时选「能最大提升语言模型似然」的字符对 | BERT |
| SentencePiece | 不依赖空格分词,直接在原始字节流上做,适合中文/多语言 | LLaMA、T5 |
为什么不直接用单词或单字?用整词:词表会爆炸,且遇到没见过的词(OOV)就歇菜;用单字:序列太长、语义颗粒太碎。子词(subword)是折中:常见词当整体,罕见词拆成词根词缀,既控制词表大小又能处理生词。
2. Embedding:是什么、在哪里
模型维护一个可学习的 Embedding 矩阵,形状是 [vocab_size, hidden_size]。每个 token id 对应矩阵的一行。所谓 Embedding 就是用 token id 去这个矩阵里查表(取出对应那一行向量)。
- 位置:在模型最前端,紧跟 Tokenizer 之后、第一个 Transformer block 之前。
- 本质:就是一次查表(lookup),不是矩阵乘法。把离散的整数 id 变成稠密的连续向量。
- 可学习:这个矩阵是模型参数,训练中会被反向传播更新,语义相近的词向量会逐渐靠近。
3. 位置编码:为什么需要、好处是什么
为什么需要:Transformer 不像 RNN 那样一个一个按顺序处理,而是一次性看全局、所有 token 并行计算。Attention 本身是「无序」的——打乱输入顺序,算出来的结果只是跟着换位置,模型分不清「猫追狗」和「狗追猫」。所以必须额外注入位置信息,让模型知道每个 token 在序列中的绝对或相对位置。
好处:
- 保留单词在序列中的顺序信息,让模型能区分词序不同导致的语义差异。
- 让模型有能力建模相对距离(谁离谁近、谁在前谁在后)。
| 方案 | 做法 | 特点 |
|---|---|---|
| 正弦/余弦(原文) | 用不同频率的 sin/cos 函数算出固定位置向量,和 Embedding 相加 | 不用学习、可外推到更长序列 |
| 可学习位置编码 | 像 Embedding 一样维护一张可训练的位置向量表 | BERT 用,简单但难外推到训练没见过的长度 |
| RoPE(旋转位置编码) | 通过旋转 Q/K 向量来编码相对位置 | LLaMA 等主流大模型在用,外推性好 |
核心机制
Self-Attention:核心三步
注意力的本质是「加权求和」:每个 token 输出 = 其它所有 token 的 value 的加权平均,权重由「我和你有多相关」决定。
- 每个 token 投影出三个向量:Q(Query,我要找什么)、K(Key,我能提供什么)、V(Value,我的实际内容)。
- 用 Q 和所有 K 做点积得到相关性分数,除以
√d_k缩放,再 softmax 归一化成权重。 - 用权重对所有 V 加权求和,得到这个 token 的新表示。
公式:
Multi-Head vs Single-Head:区别和好处
Single-Head 只在一个空间里算一次注意力。Multi-Head 把 hidden_size 拆成多个并行的子空间(head),每个 head 独立算一次注意力,再把结果拼接起来过一个输出投影。
| 维度 | Single-Head | Multi-Head |
|---|---|---|
| 建模角度 | 只能学一种关注模式 | 每个头学不同模式(语法、语义、位置…) |
| 表达能力 | 较弱 | 能捕获更丰富的依赖关系 |
| 计算成本 | 差不多(总维度不变,只是拆开算) | 差不多,且天然可并行 |
Multi-Head 的好处:
- 多角度建模:每个头关注不同子空间特征,类似 CNN 里的多个卷积核。
- 表达能力更强:相比单头能同时捕获多种依赖。
- 并行性好:多个头之间互不依赖,天然并行。
手撕 Multi-Head Attention(带 KV Cache)
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
def __init__(self, hidden_size, num_heads):
super().__init__()
self.num_heads = num_heads
self.head_dim = hidden_size // num_heads
self.q_linear = nn.Linear(hidden_size, hidden_size)
self.k_linear = nn.Linear(hidden_size, hidden_size)
self.v_linear = nn.Linear(hidden_size, hidden_size)
self.o_linear = nn.Linear(hidden_size, hidden_size)
def forward(self, hidden_state, causal_mask=None,
past_key_value=None, use_cache=False):
batch_size = hidden_state.size(0)
query = self.q_linear(hidden_state)
key = self.k_linear(hidden_state)
value = self.v_linear(hidden_state)
# 多头拆分: [B, S, H] -> [B, num_heads, S, head_dim]
query = query.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
key = key.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
value = value.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
# 拼接 KV cache: 把历史的 key/value 接到前面
if past_key_value is not None:
past_key, past_value = past_key_value
key = torch.cat([past_key, key], dim=2)
value = torch.cat([past_value, value], dim=2)
new_past_key_value = (key, value) if use_cache else None
# 注意力打分 + 缩放
attention_scores = torch.matmul(query, key.transpose(-1, -2)) \
/ torch.sqrt(torch.tensor(self.head_dim, dtype=torch.float32))
# 因果掩码: 把不能看的位置加上一个极大负数, softmax 后趋近 0
if causal_mask is not None:
attention_scores += causal_mask * -1e9
attention_probs = F.softmax(attention_scores, dim=-1)
output = torch.matmul(attention_probs, value)
# 合并多头: [B, num_heads, S, head_dim] -> [B, S, H]
output = output.transpose(1, 2).contiguous() \
.view(batch_size, -1, self.num_heads * self.head_dim)
output = self.o_linear(output)
return (output, new_past_key_value) if use_cache else output
手撕代码逐段讲解(面试要能口述)
- 四个 Linear:q/k/v 把输入投影成查询、键、值;o_linear 是多头拼接后的输出投影。
- view + transpose:把
[B, S, hidden]切成[B, num_heads, S, head_dim],让每个头独立算。transpose(1,2)是为了把 head 维提到前面,方便 batch 矩阵乘。 - KV cache:自回归生成时,前面 token 的 K/V 不变,缓存下来避免重复计算,每步只算新 token 的 Q。这是推理加速的关键。
- 除以 √head_dim:缩放点积,防止 softmax 饱和、梯度消失。
- causal_mask * -1e9:给「未来位置」加一个极大负数,softmax 后这些位置权重≈0,实现「不能看未来」。
- 合并多头:transpose 回来、contiguous(保证内存连续)、view 拼回 hidden_size,最后过 o_linear。
contiguous() 不能省——transpose 后内存不连续,直接 view 会报错。Attention vs Feed Forward:各自的作用
| 模块 | 作用 | 核心含义 |
|---|---|---|
| Attention | 在 token 之间做信息交互,捕获序列依赖关系 | 「谁该关注谁」——做混合/通信 |
| Feed Forward (FFN) | 对每个 token 独立做非线性变换,提升表达能力 | 承担「知识存储」,做加工/记忆 |
形象比喻:Attention 是「开会,大家交换信息」;FFN 是「会后各自回去消化、加工」。一层 Transformer 就是「交流一次 + 各自加工一次」。研究还发现大模型的事实知识大量存储在 FFN 层里。
先搞懂:什么是梯度消失/爆炸
深层网络靠反向传播更新参数,梯度要从最后一层「逐层相乘」传回最前面。
- 梯度消失:每层梯度都小于 1,连乘后越传越小,最后趋近 0 → 浅层参数几乎不更新,学不动。
- 梯度爆炸:每层梯度都大于 1,连乘后越传越大 → 参数剧烈震荡、loss 变 NaN,训练发散。
大模型层数很深(几十上百层),这个问题尤其严重,所以需要一整套技术来稳住训练。
大模型怎么处理(六个手段,按重要性记)
| 手段 | 解决什么 | 原理 |
|---|---|---|
| 残差连接(Residual) | 梯度消失 | y = x + F(x),梯度有一条「直通车」绕过 F 直达浅层,不会被连乘衰减 |
| LayerNorm / RMSNorm | 激活值不稳定 | 对每层激活做归一化,稳定分布,让梯度幅度可控 |
| 合理初始化(Xavier/Kaiming) | 初始梯度过大/过小 | 让各层输入输出的方差保持一致,避免一开始就消失或爆炸 |
| 梯度裁剪(Gradient Clipping) | 梯度爆炸 | 梯度范数超过阈值就等比例缩小,硬性封顶 |
| 学习率 warmup + decay | 训练初期发散 | 先用小学习率慢慢升(warmup),再逐渐衰减,避免一开始步子太大 |
| 混合精度 + Loss Scaling | FP16 梯度下溢 | FP16 表示范围小,小梯度会变 0;把 loss 乘大再算梯度,更新前再除回来 |
残差连接为什么最关键
残差是 Transformer 能堆几十层的头号功臣。反向传播时 y = x + F(x) 的梯度是 1 + F'(x),那个常数 1 保证了即使 F'(x) 很小,梯度也不会衰减到 0——相当于给梯度修了一条高速公路,可以直通传回浅层。
LayerNorm vs BatchNorm vs RMSNorm
| 方法 | 归一化维度 | 为什么用在这 |
|---|---|---|
| BatchNorm | 对一个 batch 内同一特征归一化 | 依赖 batch 统计量,序列长度可变、batch 小的时候不稳定,NLP 一般不用 |
| LayerNorm | 对单个样本的所有特征归一化 | 不依赖 batch,对每个 token 独立做,原始 Transformer 用 |
| RMSNorm | 只用均方根缩放,不减均值 | 比 LayerNorm 少算一步、更快,效果相当,LLaMA 等主流大模型用 |
另外还有 Pre-Norm vs Post-Norm:原文是 Post-Norm(先残差后归一),现代大模型多用 Pre-Norm(先归一再进子层,x + F(LN(x))),训练更稳定、更容易收敛。
计算分析
先不用一上来背公式,而是把单层 Transformer 的计算量理解成两类:一类是各种线性层带来的 $nd^2$ 项,另一类是 Attention 两次大矩阵乘带来的 $n^2d$ 项。最终结论是:
$$ \text{总FLOPs} = 24nd^2 + 4n^2d $$
下面一步步推出它。
1. 单层主要算两件事
一个 Transformer Encoder Layer 主要包括 Self-Attention 和 FFN(前馈网络)两部分,所以:
$$ \text{总FLOPs} = \text{Attention 的 FLOPs} + \text{FFN 的 FLOPs} $$
2. 先理解矩阵乘法为什么是 $2MNK$
两个矩阵相乘:
$$ A_{M \times N} \times B_{N \times K} = C_{M \times K} $$
输出矩阵 $C$ 有 $M \times K$ 个元素,每个元素是一次长度为 $N$ 的向量点积:
$$ c_{ij} = a_{i1}b_{1j} + a_{i2}b_{2j} + \dots + a_{iN}b_{Nj} $$
一个元素约需 $N$ 次乘法 + $N$ 次加法 = $2N$ 次运算,共 $M \times K$ 个元素,所以:
$$ \text{FLOPs} = 2MNK $$
这是后面所有推导的基础。
3. Attention 部分
设输入 $X \in \mathbb{R}^{n \times d}$,其中 $n$ 是 token 数,$d$ 是每个 token 的向量维度。
3.1 QKV 投影:$6nd^2$
输入 $X$ 分别乘三个权重得到 Q、K、V:
$$ Q = XW_Q,\quad K = XW_K,\quad V = XW_V $$
其中 $X$ 是 $n \times d$,$W_Q, W_K, W_V$ 是 $d \times d$。一次投影是 $n \times d$ 乘 $d \times d$,按 $2MNK$($M=n, N=d, K=d$)得 $2nd^2$。三次合计:
$$ 3 \times 2nd^2 = 6nd^2 $$
3.2 计算 $QK^\top$:$2n^2d$
Q 是 $n \times d$,$K^\top$ 是 $d \times n$,相乘得到 $n \times n$ 的注意力矩阵。按 $2MNK$($M=n, N=d, K=n$):
$$ 2 \times n \times d \times n = 2n^2 d $$
$n^2$ 的来源很关键:每个 token 都要和每个 token 算相关性,产生一个 $n \times n$ 矩阵。
3.3 注意力权重乘 V:$2n^2d$
Softmax 后的权重矩阵是 $n \times n$,V 是 $n \times d$,相乘得到 $n \times d$。按 $2MNK$($M=n, N=n, K=d$):
$$ 2 \times n \times n \times d = 2n^2 d $$
3.4 输出投影:$2nd^2$
Attention 输出后再过一个输出线性层,$n \times d$ 乘 $d \times d$,得 $2nd^2$。
3.5 Attention 总和
| 步骤 | FLOPs |
|---|---|
| QKV 投影 | $6nd^2$ |
| $QK^\top$ | $2n^2d$ |
| 权重乘 V | $2n^2d$ |
| 输出投影 | $2nd^2$ |
$$ \text{Attention} = 6nd^2 + 2n^2d + 2n^2d + 2nd^2 = 8nd^2 + 4n^2d $$
4. FFN 部分
FFN 是两层线性层,维度变化 $d \rightarrow 4d \rightarrow d$。
4.1 第一层 $d \rightarrow 4d$:输入 $n \times d$,权重 $d \times 4d$:
$$ 2 \times n \times d \times 4d = 8nd^2 $$
4.2 第二层 $4d \rightarrow d$:输入 $n \times 4d$,权重 $4d \times d$:
$$ 2 \times n \times 4d \times d = 8nd^2 $$
4.3 FFN 总和:
$$ \text{FFN} = 8nd^2 + 8nd^2 = 16nd^2 $$
5. 合起来
$$ \text{Attention} = 8nd^2 + 4n^2d,\qquad \text{FFN} = 16nd^2 $$
$$ \text{总FLOPs} = 8nd^2 + 4n^2d + 16nd^2 = 24nd^2 + 4n^2d $$
6. 结果含义
> Transformer 单层的计算量由两部分组成:线性层带来的 $nd^2$,和 Attention 两次大矩阵乘带来的 $n^2d$。
- $24nd^2$:来自 QKV 投影、输出投影、FFN;
- $4n^2d$:来自 $QK^\top$ 和注意力权重乘 V。
当 $n \ll d$ 时线性层($nd^2$)占主导;当 $n$ 很大时注意力的 $n^2d$ 项成为瓶颈。
最容易卡住的点:为什么 $QK^\top$ 是 $2n^2d$
如果卡在这一步,可以这样想:有 $n$ 个 query、$n$ 个 key,每个 query 都要和每个 key 算一次相似度,所以一共 $n\times n$ 个相似度;每个相似度是两个 $d$ 维向量点积,约 $2d$ 次运算;总共 $n\times n\times 2d = 2n^2d$。
只记一个基本公式 $2MNK$,然后数有几个矩阵乘:QKV 三次投影 + 输出投影是 4 个 $n\times d$ 乘 $d\times d$,各 $2nd^2$,合 $8nd^2$;FFN 两层是 $8nd^2 \times 2 = 16nd^2$;这两类都是 $nd^2$,合 $24nd^2$。再加注意力两次大矩阵乘 $QK^\top$ 和权重乘 V,各 $2n^2d$,合 $4n^2d$。最终 $24nd^2 + 4n^2d$。整模型再乘层数,训练含反向约再 ×3。
Roofline 在 Transformer 算子中的用法
Roofline 用算术强度与机器平衡点判断 Transformer 算子更可能 compute-bound 还是 memory-bound。
| 概念 | Transformer 分析方法 |
|---|---|
| 算术强度 | 判断一个 Transformer kernel 的数据复用程度 |
| 机器平衡点 | A100 约 156 FLOPs/Byte,低于它通常更偏 memory-bound |
| 优化方向 | memory-bound 优先减少 HBM 读写;compute-bound 优先提高 Tensor Core 利用率 |
哪些操作 compute-bound,哪些 memory-bound
| 操作 | 瓶颈 | 原因 |
|---|---|---|
| 大 batch 矩阵乘(QKV、FFN) | compute-bound | 计算量随 batch 增长快,数据搬运增长慢,算术强度高 |
| 逐 token decode(batch 小) | memory-bound | 矩阵乘退化成 GEMV,算术强度低,瓶颈是读权重带宽 |
| Softmax、LayerNorm 等 element-wise | memory-bound | 计算量相对访存量很小 |
串到 Prefill / Decode
| 阶段 | 计算形态 | 瓶颈 | 关键指标 |
|---|---|---|---|
| Prefill | 多 token 并行,大矩阵乘 | compute-bound | TTFT |
| Decode | batch 小,GEMV,反复读权重 | memory-bound | TPOT |
两阶段瓶颈相反,优化手段也相反:prefill 靠 chunked prefill、提高 TensorCore 利用率;decode 靠 continuous batching 摊销权重读取、靠 KV cache 量化和 PagedAttention 降带宽与显存压力。
算它的算术强度(FLOPs ÷ 访存 bytes),和机器平衡点(峰值算力 ÷ 峰值带宽)比较。高于平衡点就是 compute-bound,低于就是 memory-bound。比如 A100 平衡点 ≈ 312 TFLOPS / 2 TB/s ≈ 156 FLOP/byte,而 batch=1 decode 算术强度约为 1,远低于平衡点,是典型 memory-bound。
我会先定义横轴和纵轴:横轴是算术强度 FLOPs/Byte,纵轴是实际性能 FLOPs/s。然后给公式:可达性能上限等于 min(峰值算力, 峰值带宽 × 算术强度)。图上斜线是 memory roof,水平线是 compute roof,交点 ridge point 是机器平衡点。如果 kernel 落在斜线区域,优化方向是减少访存、提高数据复用、融合算子;如果落在水平线区域,优化方向是提高 Tensor Core 利用率、优化 tile、使用低精度或减少 FLOPs。
面试
Transformer 面试高频题
把前面知识点压缩成可背诵的问答,点击展开。先盖住答案自己说一遍,再对照。
Encoder 处理输入序列输出上下文表示,每层 = Multi-Head Self-Attention + Feed Forward,子层都带残差和 Layer Norm。Decoder 处理目标序列输出预测,每层 = Masked Self-Attention(防止看未来)+ Encoder-Decoder Cross-Attention(交叉注意力)+ Feed Forward,同样带残差和 Layer Norm。
最前端是 Tokenizer + Embedding + 位置编码,最末端是 Linear + Softmax 输出层。
文本先由 Tokenizer 切成 token,按词表映射成整数 input_ids。模型维护一个可学习的 Embedding 矩阵 [vocab_size, hidden_size],用 token id 查表取出对应行向量,就得到 token 向量。本质是查表,不是矩阵乘。
在模型最前端,紧跟 Tokenizer 之后、第一个 Transformer block 之前,之后还要加上位置编码。
Transformer 不用 RNN 结构,而是一次性看全局、所有 token 并行计算。Attention 本身对顺序不敏感(打乱输入只是结果跟着换位),无法利用单词顺序信息。所以要用位置编码把顺序补回来。
保存单词在序列中的绝对或相对位置,让模型能区分词序不同导致的语义差异,并建模 token 之间的相对距离。
用 BPE / WordPiece 等算法把连续文本切成更小的单元(token),可以是整词、词根、词缀甚至单字。然后在预构建的词表里查每个 token,映射成唯一的整数编号(token id)。一段文本就变成一串 token id,成为模型输入。
整词词表会爆炸且无法处理生词(OOV),单字序列太长语义太碎。子词是折中:常见词当整体、罕见词拆词根词缀,既控制词表大小又能处理生词。
每个 token 投影出 Q、K、V;用 Q 和所有 K 点积得相关性分数,除以 √d_k 缩放后 softmax 成权重;用权重对所有 V 加权求和。公式 softmax(Q·Kᵀ/√d_k)·V。
维度大时点积数值会很大,softmax 进入梯度极小的饱和区,会导致梯度消失。除以 √d_k 把方差拉回来,稳定梯度。
Single-Head 只在一个空间算一次注意力;Multi-Head 把 hidden_size 拆成多个并行子空间,每个 head 独立算注意力再拼接过输出投影。总维度不变,所以参数量和计算量基本一样,只是切开算。
- 多角度建模:每个头关注不同子空间特征(语法、语义、位置)。
- 表达能力更强:能同时捕获更丰富的依赖。
- 并行性好:多头之间天然并行。
在 token 之间做信息交互,捕获序列依赖关系——「谁该关注谁」,做的是混合/通信。
对每个 token 独立做非线性变换,提升表达能力,承担「知识存储」作用,做的是加工/记忆。
自回归生成时,前面 token 的 K/V 不变,缓存下来避免重复计算,每步只算新 token 的 Q 和它的 K/V,再 cat 到历史后面。是推理加速的关键。
给「未来位置」加一个极大负数(mask * -1e9),softmax 后这些位置权重≈0,实现「只能看到自己和左边、不能偷看未来」。
合并多头前必须 contiguous(),因为 transpose 后内存不连续,直接 view 会报错。
- 残差连接:梯度有直通车直达浅层,最关键。
- LayerNorm / RMSNorm:稳定每层激活分布。
- 合理初始化(Xavier/Kaiming):保持各层方差一致。
- 梯度裁剪:梯度范数超阈值就缩小,防爆炸。
- 学习率 warmup + decay:避免训练初期发散。
- 混合精度 + Loss Scaling:解决 FP16 梯度下溢。
y=x+F(x) 的梯度是 1+F'(x),那个 1 保证梯度不衰减。BatchNorm 对 batch 内同一特征归一化,依赖 batch 统计量。NLP 里序列长度可变、batch 可能很小,batch 统计不稳定。LayerNorm 对单个样本的所有特征归一化,不依赖 batch,对每个 token 独立做,更适合变长序列。
- Encoder-only(BERT):双向注意力,擅长理解类任务(分类、抽取)。
- Decoder-only(GPT/LLaMA):单向因果注意力,擅长生成,当前主流大模型。
- Encoder-Decoder(原始 Transformer / T5):两半都有,擅长翻译、摘要等 seq2seq。
资源入口
怎么用它学习
| 你要理解什么 | 在 Explainer 里看什么 | 回到本站补什么 |
|---|---|---|
| Token 怎么进入模型 | 输入 token、embedding、position 的变化 | 输入处理:Tokenizer、Embedding、位置编码边界 |
| Attention 怎么混合信息 | attention heads 对不同 token 的权重 | Attention 机制:Q/K/V、mask、multi-head 公式 |
| 每层在做什么 | residual stream、attention block、MLP block 的变化 | 整体架构:残差主干、Norm、FFN 职责 |
| 生成为何逐 token 进行 | next-token logits 和采样过程 | LLM 推理系统:prefill/decode、KV cache、TPOT |
| 为什么算子有不同瓶颈 | attention/MLP 的结构和张量形状 | 计算分析:FLOPs、Roofline、memory-bound 判定 |
常见误区
| 误区 | 正确理解 |
|---|---|
| 看懂可视化就等于懂 Transformer | 可视化建立直觉,但还要补张量形状、复杂度、mask、KV cache 和系统瓶颈。 |
| attention head 的颜色就是固定语义 | head 的行为是 learned pattern,不要过度解释某个 head 的单次可视化。 |
| 可视化里的小模型能代表线上 LLM 性能 | 小模型适合理解机制,线上 LLM 还要看显存、并行、batching、KV cache 和 serving engine。 |
| Transformer 架构图已经够了 | 架构图是静态结构,Explainer 补动态过程,本站补工程和面试表达。 |