大模型推理优化全解:KV Cache、PagedAttention、连续批处理与投机解码

发布时间:2026/10/30 14:03:05
大模型推理优化全解:KV Cache、PagedAttention、连续批处理与投机解码

大模型推理优化全解:KV Cache、PagedAttention、连续批处理与投机解码

一、一个真实的"为什么这么慢"

搭了个大模型服务,用户一上来就问:"你为什么这么慢?"盯着屏幕看推理时间——7 秒一个 token。回头一看配置,一个 Llama-2-7B,单张 A100。理论上应该跑飞快,结果慢得像拨号上网。问题出在哪?

先把大模型是怎么干活的搞清楚:每次生成一个 token,模型都要"看"之前的所有 token。生成第 100 个 token 时,模型要把前 99 个 token 都算一遍。这就是自回归解码,天然 O(n²) 复杂度。

更糟糕的是显存带宽。你算得再快,数据从显存搬到 GPU 核心要时间。现代推理的瓶颈不是计算,是带宽。有人以为加 GPU 就能解决,结果两张 A100 并在一起,性能提升不到 40%——因为数据还是得从显存搬运到计算核心,带宽没变。

二、KV Cache:不要重复算

KV Cache 是第一道防线。原理很简单:每层 attention 都要算 Q、K、V 三个矩阵,而 K 和 V 只依赖之前的 token,生成新 token 时没必要重新算,存下来就行。

每个 token 的 KV Cache 大小大约是 0.5MB(7B 模型、32 层、4096 维、FP16)。生成 4096 个 token,光 KV Cache 就占 2GB 显存。用了 KV Cache 之后,推理速度直接提升 3-5 倍——代价是显存占用翻倍。Llama-2-7B 本身 13GB,长文本场景轻松破 30GB,单张 A100 都装不下。

这里引出一个关键矛盾:KV Cache 是速度的钥匙,也是显存的杀手。所有推理优化的核心命题,本质上都是"如何在更少的显存里装下更多 KV Cache,同时保持访问效率"。

三、PagedAttention:把显存利用率从 40% 拉到 90%

vLLM 的核心创新就是 PagedAttention。传统 KV Cache 分配是静态的——预分配固定大小,假设你要支持 2048 上下文,不管用户发多少字,显存都给满了,利用率只有 40%。PagedAttention 借鉴操作系统虚拟内存的思想:把 KV Cache 分页,动态申请和释放,用户发 100 字就只申请 100 字的显存。

# vLLM 启动命令(--block-size 16 就是页大小,默认 16 个 token 一页)python-m vllm.entrypoints.api_server \--model meta-llama/Llama-2-7b-hf \--tensor-parallel-size1\--gpu-memory-utilization0.9\--block-size16``` 实测下来,vLLM 比 HuggingFace Transformers 快3-5倍,显存利用率从40%提到90%。同样4096上下文,显存占用只有原来的60%,吞吐量直接翻倍。 分页带来的另一个隐性收益是**前缀共享**:多个请求如果有相同的系统提示,可以共享同一段 KV Cache 块,进一步省显存。这在"所有请求共用同一套角色设定"的服务里收益巨大。## 四、投机解码:小模型猜,大模型验证投机解码的原理:用一个小模型(比如1.1B)快速生成一堆 token,然后用大模型(比如 7B)并行验证。验证通过就用,不过就回退。 ```python# vLLM 开启投机解码python-m vllm.entrypoints.api_server \--model meta-llama/Llama-2-7b-hf \--speculative-model TinyLlama/TinyLlama-1.1B-Chat-v1.0\--num-speculative-tokens5``` `--num-speculative-tokens5` 表示小模型一次猜5个 token。实测 Llama-2-7B 配合1.1B 小模型,推理速度从50tokens/s 提升到130tokens/s。 但投机解码有个前提:小模型猜得准。如果猜错太多,反而会拖后腿。我在自己的数据集上测试过,小模型猜错率高达30%的场景,投机解码反而比不用还慢。**这是推理优化里最反直觉的一点:加速技术不是无条件生效的,必须实测。**## 五、连续批处理:动态拼 batch传统批处理是静态的——等凑够32个请求再一起跑,用户少的时候 GPU 就空着。连续批处理(Continuous Batching)是动态的:来一个就跑一个,中间有请求结束,立刻塞新的进去,GPU 永远不闲着。vLLM 默认开启,不用额外配置。 用 HuggingFace Transformers 时,用户一少 GPU 利用率掉到20%;切到 vLLM,哪怕只有一个用户,利用率也能维持在70%以上。连续批处理是高并发场景收益最大的优化项——尤其是请求长度差异大的在线服务。## 六、量化:INT8/FP8,显存减半速度翻倍量化就是把模型参数从 FP16 压缩到 INT8 或 FP8。FP16→INT8,显存占用减半,带宽需求减半,推理速度翻倍,代价是精度掉1-2个点,实际效果差别不大。vLLM 支持 AWQ 和 GPTQ 两种量化格式: ```bash python-m vllm.entrypoints.api_server \--model TheBloke/Llama-2-7B-AWQ \--quantization awq ``` 试过 AWQ4-bit 量化:Llama-2-7B 从 13GB 压到 4GB,推理速度从50tokens/s 提升到90tokens/s,精度肉眼几乎看不出来。 量化选择建议:效果要求严格、显存充足的场景优先 INT8(稳健性最好,损失在1%以内);显存受限、成本敏感或边缘部署选 INT4;H100 硬件可以用 FP8(几乎无损)。无论选哪种,部署前务必在目标任务真实评测集上对比原模型——量化损失在数学推理、代码生成这类精度敏感任务上会更明显。## 七、监控:让优化有据可依优化做完不能一跑了之。要监控的关键指标:KV Cache 利用率(gpu_cache_usage_perc,反映显存分配是否健康)、首 Token 延迟(time_to_first_token_seconds,用户可感知的第一道门槛)、吞吐(每秒处理请求数)、抢占次数(num_preemptions_total,抢占多说明 KV Cache 不够)。这些指标接入 Prometheus 之后,每次调参都有前后对比,而不是凭感觉判断"好像变快了"。## 八、实战建议:什么场景用什么组合搞懂了原理,剩下的就是组合拳:**短对话(512token 以内)**:普通推理就行,显存够的话 FP16 最稳,不需要额外优化。**长文档问答(2048-4096token)**:必须用 KV Cache+PagedAttention,vLLM 默认配置就能跑;显存不够就上 AWQ 量化。**高并发在线服务**:vLLM+连续批处理;投机解码可选,看小模型质量;7B 以下模型 INT8 量化就够。**成本敏感场景**:4-bit AWQ 量化+vLLM,显存减75%,速度提升2-3倍,接受1-2个点精度损失。## 八、最后说句大实话推理优化这东西,理论再多不如实测。别相信任何"理论数据",相信你自己的 benchmark。核心建议:先测自己的数据,再选方案。vLLM 是个好起点,默认配置就够用80%的场景;剩下20%一个个试——投机解码、量化、连续批处理、前缀缓存。监控显存利用率和吞吐量,哪个好用哪个。 再补充两条容易被忽略的经验:一是**量化选型要与业务绑定**,客服问答这类事实型任务对量化损失不敏感,但数学推理、代码生成这类任务,INT4 的退化可能超预期,一定要在真实评测集上对比后再定;二是**不要同时改多个变量**,投机解码和量化一起上,出问题了分不清是谁的锅——一次只动一个参数,用监控数据确认收益再动下一个。 推理优化的本质是系统工程:KV Cache 解决"重复计算",PagedAttention 解决"显存浪费",连续批处理解决"GPU 空闲",量化解决"带宽瓶颈",投机解码解决"解码太慢"。五把钥匙缺一不可,但也没有一把是万能的。先建立压测和监控的度量体系,让每一次优化都有数据支撑——这比背下所有优化参数都重要。