Transformer 实现机制 · 第 1 课

LLM 全景 —— 下一词预测、缩放定律与三段架构

⏱ 约 20–25 分钟 · 前置:无 · 主干教材:Vizuara《The Transformers》1.1–1.2 · 本课目标:说清 LLM 到底在算什么、"大"为什么重要、整台机器分几段——这张地图就是整门课的地图

欢迎开新坑。这门课的使命写在课程首页:深刻理解 Transformer 的实现机制——不是会用 ChatGPT,而是拆开它,看每一个零件为什么在那儿、数学上怎么转。全程配可运行的代码和可动手的演示,每课一个小胜利。今天是第 1 课:不进零件车间,先看整条流水线。

一、LLM 在做一件极其朴素的事:预测下一个词

把 GPT 这类大语言模型(LLM)的所有魔法剥掉,核心任务只有一个:给一串词,算出下一个词是词表里每个词的概率。看到「The cat sat on the」,它算出 mat 的概率比 moon 高得多。

Vizuara 原文 1.1 节用真实 GPT-2 做了个实验:输入「After years of hard work, your effort will take you」,模型对下一个词给出前 10 名候选——「to」以 90.7% 遥遥领先,其余候选概率依次递减。注意关键点:模型给的不是一个答案,而是一整张概率表。所以 LLM 常被叫做概率引擎——它不"知道"正确答案,它只是极其擅长估计"什么词接在这里最像话"。

The cat sat on the LLM 算一张概率表 mat p ≈ 80% 把「mat」拼回输入 →「The cat sat on the mat」→ 再来一轮(自回归)
自回归(autoregressive)生成:预测一个词 → 拼回输入 → 用更长的输入预测下一个词 → 循环,直到生成整段话(Vizuara 图 1.2)。

这套机制听起来朴素得可疑,但"下一词预测"这个目标一旦和海量训练数据、巨大模型结合,就涌现出了翻译、写代码、做算术等能力——这条因果链正是整门课要拆解的对象。

亲手跑一个"概率引擎"

下面的代码块里有一个真正的下一词预测模型——不过是个玩具版:它数一遍小语料里每对相邻词的出现次数,把计数表归一化成概率表(这叫二元模型 bigram)。它的"智力"约等于 1950 年代,但输出的东西和 GPT-2 是同一种东西:一张下一个词的概率表。点「运行」,再改改输入最后一行,多跑几次看随机生成:

# ============================================================ # 【演示目标】亲眼看到"下一词预测模型"输出的东西 = 一张概率表。 # 这是 LLM 最核心的一件事:正文说"模型给的不是答案,是概率表", # 这块代码用 1950 年代水平的玩具模型把它变成可跑的现实。 # 【思路】分四步,每步对应真实 LLM 的一个环节: # 1. 训练 → 数语料里每对相邻词的出现次数,归一化成概率表 # (真实 LLM 用神经网络"学"这张表,我们直接数——但产物同款) # 2. 预测 → 给一句话,查表报出下一个词的 Top-5 候选和百分比 # 3. 采样 → 按概率抽一个词(所以每次运行结果可能不同!) # 4. 自回归 → 把抽到的词拼回句尾,回到第 2 步循环 # ============================================================ import numpy as np # ---- 第 1 步·准备:语料与词表 ---- corpus = ( # 8 个玩具短句;故意让 the cat / "the cat sat on the mat . the cat ate the fish . " # the mat 这类搭配高频, "the dog sat on the floor . the dog ate the bone . " # 看概率表能否"学"到 "a cat and a dog sat together . the fish swam in the water . " "the mat was under the cat . the bone was on the floor ." ).split() # 按空格切开 → 词的列表 vocab = sorted(set(corpus)) # 词表 = 语料中出现过的所有词(去重) w2i = {w: i for i, w in enumerate(vocab)} # 词 → 行号:用矩阵位置存"谁后面跟谁" n = len(vocab) # 玩具词表只有 19 个词 # ---- 第 1 步·训练:数相邻词对 → 概率表 ---- counts = np.zeros((n, n)) # counts[i][j] = 词 i 后面是词 j 的次数 for a, b in zip(corpus, corpus[1:]): # zip 错开一位配对:(第1,第2)(第2,第3)… counts[w2i[a], w2i[b]] += 1 rows = counts.sum(axis=1, keepdims=True) # 每行的总次数(词 i 后面接任意词) probs = np.divide(counts, rows, out=np.zeros_like(counts), where=rows > 0) # ↑ 每行除以行和 → 行里每个格子变成概率,整行加起来 = 100% # ---- 第 2 步·预测:给定句子,报 Top-5 候选 ---- prompt = "the cat sat on the" # 改我!用小写英文词,别超纲(词表里没有就不行) last = prompt.lower().split()[-1] # 玩具模型只看最后一个词(真实 LLM 看全部上文) if last not in w2i: print("「%s」不在玩具语料里。试试 the / cat / sat / fish ..." % last) else: i = w2i[last] # 查到"最后一个词"对应的行 print("输入: %s" % prompt) print("「%s」后面的 Top-5 候选词:" % last) for j in np.argsort(probs[i])[::-1][:5]: # 该行按概率从大到小排序,取前 5 if probs[i, j] > 0: print(" %-8s %.0f%%" % (vocab[j], probs[i, j] * 100)) # ---- 第 3+4 步·采样 + 自回归生成 ---- # np.random.choice 按 probs 这一行概率抽词:21% 抽中 cat,14% 抽中 mat…… # 抽到什么拼什么,拼完再查表——这就是正文说的"预测→拼接→循环" out = prompt.lower().split() for _ in range(12): # 最多再生成 12 个词(碰到句号提前停) nxt = vocab[np.random.choice(n, p=probs[w2i[out[-1]]])] out.append(nxt) if nxt == ".": break print("自回归生成:", " ".join(out)) # 多跑几次看不同——这就是"采样"
玩具与 GPT 的差别,只在"查表"换成"神经网络" 玩具模型查的是一张 19×19 的小表,且只看前一个词;GPT-2 用一个有 15 亿参数的神经网络,看全部上文,输出 50257 个词各一个概率。但"输入词串 → 输出概率表 → 采样 → 拼回去"这个骨架完全一样。把骨架焊死在脑子里,后面 12 课全是在解释那个神经网络盒子里有什么。
多跑几次,生成结果不一样? 这不是 bug。模型输出的是概率分布,生成时按概率抽样(采样)。「the」后面 cat 21%、mat 14%……每次抽到的都可能不同。真实 LLM 回答同一个问题每次措辞不同,根源就在这。概率表怎么来的(训练)是后话;今天先记住输出长什么样。

再把"预测→抽样→拼接→再预测"这个循环演给你看——还是同一份语料、同一张概率表,只是让概率引擎转起来:

动画 · 概率引擎转动:自回归生成循环

二、为什么叫"Large":缩放定律与涌现能力

前面例子里的模型只有 19 个词的词表。真实的 GPT-3 有 1750 亿个参数,训练时读过几乎整个互联网的文本。为什么非要这么大?

缩放定律:越大越好,而且好得有规律

GPT-3 论文(Brown et al., 2020)系统对比了 1.3B → 13B → 175B 三档模型:参数每上一个台阶,各项任务成绩就上一个台阶,而且在对数坐标下几乎是一条直线——损失(loss)大致按幂律随参数量下降。这意味着"加大模型能换多少提升"是可以预测的,砸钱之前就能算出回报,这是整个行业敢百亿千亿投入的数学底气(原文图 1.5–1.6)。

涌现能力:有些技能,小模型怎么练都没有

更玄的是原文图 1.7 画的现象:某些任务(比如三位数加法、多语言翻译),小模型的准确率长期趴在零附近,规模跨过某个阈值后突然跳起来。这种"不存在→突然存在"的技能叫涌现能力(Wei et al., 2022 提出)。它不是随规模线性变好,而是像水到 0°C 结冰一样有相变点。这也解释了为什么"再大一点"从来不只是"好一点"——可能直接解锁一整类新任务。

下面把这两条规律画出来(曲线为示意,常数取 GPT-3 / Kaplan 量级,重在形状):

# ============================================================ # 【演示目标】把正文第二节的两条规律画成图,用"形状"记住它们: # 左图 缩放定律 —— 模型越大损失越低,且在双对数坐标下近似一条直线 # 右图 涌现能力 —— 有些任务小模型长期趴 0 分,跨过阈值突然及格 # 【思路】不精确复刻论文,用论文同款的数学形式造"示意曲线", # 让眼睛记住两种形状的对比:匀速直线下滑 vs 悬崖式跳变。 # (图内标签用英文是因为 matplotlib 环境没有中文字形,见坐标轴说明) # ============================================================ import numpy as np import matplotlib.pyplot as plt fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3.8)) # 一行两幅子图 # ---- 左图:缩放定律 L(N) = (Nc/N)^0.076,幂律形式来自 Kaplan et al. 2020 ---- params = np.logspace(8.5, 11.3, 200) # 参数量 ~3亿 → ~2000亿;对数均匀取 200 个点 loss = (8.8e13 / params) ** 0.076 # 幂律:参数翻 10 倍,损失只降一小截 ax1.loglog(params, loss, color="#5b5bd6", lw=2.5) # 双对数坐标:幂律 → 直线 for m, name in [(1.3e9, "GPT-3 1.3B"), (13e9, "13B"), (175e9, "175B")]: y = (8.8e13 / m) ** 0.076 # 三档真实模型在曲线上的位置 ax1.scatter([m], [y], color="#b45309", zorder=3) # 标成橙点 ax1.annotate(name, (m, y), textcoords="offset points", xytext=(-8, 10), fontsize=8, ha="right", color="#b45309") ax1.set_xlabel("model parameters N (log)") # 横轴:参数量(对数) ax1.set_ylabel("validation loss L (log)") # 纵轴:验证集损失(对数) ax1.set_title("Scaling law: L ~ N^-0.076 (schematic)") ax1.grid(True, which="both", alpha=0.25) # ---- 右图:涌现 = S 形跳变(形式参照 GPT-3 论文 Fig 3.10,数值为示意) ---- compute = np.linspace(18, 23, 200) # 横轴是 log10(训练算力):20 → 1e20 # sigmoid 函数:先贴地、过中点后迅速拉满 → 恰好模拟"跨过阈值才会" acc2 = 1 / (1 + np.exp(-3.0 * (compute - 20.0))) # 两位数加法:较小规模就涌现 acc3 = 1 / (1 + np.exp(-3.0 * (compute - 21.2))) # 三位数加法:要再大 ~16 倍算力 ax2.plot(compute, 100 * acc2, color="#1a7f37", lw=2.2, label="2-digit addition") ax2.plot(compute, 100 * acc3, color="#c0392b", lw=2.2, label="3-digit addition") ax2.axvline(21.2, color="#57606a", ls="--", lw=1) # 阈值参考线 ax2.text(21.28, 12, "threshold", fontsize=8, color="#57606a") ax2.set_xlabel("log10 training compute (schematic)") # 横轴:训练算力(以 10 为底的对数) ax2.set_ylabel("task accuracy (%)") # 纵轴:任务准确率 ax2.set_title("Emergent abilities: near-zero, then jump") ax2.legend(fontsize=8, loc="upper left") ax2.set_ylim(-5, 105) ax2.grid(True, alpha=0.25) plt.tight_layout() plt.show() # py-runner 会把图转成 PNG 显示在下方
左:缩放定律——对数坐标下损失近似直线下降(GPT-3 三档模型位置已标出);右:涌现——两位数加法先亮,三位数加法在更大规模处才突然跟上(形式参照 GPT-3 论文 Fig 3.10 与 Wei et al. 2022,数值为示意)。

三、整台机器:三段架构,也是整门课的地图

最后看这台机器长什么样。GPT 这一族叫 decoder-only 架构(Transformer 的"解码器"半个身子,2017 年 《Attention Is All You Need》提出完整版)。拆开只有三段(原文图 1.10–1.12):

① 输入 Input 文本 → 分词 / BPE (第2课) 词元 ID → 词嵌入 (第3课) + 位置编码 (第4课) 输入嵌入矩阵 X 5 词 × 8 维 = (5, 8) ② 处理 Transformer Block ×N LayerNorm (第9课) 多头自注意力 (第5–8课,Q/K/V、掩码) 残差连接 ⊕ (第9课) LayerNorm → FFN/GELU (第10课) 残差连接 ⊕ → 出块 GPT-2 small: N=12;Qwen3-ASR: 24 层 ③ 输出 Output 最后一个位置 → 线性层 logits(全词表各一个分数) softmax → 概率表 下一个词元 按概率采样(今天✓) 生成的词元拼回输入,循环 ← 自回归(今天讲过)
decoder-only Transformer 三段架构(改绘自 Vizuara 图 1.11)。蓝字是本课程各部件对应的课号——这张图就是整门课的地图,13 课结束后你应当能徒手重画并解释每一格。
今天的胜利 ① LLM 输出的是下一个词的概率表,生成 = 预测→拼接→循环(自回归);② "大"不是玄学:缩放定律让提升可预测,涌现能力让跨阈值解锁新技能;③ 整台机器 = 输入(分词→嵌入)→ 处理(×N 个 Transformer 块)→ 输出(logits→softmax)。你还亲手跑了一个真的下一词预测模型。

检索练习

本课主读材料

去读(约 10 分钟)

Vizuara《The Transformers》1.1–1.2 节(本课的原始出处)

读法:今天课内已把 1.1(LLM=下一词预测、概率引擎)和 1.2(架构三段论)讲透了,去原文主要是对图复习——看图 1.3(输入句)、2.4(top-10 概率表)、1.10–1.12(三段架构),把图和今天的地图对上号即可,不用精读文字。

可选视频:3Blue1Brown 的 But what is a GPT? 系列——给"下一词预测怎么变成智能"补直觉,睡前看。

我是你的老师,别客气 哪一步晕了,把原句贴回来问我。比如"为什么概率表要 softmax 而不是直接除总和"这种问题,后面专门有一课讲——但你现在问,我随时答。