本文由 辛梓煜@词元2号站(www.ciyuanerhao.com)撰写,转载请注明出处。
快速摘要
2026 年 7 月 16 日,月之暗面正式发布并开源 Kimi K3,总参数 2.8 万亿、激活参数 1040 亿、原生 100 万 Token 上下文、自带视觉理解能力,是全球首个开源的 3T 级大模型。它相比 2019 年的 GPT-2(1.24 亿参数)放大了约 22580 倍,但这篇拆解想讲清楚的核心是:K3 的能力跃迁不是靠堆参数堆出来的,而是把过去七年里 KV Cache、线性注意力、DeltaNet、Gated DeltaNet、KDA、MLA、AttnRes、Stable LatentMoE 这 8 条技术路线缝合成一套完整系统。全文覆盖:模型记忆机制的前世今生、KDA 的通道级遗忘设计、KDA 与 Gated MLA 的 3:1 混动架构、分块式 AttnRes 如何解决 93 层深网络的信息稀释、896 个专家的稀疏激活如何控制算力成本,以及训练基建与评测数据。
想看完整拆解,往下翻——我会把每条技术线的原理、代码级逻辑和它解决的问题,用最白话的方式讲清楚。
引言:一个 22580 倍的故事,重点不在倍数
Kimi K3 开源那天,我朋友圈里做 AI 的同行几乎都在转发。倒不是因为它 2.8 万亿的参数有多唬人——参数这种事,数字越大越容易让人麻木。真正让大家坐不住的,是它把"开源模型"这四个字的天花板,直接顶到了 3T 这个量级。
先给一个冷冰冰的对比:2019 年横空出世的 GPT-2 只有 1.24 亿参数,放到今天,K3 一个模型的参数量相当于 22580 个 GPT-2。这中间只隔了七年。如果只看这个倍数,你可能会觉得大模型的发展就是一场无脑军备竞赛——模型越做越大,参数越堆越多,仅此而已。
但我自己把 K3 的 47 页技术报告和社区里的代码级拆解翻完之后,最大的感受恰恰相反:这两万多倍,根本不是堆参数堆出来的。 技术报告里没有任何一个模块是"把原来的东西放大十倍"这么简单。K3 身上同时叠着 KDA 混合线性注意力、注意力残差、极稀疏 MoE、潜在空间压缩、量化感知训练、百万 Token 强化学习基础设施等一整套东西,每一样都是为了解决一个具体的、真实存在的瓶颈。
更有意思的是,如果你把时间线拉长看,会发现 K3 里的几乎所有设计都不是凭空冒出来的。它们是一条从 2019 年一路延伸下来的技术暗线:GPT-2 用 KV Cache 解决重复计算,线性注意力把历史压缩进固定记忆,DeltaNet 让记忆可擦写,Gated DeltaNet 给记忆加遗忘开关,KDA 把遗忘开关拆成几十个独立通道,MLA 定期回原文检索细节,AttnRes 解决深网络的信息稀释,MoE 解决万亿参数的算力成本。每一步都在解决上一步留下的问题。
这篇文章,我就沿着这条线,把 K3 的架构从头到尾拆一遍。不堆术语,不贩卖焦虑,就用大白话把"模型是怎么记住东西的""记忆太多会怎样""K3 是怎么重新设计记忆的"这几个问题讲清楚。你在别处可能已经看过 K3 的新闻稿,但那种"2.8 万亿参数 + 百万上下文"的官方口径,和真正理解它内部怎么运转,是两码事。
(本文是我辛梓煜在词元二号站整理的个人学习实践笔记,基于公开技术报告与社区拆解资料写成,力求把复杂架构讲得连刚入门的朋友也能看懂。)
第一章:大模型是怎么"记住"东西的——从 GPT-2 的 KV Cache 说起
要理解 K3 的架构,得先回到 2019 年,看看大模型最初是怎么工作的。
GPT-2 确立了后来几乎所有大模型的基本工作方式:读入前面已经写好的文字,然后一个 Token、一个 Token 地预测下一个内容。 所谓 Token,你可以粗浅地理解成"词的碎片"——英文单词可能切成子词,中文可能是单字或词。模型每生成一个新 Token,都要参考前面所有 Token 的信息,才能决定下一个字该是什么。
这个"参考前文"的过程,在技术上的名字叫注意力(Attention)。它的逻辑很朴素:当前这个位置,应该重点关注前文里的哪些位置?比如你在写"北京是中国的___",模型会重点关注"北京""中国",然后大概率填出"首都"。
没有缓存的时代:每写一个字,重读一遍全文
最早期的实现非常笨拙。每生成一个新 Token,模型都要把前文的每一个位置重新计算一遍注意力——注意,是全部重新计算。文章越长,这种重复计算就越离谱。你写 1000 个字的文章,最后一个字要回头把前面 999 个字全部再算一遍注意力;写 10000 个字,就是 9999 遍。计算量随上下文长度呈平方级爆炸,这就是业内常说的"二次复杂度"问题。
伪代码可以直观感受一下这个笨办法:
# 朴素实现:每生成一个 token,重新计算整段历史的注意力
def generate_without_cache(context, next_token):
# context: 到目前为止的所有 token
for step in range(gen_length):
# 关键问题:每次都带着全部历史重新算一遍
scores = attention(context + new_tokens) # O(长度) 的计算
new_tokens.append(sample(scores))
return new_tokens
KV Cache:把读过的书签存下来
聪明的工程师很快意识到,没必要每次都重算。注意力计算里有两个核心矩阵:Key(键)和 Value(值)。你可以把 Key 理解成"这段内容的索引标签",Value 是"这段内容本身的信息"。前文每个位置的 K 和 V 算过一次之后就不会变了,完全可以把它们缓存下来,下次直接用。
这就是 KV Cache——大模型里最基础、也最普及的加速手段。
# 带 KV Cache:新 token 只算自己的 K/V,接到缓存后面
if past_kv is not None:
k_past, v_past = past_kv[0], past_kv[1]
k = torch.cat((k_past, k), dim=2) # 把新 K 接到旧 K 后面
v = torch.cat((v_past, v), dim=2) # 把新 V 接到旧 V 后面
past_kv = (k, v) # 更新缓存
KV Cache 解决了重复计算,但代价立刻转移到了另一个地方:显存。缓存会随着上下文不断变长,长到一定程度,显存就成了新的瓶颈。你上下文开得越长,KV Cache 占的显存就越多,推理成本也跟着水涨船高。这也是后来"百万 Token 上下文"这么难做的根本原因之一——不是模型读不了那么长,而是读那么长要付出巨大的显存代价。
下面这张图概括了这条进化链的第一站:
flowchart LR
A[生成新 Token] --> B{有没有 KV Cache?}
B -- 没有 --> C[重算全部历史的 K/V]
B -- 有 --> D[只算当前 Token 的 K/V]
C --> E[计算注意力, 生成下一 Token]
D --> E
E --> F[把新 K/V 存入缓存]
F --> A
可以给个具体的量级感受。假设一个 70 亿参数级别的模型,KV Cache 的显存占用大致可以这样估算:每个 Token 需要为每一层、每个注意力头缓存一份 Key 和 Value,实际占用的显存 = Token 数 × 层数 × 头数 × 每个头的维度 × 每份数据的字节数 × 2(K 和 V 各一份)。粗略算下来,百万 Token 上下文的 KV Cache 可能占用几十 GB 显存——单张高端显卡的显存直接吃掉大半。这也是为什么"长上下文"长期是闭源大厂的核心竞争力:不是别人做不出长上下文,而是把长上下文跑起来的成本实在太高。
我见过不少刚接触大模型的同学误以为"上下文越长模型越聪明",其实不完全对。上下文是模型的"工作台面",台面越大能铺开的东西越多,但每多铺一件东西,都要多付一份显存租金。KV Cache 就是这份租金最直观的体现。
一句话总结第一站:KV Cache 用显存换速度,省了重复计算,却让缓存体积随上下文线性膨胀。 后面所有的记忆机制创新,本质上都在跟这个"缓存膨胀"较劲。
第二章:线性注意力——把历史压进一块固定大小的"记忆"
KV Cache 的膨胀问题,催生了 2020 年前后一批"线性注意力"的研究。思路非常直接:与其把前面每一个 Token 的 K/V 都原样存下来(缓存越长越大),不如把读过的内容压缩进一块固定大小的记忆里,不管上下文多长,这块记忆的大小都不变。
固定大小的记忆是怎么工作的
线性注意力的核心是把"缓存所有历史"改成"维护一个记忆状态 S"。每读到一段新内容,就用新的 Key 和 Value 去更新 S;需要输出时,用当前的 Query 从 S 里读取信息。
k = F.elu(k) + 1 # 对 key 做非线性变换,保证非负
q = F.elu(q) + 1
S, z = cache if cache is not None else (0.0, 0.0)
S = S + k @ v # 新信息写入记忆 S
z = z + k # 累计归一化项
o = q @ S # 从记忆里读信息
denom = q @ z
o_scaled = o / denom # 归一化输出
这里的 S 就是模型维护的那块固定大小记忆。更新规则用一句话说:旧记忆 + 新写入 = 新记忆。无论上下文变成 10 万 Token 还是 100 万 Token,S 的尺寸始终不变,长文本的处理成本被一下子压了下来。
便宜是有代价的:记忆污染
但"什么都往一块固定记忆里塞"很快暴露了问题。所有新信息都不断叠加进同一块记忆里,新旧内容会互相干扰。举个生活的例子:你把每天的待办事项都记在同一张便利贴上,今天的事写上去,昨天的事就被盖住了;但如果你上周三记了一条"周五交房租",这周三又记了一条"周五开会",两条信息在便利贴上重叠,你就分不清周五到底要干嘛了。
技术上的说法叫记忆污染——两个 Token 用相同的 Key 写入不同信息时,新旧内容直接叠加,谁也说不清哪个才是对的。
|
机制 |
记忆方式 |
优点 |
缺点 |
|
KV Cache |
逐 Token 缓存全部历史 |
信息完整、无丢失 |
缓存随上下文膨胀,显存压力大 |
|
线性注意力 |
压缩进固定大小记忆 S |
长文本成本恒定 |
新旧信息互相叠加,记忆污染 |
到这里,模型面临一个两难:KV Cache 记得全但太贵,线性注意力便宜但会串味。 接下来的 DeltaNet 和 Gated DeltaNet,就是冲着"串味"这个问题去的。我在下一章详细展开——那也是理解 Kimi K3 里 KDA 的关键铺垫。
在进入下一章之前,先把这两代机制的定位再钉死一遍:KV Cache 是"工程优化"——模型架构没变,只是把算过的结果缓存起来复用;线性注意力是"架构变革"——模型不再逐 Token 保留历史,而是换成一块持续更新的记忆状态。从 KV Cache 到线性注意力,大模型的"记忆观"发生了一次根本转变:从"存档式记忆"转向"摘要式记忆"。理解了这层转变,后面 DeltaNet 改记忆、Gated DeltaNet 加遗忘、KDA 拆通道,都是在同一套"摘要式记忆"框架里不断精进。这也是为什么我说 K3 的架构是一条线而不是一堆点的原因——每一步都能在前一步里找到明确的动机。
第三章:DeltaNet 与 Gated DeltaNet——让记忆可以擦写,也可以遗忘
线性注意力的"记忆污染"问题,催生了 DeltaNet。它的名字里有"Delta"(差值),因为它的核心思想就是:别再傻乎乎地把新信息直接叠到旧记忆上,先看看旧记忆里已经存了什么,只把真正需要修改的部分写进去。
DeltaNet:按差值更新,而不是按全量叠加
DeltaNet 的更新流程可以拆成三步:
- 读旧:用当前的 Key 从记忆 S 里读出旧位置已有的 Value(记作
v_old); - 算差:计算新信息
v与旧信息v_old之间的差值v - v_old; - 写差:只把差值(经过一个门控系数 β 缩放后)写回记忆。
q = F.normalize(F.silu(q), dim=-1)
k = F.normalize(F.silu(k), dim=-1)
beta = torch.sigmoid(self.w_beta(x)).view(b, 1, t, 1) # 控制修改幅度
S = cache if cache is not None else 0.0
v_old = k @ S # 从旧记忆中读出旧值
u = beta * (v - v_old) # 新旧差值 × 修改幅度
S = S + k.transpose(-1, -2) @ u # 只把修正量写回
o = q @ S
对照线性注意力的"只加不减",DeltaNet 相当于给记忆装上了橡皮擦:同一位置写入新信息时,旧信息会被替换而不是被叠加覆盖。记忆污染的根源被掐断了。
但 DeltaNet 有个工程上的大麻烦:它必须严格按照 Token 顺序一步步串行修改记忆——后一个 Token 的更新依赖前一个 Token 更新完的结果。GPU 这种为并行而生的硬件,最怕的就是串行依赖。为了让它在真正的超大模型里跑起来,研究者把连续 Token 切成小块,让整块内容可以并行处理,2024 年这套机制已经被扩展到 13 亿参数的模型、并在 1000 亿 Token 的数据上完成了训练。
这里我多说一句并行化的原理,因为它直接关系到后来 KDA 的性能表现。串行更新之所以慢,是因为"第 3 个 Token 的更新要用到第 2 个 Token 更新后的状态,第 2 个又依赖第 1 个"——环环相扣。研究者发现,只要把记忆更新拆成"块内并行 + 块间串行"两段:块内部的 Token 可以先并行算出各自的局部贡献,再把整个块的贡献一次性合并进全局记忆。块越大,并行度越高,速度越快;但块太大又会影响数值精度和训练稳定性,所以这是一个需要精细调参的平衡。K3 的 KCP(KDA 上下文并行)就是在这个思路上做的跨设备扩展。
如果用生活经验类比这四代记忆机制:KV Cache 像你把所有聊天记录一字不差地存档(完整但占地方);线性注意力像只记日记摘要(省地方但容易张冠李戴);DeltaNet 像在日记本上用修正液改写旧内容(能改但只能被动地改);Gated DeltaNet 则像定期把整本日记翻出来做一次大扫除(能清但好坏不分,全凭一个总开关)。
Gated DeltaNet:给记忆加一个"遗忘开关"
DeltaNet 能擦掉某一条旧记录,但它缺少一个更高级的能力:主动遗忘。它只能在"新信息来了"的时候被动修改对应位置,没法主动清理一批已经过时的内容。
举个具体的例子:你跟模型聊了半天的旅游攻略,它记下了你订的酒店、机票、景点清单;话题突然转到公司财报,这些旅游信息其实已经没用了,但 DeltaNet 只能干等着——等到财报里出现"酒店""机票"这类相关的新内容,才逐条把它们替换掉。在那之前,这些过时信息会一直占着记忆空间,还可能干扰新信息的读取。
Gated DeltaNet 的解法是在更新公式里加了一个门控系数 alpha,作用相当于"遗忘开关":
S = cache if cache is not None else 0.0
alpha = gate(x) # 控制旧记忆的保留比例
beta = torch.sigmoid(self.w_beta(x)) # 控制本次修改的幅度
v_old = k @ S
u = beta * (v - v_old)
S = alpha * S + k.transpose(-1, -2) @ u # 先衰减旧记忆,再写入修正
o = q @ S
关键变化就在最后两行:先让旧记忆整体乘一个 alpha 衰减,再把新修正量写进去。 alpha 接近 1,旧记忆大部分保留;alpha 接近 0,旧记忆被大幅削弱,给新内容腾地方。
到这里,模型的记忆系统已经集齐了三种能力:写入、修改、遗忘。
演进脉络一览
|
机制 |
新增能力 |
一句话总结 |
遗留问题 |
|
KV Cache |
完整缓存 |
记得全,但太贵 |
缓存随上下文膨胀 |
|
线性注意力 |
固定记忆 |
便宜,但会串味 |
记忆污染 |
|
DeltaNet |
可擦写 |
只写差值,替换旧值 |
串行计算慢、无主动遗忘 |
|
Gated DeltaNet |
可遗忘 |
加遗忘开关,整体衰减旧记忆 |
遗忘是"一刀切",无法精确到具体内容 |
Gated DeltaNet 的短板:一刀切的遗忘不够用
Gated DeltaNet 的遗忘开关有个先天局限:它只能统一削弱整块记忆,没法精确控制哪些信息该留、哪些该忘。 一个 alpha 管所有记忆通道,要么都衰减,要么都保留。
当模型开始处理百万 Token 级别的上下文、开始扮演需要连续工作数小时的 Agent(智能体)时,这种"一刀切"就露馅了。想想看:一份财报分析任务里,公司名称、财年、营收数字这些关键信息必须长期保留,而中间出现的临时性表述、重复的过渡句可以尽早忘掉——这两种信息需要的遗忘策略完全不同。一个全局的 alpha 根本做不到这种精细管理。
也正是这个短板,直接催生了 Kimi 的 KDA。下一章我们进入正题,看看月之暗面是怎么重新设计这套记忆系统的。
第四章:KDA——Kimi 把"一刀切"的遗忘,拆成了几十个独立开关
Gated DeltaNet 的"一刀切遗忘"不够用了,Kimi 就在这个基础上提出了自己的方案:Kimi Delta Attention,简称 KDA。它最大的变化,是把原来那个统一控制遗忘的 alpha,从"一个数"升级成了"一个对角矩阵"。
从 alpha 到 Diag(alpha):每个记忆通道都有自己的开关
先解释一下什么叫"记忆通道"。线性注意力家族的记忆 S 是一个多维矩阵,你可以把它想象成一个多层书架:每一层(每个维度)存放着不同类型的信息特征。Gated DeltaNet 用一个 alpha 控制整个书架的新旧更替,要么整架书一起换,要么一起留着。
KDA 的做法是:给书架的每一层都配一个独立的 alpha 开关,写作 Diag(alpha)——对角线矩阵,每个对角元素控制一条通道的遗忘速度。
# Gated DeltaNet:全局单一遗忘系数
alpha = gate(x) # 一个标量,管所有通道
S = alpha * S + k.T @ u
# KDA:逐通道遗忘系数
alpha = gate(x) # 形状为 (B, T, C) 的向量
S = alpha.unsqueeze(-1) * S + k.T @ u # 每个通道各自衰减
这样一来,模型可以让一部分记忆快速淡出,同时让另一部分记忆保存很久。回到财报那个例子:公司名称、财年、核心财务指标走"慢遗忘通道",长期保留;临时性表达、重复的过渡信息走"快遗忘通道",迅速清理。模型开始具备真正精细的记忆管理能力——它不只知道什么时候该忘,还知道具体该忘掉什么。
这个设计在直觉上非常像人脑的记忆机制:我们不会用同一个"遗忘速率"去处理所有记忆——电话号码和早餐吃了什么,遗忘速度完全不同。KDA 不过是把这个常识用工程手段实现了:给记忆矩阵的每个通道配一个可学习的门控值,门控值由当前输入动态计算,模型在训练中自动学会"哪些维度该留、哪些维度该丢"。
补充一个技术细节:KDA 的门控不是每层共享一套,而是每层、每个头、每个通道都有自己的门控参数,由输入通过一个小网络(代码里的 gate(x))实时算出来。这意味着遗忘策略是上下文相关的——同样一段"酒店预订信息",在旅游话题里走慢遗忘通道,在话题切到财报后,相关门控会自动收紧,让这些信息快速淡出。这就是"主动遗忘"和"被动替换"的本质区别:前者是模型自己判断该忘,后者是等着新信息来覆盖。
Kimi 官方的实验显示,这种设计在部分任务上的效果可以超过传统的完整注意力,同时还能够明显提高长文本生成速度。
门控机制的演进对比
|
机制 |
遗忘控制粒度 |
控制方式 |
能力边界 |
|
Gated DeltaNet |
整块记忆统一控制 |
单一标量 alpha |
只能整体衰减,无法精确取舍 |
|
KDA |
逐通道独立控制 |
对角矩阵 Diag(alpha) |
不同信息不同保鲜期,精细管理 |
Kimi Linear:先在小模型上验证 KDA
架构创新不能纸上谈兵。为了验证 KDA 在大规模场景下的真实效果,Kimi 团队先训练了一个验证模型——Kimi Linear:总参数 480 亿,每次只激活 30 亿参数,属于大规模 MoE 模型的探路阶段。
Kimi Linear 的验证结果相当亮眼:
- 在 100 万 Token 上下文下,KV Cache 占用最多可以减少 75%;
- 解码吞吐量最高达到传统完整注意力的 6 倍。
KV Cache 减少 75% 意味着什么?同样一块 GPU,原本只能同时服务 4 个百万级上下文的请求,现在可以服务 16 个;或者反过来,原来要 4 张卡才能放下的缓存,现在 1 张卡就够了。这直接关系到长上下文推理的单次成本和并发能力,是影响大模型服务可用性的硬指标。
KDA 的先天限制:压缩必然丢细节
不过 KDA 再强,也有一个绕不开的先天限制:它本质上还是在做压缩——把海量历史信息压进固定大小的记忆里。只要压缩,就一定会丢细节。
模型可能记住了一段话的"大意",但未必能准确找回其中的某个具体数字、一行代码、或者一句原文。你问它"刚才那份合同第 47 页第 3 条写的赔偿金额是多少",它可能知道"第 47 条有赔偿条款",但具体数字可能已经被压缩过程磨损掉了。
这个限制决定了 KDA 不能单打独斗。Kimi 的解法是:让 KDA 和传统的完整注意力机制配合工作——这正是 K3 架构里最精彩的设计之一,我们下一章细说。
第五章:KDA + MLA 混合架构——压缩记忆与精确检索的分工
K3 的思路可以概括成一句话:让 KDA 负责"记住大意",让 MLA 负责"找回细节"。
先认识 MLA
MLA 全称 Multi-head Latent Attention(多头潜在注意力),最早是 DeepSeek-V2 提出的机制。它的核心思路是:把 Key 和 Value 先压缩进一个低维的"潜在空间",需要时再还原成每个注意力头的投影。这么做的直接收益和 KDA 类似——大幅压缩 KV Cache,降低长上下文的内存开销。
MLA 的实现细节可以这样理解:传统注意力要为每个头单独存一份 K/V 缓存,头越多缓存越大;MLA 则先把所有头的 K/V 压缩成一个共享的低秩潜在表示,只缓存这份"浓缩版",做注意力计算时再按需还原成每个头的投影。这就像公司里所有部门共用一份"核心数据库",而不是每个部门各自复制一份完整档案——存储省了,需要时随时查。正因为 MLA 是完整注意力,它在还原之后拥有访问全部原始 Key 的能力,可以做精确的逐位置检索,这是它和"压缩成摘要"的 KDA 本质不同的地方。
MLA 属于完整的注意力机制(full attention),它不像 KDA 那样把历史压成摘要,而是保留完整的上下文信息,需要的时候做一次精确的全局检索。在 K3 里,MLA 层还有个特别之处:完全去掉了位置编码(NoPE)。因为旁边的 KDA 层本身就是循环结构、天然带有位置敏感性和"越近越清晰"的时间衰减特性,MLA 不再需要额外注入位置信息——省掉位置编码还能减少一层数值干扰,这在 93 层深网络里算是"蚊子腿也是肉"的优化。
3 层 KDA + 1 层 Gated MLA:K3 的基本循环
K3 的注意力主干不是清一色一种层,而是按固定比例混搭:
基本循环单元(重复 23 次):
┌─────────────────────────────────┐
│ KDA 层 ──► KDA 层 ──► KDA 层 │ ← 3 层线性注意力,负责低成本吃下长上下文
│ Gated MLA 层 │ ← 1 层完整注意力,负责精确检索细节
└─────────────────────────────────┘
23 个循环单元 × 4 层 = 92 层,再加最上面一层额外的 Gated MLA,K3 一共 93 个解码器层,其中 69 层是 KDA、24 层是 Gated MLA。这个 3:1 的比例不是拍脑袋定的,技术报告的消融实验显示:3:1 是"模型质量"和"解码吞吐"之间最好的平衡点。KDA 太多,细节丢失会拖累质量;MLA 太多,长上下文成本又压不下来。
一个会议纪要的类比
KDA 和 MLA 的分工,特别像职场里"看纪要"和"查原文"两种工作方式:
- KDA 就像你平时只翻整理好的会议纪要,快速掌握整体情况和主要结论。它快、省事,适合处理日常大部分信息流。
- MLA 就像当你需要确认某个具体数据时,打开原始会议记录逐字查证。它慢、贵,但保证精确。
一个管"广度与速度",一个管"深度与精确",互相补位。
为什么不能只用一种?
如果只用 KDA:上下文处理成本很低,但压缩导致细节丢失,代码里的一行关键逻辑、合同里的一个数字都可能记不准,这对编程和知识工作来说是致命的。
如果只用 MLA:细节是保住了,但百万 Token 上下文的 KV Cache 成本会让推理贵到无法商用,解码速度也上不去。
|
方案 |
长上下文成本 |
细节还原能力 |
解码速度 |
结论 |
|
纯 KDA |
极低 |
弱(压缩丢细节) |
快 |
省但糙 |
|
纯 MLA |
极高 |
强(完整保留) |
慢 |
准但贵 |
|
3:1 混合 |
低 |
强(KDA 记大意 + MLA 查细节) |
快 |
平衡最优 |
到这里,模型已经解决了"如何在百万 Token 里保存并找回信息"的横轴问题。但还有一个纵轴问题没解决:这些信息进入模型之后,要在 93 层网络里逐层传递,越传越淡,早期的重要信息怎么保住? 这就是下一章 AttnRes 要解决的问题。
第六章:AttnRes——93 层深网络里,如何找回早期信息
KDA 和 MLA 解决了"信息怎么存、怎么取"的问题,但还有一道坎:信息在 93 层网络里逐层传递时,会不断被稀释。
残差连接与信息稀释
传统 Transformer 靠残差连接传递层间信息:每经过一层,模型把这一层新算出来的结果,直接加到之前的累加结果上。用大白话说,就是"每一层都在已有的底稿上继续写",新内容盖在旧内容上面。
问题在于:层数越深,早期层提取出来的特征(比如"这是一个数学题,公式在第三行")就越是沉在底下,被后面几十层的海量新信息冲淡。93 层的 K3 比上一代 K2(61 层)深了 52%,层数越多,这种"稀释"就越严重。
打个比方:你在纸上写了一行关键信息,然后又在同一张纸上密密麻麻叠写了 92 层内容,最后还能看清最初那行字吗?基本不可能。
AttnRes:给每层一个"回看"的通道
Kimi 的解法叫 AttnRes(Attention Residuals,注意力残差):不再让每一层被动地"接收前一层的结果",而是允许每一层主动决定——我该从前面哪些阶段的输出里读取信息、各读多少。
它的实现逻辑是:把前面各网络块的输出收集起来,用注意力机制计算当前层对每个历史块"感兴趣"的程度,再按权重把历史信息重新组合回来。处理数学题时,后面的层可以重新关注早期层识别出的公式和条件;处理长文档时,可以找回前面已经提炼出的主题和关键事实。
我用一个更生活化的比喻帮你建立直觉。假设你是一个写长篇小说的作者,写到第 90 章的时候,突然要回头呼应第 3 章埋下的一个伏笔。传统残差连接相当于:你只能通过"记忆"(累加的结果)模糊记得前面写过什么,而且越往后记忆越淡。AttnRes 则相当于:你手边有一排按章节归档的资料夹,每写一章都可以随时翻开任何一个资料夹查证——第 3 章埋的伏笔、第 40 章的人物关系、第 70 章的时间线,想看哪章翻哪章,按需取用。分块式 AttnRes 则是把这些资料夹按"每 12 章合并成一册"的方式归档,翻起来更快,不用 93 本逐一去找。
有人可能会问:既然 AttnRes 这么好,为什么以前没人用?其实残差网络领域一直有类似探索(比如 DenseNet 的密集连接思路),但问题在于成本——每一层都回看所有历史层,计算量是二次方级别的,小模型勉强能跑,93 层的大模型根本扛不住。K3 的贡献在于把它工程化:分块、降维、加权重选择,把"全量回看"变成"块级选择",成本从不可行压到了可接受。
分块式 AttnRes:控制检索成本
如果每一层都重新检索此前所有 92 层的结果,成本会高得离谱——注意力的注意力,计算量二次爆炸。Kimi 做了一个工程上的聪明妥协:分块。
每经过 12 个解码器层,就把这一段网络产生的中间结果整理成一个"信息块";后续层需要回看时,只需要在这几个信息块之间做选择,不必逐层翻历史。93 层网络最终被划分成 8 个 AttnRes 信息块。
# 分块式 AttnRes 的核心检索逻辑(伪代码)
V = torch.stack(blocks + [partial_block]) # 收集前面各块 + 当前块
K = norm(V) # 归一化后作为检索索引
logits = torch.einsum('d, n b t d -> n b t',
proj.weight.squeeze(), K) # 计算每个块的关注分数
h = torch.einsum('n b t, n b t d -> b t d',
logits.softmax(0), V) # 按权重重组历史信息
return h
代码里的 V 保存了此前各信息块和当前块的结果,logits 计算每个块对当前任务的重要程度,经过 softmax 变成权重,模型再按权重重新组合前面各层的信息。8 个块的检索成本,远低于 93 层的逐层检索。
两个方向的信息丢失,两个解法
到这里,K3 的信息流难题就凑齐了完整的答案:
|
问题方向 |
根因 |
解法 |
机制 |
|
横轴:上下文太长 |
压缩记忆丢细节 |
MLA 定期回原文精确检索 |
3:1 混动架构 |
|
纵轴:网络太深 |
残差累加冲淡早期信息 |
AttnRes 分块回看早期层 |
8 个信息块 + 注意力权重 |
MLA 管"从前文找回细节",AttnRes 管"从深层网络里找回早期结果"。两个方向都堵上了,信息在 K3 里才算真正"流动得顺畅"——这也是 K3 官方技术报告里反复强调的那句话:KDA 和 AttnRes 都是为了让信息在更长序列和更深模型里流动得更顺畅。
第七章:Stable LatentMoE——2.8 万亿参数,怎么把算力成本压下来
2.8 万亿参数是个什么概念?如果每个参数都是"跑一遍全量计算",那地球上没有任何一家公司能负担 K3 的推理成本。所以 K3 必须回答一个尖锐的问题:参数这么多,凭什么每次推理只花一点点算力?
答案是 MoE——混合专