CS336 · 从零构建语言模型
Lecture 1 · 概览与分词
源文件:lecture-01.md
Lecture 1 · 概览与分词
CS336: Language Modeling from Scratch · Stanford · Spring 2025
📅 Apr 1 · 💻 可执行讲义 · 讲者 Percy Liang
承上启下
- 与 CS221 的衔接:CS221 的 Lecture 17 · 语言模型(未找到对应页面) 从建模视角回答了「语言模型是什么」——对 token 序列的概率分布 $p(x_1, \dots, x_T)$,以及从 n-gram 到神经网络的演化。CS336 则回答工程问题:这个分布在工业规模上究竟是怎么被造出来的——数据、分词、架构、系统、对齐,每一层都亲手实现。
- 本讲:课程全景(为什么要 from scratch、效率这条主线、五个单元与五次作业),然后进入整条流水线的第一块积木——分词(tokenization),重点是 BPE 算法的训练、编码与解码。
- 下一讲 Lecture 2 · PyTorch 与资源核算:从 tensor 开始搭建训练语言模型的最小积木,并学会用 napkin math 估算内存与算力。
课程定位
这门课是什么:
- 一门从零构建语言模型的课程——不调 API、不用 Hugging Face 的现成组件,而是亲手写数据处理、分词器、Transformer、训练循环、分布式并行,直到对齐。
- 目标是理解而非生产力——生产环境当然应该用现成的库,但只有把每个细节实现一遍,才能弄懂整套技术栈为什么长成这样。
为什么要 from scratch:
- 研究者正在与底层技术脱节:8 年前研究者自己写模型训练代码,6 年前下载 BERT 再微调,今天很多研究只是 prompt GPT-4——离底层越来越远。
- 抽象层能提升生产力,但语言模型的抽象是漏的(leaky abstraction):分词方式会影响算术能力,数据配比会影响下游行为,学习率会和 batch size 纠缠——这些底层细节会穿透 API 边界影响上层结果。这和编程语言、操作系统那种「几乎不漏」的抽象不同,所以要做基础研究,必须撕开这堆抽象。
- 理解 = 动手建造(”What I cannot create, I do not understand” —— Feynman)。
提示
为什么「漏的抽象」是选修这门课的核心理由?如果抽象不漏(像 TCP/IP 之于网页开发),上层研究者确实不需要懂底层。但语言模型的每个「实现细节」——分词粒度、数据去重、优化器数值稳定性——都会以意想不到的方式改变模型行为。你无法只靠调 API 定位这些问题,正如无法只靠开车学会修发动机。
知识的三个层次(哪些能从小模型迁移到大模型):
| 层次 | 例子 | 可迁移性 |
|---|---|---|
| 机制(mechanics) | Transformer 怎么算、并行怎么切 | 完全可迁移 |
| 心态(mindset) | 榨干硬件、认真对待规模 | 完全可迁移 |
| 直觉(intuitions) | 哪种数据配比好、SwiGLU 是否有用 | 只能部分迁移 |
这个三分法解释了「课上只训 <1B 小模型」为什么仍然有价值:机制和心态在任何规模下都成立;但直觉类结论(比如某个架构 trick 在 1B 有效)不保证在 100B 下仍然成立——这正是「涌现行为随规模变化」带来的根本限制。
和其他课程的区别:
- CS224n(自然语言处理):覆盖 NLP 各种任务(翻译、QA、摘要),模型训练只是其中一部分。
- CS336:专注预训练语言模型本身,深入数据 / 系统 / 缩放 / 对齐全栈——一次作业的工作量约等于 CS224n 五次作业加 final project 的总和(Percy 原话,针对作业 1)。
规模与现实的落差:
- GPT-4 据传有 1.8T 参数、训练成本约 \$100M;xAI 用 20 万张 H100 的集群训练 Grok;OpenAI/NVIDIA/Oracle 的 Stargate 项目计划 4 年投入 \$500B。
- 这门课训练的是 <1B 参数的小模型——但如上所述,**机制和心态可迁移**,直觉只能部分迁移。
**效率是主旋律**:
XMATHXPLACEHOLDERX0XENDX
符号含义:模型最终的能力(accuracy)由两个因子决定——资源(算力、数据,通常是给定的预算)和效率(单位资源能换来多少能力)。资源无法轻易改变,所以这门课的核心问题被定义为:**给定固定的算力与数据预算,如何训出最好的模型**。
- 对 bitter lesson 的正确解读**不是**「规模就是一切、算法不重要」,而是「**能随规模扩展的算法**才是一切」——scale 与算法效率相乘才是最终能力。
- 佐证:OpenAI 的研究显示,达到 AlexNet 水平的 ImageNet 精度所需算力在 2012–2019 年间下降了 **44 倍**——算法效率的进步与摩尔定律同样惊人。
**效率如何驱动全课的设计决策**(今天我们是 compute-constrained 的,所以每个环节都围绕效率展开):
- **数据处理**:不要把宝贵的梯度更新浪费在低质量、重复的数据上——这是 Lecture 13 · 数据 I 的主题。
- **分词**:直接吃原始字节更优雅,但目前计算上不划算——这正是本讲 BPE 存在的理由。
- **架构**:大量改动都是为了省内存或省 FLOPs(KV cache 共享、滑动窗口注意力等),见 Lecture 3 · 架构与超参数。
- **训练**:单个 epoch 就够——多看新数据比反复看旧数据更划算。
- **缩放定律**:在小模型上调超参数,外推到大模型,避免在大模型上试错,见 Lecture 9 · 缩放定律 I:基础。
- **对齐**:如果模型可以事后对齐到具体用途,基础模型本身可以更小更通用,见 Lecture 15 · 对齐 I:SFT 与 RLHF。
**课程结构(五个单元 + 五次作业)**:
| 单元 | 主题 | 作业 |
| :---: | :--- | :--- |
| **1. Basics** | Transformer、优化器、训练循环 | A1:BPE 分词器 + Transformer + AdamW |
| **2. Systems** | GPU、kernel、并行 | A2:Triton kernel + 数据并行 + 优化器状态分片 |
| **3. Scaling** | 缩放定律、推理优化 | A3:拟合缩放定律、预测最优超参数 |
| **4. Data** | 数据源、去重、过滤 | A4:从 Common Crawl 构建预训练数据 |
| **5. Alignment** | SFT、DPO、RL | A5:SFT + DPO + GRPO |
**程序语言的类比**:
```mermaid
graph LR
L["自然语言
(Language)"] --> T["分词
(Tokenization)"] T --> TT["token 序列"] TT --> LM["语言模型
(Language Model)"] LM --> D["分布
P(next token)"] P["程序
(Program)"] --> LEX["词法分析
(Lexing)"] LEX --> TOK["token 序列"] TOK --> PAR["解析
(Parsing)"] PAR --> AST["抽象语法树
(AST)"] style L fill:#e1f5fe,stroke:#0277bd style P fill:#e1f5fe,stroke:#0277bd style T fill:#ffe0b2,stroke:#e65100 style LEX fill:#ffe0b2,stroke:#e65100 style LM fill:#c8e6c9,stroke:#2e7d32 style PAR fill:#c8e6c9,stroke:#2e7d32 ``` 自然语言与程序语言都是结构化的字符序列——都需要先**分词**,再交给模型 / 解析器处理。但语言模型只学到分布,不像解析器那样显式构建语法树。 --- ## 1. 分词:把文本变成模型能吃的东西 ### 1.1 为什么需要分词 语言模型的输入和输出都是**离散的 token 序列**——模型不直接吃字符串,必须先把文本切成一个个「单位」,再映射为整数 ID。分词器(tokenizer)就是这个双向映射:`encode: str → list[int]`,`decode: list[int] → str`。 衡量分词器好坏的核心指标是**压缩率(compression ratio)**: XMATHXPLACEHOLDERX1XENDX 即平均每个 token 「吃掉」多少字节。压缩率越高,同一段文本的序列越短——训练和推理的每一步都在为序列长度付费,所以压缩率直接就是钱。 **四种粒度的选择**(按讲义顺序:字符 → 字节 → 词 → 子词): | 粒度 | 词表大小 | 压缩率(字节/token) | 致命缺陷 | | :--- | :---: | :---: | :--- | | **字符级**(Unicode 码点) | ~15 万 | ≈1(英文) | 词表巨大且稀有字符(如生僻 emoji)的 token 训练严重不足 | | **字节级** | 256 | 恒为 1 | 序列太长——上下文窗口和注意力的开销都按 token 数计费 | | **词级** | 无上界(数十万起) | 高 | OOV 问题:没见过的词只能映射到 `UNK`,污染困惑度 | | **子词级(BPE)** | 数万–十几万(可控) | 英文约 4 | 需要额外训练一个分词器;对数字、拼写类任务不友好 |注意
字符级和字节级不是一回事。字符级以 Unicode 码点为单位,词表约 15 万(Unicode 收录的字符数);字节级以 UTF-8 字节为单位,词表恰好 256。前者的问题是词表大且长尾稀疏,后者的问题是压缩率恒为 1、序列过长。BPE 之所以从字节(而非字符)出发,正是要同时避开这两个坑。
说明
现代语言模型几乎都用子词分词——BPE(Byte-Pair Encoding)是最流行的选择,GPT / LLaMA / Qwen 都用它(LLaMA 1/2 用的是 SentencePiece 实现的 BPE 变体)。
### 1.2 从 UTF-8 字节到 token **UTF-8 编码**是一个通用的「字符 → 字节序列」映射——任何 Unicode 字符都能表示成 1–4 个字节: ```python # 英文字母 1 字节,中文 3 字节,emoji 4 字节 "hello".encode("utf-8") # b'hello' (5 字节) "你好".encode("utf-8") # b'\xe4\xbd\xa0\xe5\xa5\xbd' (6 字节) "🌍".encode("utf-8") # b'\xf0\x9f\x8c\x8d' (4 字节) ``` BPE 的起点是**字节序列**——任何文本先转成 UTF-8 字节,保证词表是**字节安全的(byte-level fallback)**:即使遇到训练时没见过的字符,也能退化到单字节 token 表示。提示
子词分词的妙处在于「频率决定粒度」:高频词(”the”、”ing”)合并成单个 token 享受高压缩率,低频词(人名、错别字)自动退化为更细的子词甚至字节——常见的东西粗粒度处理,罕见的东西细粒度兜底。这让词表大小、序列长度、开放词表三者同时可控,是一种数据驱动的自适应折中。
说明
字节级 BPE 保证了真正的开放词表——不需要
UNKtoken,任何文本都能被编码;只要 token 序列合法,也总能解码回原文本。### 1.3 BPE 训练算法:贪心合并高频 pair BPE(Byte-Pair Encoding)1994 年由 Philip Gage 提出,本来是个数据压缩算法;Sennrich et al.(2016)把它引入 NLP 做机器翻译的子词切分;GPT-2 把它扩展到字节级并推广开来。核心思想:从 256 个单字节 token 开始,**迭代地把语料中最常见的相邻 token 对(pair)合并成一个新 token**——分词器不是人工设计的,而是从数据里「训练」出来的。 **算法伪代码**: ```python # 输入:训练语料 string,合并次数 num_merges # 输出:词表 vocab(id → bytes)、合并规则 merges(pair → new_id) indices = list(string.encode("utf-8")) # 初始:每个字节是一个 token (0–255) vocab = {i: bytes([i]) for i in range(256)} # 256 个单字节 token merges = {} for i in range(num_merges): # 1. 统计所有相邻 pair 的出现次数 counts = {} for (a, b) in zip(indices, indices[1:]): counts[(a, b)] = counts.get((a, b), 0) + 1 # 2. 找出频率最高的 pair best_pair = max(counts, key=counts.get) # 3. 分配新 token ID,更新词表和合并规则 new_id = 256 + i merges[best_pair] = new_id vocab[new_id] = vocab[best_pair[0]] + vocab[best_pair[1]] # 4. 在当前序列中把这个 pair 全部替换成新 token indices = merge(indices, best_pair, new_id) ```注意
解码方向有个陷阱:任意的 token ID 序列拼出的字节串不一定是合法 UTF-8(比如恰好截断在一个多字节字符中间)。模型自由生成时可能产生这种序列,所以实际实现中
decode要用bytes.decode("utf-8", errors="replace"),把非法片段替换成 U+FFFD(�),而不是直接抛异常。例子
初始字节序列(a=97, b=98, c=99, d=100):
[97, 97, 97, 98, 100, 97, 97, 97, 98, 97, 99](11 个 token)迭代 1:pair 计数中
(97,97)出现 4 次最高 → 合并为 256(b'aa')。从左到右贪心替换(aaa变成[256, 97]):
[256, 97, 98, 100, 256, 97, 98, 97, 99]迭代 2:
(256,97)和(97,98)各出现 2 次打平,本实现取先出现者(256,97)→ 合并为 257(b'aaa'):
[257, 98, 100, 257, 98, 97, 99]迭代 3:
(257,98)出现 2 次最高 → 合并为 258(b'aaab'):
[258, 100, 258, 97, 99](5 个 token)压缩率从 1.0(11 字节 11 token)提升到 2.2(11 字节 5 token)——高频模式
aaab被「压」成了一个 token。**训练复杂度与加速**: - 朴素实现每次迭代都要扫一遍全序列统计 pair 再替换,各是 $O(N)$;共 $M$ 次合并($M$ = 词表大小 − 256,通常数万),总计 $O(MN)$——在 GB 级语料上不可接受。 - 作业 1 的关键优化:合并 `(a,b)` 只会改变**它两侧相邻的 pair** 的计数,所以可以增量更新计数而不必全量重扫;配合预分词(见 1.5),还能把计数从「逐 token」变成「逐词型 × 词频」,语料再大也只需处理去重后的词型集合。 ### 1.4 BPE 编码与解码 训练完成后,我们得到两张表: 1. **vocab**:`{token_id: bytes}` — 每个 token 对应的字节序列 2. **merges**:`{(id1, id2): merged_id}` — 哪些 pair 可以合并、合并成什么 **编码(text → token IDs)**: ```python def encode(string: str) -> list[int]: indices = list(string.encode("utf-8")) # 初始:字节序列 # 按训练时的顺序,依次应用所有合并规则 for (pair, new_id) in merges.items(): indices = merge(indices, pair, new_id) return indices ```注意
- 贪心 ≠ 最优:BPE 每一步只看当前最高频 pair,不保证全局最优的词表。但它实现简单、训练快,实践效果好。
- 平局处理是实现细节:多个 pair 频率相同时选哪个(插入顺序、字典序)会影响最终词表——想复现 GPT-2 的分词器,必须匹配它的平局规则。
**解码(token IDs → text)**: ```python def decode(indices: list[int]) -> str: byte_seq = b''.join(vocab[i] for i in indices) # 拼回字节序列 return byte_seq.decode("utf-8", errors="replace") # 非法字节替换为 U+FFFD ``` **作业 1 的优化方向**: - 上面的 `encode()` 会遍历**全部**几万条合并规则,即使输入只有几个词——只考察输入中实际出现的 pair、用优先队列按规则序取最早可用的合并,可以快几个数量级。 - 支持**特殊 token**(如 `<|endoftext|>`):训练语料按文档边界插入,编码时整体识别、绝不被 BPE 切开。 - 使用**预分词正则**(GPT-2 的 regex,见 1.5):先把文本按语言边界切块,再对每块内部做 BPE,避免产生跨词合并。 - **性能**:预分词与计数可以并行化(多进程 map-reduce),这是在 OpenWebText 规模语料上训练分词器的前提。 ### 1.5 真实分词器的复杂度 **GPT-2 的预分词正则**(出自讲义): ```python pattern = r"""'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""" ``` 这条正则把文本先切成几类片段,BPE 只在片段内部做合并: - `'(?:[sdmt]|ll|ve|re)`:英文缩写后缀(`'s`、`'t`、`'ll`、`'ve`…),单独成段以保持一致切分; - `?\p{L}+`:连续字母(可带一个前导空格)——空格被吸附到后面的词上; - `?\p{N}+`:连续数字(可带前导空格)——注意字母和数字被强制分开; - `?[^\s\p{L}\p{N}]+`:连续标点 / 符号; - `\s+(?!\S)` 与 `\s+`:处理尾部与连续空白。 先切块、再对每块内部做 BPE——**防止合并跨越词边界**(否则可能出现 `e t` 这种横跨两个词的高频 token),保留一定语言结构。 **特殊 token**: - `<|endoftext|>`:文档分隔符(GPT 系列)——它让模型知道「上文到此为止」,也让训练数据能把多篇文档拼接进同一个 batch。 - `<|begin_of_text|>`、`<|start_header_id|>` 等(LLaMA 3):对话格式的结构标记。 - 这些 token 在词表中占位、编码时整体识别、不参与 BPE 合并。 **词表大小的选择**: | 分词器 | 词表大小 | 备注 | | :--- | ---: | :--- | | GPT-2 / GPT-3 | 50,257 | = 256 字节 + 50,000 次合并 + 1 个 `<\|endoftext\|>` | | LLaMA 1/2 | 32,000 | SentencePiece BPE | | LLaMA 3 | 128,256 | 词表扩大以改善多语言压缩率 | | Qwen 2.5 | 151,643 | 多语言词表更大 |注意
合并顺序必须与训练时一致(从早到晚)。因为后期的合并规则引用的是前期合并产生的新 token ID——例如规则
(256, 97) → 257只有在(97, 97) → 256先被应用后才可能匹配上。乱序应用会得到与训练分布不一致的切分。### 1.6 观察真实分词器的行为(tiktokenizer 演示) 用 [Tiktokenizer](https://tiktokenizer.vercel.app/) 观察 GPT-2 分词器,有几个值得记住的现象: - **空格属于后面的词**:`" world"`(带前导空格)是一个 token,和 `"world"` 是**不同**的 token——这是预分词正则 `?\p{L}+` 的直接后果。 - **同一个词在不同位置切分不同**:句首的 `"Hello"` 和句中的 `" Hello"` ID 不同;大小写变体也各自独立。模型必须分别学习它们的语义,这是子词方案的隐性代价。 - **长数字被切成不规则片段**:GPT-2 的 BPE 会把 `1234567` 切成任意学到的数字片段;GPT-4 的 cl100k 则强制数字最多三位一组。数字切分的不一致是 LLM 算术能力差的原因之一。 - **压缩率因语言而异**:英文约 4 字节/token,而 GPT-2 时代的中文常常一个汉字要 2–3 个 token——对非拉丁语言既贵又低效,这推动了后来多语言词表的扩张。 **tokenization-free 方法**:ByT5、MegaByte、BLT(Byte Latent Transformer)等工作尝试直接建模字节、彻底绕开分词器——更优雅,也消除了上述所有怪癖。但截至目前,**没有任何 frontier 模型采用**:字节序列比 BPE 序列长约 4 倍,而注意力的代价是 $O(T^2)$,压缩率的损失在大规模下太昂贵。这是「效率驱动设计决策」的又一例证。 --- ## 2. 课程工作量与期望 ### 2.1 五次作业在做什么 **作业 1(Basics:分词 + Transformer)**: - 实现完整的 BPE 分词器(训练 / 编码 / 解码 / 特殊 token / 正则预分词),并在真实语料上高效训练; - 从零实现 Transformer(多头注意力、RMSNorm、SwiGLU、RoPE)、交叉熵损失与 AdamW 优化器——只允许用 PyTorch 的张量原语,不允许用 `nn.Transformer` 之类的现成模块; - 排行榜:在 **90 分钟 H100 预算内**在 OpenWebText 上把困惑度压到最低——从第一次作业开始就在训练「效率心态」。 **作业 2(Systems:kernel 与并行)**: - 用 Triton 手写 RMSNorm 等 kernel,对比 PyTorch 原生实现的性能; - 实现分布式数据并行训练与**优化器状态分片**(ZeRO 思想的核心); - 系统性地做 benchmark 与 profiling。流水线 / 张量并行等更复杂的策略在 Lecture 7 · 并行 I:基础 与 Lecture 8 · 并行 II:分布式训练实战 中讲授。 **作业 3(Scaling:缩放定律)**: - 通过课程提供的「训练 API」提交小模型训练任务(隔离了真实算力消耗); - 在固定 FLOPs 预算内设计实验、拟合缩放定律,**预测**更大规模下的最优超参数与损失——体验「用小模型的实验外推大模型的决策」这一工业界核心工作流。 **作业 4(Data:数据)**: - 把 Common Crawl 的原始 HTML 转成干净文本; - 训练质量分类器与有害内容分类器做过滤; - 用 MinHash 做模糊去重;排行榜:固定 token 预算下的困惑度——数据质量直接决定模型质量,见 Lecture 13 · 数据 I。 **作业 5(Alignment:对齐)**: - 监督微调(SFT):少量高质量示例即可激活基座模型已有的能力(LIMA 的核心论点); - 实现 DPO(直接偏好优化)与 GRPO——对齐算法的两条主线分别在 Lecture 15 · 对齐 I:SFT 与 RLHF 与 Lecture 16 · 对齐 II:RLVR 展开。提示
词表大小是一场三方拉锯:词表越大 → 压缩率越高、序列越短(同样上下文窗口装下更多内容、每个文本训练/推理更便宜);但嵌入表和输出 softmax 也随 $V$ 线性变大,且长尾 token 出现次数太少、学不充分。5 万–15 万是当前的经验甜点区,多语言模型偏向更大词表——因为要对多种文字都维持可接受的压缩率。
### 2.2 心态:效率优先,全栈思维 ```mermaid graph TD A["效率注意
这门课很硬核——Percy 在第一讲反复警告:作业 1 的工作量就抵得上 CS224n 全部五次作业加 final project。不要因为「分词器听起来简单」而低估它:在大语料上高效训练 BPE 需要认真的算法与工程优化。
(Efficiency)"] --> B["算法效率
FLOPs per token"] A --> C["系统效率
MFU / memory bandwidth"] A --> D["数据效率
token 质量 / 去重"] B --> E["更好的架构
(Transformer变体)"] B --> F["更好的优化器
(AdamW / Muon)"] C --> G["kernel 融合
(Flash Attention)"] C --> H["并行策略
(3D parallelism)"] D --> I["数据过滤
(质量筛选)"] D --> J["合成数据
(蒸馏 / 自生成)"] style A fill:#ffe0b2,stroke:#e65100 style B fill:#e1f5fe,stroke:#0277bd style C fill:#e1f5fe,stroke:#0277bd style D fill:#e1f5fe,stroke:#0277bd ``` **全栈 = 不只写模型代码**: - 懂硬件:GPU 架构、内存层次、tensor core——见 Lecture 5 · GPU; - 懂系统:CUDA kernel、通信原语、分布式协议; - 懂算法:缩放定律、优化器、架构设计; - 懂数据:去重、过滤、质量评估。--- ## 3. 当前格局:开放模型崛起 ### 3.1 工业化的语言模型 讲义开头展示了一组令人震撼的数字: | 模型 | 参数量 | 训练数据 | 训练成本 / 规模 | | :--- | :---: | :---: | :--- | | GPT-4 | ~1.8T(传闻) | 未公开 | ~\$100M(传闻) | | LLaMA 3.1 | 405B | 15.6T tokens | 3.8×10²⁵ FLOPs,约 1.6 万张 H100 | | Qwen 2.5 | 72B | 18T tokens | 未公开 | | DeepSeek-V3 | 671B(37B 激活) | 14.8T tokens | 278.8 万 H100·小时 ≈ \$560 万 |提示
这门课的目标不是让你训出 SOTA 模型,而是让你在看到 DeepSeek / LLaMA 的技术报告时,能读懂每一行、知道每个决策背后的资源账。「懂技术报告」这个能力本身,就是通过亲手实现每一层换来的。
**frontier models 的不透明**:GPT-4 技术报告明确说「考虑到竞争格局和安全影响,本报告不包含架构、硬件、训练算力、数据集构建、训练方法的进一步细节」。 ### 3.2 开放模型的价值 开放程度是一个光谱,不是非黑即白: - **OLMo**(AI2 / Allen Institute):**完全开放**——权重、训练代码、数据、中间 checkpoint 全公开,是唯一能端到端复现的选项; - **LLaMA**(Meta):权重开放、技术报告详细,但训练数据不公开; - **Qwen**(阿里巴巴):权重开放、报告详细; - **DeepSeek**:权重开放,MoE 架构与训练基础设施的细节透明度在开放模型中数一数二(架构详见 Lecture 4 · 混合专家模型 MoE)。说明
训练一个 $N$ 参数、$D$ token 的稠密 Transformer,总浮点运算量约为
XMATHXPLACEHOLDERX2XENDX
其中 $C$ 是训练 FLOPs,$N$ 是参数量,$D$ 是训练 token 数;系数 6 = 前向 2 + 反向 4(推导见 Lecture 2 · PyTorch 与资源核算)。代入 LLaMA 3.1:$6 \times 405\times10^9 \times 15.6\times10^{12} \approx 3.8\times10^{25}$,与 Meta 实报吻合。顺带一提 Chinchilla 的计算最优配比 $D^\ast \approx 20N$:LLaMA 3.1 用了约 $38N$ 的 token,是刻意「过训练」——多花训练算力换更小的模型,摊薄推理成本。缩放定律的完整推导见 Lecture 9 · 缩放定律 I:基础。
--- ## 4. 从分词到语言模型:下一步是什么 分词只是第一步——把文本变成 `[token_id₁, token_id₂, ...]` 的整数序列。接下来: ```mermaid flowchart LR TEXT["文本说明
开放模型是研究的基石——只有看到完整的训练过程、数据配比、超参数选择,才能做可复现的科学研究。这门课大量引用 OLMo / LLaMA / DeepSeek 的公开细节作为教学素材,正是因为 frontier 闭源模型「无细节可讲」。
'the quick brown fox'"] --> TOK["分词器"] TOK --> IDS["token IDs
[1820, 4062, 14198, 39935]"] IDS --> EMB["嵌入层
(V, D)"] EMB --> VEC["嵌入向量
(T, D)"] VEC --> TF["Transformer
注意力 + MLP"] TF --> LOGITS["logits
(T, V)"] LOGITS --> NEXT["预测下一个 token"] style TOK fill:#ffe0b2,stroke:#e65100 style EMB fill:#e1f5fe,stroke:#0277bd style TF fill:#c8e6c9,stroke:#2e7d32 style LOGITS fill:#f3e5f5,stroke:#7b1fa2 ``` **下一讲(Lecture 2 · PyTorch 与资源核算)预告重点**: - **PyTorch 张量与内存布局**:浮点格式(fp32 / fp16 / bf16 / fp8)、storage 与 stride、einops 风格的张量操作; - **FLOPs 核算**:$6ND$ 公式的拆解(前向 $2ND$、反向 $4ND$)、MFU(模型 FLOPs 利用率); - **内存估算**:参数 / 梯度 / 优化器状态 / 激活各占多少; - **训练循环全件**:初始化、数据加载(memmap、pinned memory)、优化器(SGD → AdamW)、checkpointing、混合精度。--- ## 总结 ```mermaid mindmap root((Lecture 1提示
napkin math(餐巾纸计算)是下一讲的主旋律——训任何东西之前,先用几次乘法回答「放得下吗、要多久、瓶颈在哪」。这个习惯是全课所有作业的第一道工序。
概览与分词)) 课程定位 从零构建 vs 调 API 漏的抽象 机制与心态可迁移 五单元:Basics / Systems / Scaling / Data / Alignment 效率是主线 accuracy = efficiency x resources 算法效率 44 倍提升 全栈思维:算法 + 系统 + 数据 分词 字符 / 字节 / 词 / 子词权衡 压缩率 = 字节数 / token 数 BPE:贪心合并高频 pair 字节安全:UTF-8 → 开放词表 预分词正则与特殊 token 工业化现实 GPT-4: 1.8T 参数 / $100M
6ND 法则
开放模型:OLMo / LLaMA / Qwen / DeepSeek
```
关键要点:
- 从零构建 = 理解全栈:这门课不教怎么调 Hugging Face API,而是让你亲手写分词器、Transformer、训练循环、分布式并行——因为语言模型的抽象是漏的,只有实现过才能真正理解。
- 效率是贯穿全课的主线:$\text{accuracy} = \text{efficiency} \times \text{resources}$——资源是给定的,能优化的只有效率;数据处理、分词、架构、训练策略的几乎所有设计决策都由效率驱动。
- BPE 是现代语言模型的标配分词方案:从字节出发、贪心合并高频 pair、保证字节安全(无
UNK)。训练算法简单但有效;压缩率(字节/token)是衡量它的核心指标。 - 分词不是孤立的:预分词正则(避免跨词合并)、特殊 token(文档边界)、词表大小(压缩率 vs 嵌入表大小 vs 长尾学习)都是权衡后的工程决策;空格吸附、数字切分等怪癖会直接影响模型行为。
- 开放模型是研究的基石:GPT-4 不公开细节、OLMo / DeepSeek 高度透明——只有后者能支撑可复现的科学研究。
下一讲预告:
Lecture 2 · PyTorch 与资源核算——深入 PyTorch 的张量、内存布局、浮点格式(fp32 / bf16 / fp8),掌握 $6ND$ 公式的前向 / 反向拆解,学会估算参数 / 梯度 / 优化器 / 激活的内存占用,理解混合精度训练、随机性控制、数据加载器与 checkpointing。napkin math 贯穿全场——用简单乘法快速判断训练配置是否可行。这是后续所有作业的基础工具集。
复习自测
题目
因为压缩率恒为 1(每字节一个 token),序列长度会比 BPE 长约 4 倍。而注意力的计算量是 $O(T^2)$、上下文窗口按 token 计费、生成时每个 token 都要一次完整前向——序列变长 4 倍意味着训练与推理成本全面上涨。ByT5 / MegaByte / BLT 等 tokenization-free 方案试图解决这个问题,但尚未在 frontier 规模上被验证。
题目
因为后期规则引用的是前期合并产生的新 token ID。例如规则 (256,97)→257 中的 256 是由更早的 (97,97)→256 产生的——如果先尝试应用 (256,97),序列里根本还没有 256,规则匹配不上,最终切分会与训练时的分布不一致,模型看到的 token 统计特性就变了。
题目
初始:[97,98,97,98,97,98,97,98]。
迭代 1:(97,98) 出现 4 次最高 → 合并为 256(b'ab'),得 [256,256,256,256]。
迭代 2:(256,256) 最高(朴素计数会数出 3 次相邻对,但从左到右贪心替换是不重叠的)→ 合并为 257(b'abab'),得 [257,257]。
8 字节 → 2 个 token,压缩率 4.0。
题目
变好:压缩率提高、序列变短——同样的上下文窗口装下更多文本,每段文本的训练与推理成本下降;多语言(尤其非拉丁文字)的压缩率显著改善。
变差:嵌入表与输出 softmax 的参数量、显存、计算都随 $V$ 线性增长;长尾 token 出现频次更低、嵌入学习不充分;小模型上词表参数占比可能过大。
题目
$C \approx 6 \times 72\times10^9 \times 18\times10^{12} \approx 7.8\times10^{24}$ FLOPs——约为 LLaMA 3.1 405B($3.8\times10^{25}$)的五分之一。这类两秒钟的估算正是「资源核算心态」的日常应用。
参考资料
- 💻 lecture_01.py(本讲可执行讲义)
- 📄 Neural Machine Translation of Rare Words with Subword Units(Sennrich et al., 2016) — BPE 在 NLP 中的开创性应用
- 📄 Language Models are Unsupervised Multitask Learners / GPT-2(Radford et al., 2019) — 字节级 BPE 分词器 + 预分词正则
- 📄 GPT-4 Technical Report(OpenAI, 2023) — frontier model 的不透明现实
- 📄 LLaMA: Open and Efficient Foundation Language Models(Touvron et al., 2023)
- 📄 LLaMA 3 Model Card — 405B 参数 / 15.6T tokens / 3.8×10²⁵ FLOPs
- 📄 DeepSeek-V3 Technical Report(2024) — 开放的 MoE 架构与训练细节
- 📄 Training Compute-Optimal Large Language Models / Chinchilla(Hoffmann et al., 2022) — 计算最优配比 D ≈ 20N
- 📄 AI and Efficiency(Hernandez & Brown, OpenAI, 2020) — ImageNet 算法效率 7 年 44 倍
- 📄 ByT5(Xue et al., 2021) · MegaByte(Yu et al., 2023) · Byte Latent Transformer(Pagnoni et al., 2024) — tokenization-free 路线
- 🌐 CS336 课程主页(Spring 2025)
- ▶️ Lecture 1 视频(YouTube)
- 🔗 Tiktokenizer 在线体验 — 可视化 BPE 分词过程