02|token 表示与 Transformer 张量流
返回导读 · 本篇目标:从一个字符串追踪到下一 token 的概率,并区分网络容量与实际计算。
先把三个对象分开:字符串的字节/子词、token 的整数 ID、某个位置的特征向量。Tokenizer 把字符串变成 ID;embedding 按 ID 查表;decoder 让这个位置读取可见历史;最后在词表轴上产生下一 token 的概率。“合并词表”与“训练网络”是两个过程。
为读清同一条样本,主线单独规定按字符切分 abac,教学编号为 a=3,b=7,c=2,EOS=9;这不是任何厂商 tokenizer 的切分结果。

阅读图|横向跟一次表示变化;四行始终对应位置 0—3。
本文沿一条可以指认位置的主线阅读:数值张量的第 0 条序列是 [3,7,3,2];接上教学结束标记 9=EOS 后,它的训练目标是 [7,3,2,9]。这些 ID 和后面的向量都是本站教学设定,不是某厂商 tokenizer、权重、API 输出或性能实测。为了能手算,attention、二候选 softmax、RMSNorm 和 MoE 会明确采用更小的独立配置;它们的数值不能跨例拼成一套真实模型权重。
本篇采用公开 decoder 模型中常见的教学配置,参数和数值例均为自设。教学 decoder 不能被当作 GPT-6.1 或 Claude 私有结构的确认。 对当前产品只能引用厂商实际披露的部件,缺失信息保留为未知。
阅读顺序是 字符串与编号 → 向量与 decoder → attention → 位置和特征变换 → MoE。先沿一个 token 的前向计算走完整条链,再比较共享、缓存与稀疏执行改变了什么。
1. 字符串、词表与消息协议
1.1 子词表示解决开放词汇问题
一个词表包含有限个 token,但 token 不一定是词。它可能是完整词、词的一部分、空格与词首的组合、标点,甚至一段字节。设想直接按英语单词建词表:新姓名、变量名和拼写变化会不断产生新项;直接按字符切分又会让序列很长。子词方法在两者之间选择:常见片段合并,罕见内容拆开。BPE 被用于神经机器翻译的子词表示,解决了固定词表与开放词汇之间的矛盾。来源:Sennrich 等,BPE 子词论文。
1.2 BPE:先学习合并规则,再编码输入
图 1 上半部先看左侧语料频数,再比较相邻对的加权出现次数。选中 e r 后,右侧形成一个 er 符号;这时还没有训练 Transformer 的 embedding。

图 1|上半部是字符合并,下半部是独立的 UTF-8 解码示意。
BPE 的训练和编码要分开。训练阶段统计语料中相邻符号对,选择频繁的对合并,把规则写入合并表;编码阶段使用已经固定的表,按合并优先级处理新字符串。它不在每次输入时重新学习词表。下面是字符级教学例,省略词边界,频数是自设的:
| 训练项 | 初始切分 | 出现次数 |
|---|---|---|
low | l o w | 5 |
lower | l o w e r | 2 |
newer | n e w e r | 6 |
e r 出现 次,w e 也有 8 次;本例约定并列时优先选 e r。l o 出现 7 次,o w 出现 7 次。因此第一步可把 e r 合成 er;之后重新计数,而不是把原来的所有统计继续沿用。按这份频数重新计数,w er 仍有 8 次,所以下一步先合并成 wer;随后 l o 的 7 次超过其余相邻对,可合并为 lo,此时 lower 为 lo wer。若基础符号表已经包含 s、t,新词 lowest 可以由已有片段和更小符号组合,无需为整词增加新编号;若这两个字母不在初始符号表,单靠这张字符表仍无法编码它,下一节的字节覆盖正是另一项约定。实际 tokenizer 的预切分规则、边界标记和 tie-breaking 会影响结果,这个小表只解释合并机制。
1.3 字节覆盖与完整解码
现在只看图 1 下半部。格内 E7、8C、AB 是十六进制字节,不是 token ID,也不是三个模型特征。橙色括分表示一次自设 token 切分;两段字节必须重新连接后才能解释字符。
猫 的 UTF-8 字节是 E7 8C AB;图下半部把它教学性地拆成 E7 8C 与 AB,说明单个token未必可独立解码。这不声称真实tokenizer必然采用这种切分。
Byte-level BPE 则以 UTF-8 字节作为覆盖基础。ASCII 字符常对应一个字节,很多汉字对应三个字节;“字节数”与“token 数”没有固定比例。一个 token 可以合并多个字节,而单个 token 解码出的字节片段也可能不是完整 Unicode 字符。把整段 token 的字节连接后再解码,通常才能恢复正确字符串。OpenAI 的 tiktoken 教学实现展示了从单字节起步、按 rank 合并的机制;实际词表由具体 encoding 决定。来源:tiktoken 官方教学实现。
1.4 词表大小、权重配套与计算分配

图 2|相同 ID 只有在同一份词表里才指向同一含义。
词表扩大可以缩短平均序列,却会扩大 embedding 和输出层。下面这组容量例约定 1K 为 1024 个词表项。若词表大小从 32K 增到 128K,而隐藏宽度保持 4096,一张矩阵的参数量增加约 。另一方面,切分更短会降低 attention 的序列计算。Tokenizer 同时改变表示单位、序列长度和计算分配。 不同 tokenizer 下的 token 数、perplexity 和价格单位不能直接当作同一种语言工作量。
编码后的编号需要与模型权重配套。若甲 tokenizer 把 猫 编为 17,乙 tokenizer 的 17 却表示 return,相同编号进入同一 embedding 会读取错误的行。词表中新增一个 token,也不是只在词典里多写一条:网络需要对应的 embedding 与输出参数,并通过训练建立使用方式。字节覆盖解决了字符串可编码的问题,没有解决模型是否理解这段字符串的问题。
1.5 正文内容与角色标记的边界
回看图 2 的红字:字符串 assistant 是正文内容,角色标记则属于消息协议。两者即使看起来使用同一个词,也不能只凭显示文字判断实际 ID。
特殊 token 还承担协议作用。普通正文中出现“assistant”这个词,与真正的 assistant 角色标记可能使用完全不同的编号和解析规则。处理输入时应明确哪些特殊标记允许被识别,哪些只作为正文编码;否则用户提供的文字可能被误解为消息结构。检查 tokenizer 最基本的方法是比较原字符串与整段 encode/decode 的结果,再看空格、换行、中文和特殊标记的分割。逐个 token 单独转成 Unicode 再拼接,并不总与整段字节解码相同。
2. Embedding 与 decoder 张量流
2.1 从 ID 查出向量,再投影到词表

图 3|ID 张量的列是位置;embedding 矩阵的列是特征。
图中两条序列都从同一个参数矩阵查表;ID 3总索引同一行,后续隐藏状态仍可因历史不同而变化。具体词义不能从单个向量分量直接读出。
先沿图 3 的蓝色索引走一遍:s[0,2]=3,所以 X[0,2,:]=E[3,:];若 E[3,0]=0.2,则 X[0,2,0]=0.2。同一条序列的位置 0 也出现 ID 3,所以它的初始查表向量相同。两个位置的可见历史不同,之后的隐藏状态可以不同。图中 … 省略的是其余特征,不是省略了新的 token。
同一个长方形换了计算对象,行列意义也会改变。后文统一用 i 指接收位置、j 指来源位置;下表的数组索引从 0 开始:
| 对象 | 固定哪些轴后看一张二维图 | 图中的行与列 | 格内是什么 |
|---|---|---|---|
s [B,T] | 无需固定 | 行=batch b;列=位置 t | 整数 token ID |
E [V,d] | 无需固定 | 行=词表 ID;列=特征 f | 可训练参数 |
X [B,T,d] | 固定 b | 行=位置 t;列=特征 f | 该位置的向量分量 |
Q,K,U [B,H,T,d_h] | 固定 b,h | 行=位置;列=头内特征 | 投影后的分量 |
S,A [B,H,T,T] | 固定 b,h | 行=query i;列=key 来源 j | 分数或概率权重 |
O [B,H,T,d_h] | 固定 b,h | 行=接收位置 i;列=输出特征 | 汇入的 value 分量 |
Z [B,T,V] | 固定 b,t | 剩下词表候选轴 v | 每个候选的 logit |
这里 S 只是本篇新例用于缩放分数的记号;小写 s 一直表示 ID 张量。ID 0 在这张教学表里是普通合法编号,不能未经约定就当作 padding。
整数 ID 只有索引意义:编号 17 不比编号 8 多“9 个语义单位”。Embedding 是可训练矩阵,按 ID 查出对应行。这里固定 条序列,每条 个 token,词表 ,宽度 ,attention 使用 个头、每头 。这些很小的值用于核对形状,不能代表真实旗舰规模。
| 符号 | 含义、形状与单位 |
|---|---|
| batch 与位置索引;本例分别为 和 | |
| token ID,整数张量 ;合法值为 | |
| embedding 参数矩阵 ;数值无物理单位 | |
| 查表结果 ;末轴是特征维 | |
| decoder 最后输出,形状 | |
| 输出投影 ;可与 共享,也可独立 | |
| logits,形状 ;每个位置对词表各项的未归一化分数 |
若 ,输入中两次出现 ID 3,初始向量相同,但经过上下文计算后可以不同。若两处的“苹果”被切成相同 token ID,在水果讨论与公司财报里初始查表行也相同,后续隐藏状态会读取不同历史。这也解释了 embedding 相似度与最终语义能力的差别。
2.2 Attention、FFN 与残差流

图 4|第二次残差保存的是已更新的 Y。RoPE 在此教学结构中作用于 Q/K。
从上往下先追蓝色旁路:它把输入 X 留到第一次相加。中间支路读 Norm(X),投影出 Q/K/value;在本教学结构中 RoPE 旋转 Q 和 K,不旋转 value。attention 聚合后经输出投影回到原宽度,得到 Y。再追橙色旁路:第二次保存的是这个 Y,不是最初的 X。
图里的 X,Y,X′ 都是 [B,T,d]。attention 跨位置取信息,FFN 对每个位置分别处理最后的特征轴;它们不把 batch 行混在一起。图 4 是 pre-norm 的具体教学结构,不能套到采用 post-norm 或其他位置处理的全部模型。
从图中读一层:attention 子层负责跨位置交换信息,FFN 在每个位置变换特征,残差流保持统一宽度。图中的顺序可在公开 Llama 实现中找到类似结构,但它是一个具体公开实现,并不是所有模型的唯一架构。来源:Meta Llama 官方模型代码。
2.3 Logits、概率与重新输入
图 5 固定一个输出位置,只为手算另设两候选词表,ID 为 0、1。它不是从前面的 1000 类 softmax 任取两项、再假装两项之和为 1。图中概率属于完整的二候选小词表。

图 5|两个候选的独立教学词表;柱高为概率,不是 logit。
输出投影为每个候选产生一个 logit,softmax 才把它变成概率。只有候选之间的分数差有意义:所有 logits 同时加上 100,softmax 完全相同。两个候选分数相同便各得一半概率;若分差为 ,概率变为 0.75 与 0.25。一次采样选择 token 后,它的 ID 再回到 embedding,成为下一步输入。隐藏向量并不直接被当作下一步 token,也不需要寻找距离它最近的词向量。
2.4 共享矩阵的两条梯度路径

图 6|蓝色与绿色是两条梯度来源,落到同一份 E 参数。
先看左侧:输入出现 ID 3,查表直接读这一行。再看右侧:输出分类器要比较候选词,因此当 W_out=Eᵀ 时,许多未在输入里出现的行也可能从输出路径收到梯度。这里的箭头是梯度归属,与图 7 的“读取 value”箭头不同。
共享 embedding 与输出矩阵时,同一套参数承担两种职责:输入时按行查表,输出时为所有词表项打分。输入查表的直接梯度只触及出现的行;输出 softmax 通常会对许多候选行产生梯度。因此,“这个 token 没在当前输入里出现,所以它的 embedding 行不会更新”只有在排除了共享输出及其他路径之后才可能成立。参数共享减少存储,也把输入表示与输出分类的学习联系起来。
3. 因果 attention 的计算与手算
3.1 匹配历史位置并汇入 value

图 7|读取 value 的直觉图;箭头不表示梯度或下一 token 采样。
先看图 7 的形状:当前位置 2 从位置 0、1 和自身接收三条 value 内容路径;位置 3 的灰色路径在红线前停止。蓝绿格代表向量内容,不是几个单词,也不表示真实概率。先决定哪些来源可见,再比较分数,再汇入内容。下面把这三件事分到明确的矩阵轴上;两个历史来源的小例和 value 幅度对照,会在 3.1.1、3.1.2 分别展开。
一头 attention 先把输入投影成 query、key、value。Query 可以理解为当前位置要匹配的特征,key 是历史各位置可被匹配的特征,value 是实际汇入的内容;三者都由训练得到的线性投影产生,不是人工写好的知识字段。
| 符号 | 含义、形状与单位 |
|---|---|
| 此处省略 batch,形状 ,数值无物理单位 | |
| 各为 参数矩阵; 的下标表示 value | |
| 各为 ;用 表示 value,避免与词表大小 混淆 | |
| 分数矩阵;行是接收位置,列是来源位置 | |
| 可读处为 0;未来 为 ,可能再叠加 padding/文档边界限制 | |
| 无量纲权重;每个有效行之和为 1 | |
| ,value 的加权和;多头拼接后再投影回 | |
| 沿来源位置轴归一化,不沿 batch 或特征轴归一化 |
这里的 row 意味着固定一行 i,沿列 j 做 softmax,等价于代码对最后一维 softmax(dim=-1)。value 仍按同一来源 j 排列:A[i,j] 要乘 U[j,:],不能乘 U[i,:]。分数与概率无物理单位;输出的分量仍属于特征空间。
用一个非对称矩阵走完整条路径。 沿用主线的前三个位置 0、1、2,为了手算单独固定 B=H=1,T=3,d_h=2,直接给出 Q/K/U。它们是人为选取的投影结果,不声称由图 3 的示例 embedding 通过某套已训练权重计算得到:

图 8|每个格子先问“谁读取谁”,再问格内是分数还是权重。
先读上方 S[1,0]=0:接收位置 1 的 query 与来源位置 0 的 key,缩放点积为 0。它不是“来源 0 不可见”;分数 0 的指数是 1。真正的未来格 S[1,2] 要先加 −∞,才会在 softmax 后得到 0。
固定第二行 i=1,只读来源 j=0,1。两个有效指数为 [1,2],所以权重是 [1/3,2/3,0]。第三行的三个指数为 [3,2,1],权重为 [1/2,1/3,1/6]:
每个有效行之和为 1;三列之和分别是 [11/6,1,1/6]。中间列恰好为 1 是本例巧合,不是沿列归一化的证据。这个非对称例能直接发现把 softmax 轴写错的问题。

图 9|三个来源各交一条特征向量;输出保留两个特征。
再沿图 9 的三行读:第三个 query 的权重乘对应的 value 行,而不是把三个概率直接当作输出。
整张输出为 O=[[2,0],[2/3,2],[2,2]],形状 [3,2]。这里的 2 是特征值,不能按词表去解码成 token ID。多个头的输出先在特征轴拼接成 H·d_h,再用输出投影回到残差宽度 d;最后的词表读出仍是图 5 的另一层计算。
3.1.1 两个历史位置的数值代入

图 10|独立两来源小例,分数、指数、权重与 value 逐项对齐。
现在单独看两个来源,数值与图 8 的三来源矩阵不同。图 10 从上往下给出分数、指数、权重、value,便于只复算一行。
做一个省去投影的两维例子。当前位置 query 为 ,可读取的两个 key 为 、,value 分别为 、。缩放分数是 ,指数约为 ,权重约为 ,输出因此是 。若还有一个未来位置,哪怕与 query 非常相似,只要 mask 设为 ,其 softmax 权重就是 0。
图 10 的输出按权重先取三位小数计算;若保留完整 softmax 精度,权重约为 [0.669762,0.330238],输出约为 [1.339523,1.320954]。两组值说明的是舍入顺序,不是两个不同算法。
3.1.2 权重较大,不保证贡献较大

图 11|相同的权重,不同的 value 幅度,输出贡献可以反转。
图 11 固定 Q/K 和 mask,保持权重 [0.75,0.25],只把第二个 value 从 [0,4] 改到 [0,40]。变化的是被加权的内容,不是权重;下面保留可复制的逐项计算。
另设同一行权重,可由分数经softmax得到。取,两条贡献为和,合计。只把第二个value换为、保持Q/K和mask不动,权重仍相同,第二条贡献变,输出为。
较小的0.25权重现带来长度10的分量,超过0.75权重对应的1.5。因此“权重最大的位置就是最终最重要的证据”不能从此推出。真实多头输出还要拼接、投影、残差相加;不同value可能抵消,最终行为更不能仅按一张attention热图解释。这里固定Q/K独立改变U,是为隔离乘法关系的教学对照,并非声称真实网络每次能任意独立改变三者。
3.2 缩放控制初始分数尺度

图 12|横轴 d_h,纵轴未缩放分数的方差;不是实测性能曲线。
蓝柱按 d_h=1,4,16 分别高 1、4、16;除以 √d_h 后,方差除以 d_h,所以都回到约 1。这里是在改变求和的特征数,不是改变可读 token 的数量。
图中的方差柱是独立近似下的尺度示意,不是实测。缩放项可以自己估计:若 query/key 每维均值为 0、方差约 1,且暂按独立变量处理, 个乘积的和方差约为 ;除以 后恢复到数量级 1,减少 softmax 一开始过度尖锐。这是近似解释,不意味着训练后的特征仍严格独立。原始 Transformer 报告给出了 scaled dot-product attention;其整套 encoder-decoder 翻译模型与本篇 decoder 教材需分开。来源:Attention Is All You Need。
3.3 已知历史的训练与逐步生成

图 13|本例采用教学 ID;9 指定为 EOS,所有四个目标有效。
训练时各位置的真实历史已经给定,所以可在一个矩阵运算中算出所有位置;因果 mask 保证第 行看不到第 个 token。生成时未来 token 尚未产生,只能逐步追加。训练的并行来自已知历史,因果性来自可见范围。 不能把“并行算训练序列”理解为“生成整段答案时没有依赖”。
图 13 上半部固定同一列 t=2:输入 ID 是 x[2]=3,这一行的 logits 监督目标是 y[2]=2。它读取 x[0:3]=[3,7,3],不能读取 x[3]=2;知道标签用于算损失,不等于让注意力读标签。
这个纯语言建模小例的四个目标都有效,loss mask 是 [1,1,1,1]。它与图下半部的 4×4 attention 可见性矩阵不是同一种 mask。生成时如果目前只有 [3,7,3],先由最后一行分布采样得到 2,再追加成 [3,7,3,2]。目标右移一格与 attention 屏蔽未来分别解决“预测谁”和“读谁”,不能互相替代。
3.4 验证缩放、mask 与加权和的代码
下面片段只验证图中的缩放、mask 和加权和。接口保留头维度 ,可以并行处理多头;上面的单头教学例取 。它不包含投影、RoPE、dropout 或 KV cache。下面的 PyTorch 片段在本次修订中未执行;本篇新增矩阵、位移与数值已另用标准库逐项复算,属于算术核对,不是模型训练或厂商复现。原专题源码附录继续承接其完整实验入口与记录。
import math
import torch
def causal_attention(q, k, value): # 各为 [B, H, T, Dh]
t = q.shape[-2]
scores = q @ k.transpose(-2, -1) / math.sqrt(q.shape[-1])
future = torch.triu(torch.ones(t, t, dtype=torch.bool,
device=q.device), diagonal=1)
scores = scores.masked_fill(future, float("-inf"))
weights = scores.softmax(dim=-1)
return weights @ value, weights
预期输出 out.shape == q.shape,权重形状为 [B,H,T,T],上三角为 0。检查时可改动未来输入并比较前缀输出;前缀变化说明存在 mask、轴或预处理泄露问题。这里方阵假设 ;有 cache 的 decode 常是 ,不能直接照搬上述 mask 构造。

图 14|新增 query 的局部行 r 与全历史 key 的列 j 不在同一个索引起点。
具体取缓存已有 P=3 个位置,新增 query 有两行 r=0,1,对应绝对位置 i=P+r=3,4。key/value 包含旧历史和本块,长度为 5。因此加性 mask 应是 [2,5]:
第一行允许 j=0,1,2,3,屏蔽本块尚在未来的 j=4;第二行允许五项。把本地 query 行号 0 直接拿来与全历史列号比较,会错删历史。若确实只有一个新增 query,且 key 全是已可用历史和当前位置,那么这一行可以全部允许;但这不是通用的方阵 mask 规则。
代码中的 scores.softmax(dim=-1) 总沿最后的 key 来源轴归一化;weights @ value 再沿这个来源轴求和,保留 query 行和 value 特征列。每个有效 query 至少要有一个可读来源;若整行都是 −∞,softmax 可能产生非数值,不能指望后面乘零修复。padding、打包文档与 SFT 目标边界会在后文分别处理。
4. 位置、共享 KV 与门控特征
4.1 RoPE 将相对距离写进匹配
这次固定一个 query/key 的二维特征对。图 15 圆盘的水平/竖直方向是两个特征坐标,不是 token 时间轴;沿圆盘顺序变化的是两个 token 的相对距离。蓝色是 q,绿色是相对旋转后的 k,点积观察的是它在 q 方向上的投影。

图 15|蓝色 q 与绿色相对旋转后的 k;角度来自位置差,0°/90°/180° 对应点积 1/0/−1。
蓝色 q 的基准方向始终向右;沿三个圆盘从左到右看,绿色 k 与它同向、垂直、反向,水平投影分别为正、零、负。0°时两者方向重合,可见箭头主要呈绿色;公式一律取同一原点。这里只画一个维对与单一频率,真实模型使用多频率组合。
单次 attention 对已经可见的 key/value 集合做加权汇总;如果只交换历史 key/value 对的排列而不加入位置信号,汇总不会因此改变。因果 mask 本身也携带顺序约束,多层网络还可能据此获得部分位置信息,所以不能断言没有显式位置编码的 causal decoder 完全无法区分顺序。RoPE 提供更直接的位置依赖:将 query/key 的相邻维对旋转,位置决定角度。以下只展示一对二维分量;真实实现使用多个频率与成对维度。
| 符号 | 含义、形状与单位 |
|---|---|
| query/key 的整数位置索引,单位 token 位置 | |
| 选出的一对二维分量,形状 ,无物理单位 | |
| 此维对每位置旋转的角频率,单位 rad/位置;教学取 | |
| 正交旋转矩阵;转置等于逆旋转 | |
| 两位置的相对距离;点积里的旋转只依赖该差值 |
令 :同位置时点积为 1,相差一个位置为 0,相差两个位置为 -1。真实模型使用多频率组合,不会只按这个周期重复;该例用来检查相对位置信号如何进入匹配。来源:RoFormer。RoPE 能对更长位置计算角度,但“有定义”不等于模型已学会处理任意长序列,长上下文训练留给下一篇。
4.2 GQA 共享的是 K/V 头
本小节把头配置单独改为 Hq=8,Hkv=2,用于解释共享;不与前面主配置 H=2 的数值混用。图 16 的两行是两组头映射,不是前后两个生成时刻。每个组框代表一份 K/V 头状态,而不是给每条箭头复制一份。

图 16|每四个 query 头读取同一组 K/V,query 头仍有八个。
多头 attention 可以让不同 query 子空间读取不同信息;每头都保留独立 K/V 会增加 decode 缓存。GQA 让多个 query 头共用一组 KV 头。若 8 个 query 头共用 2 个 KV 头,每 4 个 query 头使用一组 K/V;以相同长度和精度计算,KV 的主体缓存为独立 8 头方案的四分之一。Query 头与 attention 权重并没有因此只剩 2 个。来源:GQA。图上八个query头没有消失,只是每四个连接到同一KV组。上述四分之一只适用于KV主体,不代表总显存或延迟降低四倍;GQA改变共享结构和访存成本,不把历史压成固定摘要。
上述共享与缓存采用普通教学decoder配置。显式跨层共享需按报告单独核对,参看第10篇DeepSeek案例。
若只数一层、一个请求、历史 S=3、每头宽 d_h=2,两组 K/V 含 2×1×2×3×2=24 个数;八组含 96 个数。开头的 2 计 K 与 value 两份。形状分别是 Q:[B,8,T,d_h]、K/U:[B,2,T,d_h],每个 query 头仍有自己的注意力权重 [T,T]。这个元素数例没有计权重、allocator、workspace、跨层共享或真实延迟。
4.3 RMSNorm 与 SwiGLU 在特征轴上计算

图 17|位置固定,特征列参与 RMS;门控是另一个独立小例。
RMSNorm 与 SwiGLU 处理的是特征轴。对单个位置向量,前者按均方根缩放,后者用一个分支调节另一个分支:
图 17 固定 b,t,只沿同一行的两个特征求均方根;不要把相邻 token 的向量放进这个平均。下式的数学索引 j=1,…,d 与图中的数组索引 f=0,…,d−1 都遍历同样 d 个特征,区别只是起点约定。
| 符号 | 含义、形状与单位 |
|---|---|
| 当前位置特征和可训练缩放,均为 ,无物理单位 | |
| 特征索引 ;稳定常数,教学可取 | |
| 门与内容投影,各为 | |
| 下投影 ;下标 表示 down,不是隐藏宽度这个变量 | |
| 逐元素乘;,输入输出形状相同 | |
| FFN 的中间特征数;门并非离散的开关 |
4.3.1 均方根与门值的数值代入
若 ,忽略 、取 ,均方根为 ,输出约 ;它没有减去均值。若某个 SwiGLU 维的门前值为 1、内容值为 2,其乘积为 。负值也不是全部硬截为 0。两种设计分别参考 RMSNorm 与 GLU Variants;这里的数值是原创教学演算。
4.4 残差保留直接相加路径

图 18|残差按特征相加,保持宽度;数值为独立教学设定。
图中蓝色直路与绿色子层在同宽的加号会合:残差连接把子层输出加回输入,保持宽度并为梯度提供直接路径;它不保证每层都只做微小变化,也不保证任意深度自然稳定。训练系统仍需处理初始化、数值精度和梯度异常。
图 18 的独立小例逐列相加:第 0 特征 1+0.5=1.5,第 1 特征 2+(−1)=1。它既不把两向量拼成 4 维,也不把位置 0 与位置 1 混加。回到图 4,每次相加的两边都必须属于同一个 [B,T,d] 接口。
5. 稀疏 MoE 的容量与代价
5.1 从一个 token 分派到少量专家
图 19 先看两个彩色模块,再看两个灰色模块:四套专家参数都还存在,但当前 token 只有两条执行路径。彩色输出路径从灰色区域外侧绕过,不进入未选专家。图中的格纹只表示不同子网络的变换,不是已证实的代码/语言专长。

图 19|彩色路径绕开灰色未选专家;两条执行路径汇为一个向量。
两条蓝绿实线是当前 token 的前向执行路径,下方两个灰色专家没有路径经过。右侧把选中分支结果加权汇成一个同宽向量;图中的四个专家是直觉示意,公式与下一幅数值图另给精确教学设定。
MoE 常把一套 dense FFN 换成多套专家 FFN,并由 router 对每个 token 选择少量专家。不同层可以作不同选择,同一词在不同上下文也能走不同路线。专家是可学习子网络,“代码专家”“中文专家”之类名称只有经过实证才有意义。稀疏门控方法的早期代表可见 Sparsely-Gated Mixture-of-Experts。
| 符号 | 含义、形状与单位 |
|---|---|
| router 参数 和专家分数 ; 为专家数量 | |
| 每 token 选中的专家数和索引集合, | |
| 选中专家内重归一化的无量纲权重;其他专家取 0 | |
| 第 个专家输出与合成输出,均为 |
5.1.1 选中集合内重归一化的手算

图 20|列是专家 ID;最后输出的两个数是特征,不是专家编号。
图中前两项分数为 的top-2选中项,权重约 ,未选门权重为0。若专家输出分别为 与 ,合成结果是 。这是“先选择、再在选中集合归一化”的教学版本;真实模型可能采用不同打分、归一化、共享专家或负载控制,不能把这个公式套到一切 MoE。
沿图 20 先固定列 0、1:选中集合为 S={0,1},归一化分母不包含列 2、3。
两条专家输出的对应特征分别相加:o[0]=g0×1+g1×5,o[1]=g0×3+g1×1。使用图中三位门权重时正好得到 [2.076,2.462];用完整精度后约为 [2.075766,2.462117]。与 attention 类似,归一化轴是“参与汇合的来源”,输出列仍是特征。
5.2 总容量、激活与跨设备调度
若 16 个专家每个有 100M 参数、每次选 2 个,专家总参数为 1.6B,本次专家计算涉及约 200M;还要加上 attention、embedding、router 等共享部件。总参数影响存储,激活参数影响部分计算,两者都不能独立预测延迟。专家分散在多个设备时还要把 token 派发出去、合并回来;热门专家会成为瓶颈,容量上限、token dropping 或负载均衡又会影响训练。稀疏选择带来的梯度和硬件调度复杂度必须一起考虑。
5.3 离散选择、连续门权重与 top-1 边界
图 21 单独用两专家的 Top-1 选择观察边界:a0=δ,a1=−δ,横轴是控制量 δ,两分数的差是 2δ,纵轴是两个 router 分数。δ 穿过 0 时,分数仍连续,选中的专家却从 1 切换为 0;并列处要另定 tie-breaking。它不是在声称 Top-2 的两个专家会在这条两专家图里进出集合,而是把离散选择的边界单独放大。

图 21|横轴为控制量 δ(两分数差为 2δ),纵轴为 router logit;不是训练 loss 曲线。
路由也有梯度边界。Top-k 的索引选择是离散的,通常不能像普通线性层那样直接对“换成另一个专家”求连续导数。在选中集合保持不变的局部区域内,门权重仍是可微的;某个专家输出更有利于降低损失,梯度便可通过其门权重影响 router。跨过分数排序边界时,选中集合会突然变化。这解释了为什么路由优化既涉及连续分数,也涉及离散的实际执行路径。
本篇采用选中集合内重归一化,若极端地设 ,唯一门权重恒为 1,任务损失就不能通过这个常数权重为 router 提供梯度。实际 top-1 方法可以保留全体 softmax 中的选中概率,或使用另外的路由与辅助目标;不能拿本篇 top-2 教学公式证明所有 top-1 系统的训练机制。负载均衡也不是让每次路由都严格平均,而是控制一段训练中的偏斜,避免少数专家拥堵或长期闲置。
5.4 未选专家的更新与部署存储

图 22|同一 batch 的两个 token,各选两个专家,合起来可以用到四个。
图 22 每行是一条 token 路由记录,列是专家 ID;“用”只表示该 token 本次执行。第一行没有执行列 2/3,不表示下一行也不会调用它们。下方 16×100M 的容量账是另一组明确标出的独立参数设定。
从梯度看,当前 token 的任务损失通常直接更新选中专家;未选中的专家会在其他 token 被调用时更新,某些辅助目标或优化器衰减也可影响参数。从部署看,所有专家权重仍要驻留、分片或被按需搬运;“不执行”与“不占存储”是两件事。一个 batch 中不同 token 可能选不同专家,聚合后实际调用的专家数也可能远大于每 token 的 。
5.5 阅读边界与下一步
最后检查三个边界:attention 权重不是可直接读取的完整“解释”;增大词表不自动提高知识覆盖;增加 MoE 总参数不等于每次 forward 都执行全部参数。本篇已经把 ID、向量、位置、可见范围和参数容量区分开。下一篇沿这条链追踪损失怎样产生、数据怎样组成训练分布、梯度怎样更新网络。
可以先不看代码,沿图回答四个具体问题:
- 图 8 的
A[1,0]=1/3:是位置 1 读取位置 0 的权重;不是“第 1 个词占 batch 的 1/3”。 - 图 13 的
Z[0,2,:]:候选轴是词表,监督目标为y[2]=2;目标正确并不保证模型的最大概率候选就是 2。 - 图 14 的第一行:允许 key 0–3,屏蔽 key 4;query 的局部行 0 不等于绝对位置 0。
- 图 22 每个 token 都只选 2 个,为什么还要存 4 套专家?因为另一个 token 可以选择别的专家,未执行也不等于不存在。
这些问题都落在一个格子、一条路径或一次运算上。读清它们以后,才把这一层重复成多层、把一个位置扩成整批,再追踪梯度怎样更新参数。
正在连接 GitHub 评论…