跳到內容

架構概述

本文件提供了 vLLM 架構的概述。

入口點

vLLM 提供了多種與系統互動的入口點。下圖展示了它們之間的關係。

Entrypoints Diagram

LLM 類

LLM 類提供了主要的 Python 介面,用於進行離線推理,即在不使用單獨的模型推理伺服器的情況下與模型進行互動。

以下是 LLM 類使用示例

程式碼
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 serve <model>

vllm CLI 的程式碼可以在 vllm/entrypoints/cli/main.py 中找到。

有時,您可能會直接看到 API 伺服器入口點被使用,而不是透過 vllm CLI 命令。例如:

python -m vllm.entrypoints.openai.api_server --model <model>

警告

python -m vllm.entrypoints.openai.api_server 已棄用,未來版本可能會不再支援。

該程式碼可以在 vllm/entrypoints/openai/api_server.py 中找到。

關於 API 伺服器的更多詳細資訊可以在 OpenAI 相容伺服器 文件中找到。

LLM 引擎

LLMEngineAsyncLLMEngine 類是 vLLM 系統執行的核心,負責模型推理和非同步請求處理。

LLMEngine Diagram

LLMEngine

LLMEngine 類是 vLLM 引擎的核心元件。它負責接收來自客戶端的請求並從模型生成輸出。LLMEngine 包括輸入處理、模型執行(可能分佈在多個主機和/或 GPU 上)、排程和輸出處理。

  • 輸入處理:使用指定的 tokenizer 處理輸入文字的 token 化。
  • 排程:決定在每個步驟中處理哪些請求。
  • 模型執行:管理語言模型的執行,包括在多個 GPU 上的分散式執行。
  • 輸出處理:處理模型生成的輸出,將語言模型的 token ID 解碼成人類可讀的文字。

LLMEngine 的程式碼可以在 vllm/engine/llm_engine.py 中找到。

AsyncLLMEngine

AsyncLLMEngine 類是 LLMEngine 類的一個非同步包裝器。它使用 asyncio 建立一個後臺迴圈,持續處理傳入的請求。AsyncLLMEngine 專為線上服務設計,能夠處理多個併發請求並將輸出流式傳輸給客戶端。

OpenAI 相容的 API 伺服器使用 AsyncLLMEngine。還有一個演示 API 伺服器,在 vllm/entrypoints/api_server.py 中作為一個更簡單的例子。

AsyncLLMEngine 的程式碼可以在 vllm/engine/async_llm_engine.py 中找到。

Worker

Worker 是一個執行模型推理的程序。vLLM 遵循一個常見的實踐,即使用一個程序控制一個加速器裝置,例如 GPU。例如,如果我們使用大小為 2 的張量並行和大小為 2 的流水線並行,那麼我們總共有 4 個 worker。Worker 透過它們的 ranklocal_rank 進行標識。rank 用於全域性協調,而 local_rank 主要用於分配加速器裝置和訪問本地資源,如檔案系統和共享記憶體。

Model Runner

每個 worker 都有一個 Model Runner 物件,負責載入和執行模型。大部分模型執行邏輯都 resides 在這裡,例如準備輸入張量和捕獲 cudagraphs。

Model

每個 Model Runner 物件都有一個 Model 物件,這是實際的 torch.nn.Module 例項。有關各種配置如何影響我們最終獲得的類的詳細資訊,請參閱 huggingface_integration

類層級結構

下圖展示了 vLLM 的類層級結構

query

此類層級結構背後有幾個重要的設計選擇

1. **可擴充套件性**:層級結構中的所有類都接受一個包含所有必要資訊的配置物件。VllmConfig 類是傳遞的 मुख्य 配置物件。類層級結構非常深,每個類都需要讀取它感興趣的配置。透過將所有配置封裝在一個物件中,我們可以輕鬆地傳遞配置物件並訪問所需的配置。假設我們想新增一個新功能(鑑於 LLM 推理領域發展迅速,這很常見),該功能僅涉及 Model Runner。我們將在 VllmConfig 類中新增一個新的配置選項。由於我們傳遞整個配置物件,因此只需將配置選項新增到 VllmConfig 類,Model Runner 就可以直接訪問它。我們無需更改 Engine、Worker 或 Model 類的建構函式來傳遞新的配置選項。

2. **統一性**:Model Runner 需要一個統一的介面來建立和初始化模型。vLLM 支援 50 多種流行的開源模型。每種模型都有自己的初始化邏輯。如果建構函式簽名因模型而異,Model Runner 將不知道如何呼叫建構函式,而沒有複雜且容易出錯的檢查邏輯。透過使 Model 類的建構函式統一,Model Runner 可以輕鬆建立和初始化模型,而無需瞭解特定模型型別。這對於組合模型也很有用。視覺-語言模型通常由視覺模型和語言模型組成。透過使建構函式統一,我們可以輕鬆建立視覺模型和語言模型,並將它們組合成一個視覺-語言模型。

注意

為了支援此更改,所有 vLLM 模型的簽名都已更新為

def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):

為了避免意外傳遞不正確的引數,建構函式現在是僅關鍵字引數。這確保瞭如果傳遞了舊配置,建構函式將引發錯誤。vLLM 開發人員已經為 vLLM 中的所有模型進行了此更改。對於 out-of-tree 註冊模型,開發人員需要更新他們的模型,例如透過新增 shim 程式碼來將舊的建構函式簽名適配到新的簽名。

程式碼
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)

from packaging import version
if version.parse(__version__) >= version.parse("0.6.4"):
    MyModel = MyNewModel
else:
    MyModel = MyOldModel

這樣,模型就可以與 vLLM 的新舊版本一起工作。

3. **初始化時的分片和量化**:某些功能需要更改模型權重。例如,張量並行需要分片模型權重,量化需要量化模型權重。有兩種方法可以實現此功能。一種方法是在模型初始化後更改模型權重。另一種方法是在模型初始化期間更改模型權重。vLLM 選擇後者。第一種方法對於大型模型來說不是可擴充套件的。假設我們想使用 16 個 H100 80GB GPU 執行一個 405B 模型(約 810GB 權重)。理想情況下,每個 GPU 應只加載 50GB 權重。如果我們模型初始化後更改模型權重,我們需要將完整的 810GB 權重載入到每個 GPU,然後分片權重,這會導致巨大的記憶體開銷。相反,如果在模型初始化期間分片權重,每一層將只建立它所需的權重分片,從而大大減小記憶體開銷。同樣的概念也適用於量化。請注意,我們還在模型建構函式中添加了一個額外的引數 prefix,以便模型可以根據字首以不同的方式進行初始化。這對於非均勻量化很有用,其中模型的不同部分被量化為不同的精度。prefix 通常是頂層模型的空字串,而對於子模型,它是像 "vision""language" 這樣的字串。通常,它與 checkpoint 檔案中模組的 state dict 的名稱匹配。

此設計的一個缺點是很難為 vLLM 中的單個元件編寫單元測試,因為每個元件都需要由完整的配置物件進行初始化。我們透過提供一個預設初始化函式來解決這個問題,該函式建立一個具有所有欄位設定為 None 的預設配置物件。如果我們想測試的元件只關心配置物件中的少數幾個欄位,我們可以建立一個預設配置物件並設定我們關心的欄位。這樣,我們就可以單獨測試該元件。請注意,vLLM 中的許多測試都是端到端測試,用於測試整個系統,因此這並不是一個大問題。

總而言之,完整的配置物件 VllmConfig 可以被視為一個引擎級別的全域性狀態,該狀態在所有 vLLM 類之間共享。