LLM 推理优化全栈指南:从 KV Cache 到投机解码

大模型部署的成本与延迟由推理优化决定,本文系统讲解自回归生成的内存瓶颈、KV Cache 原理与显存计算公式、PagedAttention 与连续批处理、GPTQ/AWQ/INT8 权重量化、投机解码与并行生成、前缀缓存与 Prompt 优化、TTFT/TPOT/ITL 指标,以及 vLLM/TensorRT-LLM/llama.cpp 框架选型与生产容量规划的完整方法论。

同样的模型,推理优化做与不做,吞吐可能相差 5~10 倍。KV Cache、连续批处理、量化与投机解码,是当前最有效的四板斧。本文从显存账单算起,给出可落地的优化路径。

推理优化的全景地图

大模型推理优化的本质,是在有限的显存与算力内压出更高的吞吐与更低的延迟。优化的杠杆分布在四个层面:

  • 算法层:KV Cache、投机解码、前缀缓存——不改变权重,只改变生成过程中的计算与访存模式。
  • 系统层:连续批处理(Continuous Batching)、PagedAttention、调度策略——提高 GPU 利用率,减少气泡(bubble)。
  • 模型层:量化、剪枝、蒸馏——直接压缩权重体积与计算量。
  • 部署层:框架选型、卡型选择、容量规划、LoRA 服务——决定单位成本能承载多少并发。

优化的度量标准同样分为两类:延迟类(TTFT、TPOT、ITL)与吞吐类(tokens/s、并发数)。两类指标经常互相掣肘:批得越大吞吐越高,但每个请求的尾部延迟也会变长。工程上必须围绕业务 SLA 选择权衡点,而不是盲目追单个指标。

自回归生成与内存瓶颈

解码阶段的生成是逐 token 自回归:每生成一个 token,都要把输入序列从头到尾重算一遍前向传播。若不缓存,长度为 N 的序列要生成 M 个 token,计算量就是 O(N·M) 的重复劳动——这正是 KV Cache 存在的意义。

真正的瓶颈往往不是算力,而是显存带宽。现代 GPU 上,权重一次性加载到显存后可以复用,但生成时每个 token 都要读取全部权重做一次矩阵乘。以一张 80GB 显存的 A100 为例,其算力可达 312 TFLOPS,但显存带宽仅约 2TB/s。权重越大,从显存搬运权重的时间就越长,单位时间能生成的 token 就越少。

一个粗略的公式:单次前向的理论时间 ≈ 权重体积 / 显存带宽。70B 模型 INT8 后约 70GB,在 2TB/s 带宽下,每个 token 的搬运时间就有 35ms 量级——这还没有算上矩阵乘本身。因此,减少权重体积(量化)与减少重复访存(KV Cache)是提速的两条主线。

KV Cache 原理与显存占用

Transformer 的注意力计算需要每个头的 Key 与 Value。生成第 N+1 个 token 时,前 N 个 token 的 K/V 已经算过,把它们缓存起来直接复用,就避免了重复计算,代价是额外的显存开销。

KV Cache 的显存占用可以精确计算:

显存(MB) = 2(K+V) × layers × hidden_size × seq_len × batch_size × bytes_per_elem

# 以 7B LLaMA 为例:32 层、hidden 4096、seq_len 2048、batch 8、FP16(2字节)
2 × 32 × 4096 × 2048 × 8 × 2 / 1024² ≈ 2.1 GB

序列越长、并发越多,KV Cache 占用的显存越夸张。长上下文场景下,KV Cache 常比权重还占显存——这是后续一切优化技术的出发点。

减少 KV Cache 的常用手段:

  • GQA/MQA:分组查询注意力,让多个查询头共享一组 K/V,缓存量降为原来的 1/8~1/2。
  • 窗口注意力:只保留最近 W 个 token 的 KV,丢弃远端历史。
  • KV Cache 量化:对 K/V 做 INT8/FP8 量化,显存再减半。

PagedAttention 与 vLLM 连续批处理

传统推理框架为每个请求预分配连续的最大长度显存块,而实际生成长度千差万别,导致大量碎片与浪费。vLLM 的核心创新 PagedAttention 借鉴操作系统的分页机制:把 KV Cache 切成固定大小的物理块(如每块 16 token),逻辑上连续、物理上可以分散,按需分配。

配合 PagedAttention,vLLM 实现了连续批处理(Continuous Batching):不再等整批全部结束才腾出位置,而是某个请求一完成,立即把 GPU 计算资源让给队列里等着的请求。这一改动让小请求的尾部延迟显著下降,吞吐大幅提升。

# vLLM 启动服务的最小配置
# python -m vllm.entrypoints.openai.api_server \
#   --model Qwen/Qwen2.5-7B-Instruct \
#   --gpu-memory-utilization 0.9 \
#   --max-model-len 8192 \
#   --max-num-seqs 64

生产中的关键参数是 gpu-memory-utilization 与 max-num-seqs:前者决定 KV Cache 预留多少显存,后者决定一个 batch 最多容纳多少请求。两者调大了吞吐上升,但单请求延迟与显存压力也会上升,需要按 SLA 标定。

权重量化:GPTQ / AWQ / INT8

量化把 FP16 权重压到更低的位宽,直接缩小权重体积与访存时间,是推理提速性价比最高的手段之一。三种主流路线:

  • INT8 量化(RTN 或 SmoothQuant):几乎无损,无需校准集,配合 TensorRT-LLM 或 vLLM 使用,体积减半。
  • GPTQ(4-bit 量化的层内贪心补偿):用校准集逐层最小化量化误差,质量损失小,是 4-bit 时代的经典方案。
  • AWQ(基于激活感知的量化):不量化对输出影响最大的少数显著通道(Salient Channels),在保留更多精度的同时比 GPTQ 更少依赖校准集。
# 用 bitsandbytes 快速加载 4-bit 模型(训练/实验场景)
from transformers import BitsAndBytesConfig, AutoModelForCausalLM

quant_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype="bfloat16",
    bnb_4bit_quant_type="nf4",
)
model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2.5-7B-Instruct", quantization_config=quant_config
)

值得强调的是:推理量化不等于训练量化。推理量化关注的是「给定固定权重,如何用低位宽尽可能还原输出」;而训练量化(QAT)把量化误差纳入训练损失,精度更高但需要额外训练成本。生产上两者常结合:先训练 QAT 模型,再导出为 4-bit/8-bit 推理格式。

投机解码与并行生成

自回归逐 token 生成的每一步都依赖前一步结果,无法并行。**投机解码(Speculative Decoding)**打破了这个僵局:用一个更小的草稿模型(Draft Model)快速预测接下来 K 个 token,再由大模型一次验证这 K 个 token 是否被接受。

# 投机解码伪代码(示意)
draft = small_model.sample_k_tokens(prompt)
for token in draft:
    big_probs = big_model(prompt + tokens_so_far)
    if token 概率 > 目标采样阈值: 接受并前进一步
    else: 回退到首个被拒位置,用 big_probs 重新采样

由于小模型每个 token 只花大模型几分之一的时间,且被接受的连续 token 一次验证通过,理论上限是小模型速度 + 大模型验证开销的叠加。配合 speculative_length(草稿长度)调参,实测可达 1.5~2.5 倍加速。vLLM 内置了该能力,只需指定草稿模型:

# vLLM 启用投机解码
# python -m vllm.entrypoints.openai.api_server \
#   --model Qwen/Qwen2.5-7B-Instruct \
#   --speculative-model Qwen/Qwen2.5-0.5B-Instruct \
#   --num-speculative-tokens 5

草稿模型与目标模型共享词表、分布足够接近时收益最大;若分布差异大,拒绝率高,收益打折扣甚至为负。

连续批处理与调度策略

除了 PagedAttention,调度策略也深刻影响吞吐。vLLM 的调度器以请求级为单位(一个 Sequence 一个调度单元),支持三种优先策略:

  • FCFS(先来先服务):公平,但长序列请求会拖慢后续请求。
  • Priority(优先级调度):给关键请求插队,适合在线低延迟场景。
  • Prefix Caching(前缀缓存):命中共享前缀的请求复用已计算 KV,聊天多轮与 Agent 场景收益极大。

实践中「前缀缓存 + 连续批处理」组合最常见:多轮对话里每轮的 prompt 前缀(system prompt + 历史)可复用,命中后 TTFT 可以降到个位数毫秒。启用后观察 prefix_cache_hit_rate,命中率高说明前缀工程设计得当。

推理框架对比:vLLM / TensorRT-LLM / llama.cpp

框架定位优势适合场景
vLLM高性能在线服务连续批处理、PagedAttention、OpenAI 兼容 API、生态最全在线 API 服务、多模型切换
TensorRT-LLMNVIDIA 深度优化INT4/FP8 极致量化、内核融合、多卡并行最稳单机多卡、极致吞吐、自研推理栈
llama.cpp轻量跨平台CPU/GPU 通吃、GGUF 量化、低内存边缘设备、本地推理、单卡消费级
# llama.cpp 量化后运行(GGUF 格式)
# ./llama-cli -m models/qwen2.5-7b-instruct-q4_k_m.gguf \
#   -ngl 99 -c 8192 --prompt "你好"

选型建议:追求上线速度和生态,选 vLLM;追求单机极限吞吐与可控内核,选 TensorRT-LLM;资源受限或本地部署,选 llama.cpp。多模型场景下 vLLM 的模型切换与 LoRA 服务更省心。

前缀缓存与 Prompt 优化

**前缀缓存(Prefix Caching / Prompt Cache)**将 KV Cache 扩展到请求维度:相同的前缀只计算一次,后续请求直接复用。收益最明显的两类场景:

  • 多轮对话:历史轮次的 KV 全部可复用,长上下文续聊延迟从秒级降到毫秒级。
  • RAG/Agent 高频系统提示词:所有请求共享同一段 system prompt 与工具描述,缓存命中率极高。
# vLLM 前缀缓存开启(自动,观察命中率)
# python -m vllm.entrypoints.openai.api_server --model ... 
# 日志会输出 prefix cache hit rate;命中率低时考虑把 prompt 前缀设计成稳定的共享段

Prompt 优化的另一面是压缩输入:把重复的 few-shot 示例、工具说明收敛进一个共享模板,既降成本又提命中率。服务端可对超长历史做截断、摘要或滑窗,避免 prompt 无限膨胀吃掉 KV Cache。

延迟与吞吐指标:TTFT / TPOT / ITL

没有指标的优化是盲目的。LLM 推理的延迟指标要拆成三个阶段:

  • TTFT(Time To First Token):请求发出到第一个 token 返回的耗时,用户感知的「首字延迟」,主要由 prefill(提示词并行计算)决定。前缀缓存能把 TTFT 从秒级降到毫秒级。
  • TPOT(Time Per Output Token):每个输出 token 的平均耗时,反映解码阶段的速度,决定「打字机」的流畅度。
  • ITL(Inter-Token Latency):相邻输出 token 的时间间隔,对流式体验最直观。ITL 抖动会带来卡顿感。
# 观测指标(vLLM 日志 / 监控面板)
# TTFT: 首 token 延迟(要求 < 500ms 为佳)
# TPOT / ITL: 单 token 耗时(要求 < 40ms 为佳)
# Tokens/s: 聚合吞吐 = batch_size / TPOT

工程上建议同时监控吞吐与延迟分位(P50/P95/P99)。P95 延迟反映尾部体验,批次过大时 P95 会明显恶化——此时优先调小 max-num-seqs,或启用优先级调度保护在线请求。

生产部署与容量规划

容量规划的核心是把显存账单算清楚:权重、KV Cache 与激活是显存的三大去向。

# 单卡可承载并发估算(示意)
总显存 = 权重体积 + KV Cache 上限 + 激活/预留
可承载并发 ≈ KV 可用显存 / (单请求 KV 峰值)

# 例:80GB 卡,INT8 7B 权重约 7GB,预留 10GB,
#     KV 可用约 60GB,单请求 seq_len 4096 的 KV 约 0.4GB
#     → 单卡并发约 60 / 0.4 ≈ 150 路(理想)

经验法则:并发目标 × 峰值序列长度 → KV 显存 → 反推卡数与量化档位。长上下文应用(32K/128K)对 KV 需求是平方级增长,优先上 KV 量化与窗口注意力;短上下文高并发应用则更看重权重量化与连续批处理。

配套的工程实践还包括:多卡张量并行(TP)扩容单模型、LoRA 多任务复用基座、自动扩缩容(按 GPU 队列长度扩缩 Pod)、以及把首 token 优化交给前缀缓存而非单纯加卡。

总结

LLM 推理优化是一场「算显存账单」的系统工程:KV Cache 决定显存地板,PagedAttention 与连续批处理决定吞吐天花板,量化决定单 token 的成本,投机解码与前缀缓存决定延迟体验。落地时建议按「先量化 + 连续批处理提吞吐,再前缀缓存 + 投机解码压延迟,最后按 SLA 微调批大小与调度策略」的顺序推进,每一步用 TTFT/TPOT/ITL 与 P95 延迟量化收益。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. 类别不平衡与异常检测:从重采样到半监督方法
  2. Embedding 深入:对比学习、双塔架构与向量检索工程
  3. MLOps 治理与可复现:模型注册、漂移监控与合规