LLM 推理与架构优化入门¶
这篇用新手能读下去的方式,解释几个经常一起出现的词:
- KV Cache
- Prefix Cache
- MHA / MQA / GQA
- MoE
- FlashAttention
- PagedAttention
- Speculative Decoding
- Quantization
- Batching
先记住一句话:
大模型优化的核心,不是让模型“少理解”,而是尽量少做重复计算、少搬数据、少占显存。
先从一次生成说起¶
假设你问模型:
请解释 Transformer。
模型不是一次吐出完整答案。
它通常是一个 token 一个 token 地生成:
第 1 步:生成 “Transformer”
第 2 步:生成 “是”
第 3 步:生成 “一种”
第 4 步:生成 “神经”
...
每生成一个新 token,模型都要看前面的上下文。
如果完全不优化,生成第 100 个 token 时,模型可能又把前 99 个 token 全部重新算一遍。
这会很浪费。
于是就有了各种 cache 和推理优化。
一次请求的生命周期¶
一个线上 LLM 请求大致会经过:
HTTP 请求
↓
排队和 batching
↓
prompt / chat template 渲染
↓
tokenizer
↓
prefill:一次性读完整个输入
↓
建立 KV Cache
↓
decode:逐 token 生成
↓
sampler:temperature / top_p 生效
↓
HTTP JSON 或 stream 返回
这些优化发生的位置不同:
| 优化 | 主要发生位置 | 解决什么 |
|---|---|---|
| Prefix Cache | prefill 前后 | 相同前缀少重复算 |
| KV Cache | prefill 后、decode 时 | 历史 K/V 不重复算 |
| Batching | 请求调度层 | 提高 GPU 利用率 |
| PagedAttention | KV Cache 管理层 | 降低显存碎片和浪费 |
| FlashAttention | attention 计算层 | 减少显存读写 |
| Speculative Decoding | decode 阶段 | 降低逐 token 等待 |
两条主线¶
这些概念可以分成两条主线。
第一条是模型结构:
MHA / MQA / GQA / MoE
它们更像是在回答:
模型内部怎么设计,才能在能力和成本之间取平衡?
第二条是推理系统:
KV Cache / Prefix Cache / FlashAttention / PagedAttention / Batching / Speculative Decoding / Quantization
它们更像是在回答:
模型已经训练好了,线上怎么让它跑得更快、更便宜、更稳定?
部署常用指标¶
看推理服务时,不要只说“快”或“慢”。
常见指标有:
| 指标 | 含义 | 主要受什么影响 |
|---|---|---|
| TTFT | time to first token,首 token 延迟 | 输入长度、prefill、排队、prefix cache |
| TPOT | time per output token,每个输出 token 耗时 | decode、KV Cache、batch、模型大小 |
| Latency | 总延迟 | 输入长度、输出长度、排队、采样 |
| Throughput | 吞吐,单位时间处理多少 token 或请求 | batching、GPU 利用率、模型结构 |
| Concurrency | 并发能力 | 显存、KV Cache、调度策略 |
比如用户说“首字很慢”,优先看 TTFT 和 prefill。
用户说“已经开始输出了但很慢”,优先看 TPOT 和 decode。
KV Cache¶
KV Cache 是大模型推理里最重要的优化之一。
先回忆 Attention 里的 Q、K、V:
Q:当前 token 想找什么?
K:历史 token 能提供什么线索?
V:历史 token 能贡献什么内容?
生成文本时,新 token 会不断出现。
例子:
输入:小明喜欢
生成:吃
再生成:苹果
当模型生成“苹果”时,前面的 token 已经有:
小明 喜欢 吃
这些历史 token 的 K 和 V 其实已经算过了。
如果每一步都重新计算它们,就会浪费。
KV Cache 的做法是:
把历史 token 的 K 和 V 存起来,下次生成新 token 时直接复用。
可以理解成:
第 1 步:算出 “小明” 的 K/V,存起来
第 2 步:算出 “喜欢” 的 K/V,存起来
第 3 步:算出 “吃” 的 K/V,存起来
第 4 步:生成 “苹果” 时,直接读取前面存好的 K/V
KV Cache 解决什么问题¶
它主要解决:
- 重复计算太多。
- 生成越长越慢。
- 每个新 token 都要重新处理历史上下文。
有了 KV Cache 后,模型生成下一个 token 时,不需要把所有历史 token 的 K/V 再算一遍。
KV Cache 的代价¶
KV Cache 会占显存。
上下文越长,缓存越大。
用户越多,缓存越多。
所以线上服务经常不是算力先不够,而是显存先紧张。
可以这样理解:
KV Cache 用显存换速度。
Prefix Cache¶
Prefix Cache 可以看成 KV Cache 的进一步复用。
KV Cache 通常复用同一次对话里的历史 token。
Prefix Cache 复用的是:
多个请求中相同开头部分的计算结果。
例子:
很多 Agent 请求都会带相同的系统提示词:
你是一个代码助手。
你需要遵守工具调用规范。
你需要先阅读项目文件。
...
用户问题:请修改登录逻辑。
另一个请求可能是:
你是一个代码助手。
你需要遵守工具调用规范。
你需要先阅读项目文件。
...
用户问题:请解释这个报错。
前面这一大段是一样的。
如果每个请求都重新计算这段系统提示词,就很浪费。
Prefix Cache 的做法是:
相同前缀只算一次,后续请求命中这个前缀时直接复用缓存。
Prefix Cache 和上下文工程的关系¶
Agent 产品里经常有稳定前缀:
- system prompt
- developer prompt
- 工具说明
- 安全规则
- 输出格式要求
- 项目规范
如果这些内容每次顺序都变、文本都变,就很难命中 Prefix Cache。
所以从产品设计角度看:
稳定、分层、少改动的 prompt 结构,更容易利用 Prefix Cache。
例子:
不利于 Prefix Cache:
每次动态拼一个大 prompt,工具说明顺序随机,系统规则也夹在中间。
更利于 Prefix Cache:
固定系统规则
固定工具说明
固定项目规范
动态用户问题
动态检索结果
这也是上下文工程的一部分。
MHA¶
MHA 是 Multi-Head Attention,多头注意力。
在入门篇里我们说过:
一个 head 像一个观察角度,多个 head 可以同时看不同关系。
例子:
小明把苹果放进书包,因为它很甜。
不同 head 可能分别关注:
- 主语是谁。
- 动作是什么。
- “它”指代谁。
- “甜”修饰哪个名词。
MHA 的好处是表达能力强。
但它也有代价:
每个 attention head 都有自己的 K/V,推理时 KV Cache 会变大。
这就是为什么后来出现了 MQA 和 GQA。
MQA¶
MQA 是 Multi-Query Attention。
它的思路是:
Q 还是多个头,但多个头共享同一份 K/V。
对比一下。
MHA:
Head 1:Q1 K1 V1
Head 2:Q2 K2 V2
Head 3:Q3 K3 V3
Head 4:Q4 K4 V4
MQA:
Head 1:Q1 共享K 共享V
Head 2:Q2 共享K 共享V
Head 3:Q3 共享K 共享V
Head 4:Q4 共享K 共享V
这样 KV Cache 会小很多。
好处:
- 显存占用更低。
- 读取 K/V 的带宽压力更小。
- 长上下文生成更友好。
可能的代价:
- 表达能力可能不如完整 MHA。
GQA¶
GQA 是 Grouped-Query Attention。
它可以理解成 MHA 和 MQA 的折中。
MHA 是每个 head 都有自己的 K/V。
MQA 是所有 head 共享一份 K/V。
GQA 是几个 head 一组,共享一份 K/V。
例子:
Head 1、2 共享 K/V A
Head 3、4 共享 K/V B
Head 5、6 共享 K/V C
Head 7、8 共享 K/V D
这样既减少 KV Cache,又保留一定表达能力。
现在很多大模型会使用 GQA,因为它在能力和推理成本之间比较均衡。
MHA、MQA、GQA 怎么选¶
可以先这样记:
| 结构 | K/V 设计 | 优点 | 代价 |
|---|---|---|---|
| MHA | 每个 head 独立 K/V | 表达能力强 | KV Cache 大 |
| MQA | 所有 head 共享 K/V | 最省显存和带宽 | 表达能力可能受影响 |
| GQA | 一组 head 共享 K/V | 折中方案 | 比 MQA 更占缓存 |
如果你做的是产品或 Agent 系统,不一定需要自己实现这些结构。
但你需要知道:
模型结构会直接影响上下文长度、并发能力、延迟和成本。
对部署来说,MHA/MQA/GQA 的关键差异是 K/V 有多少份。
K/V 越多:
- KV Cache 越大。
- 长上下文越占显存。
- 并发越容易受限。
- decode 阶段读取压力越大。
所以很多服务化模型会偏向 GQA 这类折中设计。
MoE¶
MoE 是 Mixture of Experts,混合专家模型。
普通模型可以理解成:
每个 token 都经过同一套参数。
MoE 模型可以理解成:
模型里有很多专家,每个 token 只找其中几个专家处理。
例子:
一个模型里有 8 个专家:
专家 1:擅长代码
专家 2:擅长数学
专家 3:擅长中文表达
专家 4:擅长英文表达
专家 5:擅长法律文本
专家 6:擅长医学文本
专家 7:擅长表格
专家 8:擅长闲聊
当输入是:
请帮我解释这段 Java 代码。
路由器可能选择:
专家 1 + 专家 3
当输入是:
帮我解这个方程。
路由器可能选择:
专家 2 + 专家 3
真实模型里的专家不一定像人类标签这样清晰,但这个类比有助于理解。
MoE 解决什么问题¶
MoE 想解决:
如何让模型参数很多,但每次计算只激活一小部分。
比如一个模型总参数很大,但每个 token 只用其中一部分专家。
这样可以拥有更大的容量,同时控制每次推理的计算量。
MoE 的代价¶
MoE 也会带来复杂度:
- 路由器要决定 token 进哪个专家。
- 专家负载可能不均衡。
- 分布式部署更复杂。
- 不同 token 走不同专家,工程调度更难。
一句话:
MoE 用更复杂的调度,换更大的模型容量。
Dense 模型和 MoE 模型¶
Dense 模型:
所有 token 都经过同一套主要参数。
MoE 模型:
每个 token 只激活部分专家参数。
对比:
| 类型 | 直觉 | 优点 | 难点 |
|---|---|---|---|
| Dense | 所有人走同一条流水线 | 简单稳定 | 参数大时计算重 |
| MoE | 不同 token 分配给不同专家 | 容量大、计算可控 | 路由和部署复杂 |
部署 MoE 时要额外关注:
- 专家是否分布在多张 GPU 上。
- 路由是否导致某些专家特别忙。
- batch 内 token 被分配到不同专家后的通信成本。
- 是否需要 expert parallelism 这类并行策略。
所以 MoE 不是“免费变强”,它把一部分计算压力变成了调度和通信压力。
FlashAttention¶
Attention 计算不只是“算不算得动”的问题,还有“数据怎么搬”的问题。
GPU 很快,但显存读写也很贵。
FlashAttention 的核心思路可以简化成:
重新组织 attention 的计算方式,减少显存读写,让 GPU 更高效地完成同样的数学计算。
它不是改变模型能力的结构。
它更像是把同一道题用更高效的计算方式做出来。
可以理解成:
普通 Attention:中间结果很多,频繁写显存、读显存。
FlashAttention:分块计算,尽量把中间结果留在更快的存储里。
PagedAttention¶
KV Cache 会占大量显存。
如果很多用户同时请求,每个用户的上下文长度不同,就容易造成显存管理问题。
PagedAttention 的直觉类似操作系统里的分页:
把 KV Cache 分成一页一页的小块,需要时再管理和复用。
它主要解决:
- KV Cache 显存碎片。
- 多用户并发时的缓存管理。
- 长上下文请求对显存的压力。
如果 KV Cache 是“把历史 K/V 存起来”,PagedAttention 更关注:
这些缓存在线上服务里怎么高效管理。
Batching¶
Batching 是把多个请求合在一起跑。
例子:
用户 A:请解释错误日志。
用户 B:请写一个 SQL。
用户 C:请总结文章。
如果一个一个跑,GPU 可能吃不满。
把它们组成 batch 后,GPU 可以并行处理更多计算。
但是生成式模型有个麻烦:
每个用户生成长度不同。
用户 A 可能 20 个 token 就结束。
用户 B 可能要生成 500 个 token。
所以线上系统经常会用 continuous batching,也就是动态批处理。
它会不断把新请求加入,把完成的请求移出,让 GPU 尽量保持忙碌。
Speculative Decoding¶
Speculative Decoding 可以翻译成推测解码。
它的直觉是:
让一个小模型先快速猜几个 token,再让大模型检查这些 token 能不能接受。
例子:
小模型先猜:
Transformer 是 一种 神经 网络
大模型检查后发现:
前 4 个 token 都可以接受
那就一次前进 4 步。
如果某个 token 不接受,再从那里重新生成。
这像是:
小模型负责快猜,大模型负责把关。
它的目标是减少大模型逐 token 生成的等待时间。
Quantization¶
Quantization 是量化。
模型参数原本可能用比较高精度的数字保存。
量化会把它们压到更低精度。
例子:
FP16 -> INT8
FP16 -> INT4
直觉上类似:
用更少的数字位数保存模型权重。
好处:
- 模型占用显存更少。
- 加载更快。
- 推理成本更低。
可能的代价:
- 精度下降。
- 某些任务效果变差。
- 对长上下文、复杂推理可能更敏感。
量化本身还有很多分支,比如 FP8、INT8、INT4、AWQ、GPTQ、GGUF、KV Cache 量化。详细看:模型量化与推理压缩入门。
RoPE 和长上下文¶
RoPE 是 Rotary Position Embedding,旋转位置编码。
它和“模型怎么知道 token 位置”有关。
很多大模型使用 RoPE 来表达位置信息。
当模型从短上下文扩展到长上下文时,位置编码会变得很重要。
你可以先这样理解:
KV Cache 解决生成时复用历史计算,RoPE 解决模型如何理解 token 的位置关系。
长上下文优化经常会同时涉及:
- 位置编码扩展。
- Attention 计算优化。
- KV Cache 管理。
- 上下文压缩。
这些概念放在一张表里¶
| 概念 | 解决什么问题 | 更偏模型结构还是推理系统 | 一个直觉 |
|---|---|---|---|
| MHA | 多角度理解 token 关系 | 模型结构 | 多个观察角度 |
| MQA | 减少 K/V 缓存和读取 | 模型结构 | 多个 Q 共享 K/V |
| GQA | 在能力和缓存之间折中 | 模型结构 | 一组 head 共享 K/V |
| MoE | 参数容量大但每次少算 | 模型结构 | token 分配给专家 |
| KV Cache | 避免重复计算历史 K/V | 推理系统 | 历史笔记存起来 |
| Prefix Cache | 复用多个请求相同前缀 | 推理系统 | 固定开头只算一次 |
| FlashAttention | 降低 attention 显存读写 | 推理系统 | 更高效地搬数据 |
| PagedAttention | 管理大量 KV Cache | 推理系统 | 缓存分页管理 |
| Batching | 提高 GPU 利用率 | 推理系统 | 多个请求一起跑 |
| Speculative Decoding | 降低生成延迟 | 推理系统 | 小模型先猜,大模型检查 |
| Quantization | 降低模型显存和成本 | 推理系统 | 用更低精度存模型 |
| RoPE | 表达 token 位置关系 | 模型结构 | 给 token 顺序编码 |
在 Agent 产品里为什么重要¶
Agent 产品通常会有很长的上下文:
- 系统提示词。
- 工具说明。
- 项目文件。
- 历史对话。
- 检索结果。
- 任务计划。
- 工具返回结果。
这些内容会影响:
- 首 token 延迟。
- 生成速度。
- 并发能力。
- 服务成本。
- 是否容易命中 Prefix Cache。
- KV Cache 是否撑爆显存。
所以设计 Agent 上下文时,不只是“让模型看更多”。
更准确地说,是:
让模型看到必要的信息,并让这些信息以更容易缓存、更少干扰、更低成本的方式进入上下文。
一个 Agent 请求例子¶
假设一个代码 Agent 的 prompt 结构是:
固定系统规则
固定工具说明
固定项目规范
动态用户问题
动态相关文件片段
动态工具结果
这会带来几个效果。
固定系统规则、工具说明、项目规范:
适合 Prefix Cache
动态用户问题和文件片段:
每次都可能不同,不容易复用
长对话历史:
会增加 KV Cache 压力
工具结果太长:
会增加首 token 延迟,也可能干扰模型注意力
所以工程设计上可以做:
- 把稳定 prompt 放前面,并保持顺序固定。
- 把动态检索结果放在后面。
- 对历史对话做摘要,而不是无限追加。
- 对工具结果做结构化裁剪。
- 大文件只放相关片段。
- 对常用仓库规则做稳定缓存。
常见误区¶
误区 1:KV Cache 会让模型理解更多¶
不会。
KV Cache 主要是推理加速。
它让模型少重复计算,不是增加模型能力。
误区 2:Prefix Cache 等于记忆¶
不是。
Prefix Cache 是计算缓存。
记忆是产品或系统保存的用户偏好、项目事实、任务状态。
误区 3:MoE 就一定更聪明¶
不一定。
MoE 增加了模型容量,但最终效果还取决于训练数据、路由、专家设计、后训练和推理系统。
误区 4:量化一定无损¶
不一定。
量化能降成本,但可能影响复杂任务表现。
误区 5:上下文越长越好¶
不是。
长上下文会增加 KV Cache、延迟、成本和干扰。
真正重要的是上下文质量。
建议学习顺序¶
第一遍建议这样学:
- MHA:理解多头注意力为什么需要多个观察角度。
- KV Cache:理解生成时为什么要缓存历史 K/V。
- Prefix Cache:理解 Agent 产品为什么要稳定 prompt 前缀。
- MQA / GQA:理解为什么要减少 K/V。
- MoE:理解为什么大模型可以“参数多但每次少算”。
- Batching:理解线上服务为什么要合并请求。
- FlashAttention / PagedAttention:理解高性能推理如何管理计算和显存。
- Speculative Decoding / Quantization:理解降低延迟和成本的常见办法。
第一遍只要抓住这条主线:
MHA 让模型看得更丰富
KV Cache 让生成少重复算
Prefix Cache 让相同开头少重复算
MQA/GQA 让缓存更小
MoE 让大模型每次只激活一部分
推理系统优化让线上服务跑得更快更稳
下一步¶
继续读: