Kimi3 = GPT2 x 22580

22580。这就是 KimiK3(2026 年)中可以容纳多少个 GPT-2(2019 年)模型的数量。我们在七年时间里将规模扩大了 22,580 倍。但这仅仅……是规模的问题吗?

在这篇工作日志中,我将一步步讲解我们是如何走到这一步的,以及从那时起实际上发生了多少变化(或多么少的变化)。我们将追溯通往 KimiK3 的主要架构发展历程。

1、GPT-2

GPT-2 是一种仅解码器架构:

tok_emb = self.transformer.wte(idx) # token embeddings of shape (b, t, n_embd)
pos_emb = self.transformer.wpe(pos) # position embeddings of shape (t, n_embd)
x = self.transformer.drop(tok_emb + pos_emb)
for block in self.transformer.h:
    x = block(x)
x = self.transformer.ln_f(x)
logits = self.lm_head(x)
return logits

输入接收 token 和位置嵌入:

每个 transformer 块放大后看起来像这样:

class Block(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.ln_1 = LayerNorm(config.n_embd, bias=config.bias)
        self.attn = CausalSelfAttention(config)
        self.ln_2 = LayerNorm(config.n_embd, bias=config.bias)
        self.mlp = MLP(config)

    def forward(self, x):
        x = x + self.attn(self.ln_1(x))
        x = x + self.mlp(self.ln_2(x))
        return x

注意力过程:

        B, T, C = x.size() # batch size, sequence length, embedding dimensionality (n_embd)

        # calculate query, key, values for all heads in batch and move head forward to be the batch dim
        q, k, v  = self.c_attn(x).split(self.n_embd, dim=2)
        k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
        q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
        v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)

        # manual implementation of attention
        att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
        att = att.masked_fill(self.bias[:,:,:T,:T] == 0, float('-inf'))
        att = F.softmax(att, dim=-1)
        att = self.attn_dropout(att)
        y = att @ v # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
        y = y.transpose(1, 2).contiguous().view(B, T, C) # re-assemble all head outputs side by side

        # output projection
        y = self.resid_dropout(self.c_proj(y))
        return y

一旦生成最终的隐藏状态矩阵,语言模型头就将其映射为词汇表 logits。在自回归解码过程中,只需要最终位置的 logits 来选择下一个 token。

这是仅解码器生成的一个低效之处:模型为每个输入位置计算表征,但每个解码步骤只消耗最终位置的 logits。如果没有缓存,下一个 token 的许多工作将被重复计算。

KV 缓存来自于一个直观的观察:在将生成的 token 追加到输入后,模型否则会为所有先前的 token 重新计算投影。存储它们的 key 和 value 向量可以避免这种冗余工作。

这种存储就是 KV 缓存。它保留了前 N-1 个 token 的向量,并且可能变得足够大,从而造成内存带宽瓶颈。

总体而言,对于大约 5 万个可能的 token、12 个块、12 个头以及 768 的嵌入维度,我们的基线模型大约有 1.24 亿参数。

vocab_size: int = 50304 # GPT-2 vocab_size of 50257, padded up to nearest multiple of 64 for efficiency
n_layer: int = 12
n_head: int = 12
n_embd: int = 768

在 2.8 万亿参数下,一个 KimiK3 模型包含的参数数量大致相当于 22,580 个 GPT-2 模型。

2、线性注意力

Softmax 注意力在 q·k 乘积之后应用其非线性,将每个 query 与每个 key 耦合。线性注意力则分别对 q 和 k 应用特征映射,例如 ELU+1。这使得乘积可重新关联,因此不断增长的 K 和 V 向量集可以被折叠到一个固定的 D×D 状态中。

那篇论文的 O(N²) 表述把我搞糊涂了。它并不是说“transformer 在每个时间步的成本随当前序列长度的平方而缩放”。那是 Flash Attention 解决的问题……后来我才看到它是 2020 年发布的。

当时,训练通常会物化完整的 N×N 注意力矩阵,FlashAttention 还不存在,参考自回归实现经常在没有 KV 缓存的情况下重新计算 token 历史。

def forward(self, x, mask=None, past_kv=None):
  # x is b,t,d
  b,t,d=x.shape
  d_head=d//self.num_heads
  h=self.num_heads
  qkv=self.qkv_proj(x)

  q=qkv[:, :, :d].view(b,t,h,d_head).transpose(1,2)
  k=qkv[:, :, d:2*d].view(b,t,h,d_head).transpose(1,2)
  v=qkv[:, :, 2*d:].view(b,t,h,d_head).transpose(1,2)

  # at prefill, q,k,v have shapes b,h,t,d
  # at decode, shape is b, h, 1, d
  # so i cat at the t dimension, dim(2)

  if past_kv is not None:
    k_past=past_kv[0]
    v_past=past_kv[1]
    k=torch.cat((k_past, k), dim=2)
    v=torch.cat((v_past, v), dim=2)

  scores=(q@k.transpose(-1,-2))/math.sqrt(d_head)
  if past_kv is None: #we're in prefill and need to mask
    causal_mask=torch.ones(t,t,dtype=bool, device=q.device)
    causal_mask=torch.triu(causal_mask, diagonal=1)
    scores=scores.masked_fill(causal_mask, float('-inf'))

  if mask is not None:
    scores=scores.masked_fill(~mask, float('-inf'))

  #get attn (bhtt x bhtd)
  attn=scores.softmax(-1)#bhtt
  o=attn@v #bhtd
  o=o.transpose(1,2).contiguous().view(b,t,d)  #b,t,d

  # use x to get qkv
  o_proj=self.o_proj(o)
  past_kv=(k, v)
  return o_proj, past_kv

同样的过程通过视觉更容易理解。每个解码步骤对 HBM 执行两次 ND 读取和两次 1D 写入,而 KV 缓存随序列长度线性增长,为 O(N)。

注意那些过多的读取和写入,这篇论文将其替换为:

def forward(self, x, mask=None, cache=None):
  # x is b,t,d
  b,t,d=x.shape
  d_head=d//self.num_heads
  h=self.num_heads
  qkv=self.qkv_proj(x)

  q=qkv[:, :, :d].view(b,t,h,d_head).transpose(1,2)
  k=qkv[:, :, d:2*d].view(b,t,h,d_head).transpose(1,2)
  v=qkv[:, :, 2*d:].view(b,t,h,d_head).transpose(1,2)
  
  k=F.elu(k)+1 
  k=k.transpose(-1,-2) 
  q=F.elu(q)+1
 
  S,z=cache if cache is not None else (0.0, 0.0)
  S=S+k@v
  z=z+k
      
 o=q@S #bhtd
 denom=q@z
 o_scaled=o/denom
 o_scaled=o_scaled.transpose(1,2).contiguous().view(b,t,d)
 o_proj=self.o_proj(o_scaled)
 cache=(S,z)
 
 return o_proj, cache

存在一个权衡。

在这里,我们用 ELU+1 分别应用于 q 和 k(在它们交互之前)来替换 softmax 所使用的指数函数。两种方法都对结果分数进行归一化,但线性注意力所使用的特征映射是对 softmax 核的一种表达能力较弱的近似。这种近似可能会降低保真度,尽管实际的精度损失取决于架构和工作负载。

注意我们仍然要除以 qk 的和,图中为简洁起见省略了。从高层次看,注意力由三个步骤组成:

  1. 使 qk 分数非负。线性注意力使用 ELU+1,而 softmax 使用指数化。
  2. 除以总和。
  3. 计算值的加权平均。

这保留了基本的注意力契约,但使用表达能力较弱的特征映射来使 QK 分数非负。

3、DeltaNet(快速权重编程器)

有限的缓存必须覆盖或与已存储的信息结合。第 i-1 个 token 的状态没有自己的槽位;它被添加到同一个 D×D 矩阵中。因此新的 query 无法再检索到每个早期 token 的完全孤立的表征。

这种加法也是效率提升的来源。与通过拼接增长不同,以加法方式更新缓存可以防止其以 O(N) 增长,但同样的操作会导致信息干扰。DeltaNet 解决了这种可恢复性的丧失。

Schlag 的论文(Fast Weight Programmers)中表述得很优雅:“当序列长度超过存储容量时,模型可能会进入过容量状态。为了在这种状态下正确运行,模型应该学会动态地与内存内容交互,并有选择地决定保留哪些键值关联、删除哪些键值关联。纯粹的加法指令可能不适合这个目的……像方程 17 中那样无休止地将新关联添加到有限大小的内存中,必然会达到极限。”

使线性注意力具有吸引力的状态(N 远大于 D)也暴露了它的主要限制。一旦状态超过其有效容量,由于更新是加法的且没有东西离开缓存,关联就会开始干扰。

def forward(self, x, mask=None, cache=None):
  # x is b,t,d
  b,t,d=x.shape
  d_head=d//self.num_heads
  h=self.num_heads
  qkv=self.qkv_proj(x)

  q=qkv[:, :, :d].view(b,t,h,d_head).transpose(1,2)
  k=qkv[:, :, d:2*d].view(b,t,h,d_head).transpose(1,2)
  v=qkv[:, :, 2*d:].view(b,t,h,d_head).transpose(1,2)

  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)   
  # new: per-token write strength

  S = cache if cache is not None else 0.0  

  v_old = k @ S # read the board at this key
  u = beta * (v - v_old) # the delta: only what's actually new
  S = S + k.transpose(-1, -2) @ u # same outer-product write as before

  o = q @ S # read, no denominator
  o = o.transpose(1, 2).contiguous().view(b, t, d)
  return self.o_proj(o), S

一个视觉示例更容易理解。

将单个关联写为 S = k.T @ v。如果用相同的 key 读回,你得到 k @ (k.T @ v),即 (k @ k.T) v,也就是 key 的平方范数乘以 v。因此读回的结果被 key 的平方范数缩放,如果将 k 归一化为单位长度,或者只是将结果除以范数,就能精确得到 v。

Q 也是一个学习的指针。Wq 和 Wk 读取相同的残差流,事实的 query 指向该事实被写入的 key 方向。更新首先询问当前 key 从缓存中检索到了什么信息。它从我们想要存储的值中减去那个现有信息,将 key 乘以差值,然后将结果加回去。旧信息被移除,新信息被写入其位置。

4、DeltaNet

使用 Delta 规则并行化线性 Transformer。

这是帖子中最难的部分。我花了大约七个小时才对其形成工作理解,因此我将从实现出发构建解释。简而言之,DeltaNet 实现了一个具有广义 Householder 转移矩阵的一阶线性递推,从而支持分块并行的前向传递,以实现硬件高效的线性时间训练。它将输入和输出分割成多个大小为 C 的块,并根据前一个块的最终状态和当前块的 query key value 块计算每个块的输出。

实际问题是 prefill。直接在 T 个 token 的序列上实现 Delta 规则看起来像这样:

S = torch.zeros(b, h, dh, dh) if cache is None else cache
outs = []
for i in range(t):
    k_i = k[:, :, i:i+1]  
    v_i = v[:, :, i:i+1]
    b_i = beta[:, :, i:i+1]
    v_old = k_i @ S                  
    u_i  = b_i * (v_i - v_old)
    S = S + k_i.transpose(-1, -2) @ u_i # write
    outs.append(q[:, :, i:i+1] @ S)     
o = torch.cat(outs, dim=2)                 

与标准注意力不同,这种表述需要在每个 key 向量处进行校正,因此通往并行矩阵乘法的路径并不立即明显。即使没有 Delta 规则,直接的线性注意力 prefill 仍然是顺序的:

S = torch.zeros(b, h, dh, dh) if cache is None else cache
outs = []
for i in range(t):
    q = q[:, :, i:i+1]  
    k = k[:, :, i:i+1]  
    v = v[:, :, i:i+1]

    S=S_old+k@v
      o=q@S #bhtd
      o=self.norm(o)
    o=o.transpose(1, 2).contiguous().view(b, t, d)

    out=self.o_proj(o)
    cache=S
    outs.append(out)

o = torch.cat(outs, dim=2)

分块表述提供了一种更高效的方法。通过一个例子更容易理解其机制:

将 C=N 设置为标准 O(N²) 注意力,而 C=1 给出常规线性注意力。我们在中间值之间插值,以在块内额外工作与更好的硬件利用率之间进行权衡。实践中 C 通常是 64 或 128,因为张量核指令在该粒度下高效运行;UMMA 就是一个例子。

中间 tile 被折叠到 S 中,作为状态更新的一部分:

S = torch.zeros(b, h, dh, dh) if cache is None else cache
outs = []
for i in range(t//C):
    q_c = q[:, :, i*C:(i+1)*C]  
    k_c = k[:, :, i*C:(i+1)*C]  
    v_c = v[:, :, i*C:(i+1)*C]

      o_prev=q_c@S #this is everything up to this block
      
      attn=(q_c@k_c.transpose(-1,-2)).tril() #masked attention 
      o_curr=attn@v_c
          
        o=o_prev+o_curr
    
    S_new=k_c.transpose(-1,-2)@v_c #recurrent attention 
    S=S+S_new
    outs.append(o)

o = torch.cat(outs, dim=2)

在块内,我们做 q(kᵀv)。这是先算分数,是带掩码的正常注意力顺序。在块之间,我们遵循 (kᵀv)q,因此我们做的是递推顺序,先状态。注意力以 O(N²) 增长,而这个不会。在块内我做真正的注意力(带掩码的 QKᵀV),而在块之间我将一切折叠到状态中并用一次 matmul 读回。所以成本分成两部分。有固定部分 2Ld²,即状态工作,完全不关心 C。还有增长部分 2LCd,即位于对角线上的分数矩阵。完整注意力就是 C 等于 L 的情况,那时第二项变成 2L²d,是二次的。所以我把 C 设得越小,做的 FLOPs 就越少。

纯 FLOPs 而言 C=1 是最便宜的选择,但不一定在 wall-clock 时间上最优。当工作能高效映射到 GPU 的矩阵乘硬件时,GPU 可以更快地完成更多算术。

下一步是将同样的方法扩展到 DeltaNet。

根本问题很简单:用于纯加法注意力的分块方法不能直接应用于 delta 更新:

v_old = k_i @ S                  
u_i  = b_i * (v_i - v_old)

我们需要每个状态来计算需要减去的信息。我们无法以相同方式并行化它,除非进行某种数学重参数化。因此作者将 delta 更新从以下形式重写:

u=v_new-v_old
S_t= S_(t-1)+K.T@u
o=q@S_T

这里,一个顺序循环每次迭代计算一个 delta。重参数化形式是:

S_t = S_{t-1}(I − β_t k_t k_tᵀ)  +  β_t v_t k_tᵀ
o_t = S_t q_t

这种表述允许分块代码一次性计算所有 C 个 delta:

def chunk_delta_rule_forward(Q, K, V, beta, C):
        # L: sequence length, d: head dimension
        L, d = Q.shape
        # chunking
        Q, K, V = map(lambda x: x.reshape(-1,C,d), [Q, K, V])
        beta = beta.reshape(-1, C)
        K_beta = K * beta.unsqueeze(-1)
        V_beta = V * beta.unsqueeze(-1)
        
        # compute eq. 10 with vectorized forward substitution for fast inverse
        T = -(K_beta @ K.t()).tril(-1)
        for i in range(1, C):
                T[i, :i] = T[i, :i] + (T[i, :, None] * T[:, :i]).sum(-2)
        
        T += torch.eye(C)
        W = T @ K_beta
        U = T @ V_beta

        # chunkwise parallel. Eq. 8-9
        S = torch.zeros(d, d)
        O = torch.empty_like(V)
        
        for i in range(L//C):
                q_i, k_i, w_i = Q[i], K[i], W[i]
                u_i = U[i] - w_i @ S # the corrections, all of one chunk
                o_inter = q_i @ S
                A_i = (q_i @ k_i.t()).tril() #qk.t
                o_intra = A_i @ u_i # attention @ v (with corrections, so u)
                S += k_i.t() @ u_i # update state with addition 
                O[i] = o_intra + o_inter #update output with flash + recurrent
        return O.reshape(L, d)

这让我们得到第一个比较点:MHA vs DeltaNet Transformer:

5、Gated Delta Net

我们现在有了一种对缓存进行精确更改的方法。对于每个新事实(每个新的 key 向量),我们可以精确查看存储在该点的旧信息,并用我们想要关注的新信息替换它。

然而,这种机制只能忘记它有特定替换物的关联。它无法在上下文切换时高效清除多个关联,也无法普遍衰减内存以释放容量。

如果我们做的是纯加法线性注意力:

添加遗忘能力会很简单。我们只需要一个控制遗忘状态的参数:

S_old=cache
S_new=k@v
# cache=S_old+S_new
cache=alpha * S_old + S_new

这是 Mamba-2 的贡献。我们对之前的缓存进行衰减,然后以完整强度添加新缓存,从而防止状态无界增长。

在每个时间步以动态比率均匀衰减所有键值关联是一种可行的方法,这也是 Mamba 所做的。但它没有考虑不同键值关联的不同重要性。

也就是说,如果模型需要忘记一个特定关联,所有关联都会被同等程度遗忘。相比之下,Delta 规则可以更新单个事实,但没有办法让其余事实衰减。

因此 Gated Delta 规则将 Mamba 的门控更新规则与 Delta 规则结合。它添加了一个参数 alpha,当设为 1 时切换到纯 Delta 规则,当设为 0 时清除内存。挑战在于用同样的并行分块方法实现它。

实现使用与前一节所述相同的 DeltaNet 重参数化。数学几乎相同,只有一个额外项:一个在 0 和 1 之间的数据依赖标量,控制先前状态的衰减。这将有效的键值关联学习与自适应内存管理结合在一起。

相应的代码更改如下所示:

γʳ/γⁱ 项考虑了累积衰减。在时间步 x 写入并在 x+t 读取的 token 已被乘以 αₓαₓ₊₁αₓ₊₂…αₓ₊ₜ。这是前缀和计算的乘法模拟。

得到的架构看起来像这样:

6、KDA/Kimi Linear

此时,研究人员开始试验混合模型,在一个架构中结合多种形式的注意力,例如 Gated DeltaNet 与 Mamba。

Kimi Linear 因一个核心主张而受到关注:在受控比较下,它优于完整注意力。作者将其作为更好的质量和高达 6 倍解码吞吐量的即插即用架构替换。

Kimi Linear 通过引入细粒度门控改进了 Gated DeltaNet。它不是使用单个标量衰减,而是为每个通道学习单独的衰减值。

KDA 更新规则保持相似,但代码现在看起来更像这样:

这里,alpha.reshape(nb, C, d) 捕捉了论文最重要的贡献:对内存衰减的细粒度控制。

与 DeltaNet Transformer 并排,Kimi Linear 架构引入了三个主要变化:

  • 它使用混合系统,交错 Multi-head Latent Attention (MLA) 层。
  • 它用 Mixture-of-Experts (MoE) 层替换 MLP。
  • 它通过 alpha 投影为 DeltaNet 增加容量。

后面的部分会更详细地介绍 MLA 和 MoE。目前重要的是,这不是盲目扩展。额外容量有特定的数学目的:每个通道的缩放让模型对内存衰减有更精细的控制。

缩放定律仍然相关,但容量必须以系统能使用的正确形式和位置添加。这个进展中的每个架构都添加容量来解决前一个系统的具体限制。

7、Kimi K3

最终,KimiK3 的语言主干看起来与上面的 Kimi Linear 模型相似。它包含 23 个四层宏循环。在每个宏循环中,三层使用 Kimi Delta Attention,第四层使用 Multi-head Latent Attention。第一层使用密集前馈网络;其余每一层都使用 latent Mixture-of-Experts。

乍一看,从 Kimi Linear 的变化似乎不大:

  • 大幅增加规模
  • 每 12 层 Blockwise AttnRes
  • MLA query LoRA 和输出门控
  • 潜空间 MoE
  • SiTU 激活
  • Gated MLA

KDA 提供常量状态的循环记忆,而周期性的 MLA 层保留对上下文的完整 softmax 检索。以下简化的可视化为下面讨论的变化提供了有用的参考。

我们将从更直接的变化开始:Gated MLA、潜空间 MoE 和 SiTU 激活。

Gated MLA 通过与从输入投影的 gate 进行元素级乘法,决定每个检索到的特征有多少从 MLA 传递到残差流。

在传统的 MoE 中,学习的 router 使用点积相似性将每个 token 发送到专家网络的子集。KimiK3 总共有 898 个专家。其中两个是共享的,处理每个 token;在剩余的 896 个中,router 为每个 token 选择 16 个。

KimiK3 还改变了专家激活。它不是对 up projection 应用 SiLU,与 gate 元素级相乘,然后应用 down projection,而是使用 SiTU:

d = x.shape[-1] // 2
gate = x[..., :d].to(torch.float32)
up = x[..., d:].to(torch.float32)
situ_a = self.beta * torch.tanh(gate / self.beta) * torch.sigmoid(gate)
if self.linear_beta is not None:
    up = self.linear_beta * torch.tanh(up / self.linear_beta)
return (situ_a * up).to(x.dtype)

模型还对共享专家的输入进行 down-project,并对它们的最终总和进行 up-project:

这说明了模型推理中反复出现的挑战。如果没有融合 kernel,新的激活几乎比原始路径慢 3 倍。一个抵消优化是专家在压缩的潜空间中运行,这使得它们的前向传递快得多,并且几乎将 FLOPs 减半。

其余变化是 MLA query LoRA、输出门控,以及每 12 层 Blockwise Attention Residuals。AttnRes 大约增加 2% 的推理延迟,但提供了两个重要好处:

  • 对早期表征的选择性检索,缓解残差稀释和隐藏状态增长
  • 1.25x 计算优势

AttnRes 和 MLA 从不同方向解决了同一个底层限制。KDA 层以常量大小的状态运行,必然会丢弃信息。MLA 从 token 上下文中检索,而 AttnRes 从更早的深度表征中检索。

8、AttnRes

感谢 @chloey3k 对本节的帮助。在每次前向传递中,输入通过层栈。这里,每层由一个注意力块(KDA 或 MLA)和一个 MLP 或 MoE 块组成。通常,每个层的输入是原始嵌入与每个先前层输出的总和,所有项权重相等。

这里 h_i 是层 i 的输入,h_1 是当前 token(到目前为止序列中的最后一个 token)的嵌入,f_i(h_i) 是层 i(注意力或 MLP 块)的输出。

问题是缺乏选择性访问。不同类型的层接收相同的聚合状态,尽管它们可能从不同的权重中受益。由于递推是纯加法的,后续层还必须学习越来越大的输出来影响累积的残差,这可能导致训练不稳定。AttnRes 不是平等对待所有层,而是用专门的权重乘以该总和的每一项,这让模型能根据上下文给予最有用的层更多重要性。

每个权重 alpha_i 都由 query-key 点积计算得出。query 为每层学习,而 key 和 value 来自更早的残差流状态。分数被归一化为总和为 1,然后用于形成这些状态的加权组合。

因此,模型不必仅依赖其直接前驱。AttnRes 让每层都能选择性地访问早期层的输出,允许其学习的 query 检索当前计算最有用的表征。

下面的伪代码在块粒度上应用同样的思想。一个块是跨 12 个解码器层累积的注意力和 MLP 输出的元素级总和,作为单个深度表征存储,以便后续 AttnRes 混合。

如果在每层都应用残差注意力,会增加太多训练和推理成本。只在固定块边界应用它,能以较低成本捕获大部分好处。在 KimiK3 中,每个边界发生在 12 个解码器层之后。在 23 个四层宏循环中,这产生八个 AttnRes 块,从而提高了我们的推理速度。

这可能是 block_attn_res 函数中最重要的部分。

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

这完成了从 GPT-2 到 KimiK3 的演进。

核心变化不仅仅是规模。每个架构步骤都改变了模型存储什么、如何更新该状态,或者如何检索固定大小状态无法保留的信息。

KimiK3 结合了常量状态的循环记忆、周期性的 softmax 检索、稀疏专家容量,以及选择性的深度残差访问。结果是一个系统,在具有特定功能作用的地方花费额外容量。

本质上,一个固定容量的关联记忆(固定维度)需要驱逐策略,因为纯加法线性操作一旦达到容量,最终会引入干扰。


原文链接:22580: From GPT2 to Kimi3, Explained

汇智网翻译整理,转载请标明出处