LLM 推理性能优化路线图:从瓶颈定位到 KV Cache、连续批处理与吞吐/延迟权衡

把 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 工程策略(按常见收益排序)

  1. 限制最大上下文:最粗暴但最有效
  • 给产品一个“最大输入长度”的硬上限
  • 对 RAG:先做“检索截断 + 摘要压缩”,而不是直接堆 context
  1. KV cache 量化/压缩(有风险,需验证)
  • 目标:用更低精度存 KV,省显存/带宽
  • 风险:质量回退(尤其在长上下文)
  1. 更合理的 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. 你应该怎么做一轮优化(建议顺序)

我会按这个顺序做:

  1. 建立基线:记录 TTFT/TPOT、吞吐、显存
  2. 限制输入:最大 context,RAG 截断/压缩
  3. 调度策略:连续 batching、合理并发上限
  4. 显存策略:KV 管理、必要时量化
  5. 更底层优化:kernel/fused op、张量并行/流水并行(成本高)

每一步都要做 A/B:

  • 质量是否回退(尤其长上下文与边界任务)
  • P95 是否改善(别只看平均)

结语

LLM 推理优化的本质是:

  • 把“token”当成你的单位成本
  • 把 TTFT/TPOT 当成产品体验
  • 把 KV cache 当成核心资源

如果你愿意,我可以再按你们的场景(RAG 为主?写作生成?多轮工具调用?)给一个更具体的配置建议清单:并发上限、上下文预算、检索 Top-K、rerank 以及监控指标应该怎么设。

build with Hugo, theme Stack, visits 0