vLLM 服务特性:架构原理、显存管理与连续批处理
大家好,我是你们的 SRE 讲师。今天我们来聊聊 vLLM 这个家伙。
说实话,我第一次接触 vLLM 是在一次线上推理服务压测中。当时模型跑得好好的,突然 OOM 了。我查了半天,发现是显存碎片化的问题。后来换了 vLLM,问题就解决了。嗯,从那时起,我就开始认真研究它了。
一、vLLM 的架构原理
vLLM 本质上是一个高性能的大模型推理引擎。它专门为 LLM 设计,核心目标就一个:把显存用到极致。
它的架构可以拆成三层:
- 调度层:负责管理请求的排队、优先级和批处理策略。
- 显存管理层:这是 vLLM 的灵魂,后面我会细讲。
- 执行层:调用 GPU 做真正的矩阵运算。
你想想看,传统的推理框架是怎么做的?每个请求来了,分配一块固定大小的显存,用完就释放。这就像你去图书馆,每次只借一本书,但每次都占一个独立阅览室。浪费不浪费?
vLLM 的做法不一样。它把显存当成一个共享池,动态分配,按需使用。说白了,就是「显存共享」的思想。
核心要点:vLLM 的架构设计,本质上是在做「显存资源的精细化调度」。它不是简单的批处理,而是把显存切成了小块,按需分配。
二、显存管理机制:PagedAttention
vLLM 最牛的地方,就是它的显存管理机制——PagedAttention。
这个名字你可能听过。它借鉴了操作系统的虚拟内存分页思想。什么意思呢?
传统方式下,每个请求的 KV Cache(键值缓存)是连续存储的。比如一个请求生成了 100 个 token,那它的 KV Cache 就占一块连续的大显存。如果另一个请求来了,显存不够了,那就只能等。
vLLM 把 KV Cache 切成了固定大小的「页」(Page)。每个页可以独立分配,不要求连续。就像操作系统把物理内存分成 4KB 的页框一样。
我举个例子:
传统方式:
请求A: [KV Cache 块,连续 100MB]
请求B: [KV Cache 块,连续 100MB]
—— 如果显存只剩 50MB,请求B 只能排队。
vLLM 方式:
请求A: [页1][页2][页3]...(分散在显存各处)
请求B: [页4][页5]...(插空分配)
—— 显存碎片也能用,利用率大幅提升。
我在项目中遇到过这样一个场景:一个 13B 的模型,用传统方式部署,显存占用率一直在 85% 左右,但实际有效利用率只有 60%。换成 vLLM 后,显存利用率直接飙到 95% 以上。这就是 PagedAttention 的威力。
个人建议:如果你在部署大模型时遇到 OOM,先别急着加 GPU。试试 vLLM,很多时候显存碎片才是元凶。
三、连续批处理(Continuous Batching)
连续批处理,是 vLLM 另一个让我拍大腿的设计。
传统批处理是怎么做的?等一批请求攒够了,一起推理。比如攒 32 个请求,一起跑。但问题是,每个请求的生成长度不一样。有的生成 10 个 token 就结束了,有的要生成 100 个。那 GPU 就得等最慢的那个请求跑完,才能处理下一批。
这就像食堂打饭,必须等所有人打完菜,才能开始吃。效率能高吗?
vLLM 的连续批处理,说白了就是「边推理边调度」。一个请求生成完了,立刻把它踢出批处理,把新的请求加进来。GPU 不用等,一直在干活。
我给你们画个对比图:
传统批处理:
| 请求A(10 token) | 请求B(50 token) | 请求C(100 token) |
|-----------------|-----------------|------------------|
| 等待B和C完成... | 等待C完成... | 完成 |
—— GPU 空闲时间:40%
连续批处理:
| 请求A(10 token) | 请求D(20 token) | 请求E(30 token) |
| 请求B(50 token) | 请求B(40 token) | 请求F(20 token) |
| 请求C(100 token)| 请求C(90 token) | 请求C(80 token) |
—— GPU 空闲时间:接近 0%
我曾经在线上压测过,连续批处理能让吞吐量提升 2-3 倍。特别是当请求的生成长度差异很大的时候,效果更明显。
注意:连续批处理虽然好,但也不是万能的。如果所有请求的生成长度都差不多,那优势就不明显了。另外,它需要调度器有很强的实时决策能力,对 CPU 开销有一定要求。
四、为什么需要故障转移?
好了,前面讲了 vLLM 有多牛。但再牛的系统,也会出问题。
我遇到过几次典型的故障:
- GPU 卡死:某次模型推理时,GPU 突然 hang 住,所有请求都卡在那。vLLM 本身没有自动恢复机制。
- 显存泄漏:虽然 PagedAttention 很高效,但某些极端场景下,显存回收不及时,导致 OOM。
- 节点宕机:K8s 节点挂了,vLLM Pod 直接消失,所有正在推理的请求全部丢失。
你想想看,如果 vLLM 服务没有故障转移,会发生什么?
- 用户请求超时,体验极差。
- 模型状态丢失,需要重新加载权重,耗时几分钟。
- 业务中断,影响收入。
所以,故障转移不是「要不要」的问题,而是「必须做」的问题。
我个人习惯的做法是:
- 多副本部署:至少 2 个 vLLM 副本,分布在不同的 K8s 节点上。
- 健康检查:配置 readiness probe 和 liveness probe,vLLM 挂了自动重启。
- 请求重试:客户端配置重试策略,遇到 5xx 错误自动切换到其他副本。
- 状态持久化:如果模型有状态(比如对话历史),用 Redis 或数据库保存,不要放在 vLLM 内存里。
一句话总结:vLLM 解决了显存效率和吞吐量的问题,但故障转移解决的是可用性和可靠性的问题。两者缺一不可。
五、知识体系图
下面我用一张 SVG 图,把 vLLM 的核心逻辑串起来。你可以保存下来,以后做架构设计时参考。
这张图把 vLLM 的三个核心模块串起来了。架构原理是骨架,显存管理是心脏,连续批处理是肌肉。而故障转移,就是那个「安全气囊」——平时用不上,但关键时刻能救命。
我的经验:在 K8s 上部署 vLLM 时,我建议你先把这张图打印出来贴在工位上。每次做架构设计时,对照着看一遍,能少踩很多坑。