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.ac.cn/docs/stable/notes/multiprocessing.html#cuda-in-multiprocessing
- https://pytorch.ac.cn/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.com.cn/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.pythonlang.cn/3/library/multiprocessing.html
已考虑的替代方案¶
检测是否存在 __main__
守护¶
有人建议,如果我们能检测到将 vLLM 用作库的代码中是否存在 __main__
守护,我们可以表现得更好。这篇stackoverflow 上的帖子是一位库作者面临同样问题时发布的。
可以检测我们是在原始的 __main__
进程中,还是在随后的派生进程中。然而,直接检测代码中是否存在 __main__
守护似乎并不简单。
此选项已被视为不切实际而放弃。
使用 forkserver
¶
乍一看,forkserver
似乎是解决问题的好方案。然而,它的工作方式在 vLLM 用作库时,会带来与 spawn
相同的挑战。
始终强制 spawn
¶
一种清理方法是始终强制使用 spawn
,并注明将 vLLM 用作库时需要使用 __main__
守护。不幸的是,这会破坏现有代码并使 vLLM 更难使用,这违反了使 LLM
类尽可能易于使用的愿望。
为了不把这种复杂性推给用户,我们将保留复杂性以尽力让事情正常运行。
未来工作¶
未来我们可能需要考虑一种不同的工作进程管理方法来解决这些挑战。
-
我们可以实现类似
forkserver
的东西,但让进程管理器是我们最初通过运行自己的子进程和用于工作进程管理的自定义入口点(启动一个vllm-manager
进程)来启动的东西。 -
我们可以探索可能更适合我们需求的其他库。可考虑的示例: