Skip to content

v1/v0 区别 ​

vLLM V0 与 V1 是两代推理引擎架构,并非模型格式或推理算法的两个版本。V1 复用了 V0 已验证的模型实现、GPU Kernel、分布式控制面和工具代码,主要重构 API Server、Scheduler、KV Cache Manager、Worker、Sampler 之间的边界,以降低每轮推理的 CPU 开销并统一不同优化的执行路径。

本文以 v0.7.3 中并存的 V0、V1 代码为基准比较两套架构,避免把后续 V1 的功能演进混入核心设计差异。V1 自 v0.8.0 起成为默认引擎;V0 在 v0.9 冻结,并从 v0.10 开始移除。因此,当前版本中的 vllm.v1 是正式实现,不应再把 VLLM_USE_V1 当作需要手动开启的实验开关。

1. 核心差异 ​

维度V0V1
引擎边界LLMEngine 同时编排调度、模型执行和输出处理前端负责输入与输出,EngineCore 只保留调度、KV Cache 和模型执行主循环
调度单位以 SequenceGroup、Prefill/Decode 阶段和队列状态组织请求以 Request 的待计算 Token 数组织请求,不在调度层区分 Prefill 与 Decode
调度结果传递序列元数据以及 Block 的 Swap/Copy 操作以 {request_id: num_scheduled_tokens} 表达工作量,并向 Worker 发送请求增量
Worker 状态每轮重建并传递较多输入元数据Worker 缓存请求状态并维护 Persistent Batch,只应用相邻批次的差异
Tensor ParallelScheduler 与 Worker 0 同进程,Worker 角色不对称Scheduler 与所有 Worker 解耦,各 Worker 使用对称执行路径
KV Cache 抢占支持重计算或 GPU/CPU Swap释放被抢占请求的 Block,恢复时重计算,删除核心路径中的 Swap 状态
Prefix Caching哈希与 LRU 管理的 CPU 开销较高,默认关闭使用常数时间的 Block 操作并减少 Python 对象创建,默认开启
默认优化Chunked Prefill、Prefix Caching 等能力由不同条件或开关启用将 Chunked Prefill、Prefix Caching、torch.compile 等能力纳入统一默认路径

两者都使用 Continuous Batching、Paged KV Cache 和相近的模型 Kernel。V1 的重点不是改变 Transformer 前向计算,而是缩短两次 GPU 前向之间的 CPU 临界路径。

2. 执行架构 ​

2.1 V0 引擎循环 ​

V0 的在线服务可以把 API Server 放在独立进程中,但引擎内部仍以 LLMEngine 为中心。一次 step() 需要依次完成调度、构造 ExecuteModelRequest、调用分布式 Executor、处理采样结果和更新 SequenceGroup:

text
API Server
  → AsyncLLMEngine
    → LLMEngine
      → Scheduler
      → Model Executor
        → Worker 0 / Worker 1 / ...
      → Output Processing
📄 vllm/engine/llm_engine.py#L1262-L1392
python
def step(self):
    if not self._has_remaining_steps(seq_group_metadata_list):
        (
            seq_group_metadata_list,
            scheduler_outputs,
            allow_async_output_proc,
        ) = self.scheduler[virtual_engine].schedule()

    execute_model_req = ExecuteModelRequest(
        seq_group_metadata_list=seq_group_metadata_list,
        blocks_to_swap_in=scheduler_outputs.blocks_to_swap_in,
        blocks_to_swap_out=scheduler_outputs.blocks_to_swap_out,
        blocks_to_copy=scheduler_outputs.blocks_to_copy,
        ...
    )
    outputs = self.model_executor.execute_model(
        execute_model_req=execute_model_req,
    )

这条路径功能完整,但请求状态、Cache 操作、并行执行和输出状态相互耦合。为摊薄每 Token 的 Python 开销,V0 又引入 Multi-Step Scheduling、异步输出处理等旁路,进一步增加了状态同步的复杂度。

2.2 V1 前后端分离 ​

V1 把引擎拆成前端与 EngineCore。前端的 Processor 完成 Tokenization 和多模态预处理,OutputProcessor 完成 Detokenization、停止字符串检查与流式输出;独立进程中的 EngineCore 处理 Token 级终止条件,并将主循环收敛为 schedule → execute_model → update_from_output:

text
API Server / AsyncLLM
  → Processor
  → EngineCoreClient
    ⇄ ZMQ / MessagePack
  → EngineCore
    → Scheduler + KV Cache Manager
    → Model Executor
      → Worker 0 / Worker 1 / ...
  → OutputProcessor
  → Streaming Response
📄 vllm/v1/engine/core.py#L39-L157
python
class EngineCore:
    """Inner loop of vLLM's Engine."""

    def __init__(self, vllm_config, executor_class, log_stats):
        self.model_executor = executor_class(vllm_config)
        self.scheduler = Scheduler(...)

    def step(self):
        scheduler_output = self.scheduler.schedule()
        model_output = self.model_executor.execute_model(scheduler_output)
        return self.scheduler.update_from_output(
            scheduler_output,
            model_output,
        )

Tokenization、图片预处理、Detokenization 和响应流式传输可以与 EngineCore 的下一轮调度及 GPU 执行重叠。隔离后的核心循环不需要感知 HTTP、Tokenizer 或每个连接的输出队列,职责和故障边界也更清楚。

2.3 Worker 对称性 ​

V0 为减少 Tensor Parallel 场景的进程间通信,将 Scheduler 与 Worker 0 放在同一进程,造成 Worker 0 与其他 Worker 的执行路径不对称。V1 让每个 Worker 缓存请求状态,Scheduler 首次发送完整请求,此后只发送新增 Token、新增 Block 和结束请求等差异,因此能够把 Scheduler 与 Worker 0 分离,而不必每轮广播完整状态。

调度结果中的两类数据体现了这项设计:

  • NewRequestData:新请求的 Prompt、采样参数、初始 Block 等完整信息。
  • CachedRequestData:已缓存请求的 new_token_ids、new_block_ids 和 num_computed_tokens 等增量信息。

SchedulerOutput 分别携带新请求与已缓存请求的数据。这种状态下沉使单 GPU 与多 GPU 可以复用同一套 Worker 抽象,也为后续扩展不同 Executor 和并行策略提供了稳定边界。

3. 统一调度 ​

3.1 Token 预算 ​

V0 的 Scheduler 显式维护 waiting、running、swapped 队列,并在数据结构中区分 Prefill 与 Decode。SchedulerOutputs 除了已调度的 SequenceGroup,还包含 blocks_to_swap_in、blocks_to_swap_out、blocks_to_copy 和 num_lookahead_slots 等操作。Chunked Prefill、Prefix Caching 与 Speculative Decoding 需要分别嵌入这些状态分支。

V1 把 Prompt Token、已生成 Token 和 Speculative Token 统一视为请求尚待计算的 Token。对请求 i,设目标 Token 总数为 Nitarget,已完成计算的 Token 数为 Nicomputed,则本轮最多需要处理:

Nipending=Nitarget−Nicomputed

Scheduler 再根据剩余 Token 预算、KV Cache 容量和 Encoder 预算选择本轮数量 Si:

0≤Si≤Nipending,∑iSi≤Tbudget

输出可以简化为 {request_id: S_i}。当 Si 覆盖 Prompt 的一部分时,它是 Chunked Prefill;覆盖新生成的一个 Token 时,它是普通 Decode;覆盖 Draft Token 时,它又能表达 Speculative Decoding。统一的是调度表示,不是 Attention Kernel 的计算模式:Model Runner 仍会根据各请求的 Query 长度、上下文长度和 Block Table 构造正确的 Attention Metadata。

📄 vllm/v1/core/scheduler.py#L100-L139
python
# There's no "decoding phase" nor "prefill phase" in the scheduler.
# Each request just has the num_computed_tokens and num_tokens_with_spec.
num_scheduled_tokens: Dict[str, int] = {}
token_budget = self.max_num_scheduled_tokens

for request in self.running:
    num_new_tokens = (
        request.num_tokens_with_spec - request.num_computed_tokens
    )
    num_new_tokens = min(num_new_tokens, token_budget)
    ...
    num_scheduled_tokens[request.request_id] = num_new_tokens

3.2 抢占策略 ​

V0 的显存不足处理包含 Swap 与 Recompute 两套策略,因此 Scheduler 必须维护 GPU、CPU 两级 Block 状态。V1 的核心路径选择更简单的重计算:显存不足时释放低优先级请求的 KV Block,将其 num_computed_tokens 归零并放回等待队列;恢复后可先匹配 Prefix Cache,再重算未命中的部分。

这项选择删除了每轮调度中的 Swap-In/Swap-Out 状态以及 GPU 与 CPU 之间的 KV 传输,但被抢占请求会付出重新 Prefill 的计算代价。因此它降低的是控制面复杂度,不代表抢占没有成本。

4. KV Cache 管理 ​

V0 与 V1 的 Automatic Prefix Caching 都基于 Token Block 哈希与 LRU 淘汰,差别主要在元数据实现。V0 在 Cache 命中率较低时也可能产生明显 CPU 开销,所以该功能默认关闭。V1 的 KVCacheBlock 直接保存引用计数、Block Hash 和空闲链表指针;FreeKVCacheBlockQueue 使用双向链表,使中间 Block 的移除为 O(1),操作时不创建额外链表节点。

text
Hash → cached Block
          │
          ├─ ref_cnt > 0:被请求引用,不可淘汰
          └─ ref_cnt = 0:位于 Free Block LRU Queue,可复用或淘汰

V1 因而可以把 Prefix Caching 放入默认执行路径。即使没有 Cache Hit,也只承担较小的哈希和元数据维护成本;命中时则直接复用完整 Block。该变化不修改 PagedAttention 的物理 KV Cache 布局,只重写 Block 的分配、共享和回收方式。

📄 vllm/v1/core/kv_cache_utils.py#L95-L221
python
@dataclass
class KVCacheBlock:
    block_id: int
    ref_cnt: int = 0
    _block_hash: Optional[BlockHashType] = None
    prev_free_block: Optional["KVCacheBlock"] = None
    next_free_block: Optional["KVCacheBlock"] = None

class FreeKVCacheBlockQueue:
    # Doubly linked list: removing an arbitrary free block is O(1).
    def remove(self, block: KVCacheBlock) -> None:
        ...

更多内存分页原理见 PagedAttention;基于 Radix Tree 的另一种 Prefix 复用实现见 Radix Attention。

5. Model Runner ​

V0 每轮根据调度结果重新创建输入 Tensor 与 Attention Metadata。模型较小、GPU 前向较快或并发较高时,这部分 Python 与 Host-to-Device 开销容易成为 GPU 两轮执行之间的气泡。

V1 在 Worker 内维护 Persistent Batch 和请求缓存:连续两轮中仍然存在的请求保留其槽位,只移除已结束或未调度的请求,追加新请求、Token 和 Block。更新过程尽量使用 NumPy 与预分配 Tensor,并且只有批次结构变化时才重新复制采样元数据。

📄 vllm/v1/worker/gpu_model_runner.py#L235-L417
python
def _update_states(self, scheduler_output: "SchedulerOutput") -> None:
    # Remove finished and unscheduled requests from the persistent batch.
    ...
    # Cache complete state for requests scheduled for the first time.
    for new_req_data in scheduler_output.scheduled_new_reqs:
        req_id = new_req_data.req_id
        self.requests[req_id] = CachedRequestState(...)

    # Apply only token and block deltas to cached requests.
    for req_data in scheduler_output.scheduled_cached_reqs:
        req_id = req_data.req_id
        req_state = self.requests[req_id]
        req_state.num_computed_tokens = req_data.num_computed_tokens
        ...
        if not req_data.resumed_from_preemption:
            req_state.block_ids.extend(req_data.new_block_ids)
        else:
            req_state.block_ids = req_data.new_block_ids

这套稳定输入结构还承接两项默认优化:

  • torch.compile:自动融合和优化模型计算图,减少为不同模型手写 Kernel 的需求。
  • Piecewise CUDA Graph:按可编译计算片段捕获 CUDA Graph,在保留动态图边界的同时减少 Kernel Launch 开销。

V1 并不是单独叠加这些优化,而是让 Scheduler 的统一 Token 表示、Worker 的增量状态和编译执行路径共享同一批次模型。相比 V0 的多个专用旁路,这种纵向整合更容易组合 Chunked Prefill、Prefix Caching、Speculative Decoding 与多模态请求。

6. 多模态路径 ​

V0 的多模态预处理可能阻塞引擎循环,且图片特征与文本 Prefill 的生命周期耦合:视觉 Embedding 在当轮使用后被丢弃时,文本 Prompt 难以跨轮执行 Chunked Prefill。

V1 将图片加载、裁剪和预处理移到核心循环之外,并引入两类缓存:

  • 预处理缓存:复用相同图片产生的模型输入。
  • Encoder Cache:暂存视觉 Encoder 输出,使较长的文本部分可以分块 Prefill。

Prefix Cache 的键还可包含多模态输入 Hash,因此共享图片的多轮请求能够复用相应 KV Block。多模态能力由此进入 Scheduler、KV Cache 与输入处理的统一路径,而不再只是模型执行前的特殊分支。

7. 性能与代价 ​

V1 的主要收益来自 CPU 而非模型 Kernel。官方 Alpha 基准在 Llama 3.1 8B、Llama 3.3 70B 和 ShareGPT 负载上报告,相对未启用 Multi-Step Scheduling 的 V0,V1 的吞吐最高提升 1.7×;两者使用的 Kernel 基本相同。这个数字只代表该组模型、硬件与负载,不能直接外推到任意部署。

架构重写也带来新的成本与行为边界:

  • 进程资源:在线服务需要 API Server、EngineCore 和每 GPU 一个 Worker,必须为 IPC、序列化和 CPU 线程预留资源。
  • 显存开销:V1 的 CUDA Graph 捕获通常比 V0 占用更多显存。
  • 重计算开销:删除核心 KV Swap 路径后,被抢占请求可能重新计算 Prompt。
  • 接口语义:V1 默认返回模型原始输出上的 Logprobs;需要经过 Logits Processor 的值时,应显式选择相应模式。
  • 功能取舍:best_of、请求级自定义 Logits Processor、GPU/CPU KV Cache Swapping 等 V0 能力未按原接口保留。

因此,V1 的结论不是“每个请求固定加速”,而是以更短、更稳定的 CPU 临界路径换取更高的吞吐上限和更易组合的架构。实际收益仍取决于模型大小、GPU 速度、请求长度分布、Cache 命中率和服务并发度。

8. 源码边界 ​

阅读源码时需要先确认版本。v0.7.3 同时包含两套实现,适合做一一对照:

模块V0V1
引擎循环vllm/engine/llm_engine.pyvllm/v1/engine/core.py
异步引擎vllm/engine/async_llm_engine.pyvllm/v1/engine/async_llm.py
Schedulervllm/core/scheduler.pyvllm/v1/core/scheduler.py
KV Cachevllm/core/block_manager.pyvllm/v1/core/kv_cache_manager.py
GPU Model Runnervllm/worker/model_runner.pyvllm/v1/worker/gpu_model_runner.py

V0 删除后,当前仓库中的兼容类名或 vllm/engine 路径不等于 V0 实现;判断代码代际应以它最终连接的 vllm.v1.engine.EngineCore、Scheduler 和 Worker 为准。

9. 参考 ​

  1. vLLM V1: A Major Upgrade to vLLM's Core Architecture
  2. vLLM V1 User Guide
  3. vLLM Architecture Overview
  4. RFC: Deprecating vLLM V0
  5. vLLM V0 LLMEngine(v0.7.3)
  6. vLLM V1 EngineCore(v0.7.3)
  7. vLLM V1 Scheduler(v0.7.3)
  8. vLLM V1 Prefix Caching Design