08|推理计算、KV 缓存与部署系统
同一组权重,已知前缀可以成批处理,未知输出却要逐步反馈。先看两种计算的形状,再算一次回答究竟读了什么。

图 1|提示词整体处理与逐步反馈(原创教学示意)
阅读路线:先区分参数更新和当前请求中的临时计算,再沿 token 的前缀处理、KV 追加走到缓存内存;最后比较推理预算、量化和产品任务循环。需要对照源码时,接着阅读 11|可复现实验附录。
本页目录:1. 请求计算与临时状态 · 2. 缓存内存与注意力执行 · 3. 候选验证与推理预算 · 4. 精度、产品边界与验证
参数规模、输入长度、输出步数,以及验证和工具路径共同决定计算;下面把这些因素落到请求状态与一次读量上。
本篇用常规 causal Transformer 与 GQA 展示缓存机制。这是可计算的公开技术路线,不是 GPT-6.1 或 Claude 未公开架构的示意图。不同模型可能使用潜变量缓存、滑动窗口、跨层共享或其他注意力设计,下面的内存公式不能直接套给所有模型。
1. 请求计算与临时状态
1.1 训练计算与推理时计算

图 2|参数更新与当前请求计算的区别(教学示意)
图左侧的梯度回写改变参数矩阵;右侧保持同一矩阵,变化的是候选路径和当前状态。沿着这一区别,才能说明“思考更久”究竟多做了什么。
训练执行前向、损失、反向传播和参数更新,目的是让后续请求使用更好的权重。推理通常只执行前向与采样,权重保持固定。一次回答消耗更多计算,不代表它对模型做了一次微调;对话进入上下文,也不等于内容已经写进参数。
test-time compute 指处理当前请求时追加的计算资源。 它可以用于延长一条推理轨迹,也可以用于生成多条候选、评估中间步骤、调用工具或协作搜索。增加预算的收益取决于模型是否学会使用这些资源,以及任务是否有可靠反馈。针对困难问题的公开研究表明,最有效的预算分配会随问题难度变化。来源:Scaling LLM Test-Time Compute Optimally。
四种计算位置的完整对照
| 计算发生的位置 | 直接改变什么 | 延迟由什么决定 |
|---|---|---|
| 预训练或后训练 | 参数及其形成的行为 | 数据量、计算量、并行效率、训练目标。 |
| 单轨迹推理 | 当前答案与临时状态 | 输入长度、每 token 执行时间、输出步数。 |
| 候选采样与验证 | 当前答案的选择过程 | 候选数量、验证器成本、并行资源。 |
| 工具与 agent 循环 | 当前任务可获得的信息和行动结果 | 工具等待、交互轮数、失败重试。 |
后训练可以专门教模型怎样在推理时分配预算,因此这几行存在联系,但计算的发生阶段仍然不同。测量时也应区分首 token 延迟、相邻 token 延迟和完整任务用时;一个系统首 token 很快,不保证长答案吞吐量高。
1.2 Prefill 与 Decode 的张量路径
首屏图中左侧输入已经确定,右侧逐步反馈;汉字仅作过程示意,不代表实际分词器边界。
Prefill 处理已知输入前缀。输入的所有 token 已经确定,网络可以在每层同时计算这些位置;因果 mask 阻止位置读取未来。Decode 处理刚生成的新 token,下一 token 还不知道,因此通常一步一步推进。前缀处理结束后,最后位置的 logits 可采样第一个输出 token;把这个输出 token 再送入网络,才产生下一步 logits。

图 3|前缀并行处理与逐 token 生成(原有教学图)
下表采用轴顺序 [batch, heads, sequence, head_dim]。 为 batch, 为已处理长度, 为 query 头数, 为 K/V 头数, 为单头宽度。每层都生成自己的投影。
| 张量 | Prefill | 单 token decode |
|---|---|---|
| 本次 | 各 | 各 |
| 缓存 | 写入全部前缀位置 | 追加后各为 |
| 注意力分数 | 逻辑上 |
Prefill 的大矩阵有利于 GPU 并行;decode 的单步矩阵更瘦,在一些配置下更受权重读取和 KV 读取带宽约束。长输入增加 prefill 成本,长输出则让 decode 循环更多次。Batch、硬件和调度会改变瓶颈,不能仅靠一个 FLOP 数值判断延迟。
1.3 KV 缓存的逐层所有权

图 4|当前查询读取历史 K/V,并追加当前项(原创教学示意)
图中青色和琥珀色向量代表两份对齐的缓存;观察每步右端新增的蓝色向量,旧向量保持原样。下方圆点止于当前层的 attention 输出,之后还要经过模型后续计算、词表输出投影与采样,才会得到离散的下一 token。
缓存保存历史位置在每个注意力层产生的 key 和 value。常规因果网络中,历史位置不能看见后来 token,所以这些历史表示无需随新 token 重算。缓存复用历史投影,仍保留新 token 对历史的注意力计算。 Query 通常只为当前需要计算的位置生成,不需要保存所有历史 query。来源:Transformers 缓存文档。

图 5|每层输入、缓存与完整输出各自对应(订正教学示意)
第二层的 K/V 由第二层输入产生,不能把第一层缓存当作第二层缓存。上图只画两层,真实模型重复同样的所有权关系;明确设计跨层共享的模型属于另一种结构。例如V4.1报告的CSA2区分Full、Reindex和Reuse,共享global KV与索引的职责由结构明确定义,各层仍保留自己的global Q与SWA KV;第10篇的原图细读会沿真实路径解释。缓存还与请求前缀、位置编号、模型权重及具体计算配置有关。改了前缀却沿用旧缓存,可能产生语义错误。
1.3.1 单层缓存更新与 GQA 代码
读者可沿以下代码地图检查状态:layer_hidden → Q/K/V projection → 位置处理 → 本层 cache update → attention → 下一层。以下为未实测教学片段,用单 query、无 padding 的 GQA 展示形状;省去线性层、RoPE、输出投影、残差与 FFN。
# q: [B, Hq, 1, d]
# k_new/v_new: [B, Hkv, 1, d]
# cache_k/cache_v belong to THIS layer: [B, Hkv, S, d]
k_all = torch.cat([cache_k, k_new], dim=-2)
v_all = torch.cat([cache_v, v_new], dim=-2)
group = q.shape[1] // k_all.shape[1]
k = k_all.repeat_interleave(group, dim=1)
v = v_all.repeat_interleave(group, dim=1)
score = (q @ k.transpose(-1, -2)) / math.sqrt(q.shape[-1])
out = score.softmax(dim=-1) @ v # [B, Hq, 1, d]
# Return k_all/v_all as this layer's new compact cache.
要求 能被 整除,且当前位置可以看见所有已缓存位置。这里的单 query 已在序列最后,不应误加一个只保留矩阵左上角的因果 mask。若一次输入多个新 token,则需要按真实历史偏移构造 mask。教学代码显式复制 K/V 方便看对应关系,生产 kernel 可以避免复制;逐步 cat 也不是高效缓存管理方式。
2. 缓存内存与注意力执行
2.1 GQA 的共享结构与内存演算

图 6|相同四个查询头,对应不同数量的 K/V 头(精确教学配置)
图中每条箭头标明一个查询头使用哪组 K/V。GQA 缩短的是被保存的 K/V 头轴,查询头可以继续保持更多组。
Multi-head attention 的 query 与 K/V 头数通常相同;MQA 让所有 query 头共享一组 K/V;GQA 在两者之间,用多组 query 共享较少的 K/V 头。它减少缓存头轴的长度,代价是改变注意力参数化与训练配置。来源:GQA。
2.1.1 从每个数值累加到总字节数

图 7|32 层、8192 token、BF16 的缓存内存代入
图上半部分保持层数、长度和精度不变,只把 K/V 头数从 32 减到 8;下半部分再把独立请求数从 1 增到 8。
对所有层都使用相同配置、缓存未量化且不跨层共享的模型,KV 原始存储量为:
KV 存储公式:符号、单位与完整代入
| 符号 | 含义与本例 |
|---|---|
| 所有层、所有请求的 K/V 原始数据字节数。 | |
| key 与 value 两份。 | |
| 层数,取 。 | |
| 相同长度的请求数,取 。 | |
| 已缓存 token 数,取 。 | |
| 每层 K/V 头数,取 。 | |
| 每个 K/V 头的宽度,取 。 | |
| 每个数值的字节数,BF16 本例取 。 |
代入得到 字节,即 GiB; GiB 等于 字节。
若 query 为 头,普通 MHA 的 ,同条件缓存为 GiB;GQA 的 ,为 GiB。八个同长度请求分别对应 GiB 和 GiB。这里计算的是聚合原始缓存,未计 allocator、量化元数据、预留块、权重和临时 workspace;多卡切分后,每卡持有多少还取决于分片策略。
2.1.2 可变长度请求与物理分页

图 8|逻辑块顺序与物理缓存位置分离(教学示意)
沿图中的蓝色连线读取 A0、A1、A2、A3:它们在物理池中无需连续,模型看到的逻辑前缀顺序仍需正确。
可变长度请求应对各自长度求和,不能把平均长度当作精确峰值。PagedAttention 用分页组织缓存,减少碎片并支持前缀共享;它改善分配与复用,不会凭空删除每个独立 token 的信息需求。来源:PagedAttention。
2.1.3 一步 Decode 的读量:吞吐与单请求延迟怎样分离

图 9|共享权重读取,逐请求读取 KV(原创教学示意)
先沿图中的蓝色带看:下半图有八条请求,但蓝色权重仍来自同一份模型。再数青色与琥珀色的成对带:每条请求都有自己的历史,数量从一组增到八组。批处理可以摊薄权重读取,不能把八条不同历史合成一条历史。 这里讨论的是读入数据的成本,不是在画八份模型权重,也没有表示单请求会快八倍。
把前面的内存算例接到一次执行上。仍取每请求 、32 层、8 个 K/V 头、头宽128、BF16,则每请求原始 KV 为1 GiB。另为教学预算设一个70亿参数模型,权重按每数值2字节存储,合计14 GB。两者单位不同:这里1 GiB是1.073741824 GB,不能直接把它当成1 GB相加。
采用一个明确的读量模型:每个 decode 步从显存完整读一次权重,每个请求的全部历史 KV 也各读一次;本步为每条请求生成一个 token。先略去当前新追加的一项、不计激活、额外索引、工作区及算术计算,只给这部分读量一个理想带宽时间下界:
是一个批步的读取字节数; 字节是完整权重; 字节是每请求缓存; 是同长度请求数; 是假定持续显存带宽。本例自定 字节/秒,即1 TB/s。 是这一批步的秒数, 是聚合 token/秒;单请求每秒前进量则为 。这些式子只在上述完整流式读取假设下使用,并非所有硬件和缓存策略的统一性能下界。
| 量 | ||
|---|---|---|
| 本步读取量 | 15.074 GB | 22.590 GB |
| 这部分理想时间下界 | 15.074 ms | 22.590 ms |
| 聚合生成量的上界 | 66.34 token/s | 354.14 token/s |
| 单请求前进量的上界 | 66.34 token/s | 44.27 token/s |
第一列的15.074来自 ;第二列来自 。八请求读量增加到约1.50倍,但一个批步产生八个 token,因此聚合上界增加约5.34倍。与此同时,每条请求仍要等这一步结束,单请求前进量没有随聚合吞吐一起增加。
这组数字由本页配套的纯算术检查实际生成,不是GPU测得的速度。真实执行还受矩阵计算效率、不同请求长度、排队、kernel、带宽利用率与权重驻留/复用方式影响;实际 batch 增大时,单请求延迟可以有不同变化。这个算例的用途,是防止把“整个服务每秒吐出更多 token”直接改写成“每个人得到的下一 token 更快”。
2.2 FlashAttention 与稀疏注意力

图 10|相同稠密关系的分块,与直接减少注意力关系
左图和中图的蓝色格完全一致,中图橙线只标示执行分块;右图的教学窗口直接省略部分历史关系。先观察关系集合是否变化,再判断优化属于哪一类。
两者常被同时列为长上下文优化,却改变不同对象。FlashAttention 对注意力计算分块,组织片上存储与显存读写,使用在线 softmax 合并结果,避免把完整分数矩阵写回显存。原始稠密版本仍计算允许的全部注意力关系,目标是保持数学上的 exact attention,浮点执行可能有舍入差异。来源:FlashAttention。
稀疏注意力则限制一个 query 读取哪些 key:局部窗口、选定块或检索到的位置。它可以减少计算的关系数量,同时可能丢掉相关信息;效果取决于稀疏模式、训练适配与索引开销。可以对稀疏关系使用高效分块 kernel,两种技术并不互斥。
| 技术 | 改变的核心对象 | 不能自动推出 |
|---|---|---|
| FlashAttention | 同一注意力运算的存储与执行次序 | 所有情况下复杂度都从平方变成线性。 |
| Sparse attention | query 可访问的关系集合 | 省略关系后仍与稠密模型输出等价。 |
| GQA | query 与 K/V 头的共享结构 | 历史长度不再影响缓存。 |
| KV 分页 | 缓存分配和共享 | 每个请求的有效历史被压缩。 |
因此读长上下文模型报告时,应分别找计算图、缓存表示和 kernel,而不是把“更快”都归给一个算子。公开 DeepSeek-V4.1 采用专门的压缩与共享设计,它的缓存不符合上一节的普通 GQA 简式。来源:DeepSeek-V4.1。
3. 候选验证与推理预算
3.1 投机解码与分布保持
投机解码让便宜的 draft 先猜几个 token,再由 target 模型一次前向验证候选位置。候选前缀已经确定,因此 target 能并行计算这些位置的条件分布;接受判断仍沿序列次序进行,遇到拒绝就丢弃后面的草稿。它试图减少昂贵 target 的串行调用次数,而不是让模型跳过正确性定义。来源:Speculative Decoding。
3.1.1 一次验证与实际前进长度

图 11|同时算出候选分布,沿前缀依序接受(原创教学示意)
沿图中四个草稿位置往下看:target 一次前向能够计算四个已给定候选位置的条件分布;底部的绿色勾却必须从左向右推进。若前两个被接受、第三个被拒绝,本轮输出的是两个已接受 token 加一个从残差分布重采样的 token。第四个草稿即使已经完成 target 计算,也不能继续直接输出。
原因在于它的条件前缀。第四个草稿的验证分布以原先的第三个草稿为条件;第三项一旦换成修正后的 token,实际前缀已经改变。把后面的草稿留在输出里,会把“在另一条前缀上算过的概率”冒充当前前缀的概率。因此图右侧把第四项送入弃置槽;下一轮从修正后的真实前缀重新开始。
这个四位置教学例里,draft 花了四项提议成本,target 也验证了四个位置,实际前进却是三项。更长草稿可以在高接受率下摊薄昂贵调用,也可能在较早拒绝时增加无效验证。判断速度需要同时看 draft 成本、接受位置分布、验证规模与系统调度,不能只报告草稿长度。完整算法还要处理全部接受时的额外 token;本图只展示首次拒绝的分支。下文的接受律与残差规则解释为什么这个分支仍保持指定 target 分布。
展开原有草稿验证教学图

图 12|草稿并行验证,接受按前缀推进(原有教学图)
3.1.2 接受质量与残差补偿

图 13|三 token 词表上的接受质量与残差补偿
先比较图中的 q 与 p 长条,再看绿色接受质量和红色拒绝质量;拒绝后的 0.2 质量全部补回第一个 token,最终恢复 target 分布。
对同一个当前前缀,设 target 分布为 ,draft 分布为 ,先从 抽到候选 。一个保持 target 分布的接受与修正规则是:
投机采样:符号、条件与三 token 手算
| 符号 | 含义 |
|---|---|
| 相同前缀下的 target 与 draft 归一化分布。 | |
| 从 draft 分布采样的候选;其 。 | |
| 以均匀随机数判断的候选接受概率。 | |
| 共同词表中的 token 索引。 | |
| ,只保留正的剩余质量。 | |
| 候选被拒绝时使用的归一化残差分布。 |
若 、,三个接受概率为 。被接受且输出相应 token 的质量为 ,合计 。拒绝概率为 ;正残差为 ,因此拒绝后必选第一个 token。最终输出质量为 。
若正残差总和为零,两个分布相同并全部接受,不进入残差采样分支。
“exact”指输出分布与指定 target 采样分布一致。 条件是使用正确的条件概率、接受规则和残差规则;若有温度或截断,概率必须对应实际采用的分布。贪心版本需要另一套与 target 贪心选择一致的处理。把草稿达到某个置信阈值就直接接受,通常不享有上述保证。相关原始方法:Speculative Sampling。
分布相同也不意味着固定随机种子下两条程序逐字一样,因为随机数消耗路径不同。速度收益还依赖 draft 成本、接受率、草稿长度与验证 batch;草稿太贵或很少被接受,可能没有加速。投机解码、知识蒸馏可以合作:训练更合适的 draft,但 target 权重不必改变。
3.2 并行采样、验证与搜索

图 14|生成候选与选中正确候选是两个步骤
图中绿色轨迹已经出现在候选集合里,但最终输出仍取决于右侧的选择。公式先计算“是否出现”,不能替裁判回答“是否选中”。
Best-of-N 生成 条候选,再按裁判选择。多数投票统计答案出现次数;验证器判断答案或步骤质量;搜索则依据中间反馈扩展、修订或放弃分支。三者消耗预算的路径不同,也依赖不同的可靠性假设。
用独立候选成功率 做一个理想演算,至少有一条正确的概率为:
是每条独立候选成功的概率,取 ; 为候选数,取 ; 是全部失败概率。因此 。这是候选集合包含正确答案的概率,不是系统最终选对答案的概率。相同模式的错误会破坏独立假设,裁判也可能误选。
若裁判错误地偏爱冗长、自信或某种表述,更多候选反而扩大被误选的机会。搜索还会让评分误差在多个步骤累积,所以一个好的终值检查器和一个好的过程评分器不能互换。训练数据中的验证任务,也可能与上线时的开放任务存在分布差异。
推理预算更大,不保证每个问题的答案更好。 额外 token 可能用于有效验证,也可能用于重复、错误反思或无必要的工具调用。对简单任务,长轨迹增加费用与延迟;对极难任务,若基础模型几乎无法走到正确区域,单纯多采样也可能无效。需要在固定总成本下比较策略,同时报告正确率、生成量、工具次数和时间。来源:Test-Time Compute Scaling。
4. 精度、产品边界与验证
4.1 数值精度、权重量化与缓存量化
精度选择影响训练稳定性、模型存储和推理执行,却不是同一个开关。训练可以使用 BF16 矩阵计算,同时保留更高精度的优化器状态;部署可以量化权重,同时保持 KV 为 BF16。只有“模型是 4-bit”几个字,无法知道缓存和关键算子用什么精度。
权重通常随模型固定,量化可离线校准;KV 随每个请求生成,长度不断增加,误差会影响后续注意力。SmoothQuant 研究权重和激活量化;KIVI 专门研究 KV 缓存的量化方式,二者对象不同。来源:SmoothQuant,来源:KIVI。

图 15|均匀量化格点与反量化误差(精确教学例)
图上的红点 0.37 被对齐到蓝色格点 0.4。相同位宽也可能采用不同尺度、分组和格式,因此这个过程只展示舍入与误差的基本关系。
一个示意的对称量化步骤为:
是待量化实数; 是量化步长; 四舍五入到整数; 是可表示整数的最大绝对值; 截断到指定区间; 是反量化后的近似值。取 、、,得到 ,误差 。这是自定均匀量化例子,不代表某个实际 INT4 格式的完整实现。
分组尺度、异常值、零点和校准数据会改变误差。位宽变小不必线性提升速度:解包、反量化、kernel 支持和内存访问都要计入。评估也不能只看短文本 perplexity,应覆盖长上下文、工具调用、精确格式和困难推理。不同精度 kernel 的结果通常在容差内比较,不能把它们声称为逐位相同。来源:PyTorch 数值精度说明。
4.2 模型权重、RAG、工具与 Harness
产品展示的能力由多个层次共同产生。模型权重规定条件分布;推理引擎执行张量计算、缓存与调度;RAG 把检索资料送进上下文;工具执行外部程序或查询;harness 组织系统提示、工具协议、任务循环、上下文压缩和停止条件。它们的贡献应分别核对。

图 16|产品任务循环与模型前向的边界(原有教学图)
RAG 可以在权重不变时补充新资料,原始工作把参数记忆与检索到的外部记忆结合;实际系统是否更新 retriever 或生成器,应另看训练设计。来源:Retrieval-Augmented Generation。
同一模型换一个 harness,可能改变完成率,因为工具说明、错误反馈和上下文管理不同。反过来,成功执行一个仓库任务也不能单凭终端日志推出模型训练过同一仓库。比较顶级模型的 agent 榜单,应记录模型版本、框架版本、权限、步数、时间、预算和验证方式,避免把整个系统的成绩当作纯权重能力。
4.3 固定输入验证与性能测量

图 17|附录用固定输入比较三条缓存计算路径
图中三行的输出按同一位置、同一词表对齐,适合比较数值误差;它们没有提供每秒 token 数或任务正确率。
本文代码与内存数字属于机制示例。实验附录已经用 NumPy 验证固定输入下的缓存与无缓存 logits,以及投机采样的概率分布;它没有测量生产吞吐,也没有评测不同思考预算的任务正确率。后两者需要另做真实硬件和任务集实验,统一精度、长度、框架与预算,才能把“用了更多计算”“执行更快”和“答案更可靠”分别量化。
实际排错时,可先固定一条短输入和同一组 token,而不是比较两次随机生成的答案。逐层检查缓存长度是否增加一、位置编号是否衔接、padding 是否仍被屏蔽,再比较 logits 的最大绝对误差。速度实验应先预热,记录输入和输出长度、batch、精度、硬件及计时边界;工具等待应单独统计。这样遇到回归时,才能确定问题来自状态管理、数值执行、采样策略,还是模型外围的任务循环。
正在连接 GitHub 评论…