Python 多程序¶
除錯¶
請參閱故障排除頁面,瞭解已知問題及其解決方法。
引言¶
警告
原始碼引用指向了編寫本文時(2024 年 12 月)的程式碼狀態。
在 vLLM 中使用 Python 多程序變得複雜,原因如下:
- 將 vLLM 作為庫使用,以及無法控制使用 vLLM 的程式碼
- 多程序方法與 vLLM 依賴項之間不同程度的不相容性
本文件描述了 vLLM 如何應對這些挑戰。
多程序方法¶
-
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 保護的程式碼。
對於 spawn
和 forkserver
,程序不應依賴於繼承任何全域性狀態,而 fork
則會繼承全域性狀態。
與依賴項的相容性¶
多個 vLLM 依賴項表明偏好或要求使用 spawn
- https://pytorch.org/docs/stable/notes/multiprocessing.html#cuda-in-multiprocessing
- https://pytorch.org/docs/stable/multiprocessing.html#sharing-cuda-tensors
- https://docs.habana.ai/en/latest/PyTorch/Getting_Started_with_PyTorch_and_Gaudi/Getting_Started_with_PyTorch.html?highlight=multiprocessing#torch-multiprocessing-for-dataloaders
更準確地說,是在初始化這些依賴項後使用 fork
存在已知問題。
當前狀態 (v0)¶
環境變數 VLLM_WORKER_MULTIPROC_METHOD
可用於控制 vLLM 使用哪種方法。當前的預設方法是 fork
。
當我們知道我們擁有該程序(因為使用了 vllm
命令)時,我們使用 spawn
,因為它相容性最廣。
multiproc_xpu_executor
強制使用 spawn
。
還有其他一些地方硬編碼了 spawn
的使用
- https://github.com/vllm-project/vllm/blob/d05f88679bedd73939251a17c3d785a354b2946c/vllm/distributed/device_communicators/custom_all_reduce_utils.py#L135
- https://github.com/vllm-project/vllm/blob/d05f88679bedd73939251a17c3d785a354b2946c/vllm/entrypoints/openai/api_server.py#L184
相關 PR
v1 中的先前狀態¶
有一個環境變數 VLLM_ENABLE_V1_MULTIPROCESSING
用於控制 v1 引擎核心是否使用多程序。該變數預設為關閉。
當它被啟用時,v1 LLMEngine
會建立一個新程序來執行引擎核心。
- https://github.com/vllm-project/vllm/blob/d05f88679bedd73939251a17c3d785a354b2946c/vllm/v1/engine/llm_engine.py#L93-L95
- https://github.com/vllm-project/vllm/blob/d05f88679bedd73939251a17c3d785a354b2946c/vllm/v1/engine/llm_engine.py#L70-L77
- https://github.com/vllm-project/vllm/blob/d05f88679bedd73939251a17c3d785a354b2946c/vllm/v1/engine/core_client.py#L44-L45
由於上述所有原因,它預設是關閉的——與依賴項的相容性以及將 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
類儘可能易於使用的願望。
我們不會將此負擔強加給使用者,而是會保留複雜性,盡最大努力使一切正常工作。
未來工作¶
未來我們可能需要考慮一種不同的工作程序管理方法,以解決這些挑戰。
-
我們可以實現類似於
forkserver
的功能,但讓程序管理器成為我們最初透過執行自己的子程序和用於工作程序管理的自定義入口點(啟動vllm-manager
程序)來啟動的東西。 -
我們可以探索其他可能更適合我們需求的庫。考慮的例子