当前位置: 首页 > news >正文

【学习笔记】推理加速三板斧:KV Cache、PagedAttention、Continuous Batching(11/35)

入门认知 5 篇 + 训练与微调 5 篇——前 10 篇我们走完了「模型怎么来」的全过程。

从这一篇开始,我们进入推理优化篇(第 11-15 篇)。这是整个系列里和后端工程师最贴近的部分,也是我个人最想认真讲透的部分。

为什么这么说?

很多团队的真实情况是这样的:

  • 模型训完,丢给后端「请部署上线」

  • 后端pip install transformers,写个 FastAPI,跑起来

  • 发现:单 H100 跑 70B,平均 30 tokens/s,并发 1

  • 老板:「为什么这么慢?vLLM 一个请求能跑 1000 tokens/s 啊!」

  • 后端:「……我也是按官方文档写的」

这 30 倍的性能差距,就藏在 vLLM 等推理框架里的"魔法"中。这些"魔法"不是什么不可言说的黑科技,本质就是三个核心技术:

KV Cache、PagedAttention、Continuous Batching。

任何一个想做大模型部署的工程师,必须把这三个东西理解到「能讲给别人听」的程度。这一篇我们就来做这件事。

读完本文你将能:

  1. 理解 Prefill vs Decode 两阶段的本质差异

  2. 算清 KV Cache 显存账,并知道量化怎么优化

  3. 理解 PagedAttention 如何让显存利用率从 30% 跃升到 90%+

  4. 理解 Continuous Batching 如何让吞吐量提升 5-10×

  5. 用 vLLM 跑通一个高性能推理服务,并能监控关键指标

我们开始。


一、为什么推理优化是另一个 90% 的工作

1.1 朴素推理 vs 优化推理的差距

我们先用一组真实数字震撼一下:

实验设置:Llama-3-70B INT8、单 H100 80G、prompt=2K、生成=512 token、batch=32

推理方式

单卡吞吐(tokens/s)

相对性能

HuggingFace Transformers (朴素)

~30

Transformers + KV Cache

~120

TGI(HF 生产版)

~600

20×

vLLM (默认)

~2000

67×

vLLM + INT8 + FP8 KV Cache

~3500

117×

TensorRT-LLM (极致优化)

~4500

150×

150 倍的差距。这意味着什么?

  • 不优化:你需要 150 张 H100 服务 1000 并发用户

  • 优化好:1 张 H100 就够

每月成本差距:2K。这就是为什么推理优化是大模型部署的核心议题。

1.2 推理成本 > 训练成本

一个被反复验证的事实:

模型上线后,推理累积成本会在几个月内超过训练成本。

项目

训练成本

推理成本(月)

多少月超过训练

Llama 3-70B

$13M

~$1M

13 个月

DeepSeek V3

$5.6M

~$0.6M

9 个月

中型客服应用

~$200K

~$30K

6 个月

小型 API 包装

$0

~$5K

N/A

这就是为什么 OpenAI / Anthropic 都把推理优化看作头等技术战略——训练是一次性投入,推理是持续支出。

1.3 推理优化的三大维度

工业上把推理优化拆成三个独立维度:

显存优化 ── 让模型放得下 吞吐优化 ── 让单位时间处理的请求更多 延迟优化 ── 让单个请求更快返回

三者经常冲突——比如增加 batch size 提升吞吐,但会增加单请求延迟。

工程师的核心工作就是在三者之间找平衡。

本系列推理优化篇 5 篇会系统讲清这一切:

篇号

主题

核心优化维度

11(本篇)

KV Cache / PagedAttention / Continuous Batching

吞吐 + 显存

12

量化压缩

显存

13

Flash Attention

延迟 + 显存

14

投机解码

延迟

15

长上下文优化

显存 + 延迟

我们开始第一板斧。


二、第一板斧:KV Cache

第 2 篇我们讲过 KV Cache 的基础原理。这里我们做工程视角的深度拆解——什么时候用、用多少、怎么压缩、怎么复用。

2.1 Prefill vs Decode:两阶段本质不同

大模型生成的工作流程其实是两个完全不同的阶段:

[Prefill 阶段] [Decode 阶段] 处理整个 prompt 每次生成 1 个 token 计算 n × n attention 计算 1 × (n+i) attention 计算密集 显存带宽密集 ~1 次 ~512 次(生成 512 token) 单次耗时长 单次耗时短

关键洞察

  • Prefill 是compute-bound——GPU 算力是瓶颈

  • Decode 是memory-bound——GPU 显存带宽是瓶颈

这两个阶段需要的优化完全不同:

阶段

主要瓶颈

优化方向

Prefill

算力

Flash Attention、Chunked Prefill

Decode

带宽

KV Cache、量化、Batching

生产环境的延迟构成

TTFT(Time To First Token) = Prefill 耗时 TPOT(Time Per Output Token) = Decode 单 token 耗时 端到端延迟 = TTFT + N × TPOT

TTFT 和 TPOT 是两个互相独立的优化目标。

2.2 KV Cache 显存账(再算一次)

KV Cache 显存公式:

KV_cache = 2 × batch × seq_len × num_layers × num_kv_heads × head_dim × dtype_size

Llama-3-70B(80 layers, 8 KV heads with GQA, head_dim=128, FP16)为例:

上下文

单请求 KV Cache

可并发数 (单 H100 80G, 模型占 70G 留 10G 给 KV)

4K

1.3 GB

7

8K

2.6 GB

3

32K

10.7 GB

0(连一个都装不下)

这就是短上下文 + 多并发vs长上下文 + 少并发的根本权衡。

2.3 KV Cache 量化

KV Cache 已经是显存大户,把它量化是最直接的优化。

主流方案:

方案

显存节省

精度损失

支持框架

FP16(基准)

0%

全部

FP8 (E5M2 / E4M3)

< 0.5%

vLLM, TensorRT-LLM

INT8

1-2%

vLLM, SGLang

INT4

3-5%(部分任务掉点明显)

vLLM 实验

生产推荐

  • 有 H100+ →FP8 KV Cache(性能精度兼顾)

  • 老硬件 →INT8 KV Cache

  • 极致显存 → INT4,但要测业务效果

vLLM 启用 FP8 KV Cache:

vllm serve Qwen/Qwen3-32B \ --kv-cache-dtype fp8 \ --quantization fp8

2.4 Prefix Caching:把"重复计算"省掉

很多业务场景的 prompt 有大量重复前缀

  • 多轮对话:每次新对话都带历史,前 N 轮的 KV 是重复的

  • RAG:每次请求都有相同的 system prompt + 检索文档

  • Few-shot:固定的 examples 占了大半 prompt

  • Code 助手:每次都带整个项目上下文

朴素做法:每次都重新做 Prefill 计算这些 KV。

Prefix Caching 做法:把高频前缀的 KV 缓存起来,下次直接复用。

收益惊人

  • 多轮对话场景:TTFT 降 50-80%

  • RAG 场景:TTFT 降 70-90%

  • 极端情况(90% prompt 重复):TTFT 降 95%

vLLM 启用:

vllm serve ... --enable-prefix-caching

OpenAI / Anthropic 早已做这个——所以他们的 prompt cache 价格只有正常价的 10-50%。

高级:Tree-based Prefix Caching(SGLang)

SGLang 进一步做了RadixAttention——用基数树管理多个 prompt 的前缀树,支持任意分叉共享:

[system prompt] / \ [user A] [user B] | | [response] [response]

A/B 用户共享 system prompt 的 KV,每个用户自己的对话单独存储——节省显存 + 加速 prefill


三、第二板斧:PagedAttention

PagedAttention 是vLLM 的核心创新(2023.6,Berkeley),目前已经被全行业采纳。它解决了一个所有人都忽视但极其严重的问题——KV Cache 显存碎片化

3.1 传统 KV Cache 的「三宗罪」

罪 1:连续分配的浪费

传统 KV Cache 给每个请求预分配最大长度的显存。

假设你支持 32K 上下文,那即使一个请求只用了 1K,也得占 32K 的显存(万一它说话长呢)。

实际利用率:30-50%。

罪 2:碎片化

不同请求长度不同,释放后留下碎片——新请求无法利用。

[已用][已用][已用][空][已用][空][空][已用][空] ↑ ↑ ↑ 小碎片,无法被大请求使用
罪 3:长短请求混合崩溃

短请求和长请求一起来时,短请求结束后留下的空间被长请求占满——后续短请求干等。

时刻 1:[长][短][短][短] ← 短的快结束 时刻 2:[长][空][空][空] ← 短的结束了,留下空 时刻 3:[长][新长][...] ← 新长请求占满了空间 时刻 4:[长][新长] ← 没法接新短请求了!

整体显存利用率:通常只有 30-50%。一张 80G 卡,实际有效利用 24-40G。

3.2 PagedAttention 的思路:借鉴 OS 虚拟内存

操作系统怎么处理类似的"碎片化 + 不知道用多少"问题?

分页(Paging)。

PagedAttention 直接借用了这个思想:

KV Cache 显存 → 切成固定大小的"页"(block) 默认 16 token / 页 每个请求的 KV → 按需申请页 不需要连续 用多少分多少

对比

传统:每请求一块连续大显存 [████████████████][░░░░░░░░░░][████████████████] 占了用不到 小碎片 PagedAttention:所有请求共用一个页池 [█A][█B][█A][█C][░][█B][█A][█C][░][█A][░][█B] ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ 请求 A B A C B A C A 散落但满载

通过一张page table把"虚拟的连续 KV"映射到"物理的离散页"——和 OS 完全一样。

3.3 PagedAttention 的实际收益

PagedAttention 让显存利用率从 30% 跃升到 90%+,直接带来:

  • 单卡可并发请求数提升2-4×

  • 端到端吞吐提升2-4×

  • 长短请求混合也不慌

vLLM 官方论文实测数据:

模型

朴素 KV Cache

PagedAttention

提升

LLaMA-13B

12 reqs/s

28 reqs/s

2.3×

LLaMA-33B

5 reqs/s

16 reqs/s

3.2×

LLaMA-65B

2 reqs/s

8 reqs/s

模型越大,PagedAttention 收益越明显。

3.4 进阶:Copy-on-Write(CoW)

PagedAttention 还能做一件更聪明的事——KV Cache 共享

场景:beam search、并行 sampling、A/B 测试。多个序列共享相同的 prefix。

朴素做法:每个序列复制一份 prefix KV。

CoW 做法

  1. 多个序列指向同一份prefix KV 页

  2. 一旦某个序列要修改某页,复制一份再改(Copy on Write)

  3. 没有修改的页继续共享

效果:多采样场景显存省 50-80%

3.5 PagedAttention 的代价

PagedAttention 不是没代价:

  • 页索引开销:每次 attention 计算需要先查 page table,增加几个 % 的开销

  • CUDA Kernel 复杂度高:需要专门的 paged attention kernel

  • 实现成本:vLLM 团队花了几个月才稳定

但相比 2-4× 的吞吐收益,这点代价几乎可以忽略。

现在主流推理框架都已经"PagedAttention 化":vLLM、SGLang、TensorRT-LLM、TGI 都支持。


四、第三板斧:Continuous Batching

第三板斧解决另一个看似简单但极其重要的问题——多请求怎么 batch

4.1 静态 Batching 的悲剧

静态 Batching(朴素做法)

请求 1 到达 → 等其他请求 → 凑齐 batch=32 → 一起跑 → 等最长的结束 → 全部返回

这有几个致命问题:

问题 1:等待延迟

请求来的间隔不规律。要凑齐 32 个,可能要等几秒。用户体验灾难

问题 2:木桶效应
batch 内 32 个请求,输出长度差异巨大: 请求 1: 输出 50 token → 99% 时间在等 请求 2: 输出 100 token → 95% 时间在等 请求 3: 输出 1000 token → 慢悠悠 ... 请求 32: 输出 5000 token → 大家全部陪它跑完

短请求被长请求"绑架",GPU 利用率惨不忍睹。

问题 3:长尾恶化

业务场景中"长 tail" 很常见。99 percentile 的请求可能 10× 于 median。统计上必然出现极差

结果:静态 batching 实际有效 GPU 利用率~ 20-30%

4.2 Continuous Batching:迭代级调度

Continuous Batching 的核心改动:

不在请求级别 batch,而是在 iteration(每生成一个 token)级别 batch。

关键改动

  1. 任意时刻可插入新请求——不需要等

  2. 任意时刻可移除完成的请求——立即返回

  3. 每个 iteration(生成一个 token)作为调度单位

举例

iter 1: 请求 A 在 batch iter 2: 请求 A 在 batch,请求 B 到达,B 也加入 iter 3: A, B 都在 iter 4: A 完成了!立即返回 A,B 继续 iter 5: 只有 B 在 batch,请求 C 到达加入 iter 6: B, C 都在 ...

效果

  • GPU 利用率从 20-30% 提升到 80%+

  • 短请求不再被长请求绑架

  • 吞吐量提升 5-10×

4.3 Continuous Batching 的实现挑战

实现这个看似简单的优化,工程上其实非常难:

  1. 变长 KV Cache 管理——不同请求不同长度,需要 PagedAttention 配合

  2. 变长 attention mask——每个 iteration 重新计算

  3. 请求优先级与公平性——长请求会不会饿死

  4. 预填充与解码混合——新请求要先 prefill,旧请求在 decode

这就是为什么 PagedAttention + Continuous Batching 必须一起做——分开不好实现。

4.4 PagedAttention + Continuous Batching = vLLM 的魔法

PagedAttention 解决"显存放得下",Continuous Batching 解决"GPU 跑得满"。

两者协同的效果:

  • 单卡能放下的请求数:2-4×

  • 每个请求平均跑得快:2-3×

  • 综合吞吐:5-10×

这也是为什么 vLLM 相对朴素推理能快50-100 倍

4.5 进阶:Chunked Prefill

Continuous Batching 还有一个进化版——Chunked Prefill(vLLM 0.5+ 默认)。

问题:Prefill 很重(O(n²) 计算),如果一个长 prompt 来了,它会独占 GPU,把所有 decode 请求阻塞。

Chunked Prefill 的做法:把长 prompt 切成 chunk(如 512 token),逐 chunk 处理,和 decode 请求混合调度

效果

  • 长 prompt 不再阻塞短请求

  • 整体延迟尾部大幅改善

  • 99% latency 降低 50%+

启用:

vllm serve ... --enable-chunked-prefill

五、实战 + 关键指标

5.1 vLLM 完整部署示例

# 部署 Qwen3-32B + 全套优化 vllm serve Qwen/Qwen3-32B-Instruct \ --max-model-len 32768 \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.9 \ \ --enable-prefix-caching \ # KV Cache 复用 --enable-chunked-prefill \ # 分块 prefill --kv-cache-dtype fp8 \ # KV Cache 量化 \ --max-num-batched-tokens 8192 \ --max-num-seqs 256 \ # 最大并发数 --swap-space 16 \ # CPU swap 备用 \ --port 8000

关键参数解读

参数

作用

推荐值

--gpu-memory-utilization

显存使用上限

0.85-0.95

--enable-prefix-caching

开启 prefix 复用

强烈推荐

--enable-chunked-prefill

长 prompt 不阻塞

推荐

--kv-cache-dtype

KV Cache 精度

fp8 (H100) / int8

--max-num-seqs

最大并发

看显存

--max-num-batched-tokens

单次 batch 总 token

通常 = max_seq_len × 2-4

5.2 关键监控指标

部署后必须监控的 4 个指标:

指标

含义

目标值

TTFT

(Time To First Token)

首 token 延迟

< 500ms(短 prompt)

TPOT

(Time Per Output Token)

单 token 生成时间

< 50ms

Throughput

整体吞吐 (tokens/s)

越高越好

GPU Utilization

GPU 利用率

80%+

vLLM 自带 Prometheus metrics endpoint:

curl http://localhost:8000/metrics | grep vllm

关键 metrics:

vllm:time_to_first_token_seconds_histogram vllm:time_per_output_token_seconds_histogram vllm:request_success_total vllm:num_requests_running vllm:num_requests_waiting vllm:gpu_cache_usage_perc vllm:cpu_cache_usage_perc

接到 Grafana 后能可视化看到效果。

5.3 性能调优 Checklist

部署后发现性能不达标,按这个顺序排查:

  1. GPU 利用率低(< 60%)

    • 是不是 batch 太小?提高--max-num-seqs

    • 是不是请求太少?看num_requests_waiting

  2. TTFT 高

    • prompt 太长 → 启用 chunked prefill

    • 没开 prefix caching → 启用

  3. TPOT 高

    • 显存带宽满了?看 GPU 利用率

    • 模型太大?考虑量化

  4. OOM

    • KV Cache 量化(fp8/int8)

    • 减少 max num seqs

    • 升级到张量并行

  5. 5.长尾恶化

    • 开 chunked prefill

    • 设置 max tokens 上限

5.4 性能对比实测

我们用 Qwen3-32B 做对比实验(单 H100、prompt=1K、output=512):

配置

吞吐 (tokens/s)

TTFT

TPOT

纯 transformers

25

1200ms

40ms

+ KV Cache

95

1100ms

11ms

vLLM 默认

1800

600ms

28ms (batch 摊薄)

vLLM + fp8 KV

2400

550ms

22ms

vLLM + 全套优化

3200

280ms

20ms

127 倍吞吐差距。这就是推理优化的威力。


六、扩展话题与下一篇预告

6.1 三板斧之外的优化

除了这三板斧,2024-2025 还有几个新进展:

技术

解决问题

状态

Speculative Decoding

串行解码慢

主流(第 14 篇)

Tensor Parallel + Pipeline Parallel

单卡装不下

主流(第 20 篇)

Multi-LoRA Serving

多业务复用

主流

Disaggregated Prefill/Decode

资源利用不均

新兴

CPU/GPU 卸载

极致显存

实验

6.2 推理框架横向对比预告

下一篇我们会进入量化,第 17 篇会做完整的推理框架横评:

框架

优势

适合

vLLM

易用、社区好

通用首选

SGLang

复杂控制流、JSON

Agent / 结构化

TensorRT-LLM

极致性能

生产追求极限

TGI

HF 生态融合

HF 用户

6.3 不要只看 benchmark

最后一个反直觉的提醒

性能基准很重要,但不要只盯着 benchmark。

很多框架在标准 benchmark 上跑得飞快,但生产场景(长尾、突发、长上下文)下表现不一致。所以:

  1. 真实业务数据做测试

  2. 监控p99 / p999而不只是平均值

  3. 测试长时间稳定性(运行 24 小时看看)


七、结语:理解三板斧,理解大模型推理

读完本文你应该明白:

  • KV Cache:解决重复计算,4-10× 加速。Prefill 与 Decode 是两个完全不同的阶段

  • PagedAttention:解决显存碎片,2-4× 提升并发能力。借鉴 OS 虚拟内存的智慧

  • Continuous Batching:解决静态 batch 的悲剧,5-10× 吞吐提升

  • 三者协同= vLLM 的"魔法",相比朴素推理 50-150× 加速

  • 真正的生产部署还要看TTFT/TPOT/Throughput/GPU 利用率四大指标

  • Chunked Prefill / Prefix Caching / KV Cache 量化是当下推荐的进阶配置

参考文献:

推理加速三板斧:KV Cache、PagedAttention、Continuous Batching

http://www.jsqmd.com/news/1095741/

相关文章:

  • 性能测试实战:从需求分析到TPS精准计算与瓶颈定位
  • 从硬边界到软归属:模糊聚类 (Fuzzy Clustering) 的核心思想与实践
  • 终极NCM音乐解密指南:3分钟实现网易云音乐格式转换自由
  • 微信消息自动转发终极指南:5分钟实现跨群智能同步
  • 大连不锈钢水箱模块化拼装工艺优势与工程应用要点
  • 2026实测:两款主流AI编程工具vibe coding能力深度对比
  • 为什么92%的技术决策者在Q2悄悄切换至Claude?ChatGPT的3个隐藏限制正在拖垮你的AI工作流,立即检测!
  • 企业落地 AI Agent:降低成本与 ROI 风险完整落地方案
  • MSP430 GCC工具链安装配置与项目构建全攻略
  • 实测深度测评!Paperxie智能写作,解锁毕业论文高效创作新范式
  • “一鼓转三弯,一砖撑到底”冠珠瓷砖携手东胜东队再战叠滘龙船漂移大赛
  • (深度解析)Nacos配置管理进阶:shared-configs与extension-config的优先级与实战抉择
  • AMD Ryzen处理器终极调试工具:ZenStatesDebugTool完全指南
  • 终极AMD Ryzen硬件调试指南:如何通过SMU Debug Tool掌握处理器核心控制权
  • 达梦数据库DEM组件反序列化RCE漏洞(CNVD-2023-69447)复现与防御
  • 密码学知识
  • Inspect.exe实战:5个案例解锁Windows UI自动化测试
  • Selenium Manager找不到Edge驱动?3种解决方案与深度排查指南
  • 告别尴尬黑屏!NoSleep:Windows防休眠终极解决方案
  • PDF-OCR文件识别篇(五):字段定义与提示词工程
  • Zephyr 源码调试:从零搭建 QEMU 虚拟化调试环境
  • 2026甘肃黄金回收白银回收铂金回收旧料回收怎么选?五家高实价铂金白银线下门店测评清单 + 联系方式
  • 【Pygame实战】从零到一:打造你的‘像素喵星人’跑酷游戏
  • H5+Plus实战:低功耗蓝牙设备连接与数据交互全流程解析
  • 公证处公证亲属关系需要什么材料?亲属关系公证办理流程是什么?
  • DataX实战(02)- 在IDEA中从源码编译到插件调试的一站式指南
  • Logback + ELK 实现北极星日淘日志集中收集与异常排查
  • 如何3步掌握歌词滚动姬LRC Maker:免费制作专业滚动歌词的终极指南
  • 如何3步打造个人云游戏平台:Sunshine串流服务器实战指南
  • Next.js中间件安全漏洞CVE-2025-29927:原理、复现与纵深防御实战