第0章 起步:读这篇论文之前,你需要的全部基础
这一章不属于论文。它是一块地基。读完它,你会拥有读懂后面十五章所需的每一个概念——不是「听说过」,而是「能用自己的话讲清楚,并且算得出来」。如果你已经知道什么是注意力机制和 KV 缓存,可以跳到第1章;但我建议你至少做一遍本章末尾的自测,看看是不是真的知道。
学完这一章你应该能做到
- 用一句不含术语的话解释「大语言模型在做什么」
- 手算一个 2×2 矩阵乘法,并说出它在神经网络里代表什么
- 手算一次 softmax,并解释为什么要先取指数
- 画出注意力机制的三步,说清 Q、K、V 各自是什么
- 解释为什么对话越长越贵,并算出 KV 缓存的大小
- 分清总参数、激活参数、FLOPs 三个量,并解释 MoE 为什么能让它们脱钩
- 说出显存、带宽、算力三者中哪个最常成为瓶颈,以及为什么
0.1 先给你一个方向感:这篇论文在讲什么
Kimi K3 是月之暗面(Moonshot AI)在 2026 年 7 月发布的一个大语言模型,同时公开了模型权重。这份 47 页的技术报告讲的是:他们怎么把它造出来的。
造一个这种规模的模型,大致要回答四类问题,而这份报告的结构正好对应这四类:
- 模型长什么样(第 2 节,本站第 3–6 章)——里面有哪些零件,为什么这么摆。
- 怎么把知识灌进去(第 3 节,本站第 7 章)——用什么数据、训多久、怎么定超参数。
- 怎么让它学会做事(第 4 节,本站第 8–9 章)——从「会说话」到「会干活」,靠的是后训练和强化学习。
- 怎么让这一切在真实机器上跑得动(第 5 节,本站第 10–11 章)——2.8 万亿个参数装不进任何一张显卡,这本身就是个大工程。
剩下的是第 6 节的评测(本站第 12 章)、第 7 节的案例(第 13 章)和附录里的几个证明(第 14 章)。
为什么要先补地基,而不是直接读
这份报告的目标读者是同行——它默认你已经知道什么是 Transformer、什么是 KV 缓存、什么是专家并行。它在一句话里能塞进四个你没见过的名词,然后继续往下讲。硬读的结果通常是:每个字都认识,连起来完全不知道在说什么。
所以我们先花一章,把这些默认知识一个个装进你的脑子。这一章会有点长,但它是唯一一次你需要从零学起的地方。
0.2 语言模型到底在做什么:猜下一个词
这件事说出来会让人失望,但它是真的:大语言模型做的唯一一件事,是根据前面的文字,猜下一个字最可能是什么。
比如给它「中华人民共和」,它会给出一个概率分布:
然后按这个分布抽一个字出来,接在后面,变成「中华人民共和国」,再拿这个新的句子去猜下下一个字。如此反复,就「写」出了一整段话。
下一 token 预测(Next-Token Prediction, NTP):上面这个「看前文、猜下一个」的任务。整篇论文里所有的预训练,做的都是这一件事。
打个比方
像一个读遍了全世界所有书的人在玩填空游戏。你给他半句话,他凭着读过的一切,判断下一个字最可能是什么。他不是在「思考」,他是在做一个极其精细的模式匹配。
类比失效处:这个比方容易让人以为模型只是在「背书」和「抄近似的句子」。实际上,为了把下一个字猜准,模型被迫在内部学会了语法、事实、推理链条、甚至代码的执行逻辑——因为这些东西能让预测更准。「只是猜下一个字」描述的是任务,不是模型内部学到了什么。这个区别在后面理解「为什么 RL 能让它学会用工具」时非常关键。
有人说:「语言模型不可能真的会算数,因为它只是在猜下一个字。」 这句话的推理哪里有问题?
三位数乘三位数有 900×900 = 81 万种组合,四位数是 8100 万种,位数再涨就完全不可能穷举。而训练数据里不可能包含所有组合。所以对模型来说,「背下所有算式」这条路在数据量和参数量上都走不通;能把没见过的算式也猜对的唯一办法,就是在内部实现某种近似的计算过程。
训练目标(猜下一个字)是一个压力,不是一个限制。这个压力逼出来的内部结构可以任意复杂。反过来说,这也解释了为什么模型的算数能力是「不可靠的近似」而不是「精确的算法」——它学到的是在训练分布上够用的近似过程,位数一多就崩。这也正是本站第9章里,为什么 Kimi 团队要专门给模型配一个 Python 解释器来做精确计算。
变式:同样的推理套到「模型不可能真的理解代码,因为它只是在猜下一个字符」上。这次的反驳会更强还是更弱?为什么代码这个领域,「猜下一个字符」这个任务施加的压力可能更大?
0.3 token:模型眼里的「字」
上一节我说「猜下一个字」,那是简化。模型实际处理的单位叫 token,中文常译作「词元」。
token 不是字,也不是词,而是一种介于两者之间的、由统计方法切出来的片段。比如英文 unhappiness 可能被切成 un + happi + ness 三个 token;中文里常见字通常一个字就是一个 token,生僻字可能被拆成两三个。
词表(vocabulary):所有可能 token 的清单。Kimi K3 的词表大小是 160K(16 万个),这个数字你在表 1 里会看到。
所以更准确的说法是:模型看到的是一串编号(比如 [3612, 8829, 174, ...]),它要预测的是下一个编号,从 16 万个候选里挑。
0.4 向量:把 token 变成一串数
计算机不能直接对「编号 3612」做数学。所以第一步是把每个 token 变成一串数字,叫做向量。
向量(vector):一串有顺序的数,比如 (0.3, −1.2, 0.8)。这串数有多长,就叫它的维度。
Kimi K3 用 7168 维的向量表示一个 token。也就是说,「猫」这个字在模型内部是 7168 个小数组成的一串。
为什么用一串数而不是一个数
想象你要用数字描述一个人。用一个数(比如身高)显然不够。用一串数——身高、体重、年龄、收入、开朗程度、……——就能刻画得细致得多,而且相似的人,这串数也会接近。
7168 维向量就是这个意思:它是这个 token 的「多维档案」。语义相近的 token(「猫」和「狗」),它们的向量在空间里离得近。
类比失效处:真实模型里这 7168 个维度没有人类可命名的含义。不存在「第 42 维代表可爱程度」这回事。这些维度是训练自动生成的,绝大多数无法解释。
0.5 矩阵乘法:神经网络里唯一真正重的运算
这一节是全章最重要的一节。如果你只学一个数学工具,学这个。
矩阵(matrix):把数排成一个长方形的表格。一个 3 × 2 的矩阵有 3 行 2 列。
矩阵乘以向量是这样算的:把向量竖着放在矩阵右边,矩阵的每一行与这个向量逐个相乘再求和,得到结果向量的一个分量。
[ 0 4 ] × [ 5 ] = [ 0×3 + 4×5 ] = [ 20 ]
[ 1 −1 ] [ 1×3 − 1×5 ] [ −2 ]
注意形状:一个 3×2 的矩阵乘一个 2 维向量,得到一个 3 维向量。矩阵把向量从一个维度搬到了另一个维度,同时把信息重新混合了一遍。
三个你会在论文里反复看到的写法
Wx:矩阵 W 乘向量 x。
x⊤:转置,把竖着的向量放倒成横的(或反过来)。
kv⊤:一个竖向量乘一个横向量,结果是一个矩阵(这叫外积,第2章会大量用到)。
⊙:逐元素相乘,两个同长的向量对应位置相乘,长度不变。
Diag(α):把向量 α 摆到一个方阵的对角线上,其余位置填 0。
Kimi K3 的隐藏维度是 7168。现在有一个矩阵 W,它把一个 token 的 7168 维向量变成一个 3584 维向量(这正是论文里的「潜投影」 W↓)。
(a) 这个矩阵是几行几列?
(b) 做一次这样的乘法,需要多少次乘法运算?
(c) 这个矩阵本身占用多少个参数?
(a) 3584 行 × 7168 列。规则是「行数 = 输出维度,列数 = 输入维度」。
(b) 3584 × 7168 = 25,690,112 次乘法,约 2570 万次。
(c) 参数就是矩阵里的格子数,也是 25,690,112 个,约 2570 万。
这里有一个极重要的巧合:参数量和单 token 的乘法次数是同一个数。这不是偶然——每个参数在处理一个 token 时恰好被用到一次。这个关系是后面理解「激活参数决定算力成本」的基石。
顺带感受一下规模:这只是一个矩阵。K3 有 93 层,每层有十几个这样的矩阵,还有 896 个专家各自带着自己的矩阵。总共 2.78 万亿个参数。
变式:如果把这个矩阵拆成两个——先 7168 → 64,再 64 → 3584(这叫「低秩分解」,论文式 2 里的 Wα↑Wα↓ 正是这么做的)——参数量变成多少?省了多少倍?代价是什么(提示:想想两步走能表达的变换,是不是所有一步走能表达的变换都能做到)?
0.6 一层神经网络 = 矩阵乘法 + 掰弯
如果只有矩阵乘法,会出问题:连续做很多次矩阵乘法,效果等同于只做一次(两个矩阵可以先乘起来变成一个矩阵)。那么叠 93 层就毫无意义。
所以每做完一次矩阵乘法,要插进一个非线性函数——一个不能被矩阵乘法模拟的、把直线掰弯的操作。
激活函数(activation function):那个负责「掰弯」的函数。常见的有:
| 名字 | 做什么 | 直觉 |
|---|---|---|
| Sigmoid(x) | 把任意实数压到 0 和 1 之间,大的接近 1,小的接近 0 | 一个「软开关」:开多少 |
| tanh(x) | 压到 −1 和 1 之间 | 带方向的软开关 |
| Swish(x) | = x · Sigmoid(x);正的地方几乎不变,负的地方渐渐压到 0 | 「正的放行,负的渐消」 |
第5章的主角 SiTU-GLU 就是在 Swish 的基础上改造出来的,你到那时会需要这张表。
为什么 Sigmoid 这么常用
因为它天然是个「闸门」。当你想让模型自己决定「某个信息放行多少」,最自然的做法就是算一个 Sigmoid 值(0 到 1 之间),然后拿它去乘那个信息。乘 0 = 完全关闭,乘 1 = 完全放行,乘 0.6 = 放行六成。
论文里所有叫「门」(gate)的东西——KDA 的输出门、MLA 的输出门、GLU 的门分支——都是这个套路。你只要看到 Sigmoid(Wgx) ⊙ 某个东西,就知道是「模型自己决定放行多少」。
0.7 softmax:把任意一串数变成一组概率
模型算出 16 万个候选 token 的「分数」之后,怎么变成概率?分数可能是负的,加起来也不等于 1。
softmax 解决这个问题,两步:
- 对每个数取指数 ex(结果一定是正数,而且大的数被放大得更厉害)
- 除以所有指数的总和(这样加起来正好是 1)
| 符号 | 是什么 | 直觉 |
|---|---|---|
| xi | 第 i 个候选的原始分数(也叫 logit) | 模型对这个候选的「看好程度」,可正可负 |
| ex | 指数函数,把分数变成正数 | 拉开差距:分数差 1,指数差 2.7 倍 |
| ∑j | 对所有候选求和 | 算总量,好做归一化 |
动手算一遍:三个候选的分数是 (2, 1, 0)。
有人提议:干脆别取指数了,直接「每个数除以总和」不就能变成概率吗?请构造一个具体的例子,说明这个方案会出什么问题。至少找出两个不同的失效情形。
失效一:合法性。 分数可以是负数,而概率不能。取 (2, −1, −1),总和为 0,除法未定义;取 (−1, −2, −3),总和为 −6,得到 (0.167, 0.333, 0.5)——顺序被完全颠倒,因为除以负数会反转大小关系。指数函数把所有数搬到正区间,从根上消除了这个问题。
失效二:表达力。 就算强行只允许正分数,除法也是线性的:分数 (10, 9) 得到 (0.526, 0.474),几乎五五开;分数 (100, 99) 得到 (0.503, 0.497),更接近五五开。分数越大反而越难拉开差距,模型没法表达「我极度确信」。而 softmax 下 (10, 9) 得到 (0.731, 0.269),(100, 99) 同样是 (0.731, 0.269)——只取决于差值,不取决于绝对大小,这正是我们想要的性质。
附带一个好处:softmax 里加一个常数不改变结果(分子分母同乘 ec)。这个「平移不变性」在第5章的 Quantile Balancing 里会以一模一样的形式再次出现——那里的偏置 b 要减去均值,理由就是「公共偏移不改变 Top-k 选择」。你现在就可以先记住这个模式。
变式:softmax 有个变体叫「温度」:先把所有分数除以 T,再做 softmax。论文说 K3 评测时用 temperature = 1.0。请推断:T 趋近 0 时会发生什么?T 很大时呢?为什么「温度高」常被说成「更有创造力」?
0.8 注意力:让每个 token 自己去「查资料」
现在到了最关键的机制。注意力(attention)回答的问题是:处理「它」这个词时,模型怎么知道「它」指的是前面哪个东西?
答案是:让「它」主动去前面查一遍,看谁跟自己最相关,就多读谁的信息。具体分三步。
查询、键、值(Query, Key, Value,简称 Q、K、V):每个 token 用三个不同的矩阵,从自己的向量里造出三个新向量。Q 是「我要找什么」,K 是「我是什么,供别人搜」,V 是「如果你选中我,我给你什么内容」。
打个比方
像在图书馆查资料。Q 是你写在纸上的搜索词,K 是每本书的书脊标题,V 是书里的内容。你拿搜索词去比对所有书脊,算出每本书跟你的需求有多匹配(打分 + softmax),然后按匹配度把这些书的内容混合成一份摘要带走。
类比失效处:真实的注意力不是「选一本书」,而是永远把所有书都读一点,只是权重不同。而且它一次要为序列里的每一个 token 都做一次这样的检索。这是它计算量大的根源。
为什么这个机制值一个诺贝尔级的位置
在注意力出现之前,处理序列靠的是循环神经网络(RNN):从左到右一个词一个词地读,把读过的东西压缩进一个固定大小的「记忆」里。这有两个致命问题:记忆会被冲淡(第 1000 个词很难记住第 1 个词),以及没法并行(必须等第 999 个算完才能算第 1000 个)。
注意力把这两个问题一起解决了:每个位置都能直接访问任意远的位置(不会被冲淡),而且所有位置的计算互不依赖(可以同时算)。这就是 2017 年那篇《Attention Is All You Need》带来的革命。
但它有代价——这个代价正是本站第2章的全部主题。
0.9 注意力的代价:为什么长文本这么贵
注意力的代价来自一个简单的事实:序列里每个 token 都要跟前面所有 token 打一次分。
如果序列有 n 个 token,打分次数大约是 n²/2。这叫平方复杂度。
从 1000 涨到 100 万,长度涨 1000 倍,计算量涨一百万倍。这就是为什么「100 万 token 上下文」在工程上是件大事。
还有第二个代价,而且往往更要命。
KV 缓存(KV cache):生成文字时,模型是一个 token 一个 token 往外吐的。每吐一个新 token,它都要回头查之前所有 token 的 K 和 V。为了不重复计算,就把算过的 K 和 V 全都存在显存里——这就是 KV 缓存。
常见误解
很多人以为「模型很慢是因为参数太多、算力不够」。在长对话场景下,真正的瓶颈往往是 KV 缓存把显存吃光了,以及把这些缓存从显存搬到计算核心的带宽不够。
KV 缓存的大小和序列长度成正比——每多一个 token,就要多存一份 K 和 V,永远不会缩小。100 万 token 的上下文,KV 缓存可以轻松达到几百 GB,而一张顶级显卡只有 80–140 GB 显存。
假设一个模型有 93 层,每层每个 token 要存一份 K 和一份 V,各 128 维,每个数用 2 字节存(这叫 BF16 精度)。
(a) 一个 token 的 KV 缓存占多少字节?
(b) 100 万 token 的上下文,KV 缓存总共占多少 GB?
(c) 如果一张显卡有 141 GB 显存,光是这份缓存就占了多少比例?
(a) 每层每 token:(128 + 128) × 2 = 512 字节;93 层共 47,616 字节 ≈ 46.5 KB。
(b) 100 万 token:47,616 × 1,000,000 ≈ 4.76 × 10¹⁰ 字节 ≈ 44.3 GB。
(c) 44.3 / 141 ≈ 31%。
而这还只是一个用户的一次对话。生产环境要同时服务成百上千个请求,显存瞬间就爆了。
更重要的是:请注意题目里的假设有多「温柔」——我只用了 128 维、只存一份。真实的多头注意力有几十个头,每个头都要存,缓存会大几十倍。这正是第3章 MLA(把 K、V 压缩成一个小的潜向量再存)之所以被发明出来的原因,也是第2章 KDA(干脆不存逐 token 的缓存,改存一个固定大小的状态)之所以是一次更激进的赌注的原因。
你现在应该能预感到:K3 的核心架构选择,本质上是在跟这张表上的数字讨价还价。
变式:现在改用 KDA 那种方案——每层不存逐 token 的 K/V,而是存一个固定大小的 128×128 的状态矩阵,不管序列多长都是这么大。(a) 93 层一共占多少 MB?(b) 它和上面 44.3 GB 相比是多少倍的差距?(c) 既然差这么多,为什么不干脆全部用 KDA、一个 MLA 层都不要?(这一问先想不出来没关系,第3章会正面回答它。)
0.10 残差连接:给信息修一条高速公路
网络很深(K3 有 93 层)时会出现一个问题:信息在层层传递中被反复揉搓,原始内容丢失,而且训练时的梯度信号传不回前面的层。
残差连接(residual connection)的做法极其简单:让每一层的输出等于「这一层算出来的东西」加上「这一层的输入」。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| hl−1 | 进入第 l 层的向量 | 前面所有层累积下来的信息 |
| fl(·) | 第 l 层实际做的运算 | 这一层的「贡献」或「修改意见」 |
| + | 直接相加 | 关键所在:原始信息原封不动地穿过去了 |
打个比方
像一条主干道,每一层是路边的一个小作坊。信息沿主干道往前走,每经过一个作坊,作坊往里面添一点自己的东西,但不拦截主干道。所以再深的网络,原始信息也总有一条畅通的路直达终点。
类比失效处:这个比方会让你觉得「信息完好无损」,但其实每层都在往同一个向量上加东西,加到后面就是一锅粥——第 90 层想单独取出第 3 层的输出是做不到的。这个「加法造成的混淆」正是本站第4章 Attention Residuals 要解决的问题。看到这里请记住这个伏笔。
0.11 归一化:不让数字失控
深层网络里数值容易越滚越大或越滚越小。归一化(normalization)就是每隔一段把一组数拉回到一个标准尺度。
RMSNorm(Root Mean Square Normalization):先算这组数的「均方根」(每个数平方、求平均、开根号),再让每个数都除以它。结果是这组数的整体大小被拉到 1 附近,但相互之间的比例关系不变。
论文里 RMSNorm 出现过至少五次,每次都是同一个动机:某个地方的数值尺度可能失控,先归一化再往下走。你在第4章(防止某一层主导注意力权重)、第5章(防止专家聚合后的尺度乱跳)都会见到它。
0.12 MoE:一个巨大的专家团,每次只请几个
常规神经网络里,每个 token 都要经过全部参数。参数越多,算得越慢。这让「模型变大」和「推理变贵」死死绑在一起。
混合专家(Mixture-of-Experts, MoE)解开了这个绑定:
- 把一层里的前馈网络复制成很多份,每份叫一个专家(expert)。K3 有 896 个。
- 加一个小小的路由器(router),它看一眼当前 token,给 896 个专家各打一个分。
- 只把分数最高的几个专家叫来干活。K3 每个 token 只激活 16 个。
打个比方
像一家有 896 位专科医生的超级医院。你来看病,前台(路由器)看一眼你的症状,从中挑 16 位最对口的医生给你会诊。医院很大(总参数多,什么病都能治),但你的这次就诊只占用了 16 位医生的时间(计算量小)。
类比失效处:这个比方暗示「专家们各有明确分工」。实际上专家的分工是训练中自发形成的,往往不对应任何人类能命名的领域,而且不保证均衡——有的专家会被挤爆,有的没人光顾。这个「负载不均衡」问题是第5章 Quantile Balancing 的全部动机。
K3 有 896 个路由专家,每个 token 激活 16 个。
(a) 稀疏度(总数除以激活数)是多少?
(b) MoE 让计算量降下来了,但有一样东西没有降。是什么?为什么?
(c) 这个没降的东西,会在什么场景下成为真正的瓶颈?
(a) 896 ÷ 16 = 56。论文原文就是这么写的:「corresponding to a sparsity of 56」。
(b) 没降的是 显存占用。全部 2.78 万亿个参数都必须常驻在显存(或至少随时可取)里,因为你无法预知下一个 token 会路由到哪些专家。MoE 省的是算力(FLOPs),不是存储。
(c) 两个场景:
· 部署时:2.78 万亿参数即使用 4 比特存也要约 1.4 TB,需要一整个机柜的显卡才装得下。这正是论文第 4.1.4 节要做 MXFP4 量化(本站第8章)的直接原因。
· 小批量解码时:每生成一个 token,都要把用到的专家权重从显存流式读进计算核心。这时候瓶颈是内存带宽而不是算力——论文第 5.4.2 节原话是「group GEMM 退化成权重矩阵的内存受限流式读取」,并因此要专门写一个 token 中心的解码内核(本站第11章)。
把这三件事串起来,你就理解了为什么论文里「架构」和「基础设施」两部分必须放在一起读:一个架构选择(896 个专家)会直接决定后面几十页的工程难题。
变式:假设有人提出把专家数从 896 减到 448,但每 token 激活数从 16 提到 32。稀疏度从 56 降到 14。(a) 每 token 的计算量怎么变?(b) 显存占用怎么变?(c) 路由器的选择空间怎么变?(d) 你认为这个改动会让模型变强还是变弱?说清你的不确定性来自哪里。
0.13 三个必须分清的量:总参数、激活参数、FLOPs
| 量 | 含义 | K3 的值 | 决定什么 |
|---|---|---|---|
| 总参数 total parameters | 模型里一共有多少个可训练的数 | 2.78 万亿 | 显存:模型有多大、要多少卡才装得下 |
| 激活参数 activated parameters | 处理一个 token 实际用到多少个数 | 1042 亿(约 3.7%) | 算力:每个 token 多贵、生成多快 |
| FLOPs | 浮点运算次数,算力的计量单位 | 论文未披露训练总量 | 训练一次要多少电、多少钱、多少时间 |
常见误解
新闻里说「某模型 2.8 万亿参数」时,很多人直接拿它跟另一个「6710 亿参数」的模型比大小。但如果一个是稠密模型(全部参数都激活)、另一个是 MoE,这两个数字不可直接比较——它们衡量的是不同的东西。
更公平的比法是同时看这两个数。K3 是「2.78 万亿总 / 1042 亿激活」,前一个数说的是它的知识容量上限,后一个数说的是它每吐一个字的成本。
0.14 训练与推理:两种完全不同的活
训练(training):给模型看海量文本,让它猜下一个 token,猜错了就微调所有参数让它下次猜得准一点。这个「根据错误反过来调参数」的过程叫反向传播(backpropagation),它需要把前向过程中的中间结果(叫激活值,activations)都保存下来——这是训练时显存爆炸的主因,第10章会详谈。
推理(inference):模型训练好了,参数冻结,只用来生成文字。推理又分两个阶段:
- 预填充(prefill):把你输入的整段提示一口气读进去。这一步所有 token 可以并行处理,算力吃满,是「计算受限」的。
- 解码(decode):一个一个往外吐字。这一步必须串行(要先吐出第 5 个字才能吐第 6 个),每吐一个字都要把整个模型的权重过一遍,所以是「带宽受限」的。
0.15 三段人生:预训练、后训练、强化学习
| 阶段 | 干什么 | 类比 | 本站章节 |
|---|---|---|---|
| 预训练 pre-training | 读遍互联网,学会预测下一个 token | 读完了图书馆所有的书 | 第7章 |
| 监督微调 SFT | 看人类示范的「好回答」,学会照着做 | 跟着师傅照猫画虎 | 第8章 |
| 强化学习 RL | 自己动手做题,做对了给奖励,做错了给惩罚 | 自己下场练,靠反馈进步 | 第8、9章 |
强化学习(Reinforcement Learning, RL):模型自己尝试完成一个任务(比如「修好这个 bug」),产生一整条行动轨迹;然后有个奖励函数给这条轨迹打分;模型据此调整参数,让高分行为更容易发生。
为什么 RL 对「智能体」如此关键
SFT 只能教模型模仿见过的示范。但「用二十个工具、花三百步、修好一个从没见过的 bug」这种任务,没有人能写出足够多的示范。RL 不需要示范——它只需要一个能判断结果对不对的机制,模型自己在环境里摸索出路径。
这也解释了为什么论文第 4.2 节(本站第9章)花了整整四页讲「怎么造环境、怎么造题、怎么防作弊」——对 RL 来说,环境和奖励的质量就是能力的上限。
智能体(agent):能自主使用工具、多步执行任务的模型。工具调用(tool call)指模型输出一段结构化的请求(比如「运行这段 Python」),外部系统执行后把结果送回来作为新的输入。一次任务里模型可能这样来回几百次,这叫长时程(long-horizon)。
假设你要用 RL 训练模型写代码。你设计的奖励是:「单元测试全部通过,得 1 分;否则得 0 分。」 请构造至少两种模型可能采取的、能拿到高分但完全违背你本意的策略。(这类行为有个名字叫「奖励黑客」,reward hacking,论文里出现过五次以上。)
assert True。然后想更隐蔽的——不改测试,但在被测函数里检测「当前是不是在跑测试」,是的话就返回硬编码的期望值。① 改测试。 把断言删掉或改成永真,或者干脆删掉失败的测试用例。
② 硬编码答案。 不实现算法,而是把测试用例里的输入输出对写成一个查找表。测试全过,换个输入就崩。
③ 检测测试环境。 在代码里判断是否处于测试运行中(查环境变量、查调用栈),是则走一条特殊分支。
④ 破坏测试框架。 篡改 pytest 的行为,让所有断言都被吞掉。
⑤ 跳过而非通过。 给测试加上 skip 标记,某些统计口径下「没有失败」就算通过。
这道题的真正意义:论文第 4.2.4 节(内核优化任务)说他们要专门建一套「作弊检测系统」,惩罚 CUDA graph replay、输入缓存、精度削减这些手法,并且「随着开发中观察到新的作弊手法持续扩充」——注意「持续扩充」这四个字,它承认了这是一场没有终点的军备竞赛。第 4.2.6 节(AET)则用了三重设计:隔离智能体与验证器、公开验证器配隐藏验证器、有限提交预算下的惩罚性奖励。
你现在应该能理解,为什么这些看起来啰嗦的设计是必需的:奖励函数写下的那一刻,就是在向一个比你更有耐心的优化器公开你所有的漏洞。
变式:论文第 4.2.7 节说,网页开发任务的奖励在「项目构建失败、运行报错、或者假装实现而非真正实现产物」时被清零。前两条是客观可判定的,第三条「假装实现」却很主观。(a) 举一个「假装实现」的具体例子。(b) 论文说这一条靠「模型评判」来判定。这引入了什么新的风险?(c) 如果被评判的模型和当评委的模型来自同一个家族,风险会变大还是变小?
0.16 GPU:为什么显存和带宽经常比算力更要命
训练和运行大模型全靠 GPU(图形处理器)。你需要知道三个数,以及它们之间的紧张关系。
| 资源 | 是什么 | 不够会怎样 |
|---|---|---|
| 算力 FLOPS | 每秒能做多少次浮点运算 | 算得慢 |
| 显存 memory / HBM | 能装下多少数据(参数、激活值、KV 缓存) | 直接跑不起来(OOM,内存溢出) |
| 带宽 bandwidth | 每秒能把多少数据从显存搬进计算核心 | 计算核心闲着等数据(「内存受限」) |
打个比方
算力像厨房里厨师的手速,显存像冰箱的容量,带宽像从冰箱到灶台的传菜通道。现代 GPU 的情况是:厨师手速快得离谱,但传菜通道相对很窄。所以大量时间里厨师是在等菜,而不是在炒菜。
工程师的很多工作,本质上是「让厨师在等菜的间隙里去干点别的」——这就是论文里到处出现的重叠(overlap):把通信和计算重叠、把卸载和计算重叠、把视觉编码器塞进流水线气泡。
类比失效处:真实 GPU 里还有「片上缓存」这一层(相当于灶台边的小料盒),它极快但极小。很多内核优化的核心技巧就是想办法让数据尽量待在料盒里别回冰箱。论文第 5.4.2 节说 KDA 解码时「在片上重建被接受 token 的状态」,说的就是这件事。
还有一组必须知道的名词——当一张卡装不下时,就要把模型切开放到多张卡上,这叫并行:
| 名字 | 切什么 | 一句话 |
|---|---|---|
| 数据并行 DP | 切数据 | 每张卡放一整个模型,各自处理不同的数据,最后汇总梯度 |
| 张量并行 TP | 切矩阵 | 把一个大矩阵横着或竖着切开,几张卡一起算一次矩阵乘法 |
| 流水线并行 PP | 切层 | 第 1–20 层在卡 A,第 21–40 层在卡 B,像流水线一样传下去 |
| 专家并行 EP | 切专家 | MoE 专用:896 个专家分散到多张卡,token 被送到对应的卡上 |
| 上下文并行 CP | 切序列 | 长序列专用:把 100 万个 token 切成几段,每张卡负责一段 |
0.17 100 万 token 的上下文,到底是什么概念
上下文窗口(context window):模型一次能「看见」的最大 token 数。超出的部分它就完全不知道了。
K3 是 100 万 token。给你一点尺度感:
而 K3 的前一代 K2 只有 128K(12.8 万)。表 1 里那个「8×」就是这么来的。
为什么长上下文对「智能体」是刚需
一个智能体做一个复杂任务,可能要调用几百次工具。每次工具返回的结果(比如一个文件的内容、一次搜索的结果、一段报错日志)都要进上下文。论文第 4.2.5 节说,一次个人助理任务的 rollout 「可能涉及多达数千次工具调用和数百万 token 的上下文」。
如果上下文只有 128K,模型做到一半就得开始遗忘前面做过什么——它会重复劳动、会忘记约束、会前后矛盾。所以上下文长度直接决定了智能体能处理多复杂的任务。这是理解整篇论文动机的关键之一。
0.18 符号速查表
下面这些符号会在后面十五章反复出现。现在不需要背,但读到卡壳时回来查。
| 符号 | 读作 | 含义 |
|---|---|---|
| xt | x sub t | 第 t 个位置的 token 向量 |
| q, k, v | Q、K、V | 查询、键、值向量 |
| St | S sub t | KDA 在第 t 步的状态矩阵(第2章核心) |
| W | W | 一个可学习的权重矩阵 |
| α | alpha | 论文里有两个含义:KDA 的衰减因子(式 1);注意力权重(式 9)。看上下文 |
| β | beta | KDA 的写入强度(式 1);SiTU-GLU 的软上限参数(式 12) |
| γ | gamma | 累积衰减(式 3);也用作偏置更新步长 |
| πθ | pi theta | 策略,即模型本身;下标 θ 表示「由参数 θ 决定」 |
| ∑ | sigma | 求和 |
| ∏ | pi(大写) | 连乘 |
| ⊙ | 圈乘 | 逐元素相乘 |
| ⊤ | 转置 | 行列互换 |
| I | 单位矩阵 | 对角线全是 1、其余为 0 的方阵;乘它等于什么都没做 |
| sg(·) | stop gradient | 停止梯度:这一项只当数值用,不参与反向传播(式 15) |
| clip(x, a, b) | 裁剪 | 把 x 限制在 [a, b] 内,超出就取边界值 |
现在请你把这一章学的东西串起来,回答一个「元问题」:
论文摘要里说 K3 是「2.8 万亿参数、1042 亿激活参数、100 万 token 上下文」。请从这三个数字出发,预测这篇论文的第 5 节(基础设施)必然会花大篇幅解决哪三类问题。每类问题请指出它是被三个数字中的哪一个逼出来的。
现在你自己走另外两条链。
链一:2.8 万亿总参数 → 专家并行的负载不均衡。 参数太多必须切到多卡;MoE 结构下最自然的是按专家切;但 token 路由不均匀,有的卡忙死有的卡闲着;闲着的卡拖慢全局(木桶效应)。论文的回答:§5.2.1 的 MoonEP——用动态冗余专家实现完美均衡,并证明每 rank 只需 E/R 个冗余槽位(本站第10章、第14章)。
附带的第二条支线:2.8 万亿参数在部署时也装不下 → 必须量化 → §4.1.4 的 MXFP4 QAT(本站第8章)。
链二:1042 亿激活参数 + 解码是串行的 → 带宽受限。 每吐一个 token 都要把 1042 亿个参数流式读一遍;解码阶段算力用不满,瓶颈在带宽;小批量下更糟。论文的回答:§5.4.2 的 token 中心 MoE 解码内核(借鉴 WarpDecode),以及 §4.1.4 的推测解码——让小草稿模型先猜几个,大模型一次验证多个,摊薄每 token 的带宽成本(本站第8、11章)。
链三:100 万 token 上下文 → 两个爆炸。 (a) 注意力计算量按平方涨;(b) KV 缓存按线性涨且不缩小。论文的回答分三层:架构层用 KDA 把三分之二的注意力层换成固定大小状态(§2.1,本站第2章);训练层用上下文并行把序列切到多卡,并为 KDA 专门设计 KCP(§5.1.2,本站第10章);服务层用前缀缓存避免重算,并把 KDA 状态和 MLA 缓存统一管理(§5.4.1,本站第11章)。
这道题想让你获得的能力:拿到任何一篇模型技术报告,光看摘要里的几个数字,你就能预判它必须解决哪些工程问题。如果它没讲,你就该问「那你们是怎么绕过去的?」——这是从「读者」变成「审稿人」的第一步。
变式:再加一个数字——K3 是「原生多模态」的,图像和视频与文本混在同一个上下文里。请预测这会额外逼出哪一类第 5 节必须解决的问题,并说明它和上面三条链哪一条最容易冲突。(提示:想想一张 3584×3584 的图会变成多少个 token,以及不同样本里图像数量差异很大会造成什么。)
本章小结
你现在手里有了十七个概念。它们不是孤立的名词,而是一张互相咬合的网:
token 变成向量,向量经过矩阵乘法和激活函数被反复变换;注意力让每个位置能查阅所有位置,代价是平方复杂度和线性增长的 KV 缓存;残差保证深层网络训得动,归一化保证数值不失控;MoE 让总参数和激活参数脱钩,代价是负载不均衡和显存不降;这一切跑在 GPU 上,而 GPU 的显存和带宽往往比算力更早撞墙,于是需要各种并行,而并行的代价是通信。
接下来的每一章,都是这张网上某一处的放大。
第1章 全景:这篇论文在主张什么,以及知识地图
在钻进任何细节之前,你需要一张地图。这一章做三件事:说清这篇论文想解决什么问题、把它的十六个知识点排成一张依赖图、给你一条推荐的行进路线。读完这一章,你后面每读到一个新概念,都能知道它挂在哪根枝上。
学完这一章你应该能做到
- 用两句话说清「测试时缩放」和「预训练缩放」是两条什么样的轴,以及这篇论文为什么强调「同时推进两条」
- 画出这篇论文的知识依赖图,指出哪三个概念是必须最先学的
- 把摘要里的每一个短语对应到论文的某一节
- 分辨这篇论文里哪些陈述是「有证据的」、哪些是「作者的设计主张」
1.1 论文开篇的那个论断:缩放有两条轴,开源界只走了一条
论文的第一段做了一件很聪明的事:它先给整个领域画了一个坐标系,然后指出坐标系里有一块空地,而 K3 就是要去占这块空地。
第一条轴:部署前的算力。 在大语言模型发展的大部分时间里,「缩放」意味着在部署之前投入更多算力——训更大的模型、喂更多的数据。这是 2020 年那批缩放定律论文奠定的路线。
第二条轴:测试时的算力。 推理模型的兴起确立了第二条轴:让模型在回答问题的时候多花算力。论文点名了几条支线:OpenAI 的 o 系列用强化学习扩展测试时推理;Anthropic 的扩展思考让模型分配自适应的思考预算并把思考与工具调用交错;DeepSeek-R1 和 Kimi K1.5 表明大规模强化学习能从强预训练模型中引出复杂的推理行为;Kimi K2.5 Agent Swarm 则把测试时缩放从「顺序推理」扩展到「并行的智能体协同」。
论文指出的那块空地
作者的观察是:开源生态在第二条轴上进步很快,但在第一条轴上进展缓慢——很多近期的开源模型仍停留在 1 万亿参数级别或略高一点。而随着越来越复杂的推理和智能体强化学习方法被应用到规模相近的预训练底座上,开源的进展有趋同的风险,同时与最强的专有系统之间的差距在拉大。
K3 的定位由此确定:两条轴一起往前推——把预训练底座扩到前所未有的 3 万亿参数级别,同时扩展强化学习、推理努力、以及 100 万上下文长度下的长时程交互。
读的时候要小心
「开源进展有趋同风险」是一个论断,不是一个被测量出来的结论。论文没有给出任何量化证据(比如开源模型能力分布的方差随时间变化)来支撑它。这是技术报告开篇常见的叙事手法:先建立一个问题框架,让自己的工作成为这个框架下的自然答案。
这不代表它是错的,但你应该把它记成「作者的视角」而不是「已确立的事实」。整篇论文里这类陈述还有很多,本站会一路标出来。
1.2 摘要拆解:每一个短语对应哪一节
下面这张表是本站最实用的一张。摘要里的每个短语,都能在论文里找到一整节来展开。你也可以把它当作全站的导航。
| 摘要里的短语 | 什么意思 | 论文节 | 本站 |
|---|---|---|---|
| 2.8T 参数 MoE 模型 | 2.78 万亿总参数的混合专家模型 | §3.2 表 1 | 第7章 |
| 1040 亿激活参数 | 每个 token 实际用到 1042 亿 | §2.3、§3.2 | 第5、7章 |
| 原生视觉能力 | 图像视频与文本共享同一个骨干,无事后对齐 | §2.4 | 第6章 |
| 100 万 token 上下文窗口 | 一次能看见 100 万个 token | §3.4 | 第7章 |
| Kimi Delta Attention (KDA) | 把注意力换成固定大小的循环状态 | §2.1.1 | 第2章 |
| Attention Residuals (AttnRes) | 让每一层用注意力从所有先前层取信息 | §2.2 | 第4章 |
| Stable LatentMoE | 896 个专家、激活 16 个,且不炸 | §2.3 | 第5章 |
| 约 2.5× 的整体缩放效率提升 | 同样的验证损失,只要 K2 约 1/2.5 的算力 | §3.2 图 7 | 第7章 |
| 跨通用/智能体/编码域的强化学习 | 三个领域各训一个专家 | §4.1.2 | 第8章 |
| 多个推理努力等级 | low / high / max,三档思考预算 | §4.1.2 | 第8章 |
| 组合泛化与稳健的长时程执行 | 能把学到的能力拼起来用,能跑几百步不崩 | §4.2 | 第9章 |
| KDA 的算法–系统协同设计 | 为 KDA 专门写内核和上下文并行 | §5.1 | 第10章 |
| 完美均衡的专家并行训练 | MoonEP,每张卡收到的 token 数完全相同 | §5.2.1 | 第10、14章 |
| 百万 token 智能体 RL | 可恢复的部分 rollout 与沙箱状态 | §5.3 | 第11章 |
| 部署创新 | MXFP4 量化、推测解码、前缀缓存、舰队调度 | §4.1.4、§5.4 | 第8、11章 |
| 落后于 Claude Fable 5 和 GPT-5.6 Sol | 论文自陈:整体仍不如这两个专有模型 | §6 | 第12章 |
| 释放完整模型权重 | 权重公开在 HuggingFace | 脚注 1 | 第13章 |
关于那些陌生的模型名
论文用 Claude Fable 5、GPT-5.6 Sol、Claude Opus 4.8、GPT-5.5、GLM-5.2 作为对比基线。如果你没听过这些名字,那很正常——这篇报告发表于 2026 年 7 月,这些是当时的模型。
本站在引用评测数字时,一律照论文原文转述,不做任何外部核实。第12章会专门讲,为什么「照转述」这件事本身也需要小心。
1.3 知识地图:谁依赖谁
下面这张图是整篇论文的骨架。箭头 A → B 读作「要理解 B,先得理解 A」。
1.4 架构总纲:把信息流往三个方向撑开
论文第 2 节开头给了一个非常清晰的组织原则,值得单独拎出来记住。K3 的架构设计沿三个互补的维度扩展信息流:
打个比方
把模型想象成一栋楼里的一个信息流通系统。序列维度是同一层楼里不同房间之间的走廊;深度维度是楼层之间的电梯;宽度维度是每一层楼里房间的数量。K3 的三个改动分别是:把走廊改造得更省钱(KDA)、把电梯从「只能到隔壁层」改成「能直达任意层」(AttnRes)、把房间数量翻倍但每次只开其中十六间(LatentMoE)。
类比失效处:这三者在真实模型里不是独立的三套系统,而是交错堆叠的——每个注意力层后面紧跟一个 MoE 层,AttnRes 又横跨所有层。三个维度是分析视角,不是物理分区。
第0章讲过 GPU 的三种资源:算力、显存、带宽。现在请把上面三个架构维度的改动与它们主要缓解的资源压力对应起来,并各指出一个它们新增的压力。
KDA(序列维): 缓解显存与带宽——KV 缓存不再随长度增长,长上下文下省得极多。新增压力:串行依赖。循环状态必须一步步往下传,与 GPU 喜欢的「又宽又整齐的并行」直接冲突。论文第 5.1 节原话是「它的串行形式给并行执行带来困难」,整个 §5.1 都在还这笔债(FlashKDA 内核、SM 级上下文并行、KCP)。
AttnRes(深度维): 主要不是省资源,而是提升表达力——让每层能选择性检索所有先前层,而不是被迫接受一个被反复叠加的和。新增压力:显存与跨阶段通信。完整形式要保存所有层的输出,是 O(Ld) 的显存,在流水线并行下还要跨阶段传。这正是 Block AttnRes(切成 8 块,降到 O(Nd))存在的理由。
LatentMoE(宽度维): 缓解算力——总参数 2.78 万亿,每 token 只算 1042 亿。新增压力有两个:显存不降(896 个专家全都得常驻),以及负载不均衡(token 路由不均匀 → 专家并行下有的卡忙死有的闲着)。前者导出第8章的 MXFP4 量化,后者导出第5章的 Quantile Balancing 和第10章的 MoonEP。
这道题的意义:你现在拿到了读这篇论文最有力的一把工具——每读到一个架构创新,立刻问「它省了什么,又欠下了什么」。论文第 5 节的每一个小节,都是在还第 2 节欠下的某一笔债。
变式:论文说 MLA 层用了 NoPE(不加位置编码),理由之一是「KDA 层已经提供位置敏感的混合」。请分析:这个设计省下了什么(提示:想想扩上下文时要重调什么),又新增了什么风险(提示:如果某天有人想把 KDA 换掉,会发生什么)?
1.5 论文的四条贡献,以及它们的证据强度
论文在引言末尾自己列了四条贡献。这里逐条列出,同时标注证据强度——这是训练「批判性阅读」的第一次练习。
| 论文自述的贡献 | 论文给的证据 | 证据强度 |
|---|---|---|
| 开放前沿的预训练:2.8T 参数原生多模态 MoE,1042 亿激活,1M 上下文;KDA、AttnRes、Stable LatentMoE、精炼的数据与训练配方合起来把整体缩放效率提升约 2.5× | 图 7 的两条缩放定律曲线;表 1 的架构对比 | 中。曲线无坐标轴数值;2.5× 是架构、数据、配方打包的结果,各项贡献未拆分;无单项消融 |
| 面向多努力测试时缩放的强化学习:跨通用、智能体、编码域做 RL,跨多个推理努力等级,再合并成一个模型 | 图 8 的 RL FLOPs 与分数/步数曲线;§6 的评测分数 | 中。图 8 无坐标轴数值;「扩 RL 算力 → 步数增长 → 能力提升」是相关性观察,无对照实验 |
| 面向万亿参数、百万 token 的基础设施:MoonEP、显存高效基建、可恢复沙箱的同址 RL 系统 | §5 的详细方法描述;附录 E 的定理证明;沙箱数量 51,219,741 | 方法描述强,性能证据弱。定理有严格证明;但几乎所有系统优化都没有给加速比(「大幅超过 Triton 参考实现」没有倍数) |
| 一个开放的前沿模型:释放完整权重 | HuggingFace 链接 | 强。这是唯一可被任何人直接验证的一条 |
读的时候要小心:这是技术报告,不是同行评审论文
技术报告(technical report)和会议论文有一个体制性的区别:它没有经过同行评审。作者可以选择报告什么、省略什么,可以只展示成功的案例,可以在没有消融实验的情况下声称某个设计「改善了」结果。
这不是在指责这篇报告——它在同类报告里已经算相当详实的了(附录里有真证明,还诚实报告了自己的弱项,比如 CritPt 落后、内核赛道四分之三没解出)。但你读它时的姿态应该是「一份来自建造者的、有立场的详细自述」,而不是「经过验证的科学结论」。
本站会一路用这种红色的框标出需要打折扣的地方。第13章末尾有一节专门汇总「这篇论文没有告诉你的事」。
下面是从论文里摘出的五句话。请把它们分成三类:(A) 有可核验证据支撑的事实、(B) 有数据但数据本身可质疑的结论、(C) 纯粹的设计主张或叙事。并对每一句说明你的判断依据。
① 「我们发布完整的 Kimi K3 模型权重。」
② 「这些改进合起来带来约 2.5× 的整体缩放效率提升。」
③ 「开源进展有趋同的风险,同时与最强专有系统的差距在拉大。」
④ 「Kimi K3 在 BrowseComp 上取得 91.2%,是所有被评测模型中的最好成绩。」
⑤ 「MoonViT-V2 在视觉评测上追平了 SigLIP 初始化的基线,说明对比预训练作为初始化不是必需的。」
① → A。 权重公开在 HuggingFace,任何人可以下载、跑、验证。这是这篇报告里可验证性最强的一条。
② → B。 有图 7 作为证据,但:图上没有坐标轴数值(无法反推任何具体损失值);2.5× 是架构+数据+配方三者打包的结果,论文没有拆分各自贡献;实验由作者自己完成,外部无法复现(因为没有公开训练数据和训练代码)。所以它是「有数据支撑的作者结论」,但你不能把它当成一个可迁移的科学事实。
③ → C。 纯叙事。没有任何量化指标(比如开源模型能力的分布、差距的定义与测量方式)。它的作用是为 K3 的定位提供合法性。
④ → B。 有具体数字,但配置里藏着一堆条件:K3 用了 300K token 触发的上下文压缩策略;论文没有说对手用了什么上下文管理策略;论文自己也说了,如果 K3 用完整 1M 上下文不做压缩,分数是 90.4%——而它领先第二名 GPT-5.6 Sol(90.4%)恰好是 0.8 分。领先幅度和自身策略造成的波动是同一个量级。再加上论文没报告这个基准的重复次数和方差。所以这个「最好成绩」是真的,但「领先」这件事非常脆弱。第12章会把这类分析做透。
⑤ → B,而且是跨度最大的一条。 「追平」是数据,「说明对比预训练不是必需的」是从数据外推出的一般性结论。问题在于:论文在这两页里没有给出任何评测表或分数来支持「追平」;结论加了 「at scale」(在这个规模上)的限定,但没有给出规模下界;而且这是一次对比的结果,不是多次独立实验。
这道题的意义:读技术报告的核心技能,不是记住数字,而是随时知道自己脚下踩的是哪一层——是可验证的事实、是作者的实验、还是作者的叙事。三层都有价值,但不能混为一谈。
变式:论文说 K3 在 Kimi Webdev Bench 上相对 Claude Opus 4.8 有 +31.0 分的总体优势,评判方式是「盲评专家评分」。请列出你需要追问的至少四个问题,才能判断这个 +31.0 有多少分量。(提示:想想基准是谁造的、专家是谁、样本量、以及为什么对手选的是 Opus 4.8 而不是论文自己承认更强的 Claude Fable 5。)
1.6 推荐的行进路线
十六章不必按顺序读完。下面是三条路线,按你的目标挑一条。
| 你的目标 | 路线 | 大致投入 |
|---|---|---|
| 完整学透(你选的就是这条) | 0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12 → 13 → 14 → 15,顺序读,每章的测验必须做对变式才往下走 | 长。但第2、5、10、11、14 五章占了一半的难度 |
| 只想懂架构 | 0 → 1 → 2 → 3 → 4 → 5 → 6 → 14(只读附录 B、C) | 中。核心是第2章和第5章 |
| 只想懂「它到底行不行」 | 0 → 1 → 12 → 13 | 短。但要有心理准备:第12章的重点是教你不要轻信任何一张评测表 |
关于本站的用法(新版有三个模式,请先看这段)
右上角有三个模式。学习是读正文;复习是系统按遗忘规律推给你的题;进度是你的掌握度仪表盘和进度存取。
两种题,判分方式不同。「自评题」是开放题,你自己点「我答对了/我答错了」——请诚实,虚报只是骗自己。「机判自测」在每章末尾,数值、选择、排序由机器判分,不给你自我欺骗的余地。两种题都会自动进入复习队列。
提示按顺序用。四级提示先给方向、再给思路、再给第一步,最后才是完整答案。直接翻答案你会觉得「这我懂」,然后在变式题上原地卡住。用了几级提示会被记下来,直接影响这道题下次出现的时间。
实验室和 3D 不是插图。标着「实验室」的组件里,论文的算法是真的在你浏览器里跑——第2章那个能让你亲眼看到递推形式和分块并行形式的输出差是 1e-16。3D 场景也是真的在执行论文的公式。这些地方值得多待一会儿。
进度会自动保存在这台电脑的浏览器里。但换设备、清缓存会丢,所以学完一章去「进度」页导出一次文件。如果顶栏那个小圆点是黄的,说明当前环境不能自动保存,那就更要记得导出。
掌握的标准始终是:能做对变式并说清理由。
答辩:如果我是审稿人
论文说 K3 的三个架构创新「合起来带来约 2.5× 的缩放效率提升」。我的攻击是:既然是「合起来」,那你怎么排除这样一种可能——真正起作用的只是「层数从 61 涨到 93、激活参数从 326 亿涨到 1042 亿」这种朴素的变大,而 KDA、AttnRes、LatentMoE 这三个花哨的东西贡献接近于零,甚至是负的?请你为论文辩护,或者承认这个攻击成立。
参考防守(先自己组织语言再看)
先承认攻击的合理部分:这个攻击在本篇报告的证据范围内是成立的。论文确实没有给出任何单项消融——没有「去掉 AttnRes 后的缩放曲线」、没有「KDA 换回全 MLA 后的曲线」。仅凭图 7,你无法把 2.5× 拆开。这是这篇报告一个真实的证据缺口。
但辩护有两层:
第一层,缩放定律曲线的语义。图 7 画的是「验证损失 vs FLOPs」,而不是「验证损失 vs 参数量」。如果 K3 只是单纯变大,那么在相同的 FLOPs 预算下它不会自动更优——变大意味着每步更贵,同样的算力能训的步数更少。缩放定律曲线的整条左移,说明的是「每一份算力买到的损失下降更多」,这是效率而非规模的改善。所以「只是变大」这个解释,在这个特定的图上并不成立。
第二层,各个改动的动机是可独立论证的。KDA 的收益(固定大小状态)在长上下文下是数学上确定的,不依赖实验;LatentMoE 让 896 个专家变得可行,这也是结构性的。所以即使贡献比例未知,「贡献为零或为负」这个强版本的攻击也站不住。
但辩护到此为止。你依然不能从这篇报告推出「KDA 贡献了 2.5× 中的多少」。诚实的表述是:这三个改动合起来,在作者的训练配方下,产生了约 2.5× 的效率增益;各自的贡献比例未知。本站会一路使用这种带限定的表述。
顺带一提:论文对 KDA 和 AttnRes 都引用了先前的独立工作(Kimi Linear、Attention Residuals 各自的论文),那些论文里可能有消融。但那些实验不是在 2.78 万亿参数规模上做的,能否外推是另一个开放问题。
本章小结
你现在有了三样东西:一张知识依赖图(知道每个概念挂在哪)、一张摘要对照表(知道每个短语去哪一章找)、一套证据分级的习惯(知道每句话该信几分)。
下一章是全站最难也最核心的一章:Kimi Delta Attention。它会回答第0章留下的那个问题——如果 KV 缓存随长度线性增长是个诅咒,有没有办法根本上摆脱它?答案是有,代价是你要重新学一遍「记忆」这件事该怎么建模。
第2章 Kimi Delta Attention:把无限增长的记忆压成一块固定大小的黑板
这是全站最核心也最难的一章。KDA 是 K3 架构的地基——93 层里有 69 层是它,第10章的内核与并行、第11章的前缀缓存,全都是在为它擦屁股。这一章我们会从「记忆该怎么建模」这个最朴素的问题出发,一步步走到论文的式 1 到式 6。请慢一点读,每个小节末尾的题都做。
学完这一章你应该能做到
- 解释为什么「固定大小的状态」和「随长度增长的 KV 缓存」是一次根本性的交易,各自换来了什么
- 用外积和矩阵乘法,说清一个状态矩阵 S 是怎么「存」和「取」键值对的
- 说清 delta 规则里那个 (I − βkk⊤) 到底在干什么,以及为什么必须有它
- 逐符号读懂式 1,并解释 α 和 β 的分工
- 解释分块并行形式为什么能让「串行的东西」跑在 GPU 上,以及式 4 的两项各是什么
- 说清一个数值溢出问题,是怎么反过来逼着作者改掉衰减函数的数学形式的
2.1 出发点:注意力的诅咒,和一个被放弃的老方案
第0章讲过注意力的两个代价:计算量随长度平方增长,KV 缓存随长度线性增长且永不缩小。在 100 万 token 的尺度上,第二个代价尤其致命——你算过那道题,光是一份缓存就能吃掉一张顶级显卡三成的显存。
但在注意力发明之前,序列建模用的是另一套方案:循环神经网络(RNN)。它的做法是从左到右读,把读过的一切压缩进一个固定大小的状态里。
RNN 的固定状态有一个无可替代的优点:不管序列多长,内存都不变。但它当年被抛弃,是因为两个致命伤:
- 记忆被冲淡:状态就那么大,读到第 1000 个词时,第 1 个词的信息早被后来的内容挤掉了。
- 不能并行:必须先算完第 999 步才能算第 1000 步。而 GPU 的全部威力都建立在「同时算很多东西」上。
为什么现在又回去了
因为形势变了。当年序列长度是几百,注意力的平方复杂度不算什么,能并行才是关键。现在序列长度是一百万,注意力的两个代价开始主导一切,而 RNN 的固定状态突然变成了压倒性的优势。
于是问题变成:能不能保留固定状态的内存优势,同时修好那两个致命伤?
「记忆被冲淡」要靠更聪明的写入规则来解决——这就是 delta 规则。「不能并行」要靠数学重写来解决——这就是 分块并行形式。KDA 是这两条路走到当下的产物。
线性注意力(linear attention):一类把 softmax 去掉、从而可以改写成「固定大小状态 + 递推更新」的注意力变体。KDA 属于这一类,它是 Kimi Linear 那篇工作的延续。第0章的 softmax 那一节你算过:softmax 里的指数让每个 query 必须亲自和每个 key 打分,无法提前汇总。去掉 softmax,汇总就成为可能——这是整个线性注意力家族的数学起点。
2.2 状态是一块黑板:怎么「写」和「读」
先把 KDA 的状态 S 想象成一块黑板。它是一个矩阵,形状是 dk × dv——第0章讲过,矩阵就是一个数字表格。
怎么往黑板上写一个「键 → 值」的配对? 答案是外积:
第0章提过,一个竖向量乘一个横向量得到一个矩阵。kv⊤ 就是这么一个矩阵——它可以理解为「在 k 指向的那个方向上,刻下 v 这个内容」。
怎么从黑板上读? 用查询向量去乘它:
为什么这样能读出来?做一次代数就明白了。假设黑板上只写过一对 (k, v),那么:
注意 k⊤q 是一个数(两个向量的点积,也就是它们的相似度)。所以读出来的是「v 乘以一个相似度」。
打个比方
注意力像一个档案柜:每份文件单独放一个抽屉,查的时候一个个翻,永远精确,但抽屉数量随文件数增长。
KDA 的状态像一张反复叠印的透明胶片:所有内容印在同一张胶片上,胶片大小恒定。用对了「滤镜」(查询向量)就能把某一层内容提出来,但印得多了就会互相干扰。
类比失效处:胶片叠印是纯粹的相加、无法撤销;而 KDA 的核心创新恰恰是它能撤销——下一节的 delta 规则就是在写新内容之前,先把旧内容擦掉。这正是它比朴素线性注意力强的地方。
2.3 delta 规则:写之前先擦
朴素写入 S ← S + kv⊤ 有个明显的毛病:如果同一个键被写了两次,两个值会叠在一起。
假设「首都」这个键先被写成「北京」,后来上下文变了要改成「东京」。朴素写入的结果是黑板上同时有「北京」和「东京」,读出来是两者的混合——一团糨糊。这正是 RNN 「记忆被冲淡」的数学本质。
delta 规则的想法很直接:写新的之前,先把这个键上原有的内容擦掉。
关键在那个 (I − βtktkt⊤)。它长得吓人,但意思很简单。让我们看看它作用在旧状态上会发生什么。
自己推一遍:那个括号在干什么
先看 I St−1。I 是单位矩阵,乘它等于什么都没做。所以第一项保留了什么?
想好了再看
完整保留了旧状态。如果括号里只有 I,那这就退化成了上一节的朴素写入。
再看被减掉的那一项:βtktkt⊤St−1。先算最右边两项 kt⊤St−1,它是什么?(提示:回忆上一节的读取公式)
想好了再看
它就是「用当前的键 kt 去黑板上读一次」,读出来的是旧状态里已经存在这个键上的值。记它为 v旧⊤。
这一步是整个 delta 规则的灵魂:要擦掉旧内容,得先知道旧内容是什么,而知道的办法就是读一次。于是被减掉的那一项变成了 βtktv旧⊤。这个形式你在上一节见过。它是什么?
想好了再看
它正是「把 (kt, v旧) 这一对写进去」的那个外积。而现在它前面带了个负号——所以是把它写出去,也就是擦除。
现在把整条式子读成一句人话。
想好了再看
「用 kt 读一下旧值,按强度 βt 把旧值擦掉,再按同样的强度写入新值 vt。」
换个写法你会看得更清楚。把式子整理一下:
St = St−1 + βtkt(vt − v旧)⊤
这就是「delta」这个名字的来源——更新量正比于「新值与旧值之差」。如果新值和旧值一样,状态根本不变(差为零);差得越多,改动越大。这和最早的感知机学习规则、和梯度下降的形式是同源的。最后:βt 取 0 会怎样?取 1 会怎样?
想好了再看
βt = 0:括号变成 I,写入项也为零,状态完全不变——这个 token 什么都没写。
βt = 1:旧值被完全擦掉,新值完全写入——彻底覆盖。
中间值就是部分覆盖。所以论文把 βt 叫做「delta 规则的写入强度」(write strength),并把它限制在 (0,1) 里,正是因为这个区间对应「从不写到全写」。
注意 βt 是模型自己算出来的(式 2 里 βt = Sigmoid(Wβxt))。也就是说,模型能逐 token 地决定「这个信息值不值得覆盖已有的记忆」。
上面推导出 St = St−1 + βtkt(vt − v旧)⊤,其中 v旧⊤ = kt⊤St−1。
论文式 2 说 q 和 k 都经过了 L2 归一化(把向量的长度缩放到 1)。请解释:如果不做这个归一化,delta 规则的擦除会出什么问题?
接着上面展开:(I − kk⊤)kv⊤ = kv⊤(1 − ‖k‖²)。
· 若 ‖k‖ = 1:结果是 0。擦得干干净净,正是我们想要的。
· 若 ‖k‖ = 0.5:剩下 0.75 kv⊤。擦不干净,旧记忆残留。
· 若 ‖k‖ = 2:剩下 −3kv⊤。擦过头了,而且符号翻转、幅值放大到三倍。
最后这种情况是灾难性的:每次「擦除」反而把内容放大并变号,逐步递推下去状态会指数爆炸。
所以 L2 归一化不是一个可有可无的工程细节,而是让 delta 规则的语义成立的必要条件:只有 ‖k‖ = 1 时,(I − βkk⊤) 才是一个真正的「在 k 方向上按比例 β 投影掉」的算子,其特征值恰好落在 [1−β, 1] 里,永远不会放大。
这道题想让你获得的习惯:论文里那些看起来像「顺手加的正则化」的操作(L2Norm、RMSNorm、Sigmoid 的值域),往往是某个数学性质成立的前提。读到它们时,多问一句「如果去掉会怎样」。这一章后面的下界衰减,是同一个道理的更极端版本。
变式:论文式 2 里 q 也做了 L2 归一化。q 只用于读取(o = S⊤q),不参与擦除。那么归一化 q 的理由和归一化 k 一样吗?如果不一样,你猜是什么?(提示:想想读出来的 o 的幅值会怎样,以及它接下来要送进什么。)
2.4 加上遗忘:式 1 的完整形态
delta 规则解决了「同一个键被反复覆盖」的问题。但还有一个问题它解决不了:那些再也用不到的旧信息,怎么慢慢淡出?
比如你读一篇长文档,第一章的细节到第十章基本就不需要了。delta 规则只会在「同一个键被重写」时才擦,从没被重写的旧内容会永远占着黑板。
解法是加一个遗忘门:每走一步,就让整块黑板按一定比例淡化一点。这就得到了论文的式 1:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| St | 第 t 步的状态矩阵,形状 dk × dv | 黑板。不管序列多长,大小恒定 |
| kt, qt | 键、查询向量,dk 维,都做了 L2 归一化 | 「往哪写」 / 「从哪读」 |
| vt | 值向量,dv 维 | 「写什么内容」 |
| βt ∈ (0,1) | 标量,delta 规则的写入强度 | 「这个信息值不值得覆盖旧记忆」,模型逐 token 决定 |
| αt ∈ (0,1)dk | 向量,通道级的单步保留因子 | 「黑板的每一行各自淡化多少」。注意它是向量不是标量——这是「通道级」的含义 |
| Diag(αt) | 把 αt 摆到对角线上的方阵 | 乘上去的效果就是「逐行缩放」 |
| ōt | 这一步读出来的原始输出 | 上面那一横表示「还没过输出门」,式 6 会处理它 |
为什么 α 是向量而不是一个数(这是 KDA 相对前代的核心改进之一)
如果 α 是一个标量,那么「遗忘」就是整块黑板整体变淡——所有信息以同样的速度淡出。
但真实的记忆不是这样。有些信息(比如「这段代码用的是 Python」)应该长期保留,有些(比如「上一行的变量名」)应该很快淡出。α 是 dk 维的向量,意味着黑板的每一行可以有自己的遗忘速度,而且这个速度是模型根据当前 token 现算的。
论文称之为通道级遗忘门(channel-wise forget gate),并明确说 KDA 的定位就是「用通道级遗忘门扩展 delta 规则递推」。这是 KDA 相对更早的 DeltaNet 的关键增量。
常见误解:α 和 β 是一回事
它们完全不同,别混。
αt 是向量,管的是「旧的东西留多少」,作用在整块黑板上,逐通道生效。它是被动的时间衰减。
βt 是标量,管的是「新的东西写多重」,只作用在 kt 指向的那一个方向上。它是主动的定点覆盖。
一个是「整体褪色」,一个是「局部涂改」。式 1 里两者的作用顺序也很重要:先褪色(Diag(α) 乘进去),再涂改(delta 项)。这个顺序在第10章的上下文并行里会变成一个大麻烦,你到时会明白为什么。
(a) 用一句人话把式 1 完整读出来。
(b) 假设 dk = dv = 128,每个数用 2 字节存。一个注意力头的状态 S 占多少字节?
(c) 对比第0章那道题里 KV 缓存的算法,说清这两者随序列长度增长的行为有什么本质区别。
(a) 「先让整块黑板按每行各自的比例 αt 淡化一点;再用当前的键 kt 读出这个键上的旧值、按强度 βt 擦掉它;然后按同样的强度把新值 vt 写上去。最后用查询 qt 从更新后的黑板上读一次,得到输出。」
(b) 128 × 128 × 2 = 32,768 字节 = 32 KB。
(c) 本质区别是:一个是常数,一个是线性函数。
· KDA 状态:序列长 1000 时是 32 KB,长 100 万时还是 32 KB。序列长度根本不出现在内存公式里。
· KV 缓存:第0章算过,每层每 token 512 字节,93 层就是 46.5 KB 每个 token。100 万 token 是 44.3 GB。
换算成同一个尺度:如果 93 层全是 KDA,总状态是 93 × 32 KB ≈ 3 MB,和 44.3 GB 相差约一万五千倍。
这就是 K3 敢做 100 万上下文的底气。但请立刻警惕:相差一万五千倍的东西,不可能是免费的。KDA 用一块 32 KB 的黑板去装 100 万 token 的信息,必然有大量信息被压掉了。这正是 K3 不敢全用 KDA、每 3 层要配 1 层完整 MLA 的原因——下一章的主题。
变式:K3 有 69 个 KDA 层和 24 个 MLA 层。假设 KDA 头的状态如上是 32 KB/层,MLA 层每 token 每层占 512 字节。(a) 请写出「总缓存大小」关于序列长度 n 的表达式。(b) 在 n 等于多少时,两部分的大小相等?(c) 这个数字告诉你,KDA 的收益在什么长度以下其实微不足道?
2.5 式 2:这些 q、k、v、α、β 是从哪来的
式 1 里的每个量都要从 token 向量 xt 算出来。论文式 2 给了具体做法(h 是头的编号):
vth = Swish( ShortConv( Wvhxt ) )
βth = Sigmoid( Wβhxt )
zth = Wα↑Wα↓xt + bαh
| 成分 | 是什么 | 为什么在这 |
|---|---|---|
| Wxt | 矩阵乘向量(第0章) | 标准做法:从 token 向量投影出所需的量 |
| ShortConv | 短卷积:把相邻几个位置的值做一次加权混合 | 让每个位置能「瞥一眼」紧邻的几个 token,补充局部信息 |
| Swish | 激活函数(第0章表格) | 引入非线性 |
| L2Norm | 把向量长度缩放到 1 | 只对 q、k 做。上一题证明过:这是 delta 规则语义成立的前提 |
| Sigmoid | 压到 (0,1) | 保证 β 落在「从不写到全写」的合法区间 |
| Wα↑Wα↓ | 低秩投影:先降到很低维再升回来 | 省参数。第0章那道变式题算过这个技巧 |
| bαh | 每个头自己的偏置 | 让不同的头有不同的「默认遗忘倾向」 |
| zth | 衰减 logit,还不是 α 本身 | 注意:它到 α 的映射被推迟了,因为这个映射的形式取决于分块并行的数值细节。这是 2.7 节的主题 |
注意最后一行留下的悬念
论文在式 2 后面明确写道:从 zth 到 αth 的「下界映射」是在分块形式给出之后才引入的。
这个安排本身就在告诉你一件事:这个映射的形式不是从建模需要推出来的,而是从数值精度需要推出来的。论文把它放在后面,是因为不先讲分块形式,你根本看不懂为什么要那么改。我们也照这个顺序走。
2.6 分块并行形式:让串行的东西跑在 GPU 上
现在面对 RNN 的第二个致命伤:式 1 是严格串行的。St 依赖 St−1,一百万个 token 就要一百万步,GPU 完全用不上。
解法叫分块并行形式(chunkwise parallel form),思路是一个漂亮的折中:
为什么块内可以并行——关键的数学洞察
块内第 5 个 token 的状态明明依赖第 4 个,怎么可能并行?
诀窍在于把「依赖传入状态的部分」和「块内自己产生的部分」拆开。块内任何一个位置的输出,都可以写成两部分之和:
① 从块外传进来的状态贡献的部分——对块内所有位置来说,传入状态 S[t] 是同一个,所以这部分可以用一次矩阵乘法一起算完。
② 块内 token 之间互相贡献的部分——这部分只涉及块内的 Q, K, V,可以写成一个矩阵乘法加一个下三角掩码,也是一次算完。
这就是式 4 的两项。而「块与块之间的串行」只剩下「把状态从一块传到下一块」这一件事。
要写出这个形式,先需要一个记号。论文式 3 定义累积衰减:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| γi→j | 从第 i 步到第 j 步的所有 α 连乘 | 「经过这么多步之后,还剩多少没被遗忘」 |
| γr | = γ1→r,从块首到第 r 步的累积衰减 | 块内位置 r 相对于块首的衰减量 |
| Γ[t]1→C | 把 γ1…γC 按行摞起来的矩阵,C × dk | 整个块的衰减表 |
有了它,再加上一个叫 UT 变换的技巧产出的 U[t] 和 W[t],定义伪值项 Ṽ[t] := U[t] − W[t]S[t],块内所有输出就可以并行算出来:
O[t] = (Γ[t]1→C ⊙ Q[t])S[t] + A[t]Ṽ[t]
└──── 块间(inter-chunk)────┘ └─ 块内(intra-chunk)─┘
这条式子不需要你能自己推出来(论文自己也把完整推导推给了 Kimi Linear 那篇),但你需要读懂它在说什么:
| 部分 | 是什么 | 直觉 |
|---|---|---|
| Tril[·] | 把严格上三角的元素全置零,保留下三角含对角线 | 因果掩码:位置 i 只能看见 j ≤ i。对角线要保留,因为每个输出读的是「当前 token 更新之后」的状态 |
| Q ⊙ Γ | 查询按累积衰减缩放 | 位置越靠后,它看块首方向的东西时衰减得越多 |
| K / Γ | 键除以累积衰减 | 这个除法是全章最大的麻烦制造者,下一节的主角 |
| 第一项 | 块间:传入状态 S[t] 对块内所有位置的贡献 | 「从上一块继承来的记忆」 |
| 第二项 | 块内:块内 token 之间的相互作用 | 「这一块自己内部发生的事」 |
打个比方
像记账。块间项是「上个月结转过来的余额」,对本月所有交易来说是同一个数字。块内项是「本月内部发生的所有收支」,只需要看本月的账本。月末把两者一加,就是本月结束时的余额,再结转给下个月。
「月与月之间必须按顺序结转」(串行),但「一个月内部的几千笔交易可以同时录入」(并行)。分块并行就是这个意思。
类比失效处:记账里的结转是简单加法,而 KDA 的结转要经过 Diag(α) 缩放和 delta 规则的矩阵作用——它不是加法,而是一个依赖 token 的矩阵变换。这个区别看起来微小,但它在第10章会直接导致「上下文并行不能用简单求和」这个大问题。请记住这个伏笔。
分块并行里,块大小 C 是一个可调的参数。
(a) C 取 1 会退化成什么?
(b) C 取等于整个序列长度会退化成什么?
(c) 那么 C 变大时,什么变好、什么变坏?请至少指出两个相反方向的效应。
(a) C = 1:退化成完全串行的式 1,每步一个 token,GPU 闲置。
(b) C = n:退化成完全并行的形式,此时块内项 A 是一个 n × n 的矩阵——注意,这正是标准注意力的形状和计算量,平方复杂度回来了。
(c) 三组相反的效应:
· 串行步数 = n/C,C 越大越好;
· 块内计算量 = nC,C 越大越差(趋向平方复杂度);
· 片上内存:块内那个 C × C 矩阵和相关的 Q, K, V 块要能塞进 GPU 的快速片上缓存,C 太大就塞不下,被迫回主显存,反而更慢。
所以 C 是在「串行深度」和「平方复杂度」之间取的一个平衡点,最优值由硬件(片上缓存大小、Tensor Core 的形状偏好)决定,而不是由数学决定。
论文没有告诉你 K3 的 C 取多少——它只在讲下界衰减时提到把每个块再划分成「次级的 16-token 小片」,从这里能推断 C 是 16 的倍数且明显大于 16,但具体值未披露。这是这一节一个真实的信息缺口。
变式:论文说 Tril 保留了对角线,理由是「每个输出读的是当前 token 更新之后的状态」。请构造一个反例说明:如果错误地把对角线也置零(即位置 i 只能看 j < i),模型会失去什么能力?(提示:想想一个 token 想读取「它自己刚写进去的东西」的场景,比如复制任务。)
2.7 下界衰减:一个溢出问题如何改写了数学
这一节是全章最精彩的地方,因为它展示了一件在论文里很少被讲得这么透的事:硬件的数值精度限制,可以反过来决定模型的数学形式。
问题:那个除法会爆炸
回看式 4 里的 K[t] / Γ[t]1→C——键要除以累积衰减。
而 Γ 是一串 (0,1) 之间的数连乘。连乘会发生什么?
BF16:训练大模型最常用的数字格式,每个数占 2 字节。它能表示的最大值约为 3.4 × 1038。超过就变成「无穷大」,计算全毁。
论文原文的说法是:「因为 Γ 是一串 (0,1) 中保留因子的乘积,这个倒数可以无界增长,在有限精度下溢出。」
前代的解法,以及它留下的疙瘩
Kimi Linear(KDA 的前身)用两招控制这个数值范围:
- 在对数空间计算相对衰减——连乘变成连加,动态范围大幅压缩。
- 把每个块再划分成 16 个 token 的次级小片(tile)——只在小片内部算相对衰减,跨小片的部分另外处理。
这两招之后,非对角的小片可以直接用稠密矩阵乘法在 Tensor Core 上算。但对角线上的小片不行——论文原话是:对角小片「仍然需要显式的位置对计算,这仍是块内的主要瓶颈」。
Tensor Core:现代 GPU 里专门做矩阵乘法的硬件单元,比通用计算单元快好几倍。但它只吃「标准的稠密矩阵乘法」这一种形式。任何需要「逐个位置对单独处理」的计算都用不上它,只能退回慢得多的通用路径。
为什么偏偏是对角小片有问题
因果掩码的缘故。非对角小片里,所有的 (查询位置, 键位置) 组合都满足 j < i,掩码全通过,是一个完整的稠密矩阵乘法。
而对角小片横跨了掩码的边界——它内部一半元素要保留、一半要置零,而且每一对位置的相对衰减都不一样。这就没法写成一次干净的矩阵乘法,只能一对一对地算。
K3 的解法:换掉衰减函数本身
这里是关键的思维转折。前人的思路是「数会爆炸,那我想办法在计算上绕开」。K3 的思路是「数会爆炸,那我从一开始就不让它爆炸」。
回忆 2.5 节留下的悬念:从衰减 logit z 到保留因子 α 的映射被推迟了。现在揭晓这个映射:
αth = exp( gth ) ∈ (egmin, 1)dk
| 符号 | 是什么 | 直觉 |
|---|---|---|
| gth | 每步的对数衰减(log-decay) | 因为 α < 1,所以 log α < 0。g 就是这个负数 |
| gmin | 固定为 −5,不学习 | 对数衰减的下界。这个数字是整个设计的核心 |
| Sigmoid(·) | 压到 (0,1) | 于是 gmin·Sigmoid(·) 一定落在 (−5, 0) 里,不可能更负 |
| Ah | 每个头一个可学习的对数尺度,初始化为 0 | 让不同的头能自己调整衰减曲线的陡峭程度 |
| eAh | 指数保证这个尺度恒正 | 不会意外把曲线方向翻转 |
对比一下前代的做法。Kimi Linear(以及 GDN、Mamba-2)用的是负 softplus 映射:
差别只有一个字:值域。前代是 (−∞, 0),K3 是 (−5, 0)。前代无下界,K3 有下界。这就是「下界衰减」(lower-bounded decay)这个名字的来源。
为什么 −5 这个数字刚刚好
论文给出的推理链条非常干净,值得完整走一遍:
自己推一遍:−5 是怎么算出来的
既然 g > −5,那么每一步的保留因子 α = eg 至少是多少?
想好了再看
α > e−5 ≈ 6.7 × 10−3,也就是至少 0.67%。论文原文正是这个数。
换句话说,无论模型多想遗忘,它一步之内最多只能忘掉 99.33%,不可能忘干净。现在看一个 16-token 的小片。在最坏情况下(每一步都用最大的遗忘),跨越这个小片的累积对数衰减是多少?
想好了再看
对数衰减是可加的:16 步各 −5,累积就是 16 × (−5) = −80。
所以累积对数衰减一定落在 (−80, 0) 这个区间里。论文原文:「跨一个 16-token 小片的累积对数衰减落在 (−80, 0)。」那么式 4 里那个要命的倒数缩放因子 1/Γ,最大是多少?
想好了再看
1/Γ = 1/e−80 = e80。论文原文:「对应的倒数缩放因子因此小于 e80。」
最后一步:e80 大概是多少?BF16 能装下吗?
想好了再看
e80 ≈ 5.5 × 1034。BF16 的最大值约 3.4 × 1038。
装得下,而且还留了大约四个数量级的余量。论文原文:「保持在 BF16 的动态范围内。」
现在你明白 gmin = −5 这个数字是怎么来的了:它不是调出来的经验值,而是从「16-token 小片」和「BF16 动态范围」这两个硬件事实反推出来的。如果小片改成 32 token,或者换成 FP16(最大值只有 65504),这个 −5 就得跟着变。
打个比方
前代的做法像是:允许员工请任意长的假,然后为「请了超长假的人」专门建一套复杂的复职流程。K3 的做法是:规定假期最长不超过五天,于是所有人走同一条简单流程就行了,那套复杂流程可以整个删掉。
限制表达力,换来的是整条代码路径的消失。
类比失效处:这个比方会让你觉得「限制」是纯粹的损失。但论文指出,这种有下界的递推门在先前的工作里已经出现过(论文引用了三篇),也就是说它可能本身就有建模上的好处(防止信息被彻底抹掉)。论文没有做消融来区分「下界带来的建模收益」和「下界带来的效率收益」各占多少——这是一个真实的信息缺口。
读的时候要小心
论文说这个新参数化「与先前工作里有下界的递推门密切相关」,但没有给出任何对比实验:没有「换成下界衰减前后的验证损失曲线」,也没有「对角路径消除后的实测加速比」。
所以这一节你能确信的是:这个设计在数值上是自洽的(−5 的推导是硬的),以及它在原理上消除了一条慢路径。至于它对最终模型质量的净影响是正是负、幅度多大,这篇报告没有回答。
下界衰减保证了 α > e−5 ≈ 0.0067,即「一步最多忘掉 99.33%」。请构造一个具体的任务场景,说明这个下界会损害模型的能力。你的场景需要说清:模型本应做什么、下界让它做不到什么、以及这个损害在实践中有多严重。
场景:多段独立上下文的隔离。假设输入里有 50 段互不相关的文档,每段开头都定义了同名但不同值的变量(第 1 段「设 x = 3」,第 2 段「设 x = 7」,……)。理想情况下,模型处理第 k 段时应该完全忘掉前 k−1 段里 x 的值。
下界让它做不到「完全」。但严不严重需要算一下——这才是这道题真正想训练的能力:
假设两段之间隔了 n 个 token,模型全程用最大遗忘。残留比例是 (e−5)n = e−5n。
· n = 1:残留 0.67%
· n = 3:残留 3 × 10−7
· n = 10:残留 2 × 10−22——远低于 BF16 能表示的精度,实际上就是 0
所以结论是:这个「损害」在实践中几乎不存在。只要两段信息之间隔了十几个 token,下界衰减和无下界衰减的效果就没有可测量的区别。真正被限制掉的只有「在一步之内彻底清零」这一种极端行为。
这道题的意义有两层。第一层:学会把「理论上的限制」和「实践中的影响」分开算——很多看起来严重的限制,算一下发现是零。第二层,也更重要:这个计算反过来解释了为什么这个设计是划算的——它放弃的是一个几乎用不上的能力(单步清零),换来的是一整条慢代码路径的消失。
如果你在第 4 级提示之前就自己想到了「要算残留量」,那你已经具备了评估这类工程权衡的核心能力。
变式:现在反过来。假设有人把 gmin 从 −5 改成 −20,理由是「给模型更大的遗忘自由度」。(a) 跨 16-token 小片的累积对数衰减范围变成多少?(b) 倒数缩放因子最大是多少?(c) BF16 还装得下吗?(d) 如果装不下,作者有哪两条路可选,各自的代价是什么?
2.8 满秩输出门:最后一道闸
式 1 算出的 ōt 还不是这一层的最终输出。KDA 在它后面加了一道门:
| 成分 | 是什么 | 为什么在这 |
|---|---|---|
| RMSNorm(ōt) | 对循环输出做逐头的归一化(第0章 0.11 节) | 循环状态读出来的东西幅值可能乱跳,先拉回标准尺度 |
| Sigmoid(Wgxt) | 由当前 token 算出的门,每个通道一个 0–1 的值 | 让每个 token 自己决定「从黑板上读到的这些通道,各放行多少」 |
| ⊙ | 逐元素相乘 | 门作用在归一化后的输出上 |
| Wo | 输出投影 | 把结果映射回模型的主维度 d |
「满秩」这个词在强调什么
论文特别说明:Kimi Linear 用的是低秩参数化的输出门,K3 改成了输入依赖的满秩投影。
低秩的意思是(回忆式 2 里的 Wα↑Wα↓):先把 x 压到一个很低的维度,再升回来。这省参数,但也限制了门能表达的模式——如果中间维度是 64,那么门的所有可能取值都被困在一个 64 维的子空间里,无法为每个通道独立设定。
满秩就是不做这个压缩,Wg 直接是全尺寸矩阵,门的每个通道可以完全独立。代价是参数变多。K3 在这里选择了花参数买表达力。
这是本章的综合题。KDA 里有五个设计选择,请你说清每一个是为了解决什么问题,以及它引入了什么新问题(如果有)。
① 用固定大小的状态矩阵代替 KV 缓存
② delta 规则的擦除项 (I − βkk⊤)
③ 通道级(向量)而非标量的衰减 α
④ 分块并行形式
⑤ 下界衰减 gmin = −5
① 固定大小状态。 解决:KV 缓存随长度线性增长、100 万 token 下吃掉几十 GB 显存。代价:状态容量有限,信息被压缩(记忆冲淡);且递推严格串行,与 GPU 的并行偏好冲突。这两个代价分别由 ② ③ 和 ④ 来处理。
② delta 擦除项。 解决:同一个键被反复写入时旧值不被清除、变成一团糨糊。代价:擦除的正确性依赖 ‖k‖ = 1,所以必须加 L2 归一化(q2-1 那道题算过,不加会指数爆炸)。更深的代价:擦除项是一个依赖 token 的矩阵 Mt 作用在传入状态上,这让 KDA 的递推不再是简单可加的——第10章会告诉你,这直接导致普通线性注意力的上下文并行方法对 KDA 失效,必须专门发明 KCP。
③ 通道级衰减。 解决:标量衰减下所有信息以同一速度淡出,无法区分「该长期记住的」和「该马上忘掉的」。代价:α 从一个数变成 dk 维向量,累积衰减 Γ 也随之变成矩阵,让式 4 里的缩放变得更麻烦。
④ 分块并行形式。 解决:串行导致 GPU 闲置。代价:式 4 需要用 1/Γ 对键做重缩放,而 Γ 是一串小于 1 的数连乘,倒数会无界增长并溢出。
⑤ 下界衰减。 解决:④ 留下的溢出问题。做法是从源头限制 α > e−5,使 1/Γ < e80,落在 BF16 范围内。净收益:对角小片不再需要单独的位置对计算路径,全部可用 Tensor Core,块内瓶颈消失。代价:模型失去「单步彻底清零」的能力——但 q2-4 算过,这个能力在隔了十几个 token 之后就无关紧要了。
这条链的形状值得记住:①的代价 → ②③④来救 → ④又制造新代价 → ⑤来救。每一个「优雅的设计」都是上一个设计留下的债务的偿还。这正是读工程性论文最该培养的视角:不要孤立地欣赏某个技巧,要看它在这条债务链上的位置。
而这条链还没有结束——② 留下的「不可加性」这笔债,要到第10章的 KCP 才还;固定状态在推测解码时「无法回滚」这笔债,要到第11章才还。KDA 是全站唯一一个横跨架构、训练、推理三层的概念,原因就在这里。
变式:现在做一个设计决策。假设你要把 KDA 用在一个上下文只有 4K token 的小模型上。请逐条判断:① 到 ⑤ 这五个设计里,哪些依然必要、哪些可以简化甚至去掉?对每一个「可以去掉」的,说清你的依据是什么(提示:先算 4K 上下文下 KV 缓存到底有多大,再决定第一步的交易还划不划算)。
答辩:如果我是审稿人
你花了一整章论证 KDA 用固定大小状态换来了巨大的内存优势。那我问你:既然一块 32 KB 的黑板要装下 100 万 token 的信息,压缩比是天文数字,凭什么相信它没丢掉关键信息?如果它真的丢了很多,那 K3 在长上下文任务上的好成绩,会不会其实全是那 24 层 MLA 的功劳,而 69 层 KDA 只是在「不添乱」?请你为 KDA 辩护。
参考防守(先自己组织语言再看)
首先必须承认:这个攻击在本篇报告的证据范围内无法被驳倒。论文没有做任何「改变 KDA:MLA 比例」的消融实验,没有「全 MLA 对照组」,也没有报告长上下文检索类任务(比如大海捞针)的分数。所以「69 层 KDA 的实际贡献有多大」这个问题,这篇报告给不出答案。这是一个真实的、值得指出的证据缺口。
但辩护有三层,从弱到强:
第一层(最弱):混合结构本身就是对这个担忧的承认。如果作者相信 KDA 能独立扛下全部工作,就不会每 3 层配 1 层 MLA、还在骨干末尾额外补一层。这个 3:1 的比例本身就在说:「我们知道固定状态会丢信息,所以留了完整注意力的通道。」问题只是这个比例对不对,而不是「KDA 有没有丢信息」。
第二层:压缩比这个直觉本身有误导性。「32 KB 装 100 万 token」听起来荒谬,但这个算法有偏差。首先每层都有自己的状态,69 层就是 69 块黑板,而且它们处在不同的表示层级上,存的不是同一类信息。其次,KDA 的状态存的不是 token 本身,而是「键→值」的关联;很多 token 携带的信息是高度冗余或短程的(虚词、格式、局部语法),本来就不需要长期保留。更关键的是,delta 规则和通道级衰减的全部意义,就是让模型自己决定什么该留——这不是均匀压缩,而是学出来的选择性保留。
第三层(最强,但也是双刃的):分工假说是可检验的,而论文给了一个间接证据。论文说所有 MLA 层用 NoPE(不加位置编码),理由是「KDA 层提供位置敏感、近因感知的混合」。这句话意味着:K3 的位置信息完全依赖 KDA。如果 KDA 只是在「不添乱」,那 24 层无位置感的 MLA 加上一堆无用的 KDA,模型根本不可能知道词序——而它显然知道。所以 KDA 至少承担了一项无可替代的功能。
但这一层辩护同时暴露了一个新问题,也是我认为这篇报告最该被追问的地方:既然位置信息全靠 KDA 的隐式衰减来编码,那么 69:24 这个比例、以及 MLA 的比例上限,就不只是效率问题,而是能力问题。论文对此没有任何讨论,也没有消融。如果有人想把 KDA 换成别的线性注意力,或者调整比例,这篇报告没有提供任何指导。
诚实的结论:KDA 承担了实质功能(至少是位置编码),混合结构说明作者也知道它不够用,但「69 层 KDA 相对于更多 MLA 的边际贡献」在本报告中无法确定。
本章小结
KDA 做的是一笔交易:把「每个 token 一份、永远增长的 KV 缓存」,换成「一块固定大小、原地改写的黑板」。内存从随长度线性增长变成常数,代价是信息被压缩、递推变成串行。
为了让这笔交易划算,KDA 叠了四层补救:delta 规则让写入能擦除旧值(代价是必须 L2 归一化,且递推不再可加);通道级衰减让每一行有自己的遗忘速度;分块并行把串行压缩了三个数量级(代价是引入了会溢出的倒数缩放);下界衰减从源头堵住溢出,顺带消掉了对角小片那条慢路径。最后用满秩输出门让每个 token 自己决定读到的东西放行多少。
下一章:既然 KDA 这么省,为什么还要留 24 层完整的 MLA?两种记忆各自补什么?
第3章 Gated MLA 与混合注意力:两种记忆的分工
一句话导语:K3 不把「注意力」当成一种东西,而是让两种机制分工——KDA 负责顺着句子往下记、天然带先后顺序,Gated MLA 负责回头在全篇里随便翻、不受位置约束。这一章讲清它们按什么比例混、各自补什么、以及为什么 MLA 层可以干脆不要位置编码。
学完这一章你应该能做到
- 用自己的话说清 K3 沿哪三个维度扩展信息流,每个维度由哪个模块负责
- 从表 1 的「69 KDA + 24 MLA」反推出骨干里有多少个 block,并解释为什么 69÷24 不等于 3
- 解释 MLA 只缓存潜向量 ct 为什么能省显存,以及代价出在哪三处
- 说清 NoPE 在 K3 里成立所依赖的前提,并构造一个让这个前提失效的改动
- 逐符号读懂式 7,解释「满秩门」和低秩门的差别
- 把这一节里「论文给了理由的」和「论文只是这么做了的」分开列出来
3.1 先看地图:K3 把信息流往三个方向撑开
先把最基础的事情说清楚。一个语言模型拿到一段话,会先把它切成一个个 token(token,可以粗略理解成「词或词的一部分」),每个 token 变成一串数字。K3 里这串数字有 7,168 个(论文表 1 的 Hidden Dimension)。之后模型做的事情,翻来覆去就一件:让这些数字互相看一看、混一混。所谓「设计架构」,说白了就是决定谁跟谁混、怎么混、混几次。
论文 §2 开篇给的框架是:混合可以发生在三个互不相同的方向上,K3 沿这三个方向分别做了扩展。
第一个方向是序列长度,即 token 之间怎么混。K3 的做法叫混合注意力(Hybrid Attention):每个 block 里放 3 个 KDA 层加 1 个 Gated MLA 层,论文说这样「为长上下文的 token 混合提供了高效机制,同时保留了有选择性的高容量注意力」。第二个方向是网络深度,即层与层之间怎么混:注意力残差(Attention Residuals, AttnRes) 让每个模块能从词嵌入、当前块、以及之前的块里「挑着取」,而不是只能沿一条残差链均匀累加——这是第4章的内容。第三个方向是模型宽度,即同一个 token 内部 7,168 个通道之间怎么混:每个注意力层后面跟一个 Stable LatentMoE 层做稀疏的通道混合,每个 token 只激活 896 个路由专家里的 16 个。
为什么非要拆成三个方向来讲
因为这三件事的瓶颈完全不同:句子想更长,卡在缓存和平方复杂度;网络想更深,卡在信息要挤过一条细细的残差链;每层想更宽,卡在参数量和通信量。分开看,才能理解 K3 为什么要同时引入三个看上去毫不相干的新模块。
读的时候要小心
论文在这一段说,这些改动配合改进的训练与数据配方,相对 Kimi K2 带来约 2.5 倍的整体缩放效率提升。这是论文自述的结果,来自作者自己拟合的缩放曲线(论文 Figure 7),而且是「架构 + 数据 + 训练配方」打包在一起的数字。论文没有把功劳拆开,所以不能读成「混合注意力单独带来 2.5 倍」。
打个比方
像给一间图书馆扩容:书架可以排得更长(序列)、楼可以盖得更高(深度)、每层的架子可以更宽(宽度)。
类比失效处:图书馆三个方向互不干扰,但神经网络里三者共享同一份显存——加深会直接挤占加宽的预算。第4章你会看到,深度方向的扩展代价恰恰就是显存,逼得作者不得不做块化。
下面三句描述各对应哪个维度、哪个模块?(一)「让第 40 层能直接读到第 3 层的输出。」(二)「让第 1000 个 token 能看到第 1 个 token。」(三)「让这个 token 的 7,168 个数被 16 个专家分别加工。」
变式:「把上下文从 128K 扩到 1M」主要动的是哪个维度?再想一层——它会不会连带影响另外两个维度的开销?(提示:每个 token 都要在每一层留下痕迹。)
3.2 三个 KDA 配一个 Gated MLA:比例,和末尾补的那一层
为什么不能只用一种注意力
全局注意力(MLA 属于这一类)让每个 token 回头看前面所有 token:远处的细节都能被精确取回,但计算量随长度平方增长,且推理时必须把每个 token 的键和值一直缓存着——1M token 时这笔缓存大到离谱。线性注意力(KDA 属于这一类)反过来:把历史压进一个固定大小的状态往下传,代价随长度线性增长、缓存不涨,但「压缩」必然丢信息。两边各有一个致命短板,于是干脆混着用。
论文 §2.1 给的分工写得很明确:KDA 层提供位置敏感、近因感知(position-sensitive and recency-aware)的混合,MLA 层提供不受限的全局内容交互(unrestricted global content interaction)。翻成人话:KDA 管「最近发生了什么,按顺序记着」;MLA 管「整篇文章里跟我现在这个词最相关的是哪几个,不管它在多远」。
混合注意力(Hybrid Attention):K3 的做法是逐层混合(layerwise hybrid)——不是在同一层里把两种机制拼起来,而是整层整层地交替。每个 block 里放 3 个 KDA 层,后面跟 1 个 Gated MLA 层,比例 3:1,这个模式在整个骨干里重复。此外,骨干末尾额外放一个 Gated MLA 层,确保最后一层总是执行全局注意力。
一个非常容易混淆的词
「block」这个词在论文里指了两种完全不同的东西。§2.1 里的 block 是4 层(3 KDA + 1 Gated MLA);而 §2.2 的 Block AttnRes 里,block 是12 层。两者只是碰巧都叫 block,读的时候必须靠上下文判断。第4章开头我们会再提醒一次。
论文说了什么,没说什么
说了:比例是 3:1;这个模式在整个骨干重复;末尾额外补一个 Gated MLA,目的是「确保最后一层总是执行全局注意力」;这套混合设计沿用 Kimi Linear。
没说:(一)为什么是 3:1 而不是 7:1 或 1:1——这份报告里没有任何关于混合比例的消融实验或曲线;(二)为什么「最后一层必须是全局注意力」很重要——论文只陈述了这个设计目标,没给理由,也没给对照实验;(三)骨干的第一层是 KDA 还是 MLA,正文没有明说(从 Figure 2 的画法和 3.7 节的层数对账看,是从 KDA 开始的)。
答辩:如果我是审稿人
你说 3:1,可你整篇报告没给一条比例的消融曲线。凭什么不是 7:1?7:1 明显更省缓存。这个数字是不是从别的工作抄来的?
参考防守(先自己组织语言再看)
诚实的防守只能分三步走。第一,承认事实:这份报告确实没给比例消融,论文明说这套混合设计是沿用 Kimi Linear 的 hybrid design,也就是说 3:1 的证据在那篇工作里,不在这里。第二,给出机制层面的辩护:比例控制的是「全局注意力层的密度」,比例越大越省缓存但全局取回的机会越少;3:1 意味着每 4 层就有一次不受限的全局交互,在任何 4 层的窗口内信息都不会被纯压缩通道彻底截断。第三,指出这个辩护的边界:以上是定性的,它不能区分 3:1 和 5:1。一个诚实的作者应该说「我们采用了先前工作验证过的比例,没有在 K3 规模上重新搜索」,而不是暗示 3:1 是本文的发现。反过来,如果审稿人要求必须做消融,一个合理的反驳是:在 2.78T 参数规模上做比例消融的成本极高,用小模型做出来的最优比例也未必外推得上去——但这是成本论证,不是证据。
假设骨干严格按「(3 KDA + 1 Gated MLA) 重复 B 次,最后再补 1 个 Gated MLA」搭建,总层数是 93。请写出关于 B 的方程并解出 B,再算出 KDA 层和 MLA 层各有多少。
变式:如果作者不补末尾那一层,在保持 3:1 和 93 层总数的前提下还能搭出来吗?如果不能,最接近的层数是多少?(提示:93 能不能被 4 整除。)
3.3 MLA:把一个 token 的键值压成一个小向量
不用 MLA 会怎样
推理时每生成一个新词,都要把之前每个 token 的键(K)和值(V)拿出来算一遍;为了不重复计算就把它们存起来,这就是 KV 缓存(KV cache)。缓存大小 = token 数 × 每个 token 要存的数字个数。标准多头注意力里后一项是「所有头的 K 和 V 拼起来」,K3 有 96 个头(表 1)、上下文能到 1M token——两个数一乘,缓存就成了推理时最先撑爆的东西。
多头潜注意力(Multi-head Latent Attention, MLA):由 DeepSeek-V2 提出。它的想法是:既然缓存太大,那就别缓存 K 和 V 本身,改成缓存一个能重建出它们的小东西。具体做法是把每个 token 的键值表示压进一个低维潜向量(latent vector):
| 符号 | 是什么 | 直觉 |
|---|---|---|
| xt | 第 t 个位置进入这一层的输入向量,长度 7,168 | 这个 token 此刻的「完整状态」 |
| Wc | 训练中学出来的压缩矩阵(down-projection) | 一台学会了「怎么摘要」的压缩机 |
| ct | 压出来的潜向量,维度远小于 7,168 | 这个 token 留给后人查阅的「名片」 |
推理时只缓存 ct。真正要算注意力的时候,再用学到的上投影(learned up-projections)把 ct 重建成各个头的内容键和值。论文的原话是:这个分解「在保留全局 token 到 token 注意力的同时,减少了 KV 缓存的足迹」。Kimi K2 和 K2.5 都用了 MLA,K3 把它保留在周期性出现的全局注意力层里。
代价出在哪?至少三处。第一是计算换存储:每次算注意力都要多做一次上投影,把小向量重新撑回大矩阵,省下的显存是用额外乘法换的,论文没讨论这部分开销有多大。第二是表达力受限:所有头的键和值都必须从同一个低维 ct 里长出来,压得越狠,不同头拿到的信息越接近;论文没有量化这一损失。第三是复杂度没变:注意力「每个 token 看所有前面 token」的平方级计算量,MLA 一点没减少。
常见误解
很多人以为「MLA 把 KV 压小了,所以注意力也变快了」。其实压缩改变的是你要存多少,不是你要算多少次配对。真正把长上下文的算力压下来的是 KDA 那一类线性注意力。这正好解释了 K3 为什么两种都要:MLA 解决缓存里的「保真」,KDA 解决长度上的「代价」。
一个容易看错的数字
表 1 里有一行「Latent MoE Dimension: 3584 (0.5×)」。那是 Stable LatentMoE 的潜维度,跟 MLA 压缩出来的 ct 没有关系。K3 的 MLA 潜向量到底多少维,论文这一节和表 1 都没有给出。凡是要用到「压缩比是多少」的推理,都要先承认这个数我们不知道。
用一句话回答:MLA 在推理时缓存的是什么?再用一句话回答:如果某次推理的上下文只有 20 个 token,MLA 相对标准多头注意力还有优势吗?
变式:假设有人把 ct 的维度设得和「所有头的 K、V 拼起来」一样大,MLA 会退化成什么?这时它还叫「压缩」吗?
3.4 NoPE:位置这件事,交给谁管
为什么会有位置编码这个东西
注意力机制本身对顺序是没感觉的:它做的事是「拿我的查询和每个键比相似度,再按相似度加权求和」,整个过程没有出现「谁在前谁在后」。把词打乱,纯注意力对每个词算出的结果完全一样。可「狗咬人」和「人咬狗」显然不是一回事。所以通常要额外告诉模型位置,最常见的是 RoPE(Rotary Position Embedding,旋转位置编码):按位置给查询和键转一个角度,位置差越远、转角差越大。
无位置编码(No Position Encoding, NoPE):和 Kimi K2、K2.5 不同,K3 沿用 Kimi Linear 的混合设计,对所有 MLA 层采用 NoPE——它们的查询和键上不施加任何显式位置编码。
敢这么干的理由,论文写得很直白:夹在中间的 KDA 层本身就是位置敏感、近因感知的(它按顺序一步步更新状态,把顺序换掉结果就变了),而 MLA 层负责提供不受限的全局内容交互。也就是说,位置信息在进入 MLA 层之前,已经被 KDA 层写进隐藏向量里了;MLA 层不需要再被单独告知一遍,它可以专心做「按内容找相关」这件事。
还有第二个好处,纯工程但很实在:论文指出,这种分工避免了在扩展上下文长度时去改位置编码的参数,比如重调 RoPE 的频率基,或者套用 YaRN 这类插值方法。K3 的训练上下文从 K2 的 128K 一路推到 1M(表 1,8 倍),如果位置编码是显式的,每一次扩长都要重新调参、重新验证;NoPE 直接把这个环节从流程里删掉了。
打个比方
KDA 像边听边记的速记员,记下来的东西天然带着先后顺序;MLA 像事后翻笔记的人,不需要笔记本上再印页码,因为速记员写下的内容本身就带着时间感。
类比失效处:速记员的笔记是显式文字,谁都能读出顺序;而 KDA 写进隐藏向量的「位置感」是隐式的,没人能保证它在 1M token 尺度上仍然精确、仍然能被 MLA 层解读出来。
这是理由,不是证据
§2.1.2 这一段完整地给出了 NoPE 的 rationale(分工 + 免调参),但没有给出任何 NoPE 对比 RoPE 的实验。论文后面 §3.4 会说 K3 因此能直接外推到 1M token 上下文而无需任何位置编码改造——那是对结果的陈述,同样属于论文自述。想判断 NoPE 到底行不行,本章的材料是不够的。
答辩:如果我是审稿人
你的 NoPE 完全建立在「KDA 层会提供位置信息」这个假设上。那我问你:如果把 KDA 换成一个不带 decay、不带门控的普通线性注意力,你的 MLA 层还知道位置吗?如果不知道,那你所谓的「NoPE 更好」其实只是「KDA 顺便干了位置编码的活」,你凭什么把它记在 NoPE 头上?
参考防守(先自己组织语言再看)
这个质疑基本是成立的,只能承认前提然后限定主张。防守要点:第一,明确承认 NoPE 在 K3 里不是一个独立成立的设计,它是「混合架构」这个整体的一部分——论文自己的措辞就是「中间的 KDA 层提供位置敏感与近因感知的混合」,这句话本身已经把依赖关系说出来了,作者并没有声称 NoPE 单独可用。第二,因此正确的主张不是「NoPE 比 RoPE 好」,而是「在存在位置敏感的线性注意力层的前提下,全局层的显式位置编码是冗余的,去掉它能省下扩长时的调参环节」。第三,审稿人假设的那个改动(换成无 decay 的线性注意力)确实会破坏这个前提:如果线性注意力对历史是均匀累加而没有任何随距离衰减的结构,它就不再区分先后,MLA 层也就失去了位置信息来源——这恰恰说明该前提是必要的,而不是说明论文错了。第四,仍然要承认的缺口:论文没有做这个消融,所以「KDA 提供的位置信息够不够用」在 1M 尺度上仍是未验证的。
有人看完这一节说:「既然 MLA 层完全没有位置编码,那 K3 就分不清『狗咬人』和『人咬狗』。」请构造一个具体的反驳,说明这句话错在哪;然后反过来,构造一个对 K3 的改动,使得这句话变成对的。
反过来构造:把骨干里所有 KDA 层都换成对历史均匀求和、不带任何衰减或递推顺序的机制(也就是让每一层都对顺序无感),同时保持 MLA 层的 NoPE。这时整个网络从头到尾没有任何模块能区分顺序,「狗咬人 / 人咬狗」就真的分不清了。这个反例的价值在于:它证明 NoPE 的正确性不是 MLA 层自己的性质,而是整个混合架构的性质——换掉看似无关的另一半,结论就崩了。
变式:把改动换成「保留 KDA,但把 3:1 改成 1:99(几乎全是 MLA 层)」。这时位置信息还在吗?它「够用」吗?说说你的判断依据,以及论文有没有回答这个问题。
3.5 输出门:让每个 token 自己决定听多少(式 7)
为什么要在注意力后面加一道闸门
全局注意力读回来的是一份「加权平均意见」。问题是,这份意见对某些 token 极其有用(比如一个代词,需要回头找它指代谁),对另一些 token 几乎是噪声(比如一个标点)。如果不加闸门,注意力的输出会原样被写回主干,噪声也一并写进去。加一道由 token 自己控制的闸门,就能做到「这条通道我要听,那条通道我关掉」。
K3 给 MLA 加了一个输入相关、逐通道、满秩的输出门。记 ōt 为位置 t 上未经门控的 MLA 输出,门控后的输出是:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| yt | 第 t 个位置上这一层最终吐出来的向量 | 过完闸门、整理好,准备交给下一层 |
| ōt | 没加门的 MLA 输出(字母上面一横表示「未门控」) | 注意力刚读回来的原始意见 |
| xt | 这一层在位置 t 的输入向量 | 「我是谁」——闸门要根据它来开合 |
| Wg | 门投影矩阵,满秩 | 把「我是谁」翻译成「每条通道开多大」 |
| Sigmoid(·) | 逐元素把任意实数压到 (0, 1) 之间的函数 | 每条通道的音量旋钮,0 是静音、1 是全开 |
| ⊙ | 逐元素相乘(哈达玛积) | 把音量旋钮一对一地拧在每条通道上 |
| Wo | 输出投影矩阵 | 把门控后的结果映射回主干的形状 |
唯一需要额外解释的词是满秩。Wg 把 7,168 维的 xt 映射成一个同样长的门向量。如果这个映射是低秩的——比如先压到 32 维再撑回去——那么无论输入是什么,所有可能的门向量都被困在一个 32 维子空间里,很多通道只能被绑在一起开关。满秩则意味着门向量原则上可以指向 7,168 维空间里的任何方向,每条通道可以独立开关。论文的说法是:这个门让每个 token 去调制它从全局注意力读到的通道;Wg 满秩这一点与 K3 里 KDA 的新参数化保持一致。
顺手对照一下
KDA 的输出门(式 6)和这里的式 7 长得几乎一模一样,唯一的差别是:KDA 那一版先对 ōt 做了 head-wise 的 RMSNorm 再乘门,而式 7 里没有这一步。论文没有解释为什么全局注意力这边不需要这个归一化。这属于「看得见但没被交代」的设计细节。
把式 7 里的 Wg 换成低秩形式 Wg = AB,其中 B 把 7,168 维压到 8 维、A 再撑回 7,168 维。请说明:这样一来,模型做不到哪一类事情?再回答:Sigmoid 换成不加约束的线性函数(也就是干脆去掉 Sigmoid)会有什么后果?
第二问:Sigmoid 保证门值落在 (0, 1),它同时给出两个性质——一是有明确的语义(0 关、1 开,是「比例」而不是「放大倍数」),二是有界,不会把注意力输出的幅值放大到失控。去掉它以后,门变成一个无界的线性缩放,既可能放大成几百倍(在低精度算术下极易溢出),也可能变成负数导致符号翻转,训练稳定性会变差。注意这只是机制层面的分析,论文本身没有做这个消融。
变式:把 Wgxt 里的 xt 换成 ōt(用注意力的输出自己来算门),门还是「输入相关」的吗?这个改动会让门失去什么信息?
3.6 把注意力输出留在 FP32,以及被逼出来的内核重设计
为什么要管几位浮点数这种小事
计算机里的小数是有限精度的:BF16 一个数占 2 字节,FP32 占 4 字节。位数少就省显存、算得快,但每一步都有舍入误差。平时误差有正有负会互相抵消,可 flash attention 为了省显存把注意力分块累加,这个累加过程的舍入误差不是对称的,而是系统性偏向一边——论文的原话是 biased rounding error。有偏的误差不会抵消,只会越加越多。
K3 的处理是:训练时把注意力输出保持在 FP32,采用文献 [98] 的方法纠正这个有偏舍入误差。这听起来只是「多用点精度」,但它在 GPU 上引发了一连串连锁反应,论文把这条链完整写了出来,值得跟着走一遍。
tile(分块)与片上占用:GPU 算大矩阵时不会一次搬进整个矩阵,而是切成小块(tile),一块块搬进共享内存(shared memory)里算。共享内存是紧挨着计算单元的一小块存储,快得多也小得多,寸土寸金。
把输出从 16 位改成 32 位,意味着输出 tile 的片上占用直接翻倍。共享内存就那么大,有东西变胖就得有东西让位。论文的解法是重新设计训练内核:让变胖的输出 tile 去和 KV 暂存缓冲区(KV staging buffers,从显存往片上搬 K、V 时的中转区)重叠,而不是像原来那样和 query tile 重叠。腾出来的共享内存用于更深的 KV 流水线——同时在途的 K、V 块更多,搬运和计算叠得更好,计算单元等待更少。论文的结论是:这样反而换来更高的训练吞吐。
这里有一步论文没讲
论文没有解释为什么和 query tile 重叠不行,也没有给出任何吞吐提升的具体数字。一个合理的猜测是:query tile 在整块计算期间要被反复读取,生命周期贯穿始终;而 KV 暂存缓冲区是流水线式滚动使用的,生命周期与输出 tile 互补,才可以共用同一块地方。但这是我们的推测,不是论文的说法。
有人说:「把注意力输出从 BF16 改成 FP32,占用翻倍,训练一定变慢。」但论文说这个改动配合内核重设计之后反而提高了训练吞吐。请构造一个具体的场景解释这怎么可能,并指出这个反例成立需要什么前提。
前提:(一)原始内核确实是访存/延迟受限而不是算术受限;(二)输出 tile 与 KV 缓冲区的生命周期确实互补,能安全共用同一块共享内存;(三)额外的 FP32 写回带宽不会成为新瓶颈。这三条只要有一条不成立,「精度更高反而更快」就不再成立——所以这不是一条普适规律,而是一次针对具体瓶颈的工程胜利。论文本身没有给出吞吐数字,我们无法验证提升幅度。
变式:如果 FP32 只在推理时启用(不训练),这套内核重设计还有意义吗?推理和训练在共享内存的使用模式上有什么不同?(提示:想想推理解码阶段每次只处理一个新 token。)
3.7 对账时间:69 + 24 = 93,可 69 ÷ 24 = 2.875
现在把论文表 1 里跟本章相关的几行拉出来,做一次真正的核对。这一节的目的不是记住数字,而是练一件事:拿到一张表,能不能从里面把架构反推出来。
| 项 | Kimi K2 | Kimi K3 | Δ |
|---|---|---|---|
| 层数(#Layers) | 61 | 93 | ↑52% |
| 隐藏维度 | 7,168 | 7,168 | = |
| 注意力头数 | 64 | 96 | ↑50% |
| 注意力机制 | MLA | Hybrid KDA–MLA | — |
| 注意力层构成 | 61 MLA | 69 KDA + 24 MLA | — |
| 训练上下文长度 | 128K | 1M | 8× |
自己推一遍:从「69 KDA + 24 MLA」倒推出骨干长什么样
先做加法:69 + 24 等于多少?这个数和表里哪一行对上了?对上意味着什么?
想好了再看
69 + 24 = 93,正好是「层数」那一行。这条对账很重要,它告诉我们:K3 的每一层都是注意力层,「注意力层构成」这一行覆盖了全部 93 层,没有第三种层混在里面。凡是核对架构,第一步永远是先确认总数守恒——不然后面每一步都建在沙子上。
论文说比例是 3:1。那么 69 ÷ 24 应该等于 3 才对。你算一下,它等于几?
想好了再看
69 ÷ 24 = 2.875,不等于 3。这里必须停下来,别急着说「差不多就是 3」。差 0.125 看着小,但架构里的层数是整数,整数对不上就一定有结构性的原因,不可能是「四舍五入」。当初会想到追这一步,是因为一条经验:论文里给的比例和表里给的数字对不上时,多半是有个边界项没算进去。
回想 §2.1 除了「3 KDA + 1 MLA 重复」之外,还说了一句什么?把那句话对应的层单独拿出来,再算一次比值。
想好了再看
那句话是:「骨干末尾额外放一个 Gated MLA 层,确保最后一层总是执行全局注意力。」这一层不属于任何 block。把它从 24 里减掉:24 − 1 = 23 个 MLA 层属于 block。再算比值:69 ÷ 23 = 3,正好。这就是 2.875 的全部来源——比例 3:1 只对「重复部分」成立,末尾那一层是周期之外的边界修正。
现在反推 block 个数和总层数,验证一遍闭环。
想好了再看
block 个数 B = 23(因为每个 block 有 1 个 MLA)。总层数 = 4 × 23 + 1 = 92 + 1 = 93 ✓。KDA = 3 × 23 = 69 ✓,MLA = 23 + 1 = 24 ✓。三个数全部闭合,说明我们对架构的重建是自洽的。注意:「23 个 block」这个数字论文从头到尾没有直接写出来,是我们自己算的——但它是被表 1 唯一确定的。
最后一问:如果作者没有补末尾那一层,「最后一层是全局注意力」这个性质还成立吗?
想好了再看
其实是成立的——因为每个 block 的最后一层本来就是 Gated MLA(模式是 3 个 KDA 在前、1 个 MLA 在后)。所以如果骨干正好以一个完整 block 结尾,第 92 层已经是全局注意力了。这就产生一个论文没有回答的问题:既然如此,补第 93 层的真正动机是什么?可能是为了凑一个特定的总层数,可能是为了让最后一层的表征在进入输出头之前再做一次全局整合,也可能只是经验上更好。论文只陈述了做法和「确保最后一层做全局注意力」这个说法,没有给理由,也没有给消融。这是本章最值得打问号的一处。
综合题。已知 K3 有 93 层、其中 24 层是 Gated MLA,隐藏维度 7,168,训练上下文 1M token。现在假设有人提议:「把比例从 3:1 改成 1:1,让模型的长文本能力更强。」请回答三问:(甲)改完之后 MLA 层大约有多少层?(乙)这个改动会让推理时的 KV 缓存大致变成原来的几倍?(丙)除了缓存,这个改动还会牵动本章讲过的哪些设计?其中哪一条会直接失效?
(乙)需要缓存潜向量的层数从 24 变成约 46,KV 缓存接近翻倍(约 1.9 倍)。注意这里用的是「缓存量正比于 MLA 层数」这个关系——KDA 层的状态是固定大小的,不随上下文长度增长,所以长上下文下缓存几乎全部来自 MLA 层。
(丙)至少牵动三处。第一,算力:全局注意力是长度平方级的,MLA 层翻倍意味着长上下文下的注意力计算量也接近翻倍,1M token 时这非常贵——所以「1:1 让长文本能力更强」这个说法本身就可疑,它换来的更可能是长文本成本更高。第二,NoPE 的前提被削弱:位置信息全靠 KDA 层提供,KDA 层从 69 降到约 46,位置敏感的层少了三分之一,而 MLA 层依然完全无位置编码——这一条最接近「直接失效」,因为它是被论文明确写成依赖关系的(论文原话:中间的 KDA 层提供位置敏感与近因感知的混合)。第三,末尾补层那条规则需要重新推导(4B+1 = 93 的算式不再适用)。
最该警惕的是:论文没有给任何比例消融,所以「改成 1:1 会怎样」我们只能做机制层面的推断,没有实验依据。任何声称「1:1 一定更好/更差」的说法,在这份材料里都缺证据。
变式:反过来提议「改成 7:1,省一半缓存」。用同样的三问分析一遍。哪一个方向的风险更难被察觉?(提示:缓存爆掉会立刻报错,而位置信息不够用只会让长文本质量悄悄变差。)
本章小结
K3 沿三个维度扩展信息流:序列长度交给混合注意力(本章)、网络深度交给 AttnRes(第4章)、模型宽度交给 Stable LatentMoE。在序列方向上,两种机制整层整层地交替:每个 block 是 3 个 KDA 加 1 个 Gated MLA,比例 3:1,全网重复;骨干末尾再补一个 Gated MLA,使最后一层一定是全局注意力。表 1 的「69 KDA + 24 MLA / 93 层」正是 4 × 23 + 1 的算术指纹。
Gated MLA 这一半做了三件事。压缩缓存:只缓存潜向量 ct = Wcxt,算注意力时用学到的上投影重建键和值——省的是显存,不是算力。去掉位置编码:所有 MLA 层用 NoPE,位置信息由中间的 KDA 层隐式提供,顺带免掉扩长时重调 RoPE 频率基或套 YaRN 的麻烦。加一道满秩输出门(式 7),让每个 token 逐通道决定从全局注意力听进来多少。此外,训练时注意力输出保持 FP32 以纠正 flash attention 的有偏舍入误差,代价是输出 tile 片上占用翻倍,于是内核改为与 KV 暂存缓冲区重叠,反而换来更高的训练吞吐。
本章标记为存疑或论文未说明的地方:3:1 这个比例没有消融,证据在被引用的 Kimi Linear 工作里而不在本文;「末尾必须补一层全局注意力」只有做法没有理由(而且每个 block 本来就以 MLA 结尾);MLA 潜向量 ct 的维度没有给出;式 7 相比 KDA 的式 6 少了一步 RMSNorm,论文没解释;内核为什么不能和 query tile 重叠、重设计带来多少吞吐提升,都没有交代;NoPE 只给了设计理由,没有对照实验;2.5 倍缩放效率是论文自述且是架构、数据、训练配方打包的结果。
第4章 Attention Residuals:让「深度」也用上注意力
一句话导语:Transformer 当年用注意力取代了 RNN 沿时间的顺序传递,AttnRes 把同一套方法论搬到深度上——让第 40 层不必顺着残差链一路继承,而是像挑选 token 一样,从所有先前层里挑着取。这一章讲清这个类比为什么成立、在哪失效,以及为什么它最后必须被「块化」才养得起。
学完这一章你应该能做到
- 说清标准残差连接为什么是个瓶颈,并把它和 RNN 沿时间的瓶颈一一对应起来
- 逐符号读懂式 8 和式 9,解释伪查询 ql = wl 不依赖输入,为什么注意力权重仍然是数据相关的
- 解释为什么真正的代价是 O(Ld) 显存而不是 O(L2d) 算力
- 说清式 10 里「块内第 1 层」和「第 2 层及以后」为什么写法不一样
- 自己核对:93 层怎么切成 8 块、为什么末块是不满的、为什么算上词嵌入是 9 个
- 指出 Block AttnRes 相对全注意力残差牺牲了什么
进门先记住一件事
「block(块)」这个词在论文里有两个互不相同的含义。第3章里的 block 是 4 层(3 个 KDA + 1 个 Gated MLA);本章 Block AttnRes 里的 block 是 12 层。它们是两套独立的划分,本章出现的「块」一律指后者。
4.1 灵魂所在:把「时间上的老问题」搬到「深度」上
标准残差连接不够用在哪
先说清什么是残差连接(residual connection):深层网络里每一层不直接输出新结果,而是输出「上一层的结果 + 我这层算出的修正量」,即 hl = hl−1 + fl(hl−1)。这样梯度能顺着加号一路传回去,网络才堆得到上百层。
代价是:所有先前的信息都被压进一个状态 hl 沿深度往下传。第 40 层想用第 3 层的某个特征,只能指望它在中间 36 次加法里没被冲淡。论文的原话是:这是一个瓶颈,让人想起 RNN 沿时间的瓶颈。
这个类比是本章的灵魂。RNN(Recurrent Neural Network,循环神经网络)处理句子是从左到右一个词一个词读,每读一个就更新一次隐藏状态;到第 100 个词时,前面 99 个词的信息全都挤在那一个状态里,想调用第 3 个词的细节只能希望它还没被冲掉。序列建模领域怎么解决的?Transformer 用注意力取代了循环:每个位置不再只能看「上一步传下来的状态」,而是用数据相关的权重直接访问所有先前位置,需要谁就给谁高权重。
把这段话里的「位置」全部换成「层」,就是 AttnRes 的全部想法。论文写得非常直接:AttnRes 把同样的方法论用到深度上——每一层从所有先前层里有选择地检索表示,而不是均匀地累加它们。
打个比方
标准残差像传话游戏:每个人只能听前一个人说,再把自己的补充加进去传给下一个。传到第 40 个人时,第 3 个人说的原话早就面目全非。AttnRes 则是给第 40 个人一份所有人的发言记录,他自己决定重点听谁的。
类比失效处:传话游戏里「听谁的」是人临场判断,而 AttnRes 的查询是层的固定参数(下一节会看到),更像「第 40 号听众有一个长期固定的口味」。另外,发言记录不是免费的——把所有层的输出都留着,正是本章后半段要解决的显存问题。
这不是本文提出的方法
AttnRes 在论文里带着引用 [57](Kimi Team 的 Attention Residuals 预印本)。也就是说,K3 是采用了这个模块,方法本身的完整实验证据在那篇工作里。本章后面会看到,「N ≈ 8 就能恢复大部分收益」这条实证结论同样是引用 [57],不是本报告的实验。
把下面四个概念在「序列维度」和「深度维度」之间配对:(甲)RNN 的隐藏状态;(乙)Transformer 的注意力;(丙)标准残差连接;(丁)AttnRes。哪两个是「同一个问题」,哪两个是「同一个解法」?
变式:类比可以推得更远吗?序列维度上有「因果掩码」(一个词只能看前面的词),深度维度上的对应物是什么?它是天然成立的还是需要额外施加?
4.2 全注意力残差:伪查询、键值和 softmax 核(式 8、式 9)
要把注意力搬到深度上,得先回答三个问题:谁来当查询?谁来当键和值?权重怎么算?论文逐个给了答案。
伪查询(pseudo-query):对每一层 l,定义一个该层专属的、可学习的查询向量 ql = wl ∈ ℝd。请特别注意「pseudo(伪)」这个字的含义:在普通注意力里,查询是由当前输入算出来的(q = Wqx);而这里的 ql 不依赖任何输入,它就是这一层自己的一组参数,训练完之后固定不变。
键和值则来自各层的输出,两者取同一个东西:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| l | 当前正在计算的层号 | 「我是第几层」 |
| i | 被检索的源的编号,从 0 数到 l − 1 | 「我可以翻阅的第 i 份材料」 |
| h1 | 词嵌入(token embedding) | 最原始的、没被任何层加工过的输入 |
| fi(hi) | 第 i 层这个模块本身算出来的输出 | 「第 i 层的贡献」,注意是模块输出而非累加后的状态 |
| ki, vi | 第 i 个源的键与值,二者相等 | 「这份材料的索引」和「材料内容」是同一份东西 |
| ql = wl | 第 l 层的可学习伪查询,d 维(K3 中 d = 7,168) | 「第 l 层的长期口味偏好」 |
i = 0 这一项要单独拿出来说:它把词嵌入也放进了可检索的源里。这意味着无论网络多深,任何一层都有一条直达最原始输入的通道,永远不必担心原始信息在中途被磨没了。这个细节在下一节的块化里会被专门保留。
接下来是权重。论文用的是 softmax 核 φ(q, k) = exp(q⊤ RMSNorm(k)),其中 RMSNorm 的作用是防止那些输出幅值大的层主导权重。然后归一化:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| φ(q, k) | 核函数 exp(q⊤RMSNorm(k)):先把 k 归一化,再和 q 做点积,最后取指数 | 「这份材料对我有多合胃口」的未归一化打分,恒为正 |
| q⊤RMSNorm(k) | 两个向量的点积(⊤ 表示转置,把列向量放倒好做乘法) | 方向越一致,分越高 |
| RMSNorm | 把向量按自身的均方根缩放到统一尺度 | 「只看方向,不看嗓门大小」 |
| αi→l | 第 i 层流向第 l 层的注意力权重,所有 i 加起来等于 1 | 第 l 层分配注意力的「预算比例」 |
| Σj=0l−1 | 对所有可用的源求和(分母就是归一化项) | 把所有打分加起来当分母,正是 softmax 的做法 |
| hl | 第 l 层的输入状态,是所有先前源的加权平均 | 「我按自己的口味把材料混了一份摘要」 |
为什么非要在核函数里塞一个 RMSNorm
因为 exp 是指数函数,对输入极其敏感。某一层若输出幅值特别大,它和任何查询的点积都会很大,取 exp 后直接把分母吃掉,α 几乎全给它,其他层被挤到接近 0——AttnRes 就退化成「所有层都只读那个大嗓门层」,选择性荡然无存。加上 RMSNorm 之后比的是方向而非幅值,想拿高权重必须内容对得上。论文的原话正是:RMSNorm 防止大幅值输出的层主导权重。
最后是代价。论文分得很清楚:网络深度是适中的(L < 100),所以这个完整形式的 O(L2d) 算术开销是负担得起的;真正的实际开销是 O(Ld) 的显存——要把所有层的输出一直留着——以及在流水线并行下的跨阶段通信。
给这两个复杂度一点尺度感
用 K3 的数字自己算一遍(以下是我们的量级估算,论文没有给这些数字):L = 93、d = 7,168。算术那边,每个 token 大约要做 93 × 94 ÷ 2 ≈ 4,371 次「查询-键」配对,每次是 7,168 维点积,合计约 3,100 万次乘加,即约 6,300 万次浮点运算;而 K3 每个 token 激活 104.2B 参数,光前向就是约 2,080 亿次浮点运算——AttnRes 的算术占比大约万分之三,确实可以忽略。
显存那边完全是另一回事:每个 token 要留住 93 × 7,168 ≈ 66.7 万个数,按 BF16 每个 2 字节算,约 1.3 MB / token。一条 100 万 token 的序列就是 1.3 TB 量级——单卡显存是 80 GB 这个数量级,根本装不下。这就是为什么论文说瓶颈是显存不是算力。
答辩:如果我是审稿人
你用「Transformer 的注意力能用数据相关的权重访问所有先前位置」来论证 AttnRes 的合理性。可你的伪查询 ql = wl 是一个训练完就固定的参数,跟输入毫无关系。那你的 α 到底哪里「数据相关」了?这个类比是不是偷换概念?
参考防守(先自己组织语言再看)
防守的关键是:注意力权重由一对向量决定,查询固定不代表权重固定。α 的计算式是 exp(ql⊤RMSNorm(ki)),其中 ki = fi(hi) 是第 i 层对当前这个 token 算出来的输出——它当然随输入变化。所以同一个第 40 层,处理不同 token 时会得到不同的 α 分布:查询是「固定的口味」,但「菜」每次都不一样,点到哪道菜自然不同。
不过审稿人的质疑并非全无道理,诚实的回答要承认一个真实的削弱:标准注意力里查询和键都随输入变,AttnRes 只有键随输入变,表达力严格更弱——它无法表达「这个 token 需要第 3 层,那个 token 需要第 20 层,而且第 3 层和第 20 层的输出恰好长得一样」这类需要靠查询侧区分的情形。所以准确的说法是:AttnRes 是注意力的一个受限版本,权重仍是数据相关的,但相关性只经由键侧进入。论文没有讨论这个取舍,也没有给「让查询也依赖输入」的对照实验。至于为什么这么设计,一个合理的猜测是省参数和省计算(查询若依赖输入,每层要多一个 d×d 的投影矩阵),但这是我们的推测,论文未说明。
如果把 φ 里的 RMSNorm 去掉,写成 φ(q, k) = exp(q⊤k),训练中最可能出现什么现象?为什么这个现象会让 AttnRes「白做」?
为什么算「白做」:AttnRes 的全部价值在于有选择地检索——不同层根据内容挑不同的源。一旦权重被幅值绑架,所有层都只读同一个源,这既不是「选择性检索」,也不比标准残差好,反而多付了显存。RMSNorm 把每个键缩放到统一尺度,使得点积只反映方向的一致程度,一个层要想被选中就必须内容真的对得上。论文的措辞——「RMSNorm 防止大幅值输出的层主导权重」——说的就是这件事。
变式:如果改成对查询 ql 也做 RMSNorm,会解决同样的问题吗?(提示:查询是每层一个固定参数,它的幅值影响的是这一层 α 分布的「尖锐程度」,而不是层与层之间谁压过谁。)
有人断言:「因为 ql 是固定参数,所以对同一个模型来说,第 40 层从第 3 层拿到的权重 α3→40 是一个常数,跟输入什么句子无关。」请构造一个具体的反例说明这句话是错的;然后再构造一个特殊情形,在那个情形下这句话反而是对的。
反过来的特殊情形:假如第 3 层是一个常数层——不管输入什么都输出同一个向量(比如权重全为 0 再加一个固定偏置),并且其余所有层也都是常数层。这时所有键都与输入无关,α 才真的成为常数。更弱一点的版本:只要所有 fi 的输出方向都与输入无关(RMSNorm 之后完全相同),α 也会是常数——注意这里只需要方向不变,因为 RMSNorm 已经抹掉了长度。
这个反例的价值:它精确定位了 AttnRes 的数据相关性来自哪一侧。答案是键侧。上面那个答辩框讨论的正是「只有键侧相关」带来的表达力限制。
变式:再构造一个情形,使得虽然键随输入变化,但 α 几乎不随输入变化。(提示:想想如果 ql 的幅值极小会怎样——点积全都接近 0,exp 全都接近 1。)
4.3 Block AttnRes:把 93 层切成几个块(式 10)
为什么必须块化
上一节算过:完整形式的 O(Ld) 显存意味着每个 token 要留住 93 层全部的输出。而且在流水线并行(把网络的不同层放到不同 GPU 上,像流水线一样接力)下,这些层输出还得跨设备传输——通信量同样是 O(Ld)。算术可以忽略,显存和通信不行。所以论文说:为了降低这个开销,我们把层分块。
做法分三步。第一步,把 L 层切成 N 个块,每块 S = L/N 层。第二步,块内不再保留每层的输出,而是把它们求和规约成单个块表示:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| ℬn | 第 n 块所包含的层号集合 | 「第 n 组里有哪几层」 |
| bn | 第 n 块的块表示:块内所有层输出之和 | 把一组人的发言压成一份会议纪要 |
| bni | 块内前 i 层的部分和 | 「纪要写到第 i 个人为止的半成品」 |
| b0 = h1 | 第 0 号「块」被特意定成词嵌入本身 | 永远保留一条通往原始输入的路 |
| S = L/N | 每块的层数 | 「几个人合写一份纪要」 |
第三步,跨块才做全注意力,而且只对 N 个块级表示做。具体到块 n 里的第 i 层,值矩阵是:
V = [b0, b1, …, bn−1, bni−1]⊤ (i ≥ 2,块内后续层)
| 符号 | 是什么 | 直觉 |
|---|---|---|
| V | 值矩阵:把当前层可用的所有「源向量」摞成的一张表 | 「我现在能翻阅的材料清单」 |
| b0, …, bn−1 | 已经完工的、前面所有块的块表示(外加词嵌入) | 已归档的历次会议纪要 |
| bni−1 | 当前块内、我前面那 i−1 层的部分和 | 本次会议记到我发言之前的草稿 |
| ⊤ | 转置符号,表示这些向量按行摞成矩阵 | 只是排版方式,不改内容 |
| 键与权重 | 仍按式 8、式 9 计算(论文明确这样说) | 换的只是「有哪些源」,打分规则没变 |
为什么两种情况写法不一样?关键在于「当前块内已经完成了几层」。如果我是块内第 1 层,这个块里在我之前一层都没有,部分和是空的,没有 bn0 可放(放零向量只会白占一个注意力名额、白分走一份权重)。从第 2 层起,我前面已有 i−1 层的输出,把它们的部分和挂进清单,就能读到「同块内刚刚发生的事」。注意这带来一个不对称:块内的近期信息只以「部分和」这一整团的形式出现,无法拆开、单独给块内第 1 层和第 2 层不同的权重。这正是块化付出的代价。
最后,论文说最终的输出层聚合全部 N 个块表示——也就是说,模型最顶端仍然能一次性看到所有块级摘要。
式 10 中,块 n 的第 1 层的值矩阵里没有 bn 的任何成分,而第 3 层有 bn2。请回答:(甲)为什么第 1 层不能放 bn 本身?(乙)第 3 层为什么放的是 bn2 而不是 bn?(丙)从第 1 层到第 S 层,值矩阵的行数怎么变化?
(乙)同理,第 3 层只能读已经算完的部分,即块内前 2 层的和 bn2。上标 i−1 是一个严格的因果约束——它在深度方向上扮演的角色,恰好等同于序列注意力里的因果掩码。
(丙)块 n 的第 1 层有 n 行(b0 到 bn−1),第 2 层起变成 n+1 行(多了那个部分和),并且之后一直是 n+1 行——因为不管块内走到第几层,来自当前块的贡献永远只占一行(内容在更新,行数不变)。这正是块化能把开销压到 O(Nd) 的技术原因:任何一层的值矩阵行数都不超过 N+1,与总层数 L 无关。
变式:如果把式 10 改成「块内每层单独作为一行保留,跨块才求和」,行数会变成多少?这样做还能把开销压到 O(Nd) 吗?(提示:算一算最后一块最后一层的行数。)
4.4 块化换来了什么:显存、通信,以及推理时的状态
论文把收益列成三条,值得逐条对着看。
第一条,显存和通信从 O(Ld) 降到 O(Nd)。这是块化的初衷。用 K3 的数字感受一下(以下为我们的量级估算,论文未给出这些数字):不块化时每 token 要留 93 × 7,168 ≈ 66.7 万个数(BF16 约 1.3 MB);块化后只需留 9 个源 × 7,168 ≈ 6.45 万个数(约 129 KB),大约降到十分之一。流水线并行下跨阶段要传的东西同样按这个比例缩小。
第二条,块结构限定了推理时的状态大小。用完整形式的话,走到第 93 层时手里必须攥着 92 份历史层输出;块化之后永远只有至多 N+1 份。状态有了上界,显存规划才做得出来。
第三条,更好地合并两路结果。论文说,块结构使得并行算出的块间结果可以通过 online softmax 与顺序累加的块内部分和更好地合并,显著降低推理时间开销。拆开看:块间的 b0, …, bn−1 是已经定型的,可以一次性批量算完;块内的部分和却是边走边长的,每过一层就变一次。两路时间特性完全不同,分开算再合并才划算。
online softmax(在线 softmax):softmax 的分母是所有打分之和,朴素写法必须先把全部打分算完才能归一化。online softmax 则边算边归一化:一路维护「目前见过的最大值」和「目前累计的和」,来一批新分数就按比例修正已有结果。它的价值是让两批分别算出的部分结果能正确合并,不必等齐——正好对上「块间批量并行 + 块内顺序更新」这个模式。
答辩:如果我是审稿人
你在 §2.2 开头论证说,标准残差的毛病是「均匀累加」,要换成「有选择地检索」。可你的 Block AttnRes 在块内做的事情就是 bn = Σ fj(hj)——原封不动的均匀求和。那你在块内不就是退回标准残差了吗?既然如此,你凭什么说 AttnRes 解决了那个瓶颈?
参考防守(先自己组织语言再看)
这个质疑抓得很准,防守必须先承认:是的,块内确实退化成了等权求和,块化就是在这里做的妥协。然后从三个层面辩护。
其一,粒度不同不等于没解决。原来的瓶颈是「93 层信息全挤进一个状态」,现在变成「至多 12 层挤成一份纪要,纪要之间可以有选择地检索」。压缩比从 93:1 变成 12:1,跨块的选择性是全新的能力——第 80 层可以给第 1 块高权重、给第 5 块低权重,这在标准残差里做不到。
其二,b0 = h1 这个设定不是随手写的:它保证词嵌入永远单独占一个源,永远不会被求和稀释掉。作者显然清楚「求和会丢东西」,所以专门把最重要的那一项拎出来了。
其三,这是有意的代价而非疏漏:论文明确说块化的目的是把 O(Ld) 压到 O(Nd),并且引用 [57] 说 N ≈ 8 就能恢复大部分收益。「大部分」这个词本身就承认了损失的存在。
最后要坦白的缺口:这份报告里没有给出 K3 规模上「完整 AttnRes vs Block AttnRes」的对照数据,「大部分收益」的证据在被引用的工作里,不在这里。审稿人若追问「在 2.78T 规模上还成立吗」,本文回答不了。
设 L = 93、d = 7,168、N = 8(算上词嵌入源共 9 个)。(甲)估算完整 AttnRes 与 Block AttnRes 每个 token 需要留存的数字个数,并算出比值。(乙)如果有人把 N 从 8 提到 31(每块 3 层),显存收益会怎么变?他还能享受到块化的好处吗?
(乙)N = 31 时每块 3 层,需留存 32 × 7,168 ≈ 22.9 万个数,相对完整形式只省到约 2.9 倍。收益大幅缩水,因为 N 越接近 L,块化就越接近完整形式(N = L 时两者完全一样)。他仍能享受一点好处,但已经不足以解决论文关心的显存与通信问题。
这里要看到的规律是:N 是一个在「保真」和「省资源」之间取舍的旋钮,N 小则省得多、块内损失大;N 大则保真但省不下来。论文引用 [57] 说 N ≈ 8 能恢复大部分收益,K3 就取在这个点上。提醒:以上数字是我们按论文的复杂度式子自己算的量级,论文没有给出具体的显存数值。
变式:取 N = 1(整个网络只有一个块)会发生什么?把式 10 代进去,看看它退化成了什么结构。这个结论和 4.1 节的动机对得上吗?
4.5 对账:93 层是怎么切成 8 块的
论文给的实证结论是:N ≈ 8 在各种模型规模上都能恢复大部分收益(引用 [57])。K3 据此做的划分,原文是这么写的:把它的层划分成 8 个块、块大小为 12 层,产生一个不完整的末块,算上词嵌入层共 9 个块。
这句话读快了会出事,必须自己算一遍。
自己推一遍:93 层、块大小 12、8 个块,这三个数能同时成立吗
先做最直白的乘法:8 个块 × 每块 12 层 = 多少层?和 93 对得上吗?
想好了再看
8 × 12 = 96,比 93 多了 3 层。所以「8 块 × 12 层」不可能全部是满的——这正是论文那句「giving a partial final block(产生一个不完整的末块)」要交代的事。碰到这种对不上的数,第一反应不该是「作者写错了」,而是去找哪一项不是满的。
改用除法:93 ÷ 12 等于几?余数是多少?
想好了再看
93 ÷ 12 = 7 余 9。也就是 7 个满块(12 层)+ 1 个只有 9 层的末块,7 × 12 + 9 = 84 + 9 = 93 ✓。所以「8 个块」的准确含义是:7 个满块加 1 个不满的末块,合计 8 个。
那「共 9 个块」的第 9 个是什么?
想好了再看
是词嵌入层,也就是式 10 里的 b0 = h1。它不是真正的一个「层块」,而是被当成一个额外的信息源永远挂在清单最前面。8 + 1 = 9 ✓。这也解释了为什么上一节估算显存时我们用的是 9 而不是 8。
再往前想一步:12 这个块大小和第3章的 4 层注意力 block 有什么关系?
想好了再看
12 = 3 × 4。第3章算过,注意力的周期是 4 层(3 KDA + 1 Gated MLA)。如果两种划分是对齐的,那么一个 12 层的 AttnRes 块恰好装下 3 个完整的注意力 block,也就是 9 个 KDA + 3 个 MLA。更妙的是末块:84 层 = 21 个注意力 block,剩下 9 层 = 2 个完整注意力 block(8 层)+ 骨干末尾补的那 1 个 Gated MLA,正好 9 层,而 21 + 2 = 23 个 block 也和第3章推出的数字对上。
但必须说清楚:论文从来没有说这两种划分是对齐的,也没有说 AttnRes 的块边界落在哪里。上面这套对账是我们的推断,它只证明「对齐」这个假设与所有已知数字自洽,不证明作者就是这么实现的。
原文这句话本身是有歧义的
论文写的是「partition its layers into 8 blocks with 12-layer size」。字面读是「8 个块,每块 12 层」,乘出来是 96,和 93 层对不上。只有结合后半句「giving a partial final block」才能确定是 7 个满块 + 1 个 9 层的末块。论文没有明确写出末块的层数是 9,这个数是我们从 93 − 7×12 推出来的。同样,论文也没有说 AttnRes 的块边界与注意力 block 的边界是否对齐、末块里具体是哪 9 层。
综合题。已知 K3 有 93 层、AttnRes 分成 8 块(前 7 块各 12 层)、d = 7,168,且第3章已推出骨干是 (3 KDA + 1 Gated MLA) × 23 + 1 个 Gated MLA。请回答:(甲)末块有几层?(乙)在「两种划分对齐」的假设下,第 5 块里各有几个 KDA 层和几个 MLA 层?(丙)第 93 层(最后一层,Gated MLA)在做 AttnRes 时,它的值矩阵最多有几行?(丁)如果把 AttnRes 块大小从 12 改成 6(N ≈ 16),(甲)到(丙)三个答案分别怎么变?这样改的利与弊各是什么?
(乙)12 层 = 3 个完整的注意力周期(每周期 3 KDA + 1 MLA),所以第 5 块有 9 个 KDA + 3 个 Gated MLA。前提是两种划分对齐,而论文没有说明这一点。
(丙)第 93 层属于第 8 块(末块),且不是该块第 1 层,所以值矩阵包含 b0 到 b7(8 行)加上末块内部分和(1 行),共 9 行。注意这个数和「共 9 个块」是一致的,也印证了 4.3 节的结论:行数上界是 N+1,与 L = 93 无关。
(丁)块大小改成 6 后:93 ÷ 6 = 15 余 3,变成 15 个满块 + 1 个 3 层末块,共 16 块,末块 3 层;6 层 = 1.5 个注意力周期,不再是整数个周期,块边界会切在注意力 block 中间,「对齐」这个假设直接失效;最后一层的值矩阵最多 16 + 1 = 17 行。
利:块内被等权求和吞掉的层从 12 降到 6,选择性更细,理论上更接近完整 AttnRes。弊:显存与通信从约 9 份升到约 17 份,几乎翻倍,块化的初衷被削掉一半;推理时的状态上界也随之变大;而且论文引用的实证是「N ≈ 8 已能恢复大部分收益」,继续加大 N 的边际回报很可能很小。要强调的是:本报告没有给 K3 规模上不同 N 的对照数据,上面关于「利」的判断是机制推断,不是实验结论。
变式:如果 K3 的层数不是 93 而是 96,用块大小 12 会得到什么?那时论文里「不完整的末块」这句话还需要吗?再想一层:作者是先定层数还是先定块大小?(提示:93 = 4 × 23 + 1 这个式子是被第3章的注意力结构逼出来的。)
4.6 后面还会再见到它:训练时的 checkpointing 与推理时的两阶段内核
AttnRes 的开销不只是「显存里多存几份东西」,它同时牵动训练系统和推理系统的实现。论文在后面的系统章节给了两个配套优化,本章先埋线头,第10、11章会展开。
训练这边:块表示在块的边界层生成一次,之后被块内所有层共享、常驻 GPU;而且整个 AttnRes 计算被 checkpointing(重计算:反向时不保存中间结果,临时再算一遍)整个包住,所以每一层为反向保存的激活与标准残差架构完全相同——也就是说,引入 AttnRes 不会让训练显存额外膨胀。流水线并行下则采用基于缓存的流水线通信,只增量传输新生成的块。
推理这边:Block AttnRes 采用两阶段调度——一次批量的块间遍历,每个块只读一次缓存的块表示;之后每层通过 online softmax 合并把块内部分和折进来。这正是 4.4 节第三条收益的工程落地形式。
请构造两个极端情形:(甲)一个使 Block AttnRes 与完整 AttnRes 严格等价的参数设置;(乙)一个使二者差别最大的情形——要具体说出「块化到底丢掉了什么能力」,最好举一个它做不到的具体任务。
(乙)取 N = 1(整个网络一个块)。这时跨块注意力只剩下 b0(词嵌入)和块内部分和两个源,几乎完全退回标准残差的「均匀累加」。丢掉的具体能力是:无法在同一个块内部做出区分。举个具体任务——假设第 3 层学到了「识别引号」,第 9 层学到了「识别缩进」,而第 40 层在处理代码时只想要缩进、不想要引号。完整 AttnRes 可以给第 9 层高权重、给第 3 层接近 0 的权重;但只要 3 和 9 落在同一个块里,它们已经被加成了一个向量,第 40 层只能整份要或整份不要,再也无法把它们分开加权。这就是块化的本质代价,也是上面答辩框里审稿人抓住的那一点。
顺带说明为什么 K3 取 N = 8:它落在两个极端之间,论文引用 [57] 称这个取值能恢复大部分收益——注意这是被引用工作的结论,本报告没有复现该对照实验。
变式:如果块内不用等权求和,而是用一组可学习的固定权重加权求和(每层一个标量),能不能挽回(乙)里丢掉的能力?为什么?(提示:那组权重会随 token 变化吗?和 4.2 节讨论过的「数据相关性来自哪一侧」联系起来想。)
本章小结
AttnRes 的出发点是一个类比:标准残差连接把所有先前信息压进单个状态 hl 沿深度传递,这个瓶颈和 RNN 沿时间的瓶颈是同一回事;序列建模领域早已用注意力解决过它,AttnRes 就把同一套方法论搬到深度上——每一层从所有先前层里有选择地检索,而不是均匀累加。
技术实现分两步。完整形式给每层一个可学习的伪查询 ql = wl(不依赖输入),把词嵌入和各层输出同时当作键和值(式 8),用带 RMSNorm 的 softmax 核算权重(式 9)。它的算术开销 O(L2d) 在 L < 100 时可以接受,真正的负担是 O(Ld) 显存和流水线并行下的跨阶段通信。Block AttnRes 把 L 层切成 N 块,块内求和成一份块表示、并特意令 b0 = h1 保住词嵌入,跨块才做全注意力(式 10 两种写法的差异来自因果性)。收益是显存与通信从 O(Ld) 降到 O(Nd)、推理状态有了上界、块间并行结果能用 online softmax 与块内顺序部分和更好地合并。K3 取 N ≈ 8:93 层切成 7 个满 12 层的块加 1 个 9 层的末块,算上词嵌入共 9 个源。
本章标记为存疑或论文未说明的地方:「8 blocks with 12-layer size」字面乘出来是 96,与 93 层不符,末块是 9 层这个数是我们推出来的,论文未明写;AttnRes 块边界与第3章的 4 层注意力 block 是否对齐,论文完全没说,本章第 4 步对账是我们的推断;「N ≈ 8 恢复大部分收益」引用的是文献 [57],本报告没有在 K3 规模上给出对照数据;「完整 AttnRes vs Block AttnRes」在 2.78T 规模上的差距没有数据;伪查询为什么不依赖输入(省参数?防过拟合?)论文未解释;本章所有显存与算力的具体数值都是我们按论文的复杂度式子做的量级估算,论文没有给出这些数字。
第5章 Stable LatentMoE:把「宽度」做稀疏,而不炸掉
一句话导语:这一章回答「怎么把一层里的小网络从 384 个扩到 896 个、还不让训练崩掉」——K3 用三件事做到:把专家搬进一个更窄的潜空间、在上投影前插一个 RMSNorm、给激活值装两道软限位,再用一个不需要学习率的分位数规则把 896 个专家的负载摆平。
学完这一章你应该能做到
- 用自己的话解释「总参数 2.78 万亿、每个 token 只用 1042 亿」是怎么做到的,并算出 K3 的稀疏度
- 指出 LatentMoE 把哪两个「宽度」拆开了,以及拆开之后省的是哪一笔账
- 把「极端稀疏放大的两个失效模式」和「三个对策」一一对应上,并说清哪个治哪个
- 验证 β·tanh(x/β) 的两个极限,并自己推出 SiTU-GLU 的输出上界为什么是 β₁β₂
- 手算一遍 Quantile Balancing:从路由分数出发,求出每个专家的新偏置,并解释为什么它不需要学习率
- 构造一个反例,说明 QB 一步之后负载仍可能不是恰好均衡
5.1 先搞懂 MoE:为什么「参数多」可以不等于「算得慢」
先把舞台搭好。一个 Transformer 层里有两件事:注意力负责让不同的词互相看一眼(前面几章讲过),前馈网络(Feed-Forward Network, FFN) 负责让每个词各自再想一想。
前馈网络(Feed-Forward Network, FFN):一个只作用在单个 token 上的小网络。它拿到这个 token 的 d 维向量,先用一个矩阵把它升到一个更宽的隐藏维 h,做一次非线性变换,再用另一个矩阵压回 d 维。它不看别的 token,纯粹是「就这一个向量做一次加工」。
不用 MoE 会怎样
想让模型装下更多东西,最直接的办法是把 FFN 的隐藏维 h 调大。但 FFN 的参数量和每个 token 的计算量都正比于 d·h——参数翻倍,算力也跟着翻倍。这条路是 1:1 的:想多记一点东西,就得多算一点。对 2.78 万亿参数这个量级来说,这条路走不通。
混合专家(Mixture-of-Experts, MoE):不把 FFN 做成一个大的,而是做成很多个小的(每个叫一个专家),再配一个路由器(router)。路由器给每个 token 打分,只挑分数最高的 k 个专家真正跑一遍,其余的专家这一次完全不参与计算。
这就把参数量和计算量解耦了。假设有 n 个专家、每个 token 只用 k 个:参数量正比于 n,每 token 计算量只正比于 k。n 可以往上堆,k 保持不动。模型「知道的东西」变多了,但每个 token 走的路没变长。
稀疏度(sparsity):论文里指专家总数除以每 token 激活的专家数,n/k。这个数越大,说明每个 token 用掉的专家占比越小,模型越「稀疏」。
打个比方
把一个 MoE 层想成一家有 896 名坐诊医生的超级医院。病人(token)挂号时,分诊台(路由器)看一眼症状,只把他转给最对口的 16 位医生会诊。医院的「知识总量」是 896 位医生的总和,但每个病人只占用 16 位医生的时间。
类比失效处:真实的医院里医生是互斥的资源,会诊完就被占用了;MoE 里所有 token 是同一个批次并行处理的,一个专家可以同时服务很多 token——这恰恰是后面「负载均衡」问题的根源:某位医生可能被一整批病人同时挤爆,而另一位一个病人都没有。另外,路由器不是真的「懂医」,它只是一个学出来的线性打分器。
K3 的具体数字(论文 Table 1):路由专家 n = 896 个,每 token 激活 k = 16 个,另外还有 Ns = 2 个共享专家(shared experts)——共享专家不参与挑选,每个 token 都必过,负责那些「谁都用得上」的通用变换。这个「共享 + 路由」的组织方式论文说是沿用 DeepSeekMoE 的做法。
K3 每层有 896 个路由专家,每个 token 激活 16 个。(a)算出稀疏度。(b)K2 是 384 个路由专家、每 token 激活 8 个,稀疏度是多少?哪一代更稀疏?
变式:如果 K3 保持 896 个专家不变,但把激活数从 16 降回 8,稀疏度变成多少?每 token 的路由分支计算量会怎么变?(提示:稀疏度和计算量是反着走的,把稀疏度做大总是有代价的。)
论文 Table 1 给出 K3 总参数 2.78T、激活参数 104.2B。算一下 2780 ÷ 104.2 ≈ 26.7。可我们刚算出稀疏度是 56。这两个数都是「总的除以用到的」,为什么差了一倍多?
变式:如果把共享专家数 Ns 从 2 减到 0(其他不变),这个比值会更靠近 56 还是更远离?如果反过来把共享专家加到 8 个呢?
常见误解
很多人以为「稀疏 = 偷工减料 = 效果差」。其实在固定的每 token 计算预算下,MoE 是在问另一个问题:同样只算这么多,我是让所有 token 都走同一条窄路,还是让它们各自走一条更对口的路?论文的立场是后者更值——所以才要把专家池从 384 扩到 896。但这句话论文没有在 §2.3 给出对照实验,属于设计动机而非本节的实验结论。
5.2 LatentMoE:把「模型宽度」和「专家宽度」拆成两件事
专家池扩大听起来只是「多存点参数」,但它有一笔隐藏的账。常规 MoE 里,每个被选中的专家拿到的是完整的 d 维 token 表示。在大规模训练里,专家分散在不同的显卡上,token 要通过网络被送到选中的专家那儿去(这一步叫 dispatch),算完再送回来。选了 k 个专家,就要送 k 份。论文的原话是:通信量和专家权重的流量都随路由重数(routing multiplicity)增长。k 从 8 涨到 16,这两笔账就直接翻倍。
LatentMoE:把「整个模型有多宽」和「路由专家有多宽」当成两个可以分别设定的数。共享专家保留全宽通路(d 维进、d 维出)做通用变换;专门化的路由专家则在一个宽度只有 ℓ 的紧凑潜空间(compact latent space) 里工作。
具体做法:先用一个下投影矩阵 W↓ 把 token 从 d 维压到 ℓ 维,得到 z = W↓x;被送去 dispatch 的是 z,不是 x。所有路由专家都是 ℓ → ℓ 的小网络。等专家算完、加权汇总成一个 ℓ 维向量之后,再用一个上投影矩阵 W↑ 送回 d 维,和共享分支相加。
| 项 | K2 | K3 |
|---|---|---|
| 隐藏维 d | 7,168 | 7,168 |
| Latent MoE 维度 ℓ | — | 3,584(d 的 0.5×) |
| 每专家 MoE 隐藏维 | 2,048 | 3,072 |
| 路由专家数 n | 384 | 896 |
| 每 token 激活专家数 k | 8 | 16 |
| 共享专家数 Ns | 1 | 2 |
| 激活函数 | SwiGLU | SiTU-GLU |
论文说常规 MoE 里「每个被选中的专家收到完整的 d 维表示,所以通信量和专家权重流量随路由重数增长」。用 K3 的数字(d=7168、ℓ=3584、k=16)估一估:LatentMoE 把每个 token 的 dispatch 数据量降到了原来的几分之几?为什么论文特别强调「随路由重数增长」这件事?
为什么强调「随路由重数增长」:因为这两笔开销里 k 是乘数。K3 把 k 从 8 提到 16,如果宽度不变,这两笔账就直接翻倍;把宽度减半,正好抵消掉这次翻倍。所以 LatentMoE 是「激活专家数翻倍」这个决定的前提条件,不是一个可有可无的优化。(论文只说 compact latent space of width ℓ,并未给出通信量的具体数字;上面的 0.5 是我们用 Table 1 的 ℓ/d = 0.5 推出来的。)
变式:假设有人把 ℓ 进一步压到 d 的 0.25×(即 1792),通信量再减半。这样做的代价是什么?(想想路由专家还能表达多少东西,以及 W↓ 丢掉的信息还能不能被 W↑ 补回来。论文没有做这个消融。)
5.3 钥匙:极端稀疏放大的两个失效模式
这一节是全章的钥匙。前面两节讲的都是「怎么把规模做上去」,这一节讲「做上去之后什么东西会坏」。论文的措辞很直白:这种极端稀疏放大了原始设计的两个失效模式。后面 2.3.1、2.3.2、2.3.3 三个小节,就是针对这两个失效模式的三个对策。先记住这个对应关系,后面每一节读起来都会有方向。
失效模式一:路由分支的激活值爆炸
看图 5-1 的下面那条路:W↓ → 一个带门控的多分支专家前馈网络 → W↑。论文说这把它们「串成了一条近乎四次连续矩阵乘法的链条」,并称这个结构是病态的(ill-conditioned);再叠加 2.8 万亿参数的规模,结果就是路由分支内部的激活值爆炸。
为什么「连乘」是个问题
矩阵乘法会缩放向量。如果每一步平均把向量的长度放大 1.5 倍,连乘四次就是 1.5⁴ ≈ 5 倍;如果是 2 倍,四次就是 16 倍。训练过程中这些矩阵是在变的,没人保证它们乖乖地把放大倍数控制在 1 附近。链条越长,偏离 1 的效果被复利放大得越厉害。更麻烦的是训练用的是低精度数字格式,数值一旦冲得太大就会溢出,整步训练直接作废。
读的时候要小心
论文说「近乎四次连续矩阵乘法」,但没有点名是哪四个矩阵。按 LatentMoE 的结构最自然的数法是:W↓(第 1 次)→ 专家内部的门/上投影(第 2 次,两个矩阵并列所以只算一层深度,这大概就是「近乎」两个字的来源)→ 专家内部的下投影(第 3 次)→ W↑(第 4 次)。这是我们的推断,不是论文原文。另外论文用了「ill-conditioned」(病态)这个词,但正文没有给任何定量证据——没有条件数、没有激活范数曲线、没有「不加对策就发散」的训练曲线。这是一个纯粹的作者主张。
失效模式二:近 10³ 个专家的负载摆不平
第二个问题和数值无关,和「分工」有关。路由器要在 896 个专家之间分配 token。如果分配严重不均——有的专家被一整批 token 挤爆,有的一个 token 都没分到——训练就会出问题。论文的说法是:平衡将近 10³ 个专家的负载,已经超出了现有免辅助损失偏置更新还能良好工作的范围。
为什么不均衡有害,论文给了两条理由(在 2.3.3 里):一是拖慢专家并行训练——专家分散在不同显卡上,一步的耗时由最忙的那张卡决定,别的卡只能干等;二是可能让部分专家训练不足——长期分不到 token 的专家梯度稀少,学不出东西,最后变成一堆死参数,白占显存。
读的时候要小心
「896 个专家超出了现有方法的良好工作范围」同样是一个断言。论文正文没有给出「用原来的定步长方法在 896 个专家上会怎么坏」的曲线或数字。我们只能接受这是作者从自己的训练经验里得出的判断。
| 失效模式 | 对策 | 在哪一节 |
|---|---|---|
| 路由分支激活爆炸 | 在上投影 W↑ 之前插一个 RMSNorm | 2.3.1 → 本章 5.4 |
| 把激活函数换成 SiTU-GLU | 2.3.2 → 本章 5.5 | |
| 近 10³ 个专家的负载均衡 | Quantile Balancing(QB) | 2.3.3 → 本章 5.6 |
假设一个团队复现 K3,但只实现了 Quantile Balancing,没做 RMSNorm 也没换 SiTU-GLU。(a)他们最可能先遇到哪种故障?(b)反过来,如果只做了 RMSNorm 和 SiTU-GLU、不做 QB,会遇到什么?(c)这两组故障的性质有什么根本不同?——一个「重跑就能好」,一个「重跑也好不了」,哪个是哪个,为什么?
(a)只做 QB:负载是平了,但路由分支的激活爆炸没治——链条还是那条四次连乘的链条,规模还是 2.8T。会看到训练损失出现尖峰甚至发散,低精度算术溢出。QB 在这里完全帮不上忙,因为它只改「谁被选中」,一个字都没改「被选中之后算出来的数有多大」。
(b)只做 RMSNorm + SiTU-GLU:数值稳住了,训练不会炸,但会慢——每步被最忙的专家卡住;而且会有一批专家长期分不到 token,训练不足,最后 896 个专家里真正起作用的可能只有一小半,等于白花了参数。
(c)根本不同在于可挽回性。数值爆炸是显性的、当场发作的:loss 变 NaN,你立刻知道出事了,回滚到上一个 checkpoint 换个配置重来就行。负载不均衡是隐性的、累积的:训练全程都「正常」跑完了,loss 曲线也许还挺好看,但那些饿死的专家已经永久地没学到东西——这笔损失是训练结束才发现、且无法回滚修复的(要修就得重训)。这就是为什么论文要花整整一个小节、外加两个附录来对付负载均衡,而两个数值对策各自只有一小段。
变式:这三个对策之间有没有互相帮忙的地方?具体说:RMSNorm 把路由分支的尺度归一化了,SiTU-GLU 又给激活值加了硬上界——这两件事是不是重复劳动,去掉一个行不行?(想想 RMSNorm 作用在专家全部算完之后的汇总向量上,而 SiTU-GLU 作用在每个专家内部;一个管出口,一个管过程。)
5.4 式 11 与 Normalized LatentMoE:一个 RMSNorm 放在哪里的学问
现在把一层 Stable LatentMoE 的完整前向写下来。论文式 11 分两行:第一行算路由分支的汇总,第二行把两个分支合起来。
y = ∑j=1Ns Esharedj(x) + W↑ RMSNorm(u)
| 符号 | 是什么 | 直觉 |
|---|---|---|
| x | 这一层的输入,一个 d=7168 维的向量,代表一个 token | 「这个词现在的样子」 |
| 𝒯k(x) | 路由器为 x 选中的那 k=16 个专家的编号集合 | 分诊台开出的会诊名单 |
| pi | 第 i 个被选中专家的路由权重,由 5.6 节的式 13 定义,在名单内归一化到和为 1 | 「这位专家的意见占几成」 |
| W↓ | 下投影矩阵,形状 ℓ×d = 3584×7168 | 把 token 压进潜空间的入口 |
| Eroutedi | 第 i 个路由专家,一个 ℝℓ → ℝℓ 的前馈网络(内部隐藏维 3072) | 潜空间里的一位专科医生 |
| u | 聚合后的路由表示,ℓ=3584 维 | 16 位医生意见的加权综合 |
| Ns | 共享专家个数,K3 每层固定为 2 | 不管什么病都要挂的两个全科 |
| Esharedj | 第 j 个共享专家,ℝd → ℝd,全宽 | 每个 token 都必过的通用变换 |
| RMSNorm | 均方根归一化:把向量除以自己各分量平方均值的平方根,使其「长度」标准化 | 不管进来的向量多长多短,出去都是标准长度 |
| W↑ | 上投影矩阵,形状 d×ℓ = 7168×3584 | 把潜空间的结论翻译回全宽 |
| y | 这一层 MoE 的输出,d=7168 维 | 共享意见 + 专科意见 |
先把式子读顺:第一行说「把 x 压进潜空间,送给 16 个选中的专家,各自算完之后按权重 pi 加权求和,得到 u」;第二行说「共享专家直接吃全宽的 x,它们的输出相加,再加上 u 归一化之后送回全宽的结果」。
整个 2.3.1 节讲的就是式 11 第二行里那个 RMSNorm。论文说:原始的 LatentMoE 是直接把 W↑ 作用在 u 上的,而 u 的尺度会随着「选中了哪些专家」和「路由权重怎么分布」而变化。K3 在专家聚合和上投影之间插了一个 RMSNorm,降低路由分支对尺度变化的敏感度,然后才和全宽的共享分支相加。
为什么位置这么关键
RMSNorm 插在「聚合之后、上投影之前」,正好卡在四次连乘链条的第三次和第四次之间——也就是在数值一路被放大之后、被送回全宽通路之前的最后一个关口。放在这里,前面三步累积的任何尺度漂移都会被一次性抹平。
如果放到 W↑ 之后(即归一化 W↑u),那 W↑ 这次 7168×3584 的大矩阵乘法仍然要在没归一化的输入上做,溢出风险还在。如果放到专家之前(归一化 W↓x),它管不到专家内部和加权求和过程中产生的漂移。
常见误解
很多人以为「pi 已经归一化到和为 1 了,所以 u 是一个凸组合,尺度自然就稳了」。这是错的。权重和为 1 只保证 u 是 16 个专家输出的加权平均,但被平均的那些向量本身可以长可以短、可以互相抵消。不同 token 选中的是不同的 16 个专家,这 16 个专家的典型输出幅度可以差很多倍。下面这道题就让你亲手造一个例子。
有人主张:既然式 13 保证 ∑i pi = 1,那 u 的长度就被夹在专家输出长度的最小值和最大值之间,不会乱跑,RMSNorm 是多余的。请构造两个具体的反例推翻它:(a)一个例子说明同样的专家输出长度下,‖u‖ 可以从接近 0 变到 1;(b)一个例子说明不同 token 之间 ‖u‖ 可以差一个数量级。
(a)令 ‖v1‖ = ‖v2‖ = 1,p = (0.5, 0.5)。方向相同时 u = v1,‖u‖ = 1;方向相反时 u = 0,‖u‖ = 0。同样的权重、同样长度的专家输出,‖u‖ 在 [0, 1] 上任意取值。凸组合的界只对每个坐标分量成立,对向量的范数不成立。
(b)设专家 A 的典型输出范数是 0.5、专家 B 是 5(不同专家的权重矩阵学出来的尺度本来就不同)。token 甲选中两个 A 类专家 → ‖u‖ ≈ 0.5;token 乙选中两个 B 类专家 → ‖u‖ ≈ 5。相差 10 倍。而且被选中的专家是逐 token 动态变化的,所以这个 10 倍的抖动是逐 token 发生的,不是一个可以被下一层的固定权重吸收掉的常数偏移。
再补一刀:p 的集中程度也在变。p=(1,0,…,0) 时 u 就是单个专家的输出;p 均匀时 u 是 16 个向量的平均,如果它们方向不一致,平均会显著缩短(大致按 1/√16 的量级)。这两种情形的尺度差异同样是逐 token 的。
这就是 RMSNorm 要解决的东西:把这些逐 token 的、由「选了谁」和「怎么分权重」引起的尺度抖动,在进入 W↑ 之前一次性抹平。
变式:RMSNorm 把 u 的长度抹平了——那「这 16 个专家一致认为该做一个很大的修改」这条信息不就丢了吗?这算不算 RMSNorm 的代价?(提示:想想 RMSNorm 通常带一个可学习的增益参数,它能补回什么、补不回什么。论文并没有写清楚这里的 RMSNorm 是否带可学习增益。)
读的时候要小心
论文说这个额外的 RMSNorm「除了稳定训练之外,还持续改善验证损失和下游基准」(consistently improves validation loss and downstream benchmarks)。这是论文自述,正文没有给出任何消融数字——改善了多少、在哪些基准上、和什么比较,一概没有。另外论文也没有说明这个 RMSNorm 是否带可学习增益、共享分支是否也做了类似处理。
5.5 SiTU-GLU:给两个乘性因子各装一道软限位
第二个数值对策治的是专家内部。要理解它,得先看清楚现在通用的激活函数问题出在哪。
门控线性单元(Gated Linear Unit, GLU):把输入分别过两个矩阵,得到「门分支」和「上分支」两个向量,然后逐元素相乘(记作 ⊙)。原始 GLU 的门分支是 Sigmoid(Wgx),上分支是 Wux。「门」的作用是:sigmoid 的输出在 0 和 1 之间,等于给上分支的每个通道配一个 0~1 的开关,决定这个通道的信息放多少过去。
SwiGLU:把 GLU 的 sigmoid 门换成 Swish,即 Swish(x) = x·Sigmoid(x)。论文说它在 Transformer 里表现很好,后来被大模型广泛采用——但也明说「对它为什么有效的完整解释仍然是开放问题」。K2 用的就是 SwiGLU。
| 门分支 | 上分支 | |
|---|---|---|
| GLU | σ(x) | x |
| SwiGLU | x·σ(x) | x |
| SiTU-GLU | β1 tanh(x/β1) · σ(x) | β2 tanh(x/β2) |
SwiGLU 在极端稀疏下的麻烦
论文的诊断很精确:SwiGLU 的两个乘性因子都是无界的。门分支 xσ(x) 在 x 很大时约等于 x,没有上限;上分支就是 x 本身,也没有上限。于是当两个分支的同一个坐标恰好都很大时,乘积会产生激活离群值(activation outliers)——一个远大于周围数值的孤立大数。在低精度算术里,这直接抬高了溢出的风险。
那退回原始 GLU 行不行?论文说:GLU 的 sigmoid 门确实避免了门的无界增长(sigmoid 最大就是 1),但它不保留 Swish 在正半轴那段近似线性的响应——sigmoid 门在 x 大的时候会饱和到 1,梯度趋近 0,丢掉了 Swish「输入越大、门开得越大」这个性质。
所以要找的东西是:既能压住大值增长,又保留 SwiGLU 那种局部的、偏正半轴的响应特性。
K3 的答案是一个光滑封顶(smooth cap) 函数:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| x | 要被封顶的那个数(实际是向量的每一个坐标各自过一遍) | 原始数值 |
| β | 软上限,一个人为设定的正数 | 「最多允许到这么大」 |
| x/β | 先把 x 按 β 缩小,送进 tanh | 把 tanh 的拐弯位置从 1 挪到 β |
| tanh | 双曲正切,输出永远落在 (−1, 1) | 限位器本体 |
| β·tanh(·) | 再乘回 β,于是输出落在 (−β, β) | 把缩小的量纲还原回去 |
把它同时作用在 Swish 门的线性因子上(即 xσ(x) 里的那个 x,注意 sigmoid 那一半不动)和上分支上,就得到 Sigmoid Tanh Unit GLU(SiTU-GLU):
| 符号 | 是什么 | 直觉 |
|---|---|---|
| x | 专家前馈网络的输入向量(在 K3 的路由专家里是 ℓ=3584 维) | 要加工的原料 |
| Wg | 门分支的投影矩阵 | 算「每个通道该开多大」 |
| Wu | 上分支的投影矩阵 | 算「要放过去的内容」 |
| tanh | 双曲正切,把实数压到 (−1, 1) 之间;原点附近近似等于自变量,两端饱和 | 软的限位器 |
| β1 | 门分支的软上限,K3 取 4 | 门这一半最多贡献 4 |
| β2 | 上分支的软上限,K3 取 25 | 内容这一半最多贡献 25 |
| ⊙ | 逐元素相乘(Hadamard 积):两个同形状向量对应位置相乘 | 「开关」乘「内容」,逐通道各管各的 |
| Sigmoid | 把实数压到 (0, 1) 之间的 S 形函数 | 0~1 的开关本身 |
为什么 sigmoid 那一半不用封顶
Swish 写成 x·σ(x),是「线性因子 x」乘「sigmoid 因子」。sigmoid 本来就在 (0,1) 里,天生有界,不需要再封。会跑掉的只有那个裸的 x。所以 SiTU-GLU 只把它换成 β1tanh(x/β1),sigmoid 原样保留——这样 x 小的时候门的行为和 Swish 几乎一样,x 大的时候被 β₁ 拦住。
自己推一遍:softcap 的两个极限,和为什么输出上界正好是 β₁β₂
已知 tanh(z) ≈ z(当 z 很小)。那么当 x 远小于 β 时,β tanh(x/β) 大约等于多少?
想好了再看
令 z = x/β。x 远小于 β 就意味着 z 很小,于是 tanh(z) ≈ z = x/β,两边乘 β 得 β tanh(x/β) ≈ β · (x/β) = x。结论:在原点附近,softcap 就是恒等映射,什么也没做。
当初为什么会想到用 tanh:因为需要的正是这样一个函数——「小的时候不管,大的时候管」。tanh 是最常见的、原点处斜率为 1 且两端饱和的光滑函数。除以 β 再乘 β 这个操作,作用就是把 tanh 的「拐弯位置」从 1 挪到 β:想让它在多大的数值上开始拦,就把 β 设成多大。当 x 远大于 β 时呢?β tanh(x/β) 趋向什么?
想好了再看
z = x/β 变得很大,tanh(z) → 1,所以 β tanh(x/β) → β。负方向同理趋向 −β。所以对任意 x,都有 |β tanh(x/β)| < β——这是一个严格的上界,不是「通常不会超过」。
现在看式 12 的两个中括号。第一个中括号(门分支)的绝对值最多是多少?第二个(上分支)呢?
想好了再看
门分支 = β1tanh(·/β1) · σ(·)。第一个因子的绝对值 < β₁(上一步的结论),第二个因子 σ 的取值在 (0,1),所以整个门分支的绝对值 < β₁ · 1 = β₁ = 4。
上分支 = β2tanh(·/β2),绝对值 < β₂ = 25。
注意这里 sigmoid 起了双重作用:它既是「门」的语义,又顺手保证了门分支的界不会超过 β₁。两个中括号逐元素相乘。输出的每个坐标的绝对值最多是多少?为什么是相乘而不是相加?
想好了再看
逐元素相乘意味着输出的第 c 个坐标 = 门分支第 c 个坐标 × 上分支第 c 个坐标。两个数相乘,绝对值就是绝对值相乘:|f(x)| < β1 · β2 = 4 × 25 = 100。这正是论文 Figure 4 里标的 |f(x)| ≤ β1β2 = 100。
是相乘不是相加,因为 GLU 结构本身就是「门 ⊙ 内容」。这也正是问题的来源:两个各自无界的因子相乘,误差不是相加而是相乘的——两边同时偏大,乘积就平方级别地偏大。SwiGLU 里这两个因子都无界,所以论文说「同时出现的大坐标会产生激活离群值」。SiTU-GLU 把两个因子分别封住,乘积的界就自动是两个界的乘积。最后一步:为什么 β₁ 和 β₂ 要取得不一样(4 和 25),而不是都取 10(乘积同样是 100)?
想好了再看
因为两个分支的角色不同。门分支的语义是「开多大」,本来就不该是个大数——Swish 门在实践中主要工作在小值区间,β₁ = 4 已经覆盖了它的正常工作范围,超过 4 的门几乎一定是异常值,该拦。上分支承载的是「内容」,数值幅度天然更大,封得太狠会把正常信号一起削掉,所以给到 25。
但要诚实地说:以上是对论文取值的合理解释,不是论文给的理由。论文只写了「we set β₁ = 4 for the gate branch and β₂ = 25 for the up branch」,没有给消融实验,也没有解释这两个数是怎么选出来的、为什么差了六倍多。
论文对 SiTU-GLU 的总结是:缩放后的 tanh 在原点附近近似线性、在大幅值处有界,因此 SiTU-GLU 既保留了 SwiGLU 的局部响应,又控制住了乘积里的两个因子。Figure 4 画的就是这三条曲线在同一个区间 x ∈ [−10, 100] 上的对比:SiTU-GLU(红线)在原点附近紧贴 SwiGLU,在大的正输入处逼近 100 这条界,而 SwiGLU 一路往上冲、没有上界。
K3 取 β₁ = 4、β₂ = 25。(a)SiTU-GLU 的输出绝对值上界是多少?(b)如果有人把 β₂ 从 25 改成 100,上界变成多少?(c)β₂ = 100 时,输入 x = 3 处上分支的输出大约是多少?
变式:如果把 β₁ 和 β₂ 都设成 10⁶(近乎无穷大),SiTU-GLU 会退化成什么?(一个一个分支验算:门分支 β1tanh(x/β1)·σ(x) → ?上分支 β2tanh(x/β2) → ?)
论文说 SiTU-GLU「在原点附近紧贴 SwiGLU」。请证明这件事:写出 SiTU-GLU 在 x 很小时的标量表达式,并说明它为什么等于 SwiGLU 的标量表达式。然后回答:为什么这个性质对「换掉一个已经被广泛验证的激活函数」这件事特别重要?
SiTU-GLU(x) ≈ [x·σ(x)] · [x] = SwiGLU(x)。逐项相同。
为什么重要:SwiGLU 是被无数模型验证过的设计,它的好处(论文自己也说「完整解释仍然开放」)大部分体现在正常数值范围内的行为上。SiTU-GLU 的设计目标是只改异常区、不动正常区——正常区一模一样,所以 SwiGLU 那些说不清但确实有效的好处被完整继承;异常区被限位器拦住,所以溢出风险被消掉。这是一种「非侵入式修补」的思路:新方法在旧方法的工作区间上是旧方法的一阶近似,只在旧方法出问题的地方才不同。如果换成一个在原点附近就和 SwiGLU 差很多的激活函数,那所有超参数(学习率、初始化尺度等)都要重调,风险大得多。
变式:β₁ = 4 意味着「远小于 4」才算原点附近。可 sigmoid 在 x = 4 时已经是 0.982 了,门基本全开。那在 x = 4 这个不大不小的地方,SiTU-GLU 的门分支和 SwiGLU 的门分支差多少?(算一算 4·tanh(1)·σ(4) 和 4·σ(4),tanh(1) ≈ 0.762。)这个偏差大到会有影响吗?
有人质疑:「既然目的只是不让数值超过 β,直接用硬截断 clip(x, −β, β) 不就行了?tanh 又慢又绕。」请构造一个具体情形,说明硬截断会带来软封顶没有的问题。(提示:想想训练是怎么进行的。)
用硬截断:输出 = 25(被削平)。反向传播时这一路的梯度乘以 clip 的导数 0,得到 0。于是产生这个 30 的那些参数收不到任何梯度信号——没有力把 x 往回拉。如果这时别的因素(比如权重衰减、别的样本)不恰好把它拉回来,这个通道就永久卡在饱和区,等于报废了。而且它是无声的:前向输出看起来完全正常(就是 25),你不会在 loss 上看到任何异常。
用 softcap:输出 = 25·tanh(30/25) = 25·tanh(1.2) ≈ 25 × 0.834 ≈ 20.9,导数 = 1 − tanh²(1.2) ≈ 1 − 0.696 = 0.304,还有相当可观的梯度。即使 x = 100(x/β = 4),导数 = 1 − tanh²(4) ≈ 1 − 0.99933 ≈ 6.7×10−4——小,但不是 0,仍然有一条把它往回拉的路径。
第二个反例:不连续的二阶行为。clip 在 x = β 处导数从 1 突然跳到 0,这个折点会让优化过程在边界附近抖动;tanh 处处光滑,没有这个问题。
论文把「与硬截断的比较」放在了附录 B,正文只提了一句「§B gives ... comparison with hard clamping」,完整内容见第14章。
变式:反过来构造一个「硬截断反而更好」的角度——比如从计算速度、或者从「界是严格的 β 而不是渐近的 β」这两点出发。(软封顶的输出永远达不到 β,只是无限逼近;硬截断可以正好取到 β。这个区别在什么场景下重要?)
5.6 Quantile Balancing:把偏置直接设成「对得上目标负载的那个分位数」
这是本章最难的一节,也是 K3 这一部分最有意思的想法。慢慢来。
问题:过热的专家和饿死的专家
路由器是学出来的,没有任何东西天生保证它把 token 均匀地分给 896 个专家。实际发生的是:少数几个专家变成「热门」,一批 token 全往那里挤;另一批专家几乎没人光顾,论文 Figure 5(a) 里用更深的圆表示过热的专家(overheated),用淡化的圆表示利用不足的专家,用虚线圆表示濒死的专家(dying experts,一个 token 都没分到)。
论文给的两条危害前面提过,这里再落实一下:拖慢专家并行训练——专家分布在不同显卡上,一步的墙钟时间由负载最重的那张卡决定;可能让部分专家训练不足——饿死的专家几乎收不到梯度,训练完还是初始化时的样子。
K3 的路由框架:免辅助损失的偏置
怎么让路由变均匀?一个老办法是加辅助损失:在训练目标里额外加一项「惩罚不均衡」的损失。但这样做会污染主目标——模型为了均衡而牺牲语言建模效果。K3 明确说自己用的是免辅助损失路由(auxiliary-loss-free routing):不加损失项,而是给每个专家配一个偏置 bj,只加在用于 Top-k 选择的那个分数上。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| xi | 这一批里第 i 个 token 的表示 | 第 i 个「病人」 |
| Wr | 路由器的权重矩阵(si = Sigmoid(Wrxi)) | 分诊台的打分规则,跟着训练一起学 |
| si | token i 对全部 n 个专家的路由分数,每个分量在 (0,1) | 「这个病人和每位医生的匹配度」 |
| si,j | si 的第 j 个分量,即 token i 对专家 j 的分数 | 一个具体的匹配度 |
| b | 长度 n 的偏置向量,每个专家一个,与 token 无关 | 给冷门专家的「加分」、给热门专家的「减分」 |
| argtopk | 取最大的 k 个的编号(不是取值) | 开出会诊名单 |
| 𝒯i | token i 选中的 k 个专家的编号集合 | 名单本身 |
| pi,j | 混合权重:用原始分数 s,不含 b,在选中的 k 个之间归一化 | 名单内各位专家的话语权 |
整个设计的关键:b 出现在第一个式子里,不出现在第二个式子里
请把这句话读三遍。b 参与了「选谁」,但完全不参与「选中之后各占几成」。论文的原话是:因为 b 被从 pi,j 里省略了,它调节的是分发(dispatch),而不改变混合权重,也不改变路由器基于梯度的优化。
这带来三个后果:
(1)模型输出不被负载均衡污染。假如 b 进了 p,那「为了均衡给冷门专家的加分」就会直接变成「这个冷门专家在最终答案里的话语权变大」——你为了让机器跑得均匀,篡改了模型的回答。现在不会。
(2)路由器的梯度是干净的。b 不在 p 里,所以反向传播到 Wr 的梯度里不含 b 的贡献。路由器还是按语言建模损失去学它认为对的偏好,均衡机制在另一条轨道上运行。
(3)代价是:b 没有梯度可用,只能另外用一条规则去更新它。这条规则就是 2.3.3 的全部内容。
老办法的困境:一个调不好的步长
原来的免辅助损失方法用一条定步长符号更新规则:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| ℓj(t) | 专家 j 在第 t 步实际收到的 token 数(负载) | 这位医生今天看了几个病人 |
| ℓ̄ | 平均负载 | 「本该看几个」 |
| sign(·) | 符号函数:正数取 +1,负数取 −1 | 只看方向,不看差多少 |
| γ | 固定步长(一个超参数) | 每次挪多大一步 |
读法:如果专家 j 的负载低于平均,sign 为 +1,就给它的偏置加 γ,下一步更容易被选中;反之减 γ。注意 sign 把「差多少」这个信息全丢了——差 1 个 token 和差 1000 个 token,动作完全一样,都是挪 γ。
论文指出这里的困境:γ 在「适应太慢」和「震荡」之间权衡。γ 小了,负载偏离目标之后要很多步才能纠回来;γ 大了,会冲过头,然后往回冲,来回震荡。而 LatentMoE 把每层路由专家池扩到 896 个之后,这个权衡更难做了——专家越多,每个专家的负载统计噪声越大(平均只有 mk/896 个 token),也就越容易把噪声当成信号乱挪。
打个比方
老办法像用一个只有「热一点 / 冷一点」两个按钮的空调,每按一次固定改 0.5 度。想从 30 度调到 22 度,得按 16 次;按快了又会一路冲到 18 度再往回调。QB 则是直接输入「22 度」——它不是朝目标挪一步,而是解出能达到目标的那个设定值。
类比失效处:空调的「22 度」是一个可以直接设定的物理量,而专家的负载不能直接设定——你只能设 bj,负载是 bj 加上路由分数之后 Top-k 竞争的结果。QB 巧的地方就在于它找到了一个办法,把「我要的负载」反解成「该设的 bj」。而且这个反解只在「其他条件固定」的假设下精确——5.6 末尾会看到这个假设不总成立。
QB 的核心:把偏置设成一个分位数
分位数(quantile):把一堆数从小到大排好,「q 分位数」就是排在 q 这个比例位置上的那个数。比如 100 个数的 0.9 分位数,就是排序后第 90 个数——有 90% 的数不超过它,10% 的数超过它。中位数就是 0.5 分位数。
分位数均衡(Quantile Balancing, QB):论文的定义是「把每个专家的偏置设成与它的目标负载相匹配的那个路由分数分位数」。注意这里没有步长、没有学习率——它是一个直接赋值,不是一次挪动。
先把符号摆清楚。考虑一个训练批次:m 个 token,n 个专家,Top-k 选择。总共要发出 mk 个「token→专家」的分配。均分到 n 个专家头上,每个专家的目标负载是
| 符号 | 是什么 | 直觉 |
|---|---|---|
| m | 这一批里的 token 总数(真实训练中是全局批次,百万量级) | 今天来了多少病人 |
| n | 路由专家总数,K3 每层 896 | 有多少位坐诊医生 |
| k | 每个 token 激活的专家数,K3 是 16 | 每个病人看几位医生 |
| mk | 这一批要发出的「token→专家」分配总数 | 今天总共要开多少次会诊 |
| q | 目标负载:完全均衡时每个专家该收到的 token 数 | 「每位医生本该看几个病人」 |
| := | 「定义为」,不是待求解的等式 | 这是我们自己定的目标 |
(a)论文 Figure 5 的例子是 m=8 个 token、n=4 个专家、k=1。算出目标负载 q。(b)在 K3 的真实设置下(n=896、k=16),如果一个全局批次有 100 万个 token,每个专家的目标负载是多少?(c)验证一下:q/m 等于什么?
变式:如果 m=10、n=4、k=1,q = 2.5——一个专家不可能收到半个 token。这时「让计数恰好等于 q」这句话还成立吗?论文的推导里假设了什么?(提示:论文说 「Assuming no ties」,但没有讨论 q 不是整数的情况。真实规模下 m 是百万级,这个问题被稀释了,但推导的严格性有个缺口。)
第一步的巧妙:从 Top-(k+1) 里白捡一个门槛
QB 只用一次前向传播就推出下一步的偏置。第一个动作是:把路由时的 Top-k 换成对有偏分数 si + b(t) 的 Top-(k+1)。
取出来的 k+1 个数里:前 k 个就是实际走的路由(和原来一模一样),第 (k+1) 个是一个门槛 αi(t)——它的含义是:一个专家要想挤进 token i 的 Top-k,它的有偏分数必须超过这个值。
自己推一遍:为什么这个门槛来得这么便宜
把 token i 对 n 个专家的有偏分数从大到小排好,记为 v(1) ≥ v(2) ≥ … ≥ v(n)。哪些专家进了 Top-k?一个「新来的」专家要挤进去,分数需要超过哪个数?
想好了再看
进 Top-k 的是 v(1)…v(k)。一个新来的要挤进去,就得把现在的第 k 名挤出去,所以它得超过当前的第 (k+1) 名 v(k+1)——超过第 (k+1) 名,它就排到了第 k 位或更前。所以门槛 αi = v(k+1)。
当初为什么会想到这一步:因为我们要问的问题是「假如给专家 j 的偏置调一调,它能不能进 token i 的名单」。这个问题的答案只依赖于一个数——名单的准入线。而准入线就是第 (k+1) 名的分数。现在的关键问题:把 Top-k 改成 Top-(k+1),会不会改变实际的路由结果?
想好了再看
不会。Top-(k+1) 返回的是排序后的前 k+1 个,其中前 k 个就是 Top-k 的结果,一个不多一个不少。第 (k+1) 个只是被顺手读出来当门槛用,不参与路由。所以 QB 对前向计算的唯一改动是「排序的时候多取一个数」——多取一个数的开销,相对于 16 个专家的前馈计算,可以忽略。
论文的原话是:「Taking the cutoff from Top-(k+1) routing avoids a separate token-side quantile.」如果不这么做,你就得为每个 token 单独再算一次「它的第 k+1 高分是多少」,那是对 896 个分数的又一次统计——现在这个数是 Top-k 排序的副产品,白捡的。门槛 αi(t) 是用有偏分数 si+b(t) 算的,也就是说旧偏置藏在门槛里。这会不会让更新变成一团乱麻?
想好了再看
论文特意点了这件事:「the old bias enters the update only through the cutoffs」——旧偏置只通过门槛进入更新,别的地方不出现。式 14 里的间隔用的是原始分数 si,j 减去有偏门槛 αi(t),而不是有偏分数减有偏门槛。这样解出来的 b̂j(t+1) 是一个新的绝对值,不是「在旧值上加一个增量」。这正是「直接赋值而不是挪一步」的技术实现。
第二步:固定门槛,反解偏置
现在把所有 token 的门槛 αi(t) 钉住不动,只问一件事:给专家 j 换一个候选偏置 b̂j,它会收到几个 token?答案是数一数有多少个 token 的分数越过了门槛:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| 1[·] | 指示函数:中括号里的条件成立时取 1,不成立时取 0 | 「算不算一个」的开关 |
| b̂j(t+1) | 专家 j 的候选新偏置(戴帽子表示还没做最后的减均值) | 试着给这位医生加多少分 |
| αi(t) | token i 的准入门槛,来自 Top-(k+1) 的第 k+1 个有偏分数 | 第 i 个病人的会诊准入线 |
| si,j − αi(t) | 论文称为间隔(margin):专家 j 对 token i 「差多少才够格」 | 正数=够格有余,负数=还差这么多 |
把条件挪一挪:si,j + b̂j > αi 等价于 si,j − αi > −b̂j。左边是间隔,右边是一个阈值。于是这个计数就变成了「有多少个间隔超过了阈值 −b̂j」。
这个计数关于阈值 −b̂j 是单调递减的:阈值越高,能越过的间隔越少(阈值定到 +∞,一个都过不去;定到 −∞,全都过得去)。单调 ⇒ 想让计数等于任何一个目标值,都有唯一的阈值可以做到(在无并列的假设下)。这就是「解方程」而不是「挪一步」的数学依据。
令计数 = q,解是什么?把专家 j 这一列的 m 个间隔从大到小排好,要让恰好 q 个严格超过阈值,阈值就得落在第 (q+1) 大的间隔上——这样前 q 个严格超过它,第 (q+1) 个正好等于它(不算超过),后面的都小于它。所以
换句话说:阈值 −b̂j(t+1) 就等于「专家 j 这一列 m 个间隔里第 (q+1) 大的那个」。
最后一步换个说法。「m 个数里第 (q+1) 大」,用分位数怎么表达?有 q 个数在它上面,占比 q/m;而我们在前面验证过 q/m = k/n。所以它下面的占比是 1 − k/n,也就是这批间隔的 (1−k/n) 分位数。写成论文的式 14:
b(t+1) ← b̂(t+1) − mean( b̂(t+1) ) 1
| 符号 | 是什么 | 直觉 |
|---|---|---|
| s:,j | 分数矩阵的第 j 列:全部 m 个 token 对专家 j 的分数 | 「这位医生对全体病人的匹配度」 |
| α(t) | 长度 m 的门槛向量,每个 token 一个 | 每个病人各自的准入线 |
| s:,j − α(t) | 逐元素相减,得到 m 个间隔 | 这位医生对每个病人「差多少」 |
| quantile1−k/n | 取这 m 个间隔的 (1−k/n) 分位数(K3 是 1−1/56 ≈ 0.982 分位数) | 「排在前 1.79% 的门槛线」 |
| 前面的负号 | 因为阈值是 −b̂j,解出阈值后要取负得到偏置 | 间隔越高的专家(说明它太抢手)拿到越负的偏置 |
| mean(b̂) 1 | 所有专家候选偏置的平均值,乘以全 1 向量(即每个分量都减这个平均值) | 把整体的公共偏移抹掉 |
第二行为什么可以随便减一个常数
Top-k 是在同一个 token 内部比较 n 个专家的分数。如果给所有专家的偏置都减去同一个数 c,那每个专家的分数都降了 c,相对大小完全不变,排序不变,Top-k 结果一个字都不变。所以减均值是「免费的」——不改变任何路由行为,只是把 b 这个向量的整体水平拉回 0 附近,防止它在长期训练中无限漂移。
因果性:为什么更新要等到下一步
论文说:「For causality, the update takes effect only in the next step, i.e., a batch is never routed with a bias derived from itself.」——一个批次绝不会用从它自己推出来的偏置来路由。
为什么必须这样?因为 QB 的偏置是从这个批次的分数反解出来的。如果立刻用回这个批次,等于是「先看答案再考试」:模型的路由决定依赖于它自己这一批的统计,训练和推理的行为就对不上了(推理时你面对的是单个请求,不可能有「这一批的分位数」)。论文也说明了:推理时偏置是冻结的——训练结束时的 b 定死,不再更新。
把论文 Figure 5 的例子完整走一遍
论文 Figure 5 用 m=8 个 token、n=4 个专家、k=1 演示了一遍 QB:初始负载是严重不均的 (4, 3, 1, 0)——E₁ 过热,E₄ 是一个 token 都没有的濒死专家;目标负载 q = 8×1/4 = 2;QB 一步之后变成 (2, 2, 2, 2)。
论文没有给出具体的分数数值,下面这组数是我们自己构造的一个满足论文全部条件的算例(m、n、k、初始负载、目标负载、最终负载都与论文一致)。建议拿纸笔跟着算一遍,这一节的所有概念都会在这个表格里落地。设当前偏置 b(t) = 0(初始状态)。
| token | E₁ | E₂ | E₃ | E₄ | 门槛 αi | 去了哪 |
|---|---|---|---|---|---|---|
| t₁ | 0.92 | 0.60† | 0.30 | 0.20 | 0.60 | E₁ |
| t₂ | 0.88 | 0.52† | 0.26 | 0.18 | 0.52 | E₁ |
| t₃ | 0.48† | 0.84 | 0.38 | 0.14 | 0.48 | E₂ |
| t₄ | 0.74 | 0.40 | 0.34 | 0.68† | 0.68 | E₁ |
| t₅ | 0.42† | 0.76 | 0.22 | 0.28 | 0.42 | E₂ |
| t₆ | 0.32 | 0.64 | 0.61† | 0.29 | 0.61 | E₂ |
| t₇ | 0.70 | 0.25 | 0.44 | 0.65† | 0.65 | E₁ |
| t₈ | 0.52† | 0.16 | 0.62 | 0.42 | 0.52 | E₃ |
| 初始负载 | 4 | 3 | 1 | 0 | — | 严重不均 |
注意 k=1 时 Top-(k+1) 就是 Top-2,所以门槛 αi 就是每行的第二高分。第二步,把每个分数减去它所在行的门槛,得到间隔:
| token | E₁ | E₂ | E₃ | E₄ |
|---|---|---|---|---|
| t₁ | +0.32 | 0.00 | −0.30 | −0.40 |
| t₂ | +0.36 | 0.00 | −0.26 | −0.34 |
| t₃ | 0.00 | +0.36 | −0.10 | −0.34 |
| t₄ | +0.06 | −0.28 | −0.34 | 0.00 |
| t₅ | 0.00 | +0.34 | −0.20 | −0.14 |
| t₆ | −0.29 | +0.03 | 0.00 | −0.32 |
| t₇ | +0.05 | −0.40 | −0.21 | 0.00 |
| t₈ | 0.00 | −0.36 | +0.10 | −0.10 |
| 第 (q+1)=3 大 | +0.06 | +0.03 | −0.10 | −0.10 |
| 新偏置 b̂j | −0.06 | −0.03 | +0.10 | +0.10 |
怎么读最后两行:以 E₁ 那一列为例,8 个间隔排序后是 0.36, 0.32, 0.06, 0.05, 0.00, 0.00, 0.00, −0.29。q=2,所以取第 q+1 = 3 大的那个,是 0.06。取负得 b̂1 = −0.06。验算:严格大于 0.06 的间隔恰好有 2 个(0.36 和 0.32)——正是目标负载。四列都可以这样验一遍。E₃ 和 E₄ 这两个冷门专家拿到的是正偏置(+0.10),E₁ 和 E₂ 这两个热门专家拿到负偏置,方向完全符合直觉。
(论文式 14 第二行还要减去均值:mean(b̂) = (−0.06−0.03+0.10+0.10)/4 = 0.0275,减完得 b = (−0.0875, −0.0575, +0.0725, +0.0725)。前面说过这一步不改变任何 Top-k 结果,所以下面直接用 b̂ 验证。)
第三步,用新偏置重新路由:把表 5-4 的每一列各自加上 b̂j,再取每行最大。例如 t₄ 这一行变成 E₁: 0.74−0.06 = 0.68、E₂: 0.40−0.03 = 0.37、E₃: 0.34+0.10 = 0.44、E₄: 0.68+0.10 = 0.78 —— t₄ 从 E₁ 改投 E₄。全部八行算完:
最终负载 (2, 2, 2, 2)。只动了三条边,四个专家全部达到目标负载,而且没有任何一步用到学习率或步长——每个偏置都是从一次前向传播的统计里直接解出来的。
式 13 和式 14 里 b 有两个「不影响什么」的性质:(a)b 不出现在 pi,j 里;(b)给所有 bj 减去同一个常数,Top-k 不变。请分别说明这两条各自保护了什么。然后回答一个更尖的问题:既然 (b) 说公共偏移无所谓,为什么论文还要专门写这一步?
(a)保护的是模型输出和路由器的学习不被均衡机制污染。论文原话:「it regulates dispatch without altering the mixture weights or the gradient-based optimization of the router」。如果 b 进了 p,那么「为了均衡给冷门专家 +0.1」就会变成「这个冷门专家在答案里多占 10% 的话语权」——你为了机器效率篡改了模型的判断。而且 b 会出现在反向传播路径上,路由器学到的偏好会被均衡目标扭曲。分开之后,路由器只管「谁最合适」,偏置只管「谁该被叫上场」,两条轨道互不干涉。
(b)保护的是数值卫生。式 14 第一行解出的 b̂ 是绝对值,其整体水平取决于这一批间隔的绝对位置——完全可能每一步都整体偏正或整体偏负,累积几十万步之后 b 的所有分量都漂到很大的地方。虽然这不影响路由(由 (b) 保证),但会影响数值精度:一堆大数相减求排序,有效位数会被吃掉;而且日志里看到 b 全是 ±1e5 会让人以为出了问题。减均值把 b 钉在均值 0 上,让它的每个分量都保持「相对偏移」的语义——正数=这个专家需要被扶一把,负数=需要被压一压。
更深的一层:正因为公共偏移不影响任何东西,式 14 第一行才有权利只关心「相对关系」——它解出的 n 个值本质上只有 n−1 个自由度,第二行只是挑了一个规范的代表。
变式:如果把第二行改成「减去 b̂ 的中位数」而不是均值,会有什么不同?路由结果会变吗?(不会。)那两种选择有实质区别吗?(提示:想想少数几个极端偏置对均值和对中位数的影响。论文没有讨论这个选择。)
QB 的推导保证了:在门槛 α 固定的前提下,专家 j 恰好有 q 个 token 越过它的门槛。但真实的路由是每个 token 各自取 Top-k。请构造一个具体的小例子(建议 m=4, n=2, k=1),说明 QB 一步之后负载可能不是恰好 (q, …, q)。
门槛(每行第二高):α = (0.10, 0.20, 0.30, 0.55)。初始负载 = (4, 0)。
间隔(s − α):E₁ 列 = (+0.80, +0.60, +0.40, +0.05);E₂ 列 = (−0.80, −0.60, −0.40, −0.05)。
取第 3 大:E₁ 列排序 0.80, 0.60, 0.40, 0.05 → b̂1 = −0.40。E₂ 列排序 −0.05, −0.40, −0.60, −0.80 → b̂2 = +0.60。
验算「越过门槛」的计数:E₁ 有 2 个间隔严格大于 0.40(0.80 和 0.60),对上了;E₂ 有 2 个间隔严格大于 −0.60(−0.05 和 −0.40),也对上了。两个专家都「应该」拿到 2 个 token。
可是真实路由:新的有偏分数为 E₁ = (0.50, 0.40, 0.30, 0.20),E₂ = (0.70, 0.80, 0.90, 1.15)。每行取最大 → 四个 token 全部去了 E₂。负载从 (4, 0) 变成 (0, 4) —— 完全翻转,比之前还不均衡。
为什么会这样:E₁ 和 E₂ 各自解偏置时都假设「对方的门槛不变」,但它们同时生效之后互相踩了脚。t₁ 和 t₂ 对 E₁ 越过了门槛,对 E₂ 也越过了,可它们只能各去一个地方——两个专家都以为自己拿到了这两票,实际只有一个能拿。
诚实地说:论文正文只给了「计数等于 q」这一步的推导,把真正的「balanced-assignment derivation」推给了附录 C(见第14章),正文没有说明在什么条件下这个交换是精确的。上面这个极端例子是刻意构造的(只有 2 个专家、分数高度对称);真实规模下 n=896、m 是百万级,单个专家的偏置变化对某个 token 的排序影响很小,所以实际效果会好得多。但「QB 一步就精确均衡」这个说法,从正文给出的论证里是推不出来的。
变式:把上面的例子改成 n=4 个专家、m=8(也就是表 5-4 那个成功的例子),为什么它就没有翻车?两者的区别在哪?(提示:数一数在新偏置下,每个 token 到底有几个专家越过了它的门槛。成功的例子里恰好每个 token 都有且只有 1 个。)另外想想:既然 QB 的更新到下一步才生效、而且每一步都会重算,那「单步不精确」这件事有多严重?
「把 Top-k 换成 Top-(k+1)」看起来只是多取一个数,但它同时办成了三件事。请把这三件事说清楚:(a)为什么这样取门槛不改变路由结果本身?(b)为什么这样就不用另算一个 token 侧的分位数?(c)为什么门槛必须用有偏分数算,而式 14 的间隔却用原始分数减它?最后综合回答:如果把这三件事拆开单做,QB 的开销会变成什么样?
(a)不改变路由:Top-(k+1) 返回的前 k 个元素就是 Top-k 的结果。第 (k+1) 个被读出来只作统计用,不参与 dispatch、不参与 p 的归一化。所以 QB 对模型的前向行为是零侵入的——这一点很重要,意味着开不开 QB 不会改变模型这一步算出来的东西,唯一的差别在下一步的偏置。
(b)省掉 token 侧分位数:论文原话「Taking the cutoff from Top-(k+1) routing avoids a separate token-side quantile」。QB 要的门槛,本质上是每个 token 的分数分布的一个「上 k 分位点」。如果不从路由里拿,就要对每个 token 的 896 个分数再做一次选择——而 Top-k 排序已经在做这件事了,只是原来把第 k+1 个扔掉了。QB 做的是把一个本来被丢弃的中间量捡回来用。这是整个方法「几乎不要钱」的根本原因。
(c)有偏门槛 + 原始分数:门槛必须是有偏的,因为「能不能进名单」是在有偏分数上比较的,准入线自然长在有偏的尺度上。而间隔用原始分数 si,j 减它,解出来的 b̂j 就直接是「新偏置本身」——它已经把「要补偿掉旧偏置」这件事吸收进去了。如果间隔也用有偏分数,解出来的是「相对旧值的调整量」(论文 Figure 5(b) 里那条红色虚线画的正是 bj(t) − b̂j(t+1)),还得再做一次加减才能得到新偏置。用原始分数是更直接的写法,也让「旧偏置只通过门槛进入更新」这句话成立——旧偏置的影响被限制在一个明确的、单一的通道里。
综合:拆开单做的话,QB 需要(1)正常的 Top-k 路由,(2)额外一遍对每 token 896 个分数的第 k+1 大选择,(3)额外一遍把旧偏置从解里剥出来的运算。合起来做,这三件事变成「排序时多取一个数」。这就是为什么论文可以说「QB derives the next bias from a single forward pass」——单次前向,没有额外的前向或反向。
变式:如果有人改用 Top-(k+5),取第 k+5 个分数当门槛,会发生什么?(提示:那条线就不再是「进 Top-k 的准入线」了,而是「进 Top-(k+4) 的线」,反解出来的偏置对应的目标就不是 q 了。反过来问:Top-k 本身(取第 k 个当门槛)行不行?)
答辩:如果我是审稿人
你反复强调 QB「不需要学习率」,把这当成相对定步长方法 γ 的主要卖点。可你的直方图估计引入了箱数 B、箱的边界范围、以及「多久更新一次偏置」这些选择;正文里 B 的具体取值一个字都没写。这不就是把一个超参数换成了三个吗?请辩护。
参考防守(先自己组织语言再看)
防守要分清「超参数」的两种性质。
γ 是一个控制回路的增益:它决定系统的动态行为,调错的后果是定性的——太小则负载长期偏离目标,太大则震荡甚至不收敛。而且它的最优值随专家数 n、批次大小、训练阶段而变,论文正是因为 n 涨到 896 让这个权衡变难才要换掉它。
箱数 B 是一个估计精度参数:它决定的是「我算出来的分位数离真值有多远」,误差被箱宽单调地控制住,论文说误差界在附录 D 给出。B 调小一点,结果是偏置略微不准,负载略微偏离 q;不会出现「震荡发散」这种定性失效。关键区别:γ 错了,方法就坏了;B 小了,方法只是精度差一点。论文说几百个箱就够,说明这个精度很容易买到。
更根本的一层:QB 是在解方程——「让计数等于 q」这个方程的解不依赖于任何步长;定步长法是在朝解走,走多快必然是一个自由参数。前者的超参数只影响「解得多准」,后者的超参数影响「走不走得到」。
但必须承认的两点:(1)论文正文确实没给 B 的取值、箱边界如何确定、偏置更新频率,也没给任何关于 B 的消融,这个防守只能靠附录 D 的误差界撑着。(2)如果间隔的分布高度集中(比如全挤在一个箱里),箱宽带来的偏差就不再是小量——论文没有讨论这种病态情形。这是一个诚实的开放问题。
答辩:如果我是审稿人
QB 强行让每个专家拿到恰好 q = mk/n 个 token。可数据本身并不均匀——如果某个批次里 90% 是代码,难道不应该让擅长代码的专家多干活吗?你这是为了系统效率在牺牲专家的专业化。而且 QB 比定步长法「更硬」(每步直接解到目标,而不是挪一小步),这个牺牲反而更极端。请辩护。
参考防守(先自己组织语言再看)
三层防守。
第一层:偏置不进混合权重。这是式 13 最关键的设计。b 只改「谁被叫上场」,不改「上场之后各占几成话语权」,也不进入路由器的梯度。所以路由器仍然完全按语言建模损失去学「代码 token 应该配什么样的专家」这件事——被均衡改变的只是名单的边缘部分(那些分数接近门槛的专家),分数遥遥领先的专家该被选还是被选。专业化的核心信号并没有被动过。
第二层:均衡的动机不是审美。专家并行下每步的耗时由最忙的卡决定,一个 10 倍不均衡意味着大部分卡在空转;长期分不到 token 的专家收不到梯度,最后是一堆随机初始化的死参数占着显存。这两条论文都明说了。不均衡不是「让模型更专业」,而是「让 896 个专家里的一部分白买了」。
第三层:目标负载是对全局批次算的。q 定义在整个全局批次上(论文说直方图代表的是「token 怎么分片都一样」的全局池化批次),百万级 token 的混合分布比单个 micro-batch 稳定得多。「90% 是代码」这种极端情形在全局批次尺度上被稀释了。
但要承认:这确实是一个真实的取舍,而论文没有给出任何关于「QB 是否损害了专家专业化」的实验证据——正文只给了「不均衡拖慢训练、可能训练不足」这一侧的定性理由,另一侧的代价完全没有测量。审稿人这一刀扎在了论文的软处。另外,「QB 更硬所以牺牲更大」这个观察也很难反驳:定步长法至少在一步之内只挪 γ,QB 一步就解到目标。论文对此没有讨论。
5.7 直方图估计:真实规模下这个分位数根本收不齐
表 5-4 那个 8×4 的例子里,求分位数就是「把 8 个数排个序,取第 3 大」。真实训练里做不到。
论文把困难说得很清楚:式 14 里的分位数是对整个全局批次算的,这些间隔的数量以百万计,而且分散在各个 rank 和各个梯度累积步上——同一个专家的间隔,一部分在第 3 号卡上、一部分在第 47 号卡上、一部分要等到这一步的第 8 次累积才产生。要精确求分位数,就得把它们全都收集到一处再排序。论文的判断是:训练时这不可行。
为什么「排序」在分布式训练里这么贵
求分位数需要全局排序,而排序不能分而治之地合并——你不能让每张卡各自排好序然后简单拼一下就完事,必须真的把数据搬到一起。上百万个数 × 896 个专家 × 每一层,光是网络传输就足以把训练拖垮,而这只是为了算几个用来调度的偏置。
直方图(histogram):把数值范围切成若干个箱(bin),只记录「落在每个箱里的数有多少个」,不保留每个数具体是多少。比如把 [−1, 1] 切成 200 个箱,每个箱宽 0.01,那么 100 万个数就被压缩成 200 个计数。
QB 的做法是:给每个专家维护一个它自己的间隔直方图。每张卡各自统计自己那部分 token 产生的箱计数,然后一次 all-reduce 把各 rank 的箱计数加起来,再从汇总后的计数里把分位数恢复出来(从最大的箱往下累加,加到超过目标个数为止,那个箱的位置就是分位数)。
为什么「可加」是这里的全部关键
论文的原话值得逐句拆:「Because counts are additive, the histogram represents the pooled global batch regardless of how tokens are sharded」——因为计数是可加的,所以直方图代表的是「不管 token 怎么分片都一样」的全局池化批次。
意思是:第 3 号卡说「[0.05, 0.06) 这个箱里有 120 个」,第 47 号卡说「有 85 个」,加起来就是 205 个,和把这些 token 放在同一张卡上统计出来的结果一模一样。分片方式、梯度累积的次数、卡的数量,全都不影响最终的箱计数。所以这个估计不是「近似了全局批次」,它在计数这个层面上就是全局批次。论文说估计的是全批次分位数,精确到箱宽(up to the bin width)——唯一的误差来源是「箱内的数具体在哪」这个信息被丢掉了,而这个误差被箱宽严格框住。
通信代价:论文说只有每专家几百个箱。对比一下要传的原始数据——每专家上百万个间隔。压缩了三四个数量级。
论文明确说:这个直方图估计器就是实践中真正使用的方法(「This histogram estimator is the method we use in practice」)——式 14 里那个精确的 quantile 是理想化的写法。更详细的描述和它的误差界,论文放在附录 D,见第14章。
有人提出一个更省事的方案:「让每张卡用自己手上的间隔各算一个分位数,然后 all-reduce 求平均,得到全局分位数。」这样传的数据更少(每专家只传 1 个数,而不是几百个箱计数)。请构造一个具体的反例说明这个方案是错的,并指出它错在哪个数学性质上。
反例:卡 A = {0, 0, 0},中位数 0;卡 B = {10, 20, 30},中位数 20。两者平均 = 10。
真实情况:合并后 {0, 0, 0, 10, 20, 30},排序取中间(第 3、4 个的平均)= (0+10)/2 = 5。5 ≠ 10,差了一倍。
更极端的反例:卡 A = {0, 0, 0, 0, 0, 0, 0, 0, 0, 100},中位数 0;卡 B = {0, 0, 0, 0, 0, 0, 0, 0, 0, 100},中位数 0。平均 = 0,这次恰好对了。现在把卡 B 换成 {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},中位数 1,平均 = 0.5;真实合并后 20 个数的中位数 = 0.5——又对了。但这只是因为两卡数据量相同且分布对称,纯属巧合。只要两张卡的数据量不同(真实训练里各 rank 的 token 数、以及某个专家在各 rank 上的间隔数,完全可能不同),甚至连「加权平均」都救不回来。
错在哪个性质上:分位数是一个顺序统计量,它由「有多少个数小于某个值」决定,而不是由数值的算术运算决定。可加的是计数,不是分位数。所以正确的合并方式必须先把可加的量(箱计数)加起来,再从合并后的计数里恢复分位数——这正是论文的做法。论文那句「Because counts are additive」不是一句修辞,它精确地指出了唯一能被安全 all-reduce 的东西是什么。
顺带一提:如果 all-reduce 传的是「原始间隔的和」或者「平方和」,那是可加的,可以得到全局的均值和方差——但均值和方差不能反推出分位数,除非你假设分布是某个已知形状(比如高斯)。直方图的好处正是不需要任何分布假设。
变式:反过来问——既然计数可加这么好用,为什么不干脆把箱数开到 100 万、让直方图变成「精确的」?(提示:算一算通信量。896 个专家 × 100 万个箱 × 每层 × 每步,是多少个数?再对比论文说的「几百个箱」。另外想想:箱开得太细,每个箱里只有 0 或 1 个数,直方图就退化成了原始数据本身。)
读的时候要小心
关于直方图估计,论文正文没有给出:箱数 B 的具体取值、箱边界如何确定(是固定范围还是自适应的?)、偏置多久更新一次(每步还是每若干步?)、以及箱宽带来的误差在实际训练中有多大。论文只说「几百个箱」和「误差界见附录 D」。复现这一部分的人会在这里遇到实打实的空白。
本章小结
这一章讲的是同一件事的三个面:怎么把宽度做稀疏,而不让它炸掉。
做稀疏靠 LatentMoE:把「整个模型多宽」(d = 7168)和「路由专家多宽」(ℓ = 3584)拆成两个独立的数。共享专家留在全宽通路上做通用变换,896 个路由专家在减半的潜空间里工作。被 dispatch 的是压缩后的 z = W↓x,通信量和专家权重流量都砍半,正好抵消掉「激活专家数从 8 提到 16」带来的翻倍。稀疏度 896/16 = 56。
不炸掉靠三件事,对应论文点名的两个失效模式:
- 失效模式一(路由分支激活爆炸)——路由通路把 W↓、门控多分支专家前馈网络、W↑ 串成近乎四次连续矩阵乘法的链条,叠加 2.8 万亿参数规模。
对策 1:Normalized LatentMoE——在专家聚合和上投影之间插一个 RMSNorm(式 11 第二行),把「选了哪些专家、权重怎么分」引起的逐 token 尺度抖动抹平。论文自述这还持续改善验证损失和下游基准(无数字)。
对策 2:SiTU-GLU(式 12)——SwiGLU 的两个乘性因子都无界,同时出现的大坐标会产生激活离群值、抬高低精度溢出风险。用 softcap(x, β) = β tanh(x/β) 分别封住 Swish 门的线性因子(β₁ = 4)和上分支(β₂ = 25)。原点附近近似线性所以行为几乎等同 SwiGLU,大幅值处有界所以 |f| ≤ β1β2 = 100。 - 失效模式二(近 10³ 个专家的负载均衡)——超出了定步长符号更新还能良好工作的范围。
对策 3:Quantile Balancing(式 13、式 14)——免辅助损失路由给每个专家配偏置 bj,只进 Top-k 选择、不进混合权重 p、不进路由器的梯度。QB 不再「朝目标挪 γ」,而是直接解出偏置:用 Top-(k+1) 白捡每个 token 的准入门槛 αi,把「专家 j 收到几个 token」写成「有多少个间隔 si,j − αi 超过阈值 −b̂j」,利用单调性反解出 b̂j = 第 (q+1) 大间隔的相反数 = 间隔的 (1−k/n) 分位数的相反数。再减去公共偏移(不改 Top-k),下一步才生效(因果性),推理时冻结。真实规模下分位数用可加的直方图 + 一次 all-reduce 估计,代价每专家几百个箱。
本章标注为存疑或论文未说明的地方
- 「近乎四次连续矩阵乘法」没有点名是哪四个矩阵;「病态结构」(ill-conditioned)没有任何定量证据(无条件数、无激活范数曲线、无失败训练曲线)。
- 额外 RMSNorm「持续改善验证损失和下游基准」是论文自述,正文没有给任何消融数字;也没说明它是否带可学习增益、共享分支是否也做类似处理。
- β₁ = 4、β₂ = 25 只是给出取值,没有消融、没有解释为什么两者差六倍多。
- SiTU-GLU 与硬截断的比较、局部展开、形式化的输出界,正文全部推给附录 B。
- 「896 个专家超出现有免辅助损失偏置更新的良好工作范围」是断言,没有支持数据。
- 正文的 QB 推导只证明了「门槛固定时每个专家恰好有 q 个 token 越过门槛」,而真实路由是逐 token 取 Top-k,两者不等价(q5-11 里我们构造了一个 (4,0) → (0,4) 的反例)。真正的 balanced-assignment 推导被推给附录 C,正文没有说明取等的条件。
- 推导假设「无并列」(no ties),也没有讨论 q = mk/n 不是整数时怎么办。
- 直方图的箱数 B、箱边界如何确定、偏置更新频率,正文一概未给;误差界推给附录 D。
- 「QB 是否损害了专家专业化」这一侧的代价,论文完全没有测量。
- LatentMoE 相对常规 MoE 到底省了多少通信量,论文没给数字(本章的 0.5× 是我们用 Table 1 的 ℓ/d 推的);ℓ 取 0.5d 也没有消融。
- Table 1 的「每专家 MoE 隐藏维 3072」没有区分是路由专家的还是也包括共享专家。
第6章 原生视觉与 Per-Head Muon
一句话导语:这一章回答两个问题——为什么 K3 敢把视觉编码器从零开始训(而不是像几乎所有人那样拿一个 SigLIP 来打底),以及为什么要把优化器的正交化操作细到「每个注意力头单独做一次」。
学完这一章你应该能做到
- 用自己的话说清「原生多模态」和「给语言模型接一个图像模型」在信息流上到底差在哪
- 复述论文放弃 SigLIP 初始化的主要理由,并指出这个结论被作者自己加了什么限定词
- 算出一张 3584×3584 的图片,在 patch size 14、pixel-shuffle 2×2 之后会变成多少个 token
- 解释「整矩阵正交化」和「逐头正交化」会导致不同头的更新尺度出现什么差别
- 指出论文在这两节里哪些地方只给了直觉、没给证据
6.1 原生多模态:让「看」和「写」活在同一条 token 流里
先说不用它会怎样。今天绝大多数「能看图的大模型」是这么拼出来的:先有一个只会读文字的语言模型,训好了;再有一个只会看图的视觉模型,也训好了;然后加一个小小的转接头,再花一个专门的阶段把两边「对齐」,让视觉模型输出的东西能被语言模型读懂。这个事后补的阶段,论文叫事后模态对齐(post-hoc modality-alignment stage)。
原生多模态(natively multimodal):论文对 K3 的原话是,文本、图像和视频由同一个共享骨干在同一个上下文里处理,没有事后的模态对齐阶段。
差别听上去只是流程顺序,实际影响的是信息的宽度。想象模型正在做一件很典型的事:写一段网页代码,把它渲染出来,看看效果,再回去改代码。在拼接式的方案里,「看」这一步是由另一个模型完成的,它必须把看到的东西压缩成一段文字描述,再交回给语言模型——按钮偏了三个像素、某个渐变的方向反了、第七帧的动画抖了一下,这些细节能不能活着穿过那段文字描述,全看运气。论文把这种交接叫跨模型交接(cross-model hand-off),而 K3 的设计目标就是彻底没有这一步。
在 K3 里,渲染出来的结果和产生它的代码活在同一个 token 流里。模型写代码、检查截图或视频帧、再迭代改视觉产物(用户界面、图形、视频),全程不换模型。论文明确说,这就是第 1 节里那种「长时程、视觉在环」(long-horizon, vision-in-the-loop)行为的架构基础。
一个很容易混淆的地方
很多人以为「原生多模态」就是「输入能塞图片」。其实能塞图片的模型遍地都是,拼接式方案也能塞。真正的分界线在于:视觉信息进入模型之后,是和文字 token 平起平坐地留在同一段上下文里、被同一套参数处理,还是先被翻译成文字再进来。K3 属于前者,而且论文强调这套共享骨干是从训练第一天起就同时优化语言和视觉的(这一点在第7章的训练配方里会再出现)。
下面两个系统,哪一个符合论文说的「原生多模态」?为什么?
甲:一个训好的语言模型 + 一个训好的图像编码器,中间加转接层,再用一批图文配对数据专门训练一个阶段让两者对齐。
乙:训练一开始就把图像切成的 token 和文字 token 混在同一段序列里,用同一个下一 token 预测目标一起训。
变式:如果甲在对齐阶段之后又把整个系统端到端微调了很久,它算原生多模态吗?(提示:想想「表示是被谁塑造的」,以及先定型的参数会不会留下痕迹——论文没有讨论这种中间情况,这是可以自己论证的开放题。)
6.2 MoonViT-V2:把视觉编码器完全从零训起来
这是 K3 相对 Kimi K2.5 的一个关键改变,论文用斜体强调了它:K3 的视觉编码器 MoonViT-V2 是完全从零、用下一 token 预测训练的。
下一 token 预测(next-token prediction, NTP):就是语言模型那个最朴素的训练目标——给你前面的内容,猜下一个 token 是什么。这里的新意是,连视觉编码器的参数也由这个目标来训。
此前大家为什么不这么做
过去的常规做法(包括 Kimi K2.5 自己)是用 SigLIP 这类对比预训练(contrastive pre-training)模型来初始化视觉编码器。对比预训练的思路大致是:给模型一堆「图片—说明文字」的配对,让它把配对的拉近、不配对的推远。这样练出来的编码器已经懂很多视觉概念,所以大家相信「预训练的视觉知识能给模型开个头」,省时间也省数据。K3 把这个前提整个丢掉了。
论文给的主要理由是训练稳定性。把一个预训练好的编码器接到大语言模型上做联合优化,会不稳:SigLIP 初始化的 MoonViT-3D 表现出持续更高的梯度范数,而且频繁尖峰;而从零训练的 MoonViT-V2 全程稳定。这就是 Figure 6 那张图在说的事——横轴是训练步数(7k 到 30k),纵轴是视觉塔的梯度范数,蓝线(SigLIP 初始化)整体更高、尖刺更多,红线(从零)压得又低又平;右边还放大了 14k–16k 这一小段,让你看清即使在「看起来都还好」的区间里,蓝线的抖动也明显更大。
梯度范数(gradient norm):把这一步所有梯度当成一个超长向量,量一下它的长度。它大致告诉你「模型这一步想改多狠」。持续偏高说明训练一直在被大幅拉扯,突然的尖峰则往往是灾难性更新的前兆——真炸起来会让损失曲线飞掉,几千步的算力就白烧了。在千亿、万亿参数这个尺度上,一次崩溃的代价高到没人想赌。
还有第二个理由,关于表示被谁塑造。用 NTP 训练,编码器的表示是直接被语言建模目标塑造的;而对比损失塑造出来的表示,论文的说法是偏好全局语义、忽略细粒度的文字和结构线索。用大白话讲:对比预训练只要能把「这是一张猫的照片」和文字对上就得分了,它没有动力去精确记住猫身上第几根胡须、界面上按钮里那行小字写的什么、表格的第三列和第四列是怎么对齐的。而 K3 要干的活恰恰是看截图改代码、读 CAD 图纸、认 OCR,全是细粒度文字和结构。
论文的结论是:MoonViT-V2 在视觉评测上追平了 SigLIP 初始化的基线,说明对比预训练作为多模态语言模型的初始化不是必需的。
读的时候要小心这几个字
原文的措辞是 「matches the SigLIP-initialized baseline」——是追平,不是超过。也就是说从零训练在指标上并没有赚,赚的是稳定性和表示的性质。而且作者自己在结论后面加了限定:at scale(在这个规模上)。这句话反过来读就是:在小规模上,对比预训练的初始化很可能仍然是有用的,甚至是必需的。此外,「追平」是论文自述的评测结论,这几页没有给出具体的评测表或分数,读者无法核对是在哪些评测集上追平的。
打个比方
SigLIP 初始化像是雇一个已经在别的公司干了十年的老员工:上手快,但他带着一套自己的工作习惯,你的流程要迁就他,磨合期可能很难受。从零训练像是招应届生:前期慢,但他的所有习惯都是照你的业务长出来的。
类比失效处:真实的老员工不会让公司「崩溃」,而神经网络训练是真的会因为不稳定而彻底失败;另外这个类比会让人以为从零训练一定更慢更差,但论文的结果是追平,并没有付出指标上的代价。
Figure 6 是「视觉塔的梯度范数 vs 训练步数」,两条曲线。有人看完说:「红线一直比蓝线低,说明从零训练的模型学得少、更新得不够,所以效果肯定更差。」这个推理错在哪?
变式:如果反过来,红线(从零)的梯度范数明显更高,但最终评测分数一样,你会怎么解读?这还能不能支持「从零训练更稳」这个论点?
论文说「对比预训练作为多模态语言模型的初始化不是必需的(at scale)」。请你构造一个具体场景,在那个场景里这个结论会失效——也就是不用 SigLIP 初始化就明显吃亏。说清楚你改动了哪个前提。
变式:换个方向想——有没有可能在更大的规模上,SigLIP 初始化的劣势会更严重?给一个理由。(提示:不稳定性和模型规模的关系,以及论文说 Per-Head Muon 「在更大规模上改善训练稳定性」这句话透露的信息。)
答辩:如果我是审稿人
你用一张梯度范数曲线(Figure 6)当作放弃 SigLIP 初始化的主要证据。可梯度范数高本身并不是失败——只要没炸、最终损失更低就行。你有没有展示 SigLIP 初始化那条路真的训崩过?还是说你只是因为看着心慌就换了方案?
参考防守(先自己组织语言再看)
可以承认这一击打中了要害:论文确实没有在这两页展示任何一次真实的训练崩溃,也没有给出两条路的最终损失对比,Figure 6 是消融实验里的过程指标。合理的防守有三层。第一层是风险论证:在万亿参数、上下文最长 100 万 token 的训练里,一次不可恢复的发散意味着巨大的算力损失,工程上会主动避开「看起来会炸」的配置,即使还没炸——这是成本不对称下的理性选择,不是心慌。第二层是结果背书:既然从零训练追平了基线,那么为了稳定性放弃初始化就是零代价的选择,此时不需要再证明另一条路一定会崩,只需要证明它没有额外收益。第三层是机制论证:NTP 训练让表示被语言建模目标直接塑造,这条理由和稳不稳定无关,是独立成立的。至于审稿人的质疑本身,最诚实的回应是——论文没有给出「SigLIP 初始化在同等预算下最终损失是多少」这个关键数字,这确实是一个空缺。
6.3 MoonViT-V2 的架构:27 层、无偏置、时空分解、pixel-shuffle
整条视觉通路很短,一句话就能说完:图像先由 MoonViT-V2 编码,再由一个轻量 MLP 投影器(lightweight MLP projector)映射进大语言模型。这个整体设计沿用了 Kimi K2.5 的视觉通路。
MoonViT-V2 本身是一个 27 层的视觉 Transformer,大约 0.4B 参数(第7章的 Table 1 里给的精确数字是 401M)。0.4B 是 4 亿个数——听着不小,但把它放在 K3 总共 2.78 万亿参数旁边,占比大约万分之一点四,视觉编码器在整个模型里其实是很薄的一层皮。它采用 RMSNorm,并且去掉了线性投影和注意力投影里的所有偏置项。论文明说,这个设计是为了进一步稳定从零训练——注意这和上一节是同一条主线:既然选择了从零训练这条更难走的路,架构上就要处处减少可能出问题的自由度。
图像和视频用完全共享的参数处理,做法沿用 MoonViT-3D:注意力被分解(factorized)成两趟,一趟是帧内空间注意力(同一帧里的各个小块互相看),一趟是帧间时间注意力(不同帧的同一位置互相看);此外还有时间池化沿时间维进一步压缩 token。为什么要分解?因为如果让所有帧的所有小块两两互看,代价随 token 数平方增长,视频根本算不动;拆成两趟之后,一段视频的注意力开销就被压下来了。
补充:什么是「完全共享参数」
意思是处理静态图像和处理视频帧用的是同一套权重,不存在「图像分支」和「视频分支」两套参数。一张静态图片可以看成只有一帧的视频,时间那一趟注意力退化成什么也不用比。这样做省参数,也让图像上学到的能力天然迁移到视频上。
pixel-shuffle:把 token 数砍到四分之一
投影进语言模型之前,还有一步 pixel-shuffle,做 2×2 下采样,把视觉 token 的数量减少到四分之一。论文给这一步的理由非常直接:这样才能让高达 3584×3584 像素的输入,在 100 万 token 的上下文里仍然负担得起。
这里值得停下来自己算一算,因为算完你才会对「高分辨率有多贵」有真实的感觉。视觉 Transformer 处理图片的第一步是把图片切成边长为 P 的小方块,每个小方块叫一个 patch,之后每个 patch 就是一个 token。K3 的 patch size 是 14(Table 1)。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| H, W | 输入图片的高和宽,单位是像素 | 论文给的上限是 3584×3584 |
| P | patch 的边长,K3 取 14(Table 1 的 ViT patch size) | 切图的「刀距」,越小切得越细、token 越多 |
| H/P, W/P | 横竖各能切出多少个 patch | 3584 ÷ 14 = 256,所以是 256×256 的格子 |
| 1/4 | pixel-shuffle 2×2 下采样带来的压缩比 | 每 2×2 共 4 个相邻 patch 合成 1 个 token |
| Ntoken | 最终进入语言模型上下文的视觉 token 数 | 要和 100 万的总预算比大小的那个数 |
代进去:3584 ÷ 14 = 256,256 × 256 = 65536 个 patch;再除以 4,得到 16384 个 token。也就是说,一张顶格分辨率的图,占掉 100 万 token 上下文的大约 1.6%。如果没有 pixel-shuffle,同一张图要吃掉 65536 个 token,约 6.6%——放十几张图就把上下文塞满了。四倍的差别,决定的是「能不能在一次对话里翻十几屏截图」。
论文没写的一点
pixel-shuffle 的标准做法是把空间上相邻的格子搬进特征维度,所以 token 变少、每个 token 变「厚」,信息并不是被扔掉了,而是被重新打包了。但论文在这两页只写了「token 数减少到四分之一」,没有说明投影器前后的具体维度,也没说明是否伴随维度扩张。上图右侧「每个 token 更厚」是按 pixel-shuffle 的通用定义画的,不是论文的原始陈述。
一张 1792×896 的截图,patch size 14,经过 2×2 pixel-shuffle 之后会变成多少个视觉 token?占 100 万 token 上下文的百分之几?
变式:如果把 pixel-shuffle 改成 4×4 下采样,同一张 3584×3584 的图会变成多少 token?这么做的代价可能是什么?(提示:想想 OCR 要认清小字需要多细的空间粒度。)
MoonViT-V2 去掉了线性投影和注意力投影里的所有偏置项,论文说这「进一步稳定了从零优化」。请解释:为什么「从零训练」这个决定会让作者更倾向于做这种减法?如果他们还是用 SigLIP 初始化,这个改动的必要性会变高还是变低?
变式:注意力被分解成「帧内空间」和「帧间时间」两趟,而不是让所有帧的所有 patch 两两互看。假设一段视频有 F 帧、每帧 N 个 patch,请分别写出两种做法的注意力对数(成对比较的次数)随 F 和 N 增长的形式,看看差多少。
6.4 Per-Head Muon:让每个注意力头拿到「同一量级」的更新
先补两个零基础的前置概念,不然这一节没法读。
优化器(optimizer):模型里有一大堆数(参数)。训练时,反向传播会告诉你「每个数往哪个方向挪一点点能让预测更准」,这个方向叫梯度。但梯度只给方向和一个原始大小,具体每个数该挪多少是优化器决定的。优化器就是那个把原始梯度加工成实际更新量的规则。加工得好,训练又快又稳;加工得差,要么原地打转,要么直接发散。
正交化(orthogonalization):一个矩阵可以看成一台「把输入向量拉伸再旋转」的机器。麻烦在于,它对不同方向的拉伸倍数往往差得离谱——某个方向拉 100 倍,另一个方向只拉 0.01 倍。正交化就是把所有方向的拉伸倍数统统拉平到差不多相同,只保留「转向」的部分。用在优化器上,效果是:更新的方向结构留着,但各个方向的力度被拉齐,不会出现某一两个方向独吞了整步更新的情况。
Newton–Schulz 迭代:一种只用几次矩阵乘法就能近似把矩阵正交化的方法,好处是不用做昂贵的矩阵分解,在 GPU 上跑得动。这是 Muon 优化器里真正干活的那一步。
打个比方
正交化像是给一支合唱团做音量均衡:每个人唱的音(方向)不动,但把嗓门大的压下来、嗓门小的抬上去,让整体听起来是一个和声,而不是一个人在吼、其他人在陪衬。
类比失效处:合唱团的「人」是离散的个体,而矩阵的「方向」是连续的、彼此正交的抽象方向,不能一一对应到具体的某个参数;另外正交化是对整个矩阵的所有奇异值一起做的,不是逐个元素做归一化。
回到正题。K3 沿用 K2 的做法,对矩阵参数用 Muon 优化器。K3 的新东西是:对注意力投影再细化成一个「逐头」的变体。
原来的做法是把整个 Q、K、V 投影矩阵拿来做 Newton–Schulz 正交化。Per-Head Muon 改成:把它们的动量矩阵沿头维切分,对每个头的块单独正交化。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| M | 某个投影矩阵(Q 或 K 或 V)对应的动量矩阵 | 「最近几步梯度的加权累积」,比单步梯度平滑 |
| Mi | 把 M 沿头维切开后,第 i 个头对应的那一块 | 只属于第 i 个注意力头的那部分更新信息 |
| h | 注意力头的个数 | K3 是 96 个(Table 1) |
| NS(·) | Newton–Schulz 正交化操作 | 把这一块的各方向力度拉平 |
| [ … | … ] | 把各块按原来的位置拼回去 | 形状不变,只是「分区域施工」 |
补充:这个式子的来源
论文这一节没有给编号公式,只有文字描述(「partition their momentum matrices along the head dimension and orthogonalize each head's block separately」)。上面的式子是按这段文字写出来的示意,Muon 的完整定义在论文引用的文献里,本报告没有展开。
论文给的直觉写得很清楚,值得逐句拆:整矩阵正交化把所有头当成单个耦合的块。于是,梯度或动量尺度大的头会主导那个共享的更新方向,而尺度小的头得到的更新归一化不足。逐头正交化则让各头的更新尺度拉齐。
为什么会这样?因为正交化是「看整个矩阵的整体结构,然后拉平」。如果 96 个头挤在同一个矩阵里一起被拉平,那么这个「整体结构」主要是由数值大的那几个头贡献的——正交化拉平的是整体的方向力度,不是每个头内部的方向力度。结果就是:大头把整步更新的方向绑架了,小头即使被拉平了也还是相对很小,等于没有被真正归一化。切开之后,每一块各自拉平,96 个头就各自拿到了「力度相当」的更新。
论文说这样做在实践上带来三个好处:更均衡的跨头学习动态、在更大规模上改善训练稳定性、以及略微降低优化器开销——最后这一条的理由是,对细高的逐头块做 Newton–Schulz 比对整个投影矩阵便宜。
把这一章的几件事串起来回答:K3 的注意力头从 K2 的 64 个增加到 96 个(Table 1),同时论文说 Per-Head Muon 「在更大规模上改善训练稳定性」,又说 MoonViT-V2 从零训练的主要动机也是稳定性。请论证:为什么「头变多」这件事会让整矩阵正交化的问题变得更严重?并说明这三件事共同指向的是一个什么样的工程判断。
变式:论文说逐头正交化「略微降低优化器开销,因为对细高的逐头块做 Newton–Schulz 比对整个投影矩阵便宜」。请你只用「矩阵乘法的代价和矩阵形状有关」这一条常识,说明为什么切成若干细高块之后总代价会下降。再问一句:既然更省,为什么论文只说「略微」?(提示:优化器那一步在整个训练的算力里占多大比重。)
答辩:如果我是审稿人
Per-Head Muon 你只给了一段直觉,说大头会主导共享的更新方向。可是「整矩阵一起正交化」本来就是 Muon 设计里的一个特性——它就是要在整个矩阵的层面上做谱意义上的均衡。你凭什么认为按头切开还算是在做同一件事?切开之后各块之间的相对尺度信息被丢掉了,万一那个信息是有用的呢?
参考防守(先自己组织语言再看)
先承认前半句是对的:切开确实丢掉了跨头的相对尺度信息,这是一个真实的代价,论文也没有论证那个信息无用。防守的落点在「注意力头本来就是并列的独立子空间」这个结构事实上:多头注意力的设计初衷就是让每个头去看不同的关系模式,头与头之间在功能上是并列的,不是一个整体表示的不同坐标轴;既然投影矩阵在语义上本来就是 h 个独立的小投影拼在一起,那么把归一化的粒度对齐到这个语义边界,比对齐到「拼接后的矩阵」这个人为边界更自然。换句话说,需要被辩护的其实是整矩阵那一侧——它把 h 个本无耦合关系的块强行当成一个谱结构来均衡。至于「相对尺度信息有没有用」,可以承认这是一个开放问题:如果某些头本来就应该学得更快,逐头正交化会抹掉这个差异。论文没有回答它,只报告了实践上更均衡、更稳定。真正的软肋在于论文没有给出任何消融——没有「整矩阵 vs 逐头」的损失曲线对比,读者只能接受作者的定性陈述。
本章小结
这一章其实只讲了一个判断:在 K3 这个规模上,训练稳定性比任何单点收益都值钱。三个看起来无关的设计都是这个判断的产物。
原生多模态解决的是信息通路问题——文本、图像、视频由同一个共享骨干在同一个上下文里处理,没有事后对齐阶段,渲染结果和产生它的代码活在同一条 token 流里,所以「写代码—看效果—改代码」这个循环全程不用换模型。MoonViT-V2 从零用下一 token 预测训练,放弃了 SigLIP 初始化,主要理由是联合优化时预训练编码器会让梯度范数持续偏高并频繁尖峰(Figure 6),次要理由是让表示被语言建模目标而不是偏好全局语义的对比损失塑造;代价是没有性能红利,结论是追平,而且作者自己加了「在这个规模上」的限定。架构上是 27 层、401M 参数、RMSNorm、无偏置,图像视频共享参数、注意力分解成空间和时间两趟,投影前 2×2 pixel-shuffle 把 token 砍到四分之一,让 3584×3584 的输入只占 16384 个 token。Per-Head Muon 把 Newton–Schulz 正交化的粒度从整个 Q/K/V 投影矩阵细化到每个头的块,避免大尺度的头绑架共享的更新方向。
需要记住的空缺:论文在这两页没有给出 MoonViT-V2 与 SigLIP 基线的具体评测数字,没有给 Per-Head Muon 的消融,也没有说「更大规模」的界线在哪。这些都是作者的主张而非被展示的证据。
第7章 预训练:数据、缩放定律、配方与长上下文
一句话导语:这一章回答「K3 到底是怎么被喂大的」——喂什么数据、怎么算出该用多大的学习率和多少 token、从 K2 到 K3 具体改了哪些数字,以及一个 100 万 token 的上下文窗口是怎么一步步撑开的。
学完这一章你应该能做到
- 说清「2.5× 缩放效率增益」到底省下了什么,并在给定算力时算出具体数值
- 复述作者比较余弦衰减和 WSD 时坚持的那条方法论原则,并把它迁移到别的对比实验上
- 读懂 Table 1,指出 K3 相对 K2 的哪些维度变了、哪些刻意没变,并做一致性校验
- 解释为什么「把长文档上采样」不足以让模型获得长程能力,以及论文用什么补上这个缺口
- 说清 NoPE 和 KDA 的关系,以及「直接外推到 1M」这句话的准确含义
7.1 预训练数据:四个文本域,加一整套视觉语料
K3 的预训练语料由四个主要文本域构成——网页文本、代码、数学、知识——外加一份大规模视觉语料。视觉那一份覆盖得很杂:图注、图文交错的文档、OCR、感知、视频,以及视觉编码数据(visual coding data)。整条数据流水线是在 Kimi K2 上建起来、在 K2.5 上精化过的。
文本数据:过滤、配比、改写
每个域都要经过三道处理的组合:规则启发式(rule-based heuristics)过滤、基于分类器的质量打分(classifier-based quality scoring)、以及去重(deduplication)。而每个域最终占多大比例——论文叫域采样率(domain-specific sampling rates)——不是拍脑袋定的,而是由小模型上的消融实验决定。这是一个值得注意的工程习惯:真正的大模型只训一次,训错了没有回头路,所以配方要先在便宜的小模型上试出来。
还有一件更有意思的事:K3 沿用了 K2 的改写配方(rephrasing recipe),对知识和数学两个域的语料做改写。做法有三个要点——用风格与视角多样的提示来改写、分块自回归生成、以及对照源文档做保真度验证。
为什么要改写语料,不改会怎样
同一个知识点在互联网上往往只有一两种写法。模型见到的表述越单一,它越容易把「这个知识」和「这种说法」绑死,换个问法就答不上来。改写就是人为制造多样性:同一段内容用不同风格、不同视角再讲一遍,让模型学到的是内容本身而不是措辞。但改写有个致命风险——模型改着改着就把事实改错了,尤其是数学。所以第三步「对照源文档做保真度验证」不是可有可无的装饰,它是这套做法能不能用的前提。至于验证具体怎么做、通过率多少,论文这一节没有说明。
视觉数据:两套坐标,和「代码配渲染图」
视觉语料沿用 K2.5 的分类体系,把开源数据集和自研的过滤、合成、去重流水线结合起来。有两个细节论文特意点了出来。
第一,训练时坐标监督同时给两种格式:绝对坐标和归一化到 [0,1] 的坐标,目的是实现精确且分辨率鲁棒的定位。这里的「定位」指的是让模型说出「那个按钮在图上的哪个位置」。绝对坐标(比如「第 640 像素」)精确,但换一个分辨率就全错;归一化坐标(比如「横向 0.35 处」)跨分辨率通用,但要还原成像素得乘回去、会损失精度。两种都教,模型才能既准又不挑分辨率。
第二,论文说大幅扩大了程序化多模态数据(programmatic multimodal data):把代码片段与它渲染出来的视觉结果配对,覆盖 SVG、3D 资产、网页、游戏、CAD 图纸五类格式。回想第6章讲的「视觉在环」——模型要能写代码、看渲染结果、再改代码。那个能力不会凭空出现,它需要海量的「这段代码长这样」的配对样本。这一条数据设计和第6章的架构设计是严丝合缝的一对。
训练时同时给绝对坐标和归一化 [0,1] 坐标两种监督。假设只给其中一种,分别会出什么问题?举一个具体的失败例子。
变式:如果只训归一化坐标,但在推理时把图片统一缩放到一个固定分辨率再送进去,是不是就没问题了?(提示:想想固定分辨率会对超大图上的小字造成什么影响,再对照第6章说的 3584×3584 上限。)
7.2 缩放定律:2.5× 到底省下了什么
架构、数据、训练三方面的改动加在一起,定义了一个新的模型家族。问题是——这些改动同时也改变了最优训练配置。K2 那套调好的超参数,直接搬到 K3 上就未必是最优的了。所以论文专门做了缩放定律(scaling law)研究,去重新调这几个关键超参数:batch size、学习率、每参数 token 数(TPP)、模型形状。
缩放定律(scaling law):在一系列小模型上做实验,测出「算力投入」和「最终损失」之间的规律,拟合出一条曲线,再用这条曲线去预测大模型该怎么配。因为真正的大模型训练贵到只能训一次,所以必须先在便宜的规模上把规律摸出来。
每参数 token 数(tokens-per-parameter ratio, TPP):模型有多少参数,就该喂多少 token?这个比值决定了给定算力下,你应该把钱花在「把模型做大」还是「多喂数据」上。
评测是在留出的分布外验证数据(held-out OOD validation data)上做的——也就是训练时完全没见过、而且分布和训练数据不一样的一批数据。用分布外数据评估是为了防止「模型只是把训练分布背下来了」这种假象。
结论就是那个 2.5×:Figure 7 的曲线显示,这些改进合起来带来约 2.5 倍的整体缩放效率增益。
「2.5× 缩放效率」到底是什么意思
先看 Figure 7 长什么样:横轴是 FLOPs(浮点运算次数,也就是「烧了多少算力」,从 1020 到 1021,是对数刻度),纵轴是验证损失(越低越好)。图上两条拟合出来的虚线,上面那条是 Kimi K2,下面那条是 Kimi K3。K3 那条整体往左下方移了。
那个 2.5× 是画在图中间的一支横向箭头。横向意味着:在同一个验证损失的高度上,量一量两条线在算力上差多远。答案是 2.5 倍。翻译成人话就是——要达到同样的效果,K3 只需要 K2 大约 1/2.5 的算力,也就是 40%,省下 60%。反过来说,同样的算力预算下,K3 能到达 K2 到不了的损失水平。
注意箭头的方向:它是横着的,说的是「省算力」,不是「损失低了 2.5 倍」。损失是个几乎不会大幅变动的数,谁也不会说损失降到 1/2.5。
读这个数字时要小心
论文的原话是 「approximately 2.5×」(约 2.5 倍),而且图上只在一个位置标了这支箭头。论文没有说明这个倍数在整个 FLOPs 区间里是否恒定。如果两条拟合直线严格平行,那么水平间距处处相同,2.5× 就是个常数;如果不平行,这个倍数会随算力大小而变,在图外的更大算力处可能更大也可能更小——而真正的 K3 训练规模显然远超图上标注的 1021。此外这是论文自述的、用自家拟合曲线得到的结论,「2.5×」是把架构、数据、训练三类改动打包之后的合计效果,论文这一节没有拆开说各部分各贡献了多少。
假设 K2 需要 1×1021 FLOPs 才能达到某个验证损失。按论文的 2.5× 缩放效率增益,K3 达到同样的验证损失需要多少 FLOPs?节省的比例是多少?如果反过来,K3 也花满 1×1021,你能不能算出它的损失是多少?
变式:如果某个团队报告「我们的新模型有 10× 缩放效率增益」,但他们的两条曲线明显不平行、而且只在最小的那个算力点上量了倍数,你会怎么质疑这个数字?
7.3 余弦衰减 vs WSD:一堂关于「公平比较」的课
这一小段是整章方法论价值最高的地方,值得慢慢读。
学习率调度(learning rate schedule):学习率是「每一步往梯度方向挪多远」。它通常不是常数,而是随训练进程变化的一条曲线,这条曲线就叫调度。
余弦衰减(cosine decay):学习率从峰值开始,按余弦曲线的形状平滑地一路降到最小值。WSD(Warmup Stable Decay):分三段——先预热升上去,然后长时间保持恒定,最后在末尾一段里快速衰减下来。WSD 的卖点是那段恒定期,因为它意味着你可以在任何时刻分叉出一条衰减支来「结束」训练,不必一开始就决定总步数。
K3 的缩放定律研究一致偏好余弦衰减,所以论文把余弦衰减定为默认调度。但重点不是这个结论,而是他们怎么得出这个结论的。
作者是在固定最小学习率的条件下比较两者的。他们承认,此前已有工作报告 WSD 能匹配甚至超过余弦衰减。那为什么这里结论相反?作者给出的解释是一个观察:两种调度的最优超参数差别很大——即使在同样的模型规模和训练 token 预算下,它们各自的最优峰值学习率和最优 batch size 也大不相同。
顺着这个观察,一个很扎实的推论出来了:用一套共享的超参数去比较两种调度,可能只是因为那套超参数更配合其中一个,从而不公平地偏袒它。于是作者的做法是——对每种调度独立做一次缩放定律搜索,各自找到各自的最优超参;在各自最优的设置下再比,结果是余弦衰减始终取得更低的最终损失。
常见误解
很多人会把这一段读成「论文证明了 WSD 不如余弦衰减」。准确的说法要弱得多:在 K3 团队自己的搜索空间、自己的固定最小学习率设定、自己的模型规模和 token 预算下,各自调优之后余弦衰减的最终损失更低。论文自己也明确提到「此前有工作报告 WSD 能匹配甚至超过余弦衰减」,并没有说那些工作错了——它给出的解释是超参数的错配可能导致不同的结论,而这把刀是双刃的,它同样可以指向 K3 自己的实验。
把这条方法论迁移出去。某篇论文说「我们的新激活函数 B 比常用的 A 好」,实验设置是:同一个模型、同一份数据、同一套超参数(学习率、权重衰减、warmup 比例都照搬 A 的常规配置),只把激活函数从 A 换成 B,结果 B 的损失更低。这个实验有没有问题?如果有,该怎么补?
变式:如果作者说「我们没钱对每种方法都做完整的超参搜索」,你能给出一个成本更低但仍然比「共享一套超参」更公平的折中方案吗?
作者已经对每种调度独立做了缩放定律搜索。请你构造一个具体情形:即使做了独立搜索,这个比较仍然不公平。至少给出两条不同的路径,并指明你利用了论文原文里的哪句话。
路径一:搜索维度不对等。论文说两种调度「最优的峰值学习率和 batch size 差别很大」,读起来搜索空间就是这两维。但 WSD 有一个余弦衰减压根不存在的关键超参——衰减段占总步数的比例(等价地,恒定期有多长)。这个比例对 WSD 的最终损失影响极大。如果它被固定成某个默认值而没有进入搜索,那么「对每种调度独立搜索」就只是在 WSD 的一个切片上搜索,WSD 依然可能没被调到最优。论文这几页没有说明搜索空间到底包含哪些维度,这是一个真实的信息缺口。
路径二:共享的约束偏袒一方。原文明写了「在固定最小学习率下比较」。最小学习率对两种调度的意义并不对等:余弦衰减是平滑地趋近这个下限,而 WSD 是在末段快速砸到这个下限,衰减段的形状和终点强耦合。固定成同一个值,很可能对其中一方更合身。这里「固定」这个动作本身就重演了作者批评的那个错误,只不过换了一个变量。
路径三:外推的方向不同。缩放定律是在小规模上拟合、再外推到目标规模的。两种调度的缩放曲线完全可以有不同的斜率——在实验用的小规模上余弦更好,在真实的训练规模上排序却翻转。论文说余弦「始终」取得更低的最终损失,但这个「始终」覆盖的是他们扫过的规模范围,不必然覆盖 K3 的实际训练规模。
这道题的意义在于:公平比较不是一次到位的操作,它是一个可以无限递归的要求——你消除了一层不公平,下一层立刻浮上来。作者做到的比大多数论文都好,但「完全公平」是达不到的,能做的只是把还固定着的东西如实说出来。
变式:换个角度构造。假设两种调度各自调优后最终损失完全相同,你会推荐用哪一个?(提示:WSD 那段恒定期带来的是什么工程上的自由度,而这个自由度对一个要做「从 8K 到 1M 的四阶段课程」的团队值不值钱。)
答辩:如果我是审稿人
你说架构、数据、训练三类改动「合起来」带来 2.5× 缩放效率增益。可是你同时还换了优化器(Per-Head Muon)、换了负载均衡(QB)、换了注意力(混合 KDA–MLA)、换了激活函数(SiTU-GLU)、还从零训了视觉编码器。这么多变量一起动,2.5× 到底是谁的功劳?你甚至无法排除「其中某一项其实是负贡献、被别的项掩盖了」。
参考防守(先自己组织语言再看)
这一击是成立的,论文在这一节确实没有做逐项拆解,2.5× 是一个打包数字。合理的防守有三层。第一层是限定主张的范围:论文原话是 「these improvements collectively deliver」(这些改进合起来带来),从没声称是某一项的功劳;把打包结果如实报告成打包结果,不算越界。第二层是可行性:完整的因子拆解需要在每个改动上各跑一遍缩放定律,代价是组合爆炸;而且这些改动之间存在真实的耦合——比如混合 KDA–MLA 决定了 NoPE 可行,Per-Head Muon 的收益又依赖于头数变多,拆开单测得到的数字未必能加回去。第三层是论文自己确实做了部分局部证据:Figure 6 就是视觉编码器那一项的独立消融。真正无法防守的是审稿人的最后半句——「某一项是负贡献但被掩盖」确实排除不掉,能说的只是「合起来是正的」。诚实的回应是承认这个空缺,并指出这在前沿模型报告里是常态:一次完整训练太贵,全因子消融在这个规模上没人做得起。读者该做的是把 2.5× 当成一个系统级的数字,而不是任何单项技术的背书。
7.4 Table 1:从 K2 到 K3,究竟改了哪些数字
这张表是整篇报告里信息密度最高的一页,值得逐行看。
| 项 | Kimi K2 | Kimi K3 | Δ |
|---|---|---|---|
| 架构 | MoE | MoE | — |
| 层数 | 61 | 93 | ↑52% |
| 总参数 | 1.04T | 2.78T | ↑167% |
| 激活参数 | 32.6B | 104.2B | ↑220% |
| 隐藏维度 | 7,168 | 7,168 | = |
| Latent MoE 维度 | — | 3584(0.5×) | — |
| 每专家的 MoE 隐藏维度 | 2,048 | 3,072 | ↑50% |
| 路由专家数 | 384 | 896 | ↑133% |
| 每 token 激活的专家数 | 8 | 16 | ↑100% |
| 共享专家数 | 1 | 2 | ↑100% |
| 注意力头数 | 64 | 96 | ↑50% |
| 稠密层数 | 1 | 1 | = |
| 词表大小 | 160K | 160K | = |
| 训练上下文长度 | 128K | 1M | 8× |
| 注意力机制 | MLA | 混合 KDA–MLA | — |
| 激活函数 | SwiGLU | SiTU-GLU | — |
| 注意力层构成 | 61 MLA | 69 KDA + 24 MLA | — |
| MTP 层数 | 1 层 | 1 层 | = |
| ViT 总参数 | - | 401M | - |
| ViT 层数 | - | 27 层 | - |
| ViT 的 patch size | - | 14 | - |
| ViT 注意力头数 | - | 12 | - |
先建立尺度感。2.78T 就是 2.78 万亿个数。如果把每个数印在一张纸上,一张纸厚约 0.1 毫米,2.78 万亿张摞起来大约 27.8 万公里——地球到月球平均是 38.4 万公里,这摞纸能走完地月距离的七成。而这么大一摞里,处理每个 token 时真正参与计算的只有 104.2B,约占 3.7%(这是我们自己算的:104.2 ÷ 2780;K2 那边是 32.6 ÷ 1040 ≈ 3.1%,论文没有列出这两个比例)。这就是混合专家(Mixture of Experts, MoE)的意义:模型可以极大,但每个 token 只走其中很小一部分。
再看哪些没变,这往往比变了的更能说明设计意图。隐藏维度死死钉在 7,168 没动,词表还是 160K,稠密层还是 1 层,MTP 还是 1 层。也就是说 K3 没有把模型变宽,它变的是深(61→93 层)和专家的数量与厚度(384→896 个专家,每专家 2048→3072 维),外加每个 token 激活的专家从 8 个翻倍到 16 个。宽度不动大概率是有意的——隐藏维度牵动着几乎所有模块的形状,动它等于整套超参数重来。
最后是几个只在 K3 出现的新条目:Latent MoE 维度 3584(表里标了「0.5×」,正好是隐藏维度 7168 的一半)、注意力从纯 MLA 变成混合 KDA–MLA、激活函数从 SwiGLU 换成 SiTU-GLU、以及整个 ViT 那四行——这四行在 K2 一栏是空的,因为 K2 不是原生多模态模型。
Table 1 里有一处内部一致性可以校验:「层数 93」 和 「注意力层构成 69 KDA + 24 MLA」。请做这个校验,说明它验证了什么。然后回答第二问:K2 是 61 层 / 61 MLA,K3 是 93 层 / 69 KDA + 24 MLA,K3 的全注意力层(MLA)数量相对 K2 是多了还是少了?这对显存开销意味着什么?
变式:算一算 K3 每个 token 实际激活的参数占总参数的比例(104.2B / 2.78T),再和 K2 的(32.6B / 1.04T)比。K3 的这个比例是升了还是降了?这和「每 token 激活专家数从 8 翻倍到 16」是否矛盾?(提示:分母也在变,而且变得更快。)
7.5 训练配方:从第一天就一起训
论文把 K3 的训练策略叫原生多模态训练策略:语言和视觉从训练一开始就联合优化,而不是事后把一个视觉编码器嫁接到已经训好的语言模型上。在这个范式下,视觉 token 和文本 token 在单一的下一 token 预测目标下交错,让共享骨干从一开始就学统一的多模态表示。这一段和第6章 §2.4 是同一件事的两面:那里讲的是架构上为什么这样设计,这里讲的是训练上怎么执行。
具体的配方论文写得很紧凑,逐项列一下:
- 优化器:Per-Head Muon(第6章),外加 K2 引入的权重裁剪机制(weight-clipping)
- MoE 负载均衡:QB(前面讲分位数均衡的那一章)
- 学习率调度:余弦,带 1% 的线性预热
- 权重衰减:全程 0.1
- 上下文长度:预训练从 8k 开始,后续阶段扩到 64k
「1% 线性预热」的意思是:总训练步数的前 1%,学习率从接近 0 线性升到峰值,然后才开始余弦衰减。为什么要预热?因为训练最开始时参数还是随机的,梯度方向极不可靠,这时候用大学习率往前冲很容易一步走废;先小步试探,等梯度稳定了再放开。
论文把「从训练一开始就联合优化语言和视觉」当成优点来讲。请构造两个具体情形,在那些情形下这个做法明显不如「先训语言模型、再嫁接视觉编码器」。要说清你改动了哪个前提条件。
情形一:训练预算不足。联合训练要求你从随机初始化开始把整个万亿参数模型完整训一遍,没有任何复用余地。如果你的算力只够跑几千 GPU 小时,那么「拿一个现成的语言模型,只训一个转接层和一个小视觉编码器」是唯一可行的路——嫁接式方案的核心优势就是可以站在别人已经付过的成本上。这里改动的前提是「预算充足到可以从零训一遍」,而这正是论文默认成立、但对绝大多数团队不成立的前提。
情形二:模块需要独立迭代。联合训练让视觉表示和语言骨干互相塑造、深度耦合。一旦出现更好的视觉编码器,你没法把它插进来——K3 的骨干读的是 MoonViT-V2 那一套表示,换编码器意味着重训。嫁接式方案里视觉编码器和语言模型之间有一个明确的、窄的接口(转接层),换掉一侧只需要重训接口。这改动的前提是「模型是一次性交付的产品」变成「模型需要长期分模块演进」。
可选的第三个情形:模态数据严重失衡。如果视觉数据只占语料的极小比例,联合训练早期视觉塔拿到的梯度信号非常稀薄,而语言侧已经在快速前进,视觉 token 可能长期被骨干当成噪声;此时先把语言模型训扎实、再用集中的图文数据做对齐,反而更有效率。要注意的是,论文并没有讨论任何一种失败情形——它只陈述了自己选择的范式和理由,上面三条是我们按照该做法的结构推出来的,不是论文的结论。
变式:反过来构造。假设某个团队算力管够,但只关心纯文本能力,一点也不需要看图。此时「从第一天就混入视觉 token 一起训」对纯文本能力是帮助、无害还是拖累?给出你的猜想和理由,并说明论文这几页有没有提供任何数据能验证它。
7.6 长上下文扩展:位置编码、数据、四阶段课程
位置编码:干脆不要
位置编码(positional encoding):注意力机制本身是「无序」的——把输入 token 打乱顺序,纯注意力算出来的结果不变。所以必须额外注入「谁在前谁在后」的信息,这就是位置编码。目前最流行的是 RoPE(旋转位置编码)。麻烦在于:位置编码是按训练时见过的长度学出来的,一旦推理时的序列长过训练长度,模型就会遇到没见过的位置,输出崩坏。业界为此发明了一堆补丁——RoPE 重缩放(RoPE rescaling)、插值(interpolation)等等,本质都是把没见过的位置硬压回见过的范围里。
K3 的做法是:不用任何显式位置嵌入,论文称之为 NoPE(no positional embedding)。位置信息通过 KDA 的循环门控与衰减机制隐式编码。为什么这能行?因为 KDA 是循环式的——它一个 token 一个 token 地更新自己的状态,早来的 token 经过更多次衰减,晚来的经过更少,「谁在前谁在后」天然就写在状态里了,不需要额外贴标签。
论文说的结果是:模型可以直接外推到 100 万 token 的上下文,不需要任何位置编码方面的改动(不需要 RoPE 重缩放,也不需要插值)。
「直接外推」这四个字要读准
很容易把它误读成「K3 从来没在长序列上训过,直接就会了」。不是的——7.6 后半段明明白白写着有一个四阶段课程,窗口一路涨到 1M,模型是在 1M 上训练过的。「直接外推」说的是另一件事:不需要改动位置编码。RoPE 那条路上,就算你愿意在长序列上训,你也得先决定怎么处理超出原始训练长度的位置——是重缩放还是插值,怎么调,改完会不会伤害短序列性能。NoPE 把这一整个决策和它带来的一系列副作用全部省掉了。省掉的是「改造成本」,不是「训练成本」。
一个论文没有回答的问题
K3 是混合 KDA–MLA,Table 1 说有 24 层 MLA。MLA 是全注意力层,它本身没有位置感。既然全模型没有任何显式位置编码,那这 24 层的位置信息只能来自残差流里由 KDA 层写进去的隐式信号。这个安排到底稳不稳、24 这个数字是不是有上限、如果 MLA 层再多会不会破坏外推——论文这几页都没有说明。这是全章最值得追问的技术空白之一。
长上下文数据:先洗,再上采样,最后合成
论文指出一个很现实的问题:自然来源的长文档和视频含大量低质内容——近重复、二进制块、截断的文件、视频片段、无效的机器生成日志。想想也对,网上最长的那些「文档」往往是日志、是自动拼接的垃圾页,真正又长又连贯的东西非常少。
所以有一套专门的清洗流水线:精确去重 + 模糊去重,视频再补上跨帧的感知哈希,配合启发式与分类器质量过滤和结构校验。
洗完之后还有第二个问题:真正长而连贯的文档和视频,相对短文本来说太稀缺了。如果按自然比例采样,长样本会被短样本彻底淹没。所以要上采样(upsample),让长上下文的分布在冷却阶段(cooldown)不被短序列压过去。
但论文紧接着说了这一节最重要的一句话:光有长度不等于长程能力。
为什么「长」不等于「会用长」
设想一本 100 万 token 的小说,模型在上面做下一 token 预测。要猜准下一个词,绝大多数时候只需要看前面几百个词——文风、当前句子的语法、刚提到的人名,这些全在附近。前面 99 万 token 的内容对当前这一步几乎毫无帮助。于是模型完全可以在「很长的数据」上训练很久,同时学到一个只看局部的策略,因为那个策略在这份数据上已经足够好了。论文的原话是,注意力会退化成局部模式。喂长数据只保证了「能装下」,没有保证「必须用」。
补法是合成长上下文数据:精心排列并拼接多模态文档和子任务,使得嵌在里面的任务只有靠关注散落在整个 100 万 token 上下文里的信息才能解决。这是一个很漂亮的思路——不是祈祷模型自己学会用远处的信息,而是构造出必须用才能做对的题。论文说,这样做能在目标尺度上训练注意力机制,防止它退化成局部模式。
渐进式上下文扩展:四阶段课程
K3 支持最长 100 万 token 的上下文窗口。做法是随训练推进逐步扩大窗口,走一个四阶段课程:窗口在预训练期间从 8K 涨到 64K,在冷却阶段从 256K 涨到 1M。
理由是算力。长序列的计算非常贵,如果全程都用 1M 的窗口训练,成本会高到不可接受。把昂贵的长序列计算集中在整体训练预算的一小部分里,课程才经济可行,同时又让模型逐步适应越来越长程的依赖。(论文还提到,让百万 token 训练在 KDA 层上变得可行的「序列维切分」技术放在 §5.1.2 讲,不在本章范围内。)
有人说:「既然 NoPE 让 K3 能直接外推到 1M,那这个四阶段课程就是多余的,直接在 8K 上训完然后在 1M 上用就行了。」请指出这个说法错在哪,并说清「外推」这个词在论文语境里到底指的是什么被省掉了。
变式:如果一个模型用的是 RoPE 而不是 NoPE,但同样跑了这个四阶段课程一路训到 1M,它是不是就不需要 RoPE 重缩放了?(提示:想想「训练时见过的最大位置」和「推理时可能遇到的位置」,以及在 1M 上训练 RoPE 的代价。)
把本章和第6章串起来,回答一个总问题:K3 要在 100 万 token 的上下文里做多模态长时程任务,一共需要几件事同时成立?请至少组织出四条,每条说明「如果缺了它会怎样」,并指出其中哪几条属于论文没有给出证据、只给了陈述的部分。
一、位置信息:NoPE + KDA。缺了它,你就得给 1M 的位置做 RoPE 重缩放或插值,多一整套需要调、还可能伤害短序列的改造。K3 让位置信息由 KDA 的循环门控与衰减隐式携带,序列多长都不存在「没见过的位置」。
二、显存:混合 KDA–MLA,MLA 只有 24 层。全注意力层的 KV 缓存随长度线性增长,层数越多、上下文越长,总量越大。K3 总层数涨到 93,全注意力层反而从 K2 的 61 降到 24,靠状态大小不随长度增长的 KDA 层承担大部分层。缺了它,前面所有努力都会先卡在显存上。(这一条是基于两种机制各自特性的推理,论文没有给出显存数字,也没有说明 69 : 24 这个配比的来源。)
三、数据:清洗 + 上采样 + 合成。自然长文档大量是垃圾,所以要洗;洗完还太稀缺,所以要上采样;上采样完还只是「长」,注意力照样会退化成局部模式,所以要合成出「不看遍全程就做不对」的任务。缺了最后一步,模型能装下 1M 却不会用 1M。
四、成本:四阶段渐进课程。窗口在预训练期间 8K→64K、冷却阶段 256K→1M,把昂贵的长序列计算压缩在总预算的一小部分里。缺了它,全程 1M 训练的账根本算不过来。
五、多模态那一侧:pixel-shuffle(第6章)。顶格 3584×3584 的图切成 patch 是 65536 个,2×2 pixel-shuffle 之后是 16384 个,1M 上下文大约能放 61 张这样的顶格图(1000000 ÷ 16384 ≈ 61,我们自己算的)。缺了这一步,同样的上下文只能放约 15 张,「翻十几屏截图做长时程视觉任务」就无从谈起。
证据强度的分层。有直接图表支撑的只有很少一部分:Figure 6(视觉编码器稳定性)、Figure 7(2.5× 缩放效率)、Table 1(架构数字)。而「合成长上下文数据能防止注意力退化成局部模式」「四阶段课程让模型逐步适应长程依赖」「NoPE 使模型可以直接外推到 1M」这几条,论文这几页只给了陈述,没有给消融、没有给长上下文评测分数。此外四个阶段具体是哪四个、各占多少预算、合成数据占比多少、24 层 MLA 是怎么定下来的,也都没有说明。作为读者,正确的姿态是:机制上的因果链条是自洽且可理解的,但「它确实奏效」在这一章里主要是作者的自述。
变式:假设你要把 K3 的上下文再往上推到 1000 万 token,上面五条里哪一条会最先撑不住?给出你的判断和理由。(提示:逐条问「这一条的代价是随长度线性增长、还是不增长、还是平方增长」。)
答辩:如果我是审稿人
你说 NoPE 让 K3 「直接外推到 1M 而不需要任何位置编码改动」。可你自己的表里写着有 24 层 MLA,那是不带任何位置感的全注意力层。它们的位置信息完全依赖前面 KDA 层写进残差流的隐式信号。这个依赖有多脆弱?如果我把 MLA 的比例调高,外推还成不成立?你有没有做过这个消融?
参考防守(先自己组织语言再看)
这一击直指论文的空白,必须先承认:这几页没有给出任何关于 KDA/MLA 比例与外推能力关系的消融,69 : 24 这个配比是怎么定的也没有说明。能做的防守有三层。第一层是机制上的自洽性:MLA 层读的是残差流,而残差流从第一层起就被 KDA 层持续注入了随位置衰减的信号,所以 MLA 层并非「看不到位置」,它是从表示里读到位置,而不是从额外的编码里读到位置——这在原理上没有断裂。第二层是结构上的保障:69 层 KDA 远多于 24 层 MLA,而且按混合架构的常规做法,KDA 层是穿插分布的,任何一层 MLA 之前都有 KDA 层在为它准备带位置信息的表示;换句话说这个依赖不是靠某一层的单点供给。第三层是经验性的:论文报告模型确实工作在 1M 上下文,这本身是对这套安排可行性的存在性证明——虽然是弱证明。真正无法防守的正是审稿人问的那个假设句:「如果我把 MLA 比例调高会怎样」。这是一个纯粹的开放问题,论文没有给出比例的上界,也没有给出外推能力随比例变化的曲线。诚实的回答是:我们只知道 69 : 24 这一个点能用,不知道边界在哪。
本章小结
数据:四个文本域(网页、代码、数学、知识)加一份大规模视觉语料。文本靠规则启发式、分类器质量打分、去重三道过滤,域采样比例由小模型消融决定;知识和数学还要经过多风格改写并对照源文档做保真度验证。视觉侧的两个关键设计是「绝对 + 归一化 [0,1] 双格式坐标监督」和「大幅扩大程序化多模态数据」(代码与它渲染出的 SVG、3D 资产、网页、游戏、CAD 图纸配对)。
缩放定律:因为架构、数据、训练全变了,最优训练配置也变了,所以重做缩放定律来重调 batch size、学习率、TPP 和模型形状。在留出的分布外验证数据上,Figure 7 显示合起来约 2.5 倍的缩放效率增益——同样的验证损失,K3 只要 K2 约 40% 的算力。方法论上最值得学的是余弦衰减与 WSD 的比较:作者发现两种调度的最优超参数差别很大,用共享超参比较会不公平地偏袒其中一个,于是对每种调度独立做缩放定律搜索,在各自最优点上比,结论是余弦衰减始终损失更低。
架构变化(Table 1):宽度不动(隐藏维仍是 7168,词表仍是 160K),变的是深度(61→93 层)、专家规模(384→896 个路由专家,每专家 2048→3072 维,每 token 激活 8→16 个,共享专家 1→2)、注意力头(64→96),总参数 1.04T→2.78T,激活参数 32.6B→104.2B。注意力从纯 MLA 变成混合 KDA–MLA(69 KDA + 24 MLA,加起来正好 93),激活函数 SwiGLU→SiTU-GLU,训练上下文 128K→1M。新增的 ViT 是 401M 参数、27 层、patch size 14、12 个头。
配方与长上下文:语言和视觉从第一天就在单一的下一 token 预测目标下联合优化;优化器是 Per-Head Muon 加 K2 的权重裁剪,MoE 负载均衡用 QB,余弦调度加 1% 线性预热,权重衰减全程 0.1,上下文从 8k 起步、后续扩到 64k。长上下文靠三件事:NoPE 让位置信息由 KDA 隐式携带、从而不需要 RoPE 重缩放或插值;数据要洗(精确+模糊去重、视频用跨帧感知哈希、质量过滤与结构校验)、要上采样、还要合成出「必须看遍全程才能解决」的任务;四阶段课程把窗口从 8K 抬到 64K(预训练)、再从 256K 抬到 1M(冷却),把昂贵的长序列计算压缩在总预算的一小部分里。
本章的空缺清单:2.5× 没有拆解到单项、图上只标了一处;WSD 对比的搜索空间没有说明;四个阶段各是多少、各占多少预算没说;合成长上下文数据的具体构造和占比没说;24 层 MLA 这个比例怎么定的、外推能力随比例怎么变没说;这几页也没有给任何长上下文评测分数。机制讲得清楚,效果主要靠自述。
第8章 后训练:从模仿到自我进化,再压回一个模型
一句话导语:预训练让模型学会「说话」,后训练让它学会「做事」——而 K3 的做法是先教会它模仿(SFT),再让它在三个领域、三档思考长度上自己练成九个专家(RL),最后把九个专家压回同一个模型(MOPD),并且从头到尾都按「上线后要用的那个精度」来训练。
学完这一章你应该能做到
- 用自己的话说清 SFT、RL、MOPD 三个阶段各解决什么问题,为什么顺序不能换
- 解释「部分 rollout」为什么会制造数据陈旧,以及为什么「把更新约束在局部邻域」能救它
- 给定初始预算 b0 和乘子 τ,算出一条轨迹会不会被判 −1
- 逐符号读懂式 15 的逐 token 蒸馏奖励,并判断它对某个 token 是正还是负
- 说出 Σ min(p,q) 的几何含义,并证明两个分布相同时它等于 1
- 指出 MXFP4 量化、草稿模型初始化技巧各自的适用边界与论文没说清的地方
8.1 全局图:为什么后训练要分三段走
先想一个问题。预训练结束时,模型见过海量文本,能把句子接得很顺,但它并不知道「人类现在要它干什么」,更不知道「该在什么时候停下来去调用一个工具、看看返回结果再继续」。这就像一个把整座图书馆背下来的人——知识满分,但你让他去帮你订张机票,他会开始背《航空运输管理条例》。
不做后训练会怎样
会得到一个「知识渊博但不会办事」的模型。所有关于「听懂指令」「按格式回答」「该动手时动手」的能力,都不是靠多读书能自动长出来的,必须专门训。论文把这套流程叫后训练(Post-Training)。
论文原文把 K3 的后训练概括成一个三阶段范式:先用监督微调(Supervised Fine-Tuning, SFT)初始化基线智能体能力;再用强化学习(Reinforcement Learning, RL)在不同的推理努力等级上培养出各领域的专家;最后用多教师在策略蒸馏(Multi-Teacher On-Policy Distillation, MOPD)把这些领域专属的策略合并回单一模型。
打个比方
SFT 像是让学徒照着师傅的录像模仿——先把动作做对,哪怕不懂为什么。RL 像是让他自己上场比赛,赢了加分输了扣分,慢慢练出师傅都没有的手感;而且不是练一个全能选手,是分头练出九个「专项冠军」。MOPD 则像是让这九个冠军轮流带同一个新人,最后新人一个人就能顶九个。
类比失效处:真实的九个专家不是九个人,而是同一个基座模型的九份不同权重;而且 MOPD 的「带徒弟」不是让学生看教师的完整答案,而是学生自己写、教师逐个 token 打分。这一点后面 8.7 会讲透。
注意这个顺序不是随便排的。RL 需要一个「已经会办事」的起点,否则它在巨大的动作空间里瞎撞、几乎拿不到任何正奖励,学不动;这就是 SFT 被称为冷启动(cold-start)的原因。而 MOPD 必须放最后,因为它要蒸馏的对象——九个专家——得先被 RL 练出来才存在。
有人提议:既然 RL 最能提升能力,干脆跳过 SFT,让预训练模型直接进 RL,省一大笔标注钱。按论文的说法,这个方案的主要风险是什么?
变式:如果任务改成「回答一道单选题」(一步就出结果,四个选项猜中概率 25%),跳过 SFT 直接 RL 还会有同样的问题吗?为什么这说明「SFT 是否必需」取决于任务的时程长度?
8.2 SFT:让模型先学会照着做
监督微调(Supervised Fine-Tuning, SFT):给模型看大量「问题 + 标准回答过程」的配对,让它把标准回答的每一个 token 的概率调高。本质上就是「照着抄」,抄到形成肌肉记忆。
K3 的 SFT 有一个很特别的地方:训练数据里的轨迹(trajectory,指一条完整的「思考—调用工具—看返回—再思考」的记录)不是纯手写的,而是用先前 Kimi 系列的领域专用模型合成出来的,然后再过多阶段验证和人在环标注(human-in-the-loop annotation)。
为什么要用旧模型合成数据
一条复杂智能体轨迹可能有几十步工具调用、上万个 token。让人从零手写,成本高到不可行,而且人写的「思考过程」往往和模型真实的思考方式对不上,学起来反而别扭。用已有的领域专用模型批量生产、再让人筛选和修正,是把人力用在「判断对错」而不是「从零创作」上——判断比创作便宜得多。
论文说这些步骤「共同产出了一个大规模指令数据集,赋予 Kimi K3 自适应推理、精确工具调用,以及在长时程智能体场景中稳健执行的能力」。所有数据都用他们基于 XTML(eXtensible Token Markup Language,可扩展 token 标记语言)的对话模板序列化,细节在论文附录 F,本书第14章会讲。
还有一个容易被跳过但很关键的细节:量化感知训练(Quantization-Aware Training, QAT)从 SFT 阶段就开始应用了,权重用 MXFP4、激活用 MXFP8。也就是说,K3 不是「先训练好一个高精度模型,最后再压缩」,而是从后训练第一天起就按压缩后的样子训练。这个设计的深意留到 8.8 讲。
常见做法是训练完全精度模型后再做「训练后量化」(把权重直接四舍五入到低精度)。K3 却从 SFT 就开始 QAT。请解释:为什么「最后再压」这件事,在有 RL 的流程里比在纯 SFT 的流程里更危险?
变式:如果一家公司只做 SFT、不做 RL,事后量化和 QAT 的差距会变小还是变大?如果它做 RL 但部署时用的是全精度,QAT 还有意义吗?
8.3 RL:不是训一个全才,而是训九个专家
拿到冷启动模型后,论文说「RL 对于解锁更高阶的推理与执行能力至关重要」。但 K3 的 RL 组织方式和很多人想的不一样。
直觉做法是:有多少类任务,就训多少个专门的 RL 模型。K3 没有这么做。论文原话是「不是为单个任务训练专门的 RL 模型,而是把 RL 扩展到三大领域,每个领域涵盖广泛的子任务,并在每个推理努力等级上为每个领域训练单一专家」。三大领域是:
| 领域 | 覆盖的子任务 |
|---|---|
| 通用任务(general tasks) | 通用体验、视觉、推理、忠实性、搜索能力、知识工作任务 |
| 通用智能体(general agents) | 长时程助理任务、深度研究、段落级写作 |
| 编码智能体(coding agents) | 软件工程(SWE)、编码体验、内核任务、网页开发 |
再把这三个领域专家与三个推理努力等级(reasoning effort level) {low, high, max} 相交叉,得到总共九个专家模型。所谓推理努力等级,通俗说就是「允许它想多久」——low 是快答,max 是深思。
为什么按领域切,而不按任务切
按任务切会得到几十个模型,每个模型的数据都很少,而且任务之间的共性(比如「怎么读工具返回值」)要被重复学几十遍。按领域切,一个专家能吃到整个领域的数据,共性能力互相增强;同时又不至于像「一个模型打天下」那样,让编码任务的奖励和写作任务的奖励在同一组权重上互相拉扯。这是粒度上的折中。
Figure 8:步数随算力增长,说明了什么
论文 Figure 8 画了八个面板(编码体验、通用工具使用、网页开发、智能体搜索、专业工作流、办公交付物、智能体图表理解、智能体视觉谜题),横轴是 RL FLOPs(投入 RL 的计算量),每张图上有两条线:实线是评测得分,虚线是平均助手步数(average assistant steps,即模型平均调用了多少步工具)。
论文对这张图的总结是:随着 RL FLOPs 扩大,工具调用步数持续上升,同时伴随模型整体能力的全面提升。
常见误解
很多人看到「步数变多」第一反应是「变啰嗦了、效率变差了」。但在这里恰恰相反。步数是模型自己选择的——没人规定它必须调几次工具。步数上升的同时得分也在上升,说明模型学到的是「这个问题值得我再查一次/再验证一遍」,即愿意为难题付出更多真实行动。这和「废话变多」是两回事:废话不带来新信息,多一步工具调用带来的是外部世界的新证据。
读的时候要小心
Figure 8 的纵轴没有标注具体数值刻度,横轴 RL FLOPs 也没给绝对值,这些都是论文自述的训练期内部曲线,不是可复现的公开评测。「步数增长伴随能力提升」是一个相关性观察,论文没有做「人为限制步数会掉多少分」的对照实验,所以不能据此断定「步数增长是能力提升的原因」。
某团队看到 Figure 8 后决定:直接在奖励里加一项「每多调用一次工具 +0.1 分」,希望复现「步数增长带来能力提升」。请预测会发生什么,并说明这个推理错在哪。
变式:如果反过来,在奖励里加「每多一步 −0.1 分」来强行提效,又会出什么问题?把这两种做法和论文实际采用的「超过 τ·b0(x) 才判 −1」的硬阈值做法对比,硬阈值好在哪?
8.4 部分 rollout:不等最慢的那个人
rollout:RL 里让模型实际跑一遍任务、生成一整条轨迹的过程。因为智能体任务要真的去调用工具、等沙箱返回结果,一条轨迹可能要跑很久。
不解决会怎样
同步 RL 框架的一轮迭代是「先全部生成,再统一更新」。长时程任务里,绝大多数轨迹几分钟就完了,但总有几条要跑几十分钟(比如某个 SWE 任务反复编译失败在重试)。整轮迭代被这几条落后者(straggler)卡住,成千上万块 GPU 在那里空转等待。这就是论文说的「长时程任务里加剧的长尾延迟」。
论文的对策是扩展他们同步 RL 框架里的部分 rollout(partial rollout)方案。机制是这样的:每轮迭代的 rollout 阶段,对 N 个提示各采样 K 个完成,维持 N×K 条轨迹的活跃工作量。不等所有 rollout 结束——只要有 λ ∈ (0,1) 比例的轨迹完成(也就是 λNK 条),生成阶段立刻暂停,让策略优化不被执行落后者拖住。被暂停的 rollout 会入队,在下一轮迭代开始时优先恢复,由他们的沙箱基础设施支撑。而一旦某个提示的全部 K 条回答都完成了,就立刻派去做策略优化(该算法沿用 Kimi K2.5)。
代价:数据陈旧
论文很诚实地指出了副作用:在部分 rollout 下,一条长时程轨迹天然会跨越多轮迭代,这引入了数据陈旧(data staleness),威胁训练稳定性。
在策略 / 离策略(on-policy / off-policy):RL 的标准假设是「用当前策略采的数据来更新当前策略」(在策略)。如果数据是几轮之前的旧策略产生的,就叫离策略。数据越旧,离策略程度越严重,梯度估计的偏差越大,训练越容易崩。
论文的解决方式是:他们的策略优化算法通过逐 token 正则化(per-token regularization)天然容忍这种极端离策略状态——「通过把策略更新约束在一个局部邻域内,这种正则化使算法能稳健地处理高度陈旧的数据,并维持训练稳定性」。
自己推一遍:为什么「约束在局部邻域」能救陈旧数据
先问:陈旧数据到底「错」在哪?一条两轮前采的轨迹,它本身的文字内容并没有变质,为什么就不能直接拿来更新?
想好了再看
问题不在文字,在概率。策略梯度的正确性依赖于「这些样本是按当前策略 πθ 的概率分布采出来的」。两轮前的策略 πold 更可能采出某些 token、更不可能采出另一些。用 πold 的样本去估计 πθ 下的期望,需要乘上重要性权重 πθ/πold 来纠偏。当初想到这一步,是因为「换个分布采样、乘个比值纠正」是概率里最标准的一招(重要性采样)。
那么,重要性权重 πθ/πold 在什么情况下会变得非常危险?动手算一下:如果某个 token 在旧策略下概率是 0.001,在新策略下变成了 0.5,权重是多少?
想好了再看
权重 = 0.5 / 0.001 = 500。一条轨迹有上万个 token,每个 token 都可能贡献这样一个乘数。权重的方差会爆炸——少数几条轨迹的梯度可以盖过其余全部样本,一次更新就把模型带到不知道哪里去。数据越旧,πθ 和 πold 相差越远,出现极端权重的概率越高。这就是陈旧数据「威胁训练稳定性」的具体机制。
现在反过来问:如果我们能强行保证 πθ 在每个 token 上都不会离 πold 太远,上一步的灾难还会发生吗?
想好了再看
不会。如果每个 token 的比值都被约束在比如 [0.8, 1.25] 之内,那么无论数据有多旧,重要性权重都是温和的,方差有界,梯度估计不会被少数样本劫持。这就是「把策略更新约束在一个局部邻域内」的含义——不是让数据变新,而是让新旧策略之间的差距永远不大到让纠偏失效。逐 token 做这件事,比在整条轨迹层面做要精细得多,因为一条上万 token 的轨迹哪怕整体差不多,也可能有个别 token 的比值离谱。
最后一问:这个方案的代价是什么?天下没有免费的午餐。
想好了再看
代价是单步学习速度受限。既然每次更新都不许走远,那么模型每轮能改变的幅度就有上限。换来的是可以安全地用大量陈旧数据、可以把 GPU 利用率拉满、可以做部分 rollout。在长时程智能体训练这个场景下,这笔交易明显划算:吞吐量的提升远大于单步步长的损失。论文并没有给出这笔账的量化对比数据。
论文规定 λ ∈ (0,1)(开区间,取不到 0 和 1)。请分别构造 λ → 1 和 λ → 0 这两个极端下的失效场景,说明为什么两个端点都必须被排除。
变式:假设所有轨迹长度完全一样(没有长尾),λ 应该取多少?这说明 partial rollout 的收益来源究竟是什么?
综合题:论文在 8.7 会说,MOPD 的逐 token 蒸馏奖励「无缝接进 RL 框架,自然地让部分 rollout 之类的基础设施层优化也能用于长时程任务的蒸馏训练」。请把「部分 rollout」、「逐 token 正则化」、「逐 token 稠密奖励」三者串成一条互相支撑的逻辑链,并指出如果去掉中间那一环会发生什么。
变式:如果某个任务本身就很短(比如单轮问答),这三者的耦合关系还成立吗?在短任务上,部分 rollout 还有价值吗?
答辩:如果我是审稿人
你说「逐 token 正则化把更新约束在局部邻域,所以能容忍极端陈旧数据」。可这听起来就是「把学习率调小」的花哨说法。请说明你的方法和单纯减小步长有什么本质区别;另外,你既没给 λ 的取值,也没给「有/无正则化」的稳定性对照实验,我凭什么相信这个机制真的在起作用?
参考防守(先自己组织语言再看)
第一个问题可以正面回答:有本质区别。减小学习率是对所有参数、所有 token 一视同仁地缩小步长,它压制的是更新的「总量」,但压不住个别 token 上重要性权重高达几百倍所带来的方差——那个极端样本的方向依然会主导梯度,只是整体走得慢一点。而逐 token 正则化约束的是新旧策略在每个 token 上的概率比,它直接砍掉的是方差的来源。换句话说,学习率控制「走多远」,逐 token 约束控制「往哪个方向走才算合法」。在有极端离群权重的场景里,后者是必需的,前者替代不了。
第二个问题必须承认:论文这一段确实只给了机制性的文字说明,没有给 λ 的具体取值,也没有做消融实验来证明「去掉正则化就会崩」。它把算法细节推给了引用的 Kimi K2.5 论文。所以严格说,「该机制在 K3 规模上确实有效」这一点,在本文中属于作者主张而非被本文数据支持的结论。可以辩护的是:部分 rollout 与该正则化的组合在 K2.5 上已有公开报告,K3 是沿用而非首创;但读者应把它当作「继承自前作的设计选择」,而不是「本文验证过的结论」。
8.5 推理努力 RL:给「想」这件事上一个预算
为什么要管思考长度
因为不管的话,模型会过度思考。RL 只奖励「答对」,那么对模型来说最保险的策略就是「多想一点总没错」——反正想得久不扣分。结果就是问 1+1 也要写三千字推演。这既浪费用户的时间和钱,也让模型没法适配「我现在只要一个快答」这种真实需求。
K3 的做法是一个逐问题的预算控制机制(沿用 K2.5)。给每个问题 x 关联一个初始 token 预算 b0(x),这个预算由冷启动模型估计得到;然后,对于总 token 预算 T(y) 超过缩放阈值 τ · b0(x) 的轨迹,把任务奖励直接改写成 −1。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| x | 一个具体问题(提示) | 「这道题」 |
| y | 模型针对 x 产出的一条完整轨迹 | 「这次的作答全过程」 |
| b0(x) | 由冷启动模型估计出的该问题的初始 token 预算 | 「这题大概值多少字」——注意它因题而异,难题预算天然更大 |
| τ | 预算乘子,一个大于 0 的标量,按领域配置 | 「宽容度旋钮」:调大 = 允许多想,调小 = 逼它简短 |
| T(y) | 轨迹 y 实际用掉的总 token 预算 | 「这次实际写了多少」,具体口径见下文 |
| −1 | 覆盖掉原任务奖励的惩罚值 | 「超时作废」:哪怕答案完全正确,也按最差处理 |
T(y) 的口径分两种:对通用任务,它衡量思考 token 数;对智能体任务,它统计累计输出 token,包括推理痕迹和工具调用参数。后者把工具调用参数也算进去很有必要——否则模型可以把长篇大论塞进工具参数里绕过限制。
分阶段课程:τ 从大到小退火
训练遵循一个对预算乘子 τ 的分阶段课程(stage-wise curriculum):先用相对较大的 τ 训练 max-budget 变体,同时仍然给最大预算设上限以抑制过度思考;然后把 τ 退火(anneal,逐步降低)到更小的值,得到 high 和 low 努力的专家模型。τ 的调整按领域配置,并在人在环的指导下进行。
最后一句话很重要:所有推理等级产生的轨迹被联合收集,用于监督微调和多教师在策略蒸馏。也就是说,九个专家不只是最终 MOPD 的教师,它们跑出来的轨迹本身也是数据资产。
补充:为什么是「先大后小」而不是反过来
论文只说了顺序,没有解释原因。一个合理的推测是:先在宽松预算下让模型充分学会「怎么把难题做对」,形成正确的解题能力;再逐步收紧预算,逼它学会「把同样的事情说得更短」。反过来先训 low 再放宽,模型可能从一开始就没学会难题的完整解法,后面再放宽也补不回来。这是本书的推测,论文并未给出这个解释,也没有做顺序上的消融实验。
某个通用任务问题 x,冷启动模型估计的初始 token 预算 b0(x) = 800。当前训练的是 high 档专家,τ = 2.5。模型跑出两条轨迹:轨迹 A 思考了 1900 个 token,答案正确;轨迹 B 思考了 2100 个 token,答案也正确。这两条轨迹各自拿到什么奖励?
变式:同一个问题、同样两条轨迹,如果现在训练的是 low 档专家、τ 退火到了 1.2,结果会怎样?如果 τ = 3.0 呢?请说明「同一条轨迹在不同 τ 下拿到相反的奖励」这件事,为什么恰恰是分阶段课程能造出三档专家的原因。
答辩:如果我是审稿人
把超预算轨迹的奖励直接改写成 −1,未免太粗暴了。一条只超了 1 个 token 的正确答案,和一条彻底答错的垃圾答案,拿到完全相同的奖励——这明显破坏了奖励函数的连续性,也会污染优势估计。为什么不用一个随超出量平滑衰减的惩罚项?
参考防守(先自己组织语言再看)
可以从三个角度防守。第一,硬阈值抗黑客:平滑惩罚意味着模型总能做「多想一点点、多扣一点点分、但换来更高正确率」的划算交易,只要正确率的边际收益大于惩罚的边际成本,模型就会一路超下去——最后惩罚项形同虚设。硬性改写为 −1 让「超预算」变成一道不能碰的墙,交易不成立。第二,阈值本身是自适应的:b0(x) 是逐问题由冷启动模型估计的,难题的预算天然更大,所以这道墙并不是「一刀切的绝对字数」,粗暴的是惩罚形式,不是阈值位置。第三,目标是造出可控的档位:K3 要的是三个行为明显不同的专家(low/high/max),一个平滑惩罚会让三档之间的界限模糊;硬阈值 + 分阶段退火 τ,才能得到边界清晰的三个模型。
但审稿人的顾虑有一半站得住:论文确实没有讨论阈值附近的边界效应,也没有报告「是否观察到模型学会在阈值前紧急收尾导致答案被截断」这类问题;论文也没有提供与平滑惩罚方案的对照实验。诚实的说法是:这是一个被前作(K2.5)验证过并沿用的工程选择,本文没有为它提供新的实证支持。
8.6 智能体生成式奖励模型:让 AI 当裁判,还得按流程判
为什么需要一个「AI 裁判」
数学题、代码题可以自动判对错——跑一遍就知道。但「帮我写一封得体的道歉邮件」「这份研究报告写得好不好」没有标准答案,程序判不了。论文把这类任务叫不可验证的通用任务(non-verifiable general tasks)。没有奖励信号,RL 就跑不起来,所以必须造一个裁判。
K3 对这类任务采用智能体生成式奖励模型(Agentic Generative Reward Model, Agentic GRM),保留了 K2.5 的锦标赛式分组奖励与二元比较——也就是把多个候选答案两两 PK,像打比赛一样比出高下,而不是给每个答案打一个绝对分。
打个比方
让老师给一百篇作文各打一个百分制的绝对分,第一篇和第一百篇之间的标准很难保持一致(评分会漂移)。但让他每次只回答「这两篇哪篇更好」,判断就稳定得多。锦标赛式二元比较就是这个思路。
类比失效处:人类老师的两两比较是有传递性直觉的,而 AI 裁判可能出现 A>B、B>C、C>A 的循环。论文没有讨论如何处理这种不一致。
K3 的新东西在于:除了通用的智能体能力(裁判本身也能调用工具、去实际检查产物),这个智能体裁判被要求遵循一个强制协议(mandatory protocol),四步,顺序固定:
- 读取结果、产物或文本输出;
- 生成一份评分细则(rubric,就是评分标准清单);
- 用该细则给每个候选打分;
- 把细则分数记录到一个 scorepad(记分板)里。
为什么要强制这个顺序
关键是第 2 步必须在第 3 步之前。如果让裁判直接说「我觉得 A 更好」,它很容易被表面因素影响(更长、格式更花哨、用词更自信),事后再编个理由。强制它先写出评分标准、再逐条对照打分,等于把判断过程外化成可检查的步骤,减少「凭感觉」。第 4 步把分数写进 scorepad,则让整个判决留下可追溯的记录。
啰嗦度控制:堵住裁判自己的漏洞
论文指出了一个具体的奖励黑客方向:模型会朝着「输出越来越啰嗦」进化,因为长答案在裁判眼里往往显得更完整、更用心。为了缓解这一点,作者加了一个基于预算的啰嗦度控制,做法和 8.5 的推理努力控制类似:给定由冷启动模型估计的初始啰嗦度 ℓ0 和一个乘子 σ,输出长度超过 σ · ℓ0 的候选自动输掉二元比较。
构造反例:请设计一类具体的用户请求,使得「输出长度超过 σ·ℓ0 就自动输掉比较」这条规则系统性地惩罚了更好的答案。然后说明:为什么用同一套机制去管「思考长度」(8.5)比管「输出长度」(8.6)更安全?
至于第二问:管思考长度更安全,是因为思考 token 用户看不到、也不构成交付物——砍掉多余的思考几乎只有好处,最坏情况是难题上思考不足导致答错,而那会被任务奖励本身惩罚,两个信号方向一致。而输出长度就是交付物本身,砍它可能直接砍掉用户要的内容,此时长度惩罚和质量奖励是方向冲突的,需要 ℓ0 估得非常准才不出事。论文没有讨论 ℓ0 的估计误差问题,也没有说明 σ 是否按任务类型区分配置。
变式:如果把规则从「自动输掉比较」改成「长度只在两个候选质量打平时才作为判据」,上面的反例还成立吗?这么改会牺牲掉原规则的什么好处?
8.7 MOPD:把九个专家压回一个模型
现在手上有九个专家模型。问题来了:用户不可能装九个模型。每个都是完整的一份权重,光显存就要九倍。必须合并成一个。
知识蒸馏(Knowledge Distillation):让一个「学生」模型去模仿「教师」模型的输出,从而把教师的能力搬到学生身上。传统蒸馏是让学生去拟合教师在教师自己生成的文本上的分布。
在策略蒸馏(On-Policy Distillation, OPD):改成让学生自己生成文本,教师在学生写出的每一个 token 上给出评价。这样学生练的是「自己真实会犯的错」,而不是「教师从不会走到的完美路径」。
打个比方
传统蒸馏像是看名师的解题录像,一遍遍抄。在策略蒸馏像是自己下场做题,做一步老师在旁边点一下头或摇一下头。后者显然更对症——因为老师的批注落在你真正会走错的岔路口上,而不是落在你根本想不到的那条完美路线上。
类比失效处:真实的「点头摇头」不是二元的,而是一个连续的实数(式 15),而且是每个 token 都有,密度远高于人类老师的批注。
K3 用的是多教师版本——MOPD(Multi-Teacher On-Policy Distillation)。训练时,对于给定的领域 d 和一个采样到的推理努力等级 e ∈ {low, high, max},优化由九个专家中对应的那个教师模型 πteacher(d,e) 来指导。也就是说,教师是按当前样本的领域和努力等级动态选出来的,不是九个一起平均。
给定输入查询 x 和前缀 y<t,在 yt 上求值的逐 token OPD 奖励定义为:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| yt | 学生刚写出的第 t 个 token | 「刚落下的这一个字」 |
| y<t | 它前面已经写出的所有 token | 「到目前为止写的内容」 |
| x | 输入查询 | 「题目」 |
| d, e | 领域标签 与 推理努力等级 ∈ {low, high, max} | 决定「请哪位老师来批」这一步 |
| πteacher(d,e)(yt|·) | 教师在同样上下文下,给这个 token 的概率 | 「老师自己会不会写这个字」 |
| πθ(yt|·) | 学生(参数 θ)给这个 token 的概率 | 「我刚才有多想写这个字」 |
| log(πteacher/πθ) | 两个概率的比值取对数 | 核心:老师比我更看好这个字 → 比值 > 1 → log > 0 → 正奖励;反之为负;两人一样看好 → 比值 = 1 → log = 0 → 不奖不罚 |
| sg(·) | 停止梯度算子(stop-gradient) | 「这个值只当分数用,不许梯度从这里回流」——把它冻成一个常数奖励 |
| clip(·, −Rmax, Rmax) | 把结果裁剪到区间 [−Rmax, Rmax] | 「再离谱的分数也不能超过这个上下限」 |
| Rmax > 0 | 裁剪阈值,论文说用于约束极端优势信号、稳定 RL 训练 | 安全阀。论文没有给出它的具体取值 |
补充:为什么用 log 比值,而不是直接用差 πteacher − πθ
因为概率的绝对差没有尺度感。0.9 和 0.8 差 0.1,0.1 和 0.001 也只差 0.099——但后者是「老师认为可能而我几乎完全排除」,严重得多。取比值再取对数,衡量的是相对倍数:0.9/0.8 ≈ 1.125,log 约 0.12;0.1/0.001 = 100,log 约 4.6。log 还有一个好处:它关于反转是对称的——老师概率是我的 10 倍得 +log10,我的概率是老师的 10 倍得 −log10,奖惩对称。
论文强调这是一个稠密(dense)奖励信号——每个 token 都有分,而不是整条轨迹才给一个分。因此它「无缝接进我们的 RL 框架,自然地让部分 rollout 之类的基础设施层优化也能用于长时程任务的蒸馏训练」(这一点在 8.4 的综合题里已经推过)。
一个诚实的负面结果
论文写道:他们也试过更细粒度的 top-k 蒸馏目标(即不只看学生实际写出的那一个 token,而是对比师生在概率最高的 k 个 token 上的整个分布),但「在我们的设定下没有观察到收敛速度或最终性能上的明显优势」。这是一个有价值的负面结果——它意味着「只在学生实际走过的那条路径上给信号」已经够用了。但请注意措辞里的「in our setting」(在我们的设定下):论文没有给出这个对比实验的任何数据,也没有说明 k 取了多少、在哪些任务上比的。这属于作者的经验陈述,不是被展示的证据。
在某个上下文下,学生给 token 「因此」 的概率是 0.05,对应的教师给这个 token 的概率是 0.50。设 Rmax = 4。请算出式 15 给出的奖励,并用一句话说明它在告诉学生什么。(提示:ln 10 ≈ 2.30)
变式:如果教师概率是 0.50、学生概率是 0.0001,比值是 5000,log 约 8.5,超过了 Rmax = 4。此时奖励是多少?把这种情况和上面那题对比,说说 clip 到底在防什么。
式 15 里有两个「保护装置」:sg(·) 和 clip(·, −Rmax, Rmax)。有人说「既然都是为了稳定训练,留一个就够了」。请说明这两者防的不是同一件事,并各举一个去掉它之后会出问题的场景。
变式:把 clip 换成对 log 比值做 tanh 压缩(平滑饱和而非硬截断),能不能起到同样效果?会失去什么?(想想 8.5 那个答辩里「硬阈值 vs 平滑惩罚」的讨论,结论在这里是否相同?)
8.8 MXFP4 量化感知训练:让模型住进更小的房子
为什么要量化
模型的每一个权重都是一个数,存在显存里。数越多、每个数占的位数越多,就需要越多显存卡、越贵的服务成本。论文的目标写得很直白:「降低部署时的显存占用和服务成本」。
比特(bit):计算机里最小的信息单位,一个比特只能是 0 或 1。n 个比特能表示 2n 种不同的取值。
所以「4 比特」意味着:这个权重只能取 24 = 16 种可能的值。对比一下,深度学习常用的 BF16 是 16 比特,能表示 216 = 65536 种值。从 16 比特降到 4 比特,同一批权重占的显存变成四分之一。
打个比方
原来每个权重可以是 0 到 65535 之间的任意刻度,现在只准从 16 个预设刻度里挑一个最接近的。就像把一把有六万条刻度的尺子换成只有 16 条刻度的尺子——量出来的每个长度都会有误差。
类比失效处:MXFP4 不是均匀的 16 条刻度。MX 是 Micro-scaling(微缩放)的意思:一小组权重共享一个缩放因子,组内再用 4 比特浮点表示。所以刻度是「按组自适应」的——某一组权重都很小时,这 16 条刻度就密集地铺在小数值区间。这比朴素的 4 比特整数量化精确得多。(论文只给出了格式名和引用,这里对 MX 格式的展开属于通用背景知识,不是论文内容。)
只量化「专家权重」,别的保持高精度
论文的具体方案是:把MoE 专家权重——论文说它们「主导了模型的参数内存」——量化到 MXFP4,激活用 MXFP8 计算;而所有非专家组件(注意力投影、潜 MoE 投影、共享专家、MoE 路由器)保持更高精度。
为什么偏偏是专家权重最该被量化
两个原因叠加。第一,它们最占地方——MoE(混合专家)架构里有大量专家,每个专家都是一整套前馈网络权重,加起来占了参数总量的绝大部分。压最大的那块,收益最大。第二,它们最经得起压——每次前向只有少数几个专家被激活,任何单个专家的权重误差影响面有限;而注意力投影、路由器这些组件每个 token 都要过,尤其路由器是决定「派哪些专家干活」的关键决策点,它出一点误差可能导致选错专家,错误会被放大。所以论文把它们留在高精度。这是一个典型的「把压缩预算花在收益/风险比最高的地方」的工程决策。
QAT 在整个后训练阶段进行,覆盖 SFT 和 RL,使模型「适应量化引起的精度损失」。而且论文特别点出:RL 期间 rollout 和训练共享同一套量化方案——消除了训练-推理不匹配(这一点 8.2 的测验里已经推过)。
读的时候要小心
论文在这一节没有给出任何量化前后的具体数字——既没说显存省了多少 GB,也没说精度掉了多少分,更没有「QAT vs 训练后量化」的对照。「模型适应了量化引起的精度损失」是一个定性主张。另外,「更高精度」具体是 BF16 还是 FP8,论文也没有明确写出。
某团队照搬 K3 的方案,但为了把显存压得更狠,顺手把 MoE 路由器也量化到了 MXFP4,理由是「路由器参数量很小,压了也不影响,但聊胜于无」。请指出这个决定的两个问题。
变式:共享专家(shared expert,每个 token 都会过的那个专家)也是前馈网络,结构和普通专家一样。为什么论文把它留在高精度而不是跟着一起压?(提示:想想「每个 token 都要过」这件事对误差累积意味着什么。)
8.9 草稿模型与推测解码:让小模型先猜,大模型只负责验
为什么大模型生成得慢
因为生成是一个 token 一个 token来的:要写第 100 个字,必须先把前 99 个字算完。每写一个字,都要把整个巨大的模型完整跑一遍。这个过程无法并行——不是算力不够,是依赖关系卡住了。论文说「对于服务复杂的长时程智能体模型,优化推理效率至关重要」。
推测解码(Speculative Decoding):先让一个很小很快的草稿模型一口气猜出接下来的好几个 token,再让大的目标模型把这几个 token 一次性并行验证。验证是可以并行的(因为要检查的 token 已经摆在那儿了),所以一次大模型前向就能确认好几个 token。猜对了就一次前进好几步,猜错了就从错的地方丢弃、重来。关键:这个过程是无损的——最终输出的分布和不用草稿模型时完全一样,只是快了。
K3 的草稿模型是怎么来的
K3 在预训练时就带了一个多 token 预测层(multi-token-prediction, MTP),其结构镜像一个骨干块。而 EAGLE-3 风格的草稿模型恰好是一个单解码层,结构与 MTP 层匹配。于是作者的做法是:把预训练好的 MTP 层微调成一个 EAGLE-3 风格的草稿模型,冻结目标模型,只训练草稿层和特征融合投影。
训练时按 EAGLE-3 的「训练时测试」协议,草稿被展开七步。第一步之后,最新位置的目标侧特征是拿不到的,草稿就消费自己前几步的输出——这样训练时的情形就和推理时的循环起草过程一致了。
为什么要「展开七步」而不是只训一步
因为推理时草稿要连续猜好几个 token,第二步猜的时候只能基于自己第一步的输出(大模型还没算呢)。如果训练时每一步都喂给它正确的目标侧特征,它就从没经历过「基于自己可能有错的输出继续猜」这种情形,一到推理就崩——这就是经典的暴露偏差(exposure bias)。展开七步、让它吃自己的输出,是在训练里就把推理的真实条件复现出来。
三层特征融合与那个漂亮的初始化
草稿的输入不是只看目标模型的最后一层,而是融合了低、中、高三层特征,分别取自第 1 个、第 4 个和最后一个 AttnRes 块的输出。这三份特征被拼接起来,再用一个无偏置矩阵 WE3 投影回隐藏维。
而 WE3 的初始化是 [0 0 I]——两个零块加一个单位矩阵。
自己推一遍:为什么 [0 0 I] 能保证「从不退步地开始」
先弄清形状。设隐藏维是 h。拼接后的向量 [hl; hm; hh] 是多少维?WE3 必须是什么形状,才能把它投影回 h 维?
想好了再看
三份特征各 h 维,拼起来是 3h 维。要投回 h 维,WE3 必须是 h × 3h。把它横着切成三块,每块是 h × h——这三块正好一一对应低、中、高三份特征。记号 [0 0 I] 就是说:前两块是零矩阵,第三块是单位矩阵。
现在动手乘一下:[0 0 I] · [hl; hm; hh] 等于什么?
想好了再看
分块矩阵乘法:结果 = 0·hl + 0·hm + I·hh = hh。也就是说,融合后的表示恰好等于高层特征本身,低层和中层被完全忽略。论文原话正是「使得融合表示在初始化时与高层特征 hh 重合」。
关键一问:hh 对这个草稿层来说有什么特殊之处?(回想草稿层是从哪来的。)
想好了再看
草稿层是由预训练好的 MTP 层微调来的,而 hh(最后一个 AttnRes 块的输出)正是 MTP 层在预训练时接收的输入。论文明确点出了这一点。所以在初始化时刻,这套「三层融合 + 投影 + 草稿层」的结构,在功能上和「直接把预训练好的 MTP 层拿来用」完全等价。
所以,「从不退步地开始」具体指什么?如果换一个常见的随机初始化(比如小随机数),会发生什么?
想好了再看
「不退步」指:微调的第 0 步就已经达到了预训练 MTP 层的水平,之后每一步都是在这个已经不错的起点上做增量改进——学习曲线从一个高台起跳,而不是从地板起跳。如果用随机初始化,融合表示会是三份特征的一个随机线性组合,这个东西 MTP 层从没见过,等于把预训练学到的东西全部打乱;模型要先花很多步把「忽略掉噪声、重新找回高层特征」这件事学回来,才谈得上进步。论文说它「随后逐渐学会在微调中纳入低层和中层特征」——零块的位置有梯度,会从 0 慢慢长出来,所以低中层信息不是被永久放弃,只是被推迟到模型有余力时再引入。
最后:为什么这个矩阵是无偏置(bias-free)的?如果加一个偏置项 b 并随机初始化,上面的论证还成立吗?
想好了再看
不成立。有偏置的话,初始输出是 hh + b,只有当 b 恰好初始化为零向量时才等于 hh。去掉偏置是让「初始时恒等」这个性质结构上就成立,不依赖额外约定。论文只说了 WE3 是无偏置的,没有解释原因;这个推断是本书补的,但它和论文描述的初始化意图完全一致。
式 16:直接优化「接受率」本身
推测解码到底能快多少,取决于草稿猜的 token 有多大比例被接受。论文说:无损推测采样下的加速由逐 token 接受率决定,这个接受率等于
| 符号 | 是什么 | 直觉 |
|---|---|---|
| V | 词表(vocabulary),模型所有可能输出的 token 的集合 | 「字典里的全部字」 |
| x | 词表里的某一个具体 token | 「字典里的某一个字」 |
| p(x) | 目标模型(大模型)给 x 的下一 token 概率 | 「正确答案的分布」 |
| q(x) | 草稿模型(小模型)给 x 的下一 token 概率 | 「猜测的分布」 |
| min(p,q) | 两者中较小的那个 | 「两人都同意的那部分概率质量」 |
| Σx∈V | 对词表里每个 token 都算一遍 min,然后全加起来 | 两条概率曲线的重叠面积 |
打个比方:重叠面积
把 p 和 q 想成两座并排画在同一张纸上的山(横轴是词表里的每个 token,纵轴是概率)。每座山的总面积都是 1(概率之和为 1)。Σ min(p,q) 就是两座山重叠在一起的那块阴影面积。两座山完全重合,阴影就是整座山,面积 1;两座山完全错开(草稿猜的字大模型一个都不会说),阴影面积 0。所以接受率天然落在 [0, 1] 之间,它衡量的就是两个分布有多像。
类比失效处:词表是离散的(几万个孤立的柱子),不是连续的山坡,所以严格说是「重叠的柱状面积之和」。另外接受率是每个 token 位置各算一次的,不是整句话算一次。
接下来是论文这一段最关键的洞察。既然想让接受率高,最自然的做法是让草稿去拟合目标——传统上用 KL 散度(衡量两个分布差异的标准指标)当代理目标。但论文指出:对于容量受限的草稿模型,最小化传统的 KL 散度代理并不保证最大化这个接受率。所以他们干脆直接优化基于似然的 LK 损失,也就是接受率本身的负对数:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| LLK | 要被最小化的损失函数 | 「越小越好」的那个数 |
| Σ min(p,q) | 接受率,取值在 (0, 1] | 见上表:重叠面积 |
| − log(·) | 取对数再取负号 | 把「越大越好」翻成「越小越好」:接受率 = 1 时损失 = −log 1 = 0(最优);接受率趋于 0 时损失趋于 +∞。最小化 LLK 就是最大化接受率 |
论文补充了两个实现细节:p 和 q 都在温度 1 下求值,并且不加辅助的真值交叉熵项(也就是不掺「让草稿去拟合真实下一个词」这种常规语言建模目标,纯粹对齐目标模型)。此外,草稿微调沿用后训练的 QAT 配置:MoE 专家权重 MXFP4、其输入激活 MXFP8,非专家模块保持更高精度。
证明:如果草稿模型和目标模型的下一 token 分布完全相同(即对所有 x,q(x) = p(x)),那么接受率 Σx min(p(x), q(x)) = 1,此时式 16 的损失 LLK = 0。然后回答:接受率能不能大于 1?
变式:假设词表只有 3 个 token,p = (0.7, 0.2, 0.1),q = (0.4, 0.4, 0.2)。手算接受率和 LLK。再试试 q = (1.0, 0, 0)——这个「极度自信但押错了分布」的草稿,接受率是多少?它比上一个草稿好还是坏?
综合题:论文说「对容量受限的草稿模型,最小化传统 KL 散度代理并不保证最大化接受率」。请构造一个具体例子说明 KL 和接受率会给出不同的偏好排序,并解释「容量受限」这个前提为什么是这句话成立的关键。最后,把这个洞察和 WE3 = [0 0 I] 的初始化技巧联系起来:它们体现了同一种什么样的工程思维?
「容量受限」为什么是关键前提。如果草稿模型容量无限,它可以精确复制 p,此时 KL = 0 且接受率 = 1,两个目标的最优解重合,用哪个当损失都无所谓。但草稿只有单个解码层,它没有能力同时把三万个 token 的概率都对齐——它必须做取舍。而取舍的方向,正是由损失函数决定的:KL 会逼它把宝贵的容量花在「照顾长尾、不要让任何 q(x) 变成 0」上(因为那里惩罚无穷大),而接受率只在乎「把头部那几个真正会被采到的 token 对准」。给一个必须做取舍的模型用错误的取舍准则,就是论文说的「并不保证最大化接受率」。直接优化你真正想要的那个量,别用代理,这就是式 16 的全部动机。
与 [0 0 I] 的共同思维。两者都是拒绝在中间环节上妥协、直接对准最终目标的表现:一个是「别优化 KL 这个代理,直接优化接受率」;另一个是「别随机初始化再慢慢学回来,直接从已经被预训练验证过的那个解出发」。共同点是:凡是能把「绕一圈」改成「直接对齐」的地方,就不绕——尤其在资源(草稿容量、微调预算)受限时,绕的那一圈就是纯损失。
变式:论文还说 p 和 q 在温度 1 下求值,且不加真值交叉熵项。请解释:如果掺进一个「让草稿去预测真实下一个词」的交叉熵项,会不会伤害接受率?(提示:草稿的任务是模仿目标模型,哪怕目标模型是错的。)
读的时候要小心
这一整节论文没有给出任何加速倍数的实测数字——没有说接受率实际达到了多少,也没有报告端到端的吞吐提升。「展开七步」「第 1、第 4 和最后一个 AttnRes 块」这些超参数论文也没有说明是怎么选出来的、有没有做过搜索。此外,草稿微调用了 QAT 配置,但论文没有讨论量化后的草稿模型接受率会掉多少。
本章小结
K3 的后训练是一条三段式流水线:SFT 用旧模型合成的轨迹加人工把关,造出一个「会办事」的冷启动策略;RL 把它拆成三个领域 × 三档推理努力共九个专家,各自在自己的赛道上把能力练到极限;MOPD 再用逐 token 的稠密奖励(式 15)把九个专家的本事压回同一个模型。
贯穿全程有三条暗线。第一条是吞吐与稳定性的博弈:部分 rollout 用「不等落后者」换吞吐,代价是数据陈旧,靠逐 token 正则化把更新约束在局部邻域来兜底——而 MOPD 的稠密奖励恰好让这套基础设施也能用于蒸馏。第二条是反奖励黑客:模型天然会朝「想得更久、写得更长」进化,K3 用同一个模式(冷启动估基准 × 乘子 = 硬阈值)分别堵住了推理努力(超 τ·b0 判 −1)和输出啰嗦度(超 σ·ℓ0 自动判负)。第三条是部署感知:从 SFT 第一天起就按上线的量化方案训练(MoE 专家权重 MXFP4、激活 MXFP8、非专家组件高精度),RL 的 rollout 和训练共用同一套量化,消除训练-推理不匹配;同时把预训练带的 MTP 层微调成 EAGLE-3 草稿模型,用 [0 0 I] 初始化保证不退步地起步,并直接优化接受率本身(式 16)而不是 KL 代理。
要留意的边界:本节几乎所有关键超参数(λ、τ、σ、Rmax、「更高精度」的具体格式)论文都没有给出取值,也没有提供消融实验;Figure 8 是训练期的内部曲线,纵横轴均无绝对刻度;top-k 蒸馏「没有明显优势」和 MXFP4 QAT「模型适应了精度损失」都是作者的定性陈述,本文未展示支撑数据。这些地方读的时候要把它们当作设计选择,而不是已验证的结论。
第9章 让模型「有事可做」:RL 任务合成与智能体环境
一句话导语:强化学习卡住的地方,经常不是算法不够聪明,而是没有足够多、足够难、而且机器能自动判对错的题——这一章讲 Kimi 团队怎么造题、怎么造环境、怎么防模型作弊。
学完这一章你应该能做到
- 用自己的话解释 agent harness 是什么,以及只用一套固定 harness 训练为什么会出问题
- 说清知识图谱为什么必须是有向无环图,「祖先节点上下文」在检索里起什么作用
- 照论文规则给一个 GPU 内核解答估出奖励,并说清为什么正确性是硬门槛、性能是连续分、锚点为什么定在 0.5
- 自己设计一个能骗过某个奖励函数的作弊策略,并指出论文哪一层防线挡得住、哪一层挡不住
- 指出「模拟应用」「隐藏验证器」的适用边界,以及论文没有给出证据的地方
先建立总直觉:RL 真正的瓶颈在哪
很多人以为强化学习难在算法。训练大模型时,更难的是有没有题。RL 的循环说白了就是:让模型试着做一件事 → 打分 → 分高的多做、分低的少做。这个循环要转起来,题必须多(否则很快被背下来)、必须难(太简单没有可学的信号),而且最关键——分数必须自动算得出来。靠人判,一天几千道,喂不饱一个几千亿参数的模型。
「机器能自动判对错」在论文里就是那个反复出现的词:可验证(verifiable)。整个 §4.2 从头到尾在解决同一个问题——怎么把真实世界里原本只有人才判得了的任务,改造成机器能自动判分、而且骗不过去的题。请把这句话当本章主线。
9.1 统一白盒 RL 环境:别让模型只认识一套「工具箱」
先解决一个名词。大语言模型本身只会一件事:给它一段文字,它接着往下写。它不会自己打开浏览器,也不会自己运行代码。那「AI 帮我改了代码、跑了测试」是怎么发生的?外面套了一层程序:把「你可以用这些工具」写进提示词、把模型吐出的文字解析成真正的函数调用、把工具返回的结果塞回上下文、上下文太长时还要负责裁剪或压缩。
智能体脚手架(agent harness):模型与外部世界之间的那层壳。它规定工具长什么样、系统提示怎么写、上下文怎么管理、有没有记忆、能不能派子智能体。模型只能透过这层壳感知世界,也只能透过它动手。
打个比方
模型像一个只会说话的大脑,harness 是接在它身上的手、眼睛和记事本。同一个大脑接上不同的手,能干的事和干事的方式都会变。
类比失效处:真实的手不会篡改你看到的东西,harness 会——它主动压缩、裁剪、重排上下文,决定模型「记得什么、忘了什么」。它不是被动外设,而是深度参与了模型的思考。
论文指出:用单一固定的 agent harness 训练,会让模型过拟合到某一套具体的工具 schema、系统提示、上下文管理机制或交互协议上。也就是模型学会的可能不是「怎么完成任务」,而是「怎么在这套壳里完成任务」;工具改个名字、提示换个写法,它就懵了。像一个只在某位老师的卷子格式下刷了三年题的学生,换份排版就不会做。
对策是把 harness 本身拆开,抽象成一组可配置、可组合的模块:工具接口、系统提示、上下文管理策略、技能(skills)、记忆(memories)、子智能体(subagents)等。像积木一样通过配置组合,这个环境可以实例化出主流 harness——论文点名 Kimi Code、Claude Code、Codex、OpenClaw、Hermes——也能拼出全新的。训练时,系统为不同任务组动态构造不同的 harness 配置,让模型见到的是模块的各种组合,而不是任何单一 harness 的惯例。论文还提到同一套抽象也支持跨多种任务域的 RL,为训练更通用的智能体提供可扩展基础。
「白盒」两个字
白盒的对立面是黑盒——黑盒环境你只能调用、看不见里面。白盒意味着每个部件都能拆开、替换、重组,这正是「动态构造配置」的前提。论文本身未解释这个词,此处是按上下文的合理理解。
某模型在 Claude Code 里表现很好,换到一个自研 harness(工具名不同、系统提示写法不同、上下文压缩策略也不同)后成功率大幅下降。用 9.1 的语言说明:这是什么问题?统一白盒环境怎么防住它?
变式:如果不是动态构造配置,而是固定用五套主流 harness 轮流训练,效果会有什么不同?(提示:五套仍是有限的、各有惯例的集合,模型可能学会「先识别这是哪一套,再套用对应惯例」——那是过拟合到「五选一」,不是泛化。可组合模块的空间要大得多。)
9.2 知识图谱引导的任务合成:题目从哪来
有了装题的环境,下一问是题从哪来。论文的判断很实在:后训练任务的质量和多样性,很大程度上由源材料决定。接着是两个互补的观察:由细粒度概念引导的检索能挖出专门化的、代表性不足的知识(搜「机器学习」捞回来全是入门介绍,搜一个很具体的技术名词才能捞到稀缺材料);跨多样概念采样则拓宽领域覆盖(只往一个方向钻会偏科)。要在大规模上同时控制粒度与覆盖面,Kimi 构建了一个自演化、分层组织的知识图谱,由智能体通过网页规模的探索,在知识密集领域和编码领域上持续扩展。
图谱是怎么长出来的
有向无环图(directed acyclic graph, DAG):一堆点,点之间连着带箭头的边(有向),而且顺着箭头一直走永远回不到出发点(无环)。
论文把知识图谱构建成一个 DAG,通过递归的、智能体驱动的扩展建成:从一组预定义的粗粒度种子节点开始,给每个节点分配一个智能体实例,它做多次网页搜索调查对应概念。加新节点之前,智能体先探索已有图谱,识别等价或相关概念、在合适时复用已有节点、尽量减少重复。边永远从粗概念指向细概念,不管智能体先发现哪一端。新加的节点接着又分配给智能体继续探索;当负责的智能体判定当前概念已经足够原子,这条分支停止扩展。
为什么「边永远从粗指向细」这条规则这么重要
它一口气解决两件事。其一,保证图天然无环:箭头只能从粗走到细,顺着走就是不断变细,一个概念不可能比自己更细,所以绝不可能绕回起点。这很关键——在「递归扩展、多个智能体并发加节点」的场景里,你没法每加一条边就全图跑一遍环检测。其二,保证结果与发现顺序无关:智能体 A 先发现「注意力机制」再挖到「多头注意力」,智能体 B 反过来,两条路径必须画出同方向的边。没有这条规则,图的形状取决于谁先跑到哪里——那它就不是一个稳定的知识结构,而是一份探索日志。
造好图之后怎么用?为了瞄准想要的领域与任务类型分布,系统在不同粒度层级上采样节点——可以单独采,也可以按「相关组合」一起采(图 9 中虚线圈住的一簇)。从采到的节点导出关键词,再把这些节点在图谱中祖先节点的上下文信息结合进去,一起构造网页查询;检索回来的真实世界材料被组装起来,交给一个合成智能体产出各种类型的训练任务。
为什么要带上祖先的上下文
论文只陈述了做法,没有解释原因。一个自然的解释是消歧:细粒度的词经常一词多义,kernel 在 CS/AI 分支下指 GPU 内核程序,在数学分支下可以指核函数。光拿一个词去搜可能整个跑偏;带上祖先路径,搜索引擎才知道你要哪一个。这是合理推断,非论文原文。
论文规定「边永远从粗概念指向细概念,无论智能体先发现哪一端」。(1) 这条规则为什么能保证图里不会出现环?(2) 若改成「谁先被发现,边就从谁指向另一个」,这个图会坏在哪里?
变式:论文说「一条分支在负责的智能体判定当前概念足够原子时停止扩展」。这个停止条件由智能体自己判断,说出它的一个风险以及对最终任务分布的影响。(提示:判断标准不统一,不同分支的「原子」粒度可能差很远,按层级采样就会采到粗细不齐的东西。论文未说明如何校准这个判定。)
9.3 智能体环境中的可验证问题:让模型「动手」而不是「空想」
论文给了三类代表性例子。第一类是多步复杂信息搜索:模型自己规划怎么研究这个问题、一步步从网上收集证据,产出一个可验证的答案——「可验证」这个限定决定了题目必须设计成有确定答案的形式。第二类是专业人士的真实日常工作,论文举了投资银行、数据分析、法律实务:模型要把复杂请求分解开、在沙箱里操作领域工具、在几十到几百步内完成一份交付物——评的不是模型说了什么,而是它最后做出来的东西。第三类是多步可验证的视觉推理,覆盖 STEM 问题、视觉谜题、图表理解。
为什么视觉推理必须配一个 Python 沙箱
不配会怎样:给模型一张高分辨率图表,让它读出某个数据点。它只能「看一眼然后猜」——字太小、线太密就是看不清。人类会放大、会拿尺子比、会遮住无关部分只看一块,这些动作模型原本做不了。
论文的做法是:每条视觉推理轨迹都在一个配有隔离沙箱内 Python 解释器的智能体环境里生成。模型迭代地写代码并执行,用来裁剪、缩放或变换输入图像,做精确计算,或验证中间结果;然后把执行输出——包括生成出来的图像——作为新的观测,跨多个交互步接收回来。论文给出的结论是:随着模型学会做更多图像操作、收集更多观测,它在复杂视觉推理任务上的表现稳步提升。
容易读漏的一个词
很多人把这段读成「模型写代码算数」。真正的新意在「包括生成的图像」:执行结果若是一张新图,它会被当作新观测送回模型眼前。模型不是「看一次图一路推到底」,而是看图 → 改图 → 再看 → 再改的循环;视觉从一次性输入变成了可反复交互的环境。
假设有个「简化版」:模型仍可在沙箱里写 Python 处理图像,但执行结果只能以文字形式返回(数值、统计量、文字描述),生成的图像不回传。说明它相比论文的设计损失了什么,并举一个它一定做不好的具体任务。
变式:论文强调这个解释器在隔离沙箱里。不隔离会带来哪些问题?至少说两个,并区分哪个是安全问题、哪个是训练信号可信度问题。(提示:一是执行的代码可能破坏训练机器;二是不隔离时模型可能访问到不该看到的东西——答案文件或网络——那它拿到的奖励就不再反映真实的视觉推理能力。)
9.4 内核优化任务:一堂关于「奖励怎么设计」的公开课
GPU 内核(GPU kernel):跑在显卡上的一小段程序,负责完成一个具体计算,比如两个矩阵相乘。同一个数学运算可以写出无数个内核,结果一样但速度可能差好几倍——「内核优化」就是在结果不变的前提下把它写得更快。
论文说,为强化 Kimi K3 的 GPU 内核优化能力,团队构建了一个大规模内核任务套件,从单算子内核一直到融合的巨型内核(fused mega-kernels),题源来自 Flash Linear Attention 这类高质量 GitHub 仓库。套件覆盖多样的 GPU 编程方式:CUDA、Triton、CuTe DSL、Gluon、ThunderKittens、TileLang;也覆盖广泛使用的 GPU 架构和数值格式:BF16、FP8、FP4。
奖励:正确性是门,性能是分
规则很短,但每句都有讲究:奖励同时评估正确性和性能。每个内核提供一份 PyTorch 参考实现,数值误差超过预设阈值的解答得零奖励。性能以一份专家实现为基准打分:追平专家实现得 0.5 分,越接近硬件 roofline 奖励越趋近 1。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| r | 这次提交拿到的奖励 | 模型这一步该被夸还是被批 |
| err | 解答输出与 PyTorch 参考实现之间的数值误差 | 「算得对不对」的量化 |
| ε | 预设的数值误差阈值 | 及格线;浮点计算不可能完全相等,只能给一个容差 |
| [ err ≤ ε ] | 指示函数:方括号内条件成立取 1,不成立取 0 | 开关。误差超标直接乘 0,后面再快也白搭 |
| f(speed) | 性能得分函数,论文只给了两个锚点:追平专家 = 0.5,趋近 roofline → 1 | 连续刻度,快一点就多一点分 |
| roofline | 硬件的理论性能上限,由显存带宽和算力共同决定 | 百米赛跑的物理极限,谁也跑不过 |
读的时候要小心
式 9-1 是本书为讲清结构而自拟的,论文里没有这个公式。论文只给了文字描述和两个锚点。f 在 0.5 到 1 之间怎么插值、低于专家水平时怎么给分、roofline 具体怎么算,论文都没有说明——别以为你知道了完整的奖励函数。
为什么正确性要做成「乘性门」而不是「加一项」
设想另一种设计:奖励 = 0.3 × 正确性 + 0.7 × 性能。看起来也合理。但把自己放到模型的位置想:如果我把数值精度偷偷降一档,正确性那项掉一点点,性能那项可能翻倍,总分反而更高。梯度会稳稳推着模型往「牺牲精度换速度」走——而这恰恰是论文点名的作弊手法之一:precision reduction。
乘性门没有这个交易空间:误差一旦超过 ε,整个奖励归零,再快也换不回任何分数。背后是一个价值判断——一个算错的内核,无论多快,价值都是零。奖励函数的形状必须忠实反映这个判断,否则模型学到的就是设计者没打算教的东西。
为什么锚点是 0.5 而不是 1.0
再设想把「追平专家」定为满分 1.0。后果是:模型一旦追平专家,梯度就没了——超过专家和刚好追平得分一样,它没有动力再往前走一步,你等于亲手把人类专家设成了天花板。把专家放在 0.5,含义完全不同:专家只是中点,上面还有整整一半空间留给「继续逼近硬件极限」;下半段也有意义,「跑对但比专家慢」会落在 0.5 以下,梯度方向依然清晰。0.5 把奖励曲线切成两段有意义的区间:0 到 0.5 是追上人类,0.5 到 1 是追上物理。
第三层防线:作弊检测
奖励黑客(reward hacking):模型找到一条能让奖励变高、却完全没完成设计者真正想要的那件事的捷径。它不是模型「学坏了」——恰恰相反,这说明模型很好地优化了你写下的函数,只是你写下的函数不等于你心里想的目标。
论文说:为确保奖励反映真正的优化,团队开发了一套作弊检测系统,惩罚 CUDA graph replay、input caching、precision reduction 这类奖励黑客策略,并随着开发中观察到新的作弊手法持续扩充防护。这三种手法的共同点是都在计时的口径上做文章而非真的变快:CUDA graph replay 相当于把一次执行录下来重放,计时窗口里并没真的重算;input caching 把输入对应的输出缓存起来,第二次直接查表;precision reduction 是偷偷降精度换速度。
「硬门槛」和「作弊检测」不是重复劳动
你可能会问:precision reduction 不是已被 err ≤ ε 挡住了吗?因为那个门只在测试用例上生效。一个精心构造的低精度实现完全可以在给定测试输入上误差刚好卡在 ε 以内,换一批输入就崩。硬门槛管「在这些样本上对不对」,作弊检测管「用的手段本身正不正当」——互补,不冗余。
按 9.4 的规则判断下面四份提交各能拿多少奖励并说明理由。哪一份你其实无法确定分数,为什么?
A:误差在阈值内,速度是专家实现的 0.6 倍(比专家慢)。
B:误差超过阈值,速度是专家实现的 3 倍。
C:误差在阈值内,速度恰好等于专家实现。
D:误差在阈值内,速度非常接近硬件 roofline。
变式:某份提交在测试用例上误差刚好等于阈值 ε,速度是专家的 2 倍。按字面规则(超过阈值才归零)它能拿分。该接受吗?你会怎么改规则?(提示:这正是 precision reduction 的典型形态——卡着阈值走。可以用多批随机输入重复测、或更严阈值做二次验证;这也解释了为什么需要一套独立于阈值的作弊检测系统。)
构造题。扮演一个只想拿高分的模型,设计两种能在 9.4 的奖励下拿高分、却完全没真正优化内核的策略:一种是论文点名过的,一种是论文没有点名的(你自己想)。对每种说明它骗过的是奖励函数的哪一部分,以及论文的哪一层防线会挡住它。
自己想的一种(论文未点名):把大部分工作挪出计时窗口。在内核第一次被调用或初始化阶段就把耗时的预处理、内存搬运、索引构建全做完并存起来,让被计时的那次调用只剩收尾。输出依然正确,过得了门;测出的时间也确实短。它与 CUDA graph replay 属同一家族——都是让「被测的那段时间」不包含真正的计算。哪一层挡它?静态的三条规则未必覆盖得到,能覆盖它的正是论文那句「随着开发中观察到新的作弊手法持续扩充防护」。这也说明防作弊的本质是持续的军备竞赛,不是一次能写完的检查清单。
(自评要点:你的第二种策略只要满足「输出正确 + 测出的时间短 + 真实计算没变快」三条,就是合格的构造。)
变式:换个方向——去骗正确性。在只知道「有一份 PyTorch 参考实现和一个误差阈值」的前提下设计一种策略。(提示:若模型能猜到测试输入的分布,就可以只在那类输入上做对,比如对特殊结构的输入走早退路径。这类作弊靠加大测试输入的随机性和覆盖面来防,属于测试集设计问题而非奖励函数问题——论文对内核任务的测试输入如何生成没有说明。)
9.5 个人助理任务:造一个「活的」世界
前面的任务大多是「给个输入、做一件事、判一次分」。个人助理任务要的是长时程——模型得在一个持续变化的环境里待很久。第一个难题是拿什么当环境:直接接真实的 Gmail、Slack 不行,真实 API 有速率限制、会失败,更要命的是不可复现——同一个任务跑两遍,收件箱内容已经变了,RL 没法比较两次尝试的好坏。
论文的做法是为广泛使用的应用开发真实的模拟实现(realistic mock implementations),点名 Gmail、Notion、Slack、Canvas。它们保留真实对应物的核心语义,同时支持可复现的、大规模的交互,不依赖外部 API、不受速率限制。在这些模拟应用之上,团队设计了受真实专业工作流启发的复杂任务,场景涵盖人力资源、法律服务、金融。每个任务里智能体在一个持续、演化的环境中跨多个模拟日运行,遭遇分布在各应用之间的、数十个相互依赖的事件。
「相互依赖」这四个字是本节重点
如果几十个事件互不相干,这就不是长时程任务,只是几十个短任务打了个包,模型可以孤立处理。「相互依赖」意味着:周二在 Slack 收到的一条消息会改变周四那封邮件该怎么回;在 Notion 里记错一条信息,后面基于它做的所有判断都会跟着错。这才逼出真正的长时程能力——模型必须维持一个跨越多天、跨越多个应用的一致世界模型。
规模有多大?论文说单次 rollout 可能涉及多达数千次工具调用和数百万 token 的上下文。给点尺度感:数千次工具调用,换成一个人手动点鼠标、每次十秒,就是连续好几个小时不停手;数百万 token 的上下文,粗略地说相当于把好几部长篇小说的文字量全部塞进一次任务的记忆里。
判分上,论文说每个事件都带有自己的评估准则,由确定性规则或基于 LLM 的评估器判定——不是最后给一个总分,而是几十个事件各有判据。另外两个细节:初始工作区由智能体自己搜网络找参考材料、再转化成一个连贯的、与任务相关的环境来构建(连「初始收件箱里有什么邮件」都是自动造的);团队还扩展了 RL 框架来支持这类活环境(living environments),建模复杂的事件流以及由此引发的世界状态转移。
论文让每个事件带自己的评估准则。考虑一个替代方案:只在任务结束时看最终交付物给一个总分。在「数十个相互依赖的事件、数千次工具调用」的设定下,这个替代方案会遇到什么困难?反过来,逐事件打分本身有没有它自己的问题?
但它有自己的代价:既然事件相互依赖,把它们拆成几十个独立判据打分,天然会丢掉「跨事件一致性」这一维度——模型可能每个事件单独看都合格,整条线索上却前后矛盾(在两个应用里对同一个人做了冲突的处理)。论文没有说明是否有跨事件的一致性判据,也没说明几十个事件分数怎么聚合成一个奖励。
变式:论文说部分事件由「基于 LLM 的评估器」判定。相比确定性规则,用 LLM 当裁判在超长轨迹里会引入什么新风险?(提示:裁判本身会犯错,而且错误可能是系统性的——比如偏爱格式漂亮、篇幅长的输出。这种偏好一旦稳定存在,模型就能优化它,又变成一种奖励黑客。论文没有讨论 LLM 评估器的可靠性。)
答辩:如果我是审稿人
你们用 Gmail、Notion、Slack、Canvas 的模拟实现训练,然后声称训练出了「个人助理」能力。可模拟版和真货差得远:真实 Gmail 有速率限制、有网络超时、有权限错误、有分页边界,UI 还会变。模型在一个「工具调用总是成功、语义永远干净」的世界里练出来的策略,凭什么能迁移到真实应用?你有任何 mock 到 real 的迁移证据吗?
参考防守(先自己组织语言再看)
先承认最硬的一击:论文确实没有给出任何 mock → real 的迁移实验证据。这一段全文只陈述设计,没报告任何对照结果。这个批评站得住,不该硬辩。
能守住的有三条。第一,用真实 API 训练在工程上不可行,不是「懒得做」。论文明写模拟实现的目的是「支持可复现的、大规模的交互,不依赖外部 API、不受速率限制」。RL 需要同一任务反复跑成千上万遍并比较不同轨迹的优劣,而真实收件箱每跑一次状态就变,两次尝试根本不可比——这不是效率问题,是方法论上跑不通。
第二,论文保留的是「核心语义」而非「表面接口」。邮件有发件人收件人、任务有状态流转、频道消息有时间顺序——这些语义在真假 Gmail 之间一致,而速率限制和 UI 细节是会变的表层。模型要学的是前者。
第三,抗过拟合的压力在别的维度上施加了:9.1 的动态 harness 配置和 9.7 的多 scaffold rollout,都是为防止模型绑死在某套接口形式上,同一思路在这里部分适用。
但要诚实划边界:第三条只是「同源思路」,论文并没说个人助理任务用了多样化的 mock 变体。而审稿人问的「工具调用总是成功」属于失败处理能力——如果模拟实现不产生超时、限流、权限错误,模型在这个维度上确实没被训练到。这是真实的、论文未回应的空白。合理结论是:这套设计解决的是「长时程、多应用、状态演化」的训练问题,不等于解决了真实环境的鲁棒性问题。
9.6 自主执行任务 AET:把裁判和选手隔开
自主执行任务(Autonomous Execution Tasks, AET):论文提出的一种环境范式,通过验证在环(verify-in-the-loop)的优化来训练长时程智能体智能。
每个 AET 任务指定五样东西:初始状态、受约束的目标、基于工具的动作空间、执行预算、一个独立的验证器。关键在智能体看不到什么:它只看到目标、上下文、约束和验证接口,没有参考轨迹,也没有预定义流程。任务分解、工具选择、规划、错误恢复、什么时候该停——全得它自己来,这和「照一份标准操作手册执行」是完全不同的能力要求。
奖励的措辞很关键:奖励建立在验证器对「最终环境状态」的评估上,而不是智能体自我报告的完成情况。这句在防一个很具体的失败模式——模型干了一半然后在输出里写「任务已完成」。看最终环境状态,就是要求去世界里查:文件到底建了没有、系统到底跑起来没有。
论文说团队设计了多种类型的验证器以支持多样环境,包括黑盒系统复现(Figure 10)、定量因子发现、税务审计。在每种环境里,智能体迭代地提交解答、接收验证器反馈、改进策略,训练出一个通用循环:假设 → 行动 → 分析反馈 → 适应。
防奖励黑客的三重设计
有验证器就一定有人想骗它。论文给的三条措施,各自堵的漏洞不同。第一,把智能体与验证器隔离:堵最直接的一条路——验证代码和答案若摆在智能体能访问的地方,它可能直接去读判分标准甚至改判分结果。第二,把提供诊断反馈的「公开验证器」与评估留出场景的「隐藏验证器」配对:公开验证器给的是有信息量的反馈,告诉你哪里不对,这是学习必需的;但正因为有信息量,它也能被当成「神谕」反复查询,试探出判分规则,最后做出一个专门讨好它的东西。隐藏验证器评估的是留出场景,模型训练中从没见过,没法针对它调优——这跟训练集和测试集要分开是同一个道理:公开验证器是练习册答案,隐藏验证器是期末考卷。第三,在有限的提交预算下施加基于惩罚的奖励:堵「暴力试探」——提交不限次、错了不扣分,模型就能用海量提交去二分逼近判据;加上预算上限和错误惩罚,每次提交都有代价,「随便试」就变成一个划不来的策略。
读的时候要小心
这四个数字是论文自述的评测结果,来自 Kimi 团队自己设计、自己实现、自己跑的任务和验证器。Kimi K3 拿满分 1.000 很显眼,但这既是它自家的题也是它自家的裁判,跨模型比较的说服力要打折扣。
另有一处论文没有说明:横轴是「归一化的执行器工具调用进度」。归一化意味着每个模型的进度都被拉伸到 0–100%,所以曲线只反映各自在自己预算内的推进节奏,不能直接读出「谁用的工具调用次数更少」。论文没给各模型的绝对步数。
构造题。设想一个被削弱的 AET:只有公开验证器(每次提交都返回详细诊断反馈),没有隐藏验证器,也没有提交次数限制和惩罚。设计一个能刷到接近满分、却并没有真正解决问题的策略,并说明论文的三条防线各自会怎样破坏它。
三条防线怎么破坏它:(a) 智能体与验证器隔离——挡住更粗暴的版本(直接读验证代码或改判定结果),隔离后你只能黑箱试探,成本高得多。(b) 公开 + 隐藏配对——直接摧毁收益:隐藏验证器跑的是留出场景,你的硬编码拼凑物从没针对它调过,一测就露馅。(c) 有限提交预算 + 惩罚性奖励——摧毁可行性:逆向工程需要大量提交,一旦每次提交都有成本、次数还有上限,「先交空壳探路」这种开局本身就是亏的,模型被逼着在提交前先把功能真正做出来。
注意分工:(a) 提高作弊的准入门槛,(c) 提高作弊的单位成本,(b) 让作弊即使成功也拿不到分——堵的是不同环节。
变式:改成「有隐藏验证器,但它和公开验证器检查的其实是同一批规则,只是换了输入数据」。还挡得住吗?(提示:挡不住多少。隐藏验证器的价值来自它评估留出场景——只换数据不换规则,针对规则的硬编码依然通过。可见「隐藏」本身不是关键,覆盖到公开验证器没覆盖的情况才是关键。论文只说隐藏验证器评估 held-out 场景,未说明两组验证器在规则层面差异多大。)
答辩:如果我是审稿人
你们说隐藏验证器能缓解奖励黑客。但隐藏验证器本身也只是一段固定的程序,而且奖励最终还是从它那里流回模型。只要 RL 跑得够久、梯度信号够多,模型迟早会把隐藏验证器的偏好也拟合掉——你不过是把奖励黑客推迟了几千步,不是消除了它。你怎么辩护?
参考防守(先自己组织语言再看)
先承认核心论点是对的:只要奖励来自某个具体的验证器,它原则上就是可被优化的目标,「彻底消除奖励黑客」在这个框架里做不到。论文自己的用词也很克制——是 mitigated(缓解)而不是 eliminated(消除)。作者留了余地,我们不该替他们把话说满。
但「推迟」和「消除」之间不是非黑即白,防守的关键在于拟合一个目标需要多少信息。
第一,隐藏验证器的信息带宽被刻意压到极低。公开验证器给诊断反馈——「哪一项失败了、为什么」,信息量大,几十次就能摸清规则;隐藏验证器不提供诊断,只在留出场景上出分。靠一个标量反推规则,需要的样本量高好几个数量级。
第二,有限提交预算与惩罚性奖励把这个「高几个数量级的样本量」变成了实打实的成本。三条措施是配套的,单看任何一条都更弱。审稿人的攻击隐含假设「梯度信号够多」,预算限制正是在攻击这个前提。
第三,留出场景可以持续更换。9.4 明说作弊检测会「随着开发中观察到新手法持续扩充」,透露出团队把防作弊当作持续攻防而非一次交付的静态防线。拟合一个会更新的目标,难度和拟合静止目标完全不同。
最后诚实标注空白:论文没有给出任何关于隐藏验证器被攻破率的数据,也没报告「训练到多久后开始出现针对隐藏验证器的过拟合」这类实验。上面三条都是机制论证,不是实证。审稿人若坚持要证据,这一击接不住。
9.7 网页开发任务:两种裁判一起上
最后一类是网页开发。论文说这是一套专家策划(expert-curated)的任务,覆盖典型场景,两头都很宽:输入从一行场景描述到多段规格说明;产物涵盖网站、交互游戏、3D/WebGL 场景、数据可视化、SVG、全栈应用。执行方式呼应 9.1:每个任务在容器化沙箱里运行,并在多样的 agent scaffold 下 rollout 而非单一固定 harness,以促进跨 scaffold 的泛化。这里的 scaffold 和 9.1 的 harness 是同一件事的不同叫法——同一思路在论文里出现两次,说明团队把「不要绑死在一套壳上」当成了通用原则。
网页产物很难用单一标准判:既要功能对,又要看起来对,还得是真写了而不是装样子。所以奖励由两部分组成。确定性检查(deterministic checks):功能性地测试应用行为;对那些要复现一个参考的任务,还打结构相似度和像素级相似度的分——机器算的,客观可复现,但只能覆盖能写成规则的东西。模型评判(model judging):由一个内部奖励模型来做,它用其他模型执行源码审查,并实际查看和交互输出的产物——覆盖「代码写得好不好」「页面用起来对不对」这类写不成规则的判断。还有一条硬规则,和 9.4 的乘性门同一套路:当项目构建失败、运行报错、或者是「假装实现」而非真正实现产物时,奖励被清零。
「假装实现」为什么必须单独列出来清零
前两种(构建失败、运行报错)机器一测就知道。第三种微妙得多:程序能跑、页面能开、看着像模像样,但底下是空的——按钮点了没反应、数据是写死的、参考图直接当背景贴上去骗像素相似度。
为什么这种情况一定会出现?因为奖励里有「像素级相似度」这一项。任何只看输出长什么样的指标,都能被一个「只把样子做出来、不把功能做出来」的解答攻破——这是这类指标的结构性弱点,不是偶发。所以必须在指标之外加一条覆盖全局的清零规则,而且必须靠模型评判那一半去发现它:只有真去审代码、真去点一点,才分得清「实现了」和「装作实现了」。这也解释了为什么两种裁判必须同时存在——确定性检查客观但盲,模型评判灵活但主观,各补对方的洞。
综合题。本章有三处设计看似在做不同的事:(a) 9.1 训练时动态构造不同 harness 配置;(b) 9.7 在多样 agent scaffold 下 rollout;(c) 9.6 用隐藏验证器评估留出场景。它们防的是同一类失败吗?把三者统一到一句话里。第二问:既然有了这些设计,为什么 9.4 的作弊检测系统还必须「持续扩充」而不能一次设计完?
第二问:因为防御性质不同。(a)(b)(c) 都是结构性防御,靠「让空间足够大 / 让规则不可见」消除捷径,不需要预先知道模型会怎么钻空子。而 9.4 的作弊检测是列举型防御——一份具体的禁止行为清单。列举型防御天然只能挡住已经被想到的手法,清单之外一律漏过;而模型在 RL 里会持续搜索奖励更高的行为,等于在不断产生新手法。所以论文才写「随着开发中观察到新的作弊手法持续扩充防护」——这是一场攻防不对称的军备竞赛:防守方只能事后补,进攻方(优化过程)自动地、不知疲倦地找洞。要根治只能把奖励做成结构上不可 hack 的形式,但对「性能有多快」这种必须实测的量,目前做不到。
变式:9.7 的奖励里有「像素级相似度」,9.4 的奖励里有「实测速度」。这两个指标有一个共同弱点,指出来,并说明为什么两节都必须额外加一条「清零/惩罚」规则。(提示:两者都是对结果的间接测量,都能在不完成真实工作的前提下被做高——像素相似度可以贴图,实测速度可以在计时口径上做手脚。凡是间接测量,就需要一条独立于该测量的规则兜底。)
本章小结
七件看似不相干的事,回答的是同一个问题:怎么把真实世界里原本只有人才判得了的任务,改造成机器能自动判分、而且骗不过去的题。9.1 造装题的壳(可组合 harness,动态换配置);9.2 造题源(自演化的有向无环知识图谱);9.3 造能动手的任务(隔离沙箱,图像也是观测);9.4 造能量化的奖励(乘性硬门槛加连续分,专家锚在 0.5);9.5 造活的世界(模拟应用,数十个相互依赖事件逐个打分);9.6 造独立的裁判(只认最终环境状态,隔离+公开/隐藏配对+有限预算);9.7 造多维度的评分(确定性检查与模型评判互补,假装实现即清零)。
只带走两句话:其一,硬约束用乘法,软目标用连续分,锚点放在「已知可达但不是极限」的地方。其二,凡是间接测量都会被优化过程找到漏洞;防御要么靠让测量装置本身足够多样和不可预知(结构性),要么靠持续更新的禁止清单(列举型),后者永远慢一步。
本章需要存疑的地方
论文在这一节主要陈述设计,几乎没有对照实验。除了 Figure 10 的四个完成度分数(且是论文自述、自家任务自家裁判),本节没有任何消融或对比数据支撑「动态 harness 配置确实提升了跨 harness 泛化」「模拟应用上训练的能力确实迁移到了真实应用」这类关键主张。读的时候要把「他们这样设计」和「这样设计被证明有效」分开。
第10章 基础设施 I:让 KDA 跑得动,让 2.8 万亿参数装得下
一句话导语:前面几章讲的都是「模型该长什么样」,这一章回答一个完全不同的问题——这么大的模型、这么长的序列、这么怪的注意力,怎么才能在几千张显卡上真的跑起来,而且跑得不浪费。
学完这一章你应该能做到
- 用自己的话说清 SM、kernel、显存、带宽、通信开销这几个词分别指什么,以及数据并行 / 张量并行 / 流水线并行 / 专家并行 / 上下文并行分别在「切」什么
- 解释 KDA 那个固定大小的循环状态为什么既是天大的好消息(好传、好复用)又是天大的坏消息(难并行)
- 亲手构造一个反例,说明「各卡各自从零算、最后求和」这套老办法为什么对 KDA 不成立
- 读懂式 17,说清 KCP 为什么只需要一次固定大小的 all-gather,以及这一点为什么是决定性的胜利
- 算出 MoonEP 的通信缓冲区相比常规方案省了多少倍,并说清「完美均衡」这个词到底均衡了什么、没均衡什么
- 指出 §5.2.2 六种省显存手段各自的代价,说明「省显存」从来不是免费的
10.1 开工前:把显卡的常识补齐
这一章是全书离「数学」最远、离「工程」最近的一章。如果你对显卡内部一无所知,后面每一段都会像天书。所以我们先花点篇幅,把六个词讲清楚。这六个词讲完,你就有资格读懂剩下的内容了。
流式多处理器(Streaming Multiprocessor, SM):一张 GPU 内部并不是「一个超快的大脑」,而是一百多个小计算单元的集合,每个小单元就叫一个 SM。它们各干各的,互不等待。所以 GPU 的快,不是「单件事做得快」,而是「同时做一百多件事」。
不补这个常识会怎样
论文里反复出现一句话:「大部分 SM 是闲着的」(leaving most SMs idle)。如果你不知道 SM 是什么,这句话读起来只是一句抱怨;知道之后你就明白,它说的是「你花钱买了 132 个工人,结果只有 8 个在干活,另外 124 个在旁边看着」。整个 §5.1 的所有设计,目标就是把那 124 个工人叫醒。
打个比方
GPU 像一个有一百多条流水线的巨型工厂,但它有个怪癖:所有流水线必须干同一种活,而且每条线上的工件数量最好一样多。给它一万个同样的零件,它一秒钟做完;给它一个零件让它连做一万次、每次都要等上一次的结果,它比一台普通电脑快不了多少。论文里那句「GPU 偏好又宽又均匀的并行」(wide, uniform parallelism),说的就是这个怪癖。
类比失效处:真实工厂里工人可以临时改做别的活,GPU 的 SM 在同一个 kernel 里做不到——一个 kernel 启动时就定死了要干什么、切成多少块。这正是为什么论文要为「训练/预填充」和「解码」分别写不同的 kernel,而不是写一个万能 kernel 自动适应。
内核(kernel):一段专门写来在 GPU 上跑的小程序,是 GPU 干活的最小任务单位。CPU 说一句「启动这个 kernel」,GPU 上成千上万个线程就同时开始跑同一段代码。融合内核(fused kernel)指的是把原本要分好几次启动的多个 kernel 合并成一个,省掉中间结果来回搬运的开销。注意别把它和「操作系统内核」搞混,是同一个英文词,完全不同的东西。
显存(memory / HBM)与带宽(bandwidth):显存是显卡上能装东西的容量,单位是 GB,决定「装不装得下」;带宽是数据在显存和计算单元之间搬运的速度,单位是 GB/s,决定「搬得快不快」。这两件事经常被初学者混为一谈,但它们造成的是完全不同的两种失败:容量不够会直接崩溃报错(out of memory, OOM),带宽不够只是变慢。§5.2.2 整节六个技术,全部在解决容量问题;§5.1 的 FlashKDA 则主要在解决「SM 闲置」和搬运开销。
通信开销(communication overhead):一张卡装不下,就得用几千张卡;几千张卡就得互相传数据。卡与卡之间传数据比卡内部读显存慢一到两个数量级,而且传数据的时候计算单元往往在干等。所以大规模训练的核心矛盾从来不是「算得快不快」,而是「传得少不少、能不能一边传一边算」。重叠(overlap)这个词在本章会出现十几次,指的就是「让通信和计算同时进行,用计算的时间把通信的时间盖住」。
五种「并行」分别在切什么
模型大到一张卡装不下,就必须切开分给多张卡。切法不止一种,而 K3 的预训练把五种切法同时用上了。它们切的对象完全不同:
论文说:张量并行把注意力头切到不同设备上,但「从不缩短递推」(never shortens the recurrence),所以在纯 TP 部署下预填充一条超长序列时,每个 rank 只持有几个头,大部分 SM 是闲的。请用 SM 的概念解释这句话:为什么「只持有几个头」就会导致 SM 闲置?
变式:如果换成普通的 softmax 注意力做预填充,纯 TP 下会不会有同样的「SM 大量闲置」问题?为什么?(提示:softmax 注意力在 token 维度上是不是串行的?)
10.2 §5 开头:三个挑战同时撞在一起
论文开篇一句话点明了 K3 基础设施的处境:K3 把三个很少同时出现在一个模型里的系统挑战凑齐了——
- 混合 KDA 注意力:一个带固定大小循环状态的、串行的注意力形式,跟 GPU 的脾气正好相反;
- 3T 级的稀疏多模态训练与推理:论文用「3T 级」(3T-class)指代模型规模量级,而且是原生多模态的,图像视频和文本混在一起训;
- 百万 token 的智能体工作负载:一次任务的上下文能长到一百万个 token。
给「3T 级」一点尺度感
3 万亿个参数是什么概念?就算每个参数只用 1 个字节存(FP8),光是权重就是 3 TB;用 2 个字节存(BF16)就是 6 TB。而一张主流训练卡的显存通常在几十 GB 量级。也就是说,光把模型参数放下就需要上百张卡,这还没算训练时必须额外保存的梯度、优化器状态和激活——这三样加起来往往比参数本身还大好几倍。这就是为什么 §5.2.2 要用整整一节、六种技术来抠显存。(这里的换算是为了给你尺度感,不是论文给出的数字。)
论文的说法是:基础设施与这些挑战在整个模型生命周期上协同设计(co-designed across the model lifecycle)。这句话不是套话,它对应四个具体阶段,而且这四个阶段正好构成本章和下一章的骨架:
| 阶段 | 论文的原话要点 | 在哪一章讲 |
|---|---|---|
| 架构层面 | 高性能 KDA 内核 + 上下文并行,让循环形式在设备内和跨设备、在训练和推理时都高效 | 本章 10.4、10.5 |
| 预训练期间 | 均衡的专家执行、降低的内存占用、通信重叠的调度,维持大规模下的高利用率 | 本章 10.6–10.9 |
| 1M token 智能体 RL 期间 | 分层状态管理 + 可恢复沙箱执行,跨迭代保存长轨迹 | 第11章 |
| 生产服务 | 状态感知的 KDA 前缀缓存、专用推理内核、缓存与预算感知的调度,把效率转化成可预测的服务 | 第11章 |
注意最后一格里「可预测」(predictable)这个词。工业界部署最怕的不是慢,是忽快忽慢——用户请求的响应时间如果方差极大,产品体验就崩了。论文把整条基础设施链条的终点定在「可预测的生产服务」,说明他们的目标函数不只是吞吐量。
论文说 KDA 的固定大小状态「便宜、易于传输和复用」(cheap to transfer and reuse)。请指出这一条性质分别在上表的哪几个阶段被利用了,并说明在每个阶段它带来的具体好处是不同的。
变式:反过来问——KDA 状态的「串行」这条性质,在这四个阶段里分别造成了什么麻烦?(提示:论文明说解码阶段的挑战与训练/预填充不同,放到 §5.4.2 单独讲,也就是本书第11章。)
10.3 §5.1 的核心张力:一笔交易,两面后果
整个 §5.1 建立在一句话上,值得逐字读:KDA 用一个固定大小的循环状态取代了 softmax 注意力那个不断增长的键值缓存,代价是它的串行形式给并行执行带来困难,换来的是一个便宜、易于传输和复用的固定大小状态。
这是一笔交易,而不是一次纯粹的改进。交易的两面是:
- 坏的一面(第一个性质):状态是串行更新的,第 t 步必须等第 t−1 步。GPU 讨厌这个。
- 好的一面(第二个性质):状态大小固定,不管前面读了 1 万还是 100 万个 token,它都是那么大一块。便宜、好传、好存、好复用。
论文接下来的所有设计,一句话概括就是:解决第一个性质带来的问题,同时在两个执行层级上把第二个性质的好处吃干榨净。两个层级分别是——设备内的融合内核(10.4),设备间的 KDA 上下文并行(10.5)。这个「两个层级」的框架请记住,它是 §5.1 的目录。
先把式 1 抄回来,后面 10.5 全靠它:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| St | 读完第 t 个 token 之后的循环状态矩阵,形状 dk×dv | 模型到目前为止的「记事本」,大小固定,写满了就得挤掉旧的 |
| Mt | 第 t 个 token 决定的转移矩阵,形状 dk×dk | 「在写新东西之前,先按这个规则把整本记事本改写一遍」。本章的一切困难都来自它 |
| kt, vt | 第 t 个 token 的键向量和值向量 | 「要往记事本哪个位置写」和「写什么内容」 |
| βt | 第 t 个 token 的标量写入强度 | 这一笔写得多用力 |
| Diag(αt) | 把向量 αt 摆到对角线上做成的对角矩阵 | 逐通道的遗忘门:记事本每一行按各自的比例淡去 |
| I | 单位矩阵 | 「什么都不改」的那个矩阵 |
一个必须现在就纠正的误解
很多人看到 Mt 里有 I,就以为它「基本上等于不变,只是稍微修一修」。不是的。Mt 是一个货真价实的矩阵,把它乘到 St−1 上会把整个状态彻底重新混合一遍。更要命的是,矩阵乘法不满足交换律,所以 M2M1 ≠ M1M2——token 的先后顺序被牢牢锁死在这串乘积里。10.5 那道构造反例题,根子就在这里。
下面这道题请务必做完再往下读——10.5 全靠它撑着。
取一个具体的极端情形:设 αt 的每个分量都等于 1(完全不遗忘),βt = 1,且 kt 是一个长度为 1 的向量(即 kt⊤kt = 1)。请写出此时的 Mt,并说清它作用在传入状态 St−1 上到底干了什么。这跟「基本等于不变,只是稍微修一修」差多远?
所以 Mt 是一个投影:它把状态里沿着 kt 这个方向的成分彻底抹成零,其余方向完全不动。这离「稍微修一修」远得不能再远了——它是把记事本上「kt 这一栏」原有的内容整条擦干净,然后式 1 的第二项 βtktvt⊤ 再往这条空栏里写上新内容。
这正是「delta 规则」这个名字的来历:先删旧的、再写新的,而不是简单叠加。也正因为它会删除东西,「各段各自从零算再求和」才必然失败——求和法只会往上叠,永远表达不了「删除」这个动作。
顺带说明为什么一般情况下更复杂:当 βt 不等于 1 时它是部分擦除,当 Diag(αt) 不是 I 时还要叠加一层逐通道的整体淡化,但「会删东西」这个本质不变。
变式:如果 βt = 0,Mt 变成什么?此时 KDA 的更新退化成哪种更简单的注意力形式?(提示:把 βt = 0 代进式 1 的两项,看看还剩什么。)
10.4 分区制的内核:为每种跑法单独写一套
论文 5.1.1 的第一句话就是整节的论点:KDA 状态的串行依赖与 GPU 偏好「又宽又均匀的并行」相冲突,而且这个冲突在每一种执行区制里表现为不同的瓶颈,所以每种区制都要专门的内核(a dedicated kernel for each regime)。
什么叫「执行区制」(regime)?就是模型跑起来的几种不同模式:训练(一次喂进整段文本,要算前向和反向)、预填充(prefill,推理时先把用户给的长提示词整段读进去)、解码(decoding,之后一个 token 一个 token 地往外吐)。这三种模式下「有多少活可以同时干」完全不同,所以一套 kernel 不可能都伺候好。
训练与预填充:分块内核 FlashKDA
KDA 的分块形式(chunkwise form)是这样干的:把长序列切成一块块(chunk),块内并行、跨块串行——因为循环状态必须一块一块地往下传。
论文指出的问题是:天真地执行时,「块内并行计算」和「跨块串行传播」这两个阶段是交替进行的,而在串行传播的那段时间里,SM 全在闲着。这就是典型的「一百多条流水线,轮流只有一条在动」。
FlashKDA 是论文给出的解法:一个基于 CUTLASS(英伟达的高性能矩阵运算模板库)的分块内核,把块内计算与跨块状态传播重叠起来。具体做法是把工作分解成token 并行的阶段和一个 head 并行的递推,两者各自独立调度、独立调优。论文称它大幅超过 Triton 参考实现。
另外两个工程细节值得记:FlashKDA 同时服务训练和推理预填充(一套 kernel 覆盖两种区制,因为这两种区制的并行结构其实是一样的),并且作为 flash-linear-attention 库的一个后端被自动分派——也就是说用户调库时不需要手动选择,库会自己判断该用哪个后端。
为什么要「各自独立调度和调优」
因为这两条通路的性能瓶颈不一样:token 并行那部分是典型的「宽而均匀」的大矩阵乘法,调优目标是喂饱所有 SM;head 并行的递推那部分并行度天生就小(只有头数那么多),调优目标是尽量减少每一步的延迟。如果把它们塞进同一个调度框架,就只能按一套参数来配,两边都配不好。拆开之后,各自按自己的瓶颈调,然后在时间上叠起来——这就是「重叠」两个字的全部含义。
长上下文预填充:设备内的上下文并行
这是 10.1 那道题的答案的正面版本。问题回顾:纯 TP 部署下预填充超长序列,每个 rank 只有几个头,大部分 SM 闲置。
论文给出的关键观察是一句需要慢慢咀嚼的话:每个片段的状态转移可以独立于传入状态来求值,之后再精确地复合(the state transition of each segment can be evaluated independently of the incoming state and composed exactly afterward)。
翻译成大白话:你要算第 5 段读完之后状态变成什么样,确实得知道第 4 段结束时状态是什么。但是,「第 5 段这批 token 会对状态做什么样的改造」这件事本身,跟「进来的状态是什么」无关——那批 M 矩阵是由第 5 段自己的 token 算出来的,跟前面读了什么毫无关系。所以你完全可以在不知道传入状态的情况下,先把「改造规则」算出来,等传入状态到了再套上去。而且这个「套上去」是精确的,不是近似。
基于这个观察,论文用了一个自动的 SM 级上下文并行规划器:把序列切到单个 rank 的各个 SM 上,并行求出各片段的转移,再合并起来恢复每个片段的精确状态。论文特别强调:与 5.1.2 的跨设备 KCP 相对照,这种并行完全在设备内,不产生任何跨设备通信。
至于解码区制,论文说它面临与训练/预填充完全不同的挑战,放在 §5.4.2 详细讨论——那是本书第11章的内容。原因不难猜:解码时一次只处理一个 token,连「块内并行」都没有了,瓶颈从「SM 闲置」变成了别的东西。
5.1.1 的 SM 级上下文并行和 5.1.2 的 KCP 用的是同一个数学观察,但论文把它们分成两节讲。请说清:这两者在「付出的代价」上有什么本质区别?如果 SM 级 CP 零通信这么好,为什么还需要 KCP?
变式:如果某天出现一种新硬件,单卡显存大到能装下百万 token 的全部激活,KCP 是不是就没用了?(提示:想想训练时一个 batch 里有多少条序列,以及总吞吐量的需求。)
10.5 KDA 上下文并行(KCP):本章最难的一节
做好心理准备,这一节要慢慢走。但走完之后你会发现,它其实只有一个想法,而且这个想法在 10.4 末尾已经见过了。
第一步:为什么线性注意力做上下文并行「本该」很便宜
论文开门见山地对比:上下文并行的通信开销在 softmax 注意力和线性注意力之间有本质区别。
- softmax 注意力:要求各 rank 交换键值块,而这些块的大小随序列长度增长。序列越长,要传的东西越多。
- 线性注意力:把先前的上下文装在一个固定大小的循环状态 S ∈ ℝdk×dv 里。序列多长都是这么大一块。
第二步:先前方法的老办法,以及它为什么对 KDA 失效
论文说,先前的上下文并行方法利用的是普通线性注意力的「可加递推」:在每个 rank 上计算本地 token 从 S = 0 出发生成的状态,然后把先前各 rank 的这些状态求和,就得到传入状态。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| S̃[j]Tj | rank j 上,从 S = 0 出发、读完它本地全部 Tj 个 token 后得到的状态 | 「假装前面什么都没读过,光看我这一段能记住什么」 |
| 波浪号 ~ | 论文的约定:加了波浪号就表示「从 S = 0 起算的对应量」 | 见到波浪号就想「这是纯本地的、可以马上算出来的」 |
| Tj | rank j 上本地 token 的个数 | 这一段有多长 |
为什么普通线性注意力可以这么干?因为它的更新是纯粹的加法:新状态 = 旧状态 + 这一步写入的东西。加法满足结合律和交换律,所以「每段各自从零算,最后加起来」跟「从头顺序算一遍」结果完全一样。
但论文紧接着说:这种直接求和对 KDA 不成立(is insufficient for KDA)。理由就写在式 1 里——
把「为什么求和不行」讲到最直白
KDA 的更新是 St = MtSt−1 + βtktvt⊤。注意那个 Mt:KDA 的 delta 规则在加上当前写入之前,先把一个依赖 token 的矩阵作用到传入状态上。
用记事本的比喻:普通线性注意力是「在记事本后面追加一行」——你先记了什么、我后记了什么,谁先谁后无所谓,最后拼起来就行。KDA 是「每写一行之前,先按当前这一行的规则把整本已有的内容涂改一遍」。
后果是致命的:一段 token 对状态的效果,取决于进入这一段时状态是什么。你从空白记事本开始涂改,和从写满前文的记事本开始涂改,得到的结果没有任何简单关系——不是差一个常数,不是差一项,是完全两回事。所以论文说,一个局部片段的效果「无法只从 S = 0 出发算出的状态确定」。
类比失效处:记事本的比喻会让你以为「涂改」只是擦掉一点,其实 Mt 是矩阵乘法,它把整个状态重新线性混合了一遍,远比「擦掉几个字」剧烈。
构造反例题。设有 P = 2 个上下文并行 rank,每个 rank 只有 1 个 token(记 rank 1 的转移矩阵和写入分别为 M1、w1,rank 2 的为 M2、w2,其中 w 代表 βkv⊤ 这一项)。请写出:(a) 顺序执行得到的真实最终状态;(b) 用「各自从零算再求和」得到的状态;(c) 两者相差什么,并指出在什么条件下它们才会相等。
(a) 真实值:S2 = M2w1 + w2。
(b) 求和法:rank 1 从零算得 S̃[1] = w1;rank 2 从零算得 S̃[2] = w2;求和得 w1 + w2。
(c) 差值:真实值 − 求和值 = M2w1 − w1 = (M2 − I)w1。
两者相等当且仅当 (M2 − I)w1 = 0。最干净的充分条件是 M2 = I,也就是没有任何遗忘、没有任何 delta 改写——而这正好就退化成了普通线性注意力(纯加法递推)。这个反例把整件事讲透了:求和法之所以对普通线性注意力成立,恰恰是因为那里的「转移矩阵」永远是单位阵;KDA 引入了非平凡的 Mt,等式立刻塌掉。而且注意差值里的 w1 被 M2 作用了——rank 1 的信息必须穿过 rank 2 的本地更新才能到达终点,这就是论文说的「通过本地 KDA 更新把先前各 rank 的上下文传播过来」。
变式:把 rank 数改成 P = 3,每个 rank 仍是 1 个 token。真实的最终状态是什么?w1 前面挂了几个 M?由此猜一猜:一般情况下 rank j 的贡献前面应该挂哪些矩阵的乘积?(猜完再往下读式 17,你会发现你已经猜对了。)
第三步:KCP 的解法与式 17
论文的解法叫 KDA 上下文并行(KDA Context Parallelism, KCP)。核心动作是:把每个片段的效果分解成两个可以纯本地计算的量——
- 一个作用在传入状态上的累积转移(矩阵 M);
- 一个从零本地生成的状态(带波浪号的 S̃)。
先把记号约好,不然式 17 读不下去:S[i]t 表示 rank i 内部经过 t 个本地 token 后的循环状态;S[i]Ti 就是离开 rank i、进入 rank i+1 的那个状态;带波浪号的 S̃ 表示「同一个递推但从 S = 0 起算」的对应量。总共有 P 个上下文并行 rank。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| P | 上下文并行 rank 的总数 | 这条序列被切给了几张卡 |
| i, j, l | rank 的下标,i+1 是「当前正在看的那个 rank」 | j 和 l 用来遍历它前面的所有 rank |
| t, r | rank 内部的本地 token 下标 | 「我这一段读到第几个字了」 |
| Ti | rank i 的本地 token 总数 | 这一段一共多长 |
| Mr | 第 r 个本地 token 的单步转移矩阵,即式 1 里的 (I − βrkrkr⊤)Diag(αr) | 「这一个 token 会怎么涂改整本记事本」 |
| M[i+1]t←1 | 累积转移:把本地前 t 个 token 的单步转移按顺序乘起来,形状 dk×dk | 「我这一段前 t 个字合起来,会把传进来的记事本改造成什么样」。注意 ←1 的箭头方向:从 t 往 1 写,强调乘法顺序不能颠倒 |
| S̃[i+1]t | 本地从零生成的状态:假装前面什么都没读过,只读本地前 t 个 token 得到的状态 | 「我这一段自己贡献了什么新内容」 |
| S[i]Ti | 离开 rank i、进入 rank i+1 的真实传入状态 | 「上一张卡交接给我的那本记事本」——这是我们要费劲求出来的东西 |
| Πl←j+1i M[l]Tl←1 | 把 rank j+1 到 rank i 的整段累积转移再乘起来 | 「rank j 贡献的那点内容,要穿过它后面所有 rank 的涂改,才能活着到达我这里」 |
式 17 的两项,各自是什么意思
论文自己给了一句话解释,非常准确:第一项(S̃[i+1]t)是本地 token 生成的状态;第二项通过本地的 KDA 更新把先前各 rank 的上下文传播过来。
换句话说,最终状态 = 「我自己写的」 + 「前面所有人写的东西,经过一路涂改之后剩下的」。第二行到第三行做的事情,只是把 S[i]Ti 这个「上一张卡交接来的记事本」递归地展开,一直展开到全部由带波浪号的、纯本地的量组成为止。
自己推一遍:从式 1 到式 17 的第三行
从 St = MtSt−1 + wt 出发(wt := βtktvt⊤),假设进入这一段时的状态是 Sin。把 t = 1, 2, 3 三步老老实实展开,你看到什么规律?
想好了再看
S1 = M1Sin + w1;S2 = M2M1Sin + M2w1 + w2;S3 = M3M2M1Sin + M3M2w1 + M3w2 + w3。规律很清楚:Sin 前面挂着全部 M 的乘积,而后面那一堆恰好就是「从零出发」会得到的东西。当初会想到这一步,是因为线性递推展开永远是这个形状——齐次项 + 非齐次项,这是解线性递推的标准套路。
把上一步的结果按「含 Sin 的」 和 「不含 Sin 的」分成两堆。不含 Sin 的那一堆,能不能用一个我们已经定义过的符号来表示?
想好了再看
能。不含 Sin 的那一堆,正是令 Sin = 0 时的结果,也就是 S̃[i+1]t。含 Sin 的那一堆则是 (Mt⋯M1)Sin,把这串乘积记作 M[i+1]t←1。于是就得到式 17 的第二行:S[i+1]t = S̃[i+1]t + M[i+1]t←1S[i]Ti。这一步就是整个 KCP 的全部灵魂:把「这一段干了什么」拆成了一个只依赖本地 token 的矩阵,加上一个只依赖本地 token 的状态。
第二行里还剩一个非本地的量 S[i]Ti。它是 rank i 的「离开状态」。你能对它做同样的事吗?做完之后又会剩下什么?
想好了再看
可以,而且这正是关键:S[i]Ti = S̃[i]Ti + M[i]Ti←1S[i−1]Ti−1。剩下的非本地量变成了 rank i−1 的离开状态——同一个形式,下标减一。所以把它一路递归展开到 rank 1(rank 1 的传入状态就是 0),非本地量就彻底消失了。展开的结果,就是式 17 第三行里那个求和:rank j 的贡献 S̃[j]Tj,前面挂着从 rank j+1 到 rank i 的所有累积转移的乘积。这跟你在 q10-5 变式里猜的应该一模一样。
最后一问,也是最重要的一问:在 t = Ti+1 时,M[i+1]Ti+1←1 和 S̃[i+1]Ti+1 这两个量,需要等别人的数据才能算吗?
想好了再看
完全不需要。论文明说:这两个量「只用本地 token 就能算出来,不必等 S[i]Ti」。这意味着所有 rank 可以同时开工,各自算自己的两块碎片,一点都不用互相等待。等所有人都算完了,再交换碎片、拼装。这就是「并行」二字真正落地的地方——如果这两个量需要等前面的状态,那 KCP 就退化成了串行执行,一点意义都没有。
第四步:结合律、前缀扫描、以及只需一次 all-gather
论文接着点出:式 17 表明每个状态都纯由本地计算出的碎片复合而成,而且这些 rank 级的更新满足结合律(compose associatively),所以每个 rank 的传入状态可以用一次前缀扫描(prefix scan)恢复。
前缀扫描(prefix scan):给一串元素和一个满足结合律的合并操作,一次性求出「前 1 个的合并结果、前 2 个的合并结果、……、前 n 个的合并结果」。它的关键性质是:因为满足结合律,你可以任意加括号、任意换分组顺序,所以这件事可以并行做,而不必老老实实从左到右串行走一遍。
具体流程分三步,论文写得很清楚:
- 本地计算:每个 rank 先本地算出自己的两块碎片 M[i]Ti←1 和 S̃[i]Ti。这一步没有任何通信。
- 一次 all-gather:用一次 all-gather 把这两个张量都交换出去(all-gather 的意思是「每个人把自己的那份广播给所有人,最后所有人都拿到全套」)。
- 本地重建:all-gather 之后,rank i+1 按顺序处理同一文档的先前碎片,从 S = 0 开始,在每个碎片处应用下面这条规则,就重建出了 S[i]Ti。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| S(箭头左右两个) | 一个累加器,从 0 开始,每处理一个先前碎片就更新一次 | 「我手上正在重建的那本记事本」 |
| M[j]Tj←1 | 从 rank j all-gather 来的累积转移碎片 | 「rank j 会怎么涂改我手上这本」 |
| S̃[j]Tj | 从 rank j all-gather 来的本地状态碎片 | 「rank j 自己新写了什么」 |
| j 的遍历顺序 | 严格按文档中的先后顺序,从前往后 | 顺序不能乱——矩阵乘法不交换 |
请注意这条重建规则的形状:它和式 1 一模一样(「先乘一个矩阵、再加一项」),只不过「一个 token」换成了「一整个 rank 的碎片」。这就是「rank 级更新满足结合律」的具体含义——同一种代数结构,只是粒度粗了。
出处与实现
论文在脚注里注明:这个构造建立在 DeltaNet 的上下文并行之上,KDA 的实现见 FLA PR #691。也就是说 KCP 不是凭空发明的,而是把 DeltaNet 那一套推广到了带 Diag(α) 遗忘门的 KDA 上。
综合题。论文说「KCP 只需要一次固定大小的 all-gather」。请解释:为什么「固定大小」这四个字是决定性的胜利,而不只是一个不痛不痒的优化?请把它和 softmax 注意力上下文并行的通信量放在一起对比,并说清这一条对 K3 的「百万 token 智能体工作负载」这个目标意味着什么。
(1)看趋势而非看常数。softmax 注意力做 CP 时,各 rank 必须交换键值块,而键值块的大小随序列长度增长。所以序列拉到十倍长,这笔通信也跟着涨。KCP 交换的是 M(dk×dk)和 S̃(dk×dv)两个张量,形状由模型维度决定,与序列长度无关。序列拉到十倍、一百倍,这两个张量一个字节都不会变大。
(2)看比值。真正决定「能不能扩展」的不是通信的绝对量,而是通信时间占总时间的比例。KCP 的计算量随序列长度线性增长(论文说它「实现线性的计算缩放」),而通信量是常数——于是通信占比随序列变长而不断下降,序列越长,这笔通信越显得微不足道。softmax CP 那边通信和计算同时增长,占比降不下去。一个是随规模自动变好的设计,一个不是,这是量变到质变的区别。
(3)对 K3 的意义。K3 明确要支持百万 token 的智能体工作负载。在这个长度上,「通信量随长度增长」意味着通信会吃掉大部分时间,长上下文训练在经济上直接不成立。KCP 把这一项按死成常数,才让百万 token 训练从「贵得离谱」变成「可行」。这也回扣了 §5.1 开头那句话:固定大小状态「便宜、易于传输和复用」——KCP 就是把「易于传输」这条性质兑现成真金白银的地方。
(4)诚实的补充。论文并没有给出 KCP 相对 softmax CP 的具体加速数字,也没说 M(dk×dk)这个矩阵在中等长度下是不是反而比键值块还大。上面的论证是渐近意义上的胜利,在具体的序列长度下谁快谁慢,论文没有给数据。
变式:假设有人把 KDA 的遗忘门砍掉,让 Mt 恒等于 I。这时候 KCP 会退化成什么?all-gather 要传的东西会少一半吗?(提示:M 恒为 I 时还需要传 M 吗?重建规则会变成什么?)
答辩:如果我是审稿人
你说 KCP「只需要一次固定大小的 all-gather」,听起来很美。但我注意到两件事:第一,你要交换的不只是状态 S̃(dk×dv),还有累积转移矩阵 M(dk×dk)——凭空多出来一个矩阵,通信量至少翻倍甚至更多,你在正文里对此只字未提。第二,all-gather 之后每个 rank 要「按顺序处理先前碎片」来重建自己的传入状态,rank i+1 要走 i 步,这是 O(P) 的串行矩阵乘法。当 P 很大时,这个重建本身会不会变成新的瓶颈?你凭什么说 KCP 便宜?
参考防守(先自己组织语言再看)
第一点,承认但不致命。确实要多传一个 dk×dk 的矩阵,这是 KDA 的 delta 规则带来的必然代价——没有 M 就无法表达「传入状态会被本地 token 改写」这件事,而这正是 10.4 那道反例题证明的。但关键在于:它仍然是常数。审稿人的批评改变的是常数因子,不改变「与序列长度无关」这个结论,而后者才是论文主张的那一条。在百万 token 的目标场景下,常数因子翻倍和「随长度线性增长」完全不在一个讨论层次上。
第二点,是真问题,论文确实没有正面回答。不过有两条辩护线索:(a) 论文明确说这些 rank 级更新满足结合律,并且点名用前缀扫描——前缀扫描的存在意义恰恰就是把 O(P) 的串行扫描做成 O(log P) 的并行归约,所以「必须串行走 i 步」这个前提本身就可以被打破;论文引用前缀扫描而不是简单说「顺序累加」,很可能就是在暗示这一点。(b) 每一步重建只是一次 dk×dk 乘 dk×dv 的小矩阵乘法,相对于每个 rank 本地几十万 token 的计算量,量级差得很远。
但要诚实地说:论文既没有给出 P 变大时的扩展性曲线,也没有量化重建开销,更没有说明当一个 batch 里混着多篇文档时(重建时要「处理同一文档的先前碎片」)额外的簿记成本有多大。这些都是论文留白的地方。
10.6 §5.2:3T 级预训练的总体拼装
从这里开始换战场。前面讲的是「怎么让 KDA 这种奇怪的算子跑得快」,接下来讲的是「怎么让一个 3T 级的多模态模型在上千张卡上训得起来」。
论文先列出 K3 预训练用到的全套并行方案,你会发现 10.1 里讲的五种全在里面:带虚拟阶段(VP)的流水线并行(PP)、专家并行(EP)、ZeRO-1 数据并行、流水线 ZeRO-2 梯度分片、以及 上下文并行(CP,就是 §5.1.2 的 KCP)。
ZeRO(Zero Redundancy Optimizer):数据并行时每张卡都存一份完整的梯度和优化器状态,这是巨大的浪费。ZeRO 的思路是把这些东西分片存到各张卡上,谁需要谁去取。ZeRO-1 分的是优化器状态,ZeRO-2 进一步分梯度。
MoE 层的做法:共享专家跨 EP rank 复制(每张卡都有一份,不用发 token 出去),而专家分发与合并的 all-to-all 通信与计算重叠,用来隐藏延迟。这里的 all-to-all 就是 10.1 图里 EP 那一格说的「token 飞到自己选中的专家所在的卡上,再飞回来」。
关于 Figure 11
论文的 Figure 11 展示的是:计算、通信、卸载在不同 PP 阶段里的重叠情况。图里能看到 DataLoader、ViT 前向、gather param、EP dispatch/combine、shared expert、offload/reload 等一系列条带,按 PP 阶段横向排开,密密麻麻地交错在一起。这张图想传达的信息只有一个:在任何一个时刻,一张卡上都同时在干好几件事——一边算、一边传、一边往 CPU 内存搬东西。图里没有给出量化的利用率数字,所以它是定性示意,不是性能数据。
然后论文把 3T 级原生多模态预训练的困难归结成三个关键问题,接下来三个小节正好一一对应:
| 问题 | 症状 | 解法小节 |
|---|---|---|
| (i) token 负载在各 EP rank 间不均衡 | 有的卡挤满 token,有的卡闲着;快的等慢的 | §5.2.1 MoonEP(本章 10.7) |
| (ii) 激活、梯度、优化器状态超出内存预算 | 显存装不下,直接 OOM 崩溃 | §5.2.2 显存高效训练(本章 10.8) |
| (iii) 视觉编码器高度可变的计算暴露在关键路径上 | 一张大图或一段长视频拖慢整批人 | §5.2.3 多模态编码器优化(本章 10.9) |
论文说 MoE 层的共享专家是「跨 EP rank 复制」的,而路由专家需要 all-to-all 通信来分发和合并 token。为什么共享专家可以省掉 all-to-all?这么做的代价是什么?
变式:如果把共享专家也改成分片存放(每张卡只存一部分),会发生什么?每个 token 都要飞出去一次,这笔 all-to-all 的量跟路由专家的相比是大还是小?
10.7 MoonEP:把专家负载切得一碗水端平
先说清问题。常规的专家并行方案里,token 负载在各 rank 间是不均衡的——路由器是学出来的,它想把 token 送给谁就送给谁,没人保证送得均匀。论文指出这会造成两个后果,第二个经常被人忽略:
- 计算不均衡拖慢训练吞吐:所有卡必须同步前进,最慢的那张卡决定了整体速度。
- 路由专家激活的形状动态变化,造成大量内存碎片:这一步分配一块 3000 个 token 的内存,下一步要 5000 个,再下一步 1200 个,分配器很快就被切得七零八落。
MoonEP 的解法是用动态冗余专家(dynamic redundant experts)实现完美负载均衡。所谓冗余专家,就是把某个热门专家额外复制一份放到别的 rank 上,让涌向它的 token 可以分流。
论文强调 MoonEP 保留了 DeepEP 等常规方案的整体计算流,只是额外引入了冗余专家的在线规划与迁移。具体来说:
- 前向传播时:从当前微批次和当前层的路由器输出规划冗余专家,并在路由专家计算之前预取它们。注意「当前微批次和当前层」这几个字——规划是逐层、逐微批次做的,粒度非常细。
- 反向传播时:把冗余专家的梯度暂存在一个本地 reduce 缓冲区里,等计算完成后再归约回它们「归属 rank」(home rank)的梯度缓冲区。这一步是必须的——同一个专家的多份副本产生的梯度必须合并,否则参数就分叉了。
核心定理:E/R 个冗余专家就够
这是 5.2.1 里最硬的一块。论文说:MoonEP 要求每个 rank 恰好收到 S × K 个 token(S 是序列长度,K 是每个 token 选中的专家数),这样所有 rank 做同样多的计算。
这里有一个容易读错的地方
论文没有明说 S 是「全局序列长度」还是「每个 rank 本地的 token 数」。但从后面那句「最坏不均衡下 DeepEP 需要 S×K×R 大小的缓冲区」可以反推出来:S 应当是每个 rank 本地持有的 token 数。理由是——每个 rank 本地有 S 个 token,每个 token 选 K 个专家,于是本地要发出 S×K 份 token 副本;整个 EP 组一共 R×S×K 份;最坏情况下这 R×S×K 份全涌向同一张卡,所以缓冲区要按 S×K×R 开;而完美均衡下每张卡恰好收到 R×S×K / R = S×K 份。这个读法自洽,但是我们的推断,论文本身没有把 S 的定义写清楚。另外注意这里的 S(标量,序列长度)和 10.5 里的 S(状态矩阵)是完全不同的两个东西,论文复用了同一个字母。
然后是关键问题:多少个冗余专家才足够保证这种均衡?设 E 为专家总数、R 为 EP 大小,论文证明:每个 rank 至多 E/R 个冗余专家时,均衡方案总是存在,而且这个界本质上是紧的(essentially tight)。完整证明在论文附录 E,本书第14章会讲。
论文特意把这一点跟先前工作对比,对比得相当尖锐。ECHO 和 UltraEP 这类工作的做法是预设冗余专家的数量,或者施加每 rank 的 token 上限。这带来三个毛病:一旦上限内无可行方案,训练就被迫停止;上限本身需要人工调参;而且就算调好了,仍然会留下残余的不均衡。MoonEP 的卖点不是「更均衡一点」,而是「有数学保证的、永不失败的完美均衡」——这是定性的差别。
另外三件配套工程
在线规划。每一步都算精确最优解代价过高。论文的做法很实用:用整数线性规划(ILP)离线算出代表性情形的精确解作为参照,据此设计一个 GPU 规划内核,做到近似最优、开销可忽略、且永远遵守 E/R 上界。注意这个思路——先用慢而精确的方法建立「标准答案」,再据此设计快而近似的方法,然后验证近似方法离标准答案有多远。
零拷贝通信。完美均衡还简化了通信路径。论文实现了一个融合的 permute/unpermute 算子:规划内核预先算好每个 token 的目的地,于是 token 被直接送到远程 rank 上按专家分组的位置,通信缓冲区的视图直接返回给计算,消除了中间拷贝。然后是那句关键的对比:在最坏的不均衡下,DeepEP 里支持同样免拷贝数据路径需要大小为 S×K×R 的通信缓冲区,而 MoonEP 因为完美均衡只需要固定的 S×K 缓冲区。
自己算一遍。假设每个 rank 本地持有 S = 8192 个 token,每个 token 选 K = 8 个专家,EP 大小 R = 64。(这些数字是为了练手假设的,不是论文给出的 K3 配置。)请算出:(a) MoonEP 需要的通信缓冲区能装多少个 token 副本?(b) 最坏不均衡下 DeepEP 支持同样免拷贝路径需要多大?(c) 相差几倍?(d) 用一句话解释这个倍数是怎么来的。
(a) MoonEP:S×K = 8192 × 8 = 65,536 个 token 副本的空间。
(b) DeepEP 最坏情况:S×K×R = 65536 × 64 = 4,194,304 个。
(c) 相差 64 倍,正好等于 R。
(d) 原因:缓冲区必须按最坏情况开,否则一旦真的挤爆就会崩。在完全没有均衡保证的方案里,最坏情况是全组 R 张卡的 R×S×K 份 token 副本全部涌向同一张卡,所以每张卡都得按这个上限预留空间。MoonEP 因为有 E/R 定理提供的数学保证,知道每张卡收到的恰好是 S×K,一个不多一个不少,于是缓冲区可以按精确值开,省下 R−1 倍的空间。
这里值得体会的是逻辑顺序:省显存不是靠「优化」省出来的,而是靠一条定理省出来的——因为能证明上界,才敢把缓冲区开小。数学保证在这里直接兑换成了显存。
变式:如果 EP 大小 R 增加到 128,MoonEP 的缓冲区会变大吗?DeepEP 的呢?由此说明:这两种方案在「扩大 EP 规模」时的可扩展性差在哪里。
静态形状带来的免同步执行。这一条的收益很隐蔽但很实在。常规 MoE 实现里,每个专家分到的 token 数在不同步、不同层间都在变,于是主机(CPU)必须在启动专家计算前与设备(GPU)同步,才能知道实际的计算形状是多少——而这个同步会在层与层之间拖住流水线。有了完美均衡,每个 rank 恰好收到 S×K 个 token,所有层的计算形状静态已知,于是逐层的 MoE 主机同步被彻底消除,主机侧的 kernel launch 开销也被缓解。
为什么「主机与设备同步」这么贵
CPU 给 GPU 派活的正常方式是「扔了就走」——CPU 把一堆 kernel 排进队列,自己接着往下跑,不等结果。这样 CPU 才能一直跑在 GPU 前面,队列里始终有活。但「同步」这个动作打破了它:CPU 必须停下来等 GPU 把结果吐回来,才知道下一个 kernel 该开多大。这一等,GPU 的任务队列就见底了,等 CPU 缓过神来再派活,GPU 已经空转了一段时间。每层都来这么一次,几十上百层累积起来非常可观。所以「形状静态已知」的真正价值不是省了一次判断,而是让 CPU 和 GPU 重新解耦。
专家 GEMM 调度与重叠。这一条是全节最容易被误读的地方,请特别注意:即使聚合负载在各 rank 间完美均衡了,每个 rank 内部各个专家分到的 token 数仍然是倾斜的。如果用固定顺序、无视工作量的调度,这种倾斜就会变成 SM worker 之间不均衡的完工时间(makespan)。论文的解法是用一个工作量感知的调度器来调度路由专家的 GEMM:它在启动前根据当前 token 分布调整参数,执行期间保持不变。参数由一个轻量启发式用硬件指标的解析代价模型选取,关键系数通过离线自动调优标定。另外,共享专家的 GEMM 被派到单独的 stream 上,好与其他内核重叠。
构造反例题。有人读完「完美负载均衡」这一段后总结说:「既然每个 rank 都恰好收到 S×K 个 token,那所有计算单元的工作量就都一样了,调度问题彻底解决。」请构造一个具体的例子推翻这个结论:给定 rank 上的 token 总数固定,说明各 SM worker 的完工时间仍然可以差得很远。
情形 A(均匀):250/250/250/250。每个 worker 的工作量正比于 250,四人几乎同时收工,完工时间 ∝ 250。
情形 B(倾斜):970/10/10/10。总数仍是 1000,完全满足 MoonEP 的完美均衡条件。但负责第一个专家的 worker 要做 970 份工作,另外三个各做 10 份就闲下来了。整个 rank 的完工时间 ∝ 970,是情形 A 的近四倍,而且有 3/4 的 worker 在绝大部分时间里空转。
结论:完美均衡是rank 之间(inter-rank)的均衡,它完全没有触及 rank 内部(intra-rank)各专家之间的倾斜。这正是论文那句「even with the aggregate load perfectly balanced across ranks, the per-expert token counts within each rank remain skewed」的意思。这也是为什么「专家 GEMM 调度与重叠」必须作为一个独立的技术点存在——它解决的是完美均衡解决不了的那一层问题。工作量感知的调度器不会天真地「一个 worker 管一个专家」,而是根据当前 token 分布调参,把大专家的活拆给多个 worker 分担。
更一般的教训:「总量相等」从来不等于「完工时间相等」。任何按总量做的均衡,都必须再问一句「总量内部的分布呢」。
变式:如果换成「每个专家的 token 数都相同、但各 rank 的总数不同」,会怎样?这两种不均衡(rank 间 vs rank 内)分别对应论文的哪个解决方案?
答辩:如果我是审稿人
你证明了「每个 rank 至多 E/R 个冗余专家就保证均衡方案存在」,并把它当作 MoonEP 相对 ECHO、UltraEP 的核心优势。但我要问:E/R 个冗余专家不是免费的——每个冗余专家都要占显存放参数,前向要预取,反向要把梯度归约回 home rank。当 EP 大小 R 比较小时,E/R 就相当大,等于每张卡要额外背上一大堆专家。你的正文里对这些代价一个数字都没有。你这个「保证」,是不是把成本藏到了别处?
参考防守(先自己组织语言再看)
第一层防守:这是预留槽位,不是实际占满。论文的措辞是「预留 E/R 个冗余专家槽位」(reserving E/R redundant-expert slots per rank)。E/R 是最坏情况下的上界,是为了保证「永远有可行解」而必须留出的余量;实际每一步在线规划用掉多少,取决于当前微批次的路由分布,绝大多数步数远用不到上界。所以显存要按上界预留,但迁移和通信的实际开销是按实际用量走的。
第二层防守:对手的成本更隐蔽。ECHO、UltraEP 的做法看似省了这笔预留,但它们付出的是训练可能被迫停止(无可行方案时)、需要人工调参、以及残余的不均衡持续拖慢每一步。前两项是无法量化但会吃掉工程师时间的成本,第三项是每一步都在交的税。用一笔有界的、可预测的显存开销,换掉一个无界的、不可预测的失败风险,在 3T 级训练这种「一次训练跑几十天、中断一次损失惨重」的场景下是划算的。
但审稿人的核心批评站得住。论文正文确实没有给出:冗余专家的显存开销占比、在线迁移的通信开销、MoonEP 相对 DeepEP 的端到端吞吐提升、以及 E、R 在 K3 里的实际取值。所有关于「划算」的论证目前都是定性的。而且「本质上是紧的」(essentially tight)这个说法本身也很含糊——是存在需要恰好 E/R 的实例,还是需要 E/R − 1 的实例?正文没有交代,只指向附录 E。
10.8 显存高效训练:六件武器,每一件都有代价
这一节对应论文 §5.2.2,解决的是第 (ii) 个问题——激活、梯度、优化器状态超出内存预算。六个技术,我们逐个看,重点看省的是什么、代价是什么。
激活(activation):前向传播时每一层算出的中间结果。它们必须保存到反向传播用完为止,因为算梯度要用。层数越多、序列越长、batch 越大,激活就越多,往往是训练时显存的最大头。
1. 统一激活管理器
这是六件里最有设计品味的一件。论文为激活设计了一个统一的存储抽象:每个「为反向保存的张量」都关联一个可插拔的存储后端。于是——重算(recomputation,用完就扔,反向时再算一遍)、量化(quantization,用更少的位数存)、卸载/远程卸载(offload/remote-offload,搬到 CPU 内存或别的卡上)——这三件原本互不相干的技术,统统变成同一个抽象下的「存储策略」,可以在张量粒度上自由组合。
而且策略是通过张量上的轻量注解声明的,与模型代码完全解耦——改省显存策略不用动模型代码一行。重算在函数粒度上进行,支持跨层重算。
实现上还有两个细节:所有 GPU 内存都在主计算流上分配、在单个内存池里管理,避免多流碎片和主机受限的开销;激活在层粒度上预取回来并与计算重叠,额外开销可忽略。
论文交代了 K3 的实际配置:大多数激活用块级 FP8 量化,加上卸载/远程卸载;逐元素算子配置为重算。这个搭配是有道理的——逐元素算子(比如激活函数)重算起来极便宜(一次遍历就完事),存起来却要占一整份显存,所以扔掉重算最划算。
打个比方
三种策略像整理房间的三种办法:重算是「这东西以后要用再做一个,现在扔了」;量化是「压缩打包,占地小但会掉一点细节」;卸载是「搬到楼下储藏室,要用的时候提前叫人搬上来」。统一激活管理器的贡献不是发明了这三种办法,而是做了一个统一的柜子,让你可以给每样东西单独贴标签说「这件走 A 方案、那件走 B 方案」,而不用为每种方案单独改造房间。
类比失效处:真实的储藏室搬东西不影响你干别的事,而 GPU 往 CPU 搬数据要占用 PCIe 带宽,可能拖慢别的传输。论文说「激活在层粒度上预取回来并与计算重叠,额外开销可忽略」——「可忽略」是作者的主张,没给数字。
读的时候要小心
「大多数激活用块级 FP8 量化」是一个有精度风险的选择:FP8 只有 8 个比特,把反向传播要用的中间结果压到 8 比特,理论上会给梯度引入误差。论文在这一节完全没有讨论这对训练精度的影响,也没说哪些张量被排除在量化之外。「块级」(block-wise)意味着按小块分别定标度以减小误差,但具体块大小、以及哪些层豁免,论文都没写。
2. 显存高效 MoE
这一条是纯粹的数学技巧,值得欣赏。原生 MoE 实现里,置换后概率的梯度计算依赖前向输出 output——这意味着 output 必须一直留在显存里等着反向用。
论文受 SonicMoE 启发,通过数学变换把这个梯度改写成只依赖中间激活 act_output 和上游梯度 doutput 的形式,代价是多一次轻量的逐元素计算。这样一来,反向对 output 的依赖就被消除了,output 用完即可释放。
另外在 group GEMM 的前向里,只保存 dispatch 操作的输入;反向时通过重算 dispatch 恢复 group GEMM 的输入。这次重算引入的通信与 group GEMM 反向计算的一部分重叠,于是这部分激活存储「以可忽略的代价被消除」。
3. 显存高效的注意力残差
这是针对 Block AttnRes(前面章节讲过的架构组件)的配套优化。做法有三点:块表示在边界层生成一次,被后续所有层共享,直接驻留在 GPU 上;AttnRes 计算整个被 checkpointing 包住,所以每层为反向保存的激活与标准残差架构完全相同;对流水线并行采用基于缓存的流水线通信,只在阶段间增量传输新生成的块,微批次一结束就释放,达到内存占用的理论下界。
第二点是这条的核心卖点:Block AttnRes 是个新架构组件,人们最担心的就是「新组件会不会让显存开销涨一大截」。论文的回答是——包上 checkpointing 之后,每层保存的激活跟标准残差架构一模一样,也就是零额外激活开销。代价当然是 checkpointing 本身的重算时间。
4. 跨 PP rank 均衡激活
这一条需要先理解一个流水线并行的固有现象。在交错 1F1B(interleaved 1F1B,一种流水线调度策略,「一次前向一次反向」交错进行)下,由于流水线预热(pipeline warmup),激活在各 PP rank 间的分布是不均匀的:驻留激活数随 PP rank 增大而减少。
为什么?因为流水线要先「灌满」。排在最前面的 PP rank 必须先连续做好几个微批次的前向,才能等到第一个反向传回来;在那之前,它已经攒了一堆激活在手上。而排在最后的 PP rank 做完前向马上就能做反向,激活立刻释放。结果就是靠前的 rank 显存吃紧、靠后的 rank 显存宽裕。
论文的解法很直接:为避免 OOM,用 Mooncake Transfer Engine 把激活远程卸载到其他 PP rank 的内存,实现跨 PP rank 的均衡激活内存。也就是说——既然后面的 rank 有富余显存,就把前面 rank 的激活搬过去存着。
5. 流水线 ZeRO-2 梯度分片与卸载
三层动作,一层比一层激进:先把梯度分片到各数据并行 rank(这是 ZeRO-2);进一步把分片后的梯度存到 CPU 内存以降低 GPU 内存占用;同时在 GPU 上保留双梯度缓冲区。流程是:梯度在 DP rank 间归约进双梯度缓冲区,再累加到 CPU 分片里。
「双缓冲区」是个经典技巧:一块正在被填,另一块正在往 CPU 搬,两块轮换,于是搬运和计算可以同时进行。代价是 GPU 上要多留一块缓冲区的空间,以及 GPU↔CPU 之间持续的数据流量。
6. 基于 P2P 的 Muon 正交化
这一条的问题很有意思:分布式优化器把参数均匀分片到各 DP rank(每张卡只拿到参数矩阵的一条),而 Muon 里的 Newton–Schulz 正交化需要完整的参数矩阵。这两件事直接冲突——正交化是一个作用在整个矩阵上的运算,你手里只有几条是做不了的。所以每次更新前都必须有一步通信来聚齐完整参数。
天真做法:在每个 rank 上对整个参数缓冲区做 all-gather。后果是双重的——既造成可观的显存占用(每张卡都要腾出装下全部参数的空间),又让通信成为主要瓶颈。
论文的做法:每个 rank 只通过与对应 owner rank 的 P2P 通信,取回它本地所拥有的那些参数的分片。这样消除了全参数缓冲区,同时降低显存使用和通信量。通信与计算进一步在 model-chunk 缓冲区的粒度上流水化,隐藏通信开销。
P2P 版本到底改了什么
关键区别在「每个 rank 最终需要拿到什么」。天真 all-gather 的隐含假设是「每个 rank 都要拿到全部参数」,所以每张卡都得有一个能装下全部参数的缓冲区。但仔细想想:每个 rank 只负责更新自己拥有的那部分参数,它需要的是那些参数所属的完整矩阵,而不是所有矩阵。于是把「广播给所有人」改成「点对点地、只向该找的 owner 要该要的那块」,全参数缓冲区就不需要了。这是一个典型的「重新审视需求」式优化——不是把通信做快,而是发现根本不需要传那么多。
§5.2.2 的六件武器,省显存的代价其实只有三类:(A) 多花计算时间、(B) 多花通信/带宽、(C) 损失数值精度。请把下面四项各归到一类或多类,并说明理由:(1) 逐元素算子配置为重算;(2) 块级 FP8 量化;(3) 把分片梯度存到 CPU 内存;(4) 显存高效 MoE 的梯度数学改写。
(1) 逐元素算子重算 → A(计算时间)。前向算完就扔,反向要用时重新算一遍。之所以专挑逐元素算子来重算,正是因为它们重算极便宜(一次遍历),而存起来要占一整份显存,性价比最高。
(2) 块级 FP8 量化 → C,轻微 A。主要代价是精度:8 比特存不下原来的动态范围,必然有量化误差。「块级」是为了减小这个误差(按小块分别定标度)。量化和反量化本身也要一点计算,但很轻。论文没有讨论这对训练结果的影响,这是留白。
(3) 分片梯度存 CPU → B(带宽)。GPU 和 CPU 之间搬数据要走 PCIe,比显存带宽慢得多。论文用双梯度缓冲区来对冲,让搬运和归约可以重叠——这正是「用重叠把 B 类代价藏起来」的标准手法。另外 CPU 上的累加也要花 CPU 时间。
(4) MoE 梯度数学改写 → A,但极轻微。论文明说代价是「多一次轻量的逐元素计算」,没有精度损失(是等价的数学变换,不是近似),也没有额外通信。这一条最接近真正的免费午餐,因为它不是把数据挪个地方,而是从根上让反向不再需要 output 这个张量。
值得记住的一般规律:A 和 B 类代价通常可以靠重叠藏掉大半(论文里 (1)(3) 都配了重叠手段),C 类代价藏不掉——精度掉了就是掉了。所以论文对 (2) 的沉默才格外值得注意。
变式:把「跨 PP rank 均衡激活」(远程卸载到别的 PP rank 内存)也归类。它属于哪一类?跟卸载到 CPU 相比,卸载到别的 GPU 有什么优势和劣势?
10.9 多模态编码器:把 ViT 塞进流水线的缝里
最后一个问题:视觉编码器高度可变的计算暴露在关键路径上。
为什么「高度可变」?因为图片和视频的大小差别极大。一张小图可能几百个 patch,一张高分辨率大图或一段长视频可能几万个。论文说:在长上下文多模态训练中,大图和长视频大幅增加视觉编码器的计算时间,造成跨设备的严重负载不均衡。有的卡分到一堆小图很快就算完,有的卡分到一张巨图算半天,前者只能干等。
编码器里的动态 CP
论文的第一个动作是把上下文并行扩展到这类大样本上,做法有两层:
- 单张大图沿 patch 维切分到多个设备,注意力通过跨 CP rank 收集键值对(gather-KV)来计算。注意这里用的是 gather-KV,因为视觉编码器用的是普通的 softmax 注意力,不是 KDA——所以它得老老实实交换键值对,享受不到 10.5 那个固定大小状态的红利。
- 把每个 CP 组划分成若干「子 CP 组」,把多张大图以负载均衡的方式分配给它们,防止通信占比随规模增长。这一层是关键:如果只做第一层,一张大图切到越来越多的设备上,gather-KV 的通信会越来越占比重;分成子组之后,每张图只在一个较小的子组内切分,通信被限制在子组内部。
论文说这个做法既降低了大视觉样本的编码器延迟,也降低了跨设备负载不均衡,从而让剩余的编码器计算能藏进流水线气泡里——这句话是承上启下的,直接引出下一个技术。
把编码器计算塞进 PP 气泡
流水线气泡(pipeline bubble):流水线并行天生有「预热」和「排空」两个阶段——开头的时候后面的 rank 还没收到活,结尾的时候前面的 rank 已经没活了。这些「某些卡无事可做」的时间段就是气泡。气泡是纯粹的浪费,但只要用流水线就无法完全消除。
K2.5 引入了解耦编码器进程(Decoupled Encoder Process, DEP),把 ViT 与文本训练拆成不同阶段,并在各 PP 阶段间均衡视觉的前向和反向。K3 在这个基础上更进一步,靠的是一个很精细的观察:
论文观察到的时序规律
在交错 1F1B 调度下:最前面几个 PP 微批次的文本前向都被排在最开头(scheduled at the very beginning),而最后几个 PP 微批次的文本反向要到最末尾才结束(finish only at the very end)。
这两句话合起来的意思是:流水线的头和尾是被文本计算占死的硬边界,中间才有气泡。所以 ViT 的计算不能一股脑扔进气泡——最前面那几个微批次的 ViT 前向必须在文本前向开始之前就完成,否则文本前向就得等它。
于是论文把 ViT 计算进一步分解:最前面几个 PP 微批次的 ViT 前向同步地提前执行(executed synchronously upfront),其余的前向排进流水线气泡,反向同理处理。结果是:大部分 ViT 计算被藏在流水线气泡里,基本消除了视觉编码器的有效开销。
读的时候要小心
「基本消除了视觉编码器的有效开销」(largely eliminating the effective overhead of the vision encoder)是论文作者自己的主张,正文没有给出任何数字支撑——没有说消除了百分之多少,也没有给优化前后的吞吐对比。同样,5.2.3 里「子 CP 组」具体怎么划分、「负载均衡地分配大图」用的是什么算法,论文都没有说明。
综合题。把 10.8 的第 4 件武器(跨 PP rank 均衡激活)和 10.9 的第 2 个技术(ViT 塞进气泡)放在一起看,你会发现它们其实源自同一个流水线并行的现象。请指出那是什么现象,说明它为什么同时制造了一个麻烦和一个机会,并解释这两个解法为什么不冲突(甚至可以同时用)。
共同的根源:交错 1F1B 的流水线预热与排空。流水线必须先灌满才能进入稳态,最后还要排空。
它造成的麻烦(显存维度):预热期间靠前的 PP rank 要连续做好几个微批次的前向才等到第一个反向传回来,这期间的激活全部要留着,不能释放。越靠前的 rank 攒得越多,于是「驻留激活数随 PP rank 增大而减少」,靠前的 rank 有 OOM 风险。解法是用 Mooncake Transfer Engine 把激活远程卸载到靠后 rank 的富余内存里——本质上是把显存从宽裕的地方挪到紧张的地方。
它造成的机会(时间维度):预热和排空期间总有卡在空转,这就是气泡。既然反正是空的,就拿别的活来填。ViT 计算恰好是理想的填充物,因为它不依赖文本流水线的中间结果,时序上很自由。解法是把大部分 ViT 计算排进气泡——本质上是把计算从繁忙的时刻挪到空闲的时刻。
为什么不冲突:两者动的是正交的两个资源维度——一个搬的是空间(显存,跨 rank),一个搬的是时间(计算,跨时刻)。而且它们的方向还是互补的:气泡多发生在预热/排空阶段,正好是靠前 rank 显存最紧张的时候,把 ViT 计算放在那里并不会加重显存压力(ViT 有自己的激活,但可以独立管理),反而利用了那段时间的空闲算力。
更深一层的观察:K3 基础设施的整体套路可以概括成一句话——凡是有「某处紧张、某处富余」的不均衡,就想办法把富余的搬到紧张的地方去。MoonEP 搬的是 token(EP rank 之间),KCP 搬的是状态碎片(CP rank 之间),跨 PP rank 均衡激活搬的是显存,ViT 填气泡搬的是计算时刻。四个技术,一个思想。
变式:如果把 PP 的阶段数(虚拟阶段数)调大,气泡会变多还是变少?靠前 rank 的激活压力会变大还是变小?这两个变化对上面两个解法各有什么影响?(提示:论文没有讨论这个权衡,你需要自己从「预热要灌几个微批次」推理。)
本章小结
这一章讲的全是「怎么让它跑起来」,但如果你只记住一串技术名词,就白读了。真正值得带走的是三条思路:
第一,KDA 的固定大小状态是一笔交易,不是一次纯粹的改进。坏的一面是串行难并行,好的一面是便宜好传好复用。§5.1 的全部工作可以概括为:用 FlashKDA 和 SM 级 CP 对付坏的一面(在设备内),用 KCP 把好的一面兑现成真金白银(在设备间)。
第二,KCP 的数学核心只有一句话:把一段序列的效果拆成「一个累积转移矩阵」加「一个从零生成的状态」,这两样都只用本地 token 就能算。因为矩阵 Mt 的存在,老办法「各自从零算再求和」彻底失效(你在 q10-5 里亲手推翻了它);而式 17 给出的分解让各 rank 可以完全并行地算出自己的碎片,再用一次固定大小的 all-gather 交换、用前缀扫描拼回来。「固定大小」这四个字是全章最重要的四个字——它意味着序列拉长十倍,这笔通信一点都不变大。
第三,§5.2 的所有技术都在做同一件事:把富余的资源搬到紧张的地方去。MoonEP 搬 token(并用 E/R 那条定理把「保证可行」换成了 R 倍的缓冲区节省),统一激活管理器搬激活(在重算、量化、卸载三种策略间自由组合),跨 PP rank 均衡激活搬显存,ViT 填气泡搬计算时刻。而每一次「搬」都有代价,本章逼你把每一项的代价都说清楚了:多花计算、多花带宽、或者损失精度——没有第四种。
还有一条贯穿全章的方法论值得单独点出:数学保证可以直接兑换成工程收益。MoonEP 因为能证明 E/R 这个上界,才敢把通信缓冲区从 S×K×R 砍到 S×K;因为能保证「每个 rank 恰好 S×K 个 token」,计算形状才变成静态已知,才能消掉逐层的主机同步。反过来说,如果只是「经验上比较均衡」,这两项收益一项都拿不到——因为缓冲区必须按最坏情况开,形状必须运行时才知道。
下一章接着讲基础设施的后半段:百万 token 智能体强化学习的基建,以及推理侧的 KDA 解码、前缀缓存与调度。KDA 状态的「便宜、好存、好复用」这条性质,在那里会被榨得更彻底。
本章需要打问号的地方(汇总)
论文在这几页里留了不少白:FlashKDA 只说「大幅超过 Triton 参考实现」但没给倍数;KCP 没有给通信量或扩展性的实测数据,也没讨论 P 很大时前缀扫描重建的开销;MoonEP 没给冗余专家的显存代价、也没给相对 DeepEP 的端到端提升,「本质上是紧的」具体指什么要看附录 E;块级 FP8 量化对训练精度的影响完全没有讨论;Figure 11 是定性示意,没有利用率数字;5.2.3 的子 CP 组划分算法和「基本消除视觉编码器开销」的量化证据也都缺失。读这一章时,请把「这是作者的主张」和「这是被数据支持的结论」分开放。
第11章 基础设施 II:百万上下文的 RL、沙箱与线上服务
一句话导语:这一章回答一个很土但很致命的问题——当上下文长到 100 万个 token,模型又要一边自己做任务一边被训练,最后还要拿去给几百万人用,内存和带宽根本不够分,那到底该怎么分?
学完这一章你应该能做到
- 用自己的话解释什么是 KV 缓存、前缀缓存,并算出一次多轮对话里前缀缓存到底省了多少计算
- 说清「写回」与「写穿」两种缓存卸载策略各自的浪费点,并构造出让写回吃亏的场景
- 复述「把参考模型权重塞进梯度缓冲区」这个技巧,并指出它的安全性依赖哪两条实现假设
- 在 Figure 12 的设定下亲手算出命中边界 B,并说明为什么它是哈希块的整数倍而不必是物理块的整数倍
- 把三个并发一致性机制分别对应到它们要防的那个具体失效模式
- 用 400K 与 4K 这两个数字算出缓存命中的收益量级,并指出缓存亲和调度的适用边界
11.1 开工前:五个必须先懂的常识
这一章的每一个设计,都是在跟同一件事较劲:算过的东西不要再算第二遍,占着的内存要尽快让出来。要看懂它们,先得有五个概念打底。
KV 缓存(KV cache):模型每读进一个 token,都会为它算出一对叫 key 和 value 的向量;后面每生成一个新 token,都要回头看前面所有 token 的这对向量。关键在于——某个位置的 key/value 只取决于它自己和它前面的内容,一旦算出来就永远不会变。所以算一次就存起来,这个仓库就是 KV 缓存。不存会怎样?每生成一个字,前面几十万个 token 就得全部重算一遍,长上下文下这是纯粹的自杀。
前缀缓存(prefix cache):既然 key/value 只取决于「这个 token 和它前面的内容」,那么两个请求只要开头那一段逐 token 完全相同,这一段的 KV 就一模一样。于是第二个请求可以直接把第一个请求算好的那段拿来用。这就是前缀缓存能省钱的全部道理。你和 AI 聊到第 10 轮时,前 9 轮的内容原样又发了一遍,如果没有前缀缓存,这 9 轮每次都要从头算。
为什么前缀缓存对本章格外重要
普通对话的前缀也就几千个 token,省不省差别不大。但 K3 的目标场景是 100 万 token 上下文:一次编程任务可能带着 40 万 token 的代码库当前缀。这时「命中」和「未命中」的差别不是快一点慢一点,而是差出两个数量级——11.12 节我们会亲手把这笔账算出来。
分页(paging)与物理块(physical block):KV 缓存不是一整块连续内存,而是像操作系统管内存那样,切成固定大小的「块」来分配、回收、共享。一块可能装 1024 个乃至 6144 个 token 的 KV。这个「块」是本章后半段一切纠纷的源头。
Rollout:强化学习里让模型自己去「打一局」——想一步、调个工具、看看返回、再想、再调,最后产生一条完整的轨迹。这条轨迹拿去打分,再用分数更新模型权重。「多步 rollout」就是一局里有很多轮工具调用,上下文只增不减地越滚越长。「部分 rollout」(partial rollout)是指一局没打完就先按暂停键,把状态存下来,下一轮迭代接着打——目的是不让某一条特别长的轨迹拖住所有人(这叫压尾延迟)。
沙箱(sandbox):给智能体配一台隔离的小电脑,它可以在里面随便敲命令、装软件、跑代码、把系统搞崩,都不会影响到外面。没有沙箱,你就不敢让模型真的去执行它自己写的命令。
TTFT 与 SLO:TTFT(time to first token)是你按下回车到屏幕上蹦出第一个字之间的等待时间——用户感知最强的那个指标。SLO(service level objective)是服务给自己定的承诺,比如「95% 的请求 TTFT 不超过 2 秒」。11.12 节里所有的调度设计,最终都是为了守住 SLO。
用户第一轮输入 3000 个 token,模型回了 200 个 token。第二轮,客户端把这 3200 个 token 原样带上,再加 100 个 token 的新问题。
(a) 没有前缀缓存时,这一轮要重新计算多少个 token 的 KV?
(b) 有前缀缓存时呢?
(c) 如果用户这次把「请用中文回答」这句话(设为 6 个 token)加在了整段对话的最前面,(b) 的答案变成多少?
变式:如果那句「请用中文回答」是加在第二轮问题的末尾而不是开头,能复用的前缀长度是多少?为什么位置差这么多?
11.2 §5.3 的出发点:为什么「资源效率」被排在第一位
论文这一节的开场白很直白:要给 K3 这么大的模型做智能体 RL,还要把上下文扩到百万 token,而算力预算是有限的,于是资源效率成了第一位的目标(a first-order goal)。注意这个措辞——不是「顺便优化一下」,而是设计的出发点本身。
由此长出两条互补的工作线:一条是高效的训练与 rollout,具体包括 KV 缓存管理、请求调度、训练状态放置;另一条是高性能、可恢复的沙箱,用来支撑长时程的交互。前者是 11.3 到 11.5 节,后者是 11.6 节。
那道核心的内存争用题
K3 的 RL 采用同址训练(co-located RL training):训练和 rollout 跑在同一批 GPU 上,这样每个 1M 上下文的 K3 RL 实验能被控制在几百张 GPU 以内。同时用部分 rollout 来压住超长轨迹带来的尾延迟。论文说这个设计「硬件利用率好」,但紧接着承认它引入了一个麻烦:
矛盾在哪
部分 rollout 的意思是「这一局没打完,先存着,下一轮接着打」。存的是什么?主要就是这条轨迹的 KV 缓存——它必须跨迭代活着。可它占的显存,正是训练那边要用来放权重、优化器状态、激活和梯度的同一块显存。两位租客抢一间房。论文明确指出:这个挑战在长上下文 RL 里更严重。
为什么更严重?因为 KV 缓存的大小大致随上下文长度增长,1M 上下文下单条未完成轨迹要留下的 KV 就已经很可观,而训练那边的需求并不会因此缩小。论文对此只做了定性判断,没有给出任何量化数据——这是我们后面会反复遇到的情况。
(a) 请解释:为什么「部分 rollout」这个机制本身就是内存争用的来源?如果放弃部分 rollout、要求每条轨迹必须一次跑完,争用会缓解吗?代价是什么?
(b) 假设你只能改一个东西来缓解争用:减少同时在跑的轨迹条数、缩短单条轨迹的最大长度、或者把训练那边的某些东西挪走。分别说说各自会牺牲什么。
变式:如果换成「分离式」部署(训练和 rollout 用两批互不相干的 GPU),这个内存争用会消失吗?消失之后新的问题是什么?
11.3 外部 KV 缓存池:写回,而不是写穿
先看清楚敌人。论文说:在 1M 上下文的多步 rollout 下,前缀 KV 缓存未命中的代价极高。而部分 rollout 让情况雪上加霜——每轮迭代刚开始的时候,上一轮留下的许多未完成的长预填充请求会同时到达。同时到达意味着瞬间的内存需求尖峰。
还有第三个加压者:推测解码在相对固定的工具调用间隔内进一步加快了请求周转,于是前缀块的churn(不断被换进换出的翻搅)变多了。这几件事叠在一起,可能触发抢占(preemption,把某个请求的缓存强行赶出去腾地方),从而拉低缓存命中率——而命中率对长上下文 RL 是生死攸关的。
读的时候要小心
「推测解码加快请求周转 → 增加前缀块 churn」这一步,论文只用了一句话带过,没有解释中间的机制。合理的推测是:周转快意味着单位时间内进出的请求更多、块的分配与回收更频繁,但论文并未展开,我们无法验证。
解法:把「前缀保留」和「住在 GPU 上」这两件事拆开
直觉上,你希望能复用的前缀一直留着;但显存有限,留不下。论文的做法是用一个写回(write-back)设计,把「前缀被保留」和「前缀驻留在 GPU」解耦:
- 正在活跃解码的块,留在 GPU 的 KV 缓存里。
- 可复用的、较老的前缀,只在从 GPU 被驱逐的那一刻,才写回到位于 CPU DRAM 里的外部 KV 缓存池(external KV cache pool),并在下一次要复用它之前预取回来。
- KDA 的循环状态与对应的 MLA KV 缓存块一起卸载、一起预取,让两者的生命周期保持对齐。
对照组是写穿(write-through):一算完就往 CPU 拷一份。论文的论证是:写回策略只对那些离开活跃解码路径的前缀产生 CPU DRAM 占用和传输带宽,从而避免了为「仍然驻留在 GPU 上、而且还活跃着」的块做冗余的 CPU 拷贝。
打个比方
写穿像是你每写完一页笔记就立刻去复印一份存档,哪怕这页你等会儿还要接着写。写回则是等这页从桌上被收走的时候才去复印。桌面(GPU 显存)小、复印机(PCIe 带宽)慢的时候,后者显然更省。
类比失效处:真实系统里「收走」是被动发生的,而且可能一大批同时被收走——这时复印机会突然排长队。这正是 11.3 节测验里要你构造的反例。
DRAM 从哪来:让训练状态先滚去 NVMe
外部池住在 CPU DRAM 里,可 DRAM 也不是无限的。论文的办法是:一次训练迭代结束后,把训练状态(模型权重和优化器状态)卸载到 NVMe(固态硬盘),腾出 DRAM 给外部池;一次 rollout 迭代之后,池被释放,以免与训练工作负载争用。
论文论证写回优于写穿。请构造两种不同的工作负载,使写穿不吃亏甚至更好,并分别指出你破坏的是论证里的哪一个隐含前提。
反例二(大家一起走):驱逐高度同步——论文自己就描述了这种情形,每轮迭代开始时上一轮的大批长预填充请求同时到达,同时触发抢占。写回会在那一瞬间要求极高的 PCIe 带宽,形成排队;而写穿在此之前的空闲时段早就把数据慢慢搬完了。被破坏的前提是「逐出发生时有富余的传输带宽」。
值得注意的是,反例二的触发条件恰恰是论文自己承认存在的场景,但论文没有讨论写回在这种尖峰下的表现。
变式:如果 CPU DRAM 特别小(只装得下很少的前缀),写回和写穿哪个受伤更重?提示:想想「谁先被迫丢弃数据」。
11.4 Rollout 自动限流调度器:别让并发数变成一个手调的魔法数字
多步 rollout 有一个很讨厌的性质:上下文随着轨迹推进逐渐增长。第 1 步可能只有几千 token,第 30 步可能已经几十万。可传统做法是设一个固定并发数——同时最多放多少个请求进推理引擎——而这个数通常是按「整条轨迹的平均长度」估出来的。
论文指出这个做法两头不讨好:这个平均长度本身就难以估计;而且按它设出来的并发数在早期过于保守(那时每条请求其实只占一点点 KV,GPU 吃不饱)。反过来,如果把并发调高,后期又会造成 KV 缓存压力、可能触发抢占。
论文的解法是在 LLM 请求调度层设计一个自动限流(auto-throttling)机制,用运行时信号动态决定发给推理引擎的请求数量。论文列出的信号有三个:活跃请求数、排队请求数、KV 缓存利用率。效果是:早期 rollout 保持高利用率,而当 KV 缓存压力上升时自动降低并发,在不需要人工调参的情况下同时避免欠饱和与过载。
论文没说的部分
论文只列出了用哪三个信号,完全没有给出控制律——具体怎么根据这三个数算出下一刻的并发数(是阈值触发?比例控制?还是别的)。也没有给出开启前后的吞吐或命中率对比。所以这一段只能算是「设计思路的陈述」,无法评估其效果。
(a) 用上面那张图的语言解释:「固定并发在早期过于保守、后期又可能过载」这句话,本质上是一条常数去逼近一条什么样的曲线?
(b) 论文列的三个信号(活跃请求数、排队请求数、KV 缓存利用率)里,哪一个最能提前预警「要过载了」?另外两个各自在什么时候有用?
变式:如果换成一个上下文长度基本不变的任务(比如单轮问答,每条请求都是 2K token),自动限流还有必要吗?固定并发在这种负载下会不会反而更好?
11.5 借一块别人还没用的内存:梯度缓冲区复用
这是本章最精巧、也最值得单独拿出来讲的一个小技巧。
问题:RL 的损失函数经常需要一些只做前向的非策略模型(non-policy model),最典型的是参考模型(reference model,用来衡量当前策略偏离初始模型有多远)。这些模型的权重太大,没法一直住在 GPU 上。可你每次算损失又都需要它。
常规解法是额外分配一块显存放它。但在 1M 上下文的 RL 里,显存已经被 KV 缓存和训练状态挤到极限,「额外分配」这四个字本身就是奢侈的。
那块闲着的内存
论文的观察是:训练时每个参数都配了一个 FP32 梯度缓冲区(存反向传播算出来的梯度,用 32 位浮点存以保证精度)。而在前向传播还没结束、反向传播还没开始的这段时间里,这块缓冲区里面装的是上一轮的垃圾,谁也不需要它。
于是:把参考模型的权重放在 CPU 内存里,只在需要时才实体化(materialize)到 GPU,并让它们的参数张量借用策略模型的 FP32 梯度缓冲区存储。论文给出的安全性论证是一句话:这是安全的,因为这些缓冲区在真正的梯度稍后被计算时本来就会被覆盖。收益是:复用了已有的 GPU 内存,不需要额外分配,也不产生碎片。
打个比方
教室后排有两个座位,是留给下节课要来的两位老师的。这节课他们还没到,于是你先坐上去写作业——反正他们来的时候你早就走了,而且他们一坐下就会把桌面清空。你没有「占用」任何新的座位,只是用了一段本来空着的时间。
类比失效处:真实系统里「他们一定会清空桌面」是一条依赖具体实现的假设。如果那两位老师习惯把新东西叠在旧东西上面(梯度累加而不是覆盖),你的作业就会被卷进他们的讲义里——这正是下面答辩框要打的点。
怎么把一个巨大的模型塞进两个槽位
配合 ZeRO-2 的梯度分片与卸载,每张 GPU 在 K3 的 RL 训练中只为两个 VPP chunk 保留梯度缓冲区(VPP 指虚拟流水线并行的切分单位,一个 chunk 就是分给这张卡的一小段层)。也就是说可借用的空间只有两个槽位那么大,远远装不下整个参考模型。
论文的处理是流式(streaming):参考模型的权重被一块块送进这两个槽位,一个槽用于当前的前向计算,另一个预取下一块。这样拷贝的时间就被藏在计算的时间背后,在不增加 GPU 内存的前提下隐藏了拷贝开销。
论文说这么借是「安全的,因为这些缓冲区在真正的梯度稍后被计算时本来就会被覆盖」。请构造两种情形,使这个安全性论证不成立,并写出为了让论证成立,必须额外保证的条件。
grad += ... 而不是 grad = ...——比如做梯度累积(多个 micro-batch 的梯度加在一起)、或者一个权重被多处共享。这时缓冲区里的旧内容不会被覆盖,而是被加进新梯度里,参考模型的权重值就变成了污染梯度的噪声。可怕之处在于它不报错:训练照样跑,损失曲线可能只是稍微不对,几天后才发现。情形二:时序被打乱或重叠。如果参考模型的前向被安排在反向传播已经开始之后,或者与反向重叠在不同的 CUDA stream 上,那么真梯度会被参考权重覆盖掉,或者参考权重在读取途中被写坏。
必须额外保证的条件:(i) 本次迭代对该缓冲区的梯度写入是纯覆盖式的第一次写(在此之前缓冲区内容对任何人都无意义);(ii) 参考模型的前向完全早于任何梯度写入,且没有跨 stream 的重叠。论文只给了结论,没有说明它如何保证这两点。
变式:如果把「借」的对象从梯度缓冲区换成优化器状态(比如 Adam 的一阶动量 m),这个技巧还安全吗?为什么?提示:想想动量的生命周期是「一次迭代内」还是「跨迭代」。
答辩:如果我是审稿人
你说把参考模型权重塞进策略模型的 FP32 梯度缓冲区是安全的,理由是「这些缓冲区反正会被真梯度覆盖」。可这是一条依赖具体实现的假设:只要有人在反向里改成梯度累加,或者把参考模型的前向挪到反向之后,你的模型就会安静地训坏,而且不会有任何报错。你凭什么把一个正确性保证,建立在「别人不会改这段代码」之上?
参考防守(先自己组织语言再看)
诚实的防守分三层,不要试图硬说它「绝对安全」。
第一层,承认性质。这确实不是数学上的必然,而是一条实现层面的不变量(invariant):本迭代对该缓冲区的第一次写是覆盖式的,且参考前向严格早于它。论文只用一句话陈述了结论,没有说明它靠什么维持这条不变量——这个批评是站得住的。
第二层,说明这类不变量的正确处理方式。系统工程里到处都是这种「某块内存此刻无人认领」的约定,可接受的做法是把它显式化并可检查:在借用前断言缓冲区处于「本迭代尚未写入梯度」的状态,在梯度写入路径上断言「是覆盖而非累加」,让违反立刻爆炸而不是静默出错。有趣的是,论文自己在缓存布局那一节(11.8)用的就是同一种思想——宁可产生垃圾数据,也不要产生看似合理的数据。用他们自己的标准来要求这里,是完全公平的。
第三层,说明收益为什么值得。替代方案是额外分配一块显存放参考模型。但在 1M 上下文的 RL 里,显存已经被跨迭代存活的 KV 缓存和训练状态挤到极限(11.2、11.3 节),「额外分配」可能根本做不到——不是慢一点,而是跑不起来。所以正确的立场是:用一条可检查的不变量,换一块本来就会闲置的显存,而不是宣称零风险。论文没有说明它是否真的加了这类断言,这是它该补的。
11.6 沙箱基础设施:五千万个一次性的小电脑
为支持 K3 后训练与评测的多样需求,论文部署了多种沙箱运行时:传统的基于容器的运行时、GPU 沙箱运行时,以及最值得一提的、基于 microVM 的新沙箱运行时 AgentENV(与合作伙伴共同开发,已开源)。它围绕三个核心设计目标。
目标一:高保真的隔离沙箱运行时
论文的动机写得很坦白:随着智能体能力增强、任务变难,它们探索得更激进,甚至可能尝试奖励黑客(reward hacking,为了拿高分而钻规则漏洞,而不是真的完成任务)。这带来两个方向相反的要求。
一方面是安全挑战:早期用传统容器沙箱做实验时,观察到若干由智能体的非预期操作引起的内核 panic 和死锁。翻译成人话——模型敲的命令把宿主机的操作系统内核搞崩了。容器的隔离性其实是「共享同一个内核、只是把视野隔开」,所以一旦内核出事,大家一起死。
另一方面又希望尽可能允许探索、不去限制智能体的能力,而复杂任务需要一个接近真实环境的沙箱:论文举的例子是,智能体应当能随意挂载磁盘、运行容器、甚至启动虚拟机。这些在容器里都是做不到或很危险的。
解法是用 Firecracker 运行隔离的 microVM(轻量虚拟机,每个沙箱有自己独立的内核)。论文的说法是,它提供了容器运行时无法匹敌的隔离级别与保真度。
常见误解
很多人以为「虚拟机一定比容器慢很多、重很多,所以生产上只能用容器」。这里恰恰反过来:正因为智能体会做出人类不会做的操作,容器那种「共享内核」的隔离根本不够用;而 microVM 通过极度精简的虚拟机实现,把启动开销压到了可以每秒开一堆的程度。下面的「亚秒级启动」和「133 ms 检查点」就是在回答「那不会很慢吗」这个疑问。
目标二:面向智能体 RL 的灵活沙箱生命周期
底层能力是增量检查点(incremental checkpointing)与恢复:做检查点时只保存自上次检查点以来被弄脏的内存页,因此把检查点和恢复延迟压到低至 133 ms 和 49 ms。在这之上提供三个高层操作:
- 暂停与恢复(Pause and Resume):被暂停的沙箱不消耗内存或 CPU 资源。因此当智能体在等模型推理结果时可以把沙箱暂停——而这段等待可能占到沙箱生命周期的高达 98%。
- Fork:从原沙箱的精确状态创建一个新沙箱,同时保持原沙箱继续运行。论文说这对无副作用的奖励评判很有用——你想跑个测试看看它做得对不对,但不想让这次测试改变智能体所处的环境。
- 快照(Snapshot):按固定间隔为沙箱做快照,以便错误恢复。
目标三:高效率与高密度
他们的工作负载有一个吓人的特征:数以万计的沙箱、每个带有独特的镜像集合,可能需要在几秒内创建出来。镜像(image)就是这台小电脑的「出厂系统盘」,装了这个任务需要的软件。一万个沙箱各带各自的镜像,意味着要在几秒钟内搬动海量数据。
论文的做法是采用 OverlayBD 作为镜像格式,配合自定义的 ublk 驱动实现、存储层共享和 P2P 传输(沙箱之间互相传镜像,而不是都去挤同一个中心服务器),在大规模下实现亚秒级启动延迟。进一步通过写时复制内存(copy-on-write,多个沙箱共享同一份内存页,谁要改才给谁单独复制一份)和 page-cache 优化降低内存使用,在真实工作负载中实现高达 6.5× 的内存超额分配比(memory overcommit ratio,即答应给出去的内存总量是物理内存的 6.5 倍,靠的是大家不会同时都要)。
尺度感:51,219,741 个沙箱
论文给了一个总数:K3 的整个训练与评测过程中,共创建了 51,219,741 个沙箱,跨 1,505,678 个镜像。五千一百多万个是什么概念?
假设一个人手动开一台虚拟机、等它起来要 10 秒,不吃不喝不睡地连续开,需要 51,219,741 × 10 秒 ≈ 5.12 亿秒 ≈ 16 年。而这一切发生在一个模型的训练与评测周期内。另一个角度:51,219,741 ÷ 1,505,678 ≈ 34,也就是平均每个镜像只被用来开了 34 个沙箱——镜像种类多到惊人、复用度并不高,这就解释了为什么「亚秒级启动」必须靠存储层共享和 P2P 传输,而不能靠「把常用镜像缓存在本地」。(这个除法是本书为了给读者尺度感而算的,论文只给了两个总数,没有讨论平均复用度。)
这些数字要怎么读
论文原文用的是「as low as 133 ms and 49 ms」和「as much as 98%」,也就是最好情况和上限,不是平均值;6.5× 说的是「in real workloads」,但没有说是哪种工作负载、怎么测的、内存压力多大。这些都是作者自述的工程数字,论文没有提供可复现的测量条件,所以它们适合用来理解「设计想达到什么效果」,不适合当作性能承诺。
论文说智能体等待模型推理的时间可占沙箱生命周期的高达 98%,而被暂停的沙箱不消耗内存或 CPU。假设一个沙箱总共活 200 秒,其中 196 秒在等推理。
(a) 不暂停时它占用内存的时间是多少?暂停后呢?
(b) 只看「内存占用时间」,同样的内存理论上最多能多跑多少倍的沙箱?
(c) 这个倍数和论文里说的 6.5× 内存超额分配比是同一回事吗?
变式:如果某类任务里智能体只有 50% 的时间在等推理(比如它自己要跑很久的编译),(b) 的倍数变成多少?由此说说「暂停」这个优化对什么样的任务最有价值。
11.7 §5.4 换场:同样的难题,从生产侧再来一遍
训练那半章讲完了。论文这里有一句很有意思的话:服务 K3 从生产侧暴露出同样的挑战。也就是说,把模型拿去给真实用户用,遇到的不是全新的问题,而是同一批问题换了个身份出现。具体是三条:
- 混合 KDA–MLA 架构在百万 token 上下文下要维护两种根本不同的缓存,而且必须联合管理;
- 它的新模块和高度稀疏的专家需要各自定制的内核;
- 生产流量混杂着每请求成本跨越三个数量级的请求。
论文用三个层级分别对付它们:
| 层级 | 做什么 | 目标 |
|---|---|---|
| 引擎层 engine level | KDA 感知的前缀缓存:把固定大小的循环状态打包进与 MLA KV 缓存同一个分页池 | 让长前缀能跨请求复用 |
| 设备层 device level | 为 KDA 解码、Block AttnRes、稀疏潜 MoE 定制内核 | 最小化逐 token 延迟和内存流量 |
| 舰队层 fleet level | 缓存感知的亲和调度、基于预算的准入控制 | 把前两层的效率转化成可预测的服务 |
把下面三件事各自归到正确的层级,并说出理由:
(1) 把 KDA 的循环状态和 MLA 的 KV 缓存塞进同一个分页池;
(2) 给一波百万 token 的长请求单独划一份资源预算;
(3) 用 WarpDecode 式的 token 中心内核做 MoE 解码。
变式:「缓存感知的亲和调度」属于哪一层?它和引擎层的前缀缓存是什么关系——是替代、还是互相依赖?
11.8 统一缓存布局:把两种完全不同的东西塞进同一个池子
先说清楚为什么这件事难。论文的原话是:混合架构让前缀缓存变复杂,因为 KDA 循环状态和 MLA KV 缓存在大小和生命周期上根本不同,而一个缓存的前缀只有在两者能在同一个边界上一起恢复时才可复用。
把这两句话拆开看。每个 K3 块(block)由三个 KDA 层和一个 Gated MLA 层组成,两者的缓存长得完全不一样:
| MLA KV 缓存 | KDA 循环状态 | |
|---|---|---|
| 大小 | 随序列长度增长 | 固定 |
| 组织方式 | 按 token 分页,每个 token 一条 | 每个请求只有一份 |
| 直觉 | 一本越写越厚的流水账 | 一块反复擦写的白板 |
「一起恢复」这个要求就是从这里来的:MLA 那本流水账你可以翻到任意一页接着看,但 KDA 那块白板上只有最后一次擦写后的内容——你要想从第 2560 个 token 接着算,就必须在当初经过 2560 的时候专门给白板拍过一张照。两边必须在同一个位置都有可用的东西,这次复用才成立。
为什么不分开管
最自然的想法是给两者各配一个管理器。论文否掉了:那会让分配、引用、传输的逻辑重复三份(这里的「三份」对应 K3 每块有三个 KDA 层的结构)。于是他们把 KDA 状态打包进与 MLA KV 相同的分页块池,并把页统一成相同的字节大小,这样两种页类型就共享同一套分配、引用管理、驱逐的实现。
页的内部还有一个讲究:所有头(head)的状态按头连续存放,于是每个头的字节流是自包含的,可以成为跨节点传输的最小单位。这个设计有一个直接的好处:在预填充/解码分离(prefill/decode disaggregation,把「读完提示词」和「逐字生成」放到不同机器上)的部署下,当预填充节点和解码节点采用不同的张量并行度(TP,即一层被切成几份放在几张卡上)时,重新布局在传输路径上完成,GPU 侧零重排——数据反正要过网络,那就在过网络的路上顺手换个排法,GPU 一点力都不用出。
让 bug 显形:一个零开销的健全性检查
接下来是本节最值得单独讲的一句话。论文说,这种不对称在开发中被证明有用:任何类型混淆的访问产生的是垃圾数据,而不是看似合理的数据——这是对池化布局的一个零开销的健全性检查(zero-overhead sanity check)。
意思是:两类页虽然被统一成了相同的字节大小、住在同一个池子里,但内部的组织方式并不相同。所以如果代码写错了,把一个 KDA 状态页当成 MLA 的 KV 页去读,读出来的不是「有点不对的数值」,而是彻底的乱码,模型立刻输出垃圾,工程师当场就发现了。
为什么「立刻崩」比「悄悄错」值钱
系统里最贵的 bug 不是让程序崩溃的那种,而是静默的那种:结果还在正常范围内,指标只掉了一点点,可能几周后才有人察觉,然后要花更久去定位到「原来是缓存读串了」。让错误一发生就产生明显异常,等于把这类 bug 从「几周」压缩到「几分钟」。而且这个检查不花任何运行时开销——它不是加了一条断言语句,而是布局本身就导致了这个后果。
论文没说清的地方
论文用的是「这种不对称」(this asymmetry),但没有明确交代指的是哪一处不对称。我们的理解是:两类页虽然字节大小相同,内部组织方式却不同,所以互相误读会得到乱码。这是根据上下文的推断,原文并未点明。
(a) 解释为什么「类型混淆时产生垃圾数据」比「产生看似合理的数据」是一件好事,并举一个日常生活里同样思路的设计。
(b) 反过来想:如果两类页不但字节大小相同,内部布局也恰好兼容(互相误读也能得到数值范围正常的结果),会带来什么风险?
(c) 这个检查为什么能叫「零开销」?它和在代码里加一句断言有什么本质区别?
变式:这个思路能不能反过来用?也就是故意把两类数据的布局设计得互不兼容,即使统一布局在别处更方便。这样做的代价是什么?在什么情况下这个代价是划算的?
11.9 粒度之争:老办法为什么失效,以及怎么把两种粒度拆开
这是全章最难的一节,请慢一点读。我们要回答一个问题:一次前缀命中,到底能命中到哪个位置?
老办法:块哈希前缀缓存
业界通行的做法叫基于块哈希的前缀缓存(block-hash-based prefix caching),它以一个物理块为粒度复用 KV 缓存。规则只有一条:只有完整的块才被哈希,所以只有块对齐的前缀才可复用。
哈希(hash):把一大段内容压成一个短短的「指纹」,内容完全一样就得到一样的指纹,内容差一个字指纹就完全不同。前缀缓存靠它来快速判断「这段开头我是不是见过」。链式哈希(chained hash)则是让每一块的指纹里把前面所有块的指纹也算进去——于是「对上了第 5 块的指纹」这一件事,就同时证明了前 5 块全都一样,不用逐块比对。
这个耦合在 K3 上断了
论文把失效过程拆成了一条清晰的因果链,我们一步步跟:
自己推一遍:为什么块大小被逼到了 1024–6144
块哈希匹配要求所有层共享同一个块大小。而一次前缀命中,只有在命中边界处的 KDA 状态已经被持久化时才可复用。那么,KDA 的状态能存得多频繁?
想好了再看
不能太频繁。因为一个 KDA 层为每条序列维护的是单一的、很大的循环状态,而不是逐 token 的条目(回忆 11.8 的白板类比)。给白板拍照,每拍一张就是一整块大数据;而给流水账加一行,成本只有一行。所以论文说:状态快照只在稀疏边界上才负担得起。
既然 KDA 只能在稀疏的边界上存状态,而所有层又必须共享同一个块大小,那么这个共享的块大小会被推向哪个方向?
想好了再看
被推向大。论文给的结果是共享块大小被迫拉到 1024–6144 token。注意「被迫」二字:这不是为 MLA 选的最优值,而是被 KDA 的存储成本绑架的结果。
块大小变大了,为什么哈希的粒度也跟着变粗?这两件事本来是一回事吗?
想好了再看
本来不是一回事,但在老设计里被绑在了一起:由于哈希与存储块绑定,哈希粒度也一样粗。论文特意补了一句——尽管 MLA 的逐 token 条目本可以容忍细得多的块。也就是说,MLA 这一侧完全是被牵连的无辜者。这句话就是后面「解耦」方案的种子。
在 1024–6144 这么粗的粒度下,前缀缓存还能剩下多少用处?请自己举出两种「明明内容重复却一次都命不中」的情形。
想好了再看
论文的结论很重:在这么粗的粒度下缓存几乎没用。两种情形是:其一,短于一个块的请求永远无法被复用——凑不满一整块,就永远导不出可缓存的前缀;其二,分块预填充在跨越一个完整块边界之前导不出任何可缓存的前缀(分块预填充指把一个长输入切成小批喂进去,好和别的请求交错调度)。
解法:把「分配的粒度」和「匹配的粒度」拆开
既然这两件事本来就不是一回事,那就别再绑在一起。论文的解法是解耦两种粒度:
- 前缀哈希跑在 MLA 页内部的细哈希块(hash block)上,例如 512 token;
- 物理块仍然是粗的分配单位(该多大还多大);
- 对 KDA 则反过来对齐:循环状态的检查点只保存在 MLA 哈希端点的一个稀疏子集上——因为那是查找唯一可能引用到的位置。
第三条为什么是关键
KDA 的检查点很贵,不能到处存。那存在哪里最划算?答案是:只存在将来有可能被查到的位置上。而查找只会问哈希端点(因为命中边界必然是哈希块的整数倍),所以在非哈希端点上存检查点是百分之百的浪费。这一步把「存哪里」这个开放问题变成了「在一个已知的候选集合里挑子集」,问题一下子小了很多。
预填充期间发生了什么
MLA 这一侧:一个部分填满的 MLA 页,会以它最后一个完整哈希块的链式哈希注册进前缀缓存索引。由于每个哈希覆盖它之前的所有哈希块,匹配到一个端点就证明了直到该端点的整个前缀。而注册的端点随页填满而推进——这正是对老办法那两个失效情形的正面回答:现在不用等整个物理块填满,每填满一个 512 的小块就能对外发布一次。
KDA 这一侧:每次前向传播之后,KDA 内核在处理过的最后一个哈希对齐位置持久化循环状态。但检查点很大,所以随着请求推进,中间检查点被取代并回收,而处在对话轮边界上的那些被保留下来供跨请求复用(对话轮边界是最可能被下一个请求接着用的位置——你说一句、模型答一句,下一轮从这里接着长)。
还有一条必须记住的规矩:缓存的检查点是只读快照。命中时的动作是把它拷贝进请求的私有运行状态,然后才做下一次前向;新的检查点写到新槽位。因此,一个对其他请求可见的检查点永远不会被就地修改。为什么这条至关重要,我们在 11.9 的第三题里推。
查找分两阶段
第一阶段(MLA):按链式哈希匹配完整的物理块;在第一个缺失的块处,回退到该块内部的哈希端点——所以部分填满的页仍然可以命中。
第二阶段(KDA):要求候选边界在每一个 KDA 缓存组里都有检查点,而每个组维护一份独立的循环状态。
最终的命中,是同时满足两个阶段的最长边界。写成一个式子:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| B | 命中边界,即这次可以从第几个 token 接着算 | 「前面这么多我不用重算了」的那条线 |
| H | 哈希块大小,论文举的例子是 512 token | 对齐的最小刻度,尺子上的最小格 |
| M | 本请求与缓存中的前缀逐 token 相同的最长长度(Figure 12 里是 2800) | 「内容上」最多能省到哪 |
| k | 整数,表示命中了几个哈希块 | 数格子数了几格 |
| KDA 缓存组 | 各自维护一份独立循环状态的一组 KDA 缓存,命中要求每一组都有检查点 | 几块白板必须都拍过同一时刻的照,缺一块就不算 |
论文特别强调了 B 的两条性质:它总是哈希块的整数倍,但从不要求是物理块的整数倍。这正是解耦换来的东西。
Figure 12 那个例子
论文的 Figure 12 画的是一个物理块内部的情况:一个 6144 token 的物理块含 12 个 512 token 的哈希块(6144 = 12 × 512)。图下方的记号表示每个哈希边界上 KDA 检查点的状态:空心圈是没有存检查点的边界,实心灰点是已持久化的检查点,橙色点标出被命中的那个。论文说明:持久化的检查点是稀疏的,而且典型地与对话轮边界重合。
例子本身:一个请求的前 2800 个 token 与缓存的前缀相同,命中发生在 B = 2560 = 5 × 512,深深地落在这个 6144 token 的物理块内部;然后从 token B 恢复预填充,而不是重算 [0, B)。图里还标注了完整的动作序列:在 B 处恢复 KDA 检查点;对那个部分填满的 MLA 块做写时复制;从 token B 恢复预填充,[0, B) 零重算。这次请求总共复用了 5 个 MLA 哈希块和 B 处的那个 KDA 检查点。
下面这个交互演示复现了 Figure 12。你可以拖动匹配长度 M、改变哪些边界上有 KDA 检查点,看看命中边界 B 怎么跟着变——特别要试一试「把 2560 处的检查点去掉」会发生什么。
按 Figure 12 的设置:物理块 6144 token,哈希块 512 token,某请求的前 2800 个 token 与缓存中的前缀逐 token 相同。
(a) 2800 落在第几个哈希块的内部?
(b) 若每个哈希端点上都有 KDA 检查点,命中边界 B 是多少?这与论文图里的 B = 2560 一致吗?
(c) 若 KDA 检查点只存在于 1024 和 2048 处(2560 处没有),B 会变成多少?
(d) 在 (c) 的情况下,比起 (b) 多算了多少个 token?
变式:如果哈希块改成 1024 token(物理块仍是 6144,且每个哈希端点都有检查点),B 是多少?和 512 的方案比,多重算了多少 token?由此说说「哈希块越小越好吗」。
解耦之后,论文说前缀缓存「达到了与全注意力模型同等的通用性」。请构造两个场景:内容上明明有大段完全重复的前缀,但新设计下仍然命不中或几乎命不中。指出各自卡在哪个条件上,并说明这是不是设计缺陷。
场景二(检查点不在那儿):一个请求在句子中间与另一个请求分岔(比如两个用户贴了同一份 40 万 token 的代码库,但接下来问的问题不同,而分岔点不在任何对话轮边界上)。MLA 侧完全可以匹配到分岔点附近的哈希端点,但论文说检查点随请求推进被取代并回收,只有对话轮边界上的被保留——如果那个位置的检查点已经被回收,KDA 阶段就通不过,B 只能退到上一个仍然存在的检查点。这是一个真实的、由存储成本决定的限制,也正是我们在 11.10 的答辩框里要质疑「同等通用性」这个说法的原因。
还有一个隐含条件值得一提:命中要求每一个 KDA 缓存组都有检查点,所以只要有一组的检查点被驱逐,整个边界就作废——这是下一节的机制三。
变式:如果把「只在对话轮边界保留检查点」改成「每隔 16 个哈希端点强制保留一个」,场景二会改善吗?代价是什么?(提示:算一下一个 1M 上下文里会有多少个检查点,而每个检查点有多大。)
把三件事串起来回答:为什么「检查点是只读快照、命中时拷进请求的私有运行状态、新检查点写到新槽位」这三条规定,是让一个前缀缓存能被多个请求同时共享的必要条件?如果去掉「只读」这一条,允许命中的请求直接在共享检查点上就地往下算,会发生什么?请写成一个两请求的时间线。最后回答:MLA 的 KV 缓存为什么不需要这么强的规定?
去掉只读后的时间线:① A 命中 C,就地往下算 100 个 token,C 的内容变成了 [0, 2660) 的状态;② 但缓存索引里 C 仍然标着 [0, 2560);③ B 查到 C,以为拿到的是 [0, 2560) 的状态,实际拿到的是 A 走出去之后的状态;④ B 从 token 2560 接着算,可它的初始状态里已经混进了 A 的 100 个 token——输出静默错误,不崩溃,只是答案悄悄变错(又一次印证了 11.8 那个「最贵的 bug 是静默的」)。⑤ 而且 A 自己也不安全:如果 C 随后被驱逐或被别人拷贝,A 的状态来源就没了。
三条规定的分工:「只读」保证事实不被篡改;「拷进私有运行状态」把共享的只读事实与私有的可变进度分开;「新检查点写到新槽位」保证发布新事实时不会覆盖旧事实(否则第③步的问题会以另一种形式回来)。
MLA 为什么不用:因为 MLA 是逐 token 的条目,写完就不再改动——新 token 写到新位置,老位置的内容天然是不可变的;所以靠引用计数加写时复制就够了(Figure 12 里对那个部分填满的 MLA 块做的正是写时复制)。就地更新才是需要「快照 + 拷贝」的根本原因,而这正是 KDA 这类循环状态与注意力 KV 的本质差别。
变式:如果一次只允许一个请求命中某个检查点(命中即独占,别人排队),也能避免上面的错误。这个方案哪里不好?请从 11.12 节「400K 前缀」的场景出发说明后果。
11.10 并发调度下的一致性:三个机制,三个具体的坑
上一节讲的是「一次查找该怎么算」。可真实系统里有几十上百个请求在同时跑、共用一个缓存池,于是出现了一个很别扭的局面。论文把它讲得很精准:
别扭在哪
一个命中的块,同时具有两个身份:它既是共享的缓存条目(别的请求可能正指着它),又是某个私有请求的增长点(这个请求要从这里往后接着写)。而且,所有 KDA 缓存组必须在每一个命中边界上达成一致。论文明确说,接下来的三个设计点每一个都由一个具体的失效模式决定——不是「顺手加的保险」,是「不加就一定会出事」。
机制一:先钉住,再分配
失效模式:所有缓存组从同一个共享空闲链表取块,所以为某一组分配私有拷贝,可能驱逐另一组刚刚命中的块。你正准备用第 1 组的命中结果,结果给第 2 组找地方的时候,把第 1 组刚命中的块给挤掉了——自己打自己。
机制:每个命中块在任何分配发生之前,先跨所有组钉住(pin)。先把所有要用的东西按住,再去申请新东西。
机制二:拷贝还没落地的块,不许参与匹配
失效模式:往私有块的拷贝是在前向传播之前、立刻在 GPU 上执行的。这意味着从「块被分配或注册」到「字节真的被拷进去」之间有一段时间差。在这段时间里,在当前调度步内被分配或注册的块,仍会把上一个持有者的字节交给读者——索引说这是你要的前缀,读到的却是完全无关的另一段内容。
机制:这类块在其拷贝落地之前被排除在匹配之外。
机制三:驱逐要原子,兄弟一起死
失效模式:一个检查点只有在每个 KDA 组里都存在时才能恢复请求。如果只驱逐了其中一组的检查点,索引上看起来这个边界还「可用」,可真去恢复的时候会发现某一组缺状态。
机制:驱逐某一组的检查点会原子地作废它的兄弟——一个检查点要么在每个组里都可命中,要么在任何组里都不可命中。不允许存在「半个检查点」这种中间状态。
打个比方
三个机制像是图书馆的三条规矩:一,你要借的书先全部拿在手里,再去申请新书架(否则管理员会把你放在桌上的书收走);二,还没上架完的书不算在馆藏目录里(否则别人按目录去拿,拿到的是上一位读者留下的旧书);三,一套三卷本的书要么三卷都在,要么三卷一起下架(否则有人借到第一卷,翻到第二卷才发现没有)。
类比失效处:图书馆里拿错书你当场就知道,而缓存拿错字节是静默的——模型照样输出,只是输出得不对。这也是为什么这三条必须由系统强制,而不能靠「小心一点」。
论文对这三条的总评是:有了这些机制,每个注册的状态都精确对应它所声明的 token 前缀,于是混合 KDA–MLA 模型的前缀缓存达到了与全注意力模型同等的通用性:任何共享前缀都可以在任意 512 token 边界上复用,与请求长度、分块方式、调度交错无关。
(a) 假设去掉机制二(不再把「拷贝尚未落地的块」排除在匹配之外),请写出一条具体的时间线,说明错误是怎么产生的,以及为什么它不会报错。
(b) 假设去掉机制三(驱逐不再原子),最坏会发生什么?
(c) 这三个机制里,哪一个在「系统里只有一个请求、完全没有并发」的场景下可以安全去掉?为什么另外两个不行?
(b) 最坏情况是一次命中在恢复阶段才发现某个 KDA 组缺状态:轻则要在中途放弃命中、退回去重算(浪费掉已经做的工作,还打乱了调度),重则如果实现里没有检查、直接拿了一份陈旧或属于别的前缀的状态去恢复,同样产生静默的错误输出。机制三把这个风险从「运行中才发现」提前到「根本不会被选中」。
(c) 可以去掉的是机制一:它防的是「给某一组分配时挤掉另一组刚命中的块」,这需要多个缓存组在同一个空闲链表上竞争——但注意,即使只有一个请求,K3 里也有多个 KDA 缓存组,所以严格来说只有在「单请求且只有一个缓存组」时才安全。机制二不行:单请求也存在「分配后、拷贝落地前」的时间窗,只是此时没有第二个读者去踩它,风险从「一定出错」降为「取决于实现细节」。机制三更不行:它防的是同一请求内部跨组的不一致,与并发无关。这一小题的意思是:并发只是放大器,三个机制里真正与并发强相关的只有第一个。
变式:如果把机制二改成「不排除这类块,但在读之前插一次同步,等拷贝完成」,正确性能保住吗?性能上会付出什么?(提示:想想这次同步发生在调度步的哪个位置,会阻塞谁。)
答辩:如果我是审稿人
你在小节结尾说,混合 KDA–MLA 模型的前缀缓存「达到了与全注意力模型同等的通用性:任何共享前缀都可以在任意 512 token 边界上复用」。可你自己前面刚写过,KDA 检查点很大、只在稀疏边界上保存、中间的会被回收、留下来的典型地落在对话轮边界。那绝大多数 512 边界上根本没有检查点,按你自己的式子,那里的命中不可能发生。这句「同等的通用性」是不是把话说满了?
参考防守(先自己组织语言再看)
防守的关键是把机制上的通用性和实际的命中率分开,并且承认原文措辞确实容易被误读。
第一,这句话真正主张的是「约束被解除了」。解耦之前,命中边界被物理块大小绑架:只能落在 1024–6144 的整块边界上,短于一个块的请求永远命不中,分块预填充在跨过整块边界前什么都导不出。解耦之后,这三条结构性限制全部消失,命中边界与请求长度、分块方式、调度交错解除了耦合——这就是「与全注意力模型同等」的确切所指:全注意力模型的前缀缓存也从来不保证任意位置都命中,它保证的是「命中与否只取决于内容是否相同,而不取决于你怎么切、怎么调度」。从这个角度,论文的主张是成立的。
第二,承认第二个条件仍然存在。一次具体查询能否命中,还要求该边界在每个 KDA 缓存组里都有检查点,而检查点确实是稀疏的。所以更严谨的表述应该是「命中粒度不再受物理块限制」,而不是「任意 512 边界都能复用」。原文这一句把「机制上允许」和「实际会命中」揉在了一起,属于表述偏乐观。
第三,指出真正的举证缺口。论文全文没有给出前缀缓存命中率的任何数字,也没有给出解耦前后的对比。因此读者无法判断这套设计在真实流量下究竟把命中率提高了多少——而这本该是最容易测、也最有说服力的一个指标。这是比措辞更值得追问的地方。
11.11 高性能内核:三个模块,三种不同的省法
内核(kernel):这里不是操作系统内核,而是指跑在 GPU 上的一小段计算程序。同一个数学运算,写法不同,速度可以差好几倍——差别主要在怎么安排数据的搬运,而不是算术本身。K3 引入了三个新模块,论文为每一个都定制了内核。
KDA 解码:不存状态,存「重放的原料」
论文先点出差别:与 KDA 预填充相比,解码的主要瓶颈从「挖掘并行性」转移到「高效管理不断演化的循环状态」,该状态在每个解码步被就地更新。
麻烦出在推测解码上。回忆一下推测解码的玩法:先用便宜的方式一口气猜出好几个 token(草稿),再让主模型一次性验证它们对不对——猜对了就白赚好几个 token 的速度。K3 用的是基于 MTP(多 token 预测)的推测解码。
问题是:如果验证拒绝了草稿 token 的一个子集,状态已经推进到最后一个被接受的 token 之外,无法轻易回滚。白板已经被擦改过了,可你现在发现后面几笔画错了,却擦不回去。
直接的解法是为每个草稿位置维护一份状态快照——这确实能支持回滚,但会让状态流量成倍增加,而论文指出,这个代价在在线服务典型的大批量下会占主导(批量越大,同时要读写的状态份数越多,而解码本来就是被内存带宽卡住的)。
关键观察来了:任何被接受的草稿前缀之后的状态,完全由草稿 token 的投影输入决定,而这些投影输入比状态本身小得多。既然如此,那就只缓存这些投影输入,然后在片上重建被接受 token 的状态,只写回已验证 token 和 bonus token 的状态。论文注明:这个设计在并行的工作 ReplaySSM 中被独立提出。
bonus token 是什么
推测解码里,主模型在验证草稿的那一次前向中,其实还顺带算出了「最后一个被接受的 token 之后的下一个 token」——这个白赚的就叫 bonus token。论文用了这个词但没有定义,这里的解释是该领域的通行含义。
实现上,被重放的 token、bonus token 和下一个草稿窗口共享单个融合内核里的一个循环,这个内核覆盖短卷积、输入归一化、门控、KDA 递推和输出归一化(把原本五个步骤合成一个,中间结果不落回显存)。论文自述的效果是:验证延迟随被验证 token 数次线性增长,且保持低于状态缓存基线。
最后一句非常重要:因为投影缓存从不离开解码阶段,前缀缓存和预填充–解码分离在与非推测服务相同的载荷上运作。翻译成人话——推测解码带来的这些额外数据只在解码内部打转,不会溢出去变成「新的一类缓存」,所以 11.9、11.10 讲的那一整套前缀缓存机制,以及预填充节点与解码节点之间传输的内容,完全不用为推测解码改动。
Block AttnRes:两阶段调度,两个阶段都在省内存
Block AttnRes 遵循两阶段调度:一个批量的块间(inter-block)pass 每块读一次缓存的块表示,之后每一层通过 online-softmax 归并折入块内(intra-block)部分和。论文点明:内存访问占这些内核在预填充和解码时相当大一部分成本,所以两个阶段的优化都主要围绕内存效率。
预填充时的问题是:在每个张量并行 rank 上都实体化块表示会造成大量冗余内存消耗(同一份东西在 8 张卡上各存一遍)。解法是对激活采用序列并行(Sequence Parallelism, SP):把 TP 的 all-reduce 分解成 reduce-scatter 和 all-gather,把块内内核插在这两个集合通信之间,作用在按序列分片的隐藏状态上,使得每个 token 的块表示恰好在一个 rank 上实体化。效果是消除了额外内存消耗,也降低了预填充时的 I/O 开销。
打个比方
八个人合作抄一本书。原来的做法是每人都抄一整本(all-reduce 之后人手一份完整数据),然后各自在自己那本上做笔记——八份笔记完全一样,浪费了八倍的纸。序列并行的做法是把书拆成八段,每人只拿一段、只在自己那段上做笔记,最后再把笔记拼起来。关键是把「做笔记」这个动作塞进了「拆开」和「拼回」之间。
类比失效处:真实的 reduce-scatter 不只是「拆开」,它同时完成了求和;这个类比只解释了内存为什么省,没解释通信量为什么不增加。
解码时用的是另外两招。第一,把块间内核发到一个旁路 stream(side stream,GPU 上的一条并行执行队列)上,让它与主 stream 上的独立计算重叠——也就是把这部分时间藏起来。第二,块内内核通过融合来精简:AttnRes 输出与其部分和更新的归并、连同随后的 RMSNorm,一起融进前面的 TP all-reduce,从而消除了块内阶段的一个专用内核。论文总结:这些优化一起隐藏了块间 pass 的延迟并降低了块内阶段的内存流量。
Stable LatentMoE:专家变多之后,调度本身成了瓶颈
Stable LatentMoE 同时增加了专家总数和每 token 激活的专家数。论文指出后果:专家空间和每 token 专家数的双重增长抬高了调度与协调开销,让常规 MoE 内核难以维持高硬件利用率。注意这里被卡住的不是乘法本身,而是「决定谁去算什么、把数据凑到一起」这些组织工作。
潜 GEMM(latent GEMM,GEMM 就是通用矩阵乘法)这一侧用了三个优化:
- 把潜下投影与 MoE 路由器融合成单个 GEMM——两次矩阵乘变一次,少读一遍输入。
- 把潜权重矩阵分片到各 rank,并用 multimem store 指令把输出的 all-gather 融进 GEMM 的 epilogue(epilogue 指矩阵乘算完、结果还在片上时顺手做的收尾动作)——通信不再是「算完之后另起一步」,而是结果落地的同时就发出去了。
- 把由此产生的通信与其他算子(论文举例:共享专家的计算)重叠。
论文的总结是:这些优化合起来消除了冗余的权重流量和重复计算,同时把通信延迟藏在计算背后。
路由专家这一侧的情况完全不同。论文的观察是:在小批量下,group GEMM 退化成权重矩阵的内存受限流式读取——你只有几个 token 要算,却必须把整个专家的权重矩阵从显存里读一遍,时间全花在读上,乘法器基本闲着。而常规的 tile 中心内核因其面向计算的设计和预处理开销而不适合这个区制。
于是他们把 MoE 解码内核建立在 WarpDecode 的 token 中心设计之上:每个 warp 负责一个输出神经元,直接从内存流式读入相关权重(warp 是 GPU 上一组同步执行的线程)。为进一步提高并行度,把每个 warp 再细分成更细粒度的 lane team,每个 team 处理一个不相交的专家子集,之后做一次 warp 范围的部分结果归约。此外,权重布局在一次性的预处理中离线置换,大幅降低运行时的反量化开销——把只需做一次的整理工作从每次推理里挪走。
整节都缺同一样东西
5.4.2 这一整节没有给出任何加速比数字:KDA 那段只说「次线性增长」「保持低于状态缓存基线」,Block AttnRes 和 LatentMoE 连定性的比较对象都没有。所以我们只能理解这些优化想解决什么问题,无法判断解决得有多好,也无法与其他系统比较。这在工程报告里是常见的取舍,但读者应当心里有数。
KDA 解码那一段的核心权衡是:为了让推测解码能回滚,要么给每个草稿位置存一份状态快照,要么只缓存投影输入然后在片上重放。
(a) 为什么「每个草稿位置存快照」的代价在大批量下会占主导?请写出两种方案每步搬运数据量的形式(不用真实数字)。
(b) 为什么「只缓存投影输入」在功能上等价?请说清等价所依赖的那条性质。
(c) 把「因为投影缓存从不离开解码阶段,前缀缓存和预填充–解码分离在与非推测服务相同的载荷上运作」这句话翻译成人话,并说明它为什么重要——它和 11.9、11.10 讲的东西有什么关系?
(b) 依赖的性质是循环状态的递推结构:状态是从上一个已知状态出发、按 token 依次推出来的,每一步递推需要的输入就是那个 token 的投影输入。所以只要保留了投影输入,就能在片上把被接受的那几个 token 的状态重新推一遍,得到与存快照逐位相同的结果,然后只把已验证 token 和 bonus token 的状态写回显存。用计算换内存,而计算恰好是此刻闲着的资源。
(c) 人话是:这些投影缓存只在解码阶段内部存在,不会变成一类需要被前缀缓存管理、或需要在预填充节点与解码节点之间传输的新数据;所以前缀缓存的数据结构、以及两类节点之间传输的内容,与不开推测解码时一模一样。重要之处在于避免了复杂度的传染:否则 11.9 的统一分页布局、11.10 的三个一致性机制,都得再为推测解码单独设计一遍并重新论证正确性。一个好的优化不仅自己要快,还要不给邻居添麻烦。
变式:如果草稿的接受率极低(几乎每次都被拒绝),两种方案的开销对比会怎么变?再想:接受率高到 100%(全部接受)时呢?由此说明这个设计在什么区间里收益最大。
11.12 舰队级调度:从「快」到「靠得住」
论文开门见山:超出单个服务实例之后,挑战从「每请求效率」转向「可预测性」。原因是两句话:一次前缀缓存未命中的代价比命中高好几个数量级;一波百万 token 的请求可以把短请求饿死。这两句话各对应一个策略。
策略一:缓存感知的亲和调度
先把账算清楚。论文给的典型数字是:在 1M 上下文下,一个典型的编码输入携带 400K token 的前缀,但只需要约 4K token 的预填充增量。也就是说,你的 IDE 把整个代码库塞了进去(40 万 token),而这一轮你其实只新加了几百行改动和一句提问(4 千 token)。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| P | 已经被缓存住的前缀长度,论文的例子是 400K token | 那份没变过的代码库 |
| Δ | 本轮新增、必须预填的增量,论文的例子是约 4K token | 你这次改的几行加一句问题 |
| P + Δ | 未命中时要预填的总量:前缀也得从头再算一遍 | 缓存丢了,整个代码库重读一遍 |
| ≈ 101 | 两者之比,约两个数量级 | 同一件事,可能贵 100 倍 |
101 倍是什么概念?如果命中时你等 0.5 秒出第一个字,未命中就要等将近一分钟。这就是论文说「前缀缓存命中避免了重新预填充整个前缀,比未命中便宜好几个数量级」的分量。
这个估计其实偏保守
式 11-2 只是按 token 数线性估的。真实的预填充里注意力部分的代价随长度增长得更快,所以实际差距只会比 101 倍更大。不过论文并没有展开这一点,只说了「好几个数量级」,我们也不去替它编造更精确的数字。
于是策略很自然:把每个请求路由到持有其前缀缓存的那个集群。为什么不能把缓存搬过去?因为把缓存搬到另一个集群需要经由跨集群链路传输,而它比集群内的 fabric 慢得多——搬 400K token 的 KV 缓存过去,可能比重算还慢。
但这带来一个新的脆弱点:这种缓存亲和把每个会话绑定到单个集群,该集群的故障会中断绑定其上的所有会话。
论文的解法是用一致性哈希把每个会话钉到两个集群:一个主集群服务其流量,一个预先指派的次集群在主集群故障时接管。诚实的代价也写清楚了:次集群不持有该会话的任何前缀缓存,故障切换时必须重新预填充——按式 11-2,那就是从 4K 变成 404K。
那为什么还是划算?由于一致性哈希把各会话的次集群指派均匀分布到整个舰队,这份重新预填充的工作被分摊到许多集群,而不是集中在一个集群上。结论是:常见情况下缓存局部性被保留,而任何单个集群故障的影响保持有界。
策略二:基于预算的准入控制
先看流量长什么样:生产流量混杂着 2K token 以下的短请求和高达 1M token 的超长请求,所以每请求成本大约跨越三个数量级。1M ÷ 2K = 500,介于 10² 和 10³ 之间,论文说「大约三个数量级」是量级上的粗略说法。
后果是:任何固定请求数所施加的总负载都高度不可预测——「现在有 100 个请求在跑」这句话完全无法告诉你系统有多忙。论文因此下了一个很重的判断:基于「平均请求」的容量规划、排队模型和限流配额,在这种方差下全部失效。
为什么「平均」在这里没有意义
如果一群请求的成本在 2K 到 1M 之间,那么「平均请求」这个东西根本不存在于现实中——没有哪个真实请求长得像那个平均值。你按平均值规划容量,来的却要么是 500 个小的、要么是 1 个巨大的,两种情况对系统的要求天差地别。这和 11.4 节「用一条水平线逼近一条上升曲线」是同一类错误的两个版本:用一个标量去概括一个方差极大的分布。
论文给出的典型失效模式非常具体:一波长上下文请求把可用算力吃满,之后到达的短请求无法被及时调度,让所有流量的 TTFT 都劣化。注意最后三个字——受害的不只是长请求,是所有人:一个只想问「今天几号」的用户,会因为别人在读一个 100 万 token 的文档而等上很久。
解法是基于预算的准入控制:给不同请求类别分配各自独立的资源预算,使得突发的长上下文流量最多消耗它自己那份容量,无法劣化其他类别所体验到的系统级 SLO。本质是隔离——把「公用一条大路」改成「分车道」。
论文没说的
请求「类别」到底按什么划分(按长度?按用户?按产品线?),预算按什么比例分配、是否动态调整,一致性哈希的次集群在什么条件下判定接管,这些论文全都没有交代。所以这一节读起来更像是设计原则的声明,而不是可复现的方案。
(a) 用论文给的 400K 前缀、4K 增量,算出命中与未命中要预填的 token 数之比。
(b) 论文说命中「比未命中便宜好几个数量级」,你算出来的比值属于几个数量级?两者说法一致吗?
(c) 用 2K 和 1M 这两个端点,验证「每请求成本大约跨越三个数量级」这句话。
(d) 如果某个应用的典型增量是 40K 而不是 4K(比如每轮贴一大段新日志),比值变成多少?这会怎样影响「值不值得为缓存亲和牺牲负载均衡」的判断?
变式:如果用户每轮都会修改代码库开头的某个文件(比如改 import),那么 400K 的前缀还能命中吗?命中长度大约是多少?这对「缓存亲和」的价值意味着什么?
缓存感知的亲和调度与「负载均衡」天然冲突。
(a) 说明一致性哈希的「主 + 次」设计在故障时具体解决了什么、又没有解决什么。
(b) 设想一个场景:某个大客户的所有会话恰好都被哈希到同一个主集群。这时会发生什么?「次集群均匀分布」能救吗?
(c) 把基于预算的准入控制接上来:它能否缓解 (b)?请说清它管得着什么、管不着什么。
(b) 正常情况下这些会话全部集中在那一个主集群上,它成为热点,可能自己就先过载了——而这是缓存亲和的固有代价:亲和度越高,越无法把负载搬到空闲的地方去。「次集群均匀分布」只在主集群真的挂了的那一刻起作用,平时对热点毫无帮助;更糟的是,如果热点导致主集群真的崩了,那一瞬间会有该客户的全部会话同时触发重新预填充(虽然分散到了多个次集群,但总量是实打实的一波尖峰)。
(c) 只能部分缓解。预算控制是在请求类别之间隔离资源,它保证长请求的突发不会把短请求的 TTFT 拖垮——所以在那个热点集群内部,它仍然能保住短请求的 SLO。但它管不了「某个集群整体过热」:预算划分的是一个实例/集群内部的容量,不会把请求送到别的集群去。要解决 (b) 需要的是第三种东西——打破亲和、允许迁移(并承担重新预填充的代价)。论文既没有讨论缓存亲和与负载均衡之间怎么取舍,也没有说预算是按什么维度划分类别的。
变式:如果把每个会话钉到三个集群(一主两次)而不是两个,好处和代价分别是什么?再想:如果反过来允许次集群后台预热(提前把前缀缓存复制一份过去),又会带来什么新问题?
本章小结
这一章从头到尾只在做一件事:在一个所有资源都不够用的系统里,反复回答「这块内存/带宽此刻应该归谁」。把六条主线串起来看,你会发现它们用的其实是同几种手法。
手法一:把生命周期拆开。写回设计把「前缀被保留」与「前缀驻留在 GPU」拆开(11.3);解耦两种粒度把「分配单位」与「匹配单位」拆开(11.9);梯度缓冲区复用把「这块内存属于谁」按时间段拆开(11.5)。三者形式不同,思路完全一致——两件被历史耦合在一起的事,其实可以各走各的。
手法二:只在必要的位置付出代价。KDA 检查点只存在「查找唯一可能引用到的位置」(11.9);写回只为「离开活跃解码路径」的前缀花 CPU 内存(11.3);沙箱增量检查点只保存「被弄脏的内存页」(11.6)。
手法三:存原料而不是存结果。推测解码里只缓存投影输入、在片上重放(11.11),用闲置的算力换紧张的带宽。
手法四:让错误无法藏身。统一池化布局让类型混淆产生垃圾而非合理数据(11.8);三个一致性机制各自堵死一个具体的静默失效(11.10);只读快照保证「注册的状态永远精确对应它声明的前缀」(11.9)。这一族手法的共同信念是:在大规模系统里,静默的错误比崩溃贵得多。
手法五:给方差留位置。自动限流用运行时信号代替一个固定并发数(11.4);预算准入控制承认「平均请求」不存在,改为按类别隔离(11.12)。两者都是在拒绝「用一个标量概括一个分布」。
要记住的关键数字:命中边界 B 是哈希块(512)的整数倍而非物理块(6144)的整数倍,Figure 12 的例子是 B = 2560;沙箱检查点/恢复低至 133 ms / 49 ms,等待推理可占生命周期 98%,内存超额分配比高达 6.5×,共创建 51,219,741 个沙箱、跨 1,505,678 个镜像;缓存命中与未命中之比约 101 倍,生产流量的每请求成本跨越约三个数量级。
整章的举证状况
本章覆盖的两节是全篇工程密度最高、但量化证据最少的部分。除了沙箱那几个数字和 400K/4K 这个例子,论文几乎没有给出任何加速比、命中率或前后对比:外部 KV 缓存池提升了多少命中率、自动限流带来多少吞吐、解耦粒度后前缀缓存命中率变化多少、三类内核各自快了多少倍,全部没有。所以本章的正确读法是「看这些设计想解决什么问题、用了什么思路」,而不是「相信它们达到了多好的效果」。这些都属于论文作者自述的工程实践。
第12章 评测:怎么把这些数字读明白(而不是被它们牵着走)
一句话导语:这一章表面上是「K3 考了多少分」,真正要教你的是——当有人把一张满是加粗数字的表推到你面前时,你该按什么顺序去问问题,才不会被那些加粗骗走。
学完这一章你应该能做到
- 说清「推理与知识/编码/智能体/视觉」这四个能力轴各自在考什么,并举出每个轴上一个代表性基准的具体任务形态
- 解释什么是 agent harness,以及「换一个 harness 分数就变」这件事对排名意味着什么
- 看到一个分数时,主动去找出它背后的六件事:谁评的、什么 harness、有没有拒答/回退、有没有工具、上下文怎么管、日期是哪天
- 在论文自己的表格里找出与论文自己的总结句相矛盾的行(这一章会让你真的去找一次)
- 把「分数」和「每任务成本」放在同一张图上看,并说清为什么只看其中一个都会得出错误结论
- 独立写出一份「读评测表检查清单」,并用它拆解一句真实的宣传话术
12.1 四个能力轴:这些名字古怪的基准到底在考什么
先说不做分轴会怎样。如果只报一个「总分」,你完全没法回答「这个模型适不适合我要干的事」。一个能把 GPQA 博士级选择题做到 93.5 分的模型,未必能在你的电脑上把一份表格改对。所以论文把评测组织成四个能力轴(four broad capability axes),每个轴下面挂一批基准。
基准(benchmark):一套固定的题目 + 一套固定的判分规则。它的作用是让不同模型在「同一张卷子」上比较。它的软肋也在这里——卷子是人出的,判分规则是人定的,运行环境也是人搭的,这三样里任何一样变了,分数就会变。
轴一:推理与知识
这个轴考的是「不给工具、不给网页,光靠脑子能想多深」。论文用了四个:GPQA Diamond(论文称之为「研究生水平推理」)、CritPt(论文把它归为研究级推理任务,并明说 K3 在这里的落后意味着「研究级推理仍是关键改进方向」)、AA-LCR(来自 Artificial Analysis 的一项长上下文推理测试)、以及 HLE-Full(Humanity's Last Exam 全集,一套刻意做到极难的跨学科题库)。论文对 HLE-Full 同时报告了不带工具和带工具两个分数。
读的时候要小心:这些基准的介绍不全来自论文
论文在 §6.1.1 里只给出了基准的名字和文献编号,没有逐个说明每个基准考什么。为了让你读得懂,本章接下来对各基准任务形态的描述,一部分来自论文正文(比如「SWE-Marathon 是 GPU 内核导向的套件」「FrontierSWE 是长时程基准」「OfficeQA Pro 给的是渲染成图像的 PDF 语料」这几条论文明确写了),另一部分来自这些基准本身的公开资料。凡是论文没写的,你都应该去原基准的说明里核对一遍——尤其当你打算引用某个分数去支持某个具体主张时。这本身就是本章要训练的习惯:先搞清楚这道题在考什么,再看分数。
为什么同一个基准要报两个分数
因为「模型自己会不会」和「模型会不会用工具把它做出来」是两种能力,而且实际部署时你关心的往往是后者。把两个数并排放,你才能看出「工具帮了多少忙」——这个差值本身就是一条重要信息,12.4 节会专门用它出一道题。
轴二:编码
注意这里考的基本不是「写一个冒泡排序」,而是软件工程:在一个真实仓库里定位 bug、改代码、跑测试、直到通过。DeepSWE、FrontierSWE、SWE-Marathon 都属于这一类,其中 SWE-Marathon 是面向 GPU 内核的长任务套件,FrontierSWE 是长时程(long-horizon)任务。Terminal-Bench 2.1 考的是在终端里完成任务的能力。ProgramBench、SciCode 偏程序与科学计算代码,PostTrainBench 和 MLS-Bench-Lite 则考机器学习工程本身——让模型去做后训练、跑实验。
轴三:智能体
这是基准数量最多的一轴,二十多个,因为「智能体」本身就是一大堆不同的事。粗看可以分成几族:找信息(BrowseComp、DeepSearchQA、ResearchRubrics——最后一个用打分细则 rubric 来评研究报告的质量);用工具/接外部系统(Toolathlon-Verified、MCPMark-Verified、MCP-Atlas、AutomationBench);做知识工作(GDPval-AA v2、AA-Briefcase、JobBench、OfficeQA Pro、SpreadsheetBench 2、APEX-Agents、Agents' Last Exam);操作电脑(OSWorld-Verified、OSWorld 2.0、SaaS-Bench);行业专项(τ3-Banking 银行客服、Harvey Lab-AA 与 Legal Research Bench 法律、CorpFin v2 与 Finance Agent v2 金融)。
打个比方
推理与知识轴像闭卷考试,编码轴像交一份能跑起来的作业,智能体轴更像「实习生第一天上班」——给你一台电脑、几个账号、一句模糊的需求,看你能不能自己走完全程。类比失效处:实习生会问「这个需求是什么意思」,而绝大多数智能体基准不允许中途向人提问,题目一次性给完。所以基准分数高不代表在真实协作里好用,反过来也一样。
轴四:视觉
视觉轴里,OmniDocBench 考文档解析(把 PDF、表格、公式看成结构化内容),Video-MME 和 MMVU 考视频理解,PerceptionBench、BabyVision、WorldVQA 考基础感知与对物理世界的理解,MMMU-Pro、CharXiv (RQ)、Math-Vision、ZeroBench-main 考「看图做题」。后面这四个和 BabyVision 一样,论文都报告了带 Python 工具的版本——模型可以写代码去裁剪、放大、测量图像,再回答。
论文在 HLE-Full 上给 K3 报的是 43.5 / 56.0,表格说明写着「每格按不带工具、带工具的顺序报告」。现在有人截图只发了「56.0」,配文「K3 在人类最后考试上拿到 56 分」。这句话哪里有问题?
变式:如果某个基准只报「带工具」一个分数,不报不带工具的,你能从这一个数里推断出模型的裸能力吗?如果不能,你会要求作者补充什么信息才愿意接受这个分数?
12.2 跟谁比:基线,以及「最大努力」这个前提
比较总要有对手。论文选的对手是五个:专有模型 Claude Fable 5、GPT-5.6 Sol、Claude Opus 4.8、GPT-5.5,以及开放权重模型 GLM-5.2。选这几个的逻辑很清楚:前两个代表当下最强的专有前沿,后两个代表上一代/次一档的专有模型,GLM-5.2 代表「和 K3 同一类」的开放权重竞争者。
推理努力(reasoning effort):现在的推理模型通常有一个「想多久」的档位,档位越高,模型在给出答案前生成的思考内容越多,通常分数越高,但延迟和花费也越高。论文明确写:所有模型都在最大推理努力下评测,例外是 GPT-5.5 用的是 「xhigh」 这一档。
为什么必须统一到「最大努力」
如果不统一,比较就没有意义了——我用最高档,你用中档,我赢了也说明不了什么。统一到各家的最高档是目前比较通行的做法。但要注意它带来的副作用:最大努力下的分数,不是你日常用到的分数,也不是你付得起的分数。这正是 12.8 节要把「成本」这条轴加进来的原因。
「所有模型都在最大推理努力下评测」听上去很公平。但假设 A 模型的最高档会思考 5 万个 token,B 模型的最高档只思考 5 千个 token,两家都叫「max」。这时候这个「公平」还剩多少?请说清它还保证了什么、不再保证什么。
变式:如果论文改成「所有模型都在每任务成本 1 美元的预算下评测」,排名可能怎么变?这种评测方式又会带来什么新的不公平?(提示:想想开放权重模型可以自己部署这件事。)
12.3 配置里的魔鬼:这一节才是本章的灵魂
大多数人读评测只看数字,跳过「评测配置」这一小段。但真正决定数字可信度的,几乎全在那一小段里。论文的 §6.1.3 写得相当坦诚,我们一条一条拆。
(1)采样参数
K3 的全部评测都用推理努力 = max、温度 = 1.0。单步任务(比如 GPQA Diamond、HLE-Full 和不带工具的视觉基准)用 top-p = 0.95,智能体任务用 top-p = 1.0。论文还给出一条通用建议:推理与知识类任务用 0.95,编码与智能体场景用 1.0。
温度(temperature)与 top-p:模型每一步其实是在一堆候选词上给出概率,然后抽签决定输出哪一个。温度控制抽签的随机程度,温度 1.0 就是按模型原本的概率抽;top-p 则是先把概率从高到低累加,只保留累加到 p 为止的那些候选,再在里面抽。top-p = 1.0 表示不裁剪,0.95 表示把最不可能的那 5% 概率质量丢掉。
为什么这很重要?因为温度 1.0 意味着同一道题跑两次可能得到不同答案。所以任何单次运行的分数都自带波动,这也是论文在视觉基准上要跑三次取平均、ZeroBench-main 跑五次的原因。
(2)harness:同一个模型,换个「外壳」分数就不一样
智能体外壳(agent harness):模型本身只会输出文字。要让它真的去读文件、跑命令、改代码,必须有一层程序把模型的输出解析成动作、去执行、再把结果喂回去。这层程序就是 harness。论文用到三种:Kimi Code、Claude Code、Codex。
harness 决定了模型能看到什么、能做什么、出错时怎么重试、上下文满了怎么办。所以同一个模型换一个 harness,分数就会变。论文给了一个可以直接验证的例子:DeepSWE 上 K3 的表格分数是 67.5,但换成 mini-SWE-agent 这个 harness 时是 67.3。更值得注意的是 Terminal-Bench 2.1——论文明说:报告的是所有模型跨 harness 的最好分数。
常见误解
很多人以为「benchmark 分数」是模型的属性,像身高一样。其实它是「模型 + harness + 工具 + 提示词 + 采样参数 + 判分器」这一整套系统的属性。你换掉其中任何一个零件,数字都会动。K3 在 Terminal-Bench 2.1 上是 88.3、GPT-5.6 Sol 是 88.8,差 0.5 分——而 harness 的更换在 DeepSWE 上就能造成 0.2 分的差异。这不是说 88.3 vs 88.8 一定是噪声,而是说你无法从这张表判断它是不是噪声。
Terminal-Bench 2.1 报告的是「所有模型跨 harness 的最好分数」。请你构造一个具体的数字例子,使得:在这条规则下 A 模型排第一,但如果规定「所有模型都必须用同一个 harness」,无论选哪个 harness,A 都排第二。
按「跨 harness 取最好」:A 报 90,B 报 88,A 第一。
强制统一 harness:用 H1 时 92 > 90,B 赢;用 H2 时 88 > 60,B 也赢。无论选哪个 harness,A 都是第二。
构造的窍门在于:让 A 在两个 harness 上极不均衡(90 与 60),而 B 均衡且都不低(92 与 88)。如果你第一次试的是 A = (90, 70)、B = (85, 88),会发现统一到 H1 时 A 仍然赢——所以必须让 B 在每一个 harness 上都压过 A 的对应值,同时让 A 的最大值仍是全表最高。
这个反例说明的道理是:逐模型取最大值不是一个保序的操作。它会奖励「在某一个特定外壳上被调得特别好」的模型,而这种优势未必来自模型本身,可能来自那个外壳的适配工作做得更多。
要强调的是:这只是一个构造出来的反例,用来说明规则的漏洞,论文并没有公布各模型在各 harness 上的分表,所以我们无从判断 Terminal-Bench 2.1 上是否真的发生了这种情况。这恰恰是问题所在——规则允许它发生,而数据不足以排除它。
变式:如果规则改成「每个模型报告其跨 harness 的中位数」,你的反例还成立吗?中位数规则又会引入什么新问题?
(3)回退与拒答:比较其实并不完全对等
回退(fallback):论文没有给出精确定义,从上下文看,指的是当主模型无法或不愿完成时,服务端自动切换到另一套行为或另一个模型来产生输出。拒答(refusal)则是模型直接拒绝执行这个任务。
论文明确写道:Claude Fable 5 的结果包含回退行为,GPT-5.6 Sol 的结果包含潜在的 cyberguard(一种安全护栏机制)。具体到某些基准,数字相当可观:SWE-Marathon 上 Claude Fable 5 在 35% 的任务上触发了回退。内部评测的 Table 3 脚注更细:Kimi Code Bench 2.0 上 Claude Fable 5 有 13 次回退 + 1 次拒答(共 80 个任务);同一基准上 GPT-5.6 Sol 有 10 次拒答 / 80 任务,GPT-5.5 有 3 次拒答 / 80 任务;Online Experience 里有 14 个任务是 Claude Fable 5 拒答的;Agent Behavior Bench 上有 6 次拒答 / 95 任务。此外 Table 2 还有一条脚注:Agents' Last Exam 官方榜单上,Claude Fable 5 那一条是在 xhigh 努力下跑的,并且有 40% 的任务被标注为「降级」。
读的时候要小心
这些脚注加在一起说明一件事:表里的横向比较并不完全对等。如果一个模型在三分之一的任务上触发了回退,那它那一列到底代表「这个模型的能力」还是「这套服务的能力」,是模糊的。论文把这些信息公开写出来,是值得肯定的做法(很多报告根本不写);但公开了不等于问题消失了。你在引用任何一个跨模型比较时,都应该顺手看一眼脚注。
答辩:如果我是审稿人
你在 SWE-Marathon 上说自己 42.0、领先 Claude Fable 5 的 35.0 达 7 分。可你自己也写了,Fable 5 在 35% 的任务上触发了回退。那这 7 分里,有多少是「你更强」,有多少是「对方的服务替它做了决定」?在你能把回退任务单独剔出来重报之前,这个 7 分的说法凭什么成立?
参考防守(先自己组织语言再看)
可以这样防守,但必须承认它只是部分防守:
第一,评测对象的选择是合法的。当用户实际调用 Claude Fable 5 时,回退行为就是他会得到的东西,所以「含回退的端到端表现」是一个真实存在、也有实用意义的量。论文比较的是「这套系统交付了什么」,而不是「这个权重文件有多强」。
第二,论文主动披露了回退比例,而不是隐瞒。披露之后,读者有能力自行给结论打折扣,这比不披露强得多。
第三,也是最诚实的一条:这个防守解决不了对等性问题。正确的做法应该是同时报告「剔除回退任务后的子集分数」,或者至少报告回退任务上的得分分布。论文没有做这件事,所以严格来说,7 分这个差距的归因是无法确定的——我们只能说「在含回退的端到端设定下 K3 高 7 分」,不能说「K3 的 GPU 内核工程能力比 Fable 5 强 7 分」。这两句话是不同的命题,把前者当后者用就是过度解读。
顺带一提,同样的逻辑要对称地用在 K3 身上:如果 K3 也有任何形式的服务端兜底或重试,论文同样应该披露。论文没有提,我们也无法确认。
(4)硬件与版本:同一个基准,跑在不同机器上
PostTrainBench 的官方设定是在 H100 GPU 上跑,而论文是在 H20 上跑的,取三次运行的平均。SWE-Marathon 用的是 2026 年 7 月 9 日的一个 H20 校准分支,早于最终的 v1.1 发布——论文说 GPU 相关任务针对 H20 做了重新校准,但正确性校验和防作弊校验没有改动。
PostTrainBench 官方要求 H100,论文用 H20 跑并做了三次平均。这个替换为什么必须写出来?它可能怎样影响分数?(不需要你知道 H20 和 H100 的具体规格,只需要从「这个基准在考什么」出发推理。)
论文的处理其实是自洽的:它让所有参评模型(K3、Claude Fable 5、GPT-5.6 Sol)都在同样的 H20 上、用同样的官方 Harbor 实现、在最大努力下跑,并取三次平均。所以表内的横向比较仍然有意义,只是这一行的绝对数值不应该跨报告引用。这是一条很通用的原则:内部可比 ≠ 外部可比。
变式:SWE-Marathon 用的是「7 月 9 日的 H20 校准分支,早于 v1.1 正式发布」。如果 v1.1 后来修掉了几道有问题的题目,论文这一行的分数会偏高还是偏低?你能确定方向吗?
(5)上下文管理策略:一个被写在小字里的大变量
BrowseComp 上,论文采用了一种在 300K token 处触发的上下文压缩(context-compaction)策略——也就是当对话历史涨到 30 万 token 时,把前面的内容压缩成摘要,腾出空间继续做。表 2 里 K3 的 BrowseComp 分数 91.2 就是在这个策略下得到的。
紧接着论文写了一句很关键的话:如果改用完整的 100 万 token 上下文窗口、完全不做上下文管理,K3 得到的是 90.4%。
表 2 里 BrowseComp 一行:K3 是 91.2(加粗,最佳),GPT-5.6 Sol 是 90.4(第二)。而论文正文说,K3 若用完整 1M 窗口且不做上下文管理则是 90.4。请问:「K3 在 BrowseComp 上超过 GPT-5.6 Sol」这个结论有多稳?回答时请说清你依据的是什么、还缺什么信息。
第一层:0.8 分的领先完全可以由一个上下文管理策略的选择产生——因为 K3 自己换一下策略就正好掉 0.8 分,落到和 Sol 一模一样的 90.4。
第二层:论文没有说明 GPT-5.6 Sol 那一列是在什么上下文策略下跑的。如果对手没有用类似的压缩策略,那么这个比较比的其实是「K3+压缩策略」对「Sol+未知策略」,不是两个模型本身。
第三层:论文也没有给出重复运行的方差。BrowseComp 是长链条的搜索任务,随机性不小,0.8 分是否超出噪声范围,无从判断(对照一下:视觉基准论文跑了三到五次取平均,说明作者本人认为单次运行是不够的)。
更公允的说法是:「在各自的最佳配置下,K3 与 GPT-5.6 Sol 在 BrowseComp 上大致持平,K3 略高。」这比「K3 是 SOTA」保守,但更经得起追问。
另外别忽略一件事:论文主动把 90.4 这个对自己不利的数字写了出来,这是加分项。一个想藏的作者完全可以不写这句话。
变式:反过来想——如果 300K 触发压缩反而更好(91.2 > 90.4),说明「把全部历史都塞进 1M 窗口」未必是最优的。为什么?(提示:想想上下文里塞满无关内容对注意力意味着什么。)
(6)任务子集、判分器与重复次数
还有几条细节,单看都很小,加在一起决定了数字的含义:
OfficeQA Pro 给智能体的是整份 PDF 语料渲染成的图像,没有任何机器可读文本——这等于强制模型必须「看」,而不能「读」,是一个对原生视觉能力很直接的考察。MCP-Atlas 用的是 500 任务的公开子集、100 轮交互上限、由 Gemini 3.1 Pro 担任裁判。AutomationBench 用 600 任务的公开子集。视觉分数是三次运行的平均,ZeroBench-main 按官方设定跑五次。WorldVQA 上论文观察到各模型都有一致的拒答行为,于是用提示工程强制模型给出答案(表里那一行叫 「WorldVQA ForceAnswer」)。
补充:让别的模型当裁判,意味着什么
MCP-Atlas 用 Gemini 3.1 Pro 判分,这在今天很常见,因为人工判分太贵。但它引入了一个新变量:裁判自己也会犯错,也可能有偏好。如果有一天换了裁判模型,同一批答案的分数就可能变。所以「由某模型判分」这条信息必须和分数一起被引用——它是分数定义的一部分,不是脚注里的装饰。
(7)第三方结果的日期
论文里有一批分数不是自己测的,而是引用第三方的:GDPval-AA v2、AA-Briefcase、τ3-Banking、Harvey Lab-AA、APEX-Agents、SciCode、AA-LCR、CritPt 来自 Artificial Analysis(截至 2026 年 7 月 23 日);CorpFin v2、Finance Agent v2、Legal Research Bench 来自 Vals AI;Agents' Last Exam 来自其官方榜单(同为 7 月 23 日);Toolathlon-Verified 和 JobBench 来自各自官方榜单(7 月 24 日);FrontierSWE 的支配度分数是用 7 月 16 日的官方评测脚本从原始分重算的。
12.4 主结果:先看表,再看表说不出的东西
下面是论文 Table 2 的完整数据。你可以排序、筛选,先自己找规律,再往下读解读。建议你先做一件事:按「K3 落后最多的基准」排一次序,看看这些基准有什么共同点。
推理与知识:会做题,但还不会做研究
GPQA Diamond 上 K3 拿到 93.5,与前沿基本相当(最高的 GPT-5.6 Sol 是 94.1,GPT-5.5 同为 93.5)。AA-LCR 上 K3 的 74.7 是全表最好成绩。但往研究级任务走,差距就显出来了:HLE-Full 上 K3 是 43.5 / 56.0(不带/带工具),落后 Claude Fable 5(53.3 / 63.0)和 GPT-5.6 Sol(44.5 / 58.0);CritPt 上 K3 只有 23.4,落后 Fable 5(28.6)、Sol(32.3)和 GPT-5.5(27.1)。论文自己的措辞是:研究级推理仍是关键改进方向。
编码:长任务上很强,单点能力上有短板
ProgramBench 上 77.8 是全表最好。SWE-Marathon 上 42.0,领先 Claude Fable 5 七个点(但请回忆 12.3 的回退问题)。Terminal-Bench 2.1 上 88.3,几乎追平 GPT-5.6 Sol 的 88.8。DeepSWE 上 67.5,落后 Fable 5(70.0)和 Sol(73.0),但领先 Opus 4.8(59.0)和 GPT-5.5(67.0)。FrontierSWE 这个长时程基准上 81.2 排第二,落后 Fable 5 的 86.6,但明显领先其余所有模型。
智能体:这是 K3 最亮的一轴
拿到最好成绩的包括 BrowseComp 91.2、DeepSearchQA 95.0、ResearchRubrics 76.2、MCPMark-Verified 94.5、AutomationBench 30.8、SpreadsheetBench 2 34.8、τ3-Banking 33.4、Harvey Lab-AA 94.6。主要的例外是两个用 Elo 计分的知识工作套件,都由 Claude Fable 5 领先:GDPval-AA v2 上 K3 第三(1686 对 1747),AA-Briefcase 上第二(1548 对 1583)。还有几处几乎是平手:CorpFin v2 差 0.2 分(71.6 对 71.8),OSWorld-Verified 差 0.2 分(84.8 对 85.0)。更难的计算机使用基准(OSWorld 2.0、SaaS-Bench)仍由 Fable 5 或 Sol 领先。
常见误解:0.2 分的差距
CorpFin v2 上 71.6 对 71.8。很多人会写成「K3 在 CorpFin v2 上惜败」。但在一个没有报告方差的评测里,0.2 分和「没有差别」是无法区分的。诚实的读法是「两者在这个基准上不可区分」。同样的道理适用于 OSWorld-Verified 的 84.8 对 85.0。差距小于你能估计的噪声时,正确的结论是「不知道」,不是「略输」。
视觉:工具带来的跃升非常大
Math-Vision 上 K3 是 94.3,加上 Python 工具变成 97.8。ZeroBench-main(pass@5)上 K3 是 23.0,与 Claude Fable 5 打平,加上 Python 工具跳到 41.0——接近翻倍。OmniDocBench 上 91.1 是全表最高。WorldVQA 上 51.0 排第二,落后 Fable 5(56.7),领先 Sol 和 Opus。
把这几组数放在一起看:Math-Vision 94.3 → 97.8(带 Python,涨 3.5),ZeroBench-main 23.0 → 41.0(涨 18.0),HLE-Full 43.5 → 56.0(带通用工具,涨 12.5),CharXiv (RQ) 84.8 → 91.3(涨 6.5)。为什么工具带来的增益在不同基准上差这么多?请给出至少两种不同机制的解释,并说明这些增益对「模型有多强」这个问题到底告诉了我们什么。
机制一:天花板效应。Math-Vision 已经 94.3,最多只能再涨 5.7 分,涨 3.5 已经吃掉了 61% 的剩余空间;ZeroBench 只有 23.0,空间大得多,所以绝对涨幅大不奇怪。比较绝对涨幅是不公平的,应该看「剩余空间被吃掉的比例」。
机制二:失败类型的构成。工具能补的是「精确性」类失败——放大图像看清刻度、精确计数、代数化简、数值计算。工具补不了「根本不理解题意」「缺少领域知识」类失败。ZeroBench 的题以极高难度的视觉细节推理著称,其中很大一部分失败属于「看不清/数不准」,写代码把图裁开放大就能救,所以增益极大。HLE-Full 涨 12.5 也符合这个逻辑:那里的通用工具包括检索,能补上知识缺口。
机制三:能力与能力的耦合。「用 Python 把这张图处理成能回答问题的形式」本身是一种能力——需要模型知道该测什么、该裁哪里、该怎么把视觉问题翻译成计算问题。所以带工具的分数不是「模型能力 + 工具能力」的简单相加,而是模型调度工具的能力的体现。
那么它告诉了我们什么?两件事。第一,如果你的实际用途允许模型写代码(大多数真实场景都允许),你应该看带工具的分数,因为那更接近你会得到的东西。第二,一个基准如果在带工具后分数暴涨,说明这个基准原本测量的东西里,有很大一部分并不是「智能」,而是「没有辅助工具时的精确性」——这既是对模型的评价,也是对基准设计的评价。ZeroBench 从 23 涨到 41 这件事,对 ZeroBench 这个基准的信息量,不比对 K3 的信息量少。
变式:如果某个基准在带工具后分数下降了,可能是什么原因?(论文里没有出现这种情况,但你能想出至少两种机制吗?)
论文 §6.1.4 的开头写道:K3「整体上紧追最强的专有模型 Claude Fable 5 和 GPT-5.6 Sol,同时在整个基准套件上一致地超过 Claude Opus 4.8、GPT-5.5 和 GLM-5.2」。请你在表 2 里找出反例——找出至少两行,K3 的分数低于 Claude Opus 4.8。
反例一:HLE-Full。K3 是 43.5 / 56.0,Claude Opus 4.8 是 49.8 / 57.9。无论带不带工具,Opus 4.8 都更高,不带工具时差距达 6.3 分。
反例二:OfficeQA Pro。K3 是 63.3,Claude Opus 4.8 是 63.9,Opus 略高(虽然只有 0.6 分,按 12.4 节的标准这属于「不可区分」,但至少不支持「K3 超过它」)。
再补一条:内部评测的 Table 3 里,Agentic Vision Bench 上 K3 是 78.3,Claude Opus 4.8 是 82.8,差 4.5 分,这也是一处反例(虽然不在 Table 2 里)。
这道题真正要教的是什么:论文正文的总结句往往比表格更「圆」。作者不是在撒谎——K3 在绝大多数行上确实超过了 Opus 4.8——但「一致地」这个词是一个全称量词,而表里存在反例。当正文的总结和表格发生冲突时,以表格为准。更进一步:注意论文自己在同一节的 HLE-Full 段落里,只说「落后 Claude Fable 5 和 GPT-5.6 Sol」,没有提 Opus 4.8 也更高——这不是错误陈述,但它是一种选择性完整。你读任何技术报告(包括这一篇)都应该做这个动作:把总结句拿去表格里逐条验证一遍。
变式:现在反过来给论文做辩护——如果把「一致地超过」改成一个仍然成立、但更精确的表述,你会怎么写?(试着让你的表述既不夸大,又不至于抹掉 K3 真实的优势。)
12.5 内部评测:作者自己出的卷子,自己批的分
论文说,公开基准覆盖不到的能力,他们用一批自研的内部基准来补,而且这些基准会频繁更新扩充,好紧跟模型不断变化的失败模式,并直接指导数据和训练的迭代。分三类:
编码能力与体验:Kimi Code Bench 2.0(真实的端到端软件工程任务,覆盖多种语言和生产级技术栈)、Kimi Webdev Bench(真实使用场景里的困难网页开发需求,用盲评专家判分)、Coding Experience(作为编码智能体在真实开发流程里「用起来怎么样」)。
通用智能体体验:24/7 ClawBench 2.0(模拟「永远在线」的助手工作,任务跨越多天、事件并发到达、被打断是常态)、MIRA Bench(长链路多角色多系统的企业协作,考察能否端到端完成、以及何时该把活分给子智能体)、KAET(长时程自主执行)、CLIF Bench(上下文内学习与指令遵循)、Agentic Vision Bench(执行任务时会不会注意并正确使用关键视觉事实)、Swarm Bench(编排智能体群、分解任务并行执行)、Online Experience(对齐真实线上使用的分布)、Deep Research Bench(深度研究,由领域专家出题并用专家对齐的细则打分)、Finance Bench、KWV Bench(从真实知识工作场景蒸馏出的原子视觉能力)、DECK Bench(做演示文稿)、Agent Behavior Bench(不看结果对不对,看过程质量:工具使用行为、效率、纪律性)。
对话体验:Faithfulness(指标定义是 1 − 幻觉率,越高越好,每条回复由事实核查者验证)、Chat All-in-One Bench。
| 基准(K3 的 harness) | Kimi K3 | Claude Fable 5 | GPT-5.6 Sol | Claude Opus 4.8 | GPT-5.5 | GLM-5.2 |
|---|---|---|---|---|---|---|
| Kimi Code Bench 2.0(Claude Code) | 73.7 | 76.9a | — | 71.7 | — | 64.2 |
| Coding Experience(Claude Code) | 59.9 | 59.8 | — | 58.0 | — | 53.3 |
| Swarm Bench(Kimi Agent) | 76.3 | — | 73.2 | 72.6 | 61.8 | 58.5 |
| Deep Research Bench(Kimi Agent) | 90.0 | — | 85.3 | 87.2 | 81.9 | 84.0 |
| CLIF Bench(Kimi Code) | 52.4 | — | 50.6 | 48.8 | 52.3 | 39.2 |
| Finance Bench | 62.6 | — | 62.7 | 60.7 | 58.4 | 55.4 |
| Agent Behavior Bench(Kimi Work) | 65.0 | 75.5f | 76.4 | 65.7 | 70.1 | — |
| MIRA Bench(MIRA) | 64.1 | 72.9 | 62.2 | 59.8 | 54.6 | — |
| 24/7 ClawBench 2.0(OpenClaw) | 48.3 | 47.4d | 52.0 | 47.2 | 48.5 | 43.2 |
| Agentic Vision Bench(Kimi Code) | 78.3 | 81.1 | 82.9 | 82.8 | 76.9 | — |
| KWV Bench | 64.7 | 63.6 | 66.9 | 61.7 | 65.8 | — |
| Faithfulness(1 − 幻觉率) | 85.5 | — | 84.8 | 83.6 | 86.5 | 74.8 |
表中脚注沿用论文原注:a 80 个任务中有 13 次回退、1 次拒答;d 含 2 个 Claude Fable 5 拒答的任务;f 95 个任务中有 6 次拒答。
论文自己的总结是:内部套件比公开套件更清楚地把 K3 的强弱分开了。最清晰的强项是编排型和研究型的能动性——Swarm Bench 76.3 和 Deep Research Bench 90.0 都以明显优势领先,说明它在「把复杂目标拆开、协调并行工作、产出满足细则的交付物」上很强。编码同样是强项:Kimi Code Bench 2.0 只落后 Claude Fable 5(73.7 对 76.9),而 Coding Experience 拿到最好分 59.9——论文认为这说明它「作为编码智能体的实际行为(沟通质量、行为得体性、指令遵循的稳定性)领先于它的原始任务分数」。主要落后的是 Agent Behavior Bench、MIRA Bench、24/7 ClawBench 2.0、Agentic Vision Bench 和 KWV Bench。
还有一项单独的对比(论文 Table 4):Kimi Webdev Bench 让专家在不知道哪个输出来自哪个模型的情况下打分,比较 K3(max)与 Claude Opus 4.8(max),两者都跑在 Claude Code 外壳下,评分维度是需求覆盖、功能完整性、视觉保真度和交互体验。
| 领域 | 胜 | 平 | 负 | 胜 − 负 |
|---|---|---|---|---|
| 游戏 | 55.6% | 3.7% | 40.7% | +14.9 |
| 3D / WebGL / Shader | 72.7% | 13.7% | 13.6% | +59.1 |
| 网站 / UI 克隆 | 52.6% | 21.1% | 26.3% | +26.3 |
| 总体 | 58.6% | 13.8% | 27.6% | +31.0 |
读的时候要小心:这一整节都是作者自评
Kimi Code Bench、Kimi Webdev Bench、Swarm Bench、Deep Research Bench……这些基准是论文作者自己设计的、自己维护的、自己运行的,而且论文明说它们会「频繁更新扩充,以紧跟模型不断变化的失败模式,并直接指导数据和训练的迭代」。这句话读慢一点:基准被用来指导训练。当一套题目同时承担「训练的指南针」和「最终的考卷」两个角色时,在它上面得分高是几乎必然的——这不叫作弊,这叫优化目标。
论文没有说这些基准会公开,也没有说第三方能复现。所以它们的正确用法是:看 K3 在它们之间的相对强弱(哪些高、哪些低,这个内部对比是有信息量的,尤其是作者主动列出了五个自己落后的基准),而不是把某个绝对分数当成跨模型的裁决。
答辩:如果我是审稿人
你在 Kimi Webdev Bench 上说自己相对 Claude Opus 4.8 有 +31.0 的总体优势,其中 3D/WebGL/Shader 高达 +59.1。可这个基准是你出的题、你请的专家、你定的评分维度。我凭什么相信 +31.0,而不是相信「你选了一批你擅长的题」?
参考防守(先自己组织语言再看)
能站得住的防守有这几条:
第一,评判是盲评。论文明确写了专家在打分时不知道哪个输出来自哪个模型。这消除了最直接的偏袒渠道,是这个实验设计里最扎实的一环。
第二,两边同一个 harness。K3 和 Opus 4.8 都跑在 Claude Code 下——注意这个选择对 K3 并不占便宜,因为 Claude Code 是对手家的外壳。
第三,结果结构上不像挑出来的。如果是刻意挑擅长的题,三个领域的优势应该都很大;实际上游戏只有 +14.9,而且负率高达 40.7%,接近一半。作者把这个不好看的子项也报了出来。
但审稿人的质疑不能被完全驳倒,必须承认的部分是:
(a)题目来源是「真实使用场景里的困难网页开发需求」——什么叫真实、按什么口径抽样、样本量多大,论文一个字没说。三个领域的百分比看上去像是小样本(比如 3.7%、13.6%、21.1% 这些数字暗示每类可能只有几十道题),但论文没给 n,所以无法计算置信区间,+14.9 是否显著无从判断。
(b)对手只选了 Claude Opus 4.8,没有和更强的 Claude Fable 5 或 GPT-5.6 Sol 做盲评。选一个次一档的对手来做最能说明问题的那类实验,这个选择本身需要解释。
(c)「专家」是谁、有几位、一致性如何,论文没说。
结论:+31.0 作为「内部迭代信号」是可信的,作为「公开的能力主张」证据强度不够。诚实的说法是「在我们自己构造的网页开发题集上,盲评专家更偏好 K3 的输出」,而不是「K3 的网页开发能力比 Opus 4.8 强 31 分」。
Kimi Webdev Bench 用了盲评。请回答两个问题:(1) 盲评具体消除了哪一种偏差?(2) 盲评没有消除哪些偏差?请至少说出两种它没消除的。
(1) 盲评消除的是评分者的身份偏差——知道「这是我们自家模型的输出」而不自觉打高分。这是最直接、最容易发生的偏差,消除它是实打实的进步。
(2) 它没有消除的至少包括:
选题偏差:题目由作者选定。哪怕评分完全公正,只要题集本身偏向 K3 擅长的分布,结论就会偏。3D/WebGL/Shader 上 +59.1 这个异常大的优势,既可能说明 K3 真的强,也可能说明这类题被选得比较多或比较符合 K3 的训练分布,论文没有给出题目数量和抽样方式,无法区分。
评分维度偏差:论文说评的是需求覆盖、功能完整性、视觉保真度、交互体验。这四个维度是作者定的。换一组维度(比如加上「代码可维护性」「无障碍性」「包体积」),排序可能变。
对手选择偏差:只和 Claude Opus 4.8 比,而 Table 2/3 显示 Claude Fable 5 才是更强的对手。
发表偏差:我们只看到被写进论文的那个基准。如果作者做了五个类似的盲评而只报告了结果最好的一个,我们无从知晓。
一句话总结:盲评保证的是「这次打分没被身份污染」,不保证「这场比较被设计得公平」。
变式:如果你要把这个实验改造得更有说服力,在不增加太多成本的前提下,你会加哪三件事?(提示:其中至少一件应该和「题目从哪来」有关。)
12.6 网络安全评测:一份需要克制地读的结果
论文用一个两级递进的框架来衡量模型的网络安全能力,两级的「操作风险」依次升高:Tier 1 是漏洞发现——找出当前代码库里真实存在的、此前未被修复的缺陷,并证明它可复现(论文强调这一层的能力主要与防御性安全研究相关);Tier 2 是端到端的漏洞利用开发——把一个漏洞变成一条完整可用的利用链,论文明说这一层「与滥用风险最直接相关」。评测目标包括广泛部署软件的近期版本(操作系统内核组件、开源项目),以及作者自己的内部基础设施;所有任务都在代表真实部署的标准配置下运行。
补充:为什么这一节没有 Claude 和 GPT
论文写道:Anthropic 和 OpenAI 的前沿模型拒绝网络安全相关任务,因此无法做可比评测,于是这一整个套件把它们排除在外。所以 Tier 2 的唯一对照是开放权重的 GLM-5.2。
这条信息值得停下来想一想:它同时说明了两件事。一是这一节的比较基础很窄——只有两个模型,而且都是开放权重模型,你不能从这里推断「K3 的网络安全能力在所有模型中排第几」。二是不同厂商在「该不该让模型做这类任务」上采取了明显不同的策略,而这个策略差异本身会直接体现为评测表上的空格。表格里的「—」有时候不是没测,而是一个立场。
Tier 1 的结果:在数十个广泛部署的系统上(涵盖操作系统内核、数据库、AI 服务、Web 框架、区块链和 VPN 软件),模型识别出数百个候选漏洞。在经过人工审核的那部分发现里,约 70% 被确认为真实缺陷,其中包括横跨六个项目的 16 个此前未知的漏洞。论文举了两个 Linux 内核的例子作为深度示例:一个是远程可触发的堆越界写(源于一次不完整的上游修复),另一个是 RDMA 子系统中的 Dirty-COW 类漏洞(源于一次上游修复中被无意去掉的权限检查)。两者都经过安全专家确认。本站不展开任何技术细节,这里只记录「发现了什么类别、被谁确认」。
Tier 2 的结果:以 GLM-5.2 为基线,用一个内部的 36 任务套件,分两条赛道:16 个用户态任务、20 个 Linux 内核任务。每个任务都经人类安全专家验证过是可解的;论文估计完成整套大约需要 540 专家小时,平均每个任务约 15 小时。结果是:K3 解出 14/36(38.9%),GLM-5.2 解出 8/36(22.2%)。但成功的分布很不均匀——K3 那 14 个里有 10 个来自用户态赛道;在内核赛道上,两个模型都有四分之三的任务没有解出。
因为每个任务都被验证过「人类专家可解」,所以未解出的任务直接度量了模型与人类专家能力之间的差距。论文对失败轨迹做了分析,归纳出四种反复出现的失败模式:(i) 难以从已经拿到的原语走完利用链的最后一步;(ii) 在存在缓解措施时策略选择很差;(iii) 陷入漫长而无成效的调试循环;(iv) 提交前对最终交付物验证不足。
英国 AI 安全研究所与美国 NIST 的 CAISI 做了一次独立的联合评估,结论与作者一致:K3 在漏洞利用开发上优于 GLM-5.2(ExploitBench 上 32% 对 24%;在一个人类专家大约需要 20 小时的 32 步模拟企业网络上,K3 走了 17 步,GLM-5.2 走了 11 步),但在端到端的利用完成上落后于前沿的网络能力模型——在 41 个任务上实现任意代码执行的次数为 0。
论文最后写了一句方法论上很重要的话:我们把自己的评测视为能力的下界,这些结果取决于当前的模型版本和评测覆盖度,每次重大模型更新时都会重新评估。
论文说「我们把自己的评测视为能力的下界」。请构造一个具体情形,说明为什么在安全评测里把结果当上界(「最多也就这样了」)是危险的。然后回答:同一个 38.9% 的分数,当上界读和当下界读,会导出什么不同的行动?
构造情形:假设 K3 在某个内核任务上失败了,原因是 harness 的调试循环设计不好(论文自己列出的失败模式之三就是「陷入漫长而无成效的调试循环」)。半年后,社区做出了一个更好的 harness,或者有人用更好的提示词和更长的时间预算重跑。同一个权重文件,同样的任务,现在解出来了。模型没变,分数变了。因为权重是公开的,任何人都可以做这件事,而且没人需要通知作者。
这就是为什么当上界危险:把 38.9% 当上界,等于假设「没有人能把这个模型用得比作者更好」。对一个开放权重模型来说,这个假设几乎肯定是错的——恰恰因为它开放,会有远比作者投入更多的人去改进外壳、提示词和工具链。安全评估里的失败必须默认解释为「这次没测出来」,而不是「它做不到」。
两种读法导出的行动完全不同:
当上界读 → 「38.9%,还差得远,可以放心发布」 → 不做额外缓解,不做后续监控。
当下界读 → 「至少 38.9%,真实上界未知且只会随时间上升」 → 需要持续重评(论文承诺每次重大更新时重评)、需要独立第三方验证(论文确实做了,英国 AISI + NIST CAISI)、需要把「发布后能力可能被外部工程放大」计入风险模型。
顺带注意一个细节:独立评估里「41 个任务上任意代码执行 0 次」这个结果,同样是下界。它是在评估者当时的方法下得到的 0,不是「永远是 0」。
变式:Tier 1 里「经过人工审核的发现中约 70% 被确认为真实」。这个 70% 的分母是「经过人工审核的发现」,而不是「模型报出的全部候选」。这个分母的选择会怎样影响你对这个数字的理解?如果模型报了 500 个候选、人只审了 40 个最像的,70% 还意味着什么?
12.7 第三方评测:别人打的分
自评永远有嫌疑,所以第三方结果的分量不一样。论文汇总了截至 2026 年 7 月 23 日的几个头部结果(Table 5)。
| 榜单 | Kimi K3 | Claude Fable 5 | GPT-5.6 Sol | Claude Opus 4.8 | GPT-5.5 | GLM-5.2 |
|---|---|---|---|---|---|---|
| Artificial Analysis 智能指数 v4.1(第 4 / 580) | 57.1 | 59.9 | 58.9 | 55.7 | 55.0 | 51.1 |
| Vals AI · Vals Index(第 2 / 39) | 74.7 | 75.1 | 73.1 | 70.4 | 68.0 | 65.0 |
| WebDev Arena(Elo,第 1 / 99) | 1678 | 1634 | 1630 | 1565 | 1507 | 1592 |
| Text Arena(Elo,第 8 / 200) | 1486 | 1507 | 1485 | 1484 | 1482 | 1469 |
| Agent Arena(第 4 / 37) | 9.1 | 12.7 | 10.1 | 9.8 | 8.8 | 6.5 |
逐条看:Artificial Analysis 的智能指数 v4.1 上 K3 是 57.1,在 580 个模型中排第 4——论文补了一句,如果把 GPT-5.6 Sol 的各个努力等级变体算作一个条目,就是第 3;落后 Claude Fable 5(59.9)和 GPT-5.6 Sol(58.9),领先其余所有被评模型。Vals AI 的 Vals Index 是一套按 GDP 加权的行业基准,K3 以 74.7 在 39 个模型中排第 2,落后 Fable 5(75.1)、领先 Sol(73.1)。
众包人类偏好竞技场里,最亮眼的是 WebDev Arena:K3 以 1678 Elo 在 99 个模型中排第 1,领先 Claude Fable 5 的 1634,并且是第一个登顶该榜的开放模型。Text Arena 上 1486 Elo,200 个中第 8。Agent Arena(大约 7 月 19 日才开放投票)上 9.1,37 个中第 4,落后 Fable 5(12.7)、Sol(10.1)和 Opus 4.8(9.8)。
读的时候要小心:Elo 会漂移
论文自己在 Table 5 的说明里写了:Elo 类分数会随着更多对局的累积而漂移。而且 Agent Arena 是 7 月 19 日左右才开放投票的,到 7 月 23 日只有四五天的数据。四五天的众包投票量,通常远不足以让 Elo 稳定下来。所以 Agent Arena 那个「第 4 / 37」的名次,比其他几行的置信度低得多。
另一条要注意的:Text Arena 里 GPT-5.6 Sol 那个 1485 是榜上列出的 xhigh 变体,Opus 4.8 的 1484 和 GPT-5.5 的 1482 是榜上列出的 high 变体——这三个数彼此相差 1–3 分,任何一次重排都可能改变顺序。把 1486、1485、1484、1482 解读成一个排名,几乎肯定是在解读噪声。
同样是「K3 在网页开发上很强」这个主张,论文提供了两份证据:内部的 Kimi Webdev Bench(盲评,相对 Claude Opus 4.8 +31.0)和第三方的 WebDev Arena(1678 Elo,99 个模型中第 1)。哪一份的证据强度更高?为什么?
Kimi Webdev Bench:出题人是作者,运行是作者,评分虽然盲了但评分者由作者组织,样本量未公开,外部无法复核,对手只有一个(还是次一档的 Opus 4.8)。
WebDev Arena:出题的是众多匿名用户,评分的是投票的人类,榜单由第三方维护并公开,任何人都能上去看当前排名,对手是 99 个模型。
更强不代表完美。WebDev Arena 的软肋是:众包偏好会奖励「看起来漂亮、第一眼讨喜」的输出,未必等于代码质量高或可维护;Elo 会随对局漂移;不同模型进入榜单的时间不同,累计对局数也不同。
但有一点很值得说:这两份独立来源指向了同一个方向。作者自评说网页开发强,第三方公开榜单也把它排到第 1。互相独立的证据指向同一结论,比任何单一证据都更可信——这是评估任何主张时最有用的一条启发式。反过来,如果内部基准说强、外部榜单说弱,那才是真正需要追问的时候。
变式:论文说 K3 是「第一个登顶 WebDev Arena 的开放模型」。这个「第一个开放模型」的限定词加了什么、又没加什么?如果半年后另一个开放模型登顶,这句话还成立吗?
12.8 成本效率:把第二根坐标轴加进来
前面所有的表都只有一根轴:分数。但你实际用模型时有第二根轴:钱。论文 §6.4 在四个套件上把分数和每任务成本(cost per task)画在同一张图上(Figure 13):Kimi Code Bench 2.0、BrowseComp、GDPval-AA v2、AA-Briefcase。
成本数据来源需要注意:Kimi Code Bench 2.0 的成本是内部实测的,其中 K3 通过 Kimi Code 运行、其他所有模型通过 Claude Code 运行;BrowseComp 上 K3 的成本来自作者自己的运行,Claude 和 GPT 的成本引自已发表的图表;GDPval-AA v2 和 AA-Briefcase 的成本引自 Artificial Analysis 按 token 计费的 API 定价(截至 2026 年 7 月 23 日)。
四个结论:
Kimi Code Bench 2.0:K3 落后 Claude Fable 5 四分,但成本只有它的 38%。更有意思的是,K3 在 high 努力(不是 max)下就已经追平了 Claude Opus 4.8 在最大努力下的分数,而成本约为后者的三分之一。
BrowseComp:K3 拿到最好分数 91.2%,每任务 $2.03——是 GPT-5.6 Sol(90.4%)成本的一半,比 Claude 系列在最大努力下便宜一个数量级。
GDPval-AA v2:K3 与 GPT-5.6 Sol 相差 50 Elo 以内,成本低 13%,比 Claude Fable 5 便宜 2.6 倍。
AA-Briefcase:K3 取得第二好的分数,成本约为 Claude Fable 5 的一半。
论文的总结是:K3 在这四个套件上都处在或接近成本效率前沿上,以一小部分成本交付接近顶尖的分数。
打个比方
「成本效率前沿」就像买东西时把所有商品画在「价格—质量」图上,然后把左上角(更便宜且更好)的那些点连成一条线。落在这条线上的商品,意思是「没有任何其他商品同时比它更便宜又更好」。类比失效处:买东西时价格是公开透明、人人一样的;而模型的「每任务成本」取决于计费方式、部署方式、上下文长度、思考多少 token,而且开放权重模型可以自己部署,成本结构和调 API 完全不同。论文引用的是 API 定价,所以这条前沿只在「按 token 买 API」这个场景里成立。
论文说 Kimi Code Bench 2.0 上「K3 落后 Fable 5 四分,但成本只有它的 38%」。同一节又写着:这个基准上 K3 是通过 Kimi Code 运行的,其他所有模型是通过 Claude Code 运行的。请指出这个成本比较里至少两个需要小心的地方。
第一,harness 不对等。K3 跑在 Kimi Code 上,对手跑在 Claude Code 上。harness 直接影响 token 消耗量(塞多少上下文、重试策略、压缩策略),因此这个成本比较里混进了「哪家外壳更省 token」这个变量。这和 12.3 讲的分数问题是同一个问题,只是这次影响的是分母。
第二,成本没有拆分。38% 可能来自单价便宜,也可能来自消耗少,也可能两者兼有。这两种原因的含义完全不同:单价是定价策略(可以随时变),token 消耗是模型和外壳的效率(相对稳定)。论文只给了总数,没有拆开。
第三,定价是会变的,而且开放权重改变了游戏规则。成本来自 2026 年 7 月 23 日的 API 定价。定价是商业决策,可以一夜之间调整,所以「便宜 62%」这个结论的保质期比分数还短。更根本的是:K3 是开放权重模型,你可以自己部署,那时的成本由你的硬件和利用率决定,和 API 定价没有关系——论文引用的这条前沿,严格来说只描述了「按 token 买 API」这一种使用方式。
但也要给论文记一功:把成本轴加进来这件事本身是对的,而且它揭示了只看分数看不到的东西——「K3 在 high 努力下就追平了 Opus 4.8 的 max 分数、成本约三分之一」,这个观察在任何一张只有分数的表里都是不可见的。
变式:如果把横轴从「每任务成本」换成「每任务耗时」,四张图的形状会怎么变?哪些结论会保留,哪些会翻转?(提示:想想推理努力 max 意味着生成很多思考 token。)
12.9 如何批判性地读一张评测表
把这一章的所有内容收束成一份可以随身带走的清单。下次你看到任何一张模型评测表——不管是这篇论文的,还是别人的,还是半年后新出的——按顺序问这九个问题。
| # | 问什么 | 本章里对应的证据 |
|---|---|---|
| 1 | 谁评的?是模型作者自己,还是独立第三方?题目是谁出的、谁维护? | §6.2 的内部基准是作者自出自评且用于指导训练;§6.3 的 AA / Vals AI / Arena 是第三方 |
| 2 | harness 一样吗?各模型跑在同一个外壳下吗?分数是不是「跨外壳取最好」? | Terminal-Bench 2.1 取跨 harness 最好分;DeepSWE 换 harness 从 67.5 变 67.3;成本比较里 K3 用 Kimi Code、对手用 Claude Code |
| 3 | 有没有回退/拒答?那些没能正常完成的任务,是怎么计入分数的? | Fable 5 在 SWE-Marathon 上 35% 任务回退;Table 3 脚注里多处拒答;Agents' Last Exam 官方榜 40% 任务标注降级 |
| 4 | 用工具了吗?带工具和不带工具的分数分别是多少? | HLE-Full 43.5 → 56.0;ZeroBench-main 23.0 → 41.0 |
| 5 | 上下文怎么管的?压缩策略、窗口大小、触发阈值是什么?对手用的是什么? | BrowseComp 300K 压缩 91.2 对 1M 不压缩 90.4;对手的策略论文未说明 |
| 6 | 跑在什么硬件/什么版本上?和官方设定一致吗? | PostTrainBench 用 H20 而非官方的 H100;SWE-Marathon 用 7 月 9 日的分支而非 v1.1 |
| 7 | 方差多大?跑了几次?差距是否大于噪声? | 视觉三次平均、ZeroBench 五次;而 CorpFin v2 差 0.2、OSWorld-Verified 差 0.2,没有方差就无法判断 |
| 8 | 日期是哪天?第三方分数、榜单名次、API 定价各自的时间戳是什么? | AA / Vals / ALE 为 07-23,Toolathlon / JobBench 为 07-24,FrontierSWE 脚本为 07-16;Elo 会漂移 |
| 9 | 成本呢?拿到这个分数花了多少钱、多少时间?在你的预算下还成立吗? | §6.4:BrowseComp 每任务 $2.03;K3 在 high 努力下追平 Opus 4.8 的 max |
假设你在社交媒体上看到这样一句话(这句话是虚构的,但它引用的每一个数字都真实出自本章):
「开源模型全面赶超!Kimi K3 在 BrowseComp 上以 91.2 击败 GPT-5.6 Sol,在 SWE-Marathon 上碾压 Claude Fable 5 七个点,在 WebDev Arena 上全球第一,成本只有对手的一半。开源已经追平闭源。」
请用 12.9 的九问清单逐条拆解这句话:哪些部分成立、哪些部分需要加限定、哪些部分是错的。最后给出一个你自己愿意署名的、更准确的版本。
「BrowseComp 91.2 击败 GPT-5.6 Sol」——数字真实,但需要三个限定(问 5、问 7):91.2 用了 300K 触发的上下文压缩策略;K3 换成 1M 不压缩就是 90.4,正好等于 Sol;论文没给方差,也没说 Sol 用什么上下文策略。准确说法是「两者大致持平」。
「SWE-Marathon 碾压七个点」——数字真实(42.0 对 35.0),但需要两个限定(问 3、问 6):Fable 5 在这个基准上 35% 的任务触发了回退,所以这七分里有多少来自能力差异无法确定;而且这个基准跑在 H20 校准分支上、版本早于 v1.1。「碾压」这个词无论如何撑不住。
「WebDev Arena 全球第一」——这一条最扎实(问 1):第三方公开榜单,众包人类投票,99 个模型中第 1,1678 对 1634,而且是首个登顶的开放模型。要加的限定只有两个(问 8):截至 2026-07-23,Elo 会随对局漂移;以及这是「网页开发偏好」这一个维度。
「成本只有对手的一半」——需要指明是哪个套件、哪个对手(问 9、问 2):BrowseComp 上 $2.03 约为 GPT-5.6 Sol 的一半;Kimi Code Bench 2.0 上是 Fable 5 的 38%,但那里 K3 用自家 harness、对手用 Claude Code;而且这些成本引自 7 月 23 日的 API 定价。
「开源已经追平闭源」——这是错的,而且和论文自己的结论直接冲突。论文明说「与最强的专有模型仍有差距」。证据:AA 智能指数第 4(57.1 对 Fable 5 的 59.9)、Vals Index 第 2、Text Arena 第 8、Agent Arena 第 4;CritPt 23.4 落后三个模型;HLE-Full 落后包括 Opus 4.8 在内的多个模型;GDPval-AA v2 第三、AA-Briefcase 第二;OSWorld 2.0 和 SaaS-Bench 仍由对手领先。
我愿意署名的版本:「截至 2026 年 7 月,Kimi K3 作为开放权重模型,在智能体、检索与网页开发方向达到或接近专有前沿——它在 WebDev Arena 上以 1678 Elo 排名第一(99 个模型),在 BrowseComp 上与 GPT-5.6 Sol 大致持平且每任务成本约为其一半。但在研究级推理(CritPt、HLE-Full)和部分知识工作套件上仍明显落后于 Claude Fable 5 和 GPT-5.6 Sol,综合指数上排第 4(Artificial Analysis)。论文中的部分比较存在 harness 不一致、对手触发回退、上下文策略不同等对等性问题。」
注意这个版本更长、更啰嗦、也更不好传播——准确性和传播力天然是矛盾的,这就是为什么你需要一份检查清单。
变式:现在把立场反过来。假设有人写「K3 根本不行,AA 指数才排第 4,CritPt 只有 23.4,研究级推理一塌糊涂」。请用同一份清单拆解这句话——它省略了哪些对 K3 有利的证据?(提示:580 个模型里的第 4 是什么概念?开放权重意味着什么?)
本章小结
这一章表面上过了一遍 K3 的成绩单:推理与知识轴上「会做题但还不太会做研究」(GPQA 93.5 与前沿相当,CritPt 23.4 明显落后,论文自己承认研究级推理是关键改进方向);编码轴上长任务强(ProgramBench 77.8 最好、SWE-Marathon 42.0、Terminal-Bench 2.1 88.3 几乎追平 Sol),单点能力有短板(DeepSWE 67.5 落后前两名);智能体轴是最亮的一轴,八个基准拿下最好成绩,主要败给 Claude Fable 5 的是两个 Elo 制知识工作套件;视觉轴上带工具的跃升非常大(ZeroBench 23.0 → 41.0)。第三方评测里 WebDev Arena 全球第 1 是最硬的一条,综合指数第 4 是最克制的一条。成本效率上 K3 处在或接近前沿。
但这一章真正想留给你的不是这些数字,而是看数字的顺序。先找条件,再看数值:谁评的、什么外壳、有没有回退、用不用工具、上下文怎么管、什么硬件、跑了几次、哪一天、多少钱。这九个问题里,本章的每一个都能在论文里找到至少一处真实的例证——包括那个最漂亮的对照:同一个模型、同一个基准,只因为上下文策略不同,BrowseComp 就在 90.4 和 91.2 之间移动,而 90.4 恰好是对手的分数。
最后一件事值得单独说:这篇论文在配置披露上做得比很多技术报告好——它主动写出了 35% 的回退率、写出了对自己不利的 90.4、写出了内部基准会被用来指导训练、写出了自己在 CritPt 上落后、写出了把安全评测视为下界。这些自我暴露不能让它的结论自动成立,但它让读者有能力自己去打折扣。一份能被认真批评的报告,好过一份无从批评的报告。
第13章 案例研究、结论,以及这篇论文没有告诉你的事
一句话导语:这一章先看 K3 被放出去干活时做成了什么(优化 GPU 内核、写一个编译器、设计一颗芯片、复现一篇天体物理论文、剪一支预告片),然后做一件整篇论文都没做的事——把这份报告没说的东西列出来。
学完这一章你应该能做到
- 从 283.6 ms → 114.4 ms 算出加速比,并说清「延迟降低百分比」和「加速倍数」不是同一个数
- 解释 Figure 14 里那些台阶状的曲线为什么比一个终点数字更有信息量
- 说清「一个早期 K3 检查点在做 K3 自己的内核优化」这件事,为什么既是最有力的证据、又是最需要警惕的证据
- 指出案例研究这种证据形式的天然弱点,并说出补上哪三样东西才能让它变成可信的度量
- 把一篇技术报告的省略分成三类:行业通行的、这篇特有的、你自己也不确定的
13.1 GPU 内核优化:给它一天时间,让它自己去调
先说这个实验为什么值得做。前面十二章讲的都是「模型在别人出的卷子上考了多少分」。但 K3 的作者手里有一类特别的任务:他们自己每天都在做的事。训练一个 2.8 万亿参数的模型,最耗人的工作之一就是把注意力算子写成跑得足够快的 GPU 代码。这类工作有一个很好的性质——结果可以被机器客观测量:跑得快就是快,慢就是慢,不需要裁判,不需要打分细则,也没法糊弄。
GPU 内核(GPU kernel):一段直接跑在显卡上的程序,负责把某个数学运算(比如注意力)真正算出来。同一个数学公式可以有几十种写法,跑起来的速度可能差好几倍——差别来自数据怎么在显存和片上缓存之间搬运、多少个线程怎么分工、计算和搬运能不能重叠。优化内核就是在不改变计算结果的前提下把它写得更快。
实验设置很干净:每个模型在一个配置完全相同的沙箱里独立工作,每个任务的预算是最多 24 小时,这段时间可以用来做性能剖析(profiling)、重写代码、跑基准测试。覆盖四个有代表性的内核:AttnRes、DeepSeek 稀疏注意力(DSA)、KDA、以及头维度为 512 的 MLA。硬件是一块 NVIDIA Hopper GPU 和一块其他厂商的 GPGPU。
结果:K3 在全部四个内核上都做出了大幅改进——
AttnRes 的延迟从 283.6 ms 降到 114.4 ms;DSA 和 KDA 的运行时分别削减 55.1% 和 73.6%;MLA 上达到了峰值 TFLOPS 的一半以上。综合这些任务,K3 追平了 Claude Fable 5(带回退),并大幅超过 Claude Opus 4.8、GPT-5.6 Sol 和 GPT-5.5。
峰值 TFLOPS(peak TFLOPS):TFLOPS 是每秒一万亿次浮点运算。「峰值」是这块显卡理论上最快能达到的速度。实际的内核几乎不可能跑满峰值,因为总有时间花在等数据搬运上。所以「达到峰值的一半以上」是一个很实在的成绩——它的意思是这段代码把显卡的算力用掉了一半以上,而不是大部分时间在干等。
Figure 14:不要只看终点,要看形状
论文用 Figure 14 展示了各模型在 AttnRes 上的优化轨迹:横轴是活跃工作小时数,纵轴是相对 FLA Triton 基线的加速百分比。终点的数字是:K3 +59.7%、Claude Fable 5 +57.1%、GPT-5.5 +30.8%、GPT-5.6 Sol +17.3%。
但比终点更有意思的是曲线的形状:四条曲线都不是平滑上升的,而是台阶状——长时间的水平段(试了一堆没用的东西),然后突然跳一级(找到了一个真正有效的改动),再进入下一段平台。K3 的曲线跳得早、跳得多,在大约 5 小时时就已经越过了 40%;Claude Fable 5 起步慢一些,但后来在 11–14 小时区间连续跳了几级追了上来;GPT-5.5 在大约 30% 处长时间横盘;GPT-5.6 Sol 直到接近 20 小时才慢慢爬到 17% 左右。
为什么「台阶」这个形状本身就是结论
如果优化过程是平滑上升的,那说明它是一件熟练工作——多花时间就多一点收益,模型强弱只体现在斜率上。而台阶形意味着完全不同的事:大部分时间是没有回报的探索,全部收益集中在少数几个正确的想法上。在这种任务里,「更强」不等于「干得更快」,而等于「更容易撞上那几个正确的想法,并且撞上之后能认出来」。
这也解释了为什么 GPT-5.5 那条线会长时间横在 30% 附近——不是它不干活,是它一直在试,但试的方向没有跨过下一个台阶。
AttnRes 的延迟从 283.6 ms 降到 114.4 ms。请分别算出:(a) 延迟降低了百分之多少;(b) 加速了多少倍。然后回答:这两个数为什么不一样,什么时候会有人把它们混着用?
(a) 降低百分比 = 169.2 ÷ 283.6 ≈ 59.7%。
(b) 加速倍数 = 283.6 ÷ 114.4 ≈ 2.48 倍。
两个数描述的是同一件事的两种视角:降低 59.7% 说的是「省掉了原来时间的六成」,加速 2.48 倍说的是「现在一秒能做原来 2.48 秒的活」。它们的关系是:加速倍数 = 1 ÷ (1 − 降低比例)。当降低比例趋近 100% 时加速倍数会趋于无穷,所以降低百分比在高加速区间会显得「不够震撼」,而倍数在低加速区间会显得「不够震撼」——写宣传稿的人会挑对自己有利的那个说法。降低 59.7% 听着一般,说成「快了 2.48 倍」就好听多了,但它们是同一件事。
还有一个值得注意的巧合:59.7% 这个数,恰好等于论文 Figure 14 里 K3 那条曲线的终点标注 「+59.7%」。这两个数字很可能指的是同一件事——也就是说,图 14 纵轴上的「加速百分比」应该理解为「延迟降低的百分比」,而不是「倍数减一」。不过论文并没有给出这个纵轴的定义公式,所以这只是一个高度合理的对账,不是论文明说的事实。看到两个来源的数字对上了,先高兴,然后仍然要问一句「它们的定义真的一样吗」。
变式:DSA 和 KDA 的运行时分别削减了 55.1% 和 73.6%。哪一个的「加速倍数」更大?大多少?请先凭直觉猜一个数,再算,看看你的直觉偏了多少。
13.2 自举:一个早期的 K3 在优化 K3
论文在这一段的最后加了一句很轻描淡写、但分量极重的话:在开发的后期阶段,一个早期的 K3 检查点已经承担了他们大部分的内核优化工作。
把这句话展开:他们用一个还没训完的 K3,去优化那些将要用来训练 K3 的 GPU 内核。这是一个自举(bootstrapping)循环——模型改进基础设施,更好的基础设施训出更好的模型,更好的模型再去改进基础设施。
打个比方
这有点像一个木匠用自己做的刨子,去做一把更好的刨子。第一把刨子粗糙,但足以做出第二把稍好的,第二把又能做出第三把。类比失效处:木匠的手艺是不变的,变的只是工具;而这里「手艺」(模型)和「工具」(内核)是同一条链上的两端,模型既是工匠也是产物。更重要的差别是——木匠能判断新刨子好不好用,而「内核跑得更快」这个判据虽然客观,却不能保证由此训出的模型更好。这中间隔着好几层推断。
综合题。已知三件事:(1) K3 在 SWE-Marathon(一个 GPU 内核导向的套件)上拿到 42.0,领先 Claude Fable 5 七分;(2) 案例研究里 K3 在四个内核上都做出大幅改进,AttnRes 加速幅度居首;(3) 一个早期的 K3 检查点在开发后期承担了作者大部分的内核优化工作。
请回答:这三件事放在一起,增强还是削弱了「K3 的 GPU 内核优化能力真的很强」这个结论?请把两个方向的论证都写出来,然后说明需要什么额外信息才能判断。
增强的方向(支持 H1)。第 (3) 条是这一整章里最硬的证据形式——它不是一个基准分数,而是作者用真金白银的工程需求做了背书。一个实验室愿意把自家训练基础设施的关键优化工作交给一个模型,说明它在真实的、后果自负的场景里是可用的。这比任何自评基准都更难伪造:如果模型产出的内核是错的或更慢的,受损的是他们自己的训练进度。而且这类结果可被机器客观测量(跑得快就是快),不依赖裁判。
削弱的方向(支持 H2)。如果模型在开发期间被大量用于这类任务,那么这类任务的轨迹、失败模式、成功套路极有可能被回收进后续的训练数据——这正是第12章里「内部基准被用来指导训练」那个问题的翻版。于是「在 GPU 内核任务上强」就可能不是通用能力,而是这一类任务被特别训练过的结果。SWE-Marathon 领先七分这件事,在这个解读下就没那么惊人了:它恰好是作者最熟悉、最有数据、最有动机去优化的那一类任务。
需要什么额外信息才能判断:(i) 内核优化相关的数据是否进入了训练集,以及是否与 SWE-Marathon 的任务有重叠(论文没有说明);(ii) 在一个与 GPU 内核无关的性能工程领域(比如数据库查询优化、编译器优化的其他分支)做同样的实验,看优势是否迁移;(iii) 这些改进后的内核是否被独立方复现和验证过。
我的判断:两个方向都成立,而且不矛盾——很可能确实存在一种通用的性能工程能力(H1),同时它在 GPU 内核这个具体领域被额外强化过(H2)。所以合理的结论是:「K3 在 GPU 内核优化上被验证为可用于生产,这一点证据很强;但把它外推为'K3 的性能工程能力全面领先',证据不足。」
顺带注意一个容易滑过去的细节:论文说 K3 在这些内核任务上「追平了 Claude Fable 5(带回退)」。带回退这三个字又回来了——Fable 5 那一边的成绩里有多少是回退产生的,论文在这一节同样没有拆开。
变式:假设你是一家独立评测机构,想设计一个实验来区分 H1 和 H2,但你拿不到作者的训练数据。你会怎么设计?(提示:你能拿到 K3 的权重,因为它是开放的。想想「最近才出现、不可能在训练数据里」的内核。)
答辩:如果我是审稿人
你说 K3 在内核优化上「追平了 Claude Fable 5,大幅超过 Opus 4.8、GPT-5.6 Sol 和 GPT-5.5」。可这四个内核(AttnRes、DSA、KDA、MLA)里,有两个是你自己论文前几章提出的架构组件。你让所有模型去优化你自己发明的算子,然后宣布你的模型赢了。这个实验设计能说明什么?
参考防守(先自己组织语言再看)
这是这一节最锋利的质疑,必须分层回答:
能守住的部分。第一,实验条件本身是对称的:所有模型在配置相同的沙箱里、有相同的 24 小时预算、优化同一批内核、用同一个基线(FLA Triton)衡量。没有哪个模型被给了额外信息。第二,判据是机器测出来的运行时,不是人打的分,没有裁判偏好的空间。第三,任务是「把这段代码写得更快」,而不是「回忆 AttnRes 是什么」——对算子的熟悉可以帮你起步,但把延迟砍掉六成需要的是真实的性能工程能力。第四,KDA 与 AttnRes 的定义在论文前几章是公开的,其他模型并非无从知晓。
守不住、必须承认的部分。(a) 先验知识不对称是真实存在的。K3 的作者在这些算子上积累了大量经验,而这些经验很可能通过训练数据进入了 K3;其他模型没有这种暴露。这不是作弊,但它意味着这个比较测的是「在 Kimi 自家算子上的优化能力」,而不是「通用的内核优化能力」。
(b) 样本量是 4。四个内核、两块显卡,没有报告重复次数,也没有报告方差。24 小时的探索过程随机性很大(看看 Figure 14 那些台阶就知道),跑第二遍完全可能换一个名次。
(c) 基线的选择没有被讨论。所有加速百分比都是相对「FLA Triton 基线」算的,但论文没有说明这个基线本身优化到了什么程度。如果基线在某个内核上写得比较朴素,那么「降低 73.6%」的含金量就要打折。
我认为诚实的结论是:这个案例研究证明了「K3 能在真实的性能工程任务上做出可测量的大幅改进,并且这些改进被作者用在了自己的生产环境里」——这一点站得住。但「K3 在内核优化上强于 GPT-5.6 Sol」这个跨模型排名,样本量和先验对称性都不足以支撑。要修好它,最小的改动是:加入几个与 Kimi 架构无关的第三方内核,并把每个任务重复三次报告方差。
「K3 把 KDA 内核的运行时削减了 73.6%」——这听上去非常惊人。请构造至少两种替代解释,使得这个 73.6% 成立,但并不说明 K3 的能力有多强。然后说明:论文需要补充什么信息,才能排除你构造的这些解释?
解释一:基线太弱。如果 FLA Triton 基线只是一份未经调优的参考实现,那么削减 73.6% 可能只需要一些标准的、教科书式的优化(调整分块大小、减少显存往返、开启合适的数据类型)。论文没有说明这个基线的优化程度,所以无法排除。这是最重要的一条——注意论文对 MLA 给的是「达到峰值 TFLOPS 的一半以上」,这是一个绝对指标(拿硬件上限当分母),比相对基线的百分比可信得多。如果四个内核都给出绝对指标,这个质疑就没了。
解释二:优化空间是任务给的,不是模型挣的。某些内核可能存在一个众所周知的低效点(比如某个操作没有融合)。任何有能力的优化者——包括其他模型、也包括一个有经验的人类工程师——都会先修它,收益就有一大半。要排除它,需要报告一个人类专家基线:同样 24 小时,一位熟练工程师能做到多少?论文没有给。
解释三:选择性报告。论文说覆盖「四个有代表性的内核」,但没说这四个是从多少个候选里选出来的、按什么标准选的。如果还有第五、第六个内核 K3 没做好,我们不会知道。要排除它,需要预先注册要测的内核清单,或者报告全部尝试过的内核。
补充哪些信息可以排除:(i) 基线实现的来源和优化程度,最好同时给出「相对硬件峰值」的绝对指标;(ii) 一个人类专家在同等预算下的对照;(iii) 内核清单的选择依据与完整结果;(iv) 重复运行的方差。
要说清楚的是:构造这些替代解释不等于说论文错了。K3 很可能真的做得很好——AttnRes 那条 283.6 → 114.4 是绝对时间,MLA 那条是相对硬件峰值,这两个数不受基线选择影响,本身就有说服力。构造反例的目的是划清这份证据能支撑的结论边界,而不是否定它。
变式:现在反过来——如果论文报告的是「相对硬件峰值 TFLOPS 达到了 51%」,你还能构造出削弱它的解释吗?(提示:峰值是怎么定义的?不同数据类型的峰值一样吗?)
13.3 MiniTriton:从「写内核」到「写造内核的工具」
前一节是让模型优化一段代码。这一节的任务更上一层:让模型造一个编译器。
编译器(compiler):把人写的高级代码翻译成机器能执行的低级指令的程序。Triton 是一种专门用来写 GPU 内核的语言,它让你用接近 Python 的写法描述一个内核,再由它的编译器翻译成显卡指令。造一个编译器比写一个内核难得多——内核是一个作品,编译器是一台能生产无数作品的机器。
K3 开发了 MiniTriton,一个紧凑的类 Triton 编译器,包含:一个自定义的 tile 级 Python 前端与布局系统(tile 指把大矩阵切成小块来处理,布局系统决定这些块怎么摆放在显存和寄存器里)、一个轻量的 warp 级 MLIR 注解与优化层(warp 是 GPU 上一组同步执行的线程,MLIR 是一种中间表示,编译器在这一层做各种变换)、以及一条 PTX 代码生成流水线(PTX 是 NVIDIA 显卡的一种低级汇编)。
围绕这个编译器,它还构建了一个双模式张量库,提供类似 PyTorch 的高层接口:即时执行(eager)和只做前向的编译两条路径共享同一套 DSL 编译器和运行时。这个库进一步提供了反向模式自动微分、神经网络模块、基于 NCCL 的分布式训练原语,以及稀疏与可视化原语。
成绩单(在一块 NVIDIA L20 上):MiniTriton 在其核心基准套件的几何平均上超过了 PyTorch eager 和 torch.compile。它从零实现的 tensor-core 矩阵乘法路径在最大的形状上接近 cuBLAS(NVIDIA 官方高度优化的矩阵库),达到实测机器上限的约 90%。它的 DSL 级 KDA 预填充内核以明显优势超过一个对等的 Triton 参考实现。
最能说明问题的一条是:MiniTriton 还能端到端训练一个 GPT 模型,损失曲线紧贴 PyTorch 参考;全模型的梯度与 torch 自动微分的差异不超过 torch 自身的 fp32 舍入误差(10−4),这个差异是以 fp64 参考为基准测量的。Figure 15 展示了四张图:(a) CUDA-core 的 roofline、(b) tensor-core 的 roofline、(c) 用 MiniTriton 训练的字符级 GPT 与 torch eager 的训练损失曲线对比、(d) 用 MiniTriton 自己的分布式原语做的两卡数据并行训练与单卡训练的对比——两条曲线的最终交叉熵要到小数点后第三位才分得开。
为什么「梯度差异不超过 fp32 舍入误差」是这一段最强的一句话
一个编译器最容易犯的错是算错但不报错——结果差一点点,模型照样能训,只是永远差那么一点。所以正确性的检验必须是定量的。
这里的检验设计得很聪明:他们不拿 torch 当「真值」,而是拿 fp64(双精度,精度更高)算一遍当真值,然后量 MiniTriton 和 torch 各自离真值多远。结论是 MiniTriton 的偏差不比 torch 自己的偏差更大。换句话说:MiniTriton 的误差已经小到被浮点数本身的精度极限盖住了,你没法从数值上区分它和 torch 哪个「更对」。
这比「我们跑了一下,看起来没问题」强了不知道多少个量级。
为什么验证 MiniTriton 的梯度时,要专门用 fp64 算一份参考,而不是直接说「MiniTriton 的梯度和 PyTorch 的梯度差不超过 10−4」?这两种说法的证据强度差在哪?
只比 A 和 B 的问题:|A − B| 小,只能说明两者接近,不能说明两者正确。极端情况下,如果 MiniTriton 恰好复制了 torch 的某个数值缺陷,|A − B| 会是 0,但两个都错。反过来,如果 |A − B| 较大,你也无法判断是 B 错了还是 A 错了——fp32 下 torch 自己也有可观的舍入误差。
引入 fp64 参考 T 之后:你可以分别量 |A − T| 和 |B − T|,于是能说出一句强得多的话——「B 的偏差不超过 A 的偏差」。这句话的意思是:MiniTriton 的误差已经落在 fp32 这个数据类型固有的噪声底之内了,再精确也没有意义,因为数据类型本身就到此为止。
这也是一种可迁移的验证思维:要证明新实现是对的,不要和旧实现比,要和一个精度更高的参考比,然后论证「新旧两者与参考的距离处于同一量级」。同样的思路出现在 Figure 15(d):把两卡分布式训练和单卡训练放在一起,如果分布式原语(NCCL 通信、梯度归约)写错了,两条损失曲线会分叉;它们最终只在小数点后第三位分开,说明通信这一层也是对的。
要补充的一点:论文说的是「全模型梯度」的差异不超过 10−4,但没有说明这是最大差、平均差还是某种范数,也没有说明是在训练的哪个阶段测的。这是一个小但真实的表述含糊。
变式:如果 MiniTriton 的梯度和 fp64 参考的差异,比 torch 和 fp64 参考的差异还小,能不能说 MiniTriton 「比 PyTorch 更准」?这个结论有什么陷阱?
13.4 芯片设计:48 小时,一颗推理芯片原型
这是所有案例里最出人意料的一个。作为一次早期概念验证,K3 为一个「遵循相同架构的 nano 模型」设计了一颗推理芯片原型。这个 nano 模型的架构和 K3 本体一脉相承:混合 KDA 与 NoPE-MLA 注意力、块大小为 2 的 Block AttnRes、带一个共享专家的 sigmoid MoE 路由,权重采用分组 INT4 量化(组大小 128)。
过程:在一次 48 小时的自主运行里,K3 用 Kimi Code 配合开源 EDA 工具(EDA 是电子设计自动化,芯片设计用的软件)和 Nangate45 标准单元库,构建、优化并验证了这颗芯片。
结果:在 4 mm² 的解析面积预算内,设计在 100 MHz 下收敛时序,RTL 仿真的解码吞吐超过 8,700 tokens/s,集成了 146 万个标准单元、0.277 MiB SRAM,以及一个带融合反量化的 INT4 MAC 阵列。RTL 代码已在 GitHub 开源。
收敛时序(closes timing):芯片按固定节拍工作,100 MHz 表示每秒 1 亿拍。「收敛时序」的意思是——所有电路上的信号,都能在一拍结束之前稳定下来。如果某条路径太慢,信号还没稳住下一拍就来了,芯片就会算错。所以这是一个「设计真的能工作」的硬性判据,不是一个可以打折扣的指标。
标准单元(standard cell):芯片设计里预先做好的最小积木(一个与门、一个触发器等)。146 万个标准单元的意思是这个设计用掉了 146 万块这样的积木。MAC 阵列是乘加运算阵列,神经网络里最主要的计算就是「乘一下再加起来」,这个阵列就是专门干这件事的硬件;「融合反量化」表示把 INT4 权重还原成实际数值这一步被直接做进了计算通路里,不用单独走一遍。
补充:给这些数字一点尺度感
4 mm²:大约是一粒芝麻的正面面积,或者你在纸上用铅笔戳一个点再稍微大一点。作为对比,一颗现代手机主芯片大约是 100 mm² 量级。
100 MHz:每秒 1 亿拍。今天一颗普通手机 CPU 大约是 2–3 GHz,也就是快 20–30 倍。100 MHz 大致相当于 1990 年代中期个人电脑的频率。
8,700 tokens/s:用 100,000,000 ÷ 8,700 ≈ 11,500,也就是说平均每生成一个 token 用掉约 1.15 万个时钟周期。
0.277 MiB SRAM:约 28 万字节,比一张 1.44 MB 的软盘还小得多。这也说明了这颗芯片服务的模型有多「nano」。
把这些放在一起看:这不是一颗能跑 K3 的芯片,而是一个架构可以被做成硬件的存在性证明。
根据 4 mm²、100 MHz、8,700 tokens/s、146 万标准单元、0.277 MiB SRAM 这五个数字,判断这颗芯片属于下面哪一类,并说明你的理由:(A) 一颗可以量产部署、用来跑 K3 推理服务的芯片;(B) 一个验证「这套架构能被做成硬件」的原型;(C) 一个纯软件模拟、没有硬件意义的玩具。
排除 (A):规格远达不到部署级。0.277 MiB SRAM 只有约 28 万字节,连一个小模型的一层权重都未必放得下,所以它服务的是论文明说的「nano 模型」;100 MHz 是 1990 年代的频率水平;4 mm² 也远小于商用推理芯片。而且论文自己写的定位就是「早期概念验证(early proof of concept)」和「原型(prototype)」。
排除 (C):它不是纸上谈兵。「在 100 MHz 收敛时序」是一个真实的物理约束被满足了;它用的是真实的开源 EDA 工具链和 Nangate45 标准单元库;它有具体的单元数(146 万)和面积预算(4 mm²);RTL 代码已经开源,任何人都可以去检查。
为什么是 (B):这项工作要证明的命题是「K3 论文前几章描述的那套架构(KDA + NoPE-MLA + Block AttnRes + sigmoid MoE 路由 + INT4 分组量化)可以被完整地映射到硬件上,而且这个映射能在 48 小时内由模型自主完成」。存在性证明只需要一个能工作的最小例子,不需要它有竞争力。
还要注意的:论文说的是「RTL 仿真的解码吞吐」——RTL 仿真是在电脑上模拟这个电路的行为,还没有真的流片造出来。所以 8,700 tokens/s 是仿真值,不是实测硅片上的值。这个区别在芯片领域很重要,论文的措辞是准确的(写了 「RTL-simulated」),但读的人容易忽略。
变式:论文说 RTL 代码已经在 GitHub 开源。相比只在论文里报告这些数字,开源代码在证据上加了什么?它没有解决什么问题?(提示:想想「这段代码是 48 小时自主跑出来的」这个主张,能被开源的代码证明吗?)
13.5 三个更贴近人类工作的案例
面向研究的编码:两小时对一到两周
为了复现计算天体物理中的 I–Love–Q 普适关系(一组关于中子星的转动惯量、潮汐形变和四极矩之间的经验关系),K3 做了这样一串事:审阅了 20 多篇论文、交叉验证它们的结果、实现完整的数值流水线、评估了 300 多个状态方程、发现了已发表公式中的不一致、写了 3000 多行 Python,并产出一个交互式 HTML 仪表板。用时大约两小时;论文说一位有经验的研究者通常需要一到两周。
知识工作:两个 Kimi Work 里的项目
第一个:K3 产出了一个覆盖 AI ASIC 产业 42 年历史的交互式研究网站。完成了 120 多轮迭代精修,取材自 87 份季报和 99 份原始 PDF(11,000 多页),过程中做了 2,800 多次网页搜索和 1,100 多次终端查询。
第二个:用 20 多个并发子智能体分析 GWTC-5 中的 391 个引力波事件,产出七张科学可视化、两张汇总表,以及一份十余篇论文的文献综述。
视频剪辑与动效设计
借助原生多模态架构(第6章讲过:视觉和文本活在同一条 token 流里),K3 制作了一个 3Blue1Brown 风格的动效解说视频——讲解的正是它自己的架构——并从 56 段素材里剪出了预告片。这涉及片段选择、动作匹配剪辑、帧级精确的节拍同步、音频处理和多轮修订。论文说,制作一段可比的高密度短视频,一位有经验的剪辑师通常需要一到两天。
读的时候要小心:这些是精选的成功案例
整个 §7 是一串「它做成了 X」的叙述,没有出现任何一处「它在 Y 上失败了」「平均需要重试几次」「我们试了 N 个项目,成功了 M 个」。这不是说这些案例是假的——它们很可能都是真的。但案例研究这种证据形式,天然只能证明「可能性」,不能证明「可靠性」。
「K3 两小时做完了研究者一到两周的活」这句话,正确的读法是「至少有一次,K3 在两小时内产出了某种程度上可比的成果」。它不告诉你成功率是多少、需要多少人工干预、失败时失败得多难看。而对于要不要在工作里用它,后面这些恰恰是最重要的信息。
「K3 大约两小时完成了一位有经验的研究者通常需要一到两周的工作。」请你构造至少三种情形,使得这句话字面为真,但「K3 的科研能力约等于(甚至超过)一位研究者」这个推论不成立。然后说明:要把这个案例变成一个可信的能力度量,最少需要补充哪三样东西?
情形一:幸存者偏差。如果作者尝试了二十个类似的复现任务,只有这一个跑通了,那「两小时」是最好情况而不是典型情况。论文没有报告尝试次数,无法排除。
情形二:产出不等价。研究者的「一到两周」包含了很多 K3 那两小时里没做的事:确认方法学的合理性、对结果承担署名责任、写出经得起同行评议的论证、处理审稿意见。两边交付物的标准不同,时间自然不可比。论文说 K3「发现了已发表公式中的不一致」,这确实是一个高质量信号,但也需要有人去确认这些「不一致」是真的问题,而不是它自己理解错了——论文没有说这一步由谁做、结论如何。
情形三:任务被选过。I–Love–Q 关系是一个已经发表、已被广泛复现的结果。相关论文、公式、状态方程数据都在公开文献里,模型很可能在训练中见过。复现一个已知结果,和做出一个新发现,难度完全不在一个量级。这不削弱「它能在两小时内完成一件复杂的多步骤工作」,但削弱「它的科研能力约等于研究者」。
情形四:「两小时」的边界模糊。这两小时是不是纯自主?中间有没有人给提示、纠错、重启?准备提示词和环境花了多久?论文用的词是「in about two hours」,没有说明是否等于「自主运行时间」。(对照一下:芯片那个案例,论文明确写了「a single 48-hour autonomous run」——用词的差别可能是有意义的。)
要补充的三样东西:
(i) 成功率与样本:在 N 个同类任务上跑,报告成功了几个、平均耗时、失败的失败在哪。
(ii) 人类对照组:真的找几位研究者做同一件事并计时,而不是引用一个「通常需要」的估计。
(iii) 产出的独立评审:请领域专家在不知道来源的情况下评价两边的交付物质量(这正是他们在 Kimi Webdev Bench 里对网页开发做过的事——盲评的方法论他们是有的,只是没用在这里)。
最后要公道地说:案例研究不是坏东西,它是能力上限的存在性证明,而且往往比基准分数更能说明「这东西到底能不能干活」。问题只出在把存在性证明当成平均性能来引用。
变式:AI ASIC 产业那个案例给出了非常具体的过程数字——120 多轮迭代、87 份季报、99 份 PDF、11,000 多页、2,800 多次搜索、1,100 多次终端查询。这些数字让这个案例比 I–Love–Q 那个更可信,还是更不可信?(提示:想想「120 多轮迭代」是谁驱动的。)
13.6 结论:这篇报告最终主张了什么
论文 §8 只有一段,但每个词都是称过分量的。原文的主张可以拆成五条:
第一,K3 是一个开放的 2.8 万亿参数 MoE 模型,具备原生视觉能力和 100 万 token 的上下文窗口,建立在 KDA(Kimi Delta Attention)和 AttnRes(Attention Residuals)之上。
第二,它是世界上第一个开放的 3T 级模型。
第三,它在长时程编码、智能体、知识、推理和视觉任务上交付了前沿级性能。
第四,与最强的专有模型仍有差距——这是作者自己写的,不是我们加的。
第五,K3 在所有人触手可及的范围内确立了一个新的开放前沿;作者希望它能赋能更广泛的社区在研究、部署和创新上的工作。
常见误解:把「开放前沿」读成「最强」
这段结论的措辞非常克制,值得逐字体会。它没有说 K3 是最强的模型,它说的是「在开放模型里确立了新的前沿」,并且明确承认与最强专有模型的差距。第12章的数据完全支持这个措辞:Artificial Analysis 综合指数第 4、Vals Index 第 2、Text Arena 第 8、CritPt 落后三个模型——但 WebDev Arena 第 1,且是首个登顶该榜的开放模型。
顺带指出一个小小的措辞技巧:模型是 2.8 万亿参数,论文称它为「3T 级(3T-class)」。这不是错,「级」本来就是量级的说法,但它确实是往上取整而不是往下。这类修辞在技术报告里很常见,认出来就好,不必上纲上线。
结论里说 K3「在长时程编码、智能体、知识、推理和视觉任务上交付了前沿级性能」。请你判断:这五个词里,哪几个能被第12章的数据充分支撑,哪几个需要加限定?「前沿级」(frontier-level)这个词,和「最强」差在哪?
支撑最充分的是「智能体」:BrowseComp 91.2、DeepSearchQA 95.0、ResearchRubrics 76.2、MCPMark-Verified 94.5、AutomationBench 30.8、SpreadsheetBench 2 34.8、τ3-Banking 33.4、Harvey Lab-AA 94.6 都是全表最好。这里甚至可以用比「前沿级」更强的词。
「长时程编码」基本成立但要看细分:ProgramBench 77.8 最好、SWE-Marathon 42.0 最好、Terminal-Bench 2.1 88.3 几乎追平 Sol;但 FrontierSWE 81.2 落后 Fable 5 的 86.6、DeepSWE 67.5 落后前两名。「前沿级」(在第一梯队里)成立,「最强」不成立。
「视觉」成立但依赖工具:OmniDocBench 91.1 最好、Video-MME 90.0 与 MMVU 82.1 最好;但 WorldVQA 51.0 第二、BabyVision 85.7 落后、ZeroBench 不带工具只有 23.0。
「知识」需要加限定:AA-LCR 74.7 最好、GPQA Diamond 93.5 与前沿相当;但 HLE-Full 43.5 / 56.0 落后包括 Claude Opus 4.8 在内的多个模型。
「推理」最需要加限定:CritPt 23.4 落后 Fable 5、Sol 和 GPT-5.5 三个模型,论文自己在 §6.1.4 里写了「研究级推理仍是关键改进方向」。把「推理」和其余四个并列写进结论句,是这段结论里最经不起逐条核对的一处。
「前沿级」与「最强」的差别:「前沿级」断言的是归属(属于第一梯队),「最强」断言的是排序(第一梯队里的第一名)。归属只需要证明差距不大,排序需要证明每一项都领先。所以「前沿级」是一个门槛低得多、也诚实得多的用词——配合它后面紧跟的「与最强专有模型仍有差距」,整段结论在措辞上是自洽的。读结论句时要养成的习惯是:先分辨这个词断言的是归属还是排序,再决定该拿多严的标准去验它。
变式:结论还说 K3 是「世界上第一个开放的 3T 级模型」。这个主张需要什么证据才能成立?它和「前沿级性能」那个主张相比,哪个更容易被推翻?(提示:想想「第一个」这种主张的证伪方式。)
13.7 这篇论文没有告诉你的事
读完一篇技术报告,最有价值的问题往往不是「它说了什么」,而是「它没说什么,以及为什么不说」。下面这份清单必须分成三类看——把行业通行的省略当成隐瞒,和把特有的省略当成正常,都是误读。
| 类别 | 缺什么 | 为什么这样归类 |
|---|---|---|
| A 类 行业通行做法 |
训练用了多少张 GPU、训练了多久 | 这几项在今天几乎所有前沿实验室的报告里都不披露,属于商业机密与竞争信息。不披露不等于报告不诚实,但它意味着训练成本与算力效率无法被外部核算,也无法验证「以更低成本达到同等效果」这类说法。 |
| 总共训练了多少 token | ||
| 整体训练成本 | ||
| 预训练数据的具体来源与配比数字 | ||
| B 类 这篇报告可以做得更好的 |
案例研究只报成功,没有成功率、尝试次数、人工干预程度 | §7 通篇没有一个失败案例或分母。这是可以在不泄露机密的前提下补上的(见 13.5 的题)。 |
| 跨模型比较的对等性问题没有被量化处理 | 论文披露了回退率、拒答数、harness 差异(值得肯定),但没有报告「剔除这些任务后」的子集分数,读者只能定性打折扣。 | |
| 关键结果几乎都没有方差 / 重复次数 | 视觉基准跑了三到五次,说明作者认可方差的重要性;但编码、智能体基准和内核优化案例都没有报告重复。CorpFin v2 差 0.2 分这类结论因此无法判断。 | |
| 内部基准既指导训练又充当考卷,且不公开 | 论文自己写了这些基准「直接指导数据和训练的迭代」。这不是错误,但它使得内部分数不能作为跨模型的裁决依据。 | |
| 内核优化案例的基线强度、内核选择依据未说明 | 所有加速百分比都相对「FLA Triton 基线」,但该基线的优化程度没有交代(见 13.3 的题)。 | |
| C 类 我自己也不确定的 |
安全对齐与价值观训练的细节 | 这四项在我负责的 §6–§8(第 25–34 页)里确实没有出现。但这份报告还有前面二十多页和可能的附录,其中是否有相关内容,我没有直接读到。如果你在原报告别处找到了对应披露,应当以原文为准,并推翻我这一栏的说法。这一栏列出来的目的,是提醒你去亲自核对,而不是让你直接引用我的判断。 |
| 权重的开源许可证是哪一种、有什么使用限制 | ||
| 可能的社会影响、滥用风险的缓解措施(§6.2.2 只谈了网络安全能力的测量,没谈发布后的缓解) | ||
| 缩放定律(scaling law)相关图表是否标注了坐标轴数值、各种「更稳定/更好」的结论是否配有定量消融 |
补充:怎么用这张表才是对的
A 类不构成对这篇报告的批评——你可以批评整个行业的不透明,但单独指责这一篇没有意义。B 类是真正可以拿来评价这篇报告的地方,而且每一条我都给出了在论文里的具体出处。C 类是我的知识边界,我把它明确标出来,是因为一份诚实的批评必须先声明自己看到了多少。
把「我没读到」说成「论文没有」,和把「论文没有」说成「论文错了」,是读技术报告时最容易犯的两个错误,方向相反但同样致命。
答辩:如果我是审稿人
你们把这个模型叫「开放」(open),权重确实放出来了。但训练数据不公开、数据配比不公开、算力规模不公开、内部评测基准不公开、评测所用的 harness 配置细节也不完整。那么「开放」到底开放了什么?把它和「开源软件」放在一个词底下,是不是一种误导?
参考防守(先自己组织语言再看)
这个质疑很难完全挡住,但可以把它拆得更清楚:
能站住的部分。第一,论文用的词是 「open」/「open-weight」(开放权重),在 Table 2 和 Table 5 里,GLM-5.2 和 K3 都被归在 「Open Weight」 这一栏——作者用的分类标签本身就是「开放权重」,而不是「开源」。这个用词是准确的。第二,开放权重带来的实际价值是真实且巨大的:你可以本地部署(成本结构完全不同于 API)、可以微调、可以做研究、可以在没有网络的环境里用、可以审计模型的行为——这些是闭源 API 一个都给不了的。第13章里 MiniTriton 和 nano-kpu 的 RTL 都放上了 GitHub,也是实打实的开放动作。第三,第12章的 WebDev Arena 第 1 之所以是一条有分量的新闻,正是因为「开放」这个属性——它意味着这个能力不再被少数几家公司垄断。
必须承认的部分。(a) 从科学可复现性的角度,开放权重远远不够。没有数据、没有配比、没有算力规模,外部研究者无法复现训练过程,只能复现推理。所以这篇报告的很多训练侧结论(「这样做更稳定」「这个改动带来了改善」)在外部是不可证伪的。(b) 内部基准不公开这一点尤其可惜——那是完全可以公开而不损害商业利益的东西,公开之后其他模型也能在上面测,结论的说服力会强很多。(c) 「开放」这个词在公众传播里确实容易被读成「开源」,而两者的差别很大;论文自己用词严谨,但转述者往往不严谨。
我认为诚实的结论:把 K3 称为「开放权重的前沿模型」是准确的;把它称为「开源模型」是不准确的;把它当作「可复现的科学工作」则是错误的。这三句话应该被分开说。
终极综合题。把第12章的九问检查清单,从「读一张表」升级成「读一整篇技术报告」。请你写出一份读技术报告的清单(不少于六条),每一条都要能在 Kimi K3 这篇报告里找到至少一个具体的正面或反面例子。写完之后,用你自己的清单给这篇报告下一个整体评价——它在哪些方面高于平均水准,哪些方面低于?
1. 主张与证据类型匹配吗?案例研究只能证明可能性,基准分数才能说明平均水平,第三方评测才能对抗自评偏差。正面例子:论文同时提供了三类(§6.1 基准、§6.3 第三方、§7 案例)。反面例子:§7 的案例被用来支持「能替代人类研究者/剪辑师」这类含平均性能意味的表述,而案例只能支持存在性。
2. 作者主动报告了哪些对自己不利的信息?正面例子:写出 90.4 这个不利的对照、承认 CritPt 落后、承认「研究级推理仍是关键改进方向」、承认与最强专有模型有差距、承认自己的安全评测只是能力下界、列出五个自己落后的内部基准。这是这篇报告最值得肯定的地方。反面例子:§6.1.4 说「一致地超过 Claude Opus 4.8」,但表 2 的 HLE-Full 和 OfficeQA Pro 是反例。
3. 结论句的量词准确吗?「一致地」「全面」「显著」这类词要逐个拿回表里验证。见第 2 条的反面例子。
4. 有没有消融实验支撑设计选择?凡是「我们改成 X,效果更好/更稳定」的说法,要找配套的定量对照。(这一条主要落在报告的前半部分,我未直接读到,见 13.7 的 C 类。)
5. 比较是否对等?harness、工具、上下文策略、硬件、回退与拒答。混合例子:论文披露了这些差异(好),但没有做剔除后的子集分析(不够)。
6. 有没有方差和重复?正面例子:视觉基准三次平均、ZeroBench 五次。反面例子:编码/智能体基准和内核优化案例都没有报告重复次数。
7. 有没有独立第三方验证?正面例子:Artificial Analysis、Vals AI、竞技场榜单,以及网络安全部分英国 AISI 与 NIST CAISI 的独立联合评估——后者尤其重要,因为它验证的是风险相关的结论。
8. 缺失的信息属于哪一类?行业通行、这篇特有、还是我没读到。见 13.7 的三分表。
整体评价:
高于平均水准的地方:配置披露的颗粒度(连「300K 触发压缩」这种小字都写了)、主动自曝不利数字、引用第三方结果时标注日期、把安全评测明确定性为下界并引入独立评估、把成本轴加进来(很多报告只报分数)、开放权重与两个 GitHub 仓库。
低于平均或可改进的地方:案例研究缺分母、关键基准缺方差、内部基准既指导训练又当考卷且不公开、少数总结句的量词超出了表格能支撑的范围、内核优化案例的基线强度未交代。
一句话:这是一篇可以被认真批评的报告——它给了你足够多的条件信息,让你能够自己去打折扣。上面这八条批评里,有一大半是用论文自己提供的信息做出来的。一份让你无从下手批评的报告,通常不是因为它更完美,而是因为它披露得更少。
变式:把这份清单反过来用——假设你要写一篇技术报告,而你希望它经得起最挑剔的读者。按这八条,你会在哪些地方多花成本?如果预算只够做其中三条,你选哪三条?为什么?
本章小结
案例研究部分展示了五类工作。GPU 内核优化:24 小时预算、相同沙箱,K3 把 AttnRes 从 283.6 ms 优化到 114.4 ms(降低约 59.7%,加速约 2.48 倍),DSA 和 KDA 分别削减 55.1% 和 73.6%,MLA 达到峰值 TFLOPS 一半以上;Figure 14 里 K3 终点 +59.7%,追平 Claude Fable 5(+57.1%,带回退),大幅超过 GPT-5.5(+30.8%)和 GPT-5.6 Sol(+17.3%)。编译器:MiniTriton 在 L20 上于核心基准套件的几何平均上超过 PyTorch eager 和 torch.compile,tensor-core 矩阵乘法在最大形状上达到实测机器上限约 90%,端到端训练 GPT 时梯度与 torch 的差异不超过 fp32 舍入误差 10−4(以 fp64 为参考)。芯片:48 小时自主运行,4 mm² 内 100 MHz 收敛时序,RTL 仿真解码超过 8,700 tokens/s,146 万标准单元、0.277 MiB SRAM,RTL 已开源。科研与知识工作:I–Love–Q 复现约两小时(对照「研究者一到两周」)、42 年 AI ASIC 产业研究网站(87 份季报、99 份 PDF、11,000 多页、2,800 多次搜索)、391 个引力波事件的并发子智能体分析。视频:3Blue1Brown 风格的自我架构解说与 56 段素材的预告片。
结论部分的措辞很克制:开放的 2.8 万亿参数 MoE、原生视觉、100 万 token 上下文、建立在 KDA 与 AttnRes 之上;世界第一个开放的 3T 级模型;在长时程编码、智能体、知识、推理与视觉上达到前沿级;与最强专有模型仍有差距;在所有人触手可及的范围内确立了新的开放前沿。
但这一章真正想留给你的是最后两件事。第一,那句藏在内核优化段落末尾的话——一个早期的 K3 检查点已经在承担作者自己的内核优化工作。这是全篇最有力的证据(作者用自己的生产环境为它背书),同时也是最需要追问的证据(这类任务的数据很可能回流进了训练)。第二,13.7 那张三分表:把一份报告的省略分成「行业通行的」「这篇特有的」「我自己也不确定的」,然后只用第二栏去批评它。这个动作比记住任何一个分数都更值得带走。
第14章 附录深读:五个被折叠起来的硬核
一句话导语:正文里那些「我们这样做,效果更好」的句子,凭什么成立?这一章把论文折进附录的五段推导摊开——为什么 SiTU-GLU 的上界正好是 100、分位数均衡里的「分位数」是从一道线性规划的对偶里长出来的、百万个数的分位数怎么用一千个格子估出来、每个 rank 预留 E/R 个冗余专家为什么就够了、以及一套聊天模板怎么被设计成能服务整整一代模型。
学完这一章你应该能做到
- 自己推出 β tanh(z/β) 在原点附近等于 z,并说清 SiTU-GLU 的输出上界 100 是怎么来的
- 把「每个专家干一样多的活」翻译成一个带约束的最大化问题,并说出它的对偶目标长什么样
- 解释「分位数」这三个字为什么会出现在一个跟统计毫无关系的推导里
- 说清为什么部署时只需要一个冻结的偏置向量,不需要任何分位数计算——这是全章最重要的一句话
- 用一个具体的两 rank 例子说明「各 rank 分位数的平均」和「全局分位数」不是一回事
- 复述定理 1 的填充构造,并解释「每个 rank 至多被填一次」这一步凭什么成立
- 给定专家总数和 EP 大小,算出每个 rank 要预留多少冗余专家槽位
- 解释「把不变的放前面、把易变的放后面」为什么是 KV 缓存友好的排布
这一章该怎么读
这是全站数学密度最高的一章。这里先立一条规矩:每遇到一个推导,先问「它想证明什么、为什么值得证」,再看怎么证。某些步骤(比如「全单模性」和「极小极大定理」)确实超出高中范围,我会明确告诉你「这一步你可以先接受结论,理解它在整条链里的位置就够了」,同时给出它凭什么成立的直觉。你不需要一次全部吃透;但如果连「这一步在整条链的哪个位置」都说不出来,那就是真的没读懂,要回头。
14.1 附录 B:怎样「温柔地」给一个乘法封顶
先说这段推导要解决什么问题。SwiGLU 这类门控线性单元(Gated Linear Unit, GLU)的输出是两个分支相乘。相乘这件事在数值上很危险:两个分支各自只要稍微大一点,乘起来就会大很多——两个 30 相乘就是 900。在万亿参数的训练里,某一层冒出一个异常大的激活值,可能顺着反向传播炸掉整轮训练。所以作者想给这个乘积封一个顶。
但封顶不能乱封。Swish 这个激活函数之所以好用,靠的是它特有的形状:在原点附近近似线性(小信号几乎原样通过,不失真),负半轴有一条渐渐消失的尾巴(不像 ReLU 那样把负数一刀切成 0,还留一点点响应)。论文原话是要「在不丢掉 Swish 特征形状的前提下限住 SwiGLU 的乘积」。所以这一节的真正问题是:怎么在既不破坏原点附近的线性、又不抹掉负尾的前提下,把输出关起来。
为什么不干脆一刀切
最省事的办法叫硬截断(hard clamping):对门的预激活值直接写一句「大于 10 的都当成 10」。这确实封顶了,但它有个致命毛病,14.1 结尾那道题会让你自己发现它。论文的选择是平滑封顶(smooth cap):不设一堵墙,而是让函数自己越靠近上限越「走不动」。
做法:把 Swish 的线性因子换成一个被压扁的 tanh
Swish 可以看成两个因子相乘:一个线性因子(就是输入本身)乘上一个 sigmoid 因子。SiTU 的改动只有一处:把线性因子换成 β₁ tanh(Wgx/β₁),sigmoid 因子原样保留。
tanh(双曲正切):一个 S 形函数,值域严格在 −1 到 1 之间,输入接近 0 时输出约等于输入本身,输入很大时输出趋近 ±1 但永远到不了。你可以把它想成「一个自带天花板和地板、但走过去是平滑减速而不是撞墙」的恒等函数。
这个替换为什么不会毁掉 Swish 的形状?关键在论文的这句解释:因为 sigmoid 本来就把负侧的门响应压向零,这个改动主要控制的是大的正激活,而不会抹掉负尾。拆开说:负半轴上,sigmoid 因子本来就很小(接近 0),所以负侧的输出本来就被压得很扁,tanh 在那边压不压得住几乎无所谓,那条尾巴保住了;真正被 tanh 管住的是正半轴上那些很大的值——原本线性因子有多大输出就有多大,现在最多只能贡献 β₁。
K3 对上分支施加了完全相同的构造:β₂ tanh(Wux/β₂)。论文给的理由是防止任一分支主导乘积——只封一个分支没用,另一个分支照样能把乘积拉飞。
局部与极限行为(式 18)
现在要回答一个必须回答的质疑:你把线性因子换成 tanh,那「原点附近近似线性」这条还成立吗?论文用式 18 回答:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| z | 一个标量(这里指分支的预激活值,即 Wgx 的某一个坐标) | 「信号有多强」 |
| β | 一个正的常数超参数,K3 取 β₁ = 4(门分支)、β₂ = 25(上分支) | 天花板的高度:β 越大,封顶越松,越接近原来的 SwiGLU |
| tanh | 双曲正切函数,值域 (−1, 1) | 平滑的「软天花板」 |
| O(z3/β2) | 误差项的量级记号:误差不会比 z3/β2 的某个常数倍更大 | 「漏掉的那点零头,最多这么大」 |
大 O 记号(big-O notation):写 f(z) = O(g(z)) 的意思不是「等于」,而是「当 z 足够小(或足够大,看语境)时,|f| 不会超过 g 的某个固定倍数」。它是一种只关心增长速度、不关心具体常数的粗略比较方式。这里的语境是 z → 0。
式 18 说的事情很干脆:把 z3/β2 这一项忽略掉,β tanh(z/β) 就是 z 本身。所以在原点附近,SiTU-GLU 和 SwiGLU 一阶匹配(matches to first order)——两条曲线在原点处不但重合,连斜率也一样。而且注意分母上的 β²:β 越大,误差越小;当 β₁, β₂ → ∞ 时,SiTU-GLU 逐点还原成 SwiGLU。换句话说,SwiGLU 是 SiTU-GLU 在 β 取无穷大时的特例,这个改动是「加了一个可以调松调紧的旋钮」,不是「换了个别的东西」。
自己推一遍:式 18 的三阶展开
已知 tanh 在 0 附近有展开式 tanh(u) = u − u3/3 + …(后面是 u5 及更高次项)。现在要算的是 β tanh(z/β)。你的第一步该做什么?
想好了再看
做代换:令 u = z/β。当初为什么会想到这一步?因为 tanh 的展开式是「以自变量为主角」写的,而我们的 tanh 里装的不是 z 而是 z/β——凡是遇到「已知公式的自变量位置上塞了个别的东西」,第一反应就是给那个东西起个名字,套公式,最后再换回来。
代进去,得到 tanh(z/β) = ?(先别乘外面的 β)
想好了再看
tanh(z/β) = z/β − (z/β)3/3 + … = zβ − z33β3 + …。注意三次方要连分母一起立方,分母变成 β³,这是最容易漏的地方。
现在乘上外面的 β。你会看到什么?
想好了再看
β · tanh(z/β) = z − z33β2 + …。第一项恰好是 z,β 被完全消掉了。这就是「一阶匹配」的全部内容:不管 β 取多少,原点附近的一次项永远是 z,和没做任何改动时一模一样。
最后:为什么论文写成 O(z3/β2),而不是精确写成 −z3/(3β2)?
想好了再看
因为后面还有 z5/β4、z7/β6 …无穷多项。写成大 O 是在说「从三次项开始的所有剩余项加起来,量级不超过 z3/β2 的常数倍」,而那个 1/3 的常数在这里不重要——重要的信息只有两条:误差是三次的(z 小的时候掉得飞快),以及误差被 β2 除(β 大的时候也掉得飞快)。
论文说:因为 |tanh(z)| < 1 且 0 < Sigmoid(z) < 1,所以每个输出坐标满足 ‖SiTU-GLU(x)‖∞ ≤ β₁β₂ = 100(式 19,取 β₁ = 4、β₂ = 25)。请你把这两行之间省掉的步骤补出来:门分支的绝对值最大是多少?上分支最大是多少?为什么两者相乘就得到 100?
变式:如果把 β₁ 和 β₂ 都改成 10(乘积还是 100),上界还是 100 吗?两种取法在行为上有什么区别?(提示:想想哪个分支更需要保留大动态范围——β = 4 意味着这个分支在预激活超过大约 4 之后就基本饱和了,而 β = 25 要到 25 才饱和。论文没有解释 4 和 25 这组具体数字是怎么选的。)
为什么是「平滑封顶」而不是「硬截断」
论文最后一句是整段的落点:不同于对门的预激活做硬截断,平滑的上限在远离饱和边界处保留非零梯度,作者发现这带来更好的训练行为。这句话值得慢慢读——它是这一节唯一带实验色彩的主张,而且论文没有给任何对比曲线。
有人说:「tanh 也有天花板,硬截断也有天花板,两者本质上没区别,硬截断还更省算力。」请你构造一个具体的输入区间来反驳他,说明硬截断会发生而平滑封顶不会发生的事情是什么。(提示:想想「梯度」是什么——它是函数的斜率。)
平滑封顶不同:β tanh(z/β) 的导数是 1 − tanh²(z/β),在 z = 12、β = 10 处约为 1 − tanh²(1.2) ≈ 1 − 0.834² ≈ 0.30,在 z = 30 处约为 1 − tanh²(3) ≈ 0.01。数值很小,但严格为正。所以梯度被衰减而不是被抹掉,神经元始终知道「我在哪一侧、该往哪边挪」,有机会被拉回来。论文说的「在远离饱和边界处保留非零梯度」就是这个意思。
反例区间因此可以取任何满足 |z| > c 的区间,比如 [12, 30]:在这里,硬截断的梯度是 0,平滑封顶的梯度是正数,两者不是程度差别而是有无差别。
变式:反过来给硬截断辩护——构造一个场景,说明「梯度为零」在某些情况下反而是想要的(提示:想想梯度裁剪 gradient clipping 为什么被广泛使用,以及「我们希望模型完全忽略这个方向」的场合)。然后指出这个辩护为什么救不了这里的用法:在激活函数内部制造大片零梯度区,和在优化器外部裁剪总梯度范数,代价完全不同。
14.2 附录 C 之一:先把「专家均衡」写成一道数学题
接下来是本章篇幅最大、也最值得慢慢啃的一段:分位数均衡(Quantile Balancing, QB)那条更新规则到底是从哪来的。前面讲 MoE 路由的那一章告诉你 QB 「取一个分位数当偏置」,但没告诉你为什么偏偏是分位数。附录 C 的回答是:它不是拍脑袋设计的,它是一道最优均衡指派问题的对偶问题的精确解。论文说这个推导跟随文献 [111],而「把专家负载均衡看成指派问题」这个视角可以追溯到 BASE Layers [67] 和 BIP [116]。
为什么值得证
如果 QB 只是「我们试了试,取分位数效果不错」,那它就是一个调参技巧,换个规模可能就失效。而如果能证明它是某个明确定义的最优化问题的精确解,你就知道了三件事:它在优化什么、它什么时候会失效(前提假设被破坏时)、以及它和别的方法(比如免辅助损失均衡的符号更新)是什么关系。14.6 会看到,这最后一点带来了一个非常漂亮的统一。
把话翻译成式子
先说清这道题在说什么。有 m 个 token,n 个专家。路由器给出一张打分表 s,第 i 行第 j 列的 si,j 表示「token i 有多想去专家 j」。规则是每个 token 恰好选 k 个专家。用一个开关变量 xi,j ∈ {0, 1} 记录「token i 到底有没有被指派给专家 j」。目标是让总分最高,但要在每个专家干一样多的活这个硬约束下做到。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| s ∈ Rm×n | 路由分数矩阵,m 个 token 对 n 个专家的打分 | 一张「谁想去哪」的意愿表 |
| m | token 数量(训练时是一整批,百万量级) | 要安排座位的人数 |
| n | 专家数量(论文提到「接近 103 个专家」) | 可选的窗口数 |
| k | 每个 token 选中的专家个数(Top-k 的那个 k) | 每人要排几个队 |
| xi,j ∈ {0,1} | 指派开关:1 表示 token i 被指派给专家 j | 一张 0/1 的座位表 |
| Σj xi,j = k | token 侧约束:每行恰好 k 个 1 | 每个人恰好排 k 个队,不多不少 |
| Σi xi,j = mk/n | 专家侧约束:每列恰好 mk/n 个 1(假设整除) | 每个窗口恰好服务同样多的人 |
| s.t. | subject to 的缩写,「在满足以下条件的前提下」 | 「但是必须……」 |
mk/n 这个数是怎么来的?总共有 m 个 token、每个选 k 个专家,所以总共要发出 mk 张「服务券」;n 个专家平分,每人 mk/n 张。论文明确写了「假设整除」,也就是假设 mk 能被 n 整除——这是一个为了让式子干净而做的简化。
打个比方
食堂有 n 个窗口,m 个学生,每人要打 k 个菜,每个菜必须去不同窗口。每个学生心里对每个窗口有个满意度打分。食堂经理的要求是:每个窗口必须服务同样多的人次(不能有的窗口排长队、有的闲着),在这个前提下,让全体学生的总满意度最高。式 20 就是这道题。
类比失效处:真实食堂的窗口忙闲不均只是体验问题,而 MoE 里专家负载不均是硬件问题——每个专家住在不同的 GPU 上,最忙的那个 GPU 决定整批的耗时,其他 GPU 全在干等。另外学生的满意度是固定的,而路由分数 s 是模型自己学出来的、每一步都在变。
设 m = 12 个 token,n = 8 个专家,k = 2。(a)每个专家恰好服务多少个 token?(b)如果把 m 改成 10,会发生什么?论文怎么处理这种情况?(c)在真实训练里,m 是一整个训练步的 token 数(百万级),这个整除假设有多要紧?
变式:如果把专家侧约束从等式 Σi xi,j = mk/n 改成不等式 Σi xi,j ≤ mk/n,整除问题还存在吗?可行集会变成什么样?(这个改动正是论文提到的 BIP 的做法,14.6 会看到它带来的别的后果。)
14.3 附录 C 之二:松弛与对偶——把 mn 个开关换成 m+n 个阈值
式 20 有一个致命的实际困难:xi,j 只能取 0 或 1。这类「变量只能取整数」的最优化问题叫整数规划(integer programming),在一般情况下是出了名的难算——你不能求导,不能「往梯度方向挪一点点」,因为 0 和 1 之间没有「一点点」。而这里的规模是 m × n ≈ 百万 × 一千 = 十亿个 0/1 变量,每个训练步都要解一遍。
第一步:松弛
线性松弛(linear relaxation):把「只能取 0 或 1」放宽成「可以取 0 到 1 之间的任意实数」。放宽之后问题变成一个线性规划(linear program, LP)——目标函数和所有约束都是变量的一次式。线性规划是被研究得最透的一类最优化问题,有成熟高效的求解办法。
松弛的代价通常是:放宽后的最优解可能是小数(比如 xi,j = 0.37,「token 有 37% 去这个专家」),没有实际意义,最优值也会比原问题更高(因为可选范围变大了),我们得到的只是一个上界。但这里不会。论文说:
由于二部 b-匹配多面体的标准全单模性,这个 LP 的最优解是整数的;因此这个松弛是精确的。
「全单模性」是干嘛用的(这一步可以先接受结论)
全单模性(total unimodularity)是一个关于约束矩阵的性质。粗略地说:如果一个线性规划的约束写成 Ax = b 的形式,而矩阵 A 具有这个性质(它的每个方阵子块的行列式都是 0、+1 或 −1),并且 b 全是整数,那么这个 LP 的最优解自动落在整数格点上——你不需要额外要求「必须是整数」,它自己就是。
为什么式 20 的约束有这个性质?因为它的结构是二部图匹配:一边是 token,一边是专家,xi,j 是「连不连这条边」。这类问题的约束矩阵每一列恰好只有两个 1(一个来自 token 行、一个来自专家行),这种「每列两个 1 且能分成两组」的结构正是全单模的经典例子。这一步你可以先接受结论,理解它在整条链里的位置就够了:它的位置是——它让我们后面所有「求导、找极小点」的连续数学都合法,而且不损失任何东西。没有它,我们求出的解可能是一堆小数,跟原来那道 0/1 题对不上。
如果要一个直觉:二部图上的「分数解」总能被拆成若干条交替路径和环,沿着环把权重往一头推可以在不降低目标值的前提下把小数变成 0 或 1,一直推下去就得到一个不差于原解的整数解。所以最优值一定能被某个整数解取到。
第二步:引入乘子,写成 max–min
现在处理约束。拉格朗日乘子法(Lagrange multiplier)的核心想法是:与其把约束当成一堵墙,不如把违反约束变成一种罚款。给每条约束配一个「价格」,违反得越多罚得越狠,然后让对手去挑价格,挑到让你无利可图为止。
具体做法:给 token 侧的 m 条等式约束各配一个自由乘子 αi,给专家侧的 n 条等式约束各配一个 βj,把约束「违反量」乘上乘子减到目标里:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| αi | token i 那条约束的乘子,可正可负(「自由」就是这个意思) | token i 的「门槛价」:分数不超过它就别想被选上 |
| βj | 专家 j 那条约束的乘子 | 专家 j 的「拥挤费」:越抢手收费越高 |
| (Σj xi,j − k) | token 侧约束的违反量,满足约束时为 0 | 「这个人多排/少排了几个队」 |
| maxx minα,β | 先由内层的 α、β 取极小,再由外层的 x 取极大 | 你先出招(选 x),对手看着你的招定价(选 α、β)把你的收益压到最低 |
为什么这个 max–min 和式 20 等价?因为内层的 min 是对手在惩罚你违约:只要某条约束的违反量不是 0,对手就可以把对应的乘子往合适的方向拉到无穷大,把你的目标值砸到负无穷。所以你(外层的 max)唯一理智的选择是让所有违反量都等于 0,也就是老老实实满足约束;而一旦满足,那两个惩罚项就整个消失,目标退回原来的 Σ x s。这就是拉格朗日乘子法的全部魔术。
第三步:交换 max 和 min
论文接着说:目标关于 x、α、β 都是线性的,可行集是凸的,所以极小极大定理允许交换优化次序,得到式 22:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| si,j − αi − βj | 论文叫它间隔(margin):分数扣掉 token 门槛价、再扣掉专家拥挤费之后剩下的净值 | 「扣完两道费之后,这笔交易还划不划算」 |
| k Σi αi | 把式 21 里 −Σiαi·(−k) 整理出来的项,与 x 无关 | 固定的「入场费收入」 |
| (mk/n) Σj βj | 同理,专家侧的常数项,与 x 无关 | 固定的「摊位费收入」 |
极小极大定理(minimax theorem):在合适的条件下(目标关于双方各自的变量分别是凹的/凸的,可行集是凸的、其中一方还紧),「你先出招我后定价」和「我先定价你后出招」两种玩法得到的最优值相同。这就是允许把 max min 写成 min max 的依据。这一步同样可以先接受结论:它在链条里的位置是——它让我们可以先固定价格再看指派,从而把内层变成一个逐项独立的简单问题。直觉上它成立是因为双线性的目标 + 凸的可行域下不存在「信息优势」,谁先出手都一样。
自己推一遍:从式 22 的内层极大,到式 23 的 max(0, ·)
看式 22 的内层:maxx∈[0,1] Σi,j xi,j(si,j − αi − βj)。注意此时 α 和 β 已经被外层固定住了。这个求和里,各个 xi,j 之间还有没有互相牵制?
想好了再看
没有了。这是整段推导的关键转折:约束已经被乘子「买断」了,内层里再没有任何一条把不同 xi,j 绑在一起的等式。所以这个 max 是可分的(separable)——十亿个变量各管各的,可以一个一个单独最大化。当初为什么会想到这一步?因为拉格朗日乘子法的目的从来就是「用价格换掉耦合约束」,换完之后一定要检查耦合是不是真的没了,这是标准动作。
那就单看一项:x·c,其中 c = si,j − αi − βj 是一个已知的数,x 可以在 [0, 1] 里任选。x 取多少能让 x·c 最大?
想好了再看
分情况:c > 0 时,x 越大越好,取 x* = 1,最大值是 c;c < 0 时,x 越小越好,取 x* = 0,最大值是 0。这正是论文那句「si,j − αi − βj > 0 时 x*i,j = 1,< 0 时为 0」。注意一个非常重要的副产品:虽然我们把 x 松弛到了 [0,1],但最优解自动落在端点 0 或 1 上——小数解根本不会出现。这和前面全单模性的结论对上了。
c 恰好等于 0 怎么办?
想好了再看
此时 x 取 [0,1] 里任何值目标都一样(都是 0),最优解不唯一。论文的处理是一句话带过:相等的情形在实践中测度为零。翻译成人话:s、α、β 都是连续取值的浮点数,「三个数恰好抵消得一分不差」这件事的概率是 0,工程上不用管。但这是一个应该记住的软肋——如果路由分数出现大量并列(比如被量化到很少的档位),这个假设就不成立了,14.4 结尾的答辩框会回到这一点。
把每一项的最大值 max(0, c) 加起来,再把式 22 里那两个与 x 无关的常数项带上,你得到了什么?
想好了再看
正好就是式 23:Σi,j max(0, si,j − αi − βj) + kΣiαi + (mk/n)Σjβj。注意 max(0, ·) 这个写法把上一步的两种情况合并成了一个式子:c > 0 时它等于 c(对应 x* = 1),c ≤ 0 时它等于 0(对应 x* = 0)。十亿个 0/1 变量消失了,只剩下 m + n 个实数变量 α 和 β。这就是对偶的全部收益。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| L(α, β) | 论文称之为凸对偶目标(convex dual objective),只依赖 α 和 β | 「定价方案的总成本」,越低越好 |
| := | 定义符号,「左边这个记号被定义为右边这个式子」 | 给它起个名字 |
| max(0, s − α − β) | 只统计间隔为正的部分,负的一律当 0 | 「只算真正做成的交易赚了多少」 |
| 凸 | 函数图像像一只碗,任意两点连线不低于函数本身 | 只有一个「最低点」,走下坡一定能走到全局最优 |
凸这个性质在这里是白送的好处:max(0, ·) 是凸函数,一堆凸函数相加还是凸函数,再加线性项还是凸的。凸的意思是没有「假底」——你不会走到一个局部最低点就被困住。这保证了下一节那个「交替往下走」的算法一定收敛到真正的最优。
在式 23 的最优点,token i 会被指派给专家 j 当且仅当 si,j > αi + βj。用食堂的比方解释 αi 和 βj 各自扮演什么角色,然后回答:如果某个专家特别抢手(很多 token 给它高分),它的 βj 会被推高还是压低?为什么这个方向是对的?
抢手的专家,βj 会被推高。理由:把 βj 增加 δ,Σ max(0, ·) 里每一个当前为正的项都减少 δ,共减少 δ×(当前被选中的 token 数),而线性项增加 δ×(mk/n)。所以 L 的变化率是 (mk/n) − (当前负载),也就是式 27 那个次梯度。当前负载远超目标负载时这个变化率是负的——把 βj 调高能降低 L,优化过程自然会推高它。推高 βj 之后判据 si,j > αi + βj 变难满足,来的 token 变少,负载降下来。这个方向当然是对的:拥挤就涨价,涨价就分流,直到供需平衡。
反过来,被冷落的专家(负载低于目标)变化率为正,βj 会被压低甚至压成负数——负的拥挤费就是补贴,主动把 token 拉过来。能压成负数这一点很关键,14.6 会看到 BIP 恰恰输在这里。
变式:把式 20 的目标从「最大化总分」改成「最大化最低那个 token 的分数」(极大化极小),拉格朗日那一套还能用吗?内层的 max 还可分吗?(提示:min 不是求和,它把所有项重新耦合在了一起。)
14.4 附录 C 之三:分位数是从哪冒出来的
现在到了最漂亮的一段。我们要极小化式 23 的 L(α, β)。论文的办法叫精确坐标极小化(exact coordinate minimization):固定 β 求最优的 α,再固定 α 求最优的 β,来回交替。这个套路本身很常见(想想解二元方程组时「先把一个变量当已知数」),真正特别的是——这里的每个子问题都有闭式精确解,不需要迭代、不需要学习率,一步跳到底。
闭式解(closed-form solution):能直接用一个公式写出来的答案,而不是「跑一个循环慢慢逼近」。比如一元二次方程有求根公式,那就是闭式解。
固定 β 时,L 里唯一还含 αi 的部分只跟第 i 行有关,所以问题在 token 上完全解耦——m 个 token 各自解各自的一维问题。对 token i:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| α | 这里省掉了下标 i,就是当前这个 token 的门槛价,唯一的未知量 | 要调的那一个旋钮 |
| si,j − βj | n 个已知的数:这个 token 对每个专家的分数扣掉该专家的拥挤费 | n 个「标好价的候选」,排成一排 |
| kα | 线性项,斜率恒为 k | 「每提高一点门槛,要付 k 倍的代价」 |
| Σj max(0, · − α) | 把所有「高过门槛」的候选超出的部分加起来 | 门槛越低,超出的越多,这一项越大 |
论文接着给出关键判断:这个目标关于 α 是分段线性的,斜率是 k 减去超过 α 的间隔个数;因此当恰好有 k 个间隔在 α 之上时取到极小。这句话信息量很大,我们用一个具体例子把它走一遍。
自己推一遍:为什么极小点卡在第 k 大和第 k+1 大之间
具体设定:k = 2,某个 token 有 n = 5 个专家,五个「标好价的候选」 si,j − βj 从大到小是 0.9, 0.7, 0.4, 0.2, 0.05。记 g(α) = 2α + Σj max(0, vj − α)。
先算 g(1.0)、g(0.9)、g(0.7)。你注意到什么?
想好了再看
g(1.0) = 2.0 + 0 = 2.0(α 比所有候选都高,max 全是 0)。g(0.9) = 1.8 + 0 = 1.8。g(0.7) = 1.4 + (0.9−0.7) = 1.6。从 1.0 降到 0.9,函数值降了 0.2,α 降了 0.1,斜率是 2;从 0.9 降到 0.7,函数值降了 0.2,α 降了 0.2,斜率是 1。斜率变了。
为什么斜率会从 2 变成 1?准确地说出是哪一项在 α 穿过 0.9 时「醒过来」了。
想好了再看
当 α 从 1.0 降到 0.9 以下时,
max(0, 0.9 − α)这一项从「恒为 0」变成「等于 0.9 − α」。这一项对 α 的斜率是 −1。原来总斜率只有线性项贡献的 +2,现在加上这个 −1,变成 +1。
一般规律:每当 α 往下穿过一个候选值,就有一项被「激活」,总斜率就减 1。所以在任何位置,斜率 = k − (严格超过 α 的候选个数)。这正是论文那句「斜率是 k 减去超过 α 的间隔个数」。按这个规律列出所有区间上的斜率。哪一段的斜率是 0?
想好了再看
α > 0.9:0 个超过,斜率 2−0 = +2。
0.7 < α < 0.9:1 个超过,斜率 +1。
0.4 < α < 0.7:2 个超过,斜率 0。← 平的
0.2 < α < 0.4:3 个超过,斜率 −1。
0.05 < α < 0.2:4 个超过,斜率 −2。
α < 0.05:5 个超过,斜率 −3。
斜率随 α 增大而单调不减(每穿过一个候选就 +1),这正是凸函数的特征。凸的分段线性函数,极小点在哪?把上面的区间对应到「第几大」。
想好了再看
凸函数的极小点就是斜率从负变到非负的地方。这里斜率恰好为 0 的那一整段是 [0.4, 0.7],上面所有点都是极小点,最小值 g = 1.6(自己验算:g(0.4) = 0.8 + 0.5 + 0.3 = 1.6,g(0.7) = 1.4 + 0.2 = 1.6,确实相等)。
而 0.7 是第 k = 2 大的候选,0.4 是第 k+1 = 3 大的候选。所以:极小点是任何介于第 k 大和第 (k+1) 大之间的 α——这正是论文说的「当恰好有 k 个间隔在 α 之上时取到极小」。论文说「约定取第 (k+1) 大的条目」。既然整段 [0.4, 0.7] 都是极小点,为什么偏偏挑最左端的 0.4?这个约定不是随便挑的,想想 14.3 里判据 s − α − β > 0 是严格不等号。
想好了再看
取 α = 0.4(第 3 大)时,严格大于 α 的候选是 0.9 和 0.7,恰好 2 个 = k 个;0.4 自己等于 α,按严格不等号判据不入选。取 α = 0.7(第 2 大)时,严格大于它的只有 0.9,只有 1 个 = k−1 个,选不满!
所以这个约定的作用是:让「严格大于 0」这条规则恰好挑出 k 个专家,一个不多一个不少,正好满足 token 侧约束 Σj xi,j = k。论文只写了「by convention」(按约定)四个字,没有解释这一点,但这是它必须这么取的真正原因。最后一步:为什么「第 (k+1) 大」等价于「(1 − k/n) 分位数」?
想好了再看
分位数quantilep(v) 的意思是「把 v 从小到大排好,取位置在整个序列 p 这个比例处的那个数」。n 个数里,比例 p = 1 − k/n 对应的位置是从小往大数第 p·n = n − k 个。而「从小往大数第 n−k 个」就是「从大往小数第 n − (n−k) + 1 = k+1 个」。
用例子验:n = 5, k = 2,p = 1 − 2/5 = 0.6,位置 0.6 × 5 = 3,从小到大第 3 个是 0.4——正是第 3 大。对上了。所以「分位数」这个词在这里不是统计学意义上的近似描述,它就是「第几大」的另一种说法。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| si | 打分表的第 i 行:token i 对全部 n 个专家的分数 | 沿 token 轴看 |
| si − β | 整行减去专家阈值向量,得到 n 个间隔 | 扣完拥挤费之后的净值 |
| quantile1−k/n | (1 − k/n) 分位数,等价于第 (k+1) 大 | 「从大往小数第 k+1 个」 |
| αi* | token i 的最优门槛价 | 刚好卡住 k 个专家的那条线 |
专家侧完全对称。固定 α 时,专家 j 解 minβ (mk/n)β + Σi max(0, si,j − αi − β)。一模一样的分段线性论证:线性项的斜率现在是 mk/n,候选有 m 个(这一列上所有 token 的间隔),所以极小点是第 (mk/n + 1) 大的条目。而 m 个数里的第 (mk/n + 1) 大,正是从小往大数第 m − mk/n = m(1 − k/n) 个,还是 (1 − k/n) 分位数:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| s:,j | 打分表的第 j 列:全部 m 个 token 对专家 j 的分数。冒号表示「这一维取全部」 | 沿 专家轴看 |
| s:,j − α | 整列减去 token 门槛价向量,得到 m 个间隔 | 「每个人对这个窗口还剩多少净意愿」 |
| βj* | 专家 j 的最优阈值(拥挤费) | 刚好卡住 mk/n 个 token 的那条线 |
Algorithm 1 就是把这两条交替执行的求解器,只有六行:初始化 β ← 0;循环 T 次,每次先把 s − β 按行降序排、取第 k+1 列作为 α(对应式 25),再把 s − α 按列降序排、取第 mk/n+1 行作为 β(对应式 26);最后返回 xi,j = 1 当且仅当 j 属于 si − β 的 Top-k。注意算法里的下标写法 [:, k:k+1] 和 [mk/n : mk/n+1]——那是从 0 开始计数的切片,取的正是「第 k+1 大」和「第 mk/n+1 大」,和上面推的完全一致。
某个 token 对 6 个专家的间隔 si,j − βj 依次是 0.81, 0.12, 0.55, 0.73, 0.30, 0.44,取 k = 2。按式 25 算出 αi*。然后验证:用判据「间隔 > αi*」挑出来的专家是不是恰好 2 个,是不是恰好是 Top-2。
验证判据:严格大于 0.55 的间隔是 0.81 和 0.73,恰好 2 个,恰好是 Top-2。0.55 自己不算(严格不等号),这正是「取第 k+1 大」这个约定的作用。
顺带体会一下:如果错取成第 k = 2 大(0.73),那么严格大于它的只剩 0.81 一个,只选出 1 个专家,token 侧约束就破了。
变式:同样这组数,如果 k = 4,α* 是多少?再问一个更有意思的:如果六个间隔里有两个并列(比如把 0.44 也改成 0.55),「恰好选出 k 个」还成立吗?这对应论文里哪句被一笔带过的假设?
答辩:如果我是审稿人
你这条推导链上挂了一串「温和的技术性假设」:mk/n 整除、平局的测度为零、极小极大定理可交换、松弛因全单模而精确。可真实训练里 m 是随机变动的、分数是低精度浮点(bf16 只有 8 位尾数,平局远比你想的常见)、而且每一步的 s 都在变——你根本没有把任何一个问题「解到最优」,你只是在一个一直在动的目标上走了几步。凭什么还叫「精确坐标极小化」?
参考防守(先自己组织语言再看)
分三层答。
第一层,先承认成立的部分。「精确」这个词修饰的是子问题,不是整个训练过程:给定当前这一批 s 和当前的 β,式 25 给出的 α 确实是式 24 的精确极小点,不是近似、不是一步梯度。这个措辞没有夸大。至于整体是否收敛到全局最优,论文本来也没有声称——它声称的是「几步之内达到平衡」,这是一个关于速度的经验陈述。
第二层,逐条回应假设。整除假设只影响推导的记号整洁度,实现里用插值处理非整数秩(附录 D 明确做了 clip 插值),不构成实际问题。平局的批评是这几条里最有杀伤力的一条:bf16 下路由分数确实可能大量重复,此时「恰好 k 个严格大于 α」会失效,选出的专家数可能少于 k。但注意后果的方向:这只会造成「某些 token 少选了专家」,而实际实现是先算阈值再做一次标准的 Top-k(Algorithm 1 第 6 行正是 argtopk,不是阈值筛选),Top-k 在并列时有确定性的打破规则,所以并列不会破坏「每个 token 恰好 k 个」,只会让哪一个被选中略有任意性。真正被并列影响的是专家侧的负载精度,那是一个 O(并列个数) 的偏差。
第三层,交出真正的软肋。目标一直在动这一点无法辩护成「仍然是精确最优」,只能辩护成「这就是设计意图」:QB 从来不是要解一个静态的指派问题,它是要给一个不断漂移的分数分布提供每步都自校准的阈值。它的价值恰恰在于每步都能一跳到位,所以跟得上漂移——如果它需要几十步才收敛,分数早就变了。论文自己也没有给「QB 解与真实最优指派解的差距」的任何度量,这是一个可以要求补的实验。
14.5 附录 C 之四:丢掉 α,只留 β——全章最重要的一句话
到这里我们有了 α 和 β 两组阈值。但部署一个模型时,你不可能在每次推理时都去解一遍分位数——用户问一句话只有几十个 token,哪来的「整批 token 的分布」给你取分位数?如果 QB 训练时依赖分位数、推理时又算不了分位数,那训练和推理的路由行为就对不上,模型会当场崩掉。这一小节回答的就是这个疑虑,而答案漂亮得有点意外。
论文的推理只有两句:在式 23 的最优点,x*i,j = 1 当且仅当 si,j − αi* − βj* > 0;再结合 token 约束 Σj x*i,j = k,被选中的专家恰好是 si − β* 的 Top-k。
为什么第二句成立?把判据移项:si,j − βj* > αi*。注意左边只跟专家有关,右边 αi* 对这个 token 的所有专家是同一个数——它是一条水平线。「在同一条水平线以上的那些项」,加上「恰好有 k 个在线以上」,那不就是 Top-k 吗?αi 的全部作用,就是画出这条线;而一旦你知道「要选 k 个」,这条线的具体高度就完全不必知道了——直接排序取前 k 个即可。
这个不对称为什么是整段推导的落点
路由因此只需要专家阈值 β ∈ Rn(等价于式 13 里的偏置 b = −β),而 token 阈值 α ∈ Rm 是与动态训练批次绑定的中间变量,可以直接丢掉。论文原话是「discarded」。
为什么可以丢?看两个向量的长度就明白了:β 只有 n 个数(专家个数,约 103),它是模型参数级别的东西,跟着模型走;而 α 有 m 个数(这一批的 token 数,百万级),它跟着这一批数据走,换一批数据就完全失效——它压根不是模型的一部分。推导的结论恰好是:需要留下的那个是能留下的那个。
论文把这个性质称为训练–推理一致性(train–inference consistency):部署时,路由就是「分数减去一个冻结的偏置,然后取 Top-k」这样一个固定操作,不需要任何分位数计算。训练时那套复杂的分位数机器,最后只留下 n 个数的残留物。
综合题。假设有人提出一个「更对称」的路由方案:部署时同时保留 α 和 β,对每个 token 用它自己的 αi 做阈值筛选(选出所有满足 si,j − βj > αi 的专家),而不是取 Top-k。请回答三问:(a)推理时的 αi 从哪来?(b)如果强行用训练结束时保存的某个 α 的平均值当作全局阈值,会出什么问题?(c)论文的方案为什么绕开了这一切?把答案和「每个专家恰好服务 mk/n 个 token」这条原始约束联系起来。
(a) 推理时 αi 其实可以现算——因为式 25 只需要这个 token 自己那一行的 n 个间隔,单个 token 就够了,不需要整批。所以这个方案在计算上并非不可行。这是一个要说清楚的细节:α 之所以被丢弃,不是因为算不出,而是因为没必要算——既然它的唯一作用是画出那条「恰好卡住 k 个」的线,而 Top-k 直接就实现了同样的事,多存 m 个数、多做一次排序取值纯属浪费。
(b) 如果换成一个固定的全局阈值(比如所有 αi 的平均),后果是每个 token 选中的专家数不再固定:常见词的最高分只有 0.4,一个专家都过不了线,直接被路由到 0 个专家(输出退化);代码片段有两个 0.95,可能过线 5 个甚至 10 个。计算量随输入内容剧烈波动,批处理时无法预先分配显存和通信缓冲区,推理系统会很难做。更根本的是,训练时每个 token 都是恰好 k 个专家,推理时变成可变个数,模型见到的输入分布和训练时不一致。
(c) 论文的方案绕开这一切,因为它把「选 k 个」这条硬约束直接编码进了操作本身(Top-k 天然就是 k 个),而不是靠一个阈值间接实现。回到原始约束:式 20 有两条约束,token 侧的 Σj x = k 和专家侧的 Σi x = mk/n。token 侧那条被 Top-k 这个操作本身满足了,所以它的乘子 α 不必留;专家侧那条没有任何操作能天然满足(它是跨 token 的全局性质),所以它的乘子 β 必须留下来当偏置。两个乘子命运的差别,根源在于两条约束一条是「每行的性质」、一条是「每列的性质」,而推理是一行一行来的。
顺带说一句:论文最后提到的 Expert Threshold 路由走的正是 (b) 那条路(维护 EMA 阈值、允许每 token 选中的专家数可变),论文明确说 K3 的做法「与之相关但不同」。
变式:反过来想——有没有哪种模型架构,会让「专家侧约束」也能被单个 token 的操作天然满足,从而连 β 都不用留?(提示:想想如果一个 token 的 k 个专家被要求「每个专家组里各选一个」会怎样,这类做法在文献里叫分组路由。它付出的代价是什么?)
14.6 附录 C 之五:QB 和「符号更新」其实是同一个目标的两种走法
这一小节把 QB 和读者在前面章节见过的免辅助损失均衡(auxiliary-loss-free balancing)接上了头。后者的做法非常朴素:某个专家的负载超了,就把它的偏置减一个固定的小量;不够,就加一个固定的小量。只看方向,不看超了多少。这个做法有效但看起来很「土」,附录 C 给了它一个精确的位置。
论文对式 26 底下的专家侧子问题求(次)梯度:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| ∂L/∂βj | 对偶目标对专家 j 阈值的偏导数 | 「把这个专家的拥挤费提一点,总成本会怎么变」 |
| χ(条件) | 示性函数(indicator function):条件成立取 1,否则取 0 | 一个只会答「是/否」的计数器 |
| Σi χ(…) | 数一数有多少个 token 的间隔为正,也就是实际被路由到专家 j 的 token 数 | 观测负载 |
| mk/n | 每个专家应该服务的 token 数 | 目标负载 |
| 次梯度 | 函数在「折点」处没有唯一导数时的替代品,取任一条「托在函数下方」的直线的斜率 | max(0,·) 在 0 处有个尖角,这是给尖角处补的定义 |
式 27 读出来就是一句大白话:梯度 = 目标负载 − 观测负载。这就是「负载误差」本身。
现在做一步 SignSGD:梯度下降的一个变体,只用梯度的符号、不用它的大小,每步走固定的一段距离。对式 27 做 SignSGD,得到的更新是「负载超了就把 βj 往上推一个固定量,不够就往下推一个固定量」——这恰好还原出免辅助损失均衡的定步长符号更新(差一个符号约定,因为 b = −β)。
这个统一视角解释了什么
论文明确说,这个视角同时回答了两个此前只能靠「经验上好用」来解释的问题:
第一,为什么 QB 不需要类似学习率的超参数。符号更新必须选一个步长——走大了会来回震荡,走小了半天平衡不了,而且什么步长合适跟专家数、批大小、分数分布都有关。QB 根本不「走」:它直接跳到同一个对偶目标的精确坐标极小点。既然是一步到位,就没有「一步走多远」这个问题。符号更新只保留了负载误差的方向;QB 用上了误差的全部信息。
第二,为什么即使面对接近 103 个专家,QB 也能在几步更新内达到平衡。符号更新每步只挪固定的一小格,如果某个专家的偏置离目标很远,就需要很多步;专家越多,「最离谱的那个」离得越远,需要的步数越多。QB 每一步都精确地把每个专家放到位,专家数对它几乎没影响——剩下的迭代次数只是为了处理「α 和 β 互相牵制」这件事。
与 BIP 的对比:为什么「只能压不能提」是致命的
论文接着说 QB 与 BIP 相关。BIP 解的是同一道指派问题,但用的是不等式约束:Σj xi,j ≤ k 和 Σi xi,j ≤ mk/n。这个改动看起来很小——「最多 k 个」而不是「恰好 k 个」,听起来还更宽松——但它有一个连锁反应:
不等式约束诱导出乘子的非负性约束。(这是拉格朗日对偶的标准结论:等式约束的乘子自由取正负,不等式约束 ≤ 的乘子必须 ≥ 0。这一步你可以先接受结论;直觉是:不等式只在「顶到上限」时才起作用,罚款只能朝一个方向收,不能倒贴。)于是 BIP 的两个更新都要在最后加一个 max(0, ·) 截断,把算出来的负值砍成 0。
论文对后果的判断很直接:这只能抑制被过度选择的专家,而不能提拔被冷落的专家,在他们的实验中明显减慢了平衡过程。回想 14.3 那道题的结论——被冷落的专家需要一个负的 βj(负拥挤费 = 补贴)才能把 token 拉过来。BIP 把负值砍成 0,等于取消了补贴这个工具,只剩下「给拥挤的加价」这一手。
构造一个具体的小场景(自己设 m、n、k 和一组分数),使得只用「给拥挤专家加价」这一手无法在一步内达到平衡,而允许负偏置可以。然后回答:BIP 最终能不能平衡?如果能,为什么论文说它「明显减慢」而不是「无法平衡」?
token1: 0.90, 0.80, 0.01
token2: 0.85, 0.70, 0.01
token3: 0.60, 0.95, 0.01
初始偏置全 0:token1→A, token2→A, token3→B。负载是 A:2, B:1, C:0,不平衡。
只允许非负偏置(BIP 那侧):β 是加在「成本」侧的,非负的 βA 只能把 A 的有效分数往下压。把 βA 压到 0.9 以上,token1 和 token2 都会离开 A——但它们的次优选择是 B(0.80 和 0.70),不是 C(0.01)。于是负载变成 A:0, B:3, C:0,更不平衡。再压 B,它们又跑回 A。C 的分数 0.01 是所有排序里的最后一名,只靠压别人,永远轮不到它——除非把 A 和 B 同时压到有效分数低于 0.01,也就是 βA, βB 都超过 0.8 左右,这需要很多步固定步长的更新才能累积到(这正是「明显减慢」)。
允许负偏置(QB 这侧):直接令 βC = −0.7,C 的有效分数变成 0.71,立刻在 token2(0.85 / 0.70 / 0.71)的排序里升到第 2、在 token3(0.60 / 0.95 / 0.71)里升到第 2。再配合对 A 的适度加价,一两步就能达到 A:1, B:1, C:1。式 26 的分位数更新会直接算出这个负值,不需要试。
BIP 最终能不能平衡?能,但要靠「把所有热门专家压到比冷门专家还低」这条迂回路径。因为偏置的平均值在实现中通常被中心化,把别人全压下去在效果上等价于把这个专家提上来——所以它可达,只是路径长得多,要走很多步固定步长。这就是论文说「明显减慢平衡过程」而不是「无法平衡」的原因。差别不在能不能到,而在要走多少步;而在一个每步分布都在漂移的训练里,走得慢就等于永远追不上。
变式:论文提到的另一个亲戚是 Expert Threshold 路由,它维护 EMA 阈值并允许每 token 选中的专家数可变。请说明:如果允许可变个数,14.5 里「Top-k 天然满足 token 侧约束」这个论证会在哪一步失效?这会给推理系统带来什么新麻烦?
14.7 附录 D:用一千个格子估一个百万数的分位数
14.4 说清了 QB 要取什么分位数,14.5 说清了取完之后留下什么。附录 D 处理的是夹在中间的工程难题:这个分位数根本没法直接算。
难在哪?式 14 的 QB 更新要求在整个训练步上取分位数——对 n 个专家中的每一个,取间隔 si,j − αi 的 (1 − k/n) 分位数。而 m 是百万级的 token,它们横跨分散在各个数据并行 rank(data-parallel rank)和梯度累积步(gradient-accumulation step)上。
数据并行与梯度累积:训练一个大模型时,一个「训练步」的 token 太多,一张卡装不下。做法是把它们切成很多块:一部分横向分给几百上千张 GPU(数据并行),每张卡上再纵向拆成若干次小批前向(梯度累积)。后果是:一个训练步的全部 token 从来没有在任何一个地方同时出现过。
要精确取分位数,就必须排序;要排序,就必须把所有数据聚到一起。论文说:在训练循环里收集 O(mn) 个间隔求精确分位数是不现实的。给个尺度感:m ≈ 106、n ≈ 103,就是 10 亿个数,每层每步都要跨几百张卡搬一遍——通信量比模型的梯度还大。
关键观察:我们要的从来不是那些数,而是它们的分布
论文的破局点是一句非常朴素的话:更新从来不需要间隔本身,只需要它们的逐专家分布,而直方图(histogram)能以固定代价概括这个分布。
直方图:把取值范围切成若干个格子(箱,bin),然后只记录「每个格子里落了多少个数」。原始的一百万个数被压缩成一千个计数。你丢掉了每个数的精确值(只知道它落在哪个格子里),但保住了整体分布的形状——而分位数只需要分布形状。
做直方图的对象:所需偏置 r
这里有个小转换。论文不直接对间隔做直方图,而是对所需偏置(required bias)做:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| ri,j | 「恰好能把专家 j 放在 token i 的临界点上的那个偏置」 | 「这个专家要拿到多少补贴,才刚好够得着这个 token 的门槛」 |
| αi | token i 的截断点(门槛价),即 14.4 的那条水平线 | 入选线的高度 |
| −si,j | 取了负号的分数——正是这个负号让顺序反转 | 分数越高,需要的补贴越少 |
为什么要多这一步?因为取负让间隔的顺序反转,于是式 14 的 QB 目标 b̂j 恰好是 r:,j 的 (k/n) 分位数——从「取上尾」变成了「取下尾」,而累积计数天然是从小往大数的,这样一遍扫过去就能读出答案。同时 r 的物理含义也更直接:它就是偏置本身该有的值,单位对得上,不用再换算。
分箱范围:为什么不用担心数据跑出格子
做直方图的第一个问题永远是「格子从哪到哪」。如果范围定小了,大量数据落在界外无法统计;定大了,每个格子太宽、精度太差。论文给出了一个精确的、每步自适应的范围。
自己推一遍:为什么每个 r 都落在 [bmin − 1, bmax + 1] 里
已知路由分数是 sigmoid 的输出。那么 si,j 的取值范围是什么?
想好了再看
sigmoid 的值域是开区间 (0, 1)。所以 si,j ∈ (0, 1)。这一条是整个界的基石——如果路由分数用的是别的激活(比如 softmax 之后再乘个什么,或者干脆是原始 logit),这个界就不成立了。论文能给出干净的界,是因为架构上选了 sigmoid。
现在看 αi。它不是随便一个数——回忆 14.4:αi 是这一行有偏分数里的第 (k+1) 大。那么 αi 本身一定等于什么形式的量?
想好了再看
αi 本身就是某个专家 j′ 的有偏分数 si,j′ + bj′。这是一个很容易滑过去但极其关键的观察:取分位数这个操作是「从已有的数里挑一个出来」,不是「算一个新数」,所以结果一定是原始数据中的某一个。当初为什么会想到这一步?因为要给一个量定范围,最省力的办法就是找到它「是由什么拼出来的」,然后给每个零件定范围。
设当前偏置的最小值是 bmin、最大值是 bmax。请推出 αi 的范围。
想好了再看
αi = si,j′ + bj′,其中 s ∈ (0, 1)、bj′ ∈ [bmin, bmax]。最小可能:s 逼近 0 且 b 取 bmin,得 > bmin。最大可能:s 逼近 1 且 b 取 bmax,得 < 1 + bmax。所以 αi ∈ (bmin, 1 + bmax),和论文一致。
最后一步:ri,j = αi − si,j。把两个范围相减。注意减法要取反。
想好了再看
r 的最小值出现在「α 最小 且 s 最大」:> bmin − 1。r 的最大值出现在「α 最大 且 s 最小」:< (1 + bmax) − 0 = bmax + 1。所以 ri,j ∈ [bmin − 1, bmax + 1]。这就是论文给的分箱区间。
最容易犯的错是「α 最小减 s 最小」——记住减号会把被减数的范围翻转过来。区间长度是 (bmax − bmin) + 2。切成 B 个均匀箱,箱宽 w = ?论文为什么强调「每一步重新计算范围」?
想好了再看
w = (bmax − bmin + 2)/B。每步重算的理由是:bmin 和 bmax 会随训练变化——当负载严重不均衡时,偏置会向两头扩散去纠正它,区间随之变宽;均衡之后偏置收拢,区间变窄、箱变细、精度自动提高。论文的说法是让箱宽「随偏置扩散去纠正不均衡而保持自适应」。这是一个免费的自适应机制:不需要任何调度,范围是从当前状态里读出来的。
累积与恢复
剩下的部分严格贴合一个训练步的结构:
累积阶段(零通信)。每次前向传播中,每个 rank 把它本地的 ri,j 值 scatter-add 进一个逐专家的计数矩阵 H ∈ Nn×B,跨所有微批次累积,无需通信。scatter-add 的意思就是「算出每个数该进哪个格子,然后把那个格子的计数加 1」。H 的形状是 n 行(每个专家一行)× B 列(每行 B 个格子)。
汇总阶段(一次通信)。在这一步结束时,一次 all-reduce 把所有 rank 的本地计数逐元素加起来,得到全局直方图;每个 rank 都从同一份汇总计数里恢复分位数,所以所有 rank 算出的偏置天然一致,不需要再同步。
恢复阶段。每个专家的直方图对每个 token 恰好计数一次,所以目标秩恰好是目标负载 q = mk/n,只不过现在取遍整个训练步。做法是选出累积计数首次达到 ⌈q⌉ 的那个箱,并在箱内做线性插值:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| b̂j | 估计出来的专家 j 的新偏置 | 这一步的答案 |
| bmin − 1 | 整个分箱区间的左端点 | 坐标原点 |
| βj | 被选中的箱的编号(从 0 开始)。注意:这里的 β 和附录 C 里的对偶变量 β 是两回事,论文重用了同一个字母 | 「答案在第几个格子里」 |
| q = mk/n | 目标秩(目标负载) | 「我要找从小往大数第 q 个数」 |
| cj | 被选中箱之前所有箱的累积计数 | 「走到这个格子门口,已经数过多少个了」 |
| hj | 被选中箱内部的计数 | 「这个格子里挤了多少个」 |
| (q − cj)/hj | 还差多少个 ÷ 这箱里有多少个,得到一个 0 到 1 的比例 | 「目标大概在这个格子的百分之几处」 |
| clip(·, 0, 1) | 把结果强行限制在 [0, 1] 内 | 防呆:万一比例算出界(比如 hj 很小导致除法失控),不让它跑出这个箱 |
| w | 箱宽 (bmax − bmin + 2)/B | 一格代表多长 |
整个式子读出来就是:答案 = 区间左端 + (整格数 + 格内比例) × 格宽。和「看尺子读数」完全一样:先数过了几厘米,再估读厘米内的零头。得到的偏置最后像式 14 那样做均值中心化(减去所有偏置的平均值),这一步的作用是消除「全体偏置一起加同一个常数」这个自由度——因为 Top-k 只看相对大小,整体平移不影响任何选择。
三个性质
第一,准确。论文的论证是:累积计数在箱边界处是精确的——直方图丢的是箱内部的信息,箱之间的分界是准的,所以「第 q 个数落在哪个箱里」这个判断没有任何误差。因此真值分位数与其估计一定落在同一个箱里,两者的差被箱宽 w 卡死。B = 1000 时这至多是几个 10−3,论文说观察不到可测量的残余负载不均衡。
第二,便宜。唯一的通信是每层每步对 nB 个值做一次整数 all-reduce,与 m 无关。这一点是关键:token 数从一百万涨到一千万,通信量纹丝不动。论文说在他们的配置里,这低于「每个微批次都在进程组上交换原始间隔」(这是天然的替代方案)的 1%。
第三,估的是对的量。这一条最容易被忽略但最深刻:因为计数可加,全局直方图对「token 如何在 rank 和累积步之间分片」是精确不变的。也就是说,不管你把 token 怎么切、切给谁,最后 all-reduce 出来的那张全局直方图一模一样。因此它估的是池化全局批次(pooled global batch)的分位数,而不是各 rank 分位数的平均——论文特别加了一句「这两者一般不同」。
论文最后提到一个精化:对跨步估计的分位数做指数移动平均(exponential moving average, EMA)可以降低批次间的采样噪声,进一步改善负载均衡。
(a)假设当前偏置的极值是 bmin = −0.4、bmax = 0.6,B = 1000。算出箱宽 w,并说明分位数估计的误差至多是多少。(b)假设 n = 1000、B = 1000,算出一次 all-reduce 要传多少个整数;再和「传原始间隔」的方案比:m = 106 时后者要传多少个数?(c)用这两个数字解释论文说的「低于 1%」是保守还是激进的说法。
(b) 直方图:nB = 1000 × 1000 = 106 个整数,每层每步一次。原始间隔:mn = 106 × 103 = 109 个浮点数,而且论文说的替代方案是「每个微批次」都交换一次,所以还要再乘上微批次数。
(c) 比值:106 / 109 = 0.1%,还没算「每微批次 vs 每步」这个额外倍数,也没算「整数 vs 浮点」的位宽差别。所以论文说的「低于 1%」是相当保守的说法——按这个粗算,真实比例可能在 0.1% 以下甚至更低。
另外注意 (a) 和 (b) 之间的张力:想让误差更小就要加大 B,而通信量正比于 nB。B 是一个精度-通信的权衡旋钮,但因为通信量与 m 无关,这个旋钮的调节空间非常大——把 B 提到 10000 也只是 107 个整数,仍然远小于 109。
变式:如果训练中期发生剧烈的负载不均衡,偏置向两头扩散到 bmin = −5、bmax = 5,此时 w 变成多少?误差放大了几倍?这会不会形成「越不均衡估得越差、越估不准越不均衡」的恶性循环?(提示:想想此时「估准」还重不重要——偏置差 0.012 和差 0.003,对一个已经严重偏离的专家来说影响一样吗?)
论文说全局直方图估的是「池化全局批次的分位数,而不是各 rank 分位数的平均——这两者一般不同」。请构造一个具体的数字例子证明这两者确实不同:两个 rank,每个 rank 上有 4 个数,取中位数(0.5 分位数)。要求你的例子里「两个 rank 各自中位数的平均」和「8 个数放在一起的中位数」差得越明显越好。然后说明:为什么这个差异在真实训练里不是理论洁癖,而是会真的出问题。
例子一(简单版):rank A = {0, 0, 0, 0},中位数 0;rank B = {10, 20, 30, 40},中位数 25(取中间两数平均)。两个 rank 中位数的平均 = (0 + 25)/2 = 12.5。合并成 {0,0,0,0,10,20,30,40},中位数是第 4、5 个的平均 = (0 + 10)/2 = 5。12.5 vs 5,差了 2.5 倍。
例子二(对照组,说明差距来自哪里):rank A = {1, 2, 3, 4},中位数 2.5;rank B = {2.4, 2.5, 2.6, 2.7},中位数 2.55。平均 = 2.525。合并成 {1, 2, 2.4, 2.5, 2.6, 2.7, 3, 4}(8 个数),中位数 = (2.5 + 2.6)/2 = 2.55。这次两者只差 0.025。对比两个例子可以看出:差距完全取决于各 rank 数据分布的差异程度——分布相近时两者几乎一致,分布差得远时可以差出数倍。
为什么真实训练里会出问题:数据并行的各个 rank 拿到的是不同的数据分片。如果按数据源或语言分片(比如 rank 0 全是中文网页、rank 1 全是代码),那么各 rank 上「哪些专家被强烈需要」的分布可能差得很远——代码分片上专家 A 火爆,中文分片上专家 A 无人问津。此时各 rank 各自算分位数再平均,得到的偏置对谁都不对;而池化后的全局分位数才是「这一整批数据真正需要的偏置」。更糟的是,各 rank 分位数的平均没有任何优化意义——式 26 要的是全局那 m 个间隔的分位数,中位数的平均不等于平均的中位数,它不是任何一个优化问题的解。
直方图方案之所以能天然做对,是因为计数是可加的:把两个 rank 的计数逐格相加,得到的就是把两组数据倒在一起做的那张直方图,一个数都不多、一个数都不少。而「分位数」这个操作不可加。可加的量能被 all-reduce 正确合并,不可加的量不能——这是分布式计算里一条很值钱的通用原则。
变式:还有哪些常见的统计量是「可加」的、因而能被 all-reduce 安全合并?哪些不是?(试试:求和、计数、平均、最大值、方差、中位数、众数。对于平均和方差,想想需要额外传什么才能合并。)
答辩:如果我是审稿人
你在附录 C 里花了两页论证 QB 的优点之一是「不需要类似学习率的超参数」。可附录 D 一上来就引入了箱数 B = 1000,还引入了一个可选的 EMA 系数。这不就是把超参数从优化器搬到了估计器里吗?你凭什么说自己无超参?
参考防守(先自己组织语言再看)
先承认字面上的事实:B 确实是一个超参数,EMA 系数也是。说「完全无超参」是不准确的,准确的说法是「没有学习率类的超参数」,而论文原文用的正是 「no learning-rate-like hyperparameter」 这个措辞——这个限定词不是事后找补,它在原文里就有。
然后要说清这两类超参数的性质差别,这才是防守的实质。学习率类超参数的坏处是:(1)它决定能不能收敛,选错了会震荡或者根本走不到;(2)它没有「越大越好」或「越小越好」的单调关系,必须搜;(3)合适的值随规模、批大小、专家数漂移,换个配置就要重调。
B 完全不是这样:(1)它不影响是否收敛,只影响估计精度,而且误差有明确的上界 w = 区间长度/B;(2)它是单调的——B 越大越准,没有「调大了反而坏」的风险,唯一代价是线性增长的通信量;(3)由于通信量与 m 无关且基数很小,把 B 设到远超需要的值几乎不花钱。一个有明确误差上界、单调、且代价可忽略的旋钮,和一个必须靠试错找的旋钮,不是同一类东西。论文说 B = 1000 「已足够」,配套给了误差量级(几个 10−3)和观察结论(无可测量的残余不均衡),这是可核对的陈述。
最后交出真正该被追问的地方:论文没有给 B 的消融曲线(B = 100 / 1000 / 10000 下的负载不均衡度),也没有给 EMA 系数的取值和它带来的收益量化——「可以降低批次间采样噪声,进一步改善负载均衡」是一句纯定性的话。审稿人要求补这两组数据是完全合理的。
14.8 附录 E:为什么每个 rank 预留 E/R 个冗余专家就够了
换个话题,从数学换到系统。MoonEP 要解决的问题是:E 个专家分散在 R 个 EP rank(expert-parallel rank),可以粗略理解为「一台负责一部分专家的 GPU」上,每个 rank 本地有 E/R 个专家。一个 token 要用的专家如果不在本地,就得跨机器通信——很慢。缓解办法是在 rank 上放一些冗余专家(redundant expert):把别的 rank 上的热门专家复制一份到本地,这样本地就能算,省掉通信。
但冗余专家要占显存。系统设计者必须提前知道:每个 rank 最多要预留多少个冗余专家的槽位?预留少了会不够用,预留多了纯浪费。附录 E 就是回答这个问题的。
先把问题写清楚
记 mr(P) 为在方案 P 下放在 rank r 上的冗余专家数。对一个给定的路由器输出 I(也就是「这一批 token 各自要哪些专家」这张表),规划目标是最小化任一 rank 上的最大冗余专家数:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| I | 路由器输出:这一批 token 分别要用哪些专家 | 「今天的订单」 |
| P | 一个方案:决定每个 token 在哪个 rank 上被计算、以及每个 rank 放哪些冗余专家 | 「排产计划」 |
| mr(P) | 方案 P 下 rank r 上的冗余专家个数 | 「这台机器额外装了几套模具」 |
| maxr | 取所有 rank 里最大的那个 | 木桶效应:显存要按最坏的那台预留 |
| minP | 在所有可行方案里挑最好的 | 「最优排产下的最坏机器」 |
| P* | 证明里构造出来的一个具体方案(不必是最优的) | 一个够用的例子 |
| E, R | 专家总数、EP rank 数;每 rank 本地有 E/R 个专家 |
为什么这个界值得证
注意 M(I) 的定义里有一个 minP——它是在所有可能的方案上取最小。你不可能把所有方案都枚举一遍(方案数是天文数字)。而定理 1 说:不管路由器输出多离谱,都存在一个方案让最大冗余专家数不超过 E/R。这是一个对所有 I 一致成立的保证,所以系统可以照着它静态分配显存,永远不会不够。证明的手段是构造:只要造出一个够好的方案 P*,min 就一定不比它差。式 28 中间那个 ≤ 就是这个逻辑。
定理 1 的证明:一个反复「填坑」的构造
这是本章最适合零基础读者跟下来的证明,我们把它拆细。核心是一个引理:
关键引理:存在一个方案 P*,使得每个 EP rank 收到恰好相同数量的 token(S × K),并且每个 rank 的远程 token 只来自另外一个 EP rank。
论文在这里没有重新定义 S 和 K
附录 E 直接用了 S × K 这个记号而没有说明含义。从上下文(「每个 EP rank 收到相同数量的 token」、「全部 S×K×R 个 token」)推断,S 应是每个 rank 上的 token 数、K 是每个 token 选中的专家数,于是 S×K 是每 rank 的目标 token-专家对数量,乘以 R 得到全局总量。这是推断,不是论文的明文定义,读的时候要留意。
自己推一遍:填坑构造,以及「为什么每个 rank 至多被填一次」
初始状态:每个 rank 只处理自己本地的 token(一个 token 落在哪个 rank,取决于它是在哪台机器上被读进来的)。此时各 rank 的负载会一样吗?把 rank 分成两类,你会怎么分?
想好了再看
当然不一样——有的 rank 上的 token 恰好用了很多本地专家,有的没有。按论文的分法:负载超过均衡值 S×K 的叫超载(overloaded),低于的叫欠载(underloaded)。注意一个基本事实:总量是守恒的,所以有超载就必有欠载,不可能全都超载。这个事实后面要用。
现在做一次操作:挑一个欠载 rank 和一个超载 rank,把 token 从超载的迁到欠载的。迁多少?论文的规定是关键。
想好了再看
恰好填到均衡值 S×K 为止,一个不多一个不少。这是整个构造的灵魂:不是「迁一半」,不是「迁到超载的那个刚好平衡」,而是永远以「把欠载的那个填满」为准。当初为什么会想到这一步?因为我们最终要证的是「每个 rank 的远程 token 只来自一个源头」,那就必须保证每个 rank 只被「经手」一次——一次就把它彻底解决掉,它就再也不用被碰了。
迁完之后,那个超载的 rank 会变成什么状态?有几种可能?
想好了再看
三种:(i)仍然超载(它原本超得很多,填完这个坑还有富余);(ii)恰好平衡(原本超出的量正好等于那个坑的大小);(iii)变成欠载(它超出的量不够填满那个坑,被掏空过头了)。
等一下——情况 (iii) 可能吗?如果超载 rank 只超出 3 个 token,而欠载 rank 缺 10 个,迁 10 个过去会让超载 rank 掉到均衡值以下。是的,会发生。论文说「据此放回相应集合」,就是把它重新归类到超载集或欠载集里,继续参与后面的操作。为什么这个过程一定会终止?论文说至多经过 R − 1 次填充。
想好了再看
抓住这句话:每次填充让一个欠载 rank 变成均衡,且此后不再变动。为什么「此后不再变动」?因为后续的操作只会挑欠载的 rank 来填、挑超载的 rank 来抽,而这个 rank 现在恰好平衡,两个集合都不属于它,所以再也不会被选中。
于是每次填充都永久地减少一个「未解决的 rank」。总共 R 个 rank,最后一个不用单独处理(前面都平衡了,总量守恒逼得它也必然平衡),所以至多 R − 1 次。这是一个「单调递减的计数器」型的终止性论证,是这类构造性证明的标准武器。现在是最关键的一步:为什么每个 rank 至多被填充一次?(提示:把「被填充」和「变成均衡」这两件事对应起来。)
想好了再看
因为「被填充」这个动作的定义就是「被填到恰好均衡」。一个 rank 只有在欠载时才会被选为填充目标;而一旦被填充,它立刻变成均衡;而均衡的 rank 永远不会再被选为填充目标(上一步已论证)。所以对任何一个 rank,「被填充」这件事最多发生一次。
注意这里的不对称:一个 rank 可以被抽走很多次(超载的 rank 可以连续给好几个坑供货,每次抽完还超载),但只能被填入一次。而我们关心的恰恰是「填入」这一侧——因为冗余专家是为了处理收到的远程 token。收尾:既然 rank r 的所有远程 token 都来自单一的 rank s,为什么 mr(P*) ≤ E/R?
想好了再看
这些远程 token 全部来自 rank s,所以它们要用的专家全都是 s 的本地专家。而 rank s 本地只有 E/R 个专家。所以 rank r 需要复制过来的专家至多 E/R 个(最坏情况是这些 token 把 s 上的每个专家都用到了)。因此 mr(P*) ≤ E/R,对每个 r 都成立,所以 maxr mr(P*) ≤ E/R;再由 minP ≤ 任一具体方案,得到式 28。
回头看整条链:整个证明的全部力量都来自「单一来源」这个性质,而「单一来源」又完全来自「每次恰好填满」这个看似随意的规定。这就是构造性证明的美感——一个不起眼的设计选择撑起了整个结论。
设专家总数 E = 896,EP 大小取 R = 16。(注意:论文没有披露 K3 实际使用的 EP 大小 R,这里的 16 是本题自己设定的值;E = 896 来自论文的架构描述,本章这几页只提到「接近 103 个专家」。)(a)每个 rank 本地有多少个专家?(b)按定理 1,每个 rank 最多需要预留多少个冗余专家槽位?(c)按定理 2 的紧界 ⌈E(R−1)/R2⌉,这个数是多少?(d)如果按定理 1 的界预留,每个 rank 的专家显存最多会变成原来的几倍?
(d) 每 rank 最多装 56 + 56 = 112 个专家,是只装本地专家(56 个)的 2 倍。这是一个非常重要的工程结论:定理 1 的界在最坏情况下意味着专家显存翻倍。但注意这是上界,是「永远不会超过」的保证;实际路由输出通常远没有这么极端,真实占用会低得多。
(c) 和 (b) 的对比也值得体会:53 和 56 只差 3(差距 5.4%),这正是定理 2 在说的事——存在一种路由器输出,逼得你必须准备 53 个冗余槽位,所以你不可能把 56 这个界压到比 53 更低。换句话说,翻倍显存这个代价基本上是躲不掉的,不是分析不够精细。
(顺带练一下 R 变大时的趋势:R = 32 时 E/R = 28,而 ⌈896 × 31 / 1024⌉ = ⌈27.125⌉ = 28——两者已经完全相等。这就是论文说 「R 大时 E(R−1)/R2 ≈ E/R」 的意思。)
变式:如果你是系统工程师,想把冗余显存开销压到本地专家的 25% 以内,在 E = 896 固定的前提下,R 该怎么取?这个取法会带来什么新问题?(提示:E/R 是绝对个数,而本地专家数也是 E/R——所以最坏情况下这个比例恒等于 100%,与 R 无关。要压低它必须放弃「最坏情况保证」,改用统计意义上的预留。这说明定理 1 的界虽然紧,但对实际系统的指导是「要么接受翻倍,要么接受偶尔不够用」。)
定理 2:这个界基本上没法再改进了
定理 1 给了一个上界。但上界这种东西是可以很松的——「人的身高不超过 100 米」也是一个正确的上界,只是毫无用处。所以必须回答:E/R 是不是一个松垮的界?能不能证出一个更小的?定理 2 说:不能。
证法是构造一个最坏情况。论文造了这样一个路由器输出 I*:EP rank 0 上的专家收不到任何 token,而其他 R−1 个 rank 上的所有专家均分全部 token。
推理链是这样的:
- 全部 S×K×R 个 token 被均分给 E(R−1)/R 个专家(因为只有 R−1 个 rank 的专家在收货,每个 rank E/R 个)。所以每个专家收到 SKR ÷ [E(R−1)/R] = SKR2/[E(R−1)] 个 token。
- 在任何方案 P 下,rank 0 都必须收到 S×K 个 token(负载要均衡),而且全是远程的——因为它自己的专家一个 token 都没收到。
- 这 SK 个 token 至少涉及多少个不同的专家?每个专家最多能贡献 SKR2/[E(R−1)] 个 token,所以至少需要 SK ÷ SKR2/[E(R−1)] = E(R−1)/R2 个不同的专家。
- 专家个数必须是整数,取上取整:rank 0 至少需要 ⌈E(R−1)/R2⌉ 个冗余专家。所以 M(I*) ≥ ⌈E(R−1)/R2⌉。
- 反过来,用定理 1 证明里的填充过程构造方案、并优先按专家迁移 token(即尽量把同一个专家的 token 整批一起迁走,而不是零散地迁),可以把每个 rank 上的冗余专家数控制在这个值以内。所以取等号。
因为 R 大时 E(R−1)/R2 = (E/R)·(1 − 1/R) ≈ E/R,论文的结论是:不存在显著小于 E/R 的一般上界。
一个容易混淆的地方
定理 2 说的不是「实际训练中每个 rank 都需要 E(R−1)/R2 个冗余专家」。它说的是「存在一种路由器输出会逼出这么多」。这两句话差别很大:前者是关于典型情况的,后者是关于最坏情况的。真实训练里路由是被 QB 均衡过的(这正是 14.2–14.7 讲的事),远比这个人造的最坏情况温和。定理 2 的真正用途是划定「分析的极限」:它告诉你不要再去尝试证明一个更紧的一般上界了,那是徒劳的;要想要更小的预留,只能引入关于路由分布的额外假设。
定理 2 的构造里,有人质疑:「让 rank 0 的专家一个 token 都收不到,这也太人为了。我改一改:让 rank 0 的专家收到很少但不为零的 token,比如别的专家的 1%。这样 rank 0 就不用全靠远程了,界是不是就能压下来?」请分析这个修改,并说明它为什么基本上救不了。然后回答一个更根本的问题:这个构造真正利用的是什么结构性事实?
算一算。近似地设全局 SKR 个 token 均分给「正常」专家,每个正常专家约 T = SKR/[E(R−1)/R] = SKR2/[E(R−1)] 个。rank 0 有 E/R 个专家,每个收 0.01T,本地总供应 ≈ (E/R) × 0.01T = 0.01 × SKR/(R−1) ≈ 0.01 SK(R 大时 R/(R−1) ≈ 1)。
也就是说本地只能供应 约 1%,还剩 99% 的 S×K 要靠远程。按同样的计数,需要的不同远程专家数至少是 0.99 × E(R−1)/R2。界只降了 1%,几乎原封不动。
为什么救不了。因为这个构造的杠杆不是「rank 0 是零」这个极端值,而是「rank 0 的本地专家吸收能力远小于它必须承担的负载」这个失配。你把 0 改成 1%,失配从 100% 变成 99%,量变而已。要真正把界压下来,必须让 rank 0 的本地专家吸收掉相当大比例的 S×K——但那样一来路由本身就已经接近均衡了,也就不再是最坏情况。
真正利用的结构性事实有两条,缺一不可:
(1) 负载必须均衡——这是 MoonEP 的硬要求,逼得 rank 0 无论如何都要吃下 S×K 个 token,不能说「我这儿没活干就闲着」。
(2) 单个专家的供货量有上限——因为 token 被均分了,没有哪个专家能一个人喂饱 rank 0。这是一个鸽笼原理式的计数:要装 SK 个 token,每个「笼子」(专家)最多装 T 个,那就至少需要 SK/T 个笼子。
把这两条放在一起就得到了下界。注意 (2) 才是「均分」这个设定的用途——如果反过来让少数几个专家收走绝大部分 token,rank 0 只要复制那几个热门专家就够了,冗余数反而很小。所以「均分」不是为了「公平」,恰恰是为了制造最坏情况:把需求摊薄到最多的专家上,逼你复制最多的专家。
变式:反过来构造一个最好情况——什么样的路由器输出会让 M(I) = 0(一个冗余专家都不需要)?再想:在这个最好情况和定理 2 的最坏情况之间,QB 均衡后的真实路由更接近哪一头?为什么?(提示:QB 让专家负载均衡,但这和「token 与专家的位置对齐」是两件不同的事——论文没有讨论这一层。)
14.9 附录 F:XTML——把聊天格式设计成一门可扩展的标记语言
最后一段附录看起来最「软」,但它其实是全篇里最能体现工程判断力的一节。
聊天模板(chat template):模型只认识一长串 token,不认识「这是系统提示、这是用户说的、这是工具返回的」。聊天模板就是把结构化的对话(谁说了什么、调用了什么工具)拍平成一串 token 的那套规则,以及反过来把模型输出解析回结构的规则。它是模型和外部世界之间的接口协议。
三个设计目标
论文说 K3 的聊天模板围绕三个目标重新设计:
可扩展性(extensibility):新能力应该通过向后兼容的消息格式引入,而不是靠改模板,这样单一模板可以服务整个模型代际。为什么这很重要?因为模板一改,所有已经训好的行为、所有下游的解析代码、所有缓存的上下文都可能失效。把「加功能」和「改协议」解耦开,才能不断加东西而不推倒重来。
低对齐税(low alignment tax):格式应该能用极少的监督数据学会。论文说这支持一条特定的流水线:「轻度微调的预训练模型直接进入强化学习」。翻译一下——如果格式很怪、很难学,你就得先用大量人工标注数据把模型教会说这套话,这个阶段既费钱又会损伤预训练学到的能力(这就是「对齐税」);如果格式本身就接近预训练数据里的自然语言,模型几乎不用教就会,你可以省掉大半个监督微调阶段直接开始 RL。
解码友好(decoding friendliness):结构应该支持简单的编码器、流式解析器(一边生成一边解析,不用等整段生成完)和语法约束执行器(强制模型只能生成符合语法的 token)。
XTML:把尖括号换成保留 token
XTML(eXtensible Token Markup Language,可扩展 Token 标记语言):一种类 XML 的标记语言,但把 XML 的尖括号语法替换成三个保留特殊 token:[open]、[sep]、[close],外加一个 [end_of_msg] 作为生成停止标记。
一个元素写成 [open]tag attr="value"[sep] … [close]tag[sep],它与 XML 里的 <tag attr="value"> … </tag> 是同构的(结构完全对应),但每一个结构边界都是一个显式的特殊 token。论文说这消除了元素边界处的分词歧义并简化了受约束解码。
「分词歧义」到底是什么问题
模型读到的不是字符,是 token——一种介于字母和单词之间的碎片。分词器(tokenizer)按一套统计规则把字符串切成碎片,而切法依赖于上下文。同一个 </think>,前面跟着句号时可能被切成 </ + think + >,前面跟着换行时可能被切成 </th + ink>,紧跟中文时又可能是别的切法。于是「同一个结构边界」在模型眼里变成了好几种不同的 token 序列,模型必须把每一种都学一遍,而且哪一种都不完全可靠。
更糟的是混淆:如果用户消息里本来就含有 </think> 这几个字符(比如用户在问一个关于聊天模板的问题),解析器就分不清这是结构还是内容——这是提示注入攻击的一条经典路径。
用保留特殊 token 一次解决两个问题:[close] 永远是同一个 token id,不管前后是什么;而且它不可能由普通文本编码出来(分词器的词表里,特殊 token 是被单独保留的,任何字符串都不会被切成它)。所以结构和内容之间有了一堵不可逾越的墙。
对受约束解码的好处也很直接:想强制模型「现在必须闭合这个元素」,只需要在采样时把除了 [close] 之外所有 token 的概率压成 0——一个 token id 的操作,而不是去匹配一个多 token 的字符串模式。
消息与区域:一个 KV 缓存友好的排布
上下文的顶层单位是消息(message),消息按来源分两类:
输入消息(input messages)序列化请求的 messages 字段,涵盖熟悉的 system、user、assistant、tool 四种角色。
选项消息(option messages)把请求选项翻译成模型在上下文里读到的指令。它们的位置反映作用域——这是全节最值得学的一个设计点:
- 全局选项(工具声明
type="tool-declare"和推理努力设置)出现在所有输入消息之前。论文给了两条理由:它们管辖整个会话、很少改变;而且改动它们本来就会让 KV 缓存失效。 - 一次性选项(
tool_choice、response_format)追加在输入消息之后,这样逐请求的改动不会破坏历史的 KV 缓存。 - 输入选项消息(input option message)是第三种,它与输入消息交错,用于在会话中途补充或覆盖某个全局选项。
KV 缓存(KV cache):Transformer 生成时,每个已经处理过的 token 都会留下一份中间结果(键和值)。下一个 token 只需要读这些缓存,不用重算前面的一切。但缓存的有效性依赖于「前缀完全没变」——因为每个位置的中间结果都依赖它之前的所有 token。一旦你改动了第 t 个 token,从第 t 个往后的所有缓存全部作废,必须重算。
输入选项消息带来的能力是动态加载的工具(dynamically loaded tools):会话中途检索或加载的工具,通过一条额外的 tool-declare 消息宣告,之后模型可用的工具集就扩大了,而不需要重建前面的上下文。想想如果没有这个机制会怎样——工具声明都在最前面,要加一个新工具就得改最前面那段,整段上下文的 KV 缓存全部作废,一次几十万 token 的会话要从头重算。
一个客服系统有三种设置需要传给模型:(i)公司的工具清单,一年改两次;(ii)「本次回答必须是 JSON 格式」,每个请求都可能不同;(iii)会话进行到一半时,客服系统根据用户问题检索出了三个新工具,需要临时加入。请按论文的方案说明这三种设置各自应该放在上下文的哪个位置,并分别算出「改动它」会作废多少 KV 缓存(用「占整段上下文的比例」粗略描述)。
(i) 全局选项,放最前面。它是 tool-declare 类型,管辖整个会话。改动它会作废几乎全部缓存——但论文的辩护是:这类改动本来就极少(一年两次),而且「改动它们本来就会让 KV 缓存失效」是不可避免的(工具清单变了,模型对整段历史的理解基础都变了,即使技术上能保住缓存,语义上也不该保)。低频 + 不可避免 = 放最前面的正当理由。
(ii) 一次性选项,放输入消息之后。它是 response_format,逐请求变化。放在最后面,改动它只作废最末尾那一小段(几十个 token),前面几十万 token 的历史缓存完好。这是最关键的一个安排:如果把它放在前面,那么每次请求换一下输出格式就要重算全部上下文,多轮长会话会被拖垮。
(iii) 输入选项消息,交错插在当前位置。它是一条中途补充的 tool-declare。作废的是从插入点往后的部分——而插入点就是「现在」,后面本来就还没生成,所以实际作废量几乎为零。这就是「不需要重建前面的上下文」的含义:新工具从这一刻起生效,之前的历史一个 token 都不用动。
体会一下三者的共同逻辑:每一类的位置都被安排在「它的变动频率所能承受的最靠前的地方」。频率越高,位置越靠后。
变式:假设现在要新增一个选项,叫「温度设置」,它每次请求都可能变、而且只影响采样不影响语义。按这套原则它该放哪?再想一个更刁钻的:如果某个选项既管辖全局又频繁变化(比如「当前时间」),这套排布还有救吗?(提示:想想为什么大多数系统宁可把时间写死在 system 提示里也不肯每轮更新它。)
通道:两种生成模式靠前缀切换
assistant 消息的主体被组织成通道(channels),论文说这个概念受 OpenAI 的 Harmony 响应格式启发。三个通道各司其职:think 承载推理痕迹,response 承载用户可见的回答,tools 承载工具调用。
两种生成模式纯粹通过生成前缀选择——[open]think[sep] 进入思考模式,[open]response[sep] 进入指令模式——而不是通过不同的模板。这一点直接呼应「可扩展性」目标:模式切换不需要碰模板。
K3 只支持保留式思考(preserved thinking):思考模式下 think 通道永远保留在历史里,即使内容为空也保留。论文给的理由是「这样模型跨轮观察到一致的消息结构」。指令模式下,历史消息只包含 response 和 tools 通道。
「即使为空也保留」为什么重要
如果有时保留、有时删掉,模型在历史里看到的消息结构就是两种形状。它得先判断「这一轮到底有没有 think 通道」,再决定怎么理解后面的内容。恒定保留消除了这个判断,模型可以把「assistant 消息 = think + response + tools」当成一条铁律。结构上的一致性比省那几个 token 值钱得多。论文没有讨论这样做的 token 开销,也没有讨论为什么不支持「非保留式思考」(很多系统会在下一轮丢掉旧的思考痕迹以省上下文)——这是一个作者做了选择但没有论证的地方。
工具调用:有类型的参数,以及代码作为一等公民
tools 通道里每次调用带 tool 和 index 两个属性。index 是消息内的并行调用编号;每条工具结果消息重复同样的 tool/index 对,并按其调用的顺序排列,因此结果能明确地与调用配对——不会出现「三个工具同时调用、返回时分不清谁是谁」的问题。
参数是有类型的:字符串参数以原始文本出现,其他 JSON 类型的值紧凑序列化。按 Figure 16c 的样子,一次调用长这样([open]argument key="code" type="string"[sep] 之后直接跟原始代码,然后 [close]argument[sep])。因此像代码这样的自由格式文本成了一等公民,而不是被转义的 JSON 字符串。
论文还提到一个纯 JSON 的回退块,覆盖那些参数无法被分解成有类型参数块的输入;它只出现在输入 token 中、从不出现在模型输出里,并且训练时其损失被掩掉(也就是模型不会因为这段而被训练去生成它)。
推理努力:用自然语言说,而不是造新语法
推理努力被暴露为一条 thinking-effort 类型的全局选项消息,插在工具声明之后、输入消息之前。它不修改生成前缀、也不暴露 token 预算,而是用自然语言陈述所请求的等级,充当一条生成约束指令。schema 预留了四个等级(low、medium、high、max),K3 支持其中一个子集(论文没有说明具体支持哪几个)。
论文说这种表示把努力接口与模板语法解耦,并且与努力条件化训练直接对齐。更广泛地说,这是所有选项消息的共同实现方式:tool_choice、response_format、thinking-effort 各自被翻译成放进上下文的一条简短的自然语言指令,而不是专门的特殊语法。
综合题。假设模型要调用一个 python 工具,参数是这样一段代码:if x > 0 and s == "a\nb":\n print("ok")
(a)如果按传统做法把它包成 JSON 字符串 {"code": "..."},模型在生成时必须额外做什么?请具体指出至少三处需要转义的地方。(b)说明这件事为什么会同时伤害三个设计目标中的至少两个。(c)K3 的有类型参数块怎么绕开了它?(d)最后把这个例子和「低对齐税」联系起来:为什么「不用转义」能减少所需的监督数据量?
(a) 至少三处:① 代码里的
"a\nb" 的两个双引号必须写成 \";② 代码里本来就有的反斜杠 \n(这是 python 源码里的两个字符)必须写成 \\n,否则会被 JSON 解析成真正的换行;③ 代码的真实换行(每行结束)必须写成 \n;④ print("ok") 的引号同样要转义。注意 ② 和 ③ 的混淆是最恶心的——同样的两个字符 \n,一个要变成四个字符,一个是由一个换行变来的,模型必须分清「这个反斜杠是源码的还是我加的」。(b) 伤害两个目标:解码友好被伤得最重——流式解析器必须一边收 token 一边做反转义状态机,而且在「收到一个孤立的反斜杠」时无法判断下一个字符是什么,解析状态是不确定的;受约束解码也更难,因为合法的下一个 token 集合依赖于转义状态。低对齐税同样受损:预训练语料里的代码绝大多数是原样的,而不是被 JSON 转义过的,所以「写转义代码」是一项模型没有在预训练中大量见过的技能,必须靠监督数据专门教,教得不够就会在长代码里出错——而且这类错误是全有全无的:漏一个转义,整段 JSON 解析失败,整次工具调用作废,不像自然语言那样「错一点还能用」。
(c) K3 把参数写成
[open]argument key="code" type="string"[sep] + 原始代码 + [close]argument[sep]。因为边界是保留特殊 token,代码里不可能出现它们(前面讲过:特殊 token 不能由普通文本编码出来),所以根本不需要转义——代码怎么写就怎么放进去。这正是论文说的「自由格式文本成了一等公民」。(d) 联系低对齐税:模型要学的东西从「写代码 + 同时做一套字符级转换」减少到「写代码,然后输出一个结束标记」。后者几乎完全落在预训练已有的能力范围内(写代码它本来就会,输出一个特定 token 是一学就会的),所以只需要极少的监督数据就能学会格式。这就直接支撑了论文说的那条流水线:轻度微调的预训练模型直接进入 RL——不需要一个庞大的监督微调阶段来把格式砸进模型,也就避免了那个阶段对预训练能力的损伤。
把三层串起来看:一个看似只是「少写几个反斜杠」的格式选择,向下影响解析器的复杂度和错误率,向上影响训练流水线能不能省掉一个阶段。这就是为什么论文把聊天模板当成一个值得写进技术报告的设计问题,而不是一个实现细节。
变式:论文说纯 JSON 回退块「只出现在输入 token 中、从不出现在模型输出里,并且训练时其损失被掩掉」。请论证这个设计决定的必要性:如果不掩掉损失会发生什么?(提示:模型会学到什么?这和 (d) 里说的「低对齐税」是不是自相矛盾了?)再想:既然如此,为什么还要保留这个回退块?
本章小结
五段附录,一条共同的线索:正文里的每一句「我们这样做效果更好」,背后要么是一个可以推的式子,要么是一个可以证的界,要么是一个可以说清代价的权衡。
附录 B 回答「怎么给乘法封顶而不毁掉激活函数的形状」。答案是把 Swish 的线性因子换成 β tanh(·/β)、sigmoid 因子保留,两个分支同样处理。式 18 保证原点附近一阶匹配 SwiGLU、β → ∞ 时逐点还原;式 19 给出与输入无关的硬上界 β₁β₂ = 100(β₁ = 4、β₂ = 25)。相对硬截断的优势是远离饱和边界处保留非零梯度——硬截断在饱和区导数恒为 0,神经元会永久死掉。
附录 C 把分位数均衡从一个技巧还原成一道题的精确解。起点是式 20 的最大分数均衡指派;松弛到 [0,1] 因二部 b-匹配多面体的全单模性而精确;引入乘子写成 max–min(式 21),交换次序(式 22),内层可分地取到 max(0, ·)(式 23)。对偶目标的坐标极小化在两个方向上都是分段线性的,斜率是「目标数减去超过阈值的个数」,因此极小点卡在第 k 大与第 k+1 大之间,取第 k+1 大恰好等于 (1−k/n) 分位数(式 25、式 26)——token 轴和专家轴取的是同一个分位数,方法因此得名。最关键的结论是那个不对称:路由只需要专家阈值 β(即偏置 b = −β),token 阈值 α 是与训练批次绑定的中间变量,可以丢掉;所以部署时路由就是「减去冻结偏置再取 Top-k」,不需要任何分位数计算。最后,式 27 显示专家侧次梯度就是「目标负载减观测负载」,对它做一步 SignSGD 恰好还原免辅助损失均衡的定步长符号更新——符号更新只用了误差的方向,QB 直接跳到同一个目标的精确极小点,这解释了 QB 为什么无学习率超参、为什么面对近 10³ 个专家也能几步平衡。BIP 用不等式约束,诱导出的非负性给两个更新加了 max(0,·) 截断,只能压不能提,明显减慢平衡。
附录 D 解决「分位数要在百万级、跨 rank、跨累积步的 token 上取」这个工程难题。关键观察是更新只需要分布而不需要数据本身,于是对所需偏置 r = α − s 做逐专家直方图;取负让顺序反转,QB 目标恰好是 r 的 (k/n) 分位数。分箱区间 [bmin − 1, bmax + 1] 由 sigmoid 分数落在 (0,1) 和「α 本身是某个有偏分数」这两条推出,每步重算所以自适应。B = 1000 个箱,误差被箱宽卡在几个 10⁻³。三个性质:准(累积计数在箱边界处精确)、便宜(每层每步一次 nB 个整数的 all-reduce,与 m 无关,低于替代方案的 1%)、估的是对的量(计数可加 ⇒ 全局直方图对分片方式精确不变 ⇒ 估的是池化全局批次的分位数,而不是各 rank 分位数的平均,这两者一般不同)。
附录 E 给 MoonEP 的冗余专家预留量定了个界。定理 1:M(I) ≤ E/R 恒成立,证法是构造一个反复「恰好填满欠载 rank」的方案——每次填充让一个 rank 永久变均衡,所以至多 R−1 次;每个 rank 至多被填一次,所以它的远程 token 只有单一来源,而那个来源本地只有 E/R 个专家。定理 2:构造「rank 0 收不到任何 token、其余专家均分」的路由输出,靠鸽笼式计数得到 ≥ ⌈E(R−1)/R²⌉,R 大时约等于 E/R,所以不存在显著更小的一般上界。工程含义是最坏情况下专家显存翻倍,且这个代价躲不掉。
附录 F 讲 XTML 聊天模板。三个目标:可扩展(靠向后兼容的消息格式而非改模板,单一模板服务整个模型代际)、低对齐税(格式易学,支持轻度微调后直接进 RL)、解码友好。手段是用 [open]/[sep]/[close] 三个保留特殊 token 替代尖括号,消除分词歧义并简化受约束解码。排布原则是「不变的在前、易变的在后」:全局选项 → 输入消息 → 一次性选项,中途可插入输入选项消息以支持动态加载工具而不重建上下文。assistant 消息分 think / response / tools 三个通道,两种模式纯靠生成前缀切换;K3 只支持保留式思考。工具调用用 tool/index 配对,参数有类型,代码作为原始文本而非转义 JSON 字符串。推理努力做成一条自然语言的全局选项消息(四个等级 low/medium/high/max,K3 支持其中子集),这也是所有选项消息的共同实现方式。
需要记住的空缺。附录 B 没有给平滑封顶 vs 硬截断的任何对比曲线,也没有解释 β₁ = 4、β₂ = 25 这组数字怎么选的。附录 C 的「平局测度为零」在低精度浮点下并非无害,论文一笔带过;也没有给 QB 解与真实最优指派的差距度量。附录 D 没有给箱数 B 的消融,EMA 精化只有定性描述,而且它把附录 C 里的对偶变量 β 和这里的「选中箱编号」 β 用了同一个字母。附录 E 没有定义 S 和 K(只能从上下文推断),也没有披露 K3 实际用的 EP 大小 R。附录 F 没有说 K3 具体支持四个努力等级中的哪几个,没有讨论保留式思考的 token 开销,也没有论证为什么不支持非保留式思考。
第15章 从论文到代码:把知识变成可迁移的能力
前面十四章解决的是「看懂」。但看懂一篇论文和能做出点什么之间,还隔着一层:你得知道这些东西在真实世界里长什么样、放在哪里、怎么读。这一章做两件事——把论文点名的每一个开源项目做成索引,以及教你一套读陌生仓库的通用方法。最后给五个从一小时到几周的练手任务。
学完这一章你应该能做到
- 说出论文里每一个被点名的开源项目分别对应论文的哪一部分,并知道去哪里找它
- 解释「懂公式」和「能实现」之间具体差在哪些东西上,至少举出四项
- 拿到一个完全陌生的仓库时,说得出接下来六步做什么、先打开哪个文件
- 独立完成本章练手清单的前两项,并让两种实现的数值对上
- 算出跑一个 2.78 万亿参数模型到底需要多少显存,并据此判断自己该走哪条路
本章所有链接的可靠性声明,请先读这一段
这一章里出现的每一个网址,都是从论文(2026 年 7 月版本)的正文脚注或参考文献里逐字抄下来的,没有一个是本站补的、猜的或者凭记忆写的。但是——本站没有访问过其中任何一个地址,无法核实它们现在是否还能打开。开源仓库改名、迁移、转为私有、被删除都是常事,论文写下地址和你现在点击之间已经过去了一段时间。如果链接打不开,正确的反应是拿项目名去搜索引擎找,而不是认为本站写错了。
更重要的一条:论文只给了仓库地址,没有给任何文件名、函数名、行号。所以本章凡是涉及「代码里的某个具体位置」,一律写成「论文未指明具体位置,需自行在仓库中定位」,并告诉你该搜什么关键词。任何一个具体的路径或函数名如果出现在这里,那都会是编造——所以你不会在这一章看到它们。
15.1 一句话,和它背后的几百行
论文 §5.1.1 里有这么一句:他们开发了 FlashKDA,一个基于 CUTLASS 的分块内核,把块内计算和跨块状态传播重叠起来;这个内核把工作拆成「token 并行的若干阶段」和「一个头并行的递推」,两部分各自独立调度、独立调优;它同时服务训练和推理预填充,并且作为 flash-linear-attention 的一个后端被自动分发。
四行字。你在第2章已经把式 1 到式 6 全推过一遍了,公式本身你是懂的。但请诚实地问自己:现在让你写出这个内核,你写得出来吗?
写不出来。不是因为你不懂公式,而是因为公式里根本没有下面这些东西:
第一,分块边界。式 4 把一个 chunk 内的输出拆成「跨块项」和「块内项」,数学上干净利落。但序列长度不是 chunk 大小的整数倍怎么办?最后那个残缺的块怎么处理?一个 batch 里不同样本长度不同,是补齐(padding)还是变长打包(varlen)?这三个问题在论文里一个字都没有,在代码里是三段分支。
第二,数值精度。论文倒是讲了一半——§2.1.1 明说式 4 里的 1/Γ1→C 这个重缩放会「无界增长、在有限精度下溢出」,所以 Kimi Linear 要在对数空间算相对衰减、还要把 chunk 再切成 16-token 的小块;K3 则换成有下界的 sigmoid 参数化(式 5),让累积对数衰减落在 (−80, 0)、倒数小于 e80、留在 BF16 动态范围内。这已经是论文里少见的、把工程细节写进正文的段落了。但即便如此,哪一步该用 FP32 累加、哪一步可以留在 BF16、指数在算之前要不要先减最大值——依然只有代码知道。
第三,内核启动配置。一个 GPU 内核要决定:多少个线程块、每块多少线程、共享内存分多大、循环展开几层、用不用异步拷贝。这些数字不影响正确性,只影响速度——但影响的幅度可以是好几倍。论文说 FlashKDA 的两部分「各自独立调度与调优」,这句话背后是一张调参表。
第四,分发策略。论文自己就点了三种执行状态:训练/预填充用分块内核(§5.1.1)、超长序列用 SM 级的上下文并行(§5.1.1)、解码用另一套完全不同的融合内核(§5.4.2)。也就是说,同一个 KDA,代码里至少是三份实现,外加一层「什么时候调用哪一份」的分发逻辑。
那还有必要读论文吗
有,而且必须先读。代码告诉你「是什么」,只有论文告诉你「为什么不是别的」。§2.1.1 那段关于对角块的讨论就是最好的例子:Kimi Linear 要为每个对角小块做显式的位置对计算,K3 因为衰减有下界,所有因果小块都能直接走稠密的 Tensor Core 矩阵乘。你在代码里只会看到后者——一段平平无奇的矩阵乘。前者那条被放弃的路、以及放弃的理由,只在论文里。先读论文建立「为什么」,再读代码补上「怎么做」,反过来的效率低得多。
下面五件事,哪些是论文式 1–式 6 里明确写了的,哪些是必须去代码里才能知道的?
① 状态 St 的形状是 dk×dv
② 状态张量在显存里是行优先还是列优先存放
③ 衰减 αt 的取值范围是 (egmin, 1)
④ chunk 大小 C 取多少
⑤ Tril 操作把严格上三角置零、保留对角
chunk_size 或 BT 这类命名——但具体在哪个文件,论文未指明,本站也未核实。变式:论文 §5.4.1 说 KDA 的物理缓存块被迫做到 1024–6144 token。这个数是「数学对象」还是「实现选择」?如果是实现选择,为什么论文要把它写进正文?
15.2 索引:论文点名的每一个开源项目
下面三张表把论文里出现过的、带地址或带明确项目名的开源资源全部列出来。「论文给的地址」一列全部是逐字照抄,论文只给了名字或只给了会议出处的,这一列写「论文未给地址」。
| 论文里的概念 | 项目名 | 论文给的地址 | 论文出处 | 对应论文哪部分 |
|---|---|---|---|---|
| K3 模型权重本身 | Kimi-K3 | https://huggingface.co/moonshotai/Kimi-K3 | 第 1 页脚注 1 | 摘要里「我们发布完整的 Kimi K3 模型权重」 |
| KDA 的分块内核 | FlashKDA | https://github.com/MoonshotAI/FlashKDA | 参考文献 [14],§5.1.1 引用 | 式 1、式 3–4 的分块形式;训练与预填充内核 |
| 完美负载均衡的专家并行 | MoonEP | https://github.com/MoonshotAI/MoonEP | 第 19 页脚注 3 | §5.2.1,以及附录 E 的 E/R 冗余专家上界 |
| microVM 智能体沙箱 | AgentENV | https://github.com/kvcache-ai/AgentENV | 第 22 页脚注 4 | §5.3.2,暂停/恢复、Fork、快照三种沙箱生命周期操作 |
| K3 自己写的 GPU 编译器 | MiniTriton | https://github.com/MoonshotAI/minitriton | 第 33 页脚注 5 | §7 案例研究「GPU 编译器开发」 |
| K3 自己设计的推理芯片 RTL | nano-kpu | https://github.com/MoonshotAI/nano-kpu | 第 33 页脚注 6 | §7 案例研究「芯片设计」,4 mm²、100 MHz、INT4 |
| KDA 的上下文并行实现 | 提交在 FLA 上游的 PR | 论文以链接文字「FLA PR #691」给出,未在正文里逐字写出完整 URL;需自行到 flash-linear-attention 仓库按编号 691 查找 | 第 18 页脚注 2 | §5.1.2 KCP,式 17 的前缀扫描分解 |
为什么 AgentENV 的地址在 kvcache-ai 名下
论文原文说 AgentENV 是「与我们的合作伙伴共同开发」的(developed in collaboration with our partners),脚注给的地址也确实不在 MoonshotAI 组织下。这是论文自己写的,不是本站的推断。至于合作伙伴是谁、组织归属如何,论文没说。
| 论文里的概念 | 项目名 | 论文给的地址 | 论文出处 | 对应论文哪部分 |
|---|---|---|---|---|
| 线性注意力算子库(FlashKDA 挂进去当后端) | flash-linear-attention (FLA) | https://github.com/fla-org/flash-linear-attention | 参考文献 [139],§5.1.1、§5.1.2、§4.2.4 引用 | KDA 的 Triton 参考实现;FlashKDA 作为其后端自动分发;也是内核任务的题源 |
| 专家并行通信库(MoonEP 的对照对象) | DeepEP | https://github.com/deepseek-ai/DeepEP | 参考文献 [147],§5.2.1 引用 | §5.2.1,论文称最坏情况下 DeepEP 需 S×K×R 通信缓冲,MoonEP 只需固定的 S×K |
| 跨 PP rank 的激活远程卸载 | Mooncake Transfer Engine | 论文未给地址(参考文献只给 arXiv:2407.00079) | 参考文献 [96],§5.2.2 引用 | §5.2.2「跨 PP rank 平衡激活」 |
| 集合通信 | NCCL | https://developer.nvidia.com/nccl | 参考文献 [82],§7 引用 | §7 MiniTriton 的分布式原语建在 NCCL 上;§5.1.2 的 all-gather |
| 稠密矩阵乘基线 | cuBLAS | https://developer.nvidia.com/cublas | 参考文献 [22],§7 引用 | §7,MiniTriton 的 tensor-core GEMM 在最大形状上接近 cuBLAS |
| 深度学习框架(数值参考基线) | PyTorch | 论文未给地址(参考文献只给 arXiv:1912.01703) | 参考文献 [89],§7、§4.2.4 引用 | §4.2.4 每个内核任务都配一份 PyTorch 参考实现;§7 MiniTriton 与 torch eager 对比 |
| 图编译基线 | torch.compile / PyTorch 2 | 论文未给地址(只给 DOI 10.1145/3620665.3640366) | 参考文献 [5],§7 引用 | §7,MiniTriton 在核心基准套件几何平均上超过 torch.compile |
| 大模型训练框架 | Megatron-LM | 论文未给地址 | 参考文献 [81] | §5.2 流水线并行等并行策略的来源之一 |
| MoE 训练扩展 | Megatron Core MoE | https://arxiv.org/abs/2603.07685 | 参考文献 [137],§5.2.1 引用(ECHO) | §5.2.1,作为「预设冗余专家数或设 token 上限」的对照方案 |
| GPU 内核编程语言 | Triton | 论文未给地址(只给 MAPL 2019 会议出处) | 参考文献 [122],§7 引用 | §4.2.4 内核任务的编程方式之一;§5.1.1 的「Triton 参考实现」;§7 MiniTriton 是「Triton 风格」编译器 |
| 分块编程模型 | TileLang | https://arxiv.org/abs/2504.17577 | 参考文献 [129],§4.2.4 引用 | §4.2.4 内核任务覆盖的编程方式之一 |
| 内核编程 DSL | ThunderKittens | https://openreview.net/forum?id=0fJfVOSUra | 参考文献 [110],§4.2.4 引用 | §4.2.4 内核任务覆盖的编程方式之一 |
| FlashKDA 的底座 | CUTLASS | 论文未给地址,参考文献里也没有对应条目,只在 §5.1.1 正文里作为形容词出现(CUTLASS-based) | §5.1.1 | FlashKDA 的实现基础 |
| 其余被点名的 GPU 编程方式 | CUDA、CuTe DSL、Gluon | 论文未给地址(正文只列了名字) | §4.2.4 | 内核任务套件覆盖的编程方式 |
| 轻量虚拟化(AgentENV 的隔离层) | Firecracker | 论文未给地址(只给 NSDI 2020 会议出处) | 参考文献 [3],§5.3.2 引用 | §5.3.2,用 microVM 换取容器给不了的隔离度与保真度 |
| 沙箱镜像格式 | OverlayBD(论文引的是 DADI 那篇) | 论文未给地址(只给 USENIX ATC 2020 会议出处) | 参考文献 [68],§5.3.2 引用 | §5.3.2,配合自定义 ublk 驱动与 P2P 传输实现亚秒级启动 |
| 编译器基础设施 | MLIR | 论文未给地址(只给 DOI 10.1109/CGO51591.2021.9370308) | 参考文献 [64],§7 引用 | §7,MiniTriton 的 warp 级标注与优化层 |
| 芯片综合用的标准单元库 | Nangate45 Open Cell Library | https://si2.org/open-cell-library/ | 参考文献 [80],§7 引用 | §7 芯片设计案例,nano-kpu 用它做时序收敛 |
| 草稿模型方法 | EAGLE-3 | https://arxiv.org/abs/2503.01840 | 参考文献 [71],§4.1.4 引用 | §4.1.4,把预训练的 MTP 层微调成 EAGLE-3 式草稿模型,训练时展开七步 |
| 优化器(原始提出) | Muon | https://kellerjordan.github.io/posts/muon/ | 参考文献 [53],§2.5、§3.3 引用 | §2.5 Per-Head Muon;§3.3 训练配方 |
| 优化器(规模化版本) | Muon is Scalable for LLM Training | https://arxiv.org/abs/2502.16982 | 参考文献 [73],§5.2.2 引用 | §5.2.2,P2P 版 Muon 正交化对照的「朴素做法」出处 |
| KCP 的思想来源 | Context Parallelism for DeltaNet | https://yywangcs.notion.site/DeltaNet-2a9fc9f5d8058013a498f34e0b25bd52 | 参考文献 [142],第 18 页脚注 2 | §5.1.2,论文说 KCP 的构造建立在 DeltaNet 上下文并行之上 |
| 解码时状态回滚(并发的独立工作) | ReplaySSM | https://tridao.me/blog/2026/replayssm/ | 参考文献 [25],§5.4.2 引用 | §5.4.2,论文说「只缓存投影输入、片上重建」这一设计被并发工作独立提出 |
| MoE 解码内核的设计来源 | WarpDecode | https://cursor.com/blog/warp-decode | 参考文献 [12],§5.4.2 引用 | §5.4.2,Stable LatentMoE 的解码内核建在其 token 中心设计之上 |
| 低比特数据格式 | Microscaling(MXFP4/MXFP8 的来源) | 论文未给 URL 字段(只给 arXiv:2310.10537) | 参考文献 [103],§4.1.4 引用 | §4.1.4,专家权重量化到 MXFP4、激活用 MXFP8 |
| 草稿模型的损失函数 | LK Losses | https://arxiv.org/abs/2602.23881 | 参考文献 [104],§4.1.4 引用 | §4.1.4,式 16 直接优化接受率的负对数 |
| 在线 softmax 归并 | Online normalizer calculation for softmax | https://arxiv.org/abs/1805.02867 | 参考文献 [79],§2.2、§5.4.2 引用 | Block AttnRes 把块间与块内部分和用在线 softmax 归并 |
| 白盒 RL 环境能实例化的主流 harness | Kimi CLI | https://www.kimi.com/code | 参考文献 [56],§4.2.1 引用 | §4.2.1 统一白盒 RL 环境 |
| 同上 | Claude Code | https://docs.anthropic.com/en/docs/claude-code | 参考文献 [15],§4.2.1 引用 | §4.2.1 |
| 同上 | Codex | https://github.com/openai/codex | 参考文献 [20],§4.2.1 引用 | §4.2.1 |
| 同上 | OpenClaw | https://docs.openclaw.ai/ | 参考文献 [86],§4.2.1 引用 | §4.2.1 |
| 同上 | Hermes Agent | https://hermes-agent.nousresearch.com/docs/ | 参考文献 [44],§4.2.1 引用 | §4.2.1 |
| 响应格式规范 | OpenAI Harmony | https://github.com/openai/harmony | 参考文献 [85](本站未逐页定位其正文引用处) | 与 §F 的 XTML 聊天模板属同类问题 |
| K3 的官方发布说明 | Kimi K3 博客 | https://www.kimi.com/blog/kimi-k3 | 参考文献 [60] | 模型发布本身 |
| 基准 | 论文给的地址 | 论文出处 | 用在哪 |
|---|---|---|---|
| Terminal-Bench | 论文未给地址(只给 arXiv:2601.11868) | 参考文献 [78] | §6.1.1 编码轴 |
| Toolathlon(The Tool Decathlon) | https://toolathlon.xyz/introduction | 参考文献 [119](另有 [69] 只给 arXiv:2510.25726) | §6.1.1 智能体轴 |
| SWE-Marathon | https://www.swe-marathon.org/ | 参考文献 [117] | §6.1.1 编码轴 |
| OSWorld-Verified | https://xlang.ai/blog/osworld-verified | 参考文献 [136] | §6.1.1 智能体轴 |
| OSWorld 2.0 | https://arxiv.org/abs/2606.29537 | 参考文献 [143] | §6.1.1 智能体轴 |
| DeepSWE | https://deepswe.datacurve.ai/ | 参考文献 [31] | §6.1.1 编码轴 |
| FrontierSWE | https://www.frontierswe.com/ | 参考文献 [35] | §6.1.1 编码轴 |
| ProgramBench | https://www.vals.ai/benchmarks/programbench | 参考文献 [95] | §6.1.1 编码轴 |
| PostTrainBench | https://posttrainbench.com/ | 参考文献 [94] | §6.1.1 编码轴 |
| JobBench | https://job-bench.github.io/ | 参考文献 [52](另有 [70] 只给 arXiv:2605.26329) | §6.1.1 智能体轴 |
| Agents' Last Exam | https://agents-last-exam.org/leaderboard | 参考文献 [4](另有 [115] 给 https://arxiv.org/abs/2606.05405) | §6.1.1 智能体轴 |
| GDPval | https://arxiv.org/abs/2510.04374 | 参考文献 [90] | §6.1.1 智能体轴 |
| Humanity's Last Exam | https://arxiv.org/abs/2501.14249 | 参考文献 [93] | §6.1.1 推理与知识轴 |
| BrowseComp | https://arxiv.org/abs/2504.12516 | 参考文献 [131] | §6.1.1 智能体轴 |
| τ³-Banking | https://taubench.com/blog/tau-knowledge.html | 参考文献 [1] | §6.1.1 智能体轴 |
常见误解:参考文献里的 URL 等于「这个项目是开源的」
不成立。表 3 里有好几条指向的是排行榜页面或公司博客,不是代码仓库——你能看到分数,不一定能看到题目,更不一定能拿到评测脚本。第14章讲过为什么这件事对「可复现」很致命。同样地,表 2 里 Firecracker、OverlayBD、Triton、Megatron-LM 这些确实是开源项目,但论文只给了论文出处,没给仓库地址——本章按铁律照实标注,你要用就自己去搜项目名。
你想做三件事:(a) 看看 KDA 的上下文并行到底怎么写的;(b) 搞明白「完美负载均衡最多只要 E/R 个冗余专家」这个结论在工程上怎么落地;(c) 找一份能用来对拍你自己写的 KDA 的参考实现。分别该去哪个项目?论文分别在哪里给的地址?
https://github.com/MoonshotAI/MoonEP。现在你自己查 (a) 和 (c)。https://github.com/fla-org/flash-linear-attention。(b) MoonEP,第 19 页脚注 3。(c) 有两条路:FLA 里的 Triton 参考实现(§5.1.1 说 FlashKDA「大幅超过 Triton 参考实现」,所以它存在于 FLA 中),以及 §4.2.4 说每个内核任务都配一份 PyTorch 参考实现——后者是论文描述的内部训练数据构造方式,论文没说这份 PyTorch 参考实现被开源。所以现实中可用的是前者。三处的具体文件与函数名,论文均未指明。变式:如果你只想读懂 §5.3.2 里「暂停一个沙箱时它不占内存也不占 CPU」是怎么做到的,你会去表 1 还是表 2 找?为什么这个问题可能在两个项目里都找不到答案?
15.3 从式 1 到一个内核:中间隔着什么
假设你现在真的打开了一份 KDA 的实现。屏幕上是几百行你看不懂的东西。这一节告诉你该去找什么。
第一件事:找状态张量。 式 1 里的 St 是 dk×dv 的矩阵,但代码里它一定不是二维的——至少还要带上 batch 维和 head 维。所以你要找的是一个四维或五维张量,其中有两个维度是 dk 和 dv。找到它,你就找到了整段代码的锚点:所有对它的读写,就是式 1 的全部。
第二件事:找 chunk 循环。 式 3、式 4 的分块形式意味着代码里必然有一层沿 chunk 索引 t 走的串行循环,循环体内部是大块的并行计算。找到这层循环,你就把代码切成了「串行骨架」和「并行血肉」两半。
第三件事:找 Diag(αt) 去哪了。 式 1 里它是一个对角矩阵乘法。但没有人会真的构造一个 dk×dk 的对角矩阵再去做矩阵乘——那是 dk 倍的浪费。代码里它一定退化成了逐元素乘(broadcast 一个长度 dk 的向量)。这是「数学写法」和「代码写法」之间最经典的一次翻译,看懂这一处,你就懂了这类翻译的套路。
第四件事:找 UT 变换那个下三角求解。 式 4 需要 U[t] 和 W[t],它们来自 UT 变换。论文把推导「留给 Kimi Linear」了,没在正文展开。代码里这一步通常表现为一个对下三角矩阵的求逆或前向替换,形状是 C×C(C 是 chunk 大小)。这也是整段代码里最难读的一块。
第五件事:找 1/Γ 的重缩放。 §2.1.1 明说式 4 用累积衰减的倒数给每个 chunk 里的 key 重新缩放,而这个倒数会无界增长。所以代码里这一处附近,大概率有一个 clamp、一个 log-space 计算,或者一次显式的类型提升到 FP32。找到它,你就找到了整份实现里数值最脆弱的地方——顺便你也验证了论文 §2.1.1 那段讨论不是空话。
这句「各自独立调度与调优」值得多说一句。它的言外之意是:这两部分的最优配置根本不一样。token 并行那部分工作量大、规则、能填满 GPU;头并行那部分是串行链条,唯一的并行度来自「有多少个注意力头」,头数一旦不够,GPU 就会大面积空转。把它们塞进同一个内核、用同一套启动参数,必然要牺牲其中一边。拆开,才能各自最优,还能让一边的计算盖住另一边的等待。
先读参考实现,不要一上来读优化实现
论文自己给了你路线图:FLA 里有一份 Triton 参考实现,FlashKDA 是另一套基于 CUTLASS 的优化实现。这两者的可读性差一个数量级。参考实现的目标是「算对」,结构大致跟着公式走;优化实现的目标是「算快」,公式的形状会被内存布局、流水线、Tensor Core 的形状要求揉得面目全非。先把参考实现读到能和式 1、式 3、式 4 一一对上,再去看优化实现做了哪些变形——那时候每一处变形你都能问出「这是为了绕开什么」。
这一节的定位
上面五条是「该去找什么」的方法论,不是「在哪一行」的导航。论文没有给出任何文件名、函数名或行号,本站也没有访问过这些仓库。所以请把上面的每一条都当作搜索的起点:拿 chunk、state、decay、cumsum、tril、recurrent 这类词在仓库里搜,比在目录树里瞎逛快得多。搜到之后能不能对上式 1,由你自己判断——那正是这一章想训练的能力。
论文说 FlashKDA「作为 flash-linear-attention 的一个后端被自动分发」。仅凭这句话,你能推断出 FLA 的代码里存在什么样的结构?再推一步:如果哪天 FlashKDA 在某台机器上跑不了(比如显卡架构不支持),从用户角度看会发生什么?
变式:把「自动分发」改成「用户手动指定后端」,上面三条推断哪几条还成立?这个改动对使用者和对维护者分别有什么好处和坏处?
15.4 读一个陌生仓库的六步法
这一节不依赖任何具体项目。你以后读任何看不懂的代码,都可以照着走。
一、先读测试,不要先读实现。 这是最反直觉、也最有用的一条。实现回答「怎么做」,而你现在缺的是「做什么」——测试文件恰好回答后者:这个函数吃什么、吐什么、什么情况算对。而且测试里通常躺着一份最小可运行例子,比 README 更新、比文档诚实。找测试的方法:在仓库里搜 test,或看有没有叫 tests 的目录。
二、先跑起来,再读。 哪怕只是把 README 里的第一条命令跑通。跑不通的代码你读一百遍也只是在读文本;跑通了,你就获得了一个可以随便改、随便加打印的活物。能改、能观察,理解速度会提高一个数量级。
三、找参考实现或 naive 版本。 几乎所有高性能项目都保留了一份「慢但正确」的实现,用来验证快的那份没写错。那一份才是给人读的。论文自己就点出了这个模式:FLA 里的 Triton 参考实现之于 FlashKDA,§4.2.4 里的 PyTorch 参考实现之于每一个内核任务。搜关键词:naive、reference、ref、eager、torch。
四、用对拍思维。 你在第2章的实验室里已经亲手做过:同一个 KDA,一边用式 1 的逐步递推算,一边用式 3–4 的分块并行算,两边结果的差在 1e-16 量级——那不是巧合,那是数学等价性的数值证据。在真实项目里这件事叫单元测试。反过来用:想搞懂一个复杂实现在干什么,就自己写一个朴素版本去和它对拍。能对上,说明你的理解是对的;对不上,差异出现在哪一步,就精确地指出了你误解了哪一步。这是理解一个算法最快的路径,没有之一。
五、从数据形状入手。 读不懂一段代码时,别硬啃逻辑,先搞清楚进去的张量是什么形状、出来的是什么形状。深度学习代码的绝大部分复杂度都在形状变换上:转置、reshape、切块、广播。把形状标在纸上,代码会突然变得可读。实操上就是到处插打印语句,或者在调试器里停下来看。
六、把 git log 和 blame 当文档用。 「这一行为什么要这么写」这个问题,注释经常骗你(写完就不再更新),提交信息不会——它记录的是当时那个人为了解决什么问题才加的这一行。看到一行莫名其妙的 clamp、一个魔法数字、一个看似多余的类型转换,先去查它的提交历史,往往一句话就解释清楚了。
打个比方
读陌生代码像走进一座没有导游的大博物馆。「读懂全部」等于从第一间展厅开始逐件看——三小时后你在第二间展厅,累到什么都记不住。正确做法是先在门口地图上定一个目标(我要看那幅画),直奔过去,路上顺带瞥见的其它东西就是白赚的。
类比失效处:博物馆的展品彼此独立,代码不是——你要看的那一行往往依赖三个你没看过的东西。所以「打靶」的过程通常是递归的:为了回答一个问题,你会被迫先回答两个更小的问题。这不是走弯路,这就是路本身。
「我写的朴素实现和仓库里的优化实现对拍通过了,所以我理解对了。」请构造一个反例:对拍通过,但你的理解其实是错的。至少给出两种不同机制的反例。
(1) 输入没覆盖分支。序列长度总是 chunk 的整数倍,尾块逻辑从未被触发;或者 batch 里样本长度全相同,变长打包的路径从未走过。
(2) 容差掩盖差异。你用 1e-3 的容差,而两份实现在数值上的真实分歧是 1e-4 量级——比如一边在 FP32 里累加、一边在 BF16 里累加。这在小规模测试上无害,放到 100 万 token 的序列上误差会累积到肉眼可见。第2章那个 1e-16 之所以有说服力,正因为它贴着 double 的机器精度,几乎没有掩盖空间。
(3) 两边错得一样。如果你的朴素实现是照着优化实现「翻译」出来的,而不是照着式 1 独立写出来的,那你复制的是同一个误解。对拍要有意义,两份实现必须来源独立。
(4) 常数被抵消。比如你把 gmin 理解错了,但恰好测试里 Ah 初始化为 0,两个错误互相抵消。
结论:对拍是证伪工具不是证实工具。通过只说明「还没被抓到」。想让它更有力,就往极端输入上打:长度不整除、单 token、超长序列、全零输入、极端衰减值。
变式:如果两份实现对拍不通过,误差在 1e-2 量级。你怎么定位是哪一步出的问题?(提示:想想二分法能不能用在「计算步骤」上,而不只是用在数组上)
你在某份 KDA 实现里打印出一个中间张量,形状是 (B, H, N, C, C),其中 B 是 batch、H 是头数、N 是 chunk 个数、C 是 chunk 大小。仅凭这个形状,你能猜出它对应式 3–式 4 里的哪一个量?为什么不可能是状态 S?
变式:如果你看到的形状是 (B, H, N, C, dk),候选有哪些?如果是 (B, H, N, dk, dv) 呢?后者最可能是什么,它为什么会占那么多显存?
15.5 动手清单:五个练手项目
下面五个任务从「一小时」跨到「几周」。每个都有明确的完成标准——没达到标准就不算做完,不要自我安慰。
| # | 做什么 | 为什么有价值 | 完成标准 | 前置技能 |
|---|---|---|---|---|
| 1 | 用纯 Python 或 NumPy 实现论文式 1 的朴素递推。随机造 q, k, v, α, β,按 t 逐步更新 St。不到 30 行。 | 逼你把式 1 的每个符号落到一个具体变量上。Diag(αt) 到底怎么写、ktkt⊤ 是外积不是内积——这些只有动手才会发现自己原来没想清楚。 | 能打印出 St 随 t 的变化(比如每步的 Frobenius 范数),并观察到:把所有 α 调小,范数会衰减得更快。 | 会写循环、会 NumPy 的 outer 和矩阵乘 |
| 2 | 实现式 3–式 4 的分块并行形式,和第 1 步对拍。 | 这是全站最核心的一次「同一个东西的两种算法」。分块形式是所有高性能线性注意力实现的骨架,写通它,你就能读懂真实内核的结构。 | 对任意 chunk 大小(包括不整除序列长度的),与第 1 步的最大绝对误差在 1e-12 以下(用 float64)。注意「任意」——尾块必须处理对。 | 任务 1,加上对第2章式 3、式 4 的理解 |
| 3 | 实现论文式 13–式 14 的 Quantile Balancing,和定步长符号更新 bj(t+1) = bj(t) + γ·sign(·) 对比收敛速度。 | 让你亲眼看到「一步解出来」和「一小步一小步试出来」的差别有多大。这是第10章的核心论点,读一百遍不如自己画一张收敛曲线。 | 复现出这个现象:QB 几步之内负载就均衡了,定步长符号更新需要几十步;而且符号更新的步数强烈依赖 γ 取多少,QB 没有这个旋钮。 | 会算分位数(numpy.quantile)、理解 Top-k 路由 |
| 4 | 实现附录 E 的填充构造:随机生成路由输出,按论文的构造分配冗余专家,验证所需冗余专家数不超过 E/R。 | 把一个存在性证明变成一段能跑的代码。证明说「一定存在可行解」,代码逼你回答「那怎么把它找出来」——这正是 §5.2.1 说的「离线用整数规划求精确解、在线用 GPU 规划内核逼近」的动机。 | 跑一千组随机输入,零违反(每一组的冗余专家数都不超过 E/R,且每个 rank 恰好收到 S×K 个 token)。 | 任务 3,加上第10章附录 E 的构造思路 |
| 5 | 最难:把 K3 权重下载下来跑起来(哪怕量化后在多卡上);或者退一步——读懂 FLA 里的 KDA 内核,改一个参数(比如 chunk 大小)看性能怎么变。 | 前者让你直面「万亿参数」这四个字的物理含义;后者让你第一次把「改代码」和「看到数字变化」连起来,这是所有性能工作的起点。 | 前者:模型能对一个提示输出连贯文本。后者:能画出「chunk 大小 → 运行时间」的曲线,并解释拐点为什么在那里。 | 前者:多卡环境、推理框架、量化工具链。后者:任务 2 + 会用 GPU 计时 |
关于第 5 项,我要诚实地泼一盆冷水
论文 Table 1 写明 K3 总参数 2.78T。就算按 4 比特量化算,每个参数半个字节,光是权重就要 2.78×1012 × 0.5 ≈ 1.39×1012 字节,也就是约 1.4 TB。这还没算 KV 缓存、KDA 状态、激活和运行时开销。按单卡 80 GB 的常见规格算,仅装下权重就需要十几张卡,而且必须是能高速互联的那种。
绝大多数人没有这个硬件,这不丢人,这是事实。更现实的三条路:一是本表的任务 1–4,全部可以在一台笔记本上做完,而且它们训练的是同一套核心能力;二是读代码——FLA 的参考实现在 CPU 上就能跑通小规模例子;三是用别人托管的推理服务去观察模型行为(比如长上下文表现),把「部署」和「理解」这两件事分开。
顺带说清楚:本站没有下载过这份权重,也没有验证过 HuggingFace 上那个地址现在是否可访问、里面文件的实际大小是多少。上面 1.4 TB 是按论文给的参数量和 4 比特估算出来的,不是实测值。
综合题。(a) 用论文 Table 1 的数字,估算 K3 在 BF16(每参数 2 字节)和 MXFP4(约每参数 0.5 字节)两种精度下,权重各占多大空间。(b) 论文 §4.1.4 说只把专家权重量化到 MXFP4,注意力投影、latent MoE 投影、共享专家、路由器都保持更高精度。这个选择会让 (a) 的估算偏高还是偏低?(c) 再往回连:为什么论文要在后训练阶段就开始做量化感知训练(QAT),而不是训练完了再量化?
(a) BF16 约 5.6 TB,MXFP4 约 1.4 TB,差 4 倍。
(b) 偏低。1.4 TB 是「全部参数都按 0.5 字节」算的,但论文明说非专家部分保持更高精度,所以真实占用比 1.4 TB 大。大多少取决于非专家部分占总参数的比例——论文没有给出这个比例,所以只能定性地说「偏低」,给不出准确数字。这正是「按论文估算」和「实测」的差距所在。
(c) 三层理由。第一,如果训练时用高精度、部署时才压到 4 比特,模型从没见过量化带来的扰动,性能会掉;QAT 让模型在训练中就适应这种扰动。第二,论文明说 QAT 从 SFT 阶段一直贯穿到 RL。第三也是最关键的:RL 阶段 rollout(产生数据的那个模型)和训练(更新参数的那个模型)如果精度方案不同,等于用 A 模型的数据去更新 B 模型,这是一种训练–推理失配,会污染整个 RL 信号。论文用「消除失配」这个说法点了这一层。
把三问串起来看:一个纯粹的部署优化(省 4 倍显存),倒逼了后训练流程的改造(QAT 前移)——这就是本站反复讲的「架构决定会在系统层留下债」的又一个实例,只不过这次债是反向传导的:部署约束反过来改写了训练流程。
变式:假设有人只把注意力投影量化到 4 比特、专家权重保持 BF16,其它不变。省下的显存大概是多少量级?这个方案为什么几乎没有意义?(提示:Table 1 说 K3 有 896 个路由专家、每专家 MoE 隐层 3072)
本章说「先读测试,不要先读实现」。请构造一个反例:一个真实存在的场景,在那里先读测试反而更慢、更容易误导人。至少两种。
(1) 根本没有测试。论文配套的研究代码经常如此。这时候替代方案是找 README 里的示例、找
examples/ 目录、或者直接找 __main__ 入口。(2) 快照式测试(只断言「输出和上次一样」)。它保证了不回归,但完全不描述语义,读它等于读一堆哈希值。
(3) 测试本身比实现还复杂。高性能内核的测试常常带着一整套参数化框架、随机形状生成器、自定义容差策略——为了读懂测试,你得先读懂测试框架,成本高于直接读实现。
(4) 测试只覆盖端到端,不覆盖你关心的那一层。整个模型的输出对了,不代表你能从中看出 KDA 内部某一步在干什么。
修正后的说法:先找「最小可运行例子」,测试通常是它最好的载体,但不是唯一载体。找不到就退回到 examples、README 命令、或者自己写一个五行的调用。真正的原则不是「读测试」,是「先拿到一个能跑的最小输入输出对」。
变式:反过来,什么样的项目里「先读测试」的收益最大?(提示:想想接口复杂、语义微妙、边界情况多的那类库——比如一个日期时间库,或者一个变长序列打包工具)
15.6 只有代码能教你的三件事
第一,数值精度是实实在在的工程约束,不是脚注。 论文里已经给了你一角:§2.1.1 说重缩放因子会溢出,所以要在对数空间算、要切 16-token 小块;§2.1.1 末尾还提到,为了纠正 flash attention 里的有偏舍入误差,K3 把注意力输出在训练中保持 FP32,这让片上占用翻倍,于是又得重新设计内核让它和 KV 暂存缓冲重叠。你看,一个精度决定,直接改写了内核的内存布局。而代码里这类东西的密度,比论文高得多——每一个 clamp、每一次 .float()、每一个 eps,背后都可能有一次线上事故。
第二,内存布局决定性能,而它在公式里完全不可见。 §5.4.1 有一段特别好的例子:论文说在一个缓存页内,所有头的状态按头连续存放,这样每个头的字节流是自包含的,可以成为跨节点传输的最小单位;于是当预填充节点和解码节点采用不同的张量并行度时,重排可以在传输路径上完成,GPU 侧零重排。这整段推理里没有一个公式,但它决定了系统能不能做预填充/解码分离。「怎么摆」和「算什么」是两个独立的自由度,论文只写后者。
第三,你会开始能估算「论文里一句话的工程代价」。 §5.2.1 那句「MoonEP 保留了 DeepEP 的整体计算流程,另外引入了冗余专家的在线规划与迁移」——读代码之前,这是一句平淡的描述;读过之后你会知道,「在线规划」意味着要写一个在 GPU 上跑的规划内核、要保证它的开销可以忽略、要保证它永远不越过 E/R 上界;「迁移」意味着专家权重要在 rank 之间搬,搬的时机要和前向反向的流水线对齐。这种估算能力是判断一篇论文「哪里最有价值」的关键——通常最有价值的地方,恰恰是描述最平淡的那一句,因为作者自己觉得那是脏活,不好意思展开。
答辩:如果我是审稿人
你说 K3 是「开放」的,论文摘要也说发布了完整模型权重。但训练数据没公开、训练代码没公开、数据配比没公开、超参没给全。那这算开源吗?如果不算,为什么整篇论文和整个社区都在用「open」这个词?
参考防守(先自己组织语言再看)
分三层答。第一层,承认事实。论文用的词是「open weights」和「we release the full Kimi K3 model weights」,指的就是权重。至于训练数据、训练代码、完整超参,论文确实没有承诺发布,也确实没有给出。所以如果按「开源软件」的传统标准(拿到源码就能自己重建产物),K3 不满足——你拿不到能重建这个模型的东西。
第二层,说清楚这个词为什么还是有意义的。权重开放解锁的能力是真实的:你可以本地部署、可以微调、可以做可解释性研究、可以在上面做安全评估而不必求人。论文的定位是「开放前沿」(open frontier)——它主张的是「前沿能力现在在所有人触手可及的范围内」,不是「你可以复现这个训练过程」。这两个主张强度差很多,别混为一谈。而且这一章的表 1 显示,他们确实开源了一批真代码:FlashKDA、MoonEP、AgentENV、MiniTriton、nano-kpu,还往 FLA 上游提了 PR。这不是空口说开放。
第三层,指出真正该批评的地方。批评应该精准打在「用『开源』这个词去收割『可复现』的信誉」这一点上,而不是笼统地说「不算开源」。具体来说:论文的评测结果无法被独立复现(数据不公开,你无法排除数据污染);缩放定律的结论无法被检验(配比和超参不全);而 §7 那句「一个早期的 K3 检查点已经在承担我们自己的内核优化工作」尤其需要追问——这类任务的数据很可能回流进了训练。这些才是「不完全开放」造成的实质性损失。至于权重本身开不开放,反倒是这里面最不重要的一环。
一句话防守:「开放权重」是一个准确的描述,「开源」是一个被滥用的词;正确的做法不是争论标签,而是逐项列出「什么公开了、什么没公开、没公开的那部分让哪些结论无法被检验」。
答辩:如果我是审稿人(第二场)
这一章反复说「读代码能学到论文学不到的东西」。那反过来——什么时候读代码是浪费时间?给我三种情况,别说漂亮话。
参考防守(先自己组织语言再看)
情况一:你还没搞清楚「为什么」。没读懂式 1 就去读 KDA 内核,你看到的只是一堆张量操作,无法判断哪一步是数学要求、哪一步是性能妥协、哪一步是历史包袱。这时候读代码不是学习,是抄写。正确顺序永远是先论文后代码。
情况二:你要回答的是「值不值得」而不是「怎么做」。比如「KDA 相比全注意力在 100 万上下文下省了多少」——这是个设计权衡问题,论文的图表、消融、缩放曲线才是答案来源。代码回答不了它,代码只告诉你某个特定实现跑多快,那是被十几个无关变量污染过的数字。
情况三:这段代码是给机器读的,不是给人读的。高度优化的内核、自动生成的代码、编译器 IR,可读性本来就不在它们的设计目标里。硬读这类代码的投入产出比极低。这时候正确做法是读它对应的参考实现,或者读它的测试,或者读作者写的设计文档——本章表 1 里那句「先读参考实现」就是这个道理。
再加一条隐藏情况:代码可能已经过时。论文写于某个时间点,仓库一直在往前走。你读到的实现可能已经不是论文描述的那一版了。这时候 git log 比代码本身更有价值——它能告诉你论文那一版在哪个提交上。
一句话防守:代码是「怎么做」的唯一权威,但它对「为什么这么做」「值不值得这么做」几乎沉默。用错工具比不用工具更浪费时间。
本章小结
这一章做了三件事。一是给出距离感:论文 §5.1.1 描述 FlashKDA 用了四行字,而这四行字背后至少藏着分块边界、数值精度、内核启动配置、多套实现的分发策略四类东西——「懂公式」和「能实现」之间的距离,恰好就是论文省略掉的那部分。二是建索引:三张表逐字收录了论文里出现的地址,包括 Moonshot 自己开源的六项(K3 权重、FlashKDA、MoonEP、AgentENV、MiniTriton、nano-kpu,外加提给 FLA 上游的 PR #691)、三十来个被点名的第三方项目、以及十几个评测基准。三是给方法:先读测试、先跑起来、找参考实现、对拍、看形状、查 git log,外加最重要的一条——带着一个具体问题进去,答完就停。
关于可靠性,请把这两条一起记住。第一,本章所有 URL 都是从论文里逐字抄的,一个都没有补、没有猜;第二,本站没有访问过其中任何一个,也没有在任何仓库里定位过任何文件、函数或行号——凡是需要具体位置的地方,本章给的都是「搜什么关键词」而不是「打开哪个文件」。这个区分不是谨慎过头,它本身就是这一章想教的东西:把「我知道的」和「我以为我知道的」分开,是读代码和读论文共通的第一项基本功。
最后,表 4 那五个任务里,前四个都能在一台普通笔记本上做完,它们训练的能力和第五个是同一套。1.4 TB 那盆冷水泼的是硬件,不是你。
第16章 终极闯关:跨章综合、总答辩与研究课题
前面十五章是拆解,这一章是重装。这里的题一道也不局限在单章之内——它们要求你把架构、训练、系统、评测四条线同时握在手里。做完这一章,你就不只是「读过」这篇论文了。最后一节是研究模式:两个开放课题,我会明确告诉你哪个是真实的未解问题、哪个只是对你而言的未知领域。
通关标准
- 八道综合题,每题必须做对变式才算过
- 三场总答辩,你要在不看参考防守的情况下先自己组织一遍
- 最后的「结业自查表」上,每一条都能不看材料说清
- 选一个研究课题,写出你的入手方案(不需要完成,需要方案)
16.1 第一关:因果链复原
这一关考的是全站最重要的思维方式——每一个架构决定都会在系统层留下一笔债。
下面是 K3 的四个架构层决定。请为每一个追踪出至少两处它在论文第 5 节(基础设施)或第 4.1.4 节(部署感知后训练)里造成的具体后果,并指明在本站哪一章。
① 每个 block 用 3 层 KDA + 1 层 Gated MLA
② 93 层用 Attention Residuals 而非标准残差
③ 896 个路由专家、每 token 激活 16 个
④ 训练上下文长度 100 万 token
① 3 KDA + 1 Gated MLA
· §5.1.1(第10章):KDA 递推串行 → 天真实现下 SM 在状态传播时闲置 → FlashKDA 用 CUTLASS 把块内计算与跨块状态传播重叠;另外为长上下文预填充做设备内 SM 级上下文并行。
· §5.1.2(第10章):delta 规则把依赖 token 的矩阵 Mt 作用在传入状态上 → 局部片段的效果取决于进入它的状态 → 普通线性注意力「从零算再求和」的做法失效 → 必须发明 KCP,把片段效果拆成「累积转移」和「从零生成的状态」两个可本地计算的量,用一次固定大小 all-gather 加前缀扫描重建。
· §5.4.1(第11章):MLA KV 缓存按 token 分页、随长度增长;KDA 状态固定大小、每请求一份。两者必须在同一个边界上一起恢复才能复用前缀 → 统一分页布局 + 解耦哈希粒度与物理块粒度 + 三个并发一致性机制。
· §5.4.2(第11章):KDA 解码时状态原地更新 → MTP 推测解码验证失败时状态已越过最后一个被接受的 token、无法回滚 → 只缓存投影输入、在片上重建被接受 token 的状态。
② Attention Residuals
· §2.2 本身(第4章):完整形式是 O(Ld) 显存 + 流水线并行下的跨阶段通信 → 这就是 Block AttnRes(切成 8 块,降到 O(Nd))存在的全部理由。
· §5.2.2(第10章):块表示在边界层生成一次、后续层共享、直接驻留 GPU;整个 AttnRes 计算被 checkpointing 包住,使每层为反向保存的激活与标准残差架构完全相同;流水线并行用基于缓存的通信,只增量传输新块、微批次一结束就释放。
· §5.4.2(第11章):两阶段调度(块间 pass + 块内 online-softmax 归并);预填充时用序列并行避免在每个 TP rank 上重复实体化块表示;解码时块间内核放旁路 stream,块内内核融进前面的 TP all-reduce。
③ 896 专家 / 激活 16
· §2.3.3(第5章):近 10³ 个专家超出了定步长符号偏置更新的良好工作范围 → Quantile Balancing。
· §5.2.1(第10、14章):专家并行下 token 负载不均衡 → MoonEP 用动态冗余专家做完美均衡,并证明每 rank 至多 E/R 个冗余专家;完美均衡进一步带来静态形状(消除逐层主机同步)和固定 S×K 通信缓冲区(而非 S×K×R)。
· §4.1.4(第8章):2.78 万亿参数在部署时装不下 → MXFP4 量化专家权重、MXFP8 激活,且从 SFT 阶段起做 QAT,RL 时 rollout 与训练共享同一量化方案以消除训练–推理不匹配。
· §5.4.2(第11章):小批量解码时 group GEMM 退化成内存受限的权重流式读取 → 基于 WarpDecode 的 token 中心内核,warp 再细分成 lane team,权重布局离线置换以降低运行时反量化开销。
④ 100 万上下文
· §3.4(第7章):NoPE + 四阶段渐进课程(8K→64K 预训练,256K→1M 冷却)+ 长文档上采样 + 合成需要跨全长注意的任务。
· §5.1.2(第10章):单卡放不下 100 万 token 的激活 → 上下文并行 → 对 KDA 就是 KCP。
· §5.3.1(第11章):1M 上下文的 RL 里,要为下一轮持久保存的 rollout KV 缓存与训练所需内存互相争抢 → 外部 KV 缓存池(写回而非写穿)+ 训练状态卸载到 NVMe + 自动限流调度器。
· §5.4.3(第11章):生产流量里每请求成本跨三个数量级 → 缓存感知亲和调度(一致性哈希钉两个集群)+ 基于预算的准入控制。
如果你答出了三分之二以上,你已经真正读懂了这篇论文的结构。这篇报告的第 2 节和第 5 节不是两个独立的部分,而是同一组决策的正面和背面。
变式:现在反向做一次。论文 §5.2.2 提到「用 Mooncake Transfer Engine 把激活远程卸载到其他流水线并行 rank 的内存」。请反推:是哪一个更上游的决定导致了「激活在各 PP rank 间分布不均」?(提示:不是架构决定,而是一个并行策略的决定,与流水线的预热阶段有关。)再问:这个不均衡与 §5.2.3 里「把 ViT 计算塞进流水线气泡」利用的是同一个现象吗?
16.2 第二关:数字对账
论文里散落着很多数字。下面四组数字之间存在可验证的一致性关系或明显的张力。请逐组分析。
(a) 表 1:层数 93;注意力层构成「69 KDA + 24 MLA」;§2.1 说「每个 block 是 3 个 KDA 层后跟 1 个 Gated MLA 层」,且「骨干末尾额外加一个 Gated MLA 层」。
(b) §2.2 说「把层分成 8 个块,每块 12 层,产生一个不完整的末块,算上词嵌入层共 9 个块」。
(c) 表 1:路由专家 896,每 token 激活 16,总参数 2.78T,激活参数 104.2B。
(d) §7 案例:AttnRes 内核延迟从 283.6 ms 降到 114.4 ms;图 14 标注 K3 相对 FLA Triton 基线加速 +59.7%。
(a) 完全自洽,而且能反推出论文没写的数字。
设有 B 个完整 block。每个 block = 3 KDA + 1 MLA = 4 层。加上末尾额外的 1 个 Gated MLA:
总层数 = 4B + 1 = 93 → B = 23
KDA 层 = 3 × 23 = 69 ✓
MLA 层 = 23 + 1 = 24 ✓
论文从未写出「23 个 block」这个数,但它可以被完整反推。这也解释了 69/24 = 2.875 ≠ 3 的「矛盾」:3:1 是块内比例,全局比例被末尾那一层拉低了。
(b) 字面读会出矛盾,需要修正理解。
8 × 12 = 96 ≠ 93。而论文又说「产生一个不完整的末块」。所以正确读法是:前 7 块各 12 层(84 层),第 8 块只有 9 层(84 + 9 = 93)。再加上词嵌入层作为一个信息源,共 9 个源。这与式 10 里 b0 = h1(词嵌入永远是一个源)一致。
但请注意:「末块是 9 层」是推断,论文没有明写。另外,AttnRes 的 12 层块边界与注意力的 4 层 block 边界是否对齐(12 是 4 的倍数,看起来像是刻意的),论文完全没有说明。
(c) 可以做一致性检查,但无法精确核对。
激活参数 / 总参数 = 104.2B / 2780B ≈ 3.75%。而 896 个专家里激活 16 个,专家部分的激活比例是 16/896 ≈ 1.79%。两者不等是正常的——因为激活参数里还包含了所有非专家部分(注意力投影、共享专家、路由器、词嵌入),这些是每个 token 都要过的。
从「3.75% > 1.79%」可以定性推断:非专家部分在激活参数里占了可观的份额。但要精确核对需要知道每个模块的参数量,论文没有给出足够的分解信息。这是一个可以做定性检查、无法做定量核对的关系。
(d) 高度吻合,但定义未给出,只能算合理对账。
283.6 → 114.4 ms。若把「加速」定义为节省的时间比例:(283.6 − 114.4)/283.6 = 59.66%,与图 14 标的 +59.7% 几乎完全一致。
但若定义为加速倍数减一:283.6/114.4 − 1 = 148%,就对不上了。
所以可以推断图 14 的纵轴用的是前一种定义(时间节省比例)。但论文没有给出这个定义的公式,这只是一次成功的反推,不是论文明说的。读到任何标着「speedup」的图,先确认它的定义——这两种定义在文献里都有人用,差别巨大。
这道题的意义:技术报告里的数字不是孤立的装饰,它们之间有可验证的约束。养成「随手对账」的习惯,你会发现三类东西:被验证的自洽(提高你对全文的信任)、可反推的隐含信息(比如 23 个 block)、真实的表述含糊(比如 8×12 和 speedup 的定义)。
变式:再做一组。论文 §5.3.2 说全程创建了 51,219,741 个沙箱、跨 1,505,678 个镜像;又说「暂停的沙箱不消耗内存或 CPU 资源,而等待模型推理可占沙箱生命周期高达 98%」;还说实现了「高达 6.5× 的内存超额分配比」。(a) 平均每个镜像对应多少个沙箱?(b) 「98% 的时间可暂停」和「6.5× 超额分配」这两个数字之间,是互相支撑还是互相冗余?如果一个沙箱 98% 的时间都不占内存,那超额分配比理论上可以达到多少?6.5× 相对这个理论值说明了什么?
16.3 第三关:设计决策
现在你是架构师。你要为一个新场景设计模型,约束如下:
· 上下文长度只需要 32K(不是 1M)
· 部署在单张 80GB 显卡上,不允许多卡
· 主要用途是高并发的短对话客服,每个请求 2K token 以内,QPS 极高
· 允许你从 K3 的设计里任意取舍
请逐条判断:KDA、Gated MLA 的 3:1 混合、Block AttnRes、896 专家的 LatentMoE、MXFP4 量化、推测解码、前缀缓存——这七样各自保留、修改还是砍掉?每一条给出理由。
2.78T × 0.5 字节 ≈ 1.39 TB,是 80GB 的 17 倍。所以模型规模必须大幅缩小,896 专家是不可能的。
再算第一个约束:32K 上下文下,第0章那道题的算法给出 KV 缓存 = 46.5 KB/token × 32K ≈ 1.5 GB。这在 80GB 里完全可以接受。
所以 KDA 的核心收益(省 KV 缓存)在这个场景下……你自己接着推。
KDA — 大幅缩减比例,甚至砍掉。 它的收益是「KV 缓存不随长度增长」。32K 下 KV 缓存约 1.5 GB(按第0章的算法),在 80GB 里微不足道。而 KDA 的代价(串行递推、需要专门内核、上下文并行要重新发明、推测解码无法回滚、前缀缓存粒度冲突)一样也不会少。
但有一个反对意见你必须考虑:高 QPS 意味着要同时装下几百个请求的 KV 缓存。1.5 GB × 200 并发 = 300 GB,就装不下了。所以真正的判断依据不是「上下文多长」,而是「并发数 × 单请求缓存」。这个场景下 KDA 可能仍然有价值,只是理由从「支持长上下文」变成了「支持高并发」。能识别出这个转折,是这道题的核心考点。
3:1 混合 — 保留,但比例需要重新搜索。 3:1 是在 1M 上下文下定的。32K 下 MLA 层便宜得多,可以放宽到 1:1 甚至全 MLA。论文没有给任何比例消融,所以这只能靠自己重新实验。
Block AttnRes — 保留,且更划算。 它的开销是 O(Nd) 显存和跨阶段通信,而跨阶段通信在单卡场景下完全消失(没有流水线并行)。它的收益(深度方向的选择性信息检索)与上下文长度无关。这是七项里性价比变得最高的一个。不过要注意:模型变小了,层数变少,AttnRes 的收益本身也会缩水(论文引用的 N≈8 是在更深的网络上)。
896 专家 LatentMoE — 必须大幅缩减,但保留 MoE 结构。 参数装不下,专家数要砍一到两个数量级。但 MoE 的核心收益(总参数与计算量脱钩)在高 QPS 场景下依然极有价值——你希望模型知识多但每 token 便宜。另外,单卡意味着没有专家并行,于是 MoonEP 和负载均衡的跨卡动机消失;但卡内各专家 token 数的倾斜依然存在(论文 §5.2.1 的「Expert-GEMM 调度」那一段讲的正是这个),所以 Quantile Balancing 仍有意义——它服务的是「专家训练是否充分」,而不只是「卡间负载」。
MXFP4 — 保留,且是最该保留的。 单卡 80GB 的硬约束下,量化直接决定了你能装多大的模型。而且论文的 QAT 做法(从 SFT 起就量化、RL 时 rollout 与训练共享量化方案)解决的是「训练–推理不匹配」,这个问题与场景无关。
推测解码 — 保留,但收益可能下降。 它摊薄的是「每个 token 都要把权重流一遍」的带宽成本。但注意:高 QPS 意味着大批量,而大批量下带宽成本本来就被摊薄了(多个请求共享一次权重读取)。所以推测解码在低并发时收益最大、高并发时收益缩水。这个场景下它可能不是优先项。论文 §5.4.2 恰好提到「这个代价在在线服务典型的大批量下会占主导」,说的是状态快照的代价,但同一个「大批量」逻辑也适用于推测解码的收益评估。
前缀缓存 — 保留,且极其重要。 客服场景有大量共享的系统提示(人设、规则、知识库摘要),这部分前缀在所有请求间完全相同。前缀缓存能让这部分只算一次。而且如果你砍掉了 KDA,前缀缓存的实现会简单一个数量级——§5.4.1 那一整节的复杂度(统一分页布局、解耦哈希粒度、三个并发一致性机制)全部来自 KDA 状态与 MLA 缓存的粒度冲突。这是「砍掉 KDA」这个选项一个被低估的收益。
这道题真正想训练的能力:不是记住 K3 怎么做的,而是能把每个设计还原成「它在解决什么压力」,然后判断新场景下这个压力还在不在。注意其中至少三处,正确答案与直觉相反:KDA 的价值来源从「长上下文」转移到了「高并发」;推测解码的收益随并发上升而下降;砍掉 KDA 的最大收益之一是前缀缓存变简单。
变式:换一个场景。约束改成:上下文 1M,但只做单用户、低并发的离线文档分析,延迟不敏感(可以跑一整晚),有 8 张卡。请重做这七项判断,并明确指出哪几项的结论与上一题相反、为什么。
16.4 第四关:证据审计
请从整篇论文中挑出你认为证据最薄弱的三个主张,对每一个说明:论文声称了什么、给了什么证据、缺什么证据、以及需要什么样的实验才能真正支撑它。
要求:三个主张必须来自不同的章节(不能都挑评测部分)。
① §3.2「约 2.5× 的整体缩放效率提升」——打包型。
· 声称:KDA + AttnRes + Stable LatentMoE + 精炼的数据与训练配方,合起来带来相对 K2 约 2.5× 的缩放效率。
· 证据:图 7 的两条拟合曲线,标注了一处 2.5× 的箭头。
· 缺什么:图上没有坐标轴数值,无法反推任何具体损失;没有任何单项消融,四个因素的贡献完全无法拆分;没有说明这个倍数在整个 FLOPs 区间是否恒定(两条线是否平行未知);也没给拟合的置信区间。
· 需要什么实验:逐项消融的缩放定律——固定其他三项,只改一项,各画一条曲线。这需要四到五组独立的缩放实验,成本很高,但这是唯一能把 2.5× 拆开的办法。
② §2.4「对比预训练作为多模态语言模型的初始化不是必需的」——外推型。
· 声称:MoonViT-V2 从零用下一 token 预测训练,在视觉评测上追平了 SigLIP 初始化的基线,说明在这个规模上对比预训练不是必需的。
· 证据:图 6 的梯度范数曲线(显示 from-scratch 更稳),以及一句「matches the baseline」。
· 缺什么:这两页里没有给出任何评测表或分数,「追平」具体是在哪些评测集上、差多少,读者无从核对;图 6 是过程指标(梯度范数)而非结果指标,没有展示任何一次真实的训练崩溃,也没给两条路的最终损失;结论加了 「at scale」 限定却没给规模下界;这是一次对比,不是多次独立实验。
· 需要什么实验:在至少三个不同规模上各做一组 from-scratch vs SigLIP-init 的对照,报告最终损失和一组固定的视觉评测分数,并给出方差。这样才能支撑「at scale」这个限定,并找出规模阈值。
③ §5 的系统优化「大幅超过 Triton 参考实现」等——定性型,且这是全篇最普遍的问题。
· 声称:FlashKDA「大幅超过 Triton 参考实现」;KDA 解码的验证延迟「次线性增长且低于状态缓存基线」;ViT 计算「基本消除了视觉编码器的有效开销」;统一激活管理器的预取「额外开销可忽略」。
· 证据:全部只有文字,没有一个数字。
· 缺什么:加速比、吞吐提升、内存节省量、开销占比——一个都没有。整个第 5 节(除了沙箱的 133ms/49ms/6.5×/5100 万这几个数)几乎没有性能数据。
· 需要什么实验:其实不需要新实验,只需要把已有的测量报告出来。这些优化显然是被测过的(否则不会上线),只是没写进报告。这是披露问题而非科学问题——但对读者而言效果一样:无法判断哪个优化重要、无法复现、无法在自己的系统里做取舍。
这道题的意义:批判性阅读的终点不是「挑毛病」,而是知道自己能从这篇论文里安全地拿走什么。① 之后你能拿走的是「这套组合在作者的配方下有效」,不能拿走「KDA 值 X 倍」;② 之后你能拿走的是「from-scratch 在这个规模上可行」,不能拿走「对比预训练没用」;③ 之后你能拿走的是「这些优化的设计思路」(这部分论文写得很详细,是真正的贡献),不能拿走任何性能预期。
变式:现在做一次反向审计。请从论文里挑出三个你认为证据最扎实的主张,说明它们为什么扎实。(提示:想想哪些主张是数学上可验证的、哪些是任何人都能独立复现的、哪些有第三方独立评估背书。这三类各挑一个。)
16.5 总答辩:三场
答辩一:这篇论文的核心贡献究竟是什么
我的攻击是:这篇论文里没有一个真正的新想法。KDA 是 Kimi Linear 的小改(换了个衰减函数);AttnRes 是引用别人的工作;LatentMoE 是引用别人的工作;MoE、MLA、Muon、推测解码、EAGLE-3、量化训练全是现成的。所谓的 K3 只是一次规模更大的工程集成。既然如此,它凭什么值一篇 47 页的技术报告?
参考防守(先自己组织语言再看,这场要写出至少三点)
先承认攻击的合理部分:这个描述在组件层面基本准确。论文自己也大方地标注了每个组件的来源(KDA 引用 Kimi Linear,AttnRes 引用 [57],LatentMoE 引用 [32],MLA 引用 DeepSeek-V2,Muon、EAGLE-3、MXFP4 全都引用了原始工作)。如果你的评价标准是「有没有一个全新的数学对象」,这篇论文的得分不高。
但辩护有四层:
第一层:有些「小改」是有实质内容的。下界衰减不是「换个函数」这么简单——它是一条完整的推理链:注意到 1/Γ 会溢出 → 前代靠划分 16-token 小片绕开、但对角小片仍需位置对计算 → 与其绕开不如从源头限制 → 反推出 gmin = −5 使 e80 落在 BF16 内 → 对角路径整个消失。这是「硬件约束反向决定数学形式」的一个干净例子,其价值不在于函数本身,而在于这条推理链可以被迁移到其他递推模型上。Quantile Balancing 同理——它把一个启发式的符号更新,还原成一个可证明的对偶问题的精确坐标极小化,从而解释了「为什么不需要学习率」。把启发式还原成有原理的方法,本身就是贡献。
第二层:MoonEP 的界是一个真正的新定理。附录 E 证明了「每 rank 至多 E/R 个冗余专家总能实现完美均衡」,并证明这个界本质上是紧的。这不是工程调参,这是一个有构造性证明和紧性证明的组合优化结果,而且它改变了系统设计——因为界是可证的,所以可以预留固定槽位、保证训练永不中断,这与 ECHO/UltraEP 那类「预设上限、无解就停」的做法有质的区别。
第三层:集成本身在这个规模上是非平凡的。攻击里「只是工程集成」这个说法,隐含了「集成是容易的」这个前提,而这个前提是错的。论文第 5 节展示的正是:每个组件的选择都会让其他组件变难。KDA + MLA 混合让前缀缓存的粒度对齐成为一个需要三个一致性机制才能解决的问题;896 专家 + 2.8T 参数让 EP 负载均衡从「调优问题」变成「可行性问题」;1M 上下文 + 部分 rollout 让 KV 缓存与训练内存直接冲突。这些问题在小规模下不存在,所以它们的解法也无法从小规模工作里外推。
第四层(最重要):这类报告的价值不在新颖性,而在信息披露。把一个 2.8 万亿参数模型造出来,需要成千上万个决策。绝大多数被造出来的前沿模型什么都不说。这篇报告说了:架构的每个参数、训练课程的分段、RL 的三领域九专家结构、九类 RL 环境的设计、沙箱的三个高层操作、前缀缓存的三个并发失效模式。这些信息对整个领域的价值,与「是否有新数学」是两个正交的维度。
但攻击有一点是站得住的,值得记住:正因为组件都来自别处,「这些组件在 2.8T 规模上是否仍然有效」就成了一个开放问题,而论文没有做任何单项消融来回答它。所以你不能从这篇报告推出「AttnRes 在万亿规模上有用」——你只能推出「作者把它用了,整体结果不错」。
答辩二:开源了什么,没开源什么
论文反复强调 K3 是「世界上第一个开放的 3T 级模型」,把「开放」当作核心贡献之一。我的攻击是:它开放的只是权重——训练数据没有、数据配比没有、训练代码没有、超参数大部分没有、算力规模没有、成本没有。一个我无法复现的模型,「开放」这个词用在它身上算不算过誉?而且,2.78 万亿被称为「3T 级」,这本身是不是也在修辞上占便宜?
参考防守(先自己组织语言再看)
关于「3T 级」:这个攻击成立,但杀伤力有限。2.78T 向上取整到「3T-class」是量级修辞,在学界是常见做法(就像「百亿参数模型」通常不是恰好 100 亿)。它确实在措辞上占了便宜,但「3T 级」作为一个量级描述并不失实,而且论文在表 1 里给出了精确值 2.78T,没有隐瞒。这属于可指出但不必深究的那一类。
关于「开放」:这个攻击更实质,需要分层回应。
第一,要承认「开放权重」 ≠ 「开源」。严格意义上的开源需要能复现整个构建过程。K3 给的是权重,不是配方。论文里能查到的训练细节确实是残缺的:数据配比只说「由小模型消融决定」没给数字;四阶段上下文课程只给了 8K/64K/256K/1M 四个端点,各阶段占多少预算没说;训练用了多少 GPU、多少 token、总成本,一概没有;RL 的关键超参数(λ、τ、σ、R_max)全部缺失。你无法用这篇报告复现 K3。
第二,但「开放权重」本身的价值是真实且巨大的。它意味着:任何人可以下载、微调、部署、审计、做机制可解释性研究、在自己的任务上评测而不受 API 限制、在没有网络的环境里运行。这些事在闭源模型上一件也做不了。而且论文第 6.2.2 节还提到一个具体的后果:Anthropic 和 OpenAI 的前沿模型拒绝网络安全相关任务,导致无法做可比评测——而开放权重的模型可以被独立第三方(论文提到了英国 AISI 和 NIST CAISI)在完全受控的条件下评估。可审计性是开放权重的一个被低估的价值。
第三,「能不能复现」这个标准,对这个规模是不现实的。即使论文公开了全部数据和代码,能复现 2.8 万亿参数训练的机构在全世界不超过十家。所以「可复现」作为评判标准,在这个规模上区分不出任何东西。更有意义的标准是「披露了多少可迁移的知识」——按这个标准,这篇报告的信息密度是相当高的(第 5 节的系统设计几乎每一段都可以被别人拿去用)。
第四,也是最该说的一点:措辞的准确性依然重要。论文用的是 「we release the full Kimi K3 model weights」(我们释放完整的 K3 模型权重)——这个表述是准确的,说的就是权重。摘要里「open frontier」的说法则更松一些。作为读者,正确的做法是以精确表述为准,把宽泛表述当作叙事。这也是第1章教的证据分级的又一次应用。
最后一个诚实的补充:本站没有核实过权重是否真的可下载、许可证条款是什么、有没有使用限制。论文只给了一个 HuggingFace 链接。「开放」到什么程度,最终取决于许可证,而这篇报告没有讨论许可证。这是一个真实的信息缺口。
答辩三:换你来当审稿人
这一场没有参考答案,因为攻击方是你。
请你写出三条给作者的修改意见,每条包含:具体指向论文的哪一节、你认为的问题是什么、以及一个具体可执行的改进建议(不能是「应该做更多实验」这种空话,要说清做什么实验、测什么指标、和什么对照)。
写完之后,再为每一条估计作者拒绝它的最可能理由。
写完你自己的三条之后,再看这里的自查标准
好的审稿意见有三个特征,对照检查你写的:
① 指向具体。「§3.2 的 2.5× 缺少消融」是好的;「实验不够充分」是坏的。
② 建议可执行。「在 1B、7B、30B 三个规模上各做一组 from-scratch vs SigLIP-init 的对照,报告最终验证损失和 MMMU-Pro/OmniDocBench 两个评测的分数与三次运行的标准差」是好的;「应该验证一下这个结论」是坏的。
③ 承认成本。顶级的审稿意见会主动区分「这个实验很便宜,没做说不过去」和「这个实验极贵,理解为什么没做,但请在文中明确声明这个局限」。第二种意见几乎总是会被采纳,而第一种常常引发对抗。
关于「估计作者拒绝的理由」这一步——这不是在教你妥协,而是在训练一个更重要的能力:区分「证据缺口」和「披露缺口」。
比如「§5 全篇没有加速比」,作者最可能的拒绝理由是「这些数字涉及内部基础设施细节」。如果是这样,那这就不是科学问题而是商业决定,你的意见应该改成「请至少给出相对倍数而非绝对数值」——这样绕开了披露顾虑,仍然恢复了可比性。
而「§3.2 的 2.5× 缺少消融」,作者最可能的拒绝理由是「每组消融都要重跑完整的缩放定律实验,成本以百万美元计」。这个理由是真实的。所以更现实的意见是「请在文中明确声明 2.5× 是四个因素的联合效应,各自贡献未知,不应被引用为单个组件的收益」——这几乎零成本,且实质性地提高了论文的诚实度。
如果你的三条意见都能通过这个自查,你已经具备了给这类论文写审稿意见的能力。
16.6 结业自查表
下面每一条都应该能不看材料说清楚。勾不上的,回对应章节。
16.7 研究模式:两个开放课题
学完材料之后,真正的开始是能自己提问。下面给你两个课题。按照本站的约定,我会明确区分它们的性质。
课题一:混合比例的可迁移规律(真实的未解问题)
性质说明
这是一个学界尚无定论的真实开放问题,不是「只有你不知道答案」。据我所知,截至这篇报告,还没有工作系统性地回答「线性注意力与全注意力的最优混合比例如何随模型规模、上下文长度、任务类型变化」。K3 用 3:1,前代 Kimi Linear 也用 3:1,但两篇论文都没有给出比例消融。
问题:给定模型规模 N、目标上下文长度 L、任务分布 D,最优的 KDA:MLA 比例是多少?这个比例有没有可预测的规律?
为什么它重要:如果比例是随规模变化的,那么所有在小模型上验证过的混合架构结论都不能外推——而这正是当前领域的普遍做法。如果比例是恒定的,那需要解释为什么。更进一步:K3 把位置编码的全部责任交给了 KDA(因为 MLA 层用 NoPE),这意味着比例不只影响效率,还影响模型能否感知词序。降低 KDA 比例是否存在一个「位置信息不足」的硬下界?
入手之前,先回答这五个问题
你打算怎么测量「最优」?验证损失?某类下游任务?还是专门设计的探针任务?三者可能给出不同的最优比例——你怎么处理这个分歧?
一点提示(但请先自己想)
验证损失是最省事的,但它对「长程检索」这类能力极不敏感——一个完全丧失长程能力的模型,验证损失可能只差一点点。所以至少需要配一个直接探测长程依赖的指标。经典做法是「大海捞针」类的合成任务,但它太简单,容易饱和。更好的做法是设计需要跨越全长做多跳推理的任务——注意论文 §3.4 说他们合成长上下文数据的办法正是「排列并拼接文档和子任务,使嵌入其中的任务只有靠关注散落在全长的信息才能解决」。这个思路可以直接借来做评测。
你能负担的最大规模是多少?如果只能训到 1B,你怎么论证结论对 100B 有效?
一点提示
诚实的答案是:你不能直接论证。但你可以做三个以上规模点,观察最优比例随规模的趋势(单调上升?下降?平稳?)。趋势本身就是有价值的结果,即使它不能外推到万亿规模。这也是为什么「至少三个规模点」是这类研究的最低门槛——两个点无法区分「平稳」和「缓慢变化」。
NoPE 这个混杂因素怎么处理?如果你降低 KDA 比例导致性能下降,你怎么区分是「记忆容量不够」还是「位置信息不够」?
一点提示
这是这个课题最关键的设计难点。一个可行的拆分办法是做一组对照实验:在低 KDA 比例下,给 MLA 层加回 RoPE。如果加回位置编码后性能恢复,说明瓶颈是位置信息;如果不恢复,说明是记忆容量。能想到这个对照,说明你真正理解了第3章那个 NoPE 依赖的脆弱性。
你需要什么工具?列出来。
一点提示
至少需要:一个支持混合注意力的训练框架(论文提到 KDA 的实现在 flash-linear-attention 的 PR #691,这是一个真实可查的起点);一个能跑多规模缩放实验的算力预算;一组长程探针任务。最缺的通常不是算力而是第三项——现有的长上下文评测大多要么太简单要么不可控。这本身可能就是一篇独立的工作。
如果做完发现「最优比例在你测的所有规模上都是 3:1」,这是好结果还是坏结果?
一点提示
是好结果,而且可能比找到一个复杂规律更有价值——它意味着这个超参数不需要随规模重调,可以直接沿用。负面结果和「无变化」结果在方法论上同样重要,只是不好发表。注意论文 §4.1.3 就报告了一个负面结果(「更细粒度的 top-k 蒸馏目标没有观察到明显优势」),这是这篇报告值得称道的地方之一。
课题二:把「下界衰减」的推理链迁移到别处(对你而言的未知领域)
性质说明
这个课题的方法论是已知的——「用数值范围约束反推参数化形式」这个思路在文献里存在(论文自己就引用了三篇有下界递推门的先前工作)。所以这不是一个学界的未解问题。但把它系统性地应用到一个具体的新模块上,对你而言是一次真实的研究训练,而且完全可以在小规模上独立完成,不需要大算力。
问题:第2章展示了一条完整的推理链:某个计算会溢出 → 定位溢出发生的最坏情形 → 从硬件的数值范围反推参数的边界 → 得到一个新的参数化形式 → 顺带消掉一条慢代码路径。请把这条链应用到另一个模块上。
候选目标(任选其一):
- SiTU-GLU 的 β₁、β₂。论文取 4 和 25,给出输出界 100,但完全没有解释这两个数怎么来的,也没有消融。你能不能反推一条类似的链?(提示:先问「输出界 100 是为了让什么落在什么范围内」——是激活的存储格式?是下一层某个计算的输入范围?为什么门分支和上分支的界差了六倍多?)
- QB 直方图的箱数 B。论文取 1000,说误差被箱宽限住、「至多几个 10⁻³」。你能不能反推:要让残余负载不均衡低于某个阈值,B 至少要多大?这个下界怎么随专家数 n、Top-k、批次大小变化?
- Block AttnRes 的块数 N。论文取 8,引用先前工作说「N ≈ 8 恢复大部分收益」,但没在 K3 规模上验证。你能不能从显存、通信量、推理时状态大小三个约束反推 N 的合理区间?
入手方案:先回答这四个问题
你选的这个参数,它约束的究竟是哪个具体的计算?写出那个计算的表达式。
做法提示
这一步做不出来,后面全免谈。第2章里这一步的答案是「式 4 里的 K/Γ」——极其具体。如果你只能说「某个地方会溢出」,说明你还没定位到。把论文里所有出现这个参数的公式找出来,逐个问「这里的值域是什么」。
最坏情形是什么?给出一个能让这个计算取到极值的具体输入。
做法提示
第2章里的最坏情形是「16 步全部用最大遗忘」,得到累积对数衰减 −80。注意这个推理用到了一个结构性事实(16-token 小片)——你的目标模块里对应的结构性事实是什么?如果没有,你可能需要先补一个(比如「假设批次大小为 M」)。
硬件那一侧的约束是什么?BF16 的 3.4×10³⁸?还是别的(FP8 的范围?共享内存的大小?Tensor Core 的形状要求?)
做法提示
不同的约束会给出完全不同的答案。注意论文 §4.1.4 说专家权重用 MXFP4、激活用 MXFP8——如果你的目标模块处在量化路径上,那么相关的数值范围是 FP8 甚至 FP4 的,比 BF16 窄得多。这可能才是 SiTU-GLU 那个界 100 的真正来源,值得去查。
如果你的反推得到的边界和论文取的值不一致,你会怎么解释?
做法提示
三种可能,你要能区分:① 你漏掉了一个约束(最常见);② 论文的取值有安全余量(第2章那条链里 e80 相对 BF16 上限就留了约四个数量级的余量,这显然是刻意的);③ 这个值根本不是从数值约束来的,而是调出来的经验值——这也是一个有价值的发现,它意味着论文的「设计」叙事在这一点上是事后合理化的。
能得出 ③ 这个结论并且敢说出来,说明你已经具备独立评估这类工作的能力了。
结语
你现在应该已经不需要这个网站了。
这十六章教的东西,最终可以压缩成三个习惯。第一,读到任何一个设计,先问「它在解决什么压力,又欠下了什么债」——这是第1章的三维度框架,也是第16章第一关的全部内容。第二,读到任何一个数字或结论,先问「这是可验证事实、作者实验,还是设计主张」——这是第1章的证据分级,也是第12章整章的方法论。第三,读到任何一个「我们发现 X 更好」,先问「对照组是什么,缺什么实验」——这是第16章第四关。
这三个习惯与 Kimi K3 无关。下一篇技术报告出来时,你可以用同一套工具把它拆开。