Files
agentic-kvc/third_party/vllm/vllm/beam_search.py
Gahow Wang 445e491123 Add vLLM v0.18.1 source tree with KV transfer abort fix
third_party/vllm/ now tracked in git for direct patch management.
Based on vLLM v0.18.1 release with one patch applied:

  vllm/v1/core/sched/scheduler.py:
    Replace fatal assert with graceful skip when KV transfer callback
    arrives for an already-aborted request during PD disaggregated serving.

Future vLLM modifications should be made directly in third_party/vllm/
and committed normally. The patches/ directory is kept as documentation
of what changed from upstream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 00:30:38 +08:00

158 lines
5.0 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from dataclasses import dataclass
from vllm.inputs import EncoderDecoderInputs, TokenInputs, token_inputs
from vllm.inputs.data import DecoderInputs
from vllm.logprobs import Logprob
from vllm.lora.request import LoRARequest
from vllm.multimodal.inputs import MultiModalInputs, mm_inputs
@dataclass
class BeamSearchSequence:
"""A sequence for beam search.
It keeps track of the tokens and the log probability of the sequence.
The text field is optional and will only be filled when the sequence is
about to be returned to the user.
"""
orig_prompt: TokenInputs | MultiModalInputs | EncoderDecoderInputs
# NOTE: Tokens represents decoder tokens in the encoder / decoder case
tokens: list[int]
logprobs: list[dict[int, Logprob]]
lora_request: LoRARequest | None = None
cum_logprob: float = 0.0
text: str | None = None
finish_reason: str | None = None
stop_reason: int | str | None = None
def get_prompt(self):
prompt = self.orig_prompt
if prompt["type"] == "enc_dec":
return self._build_encoder_decoder_inputs(prompt)
# Handle decoder-only inputs
prompt_text = prompt.get("prompt")
cache_salt = prompt.get("cache_salt")
if prompt["type"] == "token":
return token_inputs(
self.tokens,
prompt=prompt_text,
cache_salt=cache_salt,
)
return mm_inputs(
prompt_token_ids=self.tokens,
mm_kwargs=prompt["mm_kwargs"],
mm_hashes=prompt["mm_hashes"],
mm_placeholders=prompt["mm_placeholders"],
prompt=prompt_text,
cache_salt=cache_salt,
)
def _build_encoder_decoder_inputs(
self, prompt: EncoderDecoderInputs
) -> EncoderDecoderInputs:
"""Rebuild the encoder-decoder inputs with the current beam search
sequence's tokens.
FIXME (alex) - the encoder multimodal cache is not properly wired up
yet, which means that currently we are running the encoder on every
new beam because num_computed_tokens is 0 on each new request. This
will be fixed once the cache is correctly implemented.
"""
dec_prompt = prompt["decoder_prompt"]
# Rebuild decoder prompt with updated tokens,
# but keep everything else the same.
new_dec_prompt: DecoderInputs
if dec_prompt["type"] == "multimodal":
new_dec_prompt = mm_inputs(
self.tokens,
mm_kwargs=dec_prompt["mm_kwargs"],
mm_hashes=dec_prompt["mm_hashes"],
mm_placeholders=dec_prompt["mm_placeholders"],
prompt=dec_prompt.get("prompt"),
cache_salt=dec_prompt.get("cache_salt"),
)
else:
new_dec_prompt = token_inputs(
self.tokens,
prompt=dec_prompt.get("prompt"),
cache_salt=dec_prompt.get("cache_salt"),
)
return EncoderDecoderInputs(
type="enc_dec",
encoder_prompt=prompt["encoder_prompt"],
decoder_prompt=new_dec_prompt,
)
@dataclass
class BeamSearchOutput:
"""The output of beam search.
It contains the list of the best beam search sequences.
The length of the list is equal to the beam width.
"""
sequences: list[BeamSearchSequence]
class BeamSearchInstance:
def __init__(
self,
prompt: TokenInputs | MultiModalInputs | EncoderDecoderInputs,
lora_request: LoRARequest | None = None,
logprobs: list[dict[int, Logprob]] | None = None,
**kwargs,
):
decoder_prompt = (
prompt if prompt["type"] != "enc_dec" else prompt["decoder_prompt"]
)
initial_tokens = decoder_prompt["prompt_token_ids"]
self.beams: list[BeamSearchSequence] = [
BeamSearchSequence(
orig_prompt=prompt,
tokens=initial_tokens,
logprobs=[] if logprobs is None else list(logprobs),
lora_request=lora_request,
**kwargs,
)
]
self.completed: list[BeamSearchSequence] = []
def get_beam_search_score(
tokens: list[int],
cumulative_logprob: float,
eos_token_id: int,
length_penalty: float = 1.0,
) -> float:
"""Calculate the beam search score with length penalty.
Adapted from
https://github.com/huggingface/transformers/blob/ccb92be23def445f2afdea94c31286f84b89eb5b/src/transformers/generation/beam_search.py#L938
"""
seq_len = len(tokens)
if tokens[-1] == eos_token_id:
seq_len -= 1
return cumulative_logprob / (seq_len**length_penalty)
def create_sort_beams_key_function(eos_token_id: int, length_penalty: float):
def sort_beams_key(x: BeamSearchSequence) -> float:
return get_beam_search_score(
x.tokens, x.cum_logprob, eos_token_id, length_penalty
)
return sort_beams_key