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__ 保护的代码。
对于 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/all_reduce_utils.py#L135
- https://github.com/vllm-project/vllm/blob/d05f88679bedd73939251a17c3d785a354b2946c/vllm/entrypoints/openai/api_server.py#L184
相关 PR
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__ 保护,我们可能会表现得更好。这个 Stack Overflow 帖子来自一位面临同样问题的库作者。
可以检测我们是在原始的 __main__ 进程还是在后续的子进程中。但是,似乎无法直接检测代码中是否存在 __main__ 保护。
此选项已被放弃,因为它不切实际。
使用 `forkserver`¶
起初,forkserver 似乎是解决问题的不错方案。然而,它的工作方式与 spawn 在 vLLM 用作库时存在相同的挑战。
强制一直使用 `spawn`¶
一种清理此问题的方法是始终强制使用 spawn,并记录在使用 vLLM 作为库时需要使用 __main__ 保护。但这将破坏现有代码,并使 vLLM 更难使用,这违反了使 LLM 类尽可能易于使用的愿望。
与其将此问题推给用户,不如我们保留复杂性,尽力使事情正常工作。
未来的工作¶
我们可能需要在未来考虑一种不同的工作程序管理方法,以解决这些挑战。
-
我们可以实现类似
forkserver的功能,但让进程管理器是我们最初通过运行自己的子进程和自定义入口点来启动的(启动一个vllm-manager进程)。 -
我们可以探索可能更适合我们需求的库。考虑的示例: