01

vLLM 全局架构概览

vLLM 是一个面向高吞吐量 LLM 推理的引擎,其核心设计哲学是关注点分离:用户接口、请求调度、内存管理、模型执行各层解耦,通过清晰的接口协议连接。整个系统可以分为以下六个主要层级:

六层架构速览
  • LLM(用户入口)vllm/entrypoints/llm.py — 面向离线批推理的高层封装,屏蔽引擎细节。
  • LLMEngine(推理引擎)vllm/engine/llm_engine.py — 核心调度枢纽,管理 Tokenizer、Scheduler、Executor。
  • Scheduler(调度器)vllm/core/scheduler.py — 维护三队列(waiting/running/swapped),决定每一步执行哪些序列、分配多少 KV-Block。
  • Executor(执行器)vllm/executor/ — 抽象执行后端,支持单 GPU(GPUExecutor)、多 GPU Ray(RayGPUExecutor)、CPU、Neuron 等。
  • Worker / ModelRunner(工作单元)vllm/worker/ — 每个 GPU 对应一个 Worker,负责管理 CacheEngine(物理 KV-cache 内存)并通过 ModelRunner 运行 forward pass。
  • BlockManager(显存管理)vllm/core/block_manager_v1.py / v2.py — PagedAttention 的物理块分配器,以 block 为粒度管理 GPU/CPU KV-cache。

下图展示了各层级之间的层次关系与调用方向:

graph TD subgraph UserFacing["用户层"] LLM["LLM
entrypoints/llm.py
离线推理入口"] ASYNC["AsyncLLMEngine
在线服务入口"] end subgraph EngineLayer["引擎层"] ENGINE["LLMEngine
engine/llm_engine.py
请求管理 · step() 主循环"] end subgraph ScheduleLayer["调度层"] SCHED["Scheduler
core/scheduler.py
waiting / running / swapped 三队列"] BM["BlockManager
core/block_manager_v1.py
PagedAttention KV-Block 分配"] end subgraph ExecutionLayer["执行层"] EXEC["Executor
executor/gpu_executor.py
单/多 GPU 调度"] WORKER["Worker
worker/worker.py
GPU 管理 · 分布式通信"] RUNNER["ModelRunner
worker/model_runner.py
Prefill / Decode forward"] end subgraph TokenLayer["Tokenize 层"] TOK["Tokenizer / Detokenizer
transformers_utils/"] end LLM -->|"from_engine_args()"| ENGINE ASYNC -->|"从 from_engine_args() 继承"| ENGINE ENGINE -->|"schedule()"| SCHED SCHED -->|"分配/回收 blocks"| BM ENGINE -->|"execute_model()"| EXEC EXEC -->|"execute_model()"| WORKER WORKER -->|"execute_model()"| RUNNER ENGINE -->|"encode/decode"| TOK

从调用链看,LLM 是对 LLMEngine 的薄封装,真正的工作都在 LLMEngine.step() 里完成:先调度(Scheduler.schedule()),再执行(Executor.execute_model()),最后处理输出(_process_model_outputs())。

离线 vs 在线两条路

LLMentrypoints/llm.py)用于离线批处理:同步调用 step() 直到所有请求完成,简单直接。 AsyncLLMEngineengine/async_llm_engine.py)用于在线服务:通过 asyncio 事件循环异步地向客户端流式返回 token,支持并发请求。 两者共用同一个 LLMEngine 核心,只是驱动方式不同。

02

LLM 类详解

LLM 是 vLLM 暴露给用户的最高层接口,位于 vllm/entrypoints/llm.py,全文 267 行。它的职责非常单纯:收集参数、创建引擎、提供 generate() 方法

2.1 __init__ 初始化流程

vllm/entrypoints/llm.py — LLM.__init__ L83-L128
@LLM_decorator
def __init__(
    self,
    model: str,
    tokenizer: Optional[str] = None,
    tokenizer_mode: str = "auto",
    skip_tokenizer_init: bool = False,
    trust_remote_code: bool = False,
    tensor_parallel_size: int = 1,
    dtype: str = "auto",
    quantization: Optional[str] = None,
    revision: Optional[str] = None,
    tokenizer_revision: Optional[str] = None,
    seed: int = 0,
    gpu_memory_utilization: float = 0.9,
    swap_space: int = 4,
    enforce_eager: bool = False,
    max_context_len_to_capture: Optional[int] = None,
    max_seq_len_to_capture: int = 8192,
    disable_custom_all_reduce: bool = False,
    **kwargs,
) -> None:
    if "disable_log_stats" not in kwargs:
        kwargs["disable_log_stats"] = True  # 离线批处理默认不输出统计日志
    engine_args = EngineArgs(
        model=model,
        ...  # 所有参数透传给 EngineArgs
    )
    self.llm_engine = LLMEngine.from_engine_args(
        engine_args, usage_context=UsageContext.LLM_CLASS)
    self.request_counter = Counter()  # 单调递增的请求 ID 计数器

初始化只做了三件事:

  1. 强制 disable_log_stats=True:离线批处理场景不需要 Prometheus 统计日志,默认关闭。
  2. 构建 EngineArgs 数据类:将用户传入的参数打包成 dataclass,为 create_engine_config() 做准备。
  3. 调用 LLMEngine.from_engine_args():解析配置、选择 Executor 类型、完成引擎初始化(加载模型权重、初始化 KV-cache)。
@LLM_decorator 是什么?

LLM_decorator 来自 vllm/entrypoints/llm_decorator.py,是一个厂商扩展点,允许在不修改 vLLM 核心代码的情况下对方法进行 monkey-patch(例如用于自定义推理前后处理)。在标准 vLLM 中它是一个透明的 identity decorator。

2.2 generate() 方法执行路径

generate() 是用户调用的核心方法,接受 prompts 列表,返回 List[RequestOutput]。它的执行路径可以分为两个阶段:请求提交引擎循环

vllm/entrypoints/llm.py — LLM.generate() L143-L225
@LLM_decorator
def generate(
    self,
    prompts: Optional[Union[str, List[str]]] = None,
    sampling_params: Optional[Union[SamplingParams, List[SamplingParams]]] = None,
    prompt_token_ids: Optional[List[List[int]]] = None,
    use_tqdm: bool = True,
    lora_request: Optional[LoRARequest] = None,
    multi_modal_data: Optional[MultiModalData] = None,
) -> List[RequestOutput]:
    # 1. 输入校验与归一化
    if isinstance(prompts, str):
        prompts = [prompts]  # 单条 prompt 转为列表
    if sampling_params is None:
        sampling_params = SamplingParams()  # 使用默认采样参数

    # 2. 逐条请求提交到引擎
    for i in range(num_requests):
        self._add_request(
            prompts[i], sampling_params_i, token_ids_i, ...)

    # 3. 驱动引擎循环直到所有请求完成
    return self._run_engine(use_tqdm)
vllm/entrypoints/llm.py — LLM._run_engine() L244-L267
@LLM_decorator
def _run_engine(self, use_tqdm: bool) -> List[RequestOutput]:
    outputs: List[RequestOutput] = []
    while self.llm_engine.has_unfinished_requests():
        step_outputs = self.llm_engine.step()  # 每次调用完成一个解码步
        for output in step_outputs:
            if output.finished:
                outputs.append(output)
    # 按 request_id 排序(因为请求可能不按顺序完成)
    outputs = sorted(outputs, key=lambda x: int(x.request_id))
    return outputs

这个 while 循环是离线推理的驱动引擎:每次 step() 调用对应一次 GPU forward pass(可能是 prefill 或 decode),直到所有请求的 finished 标志为 True。最后按 request_id 排序是为了保证返回结果的顺序与输入一致,因为短请求可能先于长请求完成。

2.3 _add_request 的工作

vllm/entrypoints/llm.py — LLM._add_request() L227-L242
@LLM_decorator
def _add_request(
    self,
    prompt: Optional[str],
    sampling_params: SamplingParams,
    prompt_token_ids: Optional[List[int]],
    lora_request: Optional[LoRARequest] = None,
    multi_modal_data: Optional[MultiModalData] = None,
) -> None:
    request_id = str(next(self.request_counter))  # "0", "1", "2", ...
    self.llm_engine.add_request(
        request_id, prompt, sampling_params,
        prompt_token_ids, lora_request=lora_request,
        multi_modal_data=multi_modal_data)

_add_request 的核心是生成单调递增的 request_id(字符串形式的整数),然后把请求委托给 LLMEngine.add_request()。这个 ID 贯穿请求的整个生命周期,用于排序、跟踪和取消。

sequenceDiagram participant User as 用户代码 participant LLM as LLM participant Engine as LLMEngine participant Sched as Scheduler User->>LLM: generate(prompts, sampling_params) loop 逐条 prompt LLM->>LLM: _add_request(prompt, ...) LLM->>Engine: add_request(request_id, prompt, ...) Engine->>Engine: encode_request() 分词 Engine->>Engine: 创建 Sequence + SequenceGroup Engine->>Sched: add_seq_group(seq_group) Sched-->>Sched: 入 waiting 队列 end loop 引擎循环 LLM->>Engine: step() Engine->>Sched: schedule() Sched-->>Engine: seq_group_metadata_list Engine->>Engine: execute_model() Engine-->>LLM: List[RequestOutput] end LLM-->>User: sorted outputs
03

EngineArgs 参数解析

EngineArgs 是 vLLM 的参数收集器,位于 vllm/engine/arg_utils.py(654 行)。它是一个 @dataclass,将所有引擎配置参数汇聚在一处,并提供 create_engine_config() 方法将参数分拆为若干专职 Config 对象。

3.1 EngineArgs 字段分类

vllm/engine/arg_utils.py — EngineArgs 字段 L22-L90
@dataclass
class EngineArgs:
    """Arguments for vLLM engine."""
    # 模型加载
    model: str
    tokenizer: Optional[str] = None
    tokenizer_mode: str = 'auto'
    trust_remote_code: bool = False
    download_dir: Optional[str] = None
    load_format: str = 'auto'          # 'auto'|'pt'|'safetensors'|'npcache'|'dummy'|'tensorizer'
    revision: Optional[str] = None

    # 数值精度
    dtype: str = 'auto'                # 'auto'|'float16'|'bfloat16'|'float32'
    kv_cache_dtype: str = 'auto'       # KV-cache 独立精度,支持 fp8

    # 显存与块
    max_model_len: Optional[int] = None
    block_size: int = 16               # PagedAttention 块大小(token 数),可选 8/16/32
    gpu_memory_utilization: float = 0.90  # GPU 显存使用比例
    swap_space: int = 4                # CPU swap 空间 (GiB)
    enable_prefix_caching: bool = False
    use_v2_block_manager: bool = False

    # 并行
    tensor_parallel_size: int = 1
    pipeline_parallel_size: int = 1
    worker_use_ray: bool = False

    # 调度
    max_num_batched_tokens: Optional[int] = None  # 每步最多处理的 token 数
    max_num_seqs: int = 256            # 每步最多并发序列数
    scheduler_delay_factor: float = 0.0
    enable_chunked_prefill: bool = False

    # 量化
    quantization: Optional[str] = None  # 'awq'|'gptq'|'squeezellm'|'fp8'|None

    # 运行模式
    enforce_eager: bool = False        # 禁用 CUDA Graph,纯 eager 模式
    max_seq_len_to_capture: int = 8192 # CUDA Graph 覆盖的最大序列长度

    # LoRA
    enable_lora: bool = False
    max_loras: int = 1
    max_lora_rank: int = 16

    # 推测解码(Speculative Decoding)
    speculative_model: Optional[str] = None
    num_speculative_tokens: Optional[int] = None

    # 视觉语言模型
    image_input_type: Optional[str] = None
    image_token_id: Optional[int] = None

3.2 create_engine_config():参数分拆为专职 Config 对象

create_engine_config()EngineArgs 最关键的方法(L521-L616),它将扁平的参数字典拆分为 7 个专职配置对象,每个对象只关心自己的领域:

vllm/engine/arg_utils.py — create_engine_config() L521-L616
@preprocess_engine_config
def create_engine_config(self) -> EngineConfig:
    device_config = DeviceConfig(self.device)          # CPU/GPU/Neuron 设备类型

    model_config = ModelConfig(                        # 模型架构、精度、上下文长度
        self.model, self.tokenizer, self.tokenizer_mode,
        self.trust_remote_code, self.dtype, self.seed,
        self.revision, ..., self.max_model_len,
        self.quantization, ...)

    cache_config = CacheConfig(                        # KV-cache 块大小、显存占比、swap
        self.block_size, self.gpu_memory_utilization,
        self.swap_space, self.kv_cache_dtype,
        self.num_gpu_blocks_override,
        model_config.get_sliding_window(),
        self.enable_prefix_caching)

    parallel_config = ParallelConfig(                  # TP/PP 并行度、Ray 配置
        self.pipeline_parallel_size, self.tensor_parallel_size,
        self.worker_use_ray, ...)

    speculative_config = SpeculativeConfig.maybe_create_spec_config(...)  # 推测解码

    scheduler_config = SchedulerConfig(                # 调度策略参数
        self.max_num_batched_tokens, self.max_num_seqs,
        model_config.max_model_len, ...,
        enable_chunked_prefill=self.enable_chunked_prefill)

    lora_config = LoRAConfig(...) if self.enable_lora else None

    load_config = LoadConfig(                          # 权重加载格式、路径
        load_format=self.load_format,
        download_dir=self.download_dir, ...)

    decoding_config = DecodingConfig(                  # 引导解码后端
        guided_decoding_backend=self.guided_decoding_backend)

    return EngineConfig(
        model_config=model_config, cache_config=cache_config,
        parallel_config=parallel_config, scheduler_config=scheduler_config,
        device_config=device_config, lora_config=lora_config,
        speculative_config=speculative_config, load_config=load_config,
        decoding_config=decoding_config)

3.3 关键参数深析

block_size(默认 16)

PagedAttention 的核心参数。KV-cache 以 block 为粒度分配,每个 block 存储 block_size 个 token 的 K、V 向量。block_size=16 意味着每次至少分配 16 个 token 的 KV 空间,内碎片最大为 15 个 token。增大 block_size 提高 GPU 内存利用率但增加内碎片;减小则相反。

gpu_memory_utilization(默认 0.9)

vLLM 启动时进行一次 profile run:先加载模型权重,然后用 gpu_memory_utilization * 总显存 - 已用显存 计算出可以分配多少个 KV-cache block,这个数字直接决定了系统能并发处理的最大序列长度之和。设置过高可能 OOM,过低浪费显存。

max_num_batched_tokens 与 max_num_seqs

这两个参数共同约束调度器每一步的工作量:
max_num_batched_tokens:一次 forward pass 最多处理的 token 数(prefill+decode 之和)。
max_num_seqs:同时运行的序列数上限(默认 256)。
调度器在每个 step 选取序列时,必须同时满足这两个约束。enable_chunked_prefill=True 时,一个长 prefill 可以跨多个 step 分块处理,从而与 decode 请求混合批处理。

enforce_eager vs CUDA Graph

默认情况下(enforce_eager=False),vLLM 对长度较短的序列使用 CUDA Graph 捕获固定的计算图以消除 GPU kernel launch overhead,对超出 max_seq_len_to_capture(默认 8192)的序列回退到 eager 模式。enforce_eager=True 完全禁用 CUDA Graph,适合调试或显存紧张场景。

04

从 LLM.generate() 到 LLMEngine 的数据流全景

本节追踪一条请求从用户调用 LLM.generate("Hello, world!") 到最终拿到 RequestOutput 的完整路径,重点关注数据如何被封装、变换和传递。

4.1 LLMEngine 的初始化:from_engine_args()

generate() 触发之前,LLMEngine.from_engine_args() 已经完成了引擎初始化。这个过程是 vLLM 启动最耗时的部分(加载权重、profile GPU 显存):

vllm/engine/llm_engine.py — from_engine_args() L266-L300
@classmethod
def from_engine_args(cls, engine_args: EngineArgs, ...) -> "LLMEngine":
    engine_config = engine_args.create_engine_config()  # 分拆为各种 Config 对象

    # 根据配置选择 Executor 类型
    if device_config.device_type == "neuron":
        executor_class = NeuronExecutor
    elif device_config.device_type == "cpu":
        executor_class = CPUExecutor
    elif parallel_config.worker_use_ray:
        initialize_ray_cluster(...)
        executor_class = RayGPUExecutor
    else:
        assert parallel_config.world_size == 1
        executor_class = GPUExecutor   # 单 GPU 场景默认路径

    engine = cls(**engine_config.to_dict(),
                 executor_class=executor_class, ...)
    return engine

LLMEngine.__init__ 的执行顺序(L86-L242):

  1. 初始化 Tokenizer(L150-L156):创建 BaseTokenizerGroupDetokenizer
  2. 创建 model_executor(L162-L172):根据选定的 executor_class 实例化执行器,执行器内部立即调用 _init_executor(),进而创建 Worker,加载模型权重到 GPU。
  3. 初始化 KV-cache(L174):调用 _initialize_kv_caches(),让 executor 做一次 profile run,确定 GPU/CPU 可用的 block 数,然后分配物理 KV-cache 内存。
  4. 创建 Scheduler(L219):此时 cache_config.num_gpu_blocks 已被更新,Scheduler 拿到准确的资源数量初始化 BlockManager。
  5. 创建 output_processor(L231-L242):根据调度配置决定是否使用 beam search / speculative decoding 的输出处理器。
vllm/engine/llm_engine.py — _initialize_kv_caches() L244-L264
def _initialize_kv_caches(self) -> None:
    # Executor 在 worker 上运行 profile_run,测量实际显存占用
    num_gpu_blocks, num_cpu_blocks = (
        self.model_executor.determine_num_available_blocks())

    # 允许通过 num_gpu_blocks_override 强制指定块数(用于测试抢占)
    if self.cache_config.num_gpu_blocks_override is not None:
        num_gpu_blocks = self.cache_config.num_gpu_blocks_override

    self.cache_config.num_gpu_blocks = num_gpu_blocks
    self.cache_config.num_cpu_blocks = num_cpu_blocks
    # 分配物理 KV-cache 内存
    self.model_executor.initialize_cache(num_gpu_blocks, num_cpu_blocks)

4.2 add_request():请求封装为 SequenceGroup

用户的 prompt 字符串在 LLMEngine.add_request() 中完成从文本到数据结构的转变:

vllm/engine/llm_engine.py — add_request() L358-L466
@nvtx_tag
def add_request(self, request_id: str, prompt: Optional[str],
                sampling_params: SamplingParams, ...) -> None:
    arrival_time = time.time()  # 记录到达时间(用于调度优先级)

    # Step 1: 分词(tokenize)
    prompt_token_ids = self.encode_request(
        request_id=request_id, prompt=prompt, ...)

    # Step 2: 创建 Sequence 对象
    seq_id = next(self.seq_counter)
    eos_token_id = self.tokenizer.get_lora_tokenizer(...).eos_token_id
    seq = Sequence(seq_id, prompt, prompt_token_ids,
                   block_size, eos_token_id, lora_request)

    # Step 3: clone SamplingParams(防止多个请求共享同一对象)
    sampling_params = sampling_params.clone()
    sampling_params.update_from_generation_config(self.generation_config_fields)

    # Step 4: 创建 SequenceGroup
    seq_group = SequenceGroup(request_id, [seq], sampling_params,
                              arrival_time, lora_request, multi_modal_data)

    # Step 5: 提交到 Scheduler
    self.scheduler.add_seq_group(seq_group)  # 进入 waiting 队列
Sequence vs SequenceGroup

Sequence:单条序列,包含 token ID 列表、已分配的 KV-block 列表、当前状态(WAITING/RUNNING/SWAPPED/FINISHED)。
SequenceGroup:同一 prompt 派生出的所有序列(best_of=N 时产生 N 条)。调度器以 SequenceGroup 为粒度决策,但以 Sequence 为粒度分配 KV-block。对于普通请求(best_of=1),一个 SequenceGroup 只含一条 Sequence。

4.3 step():一次解码迭代的三步骤

LLMEngine.step()(L556-L630)是整个系统的心跳,每次调用完成一次 prefill 或 decode 迭代:

vllm/engine/llm_engine.py — step() L556-L630
@nvtx_tag
def step(self) -> List[RequestOutput]:
    # ===== Step 1: 调度 =====
    seq_group_metadata_list, scheduler_outputs = self.scheduler.schedule()
    # scheduler_outputs 包含:
    #   scheduled_seq_groups: 本步骤要运行的序列组
    #   blocks_to_swap_in:    CPU -> GPU 的 block 迁移
    #   blocks_to_swap_out:   GPU -> CPU 的 block 迁移(抢占/swap)
    #   blocks_to_copy:       copy-on-write 的 block 复制(beam search)

    # ===== Step 2: 执行模型 =====
    if not scheduler_outputs.is_empty():
        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,
            num_lookahead_slots=scheduler_outputs.num_lookahead_slots,
            running_queue_size=scheduler_outputs.running_queue_size,
        )
        output = self.model_executor.execute_model(
            execute_model_req=execute_model_req)
        # output: List[SamplerOutput],每个 SamplerOutput 包含采样到的 token
    else:
        output = []

    # ===== Step 3: 处理输出 =====
    request_outputs = self._process_model_outputs(
        output,
        scheduler_outputs.scheduled_seq_groups,
        scheduler_outputs.ignored_seq_groups,
        seq_group_metadata_list)

    self.do_log_stats(scheduler_outputs, output)
    return request_outputs

4.4 数据流全景图

flowchart TD A["用户调用 LLM.generate(prompts)"] --> B["LLM._add_request() x N"] B --> C["LLMEngine.add_request()"] C --> C1["encode_request(): 分词 → token_ids"] C1 --> C2["创建 Sequence(token_ids, block_size, eos_token_id)"] C2 --> C3["创建 SequenceGroup(request_id, [seq], sampling_params)"] C3 --> C4["Scheduler.add_seq_group() → waiting 队列"] C4 --> D["while has_unfinished_requests(): LLMEngine.step()"] D --> E["Step 1: Scheduler.schedule()"] E --> E1["检查 waiting 队列 → 分配 KV-blocks → 加入 running 队列"] E1 --> E2["构造 SequenceGroupMetadata 列表"] E2 --> E3["决策 swap-in / swap-out / copy 操作"] E3 --> F["Step 2: Executor.execute_model(ExecuteModelRequest)"] F --> F1["Worker 执行 KV-cache swap 操作"] F1 --> F2["ModelRunner.execute_model()"] F2 --> F3["Prefill(prompt tokens) 或 Decode(last token)"] F3 --> F4["Sampler 采样 → SamplerOutput(next_token_id)"] F4 --> G["Step 3: _process_model_outputs()"] G --> G1["output_processor.process_outputs(): 追加新 token 到 Sequence"] G1 --> G2["检查停止条件(EOS / max_tokens / stop_strings)"] G2 --> G3["finished 序列 → RequestOutput"] G3 --> H["返回 List[RequestOutput] 给 LLM._run_engine()"] H --> I["所有请求完成 → 按 request_id 排序 → 返回用户"]

4.5 SequenceGroupMetadata:引擎层与执行层的数据契约

SequenceGroupMetadata 是调度器向执行层传递信息的核心数据结构,包含:

  • request_id:请求 ID
  • is_prompt:是否是 prefill 阶段
  • seq_data:每条 Sequence 的 token ID 和已生成 token 数
  • sampling_params:采样参数
  • block_tables:每条 Sequence 已分配的 KV-block 映射表(逻辑块 → 物理块)
  • token_chunk_size:本次 forward 处理的 token 数(chunked prefill 场景下可小于 prompt 长度)

BlockManager 在调度阶段完成逻辑块到物理块的映射,ModelRunner 拿到 block_tables 后直接用物理块地址做 PagedAttention,无需了解调度逻辑。

05

设计哲学

理解 vLLM 的设计需要回答一个问题:为什么要这样分层?每一层的存在都有其深刻的工程动机。

5.1 为什么 LLM 和 LLMEngine 要分开?

一个引擎,两种驱动方式

LLMEngine 本身是无状态驱动的——它不会自己跑循环,需要外部调用 step()。这个设计使得同一个引擎可以被两种截然不同的方式驱动:

  • 离线批处理LLM):用一个同步 while 循环驱动,简单粗暴,适合脚本场景。
  • 在线服务AsyncLLMEngine):在 asyncio 事件循环中异步驱动,支持请求动态到达、流式输出、并发取消,适合生产服务。

如果引擎自带循环,就无法支持这种双模式。

5.2 为什么 EngineArgs 要转换为多个 Config 对象?

EngineArgs 是面向用户的接口(CLI 参数、Python API),强调易用性,所以参数扁平化放在一起。而 ModelConfigCacheConfigSchedulerConfig 等是面向内部组件的接口,强调关注点分离

  • Scheduler 只需关心 SchedulerConfig,不需要看到模型 dtype。
  • ModelRunner 只需关心 ModelConfig,不需要看到 swap_space 大小。
  • 测试时可以只构造相关的 Config 对象,不需要完整的 EngineArgs。

这种"用户友好的扁平接口 → 内部专职对象"的模式在大型系统中极为常见。

5.3 为什么 Executor 要抽象出来?

ExecutorBase 的抽象层(vllm/executor/executor_base.py)实现了执行后端无关性LLMEngine 只调用 executor.execute_model(),完全不知道下面是单 GPU、多 GPU Ray 集群、CPU 还是 Neuron 芯片。这使得:

  • 新增硬件支持只需实现一个新的 Executor,不需要修改引擎逻辑。
  • 分布式推理(RayGPUExecutor)的复杂性被完全封装在 Executor 层。
  • 测试时可以用 Mock Executor 验证调度逻辑,无需真实 GPU。
vllm/executor/executor_base.py — ExecutorBase 接口 L43-L75
class ExecutorBase(ABC):
    @abstractmethod
    def determine_num_available_blocks(self) -> Tuple[int, int]:
        """Profile GPU to determine available KV-cache blocks."""
        ...

    @abstractmethod
    def initialize_cache(self, num_gpu_blocks: int, num_cpu_blocks: int) -> None:
        """Allocate KV-cache memory."""
        ...

    @abstractmethod
    def execute_model(self, execute_model_req: ExecuteModelRequest
                      ) -> List[SamplerOutput]:
        """Run forward pass, return sampled tokens."""
        ...

5.4 为什么 Scheduler 要独立于 Engine?

调度逻辑(选哪些序列、分配多少 block、何时抢占)是 vLLM 性能的核心,也是最容易变化的部分。将其独立为 Scheduler 有几个好处:

  • 可测试性:Scheduler 的逻辑可以在不启动 GPU 的情况下单独测试。
  • 可替换性:可以实现不同的调度策略(Default、Chunked Prefill、Speculative Decoding 感知调度)而不影响 Engine 代码。
  • 关注点清晰:调度决策(要执行什么)和执行(如何执行)完全解耦。

5.5 整体设计原则总结

vLLM 的核心设计原则
  1. 迭代级调度(Iteration-level Scheduling):每个 step() 都重新调度,而不是为每个请求预留固定时间片。这使得短请求不会被长请求阻塞,GPU 始终保持高利用率。
  2. PagedAttention(分页注意力):KV-cache 不按序列长度预分配,而是按需分配物理 block,彻底消除外部碎片,显著提高 GPU 显存利用率,这是 vLLM 高吞吐量的根本来源。
  3. 层次解耦:用户接口 → 引擎 → 调度器 → 执行器 → Worker,每层只向下层发出请求,不感知实现细节。
  4. 配置与代码分离:所有可调参数集中在 EngineArgs / Config 对象中,核心代码路径不包含硬编码配置。

理解了这五点设计哲学,再阅读 vLLM 任何其他模块的代码,都能快速找到该模块在整体架构中的位置和职责边界。