跳到內容

架構概覽

本文件提供了 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>

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

更多關於 API 伺服器的詳情,請參閱 OpenAI 相容伺服器文件。

LLM 引擎

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

LLMEngine Diagram

LLMEngine

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

  • 輸入處理:使用指定的 Tokenizer 處理輸入文字的分詞。
  • 排程:選擇在每個步驟中處理哪些請求。
  • 模型執行:管理語言模型的執行,包括跨多個 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 物件,負責載入和執行模型。大部分模型執行邏輯都位於此處,例如準備輸入張量和捕獲 CUDA 圖。

模型

每個 Model Runner 物件都含有一個模型物件,即實際的 torch.nn.Module 例項。有關不同配置如何影響最終獲得的類,請參閱 huggingface_integration

類層次結構

下圖展示了 vLLM 的類層次結構:

query

該類層次結構背後有幾個重要的設計選擇:

1. 可擴充套件性:層次結構中的所有類都接受一個包含所有必要資訊的配置物件。VllmConfig 類是主要且廣泛傳遞的配置物件。類層次結構相當深,每個類都需要讀取其感興趣的配置。透過將所有配置封裝在一個物件中,我們可以輕鬆地傳遞配置物件並訪問所需的配置。假設我們想新增一個只涉及 Model Runner 的新功能(考慮到 LLM 推理領域發展之快,這種情況很常見)。我們必須在 VllmConfig 類中新增一個新的配置選項。由於我們傳遞的是整個配置物件,所以我們只需要將配置選項新增到 VllmConfig 類中,Model Runner 就可以直接訪問它。我們無需更改引擎、Worker 或模型類的建構函式來傳遞新的配置選項。

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

注意

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

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

為避免意外傳遞不正確的引數,建構函式現在僅接受關鍵字引數。這確保瞭如果傳遞舊配置,建構函式將引發錯誤。vLLM 開發者已經為 vLLM 中的所有模型進行了此更改。對於非樹內註冊模型,開發者需要更新其模型,例如透過新增 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)

if __version__ >= "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" 這樣的字串。通常,它與檢查點檔案中模組狀態字典的名稱匹配。

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

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