给网站做脚本算违法吗,Sketch 开发 wordpress,食品包装设计调研报告,网络平台维护您提出了一个极其敏锐和关键的问题#xff01;这确实让我之前的解释变得不完整#xff0c;因为它默认了 Unix 系统上最常见的 fork 行为。您贴出的代码显示了 vLLM 在特定情况下会使用 spawn#xff0c;这完全改变了资源传递的方式。 您是对的#xff1a;在使用 spawn 模式…您提出了一个极其敏锐和关键的问题这确实让我之前的解释变得不完整因为它默认了 Unix 系统上最常见的fork行为。您贴出的代码显示了 vLLM 在特定情况下会使用spawn这完全改变了资源传递的方式。您是对的在使用spawn模式时子进程不会自动继承父进程的文件描述符。那么共享套接字是如何实现的呢答案是Python 的multiprocessing库为spawn模式提供了一套特殊的、底层的机制用于在进程间安全地传递文件描述符这类“不可序列化”的资源。这个过程比fork的隐式继承要复杂得多但结果是相同的。让我们来揭开spawn模式下共享套接字的“魔法”。spawn模式下的套接字“共享”步骤当您使用spawn上下文启动一个进程并试图将一个套接字对象sock作为参数传递时multiprocessing库在后台执行了以下一系列操作第 1 步父进程准备“交接” (Reduction)当父进程调用proc.start()时multiprocessing库需要将args(..., sock, ...)中的所有参数传递给新的子进程。对于普通的可序列化对象如字符串、数字它会使用pickle来序列化。但socket对象是不可序列化的。当multiprocessing遇到socket对象时它会触发一个特殊的“归约” (reduction) 过程。第 2 步提取核心资源 - 文件描述符这个“归约”过程的核心是调用sock.fileno()从 Python 的socket对象中提取出它在操作系统内核中对应的整数文件描述符 (file descriptor)。第 3 步通过 IPC 安全地跨进程传递文件描述符这是最关键的一步。父进程需要将这个整数文件描述符传递给一个全新的、独立的子进程。它通过一个底层的进程间通信 (IPC)机制来完成。在 Unix-like 系统上这通常是通过Unix 域套接字 (Unix Domain Socket)和一种叫做辅助数据 (ancillary data)的特殊消息 (SCM_RIGHTS) 来实现的。可以把它想象成父进程建立了一条与即将出生的子进程之间的秘密通道。它不是把整个电话机socket 对象递过去而是通过这个秘密通道给了子进程一张写有“请接通 5 号线”文件描述符的特殊授权卡。第 4 步子进程“重建”套接字对象子进程在启动时其multiprocessing初始化代码会通过那条秘密通道接收到父进程发送过来的文件描述符。然后它在子进程全新的内存空间里创建一个新的 Pythonsocket对象。最重要的是它使用收到的文件描述符来初始化这个新的socket对象大致相当于socket.fromfd()。最终结果最终结果是虽然子进程的内存空间是全新的但它内部的sock对象和父进程的sock对象都指向了内核中同一个、唯一的监听套接字数据结构。所以尽管实现方式天差地别fork: 通过内存继承实现了共享。spawn: 通过显式的、基于 IPC 的文件描述符传递和对象重建实现了共享。但它们都达到了相同的目的多个进程最终都在操作同一个内核监听队列。因此我们之前讨论的关于内核级负载均衡、避免用户态锁开销、解决惊群效应等所有优点在这种模式下依然完全适用。为什么 vLLM 会使用spawn既然fork这么简单直接为什么还要用spawn这么复杂的方式呢安全性与隔离性:spawn更“干净”。子进程从一个纯净的环境开始不会继承父进程可能存在的混乱状态比如线程锁、全局变量等。对于使用了复杂第三方库尤其是那些非“fork-safe”的库的程序来说spawn更安全、更可预测。跨平台兼容性:spawn是 Windows 和 macOS新版本上默认或唯一支持的多进程启动方式。使用spawn可以让代码更具可移植性。避免 CUDA 问题: 这是最常见的一个原因。CUDA 运行时与fork存在已知的兼容性问题。如果在父进程初始化 CUDA 之后再fork子进程很可能会导致子进程中的 CUDA 调用挂起或直接崩溃。因此在涉及 GPU 的应用中强制使用spawn是一种常见的、稳健的做法。get_mp_context()函数中的_maybe_force_spawn()正是做了这个判断。总结您发现的spawn用法非常正确它揭示了 vLLM 为了健壮性和兼容性特别是与 GPU 环境的兼容性而采用的更复杂的进程创建策略。虽然实现机制从“隐式继承”变成了“显式传递”但“多个进程共享同一个内核套接字”的最终效果没有改变其性能优势也依然存在。感谢您提出这个精彩的问题它让我们的讨论更加深入和准确不是vLLM 不是通过父进程 fork() 创建 API server 进程的。vLLM 使用的是spawn方法来创建多个 API server 进程。 1具体实现在APIServerProcessManager类中vLLM 明确使用 spawn 上下文创建进程# Start API serversspawn_contextmultiprocessing.get_context(spawn)self.processes:list[BaseProcess][]fori,in_addr,out_addrinzip(range(num_servers),input_addresses,output_addresses):procspawn_context.Process(targettarget_server_fn,namefApiServer_{i},args(listen_address,sock,args,client_config),)self.processes.append(proc)proc.start()[1](#7-0)## 为什么使用 spawn 而不是 forkvLLM 选择 spawn 方法主要有以下原因1.**兼容性更好**spawn 与更多依赖库兼容特别是那些使用线程的库[2](#7-1)2.**避免 CUDA 初始化问题**fork 在 CUDA 已初始化后会有问题spawn 更安全[3](#7-2)3.**环境变量控制**虽然默认的 VLLM_WORKER_MULTIPROC_METHOD 是fork[4](#7-3) 但在 API server 创建时强制使用 spawn。## 进程创建流程1.run_multi_api_server 函数启动[5](#7-4)2.创建 APIServerProcessManager 实例3.使用 spawn 上下文创建多个 API server 工作进程4.每个进程独立运行共享监听 socket## Notes虽然 fork 方法更快因为它继承父进程的内存空间但 spawn 方法更安全可靠。vLLM 在 API server 创建时选择牺牲一些性能来换取更好的稳定性和兼容性。这种设计确保了多进程架构在各种环境下都能正常工作。[6](#7-5)Wiki pages you might want to explore:-[ConfigurationandInitialization(vllm-project/vllm)](/wiki/vllm-project/vllm#2)### Citations**File:**vllm/v1/utils.py(L194-215)python spawn_contextmultiprocessing.get_context(spawn)self.processes:list[BaseProcess][]fori,in_addr,out_addrinzip(range(num_servers),input_addresses,output_addresses):client_config{input_address:in_addr,output_address:out_addr,client_count:num_servers,client_index:i,}ifstats_update_addressisnotNone:client_config[stats_update_address]stats_update_address procspawn_context.Process(targettarget_server_fn,namefApiServer_{i},args(listen_address,sock,args,client_config),)self.processes.append(proc)proc.start()File:docs/design/multiprocessing.md (L35-41)fork is the fastest method, but is incompatible with dependencies that use threads. If you are under macOS, using fork may cause the process to crash. spawn is more compatible with dependencies, but can be problematic when vLLM is used as a library. If the consuming code does not use a __main__ guard (if __name__ __main__:), the code will be inadvertently re-executed when vLLM spawns a new process. This can lead to infinite recursion, among other problems.File:docs/design/multiprocessing.md (L60-62)It is perhaps more accurate to say that there are known problems with using fork after initializing these dependencies.File:docs/design/multiprocessing.md (L87-114)## Prior State in v1 There was an environment variable to control whether multiprocessing is used in the v1 engine core, VLLM_ENABLE_V1_MULTIPROCESSING. This defaulted to off. - https://github.com/vllm-project/vllm/blob/d05f88679bedd73939251a17c3d785a354b2946c/vllm/envs.py#L452-L454 When it was enabled, the v1 LLMEngine would create a new process to run the engine core. - 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 It was off by default for all the reasons mentioned above - compatibility with dependencies and code using vLLM as a library. ### Changes Made in v1 There is not an easy solution with Pythons multiprocessing that will work everywhere. As a first step, we can get v1 into a state where it does best effort choice of multiprocessing method to maximize compatibility. - Default to fork. - Use spawn when we know we control the main process (vllm was executed). - If we detect cuda was previously initialized, force spawn and emit a warning. We know fork will break, so this is the best we can do.File:vllm/envs.py (L728-730)VLLM_WORKER_MULTIPROC_METHOD:env_with_choices(VLLM_WORKER_MULTIPROC_METHOD,fork,[spawn,fork]),File:vllm/entrypoints/cli/serve.py (L218-288)defrun_multi_api_server(args:argparse.Namespace):assertnotargs.headless num_api_servers:intargs.api_server_countassertnum_api_servers0ifnum_api_servers1:setup_multiprocess_prometheus()listen_address,socksetup_server(args)engine_argsvllm.AsyncEngineArgs.from_cli_args(args)engine_args._api_process_countnum_api_servers engine_args._api_process_rank-1usage_contextUsageContext.OPENAI_API_SERVER vllm_configengine_args.create_engine_config(usage_contextusage_context)ifnum_api_servers1andenvs.VLLM_ALLOW_RUNTIME_LORA_UPDATING:raiseValueError(VLLM_ALLOW_RUNTIME_LORA_UPDATING cannot be used with api_server_count 1)executor_classExecutor.get_class(vllm_config)log_statsnotengine_args.disable_log_stats parallel_configvllm_config.parallel_config dp_rankparallel_config.data_parallel_rankassertparallel_config.local_engines_onlyordp_rank0api_server_manager:APIServerProcessManager|NoneNonewithlaunch_core_engines(vllm_config,executor_class,log_stats,num_api_servers)as(local_engine_manager,coordinator,addresses):# Construct common args for the APIServerProcessManager up-front.api_server_manager_kwargsdict(target_server_fnrun_api_server_worker_proc,listen_addresslisten_address,socksock,argsargs,num_serversnum_api_servers,input_addressesaddresses.inputs,output_addressesaddresses.outputs,stats_update_addresscoordinator.get_stats_publish_address()ifcoordinatorelseNone,)# For dp ranks 0 in external/hybrid DP LB modes, we must delay the# start of the API servers until the local engine is started# (after the launcher context manager exits),# since we get the front-end stats update address from the coordinator# via the handshake with the local engine.ifdp_rank0ornotparallel_config.local_engines_only:# Start API servers using the manager.api_server_managerAPIServerProcessManager(**api_server_manager_kwargs)# Start API servers now if they werent already started.ifapi_server_managerisNone:api_server_manager_kwargs[stats_update_address](addresses.frontend_stats_publish_address)api_server_managerAPIServerProcessManager(**api_server_manager_kwargs)# Wait for API serverswait_for_completion_or_failure(api_server_managerapi_server_manager,engine_managerlocal_engine_manager,coordinatorcoordinator,)