vLLM 这类推理框架为什么能提升吞吐?

深入原理性能优化约 6 分钟读完

一句话回答

生成阶段主要受显存带宽限制:同时处理的请求越多,读一遍权重能产出的 token 就越多,吞吐越高。朴素的实现有两个问题:静态批处理要等一批里最长的请求结束,新请求只能干等;KV Cache 按最大长度预留连续的显存,浪费严重,能同时跑的请求很少。vLLM 用连续批处理让请求随到随加入、完成即离开,用 PagedAttention 像操作系统分页一样按块管理 KV Cache,显存利用率高,批就能更大。此外还有张量并行、前缀缓存、推测解码等优化。

详细解析

为什么批处理能提升吞吐

生成阶段每一步都要把全部权重从显存读一遍。只处理 1 个请求时,读一遍权重只产出 1 个 token;同时处理 32 个请求,读一遍权重就能产出 32 个 token,而在显存带宽是瓶颈的范围内,这一步的耗时增加不多。所以吞吐取决于能同时跑多少请求,而能跑多少,又受限于 KV Cache 能用的显存。

连续批处理

文本
静态批处理:一批 4 个请求,要等最长的 A 结束,新请求 E 才能开始
  A ################
  B ####............    B 早就结束了,位置一直空着
  C ######..........
  D ##########......

连续批处理:每生成一步都重新安排
  A ################
  B ####EEEEEEEEEEEE    B 一结束,E 马上补上
  C ######FFFFFFFFFF
  D ##########GGGGGG

调度以"生成一步"为单位:每一步之后,完成的请求立即返回并释放资源,排队的请求立即加入。GPU 一直保持满载,新请求的排队时间也短了。新请求的 prefill(处理输入)计算量大,很多框架会把它切成小块,和其他请求的生成步骤混在一起执行(chunked prefill),避免卡住正在生成的请求。

PagedAttention

朴素实现给每个请求按最大长度预留一段连续的显存存放 KV Cache,大部分请求根本用不到这么长,造成大量浪费和碎片。PagedAttention 借鉴了操作系统的虚拟内存分页:

  • 把 KV Cache 切成固定大小的块(比如每块存 16 个 token),按需分配,物理上不必连续
  • 每个请求有一张块表,记录逻辑块到物理块的映射,计算注意力时按表读取
  • 浪费只发生在每个请求的最后一个块里
  • 多个请求有相同的前缀时(同一个系统提示、一次采样多个回答),可以共享同一批物理块,需要修改时再复制(写时复制)

省下来的显存用来容纳更多的并发请求,吞吐随之提升。

其他常见优化

优化 作用
张量并行 把每一层的权重矩阵切分到多张 GPU 上,单卡放不下的模型也能跑,需要卡间高速互联
前缀缓存 相同前缀的 KV Cache 跨请求复用,原理和 Prompt Caching 一样
推测解码 用小模型等方式一次猜出几个 token,大模型一次前向计算就能验证,猜对的直接接受,输出分布不变
量化 支持多种量化格式的权重和 KV Cache(见模型量化)
优化的注意力内核 如 FlashAttention,减少显存读写

其他框架的定位

  • SGLang:高性能的推理服务框架,用 RadixAttention 在请求之间自动复用前缀,适合前缀共享多的场景,比如 Agent、多轮对话
  • TGI(Text Generation Inference):Hugging Face 推出的推理服务
  • llama.cpp:用 C/C++ 实现,配合 GGUF 格式,能在 CPU、苹果芯片和消费级显卡上运行
  • Ollama:面向本地使用,封装了模型的下载、管理和简单的 API,适合开发和个人使用

这些框架大多提供兼容 OpenAI 格式的接口,业务代码基本不用改。选型看硬件、支持的模型、部署规模,以及在自己的负载下压测的结果。

代码示例

Shell
# MODEL 是 Hugging Face 上的模型名或本地路径
# --tensor-parallel-size:用几张 GPU 做张量并行
# --max-model-len:单个请求最多多少 token(输入加输出),不设时取模型配置里的值;
#   KV Cache 连一个这么长的请求都放不下时,启动会报错
# --gpu-memory-utilization:最多使用的显存比例,扣掉权重和运行开销,剩下的给 KV Cache
vllm serve "$MODEL" --tensor-parallel-size 2 --max-model-len 8192 --gpu-memory-utilization 0.9

# 和调用 OpenAI 格式的接口一样(默认端口 8000)
curl http://localhost:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model": "'"$MODEL"'", "messages": [{"role": "user", "content": "你好"}], "stream": true}'

面试官可能追问

吞吐高了,单个请求会变慢吗?

会有一点。批越大,每一步的计算量越大,每个请求的 token 间隔就会变长;新请求加入时的 prefill 也会占用算力。所以要在吞吐和延迟之间取舍:限制最大并发数、调整调度策略,在满足延迟要求的前提下把吞吐做到最大。

推测解码为什么不影响输出质量?

小模型只负责"猜",最终由大模型验证:大模型一次算出这几个位置的概率,按规则逐个决定接受还是拒绝,第一个被拒绝的位置,从修正后的分布(大模型概率减去小模型概率后的正数部分,再归一化)里重新采样。可以证明,这样得到的输出分布和大模型单独生成时相同。猜中率高时,一次前向计算能确认多个 token,速度就上去了;猜中率低时收益很小。

张量并行和流水线并行有什么区别?

张量并行把同一层的矩阵切开,多张卡同时计算同一层,每一层都要通信,需要 NVLink 这类高速互联,一般在单机多卡内使用。流水线并行把不同的层放到不同的卡上,通信少,适合跨机器,但一个请求要依次经过各段,需要多个请求同时在流水线里,才能提高利用率。

易错点

  • 以为推理框架让单个请求变快了,它主要提升的是并发吞吐
  • 以为显存放得下权重就够了,KV Cache 的空间决定了能同时跑多少请求
  • 拿别人的压测数字做选型:硬件、模型、请求长度不同,结果差别很大

AI 模拟面试官

用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮

登录后就可以和 AI 面试官对练,面试记录也会保存下来。登录

这道题你掌握了吗?

选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。

学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。