AI Hero Club

Transformer 实战 · 第 3 章

已自行标记完成0 / 12

仅保存在当前浏览器;完成标记不代表通过项目验收。

本章提纲 10 个小节
全课目录 12 章
完整课程计划 ↗
Transformer 实战 / 课程计划 ↗

CHAPTER 03 / 12建议学习 120 分钟

把注意力逐项算清楚

两个位置如何从 Q、K、V 算出读取结果,并把误差传回三个投影?

本章要解决的 4 个问题
  • 从输入与三个独立投影逐轴构造 Q、K、V,复算二维两位置注意力及缩放的假设边界
  • 从 AV、softmax 雅可比和缩放点积逐步推导全部 Q/K/V 与共同输入梯度
  • 用行平移、凸组合边界及全坐标有限差分独立检查前向与反向
  • 用相同键和修改值的反例区分匹配权重、读取内容与词表预测概率
课程项目 ZIP ↓

本章任务:让一次读取既能前向复算,也能反向复算

前两章已经定义 token、嵌入矩阵、softmax 与交叉熵梯度,但每个位置还没有读取其他位置。现在构造单头自注意力:每个位置依据查询与键的匹配,混合各位置的值向量。最终要从一个标量损失回溯到 Q、K、V,再回到生成它们的投影和输入。这里只计算两个位置的完整参考例子,不加因果遮罩;第四章才限制可见信息,并加入多头和位置。

先修为矩阵乘法、第二章的标量链式法则和共享梯度求和。使用行向量约定:X∈RT×DX\in\mathbb R^{T\times D},TT 是位置数、DD 是输入宽度;WQ,WK∈RD×dW_Q,W_K\in\mathbb R^{D\times d},WV∈RD×rW_V\in\mathbb R^{D\times r}。查询 Q=XWQQ=XW_Q、键 K=XWKK=XW_K 均为 T×dT\times d,值 V=XWVV=XW_V 为 T×rT\times r。dd 必须相同才能做查询与键的点积,值宽度 rr 可以不同。此处 VV 表示值矩阵,词表大小不参与本章公式,避免与上一章同字母的另一含义混用。

一、先给三个投影分工,再写读取规则

查询规定当前行以什么特征寻找信息,键规定每个候选位置以什么特征被匹配,值规定被读取的内容。这是三种线性映射的计算职责,不是三份文本或人为规定的三种语义。自注意力表示三者由同一个 X 生成,但权重矩阵彼此独立。若意外将三个矩阵绑定成同一个参数,模型族已经改变,不能只因为形状相同就认为等价。

对查询位置 ii 与候选位置 jj,计算点积分数 Sij=∑aQiaKja/dS_{ij}=\sum_a Q_{ia}K_{ja}/\sqrt d。每个查询要单独在全部候选位置上归一化:Aij=exp⁡(Sij)/∑kexp⁡(Sik)A_{ij}=\exp(S_{ij})/\sum_k\exp(S_{ik})。最后 Oiℓ=∑jAijVjℓO_{i\ell}=\sum_jA_{ij}V_{j\ell}。先有匹配分数,再有按行权重,最后是加权读取,三步的索引含义不同。若沿列归一化,得到的是另一种资源分配,通常不能保证每个查询的读取权重和为一。

S=QKTd,A=softmax⁡row(S),O=AVS=\frac{QK^{\mathsf T}}{\sqrt d},\qquad A=\operatorname{softmax}_{\rm row}(S),\qquad O=AV
S、A 的形状是 T×T;O 的形状是 T×r。此时所有位置都允许互相读取。

注意力权重与词表预测概率的样本空间不同。A 的一行对“读取哪个位置”归一化;第二章的 p 对“下一个 token 是哪个类别”归一化。O 是值向量的混合,不必非负,也不必各维和为一。即便把 A 画成概率图,也不能直接拿最大权重位置当作预测 token,或把图中的亮块当成回答正确的证明。

还要把“转置”从形状记号变成实际索引。若 Q 两行为 [1,2]、[0,1],K 两行为 [1,0]、[2,1],则查询第一行与两把键的点积分别是一和四,第二行为零和一。正确 QK转置得到 [[1,4],[0,1]];漏掉转置却得到 [[5,2],[2,1]]。两者都是二乘二,单靠形状断言发现不了。主算例选择对角矩阵方便手算,却会掩盖某些转置错误,因此独立检查必须增加这种非对称输入。

二、缩放来自哪些假设

用一个明确的随机模型解释缩放动机:假设查询和键每个坐标均值零、方差一,查询与键相互独立,不同坐标对也相互独立。对一项乘积 qakaq_ak_a,均值为零,二阶矩为 E(qa2)E(ka2)=1\mathbb E(q_a^2)\mathbb E(k_a^2)=1,所以方差为一。不同坐标项的协方差为零,dd 项相加的方差为 dd。除以 d\sqrt d 后方差回到一,这就是常用尺度的一个计算依据。

这些独立和方差条件不是训练网络始终满足的定律。例如查询与键来自相同输入,甚至二者相等时,点积包含平方项,均值不再是上面假设的零。缩放仍是我们固定的架构约定,但“方差等于一”的论证只能在声明的随机模型里精确成立。它不保证 softmax 永不集中,也不保证训练不会发散;初始分布、归一化和学习率还会改变数值行为。

算例一:两位置、二维值的完整前向

取 XX 为二阶单位矩阵,d=r=2d=r=2。设 a=2ln⁡3a=\sqrt2\ln3,WQ=aIW_Q=aI,WK=IW_K=I,WV=diag⁡(2,4)W_V=\operatorname{diag}(2,4)。因为 XX 是单位矩阵,Q=aIQ=aI,K=IK=I,VV 的两行为 [2,0][2,0]、[0,4][0,4]。QKTQK^{\mathsf T} 仍为 aIaI,再除以 2\sqrt2,SS 两行为 [ln⁡3,0][\ln3,0] 与 [0,ln⁡3][0,\ln3]。单位矩阵是刻意选择的手算输入,不是实际语言模型必须采用的嵌入。

第一行指数为 [3,1],总和四,权重 [3/4,1/4];第二行为 [1/4,3/4]。读取第一行时,四分之三乘 [2,0],加四分之一乘 [0,4],得 [3/2,1];第二行得 [1/2,3]。选择标量探针 J=O第一行第一维+2×O第二行第二维,得到 J=3/2+6=15/2。探针的作用是让后续反向有一个明确目标,不把这个人为线性目标当成语言建模损失。

A=[3/41/41/43/4],O=[3/211/23],G=∂J∂O=[1002]A=\begin{bmatrix}3/4&1/4\\1/4&3/4\end{bmatrix},\quad O=\begin{bmatrix}3/2&1\\1/2&3\end{bmatrix},\quad G=\frac{\partial J}{\partial O}=\begin{bmatrix}1&0\\0&2\end{bmatrix}
G 是上游梯度:每个输出坐标对探针损失的导数。

前向有两种不同检查。先逐行核对 A 的非负与行和,确认归一化方向。再用凸组合边界:由于每行权重非负且和为一,O 第一维必须在两个值向量第一维零和二之间,第二维在零和四之间。输出 [1.5,1] 和 [0.5,3] 都满足。这个坐标范围检查不同于重新相乘,能揭示漏归一化或把分数直接当权重的错误。

凸组合的边界只约束被读取的值及这一层读取输出。值本身已经由输入经过 W_V 投影,不一定与原始字符向量同方向;下一章的输出投影又会再次线性变换。因此不能把“某输入位置权重较大”直接解释成它对最终预测具有相同比例的因果贡献。要检验影响,可以固定其他输入后做明确扰动,并追踪所有后续分支;注意力权重可作为中间观察,却不能跳过完整计算链。

三、从 AV 开始,一步一步反向

用 dVdV、dAdA 等简写表示 JJ 对相应矩阵的梯度,并不表示时间微分。先看 Oiℓ=∑jAijVjℓO_{i\ell}=\sum_jA_{ij}V_{j\ell}。固定一个值坐标 VjℓV_{j\ell},它会被所有查询 ii 读取,故 dVjℓ=∑iAijGiℓdV_{j\ell}=\sum_iA_{ij}G_{i\ell}。固定权重 AijA_{ij},它作用于该值向量的所有维度,故 dAij=∑ℓGiℓVjℓdA_{ij}=\sum_\ell G_{i\ell}V_{j\ell}。写成矩阵就是 dV=ATGdV=A^{\mathsf T}G,dA=GVTdA=GV^{\mathsf T}。每个求和对应计算图中所有经过该变量的路径。

dV=ATG=[3/41/21/43/2],dA=GVT=[2008]\mathrm dV=A^{\mathsf T}G=\begin{bmatrix}3/4&1/2\\1/4&3/2\end{bmatrix},\quad \mathrm dA=GV^{\mathsf T}=\begin{bmatrix}2&0\\0&8\end{bmatrix}
这些导数与 G 的定义共同固定了反向起点。

接下来不能只对 softmax 的分子求导。对同一行省略查询索引,Aj=eSj/ZA_j=e^{S_j}/Z,Z=∑keSkZ=\sum_ke^{S_k}。固定 SkS_k,用商法则,分子项导数为 δjkeSj\delta_{jk}e^{S_j},分母导数为 eSke^{S_k}。于是导数为 [δjkeSjZ−eSjeSk]/Z2[\delta_{jk}e^{S_j}Z-e^{S_j}e^{S_k}]/Z^2,整理成 Aj(δjk−Ak)A_j(\delta_{jk}-A_k)。δjk\delta_{jk} 在两索引相同时为一,否则为零。这张所有输出对所有输入的导数表叫雅可比矩阵。

∂Aj∂Sk=Aj(δjk−Ak),dSik=Aik(dAik−∑jAijdAij)\frac{\partial A_j}{\partial S_k}=A_j(\delta_{jk}-A_k),\qquad \mathrm dS_{ik}=A_{ik}\left(\mathrm dA_{ik}-\sum_j A_{ij}\mathrm dA_{ij}\right)
先把同一行所有输出路径的贡献相加,再得到右侧的向量—雅可比乘积。

把数代入:第一行的加权梯度均值为 (3/4)×2+(1/4)×0=3/2,所以 dS 第一行是 [(3/4)(2−3/2),(1/4)(0−3/2)]=[3/8,−3/8]。第二行均值为 (1/4)×0+(3/4)×8=6,所以第二行为 [−3/2,3/2]。每行梯度和为零,符合整行分数同加常量不改权重的平移不变性。只保留 A⊙dA 会漏掉分母路径,行和通常不再为零。

再展开分数 Sij=∑aQiaKja/dS_{ij}=\sum_aQ_{ia}K_{ja}/\sqrt d。固定 QiaQ_{ia},对所有 jj 求和得到 dQia=∑jdSijKja/ddQ_{ia}=\sum_jdS_{ij}K_{ja}/\sqrt d;固定 KjaK_{ja},对所有查询 ii 求和得到 dKja=∑idSijQia/ddK_{ja}=\sum_idS_{ij}Q_{ia}/\sqrt d。因此 dQ=dS K/ddQ=dS\,K/\sqrt d,dK=dSTQ/ddK=dS^{\mathsf T}Q/\sqrt d。本例 K=IK=I,故 dQ=dS/2dQ=dS/\sqrt2;Q=2ln⁡3 IQ=\sqrt2\ln3\,I,故 dK=ln⁡3 dSTdK=\ln3\,dS^{\mathsf T}。数值程序将对全部坐标做差分,而不是只抽最容易的一项。

最后回到投影。根据第二章的线性链式关系,dWQ=XTdQdW_Q=X^{\mathsf T}dQ,其余两条对应得到 dWKdW_K 与 dWVdW_V。同一个 XX 分别产生三条分支,故 dX=dQWQT+dKWKT+dVWVTdX=dQW_Q^{\mathsf T}+dKW_K^{\mathsf T}+dVW_V^{\mathsf T}。它们必须相加,不能以最后计算的值支路覆盖前面两条。此时损失已通过注意力回到输入向量;只有以后把这些梯度与真实交叉熵、所有层和优化器接起来,才构成完整训练。

局部反向中把 Q、K、V 分别扰动,并不是假设它们在统计上相互独立。微积分允许先研究函数在三个自变量坐标上的变化,再通过链式法则接回共同输入。统计独立是缩放动机中的随机模型条件,坐标偏导则是计算图分解,两者不是同一个概念。若差分检查时本想只改 Q,却由 X 同时重算三项,再与局部 dQ 比较,就会比较两个不同函数,自然得不到一致结果。

算例二:相同键让查询失去区分能力

保留查询和值,只把两行键都改成 [1,1]。对任何固定查询,它与两行相同键的点积相等,所以 softmax 权重必为 [1/2,1/2],两行输出均为 [1,2],探针 J=1+2×2=5。查询怎样变化都无法区分这两个键,因此在这个点附近仅改变 Q,输出不变。这个退化例子计算的是匹配机制的限制,不能以“模型一定会自己学到不同键”跳过。

反向也能核对上述结论。dA 仍为 [[2,0],[0,8]],均值分别为一和四,因此 dS 两行为 [1/2,−1/2]、[−2,2]。每行与两行相同 K 相乘都抵消,dQ 全零。但 dK 未必为零,因为分别扰动某一行键会打破相同条件。零查询梯度和非零键梯度并不矛盾:它们对应不同的独立扰动。把所有梯度都设零反而是错误的。

也可以在二位置特例里独立验证雅可比。设一行权重为 [a,1−a],上游权重梯度为 [u,v]。第一个分数导数应为 a(1−a)(u−v),第二项恰为它的相反数。两项权重的竞争来自共同分母:提高第一个分数不仅提高第一权重,也降低第二权重。当 u=v 时,总读取目标对两者的分配不敏感,两项导数全零。若只看 softmax 分子,会错误地产生非零更新。

四、独立有限差分与故障对照

中心差分在冻结其他输入时,分别将一个坐标加减 h,用损失差除以 2h。程序把 Q、K、V 都当作可独立扰动的实数矩阵,检验刚才推导的局部函数;再将三个投影接回共同的 X,另外检查 dX,检测遗漏分支。采用双精度浮点和两个不同步长,误差门槛事先固定。差分仅提供本次数值证据,解析推导仍承担一般公式的理由。

独立运行只用标准库,检查全部 Q/K/V 坐标、共同输入 X 的反向累加与相同键退化。
import math
import copy

def transpose(A): return [list(row) for row in zip(*A)]
def mm(A,B):
    return [[sum(a*b for a,b in zip(row,col)) for col in zip(*B)] for row in A]
def scaled(A,c): return [[c*x for x in row] for row in A]
def softmax(z):
    m=max(z); e=[math.exp(v-m) for v in z]
    return [v/sum(e) for v in e]
def forward(Q,K,V):
    S=scaled(mm(Q,transpose(K)),1/math.sqrt(len(Q[0])))
    A=[softmax(row) for row in S]
    return mm(A,V),A
Q_test=[[1.,2.],[0.,1.]]; K_test=[[1.,0.],[2.,1.]]
assert mm(Q_test,transpose(K_test))==[[1.,4.],[0.,1.]]
assert mm(Q_test,K_test)!=mm(Q_test,transpose(K_test))
G=[[1.,0.],[0.,2.]]
def objective(Q,K,V):
    O,_=forward(Q,K,V)
    return sum(x*g for row,gr in zip(O,G) for x,g in zip(row,gr))
def backward(Q,K,V):
    O,A=forward(Q,K,V)
    dV=mm(transpose(A),G); dA=mm(G,transpose(V)); dS=[]
    for i,row in enumerate(A):
        mean=sum(a*g for a,g in zip(row,dA[i]))
        dS.append([A[i][j]*(dA[i][j]-mean) for j in range(len(row))])
    assert all(abs(sum(row))<1e-12 for row in dS)
    dQ=scaled(mm(dS,K),1/math.sqrt(len(Q[0])))
    dK=scaled(mm(transpose(dS),Q),1/math.sqrt(len(Q[0])))
    return dQ,dK,dV

a=math.sqrt(2)*math.log(3)
Q=[[a,0.],[0.,a]]; K=[[1.,0.],[0.,1.]]; V=[[2.,0.],[0.,4.]]
O,A=forward(Q,K,V)
assert max(abs(A[i][j]-[[.75,.25],[.25,.75]][i][j]) for i in range(2) for j in range(2))<1e-12
assert abs(objective(Q,K,V)-7.5)<1e-12
analytic=backward(Q,K,V); fd_errors=[]
for h in (1e-5,1e-6):
    for field in range(3):
        error=0.
        for i in range(2):
            for j in range(2):
                plus=copy.deepcopy([Q,K,V]); minus=copy.deepcopy([Q,K,V])
                plus[field][i][j]+=h; minus[field][i][j]-=h
                numeric=(objective(*plus)-objective(*minus))/(2*h)
                error=max(error,abs(numeric-analytic[field][i][j]))
        fd_errors.append(error)
assert max(fd_errors)<1e-7

X=[[1.,0.],[0.,1.]]; Wq=copy.deepcopy(Q); Wk=copy.deepcopy(K); Wv=copy.deepcopy(V)
def projected_loss(x): return objective(mm(x,Wq),mm(x,Wk),mm(x,Wv))
dQ,dK,dV=analytic
branches=[mm(dQ,transpose(Wq)),mm(dK,transpose(Wk)),mm(dV,transpose(Wv))]
dX=[[sum(branch[i][j] for branch in branches) for j in range(2)] for i in range(2)]
x_error=0.; h=1e-6
for i in range(2):
    for j in range(2):
        plus=copy.deepcopy(X); minus=copy.deepcopy(X)
        plus[i][j]+=h; minus[i][j]-=h
        numeric=(projected_loss(plus)-projected_loss(minus))/(2*h)
        x_error=max(x_error,abs(numeric-dX[i][j]))
assert x_error<1e-7
same_keys=[[1.,1.],[1.,1.]]
O_same,A_same=forward(Q,same_keys,V)
dQ_same,_,_=backward(Q,same_keys,V)
assert all(abs(x)<1e-12 for row in dQ_same for x in row)
assert O_same==[[1.,2.],[1.,2.]]
print("attention_weights",A)
print("output",O,"probe_loss",objective(Q,K,V))
print("dQ",dQ,"dK",dK,"dV",dV)
print("QKV_finite_difference_max_error",max(fd_errors))
print("shared_X_finite_difference_max_error",x_error)
print("same_key_output",O_same,"same_key_dQ",dQ_same)
print("all_attention_checks_passed")

在以标量乘加计费的普通稠密实现中,QK转置约需 T²d 量级运算,AV 约需 T²r,存储完整权重需 T² 个数。这是明确计算模型下的量级,不是某台设备的耗时预测;投影本身还要另外计费。小参考函数刻意保存中间矩阵以便检查,日后换加速实现时,可复用同一数值、梯度和因果测试,而不能因速度更快就省略等价验证。

探针给出的上游梯度在本例中非常稀疏,是为了让每步乘法可复算。真实语言模型里,词表交叉熵经过输出矩阵、归一化和其他层后会形成另一张上游梯度,注意力的局部反向公式不因此变化。必须从真实损失计算这张梯度,不能把本章固定 G 塞进模型训练。局部算子的可复用性来自输入输出与导数定义一致,不是来自人为探针恰好能让目标下降。

差分输入还应避开数值上几乎完全饱和的权重。如果某个指数已经下溢,微小扰动后的输出可能在机器上完全不变,解析与数值都接近零,看似吻合却很难检出错误。先用本章有明显混合权重的例子验证,再另设极端分数检查稳定性。一个检查针对导数链,一个针对有限精度边界,不能因为前者通过就省略后者。

五、递进练习与解答

TRY IT YOURSELF

练习一|改变被读取的内容

沿用算例一,保持 Q 与 K,只把第二行值从 [0,4] 改成 [0,0]。逐项计算 A、两行 O 和探针 J。解释为什么权重不变并不能说明输出不变。

提示 1
A由哪些变量决定,O又额外依赖哪个变量?先把这两步分开。
提示 2
保留原A,对新V逐行加权;最后再将新O代入J,不能由权重未变直接跳到J未变。
对照参考解答
匹配分数只由 Q、K 决定,所以 S 和 A 都不变,A 仍为 [[3/4,1/4],[1/4,3/4]]。第一行读取 (3/4)[2,0]+(1/4)[0,0]=[3/2,0];第二行读取 (1/4)[2,0]+(3/4)[0,0]=[1/2,0]。因此 J=3/2+2×0=3/2。权重回答各位置怎样分配读取比例,V 决定实际内容;同一权重图可以对应不同输出。O 也不是下一 token 概率,尚需后面的网络与词表投影。

TRY IT YOURSELF

练习二|找出漏掉 softmax 分母的反向

某实现把算例一的分数梯度写成 A⊙dA。计算其第一行,比较正确结果,并用两种不依赖“代码看起来对”的方法拒绝它。

提示 1
softmax整行同时平移时概率不变,这对该行分数梯度之和有何要求?
提示 2
先算错误行向量的和,再沿全1方向做中心差分;比较它与梯度在该方向的点积。
对照参考解答
错误第一行为 [(3/4)×2,(1/4)×0]=[3/2,0]。正确实现先算行均值 3/2,再得到 [3/8,−3/8]。第一种拒绝方法是平移不变性:错误行和为 3/2,不是零,说明整行同加常量时会产生不应存在的方向导数。第二种是在原分数上沿 [1,1] 做正负相同扰动,重新计算 softmax 和探针,两次输出不变,差分方向导数接近零。错误梯度与 [1,1] 点积却为 3/2,因此无法同时成立。

TRY IT YOURSELF

练习三|从局部梯度迁移到共同输入

若 Q=XW_Q、K=XW_K、V=XW_V,有人仅用 dV W_V转置作为 dX。解释缺少哪些路径;设计一个不依赖自动求导库、能够检出错误的数值实验。相同键时 dQ 为零,是否允许在所有样本上删掉 Q 分支?

提示 1
同一X分别进入Q、K、V,按依赖图列出所有返回X的路径。
提示 2
扰动X一个坐标后重算三路与J,而不是只重算V;再改动相同键,检验特例中的零梯度能否推广。
对照参考解答
X 同时影响三项,完整 dX 必须加 dQ W_Q转置、dK W_K转置和 dV W_V转置。固定三个权重,对 X 的一个坐标分别加减小 h,每次从 X 重算全部 Q/K/V、权重与 J;中心差分同时包含三条路径。将其与完整解析 dX 对比,再与只保留 V 的结果对比。算例一是非退化输入,能发现遗漏。相同键只让那个特定状态下的查询路径抵消,不能证明一般输入的 Q 梯度恒零,因此不能据此删除模型分支;还可故意稍微改一行键,观察查询梯度重新出现。

BUILD THE MECHANISM

写出因果注意力

实现causal_attention;从显式点积、缩放、可见域和softmax得到值聚合。 下载本课完整项目,在项目根目录运行;只编辑 learner/starter.py,已有构造框架由项目提供。

输入与输出 本章只处理无缓存的Q/K/V=[B,H,T,D],query_offset=0;返回[B,H,T,D]聚合及[B,H,T,T]概率。
逐行和为1,未来key权重为0;已有dropout接口可复用,不要求历史KV。

在课程项目目录运行待完成的学习任务
# causal_attention
def causal_attention(query, key, value, query_offset=0, dropout=None):
    raise NotImplementedError('attention')
提示 1

先列第i个query能看到的key位置0到i。

提示 2

缩放分母为sqrt(D),softmax沿key轴;本章query_offset恒为0。

提示 3

用合法分数相等时V的前缀均值,再改未来V检查不泄漏。

独立检查 .venv/bin/python -m learner.check --stage attention --output runs/learner-attention.json

迁移检验 改为T=5、D=3、多头,手算两个前缀输出并比较;分块与非零query_offset留到第10章。

参考实现与解释
在课程项目目录运行参考实现
# 第3章仅实现无缓存注意力;第10章在同一函数增加offset。
def causal_attention(query,key,value,query_offset=0,dropout=None):
    if query_offset != 0:
        raise NotImplementedError('第10章再处理历史位置')
    scores = query @ key.transpose(-2,-1) / math.sqrt(query.shape[-1])
    future = torch.arange(key.shape[-2],device=key.device)[None,:] > torch.arange(query.shape[-2],device=query.device)[:,None]
    probabilities = torch.softmax(scores.masked_fill(future,float('-inf')),dim=-1)
    weights = probabilities if dropout is None else dropout(probabilities)
    return weights @ value, probabilities

本章项目增量与下一章依赖

现在具备单头注意力的完整前向、解析反向和独立差分参考,包含三个投影共享输入时的梯度累加。本章所有位置互相可见,所以不能用于宣称下一 token 模型已经因果。第四章将在相同计算链中引入因果与填充许可、多头拆合和输出投影,以及主项目采用的可学习绝对位置,检查整个前缀接口。后面的全参数训练会使用这些实际投影,不能把它们冻结后仍称完整训练。

CHECK YOUR UNDERSTANDING

检验理解

先作答,再决定是否展开解析。选择题用于发现理解缺口;独立实现与迁移任务仍需另行检验。

1. 单头注意力中,一行 A 归一化的是哪个集合?

查看解析它为当前查询分配读取位置的权重,与下一 token 类别概率不同。
2. S 的一行同加常量,正确 dS 的行和应为?

查看解析softmax 对公共平移不变,损失沿全一方向的导数为零。
3. Q、K、V 都来自同一 X 时,dX 怎样形成?

查看解析同一变量影响多条下游路径,链式法则要求累加所有贡献。
4. 两行键相同且没有遮罩偏置时,固定查询的权重如何分配?

查看解析查询与相同键的分数相同,softmax 在两个位置均匀分配;V 不参与匹配分数。

自行标记用于记录学习进度,不代表通过项目验收。