additive-rand-transformer

一个跑在纯 CPU 上的极小 GPT(vocab=16,≤926K 参数),训练在动态生成的加/减法表达式上。

这个仓库的价值不在"造了个会算加法的模型",而在于它是一把机制探针: 用来回答机制性问题 —— 小模型是真的在计算,还是在背模式?Chain-of-Thought 是真推理,还是抄自己的草稿纸?

所有实验在 12 核 CPU 上完成,最快 100 秒可复现一次完整训练。


一、问题

📚 本文档是全部研究的一站式入口:下方「三、修改」汇总了 17 份研究文档的 核心问题、关键数据与结论;每节附详细文档链接,需要复现命令/完整表格时点进去。 按代码核对每个实验扫描参数的清单见 additive_rand_transformer/PARAMS.md

核心问题

  1. 小模型是真的在计算,还是在背模式?
  2. Chain-of-Thought 是真推理,还是抄自己的草稿纸?

TL;DR — 核心结论

  1. CoT 竖式 = 草稿纸,不是推理。 篡改实验:改"和列"→ 答案变 **88.7%**;改操作数 → **0%**;改进位 → ~12%。 模型写完了竖式,然后从自己写下的列里读出答案,而不是在答案时刻重新从操作数推导。 准确率高 ≠ 有可泛化算法。
  2. 数据形态 > 模型容量。 不换 CoT,从 117K 加到 3.4M 参数,多位加法恒 0%;换成 CoT 后 332K 就学会 2 位加法 67%。
  3. 绝不外推。 只训 1–4 位,测 5–7 位 → **全 0%**。学到的是长度受限的模式,不是进位算法。
  4. DSA 稀疏注意力 > causal / linear。 同为 166K 参数:DSA 的 add2 60% vs causal 10%。
  5. 自问自答 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.mdSWEEP.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.mdREWARD.mdREWARD_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.mdBREAK_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(已设计,未实现/未跑)

  1. §9 层间 bottleneck:b∈{无,32,16,8} × 位置{每层,末层前},检验"进位需高带宽"vs"H3 末层读答案"vs"显式压缩≈DSA 稀疏"三假说
  2. §10 reward 2×2 矩阵--reward_tt/--reward_tf/--reward_ff,扫 TF∈{0,0.25,0.5,0.75,1}(注意:格式非法格不存在,实际 3 非退化格),把 REWARD.md 负结果从 3 个离散点扩展到连续一维
  3. §A batch 扫描:batch∈{8,16,32} × {无, memory 1.5},验证 batch 与 memory_window/方差/局部多样性的耦合
  4. §B per-head top-k:multi-topk [2,4,8,16] vs causal/dsa,测"长短头"多尺度分工
  5. §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 端到端(安装 → 下载 → 加载 → 生成):

  1. 安装依赖:pip install torch numpy huggingface-hub + pip install -e .
  2. 下载权重:hf_hub_download("Hana-ame/additive-rand-transformer", "checkpoints/l4_d128_cot_bias05_final.pt"),返回本地路径。
  3. 加载权重:torch.load(CKPT, map_location="cpu", weights_only=False),从 ck["config"] 构造 TinyGPTConfig(过滤非 dataclass 字段),model.load_state_dict(ck["model"])model.eval()
  4. 生成:从 [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

Downloads last month

-

Downloads are not tracked for this model. How to track
Video Preview
loading