架构概览¶
本文档概述了vLLM的架构。
入口点¶
vLLM提供了多个与系统交互的入口点。下图展示了它们之间的关系。
LLM 类¶
LLM类提供了进行离线推理的主要Python接口,即在不使用单独模型推理服务器的情况下与模型交互。
以下是LLM类的使用示例:
Code
from vllm import LLM, SamplingParams
# Define a list of input prompts
prompts = [
"Hello, my name is",
"The capital of France is",
"The largest ocean is",
]
# Define sampling parameters
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
# Initialize the LLM engine with the OPT-125M model
llm = LLM(model="facebook/opt-125m")
# Generate outputs for the input prompts
outputs = llm.generate(prompts, sampling_params)
# Print the generated outputs
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
更多API详情可在API文档的离线推理部分查阅。
LLM 类的代码可以在 vllm/entrypoints/llm.py 中找到。
OpenAI兼容的API服务器¶
vLLM的第二个主要接口是通过其兼容OpenAI的API服务器。可以使用vllm serve命令启动此服务器。
vllm 命令行工具的代码位于 vllm/entrypoints/cli/main.py。
有时您可能会看到直接使用API服务器入口点,而不是通过vllm CLI命令。例如:
该代码可在 vllm/entrypoints/openai/api_server.py中找到。
有关API服务器的更多详情,请参阅OpenAI-Compatible Server文档。
LLM引擎¶
LLMEngine 和 AsyncLLMEngine 类是 vLLM 系统的核心组件,负责处理模型推理和异步请求处理。
LLM引擎¶
LLMEngine类是vLLM引擎的核心组件。它负责接收客户端的请求并从模型生成输出。LLMEngine包含输入处理、模型执行(可能分布在多个主机和/或GPU上)、调度和输出处理。
- 输入处理: 使用指定的分词器处理输入文本的分词。
- 调度: 决定每个步骤处理哪些请求。
- 模型执行: 管理语言模型的执行过程,包括跨多个GPU的分布式执行。
- 输出处理: 处理模型生成的输出,将语言模型产生的令牌ID解码为人类可读的文本。
LLMEngine的代码可以在 vllm/engine/llm_engine.py中找到。
AsyncLLMEngine¶
AsyncLLMEngine类是LLMEngine类的异步封装器。它利用asyncio创建一个后台循环,持续处理传入的请求。AsyncLLMEngine专为在线服务设计,能够处理多个并发请求并将输出流式传输给客户端。
OpenAI兼容的API服务器使用AsyncLLMEngine。此外,在 vllm/entrypoints/api_server.py中还提供了一个更简单的示例演示API服务器。
AsyncLLMEngine的代码可以在 vllm/engine/async_llm_engine.py中找到。
工作节点¶
工作进程是运行模型推理的进程。vLLM遵循使用一个进程控制一个加速器设备(如GPU)的通用做法。例如,如果我们使用大小为2的张量并行和大小为2的流水线并行,总共会有4个工作进程。工作进程通过它们的rank和local_rank来标识。rank用于全局协调,而local_rank主要用于分配加速器设备以及访问本地资源(如文件系统和共享内存)。
模型运行器¶
每个工作进程都有一个模型运行器对象,负责加载和运行模型。大部分模型执行逻辑都集中在此处,例如准备输入张量和捕获cudagraphs。
模型¶
每个模型运行器对象都有一个模型对象,即实际的torch.nn.Module实例。关于不同配置如何影响我们最终获得的类,请参阅huggingface_integration。
类层次结构¶
下图展示了vLLM的类层次结构:
这个类层次结构背后有几个重要的设计选择:
1. 可扩展性: 层次结构中的所有类都接受包含所有必要信息的配置对象。VllmConfig类是传递的主要配置对象。类层次结构相当深入,每个类都需要读取其感兴趣的配置。通过将所有配置封装在一个对象中,我们可以轻松传递配置对象并访问所需的配置。假设我们想添加一个新功能(考虑到LLM推理领域发展迅速,这种情况经常发生),该功能仅涉及模型运行器。我们只需在VllmConfig类中添加一个新的配置选项。由于我们传递整个配置对象,因此只需将配置选项添加到VllmConfig类中,模型运行器就可以直接访问它。我们不需要更改引擎、工作器或模型类的构造函数来传递新的配置选项。
2. 统一性: 模型运行器需要一个统一的接口来创建和初始化模型。vLLM支持50多种流行的开源模型。每个模型都有自己的初始化逻辑。如果构造函数签名因模型而异,模型运行器就无法知道如何相应地调用构造函数,除非使用复杂且容易出错的检查逻辑。通过使模型类的构造函数统一化,模型运行器可以轻松创建和初始化模型,而无需了解具体模型类型。这对于模型组合也很有用。视觉语言模型通常由视觉模型和语言模型组成。通过统一构造函数,我们可以轻松创建视觉模型和语言模型,并将它们组合成视觉语言模型。
注意
为支持这一变更,所有vLLM模型的签名已更新为:
为避免意外传递错误的参数,构造函数现在仅支持关键字参数。这样可以确保如果传入旧的配置,构造函数将抛出错误。vLLM开发者已为vLLM内的所有模型完成此更改。对于外部注册的模型,开发者需要更新其模型,例如通过添加适配代码将旧构造函数签名调整为新的签名:
Code
class MyOldModel(nn.Module):
def __init__(
self,
config,
cache_config: Optional[CacheConfig] = None,
quant_config: Optional[QuantizationConfig] = None,
lora_config: Optional[LoRAConfig] = None,
prefix: str = "",
) -> None:
...
from vllm.config import VllmConfig
class MyNewModel(MyOldModel):
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
config = vllm_config.model_config.hf_config
cache_config = vllm_config.cache_config
quant_config = vllm_config.quant_config
lora_config = vllm_config.lora_config
super().__init__(config, cache_config, quant_config, lora_config, prefix)
if __version__ >= "0.6.4":
MyModel = MyNewModel
else:
MyModel = MyOldModel
这样,模型就可以同时兼容新旧版本的vLLM。
3. 初始化时的分片与量化: 某些特性需要修改模型权重。例如,张量并行需要对模型权重进行分片,量化则需要对模型权重进行量化处理。实现这一特性有两种可能的方式。一种是在模型初始化后修改权重,另一种是在模型初始化过程中直接修改权重。vLLM选择了后者。第一种方法对于大型模型不具备可扩展性。假设我们要在16块80GB显存的H100 GPU上运行405B参数量的模型(权重约810GB)。理想情况下,每块GPU只需加载50GB权重。如果在模型初始化后修改权重,就需要将所有810GB权重加载到每块GPU上再进行分片,这将导致巨大的内存开销。相反,如果在模型初始化过程中进行权重分片,每个层就只会创建所需权重的分片,从而显著降低内存开销。同样的思路也适用于量化处理。需要注意的是,我们还在模型构造函数中添加了一个prefix参数,使模型能根据前缀进行差异化初始化。这对于非均匀量化特别有用——模型的不同部分可以采用不同的量化方式。prefix参数对顶层模型通常是空字符串,对于子模型则可能是"vision"或"language"这类字符串。通常来说,它会与检查点文件中模块状态字典的名称相匹配。
这种设计的一个缺点是,很难为vLLM中的各个组件编写单元测试,因为每个组件都需要通过一个完整的配置对象进行初始化。我们通过提供一个默认初始化函数来解决这个问题,该函数会创建一个所有字段都设置为None的默认配置对象。如果我们想要测试的组件只关心配置对象中的少数几个字段,我们可以创建一个默认配置对象并设置我们关心的字段。这样,我们就可以单独测试该组件。需要注意的是,vLLM中的许多测试都是端到端测试,用于测试整个系统,因此这不是一个大问题。
总之,完整的配置对象VllmConfig可以被视为引擎级别的全局状态,在所有vLLM类之间共享。


