vLLM 源码: 初始化 [LLM]
vLLM 的初始化主要涉及以下几ß个组件:
uvicorn:负责接收请求的服务器AsyncLLM:前端网关EngineCoreClient:负责AsyncLLM和EngineCore间的通信EngineCore: 请求调度核心,KVCache管理等Executor: 加载权重,执行推理等Scheduler
1 准备工作#
通过 vllm serve 命令启动服务:
1vllm serve Qwenxxx --host 0.0.0.0 --port 8000主类在 vllm/vllm/cli/main.py:
1def main():2 # 懒加载3 import vllm.entrypoints.cli.openai4 import vllm.entrypoints.cli.run_batch5 import vllm.entrypoints.cli.serve6 ...7 from vllm.utils.argparse_utils import FlexibleArgumentParser8
9 # 存放到 CMD_MODULES 中10 CMD_MODULES = [11 vllm.entrypoints.cli.openai,12 vllm.entrypoints.cli.serve,13 ...14 ]15
16 ...17 subparsers = parser.add_subparsers(required=False, dest="subparser")18 cmds = {}19 for cmd_module in CMD_MODULES:20 if cmd_module is vllm.entrypoints.cli.snapshot: ...21 # 遍历 CMD_MODULES 列表调用每个模块的 .cmd_init(),将子命令注册到 argparse 子解析器22 else:23 # 不同的模块返回的实例个数不同24 new_cmds = cmd_module.cmd_init()25 for cmd in new_cmds:26 # 调用实例的 subparser_init() 初始化 subparser27 cmd.subparser_init(subparsers).set_defaults(dispatch_function=cmd.cmd)28 cmds[cmd.name] = cmd29 args = parser.parse_args()30 if args.subparser in cmds:31 cmds[args.subparser].validate(args)serve.py 也在 cil 文件夹下:
1# serve 的 init() 会返回含有单个 CLISubcommand 对象的 list2def cmd_init() -> list[CLISubcommand]:3 return [ServeSubcommand()]4
5class ServeSubcommand(CLISubcommand):6 """The `serve` subcommand for the vLLM CLI."""7 name = "serve"8
9 @staticmethod10 def cmd(args: argparse.Namespace) -> None:11 # 指定模型12 if hasattr(args, "model_tag") and args.model_tag is not None:13 args.model = args.model_tag14
15 # 是否使用 vLLM 的高性能前端而非 uvicorn16 rust_frontend_path = (envs.VLLM_RUST_FRONTEND_PATH if envs.VLLM_USE_RUST_FRONTEND else None)17
18 ...19
20 # 计算启动 API 实例的个数,未指定时进行计算21 if args.api_server_count is None: ...22
23 if is_multi_port: ...24 elif args.api_server_count < 1: ...25 elif args.api_server_count > 1 or rust_frontend_path: ...26 else:27 # Single API server (this process).28 args.api_server_count = None29 # 启动服务器30 # uvloop 用来跑异步 IO 代码31 uvloop.run(run_server(args))run_server() 在 vllm/entrypoints/launchers/api_server/entry.py 下:
1async def run_server(args, **uvicorn_kwargs) -> None:2 """Run a single-worker API server."""3 # 收到 SIGTERM 信号的话执行 _interrupt_init4 signal.signal(signal.SIGTERM, _interrupt_init)5 listen_address, sock = setup_server(args, reuse_port=False)6 await run_server_worker(listen_address, sock, args, **uvicorn_kwargs)setup_server()负责参数校验,创建绑定socket…
1def setup_server(args, *, reuse_port: bool):2 if args.uds:3 sock = create_server_unix_socket(args.uds)4 else:5 # 参数中指定的 host 和 port6 sock_addr = (args.host or "", args.port)7 sock = create_server_socket(sock_addr, reuse_port=reuse_port)8
9 ...10 # 返回监听地址和 socket 对象11 return listen_address, sockrun_server_worker()加载工具,创建engine_client:
1async def run_server_worker(2 listen_address, sock, args, client_config=None, **uvicorn_kwargs3) -> None:4 """Run a single API server worker."""5 # 加载自定义解析器插件6 if args.tool_parser_plugin and len(args.tool_parser_plugin) > 3:7 ToolParserManager.import_tool_parser(args.tool_parser_plugin)8
9 # 推理插件10 if args.reasoning_parser_plugin and len(args.reasoning_parser_plugin) > 3:11 ReasoningParserManager.import_reasoning_parser(args.reasoning_parser_plugin)12 # 把 cli arg 转成 engine args,加载模型权重13 async with build_async_engine_client(14 args,15 client_config=client_config,16 ) as engine_client:17 # build_async_engine_client() 完成后执行 build_and_serve()18 shutdown_task = await build_and_serve(19 engine_client, listen_address, sock, args, **uvicorn_kwargs20 )2 启动组件#
2.1 实例化 AsyncLLM#
AsyncLLM 是 Python API 前端的异步请求网关
1async def build_async_engine_client(2 args: Namespace,3 *,4 usage_context: UsageContext = UsageContext.OPENAI_API_SERVER,5 client_config: dict[str, Any] | None = None,6) -> AsyncIterator[EngineClient]:7
8 # Context manager to handle engine_client lifecycle9 # Ensures everything is shutdown and cleaned up on error/exit10 # 从命令行中解析出来的 args 提取需要的字段生成 AsyncEngineArgs 实例11 engine_args = AsyncEngineArgs.from_cli_args(args)12 # 只有在多 API 模式下才不为 None13 if client_config: ...14
15 async with build_async_engine_client_from_engine_args(16 engine_args,17 usage_context=usage_context,18 client_config=client_config,19 ) as engine:20 yield enginebuild_async_engine_client_from_engine_args() 创建 VllmConfig 和 AsyncLLM:
1async def build_async_engine_client_from_engine_args(2 engine_args: AsyncEngineArgs,3 *,4 usage_context: UsageContext = UsageContext.OPENAI_API_SERVER,5 client_config: dict[str, Any] | None = None,6) -> AsyncIterator[EngineClient]:7 """Create EngineClient, either:8 - in-process using the AsyncLLMEngine Directly9 - multiprocess using AsyncLLMEngine RPC10
11 Returns the Client or None if the creation failed.12 """13 # 创建 VllmConfig 对象14 # vLLM 引擎的总配置容器,由 ModelConfig, CacheConfig, ParallelConfig... 等多个子配置对象组成15 # Create the EngineConfig (determines if we can use V1).16 vllm_config = engine_args.create_engine_config(usage_context=usage_context)17
18 # v1 引擎对外的主入口类19 from vllm.v1.engine.async_llm import AsyncLLM20
21 async_llm: AsyncLLM | None = None22
23 try:24 # 通过静态方法实例化 AsyncLLM25 async_llm = AsyncLLM.from_vllm_config(26 vllm_config=vllm_config,27 usage_context=usage_context,28 enable_log_requests=engine_args.enable_log_requests,29 aggregate_engine_logging=engine_args.aggregate_engine_logging,30 disable_log_stats=engine_args.disable_log_stats,31 client_addresses=client_config,32 client_count=client_count,33 client_index=client_index,34 )35
36 # Don't keep the dummy data in memory37 assert async_llm is not None38 await async_llm.reset_mm_cache()39
40 yield async_llm41 finally:42 if async_llm:43 async_llm.shutdown(timeout=vllm_config.shutdown_timeout)44
45
46@classmethod47def from_vllm_config(48 cls,49 vllm_config: VllmConfig,50 start_engine_loop: bool = True,51 usage_context: UsageContext = UsageContext.ENGINE_CONTEXT,52 stat_loggers: list[StatLoggerFactory] | None = None,53 enable_log_requests: bool = False,54 aggregate_engine_logging: bool = False,55 disable_log_stats: bool = False,56 client_addresses: dict[str, Any] | None = None,57 client_count: int = 1,58 client_index: int = 0,59) -> "AsyncLLM":60 # Create the LLMEngine.61 # cls 表示当前类的实例,也就是调构造方法62 # 获取 Executor 的类名63 return cls(64 vllm_config=vllm_config,65 executor_class=Executor.get_class(vllm_config),66 start_engine_loop=start_engine_loop,67 stat_loggers=stat_loggers,68 log_requests=enable_log_requests,69 log_stats=not disable_log_stats,70 aggregate_engine_logging=aggregate_engine_logging,71 usage_context=usage_context,72 client_addresses=client_addresses,73 client_count=client_count,74 client_index=client_index,75 )AsyncLLM 类在 vllm/v1/engine/async_llm.py 下,创建 InputProcessor 和 OutputProcessor 用于处理请求,创建 AsyncMPClient 进行通信
1class AsyncLLM(EngineClient):2
3 def __init__(4 self,5 vllm_config: VllmConfig,6 executor_class: type[Executor],7 log_stats: bool,8 usage_context: UsageContext = UsageContext.ENGINE_CONTEXT,9 mm_registry: MultiModalRegistry = MULTIMODAL_REGISTRY,10 log_requests: bool = True,11 start_engine_loop: bool = True,12 stat_loggers: list[StatLoggerFactory] | None = None,13 aggregate_engine_logging: bool = False,14 client_addresses: dict[str, Any] | None = None,15 client_count: int = 1,16 client_index: int = 0,17 profiler: TorchProfilerWrapper | None = None,18 ) -> None:19 # Create an AsyncLLM.20
21 # Ensure we can serialize custom transformer configs22 maybe_register_config_serialize_by_value()23
24 self.vllm_config = vllm_config25 ...26 self.model_config = vllm_config.model_config27 self.scheduler_config = vllm_config.scheduler_config28
29 # 处理 prompt30 self.renderer = renderer = renderer_from_config(self.vllm_config)31
32 # 将上层 api 收到的 EngineInput 转换成引擎内核可以识别的 EngineCoreRequest33 # Convert EngineInput --> EngineCoreRequest.34 self.input_processor = InputProcessor(self.vllm_config, renderer)35
36 # 把引擎输出转换成用户可以看懂的37 # Converts EngineCoreOutputs --> RequestOutput.38 self.output_processor = OutputProcessor(39 renderer.tokenizer,40 log_stats=self.log_stats,41 stream_interval=self.vllm_config.scheduler_config.stream_interval,42 tracing_enabled=tracing_endpoint is not None,43 admission_stats=self.admission_stats,44 )45
46 # EngineCore (starts the engine in background process).47 # Hand the renderer to the client so it can start the frontend MM48 # warmup only after engine-core fork (the why is in49 # BaseRenderer.start_mm_warmup_in_background). The warmup is joined50 # by reset_mm_cache / warmup / shutdown.51 # 创建 EngineCoreClient52 self.engine_core = EngineCoreClient.make_async_mp_client(53 vllm_config=vllm_config,54 executor_class=executor_class,55 log_stats=self.log_stats,56 client_addresses=client_addresses,57 client_count=client_count,58 client_index=client_index,59 renderer=renderer,60 )61 ...2.2 AsyncMPClient 及父类的实例化#
EngineCoreClient 是 AsyncLLM ↔ EngineCore 的 IPC 通信客户端,AsyncMPClient 是其中一个实现,对应文件在 vllm/v1/engine/core_client.py 下:
1class EngineCoreClient(ABC):2 def make_async_mp_client(3 vllm_config: VllmConfig,4 executor_class: type[Executor],5 log_stats: bool,6 client_addresses: dict[str, Any] | None = None,7 client_count: int = 1,8 client_index: int = 0,9 renderer: BaseRenderer | None = None,10 ) -> "AsyncMPClient":11 parallel_config = vllm_config.parallel_config12 client_args = (13 vllm_config,14 executor_class,15 log_stats,16 client_addresses,17 client_count,18 client_index,19 )20 if parallel_config.data_parallel_size > 1: ...21 # 返回一个 AsyncMPClient22 return AsyncMPClient(23 *client_args,24 renderer=renderer,25 )1# 继承了 MPClient2class AsyncMPClient(MPClient):3 """Asyncio-compatible client for multi-proc EngineCore."""4
5 @instrument(span_name="AsyncMPClient init")6 def __init__(7 self,8 vllm_config: VllmConfig,9 executor_class: type[Executor],10 log_stats: bool,11 client_addresses: dict[str, Any] | None = None,12 client_count: int = 1,13 client_index: int = 0,14 renderer: BaseRenderer | None = None,15 ):16 super().__init__(17 asyncio_mode=True,18 vllm_config=vllm_config,19 executor_class=executor_class,20 log_stats=log_stats,21 client_addresses=client_addresses,22 renderer=renderer,23 )24
25 # 消息队列,交给 OutputProcessor 解析26 self.outputs_queue = asyncio.Queue[EngineCoreOutputs | Exception]()27
28 ...29 try:30 # If we are running in an asyncio event loop, start the queue task.31 # Otherwise, it will be started lazily. If it is not started here,32 # we could miss EXECUTOR_FAILED messages from engine core if they33 # occur prior to any requests being sent.34 asyncio.get_running_loop()35 # 启动后台队列消费任务,循环从 ZMQ socket 读取 EngineCore 返回的消息,放入self.outputs_queue36 self._ensure_output_queue_task()37 except RuntimeError:38 pass模型加载、GPU worker 启动在父类 MPClient 的初始化方法中:
1class MPClient(EngineCoreClient):2 def __init__(3 self,4 asyncio_mode: bool,5 vllm_config: VllmConfig,6 executor_class: type[Executor],7 log_stats: bool,8 client_addresses: dict[str, Any] | None = None,9 renderer: BaseRenderer | None = None,10 ):11 self.vllm_config = vllm_config12 self._renderer: BaseRenderer | None = renderer13 self._effective_attention_block_sizes: set[int | None] = set()14 self._kv_event_sources: dict[int, KVEventsConfig] = {}15
16 # ZMQ setup.17 # 2 个后台 IO 线程处理消息收发18 sync_ctx = zmq.Context(io_threads=2)19 self.ctx = zmq.asyncio.Context(sync_ctx) if asyncio_mode else sync_ctx20
21 try:22 # State used for data parallel.23 self.engines_running = False24 ...25 if client_addresses: # 单机是 None26 else:27 # Engines are managed by this client.28 addresses = get_engine_zmq_addresses(vllm_config)29 self.input_socket = self.resources.input_socket = make_zmq_socket(30 self.ctx,31 addresses.inputs[0],32 zmq.ROUTER,33 bind=True,34 router_handover=enable_input_socket_handover,35 )36 self.resources.output_socket = make_zmq_socket(37 self.ctx, addresses.outputs[0], zmq.PULL38 )39
40 # 将占位地址解析成真实监听地址41 # Resolve tcp://host:0 placeholders to bound endpoints42 # before engines DEALER-connect. No-op for IPC.43 addresses.inputs[0] = self.input_socket.getsockopt(44 zmq.LAST_ENDPOINT45 ).decode()46 addresses.outputs[0] = self.resources.output_socket.getsockopt(47 zmq.LAST_ENDPOINT48 ).decode()49 # 下一步的逻辑50 with launch_core_engines(51 vllm_config, executor_class, log_stats, addresses52 ) as engine_launch:53 self.resources.coordinator = engine_launch.coordinator54 self.resources.engine_manager = engine_launch.engine_manager55 coordinator = engine_launch.coordinator56 addresses = engine_launch.addresses57 tensor_queue = engine_launch.tensor_queue58 # Engine-core processes have now all been forked/started59 # (CoreEngineProcManager.proc.start()). It is now safe to60 # launch the frontend background MM warmup: it must not61 # run while fork() is in flight (a live thread holding a62 # lock would deadlock the forked child), but the63 # engine-core model load (minutes) that follows is exactly64 # what the warmup should overlap with.65 self._start_mm_warmup()2.3 实例化 CoreEngineProcManager,EngineCore 进程#
launch_core_engines() 创建 CoreEngineProcManager:
1def launch_core_engines(2 vllm_config: VllmConfig,3 executor_class: type[Executor],4 log_stats: bool,5 addresses: EngineZmqAddresses,6) -> Iterator[CoreEngineLaunch]:7 ...8 with zmq_socket_ctx(9 local_handshake_address, zmq.ROUTER, bind=True10 ) as handshake_socket:11 # Start local engines.12 if local_engine_count:13 local_engine_manager = CoreEngineProcManager(14 vllm_config=vllm_config,15 executor_class=executor_class,16 log_stats=log_stats,17 handshake_address=handshake_address,18 client_handshake_address=client_handshake_address,19 local_client=True,20 local_engine_count=local_engine_count,21 start_index=dp_rank,22 local_start_index=local_start_index or 0,23 tensor_queue=tensor_queue,24 )25 else:26 local_engine_manager = None27
28 launch = CoreEngineLaunch(29 local_engine_manager, coordinator, addresses, tensor_queue30 )31 # 暂停回到 MPClient,MPClient执行 self._start_mm_warmup()32 # 生成的 CoreEngineLaunch 返回给 AsyncMPClient33 yield launch34 # 回到这里执行 wait_for_engine_startup,阻塞等待 EngineCore 的 READY 握手消息35 wait_for_engine_startup(36 handshake_socket,37 engines_to_handshake,38 parallel_config,39 dp_size > 1 and vllm_config.model_config.is_moe,40 vllm_config.cache_config,41 launch,42 )CoreEngineProcManager 的构造方法在 vllm/v1/engine/utils.py 下:
1class CoreEngineProcManager:2 def __init__(3 self,4 # 启动多少个 EngineCore 进程5 local_engine_count: int,6 start_index: int,7 local_start_index: int,8 vllm_config: VllmConfig,9 local_client: bool,10 # ZMQ 握手地址,用于 client 和 engine core 通信11 handshake_address: str,12 executor_class: type[Executor],13 log_stats: bool,14 client_handshake_address: str | None = None,15 tensor_queue: Queue | None = None,16 ):17 self._request_shutdown_timeout = vllm_config.shutdown_timeout18 # 从环境变量 VLLM_WORKER_MULTIPROC_METHOD 中获取启动方法 (spawn/fork/forkserver)19 context = get_mp_context()20 # 公共参数字典21 common_kwargs = {22 "vllm_config": vllm_config,23 "local_client": local_client,24 "handshake_address": handshake_address,25 "executor_class": executor_class,26 "log_stats": log_stats,27 "tensor_queue": tensor_queue,28 }29
30 if client_handshake_address:31 common_kwargs["client_handshake_address"] = client_handshake_address32
33 # 是否并行34 is_dp = vllm_config.parallel_config.data_parallel_size > 135
36 from vllm.v1.engine.core import EngineCoreProc37
38 self.processes: list[BaseProcess] = []39 local_dp_ranks = []40 # 循环创建 EngineCore 进程对象41 # vLLM 推理系统的调度与 KV 缓存管理核心42 for index in range(local_engine_count):43 local_index = local_start_index + index44 global_index = start_index + index45
46 # Start EngineCore in background process.47 local_dp_ranks.append(local_index)48 self.processes.append(49 # 根据上下文类型创建对应的 Process 对象,默认 spawn50 context.Process(51 target=EngineCoreProc.run_engine_core,52 name=f"EngineCore_DP{global_index}" if is_dp else "EngineCore",53 kwargs=common_kwargs54 | {"dp_rank": global_index, "local_dp_rank": local_index},55 )56 )57 # 当这个管理器实例被 GC 回收时,自动调用 shutdown,杀掉所有 EngineCore 子进程,防止进程泄漏58 self._finalizer = weakref.finalize(self, shutdown, self.processes)59 self.manager_stopped = threading.Event()60 self.failed_proc_name: str | None = None61
62 # All ranks share this config object: capture the user-provided63 # --device-ids list before the per-rank shard overwrites it. Mutating64 # the config before each proc.start() works because the spawn method65 # pickles process args at start() time, sequentially per rank.66 # numa(Non-Uniform Memory Access) 指在多 CPU服务器上:每个 CPU 插槽,自带一块本地内存67 user_assigned_gpu_ids = vllm_config.parallel_config.assigned_physical_gpu_ids68 try:69 # 循环启动所有子进程 + GPU/NUMA 绑定70 for proc, local_dp_rank in zip(self.processes, local_dp_ranks):71 # Populate the logical-to-physical GPU mapping in DP for72 # platforms that cannot rely on73 # torch.accelerator.set_device_index(), and for Ray.74 needs_device_env_isolation = not (75 current_platform.is_cuda_alike() or current_platform.is_xpu()76 )77 if is_dp and (78 needs_device_env_isolation or vllm_config.parallel_config.use_ray79 ):80 set_assigned_physical_gpu_ids_for_dp_rank(81 vllm_config, local_dp_rank, user_assigned_gpu_ids82 )83
84 with numa_utils.configure_subprocess(85 # EngineCore itself does not have a TP/PP-local rank.86 # When DP is enabled, set_assigned_physical_gpu_ids_for_dp_rank()87 # populates the logical-to-physical mapping for this DP88 # shard, so local_rank=0 means "the first local GPU in89 # this shard". The actual TP/PP worker processes spawned90 # by the executor are bound separately with their own91 # local_rank values.92 vllm_config,93 local_rank=0,94 dp_local_rank=local_dp_rank,95 process_kind="EngineCore",96 ):97 # 启动98 proc.start()99 finally:100 # Kill other procs if not all are running.101 if self.finished_procs():102 self.shutdown()2.4 启动 EngineCore 进程#
子进程启动后,multiprocessing 框架内部调用 run(),执行 context.Process 中的 target: run_engine_core()
1class EngineCoreProc(EngineCore):2 """ZMQ-wrapper for running **EngineCore** in background process."""3 ...4 @staticmethod5 def run_engine_core(*args, dp_rank: int = 0, local_dp_rank: int = 0, **kwargs):6 """Launch EngineCore busy loop in background process."""7 # Ensure we can serialize transformer config after spawning8 maybe_register_config_serialize_by_value()9
10 engine_core: EngineCoreProc | None = None11 signal_callback: SignalCallback | None = None12 clean_shutdown = False13 try:14 vllm_config: VllmConfig = kwargs["vllm_config"]15 parallel_config: ParallelConfig = vllm_config.parallel_config16 data_parallel = parallel_config.data_parallel_size > 1 or dp_rank > 017 # 取出全局配置,判断是否开启 DP18 if data_parallel:19 parallel_config.data_parallel_rank_local = local_dp_rank20 process_title = f"EngineCore_DP{dp_rank}"21 else:22 process_title = "EngineCore"23 set_process_title(process_title)24 maybe_init_worker_tracer("vllm.engine_core", "engine_core", process_title)25
26 parallel_config.data_parallel_index = dp_rank27 # 不开启 MoE 实例化 EngineCoreProc28 if data_parallel and vllm_config.model_config.is_moe:29 # Set data parallel rank for this engine process.30 parallel_config.data_parallel_rank = dp_rank31 engine_core = DPEngineCoreProc(*args, **kwargs)32 else:33 # Non-MoE DP ranks are completely independent, so treat like DP=1.34 # Note that parallel_config.data_parallel_index will still reflect35 # the original DP rank.36 parallel_config.reconfigure_for_independent_dp_rank()37 engine_core = EngineCoreProc(*args, engine_index=dp_rank, **kwargs)38
39 assert engine_core is not None40
41 # 调用 EngineCoreProc 的 run_busy_loop()42 engine_core.run_busy_loop()43
44 # 无论正常退出还是崩溃,一定会调用 engine_core.shutdown()45 finally:46 signal.signal(signal.SIGTERM, signal.SIG_DFL)47 signal.signal(signal.SIGINT, signal.SIG_DFL)48 if signal_callback is not None:49 signal_callback.stop()50 if engine_core is not None:51 engine_core.shutdown()52 if clean_shutdown:53 from vllm.platforms import current_platform54
55 if current_platform.is_rocm():56 # Cleanup above already unfreezes and collects the heap.57 # Freeze the surviving graph to skip another slow cyclic-GC58 # scan during finalization; process exit reclaims it.59 gc.freeze()60
61 def run_busy_loop(self):62 """Core busy loop of the EngineCore."""63 while self._handle_shutdown():64 # 1) Poll the input queue until there is work to do.65 # 检查关闭状态66 self._process_input_queue()67 # Publish request counts before and after GPU step to ensure freshness.68 # 从 ZMQ / 输入队列读取 API 侧发来的请求69 self._maybe_publish_request_counts()70 # 2) Step the engine core and return the outputs.71 # 一轮完整的引擎 step,核心计算调度72 self._process_engine_step()73 self._maybe_publish_request_counts()74
75 raise SystemExit76
77 # 调度器选出一批可以跑的 request, 做批处理调度78 # 把这个 batch 任务下发给 Executor79 # Executor 转发任务到 GPU Worker,执行 prefill 或者 decode 前向计算80 # GPU Worker 计算完成,返回生成 token、更新后的 KV 信息81 # EngineCore 收集结果,组装输出,通过 ZMQ 发回 API 客户端82 def _process_engine_step(self) -> bool:83 """Called only when there are unfinished local requests."""84 # Step the engine core.85 outputs, model_executed = self.step_fn()86 # Put EngineCoreOutputs into the output queue.87 for output in outputs.items() if outputs else ():88 self.output_queue.put_nowait(output)89 # Post-step hook.90 self.post_step(model_executed)91
92 # If no model execution happened but there is still scheduler work93 # (e.g. WAITING_FOR_REMOTE_KVS or delayed KV connector frees), yield94 # the GIL briefly to allow background transfer threads to make progress.95 if not model_executed and self.scheduler.has_requests():96 time.sleep(0.001)97
98 return model_executed2.4.1 EngineCoreProc 的实例化:#
EngineCoreProc 继承自 EngineCore,EngineCore 是内核业务逻辑父类
1class EngineCoreProc(EngineCore):2 @instrument(span_name="EngineCoreProc init")3 def __init__(4 self,5 vllm_config: VllmConfig,6 local_client: bool,7 handshake_address: str,8 executor_class: type[Executor],9 log_stats: bool,10 client_handshake_address: str | None = None,11 tensor_queue: Queue | None = None,12 *,13 engine_index: int = 0,14 ):15 # run_busy_loop 使用的内存队列16 self.input_queue = queue.Queue[tuple[EngineCoreRequestType, Any]]()17 self.output_queue = queue.Queue[tuple[int, EngineCoreOutputs] | bytes]()18 # GPU 崩溃时,往 input 队列塞一个失败事件,主循环感知到后做故障处理19 executor_fail_callback = lambda: self.input_queue.put_nowait(20 (EngineCoreRequestType.EXECUTOR_FAILED, b"")21 )22
23
24 # EngineCore 进程与 AsyncMPClient,DP Coordinator 完成 ZMQ socket 握手25 # 拿到解析好的真实监听地址26 with self._perform_handshakes(27 handshake_address,28 identity,29 local_client,30 vllm_config,31 client_handshake_address,32 ) as addresses:33 # Set up data parallel environment.34 self.has_coordinator = addresses.coordinator_output is not None35 self.frontend_stats_publish_address = (36 addresses.frontend_stats_publish_address37 )38 logger.debug(39 "Has DP Coordinator: %s, stats publish address: %s",40 self.has_coordinator,41 self.frontend_stats_publish_address,42 )43 internal_dp_balancing = (44 self.has_coordinator45 and not vllm_config.parallel_config.data_parallel_external_lb46 )47 # Only publish request queue stats to coordinator for "internal"48 # and "hybrid" LB modes.49 self.publish_dp_lb_stats = internal_dp_balancing50 self.last_counts = (0, 0)51
52 self.addresses = addresses53 self.process_input_queue_block = True54 self._init_data_parallel(vllm_config)55
56 # 调用 EngineCore 的实例化方法,创建 Executor57 super().__init__(58 vllm_config,59 executor_class,60 log_stats,61 executor_fail_callback,62 internal_dp_balancing,63 )64
65 # Background Threads and Queues for IO. These enable us to66 # overlap ZMQ socket IO with GPU since they release the GIL,67 # and to overlap some serialization/deserialization with the68 # model forward pass.69 # Threads handle Socket <-> Queues and core_busy_loop uses Queue.70 ready_event = threading.Event()71 # input_thread 读 ZMQ socket,收到请求后,put 进 input_queue72 input_thread = threading.Thread(73 target=self.process_input_sockets,74 args=(75 addresses.inputs,76 addresses.coordinator_input,77 identity,78 ready_event,79 ),80 daemon=True,81 )82 input_thread.start()83
84 self.output_thread = threading.Thread(85 target=self.process_output_sockets,86 args=(87 addresses.outputs,88 addresses.coordinator_output,89 self.engine_index,90 ),91 daemon=True,92 )93 self.output_thread.start()94
95 # Don't complete handshake until DP coordinator ready message is96 # received.97 while not ready_event.wait(timeout=10):98 if not input_thread.is_alive():99 raise RuntimeError("Input socket thread died during startup")100 assert addresses.coordinator_input is not None101 logger.info("Waiting for READY message from DP Coordinator...")2.4.2 EngineCore 实例化,创建 Executor#
EngineCore 负责调度,KV 缓存,batch 推理:
1class EngineCore:2 """Inner loop of vLLM's Engine."""3
4 def __init__(5 self,6 vllm_config: VllmConfig,7 # Executor 的抽象基类8 executor_class: type[Executor],9 log_stats: bool,10 executor_fail_callback: Callable | None = None,11 include_finished_set: bool = False,12 ):13 from vllm.plugins import load_general_plugins14 # 加载插件15 load_general_plugins()16
17 # VllmConfig 中通过 get_class() 获取对应的 Executor 类18 # executor_class = Executor.get_class(self)19 # 根据配置项 parallel_config.distributed_executor_backend 返回对应的类20 # "mp" == MultiprocExecutor21 self.model_executor = executor_class(vllm_config)22 ...23
24 # 初始化 KVCache25 kv_cache_config = self._initialize_kv_caches(vllm_config)26 self.structured_output_manager = StructuredOutputManager(vllm_config)27
28 # Scheduler 的实例化29 Scheduler = vllm_config.scheduler_config.get_scheduler_cls()30 ...31 self.scheduler: SchedulerInterface = Scheduler(32 vllm_config=vllm_config,33 kv_cache_config=kv_cache_config,34 structured_output_manager=self.structured_output_manager,35 include_finished_set=include_finished_set,36 log_stats=self.log_stats,37 block_size=scheduler_block_size,38 hash_block_size=hash_block_size,39 )40
41 ...42 # Batch Queue 流水线并行优化43 self.batch_queue_size = vllm_config.max_concurrent_batches44 self.batch_queue: (45 deque[tuple[Future[ModelRunnerOutput], SchedulerOutput, Future[Any]]] | None46 ) = None47 if self.batch_queue_size > 1:48 logger.debug("Batch queue is enabled with size %d", self.batch_queue_size)49 self.batch_queue = deque(maxlen=self.batch_queue_size)3 启动 uvicorn#
时序梳理:
1async def run_server_worker(2 listen_address, sock, args, client_config=None, **uvicorn_kwargs3) -> None:4 async with build_async_engine_client() as engine_client:5 shutdown_task = await build_and_serve(...)6
7# 进入 build_async_engine_client:8build_async_engine_client(9 async with build_async_engine_client_from_engine_args(...) as engine:10 yield engine11 ) as engine_client:12
13# 进入 build_async_engine_client_from_engine_args:14build_async_engine_client_from_engine_args(15 try:16 async_llm = AsyncLLM.from_vllm_config(...)17 yield async_llm18 ) as engine:19
20# AsyncLLM 创建 AsyncMPClient,初始化父类 MPClient 进入 launch_core_engines:21class MPClient(EngineCoreClient):22 def __init__(...):23 try:24 if client_addresses:25 else:26 with launch_core_engines(...) as engine_launch:27
28# 进入 launch_core_engines:29launch_core_engines(30 with zmq_socket_ctx(...) as handshake_socket:31 launch = CoreEngineLaunch(...)32 yield launch33 ) as engine_launch:34
35# 当 CoreEngine 子进程启动完毕返回 READY 后向上返回最后执行 build_and_serve():
1async def build_and_serve(2 engine_client: EngineClient,3 listen_address: str,4 sock: socket.socket,5 args: Namespace,6 **uvicorn_kwargs,7) -> asyncio.Task:8 """Build FastAPI app, initialize state, and start serving.9
10 Returns the shutdown task for the caller to await.11 """12 # 构建 FastAPI app13 app = build_app(args, supported_tasks, model_config)14 await init_app_state(engine_client, app.state, args, supported_tasks)15
16 logger.info("Starting vLLM server on %s", listen_address)17
18 # 启动 uvicorn http 服务19 return await serve_http(20 app,21 sock=sock,22 enable_ssl_refresh=args.enable_ssl_refresh,23 host=args.host,24 port=args.port,25 log_level=args.uvicorn_log_level,26 # NOTE: When the 'disable_uvicorn_access_log' value is True,27 # no access log will be output.28 access_log=not args.disable_uvicorn_access_log,29 timeout_keep_alive=envs.VLLM_HTTP_TIMEOUT_KEEP_ALIVE,30 ssl_keyfile=args.ssl_keyfile,31 ssl_certfile=args.ssl_certfile,32 ssl_ca_certs=args.ssl_ca_certs,33 ssl_cert_reqs=args.ssl_cert_reqs,34 ssl_ciphers=args.ssl_ciphers,35 h11_max_incomplete_event_size=args.h11_max_incomplete_event_size,36 h11_max_header_count=args.h11_max_header_count,37 **uvicorn_kwargs,38 )39
40def build_app(41 args: Namespace,42 supported_tasks: tuple["SupportedTask", ...] | None = None,43 model_config: ModelConfig | None = None,44) -> FastAPI:45
46 if args.disable_fastapi_docs:47 app = FastAPI(48 openapi_url=None, docs_url=None, redoc_url=None, lifespan=lifespan49 )50 elif args.enable_offline_docs:51 app = FastAPI(docs_url=None, redoc_url=None, lifespan=lifespan)52 else:53 app = FastAPI(lifespan=lifespan)54 app.state.args = args55 app.root_path = args.root_path56
57 # 注册 api 路由58 register_api_routers(args, app, supported_tasks, model_config)59
60 # Endpoint plugins are attached last so their routes are registered after all core61 # routers. This runs even for the CPU only render server. A plugin eligible for62 # the `render` task still gets its routes registered. It receives63 # `engine_client=None` at Phase B (see `_init_endpoint_plugins_state`).64 attach_endpoint_plugins(app, supported_tasks)65
66 init_exception_handler(app)67 init_entrypoints_middleware(args, app, supported_tasks)68 app = sagemaker_standards_bootstrap(app)69 return app