LLM 推理服务深度解析:Prefill、Decode、KV Cache 机制
深入讲解 LLM 推理的完整流程,重点阐述 prefill 和 decode 两个核心阶段的工作原理及其性能影响。
完整中文译文
你好,我是 Shrijith Venkatramana。我正在开发 git-lrc,这是一款会在每次 commit 时运行的 AI 代码审查工具。欢迎为我们点 Star,帮助更多开发者发现这个项目。也请试用并分享反馈,帮助我们改进产品。
有一群 LLM inference 爱好者,经常讨论 LLM serving。
人们会提到 prefill、decode、KV cache、continuous batching 和 paged attention 等术语。读了几篇文章后,我已经能认出这些词,却仍然回答不了一个最基本的问题:
向 LLM 发送 prompt 后,究竟发生了什么?
作为软件工程师,我们通常会按照事件发生的顺序来理解系统:Web 请求抵达服务器,middleware 开始运行,业务逻辑执行,最后返回响应。
我也希望用同样的思维模型理解 LLM。
本文将介绍这段探索之旅的第二部分。
1. 请求开始
简化后的请求流程如下:
Client
↓
Load Balancer
↓
LLM Server
↓
Tokenizer
↓
Prefill
↓
Decode
↓
Streaming Response
其中有两个重要阶段:
一旦理解了这两个概念,后面的许多主题也就顺理成章了。
假设 prompt 是:
像给十岁孩子讲解一样解释量子计算。
我的第一个问题是:
模型会立即开始生成响应吗?
答案比我预想的更简单。
模型首先会处理完整的 prompt。
这个阶段称为 prefill。
一个实用的思维模型是:
kvCache, firstToken := Prefill(prompt)
Prefill 会完成两项任务:
构建初始 KV cache。
生成第一个输出 token。
此时,模型已经掌握了继续生成响应所需的全部信息。
3. Decode 是一个简单循环
Prefill 完成后,响应的其余部分来自一项不断重复的操作。
cache, token := Prefill(prompt)
for token != EOS {
emit(token)
cache, token = Decode(cache, token)
}
每次调用 Decode() 都会执行三个动作:
生成下一个 token。
返回新的 cache。
这个循环会持续执行,直到模型生成 end-of-sequence token。
正是在这里,我第一次感觉 LLM inference 不再是一个黑盒,而是变得像普通软件一样容易理解。
4. 什么是 KV Cache?
这个名字一度让我以为,它是类似 Redis 或 Go map 的 cache。
实际上,它的概念要简单得多。
KV cache 是存储在 GPU 内存中的数据。
在 prefill 阶段,模型会为 prompt 中的每个 token 计算内部数据,并将其保存下来。
在 decode 阶段,模型会为每个新生成的 token 添加一条新记录。
可以用下面这个简单的思维模型来理解:
type KVCache struct {
Layer0 [...]
Layer1 [...]
Layer2 [...]
...
}
在整个对话过程中,cache 会持续增长。
它的用途很直接:
保存计算成本高昂的结果,让后续 token 能够复用。
5. 为什么模型不会重新处理 Prompt?
假设一个 prompt 包含 2,000 个 token。
再假设模型生成了 500 个输出 token。
一种做法可能是:
读取这 2,000 个 prompt token。
再次读取同样的 2,000 个 prompt token。
生成下一个 token。
这会重复执行大量计算。
KV cache 改变了这一过程。
模型只会在 prefill 阶段读取一次 prompt。
到了 decode 阶段,它会复用 cache 中的信息,并为每个新生成的 token 添加相应数据。
计算成本高昂的工作只需执行一次。
6. 等等……KV Cache 最终会有多大?
这是我接下来想到的问题。
如果 prompt 中的每个 token 都会向 cache 添加数据……
那么,包含一百万个 token 的 prompt 会怎样?
根据前面的思维模型,答案显而易见。
每增加一个 token,cache 都会随之增长。
粗略估算一下,对于一个 7B 模型,每个 token 的内存占用大约会达到数百 KB。
于是就会得到这样的数字:
现代模型会使用 Grouped Query Attention(GQA)等技术减少这部分内存占用。多个 query head 共享相同的 Keys 和 Values,因此 cache 可以变得小得多。
关键点其实很简单。
长 context window 需要消耗大量内存,因为 KV cache 会不断增长。
这也解释了为什么现代 serving 系统要投入如此多的精力管理 KV cache。
7. 一个简单的思维模型
理清这些概念后,我最终形成了一幅一直记在脑海里的图景。
Prompt
↓
Prefill(prompt)
↓
KV Cache + First Token
↓
Decode(...)
↓
Next Token
↓
Decode(...)
↓
Next Token
↓
...
这个模型可以回答许多基本问题。
为什么模型必须先读取 prompt,然后才能生成文本?
为什么 GPU 内存占用会随着 context length 增加?
为什么每个 serving framework 都在讨论 KV cache?
为什么 serving 优化都集中在 prefill 和 decode 上?
关于 attention、batching、scheduling 和 parallelism,我仍然有很多疑问。
但现在,当我学习这些概念时,已经有了一个可以把它们串联起来的框架。
LLM inference 中,哪个部分让你花了最长时间才真正理解?是 attention、KV cache、batching,还是其他概念?
*AI Agent 编写代码的速度很快。但它们也会悄无声息地删除逻辑、改变行为并引入 bug,而且不会告诉你。你往往要等到生产环境出问题时才会发现。
git-lrc 可以解决这个问题。它会接入 git commit,在每个 diff 落地之前进行审查。只需 60 秒即可完成设置,而且完全免费。*
欢迎提供任何反馈,也欢迎贡献者加入!它已经上线、source-available,任何人都可以直接使用。
在 Git Commit 时运行的免费微型 AI 代码审查
| 🇩🇰 丹麦语 | 🇪🇸 西班牙语 | 🇮🇷 波斯语 | 🇫🇮 芬兰语 | 🇯🇵 日语 | 🇳🇴 挪威语 | 🇵🇹 葡萄牙语 | 🇷🇺 俄语 | 🇦🇱 阿尔巴尼亚语 | 🇨🇳 中文 | 🇮🇳 印地语 |
在 Commit 时运行的免费微型 AI 代码审查

如今的 GenAI 就像一辆没有刹车的赛车。它加速迅猛——你只需描述需求,大段代码便会瞬间出现。但 AI Agent 也会悄无声息地破坏系统:删除逻辑、放宽约束、引入成本高昂的云服务调用、泄露凭据,甚至改变行为,而且不会告诉你。你往往要等到生产环境出问题时才会发现。
git-lrc 就是你的制动系统。它会接入 git commit,在每个 diff 落地之前运行 AI 审查。只需 60 秒即可完成设置,而且完全免费。
简而言之,git-lrc 可以帮助你在事故发生之前,预防服务中断、安全漏洞和技术债。
快速概览:10 类风险 · 跟踪 100 多种故障模式 · 覆盖每一次 commit……
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。