Kimi K3模型架构详解:KDA、Attention Residuals与Stable LatentMoE如何协同
本文最后更新于 2026年9月29日 下午
版本说明:本文基于Moonshot AI官方Kimi K3技术报告、官方仓库
3cb39df(报告更新于2026-08-06)、官方技术博客和部署文档整理;张量shape与prefill/decode执行路径额外对照vLLM main提交f4b161d7(2026-08-18)。架构、训练和系统数据来自官方报告;标记为“vLLM实现”的内容是当前后端与checkpoint支持,不等于模型数学只允许这一种kernel;带有“估算”“教学示例”字样的内容则是本文推导。本文没有复现2.8T模型训练、1M上下文评测或多机推理。
1. 先说结论
Kimi K3不是简单地把Kimi K2放大到2.8T参数。它的核心思路是沿三个方向分别解决信息流和计算成本问题:
- 序列方向:用69层Kimi Delta Attention(KDA)把长历史压进固定大小的递归状态,每隔三层插入一层Gated MLA恢复全局token-to-token检索能力。
- 深度方向:用Attention Residuals(AttnRes)让当前层从embedding和前面多个block的输出中选择信息,而不是把所有历史层都压进一条普通残差流。
- 宽度方向:用Stable LatentMoE让896个routed expert在3584维latent空间工作,每个token只激活16个;两个shared expert继续走7168维全宽路径。
- 模态方向:用从零训练的401M参数MoonViT-V2把图像和视频映射进同一个token流,从预训练开始就与语言骨干联合优化。
- 部署方向:只把占参数大头的MoE expert权重量化为MXFP4,expert输入激活使用MXFP8;KDA state、MLA latent KV、AttnRes状态和MoE通信则分别设计专用缓存与kernel。
官方给出的模型规模是2.78T总参数、104.2B激活参数、93层、1,048,576 token上下文。这里必须先澄清三个最容易被误读的数字:
- 2.8T不是每个token都计算2.8T参数。MoE每层只选择16/896个routed expert,并始终运行2个shared expert;官方口径下每token激活约104B参数。
- 1M上下文不意味着每层都做1M长度的softmax attention。四分之三的attention层是线性递归KDA,只有四分之一左右是全局MLA。
- “相对Kimi K2约2.5倍scaling efficiency”不是推理吞吐提升2.5倍。它表示官方拟合的缩放律中,达到相同验证损失所需的训练计算量约降到原来的1/2.5,是架构、数据和训练recipe的合并结果。
一句话概括:
Kimi K3用KDA压缩时间轴,用AttnRes打开深度轴,用LatentMoE稀疏扩展宽度轴,再用周期性MLA弥补递归状态的全局检索上限。
2. 先建立整机视图
2.1 关键配置
| 项目 | Kimi K3公开配置 | 含义 |
|---|---|---|
| 总参数 | 2.78T,通常写作2.8T | 决定权重存储、加载和跨设备切分难度 |
| 激活参数 | 104.2B,通常写作104B | 更接近单token前向计算规模,但不等于精确FLOPs |
| Transformer层 | 93 | 69层KDA + 24层Gated MLA |
| Dense层 | 1 | 其余层使用MoE通道混合 |
| 模型宽度 | 7168 | 主干hidden state维度 |
| Attention heads | 96 | vLLM实现中KDA head dim为128;MLA的QK/V head dim为192/128 |
| MLA latent | Q 1536;KV 576 | MLA使用NoPE,KV cache每token每层保存576个latent元素 |
| LatentMoE宽度 | 3584 | routed path只有主干宽度的一半 |
| Expert hidden | 3072 | 每个expert的中间维度 |
| Routed experts | 896 | 每层可选专家池 |
| 每token选择 | 16 | 稀疏度为 |
| Shared experts | 2 | 每个token都会经过的全宽公共路径 |
| 词表 | 160K | 与Kimi K2相同 |
| 上下文 | 1,048,576 | 通过NoPE、KDA和渐进长上下文训练实现 |
| 激活函数 | SiTU-GLU | 对SwiGLU两条乘法支路做平滑限幅 |
| 视觉编码器 | MoonViT-V2,401M,27层 | 从零开始与LLM联合训练 |
| 部署量化 | expert权重MXFP4,expert激活MXFP8 | 非expert模块保留更高精度 |
| MTP | 1层 | 后训练为EAGLE-3风格draft model |
2.2 93层是怎样排出来的
K3重复23次“3层KDA + 1层Gated MLA”:
骨干末尾再放一层Gated MLA,保证最终输出前一定经过全局attention:
每个attention层后都有通道混合模块;配置表列出1个Dense层,其余约92层使用Stable LatentMoE。为了描述AttnRes,93层又按每12层组成一个深度block,共8个block,最后一个不满12层;再把embedding算作一个可检索源,一共有9个block级来源。
flowchart TD
I[文本 token] --> E[共享 embedding 空间]
V[图像或视频] --> M[MoonViT-V2]
M --> P[MLP projector]
P --> E
E --> AR[Block Attention Residuals]
AR --> K1[KDA + Stable LatentMoE]
K1 --> K2[KDA + Stable LatentMoE]
K2 --> K3[KDA + Stable LatentMoE]
K3 --> G[Gated MLA + Stable LatentMoE]
G --> R[重复23个混合attention block]
R --> F[最终Gated MLA + Stable LatentMoE]
F --> O[LM head / 输出]
这个图里有两个不同含义的“block”:
- hybrid attention block是
3×KDA + 1×MLA,描述序列混合方式。 - AttnRes block是最多12个Transformer层,描述深度方向的信息聚合和缓存单位。
不要把二者混为一谈。
2.3 一层的真实数据流
对任意一层,输入和输出都回到主干宽度7168,但中间并不是“attention加一个FFN”这么简单。Block AttnRes在attention前和MoE前各执行一次,而且两次使用独立的7168维打分向量:
embedding / 已完成depth block / 当前block部分和
-> Block AttnRes [T,7168]
-> KDA或Gated MLA [T,7168]
-> 再一次Block AttnRes [T,7168]
-> Dense MLP或Stable LatentMoE [T,7168]
-> 输出作为当前depth block的增量这里的T是vLLM当前调度轮pack后的token总数,不一定能写成整齐的batch × sequence。下表先把生产checkpoint的关键shape放在一起,后文再逐项推导:
| 模块 | 关键中间shape | 跨decode step保存什么 |
|---|---|---|
| KDA | Q/K/V/Gate [T,96,128];state [96,128,128] |
每层的Conv4历史与最终recurrent state |
| Gated MLA | Q latent 1536;KV latent 576;QK/V head dim 192/128 | 每层每token的576维latent page |
| Block AttnRes | block bank [T,8,7168],另有当前block prefix |
不保存;它是当前forward的深度activation |
| Stable LatentMoE | router [T,896];down [T,3584];top-16 |
不保存请求cache;expert权重是模型常驻参数 |
TP=8时,96个attention head被切为每rank 12个。本文凡是没有写TP的shape,默认描述全局逻辑张量;不能直接拿它乘dtype当作单卡显存。
3. KDA:把无限增长的历史变成固定大小的状态
3.1 从普通attention的成本说起
标准causal attention在decode第个token时,需要读取之前个token的K/V。即使使用FlashAttention和PagedAttention,它仍然有两个随长度增长的事实:
- 每个请求的KV Cache容量近似为。
- decode每一步读取历史KV的工作近似为。
当上下文来到1M token,这种增长会同时压缩并发数、增加HBM流量,并让跨节点缓存迁移变得昂贵。
线性attention的另一条路线是把历史压成固定大小矩阵:
这样decode只保留,不再保留每个历史token的完整K/V。但简单累加会不断覆盖、污染旧记忆,也缺少可控遗忘。KDA在此基础上加入delta rule和逐key-channel forget gate。
3.2 KDA的递推式
对单个head,令:
- ;
- ;
- ;
- 是逐key channel保留率;
- 是当前token的写入强度。
先对旧状态逐channel衰减,再做delta-rule写入,可以写成:
等价地:
最终读取:
直觉可以分成四步:
- 决定旧记忆的不同key channel保留多少。
- 用当前从旧状态读出预测值。
- 只把真实与预测值的误差写回,而不是无条件叠加。
- 当前再从更新后的状态读取输出。
delta rule的重要意义是“写入纠错”:如果当前对应的内容已经被状态准确表示,就少写;如果预测错误,就沿方向修正状态。
3.3 从X:[T,7168]走到KDA输出
当前vLLM的K3 checkpoint支持给出了报告摘要表中没有展开的内部维度:96 heads,key/value head dim都是128,ShortConv kernel size为4。对一轮packed token输入,第一批线性投影产生:
X [T,7168]
├─ Wq,Wk,Wv → Qraw,Kraw,Vraw [T,96,128]
├─ Wg → G² [T,96,128]
├─ Wf_a → F_a [T,128]
└─ Wβ → β_raw [T,96]
F_b = Wf_b(F_a) [T,96,128]TP=8时每个rank持有12 heads,所以Q/K/V/G²分别是[T,12,128],复制为[T,128],为[T,12]。Q/K/V随后分别经过宽度4的causal depthwise convolution和Swish;Q/K再做L2 normalization:
这意味着请求cache不只有。每个Q/K/V channel还要保留最近3个卷积输入:全局逻辑shape为
TP=8时每rank是[4608,3]。单head的recurrent state是[128,128],全局96 heads为[96,128,128],TP=8时每rank为[12,128,128]。
decay与写入门的具体映射为:
把这些量代入上一节的delta rule后,每head得到。最后做head-wise RMSNorm、full-rank gate与输出投影:
相对Kimi Linear,K3把低秩输出门换成full-rank投影,使每个token、head、channel都能独立控制递归读出的通过量。代价是这条投影本身更大,decode必须依靠packed projection与融合kernel减少访存和launch开销。
3.4 为什么要给decay设置下界
KDA训练和prefill不能真的逐token串行跑。官方采用chunkwise算法:chunk之间传递,chunk内部把token交互改写成带因果下三角mask的矩阵乘。
问题出在累计衰减。设一个chunk内从位置到的累计保留率为:
并行改写里会出现。如果单步可以无限接近0,多个token相乘后会下溢,而倒数会溢出。Kimi Linear需要在log space计算,并对16-token对角tile走显式position-pair路径;这条路径难以充分使用Tensor Core。
K3把log-decay改为有界映射:
因此:
16个token tile的累计log-decay落在,倒数小于,仍在BF16动态范围内。于是对角和非对角causal tile都能转为dense Tensor Core GEMM。
这是一个典型的算法与kernel共同设计:数学上限制遗忘门的最小值,换来数值范围可控和统一的Tensor Core执行路径。
3.5 Chunkwise KDA到底并行了什么
flowchart LR
C0[Chunk 0 token并行] -->|输出边界state S1| C1[Chunk 1 token并行]
C1 -->|输出边界state S2| C2[Chunk 2 token并行]
C2 -->|输出边界state S3| C3[Chunk 3 token并行]
P0[chunk内QK与causal GEMM] -.与state传播重叠.-> C0
P1[chunk内QK与causal GEMM] -.与state传播重叠.-> C1
它不是消除了递推依赖,而是把依赖提升到chunk边界:
- chunk内部:并行计算token间的intra-chunk贡献。
- chunk之间:按顺序传递固定大小的recurrent state。
- FlashKDA:把token-parallel阶段与head-parallel recurrence重叠,减少串行阶段让SM空闲的时间。
3.6 跨卡KDA Context Parallelism
普通softmax attention做context parallel时,需要交换随序列长度增长的KV block。KDA只需交换固定大小状态,但它的状态更新不是简单相加。
对一个序列片段,KDA的作用可以抽象成仿射变换:
其中:
- 表示这个片段对进入状态的累计衰减与修正;
- 表示从零状态开始时,该片段自己产生的状态。
不同rank可以先独立算出自己的,再通过all-gather和prefix scan按顺序组合。仿射变换的组合满足结合律:
所以KCP同步的是固定大小片段摘要,而不是长度为的KV序列。官方报告称这实现了线性compute scaling。
3.7 固定大小不等于没有成本
KDA state对序列长度是,但对层数、head数和仍然很大:
官方报告摘要表没有列出head内部维度,但当前vLLM checkpoint支持显示。即使如此,也不能只用全局shape直接宣称“每请求占多少GB”:真实字节数还依赖TP/PP、state dtype、对齐方式、page分配以及是否启用RecoverSSM。工程上应避免把“常数状态”误解为“可以忽略的状态”。
4. Gated MLA:周期性恢复全局内容检索
4.1 MLA缓存的不是完整多头K/V
Multi-head Latent Attention不是直接把7168维输入投成96份K/V。当前vLLM K3实现的前端投影为:
X [T,7168]
└─ fused_qkv_a_proj → [q_c | c] [T,1536+576=2112]
q_c = RMSNorm(q_c) [T,1536]
c = RMSNorm(c) [T,576] ← 持久cache
q = W_QB q_c [T,96,192]
[k_h;v_h] = W_KVB c [T,96,192+128]所以K3的生产维度是q_lora_rank=1536、kv_lora_rank=576、qk_nope_head_dim=192、qk_rope_head_dim=0、v_head_dim=128。prefill可以临时物化
K [T,96,192]
V [T,96,128]来运行并行causal attention,但持久cache只写c:[T,576]。若拿96-head完整MHA作为基线,它每token每层需保存个K/V元素,而MLA保存576个,元素数约缩小53.3倍;与GQA等其他基线相比倍率会不同。
K3的MLA与K2/K2.5有两个关键差异:
- 所有MLA层都使用NoPE,query和key不加显式位置编码。
- 输出增加full-rank channel gate:
位置和近因信息主要由中间的KDA递推携带,MLA负责不受限的全局内容交互。没有RoPE也意味着扩展到1M时不需要重调RoPE base或使用YaRN插值。
4.2 decode为什么不重建所有历史K/V
令每个head的KV up-projection拆成
原始分数可以利用矩阵结合律把吸收到query侧,把放到attention输出侧:
vLLM在模型加载后的weight processing阶段准备W_UK_T和W_UV。plain decode的数据流是BMM1把[B,96,192] query变成[B,96,576],在paged latent cache上做MQA,再用BMM2升回[B,96,128]。这样不会为所有历史token临时展开96份K/V。
4.3 为什么不能全用KDA
固定大小必然是对历史的有损压缩。序列越长,需要被同一个状态表达的事实、代码符号、视觉元素和工具轨迹越多。递推模型擅长持续更新和局部/近因模式,但很难保证任意早期细节都能按内容精确取回。
全局MLA保留逐token latent,使当前token能直接和任意历史位置做内容匹配。K3用3:1比例进行职责分工:
| 维度 | KDA | Gated MLA |
|---|---|---|
| 历史表示 | 固定大小递归矩阵 | 随token增长的latent序列 |
| decode状态增长 | ||
| 单步历史读取 | 与无关 | 随增长 |
| 信息性质 | 压缩、递归、带遗忘 | 全局、逐位置、内容寻址 |
| 位置机制 | decay、gate与递推隐式编码 | NoPE,由KDA提供位置敏感上下文 |
| 主要作用 | 低成本长程状态传播 | 周期性恢复高容量全局交互 |
flowchart LR
T0[长序列输入] --> K1[KDA: 压缩并更新state]
K1 --> K2[KDA: 压缩并更新state]
K2 --> K3[KDA: 压缩并更新state]
K3 --> M1[MLA: 查询所有历史latent]
M1 --> K4[KDA]
K4 --> K5[KDA]
K5 --> K6[KDA]
K6 --> M2[MLA: 再次全局校正]
4.4 训练kernel里的一个精度细节
报告指出,FlashAttention存在有偏舍入误差,因此K3训练时把attention output保留为FP32。FP32输出tile会把片上空间翻倍,官方kernel没有简单接受这个成本,而是让输出tile与KV staging buffer重叠复用shared memory,释放空间以加深KV pipeline。
这说明“模型使用低精度训练”并不代表所有中间量都一刀切到低精度;误差敏感位置仍可能保留FP32。
5. Attention Residuals:在深度方向做attention
5.1 普通残差为什么像深度方向的RNN
标准Transformer层近似为:
第层能看到的所有早期信息都被压进单个。这和RNN把整个时间历史压进一个state很相似:路径短、实现简单,但当前层不能直接选择“我要embedding、第三个block还是第七个block的表示”。
AttnRes把attention的思想旋转90度:token位置不变,在layer depth上选择历史表示。
5.2 Full AttnRes
对第层,定义可学习pseudo-query:
embedding和每个前序层输出作为key/value,key先做RMSNorm:
这里有两个细节:
- pseudo-query是每层学习的参数,不是为每个token临时生成的query。
- attention weight仍然依赖当前token在各历史层的表示,因为key/value随token而变。
网络深度不到100,所以算术不是最大问题;真正昂贵的是保留所有层输出需要activation memory,并且pipeline parallel时要跨stage传这些表示。
5.3 Block AttnRes
K3不保留全部93层输出,而是每12层聚合成一个block representation。第个block内部维护部分和,跨block只对这些聚合表示做attention:
flowchart LR
E[Embedding b0] --> A[深度attention]
B1[Block 1聚合 b1] --> A
B2[Block 2聚合 b2] --> A
B3[Block 3聚合 b3] --> A
BN[当前block内部分和] --> A
A --> L[当前层输入]
对当前token,把已提交的depth block与当前block的running prefix记为
第个子层用自己的学习向量打分:
vLLM在每层attention前和MLP前各调用一次attn_res,两次各有一个7168维score projection。其block bank分配为[T,8,7168],当前block prefix单独为[T,7168];kernel把prefix delta更新、必要的block写入、online softmax混合以及下一步RMSNorm尽量融合。
这样把保存与通信开销从降为。K3采用8个最多12层的block,加上embedding一共9个来源。block内的顺序部分和与block间的并行attention用online softmax合并。
推理实现也围绕内存流量优化:
- prefill对activation使用sequence parallel,避免每个TP rank都复制block representation。
- decode把inter-block kernel放到side stream,与主stream的独立计算重叠。
- intra-block合并、partial-sum更新和后续RMSNorm融合进前一个TP all-reduce。
AttnRes不是“免费跳连”。它用更直接的深度检索换来额外activation状态、内存读取和并行实现复杂度。
还有一个很容易写错的生命周期:AttnRes bank只属于当前forward。prefill的[T,8,7168]在93层之间传递,forward完成后不会像MLA latent或KDA state那样留给下一个decode token;新token会在重新穿过93层时建立自己的depth bank。AttnRes解决的是层深方向的信息路由,不是token时间轴上的历史cache。
6. Stable LatentMoE:896个专家为什么还能选16个
6.1 先把full width和routed width分开
普通MoE把7168维token发送给每个被选expert。如果expert池和top-k同时增大,dispatch通信、expert输入流量和权重读取都会变重。
LatentMoE把路径拆为:
- 两个shared expert直接处理,承载通用变换。
- routed path先用把压到。
- router从896个latent expert中选择16个。
- 聚合后做RMSNorm,再用升回7168维。
先降到一半宽度,top-16才不会把16份7168维通信和expert计算直接压到系统上。
把这一层按真实shape展开:
X [T,7168]
├─ router logits(FP32) [T,896]
│ └─ Top-16 ids / normalized weights [T,16] / [T,16]
├─ shared experts [T,7168]
└─ W_down [T,3584]
└─ dispatch到16个expert,逻辑shape [T×16,3584]
├─ expert gate/up [T×16,3072] 各一份
├─ SiTU-GLU [T×16,3072]
└─ expert down [T×16,3584]
└─ weighted combine [T,3584]
└─ RMSNorm + W_up [T,7168]
Y = Y_shared + Y_routed [T,7168]shared path中的两个always-on expert可合并为中间宽度的全宽MLP。它不经过3584维bottleneck,负责所有token都需要的通用变换;router仍读取7168维,只有被派发给routed expert的数据降到3584维。
6.2 2.8T参数主要在哪里
把一个routed expert粗略视为GLU的三块矩阵,忽略bias和其他小项,则单expert参数约为:
每个MoE层的896个routed expert约为:
如果约92个MoE层都采用这个结构,仅routed expert粗估就约为:
这只是根据公开维度做的量级估算,不是官方逐tensor参数清单,但它解释了2.78T从哪里来:绝大多数参数在每层896个latent expert的权重里。
对一个token,routed path只激活16个expert,单层相关expert权重粗估为:
再加两个shared expert、attention、router、latent projection、embedding和其他dense模块,才构成官方104.2B activated parameters口径。不能把528M乘层数直接当成精确激活参数,因为shared expert内部维度、首个dense层和各attention投影也必须计入。
6.3 为什么原始LatentMoE在这个规模会不稳定
K3把expert池扩大到896,且每token选择16个。官方观察到两类问题:
- routed path形成 gated expert FFN ,接近连续四次矩阵乘,内部activation容易爆炸。
- 近千个expert让原有auxiliary-loss-free动态bias难以及时平衡负载,可能出现过热expert和dying expert。
Stable LatentMoE用了三个修复。
6.4 修复一:up-projection前做RMSNorm
不同token选到不同expert,routing weight也不同,聚合结果的尺度会波动。K3在之前加入RMSNorm,使升维投影看到更稳定的输入。报告称这不仅改善训练稳定性,也持续改善validation loss和下游评测。
6.5 修复二:SiTU-GLU同时限制两条支路
SwiGLU近似为:
两条乘法支路都无界。当两个大坐标相乘时,低精度训练很容易产生outlier或overflow。
K3提出Sigmoid Tanh Unit GLU:
\mathrm{SiTU\mbox{-}GLU}(x)= \left[\beta_1\tanh\left(\frac{W_gx}{\beta_1}\right) \odot\mathrm{Sigmoid}(W_gx)\right] \odot \left[\beta_2\tanh\left(\frac{W_ux}{\beta_2}\right)\right]
其中、。在原点附近,tanh近似线性,所以它保留SwiGLU的局部形状;大输入时两条支路分别平滑饱和,逐元素输出绝对值上界为:
它不是硬clamp,梯度和函数仍然连续。
6.6 修复三:Quantile Balancing
K3 router先得到原始分数:
Top-k选择时加入每个expert的bias :
但真正的mixture weight不含:
这意味着bias只改变“派给谁”,不直接改变expert输出的混合比例,也不进入router的梯度优化。
旧方法每步按负载高低给bias加减固定,会在“响应慢”和“来回振荡”之间取舍。Quantile Balancing直接问:如果本batch有个token、个expert、每token选个,那么每个expert目标负载应为:
对每个token多取一个Top- cutoff ,再根据expert 的margin 的分位数设置下一步bias,使恰好约个margin跨过门槛。更新只在下一step生效,避免用当前batch计算的bias反过来改变当前batch路由。
大规模训练无法收集数百万margin做精确quantile,因此每个rank先做直方图,再all-reduce bin count,从全局直方图估计quantile。通信量只有每expert几百个bin。训练结束后bias冻结,推理不再动态更新。
6.7 MoonEP解决的是执行负载,不是router数学
即使QB让长期路由更平衡,单个microbatch和单层仍可能在EP rank之间不均衡。MoonEP允许在线规划并迁移少量冗余expert,使每个rank收到相同的 token数:
- 当前microbatch根据router输出规划冗余expert位置。
- forward前prefetch冗余expert。
- backward把冗余expert梯度reduce回home rank。
- 每rank最多预留个冗余expert slot,官方给出总能找到平衡方案的证明。
- 完全平衡后各rank shape静态已知,可去掉每层host同步,并使用zero-copy permute/unpermute路径。
QB解决“模型学到的路由是否健康”,MoonEP解决“这一轮实际执行是否让某些设备等另一些设备”。两者处在不同层次。
7. MoonViT-V2:原生多模态不是外挂视觉塔
7.1 从零训练,而不是从SigLIP接入
Kimi K2.5使用对比学习预训练的视觉编码器。K3改为让MoonViT-V2从随机初始化开始,与LLM一起用next-token prediction训练。
官方给出的原因首先是稳定性:把SigLIP初始化的MoonViT-3D接到LLM后,vision tower长期有更高gradient norm和频繁spike;从零训练的MoonViT-V2更稳定。其次,语言建模目标可以让视觉表示更关注OCR、代码结构、UI细节等下一token预测真正需要的线索,而不是只优化全局图文语义对齐。
官方消融称,从零训练版本在视觉评测上能匹配SigLIP初始化基线,因此在K3规模下,对比预训练不再是必要初始化条件。这个结论应限定在其数据、规模和训练recipe内,不能直接推广成“小模型也不需要视觉预训练”。
7.2 视觉路径
MoonViT-V2公开配置为:
- 401M参数、27层、patch size 14、12个attention heads。
- 使用RMSNorm,移除linear和attention projection的所有bias。
- 图像与视频完全共享参数。
- 视频attention拆成frame内spatial attention和frame间temporal attention。
- temporal pooling沿时间维压缩视频token。
- projector前做 pixel shuffle downsampling,视觉token数降为四分之一。
- 支持最高3584×3584像素输入,放入同一个1M上下文。
flowchart LR
A[图像或视频帧] --> B[Patch embedding]
B --> C[MoonViT-V2 27层]
C --> D[空间attention]
C --> E[时间attention与pooling]
D --> F["2×2 pixel shuffle"]
E --> F
F --> G[轻量MLP projector]
G --> H[与文本共享的7168维embedding流]
所谓“原生”具体指:视觉和文本从pre-training起就在同一个next-token objective中联合优化,不是先训练纯文本LLM,再额外做一个视觉alignment stage。
8. Per-Head Muon与长上下文训练
8.1 为什么Muon按head正交化
K3延续K2,对矩阵参数使用Muon optimizer。Muon会对momentum matrix做Newton-Schulz orthogonalization。若把整个Q/K/V projection当成一块,不同attention head的gradient scale会相互竞争:大尺度head主导更新方向,小尺度head得到的归一化不足。
K3把Q/K/V momentum按head拆开,分别正交化:
- 各head更新尺度更平衡。
- 大规模训练更稳定。
- 对较窄的per-head矩阵跑Newton-Schulz,也略微降低optimizer开销。
训练系统没有在每个DP rank all-gather完整参数再做Muon,而是让rank只通过P2P取回自己负责参数的shard,并按model chunk让通信与正交化流水重叠。
8.2 NoPE怎样扩到1M
K3所有MLA层都没有RoPE,KDA通过递推、decay和gate隐式表达顺序。因此从64K扩到1M不需要修改位置编码参数。
但NoPE只解决“位置参数不阻碍外推”,不等于模型天然会使用1M上下文。官方仍然做了四阶段curriculum:
pre-training: 8K -> 64K
cooldown: 256K -> 1M长上下文数据还经过:
- exact/fuzzy deduplication和质量过滤;
- 视频frame perceptual hash去重;
- 长文档、长视频上采样;
- 拼接和重排多模态文档/子任务,使答案依赖分散在整个1M窗口的信息。
最后一项很重要:仅把短文档填充或拼长,模型可能仍只学局部模式;训练样本必须真的要求跨长距离整合信息。
9. 部署感知后训练:MXFP4与MTP
9.1 不是全模型FP4
K3从SFT开始,在整个SFT和RL阶段执行quantization-aware training:
- MoE routed expert权重:MXFP4。
- expert输入activation:MXFP8。
- attention projection、LatentMoE的、shared expert和router:更高精度。
这样做的依据是routed expert占绝大多数权重内存,量化它们收益最大;把数值敏感且参数占比较小的模块也压到FP4,风险高而节省有限。
RL rollout和训练使用同一量化scheme,减少训练策略与实际部署推理之间的数值偏差。
粗略看,2.78T参数若全部按4 bit保存,纯权重下界也约为:
实际部署还需要scale、非expert高精度权重、runtime buffer、KDA state、MLA cache和通信空间,所以不能用“1.39TB除以单卡显存”直接得出可靠卡数。
9.2 MTP怎样变成draft model
K3预训练包含一个结构类似backbone block的MTP层。后训练时把它改造成EAGLE-3风格单层draft model:
- target model冻结,只更新draft层和feature-fusion projection。
- draft融合target低、中、高层feature。
- 训练时unroll 7步,使后续step使用draft自己的历史输出,贴近推理时递归draft。
这为speculative decoding提供了原生起点,但KDA让回滚比普通KV模型复杂:draft token可能被拒绝,而recurrent state已经原地前进。
官方推理kernel不为每个draft位置复制整份KDA state,而是缓存更小的projected input。验证后,在片上重放被接受token,重建正确state,并把verified token和bonus token的state写回。短卷积、输入归一化、gate、KDA recurrence和输出归一化被融合进同一个recurrent kernel。
10. 一个请求完整走过prefill与decode
前面把模块拆开看过,下面用一个教学请求把它们重新接起来。设prompt已经tokenize为[A,B,C,D],本例只有一个请求,不启用speculative decoding。字母只是token占位符,真实词表仍有160K项。
10.1 先把时间轴对齐
prefill输入: [A,B,C,D]
模型最后位置输出: logits_D [160000]
采样器: E ~ Softmax(logits_D / temperature)
第一次decode输入: [E]
模型输出: logits_E [160000]
采样器: F ~ Softmax(logits_E / temperature)所以prefill已经给出了第一个生成token 的概率分布。第一次decode消费并给出的分布。prefill刚采样出时,cache仍然只包含A...D;只有下一轮把喂回模型,才进入KDA和MLA cache。
10.2 Prefill入口与AttnRes初始化
vLLM会把同一调度轮的token pack到第一维。本例只有一个长度4的请求,因此:
input_ids [4]
embedding lookup [4,7168]
AttnRes block bank [4,8,7168]
current block prefix [4,7168]
positions/query_start_loc/slot map metadatablock bank和prefix在当前93层forward内流转,但不是请求的长期cache。
10.3 Prefill经过一个KDA层
假设当前层输入为:
AttnRes(X) [4,7168]
Qraw/Kraw/Vraw/G² [4,96,128]
F_a / β_raw [4,128] / [4,96]
Conv4 + Swish后的q/k/v [4,96,128]
逐channel decay α [4,96,128]数学上每个head仍按顺序前进:
FlashKDA或chunk fallback把chunk内可改写的token交互并行化,最后只把写回请求的该层state slot。prefill结束后,这一层持久保留:
- recurrent state:全局逻辑
[96,128,128],TP=8每rank[12,128,128]; - Q/K/V Conv4最近3项:全局
[36864,3],TP=8每rank[4608,3]。
四个位置的Q/K/V、decay和attention输出都是临时张量,不会各自作为历史记录留下。相同过程在69个KDA层独立发生。
10.4 Prefill经过一个Gated MLA层
X [4,7168]
fused_qkv_a_proj → [q_c | c] [4,2112]
q_c / c after RMSNorm [4,1536] / [4,576]
Q [4,96,192]
临时展开K / V [4,96,192] / [4,96,128]
causal scores [96,4,4]
head output + full-rank gate [4,96,128]
o_proj [4,7168]第D个位置可看A...D,第A个位置只能看A。临时K/V在prefill attention完成后即可释放;该MLA层的paged cache只提交四个576维latent:
上标很重要:24个MLA层各自拥有数值不同的latent序列,不能共享同一份。
10.5 每层的AttnRes与LatentMoE发生什么
attention输出后,第二次AttnRes把它加入当前block prefix,并对已完成block与新prefix做深度softmax,生成MoE输入[4,7168]。随后:
router logits [4,896] FP32
top ids / weights [4,16] / [4,16]
routed down [4,3584]
dispatch逻辑输入 [4×16,3584]
expert gate/up [4×16,3072] 各一份
combine + norm + up [4,3584] → [4,7168]
shared path [4,7168]router logits、expert activation与combine workspace用完即释放。MoE不会产生请求cache;下一个token会重新计算路由,可能选中完全不同的16个expert。
10.6 Prefill结束、采样E之前和之后
第92层是最终Gated MLA。最终AttnRes与RMSNorm输出[4,7168],LM head逻辑上可产生[4,160000];生成只需要D位置的[160000] logits。采样器从这个分布得到E。
此时请求的持久状态快照是:
| 持久项 | 每层/每请求逻辑内容 | 组数 | 当前历史边界 |
|---|---|---|---|
| KDA recurrent state | [96,128,128];TP=8每rank[12,128,128] |
69层 | A…D |
| KDA Conv4 history | Q/K/V各通道最近3项 | 69层 | 可继续计算E |
| MLA latent pages | 每token 576维 | 24层 | 每层各有[c_A...c_D] |
| cache metadata | request length=4、block table、slot mapping | 请求级 | E将写逻辑位置4 |
AttnRes bank、Q/K/V、router、expert activation和logits均不在这张持久表里。实际state字节数需要显式给出TP、dtype、page与对齐假设,不能从逻辑shape直接下结论。
10.7 第一次plain decode消费E
E先查embedding得到[1,7168],然后仍然从第0层走到第92层。“decode只算一个token”不等于“只算一层”。
在每个KDA层,kernel读取该层截至D的conv history和,计算E的Q/K/V、decay、write gate与output gate:
然后原地写回,Conv4 history丢掉最老的一项并追加E。vLLM的plain decode走fused conv update + recurrent KDA路径,单步不随A...D长度增长。
在每个MLA层,先生成q_E:[1,96,192]和c_E:[1,576],把插入本层page,再通过吸收权重查询[c_A...c_E]:
BMM1: q_E → q_latent [1,96,576]
latent MQA scores [1,96,5]
latent attention output [1,96,576]
BMM2: W_UV up-projection [1,96,128]
gate + o_proj [1,7168]E在自己的93层forward中还会重新创建[1,8,7168] AttnRes bank,并重新产生[1,896] router logits;它们都不读取A到D的同类临时量。跨token历史只由KDA state和MLA latent传入。
最终LM head给出logits_E:[160000],采样得到F。此刻cache包含A...E,还不包含F。下一轮decode消费F并产生G的分布。
| 时刻 | 本轮模型输入 | 提交后的cache | 模型分布 | 随后采样 |
|---|---|---|---|---|
| prefill | A,B,C,D | A…D | E | |
| decode #1 | E | A…E | F | |
| decode #2 | F | A…F | G |
10.8 vLLM源码怎样对应这些步骤
以下文件固定到提交f4b161d7,避免main分支继续变化后行文失去对应关系:
| 文件 | 对应职责 |
|---|---|
vllm/models/kimi_k3/nvidia/model.py |
93层骨干、两次AttnRes、Stable LatentMoE、最终depth聚合 |
vllm/models/kimi_k3/nvidia/kda.py |
packed projection、prefill/plain decode/spec分派、conv与recurrent state写回 |
vllm/models/kimi_k3/nvidia/kda_metadata.py |
请求边界、state index、prefill/decode/spec token重排metadata |
vllm/models/kimi_k3/nvidia/mla.py |
prefill latent展开、cache insert、decode的W_UK_T/W_UV吸收路径 |
vllm/models/kimi_k3/nvidia/ops/attn_res.py |
prefix更新、block写入、depth softmax与output norm融合 |
vllm/models/kimi_k3/nvidia/latent_moe_runner.py |
latent routed expert执行与tail调度 |
同一scheduler batch可以混合chunked prefill、plain decode和spec token。Python层保持统一packed token流,metadata把token分组后交给不同kernel,再写回原顺序。这个分派属于vLLM后端工程,不是K3数学额外定义了第三种attention。
11. 推理系统真正要管理两种cache
11.1 KDA state与MLA KV不能用同一语义
| 属性 | KDA cache | MLA cache |
|---|---|---|
| 内容 | 每个KDA组的recurrent state/checkpoint | 每个token的压缩latent KV |
| 随长度增长 | running state固定;checkpoint按策略稀疏保存 | 线性增长 |
| 更新 | 原地递推;共享checkpoint只读 | 追加token/page |
| prefix复用 | 必须命中有完整KDA checkpoint的边界 | 可按token hash block匹配 |
| speculative rollback | 重放projected input恢复state | 丢弃未接受token对应页即可 |
| P/D传输 | 固定状态,可能需按不同TP重排 | 传输匹配的latent pages |
K3官方实现没有因此写两套allocator,而是统一物理管理、保留不同逻辑语义:
- KDA state和MLA KV page使用相同字节大小的paged pool。
- 共用allocation、reference count、eviction和transfer框架。
- KDA页里各head连续存储,每个head byte stream是最小跨节点传输单位。
- prefill/decode采用不同TP度数时,在传输路径完成重布局,不在GPU上额外shuffle。
统一pool不是把KDA伪装成普通KV,而是让两种page共享生命周期基础设施。
11.2 为什么物理block和hash block必须解耦
KDA checkpoint很大,不能每几个token保存一次。官方物理block因此可能覆盖1024到6144 token。如果prefix hash也被迫使用同样粒度,会产生严重浪费:
- 短于一个物理block的请求永远不能命中。
- chunked prefill在填满大block前没有可复用prefix。
- 对话只差少量后缀,也可能重算数千token。
K3把粒度拆开:
- physical block:例如6144 token,负责实际分配和容纳KDA checkpoint。
- hash block:例如512 token,负责prefix identity和细粒度匹配。
- KDA checkpoint:只在部分hash endpoint保存,通常保留conversation turn边界。
官方例子中,一个请求前2800 token与缓存相同:
即使2560位于一个6144-token物理block内部,也可以命中5个hash block,恢复该边界的KDA checkpoint,对MLA partial block做copy-on-write,再从2560继续prefill,而不是重算。
flowchart TD
Q[新请求前2800 token匹配] --> H[MLA chained hash找到2560边界]
H --> C{所有KDA cache group都有2560 checkpoint?}
C -- 否 --> B[退到更早的共同边界]
C -- 是 --> P[跨group pin命中页]
P --> R[复制只读KDA checkpoint到请求私有state]
R --> W[MLA partial page copy-on-write]
W --> F[从token 2560继续prefill]
11.3 并发一致性约束
当一个partial block既是共享cache entry,又是某请求的增长点时,会出现三个实际故障模式:
- 某个cache group分配私有副本时,可能淘汰另一个group刚命中的页,所以必须先跨所有group pin全部命中页,再做任何分配。
- GPU copy在forward前才真正执行,新分配或刚注册的block可能仍含旧owner数据,所以copy落地前不能参与匹配。
- 只有所有KDA group都存在同边界checkpoint时才能恢复;淘汰任一group的checkpoint,必须原子失效其siblings。
这也是为什么“统一一个cache key,然后每层自己尽力命中”会产生静默错误。混合attention模型的prefix hit是一个跨cache-group事务。
12. 从kernel到集群:架构决定系统形状
12.1 KDA kernel分三个regime
| Regime | 主要矛盾 | K3方案 |
|---|---|---|
| training/prefill | chunk内并行与chunk间串行交替 | FlashKDA重叠token-parallel和head recurrence |
| 超长prefill | TP只切head,单rank head少时SM空闲 | 单卡SM级context parallel切sequence segment |
| decode/spec decode | state每步原地更新,拒绝draft难回滚 | 缓存projected input并片上replay |
12.2 Stable LatentMoE kernel
官方实现针对latent path做了三项融合:
- latent down-projection与router融合为一个GEMM。
- latent权重跨rank切分,并用multimem store把output all-gather融合进GEMM epilogue。
- 通信与shared expert等其他operator重叠。
小batch decode时,expert group GEMM接近“流式读权重”的memory-bound问题。K3使用基于WarpDecode的token-centric kernel:每个warp负责一个output neuron,lane team分别处理不同expert,最后warp-wide reduction;权重还会离线重排,减少runtime dequantization成本。
12.3 1M agent负载需要cache affinity
报告给出的典型coding请求是:已有400K token prefix,新一轮只增加4K token。此时prefix miss要重算400K,成本相对hit不是小幅波动,而是数量级差异。
官方集群调度因此采用:
- cache-aware affinity:把session路由到持有其prefix cache的cluster。
- consistent hashing双归属:primary服务,secondary在primary故障时接管;secondary平时不复制cache,故障后重新prefill。
- budget-based admission:短于2K和长达1M的请求分开预算,避免一批超长请求让全站短请求TTFT失控。
这说明1M模型的生产调度单位不能只是“一个request”。session prefix的驻留位置已经成为路由状态。
13. K3相对K2到底改变了什么
| 项目 | Kimi K2 | Kimi K3 | 主要代价/收益 |
|---|---|---|---|
| 总参数 | 1.04T | 2.78T | 权重容量和EP规模大幅上升 |
| 激活参数 | 32.6B | 104.2B | 单token计算也明显变重 |
| 层数 | 61 | 93 | 深度增加52% |
| 主干宽度 | 7168 | 7168 | 不靠扩大hidden width完成全部扩展 |
| Attention | 61 MLA | 69 KDA + 24 MLA | 用固定state支撑长上下文,保留周期性全局检索 |
| Routed expert | 384 | 896 | 专门化空间扩大 |
| Top-k | 8 | 16 | 每token组合更多专家,但需要latent path |
| Shared expert | 1 | 2 | 增强公共全宽变换 |
| Expert hidden | 2048 | 3072 | 单expert容量上升 |
| 训练上下文 | 128K | 1M | 8倍增长,训练与服务基础设施显著复杂化 |
| 激活函数 | SwiGLU | SiTU-GLU | 用平滑上界换低精度稳定性 |
| 视觉 | 无原生ViT | 401M MoonViT-V2 | 从pre-training开始原生多模态 |
K3保持7168主干宽度,却同时增加层数、head数、expert池、top-k、shared expert和上下文。这种shape说明扩展重点不是简单“把每层做宽”,而是让token、layer、expert三个轴都拥有更强、但更稀疏或更可压缩的信息通道。
14. 怎样正确理解2.5× scaling efficiency
官方在held-out OOD validation data上重新搜索batch size、learning rate、tokens-per-parameter ratio和model shape,并为cosine decay与WSD分别寻找最优超参数。拟合结果显示,K3 family达到相同validation loss所需FLOPs约为K2 family的1/2.5。
这个结果包含:
- KDA + periodic MLA;
- Block AttnRes;
- Stable LatentMoE;
- 数据清洗、合成与采样变化;
- Per-Head Muon、cosine schedule等训练recipe。
报告没有把2.5×完整拆成每个组件的独立贡献,所以不能说“KDA本身带来2.5×”或“LatentMoE让推理快2.5×”。
更严谨的表达是:
在Moonshot针对K2与K3模型族拟合的训练缩放律中,K3整套架构、数据和训练方案在相同验证损失下约节省2.5倍训练FLOPs。
15. 架构的真实代价与边界
15.1 KDA是压缩,不是无损记忆
固定大小state不可能无损容纳无限历史。周期性MLA正是对此的补偿。实际1M recall还取决于训练数据、任务分布、global layer密度和上下文管理,不能从context window数字直接推出。
15.2 混合attention让runtime更复杂
普通Transformer主要管理一种KV Cache;K3请求长期状态同时包含KDA running state/checkpoint与MLA latent pages,spec decode还可能增加恢复记录;AttnRes block representation则是每次forward内的临时activation。即便allocator复用物理管理,一致性、回收、P/D传输和回滚语义仍然更难。
15.3 2.8T open weight仍是重型集群模型
MXFP4只显著降低expert权重内存,并没有消除:
- 约1.39TB的4-bit纯权重量级下界;
- 非expert高精度权重和量化metadata;
- 896 expert带来的EP all-to-all;
- KDA state与MLA cache;
- 高并发下的state pool压力。
它不是普通工作站或少量消费卡的实用本地模型。
15.4 Benchmark高度依赖harness和budget
官方表格混用了Kimi Code、Claude Code、Codex等harness,不同模型还可能采用不同reasoning effort、fallback和工具配置。报告已给出很多脚注,但headline score仍不能脱离评测协议单独比较。
15.5 Preserved thinking history是产品契约
K3后训练采用保留思考历史模式。多轮对话和tool call需要把API返回的完整assistant message原样带回,包括reasoning_content与tool_calls。中途从另一个模型切换到K3,或丢弃历史reasoning,官方称生成质量可能高度不稳定。
这不是模型矩阵结构的一部分,却是架构能力能否在agent harness里正确工作的接口约束。
15.6 许可证不是标准MIT/Apache 2.0
Kimi K3 License允许使用、修改、分发、微调和创建衍生作品,但包含额外条件:特定收入规模的Model-as-a-Service商业使用需要另行与Moonshot签约;达到特定月活或月收入门槛的商业产品需要显著展示“Kimi K3”。部署前应读原始许可证,不能只把它归类成“开源模型所以可无限制商用”。
16. 最后总结
理解Kimi K3,可以记住四个互相咬合的设计:
- KDA把时间历史压成固定state,通过delta rule、channel-wise decay、lower-bounded gate和FlashKDA把1M序列变得可训练、可prefill、可decode。
- 周期性Gated MLA保留全局逐token检索,避免纯递归状态在超长历史中成为唯一信息瓶颈。
- Block AttnRes在深度方向做选择性读取,把“只能逐层累加”的残差流变成对embedding和历史block的可学习路由。
- Stable LatentMoE在宽度方向稀疏扩容,用3584维latent expert、RMSNorm、SiTU-GLU和Quantile Balancing支撑896选16的极稀疏结构。
MoonViT-V2、Per-Head Muon、MXFP4 QAT、MoonEP、KDA-aware prefix cache和fleet scheduler不是外围补丁,而是让上述结构在3T参数、1M上下文和长程agent负载下真正运行起来的共同条件。
因此K3最值得研究的地方,不是“2.8T比1T更大”,而是它把模型架构、数值稳定性、GPU kernel、并行训练、prefix cache和集群调度放进了同一套约束中设计。
参考
- Kimi K3官方仓库:https://github.com/MoonshotAI/Kimi-K3
- Kimi K3 Technical Report:https://github.com/MoonshotAI/Kimi-K3/blob/main/k3_tech_report.pdf
- Kimi K3官方技术博客:https://www.kimi.com/blog/kimi-k3
- Kimi K3 License:https://github.com/MoonshotAI/Kimi-K3/blob/main/LICENSE
- Kimi Linear与KDA:https://github.com/MoonshotAI/Kimi-Linear
- FlashKDA:https://github.com/MoonshotAI/FlashKDA
- Attention Residuals论文:https://arxiv.org/abs/2602.10604
- MoonEP:https://github.com/MoonshotAI/MoonEP
- vLLM Kimi K3实现(本文shape与推理路径固定到
f4b161d7):https://github.com/vllm-project/vllm/tree/f4b161d7fca438bfe29509984759be1943a5aa88/vllm/models/kimi_k3 - vLLM Kimi K3 recipe:https://recipes.vllm.ai/moonshotai/Kimi-K3
- SGLang Kimi K3 cookbook:https://docs.sglang.io/cookbook/autoregressive/Moonshotai/Kimi-K3