# 《从零构建大模型》知识点全解 > AI生成|2026年10月28日|codebuddy|原书:Sebastian Raschka,《Build a Large Language Model (From Scratch)》, Manning, 2024(中文版《从零构建大模型》) > **我的阅读**:进度 98%|累计 4小时30分钟|开始 2026-10-21|最近 2026-10-28|读完 2026-10-28 **一句话总结**:这本书不教你"调用"大模型,而是用 PyTorch 把一个 GPT-2 small(1.24 亿参数)从分词、注意力、模型结构、预训练,一路做到分类微调和指令微调,完整走一遍"造模型"的流水线。 **前置知识**:Python 基础、矩阵乘法、softmax、交叉熵;PyTorch 不熟可以先看附录 A。前四章普通笔记本可跑,完整预训练建议有 GPU(CPU 也能跑,只是慢)。 --- ## 〇、全书地图:一条流水线,七个可验证的模块 ```text 原始语料 │ 第2章:BPE 分词 ▼ token id 序列 │ 第2章:词嵌入 + 位置嵌入 ▼ 向量序列 (B, L, 768) │ 第3章:因果多头自注意力 ▼ 上下文向量 │ 第4章:Transformer 块 × 12 + 输出头 ▼ 每个位置的下一个 token 概率 (B, L, 50257) │ ├── 第5章:预训练(在无标注文本上做下一词预测) │ └── 得到基座模型 │ ├── 第6章:分类微调(换输出头,整段文本一个标签) │ └── 第7章:指令微调(保留输出头,只对回答部分算损失) ``` 三个必须建立的整体认知: 1. **所有章节共用同一个模型**。第 5、6、7 章不是三个模型,而是同一个 GPT 的三种用法:继续预训练、换头做分类、改数据格式做 SFT。 2. **每一章都能单独跑通、单独验证**。分词有解码回原文,注意力有因果性检查,模型有参数量核对,预训练有损失曲线,微调有准确率。 3. **规模可以缩小,结构不能省**。书里把 124M 的 GPT-2 压到单卡可训练,但 LayerNorm(Pre-LN)、因果掩码、残差、权重绑定这些结构一个不少,所以学到的不是玩具。 ### 配套代码仓库 | 资源 | 地址 | 说明 | | --- | --- | --- | | 官方代码仓库 | [github.com/rasbt/LLMs-from-scratch](https://github.com/rasbt/LLMs-from-scratch) | 作者维护,全书 notebook、脚本、习题答案与加分章;与书中章节一一对应 | | 中文翻译仓库 | [github.com/MLNLP-World/LLMs-from-scratch-CN](https://github.com/MLNLP-World/LLMs-from-scratch-CN) | 社区维护的 Markdown 笔记与代码翻译,非官方,适合对照阅读 | | 原书主页 | [manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch) | 目录、试读章节与配套资源入口 | 仓库目录与章节对应: | 目录 | 对应内容 | | --- | --- | | `ch02` | BPE 分词、滑动窗口数据集、DataLoader | | `ch03` | 简化自注意力、因果多头注意力 | | `ch04` | GPT 模型结构、文本生成 | | `ch05` | 预训练循环、加载 GPT-2 权重、解码策略 | | `ch06` | 分类微调(垃圾短信) | | `ch07` | 指令微调与评估 | | `appendix-A` | PyTorch 基础 | | `appendix-D` | 训练增强:warmup、梯度累积、混合精度、DDP | | `appendix-E` | LoRA | | `bonus` | DPO、Llama 3.2 from scratch 等扩展 | ### 三条阅读路线 | 路线 | 读法 | 适合 | | --- | --- | --- | | 只想懂原理 | 第 1 → 2 → 3 → 4 章,跳过训练细节 | 产品、算法入门、面试复习 | | 想亲手跑通 | 七章全读 + 附录 D,按仓库 notebook 逐章执行 | 转行、做课程设计 | | 想改造模型 | 第 4 → 5 → 7 章 + 附录 E + DPO/Llama 加分章 | 需要做领域微调、PEFT | --- ## 一、第 1 章:理解大语言模型 ### 1.1 三个本质判断 - **本质是下一词预测器**:给定前文,输出词表上的概率分布。所有"能力"都是这个目标在超大规模数据上的副产物。 - **参数是压缩的知识**:124M 参数不是 124M 条规则,而是把语料中的统计规律压进权重;参数越多,能容纳的模式越复杂,但不等于更正确。 - **两阶段训练**:预训练(学语言与世界知识,自监督)→ 微调(学任务格式与偏好,有监督/强化)。本书第 5 章是第一阶段,第 6、7 章是第二阶段。 ### 1.2 三种架构的分工 | 架构 | 代表 | 注意力 | 擅长 | 本书关系 | | --- | --- | --- | --- | --- | | Encoder-only | BERT | 双向 | 理解、分类、抽取 | 不采用 | | Decoder-only | GPT 系列 | 因果(只看左侧) | 生成、对话、通用 | **全书主线** | | Encoder-Decoder | T5、BART | 编码双向 + 解码因果 | 翻译、摘要等序列转换 | 不采用 | 为什么生成任务选 decoder-only:训练目标(下一词)与推理过程(逐词续写)完全一致,且一套结构可以同时做分类(换头)和生成(不换头)。 ### 1.3 GPT-2 四档与本书的 124M | 型号 | 层数 | 头数 | 嵌入维度 | 参数量 | 上下文 | | --- | --- | --- | --- | --- | --- | | GPT-2 small | 12 | 12 | 768 | 124M | 1024 | | GPT-2 medium | 24 | 16 | 1024 | 355M | 1024 | | GPT-2 large | 36 | 20 | 1280 | 774M | 1024 | | GPT-2 XL | 48 | 25 | 1600 | 1558M | 1024 | 本书配置(可直接背下来): ```python GPT_CONFIG_124M = { "vocab_size": 50257, # GPT-2 BPE 词表 "context_length": 1024, # 最大上下文 "emb_dim": 768, # 隐藏维度 "n_heads": 12, # 注意力头数 "n_layers": 12, # Transformer 块数 "drop_rate": 0.1, # dropout "qkv_bias": False # 书中实现不设 QKV 偏置 } ``` ### 1.4 训练语料与预期 书里用 Edith Wharton 的短篇 *The Verdict*(约 5,000 词)做教学语料。它只够让模型"背下这篇小说",不足以获得通用语言能力。**不要把书中的生成效果当成模型能力的上限**,它验证的是流程正确性,不是规模定律。 ### 1.5 本章要记住 - 参数量、训练 token 数、数据质量三者共同决定能力,单看参数量没有意义。 - 上下文窗口是硬限制:超出部分必须截断或滑窗。 - 幻觉的根源是"下一个词预测"只优化似然,不优化事实性;后续需要 RAG、工具调用、对齐来补。 --- ## 二、第 2 章:处理文本数据 ### 2.1 为什么用子词(subword) | 粒度 | 优点 | 缺点 | | --- | --- | --- | | 词级 | 语义完整、序列短 | 词表爆炸、未登录词无解 | | 字符级 | 词表极小、无 OOV | 序列过长、单字符信息量低 | | 子词(BPE) | 兼顾词表大小与序列长度,能拼未登录词 | 分词结果不直观,跨语言差异大 | ### 2.2 BPE 的四步循环 1. 把所有文本拆成 UTF-8 字节,得到 256 个基础符号(byte-level BPE,任何字符都能表示)。 2. 统计相邻符号对的频次。 3. 把频次最高的一对合并成一个新 token。 4. 重复第 2–3 步,直到词表达到目标大小。 GPT-2 的词表构成:**256 个字节 + 50,000 次合并 + 1 个特殊 token = 50,257**。这就是 `vocab_size=50257` 的由来。 两个细节: - 空格在 GPT-2 中用特殊符号 `Ġ` 表示(byte-level 映射表),所以 `" hello"` 和 `"hello"` 是不同的 token。 - 中文一个汉字通常占 1–3 个 token;同样字数下,中文的 token 数往往多于英文,成本也更高。 ### 2.3 特殊 token `<|endoftext|>` 的 id 是 50256,作用类似"文档分隔符/BOS"。书中用它做 padding 和无标注文本的分隔。注意: - 特殊 token 的 id 必须小于 `vocab_size`,否则 embedding 越界。 - 不要把普通文本里可能出现的符号当特殊 token,否则会污染输入。 - 分词器与模型必须严格配套:换了分词器,embedding 的每一行含义就变了。 ### 2.4 滑动窗口:输入与标签只差一位 ```text 输入 x = tokens[i : i+L] 标签 y = tokens[i+1 : i+L+1] ``` 一次前向会同时在 L 个位置产生 L 个监督信号,这是 Transformer 训练效率高的原因。`stride` 控制窗口重叠程度:stride=L 不重叠,stride1 更发散 | | Top-k | 只在前 k 个里采样 | 抑制长尾胡言乱语 | 温度不能为 0(会除零);等价于贪心时应直接 argmax。 ### 4.6 本章高频坑 1. 训练/推理忘记切换 `train()` / `eval()`。 2. 生成时没包 `torch.no_grad()`,显存暴涨。 3. 上下文没裁剪,超过 1024 直接越界。 4. 权重绑定后又把输出头重新初始化,绑定失效。 5. 生成时把整个序列反复前向,效率极低(可后续用 KV cache 优化,书中未展开)。 6. 位置嵌入长度与输入长度不匹配。 --- ## 五、第 5 章:在无标注数据上预训练 ### 5.1 损失与困惑度 ```text loss = 平均交叉熵 = -(1/N) Σ log p(正确 token) perplexity = exp(loss) ``` 困惑度可以理解为"模型每一步平均在多少个候选词之间犹豫"。随机初始化的模型 loss ≈ ln(50257) ≈ 10.8,困惑度≈50,257;训练就是把这个数压下去。 ### 5.2 训练循环的六件事 1. `optimizer.zero_grad()` 清空上一轮梯度。 2. 前向得到 logits。 3. 计算交叉熵(logits 展平成 (B·L, V),标签展平)。 4. `loss.backward()` 反向传播。 5. 梯度裁剪 `clip_grad_norm_(1.0)`,防止爆炸。 6. `optimizer.step()` 更新参数。 外加两件事:每个 epoch 后在验证集上算 loss(`eval()` + `no_grad()`),以及定期采样一段文本看效果。 ### 5.3 书中的超参数 | 项目 | 设置 | | --- | --- | | 语料 | *The Verdict*,约 5,000 词 | | 划分 | 90% 训练 / 10% 验证 | | batch size | 2 | | 序列长度 | 256 | | epoch | 10 | | 优化器 | AdamW | | 学习率 | 4e-4 | | weight decay | 0.1 | | 梯度裁剪 | 1.0 | | 评估频率 | 每个 epoch | 结果很典型:**训练 loss 可以降到 0.1 以下,验证 loss 先降后升**——模型把整篇小说背下来了。这正是"小数据 + 大模型"的过拟合样本,不是训练失败。 ### 5.4 加载 OpenAI GPT-2 权重 步骤与坑: - 下载 `gpt2`(124M)权重,按名称映射到自己的模型。 - 官方权重带 QKV 偏置,书中模型 `qkv_bias=False`,加载时忽略这些 bias。 - 输出头与词嵌入绑定,加载时只取一份。 - 设备不一致会报错,先 `map_location` 再 `load_state_dict`。 - 加载后先算一遍验证 loss,确认权重真的对上了(loss 应显著低于随机初始化)。 ### 5.5 解码策略对比 | 策略 | 多样性 | 风险 | | --- | --- | --- | | 贪心 | 最低 | 重复、死循环 | | 温度 0.7–1.0 | 中 | 需要调 | | Top-k(k=25–50) | 中高 | k 太小会单调,太大恢复长尾噪声 | | Top-p(nucleus) | 高 | 书中未重点实现,工程常用 | ### 5.6 现实预期 - 124M 参数 + 5,000 词语料,只能验证"能训、能生成",不能获得通用能力。 - Chinchilla 的经验法则:每个参数约需 20 个训练 token;124M 模型理论上要 2.5B token 量级。 - 真正的预训练成本在数据清洗、去重、配比和算力,不在模型代码。 ### 5.7 本章高频坑 1. 验证时忘记 `eval()`,dropout 让 loss 抖动。 2. 学习率过大,loss 直接发散;过小则几乎不动。 3. 没有 warmup,前几百步容易崩(附录 D 补充)。 4. 只保存模型不保存优化器,无法断点续训。 5. 训练数据与验证数据重复,验证 loss 虚低。 6. 用训练 loss 判断模型好坏,忽略过拟合。 7. 生成时 temperature 设成 0 导致除零。 --- ## 六、第 6 章:用于分类的微调 ### 6.1 思路:换头,不换身体 ```text 输入 token → GPT 12 层 → 最后一个位置的隐藏状态 (768) │ ▼ Linear(768, 2) → 概率 ``` 为什么取最后一个位置:decoder-only 模型没有 BERT 的 [CLS],而因果注意力保证最后一个位置能看到全序列,因此它的隐藏状态是整段文本的聚合表示。 ### 6.2 数据与任务 - 数据集:SMS Spam Collection,约 5,572 条短信。 - 标签:正常 / 垃圾,垃圾约占 13.4%,属于类别不平衡。 - 划分:约 70% 训练 / 10% 验证 / 20% 测试。 - 指标:不能只看准确率(全判正常也有 86.6%),要看 spam 类的 precision、recall、F1 和混淆矩阵。 ### 6.3 训练策略:冻结 + 解冻最后一层 1. 先把所有参数 `requires_grad=False`。 2. 只解冻最后一个 Transformer 块、最终 LayerNorm、新的分类头。 3. 可训练参数约 700 万,占 124M 的 6% 左右。 4. 用小学习率(书中 5e-5 量级)微调几个 epoch。 这样做的原因:底层学的是通用语言特征,不该被小数据集冲掉;顶层和任务头才需要适配新任务。这也是参数高效微调(PEFT)的思想源头。 ### 6.4 评估与解释 - 混淆矩阵比单一准确率信息量大:垃圾邮件更怕漏判(FN)还是误判(FP),取决于业务成本。 - 类别不平衡时用加权损失或重采样;阈值也可以按业务调,不必固定 0.5。 - 书中测试集准确率约 97%,但重点是流程,不是刷分。 ### 6.5 本章高频坑 1. **padding 位置污染**:书中把所有样本补齐到统一长度再取末位,短样本的"最后一位"其实是 pad;更严谨的做法是按真实长度取末位,或加 attention mask。 2. 忘记冻结,全量微调在小数据上迅速过拟合。 3. 学习率用预训练的量级(4e-4),把预训练权重冲坏。 4. 只报 accuracy,掩盖了对少数类的漏判。 5. 数据泄漏:同一模板生成的短信同时出现在训练和测试集。 6. 分类头初始化后没有正确接到 hidden size。 --- ## 七、第 7 章:微调以跟随指令 ### 7.1 与分类微调的本质区别 | 维度 | 分类微调 | 指令微调 | | --- | --- | --- | | 输出头 | 换成 2 类线性层 | 保留原词表输出头 | | 监督信号 | 整段一个标签 | 每个回答 token 一个标签 | | 损失范围 | 全部 | 只算回答部分 | | 目标 | 判别 | 生成 | ### 7.2 数据格式 书中使用约 1,100 条"指令-回答"对,字段为 `instruction`、`input`、`output`,按 85/10/5 划分训练/验证/测试。模板采用 Alpaca 风格: ```text Below is an instruction that describes a task. Write a response that appropriately completes the request. ### Instruction: <指令> ### Input: <可选输入> ### Response: <回答> ``` **训练和推理必须使用完全相同的模板**,否则模型看到的分布变了,效果会明显下降。 ### 7.3 损失掩码:只学回答 - 指令部分和 padding 的标签设为 `-100`。 - `CrossEntropyLoss(ignore_index=-100)` 会跳过这些位置。 - 只有回答 token 参与 loss。 为什么必须遮: 1. 不遮的话,模型会把大量容量花在"复述指令"上。 2. 指令通常比回答长,不遮会被指令 token 稀释回答的学习信号。 3. 推理时我们只关心"给定指令后怎么答",不关心"怎么复述指令"。 ### 7.4 批处理与 padding - 同一 batch 内补齐到最长序列(或固定 1024)。 - pad token 用 `<|endoftext|>` 的 id。 - padding 位置的 label 必须是 `-100`,不能是 0 或 pad id。 - 生成时设置 `max_new_tokens`,并裁剪上下文。 ### 7.5 训练与评估 - 书中设置:2 个 epoch、较小学习率(5e-5 量级)、batch 8。 - 训练 loss 持续下降,验证 loss 先降后升,约 1–2 个 epoch 后开始过拟合。 - 评估方式:固定测试指令集上人工对比、与更大模型(如 Llama 3.1 70B / Ollama)做偏好比较。 - 结论:SFT 主要提升**格式遵循与指令理解**,不会显著增加知识;数据质量远比数量重要。 ### 7.6 本章高频坑 1. 训练/推理模板不一致(最高频)。 2. 忘记 mask,loss 看起来正常但回答质量差。 3. padding 参与 loss。 4. 指令数据里混入测试问题,评估虚高。 5. 期望 1,100 条数据把 124M 模型变成 ChatGPT。 6. 灾难性遗忘:SFT 后通用生成能力下降,需要混入通用数据或降低学习率。 7. 多轮对话没有把历史轮次正确拼进上下文。 --- ## 八、附录与加分内容 | 部分 | 内容 | 价值 | | --- | --- | --- | | 附录 A | PyTorch 速成:张量、autograd、Dataset/DataLoader、训练循环、GPU | 没写过 PyTorch 也能起步 | | 附录 B | 参考文献与延伸阅读 | 顺着论文补理论 | | 附录 C | 习题解答 | 自测 | | 附录 D | 训练循环增强:warmup、余弦退火、梯度累积、混合精度、DDP、检查点 | 从"能跑"到"跑得稳" | | 附录 E | LoRA:低秩适配 | 单卡微调大模型的标准手段 | | 加分章 | DPO 偏好对齐 | 理解 RLHF 的轻量替代 | | 加分章 | 从零构建 Llama 3.2 | 看现代架构差异 | ### 8.1 LoRA 的核心 不改原权重,只在旁边加一个低秩增量: ```text W' = W + ΔW = W + B·A A: (r, d_in) B: (d_out, r) r << d ``` - 原权重冻结,只训练 A、B。 - 参数量从百万级降到几十万级(书中 124M 模型上约 0.3M)。 - `alpha` 控制增量的缩放,常用 `alpha = 2r`。 - 推理时可把 `B·A` 合并回 `W`,不增加延迟。 - 通常加在 Q、V 投影上,效果与成本平衡最好。 ### 8.2 DPO 的核心 - 不需要单独训练奖励模型。 - 用偏好对(chosen / rejected)直接优化策略。 - 同时保留一个冻结的参考模型,用 KL 约束防止跑偏。 - 损失里 `β` 控制"允许多大程度偏离参考模型"。 - 比 RLHF(PPO)实现简单、稳定,但数据质量仍是关键。 ### 8.3 Llama 3.2 与 GPT-2 的结构差异 | 组件 | GPT-2 | Llama 3.2 | | --- | --- | --- | | 归一化 | LayerNorm | RMSNorm | | 位置编码 | 可学习绝对位置 | RoPE | | 激活/FFN | GELU + 4 倍 FFN | SwiGLU | | 注意力 | MHA | GQA(分组查询) | | 偏置 | QKV 有/无偏置 | 基本无偏置 | | 词表 | 50,257 | 128K 级 | ### 8.4 与 nanoGPT 的区别 | 维度 | 本书 | nanoGPT | | --- | --- | --- | | 定位 | 教材,逐模块构建 | 极简训练仓库 | | 分词器 | 自己实现 BPE | 直接用 tiktoken | | 覆盖 | 预训练 + 分类 + SFT + LoRA/DPO | 主要覆盖预训练 | | 适合 | 系统学习、写课程 | 快速复现训练 | --- ## 九、形状与公式速查 ### 9.1 张量形状(B=batch,L=序列长度) | 对象 | 形状 | | --- | --- | | token id | (B, L) | | 词嵌入 | (B, L, 768) | | Q/K/V | (B, L, 768) | | 拆头后 | (B, 12, L, 64) | | 注意力分数 | (B, 12, L, L) | | 单头输出 | (B, 12, L, 64) | | 多头拼接 | (B, L, 768) | | logits | (B, L, 50257) | | loss | 标量 | ### 9.2 核心公式 ```text 注意力:softmax(QKᵀ/√d_k + M)·V FFN: W₂·GELU(W₁x + b₁) + b₂ 残差: x ← x + Sublayer(LN(x)) 损失: -(1/N)Σ log p(y_t | x_ 这本书最大的价值不是让你训出一个能用的模型,而是让你在看到一个 LLM 时,能准确说出它的每一层在算什么、每个 token 从哪来、loss 为什么降不下去。