resource-ledger.md · 可读全文

这里保留原文的中文、公式标记、代码和链接。可直接交给 Agent 读取,也可选取下方原文复制。此入口返回 HTML;原始 .md / .py 文件仍保留,完整候选包同时包含 UTF-8 原件。

# 给长前缀列一张有单位的资源账本

课堂接续:[T50](https://codingai-lec05.pages.dev/classroom/T50.html?step=b0) · [T51](https://codingai-lec05.pages.dev/classroom/T51.html?step=b0) · [T60](https://codingai-lec05.pages.dev/classroom/T60.html?step=b0) · [T61](https://codingai-lec05.pages.dev/classroom/T61.html?step=b0) · [T62](https://codingai-lec05.pages.dev/classroom/T62.html?step=b0) · [T63](https://codingai-lec05.pages.dev/classroom/T63.html?step=b0) · [T64](https://codingai-lec05.pages.dev/classroom/T64.html?step=b0) · [T65](https://codingai-lec05.pages.dev/classroom/T65.html?step=b0)。连续读本:[第7章](https://codingai-lec05.pages.dev/lecture.html#chapter-7);本主题提供独立定义、例子、代码与来源。

本主题采用图文描述任务:中文“我昨天给了他一本书。”与同一事件的蓝封面书图共同提供条件,参考描述为 `I gave him a blue book yesterday .`。现在看同一个生成时刻:刚选中blue,历史已处理到a,下一次送入的新token是blue,接下来预测book。每一层均已保存截至a的旧K/V;blue在第一层产生新Q/K/V,追加本层K/V并读取历史及自己,再把该层输出送给第二层。第二层用自己的投影与缓存重复这一过程;不同层的K/V不能互相借用。

![两层各有旧KV,新增blue逐层计算并预测book](https://codingai-lec05.pages.dev/assets/r72/classroom/T50-b2.svg)

旧位置不能读取后来追加的blue。若旧前缀、位置规则、模型参数和确定性运行条件不变,旧位置的第一层输出不变,第二层输入也不变,逐层可以证明旧K/V仍合法。被省掉的是旧位置的重复投影和后续表示计算;新token的Q/K/V、FFN、输出头以及读取历史都仍要算。图像、问题或中文前缀改变时,相关历史表示也改变,旧缓存必须作废或按确切依赖重新计算。

原encoder–decoder另有cross-attention的源K/V缓存,它来自固定源H,不随目标每新增一个token而增长;这与decoder-only合并序列的self-attention缓存分开记。缓存保存的是某次计算,不是永久知识,也不会让上下文容量自动变大。

接下来切换计算阶段。单步decode常只有一个新query、分数为1×t;现在讨论已知多个query的prefill或训练,才会出现完整N×N分数表。训练还需要反向传播,prefill通常只做前向并建缓存,不能把这三者的计时与形状混在一起。

朴素实现先算 $S=QK^\top/\sqrt{d_k}$ 并把S写入HBM;下一个阶段读S,做逐行softmax并把P写回;再读P和V计算 $O=PV$。HBM是GPU高带宽显存,不是CPU内存或磁盘。片上工作区更小,不能装整张大表,但能逐块保留当前累计。

![多query阶段中S与P的显存往返,改为片上分块累计](https://codingai-lec05.pages.dev/assets/r72/classroom/T51-b4.svg)

分块算法读入一块Q与若干K/V,算出的分数块立即用于更新归一化统计与输出累计,不把完整S或P物化回HBM。它保持密集attention的数学结果,在规定浮点容差内与全量计算一致;没有把一般 $O(N^2d)$ 成对算术变成线性。下面完整展开KV单位、m/ℓ/u重缩放、17/3手算、原论文GPU测量和本课CPU缓存检查;默认课堂用六分钟建立缓存与搬运的资源直觉;T60–T65另有独立的十一分钟账本展开,两者都不压缩本章完整的独立阅读内容。


[阅读本主题](https://codingai-lec05.pages.dev/course/knowledge/resource-ledger.html) · [返回实验](https://deepnote.com/workspace/ise3309jingao-5ba66b0a-4283-4f5a-ae06-3f68d3201e99/project/Fall-26-Lecture-05-841ec8e7-e352-4840-b628-d3742b03d6d0/notebook/LAB-03-Transformer-9c5b3d45acdb438a8e7df1dc6cb97e40?utm_content=9c5b3d45acdb438a8e7df1dc6cb97e40&utm_source=openai&utm_medium=mcp&utm_campaign=openaimcp&utm_term=get_notebook) · [与Agent共读全文](https://codingai-lec05.pages.dev/course/knowledge/resource-ledger.md.read.html)

读本:[ CLS 03 · 生成、视觉条件与上下文 ](https://deepnote.com/workspace/ise3309jingao-5ba66b0a-4283-4f5a-ae06-3f68d3201e99/project/Fall-26-Lecture-05-841ec8e7-e352-4840-b628-d3742b03d6d0/notebook/CLS-03-719b25c7c5b64687998ba9c19d2f6776?utm_content=719b25c7c5b64687998ba9c19d2f6776&utm_source=openai&utm_medium=mcp&utm_campaign=openaimcp&utm_term=get_notebook),查找“给长前缀列一张有单位的资源账本”;实验中查找“G · 旧 K/V 哪些可以沿用,哪些必须作废?”。


<span id="resource-ledger-s01"></span>

## prefill 与 decode 各做什么

prefill已知整段输入,能并行投影所有token、计算合法attention并保存各层K/V。decode每次只有一个新token,要计算它的embedding/层内QKV/FFN/词表头,并用新query读历史K/V。cache省掉旧token的投影、FFN和表示重算;新token的工作和历史读取仍要做。

设一层模型宽d、FFN宽f,忽略bias、激活和归一化,乘加记为一个MAC:prefill n个token的QKV与输出投影约4nd²,FFN约2ndf;密集attention QKᵀ与AV约2n²d MAC(具体causal内核是否跳过上三角另算)。decode一步读t个key时,投影约4d²、FFN2df、交互约2td。若把乘法与加法各算一个FLOP,这些量再约乘2;不能混用计数口径。词表头还可能有d×|V|项。

<span id="resource-ledger-s02"></span>

## KV payload 的单位要写全

设 L 为缓存的层数,B 为批中序列数。每层每条序列保留 n 个位置、H_kv 个 KV 头、每头 d_h 维,K 与 V 各一份,每元素 s 字节;公式前的 2 表示 K、V 两份:

$$\text{KV bytes}=2LBnH_{kv}d_hs.$$

取L=32、B=1、n=4096、H_kv=8、d_h=128、s=2(半精度存储),得到536870912bytes,即512MiB。这只算KV有效数组,不含模型权重、激活、临时buffer、碎片与运行时。完整MHA若H_kv=32则为2GiB;同head维的GQA用8个KV头是其1/4。

若显式保存64个头、n=100000的全scores矩阵,半精度需要 `64×100000²×2=1.28×10^12bytes`,约1.28TB(十进制)≈1.164TiB;仅单层、batch=1的score张量。这说明朴素物化的压力,不是所有attention实现的显存下限,FlashAttention正是改变是否把整张矩阵写回高层存储。

<span id="resource-ledger-s03"></span>

## GQA改变共享接口

多query头仍可有不同Q,但每组共享一个K/V头。H_q=32/H_kv=8时,每组4个query头读同一组K/V。KV存储与相应投影减少,Q与每步的读取仍存在;它不是简单给同一张大KV表换压缩文件格式。原GQA论文从T5.1.1等checkpoint转换并继续训练验证质量,不能宣称任意MHA直接替换后无损。

如果瓶颈是显存容量,KV减少可能让更大batch或更长窗口可行;如果瓶颈是小batch的kernel启动/通信,比例减少不一定等于延迟同比例下降。比较要报告H_q/H_kv、d_h、精度、batch、前缀长、生成长与实现。

<span id="resource-ledger-s04"></span>

## 同一个attention,算术、容量与搬运分开记

![旧版独立扩展:有限片上空间中的分块读入与累计](https://codingai-lec05.pages.dev/assets/r71/classroom/S38-b2.svg)

先限定阶段:下面的完整N×N图属于**训练/prefill的多个query处理已知输入**。常见单请求单步decode只有一个新query,score为1×t;不能把两者都画成同样的平方临时表。训练还需要反向传播与保存或重算,prefill通常只做前向,decode读取已有KV并追加新位置。

以朴素分阶段实现为例,QKᵀ产生S,softmax产生P,再由PV产生O。S与P可能先写回大容量HBM,再被后续内核读入;Q/K/V、最终O和模型权重也占资源。这里的“朴素”是一种实现安排,不是attention数学定义强制要求保存这些中间张量。

GPU片上工作区小而快,HBM容量大但搬运有成本。可以选一块query,逐块读取key/value;每个分数块算出后立即用于累计,不把完整S或P写回HBM。Q/K/V与最终O的语义保持不变,改变的是数据何时驻留何处。FA1原算法的具体外内循环与本段“观察一块query”的解释视角要区分;FA2另有线程块并行与工作分配优化,不能混称为同一版循环。

训练反向可以保存每行归一化统计,按需重算小分数块。这样可能多做算术,却少搬运大量中间数据。是否更快取决于硬件、尺寸、精度、批量与内核,不能只从FLOPs下降或上升推出速度。


<span id="resource-ledger-s05"></span>

## FlashAttention为什么不必保存整张热图

softmax一行的输出是 `sum_j exp(s_j)v_j / sum_j exp(s_j)`,其中 s_j 是第 j 个合法 key 的分数。为了稳定,减去已处理分数的最大值 m,定义未归一化权重 $w_j=\exp(s_j-m)$。在同一个基准 m 下,$\ell=\sum_j w_j$ 是权重和,$u=\sum_j w_jv_j$ 是 value 加权和,输出为 $u/\ell$。分块时后面可能出现更大的分数,因此维护 m、ℓ、u 三个量;读新块后:

$$m'=\max(m,\max s_{new}),$$
$$\ell'=e^{m-m'}\ell+\sum_{new}e^{s_j-m'},\quad u'=e^{m-m'}u+\sum_{new}e^{s_j-m'}v_j.$$

最后输出u'/ℓ'。旧贡献和新贡献必须使用同一指数基准,所以旧ℓ与u都要重缩放。这是在线softmax的关键,而不是“只看最大项”的近似。

初始化从第一个有效块开始,或令m=−∞、ℓ=0、u=0并对空块显式跳过;不能直接计算−∞−(−∞)。被mask掉的位置不进入最大值与指数和。整行没有合法key应显式处理或避免,不把NaN当“选择不读”。u为向量时逐分量累计同样成立。

一维小例:scores=(0,ln2,ln3),values=(2,4,8)。前两项一块,m=ln2,ℓ=1.5,u=5;读第三项后m'=ln3,旧量乘2/3,得到ℓ'=2、u'=34/3,输出17/3≈5.6667。一次算的加权平均 `(1×2+2×4+3×8)/(1+2+3)`也是17/3。分块不需要保存每个权重;浮点运算顺序不同允许数值容差,不承诺逐bit一致。

FlashAttention把这个思想与GPU分块、片上存储和反向重计算组织结合,减少高带宽内存读写;仍计算密集合法连接,不等于稀疏attention,也没有把所有成对算术变为线性。GQA减少KV头,cache复用旧计算,FlashAttention减少物化和IO——三个改动可以共存,但作用不同。

<span id="resource-ledger-s06"></span>

## 在线累计的两次停点

![旧版独立扩展:旧分母与旧加权和同时重缩放](https://codingai-lec05.pages.dev/assets/r71/classroom/S39-b3.svg)

|已处理内容|m|ℓ|u|u/ℓ|
|---|---|---|---|---|
|前两项|ln2|1/2+1=3/2|(1/2)·2+4=5|10/3|
|加入第三项并统一到ln3|ln3|(2/3)·(3/2)+1=2|(2/3)·5+8=34/3|17/3|

前两项的分母按ln2缩放,新项按ln3缩放,所以不能直接相加。先把旧分母与旧加权和都乘2/3,再加新块。一口气算全部时权重为(1/6,2/6,3/6),输出仍为17/3。分别对每块softmax后只保存输出,会丢失块间归一化质量。当前小例两个块的未缩放质量恰好同为3,简单平均恰好也给17/3;因此这个例子用来核对重缩放,不用来证明简单平均必错。对一般分块,必须保留ℓ与u等可组合统计。NumPy复算能够验证这个数值关系,不能充当GPU FlashAttention实现或速度测评。

<span id="resource-ledger-s07"></span>

## 多做算术,却少花时间:一项历史测量

[FlashAttention 2022 v2物理p6 Figure2左表](https://arxiv.org/pdf/2205.14135v2)测量GPT-2 medium attention:A100、序列长1024、head维64、16 heads、batch64,前向+反向。原文的训练与混合精度实现条件属于该历史实验;此处不将结果外推成任何精度、任意尺寸的保证。

|attention子计算指标|standard|FlashAttention|
|---|---:|---:|
|算术|66.6 GFLOPs|75.2 GFLOPs|
|HBM读写|40.3 GB|4.4 GB|
|时间|41.7 ms|7.3 ms|

算术更多但HBM读写更少,支持“执行时间还受搬运影响”的解释。这里不是完整模型端到端时间,不是本课GPU实测,也不是单步decode结果。Figure1采用另一比较口径,不能把它的数字混进此表。第一讲`moving-data/1`给过计算/搬运时间下界,本讲补上了具体怎样避免大表往返。

密集attention的合法两两算术仍为O(N²d)。不物化N×N表、减少HBM读写与改变渐近算术复杂度是不同事项。KV payload随已存前缀长度增长;模型权重、FFN、词表头与其他激活另计。exact表示同一数学计算,浮点允许容差差异,不要求逐位相同。


<span id="resource-ledger-s08"></span>

## 回到当次更慢的 cache

DN1的标量incremental路径19.95ms、矩阵重算11.46ms,是微型2层、宽8、2头、batch1、float64配置中,16 token prefill之后追加32个预定token的**decode合计中位时间**,不是每token时间。它没有否定cache的工作量分析,也不证明优化内核一定有同样排名。先用相同预定token和logit容差检查计算,再计时;若用模型自己生成而两条路径选到不同长度,工作量已经不可比。

若要继续研究,分开测prefill、总decode、每步延迟随t的变化与峰值内存;固定模型、精度、硬件、batch和内核版本,给预热/重复与分布。当前课没有运行GQA/FlashAttention速度比较;手算账本与在线softmax是机制解释,不是性能基准。

来源:同版读本第七章;[GQA EMNLP2023 §2](https://aclanthology.org/2023.emnlp-main.298.pdf)、[FlashAttention2022 v2 §3/Algorithm1/Figure2](https://arxiv.org/pdf/2205.14135v2)。单位算例与在线softmax小例为可复算构造;历史毫秒值来自DN1云端G记录。


原课程组织参考:CS231n lecture_8.pdf物理p76–79在同一attention对象上提出容量问题并改变物化;页脚April 23, 2026。执行细节与历史测量以FlashAttention 2022 v2原论文为准。


<span id="resource-ledger-s09"></span>

## 相邻知识与复核资源

[路径 C:材料放得下,不代表采用了当前事实](https://codingai-lec05.pages.dev/course/knowledge/cache-and-effective-context.html)

[全部主题](https://codingai-lec05.pages.dev/course/knowledge/index.html) · [关键结果JSON](https://codingai-lec05.pages.dev/course/knowledge/data/learning-paths.json) · [只读诊断源码](https://codingai-lec05.pages.dev/course/knowledge/code/learning_paths.py.read.html) · [Seq2Seq完整实现](https://codingai-lec05.pages.dev/course/knowledge/code/seq2seq.py.read.html) · [视觉机制实现](https://codingai-lec05.pages.dev/course/knowledge/code/mechanisms.py.read.html) · [TinyDecoder与cache实现](https://codingai-lec05.pages.dev/course/knowledge/code/legacy_numpy.py.read.html)