跳過內容

Python 多程序

除錯

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

引言

警告

原始碼引用指向了編寫本文時(2024 年 12 月)的程式碼狀態。

在 vLLM 中使用 Python 多程序變得複雜,原因如下:

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

本文件描述了 vLLM 如何應對這些挑戰。

多程序方法

Python 多程序方法包括

  • spawn - 啟動一個新的 Python 程序。Windows 和 macOS 上的預設方法。

  • fork - 使用 os.fork() 來分叉 Python 直譯器。Python 3.14 版本之前在 Linux 上的預設方法。

  • forkserver - 啟動一個伺服器程序,該程序將根據請求分叉一個新的程序。Python 3.14 及更新版本在 Linux 上的預設方法。

權衡

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

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

forkserver 將啟動一個新的伺服器程序,該程序將根據請求分叉新的程序。不幸的是,當 vLLM 用作庫時,這與 spawn 存在相同的問題。伺服器程序是作為啟動的新程序建立的,它將重新執行未受 __main__ guard 保護的程式碼。

對於 spawnforkserver,程序不應依賴於繼承任何全域性狀態,而 fork 則會繼承全域性狀態。

與依賴項的相容性

多個 vLLM 依賴項表明偏好或要求使用 spawn

更準確地說,是在初始化這些依賴項後使用 fork 存在已知問題。

當前狀態 (v0)

環境變數 VLLM_WORKER_MULTIPROC_METHOD 可用於控制 vLLM 使用哪種方法。當前的預設方法是 fork

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

multiproc_xpu_executor 強制使用 spawn

還有其他一些地方硬編碼了 spawn 的使用

相關 PR

v1 中的先前狀態

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

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

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

v1 中所做更改

使用 Python 的 multiprocessing 沒有一個到處都適用的簡單解決方案。第一步,我們可以讓 v1 達到一種狀態,即它“盡力而為”地選擇多程序方法以最大限度地提高相容性。

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

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

如果發生該已知失敗情況,使用者將看到兩條解釋正在發生什麼的訊息。首先,來自 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/debugging.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__ guard

有人建議,如果我們能檢測出將 vLLM 用作庫的程式碼是否使用了 __main__ guard,我們的表現可能會更好。這篇stackoverflow 帖子來自一位面臨同樣問題的庫作者。

可以檢測我們是否處於原始的 __main__ 程序中,或後續啟動的程序中。但是,直接檢測程式碼中是否存在 __main__ guard 似乎並不容易。

此選項因不切實際而被放棄。

使用 forkserver

起初看起來 forkserver 是一個不錯的解決方案。然而,它的工作方式在將 vLLM 用作庫時也帶來了與 spawn 相同的挑戰。

始終強制使用 spawn

一種清理方法是始終強制使用 spawn,並說明將 vLLM 用作庫時需要使用 __main__ guard。這不幸會破壞現有程式碼並使 vLLM 更難使用,違背了使 LLM 類儘可能易於使用的願望。

我們不會將此負擔強加給使用者,而是會保留複雜性,盡最大努力使一切正常工作。

未來工作

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

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

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

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