大模型的首字为什么总是最慢?

下次把长文档发给大模型时,留意一个不算稀奇但很少被解释的体验:第一个字"打"出来要等好几秒,等它出现之后,后面的字反而越吐越快。这不是网络抖了,是模型本身两阶段的分工造成的:prefill 和 decode。

专属插画
大模型的首字为什么总是最慢?

大模型的首字为什么总是最慢?

下次把长文档发给大模型时,留意一个不算稀奇但很少被解释的体验:第一个字"打"出来要等好几秒,等它出现之后,后面的字反而越吐越快。这不是网络抖了,是模型本身两阶段的分工造成的:prefill 和 decode。

两阶段:先算完提示词,再一字一字生成

模型收到你的输入,先走 prefill。提示词的所有 token 并行进入网络,逐层算完注意力,一次性产出"生成第一个字所需的状态"。这一步是"把很多事同时做",总算力大,但墙钟时间不长。

之后进入 decode。生成第二个字必须依赖第一个字的结果,第三个又依赖第二个,只能一个字一个字往前推。像写数学证明,每一步要等上一步的结论成立,再多加机器也并行不了。

两个阶段的瓶颈不一样。prefill 卡在算力上,负载重,GPU 忙着做矩阵乘法;decode 卡在内存带宽上。每步只新算一个 token,量很小,但每步都要把模型权重和之前所有 token 的缓存从显存搬一遍。32K 上下文下,decode 的单步开销比 prefill 平均到每个 token 的开销高出一个量级。

KV cache:不重算,靠缓存

如果没有缓存,每生成一个新字都要重新算它和前面所有字的注意力,生成 n 个字大约要 n×(n-1)/2 次成对计算,直接二次爆炸。所以每个 Transformer 都有一层缓存,叫 KV cache:每个 token 在每层算出的 key 和 value 存下来,生成下一字时只算新 token 的 K/V,旧的照查。

代价是显存随上下文线性膨胀。拿一个假想的 8B 模型算给你看:32 层、每层 8 个 KV 头、每头向量 128 维、fp16 存储。单个 token 每层存 2(K+V)×8×128×2 = 4KB,32 层下来每 token 128KB。32K 上下文的 KV cache 大约 4GB。这就是"长上下文吃显存"的直接来源,也是 GQA、MQA 这类技术先动手砍它的方向——KV 头从 32 砍到 8,缓存就省四分之三。

搞懂之后能落地的三件事

一、TTFT 和 TPOT 是两个指标。首字延迟(TTFT)反映 prefill 快慢,字间延迟(TPOT)反映 decode。有人抱怨"回答慢",先分清是哪种:提示词很长的场景多卡在 TTFT,输出很长的场景多卡在 TPOT。混在一起谈"延迟高",优化方向会跑偏。

二、稳定的内容放前面。多个请求共享的前缀,位置越靠前,KV cache 命中直接复用的概率越大。所以 system prompt、工具说明、few-shot 示例放在提示词前段,用户这句每次都变的话放最后,是有工程含义的取舍,不是排版习惯。

三、别把上下文塞满。不是模型记不住,是查找太贵:每生成一个字,它要对前面所有 token 的 KV 做一次查找。塞 10 万字进去、九十万字和问题无关,等于每个字的延迟里都含着一笔十万吨的搬运费。正确做法是先检索再喂:向量库拿候选段,重排模型留 top-k,再进模型这一段该花多少 token 就花多少。

晚上再开一局长文档问答时,看着那个孤零零的第一个字慢慢冒出来,你等的不只是它一个字的思考——是 GPU 把你的提示词整篇并行消化完、把几 GB 缓存写进显存,然后才开始动笔。

留言区

欢迎分享你的想法!

发表留言

0/500

加载留言中…