中文回答 WorkerProc 与 WorkerWrapperBase 及worker 相关的区别与联系,从设计模式角度考虑,为啥要加一层workerwrapper. 相关类干嘛用的
这段代码的作用是处理通过 RPC 传递的可调用对象,允许主进程动态地让子进程执行任意函数(而不仅仅是预定义的方法名)。
代码含义
python
elif isinstance(method, bytes):
func = partial(cloudpickle.loads(method), self.worker)
cloudpickle.loads(method):将字节串反序列化回一个 Python 可调用对象(比如函数或方法)。partial(..., self.worker):将该可调用对象的第一个参数 固定为self.worker(即当前 Worker 实例)。- 最终
func是一个新的可调用对象,调用func(*args, **kwargs)等价于调用原始函数original(self.worker, *args, **kwargs)。
为什么要这样做?
- 灵活性 :主进程可以动态定义 Worker 需要执行的代码,而不必提前在
WorkerWrapperBase类中声明所有方法。 - 序列化传递 :函数通过
cloudpickle序列化后经消息队列传输,子进程反序列化后执行。 - 绑定 Worker 实例 :因为反序列化得到的函数通常需要访问 Worker 对象(如调用其内部方法或访问属性),通过
partial将self.worker作为第一个参数固定,使得调用时只需传入后续参数。
具体举例
假设主进程需要让 Worker 执行一个自定义操作:记录当前 GPU 显存使用情况并返回剩余显存 。这个操作不是 WorkerWrapperBase 的现有方法,但主进程可以动态定义并发送。
步骤 1:主进程(Executor)中定义并序列化函数
python
import cloudpickle
def get_free_memory(worker, threshold_mb: int):
"""返回当前 Worker 的空闲显存(MB),如果低于阈值则打印警告"""
# 假设 worker 有一个 .device 属性和 .get_memory_info() 方法
free = worker.get_memory_info()[0] / (1024**2) # 转换为 MB
if free < threshold_mb:
print(f"Warning: only {free:.2f} MB free on {worker.device}")
return free
# 序列化函数
serialized_func = cloudpickle.dumps(get_free_memory)
# 通过 RPC 发送到 Worker,参数 (threshold_mb=100) 作为 args/kwargs
# output_rank 指定由哪个 Worker 返回结果
self.rpc_broadcast_mq.enqueue((serialized_func, (100,), {}, output_rank=0))
步骤 2:子进程(WorkerProc)中处理
python
# method 是 bytes 类型,值为 serialized_func
elif isinstance(method, bytes):
# 反序列化得到原始函数 get_free_memory
original_func = cloudpickle.loads(method) # 即 get_free_memory
# 用 partial 固定第一个参数为 self.worker
func = partial(original_func, self.worker)
# 之后调用 func(*args, **kwargs)
output = func(100) # 等价于 get_free_memory(self.worker, 100)
步骤 3:执行效果
- 子进程实际执行
get_free_memory(self.worker, 100),返回当前 Worker 的空闲显存(如果低于 100MB 还会打印警告)。 - 结果通过响应队列返回给主进程。
与字符串方法名的对比
| method 类型 | 用途 | 示例 |
|---|---|---|
str |
调用 WorkerWrapperBase 的预定义方法 |
"execute_model" → getattr(self.worker, "execute_model")(*args, **kwargs) |
bytes |
执行任意自定义函数(动态行为) | 序列化的 get_free_memory → partial(..., self.worker)(*args, **kwargs) |
典型应用场景
- 调试或性能分析:临时注入监控代码,获取 Worker 内部状态。
- 动态更新策略:主进程根据当前负载发送不同的处理逻辑(如动态调整采样参数)。
- 扩展性 :无需修改
WorkerWrapperBase类即可新增功能,适合插件化架构。
这种设计让 vLLM 的 RPC 机制非常灵活,但也要注意序列化开销(函数较大时可能影响性能),因此主要用于非频繁调用的操作。
WorkerWrapperBase 和 WorkerProc 在 vLLM 的多进程架构中扮演着职责分明的两个角色。
简单来说,WorkerWrapperBase 定义了如何执行 模型(What to do),而 WorkerProc 则负责管理和通信(How to manage & communicate)。
🎭 核心区别:两类Worker的分工
| 对比维度 | WorkerWrapperBase (业务执行者) | WorkerProc (进程管理器与信使) |
|---|---|---|
| 本质身份 | 一个Python基类(Wrapper)。 | 一个独立的OS进程(Process)。 |
| 核心职责 | 1. 执行核心模型操作:execute_model, sample_tokens等。 2. 管理GPU:加载模型、管理KV Cache、执行推理。 |
1. 生命周期管理:作为守护进程被创建、启动、监控和销毁。 2. IPC通信枢纽:持有MessageQueue,接收Executor的RPC请求,并发送执行结果。 3. 封装WorkerWrapperBase:接收请求后,调用其内部的WorkerWrapperBase实例执行具体任务。 |
| 包含关系 | WorkerProc 内部包含 一个 WorkerWrapperBase 的实例作为其核心功能组件。 |
不包含,WorkerWrapperBase 是其委托执行具体工作的内部对象。 |
| 部署位置 | 作为一个对象,被包含并运行在 WorkerProc 进程中。 |
作为一个独立的OS进程运行。 |
🔗 设计剖析:为何选择 WorkerWrapperBase 作为 RPC 目标
vLLM 的设计如此选择,核心是为了实现关注点分离,这是一种经典的模块化设计原则。
-
单一职责原则(SRP):将 RPC 协议实现、进程生命周期、IPC 队列管理这些"通信与调度"的复杂逻辑,与"模型加载、执行"这些纯粹的"业务逻辑"彻底分开。
WorkerWrapperBase专注于具体的模型计算任务,而不必关心自己是被单个进程还是多进程调用。WorkerProc则专注于进程间通信和健壮性,无需理会execute_model内部复杂的计算细节。
-
实现通信与执行的解耦 :当
MultiprocExecutor广播一个RPC请求(如"execute_model")时,它发送的目标是WorkerWrapperBase的方法。WorkerProc接收请求后,通过getattr等方法,动态调用其内部WorkerWrapperBase实例对应的execute_model方法,并将结果通过worker_response_mq发送回去。- 这种设计使得 vLLM 能够无缝替换底层的执行逻辑,例如从 GPU Worker 切换到 CPU Worker,只需更换
WorkerWrapperBase的不同子类即可,而无需修改WorkerProc的进程管理代码。
中文回答 WorkerProc 与 WorkerWrapperBase 及worker 相关的区别与联系,从设计模式角度考虑,为啥要加一层workerwrapper. 相关类关系,画出流程图,时许等相关的图
WorkerProc、WorkerWrapperBase 与 Worker 的区别与联系
核心区别
| 组件 | 职责 | 使用场景 |
|---|---|---|
| WorkerBase | 抽象基类,定义 worker 接口 | 所有 worker 实现的基础 |
| WorkerWrapperBase | 通用包装器,延迟初始化和方法转发 | 跨执行器复用 |
| WorkerProc | 多进程执行器的进程包装器 | 仅用于 MultiprocExecutor |
| 具体Worker | 实际的模型执行逻辑 | GPUWorker、CPUWorker 等 |
设计模式分析
1. 延迟初始化模式 (Lazy Initialization)
WorkerWrapperBase 实现延迟初始化,将 worker 创建推迟到真正需要时 1 :
python
class WorkerWrapperBase:
def __init__(self, rpc_rank: int = 0, global_rank: int | None = None):
self.rpc_rank: int = rpc_rank
self.global_rank: int = self.rpc_rank if global_rank is None else global_rank
# 此时 worker 还未创建,只是保存配置信息
self.worker: WorkerBase # 类型注解,但实际对象未创建
真正的初始化在 init_worker 中进行 2 。
2. 代理模式 (Proxy Pattern)
通过 __getattr__ 实现方法转发,将所有调用代理给实际的 worker 实例 3 :
python
def __getattr__(self, attr: str):
return getattr(self.worker, attr)
3. 工厂模式 (Factory Pattern)
WorkerWrapperBase 根据配置动态创建不同类型的 worker 4 :
python
if isinstance(parallel_config.worker_cls, str):
worker_class: type[WorkerBase] = resolve_obj_by_qualname(
parallel_config.worker_cls
)
类关系图
包含
包含
管理
<<abstract>>
WorkerBase
+init_device()
+load_model()
+execute_model()
+shutdown()
WorkerWrapperBase
-worker: WorkerBase
-vllm_config: VllmConfig
+init_worker()
+getattr(attr)
+execute_model()
WorkerProc
-worker: WorkerWrapperBase
-rank: int
-async_output_queue: queue.Queue
+worker_busy_loop()
+handle_output()
+make_worker_process()
MultiprocExecutor
-workers: list[WorkerProc]
+_init_executor()
+execute_model()
GPUWorker
+init_device()
+load_model()
CPUWorker
+init_device()
+load_model()
RayWorkerWrapper
+adjust_rank()
+execute_method()
时序流程图
初始化流程
GPUWorker WorkerWrapperBase WorkerProc MultiprocExecutor GPUWorker WorkerWrapperBase WorkerProc MultiprocExecutor 延迟初始化 - 只保存配置 make_worker_process() worker_main() WorkerWrapperBase() init_worker() resolve_obj_by_qualname() GPUWorker(**kwargs) init_device() load_model() worker_busy_loop()
方法调用流程
GPUWorker WorkerWrapperBase WorkerProc MultiprocExecutor GPUWorker WorkerWrapperBase WorkerProc MultiprocExecutor getattr 代理转发 execute_model() execute_model() execute_model() ModelRunnerOutput ModelRunnerOutput handle_output()
为什么需要 WorkerWrapperBase 层
1. 跨执行器复用
- UniProcExecutor 直接使用
WorkerWrapperBase5 - RayExecutor 使用
RayWorkerWrapper继承自WorkerWrapperBase6 - MultiprocExecutor 通过
WorkerProc间接使用
2. 环境变量依赖处理
在 Ray 环境中,GPU 分配是在 actor 调度后确定的,需要延迟初始化 6 。
3. 生命周期管理
统一的初始化和清理接口,支持插件系统和扩展 7 。
4. 错误处理和资源管理
集中的错误处理逻辑,避免部分初始化状态。
WorkerProc 的特殊职责
作为多进程执行器的特有组件,WorkerProc 负责:
- 进程创建和管理 :通过
multiprocessing.Process创建子进程 8 - 进程间通信 :管理消息队列和管道通信 9
- 异步输出处理 :管理异步输出队列和线程 10
- 工作循环 :运行
worker_busy_loop处理 RPC 调用 11
Notes
这种分层设计体现了单一职责原则和开闭原则:
WorkerBase定义接口WorkerWrapperBase提供通用的生命周期管理WorkerProc处理多进程特有的逻辑- 具体 Worker 实现专注于模型执行
Wiki pages you might want to explore:
- Worker and Executor Architecture (vllm-project/vllm)
- Multi-Process Engine Management (vllm-project/vllm)
Wiki pages you might want to explore:
Citations
File: vllm/v1/worker/worker_base.py (L174-181)
python
class WorkerWrapperBase:
"""
This class represents one process in an executor/engine. It is responsible
for lazily initializing the worker and handling the worker's lifecycle.
We first instantiate the WorkerWrapper, which remembers the worker module
and class name. Then, when we call `update_environment_variables`, and the
real initialization happens in `init_worker`.
"""
File: vllm/v1/worker/worker_base.py (L216-301)
python
@instrument(span_name="Worker init")
def init_worker(self, all_kwargs: list[dict[str, Any]]) -> None:
"""
Here we inject some common logic before initializing the worker.
Arguments are passed to the worker class constructor.
"""
kwargs = all_kwargs[self.rpc_rank]
vllm_config: VllmConfig | None = kwargs.get("vllm_config")
assert vllm_config is not None, (
"vllm_config is required to initialize the worker"
)
self.vllm_config = vllm_config
vllm_config.enable_trace_function_call_for_thread()
from vllm.plugins import load_general_plugins
load_general_plugins()
parallel_config = vllm_config.parallel_config
if isinstance(parallel_config.worker_cls, str):
worker_class: type[WorkerBase] = resolve_obj_by_qualname(
parallel_config.worker_cls
)
else:
raise ValueError(
"passing worker_cls is no longer supported. "
"Please pass keep the class in a separate module "
"and pass the qualified name of the class as a string."
)
if parallel_config.worker_extension_cls:
worker_extension_cls = resolve_obj_by_qualname(
parallel_config.worker_extension_cls
)
extended_calls = []
if worker_extension_cls not in worker_class.__bases__:
# check any conflicts between worker and worker_extension_cls
for attr in dir(worker_extension_cls):
if attr.startswith("__"):
continue
assert not hasattr(worker_class, attr), (
f"Worker class {worker_class} already has an attribute"
f" {attr}, which conflicts with the worker"
f" extension class {worker_extension_cls}."
)
if callable(getattr(worker_extension_cls, attr)):
extended_calls.append(attr)
# dynamically inherit the worker extension class
worker_class.__bases__ = worker_class.__bases__ + (
worker_extension_cls,
)
logger.info(
"Injected %s into %s for extended collective_rpc calls %s",
worker_extension_cls,
worker_class,
extended_calls,
)
shared_worker_lock = kwargs.pop("shared_worker_lock", None)
if shared_worker_lock is None:
msg = (
"Missing `shared_worker_lock` argument from executor. "
"This argument is needed for mm_processor_cache_type='shm'."
)
mm_config = vllm_config.model_config.multimodal_config
if mm_config and mm_config.mm_processor_cache_type == "shm":
raise ValueError(msg)
else:
logger.warning_once(msg)
self.mm_receiver_cache = None
else:
self.mm_receiver_cache = (
MULTIMODAL_REGISTRY.worker_receiver_cache_from_config(
vllm_config,
shared_worker_lock,
)
)
with set_current_vllm_config(self.vllm_config):
# To make vLLM config available during worker initialization
self.worker = worker_class(**kwargs)
File: vllm/v1/worker/worker_base.py (L314-315)
python
def __getattr__(self, attr: str):
return getattr(self.worker, attr)
File: vllm/v1/executor/uniproc_executor.py (L29-29)
python
self.driver_worker = WorkerWrapperBase(rpc_rank=0)
File: vllm/v1/executor/ray_utils.py (L53-63)
python
class RayWorkerWrapper(WorkerWrapperBase):
"""Ray wrapper for vllm.worker.Worker, allowing Worker to be
lazily initialized after Ray sets CUDA_VISIBLE_DEVICES."""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Since the compiled DAG runs a main execution
# in a different thread that calls cuda.set_device.
# The flag indicates is set_device is called on
# that thread.
self.compiled_dag_cuda_device_set = False
File: vllm/v1/executor/multiproc_executor.py (L555-585)
python
def _init_message_queues(
self, input_shm_handle: Handle, vllm_config: VllmConfig
) -> None:
if vllm_config.parallel_config.nnodes_within_dp == 1:
# Initialize MessageQueue for receiving SchedulerOutput
self.rpc_broadcast_mq = MessageQueue.create_from_handle(
input_shm_handle, self.worker.rank
)
# Initializes a message queue for sending the model output
self.worker_response_mq = MessageQueue(1, 1)
self.peer_response_handles = []
else:
# Initialize remote MessageQueue for receiving SchedulerOutput across nodes
self.rpc_broadcast_mq = get_inner_dp_world_group().create_mq_broadcaster(
external_writer_handle=input_shm_handle,
# Since there is external_writer_handle from executor proc,
# where the ready signal from actual writer is sent out of the
# create_mq_broadcaster method and after this setup, we make it
# non blocking. The handshake will be triggered when
# worker.rpc_broadcast_mq.wait_until_ready() is called
blocking=False,
)
# Initializes remote message queue for sending the model output to the
# driver worker, exposing peer_response_handles for driver worker
# that include handles for all ranks
self.worker_response_mq, self.peer_response_handles = (
get_inner_dp_world_group().create_single_reader_mq_broadcasters(
reader_rank_in_group=0
)
)
File: vllm/v1/executor/multiproc_executor.py (L632-639)
python
if self.use_async_scheduling:
self.async_output_queue: queue.Queue = queue.Queue()
self.async_output_copy_thread = Thread(
target=self.async_output_busy_loop,
daemon=True,
name="WorkerAsyncOutputCopy",
)
self.async_output_copy_thread.start()
File: vllm/v1/executor/multiproc_executor.py (L685-690)
python
proc = context.Process(
target=WorkerProc.worker_main,
kwargs=process_kwargs,
name=f"VllmWorker-{rank}",
daemon=True,
)
File: vllm/v1/executor/multiproc_executor.py (L953-980)
python
def worker_busy_loop(self):
"""Main busy loop for Multiprocessing Workers"""
assert self.rpc_broadcast_mq is not None
while True:
method, args, kwargs, output_rank = self.rpc_broadcast_mq.dequeue(
indefinite=True
)
try:
if isinstance(method, str):
func = getattr(self.worker, method)
elif isinstance(method, bytes):
func = partial(cloudpickle.loads(method), self.worker)
output = func(*args, **kwargs)
except Exception as e:
# Notes have been introduced in python 3.11
if hasattr(e, "add_note"):
e.add_note(traceback.format_exc())
logger.exception("WorkerProc hit an exception.")
# exception might not be serializable, so we convert it to
# string, only for logging purpose.
if output_rank is None or self.rank == output_rank:
self.handle_output(e)
continue
if output_rank is None or self.rank == output_rank:
self.handle_output(output)
三十、WorkerWrapperBase vs WorkerProc 详解
30.1 问题的本质
在 worker_busy_loop 中,RPC 调用最终落到 self.worker 上:
python
# multiproc_executor.py L870-871
func = getattr(self.worker, method) # self.worker 是 WorkerWrapperBase!
output = func(*args, **kwargs)
这里的 self.worker 是 WorkerWrapperBase ,而不是 WorkerBase(真正的 GPU Worker)。为什么要多这一层?
30.2 三层架构总览
┌──────────────────────────────────────────────────────┐
│ WorkerProc │ ← 进程管理层
│ 职责: 进程生命周期, MQ通信, 信号处理, 异步输出线程 │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ WorkerWrapperBase │ │ ← 代理/装饰层
│ │ 职责: 懒初始化, 环境配置, MM缓存, 方法拦截 │ │
│ │ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Worker (GPU Worker) │ │ │ ← 业务实现层
│ │ │ 职责: GPU操作, 模型加载/执行, KV缓存 │ │ │
│ │ │ 继承自 WorkerBase │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
30.3 类关系图
继承
包含 self.worker
包含 self.worker
getattr 代理
<<abstract>>
WorkerBase
+vllm_config
+local_rank, rank
+device, model_runner
+init_device()
+load_model()
+execute_model(scheduler_output)
+sample_tokens(grammar_output)
+initialize_cache(num_gpu, num_cpu)
+get_kv_cache_spec()
+compile_or_warm_up_model()
+get_cache_block_size_bytes()
+add_lora() / remove_lora() / pin_lora()
+check_health()
+shutdown()
+reset_mm_cache()
+apply_model(fn)
Worker
+model_runner: GPUModelRunner
+init_device()
+load_model()
+execute_model()
+sample_tokens()
+initialize_cache()
+get_kv_cache_spec()
+execute_dummy_batch()
WorkerWrapperBase
+rpc_rank: int
+global_rank: int
+worker: WorkerBase
+mm_receiver_cache
+init_worker(all_kwargs)
+update_environment_variables()
+execute_method(method)
+getattr(attr) : → self.worker
+execute_model() : 拦截
+init_device() : 拦截
+reset_mm_cache() : 拦截
+_apply_mm_cache()
+shutdown()
WorkerProc
+rank: int
+worker: WorkerWrapperBase
+rpc_broadcast_mq: MessageQueue
+worker_response_mq: MessageQueue
+use_async_scheduling: bool
+async_output_queue
+async_output_copy_thread
+worker_busy_loop()
+handle_output()
+enqueue_output()
+make_worker_process()
+worker_main()
+shutdown()
30.4 WorkerWrapperBase 全部方法一览
| 方法 | 行号 | 类型 | 作用 |
|---|---|---|---|
__init__(rpc_rank, global_rank) |
L188 | 初始化 | 记录 rank,不创建真实 Worker |
shutdown() |
L210 | 生命周期 | 调用 self.worker.shutdown() |
adjust_rank(rank_mapping) |
L214 | 初始化 | 调整 rpc_rank(SPMD 场景) |
update_environment_variables(envs_list) |
L223 | 初始化 | 设置该 rank 的环境变量 |
init_worker(all_kwargs) |
L231 | 核心 | 懒初始化:解析 worker 类名 → 实例化 Worker |
initialize_from_config(kv_cache_configs) |
L316 | 拦截 | 按 global_rank 选配置,注入 vllm_config 上下文 |
init_device() |
L322 | 拦截 | 注入 set_current_vllm_config 上下文后委托 |
execute_method(method, *args, **kwargs) |
L328 | RPC 入口 | 通过 run_method 分发调用 |
__getattr__(attr) |
L347 | 代理 | 未定义的属性/方法 → 转发给 self.worker |
_apply_mm_cache(scheduler_output) |
L350 | 多模态 | 将共享内存中的 MM features 注入请求 |
execute_model(scheduler_output) |
L360 | 拦截 | 先 _apply_mm_cache,再委托 Worker |
reset_mm_cache() |
L367 | 拦截 | 清理 MM receiver cache + Worker cache |
30.5 为什么不是 WorkerProc 直接持有 Worker?
关键原因:WorkerWrapperBase 是跨 Executor 通用的,WorkerProc 只在多进程 Executor 中使用。
MultiprocExecutor ──→ WorkerProc ──→ WorkerWrapperBase ──→ Worker
RayExecutor ──→ RayWorker ──→ WorkerWrapperBase ──→ Worker
UniProcExecutor ──→ (直接) ──→ WorkerWrapperBase ──→ Worker
不同的 Executor 后端有不同的进程/通信管理方式,但都需要:
- 懒初始化 Worker
- 环境变量设置
- 多模态缓存拦截
- vllm_config 上下文注入
如果把这些逻辑放在 WorkerProc 里,RayExecutor 和 UniProcExecutor 就得重复实现。
30.6 职责对比
| 职责 | WorkerProc | WorkerWrapperBase | Worker (GPU) |
|---|---|---|---|
| 进程创建/销毁 | ✅ make_worker_process, worker_main |
❌ | ❌ |
| 信号处理 (SIGTERM) | ✅ signal_handler |
❌ | ❌ |
| 父进程死亡检测 | ✅ death_pipe + monitor |
❌ | ❌ |
| MessageQueue 通信 | ✅ rpc_broadcast_mq, worker_response_mq |
❌ | ❌ |
| RPC 消息循环 | ✅ worker_busy_loop |
❌ | ❌ |
| 异步输出线程 | ✅ async_output_copy_thread |
❌ | ❌ |
| Worker 类解析/实例化 | ❌ | ✅ init_worker |
❌ |
| 环境变量注入 | ❌ | ✅ update_environment_variables |
❌ |
| vllm_config 上下文 | ❌ | ✅ init_device, initialize_from_config |
❌ |
| 多模态缓存拦截 | ❌ | ✅ _apply_mm_cache, execute_model |
❌ |
__getattr__ 代理 |
❌ | ✅ 透明转发给 Worker | ❌ |
| Worker 扩展类注入 | ❌ | ✅ 动态 mixin __bases__ |
❌ |
| GPU 初始化 | ❌ | ❌ | ✅ init_device |
| 模型加载/推理 | ❌ | ❌ | ✅ load_model, execute_model |
| KV Cache 管理 | ❌ | ❌ | ✅ initialize_cache |
| LoRA 管理 | ❌ | ❌ | ✅ add_lora, remove_lora |
30.7 __getattr__ 代理模式的工作原理
python
# WorkerWrapperBase L347-348
def __getattr__(self, attr: str):
return getattr(self.worker, attr)
当 Executor 调用 collective_rpc("some_method") 时,worker_busy_loop 执行:
python
func = getattr(self.worker, "some_method")
# self.worker 是 WorkerWrapperBase
# 1. Python 先在 WorkerWrapperBase 类及其 MRO 中查找 "some_method"
# 2. 如果找到(如 execute_model, init_device)→ 调用 WorkerWrapperBase 的版本(拦截)
# 3. 如果没找到 → 触发 __getattr__ → getattr(self.worker, "some_method")
# → 转发给 Worker(GPU Worker)
示例------哪些被拦截,哪些被透传:
| 方法调用 | 路径 | 原因 |
|---|---|---|
execute_model(so) |
WorkerWrapperBase.execute_model → _apply_mm_cache → Worker.execute_model | 需要先注入 MM 缓存 |
init_device() |
WorkerWrapperBase.init_device → with set_current_vllm_config → Worker.init_device |
需要设置全局配置上下文 |
reset_mm_cache() |
WorkerWrapperBase.reset_mm_cache → clear receiver cache → Worker.reset_mm_cache | 需要额外清理 receiver cache |
load_model() |
透传 → __getattr__ → Worker.load_model |
不需要额外处理 |
sample_tokens(g) |
透传 → __getattr__ → Worker.sample_tokens |
不需要额外处理 |
check_health() |
透传 → __getattr__ → Worker.check_health |
不需要额外处理 |
add_lora(req) |
透传 → __getattr__ → Worker.add_lora |
不需要额外处理 |
get_kv_cache_spec() |
透传 → __getattr__ → Worker.get_kv_cache_spec |
不需要额外处理 |
30.8 设计模式分析
| 设计模式 | 体现 | 好处 |
|---|---|---|
| Proxy(代理) | WorkerWrapperBase.__getattr__ → self.worker |
透明转发,调用者不感知中间层 |
| Decorator(装饰器) | execute_model 先注入 MM cache 再委托 |
不修改 Worker 代码就增加新行为 |
| Lazy Initialization | init_worker() 延迟创建真正的 Worker |
允许先配置环境再初始化 |
| Template Method | init_worker 中的固定流程(加载插件→解析类→扩展→实例化) |
标准化 Worker 创建流程 |
| Strategy | parallel_config.worker_cls 字符串 → 动态解析 Worker 类 |
运行时切换 GPU/CPU/TPU Worker |
| Mixin | worker_class.__bases__ += (worker_extension_cls,) |
动态扩展 Worker 能力 |
30.9 初始化流程时序图
Worker (GPU) WorkerWrapperBase WorkerProc MultiprocExecutor Worker (GPU) WorkerWrapperBase WorkerProc MultiprocExecutor _init_executor() 仅记录 rank,不创建 Worker getattr 透传 make_worker_process() 创建子进程 worker_main() 入口 WorkerWrapperBase(rpc_rank, global_rank) init_worker(all_kwargs) 解析 worker_cls 字符串 处理 worker_extension_cls (Mixin) 创建 mm_receiver_cache Worker(**kwargs) self.worker = Worker实例 self.worker = wrapper (WorkerWrapperBase) _init_message_queues() init_device() with set_current_vllm_config worker.init_device() 初始化 GPU 设备、NCCL load_model() worker.load_model() 加载模型权重 READY 信号 worker_busy_loop() 开始接收 RPC
30.10 RPC 调用方法解析流程
是: execute_model, init_device...
否: load_model, sample_tokens...
Executor: collective_rpc('method_name')
rpc_broadcast_mq.enqueue()
WorkerProc.worker_busy_loop()
getattr(self.worker, method_name)
method_name 在
WorkerWrapperBase
中定义?
调用 WorkerWrapperBase 的方法
(拦截 + 增强)
触发 getattr
→ getattr(self.worker, method_name)
→ Worker 的方法
执行方法,返回结果
30.11 为什么这样设计?------ 总结
-
关注点分离(Separation of Concerns)
WorkerProc= 怎么运行(进程、MQ、信号)WorkerWrapperBase= 怎么管理(初始化、配置、拦截)Worker= 做什么(GPU 计算、模型推理)
-
跨 Executor 复用
WorkerWrapperBase被MultiprocExecutor、RayExecutor、UniProcExecutor共同使用- 只有
WorkerProc是MultiprocExecutor独有的
-
开闭原则(Open-Closed Principle)
- 新增 Worker 类型(如 TPU Worker)?只需实现
WorkerBase,WorkerWrapperBase自动代理 - 新增拦截逻辑(如 MM 缓存)?只需修改
WorkerWrapperBase,不动Worker - 新增执行后端(如新的分布式框架)?只需写新的 "Proc" 类,复用
WorkerWrapperBase
- 新增 Worker 类型(如 TPU Worker)?只需实现
-
动态扩展
worker_extension_cls通过 Mixin 动态注入方法到 Worker 类worker_cls通过字符串在运行时解析为具体类- 这些都在
WorkerWrapperBase.init_worker()中完成,与进程管理无关
30.12 代码文件引用
| 文件 | 行号 | 关键内容 |
|---|---|---|
vllm/v1/worker/worker_base.py |
L34-177 | WorkerBase 抽象基类全部方法 |
vllm/v1/worker/worker_base.py |
L179-373 | WorkerWrapperBase 完整实现 |
vllm/v1/worker/worker_base.py |
L347 | __getattr__ 代理方法 |
vllm/v1/worker/worker_base.py |
L231-314 | init_worker 懒初始化 + Mixin |
vllm/v1/worker/worker_base.py |
L360-365 | execute_model 拦截(MM cache 注入) |
vllm/v1/executor/multiproc_executor.py |
L506-925 | WorkerProc 完整实现 |
vllm/v1/executor/multiproc_executor.py |
L557 | wrapper = WorkerWrapperBase(...) 创建 |
vllm/v1/executor/multiproc_executor.py |
L862-888 | worker_busy_loop RPC 分发 |
vllm/v1/worker/gpu_worker.py |
L102-120 | Worker (GPU) 继承 WorkerBase |
vllm/v1/serial_utils.py |
L435-459 | run_method 方法解析逻辑 |
三十一、WorkerProc / WorkerWrapperBase / Worker 完整架构分析
本节从设计模式和跨 Executor 复用角度,全面分析三者的区别、联系、以及"为什么要加一层 Wrapper"。
31.1 一句话定位
| 类 | 一句话定位 | 文件 |
|---|---|---|
WorkerBase |
硬件抽象接口------"一个 Worker 能做什么" | worker_base.py L34 |
Worker (GPU) |
具体硬件实现------"GPU 上怎么做" | gpu_worker.py L102 |
WorkerWrapperBase |
生命周期代理------"怎么创建和装饰 Worker" | worker_base.py L179 |
RayWorkerWrapper |
Ray 专用装饰------"Ray Actor 中怎么包装 Worker" | ray_utils.py L41 |
WorkerProc |
进程容器------"怎么在独立进程中运行 Worker" | multiproc_executor.py L506 |
31.2 完整类层次关系图
继承
持有 self.worker
继承
持有 self.worker
直接持有
管理进程
管理Actor
<<abstract 硬件接口>>
WorkerBase
+init_device()
+load_model()
+execute_model(scheduler_output)
+sample_tokens(grammar_output)
+initialize_cache(gpu, cpu)
+get_kv_cache_spec()
+compile_or_warm_up_model()
+check_health()
+shutdown()
+add_lora() / remove_lora()
+apply_model(fn)
+reset_mm_cache()
<<GPU 实现>>
Worker
+model_runner: GPUModelRunner
+init_device() : 初始化CUDA+NCCL
+load_model() : 加载权重
+execute_model() : PP流水线
+sample_tokens() : 转发ModelRunner
<<生命周期代理>>
WorkerWrapperBase
+rpc_rank: int
+global_rank: int
+worker: WorkerBase
+mm_receiver_cache
--拦截方法--
+init_worker(all_kwargs)
+update_environment_variables()
+adjust_rank()
+execute_method()
+getattr() : → self.worker
+execute_model() : 注入MM缓存
+init_device() : 注入Config上下文
+reset_mm_cache() : 清理Receiver缓存
+initialize_from_config() : 按rank选配置
<<Ray Actor 装饰>>
RayWorkerWrapper
+compiled_dag_cuda_device_set
+get_node_ip()
+get_node_and_gpu_ids()
+setup_device_if_necessary()
+execute_model_ray()
<<进程容器>>
WorkerProc
+worker: WorkerWrapperBase
+rpc_broadcast_mq
+worker_response_mq
+async_output_queue
+async_output_copy_thread
+worker_busy_loop()
+handle_output()
+make_worker_process()
+worker_main()
UniProcExecutor
+driver_worker: WorkerWrapperBase
+collective_rpc() : → run_method
MultiprocExecutor
+workers: list<WorkerProcHandle>
+collective_rpc() : → MQ RPC
RayDistributedExecutor
+workers: list<RayWorkerWrapper>
+collective_rpc() : → ray.remote
31.3 三个 Executor 如何使用 WorkerWrapperBase
这是"为什么要加 Wrapper 层"的核心答案:
┌─────────────────────────────────────────────────────────────┐
│ UniProcExecutor │
│ self.driver_worker = WorkerWrapperBase(rpc_rank=0) │
│ collective_rpc → run_method(driver_worker, method) │
│ (单进程, 直接调用) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ MultiprocExecutor │
│ WorkerProc 子进程 { │
│ self.worker = WorkerWrapperBase(rpc_rank=local_rank) │
│ worker_busy_loop: getattr(self.worker, method) │
│ } │
│ collective_rpc → MessageQueue → WorkerProc.worker_busy_loop│
│ (多进程, MQ通信) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ RayDistributedExecutor │
│ Ray Actor = RayWorkerWrapper(rpc_rank=rank) ← 继承自 WWB │
│ collective_rpc → worker.execute_method.remote(method) │
│ (Ray Actor, gRPC通信) │
└─────────────────────────────────────────────────────────────┘
关键洞察: 三种 Executor 使用完全不同的通信机制(直接调用 / MQ / Ray gRPC),但都通过 WorkerWrapperBase 统一了 Worker 的创建和管理逻辑。如果没有这一层,以下逻辑需要在每个 Executor 中重复实现:
| 逻辑 | 复用位置 |
|---|---|
按 worker_cls 字符串动态解析 Worker 类 |
init_worker() L251 |
动态 Mixin worker_extension_cls |
init_worker() L262 |
| 多模态 Receiver Cache 创建 | init_worker() L290 |
execute_model 前注入 MM 缓存 |
execute_model() L360 |
set_current_vllm_config 上下文注入 |
init_device() L322, initialize_from_config() L316 |
按 global_rank 选择配置 |
initialize_from_config() L317 |
__getattr__ 透明代理 |
L347 |
31.4 设计模式分析
- Strategy 策略模式
Executor 选择通信策略
UniProc: 直接调用
Multiproc: MessageQueue
Ray: gRPC Remote - Mixin 动态混入
worker_class.bases +=
(worker_extension_cls,)
4. Template Method 模板方法
init_worker() 固定流程
- 加载插件
- 解析 worker_cls
- 处理 extension_cls
- 创建 MM cache
- 实例化 Worker
- Factory Method 工厂方法
init_worker()
resolve_obj_by_qualname(worker_cls)
GPU Worker
CPU Worker
TPU Worker - Decorator 装饰器模式
WorkerWrapperBase.execute_model()
_apply_mm_cache()
Worker.execute_model() - Proxy 代理模式
getattr(attr)
透明转发
WorkerWrapperBase
Worker (GPU)
模式详解:
1. Proxy(代理模式) --- __getattr__ 让 WorkerWrapperBase 对外表现得像一个 Worker:
python
def __getattr__(self, attr):
return getattr(self.worker, attr) # 调用者不知道中间有代理
2. Decorator(装饰器模式) --- 拦截方法,在原始行为前后注入新逻辑:
python
def execute_model(self, scheduler_output):
self._apply_mm_cache(scheduler_output) # ← 新增行为
return self.worker.execute_model(scheduler_output) # ← 原始行为
3. Factory Method(工厂方法) --- 根据配置字符串创建不同硬件的 Worker:
python
worker_class = resolve_obj_by_qualname(parallel_config.worker_cls)
# "vllm.v1.worker.gpu_worker.Worker" → GPU Worker
# "vllm.worker.cpu_worker.CPUWorker" → CPU Worker
# "vllm.worker.tpu_worker.TPUWorker" → TPU Worker
self.worker = worker_class(**kwargs)
4. Template Method(模板方法) --- init_worker() 定义固定初始化流程:
加载插件 → 解析worker_cls → 处理extension_cls → 创建MM cache → 实例化Worker
5. Mixin(动态混入) --- 运行时扩展 Worker 类:
python
worker_class.__bases__ = worker_class.__bases__ + (worker_extension_cls,)
6. Strategy(策略模式) --- 不同 Executor 用不同通信策略,但统一使用 WorkerWrapperBase:
UniProcExecutor → run_method(self.driver_worker, method)
MultiprocExecutor → rpc_broadcast_mq → getattr(self.worker, method)
RayExecutor → worker.execute_method.remote(method)
31.5 complete RPC 调用链路对比时序图
Worker (GPU) WorkerWrapperBase WorkerProc (仅Multiproc) Executor EngineCore Worker (GPU) WorkerWrapperBase WorkerProc (仅Multiproc) Executor EngineCore UniProcExecutor 路径(单进程直接调用) getattr(self, "execute_model") 命中 WWB 自身定义 MultiprocExecutor 路径(跨进程 MQ) 子进程 worker_busy_loop 命中 WWB.execute_model (拦截) async_output_copy_thread RayDistributedExecutor 路径(Ray gRPC) RayWorkerWrapper.execute_method() getattr(self, "execute_model") 命中 WWB.execute_model (拦截) collective_rpc("execute_model", args) run_method(driver_worker, "execute_model", args) _apply_mm_cache(scheduler_output) self.worker.execute_model(scheduler_output) ModelRunnerOutput return output [output] collective_rpc("execute_model", args) rpc_broadcast_mq.enqueue(method, args) FutureWrapper (non_block) rpc_broadcast_mq.dequeue() getattr(self.worker, "execute_model") _apply_mm_cache() self.worker.execute_model() AsyncModelRunnerOutput return output handle_output → async_output_queue worker_response_mq.enqueue(output) collective_rpc("execute_model", args) worker.execute_method.remote("execute_model", args) run_method(self, "execute_model", args) _apply_mm_cache() self.worker.execute_model() ModelRunnerOutput ray.get() 获取结果 [output]
31.6 方法拦截 vs 透传 详细流程图
Worker (GPU)
WorkerWrapperBase
透传方法 (触发 getattr)
拦截方法 (定义在 WWB 上)
execute_model
init_device
reset_mm_cache
initialize_from_config
load_model
sample_tokens
check_health
get_kv_cache_spec
add_lora
execute_dummy_batch
委托
委托
委托
委托
RPC: method_name + args
getattr(self, method_name)
method_name 在
WWB 中定义?
execute_model()
→ _apply_mm_cache + 委托
init_device()
→ set_current_vllm_config + 委托
reset_mm_cache()
→ 清理receiver + 委托
initialize_from_config()
→ 按rank选配置 + 委托
getattr(method_name)
→ getattr(self.worker, method_name)
load_model()
sample_tokens()
check_health()
get_kv_cache_spec()
add_lora()
execute_dummy_batch()
Worker.execute_model()
Worker.init_device()
Worker.reset_mm_cache()
Worker.initialize_from_config()
31.7 初始化完整时序
Worker (GPU) WorkerWrapperBase WorkerProc (子进程) Executor 主进程 (EngineCore) Worker (GPU) WorkerWrapperBase WorkerProc (子进程) Executor 主进程 (EngineCore) 只记录 rank,self.worker 未赋值 getattr 透传 RayWorkerWrapper 继承 WorkerWrapperBase alt [UniProcExecutor] [MultiprocExecutor] [RayDistributedExecutor] Executor(vllm_config) _init_executor() WorkerWrapperBase(rpc_rank=0) init_worker([kwargs]) resolve_obj_by_qualname("...gpu_worker.Worker") 处理 worker_extension_cls (Mixin) 创建 mm_receiver_cache Worker(**kwargs) self.worker = Worker实例 init_device() with set_current_vllm_config: worker.init_device() load_model() worker.load_model() make_worker_process() spawn worker_main() 入口 WorkerWrapperBase(rpc_rank, global_rank) init_worker([kwargs]) Worker(**kwargs) self.worker = wrapper init_device() worker.init_device() load_model() worker.load_model() READY 信号 (via Pipe) worker_busy_loop() 等待 RPC ray.remote(RayWorkerWrapper).remote(rpc_rank) collective_rpc("init_worker", args=all_kwargs) Worker(**kwargs) collective_rpc("init_device") worker.init_device() collective_rpc("load_model") worker.load_model()
31.8 为什么要加 WorkerWrapperBase ------ 总结
如果没有 WorkerWrapperBase,会怎样?
UniProcExecutor._init_executor():
worker_cls = resolve_obj_by_qualname(config.worker_cls) # 重复!
if config.worker_extension_cls: ... # 重复!
mm_cache = create_mm_cache(...) # 重复!
self.worker = worker_cls(**kwargs) # 重复!
MultiprocExecutor (in WorkerProc):
worker_cls = resolve_obj_by_qualname(config.worker_cls) # 重复!
if config.worker_extension_cls: ... # 重复!
mm_cache = create_mm_cache(...) # 重复!
self.worker = worker_cls(**kwargs) # 重复!
RayDistributedExecutor (in RayWorker):
worker_cls = resolve_obj_by_qualname(config.worker_cls) # 重复!
if config.worker_extension_cls: ... # 重复!
mm_cache = create_mm_cache(...) # 重复!
self.worker = worker_cls(**kwargs) # 重复!
加了 WorkerWrapperBase 后:
WorkerWrapperBase.init_worker(): ← 统一一份代码
worker_cls = resolve_obj_by_qualname(config.worker_cls)
if config.worker_extension_cls: 动态 Mixin
mm_cache = create_mm_cache(...)
self.worker = worker_cls(**kwargs)
UniProcExecutor → WorkerWrapperBase → Worker
MultiprocExecutor → WorkerProc → WorkerWrapperBase → Worker
RayExecutor → RayWorkerWrapper(继承WWB) → Worker
核心价值:
| 原则 | 体现 |
|---|---|
| DRY (Don't Repeat Yourself) | Worker 初始化逻辑只写一次 |
| SRP (单一职责) | WorkerProc=进程管理, WWB=Worker生命周期, Worker=GPU计算 |
| OCP (开闭原则) | 新增硬件→只加 Worker 子类; 新增拦截逻辑→只改 WWB; 新增执行后端→只加 Proc/Executor |
| DIP (依赖倒置) | Executor 依赖 WorkerWrapperBase 接口,不直接依赖 GPU Worker |
| 透明代理 | __getattr__ 让上层完全不感知 Wrapper 的存在 |
31.9 代码文件引用
| 文件 | 行号 | 关键内容 |
|---|---|---|
vllm/v1/worker/worker_base.py |
L34-177 | WorkerBase --- 硬件抽象接口 |
vllm/v1/worker/worker_base.py |
L179-373 | WorkerWrapperBase --- 生命周期代理 |
vllm/v1/worker/gpu_worker.py |
L102 | Worker --- GPU 具体实现 |
vllm/v1/executor/multiproc_executor.py |
L506-925 | WorkerProc --- 多进程容器 |
vllm/v1/executor/uniproc_executor.py |
L25-129 | UniProcExecutor --- 单进程直接使用 WWB |
vllm/v1/executor/ray_executor.py |
L62-511 | RayDistributedExecutor --- Ray 使用 RayWorkerWrapper |
vllm/v1/executor/ray_utils.py |
L41-120 | RayWorkerWrapper --- 继承 WWB 的 Ray 子类 |
vllm/v1/executor/abstract.py |
L36-191 | Executor --- 抽象基类,定义 collective_rpc |
vllm/config/parallel.py |
L215-225 | worker_cls、worker_extension_cls 配置 |
vllm/v1/serial_utils.py |
L435-459 | run_method --- 方法分发工具 |