Signed-off-by: waizuichougou <2082431897@qq.com> Signed-off-by: waizuichougou <105572299+waizuichougou@users.noreply.github.com>
176 lines
6.6 KiB
Python
176 lines
6.6 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""Model runner for instances that only run the multi-modal encoder.
|
|
|
|
An encoder-only instance -- `--mm-encoder-only`, or the producer side of
|
|
encoder-cache disaggregation -- encodes the multi-modal items and publishes the
|
|
embeddings. It runs no language model: no KV cache, sampler, or decoder graphs.
|
|
"""
|
|
|
|
import time
|
|
from collections.abc import Iterator
|
|
from contextlib import contextmanager
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
import torch
|
|
|
|
from vllm.config import VllmConfig
|
|
from vllm.config.compilation import CUDAGraphMode
|
|
from vllm.logger import init_logger
|
|
from vllm.sequence import IntermediateTensors
|
|
from vllm.utils.gc_utils import freeze_gc_for_cudagraph_capture
|
|
from vllm.v1.kv_cache_interface import KVCacheSpec
|
|
from vllm.v1.outputs import (
|
|
ModelRunnerOutput,
|
|
make_empty_encoder_model_runner_output,
|
|
)
|
|
from vllm.v1.worker.gpu.cudagraph_utils import BatchExecutionDescriptor
|
|
from vllm.v1.worker.gpu.mm.lora import set_active_mm_loras
|
|
from vllm.v1.worker.gpu.model_runner import GPUModelRunner
|
|
from vllm.v1.worker.workspace import lock_workspace
|
|
|
|
if TYPE_CHECKING:
|
|
from vllm.v1.core.sched.output import SchedulerOutput
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
class MMEncoderModelRunner(GPUModelRunner):
|
|
"""Encoder-only variant of the V2 GPU model runner."""
|
|
|
|
def __init__(self, vllm_config: VllmConfig, device: torch.device):
|
|
super().__init__(vllm_config, device)
|
|
assert self.supports_mm_inputs, (
|
|
"An encoder-only instance must serve a multi-modal model."
|
|
)
|
|
assert self.dp_size == 1, "An encoder-only instance does not support DP."
|
|
|
|
depth = vllm_config.max_concurrent_batches
|
|
# Blocking (sleep) events: busy-polling the driver lock can make this
|
|
# rank a straggler under contention.
|
|
self._input_events = [torch.Event(blocking=True) for _ in range(depth)]
|
|
self._input_event_idx = 0
|
|
|
|
@contextmanager
|
|
def input_tensor_semaphore(self) -> Iterator[None]:
|
|
"""Guard the host writes into the pooled input buffers.
|
|
|
|
`UvaBufferPool` recycles a slot every `max_concurrent_batches` steps and
|
|
the device reads the pooled host buffers in place. A sampling step is
|
|
ordered by the device wait in `AsyncOutput.get_output()`; this one never
|
|
waits, so entering blocks until the step that last wrote these slots is
|
|
done with them and leaving records this step's last write.
|
|
"""
|
|
idx = self._input_event_idx
|
|
input_event = self._input_events[idx]
|
|
input_event.synchronize()
|
|
try:
|
|
yield
|
|
finally:
|
|
input_event.record()
|
|
self._input_event_idx = (idx + 1) % len(self._input_events)
|
|
|
|
def get_kv_cache_spec(self) -> dict[str, KVCacheSpec]:
|
|
return {}
|
|
|
|
@torch.inference_mode()
|
|
def capture_model(self, *, profile_only: bool = False) -> int:
|
|
encoder_runner = self.model_state.encoder_runner
|
|
if not encoder_runner.has_cudagraph():
|
|
return 0
|
|
|
|
start_time = time.perf_counter()
|
|
with freeze_gc_for_cudagraph_capture():
|
|
torch.accelerator.empty_cache()
|
|
start_free_memory = torch.accelerator.get_memory_info()[0]
|
|
encoder_runner.capture()
|
|
graph_memory = start_free_memory - torch.accelerator.get_memory_info()[0]
|
|
|
|
if not profile_only:
|
|
lock_workspace()
|
|
|
|
manager = encoder_runner.cudagraph_manager
|
|
assert manager is not None
|
|
logger.info(
|
|
"Encoder graph capturing finished in %.0f secs, took %.2f GiB (%d graphs)",
|
|
time.perf_counter() - start_time,
|
|
graph_memory / (1 << 30),
|
|
manager.get_num_graphs_to_capture(),
|
|
)
|
|
return graph_memory
|
|
|
|
def needs_cudagraph_capture(self) -> bool:
|
|
return False
|
|
|
|
def _dummy_run(
|
|
self, *args: Any, **kwargs: Any
|
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
empty = torch.empty(0, device=self.device)
|
|
return empty, empty
|
|
|
|
def _dummy_sampler_run(self, hidden_states: torch.Tensor) -> None:
|
|
return
|
|
|
|
def _dummy_pooler_run(self, hidden_states: torch.Tensor) -> None:
|
|
return
|
|
|
|
def _no_forward(self, scheduler_output: "SchedulerOutput") -> ModelRunnerOutput:
|
|
return self._merge_ec_connector_no_forward(
|
|
scheduler_output, self.kv_connector.no_forward(scheduler_output)
|
|
)
|
|
|
|
@torch.inference_mode()
|
|
def execute_model(
|
|
self,
|
|
scheduler_output: "SchedulerOutput",
|
|
intermediate_tensors: IntermediateTensors | None = None,
|
|
dummy_run: bool = False,
|
|
skip_attn_for_dummy_run: bool = False,
|
|
is_profile: bool = False,
|
|
context_len: int = 0,
|
|
) -> ModelRunnerOutput:
|
|
assert not dummy_run, "An encoder-only instance runs no dummy batch."
|
|
|
|
with self.input_tensor_semaphore():
|
|
self.update_pp_decode_requests()
|
|
self.finish_requests(scheduler_output)
|
|
self.free_states(scheduler_output)
|
|
self.add_requests(scheduler_output)
|
|
self.update_requests(scheduler_output)
|
|
self.block_tables.apply_staged_writes()
|
|
if scheduler_output.total_num_scheduled_tokens == 0:
|
|
return self._no_forward(scheduler_output)
|
|
|
|
batch_req_state, _ = self.gather_batch_req_state(scheduler_output, False)
|
|
assert batch_req_state is not None
|
|
# No decoder graph or DP padding; encoder graphs run independently.
|
|
batch_desc = BatchExecutionDescriptor(
|
|
cg_mode=CUDAGraphMode.NONE,
|
|
num_tokens=batch_req_state.num_tokens,
|
|
num_reqs=None,
|
|
)
|
|
self.prepare_inputs(
|
|
scheduler_output, batch_req_state, batch_desc, num_active_loras=0
|
|
)
|
|
|
|
scheduled_encoder_inputs = scheduler_output.scheduled_encoder_inputs
|
|
if self.lora_config is not None:
|
|
set_active_mm_loras(
|
|
model=self.model,
|
|
lora_manager=self.lora_manager,
|
|
encoder_cache=self.encoder_cache,
|
|
req_id_to_index=self.req_states.req_id_to_index,
|
|
lora_state=self.lora_state,
|
|
scheduled_encoder_inputs=scheduled_encoder_inputs,
|
|
)
|
|
|
|
with self.ec_connector.maybe_get_output(
|
|
scheduler_output
|
|
) as ec_connector_output:
|
|
self.model_state.execute_mm_encoder(scheduled_encoder_inputs)
|
|
|
|
return ModelRunnerOutput.with_ec_conn_output(
|
|
make_empty_encoder_model_runner_output(scheduler_output),
|
|
ec_connector_output,
|
|
)
|