贯通算例:两句话怎样穿过一层模型
同一词表、同一组参数、两句有标签的输入:从数字表示走到概率,再走一次带缓存的解码。
建议先读F01—F16。每张表的行对应哪个Token、列对应哪个特征均写明。显示数值保留六位小数,计算用未舍入值。这里不要求模型生成有意义的中文。
本章目录
沿用“我爱学习/你爱”,把一层模型真正算到底
输入与条件。两句话仍采用六行四列Embedding表。下面指定一个未训练的教学模型:一层、隐藏宽度4、两个注意力头,每头宽度2,前置LayerNorm、残差、SwiGLU前馈和绑定Embedding的输出投影。为减少变量,本例不加位置编码、偏置或dropout。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1分别查表 | “我/爱/学习”取第1、2、3行;“你/爱”取第4、2行 | 两句分别是3×4和2×4。PAD只用于批次展示,实际数值见证逐条处理有效Token。 |
| 2逐位置归一化 | 每一排4个数分别减均值、除以标准差 | 归一化不会混合两句话,也不会把不同Token平均成一句话。 |
| 3计算两头注意力 | 各头分别生成分数、因果掩码、概率和Value混合 | 两个头每个位置各产出2个数,拼回4个数后,加回原来的Embedding。 |
| 4经过前馈层 | 再次归一化,再计算0.25×SiLU(z)×z并加残差 | 每个位置有4个更新后的数;最后位置才用于本轮下一个Token的预测。 |
| 5投影回词表 | 最后位置的4个数与每个词表行点积,再做softmax | 本例解码规则显式禁止PAD;“我爱学习”末尾产生的最高概率项是“爱”,约0.307383。 |
| 6继续一次 | 把刚选出的“爱”编号2输入,同时带入旧K/V | 增量最后状态与对“我/爱/学习/爱”全量重算相同,数值误差小于1e−12。 |
把过程连起来。下方逐表展示全部中间张量以及每个矩阵的取值。不能只从“预测出爱”理解模型;真正要观察的是同一份输入怎样经过每一个算子。
边界与反例。权重全部人为设置,预测重复的“爱”不体现语言能力。没有位置编码的这个简化模型也不代表现代商业模型的完整架构。
1.先固定全部参数,不把任何矩阵藏起来
词表与Embedding E
文字先变成编号,再取对应行。下面的矩阵乘法只作用于这些已声明的教学数字。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 0 · PAD | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 1 · 我 | 0.200000 | -0.100000 | 0.700000 | 0.400000 |
| 2 · 爱 | -0.300000 | 0.800000 | 0.100000 | 0.500000 |
| 3 · 学习 | 0.600000 | 0.200000 | -0.400000 | 0.900000 |
| 4 · 你 | 0.100000 | -0.500000 | 0.900000 | 0.300000 |
| 5 · ! | -0.200000 | 0.400000 | 0.500000 | -0.600000 |
Query投影 Wq
每个Token的行向量右乘这个矩阵。Q/K/V分头时把第0、1个特征交给头0,第2、3个特征交给头1。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 | 1.000000 | 0.000000 | 0.000000 | 0.000000 |
| 位置1 | 0.000000 | 1.000000 | 0.000000 | 0.000000 |
| 位置2 | 0.000000 | 0.000000 | 1.000000 | 0.000000 |
| 位置3 | 0.000000 | 0.000000 | 0.000000 | 1.000000 |
Key投影 Wk
每个Token的行向量右乘这个矩阵。Q/K/V分头时把第0、1个特征交给头0,第2、3个特征交给头1。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 | 0.000000 | 1.000000 | 0.000000 | 0.000000 |
| 位置1 | 1.000000 | 0.000000 | 0.000000 | 0.000000 |
| 位置2 | 0.000000 | 0.000000 | 0.000000 | 1.000000 |
| 位置3 | 0.000000 | 0.000000 | 1.000000 | 0.000000 |
Value投影 Wv
每个Token的行向量右乘这个矩阵。Q/K/V分头时把第0、1个特征交给头0,第2、3个特征交给头1。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 | 1.000000 | 0.000000 | 0.000000 | 0.000000 |
| 位置1 | 0.000000 | 0.500000 | 0.000000 | 0.000000 |
| 位置2 | 0.000000 | 0.000000 | 1.000000 | 0.000000 |
| 位置3 | 0.000000 | 0.000000 | 0.000000 | 0.500000 |
所有LayerNorm均按每行4个数计算,ε=0.00001、缩放为1、偏置为0。注意力输出投影为单位矩阵。前馈使用Wgate=Wup=I、Wdown=0.25I,所以输出的每项为0.25×SiLU(z)×z。最后再做一次LayerNorm,输出投影为E的转置。无训练、无位置编码、无dropout。以上都是本例明确设定,不是某个厂商模型的参数。
2.“我爱学习”的每一层中间数
本句编号为[1, 2, 3]。下面每行始终代表同一个位置;注意力会混合可见内容,前馈仍逐位置计算。“你爱”只有两个有效位置,PAD不参与本页的逐条运行。
查表后的初始表示 X
这就是文字逐Token查表后的结果;它还没有包含另一位置的内容。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | 0.200000 | -0.100000 | 0.700000 | 0.400000 |
| 位置1 · 爱 | -0.300000 | 0.800000 | 0.100000 | 0.500000 |
| 位置2 · 学习 | 0.600000 | 0.200000 | -0.400000 | 0.900000 |
第一层归一化后的输入
以本行自己的四个数求均值与方差,再归一化。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | -0.342977 | -1.371908 | 1.371908 | 0.342977 |
| 位置1 · 爱 | -1.386912 | 1.266311 | -0.422104 | 0.542705 |
| 位置2 · 学习 | 0.565020 | -0.256827 | -1.489598 | 1.181406 |
Q = LN(X) × Wq
用于给当前查询与键配对打分。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | -0.342977 | -1.371908 | 1.371908 | 0.342977 |
| 位置1 · 爱 | -1.386912 | 1.266311 | -0.422104 | 0.542705 |
| 位置2 · 学习 | 0.565020 | -0.256827 | -1.489598 | 1.181406 |
K = LN(X) × Wk
提供每个可读位置的Key;生成后可按层存入缓存。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | -1.371908 | -0.342977 | 0.342977 | 1.371908 |
| 位置1 · 爱 | 1.266311 | -1.386912 | 0.542705 | -0.422104 |
| 位置2 · 学习 | -0.256827 | 0.565020 | 1.181406 | -1.489598 |
V = LN(X) × Wv
提供最终会按注意力权重混合的内容。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | -0.342977 | -0.685954 | 1.371908 | 0.171488 |
| 位置1 · 爱 | -1.386912 | 0.633155 | -0.422104 | 0.271352 |
| 位置2 · 学习 | 0.565020 | -0.128414 | -1.489598 | 0.590703 |
头0:配对分数(已加因果掩码)
每项为该头的Q与K点积除以√2;未来键位置设为−∞。例如第0个查询只能读第0个Token。
| 对象 | 键0 · 我 | 键1 · 爱 | 键2 · 学习 |
|---|---|---|---|
| 位置0 · 我 | 0.665434 | −∞ | −∞ |
| 位置1 · 爱 | 1.038316 | -2.483729 | −∞ |
| 位置2 · 学习 | -0.485832 | 0.757798 | -0.205220 |
头0:逐行softmax权重
每一行的可见权重和为1。掩掉的位置权重是0,而不是从打印表里省略。
| 对象 | 键0 · 我 | 键1 · 爱 | 键2 · 学习 |
|---|---|---|---|
| 位置0 · 我 | 1.000000 | 0.000000 | 0.000000 |
| 位置1 · 爱 | 0.971309 | 0.028691 | 0.000000 |
| 位置2 · 学习 | 0.172648 | 0.598776 | 0.228576 |
头0:权重 × Value
每个输出分量都把各可见位置的对应Value分量乘权重再相加;不是直接挑一行。
| 对象 | 特征0 | 特征1 |
|---|---|---|
| 位置0 · 我 | -0.342977 | -0.685954 |
| 位置1 · 爱 | -0.372929 | -0.648107 |
| 位置2 · 学习 | -0.760513 | 0.231337 |
头1:配对分数(已加因果掩码)
每项为该头的Q与K点积除以√2;未来键位置设为−∞。例如第0个查询只能读第0个Token。
| 对象 | 键0 · 我 | 键1 · 爱 | 键2 · 学习 |
|---|---|---|---|
| 位置0 · 我 | 0.665434 | −∞ | −∞ |
| 位置1 · 爱 | 0.424101 | -0.323965 | −∞ |
| 位置2 · 学习 | 0.784805 | -0.924251 | -2.488762 |
头1:逐行softmax权重
每一行的可见权重和为1。掩掉的位置权重是0,而不是从打印表里省略。
| 对象 | 键0 · 我 | 键1 · 爱 | 键2 · 学习 |
|---|---|---|---|
| 位置0 · 我 | 1.000000 | 0.000000 | 0.000000 |
| 位置1 · 爱 | 0.678757 | 0.321243 | 0.000000 |
| 位置2 · 学习 | 0.820407 | 0.148524 | 0.031070 |
头1:权重 × Value
每个输出分量都把各可见位置的对应Value分量乘权重再相加;不是直接挑一行。
| 对象 | 特征0 | 特征1 |
|---|---|---|
| 位置0 · 我 | 1.371908 | 0.171488 |
| 位置1 · 爱 | 0.795594 | 0.203569 |
| 位置2 · 学习 | 1.016549 | 0.199346 |
拼接两个头的输出
头0两个分量在前,头1两个分量在后;拼接本身不进行求和。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | -0.342977 | -0.685954 | 1.371908 | 0.171488 |
| 位置1 · 爱 | -0.372929 | -0.648107 | 0.795594 | 0.203569 |
| 位置2 · 学习 | -0.760513 | 0.231337 | 1.016549 | 0.199346 |
第一次残差:X + Attention输出
同一个位置、同一个特征逐元素相加。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | -0.142977 | -0.785954 | 2.071908 | 0.571488 |
| 位置1 · 爱 | -0.672929 | 0.151893 | 0.895594 | 0.703569 |
| 位置2 · 学习 | -0.160513 | 0.431337 | 0.616549 | 1.099346 |
前馈前的第二次归一化
输入来自上一步残差,而不是重新使用原始Embedding。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | -0.537546 | -1.142223 | 1.545407 | 0.134362 |
| 位置1 · 爱 | -1.548113 | -0.193237 | 1.028388 | 0.712962 |
| 位置2 · 学习 | -1.457026 | -0.144867 | 0.265755 | 1.336138 |
门控前馈输出
逐项计算0.25×SiLU(z)×z;所有投影已在参数节声明。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | 0.026639 | 0.078904 | 0.492135 | 0.002408 |
| 位置1 · 爱 | 0.105069 | 0.004218 | 0.194755 | 0.085277 |
| 位置2 · 学习 | 0.100267 | 0.002434 | 0.009994 | 0.353417 |
第二次残差:上一步残差 + 前馈输出
这是本例这一层最终的上下文化表示。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 我 | -0.116338 | -0.707050 | 2.564043 | 0.573897 |
| 位置1 · 爱 | -0.567860 | 0.156111 | 1.090349 | 0.788846 |
| 位置2 · 学习 | -0.060246 | 0.433770 | 0.626543 | 1.452763 |
最后一个有效位置:最终LayerNorm
仅展示用于本轮下一个Token预测的最后位置;前面位置的隐藏状态已经在上表列出。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置2 · 学习 | -1.234289 | -0.328868 | 0.024441 | 1.538716 |
从最后位置的4个数,投影为6个词表分数
每个logit是最后向量与E对应行的点积。然后本例解码策略显式把PAD分数改为−∞,再归一化。PAD向量为零本身不会自动让其概率为零。
| 对象 | 过滤前logit | 过滤PAD后概率 |
|---|---|---|
| 0 · PAD | 0.000000 | 0.000000 |
| 1 · 我 | 0.418624 | 0.193974 |
| 2 · 爱 | 0.878994 | 0.307383 |
| 3 · 学习 | 0.568721 | 0.225387 |
| 4 · 你 | 0.524616 | 0.215663 |
| 5 · ! | -0.795698 | 0.057593 |
本轮贪心选择:爱,编号2。这是人为小模型产生的概率,不是语言正确性结论。
3.“你爱”的每一层中间数
本句编号为[4, 2]。下面每行始终代表同一个位置;注意力会混合可见内容,前馈仍逐位置计算。“你爱”只有两个有效位置,PAD不参与本页的逐条运行。
查表后的初始表示 X
这就是文字逐Token查表后的结果;它还没有包含另一位置的内容。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | 0.100000 | -0.500000 | 0.900000 | 0.300000 |
| 位置1 · 爱 | -0.300000 | 0.800000 | 0.100000 | 0.500000 |
第一层归一化后的输入
以本行自己的四个数求均值与方差,再归一化。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | -0.199996 | -1.399972 | 1.399972 | 0.199996 |
| 位置1 · 爱 | -1.386912 | 1.266311 | -0.422104 | 0.542705 |
Q = LN(X) × Wq
用于给当前查询与键配对打分。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | -0.199996 | -1.399972 | 1.399972 | 0.199996 |
| 位置1 · 爱 | -1.386912 | 1.266311 | -0.422104 | 0.542705 |
K = LN(X) × Wk
提供每个可读位置的Key;生成后可按层存入缓存。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | -1.399972 | -0.199996 | 0.199996 | 1.399972 |
| 位置1 · 爱 | 1.266311 | -1.386912 | 0.542705 | -0.422104 |
V = LN(X) × Wv
提供最终会按注意力权重混合的内容。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | -0.199996 | -0.699986 | 1.399972 | 0.099998 |
| 位置1 · 爱 | -1.386912 | 0.633155 | -0.422104 | 0.271352 |
头0:配对分数(已加因果掩码)
每项为该头的Q与K点积除以√2;未来键位置设为−∞。例如第0个查询只能读第0个Token。
| 对象 | 键0 · 你 | 键1 · 爱 |
|---|---|---|
| 位置0 · 你 | 0.395964 | −∞ |
| 位置1 · 爱 | 1.193865 | -2.483729 |
头0:逐行softmax权重
每一行的可见权重和为1。掩掉的位置权重是0,而不是从打印表里省略。
| 对象 | 键0 · 你 | 键1 · 爱 |
|---|---|---|
| 位置0 · 你 | 1.000000 | 0.000000 |
| 位置1 · 爱 | 0.975340 | 0.024660 |
头0:权重 × Value
每个输出分量都把各可见位置的对应Value分量乘权重再相加;不是直接挑一行。
| 对象 | 特征0 | 特征1 |
|---|---|---|
| 位置0 · 你 | -0.199996 | -0.699986 |
| 位置1 · 爱 | -0.229266 | -0.667110 |
头1:配对分数(已加因果掩码)
每项为该头的Q与K点积除以√2;未来键位置设为−∞。例如第0个查询只能读第0个Token。
| 对象 | 键0 · 你 | 键1 · 爱 |
|---|---|---|
| 位置0 · 你 | 0.395964 | −∞ |
| 位置1 · 爱 | 0.477546 | -0.323965 |
头1:逐行softmax权重
每一行的可见权重和为1。掩掉的位置权重是0,而不是从打印表里省略。
| 对象 | 键0 · 你 | 键1 · 爱 |
|---|---|---|
| 位置0 · 你 | 1.000000 | 0.000000 |
| 位置1 · 爱 | 0.690298 | 0.309702 |
头1:权重 × Value
每个输出分量都把各可见位置的对应Value分量乘权重再相加;不是直接挑一行。
| 对象 | 特征0 | 特征1 |
|---|---|---|
| 位置0 · 你 | 1.399972 | 0.099998 |
| 位置1 · 爱 | 0.835671 | 0.153067 |
拼接两个头的输出
头0两个分量在前,头1两个分量在后;拼接本身不进行求和。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | -0.199996 | -0.699986 | 1.399972 | 0.099998 |
| 位置1 · 爱 | -0.229266 | -0.667110 | 0.835671 | 0.153067 |
第一次残差:X + Attention输出
同一个位置、同一个特征逐元素相加。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | -0.099996 | -1.199986 | 2.299972 | 0.399998 |
| 位置1 · 爱 | -0.529266 | 0.132890 | 0.935671 | 0.653067 |
前馈前的第二次归一化
输入来自上一步残差,而不是重新使用原始Embedding。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | -0.355476 | -1.224424 | 1.540402 | 0.039499 |
| 位置1 · 爱 | -1.483366 | -0.296188 | 1.143117 | 0.636437 |
门控前馈输出
逐项计算0.25×SiLU(z)×z;所有投影已在参数节声明。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | 0.013017 | 0.085140 | 0.488522 | 0.000199 |
| 位置1 · 爱 | 0.101723 | 0.009354 | 0.247705 | 0.066221 |
第二次残差:上一步残差 + 前馈输出
这是本例这一层最终的上下文化表示。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置0 · 你 | -0.086979 | -1.114846 | 2.788494 | 0.400197 |
| 位置1 · 爱 | -0.427543 | 0.142243 | 1.183376 | 0.719288 |
最后一个有效位置:最终LayerNorm
仅展示用于本轮下一个Token预测的最后位置;前面位置的隐藏状态已经在上表列出。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置1 · 爱 | -1.373724 | -0.432813 | 1.286452 | 0.520084 |
从最后位置的4个数,投影为6个词表分数
每个logit是最后向量与E对应行的点积。然后本例解码策略显式把PAD分数改为−∞,再归一化。PAD向量为零本身不会自动让其概率为零。
| 对象 | 过滤前logit | 过滤PAD后概率 |
|---|---|---|
| 0 · PAD | 0.000000 | 0.000000 |
| 1 · 我 | 0.877087 | 0.242054 |
| 2 · 爱 | 0.454554 | 0.158638 |
| 3 · 学习 | -0.957302 | 0.038659 |
| 4 · 你 | 1.392866 | 0.405426 |
| 5 · ! | 0.432795 | 0.155224 |
本轮贪心选择:你,编号4。这是人为小模型产生的概率,不是语言正确性结论。
4.把刚生成的Token真正送回去
第一句的本轮输出是“爱”,编号2。把它作为新Token输入,绝不是把上一轮的最终隐藏向量直接当下一轮输入。新Token仍先查E[2]。旧三个位置的K/V已保存,新Token产生自己的Q/K/V,再与旧K/V一起计算。
新Token“爱”的初始Embedding
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置3 · 爱 | -0.300000 | 0.800000 | 0.100000 | 0.500000 |
新位置的Query
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置3 · 爱 | -1.386912 | 1.266311 | -0.422104 | 0.542705 |
追加的新Key
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置3 · 爱 | 1.266311 | -1.386912 | 0.542705 | -0.422104 |
追加的新Value
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置3 · 爱 | -1.386912 | 0.633155 | -0.422104 | 0.271352 |
带旧缓存得到的新位置最终状态
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 位置3 · 爱 | -0.192474 | 0.395838 | 0.368793 | 1.039792 |
缓存模式与全量重算:最后位置对照
程序核对全部四个分量:最大绝对差为0。这个检查要求使用相同参数、相同前缀和正确掩码。
| 对象 | 特征0 | 特征1 | 特征2 | 特征3 |
|---|---|---|---|---|
| 只算新Token+缓存 | -0.192474 | 0.395838 | 0.368793 | 1.039792 |
| 整句四个Token重算 | -0.192474 | 0.395838 | 0.368793 | 1.039792 |
自检:缓存为什么不能直接换另一句话的
第二句“你爱”的最后一个Token也是“爱”。是否可以把第一句三个位置的缓存接到第二句后面?请先自己判断。
查看参考答案
不能。Token编号相同只说明初始Embedding行相同,不说明两个前缀一样。K/V保存了对应前缀在该层的计算结果;跨句混用会把“我/爱/学习”的历史注入“你/爱”,改变可见信息与结果。