45 分钟
1 次观看

02|token 表示与 Transformer 张量流

返回导读 · 本篇目标:从一个字符串追踪到下一 token 的概率,并区分网络容量与实际计算。

先把三个对象分开:字符串的字节/子词、token 的整数 ID、某个位置的特征向量。Tokenizer 把字符串变成 ID;embedding 按 ID 查表;decoder 让这个位置读取可见历史;最后在词表轴上产生下一 token 的概率。“合并词表”与“训练网络”是两个过程。

为读清同一条样本,主线单独规定按字符切分 abac,教学编号为 a=3,b=7,c=2,EOS=9;这不是任何厂商 tokenizer 的切分结果。

四个字符按位置编码成ID与八维特征,经过decoder后在词表轴预测下一项

阅读图|横向跟一次表示变化;四行始终对应位置 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。

BPE先学规则,再用固定表编码;字节按完整串解码

图 1|上半部是字符合并,下半部是独立的 UTF-8 解码示意。

BPE 的训练和编码要分开。训练阶段统计语料中相邻符号对,选择频繁的对合并,把规则写入合并表;编码阶段使用已经固定的表,按合并优先级处理新字符串。它不在每次输入时重新学习词表。下面是字符级教学例,省略词边界,频数是自设的:

训练项初始切分出现次数
lowl o w5
lowerl o w e r2
newern e w e r6

e r 出现 2+6=82+6=8 次,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 词表大小、权重配套与计算分配

词表ID行、特征列与角色协议的配套关系

图 2|相同 ID 只有在同一份词表里才指向同一含义。

词表扩大可以缩短平均序列,却会扩大 embedding 和输出层。下面这组容量例约定 1K 为 1024 个词表项。若词表大小从 32K 增到 128K,而隐藏宽度保持 4096,一张矩阵的参数量增加约 96K×409696\text{K}\times4096。另一方面,切分更短会降低 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 查出向量,再投影到词表

固定batch和位置后,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 查出对应行。这里固定 B=2B=2 条序列,每条 T=4T=4 个 token,词表 V=1000V=1000,宽度 d=8d=8,attention 使用 H=2H=2 个头、每头 dh=4d_h=4。这些很小的值用于核对形状,不能代表真实旗舰规模。

Xb,t,:=Esb,t,:,Z=HfinalWout.X_{b,t,:}=E_{s_{b,t},:},\qquad Z=H_{\mathrm{final}}W_{\mathrm{out}}.
符号含义、形状与单位
b,tb,tbatch 与位置索引;本例分别为 0,10,1 和 0,…,30,\ldots,3
sstoken ID,整数张量 [B,T][B,T];合法值为 0,…,V−10,\ldots,V-1
EEembedding 参数矩阵 [V,d][V,d];数值无物理单位
XX查表结果 [B,T,d][B,T,d];末轴是特征维
HfinalH_{\mathrm{final}}decoder 最后输出,形状 [B,T,d][B,T,d]
WoutW_{\mathrm{out}}输出投影 [d,V][d,V];可与 E⊤E^\top 共享,也可独立
ZZlogits,形状 [B,T,V][B,T,V];每个位置对词表各项的未归一化分数

若 E3=[0.2,−0.1,0.4,0,…]E_3=[0.2,-0.1,0.4,0,\ldots],输入中两次出现 ID 3,初始向量相同,但经过上下文计算后可以不同。若两处的“苹果”被切成相同 token ID,在水果讨论与公司财报里初始查表行也相同,后续隐藏状态会读取不同历史。这也解释了 embedding 相似度与最终语义能力的差别。

2.2 Attention、FFN 与残差流

一层pre-norm decoder的两次残差及QK位置旋转

图 4|第二次残差保存的是已更新的 Y。RoPE 在此教学结构中作用于 Q/K。

从上往下先追蓝色旁路:它把输入 X 留到第一次相加。中间支路读 Norm(X),投影出 Q/K/value;在本教学结构中 RoPE 旋转 Q 和 K,不旋转 value。attention 聚合后经输出投影回到原宽度,得到 Y。再追橙色旁路:第二次保存的是这个 Y,不是最初的 X。

Y=X+Attn⁡(Norm⁡(X)),X′=Y+FFN⁡(Norm⁡(Y)).\begin{aligned} Y&=X+\operatorname{Attn}(\operatorname{Norm}(X)),\\ X'&=Y+\operatorname{FFN}(\operatorname{Norm}(Y)). \end{aligned}

图里的 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。图中概率属于完整的二候选小词表。

固定位置,在候选词表轴归一化,再把采样ID送回查表

图 5|两个候选的独立教学词表;柱高为概率,不是 logit。

输出投影为每个候选产生一个 logit,softmax 才把它变成概率。只有候选之间的分数差有意义:所有 logits 同时加上 100,softmax 完全相同。两个候选分数相同便各得一半概率;若分差为 log⁡3\log3,概率变为 0.75 与 0.25。一次采样选择 token 后,它的 ID 再回到 embedding,成为下一步输入。隐藏向量并不直接被当作下一步 token,也不需要寻找距离它最近的词向量。

2.4 共享矩阵的两条梯度路径

共享embedding的输入查表梯度和输出分类梯度

图 6|蓝色与绿色是两条梯度来源,落到同一份 E 参数。

先看左侧:输入出现 ID 3,查表直接读这一行。再看右侧:输出分类器要比较候选词,因此当 W_out=Eᵀ 时,许多未在输入里出现的行也可能从输出路径收到梯度。这里的箭头是梯度归属,与图 7 的“读取 value”箭头不同。

共享 embedding 与输出矩阵时,同一套参数承担两种职责:输入时按行查表,输出时为所有词表项打分。输入查表的直接梯度只触及出现的行;输出 softmax 通常会对许多候选行产生梯度。因此,“这个 token 没在当前输入里出现,所以它的 embedding 行不会更新”只有在排除了共享输出及其他路径之后才可能成立。参数共享减少存储,也把输入表示与输出分类的学习联系起来。

3. 因果 attention 的计算与手算

3.1 匹配历史位置并汇入 value

当前位置从历史和自身读取value,未来路径被阻止

图 7|读取 value 的直觉图;箭头不表示梯度或下一 token 采样。

先看图 7 的形状:当前位置 2 从位置 0、1 和自身接收三条 value 内容路径;位置 3 的灰色路径在红线前停止。蓝绿格代表向量内容,不是几个单词,也不表示真实概率。先决定哪些来源可见,再比较分数,再汇入内容。下面把这三件事分到明确的矩阵轴上;两个历史来源的小例和 value 幅度对照,会在 3.1.1、3.1.2 分别展开。

一头 attention 先把输入投影成 query、key、value。Query 可以理解为当前位置要匹配的特征,key 是历史各位置可被匹配的特征,value 是实际汇入的内容;三者都由训练得到的线性投影产生,不是人工写好的知识字段。

Q=XWQ,K=XWK,U=XWV,A=softmax⁡row(QK⊤dh+M),O=AU.\begin{aligned} Q&=XW_Q,\quad K=XW_K,\quad U=XW_V,\\ A&=\operatorname{softmax}_{\mathrm{row}}\left(\frac{QK^\top}{\sqrt{d_h}}+M\right),\\ O&=AU. \end{aligned}
符号含义、形状与单位
XX此处省略 batch,形状 [T,d][T,d],数值无物理单位
WQ,WK,WVW_Q,W_K,W_V各为 [d,dh][d,d_h] 参数矩阵;WVW_V 的下标表示 value
Q,K,UQ,K,U各为 [T,dh][T,d_h];用 UU 表示 value,避免与词表大小 VV 混淆
QK⊤QK^\top[T,T][T,T] 分数矩阵;行是接收位置,列是来源位置
Mi,jM_{i,j}可读处为 0;未来 j>ij>i 为 −∞-\infty,可能再叠加 padding/文档边界限制
AA[T,T][T,T] 无量纲权重;每个有效行之和为 1
OO[T,dh][T,d_h],value 的加权和;多头拼接后再投影回 dd
softmax⁡row\operatorname{softmax}_{\mathrm{row}}沿来源位置轴归一化,不沿 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 通过某套已训练权重计算得到:

Q=2[100111].Q=\sqrt2\begin{bmatrix}1&0\\0&1\\1&1\end{bmatrix}. K=[ln⁡300ln⁡200].K=\begin{bmatrix}\ln3&0\\0&\ln2\\0&0\end{bmatrix}. U=[200366].U=\begin{bmatrix}2&0\\0&3\\6&6\end{bmatrix}.

非对称attention的分数矩阵和沿来源轴归一化的权重

图 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]:

A=[1001/32/301/21/31/6].A=\begin{bmatrix} 1&0&0\\ 1/3&2/3&0\\ 1/2&1/3&1/6 \end{bmatrix}.

每个有效行之和为 1;三列之和分别是 [11/6,1,1/6]。中间列恰好为 1 是本例巧合,不是沿列归一化的证据。这个非对称例能直接发现把 softmax 轴写错的问题。

同一来源顺序下,attention权重乘value并沿来源求和

图 9|三个来源各交一条特征向量;输出保留两个特征。

再沿图 9 的三行读:第三个 query 的权重乘对应的 value 行,而不是把三个概率直接当作输出。

o2=12[2,0]+13[0,3]+16[6,6]=[1,0]+[0,1]+[1,1]=[2,2].\begin{aligned} o_2&=\tfrac12[2,0]+\tfrac13[0,3]+\tfrac16[6,6]\\ &=[1,0]+[0,1]+[1,1]=[2,2]. \end{aligned}

整张输出为 O=[[2,0],[2/3,2],[2,2]],形状 [3,2]。这里的 2 是特征值,不能按词表去解码成 token ID。多个头的输出先在特征轴拼接成 H·d_h,再用输出投影回到残差宽度 d;最后的词表读出仍是图 5 的另一层计算。

3.1.1 两个历史位置的数值代入

两个key的缩放点积指数概率及value乘法

图 10|独立两来源小例,分数、指数、权重与 value 逐项对齐。

现在单独看两个来源,数值与图 8 的三来源矩阵不同。图 10 从上往下给出分数、指数、权重、value,便于只复算一行。

做一个省去投影的两维例子。当前位置 query 为 q=[1,0]q=[1,0],可读取的两个 key 为 k0=[1,0]k_0=[1,0]、k1=[0,1]k_1=[0,1],value 分别为 u0=[2,0]u_0=[2,0]、u1=[0,4]u_1=[0,4]。缩放分数是 [0.7071,0][0.7071,0],指数约为 [2.028,1][2.028,1],权重约为 [0.670,0.330][0.670,0.330],输出因此是 [1.340,1.320][1.340,1.320]。若还有一个未来位置,哪怕与 query 非常相似,只要 mask 设为 −∞-\infty,其 softmax 权重就是 0。

图 10 的输出按权重先取三位小数计算;若保留完整 softmax 精度,权重约为 [0.669762,0.330238],输出约为 [1.339523,1.320954]。两组值说明的是舍入顺序,不是两个不同算法。

3.1.2 权重较大,不保证贡献较大

固定attention权重,只变第二个value幅度的对照

图 11|相同的权重,不同的 value 幅度,输出贡献可以反转。

图 11 固定 Q/K 和 mask,保持权重 [0.75,0.25],只把第二个 value 从 [0,4] 改到 [0,40]。变化的是被加权的内容,不是权重;下面保留可复制的逐项计算。

另设同一行权重a=[0.75,0.25]a=[0.75,0.25],可由分数[log⁡3,0][\log3,0]经softmax得到。取u0=[2,0],u1=[0,4]u_0=[2,0],u_1=[0,4],两条贡献为[1.5,0][1.5,0]和[0,1][0,1],合计[1.5,1][1.5,1]。只把第二个value换为u1′=[0,40]u_1'=[0,40]、保持Q/K和mask不动,权重仍相同,第二条贡献变[0,10][0,10],输出为[1.5,10][1.5,10]。

较小的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,且暂按独立变量处理,dhd_h 个乘积的和方差约为 dhd_h;除以 dh\sqrt{d_h} 后恢复到数量级 1,减少 softmax 一开始过度尖锐。这是近似解释,不意味着训练后的特征仍严格独立。原始 Transformer 报告给出了 scaled dot-product attention;其整套 encoder-decoder 翻译模型与本篇 decoder 教材需分开。来源:Attention Is All You Need。

3.3 已知历史的训练与逐步生成

输入目标错位和query-key可见性是两条不同规则

图 13|本例采用教学 ID;9 指定为 EOS,所有四个目标有效。

训练时各位置的真实历史已经给定,所以可在一个矩阵运算中算出所有位置;因果 mask 保证第 tt 行看不到第 t+1t+1 个 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 和加权和。接口保留头维度 HH,可以并行处理多头;上面的单头教学例取 H=1H=1。它不包含投影、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、轴或预处理泄露问题。这里方阵假设 Tq=TkT_q=T_k;有 cache 的 decode 常是 Tq=1,Tk>1T_q=1,T_k>1,不能直接照搬上述 mask 构造。

带历史缓存的非方阵attention如何按绝对位置屏蔽未来

图 14|新增 query 的局部行 r 与全历史 key 的列 j 不在同一个索引起点。

具体取缓存已有 P=3 个位置,新增 query 有两行 r=0,1,对应绝对位置 i=P+r=3,4。key/value 包含旧历史和本块,长度为 5。因此加性 mask 应是 [2,5]:

Mr,jcache={0,j≤P+r,−∞,j>P+r.M^{\mathrm{cache}}_{r,j}= \begin{cases}0,&j\le P+r,\\-\infty,&j>P+r.\end{cases}

第一行允许 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 的相邻维对旋转,位置决定角度。以下只展示一对二维分量;真实实现使用多个频率与成对维度。

R(mω)=[cos⁡(mω)−sin⁡(mω)sin⁡(mω)cos⁡(mω)],(R(mω)q)⊤R(nω)k=q⊤R((n−m)ω)k.\begin{aligned} &R(m\omega)=\begin{bmatrix}\cos(m\omega)&-\sin(m\omega)\\\sin(m\omega)&\cos(m\omega)\end{bmatrix},\\ &(R(m\omega)q)^\top R(n\omega)k=q^\top R((n-m)\omega)k. \end{aligned}
符号含义、形状与单位
m,nm,nquery/key 的整数位置索引,单位 token 位置
q,kq,k选出的一对二维分量,形状 [2][2],无物理单位
ω\omega此维对每位置旋转的角频率,单位 rad/位置;教学取 π/2\pi/2
RR[2,2][2,2] 正交旋转矩阵;转置等于逆旋转
n−mn-m两位置的相对距离;点积里的旋转只依赖该差值

令 q=k=[1,0]q=k=[1,0]:同位置时点积为 1,相差一个位置为 0,相差两个位置为 -1。真实模型使用多频率组合,不会只按这个周期重复;该例用来检查相对位置信号如何进入匹配。来源:RoFormer。RoPE 能对更长位置计算角度,但“有定义”不等于模型已学会处理任意长序列,长上下文训练留给下一篇。

4.2 GQA 共享的是 K/V 头

本小节把头配置单独改为 Hq=8,Hkv=2,用于解释共享;不与前面主配置 H=2 的数值混用。图 16 的两行是两组头映射,不是前后两个生成时刻。每个组框代表一份 K/V 头状态,而不是给每条箭头复制一份。

八个query头分别读取两组KV,头数与时间轴分开

图 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 在特征轴上计算

同一token的特征轴RMS归一化及独立SwiGLU数值例

图 17|位置固定,特征列参与 RMS;门控是另一个独立小例。

RMSNorm 与 SwiGLU 处理的是特征轴。对单个位置向量,前者按均方根缩放,后者用一个分支调节另一个分支:

图 17 固定 b,t,只沿同一行的两个特征求均方根;不要把相邻 token 的向量放进这个平均。下式的数学索引 j=1,…,d 与图中的数组索引 f=0,…,d−1 都遍历同样 d 个特征,区别只是起点约定。

RMSNorm⁡(x)=γ⊙xd−1∑j=1dxj2+ϵ,\operatorname{RMSNorm}(x)=\gamma\odot\frac{x}{\sqrt{d^{-1}\sum_{j=1}^{d}x_j^2+\epsilon}}, FFN⁡(x)=[SiLU⁡(xWg)⊙(xWu)]Wd.\operatorname{FFN}(x)=\left[\operatorname{SiLU}(xW_g)\odot(xW_u)\right]W_d.
符号含义、形状与单位
x,γx,\gamma当前位置特征和可训练缩放,均为 [d][d],无物理单位
j,ϵj,\epsilon特征索引 1,…,d1,\ldots,d;稳定常数,教学可取 10−610^{-6}
Wg,WuW_g,W_u门与内容投影,各为 [d,df][d,d_f]
WdW_d下投影 [df,d][d_f,d];下标 dd 表示 down,不是隐藏宽度这个变量
⊙,SiLU⁡(a)\odot,\operatorname{SiLU}(a)逐元素乘;aσ(a)a\sigma(a),输入输出形状相同
dfd_fFFN 的中间特征数;门并非离散的开关

4.3.1 均方根与门值的数值代入

若 x=[3,4]x=[3,4],忽略 ϵ\epsilon、取 γ=[1,1]\gamma=[1,1],均方根为 12.5=3.5355\sqrt{12.5}=3.5355,输出约 [0.8485,1.1314][0.8485,1.1314];它没有减去均值。若某个 SwiGLU 维的门前值为 1、内容值为 2,其乘积为 1×σ(1)×2≈1.4621\times\sigma(1)\times2\approx1.462。负值也不是全部硬截为 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 只有两条执行路径。彩色输出路径从灰色区域外侧绕过,不进入未选专家。图中的格纹只表示不同子网络的变换,不是已证实的代码/语言专长。

四个专家保留,当前token只执行两个并加权汇合

图 19|彩色路径绕开灰色未选专家;两条执行路径汇为一个向量。

两条蓝绿实线是当前 token 的前向执行路径,下方两个灰色专家没有路径经过。右侧把选中分支结果加权汇成一个同宽向量;图中的四个专家是直觉示意,公式与下一幅数值图另给精确教学设定。

MoE 常把一套 dense FFN 换成多套专家 FFN,并由 router 对每个 token 选择少量专家。不同层可以作不同选择,同一词在不同上下文也能走不同路线。专家是可学习子网络,“代码专家”“中文专家”之类名称只有经过实证才有意义。稀疏门控方法的早期代表可见 Sparsely-Gated Mixture-of-Experts。

a=xWr,S=TopK⁡(a,k),gi=eai∑j∈Seaj,o=∑i∈SgiFi(x).\begin{aligned} a&=xW_r,\\ S&=\operatorname{TopK}(a,k),\\ g_i&=\frac{e^{a_i}}{\sum_{j\in S}e^{a_j}},\\ o&=\sum_{i\in S}g_iF_i(x). \end{aligned}
符号含义、形状与单位
Wr,aW_r,arouter 参数 [d,En][d,E_n] 和专家分数 [En][E_n];EnE_n 为专家数量
k,Sk,S每 token 选中的专家数和索引集合,∣S∣=k\lvert S\rvert=k
gig_i选中专家内重归一化的无量纲权重;其他专家取 0
Fi(x),oF_i(x),o第 ii 个专家输出与合成输出,均为 [d][d]

5.1.1 选中集合内重归一化的手算

专家分数选择及集合内归一化和输出特征

图 20|列是专家 ID;最后输出的两个数是特征,不是专家编号。

图中前两项分数为 [2,1,0,−1][2,1,0,-1] 的top-2选中项,权重约 [0.731,0.269][0.731,0.269],未选门权重为0。若专家输出分别为 [1,3][1,3] 与 [5,1][5,1],合成结果是 [2.076,2.462][2.076,2.462]。这是“先选择、再在选中集合归一化”的教学版本;真实模型可能采用不同打分、归一化、共享专家或负载控制,不能把这个公式套到一切 MoE。

沿图 20 先固定列 0、1:选中集合为 S={0,1},归一化分母不包含列 2、3。

g0=e2e2+e1≈0.731059,g1=e1e2+e1≈0.268941.g_0=\frac{e^2}{e^2+e^1}\approx0.731059,\qquad g_1=\frac{e^1}{e^2+e^1}\approx0.268941.

两条专家输出的对应特征分别相加: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 的两个专家会在这条两专家图里进出集合,而是把离散选择的边界单独放大。

router分数差跨过零时选择突变以及top1常数门边界

图 21|横轴为控制量 δ(两分数差为 2δ),纵轴为 router logit;不是训练 loss 曲线。

路由也有梯度边界。Top-k 的索引选择是离散的,通常不能像普通线性层那样直接对“换成另一个专家”求连续导数。在选中集合保持不变的局部区域内,门权重仍是可微的;某个专家输出更有利于降低损失,梯度便可通过其门权重影响 router。跨过分数排序边界时,选中集合会突然变化。这解释了为什么路由优化既涉及连续分数,也涉及离散的实际执行路径。

本篇采用选中集合内重归一化,若极端地设 k=1k=1,唯一门权重恒为 1,任务损失就不能通过这个常数权重为 router 提供梯度。实际 top-1 方法可以保留全体 softmax 中的选中概率,或使用另外的路由与辅助目标;不能拿本篇 top-2 教学公式证明所有 top-1 系统的训练机制。负载均衡也不是让每次路由都严格平均,而是控制一段训练中的偏斜,避免少数专家拥堵或长期闲置。

5.4 未选专家的更新与部署存储

每token稀疏执行与batch累计专家使用和存储容量的区别

图 22|同一 batch 的两个 token,各选两个专家,合起来可以用到四个。

图 22 每行是一条 token 路由记录,列是专家 ID;“用”只表示该 token 本次执行。第一行没有执行列 2/3,不表示下一行也不会调用它们。下方 16×100M 的容量账是另一组明确标出的独立参数设定。

从梯度看,当前 token 的任务损失通常直接更新选中专家;未选中的专家会在其他 token 被调用时更新,某些辅助目标或优化器衰减也可影响参数。从部署看,所有专家权重仍要驻留、分片或被按需搬运;“不执行”与“不占存储”是两件事。一个 batch 中不同 token 可能选不同专家,聚合后实际调用的专家数也可能远大于每 token 的 kk。

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 评论…