文章

从 Agent 开发走向 LLM 训练:1.69 元跑通 nanoGPT Shakespeare

从 Agent 开发走向 LLM 训练:1.69 元跑通 nanoGPT Shakespeare

我正在构建 Navi Agent,一个主要受 Hermes 和 Codex 启发、当前聚焦评测与 Runtime 的轻量 Agent。过去我主要站在 Agent 工程一侧使用模型:设计提示词、调用工具、维护上下文、处理记忆与工作流。但当系统效果不稳定时,很多问题最终都会回到模型本身:token 是怎样形成的,模型为什么续写而不是“思考”,上下文为什么会丢失,微调究竟改变了什么?

为了建立更扎实的直觉,我决定从随机初始化开始训练一个小语言模型。第一站不是中文对话模型,而是 nanoGPT 的 Shakespeare 字符级模型。它规模很小,却包含了语言模型训练最核心的闭环:

1
2
文本 → token → batch → Transformer → logits
→ cross-entropy → backward → AdamW → checkpoint → 自回归生成

这篇文章记录第一次实验,包括云资源、数据处理、训练曲线、过拟合、checkpoint 恢复,以及这些经验如何反过来帮助 Agent 开发。

1. 实验目标

这次不追求“训练一个有用的 ChatGPT”,而是验证五件事:

  1. 模型权重从随机值开始训练,而不是微调现成模型。
  2. 能看到 train loss 和 validation loss 的变化。
  3. 能保存 checkpoint,并重新加载生成文本。
  4. 能从 checkpoint 恢复 optimizer 并继续训练。
  5. 能记录完整环境、数据哈希、代码版本、耗时和费用。

上游使用 karpathy/nanoGPT,固定 commit:

1
3adf61e154c3fe3fca428ad6bc3818b27a3b8291

2. 云资源与实际费用

最终使用 AutoDL 按量实例:

AutoDL RTX 3090 实例选型

项目配置
GPU1×RTX 3090 24GB
CPU14 核
内存90GB
数据盘50GB NVMe
Python3.10.8
PyTorch2.1.2+cu121
CUDA Runtime12.1
cuDNN8.9.2
单价1.32 元/小时

RTX 3090 与 PyTorch CUDA 运行环境

账单分两次扣费,共计 1.69 元,关机后余额为 18.31 元。纯训练耗时远低于整个实例会话:5000-step 主训练仅用 5 分 24.64 秒,其余时间主要用于选择机器、配置访问、安装依赖、观察日志、下载产物和解释结果。

AutoDL 实例实际账单

这个差异很重要:对小实验来说,最大的成本往往不是训练,而是人在 GPU 开机期间进行环境配置和排障。因此我给实例设置了定时关机,并在训练前把代码、配置和检查脚本准备好。

3. 数据与字符级 Tokenizer

Tiny Shakespeare 包含:

指标数值
原始字符1,115,394
词表大小65
训练 token1,003,854
验证 token111,540

这里一个字符就是一个 token。65 个 token 包括大小写字母、换行、空格和标点。编码器只是两个字典:

1
2
stoi = {ch: i for i, ch in enumerate(chars)}
itos = {i: ch for i, ch in enumerate(chars)}

字符级 tokenizer 很容易理解,但效率不高。它适合第一课,却不适合后续中文通用模型;中文阶段会切换到 BPE,并重新训练模型权重。

数据采用前 90% 训练、后 10% 验证。我还为原始文本、训练集、验证集和 tokenizer 元数据分别记录了 SHA-256,以便以后确认输入数据是否发生变化。

4. 先做 Smoke Test

正式训练前,我先运行一个 0.40M 参数的小模型:2 层、hidden size 128、2 个 attention heads,只训练 20 step。

结果:

指标数值
初始 validation loss4.1667
step 20 validation loss3.0830
总耗时3.79 秒
checkpoint4.9MB

生成结果仍近似乱码,但 smoke test 的目标不是文本质量,而是确认训练、保存、加载和生成链路都能工作。

这里还发现了第一个真实问题:训练成功后,sample.py 因缺少 tiktoken 导入失败。字符级采样实际会读取 meta.pkl,但上游脚本仍在文件顶部无条件导入 tiktoken。这说明“训练脚本能运行”不等于“项目依赖完整”。修复后,依赖被锁定到 requirements-shakespeare.txt。

5. 10.65M 参数模型怎样学习

正式配置为:

参数数值
Transformer 层数6
Attention heads6
Hidden size384
Context length256
Batch size64
Training steps5000
每 step token16,384
总 token exposure81,920,000

nanoGPT 报告约 10.65M 个非位置嵌入参数。稳定阶段单步约 39ms,约等于 42 万 token/s。显存只使用约 2GB,说明 24GB 3090 对这个模型明显过剩。

完整训练日志每 250 step 评测一次。下面摘录了能够说明趋势的关键节点:

StepTrain lossValidation loss
04.28744.2823
5001.52601.7253
10001.27391.5237
17501.10241.4693
25000.95871.4955
35000.77881.5881
50000.61961.7150

nanoGPT Shakespeare 5000 step 训练完成

6. 为什么训练越久,模型反而越差

step 1750 后,train loss 继续下降,validation loss 却持续上升:

1
2
step 1750: train 1.1024 / val 1.4693
step 5000: train 0.6196 / val 1.7150

模型越来越擅长预测训练集,却越来越不擅长预测未参与训练的文本。这就是过拟合。

第一版配置还暴露了一个 checkpoint 策略问题:为了确保能恢复训练,我曾设置每次评测都覆盖保存 checkpoint,结果验证集更差的模型也覆盖了最佳模型。正确策略应当区分:

  • best checkpoint:validation loss 改善时保存,用于评测和部署。
  • latest checkpoint:定期保存,用于故障恢复。

修正配置后,我保持原来的 5000-step 学习率轨迹,只重新运行到 step 1750。复现结果与第一次完全一致:

1
train loss 1.1024 / validation loss 1.4693

这证明数据、代码、随机种子、模型初始化和学习率轨迹都得到了固定。

7. 最佳模型与最终模型的生成对比

使用相同采样种子 1337、相同 temperature 和 top-k,两个模型都学会了人物名、冒号、换行和莎士比亚式语域。

step 1750 模型:

1
2
3
4
5
Clown:
So, who is her life?

Shepherd:
Fellow, Signior Claudio hast thou heard him to bed die.

step 5000 模型:

1
2
3
Clown:
So is it that we will make an incense to shrift the
garden to the sea for the traitor.

只看两段文本,很难稳定判断哪个更好。step 5000 的局部句式有时甚至更像训练语料,但它在整个验证集上的 next-token 概率更差。少量主观样本不能替代系统评测。

完整样本和训练日志作为实验记录单独保留;文章只展示其中具有代表性的片段。

8. Checkpoint 能恢复,但不等于逐 bit 延续

从 step 1750 checkpoint 恢复后,模型和 AdamW optimizer 均成功加载,并继续训练到 step 1760:

1
2
Resuming training from ../../artifacts/shakespeare/best
step 1750: train loss 1.1043, val loss 1.4709

恢复后的 validation loss 与原来的 1.4693 有轻微差异,因为这个 checkpoint 没有完整保存 Python、NumPy、CPU/CUDA RNG 状态和数据采样位置。

所以 checkpoint 有两个层次:

  1. 功能恢复:模型和 optimizer 能继续训练。
  2. 精确恢复:中断前后每个 batch、每个随机数和每个浮点结果都连续一致。

这次完成了第一层,没有实现第二层;恢复日志、环境版本和 checkpoint 哈希均已单独归档。

9. 这对 Agent 开发有什么帮助

训练一次小模型后,Agent 系统里几个常见现象更容易理解了:

  • 模型的基本目标仍是 next-token prediction,Agent 的“规划”建立在这个生成机制之上。
  • Tokenizer 决定上下文如何被切分,也直接影响上下文成本和可用长度。
  • 一段回答看起来更流畅,不等于模型在整体分布上更可靠。
  • checkpoint、随机种子、数据版本和评测集,和 Agent 的 prompt、tool schema、trace 版本一样需要管理。
  • 只看几个 demo 很危险;固定评测集和可复现实验比“感觉更聪明”重要。

10. 成果与下一步

本轮产物压缩后为 226MB:

1
2
SHA-256:
12bc0648271b4b759a1ed8259b2da26127aa092a3d8e92738d88613a14c525b5

下一阶段会从字符级英文玩具数据走向中文小模型:

第一次实验没有得到强大的语言模型,但得到了更重要的东西:一条可以解释、测量、失败、恢复和复现的训练链路。

本文由作者按照 CC BY 4.0 进行授权