<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Justin Huang (简体中文)</title><description>这是 Justin Huang 的个人博客，写 AI、技术、写作，也写生活。</description><link>https://justinhuangai.github.io/</link><item><title>技术报告共读：《Attention Residuals》（注意力残差）</title><link>https://justinhuangai.github.io/zh-hans/posts/attention-residuals/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/attention-residuals/</guid><description>Kimi 团队 Attention Residuals 技术报告：为什么残差连接也该“注意力化”，以及 Full AttnRes / Block AttnRes 如何把这个想法做成可训练、可部署的系统</description><pubDate>Thu, 19 Mar 2026 08:49:27 GMT</pubDate><content:encoded>2026 年 3 月 16 日，Kimi Team 在 arXiv 上传了一篇技术报告：[《Attention Residuals》](/papers/2603.15031v1.pdf)（注意力残差）。

从整份报告的结构就能看出作者真正的重心：不是只发一个新模块，而是按“动机 -&gt; AttnRes -&gt; Block AttnRes -&gt; 基础设施 -&gt; 实验 -&gt; 讨论”的顺序，把“残差连接到底在做什么”这件事重新讲了一遍。

## 0. 先认几个词

如果你完全没有机器学习背景，可以顺着这篇报告真正关心的问题，按下面这个顺序先建立一个直觉：

- `Transformer`：今天大多数大模型的基础架构。你可以先把它理解成一台一层一层处理信息的机器。
- `隐状态（hidden state）`：模型在某一层里的内部中间表示。可以粗略理解成“模型此刻脑子里的临时笔记”。
- `残差连接（residual connection）`：层和层之间的一条“保留旧内容”的通道。它会先把上一层的内容留住，再把这一层新算出来的东西加上去。
- `残差项（residual）`：更接近“这一层新补上去的增量”，也就是上面那条残差连接里新增的那一部分。
- `注意力（attention）`：从很多信息里，挑出“当前最该看哪一部分”的机制。这个词你可以先记成“有选择地看重点”。
- `PreNorm`：在进入一层之前，先把数值尺度调匀，再做后续计算。可以把它想成“先把音量调到合适，再继续混音”。

## 1. 一句话说清楚

这份技术报告问了一个非常锋利的问题：

**既然 Transformer 已经用注意力机制取代了“时间维度上的递归”，为什么大模型在“深度维度上的信息聚合”还停留在固定加法？**

现代大语言模型（LLM）几乎都在用一种很常见的层结构：先做 PreNorm，再走残差连接。直白地说，就是先把数值尺度调匀，再把这一层新算出来的结果加回原输入。大家熟悉它的一个功能，是让训练过程更稳定，深层网络不那么容易失控。但作者提醒我们，残差连接其实还有另一个同样重要、却长期被忽视的角色：

**它定义了信息怎样沿着深度被汇总。**

如果下面的式子看不熟，不用卡住，直接看后面的“翻译成人话”就够了。

标准残差的规则很简单：

$$
h_l = h_{l-1} + f_{l-1}(h_{l-1})
$$

这里可以直接把两部分拆开看：

- $h_{l-1}$：旧内容，也就是上一层已经有的表示
- $f_{l-1}(h_{l-1})$：这一层新算出来的增量，更接近“残差”这个词本身

而把这两部分重新加在一起的整条做法，才更准确地叫“残差连接”。

把这个递推式展开，你会得到：

$$
h_l = h_1 + \sum_{i=1}^{l-1} f_i(h_i)
$$

翻译成人话就是：第 $l$ 层看到的输入，本质上是“嵌入表示（embedding）加上前面所有层输出的统一加总”。每一层的权重都是 1，没有选择，没有抑制，没有“这一步我更该看第 3 层还是第 17 层”的机制。

AttnRes 的核心思想只有一句话：

**把残差连接从固定加法，改成沿深度做一次 softmax 注意力。**

## 2. 旧残差到底哪里有问题

这份技术报告最重要的地方，不在于它提出了一个新公式，而在于它把一个大家已经习惯了的东西重新问题化了。

标准残差长期被视为“训练稳定性工具”。只要能让梯度过得去，它就算完成任务了。但从信息流角度看，这条路径其实非常粗糙。

想象你在写一份持续迭代的文档。每一轮修改，你都不是“挑出最相关的旧版本内容再整合”，而是把之前所有版本一股脑全文追加到文档末尾。第 20 轮的时候，前 3 轮的重要洞察当然还在，但它们已经淹没在越来越厚的堆叠里了。

PreNorm 的问题就在这儿。报告引用了 SiameseNorm 的观察，并进一步强调：在 PreNorm 下，`hidden state` 的量级会随着深度近似按 $O(L)$ 增长。这里的隐状态，说白了就是模型每一层里的那份“内部笔记”。结果就是：

- 越往后的层，看到的是一个越来越膨胀的“历史总和”
- 早期层的信息虽然没有消失，但会被不断稀释
- 后面层如果还想“发出声音”，就被迫输出更大的量级

这篇技术报告把这个现象叫 `PreNorm dilution`。这是一个非常准确的命名。不是梯度断了，不是模型炸了，而是每一层的相对贡献被越来越稀。

报告里有一句我很喜欢的潜台词：我们在序列维度上早就不满足于“所有过去词元（token）一视同仁”了，所以才有了注意力机制；那为什么到了深度维度，却还能接受“所有过去层统一权重相加”？

## 3. AttnRes 到底做了什么

AttnRes 的形式很干净。第 $l$ 层不再机械地接收“前面所有层输出的总和”，而是对这些历史表示做一次加权选择：

$$
h_l = \sum_{i=0}^{l-1} \alpha_{i \to l} \cdot v_i
$$

其中权重 $\alpha_{i \to l}$ 来自一层 softmax。你可以先把 softmax 理解成“把一组分数压成一组权重，而且所有权重加起来等于 1”，这样模型才能明确表达“更该看谁、少看谁”：

$$
\alpha_{i \to l} = \operatorname{softmax}\left(w_l^T \operatorname{RMSNorm}(k_i)\right)
$$

如果你没接触过注意力机制，还有一个最省力的理解方式：

- `查询（query）`：当前这一层现在想找什么
- `键（key）`：每一层历史信息各自贴着什么“索引标签”
- `值（value）`：最后真正被取回来、参与汇总的内容

这里最值得注意的设计有三个。

第一，**查询不是当前隐状态现算出来的，而是每层一个可学习的伪查询向量 $w_l$。**  
这有点反直觉。我们平时看到注意力机制，会自然以为查询必须来自当前输入。但作者故意把查询设计成层级参数，而不是按词元动态生成的向量。这样做的好处是：同一个块里的多个查询可以提前批量算，后面基础设施优化才有空间做。

第二，**键和值直接来自前面层的输出。**  
也就是说，真正带来“输入相关性”的不是查询，而是各层当前样本上的表示本身。不同样本经过前面层后得到的键不一样，所以最后的深度注意力依然是输入相关的。

第三，**键前面加了 RMSNorm。**  
这是个很关键的小设计。因为如果不做归一化，量级大的层会天然在点积里占便宜，你得到的就不是“谁更相关”，而更像“谁声音更大”。报告正文也明确强调了这一点。

```python
import torch
from torch import nn


def attention_residual(
    sources: list[torch.Tensor],
    pseudo_query: torch.Tensor,
    norm: nn.RMSNorm,
) -&gt; torch.Tensor:
    keys = torch.stack([norm(source) for source in sources], dim=0)
    values = torch.stack(sources, dim=0)

    logits = keys @ pseudo_query
    weights = torch.softmax(logits, dim=0)
    return (weights.unsqueeze(-1) * values).sum(dim=0)
```

这个式子看上去像是“把注意力机制用在残差连接上”。但我觉得更准确的说法是：

**它把残差连接从“固定的累加器”改成了“可选择的深度检索器”。**

## 4. 这份报告最妙的地方：它不是只给想法，也给工程

**一句话结论：这篇报告真正站得住，不是因为它提出了 Full AttnRes，而是因为它把这个想法推进成了一套可训练、可部署、算得清账的工程方案。**

如果报告只写到 Full AttnRes，这还只能算一个漂亮的研究想法。

Full AttnRes 让每一层都看到前面所有层，理论上很好理解，实际上也不算太贵。因为网络深度 $L$ 通常远小于序列长度 $T$，所以作者说，单纯算术量 $O(L^2 d)$ 并不是最可怕的问题。

真正的问题出现在大训练里：

- 激活重计算（activation recomputation）会把本来可以丢掉的中间层输出重新变成必须保存的对象
- 流水线并行（pipeline parallelism）会让这些跨层表示需要跨阶段传输
- 一旦每层都要看所有前层，通信和缓存压力会快速上去

所以他们又提出了 **Block AttnRes**。

做法是把 $L$ 层切成 $N$ 个块。块内部先用普通求和攒成一个块级表示，跨块再做注意力。这样一来：

- Full AttnRes：看的是所有历史层
- Block AttnRes：看的是所有历史块的摘要，再加当前块的部分和

本质上是用“摘要级跨层注意力”换取可扩展性。

这还没完。作者不是只说“我们分块了，所以省内存”，而是真把系统层的账也算清楚了：

- 训练阶段用 **跨阶段缓存（cross-stage caching）**，避免流水线里重复传历史块
- 推理阶段用 **两阶段计算（two-phase computation）**
- 第一阶段并行算块间注意力（inter-block attention）
- 第二阶段顺序算块内回看（intra-block lookback），再用在线 softmax 合并

从附录和 `table/memory_access.tex` 里能看到最硬核的一组数字。按报告给的典型设定：

- 标准残差连接：每层残差机制 I/O 是 `3d`
- naive Full AttnRes：`130d`
- 优化后的 Full AttnRes：`24d`
- Block AttnRes：`5.5d`
- mHC：`34d`

这组数字特别说明问题。Block AttnRes 不是“便宜到跟标准残差连接一样”，但它已经从“明显不现实”降到了“工程上值得试”。而且报告实测给出的代价也不大：

- 训练端实际耗时开销小于 4%
- 推理端时延开销小于 2%

这就是我说它像一篇真正的系统级技术报告的原因。很多论文的问题在于“想法是新的，账是糊的”；这篇在账本上反而做得很用力。

## 5. 实验最该看什么

**一句话结论：实验最有说服力的地方，不是单项分数多了几点，而是 AttnRes 在缩放趋势、训练动力学和下游能力上都给出了方向一致的信号。**

### 5.1 缩放定律：不是偶然赢一把

作者先做了五个模型规模的缩放定律实验，对比 Baseline、Full AttnRes 和 Block AttnRes。

拟合出来的曲线是：

- Baseline：$1.891 \times C^{-0.057}$
- Block AttnRes：$1.870 \times C^{-0.058}$
- Full AttnRes：$1.865 \times C^{-0.057}$

这三条曲线最重要的信息不是“斜率差了多少”，而是：

**AttnRes 在整个算力区间里都持续更低。**

报告给了一个很容易传播的结论：在 `5.6 PFLOP/s-days` 这个预算点，Block AttnRes 的损失相当于 baseline 多花 `1.25x` 算力才能达到的水平。

换句话说，这不是“在某个模型大小上碰巧调对了”，而是有比较稳定的规模收益。

### 5.2 大模型主实验：不是玩具规模

主实验不是小模型上的玩具规模基准实验，而是基于 Kimi Linear 的一个大配置：

- `48B 总参数 / 3B 激活参数`
- 27 个 Transformer 块，也就是 54 层
- 8-of-256 路由专家 + 1 个共享专家
- 预训练 `1.4T tokens`

这说明作者不是只在“小模型上做漂亮曲线”，而是真把这个残差改造塞进了一个大训练配方里。

### 5.3 最能说明问题的图：输出量级不再失控

正文里最打动我的其实不是基准评测表，而是训练动态那张图。

Baseline 的输出量级会随着深度一路涨上去。训练动态图里给的数值非常夸张：从前面几个块的 `0.04`、`0.06`、`0.10`，一直涨到后面几个块的 `10.47`、`12.15`。这就是 `PreNorm dilution` 的视觉化版本。

Block AttnRes 则完全不是这条曲线。它在块边界形成一种周期性重置，量级大致在 `0.21` 到 `1.91` 之间波动，没有出现一路失控上扬。

这非常重要，因为它说明 AttnRes 不是只在最后 benchmark 上“多拿了几分”，而是真正在训练动力学层面改变了表示如何沿深度堆积。

### 5.4 下游任务：提升最明显的是推理和代码

预训练后，AttnRes 在报告列出的全部评测上都不差于 baseline，几个最亮眼的点包括：

- MMLU：`73.5 -&gt; 74.6`
- GPQA-Diamond：`36.9 -&gt; 44.4`
- Math：`53.5 -&gt; 57.1`
- HumanEval：`59.1 -&gt; 62.2`
- C-Eval：`79.6 -&gt; 82.5`

这里最值得注意的是 GPQA、Math、HumanEval 这种多步推理或程序生成任务涨幅更大。报告作者的解释是：如果后层能更有选择地回收前层表示，那么需要组合式推理的任务会更受益。我觉得这个解释是说得通的。

因为复杂推理最怕的不是“信息不存在”，而是“信息在网络很深的地方被埋住了”。

## 6. 消融实验告诉了我们什么

**一句话结论：消融实验最关键的结论，不是“连得更密就更强”，而是“沿深度做输入相关的选择性聚合”这件事本身在起作用。**

这份报告的消融做得不错，因为它不只是证明“有用”，还试图证明“为什么有用”。

几个最有意思的结论：

- **DenseFormer 1.767，几乎和 baseline 1.766 一样。**  
  这说明“能访问所有前层”本身还不够，关键在于权重是不是输入相关的。

- **mHC 到了 1.747，已经明显变好。**  
  这说明深度维度上的动态混合确实有效。

- **Full AttnRes 到了 1.737。**  
  它比 baseline、DenseFormer、mHC 都更低，说明显式的沿深度 softmax 注意力是一条更强的路线。

- **SWA（只看最近窗口）只有 1.764。**  
  这很有价值。它说明 AttnRes 的收益不只是“多看最近几层”，而是“能选择性地看更远的层”。

- **块大小从 2、4、8 变化时，损失都在 1.746 左右。**  
  这就是为什么作者最后固定大约 8 个块。不是拍脑袋，而是工程和效果之间一个相当好的平衡点。

- **输入相关查询版本做到 1.731，比 Full AttnRes 还好。**  
  这一点非常耐人寻味。它说明当前报告里的伪查询设计并不是性能上限，而是一个为基础设施优化让路的折中。也就是说，作者不是不知道更强的写法，而是主动选了更容易扩展的写法。

这正是我觉得这份报告有意思的原因。你从正文、消融和系统设计里能更清楚地看到他们的真实取舍：不是盲目追求最低 loss，而是在追求“足够强，同时真能训起来”。

## 7. 我怎么看这份报告

第一，这份报告最重要的，不是它发明了一个新模块，而是它把残差连接从“训练稳定性工具”重新提升成了“信息路由机制”。

这个视角一旦建立起来，很多东西都会被重新理解。残差不再只是梯度高速通道，它还是深度聚合规则。你会开始追问：

- 每一层到底能不能选择性地访问前层？
- 深度维度上有没有“注意力汇聚陷阱”（attention sink）？
- 旧的残差变体本质上是不是沿深度维度的线性注意力？

而这正是报告讨论部分真正厉害的地方。作者把一堆残差变体统一进了一个 `depth mixing matrix` 的视角里，进一步指出：

**很多已有方法，本质上都像是在深度维度上做线性注意力；AttnRes 做的是沿深度维度的 softmax 注意力。**

这个说法非常大胆，但也非常有启发性。它等于是在说：Transformer 当年把序列维度从递归推进到了 softmax 注意力；AttnRes 试图把深度维度也推进一步。

第二，这篇技术报告的气质很像“先把问题提对，再把系统做顺”。它没有执着于把每个部件都做到最花哨。比如查询故意做成按层设定的参数，而不是按词元动态生成的向量，性能上未必绝对最强，但它给了批量计算、两阶段计算、流水线缓存一个成立的基础。很多时候，一篇能落地的技术报告，靠的不是最激进的局部设计，而是整体约束下的取舍。

第三，我觉得这份报告真正值得记住的不是某个 benchmark，而是这句话：

**Why is depth-wise aggregation still fixed while everything else has become adaptive?（为什么沿深度的聚合仍然是固定的，而其他部分都已经变得自适应了？）**

这问得太对了。

## 8. 这份报告的边界

再夸一句之前，也得把边界说清楚。

第一，它目前是 **技术报告 / arXiv 预印本**，不是已经过同行评审的会议论文。写这类文章时，最稳妥的态度不是“它已经证明了未来”，而是“它提出了一个很强的视角，并给出了一套有工程可行性的实现”。

第二，它的大规模结果主要建立在 Kimi Linear 这条架构线上：MoE、KDA/MLA 混合注意力、Moonlight / DeepSeek-V3 风格训练配方。虽然这不削弱结果本身，但也意味着我们还不能自动把结论外推到所有纯稠密的仅解码器 Transformer。

第三，报告自己也承认：Full AttnRes 其实更强，Block AttnRes 是今天硬件约束下的工程解。未来如果显存、带宽、互连再往前走，或者更高效的深度注意力变体出现，今天这版 Block 设计很可能不是终点。

所以我对它的判断是：

- 它已经足够强，值得认真读
- 它已经足够完整，值得认真做复现
- 它还没有强到可以立刻盖棺定论

## 9. 最后的感受

如果把过去十年大模型架构的主线粗暴地概括一下：

- Seq2Seq 在问：怎么把一个序列压成另一个序列？
- Bahdanau 在问：为什么生成时不能回头看输入的不同位置？
- Transformer 在问：为什么序列建模一定要靠递归？
- Chinchilla 在问：为什么更多算力一定主要砸到参数量上？

那《Attention Residuals》（注意力残差）问的是：

**为什么深度上的信息汇总，还停留在“所有历史层统一加总”的时代？**

这个问题问出来，本身就已经很有价值。

我不确定几年后 AttnRes 会不会像 PreNorm 一样成为默认配置，但我很确定，这篇技术报告把残差连接重新变成了一个值得被思考、被设计、被优化的对象。

以前大家说注意力机制改写了序列建模，这份技术报告在尝试改写残差连接。

2026 年春，Kimi 团队的工作已经说明：当Scaling Laws 开始显出逼近瓶颈的迹象时，LLM 的结构创新将持续涌现。

---

**延伸阅读**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-hans/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神经网络进行序列到序列学习） — 编码器-解码器范式的确立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通过联合学习对齐与翻译实现神经机器翻译） — 注意力机制的起源
- [《Attention Is All You Need》](/zh-hans/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成为主角，Transformer 的诞生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-hans/posts/bert/)（BERT：用于语言理解的深度双向 Transformer 预训练） — 预训练范式的确立
- [《Scaling Laws for Neural Language Models》](/zh-hans/posts/scaling-laws-for-neural-language-models/)（神经语言模型的缩放定律） — 规模的数学
- [《Language Models are Few-Shot Learners》](/zh-hans/posts/language-models-are-few-shot-learners/)（语言模型是少样本学习者） — 更大的模型，更善于从上下文中诱发能力
- [《Training Compute-Optimal Large Language Models》](/zh-hans/posts/training-compute-optimal-large-language-models/)（训练计算最优的大语言模型） — 怎样花算力最划算</content:encoded><category>Technical Report Reading</category><category>technical-report-reading</category><category>residual-connections</category><category>transformer</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>论文共读：《Training Compute-Optimal Large Language Models》（训练计算最优的大语言模型）</title><link>https://justinhuangai.github.io/zh-hans/posts/training-compute-optimal-large-language-models/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/training-compute-optimal-large-language-models/</guid><description>Chinchilla 论文：为什么 2022 年的大模型全都「喂少了」，以及算力预算到底该怎么分配，附真实 Python 核心代码</description><pubDate>Wed, 11 Mar 2026 08:58:04 GMT</pubDate><content:encoded>2022 年 3 月 29 日，DeepMind 的一个团队在 arXiv（学术论文预印本网站，论文不用等期刊审稿就能直接发布）上传了一篇论文：[《Training Compute-Optimal Large Language Models》](/papers/2203.15556v1.pdf)（训练计算最优的大语言模型）。

第一作者是 Jordan Hoffmann，联合作者包括 Sebastian Borgeaud、Arthur Mensch、Elena Buchatskaya、Trevor Cai、Eliza Rutherford 等一大串名字：当时全部在 DeepMind。值得一提的是，Arthur Mensch 后来联合创办了 Mistral AI，欧洲最耀眼的 AI 公司之一。

这篇论文圈内通常叫它「Chinchilla 论文」，名字来自团队为验证结论而训练的一个 700 亿参数模型。那只龙猫（chinchilla）比论文标题更出圈：在 AI 圈子里，「Chinchilla scaling」成了这篇论文核心观点的代名词。

而这个核心观点，简单、大胆，并且让整个行业都有点不舒服：**2022 年那些最大的语言模型，很多并不是「模型不够大」，而是在各自的算力预算下被显著欠训练了。**

## 0. 先认几个词

这篇论文会频繁谈到“该把预算花在哪”，所以先把下面几个词认熟，会更容易抓住重点：

- `算力预算`：你愿意为这次训练总共花多少计算资源。
- `参数量`：模型有多大。
- `token / 词元`：模型训练时实际读进去的最小文本单位，可以粗略理解成“模型看到的字或词片段”。
- `loss / 损失`：模型总体错得有多厉害，通常越低越好。
- `scaling law / 缩放定律`：当参数量、数据量、算力变化时，模型表现如何跟着变化。
- `欠训练 / undertrained`：不是模型太小，而是训练数据和训练步数没跟上，模型的潜力没有被充分用出来。

## 1. 要解决什么问题

先交代一下背景。

2020 年，OpenAI 发了一篇 [缩放定律论文](/zh-hans/posts/scaling-laws-for-neural-language-models/)（Kaplan 等人），核心结论是：模型越大效果越好，而且好多少是可以用公式算出来的。到 2022 年春天，整个行业把这句话奉为圣旨。

GPT-3，1750 亿参数，训了 3000 亿词元（词元就是模型看的「字」，大约每个英文单词切成 1-2 个词元，中文大约一个字一个词元）。DeepMind 自家的 Gopher，2800 亿参数，也训了 3000 亿词元。随后 Google 又发布了 5400 亿参数的 PaLM。趋势很明确：参数拉满。

但有一个问题就摆在那儿，大家却视而不见。

Kaplan 等人的结论是：有更多钱（算力）的时候，大部分预算应该花在造更大的模型上（用数学写就是 N ∝ C^0.73），训练数据只需要跟着慢慢涨就行（D ∝ C^0.27）。翻译成白话就是：模型尽量大，数据差不多就行。

Hoffmann 的团队提了一个很简单的问题：这个结论靠谱吗？

## 2. 三条路走到同一个答案

这篇论文最有说服力的地方在于方法论。他们没有只做一组实验就下结论：而是从三个完全独立的角度切入，三条路走到了同一个终点。

**方法一：总预算不变，换着分。** 打个比方：你有 50 万装修预算，可以选择买贵的家电配便宜家具，也可以反过来。他们就是这么做的：训了超过 400 个模型，参数量从 7000 万到 160 多亿不等，每个模型分到的「模型大小 vs 训练数据」比例不同，但总算力一样。对于每个预算档位，他们找出效果最好的那个分配方案。

**方法二：画等高线。** 他们训了 9 种不同大小的模型（从 7000 万到 100 亿参数），每种喂不同量的数据，专门设计成每一组的总算力大致相等。就像在地图上画等高线一样，沿着每条「等算力线」找最优点。

**方法三：直接写方程，用数据拟合。** 他们假设模型表现可以用下面这个方程描述：

$$
\hat{L}(N, D) = E + \frac{A}{N^\alpha} + \frac{B}{D^\beta}
$$

这方程其实在说一件很直觉的事：模型考得不好，要么是脑子不够大（N 太小），要么是书看少了（D 太小），要么两个都有。E 是一个谁都突破不了的下限，学术上叫「熵」（entropy）。熵是信息论里的一个概念，衡量的是「一件事有多不确定」。抛一枚均匀硬币，正反各 50%，不确定性最大，熵最高。如果硬币灌了铅，99% 正面朝上，结果几乎没悬念，熵就很低。语言也有熵。「太阳从___升起」，这句话的熵很低，几乎所有人都会填「东方」。但「我今天吃了___」，后面可以是火锅、三明治、亏，答案五花八门，熵就高。自然语言整体的熵，就是 AI 模型表现的理论天花板：不管模型多强、数据多多，它不可能比这条线更好，因为语言本身就有这么多不确定性。把所有训练数据代入这个方程拟合，就能反推出：给定一笔算力预算，模型该多大、数据该多少。

三条路的答案一致：

$$
N_{\mathrm{opt}} \propto C^a, \quad D_{\mathrm{opt}} \propto C^b, \quad a \approx 0.50, \quad b \approx 0.50
$$

```python
def optimal_scaling(compute: float) -&gt; tuple[float, float]:
    a = 0.50
    b = 0.50
    n_opt = compute ** a
    d_opt = compute ** b
    return n_opt, d_opt
```

a ≈ b ≈ 0.5 的真正含义是：随着算力增长，模型大小和训练数据应当按近似相同的比例一起扩张。算力翻 10 倍时，两者都大约增到 3.2 倍；算力翻 2 倍时，两者都大约增到 1.4 倍。换句话说，模型大小每翻一倍，训练数据也应该翻一倍。这跟 Kaplan 等人的结论直接矛盾：Kaplan 说算力主要应该花在模型大小上。

打个比方：Kaplan 的建议像是「买了一套 200 平的豪宅，但只摆了几件家具」。Chinchilla 说：「不对，你应该买 100 平的房子，然后好好装修：家具配齐、软装到位。同样的总花费，住着舒服得多。」

## 3. Kaplan 为什么算偏了

这不是谁「做错了」，而是实验设定不同，最终导致了不同的最优分配结论。两个团队都做了认真的工作。

差别出在一个训练细节上：学习率调度。

学习率是什么？简单说，AI 模型在训练过程中会不断调整自己的参数。学习率就是「每次调整幅度有多大」。一般来说，训练初期步子大一点（快速学），后期步子小一点（精细调）。这个「先大后小」的节奏安排，就叫学习率调度。

Kaplan 等人用了固定的学习率调度：不管你打算训多久，节奏都一样。但合理的做法是：训得越久，后期的调整步子应该越小越细。他们没做这个适配，导致训练时间一拉长，后半段的学习效率就掉下来了。这就让「训更久」看起来不划算，间接得出了「别在训练时长上花钱，把钱花在模型大小上」的结论。

Hoffmann 的团队给每次训练都单独调整了学习率调度，让每种配置都能发挥最佳水平。一旦做到这点，训更多数据的回报远比 Kaplan 的数字暗示的要大。

这件事的教训很深刻：缩放定律是经验法则，不是物理常数。实验条件变了，结论就可能变。

```python
from dataclasses import dataclass
from typing import Literal


@dataclass(frozen=True)
class TrainingConfig:
    n_params: float
    n_tokens: float
    schedule: Literal[&quot;fixed&quot;, &quot;cosine_with_warmup&quot;]
    warmup_steps: int
    total_steps: int
```

## 4. 那个描述一切的方程

方法三值得单独展开看一看，因为它给出了一个完整的数学模型来描述模型表现：

$$
\hat{L}(N, D) = E + \frac{A}{N^\alpha} + \frac{B}{D^\beta}
$$

拟合出来的具体数字：

- E = 1.69 ： 地板值，也就是自然语言的熵。「我今天吃了___」后面填什么，连人都没法百分百答对，这种不确定性就是熵。不管模型多强、数据多多，表现不可能好过这个数
- A = 406.4，α = 0.34 ： 「脑子不够用」的罚分
- B = 410.7，β = 0.28 ： 「书看少了」的罚分

这个方程的结构很好理解。模型表现不好，原因无非三个：第一，语言本身就有不确定性，神仙来了也预测不了（E）；第二，模型太小，记不住那么多规律（A/N^α）；第三，训练数据太少，见过的世面不够（B/D^β）。后两个罚分是加法关系：哪块短板更严重，哪块就拖后腿更厉害。

就好比考试成绩不好有两个原因：一是脑子不够用（模型太小），二是书看少了（数据不够）。这两块是独立的短板，得分别补。

```python
def estimated_loss(n_params: float, n_tokens: float) -&gt; float:
    e = 1.69
    a = 406.4
    alpha = 0.34
    b = 410.7
    beta = 0.28
    return e + a / (n_params ** alpha) + b / (n_tokens ** beta)


def optimal_params_and_tokens(compute_flops: float) -&gt; tuple[float, float]:
    alpha = 0.34
    beta = 0.28
    a = beta / (alpha + beta)
    b = alpha / (alpha + beta)
    g = 2.0

    base = compute_flops / 6.0
    n_opt = g * (base ** a)
    d_opt = (1.0 / g) * (base ** b)
    return n_opt, d_opt
```

## 5. 那张让全行业尴尬的表

论文的 Table 1 列了当时几个大模型的实际参数量和训练词元数，Table 3 则给出了不同模型大小下 compute-optimal 的词元估计。把两张表对照着看，就像一份对整个行业的审计报告：

| 模型 | 参数量 | 实际训练词元 | Chinchilla 最优词元 |
|------|--------|-------------|-------------------|
| GPT-3 | 1750 亿 | 3000 亿 | 3.7 万亿 |
| Gopher | 2800 亿 | 3000 亿 | 5.9 万亿 |
| Jurassic-1 | 1780 亿 | 3000 亿 | 3.7 万亿 |
| MT-NLG | 5300 亿 | 2700 亿 | 11.0 万亿 |

发现规律了没有？所有模型训练数据都在 3000 亿词元左右。好像整个行业不约而同地认定「3000 亿够了」，然后把多余的算力全砸在了参数量上。

按 Chinchilla 的分析，GPT-3 应该训 3.7 万亿词元：是实际数据量的 12 倍多。Gopher 应该看将近 6 万亿。最夸张的是 MT-NLG，5300 亿参数的巨无霸，应该训 11 万亿词元：实际只喂了 2700 亿，差了 40 倍。

这些模型不是太小，是被饿着了。

```python
from dataclasses import dataclass


@dataclass(frozen=True)
class ModelComparison:
    name: str
    params_billions: float
    tokens_used_billions: float
    optimal_tokens_billions: float


def industry_models() -&gt; list[ModelComparison]:
    return [
        ModelComparison(&quot;GPT-3&quot;, 175.0, 300.0, 3_700.0),
        ModelComparison(&quot;Gopher&quot;, 280.0, 300.0, 5_900.0),
        ModelComparison(&quot;Jurassic-1&quot;, 178.0, 300.0, 3_700.0),
        ModelComparison(&quot;MT-NLG&quot;, 530.0, 270.0, 11_000.0),
    ]
```

## 6. 实锤：Chinchilla vs. Gopher

光说不练假把式。为了验证理论，团队训了 Chinchilla：一个 700 亿参数的模型，喂了 1.4 万亿词元。关键在于：Chinchilla 和 Gopher（2800 亿参数，3000 亿词元）用了完全一样的算力预算。同样的钱，只是分法不同。

结果一目了然。Chinchilla 虽然只有 Gopher 四分之一的参数量，在几乎所有基准测试上都赢了：

- **MMLU**（大规模多任务语言理解）：Chinchilla 67.6% vs. Gopher 60.0% vs. GPT-3 43.9%
- **阅读理解**（RACE-h）：Chinchilla 73.3% vs. Gopher 71.6%
- **常识推理**（HellaSwag）：Chinchilla 80.8% vs. Gopher 79.2%
- **BIG-bench**：Chinchilla 在大多数任务上优于 Gopher

```python
from dataclasses import dataclass


@dataclass(frozen=True)
class ModelConfig:
    name: str
    params_billions: float
    tokens_billions: float
    mmlu_accuracy: float


def chinchilla_vs_gopher() -&gt; tuple[float, float]:
    gopher = ModelConfig(&quot;Gopher&quot;, 280.0, 300.0, 60.0)
    chinchilla = ModelConfig(&quot;Chinchilla&quot;, 70.0, 1_400.0, 67.6)

    gopher_flops = 6.0 * gopher.params_billions * 1e9 * gopher.tokens_billions * 1e9
    chinchilla_flops = 6.0 * chinchilla.params_billions * 1e9 * chinchilla.tokens_billions * 1e9
    return gopher_flops, chinchilla_flops
```

一个参数量只有对手四分之一的模型，同样的花费，在几乎每项指标上都赢了：这不是理论推导，这是真刀真枪的对比。算力没有浪费，只是换了个花法：从堆参数，变成了喂数据。

打个比方：同样 100 万的教育预算，Gopher 的策略是「请一个超级天才，但只给他一本薄教材」；Chinchilla 的策略是「请一个聪明的学生，给他一整个图书馆」。后者考得更好。

## 7. 实际影响

Chinchilla 论文立刻改变了行业的做事方式。

**小模型更省钱。** 训练成本是一次性的，但用户每次提问，模型都要跑一遍：这个「推理成本」跟模型大小直接挂钩，而且是每一次请求都要付的。你可以理解为：训练费是买房，推理费是物业费。房子（模型）越大，物业费越贵，而且得一直交。一个 700 亿参数的模型，每次服务用户的成本只有 2800 亿模型的四分之一。如果小模型效果还更好，那就是双赢：质量更高，账单更低。

**数据成了瓶颈。** Chinchilla 之前，大家抢的是 GPU：你能搞到多少卡？Chinchilla 之后，大家抢的变成了数据：你从哪儿找几万亿高质量词元？这直接引发了全行业的数据争夺战：大规模网页抓取、数据集精选工程，以及后来的合成数据运动（让 AI 生成训练数据来训 AI）。

**LLaMA 时刻。** Meta 的 LLaMA（2023 年 2 月）可以说是 Chinchilla 缩放定律最直接的应用。LLaMA-13B（130 亿参数）在 1 万亿词元上训练，在大多数基准上超过了 GPT-3（1750 亿参数）：一个比你小十几倍的模型考得比你好，就因为人家书读得多。LLaMA-65B 在 1.4 万亿词元上训练，跟 Chinchilla 和 PaLM-540B 不相上下。Meta 在论文中明确引用了 Chinchilla，刻意选择更小的模型配更多的数据。

```python
def inference_cost_comparison() -&gt; tuple[float, float]:
    gopher_cost_per_token = 280.0
    chinchilla_cost_per_token = 70.0

    queries_per_day = 1_000_000.0
    tokens_per_query = 500.0

    daily_cost_gopher = queries_per_day * tokens_per_query * gopher_cost_per_token
    daily_cost_chinchilla = queries_per_day * tokens_per_query * chinchilla_cost_per_token
    return daily_cost_gopher, daily_cost_chinchilla
```

## 8. 我的思考

第一，这篇论文是一次优雅的纠偏。它没有否定 Kaplan 等人的工作，而是在同一个框架下发现了一个方法论上的差异（固定学习率调度），修正之后得出了不同的结论。损失函数 L̂(N, D) = E + A/N^α + B/D^β 是 Kaplan 公式的精化，不是推翻。科学最好的样子就是这样：有人认真做了一件事，另一个人更认真地做了一遍，领域就往前走了一步。

第二，论文最让人惊讶的不是数学：是理论和实践之间的鸿沟。整个行业的人都看到 3000 亿词元成了默认值，但没有人认真质疑过，直到这个团队把账算清楚。那些模型不是太小，是被饿着了。解决办法不是造更大的模型，而是喂更多的数据。

第三，等比例缩放的结论（a ≈ b ≈ 0.5）美在简洁。模型大小和数据量之间没有不对称性。有更多的算力？两边等量加。「下一块钱算力该花在哪？」Chinchilla 给出的答案不是继续单押参数量，而是让模型大小和训练数据近似等比例地一起增长。

第四，实际遗产是巨大的。Chinchilla 之前，通往更强 AI 的路是「做更大」。Chinchilla 之后，路变成了「训更好」。这一个转变让那些买不起最大参数量但能搞到大量数据的团队也有了机会。LLaMA、Mistral，以及整个开源大模型生态，都直接受益于这个洞察。

Kaplan 的论文说：更大的模型可预测地更强。Chinchilla 的论文说：没错，但你们大的方式不对。别囤参数了，多喂数据。

一篇论文给了行业放心做大的信心，另一篇教会了行业怎么把大做对。

---

**论文共读系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-hans/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神经网络进行序列到序列学习） — 编码器-解码器范式的确立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通过联合学习对齐与翻译实现神经机器翻译） — 注意力机制的起源
- [《Attention Is All You Need》](/zh-hans/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成为主角，Transformer 的诞生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-hans/posts/bert/)（BERT：用于语言理解的深度双向 Transformer 预训练） — 预训练范式的确立
- [《Scaling Laws for Neural Language Models》](/zh-hans/posts/scaling-laws-for-neural-language-models/)（神经语言模型的缩放定律） — 规模的数学
- [《Language Models are Few-Shot Learners》](/zh-hans/posts/language-models-are-few-shot-learners/)（语言模型是少样本学习者） — 更大的模型，更善于从上下文中诱发能力</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>chinchilla</category><category>scaling-laws</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>论文共读：《Scaling Laws for Neural Language Models》（神经语言模型的缩放定律）</title><link>https://justinhuangai.github.io/zh-hans/posts/scaling-laws-for-neural-language-models/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/scaling-laws-for-neural-language-models/</guid><description>规模的数学：为什么更大的模型可预测地更强，附真实 Python 核心代码</description><pubDate>Sun, 01 Mar 2026 08:45:39 GMT</pubDate><content:encoded>2020 年 1 月 23 日，OpenAI 的十个人在 arXiv（一个学术论文预印本网站，论文不用等期刊审稿就能直接发布）上传了一篇论文：[《Scaling Laws for Neural Language Models》](/papers/2001.08361v1.pdf)（神经语言模型的缩放定律）。

十位作者分别是 Jared Kaplan、Sam McCandlish、Tom Henighan、Tom B. Brown、Benjamin Chess、Rewon Child、Scott Gray、Alec Radford、Jeffrey Wu 和 Dario Amodei，当时全部在 OpenAI。

这份名单放到今天回看，特别有意思。Jared Kaplan 和 Sam McCandlish 是理论物理出身：Kaplan 在加入 OpenAI 之前，是约翰斯·霍普金斯大学的弦理论教授。Dario Amodei 是 OpenAI 研究副总裁。Tom B. Brown 后来成了 GPT-3 论文的第一作者。Alec Radford 设计了 GPT-1 和 GPT-2。两年之内，Kaplan、McCandlish 和 Amodei 就离开了 OpenAI，联合创立了 Anthropic（Claude 的开发商）。

弦理论学者有个职业习惯：在复杂现象里寻找简洁的普适定律。

这个习惯贯穿了整篇论文。

## 0. 先认几个词

如果你平时不常看这种带公式的论文，先把这几个词认熟，后面会顺很多：

- `参数量`：模型里一共有多少可学习参数，也就是模型的大小。
- `数据量`：训练时一共喂给模型多少文本。
- `算力 / compute`：训练时总共做了多少计算。你可以先把它当成“电费账单”。
- `loss / 损失`：模型犯错有多严重，通常越低越好。
- `幂律 / power law`：某个量按固定指数变化的关系；画在对数坐标上，经常会接近一条直线。
- `对数坐标`：像 `1、10、100、1000` 这样按倍数增长的刻度，不是 `1、2、3、4` 那样均匀加一。

## 1. 要解决什么问题

到 2020 年初，AI 圈已经知道一件事：模型越大，效果越好。但「越大越好」不是科学，是感觉。

大家回答不了几个最基本的问题：算力预算翻一倍，效果能好多少？这笔钱是该花在更大的模型上、更多的数据上，还是更久的训练上？有没有一个公式，可以在花钱之前就算出来？

这篇论文给出了公式。不是靠拍脑袋，也不是靠经验法则：靠方程。

## 2. 幂律：核心发现

论文的核心发现，一句话就能说清：**AI 模型的表现好坏，和它的「个头」之间存在一个简洁的数学关系。**

具体来说，论文测量了三样东西对模型表现的影响：模型有多大（参数量）、喂了多少数据、烧了多少算力。在论文观测到的范围内，只要模型的瓶颈主要在其中一项上，表现和这一项之间的关系都能画成一条漂亮的直线：前提是你把坐标轴的刻度取成对数（也就是 1, 10, 100, 1000 这样等距排列，而不是 1, 2, 3, 4）。

这种「对数坐标下的直线关系」，数学上叫**幂律（power law）**。

三个方程概括了整篇论文：

$$
L(N) \approx \left(\frac{N_c}{N}\right)^{\alpha_N}, \quad \alpha_N \approx 0.076
$$

$$
L(D) \approx \left(\frac{D_c}{D}\right)^{\alpha_D}, \quad \alpha_D \approx 0.095
$$

$$
L(C) \approx \left(\frac{C_c}{C}\right)^{\alpha_C}, \quad \alpha_C \approx 0.050
$$

别被符号吓到。拆开就是几个简单的角色：

- **L** 是「测试损失」：你可以理解为模型的考试成绩，只不过分数越低代表越好（想象成失误率：失误越少，能力越强）
- **N** 是参数量，也就是模型的「个头」。参数越多，模型能记住的规律就越多
- **D** 是训练数据量，也就是模型的「课本厚度」。课本越厚，能学到的东西就越多
- **C** 是训练消耗的总算力，也就是「电费账单」。单位是 PetaFLOP-days：每秒做一千万亿次运算，连续跑一整天
- **N_c、D_c、C_c** 是常数，当参考点用的
- **α**（alpha）是幂律的指数，决定了「个头翻倍时，成绩能进步多少」。指数越大，同样的投入换来的进步越大

为什么幂律这么重要？因为它意味着回报不会快速见顶。

打个比方：如果 AI 的进步像背单词：前 1000 个很容易，后面越来越慢，背到 5000 个就几乎不动了：那就是对数增长，回报递减极快。但幂律不一样，它像修路：你修到 10 公里的时候觉得效果不错，修到 100 公里效果更好，修到 1000 公里效果又上了一个台阶。每上一个量级都有实实在在的回报。

当然，论文也提醒了：路不可能修到无限远。这个趋势最终一定会变平。但在论文观测到的范围内，这条线走得干净利落，没有撞墙的迹象。

```python
def power_law_loss(x: float, x_c: float, alpha: float) -&gt; float:
    return (x_c / x) ** alpha


def scaling_law_examples() -&gt; dict[str, float]:
    alpha_n = 0.076
    alpha_d = 0.095
    alpha_c = 0.050

    return {
        &quot;10x_params&quot;: 10.0 ** alpha_n,
        &quot;10x_data&quot;: 10.0 ** alpha_d,
        &quot;10x_compute&quot;: 10.0 ** alpha_c,
    }
```

三个指数本身就在讲故事。数据量（α = 0.095）扩大一个量级带来的进步最大。模型大小（α = 0.076）次之。算力（α = 0.050）最低：因为如果你只是堆算力，却不合理分配到模型大小和训练时长上，就是在烧钱。真正的杠杆在于：扩对东西。

## 3. 在论文测过的范围内，怎么搭不重要，搭多大才重要

这是论文最出人意料的发现。

Transformer 有很多可以调的「形状」参数：堆多少层（深度）、每层有多宽（隐藏维度）、用多少个注意力头、前馈网络有多大。直觉上你可能觉得，这些比例关系至关重要，调好了事半功倍。

但论文发现：在它测过的 Transformer 形状范围内，这些比例关系几乎不影响最终表现。真正起决定作用的是一个数字：非嵌入参数的总量。

什么是「非嵌入参数」？简单说，模型的参数分两种：一种是「词典」（嵌入层，负责把文字转成数字），另一种是「大脑」（Transformer 层，负责理解和推理）。论文发现，真正决定模型能力的是「大脑」的大小，不是「词典」的大小。

一个只有 2 层但每层特别宽的 Transformer？和一个 40 层但每层很窄的 Transformer？只要它们的「大脑」总参数量接近，考试成绩就差不多。

```python
from dataclasses import dataclass


@dataclass(frozen=True)
class ArchitectureExperiment:
    n_layers: int
    d_model: int
    n_heads: int
    d_ff: int


def non_embedding_params(config: ArchitectureExperiment) -&gt; int:
    n = config.n_layers
    d = config.d_model
    d_ff = config.d_ff
    return n * (4 * d * d + 2 * d * d_ff + 4 * d)
```

这个发现的实际意义很直接：你不用花几周去搜索「最优架构」。选一个合理的 Transformer 形状，然后把精力放在做大就行。

## 4. 模型什么时候会「死记硬背」：数据瓶颈

模型不是越大越好：如果你的课本太薄的话。

想象一个记忆力超强的学生，你只给他一本 100 页的教材，他很快就能把这本书倒背如流。但这不叫「学会了」，这叫「背下来了」。考试一换题型就傻眼。这就是过拟合：模型把训练数据死记硬背了，却没有学到真正的规律。

论文这里真正漂亮的地方，是给出了一个统一公式，把「模型多大」和「数据多少」如何共同决定表现写进了一个式子：

$$
L(N, D) = \left[\left(\frac{N_c}{N}\right)^{\alpha_N / \alpha_D} + \frac{D_c}{D}\right]^{\alpha_D}
$$

这个公式说的是：模型的考试成绩不是由「个头」或「课本厚度」单独决定的，而是两者一起。如果模型够大但数据不够，性能就卡在数据上；如果数据够多但模型太小，性能就卡在模型上。过拟合，就是「个头大、课本薄」这对矛盾的自然结果。

从这个关系出发，论文还给了一个粗略的经验门槛：「课本至少要多厚，才不会让这个学生背书」：

$$
D \gtrsim 5 \times 10^3 \times N^{0.74}
$$

用大白话说：模型大 10 倍，课本只需要厚大约 5.5 倍就够了。更大的模型学习效率更高：同样看一页书，它能悟到更多。

```python
def loss_nd(n_params: float, n_tokens: float) -&gt; float:
    n_c = 8.8e13
    d_c = 5.4e13
    alpha_n = 0.076
    alpha_d = 0.095
    ratio = alpha_n / alpha_d
    return ((n_c / n_params) ** ratio + d_c / n_tokens) ** alpha_d


def min_dataset_tokens(n_params: float) -&gt; float:
    return 5_000.0 * n_params ** 0.74
```

按这个公式粗略一算，GPT-3 那个级别（1750 亿参数）的模型要想不「背书」，课本厚度应该接近万亿词元。但 GPT-3 实际只喂了 3000 亿词元：远没到安全线。回头看，GPT-3 的数据其实是偏少的。这也是为什么后来业界重新审视了「模型多大、数据喂多少」这个配比：最典型的就是 Chinchilla 论文（Hoffmann 等人，2022 年），直接指出：之前那些大模型，数据普遍喂少了。

## 5. 算力最优分配：钱该怎么花

如果你有一笔固定的算力预算：比如说够你租 1000 块 GPU 跑一个月：应该怎么花？这是论文里最有实际价值的问题，答案相当反直觉。

论文发现最优分配遵循：

$$
N_{\mathrm{opt}} \propto C^{0.73}
$$

$$
B_{\mathrm{opt}} \propto C^{0.24}
$$

$$
S_{\mathrm{opt}} \propto C^{0.03}
$$

翻译成人话：如果你的预算涨了 10 倍，你应该把模型做大约 5.4 倍，每次喂的数据量增加约 1.7 倍，训练时间几乎不延长（大约只多 7%）。

钱主要花在哪？花在把模型做大上。

反直觉的部分来了：**你应该造一个尽可能大的模型，然后不用训到头就可以停。** 大多数人的直觉是「我选个中等大小的模型，然后慢慢训，训到极致」。缩放定律说的恰恰相反：同样一笔钱，一个没训完的大模型，比一个训透了的小模型，表现更好。

就像装修：同样 50 万预算，与其在一个 60 平的小户型里堆满顶配材料，不如买一个 120 平的大户型做个简装。空间大了，住起来怎么都比小房子舒服。

```python
from dataclasses import dataclass


@dataclass(frozen=True)
class ComputeAllocation:
    n_params: float
    batch_size: float
    training_steps: float


def optimal_allocation(compute: float) -&gt; ComputeAllocation:
    return ComputeAllocation(
        n_params=compute ** 0.73,
        batch_size=compute ** 0.24,
        training_steps=compute ** 0.03,
    )


def is_compute_efficient(n_params: float, compute: float) -&gt; bool:
    optimal_n = compute ** 0.73
    return abs(n_params / optimal_n - 1.0) &lt; 0.5
```

这个结论深刻地改变了整个行业。五个月后发布的 GPT-3 直接遵循了这个思路：造一个当时规模空前的 1750 亿参数模型，而不是把小模型训到极致。后来的 Chinchilla 论文（Hoffmann 等人，2022 年）更新了具体的指数，认为大多数大模型的数据其实喂少了：但「最优权衡是可以算出来的」这个核心洞察，源头在这里。

## 6. 临界批大小：什么时候该加机器

训练 AI 模型的时候，你可以选择每次给模型看多少数据再更新一次：这叫「批大小」（batch size）。批大小越大，你可以同时用更多 GPU 并行处理，训练速度就越快。但并不是加机器就一定有用。

论文发现，批大小存在一个「甜蜜点」：

$$
B_{\mathrm{crit}} \propto L^{-4.8}
$$

训练刚开始的时候，模型还很「菜」，每批数据都能给它很大启发，小批量就够了。但训练到后期，简单的规律都学完了，每批数据带来的新信息越来越少，这时候就需要更大的批量来「凑够信号」。

甜蜜点以下，加机器很划算：机器翻倍，训练时间几乎减半。甜蜜点以上，加机器就是烧钱：多出来的机器几乎不加速。

```python
def critical_batch_size(loss: float, b_star: float, l_star: float) -&gt; float:
    return b_star * (l_star / loss) ** 4.8
```

很多团队全程用固定的批大小训练。缩放定律告诉你：应该随着训练推进逐步加大：开始用小批量，模型变强后再加机器。

## 7. 我的思考

读完这篇论文，有几点感受。

第一，论文最深层的贡献不是任何一个具体数字，而是证明了一件事：AI 模型的表现好坏，不是玄学，是可以用简洁的数学关系描述的。在这篇论文之前，训练大模型基本靠经验：试一试，调调参数，听天由命。有了这篇论文，你可以在花钱之前就算出来结果大概会怎样。它至少把大模型训练里最贵、最关键的一部分：钱怎么花：从「靠感觉」推进到了「可以算」。

第二，作者的背景很重要。Kaplan 和 McCandlish 带来了理论物理学家的思维方式：精确测量，拟合规律，寻找最简洁的描述。这不是大多数 AI 论文的写法。多数 AI 论文是「我发明了一个新东西，效果比之前好」。这篇论文没发明任何新东西。它提出的是一种看问题的方式。工具不新：洞察是新的。

第三，「尽量把模型做大，但不用训到头就可以停」这个结论是真的反直觉，而且直接重塑了行业的资源分配方式。在这篇论文之前，默认做法是选一个模型大小然后训到完全收敛：把这笔预算花到底。有了这篇论文之后，问题变成了：同样一笔预算，与其把一个小模型训到极致，不如把模型做到尽可能大，训到「够用」就停：因为一个没训完的大模型，比一个训透的小模型表现更好。这个思路直接催生了 GPT-3（1750 亿参数，3000 亿词元），也影响了后来的每一个大模型。

第四，从历史脉络看，这篇论文可以被视为 [GPT-3 论文](/zh-hans/posts/language-models-are-few-shot-learners/)的理论基础。GPT-3 直接引用了它，GPT-3 论文也明确展示了 few-shot 能力（给几个例子就能完成新任务）随模型容量平滑提升。把 GPT-3 选择 1750 亿参数看作受缩放定律启发，是合理的推断：尽管 GPT-3 论文本身并没有逐句写明「我们按 Kaplan 的公式设定了参数量」。但可以说，没有缩放定律提供的信心，在那个规模上做决策的不确定性会大得多。

「更大的模型更强」，这句话在 2020 年之前只是一种感觉。这篇论文把它变成了一组方程：告诉你强多少、花多少、怎么花最划算。

AI 行业后来变成了一场算力竞赛。读完这篇论文你就明白了：这场竞赛不是盲目的军备竞赛，而是有人先算清了账。

---

**论文共读系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-hans/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神经网络进行序列到序列学习） — 编码器-解码器范式的确立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通过联合学习对齐与翻译实现神经机器翻译） — 注意力机制的起源
- [《Attention Is All You Need》](/zh-hans/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成为主角，Transformer 的诞生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-hans/posts/bert/)（BERT：用于语言理解的深度双向 Transformer 预训练） — 预训练范式的确立
- [《Language Models are Few-Shot Learners》](/zh-hans/posts/language-models-are-few-shot-learners/)（语言模型是少样本学习者） — 更大的模型，更善于从上下文中诱发能力
- [《Training Compute-Optimal Large Language Models》](/zh-hans/posts/training-compute-optimal-large-language-models/)（训练计算最优的大语言模型） — 怎样花算力最划算</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>scaling-laws</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>OpenClaw 专题：架构分析 🦞</title><link>https://justinhuangai.github.io/zh-hans/posts/openclaw-architecture/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/openclaw-architecture/</guid><description>基于 v2026.3.8 源码，拆解一个自托管 AI 助手的工程骨架</description><pubDate>Tue, 24 Feb 2026 08:37:11 GMT</pubDate><content:encoded>![OpenClaw](/images/openclaw-logo-text-dark.webp)

[上一篇](/zh-hans/posts/openclaw-ecosystem/)聊了生态，这一篇拆架构。

OpenClaw 的代码量不小：43 万行 TypeScript。但真正值得看的不是代码量，是它的架构选择。一个要同时接二十多个聊天平台、管几个 Agent、还能调工具的 AI 助手，它是怎么把这些东西塞到一起还不散架的？

## 0. 先认几个词

如果你没有系统架构背景，也没关系。先记住 6 个词，后面会顺很多：

- `架构`：系统如何分层、每层分别负责什么。
- `Channel / 渠道层`：负责和外部聊天平台“说同一种协议”的适配层。
- `Gateway / 网关`：中央调度者，负责路由消息、管理会话和 Agent。
- `Node / 执行节点`：真正去拍照、跑命令、操作设备的执行端。
- `workspace / 工作区`：Agent 的配置、记忆、技能和会话文件所在的本地目录。
- `sandbox / 沙箱`：把高风险操作隔离起来的受限执行环境。

## 1. 3 层架构：Channel → Gateway → Node

先看全景图：

![OpenClaw Architecture](/images/openclaw-architecture-zh-hans.svg)

你可以把 OpenClaw 想成一家公司的 3 个部门：

| 谁 | 干什么 | 打个比方 |
|------|------|--------|
| **Channel（渠道层）** | 对接 WhatsApp、Telegram、Discord、飞书等 20+ 聊天平台 | 前台：负责接电话、收邮件、接待来访 |
| **Gateway（网关）** | 中央调度，管理所有会话和 Agent | 大脑：谁的消息该给谁处理、怎么回复，全在这里决定 |
| **Node（执行节点）** | 在设备上干活：拍照、录屏、跑命令 | 手和脚：大脑说「拍张照」，它去执行 |

整个系统的核心就是 Gateway：一个常驻运行的进程，默认只监听本机（`127.0.0.1:18789`），不对外网开放。想远程访问？走 Tailscale 隧道，不直接暴露端口。这个设计叫「Loopback-First」，安全上很讨巧：什么端口都不开，就没有攻击面。

为什么只用一个进程不搞分布式？原因很实际：WhatsApp 协议要求同一时间只能有一个设备在线，你搞两个进程反而会打架。与其为了「架构正确」多搞一堆协调逻辑，不如老老实实一个进程管到底。对绝大多数个人用户来说，够用了。

## 2. 一条消息的旅程

你在 Telegram 上跟你的 OpenClaw 说了一句「帮我查下明天北京天气」，发生了什么？

**第 1 步：接入。** Telegram 适配器收到消息，把 Telegram 格式的数据翻译成 OpenClaw 内部统一格式。不管消息从哪个平台来，翻译完都长一个样。

**第 2 步：验身份、找路由。** 你是谁？允不允许跟 Agent 说话？如果是陌生人，先弹一个配对码让主人批准。通过后，路由引擎决定这条消息交给哪个 Agent。

**第 3 步：组装上下文。** 从磁盘读出你之前的聊天记录，加载 Agent 的性格设定（SOUL.md）、行为规则（AGENTS.md），再从记忆库里语义搜索相关内容。组装成一份完整的「大脑简报」。

**第 4 步：问大模型。** 这份简报发给 Anthropic、OpenAI、DeepSeek、MiniMax、GLM、Qwen、Gemini 等大模型提供商（你配了哪个就用哪个），模型一个字一个字地流式吐出回复。

**第 5 步：执行工具。** 如果模型说「我需要跑个命令查天气」，运行时会拦截这个请求，根据安全策略决定在哪里执行（管理员的命令直接跑，陌生人的命令丢到 Docker 沙箱里跑），然后把结果喂回给模型继续生成。

**第 6 步：回复。** 最终答案按 Telegram 的格式要求（字数限制、Markdown 规则等）拆好，发回给你。聊天记录写入磁盘。

整个链路中，真正慢的是第 4 步：等大模型吐第一个字。其他步骤基本都是毫秒级。

## 3. 渠道适配层：一次开发，到处说话

OpenClaw 对接了二十多个聊天平台，但不是每个平台都从零写一套。它抽象了一层「适配器」，每个平台的适配器只负责四件事：

- **登录**：WhatsApp 扫二维码、Telegram 填 Bot Token、iMessage 用 macOS 原生能力：各平台各的规矩
- **翻译进来的消息**：不管是文字、图片、回复还是表情，统统翻译成内部统一格式
- **管门禁**：谁能私聊、群里要不要 @才回复、哪些群允许
- **翻译出去的回复**：把 Agent 的回复适配成各平台能显示的格式

内置的有六个大平台（WhatsApp、Telegram、Discord、Slack、Signal、iMessage），其他三十多个（飞书、LINE、Matrix、Mattermost 等）通过插件接入。

这层抽象最大的好处是：Agent 完全不需要知道消息来自哪个平台。你在 WhatsApp 上聊的 Agent 和在 Telegram 上聊的是同一个，逻辑完全复用。渠道只是「传话筒」，换一个传话筒不影响说话的人。

## 4. Agent 运行时与工作区

Agent 运行时的内核来自 Pi-mono（一个开源编程 Agent），嵌入在 Gateway 里面跑。

### 「一切皆文本」的工作区

这是 OpenClaw 最有意思的设计之一：每个 Agent 的所有配置，都是你能直接打开编辑的文本文件：

```
workspace/
├── AGENTS.md          # 定义 Agent 是谁、怎么做事（相当于简历+工作手册）
├── SOUL.md            # 灵魂设定：性格、价值观（写好就不该改）
├── USER.md            # 记录用户信息：你叫什么、喜好是什么
├── MEMORY.md          # 长期记忆：Agent 主动记下的重要事情
├── HEARTBEAT.md       # 定时任务：比如每天早上报天气
├── memory/            # 日记本
│   └── YYYY-MM-DD.md  # 每天一页，只追加不修改
├── skills/            # 技能包
└── sessions.json      # 会话记录
```

没有数据库，没有专用管理界面，纯文本文件。想改 Agent 性格？打开 SOUL.md 改两行就行。想看它记住了什么？打开 MEMORY.md 直接看。这种「你能看到它的全部大脑」的透明感，是很多封闭 AI 产品做不到的。

### 每一轮对话怎么跑

1. **分清身份**：你是管理员直聊、朋友私聊、还是群里有人 @了它？不同来源，安全等级不同
2. **组装记忆**：读聊天记录、加载性格和规则、搜索相关的长期记忆，拼成一份完整上下文
3. **问大模型**：发给配置的模型，支持 fallback：主力模型挂了自动切备用
4. **干活+记住**：执行工具调用，更新聊天记录

上下文的组装不是把所有东西一股脑塞进去。不相关的技能不加载、不需要的工具不注入：省 token 就是省钱。

## 5. 4 层记忆：让 AI 真的「记住你」

普通聊天机器人关掉窗口就失忆了。OpenClaw 不会。它有 4 层记忆，从最深层的「我是谁」到最浅层的「刚才聊了啥」：

| 层 | 是什么 | 打个比方 |
|------|--------|------|
| **SOUL** | 人格设定，永远不变 | 你的性格：出生就定了 |
| **TOOLS** | 当前装了哪些技能 | 你今天带了哪些工具出门 |
| **USER** | 关于你的长期记忆 | 你的老朋友记得你爱吃什么 |
| **Session** | 当前这次对话 | 此刻正在聊的话题 |

几个巧妙的机制：

**每日日记。** 每天的对话会自动写进 `memory/2026-03-12.md`（只追加，不覆盖）。下次开聊时，Agent 会自动翻看今天和昨天的日记，保持连续感：「你昨天说要买的那本书，买了吗？」

**自动抢救记忆。** 聊得太长，上下文快塞满了怎么办？OpenClaw 会偷偷在后台跑一个隐形轮次，把重要信息存进 MEMORY.md，然后压缩掉旧内容。你察觉不到这个过程，但关键信息不会丢。这个机制叫 Pre-Compaction。

**语义搜索。** 你说「之前聊过的那个部署问题」，它能从记忆里搜出来：不是靠关键词精确匹配，而是靠语义理解。底层是向量搜索（SQLite-vec）+ 传统关键词搜索（BM25）双管齐下。

**跨平台认人。** 你在 Telegram 上跟它聊了半小时，切到 WhatsApp 继续聊，它认得你。同一个人在不同平台的 ID 会被链接到同一个身份，共享同一份记忆。但群聊的记忆是隔离的：你在群里说的话，不会泄露到私聊里。

## 6. 工具系统：只给 4 把刀

这是 OpenClaw 最「叛逆」的设计。

别的 AI Agent 框架恨不得内置一百个工具。OpenClaw 只给了 4 个：

| 工具 | 一句话 |
|------|------|
| **Read** | 读文件 |
| **Write** | 写文件 |
| **Edit** | 改文件 |
| **Bash** | 跑命令 |

就这？就这。

创始人的逻辑是：有了命令行（Bash），你能干任何事。想查天气？`curl` 一下。想发邮件？调个 CLI 工具。想操作数据库？`psql` 命令搞定。不需要为每个场景预制一个专用工具。

这就是 Unix 哲学：小工具、可组合、文本流。代价是什么？你需要一个足够聪明的模型，能自己想出该调什么命令。所以 OpenClaw 推荐用 Claude Opus 这个级别的模型。弱模型可能搞不定。

在这 4 个核心工具之上，还有 55 个内置 Skills（技能）和 ClawHub 技能市场。Skills 是可以安装和卸载的：相当于给你的 Agent 装 App。

**有意思的是，它故意不支持 MCP。** MCP 是 Anthropic 搞的工具协议标准，现在满世界的 AI 框架都在接。OpenClaw 偏不。Peter 原话是：「MCP 是垃圾，不能 scale。你知道什么能 scale？CLI。Unix。」替代方案是用内置的 `mcporter` 做桥接。

**更有意思的是自我扩展。** OpenClaw Agent 遇到不会干的事，会自己写一个 skill 来完成，写完自动装上。发现 skill 有 bug？自己改自己重载。这意味着你的 Agent 会在使用过程中越来越强：它在「养自己」。

## 7. 多 Agent 路由：一个大脑，多个人格

一个 Gateway 可以同时跑好几个 Agent，各管各的。路由规则长这样：

```json
{
  &quot;bindings&quot;: [
    { &quot;agentId&quot;: &quot;home&quot;, &quot;match&quot;: { &quot;channel&quot;: &quot;whatsapp&quot;, &quot;accountId&quot;: &quot;personal&quot; } },
    { &quot;agentId&quot;: &quot;work&quot;, &quot;match&quot;: { &quot;channel&quot;: &quot;slack&quot; } },
    { &quot;agentId&quot;: &quot;bot&quot;, &quot;match&quot;: { &quot;channel&quot;: &quot;discord&quot;, &quot;guildId&quot;: &quot;123456&quot; } }
  ]
}
```

翻译一下：WhatsApp 私人号来的消息给「家庭助手」处理，Slack 的给「工作助手」，Discord 某个服务器的给「社区机器人」。三个 Agent 完全隔离：各自有自己的性格、记忆、技能和安全策略。

## 8. 安全：3 道门

你的 Agent 跑在你自己的服务器上，能执行命令、读写文件：安全当然是大事。OpenClaw 的安全模型分 3 道门：

**第一道门：你是谁？（私聊配对）** 陌生人给你 Agent 发消息，Agent 不会直接回。它会发一个 6 位配对码，你在已认证的渠道里确认了，对方才能用。这是默认行为：关掉就意味着任何知道你号码的人都能白嫖你的 API 额度。

**第二道门：VIP 通道（白名单）。** 信任的人可以直接加到白名单里（`allowFrom`），跳过配对直接聊。

**第三道门：群里别瞎回（群组规则）。** 群聊里 Agent 默认只在被 @ 时才回复，不会对着群里每条消息都冒泡：既省 token，又不扰民。

再往下还有纵深防御：工具权限五层过滤、Docker 沙箱隔离（陌生人的命令在沙箱里跑）、安全审计命令（`openclaw security audit`）。这些层是独立的：配对码泄露了？沙箱还在。沙箱被绕过了？工具策略还限制着能调什么。

## 9. 几个判断

**单进程不是偷懒，是务实。** 对个人用户来说，一个进程管到底比搞分布式靠谱得多。这个架构的天花板在哪？大概是同时在线的消息量超过单机处理能力的时候：对绝大多数人来说，这一天不会来。

**渠道抽象层是最值钱的一层。** 二十多个平台的差异全封装在适配器里，Agent 完全不操心消息从哪来。想加一个新平台？写个适配器就行，Agent 逻辑一行不改。这个解耦做得非常干净。

**安全设计是认真的，但落地仍有差距。** 架构上从身份、沙箱到工具策略做了纵深防御。但 Kaspersky 审计发现了 512 个漏洞（8 个严重级别），说明蓝图画得好和实际安全之间，隔着持续的工程投入。

**4 个核心工具的极简路线是一场赌注。** 赌的是模型能力会持续上升，强到不需要预制工具就能「万物皆可 Bash」。如果大模型的天花板卡住了，这条路会很难走。但如果模型能力继续提升，这可能是最优雅的方案。

**最终的检验标准不是架构有多漂亮，而是跑得稳不稳。** 这篇分析基于源码静态阅读。实际效果：高并发下的 Gateway 稳定性、沙箱真的能不能防住攻击、跨渠道认人的边界情况：需要更多真实使用数据来验证。

架构只是骨架，生产环境才是考场。</content:encoded><category>OpenClaw</category><category>AI</category><category>open-source</category><category>openclaw</category></item><item><title>论文共读：《Language Models are Few-Shot Learners》（语言模型是少样本学习者）</title><link>https://justinhuangai.github.io/zh-hans/posts/language-models-are-few-shot-learners/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/language-models-are-few-shot-learners/</guid><description>更大的模型，更善于从上下文中诱发能力，附真实 Python 代码</description><pubDate>Wed, 11 Feb 2026 08:22:54 GMT</pubDate><content:encoded>2020 年 5 月 28 日，OpenAI 在 arXiv（一个学术论文预印本网站，论文不用等期刊审稿就能直接发布）上传了一篇 75 页的论文：[《Language Models are Few-Shot Learners》](/papers/2005.14165v4.pdf)（语言模型是少样本学习者）。

作者有 31 人，全部来自 OpenAI。第一作者 Tom B. Brown，其余包括 Jared Kaplan（缩放定律的核心研究者）、Alec Radford（GPT-1 和 GPT-2 的主要设计者）、Ilya Sutskever（OpenAI 联合创始人兼首席科学家）、Dario Amodei（OpenAI 研究副总裁）等。

这份作者名单后来分化出了几家最重要的 AI 公司：Dario Amodei 和 Jared Kaplan 离开 OpenAI 创立了 Anthropic，Ilya Sutskever 后来也联合创立了 Safe Superintelligence Inc.（SSI）。

论文的核心主张很直接：把语言模型做大，大到 1750 亿参数，它就能在不更新任何权重的情况下，仅靠几个示例就完成各种任务：有时甚至逼近经过专门微调的模型。

这不是任务级微调，而是在固定参数下，通过上下文适配任务的能力：论文称之为**上下文学习（in-context learning）**。

## 0. 先认几个词

如果你对 GPT-3 这类模型的工作方式还没有概念，先记住下面几个词就够了：

- `语言模型`：给它一段前文，它的基本工作就是预测下一个词。
- `参数量`：模型里可学习的数字总数。你可以粗略把它理解成模型的“脑容量”。
- `prompt / 提示词`：你喂给模型看的任务说明、示例和输入。
- `上下文窗口`：模型一次能看到多少文本的容量。例子太多，塞不进去，就没法一起看。
- `few-shot / one-shot / zero-shot`：分别指给多个例子、给一个例子、完全不给例子。
- `in-context learning / 上下文学习`：不改模型参数，只靠 prompt 里的说明和例子，就让模型临时学会怎么做任务。

## 1. 要解决什么问题

[BERT](/zh-hans/posts/bert/) 确立的「预训练 + 微调」范式在 2020 年已经是主流做法。效果很好，但论文指出了三个根本问题。

第一，每个新任务仍然需要一个标注数据集。标注数据的获取成本高，且很多实际任务根本没有对应的标注集。

第二，微调后的模型在测试基准上的表现，不一定反映真实泛化能力。模型可能只是学到了训练数据中的虚假相关性（spurious correlations）：在基准集里得分很高，但换个分布就崩了。

第三，人类不是这样学习的。人类看一两个例子，听一句自然语言指令，就能完成新任务。而当时的 NLP 系统，每个新任务都需要成千上万条标注数据来微调。

论文的出发点是：如果模型足够大，它在预训练阶段积累的知识是否足以让它直接「读懂」任务描述和少量示例，然后给出答案？

## 2. 核心想法：不更新参数，只给提示

GPT-3 的评估方式和之前所有大模型都不一样。它定义了三种设置，全部不涉及梯度更新：

**少样本（Few-Shot）**：给模型一段任务描述，加上 10 到 100 个示例（具体数量取决于上下文窗口能装多少），然后让它完成新的输入。不更新权重，不做反向传播。

**单样本（One-Shot）**：只给一个示例。这最接近人类学习新任务的方式：有人给你演示一次，你就上手。

**零样本（Zero-Shot）**：连示例都不给，只有一句自然语言指令。这是最难的设置，但也是最实用的：如果模型真的「理解」了任务本身，它不应该需要任何例子。

```python
from dataclasses import dataclass
from typing import Union


@dataclass
class ZeroShot:
    instruction: str
    prompt: str


@dataclass
class OneShot:
    instruction: str
    example: tuple[str, str]
    prompt: str


@dataclass
class FewShot:
    instruction: str
    examples: list[tuple[str, str]]
    prompt: str


EvalSetting = Union[ZeroShot, OneShot, FewShot]


def build_prompt(setting: EvalSetting) -&gt; str:
    if isinstance(setting, ZeroShot):
        return f&quot;{setting.instruction}\n{setting.prompt}&quot;

    if isinstance(setting, OneShot):
        example_input, example_output = setting.example
        return f&quot;{setting.instruction}\n{example_input} {example_output}\n{setting.prompt}&quot;

    lines = [setting.instruction]
    lines.extend(f&quot;{example_input} {example_output}&quot; for example_input, example_output in setting.examples)
    lines.append(setting.prompt)
    return &quot;\n&quot;.join(lines)
```

论文把这种能力叫做**上下文学习**：模型在预训练时，从海量文本中隐式地学到了各种任务的模式；推理时，示例被拼接进上下文，模型在前向传播的过程中「识别」出当前任务是什么，然后完成它。论文用「元学习」来描述这个过程：预训练是外循环，上下文学习是内循环。

这和微调的区别是根本性的。微调修改模型参数来适应任务，上下文学习不修改任何东西：同一个模型，同一组权重，只靠输入文本的不同，就能切换任务。

## 3. 模型架构与规模

GPT-3 的架构本身没有新发明。它和 GPT-2 一样，就是 [Transformer](/zh-hans/posts/attention-is-all-you-need/) 的解码器部分，一层层堆起来。改动只有一处：在 Transformer 层中交替使用稠密注意力和局部带状稀疏注意力（来自 Sparse Transformer）。

真正不同的是规模。论文训练了 8 个不同大小的模型，参数量跨越三个数量级：

| 模型 | 参数量 | 层数 | 隐藏维度 | 注意力头数 |
|------|--------|------|----------|-----------|
| GPT-3 Small | 1.25 亿 | 12 | 768 | 12 |
| GPT-3 Medium | 3.5 亿 | 24 | 1024 | 16 |
| GPT-3 Large | 7.6 亿 | 24 | 1536 | 16 |
| GPT-3 XL | 13 亿 | 24 | 2048 | 24 |
| GPT-3 2.7B | 27 亿 | 32 | 2560 | 32 |
| GPT-3 6.7B | 67 亿 | 32 | 4096 | 32 |
| GPT-3 13B | 130 亿 | 40 | 5140 | 40 |
| **GPT-3 175B** | **1750 亿** | **96** | **12288** | **96** |

1750 亿参数，96 层，96 个注意力头，隐藏维度 12288。上下文窗口 2048 个词元。这个规模在当时是前所未见的：比 GPT-2（15 亿参数）大了 100 多倍。

```python
from dataclasses import dataclass


@dataclass(frozen=True)
class GPT3Config:
    n_params: int
    n_layers: int
    d_model: int
    n_heads: int
    d_head: int
    d_ff: int
    n_ctx: int


def gpt3_175b() -&gt; GPT3Config:
    return GPT3Config(
        n_params=175_000_000_000,
        n_layers=96,
        d_model=12_288,
        n_heads=96,
        d_head=128,
        d_ff=49_152,
        n_ctx=2_048,
    )
```

论文训练这些模型的目的很明确：验证缩放定律（scaling laws）。之前 Kaplan 等人的研究（就是这篇论文的共同作者之一）已经表明，语言模型的损失和参数量之间存在平滑的幂律关系。GPT-3 把这个假设推到了 1750 亿参数的规模，看看上下文学习能力是否也遵循同样的规律。

答案是肯定的：模型越大，少样本学习的提升越陡。零样本性能随模型规模稳步上升，少样本性能的上升速度更快。这意味着大模型不只是「更准」，它们在利用上下文信息的效率上也更高。

## 4. 训练数据

GPT-3 在大约 3000 亿个词元上训练，数据来自五个来源：

| 数据集 | 词元数 | 训练占比 |
|--------|--------|----------|
| Common Crawl（过滤后） | 4100 亿 | 约 60% |
| WebText2 | 190 亿 | 约 22% |
| Books1 | 120 亿 | 约 8% |
| Books2 | 550 亿 | 约 8% |
| 英文 Wikipedia | 30 亿 | 约 3% |

注意一个关键细节：数据集的采样比例和它们的大小不成正比。质量更高的数据集（WebText2、Books、Wikipedia）被过采样了：WebText2 在训练中被看了 2.9 遍，Wikipedia 被看了 3.4 遍，而 Common Crawl 连一遍都没看完（0.44 遍）。论文有意用少量过拟合的代价，换取更高质量的训练信号。

Common Crawl 的原始数据有 45TB，经过三步处理：（1）基于与高质量参考语料的相似度做过滤；（2）文档级模糊去重；（3）混入已知的高质量数据集来增加多样性。过滤后剩下 570GB，约 4100 亿词元。

所有模型在 V100 GPU 上训练，使用微软提供的高带宽集群。

## 5. 实验结果

论文在二十多个数据集上做了评估，覆盖 9 大类任务。以下是几个关键结果。

**语言建模**：在 Penn Tree Bank 上，GPT-3 少样本困惑度（perplexity，衡量模型对文本的「意外程度」，越低越好）达到 20.50，刷新了当时的记录。在 LAMBADA（需要根据长距离上下文预测最后一个词）上，零样本准确率 76.2%，少样本 86.4%，大幅超过之前的最好结果。

**翻译**：GPT-3 从未被专门训练过翻译，但在法语→英语翻译上，少样本 BLEU 分数达到 32.6，超过了无监督神经机器翻译的最好结果。不过英语→法语方向（25.2 BLEU）和微调模型的差距仍然很大。一个有趣的发现：GPT-3 翻译成英语的能力明显强于从英语翻译出去，这和训练数据以英语为主有直接关系。

**闭卷问答**：在 TriviaQA 上，少样本准确率（exact match）71.2%，超过了同一闭卷设置下经过微调的模型。模型不看任何参考文档，纯靠参数里存储的知识回答问题。

**SuperGLUE**：在这个综合基准上，GPT-3 的少样本表现已经接近一些经过微调的强基线，但仍落后于当时最强的专门微调系统。

**合成任务**：论文还设计了一些专门测试上下文学习能力的新任务。比如给模型几个「造新词」的例子（定义一个不存在的词，然后用它造句），GPT-3 能正确地学会并使用这个新词。再比如三位数加法，少样本准确率接近 100%（两位数加法也几乎完美），但四五位数时急剧下降。

```python
from typing import Callable, Protocol


class AutoregressiveModel(Protocol):
    def forward(self, tokens: list[int]) -&gt; list[list[float]]:
        ...


def in_context_learning(
    model: AutoregressiveModel,
    examples: list[tuple[str, str]],
    query: str,
    tokenize: Callable[[str], list[int]],
    decode: Callable[[list[int]], str],
    sample_from: Callable[[list[float]], int],
    eos_token: int,
) -&gt; str:
    prompt_lines = [f&quot;{example_input} {example_output}&quot; for example_input, example_output in examples]
    prompt_lines.append(query)
    prompt = &quot;\n&quot;.join(prompt_lines)

    context = tokenize(prompt)
    output_tokens: list[int] = []

    while True:
        logits = model.forward(context)
        next_token = sample_from(logits[-1])
        if next_token == eos_token:
            break
        output_tokens.append(next_token)
        context.append(next_token)

    return decode(output_tokens)
```

## 6. 数据污染问题

论文在第四章花了大量篇幅讨论一个棘手的问题：训练数据和测试数据的重叠。

GPT-3 的训练数据包含大量互联网文本，而很多测试基准的内容也在互联网上公开存在。这意味着模型可能在训练时就「看过」了测试题。论文团队尝试在训练前移除这些重叠，但由于一个处理流程中的 bug，部分重叠没有被完全清除。而重新训练一遍的成本太高，不现实。

他们的做法是：为每个基准构建一个「干净子集」（移除所有和训练数据有 13-gram 重叠的样本），然后对比模型在完整集和干净子集上的表现。结论是：大多数基准上，污染对结果的影响很小。但 PIQA 和 Winograd 两个数据集存在可疑的表现下降，论文对这些结果加了星号标注。

这种诚实在当时相当罕见。多数论文对数据污染问题避而不谈。GPT-3 不仅主动调查，还开发了系统化的检测工具。这本身就是对后续研究的一个贡献。

## 7. 局限性

论文在第五章对自身局限性的讨论相当坦率。

**文本连贯性**：GPT-3 在文档级别仍然会出现语义重复、自相矛盾、甚至生成无意义句子的情况。生成质量虽然比 GPT-2 好了很多，但长文本的连贯性仍然不够。

**常识物理**：GPT-3 对「把奶酪放进冰箱，它会融化吗？」这类常识物理问题表现不佳。它能处理语言层面的推理，但对物理世界的理解仍然是肤浅的。

**单向性的代价**：作为自回归模型，GPT-3 只能从左往右看。论文承认，在需要双向上下文的任务上（比如判断两个句子里同一个词的含义是否相同），GPT-3 的少样本表现不如经过微调的双向模型。这说明在 GPT-3 的自回归设定下，这类任务并不是它的强项；单向建模目标本身会带来结构性偏好。

**采样效率**：GPT-3 在预训练阶段看了约 3000 亿个词元，远超人类一生接触的文本量。论文明确指出，即使少样本学习在推理时很高效，预训练的数据需求仍然巨大。

**推理成本**：1750 亿参数的模型，推理成本高且不方便部署。论文提到蒸馏（distillation，用大模型的输出来训练小模型）是一个可能的方向，但在千亿参数量级上还没有尝试过。

## 8. 社会影响

论文用了整整一个章节（第六章）讨论社会影响，涵盖三个方面。

**滥用风险**：GPT-3 生成的新闻文章，人类评估者的识别准确率接近随机猜测（约 52%）。模型越强，生成的虚假文本越难辨别。论文团队表示已经在监控论坛和聊天群，追踪恶意使用的趋势。

**偏见**：论文用大量实验测试了 GPT-3 在性别、种族和宗教方面的偏见。例如，在职业-性别关联测试中，GPT-3 更倾向于将「nurse」和女性关联、将「banker」和男性关联。在宗教-情感关联中，「Islam」更多地与暴力相关词共现。论文承认这些偏见来自训练数据，但没有给出解决方案。

**能源消耗**：训练 GPT-3 需要大量算力，论文引用了估算数据但没有公布具体的能耗数字。不过论文指出，一旦训练完成，模型可以被多次使用到不同任务上，比为每个任务单独训练模型更节能。

## 9. 我的思考

读完这篇论文，有几个感受。

第一，GPT-3 展示了一件事：规模能把上下文学习推到可用阈值。1750 亿参数的模型不只是「更大的 GPT-2」，它在上下文学习上的表现比小模型强出了一个量级。模型在没有任何参数更新的情况下，仅靠上下文中的几个示例就能完成新任务。这种能力不是显式手工设计出来的，而是在规模扩大过程中逐步增强，到 GPT-3 这个量级才第一次变得足够清晰、足够实用。BERT 证明了预训练的价值，GPT-3 证明了规模的价值。

第二，论文的写作方式值得注意。31 个作者，75 页篇幅，用了大量实验来回答一个简单的问题：更大的模型是否更善于利用少量示例？他们没有回避局限性：文本连贯性、常识推理、数据污染、偏见：全部正面讨论。这种严谨程度，在后来的大模型论文中反而越来越少见了。

第三，这篇论文的作者列表就是一部 AI 行业分裂史。Dario Amodei 和 Jared Kaplan 后来创立了 Anthropic（Claude 的开发商），Ilya Sutskever 后来离开 OpenAI 创立了 SSI。这些人在 2020 年还在同一个团队里合作写论文，两年后就走向了不同的方向。论文里关于社会影响和安全风险的讨论，也许正是后来分歧的伏笔。

第四，从技术演进的角度看，GPT-3 是从「预训练 + 微调」到「预训练 + 提示」的转折点。BERT 的思路是：先学通用知识，再在每个任务上微调参数。GPT-3 说：如果模型够大，微调这一步可以省掉：直接用自然语言告诉模型你要做什么。这个思路后来演化成了 ChatGPT、Claude 等产品的核心交互范式：用户用自然语言提问，模型直接回答。

从 Seq2Seq 的编码-解码，到 [Bahdanau 注意力](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)的「该看哪里」，到 [Transformer](/zh-hans/posts/attention-is-all-you-need/) 的「所有位置同时看」，到 [BERT](/zh-hans/posts/bert/) 的「先学再调」，再到 GPT-3 的「大到不用调」：每一步都在减少人工干预，增加模型自主完成任务的能力。

GPT-3 不是终点。但它第一次让人们认真思考一个问题：如果继续把模型做大，还会涌现出什么？

这个问题的答案，就是后来发生的一切。

---

**论文共读系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-hans/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神经网络进行序列到序列学习） — 编码器-解码器范式的确立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通过联合学习对齐与翻译实现神经机器翻译） — 注意力机制的起源
- [《Attention Is All You Need》](/zh-hans/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成为主角，Transformer 的诞生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-hans/posts/bert/)（BERT：用于语言理解的深度双向 Transformer 预训练） — 预训练范式的确立
- [《Scaling Laws for Neural Language Models》](/zh-hans/posts/scaling-laws-for-neural-language-models/)（神经语言模型的缩放定律） — 规模的数学：为什么更大的模型可预测地更好
- [《Training Compute-Optimal Large Language Models》](/zh-hans/posts/training-compute-optimal-large-language-models/)（训练计算最优的大语言模型） — 怎样花算力最划算</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>gpt-3</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>OpenClaw 专题：生态分析 🦞</title><link>https://justinhuangai.github.io/zh-hans/posts/openclaw-ecosystem/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/openclaw-ecosystem/</guid><description>从一个开源项目到一个完整的 AI 助手生态</description><pubDate>Tue, 03 Feb 2026 08:09:32 GMT</pubDate><content:encoded>![OpenClaw](/images/openclaw-logo-text-dark.webp)

## 0. 先认几个词

如果你第一次看“生态分析”这类文章，可以先记住 5 个词：

- `生态`：不只是一个仓库，而是一组围绕同一核心能力长出来的产品和工具。
- `运行时 / runtime`：Agent 真正长期跑着、负责调度和执行的核心进程。
- `技能市场`：给 Agent 安装新能力的地方，有点像 App Store。
- `工作流引擎`：把重复的多步骤任务封装成可以反复调用的流程。
- `飞轮`：一种增长回路；供给越好，使用越多，反过来又吸引更多供给。

## 1. 先看人

看项目先看人。

Peter Steinberger，奥地利人。2011 年创办 PSPDFKit，做 PDF 底层技术，客户有 Apple 和 Dropbox，服务了超 10 亿台设备。结果？外界的公开说法是「九位数级别」。然后他退居幕后。

然后他倦怠了。自己说「盯着屏幕发呆，写不出代码」。买了张去马德里的单程票，彻底断开了一阵子。

2025 年中，他在博客里写：状态回来了。AI 已经不是 demo 阶段了，能出产品原型了。他用大约一小时提示词生成了项目雏形，11 月发布，取名 Clawdbot：向 Anthropic 的 Claude 致敬（Claw = 爪子），选了龙虾当吉祥物。

2026 年 1 月底，Anthropic 发了商标警告，名字和 Claude 太像。三天内从 Clawdbot 改成 Moltbot（Molt = 蜕壳），又改成 OpenClaw。改名这件事本身引爆了关注：48 小时新增 34,000 星。

一个做出过九位数结果的人，把精力押在一个 MIT 开源项目上。动机且不论，这个选择本身值得认真看。

## 2. 不只是一个项目，已经长出生态

大部分爆火开源项目的「生态」约等于：文档里一张路线图 + 几个占位仓库。

OpenClaw 不一样。它已经长出了真实的产品分层：

| 组件 | 干什么 | 收藏数 |
|------|------|--------|
| **OpenClaw** | 核心 Agent 运行时：大脑 | 140k+ |
| **ClawHub** | 技能市场：Agent 的应用商店 | 5.4k |
| **Lobster** | 工作流引擎：把重复操作打包成一键执行 | ~800 |
| **acpx** | 无头命令行工具 | ~780 |
| **openclaw-ansible** | 自动化部署：一条命令装好 | ~490 |
| **nix-openclaw** | Nix 声明式配置 | ~530 |

运行时、技能市场、工作流引擎、部署工具：各管各的，职责边界干净。这不是「把所有东西塞进一个仓库」的粗暴扩张，是有设计的分层。

## 3. 几个关键判断

### 渠道覆盖：不是炫技，是「不用你动」

WhatsApp、Telegram、Slack、Discord、Signal、iMessage、飞书、LINE、Matrix……二十多个平台直接接入。

这意味着什么？你不用为了用 AI 助手下载一个新 App、学一套新界面、改变任何习惯。它直接出现在你已经在用的聊天里。WhatsApp 里发一条消息就能用：跟给朋友发消息一样。

渠道越多，离真实使用场景越近，第一次用起来的门槛就越低。这种「低摩擦接入」带来的不只是方便，更是采用率的碾压优势。底层当然有协议适配和维护成本，但那是代价，不是重点。重点是它把 AI 助手从「一个你要去打开的新工具」变成了「已经在你聊天里的能力」。

### ClawHub：方向对，但还太早

技能市场用向量搜索做语义匹配：你不用翻分类目录，直接跟它说「我想要一个能发邮件的技能」，它就能帮你找到。设计意图很好。

但技能市场的飞轮能不能转起来，取决于两件事：优质技能够不够多，发现效率够不够高。后者已经做到了。前者呢？第三方技能的发布频率、安装量、更新频率、审核情况：这些数据目前都没有公开的仪表盘可查。飞轮开始转了，但离自己能转还早。

### Lobster：解决了一个隐藏痛点

AI Agent 最大的隐性成本是什么？重复规划。

每次让 Agent 干一个多步骤任务，它都要从头想一遍：先干啥、再干啥、怎么干。token 就是这么烧掉的。Lobster 的思路是：把高频操作打包成「一键执行」的流水线：封装一次，以后直接调用，不用重新规划。

它还有个审批关卡：关键步骤会停下来等你点头，不让 Agent 一路狂奔。这说明团队对「Agent 该有多少自主权」这件事，至少认真想过。

### 安全：绕不过去的坎

Kaspersky 审计了 512 个漏洞，8 个严重级别（审计时还叫 Clawdbot）。Cisco 安全团队直接称之为「安全噩梦」。Gary Marcus 公开说这是「一场注定发生的灾难」。

问题的本质是结构性的：你给 Agent 的权限越大，它能干的事越多，但攻击面也越大。提示词注入可以骗它干坏事，已经有技能被发现偷偷往外传数据。

OpenClaw 不是没意识到：配对码、白名单、沙箱隔离、命令审批，一层一层在收紧。但「高权限 Agent + 第三方技能 + 二十多个入口」这个组合带来的结构性张力，不是修几个 bug 就能消除的。这是一场持久战。

## 4. 风险，不说不行

说完好的，说不好的。

**安全债 ≠ 技术债，是信任债。** 一次出圈的安全事件，受伤的不只 OpenClaw，是所有走「自托管 AI Agent」路线的项目。整个赛道的叙事都会被拖下水。

**商业模式是个问号。** MIT 协议、无订阅、用户自带 API 密钥。Peter 在公开场合提过，项目每月服务器成本在一两万美元。靠赞助撑的开源项目，可持续性取决于热度能不能变成钱。目前还没看到清晰路径。

**增长质量存疑。** 1 月 30 日的爆发跟 Moltbook（AI Agent 社交网络）的走红高度绑定。病毒式传播带来的星标，有多少会变成真正的生态贡献者？星标 ≠ 代码贡献 ≠ 生态厚度。

**单点依赖。** 18,000+ 次提交，核心路线和产品判断仍然高度依赖创始人一个人。社区已经加了维护者，但能不能从「一个人的项目」变成「一群人的平台」，是后面真正的分水岭。

## 5. 结论

OpenClaw 真正稀缺的地方不是热度：热度谁都能有一阵子。稀缺的是它已经从一个项目长出了生态的雏形：有分层、有分工、有产品形态。

但热度终究会退。

退潮之后，决定命运的是 4 件事：

1. **安全收紧了没有？** 配对、白名单、沙箱这些机制，从「可选」变成「默认」了吗？
2. **技能生态转起来了没有？** 不看星标数，看高质量技能的发布、安装和审核有没有形成正循环。
3. **非创始人贡献占比涨了没有？** 这决定它是「明星项目」还是「可持续平台」。
4. **治理结构清晰了没有？** 这决定它能不能从热度项目变成长期基础设施。

我会持续跟踪。</content:encoded><category>OpenClaw</category><category>AI</category><category>open-source</category><category>openclaw</category></item><item><title>论文共读：《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》（BERT：用于语言理解的深度双向 Transformer 预训练）</title><link>https://justinhuangai.github.io/zh-hans/posts/bert/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/bert/</guid><description>预训练范式的确立，附真实 Python 代码</description><pubDate>Sat, 31 Jan 2026 08:52:21 GMT</pubDate><content:encoded>2018 年 10 月 11 日，Google AI Language 团队在 arXiv（一个学术论文预印本网站，论文不用等期刊审稿就能直接发布）上传了一篇论文：[《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/papers/1810.04805v2.pdf)（BERT：用于语言理解的深度双向 Transformer 预训练）。

作者是 Jacob Devlin、Ming-Wei Chang、Kenton Lee 和 Kristina Toutanova，四人均来自 Google。Devlin 此前在微软研究院工作，加入 Google 后主导了 BERT 的设计和实现。

BERT 的全名是 Bidirectional Encoder Representations from Transformers：来自 Transformer 的双向编码器表示。它做了一件在当时看来非常大胆的事：先在海量无标注文本上做通用预训练，然后只需要加一层输出层、在具体任务上做少量微调，就能拿到最优结果。

这个「先预训练、再微调」的范式，后来成为了整个 NLP 领域的标准做法。GPT 系列也用了类似的思路，但走的是另一条路：单向生成。BERT 选择了双向理解。两条路后来各自发展出了庞大的模型家族。

## 0. 先认几个词

如果你对大模型训练流程还不熟，可以先记住这篇论文最关键的几个词：

- `Transformer`：BERT 用的基础架构。你可以先把它理解成一台能同时结合左右上下文来处理句子的机器。
- `预训练`：先在海量通用文本上学语言本身，而不是一上来就做具体任务。
- `微调`：把预训练得到的能力，再稍微调整到某个具体任务上。
- `双向`：预测一个位置时，不只看左边，也看右边。
- `MLM / 遮蔽语言模型`：故意遮住一部分词，让模型根据上下文把它们猜出来。
- `NSP / 下一句预测`：让模型判断两个句子是不是前后相连。

## 1. 要解决什么问题

2018 年，NLP 领域有一个尴尬的现状：每个任务都需要从头设计专门的模型架构。做问答要一套模型，做情感分析要另一套，做命名实体识别又要一套。每个任务的标注数据都不多，训练出来的模型也很难迁移到其他任务。

当时已经有人尝试过预训练的思路。ELMo 用双向 LSTM 学习上下文表示，但它只是把预训练的特征「拼」到下游模型上，架构本身还是任务专用的。OpenAI GPT 用 Transformer 做预训练再微调，但它只能从左往右看（单向），每个词只能关注它前面的词，看不到后面的。

论文认为，单向语言模型在需要深度双向上下文的语言理解任务上存在明显限制。比如：

&gt; &quot;他拿起了 _____ ，开始演奏。&quot;

如果只看左边（&quot;他拿起了&quot;），填空的答案可能是任何东西。但看到右边（&quot;开始演奏&quot;），你立刻知道是某种乐器。对很多语言理解任务来说，双向上下文天然更有利。

## 2. 核心想法：遮住一些词，让模型猜

BERT 的解法很直觉：既然双向语言模型没法用传统方式训练（因为每个词会间接「看到自己」），那就换个训练目标。

**遮蔽语言模型（Masked Language Model，MLM）**：随机遮住输入中 15% 的词：具体做法是把它们替换成一个特殊标记 \[MASK\]：然后让模型根据上下文猜出被遮住的词。这个想法来自心理学中的完形填空（Cloze task，1953 年 Taylor 提出），就像上面那道填空题一样。

遮住之后，模型必须同时利用左边和右边的上下文来预测，双向理解就自然产生了。

但直接把所有被选中的词替换成 \[MASK\] 标记会引入一个问题：微调时输入里不会出现 \[MASK\]，预训练和微调之间产生了不匹配。论文的解决方案：被选中的 15% 的词里，80% 替换成 \[MASK\]，10% 替换成随机词，10% 保持不变。这样模型不能只靠「看到 \[MASK\] 就知道要预测」，而是必须对每个位置都保持理解能力。

```python
import random
from typing import Optional, Sequence


def mask_tokens(
    tokens: Sequence[str],
    mask_prob: float = 0.15,
    vocab: Optional[Sequence[str]] = None,
) -&gt; tuple[list[str], list[int], list[str]]:
    if vocab is None:
        vocab = tokens

    masked = list(tokens)
    positions: list[int] = []
    labels: list[str] = []

    for i, token in enumerate(tokens):
        if random.random() &lt; mask_prob:
            positions.append(i)
            labels.append(token)

            r = random.random()
            if r &lt; 0.8:
                masked[i] = &quot;[MASK]&quot;
            elif r &lt; 0.9:
                masked[i] = random.choice(vocab)

    return masked, positions, labels
```

## 3. 第二个预训练任务：下一句预测

很多 NLP 任务（比如问答、自然语言推理）需要理解两个句子之间的关系，但语言模型本身不直接建模这种关系。

论文加了第二个预训练任务：**下一句预测（Next Sentence Prediction，NSP）**。给模型两个句子 A 和 B，50% 的情况下 B 是 A 的真实下一句，50% 的情况下 B 是从语料库里随机抽的。模型要判断 B 是不是 A 的下一句。

这个任务的设计很简单，但论文的消融实验（ablation study，逐一去掉某个组件看效果变化）显示，去掉 NSP 会明显降低问答和自然语言推理任务的表现；不过后来也有工作（如 RoBERTa）对 NSP 的必要性提出了不同结论。

```python
from dataclasses import dataclass


@dataclass
class PretrainingExample:
    tokens: list[str]
    segment_ids: list[int]
    masked_positions: list[int]
    masked_labels: list[str]
    is_next: bool
```

## 4. 模型架构

BERT 的架构其实没有什么新发明。它就是 [Transformer](/zh-hans/posts/attention-is-all-you-need/) 的编码器部分，一层层堆起来。

论文给出了两个规格：

- **BERT_BASE**：12 层，隐藏维度 768，12 个注意力头，参数量 1.1 亿
- **BERT_LARGE**：24 层，隐藏维度 1024，16 个注意力头，参数量 3.4 亿

BERT_BASE 的参数量和 OpenAI GPT 差不多，方便直接对比。两者最关键的区别只有一个：GPT 用的是单向注意力（每个词只能看左边），BERT 用的是双向注意力（每个词能看到所有位置）。

输入的表示由三部分相加构成：

- **词嵌入（Token Embedding）**：WordPiece 分词，词表 30,000
- **段嵌入（Segment Embedding）**：标记这个词属于句子 A 还是句子 B
- **位置嵌入（Position Embedding）**：告诉模型词的位置（BERT 用的是学习得到的位置编码，不是正弦余弦）

每个输入序列的开头都加一个特殊标记 \[CLS\]，它在最后一层的隐藏状态被用来做句子级别的分类（比如 NSP、情感分析）。两个句子之间用 \[SEP\] 分隔。

```python
import torch
from torch import nn


class BertEmbeddings(nn.Module):
    def __init__(
        self,
        vocab_size: int,
        hidden_size: int,
        max_positions: int,
        type_vocab_size: int = 2,
        dropout: float = 0.1,
    ) -&gt; None:
        super().__init__()
        self.token_embedding = nn.Embedding(vocab_size, hidden_size)
        self.segment_embedding = nn.Embedding(type_vocab_size, hidden_size)
        self.position_embedding = nn.Embedding(max_positions, hidden_size)
        self.layer_norm = nn.LayerNorm(hidden_size, eps=1e-12)
        self.dropout = nn.Dropout(dropout)

    def forward(
        self,
        token_ids: torch.Tensor,
        segment_ids: torch.Tensor,
    ) -&gt; torch.Tensor:
        position_ids = torch.arange(token_ids.size(1), device=token_ids.device).unsqueeze(0)
        position_ids = position_ids.expand_as(token_ids)
        embeddings = (
            self.token_embedding(token_ids)
            + self.segment_embedding(segment_ids)
            + self.position_embedding(position_ids)
        )
        embeddings = self.layer_norm(embeddings)
        return self.dropout(embeddings)


class BertModel(nn.Module):
    def __init__(
        self,
        vocab_size: int,
        hidden_size: int = 768,
        max_positions: int = 512,
        num_layers: int = 12,
        num_heads: int = 12,
        dropout: float = 0.1,
    ) -&gt; None:
        super().__init__()
        self.embeddings = BertEmbeddings(vocab_size, hidden_size, max_positions, dropout=dropout)
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=hidden_size,
            nhead=num_heads,
            dim_feedforward=4 * hidden_size,
            dropout=dropout,
            activation=&quot;gelu&quot;,
            batch_first=True,
        )
        self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)

    def forward(
        self,
        token_ids: torch.Tensor,
        segment_ids: torch.Tensor,
    ) -&gt; torch.Tensor:
        hidden = self.embeddings(token_ids, segment_ids)
        return self.encoder(hidden)
```

## 5. 微调：一个模型适配所有任务

BERT 最优雅的地方在于微调的简单性。预训练完成后，不管什么下游任务，做法几乎一样：在 BERT 上面加一层任务相关的输出层，然后用少量标注数据微调所有参数。

- **文本分类**（情感分析、自然语言推理）：取 \[CLS\] 位置的输出向量，接一个线性分类器
- **问答**（给一段文章，找出答案的起止位置）：对每个词的输出向量做两次线性变换，分别预测答案的开始和结束位置
- **序列标注**（命名实体识别）：对每个词的输出向量接一个分类器，逐词预测标签

预训练可能需要几天，但微调通常只要几十分钟到几小时（单块 TPU 上大部分任务不超过 1 小时）。这个效率差异是「预训练 + 微调」范式的核心吸引力。

## 6. 实验结果

论文在 11 个 NLP 任务上做了实验，全部刷新了当时的记录。

**GLUE 基准**（通用语言理解评估，包含 8 个子任务）：
- BERT_LARGE 平均分 80.5%，比之前最好的 OpenAI GPT 高出 7.7 个百分点
- 在最大的子任务 MNLI 上提升了 4.6%

**SQuAD v1.1**（阅读理解问答，Test F1）：
- BERT_LARGE 单模型 + TriviaQA 数据：F1 91.8，超过人类表现（91.2）
- BERT_LARGE 集成模型 + TriviaQA 数据：F1 93.2

**SQuAD v2.0**（包含无法回答的问题）：
- F1 达到 83.1，比之前最好的系统高出 5.1 个百分点

**SWAG**（常识推理）：
- 准确率 86.3%，比 OpenAI GPT 高出 8.3 个百分点

论文还做了模型大小的消融实验，发现一个重要结论：更大的模型在所有任务上都更好，即使在标注数据很少（只有 3,600 条）的任务上也是如此。这和当时的直觉（小数据集容易过拟合大模型）不太一样，说明预训练提供的知识可以有效缓解小数据集的过拟合（模型把训练数据「死记硬背」，对新数据表现差）风险。

## 7. 训练细节

**预训练数据**：BooksCorpus（8 亿词）+ 英文 Wikipedia（25 亿词），只使用文本段落，去掉了列表、表格和标题。论文强调必须用文档级语料而不是打乱的句子级语料，这样才能提取长距离的上下文关系。

**分词**：WordPiece，词表大小 30,000。WordPiece 会把不常见的词拆成更小的子词单元，比如 &quot;playing&quot; 可能被拆成 &quot;play&quot; + &quot;##ing&quot;。

**优化器**：Adam，学习率 1e-4，前 10,000 步线性热身，然后线性衰减。批次大小 256 个序列，最大序列长度 512。

**硬件**：BERT_BASE 在 4 块 Cloud TPU（16 块 TPU 芯片）上训练 4 天。BERT_LARGE 在 16 块 Cloud TPU（64 块 TPU 芯片）上训练 4 天。

**Dropout**：所有层的 dropout 率为 0.1。激活函数用的是 GELU（Gaussian Error Linear Unit），而不是 Transformer 原版的 ReLU。

## 8. 我的思考

读完这篇论文，有几个感受。

第一，BERT 的真正贡献不是模型架构（它就是 Transformer 编码器），而是训练方法。遮蔽语言模型这个想法看起来简单，但它巧妙地解决了一个根本矛盾：怎么在不让模型「作弊」的前提下，同时利用双向上下文。80/10/10 的遮蔽策略更是精心设计的，解决了预训练和微调之间的不匹配问题。

第二，BERT 和 GPT 的分野在这篇论文里就很清楚了。GPT 的自回归目标更天然适合生成；BERT 的双向编码更适合判别式语言理解任务。后来 GPT 走向了更大的规模和更强的生成能力，BERT 则衍生出了 RoBERTa、ALBERT、DeBERTa 等一系列理解型模型。两条路线至今仍在各自的领域里发挥作用。

第三，「预训练 + 微调」这个范式的影响远超 NLP。计算机视觉后来也全面转向了类似的思路（ViT、MAE），甚至多模态模型（CLIP、GPT-4V）也是在大规模预训练的基础上做微调或提示。BERT 不是第一个做预训练的，但它是第一个用如此简洁的方式，把预训练从一种有用技巧，推进成了 NLP 的主流工作范式。

第四，用真实 Python 重写 BERT 的输入处理时，你会感受到它的设计有多工整。\[CLS\] + 句子A + \[SEP\] + 句子B + \[SEP\]，配上三种嵌入相加，整个流程可以用一套统一的代码处理分类、问答、序列标注等完全不同的任务。这种「一个模型适配所有任务」的简洁性，是它真正的力量所在。

这篇论文的标题里有一个词很关键：Pre-training。在 BERT 之前，每个 NLP 任务都在从零开始学。BERT 证明了一件事：语言的通用知识可以先学好，然后迁移到几乎任何任务上。

这个想法改变了整个领域的工作方式。

---

**论文共读系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-hans/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神经网络进行序列到序列学习） — 编码器-解码器范式的确立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通过联合学习对齐与翻译实现神经机器翻译） — 注意力机制的起源
- [《Attention Is All You Need》](/zh-hans/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成为主角，Transformer 的诞生
- [《Scaling Laws for Neural Language Models》](/zh-hans/posts/scaling-laws-for-neural-language-models/)（神经语言模型的缩放定律） — 规模的数学：为什么更大的模型可预测地更好
- [《Language Models are Few-Shot Learners》](/zh-hans/posts/language-models-are-few-shot-learners/)（语言模型是少样本学习者） — 更大的模型，更善于从上下文中诱发能力
- [《Training Compute-Optimal Large Language Models》](/zh-hans/posts/training-compute-optimal-large-language-models/)（训练计算最优的大语言模型） — 怎样花算力最划算</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>bert</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>论文共读：《Sequence to Sequence Learning with Neural Networks》（使用神经网络进行序列到序列学习）</title><link>https://justinhuangai.github.io/zh-hans/posts/sequence-to-sequence-learning-with-neural-networks/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/sequence-to-sequence-learning-with-neural-networks/</guid><description>编码器-解码器范式的确立，附真实 Python 代码</description><pubDate>Sat, 24 Jan 2026 08:41:08 GMT</pubDate><content:encoded>2014 年 9 月 10 日，三个 Google 研究员在 arXiv（一个学术论文预印本网站，论文不用等期刊审稿就能直接发布）上传了一篇论文：[《Sequence to Sequence Learning with Neural Networks》](/papers/1409.3215v3.pdf)（使用神经网络进行序列到序列学习）。

作者是 Ilya Sutskever、Oriol Vinyals 和 Quoc V. Le，均来自 Google。Sutskever 是 AlexNet 的作者之一，与 Alex Krizhevsky、Geoffrey Hinton 合作完成了那篇引爆深度学习的计算机视觉论文，后来成为 OpenAI 联合创始人之一；Vinyals 后来在 DeepMind 主导了 AlphaStar（星际争霸 AI）；Quoc V. Le 则在 Google 推动了 AutoML 等研究。

这篇论文做了一件看似简单的事：用一个神经网络读完一句话，压成一个向量，再用另一个神经网络从这个向量里生成翻译。输入和输出可以长度不同、语言不同、结构不同。这个框架有一个名字，叫「序列到序列」（Sequence to Sequence，简称 Seq2Seq）。

它确立了编码器-解码器（Encoder-Decoder）这个范式。后来 [Bahdanau 在此基础上加入了注意力机制](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)，再后来 [Vaswani 等人用 Transformer 把整个架构重写](/zh-hans/posts/attention-is-all-you-need/)。但起点，是这篇论文。

## 0. 先认几个词

如果你没有机器学习背景，可以先按这篇论文的工作流，记住下面几个词：

- `Seq2Seq / 序列到序列`：把一段输入序列直接变成另一段输出序列，比如把英文句子变成法文句子。
- `Encoder / 编码器`：负责把输入从头到尾读完。
- `Decoder / 解码器`：负责把输出一个词一个词写出来。
- `RNN / 循环神经网络`：一种只能按顺序处理文本的旧架构。
- `LSTM`：RNN 的改良版，更擅长在长句子里记住前面的信息。
- `向量 / vector`：你可以先把它理解成“用一串数字压缩出来的一份摘要”。

## 1. 要解决什么问题

2014 年，深度神经网络已经在图像识别等任务上取得突破，但像机器翻译这种「直接把一段可变长序列映射到另一段可变长序列」的任务，神经网络还不擅长。

一句英语可能是 5 个词，翻译成法语变成 7 个词。输入和输出的长度不同，而且没有简单的一一对应关系。

传统的解决方案是把大量人工设计的规则和统计特征拼在一起，形成一个复杂的翻译流水线（统计机器翻译，SMT）。它能用，但每个组件都要单独调参，而且很难整体优化。

论文提出了一个更简洁的思路：能不能用一个端到端的神经网络，直接从源语言序列映射到目标语言序列？

## 2. 核心架构：编码器-解码器

论文的方法可以用一句话概括：**一个 LSTM 读，另一个 LSTM 写。**

LSTM（Long Short-Term Memory，长短期记忆网络）是一种特殊的 RNN，专门设计来处理长距离依赖问题。普通 RNN 在序列很长时容易「遗忘」前面的内容，LSTM 通过引入门控机制（决定哪些信息保留、哪些丢弃）来缓解这个问题。

具体流程：

1. **编码器**（一个 4 层深度 LSTM）从头到尾读完源句子，把整个句子压缩成一组固定长度的最终状态，交给解码器作为起点
2. **解码器**（另一个 4 层深度 LSTM）以这个向量为起点，一个词一个词地生成目标语言的翻译，直到输出结束符号 \&lt;EOS\&gt;

论文给出的概率公式：

$$
p(y_1, \ldots, y_{T&apos;} \mid x_1, \ldots, x_T) = \prod_t p(y_t \mid v, y_1, \ldots, y_{t-1})
$$

翻译成人话：给定源句子 x，生成目标句子 y 的概率，等于每一步生成下一个词的概率连乘起来。每一步的预测都依赖两样东西：编码器压缩出来的向量 v，以及之前已经生成的所有词。

```python
import torch
from torch import nn


class Seq2Seq(nn.Module):
    def __init__(self, vocab_size: int, hidden_size: int) -&gt; None:
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, hidden_size)
        self.encoder = nn.LSTM(hidden_size, hidden_size, num_layers=4, batch_first=True)
        self.decoder = nn.LSTM(hidden_size, hidden_size, num_layers=4, batch_first=True)
        self.output_proj = nn.Linear(hidden_size, vocab_size)

    def encode(
        self,
        source_tokens: torch.Tensor,
    ) -&gt; tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:
        embedded = self.embedding(source_tokens)
        outputs, state = self.encoder(embedded)
        return outputs, state

    def decode(
        self,
        encoder_state: tuple[torch.Tensor, torch.Tensor],
        max_steps: int,
        bos_token_id: int,
        eos_token_id: int,
    ) -&gt; list[int]:
        prev_token = torch.tensor([[bos_token_id]], dtype=torch.long, device=encoder_state[0].device)
        state = encoder_state
        generated: list[int] = []

        for _ in range(max_steps):
            embedded = self.embedding(prev_token)
            output, state = self.decoder(embedded, state)
            logits = self.output_proj(output[:, -1, :])
            next_token_id = int(logits.argmax(dim=-1).item())
            if next_token_id == eos_token_id:
                break
            generated.append(next_token_id)
            prev_token = torch.tensor([[next_token_id]], dtype=torch.long, device=logits.device)

        return generated
```

架构本身不复杂。论文的贡献不在于发明了某个新组件，而在于证明了这个简单的框架真的能用，而且效果好到能和精心调校的传统系统竞争。

## 3. 三个关键设计决策

论文在实验中发现了三个对性能影响很大的设计选择：

**第一，用两个独立的 LSTM。** 编码器和解码器不共享参数。这样做稍微增加了参数量，但让模型能更好地分别处理源语言和目标语言的特性。论文提到这也让同时训练多个语言对成为可能。

**第二，用深层 LSTM。** 论文用了 4 层 LSTM，每多一层，困惑度降低近 10%。浅层 LSTM（1-2 层）的效果明显更差。深度给了模型更大的表示空间。

**第三，把源句子倒过来读。** 这是论文最出人意料的发现。把源句子 &quot;a, b, c&quot; 反转成 &quot;c, b, a&quot; 再喂给编码器，BLEU 分数从 25.9 跳到 30.6，提升了将近 5 分。

为什么反转有效？论文的解释是：正常顺序下，源句子第一个词离目标句子第一个词很远（中间隔了整个源句子）。反转之后，源句子的前几个词和目标句子的前几个词在时间上靠得很近，给梯度（模型用来调整参数的信号）创造了更多的「短距离依赖」，让优化变得更容易。

```python
import torch


def reverse_source(source_tokens: list[int]) -&gt; list[int]:
    return list(reversed(source_tokens))


source_sentence = [11, 23, 37, 42]
reversed_source = reverse_source(source_sentence)
source_tensor = torch.tensor([reversed_source], dtype=torch.long)
```

这个 trick 简单到几乎不像是正经的研究贡献，但它确实有效，而且揭示了一个深层问题：RNN 对序列里元素之间的距离很敏感，距离越近越容易学。这个问题后来被注意力机制从根本上解决了。

## 4. 实验结果

论文在 WMT &apos;14 英法翻译任务上做了实验。

关键数字：
- **单个反转 LSTM**，beam size 12：30.59 BLEU
- **5 个反转 LSTM 的集成**，beam size 2：34.50 BLEU
- **5 个反转 LSTM 的集成**，beam size 12：**34.81 BLEU**
- **传统短语翻译系统**（Moses baseline）：33.30 BLEU

在论文报告的实验设置下，5 个 LSTM 的集成以 34.81 分超过了传统短语翻译系统的 33.30 分。考虑到 LSTM 的词表只有 8 万词（遇到词表外的词只能输出 UNK），而传统系统的词表几乎不受限，这个结果很有说服力。

论文还用 LSTM 对传统系统的 1000-best 候选列表做重排序，BLEU 分数进一步提升到 36.5，接近当时的最佳公开结果（37.0）。

另一个值得注意的发现：相比当时其他神经方法，LSTM 在长句子上的性能退化没那么严重。这和当时其他研究者报告的长句子性能急剧下降形成了对比，论文将此归功于反转源句子的策略。

## 5. 模型的「理解力」

论文还做了一个有趣的可视化实验。把不同句子输入编码器，取出最终的隐藏状态向量，用 PCA 降维到二维平面上画出来。

结果显示：
- 意思相近的句子在向量空间里聚在一起
- 主动语态和被动语态的句子（&quot;I gave her a card&quot; vs &quot;I was given a card by her&quot;）落在相近的位置
- 词序不同但意思相同的句子也能被正确聚类

这至少说明，编码器学到的表示不只是简单的词袋统计（把词混在一起不管顺序），而是包含了相当多的句法和语义信息。

## 6. 训练细节

**模型规格**：4 层 LSTM，每层 1000 个单元，词嵌入维度 1000，总参数量 3.84 亿。其中 6400 万是纯循环连接参数。

**硬件**：8 块 GPU。每层 LSTM 分配一块 GPU，剩余 4 块 GPU 用来并行化 softmax（因为词表有 8 万个词，softmax 计算量很大）。训练约 10 天。

**优化器**：SGD，不带动量，初始学习率 0.7。训练 5 个 epoch 之后每半个 epoch 将学习率减半，总共训练 7.5 个 epoch。

**梯度裁剪**：当梯度的 L2 范数超过阈值 5 时，按比例缩小。这是为了防止梯度爆炸（梯度值突然变得极大，导致参数更新失控）。

**批次优化**：把长度相近的句子放在同一个批次里，避免短句子为长句子「陪跑」浪费计算资源，带来了 2 倍的训练加速。

## 7. 我的思考

读完这篇论文，有几个感受。

第一，这篇论文的野心很大但方法很简单。用一个 LSTM 读，另一个 LSTM 写，中间就靠一个向量传递信息。没有注意力，没有复杂的对齐机制，甚至没有任何关于语言结构的先验假设。然后它真的跑通了，而且效果强到足以和当时精心调校的传统系统竞争。这说明一个道理：在数据和算力充足的条件下，简单的端到端方法可以出乎意料地强。

第二，反转源句子这个发现很有启发性。它不是一个优雅的解法，更像是一个 hack。但它揭示了 RNN 的根本局限：对序列里元素之间的距离敏感。Bahdanau 的注意力机制让模型可以「跳着看」，不再受距离限制。Transformer 更进一步，完全抛弃了顺序处理，让任意两个位置之间的距离始终是 1。从反转到注意力到 Transformer，是同一个问题的三代解法。

第三，这篇论文和 Bahdanau 的论文几乎同时发表（都是 2014 年 9 月）。Sutskever 确立了编码器-解码器范式，Bahdanau 指出了固定长度向量的瓶颈并用注意力机制解决了它。两篇论文像是同一枚硬币的正反面：一面是框架，一面是修补框架最大缺陷的方法。

第四，用真实 Python 重写的过程中，你会感受到这个架构有多简洁。编码器就是循环读完，解码器就是循环写出来。但也正因为简洁，它的天花板很明显：所有信息必须挤过一个固定长度的向量。这个瓶颈在你写代码的时候会变得格外直观。

一个向量能装下多少信息？这是这篇论文暗含的问题。

对更长、更复杂的句子来说，不够。

于是后来有了注意力，有了 Transformer。

---

**论文共读系列**

- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通过联合学习对齐与翻译实现神经机器翻译） — 注意力机制的起源
- [《Attention Is All You Need》](/zh-hans/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成为主角，Transformer 的诞生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-hans/posts/bert/)（BERT：用于语言理解的深度双向 Transformer 预训练） — 预训练范式的确立
- [《Scaling Laws for Neural Language Models》](/zh-hans/posts/scaling-laws-for-neural-language-models/)（神经语言模型的缩放定律） — 规模的数学：为什么更大的模型可预测地更好
- [《Language Models are Few-Shot Learners》](/zh-hans/posts/language-models-are-few-shot-learners/)（语言模型是少样本学习者） — 更大的模型，更善于从上下文中诱发能力
- [《Training Compute-Optimal Large Language Models》](/zh-hans/posts/training-compute-optimal-large-language-models/)（训练计算最优的大语言模型） — 怎样花算力最划算</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>seq2seq</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>Clawdbot：一个值得关注的去中心化 AI 开源项目</title><link>https://justinhuangai.github.io/zh-hans/posts/clawdbot/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/clawdbot/</guid><description>一个把所有聊天渠道接入 AI Agent 的自托管平台</description><pubDate>Fri, 16 Jan 2026 08:34:57 GMT</pubDate><content:encoded>现在主流的 AI 助手，几乎都是中心化的。

你的对话、你的数据、你的上下文，最终都沉淀在别人的服务器里。你在使用 AI，但你并不真正拥有它。

[Clawdbot](https://github.com/clawdbot/clawdbot) 这个开源项目，走的恰恰是反方向。

它想做的，不是再造一个更聪明的聊天机器人，而是把 AI 的能力真正放回用户手中：跑在你自己的机器上，接入你已经在用的聊天工具，让数据、上下文和控制权都留在你自己这里。

我翻了一遍它的代码库：二十多万行 TypeScript，覆盖 macOS、iOS、Android 三端原生应用，外加 50 多个技能模块。

![WhatsApp 中与 Clawd 对话](/images/whatsapp-clawd.webp)

## 0. 先认几个词

如果你还没接触过这类项目，先记住 5 个词，这篇文章会更好读：

- `中心化 AI 助手`：聊天记录、记忆和控制权主要都留在平台方的服务器里。
- `自托管`：软件跑在你自己的机器或服务器上，部署权和数据权都在你手里。
- `AI Agent`：不只是会聊天的机器人，而是能记住上下文、调用工具、替你做事的一套系统。
- `聊天渠道`：WhatsApp、Telegram、Slack、iMessage 这类你已经在用的聊天入口。
- `开源`：代码公开，别人可以检查、修改，也可以自己部署。

这已经不是周末 Hackathon 式的“整活项目”了，而是一套按长期产品思路打磨出来的系统。很多细节里，都能看出作者在产品取舍上是有 taste 的。

更难得的是，它的边界感很好。

该有的能力都有；不该塞的东西，也没硬塞进去。没有为了“看起来更大”去堆砌功能，也没有很多 AI 项目常见的炫技感和失控感。

这种克制，比“什么都做一点”更难，也更说明问题。

所以我会觉得，Clawdbot 值得关注，不只是因为它是一个手艺很好的开源项目，更因为它代表了一种很少见、但越来越重要的方向：

不是把每个人都接到同一个 AI 平台上，  
而是让每个人都拥有属于自己的 AI 系统。

Clawdbot 这样的产品出现，就像大航海时代来临前，先拍上岸边的第一朵浪花。

浪花本身并不宣告什么。  
但它已经提前告诉你：潮水要来了。

那个每个人都拥有自己专属 AI 的时代，可能比我们以为的更近。</content:encoded><category>OpenClaw</category><category>AI</category><category>open-source</category></item><item><title>论文共读：《Neural Machine Translation by Jointly Learning to Align and Translate》（通过联合学习对齐与翻译实现神经机器翻译）</title><link>https://justinhuangai.github.io/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/</guid><description>注意力机制的起源，附真实 Python 代码</description><pubDate>Sun, 11 Jan 2026 08:26:19 GMT</pubDate><content:encoded>2014 年 9 月 1 日，三个人在 arXiv（一个学术论文预印本网站，论文不用等期刊审稿就能直接发布）上传了一篇论文：[《Neural Machine Translation by Jointly Learning to Align and Translate》](/papers/1409.0473v7.pdf)（通过联合学习对齐与翻译实现神经机器翻译）。

这三个人是 Dzmitry Bahdanau、KyungHyun Cho 和 Yoshua Bengio，来自蒙特利尔大学。Yoshua Bengio 是深度学习「三巨头」之一，另外两位是 Geoffrey Hinton 和 Yann LeCun；三人共同获得了 2018 年图灵奖。Bahdanau 当时还是博士生。

这篇论文的核心贡献可以概括成一件事：让翻译模型在生成每个词的时候，学会回头看源句子的不同部分。听起来理所当然，但在当时的神经机器翻译研究里，这是一个非常新的想法。它有一个名字，叫「注意力机制」。

三年后，Google 的八个人把这个想法推到了极致，写出了[《Attention Is All You Need》](/zh-hans/posts/attention-is-all-you-need/)（注意力就是你所需要的全部）。所以如果你想理解 Transformer，这篇论文是最重要的前史之一。

## 0. 先认几个词

如果你完全没有机器学习背景，先顺着这篇论文真正想修补的地方，记住下面几个词：

- `编码器-解码器 / Encoder-Decoder`：一部分先把源句子读完，另一部分再把目标句子一个词一个词写出来。
- `RNN / 循环神经网络`：当时主流的序列模型。它必须按顺序处理文本，不能一下子同时看整句。
- `hidden state / 隐藏状态`：模型读到某个位置时，手里那份临时笔记。
- `alignment / 对齐`：源句子里的哪一部分，对应目标句子当前要生成的这个词。
- `attention`：生成每个词时，不再只盯着一个总压缩结果，而是主动回头看源句子里更 relevant 的位置。

## 1. 问题出在哪

2014 年的神经机器翻译有一个标准架构：编码器-解码器（Encoder-Decoder）。编码器是一个循环神经网络（RNN），把源句子从头到尾读一遍，把整个句子压缩成一个固定长度的向量（可以理解为一串固定数量的数字）。解码器是另一个 RNN，从这个向量出发，一个词一个词地生成翻译。

问题很明显：不管源句子是 5 个词还是 50 个词，编码器都要把它压进同一个长度的向量里。短句子还行，长句子就丢信息。就像你让一个人读完一整页书，然后只能用一句话复述，句子越长，遗漏越多。

论文用实验证明了这一点：当句子长度超过 30 个词，传统编码器-解码器的翻译质量急剧下降。

这就是「固定长度瓶颈」。

## 2. 核心想法：别压缩，让解码器自己去找

论文的解决方案很直觉：既然把整个句子压成一个向量会丢信息，那就不压了。编码器保留每个位置的注解向量（annotation，由双向 RNN 的前向和后向隐藏状态拼接而成，可以理解为每个词处理完之后产生的中间结果），解码器在生成每个目标词时，自己决定该重点看源句子的哪些部分。

这就是注意力机制的核心：**不再强迫所有信息挤过一个瓶颈，而是让模型学会在需要的时候回头找需要的信息。**

具体来说，分三步：

**第一步，打分。** 解码器在生成第 i 个目标词之前，会用自己当前的状态 s_{i-1} 和编码器每个位置的隐藏状态 h_j 做比较，算出一个「对齐分数」e_{ij}。分数越高，说明生成当前目标词时，源句子的第 j 个位置越重要。

论文用的打分函数是：

$$
e_{ij} = a(s_{i-1}, h_j) = v_a^T \tanh(W_a s_{i-1} + U_a h_j)
$$

这叫「加性注意力」（additive attention）。把解码器状态和编码器状态各自做一次线性变换（乘以矩阵），加起来，过一个 tanh（一种把数值压缩到 -1 到 1 之间的函数），再和一个向量 v_a 做点积，得到一个标量分数。

**第二步，归一化。** 用 softmax 把所有位置的分数转成概率，加起来等于 1：

$$
\alpha_{ij} = \operatorname{softmax}(e_{ij}) = \frac{\exp(e_{ij})}{\sum_k \exp(e_{ik})}
$$

**第三步，加权求和。** 用这些概率对编码器的隐藏状态做加权求和，得到一个「上下文向量」c_i：

$$
c_i = \sum_j \alpha_{ij} h_j
$$

这个上下文向量就是解码器在生成第 i 个词时，从源句子里提取到的关键信息。每生成一个词，上下文向量都不一样，因为模型关注的源句子位置不一样。

用 Python（基于 PyTorch）写出来：

```python
import torch
from torch import nn


def bahdanau_attention(
    decoder_state: torch.Tensor,
    encoder_outputs: torch.Tensor,
    w_a: nn.Linear,
    u_a: nn.Linear,
    v_a: nn.Linear,
) -&gt; tuple[torch.Tensor, torch.Tensor]:
    decoder_features = w_a(decoder_state).unsqueeze(1)
    encoder_features = u_a(encoder_outputs)
    scores = v_a(torch.tanh(decoder_features + encoder_features)).squeeze(-1)
    weights = torch.softmax(scores, dim=-1)
    context = torch.sum(weights.unsqueeze(-1) * encoder_outputs, dim=1)
    return context, weights
```

和后来 Transformer 用的「点积注意力」（Q 和 K 直接做点积）不同，这篇论文用的是「加性注意力」（先各自做线性变换，再加起来）。两种方法各有特点，但点积注意力更适合用高效矩阵乘法实现；再加上 Transformer 去掉了 RNN 的顺序依赖，注意力才真正成为可大规模并行的核心算子。

## 3. 编码器：双向 RNN

单向 RNN 从左往右读句子，到了最后一个词才输出一个总结向量。问题是：每个位置的隐藏状态主要只带着左侧上下文，看不到右边。

论文用了双向 RNN（BiRNN）来解决这个问题。一个 RNN 从左往右读，另一个从右往左读，然后把两个方向的隐藏状态拼起来。这样每个位置的隐藏状态就同时包含了左边和右边的上下文。

```python
import torch
from torch import nn


class BidirectionalRNN(nn.Module):
    def __init__(self, input_size: int, hidden_size: int) -&gt; None:
        super().__init__()
        self.rnn = nn.GRU(
            input_size=input_size,
            hidden_size=hidden_size,
            bidirectional=True,
            batch_first=True,
        )

    def forward(self, inputs: torch.Tensor) -&gt; torch.Tensor:
        outputs, _ = self.rnn(inputs)
        return outputs
```

论文里每个方向各有 1000 个隐藏单元，拼起来就是 2000 维。这比单向 RNN 多了一倍参数，但换来的是每个位置都能看到完整的上下文。

## 4. 解码器：每一步都重新对齐

把编码器和注意力机制组装起来，解码器的工作流程就清楚了：

1. 编码器用双向 RNN 读完源句子，保留每个位置的隐藏状态（注解向量）
2. 解码器开始生成翻译，每生成一个词之前：
   - 用当前状态和所有注解向量算注意力权重
   - 加权求和得到上下文向量
   - 结合上下文向量、上一个生成的词和当前状态，预测下一个词

```python
import torch
from torch import nn


class AttentionDecoder(nn.Module):
    def __init__(self, embedding_dim: int, hidden_size: int, vocab_size: int) -&gt; None:
        super().__init__()
        self.rnn = nn.GRU(
            input_size=embedding_dim + 2 * hidden_size,
            hidden_size=hidden_size,
            batch_first=True,
        )
        self.w_a = nn.Linear(hidden_size, hidden_size, bias=False)
        self.u_a = nn.Linear(2 * hidden_size, hidden_size, bias=False)
        self.v_a = nn.Linear(hidden_size, 1, bias=False)
        self.output_proj = nn.Linear(hidden_size, vocab_size)

    def decode_step(
        self,
        prev_word: torch.Tensor,
        prev_state: torch.Tensor,
        encoder_outputs: torch.Tensor,
    ) -&gt; tuple[torch.Tensor, torch.Tensor]:
        context, _ = bahdanau_attention(
            prev_state.squeeze(0),
            encoder_outputs,
            self.w_a,
            self.u_a,
            self.v_a,
        )
        rnn_input = torch.cat([prev_word, context.unsqueeze(1)], dim=-1)
        output, new_state = self.rnn(rnn_input, prev_state)
        logits = self.output_proj(output[:, -1, :])
        return logits, new_state
```

关键在于：每生成一个目标词，解码器都会重新计算注意力分布。翻译第一个词时可能重点关注源句子的开头，翻译最后一个词时可能重点关注源句子的结尾。这种动态对齐能力，是之前的固定向量架构做不到的。

## 5. 实验结果

论文在英法翻译任务上做了实验（使用 WMT &apos;14 数据集），用 BLEU 分数（衡量机器翻译和人工翻译接近程度的标准评分，满分 100）衡量效果。

关键对比：
- **RNNencdec-50**（传统编码器-解码器，训练时最长 50 词）：26.71 BLEU
- **RNNsearch-50**（带注意力的模型，训练时最长 50 词）：**34.16 BLEU**
- **Moses**（当时最强的传统短语翻译系统）：33.30 BLEU

提升了 7.45 分。在论文报告的实验设置里，带注意力的神经模型已经达到甚至超过了当时强势的传统短语翻译系统。

更关键的发现在论文的 Figure 2：随着句子长度增加，传统编码器-解码器的 BLEU 分数急速下跌，而带注意力的模型几乎不受影响。这直接验证了论文的核心假设：固定长度向量是瓶颈，注意力机制可以绕过它。

论文还展示了注意力权重的可视化。在英法翻译里，注意力权重几乎形成了一条对角线，说明模型自动学会了「英语第 1 个词对应法语第 1 个词，英语第 2 个词对应法语第 2 个词」的对齐关系。遇到语序不同的情况（比如法语的形容词放在名词后面），注意力权重也会对应地偏移。模型不需要任何人工对齐标注，就学会了这些。

## 6. 我的思考

读完这篇论文，有几个感受。

第一，这篇论文解决的问题极其明确：编码器把整个句子压成一个向量，长句子丢信息。解决方案也极其直觉：别压了，让解码器自己去找。好的研究往往就是这样，问题清晰，解法自然。

第二，注意力机制在这篇论文里还是 RNN 的配角。编码器仍然是循环的（双向 RNN），解码器也是循环的，注意力只是在两者之间架了一座桥。三年后 Vaswani 等人问了一个更激进的问题：既然注意力这么好用，能不能把 RNN 整个扔掉，只留注意力？答案就是 Transformer。

第三，用真实 Python 重写这篇论文的注意力机制时，你会发现它的计算流程比 Transformer 的 Scaled Dot-Product Attention 复杂不少。加性注意力需要额外的权重矩阵 W_a、U_a、v_a，而点积注意力只需要 Q 和 K 直接相乘再缩放。从「加法」到「乘法」，看似一小步，实际上大幅简化了计算，也更适合用矩阵运算高效实现。

第四，Bahdanau 当时是博士生，Bengio 是他的导师。一个博士生的论文，定义了此后十年 AI 研究的核心组件。注意力机制从这里开始，经过 Transformer 的放大，最终成为 GPT、BERT、LLaMA 的基石。

这篇论文没有发明什么复杂的数学。它只是问了一个简单的问题：解码器为什么不能回头看？

然后它让解码器回头看了。

这一看，看出了整个时代。

---

**论文共读系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-hans/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神经网络进行序列到序列学习） — 编码器-解码器范式的确立
- [《Attention Is All You Need》](/zh-hans/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成为主角，Transformer 的诞生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-hans/posts/bert/)（BERT：用于语言理解的深度双向 Transformer 预训练） — 预训练范式的确立
- [《Scaling Laws for Neural Language Models》](/zh-hans/posts/scaling-laws-for-neural-language-models/)（神经语言模型的缩放定律） — 规模的数学：为什么更大的模型可预测地更好
- [《Language Models are Few-Shot Learners》](/zh-hans/posts/language-models-are-few-shot-learners/)（语言模型是少样本学习者） — 更大的模型，更善于从上下文中诱发能力
- [《Training Compute-Optimal Large Language Models》](/zh-hans/posts/training-compute-optimal-large-language-models/)（训练计算最优的大语言模型） — 怎样花算力最划算</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>attention</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>论文共读：《Attention Is All You Need》（注意力就是你所需要的全部）</title><link>https://justinhuangai.github.io/zh-hans/posts/attention-is-all-you-need/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/attention-is-all-you-need/</guid><description>分享我对 Transformer 论文的理解，附真实 Python 代码</description><pubDate>Tue, 06 Jan 2026 08:18:46 GMT</pubDate><content:encoded>2017 年 6 月 12 日，八个人在 arXiv（一个学术论文预印本网站，论文不用等期刊审稿就能直接发布）上传了一篇论文，标题只有五个词：[《Attention Is All You Need》](/papers/1706.03762v7.pdf)（注意力就是你所需要的全部）。

这八个人是 Ashish Vaswani、Noam Shazeer、Niki Parmar、Jakob Uszkoreit、Llion Jones、Aidan N. Gomez、Łukasz Kaiser 和 Illia Polosukhin，当时大多在 Google Brain 和 Google Research 工作。

论文发出之后，这个八人组几乎全部散开。Noam Shazeer 离开 Google 创立了 Character.AI，后来又被 Google 高价请回；Aidan Gomez 从多伦多大学博士还没毕业就创立了 Cohere，做企业级大模型；Llion Jones 去了日本，创立了 Sakana AI；Illia Polosukhin 走了一条谁都没想到的路，创立了 NEAR Protocol，做区块链；Ashish Vaswani 和 Niki Parmar 搭档创立了 Adept AI，后来又一起创立了 Essential AI；Jakob Uszkoreit 创立了 Inceptive，用 AI 设计 RNA 药物；Łukasz Kaiser 则加入了 OpenAI，参与了 GPT 系列的研发。

八位作者，七家公司，横跨 AI、区块链、生物技术。

近九年后的今天，ChatGPT、Claude、DeepSeek、Qwen，这些 AI 产品的底层架构思路，大多都能追溯到这 15 页纸。

这篇文章是我读完论文后的理解，附带真实 Python 代码示例。不是翻译，不是摘要。没有技术背景也能读下去。

## 0. 先认几个词

如果你没有机器学习背景，先按这篇论文真正想替换掉的旧方案，记住下面几个词就够了：

- `RNN / 循环神经网络`：一种更早的序列模型。它处理句子时必须一个词一个词往后读，像人用手指着文章逐行看。
- `attention`：从很多信息里，挑出当前最该看的那几部分。你可以先把它理解成“有选择地回头看重点”。
- `Query / Key / Value`：注意力机制里的三个角色。Query 像“我现在想找什么”，Key 像“每段信息贴着什么标签”，Value 则是“真正被取出来的内容”。
- `Transformer`：以 attention 为核心搭起来的一整套架构。它不靠循环一步步往前推，而是让每个位置都能直接看其他位置。
- `并行`：这里不是说模型更聪明，而是说它能同时处理很多位置，不必像 RNN 那样排队。

## 1. 一句话说清楚

在 Transformer 之前，AI 处理语言的方式像是一个人用手指着书，一个字一个字地往下读。读到第 100 个字的时候，第 1 个字说了什么，已经记不太清了。句子越长，遗忘越严重。这就是循环神经网络（RNN，一种早期的 AI 架构）的根本瓶颈。

论文的作者们问了一个问题：**为什么一定要按顺序读？**

和必须逐步处理的 RNN 不同，Transformer 可以并行处理整段输入，直接建模任意两个位置之间的关系。不用排队，不用等前一个词处理完才能看下一个。

论文管这个核心能力叫「注意力」。注意力机制最早由 [Bahdanau 等人在 2014 年提出](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)，当时是作为 RNN 的辅助组件。这篇论文的标题要表达的不是「模型里真的只剩注意力」，而是：在序列建模里，注意力第一次被推到了主角的位置，不再需要循环和卷积（一种通过滑动窗口提取局部特征的方法）作为骨架。

## 2. 注意力到底在做什么

想象你走进一个嘈杂的酒吧，二十个人同时在说话。你的大脑不会平均分配注意力给每个人。有人喊了你的名字，你的耳朵瞬间锁定那个方向，其他声音自动变成背景噪音。

Transformer 对每个词做同样的事。论文里定义了三个角色：

- **Query（查询）**：这个词在找什么信息。相当于你的耳朵在搜索「谁在叫我」
- **Key（键）**：这个词能提供什么信息。相当于酒吧里每个人的声音特征
- **Value（值）**：这个词实际携带的内容。相当于那个人说的具体话

每个词的 Query 会和其他词的 Key 做匹配。匹配度高的，就从对方的 Value 里获取更多信息。匹配度低的，直接忽略。

论文给出的公式叫 Scaled Dot-Product Attention：

$$
\operatorname{Attention}(Q, K, V) = \operatorname{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$

看到公式别慌。一步一步拆：

- **QK^T**：Q 和 K 做点积。什么是点积？把两组数字对应位置相乘，再加起来。比如 [1, 2] 和 [3, 4] 的点积是 1×3 + 2×4 = 11。数字越大，说明两个词越相关。这一步算的就是每对词之间的「匹配分数」
- **/ √d_k**：除以一个数来缩放。d_k 是向量的长度（向量可以理解为「一串用来描述某个东西的数字」，比如用 64 个数字描述一个词的含义）。为什么要除？因为数字串越长，点积结果越大。不缩放时，维度越大点积的方差越大，softmax 容易进入饱和区（几乎所有概率集中在一个词上），梯度（模型用来调整自身参数的信号）变得很小，训练会不稳定
- **softmax**：把一组分数转换成概率，所有概率加起来等于 1。比如三个词的分数是 [10, 2, 1]，softmax 之后大概变成 [0.99, 0.007, 0.003]。分数最高的那个词几乎拿走了全部注意力，其他的被压到接近零
- **× V**：用这些概率去加权每个词的实际内容。概率高的词贡献大，概率低的词贡献小。最终输出是一个融合了关键信息的新向量

用 Python（基于 PyTorch）写出来：

```python
import math
import torch


def scaled_dot_product_attention(
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
) -&gt; torch.Tensor:
    d_k = key.size(-1)
    scores = query @ key.transpose(-2, -1) / math.sqrt(d_k)
    weights = torch.softmax(scores, dim=-1)
    return weights @ value
```

就这么几行代码。很多后来改变行业的能力，底层都建立在这几行运算之上。

## 3. 多头注意力：同时从多个角度看

单个注意力头通常只能偏向某一类关系模式。但语言这东西，一句话里藏着好几层意思。

拿「我昨天在深圳吃了潮汕牛肉火锅」来说：
- 「我」和「吃了」之间是谁做了什么的关系
- 「昨天」和「吃了」之间是时间关系
- 「深圳」和「潮汕牛肉火锅」之间是地点与食物的关系

让一个头同时兼顾这么多层次，很难。论文的做法是用多头机制：派出 8 个头并行运算，让模型有机会从不同子空间同时观察一句话，最后把各自的发现拼起来。

论文原文的公式：

$$
\operatorname{MultiHead}(Q, K, V) = \operatorname{Concat}(\text{head}_1, \ldots, \text{head}_h)\, W^O
$$

拆开看：
- **head_1, ..., head_h**：8 个头各自独立做一次注意力运算，得到 8 份结果
- **Concat**：把 8 份结果首尾相连，拼成一个长向量
- **W^O**：一次线性变换（可以理解为「乘以一个矩阵」），把拼接后的长向量压回原来的维度。相当于一个主管听完 8 个调查员的汇报，输出一份综合结论

```python
import math
import torch
from torch import nn


class MultiHeadAttention(nn.Module):
    def __init__(self, d_model: int, num_heads: int) -&gt; None:
        super().__init__()
        if d_model % num_heads != 0:
            raise ValueError(&quot;d_model must be divisible by num_heads&quot;)
        self.num_heads = num_heads
        self.d_head = d_model // num_heads
        self.q_proj = nn.Linear(d_model, d_model)
        self.k_proj = nn.Linear(d_model, d_model)
        self.v_proj = nn.Linear(d_model, d_model)
        self.out_proj = nn.Linear(d_model, d_model)

    def _split_heads(self, x: torch.Tensor) -&gt; torch.Tensor:
        batch_size, seq_len, _ = x.shape
        x = x.view(batch_size, seq_len, self.num_heads, self.d_head)
        return x.transpose(1, 2)

    def forward(
        self,
        query: torch.Tensor,
        key: torch.Tensor,
        value: torch.Tensor,
    ) -&gt; torch.Tensor:
        q = self._split_heads(self.q_proj(query))
        k = self._split_heads(self.k_proj(key))
        v = self._split_heads(self.v_proj(value))

        scores = q @ k.transpose(-2, -1) / math.sqrt(self.d_head)
        weights = torch.softmax(scores, dim=-1)
        heads = weights @ v

        batch_size, _, target_len, _ = heads.shape
        merged = heads.transpose(1, 2).contiguous()
        merged = merged.view(batch_size, target_len, self.num_heads * self.d_head)
        return self.out_proj(merged)
```

论文里的参数：模型用 512 个数字描述一个词（d_model = 512），8 个头，每个头分到 64 个数字（512 ÷ 8 = 64）。8 个头的总计算量和 1 个 512 维的头差不多，但表达能力强得多。用同样的代价，换来多视角的理解力。这笔账算得漂亮。

## 4. 位置编码：告诉模型词的顺序

Transformer 并行处理整个句子，速度是快了，但代价是它丢掉了词的先后顺序。如果没有额外的位置信息，注意力机制本身并不知道「猫吃鱼」和「鱼吃猫」有什么区别。这显然不行。

怎么补救？给每个位置生成一个独一无二的「地址编码」，加到词的向量上。模型看到的不再是「猫」和「鱼」，而是「第 1 个位置的猫」和「第 3 个位置的鱼」。

论文用正弦和余弦函数来生成这个编码：

$$
\operatorname{PE}(pos, 2i) = \sin\left(\frac{pos}{10000^{2i / d_{\text{model}}}}\right)
$$

$$
\operatorname{PE}(pos, 2i + 1) = \cos\left(\frac{pos}{10000^{2i / d_{\text{model}}}}\right)
$$

公式看着唬人，核心思路很直观：
- **pos**：词在句子里的位置（第 1 个、第 2 个、第 3 个……）
- **i**：向量的第几个维度。偶数位置用 sin，奇数位置用 cos
- **10000^(2i/d_model)**：一个随维度变化的缩放因子。低维度变化快，高维度变化慢。就像时钟：秒针一分钟转一圈，时针十二小时才转一圈。不同「指针」覆盖不同的时间尺度，组合在一起就能精确定位任意时刻

最终效果：每个位置得到一串独一无二的数字指纹，模型靠这个指纹区分词的先后顺序。

```python
import math
import torch


def positional_encoding(seq_len: int, d_model: int) -&gt; torch.Tensor:
    positions = torch.arange(seq_len, dtype=torch.float32).unsqueeze(1)
    div_term = torch.exp(
        torch.arange(0, d_model, 2, dtype=torch.float32)
        * (-math.log(10000.0) / d_model)
    )

    encoding = torch.zeros(seq_len, d_model)
    encoding[:, 0::2] = torch.sin(positions * div_term)
    encoding[:, 1::2] = torch.cos(positions * div_term)
    return encoding
```

为什么偏偏选正弦余弦？因为它有一个优雅的数学性质：两个词相隔固定距离，无论它们出现在句首还是句尾，位置编码之间的关系是一样的。模型不用死记「位置 3 和位置 8」的关系，只需要学会「相隔 5 个位置」意味着什么。论文团队也试过让模型自己学位置编码，效果差不多，但正弦版本有一个额外的优势：它能处理训练时没见过的更长句子。

## 5. 编码器与解码器

Transformer 的完整架构分两半。

**编码器**（6 层堆叠）负责读懂输入。每层包含两个子层：一个多头自注意力，一个前馈网络。每个子层都有两个保护机制：

- **残差连接**：把子层的输入直接加到输出上，即 x + Sublayer(x)。为什么？想象你给一张照片加滤镜。如果滤镜效果不好，残差连接保证你还能看到原图。在深层网络里，信息每经过一层都会被变换，传到第六层可能已经面目全非。残差连接让原始信号可以「抄近道」直达深层，防止信息在传递中丢失
- **层归一化**（LayerNorm）：把数值调整到统一范围，防止有的数字大到爆炸、有的小到消失。类似于考试成绩标准化，不管原始卷面分差异多大，标准化后都在一个可比较的区间

**解码器**（6 层堆叠）负责生成输出。结构和编码器类似，但多了两个关键设计：

第一，**交叉注意力**：解码器生成每个词时，会回头「看」编码器的输出。翻译场景下，就是一边写英文一边回头看中文原文。

第二，**遮罩**（masking）：生成第 3 个词时，只允许看到前 2 个词，第 4 个及之后的位置被屏蔽（注意力分数设为负无穷，经过 softmax 后变成零）。道理很简单：你写作文的时候，下一个字还没写出来，不能偷看。

```python
from typing import Optional

import torch
from torch import nn


class Transformer(nn.Module):
    def __init__(
        self,
        vocab_size: int,
        d_model: int = 512,
        num_heads: int = 8,
        num_layers: int = 6,
        d_ff: int = 2048,
        dropout: float = 0.1,
    ) -&gt; None:
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, d_model)

        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_model,
            nhead=num_heads,
            dim_feedforward=d_ff,
            dropout=dropout,
            batch_first=True,
        )
        decoder_layer = nn.TransformerDecoderLayer(
            d_model=d_model,
            nhead=num_heads,
            dim_feedforward=d_ff,
            dropout=dropout,
            batch_first=True,
        )

        self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
        self.decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_layers)
        self.output_proj = nn.Linear(d_model, vocab_size)

    def forward(
        self,
        src_token_ids: torch.Tensor,
        tgt_token_ids: torch.Tensor,
        tgt_mask: Optional[torch.Tensor] = None,
    ) -&gt; torch.Tensor:
        memory = self.encoder(self.embedding(src_token_ids))
        hidden = self.decoder(self.embedding(tgt_token_ids), memory, tgt_mask=tgt_mask)
        return self.output_proj(hidden)
```

还有一个容易被忽略的组件：前馈网络。公式是 FFN(x) = max(0, xW1 + b1)W2 + b2。翻译成人话：先把每个词的 512 维向量扩大到 2048 维（乘以一个矩阵再加一个偏置），用 ReLU 过滤一遍（所有负数变成零，正数保留），再压回 512 维。ReLU 这一步是关键：它引入了「非线性」，让模型能学到直线画不出来的复杂模式。如果全是线性变换，多层叠加在数学上仍可合并为单层，非线性是模型表示复杂模式的前提。

## 6. 训练细节

架构设计完了，怎么训练它？论文在这里也有不少讲究。

**硬件**：8 块 NVIDIA P100 GPU。基础版模型训练 12 小时（10 万步），大号模型训练 3.5 天（30 万步）。放在今天看，这个成本低得惊人。

**优化器**：用的是 Adam（一种让模型自动调整参数的算法），但学习率的设计很巧妙。学习率决定了模型每一步「迈多大步子」。步子太大容易跨过最优解，太小又走得慢。论文的策略是前 4000 步逐渐提速（warmup，热身），避免一开始更新过猛；4000 步之后按计划逐渐减速，让训练后期更稳定。先升后降，前半段大胆探索，后半段精细打磨。

**正则化**：两招。第一招是 Dropout，训练时随机关掉 10% 的神经元（可以理解为网络中的计算节点），迫使模型不依赖任何单一路径，学到更鲁棒的特征。第二招是 label smoothing（标签平滑，ε = 0.1）：训练时不告诉模型「正确答案的概率是 100%」，而是说「90% 是正确答案，剩下 10% 分给其他选项」。这会让模型在一个指标上变差（困惑度，衡量模型有多「拿不准」），但翻译质量反而更好。直觉上说，一个承认自己不是 100% 确定的模型，比一个过度自信的模型更可靠。

**结果**：论文用 BLEU 分数（机器翻译的标准评分，衡量机器翻译和人工翻译有多接近，满分 100）来衡量效果。英德翻译 28.4 分，英法翻译 41.8 分，都刷新了当时的记录。训练成本比之前的方法低了一到两个数量级。更快，更强，更便宜。

## 7. 我的思考

读完这篇论文，有几个感受。

第一，这篇论文的核心洞察极其简洁：扔掉顺序处理的包袱，让注意力机制直接建模任意两个位置之间的关系。Self-Attention、残差连接、Layer Normalization，没有一个是新发明。真正的突破不在于发明新工具，而在于作者们敢赌「这些简单的积木拼在一起就够了」，然后用实验证明了自己是对的。

第二，用真实 Python 代码写出来的过程让我更深地理解了每一个设计决策。当你自己写出 Scaled Dot-Product Attention，你会切实感受到那个 √d_k 的缩放有多重要。当你实现 masking，你会理解自回归生成的约束从何而来。论文读十遍，不如自己写一遍。

第三，真正让我震撼的，不是它后来衍生出了多少模型，而是它当年就把问题改写了：从「怎么按顺序记住一句话」，变成「怎么让每个位置直接找到它最该看的信息」。GPT、BERT、T5、LLaMA，全是这个问题改写之后的产物。

一个足够好的架构，能走多远，取决于有多少人愿意在它上面继续建设。

这篇论文给出了那个架构。

《Attention Is All You Need》（注意力就是你所需要的全部）。

---

**论文共读系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-hans/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神经网络进行序列到序列学习） — 编码器-解码器范式的确立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-hans/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通过联合学习对齐与翻译实现神经机器翻译） — 注意力机制的起源
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-hans/posts/bert/)（BERT：用于语言理解的深度双向 Transformer 预训练） — 预训练范式的确立
- [《Scaling Laws for Neural Language Models》](/zh-hans/posts/scaling-laws-for-neural-language-models/)（神经语言模型的缩放定律） — 规模的数学：为什么更大的模型可预测地更好
- [《Language Models are Few-Shot Learners》](/zh-hans/posts/language-models-are-few-shot-learners/)（语言模型是少样本学习者） — 更大的模型，更善于从上下文中诱发能力
- [《Training Compute-Optimal Large Language Models》](/zh-hans/posts/training-compute-optimal-large-language-models/)（训练计算最优的大语言模型） — 怎样花算力最划算</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>transformer</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>👋 你好，世界</title><link>https://justinhuangai.github.io/zh-hans/posts/hello-world/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-hans/posts/hello-world/</guid><description>欢迎来到 Astro-Theme-Aither——一个相信文字本身就很美的 AI 原生 Astro 主题。</description><pubDate>Thu, 01 Jan 2026 08:07:13 GMT</pubDate><content:encoded>欢迎来到 Astro-Theme-Aither。

这是一个基于一个信念构建的 AI 原生博客主题：文字本身就很美。统一的无衬线系统字体栈、Apple HIG 排版参数，以及不喧宾夺主的布局。这里的一切都服务于一个目标——让你的文字看起来优美、读起来舒适。

## 为什么再造一个博客主题

互联网上有无数博客主题，那为什么还要再做一个？答案在于优先级。大多数主题为视觉冲击力而优化——大图、复杂布局、华丽动画。这些在演示中很好看，但当读者真正坐下来阅读一篇两千字的文章时，它们只会碍事。

Astro-Theme-Aither 从不同的前提出发：内容就是产品。主题的职责是以它应得的认真态度呈现内容：Apple HIG 正文参数（17px / 1.47 / -0.022em）、充足的留白、让长文阅读变得舒适而不是疲惫的垂直节奏。

技术决策也延续了这一理念。主题使用 Astro 的岛屿架构——只有交互组件（主题切换、语言切换、语言检测、移动端导航）加载 JavaScript。其他一切都是静态 HTML 和 CSS。没有布局偏移，没有加载动画。页面加载完毕，你开始阅读。

## 开始使用

搭建只需几步：

1. **克隆仓库** — 使用 GitHub 模板按钮或直接 `git clone`
2. **安装依赖** — 运行 `pnpm install`
3. **配置站点** — 编辑 `src/config/site.ts` 设置标题、描述和导航
4. **配置服务** — 复制 `.env.example` 为 `.env`，填入 API 密钥（GA、Crisp、Giscus）
5. **替换内容** — 用你自己的文章替换 `src/content/posts/` 中的示例
6. **本地开发** — 运行 `pnpm dev` 启动热更新开发服务器
7. **部署** — 推送到 `gh-pages`，内置 GitHub Pages 工作流会自动发布站点

### 项目结构

```
src/
├── components/     # Astro 和 React 组件
├── config/         # 站点配置（site.ts）
├── content/        # Markdown 文章（按语言组织）
├── i18n/           # 翻译和语言工具
├── layouts/        # 页面布局（Layout.astro）
├── lib/            # 共享工具（posts, formatter, markdown-endpoint）
├── pages/          # 路由页面（按语言）
└── styles/         # 全局 CSS + Tailwind v4 @theme 令牌
```

### 写第一篇文章

在 `src/content/posts/zh-hans/` 下创建 `.md` 文件：

```markdown
---
title: 我的第一篇文章
date: &quot;2026-01-15T16:27:43+08:00&quot;
category: General
description: SEO 和社交预览的简短摘要
tags: [topic, another]
pinned: false
---

正文从这里开始。
```

`title`、`date`、`category` 是必填项。`date` 建议使用带时区、精确到秒的 ISO 8601 格式，例如 `2026-01-15T16:27:43+08:00`。`description` 强烈建议填写。设置 `pinned: false` 可将文章置顶。

多语言内容只需在对应语言目录（`en/`、`zh-hant/`、`ko/`）创建同名文件。

## 开箱即用

### 内容功能

- **RSS 订阅** — 自动生成 `/rss.xml`
- **站点地图** — 通过 `@astrojs/sitemap` 自动生成
- **SEO 标签** — 每页自动生成 Open Graph、Twitter Cards、规范 URL
- **JSON-LD** — Article 结构化数据，服务 AI 和搜索引擎
- **深色模式** — 浅色 / 深色 / 系统切换，View Transitions API 圆形展开动画
- **i18n** — 多语言支持，自动浏览器语言检测
- **文章置顶** — 将重要文章固定在列表顶部
- **分页** — 基于文件的 SSG 分页，带页码导航

### AI 原生功能

- **llms.txt** — AI agent 内容索引，`/llms.txt`
- **llms-full.txt** — AI 全文消费，`/llms-full.txt`
- **Markdown 端点** — 任何文章 URL 后加 `.md` 获取纯 Markdown
- **robots.txt** — 明确欢迎 AI 爬虫（GPTBot、ClaudeBot、PerplexityBot）

### 开发者功能

- **TypeScript** — 严格模式，全类型化
- **Content Collections** — 构建时 frontmatter 类型验证
- **Tailwind CSS v4** — `@theme` 设计令牌
- **校验链路** — 通过 `pnpm validate` 运行内容覆盖检查和 agent 协议 smoke test
- **部署** — 内置 GitHub Pages 工作流
- **Google Analytics** — 可选，环境变量配置
- **Crisp Chat** — 可选在线客服，环境变量配置
- **Giscus 评论** — 可选 GitHub Discussions 评论

### 性能

静态 HTML + 最小 JavaScript 岛屿 = Lighthouse 四项满分。

## 自定义

- **颜色** — 编辑 `src/styles/global.css` 中的 CSS 变量
- **字体** — 修改 Tailwind 主题配置中的字体族
- **导航** — 更新 `src/config/site.ts` 中的导航数组
- **服务** — 在 `.env` 中设置 GA、Crisp、Giscus 环境变量
- **语言** — 在 `src/i18n/` 添加新语言，创建对应路由

## 设计理念

主题的视觉简洁是刻意的，但这不等于工程简单。底层处理了大量关注点：Apple HIG 排版参数、明暗两种模式的无障碍色彩对比、View Transitions API 动画、自动浏览器语言检测、语义化 HTML 结构、AI 友好的内容端点，以及从手机到超宽屏的阅读体验优化。

好的设计是隐形的。当你在这个主题上阅读一篇文章，只是单纯享受文字而完全没注意到主题的存在——这就是设计在按预期工作。

祝写作愉快。</content:encoded><category>Tutorial</category><category>hello</category><category>astro</category></item></channel></rss>