vLLM基础:架构、推理流程与显存管理机制
在深入排查显存泄漏之前,必须先建立对 vLLM 运行机制的整体认知。vLLM 之所以能实现高吞吐推理,核心在于它对 KV Cache 的精细管理,而显存泄漏问题也几乎都围绕这一机制展开。本节将拆解 vLLM 的三大支柱:架构组成、推理流程、显存管理。
2.1 vLLM 核心架构
vLLM 采用经典的 客户端-服务端 分层架构,各模块职责清晰,任何一层的异常都可能表现为显存异常增长。其核心组件如下:
- LLM Engine(推理引擎):负责模型加载、分词、前向计算与采样。它是显存消耗的主体,包含模型权重、激活值、KV Cache 三大部分。
- Scheduler(调度器):vLLM 的“大脑”,决定每个 step 将哪些请求的 token 送入模型。它维护着连续的 token 序列状态,并管理 KV Cache 的物理块分配。
- Block Manager(块管理器):显存分配的核心执行者。它将 KV Cache 划分为固定大小的物理块(默认 16 个 token/块),通过页表映射实现逻辑连续、物理离散的存储。
- Executor(执行器):负责将调度器输出的 token 批次实际送入 GPU 计算。在张量并行或多 GPU 场景下,它负责跨设备通信。
- API Server(HTTP 服务层):基于 FastAPI 实现,接收 OpenAI 兼容的请求,转换为内部 token 序列,并返回生成结果。
2.2 推理流程:从请求到输出
理解 vLLM 的推理流程,有助于定位显存增长发生在哪个阶段。整个流程分为 预填充(Prefill) 和 解码(Decode) 两个阶段,vLLM 通过 连续批处理(Continuous Batching) 将两者混合执行:
- 请求接入:API Server 接收请求,进行 tokenize,生成
Sequence对象。 - 序列注册:Scheduler 将新序列加入等待队列,并向 Block Manager 申请初始 KV Cache 物理块。
- Prefill 阶段:对序列的 全部输入 token 并行计算,生成首个输出 token,同时填充 KV Cache。此阶段计算密集,显存占用随输入长度线性增长。
- Decode 阶段:每次迭代只计算 1 个新 token,并更新 KV Cache。此阶段显存增长缓慢,但需要持续占用已分配的物理块。
- 调度决策:Scheduler 在每个 step 检查所有序列状态,决定哪些序列继续解码、哪些新序列进入 Prefill、哪些序列已终止(输出
<EOS>或达到最大长度)。 - 资源回收:终止序列的 KV Cache 物理块被释放,引用计数归零,归还给 Block Manager 的空闲池。
2.3 显存管理机制:PagedAttention 与物理块
vLLM 显存管理的基石是 PagedAttention 算法,它借鉴操作系统虚拟内存的分页思想,将 KV Cache 划分为固定大小的物理块(默认 block_size=16,即每个块存储 16 个 token 的 KV 向量)。每个序列维护一张 逻辑页表,记录其 KV 数据在物理块中的实际位置。
| 概念 | 说明 | 显存泄漏风险点 |
|---|---|---|
| 物理块(Physical Block) | GPU 显存中固定大小的连续区域,存储 KV 向量 | 块分配后未释放 |
| 逻辑页表(Page Table) | 每个序列维护的映射表,逻辑 token 位置 → 物理块编号 | 页表条目未随序列销毁而清除 |
| 引用计数(Ref Count) | 每个物理块被多少个序列共享(beam search 时多个序列可能共享块) | 计数未递减导致块永久占用 |
| 空闲块列表(Free List) | Block Manager 维护的可分配物理块集合 | 块泄漏后空闲列表逐渐缩短 |
Block Manager 的分配与回收逻辑如下:
# 伪代码:Block Manager 核心逻辑
class BlockManager:
def allocate_block(self, sequence_id):
if self.free_list.empty():
raise OutOfMemoryError("GPU 显存不足")
block = self.free_list.pop()
block.ref_count = 1
block.sequence_id = sequence_id
return block
def release_block(self, block):
block.ref_count -= 1
if block.ref_count == 0:
self.free_list.push(block) # 归还空闲池
block.sequence_id = None
当序列终止时,vLLM 会遍历其页表,对每个物理块调用 release_block。如果引用计数归零,块被回收。若此处存在逻辑缺陷(例如异常路径未调用释放函数),物理块将永久滞留,表现为显存随请求数线性增长。
2.4 典型场景:连续批处理下的显存波动
假设 GPU 总显存为 40GB,其中模型权重占 14GB,KV Cache 池上限设为 24GB(通过 gpu_memory_utilization=0.6 配置)。在正常运行时,显存占用曲线应呈 锯齿状:请求到达时上升,请求完成后下降。
一个典型的泄漏场景是:请求在生成过程中被客户端断开连接。此时 vLLM 的异步任务可能抛出异常,导致序列终止清理逻辑未完整执行。Block Manager 中该序列的物理块引用计数停留在 1,永远不会归零,块无法回到空闲列表。随着此类中断请求累积,可用 KV Cache 块逐渐减少,最终触发 OutOfMemoryError。
2.5 常见误区与排查方向
- 误区一:显存增长一定是模型权重泄漏。实际上,权重在加载后是静态的,不会随请求变化。持续增长几乎总是 KV Cache 或激活值问题。
- 误区二:重启服务就能解决,无需深究。重启只是暂时清空显存,如果根因是代码路径缺陷,泄漏会复现。必须定位到具体的未释放块来源。
- 误区三:只关注 GPU 显存总量,忽略 CPU 内存。vLLM 的 Block Manager 在 CPU 上也维护页表元数据,如果页表条目泄漏,CPU 内存也会增长,并间接影响 GPU 分配效率。
- 正确方向:使用
nvidia-smi监控显存曲线,结合 vLLM 的--log-stats参数输出 Block Manager 的块分配统计,对比空闲块数量变化趋势。