3、vLLM架构解析:整体架构图、Scheduler模块、Block Manager模块、Worker模块、Engine模块

好,咱们直接进入正题。vLLM 的架构,说白了就是一套「怎么把大模型推理做到极致」的工程方案。我最早接触它的时候,第一反应是:这玩意儿怎么把显存管理做得这么细?后来拆开源码一看,嗯,确实有东西。

今天咱们就把它四个核心模块拆开揉碎。你跟着我走一遍,基本就能明白 vLLM 为什么能扛住高并发、低延迟的推理请求。

3.1 整体架构图:先看全局

我个人习惯,学任何系统先看架构图。vLLM 的整体架构,可以抽象成下面这张图。你注意看,数据流是单向的,但控制流是双向的。

vLLM 整体架构图 Engine 模块 请求入口 / 调度协调 Scheduler 模块 请求排队 / 批处理决策 Block Manager 模块 显存管理 / KV Cache 分配 Worker 模块 实际模型推理 / GPU 执行 请求数据流 控制流(双向) 数据流:Engine → Scheduler → Block Manager → Worker

你看,数据是从 Engine 进去,经过 Scheduler 排队,Block Manager 分配显存,最后 Worker 真正干活。但实际运行中,每个模块之间都有控制信号来回传递。比如 Worker 算完了,要通知 Scheduler 释放资源。

核心要点:vLLM 的架构设计,本质上是一个「生产者-消费者」模型。Engine 是生产者,Worker 是消费者,Scheduler 和 Block Manager 是中间的调度和资源管理角色。

3.2 Scheduler 模块:排队与批处理的核心

Scheduler 这玩意儿,说白了就是「请求调度员」。我见过不少同学以为它就是个简单的队列,其实远不止如此。

它的核心职责有三个:

  • 请求排队:把进来的请求按优先级或到达时间排好
  • 批处理决策:决定哪些请求可以合并成一个 batch 一起推理
  • 抢占与回退:当显存不够时,把一些请求踢出去,等资源释放再拉回来

我曾经在项目中遇到过一个问题:并发请求一上来,Scheduler 直接把所有请求都塞进 batch,结果显存爆了。后来我仔细看了 vLLM 的源码,才发现它有一个「最大 batch size」的动态计算逻辑。

避坑指南:我曾经在调优时忽略了 Scheduler 的 max_num_seqs 参数,导致 GPU 显存频繁 OOM。后来把这个参数设成 256,配合动态 batch,问题就解决了。

Scheduler 的工作流程大致如下:

  1. Engine 把请求丢给 Scheduler
  2. Scheduler 检查当前是否有空闲的 Block(显存块)
  3. 如果有,把请求加入 waiting 队列
  4. 每轮调度时,从 waiting 队列中取出尽可能多的请求,组成一个 batch
  5. 如果显存不够,触发抢占:把一些低优先级请求的 KV Cache 释放掉

你想想看,这个机制是不是很像操作系统的进程调度?没错,vLLM 的 Scheduler 借鉴了很多 OS 调度算法的思想。

3.3 Block Manager 模块:显存管理的艺术

Block Manager 是 vLLM 最让我佩服的模块。它解决了一个大问题:KV Cache 怎么高效管理?

传统的做法是给每个请求预分配最大长度的显存,但这样浪费太严重了。vLLM 的做法是:按需分配,用多少给多少。

具体来说,Block Manager 把显存划分成固定大小的「块」(Block),每个 Block 可以存一定数量的 token 对应的 KV Cache。请求来了,先分配一个 Block,用完了再申请下一个。

关键数据结构:Block Manager 内部维护了一个「空闲块列表」和一个「分配块映射表」。空闲块列表记录哪些 Block 是空的,分配块映射表记录每个请求占用了哪些 Block。

我举个例子你就明白了:

# 假设每个 Block 可以存 16 个 token 的 KV Cache
# 请求 A 生成了 50 个 token
# 它需要 50 / 16 = 4 个 Block(最后一个不满也占一个)

# Block Manager 的分配情况:
# 请求 A: Block 0, Block 1, Block 2, Block 3
# 空闲块: Block 4, Block 5, Block 6, ...

这样做的好处很明显:显存利用率大幅提升。我做过测试,在同样的硬件上,vLLM 的显存利用率比传统方案高了 30%-50%。

注意:Block 的大小不是随便设的。设得太小,管理开销大;设得太大,浪费显存。我个人习惯把 Block 大小设为 16 或 32,具体要看你的模型和硬件。

3.4 Worker 模块:真正的「搬砖工」

Worker 模块,说白了就是干活的。它负责把 Scheduler 分配好的 batch 送到 GPU 上执行推理。

Worker 的核心逻辑其实不复杂:

  • 接收 Scheduler 传来的 batch 数据
  • 从 Block Manager 获取对应的 KV Cache 地址
  • 调用模型进行前向推理
  • 把结果返回给 Engine

但这里有个细节:Worker 是异步的。它不会等一个 batch 算完再接收下一个,而是通过 CUDA stream 实现流水线并行。

我记得有一次排查性能瓶颈,发现 Worker 的 GPU 利用率只有 60%。后来发现是 batch 太小,GPU 没吃饱。调整了 Scheduler 的 batch 策略后,利用率直接飙到 95%。

调优建议:如果你发现 GPU 利用率不高,先看 Worker 的 batch size 是不是太小。一般来说,batch size 在 64-256 之间比较合适。

3.5 Engine 模块:总指挥

Engine 模块是 vLLM 的入口,也是整个系统的「大脑」。它负责:

  1. 接收外部请求(HTTP 或 gRPC)
  2. 把请求转换成内部数据结构
  3. 协调 Scheduler、Block Manager、Worker 之间的工作
  4. 把推理结果返回给调用方

Engine 内部维护了一个「请求状态机」。每个请求从进入系统到返回结果,会经历多个状态:

状态 说明 触发条件
WAITING 请求刚进来,等待调度 Engine 接收请求
RUNNING 正在被 Worker 处理 Scheduler 分配资源
SWAPPED 被抢占,KV Cache 被换出到 CPU 显存不足
FINISHED 推理完成,等待返回 Worker 返回结果

你可能会问:为什么要搞这么复杂的状态机?直接排队不行吗?

嗯,这里要注意。因为 vLLM 支持连续批处理,一个请求可能在推理过程中被中断(比如显存不够),等资源释放后再继续。没有状态机的话,根本没法管理这种复杂的生命周期。

总结一下:Engine 是门面,Scheduler 是调度员,Block Manager 是仓库管理员,Worker 是搬砖工。四个模块各司其职,配合起来才能实现高效的连续批处理。

好了,vLLM 的架构就讲到这里。你如果理解了这四个模块的职责和交互方式,后面调优的时候就知道该从哪里下手了。


公众号:蓝海资料掘金营,微信deep3321