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 用作庫時可能會出現問題。如果呼叫程式碼沒有使用 __main__
守護(if __name__ == "__main__":
),則當 vLLM 啟動新程序時,程式碼將被無意中重新執行。這可能導致無限遞迴等問題。
forkserver
將啟動一個新的伺服器程序,該程序將按需分叉新程序。不幸的是,當 vLLM 用作庫時,這與 spawn
存在相同的問題。伺服器程序是作為啟動的新程序建立的,它將重新執行未受 __main__
守護保護的程式碼。
對於 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
相關拉取請求
v1 中的先前狀態¶
v1 引擎核心中有一個環境變數 VLLM_ENABLE_V1_MULTIPROCESSING
用於控制是否使用多程序。此變數預設關閉。
啟用後,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__
守護,要麼停用多程序。
如果發生該已知故障情況,使用者將看到兩條解釋正在發生什麼的提示資訊。首先,來自 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__
守護,我們可以表現得更好。這篇stackoverflow 上的帖子是一位庫作者面臨同樣問題時釋出的。
可以檢測我們是在原始的 __main__
程序中,還是在隨後的派生程序中。然而,直接檢測程式碼中是否存在 __main__
守護似乎並不簡單。
此選項已被視為不切實際而放棄。
使用 forkserver
¶
乍一看,forkserver
似乎是解決問題的好方案。然而,它的工作方式在 vLLM 用作庫時,會帶來與 spawn
相同的挑戰。
始終強制 spawn
¶
一種清理方法是始終強制使用 spawn
,並註明將 vLLM 用作庫時需要使用 __main__
守護。不幸的是,這會破壞現有程式碼並使 vLLM 更難使用,這違反了使 LLM
類儘可能易於使用的願望。
為了不把這種複雜性推給使用者,我們將保留複雜性以盡力讓事情正常執行。
未來工作¶
未來我們可能需要考慮一種不同的工作程序管理方法來解決這些挑戰。
-
我們可以實現類似
forkserver
的東西,但讓程序管理器是我們最初透過執行自己的子程序和用於工作程序管理的自定義入口點(啟動一個vllm-manager
程序)來啟動的東西。 -
我們可以探索可能更適合我們需求的其他庫。可考慮的示例: