- [测试用例总结](#%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E6%80%BB%E7%BB%93)
- [Level 1: 原子级组件测试 (最小粒度)](#Level%201:%20%E5%8E%9F%E5%AD%90%E7%BA%A7%E7%BB%84%E4%BB%B6%E6%B5%8B%E8%AF%95%20(%E6%9C%80%E5%B0%8F%E7%B2%92%E5%BA%A6))
- [Level 2: 组件级测试 (小型组合单元)](#Level%202:%20%E7%BB%84%E4%BB%B6%E7%BA%A7%E6%B5%8B%E8%AF%95%20(%E5%B0%8F%E5%9E%8B%E7%BB%84%E5%90%88%E5%8D%95%E5%85%83))
- [Level 3: 模块级测试 (复杂组合组件)](#Level%203:%20%E6%A8%A1%E5%9D%97%E7%BA%A7%E6%B5%8B%E8%AF%95%20(%E5%A4%8D%E6%9D%82%E7%BB%84%E5%90%88%E7%BB%84%E4%BB%B6))
- [Level 4: 系统级测试 (完整系统/管道)](#Level%204:%20%E7%B3%BB%E7%BB%9F%E7%BA%A7%E6%B5%8B%E8%AF%95%20(%E5%AE%8C%E6%95%B4%E7%B3%BB%E7%BB%9F/%E7%AE%A1%E9%81%93))
- [项目结构](#%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84)
- [项目代码执行流程](#%E9%A1%B9%E7%9B%AE%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B)
- [1. UV 环境管理器启动](#1.%20UV%20%E7%8E%AF%E5%A2%83%E7%AE%A1%E7%90%86%E5%99%A8%E5%90%AF%E5%8A%A8)
- [2 Pytest 初始化流程](#2%20Pytest%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B)
- [3 测试执行流程(以 test_model.py::test_linear 为例)](#3%20%E6%B5%8B%E8%AF%95%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B%EF%BC%88%E4%BB%A5%20test_model.py::test_linear%20%E4%B8%BA%E4%BE%8B%EF%BC%89)
- [完整调用栈示例](#%E5%AE%8C%E6%95%B4%E8%B0%83%E7%94%A8%E6%A0%88%E7%A4%BA%E4%BE%8B)
- [实现各组件的代码](#%E5%AE%9E%E7%8E%B0%E5%90%84%E7%BB%84%E4%BB%B6%E7%9A%84%E4%BB%A3%E7%A0%81)
- [训练数据](#%E8%AE%AD%E7%BB%83%E6%95%B0%E6%8D%AE)
- [test_transformer_lm 的流程](#test_transformer_lm%20%E7%9A%84%E6%B5%81%E7%A8%8B)
- [模型能做什么?](#%E6%A8%A1%E5%9E%8B%E8%83%BD%E5%81%9A%E4%BB%80%E4%B9%88%EF%BC%9F)
- [1. 文本生成](#1.%20%E6%96%87%E6%9C%AC%E7%94%9F%E6%88%90)
- [2. 文本补全](#2.%20%E6%96%87%E6%9C%AC%E8%A1%A5%E5%85%A8)
- [3. 零样本学习 (经过大规模训练后)](#3.%20%E9%9B%B6%E6%A0%B7%E6%9C%AC%E5%AD%A6%E4%B9%A0%20(%E7%BB%8F%E8%BF%87%E5%A4%A7%E8%A7%84%E6%A8%A1%E8%AE%AD%E7%BB%83%E5%90%8E))
- [核心组件的作用](#%E6%A0%B8%E5%BF%83%E7%BB%84%E4%BB%B6%E7%9A%84%E4%BD%9C%E7%94%A8)
- [Assignment 1: Basics (GitHub: stanford-cs336/assignment1-basics) 通俗解释](#Assignment%201:%20Basics%20(GitHub:%20stanford-cs336/assignment1-basics)%20%E9%80%9A%E4%BF%97%E8%A7%A3%E9%87%8A)
- [它干嘛的?](#%E5%AE%83%E5%B9%B2%E5%98%9B%E7%9A%84%EF%BC%9F)
- [要实现的东西](#%E8%A6%81%E5%AE%9E%E7%8E%B0%E7%9A%84%E4%B8%9C%E8%A5%BF)
- [训练 mini LM 的过程(通俗版)](#%E8%AE%AD%E7%BB%83%20mini%20LM%20%E7%9A%84%E8%BF%87%E7%A8%8B%EF%BC%88%E9%80%9A%E4%BF%97%E7%89%88%EF%BC%89)
# Summary
侧重于 [[Transformer]] 里所有组件的实现
Assignment 1: 你实现的是一个 GPT 风格的自回归语言模型,而不是 Seq2Seq!这个模型的意义:
你实现的是 **GPT-2 / GPT-3 的核心架构**!虽然你的模型规模很小,但其工作原理与当今最先进的大语言模型完全相同。
- ChatGPT 的基础就是这种 Decoder-only 架构。
- 模型通过“预测下一个词”这一简单任务来学习语言的复杂规律。
- 这个基础模型可以通过微调(fine-tuning)来适应各种特定的下游任务。
所以,Assignment 1 实际上是让你从零开始,亲手构建了一个迷你版的 GPT!🎉
# Cues
[[snapshot]]
[[token ID]]
[[词汇表]]
[[pt 文件]]
[[BPE]]
# Notes
## 测试驱动学习
测试层次结构:
```Java
├── Transformer功能测试 (test_model.py)
│ └── 验证Transformer计算正确性
├── 基础设施测试 (其他所有测试)
│ ├── checkpointing机制
│ ├── 优化器功能
│ ├── 数据加载
│ └── 工具函数
```
测试重要性排序
1. 🔴 最核心:test_model.py - 没有这个,模型根本跑不起来
2. 🟠 很重要:test_nn_utils.py, test_optimizer.py - 训练必需的基础设施
3. 🟡 重要:test_data.py, test_serialization.py - 实际训练时需要
4. 🟢 次要:test_tokenizer.py, test_train_bpe.py - 可以用现成的分词器代替
当前通过情况
- ✅ 基本通过:前 4 个文件的大部分测试
- ⚠️ 数值精度问题:test_model.py 中的 transformer_lm(误差 < 0.0002)
- ❌ 未通过:test_tokenizer.py 和 test_train_bpe.py 的部分测试
按照粒度从小到大排列的所有测试用例:
- **总测试文件数**: 7
- **总测试用例数**: 45
- **原子级测试**: 7个 (基础数学运算和层)
- **组件级测试**: 7个 (组合的小模块)
- **模块级测试**: 10个 (复杂功能模块)
- **系统级测试**: 21个 (完整功能和集成测试)
### Level 1: 原子级组件测试 (最小粒度)
| 测试文件 | 测试函数 | 测试目的 | |
| ---------------- | ---------------------------- | -------------------------------------------------------- | --- |
| test_nn_utils.py | test_softmax_matches_pytorch | 验证 [[softmax]] 实现与 PyTorch 一致,包括数值稳定性 | ✅ |
| test_nn_utils.py | test_cross_entropy | 验证[交叉熵 cross_entropy](交叉熵%20cross_entropy.md)损失计算,包括溢出处理 | ✅ |
| test_nn_utils.py | test_gradient_clipping | 验证[[梯度裁剪]]功能 | ✅ |
| test_model.py | test_linear | 验证[[全连接层,线性层]]实现 | ✅ |
| test_model.py | test_embedding | 验证[嵌入 embedding](嵌入%20embedding.md)实现 | ✅ |
| test_model.py | test_silu_matches_pytorch | 验证 SiLU [[激活函数]]与 PyTorch 一致 | ✅ |
| test_model.py | test_rmsnorm | 验证 RMSNorm [[归一化]]层 | ✅ |
### Level 2: 组件级测试 (小型组合单元)
| 测试文件 | 测试函数 | 测试目的 | |
| ----------------- | ------------------------------------ | ---------------------------------------------------------------------- | --- |
| test_model.py | test_swiglu | 验证 [[SwiGLU]] [[前馈网络]]组件 | ✅ |
| test_model.py | test_rope | 验证旋转[Positional Encoding 位置编码](Positional%20Encoding%20位置编码.md) (RoPE) | ✅ |
| test_model.py | test_scaled_dot_product_attention | 验证 3D 缩放点积注意力机制 | ✅ |
| test_model.py | test_4d_scaled_dot_product_attention | 验证 4D 批次化[[注意力机制]] | ✅ |
| test_optimizer.py | test_adamw | 验证 AdamW 优化器实现 | ✅ |
| test_optimizer.py | test_get_lr_cosine_schedule | 验证余弦学习率调度器,带线性预热的[[余弦退火]] | ✅ |
| test_data.py | test_get_batch | 验证训练批次数据创建 | ✅ |
### Level 3: 模块级测试 (复杂组合组件)
| 测试文件 | 测试函数 | 测试目的 | |
| ----------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | --- |
| test_model.py | test_multihead_self_attention | 验证完整的多头自注意力模块 | ✅ |
| test_model.py | test_multihead_self_attention_with_rope | 验证带位置编码的多头自注意力 | ✅ |
| test_model.py | test_transformer_block | 验证完整的 Transformer 块 (注意力+FFN+归一化)<br>注意只是随机生成几个矩阵跑了跑",没有任何实际的NLP语义内容,只是用来验证 Transformer Block 的数值计算正确性 | ✅ |
| test_tokenizer.py | test_roundtrip_empty | 验证空字符串的编码解码往返 | |
| test_tokenizer.py | test_roundtrip_single_character | 验证单个 ASCII 字符的往返 | |
| test_tokenizer.py | test_roundtrip_single_unicode_character | 验证单个 Unicode 字符的往返 | |
| test_tokenizer.py | test_roundtrip_ascii_string | 验证 ASCII 字符串的往返 | |
| test_tokenizer.py | test_roundtrip_unicode_string | 验证 Unicode 字符串的往返 | |
| test_tokenizer.py | test_roundtrip_unicode_string_with_special_tokens | 验证带特殊标记的 Unicode 字符串往返 | |
| test_tokenizer.py | test_overlapping_special_tokens | 验证重叠特殊标记的处理 | |
### Level 4: 系统级测试 (完整系统/管道)
1. test_tokenizer.py - 使用多个真实文本文件:
- GPT-2 词汇表和合并文件:gpt2_vocab.json、gpt2_merges.txt
- 测试文本:address.txt、german.txt、tinystories_sample.txt、tinystories_sample_5M.txt
- 特殊标记测试文件
2. test_train_bpe.py - 使用真实训练数据:
- 英文语料库:corpus.en
- 参考词汇表和合并文件:train-bpe-reference-vocab.json、train-bpe-reference-merges.txt
- TinyStories 样本
3. test_model.py - 使用预训练模型权重:
- 模型文件:ts_tests/model.pt、ts_tests/model_config.json
- 用于验证各个神经网络组件的实现
| 测试文件 | 测试函数 | 测试目的 | |
| --------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------- | --- |
| **完整模型测试** | | | |
| test_model.py | test_transformer_lm | 验证完整的 Transformer 语言模型<br>注意只是随机生成几个矩阵跑了跑",没有任何实际的NLP语义内容,只是用来测试计算逻辑是否正确,而不是问题本身是否有意义。| ✅ |
| test_model.py | test_transformer_lm_truncated_input | 验证截断输入的模型处理 | |
| **分词器兼容性测试** | | | |
| test_tokenizer.py | test_empty_matches_tiktoken | 验证空字符串与 [[tiktoken]] 一致 | |
| test_tokenizer.py | test_single_character_matches_tiktoken | 验证单字符与 tiktoken 一致 | |
| test_tokenizer.py | test_single_unicode_character_matches_tiktoken | 验证 Unicode 字符与 tiktoken 一致 | |
| test_tokenizer.py | test_ascii_string_matches_tiktoken | 验证 ASCII 字符串与 tiktoken 一致 | |
| test_tokenizer.py | test_unicode_string_matches_tiktoken | 验证 Unicode 字符串与 tiktoken 一致 | |
| test_tokenizer.py | test_unicode_string_with_special_tokens_matches_tiktoken | 验证特殊标记与 tiktoken 一致 | |
| **文件处理测试** | | | |
| test_tokenizer.py | test_address_roundtrip | 验证地址文本的编码解码往返 | |
| test_tokenizer.py | test_address_matches_tiktoken | 验证地址文本与 tiktoken 一致 | |
| test_tokenizer.py | test_german_roundtrip | 验证德语文本的往返 | |
| test_tokenizer.py | test_german_matches_tiktoken | 验证德语文本与 tiktoken 一致 | |
| test_tokenizer.py | test_tinystories_sample_roundtrip | 验证 TinyStories 样本的往返 | |
| test_tokenizer.py | test_tinystories_matches_tiktoken | 验证 TinyStories 与 tiktoken 一致 | |
| **特殊情况测试** | | | |
| test_tokenizer.py | test_encode_special_token_trailing_newlines | 验证尾部换行符的特殊标记编码 | |
| test_tokenizer.py | test_encode_special_token_double_newline_non_whitespace | 验证双换行符非空白字符的处理 | |
| **内存效率测试** | | | |
| test_tokenizer.py | test_encode_iterable_tinystories_sample_roundtrip | 验证迭代器编码的往返 | |
| test_tokenizer.py | test_encode_iterable_tinystories_matches_tiktoken | 验证迭代器编码与 tiktoken 一致 | |
| test_tokenizer.py | test_encode_iterable_memory_usage | 验证迭代器编码的内存使用效率 | |
| test_tokenizer.py | test_encode_memory_usage | 验证常规编码的内存使用 | |
| **BPE 训练测试** | | | |
| test_train_bpe.py | test_train_bpe | 验证基本 [BPE](BPE.md) 训练算法 | ✅ |
| test_train_bpe.py | test_train_bpe_special_tokens | 验证带特殊标记的 BPE 训练 | |
| test_train_bpe.py | test_train_bpe_speed | 验证 BPE 训练效率 (性能测试) | |
| **模型持久化测试** | | | |
| test_serialization.py | test_checkpointing | 验证模型[checkpoint](checkpoint.md)检查点的保存和加载 | ✅ |
### test_transformer_block 的流程
```Java
输入: [batch, seq, d_model] 嵌入向量
↓
单个Transformer Block:
- LayerNorm → MultiHead Attention → 残差连接
- LayerNorm → SwiGLU FFN → 残差连接
↓
输出: [batch, seq, d_model] 经过一层处理的向量
```
### test_transformer_lm 的流程
```Java
输入: [batch, seq] token IDs
↓
Token Embedding Layer: token ID → 嵌入向量
↓
多个Transformer Blocks (num_layers层):
Block 1 → Block 2 → ... → Block N
↓
Output Layer: 向量 → 词汇表概率分布
↓
输出: [batch, seq, vocab_size] 每个位置的下个token概率
```
| 测试 | 测试范围 | 输入 | 权重 | 作用 |
|-----|---------|------|------|------|
| **test_transformer_block** | **单个**Transformer块 | `in_embeddings`<br/>(已嵌入的向量) | 单个block的权重 | 测试一层的逻辑是否正确 |
| **test_transformer_lm** | **完整**语言模型 | `in_indices`<br/>(token ID) | 整个模型的权重 | 测试端到端的模型是否正确 |
## 项目结构
1. 测试文件 (tests/test_*.py) - 定义了期望的行为
2. 适配器文件 (tests/adapters.py) - 只有函数签名,全是 raise NotImplementedError
3. 空的实现目录 (cs336_basics/) - 基本是空的
项目结构:
```Java
├── pyproject.toml # 项目配置,定义依赖
├── tests/
│ ├── fixtures/ts_tests # 会放模型的权重和配置
│ ├── conftest.py # pytest 配置和 fixtures
│ ├── adapters.py # 测试适配器,连接测试和实现
│ ├── test_*.py # 具体测试用例
│ └── _snapshots/ # 预期输出的参考值
├── cs336_basics/ # 我们的实现
│ ├── nn.py # 神经网络基础组件
│ ├── attention.py # 注意力机制
│ ├── transformer.py # Transformer 架构
│ ├── training.py # 训练工具
│ └── tokenizer.py # BPE 分词器
```
## 项目代码执行流程
### 1. UV 环境管理器启动
`uv run pytest`
当执行这个命令时:
1. UV 读取 pyproject.toml(项目根目录)
- 解析项目依赖
- 创建或激活虚拟环境.venv/
- 确保所有依赖已安装
2. UV 在虚拟环境中执行 [[pytest]]
- 实际执行:`.venv/bin/python -m pytest`
### 2 Pytest 初始化流程
2.1 配置加载
```Java
# pyproject.toml 中的 pytest 配置
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
```
2.2 测试发现
```Java
Pytest 扫描 tests/ 目录,找到所有 test_*.py 文件:
tests/
├── test_model.py
├── test_nn_utils.py
├── test_optimizer.py
├── test_data.py
├── test_serialization.py
├── test_tokenizer.py
└── test_train_bpe.py
```
### 3 测试执行流程(以 test_model.py::test_linear 为例)
| 测试文件 | 测试内容 | 预期功能 | 通俗解释 |
|:--- |:--- |:--- |:--- |
| `test_model.py` | - Linear 层<br>- Embedding 层<br>- RMSNorm<br>- SiLU/SwiGLU<br>- 注意力机制<br>- RoPE<br>- Transformer 块<br>- 完整语言模型 | 验证神经网络各组件的前向传播计算正确 | **测试"积木块"能否正确工作:**<br>• 矩阵乘法对不对<br>• 注意力有没有算错<br>• 位置编码是否正确<br>• 整个模型能否正常跑通 |
| `test_nn_utils.py` | - Softmax 函数<br>- 交叉熵损失<br>- 梯度裁剪 | 验证基础数学运算和训练工具的正确性 | **测试"数学公式"实现对不对:**<br>• 概率归一化是否正确<br>• 损失计算准不准<br>• 梯度会不会爆炸 |
| `test_optimizer.py` | - AdamW 优化器<br>- 余弦学习率调度 | 验证优化器更新权重和学习率变化的逻辑 | **测试"学习策略":**<br>• 权重更新方向对不对<br>• 学习率是否按计划衰减 |
| `test_data.py` | - 批量数据采样<br>- 输入输出对齐 | 验证从数据集中正确采样训练批次 | **测试"喂数据"的方式:**<br>• 能否随机抽取连续文本<br>• 输入和标签是否错位一个token |
| `test_serialization.py` | - 保存检查点<br>- 加载检查点<br>- 恢复训练状态 | 验证模型和优化器状态的保存/恢复 | **测试"存档读档":**<br>• 训练一半断电能否继续<br>• 模型参数有没有丢失 |
| `test_tokenizer.py` | - BPE 编码/解码<br>- 特殊标记处理<br>- Unicode 支持<br>- 与 tiktoken 对比 | 验证分词器能正确处理各种文本 | **测试"文字切分":**<br>• 中文、emoji 能否处理<br>• 特殊符号会不会出错<br>• 编码解码是否可逆 |
| `test_train_bpe.py` | - BPE 训练算法<br>- 词表构建<br>- 合并规则学习 | 验证能从语料库训练出合理的分词器 | **测试"学习切词":**<br>• 能否找出高频子词<br>• 词表大小是否符合预期<br>• 训练速度够不够快 |
3.1 Fixture 加载
```python
# tests/conftest.py:20-30
@pytest.fixture
def d_model():
return 64
@pytest.fixture
def vocab_size():
return 10000
# 这些 fixture 在测试函数需要时自动注入
```
3.2 测试函数执行
```python
# tests/test_model.py:19-27
def test_linear(ts_state_dict, in_embeddings):
# 1. 从 fixture 获取权重
w1_weight = ts_state_dict[0][f"layers.0.ffn.w1.weight"]
# 2. 调用 adapters.py 中的包装函数
actual_output = run_linear(
d_in=d_model,
d_out=d_ff,
weights=w1_weight,
in_features=in_embeddings,
)
```
3.3 适配器调用
```python
# tests/adapters.py:24-51
def run_linear(
d_in: int,
d_out: int,
weights: Float[Tensor, " d_out d_in"],
in_features: Float[Tensor, " ... d_in"],
) -> Float[Tensor, " ... d_out"]:
# 原本是: raise NotImplementedError
# 现在是: 调用我们的实现
return linear(weights, in_features)
```
3.4 实际实现执行
```python
# cs336_basics/nn.py:10-15
def linear(
weights: Float[Tensor, "d_out d_in"],
in_features: Float[Tensor, "... d_in"],
) -> Float[Tensor, "... d_out"]:
"""Apply linear transformation: out = in @ weights.T"""
return in_features @ weights.T # 实际的矩阵乘法
```
3.5 结果验证
# tests/test_model.py:29
numpy_snapshot.assert_match(actual_output, atol=1e-5)
这会:
5. 加载预期结果:tests/_snapshots/test_linear.npz
6. 比较实际输出和预期输出
7. 如果误差在容忍范围内,测试通过
## 完整调用栈示例
以 test_scaled_dot_product_attention 为例:
1. uv run pytest tests/test_model.py::test_scaled_dot_product_attention
↓
2. pytest 加载 conftest.py 中的 fixtures
↓
3. 执行 test_model.py:65 的测试函数
↓
4. 调用 adapters.py:90 run_scaled_dot_product_attention()
↓
5. 调用 cs336_basics/attention.py:10 scaled_dot_product_attention()
↓
6. 执行实际计算:
- scores = Q @ K.T / sqrt(d_k) (第20行)
- 应用 mask (第23-30行)
- softmax (第33行)
- 返回 attn_weights @ V (第36行)
↓
7. numpy_snapshot.assert_match() 验证结果
↓
8. 测试通过 ✓ 或失败 ✗
9. 测试失败时的调试流程
当测试失败时,pytest 会显示:
E AssertionError:
E Not equal to tolerance rtol=0.0001, atol=1e-06
E Array 'array' does not match snapshot for test_transformer_block
E Mismatched elements: 74 / 3072 (2.41%)
E Max absolute difference: 2.1338463e-05
E ACTUAL: array([[[0.058676, 1.071681, -0.696942,...
E DESIRED: array([[[0.058669, 1.071683, -0.696938,...
这告诉我们:
- 哪个测试失败了
- 期望值 vs 实际值
- 误差有多大
- 在代码的哪一行
# 实现各组件的代码
1. 在 cs336_basics/ 目录下创建了实现文件:
- nn.py - 实现了 Linear, Embedding, RMSNorm, SiLU, SwiGLU
- attention.py - 实现了 Scaled Dot-Product Attention, Multi-Head Self-Attention, RoPE
- transformer.py - 实现了 Transformer Block, Transformer LM
- training.py - 实现了训练相关工具
- tokenizer.py - 实现了 BPE Tokenizer
| 名词 | 在模型/代码中的角色 | 通俗解释 |
| -------------------------------------- | ------------ | ------------------------------------------- |
| **Linear** | 最基本的全连接层 | 就像“乘矩阵 + 加偏置”的计算器,把输入维度线性变换到输出维度 |
| **Embedding** | 词向量查表 | 把单词(整数 id)映射成稠密向量,等于给“词典”每个词配一行向量 |
| **RMSNorm** | 归一化层 | 用均方根(RMS)而不是均值方差来做缩放,参数更少、更快 |
| **SiLU (Swish)** | 激活函数 | 公式 _x · σ(x)_,比 ReLU/GELU 更平滑,让梯度流动更顺 |
| **SwiGLU** | 前馈网络中的“激活+门” | 两个线性变换后,一路用 SiLU 当“闸门”乘到另一路,信息筛得更精细 |
| **Scaled Dot‑Product Attention** | 注意力核心公式 | “点积相似度 / √d”再做 softmax,告诉模型“哪些词彼此相关” |
| **Multi‑Head Self‑Attention** | 多头注意力 | 把注意力拆成 h 份并行计算,小视角拼成大视角,学到多种关系 |
| **RoPE (Rotary Positional Embedding)** | 位置编码方式 | 用旋转矩阵给 Q/K 加“角度”,不新增参数却支持长序列外推 |
| **Transformer Block** | 网络基本单元 | “注意力层 + 残差 + Norm + MLP” 的组合砖,叠很多块就成大楼 |
| **Transformer Language Model** | 整体架构 | 输入 token,层层 Transformer Block,最后预测下一个 token |
| **Softmax** | 概率归一化 | 把一串实数压成 0‑1 区间且总和=1,用来表示“谁的概率大” |
| **Cross‑Entropy** | 训练损失 | 比较预测分布和真实分布差距,差距越大损失越高 |
| **Gradient Clipping** | 梯度裁剪 | 当梯度爆炸时把其“限速”,防止训练数值不稳定 |
| **Batch Sampling** | 小批量取样 | 一次喂 N 条数据给 GPU,既加速又让梯度估计更平滑 |
| **Learning Rate Scheduling** | 学习率调度 | 训练过程中动态调节步长,常见余弦、线性暖启等策略 |
| **Save Checkpoint** | 保存权重 | 把当前模型参数写入文件,断电也能从这一步继续 |
| **Load Checkpoint** | 加载权重 | 读回保存的参数,恢复模型“记忆” |
| **BPE Tokenizer** | 子词分词器 | 先把生僻词拆成常见“子词”,词表更小、覆盖率更高 |
2. 更新 `adapters.py`,把 raise NotImplementedError 替换成调用我实现的函数
3. 逐个运行测试,根据测试失败的信息调整实现,直到测试通过
# 训练数据
得到训练样本的逻辑
```Java
# 从位置15开始取数据:
原始片段: [15, 16, 17, 18, 19, 20, 21, 22]
↑————————— 7个 ————————↑ ↑
x (输入) y的最后一位
x[0] = [15, 16, 17, 18, 19, 20, 21] # 输入序列
y[0] = [16, 17, 18, 19, 20, 21, 22] # 目标序列(向右偏移1位)
```
所以训练样本:
```Java
训练样本:
位置0: 输入=15, 目标=16 → "看到15,预测16"
位置1: 输入=16, 目标=17 → "看到16,预测17"
位置2: 输入=17, 目标=18 → "看到17,预测18"
位置3: 输入=18, 目标=19 → "看到18,预测19"
位置4: 输入=19, 目标=20 → "看到19,预测20"
位置5: 输入=20, 目标=21 → "看到20,预测21"
位置6: 输入=21, 目标=22 → "看到21,预测22"
```
# test_transformer_lm 的流程
``
> 输入文本 "Hello world"
>
> ↓
>
> **[Tokenizer]** → `[101, 7592, 2088]`
>
> ↓
>
> **[Embedding]** → 将 token ID 转换为词向量
>
> ↓
>
> **[位置编码 RoPE]** → 为词向量注入位置信息
>
> ↓
>
> **[N × Transformer Block]**
> ├── Multi-Head Attention (使用因果 Mask)
> ├── [[RMSNorm]]
> ├── [[SwiGLU]]
> └── 残差连接
>
> ↓
>
> **[Final RMSNorm]**
>
> ↓
>
> **[LM Head]** → 投影到词汇表大小,得到 logits
>
> ↓
>
> **[Softmax]** → 将 logits 转换为概率分布
>
> ↓
>
> **预测下一个 token**
```Java
```
```Java
输入 Token IDs
↓
Token Embedding (将ID转换为向量)
↓
TransformerBlock 0 (第1层)
├─ Multi-Head Self-Attention
├─ Add & Norm
├─ Feed Forward (SwiGLU)
└─ Add & Norm
↓
TransformerBlock 1 (第2层)
├─ Multi-Head Self-Attention
├─ Add & Norm
├─ Feed Forward (SwiGLU)
└─ Add & Norm
↓
Final Layer Norm
↓
Linear Projection (投影到词汇表大小)
↓
输出 Logits
```
# 在写什么?
**模型类型:Decoder-only Transformer(GPT 架构)**
- **输入**:一串 token
```python
# 例如: "This is a"
input_ids = [101, 2023, 2003, 1037]
```
- **输出**:在每个位置上,预测下一个 token 的概率分布
```python
# logits 的形状: [batch_size, sequence_length, vocab_size]
logits = model(input_ids)
```
- **如何生成下一个词**:取序列最后一个位置的 logits,并找到概率最高的 token
```python
# 取最后一个位置的输出
next_token_logits = logits[:, -1, :]
# 找到概率最高的 token,例如 "test"
next_token = torch.argmax(next_token_logits)
```
与 Seq2Seq 的区别
| 特性 | 你的模型 (GPT-style) | Seq2Seq |
|:--- |:--- |:--- |
| **架构** | 只有 Decoder | Encoder + Decoder |
| **任务** | 语言建模 (预测下一个词) | 翻译、摘要等 |
| **注意力** | 因果注意力 (只看左边) | 交叉注意力 (看整个输入) |
| **训练方式** | 自回归 (一个个预测) | 输入 → 输出映射 |
| **典型应用** | 文本生成、对话、续写 | 翻译、问答、摘要 |
## 模型能做什么?
### 1. 文本生成
通过不断预测下一个词并将其添加回输入,模型可以生成连贯的文本。
```python
prompt = "Once upon a time"
for _ in range(50):
logits = model(tokenizer.encode(prompt))
next_token = sample(logits[:, -1, :]) # 从概率分布中采样
prompt += tokenizer.decode([next_token])
# 可能的输出: "Once upon a time, there was a little girl who..."
```
### 2. 文本补全
给定一段前文,模型会预测最可能的后续文本。
```Java
# 输入
text = "The capital of France is"
# 模型会预测: "Paris"
```
### 3. 零样本学习 (经过大规模训练后)
模型可以执行它没有被明确训练过的任务,只需给出正确的提示。
```Java
# 提示
prompt = "Translate to French: Hello world\nOutput:"
# 模型可能输出: "Bonjour le monde"
```
## 核心组件的作用
这是一个简化的数据流,展示了各个组件如何协同工作:
为什么你的模型不是 Seq2Seq?
1. **没有 Encoder**:你的实现中只有 Decoder 部分,没有一个独立的 Encoder 来处理源序列。
2. **没有交叉注意力**:模型中只有自注意力机制(Self-Attention),没有交叉注意力机制(Cross-Attention)来融合 Encoder 的信息。
3. **使用了因果 Mask**:注意力机制被强制要求只能关注当前位置及其左边的 token,无法“看到”未来的信息。这是通过一个上三角矩阵实现的:
```python
# 你的注意力 mask 确保了位置 i 只能看到 0...i-1 的信息
# 这使得模型在预测下一个词时,无法作弊
mask = torch.triu(torch.ones(seq_len, seq_len) * float("-inf"), diagonal=1)
```
# 测试什么?
这就像盖房子:
- 地基和框架(model, nn_utils)必须稳固
- 水电系统(optimizer, data)要正常工作
- 装修细节(tokenizer)可以后期完善
没有,Assignment 1 还没有完全通过所有测试。具体情况是:
1. 总共有 48 个测试
2. 核心功能大部分已实现:
- ✅ 基础神经网络组件
- ✅ 注意力机制
- ✅ Transformer 架构
- ✅ 训练工具
- ✅ 模型保存/加载
- ⚠️ BPE 分词器(基本实现但有问题)
3. 测试通过情况:
- 约 17-20 个核心测试通过
- 3 个测试因为数值精度问题失败(误差 < 0.0002,实际上可以接受)
- 剩余约 25-28 个测试失败,主要是 BPE 分词器相关的测试
4. 主要问题:
- Transformer 语言模型和 Transformer 块的数值精度差异(非常小,不影响实际使用)
- BPE 分词器的特殊标记处理有问题
- BPE 训练算法可能有 bug
所以严格来说,Assignment 1 还没有完全通过,但核心的 Transformer 实现已经基本完成且功能正确。
___
___
## Assignment 1: Basics (GitHub: stanford-cs336/assignment1-basics) 通俗解释
这个作业是斯坦福 CS336 课程(Language Modeling from Scratch,春季 2025)的第一个任务,目的是让你从零开始搭建语言模型的基础。简单说,就是教你怎么让计算机“学习”文本,比如预测下一个词,就像 ChatGPT 的简化版。通过这个,你能搞懂语言模型的核心步骤:怎么处理文本、建模型、训练它变聪明。重点不是理论推导,而是动手写代码,实现关键部分,然后用小数据集训练一个 mini 语言模型(LM)。整个作业用 PyTorch,代码框架在 GitHub 上,你填空就好,还有 unit tests 来检查对不对。
### 它干嘛的?
- **整体目标**:构建一个简单的语言模型系统,从文本输入到预测输出。全过程包括数据处理、模型构建和训练优化。完成后,你能用它在小数据集上训练模型,比如用故事文本预测下一个词。
- **为什么重要**:这是课程的基础,后面的作业会基于这个扩展(如优化速度、处理大数据)。它帮你理解 Transformer 等模型的“从 scratch”实现,但这里可能是简化版(不一定是全 Transformer,可能从 GPT-like 开始)。
### 要实现的东西
你要填的代码主要是三个核心组件:tokenizer(分词器)、model(模型)和 optimizer(优化器)。这些是语言模型的“积木块”。作业提供 starter code,你在 `./tests/adapters.py` 里实现函数,跑 `uv run pytest` 测试通过就算 OK。<grok:render card_id="bd9f83" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">0</argument>
</grok:render>
- **Tokenizer(分词器)**:
- **通俗解释**:文本是乱七八糟的字符串,计算机不懂。你需要写个工具,把文本拆成小块(像单词或字符),然后转成数字(tokens),模型才能处理。就像把句子切成词条,再编号。
- **Input(输入)**:原始文本数据,比如故事集(从 TinyStories 或 OpenWebText 下载的小数据集,train 和 valid 分开)。
- **Output(输出)**:tokens 序列(数字列表),比如 "Hello world" 可能变成[101, 202]这样的数值数组,便于模型计算。
- **Model(模型)**:
- **通俗解释**:这是大脑部分。你要建一个 mini LM,能根据前面的词预测下一个词。比如输入 “The cat is”,它输出 “sleeping” 的概率最高。可能基于 Transformer 或简单 RNN,但课程偏 Transformer。
- **Input(输入)**:从 tokenizer 来的 tokens 序列(一批文本片段)。
- **Output(输出)**:下一个 token 的预测(概率分布),比如对词汇表里的每个词打分,选分数最高的作为下一个词。
- **Optimizer(优化器)**:
- **通俗解释**:模型一开始预测乱七八糟,你需要写优化逻辑,让它通过比较预测和真实答案,调整内部参数(权重),越来越准。就像老师改作业,告诉模型“错哪儿了,下次改”。
- **Input(输入)**:模型的预测结果 + 真实下一个 token(从数据集标签)。
- **Output(输出)**:更新后的模型参数(权重矩阵),让 loss(错误率)下降。
### 训练 mini LM 的过程(通俗版)
1. 下载数据集:用 TinyStories(简单故事)或 OpenWebText(网页文本)的小样本。
2. 处理数据:tokenizer 把文本转 tokens。
3. 建模型:用你实现的 model,输入 tokens,输出预测。
4. 训练:喂数据给模型,算 loss,用 optimizer 更新。跑几轮 epoch,看 loss 降不降。
5. 测试:用 valid 数据检查模型准不准(e.g., perplexity 分数,低了好)。
整个作业强调 reproducibility(用 uv 工具设置环境),小数据集先在 CPU 调试,GPU 加速训练。完成后,你就有一个能跑的 mini LM,能在简单任务上工作。时间估计 4-6 小时,如果你 Transformer 基础好,很快。
以下表格按出现顺序逐个解释 CS336 作业中提到的关键名词,力求**通俗易懂**——看完就能大致知道“它干啥、在哪儿用”。
看这两个测试函数,它们测试的是**不同层次**的组件:
## 1. `test_transformer_block` - 测试单个Transformer块
```python:188-206:assignment1-basics/tests/test_model.py
def test_transformer_block(numpy_snapshot, ts_state_dict, in_embeddings, d_model, n_heads, d_ff, n_keys, theta):
# 提取单个block的权重(第0层)
block_weights = {k.replace("layers.0.", ""): v for k, v in ts_state_dict[0].items() if "layers.0." in k}
actual_output = run_transformer_block(
d_model=d_model,
num_heads=n_heads,
d_ff=d_ff,
max_seq_len=n_keys,
theta=theta,
weights=block_weights, # 只有一个block的权重
in_features=in_embeddings, # 输入是已经嵌入的向量
)
```
## 2. `test_transformer_lm` - 测试完整语言模型
```python:134-154:assignment1-basics/tests/test_model.py
def test_transformer_lm(
numpy_snapshot, vocab_size, n_keys, d_model, n_layers, n_heads, d_ff, theta, ts_state_dict, in_indices
):
state_dict, _ = ts_state_dict
actual_output = run_transformer_lm(
vocab_size=vocab_size,
context_length=n_keys,
d_model=d_model,
num_layers=n_layers, # 多层
num_heads=n_heads,
d_ff=d_ff,
rope_theta=theta,
weights=state_dict, # 完整模型的所有权重
in_indices=in_indices, # 输入是token ID
)
```