Files
vllm-project--vllm/vllm/v1/worker/gpu/model_states/encoder_decoder.py
T
wehub-resource-sync 7ce4c8e27e
pre-commit / pre-run-check (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:55:37 +08:00

190 lines
7.4 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from dataclasses import dataclass
from typing import Any
import numpy as np
import torch
import torch.nn as nn
from vllm.config import VllmConfig
from vllm.config.compilation import CUDAGraphMode
from vllm.v1.kv_cache_interface import CrossAttentionSpec, KVCacheConfig
from vllm.v1.worker.gpu.attn_utils import build_attn_metadata
from vllm.v1.worker.gpu.input_batch import InputBatch
from vllm.v1.worker.gpu.mm.encoder_cache import EncoderCache
from vllm.v1.worker.gpu.model_states.interface import (
ModelSpecificAttnMetadata,
ModelState,
)
from vllm.v1.worker.gpu.states import RequestState
from vllm.v1.worker.utils import AttentionGroup
@dataclass
class EncoderDecoderAttnMetadata(ModelSpecificAttnMetadata):
encoder_seq_lens: dict[int, tuple[torch.Tensor, np.ndarray]]
def get_extra_common_attn_kwargs(
self,
kv_cache_group_id: int,
num_reqs: int,
) -> dict[str, Any]:
encoder_seq_lens = self.encoder_seq_lens.get(kv_cache_group_id)
if encoder_seq_lens is None:
return {}
encoder_seq_lens_gpu, encoder_seq_lens_cpu = encoder_seq_lens
return {
"encoder_seq_lens": encoder_seq_lens_gpu[:num_reqs],
"encoder_seq_lens_cpu": encoder_seq_lens_cpu[:num_reqs],
}
class EncoderDecoderModelState(ModelState):
"""ModelState for cross-attention encoder-decoder models
(Whisper, CohereASR, NemotronParse, FireRedLID, ...)
"""
def __init__(
self,
vllm_config: VllmConfig,
model: nn.Module,
encoder_cache: EncoderCache | None,
device: torch.device,
) -> None:
assert encoder_cache is not None
super().__init__(vllm_config, model, encoder_cache, device)
self.max_encoder_len = getattr(
self.model_config.hf_config,
"max_source_positions",
self.max_model_len,
)
self.encoder_seq_lens_gpu = torch.zeros(
self.max_num_reqs, dtype=torch.int32, device=self.device
)
self.encoder_outputs: list[torch.Tensor] = []
def get_mm_embeddings(
self,
scheduled_encoder_inputs: dict[str, list[int]],
input_batch: InputBatch,
req_states: RequestState,
) -> None:
# Ensure encoder inputs are ordered consistently with input_batch.req_ids.
encoder_inputs: dict[str, list[int]] = {}
for req_id in input_batch.req_ids:
req_encoder_inputs = scheduled_encoder_inputs.get(req_id, [])
if req_encoder_inputs:
encoder_inputs[req_id] = req_encoder_inputs
_, mm_kwargs = self.encoder_runner.prepare_mm_inputs(encoder_inputs)
if mm_kwargs:
# Encoder-decoder models consume encoder outputs through the
# `encoder_outputs` forward kwarg, not `inputs_embeds`. Single modality
# so execute_mm_encoder preserves request order; use its return value
# directly. No need to store in encoder_cache: cross-attention K/V are
# written to the KV cache on the first step; decode steps use the cache.
self.encoder_outputs = self.encoder_runner.execute_mm_encoder(mm_kwargs)
else:
# Decode steps: encoder K/V are in cross-attention KV cache.
self.encoder_outputs = []
return None
def prepare_inputs(
self, input_batch: InputBatch, req_states: RequestState
) -> dict[str, Any]:
model_inputs = {"encoder_outputs": self.encoder_outputs}
self.encoder_outputs = []
return model_inputs
def prepare_dummy_inputs(self, num_reqs: int, num_tokens: int) -> dict[str, Any]:
return {"encoder_outputs": []}
def prepare_attn(
self,
input_batch: InputBatch,
cudagraph_mode: CUDAGraphMode,
block_tables: tuple[torch.Tensor, ...],
slot_mappings: torch.Tensor,
attn_groups: list[list[AttentionGroup]],
kv_cache_config: KVCacheConfig,
for_capture: bool = False,
) -> dict[str, Any]:
if cudagraph_mode == CUDAGraphMode.FULL:
num_reqs = input_batch.num_reqs_after_padding
num_tokens = input_batch.num_tokens_after_padding
else:
num_reqs = input_batch.num_reqs
num_tokens = input_batch.num_tokens
enc_dec_attn_metadata = EncoderDecoderAttnMetadata(
self._get_encoder_seq_lens(
input_batch.req_ids, attn_groups, for_capture, num_reqs
)
)
query_start_loc_cpu = torch.from_numpy(input_batch.query_start_loc_np)
max_query_len = input_batch.num_scheduled_tokens.max().item()
seq_lens_cpu_upper_bound = input_batch.seq_lens_cpu_upper_bound
if for_capture:
max_seq_len = self.max_model_len
else:
max_seq_len = int(seq_lens_cpu_upper_bound[:num_reqs].max().item())
attn_metadata = build_attn_metadata(
attn_groups=attn_groups,
num_reqs=num_reqs,
num_tokens=num_tokens,
query_start_loc_gpu=input_batch.query_start_loc,
query_start_loc_cpu=query_start_loc_cpu,
max_query_len=max_query_len,
seq_lens=input_batch.seq_lens,
max_seq_len=max_seq_len,
block_tables=block_tables,
slot_mappings=slot_mappings,
kv_cache_config=kv_cache_config,
seq_lens_cpu_upper_bound=seq_lens_cpu_upper_bound,
dcp_local_seq_lens=input_batch.dcp_local_seq_lens,
model_specific_attn_metadata=enc_dec_attn_metadata,
for_cudagraph_capture=for_capture,
rswa_prefix_lens=input_batch.prompt_lens,
)
return attn_metadata
def _get_encoder_seq_lens(
self,
req_ids: list[str],
attn_groups: list[list[AttentionGroup]],
for_capture: bool,
num_reqs: int,
) -> dict[int, tuple[torch.Tensor, np.ndarray]]:
encoder_seq_lens = torch.zeros(num_reqs, dtype=torch.int32, pin_memory=True)
encoder_seq_lens_np = encoder_seq_lens.numpy()
if not for_capture:
# During normal execution, use actual encoder lengths.
for i, req_id in enumerate(req_ids):
mm_features = self.encoder_cache.mm_features.get(req_id, [])
encoder_seq_lens_np[i] = sum(
feature.mm_position.get_num_embeds() for feature in mm_features
)
else:
# During CUDA graph capture, use max encoder length so max_seqlen_k
# is captured with the correct value for cross-attention.
encoder_seq_lens_np[:] = self.max_encoder_len
self.encoder_seq_lens_gpu[:num_reqs].copy_(encoder_seq_lens, non_blocking=True)
self.encoder_seq_lens_gpu[num_reqs:].fill_(0)
encoder_seq_lens_gpu = self.encoder_seq_lens_gpu[:num_reqs]
seq_lens_by_group: dict[int, tuple[torch.Tensor, np.ndarray]] = {}
for kv_cache_group_idx, groups in enumerate(attn_groups):
has_cross_attn = any(
isinstance(attn_group.kv_cache_spec, CrossAttentionSpec)
for attn_group in groups
)
if has_cross_attn:
seq_lens_by_group[kv_cache_group_idx] = (
encoder_seq_lens_gpu,
encoder_seq_lens_np,
)
return seq_lens_by_group