1. 全景:一句话怎样变成回答
先看一句用户输入:
用户:法国的首都是哪里?
模型:巴黎。2
对模型来说,这句话不是一段可以直接阅读的文字,而是一串数字。整个 nanochat 的工作,可以概括成一句话:
把文字变成词元,用大量文本训练模型预测下一个词元,再教它按对话格式回答,最后用推理引擎逐个生成词元。
这一页把这条链路完整走一遍。暂时不深入公式,只建立方向感。
模型到底在学习什么
语言模型的核心任务只有一个:给定前面的词元,预测下一个词元。
例如训练文本是:
The capital of France is Paris分词后可能是:
[<|bos|>, The, capital, of, France, is, Paris]训练时,模型会看到:
输入:<|bos|> The capital of France is
目标:The capital of France is Paris2
每次输入位置上的目标,都是它右边紧邻的词元。模型为每个位置输出一组分数,分数最高的词元就是它的预测。
推理时没有完整答案,模型只能拿自己上一步生成的词元继续往后接。这就是“自回归生成”:生成一个词元,把它接到输入末尾,再生成下一个。
五个阶段
1. 分词
分词器把文字转换成整数编号:
"Paris" → [12345]反过来,它也能把编号还原成文字。模型只处理编号,不直接处理字符。
2. 准备训练数据
训练文本被切成固定长度的样本。每个样本包含:
inputs:模型看到的词元序列。targets:同一序列向右错开一格,作为每个位置的正确答案。
例如:
inputs: [BOS, The, capital, of, France, is]
targets: [The, capital, of, France, is, Paris]2
3. 预训练
预训练使用大量普通文本,让模型学会语言规律、事实和推理的雏形。训练结束后得到基础模型(base 模型)。
基础模型会续写文本,但它不一定知道“现在应该回答用户”。你给它一句问题,它可能续写出更多问题,而不是答案。
4. 监督微调
监督微调使用“用户消息 + 助手回答”的对话数据,把基础模型整理成对话助手。这个阶段还会教模型两件事:
- 什么时候结束回答。
- 什么时候调用 Python 工具计算数学表达式。
训练结束后得到 SFT 模型。
5. 强化学习
强化学习是可选的。nanochat 在数学题上让模型多次作答:答案正确得 1 分,错误得 0 分,再用这个奖励调整模型,让它更倾向于产生能得到正确答案的回答。
训练结束后得到 RL 模型。RL 模型通常比 SFT 模型更擅长 GSM8K 数学题,但不会因此获得大量新知识。
为什么必须分阶段
可以把基础模型想象成一个读了很多书、但没做过客服的人:
- 预训练让他学会语言和知识。
- 监督微调告诉他怎样和用户对话、怎样使用工具。
- 强化学习让他在特定任务上根据结果反复练习。
如果跳过预训练直接做微调,模型没有足够的语言能力,微调数据再多也学不会复杂表达。如果跳过微调直接拿基础模型聊天,它可能只会继续续写文本。
训练和推理有什么不同
| 训练 | 推理 | |
|---|---|---|
| 输入 | 一段完整的训练文本 | 用户提示和已生成的词元 |
| 输出 | 每个位置的下一个词元分数 | 一个新的词元 |
| 是否计算损失 | 是 | 否 |
| 是否更新参数 | 是 | 否 |
| 是否使用键值缓存 | 通常不使用 | 使用,避免重复计算历史 |
| 主要瓶颈 | 计算量、显存、数据量 | 显存带宽和生成长度 |
训练时,模型一次看到很多位置,每个位置都有正确答案,可以并行计算损失。推理时必须一个词元一个词元地往后生成,前一个输出是后一个输入,所以速度慢得多。
这些阶段对应哪些文件
nanochat/tokenizer.py 文本和词元互相转换
nanochat/dataset.py 下载、读取 Parquet 数据
nanochat/dataloader.py 把文档打包成训练样本
nanochat/gpt.py Transformer 模型
nanochat/optim.py Muon + AdamW 优化器
nanochat/engine.py 带键值缓存的推理引擎
scripts/base_train.py 预训练
scripts/chat_sft.py 监督微调
scripts/chat_rl.py 强化学习
scripts/chat_cli.py 命令行对话2
3
4
5
6
7
8
9
10
scripts/base_train.py第 415–544 行↗读 SFT 数据与训练scripts/chat_sft.py第 161–298 行↗读推理主循环nanochat/engine.py第 169–275 行↗训练产物怎样被下一阶段使用
每一阶段都会把结果写到 ~/.cache/nanochat:
tokenizer/tokenizer.pkl
base_checkpoints/d20/model_XXXXXX.pt
chatsft_checkpoints/d20/model_XXXXXX.pt
chatrl_checkpoints/d20/model_XXXXXX.pt2
3
4
脚本通过 model-tag 和 step 找到对应模型。SFT 默认读取基础模型,RL 默认读取 SFT 模型,命令行默认读取 SFT 模型。这条依赖关系是理解仓库目录结构的关键。
一个常见的误解
“模型见过这句话,所以它应该记得答案。”不完全对。
模型参数保存的是训练中形成的统计规律,而不是一份可以逐条查询的数据库。它生成答案时,是在每一步计算“下一个词元最可能是什么”,而不是检索某条原文。因此它可能把真实事实、相似句式和随机猜测混在一起,产生看起来合理但错误的回答。
本章要记住的三件事
- 模型的基本动作是预测下一个词元。
- 预训练、监督微调、强化学习是三个不同目标,不能互相替代。
- 训练产物有先后依赖,读代码时先确认“这一步在读哪个检查点”。
下一章从最前面开始:分词器。