跳到內容

Python 多程序

除錯

有關已知問題及其解決方法,請參閱故障排除頁面。

簡介

重要

原始碼引用是指在 2024 年 12 月編寫程式碼時的狀態。

vLLM 中的 Python 多程序使用因以下原因而變得複雜:

  • 將 vLLM 用作庫,以及無法控制 vLLM 使用的程式碼。
  • 多程序方法與 vLLM 依賴項之間存在不同程度的不相容。

本文件介紹了 vLLM 如何應對這些挑戰。

多程序方法

Python 多程序方法包括:

  • spawn - 啟動一個新的 Python 程序。在 Windows 和 macOS 上為預設值。

  • fork - 使用 os.fork() 來 fork Python 直譯器。在 Python 3.14 之前的版本中,在 Linux 上為預設值。

  • forkserver - 啟動一個伺服器程序,該程序在請求時 fork 一個新程序。在 Python 3.14 及更高版本中,在 Linux 上為預設值。

權衡

fork 是最快的方法,但與使用執行緒的依賴項不相容。如果您在 macOS 上,使用 fork 可能會導致程序崩潰。

spawn 與依賴項的相容性更好,但在 vLLM 用作庫時可能會出現問題。如果使用者程式碼沒有使用 __main__ 保護 (if __name__ == "__main__":),則當 vLLM 啟動新程序時,程式碼將被無意中重新執行。這可能導致無限遞迴等問題。

forkserver 將啟動一個新伺服器程序,該程序會按需 fork 新程序。不幸的是,當 vLLM 用作庫時,這與 spawn 存在相同的問題。伺服器程序建立為一個新啟動的程序,該程序將重新執行未受 __main__ 保護的程式碼。

對於 spawnforkserver,程序不得依賴於繼承任何全域性狀態,這與 fork 的情況類似。

與依賴項的相容性

多個 vLLM 依賴項表明首選或要求使用 spawn

更準確地說,使用這些依賴項初始化後使用 fork 會出現已知問題。

當前狀態 (v0)

可以使用環境變數 VLLM_WORKER_MULTIPROC_METHOD 來控制 vLLM 使用哪種方法。當前預設值為 fork

當知道我們擁有該程序(因為使用了 vllm 命令)時,我們使用 spawn,因為它相容性最好。

multiproc_xpu_executor 強制使用 spawn

還有其他地方硬編碼使用 spawn

相關 PR

v1 中的先前狀態

有一個環境變數用於控制是否在 v1 引擎核心中使用多程序,即 VLLM_ENABLE_V1_MULTIPROCESSING。此變數預設為關閉。

當啟用時,v1 LLMEngine 會建立一個新程序來執行引擎核心。

由於上述所有原因,它預設關閉 - 與依賴項的相容性以及將 vLLM 用作庫的程式碼。

v1 中所做的更改

Python 的 multiprocessing 沒有一個簡單的解決方案可以普遍適用。作為第一步,我們可以讓 v1 進入一種狀態,即它會“盡力”選擇多程序方法以最大化相容性。

  • 預設使用 fork
  • 當我們知道我們控制主程序時(執行了 vllm 命令),使用 spawn
  • 如果我們檢測到 cuda 之前已被初始化,則強制使用 spawn 併發出警告。我們知道 fork 會失敗,所以這是我們能做的最好的。

在這種情況下已知仍會失敗的情況是,將 vLLM 用作庫的程式碼在呼叫 vLLM 之前初始化了 cuda。我們發出的警告應指示使用者新增 __main__ 保護或停用多程序。

如果發生該已知失敗情況,使用者將看到兩個解釋發生了什麼的錯誤訊息。首先,vLLM 的日誌訊息

WARNING 12-11 14:50:37 multiproc_worker_utils.py:281] CUDA was previously
    initialized. We must use the `spawn` multiprocessing start method. Setting
    VLLM_WORKER_MULTIPROC_METHOD to 'spawn'. See
    https://docs.vllm.tw/en/latest/usage/troubleshooting.html#python-multiprocessing
    for more information.

其次,Python 本身將引發一個帶有友好解釋的異常。

RuntimeError:
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

        To fix this issue, refer to the "Safe importing of main module"
        section in https://docs.python.club.tw/3/library/multiprocessing.html

考慮過的替代方案

檢測是否包含 `__main__` 保護

有人建議,如果我們可以檢測到使用 vLLM 作為庫的程式碼是否具有 __main__ 保護,我們可能會表現得更好。這個 Stack Overflow 帖子來自一位面臨同樣問題的庫作者。

可以檢測我們是在原始的 __main__ 程序還是在後續的子程序中。但是,似乎無法直接檢測程式碼中是否存在 __main__ 保護。

此選項已被放棄,因為它不切實際。

使用 `forkserver`

起初,forkserver 似乎是解決問題的不錯方案。然而,它的工作方式與 spawn 在 vLLM 用作庫時存在相同的挑戰。

強制一直使用 `spawn`

一種清理此問題的方法是始終強制使用 spawn,並記錄在使用 vLLM 作為庫時需要使用 __main__ 保護。但這將破壞現有程式碼,並使 vLLM 更難使用,這違反了使 LLM 類儘可能易於使用的願望。

與其將此問題推給使用者,不如我們保留複雜性,盡力使事情正常工作。

未來的工作

我們可能需要在未來考慮一種不同的工作程式管理方法,以解決這些挑戰。

  1. 我們可以實現類似 forkserver 的功能,但讓程序管理器是我們最初透過執行自己的子程序和自定義入口點來啟動的(啟動一個 vllm-manager 程序)。

  2. 我們可以探索可能更適合我們需求的庫。考慮的示例:

  3. https://github.com/joblib/loky