各章节里出现的关键词,都在这里有一条简短、面向直觉的解释。正文中带虚线下划线的词, 鼠标悬停会弹出气泡,点"更多"就跳到这里对应词条。
多维数组,深度学习里数据的基本容器。标量是 0 维、向量 1 维、矩阵 2 维,再高维就统称张量。
形如 (B, T, C) 的数据就是一个 3 维张量:B 个序列 × 每序列 T 个位置 × 每位置 C 维向量。
一次喂给模型并行处理的一组样本。batch_size=32 表示一次处理 32 条序列。
批越大,梯度估计越稳,但越吃显存。注意它和"向量维度"是两码事 —— 一个是"几条样本",一个是"每个 token 几个数字"。
把离散的 token(整数 id)映射成一个可训练的稠密向量。nn.Embedding 本质是一张查找表:
第 i 行就是第 i 个 token 的向量。训练让语义相近的 token 拥有相近的向量。
把文本和数字互相转换的工具:encode 把字符串变成整数 id 序列,decode 反过来。
本项目用最简单的"字符级"切分 —— 每个字符一个 id。
函数定义时给参数设的默认值,调用时不传就用它。例如 forward(self, idx, targets=None)
里 targets 缺省为 None —— 只想前向出 logits、不算 loss 时,就不传 targets。
模型一次能看到的最大 token 数。block_size=8 意味着每个位置最多回看前 8 个字符;
更早的历史会被截断(generate 时要 idx[:, -block_size:])。这是注意力的"视野上限"。
每个 token 用多长的向量来表示,本项目 n_embd=32。它决定了 token_embedding、
位置表、Wq/Wk/Wv 的形状。⚠️ 本章里它和 batch_size、
head_size 数值都撞成 32,但含义完全不同:一个是"每个 token 几个数字(维度)",
一个是"一次几条句子",一个是"注意力头的输出宽度"。看到 32 先问"这是谁的 32"。
模型最后一层输出的、未经 softmax 的原始打分。可正可负,数值大小代表"倾向";
过 softmax 后才变成概率。形状常见为 (B, T, vocab_size) —— 每个位置对词表里每个字符都打一个分。
把一组任意实数(打分)变成一组和为 1 的正数(概率分布):每个数取 exp 再除以总和。
分数越大,概率越大。它天生保证"每行加起来 = 1"。
把一组数按比例缩放,使其满足某种总量约束。本项目里特指"每行除以该行的和", 让每行加起来 = 1,从而变成一组权重(概率分布)。softmax 内部做的也是这件事。 它管的是"尺度",和"哪些位置为 0"(因果)是两件正交的事。
分类任务的损失函数。先对 logits 做 softmax 得概率,取正确类别的概率 p,
loss = -ln(p)。模型越确信正确答案(p→1),loss→0;越瞎猜,loss 越大。
随机初始时 loss ≈ ln(vocab_size)。
loss 对每个参数的偏导数,指出"参数往哪个方向调、loss 下降最快"。
loss.backward() 负责把它算出来,优化器再据此更新参数。
梯度在传播中变得极小,参数几乎不更新、模型学不动。 softmax 过于"尖锐"(某一项概率接近 1)时就会发生 —— 这正是注意力要对分数做缩放的原因。
PyTorch 的梯度默认累加(backward 把新梯度加到旧的上)。所以每轮训练开头必须先
optimizer.zero_grad() 清零,否则会带着上一轮的陈旧方向越走越偏。
顺序:zero_grad → backward → step。
拿到梯度后,真正决定"每个参数走多大一步"的算法。AdamW 是 Adam 的改良版(带权重衰减),
是当下训练 Transformer 的常用默认选择。lr(学习率)控制步子大小。
序列内部每个位置,通过 query/key/value 机制, 按相关性从其它位置(因果设定下只看过去)聚合信息,得到融合了上下文的新表示。 "self"指 q、k、v 都来自同一个序列。
同一个输入经三个不同的线性投影得到的三种角色。query=我在找什么; key=我宣传自己是什么(被检索);value=你看我我给你什么(被取走的信息)。 用 query 比对所有 key 算出权重,再用权重聚合 value。
两个等长向量对应元素相乘再求和,得到一个标量。结果越大,表示两向量方向越一致。
注意力用 q · k 来衡量"query 和 key 有多相关",作为关注分数。
一个方阵,对角线及其左下方保留,右上方全部为 0。torch.tril 生成。
注意力用它做因果掩码:让第 i 个位置只能看到 0..i,看不到未来。
它管的是"哪些位置必须为 0"(结构),和归一化管的"尺度"互相独立。
在注意力分数里,把"未来位置"设成 -∞,经 softmax 后它们权重变 0。
保证预测下一个字符时不能偷看答案。靠下三角实现:
masked_fill(tril==0, -inf)。
注意力把 q·k 的分数除以 √head_size。因为点积的方差随维度增大,
不缩放会让 softmax 过于尖锐、把权重几乎全压给一个位置,导致梯度消失。
除以 √d 把分数尺度拉回正常。
因为注意力本身看不出顺序(打乱输入,输出只跟着打乱,权重不变),
所以额外给"第几个位置"也学一个向量,加到 token 嵌入上,模型才知道谁先谁后。
代码:x = tok_emb + pos_emb。
把注意力拆成若干个并行的头:每个头在 n_embd 的一个子空间里独立
打分聚合(每头 head_size = n_embd / n_head 维),最后把各头输出拼接(cat)再过一个 proj 线性层融合,回到 n_embd。
要点:不是把模型加宽,而是把同一块 n_embd 切成几份 —— 切几份总料不变,显存/算力基本持平。
每个头会在训练中自发分化出不同专长(盯前一词、回看句首、配对括号…),没人指定;
这与 Q/K/V 那种"公式钦定"的分工不同。本项目第 3 关开始用。
预训练之后的一步:用「问 → 答」成对数据继续训练同一批参数(包括 Wq/Wk/Wv), 把模型调成"以助手口吻回答"的姿态。要点:"抓相关上下文"的能力是预训练就建好的,SFT 只是在上面拧风格 —— 它不会"激活" QKV 里某个为问答预留的用途(QKV 2017 年为翻译而生,SFT 2022 年才出现,不存在"提前挖坑")。 属于对齐阶段,本项目 Phase 2 才正式动手;这里先建立概念。
最基础的神经网络:几层全连接(Linear)中间夹非线性(如 ReLU)。
"多层"指它不止一层线性变换。Transformer 里每个 Block 的 FeedForward 就是一个很小的 MLP
(Linear → ReLU → Linear),负责对每个位置的向量单独"加工"。
Transformer 层里的小 MLP:Linear(n_embd, 4·n_embd) → ReLU → Linear(4·n_embd, n_embd),中间放大 4 倍再压回。
它是逐位置(position-wise)的 —— 对每个 token 的向量单独加工,位置之间不交换信息。
分工:注意力让 token 互相"沟通",FFN 让每个 token 自己"思考"。
最常用的非线性激活函数:ReLU(x) = max(0, x),把负数清零、正数保留。
作用是给网络引入非线性 —— 没有它,多层 Linear 叠起来在数学上还是一个线性变换,层数白加。
把子层的输出加回它的输入:x = x + 子层(x)。那个 + 是一条梯度高速公路,
反向传播时梯度能绕过子层直达底层,避免 梯度消失,深网络才堆得动。
子层只需学"在原信号上加什么修正"。出自 2015 年 ResNet,Transformer 全靠它。
对每个位置那一根向量(沿特征维)做 归一化:减均值、除标准差,把数值拉回稳定范围,深网络训练才不发散。
它不跨位置、不跨 batch,与序列长度无关。pre-norm(先 norm 再进子层,x + 子层(ln(x)))比原始 Transformer 的 post-norm 更好训。
Transformer 的一层 = 注意力子层 + FFN 子层,各自带 残差 和 LayerNorm:
x = x + sa(ln1(x)) 然后 x = x + ffwd(ln2(x))(沟通 + 思考)。
完整 GPT 就是把 n_layer 个 Block 摞起来。
训练时随机丢弃一部分连接(如 10%),逼网络别过度依赖某几条路径,防过拟合。 只在训练时生效,推理时关闭。模型变大后(第 3 关)容易死记硬背,所以加了它。
把输入从头到尾过一遍网络、算出输出的过程 —— 这里就是 idx → 嵌入 → Block×N → ln_f → lm_head → logits。
训练和推理都要做前向;训练还会接着反向传播更新权重,推理则到 logits 就停、拿去采样。
按模型输出的概率分布随机抽一个 token 当下一个字符(代码 torch.multinomial(probs, 1)),
不是永远取最高分(argmax)。取最高分会陷入重复死循环;随机采样保多样,所以同一个开头每次生成都可能不同。
把自己刚生成的字符接回输入,再预测下一个,如此循环。GPT 生成文本就是这样一个字一个字滚出来的: 每多一个字,就把整条序列重新做一次前向传播。
每步只保留概率最高的 k 个 token,其余打分置 −∞(概率 0)再归一化采样,把长尾低概率 token 一刀切掉,避免偶尔抽到离谱的词。默认 k=50。
把过去每个 token 算出的 K/V 向量缓存下来,生成新 token 时只算新一列、复用历史,避免每步把整段注意力重算一遍。计算量从平方级降到线性,本项目实测推理提速约 2.5–3.1×。
把文本切成子词单元的分词法,介于字符级和整词级之间。GPT-2 的 BPE 词表有 50257 个 token —— 本项目 124M 复现就用它(代码里向上取整到 50304,凑 128 的倍数更省 GPU)。
经过教育质量筛选的大规模英文网页语料,常用于预训练。本项目用其子集(300M / 10B token)做 GPT-2 124M 复现 —— 全英文,所以模型不会中文。
把超大数据集切成的一块块文件(这里是 .npy),训练时一片片顺序读入,不必把整个 10B token 数据集一次塞进内存。
优化器的事实标准:Adam 的改良版,把权重衰减从梯度里拆出来单独做。本项目用 betas=(0.9, 0.95),并对参数分组(权重给衰减、bias/LayerNorm 不给)。
16 位浮点。前向用它算更快、省一半显存,数值范围和 fp32 一样大、精度几乎无损 —— 大模型训练的常规操作。需要 N 卡支持。
把注意力算得又快又省显存的实现(F.scaled_dot_product_attention):不显式构造 T×T 注意力矩阵,边算边融合。和普通注意力数学等价,只是更高效。
预训练:在海量无标注文本上做"预测下一个 token",产出只会续写的基座模型(base model) —— Phase 1 做的就是这步。 后训练:在 base 之上继续调,让它"会听话":包括 SFT 和 偏好对齐。 现代大模型 = 预训练 → SFT → 偏好对齐 一条流水线,后一段都站在前一段权重的肩膀上。
后训练的一段,目标是让回答更合人类口味、更有用更安全。数据是"同一问题的两个回答 + 人类判断 A 比 B 好"。 它是目标,不是某个具体算法:实现它有两条路 —— RLHF(走强化学习)和 DPO(不走强化学习)。 所以"偏好对齐 = 强化学习"是把目标和其中一种手段画了等号。
微调时更新模型的全部参数。它是一种"手段"(怎么改权重),不是一种"任务" —— 既能用来做 SFT,也能用来做 DPO。 所以"全量微调 = SFT"是把手段误当成任务。它和 LoRA 是二选一的两种手段,不是先后步骤。效果直接,但每个任务都要存一份完整模型,显存/硬盘开销大。
一类"只更新极少量参数"的微调手段的统称,目的是省显存、省存储。LoRA 是其中最流行的一种(还有 QLoRA = LoRA+量化 等)。 与 全量微调 相对,二者是同一件事(微调)的两种改权重方式。
一种 PEFT 手段:冻结原模型,在某些 Linear 旁边挂两个低秩小矩阵 A、B,只训练它俩。
可训练参数常只占 ~1%,却能逼近全量微调的效果;一个底座还能挂多个适配器随时切换。它是"怎么省钱地微调"的手段,
可叠加在 SFT 或 DPO 上。本项目 07_lora.py 会手搓一遍。
SFT 的关键一步:把整条"指令+回答"喂进去做前向,但只对回答段算 loss,
指令/模板段的标签设成 -100(PyTorch 的 ignore_index)被忽略。这样模型只学"给定指令该怎么答",
而不会去学"生成用户的指令"。SFT 和预训练用同一个 loss,差别主要就在这一处掩码。
把"指令"和"回答"拼成一条序列时套的固定格式(用 <|user|> / <|assistant|> 等特殊标记标出角色边界),
让模型分清谁说的、该自己答哪段。结尾的 EOS(结束标记)尤其重要:base 模型的毛病是不会停,
SFT 在每条回答末尾放 EOS 并算进 loss,就是教模型"答完就停"。
一种不走强化学习的 偏好对齐方法:把 RLHF 的目标推成闭式解,
直接在"chosen 好 / rejected 差"的偏好对上做一个类监督损失,拉高 chosen、压低 rejected。
不需要奖励模型、不需要在线采样、没有 RL 循环,单机就能跑。它不是 PPO 的变体,
而是和 RLHF 平行的另一条路;卖点是"不用 RL 那套笨重机器也能拿到 RLHF 的效果"。本项目偏好对齐只做 DPO(08_dpo.py)。
用强化学习做 偏好对齐:先用偏好数据训一个奖励模型给回答打分, 再用 PPO(一种 RL 算法)让模型最大化奖励。要在线采样 rollout、要价值网络,工程量比 DPO 大数倍。 它和 DPO 是实现同一目标的两条路(RLHF 走 RL,DPO 不走)。本项目只在白皮书里讲原理,不写成脚本。
Google 2017 年的论文,提出 Transformer 架构:用自注意力彻底取代 RNN / CNN 来建模序列依赖, 并引入多头注意力与位置编码。它是 GPT、BERT 等几乎所有现代大模型的共同基石。 本项目第 2 关搭的单头自注意力是它的核心零件,第 3 关会完整复现它的架构(多头 + 残差 + LayerNorm + 前馈网络)。 🥚 它的"前世今生"(8 位作者、翻译起源、家谱)有个小故事 → 学习札记。