这篇重点学什么
本篇重点:画出 Token 怎样变成注意力输出,说明每个位置能看到哪些上下文、为什么不能提前看到未来内容;按需选学,不要求训练模型。
开始读正文 ↓学过相近内容?查看哪些可以跳过
两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。
注意力
共同基础Q / K / V、缩放、掩码与 softmax 的基础直觉以 Agent 入门篇为主读,属于应用路线的选修模型背景。
另一条路线补充什么?
先核对矩阵各维的大小,再检查不同框架 API 如何解释掩码、整行屏蔽时怎样处理以及 dropout 的行为;用梯度实验检查被屏蔽信息是否仍影响计算。
读完这篇,你能做什么
能画出文本进入 Transformer 后的主要处理步骤,解释因果掩码限制哪些读取,以及注意力权重不能证明什么。
学习理由、准备要求与知识点
为什么学它
先理解输入上下文怎样影响各位置的表示,才能更清楚地组织输入、排查长文本问题;这一步不要求先训练模型。
先读这些内容
知识点清单
- 文本到 Token 标识
- Token 到向量表示
- 位置与顺序信息
- Q、K、V 的作用
- 注意力权重与混合
- 因果掩码
- 编码器与解码器
- 自回归生成与训练区别
运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。
01先弄懂它是什么
读一句话时,当前位置往往需要参考前后其他内容。语言模型先把文本转成 token ID,再查出向量;ID 只是编号,本身不表示意思有多接近。Transformer 用注意力让每个位置从允许读取的其他位置汇总信息。可以先抓住一个过程:当前查询决定要从哪些位置的值向量里,各取多少信息。
这个过程里的 Q、K、V 分别叫查询、键和值。可以把 Q 看成当前想找什么,K 用来比较各位置是否匹配,V 是实际要汇总的内容。这只是帮助理解各自用途,不表示每一维都有可读标签。真实网络通常用可学习的线性映射得到它们,还会配合多头、残差、归一化和前馈网络一起处理信息。
自回归模型生成当前位置时只能使用已经允许的上下文,因果掩码禁止注意未来位置。训练时可以并行计算许多位置的损失,但仍通过掩码防止答案泄漏;推理则逐步追加生成结果。编码器、解码器及交叉注意力的可见范围不同,不能把所有Transformer都画成同一种单向结构。
文中这几个词,先认一认
- Logit
- 还没有归一化的原始分数,可以是任意实数,此时不能直接当作概率。
- Softmax
- 把一组实数分数转换成非负、相加为一的权重,分数高的项通常得到更大权重。
- 因果掩码
- 规定当前位置不能读取未来位置,防止预测下一步时提前看见后面的内容。
02看看它怎么工作
计算当前位置的输出时,先把它的 Q 与每个允许读取位置的 K 做点积,得到匹配分数,再除以键向量维数的平方根。原论文用这个缩放来缓解高维点积幅度变大:分数差得过大时,Softmax 可能把权重推得接近零或一,梯度变得很小,这叫饱和。缩放有助于缓解问题,但不保证所有输入都稳定。接着只在允许读取的位置之间把分数变成总和为一的权重,再按这些权重汇总各位置的 V,得到当前行输出。
例如两个分数为零,权重各一半,值为二和六,输出为四;第二个分数升到ln3时,权重变为四分之一与四分之三,输出为五。若因果掩码只允许第一项,输出是二,与第二项内容无关。这个例子把分数、归一化和信息混合分开,便于逐步检查。
直接exp大分数会溢出。减去可见分数最大值后,最大指数为一,其他位不超过一;公共因子在分子分母中抵消,理论权重不变。本题对点积已经溢出的情况明确拒绝,不能声称减最大值会修复此前所有数值错误。Java只展示算术机制,不含训练、分词、位置编码或模型权重。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart LR
Q["当前 Q"] --> S["与可见 K 点积并缩放"]
K["可见 K 与因果范围"] --> S
S --> M["减最大值并 Softmax"]
M --> W["非负归一权重"]
V["对应 V"] --> O["加权求和"]
W --> O按顺序看,每一步做了什么
- 给定维度一致的Q、K、V和当前可见位置数。
- 计算可见位置的缩放点积,拒绝非有限中间结果。
- 减去最大分数后做Softmax,得到归一权重。
- 按权重混合V,检查输出维度和有限性。
- 对照掩码、均匀权重及偏斜权重的手算结果。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · Math.exp
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。
double max = Arrays.stream(scores).max().orElseThrow();
double sum = 0;
for (int i = 0; i < scores.length; i++) {
weights[i] = Math.exp(scores[i] - max); // 平移分数,避免指数上溢。
sum += weights[i];
}
for (int i = 0; i < weights.length; i++) weights[i] /= sum; // 权重归一化。Math.exp是JDK指数函数入口,源码调用StrictMath对应实现,并允许运行时优化。它不认识注意力或模型;Softmax是应用把多个指数结果组合起来形成的机制。
减最大值是代数等价变换:每个exp分子和总分母都乘同一个正因子。最大项变为一,至少有一个非零贡献,因此有效有限输入下分母不会全为零。
某些极小相对权重下溢为零是浮点有限精度的结果;本题接受这种近似,不把数值相等误称为数学完全相等,测试采用容差。
真实Transformer依靠张量运行库和硬件内核完成批量运算。这里原创Java代码只重现一行缩放点积注意力,原理来源见Attention Is All You Need,不能代替完整网络源码。
代码先调用谁,再调用谁
- 当前行Q与可见K做点积
- 按键维度平方根缩放
Math.exp计算平移后的指数- 归一得到注意力权重
- 权重与V加权求和
04动手写一个例子
带可见范围的单行注意力
输入一个查询向量、若干键值向量和可见前缀长度,输出加权值向量;只支持有限数且点积不得溢出。Java17程序的main提供明确期望自测。
- 输入 1
分数0,0;值2,6;可见2- 输出 1
4
- 输入 2
分数0,ln3;值2,6;可见2- 输出 2
5
只遍历可见前缀计算分数,未来位置根本不参与最大值和分母;把未来权重最后清零却忘了重新归一化会产生错误输出。
分数数组用于先求最大值再算指数,接着累计分母。每个权重由自己的指数除同一个分母,非负且总和在舍入误差内为一。
输出每一维是所有可见V该维的凸组合,因此对于有限普通输入,它应位于该维最小值与最大值之间。这个性质是有用的机制测试,但完整Transformer含后续映射,不能对整层输出照搬这个界限。
为什么这样写是对的归一化后,可见位置权重非负且总和约为一,不可见位置不进入计算;输出逐维等于这些权重与V的加权和。
t为可见位置数,d为Q/K维度,v为V维度;计算t个点积与t次值向量累加。若计算所有序列位置,朴素注意力还会引入序列长度的平方项。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.*;
public class Main {
static double[] attend(double[] q,double[][] k,double[][] v,int visible){
if(q.length==0||k.length!=v.length||visible<1||visible>k.length)throw new IllegalArgumentException("shape");
int width=v[0].length;if(width==0)throw new IllegalArgumentException("empty value");
double[] s=new double[visible];double max=Double.NEGATIVE_INFINITY;
for(double x:q)if(!Double.isFinite(x))throw new IllegalArgumentException("query");
for(int i=0;i<visible;i++){
if(k[i].length!=q.length||v[i].length!=width)throw new IllegalArgumentException("dimension");
for(int j=0;j<q.length;j++){
if(!Double.isFinite(k[i][j]))throw new IllegalArgumentException("key");
s[i]+=q[j]*k[i][j];
}
s[i]/=Math.sqrt(q.length);
if(!Double.isFinite(s[i]))throw new IllegalArgumentException("score overflow");
max=Math.max(max,s[i]);
}
double sum=0;
for(int i=0;i<visible;i++){s[i]=Math.exp(s[i]-max);sum+=s[i];}// 稳定 Softmax。
double[] out=new double[width];
for(int i=0;i<visible;i++)for(int j=0;j<width;j++){
if(!Double.isFinite(v[i][j]))throw new IllegalArgumentException("value");
out[j]+=(s[i]/sum)*v[i][j]; // 先归一权重,再混合值向量。
}
for(double x:out)if(!Double.isFinite(x))throw new IllegalArgumentException("output overflow");
return out;
}
static void near(double expected,double got){if(!Double.isFinite(got)||Math.abs(expected-got)>1e-10)throw new AssertionError(got);}
public static void main(String[] args){
double[][] values={{2},{6}};
near(4,attend(new double[]{1},new double[][]{{0},{0}},values,2)[0]);
near(5,attend(new double[]{1},new double[][]{{0},{Math.log(3)}},values,2)[0]);
near(2,attend(new double[]{1},new double[][]{{0},{1000}},values,1)[0]);// 未来项不进入分母。
near(4,attend(new double[]{1},new double[][]{{1000},{1000}},values,2)[0]);
try{attend(new double[]{1},new double[][]{{0}},new double[][]{{2}},0);throw new AssertionError();}catch(IllegalArgumentException expected){}
System.out.println("PASS 5 tests");
}
}这些特殊情况也要试一试
- 没有可见位置时拒绝,不能对空集合归一化。
- 未来位置即使分数很大也不能影响当前输出。
- 有限坐标的点积仍可能溢出,中间结果也要检查。
进阶选读 · 后续再读:长序列注意力与KV缓存
场景逐Token生成时重复计算历史键值,或长序列注意力矩阵占用过多显存。
做法缓存已经计算的K和V,注意请求隔离及位置一致性;研究分块精确注意力与稀疏近似方案,分别确认是否改变数学结果。
复杂度朴素整段注意力分数O(L²d)、矩阵空间O(L²);缓存减少重复投影但每步仍可能读取O(Ld)历史,不能笼统承诺常数成本。
好处与代价缓存用空间换重复计算,并增加生命周期管理;近似稀疏方法可能改变可见关系,需要任务质量评估。
05项目里怎么用
应用开发通常不需要手写注意力内核,但需要理解输入长度为何影响延迟和资源。上下文越长,不只是请求体更大;模型还要处理更多位置。选择检索片段时应以回答需要的信息为依据,先去重和保留结构,不要认为把整个仓库塞进去一定优于少量准确证据。
注意力可视化能帮助观察内部权重,却不能单独证明模型推理原因或事实依据。高权重不等于某段文字在因果意义上决定最终答案,完整网络还有多层非线性路径。面向用户解释答案时,应优先展示可核对的来源和具体断言支持。
读注意力源码时,先把公式里的量对应到张量形状,再检查掩码的维度、自动扩展规则和数据类型。程序能运行,不代表允许读取的位置就正确,因为广播可能让错误形状也算得下去。先用小矩阵手算和固定断言建立对照,再学习多头、混合精度与加速内核,更容易分清基本机制和性能优化。
容易踩的坑
- 把Token编号直接当作有语义距离的坐标。
- 掩码只清零输出权重但不修正归一化分母。
- 把注意力权重等同于可信解释或来源证明。
- 将单行Java算术模型称为完整Transformer实现。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 朴素注意力 | 形状清楚,适合验证公式 | 长序列计算与内存昂贵 | 小规模教学和正确性基线 |
| KV缓存 | 减少生成中的重复投影 | 历史缓存占空间并需隔离 | 自回归推理 |
| 稀疏或压缩方法 | 降低长上下文资源消耗 | 可能改变信息可见性与质量 | 评测证明适合的长文任务 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01Q与V能混为同一个概念吗?
展开参考答案与推导
Q参与决定关注位置,V承载被混合的表示;真实模型的投影参数可不同,角色不能仅因形状相同而互换。
02减最大分数为何有效?
展开参考答案与推导
Softmax对所有分数的共同平移不变;平移后最大指数为一,减少上溢且保留相对权重。
03训练并行是否意味着可以看未来答案?
展开参考答案与推导
不是。因果模型在批量计算中仍用掩码约束每个位置的可见范围,训练目标可以并行计算而不泄漏未来输入。
换个情况,还会处理吗
01代码运行正常但未来文字改变当前输出,查哪里?
展开参考答案与推导
检查掩码应用时机和广播维度,确保不可见项不进入Softmax最大值和分母;用只改未来K/V的测试定位。
02exp得到无穷怎样处理?
展开参考答案与推导
先使用减最大值稳定化,同时检查点积是否已溢出;若前一步已非有限,不能指望Softmax自动修复。
03用户要求解释依据,可展示最高注意力词吗?
展开参考答案与推导
不能把它当成事实证明。应给来源片段与断言映射,并将内部权重可视化限定为诊断线索。
写代码试一试
01均匀三个值如何输出?
- 输入 / 条件
分数0,0,0;值3,6,9- 输出 / 预期
6
提示 相同分数权重相同。
展开参考答案与推导
三个权重各三分之一,输出是算术平均;可作为扩展到三个可见位置的测试。
02如何测试平移不变性?
- 输入 / 条件
分数0,ln3与100,100+ln3- 输出 / 预期
输出均约5
提示 保持V为2,6。
展开参考答案与推导
给全部分数加同一常数,比较输出容差;若不同,检查归一化是否一致减最大值。
03整层为何不必位于原V范围内?
- 输入 / 条件
注意力后再做线性投影- 输出 / 预期
可能超出原坐标范围
提示 区分局部机制与组合。
展开参考答案与推导
凸组合界限仅属于当前加权平均,后续矩阵映射、残差和前馈变换改变坐标,不能把局部不变量套到整层。
读完之后,独立完成
课后作业:自己做一遍
本篇共 8 道作业,先完成 6 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能画出文本进入 Transformer 后的主要处理步骤,解释因果掩码限制哪些读取,以及注意力权重不能证明什么。
本篇重点加练
注意力中的角色与可见性需要一起验证,作为选修背景重点理解信息流边界。
- Q、K、V 的作用
- 因果掩码
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
用一句含重复词的短文本画Token标识到向量的映射,说明词、Token编号和向量维度是怎样区分的,不要求训练模型。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点文本到 Token 标识 · Token 到向量表示
- 作业 02代码推演
设序列长度3、特征维度2,画出加入位置信息后形成Q、K、V的形状关系,逐步检查各矩阵乘法是否合法。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点位置与顺序信息 · Q、K、V 的作用
综合应用
- 作业 03面试场景
对三位置序列指定第二个位置为查询,先列出可见集合,再计算一次自行选择的注意力混合;改变未来位置输入并记录观察。
- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点注意力权重与混合 · 因果掩码
- 作业 04编程实践
写一个三步假自回归生成器,记录每步输入与新增Token;另画一次训练数据的输入标签对齐,并比较与逐步生成的差别。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点自回归生成与训练区别
- 作业 05排错实验
故意将因果掩码方向颠倒,对同一输入运行两版小程序,设计能暴露差异的输入,记录每个位置的可见性。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点位置与顺序信息 · 注意力权重与混合 · 因果掩码
- 作业 06编程实践
在本地小注意力程序中固定Q,分别只改变一个可见K、可见V和被掩码位置的K
/V,记录输出是否变化,并依据所用掩码规则核验信息流。- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点Q、K、V 的作用 · 因果掩码
进阶挑战 · 掌握前置后选做
- 作业 07设计评审
在学会线代后,比较编码器与解码器面对同一任务时的信息访问条件,说明选择依据及不能仅凭注意力图推出的解释。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点Q、K、V 的作用 · 因果掩码 · 编码器与解码器
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 08思维拓展 · 反例构造
为“注意力权重最大的位置一定决定最终回答”构造一个具有明确Q
/K/V和后续处理假设的反例或可检验实验,说明观察结论的适用范围。- 提交内容
- 具体反例、原主张的适用前提、复现或逻辑证明及结论边界。
- 怎样算完成
- 反例满足原主张所声称的前提,确实针对其结论。
- 说明一个反例能推翻什么、不能进一步证明什么。
练到的知识点Q、K、V 的作用 · 因果掩码
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读教程中文
Google:语言模型与 Transformer (新标签页)↗
阅读语言模型、上下文与 Transformer 相关示意,用中文补齐逐步生成和注意力的概念。
- 实践教程英文
Hugging Face:How do Transformers work (新标签页)↗
阅读模型架构、编码器解码器与注意力说明,理解表示学习和逐步生成;训练细节按需继续。
- 深入教程英文
Hugging Face:Tokenizers (新标签页)↗
阅读词级、字符级与子词分词,以及编码解码过程;用来理解字符数不能直接替代 Token 数。
- 深入参考文档英文
OpenJDK 17 Math 源码 (新标签页)↗
按指定版本定位
Math.exp;对照源码分析理解实际调用边界。 - 深入参考文档英文
Attention Is All You Need 原始论文 (新标签页)↗
阅读第3节模型架构及Scaled Dot-Product Attention公式,核对掩码、多头与本篇单行模型的区别。