﻿---
title: "做加法最少要几个参数：AdderBoard 排行榜与 Transformer 入门"
description: "AdderBoard 是一个比谁的 Transformer 更小的排行榜，题目是十位数加法。榜首的手写模型只有 6 个参数，训练出来的模型最少 36 个。我们先把 Transformer 是什么、它怎么一个字一个字地往外吐答案讲清楚，再顺着排行榜往下看：加法对它难在哪里，数位为什么要倒过来写，训练出来的模型怎么从 6080 个参数压到 36 个，手写的 6 个参数又是怎么把十位数加对的，它和 CPU 里的二进制加法器、我们列的十进制竖式比有什么不同，最后看这些数字该怎么理解。"
pubDate: 2026-10-24
updatedDate: 2026-10-25
tags: ["AI", "Transformer", "入门", "排行榜"]
hero: "./hero.svg"
refs:
  - id: board
    title: "anadim/AdderBoard"
    url: https://github.com/anadim/AdderBoard
    site: GitHub
    author: Dimitris Papailiopoulos
    note: "排行榜本体：README 里的两张榜、规则和 `verify.py`。本文依据 2026-03-26 的最新提交 `07061de`，此后仓库没有更新。"
  - id: pressure
    title: "Addition Under Pressure"
    url: https://dimitrisp.substack.com/p/addition-under-pressure
    site: Substack
    author: Dimitris Papailiopoulos
    date: 2026-02-19
    note: "排行榜的起点：同一段提示词交给 Claude Code 和 Codex，分别得到 6080 和 1644 个参数的模型。"
  - id: issue75
    title: "Submission #75: 6-parameter hand-coded adder"
    url: https://github.com/anadim/AdderBoard/issues/75
    site: GitHub
    note: "6 个参数该不该算 6 个的讨论，维护者 2026-03-25 以“6*”收录。"
  - id: zc6
    title: "zcbtrak：6 参数手写加法器"
    url: https://gist.github.com/zcbtrak/b9af065d6395a3ecd72e3b8d2e867ae9
    site: GitHub Gist
    note: "纯 numpy 实现，本文的机制分析都在这份代码上跑。"
  - id: loki10
    title: "Lokimorty：10 参数手写加法器"
    url: https://gist.github.com/Lokimorty/d54e5c61997e00fb922b6692739a0f6c
    site: GitHub Gist
    note: "6 参数版本的父版本，用 Apple MLX 写成，Linux 上跑不了；数值上和 6 参数版本完全相同。"
  - id: m10s
    title: "tbukic/M10S-Transformer"
    url: https://github.com/tbukic/M10S-Transformer
    site: GitHub
    author: Tom Bukic
    note: "训练榜前列的 36、41、44… 参数模型都出自这里，`paper/main.tex` 是附带的论文，讲参数构成、多阶段微调、“进位幽灵”和定向微调。"
  - id: attention
    title: "Attention Is All You Need"
    url: https://arxiv.org/abs/1706.03762
    site: NeurIPS 2017
    author: Ashish Vaswani 等
    date: 2017-06-12
    note: "Transformer 的原始论文。排行榜“固定位置编码不计参数”的规矩就来自这里的正弦位置编码。"
  - id: rope
    title: "RoFormer: Enhanced Transformer with Rotary Position Embedding"
    url: https://arxiv.org/abs/2104.09864
    site: arXiv
    author: Jianlin Su 等
    date: 2021-04-20
    note: "RoPE（旋转位置编码），榜上小模型几乎都用它。"
  - id: teaching
    title: "Teaching Arithmetic to Small Transformers"
    url: https://arxiv.org/abs/2307.03381
    site: ICLR 2024
    author: Nayoung Lee, Kartik Sreenivasan, Jason D. Lee, Kangwook Lee, Dimitris Papailiopoulos
    date: 2023-07-07
    note: "系统比较了加法数据的写法，倒序输出让小模型学得快得多。维护者在博客里说 Claude Code 选倒序正是受这篇影响。"
  - id: grokking
    title: "Grokking: Generalization Beyond Overfitting on Small Algorithmic Datasets"
    url: https://arxiv.org/abs/2201.02177
    site: arXiv
    author: Alethea Power 等
    date: 2022-01-06
    note: "“顿悟”现象：训练损失早就降下来，测试准确率在很久以后才突然上升。凭领域知识整理，发文前需要核对。"
  - id: adder
    title: "Adder (electronics)"
    url: https://en.wikipedia.org/wiki/Adder_(electronics)
    site: Wikipedia
    note: "全加器、行波进位加法器和超前进位加法器的标准介绍。"
  - id: bcd
    title: "Binary-coded decimal"
    url: https://en.wikipedia.org/wiki/Binary-coded_decimal
    site: Wikipedia
    note: "BCD 加法里“大于 9 就加 6”的修正规则。"
  - id: notes
    title: "本文的实验代码与调研笔记"
    note: "`/mnt/project-files/adderboard-research/`：`probe6.py` 逐步打印 6 参数模型的内部数值，`period.py` 做破坏实验，`train2.py` 是自己训练的对照实验，`binary.py` 算二进制和 BCD 的进位并测速，输出在 `logs/`。PyTorch 2.14.1 CPU 版，4 核。"
---

GitHub 上有一个叫 AdderBoard 的排行榜，比的是谁能做出**最小的 Transformer**，让它把两个十位数加对[^board]。榜首那一份提交只有两百来行 numpy 代码，我们把它下载下来，用排行榜自带的验证脚本跑了一遍：

```text
$ python verify.py submission_6p_handcoded.py
Parameters (unique): 6
Architecture: 1L Qwen-derived decoder, d=2, 1h, hd=2, ff=2 (fixed Q, folded norm)
  ...
Results: 10010/10010 correct (100.00%)
Time: 23.9s (419 additions/sec)
Status: QUALIFIED (threshold: 99%)
```

一万零十道题，全对；而整个模型只有 **6 个参数**。作为对比，ChatGPT 这一类大模型的参数以千亿计。

看到这个结果，很自然会冒出几个疑问：Transformer 到底是个什么结构，“参数”指的是什么？6 个数字怎么就能把十位数加对？榜上还分了“训练出来的”和“手写的”两张表，训练榜的第一名要 36 个参数，为什么差这么多？我们先把 Transformer 本身讲清楚（原问题里的 “transfer”，我们按 Transformer 理解），再顺着排行榜一层层往下看。

## 一、Transformer 的基本结构

### 从“预测下一个字”说起

不管是聊天机器人还是这个排行榜上的小模型，Transformer 做的事情都只有一件：**给它一串符号，它预测下一个符号**。这些符号叫 **token**（词元），可以是一个字、一个词，在加法这件事上就是一个数字 0–9。

预测完一个，把它接到序列末尾，再预测下一个，一直重复到答案写完。这种“边写边把自己写的东西当输入”的方式叫**自回归**（autoregressive）。所以“让 Transformer 做加法”的意思是：把两个加数写成一串数字喂给它，它一位一位地吐出和。十位数加十位数，和最多十一位，于是要预测 11 次。

那么，**每一次预测，模型内部发生了什么？**

### 一步预测里的四个部件

一个最简单的 Transformer 由四样东西串起来。下面这张图画的是 758 + 467 这道题，模型已经写出了个位 5、正在预测十位的那一步（数值取自后文那个 6 参数模型；输入为什么要倒着写、中间为什么有一串 0，第三、五节再讲）。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 384" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ab-step-title ab-step-desc">
<title id="ab-step-title">Transformer 预测一个数字的一步</title>
<desc id="ab-step-desc">上方是 758 加 467 的输入序列：起始 0、a 倒序 8 5 7、九个 0、b 倒序 7 6 4、结尾 0，以及已经写出的个位 5。下方四张卡片依次是词嵌入、注意力、MLP 和输出头。注意力从卡片连回序列里的 a 的十位 5 和 b 的十位 6，各给 0.45 的权重。输出头给出最高分的数字 2，写回序列末尾的空格，再进行下一步。</desc>
<defs>
<marker id="ab-step-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="ab-step-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="384" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="104" rx="8"/>
<rect class="dd-zone" x="16" y="136" width="768" height="232" rx="8"/>
<text class="t-zone" x="32" y="40">输入序列（预测十位的这一步）</text>
<text class="t-zone" x="32" y="160">一步预测：四个部件</text>
<!-- connectors -->
<path class="dd-edge-acc" d="M284 196 V136 Q284 128 276 128 H140 Q132 128 132 120 V94" marker-end="url(#ab-step-arrow-acc)"/>
<path class="dd-edge-acc" d="M324 196 V136 Q324 128 332 128 H396 Q404 128 404 120 V94" marker-end="url(#ab-step-arrow-acc)"/>
<line class="dd-edge" x1="200" y1="244" x2="220" y2="244" marker-end="url(#ab-step-arrow)"/>
<line class="dd-edge" x1="392" y1="244" x2="412" y2="244" marker-end="url(#ab-step-arrow)"/>
<line class="dd-edge" x1="584" y1="244" x2="604" y2="244" marker-end="url(#ab-step-arrow)"/>
<path class="dd-edge dd-dash" d="M700 196 V136 Q700 128 692 128 H664 Q656 128 656 120 V94" marker-end="url(#ab-step-arrow)"/>
<!-- tokens -->
<rect class="dd-card" x="32" y="56" width="32" height="32" rx="4"/><text class="t-mono" x="48" y="77" text-anchor="middle">0</text>
<rect class="dd-card" x="80" y="56" width="32" height="32" rx="4"/><text class="t-mono" x="96" y="77" text-anchor="middle">8</text>
<rect class="dd-card" x="116" y="56" width="32" height="32" rx="4"/><text class="t-acc t-mono" x="132" y="77" text-anchor="middle">5</text>
<rect class="dd-card" x="152" y="56" width="32" height="32" rx="4"/><text class="t-mono" x="168" y="77" text-anchor="middle">7</text>
<rect class="dd-card" x="188" y="56" width="32" height="32" rx="4"/><text class="t-mono" x="204" y="77" text-anchor="middle">0</text>
<text class="t-note" x="232" y="77">…</text>
<rect class="dd-card" x="256" y="56" width="68" height="32" rx="4"/><text class="t-mono" x="290" y="77" text-anchor="middle">0 ×9</text>
<rect class="dd-card" x="352" y="56" width="32" height="32" rx="4"/><text class="t-mono" x="368" y="77" text-anchor="middle">7</text>
<rect class="dd-card" x="388" y="56" width="32" height="32" rx="4"/><text class="t-acc t-mono" x="404" y="77" text-anchor="middle">6</text>
<rect class="dd-card" x="424" y="56" width="32" height="32" rx="4"/><text class="t-mono" x="440" y="77" text-anchor="middle">4</text>
<rect class="dd-card" x="460" y="56" width="32" height="32" rx="4"/><text class="t-mono" x="476" y="77" text-anchor="middle">0</text>
<text class="t-note" x="504" y="77">…</text>
<rect class="dd-card" x="544" y="56" width="32" height="32" rx="4"/><text class="t-mono" x="560" y="77" text-anchor="middle">0</text>
<rect class="dd-card" x="592" y="56" width="32" height="32" rx="4"/><text class="t-mono" x="608" y="77" text-anchor="middle">5</text>
<rect class="dd-derived" x="640" y="56" width="32" height="32" rx="4"/><text class="t-note" x="656" y="77" text-anchor="middle">?</text>
<text class="t-note" x="152" y="108">a = 758 倒序</text>
<text class="t-note" x="424" y="108">b = 467 倒序</text>
<text class="t-note" x="592" y="108">已写出</text>
<text class="t-note" x="688" y="77">← 这一步要写的</text>
<!-- cards -->
<rect class="dd-card" x="32" y="196" width="168" height="96" rx="6"/>
<path class="dd-card-head" d="M33 228 V203 Q33 197 39 197 H193 Q199 197 199 203 V228 Z"/>
<text class="t-name" x="44" y="217">词嵌入</text>
<text class="t-mono" x="44" y="250">"5" 查表</text>
<line class="dd-hair" x1="32" y1="258" x2="200" y2="258"/>
<text class="t-val" x="44" y="276">[999.98, −5]</text>
<rect class="dd-card" x="224" y="196" width="168" height="96" rx="6"/>
<path class="dd-card-head" d="M225 228 V203 Q225 197 231 197 H385 Q391 197 391 203 V228 Z"/>
<text class="t-name" x="236" y="217">注意力</text>
<text class="t-mono" x="236" y="250"><tspan class="t-acc">5</tspan>、<tspan class="t-acc">6</tspan> 各 0.45</text>
<line class="dd-hair" x1="224" y1="258" x2="392" y2="258"/>
<text class="t-val" x="236" y="276">8、7 各 0.05</text>
<rect class="dd-card" x="416" y="196" width="168" height="96" rx="6"/>
<path class="dd-card-head" d="M417 228 V203 Q417 197 423 197 H577 Q583 197 583 203 V228 Z"/>
<text class="t-name" x="428" y="217">MLP</text>
<text class="t-mono" x="428" y="250">满十没有？</text>
<line class="dd-hair" x1="416" y1="258" x2="584" y2="258"/>
<text class="t-val" x="428" y="276">满了，减掉 98.6</text>
<rect class="dd-card" x="608" y="196" width="160" height="96" rx="6"/>
<path class="dd-card-head" d="M609 228 V203 Q609 197 615 197 H761 Q767 197 767 203 V228 Z"/>
<text class="t-name" x="620" y="217">输出头</text>
<text class="t-mono" x="620" y="250">0–9 各打一分</text>
<line class="dd-hair" x1="608" y1="258" x2="768" y2="258"/>
<text class="t-val" x="620" y="276">最高：2</text>
<text class="t-say" x="32" y="324">四个部件的结果都加在同一个向量上（残差流），不是一个替换一个。</text>
<text class="t-say" x="32" y="348">写出的 2 接到序列末尾，整条序列再算一遍，得到百位；11 步后答案写完。</text>
</svg>
</div>
<figcaption>图 1 · Transformer 每一步只做一件事：看完前面的序列，给下一个数字打分；写下最高分的那个，再看一遍</figcaption>
</figure>

**词嵌入**（embedding）。神经网络只会算数，不认识符号，所以第一步是查表：每个 token 对应一个固定长度的向量，比如 “5” 对应 `[999.98, −5]`。向量的长度叫**模型宽度** $d$，大模型里是几千，这个榜上常见的是 2 到 7。

**注意力**（attention）。这是 Transformer 的标志部件。每个位置都可以“往回看”序列里所有更早的位置，决定从谁那里取多少信息。具体做法是：当前位置算一个**查询**向量 Q，每个更早的位置各算一个**键**向量 K 和一个**值**向量 V；Q 和每个 K 做点积，得到一个“相关程度”的分数，经过 softmax 变成加起来等于 1 的权重，再用这组权重把各位置的 V 加权平均。图里，预测十位时，模型把 0.45 的权重分给第一个加数的十位 5，0.45 分给第二个加数的十位 6，各 0.05 分给两个个位 8 和 7。**对加法来说，注意力就是“把同一位上的两个数字找出来”的那只手**。

**前馈网络**（MLP）。注意力只负责搬运和混合信息，真正的非线性计算交给每个位置各自的一个小网络：先乘一个矩阵升维，过一个激活函数（ReLU、SiLU 之类），再乘一个矩阵降回来。在加法里，判断“这一位满没满十”就靠它。

**输出头**。最后把向量和词表里 10 个数字各自的向量做点积，得到 10 个分数，分数最高的就是预测结果。很多模型让输出头和词嵌入共用同一张表，这叫**权重绑定**（weight tying），后面会反复出现。

还有两个不显眼但必需的零件。一是**残差连接**：每个部件的输出不是替换原来的向量，而是加到它上面，所以可以把整个过程看成一条“残差流”，各部件往上面累加东西。二是**归一化**（LayerNorm 或 RMSNorm），把向量的大小拉回一个固定范围，防止数值越算越大。

### 位置从哪里来

上面的注意力有一个漏洞：Q 和 K 的点积只看两个向量的内容，不看它们在序列里的位置。把序列打乱，每个位置得到的结果完全一样。可加法偏偏最在乎位置，“5” 在个位还是在十位，意思差了十倍。

所以 Transformer 还要加**位置编码**。2017 年的原始论文用一组固定的正弦波，加到每个位置的词嵌入上[^attention]；现在的大模型（包括这个榜上几乎所有小模型）多用 **RoPE**（旋转位置编码）：把 Q 和 K 按所在的位置转一个角度，位置 $p$ 转 $p\omega$。两个向量都转过以后再做点积，结果只取决于它们**相隔多少个位置**，而不是各自在哪里[^rope]。记住“RoPE 是一个按位置转动的钟”，第五节会用到。

### 参数是什么

图里每个部件都离不开几张矩阵：词嵌入是一张 10×$d$ 的表，Q、K、V 各是一个矩阵，MLP 有两三个矩阵，归一化还有一组缩放系数。这些矩阵里的每一个数都是**参数**；训练，就是用梯度下降一点点调这些数，让预测越来越准。

以训练榜上一个 122 参数的模型为例（宽度 3，注意力头的维度 4，MLP 中间层 3），参数是这样分的[^m10s]：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ab-params-title">122 参数模型的参数都花在哪（d=3，注意力头维度 4，MLP 中间层 3）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ab-params-title">
<colgroup><col style="width:30%"><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">部件</th><th scope="col">形状</th><th scope="col" class="num">参数</th></tr>
</thead>
<tbody>
<tr><th scope="row">词嵌入（与输出头共用）</th><td class="mono">10 × 3</td><td class="num mono">30</td></tr>
<tr><th scope="row">注意力 Q、K、V</th><td class="mono">3 × 4，各一个</td><td class="num mono">36</td></tr>
<tr><th scope="row">注意力输出投影 O</th><td class="mono">4 × 3</td><td class="num mono">12</td></tr>
<tr><th scope="row">Q、K 各自的归一化</th><td class="mono">4 + 4</td><td class="num mono">8</td></tr>
<tr><th scope="row">三处归一化（注意力前、MLP 前、输出前）</th><td class="mono">3 + 3 + 3</td><td class="num mono">9</td></tr>
<tr><th scope="row">MLP（SwiGLU：门、升维、降维）</th><td class="mono">3 × 3，三个</td><td class="num mono">27</td></tr>
<tr class="dt-group"><th scope="row">合计</th><td></td><td class="num mono acc">122</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">RoPE 是固定的旋转角度，不算参数。</p>
</div>
<figcaption>表 1 · 一个最小的单层 Transformer，参数主要花在词嵌入和注意力的几个投影矩阵上</figcaption>
</figure>

参数量是衡量模型大小最直接的尺子。GPT-2 最小的版本有 1.24 亿个参数，这个榜上的模型比它小一百万倍。那么，**用这把尺子量，能做十位数加法的 Transformer 最小可以多小？** 这就是 AdderBoard 要回答的问题。

## 二、AdderBoard 的规则

### 起点：两个编程智能体的比赛

排行榜的维护者是 Dimitris Papailiopoulos。2026 年 2 月，他把同一段提示词分别交给 Claude Code 和 Codex：训练一个尽可能小的 Transformer，十位数加法的准确率至少 99%，不许把答案编码进输入，不许调用计算器，输出必须自回归地生成[^pressure]。

Claude Code 从 79.5 万个参数开始一路往下扫，发现宽度 12（4176 个参数）完全学不会，宽度 16（6080 个参数）就能 100% 正确，最后交出一个两层、宽度 16 的模型。Codex 第一轮交出的是 36.6 万个参数；维护者把“最小化参数”的要求加重再跑一次，Codex 想出了一个办法：把同一位上的两个数字合成一个 token（第 3 位上的 3 和 7 记作 `P37`），词表从十几个涨到 114 个，序列短了一半，最后只用 1644 个参数。维护者的评价是，这样做没有违规，但为了“参数少”这个写明的目标，牺牲了“通用”这个没写明的目标，“审美上有点不舒服”[^pressure]。

文章发出去以后，很多人开始接力往下压，于是有了这个排行榜。

### 两张榜

榜分成两类[^board]：

- **训练榜**：参数由通用的训练算法从数据里学出来，梯度下降、进化搜索都可以，但算法本身不能是为加法专门设计的。
- **手写榜**：参数是人用纸笔推出来、直接填进去的。它不证明模型**学得会**，只证明这个结构**表达得了**加法，算是一种构造性证明。

### 规则在防什么

规则里写得最细的，是“什么才算 Transformer 在做加法”：

1. 必须至少有一层自注意力。
2. 必须自回归：一位一位地生成，每一位生成后接回输入。进位必须从这个过程里“长出来”，不能在 Python 里用一个变量把进位传给下一步。
3. `forward()` 必须是标准的“张量进、分数出”，里面不能有按数位循环、存进位这类专门针对加法的控制流。
4. 生成循环必须是通用的：换一套权重、换一个任务，同样的解码代码照样能用。

一句话：**算加法的必须是模型，不能是代码**。在此之外，输入怎么写（倒序、加分隔符、补零）可以自选，只要格式固定、不泄露答案；位置编码如果是固定的（正弦、RoPE），不算参数；绑定在一起的权重只算一次。

验证方法也是固定的：`verify.py` 先跑 10 个边界用例（全 9 加全 9、一串连续进位之类），再跑 10000 道随机题，随机种子固定为 2025，准确率不低于 99% 才算合格。

题目定好了，可是在动手之前，还有一个更基础的问题：**一个只会“看前面、猜下一个”的模型，做加法难在哪里？**

## 三、加法对 Transformer 难在哪

### 三件事

列竖式做加法，我们其实在做三件事[^board]：

1. **对齐**：把两个数同一位上的数字找出来。
2. **逐位相加**：算这一位的和，以及满不满十。
3. **传递进位**：把低一位的进位带到高一位。

前两件 Transformer 都有现成的部件：对齐靠注意力，逐位相加靠 MLP。麻烦在第三件。进位可能一路传下去：9999999999 + 1，个位的进位要连着翻过十位，一直传到最高位。而模型是**从左往右**一个一个写答案的。

### 现象：写答案的方向和进位的方向

按我们平常的习惯，和的最高位写在最前面。看 758 + 467 = 1225：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 352" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ab-order-title ab-order-desc">
<title id="ab-order-title">758 + 467 的两种书写顺序</title>
<desc id="ab-order-desc">左边按习惯高位在前，答案 1225 从千位写起，而进位从个位一路向左传到千位，与写字方向相反。右边把数字倒过来写，答案写成 5221，个位先写，进位从左向右传，与写字方向相同。</desc>
<defs>
<marker id="ab-order-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="ab-order-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="352" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="320" rx="8"/>
<text class="t-zone" x="32" y="44">高位在前：先写 1</text>
<text class="t-note" x="40" y="101">a</text>
<text class="t-note" x="40" y="137">b</text>
<text class="t-note" x="40" y="185">和</text>
<text class="t-note" x="40" y="216">进位</text>
<text class="t-note" x="40" y="252">写的顺序</text>
<line class="dd-edge" x1="152" y1="156" x2="292" y2="156"/>
<path class="dd-edge-acc" d="M272 204 Q258.0 226 244 204" marker-end="url(#ab-order-arrow-acc)"/>
<path class="dd-edge-acc" d="M236 204 Q222.0 226 208 204" marker-end="url(#ab-order-arrow-acc)"/>
<path class="dd-edge-acc" d="M200 204 Q186.0 226 172 204" marker-end="url(#ab-order-arrow-acc)"/>
<line class="dd-edge" x1="152" y1="268" x2="292" y2="268" marker-end="url(#ab-order-arrow)"/>
<text class="t-note" x="168" y="72" text-anchor="middle">千</text>
<rect class="dd-card" x="152" y="80" width="32" height="32" rx="4"/>
<text class="t-mono" x="168" y="101" text-anchor="middle">0</text>
<rect class="dd-card" x="152" y="116" width="32" height="32" rx="4"/>
<text class="t-mono" x="168" y="137" text-anchor="middle">0</text>
<rect class="dd-card" x="152" y="164" width="32" height="32" rx="4"/>
<text class="t-mono" x="168" y="185" text-anchor="middle">1</text>
<text class="t-mono" x="168" y="252" text-anchor="middle">①</text>
<text class="t-note" x="204" y="72" text-anchor="middle">百</text>
<rect class="dd-card" x="188" y="80" width="32" height="32" rx="4"/>
<text class="t-mono" x="204" y="101" text-anchor="middle">7</text>
<rect class="dd-card" x="188" y="116" width="32" height="32" rx="4"/>
<text class="t-mono" x="204" y="137" text-anchor="middle">4</text>
<rect class="dd-card" x="188" y="164" width="32" height="32" rx="4"/>
<text class="t-mono" x="204" y="185" text-anchor="middle">2</text>
<text class="t-mono" x="204" y="252" text-anchor="middle">②</text>
<text class="t-note" x="240" y="72" text-anchor="middle">十</text>
<rect class="dd-card" x="224" y="80" width="32" height="32" rx="4"/>
<text class="t-mono" x="240" y="101" text-anchor="middle">5</text>
<rect class="dd-card" x="224" y="116" width="32" height="32" rx="4"/>
<text class="t-mono" x="240" y="137" text-anchor="middle">6</text>
<rect class="dd-card" x="224" y="164" width="32" height="32" rx="4"/>
<text class="t-mono" x="240" y="185" text-anchor="middle">2</text>
<text class="t-mono" x="240" y="252" text-anchor="middle">③</text>
<text class="t-note" x="276" y="72" text-anchor="middle">个</text>
<rect class="dd-card" x="260" y="80" width="32" height="32" rx="4"/>
<text class="t-mono" x="276" y="101" text-anchor="middle">8</text>
<rect class="dd-card" x="260" y="116" width="32" height="32" rx="4"/>
<text class="t-mono" x="276" y="137" text-anchor="middle">7</text>
<rect class="dd-card" x="260" y="164" width="32" height="32" rx="4"/>
<text class="t-mono" x="276" y="185" text-anchor="middle">5</text>
<text class="t-mono" x="276" y="252" text-anchor="middle">④</text>
<text class="t-say" x="32" y="298">写第 ① 位之前，要先把三次进位都算完。</text>
<text class="t-say" x="32" y="320">进位从右往左走，写字从左往右走。</text>
<rect class="dd-zone" x="408" y="16" width="376" height="320" rx="8"/>
<text class="t-zone" x="424" y="44">低位在前：先写 5</text>
<text class="t-note" x="432" y="101">a</text>
<text class="t-note" x="432" y="137">b</text>
<text class="t-note" x="432" y="185">和</text>
<text class="t-note" x="432" y="216">进位</text>
<text class="t-note" x="432" y="252">写的顺序</text>
<line class="dd-edge" x1="544" y1="156" x2="684" y2="156"/>
<path class="dd-edge-acc" d="M564 204 Q578.0 226 592 204" marker-end="url(#ab-order-arrow-acc)"/>
<path class="dd-edge-acc" d="M600 204 Q614.0 226 628 204" marker-end="url(#ab-order-arrow-acc)"/>
<path class="dd-edge-acc" d="M636 204 Q650.0 226 664 204" marker-end="url(#ab-order-arrow-acc)"/>
<line class="dd-edge" x1="544" y1="268" x2="684" y2="268" marker-end="url(#ab-order-arrow)"/>
<text class="t-note" x="560" y="72" text-anchor="middle">个</text>
<rect class="dd-card" x="544" y="80" width="32" height="32" rx="4"/>
<text class="t-mono" x="560" y="101" text-anchor="middle">8</text>
<rect class="dd-card" x="544" y="116" width="32" height="32" rx="4"/>
<text class="t-mono" x="560" y="137" text-anchor="middle">7</text>
<rect class="dd-card" x="544" y="164" width="32" height="32" rx="4"/>
<text class="t-mono" x="560" y="185" text-anchor="middle">5</text>
<text class="t-mono" x="560" y="252" text-anchor="middle">①</text>
<text class="t-note" x="596" y="72" text-anchor="middle">十</text>
<rect class="dd-card" x="580" y="80" width="32" height="32" rx="4"/>
<text class="t-mono" x="596" y="101" text-anchor="middle">5</text>
<rect class="dd-card" x="580" y="116" width="32" height="32" rx="4"/>
<text class="t-mono" x="596" y="137" text-anchor="middle">6</text>
<rect class="dd-card" x="580" y="164" width="32" height="32" rx="4"/>
<text class="t-mono" x="596" y="185" text-anchor="middle">2</text>
<text class="t-mono" x="596" y="252" text-anchor="middle">②</text>
<text class="t-note" x="632" y="72" text-anchor="middle">百</text>
<rect class="dd-card" x="616" y="80" width="32" height="32" rx="4"/>
<text class="t-mono" x="632" y="101" text-anchor="middle">7</text>
<rect class="dd-card" x="616" y="116" width="32" height="32" rx="4"/>
<text class="t-mono" x="632" y="137" text-anchor="middle">4</text>
<rect class="dd-card" x="616" y="164" width="32" height="32" rx="4"/>
<text class="t-mono" x="632" y="185" text-anchor="middle">2</text>
<text class="t-mono" x="632" y="252" text-anchor="middle">③</text>
<text class="t-note" x="668" y="72" text-anchor="middle">千</text>
<rect class="dd-card" x="652" y="80" width="32" height="32" rx="4"/>
<text class="t-mono" x="668" y="101" text-anchor="middle">0</text>
<rect class="dd-card" x="652" y="116" width="32" height="32" rx="4"/>
<text class="t-mono" x="668" y="137" text-anchor="middle">0</text>
<rect class="dd-card" x="652" y="164" width="32" height="32" rx="4"/>
<text class="t-mono" x="668" y="185" text-anchor="middle">1</text>
<text class="t-mono" x="668" y="252" text-anchor="middle">④</text>
<text class="t-say" x="424" y="298">进位和写字同一个方向。</text>
<text class="t-say" x="424" y="320">写十位时，个位的 5 已经写在序列里了。</text>
</svg>
</div>
<figcaption>图 2 · 把数字倒过来写，进位就和模型写字的方向一致：每一位只需要看上一位刚写下的结果</figcaption>
</figure>

**高位在前时**，模型写下第一个数字 “1” 之前，要先知道百位有没有进位，而百位有没有进位取决于十位，十位又取决于个位。也就是说，第一步就得在脑子里把整条进位链算完；十位数加法里，这条链最长有十节，全都要压在一次前向计算里完成。

**低位在前时**，情况反过来：先写个位 5，再写十位。写十位的时候，个位已经写在序列里了，“个位有没有进位”可以从刚写下的 5 里读出来（8 + 7 = 15，写出来的是 5，说明进了一位）。每一步只需要看当前这一对数字和上一位，进位随着自回归一步步往前传，正好借用了第 2 条规则要求的那个过程。

这件事早有人系统地研究过。维护者自己参与的论文《Teaching Arithmetic to Small Transformers》比较了多种数据写法，结论是倒序输出让小模型学得又快又好[^teaching]。Claude Code 在那次比赛里第一次用不定长的 `123+45=` 格式时失败了，换成补零到定长、结果倒序以后立刻成功[^pressure]。

### 尝试：用同一个模型对比两种写法

光看图还不够有说服力，我们自己训了一下[^notes]。模型结构照搬训练榜上那篇论文的代码（单层、RoPE、SwiGLU），宽度 8、688 个参数；数据格式除了数字顺序，其余完全相同：同样的分隔 0，同样补零到十位，同样一次出 128 道随机题。每种写法用 3 个随机种子各训 1 万步，在 CPU 上每次不到两分钟。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ab-order-exp-title">同一个模型，只改数字的书写顺序（训练 1 万步，每步 128 道题）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ab-order-exp-title">
<colgroup><col style="width:26%"><col><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">模型</th><th scope="col">写法</th><th scope="col" class="num">整题全对</th><th scope="col">每一位单独看</th></tr>
</thead>
<tbody>
<tr><th scope="row" rowspan="2">688 参数<br><span class="mono">d=8，3 个种子</span></th><td>低位在前</td><td class="num mono good">100%、100%、100%</td><td>1000 步时就已全对</td></tr>
<tr><td>高位在前</td><td class="num mono bad">0%、0%、0%</td><td>最高两位约 90%，其余多在 10%–40%</td></tr>
<tr><th scope="row" rowspan="2">7336 参数<br><span class="mono">d=32</span></th><td>低位在前</td><td class="num mono good">100%（1 个种子）</td><td>1000 步时就已全对</td></tr>
<tr><td>高位在前</td><td class="num mono warn">27%、66%（2 个种子）</td><td>每位 88%–100%，错误散在各位</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">模型结构取自 M10S-Transformer（单层，RoPE θ=3，SwiGLU，2 个查询头），学习率 0.01 余弦退火；测试题是 <code>verify.py</code> 用种子 2025 生成的前 2000 道。</p>
</div>
<figcaption>表 2 · 低位在前，688 个参数一千步就学会；高位在前，放大十倍也只对一部分</figcaption>
</figure>

**低位在前**，三个种子都在第 1000 步（约 11 秒）时就把 2000 道测试题全部做对了。**高位在前**，三个种子训完 1 万步，一道整题也没对：最高两位能猜对九成左右，往下的数位大多只比瞎猜（10%）好一点。把模型放大十倍到 7336 个参数，高位在前终于能学到每一位都有九成以上的正确率，可十一位要同时对，整题正确率只有 27% 和 66%；同样大小的模型换成低位在前，一千步就全对。

高位在前的小模型卡在哪里，我们没有深究，从“只有最高两位对”来看，它大概连每一位该看哪两个数字都没学会（这是推断）。但结论很清楚：**同样的参数、同样的数据量，只是把数字倒过来写，就是学得会和学不会的区别**。


榜上的每一个模型，无论训练的还是手写的，都用了低位在前的写法。格式解决以后，剩下的问题就是：**模型能压到多小？**

## 四、训练榜：从 6080 到 36

### 现象：五周，两条阶梯

把两张榜的榜首按时间连起来（日期取自仓库的提交记录），是两条往下走的阶梯：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 384" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ab-time-title ab-time-desc">
<title id="ab-time-title">AdderBoard 两张榜第一名随时间的变化</title>
<desc id="ab-time-desc">横轴是 2026 年 2 月 19 日到 3 月 25 日，纵轴是参数个数的对数。训练榜从 6080、1644 降到 311、234、89、62，3 月 25 日到 36；手写榜从 2 月 24 日的 139 降到 36、12、10，3 月 25 日到 6。</desc>
<defs>
<marker id="ab-time-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="ab-time-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="384" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="352" rx="8"/>
<text class="t-zone" x="32" y="44">两张榜的第一名（参数个数，对数坐标）</text>
<line class="dd-hair" x1="80" y1="271.8" x2="760" y2="271.8"/>
<text class="t-note" x="72" y="275.8" text-anchor="end">10</text>
<line class="dd-hair" x1="80" y1="201.2" x2="760" y2="201.2"/>
<text class="t-note" x="72" y="205.2" text-anchor="end">100</text>
<line class="dd-hair" x1="80" y1="130.6" x2="760" y2="130.6"/>
<text class="t-note" x="72" y="134.6" text-anchor="end">1000</text>
<line class="dd-hair" x1="80" y1="60.0" x2="760" y2="60.0"/>
<text class="t-note" x="72" y="64.0" text-anchor="end">10000</text>
<text class="t-note" x="80" y="324" text-anchor="middle">2月19日</text>
<line class="dd-hair" x1="80" y1="304" x2="80" y2="310"/>
<text class="t-note" x="180" y="324" text-anchor="middle">2月24日</text>
<line class="dd-hair" x1="180" y1="304" x2="180" y2="310"/>
<text class="t-note" x="320" y="324" text-anchor="middle">3月3日</text>
<line class="dd-hair" x1="320" y1="304" x2="320" y2="310"/>
<text class="t-note" x="760" y="324" text-anchor="middle">3月25日</text>
<line class="dd-hair" x1="760" y1="304" x2="760" y2="310"/>
<path class="dd-edge" d="M180 191.1 V195.3 H220 V213.9 V222.4 V232.5 H320 V237.1 V266.2 H340 V271.8 H760 V287.4"/>
<path class="dd-edge-acc" d="M80 75.3 V115.3 H180 V166.4 H320 V175.1 V204.7 V213.5 H340 V215.8 H760 V232.5"/>
<circle class="dd-fill-acc" cx="80" cy="75.3" r="3.5"/>
<circle class="dd-fill-acc" cx="80" cy="115.3" r="3.5"/>
<circle class="dd-fill-acc" cx="180" cy="166.4" r="3.5"/>
<circle class="dd-fill-acc" cx="320" cy="175.1" r="3.5"/>
<circle class="dd-fill-acc" cx="320" cy="204.7" r="3.5"/>
<circle class="dd-fill-acc" cx="320" cy="213.5" r="3.5"/>
<circle class="dd-fill-acc" cx="340" cy="215.8" r="3.5"/>
<circle class="dd-fill-acc" cx="760" cy="232.5" r="3.5"/>
<circle class="dd-dot" cx="180" cy="191.1" r="3.5"/>
<circle class="dd-dot" cx="180" cy="195.3" r="3.5"/>
<circle class="dd-dot" cx="220" cy="213.9" r="3.5"/>
<circle class="dd-dot" cx="220" cy="222.4" r="3.5"/>
<circle class="dd-dot" cx="220" cy="232.5" r="3.5"/>
<circle class="dd-dot" cx="320" cy="237.1" r="3.5"/>
<circle class="dd-dot" cx="320" cy="266.2" r="3.5"/>
<circle class="dd-dot" cx="340" cy="271.8" r="3.5"/>
<circle class="dd-dot" cx="760" cy="287.4" r="3.5"/>
<text class="t-acc t-mono" x="88" y="79.3" text-anchor="start">6080 · Claude Code</text>
<text class="t-acc t-mono" x="88" y="135.3" text-anchor="start">1644 · Codex（成对 token）</text>
<text class="t-acc t-mono" x="172" y="160.4" text-anchor="end">311</text>
<text class="t-acc t-mono" x="312" y="179.1" text-anchor="end">234</text>
<text class="t-acc t-mono" x="312" y="208.7" text-anchor="end">89</text>
<text class="t-acc t-mono" x="348" y="209.8" text-anchor="start">62</text>
<text class="t-acc t-mono" x="752" y="224.5" text-anchor="end">36</text>
<text class="t-mono" x="172" y="195.1" text-anchor="end">139</text>
<text class="t-mono" x="212" y="248.5" text-anchor="end">36</text>
<text class="t-mono" x="312" y="270.2" text-anchor="end">12</text>
<text class="t-mono" x="348" y="283.8" text-anchor="start">10</text>
<text class="t-mono" x="752" y="281.4" text-anchor="end">6*</text>
<text class="t-say" x="420" y="120">训练榜（强调色）：变窄 → 低秩 → 绑定 → 圆弧嵌入</text>
<text class="t-say" x="420" y="144">手写榜（灰色）：开榜两天就压到 36，之后只剩个位数可争</text>
<text class="t-note" x="32" y="352">Codex 第一轮交出 366,320 个参数，在图的上方之外。日期为提交被收录的日子；6* 的星号见第六节。</text>
</svg>
</div>
<figcaption>图 3 · 五周里，训练榜从 6080 降到 36，手写榜从 139 降到 6；后半程每省一个参数都更难</figcaption>
</figure>

训练榜从 Claude Code 的 6080 一路降到 36，手写榜从开榜时的 139 降到 6。两条线有一个共同点：越往后，每一步省下的参数越少，花的力气越大。那么，**训练出来的模型是靠什么一步步变小的？**

### 第一步：把模型变窄

最直接的办法是减小宽度 $d$。参数量大致随 $d^2$ 增长，宽度减半，参数少四分之三。但宽度不能无限减：Claude Code 发现宽度 12 一下子就完全学不会，社区里也有好几个人在 800 个参数附近看到准确率断崖式下降[^board]（断崖的位置和具体结构有关，第三节那个用 RoPE 的 688 参数模型就没有遇到）。早期几支队伍不约而同地停在了宽度 7。

再往下走，需要换一种思路：**不是把矩阵变小，而是让矩阵里的数不再各自独立**。

### 第二步：让参数共用

**低秩分解**。一个 7×7 的矩阵有 49 个数，如果把它写成一个 7×3 矩阵乘一个 3×7 矩阵，只要 42 个；宽度越大省得越多。秩为 3 的分解是宽度 4 到 7 这一档模型的主要手段，311 参数的模型靠它把宽度压到了 4[^board]。

**权重绑定**。前面说过，词嵌入和输出头可以共用一张表。同样的思路可以推到注意力内部：让 V 直接等于 K（`tieKV`），让输出投影等于 Q 的转置（`tieQO`），让几处归一化共用一组系数。122 参数的那个模型，就是这样一步步减到 89、86、83 的[^m10s]：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ab-tie-title">同一个 122 参数模型，靠共用参数逐步变小</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ab-tie-title">
<colgroup><col style="width:34%"><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">改动</th><th scope="col">省掉了什么</th><th scope="col" class="num">参数</th></tr>
</thead>
<tbody>
<tr><th scope="row">基础版</th><td>只有词嵌入和输出头共用</td><td class="num mono">122</td></tr>
<tr><th scope="row">MLP 中间层 3 → 2</th><td>三个矩阵各少一列</td><td class="num mono">113</td></tr>
<tr><th scope="row">O = Qᵀ（tieQO）</th><td>输出投影不再独立</td><td class="num mono">101</td></tr>
<tr><th scope="row">V = K（tieKV）</th><td>值投影不再独立</td><td class="num mono">89</td></tr>
<tr><th scope="row">两处块内归一化共用</th><td>少一组 3 个系数</td><td class="num mono">86</td></tr>
<tr><th scope="row">三处归一化全部共用</th><td>再少一组 3 个系数</td><td class="num mono acc">83</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">数字取自 M10S-Transformer 论文表 1，公式为 95 + 9×(MLP 中间层) − 12×tieKV − 12×tieQO − 3 或 6。</p>
</div>
<figcaption>表 3 · 每绑定一处，模型就少一块独立的矩阵；代价是剩下的参数要身兼数职</figcaption>
</figure>

**把整张表换成一个公式**。到 36 参数这一档，词嵌入那 30 个数也被拿掉了：10 个数字不再各有一个自由的向量，而是等间距地排在一段圆弧上（第三个坐标恒为 0），整张表只由半径、起始角、间隔角 3 个参数决定。K 也不再是独立的矩阵，而是 Q 旋转一个角度（1 个参数），MLP 的降维矩阵是升维矩阵转置后再转一个角度[^m10s]。这些都是“结构上的先验”：人知道数字之间是有顺序的、等距的，就直接把这个形状写进模型，不让它自己去学。

### 第三步：模型越小，越难训练

参数省下来了，代价是训练变得非常难。36 参数这一系列的作者把过程写成了一篇论文，里面有几组数字值得细看[^m10s]。

**顿悟**。这些小模型的训练曲线很特别：训练损失很早就降到接近 0，测试准确率却在很长一段时间里停在接近 0，然后某一刻突然跳到接近 100%。这叫 grokking（顿悟）[^grokking]。122 个参数时，10 个随机种子里有 4 个能在 20 万步内顿悟；减到 89 个参数，从头训练 15 次，**一次也没有**。

**多阶段微调**。解决办法是接力：第一阶段从头训练，拿到一个 50%–85% 准确率的模型；第二阶段从它出发，换小一点的学习率、大一点的批量再训；一直接到第四阶段，89 参数的模型才到 100%。从一个只错一题的模型出发再微调，24 次里 24 次都能顿悟。

**进位幽灵**。四个独立训练出来、各只错一题的 89 参数模型，错的是**同一道题**，写出的错答案也一模一样：6,028,846,396 + 90,949,567，正确答案 6,119,795,963，四个模型都写成 5,119,795,963。这道题的进位是“有、有、无、有、无、有、无、有、无、无”交替出现的，模型在一个本该截断进位的位置，让一点残余的进位信号漏了过去。把这道题加进训练批次，50 步梯度更新就修好了。作者的结论是：这不是容量不够，而是随机出题时这种进位模式太少见，训练数据没覆盖到。

**定向微调**。顺着这个思路，作者干脆在评测集上找出模型做错的题，把它们混进训练批次反复训，修一轮、再找、再修。83 参数的模型修了 4 轮、累计约 170 道题才清零；榜上这个 36 参数模型的提交说明里写的是“2 轮，累计 70 道错题”[^m10s]。这里要注意，用来找错题的评测集（种子 42）和官方验证用的种子 2025 是分开的，所以官方验证仍然是没见过的题；但论文自己也承认，定向微调证明的是**这个结构装得下**正确答案，不是它**能自己学会**。

我们用官方脚本复跑了这个 36 参数模型：10010 道题全对，用时 47.7 秒[^notes]。

训练榜到这里已经很挤了，每少一个参数都要配一套复杂的训练流程。可是手写榜的榜首只有 6 个参数，比 36 少了六分之五。**不靠训练、直接把数填进去，6 个参数是怎么做加法的？**

## 五、手写榜：6 个参数怎么做加法

我们把 6 参数模型的代码逐行读了一遍，又在每一步打印了模型内部的数值[^zc6]。它其实是 10 参数版本的“记账改版”，计算过程一模一样，下一节再说差在哪里[^loki10]。整个模型宽度只有 2，一层，一个注意力头。还是用 758 + 467 这道题，看它怎么一位位算出 1225。

### 输入的写法

输入一共 31 个 token：

```text
位置   0    1 … 10          11 … 19     20 … 29          30
      [0]  a 倒序（10 位）  9 个 0      b 倒序（10 位）  [0]
758 + 467：
      0    8 5 7 0 0 0 0 0 0 0   0 … 0   7 6 4 0 0 0 0 0 0 0   0
```

和训练榜常用的“中间垫两个 0”不同，这里在两个加数之间垫了 **9 个 0**。于是 a 的第 $i$ 位在位置 $1+i$，b 的第 $i$ 位在位置 $20+i$，**同一位上的两个数字恰好相隔 19 个位置**。这个 19 是后面一切的关键。

### 词嵌入：一个坐标存数字

宽度为 2，每个 token 只有两个坐标。数字 $d$ 的嵌入是：

$$
e(d) = \big[\,1000 - 0.001\,d^2,\ \ -d\,\big]
$$

第二个坐标直接存着这个数字（取负号），第一个坐标几乎是常数 1000，只带一点二次项，最后输出时要用。整张表只用了 2 个参数：1000 和 0.001。

### 注意力：RoPE 的钟面上，两个加数重合

前面说过，RoPE 把每个位置转一个角度。这个模型把转速设成每 19 个位置转一整圈。于是“相隔 19 个位置”在钟面上就等于“没隔”：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ab-clock-title ab-clock-desc">
<title id="ab-clock-title">周期 19 的 RoPE 让同一位的两个数字重合</title>
<desc id="ab-clock-desc">左边是一个 19 格的钟面，刻度表示往回隔了几个位置除以 19 的余数。查询指向 10.3 格；10 格上是 a 的十位和 b 的十位，11 格上是两个个位。右边列出预测十位时被看的位置和权重：b₁=6 和 a₁=5 各 0.454，b₀=7 和 a₀=8 各 0.045，注意力输出 125。</desc>
<defs>
<marker id="ab-clock-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="ab-clock-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="368" rx="8"/>
<rect class="dd-zone" x="408" y="16" width="376" height="368" rx="8"/>
<text class="t-zone" x="32" y="44">RoPE 的钟面：19 个位置转一圈</text>
<text class="t-zone" x="424" y="44">预测十位：位置 31 往回看</text>
<text class="t-say" x="32" y="70">刻度 = 往回隔了几个位置，满 19 归零</text>
<circle class="dd-track" cx="208" cy="224" r="112"/>
<line class="dd-edge" x1="208.0" y1="118.0" x2="208.0" y2="106.0"/>
<text class="t-note" x="208.0" y="138.0" text-anchor="middle">0</text>
<line class="dd-edge" x1="242.4" y1="123.7" x2="246.3" y2="112.4"/>
<line class="dd-edge" x1="273.1" y1="140.4" x2="280.5" y2="130.9"/>
<line class="dd-edge" x1="296.7" y1="166.0" x2="306.8" y2="159.5"/>
<line class="dd-edge" x1="310.8" y1="198.0" x2="322.4" y2="195.0"/>
<line class="dd-edge" x1="313.6" y1="232.8" x2="325.6" y2="233.7"/>
<text class="t-note" x="297.7" y="235.4" text-anchor="middle">5</text>
<line class="dd-edge" x1="305.1" y1="266.6" x2="316.1" y2="271.4"/>
<line class="dd-edge" x1="286.0" y1="295.8" x2="294.8" y2="303.9"/>
<line class="dd-edge" x1="258.5" y1="317.2" x2="264.2" y2="327.8"/>
<line class="dd-edge" x1="225.4" y1="328.6" x2="227.4" y2="340.4"/>
<line class="dd-edge" x1="190.6" y1="328.6" x2="188.6" y2="340.4"/>
<line class="dd-edge" x1="157.5" y1="317.2" x2="151.8" y2="327.8"/>
<line class="dd-edge" x1="130.0" y1="295.8" x2="121.2" y2="303.9"/>
<line class="dd-edge" x1="110.9" y1="266.6" x2="99.9" y2="271.4"/>
<line class="dd-edge" x1="102.4" y1="232.8" x2="90.4" y2="233.7"/>
<line class="dd-edge" x1="105.2" y1="198.0" x2="93.6" y2="195.0"/>
<text class="t-note" x="120.8" y="205.9" text-anchor="middle">15</text>
<line class="dd-edge" x1="119.3" y1="166.0" x2="109.2" y2="159.5"/>
<line class="dd-edge" x1="142.9" y1="140.4" x2="135.5" y2="130.9"/>
<line class="dd-edge" x1="173.6" y1="123.7" x2="169.7" y2="112.4"/>
<line class="dd-edge-acc" x1="208" y1="224" x2="185.5" y2="307.0" marker-end="url(#ab-clock-arrow-acc)"/>
<circle class="dd-dot" cx="208" cy="224" r="4"/>
<text class="t-acc t-mono" x="204.4" y="274.2">查询 10.3</text>
<circle class="dd-fill-acc" cx="189.6" cy="334.5" r="8"/>
<text class="t-acc t-mono" x="203.6" y="360.5">10：a₁ 与 b₁</text>
<circle class="dd-dot" cx="154.7" cy="322.5" r="6"/>
<text class="t-mono" x="142.7" y="346.5" text-anchor="end">11：a₀ 与 b₀</text>
<rect class="dd-card" x="424" y="64" width="344" height="190" rx="6"/>
<path class="dd-card-head" d="M425 96 V71 Q425 65 431 65 H761 Q767 65 767 71 V96 Z"/>
<text class="t-name" x="440" y="85">被看的位置</text><text class="t-note" x="752" y="85" text-anchor="end">权重</text>
<text class="t-mono" x="440" y="113">位置 21</text><text class="t-acc t-mono" x="520" y="113">b₁ = 6</text><text class="t-val" x="592" y="113">隔 10</text><text class="t-acc t-mono" x="752" y="113" text-anchor="end">0.454</text>
<line class="dd-hair" x1="424" y1="122" x2="768" y2="122"/>
<text class="t-mono" x="440" y="139">位置 2</text><text class="t-acc t-mono" x="520" y="139">a₁ = 5</text><text class="t-val" x="592" y="139">隔 29 = 10 + 19</text><text class="t-acc t-mono" x="752" y="139" text-anchor="end">0.454</text>
<line class="dd-hair" x1="424" y1="148" x2="768" y2="148"/>
<text class="t-mono" x="440" y="165">位置 20</text><text class="t-mono" x="520" y="165">b₀ = 7</text><text class="t-val" x="592" y="165">隔 11</text><text class="t-mono" x="752" y="165" text-anchor="end">0.045</text>
<line class="dd-hair" x1="424" y1="174" x2="768" y2="174"/>
<text class="t-mono" x="440" y="191">位置 1</text><text class="t-mono" x="520" y="191">a₀ = 8</text><text class="t-val" x="592" y="191">隔 30 = 11 + 19</text><text class="t-mono" x="752" y="191" text-anchor="end">0.045</text>
<line class="dd-hair" x1="424" y1="200" x2="768" y2="200"/>
<text class="t-mono" x="440" y="217">其余 28 个</text><text class="t-mono" x="520" y="217"></text><text class="t-val" x="592" y="217"></text><text class="t-mono" x="752" y="217" text-anchor="end">&lt; 0.002</text>
<line class="dd-hair" x1="424" y1="226" x2="768" y2="226"/>
<text class="t-name" x="440" y="245">注意力输出</text><text class="t-mono" x="752" y="245" text-anchor="end">10×(5+6) + (8+7) = 125</text>
<text class="t-say" x="424" y="292">两个加数之间垫 9 个 0，同一位的两个数字</text>
<text class="t-say" x="424" y="314">正好隔 19 个位置，在钟面上落在同一格。</text>
<text class="t-say" x="424" y="346">查询指向 10.3 而不是 10，上一位的两个数字</text>
<text class="t-say" x="424" y="368">恰好分到十分之一的权重，捎上了进位的线索。</text>
</svg>
</div>
<figcaption>图 4 · 隔 10 和隔 29 在周期 19 的钟面上是同一格：注意力一次就把同一位的两个数字都取到了</figcaption>
</figure>

在预测十位的这一步，序列最后一个 token 是刚写下的个位 5，位置 31。它往回看：b 的十位在位置 21，相隔 10；a 的十位在位置 2，相隔 29，比 10 多一整圈。**在钟面上，a₁ 和 b₁ 落在同一个刻度上**。查询向量的方向被设在 10.3 这个刻度：离 10 最近，所以 a₁ 和 b₁ 各拿到 0.454 的权重；11 刻度上的 a₀、b₀（也就是上一位的两个数字）离得稍远，各拿到 0.045，恰好是 1/10。其余位置加起来不到 0.002。

值向量取的是嵌入的第二个坐标（数字本身）再乘一个系数，于是注意力的输出是：

$$
\text{注意力输出} = 10\,(a_i + b_i) + (a_{i-1} + b_{i-1})
$$

十位这一步就是 $10 \times (5+6) + (8+7) = 125$。

这里有两个细节。一是那 9 个 0 的另一个作用：算最高位（第 11 位）时，a、b 都没有这一位了，查询指向的地方正好是补的 0，结果自然是 0。二是“注意力只看相对距离”这一点被用到了极致：每一步的查询都指向“10.3 个位置之前”，同一套参数对 11 个数位通用，不需要为每一位单独设计。

### 残差：进位自己冒出来

注意力的输出加回残差流以后，要和当前 token 自己的嵌入相加。当前 token 是上一步写下的那一位 $s_{i-1}$，嵌入第二个坐标是 $-s_{i-1}$。于是：

$$
h = 10\,(a_i + b_i) + (a_{i-1} + b_{i-1}) - s_{i-1}
$$

而上一位的竖式是 $a_{i-1} + b_{i-1} + c_{i-1} = s_{i-1} + 10\,c_i$，其中 $c$ 是进位。代进去：

$$
h = 10\,(a_i + b_i + c_i) - c_{i-1}
$$

**进位就这样冒了出来**：上一位两个数字的和，减去上一位实际写下的数字，差的那 10 就是进位。模型没有任何地方专门存进位，它是从“自己刚写下的那个数字”里读出来的，正是第三节说的低位在前的好处。十位这一步，$h = 125 - 5 = 120$，正好是 $10 \times (5 + 6 + 1)$；百位那一步是 $121 - 2 = 119 = 10 \times (7 + 4 + 1) - 1$。末尾那个 $-c_{i-1}$ 最多是 −1，对后面的取整没有影响。

### MLP：满十就减掉

$h/10$ 现在是这一位的“竖式和”，范围 0 到 19。要写下的数字是它除以 10 的余数，这一步交给 MLP。宽度 2 的 SwiGLU 里有两个门，阈值设得只差一点点，两个门的输出相减就成了一个陡峭的台阶：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 384" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ab-hinge-title ab-hinge-desc">
<title id="ab-hinge-title">6 参数模型里 MLP 的台阶</title>
<desc id="ab-hinge-desc">横轴是进入 MLP 前的 h，等于 10 乘这一位的竖式和，从 0 到 190；纵轴是 MLP 之后的值。h 小于 95 时输出等于输入，h 超过 95 时整条线向下跳约 99。圆点标出竖式和为 0 到 19 时的取值，竖式和不小于 10 的点用强调色。</desc>
<rect class="dd-frame" width="800" height="384" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="352" rx="8"/>
<text class="t-zone" x="32" y="44">MLP 前后：h 的第二个坐标</text>
<line class="dd-hair" x1="96" y1="294.2" x2="736" y2="294.2"/>
<text class="t-note" x="88" y="298.2" text-anchor="end">0</text>
<line class="dd-hair" x1="96" y1="272.4" x2="736" y2="272.4"/>
<line class="dd-hair" x1="96" y1="250.5" x2="736" y2="250.5"/>
<line class="dd-hair" x1="96" y1="228.7" x2="736" y2="228.7"/>
<text class="t-note" x="88" y="232.7" text-anchor="end">30</text>
<line class="dd-hair" x1="96" y1="206.9" x2="736" y2="206.9"/>
<line class="dd-hair" x1="96" y1="185.1" x2="736" y2="185.1"/>
<line class="dd-hair" x1="96" y1="163.3" x2="736" y2="163.3"/>
<text class="t-note" x="88" y="167.3" text-anchor="end">60</text>
<line class="dd-hair" x1="96" y1="141.5" x2="736" y2="141.5"/>
<line class="dd-hair" x1="96" y1="119.6" x2="736" y2="119.6"/>
<line class="dd-hair" x1="96" y1="97.8" x2="736" y2="97.8"/>
<text class="t-note" x="88" y="101.8" text-anchor="end">90</text>
<text class="t-note" x="88" y="64" text-anchor="end">MLP 后</text>
<text class="t-note" x="96.0" y="340" text-anchor="middle">0</text>
<text class="t-note" x="264.4" y="340" text-anchor="middle">50</text>
<text class="t-note" x="416.0" y="340" text-anchor="middle">95</text>
<text class="t-note" x="601.3" y="340" text-anchor="middle">150</text>
<text class="t-note" x="736.0" y="340" text-anchor="middle">190</text>
<text class="t-note" x="736" y="360" text-anchor="end">MLP 前 h = 10 × 这一位的竖式和</text>
<path class="dd-edge" d="M96.0 294.2 L96.8 293.6 L97.7 293.1 L98.5 292.5 L99.4 292.0 L100.2 291.5 L101.1 290.9 L101.9 290.4 L102.7 289.8 L103.6 289.3 L104.4 288.7 L105.3 288.2 L106.1 287.6 L106.9 287.1 L107.8 286.5 L108.6 286.0 L109.5 285.5 L110.3 284.9 L111.2 284.4 L112.0 283.8 L112.8 283.3 L113.7 282.7 L114.5 282.2 L115.4 281.6 L116.2 281.1 L117.1 280.5 L117.9 280.0 L118.7 279.5 L119.6 278.9 L120.4 278.4 L121.3 277.8 L122.1 277.3 L122.9 276.7 L123.8 276.2 L124.6 275.6 L125.5 275.1 L126.3 274.5 L127.2 274.0 L128.0 273.5 L128.8 272.9 L129.7 272.4 L130.5 271.8 L131.4 271.3 L132.2 270.7 L133.1 270.2 L133.9 269.6 L134.7 269.1 L135.6 268.5 L136.4 268.0 L137.3 267.5 L138.1 266.9 L138.9 266.4 L139.8 265.8 L140.6 265.3 L141.5 264.7 L142.3 264.2 L143.2 263.6 L144.0 263.1 L144.8 262.5 L145.7 262.0 L146.5 261.5 L147.4 260.9 L148.2 260.4 L149.1 259.8 L149.9 259.3 L150.7 258.7 L151.6 258.2 L152.4 257.6 L153.3 257.1 L154.1 256.5 L154.9 256.0 L155.8 255.5 L156.6 254.9 L157.5 254.4 L158.3 253.8 L159.2 253.3 L160.0 252.7 L160.8 252.2 L161.7 251.6 L162.5 251.1 L163.4 250.5 L164.2 250.0 L165.1 249.5 L165.9 248.9 L166.7 248.4 L167.6 247.8 L168.4 247.3 L169.3 246.7 L170.1 246.2 L170.9 245.6 L171.8 245.1 L172.6 244.5 L173.5 244.0 L174.3 243.5 L175.2 242.9 L176.0 242.4 L176.8 241.8 L177.7 241.3 L178.5 240.7 L179.4 240.2 L180.2 239.6 L181.1 239.1 L181.9 238.5 L182.7 238.0 L183.6 237.5 L184.4 236.9 L185.3 236.4 L186.1 235.8 L186.9 235.3 L187.8 234.7 L188.6 234.2 L189.5 233.6 L190.3 233.1 L191.2 232.5 L192.0 232.0 L192.8 231.5 L193.7 230.9 L194.5 230.4 L195.4 229.8 L196.2 229.3 L197.1 228.7 L197.9 228.2 L198.7 227.6 L199.6 227.1 L200.4 226.5 L201.3 226.0 L202.1 225.5 L202.9 224.9 L203.8 224.4 L204.6 223.8 L205.5 223.3 L206.3 222.7 L207.2 222.2 L208.0 221.6 L208.8 221.1 L209.7 220.5 L210.5 220.0 L211.4 219.5 L212.2 218.9 L213.1 218.4 L213.9 217.8 L214.7 217.3 L215.6 216.7 L216.4 216.2 L217.3 215.6 L218.1 215.1 L218.9 214.5 L219.8 214.0 L220.6 213.5 L221.5 212.9 L222.3 212.4 L223.2 211.8 L224.0 211.3 L224.8 210.7 L225.7 210.2 L226.5 209.6 L227.4 209.1 L228.2 208.5 L229.1 208.0 L229.9 207.5 L230.7 206.9 L231.6 206.4 L232.4 205.8 L233.3 205.3 L234.1 204.7 L234.9 204.2 L235.8 203.6 L236.6 203.1 L237.5 202.5 L238.3 202.0 L239.2 201.5 L240.0 200.9 L240.8 200.4 L241.7 199.8 L242.5 199.3 L243.4 198.7 L244.2 198.2 L245.1 197.6 L245.9 197.1 L246.7 196.5 L247.6 196.0 L248.4 195.5 L249.3 194.9 L250.1 194.4 L250.9 193.8 L251.8 193.3 L252.6 192.7 L253.5 192.2 L254.3 191.6 L255.2 191.1 L256.0 190.5 L256.8 190.0 L257.7 189.5 L258.5 188.9 L259.4 188.4 L260.2 187.8 L261.1 187.3 L261.9 186.7 L262.7 186.2 L263.6 185.6 L264.4 185.1 L265.3 184.5 L266.1 184.0 L266.9 183.5 L267.8 182.9 L268.6 182.4 L269.5 181.8 L270.3 181.3 L271.2 180.7 L272.0 180.2 L272.8 179.6 L273.7 179.1 L274.5 178.5 L275.4 178.0 L276.2 177.5 L277.1 176.9 L277.9 176.4 L278.7 175.8 L279.6 175.3 L280.4 174.7 L281.3 174.2 L282.1 173.6 L282.9 173.1 L283.8 172.5 L284.6 172.0 L285.5 171.5 L286.3 170.9 L287.2 170.4 L288.0 169.8 L288.8 169.3 L289.7 168.7 L290.5 168.2 L291.4 167.6 L292.2 167.1 L293.1 166.5 L293.9 166.0 L294.7 165.5 L295.6 164.9 L296.4 164.4 L297.3 163.8 L298.1 163.3 L298.9 162.7 L299.8 162.2 L300.6 161.6 L301.5 161.1 L302.3 160.5 L303.2 160.0 L304.0 159.5 L304.8 158.9 L305.7 158.4 L306.5 157.8 L307.4 157.3 L308.2 156.7 L309.1 156.2 L309.9 155.6 L310.7 155.1 L311.6 154.5 L312.4 154.0 L313.3 153.5 L314.1 152.9 L314.9 152.4 L315.8 151.8 L316.6 151.3 L317.5 150.7 L318.3 150.2 L319.2 149.6 L320.0 149.1 L320.8 148.5 L321.7 148.0 L322.5 147.5 L323.4 146.9 L324.2 146.4 L325.1 145.8 L325.9 145.3 L326.7 144.7 L327.6 144.2 L328.4 143.6 L329.3 143.1 L330.1 142.5 L330.9 142.0 L331.8 141.5 L332.6 140.9 L333.5 140.4 L334.3 139.8 L335.2 139.3 L336.0 138.7 L336.8 138.2 L337.7 137.6 L338.5 137.1 L339.4 136.5 L340.2 136.0 L341.1 135.5 L341.9 134.9 L342.7 134.4 L343.6 133.8 L344.4 133.3 L345.3 132.7 L346.1 132.2 L346.9 131.6 L347.8 131.1 L348.6 130.5 L349.5 130.0 L350.3 129.5 L351.2 128.9 L352.0 128.4 L352.8 127.8 L353.7 127.3 L354.5 126.7 L355.4 126.2 L356.2 125.6 L357.1 125.1 L357.9 124.5 L358.7 124.0 L359.6 123.5 L360.4 122.9 L361.3 122.4 L362.1 121.8 L362.9 121.3 L363.8 120.7 L364.6 120.2 L365.5 119.6 L366.3 119.1 L367.2 118.5 L368.0 118.0 L368.8 117.5 L369.7 116.9 L370.5 116.4 L371.4 115.8 L372.2 115.3 L373.1 114.7 L373.9 114.2 L374.7 113.6 L375.6 113.1 L376.4 112.5 L377.3 112.0 L378.1 111.5 L378.9 110.9 L379.8 110.4 L380.6 109.8 L381.5 109.3 L382.3 108.7 L383.2 108.2 L384.0 107.6 L384.8 107.1 L385.7 106.5 L386.5 106.0 L387.4 105.5 L388.2 104.9 L389.1 104.4 L389.9 103.8 L390.7 103.3 L391.6 102.7 L392.4 102.2 L393.3 101.6 L394.1 101.1 L394.9 100.5 L395.8 100.0 L396.6 99.5 L397.5 98.9 L398.3 98.4 L399.2 97.8 L400.0 97.3 L400.8 96.7 L401.7 96.2 L402.5 95.6 L403.4 95.1 L404.2 94.5 L405.1 94.0 L405.9 93.5 L406.7 92.9 L407.6 92.4 L408.4 91.8 L409.3 91.3 L410.1 90.7 L410.9 90.2 L411.8 89.6 L412.6 89.1"/>
<path class="dd-edge" d="M416.0 303.1 L416.8 302.6 L417.7 302.0 L418.5 301.5 L419.4 300.9 L420.2 300.4 L421.1 299.8 L421.9 299.2 L422.7 298.7 L423.6 298.1 L424.4 297.6 L425.3 297.0 L426.1 296.5 L426.9 295.9 L427.8 295.4 L428.6 294.8 L429.5 294.2 L430.3 293.7 L431.2 293.1 L432.0 292.6 L432.8 292.0 L433.7 291.5 L434.5 290.9 L435.4 290.4 L436.2 289.8 L437.1 289.2 L437.9 288.7 L438.7 288.1 L439.6 287.6 L440.4 287.0 L441.3 286.5 L442.1 285.9 L442.9 285.3 L443.8 284.8 L444.6 284.2 L445.5 283.7 L446.3 283.1 L447.2 282.6 L448.0 282.0 L448.8 281.5 L449.7 280.9 L450.5 280.3 L451.4 279.8 L452.2 279.2 L453.1 278.7 L453.9 278.1 L454.7 277.6 L455.6 277.0 L456.4 276.4 L457.3 275.9 L458.1 275.3 L458.9 274.8 L459.8 274.2 L460.6 273.7 L461.5 273.1 L462.3 272.5 L463.2 272.0 L464.0 271.4 L464.8 270.9 L465.7 270.3 L466.5 269.8 L467.4 269.2 L468.2 268.6 L469.1 268.1 L469.9 267.5 L470.7 267.0 L471.6 266.4 L472.4 265.9 L473.3 265.3 L474.1 264.7 L474.9 264.2 L475.8 263.6 L476.6 263.1 L477.5 262.5 L478.3 262.0 L479.2 261.4 L480.0 260.8 L480.8 260.3 L481.7 259.7 L482.5 259.2 L483.4 258.6 L484.2 258.0 L485.1 257.5 L485.9 256.9 L486.7 256.4 L487.6 255.8 L488.4 255.3 L489.3 254.7 L490.1 254.1 L490.9 253.6 L491.8 253.0 L492.6 252.5 L493.5 251.9 L494.3 251.4 L495.2 250.8 L496.0 250.2 L496.8 249.7 L497.7 249.1 L498.5 248.6 L499.4 248.0 L500.2 247.4 L501.1 246.9 L501.9 246.3 L502.7 245.8 L503.6 245.2 L504.4 244.7 L505.3 244.1 L506.1 243.5 L506.9 243.0 L507.8 242.4 L508.6 241.9 L509.5 241.3 L510.3 240.7 L511.2 240.2 L512.0 239.6 L512.8 239.1 L513.7 238.5 L514.5 238.0 L515.4 237.4 L516.2 236.8 L517.1 236.3 L517.9 235.7 L518.7 235.2 L519.6 234.6 L520.4 234.0 L521.3 233.5 L522.1 232.9 L522.9 232.4 L523.8 231.8 L524.6 231.3 L525.5 230.7 L526.3 230.1 L527.2 229.6 L528.0 229.0 L528.8 228.5 L529.7 227.9 L530.5 227.3 L531.4 226.8 L532.2 226.2 L533.1 225.7 L533.9 225.1 L534.7 224.5 L535.6 224.0 L536.4 223.4 L537.3 222.9 L538.1 222.3 L538.9 221.7 L539.8 221.2 L540.6 220.6 L541.5 220.1 L542.3 219.5 L543.2 218.9 L544.0 218.4 L544.8 217.8 L545.7 217.3 L546.5 216.7 L547.4 216.2 L548.2 215.6 L549.1 215.0 L549.9 214.5 L550.7 213.9 L551.6 213.4 L552.4 212.8 L553.3 212.2 L554.1 211.7 L554.9 211.1 L555.8 210.6 L556.6 210.0 L557.5 209.4 L558.3 208.9 L559.2 208.3 L560.0 207.8 L560.8 207.2 L561.7 206.6 L562.5 206.1 L563.4 205.5 L564.2 205.0 L565.1 204.4 L565.9 203.8 L566.7 203.3 L567.6 202.7 L568.4 202.2 L569.3 201.6 L570.1 201.0 L570.9 200.5 L571.8 199.9 L572.6 199.4 L573.5 198.8 L574.3 198.2 L575.2 197.7 L576.0 197.1 L576.8 196.6 L577.7 196.0 L578.5 195.4 L579.4 194.9 L580.2 194.3 L581.1 193.7 L581.9 193.2 L582.7 192.6 L583.6 192.1 L584.4 191.5 L585.3 190.9 L586.1 190.4 L586.9 189.8 L587.8 189.3 L588.6 188.7 L589.5 188.1 L590.3 187.6 L591.2 187.0 L592.0 186.5 L592.8 185.9 L593.7 185.3 L594.5 184.8 L595.4 184.2 L596.2 183.7 L597.1 183.1 L597.9 182.5 L598.7 182.0 L599.6 181.4 L600.4 180.9 L601.3 180.3 L602.1 179.7 L602.9 179.2 L603.8 178.6 L604.6 178.0 L605.5 177.5 L606.3 176.9 L607.2 176.4 L608.0 175.8 L608.8 175.2 L609.7 174.7 L610.5 174.1 L611.4 173.6 L612.2 173.0 L613.1 172.4 L613.9 171.9 L614.7 171.3 L615.6 170.7 L616.4 170.2 L617.3 169.6 L618.1 169.1 L618.9 168.5 L619.8 167.9 L620.6 167.4 L621.5 166.8 L622.3 166.3 L623.2 165.7 L624.0 165.1 L624.8 164.6 L625.7 164.0 L626.5 163.4 L627.4 162.9 L628.2 162.3 L629.1 161.8 L629.9 161.2 L630.7 160.6 L631.6 160.1 L632.4 159.5 L633.3 159.0 L634.1 158.4 L634.9 157.8 L635.8 157.3 L636.6 156.7 L637.5 156.1 L638.3 155.6 L639.2 155.0 L640.0 154.5 L640.8 153.9 L641.7 153.3 L642.5 152.8 L643.4 152.2 L644.2 151.6 L645.1 151.1 L645.9 150.5 L646.7 150.0 L647.6 149.4 L648.4 148.8 L649.3 148.3 L650.1 147.7 L650.9 147.1 L651.8 146.6 L652.6 146.0 L653.5 145.5 L654.3 144.9 L655.2 144.3 L656.0 143.8 L656.8 143.2 L657.7 142.6 L658.5 142.1 L659.4 141.5 L660.2 141.0 L661.1 140.4 L661.9 139.8 L662.7 139.3 L663.6 138.7 L664.4 138.1 L665.3 137.6 L666.1 137.0 L666.9 136.5 L667.8 135.9 L668.6 135.3 L669.5 134.8 L670.3 134.2 L671.2 133.6 L672.0 133.1 L672.8 132.5 L673.7 131.9 L674.5 131.4 L675.4 130.8 L676.2 130.3 L677.1 129.7 L677.9 129.1 L678.7 128.6 L679.6 128.0 L680.4 127.4 L681.3 126.9 L682.1 126.3 L682.9 125.8 L683.8 125.2 L684.6 124.6 L685.5 124.1 L686.3 123.5 L687.2 122.9 L688.0 122.4 L688.8 121.8 L689.7 121.2 L690.5 120.7 L691.4 120.1 L692.2 119.6 L693.1 119.0 L693.9 118.4 L694.7 117.9 L695.6 117.3 L696.4 116.7 L697.3 116.2 L698.1 115.6 L698.9 115.0 L699.8 114.5 L700.6 113.9 L701.5 113.4 L702.3 112.8 L703.2 112.2 L704.0 111.7 L704.8 111.1 L705.7 110.5 L706.5 110.0 L707.4 109.4 L708.2 108.8 L709.1 108.3 L709.9 107.7 L710.7 107.1 L711.6 106.6 L712.4 106.0 L713.3 105.5 L714.1 104.9 L714.9 104.3 L715.8 103.8 L716.6 103.2 L717.5 102.6 L718.3 102.1 L719.2 101.5 L720.0 100.9 L720.8 100.4 L721.7 99.8 L722.5 99.2 L723.4 98.7 L724.2 98.1 L725.1 97.6 L725.9 97.0 L726.7 96.4 L727.6 95.9 L728.4 95.3 L729.3 94.7 L730.1 94.2 L730.9 93.6 L731.8 93.0 L732.6 92.5 L733.5 91.9 L734.3 91.3 L735.2 90.8 L736.0 90.2"/>
<line class="dd-edge dd-dash" x1="416.0" y1="86.9" x2="416.0" y2="302.9"/>
<circle class="dd-dot" cx="96.0" cy="294.2" r="3.5"/>
<circle class="dd-dot" cx="129.7" cy="272.4" r="3.5"/>
<circle class="dd-dot" cx="163.4" cy="250.5" r="3.5"/>
<circle class="dd-dot" cx="197.1" cy="228.7" r="3.5"/>
<circle class="dd-dot" cx="230.7" cy="206.9" r="3.5"/>
<circle class="dd-dot" cx="264.4" cy="185.1" r="3.5"/>
<circle class="dd-dot" cx="298.1" cy="163.3" r="3.5"/>
<circle class="dd-dot" cx="331.8" cy="141.5" r="3.5"/>
<circle class="dd-dot" cx="365.5" cy="119.6" r="3.5"/>
<circle class="dd-dot" cx="399.2" cy="97.8" r="3.5"/>
<circle class="dd-fill-acc" cx="432.8" cy="292.0" r="3.5"/>
<circle class="dd-fill-acc" cx="466.5" cy="269.8" r="3.5"/>
<circle class="dd-fill-acc" cx="500.2" cy="247.4" r="3.5"/>
<circle class="dd-fill-acc" cx="533.9" cy="225.1" r="3.5"/>
<circle class="dd-fill-acc" cx="567.6" cy="202.7" r="3.5"/>
<circle class="dd-fill-acc" cx="601.3" cy="180.3" r="3.5"/>
<circle class="dd-fill-acc" cx="634.9" cy="157.8" r="3.5"/>
<circle class="dd-fill-acc" cx="668.6" cy="135.3" r="3.5"/>
<circle class="dd-fill-acc" cx="702.3" cy="112.8" r="3.5"/>
<circle class="dd-fill-acc" cx="736.0" cy="90.2" r="3.5"/>
<text class="t-acc t-mono" x="508.2" y="267.4">十位：120 → 21.4 → 写 2</text>
<text class="t-say" x="432.8" y="104">h 过了 95（竖式和 ≥ 10），MLP 减掉约 99，</text>
<text class="t-say" x="432.8" y="126">只留下个位；输出头再取离 h/10 最近的整数。</text>
</svg>
</div>
<figcaption>图 5 · MLP 只做一件事：竖式和满十，就把 h 减掉约 99（相当于竖式和减 10），剩下的就是这一位要写的数字</figcaption>
</figure>

$h$ 不到 95 时，MLP 输出几乎是 0；超过 95，它输出大约 −99。十位那一步，120 减掉 98.6，剩 21.4。

### 输出头：离哪个数字最近

最后，输出头拿 $h$ 去和 10 个数字的嵌入做点积。还记得嵌入第一个坐标里那个 $-0.001\,d^2$ 吗？它让每个数字的分数成为 $d$ 的一个开口向下的抛物线，顶点落在 $h/10$ 附近，所以**分数最高的就是离 $h/10$ 最近的那个数字**。21.4 离 20 最近，输出 2。

把三步放在一起：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ab-steps-title">758 + 467：6 参数模型前四步的内部数值</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ab-steps-title">
<colgroup><col style="width:16%"><col><col><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">这一步</th><th scope="col">注意力输出</th><th scope="col">减去上一步写下的</th><th scope="col">MLP 之后</th><th scope="col" class="acc">写下</th></tr>
</thead>
<tbody>
<tr><th scope="row">个位</th><td class="mono">10×(8+7) + 0 = 150</td><td class="mono">150 − 0 = 150</td><td class="mono">150 − 97.8 = 52.2</td><td class="mono acc">5</td></tr>
<tr><th scope="row">十位</th><td class="mono">10×(5+6) + 15 = 125</td><td class="mono">125 − 5 = 120</td><td class="mono">120 − 98.6 = 21.4</td><td class="mono acc">2</td></tr>
<tr><th scope="row">百位</th><td class="mono">10×(7+4) + 11 = 121</td><td class="mono">121 − 2 = 119</td><td class="mono">119 − 98.6 = 20.4</td><td class="mono acc">2</td></tr>
<tr><th scope="row">千位</th><td class="mono">10×(0+0) + 11 = 11</td><td class="mono">11 − 2 = 9</td><td class="mono">9（不到 95，不减）</td><td class="mono acc">1</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">后面七位全是 0。每一格都是 <code>probe6.py</code> 打印出的实际数值（四舍五入到 0.1）。</p>
</div>
<figcaption>表 4 · 竖式里的每一步都能在残差流里找到：同位相加、读出进位、满十减十、取整</figcaption>
</figure>

### 破坏实验：每个零件都不能少

为了确认上面的解读不是看图说话，我们动了几处设定，各在 500 道验证题上重新跑[^notes]：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ab-break-title">改动 6 参数模型的一处设定，500 道验证题的准确率</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ab-break-title">
<colgroup><col style="width:44%"><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">改动</th><th scope="col">破坏了什么</th><th scope="col" class="num">准确率</th></tr>
</thead>
<tbody>
<tr><th scope="row">原样</th><td>—</td><td class="num mono good">100%</td></tr>
<tr><th scope="row">RoPE 周期 19 → 18</th><td>a、b 不在同一格</td><td class="num mono bad">0%</td></tr>
<tr><th scope="row">RoPE 周期 19 → 20</th><td>a、b 不在同一格</td><td class="num mono bad">0%</td></tr>
<tr><th scope="row">MLP 输出系数设为 0</th><td>不再满十减十</td><td class="num mono bad">0.4%</td></tr>
<tr class="dt-group"><th colspan="3" scope="rowgroup">换数值精度（2000 道题）</th></tr>
<tr><th scope="row">float32</th><td>—</td><td class="num mono good">100%</td></tr>
<tr><th scope="row">float16</th><td>128000 超出上限 65504</td><td class="num mono bad">0%</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">改周期时，查询方向和注意力缩放按同样的公式重新算过，只是周期不同。</p>
</div>
<figcaption>表 5 · 任何一个零件失灵，整台“加法电路”就停摆</figcaption>
</figure>

周期改成 18 或 20，a 和 b 就不在同一个刻度上了，一道也做不对；把 MLP 的输出系数设成 0，只剩“恰好一路都不用进位”的那 2 道题对。还有一个顺带的发现：把全部计算换成 float32 照样全对，换成 float16 则全错，因为门的系数 128000 超出了 float16 能表示的最大值 65504。6 个参数里，每一个都是精心挑选的大数或小数。

到这里，6 参数模型是怎么算加法的已经拆完了。可是计算机本来就会做加法，CPU 里的加法器是怎么算的、怎么进位的？我们平时列竖式又是怎么算的？**把这几种做法摆在一起看，Transformer 的算法有什么不一样？**

## 六、与普通加法的对比

### 十进制竖式：查表，加上进位

先看最熟悉的。列竖式时，我们从个位开始，每一位做两件事：从背熟的加法表里查出两个数字的和，再加上右边进过来的 1；结果满十，就只写个位，把 1 记到左边。758 + 467：8 + 7 = 15，写 5 进 1；5 + 6 + 1 = 12，写 2 进 1；7 + 4 + 1 = 12，写 2 进 1；最后写 1。一共进位 3 次。

这里的进位只有 0 和 1 两种可能，方向固定从右往左，图 2 里画的就是这个过程。

### 二进制：全加器串成一条链

CPU 里的数是二进制存的，每一位只有 0 和 1。十位数最大是 9,999,999,999，要 34 个二进制位才放得下。758 写成二进制是 `1011110110`，467 是 `111010011`。

二进制的一位相加只有 8 种情况（a、b、进位各取 0 或 1），所以根本不用查表，用几个逻辑门就能算出来。这个小电路叫**全加器**：这一位的和是 a、b、进位三者的异或，进位出是“a 和 b 都是 1，或者进位是 1 且 a、b 恰好有一个是 1”[^adder]：

$$
s_i = a_i \oplus b_i \oplus c_i, \qquad c_{i+1} = (a_i \wedge b_i) \vee \big(c_i \wedge (a_i \oplus b_i)\big)
$$

一个全加器是 5 个门。把 34 个全加器排成一排，每个的进位出接到左边那个的进位入，就是最简单的加法器，叫**行波进位加法器**（ripple-carry adder）：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ab-bin-title ab-bin-desc">
<title id="ab-bin-title">758 加 467 的二进制行波进位</title>
<desc id="ab-bin-desc">从左到右是第 10 位到第 0 位。a 是 758 的二进制 1011110110，b 是 467 的二进制 0111010011，和是 1225 的二进制 10011001001。每一位一个全加器，下方的箭头把进位从右边的位传到左边的位；第 1、2、4 到 9 位产生进位，用强调色表示。</desc>
<defs>
<marker id="ab-bin-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="ab-bin-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="360" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="328" rx="8"/>
<text class="t-zone" x="32" y="44">758 + 467 在 CPU 里：11 个全加器串成一条链</text>
<text class="t-note" x="32" y="88">位</text>
<text class="t-note" x="32" y="125">a</text>
<text class="t-note" x="32" y="151">b</text>
<text class="t-note" x="32" y="193">和</text>
<text class="t-note" x="32" y="246">进位</text>
<path class="dd-edge dd-dash" d="M734.0 212 V236 Q734.0 244 726.0 244 H680.0 Q672.0 244 672.0 236 V216" marker-end="url(#ab-bin-arrow)"/>
<text class="t-note" x="703.0" y="262" text-anchor="middle">0</text>
<path class="dd-edge-acc" d="M672.0 212 V236 Q672.0 244 664.0 244 H618.0 Q610.0 244 610.0 236 V216" marker-end="url(#ab-bin-arrow-acc)"/>
<text class="t-acc t-mono" x="641.0" y="262" text-anchor="middle">1</text>
<path class="dd-edge-acc" d="M610.0 212 V236 Q610.0 244 602.0 244 H556.0 Q548.0 244 548.0 236 V216" marker-end="url(#ab-bin-arrow-acc)"/>
<text class="t-acc t-mono" x="579.0" y="262" text-anchor="middle">1</text>
<path class="dd-edge dd-dash" d="M548.0 212 V236 Q548.0 244 540.0 244 H494.0 Q486.0 244 486.0 236 V216" marker-end="url(#ab-bin-arrow)"/>
<text class="t-note" x="517.0" y="262" text-anchor="middle">0</text>
<path class="dd-edge-acc" d="M486.0 212 V236 Q486.0 244 478.0 244 H432.0 Q424.0 244 424.0 236 V216" marker-end="url(#ab-bin-arrow-acc)"/>
<text class="t-acc t-mono" x="455.0" y="262" text-anchor="middle">1</text>
<path class="dd-edge-acc" d="M424.0 212 V236 Q424.0 244 416.0 244 H370.0 Q362.0 244 362.0 236 V216" marker-end="url(#ab-bin-arrow-acc)"/>
<text class="t-acc t-mono" x="393.0" y="262" text-anchor="middle">1</text>
<path class="dd-edge-acc" d="M362.0 212 V236 Q362.0 244 354.0 244 H308.0 Q300.0 244 300.0 236 V216" marker-end="url(#ab-bin-arrow-acc)"/>
<text class="t-acc t-mono" x="331.0" y="262" text-anchor="middle">1</text>
<path class="dd-edge-acc" d="M300.0 212 V236 Q300.0 244 292.0 244 H246.0 Q238.0 244 238.0 236 V216" marker-end="url(#ab-bin-arrow-acc)"/>
<text class="t-acc t-mono" x="269.0" y="262" text-anchor="middle">1</text>
<path class="dd-edge-acc" d="M238.0 212 V236 Q238.0 244 230.0 244 H184.0 Q176.0 244 176.0 236 V216" marker-end="url(#ab-bin-arrow-acc)"/>
<text class="t-acc t-mono" x="207.0" y="262" text-anchor="middle">1</text>
<path class="dd-edge-acc" d="M176.0 212 V236 Q176.0 244 168.0 244 H122.0 Q114.0 244 114.0 236 V216" marker-end="url(#ab-bin-arrow-acc)"/>
<text class="t-acc t-mono" x="145.0" y="262" text-anchor="middle">1</text>
<text class="t-note" x="734.0" y="88" text-anchor="middle">0</text>
<rect class="dd-card" x="712" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="734.0" y="125" text-anchor="middle">0</text>
<text class="t-mono" x="734.0" y="151" text-anchor="middle">1</text>
<line class="dd-hair" x1="712" y1="168" x2="756" y2="168"/>
<text class="t-mono" x="734.0" y="193" text-anchor="middle">1</text>
<text class="t-note" x="672.0" y="88" text-anchor="middle">1</text>
<rect class="dd-card" x="650" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="672.0" y="125" text-anchor="middle">1</text>
<text class="t-mono" x="672.0" y="151" text-anchor="middle">1</text>
<line class="dd-hair" x1="650" y1="168" x2="694" y2="168"/>
<text class="t-mono" x="672.0" y="193" text-anchor="middle">0</text>
<text class="t-note" x="610.0" y="88" text-anchor="middle">2</text>
<rect class="dd-card" x="588" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="610.0" y="125" text-anchor="middle">1</text>
<text class="t-mono" x="610.0" y="151" text-anchor="middle">0</text>
<line class="dd-hair" x1="588" y1="168" x2="632" y2="168"/>
<text class="t-mono" x="610.0" y="193" text-anchor="middle">0</text>
<text class="t-note" x="548.0" y="88" text-anchor="middle">3</text>
<rect class="dd-card" x="526" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="548.0" y="125" text-anchor="middle">0</text>
<text class="t-mono" x="548.0" y="151" text-anchor="middle">0</text>
<line class="dd-hair" x1="526" y1="168" x2="570" y2="168"/>
<text class="t-mono" x="548.0" y="193" text-anchor="middle">1</text>
<text class="t-note" x="486.0" y="88" text-anchor="middle">4</text>
<rect class="dd-card" x="464" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="486.0" y="125" text-anchor="middle">1</text>
<text class="t-mono" x="486.0" y="151" text-anchor="middle">1</text>
<line class="dd-hair" x1="464" y1="168" x2="508" y2="168"/>
<text class="t-mono" x="486.0" y="193" text-anchor="middle">0</text>
<text class="t-note" x="424.0" y="88" text-anchor="middle">5</text>
<rect class="dd-card" x="402" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="424.0" y="125" text-anchor="middle">1</text>
<text class="t-mono" x="424.0" y="151" text-anchor="middle">0</text>
<line class="dd-hair" x1="402" y1="168" x2="446" y2="168"/>
<text class="t-mono" x="424.0" y="193" text-anchor="middle">0</text>
<text class="t-note" x="362.0" y="88" text-anchor="middle">6</text>
<rect class="dd-card" x="340" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="362.0" y="125" text-anchor="middle">1</text>
<text class="t-mono" x="362.0" y="151" text-anchor="middle">1</text>
<line class="dd-hair" x1="340" y1="168" x2="384" y2="168"/>
<text class="t-mono" x="362.0" y="193" text-anchor="middle">1</text>
<text class="t-note" x="300.0" y="88" text-anchor="middle">7</text>
<rect class="dd-card" x="278" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="300.0" y="125" text-anchor="middle">1</text>
<text class="t-mono" x="300.0" y="151" text-anchor="middle">1</text>
<line class="dd-hair" x1="278" y1="168" x2="322" y2="168"/>
<text class="t-mono" x="300.0" y="193" text-anchor="middle">1</text>
<text class="t-note" x="238.0" y="88" text-anchor="middle">8</text>
<rect class="dd-card" x="216" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="238.0" y="125" text-anchor="middle">0</text>
<text class="t-mono" x="238.0" y="151" text-anchor="middle">1</text>
<line class="dd-hair" x1="216" y1="168" x2="260" y2="168"/>
<text class="t-mono" x="238.0" y="193" text-anchor="middle">0</text>
<text class="t-note" x="176.0" y="88" text-anchor="middle">9</text>
<rect class="dd-card" x="154" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="176.0" y="125" text-anchor="middle">1</text>
<text class="t-mono" x="176.0" y="151" text-anchor="middle">0</text>
<line class="dd-hair" x1="154" y1="168" x2="198" y2="168"/>
<text class="t-mono" x="176.0" y="193" text-anchor="middle">0</text>
<text class="t-note" x="114.0" y="88" text-anchor="middle">10</text>
<rect class="dd-card" x="92" y="100" width="44" height="112" rx="5"/>
<text class="t-mono" x="114.0" y="125" text-anchor="middle">0</text>
<text class="t-mono" x="114.0" y="151" text-anchor="middle">0</text>
<line class="dd-hair" x1="92" y1="168" x2="136" y2="168"/>
<text class="t-mono" x="114.0" y="193" text-anchor="middle">1</text>
<text class="t-say" x="32" y="298">每个全加器只接三根线：a、b 和右边传来的进位。和 = a⊕b⊕进位，进位出 = (a∧b)∨(进位∧(a⊕b))，共 5 个门。</text>
<text class="t-say" x="32" y="322">同一道题，十进制只进位 3 次；二进制进位 8 次，第 4 到第 9 位连成一串（虚线表示进位为 0）。</text>
</svg>
</div>
<figcaption>图 6 · CPU 里的加法：每一位一个固定的小电路，进位顺着导线从低位流到高位，不用“找”也不用“写下来再读”</figcaption>
</figure>

和十进制比，二进制的进位要频繁得多：同一道题，十进制进位 3 次，二进制进位 8 次，而且第 4 到第 9 位的进位连成一串。我们在 `verify.py` 的 10000 道题上数了一下，十进制里最长的连续进位平均 3.0 位，二进制里平均 7.5 位，最长一次有 31 位连着进位[^notes]。

**进位链太长就慢**。行波进位加法器里，最高位要等进位一个一个传过来，34 位就要传 34 级。所以真正的 CPU 用的是**超前进位**（carry-lookahead）：每一位先各自算出两个信号，“这一位自己会不会产生进位”（$a_i \wedge b_i$）和“右边来的进位会不会穿过这一位”（$a_i \oplus b_i$），再用一棵树形电路把所有位的进位同时算出来。以 64 位加法为例，进位要经过的级数从 64 级降到 log₂64 = 6 级左右[^adder]。这就是为什么 CPU 做一次 64 位加法通常只要一个时钟周期。

### 十进制硬件：BCD 的“加 6”

有些场合（老式计算器、金融系统里的十进制运算）要求按十进制算，常见的做法叫 **BCD**（二进制编码的十进制）：每个十进制位单独用 4 个二进制位存[^bcd]。4 个二进制位能数到 15，可十进制只用到 9，于是相加时多了一步修正：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ab-bcd-title">758 + 467 用 BCD（每个十进制位占 4 个二进制位）相加</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ab-bcd-title">
<colgroup><col style="width:12%"><col><col><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">位</th><th scope="col">4 位二进制相加（含进位）</th><th scope="col">大于 9？</th><th scope="col" class="acc">加 6 修正</th><th scope="col">写下 / 进位</th></tr>
</thead>
<tbody>
<tr><th scope="row">个位</th><td class="mono">1000 + 0111 = 1111（15）</td><td class="bad">是</td><td class="mono acc">1111 + 0110 = 1 0101</td><td class="mono">5 / 1</td></tr>
<tr><th scope="row">十位</th><td class="mono">0101 + 0110 + 1 = 1100（12）</td><td class="bad">是</td><td class="mono acc">1100 + 0110 = 1 0010</td><td class="mono">2 / 1</td></tr>
<tr><th scope="row">百位</th><td class="mono">0111 + 0100 + 1 = 1100（12）</td><td class="bad">是</td><td class="mono acc">1100 + 0110 = 1 0010</td><td class="mono">2 / 1</td></tr>
<tr><th scope="row">千位</th><td class="mono">0000 + 0000 + 1 = 0001（1）</td><td class="good">否</td><td class="mono">不修正</td><td class="mono">1 / 0</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">4 位二进制能数到 15，十进制只用到 9；加 6 正好跳过 10–15 这 6 个用不上的编码，在 4 位里等于减 10。</p>
</div>
<figcaption>表 6 · BCD 的“加 6”和 6 参数模型 MLP 的“减 99”是同一件事：满十，就从这一位拿走十</figcaption>
</figure>

8 + 7 用 4 位二进制相加得 1111，也就是 15，不是一个合法的十进制位；规则是**大于 9 就再加 6**。加 6 等于跳过 10 到 15 这 6 个用不上的编码，在 4 位里就相当于减 10，溢出来的那一位正好就是进位。

这个“满十就减十”，和 6 参数模型里 MLP 做的事一模一样（图 5）：h 是竖式和的 10 倍，过了 95 就减掉约 99。也就是说，**Transformer 做的其实是十进制加法，它的 MLP 扮演的就是 BCD 里那个“加 6”修正电路**。

### 三种算法放在一起

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ab-cmp-title">同一道加法，三种算法</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ab-cmp-title">
<colgroup><col style="width:17%"><col><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col"></th><th scope="col">十进制竖式（人）</th><th scope="col">二进制加法器（CPU）</th><th scope="col" class="acc">6 参数 Transformer</th></tr>
</thead>
<tbody>
<tr><th scope="row">数怎么表示</th><td>0–9 十个符号</td><td>0/1 两种电平，十位数要 34 位</td><td>一个 token 一个数字，嵌入成 <span class="mono">[1000−0.001d², −d]</span></td></tr>
<tr><th scope="row">同一位怎么对齐</th><td>竖式上下对齐</td><td>导线固定：第 i 位接第 i 位</td><td>注意力去“找”：周期 19 的钟面让两位重合</td></tr>
<tr><th scope="row">一位怎么相加</th><td>背加法表</td><td>异或门：a⊕b⊕进位</td><td>注意力加权求和：10(aᵢ+bᵢ)</td></tr>
<tr><th scope="row">怎么知道要进位</th><td>和 ≥ 10</td><td>与门、或门：(a∧b)∨(进位∧(a⊕b))</td><td>上一位两数之和，减去上一步写下的数字，差 10 就是进位</td></tr>
<tr><th scope="row">满了怎么办</th><td>只写个位</td><td>二进制每位本来只有 0/1，进位直接出去</td><td>MLP 台阶：h ≥ 95 时减约 99</td></tr>
<tr><th scope="row">进位怎么传</th><td>记在脑子里或写个小 1</td><td>沿导线传给左边的全加器；快的 CPU 用超前进位并行算出</td><td>写进序列，下一步从刚写下的数字里重新读出来</td></tr>
<tr><th scope="row">代价（我们的测量）</th><td>—</td><td>34 位行波加法器 170 个门；Python 里一次 <span class="mono">a+b</span> 约 29 纳秒</td><td>numpy 实现每道题约 2.4 毫秒，慢约 8 万倍</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">门数按每个全加器 5 个门估算；Python 的 29 纳秒大部分是解释器开销，CPU 的加法指令本身通常一个时钟周期。</p>
</div>
<figcaption>表 7 · 三种算法做的是同一件事；不同的是“对齐”和“进位”分别靠什么完成</figcaption>
</figure>

把这张表横着读，三种算法的“一位怎么相加”“满了怎么办”都很像，真正的差别在两行。

**对齐**。竖式靠纸上的位置，CPU 靠导线，第 i 位和第 i 位从一开始就接在一起。Transformer 没有导线，序列里所有位置都只是排成一行的 token，它得用注意力在每一步重新“找”到同一位的两个数字，6 参数模型为此专门设计了 9 个 0 和周期 19 的钟面。

**进位**。CPU 的进位是一根导线上的电平，从低位流到高位，从来不离开电路。Transformer 里没有任何地方能存一个进位：每一步算完，模型只留下一个写出来的数字。下一步要知道进位，只能把上一位的两个数字再看一遍，和自己刚写下的数字比一比，差了 10 就说明进过位。**进位不是传过去的，而是每一步重新推出来的**。这也可以解释第三节里高位在前的写法为什么学不会：那样写时，进位还没“写下来”，模型就没有地方去读它（这是我们的理解）。

**代价**。一个 34 位的行波加法器只有 170 个门；而 6 参数模型用 numpy 跑，每道题要 2.4 毫秒，比 Python 里一次普通的 `a + b`（约 29 纳秒，大部分还是解释器开销）慢约 8 万倍[^notes]。这本来就不是用来比速度的：AdderBoard 问的是“Transformer 这种结构能不能表示加法、最少要几个参数”，不是“用 Transformer 做加法划不划算”。

看完和普通加法的对比，再回头数 6 参数模型里的数：钟面的周期 19、查询指向的 10.3、归一化的系数……这些难道不也是模型里的数吗？**为什么只算 6 个？**

## 七、参数该怎么数

### 6 还是 10

这正是 6 参数提交引起的争论[^issue75]。它的父版本有 10 个参数，6 参数版本做了两处改动：

- **查询投影的 2 个数固定下来**。作者认为这两个数决定的是“指向哪个刻度”，和 RoPE 的周期一样属于位置编码的一部分，按规则固定位置编码不计参数。
- **最后一层归一化的 2 个系数并进输出头**。归一化后再乘系数、再和嵌入表点积，等价于先把系数乘进嵌入表；而嵌入表本来就是由 2 个参数算出来的，系数并进去以后不再单独占参数。

维护者认为第二处在数学上站得住，第一处“更有争议”，因为父版本明明把这两个数当参数算了。最后的处理是收录，但在 6 后面打一个星号，注明“按严格算法是 10 个”。提交者也说，如果争议太大可以撤回，这两个技巧只是他的智能体在研究别的问题时顺手发现的。

这场争论说明，在这个尺度上，“参数个数”已经不只是模型的属性，也取决于记账规则：哪些数算结构常数，哪些数算参数。从 10 到 6，模型算的东西一点没变。

### 能表示，不等于学得会

另一个容易混淆的地方是两张榜之间的差距。手写的 6（或 10）和训练的 36，并不意味着训练方法落后了五六倍。手写模型里的数是人推出来的：1000、0.001、128000，跨了八个数量级，台阶陡到几乎是一个开关。梯度下降很难从随机起点走到这样一个尖锐的解上，训练榜上的模型都要用平滑得多的方式实现同样的功能。

所以两张榜回答的是两个问题：手写榜问“这个结构**能不能表示**加法”，训练榜问“通用的训练方法**能不能找到**它”。手写榜说明，一层、宽度 2 的 Transformer 就足以表示十位数加法；训练榜说明，要让梯度下降自己找到解，目前至少要 36 个参数，外加一整套多阶段、定向的训练流程。

## 八、回到开头

回到开头那段输出：6 个参数，10010 道题全对。现在可以说清楚它是怎么回事了。

它不是一个“学会了加法”的模型，而是一个**用 Transformer 零件搭出来的加法电路**：RoPE 的钟面负责把同一位的两个数字对齐，注意力把它们加起来，顺便捎上上一位；残差流里减掉上一步写下的数字，进位自然冒出来；MLP 的台阶负责满十减十；抛物线形的输出头负责取整。每个零件都是标准 Transformer 里本来就有的，只是参数被设成了特定的值。

这个排行榜还有几个值得带走的观察。

**格式比容量重要**。从第三节的实验可以看到，同一个模型，只是把数字倒过来写，就从学不会变成很快学会。

**智能体参与了大部分提交**。榜上 52 条记录里，有 27 条在“Built with”一栏填了 Claude Code、Codex、Gemini 之类的工具，排行榜本身也起源于让两个智能体比赛。它们会不会钻规则的空子（成对 token、参数记账）也是这个榜的一部分故事：规则写不到的地方，“最优”会往意想不到的方向走。

**这里的“小”有边界**。这些模型只会十位数加法，宽度、周期、补零的个数都是为这道题量身定做的；换成十一位数，6 参数模型多半要重新设计补零个数和钟面周期（这是我们的推断，没有实测）。它们回答的是“表示加法最少需要什么”，不是“怎么做一个更好的语言模型”。

## 小结

1. **Transformer 是什么？** 一个预测下一个 token 的模型：词嵌入把符号变成向量，注意力往回看并取信息，MLP 逐位置计算，输出头给每个候选打分，残差流把各部件的结果累加起来；位置编码（如 RoPE）告诉它谁在前谁在后。答案一位一位地自回归生成。
2. **AdderBoard 比什么？** 比谁的 Transformer 用最少的参数把十位数加法做到 99% 以上，分训练和手写两张榜，规则要求加法必须由模型算出来，不能由解码代码代劳。
3. **加法难在哪？** 难在进位。高位在前时，第一位就要算完整条进位链；低位在前时，进位可以从上一步写下的数字里读出来。所有上榜模型都用低位在前的写法。
4. **训练模型怎么变小？** 先变窄，再靠低秩分解、权重绑定、用公式代替整张表让参数共用；代价是训练越来越难，要靠顿悟、多阶段微调和定向微调，目前最少 36 个参数。
5. **6 个参数怎么做加法？** 周期 19 的 RoPE 让同一位的两个数字在钟面上重合；注意力算出 $10(a_i+b_i)$ 加上一位的和；减去上一步写下的数字得到进位；MLP 满十减十；抛物线输出头取整。
6. **和普通加法有什么不同？** 一位怎么加、满十怎么办都差不多，MLP 的“减十”就是 BCD 的“加 6”；差别在对齐和进位：CPU 靠导线对齐、靠导线传进位，Transformer 要用注意力去找同一位，进位则从自己上一步写下的数字里重新推出来。
7. **6 个还是 10 个？** 取决于把哪些数当作结构常数。手写榜证明“能表示”，训练榜证明“学得会”，两者回答的是不同的问题。

如果想自己看一遍，最省事的是下载 6 参数那份代码[^zc6]，它只依赖 numpy，配合排行榜的 `verify.py` 半分钟就能跑完；在 `_forward` 里打印注意力权重，就能看到上面那张钟面。

## 参考资料
