1.2 Prefill 与 Decode 的两段式成本


文档摘要

1.2 Prefill 与 Decode 的两段式成本 本节摘要:上一节的账单显示,付费大头是每轮重复计费的输入 token;本节潜入推理流水线,看这些 token 是怎么被算的。推理分两段:Prefill 一次并行处理全部输入,计算密集、GPU 利用率高;Decode 逐 token 生成,访存密集、利用率常不足一成。两段性格迥异,共同指向一个结论——多轮对话中最大的浪费源是重复 Prefill,这也是下一节四层缓存版图要回收的头号目标。 Prefill 与 Decode 各自在算什么 上一节的表格显示,第 8 轮要为近 10 万 token 的输入付费。这 10 万 token 在推理引擎内部是如何被处理的?答案是把推理拆成两个阶段,而两阶段的计算性格截然不同。

1.2 Prefill 与 Decode 的两段式成本

本节摘要:上一节的账单显示,付费大头是每轮重复计费的输入 token;本节潜入推理流水线,看这些 token 是怎么被算的。推理分两段:Prefill 一次并行处理全部输入,计算密集、GPU 利用率高;Decode 逐 token 生成,访存密集、利用率常不足一成。两段性格迥异,共同指向一个结论——多轮对话中最大的浪费源是重复 Prefill,这也是下一节四层缓存版图要回收的头号目标。

Prefill 与 Decode 各自在算什么

上一节的表格显示,第 8 轮要为近 10 万 token 的输入付费。这 10 万 token 在推理引擎内部是如何被处理的?答案是把推理拆成两个阶段,而两阶段的计算性格截然不同。

Prefill 阶段处理完整输入序列:所有 token 的注意力与前馈计算组织成一次并行前向,落到 GPU 上就是大规模矩阵乘法,数千个计算核心同时开工。Prefill 结束时产出两样东西——第一个生成 token,以及全部输入 token 的 KV 张量(第 2 章的主角)。用户感知的"首 token 延迟"主要由这一段决定。

Decode 阶段则慢工出细活:每一步只生成一个 token。生成第 n 个 token,只需要用它前一个 token 的查询向量与全部历史的 KV 做注意力,再过一遍前馈网络。计算量骤降到每步约两倍参数量的浮点数,但每一步都要把几十 GB 的权重从显存完整读一遍——算得少、搬得多,瓶颈从算力转到了显存带宽。

一组数字看懂两段的性格差异

以 7B 参数模型、FP16 精度为例(权重约 14 GB),输入 2000 token、生成 500 token:

Prefill 的浮点计算量约为 2 乘以 70 亿再乘以 2000,约 28 万亿次;在 312 万亿次每秒(FP16)的加速卡上,理论耗时约 90 毫秒,计入 40% 到 50% 的实际利用率后约 200 毫秒。这段时间里 GPU 做的是密集矩阵乘,利用率高,每一分算力都在干活。

Decode 每步的计算量仅约 140 亿次浮点,理论上不到 0.1 毫秒;但每步必须读取 14 GB 权重,外加约 1 GB 的 KV 张量,按 2 TB/s 的显存带宽算就要 7 到 8 毫秒。算力利用率不足 1%——GPU 大部分时间在等数据到位,而不是在算。

把这次请求的两本账并排放在一起:

项目 Prefill(输入 2000) Decode(输出 500)
浮点计算量 约 28 万亿次 约 7 万亿次(分摊到 500 步)
每步读取显存 权重读 1 次,14 GB 每步 14 GB 权重加约 1 GB KV
受限资源 算力:28 万亿除以 312 万亿每秒,理论 90 毫秒 带宽:15 GB 除以 2 TB/s,每步约 7.5 毫秒
实际耗时 约 200 毫秒(计入利用率) 约 3.7 秒(500 步乘 7.5 毫秒)
GPU 利用率 40%–70% 不足 1%

同一个请求,Prefill 两百毫秒干完重活,Decode 花了它十几倍的时间在搬数据。两笔账的结构差别更大:Prefill 的耗时随输入长度近似线性增长(注意力部分还是平方增长——输入翻倍,注意力计算量翻四倍,线性层翻倍);Decode 的单步耗时却几乎与已生成长度无关,只与模型大小和带宽有关。这就是"长输入贵在 Prefill、长输出贵在时间"的物理来源。

同一个模型、同一块卡,两段的瓶颈完全不同:Prefill 吃算力,Decode 吃带宽。这也解释了 batching 策略的不对称性。多个请求拼成一个批次做 Decode,几十个请求共用一次权重读取,吞吐量成倍提升而算力几乎没多用;Prefill 本来就计算密集,批次越大越挤占算力,所以成熟框架会把两段拆开调度、分别配额,甚至部署到不同的机器上。现代推理系统普遍采用的连续 batching 把这一思路推到极致:Decode 批次的组成每一步都动态调整,先完成的请求立刻退出、新请求立刻插入,GPU 始终保持满批运转——这些调度技术的共同前提,正是 1.1 节结尾留下的那个事实:Prefill 可以被缓存免掉,而 Decode 每一步都必须算。

用一段示意伪代码把两段的关系钉死:

# 两段式推理主循环(示意伪代码) def generate(prompt_tokens, max_new_tokens): kv_cache, first_token = prefill(prompt_tokens) # 阶段一:并行前向 outputs = [first_token] # 一次算完全部输入 for step in range(max_new_tokens): # 阶段二:逐 token 循环 logits = decode_step(outputs[-1], kv_cache) # 每步读全部权重与 KV next_token = sample(logits) kv_cache.append(next_token) # 新 token 的 KV 追加进缓存 outputs.append(next_token) return outputs

注意循环里那一行追加操作:每生成一个 token,它的 KV 就进入缓存,供后续所有步复用——单次请求内的复用由此建立。而跨请求的复用,也就是"上一轮算过的前缀,这一轮别再算",伪代码里完全没有体现:默认行为是每次调用都从零开始 Prefill,这正是浪费的入口。

两段成本对照表

维度 Prefill Decode
处理对象 全部输入 token,一次并行前向 每步仅 1 个新 token
计算形态 大规模矩阵乘,可充分并行 向量级小计算,步间串行依赖
算术强度 高:每字节权重参与大量运算 低:每字节权重读出后只用一次
瓶颈资源 算力(TFLOPS) 显存带宽(TB/s)
GPU 利用率 通常 40%–70%,调优后可上 85% 常低于 10%
用户感知 首 token 延迟 每 token 生成速度
batching 影响 批次过大挤占算力,需分块调度 批次越大吞吐越高,收益显著
多轮对话中的表现 重复出现:不变的前缀每轮重算 增量出现:只算新增 token

图 1-2:Prefill 与 Decode 两段式流水线成本对比图

图 1-2:Prefill 与 Decode 两段式流水线成本对比图

为什么重复 Prefill 是最大的浪费源

回到 1.1 的膨胀表。第 8 轮输入 99840 token,其中 99760 个(系统提示词加历史)在第 1 到第 7 轮已经全部 Prefill 过。按前文每 2000 token 约 200 毫秒的量级估算,这一次 Prefill 要烧掉近 10 秒的纯算力时间,而这段时间产出的信息增量为零——结果在上一轮的显存里已经存在过,只是随着请求结束被丢弃了。

浪费是双重的。计费上,这些 token 每轮按全价重付,1.1 节已经算过账;体验上,首 token 延迟随会话变长线性恶化,8 轮会话的末轮可能要等几秒才吐出第一个字。而且两项浪费会同向放大:会话越长,重复 Prefill 的 token 越多,用户等待越久,付费越高,三者共享同一个增长曲线。把两重浪费合起来看,结论很清晰:谁能让"算过的前缀"活过请求边界,谁就能同时省下钱和延迟。这正是缓存的经济动机,也是四层版图存在的理由。

案例复盘:高峰期首 token 延迟排查

背景:某文档助手产品在下午高峰期收到投诉,长会话用户的首 token 延迟从 300 毫秒涨到 2 秒以上。团队最初的怀疑对象是高峰期请求排队。
操作:拉取请求明细,按会话轮次拆分首 token 延迟,并对照每轮的输入长度。数据呈现出清晰的规律:同一条会话内,延迟随轮次单调上升——第 1 轮约 300 毫秒,第 6 轮约 2.1 秒,且延迟与输入 token 数近似成正比;而 Decode 侧的每 token 生成速度全程稳定在 30 毫秒左右,完全没有随轮次变化。
结果:定位结论是延迟增量全部来自 Prefill——每一轮都对全部历史重新做并行前向,第 6 轮要处理近 2.5 万 token 的输入。高峰期 GPU 饱和,Prefill 请求排队进一步放大了这一项,但它只是放大器,不是根因。
解读:首 token 延迟的主体是 Prefill 耗时,而多轮会话的 Prefill 绝大部分在算"没变过的内容"。找到根因后,缓解手段的清单其实很短:要么少算(压缩历史),要么错峰算(调度隔离),要么算一次用多次(前缀缓存)。三条路的成本收益差别很大,值得放在一起掂量。
变式:如果暂时用不上缓存,工程上有两个缓解口。一是控制会话轮数或对历史做摘要压缩,把第 6 轮输入从 2.5 万压到 8000,Prefill 时间近似按比例下降,但摘要本身也要花一次推理,且压缩损失细节;二是把 Prefill 与 Decode 分开调度(分块 Prefill、两段隔离部署),避免长 Prefill 阻塞其他请求的 Decode,这只改善公平性,总计算量一点没少。从复用经济学的视角看,这两个变式都是"少算"或"错峰算",只有前缀缓存是"算一次、收多次"。具体怎么收、在哪一层收,正是下一节四层版图要系统回答的问题。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U