594 lines
28 KiB
Python
594 lines
28 KiB
Python
from typing import TYPE_CHECKING, List, Optional, Union, cast
|
|
|
|
import numpy as np
|
|
import torch
|
|
|
|
from vllm.config import CUDAGraphMode
|
|
from vllm.distributed.parallel_state import get_pp_group
|
|
from vllm.forward_context import BatchDescriptor, set_forward_context
|
|
from vllm.logger import init_logger
|
|
from vllm.model_executor.models.interfaces_base import VllmModelForPooling
|
|
from vllm.sampling_params import SamplingType
|
|
from vllm.utils import LazyLoader, cdiv
|
|
from vllm.v1.attention.backends.utils import (
|
|
CommonAttentionMetadata,
|
|
split_attn_metadata,
|
|
)
|
|
from vllm.v1.spec_decode.eagle import EagleProposer
|
|
# from vllm.v1.worker.cpu import CachedRequestState
|
|
from vllm.v1.worker.cpu_model_runner import CPUModelRunner
|
|
from vllm.v1.worker.gpu_input_batch import CachedRequestState
|
|
from vllm.v1.worker.gpu_model_runner import PerLayerAttnMetadata
|
|
from vllm.v1.worker.ubatch_splitting import ubatch_split
|
|
|
|
if TYPE_CHECKING:
|
|
from vllm.v1.core.sched.output import SchedulerOutput
|
|
else:
|
|
xgr = LazyLoader("xgr", globals(), "xgrammar")
|
|
xgr_torch_compile = LazyLoader(
|
|
"xgr_torch_compile",
|
|
globals(),
|
|
"xgrammar.kernels.apply_token_bitmask_inplace_torch_compile",
|
|
)
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
class OmniCPUModelRunner(CPUModelRunner):
|
|
def _update_states(self, scheduler_output: "SchedulerOutput") -> None:
|
|
"""Update the cached states and the persistent batch with the scheduler
|
|
output.
|
|
|
|
The updated states are used by the `_prepare_inputs` function to create
|
|
the input CPU tensors for the model.
|
|
|
|
The SamplingMetadata is updated and copied to the CPU if there is a
|
|
new/resumed/paused/finished request in the batch.
|
|
"""
|
|
# Remove finished requests from the cached states.
|
|
for req_id in scheduler_output.finished_req_ids:
|
|
self.requests.pop(req_id, None)
|
|
# Remove the finished requests from the persistent batch.
|
|
# NOTE(woosuk): There could be an edge case where finished_req_ids and
|
|
# scheduled_req_ids overlap. This happens when a request is aborted and
|
|
# then resubmitted with the same ID. In this case, we treat them as two
|
|
# distinct requests - clearing the cached states for the first request
|
|
# and handling the second as a new request.
|
|
for req_id in scheduler_output.finished_req_ids:
|
|
self.input_batch.remove_request(req_id)
|
|
|
|
# Free the cached encoder outputs.
|
|
for mm_hash in scheduler_output.free_encoder_mm_hashes:
|
|
self.encoder_cache.pop(mm_hash, None)
|
|
|
|
# Remove the unscheduled requests from the persistent batch.
|
|
# NOTE(woosuk): The unscheduled requests are either preempted requests
|
|
# or running requests that are not scheduled in this step. We remove
|
|
# them from the persistent batch but keep their cached states since
|
|
# they will be scheduled again sometime in the future.
|
|
scheduled_req_ids = scheduler_output.num_scheduled_tokens.keys()
|
|
cached_req_ids = self.input_batch.req_id_to_index.keys()
|
|
unscheduled_req_ids = cached_req_ids - scheduled_req_ids
|
|
# NOTE(woosuk): The persistent batch optimization assumes that
|
|
# consecutive batches contain mostly the same requests. If batches
|
|
# have low request overlap (e.g., alternating between two distinct
|
|
# sets of requests), this optimization becomes very inefficient.
|
|
for req_id in unscheduled_req_ids:
|
|
self.input_batch.remove_request(req_id)
|
|
|
|
reqs_to_add: list[CachedRequestState] = []
|
|
# Add new requests to the cached states.
|
|
for new_req_data in scheduler_output.scheduled_new_reqs:
|
|
req_id = new_req_data.req_id
|
|
sampling_params = new_req_data.sampling_params
|
|
pooling_params = new_req_data.pooling_params
|
|
|
|
if (
|
|
sampling_params
|
|
and sampling_params.sampling_type == SamplingType.RANDOM_SEED
|
|
):
|
|
generator = torch.Generator(device=self.device)
|
|
generator.manual_seed(sampling_params.seed)
|
|
else:
|
|
generator = None
|
|
|
|
if self.is_pooling_model:
|
|
assert pooling_params is not None
|
|
task = pooling_params.task
|
|
assert task is not None, "You did not set `task` in the API"
|
|
|
|
model = cast(VllmModelForPooling, self.get_model())
|
|
to_update = model.pooler.get_pooling_updates(task)
|
|
to_update.apply(pooling_params)
|
|
|
|
req_state = CachedRequestState(
|
|
req_id=req_id,
|
|
prompt_token_ids=new_req_data.prompt_token_ids,
|
|
prompt_embeds=new_req_data.prompt_embeds,
|
|
mm_features=new_req_data.mm_features,
|
|
sampling_params=sampling_params,
|
|
pooling_params=pooling_params,
|
|
generator=generator,
|
|
block_ids=new_req_data.block_ids,
|
|
num_computed_tokens=new_req_data.num_computed_tokens,
|
|
output_token_ids=[],
|
|
lora_request=new_req_data.lora_request,
|
|
)
|
|
self.requests[req_id] = req_state
|
|
|
|
# If prompt embeddings are provided, decode and attach to inter_data
|
|
try:
|
|
if getattr(new_req_data, "prompt_embeds", None) is not None:
|
|
payload = new_req_data.prompt_embeds
|
|
dtype = getattr(np, payload.dtype)
|
|
arr = np.frombuffer(payload.data, dtype=dtype)
|
|
arr = arr.reshape(payload.shape)
|
|
pe_cpu = torch.from_numpy(arr)
|
|
# Store temporarily on CPU; later moved to device in builder
|
|
setattr(self.requests[req_id], "prompt_embeds_cpu", pe_cpu)
|
|
# Also replace payload with Tensor for user visibility in
|
|
# scheduler_output
|
|
try:
|
|
new_req_data.prompt_embeds = pe_cpu # type: ignore[assignment]
|
|
except Exception:
|
|
pass
|
|
except Exception as e:
|
|
logger.error(f"Error decoding prompt embeds: {e}")
|
|
# Decode additional_information payloads (dictionary)
|
|
try:
|
|
if getattr(new_req_data, "additional_information", None) is not None:
|
|
payload_info = new_req_data.additional_information
|
|
info_dict = {}
|
|
if isinstance(payload_info, dict):
|
|
info_dict = payload_info
|
|
else:
|
|
from vllm_omni.engine import AdditionalInformationPayload
|
|
|
|
if isinstance(payload_info, AdditionalInformationPayload):
|
|
for k, entry in payload_info.entries.items():
|
|
if entry.tensor_data is not None:
|
|
dt = np.dtype(
|
|
getattr(entry, "tensor_dtype", "float32")
|
|
)
|
|
arr = np.frombuffer(entry.tensor_data, dtype=dt)
|
|
arr = arr.reshape(entry.tensor_shape)
|
|
info_dict[k] = torch.from_numpy(arr)
|
|
else:
|
|
info_dict[k] = entry.list_data
|
|
if info_dict:
|
|
setattr(
|
|
self.requests[req_id],
|
|
"additional_information_cpu",
|
|
info_dict,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Error decoding additional information: {e}")
|
|
pass
|
|
|
|
# Only relevant for models using M-RoPE (e.g, Qwen2-VL)
|
|
if self.uses_mrope:
|
|
self._init_mrope_positions(req_state)
|
|
|
|
reqs_to_add.append(self.requests[req_id])
|
|
|
|
# Update the states of the running/resumed requests.
|
|
is_last_rank = get_pp_group().is_last_rank
|
|
req_data = scheduler_output.scheduled_cached_reqs
|
|
for i, req_id in enumerate(req_data.req_ids):
|
|
req_state = self.requests[req_id]
|
|
num_computed_tokens = req_data.num_computed_tokens[i]
|
|
new_block_ids = req_data.new_block_ids[i]
|
|
resumed_from_preemption = req_data.resumed_from_preemption[i]
|
|
|
|
# Update the cached states.
|
|
req_state.num_computed_tokens = num_computed_tokens
|
|
|
|
if not is_last_rank:
|
|
# When using PP, the scheduler sends the sampled tokens back,
|
|
# because there's no direct communication between the first-
|
|
# stage worker and the last-stage worker.
|
|
new_token_ids = req_data.new_token_ids[i]
|
|
# Add the sampled token(s) from the previous step (if any).
|
|
# This doesn't include "unverified" tokens like spec tokens.
|
|
num_new_tokens = (
|
|
num_computed_tokens + len(new_token_ids) - req_state.num_tokens
|
|
)
|
|
if num_new_tokens == 1:
|
|
# Avoid slicing list in most common case.
|
|
req_state.output_token_ids.append(new_token_ids[-1])
|
|
elif num_new_tokens > 0:
|
|
req_state.output_token_ids.extend(new_token_ids[-num_new_tokens:])
|
|
|
|
# Update the block IDs.
|
|
if not resumed_from_preemption:
|
|
if new_block_ids is not None:
|
|
# Append the new blocks to the existing block IDs.
|
|
for block_ids, new_ids in zip(req_state.block_ids, new_block_ids):
|
|
block_ids.extend(new_ids)
|
|
else:
|
|
assert new_block_ids is not None
|
|
# The request is resumed from preemption.
|
|
# Replace the existing block IDs with the new ones.
|
|
req_state.block_ids = new_block_ids
|
|
|
|
req_index = self.input_batch.req_id_to_index.get(req_id)
|
|
if req_index is None:
|
|
# The request is not in the persistent batch.
|
|
# The request was either preempted and resumed later, or was not
|
|
# scheduled in the previous step and needs to be added again.
|
|
reqs_to_add.append(req_state)
|
|
continue
|
|
|
|
# Update the persistent batch.
|
|
self.input_batch.num_computed_tokens_cpu[req_index] = num_computed_tokens
|
|
if new_block_ids is not None:
|
|
self.input_batch.block_table.append_row(new_block_ids, req_index)
|
|
|
|
# For the last rank, we don't need to update the token_ids_cpu
|
|
# because the sampled tokens are already cached.
|
|
if not is_last_rank:
|
|
# Add new_token_ids to token_ids_cpu.
|
|
start_token_index = num_computed_tokens
|
|
end_token_index = num_computed_tokens + len(new_token_ids)
|
|
self.input_batch.token_ids_cpu[
|
|
req_index, start_token_index:end_token_index
|
|
] = new_token_ids
|
|
self.input_batch.num_tokens_no_spec[req_index] = end_token_index
|
|
self.input_batch.num_tokens[req_index] = end_token_index
|
|
|
|
# Add spec_token_ids to token_ids_cpu.
|
|
spec_token_ids = scheduler_output.scheduled_spec_decode_tokens.get(
|
|
req_id, ()
|
|
)
|
|
if spec_token_ids:
|
|
num_spec_tokens = len(spec_token_ids)
|
|
start_index = self.input_batch.num_tokens_no_spec[req_index]
|
|
end_token_index = start_index + num_spec_tokens
|
|
self.input_batch.token_ids_cpu[
|
|
req_index, start_index:end_token_index
|
|
] = spec_token_ids
|
|
# NOTE(woosuk): `num_tokens` here may include spec tokens.
|
|
self.input_batch.num_tokens[req_index] += num_spec_tokens
|
|
|
|
# Add the new or resumed requests to the persistent batch.
|
|
# The smaller empty indices are filled first.
|
|
for request in reqs_to_add:
|
|
self.input_batch.add_request(request)
|
|
|
|
# Condense the batched states if there are gaps left by removed requests
|
|
self.input_batch.condense()
|
|
# Allow attention backend to reorder the batch, potentially
|
|
self._may_reorder_batch(scheduler_output)
|
|
# Refresh batch metadata with any pending updates.
|
|
self.input_batch.refresh_metadata()
|
|
|
|
@torch.inference_mode()
|
|
def extract_multimodal_outputs(
|
|
self, hidden_states: Union[torch.Tensor, List[torch.Tensor]]
|
|
) -> dict:
|
|
if (
|
|
hasattr(self.model, "have_multimodal_outputs")
|
|
and self.model.have_multimodal_outputs
|
|
):
|
|
text_hidden_states = hidden_states.text_hidden_states
|
|
multimodal_outputs = hidden_states.multimodal_outputs
|
|
|
|
elif isinstance(hidden_states, torch.Tensor):
|
|
text_hidden_states = hidden_states
|
|
multimodal_outputs = {}
|
|
elif isinstance(hidden_states, List):
|
|
text_hidden_states = hidden_states[0]
|
|
multimodal_outputs = {}
|
|
else:
|
|
raise ValueError(f"Invalid hidden states type: {type(hidden_states)}")
|
|
return text_hidden_states, multimodal_outputs
|
|
|
|
@torch.inference_mode()
|
|
def _dummy_run(
|
|
self,
|
|
num_tokens: int,
|
|
cudagraph_runtime_mode: Optional[CUDAGraphMode] = None,
|
|
force_attention: bool = False,
|
|
uniform_decode: bool = False,
|
|
allow_microbatching: bool = True,
|
|
skip_eplb: bool = False,
|
|
is_profile: bool = False,
|
|
create_mixed_batch: bool = False,
|
|
remove_lora: bool = True,
|
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
"""
|
|
Run a dummy forward pass to warm up/profile run or capture the
|
|
CUDA graph for the model.
|
|
|
|
Args:
|
|
num_tokens: Number of tokens to run the dummy forward pass.
|
|
cudagraph_runtime_mode: used to control the behavior.
|
|
- if not set will determine the cudagraph mode based on using
|
|
the self.cudagraph_dispatcher.
|
|
- CUDAGraphMode.NONE: No cudagraph, for warm up and profile run
|
|
- CUDAGraphMode.PIECEWISE: Piecewise cudagraph.
|
|
- CUDAGraphMode.FULL: Full cudagraph, attention metadata is
|
|
needed.
|
|
force_attention: If True, always create attention metadata. Used to
|
|
warm up attention backend when mode is NONE.
|
|
uniform_decode: If True, the batch is a uniform decode batch.
|
|
skip_eplb: If True, skip EPLB state update.
|
|
is_profile: If True, this is a profile run.
|
|
create_mixed_batch: If True, create a mixed batch with both decode
|
|
(1 token) and prefill (multiple tokens) requests.
|
|
remove_lora: If False, dummy LoRAs are not destroyed after the run
|
|
"""
|
|
assert cudagraph_runtime_mode is None or cudagraph_runtime_mode in {
|
|
CUDAGraphMode.NONE,
|
|
CUDAGraphMode.PIECEWISE,
|
|
CUDAGraphMode.FULL,
|
|
}
|
|
|
|
# If cudagraph_mode.decode_mode() == FULL and
|
|
# cudagraph_mode.separate_routine(). This means that we are using
|
|
# different graphs and/or modes for mixed prefill-decode batches vs.
|
|
# uniform decode batches. A uniform decode batch means that all
|
|
# requests have identical query length, except a potential virtual
|
|
# request (shorter) in the batch account for padding.
|
|
# Uniform decode batch could either be common pure decode, where
|
|
# max_query_len == 1, or speculative decode, where
|
|
# max_query_len == 1 + num_spec_decode_tokens.
|
|
|
|
# When setting max_query_len = 1, we switch to and capture the optimized
|
|
# routine of FA2 for pure decode, i.e., Flashdecode + an optimization
|
|
# for GQA/MQA.
|
|
max_query_len = self.uniform_decode_query_len if uniform_decode else num_tokens
|
|
|
|
# Set num_scheduled_tokens based on num_tokens and max_num_seqs
|
|
# for dummy run with LoRA so that the num_reqs collectively
|
|
# has num_tokens in total.
|
|
assert num_tokens <= self.scheduler_config.max_num_batched_tokens
|
|
max_num_reqs = self.scheduler_config.max_num_seqs
|
|
if create_mixed_batch:
|
|
assert not uniform_decode
|
|
# Create mixed batch:
|
|
# first half decode tokens, second half one prefill
|
|
num_decode_tokens = num_tokens // 2
|
|
num_prefill_tokens = num_tokens - num_decode_tokens
|
|
num_reqs = num_decode_tokens + 1
|
|
|
|
# Create decode requests (1 token each) followed by prefill request
|
|
num_scheduled_tokens_list = [1] * num_decode_tokens + [num_prefill_tokens]
|
|
# Note: Overriding max_query_len to be the prefill tokens
|
|
max_query_len = num_prefill_tokens
|
|
elif uniform_decode:
|
|
assert not create_mixed_batch
|
|
num_reqs = cdiv(num_tokens, max_query_len)
|
|
num_scheduled_tokens_list = [max_query_len] * num_reqs
|
|
if num_tokens % max_query_len != 0:
|
|
num_scheduled_tokens_list[-1] = num_tokens % max_query_len
|
|
else:
|
|
num_reqs = min(num_tokens, max_num_reqs)
|
|
min_tokens_per_req = num_tokens // num_reqs
|
|
num_scheduled_tokens_list = [min_tokens_per_req] * num_reqs
|
|
num_scheduled_tokens_list[-1] += num_tokens % num_reqs
|
|
|
|
assert sum(num_scheduled_tokens_list) == num_tokens
|
|
assert len(num_scheduled_tokens_list) == num_reqs
|
|
num_scheduled_tokens = np.array(num_scheduled_tokens_list, dtype=np.int32)
|
|
|
|
total_num_scheduled_tokens = int(num_scheduled_tokens.sum())
|
|
|
|
ubatch_slices = None
|
|
num_tokens_after_padding = None
|
|
|
|
# We currently only microbatch if the number of tokens is
|
|
# over a certain threshold.
|
|
if self.parallel_config.enable_dbo and allow_microbatching:
|
|
ubatch_slices, ubatch_num_tokens_after_padding = ubatch_split(
|
|
num_scheduled_tokens,
|
|
total_num_scheduled_tokens,
|
|
total_num_scheduled_tokens,
|
|
uniform_decode=uniform_decode,
|
|
vllm_config=self.vllm_config,
|
|
)
|
|
# Currently when DBO is enabled `ubatch_split` returns
|
|
# the num_tokens_after_padding for a single ubatch, but we have 2
|
|
# TODO(sage,lucas): this is cruft that should be addressed in the
|
|
# padding refactor.
|
|
if ubatch_num_tokens_after_padding is not None:
|
|
num_tokens_after_padding = ubatch_num_tokens_after_padding * 2
|
|
|
|
# If we failed to microbatch, currently need to resynchronize
|
|
# TODO(lucas,sage): we should be able to avoid this second sync by
|
|
# refactoring `get_dp_padding_ubatch` and `get_dp_padding` into
|
|
# a single `coordinate_batch_across_dp` function.
|
|
if num_tokens_after_padding is None:
|
|
num_pad, num_tokens_across_dp = self.get_dp_padding(num_tokens)
|
|
num_tokens_after_padding = num_tokens + num_pad
|
|
else:
|
|
num_tokens_across_dp = num_tokens_after_padding
|
|
num_tokens_after_padding = int(num_tokens_after_padding[0].item())
|
|
|
|
attn_metadata: Optional[PerLayerAttnMetadata] = None
|
|
|
|
# If force_attention is True, we always capture attention. Otherwise,
|
|
# it only happens for cudagraph_runtime_mode=FULL.
|
|
if force_attention or cudagraph_runtime_mode == CUDAGraphMode.FULL:
|
|
attn_metadata = {}
|
|
if ubatch_slices is not None:
|
|
attn_metadata = [dict() for _ in range(len(ubatch_slices))]
|
|
|
|
if create_mixed_batch:
|
|
# In the mixed batch mode (used for FI warmup), we use
|
|
# shorter sequence lengths to run faster.
|
|
# TODO(luka) better system for describing dummy batches
|
|
seq_lens = [1] * num_decode_tokens + [num_prefill_tokens + 1]
|
|
else:
|
|
seq_lens = max_query_len
|
|
self.seq_lens.np[:num_reqs] = seq_lens
|
|
self.seq_lens.np[num_reqs:] = 0
|
|
self.seq_lens.copy_to_gpu()
|
|
|
|
cum_num_tokens, _ = self._get_cumsum_and_arange(num_scheduled_tokens)
|
|
self.query_start_loc.np[1: num_reqs + 1] = cum_num_tokens
|
|
self.query_start_loc.copy_to_gpu()
|
|
|
|
for kv_cache_group_id, kv_cache_group_spec in enumerate(
|
|
self.kv_cache_config.kv_cache_groups
|
|
):
|
|
common_attn_metadata = CommonAttentionMetadata(
|
|
query_start_loc=self.query_start_loc.gpu[: num_reqs + 1],
|
|
query_start_loc_cpu=self.query_start_loc.cpu[: num_reqs + 1],
|
|
seq_lens=self.seq_lens.gpu[:num_reqs],
|
|
seq_lens_cpu=self.seq_lens.cpu[:num_reqs],
|
|
num_computed_tokens_cpu=self.input_batch.num_computed_tokens_cpu_tensor[ # noqa: E501
|
|
:num_reqs
|
|
],
|
|
num_reqs=num_reqs,
|
|
num_actual_tokens=num_tokens,
|
|
max_query_len=max_query_len,
|
|
max_seq_len=self.max_model_len,
|
|
block_table_tensor=self.input_batch.block_table[
|
|
kv_cache_group_id
|
|
].get_device_tensor(num_reqs),
|
|
slot_mapping=self.input_batch.block_table[
|
|
kv_cache_group_id
|
|
].slot_mapping.gpu[:num_tokens],
|
|
causal=True,
|
|
)
|
|
for attn_group in self.attn_groups[kv_cache_group_id]:
|
|
if ubatch_slices is not None:
|
|
common_attn_metadata_list = split_attn_metadata(
|
|
ubatch_slices, common_attn_metadata
|
|
)
|
|
for ubid, common_attn_metadata in enumerate(
|
|
common_attn_metadata_list
|
|
):
|
|
assert common_attn_metadata.max_query_len == 1
|
|
attn_metadata_i = attn_group.get_metadata_builder(
|
|
ubatch_id=ubid
|
|
).build_for_cudagraph_capture(common_attn_metadata)
|
|
for layer_name in attn_group.layer_names:
|
|
assert type(attn_metadata) is list
|
|
attn_metadata[ubid][layer_name] = attn_metadata_i
|
|
else:
|
|
assert type(attn_metadata) is dict
|
|
attn_metadata_i = attn_group.get_metadata_builder().build_for_cudagraph_capture( # noqa: E501
|
|
common_attn_metadata
|
|
)
|
|
for layer_name in attn_group.layer_names:
|
|
attn_metadata[layer_name] = attn_metadata_i
|
|
|
|
with self.maybe_dummy_run_with_lora(
|
|
self.lora_config, num_scheduled_tokens, remove_lora
|
|
):
|
|
model_kwargs = self._init_model_kwargs(num_tokens)
|
|
if self.supports_mm_inputs and not self.model_config.is_encoder_decoder:
|
|
input_ids = None
|
|
inputs_embeds = self.inputs_embeds.gpu[:num_tokens]
|
|
model_kwargs = {
|
|
**model_kwargs,
|
|
**self._dummy_mm_kwargs(num_reqs),
|
|
}
|
|
elif self.enable_prompt_embeds:
|
|
input_ids = None
|
|
inputs_embeds = self.inputs_embeds.gpu[:num_tokens]
|
|
model_kwargs = self._init_model_kwargs(num_tokens)
|
|
else:
|
|
input_ids = self.input_ids.gpu[:num_tokens]
|
|
inputs_embeds = None
|
|
|
|
if self.uses_mrope:
|
|
positions = self.mrope_positions.gpu[:, :num_tokens]
|
|
else:
|
|
positions = self.positions.gpu[:num_tokens]
|
|
|
|
if get_pp_group().is_first_rank:
|
|
intermediate_tensors = None
|
|
else:
|
|
if self.intermediate_tensors is None:
|
|
self.intermediate_tensors = (
|
|
self.model.make_empty_intermediate_tensors(
|
|
batch_size=self.max_num_tokens,
|
|
dtype=self.model_config.dtype,
|
|
device=self.device,
|
|
)
|
|
)
|
|
|
|
intermediate_tensors = self.sync_and_slice_intermediate_tensors(
|
|
num_tokens, None, False
|
|
)
|
|
|
|
# filter out the valid batch descriptor
|
|
_cg_mode, batch_descriptor = (
|
|
self.cudagraph_dispatcher.dispatch(
|
|
BatchDescriptor(
|
|
num_tokens=num_tokens_after_padding,
|
|
uniform_decode=uniform_decode,
|
|
)
|
|
)
|
|
if not is_profile
|
|
else (CUDAGraphMode.NONE, None)
|
|
)
|
|
if cudagraph_runtime_mode is not None:
|
|
# we allow forcing NONE when the dispatcher disagrees to support
|
|
# warm ups for cudagraph capture
|
|
assert (
|
|
cudagraph_runtime_mode == CUDAGraphMode.NONE
|
|
or cudagraph_runtime_mode == _cg_mode
|
|
), (
|
|
f"Cudagraph runtime mode mismatch at dummy_run. "
|
|
f"Expected {_cg_mode}, but got {cudagraph_runtime_mode}."
|
|
)
|
|
else:
|
|
cudagraph_runtime_mode = _cg_mode
|
|
|
|
if ubatch_slices is not None:
|
|
# Adjust values to reflect a single ubatch.
|
|
# TODO(sage,lucas): this is cruft that should be addressed in
|
|
# the padding refactor.
|
|
num_tokens_after_padding = ubatch_slices[0].num_tokens
|
|
if num_tokens_across_dp is not None:
|
|
num_tokens_across_dp[:] = num_tokens_after_padding
|
|
|
|
with self.maybe_randomize_inputs(input_ids), set_forward_context(
|
|
attn_metadata,
|
|
self.vllm_config,
|
|
num_tokens=num_tokens_after_padding,
|
|
num_tokens_across_dp=num_tokens_across_dp,
|
|
cudagraph_runtime_mode=cudagraph_runtime_mode,
|
|
batch_descriptor=batch_descriptor,
|
|
ubatch_slices=ubatch_slices,
|
|
):
|
|
outputs = self.model(
|
|
input_ids=input_ids,
|
|
positions=positions,
|
|
intermediate_tensors=intermediate_tensors,
|
|
inputs_embeds=inputs_embeds,
|
|
**model_kwargs,
|
|
)
|
|
|
|
if self.use_aux_hidden_state_outputs:
|
|
hidden_states, _ = outputs
|
|
else:
|
|
hidden_states = outputs
|
|
|
|
if self.speculative_config and self.speculative_config.use_eagle():
|
|
assert isinstance(self.drafter, EagleProposer)
|
|
self.drafter.dummy_run(num_tokens)
|
|
|
|
# This is necessary to avoid blocking DP.
|
|
# For dummy runs, we typically skip EPLB since we don't have any real
|
|
# requests to process.
|
|
# However, in DP settings, there may be cases when some DP ranks do
|
|
# not have any requests to process, so they're executing dummy batches.
|
|
# In such cases, we still have to trigger EPLB to make sure
|
|
# ranks execute the rearrangement in synchronization.
|
|
if not skip_eplb:
|
|
self.eplb_step(is_dummy=True, is_profile=is_profile)
|
|
|
|
logit_indices = np.cumsum(num_scheduled_tokens) - 1
|
|
hidden_states, multimodal_outputs = self.extract_multimodal_outputs(
|
|
hidden_states
|
|
)
|
|
if len(hidden_states.shape) == 3:
|
|
return hidden_states[0], hidden_states[0][logit_indices]
|
|
else:
|
|
return hidden_states, hidden_states[logit_indices]
|