Python多进程处理¶
调试¶
请查看故障排除页面了解已知问题及其解决方法。
简介¶
重要
源代码引用基于2024年12月撰写时的代码状态。
在vLLM中使用Python多进程的复杂性源于:
- 将vLLM作为库使用以及无法控制使用vLLM的代码
- 多进程方法与vLLM依赖之间存在不同程度的兼容性问题
本文档描述了vLLM如何应对这些挑战。
多进程方法¶
-
spawn- 生成一个新的Python进程。这是Windows和macOS上的默认方式。 -
fork- 使用os.fork()来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
相关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
../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.org/3/library/multiprocessing.html
考虑过的替代方案¶
检测是否存在__main__守卫条件¶
有人建议,如果我们能检测到使用vLLM作为库的代码是否包含__main__保护机制,就能表现得更好。这篇stackoverflow上的帖子来自一位面临相同问题的库作者。
可以检测当前是否处于原始的__main__进程或后续生成的进程中。然而,要检测代码中是否存在__main__防护似乎并不直接明了。
该选项因不切实际已被弃用。
使用 forkserver¶
起初,forkserver似乎是一个不错的解决方案。然而,当vLLM作为库使用时,其工作方式会带来与spawn相同的挑战。
强制始终使用spawn模式¶
一种清理方法是强制始终使用spawn,并文档说明当将vLLM作为库使用时必须使用__main__保护机制。遗憾的是这会破坏现有代码并使vLLM更难使用,违背了让LLM类尽可能易于使用的初衷。
我们不会将这种复杂性推给用户,而是会自行承担,尽力确保一切正常运行。
未来工作¶
未来我们可能需要考虑采用一种不同的工作节点管理方法,以应对这些挑战。
-
我们可以实现类似
forkserver的功能,但让进程管理器成为我们最初通过运行自己的子进程和用于工作进程管理的自定义入口点来启动的东西(启动一个vllm-manager进程)。 -
我们可以探索其他可能更符合需求的库。值得考虑的示例: