vLLM基础:架构、工作原理、推理流程及关键组件

在深入分析推理结果偏差之前,必须先建立对 vLLM 运行机制的系统认知。vLLM 并非简单的模型封装,而是一个以PagedAttention为核心、面向高吞吐与低延迟设计的推理引擎。其架构设计直接决定了 KV Cache 的管理方式、调度策略以及最终输出的数值行为。本节将拆解 vLLM 的四大核心模块,并梳理一次完整推理请求的生命周期。

2.1 整体架构:分层解耦的推理引擎

vLLM 采用经典的前端-调度-执行三层架构,各层职责清晰,通过异步队列解耦。前端负责协议解析与请求管理,调度层负责 KV Cache 分配与批处理决策,执行层则驱动 GPU 完成张量计算。这种分层设计使得 vLLM 能够在高并发下保持稳定的吞吐。

核心架构组件:

  • LLM Engine:面向 Python 开发者,提供 generate() 接口,封装了 tokenize、采样、反 tokenize 全流程。
  • Async LLM Engine:基于 asyncio 的事件循环,支持流式输出与并发请求,是 OpenAI 兼容服务器的基础。
  • Scheduler:全局调度器,维护等待队列与运行队列,决定每轮迭代(step)中哪些序列参与计算。
  • Worker:执行层单元,持有模型权重与 KV Cache 管理器,负责实际的 Transformer 前向传播。
  • KV Cache Manager:基于 PagedAttention 的显存管理模块,以“块”(block)为单位分配显存。
vLLM 推理引擎架构总览 前端层 (Frontend) LLM Engine 同步 generate() 接口 Async LLM Engine asyncio 事件循环 / 流式输出 OpenAI 兼容 Server HTTP / SSE 协议 请求队列 Sequence 请求/采样参数 调度层 (Scheduler) Scheduler 全局调度器 等待队列 / 运行队列管理 Continuous Batching 迭代级动态批调度 抢占 / 交换策略 显存不足时 Swap 或 Recompute 批处理 token 序列 执行层 (Executor / Worker) Worker 模型权重加载 Transformer 前向传播 张量并行 / 流水线并行 KV Cache Manager PagedAttention 块分配 块表 (Block Table) 管理 显存利用率优化 GPU 计算核心 QKV 投影 / Attention 计算 MLP 前馈网络 采样 (Sampling) 生成 token 序列 → 返回给前端

上图展示了 vLLM 的三层协作关系。前端层接收请求后,将其封装为内部 Sequence 对象并放入队列;调度层从队列中取出可执行的序列,基于显存状态决定批大小;执行层则按批次完成前向计算,并将新生成的 token 追加到对应的 KV Cache 中。值得注意的是,调度层与执行层之间是同步阻塞的,每一轮迭代(step)必须等待 GPU 计算完成才能继续。

2.2 核心原理:PagedAttention 与 KV Cache 管理

PagedAttention 是 vLLM 区别于传统推理框架的根本特性。传统框架在请求开始时为整个序列分配连续的 KV Cache 显存,导致内部碎片与预留浪费。vLLM 将 KV Cache 划分为固定大小的块(Block),每个块可存储若干个 token 的键值向量,通过块表将非连续的物理块映射为逻辑上连续的序列。

关键参数:块大小(block_size)默认为 16 或 32。较小的块减少内部碎片,但增加块表开销;较大的块提升连续读写效率,但可能浪费显存。实际部署中,block_size=16 是吞吐与显存利用率的常见平衡点。

这种设计带来了两个直接影响推理结果的行为:

  • 显存利用率提升:可以容纳更多并发序列,批大小增大,但每个序列的 KV Cache 可能被分散在多个物理块中,访问时需要查块表。
  • 数值行为变化:当序列长度超过初始分配的块数时,vLLM 会动态分配新块。若显存不足,调度器会触发抢占(Preemption),将部分序列的 KV Cache 交换到 CPU 内存(Swap)或直接丢弃后重算(Recompute)。这两种操作都会改变计算顺序,可能引入浮点累加顺序差异,进而导致输出 token 与无抢占场景不一致。

2.3 推理流程:从请求到输出的完整生命周期

一次完整的 vLLM 推理请求,在引擎内部经历以下阶段。理解每个阶段的耗时与潜在误差来源,是后续排查结果偏差的基础。

  1. 请求预处理:对输入文本进行 tokenize,转换为 token ID 序列,并初始化采样参数(temperature、top_p 等)。
  2. 序列初始化:为序列分配第一个 KV Cache 块,记录初始长度,加入调度器的等待队列。
  3. 调度决策:调度器从等待队列中选取序列,结合当前运行队列的显存占用,决定本轮迭代的批处理集合。
  4. 前向传播:执行层对批内所有序列的当前 token 进行 Transformer 计算,更新 KV Cache,并输出下一个 token 的 logits。
  5. 采样与解码:根据 logits 和采样参数选择下一个 token。若使用贪心解码,则直接取 argmax;若使用随机采样,则引入随机数。
  6. 终止判断:若序列生成 EOS token 或达到最大长度,则标记为完成,释放其 KV Cache 块。
  7. 输出后处理:将 token ID 序列反 tokenize 为文本,通过流式接口逐步返回给调用方。
阶段 主要耗时 潜在偏差来源
预处理 低(毫秒级) tokenizer 版本差异
调度 低(微秒级) 批大小变化影响计算图
前向传播 高(GPU 计算) 浮点累加顺序、Tensor Core 非确定性
采样 低(CPU/GPU) 随机种子、采样算法实现
后处理 低(毫秒级) 反 tokenize 映射

2.4 关键组件深入:调度器与采样器

调度器(Scheduler)和采样器(Sampler)是 vLLM 中与推理结果偏差最密切相关的两个组件。

调度器的三种策略

  • FCFS(先来先服务):按请求到达顺序调度,简单但可能造成队头阻塞。
  • SJF(短作业优先):优先处理输出长度短的请求,降低平均延迟,但需要预估长度。
  • 动态优先级:结合队列长度、显存水位、用户优先级综合决策,vLLM 默认采用此策略。

调度策略直接影响批内序列的长度分布。当批内序列长度差异较大时,GPU 计算需要 padding 到最大长度,导致短序列的 KV Cache 被无效填充,且 padding 操作本身不改变数值结果,但会改变 Attention 的 mask 模式,从而影响 softmax 的归一化分母——这是结果偏差的一个隐蔽来源。

采样器的确定性控制

vLLM 的采样器支持多种解码策略。在排查结果偏差时,需要特别关注以下参数:

# vLLM 采样参数示例
sampling_params = {
    "temperature": 0.8,        # 控制随机性,0 为贪心
    "top_p": 0.95,             # 核采样阈值
    "top_k": 50,               # 仅从概率最高的 k 个 token 中采样
    "max_tokens": 512,         # 最大生成长度
    "seed": 42,                # 随机种子,固定后结果可复现
    "repetition_penalty": 1.1  # 重复惩罚
}

重要提醒:即使固定了 seed,vLLM 在多 GPU 张量并行或批大小变化时,采样结果仍可能不同。这是因为随机数生成器的调用顺序与批内序列数量有关,且并行计算时各 rank 的 logits 归约顺序可能变化。

2.5 常见误区与排查思路

在理解 vLLM 架构后,可以澄清几个常见误区,这些误区也是后续章节分析偏差案例的切入点。

  • 误区一:vLLM 与 HuggingFace Transformers 输出完全一致。实际上,由于 KV Cache 管理方式、Attention 实现(如 FlashAttention vs. eager attention)和浮点累加顺序不同,两者输出可能在小数位后出现差异,极端情况下导致 token 选择不同。
  • 误区二:固定 seed 就能保证完全可复现。如前所述,批大小、抢占、并行策略都会影响随机数序列的消费顺序。
  • 误区三:KV Cache 只是性能优化,不影响结果。当发生 Swap 或 Recompute 时,KV Cache 的数值可能因精度损失而轻微变化,进而影响后续 token 的生成。
  • 误区四:增大批大小只会提升吞吐,不影响单请求结果。批内 padding 和 Attention mask 的变化会改变 softmax 分母,对低概率 token 的影响尤为明显。

在实际部署中,建议通过 vllm.engine.arg_utils.EngineArgs 显式设置 seed、block_size、enforce_eager 等参数,并在测试阶段固定批大小为 1 进行基线验证,再逐步增加并发以观察结果变化。这种控制变量法能有效定位偏差来源。