第 1 课:Transformer 回顾

用你熟悉的 BERT 作为锚点,一步步理解 Self-Attention 做了什么,以及为什么 GPT 必须走 Decoder-only 路线。

🎯 学完这 6 步你将能够:
① 用自己的话推导 Self-Attention 的计算流程(Q·KT → Softmax → 加权 V)
② 说清为什么生成任务必须用 Causal Mask,以及它怎么把 BERT 和 GPT 分成两条路
③ 向别人解释「RNN 到底哪里不行」并用具体数字举例,而不是背概念
🗺 本节推理链(6 步:任务约束 → RNN 撞墙逼出 Self-Attention(含 Softmax 加权)→ Causal Mask → BERT 与 GPT 分岔 → Multi-Head → KV Cache,即生成时把已算过的键和值存下来复用、不必每步重算)
1 任务约束:跨任意距离传信息,且能并行 — 语言建模要求每个位置能直接读到前面任意远的 token(长距离依赖),又不能按时间步串行。用「我把昨天在图书馆借的那本书还了」试:句首的"把"和句尾的"还"相隔 12 个字,信息必须跨过这 12 个位置传过来。
↓
2 先试 RNN → 撞墙,逼出 Self-Attention — RNN 用一张被反复覆盖的 hidden state 记笔记,读得越远开头的"把"越模糊,而且只能按时间步串行,两条约束都破。于是让每个位置各自投影出 query / key,点积一次得到 n×n 的分数矩阵,再做 softmax 归一化(不用 argmax:不可导;÷√d_k 防饱和)并对 value 加权求和,任意两位置直连、可并行。
↓
3 Causal Mask:生成时只能看左边 — 自回归于是只允许看左侧,因此对分数矩阵上三角填 -∞,得到的可见范围只含左下角
↓
4 BERT 与 GPT 分岔:可见范围不同,训练目标就不同 — 同一个架构只因"要不要挡未来",所以分成 BERT(不挡,双向,MLM 完形填空,擅长理解)与 GPT(必须挡,单向,自回归,擅长生成)两条路,这就决定了各自的attention 权重能看到哪些位置
↓
5 Multi-Head:一个头不够用 — 无论 BERT 还是 GPT,前面算的都是单个头:一个词只有一组 attention 权重,表达不了"苹果-甜"(属性)与"苹果-买"(动作)等多种关系,这就要求并行开 h 个 head、各自独立投影,每头一组 每个 head 一组 K/V
↓
6 KV Cache:生成时别重算 — 因为 Causal Mask 让旧 token 的 K/V 不会随新 token 改变,所以每一步都可以复用已算过的 每个 head 一组 K/V,不必重算;KV Cache 大小 = 2 × 层数 × KV 头数 × d_k × 序列长 × 每个数的字节数
Self-Attention Q×K^T Causal Mask Encoder vs Decoder BERT GPT
1
RNN 为什么记不住远处的词?
提示:数一数句首的"把"和句尾的"还"隔了多少个字
▾
知识背景
RNN(Recurrent Neural Network)的标准形态由 Elman 于 1990 年提出,称为 Simple Recurrent Network(更早的循环结构可追溯到 Jordan 1986);LSTM(Long Short-Term Memory)由 Hochreiter & Schmidhuber 于 1997 年提出,用门控机制缓解了短期记忆问题,但串行瓶颈仍在。2017 年之前,RNN/LSTM 是 NLP 的默认架构,广泛用于机器翻译、语音识别。

先看一个任务:语言建模(language modeling)——给定前文预测下一个 token(分词后的最小单位,可以是字、词或 subword)。用「我把昨天在图书馆借的那本书还了」走一遍:句首的"把"和句尾的"还"构成同一个"把…还了"结构,两者相隔 12 个字("把"在第 2 位、"还"在第 14 位)——信息必须跨过这 12 个位置传过来。① 相隔任意远的两个位置必须能直接交换信息,即建模长距离依赖;② 不能按时间步串行计算,必须可并行。这两条就是我们要满足的任务约束。

先用 RNN(循环神经网络)撞一下墙。它的工作方式:从左到右,一个词接一个词地读。"我" → 更新隐藏状态 → "把" → 更新隐藏状态 → ...(隐藏状态 = RNN 用来记住"前面所有词"的那个向量。)RNN 的隐藏状态每步都被整体重写(像只用一张便签纸记笔记),读到句尾开头的信息早被擦没了;而且它必须按时间步一个个算。这两点正好同时踩中上面两条约束。在 2017 年之前,处理句子的主力就是 RNN 及其门控版本 LSTM。

打个比方:RNN 读句子像一个人只用一张便签纸做笔记——每读一个词就把便签擦掉重写,便签上只剩当前这一行;读到句尾,开头记的细节早被擦没了。后面所有"遗忘"和"消失"的毛病,都源于这一张便签。

RNN 的两个致命问题(一前一后):

① 前向:hidden state 被反复覆盖,远处信息留不住。每读一个新词,整个隐藏向量就被重写一次——像一张便签纸擦了又写,读到句尾时开头的细节早被挤没了。这踩中了第一条约束(长距离依赖)。

② 反向:梯度消失,开头学不到。训练时要反向传播(把误差从输出端往回传、据此调参数)。梯度沿时间步往回传,每传一步就乘一个小于 1 的数,传到开头几乎为零——模型根本看不到句首在说什么。这进一步加剧了远距离遗忘:即便信息勉强传到句尾,梯度也回不到句首去调参数。

③ 串行慢。第 100 个词必须等前 99 个词全算完。GPU 有几千个核心,RNN 的时间步却只能一个一个来——并行度用不上。这踩中了第二条约束(必须可并行)。

🧮 具体感受一下"梯度消失"
Step 1:设每步梯度乘 0.9,看 1 / 10 / 50 步后的残余
步数梯度残余含义
10.9还行
100.9¹⁰ = 0.35不到一半了
500.9⁵⁰ = 0.005几乎为零,模型学不到任何东西
Step 2:50 步后只剩 0.005,等于断了
这就是为什么 RNN 处理长句子时,开头的信息根本学不到——梯度在路上"饿死了"。
🎯 留下什么问题

现在我们知道必须同时满足:任意两位置直连 + 计算能并行。

下一个问题是:这两条要求,具体该用一个什么样的算式实现?

AI 助教
👋 我是你的课程助教。对这张卡片(RNN 的局限性和 Self-Attention 的动机)有任何疑问都可以问我。

我会用具体数字和计算例子来回答,而不是堆砌概念。
2
要让相隔 12 个位置的两处直接比对,该怎么算?
提示:要一次算出所有位置对
▾
知识背景
Self-Attention 出自 "Attention Is All You Need"(Vaswani et al., Google, 2017)。"Self" 的含义是 Q、K、V 都来自同一个序列(区别于 cross-attention:Q 来自一个序列、K/V 来自另一个序列)。Q/K/V 的术语源于信息检索:Query(你在搜什么)、Key(索引)、Value(实际内容)。今天主流的 LLM 都以 Self-Attention 为核心组件。

上一张卡片看到 RNN 的 hidden state 每读一个词就被整体覆盖一次,开头的信息留不住。要解决它,最直接的做法是让每两个位置之间都算一个"像不像"的分数——n 个 token 就是 n×n 张分数表。

Self-Attention 的核心:让每个词都去"查"一遍所有其他词,看它们和自己有多相关。

用图书馆打个比方:Q 是你手里的索书号,K 是每本书书脊上的分类标签,V 是书页里的正文——拿 Q 去和每一张 K 比对,谁最像就把谁的 V 借走。句子里的每个词同时扮演这三种角色。

Q(Query) = "我在找什么?"  K(Key) = "我有什么可以提供?"  V(Value) = "如果选了我,我给你什么信息?"

为什么不直接用词向量本身两两算点积、非要投影出 Q、K?因为词向量的相似度是固定的——不管任务是翻译还是问答,「苹果」和「甜」的分数都一样,模型没法按任务调;投影成 Q、K 之后,同一对词的分数可以被训练成任意大小(本例 Q_我·K_吃 = 0.16 就是这样算出来的)。

🧮 完整计算走查:句子 = ["我", "吃"],维度简化为 3(Q/K/V 再经 W 矩阵投影为 2 维;以下数值为演示用示意值)
Step 1:输入 Embedding
词d₁d₂d₃
"我"0.50.30.8
"吃"0.20.90.4
Step 2:乘 W^Q、W^K、W^V,得到 Q、K、V(简化)
词QKV
"我"[0.6, 0.2][0.4, 0.7][0.8, 0.3]
"吃"[0.3, 0.9][0.1, 0.5][0.6, 0.2]
Step 3:注意力分数 = Q × K^T
"我"看清楚自己:Q_我·K_我 = 0.6×0.4 + 0.2×0.7 = 0.24+0.14 = 0.38
"我"看清楚"吃":Q_我·K_吃 = 0.6×0.1 + 0.2×0.5 = 0.06+0.10 = 0.16
注意力矩阵 = 0.38 0.16
0.75 0.48
Step 4:÷√d_k(这里 d_k=2,√2≈1.41)→ Softmax(把分数折成权重)
"我"看"我":0.38/1.41=0.27,"我"看"吃":0.16/1.41=0.11
Softmax(0.27, 0.11) → [0.54, 0.46](注意力权重,这一行两个权重和为 1)
为什么先要把分数折成"和为 1 的权重":分数只表示"像不像",不是内容;就像两个人合伙出资,不能按出资的绝对金额直接分账,得先折成各自的占比。折成占比有好几种做法,Transformer 选了 softmax:它对任何分数(包括负分)都给出合法权重,而且处处可导;argmax 虽然也能"选一个",但它不可导,梯度传不回去。
Step 5:用权重加权求和 V
"我"的新表示 = 0.54×[0.8, 0.3] + 0.46×[0.6, 0.2]
    = [0.43+0.28, 0.16+0.09]
    = [0.71, 0.25] ← 这个向量同时包含了"我"和"吃"的信息

关键洞察:所有词的计算是同时进行的——这就是为什么 Self-Attention 能并行:串行步数从 RNN 的 O(n) 降到 O(1)。总计算量并不一定更小——原论文 Table 1 给出每层复杂度,Self-Attention 是 O(n²·d),RNN 是 O(n·d²):n 小于 d 时前者更省,n 远大于 d 时反而更贵(例如 d=512:n=100 时前者约为后者的 0.2,n=1000 时约为 1.95 倍)。

🎯 Q、K、V 的本质角色

Q(Query) = 每个词在问"谁和我最相关?"
K(Key) = 每个词在答"我是什么,来找我"
V(Value) = "如果真的选了我,我给你这些信息"

这种"查询-索引-取值"的三步机制,让 Self-Attention 的每条信息路径长度恒为 1——不管两个词距离多远,梯度都能一步直达。这就是它在"长距离依赖"上明显优于 RNN 的核心原因。

🎯 不除 √d_k 会怎样

以原论文 base 的每头维度 d_k=64(d_model=512、8 个头)为例,两个独立随机向量的点积,标准差是 √d_k=√64=8(均值为 0);每头维度越大,点积的方差越大。不缩放的话,Softmax 的输入量级太大 → 输出极度尖锐(≈one-hot)→ 梯度趋于零,模型学不动。

两个分数Softmax 权重较大那项的梯度 p(1−p)
[1, 0](缩放后,量级≈1)[0.73, 0.27]0.197
[8, 0](不缩放,量级≈8)[0.9997, 0.0003]0.000335,缩小约 587 倍

前提是 q、k 各分量独立、均值 0、方差 1,点积方差才恰好是 d_k。除以 √d_k 把量级拉回 1 附近。这就是一根"保险丝"——d_k 小时无害(本卡走查的小例子里 d_k=2),统一保留。

AI 助教
👋 对 QKV 计算有疑问?比如:"为什么是 Q×K^T 而不是别的运算?""Softmax 换成别的激活函数会怎样?"

我会用具体数字帮你走一遍。
3
生成时不允许看后面的词,这个限制加在哪一层最自然?
提示:回头看上一张卡片,哪个量决定了"谁能看到谁"
▾
知识背景
Causal Mask 中的 "Causal" 指因果——原因必须在前、结果必须在后。GPT(Radford et al., OpenAI, 2018)把「因果掩码 + Decoder-only」确立为生成式预训练范式(规模化验证由后续 GPT-2/GPT-3 完成;因果掩码本身在 2017 年原论文的 Decoder 里就已使用)。这一机制让模型不能"偷看"未来的词——是 Decoder-only 架构(GPT)和 Encoder-only 架构(BERT)分道扬镳的关键。

上一张卡片算出了一张 n×n 的分数矩阵(attention 分数)。但生成有个前提:模型从左往右吐词,生成"我"的时候"爱"还不存在。所以矩阵里"列号大于行号"的格子(上三角)必须作废——填成 -∞,Softmax 之后自然是 0。

这就像闭卷考试:练习时若允许翻书(看到未来),上考场却只能凭记忆,成绩必然崩,所以练习就得按考场规则来。

训练一个"理解"任务时,双向注意力没问题——"苹果很甜"中,"甜"确认了"苹果"是水果。

但生成时完全不同。模型从左往右逐 token 生成,生成"我"的时候,"爱"还不存在。如果训练时允许模型"偷看"未来 token,因为整句话是并行喂进去的,每个位置都能直接读到自己要预测的答案,学到的只是照抄;上线做生成时,右边没有答案可抄,这个本事就用不上了。

🧮 Causal Mask 怎么加的?
句子 = ["我", "爱", "你"],正常 Q×K^T 矩阵(构造的示意值):
"我""爱""你"
"我"0.50.30.1
"爱"0.20.60.4
"你"0.10.30.7
"我"能看到"爱"(0.3)和"你"(0.1)——训练时偷看了未来!
加上 Causal Mask(上三角 = "列号大于行号"的那些格子 → -∞):
"我""爱""你"
"我"0.5-∞-∞
"爱"0.20.6-∞
"你"0.10.30.7
Softmax 之后(e^(-∞) = 0,其余正常归一化):
"我""爱""你"
"我"1.000
"爱"0.400.600
"你"0.250.300.45
✅ "我"只能看到自己,"爱"能看到"我"和自己,"你"能看到前面所有。因果链成立。
🎯 这个限制带来的分岔

同一个架构,只因"要不要挡未来",就分成两条路:不挡未来的叫 BERT(双向 Encoder),必须挡的叫 GPT(单向 Decoder)。下一张卡片会把这两条路的训练方式、分工逐项对比。

🎯 -∞ 与 0 的差别:Softmax 之后各是什么

填 0 → Softmax 后 = e⁰/(...) ≠ 0,被 mask 的词仍有残留权重。

填 -∞ → Softmax 后 = e^(-∞)/(...) = 0/(...) = 0,真正"完全忽略"。

AI 助教
👋 对 Causal Mask 有疑问?比如"训练时也要 mask 吗?""为什么推理时不加 mask 也能跑?"

我会用矩阵数字走一遍。
4
同一个 Transformer,为什么会分化出 BERT 和 GPT 两条路线,各自擅长什么?
提示:想想预训练时它们各自的"考题"是什么样的
▾
知识背景
BERT = Bidirectional Encoder Representations from Transformers(Devlin et al., Google, 2018)——双向 Encoder,MLM 训练,擅长理解任务。GPT = Generative Pre-trained Transformer(Radford et al., OpenAI, 2018)——单向 Decoder,自回归训练,擅长生成任务。源于同一篇 Transformer 论文(2017),走向了两条路线。GPT 这条 Decoder-only 路线后来放大到 GPT-3 的 175B 参数(Brown et al., 2020);BERT 这条路线上的 DeBERTa(扩大到 1.5B 参数的版本)在 SuperGLUE 基准上首次超过了人类基线(He et al., 2020,1.5B 结果见 2021 年 1 月的 v2)。

上一张卡片看到:Causal Mask 在分数矩阵的上三角填 -∞,Softmax 之后变成 0——每个词只能看到自己和左边的词。这个"挡不挡未来"的选择,让同一个 Transformer 自然分成了两条路线:因为训练时能看到什么,决定了模型学会做什么,所以训练目标不同,擅长的任务就不同。

就像两种考生的分工:一个负责阅读理解(前后文一起看,判断"苹果很甜"是不是在说水果),一个负责续写作文(只能看已经写出来的部分)。前者对应 BERT,后者对应 GPT;真正的区别在下面这张表里的训练方式。

Encoder-only(BERT)Decoder-only(GPT)
注意力(attention)双向(看全部)单向(←只看左边)
训练目标MLM:随机遮15%的词,猜被遮住的是什么自回归:预测下一个词
擅长理解:分类、实体识别、语义匹配生成:续写、翻译、对话、写代码
代表模型规模BERT-base 110M、BERT-large 340M(2018)GPT-3 175B(2020);两者差距主要来自年代与训练预算,不是架构本身

为什么 BERT 不适合直接生成?

BERT 训练时习惯看到整句再判断。让它生成,就只能从左往右逐 token 地来——而 BERT 从来没被训练过"在只知道前半句的情况下猜下一个词"。

GPT 从预训练第一天起就在做这件事:只看左边,猜右边。这不是能力高低,而是训练范式决定的分工。

🧮 同一句话,两种训练——BERT 为什么不会生成
Step 1:BERT 的训练(MLM)——句子"苹果很甜"(示意:按词切成 苹果 / 很 / 甜 共 3 个 token,实际切分取决于分词器)
输入 = "苹果 [MASK] 甜" → 预测被遮住的"很"时,模型左边能看到 苹果,右边能看到 甜 = 双向上下文
BERT 学到的是:"我有左右两边的词,我猜中间被遮住的词"
Step 2:GPT 的训练(自回归)——每个位置只预测下一个词
步骤输入预测可用信息
1苹果很仅"苹果"(左边 1 个词)
2苹果 很甜仅"苹果 很"(左边 2 个词)
每步只看到已有的词,右边的词不可见。GPT 学到的是:"我只知道前面的词,我要猜下一个词"
Step 3:每次训练"教"了多少个位置?(以 40 个 token 的句子为例)
被监督的位置数怎么算
BERT(MLM)约 6 个遮 15%:40 × 0.15 = 6
GPT(自回归)39 个除第 1 个词外,每个位置都在预测它的下一个词:40 − 1 = 39
关键:BERT 训练时永远有双向上下文,所以让它"只从左边猜右边"是它没学过的事。这不是能力问题,是训练范式问题。
🎯 推荐系统为什么常用 BERT 系做 Embedding?

因为 Embedding 本质是理解任务——要把整段商品描述压缩成一个向量。双向注意力能同时利用上下文所有信息,而且 BERT-base 只有 110M 参数,比动辄 1B 以上的 Decoder-only 模型小得多,在需要大批量编码的召回场景里算得起。

而对话产品需要逐 token 生成回答,用的是 Decoder-only。没有谁更好,只是服务于不同目的。

AI 助教
👋 对 BERT vs GPT 的路线选择有疑问?比如"Encoder-Decoder(T5)为什么没有成为主流?"
5
多开几个注意力头,究竟比单头多看到了什么?
提示:挑一个词,看它同时和哪几个词发生关系
▾
知识背景
Multi-Head Attention 同样出自 "Attention Is All You Need"(2017)。原论文设 h=8 个头,每头 d_k=64。与 CNN 的多卷积核同源:不同头捕捉不同关系模式。现代 LLM 的头数随规模变:Llama 2 7B 是 32 头(隐藏维度 4096、每头 128 维),Qwen2.5-7B 是 28 头(隐藏维度 3584、每头仍是 128 维),Llama 2 70B 是 64 头。

上一张卡片看到 BERT 与 GPT 的差别在"能看到哪些位置";但前几张卡片算的一直是单个头的注意力:一个词对其他词只有一组 attention 权重,所有关系被迫压进同一张矩阵。(BERT 与 GPT 的每一层其实都是多个头并行,本卡就来拆开看为什么。)"苹果"和"甜"(属性)、"苹果"和"买"(动作)、"苹果"和"斤"(数量)是三种异质关系,一组权重表达不了。

就像多个人同时读同一句话:一个人盯语法、一个人盯语义、一个人盯数量,最后把各自的笔记合并成一份更完整的理解。

"苹果很甜,我昨天买了三斤"——"苹果"既和"甜"有关(属性),又和"买"有关(动作),还和"斤"有关(数量)。一个头很难同时捕捉三种异质关系。

那能不能干脆不分组,只把单头加宽(每头维度由 64 提到 128)?不能——加宽只是把同一张打分表放大,Softmax 之后仍然只有一行权重;要同时表达「属性 / 动作 / 数量」三套彼此独立的分布,就必须真的开出三个头,让它们各持一张打分表。

🧮 Multi-Head 的计算本质
Step 1:按原论文口径,d_model=512、h=8 个头
每头分到 512/8 = 64 维(与知识背景里的 d_k=64 一致)
Step 2:每头独立算,再拼回 512 维
每头独立算 Attention(64 维的 Q、K、V)
8 个头各自输出 64 维 → 拼接回 64×8 = 512 维
最后过一个线性层 W_O,把拼接结果再融合一次
总计算量和单头一样(每头维度缩小了),但能学到 8 种不同的"注意力模式"。
🧮 具体对比:单头的弱点 vs 多头的专业化(权重为示意值)
假设句子:"苹果很甜,我昨天买了三斤"——"苹果"同时和"甜"(属性)、"买"(动作)、"斤"(数量)有三种关系。
单头:只能用一个权重分布表达所有关系
苹果→苹果(自)甜(属性)买(动作)斤(数量)
权重0.180.220.270.33
"甜"(属性)只分到 0.22,"斤"(数量)分到 0.33——三种关系挤在一个分布里,互相稀释。
8 个头:每个头可以专注一种关系(模拟其中 3 个头的权重)
头功能苹果(自)甜(属性)买(动作)斤(数量)
#1属性0.100.550.150.20
#3动作0.080.100.620.20
#6数量0.050.080.150.72
头#1 把 55% 注意力给"甜",头#3 把 62% 给"买",头#6 把 72% 给"斤"——每种关系都有专属的"专家"。单头永远做不到这种专业化。
🎯 头数多了,K/V 的存储成本也跟着涨——分组共享 K/V 怎么折中

每个 head 都要存一份 K/V,头数越多,生成时要缓存的 K/V 越大(下一张卡片会看到这笔账)。GQA(Grouped Query Attention,分组查询注意力)的做法是:保留全部 Q 头,但让每组 Q 头共享同一份 K/V。若 Q 头有 h 个、K/V 分成 g 组,缓存就降为原来的 g/h。GQA 论文(Ainslie et al., 2023)报告:已训练好的 MHA 模型,只花 5% 的原训练算力就能改成 GQA。改完后质量接近 MHA,速度接近 MQA(Multi-Query Attention,所有 Q 头只共用 1 组 K/V)。

旁证是头确实有冗余:Voita 等人 2019 年在英俄翻译模型的 encoder 上剪头,48 个剪掉 38 个,BLEU(机器翻译标准评测指标,分数越高越好)只掉 0.15——但那是整个头被删掉,GQA 是保留所有 Q 头、只共享 K/V,二者机制不同,这里只能说"头冗余使共享成为可能"。Llama 2 70B 起、Llama 3 全系列及 Mistral、Qwen 采用 GQA;Llama 2 的 7B/13B 仍是 MHA(Multi-Head Attention,标准多头注意力)。

AI 助教
👋 对多头注意力有疑问?比如"能不能可视化不同头学到了什么?""减少头数会损失多少性能?"
6
解码时什么被重复计算了?
提示:看每一步都要用到前面哪些 token 的什么
▾
知识背景
自回归(Autoregressive)含义:用过去值预测未来值。在语言模型中:P(w_t | w_1...w_{t-1})。从 n-gram(1950s)→ 神经语言模型(Bengio et al., 2003)→ GPT(2018)。主流 Decoder-only LLM(如 GPT、Llama)都基于自回归生成——上一步输出成为下一步输入。

上一张卡片看到每一层有多个 head、每个 head 一组 K/V。进入生成阶段后,每一步都要用到前面所有 token 的 K/V——如果每一步都从头算一遍,同样的 K/V 就被反复重算。

🧮 完整走一遍:GPT 生成 "我爱吃苹果"
初始:输入 = [BOS]
BOS = Begin of Sequence(开始符,告诉模型"从这儿开始生成");配套的 EOS = End of Sequence(结束符,模型输出它就表示"这句话说完了")
第1步:输入=[BOS] → 预测(以下概率为示意值)
候选:我(0.32), 今天(0.18), 请(0.09)... → 采样选中"我"
第2步:输入=[BOS,"我"] → 预测
候选:爱(0.41), 想(0.22), 喜欢(0.15)... → 选中"爱"
第3步:输入=[BOS,"我","爱"] → 选中"吃"
第4步:输入=[BOS,"我","爱","吃"] → 选中"苹果"
第5步:输入=[BOS,"我","爱","吃","苹果"] → 预测到 EOS → 停止
✅ 输出:"我爱吃苹果"
步数当前输入预测概率本步 K/V 投影量(不含Cache)
1[BOS]我0.321×d
2[BOS,我]爱0.412×d
3[BOS,我,爱]吃—3×d
4[BOS,我,爱,吃]苹果—4×d
5[BOS,…,苹果]EOS—5×d
这里只数 K/V 投影的次数:无 KV Cache 时累计 = (1+2+3+4+5)×d = 15d,有 KV Cache 后 = 5d(每步只为新 token 算一次)。K/V 投影省了 67%。(注意力打分本身每步仍要和全部旧 token 比,所以整体计算省得没这么多;该比例随序列变长而升高:100 步时 K/V 投影约省 98%,见本卡追问 5)

关键洞察:把已经算过的 Key 和 Value 存下来、下一步直接复用,这就是 KV Cache(Key-Value Cache,键值缓存)。每一步只需要为新 token 算一组新的 K/V,再和缓存里的旧 K/V 拼起来做注意力。打个比方,就像读书时在书页上做过的批注不用每翻一页都重做一遍;回到术语,缓存下来的就是前面所有 token 的 K 和 V。

它之所以成立,靠的是 Causal Mask:旧 token 看不到未来,所以新 token 的出现不会改变旧 token 的 K/V,缓存下来的值永远有效。如果像 BERT 那样双向可见,新增一个词会改变所有旧词的表示,缓存就失效了。

🔗 连接你的推荐经验

BERT 做 Embedding = encode 一次,拿向量就用。GPT = 逐 token 生成。

但 GPT 也能做 Embedding——最朴素的做法是取最后一个 token 的 hidden state 作为整个序列的表示;而真正把 Decoder-only 改造成 Embedding 模型的 LLM2Vec、GTE-Qwen、NV-Embed 都各自做了额外改造(LLM2Vec 加双向注意力改造 + 池化,GTE-Qwen 走指令微调,NV-Embed 用 latent 池化——都不是单纯"取最后一个 token")。(注意区分:E5 原始版基于 BERT 式双向编码器,BGE 基于 BERT 编码器 + CLS 池化,都不是 Decoder-only 取 last-token 的做法。)Decoder-only 正在"侵蚀"原本属于 BERT 的领地。

AI 助教
👋 对自回归生成有疑问?比如"采样和贪心解码有什么区别?""temperature=0.7 和 temperature=0 的区别是什么?"