资讯动态

Transformer核心机制图解:从QKV到多头注意力的直观理解

发布时间:2026/10/22 11:25:10 来源:尧图企业网站定制
Transformer刚火起来的那几年圈子里流传过一篇特别经典的图解文章很多人的收藏夹里都有它。我当时第一次看完感觉是“每个字都认识连起来就懵”隔了一段时间再翻突然就通了。后来带过不少新人发现大家卡住的点高度一致无非是 QKV 到底是什么、为什么要除根号 dk、多头注意力又是干嘛的。这篇就把我消化那篇图解之后的理解用大白话加具体数字重新讲一遍配上实操里容易踩的坑。整篇不用公式吓人核心就四个字看图、算数。1. 先搞懂Transformer到底想干什么1.1 老选手的麻烦一个字一个字往前传在Transformer出现之前处理序列问题的主力是循环神经网络那一类结构。你可以把它想象成一条传送带每个时刻只处理一个输入然后把手里的“状态”传给下一个时刻。比如翻译“我爱机器”它先看“我”更新一个状态再看“爱”结合刚才的状态再更新最后看“机器”把累积的信息用于预测输出。这个设计看起来很自然但有两个绕不开的麻烦。第一个是长距离依赖问题顺序一长最初的信息在传递过程中会被层层稀释就像一群人传话传到后排时早就走样了。第二个是并行度低当前时刻必须等前面所有时刻算完才能开始训练速度被锁死。GPU 再强也得老老实实地排队。所以业界一直想要一个更彻底的方案能不能让所有位置同时看全局而不是一个接一个地往后传Transformer 就是对这个问题的一次颠覆性回答。1.2 一句话理解Transformer核心Transformer 的核心可以压缩成一句大白话让输入序列里的每一个词同时和所有词做一次“关联度打分”然后用这些分数把所有词的信息加权汇总得到每个词的新表示。这个“关联度打分”的机制就是自注意力。它是Transformer 的心脏。后面的多头注意力、位置编码、残差连接全都是为了让这颗心脏跳得更稳。我用一个生活化的例子帮你记住它。假设你在一个全是陌生人的房间里想理解“我”这个词在当前句子里到底指谁、指什么不能只看它自己得环顾四周每一句话、每一个词看看谁和“我”关系近谁关系远再结合上下文重新理解“我”。自注意力干的就是这件事只不过它把“关系近”量化成了一个分数。这篇博文要做的就是把这个“环顾四周打分加权”的过程拆开揉碎顺带把输入输出怎么流动、训练和推理又有啥区别讲清楚。适合完全没接触过注意力机制的小白也适合看过理论但总感觉差一层窗户纸的读者。2. 宏观地图从输入到输出的完整流水线2.1 Encoder-Decoder 到底是啥Transformer 最初是为机器翻译设计的所以天然分成两半左边是编码器Encoder右边是解码器Decoder。你可以把编码器理解成一个“阅读理解”模块负责把输入句子读成一组富含语义的向量解码器是一个“写作”模块负责拿着这些向量一个一个词地往外写目标句子。完整结构大致是这样输入句子先经过词嵌入变成一个一个向量。再叠加上位置编码让每个词知道自己站在句子的第几个位置。然后进入编码器的一块Encoder Block里面有自注意力层和前馈网络层外面套着残差连接和层归一化。编码器的输出会作为解码器里“交叉注意力”的输入之一。解码器自己也有自注意力层但多了一个掩码防止它偷看未来的词。最后经过一个线性层加 Softmax输出下一个词的概率分布。初看很乱但记住一条主线输入先去编码器做特征提取输出端在解码器里一边盯着已生成的内容一边参考输入信息逐步预测下一个词。2.2 张量形状变化一张表看懂维度理解 Transformer 最容易被劝退的地方是维度。我建议你盯住三个数字batch size一批多少个句子sequence length一个句子多长d_model每个词用多少维向量表示比如一个 batch 是 32 句话每句话 10 个词词向量维度是 512那么输入张量就是 (32, 10, 512)。后面的操作其实都在变换最后这一维阶段张量形状变化干了什么词嵌入(32, 10, 512)把词 ID 映射成稠密向量位置编码(32, 10, 512)逐元素加到词向量上自注意力(32, 10, 512)每个位置融合全序列信息前馈网络(32, 10, 512) 先扩到 (32, 10, 2048) 再缩回逐位置的非线性变换编码器输出(32, 10, 512)作为解码器交叉注意力的 K 和 V解码器输出(32, 目标句长度, 512)预测目标序列的表示线性层Softmax(32, 目标句长度, 词表大小)输出每个位置下一个词的概率注意一个关键点自注意力从头到尾不改变序列长度和维度。它做的是特征融合而不是压缩或扩维。压缩和扩维是前馈网络和线性层负责的。2.3 为什么Encoder可以并行Decoder却不行这是很多初学者困惑的点。编码器里自注意力一次就能看到整个句子10个词之间互相打分理论上可以同时算所以 GPU 能一窝蜂地并行处理。解码器在训练时其实也可以部分并行——因为训练阶段你知道完整的目标句子是什么可以一次性把整句喂进去只是要用掩码遮住未来位置。但在推理阶段就不行了你只知道已经生成的词不知道下一个词是什么只能先生成第 1 个词再基于第 1 个词生成第 2 个再基于前两个生成第 3 个这就是自回归生成。打个比方编码器是站在山顶看完整幅地图解码器是蒙着眼睛猜下一步往哪走每走一步才多知道一点路况。3. 自注意力机制一场全员参与的打分游戏3.1 Query、Key、Value 到底是什么自注意力的核心是三个向量Query查询、Key键、Value值。很多人一看到 QKV 就头大其实拿检索来类比特别容易。你在百度搜索“Transformer教程”的时候Query 就是你输入搜索框的那几个字“Transformer教程”。Key 是互联网上每篇文章的标签、标题、摘要用来和 Query 做匹配。Value 是文章正文本身是真正有用的内容。搜索引擎拿到 Query 之后先和所有 Key 算一下相关度然后把相关度最高的几篇文章的正文Value取出来按相关度加权拼在一起还给你。Transformer 里的自注意力如出一辙一个词作为 Query去和序列里所有词的 Key 比对得到一堆分数再用这些分数去加权所有词的 Value。这里有个容易混淆的点同一个词它既当 Query也当 Key也当 Value。因为三个角色都是从这个词的原始向量通过不同的权重矩阵映射出来的。你可以理解成这个词有三张名片一张用于“询问别人”、一张用于“被询问”、一张用于“提供内容”。3.2 具体算一遍三个词的例子纸上谈兵没用直接拿一个极简句子“我 爱 AI”来算。假设每个词的原始向量是我[1, 0]爱[0, 1]AI[1, 1]为了得到 Q、K、V我们先设置三组权重矩阵。为了能口算我用最简单的矩阵Wq [[1, 0], [0, 1]] Wk [[1, 0], [0, 1]] Wv [[1, 1], [0, 1]]把每个词的向量分别乘上这三个矩阵得到“我”Q[1,0]K[1,0]V[1,1]“爱”Q[0,1]K[0,1]V[1,2]“AI”Q[1,1]K[1,1]V[2,3]现在计算“我”这个词和所有词的注意力分数。先算 Query 和每个 Key 的点积“我”和“我”1×1 0×0 1“我”和“爱”1×0 0×1 0“我”和“AI”1×1 0×1 1然后做 Softmax。Softmax 会把分数变成概率而且会放大差距。为了简单我们假设这三个数的 Softmax 结果是[0.45, 0.10, 0.45]。最后加权求和 Value0.45×[1,1] 0.10×[1,2] 0.45×[2,3] [0.450.100.90, 0.450.201.35] [1.45, 2.00]于是“我”的新表示变成了[1.45, 2.00]。仔细看这个新向量里混进了“爱”和“AI”的信息。这就是自注意力干的活每个词的新表示是全体词之前的信息按相关度加权混合后的结果。3.3 为什么要除以根号dk真正实现时点积算完后还要除以sqrt(d_k)其中 d_k 是 Key 向量的维度。这是很多教程一笔带过的地方但我觉得这是全篇最巧妙的设计之一。原因要从 Softmax 的脾气说起。Softmax 对输入数值的大小非常敏感如果输入分数都很大Softmax 之后概率分布会变得非常“尖锐”接近 one-hot也就是只挑一个词看其他都忽略。这样模型就丧失了“兼顾上下文”的能力梯度也容易消失。举个数假设 d_k512Key 和 Query 都是独立随机向量每个元素约在 0 附近那么点积的方差就大约是 512。想象你在投一个 512 面的骰子点数之和的波动范围自然比 2 面骰子大得多。点积结果一波动Softmax 输入就进入饱和区。除以根号 d_k本质上是把点积的方差重新拉回到 1 左右让 Softmax 的工作区间保持在温和地带。这就好比把音量先调低一点再进功放防止爆音失真。如果不用这个缩放训练初期会明显不稳表现为 loss 震荡、收敛变慢。我自己调试时试过去掉缩放模型虽然也能跑但要多花不少时间去哄它收敛所以这个细节千万别乱动。3.4 从向量到矩阵实际代码里怎么组织实际实现当然不是一个个词循环算而是把所有词的 Q、K、V 拼成矩阵一次性算完# 伪代码自注意力矩阵运算 # X: (seq_len, d_model) Q X Wq # (seq_len, d_k) K X Wk # (seq_len, d_k) V X Wv # (seq_len, d_v) scores Q K.T # (seq_len, seq_len) scores scores / sqrt(d_k) weights softmax(scores, dim-1) output weights V # (seq_len, d_v)如果你用 NumPy 对着上面那个三词例子复现会得到和我手算一致的结果。我建议初学者都手动写一遍这个小矩阵乘比看十遍文章都管用。其中scores矩阵的第 i 行第 j 列就是第 i 个词对第 j 个词的注意力强度。4. 多头注意力一副眼镜不够就戴多副4.1 为什么一个注意力不够用前面我们算了一轮注意力得到一组加权结果。问题在于一个注意力头只能表达一种“相关性理解”。但自然语言里的相关关系是多维的。比如“苹果”和“好吃”可能是口感上的相关和“公司”可能是商业上的相关和“手机”可能是产品线上的相关。如果只用一个头模型被迫把这几种相关混在一起就像一副眼镜没法同时满足近视、散光、变色三重需求。多头注意力提供了解决办法并行走多组 QKV 变换每组只关注一种关系最后再合并。你可以理解为组了多个“搜索小组”每个小组用不同的搜索引擎有的偏重语法关系有的偏重语义相近有的偏重指代关系。汇总时各小组投票从而得到更丰富的表示。4.2 多头的计算流程假设 d_model512头数 h8那么每个头分到的维度 d_k d_model / h 64。输入 X 分别乘上 8 组 Wq、Wk、Wv得到 8 组 Q、K、V。每组独立计算自注意力得到 8 个 (seq_len, 64) 的输出。把这 8 个输出在最后一维拼接起来得到 (seq_len, 512)。再乘一个输出权重矩阵 Wo把维度压回 512完成融合。用图来想原始向量被切成 8 个“窄通道”每个通道单独做一次信息交换最后又重新拼成宽通道。这有一点像乐队里不同乐器声部先单独练习再合到一起排练。这里有个初学常见误区多头并不是把每个词的向量切成 8 段然后各自处理。它是用 8 组不同的权重矩阵把原始向量映射到 8 个不同的子空间。你可以在 PyTorch 里观察每个头的输出会发现它们关注的东西确实很不一样。4.3 头数怎么定头数不是越大越好。头数增加会摊薄每个头的维度如果头数过多每个头分到的容量太小反而学不到有效模式。常见配置是 8 头、12 头、16 头。业界流行的几个大模型基本都在这个范围里。我见过有人把头数从 8 调到 32结果训练速度变慢效果不升反降。原因就是每头只有 16 维信息表达太局促。选头数时有个经验头数乘以每个头的维度大致等于 d_model。8×64512、12×64768、16×641024都是常见组合。5. 位置编码与残差连接让模型知道顺序和防止退化5.1 顺序感哪里来自注意力有个天生的毛病它看不出词的先后顺序。你让它处理“我打你”和“你打我”在纯注意力眼里“打”和两个词的距离几乎一样语义却天差地别。因为注意力只算两两相关和位置无关。所以 Transformer 必须人为给每个词的位置信息这就是位置编码。位置编码的向量和词嵌入向量维度相同直接相加模型才知道“这个向量代表的是第几个位置的词”。位置编码有两种常见做法一种是正弦余弦固定公式另一种是把位置编码当作可学习的参数让模型自己学。早期 Transformer 用前者现在很多大模型用后者或者更复杂的旋转位置编码。5.2 正弦位置编码的直觉正弦位置编码公式长这样PE(pos, 2i) sin(pos / 10000^(2i/d_model))PE(pos, 2i1) cos(pos / 10000^(2i/d_model))为什么用这个公式核心好处是它可以用三角函数的和差公式把任意两个位置之间的相对位置关系表示成当前两个位置编码的线性组合。也就是说模型只要学到一组线性变换就能“算出”相对位置。这比直接用整数 1、2、3 当位置信息要灵活得多。你不需要记住公式但要知道它的两个特点位置越靠后波形的频率就越低不同维度上的“步调”不一样。它不引入额外参数而且能处理比训练时更长的序列。不过在处理超长文本时经典正弦编码的效果也会下滑所以后来大家才研究各种新的位置编码方案。这属于进阶话题初学者先理解“为什么要加位置信息”最重要。5.3 残差连接给梯度留条高速路Transformer 里的每个子层自注意力、前馈网络都套了一层残差连接输出的不是子层计算结果而是“输入 子层结果”。也就是output LayerNorm(x Sublayer(x))这样做最直接的好处是万一某一层什么都没学到至少做个“恒等映射”把输入原样传下去不会比什么都不加更差。从梯度角度看反向传播时梯度多了一条近路可以跳过子层直接流回前面有效缓解深层网络梯度消失。很多初学者看结构图时只盯着注意力那一块忽略了旁边那条拐到后面的箭头。那条线恰恰是训练稳定的命根子。我试过去掉残差连接跑一个小模型结果层数一加深就出现 loss 不降、梯度几乎为零的问题加了残差马上就正常了。层归一化在这里还有个细节它放在残差相加之后和原始论文一致。后来的很多模型改成 Pre-LN也就是先归一化再进子层训练更稳。这个差异对结果有实际影响但不影响理解主流程知道有这回事即可。6. 解码器与训练推理从抄答案到闭关答题6.1 Masked自注意力不许偷看未来解码器里也有自注意力但多了一层“掩码”。为什么因为解码器的任务是逐词预测预测第 t 个词时只能看到第 1 到 t-1 个词不能看到 t 之后的内容。否则就是拿着答案做题训练出来没有任何实际生成能力。实现上很简单把注意力分数矩阵的上三角部分未来位置的分数设成一个极大的负数比如 -1e9这样 Softmax 之后这些位置的概率会趋近于 0相当于“未来的词虽然躺在输入里但注意力把眼睛蒙上了”。训练时你可以把完整的目标句子喂给解码器通过掩码实现并行。这一步和推理的区别很重要训练目标句子已知一次性并行输入但每个位置只能看到前面的词。推理目标句子未知一个一个词地生成生成的词同时充当后续词的输入。这就像考试辅导和正式考试的区别训练时题目和答案都在手里但要求你假装没看到后面的题推理时才是真正闭卷一道一道做。6.2 交叉注意力解码器怎么参考输入解码器除了自带的自注意力还有一个叫“交叉注意力”的模块。它的 Query 来自解码器自身的输出而 Key 和 Value 来自编码器的输出。意思很明确解码器在生成每一个词时都要去“原文里查资料”。这里就又回到搜索类比解码器当前已生成的内容是 Query编码器提炼出的原文信息是 Key 和 Value。解码器先生成“我”再查原文看下一个最可能接什么词生成“爱”再去查原文如此反复直到生成结束。整个解码器由多层这样的结构堆叠每一层都做一次“自身回顾 原文查阅”。层数越多对关系的捕捉理论上越细腻代价是计算量成倍增长。6.3 训练和推理的数据流差异这个差异是面试和大厂新人培训最爱问的点。我把关键区别列成一张表对比项训练阶段推理阶段输入完整的目标句子已经生成的词并行度高整句喂入低逐词生成损失计算每个位置都在预测只关心下一个词掩码需要 Mask天然只看已生成内容速度快可以大批量慢延迟随长度累积训练和推理如果共用一个模型你需要在代码里对 Mask 参数做区分。新手常见错误是推理时忘了关掉训练 Mask 逻辑导致生成效果异常或者在训练时没加 Mask让模型偷看了未来训练指标虚高实际生成时崩盘。7. 图解视角怎么把那张经典结构图变成自己的东西7.1 拆图四步法那篇经典图解之所以出名是因为它真的能用图让人懂。但很多人收藏完就吃灰。我的建议是别干看按四步走第一步从左往右读数据流。看一个向量从底部输入开始经过哪些方框每一步形状怎么变。不看含义只跟踪数据形状。第二步关注注意力那一块的三个箭头。找到 Q、K、V 三个分支分别从哪来、到哪去明白“MatMul”在算谁和谁的点积。第三步把 Softmax 前面的“Mask可选”圈出来思考它在什么时候生效什么时候不生效。第四步看每条“残差捷径”是从哪一步开始拐到输出的。把这一步做好你基本上能把结构图默画出来。7.2 用极简实现验证理解动手是验证理解最好的方式。下面这段代码是极简版单头自注意力用 NumPy 实现没有训练逻辑纯粹展现前向计算import numpy as np def softmax(x, axis-1): e np.exp(x - np.max(x, axisaxis, keepdimsTrue)) return e / e.sum(axisaxis, keepdimsTrue) def self_attention(X, Wq, Wk, Wv): Q X Wq K X Wk V X Wv d_k K.shape[-1] scores Q K.T / np.sqrt(d_k) weights softmax(scores) return weights V, weights # 模拟4个词词向量维度3d_k3 X np.random.randn(4, 3) Wq np.random.randn(3, 3) Wk np.random.randn(3, 3) Wv np.random.randn(3, 3) out, attn self_attention(X, Wq, Wk, Wv) print(注意力权重矩阵, attn.shape) # (4, 4) print(输出形状, out.shape) # (4, 3)拿到这份代码你可以在 jupyter 里改改序列长度、维度观察注意力矩阵每一行的权重分布。我当年就是这么“看透”自注意力的比任何课程都直观。7.3 推荐一条完整学习路径如果你真的想彻底搞懂 Transformer下面的路线可以照着走第 1 步把这篇博文里 QKV 手算例子自己算一遍。第 2 步用上面 NumPy 代码改成多头版本。第 3 步用现成框架把结构图里的模块搭一遍不看参考答案。第 4 步训练一个小翻译模型观察训练和推理的表现差异。第 5 步去读相关经典论文对照结构图逐段理解。完成前四步你已经超过一大堆只会调包的人。第五步是登堂入室的门槛能坚持到那里的基本就是真懂 Transformer 了。8. 常见问题与避坑指南8.1 Q、K、V的权重能不能共享理论上可以但实践中几乎没人这么做。如果共享就相当于每个词的查询、被查询、内容提供完全用同一套映射注意力会退化成“只看自身相似度”表达能力大幅缩水。这就是为什么原始实现里一定有三份不同矩阵。有个值得试验的变体是“共享 QK”但那是为了特定效率需求做的妥协不是常规操作。你遇到类似代码时先想想作者的动机别盲目照搬。8.2 为什么注意力分数要经过Softmax而不是直接用Softmax 能让权重非负且总和为 1这样输出就是所有 Value 的加权平均既稳定又易于解释。如果不做 Softmax直接用原始点积去加权很容易出现负权重或权重绝对值失控数值不稳定。顺带一提Softmax 的温度参数除以温度系数可以控制注意力分布“软硬”程度。温度越低分布越尖锐越高越平滑。这不是 Transformer 的最初设计但后来很多改进工作会用到。8.3 位置编码用固定还是可学习早期那篇经典实现用的是固定正弦编码稳定且不需要训练参数。后来随着模型规模增大不少人改用可学习的位置编码让模型根据训练数据自行调整位置表示。两者各有优势对新手来说不用纠结先学会“要加位置编码”这件事。如果你想追新可以去看旋转位置编码RoPE现在不少新一代模型用它来拓展长文本能力原理也是给词向量加“旋转角”比固定正弦编码更好地处理相对距离。8.4 初学者最容易踩的四个坑第一个坑以为自注意力改变了序列长度。没有它只改变每个位置的表示内容长度保持不变。第二个坑分不清 d_model 和 d_k。d_model 是整个词的向量维度d_k 是每个注意力头的维度。d_model 除以头数等于 d_k这个关系一定要记牢。第三个坑训练和推理用同一套代码逻辑。训练时用 Mask 和完整句子推理要求逐词生成两者切换时要特别小心。第四个坑忽略残差连接。有人画图或者搭模型时觉得残差“只是加了一下”其实有没有这条捷径训练稳定性和最终效果差别巨大。还有一个经验上的提醒如果你自己从零搭 Transformer千万别一上来就搞大模型、大批次。先用极小数据集比如几十条样本验证前向传播形状正确、loss 能下降再逐步扩大范围否则你会被各种维度不匹配的报错淹没。我见过太多人卡在形状报错上其实把每个张量的维度打印出来逐个对照本文那张表五分钟就能定位问题。Transformer 刚出来时很多人觉得它复杂得像天书。但等你把它拆成“搜索打分、加权汇总、多头并行、位置编码、残差保护”这几块会发现每一步都不难。个人体会是最难的不是理解某个模块而是把这些模块串成完整数据流时脑子里的那根线不能断。拿着结构图手里写一遍数字示例再跑一遍极简代码这比反复看视频有效得多。希望这篇帮你把那根线接上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑