Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
754e0442ae | ||
|
|
c188ff8872 | ||
|
|
8bac9a12ac | ||
|
|
ac149f6ea9 | ||
|
|
8ee1d73d02 | ||
|
|
a4e8327797 | ||
|
|
30dd7aad34 | ||
|
|
14d8c3d95c | ||
|
|
c1a41bde05 | ||
|
|
17e2119d2c | ||
|
|
c10cbbd2ea | ||
|
|
7b9fa7708a | ||
|
|
a42dba230d | ||
|
|
dfe92f7d86 | ||
|
|
86b8669494 | ||
|
|
b5136c2e0d | ||
|
|
13dfb56a68 | ||
|
|
6c6ff9f27c | ||
|
|
b8b82347d3 | ||
|
|
62b0e81795 | ||
|
|
8f7b102108 | ||
|
|
5c038ae553 | ||
|
|
8b30818f2e |
No files matched your search
@@ -8,7 +8,7 @@ stage_args:
|
|||||||
engine_args:
|
engine_args:
|
||||||
model_stage: thinker
|
model_stage: thinker
|
||||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||||
worker_cls: vllm_omni.worker.gpu_ar_worker.GPUARWorker
|
worker_cls: vllm_omni.worker.gpu.gpu_ar_worker.GPUARWorker
|
||||||
scheduler_cls: vllm_omni.core.sched.scheduler.OmniScheduler
|
scheduler_cls: vllm_omni.core.sched.scheduler.OmniScheduler
|
||||||
gpu_memory_utilization: 0.8
|
gpu_memory_utilization: 0.8
|
||||||
enforce_eager: true # need to discuss
|
enforce_eager: true # need to discuss
|
||||||
@@ -25,7 +25,7 @@ stage_args:
|
|||||||
engine_args:
|
engine_args:
|
||||||
model_stage: talker
|
model_stage: talker
|
||||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||||
worker_cls: vllm_omni.worker.gpu_ar_worker.GPUARWorker
|
worker_cls: vllm_omni.worker.gpu.gpu_ar_worker.GPUARWorker
|
||||||
scheduler_cls: vllm_omni.core.sched.scheduler.OmniScheduler
|
scheduler_cls: vllm_omni.core.sched.scheduler.OmniScheduler
|
||||||
gpu_memory_utilization: 0.8
|
gpu_memory_utilization: 0.8
|
||||||
enforce_eager: true
|
enforce_eager: true
|
||||||
@@ -42,7 +42,7 @@ stage_args:
|
|||||||
engine_args:
|
engine_args:
|
||||||
model_stage: code2wav
|
model_stage: code2wav
|
||||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||||
worker_cls: vllm_omni.worker.gpu_diffusion_worker.GPUDiffusionWorker
|
worker_cls: vllm_omni.worker.gpu.gpu_diffusion_worker.GPUDiffusionWorker
|
||||||
scheduler_cls: vllm_omni.core.sched.diffusion_scheduler.DiffusionScheduler
|
scheduler_cls: vllm_omni.core.sched.diffusion_scheduler.DiffusionScheduler
|
||||||
gpu_memory_utilization: 0.15
|
gpu_memory_utilization: 0.15
|
||||||
enforce_eager: true
|
enforce_eager: true
|
||||||
|
|||||||
Whitespace-only changes.
@@ -0,0 +1,649 @@
|
|||||||
|
"""AR CPU Model Runner for vLLM-omni.
|
||||||
|
|
||||||
|
Exposes per-request hidden representations via ModelRunnerOutput.pooler_output
|
||||||
|
and also outputs sampled tokens.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from vllm.forward_context import BatchDescriptor
|
||||||
|
from vllm.logger import init_logger
|
||||||
|
from vllm.v1.core.sched.output import SchedulerOutput
|
||||||
|
from vllm.v1.outputs import AsyncModelRunnerOutput
|
||||||
|
from vllm.v1.structured_output.utils import apply_grammar_bitmask
|
||||||
|
from vllm.v1.utils import record_function_or_nullcontext
|
||||||
|
from vllm.v1.worker.gpu_model_runner import (
|
||||||
|
EMPTY_MODEL_RUNNER_OUTPUT,
|
||||||
|
AsyncGPUModelRunnerOutput,
|
||||||
|
IntermediateTensors,
|
||||||
|
get_pp_group,
|
||||||
|
get_tp_group,
|
||||||
|
has_kv_transfer_group,
|
||||||
|
set_forward_context,
|
||||||
|
)
|
||||||
|
from vllm.v1.worker.ubatch_utils import UBatchSlices
|
||||||
|
from vllm.v1.worker.utils import is_residual_scattered_for_sp
|
||||||
|
from vllm_omni.engine import AdditionalInformationPayload, PromptEmbedsPayload
|
||||||
|
from vllm_omni.outputs import OmniModelRunnerOutput
|
||||||
|
from vllm_omni.worker.cpu.cpu_model_runner import OmniCPUModelRunner
|
||||||
|
|
||||||
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CPUARModelRunner(OmniCPUModelRunner):
|
||||||
|
"""Autoregressive GPU model runner that returns hidden states per request.
|
||||||
|
|
||||||
|
This runner follows the same preparation and forward path as GPUModelRunner
|
||||||
|
(inputs assembly, multi-modal handling, TP/PP/DP integration, CUDA graphs),
|
||||||
|
and additionally performs lightweight sampling so that sampled tokens are
|
||||||
|
available in outputs. Hidden representations are taken at the same indices
|
||||||
|
that GPUModelRunner would use for sampling/logits (i.e. `logits_indices`).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.input_ids = self._make_buffer(self.max_num_tokens, dtype=torch.int32)
|
||||||
|
|
||||||
|
def _preprocess(
|
||||||
|
self,
|
||||||
|
scheduler_output: "SchedulerOutput",
|
||||||
|
num_scheduled_tokens_np: np.ndarray,
|
||||||
|
intermediate_tensors: Optional[IntermediateTensors] = None,
|
||||||
|
ubatch_slices: Optional[UBatchSlices] = None,
|
||||||
|
num_tokens_after_padding: Optional[torch.Tensor] = None,
|
||||||
|
) -> tuple[
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
Optional[torch.Tensor],
|
||||||
|
Optional[torch.Tensor],
|
||||||
|
Optional[torch.Tensor],
|
||||||
|
torch.Tensor,
|
||||||
|
Optional[IntermediateTensors],
|
||||||
|
dict[str, Any],
|
||||||
|
Optional[dict[str, dict]],
|
||||||
|
]:
|
||||||
|
|
||||||
|
num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
|
||||||
|
if ubatch_slices:
|
||||||
|
assert num_tokens_after_padding is not None
|
||||||
|
num_input_tokens = int(num_tokens_after_padding[0].item() * 2)
|
||||||
|
self.pad_out_ubatch_slice(ubatch_slices, num_input_tokens)
|
||||||
|
elif ubatch_slices is None:
|
||||||
|
num_input_tokens = self._get_num_input_tokens(num_scheduled_tokens)
|
||||||
|
num_pad, num_tokens_after_padding = self.get_dp_padding(num_input_tokens)
|
||||||
|
num_input_tokens += num_pad
|
||||||
|
|
||||||
|
# _prepare_inputs may reorder the batch, so we must gather multi
|
||||||
|
# modal outputs after that to ensure the correct order
|
||||||
|
per_req_additional_information: Optional[dict[str, dict]] = None
|
||||||
|
if (
|
||||||
|
self.supports_mm_inputs
|
||||||
|
and get_pp_group().is_first_rank
|
||||||
|
and not self.model_config.is_encoder_decoder
|
||||||
|
):
|
||||||
|
# Run the multimodal encoder if any.
|
||||||
|
self._execute_mm_encoder(scheduler_output)
|
||||||
|
mm_embeds = self._gather_mm_embeddings(scheduler_output)
|
||||||
|
|
||||||
|
# NOTE(woosuk): To unify token ids and soft tokens (vision
|
||||||
|
# embeddings), we always use embeddings (rather than token ids)
|
||||||
|
# as input to the multimodal model, even when the input is text.
|
||||||
|
inputs_embeds_scheduled = self.model.get_input_embeddings(
|
||||||
|
input_ids=self.input_ids.gpu[:num_scheduled_tokens],
|
||||||
|
multimodal_embeddings=mm_embeds or None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO(woosuk): Avoid the copy. Optimize.
|
||||||
|
self.inputs_embeds.gpu[:num_scheduled_tokens].copy_(inputs_embeds_scheduled)
|
||||||
|
|
||||||
|
# Reset per-step additional information collector (deprecated concat path)
|
||||||
|
if hasattr(self, "_forward_additional_information"):
|
||||||
|
self._forward_additional_information = None
|
||||||
|
# New: per-request additional information for this step
|
||||||
|
per_req_additional_information = {}
|
||||||
|
|
||||||
|
# Overlay custom prompt_embeds per request for the prompt portion;
|
||||||
|
# collect additional_information (tensor/list) for prefill portion only
|
||||||
|
for req_index, req_id in enumerate(self.input_batch.req_ids):
|
||||||
|
req_state = self.requests[req_id]
|
||||||
|
pe_cpu = getattr(req_state, "prompt_embeds_cpu", None)
|
||||||
|
addi_cpu = getattr(req_state, "additional_information_cpu", None)
|
||||||
|
num_computed_tokens = int(
|
||||||
|
self.input_batch.num_computed_tokens_cpu[req_index]
|
||||||
|
)
|
||||||
|
prompt_len = len(req_state.prompt_token_ids)
|
||||||
|
prompt_remaining = max(0, prompt_len - num_computed_tokens)
|
||||||
|
sched_tokens = int(num_scheduled_tokens_np[req_index])
|
||||||
|
overlay_len = min(sched_tokens, prompt_remaining)
|
||||||
|
if overlay_len <= 0:
|
||||||
|
continue
|
||||||
|
if pe_cpu is not None:
|
||||||
|
src = pe_cpu[
|
||||||
|
num_computed_tokens : num_computed_tokens + overlay_len
|
||||||
|
].to(dtype=self.dtype, device=self.device, non_blocking=True)
|
||||||
|
start_offset = int(self.query_start_loc.cpu[req_index])
|
||||||
|
self.inputs_embeds[start_offset : start_offset + overlay_len].copy_(
|
||||||
|
src
|
||||||
|
)
|
||||||
|
# Build per-request additional information (no cross-request concat)
|
||||||
|
if addi_cpu is not None and isinstance(addi_cpu, dict):
|
||||||
|
req_info: dict[str, object] = {}
|
||||||
|
for k, v in addi_cpu.items():
|
||||||
|
if isinstance(v, torch.Tensor):
|
||||||
|
# For prefill tokens, pass only the scheduled slice; for decode or no scheduled tokens, pass whole tensor
|
||||||
|
if overlay_len > 0:
|
||||||
|
try:
|
||||||
|
seg = v[
|
||||||
|
num_computed_tokens : num_computed_tokens + overlay_len
|
||||||
|
].detach().to("cpu").contiguous()
|
||||||
|
except Exception:
|
||||||
|
seg = v.detach().to("cpu").contiguous()
|
||||||
|
req_info[k] = seg
|
||||||
|
else:
|
||||||
|
req_info[k] = v.detach().to("cpu").contiguous()
|
||||||
|
elif isinstance(v, list):
|
||||||
|
req_info[k] = v
|
||||||
|
else:
|
||||||
|
req_info[k] = v
|
||||||
|
per_req_additional_information[req_id] = req_info
|
||||||
|
|
||||||
|
input_ids = self.input_ids.gpu[:num_input_tokens]
|
||||||
|
inputs_embeds = self.inputs_embeds.gpu[:num_input_tokens]
|
||||||
|
model_kwargs = {
|
||||||
|
**self._init_model_kwargs(num_scheduled_tokens),
|
||||||
|
**self._extract_mm_kwargs(scheduler_output),
|
||||||
|
}
|
||||||
|
elif self.enable_prompt_embeds and get_pp_group().is_first_rank:
|
||||||
|
# Get the input embeddings for the tokens that are not input embeds,
|
||||||
|
# then put them into the appropriate positions.
|
||||||
|
# TODO(qthequartermasterman): Since even when prompt embeds are
|
||||||
|
# enabled, (a) not all requests will use prompt embeds, and (b)
|
||||||
|
# after the initial prompt is processed, the rest of the generated
|
||||||
|
# tokens will be token ids, it is not desirable to have the
|
||||||
|
# embedding layer outside of the CUDA graph all the time. The v0
|
||||||
|
# engine avoids this by "double compiling" the CUDA graph, once
|
||||||
|
# with input_ids and again with inputs_embeds, for all num_tokens.
|
||||||
|
# If a batch only has token ids, then including the embedding layer
|
||||||
|
# in the CUDA graph will be more performant (like in the else case
|
||||||
|
# below).
|
||||||
|
token_ids_idx = (
|
||||||
|
self.is_token_ids.gpu[:num_scheduled_tokens]
|
||||||
|
.nonzero(as_tuple=False)
|
||||||
|
.squeeze(1)
|
||||||
|
)
|
||||||
|
# Some tokens ids may need to become embeds
|
||||||
|
if token_ids_idx.numel() > 0:
|
||||||
|
token_ids = self.input_ids.gpu[token_ids_idx]
|
||||||
|
tokens_to_embeds = self.model.get_input_embeddings(input_ids=token_ids)
|
||||||
|
self.inputs_embeds.gpu[token_ids_idx] = tokens_to_embeds
|
||||||
|
|
||||||
|
inputs_embeds = self.inputs_embeds.gpu[:num_input_tokens]
|
||||||
|
model_kwargs = self._init_model_kwargs(num_input_tokens)
|
||||||
|
input_ids = None
|
||||||
|
else:
|
||||||
|
# For text-only models, we use token ids as input.
|
||||||
|
# While it is possible to use embeddings as input just like the
|
||||||
|
# multimodal models, it is not desirable for performance since
|
||||||
|
# then the embedding layer is not included in the CUDA graph.
|
||||||
|
input_ids = self.input_ids.gpu[:num_input_tokens]
|
||||||
|
inputs_embeds = None
|
||||||
|
model_kwargs = self._init_model_kwargs(num_input_tokens)
|
||||||
|
if self.uses_mrope:
|
||||||
|
positions = self.mrope_positions.gpu[:, :num_input_tokens]
|
||||||
|
else:
|
||||||
|
positions = self.positions.gpu[:num_input_tokens]
|
||||||
|
|
||||||
|
if get_pp_group().is_first_rank:
|
||||||
|
intermediate_tensors = None
|
||||||
|
else:
|
||||||
|
intermediate_tensors = self.sync_and_slice_intermediate_tensors(
|
||||||
|
num_input_tokens, intermediate_tensors, True
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.model_config.is_encoder_decoder
|
||||||
|
and scheduler_output.scheduled_encoder_inputs
|
||||||
|
):
|
||||||
|
encoder_inputs = self._extract_encoder_inputs(scheduler_output)
|
||||||
|
model_kwargs.update(encoder_inputs)
|
||||||
|
|
||||||
|
return (
|
||||||
|
num_scheduled_tokens,
|
||||||
|
num_input_tokens,
|
||||||
|
num_tokens_after_padding,
|
||||||
|
input_ids,
|
||||||
|
inputs_embeds,
|
||||||
|
positions,
|
||||||
|
intermediate_tensors,
|
||||||
|
model_kwargs,
|
||||||
|
per_req_additional_information,
|
||||||
|
)
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
def execute_model(
|
||||||
|
self,
|
||||||
|
scheduler_output: "SchedulerOutput",
|
||||||
|
intermediate_tensors: Optional[IntermediateTensors] = None,
|
||||||
|
) -> Union[OmniModelRunnerOutput, AsyncModelRunnerOutput, IntermediateTensors]:
|
||||||
|
with record_function_or_nullcontext("Preprocess"):
|
||||||
|
with self.synchronize_input_prep():
|
||||||
|
# Update persistent batch states.
|
||||||
|
self._update_states(scheduler_output)
|
||||||
|
|
||||||
|
# Decode per-request prompt_embeds / additional_hidden_states payloads
|
||||||
|
# (if present) into CPU tensors
|
||||||
|
try:
|
||||||
|
new_reqs = getattr(scheduler_output, "scheduled_new_reqs", [])
|
||||||
|
if new_reqs:
|
||||||
|
for nr in new_reqs:
|
||||||
|
req_id = getattr(nr, "req_id", None) or getattr(
|
||||||
|
nr, "request_id", None
|
||||||
|
)
|
||||||
|
if req_id is None:
|
||||||
|
continue
|
||||||
|
# prompt_embeds
|
||||||
|
payload_pe = getattr(nr, "prompt_embeds", None)
|
||||||
|
if payload_pe is not None:
|
||||||
|
if isinstance(payload_pe, torch.Tensor):
|
||||||
|
pe_cpu = payload_pe.detach().to("cpu").contiguous()
|
||||||
|
elif isinstance(payload_pe, PromptEmbedsPayload):
|
||||||
|
dt = np.dtype(
|
||||||
|
getattr(payload_pe, "dtype", "float32")
|
||||||
|
)
|
||||||
|
arr = np.frombuffer(payload_pe.data, dtype=dt)
|
||||||
|
arr = arr.reshape(payload_pe.shape)
|
||||||
|
pe_cpu = torch.from_numpy(arr)
|
||||||
|
else:
|
||||||
|
pe_cpu = None
|
||||||
|
if pe_cpu is not None and req_id in self.requests:
|
||||||
|
setattr(
|
||||||
|
self.requests[req_id],
|
||||||
|
"prompt_embeds_cpu",
|
||||||
|
pe_cpu,
|
||||||
|
)
|
||||||
|
# additional_information
|
||||||
|
payload_info = getattr(nr, "additional_information", None)
|
||||||
|
if payload_info is not None:
|
||||||
|
info_dict = {}
|
||||||
|
if isinstance(payload_info, dict):
|
||||||
|
# Already decoded
|
||||||
|
info_dict = payload_info
|
||||||
|
elif 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 and req_id in self.requests:
|
||||||
|
setattr(
|
||||||
|
self.requests[req_id],
|
||||||
|
"additional_information_cpu",
|
||||||
|
info_dict,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"Error decoding prompt_embeds / additional_information: {e}"
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not scheduler_output.total_num_scheduled_tokens:
|
||||||
|
if not has_kv_transfer_group():
|
||||||
|
# Return empty ModelRunnerOutput if no work to do.
|
||||||
|
return EMPTY_MODEL_RUNNER_OUTPUT
|
||||||
|
return self.kv_connector_no_forward(
|
||||||
|
scheduler_output, self.vllm_config
|
||||||
|
)
|
||||||
|
if self.cache_config.kv_sharing_fast_prefill:
|
||||||
|
assert not self.input_batch.num_prompt_logprobs, (
|
||||||
|
"--kv-sharing-fast-prefill produces incorrect "
|
||||||
|
"logprobs for prompt tokens, tokens, please disable "
|
||||||
|
"it when the requests need prompt logprobs"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prepare the decoder inputs.
|
||||||
|
(
|
||||||
|
attn_metadata,
|
||||||
|
logits_indices,
|
||||||
|
spec_decode_metadata,
|
||||||
|
num_scheduled_tokens_np,
|
||||||
|
spec_decode_common_attn_metadata,
|
||||||
|
max_query_len,
|
||||||
|
ubatch_slices,
|
||||||
|
num_tokens_after_padding,
|
||||||
|
) = self._prepare_inputs(scheduler_output)
|
||||||
|
|
||||||
|
(
|
||||||
|
num_scheduled_tokens,
|
||||||
|
num_input_tokens,
|
||||||
|
num_tokens_across_dp,
|
||||||
|
input_ids,
|
||||||
|
inputs_embeds,
|
||||||
|
positions,
|
||||||
|
intermediate_tensors,
|
||||||
|
model_kwargs,
|
||||||
|
per_req_additional_information,
|
||||||
|
) = self._preprocess(
|
||||||
|
scheduler_output,
|
||||||
|
num_scheduled_tokens_np,
|
||||||
|
intermediate_tensors,
|
||||||
|
ubatch_slices,
|
||||||
|
num_tokens_after_padding,
|
||||||
|
)
|
||||||
|
|
||||||
|
uniform_decode = (max_query_len == self.uniform_decode_query_len) and (
|
||||||
|
num_scheduled_tokens == self.input_batch.num_reqs * max_query_len
|
||||||
|
)
|
||||||
|
batch_descriptor = BatchDescriptor(
|
||||||
|
num_tokens=num_input_tokens, uniform_decode=uniform_decode
|
||||||
|
)
|
||||||
|
cudagraph_runtime_mode, batch_descriptor = (
|
||||||
|
self.cudagraph_dispatcher.dispatch(batch_descriptor)
|
||||||
|
)
|
||||||
|
|
||||||
|
# This is currently to get around the assert in the DPMetadata
|
||||||
|
# where it wants `num_tokens_across_dp` to align with `num_tokens`
|
||||||
|
if ubatch_slices is not None:
|
||||||
|
num_input_tokens = ubatch_slices[0].num_tokens
|
||||||
|
|
||||||
|
# Run the model.
|
||||||
|
# Use persistent buffers for CUDA graphs.
|
||||||
|
with (
|
||||||
|
set_forward_context(
|
||||||
|
attn_metadata,
|
||||||
|
self.vllm_config,
|
||||||
|
num_tokens=num_input_tokens,
|
||||||
|
num_tokens_across_dp=num_tokens_across_dp,
|
||||||
|
cudagraph_runtime_mode=cudagraph_runtime_mode,
|
||||||
|
batch_descriptor=batch_descriptor,
|
||||||
|
ubatch_slices=ubatch_slices,
|
||||||
|
),
|
||||||
|
record_function_or_nullcontext("Forward"),
|
||||||
|
self.maybe_get_kv_connector_output(scheduler_output) as kv_connector_output,
|
||||||
|
):
|
||||||
|
|
||||||
|
model_kwargs_extra = {}
|
||||||
|
# Pass per-request additional information map for this step (no concat)
|
||||||
|
if per_req_additional_information:
|
||||||
|
model_kwargs_extra["additional_information_by_req_id"] = per_req_additional_information
|
||||||
|
# Always pass per-request runtime additional_information (persisted in request state)
|
||||||
|
try:
|
||||||
|
per_req_runtime_info = []
|
||||||
|
for req_id in self.input_batch.req_ids:
|
||||||
|
req_state = self.requests.get(req_id)
|
||||||
|
info = (
|
||||||
|
getattr(req_state, "additional_information_cpu", None)
|
||||||
|
if req_state is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
per_req_runtime_info.append(info if isinstance(info, dict) else {})
|
||||||
|
model_kwargs_extra["runtime_additional_information"] = (
|
||||||
|
per_req_runtime_info
|
||||||
|
)
|
||||||
|
model_kwargs_extra["request_ids"] = self.input_batch.req_ids
|
||||||
|
# Pass each request's token span within the flattened sequence for this step, enabling the model to map decode/prefill by request
|
||||||
|
req_token_spans = []
|
||||||
|
for req_index in range(len(self.input_batch.req_ids)):
|
||||||
|
start_offset = int(self.query_start_loc.cpu[req_index])
|
||||||
|
sched_tokens = int(num_scheduled_tokens_np[req_index])
|
||||||
|
req_token_spans.append((start_offset, start_offset + sched_tokens))
|
||||||
|
model_kwargs_extra["request_token_spans"] = req_token_spans
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
model_output = self.model(
|
||||||
|
input_ids=input_ids,
|
||||||
|
positions=positions,
|
||||||
|
intermediate_tensors=intermediate_tensors,
|
||||||
|
inputs_embeds=inputs_embeds,
|
||||||
|
**model_kwargs,
|
||||||
|
sampling_metadata=self.input_batch.sampling_metadata,
|
||||||
|
logits_index=logits_indices,
|
||||||
|
sampler=self.sampler,
|
||||||
|
**model_kwargs_extra,
|
||||||
|
)
|
||||||
|
|
||||||
|
with record_function_or_nullcontext("Postprocess"):
|
||||||
|
if self.use_aux_hidden_state_outputs:
|
||||||
|
# True when EAGLE 3 is used.
|
||||||
|
hidden_states, aux_hidden_states = model_output
|
||||||
|
else:
|
||||||
|
# Common case.
|
||||||
|
hidden_states = model_output
|
||||||
|
aux_hidden_states = None
|
||||||
|
|
||||||
|
hidden_states, multimodal_outputs = self.extract_multimodal_outputs(
|
||||||
|
hidden_states
|
||||||
|
)
|
||||||
|
# The model side may return per-request additional_information updates (model-agnostic channel).
|
||||||
|
# Convention: multimodal_outputs["additional_information_update"] is a list[dict] in batch order;
|
||||||
|
# the runner merges it into the corresponding request's additional_information_cpu for subsequent decode.
|
||||||
|
try:
|
||||||
|
if (
|
||||||
|
isinstance(multimodal_outputs, dict)
|
||||||
|
and (
|
||||||
|
"additional_information_update" in multimodal_outputs
|
||||||
|
or "additional_information_update_by_req_id" in multimodal_outputs
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# Option A: list[dict] in batch order
|
||||||
|
updates_list = multimodal_outputs.get(
|
||||||
|
"additional_information_update"
|
||||||
|
)
|
||||||
|
if isinstance(updates_list, list):
|
||||||
|
for idx, upd in enumerate(updates_list):
|
||||||
|
if not isinstance(upd, dict) or idx >= len(self.input_batch.req_ids):
|
||||||
|
continue
|
||||||
|
req_id = self.input_batch.req_ids[idx]
|
||||||
|
self._merge_additional_information_update(req_id, upd)
|
||||||
|
# Option B: dict[str, dict] keyed by req_id
|
||||||
|
updates_map = multimodal_outputs.get(
|
||||||
|
"additional_information_update_by_req_id"
|
||||||
|
)
|
||||||
|
if isinstance(updates_map, dict):
|
||||||
|
for req_id, upd in updates_map.items():
|
||||||
|
if not isinstance(upd, dict):
|
||||||
|
continue
|
||||||
|
if req_id not in self.requests:
|
||||||
|
continue
|
||||||
|
self._merge_additional_information_update(req_id, upd)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error merging for requests:{self.input_batch.req_ids} additional information update: {e}, with the multimodal_outputs as {multimodal_outputs}")
|
||||||
|
if not self.broadcast_pp_output:
|
||||||
|
|
||||||
|
# Common case.
|
||||||
|
if not get_pp_group().is_last_rank:
|
||||||
|
# Return the intermediate tensors.
|
||||||
|
assert isinstance(hidden_states, IntermediateTensors)
|
||||||
|
hidden_states.kv_connector_output = kv_connector_output
|
||||||
|
return hidden_states
|
||||||
|
|
||||||
|
if self.is_pooling_model:
|
||||||
|
# Return the pooling output.
|
||||||
|
output = self._pool(
|
||||||
|
hidden_states, num_scheduled_tokens, num_scheduled_tokens_np
|
||||||
|
)
|
||||||
|
output.kv_connector_output = kv_connector_output
|
||||||
|
return output
|
||||||
|
|
||||||
|
sample_hidden_states = hidden_states[logits_indices]
|
||||||
|
logits = self.model.compute_logits(sample_hidden_states)
|
||||||
|
else:
|
||||||
|
# Rare case.
|
||||||
|
assert not self.is_pooling_model
|
||||||
|
|
||||||
|
if not get_pp_group().is_last_rank:
|
||||||
|
all_gather_tensors = {
|
||||||
|
"residual": not is_residual_scattered_for_sp(
|
||||||
|
self.vllm_config, num_input_tokens
|
||||||
|
)
|
||||||
|
}
|
||||||
|
get_pp_group().send_tensor_dict(
|
||||||
|
hidden_states.tensors,
|
||||||
|
all_gather_group=get_tp_group(),
|
||||||
|
all_gather_tensors=all_gather_tensors,
|
||||||
|
)
|
||||||
|
logits = None
|
||||||
|
else:
|
||||||
|
sample_hidden_states = hidden_states[logits_indices]
|
||||||
|
logits = self.model.compute_logits(sample_hidden_states)
|
||||||
|
|
||||||
|
model_output_broadcast_data = {}
|
||||||
|
if logits is not None:
|
||||||
|
model_output_broadcast_data["logits"] = logits.contiguous()
|
||||||
|
|
||||||
|
model_output_broadcast_data = get_pp_group().broadcast_tensor_dict(
|
||||||
|
model_output_broadcast_data, src=len(get_pp_group().ranks) - 1
|
||||||
|
)
|
||||||
|
assert model_output_broadcast_data is not None
|
||||||
|
logits = model_output_broadcast_data["logits"]
|
||||||
|
|
||||||
|
# Apply structured output bitmasks if present
|
||||||
|
if scheduler_output.grammar_bitmask is not None:
|
||||||
|
apply_grammar_bitmask(
|
||||||
|
scheduler_output, self.input_batch, logits, self.device
|
||||||
|
)
|
||||||
|
|
||||||
|
with record_function_or_nullcontext("Sample"):
|
||||||
|
sampler_output = self._sample(logits, spec_decode_metadata)
|
||||||
|
|
||||||
|
def propose_draft_token_ids(sampled_token_ids):
|
||||||
|
assert spec_decode_common_attn_metadata is not None
|
||||||
|
with record_function_or_nullcontext("Draft"):
|
||||||
|
self._draft_token_ids = self.propose_draft_token_ids(
|
||||||
|
scheduler_output,
|
||||||
|
sampled_token_ids,
|
||||||
|
self.input_batch.sampling_metadata,
|
||||||
|
hidden_states,
|
||||||
|
sample_hidden_states,
|
||||||
|
aux_hidden_states,
|
||||||
|
spec_decode_metadata,
|
||||||
|
spec_decode_common_attn_metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
use_padded_batch_for_eagle = (
|
||||||
|
self.speculative_config
|
||||||
|
and self.speculative_config.use_eagle()
|
||||||
|
and not self.speculative_config.disable_padded_drafter_batch
|
||||||
|
)
|
||||||
|
effective_drafter_max_model_len = self.max_model_len
|
||||||
|
if effective_drafter_max_model_len is None:
|
||||||
|
effective_drafter_max_model_len = self.model_config.max_model_len
|
||||||
|
if (
|
||||||
|
self.speculative_config
|
||||||
|
and self.speculative_config.draft_model_config is not None
|
||||||
|
and self.speculative_config.draft_model_config.max_model_len is not None
|
||||||
|
):
|
||||||
|
effective_drafter_max_model_len = (
|
||||||
|
self.speculative_config.draft_model_config.max_model_len
|
||||||
|
)
|
||||||
|
input_fits_in_drafter = spec_decode_common_attn_metadata and (
|
||||||
|
spec_decode_common_attn_metadata.seq_lens.max()
|
||||||
|
+ self.speculative_config.num_speculative_tokens
|
||||||
|
<= effective_drafter_max_model_len
|
||||||
|
)
|
||||||
|
if use_padded_batch_for_eagle and input_fits_in_drafter:
|
||||||
|
# EAGLE speculative decoding can use the GPU sampled tokens
|
||||||
|
# as inputs, and does not need to wait for bookkeeping to finish.
|
||||||
|
propose_draft_token_ids(sampler_output.sampled_token_ids)
|
||||||
|
|
||||||
|
with record_function_or_nullcontext("Bookkeep"):
|
||||||
|
(
|
||||||
|
num_nans_in_logits,
|
||||||
|
logprobs_lists,
|
||||||
|
valid_sampled_token_ids,
|
||||||
|
prompt_logprobs_dict,
|
||||||
|
req_ids_output_copy,
|
||||||
|
req_id_to_index_output_copy,
|
||||||
|
invalid_req_indices,
|
||||||
|
) = self._bookkeeping_sync(
|
||||||
|
scheduler_output,
|
||||||
|
sampler_output,
|
||||||
|
logits,
|
||||||
|
hidden_states,
|
||||||
|
num_scheduled_tokens,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.speculative_config
|
||||||
|
and not use_padded_batch_for_eagle
|
||||||
|
and input_fits_in_drafter
|
||||||
|
):
|
||||||
|
# ngram and other speculative decoding methods use the sampled
|
||||||
|
# tokens on the CPU, so they are run after bookkeeping.
|
||||||
|
propose_draft_token_ids(valid_sampled_token_ids)
|
||||||
|
|
||||||
|
with record_function_or_nullcontext("EPLB"):
|
||||||
|
self.eplb_step()
|
||||||
|
|
||||||
|
# Convert to per-request tensors on CPU
|
||||||
|
hidden_states_cpu = hidden_states.detach().to("cpu").contiguous()
|
||||||
|
pooler_output: list[Optional[torch.Tensor]] = []
|
||||||
|
prev_logits_index = 0
|
||||||
|
for logits_index in logits_indices:
|
||||||
|
pooler_output.append(
|
||||||
|
hidden_states_cpu[prev_logits_index : logits_index + 1]
|
||||||
|
)
|
||||||
|
prev_logits_index = logits_index + 1
|
||||||
|
|
||||||
|
output = OmniModelRunnerOutput(
|
||||||
|
req_ids=req_ids_output_copy,
|
||||||
|
req_id_to_index=req_id_to_index_output_copy,
|
||||||
|
sampled_token_ids=valid_sampled_token_ids,
|
||||||
|
logprobs=logprobs_lists,
|
||||||
|
prompt_logprobs_dict=prompt_logprobs_dict,
|
||||||
|
pooler_output=(
|
||||||
|
pooler_output
|
||||||
|
if self.vllm_config.model_config.engine_output_type != "text"
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
kv_connector_output=kv_connector_output,
|
||||||
|
num_nans_in_logits=num_nans_in_logits,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.use_async_scheduling:
|
||||||
|
return output
|
||||||
|
|
||||||
|
return AsyncGPUModelRunnerOutput(
|
||||||
|
model_runner_output=output,
|
||||||
|
sampled_token_ids=sampler_output.sampled_token_ids,
|
||||||
|
invalid_req_indices=invalid_req_indices,
|
||||||
|
async_output_copy_stream=self.async_output_copy_stream,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _merge_additional_information_update(self, req_id: str, upd: dict) -> None:
|
||||||
|
req_state = self.requests.get(req_id)
|
||||||
|
if req_state is None:
|
||||||
|
return
|
||||||
|
existing = getattr(req_state, "additional_information_cpu", {})
|
||||||
|
if not isinstance(existing, dict):
|
||||||
|
existing = {}
|
||||||
|
merged = dict(existing)
|
||||||
|
for k, v in upd.items():
|
||||||
|
if isinstance(v, torch.Tensor):
|
||||||
|
merged[k] = v.detach().to("cpu").contiguous()
|
||||||
|
elif isinstance(v, list):
|
||||||
|
new_list = []
|
||||||
|
for item in v:
|
||||||
|
if isinstance(item, torch.Tensor):
|
||||||
|
new_list.append(item.detach().to("cpu").contiguous())
|
||||||
|
else:
|
||||||
|
new_list.append(item)
|
||||||
|
merged[k] = new_list
|
||||||
|
else:
|
||||||
|
merged[k] = v
|
||||||
|
setattr(req_state, "additional_information_cpu", merged)
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import os
|
||||||
|
import platform
|
||||||
|
|
||||||
|
from vllm import envs
|
||||||
|
import torch
|
||||||
|
from vllm.logger import init_logger
|
||||||
|
from vllm.model_executor import set_random_seed
|
||||||
|
from vllm.platforms import current_platform, CpuArchEnum
|
||||||
|
|
||||||
|
|
||||||
|
from vllm.v1.worker.cpu_worker import CPUWorker
|
||||||
|
from vllm.v1.worker.gpu_worker import init_worker_distributed_environment
|
||||||
|
|
||||||
|
from vllm_omni.worker.cpu.cpu_ar_model_runner import CPUARModelRunner
|
||||||
|
|
||||||
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CPUARWorker(CPUWorker):
|
||||||
|
def init_device(self):
|
||||||
|
# Setup OpenMP threads affinity.
|
||||||
|
omp_cpuids = envs.VLLM_CPU_OMP_THREADS_BIND
|
||||||
|
if omp_cpuids == "auto" and platform.system() == "Linux":
|
||||||
|
cpu_arch = current_platform.get_cpu_architecture()
|
||||||
|
if cpu_arch in (CpuArchEnum.POWERPC, CpuArchEnum.S390X):
|
||||||
|
# For S390X/POWERPC SMT-8/4/2
|
||||||
|
self.local_omp_cpuid = self._get_autobind_cpu_ids(
|
||||||
|
lambda cpus: [cpu for cpu in cpus if cpu.id % 8 < 4])
|
||||||
|
elif current_platform.get_cpu_architecture() == CpuArchEnum.X86:
|
||||||
|
# For x86 SMT-2, use 1 CPU per core
|
||||||
|
self.local_omp_cpuid = self._get_autobind_cpu_ids(
|
||||||
|
lambda cpus: cpus[-1:])
|
||||||
|
else:
|
||||||
|
self.local_omp_cpuid = "all"
|
||||||
|
else:
|
||||||
|
local_dp_rank = self.parallel_config.data_parallel_rank_local
|
||||||
|
omp_cpuids = omp_cpuids.split("|")
|
||||||
|
if local_dp_rank is not None:
|
||||||
|
world_size = self.parallel_config.world_size
|
||||||
|
omp_cpuids = omp_cpuids[local_dp_rank *
|
||||||
|
world_size:(local_dp_rank + 1) *
|
||||||
|
world_size]
|
||||||
|
self.local_omp_cpuid = omp_cpuids[self.rank]
|
||||||
|
|
||||||
|
if self.local_omp_cpuid != "all":
|
||||||
|
ret = torch.ops._C_utils.init_cpu_threads_env(self.local_omp_cpuid)
|
||||||
|
if ret:
|
||||||
|
logger.info(ret)
|
||||||
|
|
||||||
|
# Note: unique identifier for creating allreduce shared memory
|
||||||
|
os.environ["VLLM_DIST_IDENT"] = self.distributed_init_method.split(
|
||||||
|
":")[-1]
|
||||||
|
# Initialize the distributed environment.
|
||||||
|
init_worker_distributed_environment(self.vllm_config, self.rank,
|
||||||
|
self.distributed_init_method,
|
||||||
|
self.local_rank,
|
||||||
|
current_platform.dist_backend)
|
||||||
|
# Set random seed.
|
||||||
|
set_random_seed(self.model_config.seed)
|
||||||
|
|
||||||
|
# Construct the model runner
|
||||||
|
self.model_runner: CPUARModelRunner = CPUARModelRunner(
|
||||||
|
self.vllm_config, torch.device("cpu"))
|
||||||
@@ -0,0 +1,620 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import gc
|
||||||
|
import logging
|
||||||
|
from typing import Any, List, Optional, Union
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from vllm.config import CUDAGraphMode
|
||||||
|
from vllm.forward_context import BatchDescriptor
|
||||||
|
from vllm.multimodal.inputs import MultiModalKwargs
|
||||||
|
from vllm.v1.attention.backends.utils import CommonAttentionMetadata
|
||||||
|
from vllm.v1.core.sched.output import SchedulerOutput
|
||||||
|
from vllm.v1.spec_decode.eagle import EagleProposer
|
||||||
|
from vllm.v1.utils import record_function_or_nullcontext
|
||||||
|
from vllm.v1.worker.gpu_model_runner import (
|
||||||
|
EMPTY_MODEL_RUNNER_OUTPUT,
|
||||||
|
AsyncGPUModelRunnerOutput,
|
||||||
|
IntermediateTensors,
|
||||||
|
PerLayerAttnMetadata,
|
||||||
|
get_pp_group,
|
||||||
|
set_forward_context,
|
||||||
|
)
|
||||||
|
from vllm.v1.worker.ubatch_utils import UBatchSlices
|
||||||
|
from vllm.v1.worker.utils import sanity_check_mm_encoder_outputs
|
||||||
|
from vllm_omni.outputs import OmniModelRunnerOutput
|
||||||
|
from vllm_omni.worker.cpu.cpu_model_runner import OmniCPUModelRunner
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CPUDiffusionModelRunner(OmniCPUModelRunner):
|
||||||
|
"""Diffusion model runner for vLLM-omni (non-autoregressive).
|
||||||
|
|
||||||
|
- Reuses CPUModelRunner preparation, multimodal handling, and TP/PP/DP glue.
|
||||||
|
- Does not compute logits or perform token sampling.
|
||||||
|
- Executes diffusion process and returns tensors via `pooler_output`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _preprocess(
|
||||||
|
self,
|
||||||
|
scheduler_output: "SchedulerOutput",
|
||||||
|
intermediate_tensors: Optional[IntermediateTensors] = None,
|
||||||
|
ubatch_slices: Optional[UBatchSlices] = None,
|
||||||
|
num_tokens_after_padding: Optional[torch.Tensor] = None,
|
||||||
|
) -> tuple[
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
Optional[torch.Tensor],
|
||||||
|
Optional[torch.Tensor],
|
||||||
|
Optional[torch.Tensor],
|
||||||
|
torch.Tensor,
|
||||||
|
Optional[IntermediateTensors],
|
||||||
|
dict[str, Any],
|
||||||
|
]:
|
||||||
|
|
||||||
|
# Input token count for this iteration (not used by diffusion, but
|
||||||
|
# retained to keep DP padding/ordering consistent)
|
||||||
|
num_input_tokens = scheduler_output.total_num_scheduled_tokens
|
||||||
|
num_pad, num_tokens_after_padding = self.get_dp_padding(num_input_tokens)
|
||||||
|
num_input_tokens += num_pad
|
||||||
|
|
||||||
|
# _prepare_inputs may reorder the batch, so we must gather multi
|
||||||
|
# modal outputs after that to ensure the correct order
|
||||||
|
if (
|
||||||
|
self.supports_mm_inputs
|
||||||
|
and get_pp_group().is_first_rank
|
||||||
|
and not self.model_config.is_encoder_decoder
|
||||||
|
):
|
||||||
|
# Run the multimodal encoder if any.
|
||||||
|
self._execute_mm_encoder(scheduler_output)
|
||||||
|
mm_embeds = self._gather_mm_embeddings(scheduler_output)
|
||||||
|
|
||||||
|
# NOTE(woosuk): To unify token ids and soft tokens (vision
|
||||||
|
# embeddings), we always use embeddings (rather than token ids)
|
||||||
|
# as input to the multimodal model, even when the input is text.
|
||||||
|
inputs_embeds_scheduled = self.model.get_input_embeddings(
|
||||||
|
input_ids=self.input_ids.gpu[:num_input_tokens],
|
||||||
|
multimodal_embeddings=mm_embeds or None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO(woosuk): Avoid the copy. Optimize.
|
||||||
|
self.inputs_embeds.gpu[:num_input_tokens].copy_(inputs_embeds_scheduled)
|
||||||
|
|
||||||
|
input_ids = self.input_ids.gpu[:num_input_tokens]
|
||||||
|
inputs_embeds = self.inputs_embeds.gpu[:num_input_tokens]
|
||||||
|
model_kwargs = {
|
||||||
|
**self._init_model_kwargs(num_input_tokens),
|
||||||
|
**self._extract_mm_kwargs(scheduler_output),
|
||||||
|
}
|
||||||
|
elif self.enable_prompt_embeds and get_pp_group().is_first_rank:
|
||||||
|
# Get the input embeddings for the tokens that are not input embeds,
|
||||||
|
# then put them into the appropriate positions.
|
||||||
|
# TODO(qthequartermasterman): Since even when prompt embeds are
|
||||||
|
# enabled, (a) not all requests will use prompt embeds, and (b)
|
||||||
|
# after the initial prompt is processed, the rest of the generated
|
||||||
|
# tokens will be token ids, it is not desirable to have the
|
||||||
|
# embedding layer outside of the CUDA graph all the time. The v0
|
||||||
|
# engine avoids this by "double compiling" the CUDA graph, once
|
||||||
|
# with input_ids and again with inputs_embeds, for all num_tokens.
|
||||||
|
# If a batch only has token ids, then including the embedding layer
|
||||||
|
# in the CUDA graph will be more performant (like in the else case
|
||||||
|
# below).
|
||||||
|
token_ids_idx = (
|
||||||
|
self.is_token_ids.gpu[:num_input_tokens]
|
||||||
|
.nonzero(as_tuple=False)
|
||||||
|
.squeeze(1)
|
||||||
|
)
|
||||||
|
# Some tokens ids may need to become embeds
|
||||||
|
if token_ids_idx.numel() > 0:
|
||||||
|
token_ids = self.input_ids.gpu[token_ids_idx]
|
||||||
|
tokens_to_embeds = self.model.get_input_embeddings(input_ids=token_ids)
|
||||||
|
self.inputs_embeds.gpu[token_ids_idx] = tokens_to_embeds
|
||||||
|
|
||||||
|
inputs_embeds = self.inputs_embeds.gpu[:num_input_tokens]
|
||||||
|
model_kwargs = self._init_model_kwargs(num_input_tokens)
|
||||||
|
input_ids = self.input_ids.gpu[:num_input_tokens]
|
||||||
|
else:
|
||||||
|
# For text-only models, we use token ids as input.
|
||||||
|
# While it is possible to use embeddings as input just like the
|
||||||
|
# multimodal models, it is not desirable for performance since
|
||||||
|
# then the embedding layer is not included in the CUDA graph.
|
||||||
|
input_ids = self.input_ids.gpu[:num_input_tokens]
|
||||||
|
inputs_embeds = None
|
||||||
|
model_kwargs = self._init_model_kwargs(num_input_tokens)
|
||||||
|
if self.uses_mrope:
|
||||||
|
positions = self.mrope_positions.gpu[:, :num_input_tokens]
|
||||||
|
else:
|
||||||
|
positions = self.positions.gpu[:num_input_tokens]
|
||||||
|
|
||||||
|
if get_pp_group().is_first_rank:
|
||||||
|
intermediate_tensors = None
|
||||||
|
else:
|
||||||
|
intermediate_tensors = self.sync_and_slice_intermediate_tensors(
|
||||||
|
num_input_tokens, intermediate_tensors, True
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.model_config.is_encoder_decoder
|
||||||
|
and scheduler_output.scheduled_encoder_inputs
|
||||||
|
):
|
||||||
|
encoder_inputs = self._extract_encoder_inputs(scheduler_output)
|
||||||
|
model_kwargs.update(encoder_inputs)
|
||||||
|
|
||||||
|
return (
|
||||||
|
num_input_tokens,
|
||||||
|
num_input_tokens,
|
||||||
|
num_tokens_after_padding,
|
||||||
|
input_ids,
|
||||||
|
inputs_embeds,
|
||||||
|
positions,
|
||||||
|
intermediate_tensors,
|
||||||
|
model_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
def execute_model(
|
||||||
|
self,
|
||||||
|
scheduler_output: "SchedulerOutput",
|
||||||
|
intermediate_tensors: Optional[IntermediateTensors] = None,
|
||||||
|
) -> Union[OmniModelRunnerOutput, IntermediateTensors]:
|
||||||
|
with record_function_or_nullcontext("Preprocess"):
|
||||||
|
with self.synchronize_input_prep():
|
||||||
|
# Update persistent batch states.
|
||||||
|
self._update_states(scheduler_output)
|
||||||
|
if not scheduler_output.total_num_scheduled_tokens:
|
||||||
|
return EMPTY_MODEL_RUNNER_OUTPUT
|
||||||
|
|
||||||
|
# Prepare the decoder inputs.
|
||||||
|
(
|
||||||
|
attn_metadata,
|
||||||
|
logits_indices,
|
||||||
|
spec_decode_metadata,
|
||||||
|
num_scheduled_tokens_np,
|
||||||
|
spec_decode_common_attn_metadata,
|
||||||
|
max_query_len,
|
||||||
|
ubatch_slices,
|
||||||
|
num_tokens_after_padding,
|
||||||
|
) = self._prepare_inputs(scheduler_output)
|
||||||
|
|
||||||
|
# Input token count for this iteration (not used by diffusion, but
|
||||||
|
# retained to keep DP padding/ordering consistent)
|
||||||
|
(
|
||||||
|
num_scheduled_tokens,
|
||||||
|
num_input_tokens,
|
||||||
|
num_tokens_across_dp,
|
||||||
|
input_ids,
|
||||||
|
inputs_embeds,
|
||||||
|
positions,
|
||||||
|
intermediate_tensors,
|
||||||
|
model_kwargs,
|
||||||
|
) = self._preprocess(
|
||||||
|
scheduler_output,
|
||||||
|
intermediate_tensors,
|
||||||
|
ubatch_slices,
|
||||||
|
num_tokens_after_padding,
|
||||||
|
)
|
||||||
|
|
||||||
|
cudagraph_runtime_mode = CUDAGraphMode.NONE
|
||||||
|
# Run the model.
|
||||||
|
# Use persistent buffers for CUDA graphs.
|
||||||
|
with (
|
||||||
|
set_forward_context(
|
||||||
|
attn_metadata,
|
||||||
|
self.vllm_config,
|
||||||
|
num_tokens=num_input_tokens,
|
||||||
|
num_tokens_across_dp=num_tokens_across_dp,
|
||||||
|
cudagraph_runtime_mode=cudagraph_runtime_mode,
|
||||||
|
batch_descriptor=None,
|
||||||
|
ubatch_slices=ubatch_slices,
|
||||||
|
),
|
||||||
|
record_function_or_nullcontext("Forward"),
|
||||||
|
self.maybe_get_kv_connector_output(scheduler_output) as kv_connector_output,
|
||||||
|
):
|
||||||
|
|
||||||
|
outputs = self._run_diffusion(
|
||||||
|
input_ids=input_ids,
|
||||||
|
positions=positions,
|
||||||
|
intermediate_tensors=intermediate_tensors,
|
||||||
|
inputs_embeds=inputs_embeds,
|
||||||
|
multimodal_kwargs=model_kwargs,
|
||||||
|
logits_indices=logits_indices,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, multimodal_outputs = self.extract_multimodal_outputs(outputs)
|
||||||
|
# Ensure one tensor per request, map to CPU for output struct
|
||||||
|
pooler_output: List[Optional[torch.Tensor]] = []
|
||||||
|
if isinstance(multimodal_outputs, torch.Tensor):
|
||||||
|
# If model returned a single stacked tensor, split by requests
|
||||||
|
assert multimodal_outputs.shape[0] == self.input_batch.num_reqs
|
||||||
|
for i in range(self.input_batch.num_reqs):
|
||||||
|
pooler_output.append(
|
||||||
|
multimodal_outputs[i].detach().to("cpu").contiguous()
|
||||||
|
)
|
||||||
|
elif isinstance(multimodal_outputs, list):
|
||||||
|
for out in multimodal_outputs:
|
||||||
|
pooler_output.append(
|
||||||
|
out.detach().to("cpu").contiguous() if out is not None else None
|
||||||
|
)
|
||||||
|
elif isinstance(multimodal_outputs, dict):
|
||||||
|
for out in multimodal_outputs.values():
|
||||||
|
pooler_output.append(
|
||||||
|
out.detach().to("cpu").contiguous() if out is not None else None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Unsupported diffusion output type")
|
||||||
|
|
||||||
|
output = OmniModelRunnerOutput(
|
||||||
|
req_ids=self.input_batch.req_ids,
|
||||||
|
req_id_to_index=self.input_batch.req_id_to_index,
|
||||||
|
sampled_token_ids=[],
|
||||||
|
logprobs=None,
|
||||||
|
prompt_logprobs_dict={},
|
||||||
|
pooler_output=pooler_output,
|
||||||
|
kv_connector_output=kv_connector_output,
|
||||||
|
num_nans_in_logits={},
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.use_async_scheduling:
|
||||||
|
return output
|
||||||
|
|
||||||
|
return AsyncGPUModelRunnerOutput(
|
||||||
|
model_runner_output=output,
|
||||||
|
sampled_token_ids=[],
|
||||||
|
invalid_req_indices=[],
|
||||||
|
async_output_copy_stream=self.async_output_copy_stream,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_diffusion(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
input_ids: torch.Tensor,
|
||||||
|
positions: torch.Tensor,
|
||||||
|
intermediate_tensors: Optional[IntermediateTensors],
|
||||||
|
inputs_embeds: Optional[torch.Tensor],
|
||||||
|
multimodal_kwargs: dict,
|
||||||
|
logits_indices: torch.Tensor,
|
||||||
|
) -> Union[torch.Tensor, list[torch.Tensor]]:
|
||||||
|
"""Runs the diffusion process and returns per-request tensors.
|
||||||
|
|
||||||
|
Tries model interfaces in the following order for maximal compatibility:
|
||||||
|
1) model.sample(condition=..., **kwargs)
|
||||||
|
2) model.forward(condition=..., **kwargs)
|
||||||
|
3) model.diffuse(condition=..., **kwargs)
|
||||||
|
"""
|
||||||
|
# Keep inputs identical to AR runner
|
||||||
|
kwargs = dict(
|
||||||
|
input_ids=input_ids,
|
||||||
|
positions=positions,
|
||||||
|
intermediate_tensors=intermediate_tensors,
|
||||||
|
inputs_embeds=inputs_embeds,
|
||||||
|
**MultiModalKwargs.as_kwargs(multimodal_kwargs, device=self.device),
|
||||||
|
sampling_metadata=self.input_batch.sampling_metadata,
|
||||||
|
logits_index=logits_indices,
|
||||||
|
sampler=self.sampler,
|
||||||
|
)
|
||||||
|
|
||||||
|
# For Qwen 2.5 Omni's current implementation, we only support the forward method
|
||||||
|
if hasattr(self.model, "forward"):
|
||||||
|
return self.model.forward(**kwargs)
|
||||||
|
# TODO: add the diffuse method for other models
|
||||||
|
|
||||||
|
raise RuntimeError(
|
||||||
|
"The loaded model does not expose diffusion interfaces 'sample', "
|
||||||
|
"'forward', or 'diffuse'. Please implement one of them or adapt the runner."
|
||||||
|
)
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
def _dummy_run(
|
||||||
|
self,
|
||||||
|
num_tokens: int,
|
||||||
|
cudagraph_runtime_mode: Optional[CUDAGraphMode] = None,
|
||||||
|
force_attention: bool = False,
|
||||||
|
uniform_decode: bool = False,
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
num_tokens_after_padding = None
|
||||||
|
|
||||||
|
# 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 = {}
|
||||||
|
|
||||||
|
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]:
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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,
|
||||||
|
):
|
||||||
|
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.
|
||||||
|
|
||||||
|
# logit_indices = np.cumsum(num_scheduled_tokens) - 1
|
||||||
|
hidden_states, _ = self.extract_multimodal_outputs(hidden_states)
|
||||||
|
return hidden_states, None
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
def _dummy_sampler_run(self, hidden_states: torch.Tensor) -> None:
|
||||||
|
logger.warning("Dummy sampler run is not implemented for diffusion model")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def profile_run(self) -> None:
|
||||||
|
# Profile with multimodal encoder & encoder cache.
|
||||||
|
if self.supports_mm_inputs:
|
||||||
|
if self.model_config.multimodal_config.skip_mm_profiling:
|
||||||
|
logger.info(
|
||||||
|
"Skipping memory profiling for multimodal encoder and "
|
||||||
|
"encoder cache."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
mm_budget = self.mm_budget
|
||||||
|
assert mm_budget is not None
|
||||||
|
|
||||||
|
if (encoder_budget := mm_budget.get_encoder_budget()) > 0:
|
||||||
|
# NOTE: Currently model is profiled with a single non-text
|
||||||
|
# modality with the max possible input tokens even when
|
||||||
|
# it supports multiple.
|
||||||
|
dummy_modality = mm_budget.get_modality_with_max_tokens()
|
||||||
|
max_mm_items_per_batch = mm_budget.max_items_per_batch_by_modality[
|
||||||
|
dummy_modality
|
||||||
|
]
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Encoder cache will be initialized with a budget of "
|
||||||
|
"%s tokens, and profiled with %s %s items of the "
|
||||||
|
"maximum feature size.",
|
||||||
|
encoder_budget,
|
||||||
|
max_mm_items_per_batch,
|
||||||
|
dummy_modality,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create dummy batch of multimodal inputs.
|
||||||
|
batched_dummy_mm_inputs = self._get_mm_dummy_batch(
|
||||||
|
dummy_modality,
|
||||||
|
max_mm_items_per_batch,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Run multimodal encoder.
|
||||||
|
dummy_encoder_outputs = self.model.get_multimodal_embeddings(
|
||||||
|
**batched_dummy_mm_inputs
|
||||||
|
)
|
||||||
|
|
||||||
|
sanity_check_mm_encoder_outputs(
|
||||||
|
dummy_encoder_outputs,
|
||||||
|
expected_num_items=max_mm_items_per_batch,
|
||||||
|
)
|
||||||
|
|
||||||
|
# NOTE: This happens when encoder cache needs to store
|
||||||
|
# the embeddings that encoder outputs are scattered onto.
|
||||||
|
# In this case we create dummy embeddings of size
|
||||||
|
# (encode_budget, hidden_size) and scatter encoder
|
||||||
|
# output into it.
|
||||||
|
encoder_output_shape = dummy_encoder_outputs[0].shape
|
||||||
|
if encoder_output_shape[0] < encoder_budget:
|
||||||
|
expanded_outputs = []
|
||||||
|
for output in dummy_encoder_outputs:
|
||||||
|
expanded = output.new_zeros(
|
||||||
|
(encoder_budget, encoder_output_shape[-1])
|
||||||
|
)
|
||||||
|
num_tokens = output.shape[0]
|
||||||
|
expanded[:num_tokens].copy_(output)
|
||||||
|
expanded_outputs.append(expanded)
|
||||||
|
|
||||||
|
dummy_encoder_outputs = expanded_outputs
|
||||||
|
|
||||||
|
# Cache the dummy encoder outputs.
|
||||||
|
self.encoder_cache["tmp"] = dict(enumerate(dummy_encoder_outputs))
|
||||||
|
|
||||||
|
# Add `is_profile` here to pre-allocate communication buffers
|
||||||
|
hidden_states, _ = self._dummy_run(self.max_num_tokens, is_profile=True)
|
||||||
|
if get_pp_group().is_last_rank:
|
||||||
|
pass
|
||||||
|
self._sync_device()
|
||||||
|
del hidden_states
|
||||||
|
self.encoder_cache.clear()
|
||||||
|
gc.collect()
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import gc
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from vllm import envs
|
||||||
|
from vllm.logger import init_logger
|
||||||
|
|
||||||
|
from vllm.model_executor import set_random_seed
|
||||||
|
from vllm.platforms import current_platform, CpuArchEnum
|
||||||
|
|
||||||
|
from vllm.v1.worker.cpu_worker import CPUWorker
|
||||||
|
|
||||||
|
from vllm.v1.worker.gpu_worker import init_worker_distributed_environment
|
||||||
|
|
||||||
|
from vllm_omni.worker.cpu.cpu_diffusion_model_runner import CPUDiffusionModelRunner
|
||||||
|
|
||||||
|
|
||||||
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CPUDiffusionWorker(CPUWorker):
|
||||||
|
def init_device(self):
|
||||||
|
# Setup OpenMP threads affinity.
|
||||||
|
omp_cpuids = envs.VLLM_CPU_OMP_THREADS_BIND
|
||||||
|
if omp_cpuids == "auto" and platform.system() == "Linux":
|
||||||
|
cpu_arch = current_platform.get_cpu_architecture()
|
||||||
|
if cpu_arch in (CpuArchEnum.POWERPC, CpuArchEnum.S390X):
|
||||||
|
# For S390X/POWERPC SMT-8/4/2
|
||||||
|
self.local_omp_cpuid = self._get_autobind_cpu_ids(
|
||||||
|
lambda cpus: [cpu for cpu in cpus if cpu.id % 8 < 4])
|
||||||
|
elif current_platform.get_cpu_architecture() == CpuArchEnum.X86:
|
||||||
|
# For x86 SMT-2, use 1 CPU per core
|
||||||
|
self.local_omp_cpuid = self._get_autobind_cpu_ids(
|
||||||
|
lambda cpus: cpus[-1:])
|
||||||
|
else:
|
||||||
|
self.local_omp_cpuid = "all"
|
||||||
|
else:
|
||||||
|
local_dp_rank = self.parallel_config.data_parallel_rank_local
|
||||||
|
omp_cpuids = omp_cpuids.split("|")
|
||||||
|
if local_dp_rank is not None:
|
||||||
|
world_size = self.parallel_config.world_size
|
||||||
|
omp_cpuids = omp_cpuids[local_dp_rank *
|
||||||
|
world_size:(local_dp_rank + 1) *
|
||||||
|
world_size]
|
||||||
|
self.local_omp_cpuid = omp_cpuids[self.rank]
|
||||||
|
|
||||||
|
if self.local_omp_cpuid != "all":
|
||||||
|
ret = torch.ops._C_utils.init_cpu_threads_env(self.local_omp_cpuid)
|
||||||
|
if ret:
|
||||||
|
logger.info(ret)
|
||||||
|
|
||||||
|
# Note: unique identifier for creating allreduce shared memory
|
||||||
|
os.environ["VLLM_DIST_IDENT"] = self.distributed_init_method.split(
|
||||||
|
":")[-1]
|
||||||
|
# Initialize the distributed environment.
|
||||||
|
init_worker_distributed_environment(self.vllm_config, self.rank,
|
||||||
|
self.distributed_init_method,
|
||||||
|
self.local_rank,
|
||||||
|
current_platform.dist_backend)
|
||||||
|
# Set random seed.
|
||||||
|
set_random_seed(self.model_config.seed)
|
||||||
|
|
||||||
|
# Construct the model runner
|
||||||
|
self.model_runner: CPUDiffusionModelRunner = CPUDiffusionModelRunner(
|
||||||
|
self.vllm_config, torch.device("cpu"))
|
||||||
@@ -0,0 +1,593 @@
|
|||||||
|
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]
|
||||||
Whitespace-only changes.
+1
-1
@@ -30,7 +30,7 @@ from vllm.v1.worker.ubatch_utils import UBatchSlices
|
|||||||
from vllm.v1.worker.utils import is_residual_scattered_for_sp
|
from vllm.v1.worker.utils import is_residual_scattered_for_sp
|
||||||
from vllm_omni.engine import AdditionalInformationPayload, PromptEmbedsPayload
|
from vllm_omni.engine import AdditionalInformationPayload, PromptEmbedsPayload
|
||||||
from vllm_omni.outputs import OmniModelRunnerOutput
|
from vllm_omni.outputs import OmniModelRunnerOutput
|
||||||
from vllm_omni.worker.gpu_model_runner import OmniGPUModelRunner
|
from vllm_omni.worker.gpu.gpu_model_runner import OmniGPUModelRunner
|
||||||
|
|
||||||
logger = init_logger(__name__)
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ from vllm.utils import GiB_bytes, MemorySnapshot
|
|||||||
from vllm.v1.utils import report_usage_stats
|
from vllm.v1.utils import report_usage_stats
|
||||||
from vllm.v1.worker.gpu_worker import Worker as GPUWorker
|
from vllm.v1.worker.gpu_worker import Worker as GPUWorker
|
||||||
from vllm.v1.worker.gpu_worker import init_worker_distributed_environment
|
from vllm.v1.worker.gpu_worker import init_worker_distributed_environment
|
||||||
from vllm_omni.worker.gpu_ar_model_runner import GPUARModelRunner
|
from vllm_omni.worker.gpu.gpu_ar_model_runner import GPUARModelRunner
|
||||||
|
|
||||||
|
|
||||||
class GPUARWorker(GPUWorker):
|
class GPUARWorker(GPUWorker):
|
||||||
+1
-1
@@ -25,7 +25,7 @@ from vllm.v1.worker.gpu_model_runner import (
|
|||||||
from vllm.v1.worker.ubatch_utils import UBatchSlices
|
from vllm.v1.worker.ubatch_utils import UBatchSlices
|
||||||
from vllm.v1.worker.utils import sanity_check_mm_encoder_outputs
|
from vllm.v1.worker.utils import sanity_check_mm_encoder_outputs
|
||||||
from vllm_omni.outputs import OmniModelRunnerOutput
|
from vllm_omni.outputs import OmniModelRunnerOutput
|
||||||
from vllm_omni.worker.gpu_model_runner import OmniGPUModelRunner
|
from vllm_omni.worker.gpu.gpu_model_runner import OmniGPUModelRunner
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
+1
-1
@@ -9,7 +9,7 @@ from vllm.utils import GiB_bytes, MemorySnapshot
|
|||||||
from vllm.v1.utils import report_usage_stats
|
from vllm.v1.utils import report_usage_stats
|
||||||
from vllm.v1.worker.gpu_worker import Worker as GPUWorker
|
from vllm.v1.worker.gpu_worker import Worker as GPUWorker
|
||||||
from vllm.v1.worker.gpu_worker import init_worker_distributed_environment
|
from vllm.v1.worker.gpu_worker import init_worker_distributed_environment
|
||||||
from vllm_omni.worker.gpu_diffusion_model_runner import GPUDiffusionModelRunner
|
from vllm_omni.worker.gpu.gpu_diffusion_model_runner import GPUDiffusionModelRunner
|
||||||
|
|
||||||
|
|
||||||
class GPUDiffusionWorker(GPUWorker):
|
class GPUDiffusionWorker(GPUWorker):
|
||||||
File renamed without changes.
Reference in new issue
Block a user