把 LLM 服务真正跑起来后,你会很快发现:
- “模型很强”不等于“服务好用”
- 性能问题不是一个点,而是一条链路:请求 → 编排 → 推理 → 解码 → 传输
这篇文章给一套我认为实用的推理优化路线图:先定位瓶颈,再按收益/风险排序做改动。重点讲清楚三个常见核心点:KV Cache、连续批处理(continuous batching)、吞吐与延迟的权衡。
说明:以下讨论以 Decoder-only LLM(GPT 类)为主。
1. 先把指标做对:不然你永远在“感觉优化”
推理服务至少要同时看两类指标:
1.1 用户体验类(Latency)
- TTFT(Time To First Token):从收到请求到吐出第一个 token 的时间
- TPOT(Time Per Output Token):后续每个 token 的平均时间
- P50/P95/P99(尤其看 P95)
TTFT 决定“有没有卡住”,TPOT 决定“输出快不快”。
1.2 资源效率类(Throughput/Cost)
- tokens/s(整体吞吐)
- GPU 利用率(SM occupancy 只是其中之一)
- 显存占用(KV cache 往往是大头)
- 单请求平均成本(按 token 计费更贴近现实)
一个常见误区:只盯 tokens/s,然后为了吞吐把 batch 拉很大,结果 TTFT 飙升,产品体验崩掉。
2. 理解两个阶段:Prefill 与 Decode
LLM 推理可以粗略分成:
- Prefill:把 prompt 全部喂进去,计算每层 attention 的 K/V 并写入 KV cache
- Decode:每步只生成 1 个 token(或少量 token),每步读取 KV cache 做 attention
性能瓶颈往往在:
- Prefill 阶段:矩阵乘、attention 计算量大,吞吐与并行相关
- Decode 阶段:每步都要读 KV cache,常被显存带宽/访问模式限制
因此优化也要分开看:
- 长 prompt(RAG、工具调用)→ Prefill 压力更大
- 长输出(写作、代码生成)→ Decode 压力更大
3. KV Cache:为什么它是显存杀手,也是性能命门
3.1 KV cache 是什么
对每一层 self-attention,你都要保存历史 token 的 Key/Value,后续解码才能复用。
因此 KV cache 大小近似与以下因素线性相关:
- batch size
- context length(已处理 token 数)
- layer 数
- hidden size / head 数
- dtype(FP16/BF16/FP8/INT8 等)
3.2 你会遇到的典型问题
- 显存 OOM:并发上来后突然炸
- 碎片化:请求长短不一,cache 分配释放频繁,显存利用率下降
- 带宽瓶颈:decode 阶段每步都要从显存读取大量 KV
3.3 工程策略(按常见收益排序)
- 限制最大上下文:最粗暴但最有效
- 给产品一个“最大输入长度”的硬上限
- 对 RAG:先做“检索截断 + 摘要压缩”,而不是直接堆 context
- KV cache 量化/压缩(有风险,需验证)
- 目标:用更低精度存 KV,省显存/带宽
- 风险:质量回退(尤其在长上下文)
- 更合理的 KV 分配策略(解决碎片)
- 思路:不要为每个请求随意 malloc/free,而是做“块化管理”
- 这也是很多推理引擎会重点优化的点
4. 连续批处理(Continuous Batching):吞吐提升的关键
4.1 静态 batching 的问题
传统 batching:等凑够一批再跑。
- 对吞吐好
- 对延迟差(TTFT 会因为排队变长)
4.2 连续 batching 的核心思想
在 decode 的每一步,把“当前可执行的请求”动态拼成 batch。
- 新请求在 prefill 完成后可以插入 decode batch
- 已完成的请求随时退出
这能显著提升 GPU 利用率,同时尽量控制 TTFT。
4.3 现实中的 trade-off
- batch 越大,吞吐越高,但单步 decode 变慢(每步更重),可能拉高 TPOT
- 请求长度差异越大,调度策略越重要(谁先跑、谁后跑)
一个很实用的经验:
- 把 TTFT 当成 SLO(比如 P95 TTFT < 1.5s)
- 在满足 TTFT 的前提下尽量追吞吐
5. 请求层面的“最划算”优化:减少无效 token
很多团队上来就调 kernel、换引擎,但最便宜的优化其实是少算 token。
5.1 Prompt 预算管理
- 系统提示词别写成论文
- 把固定指令改成短模板
- 把“历史对话”做摘要而不是全量回灌
5.2 RAG 的上下文压缩
- Top-K 不要盲堆(先加 rerank)
- 召回后做“句级选择/段内抽取”
- 对重复内容做去重
每少 1k tokens 的 prefill,能直接省 latency 和成本。
6. 你应该怎么做一轮优化(建议顺序)
我会按这个顺序做:
- 建立基线:记录 TTFT/TPOT、吞吐、显存
- 限制输入:最大 context,RAG 截断/压缩
- 调度策略:连续 batching、合理并发上限
- 显存策略:KV 管理、必要时量化
- 更底层优化:kernel/fused op、张量并行/流水并行(成本高)
每一步都要做 A/B:
- 质量是否回退(尤其长上下文与边界任务)
- P95 是否改善(别只看平均)
结语
LLM 推理优化的本质是:
- 把“token”当成你的单位成本
- 把 TTFT/TPOT 当成产品体验
- 把 KV cache 当成核心资源
如果你愿意,我可以再按你们的场景(RAG 为主?写作生成?多轮工具调用?)给一个更具体的配置建议清单:并发上限、上下文预算、检索 Top-K、rerank 以及监控指标应该怎么设。