AI 见闻
精选· 重要性 4/5

vLLM内部解析:高吞吐量LLM推理系统剖析

Hacker News (AI)··sebg·约 7 分钟阅读
社区热度 146
中文导读

本文深入解析vLLM这一高吞吐量LLM推理系统的核心组件与高级特性,从分页注意力、连续批处理到多GPU扩展,为理解现代推理引擎提供清晰框架。

vLLM内部解析:高吞吐量LLM推理系统剖析从分页注意力、连续批处理、前缀缓存、推测解码等到多GPU、多节点大规模动态服务2025年8月29日在本文中,我将逐步介绍构成现代高吞吐量LLM推理系统的所有核心系统组件和高级功能。

特别是,我将详细解析vLLM [1]的工作原理。这篇文章是一个系列的第一篇。它从宏观入手,然后层层深入(采用倒金字塔方法),以便你能形成对整个系统的准确高层心智模型,而不会迷失在细节中。后续文章将深入探讨特定子系统。

本文分为五个部分:- LLM引擎与引擎核心:vLLM的基础(调度、分页注意力、连续批处理等)- 高级特性:分块预填充、前缀缓存、引导与推测解码、分离式P/D- 扩展:从单GPU到多GPU执行- 服务层:分布式/并发Web脚手架- 基准测试与自动调优:

测量延迟和吞吐量- 分析基于提交42172ad(2025年8月9日)。- 目标读者:任何对最先进LLM引擎工作原理感兴趣的人,以及有兴趣为vLLM、SGLang等项目贡献代码的人。- 我将重点介绍V1引擎。

我也探索了V0(现已弃用),这对理解项目演进很有价值,许多概念仍然适用。

- 关于LLM引擎/引擎核心的第一部分可能有点令人不知所措或枯燥——但博客其余部分有大量示例和视觉效果。:)LLM引擎与引擎核心LLM引擎是vLLM的基本构建模块。它本身已经实现了高吞吐量推理——但仅限于离线场景。

你暂时无法通过网络向客户提供服务。我们将使用下面的离线推理片段作为运行示例(改编自basic.py)。

from vllm import LLM,SamplingParamsprompts = ["Hello,my name is","The president of the United States is",

]sampling_params = SamplingParams(temperature=0.8,

top_p=0.95)def main():llm = LLM(model="TinyLlama/TinyLlama-1.1B-Chat-v1.0")outputs = llm.generate(prompts,

sampling_params)if __name__ == "__main__":main()- VLLM_USE_V1="1" # 我们使用引擎V1- VLLM_ENABLE_V1_MULTIPROCESSING="0" # 我们在单进程中运行此配置为:

- 离线(无Web/分布式系统脚手架)- 同步(所有执行都在单个阻塞进程中完成)- 单GPU(无数据/模型/流水线/专家并行;

DP/TP/PP/EP = 1)- 使用标准Transformer [2](支持Jamba等混合模型需要更复杂的混合KV缓存分配器)从这里开始,我们将逐步构建一个在线、异步、多GPU、多节点的推理系统——但仍为标准Transformer提供服务。

在这个例子中,我们做两件事:- 实例化引擎- 调用generate函数,根据给定提示词进行采样让我们开始分析构造函数。

LLM引擎构造函数引擎的主要组件包括:- vLLM配置(包含配置模型、缓存、并行度等的所有旋钮)- 处理器(通过验证、分词和处理,将原始输入转换为EngineCoreRequests)- 引擎核心客户端(在我们的运行示例中,我们使用InprocClient,

它基本上等同于EngineCore;我们将逐步构建到DPLBAsyncMPClient,它支持大规模服务)- 输出处理器(将原始EngineCoreOutputs转换为用户看到的RequestOutput)引擎核心本身由几个子组件组成:- 模型执行器(驱动模型的前向传播,

我们目前处理的是UniProcExecutor,它在单个GPU上有一个Worker进程)。我们将逐步构建到支持多个GPU的MultiProcExecutor- 结构化输出管理器(用于引导解码——我们稍后会介绍)- 调度器(决定哪些请求进入下一个引擎步骤)——它还包含:

- 策略设置——可以是FCFS(先来先服务)或优先级(高优先级请求优先服务)- 等待队列和运行队列- KV缓存管理器——分页注意力的核心 [3]KV缓存管理器维护一个free_block_queue——可用KV缓存块池(通常有数十万个,

具体取决于VRAM大小和块大小)。

在分页注意力期间,这些块作为索引结构,将token映射到其计算出的KV缓存块。

2(键/值)* block_size(默认=16)* num_kv_heads * head_size * dtype_num_bytes(例如bf16为2)在模型执行器构建期间,会创建一个Worker对象,并执行三个关键过程。

(稍后,使用MultiProcExecutor时,这些相同的过程会在不同GPU上的每个工作进程上独立运行。)- 初始化设备:- 为worker分配CUDA设备(例如"cuda:

0")并检查模型dtype是否受支持(例如bf16)- 根据请求的gpu_memory_utilization(例如0.8→总VRAM的80%)验证是否有足够的VRAM可用- 设置分布式设置(DP/TP/PP/EP等)- 实例化model_runner(保存采样器、

KV缓存和前向传播缓冲区,如input_ids、positions等)- 实例化InputBatch对象(保存CPU端前向传播缓冲区、用于KV缓存索引的块表、采样元数据等)- 加载模型:

- 实例化模型架构- 加载模型权重- 调用model.eval()(PyTorch的推理模式)- 可选:在模型上调用torch.compile()- 初始化KV缓存- 获取每层KV缓存规范。

历史上,这总是FullAttentionSpec(同质Transformer),但随着混合模型(滑动窗口、Transformer/SSM如Jamba)的出现,它变得更加复杂(参见Jenga [5])- 运行一次虚拟/剖析前向传播,并拍摄GPU内存快照,

以计算可用VRAM中能容纳多少个KV缓存块- 分配、

重塑并将KV缓存张量绑定到注意力层- 准备注意力元数据(例如将后端设置为FlashAttention),供内核在前向传播期间使用- 除非提供--enforce-eager,否则对每个预热批次大小进行虚拟运行并捕获CUDA图。

CUDA图将整个GPU工作序列记录到DAG中。稍后在前向传播期间,我们启动/重放预烘焙的图,减少内核启动开销,从而改善延迟。- 获取每层KV缓存规范。历史上,这总是我在这里抽象了许多低级细节——但这些是我现在要介绍的核心部分,因为我将在以下部分中反复引用它们。

现在我们已经初始化了引擎,让我们继续generate函数。Generate函数第一步是验证请求并将请求送入引擎。

对于每个提示词,我们:- 创建唯一的请求ID并捕获其到达时间- 调用输入预处理器,对提示词进行分词,并返回包含prompt、prompt_token_ids和type(文本、token、嵌入等)的字典

)- 将此信息打包到EngineCoreRequest中,添加优先级、采样参数和其他元数据- 将请求传递到引擎核心,引擎核心将其包装在Request对象中,并将其状态设置为WAITING。

然后,此请求被添加到调度器的等待队列中(如果是FCFS则追加,如果是优先级则堆推)此时,引擎已被喂入,执行可以开始。在同步引擎示例中,这些初始提示词是我们唯一要处理的——没有在运行中注入新请求的机制。

相比之下,异步引擎支持这一点(即连续批处理 [6]):在每一步之后,新请求和旧请求都会被考虑。接下来,只要有请求需要处理,引擎就会重复调用其step()函数。

每个步骤有三个阶段:- 调度:选择在此步骤中运行哪些请求(解码,和/或(分块)预填充)- 前向传播:运行模型并采样token- 后处理:将采样的token ID追加到每个Request,进行去分词,并检查停止条件。

如果请求

原文出处
Inside vLLM: Anatomy of a High-Throughput LLM Inference System (2025)

本文为机器翻译辅以 AI 润色,仅供参考。原始事实以原文为准。

相关阅读