6. 其余部件:MoE、mHC、Engram、DSpark
6.1 四个和 KV 无关的部件
前五章都在讲注意力那一块:它们回答"长上下文的记忆怎么存、怎么读"。但模型的参数计算、短语记忆、解码利用率和多模态输入,各自还有别的问题要解决。报告里另有四个模块,它们不省 KV,却决定了这个模型能不能在 552B 参数下跑出 8B/16B 的激活量。这一章每个只讲"它替换了两本书里的什么"和"它的数学在哪一步"。
bllm 里的组件 V4.1 的替换 FFN (SwiGLU) MoE 384 选 6 残差 x + F(x) mHC 4 条流 (无) Engram 查表 逐 token 生成 DSpark 草稿
6.2 MoE:把 FFN 换成一排专家
bllm 4.3 节的 Transformer 块里,注意力后面是一个前馈网络(FFN),每个 token 都过它。MoE(专家混合,mixture of experts)把它换成 \(N\) 个并列的 FFN,每个 token 只走其中 \(k\) 个。
报告的参数:384 个路由专家 + 1 个共享专家,每个 token 选 6 个路由专家,专家中间维 2304,主干隐维 5120。先把"参数量"和"激活量"分开算清楚——这是 MoE 最容易混淆的地方。
- 一个专家多少参数:SwiGLU 是三块矩阵(门、值、输出),\(3\times 5120\times 2304=35{,}389{,}440\approx35.4\text{M}\)。
- 一层的路由专家总量:\(35.4\text{M}\times 384=13.59\text{B}\)。
- 40 层路由专家:\(13.59\text{B}\times 40=543.6\text{B}\);再加 40 个共享专家(\(35.4\text{M}\times40\approx1.4\text{B}\))。
报告说主干参数 552B。路由专家 543.6B 加共享专家 1.4B 已经占 98.7%,剩下约 7B 才是注意力、嵌入与路由。这个比例解释了为什么叫"稀疏"模型:参数几乎全在专家里,但每个 token 只碰 6 个路由专家 + 1 个共享专家。
激活量:\(6\times35.4\text{M}+35.4\text{M}\approx247\text{M}\) 是每层专家部分(共享专家每 token 都过),加上注意力,每层约 400M(由报告口径反推:decode 16B ÷ 40 层)。于是
\[ \text{prefill}: 20\times400\text{M}=8\text{B}, \] \[ \text{decode}: 40\times400\text{M}=16\text{B}. \]正好是 2 倍关系——不是巧合,是 CED 的直接结果(第 3 章):prefill 只走 20 层。
两个训练细节值得知道。路由打分用 sqrtsoftplus 而不是 softmax(分数不必归一化,只用于选 Top-6 与加权);多模态输入时为文本和图像各维护一组逐专家校正偏置,因为两种模态的专家偏好不同,混在一起平衡会掩盖各模态内部的不均衡。
6.3 mHC:把残差从一条流变成四条
bllm 4.4 节的残差连接是 \(x_{l+1}=x_l+\mathcal F_l(x_l)\):一条残差流,加法,恒等映射。mHC(multi-head residual channels,多流残差)把它扩成 \(n=4\) 条流,并让"怎么混合"变成可学习的:
\[ X_{l+1}=B_lX_l+C_l\,\mathcal F_l(A_lX_l), \] \[ (A_l,B_l,C_l)=\mathcal H(X_l), \]其中 \(X_l\in\mathbb{R}^{n\times d}\) 是 \(n\) 条 \(d\) 维残差流,\(A_l\in\mathbb{R}^{1\times n}\) 决定"从 4 条流里怎么读成 1 条送进子层",\(C_l\in\mathbb{R}^{n\times1}\) 决定"子层输出怎么写回 4 条流",\(B_l\in\mathbb{R}^{n\times n}\) 决定"4 条流彼此怎么混合"。\(n=1\) 时三者退化成 1,式子就回到 bllm 的残差。
为什么要约束 \(B_l\) 是双随机矩阵(元素非负,且每行每列之和都是 1)。先只看直接残差混合路径、并把系数冻结成常数:这条路径的传输因子是连乘 \(\prod_l B_l\)。若 \(B_l\) 是任意矩阵,这个连乘会随深度指数爆炸或消失——正是 bllm 讲残差时"梯度连乘"的同一个问题。而双随机矩阵的谱范数不超过 1(它是置换矩阵的凸组合,见下),于是
\[ \|B_l\|_2\le 1 \quad\Longrightarrow\quad \Big\|\prod_{l}B_l\Big\|_2\le 1 , \]连乘不发散。注意是非扩张,不是"范数保持":\(B=\mathbf 1\mathbf 1^{\top}/n\) 一步就把所有零和方向的差异抹平。所以准确说法是"直接混合路径不会放大,但不排除收缩"。
别把 \(\prod_lB_l\) 当成完整的 \(\partial X_L/\partial X_0\)。因为 \((A_l,B_l,C_l)=\mathcal H(X_l)\) 本身依赖输入,完整 Jacobian 还包含系数对 \(X_l\) 的导数和子层分支 \(\mathcal F_l\)。上面的不等式只描述被冻结的那条直接路径——是"这层设计至少不会在这条路上放大"的保证,不是整个模型的梯度界。
这也是"双随机"值得学的地方:普通残差是 \(B_l=I\)(最稳但最死板),mHC 是"在双随机矩阵里学一个"(稳但可学)。
Sinkhorn 怎么把任意矩阵变成双随机矩阵。给定打分矩阵 \(S\),先取 \(M=\exp(S)\)(逐元素,所以全正),然后交替归一化行和列:
\[ u^{(k+1)}=\mathbf 1\oslash\big(Mv^{(k)}\big), \] \[ v^{(k+1)}=\mathbf 1\oslash\big(M^{\top}u^{(k+1)}\big), \] \[ B=\operatorname{diag}(u)\,M\operatorname{diag}(v). \](\(\oslash\) 是逐元素除法。)每归一化一次行,列和就被破坏;再归一化列,行和又被破坏。但对全正矩阵这个交替过程收敛,极限是一个行和列都为 1 的矩阵。报告的 hc_sinkhorn_iters=20 就是迭代 20 次后截断。
为什么值得收敛到双随机而不是别的?因为有 Birkhoff–von Neumann 定理:双随机矩阵恰好是所有置换矩阵的凸包。所以 \(B_l\) 可以读作"对 4 条流做一次软的、连续的重新排序"——把"哪个槽位放哪条信息"变成可微的选择。这是 \(n=1\) 的残差做不到的。
Single-Pass 把系数挪后一格。原式里 \(A_l=\mathcal H(X_l)\) 依赖 \(X_l\),而 \(A_lX_l\) 又要立刻用——"算系数"和"用系数"在同一次前向里串成依赖。数据依赖本身不强制"必须同一个 kernel",但它确实让调度器难以把系数计算与子层计算重叠。报告的改法是把输入混合系数挪一格:
\[ X_{l+1}=B_lX_l+C_l\,\mathcal F_l(\underbrace{A_{l-1}}_{\text{上一块}}X_l), \]代价是 \(A\) 落后一个块(第 \(l\) 块用第 \(l-1\) 块的系数),收益是"系数计算"与"子层计算"解耦:前一块的系数已经就绪,后一块的子层就可以和前一块的系数计算重叠执行。这是工程折中;至于"块"指整个 Transformer 层还是单个子层、缓存的是系数还是已经混合的激活,要按实现核对。
6.4 Engram:把"背下来的短语"放进一张表
两本书的模型把一切知识都放在参数里,用计算去取。Engram 加了一条边路:用 n-gram 直接当索引进一张巨大的嵌入表,查到的东西作为残差加回当前位置。
- 阶数 \(n\in\{2,3,4\}\):取以当前位置结尾的 2-gram、3-gram、4-gram。
- 每阶 8 个哈希头,每头 256 维,合计 2048 维(报告写作"每阶总嵌入维 2048")。
- 每个哈希头各自维护一张约 16M 条目的表;同一阶内不同头的表大小取互不相同的素数。
- 两组模块(
layer_idx1 与 14,0-based)平分 196B 参数:\(2\times3\times8\times16\text{M}\times256\approx196.6\text{B}\)。嵌入与 KV 投影都用 FP8。
数学上它的寻址就是一个哈希:把 n-gram 的 token id 元组映射成行号,取一行向量。寻址没有点积、没有 softmax,所以它不占注意力的预算;开销主要在存储与搬运上,查表本身是 \(O(1)\)。行号确定,可以在计算第一个 Transformer 块的同时用后台 RDMA 从主机内存预取。
它和注意力的分工,用一句话说:注意力负责"这次上下文里出现过什么",Engram 负责"这种短语在训练语料里通常接什么"。第一层 SWA 的输出给 Engram 提供上下文门控(同一个 2-gram 在不同句子里权重不同),第二层 SWA 再把注入后的表示整合进局部上下文——这就是为什么它插在第 1 层之后、而不是插在最前面。
6.5 DSpark:投机解码的期望收益
decode 的瓶颈是带宽(2.1 节)。投机解码的思路:先让一个便宜的小模型猜 \(\gamma\) 个 token,再让大模型一次前向验证这 \(\gamma\) 个位置。验证时计算量变多了,但同一批权重和 KV 读取可以服务多个位置,算术强度上升;只要还没转成计算受限,总延迟就可能明显小于逐 token 验证——这时候猜得越准越赚。
DSpark 的草稿模型是 3 个 SWA-128 的 Transformer 块,每层 128 个路由专家选 3 个;它一次并行算出 5 个草稿位置(不是自回归地一个个算),再用一个轻量 Markov 头补上草稿 token 之间的依赖,用 confidence 头给出每个位置的接受概率。草稿块的输入取自主干 dspark_target_layer_ids = [37,38,39](0-based);具体接哪条残差流、怎么合并,报告没有展开。
收益能算,但要把两个量分开:接受长度 \(L\)(被接受的草稿 token 数)和实际输出长度 \(T\)。设每个草稿 token 被接受的概率是 \(\alpha\)、接受彼此独立、\(\gamma=5\)。被接受的草稿数期望是
\[ \begin{aligned} \mathbb E[L]&=\sum_{k=1}^{\gamma}\Pr(\text{至少接受 }k) =\sum_{k=1}^{\gamma}\alpha^{k}\\ &=\frac{\alpha\,(1-\alpha^{\gamma})}{1-\alpha}. \end{aligned} \]标准投机采样里,一次验证后总还会多出一个 token:首个草稿被拒时主模型从自己的分布里补一个(纠正 token),全部接受时再从主模型多采一个。所以实际输出长度是
\[ \mathbb E[T]=1+\mathbb E[L] =\frac{1-\alpha^{\gamma+1}}{1-\alpha}. \]代入:\(\alpha=0.8,\gamma=5\) 得 \(\mathbb E[L]=3.69\)、\(\mathbb E[T]=4.69\);\(\alpha=0.9\) 得 4.69 与 5.69。所以 3.69 不要直接叫作"每次前向的输出数"。而且这还是理想模型下的上界:草稿模型的成本、验证时多出的计算、调度开销都要扣掉,真正的提速倍数要实测。这个收益与第 2 章的带宽账互补——每步读同一份 KV,但一次前向吐出多个 token。
上面假设各草稿 token 的接受事件独立、接受率固定为 \(\alpha\)。实际接受率随位置和上下文变化(DSpark 的 confidence 头就是逐次决定验证几个位置),所以这个式子只用来估量级,不能当成线上提速的预测。
两个工程点:草稿块的 MoE 只有 128 个专家(草稿要便宜),且 DSpark 是在预训练之后单独训练、后训练阶段与主干一起训但不把梯度回传给主干——这样它始终跟着当前策略走。
6.6 容易算错的地方
- 把 384 个专家都算进激活量。激活的是 6 个路由专家 + 1 个共享专家;384 是存储。
- 用 552B 除 40 层再乘 6/384 估算激活量。这样算出来是 \(552/40\times6/384=0.216\)B/层,乘 40 层是 8.6B,已经和报告的 16B 对不上;错在拿总参数(几乎全是专家)去乘路由比例,正确做法是"6 × 单专家 35.4M + 注意力"逐层算。
- 把 Sinkhorn 当成"归一化一次"。行归一化会破坏列和,必须交替;20 次是收敛速度与 kernel 开销的折中。
- 把 Engram 当成注意力的一种。它的寻址没有查询、没有打分加权,是确定性的哈希;读出之后虽有上下文门控,但那不是注意力式的检索。它提供的是"记忆",不是"检索"。
- 把 \(\sum\alpha^k\) 当成"每次前向输出的 token 数"。那是接受长度;输出长度还要 +1(纠正或额外 token)。也别用 \(\alpha^\gamma\)——那是"全部接受"的概率。