从 GPT-2 到今天:骨架没变
这本书拆了十八章的 GPT-2,一台 2019 年的模型。一个合理的担心是:这些东西放到今天还算数吗?这一章不靠印象回答,直接去 Hugging Face 上下载几个当下主流开源模型的配置文件(config.json,几 KB 的文本,写着模型每一个结构参数),和 GPT-2 的那份逐行对照。你会看到一副没变的骨架,和五个被换掉的零件。
Meta 在 2024 年发布的 Llama 3.1 8B,上下文窗口是 131072 个 token(128K)。第 15 章说过,生成时每个 token 都要在每一层存一份键和值(KV 缓存)。
问:把 128K 的窗口用满,一段对话的 KV 缓存大约多大?(按常用的半精度,每个数 2 字节。)
答案在本章末尾。
五份配置文件
下面是五份真实的 config.json 里读出来的数(GPT-2 是 OpenAI 2019 年发布的;Llama 3.1 8B 是 Meta 2024 年;Qwen3 两个是阿里巴巴 2025 年;DeepSeek-V3 是深度求索 2024 年底):
| GPT-2 small | Llama 3.1 8B | Qwen3-8B | Qwen3-30B-A3B | DeepSeek-V3 | |
|---|---|---|---|---|---|
| 层数 | 12 | 32 | 36 | 48 | 61 |
| 总线宽度 | 768 | 4096 | 4096 | 2048 | 7168 |
| 注意力头 / 键值头 | 12 / 12 | 32 / 8 | 32 / 8 | 32 / 4 | 128(压缩) |
| 词表 | 50257 | 128256 | 151936 | 151936 | 129280 |
| 最长上下文 | 1024 | 131072 | 40960 | 40960 | 163840 |
| 位置编码 | 学出来的表 | RoPE | RoPE | RoPE | RoPE |
| 归一化 | LayerNorm | RMSNorm | RMSNorm | RMSNorm | RMSNorm |
| MLP 的激活 | GELU | SiLU(门控) | SiLU(门控) | SiLU(门控) | SiLU(门控) |
| 专家 | — | — | — | 128 选 8 | 256 选 8 |
把 Llama 3.1 8B 的配置代进去,按它的结构一个矩阵一个矩阵地数参数:词嵌入和输出层各 128256 × 4096,每层的注意力(4096 × 4096 的查询和输出、4096 × 1024 的键和值)加 MLP(三个 4096 × 14336 的矩阵),一共 8030261248 个——正好是它名字里的「8B」(官方说 8.03B)。从 GPT-2 small 的 1.24 亿到这里,是 65 倍。
没变的:那副骨架
先看什么没变。这五个模型,前向计算的流程一模一样:
文字 → 分词(第 2 章)→ 查嵌入表(第 5 章)
→ [ 注意力(第 9 章)+ MLP(第 11 章)] × 层数,全部加在同一条总线上(第 10 章)
→ 最后一次归一化 → 乘回词表 → 一张候选表(第 1 章)→ 抽一个(第 14 章)→ 接到末尾,再来
训练目标也一样:在海量文本上猜下一个 token,压低交叉熵(第 4 章);然后做后训练(第 17 章)。这本书里拆出来的现象——总线上的加法、在中间层读出答案、归纳头、叠加、复读、校准——在这些大模型上都被研究者反复观察到了。你在 GPT-2 上建立的直觉,在今天的模型上几乎全部成立。
(GPT-2 知道 2017 年那篇论文的标题:Attention is all you 后面,␣need 46.50%。那篇论文定下的这副骨架,至今没有被换掉。)
换掉的五个零件
一、位置:从「查表」到「旋转」
GPT-2 给每个位置学一个向量(第 9 章),学了 1024 个,所以它最多只能看 1024 个 token,第 1025 个位置根本没有向量。今天的模型几乎都用 RoPE(旋转位置编码):不再往向量上加东西,而是把查询和键的每两维当成一个平面,按位置旋转一个角度,位置越靠后转得越多,不同的维度对用不同的转速。两个向量做点积时,旋转角度一相减,留下的就只是它们之间的相对距离。没有「第几个位置」的表,所以也就没有硬性的长度上限——配置里那个 rope_theta(Llama 是 500000,Qwen3 是 1000000)就是最慢那一档的转速,调大它是把窗口撑长的常用手段之一。
二、归一化:更简单的 RMSNorm
GPT-2 每层用 LayerNorm 把向量拉回标准尺度(减均值、除标准差、再缩放平移)。今天普遍换成 RMSNorm:不减均值、不加平移,只除以均方根再缩放。少算一点,效果几乎一样,训练还更稳。
三、MLP:加一道门
GPT-2 的 MLP 是「乘矩阵 → GELU → 乘矩阵」。今天普遍用门控的版本(叫 SwiGLU):三个矩阵,一个算「值」,一个算「门」,门过一个平滑的激活函数(SiLU)之后和值逐个相乘,再乘第三个矩阵回到总线。第 11 章说 MLP 是一张键值表;这道门让每个神经元在「要不要写」和「写多少」之间多了一层控制。配置里 Llama 的 intermediate_size 是 14336,是总线宽度的 3.5 倍(GPT-2 是 4 倍),因为三个矩阵比两个多出一份。
四、注意力:很多个头共用一套键值
这是对推理成本影响最大的一处。GPT-2 的 12 个头各有自己的键和值;Llama 3.1 8B 有 32 个查询头,却只有 8 组键和值,每 4 个查询头共用一组。这叫分组查询注意力(GQA)。查询头可以多,因为它们不用存;键和值要存进 KV 缓存,越少越好。算一下它省了多少(半精度,每个数 2 字节):
| 模型 | 每个 token 的 KV 缓存 | 用满 128K 窗口 |
|---|---|---|
| GPT-2 small(12 层 × 12 组 × 64 维) | 36 KiB | (窗口只有 1024) |
| Llama 3.1 8B,假如 32 个头各存一组 | 512 KiB | 64 GiB |
| Llama 3.1 8B,实际(8 组) | 128 KiB | 16 GiB |
| Qwen3-8B(36 层 × 8 组) | 144 KiB | — |
| Qwen3-30B-A3B(48 层 × 4 组) | 96 KiB | — |
| DeepSeek-V3(压缩成 512 + 64 维) | 68.6 KiB | — |
GQA 让同一段 128K 对话的缓存从 64 GiB 降到 16 GiB。DeepSeek-V3 走得更远:它有 128 个头、总线宽 7168,却不存完整的键值,只存一个压缩后的 512 维向量加 64 维的位置部分(叫 MLA,多头潜在注意力),每层每个 token 576 个数,61 层下来每个 token 只要 70272 字节——比一台小它几十倍的 Llama 3.1 8B 还少。这些改动不让模型更聪明,让它更便宜——尤其是让「长」变得便宜。
五、混合专家:参数很多,每次只用一小部分
Qwen3-30B-A3B 和 DeepSeek-V3 的配置里多了几行:num_experts: 128 / n_routed_experts: 256,num_experts_per_tok: 8。意思是:每一层的 MLP 不是一块,而是 128 块(或 256 块)较小的「专家」,加上一个小小的路由器;每个 token 来了,路由器只挑其中 8 块去算。
于是总参数和「每个 token 实际动用的参数」分了家。Qwen3-30B-A3B 名字里写得明白:总共约 300 亿,每个 token 只激活约 30 亿(「A3B」)。DeepSeek-V3 的技术报告给的数字是总共 6710 亿、每个 token 激活 370 亿。知识可以存得很多(第 11 章:MLP 是知识的主要存放处),而每一步的计算量只按激活的那部分付钱。代价是所有专家都得放在内存里——它省的是算力,不是内存。
还有两样不在配置文件里的
- 数据。GPT-2 用了约 40 GB 的网页文本;Llama 3 的训练数据超过 15 万亿个 token。第 8 章的幂律在这里起作用:参数和数据一起往上推,损失一起往下走。
- 后训练。今天的「助手」和 GPT-2 之间最大的体感差距——听懂指令、会停、会拒绝、会调用工具——主要来自第 17 章那一段,而那一段在配置文件里一个字都看不到。
读配置文件不需要下载模型权重,几 KB 而已:
from transformers import AutoConfig
print(f"{'模型':30} 层 头/KV头 词表 上下文 KV 缓存/token")
for repo in ["gpt2", "NousResearch/Meta-Llama-3.1-8B", "Qwen/Qwen3-8B", "Qwen/Qwen3-30B-A3B"]:
c = AutoConfig.from_pretrained(repo) # 只下载几 KB 的 config.json
L, H = c.num_hidden_layers, c.num_attention_heads
kv = getattr(c, "num_key_value_heads", None) or H
hd = getattr(c, "head_dim", None) or c.hidden_size // H
kv_bytes = 2 * L * kv * hd * 2 # K 和 V × 层数 × KV 头 × 每头维度 × 2 字节(bf16)
print(f"{repo:30} {L:2d} {H:2d}/{kv:<2d} {c.vocab_size:6d} {c.max_position_embeddings:6d} {kv_bytes / 1024:5.0f} KiB")
本机输出:
模型 层 头/KV头 词表 上下文 KV 缓存/token gpt2 12 12/12 50257 1024 36 KiB NousResearch/Meta-Llama-3.1-8B 32 32/8 128256 131072 128 KiB Qwen/Qwen3-8B 36 32/8 151936 40960 144 KiB Qwen/Qwen3-30B-A3B 48 32/4 151936 40960 96 KiB
想亲手跑一个今天的模型:在 Apple 芯片的 Mac 上,mlx-lm 或 Ollama 都能直接跑 8B 级别的模型(4 比特量化后约 5 GB);然后用这本书的眼光去看它——它的候选表、它的复读、它的校准。
pip install transformers && python 19.py
也可以直接在浏览器里看:https://huggingface.co/Qwen/Qwen3-8B/blob/main/config.json,任何一个开源模型仓库的根目录下都有这个文件。
- 本地跑模型时,内存要同时装下权重和 KV 缓存。8B 模型的权重量化到 4 比特约 5 GB,可是一段长对话的缓存可能比权重还大——「能加载」和「能聊长」是两回事。
- 「量化」是把每个权重从 16 位压成 8 位、4 位,损失一点精度换一半甚至四分之三的内存。为什么少几位精度模型照样能用、什么时候会突然不能用,《抹零》讲的是同一类问题。
- 看一个新模型的技术报告时,先找这五样:位置怎么编、KV 怎么省、MLP 是不是专家、上下文多长、训练了多少 token。剩下的大多是规模和数据。
「今天的大模型和 2019 年的 GPT-2 已经是完全不同的东西,拆 GPT-2 学到的那一套早就过时了。」
逐行读配置文件:切 token、查向量、几十层「注意力 + MLP」加在一条总线上、一张候选表、抽一个——这副骨架在 Llama、Qwen、DeepSeek 里原样都在。换掉的五个零件(RoPE、RMSNorm、门控 MLP、共享或压缩的键值、混合专家),主要是在让同样的事情更便宜、能更长,而不是换了一种做法。
真正拉开距离的是三样配置文件之外、或者只体现为一个数字的东西:规模(65 倍到几千倍的参数)、数据(多了上千倍),以及后训练。理解它们的直觉,前面十八章都给过了。
正确答案是 C:每个 token 2 × 32 层 × 8 组 × 128 维 × 2 字节 = 128 KiB,乘 131072 个 token = 16 GiB。
如果 32 个头各存一组键值(像 GPT-2 那样),是 64 GiB。
《绕圈》的母题是「把信号缠到圆上看」——RoPE 恰好就是把查询和键按不同频率缠到圆上,让点积只剩下相对位置。《张成》第 23 章讲大模型里的低秩直觉:DeepSeek 的 MLA 把键值压到 512 维,和 LoRA 是同一种「真正有用的维度没那么多」的赌注。《抹零》讲的浮点精度,是理解量化的前置课。
这一章的一句话
从 2019 年的 GPT-2 到今天的开源模型,骨架是同一副:token → 向量 → 几十层「注意力 + MLP」加在一条总线上 → 一张表 → 抽一个。换掉的五个零件主要在让它更便宜、更长;拉开距离的是规模、数据和后训练。
最后一章收尾。把前面十九个「✗ 这个直觉是错的」收成一张自查表,再按方向给出继续往下走的路——以及哪些问题至今没有定论。顺便让 GPT-2 读一下这本书的最后一句话:「This is not the end. It is only the」——它给 ␣beginning 的概率是 90.56%。