【HCIE-AI】8.Transformer 架构详解
Transformer 架构详解【Transformer架构详解】学习笔记-CSDN博客目录一句话本质Transformer 总图核心组件拆解7 个3.1 Self-Attention自注意力3.2 Multi-Head Attention多头注意力3.3 Positional Encoding位置编码3.4 Feed Forward Network前馈神经网络3.5 Layer Normalization层归一化3.6 Residual Connection残差连接3.7 Masked Self-Attention掩码自注意力数据流实例我爱学习通过 Encoder 全过程Encoder vs Decoder vs Encoder-Decoder关键参数与模型规模为什么 Transformer 能工作的三点核心直觉KV Cache键值缓存详解1. 一句话本质Transformer Attention 机制 并行计算的组合。放弃了RNN/LSTM的逐个时间步递推模式改为一次性看完整句话、用注意力机制决定当前词应该关注哪些其他词。和 RNN 的根本区别RNN按顺序读词当前时间步依赖上一个时间步的隐状态 我___学习 → 读完我才能读学习 → 顺序处理无法并行 Transformer一次性看到所有词用注意力矩阵算出每个词和其他词的关系 我 爱 学习 同时计算我→ 关注爱的程度 0.6关注学习的程度 0.4 爱→ 关注我的程度 0.5关注学习的程度 0.5 学习→ 关注爱的程度 0.7关注我的程度 0.3 → 并行计算速度快 N 倍N 序列长度2. Transformer 总图输出序列概率分布 ↑ Linear Softmax ↑ ┌──────────────────────────┐ │ Add Norm │ │ Feed Forward │ │ Add Norm │ │ Multi-Head Attention │ ← Decoder 层重复 N 次 │ Add Norm │ │ Masked Multi-Head Attn │ └──────────┬───────────────┘ │ ← 右移的 Decoder 输入 │ ┌────────────┴────────────┐ │ Add Norm │ │ Feed Forward │ │ Add Norm │ │ Multi-Head Attention │ ← Encoder 层重复 N 次 └──────────┬──────────────┘ │ 输入序列Embedding Positional Encoding关键左边是 Encoder编码器右边是 Decoder解码器。GPT 只用 DecoderBERT 只用 Encoder。3. 核心组件拆解7 个3.1 Self-Attention自注意力—— 最关键本质对序列中的每个词计算它和所有其他词的关联强度。Q、K、V 的形象理解数据库查询的类比 Query查询 我现在需要什么信息 Key键 我有什么信息可以提供给其他人 Value值 我的实际内容 计算过程 1. 每个词都有三个角色Q、K、V 2. 我 用 Query 去匹配所有词的 Key包括自己的 3. 匹配得分Q·K越大说明我越应该关注这个词 4. 用 Softmax 把得分归一化成概率 5. 用概率加权求和所有的 Value 6. 结果就是我在看完整句话后的理解公式Google 论文中的经典公式Attention(Q, K, V) softmax(Q·Kᵀ / √d_k) · V 为什么除以 √d_k → 防止 Q·K 的结果太大导致 Softmax 梯度消失 → d_k 是每个头的维度举个具体的例子句子那只猫追了老鼠它跑得很快 它是谁 → 人类理解根据上下文它很可能是猫或老鼠 → Self-Attention让它去关注所有词算出 它→猫的权重 0.65 它→老鼠的权重 0.30 它→其他词的权重 0.05 → 加权求和后它的表示包含了猫的信息 → 模型就知道了它指的是猫 这就是 Self-Attention 的指代消解能力。单头注意力计算图输入序列 X形状: 序列长度 × d_model │ ▼ ┌─ W_Q ─→ QQuery │ 各自做线性投影 ├─ W_K ─→ KKey │ └─ W_V ─→ VValue │ ▼ Q · Kᵀ → 原始注意力分数形状: seq_len × seq_len │ ▼ / √d_k → 缩放防止 Softmax 梯度消失 │ ▼ Softmax按行归一化每行和为 1 │ ▼ Softmax(QKᵀ/√d_k) · V → 加权求和形状: seq_len × d_k │ ▼ 输出每个 token 融合了所有 token 的信息的新表示3.2 Multi-Head Attention多头注意力本质同一个 QKV 计算做多次8 次或 16 次每次用不同的线性变换。为什么需要多头 → 语言中的关系是多维的 头 1关注语法关系主语→谓语 头 2关注语义关系动词→宾语 头 3关注位置关系相邻词 头 4关注长距离依赖句首→句尾 头 5-8关注其他隐含模式 → 一个头只能捕捉一种关系模式 → 多个头可以捕捉不同模式然后拼接起来 类比一个团队同时从 8 个角度分析同一句话 → 头 1 说它们的语法关系是这样的 → 头 2 说它们的语义关联是这样的 → ... → 最终综合所有视角得到完整的理解数学过程单个头的计算 head_i Attention(X·W_Q_i, X·W_K_i, X·W_V_i) 其中 W_Q_i, W_K_i ∈ ℝ^{d_model × d_k}, W_V_i ∈ ℝ^{d_model × d_v} 常见设置d_k d_v d_model / hh 是头的数量 拼接 投影 MultiHead(X) Concat(head_1, ..., head_h) · W_O 其中 W_O ∈ ℝ^{h·d_v × d_model} 形状变化 输入: X ∈ (seq_len, d_model) Q_i: (seq_len, d_k) ← 每个头只关注 d_k 维度 拼接后: (seq_len, h·d_k) ← 综合所有头的发现 投影后: (seq_len, d_model) ← 恢复成模型的主维度3.3 Positional Encoding位置编码本质因为 Transformer 同时看到所有词它不知道词的先后顺序。位置编码就是给每个词的位置打标签。问题 输入 我打你 和 你打我 如果不加位置信息模型看到的是完全相同的词集合 → {我, 打, 你}顺序丢了 解决方案正余弦编码 每个位置 pos 对应一个固定公式计算的向量 PE(pos, 2i) sin(pos / 10000^(2i/d_model)) PE(pos, 2i1) cos(pos / 10000^(2i/d_model)) 用正余弦的好处 ✓ 值域在 [-1, 1] 之间和 Embedding 值域匹配 ✓ 可以表示任意长度不受序列长度限制 ✓ 位置之间的相对关系可以用线性变换表示sin/cos 的数学性质 位置 1 的编码 [sin(1/10000^0), cos(1/10000^0), sin(1/10000^2/...), ...] 位置 2 的编码 [sin(2/10000^0), cos(2/10000^0), ...] 位置 k 的编码 [sin(k/...), cos(k/...), ...]现代变种RoPERotary Position EncodingLLaMA/GPT-NeoX/Qwen 等现代模型使用的不是传统的正余弦加法编码 而是 RoPE——对 Q 和 K 向量做旋转变换。 RoPE 的核心思想 不把位置信息加到 Embedding 上 而是通过旋转 Q 和 K 向量来注入位置信息 f(q, m) R_m · q ← 把 Query 旋转 m 度m 是位置 f(k, n) R_n · k ← 把 Key 旋转 n 度 然后计算注意力分数时 score(m, n) f(q, m)ᵀ · f(k, n) (R_m·q)ᵀ · (R_n·k) qᵀ · R_mᵀ · R_n · k qᵀ · R_(n-m) · k ← 只依赖于相对位置 (n-m) 这就是 RoPE 的核心优势 ✓ 注意力分数只取决于相对位置不取决于绝对位置 ✓ 外推能力强训练 2k推理时能到 8k ✓ 天然支持相对位置编码的优点3.4 Feed Forward Network前馈神经网络 / FFN本质注意力层之后对每个 token 独立做一次非线性变换。结构 FFN(x) ReLU(x·W₁ b₁)·W₂ b₂ 或者等效地现在常用的 SwiGLU 变体 FFN(x) (Swish(x·W₁) ⊙ (x·W₃))·W₂ 解读 注意力是词与词之间的互动 → 编码关联信息 FFN 是每个词独立的思考 → 编码知识信息 类比 注意力 开会讨论 → 了解同事说了什么 FFN 会后独立写总结 → 消化吸收、内化知识激活函数进化史ReLU (2017, 原始Transformer) │ ├── GELU (2020, GPT-3/BERT) │ f(x) 0.5x(1 tanh(√(2/π)(x 0.044715x³))) │ ├── Swish/SiLU (2021) │ f(x) x · σ(x) │ └── SwiGLU (2022, LLaMA/PaLM) ← 现在最主流 SwiGLU(x) Swish(x·W₁) ⊙ (x·W₃) · W₂ 需要 3 个权重矩阵多了 W₃参数量增加 50% 但效果显著更好一些数字模型d_modeld_ffn (intermediate)比例原始 Transformer Base51220484×BERT Base76830724×GPT-3 175B12288491524×LLaMA 3 8B4096143363.5×DeepSeek-V37168204802.86×FFN 参数占总参数约2/3最大的一块。3.5 Layer Normalization层归一化本质对每个 token 的表示向量做归一化加速训练稳定。Normalization 方式对比 Batch Norm批归一化CNN 常用 对一个 batch 内所有样本的同一特征维度做归一化 不适合 NLP序列长度变长/变短时统计量剧烈变化 Layer Norm层归一化Transformer 用 对一个 token 的所有特征维度做归一化 不依赖 batch 大小不依赖序列长度 适合 NLP 公式 LN(x) γ · (x - μ) / √(σ² ε) β 其中 μ、σ 是当前 token 向量的均值和标准差 γ、β 是可学习的缩放和偏移参数LayerNorm 的位置进化原始 Transformer (2017) x → LayerNorm → Attention → x → LayerNorm → FFN → x Post-NormLayerNorm 在残差连接之前 GPT-2 / 现代做法 (2019-) x → Attention → LayerNorm → x → FFN → LayerNorm → x Pre-NormLayerNorm 在 Attention/FFN 之前 Pre-Norm 为什么更好 Post-Norm 训练大模型时不稳定——残差路径上的信号被反复归一化 Pre-Norm 的残差路径更干净——归一化只影响子层内部 → 训练更稳定可以训练更深更大的模型3.6 Residual Connection残差连接本质把层的输入直接加到输出上。结构 x → LayerNorm → Attention → x原始输入 → LayerNorm → FFN → x ──残差连接──▶ ← 加法 ⊕ ──Attention──▶ 为什么需要残差连接 1. 梯度可以直接流过快捷通道到浅层 → 缓解梯度消失 2. 即使某一层学坏了原始信息也不会丢失 3. 网络可以更深从原始 Transformer 的 6 层 → 现在的 100 层 数学形式 output LayerNorm(x SubLayer(x)) 反向传播时梯度有两路可以走 ∂output/∂x ∂(x SubLayer(x))/∂x 1 ∂SubLayer(x)/∂x ↑ ↑ 直接通道 经过子层的通道可能梯度消失/爆炸但有1兜底3.7 Masked Self-Attention掩码自注意力只在 Decoder 中用。训练时不让当前词看到后面还没生成的词。语言模型生成时 我 爱 学习 生成爱的时候只能看到我不能看到学习 否则就是作弊——相当于提前看了答案 Mask 的实现 对每个位置把未来的位置的注意力分数设置为 -∞ 经过 Softmax 后这些位置的权重 ≈ 0 矩阵形式上三角为 -∞ 我 爱 学习 0 -∞ -∞ ← 我只能关注自己 0.7 0 -∞ ← 爱能关注我和自己 0.4 0.5 0 ← 学习能关注所有前面的词 这就是 Causal LM因果语言模型名字的由来——因果链不能先知。4. 数据流实例我爱学习通过 Encoder 全过程输入我爱学习 # Step 1: Tokenization Embedding token_ids [1287, 392, 561, 1893] # 4 个 token embeddings [[...], [...], [...], [...]] # 每个 512 维向量d_model512 # Step 2: Positional Encoding # 把位置信息加到 Embedding 上 embeddings embeddings positional_encoding # 形状不变 (4, 512) # Step 3: × N 层 Encoder Block for _ in range(6): # 原始 paper 是 6 层 # 3.1 Multi-Head Self-Attention8 个头 attention_input embeddings # (4, 512) Q attention_input · W_Q # 每个头是 (4, 64)8 个头并行 K attention_input · W_K # (4, 64) per head V attention_input · W_V # (4, 64) per head scores Q · Kᵀ / √64 # (4, 4) 注意力矩阵 weights softmax(scores) # (4, 4) 归一化权重 head_output weights · V # (4, 64) # 拼接 8 个头投影回 d_model concat concat([head_1, ..., head_8]) # (4, 512) attention_output concat · W_O # (4, 512) # 3.2 Residual LayerNorm embeddings LayerNorm(embeddings attention_output) # 3.3 FFN每个 token 独立 ffn_input embeddings hidden ReLU(ffn_input · W₁ b₁) # (4, 2048) 中间层 ffn_output hidden · W₂ b₂ # (4, 512) 恢复维度 # 3.4 Residual LayerNorm embeddings LayerNorm(embeddings ffn_output) # Step 4: 输出 # 最终 embeddings 中的每个 token 已经看过了所有其他 token 的信息 output embeddings # (4, 512) ← 我的向量包含了爱学习的上下文5. Encoder vs Decoder vs Encoder-Decoder架构代表模型结构使用场景注意力类型Encoder-OnlyBERT、RoBERTa只有 Encoder分类、序列标注、语义相似度双向 Self-Attention全可见Decoder-OnlyGPT、LLaMA、Qwen只有 Decoder文本生成、对话、代码生成因果 Masked Self-Attention只看左边Encoder-DecoderT5、BARTEncoder Decoder翻译、总结、文本到文本Encoder 双向 Decoder 交叉注意为什么 GPTDecoder-Only成为主流核心原因 1. 统一架构生成理解都能做ChatGPT 用同一个模型 2. Scale up 更稳定Decoder-Only 在更大规模下训练更稳定 3. 推理高效Encoder-Decoder 需要分别跑编码和解码成本更高 4. 生态效应GPT-3 证明了大力出奇迹 → 大家都跟6. 关键参数与模型规模以 LLaMA 3 8B 为例 d_model (hidden_dim) 4096 ← 每个 token 的向量维度 n_layers 32 ← Transformer 层数 n_heads 32 ← 注意力头数 n_kv_heads 8 ← GQA 的 KV 头数小于 Q 头数节省显存 d_ffn (intermediate) 14336 ← FFN 中间层维度约 3.5× d_model vocab_size 128000 ← 词表大小 max_seq_len 8192 ← 最大序列长度上下文窗口 总参数量 ≈ 8.03B 参数分布大致 Embedding 层 vocab_size × d_model ≈ 128k × 4096 ≈ 0.5B Attention 层 4 × d_model² × n_layers ≈ 4 × 4096² × 32 ≈ 2.1B FFN 层 2 × d_model × d_ffn × n_layers ≈ 2 × 4096 × 14336 × 32 ≈ 3.8B LayerNorm 其他少量 ≈ 0.1B 结论FFN 占了约一半以上的参数Attention 次之。常见模型参数汇总模型参数量d_modeln_layersn_headsd_ffn词表GPT-21.5B16004825640050kGPT-3175B1228896964915250kLLaMA 27B409632321100832kLLaMA 38B4096323214336128kQwen 2.57B4096282811008152kDeepSeek-V3671B (37B active)71686112820480128kBERT Base110M7681212307230k7. 为什么 Transformer 能工作的三点核心直觉1. Attention 让每个词都能看到整个句子的所有其他词 → 长距离依赖不再是问题RNN 的顽疾 → 无论猫和它隔多远注意力都能直接关联 2. 并行计算让超大模型成为可能 → RNN 必须逐时间步运行串行 → Transformer 一次处理整句话并行 → 同样的时间Transformer 训练了 100 倍的数据 3. 堆叠加深层数让抽象层次不断上升 → 底层学到词汇级别的模式学习是一个词 → 中层学到短语级别的模式在...学习是一个结构 → 高层学到句法和语义级别的模式虽然...但是...的关系 → 越深的层提取的语义越抽象8. KV Cache键值缓存详解8.1 问题推理时生了什么Decoder-Only 模型生成文本时是逐个 token生成的已生成的 我 爱 正在生成的 学第 3 个 token 生成学的时候模型需要 1. 把我的 Q、K、V 都算一遍 2. 把爱的 Q、K、V 都算一遍 3. 把学的 Q、K、V 都算一遍 4. 对所有位置的 Q·K 做注意力 下一步生成习 1. 把我的 Q、K、V 都算一遍 ← 重复了 2. 把爱的 Q、K、V 都算一遍 ← 重复了 3. 把学的 Q、K、V 都算一遍 ← 重复了 4. 把习的 Q、K、V 算一遍 5. 对所有位置做注意力发现问题了吗——每生成一个 token都要重新计算所有历史 token的 K 和 V。对于已经生成过的 token它们的 K 和 V 其实是不会变的因为 token 本身已经固定了权重 W_K / W_V 也固定了每次重新算就是浪费算力。8.2 KV Cache 的核心思想把已经算好的 K 和 V 缓存起来每次只算新 token 的 K 和 V然后拼到缓存后面。没有 KV Cache每次全部重算 生成第 t 个 token 时计算量 t × 2 × d_model × d_k ← 每步都线性增长 有 KV Cache只算新 token 生成第 t 个 token 时计算量 1 × 2 × d_model × d_k ← 常量 加速比 生成第 1000 个 token 时加速约 1000 倍Copy可视化对比没有 KV Cache Step 1: [我] → 算 Q₁K₁V₁ Step 2: [我, 爱] → 算 Q₁K₁V₁ Q₂K₂V₂ ← 重复算了 Q₁K₁V₁ Step 3: [我, 爱, 学] → 算 Q₁K₁V₁ Q₂K₂V₂ Q₃K₃V₃ ← 重复算了 Q₁K₁V₁ Q₂K₂V₂ Step 4: [我, 爱, 学, 习] → 算 Q₁K₁V₁ Q₂K₂V₂ Q₃K₃V₃ Q₄K₄V₄ ... 总计算量 1 2 3 ... N N(N1)/2 ← O(N²) 有 KV Cache Step 1: [我] → 算 Q₁K₁V₁, 缓存 K₁V₁ Step 2: [我, 爱] → 缓存中有 K₁V₁, 只算 Q₂K₂V₂, 追加缓存 K₂V₂ Step 3: [我, 爱, 学] → 缓存中有 K₁V₁K₂V₂, 只算 Q₃K₃V₃, 追加缓存 K₃V₃ Step 4: [我, 爱, 学, 习] → 只算 Q₄K₄V₄, 追加缓存 ... 总计算量 1 1 1 ... 1 N ← O(N)8.3 KV Cache 的具体实现Forward Pass 的变化# 没有 KV Cache训练模式 def forward(x): # x 的形状: (batch, seq_len, d_model) Q x W_Q # (batch, seq_len, d_k) K x W_K # (batch, seq_len, d_k) V x W_V # (batch, seq_len, d_v) attn softmax(Q K.T / sqrt(d_k)) V return attn # 有 KV Cache推理模式 def forward_with_cache(x, past_KNone, past_VNone): # x 的形状: (batch, 1, d_model) ← 每次只输入 1 个 token Q x W_Q # (batch, 1, d_k) ← 新 token 的 Q K x W_K # (batch, 1, d_k) ← 新 token 的 K V x W_V # (batch, 1, d_v) ← 新 token 的 V # 拼接到历史的 KV 缓存 if past_K is not None: K torch.cat([past_K, K], dim1) # (batch, t, d_k) V torch.cat([past_V, V], dim1) # (batch, t, d_v) # Q 只有新 token 的K 和 V 是全部历史 attn softmax(Q K.T / sqrt(d_k)) V # Q: (1, d_k), K: (t, d_k) # 输出形状: (batch, 1, d_v) ← 只输出当前 token 的结果 return attn, K, V # 返回 K、V 给下一步使用8.4 KV Cache 的显存消耗KV Cache 用计算换显存——它省了计算量但消耗了大量显存。计算公式单层、单头 每个 token 的 KV cache 2 × d_k × 每个值的字节数 每层 2 × n_heads × d_k × tokens 所有层 2 × n_heads × d_k × tokens × n_layers 实际例子LLaMA 3 8BFP16 推理 每层 n_heads 32, d_k 1284096/32 每层 KV cache 2 × 32 × 128 × tokens × 2 bytes 16,384 × tokens bytes 32 层 总 KV cache 32 × 16,384 × tokens 524,288 × tokens bytes 推理 4096 tokens 时 KV cache 占用 524,288 × 4096 2,147,483,648 bytes ≈ 2 GB | 序列长度 | KV Cache 显存FP16 | KV Cache 显存FP8 | |:--------:|:--------------------:|:-------------------:| | 1k | ~0.5 GB | ~0.25 GB | | 4k | ~2 GB | ~1 GB | | 8k | ~4 GB | ~2 GB | | 32k | ~16 GB | ~8 GB | | 128k | ~64 GB | ~32 GB | 结论KV Cache 是长上下文推理的主要显存瓶颈。 这就是为什么 7B 模型在 16GB 显存上最多跑 8k 上下文—— 模型本身只占 ~14GBFP16KV Cache 再占 4GB加上中间变量刚好撑满。8.5 KV Cache 的优化技术① GQAGrouped Query Attention核心思想减少 K/V 头的数量但保持 Q 头的数量不变。MHA标准多头注意力 Q 头数 32, K 头数 32, V 头数 32 KV Cache 32 × d_k × tokens × 2 × layers GQA分组查询注意力如 LLaMA 3 Q 头数 32, K 头数 8, V 头数 8 KV Cache 8 × d_k × tokens × 2 × layers MHA 的 1/4 MQA多查询注意力如 Falcon Q 头数 32, K 头数 1, V 头数 1 KV Cache 1 × d_k × tokens × 2 × layers MHA 的 1/32 | 架构 | K/V 头数 | KV Cache 节省 | 质量损失 | |------|:--------:|:-------------:|:--------:| | MHA | h | 0% | 无标准 | | GQA | h / g | 75%g4 | 极小几乎无 | | MQA | 1 | 97% | 明显特定任务 | GQA 是目前的最佳实践——平衡了 KV Cache 节省和模型质量。② 共享 KV Cache跨层现象Transformer 不同层的注意力模式有些相似 → 相邻层的 KV 可能差不多 方案 层 A 和层 B 共享同一个 K/V 投影 只缓存一份 K/V两层都用 节省30-50% 的 KV Cache 空间 代表ALBERT跨层参数共享部分小模型③ 窗口注意力 滑动缓存假设上下文已经 128k tokens 了 全部缓存 → KV Cache 太大 方案只保留最后 N 个 token 的 KV Cache 窗口大小 4096 只缓存最近的 4096 个 token 更早的 token 的 KV 被丢弃 适用场景 - 局部依赖为主的任务如对话 - 对长距离依赖要求不高的场景 不适用 - 需要检索长上文的任务如长文档问答 变种StreamingLLM 在窗口前保留 Attention Sink tokens 代表Mistral、Qwen 2.5 的部分推理优化④ 量化 KV CacheFP16 的 KV Cache: 每个值 2 字节 FP8 (E5M2/E4M3): 每个值 1 字节 ← 节省一半 INT4: 每个值 0.5 字节 ← 节省 75% 实际效果 8k 上下文下KV Cache 从 4GB → FP8 2GB → INT4 1GB 质量损失FP8 几乎无损INT4 在某些任务上有 1-2% 下降8.6 KV Cache 的多轮对话陷阱场景多轮对话 用户 助手 ┌────────────────────────────────────┐ │ 你好 → 你好有什么帮你的│ ← 第 1 轮 │ 解释一下Attention → 注意力机制是... │ ← 第 2 轮 │ 再详细一点 → 具体来说... │ ← 第 3 轮 └────────────────────────────────────┘ 没有 KV Cache → 每轮对话都从零开始重新算 → 浪费第 1、2 轮的内容每次都重算 有 KV Cache → 第 1 轮缓存 (user: 你好, assistant: 你好有什么...) → 第 2 轮第 1 轮的 KV Cache 还在只算 user 新输入和 assistant 新生成 → 第 3 轮前两轮的 KV Cache 都还在只算新的 问题 如果用户中途删除了上一条消息比如等一下我刚说错了 → 必须重建 KV Cache → 这是为什么消息撤回在多轮对话中很慢的原因8.7 总结KV Cache 的本质KV Cache 是以显存换算力的典型工程优化 代价消耗显存7B 模型 8k 上下文约 2-4 GB 收益推理速度提升 O(N²) → O(N) 在实际推理系统中 1. 推理引擎vLLM / TensorRT-LLM都内置 KV Cache 管理 2. 需要做 PagedAttentionvLLM 的核心创新来解决KV Cache 碎片化问题 3. 长上下文32k时KV Cache 是主要瓶颈需要配合 GQA 量化 滑动窗口