继续第一篇《Token、Transformer 与自回归生成》的海洋问题。模型已经给出:
海水会吸收较多红光,蓝光更容易被散射回来,所以大海常呈蓝色。
你再追问一句,这段问答就会进入下一轮对话。随着聊天变长,在其他条件相近时,模型生成新内容需要利用的历史信息会增多,保存这些历史信息也会占用更多显存。用户感受到的,可能是第一个字等得更久,也可能是后续文字出得更慢。
这两种感受对应不同的时间段,不能只用"回复慢"概括。本文接着第一篇的真实 token 示例,说明模型怎样复用历史计算,一次请求怎样分成两个阶段,以及缓存为什么会变长。最后用 Qwen3.8-27B 计算缓存大小,并分清几个常用的延迟指标。
一、生成新 token 时,历史内容要重新计算吗
第一篇讲过,模型算到"它"时,会用当前位置的 **Q(查询)**寻找相关信息,用历史位置的 **K(键)**完成匹配,再读取历史位置的 V(值)。例如,"它"需要利用前面"海水"的信息。
现在让回复继续向后生成。第一篇给出的切分中,开头几个 token 依次是:
text
海水 | 会 | 吸收 | 较多
生成"会"时,需要读取提示和"海水"的历史信息;生成"吸收"时,还要利用"会"。如果每走一步都把历史位置的 K/V 重新算一遍,同样的工作就会反复发生。
更合适的做法是:某个位置的 K/V 算好后先保留,下一步直接读取;处理新位置时,再追加它的 K/V。这份不断增长的缓存叫 KV Cache。

图 1:token 顺序沿用第一篇的官方 Tokenizer 实测。处理"海水"并选出"会"时,会保存"海水"的 K/V;后面同理。一块代表该 token 在各全注意力层的 K/V,不表示实际内存布局。
KV Cache 省下的是历史 K/V 的重复计算,代价是显存占用随序列变长而增加。它保存的是注意力后续还要读取的 K/V,不是 Embedding、候选 token 分数,也不是每一层的全部中间结果。
还要区分"缓存复用"和"不再读取历史":缓存避免了重新算出历史 K/V,但生成新 token 时,Attention 仍要读取允许看到的历史 K/V。历史变长后,这部分读取和计算也会变化。
二、一次请求为什么分成 Prefill 和 Decode
你已经完整输入了问题,新回复却还不存在。这个差别决定了一次请求的两个阶段。

图 2:箭头表示处理顺序。Prefill 建立提示的 K/V 并产生第一个新 token;Decode 每次处理上一步选出的 token,追加它的 K/V,再产生下一个 token。
1. Prefill:一起处理已有提示
假设你的问题已经切成若干 token。模型可以一起处理这些已经存在的位置,同时遵守第一篇讲过的读取规则:每个位置只能使用自己和之前的内容,不能提前看到后面的内容。
这个阶段会建立提示的初始 KV Cache,并算出候选 token 的分数,供生成规则选出第一个新 token。图中的第一个新 token 是"海水"。
这种"先一起处理已有提示"的阶段叫 Prefill。它不是重新逐个生成你的问题,也不是只把提示读进内存。
2. Decode:一步一步产生后续内容
第一个新 token 出现后,后续内容仍要逐步生成。处理"海水"后选出"会",处理"会"后选出"吸收",每一步都读取已有缓存,并追加当前处理位置的 K/V。
这个阶段叫 Decode。它每次只处理一个新位置。在资源独占且实现简单的场景中,各步耗时可能接近;缓存变长和资源竞争会让间隔发生变化,具体要看实际测量。
Prefill 和 Decode 使用同一套模型权重。把它们分开,是为了说清"第一个 token 前做了什么"和"后续 token 怎样产生",不是换成了两个模型。
把两个阶段放在一起比较:
| 对比项 | Prefill | Decode |
|---|---|---|
| 当前已有的内容 | 用户输入的完整提示 | 提示加上已经生成的回复 |
| 一步处理的位置 | 可以一起处理多个提示位置 | 每次处理一个新位置 |
| 缓存怎样变化 | 建立提示的初始 K/V | 每次追加当前位置的 K/V |
| 产生什么 | 第一个新 token | 后续 token |
| 主要影响哪段体验 | 首个 token 前的等待 | 首个 token 之后的出字过程 |
还要避免两个常见误解:
- Prefill 不是"没有生成":它除了处理提示,也会给出第一个新 token 所需的分数。
- Decode 不是"完全不处理历史":它避免重新计算历史 K/V,却仍然要读取缓存。缓存越长,需要处理的历史关系也可能越多。
三、KV Cache 会占用多少显存
先看一个全注意力层要保存什么。模型里还有其他类型的层,下文会说明为什么这里只算这一种。每个历史位置都有 K 和 V 两份数值;模型有多少个全注意力层,就要在这些层分别保存。可以把每个位置的 K 和 V 分别想成若干组数值:比如分成 4 组,每组 256 个数。每组占多少空间,还取决于每个数用多少字节表示。
因此,一条序列的全注意力 KV Cache 可以这样计算:
text
2(K 和 V)
× 全注意力层数
× KV 头数
× 每头维度
× 每个元素的字节数
"KV 头数"和"每头维度"就是这里的组数和每组数值个数。第一篇只看一组关系方便理解,实际模型可以同时进行多组注意力计算;这里只需要知道,4 个头意味着有 4 组并行的匹配和读取,不需要展开矩阵细节。
用 Qwen3.8-27B 代入一次
Qwen3.8-27B 是 27B 稠密模型(dense)。"27B"表示约 270 亿参数;"稠密"表示每次计算都会使用这套模型的全部参数,而不是只启用其中一部分参数。
它有 64 层,其中只有 16 个全注意力层需要保存这里计算的标准 K/V。其余层使用线性注意力,也就是另一种计算历史关系的方式;本文不展开它的机制。线性注意力层保存的是另一种状态,不能套用上面的全注意力 K/V 公式。没有对应结构和实现资料时,也不能把这部分大小随意写成零或固定值。
BF16(bfloat16)是一种 16 位浮点数格式,每个数值占 2 字节。本例使用的配置为:
text
全注意力层数:16
KV 头数:4
每头维度:256
BF16:每个元素 2 字节
代入后,一条序列中每个 token 的全注意力 KV 大小是:
text
2 × 16 × 4 × 256 × 2 字节
= 65536 字节
= 64KiB
这里的 KiB 按 1024 字节计算,所以 65536 ÷ 1024 = 64。继续按序列长度计算:
| 一条序列的长度 | 仅全注意力 KV Cache【推算】 |
|---|---|
| 4096 token | 64KiB × 4096 = 256MiB |
| 262144 token | 64KiB × 262144 = 16GiB |
按这套配置推算,一条达到 262144 token 的序列,仅 16 个全注意力层的 K/V 就需要 16GiB。这还不是整个请求的显存需求。
多条请求同时处理时,要按每条序列的实际长度分别计算再相加。例如:
- 4 条 4096 token 序列:
4 × 256MiB = 1GiB; - 一条 8192 token 序列和一条 2048 token 序列:
512MiB + 128MiB = 640MiB。
!四条相同序列与两条不同长度序列的全注意力 KV 计算(
)
图 3:多请求缓存的加法。每条序列先按实际长度计算,再相加;不包含其他显存占用。
这里仍然只算全注意力 K/V,不是把这些序列的全部显存加总。**这个计算明确不包含线性注意力状态和实现开销。**模型权重、计算过程中的临时数据、缓存管理信息和运行时预留也都不在里面,不能把这些数字直接当成整张 GPU 的容量结论。
公式中的每一项也都有明确来源:2 表示 K 和 V 两份数据;层数表示每层都要保存;头数乘每头维度表示每层的 K 或 V 包含多少数值;最后的 2 字节表示 BF16 中每个数值的大小。改变精度、层结构或头配置,结果都会变化。
四、第一个字慢和后续出字慢,要分开测量
要把这些等待分清楚,可以看一次请求从到达到最后一个 token 发出的时间线。
!TTFT、ITL、TPOT 与端到端延迟分别覆盖的时间段(
)
图 4:服务端计时示意,各段宽度不代表真实耗时。TTFT 覆盖首个 token 前的全部等待,组成见下文;ITL 是一个相邻间隔,TPOT 是首个 token 之后这些间隔的平均值。
1. TTFT:看到第一个 token 前等了多久
从服务端收到请求开始计时,到第一个输出 token 发出,这段时间叫 TTFT(Time To First Token,首 token 时间)。
它通常包括:
- 请求排队;
- 调度等待;
- Prefill 计算;
- 第一个 token 的输出处理。
所以 TTFT 不等于 Prefill 时间。即使 Prefill 已经算完,只要请求此前排过队,那段等待也仍在 TTFT 里。若从用户点击发送开始计时,还要考虑网络传输等客户端到服务端之外的时间,比较数据时要先说明计时边界。
2. ITL:相邻两个 token 隔了多久
第一个 token 发出后,每相邻两个输出 token 之间都有一个时间间隔,这个间隔叫 ITL(Inter-Token Latency,相邻 token 延迟)。
"会"比"海水"晚多久发出,是一个 ITL;"吸收"比"会"晚多久发出,是另一个 ITL。它们可能接近,也可能因为中间一次停顿而相差很大。
3. TPOT:后续 token 平均每个用了多久
把首个 token 之后的所有相邻间隔平均起来,可以得到 TPOT(Time Per Output Token,平均每个输出 token 的时间)。
若一次请求共有 N 个输出 token,第 1 个 token 的发出时刻为 T₁,最后一个为 Tₙ,本文采用的计算方式是:
text
TPOT = (Tₙ - T₁) / (N - 1)
其中 N > 1
分母是 N - 1,因为 N 个 token 之间只有 N - 1 个后续间隔;第一个 token 之前的等待已经归入 TTFT。只有一个输出 token 时,这个公式没有可平均的后续间隔,所以不能套用。
TPOT 是平均值,ITL 是单个间隔。平均 TPOT 相同,不代表每次 ITL 都平稳,因此排查卡顿时还要看各个间隔,不能只保留平均值。不同资料对 TPOT 的计时边界可能略有差异,对比前也要核对定义。
4. 端到端延迟:整次请求用了多久
从请求到达到最后一个输出 token 发出,是服务端口径的端到端延迟。它包含 TTFT,也包含之后全部 token 的生成间隔;如果按用户侧体验测量,还要包含网络传输和界面显示等时间。
端到端延迟告诉你"总共等了多久",TTFT 告诉你"多久开始看到回复",ITL 和 TPOT 告诉你"开始之后出字是否连续"。长提示、排队、长输出可能分别影响不同部分,只看一个总时长,无法判断等待发生在哪一段。
5. 用一组构造时间把指标算清楚
下面这组时间只用于演示计算,不是实测数据。假设服务端在 0ms 收到请求,随后 4 个输出 token 的发出时刻分别是:
text
第 1 个:300ms
第 2 个:360ms
第 3 个:420ms
第 4 个:500ms
那么:
| 指标 | 计算 | 结果 |
|---|---|---|
| TTFT | 300 - 0 |
300ms |
| 第 1 个 ITL | 360 - 300 |
60ms |
| 第 2 个 ITL | 420 - 360 |
60ms |
| 第 3 个 ITL | 500 - 420 |
80ms |
| TPOT | (500 - 300) ÷ (4 - 1) |
约 66.7ms |
| 服务端端到端延迟 | 500 - 0 |
500ms |
!用 0、300、360、420、500 毫秒计算 TTFT、ITL、TPOT 和端到端延迟(
)
图 5:同一组构造时间对应的四个指标。数值只用于演示计算,不是实测结果。
这个例子也能看出,TPOT 约为 66.7ms,但最后一个 ITL 是 80ms,平均值不会告诉你哪一次间隔更长。若用户从点击发送到界面显示来计算,还要另加网络和渲染时间,不能把用户侧总时长与服务端时间直接混用。
五、把生成过程、缓存和延迟连起来
现在再看"服务慢"这个描述,就能提出更具体的问题:
| 观察到的现象 | 应该先检查什么 | 本文提供的解释方向 |
|---|---|---|
| 提示很长,首个 token 等得久 | TTFT、Prefill、排队和调度 | 已有提示较多,首个 token 前还有排队和计算 |
| 回复越长,后续出字变慢 | ITL、TPOT、缓存长度和资源竞争 | Decode 逐步进行,历史缓存也会增加 |
| 多条长对话同时进行 | 各序列 K/V 和运行时显存 | 每条序列的缓存要分别计算再相加 |
| 平均 TPOT 接近,但用户仍感觉卡顿 | 各个 ITL,而不只是平均值 | 平均值可能掩盖个别长间隔 |
| 首个 token 很快,整段回复仍很久 | 输出长度和后续各间隔 | TTFT 短不代表端到端延迟短 |
这些指标还不能直接告诉你瓶颈究竟是带宽、算力、显存容量还是调度。要回答那一步,需要继续区分硬件容量、数据读取量和计算量,这是第三篇的主题。
小结
| 概念 | 本文要记住的内容 |
|---|---|
| KV Cache | 保存历史 K/V,避免重复计算;序列变长时会占用更多显存 |
| Prefill | 一起处理已有提示,建立初始缓存,产生第一个新 token |
| Decode | 每次处理一个新位置,读取并追加缓存,产生后续 token |
| TTFT | 首个 token 前的等待,包含排队、调度、Prefill 和首 token 处理 |
| ITL / TPOT | 前者是单个相邻间隔,后者是首个 token 之后各间隔的平均值 |
| 端到端延迟 | 从请求到达到最后一个 token 发出的完整时间 |
Qwen3.8-27B 的例子还说明,缓存计算必须看具体模型结构:64 层中只有 16 个全注意力层参与这次标准 K/V 计算;每条序列每 token 是 64KiB ,4096 token 是 256MiB ,262144 token 是 16GiB,而且不含线性注意力状态和实现开销。
参考
- Qwen3.8-27B 官方模型卡与模型配置:模型规模、层数、全注意力层数、KV 头数、头维度和精度。
- Attention Is All You Need:Q/K/V 与注意力的基础机制。
- Efficient Memory Management for Large Language Model Serving with PagedAttention:KV Cache 随请求动态增长及显存管理背景。