【轻量化复刻大模型 / KIMI K3】Attention Residuals 完整梳理(2)
# 【轻量化复刻大模型 / KIMI K3】Attention Residuals 完整梳理(2)
- 0x00 概要
- 0x01 子知识点拆解
- 0x02 子知识点深度分析
- 2.0 共享抽象与设计动机
- 2.1 打分核(scoring kernel)与伪查询
- 2.2 Full AttnRes
- 2.3 Block AttnRes
- 2.4 状态机(AttnResState —— 深度历史如何在层间流动)
- 2.5 online-softmax 两阶段算法(论文 Algorithm 1)
- 2.6 生产级 fused kernel 原理(SGLang;TMA + online softmax + 通信融合)
- 2.7 对比对象 —— mHC(Manifold-Constrained Hyper-Connections)
- 2.8 基础设施:跨阶段缓存 + 高效 Prefill(论文 §4)
- 2.9 学到的深度注意模式(论文 §5.4.2,Fig. 8)
- 2.10 统一视角:残差连接作为结构化矩阵(论文 §6.2,Table 5)
- 2.11 最优架构分析(论文 §5.4.1,Fig. 7)
- 0xFF 参考
0x00 概要
本文为 AttnRes 的第二部分。
注:
- 项目https://github.com/RossiLH/Model-Viz 和系列博客是我的一个尝试,也是我学习的过程。不确定是否满足读者的需求,我自己会反思改进,也希望大家提出宝贵意见。
- 涉及 KIMI K3,DeepSeek V4,PI0.5,PI0.7 等模型。后续可能会跟踪新模型。
- 全 CPU 复刻,不涉及 GPU,也不涉及大规模训练(这是另外一个庞杂深邃的领域)和硬件。
- 模型的组件尽量是原始大模型的缩小版,如果组件没有开源版本,则用近似组件替代。
- 会解释每个组件在解决一个什么问题、这个问题难在哪、为什么只能这么解、解法的原理是什么,以及组件之间如何咬合成一个整体。重点不是记名词,而是理解一条主线。
- 每个主要组件都有可直接运行的 Gradio demo——无需 GPU。另有若干可视化脚本(
plot_*.py),可生成静态图。 - 所有文件行号基于本地源码快照;源码后续变化时,行号可能移动,但函数名和调用关系仍可作为定位入口。
- 为了保证每个文章可以尽量独立成文,所以有些内容会在不同文章中都存在,可能在系列层面上存在冗余,还请大家理解。
0x01 子知识点拆解
AttnRes 拆解为以下子知识点(树状结构,标注依赖与项目代码位置):
AttnRes
├── S1 打分核(scoring kernel):φ(q,k) = exp(q^T·RMSNorm(k))
│ ├── S1.1 伪查询向量 pseudo-query w_l ∈ R^d(每层一个可学习参数,零初始化)
│ ├── S1.2 key = value = 历史层输出(含 embedding 源 h_1)
│ └── S1.3 RMSNorm 在 key 上(防大范数垄断)
│ 代码:attn_res.py 的 mix() 函数
├── S2 Full AttnRes(O(L^2 d) 算术 + O(Ld) 内存,保留所有历史层)
│ 代码:attn_res.py 的 FullAttentionResiduals(block_units=1 等价)
├── S3 Block AttnRes(O(Nd) 内存;分块 + partial sum 运行态)
│ ├── S3.1 块表示 b_n = Σ_{j∈B_n} f_j(h_j)
│ ├── S3.2 运行 partial sum(当前块内累计)
│ ├── S3.3 attention 范围 = 已完成的块表示 + 当前 partial(Eq. 6)
│ └── S3.4 embedding 永远是源 b_0 = h_1
│ 代码:attn_res.py 的 AttentionResiduals 类(init_state/mix/add_output/finalize)
│ 集成:model.py 的 Kimi3Model 集成
├── S4 状态机(服务端/推理维度:sources 的演进)
│ 代码:attn_res.py 的 AttnResState 数据类
├── S5 在线 softmax 两阶段算法(分块间并行 + 块内顺序)
│ 参考代码:kimi-k3-pytorch-main 的 online_softmax.py / two_phase.py
├── S6 生产级 fused kernel(SGLang;TMA + online softmax + 通信融合)
│ 参考代码:sglang-main 的 attn_res.py + fused_tma.cuh
└── S7 对比对象:mHC(Manifold-Constrained Hyper-Connections)
依赖关系:S1 → S2 / S3(所有 mix 用打分核);S3 → S4(block 演进状态);S2/S3 → S5(两阶段加速);S5 → S6(fused kernel)。S7 为横向对比,不依赖其它子知识点。
各子知识点的实现/变体切换入口:
| 变体 | 切换参数 | 位置 |
|---|---|---|
| full vs block | AttnResConfig.full(默认 False→block) |
config.py 的 AttnResConfig |
| block size | AttnResConfig.block_size(默认 4) |
同上 |
| granularity sublayer vs layer | AttnResConfig.granularity |
同上 |
| eager vs two_phase | kimi-k3-pytorch 的 backend 字段 | kimi-k3-pytorch-main 的 config.py |
关于论文 48B 配置 vs K3 配置的澄清:
- AttnRes 论文主实验(§5.2):基于 Kimi Linear 48B(27 Transformer blocks = 54 层),Block AttnRes 每块 6 层 → 9 个块 + embedding = 10 个深度源。
- K3):K3 技术报告 §2.2 明确 "we partition its layers into 8 blocks with 12-layer size, giving a partial final block and 9 total blocks when counting the embedding layer"。K3 总层数为 93 层。
- 需注意分清:9 源对应 K3(12 层/块),论文主实验是 10 源(6 层/块)。本项目测试
test_paper_partition_for_the_2p8t_config验证的正是 K3 的 93 层 → 8 块 → 9 源切分。
0x02 子知识点深度分析
2.0 共享抽象与设计动机
所有变体共享同一抽象:一个"源(source)"集合 + 一次"查询-键打分 + 归一化混合"。
- 源(sources):
V = [v_0, v_1, ..., v_{n-1}],是历史层输出的集合(显式保留,不压缩成一个状态)。这是与标准残差的分水岭:标准残差把历史"压成"h_{l-1},AttnRes 把历史"留作"列表。 - 打分:每层一个
w_l,对所有源求exp(w_l^T·RMSNorm(v_i))后 softmax,得到α_i。 - 混合:
h_l = Σ_i α_i v_i。 - 设计动机:为什么把历史"留作列表"而非"压成状态"?因为"选择性读取"必须保得住"每一项"才能选——压成一个状态就丢了选择性。代价是保留全部历史的内存,这正是 Block 形式要解决的问题。
统一公式(Full / Block 都是它的实例):
V = [v_0, ..., v_{n-1}] # 源列表
K = RMSNorm(V) # key = value,RMSNorm 保证可比
w_l = learned query of layer l
α = softmax(V·w_l) # 每源权重
h_l = Σ_i α_i · v_i # 混合结果
Block 形式唯一的差异是:V 被压缩为 [b_0, ..., b_{N-1}, partial](块表示 + 运行 partial),即 n = N+1 个源而非 L 个。
2.1 打分核(scoring kernel)与伪查询
2.1.1 原理(三视角)
- 直觉:每个源是一个"词汇",每层是"搜索的 query"。伪查询向量
w_l表示"这一层现在想要什么样的表示",RMSNorm 把每个 key 缩放到同一长度,保证比的是"方向"而不是"长度"。 - 准专业:
α_i = exp(w_l^T·RMSNorm(v_i)) / Σ_j exp(w_l^T·RMSNorm(v_j))。w_l∈R^d是层级参数,训练时学出「这一层该主要读哪些 early 层的输出」。 - 专家:
φ(q,k)=exp(q^T RMSNorm(k))是 inner-product 点积核,softmax 保证α非负且和=1,即每一层是一个「往历史层上的概率分布」。RMSNorm(k) 的作用论文有消融验证:去掉它性能下降(Full 1.743 vs 1.737,Block 1.750 vs 1.746),因为它防止了「范数大的层输出靠量级垄断权重」——也就是防"权重只看 magnitude 不看 semantic dimension"。对 Block 更关键(块表示跨层累积,量级差异更大,论文 §2.3 明确说明)。
2.1.2 对比基线
| 维度 | 标准做法(残差) | 打分核(伪查询) |
|---|---|---|
| 权重来源 | 固定常数 1 | 每层的 w_l 可学习向量 |
| 是否 input-dependent | 否 | 是(经 v_i 内容影响) |
| 额外参数 | 0 | d per layer (` |
| 归一化 | 无 | softmax + RMSNorm(key) |
2.1.3 反方辩手
- a) 最强反对:伪查询是每层一个静态向量,无法根据输入 token 动态切换。论文消融确实提出把
w_l变成 input-dependent 投影(1.731,更低),但有d×d投影/层的参数代价和顺序访存代价。 - b) 失败假设:假设每层最重要的历史是输入无关的(或近似静态)。若每 layer 需要 "task / sequence" 级别的动态读取,固定
w_l就力不从心。 - c) 胜出条件:当"深度方向的读取模式本身比较稳定"(例如固定关注 embedding / 邻居层)时静态伪查询更省参且稳定;失败条件:需要动态(per-token)改变读取。
- d) 更稳做法:用一个小的 input projection 替换
w_l(hybrid),保留两者优点。 - e) 受损方:无——参数极小(每层 d 个标量),唯独增加 PP/通信(块表示跨 stage)。
2.1.4 计算复杂度
- 增加:每层一次
w_l·RMSNorm(v_i)是d点积 ×n源 =O(nd);总 L 层O(nLd)。 - n 个源:Full n=L(K3 以 sublayer 计 2L);Block n=N+1。
- 内存开销:每层存
w_l∈R^d,各 key&value 来自前存输出,零新增。
2.1.5 参数敏感性
w_l的初始值:论文 §5 明确"所有伪查询向量必须初始化为零"——这保证初始w=0→α=uniform(softmax(0)=均匀)→ 初始状态 = 标准残差(等权平均)!论文原文:"This reduces AttnRes to an equal-weight average at the start of training and prevents training volatility, as we validated empirically." 本项目queries用torch.zeros(...)初始化正是此设计。- 若
w_l初始为随机大值,会跳过均匀漂移直接学两极性,易造成层间读数剧烈变化。因此 zeros 是必要工程要点。
2.1.6 工程实现
attn_res.py 中的 mix() 方法逐行对应。rms_norm 来自同目录 norm.py。权重两个可训练参数:self.queries(形状 (n_units+1, d),多一个 final 聚合),self.key_norm_weight(形状 (d,))。
def mix(self, state: AttnResState, unit_idx: int) -> Tensor:
"""Eq. 9 -- the representation handed to unit ``unit_idx``.
【中文释义】深度维 softmax 注意的核心算子(对应论文 Eq. 9):把当前可读
源列表(``state.sources()``)叠成一个张量,其 key 统一过 RMSNorm,
用本 unit 的伪查询 ``self.queries[unit_idx]`` 打分得到 ``alpha``(softmax
归一化),最后加权求和解组成交给该 unit 的表示 ``h_l``。
当只有 embedding 一个源时直接返回(避免无谓的 einsum/softmax)。
shape 说明:source 叠成 ``(n, b, t, d)``,scores 为 ``(n, b, t)``,
softmax 沿源维 ``n`` 归一化,输出回 ``(b, t, d)``。
"""
sources = state.sources()
if len(sources) == 1:
return sources[0]
stacked = torch.stack(sources, dim=0) # (n, b, t, d)
keys = rms_norm(stacked, self.key_norm_weight, self.eps)
query = self.queries[unit_idx] # (d,)
scores = torch.einsum("nbtd,d->nbt", keys, query)
weights = torch.softmax(scores.float(), dim=0).to(stacked.dtype)
return torch.einsum("nbt,nbtd->btd", weights, stacked)
2.1.7 探讨代码
测试已覆盖:test_attention_weights_form_a_distribution(验证 α 非负、和为 1)与 test_key_rmsnorm_prevents_magnitude_domination(把某个源放大 1000x 后权重不变)。结论:RMSNorm 确实让权重对源 magnitude 不敏感——验证了论文论点。
2.1.8 验证 demo
tests/math/test_attn_res.py 全部通过(可与 test_key_rmsnorm_prevents_magnitude_domination 配合在 notebook 上可视化)。完成度:已实现并运行通过。
2.2 Full AttnRes
2.2.1 原理(三视角)
- 直觉:无分块,全历史可读——"之前每一页都是独立卡片,随便读哪一张"。
- 准专业:
n_units = L(或 sub-layer),每个源都是单一层输出;h_l = softmax 混合所有早期层 + embedding(Eq. 4)。 - 专家:
M稠密 rank-L;内容相关。O(L²d) 算术在L<100时可负担(论文:L<100 是 Full 形式可用的前提);瓶颈是 O(Ld) 内存(每层都要持久保存所有层的输出,PP 下还要跨 stage 传输)。
2.2.2 对比基线
Full vs 标准残差:所有源都可见+可选择性 vs 只可见前一状态。
2.2.3 反方辩手
- a) 反对最强理由:O(Ld) 内存让 PP 无法承受,L 大时退化。
- b) 失败假设:假设
L足够小(L<100),所以 O(L²) 可负担。若 L 数百,算术也可爆。 - c) 胜出条件:
L ≤ ~40单卡(教育/小模型);失败:PP/大数据。 - d) 更稳做法:Block 形式(见下)。
- e) 受损方:训练方(内存/通信);演进为 Block。
2.2.4 复杂度
- 算术:训练 O(L²d)(层间全对全 attention);推理在"两阶段调度"下可降到 O(L·(S+N)·d)(附录 C)。
- 内存:存储所有源 O(Ld) vs 标准 O(d)。
- 参数:新增 0(只有 w_l 固定,d/L)。
2.2.5 参数敏感性
n_units必须 ≤ 层数× granularity。若设大,多出的 queries 永不使用。- 论文:伪查询必须零初始化。
2.2.6 工程实现
FullAttentionResiduals(AttentionResiduals) 仅设 block_units=1、full=True,等价性由测试 test_block_size_one_recovers_the_full_form 断言(逐对 allclose atol=1e-6)。
可以看到,在 Kimi3Model 的前向传播中,会用 self.attn_res.add_output(self._attn_res_state, h) 往self._attn_res_state中增加模型每层生成的中间状态。
class Kimi3Model(nn.Module):
"""K3 剔除 lm_head 的完整主杆:embedding + 全部 block + 最后 norm。
额外管理一个可选的 AttentionResiduals 模块(跨层的深度方向注意力)。
AttnRes 启用时,每个 block 被**调用两次**(block 内前半个单元 da 次),
AttnRes 在其间插入"层间混合"(brand-residual mix)。
"""
def __init__(self, config: Kimi3Config):
super().__init__()
self.config = config
self.tok_embed = nn.Embedding(config.model.vocab_size, config.model.dim)
# 每层交替:偶数层 KDA(use_kda=True),奇数层 MLA(use_kda=False)。
# 这决定了 init_caches 返回的列表 K/ML 交替出现。
self.blocks = nn.ModuleList([
Kimi3Block(config, i, use_kda=(i % 2 == 0))
for i in range(config.model.n_layers)
])
if config.model.attn_res.enabled:
# 启用 AttentionResiduals:n_units = 2*n_layers(每 block 两个
# unit:attn+ffn),block_size 决定多少个 unit 共享一个残差状态。
n_units = config.model.n_layers * 2
block_units = config.model.attn_res.block_size
self.attn_res = AttentionResiduals(
config.model.dim, n_units, block_units,
full=config.model.attn_res.full,
)
self._attn_res_state = None # 前向过程中暂存的 AttnRes 状态缓存
else:
self.attn_res = None
self.norm = RMSNorm(config.model.dim, config.model.norm_eps)
def forward(
self,
tokens: Tensor,
doc_ids: Optional[Tensor] = None,
caches: Optional[list] = None,
) -> Tensor:
"""完整模型前向。
``caches`` 应与 ``self.blocks`` 等长(每 block 一个 cache)。
解码时与服务端相同:传一个 token,返回一层 logits,内部自动调用
cache 完成增量前向。训练时一次性给整段 tokens,cache 每层重新
复算或者用 cache 减少重复计算。
"""
h = self.tok_embed(tokens)
reset_mask, attn_mask = doc_masks(
doc_ids, tokens.shape[1], tokens.device
)
if self.attn_res is not None:
# ---- 带 AttnRes 的路 =========================================
self._attn_res_state = self.attn_res.init_state(h)
unit_idx = 0
for i, block in enumerate(self.blocks):
cache = caches[i] if caches is not None else None # 每 block 一个 cache
# 每个 block 被调度两次,中间混入一次 AttnRes 深度混合:
# mix -> attn -> add_output -> mix -> ffn -> add_output
h = self.attn_res.mix(self._attn_res_state, unit_idx)
h = block(h, cache=cache, reset_mask=reset_mask, attn_mask=attn_mask)
self._attn_res_state = self.attn_res.add_output(self._attn_res_state, h)
unit_idx += 1
h = self.attn_res.mix(self._attn_res_state, unit_idx)
h = block(h, cache=cache, reset_mask=reset_mask, attn_mask=attn_mask)
self._attn_res_state = self.attn_res.add_output(self._attn_res_state, h)
unit_idx += 1
h = self.attn_res.finalize(self._attn_res_state)
else:
# 不带 AttnRes:每个 block 只调用一次。cache 也是"每 block 一个"。
for i, block in enumerate(self.blocks):
cache = caches[i] if caches is not None else None
h = block(h, cache=cache, reset_mask=reset_mask, attn_mask=attn_mask)
return self.norm(h) # 最后一层 RMSNorm,喂给 lm_head
2.2.7-2.2.8 探讨/验证
测试:test_block_form_matches_the_eq10_oracle(naive 独立转写比对);test_block_size_one_recovers_the_full_form;test_gradients_reach_every_depth;test_paper_partition_for_the_2p8t_config(93 层/12 → 8 块/9 源)。
2.3 Block AttnRes
2.3.1 原理
- 直觉:把 L 层分成 N 个块,每个块"收官"时压缩成一个总表示
b_n。读历史时只读N个块 + 当前块的运行 partial。 - 准专业(论文 Eq. 5-6):设块
n,层集合B_n,块表示b_n = Σ_{j∈B_n} f_j(h_j),当前块前i-1层部分和b_n^{i-1}。则V = [b_0, ..., b_{n-1}](第一层,还没有 partial)V = [b_0, ..., b_{n-1}, b_n^{i-1}](i≥2 时再附加 partial)- embedding
b_0 = h_1。
- 专家:内存从 O(Ld) → O(Nd);
1时介于"等权相加"(N=1)和 Full(N=L)之间;论文的实验: N≈8复原几乎全部(论文图 4 中 Full 与 Block 差距随规模缩小,最大规模仅 0.001);K3 用 8 blocks × 12 层 → 9 源(加 embedding)。
2.3.2 对比基线(block vs full)
维度
Block
Full
内存
O(Nd)
O(Ld)
算术
O(L·N·d)
O(L²d)
精度
最接近
最优
针对性
面向训练内存/通信
参照(理论最优)
Block size 扫描(论文 Fig. 6,16 层模型):S 从 1(即 Full)粗化到 32,loss 优雅退化:
Block size S
1 (Full)
2
4
8
16
32
Loss
1.737
1.746
1.746
1.746
1.757
1.753
S=2/4/8 都落在 1.746 附近,S=16/32 靠近 baseline(1.766)。论文因此固定 ≈ 8 个块。
2.3.3 反方辩手
- a) 反对:块内压缩丢失层间差异(块内所有层共享同一个 key/value),block 是"近似 full"的折扣——本质上是 N 的选择。
- b) 失败假设:假设块内所有层输出可以被同等压缩(相加);极端情况可能丢信息。
- c) 胜出条件:随着模型变大 L 越来越大、块数 N 固定(≈8)→ 每个块更"稠密",信息损失越来越小;失败:块过小(内存仍大)或块太大(早期块太稠密,读到的已是压缩)。
- d) 更稳做法:soft-block 或轻量跨块(论文未做)。
- e) 受损方:训练(PP 通信必须在块边界)。
2.3.4 复杂度
- 内存:
O(Nd)(K3: 8 块)。
- 算术:每层
O(N d)(attention over blocks),总 O(L N d)。
- 对比 Full:算术 O(L²) → O(LN),N=8 时 ~L/8 倍省(L=93 时 ~11.6×)。
2.3.5 参数敏感性
- block_size(每块层数):本项目
block_size 直接当 block_units(默认 4),kimi3-master 用 block_units = block_size * per_layer(per_layer=2,默认 24)。论文建议 block_size≈12(层)/ 24(单元)对应 K3。
- 论文经验:
N≈8「recover most of the benefit」;增加 block count 一般会提升但受限于通信/内存。
2.3.6 工程实现(论文 Fig. 2 伪代码 vs 本项目)
论文 Fig. 2 的关键伪代码:
def block_attn_res(blocks, partial_block, proj, norm) -> Tensor:
# blocks: N tensors [B,T,D]; partial_block: [B,T,D] (intra-block partial sum)
V = torch.stack(blocks + [partial_block]) # [N+1, B, T, D]
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
def forward(self, blocks, hidden_states):
partial_block = hidden_states
# apply block attnres before attn
h = block_attn_res(blocks, partial_block, self.attn_res_proj, self.attn_res_norm)
# if reaches block boundary, start new block (block_size counts ATTN+MLP)
if self.layer_number % (self.block_size // 2) == 0:
blocks.append(partial_block); partial_block = None
attn_out = self.attn(self.attn_norm(h))
partial_block = partial_block + attn_out if partial_block is not None else attn_out
# apply block attnres before MLP
h = block_attn_res(blocks, partial_block, self.mlp_res_proj, self.mlp_res_norm)
mlp_out = self.mlp(self.mlp_norm(h))
partial_block = partial_block + mlp_out
return blocks, partial_block
与本项目 attn_res.py 的逐行对应:
block_attn_res ↔ mix():V=stack(sources)、K=rms_norm、logits=einsum('d,nbtd->nbt')、h=einsum('nbt,nbtd->btd') 完全同构。
- 块边界判断
layer_number % (block_size//2) == 0 ↔ add_output 的 units_in_block >= block_units。
- 双
block_attn_res 调用(attn 前 + MLP 前)↔ model.py 的每 block 两次 mix。
集成:model.py 中的 Kimi3Model —— 先把 h 进 init_state,对每个 block 先 mix(state, unit_idx)(把历史混合作为该层输入),执行 block 后 add_output。granularity="sublayer"(默认)时,每个 transformer 层有 attention 读 + MLP 读两个单位,故 n_units = n_layers*2;
不同库的块单位换算不同:Model-Viz 把 block_size 直接当 block_units(默认 4),而 kimi3-master 用 block_units = block_size * per_layer(per_layer=2 → 默认 24)。
2.3.7 探讨/验证
test_block_form_matches_the_eq10_oracle(naive 独立转写比对);test_memory_is_bounded_by_block_count;test_gradients_reach_every_depth;test_paper_partition_for_the_2p8t_config(93 层/12 → 8 块/9 源)。全部通过。完成度:已实现并运行通过。
Q1:activation recomputation 为什么被影响
标准 activation checkpointing 可以丢掉中间激活,反向时从 checkpoint 重算。但 Full AttnRes 要让后续很多层继续访问旧层输出;如果丢掉它们,后续层无法直接算深度注意力,或者必须频繁重算历史输出。于是它把“可释放的中间态”变成“跨多层被读取的 KV‑like 源”。
Block AttnRes 的妥协是只保存每个块的聚合表示和当前块 partial prefix。这样 activation checkpointing 仍可丢掉 softmax 中间量和大量块内细节,保留少量块级 bank。
Q2:pipeline parallelism 为什么变贵
普通 PP stage 边界只传 hidden_states 和常规 residual。AttnRes 需要下游 stage 也能看到历史块,所以边界还要传 block bank。
若 Full AttnRes 传每层历史,成本随层数涨;Block AttnRes 传块表示,成本随块数涨。
论文提出 cross‑stage caching: 同一物理 stage 在交错流水线的不同 virtual stage 会重复看到部分历史块,因此可缓存已见块,只传新增块,减少冗余通信。
2.4 状态机(AttnResState —— 深度历史如何在层间流动)
AttnResState 是 Block Attention Residuals 的跨层状态机,解决的核心问题是:如何在 O(Nd) 内存约束下(而不是 O(Ld)),让每一层都能"选择性读取"之前所有层的输出。
2.4.1 原理
问题:attn 需要在遍历 93 层时"记住"哪些源可用、块进行到哪一步、partial 是什么。
难点:这是一个"顺序推进的、跨层的可变状态",且需要支持 full/block 两模式。
思路:用一个可变 state 对象,每过一层由 add_output 更新。blocks = 已完成的块表示列表;partial = 当前块内运行和;units_in_block = 当前块已累积的单位数。sources() 根据 partial 是否为 None 组装可读列表。
原理:把 block 折叠的时机编码进状态机,而非 per-layer 的全局逻辑。
下图给出 AttnResState 在 AttentionResiduals 中的状态演进(Block 形式):init → 逐子层主循环(mix/block/add_output)→ 满块折叠 → finalize。

循环:每个 unit 走一遍 mix → 输出 → add_output
- partial 累计本块输出,units_in_block 计数
- 满块:partial 归档进 blocks[],partial 置 None、计数清零(O(Ld)→O(Nd))
- finalize 把尾部不全块并入后做最终 mix
2.4.2 对比基线
标准残差没有"状态"——只用局部 h_{l-1}。
AttnRes 需要显式的跨层状态。这是「深度上像 RNN 一样需要 carry 状态」的体现,与序列建模中 RNN 隐状态是同构的。
2.4.3 反方辩手
- a) 反对:状态机的存在让并行(PP/SP)变麻烦——状态必须跨 stage 传递。论文用 cache-based pipeline communication 缓解(见 2.8)。
- b) 失败假设:假设状态很小(N 个块 + 1 partial),可被高效序列化;当 N 变大时仍要承担。
- c) 胜出条件:N 固定小(8);失败:大 N。
- d) 更稳做法:把状态换成"固定长度 buffer"(预分配)。
- e) 受损方:推理/训练基础设施(需同步状态)。
2.4.4 复杂度
内存:state = O(N·B·T·d)(N 个块表示)。partial 为 1×B×T×d。总 O(N·B·T·d) 与 Full O(L·B·T·d) 相比 N≪L。
2.4.5 参数敏感性
block_units 直接决定 units_in_block 的翻转阈值。若设太小,partial 频繁折叠成 block(→ 退化为 Full 般大 N);设太大,块表示太稠密(选择性降低)。论文经验:K3 取 12 层/块(24 单元)。
2.4.6 工程实现
AttnResState管理两个数据结构:
字段
含义
内存复杂度
blocks: list[Tensor]
已完成的块塌缩表示(blocks[0] 恒为 token embedding)
O(Nd),N=块数
partial: Tensor
当前块正在累计的运行和
O(d)
units_in_block: int
当前块已累计的子层数
O(1)
每过一个子层调用一次 add_output():把该子层输出累加进 partial,块写满后把 partial 归档进 blocks 并清空——这就是"块塌缩"发生的时刻,把 L 层输出压缩到 N 个块表示。
init_state(embedding) 设 blocks=[embedding]、partial=None;add_output 折叠逻辑;finalize 把残余 partial 也 append 后 mix。对应 kimi3-master 完全一致。
一屏示意图(对应全过程的状态演进):
┌─────────────────────────────────────────────────────────────┐
│ AttentionResiduals 数据流(AttentionState — Block 形式) │
└─────────────────────────────────────────────────────────────┘
① init_state(h_embed) states:blocks = [h_embed]
inputs: partial = None, units_in_block = 0
(embedding 恒为第 0 个源)
│
▼
② for each unit_idx in [attention 子层, FFN 子层, ...] # n_units
│
▼
③ mix(unit) :本 unit 的 深度 softmax 注意力
sources = state.sources() # blocks + [partial](partial None 时仅 blocks)
keys = RNorm(sources) # 防"嗓门大"的源垄断权重
alpha = softmax( w_l · keys ) # 对"源"维归一化(Eq.9)
h_l = sum_i alpha_i · sources_i
│
▼
④ 执行当前 sublayer(按真实模型跑正常 sublayer)
out = sublayer( h_l ) # attention 层 / FFN 层
│
▼
⑤ add_output( out )
partial += out
units_in_block += 1
│
▼
⑥ units_in_block == block_units ? ── 满块?
├─ 否 ──────────────────────────────► 回到 ③(本块下一个 unit)
└─ 是 ──── 折叠归档:
blocks.append( partial )
partial = None ; units_in_block = 0
▼
(下一块第一个 unit 从 ③ 开始)
│
▼ (循环结束后)
⑦ finalize()
if partial is not None: blocks.append( partial)
out = mix( final 伪查询 ) # 对全部 blocks 再混合一次
│
▼
⑧ 返回末层输出 h_out
直观解读:③ mix 是"读"(深度维度 attention),④ 是"处理"(真实模型),⑤ 是"写"(折入状态);⑥ 决定何时把「块与块之间的运行和 partial」归档成「不可变的块表示」。这就是 AttnResState 这台"深度流水线状态机"的完整轨迹,与 online-softmax 两阶段加速完全对应。
2.4.7 如果不用 AttnResState,会造成什么困难?
困难 1:内存爆炸(Full AttnRes 的 O(Ld))
如果不分块(Block),每层都要保留之前所有层的完整输出:
# 不用 AttnResState(朴素 Full AttnRes)
all_outputs = [embedding] # 每加一层 append 一次
for layer in layers:
h = attention_mix(all_outputs) # 对所有历史做 softmax
all_outputs.append(layer(h))
# 内存 = L * d,L=96 层时 96 份完整激活
K3 用 96 层,96 份 hidden state 常驻内存 → pipeline stage 间通信量爆炸,GPU 内存吃不消。
困难 2:退化为标准残差(失去选择性)
如果根本不做"跨层注意",就退化成普通残差连接:
# 标准残差(无 AttnRes)
h = embedding
for layer in layers:
h = h + layer(h) # 无差别累加
这等价于 alpha_i = 1/L(均匀权重)——每一层对历史的影响完全相同,模型无法学习"第 30 层应该多读 embedding、少读第 5 层"这种选择性。
困难 3:手写状态机容易出错
Block 塌缩的边界条件很微妙:
- 何时归档 partial?(满
block_units 时)
- 最后一个不满的 partial 怎么办?(
finalize() 兜底)
- 如何对 gradient checkpoint 友好?(
detach() 深拷贝)
手写这些逻辑每个模型都要重新实现一遍,容易写出 off-by-one 错误(比如少归档最后一个 partial、或 partial 没清空)。
困难 4:失去可解释性窗口
AttnResState.attention_weights() 暴露 alpha 分布用于分析/可视化——能看到"第 L 层到底读了哪些源"。没有这个抽象,调试"为什么模型学不出选择性"就只能盲猜。
一句话总结
AttnResState 是用 O(Nd) 内存实现"深度维 softmax 注意力"的状态机,它解决了:
问题
不用它
用它
内存
O(Ld) = 96 份激活
O(Nd) = 8 份激活
选择性
退化为均匀残差
每层学习专属 alpha
正确性
手写状态机易错
统一的 init/mix/add/finalize
可解释性
黑盒
可读 alpha 分布
2.4.8 AttnResState 如何解决问题
FullAttentionResiduals 就是 block_units=1 的特例——证明 Block 和 Full 在数学上等价,只是内存策略不同。这是理解整个设计的钥匙。
问题 1:内存爆炸 → add_output() 的块塌缩
代码:attn_res.py:178-195
def add_output(self, state: AttnResState, output: Tensor) -> AttnResState:
state.partial = output if state.partial is None else state.partial + output
state.units_in_block += 1
if state.units_in_block >= self.block_units:
state.blocks.append(state.partial) # ★ 块塌缩发生
state.partial = None
state.units_in_block = 0
return state
机制:每个子层的输出先累加进 partial(running sum),写满 block_units 个后才"归档"为 blocks 中的一个条目。K3 用 block_units=12,意味着 12 个子层输出被压缩成 1 个块表示。
数学等价性:blocks[n] = Σ_{i∈block_n} output_i(求和塌缩)。虽然求和有损,但实验表明它保留了足够信息让后续的 softmax 注意仍能区分块。
内存对比(K3, d=7168):
形式
内存
96 层 × 7168
Full
L × d
688 KB/token(爆炸)
Block (N=8)
N × d
57 KB/token(可接受)
问题 2:选择性 → mix() 的深度维 softmax
代码:attn_res.py:157-176
def mix(self, state: AttnResState, unit_idx: int) -> Tensor:
sources = state.sources() # [emb, b_1, ..., b_n, partial?]
if len(sources) == 1:
return sources[0]
stacked = torch.stack(sources, dim=0) # (n, b, t, d)
keys = rms_norm(stacked, self.key_norm_weight) # 防垄断
query = self.queries[unit_idx] # (d,) 每层专属
scores = torch.einsum("nbtd,d->nbt", keys, query)
weights = torch.softmax(scores.float(), dim=0) # 沿源维归一化
return torch.einsum("nbt,nbtd->btd", weights, stacked)
机制:每个 unit(子层)有独立的可学习伪查询 self.queries[unit_idx],对所有可见源(已完成块 + 当前 partial)打分,softmax 归一化后加权混合。
这就是"选择性"的物理实现:
queries[unit_idx] 高维向量是可学习的"地址"
- 训练后模型可能学到:第 30 层应该多读
blocks[0](embedding),少读 blocks[3]
- 标准残差只是
weights = 1/n 的特例——mix() 把它推广为可学习的分布
RMSNorm 的作用(attn_res.py:172):防止某层输出幅值过大导致 scores 被它垄断,保证 softmax 不塌缩成 one-hot。
问题 3:正确性 → finalize() 的兜底逻辑
代码:attn_res.py:197-208
def finalize(self, state: AttnResState) -> Tensor:
if state.partial is not None: # ★ 兜底:尾巴的 partial 也要归档
state.blocks.append(state.partial)
state.partial = None
state.units_in_block = 0
return self.mix(state, self.n_units) # 用第 n_units 个 query 做最终聚合
机制:K3 有 96 层 ÷ 12/块 = 8 个完整块,但如果改成 100 层 ÷ 12/块 = 8 块 + 4 层尾巴,第 9 个块是不满的。finalize() 检查 partial is not None,把这个不满的尾巴也归档进 blocks,确保所有子层输出都被纳入最终聚合,一个不漏。
这就是手写状态机容易出错的地方:
- 忘记归档尾巴 → 最后 4 层输出被丢弃
- 忘记清空 partial → 下次 forward 状态污染
- 忘记用
n_units 作为 query index → 用了某个中间层的 query 做最终聚合
AttnResState 把这 3 个坑全部封装在 finalize() 里。
问题 4:可解释性 → attention_weights() 的暴露
代码:attn_res.py:210-221
def attention_weights(self, state: AttnResState, unit_idx: int) -> Tensor:
sources = state.sources()
stacked = torch.stack(sources, dim=0)
keys = rms_norm(stacked, self.key_norm_weight, self.eps)
scores = torch.einsum("nbtd,d->nbt", keys, self.queries[unit_idx])
return torch.softmax(scores.float(), dim=0) # (n, b, t)
机制:与 mix() 共享前 4 行(打分核完全相同),但不做加权求和,直接返回 alpha 分布。形状 (n, b, t) 告诉你"unit_idx 这个子层,对每个源、每个 batch、每个 token 位置分配了多少注意力权重"。
这就是 debug 的窗口:
state = attn_res.init_state(embedding)
for i, layer in enumerate(layers):
h = attn_res.mix(state, i)
out = layer(h)
state = attn_res.add_output(state, out)
if i % 12 == 0: # 每块结束看一次
alpha = attn_res.attention_weights(state, i)
print(f"layer {i}: alpha over {len(state.sources())} sources = {alpha}")
没有这个方法,你只能看 mix() 的最终输出,完全不知道模型在深度维上"读"了哪些层。
总结:状态机的 4 个方法对应 4 个问题
方法
行号
解决什么问题
关键操作
init_state()
151-155
初始化(embedding 永远是源)
blocks=[embedding]
mix()
157-176
选择性(softmax 深度注意)
queries[unit_idx] × RMSNorm × softmax
add_output()
178-195
内存(块塌缩 O(Ld)→O(Nd))
满 block_units 时归档 partial
finalize()
197-208
正确性(兜底尾巴)
不满块也归档 + 最终聚合
attention_weights()
210-221
可解释性(暴露 alpha)
只打分不混合
Block 和 Full 的等价性(attn_res.py:230-244):FullAttentionResiduals 就是 block_units=1——每个输出立即归档成"块",数学上和 Block 完全等价,只是内存策略不同。这证明了块塌缩不损失表达能力,只是工程上的内存优化。
2.5 online-softmax 两阶段算法(论文 Algorithm 1)
2.5.1 原理
问题:Full/Block AttnRes 在推理时是逐 token 的(decode)。要算 h_l = softmax(w_l·K)·V,理论上需要完整 softmax(读所有源)。但 Block 形式下,「已完成块的表示」是固定不变的(前块已经写完,不会变),只有「当前 partial」是逐 token 在变。
难点:不想每 token 都对所有块重新做完整 softmax。
思路(两阶段):
- Phase 1(跨块,可并行):对当前块的所有
S 个 site(该块内的各子层),一次性算出它们对所有已完成块的 logits → 得到每 site 的「块间统计量」(online-softmax 的 (max, sum-exp, weighted-sum))。
- Phase 2(块内,顺序):每个 site 只需要额外把「当前 partial」的 logit 与 Phase1 统计量 merge(在线合并),再 normalize → 得到最终输出。因为 partial 每 token 变,而块间统计量复用,decode 成本大幅降低。
专家:这是把 softmax 拆成「可复用的块间 part + 变化的块内 part」的 online softmax 技巧,与 flash attention 的同款思想(把 attention 分解成可 merge 的 (m, l, Σ) 三元组)。
三个库对比:
- kimi3-master:朴素 torch(无两阶段)。
- kimi-k3-pytorch:实现了完整 online-softmax 两阶段(
online_softmax.py + two_phase.py),且 BlockAttentionResidualController 提供 backend="eager"|"two_phase" 开关。
- SGLang:在 CUDA kernel 里把「对 bank 的 online softmax」直接 fused 进单 kernel。
2.5.2 对比基线
维度
eager
two_phase(online)
对已完成块的重复计算
每次 site 都重算
只算一次(Phase1 向量化)
需要的数据结构
sources 列表
(max, Σexp, weighted_sum) 统计量
decode 单 token 成本
O(N·d)(每 token 重扫全块)
O(d)(只 merge partial)
2.5.3 反方辩手
- a) 反对:多了一套统计量状态,复杂;且 phase1 需要在块开头预先跑所有 site 的块间 logits(内存 = 块内 site 数 × 块数 × B×T)。
- b) 失败假设:假设「已完成块」在 decode 期间不参与 partial 的在线更新;若 partial 与已完成块之间存在耦合(例如分层 RMSNorm),online 合并的数学成立性可能受考验。
- c) 胜出条件:decode 场景、长序列、块数 N 固定。失败:训练/prefill 全量序列(full softmax 直接算即可,省复杂度)。
- d) 更稳做法:放弃两阶段,每 token 重算(简单可靠,但 O(N) 每 token)。
- e) 受损方:实现者/推理引擎(复杂度);换来性能。
2.5.4 复杂度
- eager decode:每 token 每 site 重扫 N 个块 → O(N·B·T·d)(T 为 batch tokens)。
- two_phase decode:Phase1 每块一次 O(N·B·T·d),Phase2 每 token 每 site O(d)。节省量随「一个块内的 site 数 S」增大而增加。
- 论文报告:inference overhead < 2%(典型 workload,Table 1)。
2.5.5 参数敏感性
backend 开关(eager/two_phase)直接控制此算法。S 越大(块内 site 越多),two_phase 收益越大。
logits_in_fp32 / weighted_sum_in_fp32(kimi-k3-pytorch config)控制精度策略——默认 fp32 累加,保证两阶段 merge 的数值稳定性。
2.5.6 工程实现

代码(参考库):kimi-k3-pytorch-main 的 online_softmax.py(depth_softmax_stats / single_source_stats / merge_depth_softmax_stats / normalize_depth_softmax_stats / weights_from_stats),以及 two_phase.py(precompute_inter_block_stats / score_single_partial)。
本项目对应:本项目采用 kimi3 风格(朴素 torch 实现),未实现两阶段。若要参考生产做法,可对照上述文件。
完成度:参考实现已提供(kimi-k3-pytorch),本项目未实现(采用朴素 torch,与 kimi3 一致)。
2.6 生产级 fused kernel 原理(SGLang;TMA + online softmax + 通信融合)
2.6.1 原理
问题:K3 真实部署时,AttnRes 每 token 要对 bank(最多 8 个块表示快照 + 1 个 prefix)做 online softmax 聚合 + 输出 RMSNorm。在 GB200/SM100 上这是高频、低算量、高访存操作,纯 PyTorch 逐层调用会卡在 kernel launch / 内存带宽。
难点:把「读 bank 行 → score → online softmax → weighted combine → 输出 RMSNorm」整条链 fused 成一个 kernel,还要处理 TP(张量并行)下的通信。
思路(SGLang):
- 单 kernel:warp-specialized TMA kernel。一个 persistent CTA/SM:producer warp 用 bulk TMA 一次拉一行 bank 到 smem ring(2 个 chunk slot 双缓冲),8 个 consumer warp 做 score + online softmax fold + 写 TMEM。
- 参数:
bank[T, NB, H](NB=最多块表示数=8+1,H=7168),cw(score_norm_weight×proj_weight 预乘),ow(输出 RMSNorm weight),eps(两个 norm 共用)。nvb = 有效 bank 行数(1..8)。
- 通信融合:
attn_res_fused_pull_rs(NVLS pull 规约 + local residual + fused)与 attn_res_fused_direct_ag(直接 multicast all-gather 融合)——把 AttnRes 聚合与 TP all-reduce 合二为一,省一次整张往返。
专家视角:这是「online softmax 状态 (m,l,Σ) 天然可 merge」特性在 kernel 层的极致利用:bank 行是"已完成块",其统计可被并行 fold;write_prefix 把当前 prefix 行也写回 bank,支持后续复用。_TMA_BEST_CONFIG 是每 nvb 的调参表(GB200/GB300 实测),occupancy/consumer_regs 是 CTA 调度权衡。
2.6.2 对比基线
维度
朴素(kimi3 torch)
SGLang fused TMA
kernel 数
多次(norm/score/softmax/mix/norm)
1 个 fused kernel
通信
独立 all-reduce
fused 进 kernel(pull_rs/direct_ag)
内存
多次 HBM 往返
TMA 批量 + TMEM staging,最少往返
平台
任意
SM100a+(B100/B300/GB200 等)
参数限制
任意 d
H==7168 模板参数
2.6.3 反方辩手
- a) 反对:这套 kernel 复杂度极高,且 H=7168 硬编码、SM100 专用——不可移植、难维护。
- b) 失败假设:假设"每 token 聚合 bank"值得如此大的 kernel 工程投入;若该算子只占 <1% 时间,就是过度工程。
- c) 胜出条件:大 batch、多 token、SM100 集群(GB200 为主力时);失败:小规模 / 非 SM100。
- d) 更稳做法:用 Triton 通用 kernel 折中(本项目可接受)。
- e) 受损方:运维方(版本约束、架构绑定)。
2.6.4 复杂度
- HBM 流量:bank 读 = nvb×H 每 token;输出 = H。TMA 批量把读合并。通信:pull_rs/direct_ag 把 all-reduce 从独立 O(T·H) 变 fused(省一次 HBM 写读)。
- 具体数字需跑 benchmark;论文报告 Block AttnRes inference overhead <2%。
2.6.5 参数敏感性
nvb(有效块数)、chunk_rows(每 chunk 行数)、occupancy(CTA/SM)、consumer_regs。_TMA_BEST_CONFIG 表(sglang attn_res.py)给出每 nvb 的最优 (chunk_rows, occupancy, consumer_regs)。
write_prefix:是否把 prefix 行回写 bank(后续可作 prefix 复用)。
2.6.6 工程实现
代码(参考):sglang-main 的 python/sglang/srt/layers/attn_residual.py(Python 层调度,300+ 行)+ python/sglang/srt/kernels/kimi_k3/attn_res.py(TMA kernel wrapper)+ jit/csrc/kimi_k3/attn_res/fused_tma.cuh(CUDA 内核,946 行)。
本项目:本项目采用朴素 torch(与 kimi3 一致),未实现 fused kernel;本文档将其列为「生产级参考」。
完成度:参考实现已提供(SGLang),本项目未采用。
2.7 对比对象 —— mHC(Manifold-Constrained Hyper-Connections)
2.7.1 原理
问题:HC(Hyper-Connections)把残差变成可学习的多条平行流,但丧失了恒等映射性质(identity mapping 被破坏),训练不稳定、难扩展。
mHC 的解决:把残差映射矩阵投影到 Birkhoff 多胞体(双随机矩阵)——行列和都等于 1。这样 P x 成为输入特征的凸组合(权重非负、和=1),特征均值守恒、信号范数被严格限制,组合闭包保恒等稳定性。
- 直觉:HC 是"多车道",mHC 给每车道加了"所有车道权重加和为 1"的护栏(每列、每行都归一),既保住了多车道的信息流通,又不让信号越飙越大。
- 准专业:
H_l^{res} x_l 其中 H_l^{res} 是双随机矩阵(非负、行和=1、列和=1)。投影用 Sinkhorn-Knopp 算法(迭代行/列归一化)把任意矩阵投影到 Birkhoff 多胞体。
- 专家:
m 条平行流 H_l ∈ R^{d×m},更新 H_l = H_{l-1} A_l + f_{l-1}(H_{l-1} α_{l-1}) β_{l-1}^T。展开后有效权重 M_{i→l} = β_i^T A_{i+1→l} α_l,是 m-semiseparable 的深度维 linear attention(state expansion 到 d×m,提高 semiseparable rank)。双随机约束使 A_l 乘积 A_{i→l} 保持性质(范数守恒、composition closure),恢复 identity 稳定性。
AttnRes 与 mHC 的本质差异(按论文 Table 5 与 §6.2):
- 两者都是 input-dependent(动态)。真正的区别是注意力范式:
- mHC:深度维 linear attention(m-semiseparable,低秩)。权重是「学习 + 双随机约束投影」得到的线性变换(
M_{i→l}=β^T_i A_{i+1→l} α_l),softmax 的竞争性归一化缺失——各源概率质量不竞争。
- AttnRes:深度维 softmax attention(稠密 rank-L)。权重
α_{i→l} 是 softmax 归一化的 input-dependent(直接对 v_i 内容做 exp 打分),概率质量在源间竞争,形成更锐利的选择。
- 论文 Table 4 中 AttnRes(1.737/1.746)优于 mHC(1.747)的原因就是:softmax 的竞争性归一化 + 跨层直接检索。
mHC 会维护多条并行的 Residual Stream。每一层先动态混合这些信息流,形成这一层的输入,再把这一层的输出分发回不同的信息流。它还会约束混合过程,避免模型变深后信息和梯度被无限放大。
Attention Residuals 则更像为每一层提供一份历史目录,让它可以直接找到前面某个阶段产生的信息,并选择性地读取。
只看理论表达能力,Attention Residuals 的上限可能更高。mHC 仍然要把历史信息递归压缩进固定数量的 Residual Stream;Full Attention Residuals 则可以让每个 token 直接关注所有较浅层的输出。打个比方,mHC 更像 RNN(循环神经网络)或线性注意力,信息通过有限状态逐层传递;Full Attention Residuals 更像 Transformer,可以直接跨层读取信息。
但实际效果仍取决于具体实现。K3 使用的并不是所有层之间直连的 Full Attention Residuals,而是 Block Attention Residuals:先把模型层划分成多个 block,在每个 block 内汇总信息,再在不同 block 之间做 Attention。
2.7.2 对比基线表
维度
Standard Residual
mHC
AttnRes (Block)
更新公式
h_l = h_{l-1} + f_{l-1}(h_{l-1})
H_l 多流 + 连接矩阵
h_l = Σ α_{i→l} v_i
状态数量
1
m(如 4 条流)
1 伪查询 + N 块表示
权重来源
固定单位 1
学习 + 双随机投影(Sinkhorn)
学习 + 输入相关 softmax
是否 input-dependent
否
是(Dynamic,Table 5)
是
注意力范式
深度维等权加法
深度维 linear attention(m-semiseparable)
深度维 softmax attention(dense softmax)
恒等稳定性
好
双随机保证(identity 保真)
zeros 初始化平滑出发(等效均匀)
参数开销
0
中等(连接矩阵 A + 转换 α/β)
低(每层一个 w_l)
内存 I/O
~3d/层
~34d/层(m=4)
~5.5d/层(Block)
性能(16 层,论文 Table 4)
1.766
1.747
1.737 / 1.746
并行 friendly
是
一般
Block 形式友好(块边界通信)
可解释性
无
一般
好(α 可直接可视化)
亲密度(本项目)
基线
未实现(仅对照)
已实现
三条路线并排示意图(ASCII,重点:传给"深度历史"的信息流差异):

2.7.3 反方辩手
- a) 反对 AttnRes:固定伪查询无法做 input-dependent query;mHC 至少在结构上是动态的。且 AttnRes 的 softmax 权重在推理时主要由静态向量 w_l 决定(query 不含 token 信息),所谓 input-dependent 主要体现在 key 上。
- b) 失败假设:假设"每层的读取模式静态即可"。论文承认 input-dependent query 能再涨(1.731),说明固定伪查询仍有提升空间。
- c) 胜出条件:深度读模式相对稳定(关注 embedding / 邻居层 / 特定块)的任务;失败:需要 per-token 动态深度读。
- d) 更稳做法:AttnRes + input-dependent query(论文指出可做但需权衡)。
- e) 受损方:无显著(两者都小开销)。
2.7.4 复杂度
- mHC:状态
d×m,每层 I/O ~34d(m=4)远高于 AttnRes ~5.5d(Block,Table 1)。
- AttnRes:算术 O(N·d)(Block)/ O(L·d)(Full),内存 O(N·B·T·d)(Block)。
- 结论:AttnRes 在「表达力相仿或更好」的同时,I/O 低约 6 倍(34d vs 5.5d)。
2.7.5 参数敏感性
- mHC:流数 m(2/4/8)、Sinkhorn 迭代数、use_dynamic_mapping。
- AttnRes:块大小 N、伪查询初始 w=0、RMSNorm eps。
- 论文:N≈8 最优,mHC 的 m 视任务(通常 4)。
2.7.6 工程实现
- 两者都是「残差可学习化」家族,但一个走约束矩阵(线性注意)、一个走软注意力。本项目选择 AttnRes(K3 采用),因其为 softmax attention(与序列建模同构)、实现简单、I/O 低、可解释性好。
2.7.7 对比图(标准残差 vs mHC vs AttnRes)

- 上排 3 图:架构示意(标准残差蓝 / mHC 琥珀 / AttnRes 红主角)。
- 下排 3 图:深度-时间对偶性、每层 I/O(Table 1:3d vs 34d vs 5.5d)、Table 4 困惑度(1.766 vs 1.747 vs 1.737/1.746)。
- 图表关键读数(实测值回填):
- Full 1.737 / Block 1.746 / mHC 1.747 / PreNorm 1.766(Table 4)
- 每层 I/O:标准残差 ≈3d、mHC(m=4) ≈34d、AttnRes Block ≈5.5d(Table 1)
- 关键推论:图上 AttnRes 同时满足"更低 loss + 更低 I/O",直接支撑"softmax attention 兼具表达力与效率"观点。
2.8 基础设施:跨阶段缓存 + 高效 Prefill(论文 §4)
这是 "Block AttnRes 成为标准残差即插即用替代品" 的工程前提。
2.8.1 训练:跨阶段缓存(Cross-stage Caching)
问题:标准残差下 pipeline parallelism 只在相邻 stage 间传一个固定大小的 hidden state(与 pipeline 深度无关)。Block AttnRes 需要每个 stage 的所有已累积块表示来做跨块注意,naïvely 每次 stage 过渡都传完整历史 → 冗余通信。
推导(论文 Eq. 7-8):设 interleaved pipeline 有 P 个物理 stage、每物理 stage V 个虚拟 stage,C = P·V 个 chunk,第 j 个 chunk 累积 j·N_p 个块(N_p = 每物理 stage 每 token 产生的块数)。
-
naïve 通信(每次过渡传全部累积块):
Comm_naive = Σ_{j=1}^{C-1} j·N_p·d = C(C-1)/2 · N_p·d (Eq. 7)
-
缓存后(块在本地位存,跨 stage 只传增量;第一虚拟 stage 无缓存正常累积,v≥2 只传 ~P·N_p 增量块):
Comm_cached = P(P-1)/2 · N_p·d (第一虚拟 stage)
+ (V-1)·P²·N_p·d (后续虚拟 stage) (Eq. 8)
效果:每过渡的峰值通信从 O(C) 降到 O(P),是 V× 提升,且能完全与稳态 1F1B 的计算重叠。论文 Fig. 3 用 P=4、V=2 举例:第二虚拟 stage 时缓存消除了 6 次冗余块传输(每 stage 只在块边界才传增量 +[b1,b2] 而非全部 [b0,b1,b2,...])。注:块边界无需对齐物理 stage 边界(Fig. 3 中每块跨越两个物理 stage)。
训练开销:无 PP 时 Block AttnRes 训练开销可忽略;有 PP 时端到端 overhead < 4%(论文 §4.1)。
2.8.2 并行状态下的 AttnResState
AttnResState 不是分布式的,每个 stage 有自己的副本。
每个 pipeline stage 都持有自己的 AttnResState 实例,blocks 字段的内容随 stage 递增,即 blocks 列表内容是从上游复制/继承来的。
单机视角
单机上,整个模型只有一个 AttnResState。
# 单机视角(你想象的)
state = attn_res.init_state(emb)
for layer in all_96_layers:
h = attn_res.mix(state, i)
out = layer(h)
state = attn_res.add_output(state, out) # blocks 累积到 9 个
final = attn_res.finalize(state)
分布式视角
物理 Rank 0 物理 Rank 1 物理 Rank 2
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ AttnResState │ │ AttnResState │ │ AttnResState │
│ blocks=[b0] │──→ │ blocks=[b0,b1] │──→ │ blocks=[b0,b1,b2]
│ partial=None │ 传输 │ partial=b2_acc │ 增量 │ partial=None │
│ units_in_block=0 │ units_in_block=1 │ units_in_block=0
└─────────────────┘ └─────────────────┘ └─────────────────┘
处理 layer 0-1 处理 layer 2-3 处理 layer 4-5
pipeline parallelism 下,每个物理 rank 只跑一部分层,所以每个 rank 需要自己的 state:
# Rank 0(layer 0-23)
state_r0 = attn_res.init_state(emb)
for i in range(24):
h = attn_res.mix(state_r0, i)
out = layer_r0[i](h)
state_r0 = attn_res.add_output(state_r0, out)
# state_r0.blocks = [emb, b1, b2](2 个完整块 + partial)
# Rank 1(layer 24-47)—— 不重建,继承 r0 的 blocks
state_r1 = AttnResState(blocks=state_r0.blocks.copy(), partial=state_r0.partial, ...)
for i in range(24, 48):
h = attn_res.mix(state_r1, i)
out = layer_r1[i-24](h)
state_r1 = attn_res.add_output(state_r1, out)
# state_r1.blocks = [emb, b1, b2, b3, b4](4 个完整块)
关键区别
概念
是不是分布式的
说明
AttentionResiduals(nn.Module)
是(共享参数)
所有 stage 共享同一份 queries 和 key_norm_weight 权重(数据并行同步)
AttnResState(dataclass)
否(每 stage 独立)
每个 stage 一个实例,blocks 内容递增
blocks 列表
跨 stage 复制
每个 stage 的 blocks 包含上游所有块 + 自己新产生的块
2.8.3 Cross-stage Caching 原理
① Cross-stage Caching 到底缓存什么?
缓存的是 Block 表示 bn(不是 layer 输出、不是 hidden state、不是梯度)。
回看 attn_res.py:178-195 的 add_output():
if state.units_in_block >= self.block_units:
state.blocks.append(state.partial) # ★ 这就是要被缓存的东西
每个 blocks[i] 是一个已完成块的塌缩表示 b_n = Σ_{j∈B_n} f_j(h_j),shape (B, T, d)。
在 pipeline parallelism 下,物理 rank 处理多个虚拟 stage,每个虚拟 stage 会产生若干块表示。Cross-stage caching 就是把这些 b_n 留在本地内存,下次遇到同一 micro-batch 时不再重传。
② 为什么这些可以缓存?
关键洞察:块表示 bn 一旦归档,就不再变化。
看 attn_res.py 的状态机:
state.partial = output if state.partial is None else state.partial + output
# 满 block_units 后才归档
state.blocks.append(state.partial)
state.partial = None # 归档后 partial 清空,开始新块
b_n 是求和塌缩——一旦块写满(units_in_block >= block_units),这个块的表示就定型了,后续 layer 不会再修改它。这跟 KV cache 的"append-only"性质完全一样:
特性
Block 表示 bnb**n
KV cache 的 K/V
何时定型
块写满时
token 写入时
后续是否修改
否(只读)
否(只读)
内存生命周期
整个 forward/backward
整个 decode session
所以可以缓存:rank 1 在第 1 个虚拟 stage 把 [b_0, b_1] 发给 rank 2,rank 2 处理完存下来;第 2 个虚拟 stage 时 rank 1 又产生新的 [b_2],只需发 +[b_2],rank 2 把它和本地缓存的 [b_0, b_1] 拼起来就是完整历史 [b_0, b_1, b_2]。
③ 新阶段会产生什么?
每个新虚拟 stage 会产生 ∼P/N_p 个新块表示(P=物理 stage 数,N_p=平均每 stage 的块数)。
用论文 Figure 3 的例子(P=4, V=2):
Virtual Stage 0(无缓存):
Rank 0 产生 [b_0]
Rank 1 收到 [b_0],产生 [b_1] → 发给 Rank 2: [b_0, b_1]
Rank 2 收到 [b_0, b_1],产生 [b_2] → 发给 Rank 3: [b_0, b_1, b_2]
Rank 3 收到 [b_0, b_1, b_2],产生 [b_3]
Virtual Stage 1(有缓存):
Rank 0 已有 [b_0],产生新的 [b_0'] → 发给 Rank 1: +[b_0']
Rank 1 已有 [b_0, b_1],收到 +[b_0'],产生 [b_1']
→ 发给 Rank 2: +[b_0', b_1'] (只发增量!)
Rank 2 已有 [b_0, b_1, b_2],收到 +[b_0', b_1'],产生 [b_2']
→ 发给 Rank 3: +[b_0', b_1', b_2']
关键差别:
- Naïve(无缓存):每次发完整历史
[b_0, b_1, b_2, ...],通信量随 stage 数平方增长
- Cached:只发增量
+[b_new],通信量随 stage 数线性增长
论文 Eq. 7→8 给出量化:
- Naïve:Commnaı¨ve=C(C−1)2NpdCommnaı¨ve=2C(C−1)Npd
- Cached:Commcached=P(P−1)2Npd+(V−1)P2NpdCommcached=2P(P−1)Npd+(V−1)P2Npd
当 V=2、P=4 时,消除 6 次冗余传输(论文原话)。
回到 AttnResState:缓存的工程映射
AttnResState.blocks 就是 Cross-stage Caching 的本地缓存:
@dataclass
class AttnResState:
blocks: list[Tensor] # ★ 这就是 cache:已归档的块表示
partial: Tensor | None # ★ 正在累计的(还没定型,不能缓存)
units_in_block: int # 进度计数
blocks:跨 stage 缓存的对象(只读、可复用)
partial:当前 stage 正在产生的增量(未定型,必须传输)
pipeline 边界的传输就是:
# 发送方:把当前 stage 新产生的 blocks 发给下一个 rank
new_blocks = state.blocks[len(receiver_cached_blocks):]
send(new_blocks)
# 接收方:把新块追加到本地缓存
state.blocks.extend(new_blocks)
partial 永远不跨 stage 传输——它必须在当前 stage 内完成塌缩成 blocks 才能进入缓存。这就是"为什么 partial 不能缓存":它还在变,不是只读的。
一句话总结
问题
答案
缓存什么
已归档的 Block 表示 b_n(AttnResState.blocks)
为什么能缓存
块一旦写满就只读不变(求和塌缩的 append-only 性质)
新阶段产生什么
∼P/N_p 个增量块 +[b_new],只发增量不重发历史
Cross-stage Caching 的精确含义
缓存的不是"AttnResState 的一部分",而是"blocks 列表的前缀"。
Rank 1 在第 1 个虚拟 stage 结束后,把自己的 blocks=[b0, b1] 发给 Rank 2。Rank 2 存进自己的 state.blocks。第 2 个虚拟 stage 时,Rank 1 又产生新的 b2,只发 +[b2],Rank 2 把它 append 到自己的 state.blocks。
所以:
blocks 物理上分布在多个 rank 的内存里(每个 rank 一份副本)
- 但逻辑上它们是同一个列表的不同前缀
- Cross-stage caching 就是避免重复发送已经共享的前缀
一句话总结
问题
答案
缓存的是 AttnResState 的一部分?
不准确。缓存的是blocks 列表的前缀(跨 rank 共享的已归档块)
AttnResState 是分布式的?
不是。每个 rank 有自己的独立实例
系统中应该只有一个 AttnResState?
单机是,pipeline 下每个 rank 一个
blocks 遍布在每个 stage?
是的,但内容递增——每个 rank 的 blocks 包含上游所有块 + 自己新产生的
2.8.4 讨论
为什么 activation recomputation 被影响
标准 activation checkpointing 可以丢掉中间激活,反向时从 checkpoint 重算。但 Full AttnRes 要让后续很多层继续访问旧层输出;如果丢掉它们,后续层无法直接算深度注意力,或者必须频繁重算历史输出。于是它把"可释放的中间态"变成"跨多层被读取的 KV-like 源"。
Block AttnRes 的妥协是只保存每个块的聚合表示和当前块 partial prefix。这样 activation checkpointing 仍可丢掉 softmax 中间量和大量块内细节,保留少量块级 bank。
为什么 pipeline parallelism 变贵
普通 PP stage 边界只传 hidden_states 和常规 residual。AttnRes 需要下游 stage 也能看到历史块,所以边界还要传 block bank。若 Full AttnRes 传每层历史,成本随层数涨;Block AttnRes 传块表示,成本随块数涨。
cross-stage caching: 同一物理 stage 在交错流水线的不同 virtual stage 会重复看到部分历史块,因此可缓存已见块,只传新增块,减少冗余通信。
2.8.5 推理:两阶段计算(Algorithm 1)
问题:Block AttnRes 的逐层注意计算类似自回归解码——块表示是被所有层共享复用的"KV cache"。naïve 实现每层都要全扫一遍所有前置块 → O(L·N) 内存访问。关键洞察:伪查询 w_l 与前向计算解耦(是学习参数),所以一个块内的所有 S 个 query 可以批量成一个矩阵乘,把 S 次读摊销成 1 次。
Algorithm 1 两阶段:
-
Phase 1(跨块,并行):对块 n 的所有 S 层 query [w_l]_{l∈B_n} 一次性对块表示 [b_0..b_{n-1}] 算注意,返回输出 + softmax 统计(max 和 log-sum-exp)。
块 n 的 S 个 query {w_l} 在执行前全部已知,所以前面 (n-1)S 个 key-value 对只需从 HBM 读一次,被 S 个 query 复用:
Read_inter(n) = 2(n-1)S·d # 因子 2 = key+value Read_inter = Σ_n 2(n-1)S·d = 2Sd·N(N-1)/2 = dL(N-1) Write_inter = L·d
-
Phase 2(块内,顺序):逐层用不断演化的 partial sum 算块内注意,然后通过 online softmax 与 Phase 1 输出合并(m_l = max(m^(1), m^(2)),h_l = (e^{m1-m}·o1 + e^{m2-m}·o2) / (e^{m1-m}·l1 + e^{m2-m}·l2))。因为 online-softmax 合并是逐元素操作,Phase 2 可自然与周边算子 kernel 融合,进一步降 I/O。
块内第 t 层(1≤t≤S)依赖前面 t-1 个块内 key-value:
Read_intra(n) = Σ_{t=1}^S 2(t-1)d = S(S-1)d Write_intra = S·d
-
总摊销 I/O(除以 L):
Read per layer = dL(N-1)/L + N·S(S-1)d/L = (N-1)d + (S-1)d = (S+N-2)d Write per layer = 2d Total I/O = (S+N)d
结论:批量跨块读把 Full AttnRes 的每层 I/O 从 O(L) 降到 O(S+N)。典型值 L=128, N=8, S=16 → Full = 24d(对比 Block 的 (N/S+5)d=5.5d,Table 1)。Block 形式进一步把存储的表示数从 L 降到 N(每块压成一个向量),故 I/O 更低。
推理两阶段的直觉读法:
- 批量计算当前 block 中多层 query 对历史 blocks 的注意力统计。
- 层内顺序处理当前 partial prefix,并用 online softmax 合并历史块统计。
核心收益:历史块只读一次或少数几次,避免每层重复扫完整 bank;online softmax 只维护 max、denominator、weighted sum,不必物化大矩阵。
结果(Table 1):典型参数 L=128, N=8, S=16, m=4:
方案
Read
Write
Total I/O
标准残差
2d
d
3d
mHC(m 流)
—
—
34d
AttnRes Full(两阶段)
—
—
(S+N)d = 24d
AttnRes Block(两阶段)
—
—
(N/S+5)d = 5.5d
注:Full 形式用两阶段调度也可从 O(Ld) 摊薄到 (S+N)d(见附录 C),只是块数 N 远大于 Block。
2.8.6 推理:内存高效 Prefill(sequence sharding)
问题:prefill 阶段存块表示需 N·T·d 元素——128K token 序列、8 块时高达 15 GB。
方案:把块表示沿序列维 shard 到 P 个 tensor-parallel 设备(每设备 N·(T/P)·d)。Phase 1 在本地序列分片上独立执行;Phase 2 的 online-softmax 合并融入标准 TP all-reduce 通信路径(output reduce-scatter → 本地合并 → all-gather 重建),并可自然与 RMSNorm 等 kernel 融合。
效果:每设备内存从 15 GB 降到 ~1.9 GB(128K/8 块/P=8);配合 chunked prefill(如 16K chunk)进一步降到 <0.3 GB/设备。
2.8.7 反方辩手
- a) 反对:这套基础设施(缓存 + 两阶段 + sharding)本身复杂度高,对小模型/单卡无意义。
- b) 失败假设:假设 PP 是大规模训练标配、块表示必须跨 stage 传。若未来互联带宽暴涨,Full AttnRes 的 O(Ld) 通信也可行(论文明确说这是 Full 形式的潜在未来)。
- c) 胜出条件:大规模 PP 训练 + 长上下文推理;失败:单卡教育/小模型。
- d) 更稳做法:不做优化,Block AttnRes 直接朴素实现(本项目采用)。
- e) 受损方:基础设施工程师(复杂度)。
完成度:本项目的朴素实现未包含跨 stage 缓存/两阶段/序列分片(与 kimi3-master 一致);本文档记录论文方案作参考。
2.9 学到的深度注意模式(论文 §5.4.2,Fig. 8)
论文可视化 16 层模型(Table 2 436M 行)在 Full 与 Block (N=8) 形式下学到的权重 α_{i→l}(Pre-Attn 与 Pre-MLP 分开展示)。三个关键观察:
- 保留局部性(Preserved locality):每层主要注意直接前驱(对角占优),但出现选择性的非对角集中——如 layer 4 注意早期源、layers 15-16 在 Block 设置下回溯远处 → 超出标准残差路径的学到的 skip connections。
- 层级专门化(Layer specialization):embedding
h_1 全程保留非平凡权重,尤其 Pre-Attn 层(深度维 attention sink,对应 §6.2 与序列维 attention sink 同源);Pre-MLP 输入的 diag 依赖更锐利(读最近表示),Pre-Attn 输入的感受野更广(attention 跨层路由信息,MLP 局部工作)。
- Block 保留结构:对角占优、embedding 持续、层级专门化都从 Full 转移到 Block → 块级压缩充当隐式正则,同时保留关键信息通路。
教学价值:本项目的 attention_weights()(attn_res.py)正是为此设计的分析窗口——可复现论文 Fig. 8 的"哪一层读了哪个源"。实验 demo 已演示学到权重收敛到 embedding 源(见 0x07)。
完成度:论文实验(Fig. 8),本项目可复现(attention_weights() + demo),但未做 16 层模型级可视化。
2.10 统一视角:残差连接作为结构化矩阵(论文 §6.2,Table 5)
深度混合矩阵 M:把残差变体统一表示为 h_l = Σ_{i=0}^{l-1} M_{i→l} v_i(v_0=h_1,v_i=f_i(h_i))。M 是 R^{L×L} 下三角矩阵,M_{i→l} 是层 l 给层 i 输出的权重。各变体的区别在:权重如何产生(fixed / learned / input-dependent)+ M 是否受低秩约束。semiseparable rank 是统一比较的标尺。
论文 Table 5(更新机制分类):

结构化矩阵视角(Fig. 9,L=4 示例,Block S=2):

- 标准残差:M = 全 1 下三角矩阵。
- Highway:
M_{0→l}=γ_{1→l}、M_{i→l}=g_{i+1}·γ_{i+1→l}(γ_{i→l}=Π_{j=i+1}^l (1-g_j) 是 carry product)。因累积积因子化通过标量门,M 是 1-semiseparable(与标准残差同 rank,但 input-dependent)。权重自动和为 1 → Highway 是 softmax-free 的深度维 stick-breaking attention。
- (m)HC:
M_{i→l}=β^T_i A_{i+1→l} α_l(A_{i→j}=Π_{k=i+1}^j A_k)。m×m 转移使 M 是 m-semiseparable。mHC 进一步要求每个 A_l 双随机,稳定跨深度累积积。
- Full AttnRes:
M_{i→l}=α_{i→l} 经 φ(w_l,k_i)=exp(w_l^T RMSNorm(k_i)) 归一化 → 稠密 rank-L。
- Block AttnRes:已完成块
B_n 内所有源 i 共享块级 key/value b_n → M_{i→l}=α_{n→l} ∀i∈B_n;当前块内每层额外注意演化 partial sum b_n^{i-1} → 每块内多一个额外源。有效 rank 介于 N 和 N+S 之间,在标准残差(N=1)与 Full(N=L)之间插值。
两个分析洞察(论文 §6.2):
- 深度维 attention sink:input-dependent 的 M 揭示某些层无论输入如何都持续吸引高权重(2.10 的 embedding 持续),与序列维 attention sink 同源。
- φ 可分解性 → 递推塌缩:当
φ(q,k)=φ(q)^T φ(k) 可分解时,深度注意塌缩成递推——正是 MRLA↔GLA、DDL↔DeltaNet 对应的结构根源;不可分解的 softmax 核让 AttnRes 保持稠密可检索。
2.10.1 与序列-深度对偶(论文 §6.1)
论文把深度递推与序列递推形式化对齐:TTT 把每个递推步视为对自监督 loss 的梯度下降 W_t = W_{t-1} - η∇ℓ(W_{t-1}; x_t),f 线性时退化为线性注意 S_t = S_{t-1} + k_t v_t^T;标准残差沿深度方向有同样加性形式(h_l 是状态,每层 f_l 是一个"梯度步")。
跨尺度的对应关系(论文指出):
- 序列维 data-dependent gates(RetNet, GLA)↔ 深度维 Highway
- delta rule ↔ DDL
- MRLA ↔ GLA(gated linear attention)
- 线性注意
S_t = S_{t-1} + k_t v_t^T ↔ 标准残差 h_l = h_{l-1} + f_{l-1}(h_{l-1})
这些方法都停留在递推范式内。AttnRes 更进一步:用直接跨层注意替换深度递推——正如 Transformer 用 self-attention 替换时间递推。论文明确:"Since the number of layers in current architectures remains well within the practical regime of softmax attention, we adopt vanilla depth-wise attention",并把"更高效(线性复杂度)替代"列为 future work。
2.11 最优架构分析(论文 §5.4.1,Fig. 7)
问题:AttnRes 是否改变最优的 depth-width-attention 权衡?尤其——既然它在深度维度上有优势,是否偏好更深的模型?
方法:固定计算预算(FLOPs ≈ 6.5×10^19)和激活参数量(≈ 2.3×10^8),枚举 5×5 网格:d_model/L_b ∈ {15,30,45,60,75} × H/L_b ∈ {0.3,0.4,0.5,0.6,0.7}(L_b=L/2 为 Transformer 块数,H 为注意力头数)。固定每专家 MLP 扩展比 d_ff/d_model ≈ 0.45,隔离纯架构影响。

结果(Fig. 7):
- 两方法共享大趋势:loss 随
d_model/L_b 增大、H/L_b 减小而下降,都在 H/L_b≈0.3 达优。
- AttnRes 在全部 25 个配置上低于 baseline,优势 0.019~0.063。
- 最优位置移动:baseline 在
d_model/L_b≈60(loss 1.847)达优;AttnRes 移到 d_model/L_b≈45(loss 1.802)。
推论:固定参数预算下,更小的 d_model/L_b = 更深更窄的网络 → AttnRes 能更有效利用额外深度。但论文明确警示:这不是部署推荐(更深模型推理延迟更高),而是揭示"AttnRes 在哪里收益最大"的诊断,可作为选架构时与推理成本一起权衡的因子。
完成度:论文实验(Fig. 7),本项目未复现(CPU 无法枚举 25 配置)。

0xFF 参考
- [1] Kimi Team. Attention Residuals(技术报告), arXiv:2602.15031, 2026. 仓库:https://github.com/MoonshotAI/Attention-Residuals
- [2] Kimi Team. Kimi K3 Technical Report §2.2(Eqs. 8-10 与 8 块×12 层=9 源配置), arXiv:2607.24653.
- [3] Zhenda Xie et al. mHC: Manifold-Constrained Hyper-Connections, arXiv:2512.24880.
- [4] Yongyi Yang & Jianyang Gao. mHC-lite: You Don't Need 20 Sinkhorn-Knopp Iterations, arXiv:2601.05732.
- [5] Yu Zhang et al. Kimi Linear: An Expressive, Efficient Attention Architecture, arXiv:2510.26692.
"原文地址: https://www.cveoy.top/t/topic/qHWi 著作权归作者所有。请勿转载和采集!