qinghuan
← 理解模型与输入 / 全部笔记

单元 06 / 理解模型与输入

P2 · 按需深入L1 · 基础

理解 Transformer 如何利用上下文

从一句话怎样逐步生成出发,理解分词、注意力和上下文,不必先实现整套训练。

本篇目录 · 按需要跳转

这篇重点学什么

本篇重点:画出 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

按顺序看,每一步做了什么

  1. 给定维度一致的Q、K、V和当前可见位置数。
  2. 计算可见位置的缩放点积,拒绝非有限中间结果。
  3. 减去最大分数后做Softmax,得到归一权重。
  4. 按权重混合V,检查输出维度和有限性。
  5. 对照掩码、均匀权重及偏斜权重的手算结果。

03对照实现代码

OpenJDK 17 Math 源码 (新标签页)↗

OpenJDK 17 GA,jdk-17+35 · Math.exp

下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。

核心机制代码 · java
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,不能代替完整网络源码。

代码先调用谁,再调用谁

  1. 当前行Q与可见K做点积
  2. 按键维度平方根缩放
  3. Math.exp计算平移后的指数
  4. 归一得到注意力权重
  5. 权重与V加权求和

04动手写一个例子

带可见范围的单行注意力

输入一个查询向量、若干键值向量和可见前缀长度,输出加权值向量;只支持有限数且点积不得溢出。Java17程序的main提供明确期望自测。

输入 1
分数0,0;值2,6;可见2
输出 1
4
输入 2
分数0,ln3;值2,6;可见2
输出 2
5

只遍历可见前缀计算分数,未来位置根本不参与最大值和分母;把未来权重最后清零却忘了重新归一化会产生错误输出。

分数数组用于先求最大值再算指数,接着累计分母。每个权重由自己的指数除同一个分母,非负且总和在舍入误差内为一。

输出每一维是所有可见V该维的凸组合,因此对于有限普通输入,它应位于该维最小值与最大值之间。这个性质是有用的机制测试,但完整Transformer含后续映射,不能对整层输出照搬这个界限。

为什么这样写是对的归一化后,可见位置权重非负且总和约为一,不可见位置不进入计算;输出逐维等于这些权重与V的加权和。

时间 O(t(d+v))额外空间 O(t+v)

t为可见位置数,d为Q/K维度,v为V维度;计算t个点积与t次值向量累加。若计算所有序列位置,朴素注意力还会引入序列长度的平方项。

保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。

展开收起代码 · Main.java · 完整可运行程序Java · 38 行

长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。

Main.java · 完整可运行程序 · Java
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 的作用
  • 因果掩码

重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。

先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。

基础必做

  1. 作业 01原理问答

    用一句含重复词的短文本画Token标识到向量的映射,说明词、Token编号和向量维度是怎样区分的,不要求训练模型。

    提交内容
    一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
    怎样算完成
    • 逐项回应题目要求,区分事实、假设与个人判断。
    • 能用自己的例子解释适用条件,记录尚未弄懂的点。

    练到的知识点文本到 Token 标识 · Token 到向量表示

  2. 作业 02代码推演

    设序列长度3、特征维度2,画出加入位置信息后形成Q、K、V的形状关系,逐步检查各矩阵乘法是否合法。

    提交内容
    手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
    怎样算完成
    • 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
    • 主动检查至少一个边界或异常条件,能解释手算与运行差异。

    练到的知识点位置与顺序信息 · Q、K、V 的作用

综合应用

  1. 作业 03面试场景

    对三位置序列指定第二个位置为查询,先列出可见集合,再计算一次自行选择的注意力混合;改变未来位置输入并记录观察。

    提交内容
    场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
    怎样算完成
    • 方案回应给定约束,列出至少一种替代方案及代价。
    • 每项关键结论说明所需证据,不能核实的部分明确标注。

    练到的知识点注意力权重与混合 · 因果掩码

  2. 作业 04编程实践

    写一个三步假自回归生成器,记录每步输入与新增Token;另画一次训练数据的输入标签对齐,并比较与逐步生成的差别。

    提交内容
    可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
    怎样算完成
    • 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
    • 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。

    练到的知识点自回归生成与训练区别

  3. 作业 05排错实验

    故意将因果掩码方向颠倒,对同一输入运行两版小程序,设计能暴露差异的输入,记录每个位置的可见性。

    提交内容
    最小故障输入、复现步骤、观测记录、定位依据及回归用例。
    怎样算完成
    • 其他人可按记录复现故障,并区分现象与已验证原因。
    • 定位过程有观测依据,回归用例可以再次暴露原问题。

    练到的知识点位置与顺序信息 · 注意力权重与混合 · 因果掩码

  4. 作业 06编程实践

    在本地小注意力程序中固定Q,分别只改变一个可见K、可见V和被掩码位置的K/V,记录输出是否变化,并依据所用掩码规则核验信息流。

    提交内容
    可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
    怎样算完成
    • 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
    • 验证目标由合同确定,模拟结果不冒充真实服务证据。

    练到的知识点Q、K、V 的作用 · 因果掩码

进阶挑战 · 掌握前置后选做

  1. 作业 07设计评审

    在学会线代后,比较编码器与解码器面对同一任务时的信息访问条件,说明选择依据及不能仅凭注意力图推出的解释。

    提交内容
    设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
    怎样算完成
    • 比较使用一致条件与事先声明的标准,记录失败与未知结果。
    • 说明方案的适用前提、未覆盖风险及后续验证方法。

    练到的知识点Q、K、V 的作用 · 因果掩码 · 编码器与解码器

思维拓展

先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。

  1. 作业 08思维拓展 · 反例构造

    为“注意力权重最大的位置一定决定最终回答”构造一个具有明确Q/K/V和后续处理假设的反例或可检验实验,说明观察结论的适用范围。

    提交内容
    具体反例、原主张的适用前提、复现或逻辑证明及结论边界。
    怎样算完成
    • 反例满足原主张所声称的前提,确实针对其结论。
    • 说明一个反例能推翻什么、不能进一步证明什么。

    练到的知识点Q、K、V 的作用 · 因果掩码

完成后,再检验一次

给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。

继续阅读

先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。

打开资料库,找下一份学习材料 ↗