恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Attention is all you need?从Encoder到Decoder拆解Transformer的Self-Attention机制
首页
资讯中心
/
Attention is all you need?从Encoder到Decoder拆解Transformer的Self-Attention机制
Attention is all you need?从Encoder到Decoder拆解Transformer的Self-Attention机制
发布时间:2026/10/2 15:20:34
1. 从 Encoder 到 DecoderSelf-Attention 到底在算什么如果你第一次翻开《Attention Is All You Need》这篇论文大概率会被那张经典的 Transformer 结构图劝退左边一摞 Encoder右边一摞 Decoder中间还插着箭头。但真正撑起整个架构的其实只有一个核心动作——Self-Attention自注意力。它解决的问题很朴素一句话里每个词怎么知道该重点看哪些其他词传统 RNN 是按顺序一个词一个词往后传长距离依赖容易丢CNN 要靠堆叠卷积核扩大感受野效率不高。Self-Attention 的思路是让每个位置直接和序列里所有位置算一次相关性一步到位建立全局依赖。这也是论文标题敢叫 Attention is all you need 的底气。这篇面向想真正吃透注意力机制的开发者我会从 Encoder 和 Decoder 两个视角把 Self-Attention 的计算流程拆开配上可复现的注意力权重计算代码和逐层维度验证步骤。同时为了做对照实验我会用 TaoToken 统一 Key/API 通道调用模型把同一段文本喂进去观察注意力行为避免在多个平台之间来回切换 Key。适合谁看写过 PyTorch、知道张量维度概念、但被 Q/K/V 和 mask 绕晕的同学以及想自己动手验证注意力权重、而不是只停留在公式层面的工程师。读完之后你应该能独立写出一个带维度打印的 Self-Attention 模块并说清楚 Encoder 和 Decoder 里那两层注意力的区别到底在哪。2. TaoToken 前置准备统一 Key 与 API 通道在动手写代码之前先把调用通道理顺。做注意力对照实验时我经常需要把同一段输入分别送给不同模型观察它们对长距离依赖的处理差异。如果每个模型都要单独申请 Key、单独记 Base URL实验还没开始人已经累了。TaoToken 的价值就在这里一个 Key、一个 API 入口覆盖多种模型切换模型只改一个 model 字段。先注册并拿到 Key。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 完成账号注册后进入控制台。控制台地址是 https://taotoken.net/console 在左侧找到 API Keys 菜单新建一个 Key 并复制保存。注意 Key 只在创建时完整显示一次丢了只能重建。拿到 Key 之后你需要记住两个地址。API 基础地址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 OpenAI 兼容接口的 base_url 使用。文档入口在 https://taotoken.net/doc 里面有各语言 SDK 的接入示例遇到字段不确定时先查文档比猜快。这里有个容易踩的坑很多人把 base_url 写成 https://taotoken.net/api/v1 或者漏掉 /api结果请求直接 404。正确做法是 base_url 填 https://taotoken.net/api SDK 内部会自动拼接 /v1/chat/completions 这类路径。如果你用的是 OpenAI 官方 Python SDK代码里这样写from openai import OpenAI client OpenAI( api_key你的TaoToken Key, base_urlhttps://taotoken.net/api ) resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 用一句话解释自注意力}] ) print(resp.choices[0].message.content)模型 ID 怎么填在控制台的模型列表里能看到当前可用的模型名直接复制即可。做注意力对照实验时我一般会选两到三个不同规模的模型比如一个小模型和一个大模型观察它们对同一段长文本的注意力分布差异。切换时只改 model 参数Key 和 base_url 都不动这就是统一通道省下来的时间。如果你更习惯用命令行工具做长期编码或 Agent 任务可以了解 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它适合把模型能力接进日常开发流而不是每次手动发请求。对于本篇的注意力实验用上面的 Python SDK 就够了。3. 可复制配置Self-Attention 权重计算与维度验证这一节是全文的技术核心。我会先给出一个最小可运行的 Self-Attention 实现然后逐行打印维度让你亲眼看到 Q、K、V 是怎么从输入变出来的。所有代码基于 PyTorch直接复制就能跑。先明确输入约定。假设 batch_size2序列长度 seq_len4模型维度 d_model8。输入张量 x 的形状是 [2, 4, 8]。Self-Attention 要做的第一件事是用三个线性层把 x 分别投影成 Q、K、Vimport torch import torch.nn as nn import math class SelfAttention(nn.Module): def __init__(self, d_model, n_heads): super().__init__() assert d_model % n_heads 0, d_model 必须能被 n_heads 整除 self.d_model d_model self.n_heads n_heads self.d_k d_model // n_heads self.w_q nn.Linear(d_model, d_model) self.w_k nn.Linear(d_model, d_model) self.w_v nn.Linear(d_model, d_model) self.w_o nn.Linear(d_model, d_model) def forward(self, x, maskNone): batch_size, seq_len, _ x.shape print(f输入 x 形状: {x.shape}) Q self.w_q(x) K self.w_k(x) V self.w_v(x) print(fQ/K/V 形状: {Q.shape}, {K.shape}, {V.shape}) # 拆成多头: [B, L, d_model] - [B, n_heads, L, d_k] Q Q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2) K K.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2) V V.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2) print(f多头拆分后 Q 形状: {Q.shape}) # 缩放点积注意力 scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) print(f注意力分数 scores 形状: {scores.shape}) if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) attn torch.softmax(scores, dim-1) print(f注意力权重 attn 形状: {attn.shape}) out torch.matmul(attn, V) print(f加权输出 out 形状: {out.shape}) # 合并多头 out out.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model) out self.w_o(out) print(f最终输出形状: {out.shape}) return out, attn跑一下这段代码你会看到维度变化链条输入 [2,4,8] → Q/K/V [2,4,8] → 多头拆分 [2,2,4,4] → scores [2,2,4,4] → attn [2,2,4,4] → 输出 [2,4,8]。每一步都打印出来比盯着论文公式猜要直观得多。关键点在于 scores 的计算Q 乘以 K 的转置得到的是每个位置对其他所有位置的相关性分数形状是 [B, n_heads, L, L]。除以 sqrt(d_k) 是论文里的缩放操作防止 d_k 变大时点积结果过大导致 softmax 梯度消失。你可以做个实验把 math.sqrt(self.d_k) 去掉观察 attn 的分布是不是变得更尖锐、接近 one-hot这就是梯度消失的直观表现。mask 的处理是 Encoder 和 Decoder 的分水岭。Encoder 的 Self-Attention 不需要 mask每个位置都能看到全序列。Decoder 的 Masked Self-Attention 需要一个下三角矩阵把未来位置的分数置为 -infsoftmax 之后这些位置权重为 0。生成 mask 的代码如下def causal_mask(seq_len): mask torch.tril(torch.ones(seq_len, seq_len)).bool() return mask # [L, L], 下三角为 True把它传进 forward 的 mask 参数注意形状要能广播到 [B, n_heads, L, L]。实测下来最容易出错的就是 mask 的 True/False 语义搞反导致该屏蔽的位置反而被保留。建议先打印 mask 矩阵确认下三角是 True。如果你想把这段代码接进真实模型做对照可以用 TaoToken 的模型对话入口 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 快速验证输入输出再回到本地代码调维度。两者配合调试效率会高很多。4. 验证请求从注意力权重到模型对照实验代码写完了怎么确认它真的算对了光看形状对还不够得看数值行为是否符合预期。这一节我给出三个验证步骤从单元测试到真实模型对照层层递进。第一步验证注意力权重每行和为 1。softmax 之后attn 在最后一维的和应该恒等于 1。加一行断言row_sum attn.sum(dim-1) print(每行权重和:, row_sum) assert torch.allclose(row_sum, torch.ones_like(row_sum), atol1e-5)如果这步失败说明 softmax 的 dim 写错了或者 mask 里混进了 nan。第二步验证 mask 生效。构造一个 seq_len4 的输入用 causal_mask 跑一遍打印第一个头的 attn 矩阵。你应该看到上三角全是 0下三角有值。位置 0 只能看到自己位置 3 能看到 0 到 3。这个下三角结构就是 Decoder 自回归生成的数学保证。第三步做真实模型对照。用第 2 节的 TaoToken 客户端把同一段文本分别发给两个不同模型让它们完成一个需要长距离依赖的任务比如把这句话里指代它的词找出来。虽然我们拿不到模型的内部注意力权重但可以通过输出质量间接判断模型对长距离关系的处理能力。代码示例text 小猫追着球跑它玩得很开心。请问它指代什么 for model_name in [gpt-4o-mini, gpt-4o]: resp client.chat.completions.create( modelmodel_name, messages[{role: user, content: text}] ) print(model_name, -, resp.choices[0].message.content)实测下来不同模型对这类指代消解任务的表现差异恰好反映了它们在注意力分配上的不同策略。小模型可能更依赖局部邻近词大模型能捕捉更远的依赖。这就是为什么我说统一 Key 通道很重要——你可以在同一个脚本里循环切换模型把对照实验做成批量任务。如果你在验证过程中遇到请求失败先检查三件事Key 是否复制完整、base_url 是否为 https://taotoken.net/api 、model 字段是否在控制台模型列表里。这三项确认无误绝大多数问题都能解决。需要重新生成 Key 时去 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 操作即可。5. 本篇常见错误排查401、维度不匹配与 mask 陷阱即使代码看起来没问题跑起来还是可能报错。这一节我把注意力实验里最常遇到的几个错误列出来对照真实报错信息给出排查路径。第一个高频错误是 401 Unauthorized。报错信息通常是Error code: 401 - {error: {message: Invalid API key}}。原因无非三种Key 复制时带了空格、Key 已过期或被删除、base_url 写错导致请求打到了别的服务。排查顺序是先打印 api_key 的前后各 4 位确认没截断再确认 base_url 是 https://taotoken.net/api 最后去控制台看 Key 状态。如果用的是环境变量注意 shell 里 export 的变量名和代码里读取的名字要一致。第二个错误是维度不匹配典型报错RuntimeError: matmul: Input operand 1 has a mismatch in its core dimension。这几乎总是 Q 和 K 的转置维度对不上。检查你的 Q 形状是 [B, n_heads, L, d_k]K.transpose(-2,-1) 之后应该是 [B, n_heads, d_k, L]两者相乘得到 [B, n_heads, L, L]。如果 d_model 不能被 n_heads 整除view 操作会直接报错所以init里的 assert 别删。第三个错误是 mask 语义搞反。如果你用 masked_fill(mask 0, -inf)那 mask 里 0 代表要屏蔽的位置。但如果你用 torch.tril 生成的是 1/0 矩阵下三角是 1那 mask0 屏蔽的就是上三角逻辑正确。可如果你不小心用了 mask 1那就把该保留的位置全屏蔽了softmax 之后全是 nan。报错信息可能是RuntimeError: Function SoftmaxBackward returned nan values。解决办法很简单打印 mask 矩阵肉眼确认下三角是保留值。第四个错误和 OAuth 或本地代理有关。如果你在环境里配置过 HTTP_PROXY 之类的变量请求可能被转发到不可达的地址报错类似local proxy failed或连接超时。这时候检查环境变量把代理相关配置清掉再试。注意这里说的是清理本地环境变量不是让你去配置任何网络工具。第五个错误是读取响应时reading choices报错通常是 resp.choices 为空。原因可能是请求被限流、模型名写错、或者 messages 格式不对。先打印完整 resp 对象看 error 字段再对照文档检查参数。如果模型名不在可用列表里控制台会返回明确提示。把这几类错误记下来下次遇到直接对号入座能省下大量搜索时间。6. 语义一致 CTA把注意力实验接进你的工作流代码跑通、错误排查完接下来就是把它变成日常可用的工具。我自己的做法是本地保留一份带维度打印的 Self-Attention 模块作为教学和调试用真实任务则通过 TaoToken 统一通道调用模型两者用同一套输入做对照。如果你主要做模型能力验证和对照实验模型对话入口最顺手https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。它适合快速试输入、看输出不用写完整脚本。如果你要把模型接进长期编码或 Agent 流程Coding Plan 更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它解决的是每次手动发请求的重复劳动。需要管理多个 Key 或重建 Key 时API Keys 页面在这里https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。接入细节不确定就查文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后留一个实用技巧做注意力对照实验时把每次请求的 model、输入文本、输出结果写进一个 CSV跑完一批之后横向对比。这个习惯让我发现了很多单次请求看不出来的规律比如某些模型在长文本后半段注意力明显衰减。数据攒多了比任何教程都更能帮你理解注意力的真实行为。