additive-rand-transformer
一个跑在纯 CPU 上的极小 GPT(vocab=16,≤926K 参数),训练在动态生成的加/减法表达式上。
这个仓库的价值不在"造了个会算加法的模型",而在于它是一把机制探针: 用来回答机制性问题 —— 小模型是真的在计算,还是在背模式?Chain-of-Thought 是真推理,还是抄自己的草稿纸?
所有实验在 12 核 CPU 上完成,最快 100 秒可复现一次完整训练。
一、问题
📚 本文档是全部研究的一站式入口:下方「三、修改」汇总了 17 份研究文档的 核心问题、关键数据与结论;每节附详细文档链接,需要复现命令/完整表格时点进去。 按代码核对每个实验扫描参数的清单见
additive_rand_transformer/PARAMS.md。
核心问题
- 小模型是真的在计算,还是在背模式?
- Chain-of-Thought 是真推理,还是抄自己的草稿纸?
TL;DR — 核心结论
- CoT 竖式 = 草稿纸,不是推理。 篡改实验:改"和列"→ 答案变 **88.7%**;改操作数 → **0%**;改进位 → ~12%。 模型写完了竖式,然后从自己写下的列里读出答案,而不是在答案时刻重新从操作数推导。 准确率高 ≠ 有可泛化算法。
- 数据形态 > 模型容量。 不换 CoT,从 117K 加到 3.4M 参数,多位加法恒 0%;换成 CoT 后 332K 就学会 2 位加法 67%。
- 绝不外推。 只训 1–4 位,测 5–7 位 → **全 0%**。学到的是长度受限的模式,不是进位算法。
- DSA 稀疏注意力 > causal / linear。 同为 166K 参数:DSA 的 add2 60% vs causal 10%。
- 自问自答 RL 的坍缩可以打破,钥匙是"让重复有代价":全局记忆
memory_bonus=1.5把唯一式从 1/60 推到 **40/60 且答案率 98%**(0.5 不足、2.0 过冲;熵正则/局部多样性/稠密奖励/递增锚定全失败;LoRA 见 C2)。
局限
- 全部结论来自单任务(加/减法)+ tiny 规模(≤926K)。结论指向的是 "CoT 中间产物可能被当作读表来源"这一风险,不等于"LLM 的 CoT 都是抄草稿"—— 大模型机制可能不同,但评估方法(长度外推测试 + 中间量篡改)可直接迁移。 (文献面见 PRIOR_ART.md:预训练中型 LLM 确有另一种螺旋/Fourier 加法机制, 本项目结论限于无预训练极小模型。)
二、方案
目录结构
additive-rand-transformer/
├── additive_rand_transformer/ # 代码包(纯 torch + 标准库,零其他依赖)
│ ├── model.py # TinyGPT:causal / linear / dsa 三种注意力 + MoE + LoRA
│ ├── data.py # 动态表达式生成器(CoT 竖式、稀疏采样、难度 bias)
│ ├── train.py # SFT 训练
│ ├── evaluate.py # 评测(membership 5 类反例 + sequence_logprob)
│ ├── rl.py / grpo.py # 外部出题 RL:REINFORCE / GRPO
│ ├── rl_selfplay.py # 自问自答 RL(memory_bonus/decay/dense/curriculum/entropy/diversity)
│ ├── verify_selfplay.py # 采样验证(唯一式=坍缩探针)
│ ├── improve.py # MoE / LoRA / 课程 SFT 三实验
│ ├── explore_h1.py # H1 篡改探针(机制研究核心)
│ ├── scan*.py / bench.py / attn_compare.py / volume_sweep.py # 扫描与基准
│ └── *.md # 17 份研究文档(见下)
└── checkpoints/ # 7 个关键权重(34 MB,git-LFS)
Checkpoints
| 文件 | 配置 | 参数量 | 用途 |
|---|---|---|---|
l4_d128_cot_bias05_final.pt |
L4·D128 causal, 4 位 bias 0.5 | 926,464 | 机制研究主体(H1–H4、破坍缩实验基座) |
l4_d128_sft_nobias_final.pt |
L4·D128 causal, 无 bias | 926,464 | RL 基线(add4 起点 27%) |
l4_d128_grpo_final.pt |
同上 + GRPO 150 步 | 926,464 | add4 27%→32%,sub 全 100% 无回退 |
l4_d128_reinforce_conservative.pt |
同上 + REINFORCE 150 步 | 926,464 | 保守 RL,add4 +5pp,sub 小幅回退 |
l4_d128_selfplay_anchor.pt |
自问自答 RL + 难度锚定 | 926,464 | 治作弊但模式坍缩 |
l2_d64_attn_dsa.pt |
L2·D64 DSA (top-8) | 166,656 | 注意力变体最优 |
l2_d64_attn_causal.pt |
L2·D64 causal | 166,656 | 注意力基线 / 中间态基座(fmt98% ans22%) |
Quickstart
安装
pip install torch numpy huggingface-hub
pip install -e .
下载权重 —— .pt 走 git-LFS,用 hf_hub_download 最省事(自动解析指针,不需要装 git-lfs):
from huggingface_hub import hf_hub_download
CKPT = hf_hub_download(
"Hana-ame/additive-rand-transformer",
"checkpoints/l4_d128_cot_bias05_final.pt",
)
纯 git 也行:
git lfs install && git clone https://huggingface.co/Hana-ame/additive-rand-transformer——但必须先装 git-lfs,否则.pt只是 131 字节的指针文件。
加载 + 生成
import torch
from additive_rand_transformer.model import TinyGPT, TinyGPTConfig
from additive_rand_transformer.data import BOS, EOS, SP, PLUS, _int_to_tokens
ck = torch.load(CKPT, map_location="cpu", weights_only=False)
cfg = TinyGPTConfig(**{k: v for k, v in ck["config"].items()
if k in TinyGPTConfig.__dataclass_fields__})
model = TinyGPT(cfg)
model.load_state_dict(ck["model"])
model.eval()
def generate(a, b, op=PLUS, max_new=80):
ids = [BOS] + _int_to_tokens(a) + [SP, op, SP] + _int_to_tokens(b) + [SP]
with torch.no_grad():
for _ in range(max_new):
x = torch.tensor([ids], dtype=torch.long)
out = model(x, None)
logits = out[0] if isinstance(out, (tuple, list)) else out
ids.append(int(logits[0, -1].argmax()))
if ids[-1] == EOS:
break
return ids
三、修改
A. 基础能力线 —— 模型"会"什么
A1. 最早实测量:membership(RESULTS.md)
问题:模型能不能判断一个式子"是不是本生成器产的"?→ 能(格式层)。
- 5 类反例(positive / wrong_result / leading_zero / negative_result / wrong_operator)
- 整体 margin:packed +0.134 → single 小 +1.057 → single 大 +1.760(single 模式优势明显)
- 强检出:前导零、运算符位置错误、减法 a<b;检不出 wrong_result(≈0)——格式与算术是两种能力
- 1 位减法 74%,**多位加法 0%**(~1M 参数的真实边界)
A2. 参数量 × 层数扫描(SCAN.md)
问题:加大模型能学会多位加法吗?→ 不能。
- 网格:n_layer∈{1,2,4,6} × d∈{64,128,256} = 12 配置,非 CoT,single 模式
- 格式检测 2–4 层是甜点(wrong_operator 分离度 3.0–4.4;1 层 <1.4)
- a<b 规则强依赖层数(L1 0.36 → L2 1.7~2.2)
- wrong_result 分离度全部 ≈0(-0.02~+0.01)——本研究的持久负结果
- 1 位减法随容量 12%→44%(L6·D128 最佳);2/4 位全 ≈0% →
≤5M 参数学不会进位
A3. 思维链 CoT(COT.md)
问题:把"一步猜答案"拆成"逐位竖式进位"能否教会多位?→ 能。
- 格式:
<BOS> 37 + 85 = 7+5+0=12 1 3+8+1=12 1 0+0+1=1 0 122 <EOS>(低位到高位,含进位/借位) - **L4·D128(926K)加 1–3 位 100%、减法 1–4 位 100%**,对比非 CoT 的 0%
- four_digit_bias(双 4 位操作数比例):0.5 把"4 位结果"从 76%→94%;bias=1.0 过犹不及(1-3 位全归零,数据多样性不可牺牲)
- 短板:4+4 溢出到 5 位(欠学,21%)
A4. CoT vs plain 数据形态(COT_VS_PLAIN.md)
问题:是模型不够大,还是数据形态不对?→ 形态决定性。
- 网格:9 规模 × 2 形态 = 18 模型(同步数/同评估协议)
- **plain 任意规模(117K–3.4M)add2/3/4 全 0-2%**;cot 从 L1·D128(332K)就 add2 67%
- d 比层数关键:D64 是墙(任意层数多位≈0),D≥128 解锁;L1·D256 单层亮点(add2 87%/add3 70%)
- 减法普遍比加法容易(无进位传播);性价比:L2·D128(530K)~6 分钟 add1-3 ≥90%
B. 机制线 —— 它到底在"算"还是在"背"
B1. 四假说机制探究(EXPLORE.md)
问题:准确率高 = 真会算吗?→ 不是。
| 假说 | 实验 | 结果 |
|---|---|---|
| H1 答案来源 | 篡改三类 token(给列无答案作前缀再生成答案) | 和列 88.7% 敏感 / 操作数 0% / 进位 ~12% |
| H2 算法泛化 vs 记忆 | 训 1-4 位测 5-7 位 | **全 0%**(不泛化) |
| H3 计算在哪个层 | 逐层消融 | 末层 +2.742,前 3 层 <0.01 |
| H4 能力相变 | 600-2800 步细扫 | 渐进单调,无拐点 |
核心机制发现:模型从操作数写列是真的(裸前缀正确率 98.7%),但生成答案时 只读已写好的列、不回读操作数——CoT 在这里是草稿纸/工作记忆,答案在最后一层 从草稿读出。评估方法的警示:准确率高 ≠ 有可泛化算法,判别要靠"长度外推 + 中间量篡改"。
B2. Prior Art 文献比对(PRIOR_ART.md)
问题:有没有人做过类似的事?→ 方向不新,结论高度一致;机制细化是独特贡献。
- 中心论点已有 4 篇高引先例:Nye'21(scratchpad 无则多位加法做不了)、Turpin'23(NeurIPS,CoT 不忠实)、GSM-Symbolic'24(ICLR,改数值即崩)、Yang'25(EMNLP,LLM 不真懂加法)
- 最直接近亲:**Shih 2026, "Do Models Read What They Write?"**(编辑内部已写状态 → 下游预测 80%/91%)——本项目补上"读什么、不读什么"的不对称粒度(和列/操作数/进位三分)
- 真正缺先例的三件事:①篡改敏感度不对称;②<1M 参数 DSA 的收益;③自出题 RL 模式坍缩(1/60)且熵/多样性救不回
- 附 10 条引用勘误(含 LoRA arXiv ID 应为 2106.09685 等)
C. 注意力与性能提升
C1. 注意力变体 / 吞吐 / 训练量(SWEEP.md)
问题:换注意力实现能变强吗?→ 能,稀疏化=正则化。
| 注意力(L2·D64,4000 步) | loss | add1 | add2 | add3 | sub1 | sub2 |
|---|---|---|---|---|---|---|
| causal | 0.238 | 65% | 10% | 0% | 52% | 0% |
| linear | 0.257 | 45% | 10% | 0% | 65% | 5% |
| dsa(top-k=8) | 0.176 | 90% | 60% | 12% | 95% | 78% |
- 吞吐(gen tok/s,朴素全上下文):causal L2·D64 605 / L4·D128 279 / L6·D256 109;linear 在 d 大时反而慢(实现未 chunked)
- 训练量(L4·D128,sparse):add1/sub1 约 4.5M token 饱和,add2/3 需 ~4k 步
C2. MoE / LoRA / 课程 SFT(IMPROVE.md)
问题:哪些技术提升有效?→ LoRA 最实用。
| 技术 | 对比 | 结果 |
|---|---|---|
| MoE(4,top2) | dense L2·D64 | 2.2× 参数换多位大幅提升(add2 10%→45%),但吞吐 **-66%**(700→238 tok/s) |
| LoRA r8 | 全量微调同基座 | 防灾难性遗忘:全量微调 add1-3 归零 vs LoRA(20% 可训练)保留 88/78/82% 且 add4 35% |
| 课程 SFT | 混训 | 无干净收益(D64 容量墙内先易后难 ≈ 混训) |
D. RL 探究线 —— 从外部出题到自问自答
D1. 稀疏数据源 + REINFORCE / GRPO(SPARSE_RL.md、SWEEP.md §4)
问题:RL 能补 add4 短板吗?→ 能,但要保守。
- 稀疏源(sparse_from=3, density):1-2 位占比 31%→**51-61%**(不再穷举长操作数组合)
- REINFORCE 保守版(temp.5, lr1e-5, kl.1, n12):add4 **27%→32%**,代价 sub 小幅回退
- GRPO(group8):add4 27%→32% 且 sub 全 100% 无回退——组内标准化方差更小,最稳
- 激进 REINFORCE(temp.8)崩盘(贪婪能力崩)
D2. 自问自答 RL:作弊与坍缩(SELFPLAY.md、REWARD.md、REWARD_ANCHOR.md)
问题:让模型自己出题自己答,能学会吗?→ 会作弊,会被奖励绑架。
- naive:reward 刷到 1.000 但只出 1 位题(作弊坍缩
8+8=16,唯一式 1/60) - 难度锚定(min_digits_reward=2):治作弊(出 3 位题
999-10=989)但仍坍缩 1/60 - 奖励目标三分(format / answer / both):强基座下行为完全相同、弱基座都学不动——奖励"对象"不是杠杆
- 锚定 × 三模式:分化出现(format 贴 2 位下限
82-33=49、answer/both 停 3 位9000-10=8990),但三者仍坍缩 1/60
D3. 抗坍缩攻防战(COLLAPSE_ANTIDOTES.md、BREAK_COLLAPSE.md)
问题:模式坍缩(1/60)能打破吗?→ **能,钥匙是"让重复有代价"**。
| 手段 | 扫描 | 结果 |
|---|---|---|
| 锚定强度 | {2,3,4} | 只把坍缩点推高,不破坍缩(1/60) |
| 熵正则 | β∈{0.5,1.0} | 无效(1/60)——被稀疏奖励梯度淹没 |
| 局部多样性 | {1.0,2.0} | 仅改轨迹,仍 1/60(batch 局部可被跨 batch 骗过) |
| 稠密奖励 | 1/(1+|err|) |
不破坍缩(1/60) |
| 递增锚定 | 2→3→4 | 崩(ans 12%,伪多样性) |
| 全局记忆 | memory_bonus=1.5 | ✅ 40/60 唯一式 + ans 98%(甜点);1.0→8/60、0.5 不足、2.0 过冲 |
- why 有效:全局记忆(跨 batch 已见式集合,新式
r*(1+β)、重复式r*(1-β))把"重复"变成真实代价,补上局部→全局缺失的一环 - ⚠️ 长训练饱和崩坏(mb=1.5 × 300 步):200 步起 reward 崩 0——记忆塞满后全是重复式 → 必须配遗忘机制(LRU 窗口,实现中)
- 中间态基座分化(L2·D64 fmt98%/ans22%):format 撇答案(
6+2=10错照出)、answer/both 保答案——三个基座强度下奖励目标首次成为行为分水岭
E. 参数清单与使用
E1. 按代码的扫描参数清单(PARAMS.md)
每个实验脚本实际扫了哪些参数,逐项取自源码(网格值、固定超参、报告值 vs 脚本默认的差异表)。查证/复现第一站,含 scan.py 12 配置、scan_cot_vs_plain.py 18 模型、attn_compare.py 三注意力、improve.py 三实验、RL 三脚本全参数、verify_selfplay.py 等。
E2. 包级使用手册(additive_rand_transformer/README.md)
vocab(16 token)、生成器规则、CoT 格式、membership 5 类反例、train.py 完整 CLI 表、其余入口脚本表、checkpoint 加载(hf_hub_download LFS 安全)。
E3. 全实验执行记录(agents.md)
每个实验按「步骤 / 参数 / 现象」三段式改写——确切命令、网格值、OMP 设置、实测输出。复现对照表:先看这里抄命令,再看对应报告读结论。
待执行 PLAN(已设计,未实现/未跑)
- §9 层间 bottleneck:b∈{无,32,16,8} × 位置{每层,末层前},检验"进位需高带宽"vs"H3 末层读答案"vs"显式压缩≈DSA 稀疏"三假说
- §10 reward 2×2 矩阵:
--reward_tt/--reward_tf/--reward_ff,扫 TF∈{0,0.25,0.5,0.75,1}(注意:格式非法格不存在,实际 3 非退化格),把 REWARD.md 负结果从 3 个离散点扩展到连续一维 - §A batch 扫描:batch∈{8,16,32} × {无, memory 1.5},验证 batch 与 memory_window/方差/局部多样性的耦合
- §B per-head top-k:multi-topk [2,4,8,16] vs causal/dsa,测"长短头"多尺度分工
- §C error memory:B=惩罚已知错(RL)、C=错误集合注入 context(模型可读)、D=B+C
(完整设计见 additive_rand_transformer/SWEEP_PLAN.md;
§9/§10 详见 additive_rand_transformer/RESEARCH.md。)
四、测试
4.1 测试方案(验收用例)
| 用例组 | 目的 | 判定 |
|---|---|---|
| T1 权重下载 | hf_hub_download 能否解析 LFS 指针文件 |
返回本地 .pt 路径(非 131 字节指针) |
| T2 权重加载 | torch.load + load_state_dict 是否完整 |
ck["config"] 与 ck["model"] 全部 key 匹配 |
| T3 3 位加法生成 | 已知正确案例 | 123+456=579 正确输出 |
| T4 4+4 溢出 | 暴露草稿纸失败形态 | 五列进位全对、答案只吐 18(应为 18887) |
| T5 CoT SFT 复现 | 100 秒级训练可跑通 | L2·D64 4000 步 ≈ 100s |
| T6 H1 篡改探针 | explore_h1 机制实验 |
和列 88.7% 敏感 / 操作数 0% / 进位 ~12% |
| T7 GRPO RL | add4 提升 | 27%→32%,sub 全 100% 无回退 |
| T8 注意力变体 | attn_compare 三注意力对比 |
dsa add2 60% vs causal 10%(同 166K 参数) |
| T9 破坍缩 | 全局记忆 memory_bonus=1.5 |
唯一式 1/60 → 40/60,ans 98% |
| T10 长度外推 | 训 1-4 位测 5-7 位 | 全 0%(不泛化) |
4.2 测试方法
Quickstart 端到端(安装 → 下载 → 加载 → 生成):
- 安装依赖:
pip install torch numpy huggingface-hub+pip install -e . - 下载权重:
hf_hub_download("Hana-ame/additive-rand-transformer", "checkpoints/l4_d128_cot_bias05_final.pt"),返回本地路径。 - 加载权重:
torch.load(CKPT, map_location="cpu", weights_only=False),从ck["config"]构造TinyGPTConfig(过滤非 dataclass 字段),model.load_state_dict(ck["model"]),model.eval()。 - 生成:从
[BOS] + _int_to_tokens(a) + [SP, op, SP] + _int_to_tokens(b) + [SP]前缀出发,torch.no_grad()下逐步argmax,遇<EOS>停止,解析尾部数字为答案。
加载验证:
ck["config"]含n_layer/n_embd/n_head/n_ctx/vocab_size等,过滤后与TinyGPTConfig.__dataclass_fields__交集构造配置。ck["model"]的 state_dict 与TinyGPT(cfg)的named_parameters()key 完全匹配(load_state_dict无 missing/unexpected)。- 模型参数量 926,464(L4·D128)或 166,656(L2·D64),与 Checkpoints 表一致。
生成验证:
- 固定输入
(a, op, b),greedy 解码完整序列,从尾部提取数字序列构造int,与真值c = a + b(或a - b)比较。 - CoT 格式:
<BOS> a op b = [被加数位+加数位+进位=和 新进位]... 答案 <EOS>,低位到高位,含进位/借位。
4.3 测试结果
T1 下载路径(hf_hub_download 返回):
/path/to/hf_hub_cache/.../l4_d128_cot_bias05_final.pt
LFS 指针自动解析为真实权重文件(约 34 MB)。纯 git 克隆需先 git lfs install,否则 .pt 仅 131 字节指针。
T2 加载成功:load_state_dict 无 missing/unexpected key;模型参数量 926,464(L4·D128)。
T3 3 位加法 —— 写完列,读出正确答案:
123 + 456 = 579 → <BOS>123 + 456 = 3+6+0=9 0 2+5+0=7 0 1+4+0=5 0 0+0+0=0 0 579<EOS>
T4 4+4 溢出 —— 竖式每一列都算对了,但答案错了:
9999 + 8888 = 18887 → <BOS>9999 + 8888 =
9+8+0=17 1 ✓
9+8+1=18 1 ✓
9+8+1=18 1 ✓
9+8+1=18 1 ✓
0+0+1=1 0 ✓
18 ← 答案错(应为 18887)
模型把五列进位全部写对,却在最后只吐出一个 18。写草稿的能力是真的,读草稿的逻辑是脆的,且严格卡在训练见过的长度内。
4.4 复现或维护
从零复现:
PY=/path/to/python # 需要 torch>=2.0
# 最常用:CoT SFT,L2·D64 约 100 秒(CPU)
$PY -m additive_rand_transformer.train --single --cot --steps 4000 \
--max_digits 4 --n_layer 2 --n_embd 64 --batch_size 32
# 机制探针(H1 篡改实验)
$PY -m additive_rand_transformer.explore_h1 --checkpoint <ckpt>
# 注意力变体 / 吞吐 / 训练量扫描
$PY -m additive_rand_transformer.attn_compare --n_layer 2 --n_embd 64
$PY -m additive_rand_transformer.bench --n_layer 4 --n_embd 128 --attn_type dsa
$PY -m additive_rand_transformer.volume_sweep --n_layer 4 --n_embd 128
# RL
$PY -m additive_rand_transformer.grpo --checkpoint <SFT ckpt>
$PY -m additive_rand_transformer.rl_selfplay --checkpoint <SFT ckpt> --reward_mode both \
--memory_bonus 1.5 --min_digits_reward 2 --runs_dir runs/memory
# MoE / LoRA / 课程 SFT
$PY -m additive_rand_transformer.improve --experiment A # MoE
$PY -m additive_rand_transformer.improve --experiment B # LoRA(需 SFT 基座)
$PY -m additive_rand_transformer.improve --experiment C # 课程 SFT
代表性训练时间(12 核 CPU,单跑):CoT SFT L2·D64 4000 步 ≈ 100s;L4·D128 3000 步 ≈ 377s;GRPO 150 步 ≈ 950s。
维护注意事项:
- H1 在两个模型上复现过(bias 版与无 bias 版结论同构),H2/H3/H4 未跨模型复现。
- 注意力变体为纯 PyTorch 实现、未做 chunked 优化;linear 注意力在 d 较大时反而更慢;
gen吞吐为朴素全上下文前向;实现 KV cache 增量解码可快约 10 倍。 - 破坍缩的全局记忆在长训练(>200 步)会饱和崩坏,需遗忘窗口(LRU,实现中)。
一句话结论:≤926K 参数、纯 CPU 100 秒可复现的极小 GPT 证明 CoT 竖式是"草稿纸"而非推理——H1 篡改(和列 88.7% / 操作数 0% / 进位 ~12%)与长度外推(5-7 位全 0%)共同定位了"读列不读操作数"的机制边界,DSA 稀疏注意力(add2 60% vs causal 10%)与全局记忆 RL(唯一式 40/60、ans 98%)则给出两条可迁移的改进杠杆。
License
MIT