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 的三层协作关系。前端层接收请求后,将其封装为内部 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 推理请求,在引擎内部经历以下阶段。理解每个阶段的耗时与潜在误差来源,是后续排查结果偏差的基础。
- 请求预处理:对输入文本进行 tokenize,转换为 token ID 序列,并初始化采样参数(temperature、top_p 等)。
- 序列初始化:为序列分配第一个 KV Cache 块,记录初始长度,加入调度器的等待队列。
- 调度决策:调度器从等待队列中选取序列,结合当前运行队列的显存占用,决定本轮迭代的批处理集合。
- 前向传播:执行层对批内所有序列的当前 token 进行 Transformer 计算,更新 KV Cache,并输出下一个 token 的 logits。
- 采样与解码:根据 logits 和采样参数选择下一个 token。若使用贪心解码,则直接取 argmax;若使用随机采样,则引入随机数。
- 终止判断:若序列生成
EOStoken 或达到最大长度,则标记为完成,释放其 KV Cache 块。 - 输出后处理:将 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 进行基线验证,再逐步增加并发以观察结果变化。这种控制变量法能有效定位偏差来源。