2. vLLM基础回顾:架构、推理流程与关键组件

在深入排查推理结果一致性之前,必须先建立对 vLLM 工作方式的清晰认识。vLLM 并非简单地封装了 HuggingFace Transformers 的 generate() 接口,而是实现了一套完整的高性能推理服务系统。它的核心设计目标是在高吞吐场景下维持低延迟,而这套设计中的每一个环节——从调度器到显存管理——都可能成为影响结果一致性的潜在变量。

2.1 vLLM 整体架构与推理流程

vLLM 采用经典的客户端/服务端架构,服务端内部可划分为三个关键层次:API 层、调度执行层和显存管理层。一次完整的推理请求从进入服务到返回结果,会经历以下核心阶段:

  1. 请求接收与 Tokenize:HTTP 请求经 FastAPI 网关进入,由 LLMEngine 接收,将输入文本转换为 token ID 序列。
  2. 序列分组与调度:Scheduler 将多个请求的序列按 continuous batching 策略动态组合成 step,决定当前迭代执行哪些序列。
  3. 模型前向计算:将 batch 中的 token 输入 GPU,执行 Transformer 层的并行计算,生成 logits。
  4. 采样与解码:根据 sampling_params(temperature、top_p 等)从 logits 中采样下一个 token。
  5. KV Cache 管理与更新:新生成的 token 对应的 Key/Value 张量被写入 PagedAttention 管理的显存块中。
  6. 结果返回:若序列触发停止条件(EOS、max_tokens),则从缓存中取出完整 token 序列并 Detokenize 返回。
关键认知:vLLM 的调度是 动态且非确定性的。同一时刻,不同请求可能处于不同的解码阶段,GPU 上同时存在 prefill(预填充)和 decode(解码)的计算。这种动态性意味着,即使两次请求的输入完全相同,如果它们被调度到不同的 batch 组合中,浮点累加顺序也可能不同,从而引入微小的数值差异。
vLLM 推理流程与结果一致性影响点 ① 请求接入 Tokenize + 参数解析 ② 连续批处理调度 动态组batch · prefill/decode ③ Transformer前向 MatMul + Attention ④ 采样解码 temperature/top_p ⑤ 返回 Detokenize PagedAttention 显存管理 逻辑块 ↔ 物理块映射 · 按需分配 · 引用计数 ⚠ 块分配顺序影响显存布局 → 影响数值累加顺序 写入KV 显存状态反馈 结果一致性影响点 • 动态batch改变浮点累加顺序 • KV Cache块布局影响Attention • 采样随机性(seed) 排查一致性时的关注重点: 1. 调度器是否将相同请求放入不同 batch → 对比单请求与并发请求的输出 2. PagedAttention 的物理块分配是否因显存碎片而变化 → 检查 block 映射表 3. 采样参数中的 seed 是否固定 → 未固定 seed 时每次结果天然不同 4. 模型权重加载的确定性 → 检查是否启用 torch.use_deterministic_algorithms(True)

2.2 PagedAttention 与 KV Cache 的机制

PagedAttention 是 vLLM 区别于传统推理框架的核心创新。传统框架为每个请求预分配固定大小的 KV Cache 显存,导致显存浪费严重;而 PagedAttention 将 KV Cache 划分为固定大小的物理块(block),通过逻辑块到物理块的映射表实现按需分配。这种设计带来了两个直接影响:

  • 显存利用率提升:接近零浪费的显存管理,使得同一 GPU 上可以并发更多请求。
  • 计算模式改变:Attention 计算不再连续,而是通过块索引间接访问内存,这引入了额外的访存开销,也使得浮点运算顺序与块布局强相关。

从一致性角度看,KV Cache 的物理布局并非固定不变。当显存碎片化或请求动态加入/退出时,新分配的物理块可能位于不同的显存地址。虽然数学上 Attention 结果与存储位置无关,但浮点数的加法不满足结合律,不同顺序的累加会产生 1e-6 量级的差异。在长序列生成中,这种微小差异会通过自回归过程逐步累积,最终导致 token 选择不同。

2.3 关键组件对结果一致性的影响矩阵

组件 功能定位 一致性影响机制 排查建议
Scheduler 动态组batch、决定prefill/decode batch组成变化 → 矩阵乘法维度不同 → 累加顺序变化 固定请求数、固定并发压力测试
PagedAttention KV Cache块管理 物理块地址变化 → 访存顺序变化 → Attention累加差异 对比不同显存占用下的输出
Sampler 从logits采样token 未固定seed时,随机数序列不同 设置 seed 并对比
模型权重 前向计算参数 权重加载顺序、量化方式影响数值 使用同一权重文件、关闭量化

2.4 典型场景:并发请求下的输出不一致

假设你向 vLLM 服务发送两次完全相同的请求,第一次单独发送,第二次与另外 7 个请求并发发送。你可能会发现两次输出存在差异。这并非 bug,而是 vLLM 动态调度机制的正常表现。具体原因如下:

# 场景模拟
# 请求A:单独发送,batch_size=1,prefill阶段计算完整
# 请求A':与B~H并发,batch_size=8,prefill阶段与其他请求混合计算

# 在 batch_size=1 时,Attention 计算为:
#   out = softmax(Q @ K^T / sqrt(d)) @ V
# 在 batch_size=8 时,Q、K、V 矩阵的行数变为8倍,
# 矩阵乘法的分块策略(tiling)可能改变,导致浮点累加顺序不同。

这种差异在 fp16 精度下尤为明显,因为半精度浮点数的有效位数仅约 3 位十进制数字。若使用 fp32 或 bf16,差异会减小但不会完全消失。

实践建议:在排查一致性时,不要将“并发与单发结果不同”直接判定为错误。首先固定 seed,然后对比 --dtype float32 与默认 float16 的输出差异。若差异消失,则说明是浮点精度问题;若差异仍在,则需检查调度逻辑或权重加载。

2.5 常见误区与复盘

在理解 vLLM 架构时,初学者常陷入以下误区:

  • 误区一:认为 vLLM 与 HuggingFace 的 generate() 完全等价。实际上,vLLM 重写了 Attention 计算和采样循环,即使设置完全相同的参数,输出也可能与 Transformers 库不同。
  • 误区二:忽略 KV Cache 的显存碎片化。当服务运行较长时间后,物理块分配会变得碎片化,新请求获得的块位置与刚启动时不同,导致 Attention 计算顺序变化。
  • 误区三:将采样随机性误认为模型错误。若未设置 seed,vLLM 每次采样都使用新的随机数,输出自然不同。这不是一致性问题,而是随机采样的预期行为。
复盘要点:当遇到“相同输入不同输出”时,请按以下顺序排查:① 确认 seed 是否固定;② 对比单请求与并发请求的输出;③ 检查是否启用了确定性算法(torch.use_deterministic_algorithms(True));④ 尝试切换 dtype 观察差异是否缩小。只有完成上述步骤,才能定位到真正的根因。