用 80 万参数的小模型、40 万条自生成数据、20 组对照实验——完整复现 LLM 训练方法论,并把每一步的收益与代价标定出来。
从 2.2% 到 94.4%(test_id 准确率),不是一次训练的成果,而是 17 组对照实验逐步堆出来的路径。本项目把这个路径完整开源:每个实验都有预注册预测、对照设计、机制解释——包括 **5 次"优雅失败"**的完整记录。
🇬🇧 English documentation: README.en.md
学习 LLM 训练的人面临一个困境:真实大模型训练动辄千卡万卡,看再多文章也无法亲手操作。市面教程又几乎只展示最终成功结果——"哪些方法无效、为什么无效、怎么发现的"恰恰是学习者最缺的内容。
本项目的解法:把任务压缩到一台 Mac 可承载的规模(四则运算表达式计算),但完整保留训练方法论的全貌——数据合成、SFT、架构对照、后训练(RLVR/DPO)、评估方法论,每个环节都有对照实验标定收益。
核心命题:算术的组合空间有限,理论上少量数据就该涌现出真正的运算能力。实际会怎样? 答案比预期复杂得多,见核心发现。
| 阶段 | 方法 | test_id 准确率 | 增量 |
|---|---|---|---|
| E1 | 直接输出答案(基线) | 2.2% | — |
| E5 | 化简链 CoT | 65.5% | +63pp(决定性) |
| E8a | 扩容(805K→4.7M) | 70.1% | +4.6pp |
| E8b | + RoPE | 71.9% | +1.8pp |
| E10 | + 数据×3(30 万) | 81.8% | +9.9pp |
| E14 | + 现实分布采样+交换增强 | 83.4% | +1.6pp |
| E15 | + 反转数字 + 位值嵌入 | 92.6% | +9.2pp |
| E17 | + RLVR (GRPO-lite) | 94.4% | +1.8pp |
| — | + 推理时 Best-of-N 验证投票 | 95.5% | +1.1pp |
从 2.2% 到 95.5%,四十三倍,每一步都有对照实验标定。
- 化简链 CoT 是唯一决定性杠杆(+63pp)——但粒度有最优值:部分积分解有效(2×2 达 98.6%),拆到逐位(三因子句法)反而崩溃。人类逻辑清晰 ≠ 模型统计顺口。
- 反转数字 + 位值嵌入是第二杠杆(+9.2pp)——把进位传播从"跨越数字宽度"变成"相邻位置",自回归生成先出个位、进位顺势传递。3 位数准确率 77.2% → 89.0%。零额外算力,纯表示改进。
- 容量与数据是配套插座,单独插哪个都不亮——805K 模型加数据无效(E4),4.7M 模型在 10 万数据上只有 +4.6pp(E8a),两者齐备后 +9.9pp(E10)。
- RLVR/GRPO 能压过 SFT 饱和点,但信号受限于组内分歧——全对/全错的组优势为零。复现了 o1/R1 式训练的核心机制与局限。
- MoE 专家自发按"数字 vs 运算符"分工(路由概率 0.98)但准确率不变——结构收益 ≠ 准确率收益,可解释性与性能是独立维度。
- 两堵未破的墙:长序列组合推理(6+ 操作数 ≈0%,RoPE 无效)与 5 位数以上外推——架构级边界,诚实标注为 future work。
- 评估方法论本身就是一门学问——我们踩过并修复了:loss 与准确率脱钩、val-loss 选型埋没最优模型(差 30~50pp)、反转世界验证器的语义错位、评估脚本 bug 制造假结论。见调试编年史。
# 1. 环境
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# 2. 生成数据(约 1 分钟,30 万条化简链 CoT)
python src/generate_data.py --cot --n 300000
# 3. 训练(MPS/CPU 约 3.5 小时;tiny 配置见下节约 20 分钟)
python src/train.py --train-file train_cot --cot \
--block-size 128 --n-embd 256 --n-layer 6 --n-head 8 --steps 60000
# 4. 评估(四测试集 + 按位数分层)
python src/eval_rev.py --ckpt final_best.pt --n 500tiny 配置(约 20 分钟跑通全流程,适合 first run):
python src/generate_data.py --cot --n 20000
python src/train.py --train-file train_cot --cot --block-size 128 \
--n-embd 128 --n-layer 4 --n-head 4 --steps 1000020 组实验按主题分入 experiments/,每组一个目录:
| 主题 | 实验 | 一句话结论 |
|---|---|---|
| 基线与格式 | E01 E02 E03 | CoT 化简链 +63pp;错误集中于多位数乘法 |
| 数据与采样 | E04 E10 E14 | 数据×容量须配套;现实分布+交换增强 +1.4pp |
| 容量与架构 | E08 E15 E16 | 扩容+4.6pp;反转+位值 +9.2pp;深窄 -3.8pp |
| 后训练 | E17 E20 | RLVR +1.8pp;DPO +1.4pp(偏好对稀缺) |
| 推理时计算 | E18 | 验证式 BoN 全线 ≥ 贪心,上限=覆盖率 |
| 涌现探针 | E19 | 高权重衰减致欠拟合;grokking 未触发 |
| 诊断方法论 | E07 E09 E13 | loss≠准确率;辅助任务有益;MoE 分工涌现 |
完整原始记录(含全部排障过程): docs/EXPERIMENTS.md
├── README.md / README.en.md # 本文件(中/英)
├── LICENSE # MIT
├── requirements.txt
├── src/ # 最终整合代码
│ ├── tokenizer.py / tokenizer_rev.py # 字符级 / 位值版(E15)
│ ├── model.py # TinyGPT(可开关: RoPE / MoE / ffn_mult)
│ ├── generate_data.py # 数据合成(多种分布/格式)
│ ├── train.py # SFT(含全部实验开关)
│ ├── train_dpo.py / train_grpo.py # 后训练
│ ├── evaluate.py / eval_rev.py / analyze.py
│ └── ...
├── experiments/ # E01~E20: 每组实验的文档与复现命令
├── docs/ # 深入文档
│ └── EXPERIMENTS.md # 完整原始实验记录
├── data/ # 数据合成脚本(*.jsonl 产物不入库)
└── scripts/
- 所有训练固定 seed(42),数据生成固定 seed,同类实验共用评估抽样种子
- 每组实验的精确配置见
experiments/E*/README.md的复现命令 - 已知平台注意: Apple MPS 上
(N,64)*(N,)广播需unsqueeze(-1)(已在代码内规避)
- test_len(6+ 操作数长链)≈0%:三种方案(RoPE/更多数据/更多容量)均无效——组合算法缺失,架构级边界
- 5 位数以上外推 <5%:位值嵌入给了部分脚手架,但容量仍是主限制
- MoE 在 tiny 尺度无准确率收益(但专家自发按数字/运算符分工,路由概率 0.98,见 E13)
- grokking 未触发(E19):高权重衰减致欠拟合;正确的 grokking 复现需要小数据+大量 epoch 的不同区间