Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

模型架构:把计算花在哪里,把过去存在哪里

假设我们要让一个模型读完工坊一整年的记录。它需要记住某次订单的编号,也要理解反复出现的工作流程。若每来一个词元,都让所有参数参与计算,再重新查看此前所有位置,代价会不断增加。

研究者于是追问两个不同的问题:一次处理能否只动用部分参数?读过的内容能否压进一份较小的状态?混合专家、稀疏注意力、线性注意力和状态空间模型,就从这些问题走出不同路线。

本专题承接注意力机制、Transformer与推理加速。需要用到矩阵乘法、加权平均和递推;暂时忘了可以回看第一篇。资料核查截至 2026-10-20,包含 2026 年 8 月的一份架构报告。本页运行的是可手算的机制实验,没有训练或测量这些大型语言模型。

1. 先认清三笔不同的开销

在一个常见的 Transformer 层中,注意力让不同位置交换信息,前馈网络则对每个位置做进一步变换。生成时还要保存或恢复过去的信息。它们的成本不在同一个地方。

想改变的部分一个常见办法仍要追问的代价
每个词元使用多少前馈参数混合专家,只选择部分专家路由、负载、通信和全部权重存放在哪里
当前查询查看哪些位置稀疏注意力,只访问部分位置选位置本身的成本,以及漏掉重要信息的可能
怎样保存此前信息线性注意力或状态空间递推状态容量、更新规则和精确检索能力
怎样兼顾不同需求在不同层组合多种机制层的比例、接口、训练稳定性和整体成本

这里的“稀疏”有两个对象。混合专家在参数模块中做选择;稀疏注意力在序列位置之间做选择。一个模型可以同时采用两者,也可以只采用其中一种。

2. 混合专家:有很多参数,每次只叫来几组

把普通前馈网络想成一组可学习的矩阵变换。混合专家(Mixture of Experts,MoE) 准备多组这样的变换,再由路由器为当前输入选择其中几组。“专家”是模型组件的名称,并不表示它们天然就是数学老师、程序员或某种固定职业。

设输入向量是 xx,路由分数为 r=xWrr=xW_r。共有 EE 个专家,选择分数最高的 KK 个,组成集合 I(x)\mathcal{I}(x)。本页采用一种简单约定:只在选中的分数上做 softmax,再把专家输出相加:

ge(x)=exp⁡(re)∑j∈I(x)exp⁡(rj),y=∑e∈I(x)ge(x) fe(x). \begin{aligned} g_e(x)&=\frac{\exp(r_e)} {\sum_{j\in\mathcal{I}(x)}\exp(r_j)},\\ y&=\sum_{e\in\mathcal{I}(x)}g_e(x)\,f_e(x). \end{aligned}

geg_e 是非负权重,选中项之和为 1;fe(x)f_e(x) 是第 ee 个专家对这个输入的输出。未选中的专家不需要执行。不同论文会使用不同评分、归一化和共享专家设计,这个公式是我们马上要运行的教学版本。

总参数和本次参与计算的参数,要分开数

取输入宽度 2、隐藏宽度 3。每个专家由两个无偏置矩阵组成,分别有 2×32\times3 和 3×23\times2 个参数,共 12 个。四个专家共 48 个;路由矩阵形状为 2×42\times4,另有 8 个。

因此,这个小层总共保存 56 个参数。若每次选两个专家,一个词元会使用 2×12+8=322\times12+8=32 个参数。六个词元一共执行 12 次专家计算;把四个专家全算一遍则是 24 次。

这还不是“整个模型快了一倍”。路由、调度和输出汇合也要时间;在多台设备上,还可能需要把词元发往持有对应专家的设备。批量中不同词元也可能合起来用到所有专家,因此不能据单词元的激活参数量决定整批权重的存储需求。

只选两组,仍可能让两组挤满、两组闲着

我们的六个输入故意比较相似,路由器也使用固定的教学参数。结果六个词元全选了专家 0 和 1,请求次数是 [6, 6, 0, 0]。如果每个专家最多接收三次分配,再按词元顺序处理,后面三个词元的两次分配都会被丢掉。

图 专题 1-1:限制容量可以限制工作量,却不会自动把路由分配好。虚线表示每个专家接收三次分配的上限。

这个人为反例采用“溢出就丢弃、权重不重新归一化”的规则,也没有残差支路,因此被完全丢弃的词元得到零输出。真实系统可以选择不同策略,例如改善负载分配或保留全部词元。先读清处理规则,再比较吞吐与质量,才不会把少算了任务误认成无代价加速。

一个值得精读的实例是 DeepSeek-V3 技术报告 v1,第 2.1.2 节。它区分共享专家与路由专家,并用额外偏置影响专家选择;输出混合权重仍来自原始亲和分数。报告还保留了较小的序列级辅助平衡损失。因此,“无辅助损失的负载平衡策略”不能读成“整个训练没有任何辅助损失”。本页没有实现这套完整路由,也没有复现报告的训练结果。

3. 线性注意力:先汇总,再让查询来读

普通 softmax 注意力对当前查询计算各个历史位置的分数,再归一化。线性注意力的一条路线,是把相似度换成可分解的点积,从而改变计算顺序。这条思路见 Katharopoulos 等,ICML 2020。

为了看清运算,下面让查询 qtq_t 和键 kik_i 已经是非负特征向量,值为 viv_i;分母需要大于零。到第 tt 步时,输出是:

yt=∑i=1t(qt⊤ki)vi∑i=1tqt⊤ki. y_t= \frac{\sum_{i=1}^{t}(q_t^\top k_i)v_i} {\sum_{i=1}^{t}q_t^\top k_i}.

这仍然是一个加权平均,但权重是点积,而不是点积经过指数函数后的 softmax 权重。这里换了注意力函数,不能说只是对普通 softmax 做了完全等价的加速。

利用乘法分配律,可以先把历史项累起来。取 qt,kt∈Rdkq_t,k_t\in\mathbb{R}^{d_k}、vt∈Rdvv_t\in\mathbb{R}^{d_v},维护矩阵 St∈Rdk×dvS_t\in\mathbb{R}^{d_k\times d_v} 与向量 zt∈Rdkz_t\in\mathbb{R}^{d_k}:

St=St−1+ktvt⊤,zt=zt−1+kt,yt=St⊤qtzt⊤qt. \begin{aligned} S_t&=S_{t-1}+k_tv_t^\top,\\ z_t&=z_{t-1}+k_t,\\ y_t&=\frac{S_t^\top q_t}{z_t^\top q_t}. \end{aligned}

初值都为零。外积 ktvt⊤k_tv_t^\top 把“这个键对应这个值”的关联加进状态。计算时先写入当前项再读取,所以这里的因果范围包含当前位置。

对于固定的特征维度,每来一个词元,只需更新这份固定大小的状态。长度增长时,不必新增一整条历史键和值。但矩阵状态也可能很大:它有 dkdvd_kd_v 个数,另加 dkd_k 个归一化量。是否划算,要连同维度一起算。

本页的两维键、标量值例子只需保存 4 个状态数。保存全部键和值则在长度 32 时需要 96 个数,长度 128 时需要 384 个数。这只是单头历史状态的元素计数,没有算权重、训练激活、临时缓冲或 Python 对象,更不是实际进程内存测量。

代码同时执行“逐项计算全部历史权重”和“递推维护状态”,二者在浮点容差内相同。把相同点积分数换成 softmax 后,输出却不同。这个对照可以帮我们守住两条界线:同一数学函数的两种实现,与两种不同数学函数。

4. 压进状态之后,旧信息怎样被保留和改写

假设有两个名字 A、B,键分别是 (1,0)(1,0) 和 (0,1)(0,1)。依次写入:

A 对应 10
B 对应 20
A 现在对应 30

归一化累加会把两个 A 的值平均,最后读取 A 得到 20,读取 B 也得到 20。如果任务需要所有历史观测的平均,这样做有道理;如果任务问“现在 A 对应多少”,我们更希望读到 30。

状态空间模型:先从一条递推看起

最简单的离散状态更新可以写成:

ht=Atht−1+Btxt,yt=Ctht. h_t=A_th_{t-1}+B_tx_t,\qquad y_t=C_th_t.

hth_t 是内部状态,xtx_t 是新输入,yty_t 是读出的结果;三个矩阵分别决定旧状态怎样变化、新输入怎样写入、状态怎样被读取。状态空间模型(State Space Model,SSM)的具体设计还要说明这些矩阵如何参数化、是否随输入改变,以及怎样高效计算。

先缩成只有一个数的例子:ht=aht−1+xth_t=ah_{t-1}+x_t。第一次写入 1,以后只输入 0。再过 50 步,留下的是 a50a^{50}。本页实际递推得到:a=0.8a=0.8 时约为 0.000014,a=0.99a=0.99 时约为 0.605006。较慢衰减能保留更久,也会让不再需要的信息消失得更慢。

这条标量递推帮助理解状态与时间尺度;它没有实现 Mamba 的完整模块。Mamba-2 的 SSD 论文研究特定状态空间计算与结构化矩阵之间的联系,并据此组织高效算法。标题中的“Transformers are SSMs”也不意味着任意 softmax Transformer 都能原样压成这一个标量。

delta 更新:先读旧值,再写入差额

Gated Delta Networks v1 的公式 8把衰减与有针对性的更新结合起来。这里使用与前面一致的状态方向;原文的状态矩阵是它的转置。把一次更新拆开看:

S~t−1=αtSt−1,et=vt−S~t−1⊤kt,St=S~t−1+βtktet⊤,yt=St⊤qt. \begin{aligned} \widetilde S_{t-1}&=\alpha_t S_{t-1},\\ e_t&=v_t-\widetilde S_{t-1}^{\top}k_t,\\ S_t&=\widetilde S_{t-1}+\beta_t k_te_t^\top,\\ y_t&=S_t^\top q_t. \end{aligned}

αt\alpha_t 控制整体衰减,βt\beta_t 控制写入强度;ete_t 是新值与当前读出值的差。这个读出没有上一节的归一化分母。完整网络还包含投影、归一化、局部卷积与门,本页只计算这条核心递推。

先取理想边界 αt=βt=1\alpha_t=\beta_t=1,并使用单位正交键。写入 A=30 时,先读出旧 A=10,差额是 20,再把这 20 加进 A 的方向。因此 A 变成 30,B 仍是 20。

如果末步先乘 0.5,旧状态中的 A 和 B 都会减半。随后更新 A,可以让它回到 30,但 B 留在了 10。门没有免费判断“B 还重要”,它只执行我们指定的衰减规则。

图 专题 1-2:上图使用人工键值与门;下图是独立的标量递推。它们展示更新规则的后果,没有经过语言训练。

再把第二个键改成与 A 不正交的 (1/2,1/2)(1/\sqrt{2},1/\sqrt{2}),先写 A=10,再给新键写 30。程序再次查询 A,得到约 26.213203。因为两条键共享方向,新写入影响了旧关联。不能从正交键上的漂亮结果推出“固定状态能精确保留任意多条记录”。

5. 怎样把这些机制放回一篇论文

阅读架构论文时,先把三个层次分开:公式规定怎样更新;算子规定怎样把同一计算交给硬件;整套训练决定模型最后学会什么。我们的算例主要检查第一层。

以 Gated Delta Networks 为例,可以沿“公式 8 → 模块图 → 实验设置 → 结果表 → 吞吐图”阅读。v1 第 4 节的 1.3B 对照使用 FineWeb-Edu 的 100B 词元、LLaMA 2 词元化器与 4K 训练长度;混合模型还包含滑动窗口注意力。它提供了特定设置下的比较,不能把结果完全归因于刚才那条孤立的递推公式。作者的高速分块算法也不是本页的 Python 循环。实验设置

混合架构值得关注,因为它允许一部分层压缩历史,另一部分层保留更直接的访问。Mamba-2 论文的 350M、48 层、7B 训练词元消融中,少量注意力层改善了困惑度。但这仍是指定规模和训练条件下的观察;不能把某个比例变成以后所有模型的标准答案。第 9.2.3 节

2026 年的一次继续追踪

Qiu 等的 On the Design of Qwen3.8-Next Architecture: Evaluation, Efficiency, and Training Stability,v1 发布于 2026-08-31,继续把 Gated DeltaNet 与注意力结合,并在后续训练中引入按微块选择上下文的 QSA。报告原文

读它时可以先看第 2.1.1 节的对照条件:三个 28 层、25B-A3B 检查点,先用 4K 上下文训练 400B 词元,再用 32K 训练 80B 词元。表 1 中 GDN 混合方案的九项平均较高,但 EvalPlus 为 49.71,低于全注意力的 51.01。这是作者报告的结果,本书没有重跑。它提醒我们同时看逐项能力、训练条件与成本,不能只摘“平均更高”。对照与表 1

报告中的算子加速与完整模型收益也应分别读。某个核在长序列上更快,并不直接决定短请求、并发服务或整轮训练的加速比例。若把固定状态层与仍保存历史的注意力层混合,整个模型的历史存储也不能笼统称作常数。具体成本需要把每类层分别相加。

6. 亲手运行这些差别

完整脚本使用本书现有的 NumPy 环境,无需 GPU、外部数据或预训练权重:

python src/examples/architecture_lab.py

固定种子 7 只用于生成教学权重和小数组;没有用它选择最好结果。实际输出为:

专家请求数:[6, 6, 0, 0];实际调用 12 次,全算需 24 次。
总参数 56;每个词元使用 32 个;稀疏与遮罩路径一致:True
容量为 3 时接收:[3, 3, 0, 0];丢弃 6 次分配。
线性注意力两种计算一致:True;与 softmax 对照最大差:0.041269
读取 A/B:平均 [20.0, 20.0];delta [30.0, 20.0];末步衰减 [30.0, 10.0]
非正交键更新后再读 A:26.213203(原值 10)。
50 步后旧脉冲:0.8 门 0.000014;0.99 门 0.605006
范围:固定输入与未训练参数的机制检查,没有测量语言能力或端到端速度。

“稀疏与遮罩路径一致”比较的是:只执行被选专家,与全算后用相同路由权重筛选,是否得到同样输出。它没有声称选两个专家与把四个专家都混合得到相同结果。

线性注意力的 softmax 对照直接使用相同点积、没有额外除以维度平方根,目的是只改变归一化函数。不能把这行数值当作两个训练完成的网络之间的质量差。

可以下载全部输入、参数与计算记录,也可以保存一次新运行:

python src/examples/architecture_lab.py --out runs/my-architecture-lab.json

输出文件必须尚不存在。记录保留各词元的专家选择、混合权重、路由输出和容量限制后的输出、线性注意力的完整输入输出、记忆读数和脉冲轨迹,以及环境和代码指纹。

展开完整代码
"""前沿架构:手算规模的专家路由、线性注意力与记忆更新;不是语言模型训练。"""

from argparse import ArgumentParser
import hashlib
import json
from pathlib import Path
import platform

import numpy as np


def softmax(values):
    weights = np.exp(values - values.max(axis=-1, keepdims=True))
    return weights / weights.sum(axis=-1, keepdims=True)


def expert(x, first, second):
    return np.maximum(x @ first, 0) @ second


def routed(x, router, first, second, top_k=2, capacity=None):
    """按词元顺序分配;超过容量就丢弃该次分配,不重新归一化。"""
    n_experts = len(first)
    if not 1 <= top_k <= n_experts:
        raise ValueError("top_k 必须位于专家数量范围内")
    if capacity is not None and capacity < 0:
        raise ValueError("容量不能为负")
    ids = np.argsort(-(x @ router), axis=-1, kind="stable")[:, :top_k]
    scores = np.take_along_axis(x @ router, ids, axis=1)
    weights = softmax(scores)
    output = np.zeros_like(x)
    accepted = np.zeros(n_experts, dtype=int)
    requested = np.bincount(ids.ravel(), minlength=n_experts)
    dropped = 0
    for token in range(len(x)):
        for slot, index in enumerate(ids[token]):
            if capacity is not None and accepted[index] >= capacity:
                dropped += 1
                continue
            output[token] += weights[token, slot] * expert(x[token], first[index], second[index])
            accepted[index] += 1
    return output, ids, weights, requested, accepted, dropped


def linear_recurrent(q, k, v):
    """q/k 已是非负特征;写入当前位置后读取,包含当前位置的因果注意力。"""
    state = np.zeros((k.shape[1], v.shape[1]))
    normalizer = np.zeros(k.shape[1])
    outputs = []
    for query, key, value in zip(q, k, v):
        state += np.outer(key, value)
        normalizer += key
        denominator = query @ normalizer
        if denominator <= 0:
            raise ValueError("当前查询没有正的匹配权重")
        outputs.append(query @ state / denominator)
    return np.array(outputs), state, normalizer


def linear_explicit(q, k, v):
    similarity = np.tril(q @ k.T)
    return similarity @ v / similarity.sum(axis=1, keepdims=True)


def delta_memory(k, v, decay, write):
    """S 的形状是 d_k × d_v;门值为手工给定,没有训练投影或门。"""
    state = np.zeros((k.shape[1], v.shape[1]))
    history = []
    for key, value, alpha, beta in zip(k, v, decay, write):
        state = alpha * state
        error = value - key @ state
        state = state + beta * np.outer(key, error)
        history.append(state.copy())
    return np.array(history)


def scalar_state(inputs, decay):
    state, history = 0., []
    for value in inputs:
        state = decay * state + value
        history.append(state)
    return history


def run():
    rng = np.random.default_rng(7)
    x = np.array([[1 + i / 10, i / 10] for i in range(6)])
    router = np.array([[2., 1., -1., -2.], [0., .2, .1, 0.]])
    first = rng.normal(size=(4, 2, 3))
    second = rng.normal(size=(4, 3, 2))
    output, ids, weights, requested, accepted, dropped = routed(x, router, first, second)
    # 全算后遮罩仅作独立执行路径对照,不作为“稀疏省计算”的实现。
    all_outputs = np.stack([expert(x, a, b) for a, b in zip(first, second)], axis=1)
    mask = np.zeros((len(x), len(first)))
    np.put_along_axis(mask, ids, weights, axis=1)
    reference = np.einsum("ne,ned->nd", mask, all_outputs)
    limited, _, _, _, limited_load, limited_drops = routed(
        x, router, first, second, capacity=3)

    q = rng.uniform(.1, 1., size=(7, 3))
    k = rng.uniform(.1, 1., size=(7, 3))
    v = rng.normal(size=(7, 2))
    recurrent, _, _ = linear_recurrent(q, k, v)
    explicit = linear_explicit(q, k, v)
    scores = q @ k.T
    scores[np.triu_indices(len(q), 1)] = -np.inf
    conventional = softmax(scores) @ v  # 单独对照 exp(dot),未加缩放。

    keys = np.array([[1., 0.], [0., 1.], [1., 0.]])
    values = np.array([[10.], [20.], [30.]])
    _, state, normalizer = linear_recurrent(keys, keys, values)
    queries = np.eye(2)
    averages = (queries @ state) / (queries @ normalizer)[:, None]
    delta = delta_memory(keys, values, [1., 1., 1.], [1., 1., 1.])
    decayed = delta_memory(keys, values, [1., 1., .5], [1., 1., 1.])
    mixed_keys = np.array([[1., 0.], [2 ** -.5, 2 ** -.5]])
    collision = delta_memory(mixed_keys, np.array([[10.], [30.]]), [1., 1.], [1., 1.])
    return {
        "kind": "untrained_synthetic_mechanism_checks",
        "seed": 7,
        "moe": {
            "inputs": x.tolist(), "router": router.tolist(),
            "first": first.tolist(), "second": second.tolist(),
            "ids": ids.tolist(), "weights": weights.tolist(), "output": output.tolist(),
            "requested": requested.tolist(), "accepted": accepted.tolist(),
            "calls": int(accepted.sum()), "dense_calls": int(len(x) * len(first)),
            "total_parameters": int(first.size + second.size + router.size),
            "parameters_used_per_token": int(2 * first[0].size + 2 * second[0].size + router.size),
            "reference_max_error": float(np.max(np.abs(output - reference))),
            "capacity": 3, "limited_load": limited_load.tolist(),
            "dropped_assignments": limited_drops, "limited_output": limited.tolist(),
        },
        "linear": {
            "q": q.tolist(), "k": k.tolist(), "v": v.tolist(),
            "recurrent": recurrent.tolist(), "explicit": explicit.tolist(),
            "max_error": float(np.max(np.abs(recurrent - explicit))),
            "softmax_max_difference": float(np.max(np.abs(recurrent - conventional))),
        },
        "memory": {
            "keys": keys.tolist(), "values": values.tolist(),
            "average_read": averages.ravel().tolist(),
            "delta_read": (queries @ delta[-1]).ravel().tolist(),
            "decayed_read": (queries @ decayed[-1]).ravel().tolist(),
            "collision_read": float(collision[-1, 0, 0]),
            "impulse_traces": {str(a): scalar_state([1.] + [0.] * 50, a) for a in [.8, .99]},
            "cache_counts": [{"tokens": n, "kv_scalars": n * 3,
                              "normalized_linear_scalars": int(state.size + normalizer.size)}
                             for n in [32, 128]],
        },
    }


def main():
    parser = ArgumentParser(description=__doc__)
    parser.add_argument("--out", type=Path, help="将完整机制算例保存为新 JSON 文件")
    args = parser.parse_args()
    report = run()
    report["environment"] = {"python": platform.python_version(), "numpy": np.__version__,
                             "code_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest()}
    if args.out:
        args.out.parent.mkdir(parents=True, exist_ok=True)
        with args.out.open("x", encoding="utf-8") as file:
            json.dump(report, file, ensure_ascii=False, indent=2, allow_nan=False)
            file.write("\n")
    moe, linear, memory = report["moe"], report["linear"], report["memory"]
    print(f'专家请求数:{moe["requested"]};实际调用 {moe["calls"]} 次,全算需 {moe["dense_calls"]} 次。')
    print(f'总参数 {moe["total_parameters"]};每个词元使用 {moe["parameters_used_per_token"]} 个;稀疏与遮罩路径一致:{moe["reference_max_error"] < 1e-12}')
    print(f'容量为 3 时接收:{moe["limited_load"]};丢弃 {moe["dropped_assignments"]} 次分配。')
    print(f'线性注意力两种计算一致:{linear["max_error"] < 1e-12};与 softmax 对照最大差:{linear["softmax_max_difference"]:.6f}')
    print(f'读取 A/B:平均 {memory["average_read"]};delta {memory["delta_read"]};末步衰减 {memory["decayed_read"]}')
    print(f'非正交键更新后再读 A:{memory["collision_read"]:.6f}(原值 10)。')
    print(f'50 步后旧脉冲:0.8 门 {memory["impulse_traces"]["0.8"][-1]:.6f};0.99 门 {memory["impulse_traces"]["0.99"][-1]:.6f}')
    print("范围:固定输入与未训练参数的机制检查,没有测量语言能力或端到端速度。")


if __name__ == "__main__":
    main()

7. 接下来值得比较什么

要继续研究,可以围绕一个问题建立对照,而不是把所有新组件一起换上。下面的实验尚未执行:

  • 路由与分布变化:先在一类输入上训练路由器,再改变输入分布,记录每个专家的负载、丢弃率和任务质量。负载均匀不自动等于分工有用。
  • 压缩与检索:固定状态大小,改变关联条数、键的相似程度、重复更新和干扰距离,观察错误发生在哪里。
  • 混合比例与总预算:固定数据和评价,把权重、激活、历史状态、通信、训练与生成时间分别记账,再比较不同层组合。

如果关心算子速度,还需要相同设备、精度、形状、预热与同步条件。高效的整块矩阵计算可能胜过理论操作更少的许多小循环,原因可以回到硬件与分布式训练寻找。

目前仍值得追问:训练分布改变后,专家是否还能有效分工?固定状态在哪些查询上足够,哪些查询需要更直接的历史访问?混合架构的收益有多少来自结构,又有多少来自同时调整的优化器和训练配方?这些问题需要分开的证据,单个平均榜单分数不能全部回答。

自测与小实验

  1. 四个专家、每次选两个,是否意味着整台设备只需要保存一半专家权重?
  2. 本例为什么减少了专家调用次数,却没有报告“加速两倍”?
  3. 手算 A=10、B=20、A=30 后,归一化累加读取 A 的结果。
  4. 在正交单位键与无衰减条件下,把最后的写入强度改成 0.5,delta 更新后 A 是多少?
  5. 为什么末步衰减会影响未被当前查询选中的 B?
  6. 有些层维护固定状态、有些层保存全部 KV,整个模型的历史存储是否与长度无关?
  7. 把专家容量从 3 改成 6,比较完整输出;再改变路由分数,观察专家负载是否变化。
  8. 给非正交键的反例推导一个解析答案,并说明它能限制哪句过强的结论。
参考答案与提示
  1. 不能。不同词元可能选不同专家;权重仍需存在于某处,设备布局与搬运另有成本。
  2. 调用计数没有包含路由、调度、通信与硬件执行效率,也没有进行计时。
  3. A 的两次值为 10 与 30,匹配权重相同,平均为 20。
  4. 旧值为 10,差额为 20,写一半后得到 20;B 仍为 20。
  5. 衰减作用于整份旧状态。只在 A 方向写回差额,不会补回 B 丢失的部分。
  6. 不能。保存全部 KV 的层仍随长度增加;应按不同层的状态形式求和。
  7. 容量 6 足以接收当前全部请求,应恢复无容量限制时的输出。改变路由后要重新核对选择和负载,不能只看总次数。
  8. 新键先读到 10/210/\sqrt{2},差额为 30−10/230-10/\sqrt{2}。A 方向的新值为 10+(30−10/2)/2=5+15210+(30-10/\sqrt{2})/\sqrt{2}=5+15\sqrt{2},约 26.213203。它否定了这个更新规则能对任意非正交键都无干扰改写的说法。

原始资料与更新入口

本次实际阅读范围是线性注意力的基本形式、Mamba-2 的状态与混合消融、Gated DeltaNet 的递推和实验设置、DeepSeek-V3 的路由部分,以及 Qwen3.8-Next 报告的架构与对照部分。这不是对所有架构论文的系统综述,也没有验证下面仓库在当前机器上的安装或 GPU 性能。

文献与本页所用版本作者实现或相关代码入口
Katharopoulos、Vyas、Pappas、Fleuret,Transformers are RNNs: Fast Autoregressive Transformers with Linear Attention,ICML 2020 会议版fast-transformers
Dao、Gu,Transformers are SSMs: Generalized Models and Efficient Algorithms Through Structured State Space Duality,2024-05-31 v1Mamba 作者仓库
Yang、Kautz、Hatamizadeh,Gated Delta Networks: Improving Mamba2 with Delta Rule,2024-12-09 v1;后发表于 ICLR 2025GatedDeltaNet
DeepSeek-AI,DeepSeek-V3 Technical Report,2024-12-27 v1DeepSeek-V3
Qiu 等,On the Design of Qwen3.8-Next Architecture: Evaluation, Efficiency, and Training Stability,2026-08-31 v1报告链接的线性注意力算子库 FlashQLA,不是完整训练复现包

本专题首版日期为 2026-10-20。后续更新会优先检查新方法改变了哪一种选择或状态更新、比较条件是否一致,再决定是否需要修订这里的解释。读到更新的模型名称时,也可以先用这几个问题拆开它,而不必从头背一套新术语。